Skip to content

fix(gpu): clamp the ADM contrast-masking far edge and finish the ssimulacra2 FMA unification - #1363

Merged
lusoris merged 4 commits into
masterfrom
fix/gpu-parity-adm-edge-and-ssimulacra2-fma
Sep 6, 2026
Merged

lusoris merged 4 commits into
masterfrom
fix/gpu-parity-adm-edge-and-ssimulacra2-fma

Conversation

@lusoris

@lusoris lusoris commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Two cross-backend parity gates were red on any host with a GPU and green in CI, because every CUDA parity test skips when no CUDA device is visible and GitHub runners have none. On an RTX 4090 the shipped suite was 16/18. Both are fixed here, along with the test blind spot that hid them.

before after gate
float_adm CPU↔CUDA (adm_scale3) 8.58e-04 4.00e-08 1e-4
ssimulacra2 CPU↔CUDA 2.62e-03 2.81e-09 1e-4
shipped CUDA parity suite 16/18 18/18 —

ADR-1204 — the ADM contrast-masking far edge. The CPU closed form adm_cm_thresh3x3_s is asymmetric: the near edge mirrors to index 1, the far edge clamps to the last index. All four GPU twins mirrored both edges (2 * half_w - x - 2, i.e. w - 2). It only diverges when the ADM border crop (int)(dim * 0.1 - 0.5) collapses to 0 — band dimensions ≤ 14 — because only then are the first and last row and column inside the sum. Isolated by sweep, not inspection: scale-3 heights 9..14 diverge and 15+ are exactly 0.00; the predicted symmetric case (small width → left crop 0) was then confirmed independently.

ADR-1205 — ssimulacra2 was not FMA-unified outside the SIMD kernels. ADR-0891 reached the AVX2 / AVX-512 / NEON / SVE2 kernels and the SIMD test's own private scalar reference, but not the five shipped non-SIMD copies: the scalar fallback plus the CUDA, HIP, Metal and SYCL host conversions. Because the test compares kernels against its private reference rather than the shipped function, it asserted bit-exactness and passed. This was also a CPU-only reproducibility bug — a host without AVX2 scored ssimulacra2 differently from one with it.

Two things worth flagging about how this was diagnosed:

  • The GPU was not the wrong side. Forcing the CPU onto its scalar dispatch made CPU and CUDA bit-identical (delta exactly 0.00e+00), proving the whole CUDA chain was already correct and the CPU SIMD path was the outlier. Fixing the kernel would have been fixing the wrong end.
  • The obvious explanation was false. "GCC contracted the scalar mul-add into an FMA" is tidy; rebuilding the entire library with -ffp-contract=off left the delta at 2.62e-03, unchanged. The cause was an explicit fmaf() five copies never received.

A ~1 ULP seed becomes 1e-3 because the pipeline is ill-conditioned: the edge-diff term is |img - blur(img)| (catastrophic cancellation) and pooling is a 4-norm. The 4-norm slots carried the entire error while every 1-norm slot matched — that signature is what located it.

ADR-1206 — the blind spot. Every CUDA parity test pinned one small fixture, holding the SSIM/MS-SSIM auto-scale max(1, round(min(w,h)/256)) permanently at 1. Three defects have now hidden there (this pair plus the speed_chroma 4K bug). Fixture macros are #ifndef-guarded so a 960x540 variant is built from the same source via -D.

Type

  • fix — bug fix
  • sycl / cuda / simd — backend-specific

Checklist

  • Commits follow Conventional Commits (the commit-msg hook enforces this).
  • make format && make lint is green locally (pre-commit run --files clean on all 40 changed files; assertion-density, check-copyright, check-state-md-rows, check-adr-numbering, check-dispatch-registry all pass).
  • Unit tests pass: meson test -C build — 179 ok, 1 failure (test_vmaf_cuda_gpumask, pre-existing and unrelated; new open row below).
  • If I touched any SIMD/GPU code path, I ran /cross-backend-diff and the worst ULP is ≤ 2 — equivalent done directly: CPU↔CUDA agreement is 4.00e-08 (ADM) and 2.81e-09 (ssimulacra2), both far inside the places=4 gate.
  • If I touched a feature extractor with SIMD/GPU twins, I either updated every twin or listed the gap under "Known follow-ups" — all four twins updated for both fixes.
  • If I added a new .c / .cpp / .cu / .h / .hpp, it has the appropriate license header — no new source files added.
  • If this is a breaking change — not a breaking change.
  • If this PR adds an ADR, the ADR row lives in docs/adr/_index_fragments/<NNNN-slug>.md and the slug is appended to _order.txt — concat-adr-index.sh --check is in sync.

Bug-status hygiene (ADR-0165)

  • docs/state.md updated in this PR with a row — two closed (T-GPU-FLOAT-ADM-CM-EDGE-MIRROR-2026-09-06, T-SSIMULACRA2-FMA-SCALAR-GPU-DRIFT-2026-09-06) and one newly opened (T-CLI-GPUMASK-NEGATIVE-REJECTED-2026-09-06).

Netflix golden-data gate (ADR-0024)

  • I did not modify any assertAlmostEqual(...) score in the Netflix golden Python tests.
  • If I believe a golden value must change — no golden value changes. ssimulacra2 is fork-added and not in the VMAF golden set, and scores on AVX2/AVX-512/NEON/SVE2 hosts are bit-unchanged, so no snapshot moved.

Cross-backend numerical results

Measured on an RTX 4090 in the dev-mcp container. ADM scale3 delta by scale-3 band height, W=256:

height 9 10 11 12 13 14 15 16 18
before 8.6e-04 8.6e-04 8.2e-04 6.7e-04 5.5e-04 5.1e-04 0.00 0.00 0.00
after 4.0e-08 0.00 3.7e-08 0.00 0.00 3.4e-08 0.00 0.00 0.00

ssimulacra2 CPU↔CUDA after the fix: 2.81e-09 @ 256x144, 1.12e-08 @ 512x288, 1.25e-09 @ 960x540, 1.14e-09 @ 1280x720.

SYCL, HIP and Metal carry the identical ADM and FMA changes but are verified by their own CI parity lanes — this workstation only had a free CUDA device.

Deep-dive deliverables (ADR-0108)

  • Research digest — docs/research/2032-gpu-parity-resolution-blind-spot.md, the measurement method and a reusable checklist.
  • Decision matrix — ## Alternatives considered in ADR-1204, ADR-1205 and ADR-1206 (including why fixing the GPU side, and why widening the tolerance, were both rejected).
  • AGENTS.md invariant note — both invariants added to core/src/feature/AGENTS.md.
  • Reproducer / smoke-test command — below.
  • CHANGELOG fragment — changelog.d/fixed/gpu-adm-cm-edge-clamp-and-ssimulacra2-fma.md.
  • Rebase note — docs/rebase-notes.md, "ADR-1204 / ADR-1205 — ADM CM edge policy and the ssimulacra2 FMA contract".

Reproducer

Needs a host with a CUDA device — without one the tests skip and report green, which is the whole point.

meson setup build -Denable_cuda=true -Denable_sycl=false
ninja -C build
meson test -C build test_cuda_float_adm_parity test_cuda_ssimulacra2_parity \
                    test_cuda_float_adm_parity_large test_cuda_ssimulacra2_parity_large

On master the first two fail (delta=4.78e-04 and delta=2.62e-03 against tol=1.00e-04); on this branch all four pass.

Known follow-ups

  • test_ssimulacra2_simd.c should assert against the shipped scalar functions rather than its own private references; until it does, this class of drift can recur silently. Not changed here to keep the diff reviewable.
  • The _large variants cover the CUDA family only, because that is the device this workstation had free. Extending the same pattern to the SYCL / HIP / Metal parity families is a follow-up.
  • T-CLI-GPUMASK-NEGATIVE-REJECTED-2026-09-06 (opened here, not fixed): the gpumask test script documents --gpumask -1 as "use cpu", but the CLI rejects negatives, so test_vmaf_cuda_gpumask fails on any GPU host and is green only via its no-GPU skip. Needs a decision on which side is right, so it needs its own ADR.

🤖 Generated with Claude Code

lusoris and others added 4 commits September 6, 2026 18:37
…ulacra2 FMA unification

Two cross-backend parity gates were red on any host with a GPU and green in
CI, because every CUDA parity test skips when no CUDA device is visible. On an
RTX 4090 the shipped suite was 16/18.

ADR-1204 — float_adm GPU twins read the wrong far-edge sample. The CPU closed
form `adm_cm_thresh3x3_s` is asymmetric: the near edge mirrors to index 1, the
far edge clamps to the last index. All four twins mirrored both edges
(`2 * half_w - x - 2`). It only diverges when the ADM border crop
`(int)(dim * 0.1 - 0.5)` collapses to 0, i.e. band dimensions <= 14, since only
then are the first and last row and column inside the sum. Isolated by sweep:
scale-3 heights 9..14 diverge, 15+ are exactly 0.00; the predicted symmetric
case (small width -> left crop 0) was then confirmed. adm_scale3 improves from
8.58e-04 to 4.00e-08 against the places=4 gate.

ADR-1205 — ssimulacra2 scored differently with and without SIMD, and on every
GPU backend. ADR-0891's FMA unification reached the four SIMD kernels and the
SIMD test's own private scalar reference, but not the five shipped non-SIMD
copies (the scalar fallback plus the CUDA, HIP, Metal and SYCL host
conversions). Because the test compares kernels against its private reference
rather than the shipped function, it asserted bit-exactness and passed.
Forcing the CPU to scalar made CPU and CUDA bit-identical (delta exactly 0.0),
which proves the GPU side was correct and the CPU SIMD path was the outlier.
FP contraction was ruled out by measurement: rebuilding with
`-ffp-contract=off` left the delta unchanged. The ~1 ULP seed is amplified by
an ill-conditioned pipeline (edge-diff is a catastrophic cancellation, pooling
is a 4-norm) into 2.62e-03; the fix takes it to ~2.8e-09. Scores on
AVX2/AVX-512/NEON/SVE2 hosts are unchanged, so no snapshot moved.

ADR-1206 — every CUDA parity test now also runs against a 960x540 fixture.
All of them pinned one small size, which held the SSIM/MS-SSIM auto-scale at 1
and kept resolution-dependent branches unreachable; three defects have now hid
behind that gap. Fixture macros are `#ifndef`-guarded so the second size comes
from `-D` with no duplicated source. `float_ssim_cuda` is a documented v1
scale=1-only extractor, so its large variant asserts that contract as a skip
rather than being dropped.

Also opens T-CLI-GPUMASK-NEGATIVE-REJECTED-2026-09-06: the gpumask test script
documents `--gpumask -1` as "use cpu" but the CLI rejects negatives. Found
while running the full suite; unrelated to this change and not fixed here.

Verified on an RTX 4090: shipped CUDA parity suite 18/18 (was 16/18), large
fixture suite 16/16, `meson test` 179 ok / 1 pre-existing unrelated failure
(test_vmaf_cuda_gpumask, the row above). SYCL/HIP/Metal carry the identical
ADM and FMA changes but are verified by their own CI parity lanes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nts (ADR-1204, ADR-1205)

Adds the ADR-0108 AGENTS.md invariant note for both fixes, and moves the ADR
index rows into docs/adr/_index_fragments/ so docs/adr/README.md stays
generated rather than hand-edited (ADR-0221).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tches it

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…changelog

The docs/state.md gate (ADR-0334) rejects `PR #TBD` placeholders, and the
release-script contract requires CHANGELOG.md's Unreleased block to be
rendered from changelog.d/.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant