快速结论:该报错出现在 vLLM 的 GatedDeltaNet(GDN)路径上,当一个调度批次里全部都是长度为 1 token 的行、且其中至少有一行是首次 chunk(num_computed_tokens == 0)时,共享的 GDNAttentionMetadataBuilder 会把这条无状态的首 chunk 误判成 decode,导致读取未清零的线性注意力状态槽。优先排查单 token 批次是否被错误分类为 decode、以及 has_initial_state 是否被置为 None。
适用环境:Issue 中确认涉及 vLLM 的 vllm/v1/attention/backends/gdn_attn.py 共享 builder;受影响的模型包括 Qwen3-Next、Qwen3.5、OLMo-hybrid、InternS2-Mobius、Bailing-MoE-v3 以及独立的 Kimi-Linear(KimiLinearForCausalLM),其 MTP 与 drafter 变体复用同一个 base builder。评论中已验证的复现环境为 backport 镜像 lazymio/vllm-backport、PyTorch 2.13.0+cu130、NVIDIA CMP170HX/SM80、eager 模式、无投机解码;这不是官方 vLLM 0.12.0 包含相同模型代码的声明。
最快修复方案:暂无确认的一步修复方案。#51483 只修复了 KimiK3KDAMetadataBuilder 子类,base builder 及其他模型不受该改动影响。评论中验证过的关键实验是:把共享非投机路径的 split 改为 split_decodes_and_prefills(m, decode_threshold=1, treat_short_extends_as_decodes=False) 后,builder 报告 0 个 decode、1 个 prefill、has_initial_state=[False],两种 poison 都得到正确输出 2.828125。但该一行改动与尚未合并的 #51565 并不相同,可优先作为验证手段尝试,而不是正式替代方案。
注意事项:评论中已验证的一行改动会把“续接的短 prefill”也走 chunk 计算路径,与 #51565 更精细的 no_prior_state/padding/graph-staging 逻辑不同。GPU FULL-graph replay、已建立的 decode、续接 prefill、混合请求以及其他 GDN 模型消费者均未在该独立复现中验证;该受控 poison 实验本身也没有真正触发调度器页面重分配或证明私有数据被提取。Issue 作者表示已直接验证误分类与无条件 decode 读取,但尚未跑出端到端生成、证明回收 block 仍持有前一请求状态,因此“状态继承”应视为代码路径推论而非已观测失败。
问题场景
在使用 vLLM 运行基于 GatedDeltaNet 的线性注意力模型时触发,例如 Qwen3-Next、Qwen3.5、OLMo-hybrid、InternS2-Mobius、Bailing-MoE-v3,以及独立的 Kimi-Linear(KimiLinearForCausalLM,其 KimiGatedDeltaNetAttention 没有像 Kimi-K3 那样覆写后端)。这些模型及其 MTP、drafter 变体共用同一个 GDNAttentionMetadataBuilder。
触发条件是某个调度步的批次全部由单 token 行组成,且其中至少包含一条首次 chunk(num_computed_tokens == 0)。在默认 flag 下这些模型解析为 mamba_cache_mode="align",此时一个与 decode 一同被接纳的普通单 token prompt 就是可达的触发路径;budget 截断的首 chunk 还额外受 _mamba_block_aligned_split 限制,只有当 align 关闭或 long_prefill_token_threshold / max_num_batched_tokens 小于 mamba block size 时才会降到 1 个 token。只要批次里存在任何更长的行,就不受影响。
报错原文
[Bug]: GatedDeltaNet metadata builder classifies a stateless first chunk as a decode (same root cause as #51483)
该 Issue 描述的并非异常堆栈,而是元数据构建阶段的逻辑错误:真实 GDNAttentionMetadataBuilder.build 在 seq_lens=[100, 50, 1]、query_lens=[1, 1, 1]、is_prefilling=[False, False, True] 下返回 3 个 decode、0 个 prefill、has_initial_state=None,与 #51483 修复前 Kimi 子类的错误分类一致。
原因分析
最可能的原因(Issue 中有明确代码路径依据):
gdn_attn.py:213调用split_decodes_and_prefills(m, decode_threshold=1)时使用默认treat_short_extends_as_decodes=True,因此所有query_len <= 1的行都被算作 decode,包括从未被 forward 过的行(num_computed_tokens == 0)。has_initial_state只在num_prefills > 0时计算(gdn_attn.py:391-392)。全单 token 批次全是 decode,num_prefills == 0,于是has_initial_state为None。- decode 路径读取 recurrent state 槽时没有
has_initial_state门控、也没有零填充。OLMo 层在num_decodes > 0分支调用fused_recurrent_gated_delta_rule(initial_state=ssm_state, ssm_state_indices=non_spec_state_indices_tensor, ...)(olmo_gdn_linear_attn.py);Qwen 层在相同分支调用fused_sigmoid_gating_delta_rule_update/fused_recurrent_gated_delta_rule_packed_decode,initial_state=ssm_state,同一分支的 conv 更新(causal_conv1d_update)也以同样方式读取槽;prefill 路径则按has_initial_state门控,会清零无状态槽。 - Mamba 风格 state page 在重新分配时不会清零:mamba spec 不在 worker 侧的 zero-on-allocation 集合中(
single_type_kv_cache_manager.py:86)。
综合起来,一条刚被接纳、其 block 之前归另一条请求所有的请求,会读到那条请求遗留的 recurrent 与 conv 状态,而不是零状态。这本身是正确性缺陷,也是一条受污染(NaN)状态块被新请求继承的路径。 Issue 作者已直接验证误分类与无条件 decode 读取,但尚未跑出端到端生成来证明回收 block 仍持有前一请求状态。
另外,评论中给出的 model-free poison 证据显示:真实 builder 看到 query length 1、computed context 0、is_prefilling=True 时选中了一个真实 state slot,而原 builder 报告 1 个 decode、0 个 prefill、has_initial_state=None。用合成投影和被 poison 为 2 或 6 的 cache slot 调用 Glm5NextLinearAttention._forward,输出分别为 7.875 和 18.0,而正确 fresh-state 输出应为 2.828125,说明仅改变逻辑上新 cache 的旧内容即可改变输出与结果 cache。
这与 #49918 / PR #47123 是不同缺陷。
环境排查
- 确认 vLLM 版本与提交,检查是否包含 #51483 的改动;该改动只覆盖
KimiK3KDAMetadataBuilder子类,不覆盖 base builder。 - 确认运行模型是否属于受影响列表:Qwen3-Next、Qwen3.5、OLMo-hybrid、InternS2-Mobius、Bailing-MoE-v3、独立 Kimi-Linear 及其 MTP/drafter 变体。
- 确认
mamba_cache_mode取值,默认 flag 下这些模型为"align"。 - 确认是否出现全单 token 批次、且含
num_computed_tokens == 0的首次 chunk。 - 评论中验证环境参考:PyTorch 2.13.0+cu130、NVIDIA CMP170HX/SM80、eager、无投机解码、backport 镜像
lazymio/vllm-backport。不要将其直接等同于官方 vLLM 0.12.0。
解决步骤
- 复现并确认分类错误:在目标模型上运行单 token 全 decode 批次,观察
GDNAttentionMetadataBuilder.build是否返回 0 prefills、has_initial_state=None,同时is_prefilling中却含True。 - 检查共享非投机 split 的调用:
gdn_attn.py中split_decodes_and_prefills(m, decode_threshold=1)是否使用了默认treat_short_extends_as_decodes=True。 - 作为验证手段(可优先尝试,非正式修复),把该调用改为
split_decodes_and_prefills(m, decode_threshold=1, treat_short_extends_as_decodes=False),观察 builder 是否变为 0 decodes、1 prefill、has_initial_state=[False],且 poison 输出回到正确值。 - 不要直接把这一行改动当作最终方案:它与尚未合并的 #51565 不同,会把续接短 prefill 也送进 chunk 计算路径。若需正式修复,应等待或跟进 #51565 的
no_prior_state/padding/graph-staging 逻辑。 - 若目标部署涉及 Kimi-K3 的 KDA 路径,确认 #51483 已合并;注意它不覆盖本 Issue 的 base builder 范围。
验证方法
在单 token、eager、无投机的受控场景下,用合成投影和被 poison 的 cache slot 调用对应层的 _forward,检查输出是否始终为 fresh-state 参考值(评论中为 2.828125),且完整 output/conv/recurrent cache 与独立 dyadic 参考一致,包括未选中槽保持不变。同时确认 builder 输出为 0 decodes、1 prefill、has_initial_state=[False]。
评论中的复现脚本 SHA256 为 e3aaf38b824cc95ab3aa8bce88fcf9e7aca840b89e6472f05a5b58ac9f57ae0f,保存为 upstream-repro-gdn.py,在所述 backport 环境中运行 OMP_NUM_THREADS=2 MKL_NUM_THREADS=2 OPENBLAS_NUM_THREADS=2 MAX_JOBS=2 python3 upstream-repro-gdn.py。该脚本只覆盖 fresh 单 token eager 场景,不覆盖 GPU FULL-graph replay、已建立 decode、续接 prefill、混合请求或其他 GDN 模型消费者。
参考来源
相关:vllm-project/vllm PR #51565(尚未合并的修复方向)
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。
![[Bug]: Streaming responses broken since 0.14.0 for ContextChatEngine and similar classes](https://www.chat-gpts.plus/wp-content/uploads/2026/09/22749-8310a4fe-768x403.jpg)

