Liquid4All/antidoom

GitHub: Liquid4All/antidoom

Antidoom 通过自动生成针对性偏好数据并训练 LoRA adapter,专门用于减少大语言模型推理中重复循环(末日循环)的失败模式。

Stars: 300 | Forks: 26

# Antidoom Antidoom 用于生成并训练有针对性的偏好数据,以减少模型的重复循环(即末日循环,doom loops)。 它是一款针对特定失败模式的专用工具:通过采样模型生成结果,检测重复片段的起始位置,将循环开始的第一个 token 标记为拒绝(rejected),选择连贯的替代后续 token,然后使用最终 Token 偏好优化(FTPO)训练一个 LoRA adapter。 该方法将[Antislop](https://arxiv.org/abs/2510.15061)及其[auto-antislop 实现](https://github.com/sam-paech/auto-antislop)中的单 token 偏好训练思路,应用于推理过程中出现的失控重复问题。 ## 前置条件 Antidoom 需要受支持的 GPU 运行时来运行 PyTorch 和 vLLM。NVIDIA/CUDA 是默认配置;AMD/ROCm 则需要单独的环境。 - **NVIDIA / CUDA:** 使用锁定的 `uv.lock` 和 [`configs/default.yaml`](configs/default.yaml)。 - **AMD / ROCm (Instinct MI 系列):** 不要使用默认的 `uv.lock`,因为其仅支持 CUDA。请参阅 [AMD / ROCm](#amd--rocm) 来创建 ROCm 虚拟环境,并使用 [`configs/default_amd.yaml`](configs/default_amd.yaml) 运行,该配置添加了必要的 `attention_backend: TRITON_ATTN` 和 `optim: adamw_torch` 覆盖项。 ## 快速开始 ``` git clone https://github.com/Liquid4All/antidoom cd antidoom uv sync ``` 编辑 [`configs/default.yaml`](configs/default.yaml) 并将 `model_name` 设置为你想要进行 antidoom 处理的 checkpoint。默认配置会从 [LiquidAI/antidoom-mix-v1.0](https://huggingface.co/datasets/LiquidAI/antidoom-mix-v1.0) 读取 prompt,生成 FTPO 对,训练 LoRA adapter 并将其合并。 运行完整的生成与训练流程: ``` uv run antidoom -c configs/default.yaml -r runs/antidoom1 \ --temp 0.01 \ --model-name LiquidAI/LFM2.5-1.2B-Base ``` 这会将生成的结果和 FTPO 对写入 `runs/antidoom1/` 目录,基于对文件进行训练,并将 adapter 和合并后的模型写入同一个运行目录中。 ## 为什么会出现末日循环 末日循环往往在以下三种因素叠加时出现: - **过度训练的 token。** 常见的推理 token,如 `Wait`、`So`、`But`、`Alternatively`,在经过大量合成推理训练后可能会变得异常活跃。当模型不确定时,这些 token 可能会在 next-token 分布中占据主导地位,而无法推动推理向前发展。 - **自我强化的上下文。** 一旦出现了短序列,先前的上下文就会使该序列更有可能再次出现。在同一个循环的多次重复中,每个 token 的概率可能会不断攀升直至变为确定事件。 - **低温采样。** 在 temperature 为 0 或接近 0 时,模型会不断选择概率最高的后续内容,因此局部被强化的循环将没有自然的逃逸路径。 Antidoom 会在循环开始的 token 处对该失败进行攻击。它不使用完整的黄金答案进行训练,而是仅在局部偏好上进行训练:不选择开始重复的 token;而是在同一位置可用的合理替代项中进行选择。 ## 工作原理 对于每个 prompt,Antidoom 会生成一段补全内容并扫描其中的内部重复。当发现循环时,它会在 token 空间中细化边界,确保被拒绝的 token 是重复片段的第一个可读 token。 每一行 FTPO 数据包含: - 一个在被拒绝 token 之前立即结束的 prompt/上下文前缀 - 一个被拒绝的 token,即开始循环的 token - 一个或多个从该位置过滤后的替代项中采样得到的选择(chosen)token - 关于源 prompt、生成内容和检测到的循环的元数据 训练步骤会对代表性过高的被拒绝 token 和选择 token 进行正则化,以便 adapter 能够学习到广泛的反循环偏好,而不是单纯地抑制某一个词。 ## 数据集 默认配置使用 [LiquidAI/antidoom-mix-v1.0](https://huggingface.co/datasets/LiquidAI/antidoom-mix-v1.0),这是一个专门为此 pipeline 构建的仅包含 prompt 的 ShareGPT 混合数据集。 该数据集特意排除了黄金答案、推理依据、隐藏测试、验证目标和答案标签。 数据集构建脚本和许可证说明位于 `datasets/` 目录中。 ## 配置 从 [`configs/default.yaml`](configs/default.yaml) 开始。最常需要修改的字段是: - `model_name`:基础 checkpoint 或 Hugging Face 模型 ID - `generation.hf_dataset`:prompt 数据集,或设置 `generation.input_jsonl` - `generation.prompt_field`:从本地/HF 数据行中读取的列 - `generation.target_pairs`:要收集的 FTPO 对数量 - `generation.temperature` 或 `generation.temperatures`:采样 temperature - `train.max_train_examples`:用于训练的最大 FTPO 行数 - `train.output_dir` 和 `train.merged_output_dir`:adapter 和合并后模型的目录 `prompt_field` 是权威的:生成阶段会读取该列并根据值的结构进行解析。字符串被视为普通的用户 prompt,OpenAI 风格的 `[{"role": ..., "content": ...}]` 列表会被用作聊天消息,而 ShareGPT 风格的 `[{"from": ..., "value": ...}]` 列表会被转换为聊天消息。解析器不会根据列名来推断 prompt 格式。 ### 重要超参数 `max_train_examples` 在训练前生成足够的偏好样本非常重要。生成样本的数量取决于生成步骤可用的 prompt 数量、运行的 temperature 轮次以及该 checkpoint 陷入末日循环的倾向程度。根据经验,建议至少从 15k 个 prompt 开始,目标是生成大约 15k-20k 条偏好数据。 将 `max_train_examples` 设置为小于生成的偏好行数,例如从 15k-20k 的生成集中选取 12k 行。这为 `rejected_regularisation_strength` 留出了削减代表性过高的被拒绝 token 的空间。正则化非常重要:如果没有它,生成的数据集可能会严重失衡,从而导致糟糕的训练结果。 使用提示:将 `max_train_examples` 设置为最多占生成数据集中实际偏好行数的 70%。 `learning_rate` Antidoom 训练器可能会出现训练不足或过度训练的情况。如果过度训练,模型性能可能会下降并产生更多的末日循环,因此找到合适的学习率可能需要反复试验。 使用提示:在基于约 12k 样本进行训练时,`0.00001`-`0.00002` 是一个很好的起始范围。早停机制有助于避免过度训练。 `early_stopping_chosen_win` 当 `chosen_win` 超过配置值时,这将停止训练。 `chosen_win` 是指选择的 token 战胜被拒绝的 token 的样本比例。在 `0.15`-`0.3` 左右时,你通常应该能看到末日循环显著减少。超过 `0.5` 继续训练可能是过度训练,尽管还需要更多的消融实验来证实。 使用提示:尝试设置为 `0.4` 并据此进行调整。 ### 其他超参数 `generation.*` 这些参数控制生成阶段,我们试图在此阶段引出末日循环以构建偏好训练集。默认值通常是一个合理的起点。 `rejected_regularisation_strength` 这通过剔除样本来平缓被拒绝 token 的频率分布。某些被拒绝 token 的出现频率远高于其他 token,因为它们是引发末日循环的 token。如果训练集过于不平衡,这些 token 可能会被抑制得过于激进。 使用提示:默认的 `0.3` 是一个很好的起点。 `chosen_regularisation_strength` 这会平缓选择 token 的频率分布。如果某个选择 token 的代表性过高,其概率可能会被增加得过多,从而可能开始引发自身的循环。此步骤通常会修剪选择的替代项,而不是整个样本,因为每个样本可以包含多个选择 token。 使用提示:默认的 `0.5` 是一个很好的起点。 `filter_rejected_stop_words` 过滤被拒绝 token 为诸如 `the` 或 `a` 等停用词的样本。 保持将其设置为 `false`;常用词确实有可能是引发循环的起始 token,而正则化步骤是处理频率的更安全方法。 `source_balance_mode` (可选)在被拒绝 token 正则化之后,根据来源平衡训练集。默认的 `off` 就很好。旧配置可能将其称为 `balance_by_source_task`。 `min_chosen_tokens` 包含样本所需的最少选择 token 数量。有时要求多个选择 token 有助于避免在单一首选 token 上出现模式崩溃,但选择 token 的正则化已经缓解了这个问题,对于 Antidoom 训练来说,通常设置为 `1` 就可以了。 `lora_r` 对于此训练器,比平时更高的 LoRA 秩往往能提高可学习性,同时减少性能下降。如果不确定,请使用 `128` 或 `256`。 `lora_alpha` 将其设置为与 `lora_r` 相同的值,或者 `lora_r` 的一半。 `lora_dropout` 目前尚未对其进行大量消融实验,但在当前的运行中 `0` 效果很好。 `target_modules` 之前的 Antislop 消融实验发现,训练所有层虽然更容易学习,但也更容易退化;限制在 `up_proj`、`down_proj` 和 `lm_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 loss 生效之前,相对于参考模型的免惩罚 logit 差值。 `lambda_mse` 对于剩余词表(排除选择和被拒绝的 logits),将其连接到参考模型的 MSE 系数。这通常设置得高于 `lambda_mse_target`,因为选择/被拒绝的 logits 应该更自由地变动,而其余词表则应保持接近参考模型。 `clip_epsilon_logits` 一旦选择 token 以此 logit 差距击败被拒绝 token,偏好 loss 就会关闭。 ## 生成与训练 默认情况下,`antidoom` 是一个一次性 pipeline。它会不断生成偏好行直到满足 `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 ``` 合并 adapter: ``` uv run antidoom -c configs/default.yaml -r runs/antidoom1 merge \ --model-name LiquidAI/LFM2.5-1.2B-Base ``` ## 过滤 生成阶段会跳过超过 `generation.max_prompt_tokens` 的 prompt。生成的偏好数据集会进行正则化处理,以确保任何给定的 token 在 *chosen* 或 *rejected* 集合中出现的频率不会过高。 ## 输出结果 每一行 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 wheels 中构建一个 **独立的 ROCm 虚拟环境**,并使用 [`configs/default_amd.yaml`](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 wheels 支持 MI200/MI300/MI350 和 Radeon RX 7900/9000;有关支持矩阵和 ROCm 要求(ROCm 6.3+,或 MI350 的 7.0+),请参阅 vLLM 的 [GPU 安装指南](https://docs.vllm.ai/en/stable/getting_started/installation/gpu.html)。 ### 环境设置 要求主机具备 ROCm 6.3+(`rocminfo` 会列出你的 GPU)和 Python 3.12(已在 `.python-version` 中锁定)。在仓库根目录下,使用单条命令创建虚拟环境并安装 antidoom + ROCm 版的 torch/vLLM——`--extra-index-url` 提供了 ROCm wheel 索引的优先级,这样 `torch`/`vllm` 就会解析为其 ROCm 构建版本,而不是 CUDA PyPI wheels([参考](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`](configs/default_amd.yaml) 就是在 `.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 数学运算;其“分页(paged)”的 CPU 卸载在 256 GB 的 MI325 上毫无意义。 多 GPU 生成路径还会为每个 worker 额外设置以下参数(位于 `generate.py` 中,无需配置):`HIP_VISIBLE_DEVICES`(因为 ROCm 无法仅通过 `CUDA_VISIBLE_DEVICES` 可靠地固定 GPU),每个 worker 节点本地的 `VLLM_CACHE_ROOT`/`TORCHINDUCTOR_CACHE_DIR`/`TRITON_CACHE_DIR`(共享 JIT 缓存竞争会导致“内存访问错误”),以及探测到的空闲端口用于 `VLLM_PORT`(避免 `EADDRINUSE`)。这些在 NVIDIA 上不起作用。 ### 在 AMD 上运行 命令与快速开始相同,但需使用 `configs/default_amd.yaml` 和 `uv run --no-sync`(`--no-sync` 可阻止 `uv run` 将虚拟环境重新同步回 CUDA lock): ``` uv run --no-sync antidoom -c configs/default_amd.yaml -r runs/antidoom1 \ --temp 0.01 \ --model-name ``` 生成阶段为每个可见的 GPU 使用一个单 GPU vLLM 引擎;设置 `CUDA_VISIBLE_DEVICES` / `HIP_VISIBLE_DEVICES` 来选择 GPU。训练阶段使用单 GPU LoRA + 合并。 倾向于使用容器?vLLM 还提供了官方的 ROCm 镜像 —— `vllm/vllm-openai-rocm:latest`(稳定版)或 `:nightly`——详见 [GPU 安装指南](https://docs.vllm.ai/en/stable/getting_started/installation/gpu.html)。在容器内使用 **相同的 `configs/default_amd.yaml`** 运行 antidoom;此时仍然需要设置 `TRITON_ATTN`。 ### AMD 故障排除 | 症状 | 原因 | 解决方案 | |---|---|---| | 生成期间出现 `Memory access fault by GPU` | vLLM 的 `ROCM_ATTN` 内核在 LFM2 上的 bug | 使用 `configs/default_amd.yaml` (`attention_backend: TRITON_ATTN`) —— 强制要求,影响所有 cudagraph 模式 | | 训练期间出现 `ImportError: You need to install bitsandbytes` | `optim: paged_adamw_32bit` | 使用 `configs/default_amd.yaml` (`optim: adamw_torch`) | | vLLM 初始化时出现 `RuntimeError: Device string must not be empty` | 缺少 `amdsmi`(未检测到 ROCm 平台) | `cp -r "${ROCM_PATH:-/opt/rocm}/share/amd_smi" /tmp/amd_smi_build && uv pip install /tmp/amd_smi_build`(先进行复制——ROCm 附带的目录树归 root 用户所有) | ## 引用 ``` @article{liquidAI2026Antidoom, author = {Liquid AI}, title = {Reducing Doom Loops with Final Token Preference Optimization}, journal = {Liquid AI Blog}, year = {2026}, note = {www.liquid.ai/blog/antidoom} } ```
标签:DLL 劫持, IaC 扫描, PyTorch, Vectored Exception Handling, vLLM, 人工智能, 凭据扫描, 大语言模型, 模型微调, 用户模式Hook绕过, 逆向工具