Repository navigation
fix(adm): do not left-shift a negative masking threshold - #1662
Merged
Merged
Conversation
6 of 26 tasks
lusoris
force-pushed
the
port/upstream-1602-adm-cm-threshold-shift
branch
from
October 1, 2026 08:27
f243bbf to
5db4361
Compare
The scalar integer ADM contrast masking computed the scale-0 excess as abs(x) - ((int32_t)(thr) << shift_sub). The threshold's centre tap is narrowed to int16, so one large coefficient among small neighbours makes thr negative, and shifting a negative int is undefined in C. An ASan + UBSan build of master c7f2831 stops on 576x324 full-range noise with "integer_adm.c:1118:42: runtime error: left shift of negative value -826" under --cpumask 4294967295. Every aarch64 run takes this path. adm_cm_excess_s0() in adm_cm_accumulator.h computes the same expression modulo 2^32 in uint32_t, which is what the AVX2 and AVX-512 vector loops and the SYCL twin already do, and adm_cm_accum_round() calls it. Scores are unchanged: the three Netflix reference pairs, the 16-bit src01 pair, 576x324 noise, stripes and checkerboards give JSON identical to master at --precision max under the default dispatch, AVX2 alone and scalar. test_integer_adm_cm_threshold pins the helper for positive, negative and wrapping operands, and scores a 64x64 picture that reaches a threshold of -15176 on every dispatch level. The sanitizer build stops it on the old expression. Found while re-checking Netflix/vmaf PR #1602 against master. Two rows are opened in docs/state.md, both deferred. The scalar tail loops of adm_avx2.c and adm_avx512.c carry the same shift, and the commit hook refuses a change to those files until their oversized functions are split (T-ADM-CM-X86-TAIL-NEGATIVE-THRESHOLD-SHIFT-2026-10-01). Upstream PR #1602's second revision removes the int16 narrowing instead, which changes scores and needs a decision (T-ADM-CM-CENTRE-TAP-WRAP-ABOVE-ONE-2026-10-01).
lusoris
force-pushed
the
port/upstream-1602-adm-cm-threshold-shift
branch
from
October 1, 2026 08:53
5db4361 to
98643d1
Compare
lusoris
added a commit
that referenced
this pull request
Oct 1, 2026
Each of the fork's fifteen open Netflix/vmaf pull requests (#1588 to against the fork's code, with ASan + UBSan builds where the report is about memory or undefined behaviour. The fork needs none of the six commits. It carries the fix of every pull request except the second revision of #1602, which is a score change left to the maintainer. Three gaps found on the way are in their own pull requests: #1662 (a negative threshold shift in the scalar adm_cm), #1663 and #1664 (regression tests for #1590 and #1604). docs/state.md gains seventeen rows under "Confirmed not-affected", each with the fork file and function, the test, and what was run; the #1604 row is corrected (the tool refuses odd 4:2:0 dimensions for raw input only). docs/rebase-notes.md says what a sync can skip and what it must keep. docs/development/known-upstream-bugs.md lists all fifteen pull requests with the fork's status and records that they are no longer updated while upstream does not act on them.
lusoris
added a commit
that referenced
this pull request
Oct 1, 2026
* docs(state): record the 2026-10-01 upstream reconciliation Each of the fork's fifteen open Netflix/vmaf pull requests (#1588 to against the fork's code, with ASan + UBSan builds where the report is about memory or undefined behaviour. The fork needs none of the six commits. It carries the fix of every pull request except the second revision of #1602, which is a score change left to the maintainer. Three gaps found on the way are in their own pull requests: #1662 (a negative threshold shift in the scalar adm_cm), #1663 and #1664 (regression tests for #1590 and #1604). docs/state.md gains seventeen rows under "Confirmed not-affected", each with the fork file and function, the test, and what was run; the #1604 row is corrected (the tool refuses odd 4:2:0 dimensions for raw input only). docs/rebase-notes.md says what a sync can skip and what it must keep. docs/development/known-upstream-bugs.md lists all fifteen pull requests with the fork's status and records that they are no longer updated while upstream does not act on them. * docs(upstream): keep the reconciliation page to the technical status
17 tasks done
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
The scalar integer ADM contrast masking left-shifted a negative threshold, which is undefined behaviour in C. A sanitizer build of master stops on it; this PR computes the expression modulo 2^32 instead, as the AVX2 / AVX-512 vector loops and the SYCL twin already do. Scores do not change.
adm_cm_accum_round()incore/src/feature/integer_adm.ccomputed the scale-0 masking excess asabs(x) - ((int32_t)(thr) << shift_sub). The threshold's centre tap is narrowed to int16, so one large coefficient among small neighbours makesthrnegative. The scalar path is the one every aarch64 run takes.The new
adm_cm_excess_s0()incore/src/feature/adm_cm_accumulator.hdoes the subtraction inuint32_t, andadm_cm_accum_round()calls it.Found while checking whether the fork already carries the fix of our upstream Netflix/vmaf PR #1602. It carries the first revision of that PR (SIMD follows the scalar int16 wrap). This defect is what remains of it under the fork's semantics.
Type
feat— new featurefix— bug fixperf— performance improvementrefactor— no behavior changedocs— documentation onlytest— test-onlybuild/ci— tooling / infraport— cherry-pick from upstream Netflix/vmafsycl/cuda/simd— backend-specificChecklist
make format && make lintis green locally. Not run in full. Run instead:clang-format --dry-run -Werroron the touched C files (clean),scripts/ci/tidy-ratchet.py --lane cpu --onlyoninteger_adm.cand the new test (0 warnings), and the pre-commit hook set (passed)./cross-backend-diffand the worst ULP is ≤ 2. No SIMD or GPU code path is touched..c/.cpp/.cu/.h/.hpp, it has the appropriate license header (seeCONTRIBUTING.md).!orBREAKING CHANGE:and the migration path is documented below.docs/adr/_index_fragments/<NNNN-slug>.mdand the slug is appended todocs/adr/_index_fragments/_order.txt.Bug-status hygiene (ADR-0165)
docs/state.mdupdated in this PR:T-ADM-CM-NEGATIVE-THRESHOLD-SHIFT-2026-10-01under Recently closed, and two rows under Open bugs, both explicitly deferred and added to the disposition table:T-ADM-CM-X86-TAIL-NEGATIVE-THRESHOLD-SHIFT-2026-10-01andT-ADM-CM-CENTRE-TAP-WRAP-ABOVE-ONE-2026-10-01.Netflix golden-data gate (ADR-0024)
assertAlmostEqual(...)score in the Netflix golden Python tests.Cross-backend numerical results
The change is bit-exact. 24 comparisons of the
vmafJSON at--precision max, masterc7f28317fagainst this branch, all identical:--cpumask 48(AVX2)--cpumask 4294967295(scalar)src01_hrc00/src01_hrc01576x324, default model, 48 framessrc0116-bit pair,--feature adm--feature adm--feature admThe 64x64 picture of the new test also scores identically on master and on this branch at all three levels.
A CUDA build (
-Denable_cuda=true, gcc, nvcc) and its fast suite (249 passed, 0 failed, 1 skipped) were run because the CUDA and HIP kernels include the header that gains the helper; the kernels themselves are unchanged.Deep-dive deliverables (ADR-0108)
docs/state.mdrows.AGENTS.mdinvariant note — added tocore/src/feature/AGENTS.md.changelog.d/fixed/adm-cm-negative-threshold-shift.md.docs/rebase-notes.md.Reproducer
On master the same report comes from
vmaf --feature adm --no_prediction --cpumask 4294967295on 576x324 full-range noise (left shift of negative value -826). Random noise reaches it in 1 of 40 seeds at 96x64 and 3 of 40 at 176x144; the seeds oftest_integer_adm_simd_noisedo not, which is why the sanitizer lane never reported it.Known follow-ups
T-ADM-CM-X86-TAIL-NEGATIVE-THRESHOLD-SHIFT-2026-10-01). The macroADM_CM_ACCUM_ROUNDinadm_avx2.candadm_avx512.ccarries the same shift for the edge columns and the columns left over after the vector loop. A 24x24 grey frame with one 4x2 patch255 0 0 0at x=3, y=3 stops a sanitizer build atadm_avx512.c:2291and, with--cpumask 48, atadm_avx2.c:2687. The fix is one line per file, but the commit hook's touched-file rule (praetorctl audit, HISS-04) refuses any change to those files while they carry 22 baselined oversized functions, and-touched-debt-delta-reasonis out of bounds (ADR-1298). The row names the trigger: apply the helper when the HISS-04 split of those files lands.T-ADM-CM-CENTRE-TAP-WRAP-ABOVE-ONE-2026-10-01). The wrap that makes the threshold negative also adds masked contrast that is not in the picture: the test's picture scoresinteger_adm_scale01.0829 andinteger_adm21.0355 wherefloat_admgives exactly 1. The second revision of upstream PR fix(release): build the native Linux bundle on the Debian 13 release track #1602 removes the narrowing. Doing that here changes the scalar reference away from upstream master on such content, across the CPU paths and four GPU twins, so it needs a maintainer decision and an ADR.thr << shiftin device code and are unchanged; no device run was made for them.