我的目标是把模型的自我认知的名字改为 黄大力
! 🦾
# 下载这个仓库,因为这里面有个微调数据集assistant_Tuner.jsonl
git clone https://github.com/InternLM/Tutorial.git -b camp4
mkdir -p /root/finetune && cd /root/finetune
# 创建虚拟环境并激活
conda create -n xtuner-env python=3.10 -y
conda activate xtuner-env
# 下载xtuner源码,安装
git clone https://github.com/InternLM/xtuner.git
cd /root/finetune/xtuner
pip install -e '.[all]'
# 下载其他依赖
pip install torch==2.4.1 torchvision==0.19.1 torchaudio==2.4.1 --index-url https://download.pytorch.org/whl/cu121
pip install transformers==4.39.0
一共1500条数据。
数据结构分析:
{
"conversation": [{
"system": "Role: 黄大力的智能助手...", # 系统提示词
"input": {
"conversation": [{ # 历史对话
"system": "...",
"input": "xxx",
"output": "xxx"
}]
},
"output": "我是黄大力的智能助手..." # 当前回复
}]
}
- 每个样本中外层的system和内层conversation中的system是完全一致的,都包含了完整的角色设定。这看起来是数据结构的冗余。
- 有些我不理解的地方:
- 内层conversation中的input和output全都是xxx,没有实际内容;
- 只有output没有input,谁问它了?
我猜测最可能会被拼接成:
<|im_start|>system
Role: 黄大力的智能助手...
<|im_end|>
<|im_start|>user
xxx
<|im_end|>
<|im_start|>assistant
xxx
<|im_end|>
<|im_start|>user
{当前用户输入}
<|im_end|>
<|im_start|>assistant
我是黄大力的智能助手...
<|im_end|>
脚本逻辑:
运行截图:
配置文件有以下内容:
- 基础设置部分:
- 使用的是 InternLM2 5-7B Chat 模型作为基础模型
- 训练数据路径为 '/root/finetune/data/assistant_Tuner_change.jsonl'
- 主要训练参数:
- 最大序列长度: 2048
- batch size: 1
- 训练轮数(epochs): 3
- 学习率: 2e-4
- 预热比例: 3%
- 每500步保存一次检查点,最多保存2个检查点
- 模型和分词器配置:
- 使用 AutoModelForCausalLM 和 AutoTokenizer
- 模型量化配置:
- 使用4比特量化(load_in_4bit=True)
- 使用 float16 数据类型
- LoRA配置:
- rank(r)=64
- alpha=16
- dropout=0.1
- 数据集和数据加载配置:
- 使用自定义的数据集处理函数
- 支持序列并行采样
- 使用 InternLM2 的对话模板
- 数据会被打包到最大长度
- 优化器和调度器配置:
- 使用 AdamW 优化器
- 梯度裁剪最大范数为1
- 混合精度训练(AmpOptimWrapper)
- 学习率调度:
- 开始使用线性预热(LinearLR)
- 然后使用余弦退火(CosineAnnealingLR)
- 运行时配置:
- 包含多个训练钩子(Hooks):
- 数据集信息钩子(DatasetInfoHook)
- 评估对话钩子(EvaluateChatHook):每500步评估一次
- 还有定时器、日志、参数调度、检查点等基础钩子
- 使用NCCL作为分布式后端
- 日志级别设置为INFO
- 随机性配置:未固定随机种子
特别值得注意的是:
- 使用了LoRA技术进行高效微调
- 采用了 bitsandbytes 库提供的 4bit 量化方案来降低显存占用
- 内置了定期评估机制,这个机制会每训练500步就停下来,用两个预设的问题去测试模型,将回答记录到训练日志中,这样你可以通过阅读日志,观察模型在训练过程中生成能力的变化
- 使用了混合精度训练来提高训练效率
- 部分计算用 float16 进行(比如前向传播),可以加快计算速度,减少显存占用
- 关键的计算(比如权重更新)仍用 float32,保证数值稳定性
- 学习率调度策略比较标准,使用了warmup+余弦退火的组合
让我解释一下 LoRA (Low-Rank Adaptation) 的这些关键参数:
lora=dict(
type=LoraConfig,
r=64, # rank
lora_alpha=16, # alpha
lora_dropout=0.1,
bias='none',
task_type='CAUSAL_LM')
解释 LoRA (Low-Rank Adaptation) 的关键参数:
- LoRA 的基本原理:
- 不直接更新原始模型的权重矩阵
- 而是用两个小矩阵的乘积来表示权重的更新:W = W₀ + BA
- W₀ 是原始权重(保持冻结)
- B 是一个 d×r 的矩阵
- A 是一个 r×k 的矩阵
- r 就是我们配置中的 rank
- rank (r=64):
- r 决定了低秩矩阵的维度
- r 越大:
- 可以学习更复杂的适应性变化
- 需要更多的显存和计算资源
- 训练参数量 = 2 × r × 原始矩阵维度
- r 越小:
- 参数更少,训练更快
- 但可能难以学习复杂的任务
- r=64 是一个相对较大的值,表明这个微调任务可能比较复杂
- alpha (lora_alpha=16):
- alpha 是缩放因子,用于调节 LoRA 更新的幅度
- 实际的 LoRA 更新计算公式:W = W₀ + (alpha/r)BA
- alpha/r 决定了更新的强度:
- 这里是 16/64 = 0.25
- alpha 越大:
- 更新强度越大
- 模型变化可能更显著
- alpha 越小:
- 更新更保守
- 更接近原始模型的行为
- 实践建议:
- r 和 alpha 的比值通常在 0.25 左右(这里正好是 16/64 = 0.25)
- 如果觉得模型变化太大,可以:
- 减小 alpha
- 或增大 r
- 如果觉得模型学习不够,可以:
- 增大 alpha
- 或减小 r
这个配置(r=64, alpha=16)是一个相对激进的设置,说明:
- 希望模型能学习到较大的变化
- 任务可能需要较强的适应性调整
- 有足够的计算资源支持较大的 rank
你可以根据实际需求调整这些参数:
- 如果显存不够,可以减小 r
- 如果觉得模型变化太大,可以减小 alpha
- 如果模型学习效果不够好,可以适当增大这些值
- 每个训练样本的处理:
- 每条JSON会被转换成一段连续的文本
- 这段文本会被tokenizer转换成token ids
- 如果长度不足2048,会用padding token补齐
- 如果超过2048,可能会被截断
所以训练过程是:
- 读一条JSON
- 转成连续文本
- 转成token ids
- padding到2048长度
- 送入模型训练
就像一条条"定长"的数据流一样被模型消化。
- 关于pack_to_max_length=True:
- 如果单条数据转换后太短
- 可能会将多条短数据打包在一起凑成2048长度
假设我们有3条原始数据(简化表示):
# 数据1 (转token后400长)
"""
<|im_start|>system
我是助手...
<|im_end|>
<|im_start|>user
问题1
<|im_end|>
<|im_start|>assistant
回答1
<|im_end|>
"""
# 数据2 (转token后300长)
"""
<|im_start|>system
我是助手...
<|im_end|>
<|im_start|>user
问题2
<|im_end|>
<|im_start|>assistant
回答2
<|im_end|>
"""
# 数据3 (转token后350长)
"""
<|im_start|>system
我是助手...
<|im_end|>
<|im_start|>user
问题3
<|im_end|>
<|im_start|>assistant
回答3
<|im_end|>
"""
当 pack_to_max_length=True:
# 会尝试将短数据拼在一起
[数据1 + 数据2 + 数据3 + padding(998个token)] -> 2048长
这样做的好处:
- 减少padding带来的计算浪费
- 更有效地利用计算资源
- 可以在同样时间内让模型看到更多真实数据
是不是很常见:
- 是的,这是一种常见的优化
- 特别在处理短文本数据时更有价值
- 主流框架(如HuggingFace)都支持这种优化
- 但需要注意:
- 对于长文本或变长明显的数据集,这种优化效果可能不明显
- 可能增加数据处理的复杂性
- 需要确保模型能够正确处理打包后的数据
激动的心 颤抖的手
直接运行指令就行:
xtuner train ./config/internlm2_5_chat_7b_qlora_alpaca_e3_copy.py --deepspeed deepspeed_zero2 --work-dir ./work_dirs/assistTuner
我用教程默认的配置参数,微调这个7B模型。
可以看到:
- 微调之前,让模型介绍自己,模型不会说自己的名字;
- 显存占用在16GB左右;
可以看到500步的时候,效果就不错了:
一共跑了2个小时才跑完:
跑完之后,在目录下面可以看到训练结果的lora adapter文件,是一个大小为4KB的pth文件:
执行命令
xtuner convert pth_to_hf ./internlm2_5_chat_7b_qlora_alpaca_e3_copy.py /root/finetune/work_dirs/assistTuner/iter_1617.pth ./hf
执行命令
xtuner convert merge /root/finetune/models/internlm2_5-7b-chat ./hf ./merged --max-shard-size 2GB
-
xtuner copy-cfg
- 命令用于复制一个内置的配置文件。
- 需要两个参数:
CONFIG
代表需要复制的配置文件名称,SAVE_PATH
代表复制的目标路径。 - 用法示例:
xtuner copy-cfg internlm2_chat_1_8b_qlora_alpaca_e3 .
-
xtuner train
- 命令用于启动模型微调进程!
- 需要一个参数:
CONFIG
用于指定微调配置文件。 - 训练过程中产生的所有文件,包括日志、配置文件、检查点文件、微调后的模型等,默认保存在
work_dirs
目录下,也可以通过添加--work-dir
指定特定的文件保存位置。 --deepspeed
则为使用 deepspeed, deepspeed 可以节约显存。- 用法示例:
xtuner train ./internlm2_chat_1_8b_qlora_alpaca_e3_copy.py --deepspeed deepspeed_zero2 --work-dir ./work_dirs/assistTuner
-
xtuner convert pth_to_hf
- 命令用于将PyTorch格式的模型文件转换为HuggingFace格式的模型。
- 需要三个参数:
CONFIG
表示微调的配置文件,PATH_TO_PTH_MODEL
表示微调的模型权重文件路径,SAVE_PATH_TO_HF_MODEL
表示转换后的HuggingFace格式文件的保存路径。 - 可以添加额外参数,如
--fp32
表示以fp32精度开启(默认为fp16),--max-shard-size {GB}
表示每个权重文件最大的大小(默认为2GB)。 - 用法示例:
xtuner convert pth_to_hf ./internlm2_chat_1_8b_qlora_alpaca_e3_copy.py ${pth_file} ./hf
-
xtuner convert merge
- 命令用于合并原始LLM模型和Adapter模型。
- 需要三个参数:
LLM
表示原模型路径,ADAPTER
表示Adapter层的路径,SAVE_PATH
表示合并后的模型最终的保存路径。 - 可以添加额外参数,如
--max-shard-size {GB}
表示每个权重文件最大的大小(默认为2GB),--device {device_name}
可选择的有cuda、cpu和auto,默认为cuda即使用GPU进行运算,--is-clip
这个参数主要用于确定模型是不是CLIP模型。 - 用法示例:
xtuner convert merge /root/InternLM/XTuner/Shanghai_AI_Laboratory/internlm2-chat-1_8b ./hf ./merged --max-shard-size 2GB