Skip to content

Latest commit

 

History

History
370 lines (294 loc) · 11.6 KB

L1 G5000.md

File metadata and controls

370 lines (294 loc) · 11.6 KB

微调模型自我认知

我的目标是把模型的自我认知的名字改为 黄大力 ! 🦾

1. 配环境

# 下载这个仓库,因为这里面有个微调数据集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

2. 准备微调数据:修改名字

2.1 探索微调数据集文件

一共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|>

2.2 运行文字替换脚本

脚本逻辑:

image

运行截图:

image

3. 启动微调!!!

3.1 准备训练的config文件:读懂一些配置

配置文件有以下内容:

  1. 基础设置部分
  • 使用的是 InternLM2 5-7B Chat 模型作为基础模型
  • 训练数据路径为 '/root/finetune/data/assistant_Tuner_change.jsonl'
  • 主要训练参数:
    • 最大序列长度: 2048
    • batch size: 1
    • 训练轮数(epochs): 3
    • 学习率: 2e-4
    • 预热比例: 3%
    • 每500步保存一次检查点,最多保存2个检查点
  1. 模型和分词器配置
  • 使用 AutoModelForCausalLM 和 AutoTokenizer
  • 模型量化配置:
    • 使用4比特量化(load_in_4bit=True)
    • 使用 float16 数据类型
  • LoRA配置:
    • rank(r)=64
    • alpha=16
    • dropout=0.1
  1. 数据集和数据加载配置
  • 使用自定义的数据集处理函数
  • 支持序列并行采样
  • 使用 InternLM2 的对话模板
  • 数据会被打包到最大长度
  1. 优化器和调度器配置
  • 使用 AdamW 优化器
  • 梯度裁剪最大范数为1
  • 混合精度训练(AmpOptimWrapper)
  • 学习率调度:
    • 开始使用线性预热(LinearLR)
    • 然后使用余弦退火(CosineAnnealingLR)
  1. 运行时配置
  • 包含多个训练钩子(Hooks):
    • 数据集信息钩子(DatasetInfoHook)
    • 评估对话钩子(EvaluateChatHook):每500步评估一次
    • 还有定时器、日志、参数调度、检查点等基础钩子
  • 使用NCCL作为分布式后端
  • 日志级别设置为INFO
  • 随机性配置:未固定随机种子

特别值得注意的是:

  1. 使用了LoRA技术进行高效微调
  2. 采用了 bitsandbytes 库提供的 4bit 量化方案来降低显存占用
  3. 内置了定期评估机制,这个机制会每训练500步就停下来,用两个预设的问题去测试模型,将回答记录到训练日志中,这样你可以通过阅读日志,观察模型在训练过程中生成能力的变化
  4. 使用了混合精度训练来提高训练效率
    • 部分计算用 float16 进行(比如前向传播),可以加快计算速度,减少显存占用
    • 关键的计算(比如权重更新)仍用 float32,保证数值稳定性
  5. 学习率调度策略比较标准,使用了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) 的关键参数:

  1. LoRA 的基本原理
  • 不直接更新原始模型的权重矩阵
  • 而是用两个小矩阵的乘积来表示权重的更新:W = W₀ + BA
    • W₀ 是原始权重(保持冻结)
    • B 是一个 d×r 的矩阵
    • A 是一个 r×k 的矩阵
    • r 就是我们配置中的 rank
  1. rank (r=64)
  • r 决定了低秩矩阵的维度
  • r 越大:
    • 可以学习更复杂的适应性变化
    • 需要更多的显存和计算资源
    • 训练参数量 = 2 × r × 原始矩阵维度
  • r 越小:
    • 参数更少,训练更快
    • 但可能难以学习复杂的任务
  • r=64 是一个相对较大的值,表明这个微调任务可能比较复杂
  1. alpha (lora_alpha=16)
  • alpha 是缩放因子,用于调节 LoRA 更新的幅度
  • 实际的 LoRA 更新计算公式:W = W₀ + (alpha/r)BA
  • alpha/r 决定了更新的强度:
    • 这里是 16/64 = 0.25
  • alpha 越大:
    • 更新强度越大
    • 模型变化可能更显著
  • alpha 越小:
    • 更新更保守
    • 更接近原始模型的行为
  1. 实践建议
  • r 和 alpha 的比值通常在 0.25 左右(这里正好是 16/64 = 0.25)
  • 如果觉得模型变化太大,可以:
    • 减小 alpha
    • 或增大 r
  • 如果觉得模型学习不够,可以:
    • 增大 alpha
    • 或减小 r

这个配置(r=64, alpha=16)是一个相对激进的设置,说明:

  1. 希望模型能学习到较大的变化
  2. 任务可能需要较强的适应性调整
  3. 有足够的计算资源支持较大的 rank

你可以根据实际需求调整这些参数:

  • 如果显存不够,可以减小 r
  • 如果觉得模型变化太大,可以减小 alpha
  • 如果模型学习效果不够好,可以适当增大这些值

  1. 每个训练样本的处理:
  • 每条JSON会被转换成一段连续的文本
  • 这段文本会被tokenizer转换成token ids
  • 如果长度不足2048,会用padding token补齐
  • 如果超过2048,可能会被截断

所以训练过程是:

  1. 读一条JSON
  2. 转成连续文本
  3. 转成token ids
  4. padding到2048长度
  5. 送入模型训练

就像一条条"定长"的数据流一样被模型消化。

  1. 关于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

这样做的好处:

  1. 减少padding带来的计算浪费
  2. 更有效地利用计算资源
  3. 可以在同样时间内让模型看到更多真实数据

是不是很常见:

  • 是的,这是一种常见的优化
  • 特别在处理短文本数据时更有价值
  • 主流框架(如HuggingFace)都支持这种优化
  • 但需要注意:
    • 对于长文本或变长明显的数据集,这种优化效果可能不明显
    • 可能增加数据处理的复杂性
    • 需要确保模型能够正确处理打包后的数据

3.2 XTuner 启动~!

激动的心 颤抖的手

直接运行指令就行:

xtuner train ./config/internlm2_5_chat_7b_qlora_alpaca_e3_copy.py --deepspeed deepspeed_zero2 --work-dir ./work_dirs/assistTuner

我用教程默认的配置参数,微调这个7B模型。

可以看到:

  • 微调之前,让模型介绍自己,模型不会说自己的名字;
  • 显存占用在16GB左右;

image

可以看到500步的时候,效果就不错了:

image

一共跑了2个小时才跑完:

image

跑完之后,在目录下面可以看到训练结果的lora adapter文件,是一个大小为4KB的pth文件:

image

3.3 pth转bin

执行命令

xtuner convert pth_to_hf ./internlm2_5_chat_7b_qlora_alpaca_e3_copy.py /root/finetune/work_dirs/assistTuner/iter_1617.pth ./hf

image

3.4 lora和本体合并

执行命令

xtuner convert merge /root/finetune/models/internlm2_5-7b-chat ./hf ./merged --max-shard-size 2GB

image

4. WebUI 检验效果

image

附录:本节用到的xtuner指令

  1. xtuner copy-cfg

    • 命令用于复制一个内置的配置文件。
    • 需要两个参数:CONFIG 代表需要复制的配置文件名称,SAVE_PATH 代表复制的目标路径。
    • 用法示例:xtuner copy-cfg internlm2_chat_1_8b_qlora_alpaca_e3 .
  2. 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
  3. 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
  4. 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