Prhub

#54539 [Bugfix][Frontend] Truncate the assistant tokens mask with the prompt

原始 PR 作者 Hotragn 合并时间 2026-09-01 13:53 文件变更 2 提交数 1 评论 4 代码增减 +67 / -11

执行摘要

修复 assistant_tokens_mask 在左截断时与 token_ids 位置错位的 bug,并用统一循环收口同类问题。

当 truncate_prompt_tokens + truncation_side="left" 同时生效时,HfRenderer.render_messages 将 _assistant_tokens_mask 暂存在 prompt 上,随后 apply_post_tokenization 截断 prompt_token_ids 却不触碰 mask。渲染服务器检测到长度不匹配后从右侧盲目裁切 mask,导致左侧截断场景下 mask 与 token_ids 位置错位——mask 标记了错误的 token 位置。这是公开 API 字段 return_assistant_tokens_mask 中的静默数据错误,属于 #54407、#54509 同类缺陷的最后一个遗漏点。

这是一个小而精的 bugfix,附带了有价值的结构性清理。建议精读 _PARALLEL_TO_PROMPT_TOKENS 的设计模式——它将隐式的"哪些字段与 prompt 位置对应"知识显式化,是防止同类缺陷复发的好实践。对于维护 renderer 模块的工程师,值得关注此元组作为未来扩展点。

讨论亮点

核心讨论来自 Issue 评论而非 PR review。维护者 DarkLight1337 要求作者将同类缺陷一次性修复以减少 CI 开销。作者回应这确实是最后一个遗漏点,并详细列举了 vllm/inputs/llm.py 中所有 renderer prompt 类型的字段,证明位置并行字段集合已闭合(prompt_token_ids/prompt_embeds 由 _validate_tokens 处理;prompt_token_offsets 由 #54407 修复;prompt_is_token_ids 由 #54509 修复;token_type_ids 从未由 renderer 填充;_assistant_tokens_mask 由本 PR 修复;其余字段非位置性)。PR review 仅有一条 approve,无技术争议。

实现拆解

  1. 新增模块级常量 _PARALLEL_TO_PROMPT_TOKENS(vllm/renderers/params.py):定义 ("prompt_token_offsets", "prompt_is_token_ids", "_assistant_tokens_mask") 元组,明确列出所有与 prompt token 位置一一对应的字段,作为 apply_post_tokenization 统一截断的依据。

  2. 重构 apply_post_tokenization(vllm/renderers/params.py):移除原先分别针对 prompt_token_offsets 和 prompt_is_token_ids 的 if is not None 代码块,替换为遍历 _PARALLEL_TO_PROMPT_TOKENS 的统一循环,对每个 key 调用 _truncation_slice 并就地截断。_assistant_tokens_mask 作为第三个元素被自动覆盖。

  3. 更新 _truncation_slice 文档(vllm/renderers/params.py):docstring 从列举具体字段名改为引用 _PARALLEL_TO_PROMPT_TOKENS,确保未来新增并行字段只需修改元组一处。

  4. 新增回归测试(tests/entrypoints/scale_out/render/test_render.py):test_chat_render_assistant_tokens_mask_follows_truncation 先渲染完整 prompt 获取 full_mask,再以 truncation_side="left" 截断后比较截断后的 mask 是否等于 full_mask 的尾部切片,并前置断言头尾切片不等以防对称 mask 误通过。测试基于 HTTP 请求,无需 GPU。

文件 模块 状态 重要度
vllm/renderers/params.py 渲染参数 modified 6.44
tests/entrypoints/scale_out/render/test_render.py 渲染测试 modified 5.93

关键符号

apply_post_tokenization _truncation_slice

关键源码片段

vllm/renderers/params.py core-logic

核心修复文件:新增 _PARALLEL_TO_PROMPT_TOKENS 元组,重构 apply_post_tokenization 将 _assistant_tokens_mask 纳入统一截断循环

# Prompt keys whose entry i describes token i of the prompt, so they must be
# reduced with the same slice as the prompt tokens themselves. Anything added
# here is truncated by `TokenizeParams.apply_post_tokenization`.
# `_assistant_tokens_mask` is renderer-internal: `HfRenderer.render_messages`
# stashes it on the prompt so `_process_tokens` can move it onto the engine
# input.
_PARALLEL_TO_PROMPT_TOKENS = (
    "prompt_token_offsets",
    "prompt_is_token_ids",
    "_assistant_tokens_mask",
)
​
# ... (within apply_post_tokenization)
​
        # 统一处理所有与 prompt token 位置一一对应的字段:
        # 用 _truncation_slice 计算截断切片,就地截断每个并行字段。
        # 新增并行字段只需在 _PARALLEL_TO_PROMPT_TOKENS 元组中添加名称。
        prompt_dict = cast(dict, prompt)
        for key in _PARALLEL_TO_PROMPT_TOKENS:
            parallel = prompt_dict.get(key)
            if parallel is None:
                continue
            truncation = self._truncation_slice(tokenizer, len(parallel))
            if truncation is not None:
                prompt_dict[key] = parallel[truncation]
​
        return prompt

评论区精华

同类缺陷批量修复 设计

维护者要求将同类 truncation 漏洞一次性修复以减少 CI 开销。作者回应这是最后一个遗漏点,并完整枚举了 renderer prompt 类型的所有字段,证明并行字段集合已闭合。

结论:作者通过重构为 _PARALLEL_TO_PROMPT_TOKENS 元组收口,确保未来新增并行字段不会遗漏截断。维护者接受方案并 approve。 · 已解决

风险与影响

变更仅涉及 vllm/renderers/params.py 中 apply_post_tokenization 的内部实现,将重复的 if-else 块替换为循环,逻辑等价(原有两个 key 加上新增的 _assistant_tokens_mask)。风险点:(1) cast(dict, prompt) 后写入 _assistant_tokens_mask 这个 TypedDict 未声明的 key,类型安全性略降,但该 key 本身就是 renderer 内部约定,不影响外部接口;(2) _PARALLEL_TO_PROMPT_TOKENS 是模块级不可变 tuple,无并发风险;(3) 下游 _process_tokens 依赖 mask 在此时仍与 token_ids 并行,截断在此时机(多模态占位符扩展之前)执行是正确的,不会影响后续逻辑。

直接影响使用 return_assistant_tokens_mask: true + truncate_prompt_tokens + truncation_side: left 的渲染服务器用户——此前返回的 mask 标记了错误位置的 token,现在正确对齐。对所有其他使用场景(无截断、右截断、不请求 mask)无行为变化。变更封闭了一类系统性缺陷,降低了未来新增并行字段时再次引入同类 bug 的概率。

静默数据错误修复 类型安全性微降

关联 Issue

#54364 [Bugfix][Frontend] Truncate pooling prompts before padding them

完整报告

参与讨论