vLLM-Omni:能出声、出图、出动作的服务运行时,前提是先切成阶段

为什么单独跑得很快的模型,凑成一条链路就崩了?

想象这样的画面:你对手机说了一句“帮我看下这件上衣配哪条裤子”,它一边把回答念给你听,一边为你推了一张搭配图。这段体验背后不是一次推理,而是一条流水线:一个 30B 参数的文本模型先想好说什么,一个 3B 的语音模型把回答转成离散音频码,一个小声码器把音频码逐帧合成波形,与此同时还有一个扩散模型正在为那张搭配图迭代去噪。这几样东西是四个不同“物种”的生成器,各有各的执行节奏。真正的问题藏在最后一步:这四个物种被塞进同一个请求之后,谁来替它们统一指挥。

单看每一路,业界其实都解决得相当好。vLLM、SGLang 这类系统把自回归解码这一条循环优化到接近极致;xDiT 这类引擎把扩散去噪的并行研究得很透。可一旦这几条被磨到极致的循环拼在一起,事情就崩了:一次跨模态请求要穿过好几个异构生成器,而没有任何一个服务系统拥有这条链路的全局视图。应用层只能自己手搓临时胶水,把几个互不相识的运行时黏在一起,性能一路打折。所以这个问题只有一个答案:异构请求需要一个统一控制面来持有全局视图,而不是让每个引擎各自为战。

为什么偏偏是这个答案,而不是把引擎之间的胶水涂得更好?因为今天需要被服务的模型已经分裂成四种执行范式。第一类是语音与 omni 模型,本质是多阶段自回归流水线;第二类是图像视频生成,靠扩散迭代去噪;第三类是 Cosmos 这类世界模型,上一轮预测出的帧要变成下一轮的条件,状态必须跨调用存活,链路被拉长到一次调用之外;第四类是机器人策略,在观测到动作的闭环里反复循环。vLLM-Omni 每种范式都有自己的服务系统,可服务系统各自只优化其中一条解码循环,没有谁为“会在一个请求里串起多个生成器”的负载负责。一条链路上串着不同的执行节奏,就必须有一个角色拥有全局视图,替这条链路决定请求边界、阶段推进与完成判定,这正是“统一控制面”成为唯一答案的根据。紧迫感来自部署现实:语音助手、文生图、机器人这些模型正在同一批 GPU 上同时上线,跨模态请求不再是演示功能,而是要稳定跑满负载的常规流量。

而被服务的对象自己往往就是一条流水线,这是统一控制面必须存在的直接证据。以 Qwen3-Omni 为例,它就是 thinker 到 talker 再到声码器的三段结构:

图注:Qwen3-Omni 整体架构:左侧是音频、视觉编码器与分词器,中间 Thinker 负责生成文本,下方 Talker 接收 Thinker 的高层表征并输出多 codebook 的离散音频码,MTP 模块补出每帧残差 codebook,最右侧的 Code2Wav 轻量卷积网络逐帧合成波形。模型自己就是一条多阶段流水线。Qwen3-Omni

这条三段结构里,模型本身早就允许重叠:Qwen3-Omni 在自己论文里宣称冷启动理论首包延迟 234 ms,这是模型侧的结构极限值,不是任何服务系统的实测数字。Qwen3-Omni 模型结构自带的重叠能力,正是服务层要追赶的基准:没有角色替它决定阶段边界,这份重叠优势在真实服务负载下就会被逐段抵消。vLLM-Omni 实验的目标正是让这条流水线逼近模型侧的理论重叠,而这件事的前提,依然是一个握有全局视图的角色先来问一句“该轮到谁了”。

而这条“把模型拆开再统一调度”的路,vLLM 团队并非第一次走。前作已经提出用一张由用户定义的 stage graph 把模型拆成多个阶段,每个阶段跑一个独立引擎,stage 之间用统一 connector 搬运中间数据,在 Qwen3-Omni 这类负载上把任务完成时间压低了最多 91.4%。vLLM-Omni 前作 但那套设计里,编排逻辑还缩在 backend 内部,拓扑要用户手写,它只证明了“拆阶段”能让拼接不亏本,却没有回答谁该拥有这条链路的全局视图。于是问题反而变得尖锐:当一次请求要在这些异构生成器之间流转,到底该由谁来决定下一步跑谁?

把“下一步跑谁”收进 orchestrator,能解决什么?

vLLM-Omni 的回答是:把决定权上收进一个显式的 orchestrator(编排器)。它负责请求准入、跨阶段推进、客户端可见的进度,以及“请求何时算完成”的判定。vLLM-Omni 关键的分工在于:orchestrator 调度的是阶段,不碰 token。它不调度 token、不管分页的 KV cache、不去噪、也不搬运大张量,那些事各自留给引擎和传输层。

系统里的基本单元叫 stage(阶段),一个 stage 绑定三样东西:一个模型组件、一个执行引擎、一份资源预算。stage 之间交换带类型的中间数据,可以放在同一台机器上,也可以跨进程、跨设备、跨主机拆开部署。这是 vLLM-Omni 看待一切负载的统一镜头。下面的系统总览图一次呈现了全部顶层关系:

图注:vLLM-Omni 系统总览:顶部 OpenAI/WebSocket 与 OpenPI/Omni API 进入 Entrypoint,其中 AsyncOmni 持有请求与输出队列;中间是控制平面 Orchestrator,标注了准入、推进、流式、完成四条动词;其下是可选的 Session 层;底部 Execution stages 三列 stage 由横向的 OmniConnector 串起。vLLM-Omni

orchestrator 的推进是一个显式的循环。请求准入后提交给第一个 stage,轮询各 stage 的输出,再决定把结果交给客户端还是推进到下一个 stage,如此往复:

这套循环里有三个值得停下来细看的抽象。第一个是“流式即请求进度”:流式输入输出被建模为同一个请求身份上的进度,第一个 chunk 是 stage 0 的可续传请求,后续 chunk 是同一身份的流式更新,迟到或过期的更新直接丢弃。第二个是“模态集合完成”:一次请求只有当最终输出集合里每个 stage 都完成才算完成。正因为有这个判定,语音场景可以一边流式吐出 thinker 的文本,一边等波形合成慢慢收尾,两者互不阻塞。这个设计还顺带解决了一个工程难题:到底哪份输出算“结果”、哪份只是阶段交接,判定权落在了唯一持有全局视图的 orchestrator 手里,而不是散落在各引擎之间互相猜。

第三个也是最关键的机制叫 async_chunk。很多流水线根本不需要等上游全部结束才能让下游开工,语音就是典型。做法是:orchestrator 先在下游 stage 预提交一个占位请求,上游每产出一段 chunk 就通过 connector 发布出去,下游引擎与上游生成并行解码。这正是把“第一段音频包到达时间”(TTFP)压下来的主要手段。

这里要划清一条抽象层的界线,否则很容易把 async_chunk 和常见的流式解码混为一谈。Sarathi-Serve 这类系统也做 chunking,但它切的是单个模型 prefill 内部的 token 子集,目的是让长 prefill 不再卡住同批的 decode:

图注:同一组请求 A 到 F 在三种调度器下的时间线:vLLM(上)与 Orca(中)把整块 prefill 插在 decode 之间造成停顿;Sarathi-Serve(下)把 prefill 切成 p0、p1 小块与 decode 交错,实现无停顿批处理。这里切的是单模型 prefill 内部的 token 子集。Sarathi-Serve

vLLM-Omni 的 async_chunk 切的是跨 stage 的 payload 子集,不同 chunk 可以由不同 stage、不同 GPU 上的引擎并行处理,推进权握在 orchestrator 手里。两者只在“把长任务切成块”这个表象上相似,抽象层完全不同。

把镜头拉远看演变:前作里 orchestrator 还只是 backend 内部的调度进程,stage graph 由用户显式声明;本报告把它抬升为请求生命周期的唯一所有者,并补上了 StagePool、统一传输和会话控制。vLLM-Omni 前作 这条思路也早有铺垫:EPD 已经把多模态语言模型的 serving 拆成编码、预填充、解码三段各自配资源,vLLM-Omni 做的是把这种“拆开再统一调度”的思想泛化到异构生成范式上。EPD

这一章的收获可以浓缩成一句:统一的关键不是让引擎变快,而是让“请求边界”有了唯一的归属者。但 orchestrator 也只管到“谁跑、何时算完成”,它不碰那些动辄上 GB 的中间张量。阶段之间搬的到底是什么?这成了新瓶颈所在。

阶段之间搬的是什么?为什么它是新瓶颈?

orchestrator 的准入、路由、完成判定都走控制面,大块的中间数据必须另走一条路,这条路叫 OmniConnector。它承载的载荷不是不透明的 blob,而是四种带类型的中间数据:

图注:OmniConnector 之上四类中间载荷:hidden states(Thinker 到 Talker,async_chunk 分块传输)、codec codes(Talker 到 Code2Wav,分块加上下文)、KV blocks(PD 或 AR 到 DiT 边上的 KV 搬运)、latents(DiT 到 VAE 的 stage 输出)。四类载荷共用底部这条连接器,但每条边可以各自选择传输后端。vLLM-Omni

第一种是 hidden states,即激活和 embedding,Qwen3-Omni 里 Thinker 把选定层的隐藏状态、prompt 的 embedding 等交给 Talker,附带 token id 元数据;第二种是 codec codes,即离散音频 token,从 Talker 流向声码器,携带分块边界信息;第三种是 KV blocks,即自回归解码的注意力缓存块,出现在 prefill 与 decode 拆分的边上,或 AR 模型给扩散模型递上下文的边上;第四种是 latents,即扩散模型的张量,比如 DiT 与 VAE 之间的中间表示。vLLM-Omni

把 KV cache 也列为一种 payload,是这份报告很关键的一个选择,它意味着 KV 不再走临时通道,而是与其它载荷共用连接器,只多配一个专门的管理器。这个想法有个清晰的原型:DistServe 把 prefill 和 decode 拆到不同 GPU 上,中间要搬的东西恰恰就是 KV cache,intermediate data 这个概念最初就是从这里来的。DistServe Mooncake 更进一步,把 KV cache 当作整个拆合架构的中心调度对象,用专门的存储与 RDMA 通道在节点间搬 KV。vLLM-Omni 做的,是把这条“KV 是头等公民”的思路推广到所有跨阶段张量上。Mooncake

连接器本身的接口窄得惊人:put、get、cleanup,加健康检查。生产者把载荷发布到一个 key 下,只把一小段元数据放上控制队列,消费者凭这段元数据去取。连接器是语义无关的,它不解释哪份是 hidden states、哪份是 KV,分块传输层和 KV 传输层都叠在它上面。适配不同部署位置的是后端的可选择性:单节点默认走共享内存,绕开 orchestrator 的拷贝,换来低延迟的进程间访问;跨节点走存储型或传输引擎型后端,包括 Mooncake 的 TCP 与 RDMA 路径,以及各平台专属的传输。同一条流水线里,本地的 Thinker 到 Talker 可以走共享内存传 hidden states,AR 到远端扩散模型的 KV 则可以走 RDMA 传输引擎,每条边各自挑。

边界上的细节决定成败。以 AR 给扩散模型递 KV 为例:专门的 KV 传输管理器把 AR 侧已经算完的块摘出来,发布到 KV 专属的 key 下,扩散 runner 在去噪开始前把它们当成 past_key_values 注入。类型搞错或落点不对,整段注意力状态就废了。TTS 路径上还有个自适应分块斜坡,chunk 大小跟着下游缓冲区的反馈浮动,而不是死守一个水位。这些机制都印证了同一个论断:统一运行时的性能与正确性,全都卡在阶段边界上。控制面与数据面分离是一条不可违反的不变量:准入、路由、完成判定留在 orchestrator,payload 的字节必须走连接器。

但连接器只负责搬。真正干活的是每个 stage 里的执行引擎,而 AR 与扩散是两种截然不同的执行范式。它们怎么共用一个调度器,还撑得住长会话?

AR 与扩散两套引擎,怎么共用一个调度器、还撑得住长会话?

vLLM-Omni 没有试图把两种引擎融成一种,而是让它们各干各的,只在请求边界和会话状态上对齐。自回归这一侧的选择是“复用而不是重建”:AR worker 直接继承 vLLM 的 worker 与 scheduler 栈,连续批处理、PagedAttention 的分页 KV 管理、chunked prefill 全都白捡。PagedAttention 这是有道理的:vLLM 这一层经过多年的打磨已经是高度优化层,值得当轮子而不是重新发明。SGLang 的 RadixAttention 树形 KV 复用则是同一个战场上的另一条优化方向,也只需作为背景记住。SGLang

扩散这一侧没有现成的轮子可捡,Diffusion Engine 自建了一个调度器,负责扩散任务的准入、执行与可选的逐步控制,并支持跨去噪步的连续批处理,让多个扩散任务共享同一批而不是一次只跑一个。序列并行、CFG 并行、TeaCache 与 Cache-DiT 这类去噪步缓存都挂在引擎内部,不打扰 orchestrator。这些并行思路与 xDiT 这类专门研究扩散推理的引擎同源,但 vLLM-Omni 是借鉴,并没有把 xDiT 嵌进来当一个 stage。xDiT

两套引擎之外还有一座桥。AR 与 DiT 混合的世界模型需要一种特殊的持续性:模型要在分块的 rollout 里不断预测下一帧,注意力状态必须跨块存活。为此 AR 与 Diffusion 后端在 vLLM 的 block manager 上搭了一个 runner 局部的分页 KV 池,让会话级 KV 在一个模型实例内部跨滚动步存活。需要强调,它不是用来替代跨 stage 的那条 AR 到 DiT 的 KV 搬运通道的,两者分工不同。

stage 之上是副本管理,这里的核心结构叫 StagePool:

图注:每个 stage 一列:StagePool 里的 client 垂直叠在 EngineCore 副本之上,这个竖向配对就是 sticky affinity。Stage 0(AR)单副本,Stage 1(AR)两个副本演示横向扩展,Stage 2(Gen)单副本并注明可以是 code2wav。下方是可选的 Omni DP Coordinator,负责跨主机副本成员关系。vLLM-Omni

每个逻辑 stage 拥有且只拥有一个 StagePool,池里可以放多份引擎副本。sticky affinity 这条竖向绑定很要紧:它保证同一个 client 的流式更新始终回到同一个引擎副本,stage 内的 KV 局部性因此保得住。对无绑定关系的新请求,池子按轮转或最短队列做负载均衡;TP、DP、PP、EP 这些轴在每份副本内部展开。

最后是会话层。全双工语音、世界模型、机器人这类负载有一个共同点:交互的生命周期超过一次 generate 调用。vLLM-Omni 把会话控制做成同一 orchestrator 的扩展,而不是第二套运行时,流式钩子提供 I/O 通道,会话层加上长期身份与容量准入。三种共享关切在语义上分得很清。其一是生命周期:会话身份用 open、update、signal、close 管理,饱和时准入可以拒绝新会话,因为实时语音每个会话的 GPU 占用很大。其二是状态连续性:对话或 stage 的 KV、潜在状态环、回合与周期的围栏元数据都要跨 turn 存活,围栏的作用是防止旧回合迟到的更新污染新回合的响应;非 KV 的世界模型状态由一层带类型的会话状态管理层负责,提供 allocate、commit、view、reset、evict 五类操作,并记录设备侧字节占用,供逐出与回收做决策。其三是并发的双向 I/O:全双工语音一边播放一边追加麦克风音频,流式视频一边去噪一边接收帧,机器人一边推动作块一边收新观测。Qwen3-Omni 的双工语音和 Cosmos 的 predict 到 recondition 循环,正是这三类关切最典型的两种负载。

这一章想带走的判断是:统一运行时并没有把两种引擎熔成一种,而是让它们在请求边界与会话状态上对齐。这既是它的能耐,也是它必须逐阶段补齐的地方。但说了这么多抽象,读者自然会问:切成阶段之后,到底快了多少?

切成阶段之后到底快了多少?又卡在哪里没快?

实验以 Qwen3-Omni 为主角,在 2 张 H200 上跑 Fixed-Len-2500/900 工作负载,对比默认 V1 形态与开启 async_chunk 的 MRv2 形态,并发 32。结果很有说服力:平均端到端延迟从 75.06 秒砍到 39.53 秒,平均实时率因子 RTF 从 0.423 压到 0.212,平均音频首包延迟从 1064.6 毫秒降到 500.4 毫秒。音频路径几乎并行于文本解码在跑。vLLM-Omni

把 async_chunk 开关对比一下,收益的来源就清楚了。在 2 张 H100 上同一负载并发 32,关掉 async_chunk 后首 token 延迟 3420.1 毫秒、端到端 136.62 秒、RTF 0.927;打开后分别降到 507.8 毫秒、72.63 秒、0.630。差距几乎全是跨阶段重叠带来的。换到混合负载的 Random-MM 形态,MRv2 也把文本加音频分支的平均端到端延迟压低了 15% 到 33%,纯文本分支的请求则稳定在 1 秒内收尾。

纯文本分支还暴露了统一运行时的另一面。固定长度文本负载并发 1 时,vLLM-Omni 与只服务 thinker 的 vLLM 在首 token 和端到端上差距在 6% 以内;并发 32 时端到端持平在 13.7 秒,但 orchestrator 侧的首 token 延迟是 860.1 毫秒,比裸 vLLM 的 562.4 毫秒多了约 300 毫秒,论文坦言这个差距没有被定位。跨阶段的统一是有成本的,只是这份成本在不需要跨阶段时就暴露出来了。

TTS 与图像视频的成果可以放进一张表:

场景与对比对象硬件与并发指标默认形态优化形态
Qwen3-Omni,MRv2+async_chunk 对比默认 V12×H200,并发 32平均端到端 / RTF / 音频首包75.06 s / 0.423 / 1064.6 ms39.53 s / 0.212 / 500.4 ms
async_chunk 开关对比2×H100,并发 32首 token / 端到端 / RTF3420.1 ms / 136.62 s / 0.927507.8 ms / 72.63 s / 0.630
Qwen3-TTS,fused 单阶段对比默认两阶段H200,1 卡对比 2 卡,并发 64音频吞吐(audio-s/s)234.6612.1(2.61 倍)
Qwen-Image,Cache-DiT+Ulysses2+CFG2+4 卡H100,并发 11536²/35 步延迟23.60 s5.20 s
Wan2.2-I2V-A14B,USP2+HSDPH100,1 卡,832×480/81 帧/4 步端到端26.20 s18.04 s

TTS 一行值得单独读。Qwen3-TTS 提供一种 fused 单阶段管线,让 talker 直接持有流式解码器、返回 PCM 波形,砍掉 code2wav 这个 stage 和它的一跳连接器开销。在 1 张 GPU 上,它对比默认两阶段跑在 2 张 GPU 上,每个格子都赢:CustomVoice 默认音色并发 64 时音频吞吐从 234.6 提到 612.1 audio-s/s,整整 2.61 倍;并发 1 时的中位首包延迟从 21.5 毫秒降到 15.3 毫秒。连接器少一跳,收益就是这么直接。

图像视频的加速则来自引擎内部的并行组合与去噪步缓存,与跨阶段重叠关系不大。Qwen-Image 在 H100 上把 1536 分辨率、35 步的延迟从 23.60 秒压到 5.20 秒,靠的是 Cache-DiT 加 Ulysses2 加 CFG2 加四卡并行;Wan2.2 的图像转视频任务在 1 卡上从 26.20 秒降到 18.04 秒。这些数字说明 vLLM-Omni 并非只顾着管 orchestrator,引擎侧的优化它也照单全收。

比数字更值得注意的,是论文在报告结果时的取舍纪律。MiniCPM-o 4.5 有个 turn 模式的 MRv2 配置,首包延迟 658 毫秒、吞吐 5.49 req/s,比默认形态快不少。但它在 13% 的输出里会追加不相关的语音,WER 飙到 12.9%,停止不可靠。论文没有把这个更快的配置当结果报。反过来这些数字也印证了收益的边界:在并发 32 时,即便给 talker 和 code2wav 加副本、用上 3 张卡,平均 RTF 反而比 2 卡还高。瓶颈已经不在声码器够不够快,而在 thinker 的准入与跨阶段交接上。阶段边界接不好,副本翻倍也救不回来。

一句话总结这章:收益集中在跨阶段重叠与引擎内部并行这两处,而不是单模型本身变快了。那么回到开头那个问题:这些提升对你的负载值不值?这取决于你在服务系统坐标系里的位置。

它在服务系统坐标系里的位置,以及论文自己承认还差什么?

把前文出现过的系统放到一张坐标系里看,每条既有工作线都只优化一条路径,vLLM-Omni 新增的是整个控制平面层:

只优化一条路径的既有工作vLLM-Omni 新增的控制平面层
vLLM、SGLang、Sarathi-Serve、DistServe、Mooncake、EPD 都在优化一条自回归解码循环的调度、缓存与拆合跨阶段路由:由 orchestrator 决定下一步跑哪个 stage、哪份副本接手
xDiT、SGLang-Diffusion 及 TeaCache/Cache-DiT 优化单条扩散链路的去噪与并行流式交接:async_chunk 让下游与上游并行,把结果何时可见的定义收进 orchestrator
前作 vLLM-Omni 提出 stage graph 但编排在 backend 内部,SGLang-Omni、M 等各自探索组合连接器传输与生命周期:OmniConnector 搬字节,会话层管长期身份、状态连续性与并发 I/O

这个坐标系成立的关键,是论文自己那份直白的结论:既有系统们“提供的只是单个 AR stage 内部的调度、缓存与拆合基底,没有定义跨语音编码器、扩散引擎与动作生成器的一次请求的执行语义”。vLLM-Omni 把这三段语义收进了同一个控制平面。支撑这张坐标系的是模型矩阵:受支持的模型按 serving 拓扑而不是模态类型归成六大家族,从 AR omni、TTS、扩散图像视频、世界模型、VLA 到全双工语音,全都落在同一套 stage 编排语义下。vLLM-Omni

论文也给自己划了明确的能力边界,一共三条。第一,扩散 paged KV 已经在部分 DiT 流水线上可用,但覆盖面和考虑缓存的准入在模型家族间仍然参差不齐。第二,闭环机器人评估没有做,也就是让策略真实驱动机器人、用端到端成功率检验服务的这类评测,被留作未来工作。第三,跨硬件测量没有系统展开,当前评估数字只覆盖 NVIDIA H100 与 H200,跨硬件平台的测量被明确留作未来工作。这三条正好对应前文反复出现的三个边界:阶段边界的 payload 语义、会话层的新负载、以及异构硬件上的传输后端。

想上手的话,工程入口都在那里:项目以 Apache-2.0 开源在 vLLM 官方组织下,2025 年 11 月建仓,技术报告 2026 年 10 月发布,recipes、supported models、nightly Docker 三件套齐全,文档入口与上游 vLLM 的插件机制一脉相承。vLLM-Omni 仓库 它不是一个 fork,而是以插件形式挂在 vLLM 生态里,Omni 专属的模型注册、平台钩子、量化工厂都沿上游 vLLM 的插件纪律延展,版本号跟着上游偶数版本节奏走。

回到标题那句话:vLLM-Omni 的主张是一个可以拿去检验的立场。多模态生成服务应该有一个唯一的控制平面,而它把几乎全部复杂度都押在了“阶段边界”这个新抽象上:你得先把模型切成显式的 stage,边界的语义、载荷的类型、KV 的特例处理、会话的生命周期,每一项都是一笔待还的账。这笔账值不值得还,只取决于一件事:你的负载是不是真的跨阶段。纯文本聊天用不上它;可一旦你的请求要同时出声、出图、出动作,你就需要一个为边界负责的人。

想深入可以读原文:vLLM-Omni Technical Report: A Unified Serving Runtime for Omni-Modality Generation — arXiv:2610.09307

参考文献

vLLM-Omni:能出声、出图、出动作的服务运行时,前提是先切成阶段