调度不是一个循环,而是三类循环
SGLang-Omni 为不同计算形状使用不同 scheduler。AR Thinker 或 Talker 使用 OmniScheduler,它把 SGLang 的请求选择、prefill/decode、KV allocator 和结果处理桥接到 Omni payload。普通 encoder、聚合器和轻量函数 stage 倾向使用 SimpleScheduler;持续消费/产生 chunk 的 codec 或 vocoder 使用 StreamingSimpleScheduler 或 StreamingVocoderBase。
这不是三种互换的实现风格。encoder 的主要问题是媒体 batch、显存和重复输入;AR stage 的主要问题是 KV、连续 batching 和 decode 时间;vocoder 还需要保存 per-request window、flush 末尾 chunk,并决定何时可发第一段音频。把它们强行放入一个全局 batch 循环会掩盖这些不同的 SLO。
来源:OmniScheduler、SimpleScheduler、Streaming scheduler。
OmniScheduler 如何复用 SGLang
OmniScheduler 没有继承 upstream Scheduler;源码说明它通过未绑定方法和 __getattr__ 委托,借用上游的 batch selection、memory check 和 batch-result processing。它自身再处理 inbox/outbox、Omni request builder、payload projection、stream chunk 与完成事件。这样保留了 SGLang AR engine 的成熟部分,同时允许模型 runner 在 hidden state、codec token 或 Talker feedback 上添加合同。
对 AR stage,这意味着可使用 SGLang 侧的连续 batching、tree/Radix cache、request-to-token pool 和 KV allocator;它不意味着 Omni 自动开启 upstream 的全部 feature。源码明确配置了自己的 compatibility 字段与 feature gate,研究或部署时应检查实际 model runner 和 ServerArgs,而不要只读上游 release note。
AR stage 的责任切分
SGLang bridge:
选择 prefill/decode batch、管理 KV/token pool、执行模型 forward、采样
OmniScheduler:
inbox/outbox、Omni payload -> request、结果投影、chunk/terminal 生命周期
model-specific runner:
输入 embedding、hidden-state side channel、codec token、Talker feedback
已实现的 async decode,与被拒绝的 overlap
当前 enable_async_decode 是一阶段 lookahead:模型 runner 先 launch 一个 decode step,用 CUDA event 在后续 resolve 时读取结果。默认阈值为 batch size 2,batch size 1 走同步路径,因为低并发时额外调度开销可能超过重叠收益。该路径要求 runner 实现相应的 launch/resolve 合同,不能当成对所有 stage 的通用开关。
相反,enable_overlap 在当前源码中不是“实验性可选优化”,而是会直接抛 NotImplementedError。注释给出的原因是 overlap loop 的 Req.inflight_middle_chunks 比结果处理滞后一轮,TTS runner 会静默产生错误 chunk boundary;代码选择拒绝执行而不是输出可能损坏的流。这是已实现的保护,不应宣传为可用的 overlap feature。
同一文件还将 LoRA、speculative decoding、grammar 和 disaggregation 置于关闭状态。因此“Omni 基于 SGLang”只能说明它复用了选定的 AR 基础设施,不能推导出这些功能在 Omni pipeline 中已可部署。
两类缓存:AR KV 与媒体 encoder 结果
AR cache 由上游 SGLang 侧的 tree cache、request-to-token pool 与 KV allocator 管理,作用是可复用 prompt/prefix 的 token state。媒体 cache 则是另一层:它保存已经完成的 image/audio/video encoder 输出,避免同一媒体在不同请求中反复 forward。这两类缓存的键、内存位置、失效条件和收益完全不同,压测必须分别记录命中率。
Qwen stage factory 定义了 CPU LRU 的 encoder 输出 cache,上限为 64 entries、4 GiB。预处理在昂贵解码前对原始 image/audio/video 生成 key;批内若出现相同 key,后到请求成为 waiter,等待首个计算结果。Ming 的预处理也把媒体衍生 key 加入 prompt/cache 语义,避免不同媒体因为共享 placeholder token 而被错误地当成同一 prefix。
一个重要反例是 Qwen Talker:configure_talker_server_args 显式设定 disable_radix_cache=True。不要把 Thinker 的 prefix-cache 结论外推到 Talker,也不要用“缓存已开启”替代每个 stage 的实际 cache policy。
来源:Qwen stage cache 常量与使用、StageOutputCache、媒体 key、Qwen Talker scheduler。
组批、TP 与进程并行的限制
stage 可以独立选择 batch 的边界;这有利于让快速 audio encoder 不被长 Thinker decode 拖住,也意味着每个 stage 都可能形成队列。TP stage 的多个 rank 必须以相同顺序接纳请求。为维持这个锁步约束,OmniScheduler 在 tp_size > 1 时把 request-build worker 强制为 1,并禁用基于本地时钟的 prefill coalescing。
同样,stage 级复制、process 级复制和同 GPU MPS data parallel 是不同层次。当前官方 MPS 文档的验证清单不支持 Qwen3-Omni 或 Ming-Omni:Qwen speech 有两个 SGLang engine,而 Qwen text pipeline 没有该 launcher 所需的 generation stage;Ming-Omni 也没有完成同一资格过程。Higgs、MOSS 或 ASR 的 MPS 数字不能迁移为 Omni 模型的部署结论。
Router 在更外层选择完整 worker。它会检查 worker 健康和 capability,例如 audio input、audio output、video input、streaming;对 Ming,它明确不会将一个 request 的 Thinker 和 Talker 分派到不同 worker。它是 ingress 与副本选择器,不替代 pipeline 内部的 stage 并行。
来源:MPS DP 范围、Router 文档、process-level replica tracking issue。最后一个链接是未完成 tracking,不是已完成 capability。
控制面:消息比 tensor 更早到达
官方通信文档将 SubmitMessage、DataReadyMessage、CompleteMessage、StreamMessage、ShutdownMessage 和 profiling 信号放在 ZMQ 控制面,abort 使用 PUB/SUB。控制面传达请求身份、状态和数据引用;它并不承载大 tensor 本身。
这个区分让接收 stage 能先知道“哪个 request 的什么 payload 已就绪”,再按引用读取数据。对 streaming 边,先控制后等待还避免了跨节点 credit 等待的环路。研究 fault semantics 时,应验证 data reference ACK、取消、worker 退出和 terminal 聚合是否仍然闭合,而不是只检查 ZMQ 消息是否送达。
数据面:路径取决于进程、节点与 tensor 位置
| 条件 | 文档描述的传输 | 研究时应检查 |
|---|---|---|
| 同进程 | LOCAL_OBJECT,Python 对象引用 | 对象只读、生命周期覆盖队列等待;fan-out 不共享可变外层容器。 |
| 兼容的同 GPU edge | direct PyTorch CUDA IPC | producer/consumer 生命周期和 IPC handle 存活,不经过 relay ACK。 |
| 同节点 GPU 跨进程 | pooled CUDA IPC relay | slot 尺寸、DataRef、ACK 后回收和大 tensor 的多 slot 拼接。 |
| 同节点 host/CPU tensor | shared memory | 序列化开销、共享段回收与 abort 时是否泄漏。 |
| 跨节点 | 文档选择表指向 Mooncake | 网络、credit、tail latency 与失败恢复;不能仅凭模块存在宣称生产资格。 |
源码树还包含 relay/nccl.py 与 relay/nixl.py,官方博客也把 shm、nccl、nixl、mooncake 列为可用 backend。这里应保持措辞:这是实现/文档列出的 backend 范围;某个模型在跨节点、特定驱动和特定载荷上的稳定表现仍需单独基准和复现。
Qwen:feedback、codec chunk 与 Code2Wav graph
Qwen speech config 启用 Thinker async decode,Talker 使用 feedback,Code2Wav 作为 terminal 流式 vocoder。Talker 将 code chunks 投影给 Code2Wav;后者为每个 request 保存已经接收、已经发声和音频片段的状态,到达阈值时解码新窗口,收到 stream_done 时 flush。stream request 只发送增量音频;非 stream request 最终拼接完整 waveform。
Code2Wav 的 CUDA Graph 不是一般的“开启图模式”。当前配置只捕获精确 [B, Q, T] 形状,默认 B=1 的阈值窗口为 T={10,20,30,35},并在 config 中为其预留 2% typed GPU memory budget。尾部窗口、未捕获 shape 或 capture incompatibility 会走 eager。这是正确性优先的 fallback,性能测量必须分别报告 graph replay 命中率、eager 次数、TTFA 和尾延迟。
Qwen realtime 又是独立输入合同:服务端收 mono 16 kHz PCM16,输出 mono 24 kHz PCM16。视频输入还可选择在预处理时抽取音轨;不要把“视频支持”简化为只抽帧,也不要将 request-level 像素/帧数上限当成模型原生上下文的唯一约束。
来源:Code2Wav scheduler、CUDA Graph runner、Qwen config、Qwen usage。
Ming:流式取舍和音频格式边界
Ming 的普通 speech pipeline 是完整 utterance Talker;streaming variant 才有 segmenter -> talker_stream。官方 cookbook 说明 generic launch 当前直接暴露默认 speech 和 --text-only,流式 path 尚需要 pipeline config。text-only stream=true 目前输出聚合文本,而不是 token-by-token delta。
该 cookbook 给出带条件的 H100 类实验:TP4 Thinker、独立 Talker、7-stage non-streaming pipeline,Talker 的 SimpleScheduler.max_concurrency=1,高并发吞吐约在 3 req/s 附近平台化。流式方案在并发 1 时更早给出首音频,但吞吐较低,并发增加后队列效应可能反转优势。这里是特定硬件、模型、声音与负载下的方向性证据,不是所有 Omni pipeline 的性能定律。
当前文档还登记了一个明确格式缺陷:Ming Talker 实际生成 44.1 kHz,但 chat-completions 的 WAV header 当前可能按 client 默认 24 kHz 编码;文档建议保存时将 header 设为 44100,而 /v1/audio/speech 路径已转发 rate。部署前应把该例作为音频契约回归测试,而不是假定播放器会自动修复。
来源:Ming cookbook 的 streaming、benchmark 与限制、client audio encoding。
部署:版本、GPU、Router 与镜像
安装文档推荐 Docker,官方博客示例使用 lmsysorg/sglang-omni:dev、GPU、host IPC 与较大 shared memory。由于 :dev 是移动目标,而正式 release 仍是 0.1.0,严肃复现实验应记录镜像 digest、SGLang-Omni commit、sglang==0.5.16、PyTorch/CUDA、GPU 型号、stage placement、TP、静态显存比例、Graph 配置和模型 snapshot。
当前多硬件 RFC 明确写到 Omni 只在 NVIDIA CUDA 上运行;ROCm、Ascend NPU 与 Intel XPU 还是平台支持设计和后续 model enablement 工作,不能列入当前普遍支持矩阵。Ming 还需要谨慎规划 TP 和独立 Talker GPU;其 cookbook 对 H100 80 GB 的说明是具体模型/配置的容量指导,不是所有 MoE 的通用下界。
Router 适合多个完整 worker 的外层部署:它能启动本地 worker pool 或接入已有 URL,做健康检查、capability 路由、admission bound 和持久化的权重更新 journal。它不会把一个多阶段模型拆散到不同 worker,因此扩容决策仍需结合每个完整 pipeline 的瓶颈。
来源:安装文档、Router 文档、multi-hardware RFC #1310。RFC 是当前限制和计划的证据,不是非 CUDA 已完成的宣称。
已知限制:按证据等级阅读
| 等级 | 截至快照的说法 | 正确用法 |
|---|---|---|
| 当前代码 | OmniScheduler overlap loop 被明确拒绝;LoRA、speculative、grammar、disaggregation 关闭。 | 写成当前实现边界,不写成 SGLang core 永久不支持这些能力。 |
| 当前文档 | Qwen Code2Wav exact-shape graph 有 eager fallback;Ming streaming launch 与音频 header 有明确限制。 | 作为部署前检查和基准维度。 |
| 开放 issue | #1322 报告 realtime VAD 在相邻 turn 边界可能丢音频;#1360 报告 repetition penalty 可能双重应用。 | 作为可复现风险候选与回归测试,不写成所有版本、所有部署均受影响的已证实结论。 |
| 开放性能工作 | #1378 说明 Qwen Thinker prefill CUDA Graph 仍按路径逐项资格化;#1236 探索 Code2Wav 的 traffic-aware dispatch。 | 用来定义研究缺口,不得宣传为已发布默认优化。 |
值得做的系统实验
第一组应拆开测量缓存:同一前缀但不同媒体、同一媒体但不同 prompt、同一媒体重复请求,分别报告 SGLang KV/tree hit、Qwen encoder LRU hit、内存占用和输入准备时间。第二组应对同一 Qwen speech workload 比较 direct CUDA IPC、relay CUDA IPC、SHM 与跨节点路径,记录首 token、首音频、完整 waveform、ACK 延迟、abort 后资源回收。
第三组应验证语义正确性而非只跑 QPS:Qwen Code2Wav 的 graph/eager 波形一致性与 tail shape,Ming 的 WAV sample-rate header,VAD 在一个 chunk 内跨两轮语音,TP request admission 顺序,以及重复媒体 cache 是否会错误复用。最后,将 router 的完整 worker 分配与 stage 内部队列指标一起记录,才能知道瓶颈是在 ingress、encoder、Thinker、Talker 还是 vocoder。
一手来源清单
- SGLang-Omni 固定源码树,尤其是 通信文档 与 配置文档。
- LMSYS 官方 SGLang-Omni 技术博客,用于框架目标和已发布 TTS 示例。
- 上一页:架构与请求路径;下一页:与 vLLM-Omni 的逐层比较。