GSV.CPP / docs /benchmark.md
KakaruHayate's picture
docs: api/benchmark/usage
c9aef91 verified
|
Raw History Blame Contribute Delete
5.47 kB
# Benchmark
Full pipeline (frontend β†’ AR β†’ cond β†’ DiT β†’ vocoder), same text / reference audio / seed.
Times in milliseconds, median of three warm runs (rows 2–6). Rows 2–6 were re-measured on
2026-11-03 with the current development build (ggml-only F32 HiFi-GAN vocoder with sub-pixel upsampling,
RoPE/adaLN/mish fusion included); rows 1 and 4 are the unchanged torch reference.
| # | configuration | total | G2P/frontend | cond | AR | DiT | vocoder | cache |
|---|---|---|---|---|---|---|---|---|
| 1 | v5turbo torch | **3142** | 162 | 251 | ~2103* | 561 | 65 | β€” |
| 2 | v5turbo ggml (AR fp16) | **945** | 102 | 93 | 363 | 275 | 68 | β€” |
| 3 | v5turbo ggml (AR q8) | **1028** | 101 | 95 | 425 | 279 | 67 | β€” |
| 4 | v5dev torch | **11716** | 163 | 247 | ~2326* | 8924 | 56 | β€” |
| 5 | v5dev ggml | **5415** | 102 | 126 | 456 | 4617 | 68 | β€” |
| 6 | v5dev ggml + cache-dit | **4740** | 101 | 120 | 422 | 4005 | 69 | **17/64 = 26.6%** |
The vocoder is the ggml F32 HiFi-GAN with **sub-pixel upsampling**: the five transposed
convolutions are replaced by one 3-tap conv1d on the input grid plus a phase interleave
(`src/gsv_vocoder_ggml.cpp::upsample_subpixel`), and every convolution runs through
`ggml_conv_direct_1d_fused` (no im2col buffer; bias/LeakyReLU, the resblock input LeakyReLU and
the residual add all fold into the same kernel). For the 3.4 s clip in this table the vocoder
takes **~68 ms**, down from ~140 ms with `ggml_conv_transpose_1d`, ~115 ms with sub-pixel + im2col
and ~75 ms with per-conv direct conv (-51% overall). The old ONNX vocoder was ~114 ms. AR varies
by roughly Β±20% between runs on this machine (CPU sidecar + GPU sharing), which dominates the
spread of the totals.
\* torch's AR loop is captured as a single CUDA graph and cannot be timed separately; this
column is `total βˆ’ (frontend + cond + DiT + vocoder)` (AR + scheduling overhead).
## Environment
- CPU: Intel Xeon E5-2675 v3 @ 1.80 GHz (16 threads)
- GPU: NVIDIA GeForce RTX 2070 8 GB, driver supporting Vulkan 1.2
- OS: Windows 10 x64
- ggml: Vulkan backend (`--device vulkan`), weights as described per row
(AR fp16 = `gsv-ar-f16.gguf`, AR q8 = `gsv-ar-q8.gguf`; cond/DiT F16; refcode F16)
- vocoder: ggml HiFi-GAN, F32 weights (`gsv-vocoder-f32.gguf`), Vulkan backend
- torch: 2.12.1 + CUDA 13.0, fp16, `use_cuda_graph=True`, batch size 1, text split `cut0`
- sample steps: v5turbo 4 / cfg 0.0; v5dev 32 / cfg 1.30
- cache-dit: DBCache `Fn=8`, threshold 0.02, warmup 8, downsample 4
Notes:
- Text: `δ½ ε₯½οΌŒζ¬’θΏŽδ½Ώη”¨θ―­ιŸ³εˆζˆη³»η»Ÿγ€‚`
- Reference: 16 kHz Chinese prompt, ~3.4 s output audio in every row.
- Timings vary by a few percent between runs; the torch numbers include CUDA graph replay
(two warm-up runs with the same text are done before the measured run).
## DiT fusion (RoPE + adaLN)
`gsv_dit.cpp` builds the DiT step graph with a fused RoPE (`ggml_rope_ext`, head 0 only, with a
`pos` tensor and freq factors that reproduce the checkpoint `inv_freq`) and a fused
`LN(x)Β·(1+scale)+shift` (`ggml_layernorm_affine` with `wb = [1+scale, shift]`), replacing the
per-block cos/sin multiply chain and the `norm β†’ add β†’ mul β†’ add` sequence.
Same machine, turbo-q8, same text / seed, warm:
| DiT | before | after | delta |
|---|---|---|---|
| v5turbo DiT | 333 ms | 291 ms | βˆ’12.6% |
| v5turbo total | 1130 ms | 1033 ms | βˆ’8.6% |
Audio correlation against the pre-fusion build is 0.9955; two runs with the same seed are
bit-identical (corr 1.000000), so determinism is preserved. The `mish` epilogue fusion (A4 in the
DiT report) is also in: `ggml_add_act` gained an `act=3` (mish) branch on CPU and in the Vulkan
shader, and `conv_pos` folds bias + mish into one node. Its effect is below the 0.2% noise floor,
so the numbers above do not change. `tests/test_add_act.cpp` checks all four activations against
a host reference on CPU and on every GPU backend; CI runs it under Mesa lavapipe, which exercises
the Vulkan shader branch (max|diff| 2.4e-7).
## CPU backend threading
ggml's barrier spins while waiting, so on SMT CPUs the default hardware_concurrency() (logical
threads) oversubscribes and slows requests down. gsv_server now defaults to physical cores when
--threads is not given (hardware_concurrency()/2 when > 8).
Windows, Xeon E5-2675 v3 (16 cores / 32 threads, 1.8 GHz), turbo-q8, 4 steps, 3.04 s audio:
| threads | total | DiT | cond | AR | vocoder |
|---|---|---|---|---|---|
| 32 (old default) | 39.9 s | 28.8 s | 1.80 s | 2.26 s | 3.07 s |
| 16 (new default) | 35.4 s | 29.0 s | 1.20 s | 1.52 s | 1.62 s |
| 8 | 35.9 s | 28.8 s | 1.15 s | 1.54 s | 1.93 s |
DiT is memory-bound in this configuration and does not improve past 8 threads; the win is in the
AR/cond/vocoder stages. --threads N overrides the default.
## What is not parallelised
- **Pipeline overlap** (AR of segment i+1 while DiT/vocoder runs segment i) is not implemented:
ggml exposes a single backend queue, so both stages would serialize on the device anyway, and
on CPU they compete for the same cores. Batches are formed *across* requests instead
(/batch, AR batcher, request scheduler).
- **Larger conv tiles** in conv_direct_1d were not beneficial: the kernel already uses 6-wide
time blocks x 16-wide output blocks, and DiT (the dominant CPU stage) is memory-bound, so
thread count beyond 8 did not move it.