Repository navigation
fix(cuda): reset integer_vif_cuda's accumulators on the picture stream - #1750
Merged
Merged
Conversation
…later, larger context scores its first frame (#1749) * fix(hip): queue each accumulator clear after the frame's upload so a later, larger context scores its first frame float_moment_hip and vif_hip cleared their device accumulators with hipMemsetAsync ahead of the frame's plane upload. On a gfx1036 a clear queued there has no effect in the first context of a process that needs larger planes than the contexts before it, and the kernels add onto the sums the earlier context left in recycled device memory (ADR-1427). adm_hip had the same order and failed the run; ADR-1423 moved its clear. One frame in a 640x360 context, then one in a 3840x2160 context, each twin in its own process, on master e2954fc, every run: float_moment_ref1st 130.53 where the CPU has 127.00, vif_hip scale 0 to 2 at 0.6748 / 0.8040 / 0.8749 where the CPU has 0.6934 / 0.8260 / 0.8988. The vmaf tool has one context per process, where fresh device memory is zero, and never showed it. float_moment_hip, vif_hip and float_psnr_hip now upload first and queue the clear directly ahead of their kernels, the order psnr_hip, float_vif_hip, float_adm_hip and cambi_hip already had. float_psnr_hip's scores were right (its kernel writes every partial); it is reordered so that one rule holds for every twin. After the change float_moment_hip and adm_hip equal the CPU on that frame and vif_hip is within 1.2e-7. What is measured about the cause: with the clear after the upload the frame is correct in 23 of 23 runs, also with an allocation, a second upload, an upload of a new host buffer or an event wait between the clear and the kernel; a hipStreamSynchronize() after a clear ahead of the upload cures it; a hipMemset at allocation does not, since on device memory that call is queued on the null stream without waiting (hipamd ihipMemset(), rocm-7.2.4); blocking and non-blocking streams both fail. Why the clear is lost is the runtime's or the driver's and not established. Tests: test_hip_first_frame_clear_<twin>, one binary per twin for fourteen twins, because only the first larger context of a process is exposed; three fail on master and all pass here, three of three runs each. test_hip_clear_after_upload_contract.py reads every HIP source and reports a function that clears and uploads afterwards, helpers included; it reports the four pre-fix functions on master and has six planted regressions. Time per frame is unchanged within the samples: float_moment_hip 1.95 and 1.97 ms at 1080p, 7.22 and 7.14 at 4K; vif_hip 46.86 and 47.84, 207.30 and 202.10. docs/state.md: T-HIP-FIRST-FRAME-ASYNC-CLEAR-OTHER-TWINS-2026-10-01 closed.
…the twin is bit-identical (#1736) * fix(sycl): compute float_vif in the CPU's arithmetic without fp64 so the twin is bit-identical float_vif_sycl matched the CPU extractor on no frame of the Netflix pair and was up to 3.8e-5 from it (the SYCL part of T-GPU-FLOAT-VIF-CPU-ARITHMETIC-2026-10-01, which ADR-1412 opened when it fixed the CUDA twin). It had the same four differences. Each put back alone into the corrected twin, on an Arc A380, largest difference on the Netflix pair and at 3840x2160: - a table of Gaussian taps the CPU dropped in #758, when float_vif.c began to compute its filters with vif_get_filter(): 3.83e-5, 7.5e-6; - the device log2, where VIF_OPT_FAST_LOG2 makes the CPU's log2f the polynomial log2f_approx(): 2.1e-7, 1.2e-7; - vif_sigma_nsq as a float, where vif_pixel_statistic_s() keeps it in double: 1.3e-7, 7.5e-8; - sub-group and block reductions, where vif_statistic_s() adds a row into one float and the rows into another: 5.0e-7, 5.9e-6. ADR-1422: - float_vif_sycl.cpp takes each scale's taps from vif_get_filter() and passes them to the kernels by value. - sycl/sycl_float_vif_math.h holds vif_pixel_statistic_s() and log2f_approx() operation for operation. A SYCL kernel has no fp64 type (ADR-0220), so the reference's two fp64 expressions are evaluated as exact fp32 pairs and, within 2^-12 of an fp32 step of a rounding boundary (one sample in 1650), by replaying the fp64 add, divide and conversion in 64-bit integers. The pair alone rounds wrongly on 85 of 8.4e9 random quotients; the selected value on none. - Per scale the filter kernel stores the variances, a statistic kernel turns them into the two terms, and a row kernel adds each row in one work-item; the host adds the rows in fp32. The statistic has its own kernel because it spilled registers inside the filter kernel, and no kernel of the twin uses scratch memory (ADR-1395). - The twin gains the CPU's vif_scale1..3_min_val floors. - EXACT_TWINS lists float_vif / sycl: the gate cell is an equality. Measured on the A380 at --precision max, identical frames on every scale before and after: Netflix 576x324 0/48 and 48/48, checkerboard 1 px 0/3 and 3/3, 10 px 1/3 and 3/3, BBB 3840x2160 0/20 and 200/200; also identical at 10, 12 and 16 bits, with debug=true and with non-default options. A 3840x2160 frame takes 23.95 ms, 20.54 before (T-SYCL-FLOAT-VIF-EXACT-THROUGHPUT-2026-10-01); 576x324: 0.93 and 0.73. The HIP and Metal twins still hold the tap table. Tests: test_sycl_float_vif_parity asserts equality over seven cases (float_vif_twin_parity.h) and fails on the old twin; test_sycl_float_vif_math compares the header with vif_statistic_s() on the host and in a device kernel, and with the compiler's fp64 on twelve operands where the pair alone is wrong; test_sycl_float_vif_exact_contract.py pins the design. * docs: regenerate the indexes and the citation map after rebasing
#1750) * fix(cuda): reset integer_vif_cuda's accumulators on the picture stream The accumulator reset was queued on the private stream while the scale 0 kernels that add into it run on the picture stream, with nothing ordering the two. Under contention from other instances on one device a late reset erased the first adds: four instances on one CUcontext returned wrong vif scales in every run (Netflix/vmaf#1305). The reset now takes the picture stream. test_cuda_multi_instance runs four instances on one context and compares every vif scale, adm2 and motion2 with a single instance; it fails on every run without the fix.
5 of 7 tasks
lusoris
force-pushed
the
fix/cuda-vif-accum-reset-order
branch
from
October 1, 2026 19:22
30c5786 to
80c5a03
Compare
This was referenced Oct 1, 2026
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
Four
vmafCUDA instances on one device return wrongvifscales in every run; one instance never does.integer_vif_cudacleared its accumulators on a stream the kernels that add into them did not wait for. The clear now runs on the picture stream.Closes
T-UPSTREAM-1305-CUDA-VIF-ACCUM-STREAM-2026-10-01(found and closed here). Netflix/vmaf#1305 reports the same defect on upstream (integer_vif_cuda.c:458); upstream's patch is the same one-argument change. RC3, correctness (ADR-1421).Cause
vif_submit_plane()queuedcuMemsetD8Async(accum_data, ..., s->str)and then launched the scale 0 kernels, whichatomicAddintoaccum_data, on the picture stream. A stream orders only its own work. With other instances loading the device, the clear can run after the first atomic adds and erase them.I checked every other accumulator reset of the CUDA extractors for the same pattern (
rg "cuMemsetD8Async"overcore/src):integer_motion_sad_cuda.c(picture stream, ADR-0358),integer_adm_cuda.c(every kernel ons->str),float_adm_cuda.c,float_psnr_cuda.c,integer_psnr_cuda.c,integer_cambi_cuda.candkernel_template.hclear on the stream of their kernels. The HIP twins queue clear and kernels on one stream (read from the source; no HIP multi-instance run).Output on an RTX 4090
Upstream's harness
repro1305(N instances, one thread each, one sharedCUcontext, Netflix 576x324 pair, 48 frames, thevmaf_v0.6.1features, every score of every frame compared with a flushed single-instance run):adm228 to 35,motion263 to 85, eachvifscale 56 to 71 (of 192)adm21 to 7,motion21 to 20, eachvifscale 2 to 14After the fix: 0 wrong values in 105 runs of the seven configurations. A scratch run of four instances on every registered CUDA extractor (
vif,adm,motion,motion_v2,float_adm,float_motion,float_psnr,float_ssim,float_vif,float_ms_ssim,float_moment,psnr,psnr_hvs,ssim,ciede,cambi,speed_chroma,speed_temporal,ssimulacra2; 24 frames; the CSV output of each instance compared byte for byte with a single instance's) gives identical output for all.Tests
test_cuda_multi_instance(new,fast+gpu, skips without a device): four threads, each with its ownVmafContextandVmafCudaStateon oneCUcontext, score 48 frames with the features ofvmaf_v0.6.1; everyvifscale,adm2andmotion2is compared with==against a single instance. Without the fix it fails on every run (3 of 3: an instance's frame turns non-finite and the call returns-EINVAL); with it 5 of 5 pass.Type
fix— bug fixDeep-dive deliverables (ADR-0108)
AGENTS.mdinvariant note —core/src/feature/cuda/AGENTS.md, "Accumulator reset runs on the stream of the kernels that add into it".changelog.d/fixed/cuda-vif-accum-reset-order.md.docs/rebase-notes.md, "integer_vif_cudaresets its accumulators on the picture stream".Reproducer
On a CUDA host, in a build with
-Denable_cuda=true:flock ~/.cache/vmafx-locks/cuda-4090.lock timeout 120 build/test/test_cuda_multi_instanceIt prints
passwith the fix andan instance failedwithout it. Upstream's harness is/home/kilian/.cache/vmafx-upstream-rebase/evidence/c1305/repro1305.c.Notes
core/test/meson.build(one block, next totest_cuda_vif_min_dim); no open PR touchesinteger_vif_cuda.c.