ADR-1191: Integer ADM rejects CSF configurations its fixed-point storage cannot represent¶
- Status: Accepted
- Date: 2026-09-06
- Deciders: Lusoris
- Tags:
metrics,adm,cuda,sycl,hip,correctness
Context¶
The integer ADM pipeline stores the contrast-sensitivity (CSF) weight of each DWT scale as a fixed-point integer: uint16_t i_rfactor[3] at scale 0 (the 16-bit pipeline; horizontal/vertical bands scaled by 2^21, diagonal by 2^23) and uint32_t i_rfactor[3] at scales 1-3 (all bands scaled by 2^32). Those budgets were sized for the Watson97 CSF, whose weights sit around 1e-2.
The fork-added adm_csf_mode option exposes three more models. Two of them produce weights the storage cannot hold:
- Barten (
adm_csf_mode=1) at the defaultadm_csf_scale=1.0returns 1.2105 at scale 0 and 26.98 at scale 3. The scale-0 conversions are 2 538 595 and 10 154 382 -- 38x and 155x past 65 535 -- and every scale-1..3 conversion is past 2^32 as well. The narrowing casts wrapped silently. - The blended-CSF tables (
adm_csf_mode=2/=3) return-EINVALas a float (i.e.-22.0f) for any(adm_norm_view_dist, adm_ref_display_height)pair they do not tabulate. Converting a negative float to an unsigned integer type is undefined behaviour (C17 6.3.1.4p1).adm_ref_display_heightvalues such as 1200 clear the pre-existingnvd * rdh >= 3240guard and reach that cast.
Neither produced an error. Scoring the 576x324 Netflix fixture pair with --feature adm=adm_csf_mode=1 emitted integer_adm2_csf_1: null (NaN), integer_adm_scale0_csf_1: 0.030096 and integer_adm_scale2_csf_1: 0.00032 against the fork's own float reference of 0.9396 -- silently wrong numbers for a documented, range-validated public option. Tracked as T-UPSTREAM-1494-ADM-CSF-MODE-IRFACTOR-OVERFLOW-2026-09-03 (and, from the GPU-parity side, T-ADM-CSF-MODE-1-BARTEN-DEGENERATE-2026-09-05).
Widening the storage is not a local change: the scale-0 CSF output is written into int16_t bands, so a 70x larger weight overflows the next stage too, and the ADM_CM_ACCUM_ROUND shift contract and the AVX2 / AVX-512 32-bit-lane twins would all have to be re-derived. That work is out of scope here; what is in scope is that wrong numbers must not be emitted in the meantime.
Per SEI CERT INT31-C and FLP34-C, a conversion that cannot represent its operand is a defect, not a rounding mode; per NASA/JPL Power of 10 rule 7, the configuration has to be validated before it is used.
Decision¶
We will validate the configured CSF weights against the fixed-point storage that will hold them, and refuse the configuration with -EINVAL when they do not fit. core/src/feature/adm_csf_fixed_point.h owns the bounds, the tabulated-fast-path predicate, and the narrowing conversion; init() evaluates the verdict once per extractor context (the weights cannot change after option parsing) and extract() returns it, beside the pre-existing viewing-geometry guard. The CUDA, HIP and SYCL twins apply the identical bounds from the same header so their accept/reject set matches the CPU reference exactly.
Alternatives considered¶
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
Widen i_rfactor to uint32_t / int64_t products now | Actually supports Barten in the integer pipeline. | The scale-0 CSF output lands in int16_t bands, so widening the weight alone still overflows the next stage; ADM_CM_ACCUM_ROUND and the AVX2 / AVX-512 32-bit-lane twins need re-derivation, and there is no reference to validate the new Q format against. | Rejected for this change, not forever: it is the remaining half of the ledger row and stays open. Emitting -EINVAL is a correct intermediate state; emitting NaN is not. |
Narrow the adm_csf_mode range to 0,2,3 on the integer extractor | One-line change. | Removes a user surface, and mode 1 is usable with small adm_csf_scale values -- python/test/feature_extractor_test.py pins integer scores for adm_csf_mode=1 with adm_csf_scale=0.002893. A range narrowing would break those. | Rejected: the defect is representability of the weights, not of the mode. |
| Clamp the converted weight to the storage maximum | Always produces a number. | Saturating is as wrong as wrapping, and quieter: the score would look plausible while the CSF model was silently replaced. | Rejected. Fail loudly, never plausibly. |
Check per-frame in extract() without caching | No new state field. | adm_csf_factors() runs pow() / log10() for four scales; the answer is fixed after option parsing. | Rejected as needless per-frame work; the verdict is cached in AdmState. |
Fail in init() instead of extract() | Fails before buffers are allocated. | Relocates the established contract: core/test/test_adm_coverage.c::test_adm_invalid_view_dist_returns_einval pins that an unsupported ADM configuration initialises and then fails at extract(). | Rejected on the CPU twin to leave that contract (and its test) untouched. The GPU twins already reject the viewing geometry in their own init(), so they keep that idiom. |
Consequences¶
- Positive:
--feature adm=adm_csf_mode=1now prints which scale, band and weight overflowed and returns-EINVAL, instead of emitting NaN and three-orders-of-magnitude-wrong scale scores. The undefined negative-to- unsigned conversion on untabulated blend geometries is gone. CPU, CUDA, HIP and SYCL accept exactly the same configurations, which the ADR-1183 option / feature-name parity contract depends on. - Negative: a configuration that previously "ran" now errors. It never produced usable numbers, so no result is lost -- but any script that scraped
integer_adm*_csf_1values will now see a failed run instead of nonsense. Callers that want the Barten CSF at full scale must usefloat_adm, which has no fixed-point limit. - Neutral / follow-ups: the widening half of
T-UPSTREAM-1494-ADM-CSF-MODE-IRFACTOR-OVERFLOW-2026-09-03and all ofT-ADM-CSF-MODE-1-BARTEN-DEGENERATE-2026-09-05stay open. The AVX2 / AVX-512 ADM kernels keep their own copies of the scale-0 conversion; they are unreachable with an out-of-range weight now thatinit()gates the configuration, so they were left byte-identical to keep the SIMD bit-exactness story unchanged.
References¶
docs/state.md::T-UPSTREAM-1494-ADM-CSF-MODE-IRFACTOR-OVERFLOW-2026-09-03,T-ADM-CSF-MODE-1-BARTEN-DEGENERATE-2026-09-05.- Netflix/vmaf#1494 (the upstream report; its
nvd/rdhhalf does not affect this fork -- see the "Confirmed not-affected" row indocs/state.md). - ADR-1183 -- the option / feature-name parity contract the twins must not break.
- ADR-0141, ADR-0165.
- SEI CERT
INT31-C,FLP34-C; NASA/JPL Power of 10 rule 7. core/test/test_adm_csf_representable.c(the regression assertions).