Repository navigation
Conversation
Author
|
Correction on my side: this is the same fix as #1605, which I closed on 2026-09-21 after @anematode pointed out that #1584 removes this code path (it replaces the per-lane The overlap, so nobody reviews this twice:
Either one resolves the UBSan reports in the description. If #1584 lands, close this. I am leaving it open only as the smaller change in case #1584 is not ready. |
lusoris
force-pushed
the
fix/adm-avx2-get-best15-guard
branch
from
October 1, 2026 18:11
3badd69 to
9e8e5e2
Compare
lusoris
force-pushed
the
fix/adm-avx2-get-best15-guard
branch
2 times, most recently
from
October 2, 2026 18:43
f15acf8 to
7dca576
Compare
The scalar and the AVX-512 adm_decouple_s123() call get_best15_from32()
only for values of 32768 and above:
uint16_t kh_msb = (abs_oh < (32768) ? abs_oh
: get_best15_from32(abs_oh, &kh_shift));
adm_decouple_s123_avx2() cannot branch per lane, so it runs the function
on all eight lanes and selects afterwards with
_mm256_cmpgt_epi32(const_32768_epi32, abs_oh_epi32). The lanes below
32768 are discarded, but they are evaluated first, and there the shift
count 17 - __builtin_clz(temp) is zero or negative, and for temp of 0
__builtin_clz has no defined result at all. UBSan on the src01 clip at
576x324 with --cpumask 16 (which keeps AVX-512 off, so that the AVX2 kernel
runs on an AVX-512 machine):
adm_avx2.c:1348:13: runtime error: passing zero to __builtin_clz(), which is not a valid argument
adm_avx2.c:1350:23: runtime error: shift exponent -1 is negative
#0 get_best15_from32 ../libvmaf/src/feature/x86/adm_avx2.c:1350
Netflix#1 adm_decouple_s123_avx2 ../libvmaf/src/feature/x86/adm_avx2.c:1543
Netflix#2 integer_compute_adm ../libvmaf/src/feature/integer_adm.c:2954
adm_avx2.c:1350:36: runtime error: shift exponent -4 is negative
Return, for those values, what the select picks for them: the value
itself and a shift of 0. blend(a, b, mask) is (mask & a) | (~mask & b),
the mask is set where abs_oh is below 32768, and the two calls there are
blend(abs_oh_epi32, tmp_kh_msb_epi32, mask) and
blend(_mm256_setzero_si256(), kh_shift_epi32, mask), so the guarded
lanes carry the same bits as before.
No score changes. The three reference pairs give identical JSON before
and after under the default dispatch, --cpumask 16 and --cpumask -1, and
identical %.17g values for vmaf, adm2, aim, adm3, the four ADM scale
scores, vif scale 0 and motion2 at --cpumask 16, --cpumask 0 and
--cpumask -1.
lusoris
force-pushed
the
fix/adm-avx2-get-best15-guard
branch
from
October 8, 2026 17:15
7dca576 to
583165f
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.
adm_decouple_s123_avx2()callsget_best15_from32()on every lane, including lanes below 2^15 whose result it throws away. For those lanes the function shifts by a negative count, and for a value of 0 it calls__builtin_clz(0). UBSan reports both. Scores do not change.Cause
The scalar
adm_decouple_s123()and the AVX-512 kernel call the function only for values of 32768 and above:The AVX2 kernel cannot branch per lane. It extracts all eight lanes, runs
get_best15_from32()on each, and selects afterwards with_mm256_cmpgt_epi32(const_32768_epi32, abs_oh_epi32)(kh,kvandkdalike). Below 32768 the shift count17 - __builtin_clz(temp)is zero or negative, so(1 << (k - 1))and>> kare undefined, andtemp == 0has no defined__builtin_clz. The 24 per-lane calls inadm_decouple_s123_avx2()are the AVX2 callers that see such values; the three calls in the scalar tail of the same function (about line 1883) already guard with< 32768.Reproducer
Master
6ec23e8f2(reproduced again on9e48141b: 3 UBSan reports on master, 0 with this patch), ASan/UBSan build.--cpumask 16keeps AVX-512 off so that the AVX2 kernel runs on an AVX-512 machine; UBSan reports each site once:meson setup build-san libvmaf --buildtype debug -Db_sanitize=address,undefined -Db_lto=false ninja -C build-san build-san/tools/vmaf -r src01_hrc00_576x324.yuv -d src01_hrc01_576x324.yuv -w 576 -h 324 -p 420 -b 8 \ --feature adm --cpumask 16 --threads 1 -q --json -o out.jsonOn master:
On this branch the same command prints no runtime error. This is the report that #1599 and #1601 list as present before and after their change; it also appears as the remaining report in the frame-size sweep of #1599.
Fix
get_best15_from32()inadm_avx2.creturns, fortemp < 32768, the value itself and a shift of 0:That is what the caller's select picks for those lanes:
blend(abs_oh_epi32, tmp_kh_msb_epi32, mask)takesabs_ohwhere the mask is set (abs_oh < 32768), andblend(_mm256_setzero_si256(), kh_shift_epi32, mask)takes a shift of 0. The discarded lanes carry the same bits as before, so no result moves. Values of 32768 and above take the old path.Tests
No new test. The defect is undefined behaviour whose result is discarded: on x86 the negative shift and the
clz(0)produce a value that the select throws away, so no output differs and a test cannot fail on master without a sanitizer.check_adm'sadm_decouple_s123test already covers this kernel for equality with the scalar kernel and passes on master and here; under UBSan it reports the same three errors on master (checkasm --test=adm --function='adm_decouple_s123*'in the ASan/UBSan build: 3 reports on master, 0 here). The UBSan reproducer above is the regression check. (The fullcheckasm --test=admaborts under ASan on master and here with aheap-buffer-overflowinadm_dwt2_16(called fromcheck_adm.c:382), unrelated to this change, so the filter is needed; under ASan the filtered run also reports "missing vzeroupper" for both AVX2 and AVX-512 on master and here, which the release build does not.)Validation
Rebased on master 9e48141 (2026-10-02). x86-64 Linux, GCC 16.2.1, on master
9e48141b, Ryzen 9 9950X3D,-Denable_float=true. The%.17gcomparison and thecheckasm --benchcycle counts below were measured on 6ec23e8 and not repeated; the UBSan reproducer, the test counts,checkasm --test=admand the reference-pair comparison were re-run on 9e48141.meson test(-Denable_float=true -Denable_checkasm=true): 25/25 on master, 25/25 here.-Db_sanitize=address,undefined -Db_lto=falsebuild: 22 pass and 3 fail on master, 22 pass and 3 fail here;test_predictandtest_pic_preallocationfail on LeakSanitizer reports andcheckasmaborts on aheap-buffer-overflowinadm_dwt2_16(integer_adm.c:2603on master), on both, and this change does not touch them.checkasm --test=adm(release,-Denable_checkasm=true): all 265 tests pass on master and here..engagement/golden_compare.py): identical per-frame and pooled output between master and this branch, default dispatch and--cpumask -1. No golden assertion changes.%.17gcomparison through the C API at--cpumask 16,0and-1on the three pairs:vmaf(vmaf_v0.6.1),adm2,aim,adm3, the four ADM scale scores, vif scale 0 andmotion2, 480 values forsrc01and 30 for each checkerboard pair per mask, all bit-identical between master and this branch (9 of 9 pair/mask combinations).checkasm --benchofadm_decouple_s123, cycles per call, median of 8 alternating runs pinned to one core. The AVX2 kernel gets faster, because the guarded lanes skip theclzand shifts: 16x16 2237 to 1929, 32x20 5720 to 4723, 33x21 6398 to 5582, 64x48 25388 to 22357, 65x49 26935 to 22711 (11.9% to 17.4% fewer cycles). The AVX-512 kernel is unchanged within noise (-1.0% to +2.3%). The unchanged scalar kernel moves by -0.9% to +2.0% between the two builds, which is the noise level. Three bench runs stalled (killed after 90 s or by hand) and were dropped; the numbers above come from the 8 completed runs per build.Overlap:
git merge-treeof this branch against the branches of #1599, #1600, #1601, #1602, #1603, #1631, #1633 and #1634 reports no conflict with any of them (#1599, #1600, #1601, #1602 and #1633 touchadm_avx2.corcheck_adm.c; their hunks do not meet this one).The workflow run on this PR needs a maintainer's approval.