【人工智能】Qwen3 MoE 模型 vLLM 死循环问题排查与修复经验
Qwen3 MoE 模型 vLLM 死循环问题排查与修复经验
日期: 2026-08-24 | 模型: Qwen3 系列 MoE (35B-A3B-FP8) | vLLM: 0.27.1
1. 前提条件
1.1 硬件环境
| 项目 | 值 |
|---|---|
| GPU | 4 × NVIDIA GeForce RTX 4090 |
| 显存 | 24 GB / 卡 (共 ~96 GB) |
| GPU 互联 | PCIe (无 NVLink) |
| 操作系统 | Ubuntu 22.04 LTS |
1.2 软件版本
| 组件 | 版本 |
|---|---|
| vLLM | 0.27.1 |
| Python | 3.13 |
| 推理后端 | FlashInfer |
| KV Cache | FP8 量化 |
1.3 模型信息
| 项目 | 值 |
|---|---|
| 模型系列 | Qwen3 MoE (混合注意力架构: GDN) |
| 总参数 / 激活参数 | ~35B / ~3B |
| 量化方式 | FP8 (e4m3, block 128×128) |
| 上下文长度 | 256K |
| EOS token IDs | [248046, 248044] |
| 原始 generation_config | temperature=1.0, top_k=20, top_p=0.95 |
1.4 vLLM 启动关键参数
vllm serve /path/to/model \
--tensor-parallel-size 4 \
--max-model-len 262144 \
--gpu-memory-utilization 0.92 \
--max-num-seqs 32 \
--enable-chunked-prefill \
--reasoning-parser qwen3 \
--enable-auto-tool-choice \
--tool-call-parser qwen3_xml \
--enable-expert-parallel \
--async-scheduling \
--attention-backend flashinfer \
--kv-cache-dtype fp8 \
--override-generation-config '{"temperature":0.6,"top_k":20,"top_p":0.95,"min_p":0.0,"repetition_penalty":1.0,"presence_penalty":1.5}'
1.5 业务调用方式
- 通过 OpenAI 兼容 API (
/v1/chat/completions) 调用 - 支持 streaming 和 non-streaming
- 使用 thinking 模式 (enable_thinking=true)
- 使用工具调用 (tool_choice=auto)
- 业务方未显式传
presence_penalty/frequency_penalty/stop_token_ids, 依赖服务端默认值
2. 问题现象
2.1 线上表现
- 某些请求 (尤其是"重复 X 次"类 prompt) 触发模型无限生成, 持续 ~155 tokens/s 不停止
- GPU 利用率 97%, KV cache 使用率从 0.2% 持续增长直至耗尽
- 请求一直处于
Running: 1状态, 超过 5 分钟未结束 - vLLM 日志无 error, 仅显示持续的高吞吐量生成
- 只有重启 vLLM 服务才能终止该请求
2.2 触发条件
- 高重复性 prompt: 如"请重复以下内容 50 次:XXX"
- 低 temperature (0.6): 分布尖锐化, 容易锁定重复模式
- 高 repetition_penalty (1.2): 反向将概率分布推向更窄的 token
- 未设置 presence_penalty: 缺少对已出现 token 的存在性惩罚
2.3 复现方法
# 高概率触发死循环的请求 (原始配置下)
curl -s http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "your-model-name",
"messages": [{"role": "user", "content": "请重复以下内容50次:人工智能"}],
"max_tokens": 4096
}'
# 观察: finish_reason=length, 输出 13000+ 字符, 全部是重复内容
3. 根因分析
3.1 Qwen3 MoE 死循环机制
Qwen3 系列 MoE 模型在特定采样参数组合下, 概率分布会塌缩到极少数 token 上, 形成 N-gram 重复循环, 且无法生成 EOS token 终止:
高 repetition_penalty (1.2)
→ 高频 token 被过度惩罚
→ 概率分布推向低频但已出现的 token
→ 这些 token 再次出现时又被惩罚
→ 最终锁定在少数未惩罚 token 的循环中
→ 无 EOS token 生成 → 无限循环
3.2 关键参数影响
| 参数 | 问题值 | 影响 |
|---|---|---|
repetition_penalty |
1.2 | 最关键 — 按频率惩罚, 出现越多惩罚越大, 但反向将分布推向更窄 token, 加剧循环 |
presence_penalty |
未设置 (0.0) | 缺少按存在性惩罚的机制, 已出现 token 无额外抑制 |
min_p |
0.0 | 不裁剪低概率 token, 允许模型选择极低概率的重复 token |
temperature |
0.6 | 偏低温度使分布尖锐化, 容易锁定重复模式 |
3.3 vLLM bug: presence_penalty 被静默忽略
vLLM 0.27.1 的 --override-generation-config 不支持 presence_penalty 和 frequency_penalty:
- 这两个参数不在
get_diff_sampling_param()的available_params白名单中 - 即使写入 JSON 配置也不会生效, 且无任何警告
- 客户端不传时, 始终回退到
0.0而非服务端配置值
Bug 详情: vLLM #50767
官方修复 PR: vLLM PR #35988 (截至 2026-08 尚未合并)
3.4 参考来源
| 来源 | 关键信息 |
|---|---|
| Qwen 官方 Best Practices | 推荐 presence_penalty=1.5 防循环, repetition_penalty=1.0 |
| QwenLM/Qwen3.5 #88 | 社区报告 LiveCodeBench 重复循环问题 |
| HuggingFace Qwen3.5-35B-A3B 讨论 | MoE 模型 looping prevention 讨论 |
| vLLM #50767 | presence_penalty 被 override-generation-config 静默忽略 |
| vLLM PR #35988 | 官方修复 PR (添加 presence/frequency penalty 到 available_params) |
| vLLM PR #35451 | repetition_detection 引擎层兜底机制 |
| vLLM PR #32443 | EOS token 未生效修复 |
4. 修复方案
4.1 方案迭代
方案 1: 调整采样参数 (min_p=0.2) — ❌ 废弃
| 参数 | 原值 | 方案 1 | 思路 |
|---|---|---|---|
min_p |
0.0 | 0.2 | 裁剪低概率 token |
temperature |
0.6 | 0.8 | 增加分布多样性 |
top_k |
20 | 40 | 扩大候选范围 |
repetition_penalty |
1.2 | 1.05 | 降低惩罚 |
测试结果:
- 48 项完整测试: 47 通过, 1 死循环
- F5 (重复 50 次) 单独测 20 次: 20% 死循环率 (2/10)
- 问题: 偏离 Qwen 官方推荐参数, 高温 + 高 min_p 影响输出质量
方案 2: Qwen 官方参数 + presence_penalty=1.5 + vLLM patch — ✅ 最终方案
核心思路: 保持 Qwen 官方推荐采样参数不变 (保质量), 通过 presence_penalty=1.5 防循环 (Qwen 官方推荐), 手动 patch vLLM bug 让该参数能通过 --override-generation-config 全局生效。
4.2 vLLM Patch (手动修复 #50767)
适用版本: vLLM 0.27.1 | 文件路径根据实际安装位置调整
文件 1: vllm/config/model.py
get_diff_sampling_param() 方法中, available_params 列表添加 presence_penalty 和 frequency_penalty:
# 修改前
available_params = [
"repetition_penalty",
"temperature",
"top_k",
"top_p",
"min_p",
"max_new_tokens",
]
# 修改后
available_params = [
"repetition_penalty",
"temperature",
"top_k",
"top_p",
"min_p",
"max_new_tokens",
"presence_penalty", # ← 新增
"frequency_penalty", # ← 新增
]
文件 2: vllm/entrypoints/openai/chat_completion/protocol.py
修改 1: ChatCompletionRequest 字段默认值从 0.0 改为 None, 让 fallback 逻辑能区分 “客户端未传” 和 “客户端显式传 0”:
# 修改前
frequency_penalty: float | None = 0.0
presence_penalty: float | None = 0.0
# 修改后
frequency_penalty: float | None = None
presence_penalty: float | None = None
修改 2: _DEFAULT_SAMPLING_PARAMS 添加两个参数:
# 修改前
_DEFAULT_SAMPLING_PARAMS: dict = {
"repetition_penalty": 1.0,
"temperature": 1.0,
"top_p": 1.0,
"top_k": 0,
"min_p": 0.0,
}
# 修改后
_DEFAULT_SAMPLING_PARAMS: dict = {
"repetition_penalty": 1.0,
"temperature": 1.0,
"top_p": 1.0,
"top_k": 0,
"min_p": 0.0,
"presence_penalty": 0.0, # ← 新增
"frequency_penalty": 0.0, # ← 新增
}
修改 3: to_sampling_params() 方法中, 在 min_p 的 fallback 逻辑之后添加:
if (presence_penalty := self.presence_penalty) is None:
presence_penalty = default_sampling_params.get(
"presence_penalty",
self._DEFAULT_SAMPLING_PARAMS["presence_penalty"],
)
if (frequency_penalty := self.frequency_penalty) is None:
frequency_penalty = default_sampling_params.get(
"frequency_penalty",
self._DEFAULT_SAMPLING_PARAMS["frequency_penalty"],
)
修改 4: SamplingParams.from_optional() 调用处, 使用解析后的变量而非 self 原始值:
# 修改前
presence_penalty=self.presence_penalty,
frequency_penalty=self.frequency_penalty,
# 修改后
presence_penalty=presence_penalty,
frequency_penalty=frequency_penalty,
注意: 此 patch 在 vLLM 升级 (
pip install --upgrade vllm) 后会被覆盖, 需重新应用。未来 vLLM 合并 PR #35988 或 #51175 后将不再需要。
4.3 最终采样参数
| 参数 | 原始值 | 最终值 | 来源 |
|---|---|---|---|
temperature |
0.6 | 0.6 | Qwen 官方推荐 (thinking 模式) |
top_k |
20 | 20 | Qwen 官方推荐 |
top_p |
0.95 | 0.95 | Qwen 官方推荐 |
min_p |
0.0 | 0.0 | Qwen 官方推荐 (配合 presence_penalty) |
repetition_penalty |
1.2 | 1.0 | 中性值, 不干扰概率分布 |
presence_penalty |
无 | 1.5 | Qwen 官方推荐防循环 |
{"temperature":0.6,"top_k":20,"top_p":0.95,"min_p":0.0,"repetition_penalty":1.0,"presence_penalty":1.5}
4.4 额外防护 (per-request 层, 可选)
vLLM 0.22.1+ 内置 RepetitionDetectionParams, 可在 API 请求中传入作为引擎层兜底:
{
"repetition_detection": {
"max_pattern_size": 20,
"min_pattern_size": 4,
"min_count": 3
}
}
含义: 检测 4~20 token 的 N-gram 模式, 重复 ≥3 次则自动终止生成。
注意: 此参数为 per-request, 无法通过 vLLM 启动参数全局设置, 需在业务调用方统一传入。
5. 验证结果
5.1 测试方法
- 基础测试: 12 类 48 个测试用例, 覆盖短/长文本、思考模式、工具调用、多轮对话、边界条件、特殊字符、温度参数、结构化输出、Agent 场景、递归陷阱、系统提示词、并发测试
- 专项测试: F5 (重复输入 50 次) 连续 20 次, 专门针对死循环触发场景
5.2 测试结果
48 项完整测试
| 测试类别 | 用例数 | 结果 |
|---|---|---|
| A-基础功能 | 5 | ✅ 全通过 |
| B-思考模式 | 5 | ✅ 全通过 |
| C-长文本 | 4 | ✅ 全通过 |
| D-多轮对话 | 3 | ✅ 全通过 |
| E-工具调用 | 3 | ✅ 全通过 |
| F-边界条件 | 5 | ✅ 全通过 (含 F5-重复50次) |
| G-特殊字符 | 4 | ✅ 全通过 |
| H-温度参数 | 4 | ✅ 全通过 |
| I-结构化输出 | 3 | ✅ 全通过 |
| J-Agent场景 | 3 | ✅ 全通过 |
| K-递归陷阱 | 3 | ✅ 全通过 |
| L-系统提示词 | 3 | ✅ 全通过 |
| M-并发测试 | 3 | ✅ 全通过 |
| 总计 | 48 | 48/48 = 100%, 0 死循环 |
F5 专项死循环测试 (连续 20 次)
| 次数 | 结果 |
|---|---|
| 1-10 | ✅ 10/10 正常 (finish_reason=stop) |
| 11-20 | ✅ 10/10 正常 (finish_reason=stop) |
| 总计 | 20/20 = 100%, 0 死循环 |
5.3 方案对比
| 对比项 | 原始配置 | 方案 1 (min_p=0.2) | 最终方案 |
|---|---|---|---|
| temperature | 0.6 | 0.8 | 0.6 (Qwen 官方) |
| top_k | 20 | 40 | 20 (Qwen 官方) |
| min_p | 0.0 | 0.2 | 0.0 (Qwen 官方) |
| repetition_penalty | 1.2 | 1.05 | 1.0 (中性) |
| presence_penalty | 无 | 无 | 1.5 (Qwen 官方推荐) |
| F5 重复50次 (20次) | 死循环 | 20% 死循环 | 0% 死循环 |
| 48项完整测试 | - | 47/48 | 48/48 |
| 质量保持 | - | ❌ 有损 | ✅ 无损 |
| 需要 vLLM patch | 否 | 否 | 是 |
6. 经验总结
6.1 核心教训
-
优先使用模型官方推荐参数: Qwen 官方文档明确推荐
presence_penalty=1.5防循环, 不要自行调参偏离官方值。官方参数经过充分测试, 在质量和稳定性之间取得了最佳平衡。 -
repetition_penalty≠presence_penalty:repetition_penalty: 按频率惩罚, token 出现越多惩罚越大。过高 (如 1.2) 反而加剧循环 — 高频 token 被过度惩罚后, 概率分布推向更窄的低频 token, 形成新的循环。presence_penalty: 按存在性惩罚, token 只要出现过就施加固定惩罚。对防止重复循环更有效, 且不会反向扭曲概率分布。
-
vLLM
--override-generation-config有 bug: 0.27.1 版本静默忽略presence_penalty/frequency_penalty。这两个参数不在available_params白名单中, 写入 JSON 不会生效且无警告。必须手动 patch 或等待官方合并 PR。 -
min_p调整不是万能药:min_p=0.2虽然能降低死循环概率, 但在极端重复输入场景下仍有 20% 死循环率, 且偏离官方参数影响输出质量。应优先使用presence_penalty而非min_p来防循环。 -
多层防护策略: 单一手段无法 100% 防止死循环, 建议三层兜底:
- 服务端全局:
presence_penalty=1.5(通过--override-generation-config+ vLLM patch) - 业务方 per-request:
repetition_detection(N-gram 检测, 自动终止) - 硬限制: 合理的
max_tokens设置 (防止无限生成耗尽资源)
- 服务端全局:
-
vLLM patch 维护: 手动 patch 的代码在
pip install --upgrade vllm后会被覆盖。建议:- 记录 patch 内容和位置 (本文档)
- 升级后检查 patch 是否需要重新应用
- 关注官方 PR #35988 / #51175 合并状态
6.2 排查方法论
- 确认现象: 通过 vLLM metrics / logs 确认是死循环 (持续高吞吐量生成, KV cache 持续增长, 请求不结束)
- 复现问题: 构造最小复现用例 (如"重复 X 次"类 prompt)
- 查阅官方文档: 模型官方 Best Practices 通常有推荐参数和已知问题说明
- 查阅社区 issue: HuggingFace discussions / GitHub issues 搜索 “loop” “repetition” “infinite”
- 验证参数生效: 检查 vLLM 日志中
override_generation_config是否包含目标参数, 确认未被静默忽略 - 迭代测试: 从简单到复杂, 先跑专项测试 (复现用例), 再跑完整测试套件
- 对比方案: 记录每个方案的测试结果, 量化对比 (通过率、死循环率、质量评估)
6.3 适用范围
- 模型: Qwen3 系列 MoE 模型 (包括 Qwen3-235B-A22B, Qwen3-30B-A3B, Qwen3.5-35B-A3B 等)
- vLLM 版本: 0.22.x ~ 0.27.x (presence_penalty bug 在 0.27.1 仍存在)
- 不适用: 非 MoE 架构的 Qwen 模型 (如 Qwen2.5 系列) 可能不需要此修复
7. 快速修复 Checklist
如果你遇到类似问题, 按以下步骤操作:
- 1. 确认死循环现象 (vLLM logs 显示持续生成不停止)
- 2. 构造复现用例 (如
"请重复以下内容50次:XXX") - 3. 检查当前
--override-generation-config参数 - 4. 确认
repetition_penalty是否为 1.2 (需改为 1.0) - 5. 确认是否设置了
presence_penalty(需设为 1.5) - 6. 检查 vLLM 版本是否 < 0.28 (需要 patch)
- 7. Patch
vllm/config/model.py(添加 presence/frequency_penalty 到 available_params) - 8. Patch
vllm/entrypoints/openai/chat_completion/protocol.py(4 处修改) - 9. 更新
--override-generation-config为最终参数 - 10.
systemctl daemon-reload && systemctl restart vllm - 11. 验证日志中
presence_penalty: 1.5已生效 - 12. 运行死循环专项测试 (重复 50 次 prompt × 20 次)
- 13. 运行完整测试套件确认质量无损
- 14. 通知业务方可选添加
repetition_detection作为 per-request 兜底 - 15. 记录 patch 内容, 标记 vLLM 升级时需重新应用
更多推荐



所有评论(0)