Skip to content

perf(sidecar): Qwen3-TTS ONNX runs at only ~1.2x realtime on GPU (launch-bound AR loop) #293

Description

@jiangzhuo

Problem

Qwen3-TTS 0.6B through the ONNX talker runtime (sidecar/sokuji_sidecar/qwen3_tts/) generates at only ~1.2x realtime on a capable GPU — barely faster than playback. Since the card is non-streaming (STREAMING=False), a 5s sentence stalls the pipeline for ~4.2s before audio starts, which is very noticeable in a live translation session.

Measured on a DGX Spark (GB10, CUDA via NVIDIA's sbsa onnxruntime-gpu 1.24.0), qwen3-tts-0.6b fp32, ICL preset voice:

Device Throughput
CUDA (GB10) 1.15x realtime (app-reported rtf: 0.839)
CPU (20-core Grace) 0.38x realtime (unusable)

For contrast, MOSS-TTS-Nano (100M, streaming) does 3-4x realtime on the same box.

Model assets: jiangzhuo9357/qwen3-tts-0.6b-onnx / jiangzhuo9357/qwen3-tts-1.7b-onnx (fp32 ONNX, repacked from zukky/Qwen3-TTS-ONNX-DLL, exported from QwenLM/Qwen3-TTS).

Why it is slow (analysis, verified on-device)

The bottleneck is per-step host/device round-trip overhead, not GPU compute — the GPU is mostly idle:

  1. 16 sequential session.run calls per 80ms audio frame. The talker is a 12.5Hz AR model: each frame runs 1x talker_decode + 15x code_predictor (16 codebooks, each sub-step depends on the previous one's embedding). Every call is a full Python -> ORT -> GPU round trip with a tiny batch — pure kernel-launch latency.
  2. CPU<->GPU copies every step. ORT inserts 12-36 Memcpy nodes into the graphs on CUDA (logged at session build), and the AR loop's sampling / embedding concat runs Python/numpy-side, so tensors bounce between host and device each step.
  3. fp32-only export — double the memory bandwidth of an fp16 path, no KV quantization.

Possible directions (unverified ideas only — none validated, other paths not excluded)

  • IO binding / keep tensors resident on device between steps to eliminate the per-step Memcpy traffic. Moderate effort; plausibly the best effort/reward ratio.
  • fp16 re-export of the talker/code-predictor graphs (halves bandwidth; needs new model assets).
  • TensorRT EP (available in the sbsa onnxruntime-gpu build): graph fusion could help the most, but engine build time and op coverage are unknowns.
  • Fuse the 15 code_predictor sub-steps into a single graph (loop inside the ONNX graph); biggest change, requires re-export.
  • Streaming support for the qwen3 lane would not raise throughput but would hide latency (start playback at the first decoded chunk), similar to how MOSS feels fast.

These are starting points for investigation, not conclusions — the right fix may well be something else entirely.

Context

  • Runtime: sidecar/sokuji_sidecar/qwen3_tts/ (pure-onnxruntime port; reference was zukky/Qwen3-TTS-ONNX-DLL's run_pipeline.py)
  • The macOS lane uses MLX instead and is unaffected.
  • Related recent fix: the tokenizer added_tokens bug (babble output) — fixed in 132ebd5; this issue is purely about throughput/latency.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions