已关闭
[RFC]: MindIE Motor 接入 vLLM Render/Derender,演进至 Token-in/Token-out 推理架构 #479
yilunh创建于 8月13日关闭于 4 天前
8月13日 添加了label:rfc
wangyang
8月14日 评论:
8月14日 评论:
👋 您好,感谢向 mindie-motor 提交 Issue!
🎉 我们已收到您的反馈,感谢你对开源社区的支持!
📅 处理时效 维护团队将在工作日 24 小时内查看并回复您的问题。
🔍 自助排查(推荐优先查看) 在等待回复期间,您可以先查阅仓库README以及历史 Issue 中相似问题的解决方案,多数问题可快速解决。
💡 为了更快定位问题,请您确保 Issue 包含:
清晰的问题描述
可复现的操作步骤
相关日志、截图或环境信息
我们会尽快跟进,感谢您的理解与配合!


此处折叠了17条事件消息 查看更多
4 天前 添加了label:resolved
Coordinator 接入 vLLM Render 的 Token In/Token Out Serving RFC
需求描述
背景
当前 MindIE Motor 由 Coordinator 作为推理服务统一入口,负责接收 OpenAI 请求、执行请求流控、选择推理实例,并根据部署拓扑完成 P/D 或 Union 调度。请求被选定实例后,仍以 OpenAI 请求的形式发送给 vLLM API Server,由 vLLM 在 NPU 推理实例内部完成 Chat Template 渲染、Tokenization、模型推理、Detokenization,以及 Reasoning 和 Tool Call 等后处理。
OpenAI-compatible 请求在 Coordinator、Prefill Engine 和 Decode Engine 之间传递时,各组件可能重复执行协议解析、模板渲染和分词。重复 Tokenize 会增加 CPU 开销和首 Token 延迟,也可能因模板或 tokenizer 配置差异导致 Coordinator、P 实例和 D 实例得到不同的 Token 序列。
vLLM 提供的 Render API 可以将标准 OpenAI 请求转换为包含 Prompt Token IDs、Sampling Parameters 等信息的 GenerateRequest;Derender API 则完成相反方向的转换,将 Engine 返回的生成 Token IDs 转换为 OpenAI Response,并复用 vLLM 的 Detokenizer、Reasoning Parser 和 Tool Parser。
vLLM Render 将 Frontend Preprocessing 从推理引擎中拆出:
这与 Motor 的 P/D 解耦架构相匹配:Coordinator 仍掌握调度、重试和请求生命周期,Engine 不再重复执行 Frontend 处理。因此,本 RFC 提议在 MindIE Motor 中引入 Render → Generate → Derender 三阶段处理链路,将请求前后处理与 NPU 推理解耦,并逐步把 Engine 演进为纯 Token-in/Token-out 服务。
本方案分阶段落地:
术语说明
为避免混淆,本文统一使用以下术语:
本文所称 Frontend,统一指 Render/Derender 所在的无 NPU 前后处理层;本文所称 Engine,统一指执行模型推理的 Token Engine,不包含 OpenAI 协议拼装职责。
目标态
本需求的目标不是在当前请求转发链路前简单增加一次
/render调用,而是重新明确 OpenAI Frontend、Motor Coordinator 和 Token Engine 三者之间的职责边界。目标态下,客户端仍然调用 Coordinator 暴露的标准 OpenAI API。Coordinator 收到请求后,首先将其发送给与模型配套的 Render Sidecar。Render 负责执行 Chat Template、Tokenization 和模型相关的输入处理,返回 Engine 可以直接消费的 Token IDs 和推理参数。
Coordinator 随后使用这些真实 Token IDs 完成流控、KV Cache 查询和实例调度,再把 Token-in 请求发送给 P/D 或 Union Engine。
Engine 不再理解 OpenAI messages,也不负责 Tool Call、Reasoning 和 OpenAI Response 拼装,而是接收 Token IDs 并输出生成 Token IDs。生成结果返回 Coordinator 后,由 Derender 完成 Detokenization、Reasoning 和 Tool Call 解析,并转换为标准 OpenAI Response 返回客户端。
完整目标链路如下:
flowchart LR Client["OpenAI Client"] --> Coordinator["Motor Coordinator"] Coordinator --> Render["Render Sidecar<br/>Chat Template + Tokenization"] Render --> Coordinator Coordinator --> Schedule["Flow Control<br/>KV Query<br/>P/D Scheduling"] Schedule --> Engine["Token-in/Token-out Engine"] Engine --> Coordinator Coordinator --> Derender["Derender<br/>Detokenization + Parsing"] Derender --> Coordinator Coordinator --> Client当前采用 Sidecar 部署:
在这一架构中:
目标态同时支持流式和非流式请求。
对于非流式请求,Coordinator 等待完整 GenerateResponse,再执行一次 Derender,最终返回完整 OpenAI Response。
对于流式请求,Engine 每生成一个 Token Chunk,Coordinator 就将其与上一份
stream_state一起交给 Streaming Derender。Derender 返回 OpenAI Chunk 和下一份状态,Coordinator 随即通过原 SSE 连接返回客户端。阶段一:非流式闭环
阶段一已经实现
stream=false请求的 Render → Generate → Derender 闭环。Coordinator 在调度前调用 Render 获得最终 Prompt Token IDs,并基于这些 Token IDs 完成流控、KV Cache 查询和实例调度。Engine 接收 Tokenized GenerateRequest 后执行推理,Coordinator 等待 Engine 返回完整 GenerateResponse,再将结果交给 Derender。Derender 完成一次性后处理后,Coordinator 向客户端返回最终 OpenAI Response。
sequenceDiagram participant C as Client participant M as Motor Coordinator participant R as Render Sidecar participant E as Token Engine C->>M: OpenAI Request, stream=false M->>R: Render OpenAI Request R-->>M: GenerateRequest + Prompt Token IDs M->>M: Flow Control + KV Query + Scheduling M->>E: Token-in GenerateRequest E-->>M: Complete GenerateResponse M->>R: Derender GenerateResponse R-->>M: OpenAI Response M-->>C: Complete JSON Response阶段一支持:
sampling_params.max_tokens同步;阶段一解决了 Coordinator 在调度前拿不到真实 Token IDs 的核心问题,也验证了 Render、Generate、Derender 的协议闭环。其主要限制是必须等待完整 GenerateResponse,不能提供流式接口所需的实时增量输出。
当前阶段:流式闭环
当前阶段在非流式公共模型和 token-only Engine Client 上增加 Streaming Derender,不另建一套完全独立的 Render Token In 逻辑。
sequenceDiagram participant C as Client participant M as Motor Coordinator participant R as Render Sidecar participant E as Token Engine C->>M: OpenAI Request, stream=true M->>R: Render OpenAI Request R-->>M: GenerateRequest + Prompt Token IDs M->>M: Flow Control + KV Query + Scheduling M->>E: Token-only Generate Stream loop each GenerateResponse Chunk E-->>M: Token Chunk M->>R: generate_chunk + previous stream_state R-->>M: OpenAI Chunk + next stream_state M-->>C: SSE Chunk end M-->>C: [DONE]vLLM Streaming Derender 采用无服务端请求会话的设计。Render Sidecar 不持有整个请求的 Session;Coordinator 在每次 Derender 调用中携带上一份
stream_state,并在请求生命周期内保存最新已提交状态。这与原初稿设想的“Render 创建有状态 Derender Session”不同。当前实现不需要 Session ID 或粘性路由,状态所有权明确位于 Coordinator,也更适合在 Engine attempt 之间恢复。
流式实现需要额外处理:
功能要点
Render 阶段
Render 必须位于流控和实例调度之前。
Coordinator 通过 Render 得到最终 Prompt Token IDs,并以此作为 Token 流控、KV Cache 查询和实例选择的输入。对于 Chat 请求,这些 Token IDs 已经应用 Chat Template、工具定义和 Special Token。
Coordinator 不应使用另一套 tokenizer 再次计算或覆盖 Render 结果,否则会重新引入 Token 不一致问题。KV Conductor 中既有的 prefix hash 仍基于最终 Token IDs 计算,不需要 Render 定制或返回额外
prompt_hash。Render 响应会被转换为 Motor 内部的 TokenizedRequest,而不是直接传播 vLLM Response Object。内部结构区分:
tokenizer_source=render|local。当
context_budget_mode=on时,Coordinator 先使用 Render 返回的准确 Prompt Token IDs 计算剩余上下文预算,再把截断后的max_tokens同步到sampling_params.max_tokens,确保调度预算和 Engine 实际生成预算一致。Render Token In fallback
Render 连接失败、超时、明确不支持或响应结构无效时,Coordinator 自动回退本地 tokenizer。fallback 不再由单独开关控制,开启 Render 后始终具备该可靠性兜底。
本地 tokenizer 在首次真实 fallback 时延迟加载,避免 Render 正常时提前占用额外内存和启动时间。模型路径复用部署阶段从 Engine 配置解析出的 model path,不依赖 KV Conductor 是否开启。
请求本身的校验错误不应回退。例如非法参数、上下文超限或无法应用的请求配置,应保留原始错误,避免本地 tokenizer 接受请求后改变语义。
Token In fallback 只保证 Coordinator 可以获得 Prompt Token IDs。若 fallback 结果缺少构造 token-only Generate 所需的完整 Render metadata,则该请求继续沿用已有原生 Engine 链路,不会静默构造不完整的 token-only 请求。
Generate 阶段
Generate 位于调度之后。现有 P/D、Union 和降级调度职责保持不变,但支持的请求会从 OpenAI Request 转换为 token-only EngineRequest。
Engine 只负责模型推理,不负责 OpenAI Response 拼装。
TokenizedRequest 到 EngineRequest 的转换、GenerateResponse 校验和 batch request ID 生成集中在 token-only 公共层。Router 只负责资源分配、P/D 生命周期、重试和释放,避免三种拓扑重复理解 Render 协议。
非流式 Derender
非流式请求由 Coordinator 将完整 GenerateResponse 交给 Derender。Derender 根据原始请求上下文和 GenerateResponse 恢复:
Derender 失败发生在推理完成后的响应转换阶段,因此不会重新执行 Prefill/Decode,也不会计入 Engine circuit breaker。
Streaming Derender
Streaming Derender 不能通过对每个 Token Chunk 独立调用非流式 Derender 实现。单个 Chunk 可能只包含半个 UTF-8 字符、未闭合的 Tool Call JSON、Reasoning 与 Content 的切换片段,或尚未完整出现的 Stop Token,这些处理都依赖前序状态。
当前由 Coordinator 维护请求级
StreamingRenderSession。每条 prompt/choice 逻辑流拥有独立stream_state:该状态模型既满足 vLLM 官方的无状态 Render tier 设计,也能与 Motor 的 Rescheduler 生命周期对齐。
多 prompt 流式 Completions
vLLM Render 可以把 Completions 的 prompt 列表转换为多个 GenerateRequest,而 token-only
/inference/v1/generate仍以单 GenerateRequest 为执行单位。因此,Coordinator 在 Handoff 和 Union 下执行 fan-out/merge:处理规则如下:
stream_state;[DONE];Trigger 多 prompt 流式仍沿用原生链路。Trigger 已计划日落,本阶段不为它新增复杂的多流编排。
流式 replay 与重调度
Render 接入后不扩大 Motor 原有 Rescheduler 能力,但原来支持的单 prompt 流式 replay 需要继续保留。
单 prompt Engine attempt 失败时:
stream_state接续;重调度后仍走 token-only 链路,不重新执行 Render Tokenization。replay 输入已经是确定的 Token IDs,重新应用 Chat Template 或 tokenizer 可能改变请求语义。
多 prompt 在客户端首个可见 Chunk 之前可以按请求级语义整体重试。已经输出后的逐 prompt 跨实例 replay 需要独立维护每条 prompt 的 Token ledger、剩余预算、实例和 Derender 状态;未接入 Render 前也不具备该能力,因此本阶段不扩展。
Token 混淆适配
主干已经提供 Token/图像混淆能力。流式 Render 需要明确语义 Token IDs 与物理 Token IDs 的边界:
本方案只适配 Render 与现有混淆接口的交互,不修改混淆算法和生命周期。
能力范围
表中的“原生链路”表示继续使用接入 Render 前的 Engine API 和响应处理,不表示请求失败。
上下游影响
客户端和客户面接口
客户面继续使用:
客户端不需要感知 Motor 内部的 Render、Generate 和 Derender 接口。非流式响应保持完整 JSON;流式响应保持 OpenAI-compatible SSE。未开启 Render 时,请求完整沿用原链路。
当前 Render Client 只为 Chat Completions 和 Completions 配置了明确 endpoint。Responses API、Anthropic API 和 SGLang 请求不会先尝试一个不存在的 Render 路由,而是继续使用原生链路。
Coordinator
Coordinator 从 OpenAI 请求转发和调度组件,演进为 Tokenized Serving 链路的编排者。一个请求生命周期内需要管理:
这些能力通过现有 Render Client、tokenization service、token-only 公共层和 response processor 承载,本阶段不引入通用 Frontend Adapter 抽象。
非流式请求需要暂存完整 GenerateResponse。流式请求只保留有界队列、状态和必要的 replay ledger,不等待完整生成结束后再响应。
Router、Scheduler 和 KV Conductor
Router 和 Scheduler 的职责不变,但支持 Render 的请求会使用 Render 返回的最终 Prompt Token IDs。
Prompt Token 数不再通过文本长度推测;KV Conductor 查询也使用最终 Token IDs。Chat Template 中加入的系统提示词、角色标记和工具定义都能被纳入 KV Cache 匹配,从而提高亲和性调度的准确性,并避免流控统计与 Engine 实际 Token 消耗不一致。
KV Conductor 原有 prefix hash 计算逻辑继续复用,无需修改 Scheduler 策略。输入 Token IDs 的变化可能影响 KV 命中和实例选择结果,这是预期行为,不属于调度策略变更。
Engine 和 token-only 接口
Token-only 请求通过以下接口发送:
非流式时返回完整 GenerateResponse,流式时返回 GenerateResponse Chunk 流。Motor 保留 Engine 所需的 P/D dispatch、KV Transfer、优先级和请求元数据,不因接入 Render 丢失原有 Coordinator 与 Engine 交互能力。
旧 OpenAI Engine 链路仍作为未开启 Render、不支持 API,以及受控能力回退场景的承载方式。当前方案不要求立即删除 Engine 中所有 tokenizer 和 OpenAI API 能力。
API 变更
客户面 API
客户面 OpenAI API 路径不变:
Coordinator 与 Render Sidecar
Render 接收原始 ChatCompletionRequest 或 CompletionRequest,并返回供 Engine 消费的 GenerateRequest 信息。
非流式 Derender 接收完整 GenerateResponse;Streaming Derender 接收单个 Generate Chunk 和上一份
stream_state。Render Sidecar 不保存请求级 Session。Completions 支持单 prompt 和多 prompt。多 prompt 场景下,Coordinator 保持 Rendered Request、GenerateResponse、choice index 和 Prompt Token 统计之间的对应关系。
Coordinator 与 Token Engine
同一接口承载非流式和流式 token-only Generate。是否流式由请求参数和响应读取方式决定,不另外增加一套 Engine endpoint。
配置
"render_config": { "enable": true, "image_name": "vllm-render-cpu:v0.28.0-arm64", "endpoint": { "host": "127.0.0.1", "port": 8100 }, "timeout_ms": 5000, "renderer_num_workers": 4 }配置说明:
enable:显式开启 Render;image_name:非空时使用独立 Render CPU 镜像,为空时复用 Motor 服务镜像;endpoint:Sidecar 模式通常使用127.0.0.1:8100;timeout_ms:单次 Render 或 Derender HTTP 请求超时,默认 5000 ms。流式场景中每个 Derender Chunk 单独应用该超时,并非整条 SSE 共用 5 秒;renderer_num_workers:控制 Render worker 数量,默认值为4,高并发场景可增加。Render worker 数在部署时默认与
inference_workers_config.num_workers保持一致,避免多个 Coordinator inference worker 竞争单个 Renderer 形成排队瓶颈。模型路径和 served model name 从 Engine 配置读取,用户无需在 YAML 中重复维护。此处vllm官方流式derender未复用wokers线程池,导致流式压测时出现性能劣化,已向vllm社区提出issue,见https://github.com/vllm-project/vllm/issues/57350
启用 KV Cache affinity 时建议同时开启 Render,让 KV 查询和 Engine 推理使用相同 Token IDs。但 Render 不因亲和性功能自动开启,避免使用不支持 Streaming Derender 的旧 vLLM 版本时导致请求失败。
版本要求
Render、Derender 和 Engine 必须使用一致的 Model、Tokenizer、Chat Template、Special Token、Tool Parser 和 Reasoning Parser 配置。当前通过部署配置保证版本配套,尚未实现 revision/capability 自动协商。
技术方案
Coordinator 内部结构
本阶段不引入通用 Frontend Adapter,而是在 Render 相关目录中直接实现 vLLM Client 和公共处理能力:
Router 侧通过 token-only 公共模块完成 TokenizedRequest 到 EngineRequest 的转换:
Handoff、Trigger 和 Union 的策略实现只保留拓扑相关生命周期,不重复实现 Render 协议细节。Trigger 的现有入口由公共层提供薄兼容,后续 Trigger 日落时可以删除包装而不影响 Render 核心能力。
请求生命周期
非流式请求:
流式请求额外维护:
Render 失败发生在推理调度之前,不占用 Engine retry。Generate 失败复用已有实例熔断、重调度和 relay。Derender 失败发生在响应转换阶段,不重新执行推理。
客户端断开后,Coordinator 取消正在执行的 Generate 和 Derender producer,等待任务退出,并通过现有 Router 生命周期释放 P/D 或 Union 状态和流控配额。
故障处理
/inference/v1/generate返回 404/501Render、Engine 和 Derender 是不同故障域。Derender 错误不消耗 P/D retry,也不上报 Engine circuit breaker。当前不对 Derender 增加自动重试;后续如果增加,只能针对相同 Chunk 和相同 previous state 做有限短重试,并保持成功后才提交状态。
新的 Render 可用性判断只影响新请求是否尝试 Token In,不能中断已经进入 Streaming Derender 的在途请求。
可观测性
请求链路需要能够区分以下阶段:
当前通过日志记录:
tokenizer_source=render;fallback=local_tokenizer;测试方案
功能测试
功能测试需要覆盖:
[DONE];一致性测试
使用同一组请求分别调用:
对比:
对于确定性推理应要求结果完全一致。对于非确定性推理,可以固定生成 Token IDs,单独验证 Derender 的输出一致性。
故障测试
故障测试覆盖:
每类故障都要验证不会遗留流控配额、P/D 请求状态、Request Context、后台 producer 或未关闭的 HTTP stream。
性能测试
在相同模型、请求集、并发度和拓扑下分别关闭和开启 Render,观察:
非流式 E2E 包含完整 Derender;流式 TTFT 包含首个 Generate Chunk 和首个 Streaming Derender RPC,二者需要拆分观察。
验收标准
非流式能力
流式能力
总结
阶段一已经打通非流式 Render、Generate 和 Derender,使 Coordinator 能够在调度前获得最终 Prompt Token IDs,并验证 Token 流控、KV Cache 亲和性调度和 token-only Engine 的组件边界。
当前阶段在同一数据模型上补齐了流式 Generate 和 Streaming Derender,并实现多 prompt fan-out/merge、单 prompt token-only relay、状态提交与回滚、异常取消和 Token 混淆适配。接入前后,客户面 API、Scheduler 契约和原有调度职责保持不变;Render 新增的是前后处理协议和相应故障域,不扩展 Motor 原本不具备的调度或多 prompt replay 能力。