Skip to content

fix(gpu): honour adm_p_norm, adm_bypass_cm and adm_skip_scale0 in the float-ADM twins - #1379

Draft
lusoris wants to merge 1 commit into
masterfrom
fix/gpu-float-adm-options
Draft

fix(gpu): honour adm_p_norm, adm_bypass_cm and adm_skip_scale0 in the float-ADM twins#1379
lusoris wants to merge 1 commit into
masterfrom
fix/gpu-float-adm-options

Conversation

@lusoris

@lusoris lusoris commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Three VMAF_OPT_FLAG_FEATURE_PARAM options that the GPU float_adm twins
declare with the CPU's names, aliases, defaults and ranges — and did not
implement. The framework accepts each, range-checks it, and folds it into the
derived feature name (ADR-1183), so a score filed under adm2_apn_2 asserted
a setting that was never applied
.

1. adm_p_norm — four application points, one honoured (all four twins)

adm_tools.c applies the p-norm in four places:

/* 1. DLM numerator sum, 2. CSF denominator sum — both branch on p == 3 */
if (c->adm_p_norm == 3.0) { inner[0] += (xh * xh * xh); }
else                      { inner[0] += powf(xh, c->adm_p_norm); }

/* 3. pooling root, 4. the noise constant */
powf(accum[0], 1.0f / adm_p_norm) + get_noise_constant(w, h, weight, adm_p_norm)

All four twins hardcoded the cube in the kernel and 1.0f / 3.0f in the host
pooling, and used adm_p_norm for the AIM exponent alone — producing a sum
of cubes raised to 1/p, with adm2 and every adm_scaleN left at p = 3.

2. adm_bypass_cm — declared, stored, read by nothing (CUDA + Metal)

grep -n adm_bypass_cm over either twin returns exactly two hits: the state
field and the option-table entry. The 3x3 masking threshold was always
subtracted. It now gates both the DLM and the AIM CM kernel, as adm.c
passes it to both adm_cm() calls. SYCL and HIP do not declare it and reject
it — unchanged.

3. adm_skip_scale0 — a pooling rule mistaken for a reporting rule (Metal)

adm.c sets num_scale = 0, den_scale = 1e-10 for scale 0, so it drops out
of the pooled adm2 / aim. The Metal twin zeroed only the reported
adm_scale0 sub-score while still folding the full scale-0 numerator and
denominator into the pooled score. No kernel change was needed: adm_dwt2_lo_s
writes only band_a, which adm_dwt2 computes identically.

Reproducer

meson setup build-cuda core -Denable_cuda=true -Denable_sycl=false -Db_lto=false
ninja -C build-cuda
meson test -C build-cuda test_cuda_float_adm_parity --print-errorlogs -v

Revert core/src/feature/cuda/ and the new variants fail. Measured at
adm_p_norm = 2.0, against the 1e-4 ADR-0214 gate:

Backend key CPU GPU delta
CUDA (RTX 4090) adm2_apn_2 0.43097075 0.45416959 2.32e-02
SYCL (Arc A380) adm_scale0_apn_2 0.90092957 0.89784085 3.09e-03
HIP (gfx1030) adm2_apn_2 0.99818595 0.99833104 1.45e-04

Each variant fails with the fix reverted and passes with it applied, on all
three local devices. SYCL and HIP legs:

source /opt/intel/oneapi/setvars.sh --force
CC=icx CXX=icpx meson setup build-sycl core -Denable_sycl=true -Denable_cuda=false -Db_lto=false
ninja -C build-sycl && meson test -C build-sycl test_sycl_float_adm_parity

meson setup build-hip core -Denable_hip=true -Denable_hipcc=true -Denable_cuda=false -Denable_sycl=false -Db_lto=false
ninja -C build-hip && meson test -C build-hip test_hip_float_adm_parity

The default path does not move

The kernels keep the CPU's own p == 3 literal-cube fast path
(fadm_pnorm_term). Device powf(x, 3.0f) is not guaranteed to equal
x * x * x, and p = 3 is what every shipped model uses. The pre-existing
default-options test_cuda_float_adm_parity and test_cuda_adm_parity stay
green as the proof.

Why no test caught it

Every float_adm parity test ran with NULL options, where p = 3 is the
hardcoded exponent and bypass = 0 is the hardcoded behaviour.

The SYCL test also had to be widened from adm2 alone to all five ADM features:
on its fixture the aggregate never moves past the gate, and the variant passed
against the unfixed code
until adm_scale0 was added. The per-scale
sub-scores are where a kernel-vs-CPU divergence surfaces first — the CUDA test's
own comment says so, and the SYCL one had not followed it.

Scope

Metal gets all three fixes by construction — there is no Apple hardware here to
measure on, and that is stated in the ADR rather than implied. Its CM uniform's
two former padding slots now carry p_norm and bypass_cm, so FadmCsf and
FadmCsfHost keep the same size and alignment.

Deep-dive deliverables (ADR-0108)

Docs (rule 10)

docs/metrics/features.md: the adm_p_norm and
adm_skip_scale0 rows now state which backends honour them and what the
pre-fix behaviour was, and adm_bypass_cm gains a row it never had — including
that SYCL and HIP reject it.

Bug status (rule 13 / ADR-0165)

  • docs/state.md — closes T-GPU-FLOAT-ADM-OPTIONS-IGNORED-2026-09-07, and opens T-GAP-FLOAT-ADM-BYPASS-CM-SYCL-HIP-2026-09-07 for the option SYCL and HIP still do not declare.

🤖 Generated with Claude Code

@lusoris
lusoris force-pushed the fix/gpu-float-adm-options branch 5 times, most recently from d5edf21 to f79c01a Compare September 7, 2026 11:13
… float-ADM twins

Three VMAF_OPT_FLAG_FEATURE_PARAM options that the GPU float_adm twins declare
with the CPU's names, aliases, defaults and ranges -- and did not implement. The
framework accepts each one, range-checks it, and folds it into the derived
feature name, so a score filed under `adm2_apn_2` asserted a setting that was
never applied.

1. adm_p_norm has FOUR application points in adm_tools.c: the DLM numerator sum
   and the CSF denominator sum (each branching on p == 3 to a literal cube), the
   pooling root powf(accum, 1.0f / adm_p_norm), and get_noise_constant(), which
   is powf(w * h * weight, 1.0f / p). All four twins hardcoded the cube in the
   kernel and 1.0f / 3.0f in the host pooling, and used adm_p_norm for the AIM
   exponent alone -- producing a sum of cubes raised to 1/p, with adm2 and every
   adm_scaleN left at p = 3.

   Measured at apn=2.0 against the 1e-4 ADR-0214 gate:

       CUDA (RTX 4090)  adm2_apn_2        cpu=0.43097075 gpu=0.45416959  2.32e-02
       SYCL (Arc A380)  adm_scale0_apn_2  cpu=0.90092957 gpu=0.89784085  3.09e-03
       HIP  (gfx1030)   adm2_apn_2        cpu=0.99818595 gpu=0.99833104  1.45e-04

2. adm_bypass_cm was declared and stored by the CUDA and Metal twins and read by
   nothing: `grep` over each returns only the struct field and the option-table
   entry, and the 3x3 masking threshold was always subtracted. It now gates both
   the DLM and the AIM CM kernel, as adm.c passes it to both adm_cm() calls.
   SYCL and HIP do not declare it and reject it; that is unchanged.

3. Metal's adm_skip_scale0 zeroed only the REPORTED adm_scale0 sub-score while
   still folding the full scale-0 numerator and denominator into the pooled
   adm2 / aim. adm.c sets num_scale = 0 and den_scale = 1e-10 instead. No kernel
   change is needed: adm_dwt2_lo_s writes only band_a, which adm_dwt2 computes
   identically, so scales 1..3 are unaffected.

The kernels keep the CPU's own `p == 3` literal-cube fast path
(fadm_pnorm_term). Device powf(x, 3.0f) is not guaranteed to equal x * x * x,
and p = 3 is what every shipped model uses -- the pre-existing default-options
parity tests stay green as the proof.

No test could see any of this: every float_adm parity test ran with NULL
options, where p = 3 IS the hardcoded exponent and bypass = 0 IS the hardcoded
behaviour. Each backend now carries a variant per option it declares, reading
the ADR-1183-derived key. The SYCL test also had to be widened from adm2 alone
to all five ADM features -- on its fixture the aggregate does not move past the
gate, and only adm_scale0 exposes the defect.

Metal gets all three fixes by construction; there is no Apple hardware here to
measure on.

ADR-1220.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lusoris
lusoris force-pushed the fix/gpu-float-adm-options branch from f79c01a to a667649 Compare September 7, 2026 13:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant