[Bug]: Wide speculative decoding verify (num_speculative_tokens ≥ 7) samples EOS at `prompt_len+8` on hybrid GDN models (Qwen3.5): silent ea

在 vLLM 启用投机解码(speculative decoding)跑 Qwen3.5 这类 hybrid GDN 模型时,可能在第 10 个 token 附近静默提前停止( finish_reason=stop ),输出在 EOS 行之前逐字节干净;Issue 讨论最终判定为 bf16 下 EO

快速结论:在 vLLM 启用投机解码(speculative decoding)跑 Qwen3.5 这类 hybrid GDN 模型时,可能在第 10 个 token 附近静默提前停止(finish_reason=stop),输出在 EOS 行之前逐字节干净;Issue 讨论最终判定为 bf16 下 EOS 与 DATA 的近乎平局造成的数值差异(numerical variance),不是投机解码的正确性 bug,暂无可靠的“一步修复”手段。

适用环境:Issue 报告的原始环境为 Ubuntu 22.04.4 (WSL2)、Python 3.11.13、PyTorch 2.13.0+cu130、CUDA 13.0、双 RTX 4090;复现还涉及 NVIDIA B200(v0.31.0 与 nightly 77154ca6)以及 v0.32.0 stock pip wheel + RTX 4090。模型为 Qwen3.5(hybrid GDN / gated delta net),投机方式涉及 ngram_gpu(V1 / Model Runner V2)与 MTP(V2),使用 greedy、enforce_eager=True、开启前缀缓存、max_tokens=96 等配置。

最快修复方案:暂无确认的一步修复方案。Issue 维护者复现后给出的结论是数值方差、无法在此修复;若用于生产且不能接受静默截断,可优先尝试降低投机宽度(例如不使用 K≥7 的配置)或用非投机 greedy 路径对照验证输出,但这些都是排查手段,不是官方修复。

注意事项:投机解码本身不保证与非投机解码逐 bit 一致,此类 EOS/DATA 平局会随 kernel 路径、verify 宽度、chunking、GPU 架构、精度(bf16 vs fp32)改变符号。B200 上 K≥7 阈值并不成立(K=7 停、K=8 继续),说明简单调 K 不能稳定规避。v0.32.0 上 MTP 路径(K=1 与 K=8)仍有 10/10、33/33 的确定性截断,且 eager 下也会出现,说明翻转面并未收窄。hf transformers fp32 参考与 bf16 参考给出相反 top-1,是该现象的关键证据。

问题场景

用户在 vLLM 上以投机解码方式服务 Qwen3.5 系列 hybrid GDN 模型(gated delta net),投机方式为 ngram_gpu 或 method="mtp",并设置较宽的 verify 宽度(num_speculative_tokens ≥ 7)。请求在生成到 prompt_len+8 附近(报告中具体为第 10 个输出 token 之后)被静默判为 stop,输出在 EOS 行前逐字节正确,例如停在 "[DATA] id=42 name=" 之后,未报任何引擎异常,只表现为提前截断、finish_reason=stop 而非 length。该现象在 ngram_gpu V1 与 stock MTP V2 两条 model runner 路径上都复现过。

报错原文

[Bug]: Wide speculative decoding verify (num_speculative_tokens ≥ 7) samples EOS at `prompt_len+8` on hybrid GDN models (Qwen3.5): silent early stop, byte-clean until the EOS row — reproduced on both model runners (`ngram_gpu` V1, stock MTP V2)

原因分析

讨论中维护者在 B200 上复现后发现:非投机 plain greedy 在同一 prompt 上也会在此位置输出 EOS;HF transformers 的 fp32 参考在 logprob @ pos 1854 上 EOS(16.47)高于 DATA(15.80),而 bf16 参考则相反(DATA 16.50 > EOS 15.81)。因此更可能的原因是在 bf16 下 EOS 与 DATA token 的 logits 近乎平局,其符号取决于归约顺序(reduction order)——即 kernel 路径、verify 宽度、chunking、GPU 架构等,而不是投机解码本身算错。投机解码不保证与非投机解码 bitwise 一致,所以这类差异在数值上属于预期范围。此外,v0.32.0 上 MTP 路径的截断仍然存在(K=8 为 10/10,K=1 eager 也为 10/10,compiled + cudagraph_mode=NONE 为 33/33),且每次截断输出的 token 序列完全相同,说明是确定性的数值平局而非随机抖动。

环境排查

  • 确认 vLLM 版本:原始报告为 v0.31.0,后续测试覆盖 nightly main 77154ca6 与 stock v0.32.0 pip wheel。
  • 确认 Python 与 PyTorch:报告中为 Python 3.11.13、PyTorch 2.13.0+cu130、CUDA 13.0。
  • 确认显卡与驱动:RTX 4090 ×2(NVIDIA 驱动 591.44)以及 B200;WSL2 / Ubuntu 22.04.4。
  • 确认投机配置:method="mtp" 或 ngram_gpu,num_speculative_tokens(K=1/6/7/8)是否为奇数或 ≥7。
  • 确认执行模式:是否 enforce_eager=True、是否启用 CUDA graphs(cudagraph_mode=NONE)、是否开启前缀缓存(enable_prefix_caching)。
  • 确认采样参数:是否为 greedy;报告中 max_tokens=96,prompt 为 1845 tokens。
  • 确认是否使用 out-of-tree patch:v0.32.0 的测试为 stock wheel,未打补丁。

解决步骤

  1. 先用同一 prompt、同一 greedy 配置在关闭投机解码(plain greedy)下跑一遍,记录是否同样在 prompt_len+8 附近 stop,并对比 finish_reason 与 token 序列。报告显示非投机路径也可能在此位置停止,这一步用于判断问题是否与投机解码直接相关。
  2. 对照 ngram_gpu 的不同 K 值(如 1、6、7、8)与 MTP 路径分别测试,记录每次的 token 数量、finish_reason 和输出尾部。注意报告显示 K≥7 阈值并不稳定(B200 上 K=7 停、K=8 继续),不要据此认定单一 K 值一定安全。
  3. 检查截断处是否有引擎异常或日志告警;报告中为静默截断、无 exception,仅有 finish_reason=stop,需从响应与 /metrics acceptance 计数确认走的是哪条路径。
  4. 若需判断是否为数值平局,可在相同前缀上用更高精度(如 fp32 HF transformers 参考)检查 EOS 与邻近 DATA token(报告中为 17247)的 logprob 差值;差值很小时说明属于平局范围。
  5. 若业务不能接受静默截断:可优先尝试避开宽 verify 配置(例如不使用 K≥7),或对输出做完整性校验(检查是否在预期位置出现 stop),但这只是规避手段。Issue 被关闭为数值方差,归档状态未提供官方修复。

验证方法

用同一 prompt 重复请求(报告中 MTP 路径 10/10、33/33 均一致),确认输出 token 序列与 finish_reason 是否稳定。若切换配置后输出恢复为 96 tok / length,说明该配置下平局未触发;若仍在同一绝对位置出现 stop 且尾部 token 序列一致,则说明仍命中同一数值平局,而非随机错误。同时对比非投机 greedy 的结果,若两者在同一位置表现相同,则更支持数值方差结论。

参考来源

vllm-project/vllm #61738

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 29668

发表回复

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