perf(speed): dispatch the SpEED matrix product through bit-exact AVX2/AVX-512 kernels - #1344
Draft
lusoris wants to merge 1 commit into
Draft
perf(speed): dispatch the SpEED matrix product through bit-exact AVX2/AVX-512 kernels#1344lusoris wants to merge 1 commit into
lusoris wants to merge 1 commit into
Conversation
lusoris
force-pushed
the
perf/hot-path-1245
branch
6 times, most recently
from
September 7, 2026 17:01
948e671 to
ca87206
Compare
…/AVX-512 kernels A `perf record` profile of the CPU pipeline under the default model (`vmaf_v1.0.16_3d0h`) put `matrix_mul` in `core/src/feature/speed.c` at 20.78 % of total samples — the dense product behind SpEED's Householder QR and its `Q^T B` solve, running on 128-bit `mulps`/`addps` because the generic C library compiles at the x86-64 baseline while the host has AVX-512. Add `speed_matmul_avx2` / `speed_matmul_avx512` as runtime-dispatched twins of an exported `speed_matmul_scalar`, selected in `speed_dispatch_cpu_kernel()` from `vmaf_get_cpu_flags()` exactly like the existing `compute_cov_kernel_*` family, and thread the pointer through `matrix_qr_decomposition()` and `solve_linear_system()`. Scores do not move, and the reason is structural rather than statistical: `j` in `dst[i][j] += x[i][k] * y[k][j]` is an output index, not a reduction axis, so vector width cannot reorder any single element's accumulation over `k`. The only remaining hazard is FMA contraction, so both kernels keep the multiply and the add as separate intrinsics and each compiles in its own `-ffp-contract=off` static library — the same carve-out the tree already uses for `x86_ssim_avx2` and `x86_float_adm_avx2`. Verified on this host (`ryzen-4090-arc`): - `--precision=max` JSON byte-identical before and after on all three dispatch paths (`--cpumask` 56 scalar / 48 AVX2 / 0 AVX-512), for both the default model and `vmaf_v0.6.1.json`. - Netflix golden gate: 271 passed, 12 skipped, 0 failed. - `meson test -C core/build`: 129 Ok, 0 Fail. - `test_speed_simd` gains eight `memcmp`-exact cases (25x25x25 QR shape, 25x25x448 rectangular solve, tail and degenerate shapes): 16/16 pass. - 1.20x on the default model — 2.4820 s -> 2.0674 s median CPU time over 9 interleaved repetitions of 1440 frames at 576x324, 1-minute load average 2.81. `vmaf_v0.6.1.json` (no SpEED feature) is unchanged at 1.004x. - `matrix_mul` 20.78 % -> `speed_matmul_avx512` 5.67 % in the re-run profile. `si_mat_mul` in `speed_internal.c` (the ADR-0964 duplicate used by the GPU twins' host side) is deliberately left scalar: no GPU backend currently completes a scored run longer than one motion batch, so the change could not be measured, and an unmeasured change is not a win. Refs ADR-1196, research digest 2030, epic #1245 item 3. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
lusoris
force-pushed
the
perf/hot-path-1245
branch
from
September 7, 2026 18:32
ca87206 to
eeb4619
Compare
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
A
perf recordprofile of the CPU pipeline under the default model(
vmaf_v1.0.16_3d0h) putmatrix_mulincore/src/feature/speed.cat20.78 % of total samples — the dense product behind SpEED's Householder QR
and its
QᵀBsolve, running on 128-bitmulps/addpsbecause the generic Clibrary compiles at the x86-64 baseline while the host has AVX-512. This PR
adds bit-exact AVX2 and AVX-512 twins, runtime-dispatched exactly like the
existing
compute_cov_kernel_*family, and publishes the full profile as aresearch digest including the wins that were found and deliberately not
taken because they would move scores.
Result: 1.20x on the default-model CPU run, scores byte-identical on the
scalar, AVX2 and AVX-512 dispatch paths, Netflix golden gate 271 passed /
12 skipped / 0 failed.
Type
perf— performance improvementsycl/cuda/simd— backend-specificChecklist
make format && make lintis green locally. clang-format clean on every touched file;clang-tidy -p <no-LTO CPU build>reports 0 findings on both new kernels and onspeed.c;core/test/test_speed_simd.cstays at its ADR-1142 baseline of 5 (4 ×modernize-use-nullptr+ 1 ×readability-function-size) — the extrareturn NULLthe new helper would have added is offset by turningrun_tests's earlyreturn NULLguard into anif (have)block. No baseline file is touched.scripts/ci/assertion-density.sh,check-copyright.sh,check-adr-numbering.sh,check-conflict-markers.sh,check-no-tracked-venv.sh,check-state-md-rows.shand both fragment--checks pass.meson test -C core/build→ 129 Ok, 0 Fail./cross-backend-diffand the worst ULP is ≤ 2. — stronger than that here: 0 ULP, byte-identical--precision=maxJSON. See "Cross-backend numerical results". No GPU path is touched by this PR..c/.cpp/.cu/.h/.hpp, it has the appropriate license header (Copyright 2026 Lusoris, verified byscripts/ci/check-copyright.sh).!orBREAKING CHANGE:— not a breaking change; no public header, CLI flag ormeson_options.txtentry changes.docs/adr/_index_fragments/1196-speed-matmul-simd-dispatch.mdand the slug is appended to_order.txt;docs/adr/README.mdis regenerated by the script.Bug-status hygiene (ADR-0165)
docs/state.mdupdated —no state delta: performance work with no user-visible behaviour change; no bug is opened, closed or ruled not-affecting by this PR. The two open GPU rows the digest cites (T-GPU-MOTION-FLUSH-DOUBLE-EMIT-2026-09-06, T-GPU-CLI-THREADS-CTX-SYNC-2026-09-06) already exist and are unchanged.Netflix golden-data gate (ADR-0024)
assertAlmostEqual(...)score in the Netflix golden Python tests. The gate is untouched and green:Cross-backend numerical results
This PR changes only CPU dispatch, so the meaningful comparison is
scalar vs AVX2 vs AVX-512 against the pre-change binary.
--precision=max(
%.17g) JSON, unmodifiedorigin/masterbuild vs this branch, same fixture(
src01_hrc00_576x324.yuv/src01_hrc01_576x324.yuv),--cpumaskbits namethe ISAs to disable:
"IDENTICAL" =
diffof the two JSON files with only the"fps"and"version"lines excluded is empty. Worst ULP: 0.core/test/test_speed_simdadditionally gates the twins withmemcmpequality against the production scalar reference (not a copy of it) across the
25x25x25 QR shape, the 25x25x448 rectangular solve, a
cols=41shape hittingevery tail branch, and a
cols=3, inner=1degenerate shape:Performance (if
perforfeat)Host
ryzen-4090-arc, base =origin/masterbuild, new = this branch, both--buildtype=release -Db_ndebug=true, CUDA/SYCL/HIP off. Timing isru_utime + ru_stimeof the child process viaos.wait4, base and newinterleaved run-for-run so machine drift hits both sides equally. Median of N
repetitions after a discarded warm-up; the 1-minute load average during each
round is quoted because this workstation was not idle for most of the session.
v1.0.16_3d0hv1.0.16_3d0hvmaf_v0.6.1.jsonThe
v0.6.1row is the control: that model has no SpEED feature, so thesekernels are never reached, and 1.004x is inside the noise of no change (four
rounds were run in total; the two that returned 0.977x and 0.938x had 24–50 %
spread under load 14–51, with the patched side marginally faster at its
minimum). Earlier default-model rounds under load 15–16 gave 1.188x and
1.134x — the direction is stable across every round and the magnitude
compresses under contention, which is why the near-idle round is the one
quoted.
Profile before → after,
perf report --no-children -g none, same command,13 289 / 10 522 samples:
A 15.1-point drop in total samples predicts
1/(1−0.151) = 1.18x; the measured1.20x agrees.
GPU profilers are not installed on this host —
ncu,nsys,vtuneandrocprofare all absent (/opt/cuda/binhasnvcc,cuda-gdbandcompute-sanitizeronly), so no CUDA / SYCL / HIP kernel profile wascollected. A GPU profile would in any case have been blocked by
T-GPU-MOTION-FLUSH-DOUBLE-EMIT-2026-09-06: no GPU backend currentlycompletes a scored run longer than one motion batch.
Deep-dive deliverables (ADR-0108)
docs/research/2030-speed-matmul-and-cambi-cpu-hot-path.md.docs/adr/1196-speed-matmul-simd-dispatch.md## Alternatives considered(six options, including the two larger wins that were rejected for moving scores).AGENTS.mdinvariant note —core/src/feature/AGENTS.md, new section "matrix_muldispatches;si_mat_muldeliberately does not (ADR-1196)".changelog.d/changed/speed-matmul-simd-dispatch.md;CHANGELOG.mdregenerated byscripts/release/concat-changelog-fragments.sh.docs/rebase-notes.md, "perf/hot-path-1245 — SpEEDmatrix_mulgains a kernel-pointer parameter (2026-09-06)".Reproducer
meson setup build-prof core -Denable_cuda=false -Denable_sycl=false -Denable_hip=false \ --buildtype=release -Db_ndebug=true \ -Dc_args='-g -fno-omit-frame-pointer' -Dcpp_args='-g -fno-omit-frame-pointer' ninja -C build-prof # 1. bit-exactness: all three dispatch paths must agree, byte for byte for mask in 0 48 56; do ./build-prof/tools/vmaf \ -r python/test/resource/yuv/src01_hrc00_576x324.yuv \ -d python/test/resource/yuv/src01_hrc01_576x324.yuv \ -w 576 -h 324 -p 420 -b 8 --no_cuda --no_sycl --no_hip \ --cpumask $mask --json --precision=max -o out-$mask.json done diff <(grep -vE '"fps"|"version"' out-0.json) <(grep -vE '"fps"|"version"' out-56.json) # empty # 2. SIMD parity unit tests (memcmp-exact) ./build-prof/test/test_speed_simd # 16 tests run, 16 passed # 3. the profile that motivated the change perf record -F 4999 -g --call-graph=fp -o perf.data -- \ ./build-prof/tools/vmaf -r ref1440.yuv -d dis1440.yuv -w 576 -h 324 -p 420 -b 8 \ --no_cuda --no_sycl --no_hip --json -o /dev/null perf report -i perf.data --no-children -g none --percent-limit 0.3 --stdioKnown follow-ups
Ranked in the digest with the evidence for each:
Q ← Q − 2v(vᵀQ)) instead of the explicit25x25x25 product — O(n²) per QR iteration instead of O(n³), roughly a
further 25x on the same code, and still the largest single SpEED CPU item
after this PR. Not taken: it changes the summation order, so scores
move. Needs its own PR with a snapshot-regeneration justification and a
cross-backend re-parity pass.
anti_dithering_filterincambi.c(4.98 % of the baseline run) — a2x2
uint16_tbox filter with>>2, done in place, and the in-placeupdate provably has no read-after-write hazard within a row. Widening it to
AVX2 via 32-bit unpack/add/shift/pack is integer-exact by construction.
Left out only to keep this PR to one subsystem; it is the obvious next
bit-exact increment.
quick_select_partition(10.22 %) andaverage_topk_elements(3.70 %) — now the largest remaining items, but both are order-sensitive
(the top-k sum accumulates in array order into a
double), so neitheradmits a bit-exact rewrite.
si_mat_mulinspeed_internal.c— the ADR-0964 duplicate of the sameloop used by the host side of the CUDA / SYCL / HIP SpEED twins. Routing it
through the same dispatch is bit-exact by the identical argument, but the
benefit cannot be measured on this host while
T-GPU-MOTION-FLUSH-DOUBLE-EMIT-2026-09-06is open, so it is deliberatelyleft scalar rather than landed on faith. Documented in
core/src/feature/AGENTS.mdso a future reader does not "resync" it asdrift.
No GPU twin is out of date as a result of this PR:
speed_chroma/speed_temporalon CUDA, SYCL and HIP go throughspeed_internal.c, which isbyte-for-byte unchanged.