pubanswer

如何在 Mac Studio 上微调 Llama-2

CosmicFlame2024-06-19

这是一个使用 llama.cpp 在 Mac Studio 上微调 Llama-2 模型的端到端教程。只需三个步骤:

1. 构建 llama.cpp 并将 Llama-2 模型转换为 gguf 格式

git clone https://github.com/ggerganov/llama.cpp.git
cd llama.cpp
make -j

假设你的 Llama-2 7B 模型位于路径 ./models/llama-2-7b/ 下,然后运行:

python3 convert.py ./models/llama-2-7b/
./quantize ./models/llama-2-7b/ggml-model-f16.gguf ./models/llama-2-7b/ggml-model-q4_0.gguf q4_0

如果你没有 Llama-2 模型,可以参考其他文章下载。

2. 准备微调数据

在本教程中,我们将使用 TinyStories 数据集来微调 Llama-2 7B 模型。

下载数据集到文件夹 tinystories:

mkdir tinystories && cd tinystories
wget https://huggingface.co/datasets/roneneldan/TinyStories/resolve/main/TinyStories_all_data.tar.gz
tar xf TinyStories_all_data.tar.gz

你会得到一个包含 50 个 json 文件(data00.json — data49.json)的列表,每个文件都包含大量短篇儿童故事。

将一部分数据(data49.json)提取到 data49.txt 中:

./extract.sh data49.json

extract.sh 脚本如下:

#!/bin/bash

# Check for input argument
if [ "$#" -ne 1 ]; then
    echo "Usage: $0 <input_file.json>"
    exit 1
fi

input_file="$1"
output_file="${input_file%.json}.txt"

# Check if jq is installed
if ! command -v jq &> /dev/null; then
    echo "Please install 'jq' first."
    exit 1
fi

# Check if the input file exists
if [ ! -f "$input_file" ]; then
    echo "File '$input_file' not found!"
    exit 1
fi

# Extract stories and format them
jq -r '.[] | "<s>" + .story + "\n\n"' "$input_file" > "$output_file"
echo "Stories have been saved to $output_file"

文件 data49.txt 包含约 68000 个由 <s> 分隔的短篇故事,我们将使用它作为微调数据集。

3. 开始训练(需要运行 24 小时或更长时间)

./finetune --model-base ./models/llama-2-7b/ggml-model-q4_0.gguf --train-data tinystories/data49.txt --threads 26 --sample-start "<s>" --ctx 512

你将在终端中看到一长串日志,包括以下内容:

main: init model
print_params: n_vocab:   32000
print_params: n_ctx:     512
print_params: n_embd:    4096
print_params: n_ff:      11008
print_params: n_head:    32
print_params: n_head_kv: 32
print_params: n_layer:   32
...
main: total train_iterations 0
main: seen train_samples     0
main: seen train_tokens      0
main: completed train_epochs 0
...
tokenize_file: warning: found 942 samples (max length 1164) that exceed context length of 512. samples will be cut off.
tokenize_file: warning: found 66924 samples (min length 4) that are shorter than context length of 512.
tokenize_file: total number of samples: 67871
...
train_opt_callback: iter=     0 sample=1/67871 sched=0.000000 loss=0.000000 |->
train_opt_callback: iter=     1 sample=9/67871 sched=0.010000 loss=4.608573 dt=00:11:24 eta=2d 00:28:18 |->
train_opt_callback: iter=     2 sample=17/67871 sched=0.020000 loss=4.691439 dt=00:23:23 eta=4d 03:00:01 |>
...

一些关键信息解释

模型初始化参数

  • n_vocab: 词汇表大小设置为 32,000。
  • n_ctx: 上下文长度(最大序列长度)设置为 512。
  • n_embd: 嵌入大小(通常与模型的大小/复杂性有关)为 4,096。
  • n_ff: Transformer 中前馈网络的大小为 11,008。
  • n_head: 注意力头的数量为 32。更多的头允许模型同时关注输入的不同部分。
  • n_layer: Transformer 层的数量为 32。更多的层通常意味着模型更复杂,可以捕捉更复杂的模式。

LoRA(低秩适应)模型

  • lora_size, opt_size, input_size, 和 compute_size 表示训练过程中各部分的内存占用,提供了关于所用计算资源的信息。
  • total number of samples: 训练数据中的总样本数为 67,871。
  • iter: 表示“迭代”。每次迭代代表通过神经网络模型的一批数据的一次前向和后向传播。
  • sample: 告诉你当前处理的是总批次中的哪个样本。
  • sched: 学习率调度器的当前速率。学习率调度是用于在训练期间调整学习率的策略,可以导致更高效和更稳定的训练。
  • loss: 表示当前的损失值。损失值衡量模型预测与实际数据的匹配程度。较低的损失值通常更好,表示模型做出了准确的预测。损失从 0 开始,然后稳定在大约 4 的值附近。监控此值及其趋势对于确保模型在学习非常重要。
  • dt(该迭代所花费的时间)随着模型处理更多批次逐渐稳定在约28分钟左右。
  • eta(估计剩余训练时间)最初增加,然后减少,因为模型可能变得更高效,或者估计过程变得更准确。

检查点: 每10次迭代会保存一个微调模型(LoRA 模型)的检查点。模型以 .gguf 为扩展名格式保存。

如何使用微调后的模型?

要运行微调后的模型,可以使用以下命令:

./main --model ./models/llama-2-7b/ggml-model-q4_0.gguf --lora ggml-lora-LATEST-f32.gguf --prompt "Can you please write a children's story with 200 words about father and son and friendship and bravery?"

注意,使用带有量化模型的 lora adapter 可能会导致质量下降。建议使用 f16 或 f32 基础模型与 --lora-base 一起使用。

以下是带有 lora-base 的完整命令:

./main --model ./models/llama-2-7b/ggml-model-q4_0.gguf --lora ggml-lora-LATEST-f32.gguf --lora-base ./models/llama-2-7b/ggml-model-f16.gguf --prompt "Can you please write a children's story with 200 words about father and son and friendship and bravery?"

以下是一个使用 lora-base llama-2–7b/ggml-model-f16.gguf 的输出示例:

Can you please write a children's story with 200 words about father and son and friendship and bravery?
...My Dad and I...

微调时间多长?

每次迭代大约需要30分钟,如果让它运行100次迭代,大约需要50小时才能完成。但你可以配置它运行任意 N 次迭代,使用 --adam-iter N

在我们的设置中,它默认运行256次迭代,因此日志显示在第13次迭代时,损失为1.580555,预计剩余训练时间为4天23小时43分05秒(约五天)。

train_opt_callback: iter=    12 sample=97/67871 sched=0.120000 loss=1.580555 dt=00:29:26 eta=4d 23:43:05 |------------------------------->

注意,微调时间也取决于训练数据中的样本数量和上下文窗口大小。如果你使用较少数量的样本(例如,1000 而不是约68000个故事),则可能需要更短的时间,大约48小时:

iter=     1 sample=9/951 sched=0.010000 loss=4.199916 dt=00:10:55 eta=1d 22:26:21 |->