@helloiamleonie:与@liquidai团队一起编写这些工程博客真是太有趣了!这是我们一直在做的:Reas…

X AI KOLs Following 工具

摘要

这篇文章解释了推理模型中的“末日循环”,即模型重复诸如“Wait”之类的令牌,直到上下文填满,并介绍了FTPO(最终令牌偏好优化)作为训练时的修复方法。相关的Antidoom工具显著降低了末日循环率(例如,在Qwen3.5-4B上从22.9%降至1%)。

与@liquidai团队一起编写这些工程博客真是太有趣了! 以下是我们一直在做的工作: 推理模型可能会在思考中途卡住。 这就是我们所说的“末日循环”。 当一个模型重复相同的令牌("Wait", "Let me reconsider")直到上下文窗口填满时,就会发生末日循环。 导致这一问题的3个原因: • **过度训练的令牌**:在不确定性下,"Wait"和"Alternatively"占主导 • **自我强化的上下文**:每次重复都会提高下一个令牌的概率 • **贪婪采样**:在低温度下,最可能的令牌总是胜出 标准的修复方法是在推理时使用 repetition_penalty。 它只是缓解症状,而不是根本原因(而且会降低性能)。 但实际的根本原因是: **一个令牌触发整个循环**。 因此他们构建了FTPO(最终令牌偏好优化)来在训练时修复它: 1. 识别在生成中间启动循环的令牌 2. 将其标记为拒绝,并从基础模型中采样多达20个替代项 3. 重新分配概率 训练后的末日循环率: LFM2.5-2.6B:10.2% → 1.4% Qwen3.5-4B:22.9% → 1% 代码:http://github.com/Liquid4All/antidoom… 博客:https://liquid.ai/blog/antidoom
查看原文
查看缓存全文

缓存时间: 2026/07/07 20:22

与@liquidai团队一起撰写这些工程博客真是太有趣了!这是我们一直在做的事情:推理模型可能会在思考中途卡住。我们称之为“死循环”(doom loop)。死循环发生时,模型会重复同一个词元(“Wait”、“Let me reconsider”),直到上下文窗口填满。导致死循环的三个原因: • 过度训练的词元:在不确定情况下,“Wait”和“Alternatively”占据主导 • 自强化上下文:每次重复都会提高下一次出现的概率 • 贪婪采样:在低温度下,最可能的词元总是胜出

标准的修复方案是在推理时使用repetition_penalty(重复惩罚)。它治标不治本(而且会降低性能)。但真正的根本原因是:一个词元触发了整个循环。因此他们构建了FTPO(Final Token Preference Optimization,最终词元偏好优化)来在训练时修复它:

  1. 识别出在生成中期开启循环的那个词元
  2. 将其标记为“被拒绝“,并从基础模型中采样最多20个替代词元
  3. 重新分配概率

训练后的死循环率: LFM2.5-2.6B: 10.2% → 1.4% Qwen3.5-4B: 22.9% → 1%

代码:http://github.com/Liquid4All/antidoom… 博客:https://liquid.ai/blog/antidoom


Liquid4All/antidoom

来源:https://github.com/Liquid4All/antidoom

Antidoom

Antidoom生成并训练针对性的偏好数据,用于减少模型重复循环(死循环)。它是一个针对特定失败模式的窄工具:对模型补全进行采样,检测重复跨度开始的位置,将第一个开启循环的词元标记为“被拒绝“,选择合理的替代后续词元,然后使用最终词元偏好优化(FTPO)训练LoRA适配器。该方法将Antislop(https://arxiv.org/abs/2510.15061)及其auto-antislop实现(https://github.com/sam-paech/auto-antislop)中的单词元偏好训练思路适配到推理过程中失控重复这一具体问题上。

前提条件

Antidoom需要一个支持PyTorch和vLLM的GPU运行时。默认路径是NVIDIA/CUDA;AMD/ROCm需要单独的环境。

  • NVIDIA / CUDA: 使用固定的uv.lockconfigs/default.yaml
  • AMD / ROCm(Instinct MI系列): 不要使用默认的uv.lock,它仅支持CUDA。请参见AMD / ROCm创建ROCm虚拟环境,并使用configs/default_amd.yaml运行,该文件添加了所需的attention_backend: TRITON_ATTNoptim: adamw_torch覆盖。

快速开始

git clone https://github.com/Liquid4All/antidoom
cd antidoom
uv sync

编辑configs/default.yaml,将model_name设置为你想进行antidoom处理的检查点。默认配置从LiquidAI/antidoom-mix-v1.0(https://huggingface.co/datasets/LiquidAI/antidoom-mix-v1.0)读取提示词,生成FTPO对,训练LoRA适配器,并将其合并。

运行完整的生成+训练流程:

uv run antidoom -c configs/default.yaml -r runs/antidoom1 \
    --temp 0.01 \
    --model-name LiquidAI/LFM2.5-1.2B-Base

这会将生成的补全和FTPO对写入runs/antidoom1/,在配对文件上训练,并将适配器和合并后的模型写入同一运行目录。

为什么死循环会发生

死循环通常在三种力量同时作用时出现:

  • 过度训练的词元。 常见的推理词元如WaitSoButAlternatively,在大量合成推理训练后可能变得异常吸引人。当模型不确定时,这些词元可能主导下一个词元的分布,而不推动推理前进。
  • 自强化上下文。 一旦出现短序列,先前的上下文会使该序列更可能再次出现。在同一循环的重复轮次中,每个词元的概率可能会攀升至确定性。
  • 低温采样。 在温度为0或接近0时,模型会持续选择最高概率的延续,因此局部强化的循环没有自然的逃逸路径。

Antidoom在循环开始的词元处攻击故障。它不训练完整的正确答案,而是仅训练局部偏好:不要选择开启重复的词元;选择同一位置上可用的合理替代项之一。

工作原理

对于每个提示词,Antidoom生成一个补全并扫描其中的内部重复。当发现循环时,它会在词元空间中细化边界,使被拒绝的词元成为重复段的第一个可读词元。每个FTPO行包含:

  • 一个提示词/上下文前缀,在被拒绝的词元之前结束
  • 一个被拒绝的词元,即开启循环的那个词元
  • 一个或多个从该位置筛选出的替代词元中选择的“被选“词元
  • 关于源提示词、生成的补全和检测到的循环的元数据

训练步骤会对过度表示的被拒绝和被选词元进行正则化,使适配器学习广泛的抗循环偏好,而不是简单地抑制一个词。

数据集

默认配置使用LiquidAI/antidoom-mix-v1.0(https://huggingface.co/datasets/LiquidAI/antidoom-mix-v1.0),这是一个为此流程构建的仅含提示词的ShareGPT混合数据集。该数据集有意排除了正确答案、推理过程、隐藏测试、验证器目标和答案标签。数据集构建脚本和许可说明位于datasets/

配置

configs/default.yaml开始。最常见的要修改的字段包括:

  • model_name:基础检查点或Hugging Face模型ID
  • generation.hf_dataset:提示词数据集,或设置generation.input_jsonl
  • generation.prompt_field:从本地/HF行中读取的列
  • generation.target_pairs:要收集的FTPO对数量
  • generation.temperaturegeneration.temperatures:采样温度
  • train.max_train_examples:用于训练的最大FTPO行数
  • train.output_dirtrain.merged_output_dir:适配器和合并模型目录

prompt_field具有权威性:生成阶段读取该列并根据其形状解析值。字符串被视为普通用户提示词,OpenAI风格的[{"role": ..., "content": ...}]列表用作聊天消息,ShareGPT风格的[{"from": ..., "value": ...}]列表会转换为聊天消息。解析器不会从列名推断提示词格式。

重要超参数

max_train_examples 在训练之前生成足够的偏好样本非常重要。产生的样本数量取决于生成步骤可用的提示词数量、您运行的温度遍历次数以及检查点的死循环倾向。经验法则是,从至少15k个提示词开始,目标是产生大约15k-20k个偏好行。将max_train_examples设置为低于生成的偏好行数量,例如从15k-20k的生成集中取12k行。这给rejected_regularisation_strength留出空间来削减过度表示的被拒绝词元。正则化很重要:没有它,生成集可能严重不平衡,从而导致训练结果不佳。 使用提示:将max_train_examples设置为生成数据集中实际偏好行数的最多70%。

learning_rate Antidoom训练器可能训练不足或过训练。如果过训练,模型可能会退化并产生更多的死循环,因此正确的学习率可能需要一些试错。 使用提示:在训练约12k样本时,0.00001-0.00002是一个不错的起始范围。早停有助于避免过训练。

early_stopping_chosen_winchosen_win超过配置值时停止训练。chosen_win是被选词元战胜被拒绝词元的样本比例。大约0.15-0.3时,通常应该能看到死循环显著减少。超过0.5的训练可能是过训练,但需要更多消融实验。 使用提示:尝试0.4,然后据此调整。

其他超参数

generation.* 这些控制生成阶段,我们试图在该阶段引出死循环以构建偏好训练集。默认值通常是一个合理的起点。

rejected_regularisation_strength 通过剔除样本来平滑被拒绝词元频率的分布。某些被拒绝词元出现的频率远高于其他词元,因为它们是开启死循环的词元。如果训练集过于不平衡,这些词元可能被过度抑制。 使用提示:默认值0.3是一个不错的起点。

chosen_regularisation_strength 平滑被选词元频率的分布。如果某个被选词元过度表示,其概率可能增加过多,从而可能开始引起自身的循环。此步骤通常修剪被选替代项而非整个样本,因为每个样本可能包含多个被选词元。 使用提示:默认值0.5是一个不错的起点。

filter_rejected_stop_words 过滤被拒绝词元是停用词(如thea)的样本。保持设置为false;常见词确实可能是循环开启词元,而正则化步骤是处理频率的更安全方式。

source_balance_mode 在被拒绝词元正则化之后,可选择按来源平衡训练集。默认off即可。旧配置可能将此称为balance_by_source_task

min_chosen_tokens 一个样本被包含所需的最少被选词元数量。有时要求多于一个被选词元有助于避免在单个首选词元上模式崩溃,但被选词元正则化已经缓解了这个问题,对于Antidoom训练来说1通常没问题。

lora_r 比通常更高的LoRA秩往往能提高可学习性且退化更少。如果不确定,使用128256

lora_alpha 设置为与lora_r相同的值,或者lora_r的一半。

lora_dropout 尚未经过大量消融,但当前运行中0效果良好。

target_modules 之前的Antislop消融研究发现,训练所有层更容易学习,但也可能更容易退化;限制到up_projdown_projlm_head在那里有帮助。Antidoom训练似乎倾向于训练所有层。安全的起点是:

["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj", "lm_head"]

freeze_early_layers 这可以减少模型影响和退化,但会牺牲可学习性。在实践中,Antidoom训练在冻结关闭时效果良好。

lambda_mse_target 对被选和被拒绝logits与参考模型的MSE约束强度。

tau_mse_target 在目标logits的MSE损失生效之前,与参考模型的免惩罚logit增量。

lambda_mse 对剩余词汇(不包括被选和被拒绝logits)与参考模型的MSE约束强度。通常设置得比lambda_mse_target高,因为被选/被拒绝logits应该更自由地移动,而词汇表的其余部分保持接近参考模型。

clip_epsilon_logits 一旦被选词元以这个logit差距击败被拒绝词元,偏好损失就会关闭。

生成与训练

默认情况下,antidoom是一条流水线。它生成偏好行直到满足generation.target_pairs,然后训练。如果目标配对JSONL已经存在且有足够的行,生成步骤立即返回并开始训练。

uv run antidoom -c configs/default.yaml -r runs/antidoom1 \
    --temp 0.01 \
    --model-name LiquidAI/LFM2.5-1.2B-Base

使用-r runs/antidoom1,生成步骤写入:

runs/antidoom1/iter_0_generations.jsonl
runs/antidoom1/iter_0_ftpo_pairs.jsonl

训练步骤写入:

runs/antidoom1/lora
runs/antidoom1/

分阶段命令

仅运行生成:

uv run antidoom -c configs/default.yaml -r runs/antidoom1 generate \
    --model-name LiquidAI/LFM2.5-1.2B-Base

从现有配对文件训练:

uv run antidoom -c configs/default.yaml -r runs/antidoom1 train \
    --dataset-jsonl runs/antidoom1/iter_0_ftpo_pairs.jsonl \
    --model-name LiquidAI/LFM2.5-1.2B-Base

合并适配器:

uv run antidoom -c configs/default.yaml -r runs/antidoom1 merge \
    --model-name LiquidAI/LFM2.5-1.2B-Base

过滤

生成步骤会跳过超过generation.max_prompt_tokens的提示词。生成的偏好数据集经过正则化,使得任何给定词元在“被选“或“被拒绝“集合中不会出现过于频繁。

输出

每个FTPO行包含如下字段:

{
  "source_task": "openai/gsm8k",
  "source_sample_id": "1234",
  "context_with_chat_template": "... text before rejected token ...",
  "rejected_decoded": " Wait",
  "multi_chosen_decoded": [" Therefore", " The"],
  "repetition": {
    "start_char": 1024,
    "repeat_start_char": 1526,
    "rejected_token_index": 342
  }
}

iter_0_generations.jsonl存储完整的生成补全和检测器元数据。 iter_0_ftpo_pairs.jsonl存储用于训练的偏好行。

AMD / ROCm

上述快速开始针对NVIDIA/CUDA,其固定的uv.lock仅支持CUDA,无法驱动AMD GPU。在AMD上,您需要从vLLM预构建的ROCm wheel中构建一个单独的ROCm虚拟环境,并使用configs/default_amd.yaml运行——其关键设置是attention_backend: TRITON_ATTN,因为vLLM默认的ROCM_ATTN内核在LFM2上会内存错误。使用uv run --no-sync antidoom ...运行——需要--no-sync:裸uv run(如快速开始中)会重新同步虚拟环境到仅CUDA的uv.lock,从而破坏ROCm安装。

已在**Instinct MI325 (gfx942)**上验证。vLLM的ROCm wheel支持MI200/MI300/MI350和Radeon RX 7900/9000;请参阅vLLM的GPU安装指南(https://docs.vllm.ai/en/stable/getting_started/installation/gpu.html)了解支持矩阵和ROCm要求(ROCm 6.3+,或MI350需要7.0+)。

环境设置(ROCm虚拟环境)

主机需要ROCm 6.3+(rocminfo列出您的GPU)和Python 3.12(已在.python-version中固定)。从仓库根目录,在一个命令中创建虚拟环境并安装antidoom + ROCm torch/vLLM——--extra-index-url优先使用ROCm wheel索引,因此torch/vllm会解析为它们的ROCm构建,而非CUDA PyPI wheel(参考https://docs.vllm.ai/en/stable/getting_started/quickstart/#installation):

uv venv && uv pip install -e . --extra-index-url https://wheels.vllm.ai/rocm/

验证:

uv run --no-sync python -c "import torch, vllm; print(torch.__version__, torch.version.hip, vllm.__version__)"
# torch应该打印一个'+rocm...'构建和一个非None的torch.version.hip

ROCm特定设置(已在configs/default_amd.yaml中)

configs/default_amd.yaml是在default.yaml基础上精确添加以下覆盖:

  • generation.vllm_kwargs
vllm_kwargs:
  enforce_eager: true # eager在ROCm上稳定且快速
  attention_backend: TRITON_ATTN # 修复:ROCM_ATTN在LFM2上内存错误

attention_backend值被传递给vLLM引擎。在最近的vLLM中,VLLM_ATTENTION_BACKEND环境变量是无效的——必须使用引擎参数,而vllm_kwargs会传递它。

  • train.optim: adamw_torch——NVIDIA默认的paged_adamw_32bit需要bitsandbytes(在ROCm上很麻烦)。adamw_torch是相同的32位AdamW数学;其“分页“CPU卸载在256 GB的MI325上毫无意义。

多GPU生成路径还在每个工作进程上设置(在generate.py中,无需配置): HIP_VISIBLE_DEVICES(ROCm无法仅通过CUDA_VISIBLE_DEVICES可靠地固定GPU),每个工作进程的节点本地VLLM_CACHE_ROOT/TORCHINDUCTOR_CACHE_DIR/TRITON_CACHE_DIR(共享JIT缓存存在竞争→“内存访问错误”),以及探测到的空闲端口用于VLLM_PORT(避免EADDRINUSE)。这些在NVIDIA上是空操作。

在AMD上运行

与快速开始相同的命令,但使用configs/default_amd.yamluv run --no-sync--no-sync阻止uv run重新同步虚拟环境回到CUDA锁):

uv run --no-sync antidoom -c configs/default_amd.yaml -r runs/antidoom1 \
    --temp 0.01 \
    --model-name ...

生成使用一个单…

相似文章

利用Final Token Preference Optimization减少Doom循环

Hacker News Top

Liquid AI推出了Antidoom方法,该方法使用Final Token Preference Optimization来减少小型推理模型在推理过程中的重复性Doom循环,将循环率从10.2%降至1.4%,同时提升了评估分数。