vLLM-Omni TTS 推理加速实战手册

vLLM-Omni TTS 推理加速实战手册

写给「懂 TTS 部署、听过 vLLM、但没用过 vLLM-Omni」的你。

本文全程围绕语音合成(TTS)推理加速展开,视觉/图像/视频等能力只在必要时一笔带过。


目录

  • 写在前面:用你已经懂的东西,理解你要学的东西
  • [一、快速开始:跑通一个 Qwen3-TTS 模型](#一、快速开始:跑通一个 Qwen3-TTS 模型)
    • [1.1 环境准备](#1.1 环境准备)
    • [1.2 启动 Qwen3-TTS 服务](#1.2 启动 Qwen3-TTS 服务)
    • [1.3 发送你的第一个 TTS 请求](#1.3 发送你的第一个 TTS 请求)
    • [1.4 跑一次基准测试,看懂性能数字](#1.4 跑一次基准测试,看懂性能数字)
  • 二、项目整体认知
    • [2.1 vLLM-Omni 到底是什么](#2.1 vLLM-Omni 到底是什么)
    • [2.2 它和原生 vLLM 是什么关系](#2.2 它和原生 vLLM 是什么关系)
    • [2.3 技术分层架构](#2.3 技术分层架构)
    • [2.4 一个 TTS 请求的生命周期](#2.4 一个 TTS 请求的生命周期)
    • [2.5 横向对比:原生 vLLM vs vLLM-Omni](#2.5 横向对比:原生 vLLM vs vLLM-Omni)
  • [三、代码结构与模块详解(聚焦 TTS)](#三、代码结构与模块详解(聚焦 TTS))
    • [3.1 项目目录地图](#3.1 项目目录地图)
    • [3.2 核心模块逐一拆解](#3.2 核心模块逐一拆解)
    • [3.3 高频调优场景代码定位速查表](#3.3 高频调优场景代码定位速查表)
  • 四、核心加速技术原理全面详解
    • [4.1 通用加速技术(复用/改造自 vLLM)](#4.1 通用加速技术(复用/改造自 vLLM))
    • [4.2 TTS/音频专属加速技术](#4.2 TTS/音频专属加速技术)
    • [4.3 加速技术之间的关系](#4.3 加速技术之间的关系)
  • [五、开源 TTS 模型适配完整工程指南](#五、开源 TTS 模型适配完整工程指南)
    • [5.1 接入前必须理解的概念](#5.1 接入前必须理解的概念)
    • [5.2 标准接入步骤](#5.2 标准接入步骤)
    • [5.3 适配后的性能优化方向](#5.3 适配后的性能优化方向)
    • [5.4 高频故障排查清单](#5.4 高频故障排查清单)
  • 六、附录

写在前面:用你已经懂的东西,理解你要学的东西

在开始之前,先把「你已经懂的」和「你即将学的」做个对照。这份文档的每一个新概念,我都会先拿你熟悉的 TTS 知识当跳板。

你已经懂的(TTS 部署经验) 你即将学的(vLLM-Omni 里的对应物)
文本前端(g2p/音素) → 文本编码器 → 声学模型/Flow Matching → 声码器 → PCM 同样这条链路,但被拆成可独立调度的 Stage(阶段)
一个 TTS 请求独占 GPU,串行推理 连续批处理(Continuous Batching):多个请求在同一块 GPU 上交错执行
decoder 推理时为每条序列存一份 attention 的 K/V,占一大块连续显存 PagedAttention:把这块显存切成「页」,按需分配
离线脚本跑批量合成、用 FastAPI 包一个推理接口 Omni.generate() 离线接口 + vllm-omni serve 在线服务(OpenAI 兼容)
给同一个人合成很多句话,每次都重新跑一遍参考音频编码 音频特征缓存:参考音频/说话人向量只算一次,后续复用
Flow Matching / DiT 解码很慢,要分块流式出音频 分块流式解码(chunked streaming) + 跨 stage 异步流水线(async_chunk)

记住一句话,贯穿全文:

vLLM-Omni 的本质 = 把 vLLM「高效的文本 LLM 推理引擎」能力,扩展到「语音/音频/图像」的多阶段流水线上。它会 vLLM 就会 vLLM-Omni(官方原话:"If you use vLLM, then you know how to use vLLM-Omni from Day 0")。

还有一个你需要现在就知道的「现实校准」,避免你读到后面产生错误预期(这一点非常重要,官方文档里都写了,但容易忽略):

  • ⚠️ 当前版本(0.14.0)的纯 TTS 在线服务,还不支持「流式音频」 。也就是说,现在的 /v1/audio/speech 接口是「整段音频生成完一次性返回」,不是边生成边推流。流式能力还在路上(官方 RFC #938)。下文讲到流式时,我会明确区分「已实现的内部机制」和「对外服务还没开放」。
  • ⚠️ 官方默认 stage 配置里 TTS 的并发是 max_batch_size: 1,也就是单请求串行。多请求并发需要你改配置(第 5.3 节会讲)。
  • ⚠️ 官方默认所有 stage 都是 enforce_eager: true(关闭 CUDA Graph),原因是框架目前主要在 eager 模式下验证稳定。
  • ⚠️ 音频路径里没有任何自定义 CUDA 算子 ------RVQ、STFT、BigVGAN、Flow Matching ODE 全是纯 PyTorch 实现(唯一的例外是一个用 ONNX Runtime 跑的说话人向量提取器 campplus.onnx)。

这些不是缺点,而是一个 2025 年 11 月才发布、2026 年 2 月才出第一个稳定版的新框架的真实现状。理解了现状,你才能正确评估它、用好它。


一、快速开始:跑通一个 Qwen3-TTS 模型

这一节的目标只有一个:让你先把服务跑起来、听到声音,建立感性认识。原理全部留到后面。

1.1 环境准备

硬件要求:

  • NVIDIA GPU,建议 ≥ 24GB 显存(Qwen3-TTS-1.7B 本身很小,但 framework 有开销);架构支持 Ampere(30系)/Hopper(H100)/Ada(40系)。
  • AMD ROCm 也支持(本节以 NVIDIA 为主)。

操作系统:

  • ⚠️ 必须 Linux 。官方文档 docs/getting_started/installation/gpu.md:10-12 明确写:vLLM-Omni is currently not natively supported on Windows.。你现在在 Windows 上看这份文档没关系,但部署请用 Linux 服务器。

Python: 3.12

安装步骤(来自 docs/getting_started/quickstart.md:14-30):

bash 复制代码
# 1. 建虚拟环境
uv venv --python 3.12 --seed
source .venv/bin/activate

# 2. 先装上游 vLLM(指定版本,因为 vLLM-Omni 依赖它)
uv pip install vllm==0.15.0 --torch-backend=auto

# 3. 克隆 vLLM-Omni 并以开发模式安装
git clone https://github.com/vllm-project/vllm-omni.git
cd vllm-omni
uv pip install -e .

💡 类比:你以前部署 CosyVoice 时,要先装 torch、再装 transformers、再 clone CosyVoice 仓库。这里一样的套路:先装 vLLM(相当于你的 torch 底座),再装 vLLM-Omni(相当于你的 CosyVoice)。

验证安装:

bash 复制代码
# 这个命令能正常返回帮助信息,就说明 vllm-omni 装好了
vllm-omni serve --help

⚠️ 常见坑:如果 vllm-omni 命令找不到,检查 (1) 虚拟环境是否激活 (2) vllm==0.15.0 版本是否对得上(vLLM-Omni 对上游版本很敏感)。

1.2 启动 Qwen3-TTS 服务

vLLM-Omni 官方原生支持三个 Qwen3-TTS 变体(见 docs/models/supported_models.md:40-42):

模型 用途 HuggingFace ID
CustomVoice 用预置音色(如 Vivian/Ryan)合成 Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice
VoiceDesign 用自然语言描述想要的音色风格 Qwen/Qwen3-TTS-12Hz-1.7B-VoiceDesign
Base 声音克隆(给一段参考音频,克隆音色) Qwen/Qwen3-TTS-12Hz-1.7B-Base

我们用 CustomVoice 走一遍。最省事的方式是直接跑官方提供的启动脚本(examples/online_serving/qwen3_tts/run_server.sh:33-40):

bash 复制代码
vllm-omni serve Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice \
    --stage-configs-path vllm_omni/model_executor/stage_configs/qwen3_tts.yaml \
    --host 0.0.0.0 \
    --port 8000 \
    --gpu-memory-utilization 0.9 \
    --trust-remote-code \
    --enforce-eager \
    --omni

逐个参数解释(这是你最该先搞懂的):

参数 对 TTS 的意义
vllm-omni serve <MODEL> 第一个位置参数 要加载的模型,等价于你部署时的 model_name_or_path
--omni 开关 必须加 。它告诉 CLI「走 vLLM-Omni 多阶段流水线」。漏了它,命令会回退到上游纯文本 vLLM,TTS 路由完全不生效。判断逻辑在 vllm_omni/entrypoints/cli/main.py:12if "--omni" not in sys.argv: 走上游
--stage-configs-path YAML 路径 TTS 部署的核心配置文件 。它定义了「这个模型拆成几个 stage、每个 stage 用什么调度器、占多少显存」。TTS 用 qwen3_tts.yaml(下文详解)
--host/--port 地址 同 FastAPI,服务监听地址
--gpu-memory-utilization 0.9 0~1 浮点 vLLM 能用多少比例的 GPU 显存。TTS 模型本身小,但 KV cache、激活值要占地方
--trust-remote-code 开关 加载需要执行远程代码的 HF 模型时必须开(Qwen3-TTS 需要)
--enforce-eager 开关 强制 eager 模式,不用 CUDA Graph。TTS 默认就是 true(见 1.1 节现实校准)

还有一组 Omni 专属参数 (定义在 vllm_omni/entrypoints/cli/serve.py:76-132omni_config_group):

参数 默认 含义
--stage-init-timeout 300 单个 stage 初始化超时(秒)。stage 多、模型大时调大
--init-timeout 600 整体所有 stage 初始化超时
--shm-threshold-bytes 65536 跨 stage 数据传输时,大于这个阈值的数据走共享内存(SHM)而非进程队列
--batch-timeout 10 stage 攒批的超时(秒)。影响首包延迟
--worker-backend multi_process stage 间执行后端,可选 ray(分布式)

TTS 的 stage 配置文件长什么样?vllm_omni/model_executor/stage_configs/qwen3_tts.yaml(全文就 23 行):

yaml 复制代码
stage_args:
  - stage_id: 0
    stage_type: llm               # 用 LLM 引擎类型
    runtime:
      devices: "0"                # 用 GPU 0
      max_batch_size: 1           # ⚠️ 当前并发=1(串行)
    engine_args:
      model_stage: qwen3_tts      # 标识:这是个 TTS 模型
      model_arch: Qwen3TTSForConditionalGeneration   # 注册表里的架构类名
      worker_type: generation     # 用「生成型」worker(一步出结果,非自回归)
      scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler
      enforce_eager: true
      enable_prefix_caching: false
      engine_output_type: audio   # 最终输出:音频波形
      gpu_memory_utilization: 0.1 # 这台 stage 只用 10% 显存
      distributed_executor_backend: "mp"
      max_num_batched_tokens: 1000000   # 音频 token 序列很长,故意放大上限
    final_output: true
    final_output_type: audio

💡 你不需要现在就看懂每一行。先记住两个关键点:

  1. Qwen3-TTS 在这里只占一个 stage(不像 Omni 模型有 3 个 stage)------因为它本身就是「文本直接到音频」的端到端模型。
  2. worker_type: generation + OmniGenerationScheduler ------ 这告诉框架「这个模型不是逐 token 自回归的,而是『一步前向就出结果』」。这是 vLLM-Omni 为 TTS/扩散模型专门做的 fast-path(详见第 4 节)。

启动后你会看到类似 vLLM 的启动日志,最后出现 Uvicorn running on http://0.0.0.0:8000 就说明服务起来了。

先做个健康检查

bash 复制代码
# 列出可用音色
curl http://localhost:8000/v1/audio/voices
# 返回类似:{"voices": ["cherry","Ethan","chelsie", ...]}

# 服务健康
curl http://localhost:8000/health

1.3 发送你的第一个 TTS 请求

vLLM-Omni 的 TTS 接口是 OpenAI 兼容的 /v1/audio/speech (就是 OpenAI 那个 TTS API 的格式)。路由注册在 vllm_omni/entrypoints/openai/api_server.py:775

用 curl:

bash 复制代码
curl -X POST http://localhost:8000/v1/audio/speech \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer EMPTY" \
  -d '{
    "model": "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice",
    "input": "你好,这是通过 vLLM-Omni 加速合成的第一句话。",
    "voice": "Vivian",
    "response_format": "wav"
  }' \
  --output hello.wav

# 听一下
play hello.wav   # 或用任意播放器

请求体字段(OpenAICreateSpeechRequest,定义在 vllm_omni/entrypoints/openai/protocol/audio.py:7-57):

字段 类型 默认 说明
input str 必填 要合成的文本
model str 可选 单模型部署时可省
voice str 可选 说话人(如 Vivian/Ryan),大小写不敏感
instructions str 可选 风格/情感指令(如「用愤怒的语气说」)
response_format wav/pcm/flac/mp3/aac/opus wav 输出音频格式
speed 0.25~4.0 1.0 变速不变调(用 librosa 实现,见 audio_utils_mixin.py:76-93
task_type CustomVoice/VoiceDesign/Base 可选 任务类型
language str 可选 Auto/Chinese/English/...
ref_audio str Base 必填 声音克隆的参考音频(URL 或 base64 data URL)
ref_text str 可选 参考音频的转录文本(ICL 模式用)
x_vector_only_mode bool 可选 只用说话人向量,不走上下文学习克隆
max_new_tokens 1~4096 可选 生成 token 上限

用 Python(参考 examples/online_serving/qwen3_tts/openai_speech_client.py:65-91):

python 复制代码
import httpx

resp = httpx.post(
    "http://localhost:8000/v1/audio/speech",
    headers={"Authorization": "Bearer EMPTY"},
    json={
        "model": "Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice",
        "input": "这是用 Python 调用的合成结果。",
        "voice": "Vivian",
        "response_format": "wav",
    },
    timeout=300.0,
)
with open("out.wav", "wb") as f:
    f.write(resp.content)   # ⚠️ 注意:响应是完整的一段音频字节流

⚠️ 关于「流式」的重要澄清 :上面两段代码都是「一次性拿到完整音频」。

你可能会问:TTS 不都该流式吗?OpenAI 的 /v1/audio/speech 不是支持 stream_format: "sse" 吗?

答案是:当前版本的 vLLM-Omni 会直接拒绝 sse 。证据在 vllm_omni/entrypoints/openai/protocol/audio.py:52-57,校验函数会抛错:"'sse' is not a supported stream_format yet. Please use 'audio'."。服务端在 vllm_omni/entrypoints/openai/serving_speech.py:303 直接返回 Response(content=完整音频字节, media_type=...)

所以目前 TTS 是「整段生成完再返回」 。流式是内部已经有机制(见第 4.2 节的 chunked_decode_streamingasync_chunk),只是对外服务接口还没开放。这是框架演进路线上的已知限制。

验证输出正确:把 wav 存下来听,确认音色/内容/语言正确即可。

1.4 跑一次基准测试,看懂性能数字

vLLM-Omni 自带两套 benchmark:

  1. 离线管线基准 (vLLM-Omni pipeline vs HF Transformers):benchmarks/qwen3-omni/
  2. 在线服务基准vllm bench serve,含音频专属指标)

先看在线服务怎么压测(对你日常评估 TTS 服务最有用):

bash 复制代码
# 用 vllm 自带的 bench serve 子命令压测(vLLM-Omni 扩展了音频指标)
vllm bench serve \
    --backend vllm-omni \
    --base-url http://localhost:8000 \
    --model Qwen/Qwen3-TTS-12Hz-1.7B-CustomVoice \
    --num-prompts 100 \
    --percentile-metrics ttft,tpot,itl,e2el,audio_ttfp,audio_rtf,audio_duration

输出里的关键指标解读 (指标定义在 vllm_omni/benchmarks/metrics/metrics.py:11-27):

指标 全称 对 TTS 的意义
TTFT Time To First Token 文本侧首 token 延迟。对纯 TTS 意义不大
TPOT Time Per Output Token 每生成一个 token 的平均耗时
AUDIO_TTFP Time To First Packet(音频首包延迟) TTS 最关键的延迟指标------用户从发请求到听到第一个音要等多久
AUDIO_RTF Real Time Factor 生成 1 秒音频需要多少秒。<1 就是「实时」(生成比播放快)
audio_throughput audio duration / s 每秒能产出多少秒的音频(吞吐)

官方公布的性能数字 (来自 docs/cli/bench/serve.md,注意:这是 Qwen3-Omni 全模态模型,含 ASR+TTS,不是纯 TTS,仅供参考量级):

场景 Token 吞吐 TTFT TPOT 音频 RTF
单流 73.04 tok/s 124.76 ms --- ---
并发 2603 tok/s(约 35×) 117.15 ms 0.73 ms 0.47

读这张表你能得到两个直觉:

  1. 并发下吞吐提升约 35 倍(73 → 2603 tok/s)------这正是连续批处理的威力(第 4.1 节)。
  2. AUDIO_RTF = 0.47 意味着生成速度约是实时的 2.1 倍------说明这个模型/框架组合已经能支撑实时语音交互(但前提是流式接口开放后)。

关于「vLLM-Omni 比 HF Transformers 快多少」,官方放了一张图 benchmarks/qwen3-omni/vllm-omni-vs-hf.png(README :70-74 标注 "actual experiment results"),论文 arXiv:2602.02204 有详细数字。纯 Qwen3-TTS 的独立对比数字仓库里没有给出 ,要自己跑 benchmarks/qwen3-omni/ 下的脚本复现。

离线基准复现命令(benchmarks/qwen3-omni/):

bash 复制代码
# HF Transformers 基准
bash benchmarks/qwen3-omni/transformers/eval_qwen3_moe_omni_transformers.sh

# vLLM-Omni 基准
bash benchmarks/qwen3-omni/vllm_omni/eval_qwen3_moe_omni.sh

二、项目整体认知

2.1 vLLM-Omni 到底是什么

一句话:vLLM-Omni 是在 vLLM 之上扩展的「全模态(Omni-Modality)统一推理与服务平台」

  • 发布时间:2025 年 11 月首发,2026 年 2 月发布第一个稳定版 0.14.0(README.md:18-22)。
  • 论文:arXiv:2602.02204《vLLM-Omni: Fully Disaggregated Serving for Any-to-Any Multimodal Models》。

它要解决的三个核心问题docs/design/architecture_overview.md:14-19):

  1. 非文本输出 :vLLM 原本只会输出 token(文本),Omni 让它能输出图像、音频、视频。
  2. 非自回归结构:vLLM 原本只支持「逐 token 生成」的自回归模型,Omni 扩展到 **DiT(Diffusion Transformer)**等「一步或迭代并行生成」的结构------这恰好是很多 TTS vocoder 和声音生成模型的范式。
  3. 与 vLLM 核心无缝集成:尽可能复用 vLLM 已有的 KV cache、调度、量化等优化,而不是另起炉灶。

在 TTS 场景下的核心价值主张:

把 TTS 推理从「1 个请求独占 1 张 GPU、串行处理」的传统模式,变成「多请求并发批处理 + 多阶段流水线并行」的高吞吐模式。

典型 TTS 落地场景:

  • 实时 TTS 流式服务:在线客服、语音助手、有声读物边合成边播放。(⚠️ 注意 1.3 节:当前纯 TTS 在线流式接口未开放,但内部机制已就绪)
  • 离线批量语音合成:把几万条文本一次性喂进去,靠连续批处理榨干 GPU。
  • 语音对话(Omni 模型):ASR 听懂 → LLM 思考 → TTS 说出来,一条流水线搞定(Qwen3-Omni 这种)。

2.2 它和原生 vLLM 是什么关系

这是你最该搞清楚的一件事。结论先行:vLLM-Omni 没有 fork(分叉)vLLM,而是用「继承 + monkey-patch(运行时打补丁)」的方式做最小侵入式扩展。

直接复用 vLLM 的部分(基本没动):

  • vLLM 的引擎主循环(LLMEngine 的 step/prefill/decode)------完全复用,连代码都没改
  • PagedAttention 算子、KV cache 管理(KVCacheManager)。
  • 量化引擎(AWQ/GPTQ/FP8/compressed-tensors)。
  • Worker、ModelRunner 的骨架。
  • OpenAI 兼容的 API server 框架。

vLLM-Omni 新增/改造的部分(Omni 独有):

  • 多 Stage 编排层Omni / AsyncOmni / OmniStage)------这是 vLLM 完全没有的东西。vLLM 只有一个引擎,Omni 在它之上加了「多引擎流水线编排」。
  • OmniConnector:stage 之间的数据传输抽象(共享内存/跨节点)。
  • 定制调度器OmniARScheduler(自回归 stage)、OmniGenerationScheduler(一步生成 stage,用于 TTS code2wav)。
  • 音频前后处理:mel 谱、RVQ codec、声码器、流式拼接。
  • monkey-patch 机制vllm_omni/patch.py):导入 vllm_omni 时,自动把 vLLM 里的若干类替换成 Omni 版本。

「继承关系」一图看懂 (文字版,源图 docs/source/architecture/vllm-omni-main-architecture.png):

复制代码
你的业务代码 (TTS 服务)
        │ 调用
        ▼
┌─────────────────────────────────────────────┐
│  Omni 编排层 (vLLM-Omni 新增)                │
│  Omni / AsyncOmni  ←── 离线/在线入口          │
│       │ 管理                                  │
│       ▼                                      │
│  OmniStage (stage 0) → OmniConnector → OmniStage (stage 1) → ...
│  (每个 stage 独立进程、独立 GPU、独立引擎)     │
└─────────────────────────────────────────────┘
        │ 每个 stage 内部包裹一个
        ▼
┌─────────────────────────────────────────────┐
│  vLLM 引擎 (复用,未改主循环)                  │
│  LLMEngine ── 调度器 ── Worker ── ModelRunner │
│  + PagedAttention + KV cache + 量化          │
└─────────────────────────────────────────────┘

「monkey-patch」具体干了啥vllm_omni/patch.py:18-33,在 vllm_omni/__init__.py:16 导入时自动执行):

它把 vLLM 里的这些类替换成 Omni 加强版:

vLLM 原始类 替换为 加了什么
Request OmniRequest (vllm_omni/request.py:14) prompt_embedsexternal_req_idadditional_information(携带 stage 间张量)
TokensPrompt OmniTokensPrompt (vllm_omni/inputs/data.py) additional_information
EngineCoreRequest/Output Omni* (vllm_omni/engine/__init__.py:56,76) 多模态输出字段
MRotaryEmbedding OmniMRotaryEmbedding 多模态位置编码

💡 类比你熟悉的:这就像你给一个 Python 类用 @property 或装饰器加功能,而不是把整个类复制一份出来改。好处是 vLLM 升级了,Omni 跟着升级的代价很小。

「LLMEngine 本身没被 fork」的铁证vllm_omni/entrypoints/omni_llm.py:158):

python 复制代码
class OmniLLM:
    def __init__(self, ...):
        # 直接调原生 vLLM 创建引擎
        self.llm_engine = LLMEngine.from_engine_args(engine_args=engine_args, ...)
        # 只换了两个 processor(数据进出引擎的切面)
        self.llm_engine.output_processor = MultimodalOutputProcessor(...)
        self.llm_engine.input_processor = OmniInputProcessor(...)

调度器则是通过 engine_args.scheduler_cls(YAML 里那个字符串路径)被 vLLM 自己的配置机制加载进来的------完全不用改 LLMEngine 代码

2.3 技术分层架构

从上到下六层,每层标注它在 TTS 推理里干啥:

复制代码
┌──────────────────────────────────────────────────┐
│ 服务接口层                                        │ ← TTS 用户请求入口
│ /v1/audio/speech、/v1/audio/voices (OpenAI 兼容)  │   api_server.py / serving_speech.py
├──────────────────────────────────────────────────┤
│ 多 Stage 编排层 (Omni 独有)                       │ ← 决定请求怎么在 stage 间流动
│ Omni / AsyncOmni (Orchestrator)                   │   omni.py / async_omni.py
│ OmniStage (每 stage 独立进程/GPU)                 │   omni_stage.py
│ OmniConnector (stage 间数据传输: SHM)             │   distributed/omni_connectors/
├──────────────────────────────────────────────────┤
│ 模型推理层                                        │ ← TTS 模型真正计算的地方
│ Qwen3-TTS / Thinker+Talker+Code2Wav 等            │   model_executor/models/
├──────────────────────────────────────────────────┤
│ 加速引擎层                                        │ ← 提速的核心
│ PagedAttention / 连续批处理 / KV cache / 量化     │   core/sched/ + 复用 vLLM
│ 投机采样 / CUDA Graph / 算子融合                  │
├──────────────────────────────────────────────────┤
│ 前后处理层                                        │ ← TTS 输入输出的转换
│ 文本 tokenize / RVQ codec / Mel 谱 / BigVGAN      │   model_executor/models/qwen3_tts/
│ 流式音频拼接 / 说话人向量提取                      │
├──────────────────────────────────────────────────┤
│ 硬件通信层                                        │ ← GPU 上的底层操作
│ CUDA / ROCm / NCCL / 显存管理                     │   worker/ + platforms/
└──────────────────────────────────────────────────┘

2.4 一个 TTS 请求的生命周期

这是把上面六层「串起来」看的最佳方式。以 Qwen3-TTS(单 stage)为例,一个请求从进入到返回的全过程(对应代码在 vllm_omni/entrypoints/omni.py:619_run_generation()):

复制代码
[客户端] POST /v1/audio/speech {"input":"你好", "voice":"Vivian"}
   │
   ▼
[服务接口层] serving_speech.py:183 create_speech()
   │  把 OpenAI 请求转成内部 prompt dict:
   │  {"prompt":"<|im_start|>assistant\n你好<|im_end|>...",
   │   "additional_information":{"task_type":["CustomVoice"], "text":["你好"], "speaker":["Vivian"], ...}}
   ▼
[编排层] Omni.generate() / AsyncOmni.generate()
   │  生成 request_id,submit 到 stage_list[0]
   ▼
[OmniStage 0: code2wav] (omni_stage.py)
   │  stage worker 进程从队列取请求
   │  按max_batch_size攒批 → 喂给内部 vLLM engine
   ▼
[加速引擎层] OmniGenerationScheduler (omni_generation_scheduler.py:39)
   │  fast-path: 一次性分配所有 input token (音频序列长)
   ▼
[模型推理层] Qwen3TTSModelForGeneration.forward() (qwen3_tts.py:91)
   │  分发到 generate_custom_voice()
   │  内部: 文本→token→talker生成声学token→speech_tokenizer.decode→BigVGAN→波形
   ▼
[前后处理层] 音频 PCM 张量 → OmniOutput(multimodal_outputs={"model_outputs":audio, "sr":24000})
   ▼
[编排层] MultimodalOutputProcessor (output_processor.py:430 _process_audio_output)
   │  从 multimodal_outputs 提取音频张量
   ▼
[服务接口层] audio_utils_mixin.py:24 create_audio()
   │  PCM → wav/pcm/mp3 编码 (soundfile)
   ▼
[客户端] 收到完整音频字节流 (Response, media_type=audio/wav)

💡 对比你熟悉的传统 TTS 部署:传统方式里,这条链路是你自己用 Python 串起来的(g2p → encoder → decoder → vocoder,一个个函数调用)。vLLM-Omni 把它装进了一个「带调度、带批处理、带 stage 管理」的框架里------单请求时看起来一样,多请求并发时差别就出来了(见第 4 节)。

如果是 Omni 模型(如 Qwen3-Omni,3 个 stage),请求会在 stage 间流动:

复制代码
stage 0 (thinker, AR): 多模态理解 + 生成文本/COT
   │ OmniConnector (SHM) 传 latent
   ▼
stage 1 (talker, AR): 文本嵌入 → RVQ codec codes
   │ OmniConnector (SHM) 传 codec codes
   ▼
stage 2 (code2wav, generation): codec codes → 音频波形
   │
   ▼
最终输出 (text + audio)

stage 之间通过 engine_input_sourcecustom_process_input_func 串联(详见第 5 节)。

2.5 横向对比:原生 vLLM vs vLLM-Omni

对比维度 原生 vLLM(纯文本) vLLM-Omni(TTS)
输入类型 token ids 文本 + 可选参考音频(声音克隆)
输出类型 token ids(离散文本) 音频 token + 波形 PCM(连续 + 计划流式)
核心模型 LLM LLM + Audio Encoder + Vocoder(如 BigVGAN)
生成范式 纯自回归(逐 token) 自回归(AR)+ 一步生成(generation,用于 code2wav)
批处理 纯文本连续批处理 文本+音频混合批处理;generation stage 有专门 fast-path
调度策略 单一 Scheduler 多 stage 各自一个调度器,stage 间靠 connector 串联
加速重点 KV cache、PagedAttention KV cache + 跨 stage KV 转移 + 音频特征缓存 + 分块流式
显存分布 模型权重 + KV cache 模型权重 + KV cache + 音频缓存 + 每 stage 独立显存配额
服务协议 HTTP/gRPC(文本流) HTTP SSE/gRPC(文本流已支持;音频流接口待开放
部署形态 单引擎进程 多 stage = 多进程,可跨 GPU/节点

三、代码结构与模块详解(聚焦 TTS)

本章只讲跟 TTS 推理相关的目录和模块。视觉/图像相关的一律跳过。

3.1 项目目录地图

复制代码
vllm-omni/
├── vllm_omni/                    ★ 核心源码包
│   ├── __init__.py              导出 Omni / AsyncOmni / OmniModelConfig;导入时触发 patch.py
│   ├── patch.py                 ★ monkey-patch:替换 vLLM 的 Request/Prompt 等类
│   ├── request.py               OmniRequest(带 prompt_embeds/additional_information)
│   ├── outputs.py               OmniRequestOutput / OmniModelRunnerOutput
│   ├── engine/                  ★ 引擎层(继承 vLLM EngineArgs/InputProcessor/OutputProcessor)
│   │   ├── arg_utils.py         OmniEngineArgs / AsyncOmniEngineArgs(加 stage 相关参数)
│   │   ├── input_processor.py   OmniInputProcessor(序列化 prompt_embeds/additional_info)
│   │   └── output_processor.py  MultimodalOutputProcessor(音频/多模态输出路由)
│   ├── core/                    ★ 调度与执行核心
│   │   └── sched/               ★ Omni 定制调度器(继承 vLLM Scheduler)
│   │       ├── omni_ar_scheduler.py          OmniARScheduler(自回归 stage)
│   │       ├── omni_generation_scheduler.py  OmniGenerationScheduler(TTS code2wav!)
│   │       └── output.py        OmniNewRequestData 等
│   ├── worker/                  ★ Worker 层(继承 vLLM GPUWorker)
│   │   ├── gpu_worker 派生       GPUARWorker / GPUGenerationWorker
│   │   ├── gpu_model_runner.py   OmniGPUModelRunner(prompt_embeds overlay、M-RoPE)
│   │   └── gpu_ar_model_runner.py GPUARModelRunner(两阶段 execute/sample)
│   ├── entrypoints/             ★ 服务与编排入口
│   │   ├── omni.py              ★ Omni 类(同步 Orchestrator,离线入口)
│   │   ├── async_omni.py        AsyncOmni(异步,在线服务用)
│   │   ├── omni_stage.py        ★ OmniStage(单 stage 生命周期、队列、进程)
│   │   ├── omni_llm.py          OmniLLM(包装原生 LLMEngine)
│   │   ├── stage_utils.py       SHM 读写、IPC 编解码
│   │   ├── cli/                 ★ vllm-omni 命令行(serve/benchmark)
│   │   └── openai/              ★ OpenAI 兼容 API
│   │       ├── api_server.py    路由注册(/v1/audio/speech 等)
│   │       ├── serving_speech.py ★ TTS speech 接口实现
│   │       └── audio_utils_mixin.py  音频编码(wav/pcm/mp3)
│   ├── model_executor/          ★ 模型实现与加载
│   │   ├── models/              ★ 各模型实现(按架构分目录)
│   │   │   ├── registry.py      ★ 模型注册表(_OMNI_MODELS 字典)
│   │   │   ├── qwen3_tts/       ★ Qwen3-TTS 实现(本文重点)
│   │   │   ├── qwen3_omni/      Qwen3-Omni 3-stage
│   │   │   ├── qwen2_5_omni/    Qwen2.5-Omni 3-stage(含 BigVGAN vocoder)
│   │   │   └── output_templates.py  OmniOutput(forward 返回类型)
│   │   ├── stage_configs/       ★ Stage 配置 YAML(qwen3_tts.yaml 等)
│   │   └── stage_input_processors/  ★ stage 间数据转换函数
│   ├── inputs/                  输入数据结构(OmniTokensPrompt 等)
│   ├── distributed/             ★ 分布式与 stage 间通信
│   │   └── omni_connectors/     ★ OmniConnector(SHM/Mooncake/Yuanrong)
│   ├── diffusion/               扩散模型(图像/视频,TTS 可跳过)
│   ├── config/                  ★ OmniModelConfig 配置
│   ├── platforms/               硬件平台(CUDA/ROCm/NPU/XPU)
│   ├── metrics/                 ★ 性能统计(stats.py)
│   └── benchmarks/              bench serve 指标定义
├── examples/                    ★ 示例(必看)
│   ├── offline_inference/
│   │   ├── qwen3_tts/          ★ 离线 TTS(end2end.py)
│   │   └── qwen3_omni/         离线全模态
│   └── online_serving/
│       └── qwen3_tts/          ★ 在线 TTS 服务(run_server.sh)
├── benchmarks/                  ★ 性能基准(vs HF Transformers)
├── docs/                        ★ 官方文档(design/configuration/getting_started)
├── tests/                       测试(含 e2e stage_configs)
└── pyproject.toml / setup.py    打包

3.2 核心模块逐一拆解

每个模块按 作用 → 源码路径 → 入口类/函数 → 关键执行逻辑 讲。

(1)多 Stage 编排器(Omni / AsyncOmni / OmniStage)

作用:把一个多阶段模型(或单阶段)组织成一条流水线,管理请求在 stage 间的流动、批处理、资源分配。这是 vLLM 完全没有的层。

源码

  • vllm_omni/entrypoints/omni.py ------ 同步编排器(离线用)
  • vllm_omni/entrypoints/async_omni.py ------ 异步编排器(在线服务用)
  • vllm_omni/entrypoints/omni_stage.py ------ 单 stage 抽象

入口

  • Omni 类(omni.py:492),导出于 vllm_omni/__init__.py:28
  • Omni.generate()omni.py:549)→ 内部 _run_generation()omni.py:619

OmniStage 关键属性omni_stage.py:226-266):

  • stage_idstage_type"llm" / "diffusion"
  • model_stage"qwen3_tts" / "thinker" / "talker" / "code2wav"
  • engine_input_source(上游 stage 列表,如 [0]
  • final_outputfinal_output_type"text" / "audio"
  • custom_process_input_func(stage 间数据转换函数)

关键执行逻辑_run_generation()omni.py:717-878):

复制代码
1. 校验 sampling_params_list 长度 == stage 数 (omni.py:630)
2. 为每个请求生成 request_id (omni.py:651)
3. 确定请求终止于哪个 stage (get_final_stage_id_for_e2e, omni.py:658)
4. 把请求 submit 到 stage_list[0] (omni.py:688)
5. 进入调度循环 while completed_requests < total_requests (omni.py:717):
   a. 轮询每个 stage 的 try_collect() 取已完成结果 (omni.py:719)
   b. 反序列化(SHM 或 inline)(omni.py:738)
   c. stage.set_engine_outputs() 存到 stage 对象 (omni.py:777)
   d. 若该 stage 是 final_output → 构造 OmniRequestOutput 并 yield (omni.py:779)
   e. 否则 next_stage.process_engine_inputs() 派生下游输入 → try_send_via_connector 转发 (omni.py:824)
   f. 无进展时 time.sleep(0.005) 让出 CPU (omni.py:877)

Stage worker 进程内的攒批omni_stage.py:626_stage_worker):

  • in_q.get() 取 task
  • max_batch_sizebatch_timeout 攒批(omni_stage.py:770-833
  • 喂给内部 stage_engine.generate(),逐 request 把结果 out_q.put()

💡 TTS 并发怎么调? 关键就是改 stage config YAML 里的 runtime.max_batch_size(当前默认 1)。但注意:单请求串行是当前官方验证过的稳定配置,调大并发需要你充分测试音质和稳定性。

(2)调度器(Omni 的「改造点」之一)

作用 :决定「哪些请求、以什么顺序、凑成多大的 batch」送进引擎。Omni 的调度器继承 vLLM 的 Scheduler,不是从零写

继承关系docs/design/module/ar_module.md:24-39):

复制代码
vllm.v1.core.sched.scheduler.Scheduler (VLLMScheduler)
        ├── OmniARScheduler          (core/sched/omni_ar_scheduler.py:41)
        │   用于 thinker/talker 等自回归 stage
        └── OmniGenerationScheduler  (core/sched/omni_generation_scheduler.py:25)
            用于 code2wav 等一步生成 stage(TTS!)

OmniARScheduler(自回归 stage 用):

  • 重写 schedule()omni_ar_scheduler.py:154):调 super().schedule() 后,把 NewRequestData 包成 OmniNewRequestData(附加 prompt_embedsadditional_information),并处理跨 stage 的 chunk。
  • 重写 update_from_output()omni_ar_scheduler.py:202):加 KV cache 转移触发、put_chunk() 推送 latent 到下游。

OmniGenerationSchedulerTTS code2wav 专用omni_generation_scheduler.py:25):

  • 完全重写 schedule()omni_generation_scheduler.py:39 ,不调 super:走「Diffusion/generation fast-path」------一次性把请求所有 input token 调度掉(:80-100),预算不足才回退 super().schedule():157)。
  • 重写 update_from_output():272):一步就标记 FINISHED_STOPPED:357),释放资源。

💡 为什么 TTS code2wav 要单独的 fast-path? 因为 code2wav 不是「逐 token 生成」,它是「拿到全部 codec codes 后一次性算出整段音频」。vLLM 原生的 prefill/decode 循环对它不适用,所以要专门写一个「一次性调度」的路径。

连续批处理在哪实现? ------不在 Omni 层,而是复用 vLLM 引擎核心 。每个 stage 内部的 OmniLLM/AsyncOmniLLM 包的是原生 vLLM.v1.engine.llm_engine.LLMEngine。vLLM 的 step() 循环 + Scheduler.schedule() 内部的 running/waiting 队列管理就是 continuous batching。Omni 在 stage worker 层做的是跨请求攒批max_batch_size + batch_timeout),然后一次性喂给 vLLM engine,由 vLLM 内部做 token 级别的连续批处理。

(3)模型加载与实现(TTS 模型怎么进来的)

注册机制 :基于字典的声明式注册(不是 @register 装饰器)。

源码vllm_omni/model_executor/models/registry.py

  • _OMNI_MODELS 字典(registry.py:3-56):架构名 → (模块目录, 模块文件, 类名)
  • OmniModelRegistry = _ModelRegistry(...)registry.py:65-82):构建懒加载注册表,组合 vLLM 的 _VLLM_MODELS_OMNI_MODELS

Qwen3-TTS 的注册条目(registry.py:51-55):

python 复制代码
"Qwen3TTSForConditionalGeneration": ("qwen3_tts", "qwen3_tts", "Qwen3TTSModelForGeneration")

Qwen3-TTS 实现vllm_omni/model_executor/models/qwen3_tts/qwen3_tts.py):

  • Qwen3TTSModelForGeneration:65):vLLM 可运行的封装器。
  • __init__:66):通过 Qwen3TTSModel.from_pretrained 加载 HF 模型,设 self.have_multimodal_outputs = True
  • forward:91):分发到 generate_custom_voice / generate_voice_design / generate_voice_clone:137-148)。
  • compute_logits:272):返回 None(非自回归)。
  • load_weights:259):空操作 ------权重在 from_pretrained 里用 HF 的 AutoModel 加载,不走 vLLM 的 loader。

HF 顶层模型modeling_qwen3_tts.py):

  • Qwen3TTSForConditionalGeneration:1796):持有 self.talker:1803)、self.speaker_encoder(ECAPA-TDNN,:277,仅 Base 模型)、self.speech_tokenizer:1810,懒加载)。
  • from_pretrained:1838):加载主模型后,额外下载 speech_tokenizer/ 子目录(:1871),并加载 Qwen3TTSTokenizer:1892)。
  • extract_speaker_embedding(audio, sr):1918):建 mel → self.speaker_encoder → 说话人向量(声音克隆用)。

💡 重要差异 :Qwen3-TTS 的权重加载走的是 HF 的 AutoModel,不是 vLLM 的权重加载器 (所以 load_weights 是空操作)。这是它跟一般 vLLM 文本模型最大的不同之一------也意味着 TTS 的量化/加载优化目前不像纯文本 LLM 那么成熟。

(4)音频前后处理(TTS 的「输入输出转换」)

输入处理(文本→token、参考音频、说话人向量)

  • 文本 tokenize:qwen3_tts.py:508_tokenize_texts)。
  • 声音克隆 prompt 构建:qwen3_tts.py:589create_voice_clone_prompt)------标准化参考音频 → speech_tokenizer.encoderef_codes:668)→ 重采样 → extract_speaker_embedding:693)。支持两种模式:
    • x-vector only:只用说话人向量。
    • ICL(上下文学习) :需要 ref_text,用参考音频的 codec codes 做上下文。
  • 说话人向量提取有两条路径:
    • ECAPA-TDNNmodeling_qwen3_tts.py:277,PyTorch,Base 模型用)。
    • campplus.onnxqwen3_tts/tokenizer_25hz/vq/speech_vq.py:144ONNX Runtime CPU,25Hz codec 路径用)。

输出处理(声学 token → mel → PCM 流式拼接)

不同模型的 vocoder 实现不一样:

模型 声码器实现 关键文件
Qwen2.5-Omni DiT(流匹配,RungeKutta4 ODE)+ BigVGAN qwen2_5_omni/qwen2_5_omni_token2wav.py:1446
Qwen3-Omni codec → 波形(无 mel 阶段,1280× 上采样) qwen3_omni/qwen3_omni_code2wav.py:34
Qwen3-TTS codec → mel(DiT) → 波形(BigVGAN),通过 speech_tokenizer.decode qwen3_tts/qwen3_tts_tokenizer.py:261

流式分块拼接(保证音频 chunk 边界无裂纹/pop 音):

  • Qwen2.5-Omni:qwen2_5_omni_token2wav.py:1564process_little_chunk)、:1632_process_chunk_for_50hz)。
  • Qwen3-Omni:qwen3_omni_code2wav.py:160chunked_decodeleft_context_size=25 重叠去伪影)、:199chunked_decode_streaming)。

音频编解码(RVQ codec / Mel)

  • Qwen3TTSTokenizerqwen3_tts_tokenizer.py:46):25Hz/12Hz speech tokenizer 封装,encode:210)/decode:261)。
  • RVQ 核心(纯 PyTorch):tokenizer_25hz/vq/core_vq.pyEuclideanCodebook:114VectorQuantization:245DistributedResidualVectorQuantization:334)。
  • WhisperEncoderVQspeech_vq.py:189)、MelSpectrogramFeaturesspeech_vq.py:41)。

引擎输出路由vllm_omni/engine/output_processor.py:430_process_audio_output)------从 multimodal_outputs 里按 ("audio","audios","wav","waveform","audio_pcm","pcm") 等键提取音频张量(:434),暴露为 pooling_output

⚠️ 再次强调 :音频路径没有任何自定义 CUDA 算子 。RVQ、STFT、BigVGAN、Kaiser-sinc 上采样、flow-matching ODE 全是纯 PyTorch。唯一的非 PyTorch 是 campplus.onnx(ONNX Runtime CPU)。这影响你对「算子级优化空间」的预期------目前 TTS 的优化主要在调度和流水线层,不在底层 kernel。

(5)服务接口(HTTP/OpenAI 兼容)

CLI 入口

  • vllm_omni/entrypoints/cli/main.py:12 ------ 检测 --omni 标志分流。
  • vllm_omni/entrypoints/cli/serve.py:41 ------ OmniServeCommandname="serve"

路由注册vllm_omni/entrypoints/openai/api_server.py):

  • :772 ------ 先移除上游默认的 /v1/audio/speech
  • :775-800 ------ 注册新的 POST /v1/audio/speechcreate_speech)。
  • :803-819 ------ 注册 GET /v1/audio/voiceslist_voices,返回可用音色)。

TTS speech 接口实现vllm_omni/entrypoints/openai/serving_speech.py:40OmniOpenAIServingSpeech(OpenAIServing, AudioMixin))。

  • _TTS_MODEL_STAGES = {"qwen3_tts"}:21)------靠这个判断是否是 TTS 模型。
  • _load_supported_speakers:47)------从 hf_config.talker_config.spk_id 加载音色。
  • create_speech:183)------主流程:校验 → 拼 prompt → 调 engine_client.generate(output_modalities=["audio"]) → 提取音频 → 编码返回。

音频编码audio_utils_mixin.py:50-57):

python 复制代码
supported_formats = {
    "wav":  ("WAV",  "audio/wav",  {}),
    "pcm":  ("RAW",  "audio/pcm",  {"subtype": "PCM_16"}),
    "flac": ("FLAC", "audio/flac", {}),
    "mp3":  ("MP3",  "audio/mpeg", {}),
    "aac":  ("AAC",  "audio/aac",  {}),
    "opus": ("OGG",  "audio/ogg",  {"subtype": "OPUS"}),
}

底层用 soundfile 编码;speed != 1.0 时用 librosa.effects.time_stretch 变速不变调(:76-93)。

gRPC 接口:当前 TTS 路径主要暴露 HTTP/OpenAI 兼容接口;gRPC 更通用在 Omni 的 chat 路径。TTS 日常用 HTTP 即可。

(6)Stage 间通信(OmniConnector)

作用:stage 之间传数据(latent、codec codes、KV cache)。

双层架构

  • 轻量通知走 mp.Queueomni.py:107-108_stage_in_queues/_stage_out_queues)。
  • 重量数据走 OmniConnector

三种实现vllm_omni/distributed/omni_connectors/connectors/):

Connector 适用 文件
SharedMemoryConnector(默认) 单机,用 /dev/shm + 文件锁 shm_connector.py:18
MooncakeConnector 多节点(RDMA/SHM) mooncake_connector.py
YuanrongConnector 多节点 yuanrong_connector.py

SHM 阈值机制stage_utils.py:144,174,218):序列化后 > shm_threshold_bytes(默认 65536)的数据写共享内存,否则走进程队列。

⚠️ 当前所有 connector 都是 D2H2D(device→host→device) 模式------数据要先拷到 CPU 再传。未来才有 D2D(直接 GPU 间传输,用 NCCL/UCX)。这意味着跨 stage 传输大张量(如 latent)目前有 CPU 序列化开销,是延迟优化点。

(7)配置层(OmniModelConfig)

源码vllm_omni/config/model.py:35OmniModelConfig(ModelConfig))。

关键字段(config/model.py:62-75):

字段 默认 含义
stage_id 0 stage 编号
async_chunk False 跨 stage 异步流式分块(TTS 流式关键开关)
model_stage "thinker" thinker/talker/code2wav/qwen3_tts
model_arch "Qwen2_5Omni..." 注册表里的架构类名
engine_output_type None text/audio/latent(输出路由)
hf_config_name None stage 专属 hf config(如 talker_config)
stage_connector_config SharedMemoryConnector stage 间通信后端
omni_kv_config None KV 跨 stage 转移配置

命令行参数:vllm_omni/engine/arg_utils.py:48OmniEngineArgs)、:154AsyncOmniEngineArgs)。其余 TTS/audio/concurrency 参数继承自 vLLM 的 EngineArgsmax_num_seqsmax_num_batched_tokensgpu_memory_utilization 等)。

3.3 高频调优场景代码定位速查表

调优场景 主要修改文件 核心入口 改动要点
调整 TTS 并发上限 vllm_omni/model_executor/stage_configs/qwen3_tts.yaml runtime.max_batch_size 当前=1(串行),调大需充分测稳定性;同时关注 gpu_memory_utilization 是否够
优化首包延迟 stage config + entrypoints/cli/serve.py:120 --batch-timeoutasync_chunk 减小 batch_timeout;Omni 模型开 async_chunk: true(用 qwen3_omni_moe_async_chunk.yaml
改 TTS 前后处理 model_executor/models/qwen3_tts/qwen3_tts.py generate_custom_voice:956create_voice_clone_prompt:589 改 mel 参数/声码器;注意 forward 分发逻辑
换/加音色 serving_speech.py:47 + 模型 hf_config _load_supported_speakers 音色来自 hf_config.talker_config.spk_id
新增自定义 TTS 接口 entrypoints/openai/api_server.py + serving_speech.py 路由注册 :775 仿照 create_speech 加路由;复用 engine_client.generate(output_modalities=["audio"])
调整显存分配 stage config YAML engine_args.gpu_memory_utilization 单 GPU 多 stage 共享时,各 stage 配额之和 ≤ 1(见 docs/configuration/gpu_memory_utilization.md
改跨 stage 数据传输 distributed/omni_connectors/ SharedMemoryConnector:shm_connector.py:18 shm_threshold_bytes;多节点换 Mooncake/Yuanrong
看性能瓶颈 vllm_omni/metrics/stats.py OrchestratorAggregator:388 --log-stats,看 e2e_stage_N_wall_time_mstransfers_total_time_ms 找瓶颈 stage

四、核心加速技术原理全面详解

本章是全文技术深度最高的部分,不简化,每一项都详细展开。

统一四点结构:① 解决了什么痛点(用 TTS 经验描述)② 底层原理 + TTS 适配改造 ③ 源码落地 ④ 性能收益 + 触发条件。

先给你一张「复用 vs 改造」总览表(贯穿本章):

加速技术 复用 vLLM Omni 改造点 对 TTS 的价值
PagedAttention 算子 + KVCacheManager 调度器做 block_ids 截断支持跨 stage KV 迁移 高(code2wav 之外的自回归 stage)
连续批处理 调度骨架 为一步生成 stage 写 fast-path 极高(多请求并发的核心)
动态调度 调度器基类 token 预算 + 显存回退
KV cache / Prefix caching 完整功能 TTS 路径默认关;用跨 stage KV 转移替代 中(latent 输出不支持 prefix)
权重量化 算子 + 校验 仅透传 quant_config 中(TTS 权重走 HF loader,收益有限)
投机采样 EAGLE/ngram 新增 Qwen3 MTP(codec 多码本一次出) 高(talker stage)
CUDA Graph 包装器 YAML 默认 enforce_eager 低(当前默认关)
算子融合 RMSNorm/QKV/RoPE/MRoPE 无自写 kernel 中(仅自回归部分)
音频/说话人缓存 mm encoder cache 自有 prompt_embeds_cpu、VoiceClonePromptItem 预提取复用 极高(声音克隆场景)
流式分块 --- Omni 自有:chunked_decode_streaming + async_chunk 极高(实时性)

4.1 通用加速技术(复用/改造自 vLLM)


技术 1:PagedAttention(KV Cache 分页管理)

① 解决了什么痛点

你部署原生 TTS 时,decoder 在推理每个 token 时都要存这个 token 对应的 K(Key)和 V(Value)向量,用来算 attention。传统做法是为每条序列预分配一块连续显存(按最大长度算)。问题:

  • 如果序列比预期短,预留的显存白白浪费(内部碎片)。
  • 如果序列比预期长,得重新分配更大的块再拷贝(昂贵的显存搬运)。
  • 多条序列并发时,显存里东一块西一块,碎片化严重,能并发的请求数大打折扣

② 底层原理 + TTS 适配

PagedAttention 借用了操作系统虚拟内存的分页思想:把 KV cache 的显存切成固定大小的「页(block)」(比如每页存 16 个 token 的 KV),按需分配。一条序列用到哪页申请哪页,序列的 KV cache 用一张「页表」逻辑串联起来。

💡 类比:传统做法像「给每个客人预留一整间包厢,不管他来几个人」;PagedAttention 像「餐厅按人头拼桌,来几个坐几个位置」。同样的场地,能服务的客人多得多。

TTS 场景的适配改造

  • AR stage(thinker/talker)的 KV cache 管理完全复用 vLLM 的 PagedAttention。
  • 跨 stage KV 转移(Omni 独有):当 thinker stage 算完,它的 KV cache 可以「转移」给下游 stage 复用,而不是释放掉重来。Omni 调度器为此做了 block_ids 的截断/拷贝。

③ 源码落地

内容 位置
分页块分配(generation stage 调用) vllm_omni/core/sched/omni_generation_scheduler.py:82-86, 128-132self.kv_cache_manager.allocate_slots(...)
block_ids 截断(跨 stage KV 迁移用) vllm_omni/core/sched/omni_ar_scheduler.py:564-587_mark_request_for_kv_transfer
KV 释放 omni_ar_scheduler.py:462self.kv_cache_manager.free(req)
跨 stage KV 转移管理 vllm_omni/distributed/omni_connectors/kv_transfer_manager.py:47OmniKVTransferManager

注意:PagedAttention 算子本身完全复用 vLLM,Omni 没有重写 kernel。Omni 只在调度器层做了 block_ids 的拷贝/截断以支持跨 stage KV 转移。

④ 性能收益 + 触发条件

  • 收益 :显存碎片从「按最大长度预留」降到「按实际使用」,典型场景下能支持的并发请求数提升数倍(vLLM 原论文报告 KV cache 显存浪费从 ~60-80% 降到 <4%)。
  • 触发条件:默认开启,无需配置(vLLM v1 架构内置)。
  • TTS 注意:Qwen3-TTS 这种「一步生成」模型,KV cache 的作用主要在 talker 的自回归部分;code2wav 阶段不是逐 token 的,PagedAttention 价值相对小。

技术 2:连续批处理(Continuous Batching)

① 解决了什么痛点

你跑传统 TTS 批处理(static batching)时:凑够 N 个请求组成一个 batch,一起送 GPU。问题:这 N 个请求里,有的句子长有的短,短的先算完了,却得等最长的那个算完,整个 batch 才能释放 ------GPU 大量时间在「陪跑」短请求。更糟的是,得等 batch 凑满才开始算,第一个请求被拖着白白等了凑批时间

② 底层原理 + TTS 适配

连续批处理(也叫 iteration-level batching)的核心:不按 batch 粒度调度,按「每一步(iteration)」粒度调度 。每算完一个 token 步骤,调度器就重新看一遍队列------有请求完成了就踢出去,有新请求来了就插进来。请求即来即算、即完即走,GPU 几乎不停闲。

💡 类比:static batching 像「大巴车,凑满 40 人才发车,有人中途下车也不停」;continuous batching 像「公交车,每站都停,随时上下客,车一直在跑」。吞吐量天差地别。

TTS 场景适配

  • AR stage(thinker/talker):直接复用 vLLM 的连续批处理(omni_ar_scheduler.py:154super().schedule())。
  • generation stage(code2wav,TTS 核心)专门写了 fast-path :因为 code2wav 是「一步生成」,vLLM 原生 prefill/decode 循环不适用,Omni 写了 OmniGenerationScheduler.schedule()omni_generation_scheduler.py:39-261),一次性把请求所有 input token 调度进去,单步出结果。

③ 源码落地

内容 位置
AR stage 连续批处理(调父类) omni_ar_scheduler.py:154-155
generation stage fast-path(边到边连续批) omni_generation_scheduler.py:39-261(running 队列 65-100,waiting 队列 108-149)
单步即完结语义 omni_generation_scheduler.py:356-375update_from_outputFINISHED_STOPPED
stage worker 跨请求攒批 omni_stage.py:770-833max_batch_size + batch_timeout

举个 TTS 调度时序例子(帮你直观理解):

假设 code2wav stage,max_batch_size=4,同时来了 3 个合成请求(A=2秒音频、B=5秒、C=1秒):

复制代码
时刻 t0: A,B,C 到达 → stage worker 攒批成 [A,B,C],一次性送进 OmniGenerationScheduler
时刻 t1: GPU 一步算出 A,B,C 全部音频 → 三个请求同时 FINISHED → out_q
时刻 t2: 又来 D,E → 攒批 [D,E] → 送 GPU

(注:code2wav 是一步生成,所以「连续批」体现为「攒多个请求一起算一步」。如果是 talker 这种自回归 stage,则是真正的「逐 token 步骤级别」连续批处理,长短请求交错。)

④ 性能收益 + 触发条件

  • 收益 :官方 Qwen3-Omni 数据(docs/cli/bench/serve.md):单流 73 tok/s → 并发 2603 tok/s(约 35×) ,TPOT 从 481ms 降到 0.73ms
  • 触发条件
    • max_batch_size > 1(⚠️ Qwen3-TTS 默认是 1,你要改 stage config 才能享受并发红利)。
    • 请求有足够并发度(得真的有多个请求同时来)。
    • 显存够(PagedAttention 是基础,没它并发不起来)。
  • TTS 注意:纯 Qwen3-TTS(单 stage)的并发收益需要你自行测试验证(官方 35× 是 Omni 模型数据)。

技术 3:动态调度(Dynamic Scheduling)

① 解决了什么痛点

固定调度策略(比如「先来先服务」或「按固定 batch size」)在面对请求长度差异大、显存压力波动的场景时表现差:要么短请求被长请求阻塞,要么显存吃紧时还硬塞请求导致 OOM。

② 底层原理 + TTS 适配

动态调度:调度器在每一步实时评估「token 预算还剩多少」「显存还能不能塞下新请求」,据此决定是「从 waiting 队列拉新请求」还是「暂停拉取」。请求长度、显存余量、batch 构成都被动态考虑。

TTS 适配

  • token 预算动态分配:omni_generation_scheduler.py:47, 80-81, 126-127token_budget = self.max_num_scheduled_tokensnum_new_tokens = min(required_tokens, token_budget))。
  • 显存压力回退::87-92, 133-138new_blocks is None 时 break,避免 OOM)。
  • 每个 stage 独立配 max_num_batched_tokens(TTS 音频 token 序列长,所以 code2wav 配了 1000000 这种大值)。

③ 源码落地

内容 位置
调度器基类 omni_ar_scheduler.py:41OmniARScheduler(VLLMScheduler)
token 预算动态分配 omni_generation_scheduler.py:47, 80-81, 126-127
显存压力回退 omni_generation_scheduler.py:87-92, 133-138
每 stage 独立 token 上限 stage config max_num_batched_tokens

④ 性能收益 + 触发条件

  • 收益:避免 OOM 崩溃;在混合长短请求时保持高吞吐(短请求不被长请求完全拖死)。
  • 触发条件:默认开启(vLLM 调度器内置行为)。
  • TTS 调参 :code2wav 的 max_num_batched_tokens 设很大(1000000)是因为音频序列动辄几千 token,不能被默认的小上限卡住。

技术 4:KV Cache 优化 / Prefix Caching

① 解决了什么痛点

很多请求有公共前缀(比如同一个系统 prompt、同一个说话人 prompt 前缀)。传统做法每个请求都重新算一遍这些前缀的 KV cache,浪费算力。

② 底层原理 + TTS 适配

Prefix caching:把公共前缀的 KV cache 缓存下来,后续请求命中相同前缀就直接复用,跳过 prefill 计算。

TTS 场景的关键事实

  • ⚠️ 所有 TTS/Omni stage config 里 enable_prefix_caching: falseqwen3_tts.yaml:15qwen3_omni_moe.yaml:24,54,84 等)。
  • 原因(docs/configuration/stage_configs.md:207-211):输出 latent(hidden states)的 stage 不支持 prefix caching。而 Omni 模型的 thinker/talker 输出 latent,所以不能开。
  • Omni 的替代方案 :跨 stage KV 转移(OmniKVTransferManager)。当一个 stage 算完,KV cache 可以转移给下游复用,而不是丢弃。

③ 源码落地

内容 位置
Prefix caching 开关 vLLM EngineArgs.enable_prefix_caching(TTS 默认 false)
关闭原因 docs/configuration/stage_configs.md:207-211
跨 stage KV 转移(替代方案) omni_ar_scheduler.py:494-548_should_transfer_kv_for_request + _free_request
KV 转移配置 config/model.py:75omni_kv_configneed_send_cachekv_transfer_criteria
转移触发条件 omni_ar_scheduler.py:100-152prefill_finished / special_token

④ 性能收益 + 触发条件

  • Prefix caching 收益 :纯文本 LLM 场景能省 30-50% prefill 算力。但 TTS 路径当前默认关闭
  • 跨 stage KV 转移收益:Omni 多 stage 模型避免下游重复计算上游的 KV,是 Omni 流水线效率的关键。
  • 触发条件 :prefix caching 需 enable_prefix_caching: true(TTS 慎用);KV 转移需配置 omni_kv_config

技术 5:权重量化(AWQ / GPTQ / FP8)

① 解决了什么痛点

模型权重存成 FP16/BF16,显存占用大、显存带宽压力大(推理往往是 memory-bound,瓶颈在把权重从显存搬到计算单元)。量化把权重压到 INT4/INT8/FP8,显存省一半到四分之一,带宽压力也降,吞吐提升

② 底层原理 + TTS 适配

  • AWQ/GPTQ:训练后量化,把权重压到 INT4/INT8,保留少量高精度缩放因子。
  • FP8:用 8 位浮点(Hopper 架构原生支持),精度损失小。

TTS 场景的关键事实

  • 量化算子完全复用 vLLM(compressed_tensors/awq/gptq/fp8 通路)。
  • TTS 子模块通过 quant_config 参数透传给 QKVParallelLinear 等层。
  • ⚠️ 但 Qwen3-TTS 的主权重走 HF 的 AutoModel 加载(load_weights 是空操作),所以 TTS 主体权重的量化收益目前有限------量化主要作用于 code predictor 等用 vLLM 线性层的子模块。
  • ⚠️ 音频对量化更敏感:语音质量(MOS)受量化影响比文本更明显,需要评估。

③ 源码落地

内容 位置
量化配置注入(code predictor 注意力层) vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_code_predictor_mtp.py:71-88QKVParallelLinear(..., quant_config=...)
QuantizationConfig 导入 qwen3_omni_moe_code_predictor_mtp.py:32
量化校验 vllm_omni/config/model.py:303_verify_quantization()

④ 性能收益 + 触发条件

  • 收益:INT4 量化约省 75% 权重显存,吞吐提升 1.5-3×(视模型和硬件)。
  • 触发条件 :模型本身要有量化版权重(如 *-AWQ*-FP8),通过 --quantization 参数指定。
  • TTS 注意:务必评估量化后的音频 MOS,语音质量比文本更脆弱。

技术 6:投机采样(Speculative Decoding)

① 解决了什么痛点

大模型逐 token 生成,每出一个 token 都要跑一次完整 forward。但很多 token 是「容易预测的」(高频词、固定模式)------能不能先用个小模型快速「猜」一批,再让大模型一次性验证?猜对的就白赚,猜错的才重新算。

② 底层原理 + TTS 适配

  • EAGLE/n-gram(vLLM 复用):用小 draft 模型或 n-gram 预测若干 token,大模型并行验证,命中则一次出多个 token。
  • Omni 独有的 MTP(Multi-Token Prediction):Qwen3-Omni 的 codec 预测------一次预测多个 RVQ 码本层(残差预测),让 talker stage 一次出多层 codec codes。

TTS 场景:talker stage 生成 codec codes 时,用 MTP 一次出多码本,减少自回归步数。

③ 源码落地

内容 位置
vLLM EAGLE/ngram 接入 vllm_omni/worker/gpu_ar_model_runner.py:460-514EagleProposer 导入于 :25
spec_decode 统计上报 omni_ar_scheduler.py:257-279omni_generation_scheduler.py:329-345
Omni MTP(codec 多码本一次出) vllm_omni/model_executor/models/qwen3_omni/qwen3_omni_moe_code_predictor_mtp.py:1-7
talker_mtp 调用(包进 CUDA Graph) vllm_omni/worker/gpu_model_runner.py:1019-1026, 1050-1077_talker_mtp_forward

④ 性能收益 + 触发条件

  • 收益:EAGLE 在文本 LLM 上典型 2-3× 加速;MTP 让 codec 多码本一次出,减少 talker 步数。
  • 触发条件 :需要 draft 模型或 MTP 权重;命中率依赖采样温度(TTS 常用高温度采样,命中率可能下降,需权衡)。
  • 冲突限制:低温度/贪心解码命中率最高;高温度采样命中率下降。

技术 7:CUDA Graph 加速

① 解决了什么痛点

GPU 每次执行一个 kernel,CPU 都要发一次 launch 指令(launch overhead)。模型 forward 是成百上千个 kernel 串起来的,CPU 发指令的开销在小 batch、短计算时甚至比 GPU 算还慢------这叫 kernel launch bound。

② 底层原理 + TTS 适配

CUDA Graph:把一串 kernel 调用「录制」成一个图,之后一次 launch 整个图,消除逐 kernel 的 launch overhead。vLLM 把 forward + sampling 录制成图复用。

TTS 场景的关键事实

  • ⚠️ 所有 TTS/Omni stage config 默认 enforce_eager: trueqwen3_tts.yaml:12 等),即默认关闭 CUDA Graph
  • 原因(docs/configuration/stage_configs.md:189-193):框架目前主要在 eager 模式下验证稳定
  • 仅 talker 的 MTP 部分被包进 CUDA Graph(gpu_model_runner.py:50-53)。

③ 源码落地

内容 位置
CUDAGraphMode 判定 vllm_omni/worker/gpu_ar_model_runner.py:14, 217, 267
CUDAGraphWrapper 包装 talker_mtp vllm_omni/worker/gpu_model_runner.py:5, 50-53
强制 eager(所有 stage) stage config enforce_eager: true
校验入口 vllm_omni/config/model.py:304_verify_cuda_graph()

④ 性能收益 + 触发条件

  • 收益:小 batch 短序列场景,launch overhead 占比高,CUDA Graph 能带来 10-30% 提速。
  • 触发条件enforce_eager: false(但 ⚠️ TTS 当前默认 true,强行关闭需充分测试)。
  • TTS 现状:目前享受不到,等框架支持非 eager 后再说。

技术 8:算子融合(Operator Fusion)

① 解决了什么痛点

模型里很多操作是连续的小算子(比如 RMSNorm 后接 attention,attention 里 Q/K/V 三个矩阵乘法)。每个算子都要读写一次显存。把多个小算子合并成一个大算子,减少显存读写往返,提速

② 底层原理 + TTS 适配

典型融合:

  • QKV 融合 :把 Q、K、V 三个独立矩阵乘法合并成一个(QKVParallelLinear)。
  • RMSNorm + 量化融合
  • RoPE(旋转位置编码)融合进 attention
  • MRoPE:多模态位置编码(音频/视频/图像联合)。

TTS 场景的关键事实

  • 所有融合算子直接复用 vLLM vllm.model_executor.layers.*Omni 没有自写任何 CUDA kernel
  • 这些融合主要作用于自回归部分 (thinker/talker 的 transformer 层);TTS 的音频专属算子(RVQ、STFT、BigVGAN)目前没有融合优化(都是纯 PyTorch 逐算子执行)。

③ 源码落地

内容 位置
融合 RMSNorm / RoPE / QKV qwen3_omni/qwen3_omni_moe_code_predictor_mtp.py:26-33RMSNormQKVParallelLinearMergedColumnParallelLinearget_rope
q_norm/k_norm + Rotary 融合 qwen3_omni_moe_code_predictor_mtp.py:89-100
MRotaryEmbedding(多模态位置编码) vllm_omni/worker/gpu_model_runner.py:10_init_mrope_positions gpu_model_runner.py:63-119

④ 性能收益 + 触发条件

  • 收益:QKV 融合约省 1/3 的显存读写;RMSNorm 融合省一次显存 round-trip。
  • 触发条件:使用 vLLM 的标准 transformer 层即自动生效。
  • TTS 注意:音频专属算子(BigVGAN 等)目前无融合,是潜在优化方向(需自己写 kernel)。

4.2 TTS/音频专属加速技术

这些是 vLLM-Omni 独有在 TTS 上特别重要的加速技术。


技术 9:音频特征 / 说话人嵌入缓存

① 解决了什么痛点

声音克隆(Base 模型)场景:你给一段参考音频,模型要先提取说话人向量(x-vector)和参考 codec codes。如果同一个说话人要合成很多句话,每次都重新提取一遍参考音频特征,纯属浪费------提取一次要跑一遍 Whisper 编码 + VQ + ECAPA/campplus,很贵。

② 底层原理 + TTS 适配

把参考音频的说话人向量、参考 codec codes 预提取出来缓存,后续同说话人的请求直接复用。VoiceClonePromptItem 容器封装这些预提取结果,可跨请求复用。

③ 源码落地

内容 位置
跨 stage prompt_embeds 缓存(CPU 暂存→prefill 覆盖) vllm_omni/worker/gpu_model_runner.py:216prompt_embeds_cpu)、:856-877_collect_additional_information_for_prefill
additional_information 跨步缓存 gpu_model_runner.py:225-252, 783-855
多模态编码器缓存(vLLM 复用) gpu_model_runner.py:145self.encoder_cache.pop(mm_hash));大小由 mm_processor_cache_gb 控制(config/model.py:273
说话人向量/参考码预提取复用 model_executor/models/qwen3_tts/qwen3_tts.py:50-62VoiceClonePromptItem),预提取 create_voice_clone_prompt:589-706,跨请求复用 :807-815
codec 内部 KV 复用(HF DynamicCache) modeling_qwen3_tts.py:1064-1065

④ 性能收益 + 触发条件

  • 收益:声音克隆场景,同说话人多句话合成时,省掉重复的参考音频编码(可能省 30-50% 单请求延迟,取决于参考音频长度)。
  • 触发条件 :同说话人多次请求;业务层需要做 prompt_items 的缓存管理(按说话人 ID 缓存 VoiceClonePromptItem)。

技术 10:TTS 分块流式推理(Chunked Streaming)

① 解决了什么痛点

合成一段长音频(比如 30 秒),如果等整段算完才返回,用户要等好几秒才听到第一个音------首包延迟极高,体验差。而且一次性算长音频容易 OOM(中间激活值巨大)。

② 底层原理 + TTS 适配

分块流式:把长音频切成多个 chunk,算完一个 chunk 就推送一个,客户端边收边播。同时用「重叠窗口」(left_context)处理 chunk 边界,避免拼接处出现裂纹/pop 音。

两个层次

  1. 单 stage 内的分块解码 :code2wav 把 codec codes 切块,逐块出波形(chunked_decode_streaming)。
  2. 跨 stage 的异步流式async_chunk):上游 stage 算出一部分,立刻通过 connector 推给下游,下游不用等上游全算完。这是降低端到端首包延迟的核心机制

③ 源码落地

内容 位置
codec 分块解码(去边界伪影) qwen3_omni/qwen3_omni_code2wav.py:160-197chunked_decodechunk_size=300, left_context_size=25)、:199-230chunked_decode_streaming
跨 stage 流式分块配置 config/model.py:63async_chunk)、engine/arg_utils.py:75,177
connector chunk 收发 distributed/omni_connectors/adapter.py:204-272get_chunk)、:275-306get_chunk_for_generation)、:308-352put_chunk
启用 YAML model_executor/stage_configs/qwen3_omni_moe_async_chunk.yamlasync_chunk: true
多模态输出累积/合并 engine/output_processor.pyOmniRequestState.add_multimodal_tensor
Qwen2.5 mel 分块拼接 qwen2_5_omni_token2wav.py:1564process_little_chunk)、:1632_process_chunk_for_50hz

④ 性能收益 + 触发条件

  • 收益:首包延迟从「等整段算完」降到「等第一个 chunk 算完」。例如 30 秒音频,可能从 ~10 秒首包降到 ~1-2 秒。
  • 触发条件
    • Omni 模型:stage config 设 async_chunk: true(用 qwen3_omni_moe_async_chunk.yaml)。
    • ⚠️ 纯 Qwen3-TTS 在线服务目前对外不开放流式stream_format="sse" 被拒绝,见 1.3 节)。分块机制在内部已实现,但 /v1/audio/speech 接口还没接上流式输出(RFC #938 待完成)。
  • 现状提醒:内部机制就绪 ≠ 对外可用。等流式接口开放后,这项技术对实时 TTS 是杀手锏。

技术 11:多模态编码器并行 / Stage 流水线重叠

① 解决了什么痛点

Omni 模型有多个 stage(thinker→talker→code2wav)。如果严格串行(thinker 全算完 → talker 开始 → code2wav 开始),下游 stage 在上游算的时候一直空闲,GPU 利用率低

② 底层原理 + TTS 适配

Pipeline 并行:stage 之间重叠执行 。上游算出一部分就推给下游,下游立刻开始算自己的部分,上下游形成流水线。配合 async_chunk,stage 间通过 connector 流式传递 chunk。

标准模式(async_chunk: falsewindow_size: -1):stage 严格串行,上游完整完成才触发下游。

异步模式(async_chunk: true):stage 间流式重叠。

③ 源码落地

内容 位置
async_chunk 开启 stage config async_chunk: trueqwen3_omni_moe_async_chunk.yaml:7
put_chunk / get_chunk adapter.py:308put_chunk)、:204get_chunk
chunk 处理函数 stage_input_processors/qwen3_omni.py:86thinker2talker_async_chunk)、:208talker2code2wav_async_chunk
异步 orchestrator 协调 async_omni.py:362_process_async_results
runtime edges 配置 stage config runtime.edgeswindow_sizemax_inflight

④ 性能收益 + 触发条件

  • 收益:多 stage 模型整体吞吐提升(stage 重叠执行,减少空闲);首包延迟下降(下游提前开始)。
  • 触发条件 :多 stage 模型(Omni);设 async_chunk: true;注意 chunk 边界处理正确性。
  • Qwen3-TTS(单 stage)不涉及此项------它是单 stage,没有 stage 间重叠问题。

技术 12:Mel 谱 / 声学 Token 高效编解码

① 解决了什么痛点

音频是连续波形(每秒 24000 个采样点),直接喂给模型数据量太大。需要高效的「音频 ↔ 离散表示」转换:编码时把波形压成紧凑的声学 token(codec codes),解码时还原。

② 底层原理 + TTS 适配

  • RVQ(Residual Vector Quantization):把连续特征量化成多层(如 8 层/16 层)残差码本,第一层粗粒度,后续层补细节。大幅压缩音频表示。
  • Mel 谱:STFT + mel 滤波器组,把波形转成对数 mel 频谱(声码器输入)。
  • Whisper 编码器 + VQ:先用 Whisper 编码音频,再 VQ 量化。

③ 源码落地

内容 位置
RVQ 核心(纯 PyTorch) qwen3_tts/tokenizer_25hz/vq/core_vq.pyEuclideanCodebook:114VectorQuantization:245DistributedResidualVectorQuantization:334
Whisper + VQ 编码器 speech_vq.py:189WhisperEncoderVQ
Mel 谱特征 speech_vq.py:41MelSpectrogramFeatures)、whisper_encoder.py:119get_mel_audio
speech tokenizer 封装 qwen3_tts_tokenizer.py:46Qwen3TTSTokenizerencode:210/decode:261

④ 性能收益 + 触发条件

  • 收益:这是「能跑」的基础而非「加速」------把音频压到离散 token 才能用 LLM 范式生成。Qwen3-TTS 用 12Hz/25Hz tokenizer(每秒 12/25 个 token),远小于原始 24000 采样点。
  • 触发条件:模型自带,无需配置。
  • 优化空间:当前这些算子是纯 PyTorch,没有 kernel 优化------是潜在加速方向。

技术 13:音频批处理维度对齐 / Mel 长度对齐

① 解决了什么痛点

多个 TTS 请求的音频长度不同,拼成一个 batch 时,tensor 维度对不齐(batch 要求是规整矩阵)。直接 padding 会浪费计算;处理不好会维度报错。

② 底层原理 + TTS 适配

  • mel 长度对齐:确保 mel 帧数是 codec 扩展因子(repeats)的倍数(DiT 采样器需要)。
  • 音频 tensor 合并时,形状不一致时跳过 torch.cat,避免报错。

③ 源码落地

内容 位置
mel 长度对齐 qwen2_5_omni/audio_length.py:25cap_and_align_mel_length,确保 mel 长度是 repeats 倍数)
DiT 采样器使用 qwen2_5_omni_token2wav.py:1275
音频 tensor 合并(形状不一致跳过 cat) engine/output_processor.py:111-119_consolidate_multimodal_tensors,特殊处理 "audio" 键)

④ 性能收益 + 触发条件

  • 收益:避免维度报错;减少无效 padding 计算。
  • 触发条件:多请求并发批处理时自动生效。

4.3 加速技术之间的关系

依赖关系(哪些技术是其他技术的基础):

复制代码
PagedAttention (分页 KV cache)
    │ 是基础
    ▼
连续批处理 (多请求并发)  ←─ 需要 PagedAttention 才能高效并发
    │ 配合
    ▼
动态调度 (按显存/长度实时决策)

Prefix caching (前缀复用) ── 与连续批处理叠加,进一步提升吞吐
                          └─ 但 TTS latent 路径不支持,用「跨 stage KV 转移」替代

权重量化 ── 独立,可与所有技术叠加(省显存 → 更多并发)
投机采样/MTP ── 独立,减少自回归步数
CUDA Graph ── 独立,减少 launch overhead(⚠️ TTS 默认关)
算子融合 ── 独立,减少显存读写(仅自回归部分)

音频缓存 (VoiceClonePromptItem) ── 独立,声音克隆场景省参考编码
分块流式 (chunked_decode + async_chunk) ── 独立,降首包延迟

多技术叠加的综合收益

  • PagedAttention + 连续批处理 + 动态调度 → 并发吞吐提升数十倍(官方 35×)。
  • 量化 + PagedAttention → 显存省更多 → 并发更高(叠加放大)。
  • async_chunk + 分块解码 → 端到端首包延迟大幅下降。

冲突/限制场景

冲突 说明
Prefix caching × latent 输出 输出 latent 的 stage 不支持 prefix caching(TTS 默认关)
投机采样 × 高温度采样 TTS 常用 temperature=0.9 等高温度,spec decoding 命中率下降
量化 × 音频质量 量化对语音 MOS 影响比文本大,需评估
CUDA Graph × 当前 TTS 框架默认 eager,强行关 eager 需测试稳定性
并发 × 音质/稳定性 max_batch_size 调大需充分测试,可能影响音质或触发 OOM

五、开源 TTS 模型适配完整工程指南

本章教你「把一个新 TTS 模型接进 vLLM-Omni」。以官方 docs/contributing/model/adding_omni_model.md 为蓝本,结合源码实战。

5.1 接入前必须理解的概念

① 「模型」在 vLLM-Omni 里的抽象

一个模型 = 一个统一模型类 (按 model_stage 分支初始化不同 stage)+ 各 stage 组件实现 + stage config YAML + stage input processors(stage 间转换)。

② 模型在框架里的三个组成部分如何注册

部分 注册位置 作用
LLM/统一类 model_executor/models/registry.py_OMNI_MODELS 架构名 → 实现类
Audio Encoder / Vocoder 统一类内部组合(如 talker 持有 audio_tower,code2wav 持有 vocoder) 作为 stage 的子模块
Stage 拓扑 stage config YAML(stage_configs/ 定义有几个 stage、怎么连

③ 模型配置文件的关键扩展字段

字段 含义
model_stage thinker/talker/code2wav/自定义
model_arch registry 里注册的架构类名
engine_input_source 上游 stage id 列表(流水线连接)
custom_process_input_func stage 间数据转换函数路径
final_output / final_output_type 是否最终输出、输出类型(text/audio)
hf_config_name stage 专属 hf config(如 talker_config)

④ 两个核心数据结构

  • OmniOutputmodel_executor/models/output_templates.py:7):模型 forward() 返回的类型,含 text_hidden_statesmultimodal_outputs: dictnext_token_id。音频以 multimodal_outputs["model_outputs"](波形张量)形式呈现。
  • OmniTokensPromptinputs/data.py):stage 间传递的输入,含 prompt_token_idsadditional_information(携带 embeddings/hidden states)、multi_modal_data

5.2 标准接入步骤

以接入一个新的 TTS/Omni 模型为例(参照 Qwen3-Omni 的实现)。

目录结构adding_omni_model.md:28-47):

复制代码
vllm_omni/model_executor/models/your_tts_model/      # 模型目录
├── __init__.py                  # 导出主类
├── your_tts_model.py            # 统一模型类(按 model_stage 分支)
├── your_tts_thinker.py          # 各 stage 实现(如果是单 stage TTS,可省)
├── your_tts_talker.py
└── your_tts_code2wav.py

vllm_omni/model_executor/stage_input_processors/
└── your_tts_model.py            # stage 间数据转换(多 stage 才需要)

vllm_omni/model_executor/stage_configs/
└── your_tts_model.yaml          # stage 配置

标准步骤表

步骤 任务 新增/修改文件 关键动作
1 建模型目录 model_executor/models/your_tts_model/ 仿 qwen3_tts 目录结构
2 实现统一模型类 your_tts_model.py model_stage 分支初始化;实现 forward()load_weights()compute_logits()
3 实现各 stage 组件 *_talker.py*_code2wav.py 继承 vLLM 基类(如 Qwen3MoeForCausalLM),实现 SupportsMultiModal/SupportsPP 接口
4 创建 __init__.py your_tts_model/__init__.py from .your_tts_model import YourModelForConditionalGeneration
5 注册模型 model_executor/models/registry.py_OMNI_MODELS "YourArch": ("your_tts_model", "your_tts_model", "YourModelForGeneration")
6 写 stage config stage_configs/your_tts_model.yaml 定义 stage 数、调度器、worker、显存
7 写 stage input processors(多 stage) stage_input_processors/your_tts_model.py 实现 thinker2talkertalker2code2wav 等转换函数
8 测试验证 tests/e2e/ 单请求验证 → 并发压测 → 与原生推理音频质量对齐

步骤 2 关键代码模板 (统一模型类,参照 qwen3_omni.py):

python 复制代码
class YourModelForConditionalGeneration(nn.Module, SupportsMultiModal, SupportsPP):
    def __init__(self, *, vllm_config, prefix=""):
        super().__init__()
        self.have_multimodal_outputs = True
        config = vllm_config.model_config.hf_config
        self.model_stage = vllm_config.model_config.model_stage  # 关键:按 stage 分支

        if self.model_stage == "code2wav":   # TTS 单 stage 就这一支
            self.code2wav = init_vllm_registered_model(
                vllm_config=vllm_config.with_hf_config(config.code2wav_config, ...),
                prefix=maybe_prefix(prefix, "code2wav"),
                ...
            )
            self.model = self.code2wav

    def forward(self, ...):
        # ... 处理输入、调用子模型、生成音频 ...
        return OmniOutput(
            text_hidden_states=None,
            multimodal_outputs={"model_outputs": audio_tensors, "sr": sample_rate},
        )

    def load_weights(self, weights):
        # 按前缀(code2wav./talker.)分离权重加载
        ...

步骤 6 stage config 模板 (单 stage TTS,仿 qwen3_tts.yaml):

yaml 复制代码
stage_args:
  - stage_id: 0
    stage_type: llm
    runtime:
      devices: "0"
      max_batch_size: 1
    engine_args:
      model_stage: your_tts
      model_arch: YourModelForConditionalGeneration   # 必须匹配 registry
      worker_type: generation                          # 一步生成用 generation,自回归用 ar
      scheduler_cls: vllm_omni.core.sched.omni_generation_scheduler.OmniGenerationScheduler
      enforce_eager: true
      trust_remote_code: true
      engine_output_type: audio
      gpu_memory_utilization: 0.5
      max_num_batched_tokens: 1000000
    final_output: true
    final_output_type: audio

步骤 7 stage input processor 模板 (多 stage 才需要,仿 qwen3_omni.py:492):

python 复制代码
def talker2code2wav(stage_list, engine_input_source, prompt=None, requires_multimodal_data=False):
    """把 talker 的输出转成 code2wav 的输入。"""
    source_stage_id = engine_input_source[0]
    talker_outputs = stage_list[source_stage_id].engine_outputs
    code2wav_inputs = []
    for talker_output in talker_outputs:
        output = talker_output.outputs[0]
        # 从 talker 的 multimodal_output 提取 codec codes
        codec_codes = output.multimodal_output["code_predictor_codes"].to(torch.long).reshape(-1).tolist()
        code2wav_inputs.append(
            OmniTokensPrompt(prompt_token_ids=codec_codes, multi_modal_data=None)
        )
    return code2wav_inputs

5.3 适配后的性能优化方向

方向 怎么做 注意
自定义算子兼容 TTS 特有算子(如你的 vocoder)目前是 PyTorch,如需更快可写 CUDA kernel 仓库当前无音频 CUDA kernel 先例,需自研
多模态批处理兼容 max_batch_size,处理不同时长音频的 padding/对齐 cap_and_align_mel_length 思路
KV cache 调优 AR stage 调 max_num_seqsgpu_memory_utilization;考虑跨 stage KV 转移 latent 输出 stage 不能开 prefix caching
流式前后处理对齐 实现 chunked_decode_streaming,用 left_context 重叠去边界伪影 保证 chunk 边界无缝(听感检查)
量化适配 评估 INT4/INT8/FP8 对音频 MOS 的影响 音频比文本敏感,建议 FP8 起步
并发稳定性 逐步调大 max_batch_size,压测观察 OOM、音质退化 当前官方默认 1,调大需充分测

5.4 高频故障排查清单

故障现象 根因 排查思路 解决方案
显存溢出 (OOM) gpu_memory_utilization 过高;多 stage 共享 GPU 配额超 1.0;max_num_batched_tokens 过大 nvidia-smi;检查各 stage gpu_memory_utilization 之和 gpu_memory_utilization;多 stage 分配到不同 GPU;减 max_num_batched_tokens(参考 docs/configuration/gpu_memory_utilization.md
多模态特征维度不匹配 stage 间 tensor shape 没对齐;mel 长度不是 repeats 倍数 在 stage input processor 打印 shape;检查 cap_and_align_mel_length cap_and_align_mel_length 对齐;在转换函数里 reshape
流式响应断流/音频不完整 stream_format="sse" 被拒(当前不支持);chunk 边界处理错误 看是否误用 sse;检查 chunked_decode_streamingleft_context 当前用 stream_format="audio"(整段);等 RFC #938;检查 chunk 重叠逻辑
推理速度比原生还慢 enforce_eager=true 关了优化;并发=1 没发挥批处理;stage 间 SHM 传输开销大 --log-statse2e_stage_N_wall_time_mstransfers_total_time_ms 调大 max_batch_size;多 stage 开 async_chunk;定位最慢 stage
模型权重加载失败 load_weights 实现错误;HF AutoModel 加载失败;trust_remote_code 没开 看加载日志;确认权重前缀与 load_weights 的分离逻辑一致 --trust-remote-code;检查权重 key 前缀(code2wav./talker.)
输出音频有杂音/裂纹 chunk 边界伪影;量化精度损失;mel 归一化参数不对 对比原生推理的波形/MOS;检查 left_context_size 增大 left_context_size;降量化精度;核对 mel 参数
并发上去后延迟指数增长 stage 间队列堆积;SHM 阈值不当;某个 stage 成瓶颈 TransferEdgeStatsstats.py:58-71)的 in_flight_time_ms 增大瓶颈 stage 的 max_batch_size;调 shm_threshold_bytes;给瓶颈 stage 单独 GPU
量化后音频质量严重下降 INT4 对音频太激进;量化层选错 对比量化前后的 MOS/频谱图 用 FP8 替代 INT4;只量化非关键层;提高量化group size
--omni 没生效 命令漏了 --omni,回退到上游纯文本 vLLM 看启动日志是否走了 Omni 路由 确认命令含 --omnicli/main.py:12 的判断)
stage 初始化超时 模型大/网络慢下权重;stage_init_timeout 太小 看是哪个 stage 卡住 调大 --stage-init-timeout(默认 300)和 --init-timeout(默认 600)

六、附录

6.1 关键术语速查表

术语 简短定义
Stage vLLM-Omni 的推理阶段单元,每个是独立进程/引擎。如 thinker/talker/code2wav
OmniConnector stage 间数据传输抽象(默认 SharedMemoryConnector)
OmniStage 单 stage 的封装类(omni_stage.py:226
Omni / AsyncOmni 多 stage 编排器(同步离线 / 异步在线)
AR stage 自回归 stage(逐 token 生成),用 OmniARScheduler
generation stage 一步生成 stage(如 code2wav),用 OmniGenerationScheduler
PagedAttention KV cache 分页管理(复用 vLLM)
Continuous Batching 连续批处理,请求即来即算即走(复用 vLLM)
Prefix caching 公共前缀 KV 复用(TTS 默认关)
async_chunk 跨 stage 异步流式分块(降首包延迟)
engine_output_type stage 输出类型:text/audio/latent
RVQ 残差向量量化,把音频压成多层 codec codes
BigVGAN mel → 波形的声码器
DiT Diffusion Transformer(流匹配生成 mel)
x-vector 说话人向量(ECAPA-TDNN 或 campplus 提取)
MTP Multi-Token Prediction(Omni 投机采样,codec 多码本一次出)
TTFT / AUDIO_TTFP 首 token / 首音频包延迟
RTF Real Time Factor,生成 1 秒音频所需秒数(<1 即实时)
D2H2D 当前 connector 的 device→host→device 传输模式
monkey-patch 运行时替换 vLLM 类的机制(patch.py

6.2 推荐学习路径

从本文档出发的后续深入顺序:

  1. 先跑通:照第 1 节把 Qwen3-TTS 服务跑起来,发请求听到声音。
  2. 读 stage config :精读 docs/configuration/stage_configs.mdqwen3_tts.yaml,理解每个字段。
  3. 读架构docs/design/architecture_overview.md + docs/design/module/ar_module.md(继承关系图)。
  4. 读 TTS 实现vllm_omni/model_executor/models/qwen3_tts/qwen3_tts.py,跟一遍 forward。
  5. 读调度器vllm_omni/core/sched/omni_generation_scheduler.py(TTS fast-path)。
  6. 读适配指南docs/contributing/model/adding_omni_model.md,尝试接一个自己的小 TTS 模型。
  7. 跑 benchmarkbenchmarks/qwen3-omni/,对比 vLLM-Omni vs HF Transformers。
  8. 读论文:arXiv:2602.02204,理解完全解耦架构的设计动机和性能结果。
  9. 关注社区#sig-omni Slack 频道(slack.vllm.ai)、RFC #938(TTS 流式进展)。

6.3 参考资料

官方文档(仓库内 docs/):

  • docs/getting_started/quickstart.md ------ 快速开始
  • docs/getting_started/installation/gpu.md ------ GPU 安装
  • docs/design/architecture_overview.md ------ 架构总览
  • docs/design/module/ar_module.md ------ AR 模块设计(继承关系图)
  • docs/design/feature/disaggregated_inference.md ------ 解耦推理与 Connector
  • docs/configuration/stage_configs.md ------ Stage 配置全字段说明
  • docs/configuration/gpu_memory_utilization.md ------ 显存计算
  • docs/models/supported_models.md ------ 支持的模型列表
  • docs/contributing/model/adding_omni_model.md ------ 添加新模型指南
  • docs/cli/bench/serve.md ------ bench serve(含音频指标)

关键源码索引:

关注点 文件
包导出 + patch vllm_omni/__init__.pyvllm_omni/patch.py
Omni 编排器 entrypoints/omni.pyasync_omni.pyomni_stage.py
TTS 服务接口 entrypoints/openai/serving_speech.pyapi_server.pyaudio_utils_mixin.py
调度器 core/sched/omni_ar_scheduler.pyomni_generation_scheduler.py
Qwen3-TTS 实现 model_executor/models/qwen3_tts/qwen3_tts.pymodeling_qwen3_tts.py
Stage 配置 model_executor/stage_configs/qwen3_tts.yaml
模型注册 model_executor/models/registry.py
Stage 间转换 model_executor/stage_input_processors/qwen3_omni.py
Connector distributed/omni_connectors/
性能统计 vllm_omni/metrics/stats.pybenchmarks/metrics/metrics.py

外部资料:


最后一句:vLLM-Omni 还很年轻(2026/02 才稳定版),TTS 路径的某些能力(在线流式、高并发默认配置、音频 CUDA 算子)还在演进中。但它「继承 vLLM、复用其全部加速基础设施、在其上加多 stage 流水线」的设计思路是扎实的。对 TTS 工程师来说,现在上手能享受到 vLLM 成熟的 KV cache/连续批处理红利,同时为未来的流式/分布式能力做好架构准备。

相关推荐
Yunzenn2 天前
强化学习1-Liu2026_GFlowRL_精读笔记
人工智能·笔记·深度学习·机器学习·transformer·集成学习·vllm
寒冰碧海2 天前
大模型部署从0到1(三):单机多卡 vLLM 部署Qwen3.6实战|Docker Compose 一键启动
docker·容器·vllm
执笔论英雄3 天前
【大模型推理 】 vllm kvconnet 学习
学习·vllm
古城小栈4 天前
vLLM 从入门到精通:企业级线上部署设置全讲解
vllm
Briwisdom4 天前
Speculative Decoding:用小模型给大模型“打草稿“,推理加速 2-3×
模型部署·vllm·eagle·llama.cpp·prefill·speculative·decoding
Briwisdom5 天前
LLM 推理引擎架构:vLLM / SGLang 的核心设计
架构·vllm·sglang·pagedattention·radixattention
陈 洪 伟6 天前
大模型推理引擎 vLLM(28):custom_all_reduce 单机/跨节点原理(IPC、Fabric、HSA)
fabric·vllm
一颗小树x8 天前
NVIDIA Jetson Thor 运行 LLM / VLM:模型全整理与 vLLM 实践
人工智能·llm·jetson·vllm
一个王同学9 天前
从零到一 | CV转多模态大模型 | week17 | LLM 推理优化 & vLLM 详解
人工智能·深度学习·算法·机器学习·计算机视觉·vllm