[Feature]: Integer token IDs for logprobs in `/inference/v1/generate` responses (`GenerateLogProbs`)

这是一个 /inference/v1/generate 返回体形状(schema)变更追踪 Issue,核心报错/变更点围绕 [Feature]: Integer token IDs for logprobs in `/inference/v1/generate` responses (`Gener

快速结论:这是一个 /inference/v1/generate 返回体形状(schema)变更追踪 Issue,核心报错/变更点围绕 [Feature]: Integer token IDs for logprobs in `/inference/v1/generate` responses (`GenerateLogProbs`)。如果你在 generate 直连消费方看到 logprobs 里是 "token_id:N" 这类占位字符串,或者发现 Python 与 Rust 前端返回的 bytes 不一致,优先排查你依赖的是解析占位字符串的旧逻辑,而不是整数 token_id 新契约。

适用环境:Issue 中确认涉及的组件为 vLLM 的 Python /inference/v1/generate(scale_out/token_in_token_out)与 Rust 前端 routes/inference/generate,以及 derender(批量与流式 OnlineDerenderer);覆盖流式与非流式两种模式。Issue 未给出具体 Python、CUDA、GPU 或依赖版本,这些项目不做补写。

最快修复方案:暂无确认的一步修复方案。该 Issue 的已确认结论是:在单个版本中直接替换字段并附 release note,不提供 opt-in 开关,也不保留第二字段;rank 保留,top_logprobs 使用 list 而非 dict。可优先尝试的适配方向是:把消费方从解析 "token_id:N" 占位字符串改为直接读取整数 token_id。

注意事项:字段替换后,仅读取 content[i].logprob 的代码可继续工作;依赖 token 字符串或 bytes 的直连消费者需要同步改造。return_tokens_as_token_ids 属于 /v1/chat/completions 与 /v1/completions 的用户可见 OpenAI 选项,本次明确不在范围内,不要混改。Issue 中讨论的 GenerateLogProbs.sampled 属于后续叠加 PR(#57442)的内容,本 Issue 尚未包含,使用前需确认实际版本是否已合入。

问题场景

用户通过 vLLM 的 /inference/v1/generate 接口(token in / token out)获取输出 logprobs,无论是走 Python 后端还是 Rust 前端、流式还是非流式,都会遇到两类现象:一是返回体中的 logprobs 沿用 OpenAI 的 ChatCompletionLogProbs 形状,token 字段是字符串;二是 generate server 本身没有 tokenizer,只能把每个 token 写成 "token_id:N" 占位符,再由 derender 反向解析出 token id。直接读取 generate 响应(例如 RL rollout 采集器)的客户端因此拿到的是占位字符串而非整数 ID,并且引擎已经计算出的 rank 被丢弃。

报错原文

[Feature]: Integer token IDs for logprobs in `/inference/v1/generate` responses (`GenerateLogProbs`)

GenerateResponseChoice.logprobs and GenerateResponseStreamChoice.logprobs are ChatCompletionLogProbs where `token` is a string.

The generate server has no tokenizer, so it writes every token as a `"token_id:N"` placeholder and derender parses the placeholders back out.

The two frontends already disagree on `bytes`. Python leaves `bytes` unset. Rust sets it to the UTF-8 bytes of the placeholder itself (`"token_id:42"`).

原因分析

最可能的原因是协议层形状设计问题,而不是运行时报错。generate 是 vLLM 自定义的 token in / token out API,但其输出 logprobs 复用了 OpenAI 的字符串 token 形状,导致 OpenAI 形状泄漏进 vLLM 协议。由于 generate server 没有 tokenizer,无法直接产出真实 token 文本或字节,只能写入 "token_id:N" 占位符;编码侧依赖 Python 的 _create_tokens_logprobs 与 Rust 前端的 format_token_id,解码侧依赖 resolve_token_id_placeholder 与 _parse_token_id_placeholder,schema 中没有任何字段声明 token 到底承载什么,属于无类型字符串契约。此外,同一 GenerateResponse 上的 prompt_logprobs 已经是整数键(list[dict[int, Logprob] | None]),输出 logprobs 却不是,形成同响应两种形状的不一致。Python 与 Rust 对 bytes 的处理不同,说明任何解析该占位格式的消费者本身就会拿到与前端相关的行为,这也是讨论中支持“一次替换”而非过渡字段的理由。

环境排查

  • 确认你使用的 vLLM 版本中 /inference/v1/generate 属于 Python 还是 Rust 前端实现,两者对 bytes 的处理不同。
  • 确认请求是流式还是非流式:GenerateResponseChoice.logprobs 与 GenerateResponseStreamChoice.logprobs 都受影响。
  • 确认调用参数中的 logprobs 取值:logprobs=0 与 logprobs>0 在后续 sampled 方案下的返回语义不同。
  • 确认消费方是否依赖 "token_id:N" 占位字符串、token 字符串字段或 bytes 字段。
  • 确认是否同时使用了 return_tokens_as_token_ids:该选项属于 /v1/chat/completions 与 /v1/completions,与本 Issue 范围无关。
  • 确认 derender 路径(批量或 OnlineDerenderer)是否参与转换,以及是否涉及跨 chunk 的 logprob_context_token_ids 上下文。

解决步骤

  1. 先判定你的角色:如果你只是消费 generate 响应,按新契约把取值从 token 字符串改为整数 token_id,并停止解析 "token_id:N" 占位符。
  2. 如果代码仅读取 content[i].logprob,按 Issue 结论可继续工作,无需改动;如果读取 content[i].top_logprobs[j],注意 top_logprobs 采用 list 且保持 rank 顺序,不要假设为 dict。
  3. 若需要 rank,直接使用新形状中的 rank 字段(可为 None),不要再从占位字符串顺序反推。
  4. 如果依赖 bytes:不要再假设它存在或与前端无关,新形状中去掉了 bytes,真实 token 文本与字节由 derender 用带 U+FFFD 修正的 _correct_decoded_token 逻辑填充。
  5. 如果你维护 generate 服务端实现:需要同时改 Python 与 Rust 两条 generate 路径以及 derender 的批量与流式转换,并附带测试与 release note;按讨论结论采用单版本替换字段,不加 opt-in 开关、不加第二字段。
  6. 如果你在等 sampled 快速路径(logprobs=0 时不构造逐 token 对象):这属于 #57442 的范围,需等 GenerateLogProbs 形状落地后再 rebase 使用,不要在本 Issue 范围内预期该字段存在。
  7. 如果涉及流式 derender 的 U+FFFD 修正:确认 logprob_context_token_ids 已携带前置采样 token ID 上下文(#55029),因为流式中这些 ID 出现在更早的 chunk 里,否则修正结果可能不正确。

验证方法

改造后重新请求 /inference/v1/generate(流式与非流式各测一次),确认输出 logprobs 中是整数 token_id 而非 "token_id:N" 字符串;确认 top_logprobs 是按 rank 顺序的 list;确认 rank 字段可用;确认返回体中不再出现 bytes 字段。同时用同一请求分别走 Python 与 Rust 前端,检查两者不再出现 bytes 行为不一致。若消费链路包含 derender,还需确认经由 ChatCompletionLogProbs / CompletionLogProbs 转换后,token 与 bytes 由 tokenizer 正确填充且 U+FFFD 修正行为与改造前一致。

参考来源

vllm-project/vllm #57574

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 28023

发表回复

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