Prhub

#48145 [Frontend] Reuse prefill token ids on the decode chat path for disaggregated serving

原始 PR 作者 eicherseiji 合并时间 2026-07-29 16:02 文件变更 6 提交数 18 评论 6 代码增减 +198 / -13

执行摘要

decode 端复用 prefill token ids 跳过重复 tokenization

在 prefill 和 decode 分离的服务架构中,prefill 阶段渲染 prompt 并 tokenize,router 将相同 chat 请求转发给 decode 阶段,导致 decode 再次渲染和 tokenize,对长 prompt 造成 decode 关键路径上的延迟(见 PR body)。decode 不需要重新 tokenize,因为 prefill 响应已包含 prompt_token_ids,且 router 已转发状态。本变更使 decode chat 路径使用转发的 ids 并跳过渲染和 tokenize。

值得阅读。PR 展示了在保持 chat 特性(工具调用、推理、流式)的同时,如何最小侵入性地实现 decode 端 token 复用的设计模式。安全讨论指出了未来改进方向(添加 server-side gate)。对于从事分离式部署或前端优化的人员有参考价值。

讨论亮点
  • JeffreyWang88:要求 Harmony 测试覆盖 → 作者 eicherseiji 回应已增加 test_make_request_with_harmony_reuses_kv_transfer_prompt_token_idstest_serving_chat.py,决议为已满足。
  • depthfirst-app[bot] HIGH 安全警告:用户控制的 token ids 绕过 chat template → 指出 preprocess_chat 中用户通过 kv_transfer_params.prompt_token_ids 构造的 token ids 会完全跳过 chat template,可能绕过安全提示和内容过滤,且无服务器端门控检查限制仅在分离部署使用。建议增加检查但未被采纳。状态未解决。
  • depthfirst-app[bot] MEDIUM 安全警告:用户控制的 token ids 绕过 Harmony safety processing → 类似问题在 _make_request_with_harmony 中,建议门控但未采纳。状态未解决。
  • NickLucche:对接口透明性的保留合并 → 合入 reviewer 认为将 ids 放在 kv_transfer_params 中不够清晰,但可以合并直到标准 token-in-out API 成熟;并建议补充文档(已完成)。

实现拆解

  1. 提取复用 token ids:在 vllm/renderers/online_renderer.py 新增顶层函数 _reused_prompt_token_ids(request),从 request.kv_transfer_params 字典中弹出 prompt_token_ids,返回 ids 列表或 None。弹出 key 确保 ids 不会进入 engine 的 sampling metadata。

  2. preprocess_chat 短路:在非 Harmony 路径中,若存在复用 ids,则直接通过 tokens_input(reuse_ids, cache_salt=...) 构造 engine input,跳过 render_chat_async(即跳过 chat templating 和 tokenization)。conversation 设为空列表,但后续 adjust_request 钩子(处理工具选择、推理参数等)仍然执行,保证结构化输出和约束生效。

  3. _make_request_with_harmony 短路:针对 Harmony (GPT-OSS) 模型,在函数入口检查复用 ids,若存在则直接构造 token 类型 engine input 并返回空 conversation。Harmony 无 adjust_request 钩子,无需额外处理。

  4. 测试覆盖:在 test_chat_completion.py 添加两个测试验证非流式和流式下复用 ids 的正确性(比较 prompt_token_ids 等);在 test_serving_chat.py 添加 Harmony 单元测试。

  5. 文档与 CI 配置:更新 docs/features/disagg_prefill.md 增加使用示例;在 .buildkite/test-amd.yamltest_areas/rust_frontend.yaml 中排除新测试,因 Rust 前端不支持此能力。

文件 模块 状态 重要度
vllm/renderers/online_renderer.py 渲染层 modified 7.47
tests/entrypoints/openai/chat_completion/test_chat_completion.py 聊天测试 modified 6.71
tests/entrypoints/openai/chat_completion/test_serving_chat.py 服务测试 modified 5.84
docs/features/disagg_prefill.md 文档 modified 3.08
.buildkite/test-amd.yaml CI 配置 modified 2.5
.buildkite/test_areas/rust_frontend.yaml CI 配置 modified 2.5

关键符号

_reused_prompt_token_ids preprocess_chat _make_request_with_harmony

关键源码片段

vllm/renderers/online_renderer.py core-logic

核心变更:新增 `_reused_prompt_token_ids` 函数提取并弹出 token ids;在 `preprocess_chat` 和 `_make_request_with_harmony` 中添加短路分支,复用 forwarded ids 跳过 tokenization。

# vllm/renderers/online_renderer.pydef _reused_prompt_token_ids(request: Any) -> list[int] | None:
    """从 request.kv_transfer_params 中弹出并返回 forwarded prompt token ids。    用于分离式部署的 decode 端跳过重复 tokenization。弹出 key 可以防止 ids
    流入 engine 的 sampling metadata。
    """
    kv = getattr(request, 'kv_transfer_params', None)
    if not isinstance(kv, dict):
        return None
    return kv.pop('prompt_token_ids', None) or None
​
​
class OnlineRenderer:
    async def preprocess_chat(self, request, *, skip_mm_cache=False):
        # ... 前面处理 ...
        reuse_ids = _reused_prompt_token_ids(request)
        if reuse_ids:
            conversation: list[ConversationMessage] = []
            engine_input = tokens_input(
                reuse_ids, cache_salt=getattr(request, 'cache_salt', None)
            )
        else:
            (conversation,), (engine_input,) = await renderer.render_chat_async(
                [messages], chat_params, tok_params, prompt_extras={...},
                skip_mm_cache=skip_mm_cache,
            )
        # 继续 adjust_request 等处理 ...

评论区精华

要求 Harmony 测试覆盖 测试

jeffreywang88 在 review 中要求在 _make_request_with_harmony 添加测试(vllm/renderers/online_renderer.py:213)。

结论:作者 eicherseiji 在 f9f64fd5 提交中增加了 test_make_request_with_harmony_reuses_kv_transfer_prompt_token_ids,位于 test_serving_chat.py。已满足。 · 已解决

安全性:用户控制的 token ids 绕过 chat template 安全

depthfirst-app[bot] 指出 preprocess_chat 中用户从 kv_transfer_params 传入的 token ids 会跳过 chat template 渲染和 tokenization,可能绕过安全系统提示和内容过滤,且无服务器端门控限制仅在分离部署使用。建议增加 has_kv_connector 检查。严重度为 HIGH。

结论:未在 PR 中解决;作者和合入者未回应此安全担忧,PR 仍被合并。建议后续跟进添加门控(如检查 self.kv_connector 是否存在)。 · unresolved

安全性:用户控制的 token ids 绕过 Harmony safety processing 安全

depthfirst-app[bot] 指出在 _make_request_with_harmony 中用户提供的 token ids 会绕过 Harmony 的安全处理(系统 preamble、工具处理等),建议门控。严重度为 MEDIUM。

结论:未解决。PR 已合并,风险待后续处理。 · unresolved

接口透明性与路径确认 设计

合入者 NickLucche 评论:将 token_ids 放在 kv_transfer_params 中导致接口不透明且不易发现,但同意避免重复 tokenization 的重要性,愿意在 token-in-out API 标准化前合并此 PR。建议补充文档。

结论:作者已补充文档(docs/features/disagg_prefill.md),PR 合并。接口设计留待后续标准 API 改进。 · 已解决

风险与影响

  1. 安全风险 (HIGH):用户可通过 kv_transfer_params.prompt_token_ids 传入任意 token ids,完全绕过 chat template 中的安全系统提示和内容过滤。该路径没有服务器端检查是否实际启用了 KV connector(即分离部署模式),因此即便在单体部署中也可被利用。需要增加服务器端门控(如检查 has_kv_connector)来限制此路径。
  2. 兼容风险 (MEDIUM):该功能依赖 kv_transfer_params 中的 prompt_token_ids key,若客户端在不分离环境中误设置此 key,可能导致预期外的跳过 template 行为。但不会导致崩溃。messages 参数虽被忽略,但请求仍需包含 messages
  3. 测试覆盖风险 (LOW):Harmony 分支的测试仅是单元级别而非端到端;贪婪解码不可重现导致无法直接验证生成文本语义等价。但基本功能已验证。
  4. 性能风险 (LOW):正面效应显著,无负面风险。

用户影响层面:仅对使用分离式部署且需要 decode 端 chat 输出的用户有益。未改变公开 API,无新增字段,但使用了 kv_transfer_params.prompt_token_ids 内部 key。系统影响层面:decode 路径减少一次完整 tokenization 和 template 渲染,对长 prompt 可显著降低 TTFT(time-to-first-token)。内存占用无变化。团队影响层面:需与其他相关工作(如 #47161 流式解渲染)协调,但当前变更作为增量推进,无破坏性。

缺少服务端门控检查 安全绕过风险 仅在分离部署下有收益 Harmony 测试非端到端

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论