Prhub

#42748 [Bugfix] Expose usage field in GenerateResponse for disaggregated serving

原始 PR 作者 AIvashov 合并时间 2026-07-01 18:00 文件变更 2 提交数 3 评论 8 代码增减 +4 / -2

执行摘要

暴露 disagg 响应中 usage 等字段

disaggregated serving 场景下,客户端需要获取 token 用量信息(尤其是 prompt_tokens_details.cached_tokens)以监控缓存命中率并优化前缀复用。但 GenerateResponse 缺少 usage 字段定义,导致服务端已计算的值被 Pydantic 丢弃,modelcreated 也面临同样问题。PR body 明确写道:'The usage field was missing from the GenerateResponse model, so pydantic silently dropped it.' Issue 评论中用户 @codeByJiaDong 提供了详细复现证据,确认该 bug 影响 vLLM 0.20.0。

值得精读的样板级小 bugfix:展示了如何通过 Pydantic 模型定义缺少字段导致数据丢失,以及 AI 辅助审查能快速发现连带字段缺失和参数名不匹配等隐蔽问题。对维护 disagg 相关代码的开发者尤其有参考价值。

讨论亮点

Gemini Code Assist 自动审查指出两个问题:一是 GenerateResponse 除了 usage 外还缺少 modelcreated 字段;二是 serving.py 中传参 id=request_id 与模型字段名 request_id 不匹配,导致实际 request ID 被随机 UUID 替代。这两点均在后续提交中修复。此外无人工争议,reviewer @NickLucche 最终批准合并。

实现拆解

  1. protocol.py 中补充字段定义:为 GenerateResponse 类添加 model: str | None = Nonecreated: int | None = Noneusage: UsageInfo | None = Field(default=None) 三个可选字段。其中 usage 使用 Field(default=None) 保持与 GenerateStreamResponse 一致。
  2. 修复构造函数调用的参数名:在 serving.pyserve_tokens_full_generator 方法中,将构建 GenerateResponse 时的 id=request_id 改为 request_id=request_id,确保实际 request ID 被正确传入,而非使用模型默认的随机 UUID。
  3. 对齐流式与非流式响应:流式响应 GenerateStreamResponse 早已包含 usage 字段,本次改动使非流式 GenerateResponse 与之对等,modelcreated 也是 GenerateStreamResponse 已有的字段。
文件 模块 状态 重要度
vllm/entrypoints/scale_out/token_in_token_out/protocol.py 入口层 modified 5.11
vllm/entrypoints/scale_out/token_in_token_out/serving.py 入口层 modified 4.82

关键符号

serve_tokens_full_generator

关键源码片段

vllm/entrypoints/scale_out/token_in_token_out/protocol.py core-logic

核心修复所在:为 GenerateResponse 类新增 usage、model、created 三个可选字段,使其与 GenerateStreamResponse 对等,并防止 Pydantic 静默丢弃数据。

# vllm/entrypoints/scale_out/token_in_token_out/protocol.pyclass GenerateResponse(BaseModel):
    request_id: str = Field(
        default_factory=lambda: f"{random_uuid()}",
        description=(
            "The request_id related to this request. If the caller does "
            "not set it, a random_uuid will be generated. This id is used "
            "through out the inference process and return in response."
        ),
    )
    # 新增 : model 与 created 之前被构造时传入但被 Pydantic 静默丢弃
    model: str | None = None
    created: int | None = None
    choices: list[GenerateResponseChoice]
    # 新增 : 与 GenerateStreamResponse 保持一致的 usage 字段
    # 确保 prompt_tokens_details.cached_tokens 等信息能被客户端接收
    usage: UsageInfo | None = Field(default=None)
    prompt_logprobs: list[dict[int, Logprob] | None] | None = None
    kv_transfer_params: dict[str, Any] | None = Field(
        default=None,
        description="KVTransfer parameters used for disaggregated serving.",
    )

评论区精华

GenerateResponse 缺少 model 和 created 字段 正确性

Gemini Code Assist 指出 : 'In addition to usage, the GenerateResponse model is missing the model and created fields which are explicitly passed during instantiation in serving.py (lines 304-305). Without these fields defined in the Pydantic model, they are silently dropped.'

结论:作者随后补充了 model 和 created 字段到 GenerateResponse 中。 · 已解决

id=request_id 参数名不匹配 正确性

Gemini Code Assist 指出 : 'serving.py passes id=request_id, but the model defines request_id. This results in the request_id field being populated by its default factory.'

结论:作者将 serving.py 中的 id=request_id 修正为 request_id=request_id。 · 已解决

风险与影响

风险极低:新增字段均为 Optional 并带 None 默认值,不会破坏现有调用方;仅修正构造函数传参名,不改变任何计算逻辑;变更仅涉及 2 个文件共 4 行新增、2 行删除。唯一的边缘情况是,如果外部代码显式检查 GenerateResponse 的字段集合(如 __fields__),新增字段可能引发意外行为,但此类使用极少且属于良性变更。

直接影响:disaggregated serving 场景下,使用 GenerateResponse 的客户端现在能正确获得 usage(含 prompt_tokens_details.cached_tokens)、modelcreated 字段,同时 request_id 不再被错误替换为随机值。影响范围限于 vllm/entrypoints/scale_out/token_in_token_out/ 模块下的非流式生成响应,不涉及其他 API 端点或核心推理逻辑。

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论