InstructGPT高效实践——【DeepSpeed-Chat】源码详解(2/3):Supervised Finetuning、Reward Model Finetuning

目录

  • 前言
  • 1 phase-1: Supervised Finetuning
    • 1.1 训练数据样例
    • 1.2 训练过程
    • 1.3 关键代码详解
      • 1.3.1 基座模型结构
      • 1.3.2 LoRA结构及其正向传播
      • 1.3.3 phase1的指标评估
    • 1.4 实例测试
    • 1.5 相关拓展
      • 1.5.1 多轮对话性能
      • 1.5.2 本阶段训练更倾向过拟合
    • 1.6 版块相关问题
  • 2 phase-2: Reward Model Finetuning
    • 2.1 训练数据样例
    • 2.2 训练过程
    • 2.3关键代码详解
      • 2.3.1 RM具体结构
      • 2.3.2 DataCollator及RM所需输入形式
      • 2.3.3 RM的正向传播及成对排序损失
      • 2.3.4 phase2的指标评估
    • 2.4 实例测试
    • 2.5 相关拓展
      • 2.5.1 对话奖励聚合设计
    • 2.6 板块相关问题
  • 后续

前言

DeepSpeed-Chat PPO trainer
  本篇为上中下三篇章的【中篇】,接续自【上篇】。主要针对三阶段训练中第一阶段、第二阶段较为重要的部分源码进行详解。
  尽管官方的上手文档均是以sh文件为例进行演示,且源码中确实也提供了便捷的sh文件供直接使用,但我仍建议通过各阶段的main.py文件(applications/DeepSpeed-Chat/training/step*/main.py)来运行训练,大致原因有二:其一是因为官方预设的sh文件调用了applications/DeepSpeed-Chat/train.py,其中对模型选型等参数进行了严格的限制,虽然提高了代码安全性,但对于以学习为目的的探索来说失去了一定的灵活性(见下方代码块),直接通过main.py进行传参即可实现绕过限制、使用更轻量的模型进行训练;其二是因为main.py中的传参相对更接近底层,更有助于深入理解代码。
  因此各阶段的解析我都将在其main.py文件的基础上进行。

# applications/DeepSpeed-Chat/train.py
parser.add_argument("--actor-model",# actor模型只能选用“facebook/opt”系列的1.3b~66b# 但即使是1.3b也仍对设备显存有一定的要求# 对于使用较弱设备进行学习的用户不是很友好type=lambda x: x.replace("facebook/opt-", ""),default="1.3b",choices=("1.3b", "6.7b", "13b", "66b"),help="Which facebook/opt-* model to use for Actor (step 1)",)

1 phase-1: Supervised Finetuning

1.1 训练数据样例

数据格式名称说明样例
chosen_sentence人类偏好的完整对话,由prompt衔接偏好应答chosen得到,适用于phase1和phase2Human: Please tell me about Microsoft in a few sentence? Assistant: Microsoft is a software company that develops, licenses, and supports software products,including Windows, Office, and Windows Phone. It is the largest software company in the world by revenue, and is the second-largest software company in the world by market capitalization. Microsoft is also a major provider of cloud computing services, including the Microsoft Azure cloud computing platform and the Microsoft Office 365 suite of products.”

  模型将基于自回归语言建模任务对形如上述样例的数据进行训练,最后将得到具备更佳对话能力的模型SFT/ref(Supervised Fine-Tuning)。
  更多的数据格式可见【上篇】的“1.2.1 数据格式基本概念”。

1.2 训练过程

在此简单讲述UML时序图的元素含义:
- 箭头表示信息传递:实线表示调用,虚线表示返回;
- alt表示假设分支,其后方“[]”中的内容表示“条件”;
- loop表示循环;
- 淡蓝色区域即为高亮部分。
  • applications/DeepSpeed-Chat/training/step1_supervised_finetuning/main.py
main1.py utils.py model_utils.py lora.py data_utils.py transformers load_hf_tokenizer() 1 tokenizer 2 create_hf_model() 3 model 4 convert_linear_layer_to_lora() 5 model 6 only_optimize_lora_parameters() 7 model 8 alt [only_optimize_lora] alt [lora_dim > 0] create_prompt_dataset() 9 train_dataset, eval_dataset 10 train_dataloader, eval_dataloader 11 DeepSpeedEngine: model, opt, lrs 12 evaluation() 13 perplexity 14 model.forward() 15 LinearLayer_LoRA.forward() 16 output 17 alt [lora_dim > 0] loss 18 backward() 19 step() 20 loop [step] evaluation() 21 perplexity 22 save model 23 loop [epoch] main1.py utils.py model_utils.py lora.py data_utils.py transformers

phase1的大致训练过程如UML时序图所示(“括号序号”与UML时序图的“圈序号”对应):

  1. 载入tokenizer(1-2);
  2. 载入基座模型(目前仅支持部分CausalLM模型)(3-4);
  3. 根据是否设置lora_dim(LoRA的低秩维度)判断是否启用LoRA技术, 如果启用,则将基座模型结构进行LoRA改造(具体可见后续详述),并返回改造后的模型(5-6);
  4. 判断是否启用“仅更新LoRA参数”,如果启用,则对其余结构参数进行冻结处理,并返回冻结处理后的模型(7-8);
  5. 获取Dataset(具体流程可见【上篇】)(9-10);
  6. 实例化DataLoader(11);
  7. 使用DeepSpeed的优化技术DeepSpeedEngine包裹模型等对象(12);
  8. 开始正式训练前首先进行指标评估,选用的指标为困惑度perplexity(13-14);
  9. 开始训练,epoch循环:
    1. step循环:
      1. 正向传播得到loss(15-18),如果模型启用了LoRA技术,则正向传播还需要经过LoRA结构(16-17);
      2. 反向传播计算梯度(19);
      3. 更新模型参数(其中所涉及的梯度累计gradient_accumulation_steps将由DeepSpeedEngine自动进行管理,无需过度关注)(20);
    2. 经过1个epoch的训练后进行指标评估(21-22);
    3. 保存模型(23)。

1.3 关键代码详解

上述过程存在几个值得关注的地方(即文字描述加粗、UML时序图高亮的部分):

  • 基座模型的基本结构,主要是观察其所使用的输出头类型,基本就能知道该阶段使用了什么样的模型进行训练;
  • 启用LoRA技术进行结构改造的细节及其正向传播过程;
  • 关于phase1的指标评估方式。

以下将对相关部分的源码进行讲解。

1.3.1 基座模型结构

  从基座模型的载入类可以大致知晓模型的结构,可见下方代码块。此处使用了transformers.AutoModelForCausalLM.from_pretrained()来进行模型构建,因此第一阶段的SFT(ref)模型将会是一个因果语言模型/自回归语言模型(CausalLM),其所需要训练的任务自然就是自回归语言建模,即
p ( x ) = ∏ t = 1 T p ( x t ∣ x < t ) p(x) = \prod_{t=1}^{T} p(x_t|x_{<t}) p(x)=t=1Tp(xtx<t)

# applications/DeepSpeed-Chat/training/step1_supervised_finetuning/main.py
"""
模型调用create_hf_model方法进行构建,
参数指定有AutoModelForCausalLM
"""
model = create_hf_model(AutoModelForCausalLM, ···)# applications/DeepSpeed-Chat/training/utils/model/model_utils.py                        
def create_hf_model(model_class, ···):···"""model_class=AutoModelForCausalLM"""model = model_class.from_pretrained(model_name_or_path,from_tf=bool(".ckpt" in model_name_or_path),config=model_config)···

1.3.2 LoRA结构及其正向传播

  LoRA技术的大致思路如下图所示:
LoRA图示

  1. 在关键的参数层中加入旁路;
  2. 原参数冻结不变,训练时优化旁路参数;
  3. 原路输出 W x Wx Wx和旁路输出 B A x BAx BAx的加和即为最终输出 h = W x + B A x h=Wx+BAx h=Wx+BAx

LoRA结构定义
  而DeepSpeed-Chat的实现基本与上述思路一致,当设置LoRA的低秩维度lora_dim(如lora_dim=128)时,即认为启用了LoRA训练,则将原始模型中名称含有“deoder.layers.”且为线性层修改为LoRA层,具体操作为:

  1. 将原始结构的weight参数冻结;
  2. 新引入了2个线性层lora_right_weight和lora_left_weight,可实现先降维至lora_dim再升维回原维度;
  3. LoRA层主要实现了两分支通路,一条分支为已被冻结weight参数的原始结构、另一条分支为新引入的降维再升维线性层组。
# applications/DeepSpeed-Chat/training/step1_supervised_finetuning/main.py
# 判断是否启用LoRA模式
if args.lora_dim > 0:
"""
如果启用,则对名称中含有“decoder.layers.”且为线性层的结构部分引入LoRA旁路(实现先降维后升维的2个线性层),
这类结构基本都是attention、信息交互用的inner线性层,
这类结构的Weight参数将被冻结,转而优化LoRA旁路的参数。
"""args.lora_module_name = "decoder.layers."model = convert_linear_layer_to_lora(model, args.lora_module_name,args.lora_dim)# applications/DeepSpeed-Chat/training/utils/module/lora.py
def convert_linear_layer_to_lora(model,part_module_name,lora_dim=0,lora_scaling=1,lora_droppout=0):"""将名称中带有"decoder.layers."的线性层转换为lora层""""""取出模型中参数名含有decoder.layers.的线性层"""repalce_name = []for name, module in model.named_modules():if isinstance(module, nn.Linear) and part_module_name in name:repalce_name.append(name)for name in repalce_name:"""recursive_getattr实现了从model中根据属性名取出对应原始结构"""module = recursive_getattr(model, name)"""纳入原始结构的参数,实例化lora层"""tmp = LinearLayer_LoRA(module.weight, lora_dim, lora_scaling, lora_droppout,module.bias).to(module.weight.device).to(module.weight.dtype)"""recursive_getattr实现了将model对应属性的结构换成lora层实例"""recursive_setattr(model, name, tmp)return model# applications/DeepSpeed-Chat/training/utils/module/lora.py
class LinearLayer_LoRA(nn.Module):"""具体的lora层"""def __init__(...):..."""此处的weight和bias即为原始结构中的参数"""self.weight = weightself.bias = bias···"""冻结weight部分的参数"""self.weight.requires_grad = False···self.lora_right_weight = nn.Parameter(torch.zeros(columns, lora_dim))self.lora_left_weight = nn.Parameter(torch.zeros(lora_dim, rows))···"""初始化LoRA线性层的参数"""self.reset_parameters()def reset_parameters(self):"""初始化LoRA线性层的参数"""# 降维矩阵使用kaiming均匀分布初始化,# 服从均匀分布U(-\sqrt{1/in_feature}, +\sqrt{1/in_feature})# 与LoRA原始定义所用的(0,\sigma^2)正态分布初始化不同nn.init.kaiming_uniform_(self.lora_right_weight, a=math.sqrt(5))# 升维矩阵使用全0初始化nn.init.zeros_(self.lora_left_weight)def forward(self, input):"""LoRA的正向传播"""···else:return F.linear(input, self.weight, self.bias) + (self.lora_dropout(input) @ self.lora_right_weight @ self.lora_left_weight) * self.lora_scaling

  经过LoRA改造后,原始基座模型(此处的基座模型为“facebook/opt-125m”)的结构如下所示,可见模型中除了输出头部分的线性层基本都被改成了LoRA结构,因此模型在进行正向传播时也将流经LinearLayer_LoRA(nn.Module)中所定义的forward()方法(见上方代码块forward()部分)。

OPTForCausalLM((model): OPTModel((decoder): OPTDecoder((embed_tokens): Embedding(50272, 768, padding_idx=1)(embed_positions): OPTLearnedPositionalEmbedding(2050, 768)(final_layer_norm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)(layers): ModuleList((0-11): 12 x OPTDecoderLayer((self_attn): OPTAttention((k_proj): LinearLayer_LoRA((lora_dropout): Identity())(v_proj): LinearLayer_LoRA((lora_dropout): Identity())(q_proj): LinearLayer_LoRA((lora_dropout): Identity())(out_proj): LinearLayer_LoRA((lora_dropout): Identity()))(activation_fn): ReLU()(self_attn_layer_norm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)(fc1): LinearLayer_LoRA((lora_dropout): Identity())(fc2): LinearLayer_LoRA((lora_dropout): Identity())(final_layer_norm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)))))(lm_head): Linear(in_features=768, out_features=50272, bias=False)
)

LoRA正向传播
  常规部分的正向传播由transformers所定义,而LoRA部分的正向传播则由LinearLayer_LoRA(nn.Module)forward()所定义(可见下方代码块),即“LoRA层的两条分支结果进行加和”。在代码中体现为F.linear(input, self.weight, self.bias) + (self.lora_dropout(input) @ self.lora_right_weight @ self.lora_left_weight) * self.lora_scaling,加号左侧为原结构支路,加号右侧为新增支路,self.lora_right_weightself.lora_left_weight分别为两个新引入线性层的参数。

# applications/DeepSpeed-Chat/training/utils/module/lora.py
class LinearLayer_LoRA(nn.Module):"""具体的lora层"""···def forward(self, input):"""LoRA的正向传播"""···else:return F.linear(input, self.weight,self.bias) + (self.lora_dropout(input) @ self.lora_right_weight@ self.lora_left_weight) * self.lora_scaling

1.3.3 phase1的指标评估

  DeepSpeed-Chat选择了困惑度perplexity作为phase1训练期间的评估指标。需要注意的是,perplexity不是绝对的评估准则,甚至有可能perplexity评估结果与实际情况并不一致(即,perplexity已经处于较低水平,但模型的实际生成能力却仍然堪忧),这点DeepSpeed-Chat团队也有做出过说明。

Supervised fine-tuning (SFT) has indeed made significant progress in the field of large language models (LLMs). However, unexpected behaviors such as repeating content generation and inconsistency between perplexity (PPL) scores and generation capabilities can still occur.

  但无论如何,源码中phase1定义的evaluation是基于perplexity来进行的,我们仍有必要具体了解其实现过程。

  困惑度perplexity是一种度量语言模型性能的指标,它衡量了训练好的模型对测试数据的拟合程度,对于输出句子的每个token,都可以得到其输出的置信概率值,将这些值相乘并取其几何平均数的倒数即可计算得到困惑度perplexity,使用公式表达更为简洁:
p e r p l e x i t y = ( ∏ t = 1 T p t ) − 1 T perplexity = (\prod_{t=1}^{T} p_t)^{-\frac{1}{T}} perplexity=(t=1Tpt)T1
其中,输出的句子共有 T T T个token,第 t t t个token的置信概率值为 p t p_t pt

  而CausalLM模型的训练过程通常采用对数似然损失来进行优化,其输出的损失公式如下:
l o s s = − 1 T ∑ t = 1 T log ⁡ p t loss = -\frac{1}{T} \sum_{t=1}^{T}\log{p_t} loss=T1t=1Tlogpt
其中,输出的句子共有 T T T个token,第 t t t个token的置信概率值为 p t p_t pt

  因此perplexity与CausalLM的loss之间实际存在如下关系:
p e r p l e x i t y = exp ⁡ ( l o s s ) perplexity = \exp(loss) perplexity=exp(loss)

  相关源码的perplexity计算也是基于上述公式得到的:先是将验证数据输入至模型,得到模型loss输出,然后通过perplexity与loss之间的指数关系计算得到perplexity。

    def evaluation(model, eval_dataloader):"""以困惑度perplexity为评估指标进行验证"""model.eval()losses = 0for step, batch in enumerate(eval_dataloader):"""batch: 由input_ids、attention_mask、labels共3个部分组成的dict。其中每个部分的shape均为(bs, max_seq_len)"""batch = to_device(batch, device)with torch.no_grad():outputs = model(**batch)"""Causal LM 的损失函数为交叉熵损失"""loss = outputs.losslosses += loss.float()losses = losses / (step + 1)try:"""困惑度perplexity通常可以通过exp(CELoss)计算得到"""perplexity = torch.exp(losses)except OverflowError:perplexity = float("inf")try:"""- get_all_reduce_mean中调用了torch.distributed.all_reduce(perplexity, op=torch.distributed.ReduceOp.SUM)- 对所有进程、或者说GPU(因为通常情况下就是单个进程控制单个GPU)中的perplexity进行求和- 然后再除以全局进程数torch.distributed.get_world_size()得到平均的perplexity结果"""perplexity = get_all_reduce_mean(perplexity).item()except:passreturn perplexity

1.4 实例测试

  “实例测试”与“指标评估”并不是完全相同的概念,实例测试是选择具体的数据实例输入进模型中,人工观察其输出结果,而非使用具体指标对结果进行评估。实例测试实际上更体现了正向传播到解码、再到返回具体文本的过程。例如我对模型输入了一句prompt文本,那么整个实例测试流将会返回给我一份answer文本,我将以主观视角来感知这个answer的优劣程度,而不是采用具体的指标来进行评估。
  待完善…

1.5 相关拓展

1.5.1 多轮对话性能

  倘若想要使得模型通过具备更好的多轮对话的性能,除了需要考虑其“潜力”(就目前技术来说,模型支持的最大序列长度即为“潜力”,不可否认未来是否会出现新的长文本拓展技术)外,其多轮对话性能表现仍主要与本阶段的训练数据有关,还需要为本阶段加入更多的多轮对话数据,因此需要明确的是,用于本阶段的训练数据并不只局限于单轮对话,同样可以使用多轮对话内容进行训练,多轮对话无非只是具有更长的prompt而已,单轮对话与多轮对话的数据样例可见下表。

单轮或多轮样例
单轮prompt"Human: Please tell me about Microsoft in a few sentence? Assistant: "
多轮promptHuman: I’m buying a new video game console for the first time since in a decade, but I forget their setups. What do I need in addition to a Playstation 5 in order to play the console? Assistant: You need to buy a Playstation 5 first. Then you’ll also need a TV with HDMI input. It’s possible that HDMI to HDMI cables will also work, but it’s more likely that you’ll need a physical HDMI cord. You also might want to buy an extra power adapter, since the ones that come with new Playstation 5’s are quite short. Are you looking to play on a PC or any other system? That might affect what other hardware you need to buy. Human: Playstation 5’s cables aren’t short, but that’s good information. Can any television with an HDMI input play PS5? Assistant:
单轮chosen_sentenceHuman: Please tell me about Microsoft in a few sentence? Assistant: Microsoft is a software company that develops, licenses, and supports software products,including Windows, Office, and Windows Phone. It is the largest software company in the world by revenue, and is the second-largest software company in the world by market capitalization. Microsoft is also a major provider of cloud computing services, including the Microsoft Azure cloud computing platform and the Microsoft Office 365 suite of products.”
多轮chosen_setenceHuman: I’m buying a new video game console for the first time since in a decade, but I forget their setups. What do I need in addition to a Playstation 5 in order to play the console? Assistant: You need to buy a Playstation 5 first. Then you’ll also need a TV with HDMI input. It’s possible that HDMI to HDMI cables will also work, but it’s more likely that you’ll need a physical HDMI cord. You also might want to buy an extra power adapter, since the ones that come with new Playstation 5’s are quite short. Are you looking to play on a PC or any other system? That might affect what other hardware you need to buy. Human: Playstation 5’s cables aren’t short, but that’s good information. Can any television with an HDMI input play PS5? Assistant: So you’ve got a Playstation 5 and a TV that you’re going to connect together with an HDMI cable, and you want to know if that’s going to work? It’s definitely possible for the two to work together, and you might need an additional power adapter if your TV only came with a shorter adapter. However, it may be difficult to determine if it will work for sure. This is one area where troubleshooting and making educated guesses may be necessary. You should still be able to easily use your console, but it may be necessary to troubleshoot first.”

1.5.2 本阶段训练更倾向过拟合

  DeepSpeed-Chat团队称,根据InstructGPT的建议,本阶段的训练结果应适当倾向于过拟合(可以考虑更多的epoch),以此获得更好的对话能力。DeepSpeed-Chat团队还发现这个设计尤其对诸如opt-1.3B这类较小的模型微调特别有效。

From InstructGPT work, it is recommended to train the model for overfitting (aka longer epochs) for better human-preferred answers. Through our exploration, we have found this to be particularly helpful for smaller model finetuning, such as OPT-1.3B.

1.6 版块相关问题

  暂无

2 phase-2: Reward Model Finetuning

2.1 训练数据样例

数据格式名称说明样例
chosen_sentence人类偏好的完整对话,由prompt衔接偏好应答chosen得到,适用于phase1和phase2Human: Please tell me about Microsoft in a few sentence? Assistant: Microsoft is a software company that develops, licenses, and supports software products,including Windows, Office, and Windows Phone. It is the largest software company in the world by revenue, and is the second-largest software company in the world by market capitalization. Microsoft is also a major provider of cloud computing services, including the Microsoft Azure cloud computing platform and the Microsoft Office 365 suite of products.”
reject_sentence人类排斥的完整对话,由prompt衔接排斥应答rejected得到,适用于phase2Human: Please tell me about Microsoft in a few sentence? Assistant: I’m not sure what you mean.”

  模型将基于排序损失对形如上述样例的数据对进行训练,最后将得到具备类人评分能力的RM(Reward Model)。
  更多的数据格式可见【上篇】的“1.2.1 数据格式基本概念”。

2.2 训练过程

在此简单讲述UML时序图的元素含义:
- 箭头表示信息传递:实线表示调用,虚线表示返回;
- alt表示假设分支,其后方“[]”中的内容表示“条件”;
- loop表示循环;
- 淡蓝色区域即为高亮部分。
main2.py utils.py model_utils.py reward_model.py lora.py data_utils.py transformers load_hf_tokenizer() 1 tokenizer 2 create_critic_model() 3 create_hf_model() 4 critic_model 5 RewardModel() 6 critic_model 7 rm_model(critic_model) 8 convert_linear_layer_to_lora() 9 model 10 only_optimize_lora_parameters() 11 model 12 alt [only_optimize_lora] alt [lora_dim > 0] create_prompt_dataset() 13 train_dataset, eval_dataset 14 DataCollatorReward() 15 data_collator 16 train_dataloader, eval_dataloader 17 DeepSpeedEngine: rm_model, opt, lrs 18 evaluation_reward() 19 reward_score, acc 20 critic_model.forward() 21 rwtranrsformer.forward() 22 LinearLayer_LoRA.forward() 23 output 24 alt [lora_dim > 0] output 25 loss 26 backward() 27 step() 28 loop [step] evaluation_reward() 29 reward_score, acc 30 save model 31 loop [epoch] main2.py utils.py model_utils.py reward_model.py lora.py data_utils.py transformers

phase2的大致训练过程如UML时序图所示(“括号序号”与UML时序图的“圈序号”对应):

  1. 载入tokenizer(1-2);
  2. 载入模型(rm_model),其中涉及一定的结构更改(3-8)
  3. 根据是否设置lora_dim(LoRA的低秩维度)判断是否启用LoRA技术, 如果启用,则将基座模型结构进行LoRA改造(具体可见后续详述),并返回改造后的模型(9-10);
  4. 判断是否启用“仅更新LoRA参数”,如果启用,则对其余结构参数进行冻结处理,并返回冻结处理后的模型(11-12);
  5. 获取Dataset(具体流程可见【上篇】)(13-14);
  6. 实例化DataCollator,用于进一步对加载的数据进行整理(15-16);
  7. 实例化DataLoader(17);
  8. 使用DeepSpeed的优化技术DeepSpeedEngine包裹rm_model等对象(18);
  9. 开始正式训练前首先进行指标评估,选用的指标为排序结果的准确率accuracy(19-20);
    10.开始训练,epoch循环:
    1. step循环:
      1. 正向传播得到loss(21-26),如果模型启用了LoRA技术,则正向传播还需要经过LoRA结构(23-24);
      2. 反向传播计算梯度(27);
      3. 更新模型参数(其中所涉及的梯度累计gradient_accumulation_steps将由DeepSpeedEngine自动进行管理,无需过度关注)(28);
    2. 经过1个epoch的训练后进行指标评估(29-30);
    3. 保存模型(31)。

2.3关键代码详解

上述过程存在几个值得关注的地方(即文字描述加粗、UML时序图高亮的部分):

  • rm_model(RM)的具体结构;
  • phase2的数据整理器DataCollatorReward所实现的操作,通过这部分可以了解rm_model所需的输入形式;
  • 关于phase2的指标评估方式;
  • rm_model的正向传播过程。

2.3.1 RM具体结构

  首先使用transformers的AutoModel类来读取指定模型的主干网络(不直接定义有输出头的网络结构),然后引入一个可实现从hidden_size降维至1的线性层,该线性层将作为主干网络的输出头,为输入序列的每个位置输出1个评分。

# applications/DeepSpeed-Chat/training/step2_reward_model_finetuning/main.py
"""
rm_model调用了create_critic_model进行载入
默认情况下rm_model是不启用dropout的
"""
rm_model = create_critic_model(···)# applications/DeepSpeed-Chat/training/utils/model/model_utils.py
def create_critic_model(···):"""此处的模型读取方法用的是“AutoModel”,因此此处critic_model只有主干部分"""critic_model = create_hf_model(AutoModel, ···)"""critic_model传入RewardModel,将额外得到线性层输出头,因此此处的critic_model结构为“v_head + 主干部分”"""critic_model = RewardModel(critic_model, ···)...return critic_model# applications/DeepSpeed-Chat/training/utils/model/reward_model.py
class RewardModel(nn.Module):"""将读取得到的model的结构修改为适用于RewardModel的形式,总的来说即是使用载入的主干网络进行特征提取,其所提取的特征(最后层的各位置输出特征hidden_states)将被传入线性层,输出得到1个数值,该数值即为分值,因此max_seq_len维度的每个位置均会得到1个分值"""def __init__(self, base_model, ...):super().__init__()···if hasattr(self.config, "word_embed_proj_dim"):"""OPT系列模型的word_embed_proj_dim为embedding层的输出维度,通常在transformer模型中也就等于 hidden_size,v_head将基于主干网络的输出特征 hidden_state 进行分值预测,共输出max_seq_len个分值"""self.v_head = nn.Linear(self.config.word_embed_proj_dim,1,bias=False)···"""base_model即为主干网络,因此RM最终由1个主干网络和1个线性层构成"""self.rwtranrsformer = base_model

  RM的模型结构基本如下所示(此处的基座模型为“facebook/opt-125m”),由主干网络rwtransformer及输出头v_head组成:

RewardModel((v_head): Linear(in_features=768, out_features=1, bias=False)(rwtranrsformer): OPTModel((decoder): OPTDecoder((embed_tokens): Embedding(50272, 768, padding_idx=1)(embed_positions): OPTLearnedPositionalEmbedding(2050, 768)(final_layer_norm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)(layers): ModuleList((0-11): 12 x OPTDecoderLayer((self_attn): OPTAttention((k_proj): Linear(in_features=768, out_features=768, bias=True)(v_proj): Linear(in_features=768, out_features=768, bias=True)(q_proj): Linear(in_features=768, out_features=768, bias=True)(out_proj): Linear(in_features=768, out_features=768, bias=True))(activation_fn): ReLU()(self_attn_layer_norm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)(fc1): Linear(in_features=768, out_features=3072, bias=True)(fc2): Linear(in_features=3072, out_features=768, bias=True)(final_layer_norm): LayerNorm((768,), eps=1e-05, elementwise_affine=True)))))
)

2.3.2 DataCollator及RM所需输入形式

  phase2使用的数据整理器data_collator为DataCollatorReward(),本阶段取出的单个样本example实际上是一个chosen-rejected数据对(见下方代码块),即1个大小为batch_size的batch取出了batch_size个数据对,data_collator将把数据对拆成chosen_sentence和reject_sentence(example一分为二),因此实际上1个batch真正输入模型的数据量大小应当为“batch_size * 2”

# applications/DeepSpeed-Chat/training/step2_reward_model_finetuning/main.py
"""phase2使用的data_collator为DataCollatorReward()"""
data_collator = DataCollatorReward()# applications/DeepSpeed-Chat/training/utils/data/data_utils.py
class DataCollatorReward:def __call__(self, data):"""对dataloader取到的数据 data 进一步整理,将数据整理成batch输入形式入参 data 的具体样式可见下个代码块"""batch = {}"""f为data中的1个tuple,tuple的第0个元素和第2个元素分别为chosen_sentence和reject_sentence的input_ids"""batch["input_ids"] = torch.cat([f[0] for f in data] + [f[2] for f in data],dim=0)"""f为data中的1个tuple,tuple的第1个元素和第3个元素分别为chosen_sentence和reject_sentence的attention_mask"""batch["attention_mask"] = torch.cat([f[1] for f in data] +[f[3] for f in data],dim=0)"""batch的具体样式可见下个代码块"""return batch
输入的data为一个batch的数据列表,其中的 每个元素 为一对chosen-rejected数据:(chosen_sentence_input_ids, chosen_sentence_attention_mask,reject_sentence_input_ids,reject_sentence_attention_mask)每组数据的第0个元素和第2个元素为input_ids,第1个元素和第3个元素为attention_mask。输出的batch为字典:{“input_ids”: tensor([...]), "attention_mask": tensor([...])}
并且字典值中chosen位于前半部分,rejected位于后半部分:{"input_ids": [chosen_sentence_1_input_ids,chosen_sentence_2_input_ids,...,reject_sentence_1_input_ids,reject_sentence_2_input_ids,...]"attention_mask": [chosen_sentence_1_attention_mask,chosen_sentence_2_attention_mask,...,reject_sentence_1_attention_mask,reject_sentence_2_attention_mask,...]}
后续输入模型后,直接将数据切分出前半部分和后半部分进行并列,即可获得对应的chosen-rejected数据对。

2.3.3 RM的正向传播及成对排序损失

  RM的正向传播过程不算复杂,总的来说就是:

  1. 数据经过主干网络得到shape为(bs*2, max_seq_len, hidden_size)的最后层输出特征hidden_states;
  2. 然后将输出特征送入线性层v_head得到shape为(bs*2, max_seq_len)的评分rewards。

  较为复杂的部分实际上是“成对排序损失的计算”以及“评分聚合设计”。

成对排序损失(Pairwise Ranking Loss)
l o s s ( θ ) = E ( x , y c , y r ) ∼ D [ − l o g ( σ ( r θ ( x , y c ) − r θ ( x , y r ) ) ) ] loss(\theta) = E_{(x, y_c, y_r) \sim{D}} [-log(\sigma(r_\theta(x, y_c) - r_\theta(x, y_r)))] loss(θ)=E(x,yc,yr)D[log(σ(rθ(x,yc)rθ(x,yr)))]
  其中, r θ r_\theta rθ为RM, x x x为prompt, y c y_c yc为chosen, y r y_r yr为rejected, ( x , y c ) (x, y_c) (x,yc) ( x , y r ) (x, y_r) (x,yr)则分别为chosen_sentence和reject_sentence。
  该损失函数的目的在于最大化“chosen/好的/排序靠前的”和“rejected/坏的/排序靠后的”的差值,由此促使 r θ r_\theta rθ学习到相应的排序模式。
  DeepSpeed-Chat在实现这部分时, r θ ( x , y c ) r_\theta(x,y_c) rθ(x,yc) r θ ( x , y r ) r_\theta(x,y_r) rθ(x,yr)分别选择了chosen_sentence和reject_sentence两者answer的对齐部分,通过文字叙述略显抽象,查看下方的代码块有助于你理解这个概念:

max_seq_len为10,pad_token_id为0,
有同属同个prompt的chosen_sentence和reject_sentence:
prompt: [11, 22, 33]
chosen_sentence: [11, 22, 33, 44, 55, 66, 0, 0, 0, 0]
reject_sentence: [11, 22, 33, 40, 50, 0, 0, 0, 0, 0]“两者answer的对齐部分”即为“非prompt部分也非padding部分、但长度要对齐”:
chosen_truncated: [44, 55, 66]
reject_truncated: [40, 50, 0]chosen_sentence的answer比较长,所以reject_sentence在取相应部分时要取至与chosen部分等长为止;
reject_sentence的answer较长时同理。

  为了取到上述提及的“对齐部分”,代码进行了较为晦涩抽象的取index操作,但只要理解其最终目的是为了取到chosen_sentence和reject_sentence对齐部分的reward,来进行损失计算即可。

对话奖励设计
  尽管使用的是“对齐部分”的reward来计算成对排序损失,但RM模型对一个对话的预测评分实际上取的是该对话文本最后一个有效token(通常会是“结束标记”)的reward,下方代码块提供了一个简单例子说明了这个情况。

pad_token_id = 0
conversation = [11, 22, 33, 44, 55, 66, 0, 0, 0, 0]
conversation_rewards = [2.01, 0.23, 2.89, 0.66, 0.33, 2.25, 0.36, 0.99, 1.32, 1.62]
token_id为66的token作为该对话的最后1个有效token,
其对应的reward“2.25”将被用于表示整个对话的reward。

整体代码如下所示:

# applications/DeepSpeed-Chat/training/utils/model/reward_model.py
class RewardModel(nn.Module):def __init__(self, ···):······def forward(self, input_ids=None, ···):"""获得主干网络的输出的特征"""transformer_outputs = self.rwtranrsformer(···)"""取最后一层的输出特征hidden_states.shape: (bs*2, max_seq_len, hidden_size)"""hidden_states = transformer_outputs[0]"""将特征送入全连接层得到分数回归值rewards.shape: (bs*2, max_seq_len)"""rewards = self.v_head(hidden_states).squeeze(-1)"""先前提及过,实际的bs应该是输入bs的一半"""bs = input_ids.shape[0] // 2"""区分出chosen和reject"""chosen_ids = input_ids[:bs]rejected_ids = input_ids[bs:]chosen_rewards = rewards[:bs]rejected_rewards = rewards[bs:]loss = 0for i in range(bs):"""取出同组chosen和rejected的token_id和分值rewardchosen_id.shape: (max_seq_len, )"""chosen_id = chosen_ids[i]rejected_id = rejected_ids[i]chosen_reward = chosen_rewards[i]rejected_reward = rejected_rewards[i]"""下方本应有各种取index相关的操作,基于源码解读的可读性考量,且这些部分只是逻辑形式上的弯弯绕绕,与相关原理并不存在直接关系,所以我选择暂且将它们忽略。""""""c_ind为chosen_sentence的answer后的第一个pad_token的index例如pad_token_id=0,sentence[11,22,33,44,55,66,0,0,0,0],c_ind即为第一个pad_token的index=6。"""c_ind = ···"""r_ind同理,为reject_sentence的answer后的第一个pad_token的index"""r_ind = ···"""end_ind则为两者的较大者"""end_ind = max(c_ind, r_ind)# 取chosen和rejected第一个不同的地方的index,可以理解为“response中两个回答自由发挥的第1个token的index”"""divergence_ind为chosen_sentence和reject_sentence两者answer的第1个token的index"""divergence_ind = ···"""以chosen_sentence和reject_sentence最先不同的地方为起始、生成结束的地方为终止,取两者在这个片段的对应分值这部分其实就是上个代码块提及的“对齐部分”"""c_truncated_reward = chosen_reward[divergence_ind:end_ind]r_truncated_reward = rejected_reward[divergence_ind:end_ind]"""(c_truncated_reward - r_truncated_reward).shape: (truncated_seq_len,)计算损失时使用了rank loss的形式,并且是对chosen和rejected“对齐片段”进行计算的"""loss += -torch.log(torch.sigmoid(c_truncated_reward - r_truncated_reward)).mean()loss = loss / bs"""取代表结束的pad token所在位置的前一个位置(可以理解为的最后一个有效token的位置)的分值作为参考分值"""chosen_mean_scores.append(chosen_reward[c_ind - 1])  #use the end score for referencerejected_mean_scores.append(rejected_reward[r_ind - 1])chosen_mean_scores = torch.stack(chosen_mean_scores)rejected_mean_scores = torch.stack(rejected_mean_scores)"""返回损失和参考分值"""return {"loss": loss,"chosen_mean_scores": chosen_mean_scores,"rejected_mean_scores": rejected_mean_scores,}···

2.3.4 phase2的指标评估

  DeepSpeed-Chat在phase2中使用的评估指标为排序正确的accuracy,主要过程为:

  1. 将数对chosen-rejected数据对(过程中被data_collator拆分为chosen_sentence和reject_sentence)输入RM中进行推理,得到各个sentence的分值;
  2. 将同属一个prompt的chosen_sentence得分与reject_sentence得分进行比较,当chosen_sentence得分大于reject_sentence得分时,即为“正确预测”,否则为“错误预测”;
  3. 统计正确预测的结果,计算accuracy作为评估指标。
  4. 此外评估过程中还将统计平均的chosen_sentence分值“scores”供参考。
def evaluation_reward(model, eval_dataloader):model.eval()"""统计预测(赋分)正确的结果即 chosen_reward > rejected_reward 的结果数"""correct_predictions = 0"""统计预测总数"""total_predictions = 0scores = 0for step, batch in enumerate(eval_dataloader):batch = to_device(batch, device)with torch.no_grad():"""outputs: {'loss':tensor(), 'chosen_mean_scores':tensor(bs,), 'rejected_mean_scores':tensor(bs,)}"""outputs = model(**batch)"""chosen.shape: (bs,)"""chosen = outputs["chosen_mean_scores"]"""rejected.shape: (bs,)"""rejected = outputs["rejected_mean_scores"]""""赋分正确"即为chosen分值大于rejected分值"""correct_predictions += (chosen > rejected).sum()total_predictions += chosen.shape[0]"""累加每个step的平均chosen分值"""scores += outputs["chosen_mean_scores"].mean().float()if step == 99:  # For faster evaluation and debuggingbreak"""计算acc指标"""acc = correct_predictions / total_predictions"""计算当前step的平均chosen分值"""scores = scores / (step + 1)try:"""多进程结果求和求平均"""acc = get_all_reduce_mean(acc).item()scores = get_all_reduce_mean(scores).item()except:passreturn scores, acc

2.4 实例测试

  “实例测试”与“指标评估”并不是完全相同的概念,实例测试是选择具体的数据实例输入进模型中,人工观察其输出结果,而非使用具体指标对结果进行评估。
  待完善…

2.5 相关拓展

2.5.1 对话奖励聚合设计

  在DeepSpeed-Chat的实现中,RM模型对一个对话的预测评分实际上取的是该对话文本最后一个token的reward,当然此处并不是只能采用这种方式对对话进行评分,这是一个开放性的策略设计,只是DeepSpeed-Chat团队采取了这样的实现,用户当然也可以自己制定评分的处理策略,比如answer部分的平均reward、序列reward再接全连接层得到聚合rewad等等。

In our implementation, we use either the end token of the sequence or the first padding token as the aggregated score and compare them. Others may also use the average score for the entire answer as an alternative.

2.6 板块相关问题

  暂无

后续

  RLHF阶段的训练具体内容可见【下篇】。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/28093.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

NEWS|药物发现公司正在定制ChatGPT:方法如下

大型语言模型正在帮助科学家与人工智能交谈&#xff0c;甚至产生潜在的药物靶点。 近几个月来&#xff0c;世界大部分地区都被OpenAI的ChatGPT等文本生成引擎的出现所震惊&#xff0c;人工智能&#xff08;AI&#xff09;算法能够生成看起来像是由人类编写的文本。虽然像微软和…

ChatGPT强势加入芯片设计!不用学专业硬件描述语言了,说人话就行

西风 发自 凹非寺量子位 | 公众号 QbitAI 和ChatGPT聊聊天&#xff0c;就可解决CPU开发过程中的一大难题&#xff1f; 纽约大学&#xff08;NYU&#xff09;研究人员完成了一件看似不可能的事情&#xff1a; 无需专业的硬件描述语言&#xff08;HDL&#xff09;&#xff0c;仅靠…

激素、酶、细胞因子区别;肿瘤细胞信号通路

参考&#xff1a; https://www.xuetangx.com/course/THU08261001403/12423502?channeli.area.learn_title 本文章主要有chatgpt生成&#xff1a; 1、激素、酶、细胞因子区别 概念、功能 激素、酶和细胞因子都是生物活性物质&#xff0c;激素、酶和细胞因子都是蛋白质&#…

雷军宣布进军ChatGPT大战!国内ChatGPT赛道一触即发!

4-14号&#xff0c;小米CEO雷军在微博宣布&#xff0c;正在研发一些“有趣的技术和产品”。 雷军称&#xff0c;此前曾多次被问及对于大模型和AIGC的看法。 此次&#xff0c;雷军正式对这些问题公开进行回应&#xff0c;表示“在AI领域已经耕耘多年”&#xff0c;对大模型“当然…

ChatGPT 提示词全网最全案例汇总

GPT地址&#xff0c;收藏不迷路&#xff1a;https://ai.cxyquan.com/ ChatGPT 提示词案例分享 充当旅游指南 我想让你做一个旅游指南。我会把我的位置写给你&#xff0c;你会推荐一个靠近我的位置的地方。在某些情况下&#xff0c;我还会告诉您我将访问的地方类型。您还会向我推…

比较了几种编程语言后,我终于get到了少儿编程的真谛

真是让人感受到人工智能有多么神奇&#xff01; 看展途中&#xff0c;同去的朋友对我说&#xff0c;自己一直觉得未来人工智能是大趋势&#xff0c;所以正在让孩子学编程。可是&#xff0c;他看孩子学的编程都只是用一款叫Scratch的软件拖来拖去&#xff0c;做做简单的动画和游…

少儿编程简介

少儿编程一般来说&#xff0c;是针对4-17岁的青少年儿童开展的教育&#xff0c;不像众多家长了解的成人编程那样&#xff0c;不是单纯的敲击键盘、枯燥地编写一行行晦涩难懂的英文代码&#xff0c;而是以青少年可以接受的方式&#xff0c;比如实体积木块&#xff0c;图形化积木…

少儿编程值得报班学习吗?别问了,程序员懵了

今年中小学生的暑假期间&#xff0c;想必很多程序员收到了以下私信&#xff1a; 程序员你好&#xff0c;少儿编程值得学习吗&#xff1f; 1、不是一个行业&#xff0c;你让程序员怎么回答&#xff1f; 程序员从事的工作&#xff0c;属于互联网行业&#xff1b;少儿编程的培训&…

向幼儿群体提供实用的少儿编程

政策为青少年、儿童编程教育背书。首先&#xff0c;我国政府、教育部门发布文件明确表明支持青少年、儿童编程教育的发展。格物斯坦认为&#xff1a;为了应对人工智能时代发展的需要&#xff0c;越是进步和充满便利的时代&#xff0c;越需要人的思考和认识。AI时代&#xff0c;…

谷歌全线反击!PaLM 2部分性能已经超越GPT-4

ChatGPT横空出世&#xff0c;所有人都能够明确感知到AI的惊人潜力&#xff0c;瞬间改变了整个AI行业的节奏&#xff0c;不紧不慢的谷歌也开始紧张了。 ChatGPT舆论热潮仍未消退&#xff0c;红色警报又拉响 北京时间5月11日凌晨1点&#xff0c;Google I/O 2023开发者大会上发布…

ChatGPT初体验step by step:ChatGPT解决人类提出的数理逻辑问题,Python编程实践

ChatGPT初体验step by step&#xff1a;ChatGPT解决人类提出的数理逻辑问题&#xff0c;Python编程实践 如果已有有效的open ai的api key&#xff0c;则跳过本文&#xff08;1&#xff09;&#xff08;2&#xff09;&#xff08;3&#xff09;&#xff08;4&#xff09;&#x…

我的 ChatGPT初体验

要有一个ChatGPT帐号&#xff0c;这个很重要&#xff0c;YouTube 有很多教程&#xff0c;这里就不细说了&#xff0c; 最近家里的房子想装修&#xff0c;个人是小白知识匮乏&#xff0c;就想问下ChatGPT给一些学习思路和方法下面直接上图了。

写给开发同学的 AI 强化学习入门指南

该篇文章是我学习过程的一些归纳总结&#xff0c;希望对大家有所帮助。 最近因为 AI 大火&#xff0c;搞的我也对 AI 突然也很感兴趣&#xff0c;于是开启了 AI 的学习之旅。其实我也没学过机器学习&#xff0c;对 AI 基本上一窍不通&#xff0c;但是好在身处在这个信息爆炸的…

vue3.0仿微信聊天|Vue3+Vant3.x聊天实例

Vue3-Chatroom 基于vue3.x开发的仿微信界面聊天室。 使用vue3.0vuex4.xvue-router4vant3.xv3popup构建的移动端聊天实例。基本实现了消息发送/gif动图、图片/视频预览、网址查看、红包/朋友圈等功能。 实现技术 编码/技术&#xff1a;vscodevue3.0/vuex4.x/vue-router4UI组件库…

4DIAC 运行时(Forte)连接PIFace Digital 2 模块

Piface 模块 Piface 是树莓PI 上的一个通用数字输入输出模块。采用SPI 与树莓派通信。 该模块使用microchip的MCP23S17SP 芯片。通过SPI 接口控制16个GPIO 端口&#xff0c;端口可以设置为输入或者输出方式。使用单一的接口来扩展linux IO 端口&#xff0c;减少了软件的复杂性…

什么是音色?

要问最近最火的节目是什么&#xff1f; 《浪姐》绝对可以冲击C位。 要问最近最火的剧是哪部&#xff1f; 有全中国小学生最近都怕的张东升老师那部前三甲无疑。 要问最近最火的歌是哪首&#xff1f; 《Mojito》或许是唯一的答案。 这首极具拉丁风格的歌让周董再一次回答了“谁是…

关于springboot+simbot+mriai实现QQ群智能回复机器人

前言 前几天在一个在一个java的交流群上发现了一个舔狗机器人&#xff0c;感觉有点意思。在git上逛了一圈发现simbot这个框架封装得还不错&#xff0c;这是一个基于kotlin的框架但他并不仅至此。用java也是能进行编写工作&#xff0c;我们简单尝试一下。 前期准备 本次demo使用…

四. IEC 61499开源项目4diac配置modbus

开源的4diac运行时只支持modbus主站&#xff08;modbus客户端&#xff09;&#xff0c;配置forte运行时支持modbus主站可以通过运行时操作支持modbus从站的远程IO模块&#xff0c;此处讲解的是modbus tcp。从4diac官网下载的forte运行时默认是不支持modbus协议的&#xff0c;要…

PDF Forte Pro(PDF转换器)v3.1.2免费版

PDF Forte Pro是一款优秀的PDF转换器&#xff0c;它支持将超过10种常用文件格式转换为PDF&#xff0c;包括word&#xff0c;Excle&#xff0c;PPT&#xff0c;PSD&#xff0c;Image和Dwg。所有Windows平台完美兼容&#xff0c;转换后的PDF文档无质量损失&#xff0c;而且拥有超…

FORTE和RIPPLE(瑞波)出资一亿美元成立基金,帮助游戏开发者应用区块链技术

a16z被投企业Forte向游戏开发者提供区块链技术平台和资金支持。 为游戏行业提供区块链技术平台的Forte和Ripple&#xff08;瑞波&#xff09;的开发者生态基金Xpring出资一亿美元成立基金帮助游戏开发者更好的利用区块链技术。该笔资金将与Forte的技术平台一起运作&#xff0c;…