Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
218 changes: 218 additions & 0 deletions .agents/benchmark-record.md
Original file line number Diff line number Diff line change
Expand Up @@ -24838,3 +24838,221 @@ The job builds `ltx2-gen` from `7b9e207b1` in-lease, refusing to proceed unless
`bin/` and `job/` are staged under `/workspace/ltx25-fullmodel` on the house NAS.
Run evidence is `out/20260819T150230Z/` with `PROVENANCE`, `memguard.tsv`,
`phases.tsv`, `verify.txt` and `render.log` per rung.
---

## MUSIC3-VOCODER-CONV — the vocoder decode window, dependent-chain vs tiled f64 accumulators, Jetson Thor sm_110 (2026-08-19, `row/MUSIC3-VOCODER-CONV-SPEED`, #672, #1334)

### What was measured, and what it is not

`music3::VocoderDecode` at the shipped `MiniMaxMusic3VocoderConfig` geometry —
the call `Music3DecodeChunks` brackets as the `vocoder.decode_window` profile
bucket (`minimax_music3_speech.cpp:397-398`) — over a sweep of latent window
lengths, on the DEFAULT arm (`VLLM_CPP_VOCODER_DEVICE` unset, so the host
kernel). Driver `tools/bench/music3_vocoder_conv_ab.cpp`, recipe
`scripts/music3-vocoder-conv-ab.sh`.

It is NOT an end-to-end synthesis. The weights are synthetic, so nothing here is
a claim about audio, and **no checkpoint was read** — the staging assertion
`SRC_BYTES == DST_BYTES` is therefore NOT APPLICABLE rather than skipped, because
there is no path to assert. The e2e pair on the real checkpoint is owed
(`minimax-music3.md` §18.9).

It takes **no GPU clock window** and it is a CPU measurement, so nothing here is
quotable as a per-kernel or cross-box figure.

**53.6 s is the CUDA arm.** §15.2's profile ran `VLLM_CPP_VOCODER_DEVICE=cuda`,
so the 53.6 s / 33.2 %-of-wall figure that defined this row is the DEVICE arm's
decode window, while the default is `cpu` (§13.6) and that is the arm this row
moves. No projection from 1.42x onto 53.6 s is made, because that would multiply
two different arms.

### The lease, and why it is the whole of the serialisation

`rc` job `da3a2f94-90e3-4e97-b519-9456310673b7` on **`thor:gpu0`**,
`--max-runtime 150m`. Worker `rc-worker-hqfj4`, `Linux 6.8.12-1021-tegra`
aarch64, 14 cores. No `ssh`, no `rc hold`, no `$GPU_LOCK`. §13.10's whole speed
axis is VOID because its arms went in over `ssh` under the file mutex while
another session held the same box through `rc`, so the two mutexes did not
exclude each other; this run has exactly one.

### The arms — two trees, two build directories, two binaries

Both built INSIDE the lease from two clones under `/tmp` (local overlay, not the
CIFS `/workspace`), `CMAKE_BUILD_TYPE=Release` (`-O3`), CPU-only, `ninja -j 8`.

| | before | after |
|---|---|---|
| ref | `f06b9e93d` (kernel only) | `c16bacc13` = `row/MUSIC3-VOCODER-CONV-SPEED` |
| `src/vt/cpu/cpu_conv1d_general.cpp` sha256 | `6fb15174c1533b93b1bc58401e3a21fe0f4da9101729108d89118ee72fb146db` | `a0e429ace35364798dfa060f08002941544b3136a59c62712778df1c2174dba1` |
| `vllm_music3_vocoder_conv_ab` sha256 | `d90e3912cd636666067df089b0267fbd428300cac80a29706113693b46610b22` | `41ba78d2b7a8b99ef34a32fd6620b1530c88ac1cc279a811d338482543b811b5` |

`diff -rq` over `src/` names that ONE file as the only difference, and the recipe
HARD-FAILS (`exit 5`) when the two binaries hash the same. That guard is there
because the depth row's first Thor pair was VOID for precisely this — both arms
were one binary, and the tell was identical call counts, since equal times are
noise where equal binaries are identity (§16.6a).

**The `after` ref `c16bacc13` was later rewritten** (the branch was rebuilt to
fold `docs/USAGE.md` into the commit that owed it, because
`check-doc-checkpoint` validates each commit individually). The kernel is
BYTE-IDENTICAL across that rewrite — `a0e429ace3536479...` at both
`c16bacc13` and the branch head — so the measurement is unaffected, and this is
recorded rather than left for a reader to discover that the sha is unreachable.

### Correctness, established before any speed number was read

On the after arm, in the same container. `test cases`, `assertions` AND `Status`
are quoted for each, because `assertions: 0` is a skip wearing a pass.

| suite | result | rc |
|---|---|---|
| `test_ops_conv1d_general` | 9 cases, 375 assertions, 0 failed, `SUCCESS!` | 0 |
| `test_host_parallel` | 8 cases, 877 assertions, 0 failed, `SUCCESS!` | 0 |
| `test_vocoder1d` | 10 cases, 58 assertions, 0 failed, `SUCCESS!` | 0 |
| `test_bigvgan` | 6 cases, 65 assertions, 0 failed, `SUCCESS!` | 0 |

Three `[SKIP]` lines printed and are read rather than ignored: this worker has
**no `nvcc`**, so the build is CPU-only and every CPU-vs-CUDA arm in that file
did NOT run. The device provider is untouched by the change and §18.3's argument
says its `memcmp` must still hold, but an argument is not a measurement, and the
re-measurement is OWED.

(That probe also CORRECTS §13.10's record of this worker: it carries `gcc`,
`g++`, `cmake`, `ninja`, `make`, `python3` and `git`, and only `nvcc` is
missing. §13.10 recorded "no compiler and no toolchain at all" and named a
worker image as the blocker for re-measuring under a lease. For a CPU arm that
blocker does not exist.)

### The sweep — arms ALTERNATED, 3 rounds, best-of-3 per point

`uptime` load average **3.29 before the build, 9.07 before the sweep, 8.48
after** it, 0 other users. The box was NOT idle; both arms ate the same
contention, which is what alternating them is for.

Medians of the three rounds, in seconds:

| latent frames | before | after | ratio | s/latent after |
|---|---|---|---|---|
| 20 | 5.5688 | 4.0831 | **1.364x** | 0.2042 |
| 40 | 11.0535 | 7.8186 | **1.414x** | 0.1955 |
| 86 | 23.5149 | 16.6614 | **1.411x** | 0.1937 |
| 172 | 47.9201 | 33.6498 | **1.424x** | 0.1954 |
| 344 | 97.4463 | 67.7083 | **1.439x** | 0.1974 |

The loudest pair is KEPT and not dropped: 20 frames is the weakest ratio in the
set, and it is the size §15.9 priced the device arm at. Round-to-round spread
within an arm is under 1 % at every point (e.g. before@344: 97.1298 / 97.4463 /
97.9561; after@344: 67.4177 / 67.7083 / 67.9122), so the 1.36-1.44x band is well
clear of the noise.

**Flat across a 17x span of work**, which is the signature of a RATE change
rather than of a fixed overhead — the same test §13.10 applied to the device arm,
run in the other direction.

### The arms are BIT-IDENTICAL at full scale

The driver prints an FNV-1a fingerprint over the whole stereo waveform. Across
all six arm-rounds every length produced ONE value on both arms:

| latent frames | fingerprint (both arms, all rounds) |
|---|---|
| 20 | `0x7c31c2ea73418503` |
| 40 | `0x35a02aad4c9cb983` |
| 86 | `0xc2d5eaf095d1c483` |
| 172 | `0x2dc69976150a5903` |
| 344 | `0x95e771d5f0051283` |

Six processes, two binaries, one answer per length. A sum would let two different
waveforms agree; a byte-wise hash cannot.

### The x86 half, labelled as what it is

Design-selection measurements, single-threaded, `taskset -c 2`, AMD Ryzen 9
9950X3D, g++ 13.3.0, `-ffp-contract=off`, min of 5, `memcmp`-identical on every
case. These SELECTED the design; they do not gate it, and they carry no clock
attribution.

| stage (per stereo stream, latent 20) | MMAC | shipped | tiled | |
|---|---|---|---|---|
| `conv_in` k7 1024->1536 | 220.2 | 0.1096 | 0.0322 | 3.40x |
| `b0.res.conv1` k7 768 | 660.6 | 0.3289 | 0.0612 | 5.37x |
| `b1.res.conv1` k7 384 d3 | 1321.2 | 0.6630 | 0.1260 | 5.26x |
| `b1.res.conv2` k1 384 | 188.7 | 0.1585 | 0.0245 | 6.48x |
| `b2.res.conv1` k7 192 | 1321.2 | 0.6760 | 0.1238 | 5.46x |
| `b3.res.conv1` k7 96 | 660.6 | 0.3281 | 0.0613 | 5.35x |

Every ratio in the table above is a KERNEL figure at one stage geometry, not a
decode-window figure. The window also runs `vt::ConvTranspose1d`, the alias-free
activations, the strided downsamples and the threadpool around all of them; the
window ratios on this row are 1.36-1.44x (14 threads, §18.8a) and 2.16x (one
thread, §18.8b), and the review that asked for this distinction measured the real
project build at `-O2` single-threaded and got 2.56x / 2.67x on the window
against the 5.2-5.8x the kernel gives there. The two quantities are never
multiplied or substituted.

The shipped kernel's rate is **1.76-1.86 GMAC/s on one core** of the 20-core Zen
5 — 1.86 / 1.82 / 1.84 at ranks 8 / 16 / 32 and 1.7622 at rank 1024 for the depth
decoder's loop of the same shape, recorded in the MUSIC3-DEPTH entry above, and
1.827 for THIS loop at `in_per_group = 384`, `kernel = 7` under GCC 13.3 `-O2
-ffp-contract=off`, pinned, measured by this row's fresh review on 2026-08-19.

**The cycles-per-MAC conversion this entry first carried is WITHDRAWN.** It read
"1.7-2.0 GMAC/s ... ~2.8-3.0 cycles per multiply-accumulate on a 5.0 GHz Zen 5
whose `fadd` latency is 3", which is not self-consistent — 2.0 GMAC/s at 5.0 GHz
is 2.5 cycles per MAC, and a strictly dependent chain of latency-3 `fadd`s cannot
exceed 5.0 / 3 = 1.67 GMAC/s at all, so the top of that band sat above its own
ceiling. The deeper problem is the instrument: the x86 host is a KVM guest
reporting a nominal 4291.948 MHz with no `cpufreq` interface and
`perf_event_paranoid` at 4, so neither its boost clock nor a cycle count is
observable from inside it, and every cycles-per-MAC number here was a conversion
through an assumed clock. What survives is the arithmetic identity — a dependent
chain of latency-`L` adds cannot beat `clock / L` MACs per second — and the fact
that breaking the chain, changing nothing else about the arithmetic, its order or
its width, is worth 2.16x per core. `minimax-music3.md` §18.2 carries the full
correction.

`vt::ConvTranspose1d` measured separately: at `-O3` the compiler already
vectorises its scatter and the op is **~6 % of the chain's wall** against
`vt::Conv1d`'s ~94 %; at `-O2` it does not, and the fixed tap chunk is worth
2.7-2.9x on the three kernels of 8 taps or more.

### The single-thread pair: the KERNEL is 2.16x, the THREADPOOL returns 1.37x

Second lease, `rc` job `5b98f95e-a37b-4fa2-8ee9-81959caa828f` on `thor:gpu0`,
`--max-runtime 45m`, a FRESH container with both arms rebuilt from the same two
refs into a NEW pair of binaries (`a3b14f2995...` before, `5b894d6b67...` after),
so this is an independent build as well as an independent run. Latent length 20,
the point where the 14-thread ratio is weakest:

| threads | before | after | ratio |
|---|---|---|---|
| **1** (`VLLM_CPP_CPU_THREADS=1`) | 37.0508 s | 17.1751 s | **2.157x** |
| 14 (default), same container, same binaries | 5.4845 s | 4.0182 s | **1.365x** |

The 14-thread control reproduces the main run's 1.364x to three digits on
different binaries, which is what makes the single-thread leg comparable rather
than merely adjacent. Fingerprint `0x7c31c2ea73418503` on every leg of both runs.

Scaling, which is where the cause is:

| arm | 1 -> 14 threads | of a possible 14x |
|---|---|---|
| before | 6.76x | 48 % |
| after | **4.27x** | **31 %** |

Neither arm scales and the FASTER one scales worse — the signature of a shared
resource that the tiled kernel reaches sooner because it needs the same bytes in
less time. This does NOT identify the resource: no bandwidth counter was read and
none is available on this worker, so "memory bandwidth" is the leading candidate
and not a finding. What it does establish is that the next lever is the
vocoder's PARALLEL DECOMPOSITION and not the kernel: `ForOutputRows` partitions
output channels, so all 14 threads sweep the whole input tensor.

It also settles the aarch64-vs-x86 question posed by the headline ratios: per
core the same source is worth 2.16x here against ~5x on AVX-512, a gap the 4x
narrower f64 vector explains without anything further.

### No parity claim

SGLang-Omni is still `gateable = no`. Every reference axis in `docs/BENCHMARKS.md`
stays `PENDING`. Everything above is an internal two-arm number on one named box.
Loading
Loading