01 - SGLang-Omni 总览
把听、想、说拆成三个进程之后,会凭空长出五个跟模型无关的新问题 —— 本章索引那张图列了它们。这一篇给出解法的全景。
先看骨架。六层,从 HTTP 一路到模型前向:
判断一层归谁管,只用一条判据:它认不认识模型。
| 层 | 职责 | 认识模型吗 | 换个模型要不要改 |
|---|---|---|---|
| HTTP / WebSocket | OpenAI 兼容的请求响应格式、SSE 分帧、错误码 | 否 | 不改 |
| Client | 把 HTTP 请求降成内部请求、聚合结果、编码音频 | 否 | 不改 |
| Coordinator | 请求生命周期、投给入口阶段、合并多个终点、广播中止 | 否 | 不改 |
| Stage | 收发控制消息、读写 relay、扇入、流路由 | 否 | 不改 |
| Scheduler | 批选择、KV cache、把活分发下去 | 部分 | 选一种,不写新的 |
| ModelRunner | 前向、采样、模型特有钩子 | 是 | 只改这里 |
只有最下面一层认识模型 —— 这是整套设计的支点。接一个新模型时你写的代码全部落在那一层加它的配置里,上面五层一行不动。
一、它做什么,不做什么
先划清楚边界,不然很容易把它当成「又一个推理引擎」,然后拿它跟 vLLM 比吞吐 —— 那是两个层面的东西。
它不做的事:KV cache 怎么分页、一个批里该挑哪些请求、显存不够时踢谁、怎么把解码步录成 CUDA Graph。这些是推理引擎的活,SGLang 已经做得很好,它直接拿来用。
它做的事:上面开场列的那五个问题。流水线长什么样、每个阶段什么时候生、张量怎么在阶段之间搬、新模型怎么接进来、请求怎么进来结果怎么出去。
用一句话概括就是:它管编排,不管单段怎么算。下面这张图把两边的分工摊开。
二、数据在各层之间变成了什么
上面那张图画的是「谁调用谁」。但读代码时更容易懵的是另一件事:同一份数据,在每一跳上叫什么、长什么样。
三个容易懵的点,看这条链就清楚了:
StagePayload是阶段之间唯一流动的东西。 它是个容器,张量挂在它的data里。跨进程时张量被抽出来走 relay、剩下的部分序列化走控制面 —— 04 篇讲这个拆包过程。request_id从OmniRequest开始就固定了,一路带到最后。中止、清理、缓存键全靠它,所以任何以它为键存在阶段之外的东西都必须被清理干净(06 篇的三条竞态)。- 码本变波形那一跳可能跨卡。上图里 D6 到 D7 那条边,在 Qwen3-Omni 上是 GPU 1 到 GPU 1、在别的模型上可能是 GPU 0 到 GPU 1,走哪条通路由拓扑推导。
还有一条平行的线:请求本身的状态。协调器给每个 request_id 记一个状态,它跟数据流是两回事:
「全部终点都报完成」这一条是 omni 特有的:Qwen3-Omni 有文本和音频两个出口,任何一个没报完成,这个请求就还不算完。纯文本服务只有一个出口,压根不需要这个概念。
三、一个请求的完整旅程
官方给了一张把五层和三个阶段画在同一张图上的端到端图,用的是最简单的 Fish Audio S2-Pro 三阶段 TTS 流水线。左列是请求生命周期(HTTP → Client → Coordinator 再回来),右列是三个阶段,每个阶段标出它的调度器、模型运行器,以及阶段之间「ZMQ 走控制信号、relay 走张量」的分工:
docs/design/diagram/s2pro_example_diagram.svg。图里 Request flow / ZMQ (signals) / Relay (tensors) 三种线型是理解这套架构的关键 —— 控制和数据走的是两条完全独立的通路。按这张图把五层的职责写清楚,重点是每一层认不认识模型:
| 层 | 职责 | 认识模型吗 | 细讲 |
|---|---|---|---|
| HTTP / WebSocket | OpenAI 兼容的请求响应 schema、SSE 分帧、HTTP 错误 | 否 | 06 篇 |
| Client | GenerateRequest 转 OmniRequest、结果聚合、音频编码 | 否 | 06 篇 |
| Coordinator | 生命周期、入口阶段投递、终点结果收集、中止广播 | 否 | 02 篇 |
| Stage | 控制面 IO、relay 读写、扇入、流路由、调度器收发桥接 | 否 | 02 篇 |
| Scheduler | 每个阶段的执行循环与失败传播 | 部分 | 03 篇 |
| ModelRunner | 前向准备、前向分发、输出抽取 | 是 | 03 篇 |
「Stage 不认识模型,也不认识调度器类型」是最关键的一条不变量。 Stage 的代码里没有任何 if isinstance(scheduler, OmniScheduler) 这样的分支 —— 三种调度器 对外呈现完全相同的接口。把分支收敛在这一层,是这套架构能不断加新模型而不塌的原因。
四、四个模型的流水线形状
「多阶段」到底长什么样,看四个真实模型的对照最直接。行是阶段类别,列是模型,格子里是这个模型在这一类上的实际阶段名与调度器:
复杂度从上到下渐进,而每多一个阶段都对应一个具体需求:
| 模型 | 比上一个多了什么 | 为什么必须多这一段 |
|---|---|---|
| Fish S2-Pro | 最小形状 | 预处理、生成码本、还原波形,三段是下限 |
| Higgs Audio v3 | 多一个 audio_encoder | 参考音频要跑多码本编码,重到值得单独占一次调度 |
| MOSS-TTS-Local | 阶段数没变 | 复杂度在阶段内部:主干与 1+12 微步分别捕 CUDA Graph |
| Qwen3-Omni | 多五个 | 三种模态(两个塔+扇入)、两个自回归段串联、同时吐文本和音频 |
三类阶段的调度器类型是固定的:预处理与编码器用 SimpleScheduler(无 KV cache),自回归引擎用 OmniScheduler(有 KV cache),声码器用批量 SimpleScheduler 或流式 Code2WavScheduler(无 KV,但流式有跨块状态)。
最下面那个 Qwen3-Omni 有两个终点,是纯文本服务里完全不存在的东西 —— 协调器必须能等两条终点都完成。这一条需求单独撑起了 Coordinator 这一层。
五、目录地图
源码按「框架层」和「模型层」严格分开,接新模型时只碰后者:
sglang_omni/
├── pipeline/ # 阶段编排、Stage、Coordinator、多进程运行器 ← 02 篇
├── scheduling/ # 三种调度器循环与收发消息类型 ← 03 篇
├── model_runner/ # AR 阶段的模型运行器基类与共享实现 ← 03 篇
├── comm/ relay/ # 传输选择与各后端实现 ← 04 篇
├── config/ # PipelineConfig、StageConfig、拓扑与放置 ← 02 · 05 篇
├── serve/ client/ # HTTP 服务与 OpenAI 兼容适配 ← 06 篇
├── proto/ # 请求、载荷、阶段、控制面消息类型
└── models/ # 每个模型一个子包,自动发现 ← 06 篇
下一篇:02 - 流水线与阶段。