[Bug]: ngram speculative decoding default prompt_lookup_min=2 causes tool-call output corruption on Qwen3-class models with structured outpu

该问题通常出现在 vLLM 使用 ngram 推测解码(speculative decoding)、对 Qwen3 级别模型发起带 tools/tool_choice 的请求、且启用结构化输出(tool-call)时,表现为约一半请求的工具调用输出被污染(出现 << 、 parameter=para

快速结论:该问题通常出现在 vLLM 使用 ngram 推测解码(speculative decoding)、对 Qwen3 级别模型发起带 tools/tool_choice 的请求、且启用结构化输出(tool-call)时,表现为约一半请求的工具调用输出被污染(出现 <<、parameter=parameter= 等错误 token 级联)。优先排查点不是采样器或解析器,而是 ngram 推测解码在混合 GDN 模型上的生成端状态问题,以及在 TurboQuant + FULL cudagraph 组合下的捕获/重放路径。

适用环境:Issue 中确认的环境包括 vLLM nightly(0.19.2rc1.dev205+g07351e088 与 0.26.1rc1.dev1424+gdafbef15a)、模型 Qwen3.6-35B-A3B-FP8(混合线性注意力 + MoE + 全注意力层)、硬件 2×RTX A5000(Ampere SM 8.6,TP=2)以及 A800(Ampere SM 8.0)、B200;KV cache turboquant_k8v4;推测配置 method=ngram, num_speculative_tokens=3, prompt_lookup_max=4, prompt_lookup_min=2;解析器 qwen3_coder / qwen3;启用 --enable-chunked-prefill 与 --enable-prefix-caching。A800 侧驱动为 580.126.09、torch 2.11+cu130。

最快修复方案:暂无确认的一步修复方案。Issue 中最初提出的 prompt_lookup_min=8 配置修复被后续复核否定:它之所以看似有效,是因为阈值过高导致 ngram 在短 tool-call 输出上几乎不匹配、推测解码实际被关闭,并非真正修复;在重复性文本上 FULL + min=8 仍会污染。可优先尝试的隔离性验证是:改用 MTP(同模型下 num_speculative_tokens=3 为 0/20 污染)或直接禁用推测解码(0/30 污染)以确认污染是否由 ngram 路径引入。

注意事项:prompt_lookup_min=8 是工作负载相关(workload-dependent),不是通用修复;在不同硬件与不同 cudagraph 模式下结论不一致。A800 侧结论指向 FULL cudagraph 捕获 TurboQuant spec-verify attention(enforce_eager 与 PIECEWISE 保持干净),而 B200 侧复核显示 --enforce-eager 无效(18/20 污染),因此两条路径可能都存在,需按自己的栈单独隔离,不要直接照搬阈值。

问题场景

用户在 vLLM 上加载 Qwen3 级别模型(Qwen3.6-35B-A3B-FP8,混合线性注意力 + MoE + 全注意力层),启用 ngram 推测解码,并通过 /v1/chat/completions 发起带 tools 数组和 tool_choice=auto 的请求。在默认 prompt_lookup_min=2 下,工具调用输出高概率被破坏:正确的 chat-template 格式是 <tool_call>\n<function=NAME>\n<parameter=KEY>\nVALUE\n</parameter>,实际却出现 <tool_call>\n<<tool_code>\nprint(...)、parameter=parameter=name、<<argname>...<argvalue> 等错误 token 级联。复现不需要并发:batch size 1 的单个串行请求即可触发,30 次里约 22 次污染。

报错原文

[Bug]: ngram speculative decoding default prompt_lookup_min=2 causes tool-call output corruption on Qwen3-class models with structured outpu

<function=get_weather\ncity="Tokyo"\n</parameter>
get_weather(city="Tokyo")
get_weather\n<parameter=city
get_weather(city="Tokyo")\n</parameter>
"Tokyo"Tokyo"

原因分析

后续复核否定了“拒绝采样(rejection sampling)导致输出分布改变”的初始假设:在 temperature=0 下,ngram 贪心会在每个被验证位置写入目标模型的 argmax,推测解码与不推测按构造应逐 token 相同,采样器无法产生这类污染。用 return_token_ids 观察发现原始文本本身就已经损坏(缺少 >、think 块内出现重复片段如 "Tokyo"Tokyo"、重复的 </think>\n\n<tool_call>\n<function=get_weather 片段),这是生成端的状态/记账指纹。

可能原因有两条,需按环境区分:一是 ngram 推测解码在混合 GDN 模型上的状态/记账问题——污染是 ngram 特有的(同模型 MTP 干净、dense 模型同 ngram 配置干净),且 ngram 被强制路由到 V1 model runner,改用 VLLM_USE_V2_MODEL_RUNNER=0 重新验证 MTP 仍然干净,说明 runner 不是变量;二是 FULL cudagraph 捕获了 TurboQuant 的 spec-verify attention(A800 侧观察到 temp=0 下 turboquant_3bit_nc + ngram + cudagraph_mode=FULL_AND_PIECEWISE 退化坍缩,而 enforce_eager 与 PIECEWISE 干净)。此外在 stock main 上,TurboQuant + FULL cudagraph 需要先打上 #44053 才能在 _decode_attention 通过 workspace-lock 断言。

环境排查

  • 确认 vLLM 版本与安装方式(nightly 构建号),是否已包含 #40738、#36138、#40783、#39055 等相关修复。
  • 确认模型是否为 Qwen3 类混合 GDN 模型(如 Qwen3.6-35B-A3B-FP8),并确认 dense 模型对照是否干净。
  • 确认推测解码配置:method=ngram、num_speculative_tokens、prompt_lookup_max、prompt_lookup_min 的实际取值。
  • 确认 cudagraph 模式(FULL / FULL_AND_PIECEWISE / PIECEWISE / NONE)与是否使用 --enforce-eager。
  • 确认 KV cache 类型(如 turboquant_k8v4 / turboquant_3bit_nc),以及是否需要 #44053 才能启动。
  • 确认硬件与驱动:A5000(SM 8.6)、A800(SM 8.0,driver 580.126.09)、B200,以及 torch 版本(如 2.11+cu130)。
  • 确认 --enable-chunked-prefill、--enable-prefix-caching 是否开启(复核中 --no-enable-prefix-caching 仍为 20/20 污染)。
  • 确认解析器与模板参数:--tool-call-parser qwen3_coder、--reasoning-parser qwen3、enable_thinking 取值。

解决步骤

  1. 先做隔离实验,判断污染是否由 ngram 引入:同一模型同一请求,分别测试 ngram 推测、MTP 推测(num_speculative_tokens=3)、完全禁用推测解码,各跑 20–30 次统计污染率。Issue 复核数据为 MTP 0/20、无推测 0/30、ngram 22/30。
  2. 若确认是 ngram 路径,测试 num_speculative_tokens=1(复核为 6/20),观察污染率是否显著下降,用于缩小是否与草稿长度相关。
  3. 若怀疑 TurboQuant + cudagraph 路径,对比 cudagraph_mode=FULL_AND_PIECEWISE、PIECEWISE 与 enforce_eager 三种模式下的污染率。注意 A800 与 B200 结论不一致:A800 侧 enforce_eager/PIECEWISE 干净,B200 侧 --enforce-eager 仍为 18/20 污染。
  4. 可优先尝试的配置修复:prompt_lookup_min=8。但必须理解其生效机制是抑制 ngram 匹配、等效于关闭推测,在短 tool-call 提示上才接近 100% 干净;在重复性文本上 FULL + min=8 仍会污染,因此不要把它当作通用解。
  5. 打开 return_token_ids,直接查看原始文本是否已损坏(缺 >、重复 span、重复 <tool_call> 片段),以排除是解析器把正确文本解析错的可能。
  6. 按 A800 侧路径确认已应用 #44053,否则 TurboQuant + FULL cudagraph 可能在 _decode_attention 处因 workspace-lock 断言而无法运行。
  7. 若需要规避,可优先尝试在 ngram 场景下禁用 prefix caching、或改用 MTP / 关闭推测解码,直到上游修复落地。

验证方法

用 Issue 给出的复现脚本或客户端循环发送 20–30 次单请求(batch size 1),统计 function name 与 arguments 是否干净:arguments 不以 { 开头、或包含 << 或 parameter=,即判为污染;function name 不等于 get_weather(如 get_weather(city="Tokyo")、get_weather\n<parameter=city)即判为污染。切换配置后污染率应显著变化;理想状态为 0/30。若仅把 prompt_lookup_min 提到 8 而污染率下降,还要在重复性文本提示上复测,确认不是仅仅关闭了推测。

参考来源

vllm-project/vllm #40875

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 27482

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注