Qwen3-ASR:语音识别大模型

Qwen3-ASR-Flash实现了⾼精度⾼鲁棒性的语⾳识别性能,⽀持11种语⾔和多种⼝⾳。与众不同的是,Qwen3-ASR-Flash⽀持⽤户以任意格式提供⽂本上下⽂,从⽽获得定制化的 ASR 结果,同时还⽀持歌声识别。

Qwen3-ASR-Flash 单模型支持多种语言、方言和口音的精准转录:

  • 中文:包括普通话以及四川话、闽南语、吴语、粤语等主要方言。
  • 英语:支持英式、美式及多种其他地区口音
  • 其他支持语言:法语、德语、俄语、意大利语、西班牙语、葡萄牙语、日语、韩语和阿拉伯语。

为获得定制化的ASR结果,用户可提供任意格式的背景文本来获得倾向性ASR结果,Qwen3-ASR-Flash无需对上下文信息进行格式预处理。

支持的格式包括但不限于:

  • 简单的关键词或热词列表。
  • 任意长度和来源的完整段落或整篇文档。
  • 以任意格式混合的关键词列表与全文段落。
  • 无关甚至无意义的文本(模型对无关上下文的负面影响具有高度鲁棒性)。

 性能表现:

核心特性:

  • 领先的识别准确率:Qwen3-ASR-Flash在多个中英文,多语种benchmark测试中表现最优。
  • 惊艳的歌声识别能力:支持歌唱识别,包括清唱与带bgm的整歌识别,实测错误率低于8%。
  • 定制化识别:用户可以以任意格式(如词汇表、段落或完整文档)提供背景文本,模型能智能利用该上下文识别并匹配命名实体和其他关键术语,输出定制化的识别结果。
  • 语种识别与非人声拒识:模型能精确分辨语音的语种,自动过滤非语音片段包括静音和背景噪声
  • 鲁棒性:面对长难句、句中语言切换和重复词语等困难文本模式,以及在复杂的声学环境中,模型仍能保持高准确率

MOSS-TTSD 中英双语口语对话合成模型

当前的文本到语音(TTS)模型在单句或孤立段落的语音生成效果上取得了令人瞩目的进展,合成语音的自然度、清晰度和表现力都已显著提升,甚至接近真人水平。不过,由于缺乏整体的对话情境,这些 TTS 模型仍然无法合成高质量的对话语音。

MOSS-TTSD 是一个口语对话语音生成模型,实现了中英双语的高表现力对话语音生成,支持零样本多说话人音色克隆,声音事件控制以及长语音生成。与传统 TTS 模型只能生成单句语音不同,MOSS-TTSD 能够根据完整的多人对话文本,直接生成高质量对话语音,并准确捕捉对话中的韵律变化和语调特性,实现超高拟人度的逼真对话语音合成。

亮点

  • 高表现力对话语音:基于统一语义-声学神经音频Codec、预训练大语言模型、百万小时TTS数据与约40万小时的真实/合成对话语音数据,MOSS-TTSD能够生成高表现力,高自然度,具有自然对话韵律的拟人对话语音。
  • 双说话人零样本声音克隆:MOSS-TTSD支持零样本双说话人克隆,按脚本精确进行角色/声线切换。只需要提供10到20秒的参考音频片段。
  • 中英双语:MOSS-TTSD支持中英两种语言的高表现力语音生成。
  • 长音频生成:得益于低码率Codec与训练框架优化,MOSS-TTSD在长音频生成场景进行了大量训练(训练最大长度达到960s),能够单次生成超长音频。

模型概览

1 模型结构概览:基于Qwen3-1.7B-base模型进行训练,使用八层RVQ码本进行语音离散化,使用自回归加Delay Pattern进行语音token生成,最后使用Tokenizer的解码器将语音token还原为语音。

MOSS-TTSD 使用完全离散化的方式进行语音生成。我们训练了一个8层 RVQ 的音频 Codec:XY-Tokenizer,来对原始音频进行量化。 XY-Tokenizer 能够同时编码语音的语义和声学信息,并具有较低的比特率(1kbps),这使得LLM能够有效地学习音频序列并建模细节声学特征。 在序列建模方面,受到 MusicGen 和 VOICECRAFT的启发,我们使用自回归建模加多头 Delay 的方式进行语音 token 生成

语音离散化: XY-Tokenizer

为了统一建模语音的语义和声学信息,并实现低比特率,我们构建了 XY-Tokenizer,它使用了双路 Whisper Encoder 进行语音编码,8层 RVQ 量化,两阶段多任务学习的方式进行训练。实现了 1kbps 的比特率和 12.5Hz 的帧率[1024码本大小]。

XY-Tokenizer 采用了两阶段多任务学习的方式进行训练。第一阶段(上半部分)训练ASR任务和重建任务,让编码器在编码语义信息的同时保留粗粒度的声学信息。第二阶段(下半部分)我们固定住编码器和量化层部分,只训练解码器部分。通过重建损失和 GAN 损失,利用生成式模型的能力补充细粒度声学信息。

我们扩展了Codec训练的数据量,使用了10万小时带有转录文本的语音数据进行训练。下表对比了在LibriSpeech测试集上不同 Codec 在语义和声学性能上的表现。WER为ASR任务中的词错误率,WER越低表示语音 token 的语义信息与文本对齐程度更好。粗体为低比特率 Codec 组中的最优或次优性能。

XY-Tokenizer 是在1kbps,12.5Hz的帧率下同时建模语义和声学信息性能最好的 Codec ,在语义和声学指标上都取得了最优或次优的结果。

为了更好地编码和重建复杂的对话音频,我们扩展了50万小时无转录音频数据进行增强训练,扩展 Codec 对于复杂音频和场景的处理能力。

益于Codec的超低比特率,我们模型的训练长度最长达到了960s的音频,这使得我们的模型可以一次性地生成超长的语音,避免了拼接语音片段之间的不自然过渡。

数据工程

TTS 模型的性能与训练数据的质量和数量有着密切的关系,为了规模化高质量 TTS 数据和 TTSD 数据,我们设计了高效的数据处理流水线,可以从海量原始音频中准确筛选出单人语音和多人对话语音并进行标注。

对于原始音频,我们首先使用内部的说话人分离模型进行语音分段和说话人标注。 基于预训练基模,我们的说话人分离模型性能已经优于开源说话人分离模型 pyannote-speaker-diarization-3.1 及其商用版本 pyannoteAI 。

说话人分离模型在不同数据集上的 DER(Diarization Error Rate) 结果(越低越好),我们的模型在四个测试集上都取得了最优性能

我们使用 DNSMOS 分数来作为语音质量的评估标准,我们假设 DNSMOS 分数高的语音大概率不包含背景噪声。 为了保证语音的质量和较少的噪声,我们只保留 DNSMOS >=2.8的语音片段。 对于高质量的音频片段,我们直接对语音进行转录,作为 TTS 训练数据。 此外,我们设计了一套规则来将 Diarization 分离的语音片段组合成双人对话的片段用作 TTSD 训练,这样得到的对话片段我们称之为粗粒度对话片段。 虽然说话人分离模型能够较准确地分离说话人,但是我们发现它对一些较短的 Backchannel 不是特别敏感,存在漏分离的情况。 此外,当前的 ASR 模型无法准确地转录对话中重叠的语音。 因此,受 Parakeet[4] 的启发,我们训练了中文版的 Whisper-d 模型来对中文数据进行细粒度说话人标注和文本转录。对于英文数据我们直接使用 Parakeet 的开源 Whisper-d。 最终,我们使用说话人分离模型的粗粒度标签和 Whipser-d 模型的细粒度标签来将短对话片段组成长对话片段。

TTS 预训练

TTS 预训练模型在 Seed-tts-eval 测试集上的词错误率对比(越低越好),加粗的结果代表最优和次优的性能; WER(Norm) 表示我们针对 ASR 的同义结果做了规则修正,减少了 ASR 模型错误导致的误判; CER(Norm) 表示我们将中文文本转为拼音后再计算词错误率,即 PER 指标,我们认为这是更加合理的方式; SparkTTS 和 Cosyvoice2 的结果为我们本地使用官方推理代码重新测试的结果;

我们使用了110万小时的中英文 TTS 数据对模型进行了预训练,大规模的 TTS 预训练可以显著增强 TTSD 模型的语音韵律和表现力,并提升模型泛化能力。 我们使用了 Seed-tts-eval评测了 TTS 预训练模型的性能,取得了和当前顶尖闭源模型 Seed-TTS 相当的性能。 经过 TTS 预训练后的模型已经有了较强的语音生成能力和零样本音色克隆能力。

TTSD 后训练

最终,我们收集了10万小时中文对话数据和27万小时英文对话数据。 此外,为了增强模型的说话人切换准确率,我们合成了4万小时中文对话数据和4万小时英文对话数据。 为了增强模型对于中文标点符号的感知能力,我们使用 Gemini 对部分数据(约7万小时)中的转录文本进行了修正。

在训练阶段,我们基于 TTS 预训练的检查点,使用 WSD Scheduler 进行训练,我们没有针对 Decay 阶段做特殊的数据规划。 此外,我们发现无法通过验证集挑选表现最好的检查点,因此我们通过人工评估的方式挑选了主观表现最好的检查点。

DeSTA2.5-Audio 保留大模型推理能力

核心:通过设计模型自我生成数据的方法,仅使用少量数据就能跨模态对齐,同时实现鲁棒、泛化强、无需任务调参的通用音语大模型。 实现对音频输入的有效适应的同时,保留其指令跟随能力。 适用于没有大量的训练数据的情况!!!

论文标题:DeSTA2.5-Audio: Toward General-Purpose Large Audio Language Model with Self-Generated Cross-Modal Alignment

当前主流音频语言模型虽可执行听觉感知与指令遵循任务,但往往依赖人工构建或跨模型生成的数据集,导致模型出现灾难性遗忘(Catastrophic Forgetting)现象,语言能力退化明显。本论文从根本出发,重新审视数据构建流程,提出「模型自我生成训练目标」机制保留 LLM 的语言能力,同时实现精准的跨模态对齐,从而训练出鲁棒、泛化强、无需任务调参的通用音语大模型。

论文的主要研究成果与创新点:自生成跨模态对齐策略 DeSTA: 由 LLM 自行生成训练标签,确保风格与输出一致性,克服灾难性遗忘,提升跨模态迁移鲁棒性;大规模通用数据集 DeSTA-AQA5M: 覆盖语音、环境声、音乐三大领域,含 500 万组音频-指令-响应数据,源自 50 个公开数据集,总计约 7000 小时;强大的泛化性能: DeSTA2.5-Audio 在多个标准测试集(Dynamic-SUPERB、MMAU、SAKURA、Speech-IFEval、VoiceBench)上展示优异的性能。 

首篇系统提出“自生成音频文本对齐”策略并应用于 LALM 训练的研究;无需人工调教或额外任务调参,模型即能在多个语音理解、情绪识别、环境声分析等任务中展现 SOTA 表现;重要对比发现: 明确指出模型训练过程中数据来源与模型分布不一致将大幅损害性能,即使采用更强大的 LLM 生成数据亦无法弥补,凸显「数据生成一致性」为构建通用 LALM 的关键。以少胜多,仅用 7000 小时音频达成超过使用 51 万小时数据的模型效果,堪称“大模型训练范式创新”典范。

当训练数据与模型原有生成分布不一致时,模型容易遗忘其原有的语言理解与生成能力,这种现象在 LLM 融入新模态时尤为突出。

原因: 该方法本质上是利用encoder+Qformer学习语音中的元数据信息,采用同一个LLM为了保证输出分布一致性,这样只要encoder+Qformer学习到了语音中的元数据信息(对齐语音-文本),那么最后模型的输出就跟LLM的输出一致。如果构造数据的LLM跟训练的LLM不一致,那么不仅仅需要对齐语音和文本,还需要重新学习文本LLM的输出分布,那么就需要放开LLM的权重进行训练,会影响模型本身的文本能力,会逐渐扭曲大模型原本的输出分布或指令跟随能力,最终损害其基于文本的知识!!!

图 2. (左)数据集构建:将音频描述 xtext和随机采样的提示 p输入到基础大模型中,以生成训练目标 y。
(右)模型训练:融合模型使用自生成的目标 y 以及相应的音频输入 xaudio 和提示 p进行训练。火焰和雪花图标分别表示可训练模块和冻结模块。音频解码器为可选组件。

DeSTA2,一种自生成的跨模态对齐框架,通过让基础语言模型生成其自身的训练目标,从而缓解监督信号冲突。具体来说,我们将每个音频片段的元数据转换为结构化的文本描述,并与任意提示词配对;随后,大语言模型生成相应的响应,作为跨模态对齐的训练目标。这种自生成监督确保了风格和语义与大模型原生输出分布保持一致,从而在实现对音频输入的有效适应的同时,保留其指令跟随能力。

自生成数据集构建

Step1:收集多样化的音频数据集,这些数据集包含丰富的元数据信息。将每段音频的元数据转换为结构化的文本格式。

  • 例:语音片段 → "[00:00-00:05] Hello world (Gender:Female, Emotion:Happy...)"
  • 例:音频描述片段 → "[00:00-00:10] (A dog barking)"

Step 2:构建初始配对数据集

  • 形成初始数据集 Dinitial={(xaudio,xtext)},其中每条音频xaudio​ 与其对应的文本描述 xtext​ 对齐。

Step 3:采样提示词

  • 从预定义的指令池P 中随机采样一个提示词 p
  • 指令池包含多样化的提示类型:
    • 描述类任务(如 “Describe the audio”)
    • 角色扮演类任务(如 “Respond to the audio based on its expression”)
    • 开放式问题(如 “Where is the audio being recorded?”)

Step 4:生成训练目标

  • 将文本描述 xtext​ 与提示词 p 输入到大语言模型。
  • 模型输出响应 y=LLM(xtext,p)

Step 5:形成最终训练数据集

  • 构建最终的数据集D=(xaudio​ , xtext ​, p , y)
  • 每条样本包含:音频输入、对应文本描述、提示词、以及大模型生成的响应。

该方法的一个关键优势在于,它能够保留大语言模型对输入的原生理解与响应方式,从而保证训练数据在风格与语义上的一致性。举例来说,我们观察到经过指令调优的 Llama3.1往往会生成带有解释性的回答,使用项目符号组织内容,并且常常在正文前包含问候语。这些特定于模型的风格模式会自然地体现在生成的数据中。因此,虽然该构建流程可兼容任意文本类大模型,但在跨模态对齐任务中,采用相同模型(即自生成方式)是最合理的设计。

模型训练

采用 Llama3.1-8B-Instruct 和 Whisper-large-v3,六层 Q-former 【 64 个查询】架构。

预训练的音频模型与经过指令调优的大语言模型(LLM)进行融合。为了实现音频与语言模态之间的桥接,我们在二者之间引入了由 Q-Former 块 构成的模态适配器。

音频模型与 LLM 参数均被冻结,仅对模态适配器进行微调,以学习稳健的音频–文本对齐表征。融合模型在三元组形式(xaudio​,p,y) 上进行训练。

输入音频xaudio​ 可选地通过音频解码器转录为文本序列 t∈RL,其中 L 为序列长度。该转录结果进一步输入 LLM 的词嵌入层,用于增强语言对齐。

  • 优化器:Adam
  • 学习率调度:余弦退火(cosine annealing),包含 2000 步预热
  • 训练轮数:5 epoch
  • 硬件配置:8 张 NVIDIA A100-80GB GPU
  • 全局 batch size:96
  • 初始学习率:1e-4
  • 总训练步数:约 250,000 steps

Dataset

元数据包括副语言特征(例如音高、响度、语速、韵律、音色、情绪基调和说话风格)、说话者身份属性(例如口音、性别和年龄)、音频质量指标(例如背景噪音水平、混响以及伪造或合成音频)以及环境或情境声音(例如动物叫声、人类动作、环境声音、乐器、音乐类型和自然环境)。

数据集总计约 7,000 小时音频:5,400 小时语音、1,000 小时环境声音和 500 小时音乐。

关于指令池,为语音类别挑选了 4,000 个提示,为环境声音和音乐类别挑选了 3,000 个提示。

响应均使用 vLLM 工具包 生成,解码参数设定为 temperature = 0.05top-p = 1.0。通过这一过程,我们构建了一个规模约 500 万条音频–提示–响应三元组 的大规模数据集,命名为 DeSTA-AQA5M,并将其作为 DeSTA2.5-Audio 的训练语料。

实验结果

模型在多个基准测试中的排名呈现出一致的趋势。值得注意的是,DeSTA2.5-Audio 始终展现出卓越的性能,凭借在各种音频语言任务中强大的泛化能力,成为表现最佳的模型。它在 Dynamic-SUPERB Phase-1(69.53)、MMAU(57.50)、SAKURA-Multi(69.85)和 Speech-IFEval(93.89)上均取得了最高分,彰显了其在多个领域和条件下的稳健性和泛化能力。

消融实验:【核心】

 (PPL)困惑度越低,表明模型对训练目标越熟悉,分布差异就越小。

如表三所示,自生成的训练数据始终表现出较低的困惑度,这表明生成的响应与主干 LLM 的分布很好地一致。比较 Llama3.1 (A1) 和 Qwen2.5 (A2),Qwen2.5 在所有基准测试中始终优于 Llama3.1。这种性能差距可能归因于 Qwen2.5 更强大的文本生成能力。虽然 Qwen2.5 在基本内容理解任务中的表现与 Llama3.1 相对相当,但它在其他领域表现更佳,例如 Dynamic-SUPERB Phase-1 中的说话人分类,以及 MMAU 中的环境声音和音乐理解。先前对基于文本的基准测试的评估也表明,与 Llama3.1 相比,Qwen2.5 表现出更出色的推理和数学能力 。然而,目前尚无确凿证据表明在听觉感知方面有相应的优势,这值得进一步研究。尽管如此,在相同的训练条件下,我们的实验结果表明 Qwen2.5 作为主干 LLM 比 Llama3.1 更有效。这些发现也表明我们的训练框架在不同 LLM 上具有良好的泛化能力。

提示多样性对模型性能也起着重要作用,尤其是在 A1 和 A3 的比较中。在 A3 中,我们采用了使用单个描述性提示 (1-p) 的自生成设置,已经展示了强大的零样本泛化能力。通过简单地增加提示多样性(就像在 A1 中所做的那样),进一步丰富了训练目标并提高了训练方法的整体有效性。值得注意的是,这些结果是在不需要任何特定于任务的指令对的情况下实现的。这凸显了自生成设计的优势。即使数据构建完全依赖于随机抽样的提示,该模型仍然可以利用 LLM 的固有功能实现零样本生成。

比较自生成和跨模型设置时,跨模型设置中的训练目标会导致更高的困惑度,这表明主干 LLM 对数据分布的熟悉程度较低。例如,虽然在 Qwen2.5 生成的数据 (A2) 上训练 Qwen2.5 会产生很好的结果,但在 Qwen2.5 生成的数据 (B1) 上训练 Llama3.1 会导致模型退化,输出包含重复或无意义的标记。同样,在 Gemma3-12B (B2) 生成的数据上训练 Llama3.1 也无法达到在自生成设置 (A1) 中观察到的性能。这些结果支持了我们的分布不匹配假设,并强调了使用自生成配置的重要性,即使在注释器 LLM 功能更强大的情况下也是如此。我们还探索了使用 Llama3.1-70B 生成训练数据 (B3),它代表了同一系列中更强大的模型。在这种情况下,较低的困惑度 (2.20) 表明训练数据与 Llama3.1 的分布更加一致。然而,与 A1 相比,B3 在 Dynamic-SUPERB 和 SAKURA 上取得了更好的表现,但在 MMAU 和 Speech-IFEval 上表现不佳。这表明使用更强大的模型并不一定能在所有任务上带来一致的改进。

在 LoRA 适配器设置中,我们向骨干 LLM 引入了可训练参数,预计这将提升模型容量并有助于缓解分布不匹配问题。在自生成设置 (C1) 中,数据集与骨干 LLM 高度对齐,我们发现添加 LoRA 层可获得相似或略微提升的性能。这表明,在自生成设置下,加入 LoRA 适配器并不能带来显著的优势。换句话说,在使用我们提出的训练框架时,微调轻量级模态适配器足以实现跨模态对齐,其中模型专注于学习听觉概念,而不会受到风格或分布不匹配的影响。有趣的是,当使用 Qwen2.5 生成的数据 (C2) 进行训练时,在 Dynamic-SUPERB、MMAU 和 SAKURA-Single 等音频处理基准测试中的表现与自生成设置 (A2) 相当。然而,它们在 SAKURA-Multi 和 Speech-IFEval 中的表现显著下降,这需要额外的文本知识和指令遵循能力。这一差异表明,虽然添加 LoRA 适配器有助于缓解分布不匹配问题,并在领域内任务中取得良好表现,但在需要 LLM 预训练知识的基准测试中,它仍可能降低模型的通用能力。这揭示了当前 LALM 训练策略的一个关键设计缺陷。LTU-AS 和 SALMONN 等模型试图通过在 LLM 中引入 LoRA 适配器层来解决灾难性遗忘问题。 然而,我们的实验结果表明,减少训练数据和模型分布之间的差异对于保持泛化能力是比单纯的架构修改更为关键的因素。

在 5 个 epoch 的设置下,我们研究了训练时长对模型性能的影响。5 个 epoch 的结果(D1 和 D2)表明,即时多样性不仅提升了有效性,也提高了训练效率。尽管训练次数仅为 epoch 的一半,但这些模型的性能与 10 个 epoch 的模型(A1)相当。值得注意的是,虽然 D2 随着训练时间的延长而持续改进(与 A3 类似),但收敛速度较慢,最终性能仍然较差,这表明多样化的训练目标对于实现更好的对齐效果也至关重要。相比之下,尽管 D3 仅用 5 个 epoch 就取得了不俗的性能,但 B1 表明在分布不匹配的情况下延长训练会导致模型退化。这些发现强调了我们的主要动机:有效的跨模态对齐需要反复训练以在不同 epoch 之间对齐音频表征。当训练数据与骨干模型匹配时,性能会稳步提升,而不会降低模型固有的语言能力。相反,从不匹配的数据中学习会给模型带来更重的负担,最终导致性能不佳并忘记其预先训练的语言能力。

Higgs Audio V2-语音大模型

Higgs Audio V2模型,不仅能处理文本,还能同时理解并生成语音。除了一些常规语音任务外,这个模型还具备一些较为罕见的能力,比如生成多种语言的自然多说话人对话、旁白过程中的自动韵律调整、使用克隆声音进行旋律哼唱以及同时生成语音和背景音乐。

整个过程堪称“大力出奇迹”,直接将1000万小时的语音数据整合到LLM的文本训练,

Higgs Audio v2 采用上图架构图中所示的“generation variant”。其强劲的性能源于三项关键技术创新:

  • 开发了一套自动化注释流程,该流程充分利用了多个 ASR 模型、声音事件分类模型以及我们内部的音频理解模型借助该流程,我们清理并注释了 1000 万小时的音频数据,并将其命名为 AudioVerse 。该内部理解模型在 Higgs Audio v1 Understanding 的基础上进行了微调,并采用了架构图中所示的“理解变体”。
  • 从零开始训练了一个统一的音频分词器,它可以同时捕捉语义和声学特征。
  • 提出了 DualFFN 架构,它增强了 LLM 以最小计算开销建模声学 token 的能力。

Higgs Audio V2 在音频 AI 能力上实现了重大飞跃:

  • 多说话人对话自然流畅:多说话人对话往往难以处理,尤其是在角色无法匹配彼此的情绪和语气时。而借助 Higgs Audio V2,这种对话轻松自然,仿佛现场交流,充满生命力。
  • 支持长音频生成:生成长音频时需要保持声音的一致性,同时兼顾真实感、吸引力和生动性。Higgs Audio 提供条件控制与提示机制,使长音频表现出色。
  • 高保真音质:为了在高品质扬声器和耳机上实现逼真音效,V2 将音频处理管线从 16kHz 升级至 24kHz,带来更佳音质。
  • 高效推理,资源友好:无论是个人项目还是商用部署,推理效率都很重要。我们最小的模型可以在 Jetson Orin Nano 上运行;最新的 3B Audio Generation V2 模型则至少需要 RTX 4090 才能高效推理。
  • 生成真实、有情感的语音表现领先:在 EmergentTTS-Eval 基准测试中,其胜率超过 75%,超越 ChatGPT 4o。
  • 开源:模型开源。
  • 训练数据超千万小时:为实现更高音质与更逼真的语音效果,模型在超过1000万小时的音频上训练,并依托精细的处理与标注流程自动生成训练数据

模型原理:

传统的语音和文本模型之间相互独立,李沐老师就想,欸,能不能将两者结合起来,直接让LLM用语音进行沟通。那么首先就要知道文本语言模型的本质是用给定的一段指令去生成预测结果,就是将任务先拆解为系统指令(system)用户输入(user)模型回复(assistant)三个部分。system告诉模型,需要做什么事情,例如回答该问题、写一段文字或者其他,user就是告知事情的详细内容,例如问题具体是什么、文字要什么风格。

所以如果要让模型支持语音,就需要为模型增加一个系统命令,在user里输入要转录为语音的文字,让模型从system里输出对应语音数据。这样语音任务就能转换成相同的处理格式,直接打通语音和文本之间的映射,通过追加更多的数据和算力,直接scaling law“大力出奇迹”。

音频分词器:

这就引出了新的问题,语音信号本质是连续的,要如何才能在离散的文本token中表示呢?

现有的方法是将一秒的语音信号裁切成多段(如100毫秒一段),为每一段匹配最相似的预定义模板(如45个模板),然后将其表示为长度为10的编号序列,也就是一个个token。

但这样做,虽然可以将一小时的音频从60兆压缩到0.16兆,但质量相当糟糕,所以需要优先保留语音的语义信息而声学信号只保留少量部分,后续再通过其他手段还原

于是他们训练了一个统一的离散化音频分词器,以每秒25帧 [40ms/帧] 的速度运行,同时保持甚至提高音频质量,以捕获语义和声学特征。

新的离散化音频分词器运行速度仅为每秒25帧,同时在音质上保持甚至优于码率翻倍的分词器。该模型是首个在 24 kHz 数据上训练的统一系统覆盖语音、音乐与声音事件。同时,该模型采用简单的非扩散式编码器/解码器,实现快速批量推理

解析模块

  • 语义教师模型 (Semantic Teacher): 生成语义表示 S,用于指导语义编码器提取语义信息;
  • 语义编码器 (Semantic Encoder): 接收语义表示 S,提取语义特征 hS ;
  • 声学编码器 (Acoustic Encoder): 直接从输入音频 X 中提取声学特征 hA ;
  • 特征组合 (Concatenation): 将语义特征 hS 和声学特征 hA 进行特征组合,形成联合特征表示;
  • Dense 层: 对联合特征进行非线性变换,生成预量化特征 hpre ;
  • 残差向量量化 (RVQ): 对预量化特征进行hpre 量化,生成量化后的特征 hpost,并产生多个量化码本 Q1,Q2,…,Qm ;
  • Dense 层: 对量化后的特征进行非线性变换hpost,生成用于解码的特征表示;

解码过程:

  1. 语义解码器 (Semantic Decoder): 使用处理后的量化特征重建语义表示 S^ ;
  2. 声学解码器 (Acoustic Decoder): 使用处理后的量化特征重建音频信号 X^ ;

具体流程:

  1. 特征提取: 原始音频 X 输入后,通过语义教师模型生成语义表示 S,然后通过语义编码器和声学编码器分别提取语义特征和声学特征;
  2. 特征组合与量化: 提取的语义和声学特征进行组合,经过非线性变换后进行残差向量量化,得到量化后的特征表示;
  3. 解码与重建: 量化后的特征分别输入语义解码器和声学解码器,重建语义表示 S^ 和音频信号 X^ ;
  4. 损失计算与优化: 通过计算语义损失和声学损失,优化模型的参数,使重建的语义和音频尽可能接近原始输入;

音频分词器性能:

整体架构

HiggsAudio-V2 模型基于大型语言模型(LLM),并集成了音频适配器(Audio Adapter)和音频解码器(Audio Decoder),用于处理音频输入和输出。

模型基于 Llama-3.2-3B 构建。为了增强模型处理音频 token 的能力,引入了“DualFFN”架构作为音频 adapter。DualFFN 充当音频专家,以最小的计算开销提升 LLM 的性能。通过引入 DualFFN,我们的实现保留了原始 LLM 91% 的训练速度。

组件组成

  • Text Branch(浅蓝色):处理文本输入。
  • Understanding Variant(浅蓝色):用于理解文本和音频输入。
  • Generation Variant(黄色):用于生成文本和音频输出。
  • Audio Adapter – Dual FFN(虚线框):专门设计用于处理音频令牌的模块,包含两个前馈网络(FFN)和多头自注意力(MHA)模块。

文本输入

  • 文本输入通过 Text Tokenizer 转换为文本令牌(Text Token)。
  • 文本令牌通过 Text Branch 进行处理。

音频输入

  • 音频输入通过 Audio Tokenizer 转换为音频令牌(Audio Token)。
  • 音频令牌通过 Understanding Variant 进行处理。
  • 音频输入还通过 Semantic Encoder 提取语义信息。

文本和音频融合

  • 文本和音频令牌在 LLM 中进行融合处理。
  • Understanding Variant 和 Generation Variant 分别负责理解和生成任务。

音频适配器(Dual FFN)

  • Audio Adapter 包含两个并行的前馈网络(FFN),分别处理文本和音频特征。
  • 每个 FFN 之后都有一个归一化层(Norm)。
  • 处理后的特征通过多头自注意力(MHA)模块进行进一步处理。
  • 最终,处理后的文本和音频特征在音频适配器中融合。

为了提升模型处理音频令牌的能力,HiggsAudio-V2 引入了 “DualFFN” 架构作为音频适配器。DualFFN 作为音频专家模块,可以显著提升模型在音频任务上的性能,同时保持较低的计算开销。具体来说,DualFFN 在音频令牌处理过程中提供了额外的处理能力,使模型能够更有效地理解和生成音频数据。

延迟模式(Delay Pattern)

由于音频令牌化过程中涉及多个代码本,HiggsAudio-V2 采用了延迟模式(delay pattern)来实现跨代码本的并行代码生成。该模式通过在不同代码本之间引入偏移量,使得模型能够在保持音频质量的同时支持流式处理。延迟模式允许模型在生成音频时,同时处理多个代码本中的令牌,从而提高了生成效率。

然后要让模型很好地理解和生成声音,就需要利用模型的文本空间,将语音的语义尽量地映射回文本,当中需要大量的数据支持。

由于版权问题,沐导没有使用B站或YouTube这类公开视频网站数据,而是购买或从允许抓取的网站获取。这样得到的数据质量参差不齐,需要删除其中的90%才能满足1000万小时的训练数据需求。

其次,将语音对话表示为相应的system(场景描述、声学特征、人物特征等)、user(对话文本)、assistant(对应音频输出)的形式。由于OpenAI和谷歌一向禁止使用他们的模型输出再训练,且训练成本过高,为了实现这种标注,他们利用相同的模型架构额外训练出一个语音模型AudioVerse

该模型接收用户语音输入,分析并输出场景、人物、情绪、内容等信息,再将输出反过来作为生成模型的system提示和user输入,实现模型的共同进步。

举个例子就是,如果想要教一个徒弟同时会拳脚功夫,但师傅一次又教不了,那就同时教两个徒弟,一个学打拳,一个学踢腿,然后让他们俩天天互相打,打着打着两个就都会拳脚功夫了。

最终,这个多模态模型就完成了,不仅可以完成简单的文本转语音,还能实现更复杂的任务,比如让它写一首歌并唱出来,再加上配乐。

还能根据语音分析场景、人物(性别、年龄、情绪状态)、环境音(室内外),并进行复杂的理解和推理。

在实时语音聊天上,还可实现低延迟、理解情绪并表达情绪的自然语音交互,而不仅仅是机械的问答。

EmergentTTS-Eval基准上,相较于其他模型,性能可以说是遥遥领先,尤其是在“情绪”和“问题”类别中,相比GPT-4o-mini-tts高出了75.7%和55.7%的胜率。

此外,它在Seed-TTS Eval和情感语音数据集 (ESD) 等传统TTS基准测试中也取得了最佳性能。

Evaluation:

Seed-TTS Eval & ESD

我们使用参考文本、参考音频和目标文本对 Higgs Audio v2 进行零样本语音合成(TTS)测试。评估采用 Seed-TTS Eval 和 ESD 中的标准评估指标。【SIM 指标一般是指 Speaker Similarity

EmergentTTS-Eval (“Emotions” and “Questions”):根据 EmergentTTS-Eval 论文,我们报告了在使用 “alloy” 音色时,相较于 “gpt-4o-mini-tts” 的胜率。评判模型为 Gemini 2.5 Pro。

多说话人评估:我们还设计了一个多说话人评估基准,用于评估 Higgs Audio v2 在多说话人对话生成方面的能力。该基准集包含三个子集:

  • two-speaker-conversation:包含1000条双人合成对话。我们固定两段参考音频,用以评估模型在双人语音克隆方面的能力,对话轮数在4到10轮之间,角色随机选择。
  • small talk(无参考音频):包含250条合成对话,生成方式与上类似,但特点是发言简短、轮数较少(4–6轮)。本集合未提供参考音频,旨在评估模型自动为角色分配合适声音的能力。
  • small talk(有参考音频):同样包含250条合成对话,发言更短。该集合在上下文中包含参考音频片段,类似于 two-speaker-conversation,用于评估基于参考音频的表现。

我们在这三个子集上报告了词错误率(WER)和说话人内相似度与说话人间差异度的几何平均值。除 Higgs Audio v2 外,我们还评估了 MoonCast 和 nari-labs/Dia-1.6B-0626 这两个当前最受欢迎、支持多说话人对话生成的开源模型。结果总结在下表中。由于 nari-labs/Dia-1.6B-0626 对话语长度及输出音频的严格限制,我们未能在 “two-speaker-conversation” 子集上运行该模型。

Seed LiveInterpret 2.0 端到端同声传译大模型

!!!总结:必须认识到数据在模型训练的重要性。模型经过数十万小时语音数据的训练,数据质量中的任何瑕疵都可能在最终效果中被显著放大,这些潜在问题包括口音差异、准确读音、时间戳的准确预测,以及句子衔接的流畅度等关键要素。良好的性能正是建立在海量优质训练数据之上。

Seed LiveInterpret 2.0 是首个延迟&准确率接近人类水平的产品级中英语音同传系统,在中英同传翻译质量达到业界 SOTA 的同时,实现了极低的语音延迟水平。

它基于全双工端到端语音生成理解框架,支持中英互译,可实时处理多人语音输入,像人类同传译员一样以极低的延迟 “边听边说”,一边接收源语言语音输入,一边直接输出目标语言的翻译语音。同时,Seed LiveInterpret 2.0 还支持 0 样本声音复刻,让沟通更加流畅自然。

  • 接近真人同传的翻译准确率 精准的语音理解能力保障了翻译准确度,在多人会议等复杂场景中英双向翻译准确率超 70%,单人演讲翻译准确率超 80%,接近真人专业同传水平。
  • 极低延迟的 “边听边说” 能力 采用全双工语音理解生成框架,翻译延迟可低至 2-3 秒,较传统机器同传系统降低超 60%,实现了真正的 “边听边说” 翻译。
  • 零样本声音复刻,音色真实自然 只需采样实时语音信号,便能提取声音特征,用说话人的音色特质实时 “说出” 外语,提升交流的沉浸感和亲和力。
  • 智能平衡翻译质量、延迟和语音输出节奏 可根据语音清晰度、流畅度、复杂程度,调整输出节奏,并适配不同语言特性。面对超长信息,依然能保证传译语音节奏的自然流畅。
同声传译系统评估:左右两图比较了人工评估的翻译质量分数和响应效率  对于语音转文本 (S2T) 和语音转语音 (S2S) 模式,响应效率是相对于人工翻译延迟来衡量的。人工评估准确度反映了翻译输出对说话者原始意图的忠实程度

框架:

系统会克隆每位说话者的声音,并以相应的语调将其翻译为另一种语言
全双工流式端到端模型架构: Hibiki ,模型架构和数据相关可参考该论文

提出一种端到端的语音到语音同步翻译模型,在一个统一框架内无缝整合了同步语音翻译和语音克隆功能。

  • 语言模型预训练:使用 Seed LLM 系列的方法对初始语言模型进行预训练,建立基础的文本生成与理解能力。
  • 多模态扩展:集成一个预训练的音频编码器,使模型能够接受流式音频输入,扩展为具备音频处理能力的多模态 LLM。
  • 多任务持续学习训练:在大规模多任务数据上进行自回归训练,生成包括文本 token(可选)和音频 token 的输出,实现语音合成。
  • 高质量数据微调:使用人工标注的高质量数据进行微调,进一步优化模型在指令理解、多说话人识别、翻译策略关键能力上的表现。

问题:面临严格延迟约束下的同步翻译优化难题,需要在翻译质量时序控制之间权衡。

核心思路:优化两个互补目标

  • 片段内一致性确保每个翻译片段自身准确、流畅
  • 片段间连贯性确保不同翻译片段之间逻辑衔接自然

奖励机制设计

  • 多维单轮奖励(step-level):为每一步生成即时反馈,评估翻译准确性与时序控制,实现片段内部一致性优化
  • 统一多轮奖励(sequence-level):从全局角度评估整个翻译段落的连贯性,优化跨片段一致性

两阶段训练策略

  • 第一阶段:单轮奖励训练
    • 仅使用 step-level 奖励,学习人类翻译的先验知识,确保训练稳定
  • 第二阶段:联合优化训练
    • 引入 sequence-level 奖励,与 step-level 奖励联合优化,平衡过程指标(每步表现)与结果指标(整体输出质量)

主要贡献包括:统一的语音到语音架构、跨语言的语音克隆机制,以及接近人类水平的翻译性能。

Training

Continual Training and Supervised Fine-tuning

为实现文本与语音之间的有效模态对齐,并提升跨语言能力,我们采用了全面的多任务多模态持续训练(CT)策略。该策略有效促进了语音与文本模态之间的对齐,并强化了模型的跨模态与跨语言泛化能力

具体措施如下:

  1. 多模态多任务训练数据
    • CT 数据集涵盖约 1000 亿 tokens,任务类型包括:
      • 语音转文本(Audio-to-Text Transcription)
      • 文本转语音(Text-to-Audio Synthesis)
      • 纯文本处理(Text-Only Tasks)
  2. 数据质量控制
    • 为提升训练效率并确保数据质量,我们引入了基于语音质量指标的严格过滤流程,对语音数据进行筛选。

在持续训练之后,我们在高质量的人类标注数据上进行有监督微调,以激活同步语音传译所需的关键能力。该过程使模型能够建立以下数据驱动能力:

  1. 读-写策略(read-write policy)
  2. 多说话人区分能力
  3. 语音翻译能力
  4. 声音克隆能力

有监督微调显著提升了模型的指令跟随能力以及在核心传译任务上的整体表现。经过微调后的模型为后续的强化学习阶段提供了强大基础,使得后续优化更具针对性有效性

Reinforcement Learning

现代同声传译系统采用双工处理,将输入流分割成连续的音频块。形式上,我们将输入输出序列表示为:

每个音频片段(audioₜ)对应一个增量翻译 yₜ。我们将(audioₜ, yₜ)表示为序列中的第 t 个片段,并将 audio :=(audio₁, audio₂, …, audioₜ)表示为从 1 到 T 的聚合音频。在每个 t 片段中,我们有 yₜ :=(yₜ₁, yₜ₂, …, yₜₙ, …, yₜₙ),其中 N 是输出的长度。该模型利用当前音频片段(audioₜ)和之前的上下文 x<t,通过策略生成翻译 yₜ。

其中 πθ 是具有参数 θ 的策略 决定翻译策略。完整的轨迹概率定义为:

我们将 rtn 表示为 t 个块中第 n 个 token 的奖励。强化学习的目标是最大化每条轨迹上的累积奖励,即:

其中 𝒟 是训练数据集。以下部分详细说明了 rtn  的设计方式。

 奖励设计:平衡单轮反馈和多轮反馈

  • 单轮奖励(Single-turn rewards):在每个决策点提供即时反馈,用于评估中间的推理或生成步骤。
  • 多轮奖励(Multi-turn rewards):评估整个输出序列的质量,反映多个决策步骤的长期、累积效果。

同步翻译系统尤其具有独特挑战,因此需要精细化的奖励设计。该任务需同时优化两个互补目标:

片段内一致性(Intra-segment consistency):要求模型在逐步输出时保持语义与时间上的准确性和完整性,适合采用单轮奖励(single-turn reward)进行即时评估。

片段间连贯性(Inter-segment coherence):确保整个翻译序列在语义和时间上的连续性与一致性,适合采用多轮奖励(multi-turn reward),从全局角度评估累积的序列质量。

基于上述考量,我们提出了一种新颖的框架,将多维单轮奖励统一多轮奖励相结合。

Single-turn Reward:方法利用了每个增量步骤的细粒度反馈,我们通过实证研究发现,这些反馈与人类的评估指标高度相关。

给定一个音频序列 {audiot}1T 和相应的真实值 {yt}1T ,沿着五个派生维度定义段内奖励:

检测准确性奖励(rl​):该奖励旨在鼓励模型在翻译前进行充分“倾听”,避免过早输出,从而提升语义单元完整性。

I(⋅) 为指示函数,条件成立时取值为 1,否则为 0;∣yt​∣ 表示模型在第 t 步生成的 token 数量;∣yt∗​∣ 表示参考翻译在第 t 步应生成的 token 数量。当模型和参考翻译在当前步都没有输出(token 数为 0)时,奖励为 1,否则为 0该设计鼓励模型在语义信息尚不完整时保持“静默”,从而提升翻译的延迟-准确性权衡表现。

翻译主动奖励 ( rs ):通过奖励尽快生成已确认的语义单元来鼓励语音翻译:鼓励模型在语义单元一旦可用时立即翻译

翻译质量奖励(rq:衡量当前步生成内容与参考翻译的相似度(可通过 BLEU、BERTScore 等):

时序匹配奖励(rc​):鼓励模型生成的语音时长与参考时长一致,惩罚过长或过短:

格式一致性奖励(rf):保证输出结构正确,如标点、格式符号等符合预设正则表达式:

最终单轮奖励定义如下:

多轮奖励:单轮奖励机制提供了详细的、逐步的反馈,能够在每一步的递增中平衡延迟和翻译质量,但它未能完全捕捉同声传译中固有的长期依赖关系和累积效应。尤其是,当生成的目标音频逐渐落后于源音频时,会导致破坏性延迟,从而降低用户体验。为了解决这些全局序列级的动态问题,我们设计了一个互补的多轮奖励机制,可以整体评估整个输出序列。

延迟惩罚(rL​):惩罚翻译滞后,鼓励更及时的输出:

  • l:可接受的最大等待阈值
  • K:翻译片段数
  • dk​:第 k 个翻译片段前等待的片段数量

序列级翻译质量奖励(rQ​):衡量整个翻译序列与参考的匹配度(例如通过全局对齐算法):

多轮奖励定义为:

最终奖励融合与正则项

  • 每个子奖励在 batch 中进行标准化(均值为 0,方差为 1),提高数值可比性。
  • 总奖励为标准化后的单轮与多轮奖励之和,融合了局部细粒度指导全局一致性目标
  • 引入 KL 散度正则项:

用于鼓励当前策略 πθ​ 向参考策略靠拢,提升训练稳定性与可控性。

稳定强化学习训练:通过近端策略优化 (PPO)来优化定义的目标,该算法通过修剪的目标函数实现稳定高效的策略更新。训练目标公式如下:

 audio={audiot}1T 表示输入的音频序列, y={yt}1T 表示从旧策略 πθo⁢l⁢d 采样的翻译响应。优势估计 Atn 使用广义优势估计 (GAE)计算。由于这些奖励之间紧密耦合且差异化,调整它们各自的权重颇具挑战性,而且通常效果不佳。为了解决这些问题并稳定训练,我们采用了两种主要策略:自适应 KL 惩罚 和两阶段强化学习训练方案。

 Adaptive KL

对于包含音频和文本 token 的序列,由于其长度较长,控制 KL 散度会更加困难,这自然会导致更高的累积 KL 散度。因此,KL 惩罚系数 β 必须设置为高于传统 RLHF 的设置。

采用对数空间中的比例控制器来自适应地调整 β ,以确保 KL 散度始终接近预定目标。

两阶段强化学习训练方案:在第一阶段,通过仅优化多维单轮奖励来预热模型,使其内化人类先验知识并实现稳定的学习动态。在第二阶段,使用结合过程和结果成分的多轮奖励对模型进行进一步训练,使其能够有效地优化和平衡延迟与翻译质量。、

Experiments

 评估指标:

对于文本翻译质量评估,我们主要依赖于人工评估指标——有效信息比例 (VIP),该指标衡量翻译输出对每个语义片段传达说话者原始意图的准确程度,与人工翻译的判断高度一致。

在语音到语音评估中,我们提出了“语音有效信息比例”(SVIP)作为一种全面的人类评估指标。该指标建立在已有的“有效信息比例”(VIP)框架[6]之上,用于衡量完整语音会话中有效语义片段所占的比例。

当一个语音语义片段能够有效传达源语音的核心信息、准确表达说话者的原始意图、在可接受的延迟范围内完成传递、保持适合听众理解的语速,并达到清晰和易懂的声音质量标准时,即被视为有效。

在延迟评估方面,我们采用“首字母出现延迟”(FLAL)指标来衡量系统在段落级别输出第一个确定翻译所需的时间。在句子级别,我们使用广泛应用的“平均延迟”(AL)和“长度自适应平均延迟”(LAAL)指标,以比较不同方法之间的延迟表现。

在延迟表现上,Seed LiveInterpret 2.0 在语音到文本场景中,输出首字平均延迟仅 2.21 秒,在语音到语音场景中,输出延时仅 2.53 秒,做到了对翻译质量以及时延的均衡。

针对 Seed LiveInterpret 2.0 中译英和英译中两个方向的表现进行了客观评估,与其他翻译系统在翻译质量(BLEURT/ COMET)和延迟(AL/ LAAL/FLAL)等指标上进行对比。

结果显示,Seed LiveInterpret 2.0 在两个数据集上均表现出最高的翻译质量。在延迟方面,Seed LiveInterpret 2.0 在英到中方向上实现了语音到语音翻译的最低平均滞后(AL),在中到英方向上也表现出竞争力,展现了速度与准确度的良好平衡。

总体来看,Seed LiveInterpret 2.0 在句子级基准测试中,有效平衡了翻译质量与延迟。这不仅缓解了传统同传中 “译得准则慢,译得快则偏” 的痛点,配合音色复刻能力,让中英跨语言交流首次具备自然对话般的流畅感。

总结与展望

在本研究中,团队进一步认识到数据对模型训练的重要性。模型经过数十万小时语音数据的训练,数据质量中的任何瑕疵都可能在最终效果中被显著放大,这些潜在问题包括口音差异、准确读音、时间戳的准确预测,以及句子衔接的流畅度等关键要素。Seed LiveInterpret 2.0 良好的性能正是建立在海量优质训练数据之上。

Seed LiveInterpret 2.0 已初步展现出一定优势,其边界仍有拓展空间。比如,在语言覆盖方面,目前模型主要支持中英互译,其他语种尚未较好支持。此外,其声音复刻的稳定性、语音表现力、情绪复刻能力、极复杂情况下的翻译准确性等仍有进步空间。

在未来研究中,我们希望进一步挖掘模型潜力,通过优化算法、增强数据及改进训练策略等方式,逐步拓展同传模型的能力边界,提升其在复杂场景下的适应性和性能表现。

高效 LLM 训练方法:Packed samples和 sorted batching

 要让大型语言模型更有效地处理长文本上下文,需要在相似长度的输入序列上进行指令微调。LongAlign 方法,它可以帮助大型语言模型有效处理长达 64k 的长上下文,并展现出强大的长文本理解和生成能力。

LongAlign :

动机:

  • 目前缺乏用于有监督微调(SFT)的长文本指令跟随数据集,更缺乏构建此类数据的方法。
  • 长上下文数据的长度分布差异较大,在多GPU环境中严重降低了传统批处理方法的训练效率——处理较短输入的GPU必须等待处理较长输入的GPU完成任务后才能继续运行。
  • 亟需一个强健的基准评估体系,用于衡量大型语言模型在面对真实世界长文本查询时的处理能力。

贡献:

LongAlign 方法,分别从数据构建、高效训练和评估三个方面入手:

在数据方面,为构建一个多样化的长文本指令跟随数据集,从九个来源收集了长文本序列,并使用 Self-Instruct生成了 1 万条长度在 8k 到 64k 之间的指令数据。

在训练方面,为应对不均匀批处理导致的效率问题,采用了 packing 策略,即在将数据分发到 GPU 之前,将多个序列打包为接近最大长度的组合。但我们发现这种打包训练中的损失计算存在偏差:不同数量序列的打包在最终损失计算中被赋予相同权重。为缓解这一问题,我们提出了“损失加权策略”,对每条文本的损失进行加权平均,以平衡不同序列对整体损失的贡献。此外,我们还引入了“排序批处理”方法,将长度相近的序列分组,从而减少批内空闲时间

在评估方面,开发了 LongBench-Chat 基准测试,它包含长度为 10k-100k 的开放式问题,这些问题由博士生进行标注。评估内容涵盖推理、编程、摘要以及多语种翻译等多种长文本指令跟随能力。使用 GPT-4(OpenAI,2023b)结合人工标注结果和少量示例,对模型生成的回答进行评分。

结论:

数据量与多样性的影响:长文本指令数据的数量和多样性都会显著影响模型处理长上下文的能力,最终性能差异最高可达 30%。

长文本指令数据的益处:增加长文本指令数据有助于提升模型在长上下文任务中的表现,同时不会削弱其处理短上下文任务的能力。

训练策略的有效性采用的打包和排序批处理策略可将训练速度提升超过 100%,且不影响模型性能。此外,提出的损失加权技术还能将长文本任务的性能提升 10%。

数据集构建:

构建了一个包含10,000条长度在8k-64k之间的长文指令跟随数据集,这些数据来自于9个不同的数据源,包括学术论文、书籍、百科全书等,覆盖了多样化的任务类型。

高效训练方法:

为了确保模型在有监督微调(SFT)后依然具备处理长文本和短文本(即通用能力)的能力,将长文本指令数据与通用指令数据集混合用于训练。这种训练策略使得大量通用短文本数据与相对较少的长指令数据结合,从而形成了一个“长尾”式的数据长度分布。探索了两种训练方法:packingsorted batching

Packing(打包)

该方法通过将不同长度的数据拼接,直至达到最大长度,生成的打包数据整体长度接近最大限值。这些打包后的数据再进行批处理并在多 GPU 上处理,有效减少了每个批次中的空转时间。

此外,为防止同一 pack 中的不同序列在自注意力计算中发生“交叉污染”,我们传入了每个序列的起始与结束位置列表,并使用了 FlashAttention 2 中的 flash_attn_varlen_func 该方法支持高效的块对角注意力计算,计算量与 IO 时间均优于传统的二维注意力掩码。

Packing 策略存在的偏差

不过我们注意到,packing 会带来对长序列目标 token 较多的序列偏向。这是因为:不同的打包组(pack)在最终损失计算中被赋予相同权重,而每个打包组中包含的序列数量和每个序列的目标 token 数量却不同。

因此,在对每个批次求平均损失时,包含序列较少(通常是较长序列)或目标 token 较多的 pack,会对最终损失产生更大影响。

形式上,设将 M 个序列打包成 K 个 pack,第 i 个 pack 包含索引区间为 [Pi−1,Pi)的序列,其中 P0=1,PK=M+1。设 Li 为第 i个序列在其 Ni​ 个目标 token 上的总损失。如果我们希望对每个序列赋予相等的权重[ SFT中算loss ],则损失应当为:

而在 packing 情况下计算得到的损失为:

(3)与公式 (2) 相比,在 packing 情况下,相当于为第 j个序列分配了一个权重:

也就是说,损失更偏向于目标 token 数较多的序列,以及位于pack 较小的组中的序列。

为了解决这种不公平,我们提出对第 i 个序列的损失进行缩放,缩放因子为:K/(NiM),然后对每个 pack 中缩放后的损失求和,这样得到的总损失将与公式 (2)(即平均每个序列损失)保持一致,从而消除了不同序列在损失计算中所受到的偏倚。

损失加权策略在下游任务中带来了约 10% 的性能提升

Sorted Batching(排序批处理)

还提出了一种高效的 排序批处理策略。为确保每个 batch 中的序列长度相近,我们先按照序列长度对数据进行排序,然后在每轮训练中从中随机选取一段连续的数据组成一个 batch,且不重复使用。

不过,该方法不可避免地会引入 批次间数据分布的不均衡某些 batch 可能全部由长序列组成,另一些则全是短序列。这种偏差可能对 SGD(随机梯度下降)优化过程造成严重影响。

尽管如此,我们在实验中发现,排序批处理显著加快了训练速度,且几乎不会对模型性能产生负面影响。这可能得益于我们使用了较大的梯度累积步数(gradient accumulation steps)和优化器本身较强的适应能力。

训练方法细节

这里介绍 packing 策略与损失加权的具体实现方式。

Packing 策略实现

在打包训练过程中,每个数据批次会传入一个特殊的一维注意力掩码。在该掩码中,第 i个元素表示第 i 个序列在该批次中的起始位置。掩码的第一个元素为 0,最后一个元素等于 batch_size × seq_len

在注意力计算时,我们使用 FlashAttention 2 提供的 flash_attn_varlen_func 函数,并将该掩码传入其参数 cu_seqlens_qcu_seqlens_k。该函数会根据掩码中相邻元素表示的起始和结束位置,在每个序列内部进行注意力计算因此,每个序列的 Query 只能与自身的 Key 进行注意力操作,实现了“序列内独立注意”。

损失加权策略实现

在实现损失加权策略时,首先对训练数据进行预处理:为每个 pack 中的序列生成一个加权的一维掩码。该掩码中,对应目标 token 的位置权重为 1/N(其中 N 是当前序列的目标 token 数),其他位置为 0。

训练时,根据当前配置动态设置 M 和 K,表示即当前批次中序列的数量和 pack 的数量。然后,损失计算方法为:对每个 token 的交叉熵损失乘以比例系数 K/(MN),再求和得到最终损失值。

Packing 加权loss代码实现:

SFT中算loss通常来讲都是样本内作token-level mean,样本间作sequence-level mean,也就是等式(2)的计算方式。如果不同样本间作token-level mean,则会使target token数量多的样本更受重视(相当于被upsample),从而引入不同样本间的不平衡。

### Support loss weighting for packing ###
        loss = None
        if labels is not None:
            lm_logits = lm_logits.to(torch.float32)
            # Shift so that tokens < n predict n
            shift_logits = lm_logits[..., :-1, :].contiguous()
            if isinstance(labels, tuple) or isinstance(labels, list):
                labels, weights = labels
            shift_labels = labels[..., 1:].contiguous()
            if self.pack_loss:
                shift_weights = weights[..., 1:].contiguous()
                loss_fct = CrossEntropyLoss(ignore_index=-100, reduction='none')
                loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
                loss = (loss * shift_weights).sum()
            else:
                loss_fct = CrossEntropyLoss(ignore_index=-100)
                loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))

            lm_logits = lm_logits.to(hidden_states.dtype)
            loss = loss.to(hidden_states.dtype)
        ### -------------------------------------- ###

图解OpenRLHF中基于Ray的分布式训练流程

摘自:图解OpenRLHF中基于Ray的分布式训练流程

本文着重分析OpenRLHF中的PPO-Ray训练架构设计,不了解Ray的朋友也可以通过本文快速上手,全文共分成五块:

  1. 为什么用Ray
  2. 使用图例抽象出整体训练流程
  3. Ray核心知识速过
  4. 使用图例进一步抽象出核心代码细节,包括:
  • 训练入口
  • 部署PPO-Actor/Ref/Critic/RM实例
  • 部署vllm_engines实例
  • PPO-Actor与vllm_engines之间的通讯
  • PPO-Actor/Critic训练

5. RLHF-PPO算法细节介绍

Ray介绍

https://github.com/ray-project/ray

Ray 是一个用于扩展 AI 和 Python 应用程序的统一框架。Ray 由一个核心分布式运行时和一组用于简化 ML 计算的 AI 库组成:

一站式平台
包含多个模块(Ray Core, Ray Data, Train, Tune, RLlib, Serve)支持从数据加载、并行任务调度,到分布式训练、超参调优、强化学习、模型部署的完整 AI 工作流。

Python 原生,门槛低
采用装饰器 @ray.remote 标记函数或 Actor,即可并行执行,无需了解底层分布式原理。

高性能 & 容错能力
利用分布式调度、共享内存对象存储、层级调度机制,实现低延迟、高吞吐、故障恢复。

一、为什么要使用Ray

对于通常的rlhf框架,在训练时会在单卡上同时部署actor/ref/reward/critic四类模型,这种单一的部署方式可能存在如下问题:

  • 难以突破单卡显存的限制。
  • 无法实现更多的并行计算。例如在收集exp阶段,拿到(prompt, responses)结果的四类模型其实可以做并行推理;在训练阶段,拿到exp的actor和critic也可以做并行训练。但受到单卡显存等因素影响,通常的rlhf框架中使用更多的是串行。
  • 无法独立优化训练和推理过程。诸如vllm之类的框架,是可以用来提升actor生成(prompt, responses)的速度的,而对于其它模型,我们也可能会视算法需要有不同的推理需求。因此我们期望能更加灵活地设计训练、推理过程


而解决以上问题,需要开发者能设计一套较为灵活的分布式计算框架,能够实现资源定制化分配、分布式调度、节点内外通信等目标,同时相关的代码不能太复杂,能够让使用者更专注于算法部分的研发。而Ray天然可以帮我们做这件事:我们只需提供自己的资源分配方案,告诉Ray我想怎么部署这些模型,不管是分开还是合并部署Ray都可以帮我们实现。而复杂的调度策略和通信等事项,就由Ray在后台去做,我们无需关心这个过程。

二、整体流程

本节我们将提供2个例子,帮助大家更好理解使用Ray可以做什么样的“定制化”部署。注意,这些例子只做讲解用,不代表它们一定是训练的最优配置。

2.1 非共同部署


这个例子展示如何完全独立部署各个模型。假设我们有3台node,每台node 8张卡。以下展示其中一种可行的部署方式:

(1)部署4类模型

在这个例子中,4类模型分开部署在node0和node1上。以Actor为例,它分布在“node0的gpu0/1 + node1的gpu0/1”上。这一点是由Ray实现的:我们自己定制化资源分配的方案,进而管控模型的分配方式

而当实际训练时,我们还可进一步引入Deepspeed zero做优化:以Actor为例,上图中的4个Actor构成zero中的数据并行组(world_size = 4),根据zero的配置,我们可以在这4张卡间做optimizer/gradients/weights的切片

(2)部署vllm_engines

前文说过,对于Actor模型,在收集exp阶段我们可以采用vllm之类的框架加速(prompt, responses)的生成。在这个例子中:

  • 1个vllm_engine维护着一个vllm实例,每个vllm实例下维护一个完整的Actor模型,这里我们还假设一个vllm实例按tp_size = 2的方法切割模型。
  • 在node2中,共有4个vllm_engines(也即4个vllm实例),这种分配方式是通过Ray实现的。而每个vllm实例内的分布式推理则是由vllm自己管控。


(3)Actor与vllm_engines之间的通讯

我们称:

  • vllm_engines中的actor为vllm_actor
  • node0/1中的actor为ds_actor

在整个训练过程中,vllm_actor需要和ds_actor保持权重一致。我们来看这个一致性是如何维护的:

  1. 初始化阶段

假设pretrain路径下存储着sft模型,当我们首次开始训练时,ds_actor和vllm_actor都直接从pretrain中加载权重,两者互不影响,独立加载。


2. 训练中

在1个step结束后,ds_actor需要把更新后的权重broadcast给vllm_actor,具体步骤如下:

  • 首先,对ds_rank0 + all_vllm_ranks创建一个通讯组。在本例中:
    • node0/gpu0上的actor是ds_rank0
    • node2中所有的gpu构成all_vllm_ranks。
    • 我们就是把这两者纳入一个通讯组内,这个通讯组的world_size = 9。如果我们多一台node3来做vllm_engines,那么这个通讯组的world_size = 17,以此类推。
  • 假设我们使用ds_zero1/2,则ds_rank0上维护的是完整的actor权重,我们把ds_rank0上的权重broadcast到每一个vllm_rank,如有设置tp,vllm会自动帮我们完整接下来的模型切割。
  • 假设我们使用ds_zero3,则ds_rank0上只维护部分actor权重,那么:
    • ds_rank0先从ds_actor组内all gather回完整的模型权重
    • 再将完整的模型权重brocast给每一个vllm_rank

3. 从检查点恢复训练(load_checkpoint)

当我们需要从检查点恢复训练时,ds_actor会负责把检查点权重broadcast给vllm_actor,方式同2。

(4)整体运作流程

结合2.1开头的图例,我们来简述一下整体运作流程。

  • 首先明确一些表达。例如,node0中的Actor0/1 + node1中的Actor0/1属于相同的数据并行组,所以接下来我们会用它们在dp组中的rank来描述它们,也就是分别改称Actor0/1/2/3。对于其余三类模型也是同理。
  • 接着进行分组:
    • Actor0 / Ref0 / RM0 / Critic0 / vllm_engine0为一组
    • Actor1 / Ref1 / RM1 / Critic1 / vllm_engine1为一组
    • Actor2 / Ref2 / RM2 / Critic2 / vllm_engine2为一组
    • Actor3 / Ref3 / RM3 / Critic3 / vllm_engine3为一组
    • 你可以把每一组想象成原来的一张单卡,那么它的作用就是负责一个micro_batch的训练,这样我们就能大致想象到它们之间是如何配合运作的了。需要注意的是,在我们的例子中,这些实例都是一一对应的(各自有4个实例),但在实际操作中,根据不同用户的资源配置,不一定存在这个一一对应的关系。例如你可能用4卡部署Actor,2卡部署Critic,8个vllm_engines…以此类推。不管怎样,我们应该尽量在处理micro_bathes的各个组间均匀分配负载,在代码里相关的操作如下:

为每个actor分配其对应的critic/reward/ref,并启动每个分组的训练:https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ray/launcher.py#L278-L299
为每个actor分配对应的vllm_engine,并使用vllm_engine进行推理:https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ppo_utils/experience_maker.py#L627

2.2 共同部署


同样,我们可以按照自己的需求,选择性地在单卡上部署不同种类的模型,例如下面的例子中,actor/ref共部署,critic/reward共部署,图例如下,运作流程和2.1相似,这里不赘述:

三、Ray的核心概念

在传统的编程中,我们经常使用到2个核心概念:function和class。而在分布式系统中,我们希望可以分布式并行执行这些function和class。Ray使用装饰器@ray.remote来将function包装成Ray task,将class包装成Ray actor,包装过后的结果可以在远端并行执行。接下来我们就来细看task/actor(注意,这里的actor是ray中的概念,不是rlhf-ppo中actor模型的概念)

请大家一定仔细看3.1和3.2节代码中的注释。

3.1 Ray Task

import ray
ray.init()

@ray.remote
def f(x):
    return x * x
# ===================================================================
# 创建driver进程,运行main
# ===================================================================
if __name__ == "__main__":
    # ===================================================================
    # 创建4个worker进程,可以在远端并行执行。
    # 每执行1次f.remote(i),会发生以下事情:
    # - 创建1个worker进程,它将在远端执行函数f(i)
    # - 在driver进程上立刻返回一个引用(feature),该引用指向f(i)远程计算的结果
    # ===================================================================
    futures = [f.remote(i) for i in range(4)]
    # ===================================================================
    # 阻塞/同步操作:等待4个worker进程全部计算完毕
    # ===================================================================
    results = ray.get(futures)) 
    # ===================================================================
    # 确保全部计算完毕后,在driver进程上print结果
    # ===================================================================
    print(f"The final result is: {results}") # [0, 1, 4, 9]



3.2 Ray Actor

import ray
ray.init()

@ray.remote
class Counter(object):
    def __init__(self):
        self.x = 0
    
    def inc(self):
        self.x += 1
    
    def get_value(self):
        return self.x

# ===================================================================
# 创建driver进程,运行main
# ===================================================================
if __name__ == "__main__":
    # ===================================================================
    # 创建1个worker进程,具体做了以下事情:
    # - 在远端创建Counter实例
    # - 在driver端即刻返回对该实例的引用c(称为actor handler)
    # - 我们可以在Ray集群的任何节点上传递和使用这个actor handler。即在任何地方,
    #   我们可以通过c来invoke它对应Counter实例下的各种方法
    # ===================================================================
    c = Counter.remote()

    # ===================================================================
    # 阻塞/同步:通过c来invoke远端Counter实例的get_value()方法,并确保方法执行完毕。
    # 执行完毕后才能接着执行driver进程上剩下的代码操作
    # ===================================================================
    print(ray.get(c.get_value.remote()))  # 0
    
    # ===================================================================
    # Increment the counter twice and check the value again.
    # 道理同上,不赘述
    # ===================================================================
    c.inc.remote()
    c.inc.remote()
    print(ray.get(c.get_value.remote()))  # 2

3.3 Ray cluster架构简图


现在我们已经通过以上例子对Ray运作原理有了一些基本感知,我们来进一步探索一个ray cluster的组成

  • 在一个ray cluster中,会有一台head node和若干worker node
  • Driver process是一种特殊的worker process,它一般负责执行top-level application(例如python中的__main__),它负责提交想要执行的任务,但却不负责实际执行它们。理论上driver process可以运行在任何一台node内,但默认创建在head node内。
  • Worker process负责实际任务的执行(执行Ray Task或Ray Actor中的方法)。
  • 每台node中还有一个Raylet process,它负责管控每台node的调度器和共享资源的分配。
  • Head node中的GCS将会负责维护整个ray cluster的相关服务。

四、代码细节


本章将解读更多代码实践上的重要细节。我们通过图例的方式抽象出代码运行的过程,而具体代码可参考文中给出的相关链接

4.1 训练入口

ppo_ray相关的训练入口在:https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/cli/train_ppo_ray.py


在main中我们启动了driver进程,并执行训练函数train(args),这里主要做了如下几件事:

  • 在ray集群上部署Actor/Ref/Critic/RM实例
  • 在ray集群上部署vllm_engines实例
  • 配置Actor和vllm_engines之间的通讯,用于传递权重
  • 训练Actor和Critic模型

我们依次来解读这几个关键步骤。同时为了在表述上消除歧义,我们接下来谈到“Actor”时,会使用Ray-Actor和PPO-Actor来做区分,从之前的介绍中可知,Ray-Actor是指部署在Ray集群中的远端class,PPO-Actor/Ref/Critic/RM都属于Ray-Actor。

4.2 部署Actor/Ref/Critic/RM实例


(1)非共同部署

针对图2.1的情况,我们以PPO-Actor为例,看代码是如何将其部署到Ray集群上的。

  • PPORayActorGroup创建在driver进程上,可将它理解成一种部署方案,专门负责部署PPO中的4类模型
    • PPORayActorGroup中维护着self._actor_handlers,它是一个List[ray.actor.ActorHandle],列表中每个元素表示某个远端Ray-Actor的引用,而这个远端Ray-Actor可以是PPO-Actor/Ref/Critic/RM实例。如前文所说,我们可以在ray集群中的任何位置调用这个handler,来对相应的远端Ray-Actor执行操作。
    • 在本例中,我们创建了4个Ray-Actor(1个master-actor,3个worker_actor)。每个Ray-Actor都运行在一个worker进程中。在创建Ray-Actor的同时,我们也会去修改worker进程的环境变量。后续当我们在这些worker进程中启动ds_zero相关的分布式配置时,ds会读取这些环境变量信息,这样我们就知道哪些Ray-Actor同时又构成ds中的数据并行组。
    • 使用PPORayActorGroup部署模型实例的代码如下:
model = PPORayActorGroup(
        # 为部署该模型的全部实例,我们想用多少台node,例如本例中为2
        args.actor_num_nodes,
        # 为部署该模型的全部实例,我们每台node上想用多少gpu,例如本例中为2
        args.actor_num_gpus_per_node,
        # Actor/Critic/Reward/ReferenceRayActor
        ActorModelRayActor, 
        # pg可理解为,在ray cluster中锁定/预留一片资源,然后只在这片资源上部署该模型全部实例。
        # (pg维护在Head Node的GCS上,参见3.3)
        # 例如本例中,pg锁定的资源为node0 gpu0/1, node1 gpu0/1,
        # 我们只在上面部署ActorModelRayActor全部实例
        pg=pg,
        # 当我们在pg指向的预留资源中分配模型实例时,再进一步指定每个实例占据一张gpu的多少部分
        # 等于1说明每个实例占满一张gpu,即“非共同部署”
        # 小于1说明每个实例只占部分gpu,即“共同部署”,例如PPO-Actor/Ref共同部署在一张卡上
        num_gpus_per_actor=0.75 if pg else 1,
    )
  • ActorModelRayActor创建在远端worker进程上,是Ray-Actor。它包含了设置ds_zero分布式环境、加载模型权重、数据集准备、optimizer/scheduler准备、训练等一系列操作。

PPORayActorGroup代码参见:https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ray/launcher.py#L143
根据这份代码,大家可自行去找Actor/Critic/Reward/ReferenceRayActor的相关实现。

(2)共同部署

针对图2.2的情况,我们以PPO-Actor为例,看代码是如何将其部署到Ray集群上的。

  • PPORayActorGroup:在driver进程上创建2个PPORayActorGroup,分别管理PPO-Actor,PPO-Ref的部署
  • 使用actor_model = PPORayActorGroup(..., pg = pg, num_gpus_per_actor=0.75)创建PPO-Actor部署方案实例;使用ref_model = PPORayActorGroup(..., pg = pg, num_gpus_per_actor=0.25)创建PPO-Ref部署方案实例
  • 这里,两个方案实例使用的pg都是同一个,即这个pg都指向“1台node,每台node 8张卡”这片预留好的资源。
  • num_gpus_per_actor = 0.75/0.25是一种创建trick,虽然我们的最终目的是为了让PPO-Actor和PPO-Ref对半分一张卡(对半=共享,不是指显存上对半分),但是:
    • 假设设置为0.5,当我们实际部署ActorModelRayActor时,Ray先在单卡上部署1个ActorModelRayActor实例,当它准备部署第二个ActorModelRayActor实例时,它发现由于每个实例只占0.5块卡,因此完全可以把第2个实例接着第1个实例在同一张卡上部署,这样就导致最终无法让PPO-Actor和PPO-Ref共享一张卡
    • 假设设置0.75,当我们在单卡上部署完1个ActorModelRayActor实例后,ray发现单卡剩下的空间不足以部署第2个ActorModelRayActor实例,所以就会把第二个实例部署到别的卡上,这样最终实现PPO-Actor和PPO-Ref共享一张卡
    • 所以,这个设置是为了达到不同类型模型的实例共享一张卡的目的,而并非真正指模型实际占据的单卡显存空间。
  • 最后,在这一步中,我们对全部ActorModelRayActor共创建8个worker进程,对全部RefenreceModelRayActor共创建8个worker进程,一共创建16个工作进程。

相关代码依然在:https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ray/launcher.py#L143

4.3 部署vllm_engines实例

  • create_vllm_engines:在driver端,我们通过运行该函数来创建vllm_engines,过程相似于4.2节中的介绍,信息都在图中,这里不赘述。
  • LLMRayActor:worker端Ray-Actor,它主要是把vllm实例进行了一些包装,包装的目的是为了让ds_rank0和all vllm ranks间可以进行PPO-Actor的权重通讯(参见2.1(3))
  • 在上面的例子中,我们会创建4个worker进程(不占gpu资源,只占cpu资源),用于运行管理4个vllm_engine。在每个worker进程内,vllm实例还会创建属于自己的worker进程做分布式运行(这些worker进程会实际占据gpu资源)。

相关代码参见:
https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ray/vllm_engine.py
https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ray/vllm_worker_wrap.py



4.4 ds_rank0与vllm_ranks之间的通讯

在2.2中,我们说过,PPO-Actor的ds_rank0需要和all_vllm_ranks进行通讯,传递最新的PPO-Actor权重,例如以下ds_rank0要把完整的权重broadcast给16个vllm_ranks:


我们分成如下几步实现这个目标:

(1)创建通信组



Step1:

代码来自:https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ray/ppo_actor.py#L58
这段代码执行在PPO-Actor0(ds_rank0)所在的worker进程中。这个worker进程将通过handler引用,触发远端每个vllm_engine上的init_process_group操作,并将ds_rank0纳入通讯组

        # Create torch group with deepspeed rank 0 and all vllm ranks
        # to update vllm engine's weights after each training stage.
        #
        # Say we have 3 vllm engines and eache of them has 4 GPUs,
        # then the torch group is:
        # [    0,      1, 2, 3, 4,  5, 6, 7, 8,  9, 10, 11, 12]
        # |ds rank 0 |  engine-0  |  engine-1  |   engine-2   |
        #
        # For ZeRO-1/2:
        #   1. Broadcast parameters from rank 0 to all vllm engines
        # For ZeRO-3:
        #   1. AllGather paramters to rank 0
        #   2. Broadcast parameters from rank 0 to all vllm engines
        if self.vllm_engines is not None and torch.distributed.get_rank() == 0:
            ...
            # world_size = num_of_all_vllm_ranks + 1 ds_rank0
            world_size = vllm_num_engines * vllm_tensor_parallel_size + 1
            ...
            # =====================================================================
            # 遍历每个vllm_engines,将其下的每个vllm_rank添加进通讯组中,这里又分成两步:
            # 1. engine.init_process_group.remote(...):
            #    首先,触发远程vllm_engine的init_process_group方法
            # 2. 远程vllm_engine是一个包装过的vllm实例,它的init_process_group
            #    方法将进一步触发这个vllm实例下的各个worker进程(见4.4图例),
            #    最终是在这些worker进程上执行“将每个vllm_rank"添加进ds_rank0通讯组的工作
            # =====================================================================
            refs = [
                engine.init_process_group.remote(
                    # ds_rank0所在node addr
                    master_address, 
                    # ds_rank0所在node port
                    master_port,
                    # 该vllm_engine的第一个rank在"ds_rank0 + all_vllm_ranks“中的global_rank,
                    # 该值将作为一个offset,以该值为起点,可以推算出该vllm_engine中其余vllm_rank的global_rank
                    i * vllm_tensor_parallel_size + 1, 
                    world_size,
                    "openrlhf",
                    backend=backend,
                )
                for i, engine in enumerate(self.vllm_engines)
            ]
            # =====================================================================
            # 将ds_rank0添加进通讯组中
            # =====================================================================
            self._model_update_group = init_process_group(
                backend=backend,
                init_method=f"tcp://{master_address}:{master_port}",
                world_size=world_size,
                rank=0,
                group_name="openrlhf",
            )
            # =====================================================================
            # 确保all_vllm_ranks都已添加进通讯组中
            # =====================================================================
            ray.get(refs)



Step2:

代码来自:https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ray/vllm_worker_wrap.py#L11
这段代码实际运行在每个vllm_engine(即每个包装后的vllm实例)下的worker进程内。例如tp_size=2,那么每个vllm实例下就有2个worker进程,这两个worker进程都会运行这段代码。

class WorkerWrap(Worker):
    def init_process_group(self, master_address, master_port, rank_offset, world_size, group_name, backend="nccl"):
        """Init torch process group for model weights update"""
        assert torch.distributed.is_initialized(), f"default torch process group must be initialized"
        assert group_name != "", f"group name must not be empty"
        # =====================================================================
        # torch.distributed.get_rank(): 在当前vllm_engine内部的rank,
        #                               例如在tp_size = 2时,这个值要么是0,要么是1
        # rank_offset:当前vllm_engine中的第一个rank在“ds_rank0 + all_vllm_ranks"中的global_rank
        # 两者相加:最终得到当前rank在“ds_rank0 + all_vllm_ranks"中的global_rank
        # =====================================================================
        rank = torch.distributed.get_rank() + rank_offset
        self._model_update_group = init_process_group(
            backend=backend,
            init_method=f"tcp://{master_address}:{master_port}",
            world_size=world_size,
            rank=rank,
            group_name=group_name,
        )
        ...




(2)_broadcast_to_vllm

构建好通讯组,我们就可以从ds_rank0广播PPO-Actor权重到all_vllm_ranks上了,这里也分成两步。


Step1:PPO-Actor ds_rank0发送权重

代码在:https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ray/ppo_actor.py#L146
这段代码运行在ds_rank0对应的worker进程中

      def _broadcast_to_vllm(self):
        # avoid OOM
        torch.cuda.empty_cache()
        model = self.actor.model.module
        count, num_params = 0, len(list(model.named_parameters()))
        for name, param in model.named_parameters():
            count += 1  # empty_cache at last param

            # Fire all vllm engines for broadcast
            if torch.distributed.get_rank() == 0:
                shape = param.shape if self.strategy.args.zero_stage != 3 else param.ds_shape
                refs = [
                    # 远端vllm_engine的每个rank上,初始化一个尺寸为shape的empty weight张量,
                    # 用于接收广播而来的权重
                    engine.update_weight.remote(name, dtype=param.dtype, shape=shape, empty_cache=count == num_params)
                    for engine in self.vllm_engines
                ]

            # For ZeRO-3, allgather sharded parameter and broadcast to all vllm engines by rank 0
            # ds_rank0发出权重(视是否使用zero3决定在发出前是否要做all-gather)
            with deepspeed.zero.GatheredParameters([param], enabled=self.strategy.args.zero_stage == 3):
                if torch.distributed.get_rank() == 0:
                    torch.distributed.broadcast(param.data, 0, group=self._model_update_group)
                    ray.get(refs) # 确保所有vllm_ranks接收权重完毕




Step2: 各个vllm_ranks接收权重

代码在:https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ray/vllm_worker_wrap.py#L29
代码运行在每个vllm_engine(即每个包装后的vllm实例)下的各个worker进程中。例如tp_size = 2,那么每个vllm实例下有2个worker进程,这2个worker进程都会运行这段代码。

    def update_weight(self, name, dtype, shape, empty_cache=False):
        """Broadcast weight to all vllm workers from source rank 0 (actor model)"""
        if torch.distributed.get_rank() == 0:
            print(f"update weight: {name}, dtype: {dtype}, shape: {shape}")

        assert dtype == self.model_config.dtype, f"mismatch dtype: src {dtype}, dst {self.model_config.dtype}"
        # 创建同尺寸空张量用于接收ds_rank0广播来的权重
        weight = torch.empty(shape, dtype=dtype, device="cuda")
        # 接收权重
        torch.distributed.broadcast(weight, 0, group=self._model_update_group)
        # 使用接收到的权重进行更新
        self.model_runner.model.load_weights(weights=[(name, weight)])

        del weight




4.5 PPO-Actor/Critic Training


正如2.1(4)中所说,我们将部署在ray集群上的PPO-Actor/Ref/Critic/RM实例们进行分组,每组分别负责一份micro-batch的训练,上图刻画了某个组内的训练流程。一组内的训练流程发起自PPO-Actor实例(fit方法),注意不同颜色的worker0表示的是不同工作进程。共分成如下步骤执行。


Step1:发送prompts,并从vllm_engine上收集(prompt, response)。

代码参见:https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ppo_utils/experience_maker.py#L627



Step2:从Ref/Reward/Critic上收集并处理exps

代码参见:https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ppo_utils/experience_maker.py#L492



Step3: 确保将处理后的exps传送给Critic,并行执行Actor和Critic的训练

将exps传送给Critic:https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ppo_utils/experience_maker.py#L470
Actor训练:https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ray/ppo_actor.py#L125
Critic训练:https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ray/ppo_actor.py#L122
我们在Actor实例所在的worker进程上出发Actor和Critic的训练。以上代码只给出了训练入口,更多细节需要顺着入口去阅读。



Step4:vllm_engine权重更新。

代码参见:https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ray/ppo_actor.py#L130

五、RLHF-PPO算法细节

到目前为止,我们已将Ray分布式训练部分介绍完毕。有了对工程架构的认识,很多朋友可能想进一步探索算法上的细节:例如在4.5图例中,我们收集的是什么样的exps?又是如何对PPO-Actor/Critic进行更新的?

我们知道整个RLHF-PPO训练过程大致分成2步:

  • Stage1:收集exps
  • Stage2:使用收集到的exps计算actor_loss和critic_loss,用于训练actor和critic

在OpenRLHF中的核心代码为:https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ppo_trainer.py#L19

下面我们分别解读这2个stage的过程

5.1 Stage1:collect exps


假设大家已读过上述说的【代码篇】和【理论篇】,那么对这张图的大部分细节应该不难理解。这里我们对优势(假设选择的是GAE)再做一些补充说明


在理论篇8.3(2)中,我们详细介绍过GAE定义和使用它的原因,第t步GAE的定义为:

由上式可知,如果我们想要计算出每个t时刻的GAE,我们需要从头到尾遍历2次序列:第1次计算 δt ;第2次计算 At 。我们期望尽可能减少计算复杂度,仅在1次遍历中同时计算出 δt,At ,那么我们可以按如下步骤改写

在OpenRLHF中,还设计到“advantage norm”,“reward clip & norm”等细节操作,篇幅限制不展开,大家可以自行阅读。

核心代码在:https://github.com/OpenRLHF/OpenRLHF/blob/bb46342711a203c457df2fbca5967fd0549557e0/openrlhf/trainer/ppo_utils/experience_maker.py

5.1 Stage2:Training

大家可以对照理论篇8.5和8.6节对actor_loss和critic_loss的介绍来理解上面的流程图~另外,ybq佬写过一篇对OpenRLHF中的loss分析,也可以对照阅读:ybq:OpenRLHF学习笔记-loss篇

六、参考

1、OpenRLHF
2、Ray official document
3、Ray official architecture whitepaper(要梯子,建议想看ray架构的朋友,直接看这个最新的官方白皮书,不要看2018年的那篇paper了,那个比较老了)
4、推荐一篇快速了解Ray 应用层核心概念的blog(要梯子)
5、Ray
6、vllm

ThinkSound:多模态大语言模型中的链式思维推理,用于音频生成与编辑

ThinkSound 是一个统一的 Any2Audio 生成框架,通过链式思维(Chain-of-Thought, CoT)推理进行流匹配指导

基于 PyTorch 的多模态音频生成与编辑实现:可基于视频、文本、音频及其组合,生成或编辑音频,底层由多模态大语言模型(MLLMs)逐步推理驱动。

主要特性

  • Any2Audio:支持任意模态(视频、文本、音频或其组合)生成音频。
  • 视频转音频 SOTA:在多个 V2A 基准上取得最新最优结果。
  • CoT 驱动推理:基于链式思维推理,实现可组合、可控的音频生成。
  • 交互式面向对象编辑:通过点击视觉对象或文本指令,细化或编辑特定声音事件。
  • 统一框架:单一基础模型,支持生成、编辑与交互式工作流。

Abstract

ThinkSound 将音频生成与编辑分为三个交互式阶段均由基于 MLLM链式思维(CoT)推理指导

  1. 拟音生成(Foley Generation): 从视频生成基础、语义与时序对齐的声景。
  2. 面向对象的细化: 通过点击或选择视频中的对象区域,对用户指定对象的声音进行细化或添加。
  3. 定向音频编辑: 使用高级自然语言指令对生成音频进行修改。

在每个阶段,一个多模态大语言模型都会生成与上下文相符的 CoT 推理内容,用以指导统一的音频基础模型。此外,我们还引入了 AudioCoT,一个包含结构化推理标注的综合数据集,用于建立视觉内容、文本描述与声音合成之间的联系。

带有链式思维(CoT)的 ThinkSound:(1) 由 CoT 驱动的拟音合成,捕捉语义与时间细节;(2) 面向对象的交互式精细化处理,实现用户控制;(3) 有针对性的音频编辑。

为视频生成真实的声音不仅仅是识别物体,它还需要对复杂的视觉动态和上下文进行推理,比如判断一只猫头鹰是在鸣叫还是在拍打翅膀,识别树枝轻微的摆动,并在一个场景中同步多个声音事件。

ThinkSound——在技术上,提出了三个关键创新:

  • a) 对 MLLM 进行 AudioCoT 微调,使其能生成结构化、面向音频的推理链,明确捕捉时间依赖关系、声学属性与复杂音频事件的分解过程;
  • b) 设计了一个基于 flow matching 的统一音频基础模型,支持所有三个阶段,能够从任意组合的输入模态(视频、文本、音频)中合成高保真音频。该模型直接受益于 MLLM 提供的细致 CoT 推理,将复杂音频场景分解为可控组件,在保证整体连贯性的同时实现重点声音事件的精确合成;
  • c) 引入了一个新颖的基于点击的交互界面,使用户能够选择特定视觉对象进行音频精修,CoT 推理机制将视觉关注转化为语境合理的声音合成过程。

AudioCoT Dataset for CoT-Guided Generation and Editing

Multimodal Data Sources

AudioCoT 数据集包含视频-音频和音频-文本对。对于视频-音频数据,我们利用 VGGSound 和 AudioSet中精选的非语音子集,以确保广泛覆盖现实世界的视听事件。对于音频-文本数据,我们聚合了来自 AudioSet-SL 、Freesound 、AudioCaps和 BBC Sound Effects数据对,从而构建了一个用于训练多模态模型的多样化且具有代表性的语料库。

首先移除静默的音频-视频片段,仅保留含有效内容的素材。针对AudioSet子集,根据标签信息进一步剔除了含有人声的片段,以专注于非语音音频。随后将所有音视频片段统一分割为9.1秒的固定时长,舍弃较短片段以保证数据 uniformity(统一性)。为实现数据平衡,保持音乐与音效样本的比例约为1:1,确保两类别的均衡表征。

自动化 CoT 生成流程:

 AudioCoT 数据集构建流程概览

第一阶段:基础拟音思维链生成

  • 视频-音频对处理
    1. 使用VideoLLaMA2通过差异化提示策略提取视频的时序与语义信息
    2. 结合Qwen2-Audio生成音频描述
    3. 将视频描述与音频描述通过GPT-4.1-nano整合为完整思维链
  • 纯音频-文本对处理
    采用简化流程(无需VideoLLA2),直接生成音频描述后与现有文本标注整合
    该阶段生成的思维链能捕捉内容条件与对应音频元素的复杂关联,确保两类数据共同促进音频生成推理的全面理解

第二阶段:交互式对象中心思维链生成
为实现对象聚焦的音频生成,开发基于Grounded SAM2的ROI提取框架:

  1. 对象定位:以音频描述为提示,生成潜在发声物体的边界框
  2. 时序追踪:跨视频帧持续跟踪坐标变化
  3. 语义增强:VideoLLA2为每个ROI片段提供详细语义描述
  4. 复杂操作处理
    • 建立分层推理结构,合并目标视频CoT 与参考视频的思维链CoT 以构建全局上下文
    • 结合ROI特定生成信息,通过GPT-4.1-nano生成连贯的操作逻辑

第三阶段:基于指令的音频编辑思维链生成
针对指令引导的音频编辑任务,基于四类核心(扩展、修复、添加和移除)分析并整合来自第一阶段的 CoT 信息。这些操作涵盖从扩展序列到删除不需要的片段的各种场景。GPT-4.1-nano 处理这些整合的信息,生成特定于指令的 CoT 推理链,同时执行相应的音频操作,创建(指令-CoT、输入音频、输出音频)三元组,用于模型训练和评估。

ThinkSound

 ThinkSound 架构概览。 左图: 我们的多模态 LLM 框架,其中经过微调的 VideoLLaMA 2 模型生成用于音频生成和编辑的 CoT 推理。 右图: 我们增强型多模态 Transformer 架构,该架构以 MM-DiT 为骨干,具有用于处理多模态输入的专用路径和 CoT 驱动的条件反射,从而实现高保真、基于情境的音频生成。

Overview

ThinkSound 引入了一个新颖的分步式交互式音频生成和编辑框架,该框架由 CoT 推理引导。我们的方法将复杂的 V2A 任务分解为三个直观的阶段:(1) 基础拟音生成,创建语义和时间匹配的音景;(2) 通过用户点击进行基于区域的交互式细化;以及 (3) 基于高级指令的定向音频编辑。在每个阶段,MLLM 都会生成 CoT 推理,引导统一的音频基础模型制作和细化音轨。

使用多模态 LLM 进行 CoT 推理

为了实现分步式、情境感知的音频生成,我们利用 VideoLLaMA2 作为核心多模态推理引擎。VideoLLaMA2 之所以被选中,是因为其在融合视频、文本和音频模态方面拥有领先的能力,而其先进的时空建模对于捕捉视觉事件与其对应听觉表现之间的微妙相互作用至关重要。

通过AudioCoT数据集对VideoLLA2进行微调,使其适配音频推理领域。该数据集包含专为视听任务定制的丰富标注推理链,通过微调过程使模型具备三大核心能力:

(1)音频中心化理解能力

  • 声学特性推断(如材料属性、空间混响等)
  • 声音传播建模
  • 视听对应关系推理(包括音频事件间的时序与因果关系,例如”脚步声先于开门声,随后出现对话声”)

(2)结构化思维链分解能力
将复杂音频生成/编辑任务拆解为明确可执行的步骤序列

(3)多模态指令跟随能力
可靠地解析并执行跨模态的多样化生成/编辑指令

如图3所示,微调目标采用标准的下一个token预测交叉熵损失。通过这种针对性适配,VideoLLA2被转化为专用音频推理模块,能够生成上下文精确的思维链指令,驱动ThinkSound流程的每个阶段。

CoT 引导的统一音频基础模型

ThinkSound 的核心是我们统一的音频基础模型,它能将 CoT 推理无缝转换为高质量音频,具体细节见图 3 右侧部分。使用预训练的 VAE将音频编码为潜在表示,并采用条件流匹配对模型进行训练,其中速度场预测以多模态上下文为条件,包括视觉内容、CoT 推理、文本描述和音频上下文。为了支持任意组合的输入模态,我们在训练过程中引入了无分类器引导的随机丢弃方法。通过以概率 p_drop 随机丢弃不同模态的组合,使模型在推理阶段能够处理任意输入配置——这对于我们的交互式框架至关重要。我们还结合了策略性音频上下文遮蔽,以支持音频修补和扩展等高级编辑操作

在文本处理方面,我们采用了双通道编码策略:MetaCLIP对视觉字幕进行编码,提供场景级上下文;而 T5-v1-xl则处理结构化的 CoT 推理,以捕捉详细的时间和因果关系。这两种互补的表示被有效融合,MetaCLIP 的特征作为全局条件信号,而 T5 的输出则支持基于推理的精细控制。

我们改进的 MM-DiT 架构基于多模态生成建模领域的最新进展,包含三大关键组件:(1)采用混合型 Transformer 主干网络,在模态专用与共享处理之间交替进行。多流 Transformer 块为每个模态维护独立参数,同时共享注意力机制,从而高效处理多样输入,同时兼顾跨模态学习。(2)设计了自适应融合模块,通过门控机制对视频特征进行上采样并与音频潜变量融合。这不仅能够突出显著的视觉线索、抑制无关信息,还确保视频信息直接参与后续的单流 Transformer 块。通过将视频整合到音频潜变量空间,模型可以更好地捕捉细微视觉细节及其对声景的微妙影响,实现比仅依赖音频潜变量更丰富的跨模态推理。(3)通过对字幕和视频的 CLIP 特征进行均值池化,实现全局条件控制,并借鉴 MMAudio,引入同步特征以提升音视频时间对齐效果。最终得到的全局条件被添加到时间步嵌入中,并通过自适应层归一化(AdaLN)注入多流与单流块。

 逐步 CoT 引导的音频生成和编辑

通过支持输入模式与 CoT 的灵活组合,ThinkSound 支持将音频生成分解为图 1 所示的三个直观阶段。该三阶段流程通过直观的交互式工作流程,实现了逐步精细化、高度定制化的音频生成,CoT 推理在每个步骤中将用户意图与音频合成连接起来。

阶段 1:基于 CoT 的拟音生成
在第一阶段,系统分析整段视频以识别声学要素及其关系。经过微调的 MLLM 生成详细的 CoT 推理,明确识别主要声事件、环境元素、声学属性以及它们的时间依赖关系——确定物体何时发声及声音间的相互作用。这种结构化推理指导音频基础模型生成高保真音频,精准匹配视觉场景的语义内容与时间动态。借助 CoT 推理将复杂音频场景拆解为显式声源,模型能够生成多样且连贯的声景,捕捉微妙视觉线索与运动动态,实现逼真的音频合成。

阶段 2:交互式对象聚焦音频生成
第二阶段引入交互框架,让用户通过关注特定视觉元素来优化初步声景。借助简单的点击界面,用户可以选择感兴趣的物体进行音频强化。不同于第一阶段的整体生成方式,此阶段采用基于目标区域(ROI)的局部细化,利用分割出的目标区域指导定向音频合成。经过微调的 MLLM 针对所选 ROI 生成专门的 CoT 推理,关注该物体在全局背景下的声学特性。模型在这些结构化推理引导下生成物体专属声音,与第一阶段生成的音轨自然融合。值得注意的是,此阶段的基础模型将已有音频上下文作为附加条件信号纳入考虑。

阶段 3:基于指令的音频编辑
在最后阶段,用户可通过高层次的编辑指令来优化音质或修改特定元素。MLLM 将自然语言指令转译为具体的音频处理操作,利用 CoT 推理综合视觉内容和当前音频状态。基础模型在此推理及现有音频上下文条件下执行定向修改,同时保持整体连贯性。通过自然语言控制,非专业用户也可以完成复杂的音频操作,包括添加声音、移除声音、音频修补以及音频延展。

Results

虽然目前的 MLLM 模型能够很好地理解和推理语义信息,但它们在理解视频的精确时间和空间信息方面仍然存在局限性。例如,在定位声音事件的精确时间戳时,MLLM 模型经常无法提供准确的结果或给出错误的结果。此外,目前用于音频生成的开源视音频数据集在多样性和覆盖范围方面存在局限性,可能缺少稀有或特定文化的声音事件。未来,我们将继续探索更加多样化和全面的数据集,以提升模型的性能。此外,我们还将探索更有效的方法来提升生成音频的时间和空间对齐效果。

Ke-Omni-R :通过思考实现高级音频推理

Github:https://github.com/shuaijiang/Ke-Omni-R 【开源训练和推理代码】

贡献:用于将GRPO/思考过程 加入到语音大模型的强化训练过程中。

  • [1] Xie, Zhifei, et al. “Audio-Reasoner: Improving Reasoning Capability in Large Audio Language Models.” arXiv preprint arXiv:2503.02318.
  • [2] Ma, Ziyang, et al. “Audio-CoT: Exploring Chain-of-Thought Reasoning in Large Audio Language Model.” arXiv preprint arXiv:2501.07246.
  • [3] Li, Gang, et al. “Reinforcement Learning Outperforms Supervised Fine-Tuning: A Case Study on Audio Question Answering.” arXiv preprint arXiv:2503.11197
  • [4] Xu, Jin, et al. “Qwen2.5-Omni Technical Report.” arXiv preprint arXiv:2503.20215

Ke-Omni-R 是基于 Qwen2.5-Omni 构建的高级音频推理模型。构建音频推理模型,通过强化学习引入深度思考过程,提升复杂任务的理解和推理能力。仅使用 10,000 个训练后样本,Ke-Omni-R 就在 MMAU Test-mini 和 Test 基准测试中取得了最佳性能。其开发过程中的关键洞察包括:

  • GRPO 算法 :GRPO 算法显著增强了已经很强大的基础模型(Qwen2.5-Omni-7B)的性能,即使在看不见的语音领域也表现出卓越的泛化能力。
  • 思考过程 :融入简洁的思考过程(少于 50 个字)对于提高推理能力起着至关重要的作用。
  • KL 散度 :通过利用 KL 散度,在 GRPO 训练期间观察到轻微的改进。
  • 领域比例 vs. 数据量 :领域多样性比数据量更重要。我们仅使用了 10,000 个样本,其中 5,000 个从 AVQA 中随机选取,另外 5,000 个从 MusicBench 中选取。

Performance: Accuracies (%)↑ on MMAU Test-mini and Test benchmark

ModelMethodSound (Test-mini)Sound (Test)Music (Test-mini)Music (Test)Speech (Test-mini)Speech (Test)Average (Test-mini)Average (Test)
Human*86.3178.2282.1782.23
Gemini Pro 2.0 FlashDirect Inference*56.4661.7358.6856.5351.6561.5355.6059.93
Audio Flamingo 2Direct Inference*61.5665.1073.9572.9030.9340.2655.4859.42
GPT4o + Strong Cap.Direct Inference*57.3555.8349.7051.7364.8668.6657.3058.74
Llama-3-8B-Instruct + Strong Cap.Direct Inference*50.7549.1048.9348.9355.2562.7052.1053.57
Qwen2-Audio-7B-InstructDirect Inference*54.9545.9050.9853.2642.0445.9049.2052.50
SALAMONNDirect Inference*41.0040.3034.8033.7625.5024.2433.7032.77
Audio-Reasoner(Qwen2-Audio-7B-Instruct)[1]60.0664.3060.7061.71
Audio-Cot(Qwen2-Audio-7B-Instruct)[2]61.8656.2955.2657.80
R1-AQA(Qwen2-Audio-7B-Instruct)[3]68.7769.7664.3761.4063.6662.7065.6064.36
Qwen2.5-Omni-7B[4]67.8769.1659.7665.60
Qwen2.5-Omni-3B[4]70.2760.4859.1663.30
Ke-Omni-R-3B(Qwen2.5-Omni-3B)GRPO w/ think (ours)72.3771.8765.5759.6064.2664.1767.4065.17
Ke-Omni-R(Qwen2.5-Omni-7B)GRPO w/o think (ours)69.6770.5767.6664.0066.3767.1767.9067.24
Ke-Omni-R(Qwen2.5-Omni-7B)GRPO w/ think (ours)69.3771.9069.4667.1367.8767.1068.9068.71

Performance: CER/WER (%)↓ on ASR benchmark

ModelMethodWenetSpeech test-netWenetSpeech test-meetingLibriSpeech test-cleanLibriSpeech test-other
Qwen2.5-Omni-3B[4]6.38.12.24.5
Qwen2.5-Omni-7B[4]5.97.71.83.4
Ke-Omni-3Bours11.716.11.83.8
Ke-Omni-7Bours7.59.81.63.1

DPO为什么会让大语言模型输出变长

摘自:https://zhuanlan.zhihu.com/p/5830338806

总的来说,DPO让模型输出变长主要可以分为以下几个原因:

  1. RM和模型评测的长度偏好。不管是Reward Model还是当前用与评测的模型(即便是GPT4)都会存在比较明显的长度偏好,即倾向于给更长的回答一个更高的分数。这一点已经有非常多工作给出过分析了。
  2. 训练数据本身长度分布不均衡。实战过程中往往就是用RM进行排序构造训练数据,RM的长度偏好就是会导致训练数据中容易出现chosen比rejected更长的情况。训练数据的长度差异(chosen比rejected长)就会导致训练后模型输出变长。
  3. 数据长度差异导致的reward被高估或低估。《Eliminating Biased Length Reliance of Direct Preference Optimization via Down-Sampled KL Divergence》中发现,DPO的算法本身也存在对response长度的依赖,chosen和rejected之间的长度差异可能会导致reward被高估/低估(overestimated or underestimated rewards)。即,当chosen过短时,reward会被低估,而当chosen过长时,reward会被高估
  4. DPO算法本身的长度敏感性。《Length Desensitization in Direct Preference Optimization》中提到,response长度会影响到似然概率的大小,并且进一步影响到训练优化方向:当chosen更长时,DPO会往chosen的方向进行优化(增大chosen概率),从而使输出变长;而rejected更长时,DPO会往远离rejected的方向优化(降低rejected概率),但却未必会让输出变短。

如何解决:

  1. RM的优化:前面讲的都是对DPO进行长度控制的工作,但对RM本身的长度偏好进行优化的工作没有看到太多,如果大家有看到相关的也可以在评论区提供一下。如果将RM本身的长度偏好问题解决的话,那就可以极大程度上解决训练数据的长度分布均衡问题了。
  2. 数据的优化:有些工作会在数据构造时对长度进行综合考虑,如对RM打分进行长度归一后再排序、采样多个答案进行排序时根据均值方差限制chosen的长度等,通过这些方式可以减少长度差距过大的情况。如果数据本身的长度分布均衡了,也能一定程度上减缓这种问题。
  3. 训练算法上的优化:如果从LD-DPO的分析上看,即便数据分布比较均衡,只要存在长度差异,DPO本身的长度敏感性就是会导致模型输出变长,因此可能还是需要一些算法层面的优化,比如在DPO阶段加入SFTloss就是一种简单有效的方法,在很多公开的大模型技术报告中也都有用到该方法。另外R-DPO、SamPO和LD-DPO的长度控制效果都算是比较好的方法。

DPO面临的一个问题(准确来讲是一种现象)就是会让大模型的输出变长,且多轮DPO的话会让模型输出越来越长。本篇文章我们将结合搜集到的一些相关工作,探讨一下业界对该现象的一些分析,探究这一现象产生的根本原因,以及如何有效地解决。

首先我们需要思考一个问题,模型输出变长到底是不是一件坏事?一般来说,输出变长可能会使内容更加详细,信息量更丰富,回复质量更高,用户体验更好。但如果过度长,输出了很多冗余信息,回复质量没有明显改善,反而带来了推理成本的增加,回复变得啰嗦,用户体验反而变差了。

因此,无论是从用户体验的角度还是多轮DPO能否run下去的角度,做好长度控制都是有必要的。

相关工作

先简要介绍一些相关工作,然后后面详细总结。

1.《Disentangling Length from Quality in Direct Preference Optimization》(简称R-DPO)

在这之前的一些RL的工作也有分析过长度爆炸问题,但该文章可能是第一个提出DPO的长度爆炸问题的。

文章中发现,无论是RL训练中使用的Reward Model还是用来评测模型效果的打分模型(如GPT-4)都表现出明显的长度偏好,即会给更长的答案一个更高的分数(如下图)。且在一些公开的DPO训练数据集中,chosen的长度往往会比rejected更长,而这可能就是DPO后的模型输出长度明显比SFT模型更长的原因

为了解决这个问题,该文章提出了一种长度正则化的策略,即在计算loss的时候添加一个长度正则项,从而避免模型对长度的过度拟合,公式如下:

其中 |yw| 表示chosen的长度, |yl| 表示rejected的长度,从公式中可以看出,当chosen与rejected的长度差距越大,正则项的值越大,从而实现对长度的“惩罚”效果。

从文章中的实验结果可以看出,该方法确实可以在尽可能减少性能损失的前提下有效解决长度增长问题。(有时还是会损失一定的性能。)

2.《SimPO: Simple Preference Optimization with a Reference-Free Reward》(简称SimPO)

陈丹琦团队的工作,直接去掉了reference model,用长度归一的方式实现长度控制。其loss如下:

文章中提到了很多输出长度相关的内容,但核心贡献并不是做长度控制,而是用一种更简单高效的方法实现偏好训练。从公式上看,和原始DPOloss相比主要有两处不同,一个是分母从reference model的logp替换成了长度,另外就是增加了一个 γ ,类似一个offset的作用。不过其中对chosen和rejected的reward做长度归一的部分,直觉上看起来应该是能起到一定的长度控制效果的。

不过从论文中的实验结果看,该方法的效果还是比较好的(当时声称训出最强8B大模型),但与标准DPO相比似乎并没有实现长度控制的效果。

3.《Eliminating Biased Length Reliance of Direct Preference Optimization via Down-Sampled KL Divergence》(简称SamPO

这篇论文对DPO后长度变长的问题进行了一定的分析,提出的一个核心观点是:DPO的算法本身也存在对response长度的依赖,chosen和rejected之间的长度差异可能会导致reward被高估/低估(overestimated or underestimated rewards)。即,当chosen过短时,reward会被低估,而当chosen过长时,reward会被高估。

这篇工作中提出的一种方式就是在token级别下采样的概率特征,以计算正则化的KL散度,从而减少因pair长度不同而导致的奖励偏差。其loss的计算如下:

从公式可以看出,该方法的核心就是在计算reward的时候不再是全部token的条件概率的累乘(取log后就是累加),而是随机采样公共数量的token进行累乘。这样即便chosen和rejected长度不同,参与reward计算的token数是一样的。也就是说,在SamPO训练过程中,魔都看到的chosen和rejected相当于是完全等长的。

从文章中的实验结果看,该方法确实能有效控制模型输出长度的增长,甚至在多轮DPO依然能有效控制长度。但是在性能上看依然做不到碾压标准DPO的效果。

但该方法有两个风险便是:

  1. 本身DPO就存在一定的波动,随机下采样可能会导致训练的稳定性不强;
  2. 随机采样必然会导致一些信息缺失,如果采样时舍弃掉了一些非常重要的token可能会影响到训练效果。

4.《Length Desensitization in Direct Preference Optimization》(简称LD-DPO)

该论文可能是第一个从理论层面分析DPO后模型输出变长的原因的,其核心分析主要包括两方面:

  1. DPO的梯度优化方向和chosen/rejected的似然概率成反比。
  2. Response长度对似然概率的影响极大,因此长度会直接影响reward的计算,并影响到DPO的优化方向。

上图是一个对训练数据的统计热力图,图中,横坐标为chosen的长度,纵坐标为rejected的长度,颜色深度表示 log⁡πθ(yl|x)−log⁡πθ(yw|x) 值的大小。第一张图(a)是标准DPO,可以看出长度差距越大时,颜色越深,也就说明长度差距可能会导致reward计算产生bias,且长度差距越大这种bias越大。而这种bias会进一步影响到DPO的优化方向,使其往输出更长的方向进行优化。

该文章提出的解决方案是在计算似然概率时对长度进行解耦,将更长的答案拆成“公共长度部分”和“额外部分”,并进一步将后者拆分为真实偏好和冗余偏好,并对其中的冗余部分进行降权操作,通过一系列推导后将 πθ(y|x) 转化为如下的形式(可近似理解为完整似然概率部分与公共长度部分似然概率的加权和):

从公式上看,这种方式可以让长度更长的那个response(不管是chosen还是rejected)实现一定的缩放(),减少长度带来的似然概率的断崖式下滑,使其与另一个短response(不受影响)之间更具可比性,同时又不会像SamPO那样完全舍弃掉额外部分的信息。

从论文中的实验结果看,这种方法能够实现比较好的长度控制,且模型性能还能有一定提升,并且可以通过调整参数 α 可以实现不同程度的控制效果。另外文章还提出一个比较有意思的发现,就是过度冗余的回答可能反而会损害模型的推理能力,他们通过这种方法控制长度后,模型的推理能力也有明显提升。

其他工作

除此之外,还有一些工作直接在数据上做文章,通过控制chosen和rejected的长度差距来实现长度控制,如《Following Length Constraints in Instructions》(简称LIFT-DPO)。以及在一些开源模型的技术报告中我们也能看到一些相关的长度控制方法,如在利用RM打分排序时就综合考虑长度问题等,这些数据工作就不再详细展开了。

如何实现有效的长度控制?

  1. RM的优化:前面讲的都是对DPO进行长度控制的工作,但对RM本身的长度偏好进行优化的工作没有看到太多,如果大家有看到相关的也可以在评论区提供一下。如果将RM本身的长度偏好问题解决的话,那就可以极大程度上解决训练数据的长度分布均衡问题了。
  2. 数据的优化:有些工作会在数据构造时对长度进行综合考虑,如对RM打分进行长度归一后再排序、采样多个答案进行排序时根据均值方差限制chosen的长度等,通过这些方式可以减少长度差距过大的情况。如果数据本身的长度分布均衡了,也能一定程度上减缓这种问题。
  3. 训练算法上的优化:如果从LD-DPO的分析上看,即便数据分布比较均衡,只要存在长度差异,DPO本身的长度敏感性就是会导致模型输出变长,因此可能还是需要一些算法层面的优化,比如在DPO阶段加入SFTloss就是一种简单有效的方法,在很多公开的大模型技术报告中也都有用到该方法。另外R-DPO、SamPO和LD-DPO的长度控制效果都算是比较好的方法。

最后结合我自己的一些尝试来直接对比一下上面的四种方法:

  1. R-DPO是通过加正则项的方式实现长度控制,说是正则项,但其实只是一个常数,其原理相当于是对每条数据加上一个权重(文章中也提到了这点),即当chosen和rejected长度差距大时降低该数据的权重。也就是说,该方法其实是让模型减少对长度差距大的数据的学习权重。这种方法确实可以实现一定的长度控制效果,但必然会减少一些数据的利用率,这可能也是训练效果会有一定损失的原因。我自己尝试了一下该方案,实验下来确实可以做到长度控制效果,但大部分情况下性能都会比标准DPO差一些。
  2. SimPO是用长度归一来替换Reference Model的KL约束,理论上和长度控制其实没有太大关系,更多的是简化训练和提升性能。实验结果确实也体现了并不会比标准DPO更短。(该方法热度很高,但网络上褒贬不一,很多人表示无法复现结果。)根据我自己实验经验来看,跑出好的结果需要仔细调参,论文推荐的超参不一定适合所有情况。
  3. SamPO是直接用下采样的方式,强行将模型视角下的长答案变得和短答案一样长,该方法给人的直观感受就是长度控制效果肯定很好,但是很可能会有性能损失。但我自己实验下来,长度控制效果和R-DPO差不多,但是性能也比较不稳定,更换随机种子就会导致性能产生波动。我也尝试过将随机下采样改为top-k采样,即保留概率最大的top-k个token,但效果并不会比随机更好(这么直觉的方法可能论文作者也尝试过了)。
  4. LD-DPO的方法是只对答案过长的部分做了解耦和降权处理,通过降低过长部分的权重来实现整个条件概率的缩放,看起来是四种方法中实现最优雅的一种,既降低了长度差异带来的reward bias问题,又不会丢弃信息,相当于是用极小的代价实现了概率缩放目的。从论文中贴出的结果看,确实也是性能最强的一个,长度控制效果也是最好的。但论文代码没有开源,所以没有实验验证。但从公式上看复现难度应该不是很大,有能力的可以尝试复现一下看看效果。