全部文章

PEFT主流高效微调方法介绍&实战

前言

微调是一种在特定于特定任务的新数据集上进一步训练(或微调)预训练模型的方法。该技术涉及根据新数据调整模型所有层的权重。它允许模型专门满足细微的任务,并且通常会为专业应用程序带来更高的性能。

目录

目录

一 主流微调方法
 1 Full Fine-Tuning
 2 Prefix-Tuning
 3 Prompt Tuning
 4 P-Tuning v1
 5 P-Tuning v2
 6 LoRA & QLoRA
二 DeepSpeed原理浅析
 1 ZeRO-Offload
 2 ZeRO-Infinity
 3 ZeRO-1、2、3
三 基于ChatGLM3-6b进行P-Tuning v2微调实战
 1 微调环境准备
 2 微调数据下载与预处理
 3 执行P-Tuning v2微调
 4 使用微调后的模型
 5 微调效果评测

微调是什么

微调大型语言模型 (LLM) 涉及在特定数据集上调整预训练模型,以提高特定任务的性能。此过程在一般预训练结束后开始。用户为模型提供更集中的数据集,其中可能包括特定于行业的术语或以任务为中心的交互,目的是帮助模型为特定使用案例生成更相关的响应。

微调允许模型调整其预先存在的权重和偏差,以更好地适应特定问题。这提高了输出的准确性和相关性,使 LLM 在实际、专业的应用中比受过广泛训练的同类产品更有效。虽然微调可能是高度计算密集型的,但参数高效微调 (PEFT) 等新技术使其效率更高,甚至可以在消费级显卡上运行。微调既可以在开源 LLM(如 Meta LLaMA 和 Mistral 模型)上执行,也可以在某些商业 LLM 上执行,前提是模型的开发人员提供了此功能。例如,OpenAI 允许对 GPT-3.5 和 GPT-4 进行线上微调。

  • 微调方法分类

在微调的方法上分类可分为全参数微调(Full Fine-Tuning)和部分参数微调(Partial Fine-Tuning),其中全参数微调指的是对整个模型进行微调,该预训练模型的所有层和参数都会被更新和优化。全参数微调的方法适用于任务和预训练模型之间存在较大差异的情况,或者任务需要模型具有高度灵活性和自适应能力的情况,但是这种方法会消耗大量的计算资源和时间,对成本有较高的要求。部分微调指的是在微调过程中只更新模型的顶层或少数几层,其余的层和权重固定不变。这样可以节省大量的计算资源,但是效果会随着数据量的大小、与原模型的相似程度以及微调方法而定。

PEFT (Parameter-Efficient Fine-Tuning)是部分微调的一种具体实现方式,它通过引入不同的技术,进一步提高了微调效率,通常在大语言模型(如 GPT、BERT 等)的微调中广泛应用。PEFT 的目标是在保持模型性能的前提下,极大减少需要微调的参数量,避免了对整个模型进行大规模训练,从而降低资源消耗。同时,也能实现与全量参数微调相当的性能。参数高效微调方法甚至在某些情况下比全量微调效果更好,可以更好地泛化到域外场景。

  • 使用高效微调方法的原因

  1. 大模型参数量过大,训练成本高。

  2. 通过自有数据提升大模型在特定领域的能力。

  3. 在特定服务中使用大模型的能力。

  4. 保护企业数据安全。

一、主流微调方法

1. Prefix-Tuning

Prefix-Tuning指的是在微调模型的过程中固定语言模型的参数,仅优化一个小的连续的特定任务向量(称为prefix)。prefix-tuning在充分数据下获得了与微调相当的性能,在小数据集上优于微调。以下是 Prefix-Tuning论文地址:

https://arxiv.org/abs/2101.00190

在Prefix Tuning方法提出之前,相关工作主要集中在手动设计离散模板或自动化搜索离散模板。手动设计的模板对模型的最终性能极其敏感,哪怕仅仅增加、删除一个词或调整词的位置,都会对结果产生显著影响。而自动化搜索离散模板的过程通常成本较高,且搜索出的离散token组合可能并非最优解。此外,传统的微调范式要求针对每个下游任务对预训练模型进行独立微调,并保存相应的微调后模型权重,这不仅增加了存储空间的占用,也导致微调过程耗时较长。

传统的微调范式Fine-turning会利用预训练模型去对不同的下游任务进行微调,对每个任务都要保存一份微调后的模型权重。比如下图展示的三个不同任务的Transformer模型,分别用来做翻译、摘要和将格式转化(table-to-text)。每个任务都有自己的微调模型,这意味着模型的所有权重都在微调过程中针对特定任务进行了更新。这种方法通常需要大量的数据和计算资源,因为整个模型都在学习任务特定的知识。

基于上述两个因素,Prefix-tuning 就提出了一种不同的微调策略,对基于Transformers结构的模型,它会将特定的前缀添加到输入序列的开始部分,相当于任务特定的提示,可以是一组固定的词或是可训练的嵌入向量。这样做的效果相较于传统的离散token微调范式所消耗的成本更小,优化效果更好。

该方法其实和构造Prompt类似,只是Prompt是人为构造的“显式”的提示,并且无法更新参数,而Prefix则是可以学习的“隐式”的提示。但是这个Prefix 并不是一些明确的单词,比如对于文本摘要任务来说,我们添加 this is summarization(明确指出这是一个摘要的任务),相反,这个prefix加的是一些隐式的Token。这里就需要了解两个概念:

  • Hard Prompt:也称离散Prompt,是一个实际的文本字符串(自然语言,人工可读),通常由中文或英文词汇组成;

  • Soft Prompt:也称连续Prompt,通常是在向量空间优化出来的提示,通过梯度搜索之类的方式进行优化;

在Hoft Promot中,提示语的变化对模型最终的性能特别敏感,加一个词、少一个词或者变动位置都会造成比较大的变化。成本比较高,并且效果不太好。显然:Prefix Tuning属于Soft prompt。也就是我们学习调整的就是这部分的参数,从而达到微调的目的。与调整约3.6%LM参数的工作相比,Prefix Tuning的方法进一步减少了30倍的任务特定参数,仅调整了0.1%,同时保持表到文本任务的可比性能。

在Soft Prompt中,LM 的模型权重被冻结,并且有单独的可学习张量与模型权重连接,并针对特定的下游任务进行训练。我们希望以最佳方式学习能够给我们带来最佳结果的提示。所有方法都适用于足够小的标记数据集。

Prefix Tuning针对不同的模型结构有设计不同的模式,以自回归的模型为例,不再使用token去作为前缀,而是直接使用参数作为前缀(粉红色部分),并只优化这部分,冻结主体部分的参数。同时,为了防止直接更新Prefix的参数导致训练不稳定的情况,他们在Prefix层前面加了MLP结构(相当于将Prefix分解为更小维度的Input与MLP的组合后输出的结果),训练完成后,只保留Prefix的参数。

简单的说,Prefix Tuning训练一个上游前缀,它引导一个未修改的LM,因此,单个LM可以同时支持许多任务。在个性化的上下文中,任务对应于用户。Prefix Tuning将为每个用户设置一个单独的前缀,只训练该用户的数据,从而避免数据交叉污染。此外,基于前缀的体系结构使Prefix Tuning能够在单个批处理中处理来自多个用户/任务的示例,这是其他轻量级微调方法不可能实现的。

Encoder端增加前缀是为了引导输入部分的编码,Decoder 端增加前缀是为了引导后续token的生成。

Prefix-tuning 的优势在于它不需要调整模型的全部权重,而是通过在输入中添加前缀来调整模型的行为,这样可以节省大量的计算资源,同时使得一个单一的模型能够适应多种不同的任务。

2. Prompt Tuning

Prompt Tuning 方法可以看做是Prefix Tuning的简化版本,它给每个任务都定义了自己的Prompt,将真实的Tokens转化为可微的virtual token,并加入人工设计的锚字符(与任务高度相关的Token),拼接到数据上作为输出,但只在输入层加入Prompt tokens。它使用手动设计的提示,在few shot设置中适应不同的任务。以下是Prompt Tuning论文地址:

https://arxiv.org/pdf/2104.08691.pdf

Prompt Tuning通过在输入中添加特定的提示(prompt)来引导模型生成所需的输出。这些提示是可学习的,并在微调过程中进行优化,以提高模型在特定任务上的表现。

如图所示,传统的微调方法需要为每个下游任务创建整个预训练模型的任务特定副本,并且推理必须在不同的批次中进行。Prompt Tuning只需要为每个任务存储一个较小的任务特定提示,同时可以使用原始的预训练模型进行混合任务推理。Prompt Tuning可以通过反向传播更新参数来学习prompts,而不是人工设计prompts;同时冻结模型原始权重,只训练prompts参数,训练完以后,用同一个模型可以做多任务推理。

实验表明,随着大模型的参数量的增加,Prompt Tuning的方法会逼近全参数微调的结果。此外,Prompt token 的长度在20左右时的表现已经不错(超过20之后,提升Prompt token长度,对模型的性能提升不明显了),同样的,这个gap也会随着模型参数规模的提升而减小(即对于超大规模模型而言,即使 Prompt token 长度很短,对性能也不会有太大的影响)。

虽然Prompt Tuning和Prefix Tuning都涉及在输入数据中加入可学习的向量,但两者的策略和目的不一样:

  • Prompt Tuning:可学习向量(通常称为prompt tokens)旨在模仿自然语言提示的形式,它们被设计为引导模型针对特定任务生成特定类型的输出。这些向量通常被看作是任务指导信息的一部分,倾向于用更少量的向量模仿传统的自然语言提示。

  • Prefix Tuning:可学习前缀Prefix更多地用于提供输入数据的直接上下文信息,这些前缀作为模型内部表示的一部分,可以影响整个模型的行为。

下面的训练例子说明了两者的区别:

```
Prompt Tuning示例:

输入序列: "Prompt 1, Prompt2 | 这部电影令人振奋。"

问题: 评价这部电影的情感倾向。

答案: 模型需要预测情感倾向(例如“积极”)

提示: 无明确的外部提示,
 
充当引导模型的内部提示,因为这里的问题是隐含的,即判断文本中表达的情感倾向。
```
Prefix Tuning 示例:

输入序列: " Prefix1, Prefix 2 | I want to watch a movie."

问题: 根据前缀生成后续的自然语言文本。

答案: 模型生成的文本,如“that is exciting and fun.”

提示: 前缀本身提供上下文信息,没有单独的外部提示
```

该数据集任务为根据输入(content)生成一段广告词(summary),其数据格式如下:

{"content": "类型#上衣*风格#街头*图案#创意*衣样式#卫衣", "summary": "在这件卫衣上,BRAND-white集合了女性化的柔美还有不变的街头风采,<UNK><UNK>的向日葵花朵美丽的出现在胸前和背后,犹如暗<UNK>闪光的星星一般耀眼又充满着<UNK>的生命力,而后品牌标志性的logo<UNK>出现,呈现出将花束固定的效果,有趣极了,穿的不仅是服饰。更是新颖创意的载体。"}

我们需要修改成单轮对话的数据微调格式。可以直接在数据集所在的文件夹新建一个python脚本文件用来转化,代码内容如下:

import json
import time
 
def process_and_save_json(input_filepath, output_filepath):
    # 记录开始时间
    start_time = time.time()   
    with open(input_filepath, 'r', encoding='utf-8') as file:
        for line in file:
            data = json.loads(line.strip())
 
            user_data = {
                "role": "user",
                "content": data["content"]
            }
            assistant_data = {
                "role": "assistant",
                "content": data["summary"]
            }
            outfile = open(output_filepath, 'a', encoding='utf-8')
            json.dump({"conversations": [user_data, assistant_data]}, outfile, ensure_ascii=False)
            outfile.write('\n')
    # 记录结束时间
    end_time = time.time()
    elapsed_time = end_time - start_time
 
    print("耗时:", elapsed_time, "秒")
 
input_filepath = 'train.json'  # 传入处理前的json
output_filepath = 'train2.json'  # 处理后的保存位置
 
process_and_save_json(input_filepath, output_filepath)

执行后,数据格式如下:

{"conversations": [{"role": "user", "content": "类型#裙*裙长#半身裙"}, {"role": "assistant", "content": "这款百搭时尚的仙女半身裙,整体设计非常的飘逸随性,穿上之后每个女孩子都能瞬间变成小仙女啦。料子非常的轻盈,透气性也很好,穿到夏天也很舒适。"}]}

  • 转化结束后新文件名称是xx2.json文件,注意文件名称的修改或调用地址的确定。

3.3 执行P-Tuning v2微调

在正式微调之前可以,先通过配置文件了解以下对应的参数。P-Tuning v2的配置文件在相对路径chatglm3/finetune_demo/configs中,适当调节参数可以达到更合适的微调效果以及适配对应具体的显卡能力:

具体的参数含义如下:

data_config部分

  • train_file: 训练数据集的文件路径。

  • val_file: 验证数据集的文件路径。

  • test_file: 测试数据集的文件路径。

  • num_proc: 在加载数据时使用的进程数量。

  • max_input_length: 输入序列的最大长度。

  • max_output_length: 输出序列的最大长度。: 输出序列的最大长度。: 输出序列

training_args 部分

  • output_dir: 用于保存模型和其他输出的目录。

  • max_steps: 训练的最大步数。

  • max_steps: 训练的最大步数。

  • per_device_train_batch_size: 每个设备(如 GPU)的训练批次大小。

  • dataloader_num_workers: 加载数据时使用的工作线程数量。

  • remove_unused_columns: 是否移除数据中未使用的列。

  • save_strategy: 模型保存策略(例如,每隔多少步保存一次)。

  • save_steps: 每隔多少步保存一次模型。

  • log_level: 日志级别(如 info)。

  • logging_strategy: 日志记录策略。

  • logging_steps: 每隔多少步记录一次日志。

  • per_device_eval_batch_size: 每个设备的评估批次大小。

  • evaluation_strategy: 评估策略(例如,每隔多少步进行一次评估)。

  • eval_steps: 每隔多少步进行一次评估。

  • predict_with_generate: 是否使用生成模式进行预测。

generation_config 部分

  • max_new_tokens: 生成的最大新 token 数量。

peft_config 部分

  • peft_type: 使用的参数有效调整类型(如 LORA)。

  • task_type: 任务类型,这里是因果语言模型(CAUSAL_LM)。

  • P-TuningV2 参数:

    • num_virtual_tokens: 虚拟 token 的数量。


通过在命令行输入以下代码执行可以在单机多卡/多机多卡环境下运行,这是使用 deepspeed 作为加速方案的。

OMP_NUM_THREADS=1 torchrun --standalone --nnodes=1 --nproc_per_node=8  finetune_hf.py  data/AdvertiseGen/  /home/util/ARIS/chatglm3/chatglm3-6b  configs/ptuning_v2_.yaml
  • OMP_NUM_THREADS=1:这是一个环境变量,控制OpenMP库在多线程运算中的并行线程数。将其设置为1表示每个进程只使用一个线程进行计算,避免过多的线程争用资源,可能会提升分布式计算的效率。

  • torchrun: 是用于启动分布式PyTorch训练的工具。它用于替代之前的 torch.distributed.launch,更现代化和易于使用。它会自动初始化分布式环境并管理多进程训练。

  • standalone:这个表示在单机模式(standalone mode)下运行,即所有训练进程都在一台机器上进行。它不需要额外的集群配置,是常用于小规模分布式训练的简化模式。

  • nnodes=1:指定训练的节点数。在分布式训练中,节点通常指的是一台物理或虚拟机。这里 nnodes=1 表示只有一个节点参与训练。

  • nproc_per_node=8:表示在每个节点上启动 8 个进程。由于是单节点,意味着这台机器将运行 8 个并行的训练进程。通常,nproc_per_node 与机器上的 GPU 数量对应,这里应该是有 8 个 GPU,每个进程使用一张 GPU。

通过以下代码可以在单机单卡环境下运行。

python finetune_hf.py  data/AdvertiseGen/    /home/util/ARIS/chatglm3/chatglm3-6b   configs/ptuning_v2_.yaml

从保存点开始训练

如果按照上述方式进行训练,每次微调都会从头开始。如果你想从训练一半的模型开始微调,可以加入第四个参数,这个参数有两种传入方式:

  • yes, 自动从最后一个保存的 Checkpoint开始训练

  • XX, 断点号数字 例如500则从序号为500 的Checkpoint开始训练。

以下是一个从最后一个保存点继续微调的示例代码的示例:

python finetune_hf.py  data/AdvertiseGen/   /home/util/ARIS/chatglm3/chatglm3-6b   configs/ptuning_v2_.yaml yes

checkpoint中存储的是训练过程中保存的模型状态,包括模型参数、优化器状态、当前epoch等信息。

不同的训练参数,会产生不同数量的checkpoint,比如在脚本中,SAVE_INTERVAL 设置为1000,这说明每1000个训练步骤保存一次模型。如果MAX_STEP设置为3000,就应该有3个checkpoints被保存,这个也很好计算。

3.4 使用微调后的模型

可以在 finetune_demo/inference_hf.py 中使用微调后的模型,仅需要一行代码就能进行模型的推理测试。

python inference_hf.py your_finetune_path --prompt your prompt

也可以在/chatglm3/finetune_demo文件夹下的finetune_hf.py启动微调后的模型推理,不过需要先修改文件adapter_config.json中调用的地址才能正确运行。

 

posted @ 2025-09-30 16:38  指尖下的世界  阅读(10)  评论(0)    收藏  举报