Repository navigation
qwen3-tts: one kern manifest with a streamed codec, one graph per decode step - #4
Merged
Merged
Conversation
…ifest Every running stream's newest frame is decoded in one batched call per step, its conv, transposed-conv and attention history kept in a per-stream state slot, instead of re-decoding a 72-frame window per chunk. The call list is a generated kern manifest over our own kernels (kernels/codec.cu) and cuBLASLt, run by kern-runtime as one CUDA graph per batch bucket. New dependencies: - kern-manifest, kern-pool, kern-runtime (git): manifest schema, per-stream state leases and graph capture/replay, so the codec is described as data rather than hand-driven launches. - sha2: names the embedded cubin on disk by content. - cc (build): locates nvcc to compile kernels/codec.cu to a cubin. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
… deep RoPE, the KV ring write and attention are one launch per layer; a block per (row, head) scores a frame per thread and sums V in sixteen groups instead of a warp walking the 72 frames serially. Codec attention per step: 222 -> 33 us at batch 1, 231 -> 62 us at batch 64; codec span 772 -> 580 us and 2489 -> 2312 us. Golden SNR 36.89 dB. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
Every elementwise kernel moves eight channels per thread. A residual unit is im2col (snake of the input plus the carried bias, scattered once per source row into the dilated taps, history written in the same launch), GEMM, bias+snake, and a GEMM that accumulates into the residual stream (cublaslt_bf16_tn_acc), so bias_residual, the separate snake and ring_write are gone. Conv histories are double-buffered by frame parity so a launch never overwrites rows its other threads still read. Biases that only feed linear ops are folded at load (pre_conv into input_proj, output_proj and ConvNeXt pwconv2 into the next transposed conv's per-tap bias); the rest are carried to the residual stream's consumers. The upsamplers' unfold is gone (the GEMM rows already are the output rows), dwconv+LayerNorm is vectorized with its bias add and history fused, conv_out snakes each row once into shared memory, norms are a warp per row. Codec per step (graph span, codec bench): batch 1 580 -> 487 us, batch 64 2312 -> 1168 us; 199 -> 150 kernels. Golden SNR 36.59 dB. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
The accurate sinf made every SnakeBeta kernel instruction-bound; a two-step reduction to [-pi, pi] and __sinf keeps the absolute error near 4e-7. Batch 64 codec span 1168 -> 1077 us (bias_snake 138 -> 91, im2col_snake 227 -> 196 us per step); batch 1 487 -> 478 us. Golden SNR 36.57 dB. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
Every handwritten kernel waits on griddepcontrol before touching memory and then triggers its successor, so launch latency overlaps the previous kernel's tail (cuBLAS launches stay plain). Codec span batch 1 478 -> 422 us, batch 64 1077 -> 1045 us. Golden SNR 36.57 dB. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
The earlier frames' K and V are loaded before the block syncs on the rotated q, the current frame's K and V are taken from shared memory, so attention is one memory round trip deep. Attention on the critical path per step: batch 1 33 -> 25 us, batch 64 74 -> 63 us; codec span 422 -> 415 us and 1045 -> 1037 us. Golden SNR 36.57 dB. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
conv_out is a block of 256 per 64 rows: all loads issued up front, each row snaked once into shared memory, then a thread slides four outputs over a sixteenth of the channels with the weights in shared memory. dwconv_ln's seven taps are a compile-time constant, its weights loaded before the PDL wait and every tap row in one round. Per step: conv_out batch 1 14.1 -> 4.8 us, batch 64 34.2 -> 23.6 us; dwconv_ln 15 -> 6.7 and 17 -> 8.8 us. Codec span 415 -> 398 us and 1037 -> 1019 us. Golden SNR 36.57 dB. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
…ern manifest One kern runtime executes the whole synthesis path. `model.rs` generates the manifest: `init` (the pad embedding, once), `prefill` (ragged prompts, eager), `first` (hidden state to frame to PCM) and `decode` (last frame to talker to frame to PCM), the last two CUDA graphs per batch bucket, so a decode step is one graph launch and one read of codes and PCM. - kernels/talker.cu: our own paged and dense attention, RoPE with QK-norm, fused norms, embeddings and a sampler that keeps the Hugging Face order (repetition penalty, suppression, temperature, top-k with ties, draw) with per-request uniforms; `force` (-1 = draw) lets tests take the real path with recorded frames. - The talker's KV is kern paged state (one state per layer); a sequence's slot of the per-sequence state holds its last frame, repetition bitmap and codec decoder state, so nothing round-trips through the host. - manifest.rs: the generator both halves share (was inside codec.rs); stack.rs emits a Qwen3 decoder stack for the talker and the predictor. - The engine leases each request's KV and slot up front and calls `start` for new requests and `step` for running ones. - omni-cuda and the FlashInfer submodule are gone; CI installs nvcc and the CUDA headers only. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
…nd a/b Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
…in are measured Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
`all` sets up the venv, runs vLLM-Omni and then pega-omni alone on the GPU through the CI points, and renders assets/qwen3-tts-vs-vllm-omni.png with chart.py, whose headline ratios come from the results. vLLM-Omni now runs its opt-in single-GPU throughput profile under a private MPS daemon, the strongest setup we measured for it (c=64: 210-230 vs 181-190 audio-s/s on the default deploy). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
…rting it and stops mps on exit Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
…ejected, cubins cache per user - `kv_pages` holds `pages_max + max_seqs` entries: every padding row of a graph call names the pad lease's page again, so a nearly full KV plus a padded bucket overflowed the input and stopped the engine. - `Model::open` returns `ExceedsPool` instead of `Busy` when the lease and the pad alone exceed the page budget, so such a request is rejected instead of sitting at the queue head forever; the log names its prompt tokens and frame cap. - The cubins go to `$XDG_CACHE_HOME` (else `~/.cache`, else the temp dir) under `pega-omni/kernels`: a shared `/tmp/pega-omni-kernels` belongs to whoever ran first and fails with EACCES for everyone else. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
…ut the cc crate - `kernels/common.cuh` holds what `talker.cu` and `codec.cu` both defined: the bf16 conversions, 8-wide loads and stores, warp and block reductions, the per-sequence slot address and the SiLU-times-up body. Same code, so the same results (golden unchanged). - The norm kernels state their semantics instead of pointing at FlashInfer; `codec.cu` names `codec::build`, which generates its calls. - build.rs used the `cc` crate only to locate nvcc. It now takes `$NVCC`, else `$CUDA_PATH/bin/nvcc`, else `/usr/local/cuda/bin/nvcc`, else `nvcc` on the PATH, and `cc` is dropped from the workspace dependencies. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
…he crate exposes only what callers use - `launch` derives a kernel's module (and whether it is PDL-safe) from its entry's prefix; `Gen.module` and its assignments are gone. - `each8` takes rows as a var expression (or a number) and the values per row, and replaces every hand-rolled elementwise launch in the talker and stack; `each`, `blocks` and `rows_arg` are gone, `i32a` takes any integer that fits an i32 and replaces talker's `int`. - The talker allocates its slot region like every other generator; the `region(..) == 0` side-effecting check is gone. - `Model::load` turns `kv_gib` into KV tokens itself; the engine no longer reads the config a second time. - Only `config`, `engine`, `model`, `prompt` and `weights` are public; `Gen`'s fields are private and `bytes_of` (an unsafe generic) is replaced by a safe i32 serializer. Unused `Seq::frames`, `Model::limits` and `Model::eos` are gone; the engine asks `Model::is_end`. - Clarity: `Frame` is a struct, `stack::Out` is `Keep`, `Engine::take` is `enqueue`, `Stack::forward`'s two attention blocks are one `attention`; the codec imports `GROUPS`, sizes its KV ring by `sliding_window` and its zero bias by `dim`; the golden test reads the predictor vocabulary from the config. - `--max-step-tokens` says what it counts: prompt tokens prefilled per step. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
Nothing links cuBLAS or cuRAND any more and kern-runtime loads CUDA at runtime, so the tests need only nvcc and the headers to build. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
… method is reproducible The codec-alone numbers came from a scratch harness against the codec's own runtime, which no longer exists; bench.md now says so and how to take the same numbers from the one-manifest model with nsys. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
…mance are measured on this branch vLLM-Omni now runs its single-GPU throughput profile under MPS, redrawn by vs_vllm_omni.sh all; the A/B against main is recorded in bench.md. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Signed-off-by: JinYan Su <751080330@qq.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Qwen3-TTS now runs as one generated kern manifest on one runtime. The talker, code predictor, sampler and a streaming codec decoder all run from it, and each decode step is one CUDA graph launch.
omni-cudaand the FlashInfer submodule are gone.prefill(eager, ragged),first(graph) anddecode(graph). A request runsprefill, thenfirst, thendecodeuntil it draws the end token.--context-framesis gone.kernels/codec.cu: fused and vectorized epilogues, residual GEMMs that accumulate in place, and programmatic dependent launch.startfor new requests andstepfor running ones. A request that can never fit is rejected rather than queued forever.tools/qwen3_tts/vs_vllm_omni.sh allreruns the comparison with vLLM-Omni and redraws the README chart; its headline ratios are computed from the results.docs/qwen3-tts.mdcovers the programs, state, kernels, correctness and performance.docs/bench.mdrecords the codec kernel breakdown and this A/B.docs/qwen3-tts-vs-vllm-omni.mdhas new results.CLAUDE.mdorientation now covers the crate and its tools.New dependencies:
Removed: omni-cuda, FlashInfer, cudarc, and CI's cuBLAS/cuRAND installs.
Performance
2026-09-23 on one GB300, measured against main with
omni-bench, default 2+8 chunks, pinned cores. Values are the mean of two alternating runs.Against vLLM-Omni, on its own benchmark with the same 1+25 schedule, pega-omni reaches first audio 3.7-6.6x sooner and serves 1.4-2.5x the audio per second (c=64: 504 vs 202).
Test plan
prefill/first/decodepath, with the recorded frames forced.cargo fmt --check,cargo clippy --all-targets --all-features -D warnings,cargo test --workspace.tools/openai_sdk_check.pyagainst a live server.cuGraphLaunchper decode step.Follow-ups: #2 (VoiceDesign) and #3 (Base / voice cloning).
🤖 Generated with Claude Code