Fine-tuning:GRPO,DPO,vLLM

1,GRPO

1.1,RLHF

RLHF 引入了"反馈机制",让 LLM 能够生成更符合人类期望的答案:

  • 生成:AI 生成多个不同的回答

  • 评分:人类(或奖励模型)对回答打分

  • 学习:AI 学会更多地生成高分回答,更少地生成低分回答

  • 改进:重复这个过程,AI 越来越好

这就像一个持续改进的循环:生成回答 → 获得反馈 → 调整策略 → 生成更好的回答。

监督学习 SFT策略梯度 RLHF
学习正确答案学习什么是好的
模仿固定的例子根据反馈持续改进
一次性训练交互式学习
答案相对固定能适应新的情况

1.2,RL,PPO,GRPO

【Policy-Base】假如问题定义在离散的动作空间,智能体基于此刻的状态要输出每一个动作的概率。Policy-Base 就是你有一个 DNN,最后一层是一个 Softmax,输出了后续每一个动作的概率,概率越大未来的价值越高,如果是玩游戏就是未来得分高,存活几率大,如果是序列生成,就是生成的序列更优质。那么怎么更新这个 Policy 呢?还是对比多分类,模型输出了每个类别的概率,知道多类里有一个是正确的,那么用交叉熵损失即可,调大正确那一类的输出概率。

【强化学习】知道执行了一个动作,这个动作产生了 reward,如果 reward 的是好的,那么应该调大这一个动作的概率,如果 reward 是差的,应该调小这个动作的概率。这怎么做呢?就是交叉熵乘以 reward,来影响概率的调节方向和调节力度。

【REINFORCE with baseline】这里有一个漏洞,那就是 reward 可能都是设计成正数,reward 无论好坏都是正的,那么概率始终在被调大,只是力度不同。希望调小概率时,reward 应该是个负的。直观的想法,如果见过一批 reward,减去的均值就好了。

【Actor-Critic】这个启发式搞出来的均值 baseline 不一定是最好的,想知道的是此刻状态对应的价值,均值没有这样的物理意义。可以用一个 NN 模型来预估出这个价值呢?Actor 就是你的动作概率模型,Critic 就是用一个 NN 在算这个 baseline,或者叫 value-base 的 model。

【GRPO】PPO 就属于 Actor-Critic,所以它带着 Value-Base model,GRPO 的 motivation 就是干掉了这个 Value-Base 的 model,节省了显存。如果不用一个 model 来估计状态的价值,还有什么好办法?那你就基于每一条原始样本生成一组序列,用它们的 reward 均值作为 baseline,这种方法呢,就叫 self-critic。利用了蒙特卡洛方法代替了 TD error。

GROP 的精髓呢,就是 Self-Critic,某个状态开始生成多个结果,用这个结果 reward 的均值作为 baseline。至于其他的那个 KL 散度的动作连续性校正,是从 PPO 继承来的,但那不是精髓。这个 Self-Critic 方法是哪里来的?最早溯源到了这篇 paper《Self-critical Sequence Training for Image Captioning》,输入图片,生成一段自然语言描述,里面剔除到 Value-base model,提出了 Self-critic 的概念。《BUY 4 REINFORCE SAMPLES,GET A BASELINE FOR FREE!》就是更接近了,每个生成 4 个序列,每 4 个获得一个均值作为 baseline,而且它宣称 sample 出 4 个边际收益已经非常高了

为什么在游戏领域,更多的使用 PPO,而在序列生成上,GRPO 才有了一席之地,并且 self-critic 最早应用在了图生文上,答案很简单。相比游戏里的轨迹,序列生成要短的多,它有明确的终止条件,而游戏未必有,因而容易通过蒙特卡洛模拟获得 baseline。

1.3,为什么 GRPO 是 On-Policy?

On-Policy:指的是“用正在学习的策略产生的数据来学习”。即,智能体(在LLM中指语言模型本身)严格使用其当前策略(Policy)与环境交互所产生的数据来更新和优化自身。这意味着,一旦策略发生更新,所有旧的交互数据都将被废弃,因为它们是由一个“过时”的策略产生的。

Off-Policy:则更为灵活,它指的是“用并非当前学习的策略所产生的数据来学习”。智能体可以利用由其他策略(甚至是历史策略或人类示范数据)产生的数据来更新当前策略。这解耦了“数据生成”和“策略学习”两个过程,从而提高了数据利用效率。

【DPO:一种典型的 Off-Policy 思路】利用一个固定的、离线的人类偏好数据集,直接优化语言模型。这个数据集通常包含一系列的提示(Prompt),以及对模型生成的两个回答的偏好标签(哪个更好 y_w,哪个更差 y_l)。DPO 损失函数直接将策略与人类偏好挂钩,其目标是最大化模型生成“更优”回答的概率,同时最小化生成“更差”回答的概率,并且与一个固定的参考模型(Reference Model)保持一定的距离,防止模型在优化过程中“忘掉”其预训练时学到的知识。

【为什么 DPO 是 Off-Policy?】

  • 数据来源的非交互性:DPO 所用的偏好数据 xy_wy_l一次性收集并固定的。在整个 DPO 的训练过程中,模型不会用其正在更新的策略去与环境(或人类)交互产生新的偏好数据。
  • 行为策略与目标策略的分离:将生成这个偏好数据集的策略(可能是早期的某个模型版本,甚至是多个不同模型的混合)看作是行为策略(Behavior Policy)。而正在优化的当前模型,则是目标策略(Target Policy)。DPO 的目标是利用行为策略产生的数据,来优化目标策略,使其更符合人类偏好。

【GRPO:On-Policy 算法】 对于一个给定的提示,让当前模型生成一组(Group)候选回答(例如,生成 8 个不同的回答)。然后,一个奖励函数(可以是一个训练好的奖励模型,也可以是基于规则的打分器)会为这组中的每一个回答打分。GRPO 利用这一组回答的相对好坏来计算优势(Advantage),并更新策略。它通过比较组内样本的得分均值和标准差来归一化奖励,从而指导模型向着生成更高分回答的方向优化。

  • 数据由当前策略生成:在每个训练步骤中,使用当前正在优化的策略 \pi_{\theta} 来生成一组候选回答。
  • 即时更新于抛弃:基于这组新生成的数据计算出的梯度来更新策略。更新完成后,这组数据就会被抛弃,下一步骤会由新的策略 \pi_{\theta}^{'} 重新生成数据。整个流程保证了数据分布与策略分布的一致性。

2,DPO 算法

DPO(Direct Preference Optimization)是一种基于人类偏好的语言模型训练方法。 它通过最小化一个特殊的损失函数来优化模型,使模型生成的文本更符合人类偏好。 相比其他基于人类偏好的训练方法(如 PPO、RLHF 等),DPO 具有以下优势:

  • 计算效率高:不需要进行策略梯度估计和价值函数学习。
  • 训练过程简单:可以直接在单个前向传播中计算损失。
  • 计算开销小:无需额外的网络结构和训练步骤。

DPO 的核心损失函数为:

-ln\, \sigma(\beta\,ln\frac{\pi(y_m|x)}{\pi_{ref}}-\beta\,ln\frac{\pi(y_l|x)}{\pi_{ref}(y_l|x)})

2.1,KL 散度

在 DPO 中,需要限制优化后的策略 \pi(y|x) 与初始测试 \pi_{ref}(y|x) 之间的差异。这种限制主要是为了避免模型发生剧烈的变化,保持 pretrained model 的语言能力,避免过拟合。使用 KL 散度(Kullback-Leibler 散度)来计算两个策略之间的差异,KL 散度是衡量两个概率分布差异的度量工具。

KL(P||Q)=\sum_{x\in X}P(x)log(\frac{P(x)}{Q(x)})

KL 散度具有以下特点:

  • 非负性:KL(P||Q) ≥ 0

  • 非对称性:KL(P||Q) ≠ KL(Q||P)

  • 当且仅当 P=Q 时,KL(P||Q)=0

在 DPO 中,KL 散度约束的具体形式为:

\mathbb{D}_{KL}=[\pi(y|x)||\pi_{ref}(y|x)]

2.2,Bradley-Terry 模型

Bradley-Terry 模型是一个用于分析配对比较数据的概率模型。主要用于:

  • 对事物进行排序和评级。

  • 预测两个对象之间的胜率。

  • 从成对比较中估计对象的潜在能力值。

模型的核心思想是:

  • 每个对象 i 都有一个正实数参数 \alpha _i >0,表示其能力值

  • 对象 i 胜过对象 j 的概率为:P(i>j)=\frac{\alpha_i}{\alpha _i+\alpha _j}

  • P(i>j)+P(i<j)=1

  • 能力值 \alpha 越大,胜率越高。

2.3,DPO 模型推导

DPO 模型的目标是让语言模型输出更贴近人类偏好。它使用成对的偏好数据(preferred/dispreferred pairs)进行训练,使模型能直接学习人类的价值判断。

  • x:输入的 prompt
  • y:模型生成的回答
  • (x,y):状态-动作对
  • r(x,y):衡量回答质量的 reward 函数

受 Bradley-Terry 模型启发,定义 y_1 优于 y_2 的概率为:

P(y_1>y_2)=\frac{r(x,y_1)}{r(x,y_1)+r(x,y_2)}

为确保概率值在 [0,1] 区间,对 reward 函数进行指数变换:

P(y_1>y_2)=\frac{exp(r(x,y_1))}{exp(r(x,y_1))+exp(r(x,y_2))}

通常定义 y_1 为 winner,y_2 为 loser,所以下面这个公式把 y_1y_2 的公式表示为 y_w 优于  y_l 的概率。

接下来需要将这个概率公式转换为可优化的损失函数。为了在整个数据集上进行优化,引入期望值 \mathbb{E}

  • 训练数据集 D 包含大量的三元组 (x,y_w,y_l),其中 x 是输入,y_w 是优选输出,y_l 是次选输出。

  • 每个三元组都对应一个具体的损失值。

  • 目标是最小化整个数据集上的平均损失。

优选输出胜出的概率公式:P(y_1>y_2)=\frac{exp(r(x,y_1))}{exp(r(x,y_1))+exp(r(x,y_2))}

然后定义损失函数为:

Loss=-\mathbb{E}_{(x,y_w,y_l)\sim D}\left [ln \frac{exp(r(x,y_w))}{exp(r(x,y_w))+exp(r(x,y_l))}\right ]

ln 是为了最大化 log 概率,符合交叉熵最小化原理,同时也有数值和优化上的好处。

=-\mathbb{E}_{(x,y_w,y_l)\sim D}\left [ln \frac{1}{1+exp(r(x,y_l)-r(x,y_w))}\right ]

因为 \sigma(z)=\frac{1}{1+exp(-z)}

\frac{1}{1+exp(r(x,y_l)-r(x,y_w))}=\sigma(r(x,y_w)-r(x,y_l))

即:

=-\mathbb{E}_{(x,y_w,y_l)\sim D}\left [ ln\, \sigma(r(x,y_w)-r(x,y_l))\right ]

对于单个样本,损失函数为:

-ln\,\sigma(r(x,y_w)-r(x,y_l))

2.4,基于 KL 散度约束的奖励值的推导

接下来,根据 KL 散度计算出奖励值 r(x,y),这个奖励值将隐式受到 KL 散度约束。结合前面两部分就可以得到 DPO 的优化目标为下面公式,其中 \beta 是平衡 KL 散度和奖励函数之间的超参数。\beta 越大,模型越倾向于优化 KL 散度(限制模型变化),反之则越倾向于优化奖励函数(优化模型)。

\underset{\pi}{max}\, \mathbb {E}_{x\sim D,y\sim \pi}\left [ r(x,y)\right ]-\beta\mathbb{D}_{KL}[\pi(y|x)||\pi_{ref}(y|x)]

期望值的公式就是 \mathbb{E}_x=\sum_xp(x)f(x),可用将 KL 散度的 \pi(y|x) 认为 p(x)log\frac{\pi(y|x)}{\pi_{ref}(y|x)} 认为 f(x),转换为:

\underset{\pi}{max}\, \mathbb {E}_{x\sim D,y\sim \pi}\left [ r(x,y)\right ]-\mathbb {E}_{x\sim D,y\sim \pi}\left [ \beta\,log\frac{\pi(y|x)}{\pi_{ref}(y|x)} \right ]

合并期望:

\underset{\pi}{max}\, \mathbb {E}_{x\sim D,y\sim \pi}\left [ r(x,y)-\beta\,log\frac{\pi(y|x)}{\pi_{ref}(y|x)} \right ]

同时除 \beta

\underset{\pi}{min}\, \mathbb {E}_{x\sim D,y\sim \pi}\left [log\frac{\pi(y|x)}{\pi_{ref}(y|x)}- \frac{1}{\beta}r(x,y)\right ]

\underset{\pi}{min}\, \mathbb {E}_{x\sim D,y\sim \pi}\left [log\frac{\pi(y|x)}{\pi_{ref}(y|x)}- log\,exp(\frac{1}{\beta}r(x,y))\right ]

合并 log:

\underset{\pi}{min}\, \mathbb {E}_{x\sim D,y\sim \pi}\left [log\frac{\pi(y|x)}{\pi_{ref}(y|x) exp(\frac{1}{\beta}r(x,y))}\right ]

除 Z(x),再乘 Z(x)

Z(x)=\sum_y\pi_{ref}(y|x)exp(\frac{1}{\beta}r(x,y))

即:

\underset{\pi}{min}\, \mathbb {E}_{x\sim D,y\sim \pi}\left [log\frac{\pi(y|x)}{\pi_{ref}(y|x) exp(\frac{1}{\beta}r(x,y))\frac{1}{Z(x)}Z(x)}\right ]

\underset{\pi}{min}\, \mathbb {E}_{x\sim D,y\sim \pi}\left [log\frac{\pi(y|x)}{\frac{1}{Z(x)}\pi_{ref}(y|x) exp(\frac{1}{\beta}r(x,y))}-log\,Z(x)\right ]

在优化策略 \pi 时,由于 Z(x) 只包含参考策略 \pi_{ref} 和奖励函数 r,而不包含待优化的 \pi,所以在优化过程中可用将其视为常数项,可以忽略:

\underset{\pi}{min}\, \mathbb {E}_{x\sim D,y\sim \pi}\left [log\frac{\pi(y|x)}{\frac{1}{Z(x)}\pi_{ref}(y|x) exp(\frac{1}{\beta}r(x,y))}\right ]

带入 Z(x),分母:

\frac{1}{Z(x)}\pi_{ref}(y|x) exp(\frac{1}{\beta}r(x,y))=\frac{\pi_{ref}(y|x) exp(\frac{1}{\beta}r(x,y))}{\sum_y\pi_{ref}(y|x)exp(\frac{1}{\beta}r(x,y))}=\pi^*(y|x)

可以看到,分子是某一个特定样本,而分母是所有样本的和,所以可以认为它是一个概率分布,并把它标为 \pi^*(y|x)

所以 DPO 的优化目标转换为下面公式,它又可以转换为 KL 散度:

\underset{\pi}{min}\, \mathbb {E}_{x\sim D,y\sim \pi}\left [log\frac{\pi(y|x)}{\frac{1}{Z(x)}\pi_{ref}(y|x) exp(\frac{1}{\beta}r(x,y))}\right ]=\underset{\pi}{min}\, \mathbb {E}_{x\sim D,y\sim \pi}\left [ log\,\frac{\pi(y|x)}{\pi^*(y|x)} \right ]

最终的目的是最小化上面的公式的期望,而这个期望最小的时候,\pi(y|x)=\pi^*(y|x),这代表 \pi^* 是 \pi 的最优解,也就是说,希望:

\pi(y|x)=\frac{1}{Z(x)}\pi_{ref}(y|x)exp(\frac{1}{\beta}r(x,y))

移项:

exp(\frac{1}{\beta}r(x,y))=\frac{\pi(y|x)}{\pi_{ref}(y|x)}Z(x)

r(x,y)=\beta ln\left ( \frac{\pi(y|x)}{\pi_{ref}(y|x)}Z(x) \right )

r(x,y)=\beta ln\left ( \frac{\pi(y|x)}{\pi_{ref}(y|x)}\right )+\beta ln Z(x)

在 Bradley-Terry 模型中定义的损失函数 Loss:

-ln\,\sigma(r(x,y_w)-r(x,y_l))

把 r(x,y) 带入到损失函数中,可以得到:

-ln\,\sigma\left ( \beta \,ln\frac{r(y_w,x)}{r_{ref}(y_w,x)} +\beta\,ln\,Z(x)-\beta\,ln\frac{r(y_l,x)}{r_{ref}(y_l,x)}-\beta\,ln\,Z(x)\right )

同样,可以把不涉及到优化的 Z(x) 提出来,这样就可以得到最终的 DPO 损失函数:

-ln\,\sigma\left ( \beta\,ln\frac{r(y_w,x)}{r_{ref}(y_w,x)}-\beta\,ln \frac{r(y_l,x)}{r_{ref}(y_l,x)}\right )

这个损失函数可以通过一次前向传播,就可以计算出来对应的值。

3,vLLM

3.1,基础概念

vLLM 是一个 LLM(Large Lanuage Model)推理和部署服务库,它结合 iterative-level schedule(常被称为 continuous batching,该调度算法在 Orca 中首次被提出)和 PagedAttention 注意力算法以提高服务的吞吐量。

  • 前者(iterative-level schedule)以单轮迭代的方式对用户的请求进行处理,即 LLM 生成一个 token 后会重新调度下一轮要处理的请求。
  • 后者(PagedAttention)受操作系统虚拟内存和分页思想启发,将原本连续的 KV cache 存储在不连续的空间,以避免 KV cache 带来的显存浪费。

LLM 主要的两个接口是初始化方法和 generate 方法,前者用于实例化 LLM 对象,后者处理接收到的 prompts 和采样参数。

from vllm import LLM, SamplingParams
prompts = [
    "Hello, my name is",
    "The future of AI is",
]
sampling_params = SamplingParams(temperature=0.8, top_p=0.95)
llm = LLM(model="meta-llama/Llama-2-7b-chat-hf")
outputs = llm.generate(prompts, sampling_params)
# Print the outputs.
for output in outputs:
    prompt = output.prompt
    generated_text = output.outputs[0].text
    print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")
-------------
Prompt: 'Hello, my name is', Generated text: ' Dustin Nelson and I’m going to be your tutor!\n'
Prompt: 'The future of AI is', Generated text: ' bright, but it’s unclear how many of the blue-sky concepts we'

3.2,Scheduler & Worker

vLLM 的核心组件是 LLMEngine 类,外层接口类 LLM 和 AsyncLLMEngine 都是对 LLMEngine 的封装。LLMEngine 有两个核心组件,分别是负责请求调度的 Scheduler 和负责模型推理的 Worker,前者从等待队列中选择接下来要处理的请求,后者负责使用模型对被调度的请求进行推理。

【Scheduler】Scheduler 使用 iterative-level 策略对请求进行调度(选择要被处理的请求),被调度的请求在生成一个 token 后会被重新调度。得益于 itertive-level 策略,vLLM 能够在每一轮新的迭代时选择不固定数量的请求进行处理(即 batch size 每次都不一定相同),因此它能够尽可能多地处理请求。请求的处理通常分为两个阶段,第一个阶段对 prompt 进行处理(也被称为填充阶段,后文使用填充阶段表示这一个阶段),生成 prompt KV cache 的同时生成第一个 token,第二个阶段是生成阶段,不断预测下一个 token。

目前对 iterative-level 的实现有两种方式,一种是区分填充阶段和生成阶段,另一种是不区分这两个阶段。具体而言,同一个 batch 里被处理的请求是否均处于同一个阶段(例如填充阶段或者生成阶段。即同一批被调度的请求要么都处于填充阶段,要么都处于生成阶段,这和 huggingface 的 TGI 推理库一致。而提出 iterative-level 策略的 Orca 系统是不区分这两个阶段的。

Scheduler 中有 3 个队列,waiting(接受到的新请求会先放入 waiting 队列)、running(被调度的请求)和 swapped 队列(swapped 队列用于存放被抢占的请求。即当请求处于生成阶段时,但由于空间的不足,需暂时将 running 队列中优先级低的请求移到 swapped 队列)。在调度时,Scheduler 会按照先到先处理(first come first served)的原则从 waiting 队列中选择请求放入 running 队列(注意,实际的调度包含更多细节)。此外,Scheduler 的另一个核心组件是 BlockSpaceManager,它主要负责块表的维护。

【Worker】Worker 负责模型的执行。如果模型过大,可以将模型切分到多个 Worker 共同完成请求的处理。假设模型有 4 层,现在有 4 张卡,可以设置 Tensor Parallel=4(注意:写本文时截止 v0.4.0,vLLM 还没有支持 Pipeline Parallel),则将模型每一层切分为 4 份,每张卡存放模型的一部分。

Worker 的一个核心组件是 CacheEngine,它负责 KV cache 的初始化以及 KV cache 的相关操作。

3.3,工作流

初始化主要初始化 LLMEngine 中的 Scheduler 和 Worker 对象,Scheduler 的初始化主要是块表(block table)的初始化,Worker 的初始化包括模型的初始化以及 KV cache 的初始化。

【调度和推理】假设 vLLM 接收到 3 个请求(记为 s0,s1,s2)并放入 waiting 队列中,它们的 prompt 分别为 "Hello, my name is"、"The future of AI is" 和 "The life is"。

  • vLLM 的第一轮处理:假设 vLLM 在这一轮只能调度两个请求进行处理,那么根据先到先处理的原则,会从 waiting 队列中选择 s0 ("Hello, my name is") 和 s1 ("The future of AI is") 放入到 running 队列。对于 s0,Worker 生成的 token 为 Dustin,对于 s1,Worker 生成的 token 为 bright。同时,Worker 会将计算过程产生的 KV 值存储在 KV cache 中。

  • vLLM 的第二轮处理:由于 waiting 队列中还有一个请求 s2(The life is),因此,vLLM 在第二轮只会处理这一个请求,因为前面提到,vLLM 只会处理要么都是填充阶段的请求,要么都是生成阶段的请求。

  • vLLM 的第三轮处理:waiting 队列中没有要处理的新请求,所以会从 running 队列中选择此轮要处理的请求(这些请求均处于生成阶段)。但由于没有多余的空间,vLLM 只会选择 s0 和 s1 进行处理。经过多轮调度和推理,最终完成 3 个请求的处理,以上就是 vLLM 的工作流。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

燕双嘤

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值