做低资源神经机器翻译的同行大概率都经历过这种拧巴语料只有一二十万句对怎么切词表都感觉稀疏训练好不容易收敛推到线上又发现解码速度一言难尽——自回归模型一个一个往外蹦 token长句来了要等半天。我最近把一个基于投机解码的双协同低资源神经机器翻译系统从设计到落地完整跑了一遍效果比我预想的要顺。这套系统的核心就三件事投机解码Speculative Decoding负责推理阶段的小模型草拟、大模型验稿双协同则拆成训练阶段的正反向对偶重建和推理阶段的草稿-验证协同。简单说就是一边用对偶学习把稀缺的平行语料榨得更干净一边用投机解码把大模型的推理延迟降下来。这篇文章会比较啰嗦因为我想把动机、原理、工程细节以及踩过的坑都讲透正在做低资源 MT 落地或者想给生成模型推理加速的朋友应该能直接抄走不少东西。1. 这套系统要同时解决的两个老问题低资源翻译又缺数据又慢1.1 先处理缺低资源语料的常见补法低资源翻译最直观的问题就是缺。平行语料少模型学不到足够丰富的搭配、句式变换和领域表达结果就是译文生硬、漏翻、重复。业内常用的补救手段我简单梳理过回译back-translation用目标语言单语语料造 pseudo 平行句对覆盖面广但噪声大对偶学习dual learning把正向和反向翻译当作一组互逆任务互相监督不需要额外外部平行语料多语种预训练则希望借助相近语言的共性来补低资源语言的稀疏表示。这些方法解决的问题高度集中在怎么把数据用得更狠。但它们都没有正面回答一个问题假设模型质量终于被喂到能上线了这种模型通常不大可自回归解码照样慢线上延迟照样难看。低资源场景往往配套低算力部署环境这个矛盾会更刺眼。1.2 再说慢自回归解码在低资源场景更难受自回归解码的慢来自本质生成第 t 个 token 必须等前 t-1 个 token 全部确定每一步都是一次完整的前向计算。如果模型靠 beam search 维护多个候选序列那每一步实际要做的是多个候选 × 序列长度量级的前向计算量还要再翻几倍。低资源任务里模型本身容量小、上下文表征能力弱遇到长句和罕见词时模型很容易在某个低置信区间反复打转表现为解码停顿、重复循环用户体感就是卡死了。更麻烦的是低资源模型的隐状态容量有限beam search 宽度稍微加大内存和耗时同时上涨宽度不变译文质量又不够。我的结论是低资源 MT 的慢不是单纯工程优化问题它跟缺绑在一起——模型不够强所以需要更大的搜索空间来补救质量而更大的搜索空间又进一步拖慢速度。这两个问题必须放进同一个框架里解决。1.3 把两个问题合进一个框架的原因如果只做数据增强模型质量上去了但推理还是慢如果只做蒸馏、剪枝速度快了但低资源下本就脆弱的翻译能力会被进一步稀释。投机解码提供了一条不同路线小模型承担大部分解码步大模型集中精力做校验语义底线始终握在大模型手里。这跟低资源场景下另一个经典技巧——对偶学习——恰好能拼接起来对偶训练本身就要求系统中存在正向翻译和反向翻译两套能力而投机解码又需要小模型草稿 大模型验证两个角色。这两套结构在同一个系统里互相嵌套就是我标题里双协同的完整含义。下面先把投机解码这边的原理说清楚。2. 投机解码的运转方式小模型草拟K个token大模型一次验完2.1 实习生写稿、专家批改的直觉类比投机解码可以这样理解实习生先把一段译文草稿一口气写出来专家拿到草稿后从头到尾快速扫一遍只在明显不对的地方改如果草稿质量足够高专家只需要在最后补一个 token 就算完事。对应到模型上实习生就是一个小而快的草稿模型draft model专家就是容量更大、质量更好的目标模型target model。传统自回归的目标模型要生成 N 个 token就得串行前向 N 次。投机解码的做法是让草稿模型先自回归生成 K 个候选 token目标模型把这 K 个 token 连同已有上下文一起喂进去只做一次前向就能拿到这 K 个位置上完整词表分布然后逐个位置决定接受还是拒绝草稿 token被拒绝的位置就用目标模型的修正分布重新采样。这样目标模型一次前向解决了原本需要串行 K 次才能解决的问题而草稿模型的 K 次自回归因为模型小、单步成本低整体耗时依然远小于大模型串行 K 步。2.2 验证与拒绝采样的无偏性保障很多人第一次看到投机解码会担心草稿模型质量不行验证后改来改去输出分布是不是就被带偏了答案是不会。拒绝位置的修正采样方式有数学保证如果目标模型在某个 token 上的概率为 p草稿模型在同一个 token 上的概率为 q那么当随机数满足接受条件时保留草稿 token不满足时则从 max(0, p-q) 归一化后的分布里重新采样最终得到的边缘分布恰好等于目标模型的原始分布 p。换句话说投机解码不会改变目标模型的输出分布它只是把目标模型自回归 N 步重排成了草稿模型自回归 K 步 目标模型并行验证一次。这个性质放在低资源 MT 里非常关键草稿模型再弱也不会把翻译质量的上限拖下去。质量底线始终由目标模型决定。我之前看过一些项目盲目相信蒸馏小模型代替大模型结果翻译在长尾表达上肉眼可见地退化投机解码没有这个风险加速和保质量可以同时成立。2.3 草稿长度K和接受率α对加速比的影响投机解码的收益主要看两个参数草稿长度 K 和接受率 α被采纳的草稿 token 数占比。期望一次投机迭代能产出的 token 数大约是E K × α 1这里的加 1 来自两种情况如果草稿全部被接受目标模型必须额外采样一个 token 作为无偏性收尾如果中途有拒绝目标模型在被拒位置采样出的那个 token 也算一个产出。假设草稿模型单步前向耗时是目标模型的 γ 倍通常 0.1~0.3那么一次投机迭代的总成本约是 K×γ 1加速比近似为Speedup ≈ (K × α 1) / (K × γ 1)举个例子K5α0.7γ0.15那么 E4.5成本1.75加速比约 2.57。如果 α 掉到 0.5K5 时 E3.5成本仍是 1.75加速比只有 2.0。所以优化投机解码的核心目标不是无限拉大 K而是尽量把 α 做高。这也是后面章节里反复出现温度对齐蒸馏这些操作的原因——它们都在为提高 α 服务。3. 双协同的第一重正反向模型通过重建损失互相校准3.1 双协同到底指哪两层为了避免双协同这个词变成玄学我这里给出一个能直接落地的实例化系统里同时维护正向源语言→目标语言和反向目标语言→源语言两条翻译链路每条链路内部又由轻量草稿模型和容量更大的目标模型组成。训练阶段正反两个方向通过重建损失互相约束这是第一重协同推理阶段每个方向内部做草稿-验证式投机解码这是第二重协同。两重协同不是各自独立第一重协同让两个方向的模型都变得更懂对方语言第二重协同则在这两个更强模型的基础上进一步压低延迟。这种设计的工程动机是低资源 MT 本来就常做回译或对偶训练系统里天然存在正向和反向两套模型投机解码又要求每个方向有一快一慢两个角色。把两个需求合并就不再需要额外训练一套专门用于加速的辅助模型草稿模型本身就是对偶训练框架的一部分。3.2 对偶重建损失的具体设计第一重协同的核心是对偶重建。以正向为例给定源语言句子 x正向目标模型先生成译文 y反向模型再把 y 翻译回 x理想情况下 x 应该逼近真实的 x。反过来反向生成的过程也要求正向模型能把它圆回去。训练损失由三部分组成正向的标准交叉熵、反向的标准交叉熵、以及两个方向的重建交叉熵。重建梯度是否回传到生成模型工程上有两种做法。一种是当数据增强用把生成出的 pseudo 序列视为常量不回传梯度只把重建后的样本加入训练集另一种是用 Gumbel-Softmax 之类的松弛方法让整个闭环可微。我实际倾向于前者简单稳定低资源场景下效果足够好。下面是一个训练循环的简化伪代码for batch in loader: x, y batch[src], batch[tgt] # 正向目标模型的标准翻译损失 logits_f forward_target(x, y[:, :-1]) loss_f cross_entropy(logits_f, y[:, 1:]) # 反向目标模型的标准翻译损失 logits_b backward_target(y, x[:, :-1]) loss_b cross_entropy(logits_b, x[:, 1:]) # 对偶重建用正向模型生成伪译文再用反向模型圆回源语言 with torch.no_grad(): pseudo_y forward_target.generate(x, max_new_tokens64) logits_recon backward_target(pseudo_y, x[:, :-1]) loss_recon cross_entropy(logits_recon, x[:, 1:]) # 反向同理这里省略 # loss_b_recon ... # 蒸馏损失让草稿模型去拟合目标模型的分布 with torch.no_grad(): soft_target logits_f.softmax(dim-1) logits_draft forward_draft(x, y[:, :-1]) loss_distill kl_div(logits_draft.log_softmax(dim-1), soft_target) loss loss_f loss_b 0.2 * loss_recon 0.1 * loss_distill loss.backward()注意伪代码里我特意把 pseudo_y 包在no_grad()里避免生成序列的离散采样路径把梯度搞炸。重建损失的实际效果是让正向模型学会译文不仅要像目标语言还要足够还原源语言信息这在平行语料稀缺时会逼着模型去挖掘更稳定的语义对应关系。3.3 对偶协同为什么在低资源下成效更明显对偶重建本质上引入了一种无监督约束它不要求额外平行语料只需要两个方向的单语生成能力。低资源场景下平行语料稀薄但单语语料通常还是能凑出不少对偶重建等于把这些单语语料也变成了训练信号。另一个容易被忽略的好处是圆回来这个动作天然抑制了翻译中的信息丢失——如果正向模型为了流畅而丢掉某个源语言细节反向模型很难把那个细节凭空补回来重建损失就会升高模型被迫学会保留完整语义。我这边实验中最直观的感觉是加上对偶重建之后模型对低频词和长距离依赖的处理明显更稳chrF字符级 F 值比纯交叉熵训练涨得比 BLEU 更明显。原因是重建约束让模型在词法和语序上更保守不容易飘。不过这里有个度λ 太大模型会变得过度圆滑这是后面调参章节要细说的问题。4. 双协同的第二重草稿-验证协作里的tokenizer、采样与温度细节4.1 统一tokenizer和输出空间是大前提投机解码能跑通的第一个工程前提是草稿模型和目标模型必须共用同一个 tokenizer。这句话看起来废话实际特别容易翻车低资源语言常常没有现成词表很多人会给草稿模型和目标模型各训一个 BPE 词表结果验证阶段 token 对不上整个接受率崩盘。正确做法是先统一训练一个子词词表草稿和目标模型都在这份词表上定义输出分布。低资源词表大小我建议控制在 4k~8k。词表太小OOV 和碎片化严重词表太大稀疏词的 embedding 几乎没有有效梯度而且投机解码的验证是在完整词表上算分布词表越大验证阶段越贵。一个折中方案是共享 embedding 矩阵让草稿模型和目标模型在词表示层面天然对齐这能显著减少两者分布之间的系统性偏移。4.2 草稿长度、接受率与加速比的联动草稿长度 K 不是一个可以拍脑袋定的常数。K 太短投机迭代频繁切换目标模型的前向次数降不下去K 太长草稿模型生成到后半段时质量衰减接受率 α 下降收益反而被吃掉。我按前面的公式算过一组典型情况草稿长度 K接受率 α期望产出 E单步成本加速比40.703.801.602.3850.704.501.752.5760.685.081.902.6780.555.402.202.45120.405.802.802.07这里的单步成本按意 K×0.151 估算。可以看到 K 从 5 加到 8加速比反而下降因为长草稿后半段接受率跌得太快。实战中稳妥做法是从 K5 起步每迭代一次就在验证集上重新统计 α如果 α 低于 0.55就把 K 往下调如果 α 能到 0.75 以上再试探性加大 K。4.3 温度与蒸馏对齐让草稿更懂目标模型的脾气接受率 α 的本质是草稿分布和目标分布的重合度。目标模型如果使用高温采样输出分布会变得平坦草稿模型很难预测它下一步会选什么α 就会掉。反过来target 用低温或贪心时分布集中在少数几个 token 上α 通常会高一些。所以不要把草稿模型的解码温度和 target 的解码温度各自独立地设成习惯值要当成一对参数去调。更治本的办法是蒸馏训练草稿模型时不要只让它拟合硬标签而是让它去拟合目标模型的 softmax 分布也就是前面伪代码里的 KL 蒸馏项。目标模型的输出分布在低资源场景下往往包含大量可能但不一定正确的候选这些信息对草稿模型预判 target 的行为非常关键。我实测中加了蒸馏之后α 从 0.55 左右提到 0.72加速比差不多翻了一倍。这个收益比任何解码期技巧都来得直接。4.4 低置信区间的重复抑制低资源模型普遍有一个毛病在低置信区间容易陷入重复循环同一个词反复出现。投机解码本身不会修复这个问题因为验证时如果 target 在某个位置也是低置信它依然可能接受一个平庸的草稿 token。我的做法是在验证阶段引入轻量的 repetition penalty对已经出现过的 token 的 logits 做衰减再和草稿分布比较是否接受。这样做的代价是严格意义上会略微偏离 target 的原始分布但在 MT 任务里抑制重复带来的质量收益远大于理论偏差的损失。5. 能直接复现的模型配置、训练流程与评估口径5.1 语料清洗和数据增强的边界条件低资源语料质量参差不齐清洗规则我按重要性排序如下按长度比过滤源句和目标句 token 数比值超过 1:0.7~1.3 的删掉过滤过长句子超过 80 token 的删掉模糊去重纯标点或纯数字句子剔除。这些规则看起来基础但低资源语料里大量噪音句对会直接拉低对偶重建的效果因为重建损失对不可复原的句子惩罚非常重。回译增强建议只使用和目标语言最接近的那部分单语语料并且回译生成的 pseudo 句对置信度低于阈值就丢弃。这里有个容易忽略的细节回译数据参与训练时不要让对偶重建损失作用在 pseudo 句对上。原因很简单pseudo 句对本身已经有噪声再用它做重建约束等于放大噪声。5.2 模型参数、损失权重与训练策略我采用的是一套可以在单卡上跑完的配置draft_cfg { d_model: 256, n_layers: 4, n_heads: 8, vocab_size: 8000, dropout: 0.1, } target_cfg { d_model: 512, n_layers: 8, n_heads: 8, vocab_size: 8000, dropout: 0.1, } train_cfg { optimizer: AdamW, learning_rate: 3e-4, warmup_steps: 4000, label_smoothing: 0.1, lr_schedule: noam, loss_recon_weight: 0.2, loss_distill_weight: 0.1, max_len: 80, batch_tokens: 4096, }训练上我推荐分两段走第一阶段用标准交叉熵把正向和反向的目标模型训到基本可用第二阶段加入草稿模型和逆向重建损失同时打开蒸馏。如果一开始就全部 loss 一起上草稿模型还没学会基础翻译蒸馏出来的分布也是乱的收敛会很慢。损失权重方面loss_recon_weight从 0.1 起步观察验证集 chrF 变化如果重建损失加得太猛超过 0.5正向模型会过度追求可回译性译文用词趋于保守BLEU 反降。蒸馏权重 0.1 一般够用太大会让草稿模型丧失自己的探索能力在 target 分布不平坦的位置反而更犹豫。5.3 评估指标不要只盯着BLEU低资源场景下我建议至少同时报四个指标BLEU 看整体翻译质量chrF 看词法层面的稳健性接受率 α 看投机解码的健康程度端到端加速比看真实工程收益。加速比不要只测单条句子应该在同一批数据、同一 batch size、同一最大生成长度下统计整个验证集的解码总耗时对比。只测单句很容易被冷启动、KV 缓存这些因素干扰。模型产出的译文还要做人工抽检重点看两个方向一是低资源高频词是否稳定翻译成同一目标词二是长句有没有出现译出来但圆不回去的信息丢失。这些是自动指标反映不出来的但对实际可用性影响巨大。6. 实测对比与调参中容易翻车的几个环节6.1 一组内部对比结果为了说明问题我在内部构造的一份约 20 万句对的低资源模拟语料上跑过一组对比。这份语料规模不大领域偏口语化结果仅供趋势参考配置BLEUchrF平均解码耗时s/句加速比小模型 beam431.254.80.581.00大模型 贪心解码33.057.10.840.69大模型 投机解码K532.856.90.421.38双协同对偶重建 投机解码33.658.30.371.57最反直觉的是第一行和第三行的对比投机解码的配置在 BLEU 上比小模型 beam 高出 1.6 个点同时延迟还低了 28%。这就是投机解码小模型草稿 大模型验证结构带来的双重红利——目标模型更强但不需要像传统大模型那样付出全程自回归的延时。双协同在这个基础上又让 BLEU 和 chrF 各涨了一截说明训练阶段的正反向约束和推理阶段的草稿-验证确实是互相增益的。6.2 最容易翻车的三个调参点第一个是草稿模型训练不足。草稿模型如果只是随便训了几个 epoch它的分布和 target 分布差距过大α 会掉到 0.4 以下这时候投机解码的加速比可能小于 1比纯目标模型还慢。草稿模型值得认真训练尤其是蒸馏阶段要给足步数。第二个是忽略 target 解码温度的影响。我在调试中曾经一度把 target 的 temperature 调成 1.2 想增加译文多样性结果 α 从 0.7 掉到 0.48净加速比反而下降。多样性提升的那点收益完全弥补不了草稿预测失败带来的性能损失。低资源任务里target 侧温度设在 0.8~1.0 之间比较稳妥。第三个是对偶重建权重过猛导致的译得更圆滑但不准确。有段时间我把loss_recon_weight调到 0.5发现 chrF 涨了 0.5 个点BLEU 却跌了 1 个点多人工看译文发现模型越来越倾向用安全的笼统表达。后来我把重建损失改成了动态权重前期 0.2后期按验证集 BLEU 的停滞情况逐步退火到 0.05这个问题才缓解。6.3 我认为值得保留的几个小技巧草稿模型和目标模型共享 embedding 是我最推荐的一个改动它既减少参数量又能让草稿模型从一开始就在词表示空间上贴近 targetα 通常能提升 3~5 个百分点。其次目标模型验证草稿时可以复用已经算好的 prefix KV 缓存避免重复计算整个前缀部分这一项在长句场景能带来额外 10%~15% 的耗时下降。还有一个隐藏在代码里的小细节验证阶段拒绝采样时修正分布max(0, p-q)的计算必须做在完整词表上而不是只在 top-k 候选上做。之前我为了省事在 top-50 里近似结果遇到生僻词时分布被截断翻译质量出现肉眼可见的下滑后来老老实实换成完整词表计算。最后说个可以带走的小经验如果你手里的低资源语料连十万句对都不到先别急着上投机解码。先把对偶重建、回译这些训练侧的数据利用手段做扎实等模型质量稳定到一定水位再加草稿-验证这套推理侧加速否则你只会得到一个加速了但译文质量飘忽的系统。我这边把顺序反过来的那轮实验就是典型的反面教材。
阅读完成 · 觉得有帮助?