[RFC]: Request level text and derender output on `/inference/v1/generate`

这个报错通常出现在把 vLLM 拆成 render → generate → derender 三段式(P/D 分离部署)后,调用 /inference/v1/generate 时带有文本或 derender 输出需求的场景。核心问题不是代码 bug,而是该接口目前只按 "tokens" 模式返回

快速结论:这个报错通常出现在把 vLLM 拆成 render → generate → derender 三段式(P/D 分离部署)后,调用 /inference/v1/generate 时带有文本或 derender 输出需求的场景。核心问题不是代码 bug,而是该接口目前只按 "tokens" 模式返回 token IDs,流式 derender 需要额外的 HTTP 往返与解析器重放。优先排查生成服务是否启用了 tokenizer、请求的 output_mode 是否被支持,以及是否仍在依赖独立的 /derender 接口。

适用环境:Issue 中已确认的基准环境为 1×A100-40G、Qwen3-8B 模型、hermes 与 qwen3 parsers,运行在 GKE 上;按进程用 /proc 采样 CPU 的负载窗口为 300–420 秒。Python、CUDA、PyTorch、vLLM 具体版本号在 Issue 中未给出。

最快修复方案:暂无确认的一步修复方案。该 Issue 是 RFC 讨论,提出的 output_mode: Literal["tokens","text","derender"] 字段属于设计提案,尚未在讨论中确认落地版本。可优先尝试的方向是:在 decode 池加载 tokenizer 而非使用 --tokens-only,并确认 SamplingParams.detokenize 未被启动参数覆盖。

注意事项:该方案仍是提案,不能保证在所用 vLLM 版本中已实现;若仅关闭 --tokens-only,需要同时考虑 GPU pod 上的 CPU 开销。讨论中测得常规 text/derender 流量下前端 CPU 未超过 0.25 个核,但 top_logprobs=20 会升至 0.83 核,这是唯一接近 GPU 瓶颈的负载形态。此外当前流式 derender 在主分支上不输出 logprobs,logprob 密集型负载可能无法通过该路径完成。

问题场景

用户在 vLLM 的 prefill/decode(P/D)分离部署中使用三段式链路:/render 把 OpenAI 请求转成 GenerateRequest,/inference/v1/generate 在 GPU 层运行并返回 token IDs,再由 /v1/chat/completions/derender 或 /v1/completions/derender 转回 OpenAI 响应。当调用方希望在一次 /inference/v1/generate 请求中同时完成“输入 token IDs、输出文本/derender 结果”时,会遇到该接口只支持 "tokens" 输出的限制。对延迟敏感、尤其是流式的交互流量,这会退化为每个 SSE chunk 一次 POST 往返,且在解析路径上每个 chunk 重发完整 chat_request,解析器状态每 chunk 靠重放全部输出 token 重建。

报错原文

[RFC]: Request level text and derender output on `/inference/v1/generate`

原因分析

这是设计层面的能力缺口,不是运行时异常。讨论中的关键点:

  • tokenizer 是否加载目前被 --tokens-only(即 skip_tokenizer_init)在启动级固定,而“本请求是否 detokenize”本可由请求级的 SamplingParams.detokenize 控制,后者默认 True 并已在线路上传输。启动参数覆盖请求值,是让 detokenization 看起来像启动级决策的唯一原因。
  • 流式 derender 层是无状态的逐块翻译器,没有存活请求可挂状态,因此每次调用都要重新解码窗口、对累积文本重跑解析器。基准中匹配负载下,derender 层的 detokenize 与 parse 成本约为进程内 CPU 的 9 倍,多千 token 推理输出的 E2E p50 上升约 15%。
  • 该成本随响应长度增长,对应 #50550 描述的 O(n³) 字符工作量。
  • 另一处部署现象:derender 层的解析器重放运行在 renderer executor 上,renderer_num_workers 默认为 1,高流式并发下该层会饱和并丢弃连接。这属于扩容问题,不直接构成本 RFC 的反对依据。

可优先尝试的判断:若 decode 服务实际已加载 tokenizer,生成 handler 仍只从 output.token_ids 构造响应并丢弃 output.text,那么问题在于响应构造路径未暴露文本,而非缺 tokenizer。

环境排查

  • 确认 generate 服务启动参数:是否带 --tokens-only / --enable-scale-out;P/D 中 prefill 池应保持 tokens-only,decode 池应加载 tokenizer。
  • 确认请求中 GenerateRequest.sampling_params 的 detokenize 值是否被启动参数覆盖。
  • 确认所用 vLLM 版本是否已包含 output_mode 字段;该字段在 Issue 中仅为提案。
  • 确认 GPU 型号与显存(讨论基准为 1×A100-40G)。
  • 确认模型与解析器:Qwen3-8B、hermes、qwen3 parsers;若使用 harmony/gpt-oss 等更重的解析器,讨论中提示 CPU 数据可能变化。
  • 确认负载形态:是否大量使用 top_logprobs;宽 logprob 是唯一测得接近 GPU 瓶颈的形态。
  • 确认是否设置了 renderer_num_workers,其默认值 1 在高并发解析重放下可能成为瓶颈。

解决步骤

  1. 先区分是“缺 tokenizer”还是“有 tokenizer 但未输出文本”:检查 decode 池启动命令,若含 --tokens-only,在 decode 池去掉它并为该池加载 tokenizer;prefill 池保持 --tokens-only。
  2. 检查请求侧:确认 sampling_params.detokenize 为 True,且没有被 --tokens-only 一类的启动级设置覆盖。
  3. 若目标是单次调用返回文本,确认当前版本是否支持请求级 output_mode。不支持时,该能力暂无现成开关,需等待或跟进该 RFC 的落地。
  4. 在支持的版本上,按讨论结论先用 output_mode="text" 同时覆盖流式与非流式(可优先尝试),derender(含推理与工具解析)作为后续阶段。
  5. 若沿用独立 derender 层,调整 renderer_num_workers 以匹配流式并发,避免单线程解析器重放导致连接被丢弃。
  6. 对 logprob 密集型流量,讨论建议保留 output_mode="tokens" 并走外部 detokenize,避免把宽 logprob 的 CPU 成本压到 GPU pod。
  7. 如需 /abort_requests 在 decode 池可用,确认其随 generate 服务一起暴露;讨论要求凡提供 generate 的池都应可用。

验证方法

  • 对同一次生成,比对 output_mode="tokens" 与 "text" 两种返回:token IDs 再 detokenize 的结果应与直接返回的文本一致(除 stop string 边界情况外)。
  • 关注 stop string:引擎可能从返回文本中移除 stop string 但保留其 token IDs,重解码会带回被排除文本;奇偶校验测试需覆盖该差异。
  • 流结束场景:验证末尾含缓冲文本、或 finish reason 无新 token IDs 时,输出仍能到达客户端;显式 abort 路径也要覆盖。
  • 负载侧:观察前端 CPU 是否在预期范围(讨论中 text/derender 流量不超过 0.25 核),以及 E2E p50 相对三段式是否不再出现约 15% 的上升。
  • 监控 derender/renderer 层是否仍有连接被丢弃,确认 worker 数调整后恢复。

参考来源

vllm-project/vllm #56851

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 32232

发表回复

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