LLM训练-人工强化反馈对齐算法:RLHF, RLAIF, PPO, DPO and More

参考论文:A Comprehensive Survey of LLM Alignment Techniques: RLHF, RLAIF, PPO, DPO and More

相关博客:https://wqw547243068.github.io/rlhf

参考代码:Align Anything: Training All-modality Model with Feedback

端到端推理学习增强推理能力方法

a.推理时扩展(Inference-time scaling): 如链式思维(CoT)或自我一致性(Self-Consistency),以增强模型的推理能力;【cot:核心思想是将复杂问题分解为一系列可解释的中间步骤。通过明确的推理链条,模型能够逐步解决原本可能超出其直接推理能力的问题。思维链方法特别适用于涉及多步骤推理的任务,如数学题、多重逻辑推理问题等。 Self-Consistency 自我一致提示是在 CoT 基础上进一步优化,通过采样多条推理路径,找出最一致的答案。它适用于对结果准确性要求更高的场景,避免一次性推理路径的偶然性导致错误。】

b.纯强化学习(Pure Reinforcement Learning, RL): 通过强化学习训练模型,使其在没有监督数据的情况下,通过试错学习复杂任务; 【deepseek-R1-zero】

c.监督微调结合强化学习(SFT + RL): 首先对模型进行监督微调,然后使用强化学习进行进一步优化,以提高模型的推理能力。【deepseek-R1】

d.纯监督微调和蒸馏(Pure Supervised Fine-Tuning and Distillation)仅使用监督学习和模型蒸馏技术来增强模型的推理能力。【deepseek-R1-distill蒸馏模型

一个完整的LLM训练过程包含以下几步:

  • Model Initialization:加载模型和处理器
  • 数据准备:解析数据集并设置其格式
  • 模型推理:将数据输入到模型中并获取输出
  • 梯度更新:根据损失函数更新模型参数

对齐(alignment)其作用就是让 LLM 与人类的价值观保持一致。在对齐 LLM 方面,基于人类反馈的强化学习(RLHF)是一种突破性的技术。该方法催生了 GPT-4、Claude 和 Gemini 等强大模型。RLHF 之后,人们也探索了多种多样的对齐 LLM 的方法。但是,此前还没有人全面总结对齐 LLM 与人类偏好的方法。

Salesforce 决定填补这一空白,于近日发布了一份 37 页的综述报告,其中按类别总结了现有的研究文献,并详细分析了各篇论文。

Introduction

这篇论文分为四大主题:奖励模型、反馈、强化学习(RL)、优化。每个主题又包含进一步的子主题,如图 1 所示。

xPO LLM 与人类偏好保持一致的 13 个分类方向

奖励模型的子主题包括:1. 显式奖励模型与隐式奖励模型;2. 逐点奖励模型与偏好模型;3. 响应层面的奖励与 token 层面的奖励;4. 负偏好优化。

反馈的子主题包括:1. 偏好反馈与二元反馈;2. 成对反馈与列表反馈;3. 人类反馈与 AI 反馈。

强化学习的子主题包括:1. 基于参考的强化学习与无参考的强化学习;2. 长度控制式强化学习;3. 强化学习中的不同分支;4. 在线策略强化学习与离线策略强化学习

优化的子主题包括:1. 在线 / 迭代式偏好优化与离线 / 非迭代式偏好优化;2. 分离 SFT 和对齐与合并 SFT 和对齐。

Individual Paper Reviews in Detail

1. RLHF/PPO

LLM 的预训练要用到大量来自不同来源的语料库,而这本身就无法确保这些数据集的质量。此外,LLM 的主要目标是预测下一个 token,这个目标与「有用且安全地遵从用户指令」的目标并不一致。因此,LLM 可能会输出不真实、有害或对用户无用的内容。本质上讲,这些模型并未与用户意图对齐。RLHF/PPO 的主要目标是在各种任务上对齐语言模型与用户意图,其做法是使用人类反馈来微调模型。有关这个主题的研究有很多。

2. RLAIF

获取人类偏好数据集的成本不低,因此基于人工智能反馈的强化学习(RLAIF)诞生了。此外,随着 LLM 的能力不断进步,所能收集到的 AI 偏好数据集的质量也不断提高,由此可提升 LLM 的对齐效果。

3.直接人类偏好优化

传统 RLHF 方法通常涉及到优化源自人类偏好的奖励函数。该方法虽有效,但也可能带来一些难题,比如增大计算复杂度以及在估计和优化奖励时需要考虑偏置 – 方差权衡。参阅论文《High-dimensional continuous control using generalized advantage estimation》。

近期有研究探索了其它一些旨在根据人类偏好(无需依赖某个标量的奖励信号)来直接优化 LLM 策略的方法。

这些方法的目标是通过更直接地使用偏好数据来简化对齐流程、降低计算开销以及实现更稳健的优化。通过将该问题描述为一个偏好优化问题,而不是奖励估计和最大化问题,这些方法能提供一种将语言模型与人类判断对齐的不同视角

4.token 级 DPO

使用 DPO 时,奖励会被一起分配给 prompt 和响应。相反,使用 MDP 时,奖励会被分配给各个动作。后续的两篇论文在 token 层面阐述了 DPO 并将其应用扩展到了 token 级的分析。

5.迭代式 / 在线 DPO

使用 DPO 时,会使用所有可用的偏好数据集来对齐 LLM。为了持续提升 LLM,应当实现迭代式 / 在线 DPO。这就引出了一个有趣的问题:如何高效地收集新的偏好数据集。下面两篇论文深入探讨了这一主题。

  • 自我奖励式语言模型,参阅论文《Self-rewarding language models》。
  • CRINGE,参阅论文《The cringe loss: Learning what language not to model》。

6.二元反馈

事实证明,收集偏好反馈比收集二元反馈(比如点赞或点踩)的难度大,因此后者可促进对齐过程的扩展。KTO 和 DRO 这两项研究关注的便是使用二元反馈来对齐 LLM。

  • KTO,Kahneman-Tversky 优化,参阅论文《KTO: Model alignment as prospect theoretic optimization》。
  • DRO,直接奖励优化,参阅论文《Offline regularised reinforcement learning for large language models alignment》。

7.融合 SFT 和对齐

之前的研究主要还是按顺序执行 SFT 和对齐,但事实证明这种方法很费力,并会导致灾难性遗忘。后续的研究有两个方向:一是将这两个过程整合成单一步骤;二是并行地微调两个模型,最终再进行融合。

  • ORPO,比值比偏好优化,参阅论文《ORPO: Monolithic preference optimization without reference model》。
  • PAFT,并行微调,参阅论文《PAFT: A parallel training paradigm for effective llm fine-tuning》。

8.长度控制式 DPO 和无参考 DPO

之前有研究表明,LLM 的输出往往过于冗长。为了解决这个问题,R-DPO 和 SimPO 的关注重心是在不影响生成性能的前提下实现对响应长度的控制。

此外,DPO 必需参考策略来确保已对齐模型不会与参考模型有太大偏差。相较之下,SimPO 和 RLOO 提出了一些方法,可以在不影响 LLM 效果的情况下消除对参考模型的需求

9.逐列表的偏好优化

之前在 PPO 和 DPO 方面的研究关注的是成对偏好,而 RLHF 方面的研究则是收集逐列表的偏好来加速数据收集过程,之后再将它们转换成成对偏好。尽管如此,为了提升 LLM 的性能,直接使用逐列表的数据集来执行偏好优化是可行的。以下三篇论文专门讨论了这种方法。

  • LiPO,逐列表偏好优化,参阅论文《LIPO: Listwise preference optimization through learning-to-rank》。
  • RRHF,参阅论文《RRHF: Rank responses to align language models with human feedback without tears》。
  • PRO,偏好排名优化,参阅论文《Preference ranking optimization for human alignment》。

10.负偏好优化

这些研究有一个共同前提:当前这一代 LLM 已经在翻译和总结等任务上超越了人类性能。因此,可以将 LLM 的输出视为期望响应,而无需依靠将人类标注的数据视为偏好响应;这样做是有好处的。反过来,不期望得到的响应依然也可被用于对齐 LLM,这个过程就是所谓的负偏好优化(NPO)。

  • NN,否定负例方法,参阅论文《Negating negatives: Alignment without human positive samples via distributional dispreference optimization》。
  • NPO,负例偏好优化,参阅论文《Negative preference optimization: From catastrophic collapse to effective unlearning》。
  • CPO,对比偏好优化,参阅论文《Contrastive preference optimization: Pushing the boundaries of llm performance in machine translation》。

11.纳什学习

之前的研究通常是使用逐点奖励和 BT 模型来得到成对偏好。但是,这种方法比不上直接成对偏好建模并且无法解决成对偏好中的不一致问题。为了克服这些局限,一些研究提出了纳什学习方法。

  • 根据人类反馈的纳什学习,参阅论文《Nash learning from human feedback》。
  • SPPO,自博弈偏好优化,参阅论文《A minimaximalist approach to reinforcement learning from human feedback》。
  • DNO,直接纳什优化,参阅论文《Direct nash optimization: Teaching language models to self-improve with general preferences》。

LLM 对齐(Alignment)方法:SFT、PPO、DPO 、ORPO详细介绍

LLM(大语言模型)的对齐(Alignment)方法旨在让 AI 的输出更加符合人类预期,减少错误信息、有害内容或不准确的回答。主要总结LLM训练中的基本的对齐算法, 监督微调 (SFT)、直接偏好优化 (DPO) 和近端策略优化 (PPO)等。

SFT(Supervised Fine-Tuning,监督微调)

监督微调(SFT)是 LLM 训练中的第一步,通过高质量的人工标注数据集对模型进行微调,使其具备基础的任务能力。SFT 是所有对齐方法的基础,如 RLHF、DPO 等都依赖于一个经过 SFT 训练的模型作为初始状态。

过程

  1. 数据准备:收集高质量的指令-响应(Instruction-Response)数据集,例如人类标注的数据或合成的数据。
  2. 模型微调:使用交叉熵损失(Cross-Entropy Loss)训练模型,使其学习提供与标注数据匹配的答案。
  3. 效果:使模型在常见任务(如问答、代码生成、对话等)中表现更好,提高其对指令的遵循能力。

给定输入 x(Prompt) 和目标输出 y(Response),模型的目标是最大化生成目标文本的概率:

其中:

  • Pθ​(yt​∣x,y<t​) 是 LLM 在给定上下文下预测下一个 token yt​ 的概率。
  • 训练时采用交叉熵损失(Cross Entropy Loss)来优化模型参数 θ。

SFT 仅依赖于人工标注数据,无法让模型学习偏好信息(比如不同回答的优劣)。无法动态调整:SFT 训练后,模型固定,难以针对用户反馈进行调整。缺乏探索性:模型只会学到训练数据中的模式,无法进行强化学习优化。

DPO(Direct Preference Optimization,直接偏好优化)

论文:https://arxiv.org/abs/2305.18290

参考代码:https://github.com/eric-mitchell/direct-preference-optimization

DPO(直接偏好优化)是一种比 RLHF 更简单、更高效的对齐方法。
它不需要训练奖励模型(RM)和使用强化学习(RL),而是直接优化 LLM,使其更符合人类偏好数据

偏好数据

  • 每个输入 Prompt 对应两个候选回答:一个优选(Preferred y+),一个劣选(Dispreferred y−)。
  • 例如:

Prompt: “如何写一封正式的电子邮件?”
Response 1 (优选): “在邮件中应保持正式语气,并包含称呼、正文和署名。”
Response 2 (劣选): “随便写就行了,不要太在意格式。”

优化 LLM 使其更倾向于优选回答

只需要加载2个相同的模型,其中一个推理[reference model:old策略模型],另外一个模型[policy model 策略模型]训练,直接在偏好数据上进行训练即可:

Reference Model(以下简称Ref模型)一般也用SFT阶段得到的SFT模型做初始化,在训练过程中,它的参数是冻结的。Ref模型的主要作用是防止Actor”训歪”

损失函数: DPO 直接优化模型输出的偏好分布:

其中:

  • σ :sigmoid函数
  • β :超参数,一般在0.1 – 0.5之间
  • yw :某条偏好数据中好的response,w就是win的意思
  • yl :某条偏好数据中差的response,l就是loss的意思,所以偏好数据也叫comparision data
  • πθ(yw|x) :给定输入x, 当前 策略policy model生成好的response的累积概率(每个tokne的概率求和,具体看代码)
  • πref(yl|x) :给定输入x, 原始模型(reference model)生成坏的response的累积概率

开始训练时,reference model和policy model都是同一个模型,只不过在训练过程中reference model不会更新权重。

为了方便分析,我们把log里的分式展开,然后β设为1,并且暂时不看前面的log_sigmoid,那么上面的loss可以简化为:

由于最初loss前面是有个负号的,所以优化目标是让本简化公式最大,即我们希望左半部分和右半部分的margin越大越好,左半部分的含义是good response相较于没训练之前的累积概率差值,右半部分代表bad response相较于没训练之前的累计概率差值,如果这个差值,即margin变大了,就意味着:

  • 1)左边变大,右边变小,理想情况,good response概率提升,bad response概率下降
  • 2)左边变小,右边更小,good response概率下降,但是bad response概率下降的更多,生成的时候还是倾向于good response
  • 3)左边变的更大,右边只大了一点点,和2)同理

所以这个loss颇有一种对比的感觉。

PPO(Proximal Policy Optimization,近端策略优化)

论文:https://arxiv.org/abs/1707.06347

详解:https://zhuanlan.zhihu.com/p/677607581

人人都能看懂的RL-PPO理论知识

是OpenAI在2017提出的一种强化学习算法,是基于策略优化的算法,用于训练能够最大化累积奖励的智能体。PPO算法通过在每次更新时限制新策略与旧策略之间的差异,从而更稳定地更新策略参数。这种方法有助于避免训练过程中出现的不稳定性和剧烈波动,使得算法更容易收敛并学习到更好的策略。

强化学习基本概念

  • 强化学习的两个实体:智能体(Agent)环境(Environment)
  • 强化学习中两个实体的交互:
    • 状态空间S:S即为State,指环境中所有可能状态的集合
    • 动作空间A:A即为Action,指智能体所有可能动作的集合
    • 奖励R:R即为Reward,指智能体在环境的某一状态下所获得的奖励。

以上图为例,智能体与环境的交互过程如下:

  • 在 t 时刻,环境的状态为 St ,达到这一状态所获得的奖励为 Rt
  • 智能体观测到 St 与 Rt ,采取相应动作 At
  • 智能体采取 At 后,环境状态变为 St+1 ,得到相应的奖励 Rt+1

奖励值 Rt ,它表示环境进入状态 St 下的即时奖励
但如果只考虑即时奖励,目光似乎太短浅了:当下的状态和动作会影响到未来的状态和动作,进而影响到未来的整体收益。
所以,一种更好的设计方式是:t时刻状态s的总收益 = 身处状态s能带来的即时收益 + 从状态s出发后能带来的未来收益写成表达式就是:Vt=Rt+γVt+1

其中:

  • Vt : t 时刻的总收益,注意这个收益蕴涵了“即时”和“未来”的概念
  • Rt : t 时刻的即时收益
  • Vt+1 : t+1 时刻的总收益,注意这个收益蕴涵了“即时”和“未来”的概念。而 Vt+1 对 Vt 来说就是“未来”。
  • γ :折扣因子。它决定了我们在多大程度上考虑将“未来收益”纳入“当下收益”。

关键概念:

策略函数是一个 概率密度函数(PDF),输入时当前状态s,输出为一个概率分布,表征每个 action 的概率:

动作值函数:评价在状态 st 的情况下做出动作 at的好坏程度。

状态值函数:

  • 消掉了动作 A ,这样 Vπ 只跟状态 s 与策略函数 π 有关了。
  • 给定 π,可以评价当前状态的好坏;给定状态st,可以评价策略 π的好坏。

优势函数:有些时候我们不需要描述一个行动的绝对好坏,而只需要知道它相对于平均水平的优势。也就是说,我们只想知道一个行动的相对 优势 。这就是优势函数的概念。

一个服从策略 \pi 的优势函数,描述的是它在状态 s 下采取行为 a 比随机选择一个行为好多少(假设之后一直服从策略 \pi )。数学角度上,优势函数的定义为:

长期价值可以表示为状态值函数(State Value Function)或动作值函数(Action Value Function)。

优化方法:

value-based:优化状态动作值函数Q(s) ,贪心选择(确定性策略) :Q-Learning

policy-based :直接优化策略函数π(s, a),按概率采样(随机性策略) :REINFORCE

Actor-Critic •融合上述方法,同时优化Q与π:TRPO、PPO

NLP中的强化学习

在第一部分介绍了通用强化学习的流程,那么我们要怎么把这个流程对应到NLP任务中呢?换句话说,NLP任务中的智能体、环境、状态、动作等等,都是指什么呢?

回想一下我们对NLP任务做强化学习(RLHF)的目的:我们希望给模型一个prompt,让模型能生成符合人类喜好的response。再回想一下gpt模型做推理的过程:每个时刻 t 只产生一个token,即token是一个一个蹦出来的,先有上一个token,再有下一个token。


复习了这两点,现在我们可以更好解读上面这张图了:

  • 我们先喂给模型一个prompt,期望它能产出符合人类喜好的response
  • 在 t 时刻,模型根据上文,产出一个token,这个token即对应着强化学习中的动作,我们记为At 。因此不难理解,在NLP语境下,强化学习任务的动作空间就对应着词表。
  • 在 t 时刻,模型产出token At对应着的即时收益为Rt,总收益为Vt复习一下, Vt 蕴含着“即时收益”与“未来收益”两个内容)。这个收益即可以理解为“对人类喜好的衡量”。此刻,模型的状态从St变为St+1,也就是从“上文”变成“上文 + 新产出的token”
  • 在NLP语境下,智能体是语言模型本身,环境则对应着它产出的语料

这样,我们就大致解释了NLP语境下的强化学习框架,不过针对上面这张图,你可能还有以下问题:


(1)问题1:图中的下标是不是写得不太对?例如根据第一部分的介绍, At 应该对应着 Rt+1  At+1 应该对应着 Rt+2 ,以此类推?
答:你说的对。但这里我们不用太纠结下标的问题,只需要记住在对应的response token位置,会产生相应的即时奖励和总收益即可。之所以用图中这样的下标,是更方便我们后续理解代码。


(2)问题2:我知道 At 肯定是由语言模型产生的,那么 ,Rt,Vt 是怎么来的呢,也是语言模型产生的吗?
答:先直接说结论, At 是由我们的语言模型产生的, ,Rt,Vt 则分别由另外两个模型来产生,在后文中我们会细说。


(3)问题3:语言模型的参数在什么时候更新?是观测到一个 Rt,Vt ,就更新一次参数,然后再去产生 At+1 吗?
答:当然不是。你只看到某个时刻的收益,就急着用它更新模型,这也太莽撞了。我们肯定是要等有足够的观测数据了(例如等模型把完整的response生成完),再去更新它的参数。这一点我们也放在后文细说。


(4)问题4:再谈谈 Rt,Vt 吧,在NLP的语境下我还是不太理解它们
答:

  • 首先,“收益”的含义是“对人类喜好的衡量”
  • Rt :即时收益,指语言模型当下产生token At 带来的收益
  • Vt : 实际期望总收益(即时+未来),指对语言模型“当下产生token At ,一直到整个response生产结束”后的期收益预估。因为当下语言模型还没产出 At 后的token,所以我们只是对它之后一系列动作的收益做了估计,因而称为“期望总收益”。

RLHF中的四个重要角色:

我们从第二部分中已经知道:生成token At 和对应收益 Rt,Vt 的并不是一个模型。那么在RLHF中到底有几个模型?他们是怎么配合做训练的?而我们最终要的是哪个模型?

如上图,在RLHF-PPO阶段,一共有四个主要模型,分别是:

  • Actor Model:演员模型,这就是我们想要训练的目标语言模型
  • Critic Model:评论家模型,它的作用是预估总收益 Vt
  • Reward Model:奖励模型,它的作用是计算即时收益 Rt
  • Reference Model:参考模型,它的作用是在RLHF阶段给语言模型增加一些“约束”,防止语言模型训歪(朝不受控制的方向更新,效果可能越来越差)

其中:

  • Actor/Critic Model在RLHF阶段是需要训练的(图中给这两个模型加了粗边,就是表示这个含义);而Reward/Reference Model参数冻结的。
  • Critic/Reward/Reference Model共同组成了一个“奖励-loss”计算体系(我自己命名的,为了方便理解),我们综合它们的结果计算loss,用于更新Actor和Critic Model

Actor Model (演员模型)

正如前文所说,Actor就是我们想要训练的目标语言模型。我们一般用SFT阶段产出的SFT模型来对它做初始化。

我们的最终目的是让Actor模型能产生符合人类喜好的response。所以我们的策略是,先喂给Actor一条prompt (这里假设batch_size = 1,所以是1条prompt),让它生成对应的response。然后,我们再将“prompt + response”送入我们的“奖励-loss”计算体系中去算得最后的loss,用于更新actor。

Reference Model(参考模型)

Reference Model(以下简称Ref模型)一般也用SFT阶段得到的SFT模型做初始化,在训练过程中,它的参数是冻结的。Ref模型的主要作用是防止Actor”训歪”,那么它具体是怎么做到这一点的呢?

“防止模型训歪”换一个更详细的解释是:我们希望训练出来的Actor模型既能达到符合人类喜好的目的,又尽量让它和SFT模型不要差异太大。简言之,我们希望两个模型的输出分布尽量相似。那什么指标能用来衡量输出分布的相似度呢?我们自然而然想到了KL散度

如图所示:

  • 对Actor模型,我们喂给它一个prompt,它正常输出对应的response。那么response中每一个token肯定有它对应的log_prob结果呀,我们把这样的结果记为log_probs
  • 对Ref模型,我们把Actor生成的”prompt + response”喂给它,那么它同样能给出每个token的log_prob结果,我们记其为ref_log_probs
  • 那么这两个模型的输出分布相似度就可以用ref_log_probs - log_probs来衡量,我们可以从两个方面来理解这个公式:
    • 从直觉上理解,两个分布的相似度越高,说明Ref模型对Actor模型输出的肯定性越大。即Ref模型也认为,对于某个 St ,输出某个 At 的概率也很高( P(At|St) )。这时可以认为Actor模型较Ref模型没有训歪。
    • 从KL散度上理解

(当然这里不是严格的等于,只是KL散度的近似),这个值越小意味着两个分布的相似性越高。

注:你可能已经注意到,按照KL散度的定义,这里写成log_probs - ref_log_probs更合适一些。但是如果你看过一些rlhf相关的论文的话,你可能记得在计算损失函数时,有一项Rt−KL散度 (对这个有疑惑不要紧,我们马上在后文细说),即KL散度前带了负号,所以这里我写成ref_log_probs - log_probs这样的形式,更方便大家从直觉上理解这个公式。

现在,我们已经知道怎么利用Ref模型和KL散度来防止Actor训歪了。KL散度将在后续被用于loss的计算,我们在后文中会详细解释。

Critic Model(评论家模型)

Critic Model用于预测期望总收益 Vt ,和Actor模型一样,它需要做参数更新。实践中,Critic Model的设计和初始化方式也有很多种,例如和Actor共享部分参数、从RW阶段的Reward Model初始化而来等等。我们讲解时,和deepspeed-chat的实现保持一致:从RW阶段的Reward Model初始化而来。


你可能想问:训练Actor模型我能理解,但我还是不明白,为什么要单独训练一个Critic模型用于预测收益呢?
这是因为,当我们在前文讨论总收益 Vt (即时 + 未来)时,我们是站在上帝视角的,也就是这个 Vt 就是客观存在的、真正的总收益。但是我们在训练模型时,就没有这个上帝视角加成了,也就是在 t 时刻,我们给不出客观存在的总收益 Vt ,我们只能训练一个模型去预测它。


所以总结来说,在RLHF中,我们不仅要训练模型生成符合人类喜好的内容的能力(Actor),也要提升模型对人类喜好量化判断的能力(Critic)。这就是Critic模型存在的意义。我们来看看它的大致架构:

deepspeed-chat采用了Reward模型作为它的初始化,所以这里我们也按Reward模型的架构来简单画画它。你可以简单理解成,Reward/Critic模型和Actor模型的架构是很相似的(毕竟输入都一样),同时,它在最后一层增加了一个Value Head层,该层是个简单的线形层,用于将原始输出结果映射成单一的 Vt 值。

在图中, Vt 表示Critic模型对 t 时刻及未来(response完成)的收益预估。

 Reward Model(奖励模型)

Reward Model用于计算生成token At 的即时收益,它就是RW阶段所训练的奖励模型,在RLHF过程中,它的参数是冻结的。


你可能想问:为什么Critic模型要参与训练,而同样是和收益相关的Reward模型的参数就可以冻结呢?
这是因为,Reward模型是站在上帝视角的。这个上帝视角有两层含义:

  • 第一点,Reward模型是经过和“估算收益”相关的训练的,因此在RLHF阶段它可以直接被当作一个能产生客观值的模型。
  • 第二点,Reward模型代表的含义就是“即时收益”,你的token At 已经产生,因此即时收益自然可以立刻算出。

你还可能想问:我已经用Critic预测出 Vt 了,而这个 Vt 包含了“即时”和“未来”的概念,那我还需要代表“即时”的 Rt 做什么呢?直接用 Vt 不就好了吗?


为了解答这个问题,我们先回顾下1.2部分中给出的价值函数: Vt=Rt+γVt+1
这个函数告诉我们,我们当前可以用两个结果来表示 t 时刻的总收益:

  • 结果1:Critic模型预测的 Vt
  • 结果2:Reward模型预测的 Rt 和critic模型预测的 Vt+1

那么哪一个结果更靠近上帝视角给出的客观值呢?当然是结果2,因为结果1全靠预测,而结果2中的 Rt 是事实数据。
我们知道Critic模型也是参与参数更新的,我们可以用MSE(上帝视角的客观收益-Critic模型预测的收益)来衡量它的loss。但是上帝视角的客观收益我们是不知道的,只能用已知事实数据去逼近它,所以我们就用 Rt+γ∗Vt+1 来做近似。这就是 Rt,Vt 同时存在的意义

Reward模型和critic模型非常相似,这里我们就只给出架构图,不再做过多的说明。

RLHF中的loss计算

到目前为止,我们已经基本了解了RLHF的训练框架,以及其中的四个重要角色(训练一个RLHF,有4个模型在硬件上跑,可想而知对存储的压力)。在本节中,我们一起来解读RLHF的loss计算方式。在解读中,我们会再一次理一遍RLHF的整体训练过程,填补相关细节。在这之后,我们就可以来看代码解析了。


在第三部分的讲解中,我们知道Actor和Critic模型都会做参数更新,所以我们的loss也分成2个:

  • Actor loss:用于评估Actor是否产生了符合人类喜好的结果,将作用于Actor的BWD上。
  • Critic loss:用于评估Critic是否正确预测了人类的喜好,将作用于Critic的BWD上。

我们详细来看这两者。

Actor loss

(1)直观设计

我们先来看一个直观的loss设计方式:

  • Actor接收到当前上文 St ,产出token At ( P(At|St) )
  • Critic根据 St,At ,产出对总收益的预测 Vt
  • 那么Actor loss可以设计为: 

求和符号表示我们只考虑response部分所有token的loss,为了表达简便,我们先把这个求和符号略去(下文也是同理),也就是说:

我们希望minimize这个actor_loss。


这个设计的直观解释是:

  • 当 Vt>0 时,意味着Critic对Actor当前采取的动作给了正向反馈,因此我们就需要在训练迭代中提高 P(At|St) ,这样就能达到减小loss的作用。
  • 当 Vt<0 时,意味着Critic对Actor当前采取的动作给了负向反馈,因此我们就需要在训练迭代中降低 P(At|St) ,这样就能到达到减小loss的作用。

一句话总结:这个loss设计的含义是,对上文 St 而言,如果token At 产生的收益较高,那就增大它出现的概率,否则降低它出现的概率。

(2)引入优势(Advantage)

在开始讲解之前,我们举个小例子:
假设在王者中,中路想支援发育路,这时中路有两种选择:1. 走自家野区。2. 走大龙路。
中路选择走大龙路,当她做出这个决定后,Critic告诉她可以收1个人头。结果,此刻对面打野正在自家采灵芝,对面也没有什么苟草英雄,中路一路直上,最终收割2个人头。
因为实际收割的人头比预期要多1个,中路尝到了甜头,所以她增大了“支援发育路走大龙路”的概率。
这个多出来的“甜头”,就叫做“优势”(Advantage)。


对NLP任务来说,如果Critic对 At 的总收益预测为 Vt ,但实际执行 At 后的总收益是 Rt+γ∗Vt+1 ,我们就定义优势为:

我们用 Advt 替换掉 Vt ,则此刻actor_loss变为:

(3)重新设计 Rt

总结一下,到目前为止,我们的actor_loss形式为:

同时注意,这个actor_loss应该是response的所有token loss的sum或者avg。这里为了表达方便,我们的公式略去了求和或求平均的符号。

按照这个理解, Rt 应该表示每个Actor产出token At 带来的即时收益,正如下图所示(其中 T 表示最后一个时刻):

但在deepspeed-chat的RLHF实践中,对 Rt 做了另一种设计:

基于这些,上面这个对 Rt 的设计可理解成:

  • t≠T时,我们更加关心Actor是否有在Ref的约束下生产token At
  • $ t=T时,我们不仅关心Actor是否遵从了Ref的约束,也关心真正的即时收益Rt


为什么只有最后一个时刻的 Rt 被纳入了考量呢?这是因为在Reward模型训练阶段,就是用这个位置的 Rt 来表示对完整的prompt + response的奖励预测(但不妨碍你理解成是执行完 AT 的即时奖励),然后用这个指标来做模型eval的(但是Reward训练阶段算loss时,还是考虑了response部分所有token输出的reward值)。所以到了RLHF的场景下,其余时刻的即时奖励,我们就用“Actor是否遵循了Ref的约束”来进行评价。


需要注意的是, Rt 的设计并不只有这一种。deepspeed在自己的代码注释中也有提过,可以尝试把最后一个时刻的 RT 替换成所有token的即时奖励的平均值。如果站在这个角度理解的话,我们同样也可以尝试在每一个位置的奖励衡量上引入 Rt 。

代码实践如下:

def compute_rewards(self, prompts, log_probs, ref_log_probs, reward_score,
                        action_mask):
        """
        reward_function:计算最终的reward分数
        复习一下几个相关参数的默认值:
        self.kl_ctl = 0.1
        self.clip_reward_value = 5
        
        对于batch中的某个prompt来说,它最终的reward分数为:
        (1) 先计算actor和ref_model的logit相似度: -self.kl_ctl * (log_probs - ref_log_probs)
            其实写成self.kl_ctl * (ref_log_probs - log_probs)更好理解些
            这个值越大,说明ref_model对actor生成的结果的认可度越高(即表明rlhf没有训歪),
            没有训歪的情况下我们也应该给模型一些奖励,这个奖励就是self.kl_ctl * (ref_log_probs - log_probs)
            
        (2)由于我们只取最后一个token对应位置的分数作为reward_score,因此我们只需要:
            self.kl_ctl * (ref_log_probs - log_probs)的最后一位 + reward_score
         
         (3) 同时我们对reward_score也做了大小限制,最大不超过self.clip_reward_value(超过统一给成self.clip_reward_value),
             最小不低于-self.clip_reward_value(低于统一给成-self.clip_reward_value)
        
         (4) 最后返回的rewards大小为:(batch_size, 各条数据的长度),对batch中的每条数据来说:
             - response的最后一位:self.kl_ctl * (ref_log_probs - log_probs)的最后一位 + reward_score
             - response的其余位置:self.kl_ctl * (ref_log_probs - log_probs)
        
        """

        kl_divergence_estimate = -self.kl_ctl * (log_probs - ref_log_probs)
        rewards = kl_divergence_estimate
        # ---------------------------------------------------------------------------------------------------
        # response开始的位置
        # (因为我们对prompt做过padding处理,因此batch中每个prompt长度一致,也就意味着每个response开始的位置一致)
        # (所以这里start是不加s的,只是一个int)
        # ---------------------------------------------------------------------------------------------------
        start = prompts.shape[1] - 1
        # ---------------------------------------------------------------------------------------------------
        # response结束的位置
        # (因为一个batch中,每个response的长度不一样,所以response的结束位置也不一样)
        # (所以这里end是加s的,ends的尺寸是(batch_size,)
        # ---------------------------------------------------------------------------------------------------
        ends = start + action_mask[:, start:].sum(1) + 1
        # ---------------------------------------------------------------------------------------------------
        # 对rewards_score做限制
        # ---------------------------------------------------------------------------------------------------
        reward_clip = torch.clamp(reward_score, -self.clip_reward_value,
                                  self.clip_reward_value)
        batch_size = log_probs.shape[0]
        for j in range(batch_size):
            rewards[j, start:ends[j]][-1] += reward_clip[j] # 

        return rewards

(4)重新设计优势

好,再总结一下,目前为止我们的actor_loss为:

同时,我们对 Rt 进行来改造,使其能够衡量Actor模型是否遵从了Ref模型的约束。
现在我们把改造焦点放在 Advt 上,回想一下,既然对于收益而言,分为即时和未来,那么对于优势而言,是不是也能引入对未来优势的考量呢?这样,我们就可以把 Advt 改写成如下形式:


(熟悉强化学习的朋友应该能一眼看出这是GAE,这里我们不打算做复杂的介绍,一切都站在直觉的角度理解)
其中,新引入的 λ 也是一个常量,可将其理解为权衡因子,直觉上看它控制了在计算当前优势时对未来优势的考量。(从强化学习的角度上,它控制了优势估计的方差和偏差)

看到这里,你可能想问:这个代表未来优势的 Advt+1 ,我要怎么算呢?
注意到,对于最后一个时刻 t ,它的未来收益( VT+1 )和未来优势( AdvT+1 )都是0,也就是 AdvT=RT−VT ,这是可以直接算出来的。而有了 AdvT ,我们不就能从后往前,通过动态规划的方法,把所有时刻的优势都依次算出来了吗?

代码实践如下(其中返回值中的returns表示实际收益,将被用于计算Critic模型的loss,可以参见4.2,其余细节都在代码注释中):

 def get_advantages_and_returns(self, values, rewards, start):
        """
        Adopted from https://github.com/CarperAI/trlx/blob/main/trlx/models/modeling_ppo.py#L134
        
        没有引入GAE前的t时刻的优势值:
        detal_t = r_t + gamma * V_t+1 - V_t
        其中:
            - r_t表示t时刻的即时收益
            - V_t+1表示未来时刻的预期收益
            - r_t + gamma * V_t+1可理解成t时刻的实际预期收益
            - V_t可理解成t时刻的预估预期收益(是模型,例如critic model自己估算出来的)
        
        引入GAE后的t时刻的优势值:
        A_t = delta_t + gamma * lambda * A_t+1
        粗暴理解为在t时刻时,不仅考虑当下优势,还考虑了未来的优势
        为了知道A_t, 我们得知道A_t+1,所以在本算法中采取了从后往前做动态规划求解的方法,也即:
        假设T是最后一个时刻,则有A_T+1 = 0, 所以有: A_T = delta_T
        知道了A_T, 就可以依次往前倒推,把A_t-1, A_t-2之类都算出来了
        
        引入GAE后t时刻的实际预期收益
        returns_t = A_t + V_t
                  = delta_t + gamma * lambda * A_t+1 + V_t
                  = r_t + gamma * V_t+1 - V_t + gamma * lambda * A_t+1 + V_t
                  = r_t + gamma * (V_t+1 + lambda * A_t+1)
        
        注意,这里不管是advantages还是returns,都只算response的部分
        """
        
        # Adopted from https://github.com/CarperAI/trlx/blob/main/trlx/models/modeling_ppo.py#L134
        lastgaelam = 0
        advantages_reversed = []
        length = rewards.size()[-1]
        # 注意这里用了reversed,是采取从后往前倒推计算的方式
        for t in reversed(range(start, length)):
            nextvalues = values[:, t + 1] if t < length - 1 else 0.0
            delta = rewards[:, t] + self.gamma * nextvalues - values[:, t]
            lastgaelam = delta + self.gamma * self.lam * lastgaelam
            advantages_reversed.append(lastgaelam)
        advantages = torch.stack(advantages_reversed[::-1], dim=1) # 优势
        returns = advantages + values[:, start:] # 实际收益
        # values: 预期收益
        return advantages.detach(), returns

(5)PPO-epoch: 引入新约束

总结一下,目前为止我们的actor_loss为:

同时

  • 我们已经对Rt进行来改造,使其能够衡量Actor模型是否遵从了Ref模型的约束。
  • 我们已经对Advt进行改造,使其不仅考虑了当前时刻的优势,还考虑了未来的优势

基于这些改造,我们重新理一遍RLHF-PPO的训练过程。

  • 第一步,我们准备一个batch的prompts
  • 第二步,我们将这个batch的prompts喂给Actor模型,让它生成对应的responses
  • 第三步,我们把prompt+responses喂给我们的Critic/Reward/Reference模型,让它生成用于计算actor/critic loss的数据,按照强化学习的术语,我们称这些数据为经验(experiences)。critic loss我们将在后文做详细讲解,目前我们只把目光聚焦到actor loss上
  • 第四步,我们根据这些经验,实际计算出actor/critic loss,然后更新Actor和Critic模型

这些步骤都很符合直觉,但是细心的你肯定发现了,文字描述中的第四步和图例中的第四步有差异:图中说,这一个batch的经验值将被用于n次模型更新,这是什么意思呢?

我们知道,在强化学习中,收集一个batch的经验是非常耗时的。对应到我们RLHF的例子中,收集一次经验,它要等四个模型做完推理才可以,正是因此,一个batch的经验,只用于计算1次loss,更新1次Actor和Critic模型,好像有点太浪费了。

所以,我们自然而然想到,1个batch的经验,能不能用来计算ppo-epochs次loss,更新ppo-epochs次Actor和Critic模型?简单写一下伪代码,我们想要:

# --------------------------------------------------------------
# 初始化RLHF中的四个模型
# --------------------------------------------------------------
actor, critic, reward, ref = initialize_models()

# --------------------------------------------------------------
# 训练
# --------------------------------------------------------------
# 对于每一个batch的数据
for i in steps: 
    # 先收集经验值
    exps = generate_experience(prompts, actor, critic, reward, ref)
    # 一个batch的经验值将被用于计算ppo_epochs次loss,更新ppo_epochs次模型
    # 这也意味着,当你计算一次新loss时,你用的是更新后的模型
    for j in ppo_epochs:
        actor_loss = cal_actor_loss(exps, actor)
        critic_loss = cal_critic_loss(exps, critic)
        
        actor.backward(actor_loss)
        actor.step()
        
        critc.backward(critic_loss)
        critic.step()

而如果我们想让一个batch的经验值被重复使用ppo_epochs次,等价于我们想要Actor在这个过程中,模拟和环境交互ppo_epochs次。举个例子:

  • 如果1个batch的经验值只使用1次,那么在本次更新完后,Actor就吃新的batch,正常和环境交互,产出新的经验值
  • 但如果1个batch的经验值被使用ppo_epochs次,在这ppo_epochs中,Actor是不吃任何新数据,不做任何交互的,所以我们只能让Actor“模拟”一下和环境交互的过程,吐出一些新数据出来。

那怎么让Actor模拟呢?很简单,让它观察一下之前的数据长什么样,让它依葫芦画瓢,不就行了吗?我们假设最开始吃batch,吐出经验的actor叫 Actorold ,而在伪代码中,每次做完ppo_epochs而更新的actor叫 Actornew ,那么我们只要尽量保证每次更新后的 Actornew 能模仿最开始的那个 Actorold ,不就行了吗?

诶!是不是很眼熟!两个分布,通过什么方法让它们相近!那当然是KL散度!所以,再回到我们的actor_loss上来,它现在就可被改进成:

我们再稍作一些改动将log去掉(这个其实不是“稍作改动去掉log”的事,是涉及到PPO中重要性采样的相关内容,大家有兴趣可以参考这篇):

其中, Pold 表示真正吃了batch,产出经验值的Actor;P表示ppo_epochs中实时迭代更新的Actor,它在模仿 Pold 的行为。所以这个公式从直觉上也可以理解成:在Actor想通过模拟交互的方式,使用一个batch的经验值更新自己时,它需要收到真正吃到batch的那个时刻的Actor的约束,这样才能在有效利用batch,提升训练速度的基础上,保持训练的稳定。

但是,谨慎的你可能此时又有新的担心了:虽然我们在更新Actor的过程中用 Actorold 做了约束,但如果 Actorold 的约束能力不够,比如说 P(At|St)/Pold(At|St) 还是超出了可接受的范围,那怎么办?

很简单,那就剪裁(clip)它吧!

我们给 P(At|St)/Pold(At|St) 设置一个范围,例如(0.8 ,1.2),也就是如果这个值一旦超过1.2,那就统一变成1.2;一旦小于0.8,那就统一变成0.8。这样就能保证 Actor 和 Actorold 的分布相似性在我们的掌控之内了。此时actor_loss变为:

这时要注意,如果超过变化范围,将 P(At|St)/Pold(At|St) 强制设定为一个常数后,就说明这一部分的loss和Actor模型无关了,而 Advt 这项本身也与Actor无关。所以相当于,在超过约束范围时,我们停止对Actor模型进行更新。

整体代码如下:

    def actor_loss_fn(self, logprobs, old_logprobs, advantages, mask):
        """
        logprobs: 实时计算的,response部分的prob(只有这个是随着actor实时更新而改变的)
        old_logprobs:老策略中,response部分的prob (这个是固定的,不随actor实时更新而改变)
        advantages: 老策略中,response部分每个token对应的优势(这个是固定的,不随actor实时更新而改变)
        mask:老策略中,response部分对应的mask情况这个是固定的,不随actor实时更新而改变)
        
        之所以要引入logprobs计算actor_loss,是因为我们不希望策略每次更新的幅度太大,防止模型训歪
        
        self.cliprange: 默认值是0.2
        """
        ## policy gradient loss
        # -------------------------------------------------------------------------------------
        # 计算新旧策略间的KL散度
        # -------------------------------------------------------------------------------------
        log_ratio = (logprobs - old_logprobs) * mask
        ratio = torch.exp(log_ratio)
        # -------------------------------------------------------------------------------------
        # 计算原始loss和截断loss
        # -------------------------------------------------------------------------------------
        pg_loss1 = -advantages * ratio
        pg_loss2 = -advantages * torch.clamp(ratio, 1.0 - self.cliprange, 1.0 + self.cliprange)
        pg_loss = torch.sum(torch.max(pg_loss1, pg_loss2) * mask) / mask.sum() # 最后是取每个非mask的response token的平均loss作为最终loss
        return pg_loss

(6)Actor loss小结

(1)~(5)中我们一步步树立了actor_loss的改进过程,这里我们就做一个总结吧:

  • 我们已经对Rt进行来改造,使其能够衡量Actor模型是否遵从了Ref模型的约束
  • 我们已经对Advt进行改造,使其不仅考虑了当前时刻的优势,还考虑了未来的优势
  • 我们重复利用了1个batch的数据,使本来只能被用来做1次模型更新的它现在能被用来做ppo_epochs次模型更新。我们使用真正吃了batch,产出经验值的那个时刻的Actor分布来约束ppo_epochs中更新的Actor分布
  • 我们考虑了剪裁机制(clip),在ppo_epochs次更新中,一旦Actor的更新幅度超过我们的控制范围,则不对它进行参数更新。

Critic loss

我们知道,1个batch产出的经验值,不仅被用来更新Actor,还被用来更新Critic。对于Critic loss,我们不再像Actor loss一样给出一个“演变过程”的解读,我们直接来看它最后的设计。

首先,在之前的解说中,你可能有这样一个印象:

  • Vt :Critic对t时刻的总收益的预估,这个总收益包含即时和未来的概念(预估收益)
  • Rt+γ∗Vt+1 :Reward计算出的即时收益 Rt ,Critic预测出的 t+1 及之后时候的收益的折现,这是比 Vt 更接近t时刻真值总收益的一个值(实际收益)

所以,我们的第一想法是:

现在,我们对“实际收益”和“预估收益”都做一些优化。

(1)实际收益优化

我们原始的实际收益为 Rt+γ∗Vt+1 ,但是当我们在actor_loss中引入“优势”的概念时,“优势”中刻画了更为丰富的实时收益信息,所以,我们将实际收益优化为: Advt+Vt

(2)预估收益优化

我们原始的预估收益为 Vt 。
类比于Actor,Critic模型在ppo_epochs的过程中也是不断更新的。所以这个 Vt 可以理解成是 Criticold ,也就是真正吃了batch,参与产出经验的那个时候的Critic产出的收益预测结果。


我们同样想用旧模型去约束新模型,但对于Critic我们采用的约束策略就比较简单了,我们直接看代码,从中可以看出,我们用老 Vt 设计了了一个变动范围,然后用这个变动范围去约束新 Vt

# self.cliprange_value是一个常量
# old_values: 老critic的预测结果
# values:新critic的预测结果
values_clipped = torch.clamp(
            values,
            old_values - self.cliprange_value,
            old_values + self.cliprange_value,
        )

那么最终我们就取实际收益和预估收益的MSE做为loss就好,这里注意,计算实际收益时 Advt,Vt 都是老Critic(真正吃了batch的那个)产出的结果,而预估收益是随着ppo_epochs而变动的。


代码如下:

def critic_loss_fn(self, values, old_values, returns, mask):
        """
        values: 实时critic跑出来的预估预期收益(是变动的,随着ppo epoch迭代而改变)
        old_values:老critic跑出来的预估预期收益(是固定值)
        returns:实际预期收益
        mask:response部分的mask
        
        self.cliprange_value = 0.2
        """
        ## value loss
        # 用旧的value去约束新的value
        values_clipped = torch.clamp(
            values,
            old_values - self.cliprange_value,
            old_values + self.cliprange_value,
        )
        if self.compute_fp32_loss:
            values = values.float()
            values_clipped = values_clipped.float()
        
        # critic模型的loss定义为(预估预期收益-实际预期收益)**2
        vf_loss1 = (values - returns)**2
        vf_loss2 = (values_clipped - returns)**2
        vf_loss = 0.5 * torch.sum(
            torch.max(vf_loss1, vf_loss2) * mask) / mask.sum() # 同样,最后也是把critic loss平均到每个token上
        return vf_loss

PPO优化目标

(1)策略梯度算法:更新幅度大,不稳定

(2)TRPO(信任区域算法):加入KL散度约束条件,但需计算二阶导数,计算量大

(3)PPO(近端策略优化算法):

这里At为优势函数:Critic Model用于估计状态的价值函数 V(st),从而计算策略梯度中的优势值A(t),下面的 r(st,at) 函数就是 RM 模型的输出: 用于计算生成某个token的即时收益 。 下图转换参考:https://zhuanlan.zhihu.com/p/651780908

PPO训练流程

  • Actor Model:要训练的目标语言模型,策略网络
  • Critic Model:预估总收益
  • Reward Model:计算即时收益
  • Reference Model:在RLHF阶段给语言模型增加一些“约束”,防止语言模型训偏

ColossalChat RLFH过程也是非常接近ChatGPT的RLFH过程,RLFH过程主要涉及四个模型分别是Actor、Critic、RM、STF,损失函数也是由三个损失函数组成分别是策略损失、价值损失和 PTX 损失。

ColossalChat RLFH过程

策略损失函数计算:

策略损失计算过程

通过instruction dataset数据训练STF模型,通过计算sft model的logits和actor model(没有经过sft的model)的logits计算kl散度,然后加上reward model的打分变成 reward R奖励值,避免太过偏向reward model加入和sft model的kl散度,同时也避免强化学习将actor模型训歪。

这样做的目的就是避免模型训飞,让模型更新保持在一个小范围内。

价值损失函数计算:

上式R是reward model和sft model计算出来的反馈分数,V(s)是Critic Model输出的价值分数。主要是衡量reward分数和价值函数分数的均方误差。

ptx的损失计算:

计算Actor输出response和输入语料的回答部分的交叉熵损失函数,用来在PPO梯度中加入预训练梯度,以保持语言模型原有性能防止遗忘。这个就是instruct gpt论文中在强化学习中加入预训练梯度以防过度拟合ppo数据带来nlp通用任务能力的下降操作。

总的强化学习损失计算:

为什么RLHF中,PPO需要Critic模型而不是直接使用RewardModel

在强化学习中,PPO(Proximal Policy Optimization)算法是一种基于策略梯度的方法,用于训练强化学习智能体。PPO算法中引入Critic模型的主要目的是为了提供一个价值估计器,用于评估状态或状态动作对的价值,从而辅助策略的更新和优化。

虽然奖励模型(Reward Model)可以提供每个状态或状态动作对的即时奖励信号,但它并不能直接提供对应的价值估计。奖励信号只反映了当前动作的即时反馈,而并没有提供关于在长期时间尺度上的价值信息。

Critic模型的作用是估计状态或状态动作对的长期价值,也称为状态值函数或动作值函数。Critic模型能够学习和预测在当前状态下采取不同动作所获得的累积奖励,它提供了对策略改进的指导。PPO算法使用Critic模型的估计值来计算优势函数,从而调整策略的更新幅度,使得更有利于产生更高长期回报的动作被选择。

另外,Critic模型还可以用于评估不同策略的性能,为模型的评估和选择提供依据。PPO算法中的Actor-Critic架构允许智能体同时学习策略和价值函数,并通过协同训练来提高性能。

因此,在RLHF(Reinforcement Learning from Human Feedback)中,PPO算法需要Critic模型而不是直接使用奖励模型,是为了提供对状态或状态动作对的价值估计,并支持策略的改进和优化。Critic模型的引入可以提供更全面和准确的信息,从而增强算法的训练效果和学习能力。

即时奖励 与 状态动作对的长期价值 的差别是什么?

即时奖励(Immediate Reward)和状态动作对的长期价值(Long-Term Value)代表了强化学习中不同的概念和时间尺度。

即时奖励是指智能体在执行某个动作后立即获得的反馈信号。它通常由环境提供,用于表示当前动作的好坏程度。即时奖励是一种即时反馈,可以指示当前动作的立即结果是否符合智能体的目标。

而状态动作对的长期价值涉及更长时间尺度上的评估,它考虑了智能体在当前状态下选择不同动作所导致的未来回报的累积。长期价值可以表示为状态值函数(State Value Function)或动作值函数(Action Value Function)。

状态值函数(V-function)表示在给定状态下,智能体从该状态开始执行一系列动作,然后按照某个策略进行决策,从而获得的预期累积回报。状态值函数估计了智能体处于某个状态时所能获得的长期价值,反映了状态的优劣程度

动作值函数(Q-function)则表示在给定状态下,智能体选择某个动作后,按照某个策略进行决策,从该状态转移到下一个状态并获得预期累积回报的价值。动作值函数估计了在给定状态下采取不同动作的长期价值,可以帮助智能体选择在每个状态下最优的动作

长期价值考虑了智能体在未来的决策过程中所能获得的累积回报,相比之下,即时奖励只提供了当前动作的即时反馈。长期价值对智能体的决策具有更全面的影响,可以帮助智能体更好地评估当前状态和动作的长期效果,并指导智能体在长期时间尺度上作出更优的决策。

在强化学习中,长期价值的估计对于确定性策略选择和价值优化非常重要,而即时奖励则提供了对当前动作的直接反馈。这两者相互补充,结合起来可以帮助智能体实现更好的决策和学习效果。

PPO中优势函数指什么

在Proximal Policy Optimization(PPO)算法中,优势函数(Advantage Function)用于评估状态-动作对的相对优劣程度。它衡量了执行某个动作相对于平均水平的优劣,即在给定状态下采取某个动作相对于采取平均动作的效果。

优势函数可以用以下方式定义:Advantage(s, a) = Q(s, a) - V(s)

其中,Advantage(s, a)表示在状态 s 下采取动作 a 的优势函数值,Q(s, a) 表示状态动作对 (s, a) 的动作值函数(也称为动作优势函数),V(s) 表示状态值函数。

优势函数的作用在于帮助评估当前动作的相对价值,以便在策略更新过程中确定应采取的动作。通过比较不同动作的优势函数值,可以决定哪些动作是更好的选择。正的优势函数值表示执行的动作比平均水平更好,而负的优势函数值表示执行的动作比平均水平更差。

在PPO算法中,优势函数用于计算策略更新的目标,以便调整策略概率分布来提高优势函数为正的动作的概率,并降低优势函数为负的动作的概率,从而改进策略的性能。

总而言之,优势函数在PPO算法中用于评估状态-动作对的相对优劣,帮助确定应该采取的动作,并在策略更新过程中引导策略向更优的方向调整。

GRPO (Group Relative Policy Optimization)

ORPO偏好优化(Odds Ratio Preference Optimization)

ORPO: Monolithic Preference Optimization without Reference Model

核心: 最大化正样本的生成概率,最小化负样本的生成概率 。相比DPO 【加载2个模型,其中一个推理,另外一个训练,直接在偏好数据上进行训练】,只加载训练模型,直接在偏好数据上进行训练

本文提出的算法ORPO是对SFT的改进,通过修改SFT阶段的损失函数,将类似于DPO中偏好对齐的思想引入到SFT中,提出一种无需奖励模型和参考模型算法。同时,ORPO只有一阶段,不需要像DPO一样需要先SFT再DPO对齐。在众多大模型任务上的实验结果表明,与SFT,DPO等算法相比,ORPO更有优势。

本文提出的算法ORPO是对SFT的改进,修改了SFT阶段的损失函数。同时,与DPO/PPO相比,ORPO将原本分两步进行的过程(SFT+DPO/PPO)合并为一步,更加简洁高效。

现在有许多方法可以使大型语言模型(LLM)与人类偏好保持一致。以人类反馈为基础的强化学习(RLHF)是最早的方法之一,并促成了ChatGPT的诞生,但RLHF的成本非常高。与RLHF相比,DPO、IPO和KTO的成本明显更低,因为它们不需要奖励模型。

虽然DPO和IPO的成本较低,但它们仍需训练两个不同的模型。首先是监督微调(SFT)步骤,即训练模型按指令回答问题,然后使用SFT模型作为初始化和参考,以使模型与人类偏好一致。

ORPO是另一种新的LLM对齐方法,这种方法甚至不需要SFT模型。通过ORPO,LLM可以同时学习回答指令和满足人类偏好。

对于STF,它是在与选择的答案配对的提示上进行训练的。用于sft的数据集可以与偏好优化使用的相同,但不包括”被拒绝”的答案。所以可以直观地认为,应该能够微调一个基础LLM,使其在学习如何回答指令的同时,也学会惩罚和偏好某些答案。

SFT只用正样本更新策略,没有考虑到负样本,会把负样本生成的概率同时拉高,如下图所示:

由于SFT的损失函数对于rejected data没有惩罚项,SFT之后正样本和负样本的生成概率有可能同时上升。

odds定义:模型θ生成 输出序列y的可能性 比 不生成y序列的可能性 比值。

OR为正负样本的odds的比值:

ORPO算法要做的就是最大化OR,即最大化正样本的生成概率,最小化负样本的生成概率,LOR项用了和DPO类似的logsigmoid的形式:

ORPO就是在这个理论基础上建立的,ORPO简单地通过添加负对数似然损失与OR损失(OR代表奇异比)来修改训练损失:

OR损失对被拒绝的答案进行弱惩罚,而对选择的答案进行强有力的奖励。这里包含了一个超参数lambda用于加权OR损失。通过ORPO的损失,模型在学习了SFT期间的内容的同时,也学会了人类偏好。

ORPO需要数千个训练步骤来学习如何区分选择的响应和拒绝的响应。为了获得类似的结果,应该训练ORPO至少2000步,总批大小为64(如论文所述)。

ORPO 已经可以在Hugging Face库上使用了,并且它因为只修改了损失函数,所以可以很好的与现有的Lora方法集成

ORPO是一种单步微调和对准指令llm的新方法。它不需要任何奖励或SFT模型,并且ORPO比DPO和RLHF更简单。根据论文ORPO的性能与DPO相当或略好。但是ORPO需要几千个训练步骤来学习好的和坏的反应之间的区别。

SimPO 简单偏好优化:

算法的核心是将偏好优化目标中的奖励函数与生成指标对齐不需要ref参考模型

SimPO 包含两个主要组件:(1)在长度上归一化的奖励【/|y|】,其计算方式是使用策略模型的奖励中所有 token 的平均对数概率;(2)目标奖励差额 γ ,用以确保获胜和失败响应之间的奖励差超过这个差额 γ 。

DPO 是最常用的离线偏好优化方法之一。DPO 并不会学习一个显式的奖励模型,而是使用一个带最优策略的闭式表达式来对奖励函数 r 进行重新参数化:

其中 π_θ 是策略模型,π_ref 是参考策略(通常是 SFT 模型),Z (x) 是配分函数。通过将这种奖励构建方式整合进 Bradley-Terry (BT) 排名目标,DPO 可使用策略模型而非奖励模型来表示偏好数据的概率,从而得到以下目标:

DPO 的奖励与生成之间的差异。使用 (1) 式作为隐式的奖励表达式有以下缺点:(1) 训练阶段需要参考模型 π_ref,这会带来额外的内存和计算成本;(2) 训练阶段优化的奖励与推理所用的生成指标之间存在差异。具体来说,在生成阶段,会使用策略模型 π_θ 生成一个能近似最大化平均对数似然的序列,定义如下:

在解码过程中直接最大化该指标是非常困难的,为此可以使用多种解码策略,如贪婪解码、波束搜索、核采样和 top-k 采样。此外,该指标通常用于在语言模型执行多选任务时对选项进行排名。在 DPO 中,对于任意三元组 (x, y_w, y_l),满足奖励排名 r (x, y_w) > r (x, y_l) 并不一定意味着满足似然排名:

图片

事实上,在使用 DPO 训练时,留存集中大约只有 50% 的三元组满足这个条件。

构建在长度上归一化的奖励。很自然地,我们会考虑使用 (3) 式中的 p_θ 来替换 DPO 中的奖励构建,使其与引导生成的似然指标对齐。这会得到一个在长度上归一化的奖励:

其中 β 是控制奖励差异大小的常量。该团队发现,根据响应长度对奖励进行归一化非常关键;从奖励公式中移除长度归一化项会导致模型倾向于生成更长但质量更低的序列。这样一来,构建的奖励中就无需参考模型了,从而实现比依赖参考模型的算法更高的内存和计算效率。

SimPO 目标

目标奖励差额。另外,该团队还为 Bradley-Terry 目标引入了一个目标奖励差额项 γ > 0,以确保获胜响应的奖励 r (x, y_w) 超过失败响应的奖励 r (x, y_l) 至少 γ

两个类之间的差额已知会影响分类器的泛化能力。在使用随机模型初始化的标准训练设置中,增加目标差额通常能提升泛化性能。在偏好优化中,这两个类别是单个输入的获胜或失败响应。

在实践中,该团队观察到随着目标差额增大,生成质量一开始会提升,但当这个差额变得过大时,生成质量就会下降。DPO 的一种变体 IPO 也构建了与 SimPO 类似的目标奖励差额,但其整体目标的效果不及 SimPO。

目标。最后,通过将 (4) 式代入到 (5) 式中,可以得到 SimPO 目标:

总结起来,SimPO 采用了与生成指标直接对齐的隐式奖励形式,从而消除了对参考模型的需求。此外,其还引入了一个目标奖励差额 γ 来分离获胜和失败响应

KTO:Kahneman-Tversky Optimisation

特点:

KTO关注的是答案偏离平均水准的程度——比平均好还是坏。所以它的训练数据集是对单个问答的“好/差”标注,而不再是成对数据间谁好谁差(所以用户对LLM结果的点赞或踩就可以当做反馈使用了)。

KTO不需要偏好数据,可以直接利用二元信号标记的数据来训练算法,对于负样本更加敏感。 KTO并不需要一个数据对,只需要对生成的结果进行good/bad的二元标注即可。 【比如:ChatGPT UI 界面会输出两个答案,用户可以选择哪个更好,适用于从生产环境中运行的聊天模型的训练】

实验表明,KTO算法在一定参数范围内能够超过DPO算法,并且KTO可以处理数据正负样本不平衡的情况。同时,在跳过SFT阶段的情况下,直接使用KTO相比于直接使用 DPO,效果有很大提升。在数据正负样本比例失衡/偏好数据有非传递性/偏好数据有噪声/的情况下,使用KTO可能是更好的选择。

KTO 使用 Kahneman-Tversky 人类效用模型,论文提出直接最大化生成效用的 HALO, 而不是最大化偏好的对数可能性。

  • 在1B~30B尺度上与基于偏好的方法的性能相匹配或超过,尽管它只从二进制信号(0或者1)中学习输出是否可取。
  • 没有一个 HALO 普遍优越;
  • 最佳损失取决于最适合给定设置的归纳偏差,经常被忽视的考虑因素。

KTO算法的具体步骤如下:

  1. 定义效用函数:根据前景理论中的效用函数公式,定义一个效用函数,用于计算模型输出相对于参考点的效用。
  2. 计算参考点:根据概率分布Q(X’, Y’ | x, y),计算出一个参考点,用于衡量模型输出的效用。
  3. 计算模型输出的效用:对于每个输入,计算模型输出相对于参考点的收益或损失,然后使用效用函数计算这些收益或损失的效用。
  4. 优化模型参数:优化模型参数以最大化模型输出的总效用。

KTO 损失函数本质是把 pair-wise 公式变成 point-wise 方式,结合了HALOs以及二元信号数据的思想提出使用Kahneman-Tversky 优化的KTO算法:

其中 zo是KL散度项,参考点zo为最优策略下reward的期望值,最终可以推导成KL散度的形式,y’表示任意输出,在实际训练中,Z0表示batch平均水准的程度Z0当前batch里面的样本进行估计得到的,平均 reward,代表不好不坏的居中的结果。 LKTO 就是DPO中推导的reward函数形式。

按照上面的定义估计z0是不切实际的,因为从πθ采样很慢,人类无法感知πθ引起的完整分布。

这个估计是有偏差的,但这是可取的,因为它使我们更接近人类如何构建他们的主观参考点。

实际上KTO相对比DPO差异就两点

  • 对正负样本进行了加权:DPO里面是使用正负样本的reward差值进行sigmoid映射,但是KTO里面使用reward模型与KL散度之间的差异!(说是KL散度,但其实也是bad的log比值数值!不过不是同一个pair
  • 注意:在实践的时候,KL项并不参与反向传播,这其实就跟DPO更相似的。DPO使一个数据对,但是这里把DPO给拆分了,相当于对每一个样本单独进行最大化或最小化了,以及进行加权。另一个作用就是,如果 rKTO(x,y) 的差异与KL散度有足够区别的话,那对应的Loss也就比较小。因此,KTO会更加鼓励差异大的数据对。
  • 但其实我们可以从KTO的目标函数直接看到。由于KTO是分别针对单条数据,如果数据是正样本,那么一定要超过 zo 才会产生预测正确反馈;对于负样本,需要低于 zo才会产生预测正确反馈

KTO和DPO的选择

  • 数据比例:如果数据集是以good/bad形式进行标注,并且数据比例不平衡,那么选择KTO
  • 数据质量:如果你的偏好数据质量高,数据噪声小,那么DPO的效果更好。由于目前公开的数据集中存在的噪声较大,这就能解释为什么KTO的效果会超过DPO了。
  • 理论分析:KTO不会从负样本中学习到很高的反馈,也不会从正样本中学习到很低的反馈(所以对噪声比较鲁棒)

KTO 的工作原理:

  • 如果模型以直接(blunt manner)方式增加了理想示例的奖励,那么 KL 惩罚也会增加,并且不会取得任何进步。这迫使模型准确地了解是什么让输出变得理想,这样就可以增加奖励,同时保持 KL 项持平(甚至减少)。
  • 实际实现中,KL 项是通过当前batch里面的正负样本进行估计得到的可以认为是batch样本的平均水平】,详细 debug KTOTrainer 源代码

对成对偏好数据进行分配

  • 与大多数比对方法一样,DPO 需要一个成对偏好数据集(x, y_w, y_l),够根据一组标准(如有益性或有害性)来标记哪种模型响应更好。
  • 实践过程中,创建这些数据是一项耗时且成本高昂的工作。
  • ContextualAI 提出替代方案,称为 Kahneman-Taversky 优化(KTO),完全根据被标记为「好」或「坏」的样本(例如在聊天 UI 中看到的图标👍或👎)来定义损失函数。这些标签更容易获得, KTO 是一种很有前景的方法,不断更新在生产环境中运行的聊天模型。

与此同时,这些方法都有相应的超参数,其中最重要的是 β ,控制对使用模型的偏好程度的权重。这些方法已经在第三方库(如 huggingface TRL)中实现

KTO 数据集

KTO 不需要成对的偏好数据,实验时直接将 GPT-4 生成的响应归类为「好」标签,将 Llama Chat 13b 的响应视为「坏」标签。

KTO数据集与偏好数据集类似,但不同于给出一个更优的回答和一个更差的回答,KTO数据集对每一轮问答只给出一个 true/false 的 label。 除了 instruction 以及 input 组成的人类最终输入和模型回答 output ,KTO 数据集还需要额外添加一个 kto_tag 列(true/false)来表示人类的反馈。在一轮问答中其格式如下:

[
  {
    "instruction": "人类指令(必填)",
    "input": "人类输入(选填)",
    "output": "模型回答(必填)",
    "kto_tag": "人类反馈 [true/false](必填)"
  }
]

对于上述格式的数据, dataset_info.json 中的 数据集描述 应为:

"数据集名称": {
  "file_name": "data.json",
  "columns": {
    "prompt": "instruction",
    "query": "input",
    "response": "output",
    "kto_tag": "kto_tag"
  }
}

代码实现:

基于pytorch、deepspeed、transformers代码:https://github.com/PKU-Alignment/align-anything/tree/main/align_anything/trainers/text_to_text

sft训练代码:

def loss(self, sft_batch: SupervisedBatch) -> dict[str, torch.Tensor]:
    """Loss function for supervised finetuning."""
    outputs = self.model(**self.infer_batch(sft_batch))
    return {'loss': outputs.loss}

def train_step(self, sft_batch: SupervisedBatch) -> dict[str, Any]:
    """Performs a single training step."""
    loss = self.loss(sft_batch)['loss']
    self.model.backward(loss)
    self.model.step()

    return {
        'train/loss': loss.item(),
        'train/lr': self.model.optimizer.param_groups[0]['lr'],
    }

dpo训练代码:

https://blog.csdn.net/weixin_43013480/article/details/141370269

# 从 logits(未归一化的概率分布)中,提取 labels 对应类别的对数概率(log probabilities)。
def gather_log_probabilities(
    logits: torch.Tensor,  # size = (B, L, V)
    labels: torch.LongTensor,  # size = (B, L)
) -> torch.Tensor:  # size = (B, L)
    """Gather log probabilities of the given labels from the logits."""
    log_probs = F.log_softmax(logits, dim=-1)  # size = (B, L, V)
    gathered_log_probs = torch.gather(  # size = (B, L, 1)
        log_probs,
        dim=-1,
        index=labels.unsqueeze(dim=-1).to(torch.int64),
    )
    return gathered_log_probs.squeeze(dim=-1)  # size = (B, L)
def compute_log_probs(
    self,
    model: AutoModelForCausalLM,
    batch: PreferenceBatch,
) -> torch.Tensor:
    """Compute log probabilities of given sequences."""
    # 获得所有可能输出的log概率,logits 表示每个 token 位置的 未归一化的概率分布
    logits = model(**self.infer_batch(batch)).logits
    device = logits.device
    input_ids = batch['input_ids']
    #取得每个样本的回复长度,用于截取模型输出
    batch_size = len(batch['meta_info']['response_lens'])
    logprob_list = []
    for idx in range(batch_size):
        response_length = batch['meta_info']['response_lens'][idx]
        # 去除填充 (PAD) token,避免计算无效 token 的概率。
        raw_input_id = strip_pad(input_ids[idx], self.tokenizer.pad_token_id)
        #只保留 回复部分的 logits,丢弃 prompt 部分。 
        logit = logits[idx][-response_length:].unsqueeze(0)
        input_id = raw_input_id[-response_length:].unsqueeze(0)
        #计算对应的better 和worse 序列token 对数概率
        log_p = gather_log_probabilities(logit[:, :-1], input_id[:, 1:])
        logprob_list.append(log_p.squeeze(0))
    # 不同样本的 log_probs 长度不同,使用 pad_sequence 进行 padding,补齐到相同长度。
    return torch.nn.utils.rnn.pad_sequence(
        logprob_list, batch_first=True, padding_value=0.0
    ).to(device)

def loss(  # pylint: disable=too-many-locals
    self,
    batch: PreferenceBatch,
) -> dict[str, torch.Tensor]:
    """Loss function for the DPO algorithm."""
   #计算当前模型 (self.model.module) 在 batch 上的 log 概率。
    sequence_log_probs = self.compute_log_probs(
        self.model.module,
        batch,
    )
# better_sequence_log_probs (用户偏好的回复)
# worse_sequence_log_probs (用户不喜欢的回复)
    (
        better_sequence_log_probs,  # size = (B, L - 1)
        worse_sequence_log_probs,  # size = (B, L - 1)
    ) = sequence_log_probs.chunk(chunks=2, dim=0)
# 计算参考模型 (self.reference_model.module) 的对数概率 (log_probs)。
# reference_model 通常是 原始未优化的模型,作为对比基准。
# torch.no_grad() 表示 不计算梯度,避免影响参考模型。
    with torch.no_grad():
        ref_sequence_log_probs = self.compute_log_probs(  # size = (2 * B, L - 1)
            self.reference_model.module,
            batch,
        )
        ref_better_sequence_log_probs, ref_worse_sequence_log_probs = (
            ref_sequence_log_probs.chunk(chunks=2, dim=0)
        )

    losses = []
    better_sample_rewards = []
    worse_sample_rewards = []

    batch_size = better_sequence_log_probs.size(0)
    for i in range(batch_size):
# 计算 更好/更差回复的总 log 概率(即累加 token 级别 log 概率)。
        better_log_prob = better_sequence_log_probs[i, :].sum(dim=-1)
        worse_log_prob = worse_sequence_log_probs[i, :].sum(dim=-1)
        ref_better_log_prob = ref_better_sequence_log_probs[i, :].sum(dim=-1)
        ref_worse_log_prob = ref_worse_sequence_log_probs[i, :].sum(dim=-1)
# 当前模型比参考模型更偏好 better 回复 的程度。
        better_log_ratio = better_log_prob - ref_better_log_prob
# 当前模型比参考模型更偏好 worse 回复 的程度。
        worse_log_ratio = worse_log_prob - ref_worse_log_prob
# 计算 better 和 worse 的 log 比值差
# 使用 -logsigmoid(x) 计算负对数 sigmoid 损失,优化模型使其更倾向 better 回复。
# logsigmoid 的性质:
# 如果 x 很大,logsigmoid(x) ≈ 0,意味着损失小,模型已经正确偏好 better response。
# 如果 x 很小或负,logsigmoid(x) ≈ x,意味着损失大,模型没有正确区分 better 和 worse,需要优化。
        losses.append(
            -F.logsigmoid(
                self.cfgs.train_cfgs.scale_coeff * (better_log_ratio - worse_log_ratio),
            ),
        )
        better_sample_rewards.append(
            self.cfgs.train_cfgs.scale_coeff * better_log_ratio.detach(),
        )
        worse_sample_rewards.append(self.cfgs.train_cfgs.scale_coeff * worse_log_ratio.detach())
    loss = torch.stack(losses).mean()  # size = ()
    better_sample_reward = torch.stack(better_sample_rewards)  # size = (B,)
    worse_sample_reward = torch.stack(worse_sample_rewards)  # size = (B,)
# 计算 奖励 (reward)、准确率 (accuracy) 和奖励间距 (margin)。
    reward = better_sample_reward + worse_sample_reward  # size = (B,)
    reward_accuracy = (better_sample_reward > worse_sample_reward).float().mean()  # size = ()
    reward_margin = better_sample_reward - worse_sample_reward  # size = (B,)

    return {
        'loss': loss,
        'reward': reward,
        'better_sample_reward': better_sample_reward,
        'worse_sample_reward': worse_sample_reward,
        'reward_accuracy': reward_accuracy,
        'reward_margin': reward_margin,
    }

def train_step(
    self,
    batch: PreferenceBatch,
) -> dict[str, Any]:
    """Perform a single training step for DPO."""
    loss_dict = self.loss(batch=batch)
    loss = loss_dict['loss']
    self.model.backward(loss)
    self.model.step()

    with torch.no_grad():
        reward = loss_dict['reward'].mean()
        better_sample_reward = loss_dict['better_sample_reward'].mean()
        worse_sample_reward = loss_dict['worse_sample_reward'].mean()
        reward_accuracy = loss_dict['reward_accuracy']
        reward_margin = loss_dict['reward_margin'].mean()

        loss = get_all_reduce_mean(loss)
        reward = get_all_reduce_mean(reward)
        better_sample_reward = get_all_reduce_mean(better_sample_reward)
        worse_sample_reward = get_all_reduce_mean(worse_sample_reward)
        reward_accuracy = get_all_reduce_mean(reward_accuracy)
        reward_margin = get_all_reduce_mean(reward_margin)

    return {
        'train/loss': loss.item(),
        'train/reward': reward.item(),
        'train/better_sample_reward': better_sample_reward.item(),
        'train/worse_sample_reward': worse_sample_reward.item(),
        'train/reward_accuracy': reward_accuracy.item(),
        'train/reward_margin': reward_margin.item(),
        'train/lr': self.model.optimizer.param_groups[0]['lr'],
    }

ppo训练代码:

#使用策略模型 (Actor Model) 生成文本,并返回其 input_ids 和 attention_mask。
def actor_step(self, mini_prompt_only_batch: PromptOnlyBatch) -> dict[str, Any]:
    infer_batch = self.infer_batch(mini_prompt_only_batch)
    actor_batch = copy.deepcopy(infer_batch)
    sequences = self.actor_model.module.generate(
        **infer_batch,
        generation_config=self.generation_config,
        synced_gpus=True,
        do_sample=True,
    )
    attention_mask = sequences.not_equal(self.tokenizer.pad_token_id)
    actor_batch['input_ids'] = sequences
    actor_batch['attention_mask'] = attention_mask

    return actor_batch
# 计算奖励值 (reward) 和对抗奖励值 (reward_values)。 
def reward_model_step(self, actor_batch: PromptOnlyBatch) -> dict[str, Any]:
        reward_batch = copy.deepcopy(actor_batch)
        if self.reward_tokenizer is not self.tokenizer:
            reward_tokenize_output = batch_retokenize(
                actor_batch['input_ids'],
                src_tokenizer=self.tokenizer,
                dest_tokenizer=self.reward_tokenizer,
                skip_special_tokens=True,
                device=self.args.device,
            )
            reward_batch['input_ids'] = reward_tokenize_output['input_ids']
            reward_batch['attention_mask'] = reward_tokenize_output['attention_mask']
        reward_infer_batch = self.reward_infer_batch(reward_batch)
        reward_batch['reward'] = self.reward_model(**reward_infer_batch).end_scores.squeeze(dim=-1)
        critic_infer_batch = self.reward_infer_batch(actor_batch)
        scores = self.reward_critic_model(**critic_infer_batch).scores
        reward_batch['reward_values'] = scores.squeeze(dim=-1)[:, :-1]

        return reward_batch
#冻结模型参数,避免影响训练,采样多个 mini-batch,生成文本,计算奖励,计算 log 概率 (log_probs),计算参考模型的 log 概率 (ref_log_probs)
# 经验回放:生成训练数据并计算指标
  @torch.no_grad()
    def rollout(self, prompt_only_batch: PromptOnlyBatch) -> list[dict[str, Any]]:
        """Rollout a batch of experiences."""
        # freeze the model for rolling out
        self.set_train(mode=False)

        total_batch_size = prompt_only_batch['input_ids'].size(0)
        micro_batch_size = int(self.cfgs.train_cfgs.per_device_train_batch_size)
        micro_inference_batches = []
        micro_training_batches = []
        mini_batch = {}
        for i in range(0, total_batch_size, micro_batch_size):

            mini_batch = {
                key: prompt_only_batch[key][i : i + micro_batch_size] for key in prompt_only_batch
            }

            # actor generation
            actor_batch = self.actor_step(mini_batch)
            # reward model and reward critic model scoring
            reward_batch = self.reward_model_step(actor_batch)
            # calculate the log probabilities
            logits = self.actor_model(**actor_batch).logits
            ref_logits = self.actor_reference_model(**actor_batch).logits
            log_probs = gather_log_probabilities(logits[:, :-1], actor_batch['input_ids'][:, 1:])
            ref_log_probs = gather_log_probabilities(
                ref_logits[:, :-1], actor_batch['input_ids'][:, 1:]
            )

            micro_training_batch = {}
            micro_training_batch['prompt_idx'] = mini_batch['input_ids'].size(-1) - 1
            micro_training_batch['log_probs'] = log_probs
            micro_training_batch['ref_log_probs'] = ref_log_probs
            micro_training_batch['reward'] = reward_batch['reward']
            micro_training_batch['reward_values'] = reward_batch['reward_values']

            mini_batch['input_ids'] = reward_batch['input_ids']
            mini_batch['attention_mask'] = actor_batch['attention_mask']
            # add rollout results to the batches
            micro_inference_batches.append(mini_batch)
            micro_training_batches.append(micro_training_batch)

        # unfreeze the model for training
        self.set_train()

        return micro_inference_batches, micro_training_batches

#计算策略梯度损失
# 计算 PPO 损失函数:
# ratios = exp(new_log_probs - old_log_probs)(新旧策略比)。
# 裁剪 ratios 避免策略剧烈变化(PPO 关键)。
# return -masked_mean(surrogate, mask):最大化优势 𝐴𝑡
   
def actor_loss_fn(
        self,
        log_probs: torch.Tensor,  # size = (B, L - S)
        old_log_probs: torch.Tensor,  # size = (B, L - S)
        advantages: torch.Tensor,  # size = (B, L - S)
        mask: torch.BoolTensor,  # size = (B, L - S)
    ) -> torch.Tensor:  # size = ()
        # size = (B, L - S)
        ratios = torch.exp(log_probs - old_log_probs)
        surrogate1 = advantages * ratios
        surrogate2 = advantages * torch.clamp(
            ratios,
            1.0 - self.clip_range_ratio,
            1.0 + self.clip_range_ratio,
        )
        surrogate = torch.minimum(surrogate1, surrogate2)
        return -masked_mean(surrogate, mask)  # size = ()
#  rl_step函数是训练过程中使用强化学习(RL)更新策略的一步。在PPo算法中,rl_step是用来更新策略网络(actor)和价值网络(critic)的一部分。具体来说,这个函数通过计算强化学习损失(actor loss和critic loss),并通过反向传播优化这两个网络。
# reward_critic_model 评估奖励函数的 价值估计,用于计算 优势函数 𝐴𝑡不是直接计算奖励,而是估算未来可能获得的奖励。主要用于时间差分(TD learning)更新策略,类似于 价值函数。

def rl_step(
        self, inference_batch: dict[str, torch.Tensor], training_batch: dict[str, torch.Tensor]
    ) -> dict[str, Any]:
        """Perform a single update step with RL loss."""
        old_log_probs = training_batch['log_probs']
        ref_log_probs = training_batch['ref_log_probs']
        reward = training_batch['reward']
        old_reward_values = training_batch['reward_values']
        start = training_batch['prompt_idx']

        input_ids = inference_batch['input_ids']
        attention_mask = inference_batch['attention_mask']

        sequence_mask = attention_mask[:, 1:]

        with torch.no_grad():
            old_rewards = self.add_kl_divergence_regularization(
                reward,
                old_log_probs,
                ref_log_probs,
                sequence_mask,
            )
            reward_advantages, reward_returns = self.get_advantages_and_returns(
                old_reward_values,
                old_rewards,
                sequence_mask,
                start,
            )

        logits = self.actor_model(**inference_batch, use_cache=False).logits
        log_probs = gather_log_probabilities(logits[:, :-1], input_ids[:, 1:])
        actor_loss = self.actor_loss_fn(
            log_probs[:, start:],
            old_log_probs[:, start:],
            reward_advantages,
            sequence_mask[:, start:],
        )
        self.actor_model.backward(actor_loss)
        self.actor_model.step()

        reward_values = self.reward_critic_model(**inference_batch).scores
        reward_values = reward_values.squeeze(dim=-1)[:, :-1]
        reward_critic_loss = self.critic_loss_fn(
            reward_values[:, start:],
            old_reward_values[:, start:],
            reward_returns,
            sequence_mask[:, start:],
        )
        self.reward_critic_model.backward(reward_critic_loss)
        self.reward_critic_model.step()

        with torch.no_grad():
            mask = sequence_mask[:, start:]
            kl_divergence = ((old_log_probs - ref_log_probs)[:, start:] * mask).sum(dim=-1).mean()
            mean_generated_length = mask.sum(dim=-1).float().mean()
            max_generated_length = mask.sum(dim=-1).float().max()

            reward = reward.mean()
            reward_with_kl_penalty = (old_rewards[:, start:] * mask).sum(dim=-1).mean()
            reward_advantage = masked_mean(reward_advantages, mask)
            reward_return = masked_mean(reward_returns, mask)
            reward_value = masked_mean(reward_values[:, start:], mask)

            actor_loss = get_all_reduce_mean(actor_loss)
            reward_critic_loss = get_all_reduce_mean(reward_critic_loss)
            reward = get_all_reduce_mean(reward)
            reward_with_kl_penalty = get_all_reduce_mean(reward_with_kl_penalty)
            reward_advantage = get_all_reduce_mean(reward_advantage)
            reward_return = get_all_reduce_mean(reward_return)
            reward_value = get_all_reduce_mean(reward_value)
            kl_divergence = get_all_reduce_mean(kl_divergence)
            mean_generated_length = get_all_reduce_mean(mean_generated_length)
            max_generated_length = get_all_reduce_max(max_generated_length)

        dist.barrier()

        return {
            'train/actor_loss': actor_loss.item(),
            'train/reward_critic_loss': reward_critic_loss.item(),
            'train/reward': reward.item(),
            'train/reward_with_kl_penalty': reward_with_kl_penalty.item(),
            'train/reward_advantage': reward_advantage.item(),
            'train/reward_return': reward_return.item(),
            'train/reward_value': reward_value.item(),
            'train/kl_divergence': kl_divergence.item(),
            'train/actor_lr': self.actor_model.optimizer.param_groups[0]['lr'],
            'train/reward_critic_lr': self.reward_critic_model.optimizer.param_groups[0]['lr'],
            'train/mean_generated_length': mean_generated_length.item(),
            'train/max_generated_length': max_generated_length.item(),
        }

    def ptx_step(self, ptx_batch: dict[str, torch.Tensor]) -> dict[str, Any]:
        """Perform a single update step with PTX loss."""
        ptx_loss = self.actor_model(**self.infer_batch(ptx_batch)).loss
        self.actor_model.backward(self.ptx_coeff * ptx_loss)
        self.actor_model.step()
        ptx_loss = get_all_reduce_mean(ptx_loss)
        return {
            'train/ptx_loss': ptx_loss.item(),
        }

    def train(self) -> None:
        """Train the model."""
        self.logger.print('***** Running training *****')

        progress_bar = tqdm(
            total=self.total_training_steps,
            desc=f'Training 1/{self.cfgs.train_cfgs.epochs} epoch',
            position=0,
            leave=True,
            disable=not is_main_process(),
        )

        if self.cfgs.data_cfgs.eval_datasets:
            self.logger.print('\n***** Evaluating at the beginning *****')
            self.eval()

        num_prompt_only_batches = len(self.prompt_only_dataloader)
        num_ptx_batches = len(self.ptx_dataloader)
        num_ptx_replicas = (num_prompt_only_batches + num_ptx_batches - 1) // num_ptx_batches
        for epoch in range(int(self.cfgs.train_cfgs.epochs)):
            for prompt_only_batch, ptx_batch in zip(
                self.prompt_only_dataloader,
                itertools.chain.from_iterable([self.ptx_dataloader] * num_ptx_replicas),
            ):
                inference_batches, training_batches = self.rollout(prompt_only_batch)

                if self.use_ptx:
                    ptx_batches = self.split_ptx_micro_batches(ptx_batch)
                else:
                    ptx_batches = [None for _ in range(len(inference_batches))]
                torch.cuda.empty_cache()

                for _ in range(self.cfgs.train_cfgs.update_iters):
                    for inference_batch, training_batch, ptx_batch in zip(
                        inference_batches, training_batches, ptx_batches
                    ):
                        rl_info = self.rl_step(inference_batch, training_batch)

                        torch.cuda.empty_cache()
                        self.logger.log(rl_info, step=self.global_step)
                        if self.use_ptx:
                            ptx_info = self.ptx_step(ptx_batch)
                            torch.cuda.empty_cache()
                            self.logger.log(ptx_info, step=self.global_step)

                        self.global_step += 1
                        progress_bar.set_description(
                            f'Training {epoch + 1}/{self.cfgs.train_cfgs.epochs} epoch '
                            f'(reward {rl_info["train/reward"]:.4f})',
                        )
                        progress_bar.update(1)

                        if self.global_step % self.cfgs.logger_cfgs.save_interval == 0:
                            self.logger.print(f'Saving checkpoint at step {self.global_step} ...')
                            self.save(tag=self.global_step)
                            self.logger.print('Checkpoint saved.')

                        if (
                            self.cfgs.data_cfgs.eval_datasets
                            and self.cfgs.train_cfgs.eval_strategy == 'steps'
                            and self.global_step % self.cfgs.train_cfgs.eval_interval == 0
                        ):
                            self.logger.print(
                                f'\n***** Evaluating at step {self.global_step} *****',
                            )
                            self.eval()

RM奖励模型训练代码:

    def loss(
        self,
        batch: PreferenceBatch,
    ) -> dict[str, torch.Tensor]:
        """Loss function for the reward model."""
        (
            better_input_ids,  # size = (B, L)
            worse_input_ids,  # size = (B, L)
        ) = batch[
            'input_ids'
        ].chunk(chunks=2, dim=0)
        assert better_input_ids.size(0) == worse_input_ids.size(0), 'batch size mismatch!'

# scores:一般来说,这代表模型在每个时间步骤(或输入分段)上的奖励得分,通常是一个形状为 (B, L, 1) 的张量,其中 B 是批量大小,L 是输入序列的长度,1 是奖励得分的维度。
#end_scores:通常表示输入序列的结束阶段的奖励得分,这可能是在整个序列处理完成后,模型计算出的最终奖励。
        output = self.model(**self.infer_batch(batch))
        scores = output.scores
        end_scores = output.end_scores
        higher_rewards, lower_rewards = scores.squeeze(dim=-1).chunk(chunks=2, dim=0)
        higher_end_reward, lower_end_reward = end_scores.squeeze(dim=-1).chunk(chunks=2, dim=0)

        loss = -F.logsigmoid(higher_end_reward - lower_end_reward).mean()

        if self.cfgs.train_cfgs.regularization > 0.0:
            loss = (
                loss
                + self.cfgs.train_cfgs.regularization
                * torch.stack([lower_end_reward, higher_end_reward]).square().mean()
            )

        accuracy = (higher_end_reward > lower_end_reward).float().mean()  # size = ()
        return {
            'loss': loss,  # size = ()
            'higher_end_reward': higher_end_reward,  # size = (B,)
            'lower_end_reward': lower_end_reward,  # size = (B,)
            'higher_rewards': higher_rewards,  # size = (B, L)
            'lower_rewards': lower_rewards,  # size = (B, L)
            'accuracy': accuracy,  # size = ()
        }

    def train_step(
        self,
        batch: PreferenceBatch,
    ) -> dict[str, Any]:
        """Perform a single training step."""
        loss_dict = self.loss(batch)
        loss = loss_dict['loss']
        self.model.backward(loss)
        self.model.step()

        accuracy = loss_dict['accuracy']

        loss = get_all_reduce_mean(loss)
        accuracy = get_all_reduce_mean(accuracy)

        return {
            'train/loss': loss.item(),
            'train/accuracy': accuracy.item(),
            'train/lr': self.model.optimizer.param_groups[0]['lr'],
        }

orpo 训练代码:

相关介绍:https://github.com/Paul33333/ORPO https://zhuanlan.zhihu.com/p/688583797

# 从 logits(未归一化的概率分布)中,提取 labels 对应类别的对数概率(log probabilities)。
def gather_log_probabilities(
    logits: torch.Tensor,  # size = (B, L, V)
    labels: torch.LongTensor,  # size = (B, L)
) -> torch.Tensor:  # size = (B, L)
    """Gather log probabilities of the given labels from the logits."""
    log_probs = F.log_softmax(logits, dim=-1)  # size = (B, L, V)
    gathered_log_probs = torch.gather(  # size = (B, L, 1)
        log_probs,
        dim=-1,
        index=labels.unsqueeze(dim=-1).to(torch.int64),
    )
    return gathered_log_probs.squeeze(dim=-1)  # size = (B, L)

# compute_log_probs 的作用是计算给定序列的 log 概率(对数概率),主要用于评估语言模型(LLM)的生成质量。
def compute_log_probs(
        self,
        model: AutoModelForCausalLM,
        batch: PreferenceBatch,
    ) -> torch.Tensor:
        """Compute log probabilities of given sequences."""
        logits = model(**self.infer_batch(batch)).logits
        device = logits.device
        input_ids = batch['input_ids']
        batch_size = len(batch['meta_info']['response_lens'])
        logprob_list = []
        for idx in range(batch_size):
            response_length = batch['meta_info']['response_lens'][idx]  # for the eos token
            logit = logits[idx][-response_length:].unsqueeze(0)
            input_id = input_ids[idx][-response_length:].unsqueeze(0)
# logit[:, :-1]取 response 部分的 logits,去掉最后一个 token(因为 logits 预测的是下一个 token)input_id[:, 1:]: 取 response 部分的 token IDs,从第二个 token 开始(因为 log_probs 计算的是下一个 token 概率)。
作用:计算 response 部分每个 token 的 log 概率(对 logit 的 softmax 取对数)。
            log_p = gather_log_probabilities(logit[:, :-1], input_id[:, 1:]) 
            logprob_list.append(log_p.squeeze(0))
#pad填充,返回张量形状 (B, max_L_resp)
        return torch.nn.utils.rnn.pad_sequence(
            logprob_list, batch_first=True, padding_value=0.0
        ).to(device)
class ORPOTrainer(DPOTrainer):

    def loss(  # pylint: disable=too-many-locals
        self,
        batch: PreferenceBatch, # size = (2*B, L)
    ) -> dict[str, torch.Tensor]:
        """Loss function for the ORPO algorithm."""
        sequence_log_probs = self.compute_log_probs(
            self.model.module,
            batch,
        )
        (
            better_sequence_log_probs,  # size = (B, L - 1)
            worse_sequence_log_probs,  # size = (B, L - 1)
        ) = sequence_log_probs.chunk(chunks=2, dim=0)

        losses = []
        better_sample_rewards = []
        worse_sample_rewards = []

        better_input_ids, worse_input_ids = batch['input_ids'].chunk(chunks=2, dim=0)
        better_attention_mask, worse_attention_mask = batch['attention_mask'].chunk(chunks=2, dim=0)

        batch_size = better_input_ids.size(0)
#diverge_index 代表 better 和 worse 输入序列开始不同的位置:diverge_index,即它之后的 token 是模型生成的部分。
        for i in range(batch_size):
            if torch.all(torch.eq(better_input_ids[i], worse_input_ids[i])).item():
                continue
            better_end_index = better_attention_mask[i].nonzero()[-1].squeeze().item()
            worse_end_index = worse_attention_mask[i].nonzero()[-1].squeeze().item()
            diverge_index = (
                (better_input_ids[i] != worse_input_ids[i]).nonzero()[0].squeeze().item()
            )
            assert 0 <= diverge_index <= better_end_index, 'diverge index is out of range!'
            assert 0 <= diverge_index <= worse_end_index, 'diverge index is out of range!'
# better_seq_slice 和 worse_seq_slice 取从 diverge_index 开始到序列结束的部分(即模型生成的 token)。
            better_seq_slice = slice(diverge_index, better_end_index + 1)
            worse_seq_slice = slice(diverge_index, worse_end_index + 1)
            better_seq_length = better_end_index + 1
            worse_seq_length = worse_end_index + 1

            # size = ()
# better_log_prob: 计算 better 部分的总 log 概率。
# worse_log_prob: 计算 worse 部分的总 log 概率。
# 计算 对数比率(log ratio):
            better_log_prob = better_sequence_log_probs[i, better_seq_slice].sum(dim=-1)
            worse_log_prob = worse_sequence_log_probs[i, worse_seq_slice].sum(dim=-1)
            better_log_ratio = better_log_prob / better_seq_length
            worse_log_ratio = worse_log_prob / worse_seq_length
# 计算 ORPO 的 odds ratio loss:
            log_odds = (better_log_ratio - worse_log_ratio) - (
                torch.log1p(-torch.exp(better_log_ratio)) - torch.log1p(-torch.exp(worse_log_ratio))
            )
#  better 的 log 概率明显高于 worse,从而优化生成策略。
            odds_ratio_loss = -F.logsigmoid(log_odds)
# 最终损失
            sft_loss = -better_log_ratio
            losses.append(
                sft_loss + self.cfgs.train_cfgs.scale_coeff * odds_ratio_loss,
            )
            better_sample_rewards.append(
                self.cfgs.train_cfgs.scale_coeff * better_log_ratio.detach(),
            )
            worse_sample_rewards.append(self.cfgs.train_cfgs.scale_coeff * worse_log_ratio.detach())

        loss = torch.stack(losses).mean()  # size = ()
        better_sample_reward = torch.stack(better_sample_rewards)  # size = (B,)
        worse_sample_reward = torch.stack(worse_sample_rewards)  # size = (B,)
        reward = better_sample_reward + worse_sample_reward  # size = (B,)
        reward_accuracy = (better_sample_reward > worse_sample_reward).float().mean()  # size = ()
        reward_margin = better_sample_reward - worse_sample_reward  # size = (B,)

        return {
            'loss': loss,
            'reward': reward,
            'better_sample_reward': better_sample_reward,
            'worse_sample_reward': worse_sample_reward,
            'reward_accuracy': reward_accuracy,
            'reward_margin': reward_margin,
        }


def main():
    # setup distribution training
    deepspeed.init_distributed()
    current_device = get_current_device()
    torch.cuda.set_device(current_device)

    # read default configs from the yaml file
    task = os.path.join('text_to_text', 'orpo')
    dict_cfgs, ds_cfgs = read_cfgs(mode='train', task=task)

    # get custom configs from command line
    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    _, unparsed_args = parser.parse_known_args()
    keys = [k[2:] for k in unparsed_args[1::2]]
    values = list(unparsed_args[2::2])
    unparsed_args = dict(zip(keys, values))
    for k, v in unparsed_args.items():
        dict_cfgs = update_dict(dict_cfgs, custom_cfgs_to_dict(k, v))

    # setup training
    cfgs = dict_to_namedtuple(dict_cfgs)
    seed_everything(cfgs.train_cfgs.seed)

    # finetune the model
    trainer = ORPOTrainer(cfgs=cfgs, ds_cfgs=ds_cfgs)
    trainer.train()
    trainer.save()

SimPO训练代码:

https://blog.csdn.net/weixin_43013480/article/details/141370269

# compute_log_probs 的作用是计算给定序列的 log 概率(对数概率),主要用于评估语言模型(LLM)的生成质量。

def compute_log_probs(
        self,
        model: AutoModelForCausalLM,
        batch: PreferenceBatch,
    ) -> torch.Tensor:
        """Compute log probabilities of given sequences."""
        logits = model(**self.infer_batch(batch)).logits
        device = logits.device
        input_ids = batch['input_ids']
        batch_size = len(batch['meta_info']['response_lens'])
        logprob_list = []
        for idx in range(batch_size):
            response_length = batch['meta_info']['response_lens'][idx]
            raw_input_id = strip_pad(input_ids[idx], self.tokenizer.pad_token_id)
            logit = logits[idx][-response_length:].unsqueeze(0)
            input_id = raw_input_id[-response_length:].unsqueeze(0)
            log_p = gather_log_probabilities(logit[:, :-1], input_id[:, 1:])
            logprob_list.append(log_p.squeeze(0))
        return torch.nn.utils.rnn.pad_sequence(
            logprob_list, batch_first=True, padding_value=0.0
        ).to(device)
class SimPOTrainer(DPOTrainer):

    def loss(  # pylint: disable=too-many-locals
        self,
        batch: PreferenceBatch,
    ) -> dict[str, torch.Tensor]:
        """Loss function for the SimPO algorithm."""
        sequence_log_probs = self.compute_log_probs(
            self.model.module,
            batch,
        )
# 使用 chunk 将 sequence_log_probs 按照第0维(批次维度)进行切分。将批次数据分为两部分:一部分对应 "更好"(better_sequence_log_probs),另一部分对应 "更差"(worse_sequence_log_probs)。每部分的大小为 (B, L - 1),B 是批次大小,L 是序列长度。  L-1 是为了删除最后的 <eos>
        (
            better_sequence_log_probs,  # size = (B, L - 1)
            worse_sequence_log_probs,  # size = (B, L - 1)
        ) = sequence_log_probs.chunk(chunks=2, dim=0)

        losses = []
        better_sample_rewards = []
        worse_sample_rewards = []

        better_input_ids, worse_input_ids = batch['input_ids'].chunk(chunks=2, dim=0)
        better_attention_mask, worse_attention_mask = batch['attention_mask'].chunk(chunks=2, dim=0)

        batch_size = better_input_ids.size(0)
        for i in range(batch_size):
#检查当前样本的 "更好" 和 "更差" 部分的 input_ids 是否相同。如果相同,跳过这个样本,因为它们对比不出差异。
            if torch.all(torch.eq(better_input_ids[i], worse_input_ids[i])).item():
                continue

#分别计算 "更好" 和 "更差" 样本的结束位置(通过 attention_mask 中的非零元素位置来确定)。
            better_end_index = better_attention_mask[i].nonzero()[-1].squeeze().item()
            worse_end_index = worse_attention_mask[i].nonzero()[-1].squeeze().item()
            better_input_length = better_end_index + 1
            worse_input_length = worse_end_index + 1
# diverge_index 是 "更好" 和 "更差" 样本之间的第一个差异位置。
            diverge_index = (
                (better_input_ids[i] != worse_input_ids[i]).nonzero()[0].squeeze().item()
            )
            assert 0 <= diverge_index <= better_end_index, 'diverge index is out of range!'
            assert 0 <= diverge_index <= worse_end_index, 'diverge index is out of range!'
#根据 diverge_index 进行切片,获取差异区域的对数概率。
#better_log_prob 和 worse_log_prob 是对应于 "更好" 和 "更差" 样本的对数概率的总和。
            better_seq_slice = slice(diverge_index, better_end_index + 1)
            worse_seq_slice = slice(diverge_index, worse_end_index + 1)
# 计算损失和奖励
            better_log_prob = better_sequence_log_probs[i, better_seq_slice].sum(dim=-1)
            worse_log_prob = worse_sequence_log_probs[i, worse_seq_slice].sum(dim=-1)
#在长度上归一化的奖励【/|y|】,其计算方式是使用策略模型的奖励中所有 token 的平均对数概率;
            better_log_ratio = better_log_prob / better_input_length
            worse_log_ratio = worse_log_prob / worse_input_length
#目标奖励差额γ,用以确保获胜和失败响应之间的奖励差超过这个差额γ
            losses.append(
                -F.logsigmoid(
                    self.cfgs.train_cfgs.scale_coeff * (better_log_ratio - worse_log_ratio)
                    - self.cfgs.train_cfgs.gamma,
                ),
            )
            better_sample_rewards.append(
                self.cfgs.train_cfgs.scale_coeff * better_log_ratio.detach(),
            )
            worse_sample_rewards.append(self.cfgs.train_cfgs.scale_coeff * worse_log_ratio.detach())
        loss = torch.stack(losses).mean()  # size = ()
        better_sample_reward = torch.stack(better_sample_rewards)  # size = (B,)
        worse_sample_reward = torch.stack(worse_sample_rewards)  # size = (B,)
        reward = better_sample_reward + worse_sample_reward  # size = (B,)
        reward_accuracy = (better_sample_reward > worse_sample_reward).float().mean()  # size = ()
        reward_margin = better_sample_reward - worse_sample_reward  # size = (B,)

        return {
            'loss': loss,
            'reward': reward,
            'better_sample_reward': better_sample_reward,
            'worse_sample_reward': worse_sample_reward,
            'reward_accuracy': reward_accuracy,
            'reward_margin': reward_margin,
        }

KTO训练代码:

#  创建 不匹配的提示-回答对:错位传入批次(batch)中的 answer_input_ids 和 answer_attention_mask 数据,以创建不匹配的提示-回答对。获取当前索引前一个样本作为回应(response)。如果当前索引是 0,则取最后一个样本作为回应。这是为了创建“不匹配”的数据对,即提示和回应不一定是成对的。
class UnmatchedSupervisedDataset(SupervisedDataset):

    def preprocess(
        self, raw_sample_for_prompt: dict[str, Any], raw_sample_for_response: dict[str, Any]
    ) -> SupervisedSample:
        return_dict = {}
        formatted_text, _ = self.template.format_unmatched_supervised_sample(
            raw_sample_for_prompt, raw_sample_for_response
        )
        return_dict['input_ids'] = self.tokenize(formatted_text)

        return return_dict

    def __getitem__(self, index: int) -> dict[str, torch.Tensor]:
        """Get a tokenized data sample by index."""
        raw_sample_for_prompt = self.raw_data[index]
        if index == 0:
            raw_sample_for_response = self.raw_data[-1]
        else:
            raw_sample_for_response = self.raw_data[index - 1]
        data = self.preprocess(raw_sample_for_prompt, raw_sample_for_response)
        return data

    def get_collator(self) -> Callable[[list[dict[str, torch.Tensor]]], dict[str, torch.Tensor]]:
        return UnmatchedSupervisedCollator(self.tokenizer.pad_token_id)

class KTOTrainer(DPOTrainer):

# 计算kl散度:通过计算当前模型(self.model.module)和参考模型(self.reference_model.module)之间的 KL 散度来比较它们的概率分布
# 选择最后一个 batch 的 KL 值可能只是实现上的简化。实际中,计算所有 batch 的 KL 散度并取平均,或者采取其他更复杂的策略,可能会增加额外的计算负担,而选择最后一个 batch 的 KL 值是一种更直接、简便的实现方式。
def compute_kl(self):
    random_dataset = UnmatchedSupervisedDataset(
        path=self.cfgs.data_cfgs.train_datasets,
        template=self.train_template,
        tokenizer=self.tokenizer,
        processor=self.processor,
        name=self.cfgs.data_cfgs.train_name,
        size=self.cfgs.data_cfgs.train_size,
        split=self.cfgs.data_cfgs.train_split,
        data_files=self.cfgs.data_cfgs.train_data_files,
        optional_args=self.cfgs.data_cfgs.train_optional_args,
    )
    seed = torch.randint(0, 100000, (1,)).item()
    torch.manual_seed(seed)
    self.random_dataloader = DataLoader(
        random_dataset,
        collate_fn=random_dataset.get_collator(),
        sampler=DistributedSampler(random_dataset, shuffle=True),
        batch_size=self.cfgs.train_cfgs.per_device_kl_batch_size,
    )
    for batch in self.random_dataloader:
        log_probs = self.compute_log_probs(  # size = (2 * B, L - 1)
            self.model.module,
            batch=batch,
        )
        ref_log_probs = self.compute_log_probs(  # size = (2 * B, L - 1)
            self.reference_model.module,
            batch=batch,
        )
        kl = (log_probs - ref_log_probs).mean()

        self.kl = max(kl, 0)
# 此方法是 DPO (Direct Preference Optimization) 算法的核心部分。它计算了在当前模型和参考模型之间的对比损失
    def loss(  # pylint: disable=too-many-locals
        self,
        batch: PreferenceBatch,
    ) -> dict[str, torch.Tensor]:
        """Loss function for the DPO algorithm."""
        sequence_log_probs = self.compute_log_probs(
            self.model.module,
            batch,
        )
        (
            better_sequence_log_probs,  # size = (B, L - 1)
            worse_sequence_log_probs,  # size = (B, L - 1)
        ) = sequence_log_probs.chunk(chunks=2, dim=0)

        with torch.no_grad():
            ref_sequence_log_probs = self.compute_log_probs(  # size = (2 * B, L - 1)
                self.reference_model.module,
                batch,
            )
            ref_better_sequence_log_probs, ref_worse_sequence_log_probs = (
                ref_sequence_log_probs.chunk(chunks=2, dim=0)
            )

        losses = []
        better_sample_rewards = []
        worse_sample_rewards = []

        better_input_ids, worse_input_ids = batch['input_ids'].chunk(chunks=2, dim=0)
        better_attention_mask, worse_attention_mask = batch['attention_mask'].chunk(chunks=2, dim=0)

        batch_size = better_input_ids.size(0)
        for i in range(batch_size):
            if torch.all(torch.eq(better_input_ids[i], worse_input_ids[i])).item():
                continue
            better_end_index = better_attention_mask[i].nonzero()[-1].squeeze().item()
            worse_end_index = worse_attention_mask[i].nonzero()[-1].squeeze().item()
            diverge_index = (
                (better_input_ids[i] != worse_input_ids[i]).nonzero()[0].squeeze().item()
            )
            assert 0 <= diverge_index <= better_end_index, 'diverge index is out of range!'
            assert 0 <= diverge_index <= worse_end_index, 'diverge index is out of range!'

            better_seq_slice = slice(diverge_index, better_end_index + 1)
            worse_seq_slice = slice(diverge_index, worse_end_index + 1)

            better_log_prob = better_sequence_log_probs[i, better_seq_slice].sum(dim=-1)
            worse_log_prob = worse_sequence_log_probs[i, worse_seq_slice].sum(dim=-1)
            ref_better_log_prob = ref_better_sequence_log_probs[i, better_seq_slice].sum(dim=-1)
            ref_worse_log_prob = ref_worse_sequence_log_probs[i, worse_seq_slice].sum(dim=-1)
            better_log_ratio = better_log_prob - ref_better_log_prob
            worse_log_ratio = worse_log_prob - ref_worse_log_prob

# 计算loss,kl值作为基准
            losses.append(
                self.cfgs.train_cfgs.scale_better
                * (1 - F.sigmoid(self.cfgs.train_cfgs.scale_coeff * (better_log_ratio - self.kl)))
                - self.cfgs.train_cfgs.scale_worse
                * (1 - F.sigmoid(self.cfgs.train_cfgs.scale_coeff * (self.kl - worse_log_ratio))),
            )
            better_sample_rewards.append(
                self.cfgs.train_cfgs.scale_coeff * better_log_ratio.detach(),
            )
            worse_sample_rewards.append(self.cfgs.train_cfgs.scale_coeff * worse_log_ratio.detach())
        loss = torch.stack(losses).mean()  # size = ()
        better_sample_reward = torch.stack(better_sample_rewards)  # size = (B,)
        worse_sample_reward = torch.stack(worse_sample_rewards)  # size = (B,)
        reward = better_sample_reward + worse_sample_reward  # size = (B,)
        reward_accuracy = (better_sample_reward > worse_sample_reward).float().mean()  # size = ()
        reward_margin = better_sample_reward - worse_sample_reward  # size = (B,)

        return {
            'loss': loss,
            'reward': reward,
            'better_sample_reward': better_sample_reward,
            'worse_sample_reward': worse_sample_reward,
            'reward_accuracy': reward_accuracy,
            'reward_margin': reward_margin,
        }
#执行训练步骤:这个方法在每一个训练步中计算并反向传播损失。它更新模型参数并计算并返回训练信息。
#奖励计算:通过 reward、better_sample_reward 和 worse_sample_reward 等指标来衡量模型的性能。
#全局平均:get_all_reduce_mean() 用于分布式训练,确保在多个设备上计算的值被平均,以保证训练的一致性。
def train_step(self, batch: PreferenceBatch) -> dict[str, Any]:
    """Perform a single training step for KTO."""
    loss_dict = self.loss(batch=batch)
    loss = loss_dict['loss']
    self.model.backward(loss)
    self.model.step()

    with torch.no_grad():
        reward = loss_dict['reward'].mean()
        better_sample_reward = loss_dict['better_sample_reward'].mean()
        worse_sample_reward = loss_dict['worse_sample_reward'].mean()
        reward_accuracy = loss_dict['reward_accuracy']
        reward_margin = loss_dict['reward_margin'].mean()

    loss = get_all_reduce_mean(loss)
    reward = get_all_reduce_mean(reward)
    better_sample_reward = get_all_reduce_mean(better_sample_reward)
    worse_sample_reward = get_all_reduce_mean(worse_sample_reward)
    reward_accuracy = get_all_reduce_mean(reward_accuracy)
    reward_margin = get_all_reduce_mean(reward_margin)

    return {
        'train/loss': loss.item(),
        'train/reward': reward.item(),
        'train/better_sample_reward': better_sample_reward.item(),
        'train/worse_sample_reward': worse_sample_reward.item(),
        'train/reward_accuracy': reward_accuracy.item(),
        'train/reward_margin': reward_margin.item(),
        'train/lr': self.model.optimizer.param_groups[0]['lr'],
    }

相关论文:

  • KTO,Kahneman-Tversky 优化,参阅论文《KTO: Model alignment as prospect theoretic optimization》。
  • DRO,直接奖励优化,参阅论文《Offline regularised reinforcement learning for large language models alignment》。
  • SimPO,简单偏好优化,参阅论文《SimPO: Simple preference optimization with a reference-free reward》

Baichuan-Omni-1.5 多模态语音大模型

Baichuan-Omni-1.5 🤗 | Baichuan-Omni-1.5-Base 🤗 | 技术报告 📖

OpenMM-Medical 🤗 | OpenAudioBench 🤗

Github: https://github.com/baichuan-inc/Baichuan-Omni-1.5/

Baichuan-Omni-1.5 是 Baichuan-omni 系列的最新、性能一流模型。该模型通过端到端方式训练和推理。与其他开源模型相比,Baichuan-Omni-1.5 在文本、图像、音频和视频输入的理解能力有显著提升,并支持了可控的实时语音对话和多模态实时交互的新功能。此外,Baichuan-Omni-1.5 也是目前最出色的开源医疗多模态模型。Baichuan-Omni-1.5 的主要特性包括:

  • 多模态理解和交互能力: Baichuan-Omni-1.5 接受图像、视频、文本、音频作为输入,并生成高质量文本和语音输出,能够在不损害任何模态能力的情况下实现无缝的高质量跨模态交互,并和用户进行实时语音对话。在针对全模态理解的综合评测基准 OminiBench 中,Baichuan-Omni-1.5 取得开源社区一流水平,并超过了 GPT-4o-mini
  • 优秀的视觉能力: Baichuan-Omni-1.5 在 OpenCompass 常用的十个视觉评测集上平均得分 73.3,在7B量级的大小下,在图像理解方面超越了 GPT-4o-mini,比GPT-4o-mini平均高出6分,并且和GPT-4o的结果相近。此外,视频理解表现也优于GPT-4V
  • 出色的语音能力: Baichuan-Omni-1.5 通过一个 8 层 RVQ 音频Tokenizer(Baichuan-Audio-Tokenizer),在 12.5 Hz 帧率下实现了语义和声学信息捕获的最佳平衡,支持高质量可控制声音的中英双语实时对话。Baichuan-Omni-1.5 在语音理解任务优于 GLM-4-Voice,并在语音对话的语义和声学评估中展现了开源模型中最高的语音生成性能。同时,我们还开源了音频理解和生成基准(OpenAudio-Bench),以评估音频的端到端能力。
  • 领先的医疗图像理解能力: 我们从开源数据集中收集了一个比较全面的医学理解基准(OpenMM-Medical)用于评估模型的医学能力。Baichuan-Omni-1.5 在 GMAI-MMBench 以及 OpenMM-Medical 上取得了最佳的表现。在 OpenMM-Medical 上,Baichuan-Omni-1.5 仅使用 7B 的 LLM 取得了 83.8% 的高分,超过 Qwen2-VL-72B 的 80.7%。
在图像、视频和音频模态上的评估。(左)Baichuan-Omni-1.5覆盖了比Qwen2 VL更多的模态,并且超越了当前领先的全模态模型VITA-1.5和MiniCPM-o 2.6。(右)各模态在所有基准测试中的平均得分。

简介

Baichuan-Omni-1.5是一款全模态模型,具备全模态理解能力,并提供端到端的音频生成能力。为了实现跨模态的流畅高质量互动,同时不妥协任何模态的能力,优先优化了三个关键方面。首先,建立了一个全面的数据清洗和合成管道,用于多模态数据处理,获得了约5000亿条高质量数据(包括文本、音频和视觉数据)。其次,设计了一种音频分词器(Baichuan-Audio-Tokenizer),能够同时捕捉音频中的语义和声学信息,从而实现无缝集成,并提升与多模态大语言模型(MLLM)的兼容性。最后设计了一个多阶段的训练策略,逐步整合多模态对齐和多任务微调,确保各模态之间的有效协同。Baichuan-Omni-1.5在全模态能力方面领先于当前的模型(包括GPT4o-mini和MiniCPM-o 2.6)。值得注意的是,在多个多模态医学基准测试中,它取得了与领先模型(如Qwen2-VL-72B)相媲美的成绩。

与开源对手相比,Baichuan-Omni-1.5在文本、图像、音频和视频输入的理解能力上表现出显著的提升。特别地,该模型在可控实时语音互动和跨模态实时协同理解方面展示了令人印象深刻的能力。除了其通用能力外,Baichuan-Omni-1.5在医疗领域作为最出色的多模态大语言模型(MLLM)脱颖而出。这为人工智能在促进人类社会福祉方面开辟了令人兴奋的新可能性。Baichuan-Omni-1.5的架构如下图所示。根据评估结果,我们总结了Baichuan-Omni-1.5的关键优势和贡献:

全模态互动:Baichuan-Omni-1.5旨在处理文本、图像、音频和视频输入,输出高质量的文本和语音。它能够实现无缝、高质量的跨模态互动,且不会妥协任何模态的能力。

卓越的视觉-语言能力:Baichuan-Omni-1.5在十个图像理解基准测试中平均得分为73.3,超越GPT-4o-mini平均6分。

统一且出色的语音能力:我们设计了一种8层的RVQ音频分词器(Baichuan-Audio-Tokenizer),在捕捉语义和声学信息之间达到了最佳平衡,帧率为12.5Hz,支持高质量的可控双语(中文和英文)实时对话。同时,我们还开源了音频理解和生成基准(OpenAudioBench),用于评估音频的端到端能力。

领先的医学图像理解能力:我们收集了一个全面的医学理解基准:OpenMM-Medical,这是多个现有数据集的整合。我们的模型在GMAI-MMBench和OpenMM-Medical上取得了最先进的表现。具体来说,在OpenMM-Medical上,Baichuan-Omni-1.5使用7B大语言模型取得了83.8%的得分,超过了Qwen2-VL-72B的80.7%。

模型架构:

Baichuan-Omni-1.5 的架构。我们的模型旨在处理纯文本/音频输入以及视频/图像与文本/音频的组合。在生成音频时,Baichuan-Omni-1.5 LLM Decoder 交替预测文本标记和音频标记。然后,音频解码器对音频令牌进行解码,以生成最终音频。

Baichuan-Omni-1.5

高质量预训练数据

Baichuan-Omni-1.5的预训练数据集展示:我们构建了一个广泛的全模态数据集,包括文本、图像-文本、视频-文本、音频-文本及它们之间的交互。我们的数据集还包含了交织的图像-音频-文本和视频-音频-文本数据。

构建了全面且高质量的跨模态数据集,包含文本、图像-文本、视频-文本、音频-文本及它们之间的交互

图像数据:我们将图像训练数据分为三种类型:交织的图像-文本数据、图像描述数据和问答数据。为了提高数据的多样性并提升模型性能,我们采用了以下两种图像数据合成策略:

1)我们利用公司内收集的书籍和论文,并解析它们生成交织的图像-文本、OCR数据和图表数据。这些数据具有高度的完整性、专业性和知识密集性。

2)我们根据[19]的方案,训练了一个专用的描述模型,能够生成所需的图像描述,如OCR提示。这些描述提供了图像内容的深入说明。

3)目前,大量的开源数据集主要是英文数据。为了避免模型的中文能力下降,我们合成了大量的中文描述和交织数据。

视频数据:视频数据集包含了广泛的公开资源,涵盖了视频分类 、动作识别 和时间定位等多种任务。视频-文本数据源可以分为视频描述数据和视频问答(QA)数据。

音频数据:音频数据可以大致分为两种主要类型:音频理解数据和音频生成数据。音频理解数据包括自动语音识别(ASR)、音频问答(AQA)、语音转文本翻译和音频-文本交织数据。音频生成数据包括文本转语音(TTS)、交织的文本转语音数据和纯音频数据。交织数据由文本和音频模态交替组成,并以标点符号分隔,以促进跨模态知识的迁移。完全对齐的生成数据由完全对齐的文本和音频内容组成,旨在增强模型在文本监督下生成音频标记的能力。音频-文本配对数据(例如ASR和TTS数据)提高了基础语音任务的表现。纯音频数据则增强了独立处理音频模态的能力。

文本数据:为了构建一个高质量的文本语料库,我们从各种来源聚合了数据,包括网页、书籍、学术论文、代码等。我们遵循之前研究中的数据处理指南,采用严格的选择方法,旨在提高文本语料库的多样性和质量。这种多样性确保训练语料库涵盖了广泛的主题和语言风格,适用于不同的应用。同时,我们的高质量处理技术旨在消除冗余和过滤噪声,从而丰富数据集的信息密度和整体效用。最终,我们获得了1.507亿条纯文本数据。

跨模态交互数据:为了增强模型的跨模态交互能力,我们合成了一系列跨模态交互数据集,涵盖了图像-音频-文本视频-音频-文本格式。图像-文本数据来源包括两种类型:图像-文本描述数据和图像-文本交织数据。具体而言,文本数据首先在句子级别进行分割。然后,将四分之一的文本通过我们的内部文本转语音(TTS)接口转换为音频元素。随后,我们利用生成的音频元素替换原始图像-文本数据中的相应文本句子。这种方法通过将多样化的音频元素融入现有的文本内容,促进了丰富的跨模态交互框架。我们的音频数据包含44种不同的语音类型,确保了语调的多样性。此设置配有任务提示,如“请听以下音频,描述图像的内容。您的任务是在听完后结合音频和图像补充额外信息”,旨在预测剩余的三分之二文本描述。对于视频-文本数据集,音频成分直接从原始视频中提取,作为跨模态的音频元素。总计,我们生成了1000亿个用于跨模态交互的标记数据。

模型结构:

Baichuan-Omni-1.5是一个统一的全模态模型,由视觉分支、音频分支和一个预训练的大型语言模型(LLM)主干组成,支持文本、音频、视觉输入以及端到端的文本和音频输出。

视觉分支
像当前主流的MLLM一样,视觉分支旨在将图像和视频输入处理为视觉标记,并将其与文本标记一起输入到LLM中。我们使用Qwen2-VL的NaViT作为视觉编码器,该编码器能够动态处理任意分辨率和纵横比的图像和视频。然后,我们应用一个由两层MLP组成的视觉投影器,将视觉特征压缩为2×2的因子,从而在性能和效率之间取得平衡。

音频分支
音频分支扩展了LLM,使其能够支持端到端的语音输入和输出。这通过引入Baichuan-Audio-Tokenizer和基于流匹配的解码器来实现,前者负责将音频信号转换为离散标记,后者负责将音频标记解码为语音波形。我们在下图中展示了详细信息。

Baichuan-Audio-Tokenizer基于残差向量量化(RVQ)和多目标训练,帧率为12.5 Hz。在使用Whisper Large Encoder 从Mel谱图特征中提取高级特征后,残差卷积网络执行下采样以获得低帧率序列特征。然后使用8层残差向量量化器对这些特征进行量化,生成音频标记。这些标记随后被输入到音频解码器和预训练的LLM中,分别执行Mel谱图重建和转录预测。音频解码器采用与Whisper编码器对称的结构,并使用多尺度Mel损失来增强声音重建的质量。在训练过程中,预训练LLM的参数保持不变,以确保音频标记器和文本空间之间的语义对齐。

除了传统的任务如ASR、AQA和S2TT外,我们还将一定比例的交织文本-音频数据融入其中,以提高VQ模块建模复杂上下文场景的能力。

为了进一步增强合成音频的质量和感知逼真度,音频解码器模块通过流匹配模型进行优化。借鉴Matcha-TTS 和CosyVoice 的设计,U-Net包括一个单独的下采样块、一个上采样块和12个中间块。具体而言,流匹配解码器在24 kHz音频数据上进行训练,以生成目标Mel谱图,然后使用HiFi-GAN 声码器将其转换为语音波形。

多阶段模型训练:

图像-文本预训练

图像-文本预训练阶段扩展了LLM,使其能够处理和理解视觉输入,使用3000亿图像-文本样本,该阶段可以分为两个部分。

第一阶段:在第一阶段,我们训练视觉投影器,利用开源图像描述数据(例如LAION-5B数据集),建立图像表示与文本之间的初步对齐。在此阶段,我们冻结LLM和视觉编码器,仅训练视觉投影器,学习率为1e−3。

第二阶段:在第二阶段,我们解冻视觉编码器和LLM,以促进图像和文本表示之间更好的对齐。具体来说,我们以学习率1e−5训练LLM和视觉投影器,并以更低的学习率1e−6训练视觉编码器。我们使用公共和内部图像文本数据,包含交织数据和图像描述数据,以增强视觉-语言的表现力。具体来说,我们收集并标注高质量的OCR数据和图表数据,以增强文本/图表识别和理解能力。此外,我们还使用高质量的纯文本数据,这些数据占总数据的40%,以更好地保持语言模型的原始能力。

图像-音频-文本预训练

图像-音频-文本预训练阶段扩展了一个预训练在视觉数据上的LLM,使其能够以端到端的方式理解音频数据,使用887k小时的语音-文本数据,并结合我们的Baichuan-Audio-Tokenizer、新引入的音频嵌入层和独立音频头

具体来说,Baichuan-Audio-Tokenizer生成的音频令牌首先通过音频嵌入层转化为音频嵌入。音频LLM交替生成对齐的文本令牌和音频令牌,使用一个特殊令牌实现文本与音频之间的模态切换。生成的音频令牌由独立的音频头处理,该音频头基于先前的工作设计,包含3层深度变换器和8个分类头。

为了缓解语音和文本特征之间显著差异带来的冲突,我们参考了之前的工作,采用音频和文本数据交织的方法进行预训练。此外,采用了两阶段训练策略,以保持原始LLM的文本知识,同时有效地整合音频模态。

第一阶段:在第一阶段,我们冻结LLM、视觉模块和音频标记器的参数,只有音频嵌入层和音频头的参数更新,学习率为1e−4。我们在这一阶段使用包括ASR、TTS、INTLV和ITTS数据的音频数据。

第二阶段:在第二阶段,训练扩展到除视觉编码器和音频标记器之外的所有参数,学习率为1e−5。具体来说,我们使用音频数据、图像数据和纯文本数据,分别占比0.2、0.4和0.4,这可以更好地提升音频能力,同时保持视觉和语言能力。

全模态预训练

基于之前预训练阶段获得的视觉和音频能力,我们继续使用高质量的跨模态交互数据集进行训练,数据集涵盖图像-音频-文本和视频-音频-文本格式,并将最大序列长度扩展到64k,以支持长时间的语音和视频流。具体来说,输入的视频帧以每秒1帧的速率进行采样,每个视频最多包含32帧。每个输入帧被调整为最大分辨率560×1120像素,以保持最佳质量和细节。这一精心配置在性能和效率之间达到了平衡,促进了有效的模型训练,同时管理了计算负载。此训练过程使用4e−6的低学习率,以进一步优化与语言模态和跨模态交互的对齐。

多模态监督微调

在本节中,我们描述了全模态监督微调(SFT)阶段,旨在增强模型在各种任务中执行复杂全模态指令的能力。我们收集了包括开源、合成和内部注释数据在内的综合数据集。这些数据集涵盖了多个任务,包含大约1700万个数据对,跨越文本、音频、图像-文本、视频-文本和图像-音频组合等多种模态。关于这些数据类型和数量的详细信息见表4。

实验

如表6所示,Baichuan-Omni-1.5 在纯文本基准测试中表现出色,特别是在与仅专注于语言模态的开源LLM模型相比时。例如,在通用MMLU基准测试中,Llama3-Instruct的得分为67.1%,而Baichuan-Omni-1.5则达到了72.2%。Baichuan-Omni-1.5在语言模态上的成功,主要归功于我们在训练策略上的调整以及多模态训练数据的平衡比例,其中保持了适当比例的纯文本数据。这些结果表明,我们的数据合成与平衡方法,以及多阶段的训练策略,能够有效解决在多模态训练过程中纯语言任务性能下降的问题。此外,相较于最新的开源多模态模型MiniCPM-o 2.6,Baichuan-Omni-1.5在中文基准测试中展示了明显的优势,例如CMMLU(63.3%对75.5%)和C-Eval(61.5%对73.1%),并且在通用基准测试中也大大超过了MiniCPM-o 2.6,MMLU(65.3%对72.2%)和AGIEval(50.9%对54.4%)。这些结果表明,相较于当前的全模态模型,这些模型在训练非文本模态数据后可能会导致文本理解能力下降,而我们的模型在理解纯文本方面依然保持强大能力。

如表7和表8所示,显然,我们的模型在大多数基准测试中优于最新的开源模型VITA-1.5和MiniCPM-o 2.6。例如,与最近的MiniCPM-o 2.6相比,我们的模型在包括MMBench、SEED-IMG、MME和MMMU在内的十个基准测试中的六个上表现更好,这些基准测试要求具备专家级的感知和推理能力。这表明,我们的全模态模型已经处于开源模型的前沿。此外,与其他非全模态模型相比,Baichuan-Omni-1.5也取得了相当或更优的表现。例如,与MiniCPM-Llama3-V 2.5相比,我们的模型在大多数视觉问答(VQA)任务中表现更好。总体而言,与Qwen2-VL-7B相比,我们的模型在各类图像理解基准测试中的表现相当。我们的模型在MMBench-CN(81.9%对83.6%)、MMMU(52.7%对53.9%)、MathVista-mini(58.2%对63.6%)和ChartQA(83.0%对84.9%)等方面取得了更好的表现。此外,值得注意的是,在MMBench-EN/CN和OCRBench上,我们的模型已经超越了像GPT4o这样的闭源模型。

总结

在这项工作中,我们介绍了Baichuan-Omni-1.5,一个全模态模型,代表了朝着开发一个涵盖所有人类感官的综合框架迈出的重要一步。通过使用高质量的多模态数据以及多阶段的全模态预训练和微调策略,Baichuan-Omni-1.5在处理视频、图像、文本和音频理解方面取得了优异的表现。Baichuan-Omni-1.5的关键特点包括:(1) 强大的纯文本和多模态理解能力;(2) 全模态输入(文本、图像、视频、文本)和双模态输出(文本和音频)的端到端并行处理;(3) 在医疗场景中的卓越表现;以及(4) 高质量的可控音频生成。

尽管这些结果很有前景,但每种模态的基础能力仍有相当大的改进空间。即:(1) 增强文本理解能力;(2) 支持更长的视频帧理解;以及(3) 改进音频理解和生成,不仅能识别人类声音,还能识别自然环境中的声音,如流水声、鸟鸣声和碰撞声等。

我们的未来研究将专注于完善这些领域,以确保开发出更复杂、更通用的模型,能够理解和与复杂环境互动。我们预计,在这些领域的持续进展将对实现人工通用智能(AGI)的更广泛目标做出重要贡献。

Kokoro TTS:支持多语言的轻量级TTS模型

最近,HuggingFace模型趋势榜上有一个很火的开源模型Kokoro-82M

开源模型:https://huggingface.co/hexgrad/Kokoro-82M

Kokoro-82M不是大模型,而是一个参数量只有82M的TTS(Text-to-Speech)模型。虽然模型不大,但是Kokoro-82M在TTS Arena榜单上排行第一!TTS Arena 是一个用于评估语音合成模型的平台,其灵感来源于 LMsys 的 Chatbot Arena。用户可以通过输入文本并对比两个模型的合成语音来投票选择更自然的结果,模型名称在投票后才会显示。该平台旨在解决语音合成领域缺乏有效质量评估方法的问题,通过公开排名使结果更透明且易于访问。虽然这个排行榜很有参考意义。

在人工智能语音合成技术快速发展的今天,Kokoro TTS 以其轻量级设计和高效性能脱颖而出。作为一个仅有82M参数的文本转语音(TTS)模型,Kokoro 在 TTS Spaces Arena 中击败了许多参数规模更大的竞争对手,成为语音合成领域的一颗新星。

模型架构与参数规模

Kokoro TTS 基于 StyleTTS 2 架构,其参数规模仅为82M,远低于许多主流 TTS 模型(如 XTTS v2 的467M 参数和 MetaVoice 的1.2B 参数),但在单声道设置下表现卓越。

支持的语音与语言

Kokoro 最新版(0.23)支持多语言支持与声音克隆,包括:中、英、法、日、韩。每种语言支持多种音色以及男、女声,每种语音包都经过专业调校,确保音质清晰自然。英语支持美国英语和英国英语,并提供了10种独特的语音包,包括男声和女声(如 af_bella、af_sarah、am_adam 等)。

不过还不支持中文或韩文中与英语混合的情况。

性能优势与创新点

Kokoro 的训练数据量不到100小时,远低于其他模型(如 XTTS v2 的10,000小时),但其在 TTS Spaces Arena 中排名第一,证明了其在参数效率上的优势。此外,Kokoro 采用 espeak-ng 进行字形到音素(g2p)转换,进一步提升了语音合成的自然度。

本地部署步骤

模型地址:https://huggingface.co/hexgrad/Kokoro-82M

以下步骤为notebook中使用

# 1️⃣ Install dependencies silently
!git lfs install
!git clone https://huggingface.co/hexgrad/Kokoro-82M
%cd Kokoro-82M
!apt-get -qq -y install espeak-ng > /dev/null 2>&1
!pip install -q phonemizer torch transformers scipy munch

# 2️⃣ Build the model and load the default voicepack
from models import build_model
import torch
device = 'cuda' if torch.cuda.is_available() else 'cpu'
MODEL = build_model('kokoro-v0_19.pth', device)
VOICE_NAME = [
    'af', # Default voice is a 50-50 mix of Bella & Sarah
    'af_bella', 'af_sarah', 'am_adam', 'am_michael',
    'bf_emma', 'bf_isabella', 'bm_george', 'bm_lewis',
    'af_nicole', 'af_sky',
][0]
VOICEPACK = torch.load(f'voices/{VOICE_NAME}.pt', weights_only=True).to(device)
print(f'Loaded voice: {VOICE_NAME}')

# 3️⃣ Call generate, which returns 24khz audio and the phonemes used
from kokoro import generate
text = "How could I know? It's an unanswerable question. Like asking an unborn child if they'll lead a good life. They haven't even been born."
audio, out_ps = generate(MODEL, text, VOICEPACK, lang=VOICE_NAME[0])
# Language is determined by the first letter of the VOICE_NAME:
#    'a' => American English => en-us
#    'b' => British English => en-gb

# 4️⃣ Display the 24khz audio and print the output phonemes
from IPython.display import display, Audio
display(Audio(data=audio, rate=24000, autoplay=True))
print(out_ps)

API 接口与 Docker 化部署

Kokoro-FastAPI 是一个基于 Docker 的 FastAPI 封装,支持 NVIDIA GPU 加速和队列处理功能。用户可以通过 API 接口发送文本转语音请求,并获取高质量的语音输出。

Kokoro-FastAPI地址:https://github.com/remsky/Kokoro-FastAPI

TangoFlux-TTA 高效的文本到音频 (TTA) 生成模型

NVIDIA发布了新模型TangoFlux,TangoFlux和Flux采用类似的MMDiT架构,但与Flux不同的是,TangoFlux是用于根据文本来生成与之匹配的音频(Text-to-Audio,TTA)。注意,TTA与文本生成语音(Text-to-Speech,TTS)是两个不同的任务,TTS是根据文本合成口语化的语音,而TTA更复杂,是根据文本内容生成相应的背景音、环境音或者情感表达的音频。TangoFlux模型参数只有515M,能够在单个A40 GPU上仅用3.7秒生成长达30秒的44.1kHz音频,而且效果上实现了SOTA,所以是一个又快又好的TTA模型。目前,TangoFlux的代码和代码均已经开源:

在对齐 TTA(文本到音频)模型时,一个关键挑战在于生成偏好对的困难,因为 TTA 缺乏像大型语言模型(LLMs)那样的结构化机制,例如可验证的奖励或黄金标准答案。为了解决这一问题,我们提出了一种新颖的框架——CLAP 排序偏好优化(CRPO),通过迭代生成和优化偏好数据来增强 TTA 的对齐性能。研究表明,使用 CRPO 生成的音频偏好数据集优于现有的替代方案。借助这一框架,TangoFlux 在客观和主观基准测试中均达到了最先进的性能。

贡献

  • 引入了 TANGOFLUX,这是一种基于修正流的小型高效 TTA 模型,能够在完全非专有的训练数据上实现最先进的性能。
  • 提出了 CRPO,这是一种简单而有效的策略,用于生成音频偏好数据并对修正流进行对齐,其在音频偏好数据集上的表现优于其他方法。
  • 公开发布了代码和模型权重,以促进文本到音频生成领域的研究。

方法:

TangoFlux 由 FluxTransformer 块组成,这些块是基于扩散变换器(Diffusion Transformer, DiT,Peebles & Xie,2023)和多模态扩散变换器(Multimodal Diffusion Transformer, MMDiT,Esser 等,2024)的模型,通过文本提示和时长嵌入进行条件化,以生成最高 44.1kHz、时长达 30 秒的音频。TangoFlux 从通过变分自动编码器(VAE,Kingma & Welling,2022)编码的音频潜在表示中学习修正流轨迹。

TangoFlux 的训练流程包括三个阶段:预训练、微调和偏好优化。通过 CRPO 对 TangoFlux 进行对齐,CRPO 通过迭代生成新的合成数据并构建偏好对,执行偏好优化。整体训练流程如图 1 所示。

TangoFlux在模型架构上参考了Flux,也是采用混合MMDiT和DiT block的transformer,首先是6层MMDiT block,然后跟着18层DiT block,模型的特征维度是1024,总参数量为515M。类似SD和Flux,这里也是采用了一个音频VAE(来源Stable Audio Open)将音频编码成一定长度的latents,然后用DiT来生成latents。这里的文本编码器采用FLAN-T5,除了文本特征,还用一个小的网络将音频时长编码成一个embedding,并和文本特征拼接在一起,从而实现对生成音频长度的控制。训练也是采用Flow Matching。

音频编码

使用 Stable Audio Open Evans et al. 的 VAE,它能够将 44.1kHz 的立体声音频波形编码为音频潜在表示。给定一个立体声音频 X∈ℝ2×d×s⁢r ,其中 d 是 时长duration 和 s⁢r 是采样率 sampling rate,VAE 编码 X 为潜在表示 Z∈ℝL×C ,其中 L ,C 分别是潜在序列长度和通道大小。VAE 将 latent 表示 Z 解码回原始立体声音频 X 。整个 VAE 在 TangoFlux 训练期间保持冻结。

Model Conditioning

为了实现不同长度音频的可控生成,我们采用了文本调节和持续时间调节。文本调节根据提供的描述控制生成的音频的事件,而持续时间调节指定所需的音频长度,最长可达 30 秒。

文本条件。给定音频的文本描述,我们从预训练的文本编码器FLAN-T5中获取文本编码 ct⁢e⁢x⁢t 

持续时间编码。为了生成可变长度的音频,我们首先使用一个小型神经网络将音频持续时间编码成一个 duration embedding cd⁢u⁢r 。这与文本编码 ct⁢e⁢x⁢t 连接并馈送到 TangoFlux 以控制音频输出的持续时间。

模型架构

采用混合 MMDiT 和 DiT 架构作为 TangoFlux 的主干, 首先是6层MMDiT block,然后跟着18层DiT block,模型的特征维度是1024,总参数量为515M

Flow Matching

流匹配(Flow Matching)基于连续归一化流框架。该方法通过学习一个时间相关的向量场,将来自简单先验分布(例如高斯分布)的样本映射到复杂的目标分布,从而生成目标分布的样本。

在 TTA(文本到音频)领域的先前研究中,例如 AudioBox(Vyas 等,2023)和 Voicebox(Le 等,2023),主要采用了 Lipman 等(2023)提出的最优传输条件路径(Optimal Transport conditional path)。然而,我们的方法采用了 修正流(Rectified Flows,Liu 等,2022),这是一种从噪声到目标分布的直线路径,代表了最短路径

整流流(Rectified Flows)。给定音频样本的潜在表示 x₁ 和服从正态分布 x₀ ∼ N(0, I) 的噪声样本,通过时间步 t ∈ [0, 1] 可以构建训练样本 xₜ。模型通过学习预测速度 vₜ = dxₜ/dt 来引导 xₜ 向 x₁ 演化。尽管存在多种构建传输路径 xₜ 的方法,我们采用了 Liu 等人(2022)提出的整流流(RFs)。该方法在目标分布与噪声分布之间构建直线路径作为前向过程,其定义如公式(1)所示。经验表明,当减少采样步数时,整流流具有更高的采样效率且性能下降更少(Esser 等人,2024)。我们用 θ 表示模型 u 的参数,该模型通过直接回归预测速度 u(xₜ, t; θ) 与真实速度 vₜ 的匹配,其损失函数如公式(2)所示。

推理。在推理过程中,我们从先验分布 x~0∼𝒩⁢(𝟎,𝐈) 中采样噪声,并使用常微分方程求解器根据模型在每个时间步 t 长预测的速度 vt 来计算 x1 。在此过程中,我们使用 Euler 求解器。

CLAP 排名偏好优化 (CRPO)

CLAP 排名偏好优化 (CRPO) 利用文本-音频联合嵌入模型作为代理奖励模型,根据与输入描述的相似性对生成的音频进行排名,然后构建偏好对。

我们首先设置了一个 Ta ngoFlux 架构的预训练检查点作为要对齐的基础模型,用 π0 表示。此后,CRPO 迭代地将 checkpoint πk≔u⁢(⋅;θk) 对齐到 checkpoint πk+1 中,从 k=0 开始。每个这样的对齐迭代都包括三个步骤:(i) 批量在线数据生成,(ii) 奖励估计和偏好数据集创建,以及 (iii) πk+1 通过直接偏好优化进行微调 πk 。

Main Results

表 1:跨各种指标的音频生成模型比较。Output length 表示生成的音频的持续时间。度量包括 FD 
openl3表示 Frechet 距离、 passt KL 表示 KL 散度和 CLAP score 表示对齐。所有推理时间都是在同一个 A40 GPU 上计算的。我们在 #Params 列中报告可训练参数。

表 1 在客观指标方面将 TangoFlux 与 AudioCaps 上先前的文本到音频生成模型进行了比较。表 2 报告了具有多个事件的提示(即多事件提示)的模型性能。

Qwen2 Lora LLM微调训练教程

Github:https://github.com/datawhalechina/self-llm/tree/master

 本项目是一个围绕开源大模型、针对国内初学者、基于 Linux 平台的中国宝宝专属大模型教程,针对各类开源大模型提供包括环境配置、本地部署、高效微调等技能在内的全流程指导,简化开源大模型的部署、使用和应用流程,让更多的普通学生、研究者更好地使用开源大模型,帮助开源、自由的大模型更快融入到普通学习者的生活中。

  本项目的主要内容包括:

  1. 基于 Linux 平台的开源 LLM 环境配置指南,针对不同模型要求提供不同的详细环境配置步骤;
  2. 针对国内外主流开源 LLM 的部署使用教程,包括 LLaMA、ChatGLM、InternLM 等;
  3. 开源 LLM 的部署应用指导,包括命令行调用、在线 Demo 部署、LangChain 框架集成等;
  4. 开源 LLM 的全量微调、高效微调方法,包括分布式全量微调、LoRA、ptuning 等。

环境配置

在完成基本环境配置和本地模型部署的情况下,你还需要安装一些第三方库,可以使用以下命令:

python -m pip install --upgrade pip
# 更换 pypi 源加速库的安装
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

pip install modelscope==1.18.0
pip install transformers==4.44.2
pip install streamlit==1.24.0
pip install sentencepiece==0.2.0
pip install accelerate==0.34.2
pip install datasets==2.20.0
pip install peft==0.11.1

模型下载

使用 modelscope 中的 snapshot_download 函数下载模型,第一个参数为模型名称,参数 cache_dir 为模型的下载路径。

在 /root/autodl-tmp 路径下新建 model_download.py 文件并在其中输入以下内容,粘贴代码后请及时保存文件,如下图所示。并运行 python /root/autodl-tmp/model_download.py 执行下载,模型大小为 15GB,下载模型大概需要 5 分钟。

import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
model_dir = snapshot_download('qwen/Qwen2.5-7B-Instruct', cache_dir='/root/autodl-tmp', revision='master')

指令集构建

LLM 的微调一般指指令微调过程。所谓指令微调,是说我们使用的微调数据形如:

{
  "instruction": "回答以下用户问题,仅输出答案。",
  "input": "1+1等于几?",
  "output": "2"
}

其中,instruction 是用户指令,告知模型其需要完成的任务;input 是用户输入,是完成用户指令所必须的输入内容;output 是模型应该给出的输出。

即我们的核心训练目标是让模型具有理解并遵循用户指令的能力。因此,在指令集构建时,我们应针对我们的目标任务,针对性构建任务指令集。例如,在本节我们使用由笔者合作开源的 Chat-甄嬛 项目作为示例,我们的目标是构建一个能够模拟甄嬛对话风格的个性化 LLM,因此我们构造的指令形如:

{
  "instruction": "你是谁?",
  "input": "",
  "output": "家父是大理寺少卿甄远道。"
}

我们所构造的全部指令数据集在根目录下。

数据格式化

Lora 训练的数据是需要经过格式化、编码之后再输入给模型进行训练的,如果是熟悉 Pytorch 模型训练流程的同学会知道,我们一般需要将输入文本编码为 input_ids,将输出文本编码为 labels,编码之后的结果都是多维的向量。我们首先定义一个预处理函数,这个函数用于对每一个样本,编码其输入、输出文本并返回一个编码后的字典:

def process_func(example):
    MAX_LENGTH = 384    # Llama分词器会将一个中文字切分为多个token,因此需要放开一些最大长度,保证数据的完整性
    input_ids, attention_mask, labels = [], [], []
    instruction = tokenizer(f"<|im_start|>system\n现在你要扮演皇帝身边的女人--甄嬛<|im_end|>\n<|im_start|>user\n{example['instruction'] + example['input']}<|im_end|>\n<|im_start|>assistant\n", add_special_tokens=False)  # add_special_tokens 不在开头加 special_tokens
    response = tokenizer(f"{example['output']}", add_special_tokens=False)
    input_ids = instruction["input_ids"] + response["input_ids"] + [tokenizer.pad_token_id]
    attention_mask = instruction["attention_mask"] + response["attention_mask"] + [1]  # 因为eos token咱们也是要关注的所以 补充为1
    labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] + [tokenizer.pad_token_id]
    if len(input_ids) > MAX_LENGTH:  # 做一个截断
        input_ids = input_ids[:MAX_LENGTH]
        attention_mask = attention_mask[:MAX_LENGTH]
        labels = labels[:MAX_LENGTH]
    return {
        "input_ids": input_ids,
        "attention_mask": attention_mask,
        "labels": labels
    }

Qwen2 采用的 Prompt Template格式如下:

<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
你是谁?<|im_end|>
<|im_start|>assistant
我是一个有用的助手。<|im_end|>

加载 tokenizer 和半精度模型

模型以半精度形式加载,如果你的显卡比较新的话,可以用 torch.bfolat形式加载。对于自定义的模型一定要指定 trust_remote_code参数为 True

tokenizer = AutoTokenizer.from_pretrained('/root/autodl-tmp/qwen/Qwen2.5-7B-Instruct/', use_fast=False, trust_remote_code=True)

model = AutoModelForCausalLM.from_pretrained('/root/autodl-tmp/qwen/Qwen2.5-7B-Instruct/', device_map="auto",torch_dtype=torch.bfloat16)

定义 LoraConfig

LoraConfig这个类中可以设置很多参数,但主要的参数没多少,简单讲一讲,感兴趣的同学可以直接看源码。

  • task_type:模型类型
  • target_modules:需要训练的模型层的名字,主要就是 attention部分的层,不同的模型对应的层的名字不同,可以传入数组,也可以字符串,也可以正则表达式。
  • rlora的秩,具体可以看 Lora原理
  • lora_alphaLora alaph,具体作用参见 Lora 原理

Lora的缩放是啥嘞?当然不是 r(秩),这个缩放就是 lora_alpha/r, 在这个 LoraConfig中缩放就是 4 倍。

config = LoraConfig(
    task_type=TaskType.CAUSAL_LM,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
    inference_mode=False, # 训练模式
    r=8, # Lora 秩
    lora_alpha=32, # Lora alaph,具体作用参见 Lora 原理
    lora_dropout=0.1# Dropout 比例
)

自定义 TrainingArguments 参数

TrainingArguments这个类的源码也介绍了每个参数的具体作用,当然大家可以来自行探索,这里就简单说几个常用的。

  • output_dir:模型的输出路径
  • per_device_train_batch_size:顾名思义 batch_size
  • gradient_accumulation_steps: 梯度累加,如果你的显存比较小,那可以把 batch_size 设置小一点,梯度累加增大一些。
  • logging_steps:多少步,输出一次 log
  • num_train_epochs:顾名思义 epoch
  • gradient_checkpointing:梯度检查,这个一旦开启,模型就必须执行 model.enable_input_require_grads(),这个原理大家可以自行探索,这里就不细说了。
args = TrainingArguments(
    output_dir="./output/Qwen2.5_instruct_lora",
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    logging_steps=10,
    num_train_epochs=3,
    save_steps=100,
    learning_rate=1e-4,
    save_on_each_node=True,
    gradient_checkpointing=True
)

使用 Trainer 训练

trainer = Trainer(
    model=model,
    args=args,
    train_dataset=tokenized_id,
    data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
)
trainer.train()

加载 lora 权重推理

训练好了之后可以使用如下方式加载 lora权重进行推理:

from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
from peft import PeftModel

model_path = '/root/autodl-tmp/qwen/Qwen2.5-7B-Instruct/'
lora_path = 'lora_path'

# 加载tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_path)

# 加载模型
model = AutoModelForCausalLM.from_pretrained(model_path, device_map="auto",torch_dtype=torch.bfloat16)

# 加载lora权重
model = PeftModel.from_pretrained(model, model_id=lora_path, config=config)

prompt = "你是谁?"
messages = [
    {"role": "system", "content": "现在你要扮演皇帝身边的女人--甄嬛"},
    {"role": "user", "content": prompt}
]

text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)

model_inputs = tokenizer([text], return_tensors="pt").to('cuda')

generated_ids = model.generate(
    model_inputs.input_ids,
    max_new_tokens=512
)
generated_ids = [
    output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]

response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]

print(response)

MinMo: A Multimodal Large Language Model for Seamless Voice Interaction

FunAudioLLM Team   Tongyi Lab, Alibaba Group

用于语音交互的语音-文本多模态模型的先前工作可以大致分为 原生 模型和 对齐 模型。原生多模态模型使用单个框架同时对语音和文本的端到端理解和生成进行建模,然而,他们面临着语音和文本序列长度之间巨大差异、语音预训练不足以及灾难性地忘记文本LLMs的挑战;对齐的多模态模型在保持文本LLMs,然而,现有模型通常在小规模语音数据上进行训练,在有限的语音任务集上进行研究,并且缺乏对丰富而细致的说话风格的指令跟踪能力的系统探索

MinMo,这是一种多模态大型语言模型,具有大约 8B 参数,可实现无缝语音交互。 解决了先前对齐多模态模型的主要局限性。在 140 万小时的不同语音数据和广泛的语音任务上,通过语音到文本对齐、文本到语音对齐、语音到语音对齐和双工交互对齐等多个阶段来训练 MinMo。 经过多阶段训练后,MinMo 在保持文本LLMs,并且还促进了全双工对话,即用户和系统之间的同时双向通信。 此外,还提出了一种新颖而简单的语音解码器,它在语音生成方面优于以前的模型。MinMo 增强的指令跟踪功能支持根据用户指令控制语音生成,包括各种细微差别,包括情绪、方言和语速,并模仿特定声音。对于 MinMo,语音到文本的延迟约为 100 毫秒,理论上全双工延迟约为 600 毫秒,实际约为 800 毫秒。

  • 最先进的 (SOTA) 性能:MinMo 在语音对话、多语言语音识别、多语言语音翻译、情感识别、说话人分析和音频事件分析等基准测试中实现了当前的 SOTA 性能,同时还确保了文本大型模型的功能基本保持不变。
  • 指令控制音频生成:MinMo 支持端到端语音交互,按照用户指示控制生成音频的情感、方言和说话风格,以及模仿特定的语音音调,生成效率超过 90%。
  • 低延迟双工语音交互:MinMo 支持全双工语音交互,可实现流畅的多轮对话并防止背景噪音打断。语音到文本的延迟约为 100 毫秒,全双工延迟理论上约为 600 毫秒,实际约为 800 毫秒。
基准测试的性能概览:演示了 MinMo 在自动语音识别 (ASR)、语音到文本翻译 (S2TT)、口语问答 (SQA) 方面的功能,包括语音到文本 (S2T) 和语音到语音 (S2S)、语音分类 (VSC)、语音情感识别 (SER)、语言识别 (LID)、年龄识别和性别检测。MinMo 在所有这些任务上都超越了以前的 SOTA 模型

Introduction  

无缝语音交互表示用户与系统进行实时、自然、相关且类似人类的语音对话。促进无缝语音交互带来了巨大的挑战:

  • 系统需要准确、全面地理解音频,包括理解内容以及语音中的副语言线索(例如,情感、韵律)以及音频事件;
  • 系统应产生自然且富有表现力的语音响应;
  • 系统应向用户提供相关且合理的响应,作为智能聊天机器人;
  • 系统应支持全双工对话(同时双向通信),即系统在说话时倾听,用户在系统说话时可以自由打断,然后系统要么继续语音,要么停止响应,倾听用户,并提供对新用户查询的响应。

近年来,无缝语音交互系统获得了巨大的发展势头,尤其是随着多模态大型语言模型的进步,例如 GPT-4o和 Moshi。这些系统不仅可以产生自然而富有表现力的语音,还可以理解文字以外的线索,包括情感语气和音频事件。当前用于语音交互的多模态语言模型可分为两大类。

第一类包括原生多模态模型,例如 Moshi  和 GLM-4-Voice 。这些模型通常使用仅解码器的 Transformer 作为主干,在单个框架内同时对语音和文本模态的理解和生成进行建模;它们通常需要使用语音和文本数据进行预训练。这些模型存在两个主要限制。首先,在语音离散化之后,语音标记序列的长度通常是文本长度的两倍以上(例如,在 Moshi 中每秒 12.5 个标记)。随着模型大小的增长,序列长度的这种差异会带来挑战,例如 175B GPT-3 。其次,与文本相比,语音数据的稀缺性导致语音-文本训练数据高度不平衡,进而导致灾难性的遗忘。

第二类包括对齐的多模态模型,集成语音功能,同时旨在保持现有预训练文本LLM。这导致中间输出仍然包含文本,如 Llama-Omni  和 Freeze-Omni  等模型所示。然而,这些基于对齐的模型通常是在有限的语音数据(LLaMA-Omni 为 200K 样本,Freeze-Omni 为 120K 小时)上进行训练的,这导致了关于大型语音数据集对模型功能的影响以及原始文本LLM可能受到影响的问题。此外,尚未对这些模型进行广泛的语音任务调查,例如语音翻译、情感识别、说话人分析、语言识别和音频事件检测。此外,这些模型缺乏对丰富而细致的说话风格的指令遵循能力的系统评估,也缺乏对全双工对话能力的开发和评估,以实现无缝语音交互。

在这项工作中,我们引入了一种新的多模态大型语言模型 MinMo,以解决现有对齐多模态模型的这些局限性。MinMo 经过超过 140 万小时的语音数据训练,包括各种任务,例如语音转文本、文本转语音和语音转语音。这种广泛的培训使 MinMo 能够在各种基准上实现最先进的 (SOTA) 性能。还采用了一些方法,有效缓解了在大规模数据集训练后,模型对原始文本语言模型(text-LLM)聊天能力的灾难性遗忘问题,同时增强了语音理解和生成能力。

还提出了一种新颖的语音解码器,在保持结构简单的同时,兼具了优异的语音生成性能。LLaMA-Omni 使用一种非自回归(NAR)流式 Transformer,它将语言模型(LLM)的输出隐藏状态作为输入,并通过连接时序分类(CTC)预测响应的离散语音标记序列。然而,这种方法的性能相比自回归语音解码器较差。Freeze-Omni 使用了三种语音解码器,包括 NAR 前缀语音解码器、NAR 语音解码器和 AR(自回归)语音解码器,使模型结构更为复杂。有别于这两种策略,我们为 MinMo 设计了一种自回归(AR)流式 Transformer,其通过固定比例将 LLM 的输出隐藏状态与语音标记混合处理。

开发了一种机制,可以有效地促进与 MinMo 的全双工相互作用。具体来说,我们实现了一个全双工预测模块,该模块利用文本LLM 的语义理解功能来决定是继续系统响应,还是让步、倾听和响应新的用户查询。对于 MinMo,语音到文本的延迟约为 100 毫秒;全双工延迟理论上约为 600 毫秒,实际时约为 800 毫秒。

Related Work

Multimodal Spoken Dialogue Models:

多种语音基础模型已被开发用于通用音频理解,但尚未系统地探索其在语音交互方面的应用。例如,Qwen2-Audio 将 Whisper 语音编码器与预训练文本语言模型(LLM)集成,并通过多任务预训练和基于指令的监督微调使 LLM 具备语音理解能力。SALMONN 是另一种用于通用音频理解的语音-文本 LLM,通过 Q-Former 将单独的语音和音频编码器与预训练文本 LLM 集成,并采用 LoRA 进行模态对齐。

由于本研究旨在开发一种端到端多模态模型以实现无缝语音交互,我们将重点比较 MinMo 与用于语音交互的语音-文本模型(或称多模态语音对话模型)。同时或受到 GPT-4o 的启发,多模态语音对话模型的开发正如火如荼地进行,以实现与用户实时语音对话的能力。(Ji et al., 2024a) 对近期的语音对话模型进行了深入综述。一些研究支持传统的回合制语音聊天(即半双工通信),但无法处理全双工语音交互(即同时双向通信)。这些模型包括协作系统和端到端框架。

PSLM (Mitsui et al., 2024) 是一种协作系统,因为它依赖 ASR 处理音频输入,这会丢弃副语言信息并导致错误传播。PSLM 同时生成语音和文本标记,从而减少语音生成延迟;然而,其响应质量较低。与 PSLM 等协作系统不同,端到端框架直接接受音频输入并生成音频输出。

Llama-Omni (Fang et al., 2024) 和 Mini-Omni (Xie & Wu, 2024) 是两个近期的端到端框架,但尚未针对全双工通信进行训练。Llama-Omni 集成了 Whisper 语音编码器、语音适配器、流式语音解码器和声码器,并以预训练文本 LLM 作为基础。其语音解码器以非自回归(NAR)方式生成与生成文本前缀对应的离散单元。该模型采用两阶段训练策略:第一阶段冻结语音编码器,仅对语音适配器和 LLM 进行自回归训练;第二阶段冻结语音编码器、语音适配器和 LLM,仅使用 CTC 损失对语音解码器进行训练。Llama-Omni 被评估在语音转文本指令执行和语音转语音指令执行任务上的表现。

Mini-Omni 同样采用 Whisper 编码器,并通过适配器进行最小化训练,以保留 LLM 的能力。该模型通过模态对齐、适配器训练和多模态微调三个阶段进行训练。Mini-Omni 同时生成文本和音频标记,并填充 N 个标记以确保首先生成对应的文本标记,从而指导音频标记的生成。

MinMo 支持全双工语音对话。现有的全双工语音聊天系统同样可以分为协作系统和端到端模型两类。在协作系统中,VITA (Fu et al., 2024) 同时运行两个模型,即生成模型和监控模型,以支持全双工通信。当生成模型正在生成系统响应时,监控模型会监控环境,并在检测到有效的用户打断后结合上下文对新的用户查询提供响应,同时生成模型暂停并切换到监控角色。值得注意的是,VITA 仍然依赖外部的 TTS 模块生成语音输出。

另一种协作系统 (Wang et al., 2024a) 则通过 LLM 接入 ASR 模块和流式 TTS 模块运行。该系统不需要模态对齐,而是对预训练的文本 LLM 进行监督微调,采用以下范式:在每个时间步,LLM 要么处理一个输入标记,要么生成一个文本标记,要么输出一个特殊的控制标记,用于在 SPEAK 和 LISTEN 状态之间进行状态转换。所有这些任务都被定义为基于单一序列化流对话的下一个标记预测。全双工对话学习基于由 GPT-4 合成的数据进行,这些数据生成了包含不同类型用户打断的对话。值得注意的是,由于其级联架构,该系统面临高达 680ms 的高延迟问题。

在端到端全双工模型中,早期的 dGSLM (Nguyen et al., 2022) 提出了一个双塔架构,用于联合处理用户语音和系统语音的音频标记流。然而,该方法存在以下几个缺点:它依赖于仅基于语音的训练,未能利用预训练文本 LLM 的能力;仅使用语义标记,未充分建模语音的声学信息;不支持在线模式。LSLM (Ma et al., 2024b) 使用仅解码器的 Transformer 生成语音标记,并通过流式 SSL 编码器处理监听标记。该模型引入了一个中断标记,用于在检测到用户尝试轮流对话时停止发言。然而,模型在生成合理响应方面表现不足。

在最近的端到端全双工模型中,Moshi、GLM-4-Voice 、SyncLM 、IntrinsicVoice 和 Omni-Flatten原生多模态模型。这些模型在单一框架中同时建模语音和文本模态的理解和生成,基于 GPT 主干,并需要使用语音和文本数据进行自监督自回归预训练。如第 1 节所述,这些原生多模态模型需要应对语音标记与文本标记之间序列长度差异显著的问题,以及语音-文本训练数据高度不平衡及其导致的灾难性遗忘。IntrinsicVoice 使用 GroupFormer 从 LLM 的隐藏状态生成 HuBERT 标记,有效地将语音序列缩短到与文本序列相当的长度。Omni-Flatten 采用分阶段的逐步后期训练策略,通过块级的单流平铺语音和文本标记来学习全双工和无文本的语音到语音交互。

与这些原生多模态模型不同,我们的 MinMo 属于对齐多模态模型类别,该类别还包括 Llama-Omni、Mini-Omni2和 Freeze-Omni 。对齐多模态模型集成了语音功能,同时旨在保留现有预训练文本 LLM 的能力。Mini-Omni2 引入了基于命令的中断机制以支持全双工对话,但仅在 ASR 任务上进行评估,并与 Whisper、VITA 和 Mini-Omni 进行比较。Freeze-Omni是一个语音到语音模型,通过冻结预训练的文本 LLM 来保留其能力。它支持流式输入语音和生成流式输出语音,使用多任务训练,并通过块级状态预测来建模全双工语音交互。

我们的 MinMo 与这些对齐多模态模型在以下方面不同:我们探索了在更大规模的语音数据集(1.4 百万小时多样化语音数据,相较于 LLaMA-Omni 的 20 万样本和 Freeze-Omni 的 12 万小时)以及更广泛的语音任务上训练 MinMo。MinMo 还通过新颖的语音解码器、增强的指令跟随能力,以及对全双工语音对话能力的系统性训练和评估,与现有的对齐多模态模型形成了差异化。

文本风格 – 可控语音合成

多模态语音对话模型相比于基于文本的对话模型的显著特征在于其能够理解和生成超越文本内容的声学信息。语音模态不仅包含内容,还包括情感、方言和语速等声学信息。一个智能的多模态语音对话模型应该能够全面理解输入语音中的声学信息(例如情感),并且能够生成带有指定情感、方言、语速的响应,甚至模仿特定的声音,从而在沟通中实现更深层次的理解和响应。

协作系统如 ParalinGPT 、E-Chat 和 Spoken-LLM 通过整合副语言特征增强对情感等声学信息的理解。这些系统可以与可控风格的文本转语音(TTS)系统结合,以生成带有特定情感、语速和音量的响应。在文本风格可控 TTS 方面取得了显著进展,例如 TextrolSpeech 、PromptTTS 、PromptTTS2、InstructTTS 和 ControlSpeech 。

与这些协作系统相比,Moshi 使用一个具有单一演员声音和超过 70 种说话风格录音的 TTS 引擎,生成训练数据以支持在端到端模型中理解和生成声学信息。GLM-4-Voice利用高质量的多轮语音对话数据,这些数据针对特定语音风格需求(如语速、情感或方言)进行定制,从而支持风格可控的语音对话。然而,据我们所知,尚无研究表明对齐的多模态模型能够支持风格可控的语音生成。

与之前的研究声称对齐的多模态模型(如 Llama-Omni 和 Freeze-Omni)仅允许语言模型控制语音的内容而非风格和韵律相反,我们在本研究中提出了一种新型的流式语音解码器,用于对齐多模态模型 MinMo,并发现该解码器不仅增强了指令跟随能力,还使 MinMo 能够生成符合用户指定情感、方言、语速以及模仿特定声音的语音。

MinMo

模型架构

语音编码器采用预训练的 SenseVoice-large 编码模块,提供了强大的语音理解能力,支持多语言语音识别、情感识别和音频事件检测。输入投影器由一个随机初始化的两层 Transformer 和一个 CNN 层组成,用于维度对齐和降采样。我们选用预训练的 Qwen2.5-7B-instruct 模型作为预训练的文本 LLM,因为其在各种基准测试中的表现卓越(Team, 2024)。此外,我们利用 CosyVoice 2的流式音频生成机制,该机制具备低延迟并提供具有竞争力的语音合成性能。

对于每批接收的五个文本标记,我们将这些标记及其对应的最终隐藏层向量同时传递到输出投影器和语音标记语言模型(Voice Token LM)。输出投影器是一个单层线性模块,随机初始化用于维度对齐。语音标记语言模型(Voice Token LM)采用预训练的 CosyVoice 2 LM 模块,并以自回归方式生成十五个语音标记,确保高效且无缝的音频合成。这些语音标记由 Token2wav 合成器模块实时处理,生成最终的音频输出。

Token2wav 合成器包括一个预训练的流匹配模型,该模型将标记转换为梅尔频谱图,以及一个预训练的声码器,将梅尔频谱图转换为波形,两者均来源于 CosyVoice 2。MinMo 通过额外的隐藏嵌入实现端到端的完整训练,这些嵌入有助于根据用户指令控制语音风格,例如情感、方言和语速等。语音生成的详细信息详见 3.2 节。

全双工预测器模块由一个单层 Transformer 和一个线性 softmax 输出层组成,两者均为随机初始化。该模块实时预测是否响应用户指令或暂时停止当前系统播报,以处理来自用户的进一步音频输入。一旦全双工预测器决定系统响应是合适的,MinMo 会生成文本输出并同步以逐标记方式生成音频标记。

Streaming Voice Decoder

语音解码器包括三个组件:输出投影器、语音标记语言模型(Voice Token LM)和流式标记到波形(Token2wav)合成器。

输出投影器对齐 LLM 的维度与语音解码器的维度。LLM 的隐藏状态包含丰富的上下文信息,但语义上可能不够明确;而采样得到的文本标记更加精确,与生成的文本一致。同时,当前用户输入的隐藏状态包含显式的指令信息。在每轮对话中,用户输入的嵌入与 LLM 最后一层输出的隐藏状态将沿特征维度拼接,形成查询嵌入。查询嵌入与五个采样文本标记的嵌入,以及 LLM 最后一层输出的隐藏状态,将沿序列维度拼接并输入到投影器中。投影器的输出被称为语义向量,这些向量代表了丰富且准确的语义信息。

在输出投影器之后,使用语音标记语言模型(Voice Token LM)以自回归方式生成语音标记。该语言模型在交替的文本和语音标记序列上运行。具体而言,我们以 5:15 的固定比例混合语义向量和语音标记,即每五个语义向量后跟随十五个语音标记。

在训练过程中,采用教师强制策略,并引入一个特殊标记,用于指示下一个语义向量应被连接。当 LLM 的文本响应完成且语义向量耗尽时,我们插入一个“语音轮次”(turn of speech)标记,提示语音标记语言模型接下来的标记应完全为语音标记。当生成“语音结束”(end of speech)标记时,语音合成过程结束。

为了从语音令牌重建波形,我们使用现成的流式 token2wav 合成器。token2wav 合成器包含一个数据块感知流匹配模型和一个 mel 到 wave 声码器,能够以 15 个令牌为块合成波形。

语音解码器的理论延迟可以按以下方式计算:

其中 dl⁢l⁢m 表示 LLM 生成一个文本令牌的计算时间, dl⁢m 表示 LM 生成一个语音令牌的时间, ds⁢y⁢n 表示 token2wav 合成器生成每个语音令牌对应的波形的时间。

Tasks and Training Data

MinMo 的训练任务包括四类,包括 Speech-to-TextText-to-SpeechSpeech-to-Speech 和 Speech-to-ControlToken 任务。表 2 列出了每个类别中的具体任务及其相应的数据量表。

Speech-to-Text 任务。此类别包含大约 120 万小时的语音-文本配对数据,包括自动语音识别 (ASR)、语音到文本翻译 (S2TT)、语言识别 (LID)、上下文偏差语音识别、语音情感识别 (SER)、音频事件检测 (AED)、说话人分析、口语平滑等任务。这些任务的训练数据以 ChatML 格式组织,如以下示例所示:

Text-to-Speech tasks。该类别的数据主要由基础语音合成数据组成,与训练 CosyVoice 2 的数据相同。它包括 170000 小时的文本语音配对数据,并支持四种语言:中文、英文、韩文和日文。此外,还有大约 1000 小时的音频生成数据由指令控制。这些说明扩展为包括由 Qwen-Max 生成的自然语言描述,利用人类标记的属性,例如情感、语速、方言和角色扮演。

Speech-to-Speech 任务。 Speech-to-Speech 数据主要通过模拟获取,包括大约 10000 小时的多轮对话语音和 100 小时的风格可控多轮对话语音。模拟 Speech-to-Speech 聊天数据的方法如下:

对于主要来源于 Alpaca 和 ShareGPT3 的文本聊天数据,我们利用 CosyVoice 的零样本上下文生成方法 将用户文本转换为用户语音。我们使用来自选定说话人的 2 小时数据对 CosyVoice 的基本模型进行微调,为目标说话人创建一个语音合成模型,称为 CosyVoice-SFT。该模型合成了助手的语音(即系统语音)。使用zero-shot上下文生成进行用户语音合成的优势在于它能够确保生成的用户语音的多样性,从而增强 MinMo 的泛化性。

为了解决合成音频和真实音频之间的差异,我们从 ASR 数据中选择合适的真实语音作为用户语音查询,并使用相应的文本作为 Qwen-Max 的输入生成响应文本,然后使用 CosyVoice-SFT 模型将其合成为辅助语音。这种方法进一步增强了模型对真实用户音频输入的鲁棒性。

为了生成涵盖不同说话风格的对话语音,我们最初使用 Qwen-Max 来创建丰富的风格可控、多轮次文本对话集合。用户查询通过 Cosyvoice 的 zero-shot generation 转换为语音。随后,我们使用 Cosyvoice 2 来生成助手的富有表现力的语音。具体来说,我们将助手的响应内容和指导提示输入到 Cosyvoice 2 中,以合成特定风格的语音。此外,使用小型、多样化和初步录制的语音语料库作为提示语音,通过零镜头生成合成表达性响应语音。前一种方法增强了模拟语音的多样性,而后者更有效地构建了各种风格的表现力。

Speech-to-ControlToken 任务。Speech-to-ControlToken 数据主要由两部分组成。第一部分是从现有的真实语音交互数据中提取的,而第二部分是使用文本对话数据进行模拟的。具体来说,现有的真实语音交互数据包括 Alimeeting 、Fisher 等资源,以及我们内部的语音交互数据,总共大约 3000 小时。模拟数据主要包括开源 MOSS 数据集 和通过合成我们内部文本对话数据的口语对话,产生了大约 1000 小时的语音聊天数据。当使用这些语音交互数据构建双工训练数据时,我们应用启发式规则在样本上自动注释双工标签,如下所示:

  • 对于助手的轮流,将用户轮到的终点作为助手轮次的起点。
  • 对于用户的轮次,将助手轮次结束后的时间间隔 T 作为用户轮次的起点,其中 T∼𝒩⁢(0.6,0.42) 。
  • 对于用户的 Back-Channel,当用户(以对话中的一个说话者为用户)无法打断另一个说话者时,我们从语音交互数据中选择实例,并将其视为用户 Back-channel 的训练样本。

模型训练

MinMo 通过四个对齐阶段逐步训练:(1) 语音到文本对齐,(2) 文本到语音对齐,(3) 语音到语音对齐,以及 (4) 双工交互对齐。通过四个对齐阶段,MinMo 获得了端到端的音频理解和生成能力,同时保留了主干文本LLM,实现了低延迟并为用户提供了无缝的语音聊天体验,类似于 GPT-4o。这四个阶段详细说明如下。

语音到文本对齐Pre-align > Full-Align > SFT[LoRA]

第一阶段使用表 2 所示的语音转文本数据,将音频模态的输入潜在空间与预训练文本LLM。此阶段包括对图 3 中的输入投影和语音编码器的逐步更新,以及使用 LoRA 更新文本 LLM。考虑到语音编码器和LLM (Qwen2.5-7B) 是预先训练的,而输入投影的参数是随机初始化的,我们使用表 2 所示的语音转文本数据子集执行预对齐训练 (Pre-align),仅更新输入投影器。这个 Pre-align 阶段有效地防止了随机初始化的参数在初始训练阶段对预训练的 Voice Encoder 产生大的梯度影响。在预对齐之后,我们使用完整的语音转文本数据来训练输入投影器和语音编码器,同时保持 LLM 参数冻结,这个过程称为完全对齐。在 Full-Align 之后,使用涵盖各种任务的大约 130 万个样本进行指令微调 (SFT)。在此阶段,LLM 使用 LoRA 进行更新,从而增强模型遵循指令的能力。Full-Align 和 SFT 阶段中使用的特定数据比例如图 4 所示。Pre-Align 阶段使用大约 1/10 的 Full-Align 数据。

文本到语音对齐

第二阶段使用文本转语音数据将文本LLM音频模态的输出潜在空间对齐。此阶段首先训练 Output Projector,然后联合训练 Output Projector 和 Voice Token LM,同时保持其他 MinMo 参数冻结。除了基本的文本转语音功能外,我们还利用端到端框架使 MinMo 能够在语音交互中遵循用户指示,从而提供更富有表现力和娱乐性的音频响应。例如,用户可以通过指令控制音频输出的情感、语速、方言口音或说话人风格。构建了大约 1000 小时的 Instruct 语音合成数据,格式如表 3 所示。

语音到语音对齐。

第三阶段使用大约 10,000 小时的配对音频数据继续训练 MinMo。与文本到语音对齐阶段一致,我们继续仅更新 Output Projector 和语音令牌 LM。语音到语音对齐的训练数据不仅包括一般的语音到语音对话,还包括具有各种设置的音频生成指令,例如采用特定的方言、语速和口语对话的情感。我们发现,即使不更新 LLM,仅通过利用与小规模指令数据集对齐的嵌入(150 小时),大型模型仍然可以学习相当有效的音频生成控制能力。

双工交互对齐。

在完成前三个训练阶段后,MinMo 获得了音频理解、音频生成和半双工语音对话的能力。在此基础上,我们进一步添加了一个全双工模块,该模块经过 4000 小时的长篇人际口语对话训练。Full Duplex Predictor 模块在此阶段专门进行训练。Full Duplex Predictor 采用 全双工预测器将LLM的隐藏嵌入作为输入,用于预测模型是否需要生成响应。全双工预测器利用LLM固有的语义理解能力来判断:1)模型是否应该回应当前用户查询,2)模型是否应该停止正在进行的语音输出以聆听用户查询并提供适当的响应。来预测模型是否需要生成响应

Experiments

根据多个基准评估 MinMo:

语音识别和翻译

在普通话、英语、日语、韩语和其他六种语言的公共测试集上评估了 MinMo 的语音到文本转录功能。

对于普通话 (ZH)、日语 (JA)、韩语 (KO) 和粤语 (YUE),我们采用字符错误率 (CER) 来评估转录性能。对于英语 (EN)、德语 (DE)、法语 (FR)、俄语 (RU)、西班牙语 (ES) 和意大利语 (IT),单词错误率 (WER) 用作评估指标。

与 Whisper Large v3 和 Qwen2-Audio相比,MinMo 在各种语言的大多数测试集上都实现了卓越的 ASR 性能

在提示符显示或不带有 LID【语言ID】 信息的 Common Voice 上进行测试时,Whisper Large v3 和 Qwen2-Audio 的平均错误率存在显著差距,这表明这两个模型强烈依赖 LID 信息。相比之下,无论是否存在语言识别,MinMo 都表现出稳健且一致的 ASR 性能。

多语言语音翻译

在 Fleurs  和 CoVoST2  测试集上评估了语音到文本的翻译能力。

与其他端到端基线相比,MinMo 在中 ↔ 英和日 ↔ 英翻译上实现了 SOTA 性能,在其他语言对上实现了顶级性能。我们将这种强劲的性能归功于广泛的语音翻译训练数据(表 2 中 451K 小时的 S2TT 训练数据)和强大的音频编码器。 值得注意的是,尽管我们只用 CoVoST2 集(不包括 Fleurs 集)来增强我们的训练数据,但我们的模型在两个测试集上保持一致的性能,表明具有高鲁棒性。

Language Identification  语言识别

使用 Fleurs 数据集,该数据集涵盖 102 种语言。MinMo 实现了 85.3% 的语言识别准确率,优于表 7 所示的所有先前模型。具体来说,零样本 Whisper-V3 经常将粤语错误地归类为中文,而 MinMo 可以准确地识别粤语。

上下文偏见语音识别

上下文偏见,或称为热词定制,允许用户根据特定的上下文或热词获得定制化的ASR(自动语音识别)结果。MinMo通过集成用于上下文偏见的高级提示增强了ASR功能。我们为对齐和SFT阶段准备了相应的训练数据,通过将热词组织到语音处理指令之前的提示中,从而实现有效的定制。评估包括热词偏见测试和一般偏见测试,如表8所示。

热词偏见测试涉及使用SeACo-Paraformer(Shi et al., 2024)提供的三个数据集,这些数据集包含用于偏见评估的热词。一般偏见测试则使用包含较少热词的数据集,用于评估对无关热词的抗干扰能力。

MinMo 在 ASR 准确性(带和不带热词)以及硬质热词的召回率方面优于竞争性基线 SeACo-Paraformer

Speech Analysis and Understanding

Speech Emotion Recognition

使用来自 EmoBox 的七个广泛使用的情绪识别数据集评估了 MinMo 的语音情感识别 (SER) 能力,包括 CREMA-D (Cao et al., 2014)、MELD (Poria et al., 2019)、IEMOCAP (Busso et al., 2008)、MSP-Podcast (Martinez-Lucas et al., 2020)、CASIA (Zhang & Jia, 2008)、MER2023 (Lian et al., 2023) 和 ESD (周 et al., 2021)。这些数据集包括中英文语言和场景,例如表演、电视剧和日常对话。我们采用未加权平均准确率 (UA)、加权平均准确率 (WA) 和宏观 F1 分数 (F1) 作为评价指标。引用了来自最近的 SER 工具包 EmoBox(马 et al., 2024a)的这些测试集的结果。我们还使用其发布的模型检查点评估了基线音频LLM 模型 SALMONN 和 Qwen-Audio。

MinMo 在该基准测试的所有任务上都优于所有基线模型,包括语言 ID、性别、年龄、情感、发声分类任务,除了在声音问题分类任务上表现优于 Qwen-Audio

Audio Event Understanding

使用 Air-Bench 基准测试,将 MinMo 的语音和音频事件理解能力与其他 Audio-LLM。结果如表 12 所示。在语音声音分类任务 (Vocal Sound) 上,MinMo 超越了所有基线模型。然而,我们发现,在更复杂的声音问答任务中,MinMo 的表现比 Qwen-Audio 差,尽管性能仍然优于其他模型。这可以归因于两个因素:首先,在语音编码器和训练范式中,MinMo 主要是为语音交互而设计的,因此一些声音问题可能会超出其范围;其次,在评估过程中,MinMo 会预测音频中发生的情况,而不是严格选择 Air-Bench 提供的选项,因此 MinMo 生成的一些正确或相似的正确响应与后处理脚本的错误选择保持一致。

Speaker Analysis  

说话人分析涉及几项对于理解音频数据并与之交互至关重要的任务,包括性别检测、年龄估计、说话人计数、说话人识别、多说话人识别和目标说话人识别。结果表明,MinMo 在性别检测和年龄估计任务上优于所有基线模型

Speech-to-Text Enhancement

Spoken Language Smoothing【口语平滑】

口语语言平滑任务以口语ASR(自动语音识别)转录文本为输入,输出正式风格的书面文本。表14展示了口语语言平滑的一些示例。为该任务,我们通过扩展为ASR转录文本的口语到书面转换而创建的SWAB数据集(Liu et al., 2025),构建了一个多领域数据集用于训练和评估。SWAB数据集源自中文和英文的会议、播客及讲座。

在为原始视频和音频生成ASR转录文本后,大约十位注释人员根据ASR转录文本创建正式风格的书面文本,同时保留原始内容。SWAB的训练集包含20,000段文本,测试集包括100段中文和英文的随机抽样段落。我们对SWAB测试集进行全面微调,并将MinMo与基于Qwen2.5-7B的模型进行比较,结果如表15所示。

在客观指标评估中,我们使用BLEU(Papineni et al., 2002)、ROUGE(Lin, 2004)和BLEURT(Sellam et al., 2020),以人工目标为参考。然而,我们注意到口语语言平滑任务具有显著的主观性和多样性,因此基于词汇匹配的客观指标可能无法充分反映模型性能。因此,我们采用人工和LLM注释来提供信实性(S-Faithful,即对原始内容的信实性)和正式性(S-Formal)的排名评估。自动化LLM评分的提示见附录A.1。

表15显示,我们的模型与Qwen2.5-7B的性能相当,表明MinMo在平滑口语语言方面具有较好的能力。

标点插入和反向文本规范化

对于标点插入 (PUNC) 和反向文本归一化 (ITN) 任务,我们使用来自 Fleurs 数据集的中文和英文数据。我们将 MinMo 与 SenseVoice-L 和 whisper-large-v3 进行比较,如表 16 所示。鉴于标点符号插入和 ITN 任务的主观性,我们使用 GPT-4 Turbo 对三个结果进行排序进行评估。附录 A.2 中提供了自动评分的任务提示。第一名获得 3 分,第二名获得 2 分,第三名获得 1 分。最终分数是所有分数的平均值。在准备测试数据时,我们使用随机选项洗牌和多轮评分,以减少使用 ChatGPT 进行评估时的不确定性。最终结果表明,MinMo 在标点插入和 ITN 的主观评价中表现更好

Voice Generation

文本到语音(TTS)

为了评估我们语音解码器的合成准确性,我们将最新的SEED测试集(Anastassiou et al., 2024)转换为ChatLM格式。在该格式中,文本以用户内容的形式呈现,并以“Copy:”命令为前缀,LLM预计会复制该文本。测试集包括2,020个中文案例和1,088个英文案例。对于中文案例,我们使用了Paraformer-zh模型(Gao et al., 2022),而英文案例则使用了Whisper-large V3(Radford et al., 2023)。鉴于LLM存在的指令跟随问题,我们在推理过程中应用了教师强制方案,以最小化输入和输出文本之间的差异。语音解码器的内容一致性通过中文的CER(字符错误率)和英文的WER(词错误率)进行评估。

我们的发现表明,即使在应用了教师强制方案的情况下,只有大约20%的测试案例的输入和输出文本与LLM完全一致。由于不一致的输入和输出可能导致语音解码器的隐藏状态混乱,因此仅包括输入和输出文本一致的测试案例来计算错误率。结果如表17所示。我们观察到,与TTS基线模型CosyVoice 2.0-SFT(Du et al., 2024b)相比,MinMo在中文测试集上表现出稍微较低的内容一致性和语音质量。在英文测试集上,MinMo在内容一致性上表现相似,但NMOS(语音质量评分)稍低。这个下降可以归因于微调的说话人不同的声学特性,这影响了识别模型和NMOS评分器。然而,这种下降不会显著影响人类的理解。因此,主观评估可能更适合语音到语音的语音聊天模型,我们将在未来的工作中进一步探讨这一点。

指令跟随语音生成

为了评估指令跟随语音生成的性能,我们开发了一个包含30个会话和122个回合的多轮中文语音到语音测试集,涉及12种指令控制类型。这些控制类型包括情感(高兴、悲伤、惊讶、愤怒、恐惧)、方言(粤语、四川话)、语速(快、慢)、角色扮演(机器人、佩佩)以及默认风格。

为了评估指令跟随语音生成的准确性,听众根据指令类型对生成的音频进行分类。如表18所示,MinMo在指令控制准确性方面优于基线模型GLM-4-Voice,特别是在方言和角色扮演方面。

Voice Chat

为了将基础模型的对话功能转移到语音模态,我们为语音转文本 (speech2text) 和语音转语音 (speech2speech) 场景构建了多轮次对话数据。speech2text 数据主要分为两部分。首先,它源自开源多轮次纯文本数据,我们使用零样本文本转语音 (TTS) 技术合成用户轮次。其次,我们使用真实的自动语音识别 (ASR) 训练数据作为聊天查询,从大型模型获取文本响应,从而为 speech2text 生成交互式训练数据。

MinMo 模型在语音转语音 (S2S) 模式下与现有基线相比具有显著优势,实现了新的最先进的 (SOTA) 结果。在语音转文本 (S2T) 模式下,它还在 Llama Question 和 Web Question 数据集上实现了 SOTA 性能。但是,MinMo 的测试结果仍然表明,与 S2T 模式相比,S2S 模式的性能明显下降。我们将其归因于这样一个事实,即测试集中的许多答案都具有丰富的文本结构和专业词汇,这对模型的文本转语音 (TTS) 功能提出了更高的要求。此外,在 S2S 评估中用于获取答案文本换语音的自动语音识别 (ASR) 模型也会在一定程度上影响 S2S 指标。

从表 20 中可以观察到,通过将额外的 speech2text 任务数据整合到 MinMo 训练中,我们能够有效地保持基础模型的对话能力。与 ASR 与纯文本基本模型相结合的性能相比,MinMo 的对话能力在很大程度上保持一致。但是,MinMo 的响应分数略低于 Ground Truth 响应的质量。我们认为这种差异可以归因于两个主要原因。首先,多个语音任务的集成和在基础模型上实现 LoRA 训练,在一定程度上削弱了原始大型语言模型 (LLM。该表显示,与 ChitChat 测试集相比,MinMo 在 Alpaca 测试集上表现出更大的性能变化。其次,MinMo 的音频理解能力还有进一步改进的空间,并且仍有可能降低 ASR 任务中的字符错误率 (CER)。

Full Duplex Spoken Dialogue

为了评估 MinMo 在全双工语音交互中的能力,我们构建了三个测试集:中文 Alimeeting 数据集、英文 Fisher 数据集和一个模拟测试集,旨在更接近真实的人机对话场景。我们从两个角度评估 MinMo 的全双工能力:预测性能和预测效率。关于预测性能,评估分为三个任务:辅助轮流、用户轮流和用户反向引导。对于轮流任务,我们采用正 F1 分数作为分析指标,并引入了偏移距离 ( K ) 来更好地分析模型的性能。对于用户反向通道任务,我们利用准确性来评估 MinMo 识别反向通道话语的能力。

MinMo 模型在人机对话数据集上表现出值得称道的结果,无论是用户轮流还是助手轮流。当 K=10 时,预测性能接近 99%。在实际人机对话的测试集中,与人机对话测试集相比,MinMo 模型在辅助轮流上的表现表现出一定程度的下降。我们认为这主要是由于真实人类对话中背景噪音、语速、停顿和其他因素的高度可变性,这可能导致模型在助理轮流任务中出现一定程度的误判。但是,对于人与人对话中的用户轮流预测,MinMo 模型仍然保持了高水平的灵敏度和预测性能,确保在用户说话时系统及时停止说话,从而避免与用户重叠语音。这种敏感性和对用户语音的尊重也解释了为什么 MinMo 模型对用户反向通道评论保持 70%-80% 的预测准确性,如表所示。这与 user turn-taking model 的调优是一致的,表明两者之间存在一定的权衡。

为了对 MinMo 双工模式进行效率分析,我们还分别对人机对话和人机对话测试集进行了测试。如表 23 所示,MinMo 在用户轮流中的平均响应延迟为 250ms。在人机测试集中观察到最快的响应速度,为 88.8 毫秒,而最具挑战性的 Alimeeting 测试集显示延迟为 448.8 毫秒。在辅助轮流方面,MinMo 的平均响应延迟在 660ms 左右,与用户轮流预测所需的响应时间相比,这要长。我们将其归因于以下事实:用户轮流涉及用户语音的开始部分,而助手轮流涉及用户轮到即将结束的部分。因此,助理轮流的上下文语义信息更加全面,从而缩短了决策所需的时间滞后。

Full Duplex System Latency

MinMo 的双工交互由四个模块组成:全双工预测器,负责双工控制,语音转文本模块(语音编码器+输入投影仪+LLM)、文本转语音标记模块(输出投影仪+语音标记 LM)和 Token2Wav 模块。表 21 显示了每个模块的延迟。以 Assistant Turn-taking 为例,当用户的实际语音结束时,双工模型通常需要 250 ms 的延迟进行评估。在 Speech-to-Text 过程中,对前 5 个文本标记的预测大约需要 150 毫秒。预测最初的 15 个语音令牌大约需要 70 毫秒,从语音令牌转换到第一个音频数据包需要额外的 130 毫秒。因此,在开发基于 MinMo 的全双工语音对话系统时,助理轮流的标准体验延迟约为 250 + 150 + 70 + 130 = 600 毫秒。上述数值估计值是在使用 L20 GPU 和 BF16 模型格式进行测试期间得出的。

总结/局限性

MinMo 在超过 140 万小时语音的广泛数据集上进行了训练,展示了各种基准(包括口语对话、多语言语音识别和情感识别)的最新性能。通过利用多阶段对齐策略,MinMo 巧妙地平衡了音频理解和生成,同时最大限度地减少了基于文本的 LLMs。一项关键创新是 MinMo 用于流式端到端音频生成的新颖对齐方法。通过利用文本模型的隐藏层表示,MinMo 的语音解码器实现了结构简单性和有竞争力的性能以及低延迟。这种方法显著增强了模型的指令遵循能力,能够生成细致入微的语音,准确反映用户指定的情感、方言和说话风格。此外,MinMo 支持全双工交互,以大约 600 毫秒的延迟提供无缝的对话体验。总之,MinMo 代表了语音交互系统领域的重大进步。它不仅解决了序列长度差异和数据不平衡的固有挑战,还为自然和富有表现力的语音交互设定了新标准,为多模态语言模型的未来发展铺平了道路。

MinMo 有一些需要解决的局限性。首先,MinMo 基于预训练的文本大模型,使用对齐方式集成音频理解和音频生成能力;文本大型模型仅参与 LoRA 更新,其遵循各种指令(例如语言和任务遵循)的能力需要改进。需要进一步探索以确定使用更多高质量的文本数据对文本大型模型进行更全面的更新是否可以增强其指令跟踪能力。其次,MinMo 的端到端音频生成存在一些长尾发音错误问题。这个问题部分是由于保留了 LLM,部分原因是端到端建模输出文本中的一些特殊符号无法有效地转换为语音。可以探索数据扩展来解决这些长尾问题。此外,由 MinMo 中的指令控制的音频生成的整体效率需要提高。这部分是由于当前指令数据的整体大小较小,并且仅使用隐藏嵌入进行端到端对齐的限制,这限制了历史信息的传输。最后,虽然 MinMo 实现了基于语义的双工模块,但它仍然需要单独的 AEC 【AEC模块用于消除语音通信中由于麦克风拾取扬声器输出信号而产生的回声。】和 VAD 模块。将来,将探索完全端到端的双工模型。

SLAM-Omni: 单阶段训练下支持可控音色的语音对话模型

近几个月来,许多端到端的语音对话系统涌现,旨在解决级联系统中交互延迟过高以及基于文本交互下副语言信息丢失的问题。然而,目前大多数语音对话模型依赖于大量的语音对话数据以及高昂的训练代价,且存在响应音色单一的弊端。

近日,上海交通大学计算机系X-LANCE实验室联合微软亚洲研究院推出了面向低资源场景下支持可控音色的语音对话模型——SLAM-Omni。该模型只需要在4张GPU上单阶段训练15小时,即可获得远超此前同等规模模型的对话能力,并且具有优越的语音质量以及生成语音-文本一致性。在更大规模数据集上的实验表明SLAM-Omni在中文对话以及多轮对话上都有不俗的表现。

语音对话系统建模

本文首先探索了主流的语音对话系统建模方案,现有端到端系统主要通过将文本作为中间输出或隐藏状态来利用预训练的大语言模型(LLM)。这些方法可以分为文本驱动建模和音频-文本联合建模两类。

文本驱动建模保留了LLM原始架构,将文本隐状态传递给语音解码器生成音频,能够有效保留LLM的知识,使用其隐藏状态作为语音解码器的输入用于音频生成,但由于只使用文本tokens进行自回归建模,难以捕捉音频的情感和语调等副语言特征。音频-文本联合建模分为交替和并行两种范式,均将音频 tokens加入自回归建模,理论上提升对非语言信息的建模能力。交替范式通过交替使用文本和音频tokens进行生成,需要大量的语音-文本交替数据并重新训练LLM。而并行范式则并行地对文本和音频tokens自回归生成。SLAM-Omni在此基础上,通过预测单层语义tokens并结合语义分组建模的方式来加速音频生成,显著降低了训练成本。

主要贡献:

  • 提出了第一个针对具有说话者解耦语义token的语音交互系统的零样本音色控制解决方案。
  • 提出语义组建模方法来加速单层语义语音标记生成和模型训练。
  • 历史文本提示是为了在SDM【Existing spoken dialogue models】 中进行高效的多轮历史建模而提出的。
  • SLAM-Omni 是第一个实现单阶段训练的语音助手,需要最少的数据和计算资源
  • 实验表明,SLAM-Omni 在文本相关任务上优于类似规模的先前模型,并且在所有现有 SDM 中在声学质量和语音文本对齐方面表现出卓越的性能。更大数据集上的结果证明了其多语言和多轮对话能力。

方法

模型概述

SLAM-Omni通过将系统提示、历史文本提示和用户语音嵌入拼接作为输入,并在Vocoder中通过语者提示来控制音色【 借鉴TTS模型 cosyvoice: 条件流匹配模型 +HifiGAN】;同时,采用语义分组建模加速自回归过程中的语音token生成。

输入语音建模

SLAM-Omni使用Whisper编码器从用户语音指令中提取音频特征(50 Hz)。Whisper作为在大规模跨语言语音数据上训练的语音识别模型,提供了精准的转录和强大的多语言支持,是SLAM-Omni实现多轮多语言对话能力的基础。我们通过降采样处理音频特征,将多个连续帧合并,并通过线性投影将其转换为与LLM嵌入维度对齐的形式。这些处理后的音频特征与文本提示嵌入一起,作为输入传递给LLM。

输出语音建模

在语音输出方面,SLAM-Omni采用并行的音频-文本联合建模,并行地自回归预测文本和音频的语义tokens。为此,我们扩展了LLM的词表,新增了音频tokens的码本,并将原始的词嵌入矩阵与新嵌入合并。在每个生成步骤中,LLM输出的logits包含了文本和音频tokens的预测分布。然而,由于文本tokens(约为3Hz)和音频语义tokens(50Hz)的频率差异,直接以相同速率生成这两种tokens会导致语音对话模型的训练和推理成本大幅增加,同时增加了实时语音生成的延迟。

为了解决这一问题,本文提出了“语义分组建模”方法每步生成多个音频tokens,从而缓解频率不匹配带来的挑战。该方法通过线性层将音频logits投影到分组logits中,并对应的在训练过程中将原语义token序列按组进行划分。通过这种方式,模型能够在自回归过程的每步中同时处理多个音频tokens,从而加速语音生成并极大地降低训练和推理的成本。模型的训练目标可以表示为文本层和音频层交叉熵损失的加权和。

可控音色建模

SLAM-Omni通过将语音内容建模为语义tokens,天然地实现了音色与语言信息的解耦,将zero-shot音色控制从TTS扩展到了语音对话系统上。借鉴TTS模型(Cosyvoice)中的技术,SLAM-Omni使用条件流匹配模型将语义tokens和语者提示信息转换为mel频谱图,并通过HiFi-GAN合成波形。此外,为了支持实时语音生成,SLAM-Omni在流匹配的Transformer架构中采用了块因果注意力机制。

多轮对话

过去的语音对话模型在多轮对话建模上通常将文本和音频tokens交替作为历史,但较长的音频token序列显著提高了训练成本,限制了对话轮次。此外,较长的历史也会影响模型的上下文学习能力,并可能导致早期对话内容的遗忘。为了解决这些问题,SLAM-Omni引入“历史文本提示”(Historical Text Prompting)的方案,仅使用文本模态来表示对话历史。在多轮对话交互中,SLAM-Omni采用模板:<系统提示> <历史文本> <输入> <响应>。其中,系统提示指定模型角色和任务,历史提示则以文本形式存储过去的对话内容。这种方式与LLM的训练模式高度契合,同时避免了长音频序列建模的负担,使得模型能够在受限的上下文窗口内处理更多的对话轮次。在推理过程中,通过Whisper提取的语音特征可以解码成输入语音的转录文本,模型输出的文本tokens则通过分词器转换为文本。每轮对话中,由此得到的问题-响应文本对会被追加到历史对话中,以便下一轮使用。如图所示,第一轮语音对话的转录被纳入历史提示中,第二轮推理时计算得到的KV键值缓存可以在第三轮及以后的对话中复用,从而提高多轮推理的效率。

单阶段训练

此前的端到端语音对话模型通常需要进行模态适配、模态对齐和有监督微调等多阶段训练,这不仅需要精细的训练策略,还涉及多个超参数的调整,带来了显著的时间和计算成本。而SLAM-Omni通过简化为单阶段微调训练,能够在较小的数据集上快速收敛,展现了高效的训练效果。在我们的实验探索中,TTS和ASR的预训练都展示了快速的损失收敛,表明我们的方法无需大规模的模态对齐预训练。同时,进一步的实验还揭示,预训练实际上可能对模型的指令跟随能力和预训练知识保留产生负面影响。

实验设置

由于大多数开源的对话数据集仅为文本格式,我们通过zero-shot TTS系统合成语音对话语料。具体而言,我们使用CosyVoice模型生成用户输入的语音,同时随机从音色库中抽取语者提示,以控制音色。对于语音响应,我们使用CosyVoice模型生成语义tokens,它们在SLAM-Omni训练过程中作为目标音频tokens使用。我们使用的训练数据集包括VoiceAssistant-400K、英语多轮数据集UltraChat和中文对话数据集Belle_train_3.5M_CN。为了确保数据质量,我们清理了数据中的书面体(如表情符号、URL等),并限制了语音问题和响应的时长,以更好地模拟自然对话场景。在SLAM-Omni的主要实验中,仅使用VoiceAssistant-400K数据集,其他数据集则用于补充实验,评估模型在多轮和多语言对话任务中的表现。

对于用户输入,采用CosyVoice-300M模型来产生相应的语音。声音音色是通过从音色库中随机采样扬声器提示来控制的,该音色库包含来自seed-tts-eval3的1007个英语和1010个中文人类音频提示。对于助理响应,我们使用 CosyVoice-300M-SFT 的文本到令牌 LLM 来生成语义令牌这些令牌在 SLAM-Omni 训练期间用作目标音频令牌

在训练和推理过程中,为确保在低资源环境下的公平比较,我们使用Qwen2-0.5B作为LLM骨干,并选择Whisper-small作为语音编解码器。在主要实验中,SLAM-Omni采用的语义分组大小为G = 3。在单阶段训练中,SLAM-Omni进行全量微调,只有Whisper编码器保持冻结。整个训练过程大约需要在4个A100 GPU上进行15小时

为了全面评估语音对话模型的语音交互能力,本文提出了一个新的评测框架,涵盖理解、推理和口语对话三个关键环节。通过设计八个测试集,我们分别从这三方面考察模型的表现。在“理解”部分,评估模型是否能够理解并跟随用户指令;在“推理”部分,通过逻辑、数学和常识问题测试模型的推理能力;而在“口语对话”部分,我们测试模型在开放式对话场景下的交互能力。评估指标包括内容质量(通过ChatGPT评分)、语音质量(通过UTMOS评分)以及语音与文本的一致性(通过WER评分)。

实验结果

实验结果表明,SLAM-Omni在低资源场景下的表现超越了同规模的语音对话模型,在语音内容、音频质量和语音-文本一致性上显著提升,特别是在UTMOS和ASR-WER评分上表现突出,显示出其在音频建模方面的优势。在ChatGPT评测中,尽管和更大规模的模型相比仍存在差距,SLAM-Omni在理解、推理和口语对话能力上显著超越了同规模的Mini-Omni系列,表明其保留了更多的预训练LLM知识和指令跟随能力。

在音频质量和语音-文本一致性上,SLAM-Omni的表现优于所有其他语音对话模型,特别是在ASR-WER指标上,表明其语音-文本对齐更加紧密。而其他模型在生成过程中容易出现生成音频与文本不对齐的情况,尤其在长内容生成时,容易出现音频中断或长时间的静默,导致其UTMOS和ASR-WER评分较低。

消融实验表明,语义分组建模显著提高了生成语音与文本的对齐度,尤其当组大小G≥3时,ASR-WER低于5%,相比之下,没有执行分组算法的模型(G=1)的ASR-WER高达18.23%。这一差距主要来源于音频和文本token之间的频率不匹配。通过减少音频序列长度,语义分组建模有效缓解了这一问题,同时减少了训练和推理成本,并加速了音频生成,提供了更流畅的用户体验。

关于训练策略的消融实验表明,传统的多阶段训练方法虽然能略微提高模型的音频-文本对齐度,但在语音交互任务上的整体表现并未显著改善。相比之下,SLAM-Omni采用单阶段训练策略,显著提高了ChatGPT评分,并保持了相当的音频质量。通过直接在语音到语音数据上进行单阶段微调,SLAM-Omni能够更好地保留预训练LLM的知识,避免了传统预训练任务带来的知识流失问题,提高了训练效率。

附录:

Pre-training Details

对于ASR和TTS预训练,专门使用VoiceAssistant-400K数据集来确保一致性并避免引入外部数据。在 ASR 预训练期间,提供语音指令作为输入,其相应的转录文本作为目标输出。相反,对于 TTS 预训练,语音响应的转录被用作输入文本,而相应的语义token被设置为预测目标。优化和学习策略与微调期间采用的策略一致,值得注意的是,在 ASR 预训练期间仅计算文本层损失,而 TTS 预训练专门关注多层音频损失作为训练目标。

曲线显示,ASR 和 TTS 任务都实现了快速收敛,证明了模型在短时间内有效“理解”和“生成”语音的能力。这一观察表明,理解和生成任务中的模态对齐本质上是简单的,需要最少的预训练工作。此外,如表 6 所强调的,直接对语音到语音任务进行训练可以产生卓越的性能,同时减轻通常与预训练相关的知识退化。

总结 

本文提出了SLAM-Omni,一种单阶段训练下支持可控音色的端到端语音对话模型。通过语义分组建模,SLAM-Omni有效地对齐了音频和文本模态,同时加速了训练和推理过程。采用有监督的语义tokens解耦说话人信息,使得SLAM-Omni实现zero-shot音色控制。为了解决长音频历史带来的问题,我们引入了历史文本提示技术,将对话历史存储为文本,并通过键值缓存提高多轮推理效率。在少量数据训练仅仅60个GPU小时下,SLAM-Omni在文本相关能力上超越了同规模的语音对话模型,并在音质和语音-文本对齐方面表现优越。

MiniCPM-o 2.6: 端侧可用的 GPT-4o 级视觉、语音、多模态实时流式大模型

➤ 项目网站 🔗https://github.com/OpenBMB/MiniCPM-o

➤ 模型权重 🔗https://huggingface.co/openbmb/MiniCPM-o-2_6

🔗https://modelscope.cn/models/OpenBMB/MiniCPM-o-2_6

➤ Demo https://minicpm-omni-webdemo-us.modelbest.cn/

MiniCPM-o 2.6部署教程 详细的部署教程请参考文档

简介

多模态大模型的蓬勃发展始于视觉和语言,其中开源社区在图像理解能力方面实现了越来越强的性能表现。然而,我们的物理世界本质上是一个并行的连续多模态信息流,而当前大多数多模态大模型缺乏处理这样信息流的能力。最近的 GPT-4o 和 Gemini 2.0 等突破性工作迈出了朝这个目标的第一步,为领域的未来发展建立了雄心勃勃且充满希望的方向。

为了促进开源社区的探索,我们推出了 MiniCPM-o 2.6,一个从 MiniCPM-V 系列升级而来的最新性能最佳的端侧多模态大模型。该模型接受图像、视频、文本和音频输入,并以端到端方式生成高质量的文本和语音输出。虽然总参数量仅有 8B,MiniCPM-o 2.6 的视觉、语音和多模态流式能力达到了 GPT-4o-202405 级别,是开源社区中模态支持最丰富、性能最佳的模型之一。其主要特性包括:

  • 领先的视觉能力。 MiniCPM-o 2.6 在 OpenCompass 榜单上(综合 8 个主流多模态评测基准)平均得分 70.2,以 8B 量级的大小在单图理解方面超越了 GPT-4o-202405、Gemini 1.5 Pro 和 Claude 3.5 Sonnet 等主流商用闭源多模态大模型。此外,它的多图和视频理解表现也优于 GPT-4V 和 Claude 3.5 Sonnet,并展现出了优秀的上下文学习能力。
  • 出色的语音能力。 MiniCPM-o 2.6 支持可配置声音的中英双语实时语音对话。MiniCPM-o 2.6 在语音理解任务(如 ASR 和 STT 等)优于 GPT-4o-realtime,并在语音对话的语义和声学评估中展现了开源社区最佳的语音生成性能。它还支持情绪/语速/风格控制、语音克隆、角色扮演等进阶能力。
  • 强大的多模态流式交互能力。 作为一项新功能,MiniCPM-o 2.6 能够接受连续的视频和音频流,并和用户进行实时语音交互。在综合评测基准 StreamingBench 中(包含实时视频理解、全模态视音频理解、多模态上下文理解三大类评测),MiniCPM-o 2.6 取得开源社区最佳水平,并超过了 GPT-4o-202408 和 Claude 3.5 Sonnet。
  • 强大的 OCR 能力及其他功能。 MiniCPM-o 2.6 进一步优化了 MiniCPM-V 2.6 的众多视觉理解能力,可以处理任意长宽比的高清图像,像素数可达 180 万(如 1344×1344像素)。在 OCRBench 上取得 25B 以下最佳水平,超过 GPT-4o-202405 等商用闭源模型。基于最新的 RLHF-VRLAIF-V 和 VisCPM 技术,该模型具备了可信的多模态行为,在 MMHal-Bench 上超过了 GPT-4o 和 Claude 3.5,并支持英语、中文、德语、法语、意大利语、韩语等30多种语言的多模态交互。
  • 卓越的效率。  除了对个人用户友好的模型大小,MiniCPM-o 2.6 还表现出最先进的视觉 token 密度(即每个视觉 token 编码的像素数量)。它仅需 640 个 token 即可编码 180 万像素图像,比大多数模型少 75%。这一特性显著优化了模型的推理速度、首 token 延迟、内存占用和功耗。因此,MiniCPM-o 2.6 可以首次支持 iPad 等终端设备上的高效多模态实时流式交互。
多模态大模型发展趋势。多模态大模型的性能持续增强,并在真实世界的流式场景中支持越来越多的模态能力。与传统视觉语言模型的线性发展不同,这种发展趋势更加多维,就像攀登深处的山峰一样,可能蕴含更多变革性的宝藏。

我们将介绍 MiniCPM-o 2.6 中所面临的主要挑战及其相应的解决方案。

🤔 如何实现多模态流式交互?

人类能够持续感知来自环境的视觉、音频和文本信息流,并以较低延迟生成语音和文本回复,但这对当前的多模态大模型来说是一个重大挑战。

💡 1. 我们将各模态的离线编码/解码器改造为支持在线模式,从而支持流式输入/输出处理。

大多数模态的编码器和解码器都是离线的,大语言模型必须等待完整的视觉/音频输入的编码完成后才能进行处理,用户也必须等待语音完整生成后才能收听。我们在时间维度上将不同模态的输入/输出流分割成小块,并以块为单位进行编码/解码以实现更低的延迟。对于语音生成来说,我们设计了一种新颖的流式注意力策略使解码器能够在接收到第一个文本块时就开始生成音频。

💡 2. 我们提出了一种全模态时分复用机制来处理并行多模态流。

借鉴通信领域的时分复用技术,我们将每个模态的信息流分割成小块(每秒一块),并将同一秒内的所有模态信息组合成一个紧凑的序列单元输入给大语言模型主干。基于这个策略,主干模型可以高效地在线处理多模态信息流。

🤔 如何实现高效的端到端声音、情感、口音和语速可控的语音对话?

大多数传统语音对话模型都是基于 ASR-LLM-TTS 流水线实现的,会丢失大量用户复杂情感和环境声音等细粒度信息。而直接使用大语言模型生成音频 token 的端到端模型在训练和推理过程中都存在计算效率低的问题,且训练时数据效率也较低。

💡 我们基于混合的端到端架构在自然语音上进行了大规模预训练。

我们同时通过连续特征和文本将大语言模型和一个轻量级语音解码器连接起来。大语言模型产生的连续特征确保语音监督信号能够以端到端方式反向传播到全部模型参数,从而支持更高的能力上限。文本连接则提供了强大的语义控制并减少了训练数据需求。为了学习丰富的细粒度语音知识,我们在自然语音数据进行了大规模预训练,然后将模型与用户指令对齐。

端到端全模态架构

我们首先介绍 MiniCPM-o 2.6 的端到端全模态整体架构。该模型基于SigLip-400M、Whisper-medium-300M、ChatTTS-200M 和 Qwen2.5-7B-Instruct,总共有 8B 参数。整体架构如下所示。

端到端语音建模

大多数现有的多模态模型依赖 ASR 和 TTS 工具搭建流水线来理解和生成语音,导致了明显的语音信息损失和比较有限的语音控制能力。我们提出一种混合连接的端到端语音建模架构,在保证模型能力上限的同时具备优秀的训练和推理效率。

音频编码

我们首先使用 Whisper 编码音频输入,然后进一步压缩其特征表示来减小后续的计算开销。默认情况下,Whisper 编码器会为每秒音频生成 50 个 token。受 LLaVA-UHD 的启发,我们在将音频 token 输入到大语言模型主干之前会进一步压缩音频 token,以提高 token 信息密度和计算效率。实验结果表明从 50 token/秒压缩到 25 token/秒时,造成的信息损失几乎可以忽略,并可提高近一倍的计算效率。

语音解码

为了实现对语音输出的精细控制,我们首先从大语言模型主干中获得连续特征作为 speech embedding,然后通过大语言模型继续生成文本。speech embedding 和生成的文本同时会输入给解码器作为生成语音梅尔谱的控制条件。MiniCPM-o 2.6 使用了初始化自 ChatTTS 的轻量级自回归语音解码器来联合建模输入的 speech embedding、文本和输出的音频 token。

语音到语音架构

我们直接通过编码后的音频特征(不使用 ASR)将音频编码器与大语言模型连接起来。大语言模型和语音解码器则以混合方式连接:(1)speech embedding 连续特征控制语音、情感、口音及其他细粒度语音特征。在训练过程中,来自语音解码器的梯度会反向传播到包含大语言模型主干和音频编码器的整个模型参数。模型通过端到端方式训练,没有使用任何中间损失和监督。(2)我们还将来自大语言模型的文本输入到语音解码器,来提供更好的语义控制和训练数据效率。

得益于端到端的架构设计,MiniCPM-o 2.6 成为首个支持端到端语音克隆的通用语音对话模型。我们发现 MiniCPM-o 2.6 在语音克隆任务中可以达到与一些专业 TTS 工具相近的性能。

端到端视觉理解

MiniCPM-o 2.6 采用 LLaVA-UHD 的自适应视觉编码方案以实现高清视觉理解,支持处理最高 180 万像素(例如 1344×1344)的任意长宽比图像。具体而言,我们先将图像划分为多个切片,使得每个切片在分辨率和长宽比方面接近 ViT 的预训练设置。为了提高计算效率,我们支持每张图像最多 9 个切片。然后我们将每个切片及原始完整图像输入给 SigLIP 以获取视觉特征。最后,我们应用 perceiver resampler 将每个图像切片的特征序列压缩为 64 个视觉 token。更多细节参见 LLaVA-UHD 和MiniCPM-V 原始论文。

全模态流式机制

本章我们介绍 MiniCPM-o 2.6 的全模态流式机制,包括:(1)改造语音编码器和解码器以支持在线流式处理,(2)使大语言模型主干能够处理并行的多模态流信息。

流式音频编码

大多数现有模型只能在整个音频输入信号完整后才开始进行音频编码,从而引入了显著的延迟。为了解决这个问题,我们将输入音频分割成多个片段,每个片段是表示一秒钟的音频的固定数量音频 token。在音频编码过程中,每个片段采用因果注意力机制进行编码仅关注自身及之前的片段,从而满足在线流式编码的需求,同时与离线整体编码相比保持了最小的信息损失。

每个小方块表示1s的音频片段

流式语音解码

大多数语音生成模型要求在开始生成音频之前,所有文本 token 都已经完整就位,如图 A 所示。虽然这种方式在离线任务中表现良好,但无法满足流式场景的需求。对于流式模型而言,我们需要在部分文本生成时就开始生成(和播放)相应音频。

为了实现流式语音生成,我们每次输入固定数量的文本 token(一个大小为 n 的片段),而解码器立即输出固定数量的音频 token(一个大小为 m 的片段)。这个过程会对下一个文本 token 和音频 token 片段重复进行,以此类推。值得注意的是,文本片段与其对应音频片段之间的并不是精确对齐的。因此在实际应用中,我们为文本 token 片段的大小保留了更大的空余以避免意外情况。

为了实现上述流式策略并尽可能减小对当前最佳语音解码器的改动,我们主要引入了两个改变:

  1. 为文本预留前 N 个位置: 我们使用语音解码器上下文中的前 N 个位置来放置 speech embedding 和生成中的文本。
  2. 引入流式因果注意力掩码: 每个新生成的音频片段只能关注到已经生成的前几个文本 token 片段和其之前的所有音频 token。

在图 A-E 中,我们展示了每当引入新文本 token 和音频 token 时,注意力掩码是如何一步一步变化的。通过控制文本和音频 token 之间的片段注意力掩码,我们使得音频能以流式方式生成和播放。

  1. (文本和音频生成的开始)图 B 中,大语言模型主干生成 speech embedding 和前 n 个文本 token(此处 n=2)。然后,我们将 <Text BOS>(1 个位置)、speech embedding(1个位置)和文本 token(n个位置)输入到语音解码器中(总共输入了 2+n 个位置),基于此,模型可以生成最多 m 个音频 token(此处 m=4),但仅关注前 2+n 个位置以及所有之前的音频 token 位置。
  2. (步骤 1 的重复)图 C 中,大语言模型生成下一段 n 个文本 token。我们将这些新文本 token 输入到语音解码器中。然后,解码器生成下一段 m 个音频 token。
  3. (文本生成结束)图 D 中,大语言模型完成所有文本 token 的生成,产生了最后的 k 个文本 token(k<=n,此处 k=2)。剩余的 k 个文本 token 被输入到语音解码器中。然后,语音解码器生成下一个m个音频 token。
  4. (音频生成结束)图 E 中,语音解码器继续生成音频 token,直到生成 <Audio EOS> token。

这里的 N、n 和 m 是超参数。在实际应用中,我们使用 N=300、n=10、m=50,以实现性能与推理速度之间的平衡。

流式视觉理解

我们将视频流表示为以 1 帧每秒(1 fps)的帧序列。我们每秒钟从输入的视觉流中采样一帧,并使用自适应高分辨率编码策略对其进行编码,然后将编码后的特征输入给大语言模型主干。

为了在效率和性能之间取得平衡,我们设计了一种简单的混合分辨率策略。具体而言,我们只对用户请求结束时的最后一帧进行高分辨率编码(例如 1344 x 896),而其他帧则使用中等分辨率(例如 448 x 448)。

全模态时分复用

为了实现对不同模态的时间对齐的流式理解,我们提出了一种全模态时分复用机制。如整体框架图所示,我们首先使用共享的时间线对齐来自多个模态的信息流。受到通信领域时分复用技术的启发,我们将每个模态的流分成小块(每秒一块),并将同一秒块内的所有模态信息聚集成一个紧凑的序列单元。大语言模型则按时间顺序处理这些多模态序列单元。

需要注意的是,MiniCPM-o 2.6 可以独立于用户提问持续感知多模态输入流,这与现有的多模态流式模型只在用户提问期间获取单帧或少数几帧图像信息是不同的。通过这种方式,MiniCPM-o 2.6 能够处理需要时序记忆和推理的任务(例如,“球在哪个杯子里?”、“我刚才擦掉了哪些字?”)并原生支持多轮流式对话。

延迟分析

我们提供了纯音频模式下首次响应延迟的具体拆解分析。首次响应延迟指从用户请求结束到模型语音开始播放的延迟。作为参考,GPT-4o 在纯语音模式下的初始响应延迟约为 1.8 秒。MiniCPM-o 2.6 在iPad Pro(M4 芯片)上的初始响应延迟分解如下表所示:

多模态系统提示词

社区在使用文本提示词方面已经积累了丰富的经验,一个好的系统提示可以定义模型的角色、增强其准确性、优化细节表现和令模型聚焦重点。

生成语音回复的端到端全模态模型则面临新的挑战。模型输出的音频特征——如声音、语调、口音和其他细微特征至关重要,但无法仅通过文本传达。为了应对这一挑战,我们提出了多模态系统提示词的概念,允许用户通过声学特征控制模型的声音使其与用户意图相符

具体来说,多模态系统提示词包括传统的文本系统提示词和用于确定模型声音的音频部分。这一设计使得用户在推理时可以灵活配置声音。用户可以通过提供一段语音样例作为音频系统提示,来完成端到端的声音克隆;还可以通过将音频系统提示留空,基于语言描述要求模型创建一种新声音(例如 “请使用低沉有磁性的声音和我说话”)。

模型训练

MiniCPM-o 2.6 采用多阶段训练以逐步学习新模态的知识,从而避免模态冲突并将各种模态能力整合到一个模型中。整体的训练阶段可以分为预训练、指令微调和偏好对齐阶段。

预训练

我们首先分别对视觉和音频进行预训练以学习每种模态,然后进行全模态预训练以对齐这些模态。

视觉预训练。 我们利用大规模图像-文本对来对齐视觉和语言模块。在此阶段,我们仅更新视觉编码模块,让模型具备基本的图像理解和 OCR 能力。然后,我们在图文交替数据上训练视觉编码器和大语言模型,使模型具备多图理解和多模态上下文学习能力。

音频预训练。 我们使用音频-文本对数据来训练连接部分的权重,以实现音频模态和主干的对齐。为了学习丰富的细粒度语音知识,我们在自然语音数据上进行大规模端到端的预训练,然后根据用户指令对模型进行对齐。

全模态预训练。 在这一阶段,我们结合来自大规模网络视频的视频和音频流,使用 OTDM 机制使模型能够从不同模态中获取和对齐丰富的知识。

全模态指令微调

该阶段使用高质量的多模态数据进行监督微调,包括视觉问答、语音理解、语音生成和多模态流式视频(带音频)理解数据。我们对模型进行全参数微调以统一模型的视觉能力、语音理解和生成能力,以及流式多模态能力,同时增强模型的指令遵循能力。

偏好对齐

最后,MiniCPM-o 2.6 采用 RLAIF-V 技术以进一步提高模型的可信度和综合能力。在这个阶段,模型使用分而治之的策略对不同的回复进行评分以构建偏好数据集,并进行直接偏好优化训练(DPO)。同时,我们还特别将相比图像幻觉更常见的视频幻觉比例降低了63%。我们也使用了 MMPR 等开源偏好数据集来多样化训练数据。

评测

我们充分评估了 MiniCPM-o 2.6 的视觉理解、语音对话和多模态流式交互能力。实验结果表明,该模型在视觉、语音和多模态流式交互能力上的整体表现与 GPT-4o-202405 相当。

MiniCPM-o 2.6 的 iPad Pro 实机演示和 web demo 演示样例:

局限性

  1. 可能不稳定的语音输出。 多模态系统提示词使得更灵活的语音控制和许多有趣的功能成为可能,但也给语音输出的稳定性带来了更多挑战。传统的语音对话模型通过全参数记忆单一输出声音,与之相比,MiniCPM-o 2.6 需要从多模态系统提示词中提取和复制声音。受到该问题影响,语音生成结果可能会存在背景噪音和无意义声音等。
  2. 长语音生成。MiniCPM-o 2.6 原生支持最长 45 秒的单次语音生成,我们通过滑动窗口技术来支持生成更长的语音。模型训练与滑动窗口推理之间的差距可能导致长语音生成过程中的不稳定现象。
  3. 多模态流式交互能力。 作为一项实验性能力,模型的多模态流式交互能力在感知、理解和推理能力上仍然有限。我们期待社区的共同努力构建更优秀和可靠的性能效果。
  4. 网页 demo 高延迟。 用户在使用托管在海外服务器上的网页 demo 时可能会遇到异常的高延迟情况。我们建议在本地部署 demo(例如 4090 GPU 可以流畅运行)或使用合适的网络连接。

参考文献

  1. MiniCPM-V: A GPT-4V Level MLLM on Your Phone. 2024.
  2. RLHF-V: Towards Trustworthy MLLMs via Behavior Alignment from Fine-grained Correctional Human Feedback. CVPR 2024.
  3. RLAIF-V: Aligning MLLMs through Open-Source AI Feedback for Super GPT-4V Trustworthiness. 2024.
  4. LLaVA-UHD: an LMM Perceiving Any Aspect Ratio and High-Resolution Images. ECCV 2024.
  5. Large Multilingual Models Pivot Zero-Shot Multimodal Learning across Languages. ICLR 2024.

DeepSeek-V3 技术报告

原文:https://zhuanlan.zhihu.com/p/14890557782

DeepSeek-V3 是一款性能卓越的混合专家(MoE) 语言模型,整体参数规模达到 671B,其中每个 token 激活的参数量为 37B。评估结果表明,DeepSeek-V3 在性能上超越了其他开源模型,并能够与主流闭源模型相媲美。

引言

近年来,LLM 经历了快速迭代和演进,逐步缩小了与通用人工智能(AGI) 的差距。除了闭源模型外,开源模型阵营也在取得重大进展,包括 DeepSeek 系列、LLaMA 系列、Qwen 系列和 Mistral 系列,这些模型正在努力缩小与闭源模型的性能差距。

为了进一步突破开源模型的能力边界,研究团队开发了 DeepSeek-V3,这是一个基于 MoE 架构的大模型,总参数量达到 671B,其中每个 token 会激活 37B 个参数。

基于提升性能和降低成本的双重目标,在架构设计方面,DeepSeek-V3 采用了MLA来确保推理效率,并使用 DeepSeekMoE来实现经济高效的训练。这两种架构在 DeepSeek-V2 中已经得到验证,证实了它们能够在保持模型性能的同时实现高效的训练和推理。

除了延续这些基础架构外,研究团队还引入了两项创新策略来进一步提升模型性能。

首先,DeepSeek-V3 首创了无辅助损失的负载均衡策略,有效降低了负载均衡对模型性能的负面影响。另外,DeepSeek-V3 采用了多 token 预测训练目标,这种方法在评估基准测试中展现出了显著的性能提升。

为了提高训练效率,该研究采用了FP8 混合精度训练技术并对训练框架进行了全面优化。低精度训练作为一种高效的训练方案,其发展与硬件性能的提升密切相关。本研究首次在超大规模模型上成功验证了 FP8 混合精度训练框架的有效性。通过采用 FP8 计算和存储技术,显著提升了训练速度并降低了 GPU 内存占用。

在训练框架方面,研究团队开发的 DualPipe 算法实现了高效的流水线并行处理,减少了流水线停滞,并通过计算和通信并行处理的方式降低了训练过程中的通信开销。这种优化确保了即使在模型规模进一步扩大的情况下,只要维持适当的计算通信比例,就能在不同节点间实现细粒度专家分配,同时将全节点间的通信开销降至接近于零。

此外,研究团队优化了跨节点的全节点通信内核,充分利用了 InfiniBand(IB) 和 NVLink 的带宽性能。通过精细的内存优化,使得 DeepSeek-V3 的训练无需依赖成本高昂的张量并行技术

这些技术改进的综合运用实现了极高的训练效率。

预训练阶段,DeepSeek-V3 使用了 14.8T 高质量且多样化的 token 进行训练。整个预训练过程表现出了出人意料的稳定性,不仅没有出现不可恢复的损失突增,也未发生需要回滚的情况。

随后,模型进行了两个阶段的上下文长度扩展:第一阶段将最大上下文长度提升至 32K,第二阶段进一步扩展至 128K。

接着,研究团队对 DeepSeek-V3 基础模型进行了后训练,包括 SFT 和 RL,以增强模型对人类偏好的理解并进一步提升其性能。在后训练阶段,通过从 DeepSeek R1 系列模型中提取推理能力,同时精确控制模型的输出质量和长度比例。

DeepSeek-V3 在全面的基准测试评估中表现突出。尽管其训练成本较低,但综合评估结果显示,DeepSeek-V3-Base 已成为当前性能最强的开源基础模型,尤其在代码数学领域表现卓越。其对话版本不仅超越了其他开源模型,还在多个标准和开放式基准测试中展现出与领先闭源模型(如 GPT-4o 和 Claude-3.5-Sonnet)相匹敌的性能。

值得注意的是,DeepSeek-V3 实现了极具竞争力的训练成本(详见表1),这得益于在算法、框架和硬件层面的整体优化设计。

表 1:DeepSeek-V3 的训练成本,假设 H800 的租赁价格为$2/GPU小时

在预训练阶段,每处理1T token 仅需 180K H800 GPU 小时,即在配备 2048 个 H800 GPU 的集群上仅需 3.7 天。因此,整个预训练阶段在不到两个月内完成,总计使用了 2664K GPU 小时。

加上上下文长度扩展所需的 119K GPU 小时和后训练阶段的 5K GPU 小时,DeepSeek-V3 的完整训练总共消耗了 2.788M GPU 小时。按照每 GPU 小时 2 美元的 H800 GPU 租用价格计算,总训练成本仅为 557.6 万美元。需要说明的是,这些成本仅包含 DeepSeek-V3 的正式训练环节,不包括前期架构研究、算法验证和数据实验等相关支出。

本研究的主要创新点包括:

架构创新

在 DeepSeek-V2 高效架构的基础上,创新性地提出了无辅助损失的负载均衡策略,有效降低了负载均衡过程对模型性能的影响。

开发并验证了多 token 预测(MTP)训练目标,证实了其对模型性能的提升作用,该技术还可用于推测解码来加速推理过程。

高效预训练

开发了 FP8 混合精度训练框架,首次在超大规模模型上验证了 FP8 训练的可行性和效果。

通过算法、框架和硬件的综合优化,突破了跨节点 MoE 训练中的通信瓶颈,实现了计算与通信的高度重叠。这种优化大幅提升了训练效率,降低了训练成本,同时支持了更大规模模型的训练而无需额外开销。

仅用 2.664M H800 GPU 小时就完成了 DeepSeek-V3 在 14.8T token 上的预训练,打造出当前最强大的开源基础模型。预训练后的其他训练阶段仅需 0.1M GPU 小时。

后训练——DeepSeek-R1 知识蒸馏

该研究提出了一种创新的知识蒸馏方法,将思维链 (CoT) 模型(特别是 DeepSeek R1 系列)的推理能力转移到标准 LLM 中,尤其是 DeepSeek-V3。这一方法成功地将 R1 的验证和反思机制整合到 DeepSeek-V3 中,显著提升了其推理能力,同时有效控制了输出的风格和长度。

核心评估成果

知识领域评估:

  • • 在教育类基准测试中,DeepSeek-V3 的表现超越了所有开源模型,在 MMLU、MMLU-Pro 和 GPQA 测试中分别获得了 88.5、75.9 和 59.1 的优异成绩。这一性能水平已与领先闭源模型 GPT-4o 和 Claude-Sonnet-3.5 相当,显著缩小了开源与闭源模型的性能差距。
  • • 在事实性知识评测中,DeepSeek-V3 在 SimpleQA 和中文 SimpleQA 测试中都展现出领先于其他开源模型的优势。特别值得注意的是,虽然其英语事实知识(SimpleQA)略逊于 GPT-4o 和 Claude-Sonnet-3.5,但在中文事实知识(中文 SimpleQA)方面却超越了这些模型,凸显了其在中文知识领域的特殊优势

技术能力评估:

  • • 在数学领域,DeepSeek-V3 在所有非 CoT 模型(包括开源和闭源)中取得了最优性能。值得注意的是,在 MATH-500 等特定测试中,其表现甚至超越了 GPT-4o,充分展示了其出色的数学推理能力。
  • • 在编程领域,DeepSeek-V3 在 LiveCodeBench 等编程竞赛基准测试中表现最为突出,确立了其在该领域的领先地位。在软件工程相关任务中,尽管略低于 Claude-Sonnet-3.5,但仍大幅领先于其他模型,展示了其在各类技术评测中的综合实力。

架构

DeepSeek-V3 的基本架构具有两个核心特征:

1.采用 MLA 实现高效推理

2.使用 DeepSeekMoE 实现经济高效的训练。

此外,该研究还开发了MTP训练目标,这一创新在评估基准测试中展现出显著的性能提升。

在其他未特别说明的架构细节方面,DeepSeek-V3 延续了 DeepSeek-V2 的设计方案。

基本架构

DeepSeek-V3 的基础架构建立在 Transformer 框架之上。为实现高效推理和降低训练成本,该模型采用了经 DeepSeek-V2 验证的 MLA 和 DeepSeekMoE 技术。相比 DeepSeek-V2,本研究在 DeepSeekMoE 中创新性地引入了无辅助损失负载均衡策略,有效降低了负载均衡过程对模型性能的影响。

图2展示了 DeepSeek-V3 的基本架构,本节将简要介绍 MLA 和 DeepSeekMoE 的技术细节。

图2:DeepSeek-V3 基本架构示意图。基于 DeepSeek-V2,团队采用了多头潜在注意力(MLA)和 DeepSeekMoE 架构,以实现高效推理和经济的训练。

多头潜在注意力机制

DeepSeek-V3 在注意力机制方面采用了 MLA 架构。设向量维度为d,注意力头数为𝑛,每个头的维度为 𝑑,在特定注意力层中第t个 token 的注意力输入表示为 h𝑡 ∈ Rd 。MLA 的核心创新在于对注意力键和值进行低秩联合压缩,以降低推理过程中的键值(KV)缓存开销:

其中:

  • ctKV∈Rdc 表示键和值的压缩潜在向量
  • dc(≪dhnh) 表示 KV 压缩维度
  • WDKV∈Rdc×d 为维度下投影变换矩阵
  • WUK,WUV∈Rdhnh×dc 分别为键和值的维度上投影变换矩阵
  • WKR∈RdhR×d 用于生成携带旋转位置编码(Rotary Positional Embedding, RoPE)的解耦键
  • RoPE(·)表示旋转位置编码矩阵的应用操作
  • [·;·]表示向量连接操作

在 MLA 中,生成过程仅需缓存高亮标记的向量ctKVktR,这种设计显著降低了 KV 缓存空间,同时保持了与标准MHA相当的性能水平。

对于注意力查询(Query)部分,模型同样采用低秩压缩技术,这种设计有效降低了训练过程中的激活值内存占用:

其中:

  • ctQ∈Rd′c 表示查询的压缩潜在向量
  • d′c(≪dhnh) 表示查询压缩维度
  • WDQ∈Rd′c×d 和 WUQ∈Rdhnh×d′c 分别为查询的维度降维和升维变换矩阵
  • WQR∈RdRhnh×d′c 用于生成携带旋转位置编码的解耦查询矩阵

最终,通过组合注意力查询(qt,i )、键 (kj,i)和值( vCj,i ),得到注意力机制的最终输出 Ut

其中𝑊𝑂 ∈ R𝑑×𝑑𝑛为输出维度变换矩阵。

DeepSeekMoE 及其无辅助损失负载均衡机制

DeepSeekMoE的基础架构:在前馈网络(Feed-Forward Networks, FFN) 部分,DeepSeek-V3 采用了 DeepSeekMoE 架构。相比传统的 MoE 架构(如 GShard),DeepSeekMoE 采用了更细粒度的专家分配机制,并创新性地将部分专家设置为共享专家。假设第 t个 token 的 FFN 输入为ut,其输出h′t的计算过程如下:

其中:

  •  Ns和 Nr 分别表示共享专家和路由专家的数量
  • FFNi(s) 和 FFNi(r)(·) 分别代表第 i 个共享专家和路由专家的处理函数
  • Kr 表示被激活的路由专家数量
  • , 代表第 i 个专家的权重系数
  • si,t 表示 token 与专家间的相关度
  • ei 代表第 i 个路由专家的特征向量
  • Topk(·,K) 函数返回第 t 个 token 与所有路由专家计算得到的相关度分数中最高的 K 个值。

无辅助损失负载均衡:对于 MoE 模型,不平衡的专家负载将导致路由崩溃,并在专家并行场景中降低计算效率。传统解决方案通常依赖辅助损失来避免不平衡负载。然而,过大的辅助损失会损害模型性能。为了在负载平衡和模型性能之间实现更好的权衡,研究团队开创了一种无辅助损失负载均衡策略来确保负载平衡。

具体而言,研究团队为每个专家引入了一个偏置项bi,并将其添加到相应的亲和度分数si,t中以确定 top-K 路由:

在这种设计中,偏置项仅用于路由选择,而门控值(用于与 FFN 输出相乘)仍基于原始相关度分数 si,t 计算。训练过程中,系统会实时监控每个训练步骤中所有批次的专家负载分布。在每个步骤结束时,对于负载过高的专家,其偏置项会减少 γ ;对于负载不足的专家,其偏置项会增加 γ ,其中 γ 是控制偏置更新速率的超参数。

通过这种动态调整机制,DeepSeek-V3 在训练过程中实现了专家负载的均衡分布,其性能优于传统仅依靠辅助损失来实现负载均衡的模型。

序列级辅助损失补充机制: 虽然 DeepSeek-V3 主要采用无辅助损失策略来实现负载均衡,但为了防止单个序列中出现显著的负载不均衡现象,模型还引入了补充性的序列级平衡损失:

其中平衡因子 α 是一个超参数,在 DeepSeek-V3 中被设置为极小值; 1(·) 表示指示函数; T 代表序列中的 token 总数。这种序列级平衡损失机制有助于保持单个序列内专家负载的均衡性。

节点约束路由机制: 类似于 DeepSeek-V2 的设备限制路由策略,DeepSeek-V3 采用了受控路由机制来优化训练过程中的通信开销。具体而言,系统限制每个 token 最多只能分配给 M 个计算节点,这些节点的选择基于每个节点上专家的最高 Kr/M 相关度分数总和。

在这种约束下,MoE 训练框架能够实现计算与通信的近乎完全并行处理

完整的 Token 保留机制: 得益于高效的负载均衡策略,DeepSeek-V3 在整个训练过程中都保持着良好的负载平衡状态。因此,训练过程中不存在 token 丢弃现象。同时,通过特定的推理部署策略,DeepSeek-V3 在推理阶段同样实现了完整的 token 保留。

多 token 预测机制 (Multi-Token Prediction, MTP)

DeepSeek-V3 创新性地采用了 MTP 目标,将预测范围扩展到每个位置的多个后续 token

这种设计具有双重优势:

首先,MTP 目标通过增加训练信号的密度可能提高数据利用效率;其次,它使模型能够提前规划表征,从而更准确地预测后续 token。

如图3所示,该实现方案与先前研究的方法有所不同:前者使用独立输出头并行预测 D 个额外 token,而 DeepSeek-V3 采用顺序预测方式,并在每个预测层级保持完整的因果关系链。

图3:MTP实现示意图。V3在每个深度上保持每个 token 预测过程中的完整因果依赖链。

MTP 模块架构: 具体实现中,模型采用 D 个串联模块来预测 D 个额外的 token。每个 MTP 模块(第k个)包含以下组件:

  • 共享向量层 Emb(·)
  • 共享输出头 OutHead(·)
  • Transformer 处理单元 TRM(·)
  • 维度映射矩阵 Mk∈Rd×2d

对于输入序列中的第 i 个 token ti ,在第 k 层预测时,模型首先将两个向量进行组合:该 token 在第 (k−1) 层的特征表示 hik−1∈Rd 和第 (i+k) 个 token 的向量 Emb(ti+k)∈Rd ,通过线性变换进行融合:

其中[·;·]表示向量拼接操作。需要特别说明的是,在 k=1 时,hik−1 代表主模型输出的特征表示。值得注意的是,每个 MTP 模块都与主模型共享同一个向量层。经过组合的特征向量 hi′k 随后输入到第 k 层的 Transformer 处理单元,生成该层的输出特征表示 hik :

其中 T 代表输入序列的长度, i:j 表示包含两端的切片操作。接着,系统将hik输入到共享输出层,计算第 k 个预测 token 的概率分布 Pi+1+kk∈RV (V 为词表大小):

输出层 OutHead(·) 首先通过线性变换将特征表示转换为 logits,然后使用 Softmax(·) 函数计算第 k 个预测 token 的概率分布。与向量层类似,每个 MTP 模块的输出层也与主模型共享。这种保持预测因果链的设计思路与 EAGLE 相近,但两者目标不同:EAGLE 主要用于推测解码,而本研究中的 MTP 主要用于优化训练效果。

MTP 训练目标优化: 系统为每个预测层级计算交叉熵损失 LMTPk :

其中,T 表示输入序列长度,ti 代表第 i 个位置的目标 token,ik[ti] 表示第 k 个 MTP 模块对 ti 的预测概率。最终,通过计算所有层级 MTP 损失的平均值并乘以权重系数 λ ,得到总体 MTP 损失 LMTP ,作为 DeepSeek-V3 的补充训练目标:

推理阶段的MTP:应用 MTP 机制的主要目的是提升基础模型的性能,因此在实际推理阶段可以不使用 MTP 模块,基础模型能够独立完成正常推理。此外,这些 MTP 模块也可以被重新配置用于推测解码,从而降低模型生成的时间延迟。

基础设施

计算集群架构

DeepSeek-V3 的训练环境是一个配备 2048 个 NVIDIA H800 GPU 的大规模计算集群。

该集群中的每个计算节点包含 8 个 GPU,这些 GPU 通过节点内的 NVLink 和 NVSwitch 实现高速互连。节点之间则采用 InfiniBand (IB) 技术进行高效通信。

训练框架设计

模型训练基于自主研发的 HAI-LLM 框架,这是一个经过优化的高效轻量级训练系统。DeepSeek-V3 的并行策略包含三个层面:16 路流水线并行(Pipeline Parallelism, PP)、跨 8 个节点的 64 路专家并行(Expert Parallelism, EP),以及 ZeRO-1 数据并行(Data Parallelism, DP)

为实现高效训练,该框架进行了多方面的工程优化:

  1. 开发了 DualPipe 流水线并行算法,相比现有 PP 方法,该算法显著减少了流水线停滞现象。更重要的是,它实现了前向和后向过程中计算与通信阶段的重叠,有效解决了跨节点专家并行带来的通信负载问题。
  2. 优化了跨节点全对全通信内核,充分利用 IB 和 NVLink 带宽,同时减少了通信所需的流式多处理器(SMs)资源占用。
  3. 通过精细的内存管理优化,使得模型训练无需依赖开销较大的张量并行(Tensor Parallelism, TP)技术。

DualPipe 技术与计算通信协同优化

在 DeepSeek-V3 中,跨节点专家并行机制引入了显著的通信开销,导致计算与通信比例接近1:1,影响了训练效率。

为解决这一问题,模型采用了创新性的 DualPipe 流水线并行算法。该算法通过两个关键优化实现性能提升:有效融合前向和后向计算的通信阶段,同时减少流水线阻塞。

DualPipe 的核心创新在于实现了单个前向和后向计算块内的计算通信重叠。具体来说,每个计算块被划分为四个功能模块:

  • 注意力机制
  • 全节点数据分发
  • MLP 处理
  • 全节点数据整合

特别地,在后向计算块中,注意力和 MLP 模块都被进一步细分为输入梯度计算权重梯度计算两个部分,这一设计借鉴了 ZeroBubble 的思路。此外,还包含专门的 PP 通信模块。

图4:个体前向和后向块的重叠策略(Transformer 块的边界未对齐)。橙色表示前向计算,绿色表示“输入的后向计算”,蓝色表示“权重的后向计算”,紫色表示 PP 通信,红色表示屏障。全对全(all-to-all)通信和 PP 通信可以完全隐藏。

如图4所示,通过优化排列这些功能模块,并精确调控用于通信和计算的 GPU 流处理器资源分配比例,系统能够在运行过程中有效隐藏全节点通信和 PP 通信开销。

完整的 DualPipe 调度机制如图5所示。它采用创新的双向流水线调度策略,实现了从流水线两端同时输入微批次数据,使得大部分通信过程能够与计算过程完全重叠。这种设计确保了即使在模型规模进一步扩大的情况下,只要维持适当的计算通信比例,就能在节点间实现细粒度的专家分配,同时将全节点通信开销降至接近于零。

图5:8 个 PP 排位和 20 个微批次在两个方向上的双管道调度示例。反向方向的微批次与前向方向的微批次对称,因此为简化说明,省略了反向微批次的批次 ID。两个由共享黑色边框围住的单元存在相互重叠的计算和通信。

值得注意的是,即使在通信负载相对较轻的常规应用场景中,DualPipe 仍然展现出显著的效率优势。表2对比了不同 PP 方法在流水线阻塞和内存使用方面的表现。

数据显示,相比 ZB1P 和 1F1B,DualPipe 大幅减少了流水线阻塞,而峰值活性内存仅增加了 1/PP 。虽然 DualPipe 需要维护两份模型参数副本,但由于训练过程采用了大规模 EP,这一冗余并未导致显著的内存开销增加。

与 Chimera 相比,DualPipe 的实现要求更为灵活,仅需要流水线阶段数和微批次数能被 2 整除,而不要求微批次数必须能被流水线阶段数整除。

此外,DualPipe 的一个重要特性是,随着微批次数量的增加,其流水线阻塞和激活内存占用都不会相应增加

跨节点all-to-all通信的高效实现

为了确保 DualPipe 具有充足的计算性能,系统采用了定制化的高效跨节点全对全通信内核(包括分发和组合功能),以节省用于通信的 SMs 数量。

这些内核的实现与 MoE 门控算法和集群的网络拓扑结构进行了协同设计。具体而言,在该集群中,跨节点 GPU 通过 IB 实现全连接,节点内通信则通过 NVLink 处理。NVLink 提供 160GB/s 的带宽,约为 IB(50GB/s)的 3.2 倍。为了有效利用 IB 和 NVLink 的不同带宽特性,系统限制每个 token 最多分发到 4 个节点,从而减少 IB 流量。对于每个 token,当确定其路由决策后,首先通过 IB 传输到目标节点上具有相同节点内索引的 GPU。一旦到达目标节点,系统确保其通过 NVLink 即时转发到承载目标专家的特定 GPU,避免被后续到达的 token 阻塞。

通过这种方式,IB 和 NVLink 的通信实现完全重叠,每个 token 能够在不产生 NVLink 额外开销的情况下,在每个节点上平均高效选择 3.2 个专家。这意味着,虽然 DeepSeek-V3 实际只选择 8 个路由专家,但它可以将这个数字扩展到最多 13 个专家(4 个节点 × 3.2 个专家/节点),同时保持相同的通信成本

总体而言,在这种通信策略下,仅需 20 个 SMs 就足以充分利用 IB 和 NVLink 的带宽。具体而言,系统采用了线程专门化技术,将 20 个 SMs 划分为 10 个通信信道。

在分发过程中,(1)IB 发送、(2) IB 到 NVLink 转发和(3) NVLink 接收由各自的线程组处理。分配给每个通信任务的线程组数量根据所有 SMs 的实际工作负载动态调整。

类似地,在组合过程中,(1) NVLink 发送、(2) NVLink 到 IB 转发和累积,以及(3) IB 接收和累积也由动态调整的线程组处理。此外,分发和组合内核与计算流重叠,因此还考虑了它们对其他 SM 计算内核的影响。具体而言,系统采用了定制的 PTX 指令自动调整通信块大小,这显著降低了 L2 缓存的使用和对其他 SMs 的干扰。

极致的内存节省与最小开销

为了减少训练期间的内存占用,系统采用了以下技术:

RMSNorm 和 MLA 上投影的重计算。在反向传播期间重新计算所有 RMSNorm 操作和 MLA 上投影,从而避免了持久存储其输出激活的需求。这种策略虽带来少量开销,但显著减少了存储激活所需的内存。

CPU 中的指数移动平均(EMA)。在训练期间,系统在 CPU 内存中保留模型参数的EMA,用于学习率衰减后对模型性能的早期估计。EMA 参数存储在 CPU 内存中,并在每个训练步骤后异步更新。这种方法使维护 EMA 参数不会产生额外的内存或时间开销。

MTP的共享向量和输出头。采用 DualPipe 策略,将模型的最浅层(包括向量层)和最深层(包括输出头)部署在相同的PP等级上。这种安排使 MTP 模块和主模型之间能够物理共享参数和梯度,实现共享向量和输出头。这种物理共享机制进一步提高了内存使用效率。

FP8 训练

基于低精度训练领域的最新进展,本研究开发了一种细粒度混合精度框架,采用 FP8 数据格式训练 DeepSeek-V3。

尽管低精度训练技术展现出巨大潜力,但其实际应用常受到激活值、权重和梯度中异常值的制约。虽然推理量化技术取得重要突破,但在大规模语言模型预训练中成功应用低精度技术的案例仍然有限。

为了应对这一挑战并有效扩展 FP8 格式的动态范围,本研究采用了细粒度量化策略:

采用 1×Nc 元素的条状分组或Nc×Nc元素的块状分组。

通过提高精度累积过程,大幅降低了反量化带来的计算开销,这对实现高精度 FP8 通用矩阵乘法(GEMM)至关重要。此外,为降低 MoE 训练中的内存和通信开销,系统采用 FP8 格式进行激活值的缓存和分发,同时使用 BF16 格式存储低精度优化器状态。

该框架在与 DeepSeek-V2-Lite 和 DeepSeek-V2 规模相近的两个模型上进行了验证,训练数据量约为 1T token(详见原文附录B.1)。结果表明,与 BF16 基准相比,FP8 训练模型的相对损失误差始终保持在 0.25% 以下,这完全在训练随机性的可接受范围内。

混合精度框架

本研究在已有低精度训练技术的基础上,设计了专门的 FP8 训练混合精度框架。在这一框架中,大部分计算密集型操作采用 FP8 执行,而关键操作则保持原有数据格式,以实现训练效率和数值稳定性的最优平衡。

整体框架结构如图6所示。

图6:带有 FP8 数据格式的整体混合精度框架。为清晰起见,仅展示了线性算子。

首先,为提高模型训练速度,大部分核心计算操作(尤其是 GEMM 运算),均采用 FP8 精度实现。这些 GEMM 运算接收 FP8 格式的张量输入,输出 BF16 或 FP32 格式的结果。如图6所示,线性运算相关的三个 GEMM 操作,包括 Fprop(前向传播)Dgrad(激活值反向传播)和 Wgrad(权重反向传播),均采用 FP8 执行。这种设计策略理论上将计算速度提升至原有 BF16 方法的两倍。同时,FP8 格式的 Wgrad GEMM 使得激活值能够以 FP8 格式存储用于反向传播,显著降低了内存使用量

虽然 FP8 格式在效率方面具有优势,但某些运算由于对计算精度较为敏感,仍需要更高精度的支持。另外,部分计算开销较小的运算可以采用更高精度而不会显著影响整体训练效率。

因此,经过详细评估,系统对以下模块保持原有精度(BF16 或 FP32):向量层输出层MoE 门控模块标准化运算注意力运算模块。这种针对性的高精度保留策略确保了 DeepSeek-V3 训练过程的动态稳定性。为进一步保障数值计算的稳定性,主要权重参数、权重梯度和优化器状态均采用更高精度存储。虽然这些高精度组件会带来一定的内存开销,但通过在分布式训练系统中跨多个 DP 层级进行高效数据分割,这些额外开销得到了有效控制。

量化和乘法精度优化

基于混合精度 FP8 框架,研究团队开发了多种策略来提升低精度训练的准确性,主要从量化方法和乘法计算两个方面进行优化。

细粒度量化技术: 在低精度训练框架中,由于 FP8 格式的指数位较少导致其动态范围受限,经常出现数值溢出和下溢的问题。传统方法是将输入张量的最大绝对值映射到 FP8 格式的最大可表示值,将输入分布对齐到可表示范围内。然而,这种方法使得低精度训练对激活值中的极端值特别敏感,可能导致量化精度显著下降。

为解决这一问题,该研究提出了一种更细粒度的量化方法。如图7(a)所示,该方法采用两种不同的分组策略:

  1. 激活值采用 1×128 条状分组和缩放(每个 token 的每 128 个通道)
  2. 权重采用 128×128 块状分组和缩放(每 128 个输入通道对应 128 个输出通道)
图7(a): 研究提出了一种细粒度量化方法,用于减轻由特征异常值引起的量化误差;为简化说明,仅展示了前向传播(Fprop)。(b): 配合量化策略,团队通过以间隔 NC = 128 元素的 MMA 提升到 CUDA 核心,从而提高 FP8 GEMM 的精度,以进行高精度累加。

这种方法通过在更小范围内调整缩放因子,显著提高了量化过程对极端值的适应能力。原文附录B.2 中详细分析了在块状基础上对激活值进行分组和缩放时可能出现的训练不稳定现象。

该方法的一项重要创新是在 GEMM 操作的内部维度引入组级缩放因子。虽然标准 FP8 GEMM 不直接支持这一功能,但通过与精确 FP32 累积策略的结合,实现了高效的执行。值得注意的是,这种细粒度量化策略与微缩放格式的理念高度契合,而 NVIDIA 新一代 GPU(Blackwell 系列)的 Tensor Cores 已宣布将支持更细粒度的微缩放格式。这一设计为适配最新 GPU 架构的未来研究提供了重要参考。

累积精度优化: 低精度 GEMM 运算常见的下溢问题主要通过高精度累积来解决,通常采用 FP32 精度。然而,在 NVIDIA H800 GPU 上,FP8 GEMM 的累积精度仅能保持约 14 位有效数字,远低于 FP32 的累积精度。这个问题在内部维度 K 较大时尤为显著,这正是大规模模型训练中的常见情况,因为批量规模和模型宽度都有所增加。例如,在 K=4096 的两个随机矩阵 GEMM 运算测试中,Tensor Cores 的有限累积精度导致最大相对误差接近 2%。尽管存在这些问题,部分 FP8 框架仍将有限累积精度作为默认选项,这严重制约了训练精度的提升。

为解决这一问题,系统采用了 CUDA Cores 提升策略来获得更高的计算精度。如图7(b)所示,在 Tensor Cores 执行 MMA(矩阵乘法累加)时,中间结果先使用有限位宽累加。当达到 NC 间隔时,这些部分结果会转移到 CUDA Cores 的 FP32 寄存器中进行全精度累加。结合细粒度量化在内部维度 K 上的组级缩放因子,系统能够在 CUDA Cores 上高效完成反量化过程,仅带来极少的额外计算开销。

这种设计虽然降低了单个线程组的 WGMMA 指令发出率,但在 H800 架构上通过并发执行两个 WGMMA 得到了优化:一个线程组执行提升操作的同时,另一个可以执行 MMA 操作。这种重叠执行机制保证了 Tensor Cores 的高效利用。实验证明,将 NC 设为 128 个元素(即 4 个 WGMMA)是在不引入显著开销的前提下,能够有效提升精度的最小累积间隔。

在数值表示方面,不同于先前工作采用的混合 FP8 格式(Fprop 使用 E4M3,Dgrad 和 Wgrad 使用 E5M2),本研究在所有张量计算中统一采用 E4M3 格式以提高精度。这种设计的可行性源于细粒度量化策略(平铺和块状缩放),通过在较小元素组内共享指数位来有效缓解有限动态范围的影响。

为确保量化精度并简化框架设计,系统采用在线量化方法,而不是像其他张量级量化框架那样使用基于历史记录的延迟量化。系统对每个1×128激活平铺或 128×128 权重块实时计算最大绝对值,据此确定缩放因子并完成 FP8 格式的在线量化。

低精度存储与通信优化

在 FP8 训练框架的基础上,通过将缓存的激活值和优化器状态转换为更低精度格式,系统进一步优化了内存占用和通信开销。

优化器状态的精度优化: 系统在 AdamW 优化器中使用 BF16 代替 FP32 格式来记录一阶和二阶动量,这种改变并未带来明显的性能损失。同时,为确保训练过程的数值稳定性,主要权重参数(优化器存储)和梯度值(用于批量累积)仍保持 FP32 格式。

激活值精度优化: 如图6所示,Wgrad 运算采用 FP8 执行。为降低内存占用,系统在线性运算的反向传播中使用 FP8 格式缓存激活值。但在实现低成本高精度训练时,以下运算需要特殊处理:

  1. 注意力层后的线性层输入:这些激活值同时用于注意力运算的反向传播,因此对精度特别敏感。系统为这些激活值专门设计了 E5M6 数据格式。在反向传播时,这些激活值的量化模式从 1×128 转换为 128×1 。为避免引入额外的量化误差,所有缩放因子都采用 2 的整数次幂。
  2. MoE 中 SwiGLU 运算的输入:为优化内存使用,系统仅缓存 SwiGLU 运算的输入,在反向传播时重新计算输出。这些激活值采用 FP8 格式存储,并通过细粒度量化方法实现内存效率和计算精度的最优平衡。

低精度通信优化: 通信带宽限制是 MoE 模型训练中的主要性能瓶颈。为解决这一问题,系统在执行 MoE 上投影前将激活值转换为 FP8 格式,再进行数据分发,这种方式与 MoE 上投影中的 FP8 前向传播保持兼容。与注意力层后的线性层输入处理方式相同,这里的激活值缩放因子也采用 2 的整数次幂。同样的处理方式也应用于 MoE 下投影前的激活值梯度计算。考虑到训练精度的重要性,前向和反向传播中的组合运算环节都保持 BF16 格式,以确保训练管道关键环节的计算精度。

推理和部署

DeepSeek-V3 部署在 H800 集群上,集群中每个节点内的 GPU 通过 NVLink 互连,集群内所有 GPU 通过 IB 实现全连接。为同时确保在线服务质量(SLO)和高吞吐量,该系统采用了将预填充和解码阶段分离的部署策略。

预填充

预填充阶段的最小部署单元配置为 4 个节点 32 个 GPU。

注意力机制部分采用 4 路张量并行(TP4)配合序列并行(SP),结合 8 路数据并行(DP8)。较小的 TP 规模有效控制了通信开销。

MoE 部分采用 32 路专家并行(EP32),确保每个专家能处理足够规模的批量数据,提升计算效率。MoE 的全节点通信采用与训练阶段相同的方式:先通过 IB 在节点间传输 token,再通过 NVLink 在节点内 GPU 间传递。特别地,浅层的密集 MLP 采用单路张量并行以降低 TP 通信开销。

为实现 MoE 部分各专家间的负载平衡,系统需要确保每个 GPU 处理相近数量的 token。为此,采用了冗余专家部署策略,对高负载专家进行复制和冗余部署。

系统基于在线部署时收集的统计数据识别高负载专家,并定期调整(如每 10 分钟)。确定冗余专家后,基于负载观测数据在节点内 GPU 间重新分配专家,在不增加跨节点通信开销的前提下,尽可能实现 GPU 间的负载均衡。

DeepSeek-V3 在预填充阶段配置了 32 个冗余专家,每个 GPU 除原有的 8 个专家外,还分配一个额外的冗余专家。此外,为提升吞吐量并降低全对全和 TP 通信开销,系统同时处理两个计算负载相近的微批次,将一个批次的注意力和 MoE 计算与另一个批次的数据分发和聚合重叠。

目前正在探索专家动态冗余机制,使每个 GPU 分配更多专家(如 16 个),但每次推理仅激活其中 9 个。在每层全对全操作开始前,系统实时计算全局最优路由方案。由于预填充阶段本身计算量较大,计算路由方案的额外开销几乎可以忽略。

解码

在解码阶段,系统将共享专家作为一种路由专家处理。这意味着每个 token 在路由时会选择 9 个专家,其中共享专家被视为一个必然选择的高负载专家。

解码阶段的最小部署单元由 40 个节点 320 个 GPU 构成。注意力部分采用 TP4 配合 SP,结合 DP80,而 MoE 部分使用 EP320。MoE 部分,每个 GPU 仅分配一个专家,其中 64 个 GPU 专门负责冗余专家和共享专家。分发和聚合环节的全节点通信通过 IB 直接点对点传输实现低延迟。同时,系统引入 IBGDA 技术进一步降低延迟并提升通信效率。

与预填充阶段类似,系统基于在线服务的专家负载统计数据,定期确定冗余专家配置。由于每个 GPU 仅分配一个专家,无需进行专家重新分配。系统也在研究解码阶段的动态冗余策略,但这需要对全局最优路由方案的计算算法进行更细致的优化,并与分发内核进行融合以减少开销。

此外,为提升吞吐量并降低全节点通信开销,系统正在探索在解码阶段同时处理两个计算负载相近的微批次。与预填充不同的是,在解码阶段注意力机制占用更多时间,因此系统将一个批次的注意力计算与另一个批次的分发、MoE 处理和数据聚合进行重叠。

在解码阶段,每个专家处理的批量规模相对较小(通常不超过 256 个 token),系统瓶颈在于内存访问而非计算能力。由于 MoE 部分只需加载单个专家的参数,内存访问开销较小,因此即使分配较少的 SMs 也不会显著影响整体性能。基于这一特点,系统只需分配少量 SMs 用于分发、MoE 处理和数据聚合,避免影响注意力部分的计算速度。

硬件设计建议

基于全对全通信和 FP8 训练方案的实践经验,研究团队对 AI 硬件厂商提出以下芯片设计建议。

通信硬件 DeepSeek-V3 通过实现计算与通信的并行处理,在计算过程中有效隐藏了通信延迟。这种设计相比串行计算和通信方式,显著降低了对通信带宽的要求。然而,目前的通信实现需要占用大量宝贵的 SMs 资源(如在 H800 GPU 的 132 个 SMs 中占用 20 个),这限制了计算吞吐能力。

另外,将 SMs 用于通信导致张量核心资源的严重浪费。目前,SMs 在全对全通信中主要承担以下任务:

  • 在 IB 和 NVLink 网络间转发数据,同时汇聚来自单个 GPU 发往同一节点内多个 GPU 的 IB 数据流。
  • 在 RDMA 缓冲区(注册的 GPU 内存区域)与输入/输出缓冲区间传输数据。
  • 执行全对全组合的归约运算。
  • 在跨 IB 和 NVLink 网络向多个专家传输分块数据时管理细粒度内存布局。

期望未来硬件厂商能开发专门的硬件,将这些通信任务从计算核心 SM 中分离出来,设计成类似 NVIDIA SHARP 的 GPU 协处理器或网络协处理器。同时,为降低应用开发难度,希望这种硬件能从计算单元的角度统一管理 IB(横向扩展)和 NVLink(纵向扩展)网络。通过这种统一接口,计算单元只需提交简单的通信请求,就能在整个 IB-NVLink 统一网络中轻松实现读取、写入、多播和归约等操作。

计算硬件

张量核心中的 FP8 GEMM 累积精度提升: 当前 NVIDIA Hopper 架构的张量核心在实现 FP8 GEMM 时采用定点累积方式,通过基于最大指数的右移操作对尾数积进行对齐后再相加。实验显示,该设计在符号填充右移后仅使用每个尾数积的最高 14 位,并舍弃超出范围的位。然而,例如要从 32 个 FP8 × FP8 乘法的累积中获得精确的 FP32 结果,至少需要 34 位精度。因此,建议未来芯片设计提高张量核心的累积精度以支持全精度累积,或根据具体训练和推理算法的精度需求选择合适的累积位宽,以在保证计算效率的同时将误差控制在可接受范围内。

支持平铺和块状量化:现有 GPU 仅支持整体张量量化,缺乏对平铺和块状等细粒度量化的硬件支持。当前实现中,达到NC间隔时需要将部分结果从张量核心复制到 CUDA 核心,进行缩放因子乘法运算,再添加到 CUDA 核心的 FP32 寄存器中。虽然结合精确 FP32 累积策略显著降低了反量化开销,但张量核心和 CUDA 核心间频繁的数据移动仍然制约了计算效率。因此,建议未来芯片支持细粒度量化,使张量核心能够直接接收缩放因子并实现组级缩放的 MMA 操作。这样可以直接在张量核心内完成全部的部分和累积与反量化计算,直到生成最终结果,避免频繁的数据迁移。

支持在线量化:尽管研究证实了在线量化的有效性,但当前硬件难以有效支持这一技术。现有流程中需要从 HBM 读取 128 个 BF16 激活值(上一步的计算结果)进行量化,将量化后的 FP8 值写回 HBM,然后再次读取用于 MMA 操作。为解决这一低效问题,建议未来芯片将 FP8 格式转换与 TMA 访问集成为单一融合操作,实现在激活值从全局内存传输到共享内存过程中完成量化,避免频繁的内存读写。同时建议支持线程束级格式转换指令以提升性能,促进层标准化与 FP8 转换的更好融合。另一种方案是采用近内存计算方法,将计算逻辑放置在 HBM 附近,使 BF16 元素在从 HBM 读入 GPU 时直接转换为 FP8,从而将片外内存访问减少约 50%。

支持转置GEMM操作: 现有架构难以实现矩阵转置与 GEMM 操作的有效融合。目前的工作流中,前向传播阶段的激活值需要先量化为 1×128 FP8 平铺格式并存储。在反向传播时,系统必须读取矩阵,执行反量化,进行转置操作,再重新量化为 128×1 平铺格式,最后存入 HBM。为优化内存操作效率,建议未来芯片设计中,对训练和推理中常用的精度格式,支持在 MMA 操作前直接从共享内存进行转置读取。这一改进配合 FP8 格式转换和 TMA 访问的融合机制,将大幅优化量化处理流程。

预训练

数据构建

相比 DeepSeek-V2,本次预训练语料库在提升数学编程样本占比的同时,扩大了英语和中文之外的多语言覆盖范围。

数据处理流程也经过改进,在保持语料多样性的同时降低了数据冗余。系统采用文档打包方法维持数据完整性,但训练过程中不使用跨样本注意力掩码。最终训练语料库包含 14.8T 经 tokenizer 处理的高质量多样化 token。

在 DeepSeekCoder-V2 的训练中发现,填充中间(FIM)策略在保持下一个 token 预测能力的同时,还能让模型基于上下文准确预测中间文本。因此 DeepSeek-V3 的预训练也采用了这一策略。具体实现上,使用前缀-后缀-中间(PSM)框架构建如下数据结构:

<|fim_begin|> pre<|fim_hole|> suf<|fim_end|> middle<|eos_token|>。

该结构在预打包阶段应用于文档级别,FIM 策略的应用比率为 0.1,与 PSM 框架保持一致。

DeepSeek-V3 采用词表大小为 128K 的字节级 BPE tokenizer 。为提高多语言压缩效率,对预分词器和训练数据进行了相应调整。与 DeepSeek-V2 相比,新的预分词器引入了标点符号和换行符的组合 token。然而这种设计在处理无终端换行符的多行提示词时可能产生 token 边界偏差,尤其是在少样本评估场景。为此,训练时对一定比例的组合 token 进行随机分割,使模型接触更多特殊情况来减轻这种偏差。

超参数设置

模型架构参数

系统采用 61 层 Transformer 结构,隐藏维度为 7168。所有可学习参数采用标准差 0.006 的随机初始化。

在 MLA 结构中,注意力头数量 nh 设为 128,每个头的维度 dh 为 128。KV 压缩维度 dc 为 512,查询压缩维度 d′c 为 1536。解耦的查询和键部分,每个头的维度 dhR 设为 64。

除前三层外,所有 FFN 层都替换为 MoE 层,每个 MoE 层配置 1 个共享专家和 256 个路由专家,专家的中间隐藏维度为 2048。

在路由专家中,每个 token 激活 8 个专家,且最多分配到 4 个节点。多 token 预测深度 D 设为 1,即每个 token 除预测下一个精确 token 外,还需预测一个额外 token。

与 DeepSeek-V2 类似,DeepSeek-V3 在压缩潜在向量后添加了 RMSNorm 层,并在宽度瓶颈处引入额外缩放因子。在此配置下,模型总参数量达到 671B,其中每个 token 激活 37B 参数

训练参数

模型采用 AdamW 优化器,参数设置为: β1=0.9 , β2=0.95 ,权重衰减为 0.1。预训练阶段最大序列长度为 4K,在 14.8T token 上进行训练。

学习率调度采用以下策略:首先在前 2K 步内从 0 线性增加至 2.2×10−4 ;保持该学习率直至处理完 10T 训练 token;随后在 4.3T token 区间内按余弦衰减曲线降至 2.2×10−5 。在最后 500B token 的训练中,先用 2.2×10−5 的固定学习率训练 333B token,再以 7.3×10−6 的学习率完成剩余 167B token。

梯度裁剪范数设为 1.0。批量大小采用动态调整策略,在前 469B token 训练过程中从 3072 逐步增加至 15360,此后保持不变。模型采用流水线并行将不同层分配到不同 GPU,每层的路由专家均匀分布在 8 个节点的 64 个 GPU 上。节点限制路由中,每个 token 最多分配至 4 个节点( M=4 )。

在无辅助损失负载均衡方面,前 14.3T token 的偏置更新速度 γ 设为 0.001,剩余 500B token 设为 0。平衡损失参数 α 设为 0.0001,仅用于防止单个序列内出现极端不平衡。MTP 损失权重 λ 在前 10T token 中为 0.3,剩余 4.8T token 中降至 0.1。

长上下文扩展

DeepSeek-V3 采用与 DeepSeek-V2 相似的方法实现长上下文处理能力。预训练完成后,系统使用 YaRN 进行上下文扩展,通过两个各包含 1000 步的额外训练阶段,将上下文窗口从 4K 依次扩展至 32K 和 128K。系统沿用了 DeepSeek-V2 的 YaRN 配置,仅将其应用于解耦的共享键 ktR 。两个阶段采用相同的超参数设置:尺度 s=40 , α=1 , β=32 ,缩放因子 0.1lns+1 。

第一阶段将序列长度设为 32K,批量大小为 1920。第二阶段将序列长度提升至 128K,相应地将批量大小调整为 480。两个阶段均采用与预训练末期相同的学习率 7.3×10−6 。

经过这两阶段的扩展训练,DeepSeek-V3 成功实现了对最长 128K 输入序列的高效处理。如图8所示,在完成监督微调后,模型在“大海捞针”(NIAH)测试中表现出色,在整个 128K 的上下文范围内均保持稳定的性能表现。

图 8:在 NIAH 中的评估结果显示,DeepSeek-V3 在所有上下文窗口长度(最长可达 128K)上均表现优异。

评估

评估基准

DeepSeek-V3 基座模型在以英语和中文为主的多语言语料库上完成预训练,因此评估工作主要针对英语、中文及多语言基准。

评估采用集成于 HAI-LLM 框架的内部评估系统,涵盖以下类别(下划线表示中文基准,双下划线表示多语言基准):

  • 多学科选择题评估:MMLU、MMLU Redux、MMLU-Pro、MMMLU、C-Eval 和 CMMLU
  • 语言理解与推理能力:HellaSwag、PIQA、ARC 和 BigBench Hard (BBH)
  • 知识问答评估:TriviaQA 和 NaturalQuestions
  • 阅读理解测试:RACE、DROP、C3 和 CMRC
  • 指代消歧任务:CLUEWSC 和 WinoGrande
  • 语言建模评估:Pile 中文理解与文化认知:CCPM
  • 数学能力测试:GSM8K、MATH、MGSM 和 CMath
  • 编程能力评估:HumanEval、LiveCodeBench-Base(0801-1101)、MBPP 和 CRUXEval
  • 综合能力测试:AGIEval(包含英语和中文两个子集)

作为前期工作的延续,评估采用多种方法:部分数据集使用困惑度指标,包括 HellaSwag、PIQA、WinoGrande 等;部分采用生成式评估,如 TriviaQA、NaturalQuestions、DROP 等。对 Pile-test 采用语言建模评估方法,使用每字节比特数(BPB)作为统一度量标准,确保不同分词器模型间的公平比较。

评估结果

表3展示了 DeepSeek-V3 基座模型与主流开源基座模型的性能对比,包括 DeepSeek-V2-Base、Qwen2.5-72B- Base  LLaMA-3.1-405B-Base。所有模型均使用统一的内部评估框架和评估标准。需要说明的是,由于近几个月评估框架的更新,DeepSeek-V2-Base 的部分性能指标与此前报告略有差异。

表3:DeepSeek-V3-Base 与其他具有代表性的开源基础模型的性能对比。所有模型均在内部评估框架下进行了测试,并采用了统一的评估设置。得分差距在 0.3 以内的模型被视为表现相当。评估结果表明,DeepSeek-V3-Base 在大多数基准测试中表现出色,尤其是在数学和代码任务上表现尤为突出。

综合评估显示,DeepSeek-V3-Base 全面超越 DeepSeek-V2-Base 和 Qwen2.5-72B-Base,并在绝大多数基准测试中领先 LLaMA-3.1-405B-Base,成为当前性能最强的开源基座模型

具体性能对比如下:

  1. 相比 DeepSeek-V2-Base:通过模型架构优化、规模扩展和数据质量提升,DeepSeek-V3-Base 实现了显著性能提升。
  2. 相比 Qwen2.5-72B-Base:尽管仅使用一半的激活参数,DeepSeek-V3-Base 在英语、多语言、代码和数学领域均展现出明显优势。在中文评测中,除 CMMLU 外的其他测试也优于 Qwen-2.5-72B。
  3. 相比 LLaMA-3.1-405B-Base:即便对方拥有 11 倍的激活参数量,DeepSeek-V3-Base 在多语言、代码和数学领域仍表现更优。在英语和中文语言能力评测中表现相当或更佳,特别是在 BBH、MMLU 系列、DROP、C-Eval、CMMLU 和 CCPM 等测试中表现突出。

得益于高效的架构设计和全面的工程优化,DeepSeek-V3 实现了极高的训练效率。在现有训练框架和基础设施下,每处理1T token 仅需 180K H800 GPU 小时,远低于 72B 或 405B 密集模型的训练成本。

讨论

MTP策略的效果分析

表4显示了 MTP 策略的详细分析结果。

表 4:MTP 策略的消融实验结果表明,该策略在大多数评估基准测试中均能显著提升模型性能。

研究分别在两种规模的基准模型上验证了该策略的效果。小规模实验采用了总参数量为 15.7B 的基线 MoE 模型,使用 1.33T token 进行训练;大规模实验则采用总参数量为 228.7B 的基线 MoE 模型,使用 540B token 训练。在保持训练数据和其他架构不变的情况下,为基准模型增加深度为 1 的 MTP 模块进行对比实验。值得注意的是,由于在推理阶段会移除 MTP 模块,因此比较模型的推理开销完全相同。

结果表明,MTP 策略在绝大多数评估指标上都带来了持续的性能提升。

无辅助损失平衡策略的效果分析

表5展示了无辅助损失平衡策略的分析结果。

表5:无辅助损失负载均衡策略的消融实验结果显示,与完全基于辅助损失的方法相比,无辅助损失策略在大多数评估基准测试中表现出更优的模型性能。

研究同样在两种规模的基线模型上进行了验证。小规模模型总参数量为 15.7B,使用 1.33T token 训练;大规模模型总参数量为 228.7B,使用 578B token 训练。这两个基准模型都采用纯辅助损失来实现负载平衡,使用带有 top-K 相关度归一化的 sigmoid 门控函数,其辅助损失强度的超参数分别与 DeepSeek-V2-Lite 和 DeepSeek-V2 保持一致。

研究在保持其他条件不变的情况下,移除所有辅助损失并引入无辅助损失平衡策略进行对比。结果显示,无辅助损失策略在大多数评估指标上都实现了更好的性能表现。

批次级与序列级负载平衡对比

无辅助损失平衡与序列级辅助损失的核心区别在于平衡范围:前者是批次级,后者是序列级。

批次级平衡提供了更灵活的约束条件,不要求每个序列内部实现领域平衡,这种灵活性使专家能够更好地适应不同领域的特点。为验证这一观点,研究分别记录和分析了一个 16B 的基于辅助损失模型和一个 16B 的无辅助损失模型在 Pile 测试集各领域的专家负载情况。如图9所示,无辅助损失模型确实展现出更明显的专家专业化特征。

图9:Pile 测试集三个领域中,无辅助损失模型与基于辅助损失模型的专家负载分布对比。结果显示,无辅助损失模型展现出更强的专家特化能力。相对专家负载定义为实际专家负载与理论平衡负载的比值。由于篇幅限制,仅展示两个层的结果,完整数据可参见原文附录C。

为深入探究这种灵活性与性能提升之间的关联,研究还设计并验证了一种批次级辅助损失方法,该方法在训练批次而非序列层面实现负载平衡。实验表明,在达到相似的批次级负载平衡程度时,批次级辅助损失能够实现与无辅助损失方法相当的性能。

具体而言,在 1B MoE 模型上的验证损失分别为:序列级辅助损失 2.258,无辅助损失方法 2.253,批次级辅助损失 2.253。3B MoE 模型的实验也显示类似结果:序列级辅助损失模型的验证损失为 2.085,而无辅助损失方法和批次级辅助损失方法均达到 2.080。

尽管批次级负载平衡方法展现出稳定的性能优势,但在实际应用中仍面临两个效率挑战:

  1. 个别序列或小批量数据可能出现负载不均衡;
  2. 推理阶段可能因领域迁移导致负载失衡。

对于第一个挑战,通过采用大规模专家并行和数据并行的训练框架得到了有效解决,这确保了每个微批量都具有足够规模。对于第二个挑战,研究设计了带有冗余专家部署的高效推理框架来应对。

后训练

监督微调(SFT)

研究构建了包含 150 万个多领域实例的指令调优数据集,针对不同领域特点采用了相应的数据创建方法。

推理数据处理: 在数学、代码竞赛和逻辑谜题等推理类任务中,系统采用内部 DeepSeek-R1 模型生成数据。虽然 R1 生成的数据具有较高的准确性,但同时存在推理冗长、格式不规范和输出过长等问题。因此,研究的核心目标是在保持 R1 模型高准确性的同时,实现输出的清晰简洁。

具体实施方法如下:首先针对特定领域(如代码、数学或通用推理)开发专家模型,采用 SFT 和 RL 相结合的训练流程。该专家模型随后作为最终模型的数据生成器。

对每个训练实例,系统生成两类 SFT 样本:一类是问题与原始答案的直接配对,另一类则引入系统提示词,将其与问题和 R1 答案组合。系统提示经过优化设计,包含了引导模型生成具有自我反思和验证机制响应的指令。

在RL阶段,模型通过高温采样生成响应,即使在没有明确系统提示的情况下,也能有效融合 R1 生成数据和原始数据的特征。经过数百轮RL迭代,中间模型成功整合了 R1 的响应模式,显著提升了整体性能。随后,研究采用拒绝采样方法,利用专家模型作为数据源,为最终模型筛选高质量的 SFT 数据。这种方法既保持了 DeepSeek-R1 的高准确性,又确保了输出的简洁性和有效性。

非推理数据处理: 对于创意写作、角色扮演和基础问答等非推理任务,系统利用 DeepSeek-V2.5 生成响应,并通过人工标注确保数据质量。

SFT 训练配置: 研究对 DeepSeek-V3-Base 进行了两轮 SFT 数据集训练,采用余弦衰减的学习率调度策略,初始学习率为 5×10−6,逐步降低至 1×10−6。训练过程中采用多样本序列打包技术,同时通过样本掩码机制确保各样本间的独立性。

强化学习(RL)

奖励模型设计

在强化学习过程中,系统同时采用规则型模型型两种奖励模型(Reward Model, RM)

规则型奖励模型:对于可通过明确规则验证的任务,系统采用规则型奖励机制进行反馈评估。例如,在处理具有确定性答案的数学问题时,要求模型在特定格式(如方框内)给出最终答案,从而可以通过规则进行自动验证。同样,在处理 LeetCode 编程题时,系统可通过编译器执行测试用例生成客观反馈。这种基于规则的验证方法具有较高的可靠性,能有效防止模型的投机行为。

模型型奖励模型:对于具有标准答案但形式灵活的问题,系统使用奖励模型评估输出与标准答案的匹配程度。而对于创意写作等缺乏标准答案的任务,奖励模型则基于问题和回答的整体性给出评估反馈。该奖励模型基于 DeepSeek-V3 的 SFT checkpoint 进行训练。为增强模型可靠性,系统构建的偏好数据不仅包含最终评分,还包含推导评分的完整推理过程,这种设计有效降低了特定任务中的奖励扭曲风险。

群组相对策略优化(Group Relative Policy Optimization, GRPO)

系统采用与 DeepSeek-V2 相似的GRPO方法。这种方法不需要与策略模型规模相当的评论家模型,而是通过群组评分估计基线。具体实现中,对每个问题 q ,GRPO 从原策略模型 πθold 采样一组输出{o1,o2,···,oG} ,并通过最大化以下目标函数优化策略模型 πθ:

其中ε 和 β 表示超参数;πref 代表参考模型;Ai 表示优势函数,其计算基于每组内输出所对应的奖励序列 {r1,r2,…,rG}。

在RL过程中,系统融合了编程、数学、写作、角色扮演和问答等多领域的提示词任务。这种多样化的训练策略不仅提高了模型对人类偏好的适应性,还在基准测试中取得了显著提升,特别是在监督微调数据有限的场景下表现出色。

评估

评估方法设置

评估基准:除基础模型测试所用的基准外,系统还在下列基准上评估了指令调优模型的表现:IFEval、FRAMES 、LongBench v2、GPQA、SimpleQA、C SimpleQA、SWE-Bench Verified、Aider、LiveCodeBench(选取 2024 年 8-11 月题目)、Codeforces、2024 年中国高中数学奥林匹克(CNMO)和 2024 年美国数学邀请赛(AIME)。

基准模型对比:系统选取了多个代表性模型作为性能对照基准,包括 DeepSeek-V2-0506、DeepSeek-V2.5-0905、Qwen2.5 72B Instruct、LLaMA-3.1 405B Instruct、Claude-Sonnet-3.5-1022 和 GPT-4o-0513。其中 DeepSeek-V2 系列选取最具代表性的版本,闭源模型则通过其官方 API 进行评估。

评估具体配置:在标准基准评估中,MMLU、DROP、GPQA 和 SimpleQA 采用 simple-evals 框架的标准提示词模板。MMLU-Redux 的零样本测试采用 Zero-Eval 提示词格式。其他数据集则遵循原始评估方案,使用数据集开发者提供的默认提示词模板。

在代码和数学能力评估方面

  • HumanEval-Mul 数据集覆盖 Python、Java、Cpp、C#、JavaScript、TypeScript、PHP 和 Bash 共 8 种主流编程语言。
  • LiveCodeBench(使用 2024 年 8-11 月数据)的评估同时采用CoT和直接输出两种方式。
  • Codeforces 评估采用参赛者百分位数作为衡量标准。
  • SWE-Bench verified 采用无代理框架进行评估。
  • Aider 相关测试采用”diff”格式评估。

在数学能力测试中,AIME 和 CNMO 2024 使用采样温度 0.7,结果取 16 次运行的平均值,而 MATH-500 则采用贪婪解码策略。

所有评估中,模型的最大输出长度限制为 8192 个 token。

标准评估

表6的评估结果显示,DeepSeek-V3 在开源模型中表现最佳,且与 GPT-4o 和 Claude-3.5-Sonnet 等顶级闭源模型相比具有竞争力。

表 6 | DeepSeek-V3 与其他具有代表性的聊天模型的比较。所有模型均在限制输出长度为 8K 的配置下进行评估。包含少于 1000 个样本的基准测试会通过多次不同温度设置的测试来得出稳健的最终结果。DeepSeek-V3 是表现最佳的开源模型,同时在与前沿闭源模型的对比中也展现出强大的竞争力。

英语能力评估:在 MMLU(评估大语言模型多领域知识和任务能力的标准基准)中,DeepSeek-V3 与 LLaMA 3.1-405B、GPT-4o 和 Claude-Sonnet 3.5 等顶级模型表现相当,明显超越 Qwen2.5-72B。

在更具挑战性的 MMLU-Pro 教育知识评测中,DeepSeek-V3 的表现仅次于 Claude-Sonnet 3.5。

在经过标签修正的 MMLU-Redux 测试中,DeepSeek-V3 的表现领先其他模型。

在博士级评测 GPQA-Diamond 中,DeepSeek-V3 仅落后于 Claude 3.5 Sonnet,但大幅领先其他竞争模型。

在长文本理解方面,DeepSeek-V3 继续保持顶级水平。在 DROP 的少样本测试中达到 91.6 的 F1 分数,领先所有对比模型。在需要处理 10 万 token 上下文的 FRAMES 问答测试中,仅次于 GPT-4o 但显著优于其他模型,充分展示了其处理超长文本的能力。在最新发布的 LongBench v2 测试中的最优表现,进一步证实了这一能力。

在 SimpleQA 事实性知识测试中,DeepSeek-V3 虽然落后于 GPT-4o 和 Claude-Sonnet,但这主要源于其资源分配策略——更多训练资源用于中文知识学习,因此在 C-SimpleQA 中表现优异。在指令遵循能力评估中,相比前代 DeepSeek-V2 系列有显著提升,特别是在理解和执行特定格式要求方面。

代码与数学能力评估:在编程领域,DeepSeek-V3 的评估涵盖工程实践(SWE-Bench-Verified)算法编程(HumanEval、LiveCodeBench)两个维度。

在工程类任务中,虽然未能超越 Claude-Sonnet-3.5-1022,但明显优于其他开源模型。作为开源模型,DeepSeek-V3 的强大能力将推动软件工程和算法开发领域的创新,帮助开发者和研究人员拓展开源模型在编程领域的应用边界。

在算法编程任务上,借助先进的知识蒸馏技术,DeepSeek-V3 在 HumanEval-Mul 和 LiveCodeBench 等测试中超越所有基线模型。

在数学能力测试中,DeepSeek-V3 为非 o1 类模型树立了新标准。在 AIME、MATH-500 和 CNMO 2024 等具有挑战性的测试中,其得分比第二名 Qwen2.5 72B 高出约 10 个百分点,这种显著优势充分验证了 DeepSeek-R1 知识蒸馏技术的有效性。

中文能力评估:在中英双语支持方面,Qwen 和 DeepSeek 是两个代表性的模型系列。

在中文 SimpleQA 事实性知识测试中,尽管 Qwen2.5 的训练数据量更大(18T token,超出 DeepSeek-V3 的 14.8T token 约 20%),DeepSeek-V3 仍领先 16.4 个百分点。

在 C-Eval(中文教育知识评估) CLUEWSC(中文指代消歧挑战)等测试中,两个模型表现相当,表明它们在中文推理和教育任务方面都达到了较高水平。

开放式评估

除标准基准测试外,系统还采用 LLM 作为评估者对模型的开放式生成能力进行评估,结果见表7。

表 7:英文开放式对话评估。在 AlpacaEval 2.0 中,V3使用“长度控制胜率”作为核心评估指标,以衡量模型在对话生成中的表现。

评估严格遵循 AlpacaEval 2.0 和 Arena-Hard 的标准规范,使用 GPT-4-Turbo-1106 进行配对评估。

在 Arena-Hard 测试中,DeepSeek-V3 相对于 GPT-4-0314 基准取得了 86% 以上的优胜率,与 Claude-Sonnet-3.5-1022 等顶级模型表现相当,充分展示了其在处理复杂任务(包括编程和调试)方面的卓越能力。作为首个在 Arena-Hard 测试中突破 85% 的开源模型,DeepSeek-V3 显著缩小了与闭源模型的差距,为开源模型在高难度任务领域树立了新标准。

在 AlpacaEval 2.0 评测中,DeepSeek-V3 同样表现出色,超越了所有参评的开源和闭源模型,展示了其在写作和问答方面的优秀能力。特别是相比 DeepSeek-V2.5-0905 提升了 20%,证明了模型在基础任务处理能力上的显著进步。

生成式奖励模型性能

研究将 DeepSeek-V3 的评判能力与领先模型 GPT-4o 和 Claude-3.5 进行对比。如表8所示,在 RewardBench 评测中,DeepSeek-V3 达到了 GPT-4o-0806 和 Claude-3.5-Sonnet-1022 最优版本的水平,并超越了其他版本。

表 8:GPT-4o、Claude-3.5-sonnet 和 DeepSeek-V3 在 RewardBench 基准测试中的性能对比。

通过引入投票机制,DeepSeek-V3 的评判能力得到进一步提升。基于此,系统采用 DeepSeek-V3 配合投票机制对开放式问题进行评估反馈,有效提高了模型对齐过程的可靠性和稳定性。

讨论

DeepSeek-R1 知识蒸馏分析

研究基于 DeepSeek-V2.5 模型评估了 DeepSeek-R1 知识蒸馏的效果。对比实验中,基准模型使用短链式思维数据训练,而对照组使用专家检查点生成的数据。

表9的结果显示,蒸馏数据在 LiveCodeBench 和 MATH-500 基准测试中都带来了明显提升。

表9:DeepSeek-R1 蒸馏对模型性能的贡献分析。在 LiveCodeBench 和 MATH-500 基准测试中的评估设置与表6相同,旨在确保结果的可比性。

研究发现了一个重要的平衡点:知识蒸馏能提高性能,但同时会显著增加输出长度。为此,DeepSeek-V3 在蒸馏过程中采用了经过优化的参数配置,以平衡模型准确性和计算效率。

研究表明,从推理模型进行知识蒸馏是提升模型后期性能的有效方法。当前研究虽然主要关注数学和编程领域的知识蒸馏,但这种方法在其他领域也展现出广阔前景。其在特定领域的成功表明,长链式思维蒸馏技术有望提升模型在其他需要复杂推理的认知任务中的表现。未来研究将继续探索该方法在不同领域的应用。

自我奖励机制

奖励机制是强化学习中的核心要素,决定着优化方向。在编程或数学等可通过外部工具直接验证的领域,强化学习展现出显著效果。但在更一般场景中,直接通过规则编码构建反馈机制并不可行。因此,在开发 DeepSeek-V3 时,针对这类广泛场景,采用了宪制 AI 方法,使用模型自身的投票评估结果作为反馈。这种方法在对齐效果上取得显著成效,大幅提升了模型在主观评估中的表现。

通过引入额外的宪制规则,DeepSeek-V3 能够向预期方向优化。研究认为,将补充信息与语言模型结合作为反馈来源的范式具有重要意义。大语言模型能够将各类场景中的非结构化信息转化为有效奖励信号,促进模型的持续优化。除自我奖励外,研究团队也在探索其他通用且可扩展的奖励方法,以持续提升模型在通用场景中的能力。

MTP性能

DeepSeek-V3 通过 MTP 技术实现同时预测两个 token,结合推测解码框架显著提升了解码效率。关键问题是第二个预测 token 的可用性,评估显示在不同生成任务中,第二个 token 的接受率稳定保持在 85%-90%,表现出较高的可靠性。

这种高接受率使 DeepSeek-V3 的解码速度提升至原来的 1.8 倍(以每秒生成 token 数衡量)。

结论、局限性和未来发展方向

本研究介绍了 DeepSeek-V3 大规模混合专家语言模型,该模型总参数量达到 671B,每次处理激活 37B 参数,训练数据规模达 14.8T token。

模型在延续 MLA 和 DeepSeekMoE 架构优势的基础上,创新性地提出了无辅助损失负载均衡策略,并引入多 token 预测训练目标以提升性能。

通过采用 FP8 训练技术和精细的工程优化,模型实现了高效的训练过程。在后训练阶段,成功将 DeepSeek-R1 系列模型的推理能力迁移至新模型。

综合评估显示,DeepSeek-V3 不仅成为当前性能最强的开源模型,还达到了与 GPT-4o 和 Claude-3.5-Sonnet 等顶级闭源模型相当的水平。同时,模型维持了极具竞争力的训练成本,完整训练过程(包括预训练、上下文长度扩展和后训练)仅需 2.788M H800 GPU 小时。

尽管模型在性能和训练效率上表现出色,但仍存在一些局限性,特别是在部署方面:首先,为保证推理效率,模型的最小部署单元规模较大,可能超出小型团队的资源能力;其次,虽然当前部署方案使模型的端到端生成速度比上一代提升了两倍以上,但仍有优化空间。这些局限性有望随着硬件技术的进步得到自然解决。

秉持长期发展理念,DeepSeek 将继续坚持开源路线,稳步推进通用人工智能的研究。未来研究将重点关注以下方向:

  • 持续优化模型架构,提升训练和推理效率,探索支持无限上下文长度的高效方案。同时突破 Transformer 架构的固有局限,拓展模型的建模能力边界。
  • 深化训练数据的质量提升和规模扩展,探索新的训练信号来源,实现数据在多个维度的全面扩展。
  • 加强模型的深层推理能力,通过扩展推理的广度和深度,提升模型的智能水平和问题解决能力。
  • 建立更全面的多维度评估体系,避免过度优化特定基准测试集而产生的能力误判,确保模型评估的科学性和全面性。

Speech Datasets Collection-语音数据集汇总

来源:https://github.com/RevoSpeechTech/speech-datasets-collection

文本翻译数据集:https://opus.nlpl.eu/

openslr 下载:

1)修改为国内地址

例如 aishell,默认的run.sh里写的是www.openslr.org/resources/33,需要改为国内站点,http://openslr.magicdatatech.com/resources/33

其他目录可以看: http://openslr.magicdatatech.com/resources.php

在使用 wget 下载文件时,如果遇到下载速度慢的问题,可以通过以下几种方法加速下载:

1. 使用多个连接

wget 默认只使用单个连接进行下载,但是你可以使用 aria2 这种工具,它支持多线程下载,显著加速下载速度。aria2 可以通过以下命令安装:

sudo apt install aria2  # For Ubuntu/Debian
brew install aria2  # For macOS

然后你可以使用 aria2 下载文件:

aria2c -x 16 -s 16 <URL>

-x 16 表示使用 16 个连接来下载文件,-s 16 表示将下载源分为 16 个部分。

2. 使用 --limit-rate 限制下载速度

虽然这并不会直接加速下载,但如果下载的速度不稳定,设置一个合理的速率限制可以避免带宽波动影响速度。在命令中加上 --limit-rate 参数:

wget --limit-rate=1m <URL>

这将限制下载速度为每秒 1 MB。

3. 启用断点续传

如果下载过程中断,可以使用 -c--continue 参数来启用断点续传,从中断的地方继续下载:

wget -c <URL>

This is a curated list of open speech datasets for speech-related research (mainly for Automatic Speech Recognition).

Over 110 speech datasets are collected in this repository, and more than 70 datasets can be downloaded directly without further application or registration.

Notice:

  1. This repository does not show corresponding License of each dataset. Basically it’s OK to use these datasets for research purpose only. Please make sure the License is suitable before using for commercial purpose.
  2. Some small-scale speech corpora are not shown here for concision.

1. Data Overview

Dataset AcquisitionSup/UnsupAll Languages (Hours)Mandarin (Hours)English (Hours)
download directlysupervised199k +2110 +34k +
download directlyunsupervised530k +1360 +68k +
download directlytotal729k +3470 +102k +
need applicationsupervised53k +16740 +50k +
need applicationunsupervised60k +12400 +57k +
need applicationtotal113k +29140 +107k +
totalsupervised252k +18850 +84k +
totalunsupervised590k +13760 +125k +
totaltotal842k +32610 +209k +
  • Mandarin here includes Mandarin-English CS corpora.
  • Sup means supervised speech corpus with high-quality transcription.
  • Unsup means unsupervised or weakly-supervised speech corpus.

2. List of ASR corpora

a. datasets can be downloaded directly

idNameLanguageType/DomainPaper LinkData LinkSize (Hours)
1LibrispeechEnglishReading[paper][dataset]960
2TED_LIUM v1EnglishTalks[paper][dataset]118
3TED_LIUM v2EnglishTalks[paper][dataset]207
4TED_LIUM v3EnglishTalks[paper][dataset]452
5MLSMultilingualReading[paper][dataset]50k +
6thchs30MandarinReading[paper][dataset]35
7ST-CMDSMandarinCommands[dataset]100
8aishellMandarinRecording[paper][dataset]178
9aishell-3MandarinRecording[paper][dataset]85
10aishell-4MandarinMeeting[paper][dataset]120
11aishell-evalMandarinMisc[dataset]80 +
12PrimewordsMandarinRecording[dataset]100
13aidatatang_200zhMandarinRecord[dataset]200
14MagicDataMandarinRecording[dataset]755
15MagicData-RAMCMandarinConversational[paper][dataset]180
16Heavy Accent CorpusMandarinConversational[dataset]58 +
17AliMeetingMandarinMeeting[paper][dataset]120
18CN-CelebMandarinMisc[paper][dataset]unsup(274)
19CN-Celeb2MandarinMisc[paper][dataset]unsup(1090)
20The People’s SpeechEnglishMisc[paper][dataset]30k +
21Multilingual TEDxMultilingualTalks[paper][dataset]760 +
22VoxPopuliMultilingualMisc[paper][dataset]sup(1.8k)
unsup(400k)
23Libri-LightEnglishReading[paper][dataset]unsup(60k)
24Common Voice (Multilingual)MultilingualRecording[paper][dataset]sup(15k)
unsup(5k)
25Common Voice (English)EnglishRecording[paper][dataset]sup(2200)
unsup(700)
26JTubeSpeechJapaneseMisc[paper][dataset]1300
27ai4bharat NPTEL2020English(Indian)Lectures[dataset]weaksup(15.7k)
28open_sttRussianMisc[dataset]20k +
29ASCENDMandarin-English CSConversational[paper][dataset]10 +
30Crowd-Sourced SpeechMultilingualRecording[paper][dataset]1200 +
31Spoken WikipediaMultilingualRecording[paper][dataset]1000 +
32MuST-CMultilingualTalks[paper][dataset]6000 +
33M-AILABSMultilingualReading[dataset]1000
34CMU WildernessMultilingualMisc[paper][dataset]unsup(14k)
35Gram_VaaniHindiRecording[paper] [code][dataset]sup(100)
unsup(1k)
36VoxLingua107MultilingualMisc[paper][dataset]unsup(6600 +)
37Kazakh CorpusKazakhRecording[paper] [code][dataset]335
38VoxforgeEnglishRecording[dataset]130
39TatoebaEnglishRecording[dataset]200
40IndicWav2VecMultilingualMisc[paper][dataset]unsup(17k +)
41VoxCelebEnglishMisc[paper][dataset]unsup(352)
42VoxCeleb2EnglishMisc[paper][dataset]unsup(2442)
43RuLibrispeechRussianRead[dataset]98
44MediaSpeechMultilingualMisc[paper][dataset]40
45MUCS 2021 task1MultilingualMisc[dataset]300
46MUCS 2021 task2MultilingualMisc[dataset]150
47nicolingua-west-africanMultilingualMisc[paper][dataset]140 +
48Samromur 21.05SamromurMisc[code][dataset] [dataset][dataset]145
49Puebla-NahuatlPuebla-NahuatlMisc[paper][dataset]150 +
50GolosRussianMisc[paper][dataset]1240
51ParlaSpeech-HRCroatianParliament[paper][dataset]1816
52Lyon CorpusFrenchRecording[paper][dataset]185
53Providence CorpusEnglishRecording[paper][dataset]364
54CLARIN Spoken CorporaCzechRecording[dataset]1120 +
55Czech Parliament PlenaryCzechRecording[dataset]444
56(Youtube) Regional American CorpusEnglish (Accented)Misc[paper][dataset]29k +
57NISP DatasetMultilingualRecording[paper][dataset]56 +
58Regional African AmericanEnglish (Accented)Recording[paper][dataset]130 +
59Indonesian UnsupIndonesianMisc[dataset]unsup (3000+)
60Librivox-SpanishSpanishRecording[dataset]120
61AVSpeechEnglishAudio-Visual[paper][dataset]unsup(4700)
62CMLRMandarinAudio-Visual[paper][dataset]100 +
63Speech Accent ArchiveEnglishAccented[paper][dataset]TBC
64BibleTTSMultilingualTTS[paper][dataset]86
65NST-NorwegianNorwegianRecording[dataset]540
66NST-DanishDanishRecording[dataset]500 +
67NST-SwedishSwedishRecording[dataset]300 +
68NPSCNorwegianParliament[paper][dataset]140
69CI-AVSRCantoneseAudio-Visual[paper][dataset]8 +
70Aalto Finnish ParliamentFinnishParliament[paper][dataset]3100 +
71UserLibriEnglishReading[paper][dataset]
72Ukrainian SpeechUkrainianMisc[dataset]1300+
73UCLA-ASR-corpusMultilingualMisc[dataset]unsup(15k)
sup(9k)
74ReazonSpeechJapaneseMisc[paper] [code][dataset]15k
75BundestagGermanDebate[paper][dataset]sup(610)
unsup(1038)

b. datasets can be downloaded after application

idNameLanguageType/DomainPaper LinkData LinkSize (Hours)
1FisherEnglishConversational[paper][dataset]2000
2WenetSpeechMandarinMisc[paper][dataset]sup(10k)
weaksup(2.4k)
unsup(10k)
3aishell-2MandarinRecording[paper][dataset]1000
4aidatatang_1505zhMandarinRecording[dataset]1505
5SLT 2021 CSRCMandarinMisc[paper][dataset]400
6GigaSpeechEnglishMisc[paper][dataset]sup(10k)
unsup(23k)
7SPGISpeechEnglishMisc[paper][dataset]5000
8AESRC 2020English (accented)Misc[paper][dataset]160
9LaboroTVSpeechJapaneseMisc[paper][dataset]2000 +
10TAL_CSASRMandarin-English CSLectures[dataset]587
11ASRU 2019 ASRMandarin-English CSReading[dataset]700 +
12SEAMEMandarin-English CSRecording[paper][dataset]196
13Fearless StepsEnglishMisc[dataset]unsup(19k)
14FTSpeechDanishMeeting[paper][dataset]1800 +
15KeSpeechMandarinRecording[paper][dataset]1542
16KsponSpeechKoreanConversational[paper][dataset]969
17RVTE databaseSpanishTV[paper][dataset]800 +
18DiDiSpeechMandarinRecording[paper][dataset]800
19BabelMultilingualTelephone[paper][dataset]1000 +
20National Speech CorpusEnglish (Singapore)Misc[paper][dataset]3000 +
21MyST Children’s SpeechEnglishRecording[dataset]393
22L2-ARCTICL2 EnglishRecording[paper][dataset]20 +
23JSpeechMultilingualRecording[paper][dataset]1332 +
24LRS2-BBCEnglishAudio-Visual[paper][dataset]220 +
25LRS3-TEDEnglishAudio-Visual[paper][dataset]470 +
26LRS3-LangMultilingualAudio-Visual[dataset]1300 +
27QASRArabicDialects[paper][dataset]2000 +
28ADI (MGB-5)ArabicDialects[paper][dataset]unsup (3000 +)
29MGB-2ArabicTV[paper][dataset]1200 +
303MASSIVMultilingualAudio-Visual[paper][dataset]sup(310)
unsup(600)
31MDCCCantoneseMisc[paper][dataset]73 +
32Lahjoita PuhettaFinnishMisc[paper][dataset]sup(1600)
unsup(2000)
33SDS-200Swiss GermanDialects[paper][dataset]200
34Modality CorpusMultilingualAudio-Visual[paper][dataset]30 +
35Hindi-Tamil-EnglishMultilingualMisc[dataset]690
36English-Vietnamese CorpusEnglish, VietnameseMisc[paper][dataset]500+
37OLKAVSKoreanAudio-Visual[paper] [code][dataset]1150

3. References