所有tag为书生大模型的文档,连同本文档在内,为书生大模型实战营训练内容,文档中的内容并不局限于实战营本身,但算力平台均首选上海AI实验室开发的云端集成开发环境InternStudio开发手册InternStudio算力平台的相关内容可以点击链接跳转。

1.微调

1.1 什么是微调

模型微调是一种机器学习技术,它通过在预训练模型的基础上进行额外训练,使模型适应特定的任务或数据集。这种方法非常高效,因为它利用了已经在大规模数据上预训练的模型的知识,而无需从头开始训练一个全新的模型。

微调模型的步骤通常如下:

  • 选择预训练模型
    • 通常选用在大型数据集(如ImageNet、C4、COCO等)上训练好的通用模型。
    • 这些模型可以是图像领域的卷积神经网络(如ResNet、EfficientNet),自然语言处理领域的Transformer模型(如BERT、GPT)等。
  • 准备特定任务数据集
    • 将要应用的任务的数据集整理好,例如分类任务、生成任务、预测任务的数据。
  • 调整模型架构(可选)
    • 根据任务需求,可以添加或修改模型的最后几层。例如,在分类任务中,可能需要替换最后的全连接层以适应新的类别。
  • 训练模型
    • 以更小的学习率对模型进行进一步训练,使模型在特定数据集上优化性能。
    • 使用技术如冻结部分预训练层来保留原始模型的知识,逐步解冻以适应新任务。
  • 验证和测试
    • 在验证数据集上评估微调后的模型性能,避免过拟合。

1.2 微调的分类

从参数规模的角度,大模型的微调分成两条技术路线

  • 一条是对全量的参数,进行全量的训练,称之为叫全量微调FFT(Full Fine Tuning)。
  • 一条是只对部分的参数进行训练,这条路径叫PEFT(Parameter-Efficient Fine Tuning)

FFT的原理,就是用特定的数据,对大模型进行训练,最大的优点就是上述特定数据领域的表现会好很多。但FFT也会带来一些问题,影响比较大的问题,主要有以下两个:

  • 训练的成本会比较高,因为微调的参数量跟预训练的是一样的多的;
  • 一个是叫灾难性遗忘(Catastrophic Forgetting),用特定训练数据去微调可能会把这个领域的表现变好,但也可能会把原来表现好的别的领域的能力变差。

PEFT主要想解决的问题,就是FFT存在的上述两个问题,PEFT也是目前比较主流的微调方案。

1.3 主流的PEFT方法

主流的PEFT方法包括但不限于以下几种:

  • LoRA/Low-Rank Adaptation(低秩适配)
  • QLoRA/Quantized LoRA(量化低秩适配)
  • Adapter Tuning(适配器调整)
  • Prefix Tuning(前缀调整)
  • Prompt Tuning(提示调整)
  • P-Tuning(参数化提示调整)
  • P-Tuning v2(参数化提示调整第二版)
名称参数量核心内容优势劣势
LoRA极小(<1% FFT 参数量)通过对部分权重矩阵进行低秩分解,实现轻量化微调。高效、节省显存,适用于多任务,多场景。需要额外设计低秩参数,可能对某些任务有性能限制。
QLoRA极小(<<1% FFT 参数量)在LoRA基础上结合4-bit量化,进一步减少资源需求。超低硬件需求,支持大模型微调,训练速度快。量化可能导致精度损失,适配大模型时需要精心调参。
Adapter Tuning小(<5% FFT 参数量)在模型层之间插入可训练的适配器模块,保持主模型参数不变。模块化设计,可扩展性强,适合多任务和多语言环境。插入模块会增加推理时的延迟,性能提升依赖适配器设计质量。
Prefix Tuning极小(<1% FFT 参数量)插入可训练的前缀向量,调整生成模型的输入表示。高效轻量化,适合自然语言生成任务。对判别任务效果较差,前缀的优化过程可能不稳定。
Prompt Tuning极小(<0.1% FFT 参数量)学习优化的提示词,直接引导预训练模型完成任务。参数量极小,方法简单,适合大模型。表达能力有限,仅对大规模模型效果显著,对小模型表现较弱。
P-Tuning极小(<1% FFT 参数量)使用连续可训练的嵌入向量代替离散提示,提升表达能力。灵活高效,对分类与生成任务均有效。对模型适配性依赖较高,小规模模型的效果可能不理想。
P-Tuning v2极小(<1% FFT 参数量)为大型语言模型优化的 P-Tuning 方法,提升效率和适用性。高效广泛适用,适合分类、生成、理解等任务,优化大模型效果显著。对模型规模有依赖,训练仍需一定资源,任务泛化性可能受限。

具体的说明以后会额外出一篇文章。

这次的项目使用Xtuner框架应用LoRA来微调。

XTuner是一个高效、灵活、全能的轻量化大模型微调工具库:

  • 支持大语言模型LLM、多模态图文模型VLM的预训练及轻量级微调。XTuner支持在8GB显存下微调7B模型,同时也支持多节点跨设备微调更大尺度模型(70B+)。
  • 自动分发高性能算子(如FlashAttention、Triton kernels等)以加速训练吞吐。
  • 兼容DeepSpeed,轻松应用各种ZeRO训练优化策略。

2.环境构建

2.1 虚拟环境准备

cd ~
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

2.2 安装Xtuner

cd /root/Tutorial/docs/L1/XTuner
pip install -r requirements.txt

为了验证XTuner是否安装正确,可以使用命令打印配置文件:

xtuner list-cfg
image
image

3.数据准备

mkdir -p /root/finetune/data && cd /root/finetune/data
cp -r /root/Tutorial/data/assistant_Tuner.jsonl  /root/finetune/data

此时 finetune 文件夹下应该有如下结构:

finetune
├── data
    └── assistant_Tuner.jsonl
image
image

打开该change_script.py文件后将下面的内容复制进去。

import json
import argparse
from tqdm import tqdm

def process_line(line, old_text, new_text):
    # 解析 JSON 行
    data = json.loads(line)
    
    # 递归函数来处理嵌套的字典和列表
    def replace_text(obj):
        if isinstance(obj, dict):
            return {k: replace_text(v) for k, v in obj.items()}
        elif isinstance(obj, list):
            return [replace_text(item) for item in obj]
        elif isinstance(obj, str):
            return obj.replace(old_text, new_text)
        else:
            return obj
    
    # 处理整个 JSON 对象
    processed_data = replace_text(data)
    
    # 将处理后的对象转回 JSON 字符串
    return json.dumps(processed_data, ensure_ascii=False)

def main(input_file, output_file, old_text, new_text):
    with open(input_file, 'r', encoding='utf-8') as infile, \
         open(output_file, 'w', encoding='utf-8') as outfile:
        
        # 计算总行数用于进度条
        total_lines = sum(1 for _ in infile)
        infile.seek(0)  # 重置文件指针到开头
        
        # 使用 tqdm 创建进度条
        for line in tqdm(infile, total=total_lines, desc="Processing"):
            processed_line = process_line(line.strip(), old_text, new_text)
            outfile.write(processed_line + '\n')

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Replace text in a JSONL file.")
    parser.add_argument("input_file", help="Input JSONL file to process")
    parser.add_argument("output_file", help="Output file for processed JSONL")
    parser.add_argument("--old_text", default="尖米", help="Text to be replaced")
    parser.add_argument("--new_text", default="ArtemisYi", help="Text to replace with")
    args = parser.parse_args()

    main(args.input_file, args.output_file, args.old_text, args.new_text)

其中parser.add_argument("--new_text", default="ArtemisYi", help="Text to replace with")中的default可以调整为自己想要修改的名称。

此时data文件夹下应该有如下结构:

|-- /finetune/data/
    |-- assistant_Tuner.jsonl
    |-- assistant_Tuner_change.jsonl

修改完毕以后可以检查一下内容:

image
image

4.训练

4.1 准备模型

在InternStudio开发机中的已经提供了微调模型,可以直接软链接即可。如果使用其他算力平台需要另外下载。

本模型位于/root/share/new_models/Shanghai_AI_Laboratory/internlm2_5-7b-chat

mkdir /root/finetune/models
ln -s /root/share/new_models/Shanghai_AI_Laboratory/internlm2_5-7b-chat /root/finetune/models/internlm2_5-7b-chat

4.2 修改Config

获取官方写好的 config:

cd /root/finetune
mkdir ./config
cd config
xtuner copy-cfg internlm2_5_chat_7b_qlora_alpaca_e3 ./

修改下述部分:

#######################################################################
#                          PART 1  Settings                           #
#######################################################################
- pretrained_model_name_or_path = 'internlm/internlm2_5-7b-chat'
+ pretrained_model_name_or_path = '/root/finetune/models/internlm2_5-7b-chat'

- alpaca_en_path = 'tatsu-lab/alpaca'
+ alpaca_en_path = '/root/finetune/data/assistant_Tuner_change.jsonl'


evaluation_inputs = [
-    '请给我介绍五个上海的景点', 'Please tell me five scenic spots in Shanghai'
+    '请介绍一下你自己', 'Please introduce yourself'
]

#######################################################################
#                      PART 3  Dataset & Dataloader                   #
#######################################################################
alpaca_en = dict(
    type=process_hf_dataset,
-   dataset=dict(type=load_dataset, path=alpaca_en_path),
+   dataset=dict(type=load_dataset, path='json', data_files=dict(train=alpaca_en_path)),
    tokenizer=tokenizer,
    max_length=max_length,
-   dataset_map_fn=alpaca_map_fn,
+   dataset_map_fn=None,
    template_map_fn=dict(
        type=template_map_fn_factory, template=prompt_template),
    remove_unused_columns=True,
    shuffle_before_pack=True,
    pack_to_max_length=pack_to_max_length,
    use_varlen_attn=use_varlen_attn)

除此之外,还可以对一些重要的参数进行调整,包括学习率(lr)、训练的轮数(max_epochs)等等。

常用的参数介绍如下:

参数名解释
data_path数据路径或 HuggingFace 仓库名
max_length单条数据最大 Token 数,超过则截断
pack_to_max_length是否将多条短数据拼接到 max_length,提高 GPU 利用率
accumulative_counts梯度累积,每多少次 backward 更新一次参数
sequence_parallel_size并行序列处理的大小,用于模型训练时的序列并行
batch_size每个设备上的批量大小
dataloader_num_workers数据加载器中工作进程的数量
max_epochs训练的最大轮数
optim_type优化器类型,例如 AdamW
lr学习率
betas优化器中的 beta 参数,控制动量和平方梯度的移动平均
weight_decay权重衰减系数,用于正则化和避免过拟合
max_norm梯度裁剪的最大范数,用于防止梯度爆炸
warmup_ratio预热的比例,学习率在这个比例的训练过程中线性增加到初始学习率
save_steps保存模型的步数间隔
save_total_limit保存的模型总数限制,超过限制时删除旧的模型文件
prompt_template模板提示,用于定义生成文本的格式或结构
............

如果想充分利用显卡资源,可以将max_lengthbatch_size这两个参数调大。但需要注意的是,在训练chat模型时调节参数batch_size有可能会影响对话模型的效果。

4.3 启动微调

当准备好了所有内容,只需要将使用xtuner train命令令即可开始训练。

xtuner train命令用于启动模型微调进程。该命令需要一个参数:CONFIG用于指定微调配置文件。这里我们修改好的配置文件internlm2_5_chat_7b_qlora_alpaca_e3_copy.py

训练过程中产生的所有文件,包括日志、配置文件、检查点文件、微调后的模型等,默认保存在work_dirs目录下,也可以通过添加--work-dir指定特定的文件保存位置。--deepspeed则为使用deepspeed,deepspeed可以节约显存。

cd /root/finetune
conda activate xtuner-env
xtuner train ./config/internlm2_5_chat_7b_qlora_alpaca_e3_copy.py --deepspeed deepspeed_zero2 --work-dir ./work_dirs/assistTuner

接下来就是耐心等待,微调过程中还可以看到一些信息,例如轮次Iter和损失loss

12/10 15:28:29 - mmengine - INFO - Iter(train) [780/939]  lr: 1.4840e-05  eta: 0:12:12  time: 4.5796  data_time: 0.0140  memory: 11730  loss: 0.2370
12/10 15:29:15 - mmengine - INFO - Iter(train) [790/939]  lr: 1.3083e-05  eta: 0:11:26  time: 4.5687  data_time: 0.0100  memory: 11730  loss: 0.2500
12/10 15:30:01 - mmengine - INFO - Iter(train) [800/939]  lr: 1.1430e-05  eta: 0:10:39  time: 4.5588  data_time: 0.0116  memory: 11730  loss: 0.2193
12/10 15:30:48 - mmengine - INFO - Iter(train) [810/939]  lr: 9.8817e-06  eta: 0:09:54  time: 4.7273  data_time: 0.0144  memory: 11730  loss: 0.2068
12/10 15:31:33 - mmengine - INFO - Iter(train) [820/939]  lr: 8.4409e-06  eta: 0:09:07  time: 4.5511  data_time: 0.0130  memory: 11730  loss: 0.2250
12/10 15:32:20 - mmengine - INFO - Iter(train) [830/939]  lr: 7.1089e-06  eta: 0:08:21  time: 4.6294  data_time: 0.0143  memory: 11730  loss: 0.2308
12/10 15:33:06 - mmengine - INFO - Iter(train) [840/939]  lr: 5.8874e-06  eta: 0:07:35  time: 4.6024  data_time: 0.0092  memory: 11730  loss: 0.2344
12/10 15:33:52 - mmengine - INFO - Iter(train) [850/939]  lr: 4.7778e-06  eta: 0:06:49  time: 4.5943  data_time: 0.0163  memory: 11730  loss: 0.2197
12/10 15:34:37 - mmengine - INFO - Iter(train) [860/939]  lr: 3.7814e-06  eta: 0:06:03  time: 4.5849  data_time: 0.0122  memory: 11730  loss: 0.2687
12/10 15:35:23 - mmengine - INFO - Iter(train) [870/939]  lr: 2.8995e-06  eta: 0:05:17  time: 4.5491  data_time: 0.0170  memory: 11730  loss: 0.2202
12/10 15:36:09 - mmengine - INFO - Iter(train) [880/939]  lr: 2.1330e-06  eta: 0:04:31  time: 4.5675  data_time: 0.0121  memory: 11730  loss: 0.2414
12/10 15:36:55 - mmengine - INFO - Iter(train) [890/939]  lr: 1.4828e-06  eta: 0:03:45  time: 4.6092  data_time: 0.0168  memory: 11730  loss: 0.1916
12/10 15:37:41 - mmengine - INFO - Iter(train) [900/939]  lr: 9.4987e-07  eta: 0:02:59  time: 4.6338  data_time: 0.0240  memory: 11730  loss: 0.2071
12/10 15:38:27 - mmengine - INFO - Iter(train) [910/939]  lr: 5.3467e-07  eta: 0:02:13  time: 4.6206  data_time: 0.0148  memory: 11730  loss: 0.2238
12/10 15:39:14 - mmengine - INFO - Iter(train) [920/939]  lr: 2.3775e-07  eta: 0:01:27  time: 4.6329  data_time: 0.0139  memory: 11730  loss: 0.2395
12/10 15:40:00 - mmengine - INFO - Iter(train) [930/939]  lr: 5.9455e-08  eta: 0:00:41  time: 4.6166  data_time: 0.0140  memory: 11730  loss: 0.2418

4.4 权重转换

模型转换的本质其实就是将原本使用Pytorch训练出来的模型权重文件转换为目前通用的 HuggingFace格式文件,那么可以通过以下命令来实现一键转换。

可以使用xtuner convert pth_to_hf命令来进行模型格式转换。

xtuner convert pth_to_hf命令用于进行模型格式转换。该命令需要三个参数:CONFIG表示微调的配置文件,PATH_TO_PTH_MODEL表示微调的模型权重文件路径,即要转换的模型权重,SAVE_PATH_TO_HF_MODEL表示转换后的HuggingFace格式文件的保存路径。

除此之外,我们其实还可以在转换的命令中添加几个额外的参数,包括:

参数名解释
--fp32代表以fp32的精度开启,假如不输入则默认为fp16
--max-shard-size {GB}代表每个权重文件最大的大小(默认为2GB)
cd /root/finetune/work_dirs/assistTuner

conda activate xtuner-env

# 先获取最后保存的一个pth文件
pth_file=`ls -t /root/finetune/work_dirs/assistTuner/*.pth | head -n 1 | sed 's/:$//'`
export MKL_SERVICE_FORCE_INTEL=1
export MKL_THREADING_LAYER=GNU
xtuner convert pth_to_hf ./internlm2_5_chat_7b_qlora_alpaca_e3_copy.py ${pth_file} ./hf

完成后的结构目录如下:

├── hf
│   ├── README.md
│   ├── adapter_config.json
│   ├── adapter_model.bin
│   └── xtuner_config.py
image
image

转换完成后,可以看到模型被转换为HuggingFace中常用的.bin格式文件,这就代表着文件成功被转化为HuggingFace格式了。

此时,hf文件夹即为我们平时所理解的所谓“LoRA模型文件”

可以简单理解:LoRA模型文件=Adapter

4.5 模型合并

对于LoRA或者QLoRA微调出来的模型其实并不是一个完整的模型,而是一个额外的层(Adapter),训练完的这个层最终还是要与原模型进行合并才能被正常的使用。

对于全量微调的模型(full)其实是不需要进行整合这一步的,因为全量微调修改的是原模型的权重而非微调一个新的Adapter,因此是不需要进行模型整合的。

在XTuner中提供了一键合并的命令xtuner convert merge,在使用前需要准备好三个路径,包括原模型的路径、训练好的Adapter层的(模型格式转换后的)路径以及最终保存的路径。

xtuner convert merge命令用于合并模型。该命令需要三个参数:LLM表示原模型路径,ADAPTER表示Adapter层的路径,SAVE_PATH表示合并后的模型最终的保存路径。

在模型合并这一步还有其他很多的可选参数,包括:

参数名解释
--max-shard-size {GB}代表每个权重文件最大的大小(默认为2GB)
--device {device_name}这里指的就是device的名称,可选择的有cuda、cpu和auto,默认为cuda即使用gpu进行运算
--is-clip这个参数主要用于确定模型是不是CLIP模型,假如是的话就要加上,不是就不需要添加
cd /root/finetune/work_dirs/assistTuner
conda activate xtuner-env

export MKL_SERVICE_FORCE_INTEL=1
export MKL_THREADING_LAYER=GNU
xtuner convert merge /root/finetune/models/internlm2_5-7b-chat ./hf ./merged --max-shard-size 2GB

模型合并完成后的目录结构如下:

├── merged
│   ├── README.md
│   ├── config.json
│   ├── configuration.json
│   ├── configuration_internlm2.py
│   ├── generation_config.json
│   ├── modeling_internlm2.py
│   ├── pytorch_model-00001-of-00008.bin
│   ├── pytorch_model-00002-of-00008.bin
│   ├── pytorch_model-00003-of-00008.bin
│   ├── pytorch_model-00004-of-00008.bin
│   ├── pytorch_model-00005-of-00008.bin
│   ├── pytorch_model-00006-of-00008.bin
│   ├── pytorch_model-00007-of-00008.bin
│   ├── pytorch_model-00008-of-00008.bin
│   ├── pytorch_model.bin.index.json
│   ├── special_tokens_map.json
│   ├── tokenization_internlm2.py
│   ├── tokenization_internlm2_fast.py
│   ├── tokenizer.json
│   ├── tokenizer.model
│   └── tokenizer_config.json
image
image

在模型合并完成后,就可以看到最终的模型和原模型文件夹非常相似,包括了分词器、权重文件、配置信息等等。

5.模型WebUI对话

微调完成后,可以再次运行xtuner_streamlit_demo.py脚本来观察微调后的对话效果。

将脚本中的模型路径修改为微调后的模型的路径。

cd ~/Tutorial/tools/L1_XTuner_code
image
image
# 直接修改脚本文件第33行
- model_name_or_path = "Shanghai_AI_Laboratory/internlm2_5-7b-chat"
+ model_name_or_path = "/root/finetune/work_dirs/assistTuner/merged"

然后可以直接启动应用。

conda activate xtuner-env

pip install streamlit==1.31.0
streamlit run /root/Tutorial/tools/L1_XTuner_code/xtuner_streamlit_demo.py

运行后,确保端口映射正常,如果映射已断开则需要重新做一次端口映射。

ssh -CNg -L 8501:127.0.0.1:8501 root@ssh.intern-ai.org.cn -p *****

最后,通过浏览器访问:http://127.0.0.1:8501(或者http://localhost:8501/)来进行对话了。

image
image

可以看到模型自我认知已经成功修改了。