Liquid AI 出手治 LLM 的"复读病":FTPO 只惩罚最后那个 token

Liquid AI 提出 Final Token Preference Optimization(FTPO),针对模型陷入循环输出的"Doom Loop"痼疾,只对触发重复的那个 token 做偏好优化,训练成本更低、副作用更小。
Liquid AI 上周甩出一篇技术博客,讲他们怎么治 LLM 的老毛病——Doom Loop(复读机死循环)。方法叫 FTPO(Final Token Preference Optimization),思路简单粗暴:出问题的往往就是一个 token,那就只惩罚这一个 token,别搞连坐。
如果你调过小模型的 API,尤其是让它写长文、跑 agent 多轮推理,大概率见过这种场面:模型突然开始鬼打墙,同一句话循环输出,直到把 max_tokens 撞穿。开源社区里大家的土办法基本就三招——调高 temperature、加 repetition_penalty、上 DRY sampler。都能缓解,但都治标。Liquid AI 这次是想从训练环节根治。
Doom Loop 到底是怎么发生的
先说现象。Liquid 团队在博客里给了几个典型例子:模型输出一段代码后,突然开始把某个函数名重复几十遍;或者在回答一个开放性问题时,卡在某个句式上出不来,"总之,...总之,...总之,..."没完没了。
这不是新问题。早年 GPT-2 时代就有,工程上一般归因于两点:
- 训练分布的尾部塌陷——模型在某个上下文下把概率质量过度集中在了少数几个 token 上,采样时一旦踩中,就很难跳出
- 自我强化——一旦复读了一次,这段重复内容本身又成为上下文的一部分,进一步抬高了下一次复读的概率
第二点是关键。它是一个正反馈过程,temperature 只能延缓爆发,不能阻止。

已有方案为什么不够用
DPO(Direct Preference Optimization)本来是最自然的选择:拿好的输出当 chosen,坏的(有复读的)当 rejected,一顿对齐。但 Liquid 团队指出 DPO 在这个场景下有几个硬伤。
第一,惩罚粒度太粗。DPO 是在整个 rejected 序列上算 loss,模型学到的是"这一整段都不好",但其实这段里可能 95% 的 token 都没问题,只是最后那一下滑进了循环。全序列惩罚会把好的部分也一起推低概率,副作用就是模型输出风格漂移,多样性下降。
第二,训练效率低。你得为每个 rejected 样本准备一个 chosen 对照,还得跑长序列的对数概率计算。为了修一个 token 的问题,付出的代价太高。
第三,也是最微妙的,过度对齐会把模型"抹平"。之前学术界已经有 DivPO 这类工作在提醒:偏好优化如果不考虑多样性,模型会越训越"标准化",创造力和长尾表达都会掉。
FTPO 的做法:外科手术式惩罚
FTPO 的核心洞察就一句话:Doom Loop 的形成通常可以追溯到某一个具体的 token 决策。那个 token 一旦选错,后面就是雪崩。所以训练时只要盯着这一个 token 做偏好优化就够了。
具体流程大致是:
- 采样一批模型输出,用检测器(比如 n-gram 重复率)找出发生 Doom Loop 的样本
- 定位到"临界 token"——也就是循环真正开始的那一步
- 在这一步上,构造一个偏好对:当前采到的 token 是 rejected,某个替代 token(可以是次高概率的,也可以是从参考模型采的)是 chosen
- 只在这个 token 位置上算 preference loss,其他位置的梯度不动
这么做有几个直接的好处:
训练信号极度稀疏但精准。相当于告诉模型"你其他都做得挺好,就这一步别这么走",模型不会为了修一个 bug 把整个人格重塑一遍。
样本利用率高。一条长输出里可能只有一个临界 token,但 FTPO 就针对这一个训。不用配对完整的 chosen 序列,工程复杂度和显存占用都下来了。
多样性保留得更好。这一点和 DivPO 的思路有异曲同工之处——你不动那些"没错"的 token,模型的输出分布就不会被大面积扭曲。
实测效果和一些限制
Liquid 在博客里给的评估结果,主要看两个指标:Doom Loop 触发率、以及在通用 benchmark 上的分数变化。前者显著下降,后者基本持平——这在偏好优化里是很难得的,因为大多数 alignment 手段都会在通用能力上掉一点。
但也要看到,FTPO 不是万能药。有几个明显的边界:
- 临界 token 的定位依赖检测器。如果检测器把非重复的"重复模式"(比如列表输出、代码里的重复结构)误判成 Doom Loop,训练信号就会错
- 对语义层面的"隐性复读"效果有限。有些模型会换着说法重复同一个意思,这种 semantic loop 不是靠单 token 惩罚能解决的
- 需要在训练后期做。Liquid 建议把 FTPO 放在 SFT+DPO 之后作为收尾步骤,如果直接在 pretrain 后用,可能会因为基础对齐不够而效果打折
对开发者的实际意义
如果你在做基于开源模型的产品,尤其是长文本生成、agent、代码助手这类场景,FTPO 这套思路值得抄。它不需要你重新训基础模型,只要在你现有的 fine-tuning pipeline 后面加一个针对 Doom Loop 的"补丁训练"就行。
伪代码大概长这样:
# 1. 生成一批样本,找出发生 doom loop 的
loop_samples = detect_doom_loops(model.generate(prompts))
# 2. 定位临界 token 位置
for sample in loop_samples:
critical_idx = find_loop_start(sample.tokens)
rejected_token = sample.tokens[critical_idx]
chosen_token = pick_alternative(sample.logits[critical_idx], exclude=rejected_token)
# 3. 只在这个位置算 preference loss
loss = ftpo_loss(
logits_at=critical_idx,
chosen=chosen_token,
rejected=rejected_token,
)
loss.backward()
工程上的一个隐藏收益是:这套方法对显存非常友好。传统 DPO 要跑两个模型(policy 和 reference)的完整前向,序列一长直接爆显存。FTPO 因为只关心一个 token 位置,可以做很多优化,比如只保留临界 token 附近的 hidden states。
放到更大的图景里看
这两年偏好优化领域一直在往"更细粒度"的方向卷。从最早整段的 RLHF,到 DPO 的成对偏好,到 KTO 只需要单侧标签,再到 step-level 的 Process Reward Model,现在 FTPO 直接把粒度压到单 token。
这个趋势背后其实是一个共识正在形成:模型的错误往往是局部的,但我们的训练信号一直是全局的,中间浪费了大量学习效率。FTPO 只是这个大方向上的一个具体应用,可以预期后面会有更多针对特定失败模式的"局部偏好优化"方法冒出来——比如针对幻觉的 hallucination-token PO,针对拒答的 refusal-token PO 等等。
Liquid AI 这家公司本身走的就是效率路线,之前的 LFM 系列小模型也一直强调用更少的参数打更好的效果。FTPO 这套做法很符合他们的一贯风格:不追求宏大叙事,就盯着一个具体问题,用最经济的手段解决掉。
对国内开发者来说,如果你在用 Qwen、DeepSeek、GLM 这些开源模型做产品,遇到复读问题不用再只靠 repetition_penalty 硬扛了。抄一下 FTPO 的思路,花几百条标注数据做一轮针对性微调,收益可能比你想象的大。
参考来源
- DivPO 论文解读 - 知乎:偏好对齐中考虑输出多样性的相关工作,和 FTPO 保留多样性的动机呼应

