
vLLM Derenderer API 深度解析在无 GPU 前端把 token 重新还原为 OpenAI 兼容响应【免费下载链接】vllmA high-throughput and memory-efficient inference and serving engine for LLMs项目地址: https://gitcode.com/GitHub_Trending/vl/vllm导读Derenderer API 是 vLLM 服务化scale-out体系中与 Renderer API 相对的后处理接口/render负责把用户请求向前编码成 token ID而/derender负责把 token-in / token-out 引擎产出的GenerateResponse向后还原为完整的 OpenAI 兼容响应——包括反 token 化detokenization、推理过程reasoning解析与工具调用tool call解析全程无需 GPU。阅读本文后你将掌握 Derenderer 的请求/响应协议、字段语义、payload 校验边界、无 GPU 闭环链路render → generate → derender的完整搭建方法以及其底层复用 vLLM 解析器、实现与vllm serve输出对齐的原理。背景Derenderer 解决什么问题在传统的vllm serve架构里请求的预处理分词、多模态输入处理与生成结果的 token 解码、reasoning/tool call 解析都发生在同一个进程中。Derenderer 所属的规模外扩scale-out方案希望把这一过程拆开前端无 GPU 化预处理tokenization、多模态输入处理与后处理detokenization、工具调用解析、reasoning 解析全部运行在没有 GPU 的前端服务上引擎 token 进出化推理引擎变成纯粹的 token-in / token-out 服务与请求的前后处理彻底解耦可参考 文档中 Renderer 的表述从而支撑 llm-d、Dynamo 及各类自定义前端在不上推理引擎的前提下复用 vLLM 的前后处理逻辑解析器对齐Parser ParityDerenderer 复用了 vLLM 的 tool 与 reasoning 解析器因此一个分离部署disaggregated deployment所产出的content/reasoning/tool_calls拆分结果与标准vllm serve服务完全一致。从代码结构看这一整套能力落在vllm/entrypoints/scale_out/目录下其中 derender 子目录含api_router.py与serving.py正是本主题的 HTTP 路由与业务实现而核心算法类OnlineDerenderer位于 vllm/renderers/online_derenderer.py。Pipelinerender → generate → derender 三段闭环文档给出如下链路图render generate derender request ───────────────▶ token_ids ─────────▶ token_ids ──────────▶ response (chat / (GPU less) (token-in / (GPU less) (OpenAI completion) │ token-out engine) ▲ compatible) └─────────────── request prompt_tokens ──┘这里的核心洞察是derender 阶段需要的远不止引擎返回的token_ids。它还必须消费原始chat_request/completion_request以及从 render 阶段一路携带过来的prompt_tokens见下文 Request format因为 tool / reasoning 解析器需要完整的请求上下文request.tools、request.tool_choice、_grammar_from_parser等才能正确切分输出。从源码看GenerateResponse本体只承载输出 token、usage 与可选的 prompt_logprobsprotocol.pytoken 计数信息由调用方在 derender 请求中补齐这与 OpenAI 响应中 prompt/completion token 的统计需求是对应的。API 参考两个非流式端点Derenderer 提供两个 HTTP 端点均托管在无 GPU 的 render server由vllm launch render启动参见 vllm/entrypoints/cli/launch.py 中的命令定义上与/render端点同台共存端点输入输出POST /v1/chat/completions/derender单个完整的GenerateResponse单个ChatCompletionResponsePOST /v1/completions/derender一组GenerateResponse每个 prompt 一个单个CompletionResponse路由注册位于 api_router.py两个路径都带有validate_json_request依赖并在错误时返回 BAD_REQUEST / NOT_FOUND / INTERNAL_SERVER_ERROR 错误模型。关于端点的启用条件来自 factories.py 的逻辑专用vllm launch render服务总是暴露/render与/derender端点VLLM_ENABLE_SCALE_OUT_ENDPOINTS未设置或设为1若显式将其设为0会与vllm launch render或--tokens-only冲突启动时报ValueError标准推理服务vllm serve默认不暴露这些端点需要显式启用VLLM_ENABLE_SCALE_OUT_ENDPOINTS1 vllm serve ...。在 serving.py 中ServingDerender类实现了四个处理入口derender_chat_response、derender_completion_response以及各自的流式版本细节见下文。重要约束非流式的一次性解析当前 derender 端点是非流式的请求体必须携带完整的GenerateResponse包含全部 token ID随后做一次性解析。流式 derender 需要独立的端点设计官方文档明确说明目前尚未支持、但已列入后续规划is in the pipeline。值得一提的是仓库代码中已经出现了流式 derender 的脚手架DerenderChatStreamRequest/DerenderCompletionStreamRequest等模型定义于 protocol.pyOnlineDerenderer也提供了derender_chat_stream/derender_completion_stream实现。从代码可推断这套流式协议采用无服务器端会话的设计——客户端把DerenderStreamState状态对象在相邻两次调用间来回携带每个 SSE chunk 触发一次 derender 调用服务端返回chunk 更新后的 stream_state。其中增量解码只消费 token 尾巴窗口计算量是 O(delta)通信量被窗口大小限制累计 O(n) 而非全历史回传的 O(n²)DerenderStreamState 文档注释DerenderStreamState携带prev_tokens受 1024 上限约束、prefix_offset、read_offset、role_sent等纯 JSON 字段流式路径对配置了 reasoning/tool parser 的模型会fail closed抛出NotImplementedError提示改用streamfalse的解析输出且每个 chunk 至多一个 choice多 choice 会破坏共享的解码窗口。因此在当前版本中生产使用应以两个非流式端点为标准。Request format请求体结构每个 derender 请求把引擎的GenerateResponse们与还原最终响应所需的调用方元数据封装在一起。两份协议模型的精确定义位于 protocol.py源码中以derender-chat-request/derender-completion-request标签标注的片段正是文档用 mkdocs-snippets 嵌入的同一段代码。/v1/chat/completions/derenderDerenderChatRequeststream: Literal[False] False # 固定非流式 model: str | None None # 服务名缺省时用服务端已 serve 的模型名 generate_response: GenerateResponse # 待 derender 的完整 token-in / token-out 引擎响应 prompt_tokens: int | None None # 用于 usage 统计的 prompt token 数省略时默认 0 # GenerateResponse 只含输出 token调用方 # 手里已有 len(GenerateRequest.token_ids) chat_request: ChatCompletionRequest | None None # 来自 /render 的原始post-adjust_request # ChatCompletionRequesttool/reasoning 解析器 # 需要完整请求上下文tools、tool_choice、 # _grammar_from_parser 等对应实现见 protocol.py。/v1/completions/derenderDerenderCompletionRequeststream: Literal[False] False model: str | None None generate_responses: list[GenerateResponse] # 每个 prompt 一个响应与 # /v1/completions/render 返回的 # list[GenerateRequest] 一一对应 prompt_tokens: list[int] | None None # 每个响应一个 prompt token 数各自缺省为 0 # 若提供则 len(prompt_tokens) 必须等于 # len(generate_responses)有模型校验器强约束 completion_request: CompletionRequest | None None # 来自 /render 的原始 CompletionRequest # 供解析器使用对应 chat 侧 chat_request对应实现见 protocol.py。payload 超限保护400oversized payload 会在任何tokenizer.decode()或 parser 执行之前被拒绝并返回400。_validate_derender_bounds方法serving.py逐项校验generate_responses的数量与每个响应中choices的数量都不能超过VLLM_MAX_N_SEQUENCES每个choice.token_ids与logprobs.content的长度不能超过服务端的max_model_lentop_logprobs数量不能超过ModelConfig.max_logprobs语义与默认值同ModelConfig定义每个响应的prompt_logprobs长度同样不能超过max_model_len。这一层防护是必要的请求体中的 token 结构完全由调用方提供若不设限超大的伪造 token 序列可能在 CPU/内存侧造成资源耗尽。端到端示例render → generate → derender下面的例子来自原文档完整演示了一个 chat 请求如何穿过无 GPU 的 render server/render、/derender与 token-in / token-out 引擎/inference/v1/generate走完整个闭环。先启动两个服务分别占用 8100 与 8200 端口vllm launch render meta-llama/Llama-3.2-1B-Instruct --port 8100 VLLM_ENABLE_SCALE_OUT_ENDPOINTS1 vllm serve \ meta-llama/Llama-3.2-1B-Instruct --tokens-only --port 8200然后运行客户端脚本import httpx MODEL meta-llama/Llama-3.2-1B-Instruct RENDER http://localhost:8100 # vllm launch render ... ENGINE http://localhost:8200 # token-in / token-out engine chat_request { model: MODEL, messages: [{role: user, content: What is 22?}], max_tokens: 32, } with httpx.Client(timeout60.0) as client: # 1. Render: request - token IDs (GPU less) generate_request client.post( f{RENDER}/v1/chat/completions/render, jsonchat_request ).json() prompt_tokens len(generate_request[token_ids]) # 2. Generate: token IDs - token IDs (token-in / token-out engine) generate_response client.post( f{ENGINE}/inference/v1/generate, jsongenerate_request ).json() # 3. Derender: token IDs - ChatCompletionResponse (GPU less) response client.post( f{RENDER}/v1/chat/completions/derender, json{ model: MODEL, generate_response: generate_response, prompt_tokens: prompt_tokens, chat_request: chat_request, }, ).json() print(response[choices][0][message][content])要点拆解prompt_tokens来自 render 返回的token_ids长度因为它只存在于 render 侧第 3 步传入原始chat_request后derenderer 会运行配置好的 tool 与 reasoning parser于是response[choices][0][message]携带的content/reasoning/tool_calls拆分与标准vllm serve完全一致省略chat_request则退化为纯 detokenization仅输出文本不做解析拆分。内部原理OnlineDerenderer做了什么端点的业务逻辑收敛在 vllm/renderers/online_derenderer.py 的OnlineDerenderer类中。它在构造时通过ParserManager.get_parser(...)L67-L73依据tool_parser/reasoning_parser/enable_auto_tools等配置解析出与标准 serve 完全相同的 parser 实例这是解析器对齐的根本保证。chat 非流式路径_derender_chatL104-L203对GenerateResponse的每个 choice校验token_ids非空否则抛ValueError若请求带 logprobs调用_resolve_logprobs把 token 侧携带的token_id:N占位符解析成真实字符串对 byte-fallback 场景出现的 UFFFD字符用前序最多 4 个 token 作为上下文在_correct_decoded_token中修正与 vLLM v1 引擎的LogprobsProcessor._correct_decoded_token行为一致Parser 路径当 parser 已配置且chat_request提供时使用skip_special_tokensFalse解码——这是为了让 parser 能看到/think、tool_call乃至 Harmony 信道 token 等特殊标记L122-L152随后用chat_request.build_chat_params(...)组装的 chat template kwargs 构造 parser执行parser.parse(decoded_text, chat_request, ...)得到reasoning / content / tool_calls三元组若请求include_reasoningFalse则丢弃 reasoning每个 tool call 被赋予random_uuid()生成的 ID对命名或required的tool_choicecontent为空时兜底为空串保持与 serve 响应格式一致纯 detokenization 路径无 parser 或未传chat_request时直接解码并遵循请求的skip_special_tokens无请求时默认True即丢弃特殊 token。completion 非流式路径_derender_completionL383-L434处理多 prompt 场景对generate_responses逐一解码并聚合成CompletionResponseChoice若带 logprobs先把 chat 形态的 per-token logprobs 转成 completion 所需的平行扁平结构_convert_chat_logprobs_to_completion_logprobs含text_offset/token_logprobs/top_logprobs字段。usage 统计服务端在聚合响应时从请求与GenerateResponse重组 usageprompt_tokens取调用方传入值缺省 0completion_tokens由各 choice 的token_ids长度求和total_tokens为二者之和serving.py。响应对象id、model、created则分别取自引擎返回的request_id、服务端模型名与当前时间戳。CPU 密集任务与事件循环detokenization、logprob 解析与 parsing 都是 CPU 密集操作OnlineDerenderer通过make_async(..., executorrenderer._executor)把这些同步工作一步投递到线程池保证 FastAPI 事件循环不被阻塞L88-L95。解析器对齐与一致性验证仓库中的测试可以佐证 Derenderer 的对齐承诺tests/entrypoints/scale_out/derender/test_derender.py端到端验证 derender 请求处理与响应结构tests/entrypoints/scale_out/derender/test_derender_parity.py专门验证 derender 输出的content/reasoning/tool_calls拆分与标准 serve 路径的一致性parser paritytests/entrypoints/scale_out/derender/test_derender_stream.py覆盖流式 derender 的增量解码与状态机行为。这组测试既印证了disaggregated 部署与vllm serve输出一致的设计目标也为二次开发 derender 自定义解析器提供了可直接运行的参考。使用建议与注意事项非流式主路径当前生产场景请使用streamfalse默认的两个端点确保generate_response携带完整 token 列表需要解析拆分时务必传原始请求chat_request/completion_request是 tool/reasoning parser 的上下文来源缺省则只能得到纯文本prompt_tokens尽量从 render 阶段携带否则 usage 中的 prompt token 计为 0影响计费与监控口径paylod 边界受VLLM_MAX_N_SEQUENCES、max_model_len与max_logprobs约束超限请求会在解析前被400拒绝无需自行重复校验模型上下文保持一致render server跑 tokenizer 与 parser与 token-in / token-out 引擎应使用同一模型如示例中的meta-llama/Llama-3.2-1B-Instruct并确保 derender 请求中的model与服务端 served model name 对应。延伸阅读前处理对应接口Renderer APIs/render与启动方式、VLLM_ENABLE_SCALE_OUT_ENDPOINTS开关协议模型与请求结构定义token_in_token_out/protocol.py服务端路由与业务实现derender/api_router.py、derender/serving.py核心后处理算法vllm/renderers/online_derenderer.py服务初始化与开关逻辑scale_out/factories.py、vllm/envs.py【免费下载链接】vllmA high-throughput and memory-efficient inference and serving engine for LLMs项目地址: https://gitcode.com/GitHub_Trending/vl/vllm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考