开源大模型微调技术详解:从 Prompt Tuning 到 RLHF
开源大模型微调涉及全量微调、参数高效微调(PEFT)及人类反馈强化学习(RLHF)三大方向。核心内容包括 Prompt Tuning、Prefix Tuning、P-tuning v2 和 LoRA 的原理对比,以及 DeepSpeed ZeRO 在分布式训练中的应用。通过理论推导与代码实现示例,阐述如何低成本适配大模型至特定任务,提供从数据准备到模型部署的最佳实践建议。

开源大模型微调涉及全量微调、参数高效微调(PEFT)及人类反馈强化学习(RLHF)三大方向。核心内容包括 Prompt Tuning、Prefix Tuning、P-tuning v2 和 LoRA 的原理对比,以及 DeepSpeed ZeRO 在分布式训练中的应用。通过理论推导与代码实现示例,阐述如何低成本适配大模型至特定任务,提供从数据准备到模型部署的最佳实践建议。

随着开源大模型的逐渐增多,学习并了解其基础知识已成为技术人员的重要技能。在深度学习中,微调(Fine-tuning)是一种改进预训练模型性能的关键技术。除了微调 ChatGPT 之外,还有许多其他预训练模型可以进行微调。以下是几种常见的微调预训练模型的方法:
目前来说,常用的方法一般是前三种。简单来说,模型的参数就类比于一个在大学学习到所有专业知识的大学生,基于过往的学习经验以及对生活中的一些事情,已经有了属于自己的一套学习方法思维逻辑。而微调则是一个大学生毕业后从事某一种行业的工作,那他就要开始学习工作上的内容,来产出工作的成果。下面我们就来介绍一些常用的微调方法。
Fine tuning 是一种在自然语言处理(NLP)中使用的技术,用于将预训练的语言模型适应于特定任务或领域。Fine tuning 的基本思想是采用已经在大量文本上进行训练的预训练语言模型,然后在小规模的任务特定文本上继续训练它。
经典的 Fine tuning 方法包括将预训练模型与少量特定任务数据一起继续训练。在这个过程中,预训练模型的权重被更新,以更好地适应任务。所需的 Fine-tuning 量取决于预训练语料库和任务特定语料库之间的相似性。如果两者相似,可能只需要少量的 Fine tuning。如果两者不相似,则可能需要更多的 Fine tuning。

参数高效性微调方法中实现最简单的方法还是 Prompt tuning(也就是我们常说的 P-Tuning),固定模型前馈层参数,仅仅更新部分 embedding 参数即可实现低成本微调大模型。

经典的 Prompt tuning 方式不涉及对底层模型的任何参数更新。相反,它侧重于精心制作可以指导预训练模型生成所需输出的输入提示或模板。主要结构是利用了一个 prompt encoder(BiLSTM+MLP),将一些 pseudo prompt 先 encode(离散 token)再与 input embedding 进行拼接,同时利用 LSTM 进行 Reparamerization 加速训练,并引入少量自然语言提示的锚字符(Anchor,例如 Britain)进一步提升效果。然后结合(capital, Britain)生成得到结果,再优化生成的 encoder 部分。但是 P-tuning v1 有两个显著缺点:任务不通用和规模不通用。在一些复杂的自然语言理解 NLU 任务上效果很差,同时预训练模型的参数量不能过小。
如果分析 P-tuning,那不得不提到 prefix-tuning 技术,相对于 fine-tuning,在调节模型的过程中只优化一小段可学习的 continuous task-specific vector(prefix)而不是整个模型的参数。
Prefix Tuning 针对不同的模型结构有设计不同的模式,以自回归的模型为例,不再使用 token 去作为前缀,而是直接使用参数作为前缀,比如一个 $l \times d$ 的矩阵 $P$ 作为前缀,但直接使用这样的前缀效果不稳定,因此使用一个 MLP 层重参数化,并放大维度 $d$,除了在 embedding 层加入这个前缀之外,还在其他的所有层都添加这样一个前缀。最后微调时只调整前缀的参数,大模型的参数保持不变。保存时只需要为每个任务保存重参数的结果即可。

V2 版本主要是基于 P-tuning 和 prefix-tuning 技术,引入 Deep Prompt Encoding 和 Multi-task Learning 等策略进行优化的。
实验表明,仅精调 0.1% 参数量,在 330M 到 10B 不同参数规模 LM 模型上,均取得和 Fine-tuning 相比肩的性能。

下面是 v1 和 v2 框架对比,我们可以看到右侧的 p-tuning v2 中,将 continuous prompt 加在序列前端,并且每一层都加入可训练的 prompts。在左图 v1 模型中,只将 prompt 插入 input embedding 中,会导致可训练的参数被句子的长度所限制。此外 P-Tuning v2 还包括以下改进:

总而言之,P-tuning v2 就是将 Prefix-tuning 应用到 NLU 任务上的一种方法。同时由于 P-tuning v2 每层插入了 token,增大模型训练的改变量,所以更加适用于小一点的模型。
LoRA 的本质就是对所有权重矩阵套了一层'壳',这些壳会对原来的预训练权重矩阵进行加减使其更加适合下游任务,即实现微调。他的假设前提是预训练语言模型具有低的'内在维度',因此认为在模型适配下游任务的过程中,权重更新也应该具有低的'内在秩'。
在对大语言模型进行微调的公式可以简化为下面的公式:
$$W = W_0 + \Delta W$$
其中 $W$ 是微调过后的矩阵权重(是大语言模型中的对应的稠密层,一般这些矩阵都是满秩的),$W_0$ 是预训练的权重,$\Delta W$ 是通过微调而更新的梯度。将上面的 $\Delta W$ 做一些变换,将他变成两个矩阵相乘:
$$W = W_0 + \Delta W = W_0 + BA$$
在其中引入了秩 $r$,$r << min(d, k)$,在训练过程中 lora 会冻结预训练权重 $W_0$,只训练 $A$ 和 $B$,减少了需要训练的参数的量,一般来讲,对于 lora 微调模型来讲 r 设置的越大其微调效果会越好。
LoRA 算法的核心思想是,将原始矩阵分解为两个低秩矩阵 X 和 Y 的乘积形式,即 $A = X \cdot Y$。具体地,LoRA 算法会首先对原始矩阵进行 SVD 分解,得到矩阵 $A = U \Sigma V^T$,其中 $U$ 和 $V$ 分别是 $AA^T$ 和 $A^TA$ 的特征向量矩阵,$\Sigma$ 是奇异值矩阵。然后,LoRA 算法会取 $U$ 的前 $k$ 列和 $V$ 的前 $k$ 行,得到低秩矩阵 $X = U(:,1:k)$ 和 $Y = V(1:k,:)$,其中 $k$ 是预设的参数,表示矩阵 A 的秩。最后,LoRA 算法将近似矩阵 $A_k = X \cdot Y$ 作为原始矩阵 A 的近似,即 $A_k \approx A$。

最终我们得到的一个权重文件会是各层的 BA,在推理时是需要计算一下 $W = W_0 + BA$,就可以得到最终需要的微调之后的模型权重,该方法的好处如下所示:
一般在使用 lora 去对模型进行微调的时候需要注意的参数就两个:r 和 lora_target_modules。前者决定了 lora 微调时构造的矩阵的秩的大小(这里也可以简单的理解为矩阵 B 和 A 的大小),以及在大语言模型中应用的不同模块。后者模块的具体名称需要依据不同的模型去决定。
以下是一个使用 Hugging Face peft 库进行 LoRA 微调的简单示例:
from peft import LoraConfig, get_peft_model
from transformers import AutoModelForCausalLM, AutoTokenizer
# 加载预训练模型和分词器
model_name = "your_model_name"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)
# 配置 LoRA
lora_config = LoraConfig(
r=16, # 秩
lora_alpha=32,
target_modules=["q_proj", "v_proj"], # 目标模块
lora_dropout=0.1,
bias="none",
task_type="CAUSAL_LM"
)
# 应用 LoRA
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
RLHF 的思想就是使用强化学习的方式直接优化带有人类反馈的语言模型。RLHF 使得在一般文本数据语料库上训练的语言模型能和复杂的人类价值观对齐。RLHF 是一项涉及多个模型和不同训练阶段的复杂概念,一般会分为三步,这也是一个生成自己大模型所必需的。
第一步是 supervised-fintuning,即使用上文提到的数据集进行模型微调,预训练一个语言模型 (LM)。
第二步是训练一个奖励模型,它通过对于同一个 prompt 的不同输出进行人工排序,聚合问答数据并训练一个奖励模型 (Reward Model,RM);
第三步则是用强化学习算法 (RL) 方式微调 LM。
首先,我们使用经典的预训练目标训练一个语言模型。对这一步的模型,OpenAI 在其第一个流行的 RLHF 模型中使用了较小版本的 GPT-3; Anthropic 使用了 1000 万 ~ 520 亿参数的 Transformer 模型进行训练;DeepMind 使用了自家的 2800 亿参数模型。
这里可以用额外的文本或者条件对这个 LM 进行微调,例如 OpenAI 对'更可取'(preferable) 的人工生成文本进行了微调,而 Anthropic 按'有用、诚实和无害'的标准在上下文线索上蒸馏了原始的 LM。这里或许使用了昂贵的增强数据,但并不是 RLHF 必须的一步。由于 RLHF 还是一个尚待探索的领域,对于'哪种模型'适合作为 RLHF 的起点并没有明确的答案。

接下来,我们会基于 LM 来生成训练奖励模型 (RM,也叫偏好模型) 的数据,并在这一步引入人类的偏好信息。
RM 的训练是 RLHF 区别于旧范式的开端。这一模型接收一系列文本并返回一个标量奖励,数值上对应人的偏好。我们可以用端到端的方式用 LM 建模,或者用模块化的系统建模 (比如对输出进行排名,再将排名转换为奖励)。这一奖励数值将对后续无缝接入现有的 RL 算法至关重要。
关于模型选择方面,RM 可以是另一个经过微调的 LM,也可以是根据偏好数据从头开始训练的 LM。例如 Anthropic 提出了一种特殊的预训练方式,即用偏好模型预训练 (Preference Model Pretraining,PMP) 来替换一般预训练后的微调过程。因为前者被认为对样本数据的利用率更高。但对于哪种 RM 更好尚无定论。
关于训练文本方面,RM 的提示 - 生成对文本是从预定义数据集中采样生成的,并用初始的 LM 给这些提示生成文本。Anthropic 的数据主要是通过 Amazon Mechanical Turk 上的聊天工具生成的,而 OpenAI 使用了用户提交给 GPT API 的 prompt。
关于训练奖励数值方面,这里需要人工对 LM 生成的回答进行排名。起初我们可能会认为应该直接对文本标注分数来训练 RM,但是由于标注者的价值观不同导致这些分数未经过校准并且充满噪音。通过排名可以比较多个模型的输出并构建更好的规范数据集。
对具体的排名方式,一种成功的方式是对不同 LM 在相同提示下的输出进行比较,然后使用系统建立一个完整的排名。这些不同的排名结果将被归一化为用于训练的标量奖励值。
这个过程中一个有趣的产物是目前成功的 RLHF 系统使用了和生成模型具有不同大小的 LM (例如 OpenAI 使用了 175B 的 LM 和 6B 的 RM,Anthropic 使用的 LM 和 RM 从 10B 到 52B 大小不等,DeepMind 使用了 70B 的 Chinchilla 模型分别作为 LM 和 RM)。一种直觉是,偏好模型和生成模型需要具有类似的能力来理解提供给它们的文本。

接下来是最后一步:利用 RM 输出的奖励,用强化学习方式微调优化 LM。
长期以来出于工程和算法原因,人们认为用强化学习训练 LM 是不可能的。而目前多个组织找到的可行方案是使用策略梯度强化学习 (Policy Gradient RL) 算法、近端策略优化 (Proximal Policy Optimization,PPO) 微调初始 LM 的部分或全部参数。因为微调整个 10B~100B+ 参数的成本过高 (相关工作参考低秩适应和 DeepMind 的 LM)。PPO 算法已经存在了相对较长的时间,有大量关于其原理的指南,因而成为 RLHF 中的有利选择。
事实证明,RLHF 的许多核心 RL 进步一直在弄清楚如何将熟悉的 RL 算法应用到更新如此大的模型。
让我们首先将微调任务表述为 RL 问题。首先,该策略 (policy) 是一个接受提示并返回一系列文本 (或文本的概率分布) 的 LM。这个策略的行动空间 (action space) 是 LM 的词表对应的所有词元 (一般在 50k 数量级),观察空间 (observation space) 是可能的输入词元序列,也比较大 (词汇量 ^ 输入标记的数量)。奖励函数是偏好模型和策略转变约束 (Policy shift constraint) 的结合。
最后根据 PPO 算法,我们按当前批次数据的奖励指标进行优化 (来自 PPO 算法 on-policy 的特性)。PPO 算法是一种信赖域优化 (Trust Region Optimization,TRO) 算法,它使用梯度约束确保更新步骤不会破坏学习过程的稳定性。DeepMind 对 Gopher 使用了类似的奖励设置,但是使用 A2C () 算法来优化梯度。

Deepspeed 是微软的大规模分布式训练工具。专门用于训练超大模型。其具有的 3D 并行同时解决了训练万亿参数模型的两个基本挑战:显存效率和计算效率。因此,DeepSpeed 可以扩展至在显存中放下最巨大的模型,而不会牺牲速度。

然后使用 DeepSpeed+Zero 的结合方式就可以实现全参数的微调。当然,使用 DeepSpeed 进行 full finetuning,对于显存要求较高,且训练较慢。但这个无疑是一个比较好的办法,因为 DeepSpeed ZeRO-2 主要用于训练,因为它的功能对推理没有用。但是当 DeepSpeed 发展到 ZeRO-3 后,也可用于推理,因为它允许在多个 GPU 上加载大型模型,这在单个 GPU 上是不可能的。
在 Python 中,Accelerate 库提供了简单的 API,使我们可以在任何类型的单节点或分布式节点 (单 CPU、单 GPU、多 GPU 和 TPU) 上运行,也可以在有或没有混合精度 (fp16) 的情况下运行。
这里是我用 Accelerator 和 DeepSpeedPlugin 做个示例,这里需要提前知道梯度累积步骤 gradient_accumulation_steps 和 梯度累积计算。
选择合适的微调方法取决于具体的应用场景、资源限制以及任务需求。对于资源有限的场景,LoRA 和 P-tuning v2 是极佳的选择,它们能以极少的参数更新获得接近全量微调的效果。而对于追求极致对齐效果的场景,RLHF 虽然成本高,但能显著提升模型的安全性和有用性。在实际工程中,建议先从小规模数据验证 PEFT 方法的有效性,再逐步扩展到大规模部署。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online