Skip to content

fix(speed): size speed_temporal and speed_chroma frame buffers correctly - #1643

Merged
lusoris merged 1 commit into
masterfrom
fix/speed-temporal-prescale-overflow
Sep 30, 2026
Merged

lusoris merged 1 commit into
masterfrom
fix/speed-temporal-prescale-overflow

Conversation

@lusoris

@lusoris lusoris commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Allocates speed_temporal frame buffers using alloc_height so that speed_prescale > 1 (upsampling) does not overrun the heap under AddressSanitizer or corrupt memory in release builds (Netflix/vmaf#1626, Netflix/vmaf#1627). In addition, speed_prescale_resamples() resamples when lround(dim * prescale) alters plane extents even if fabs(prescale - 1.0) < 1e-3.

Derives speed_chroma frame buffer extents on CPU, CUDA, and HIP using vmaf_chroma_extent() ceiling division instead of integer floor division, preventing picture_copy() from overflowing buffers on odd dimensions in subsampled formats (4:2:0, 4:2:2). Also bounds speed_temporal_cuda solve launch block dimensions to 256 threads (8 warps) and scales blocks to avoid CUDA launch errors for large systems.

Type

  • feat — new feature
  • fix — bug fix
  • perf — performance improvement
  • refactor — no behavior change
  • docs — documentation only
  • test — test-only
  • build / ci — tooling / infra
  • port — cherry-pick from upstream Netflix/vmaf
  • sycl / cuda / simd — backend-specific

Checklist

  • Commits follow Conventional Commits (the commit-msg hook enforces this).
  • make format && make lint is green locally.
  • Unit tests pass: python3 scripts/ci/run_meson_test.py -- -C build.
  • If I touched any SIMD/GPU code path, I ran /cross-backend-diff and the worst ULP is ≤ 2.
  • If I touched a feature extractor with SIMD/GPU twins, I either updated every twin or listed the gap under "Known follow-ups" below.
  • If I added a new .c / .cpp / .cu / .h / .hpp, it has the appropriate license header (see CONTRIBUTING.md).
  • If this is a breaking change, the commit message uses ! or BREAKING CHANGE: and the migration path is documented below.
  • If this PR adds an ADR, the ADR row lives in docs/adr/_index_fragments/<NNNN-slug>.md and the slug is appended to docs/adr/_index_fragments/_order.txt — do not edit docs/adr/README.md directly (regenerated by scripts/docs/concat-adr-index.sh; see ADR-0221).

Bug-status hygiene (ADR-0165)

  • docs/state.md updated in this PR with a row in the appropriate section (Open / Recently closed / Confirmed not-affected / Deferred), OR no state delta: REASON.

Netflix golden-data gate (ADR-0024)

  • I did not modify any assertAlmostEqual(...) score in the Netflix golden Python tests.
  • If I believe a golden value must change, I have explained why below AND pinged @lusoris for a CODEOWNERS exception.

Cross-backend numerical results

Scores at speed_prescale <= 1.0 remain bit-identical to master. GPU twins (CUDA on RTX 4090, SYCL on Arc A380) verified at prescale 1.0, 1.5, 2.0 and odd dimensions without overflow or NaN/Inf scores.

Deep-dive deliverables (ADR-0108)

  • Research digest — no digest needed: bugfix matching upstream Netflix/vmaf PR perf(sycl): run ssimulacra2 on the device and wait once per frame in ms_ssim #1627 and geometry ceiling fixes.
  • Decision matrix — no alternatives: only-one-way fix.
  • AGENTS.md invariant note — added to core/src/feature/AGENTS.md.
  • Reproducer / smoke-test command — pasted below under "Reproducer".
  • CHANGELOG fragment — added changelog.d/fixed/speed-temporal-prescale-overflow.md and changelog.d/fixed/speed-chroma-odd-size-overflow.md.
  • Rebase note — added entry to docs/rebase-notes.md.

Reproducer

./build-asan/tools/vmaf -r python/test/resource/yuv/src01_hrc00_576x324.yuv -d python/test/resource/yuv/src01_hrc01_576x324.yuv -w 576 -h 324 -p 420 -b 8 --feature speed_temporal=speed_prescale=1.5
./build-asan/test/test_speed_frame_buffers

Known follow-ups

None.

@github-actions github-actions Bot added the type:bug Something isn't working label Sep 30, 2026
Comment thread core/test/test_speed_frame_buffers.c Fixed
mu_assert("extraction failed", extract_err == 0);
for (unsigned i = 0; i < FB_FRAMES; i++) {
mu_assert("same scaled size, different score: the resample was skipped",
score_a[i] == score_b[i]);
Allocates speed_temporal frame buffers using alloc_height so that
speed_prescale > 1 (which resamples to a taller plane) does not overrun
the heap under AddressSanitizer or corrupt memory in release builds
(Netflix/vmaf#1626, Netflix/vmaf#1627). In addition, speed_prescale_resamples()
detects resampling when lround(dim * prescale) changes plane dimensions.

Derives speed_chroma buffer extents using vmaf_chroma_extent() ceiling
division on CPU, CUDA, and HIP instead of integer floor division,
preventing picture_copy() from overflowing buffers on odd dimensions in
subsampled formats (4:2:0, 4:2:2).

Fixes launch dimensions in speed_temporal_cuda solve kernel to cap
threads per block to 256 (8 warps) and scale blocks, avoiding CUDA
invalid value errors when u_nb > 256.

Adds test_speed_frame_buffers covering prescale factors 1.0, 1.5, 2.0,
4.0 and odd dimensions under ASan.
@lusoris
lusoris force-pushed the fix/speed-temporal-prescale-overflow branch from e7f65f9 to 9a20d77 Compare September 30, 2026 23:57
@lusoris
lusoris merged commit c1a2b19 into master Sep 30, 2026
74 of 76 checks passed
@lusoris
lusoris deleted the fix/speed-temporal-prescale-overflow branch September 30, 2026 23:57
lusoris added a commit that referenced this pull request Oct 1, 2026
…es()

#1643 replaced speed_internal.c's SI_ALMOST_EQUAL with the shared speed_prescale_resamples() helper. The device geometry spelled the same rule out by hand with the removed macro; it now calls the helper, so the CPU and the device twins decide the resample from one function.
lusoris added a commit that referenced this pull request Oct 1, 2026
* perf(cuda): run cambi and SpEED entirely on the device

cambi_cuda, speed_chroma_cuda and speed_temporal_cuda no longer hand work
back to the host inside a frame. Each frame reads back one small result
block (88 bytes for cambi, 40 for SpEED) and waits once, in collect(); the
twins read the planes the CUDA engine already uploaded and upload nothing
of their own.

cambi_cuda used to download the distorted picture, preprocess it on the
host and read the image and mask back at every scale for host c-values and
top-K pooling. It now runs the ADR-1357 design on CUDA: twelve kernels for
preprocessing, the spatial mask, decimation, the mode filter, the
column-histogram c-values and an exact 128-bit top-K sum. Scores equal the
CPU's to the last bit whenever cambi.c's own double sum is exact. The twin
also gains cambi.c's guard against windows above 65 x 65.

The SpEED twins run the ADR-1358 chain, 25x25 eigenvalues and QR
included, in nine kernels shared through speed_cuda_pipeline.c. Every
rounding the CPU performs is spelled with a round-to-nearest intrinsic and
the fatbin builds with --fmad=false, so the scores equal a CPU build that
rounds log2f correctly and does not fuse multiply-adds.

cambi.c exports the host helpers both device twins need, and
speed_internal_gpu_configure() sets up the SYCL and CUDA SpEED pipelines;
the SYCL twins drop their private copies. CPU output is unchanged.

There is no NVIDIA device on this host. The kernels were built for sm_80
to sm_120 and checked frame by frame through a host emulation of the CUDA
driver; the two state rows stay open with the commands to verify and time
the port on ryzen-4090-arc. Three rows are opened for what the checks
found: the lanczos4 prescale drift of both device twins, the CPU
speed_temporal buffer overflow with speed_prescale above 1, and the dev
image's -march=native FMA drift of the CPU SpEED scores.

ADR-1379, ADR-1380, Research-1379.

* fix(ci): retire the issue-provenance anchors of the removed CAMBI host code

The Release Script Contract failed on #1639: check-issue-reference-provenance
pins five historical blocks that cite lusoris/vmaf#857 and #870, four of them
in integer_cambi_cuda.c (the dispatch helpers and the host download step the
device-resident rewrite removed) and one in docs/metrics/cambi.md. The four
code contracts go with the code they described. The CAMBI page keeps its
history as an "Implementation note (before ADR-1379)" block that still names
lusoris/vmaf#870, so that contract stays.

* fix(speed): correct 48 log2 hard cases in CUDA and SYCL twins

The fp32-pair log2 evaluation misrounds 48 positive finite floats whose
exact log2 falls closer than 2^-45 to a rounding boundary. Both device
twins now look the input up in a shared table (speed_log2_hard_cases.h)
and return the correctly rounded result. The common path costs one
fraction-field compare.

The CUDA cambi twin also drops a score < 0.0 clamp that the CPU does
not perform.

Contract tests verify the table entries against quad-precision log2 and
detect removal of the correction call in both twins.

* fix(ci): suppress modernize-use-using in speed_gpu_common.h and update hardware evidence

* fix(tidy): name the includers and cite ADR-1138 in the speed_gpu_common.h NOLINT bracket

The bracket suppresses modernize-use-using for the header's typedef
structs, which clang-tidy parses as C++ under core/tools/vmaf.cpp
(through feature_dimensions.h and speed_internal.h) and under the SYCL
SpEED translation units; C cannot spell the `using` alias it proposes.
The CPU lane of the Tidy Ratchet had counted six findings there (0 -> 6).
The comment now names the C and C++ includers and cites ADR-1138, which
prescribes this file-scoped shape for C code parsed as C++. A CPU-lane
ratchet run scoped to vmaf.cpp measures 0 findings in the header. The
source ADR citation registry follows the new citation.

* test(cuda): cover the speed_temporal_cuda solve launch at 1920x1080

test_cuda_speed_temporal_parity_1080p builds the temporal parity test at
1920x1080, where the luma plane has 312 SpEED blocks. The host-split
twin on master launched its solve kernel with ((blocks + 7) / 8) * 32
threads per block, 1248 > 1024, so every frame at 1080p and above failed
with CUDA_ERROR_INVALID_VALUE (speed_temporal_cuda.c:425 at 10f27ef).
The 768x432 and 960x540 fixtures stay under 256 blocks and never reached
it. On the RTX 4090 the test fails against 10f27ef with that error and
passes with the ADR-1380 twin.

* docs: record the RTX 4090 verification of the CUDA CAMBI and SpEED twins

Measured on ryzen-4090-arc (RTX 4090, icx release build without
-march=native; before = origin/master 10f27ef built the same way):
- every per-frame cambi, speed_chroma_u/v/uv and speed_temporal equals
  --backend cpu at --precision max on the Netflix 576x324 pair (48/48)
  and on BBB 3840x2160 (50/50);
- compute-sanitizer memcheck, racecheck and synccheck report no error or
  hazard on the five CAMBI and SpEED parity tests;
- one readback and one stream synchronisation per frame (CUPTI count);
- 4K ms/frame before -> after: cambi_cuda 64.71 -> 6.01,
  speed_chroma_cuda 24.90 -> 6.89, speed_temporal_cuda fails -> 5.88;
- speed_log2() correctly rounded on every positive finite float, on the
  RTX 4090 and on the Arc A380.

T-CUDA-CAMBI-HOST-RESIDUAL-2026-09-29, T-CUDA-SPEED-HOST-RESIDUAL-2026-09-29
and T-CUDA-SPEED-TEMPORAL-SOLVE-LAUNCH-1080P-2026-09-30 are closed. The
lanczos4 row now carries device numbers (up to 2.1e-2 on a smooth 1080p
gradient), and the Arc A380 SpEED row records that its check is blocked
by the xe kernel driver, which returns wrong values from SYCL scratch
memory, rather than a vmafx bug. ADR-1379, ADR-1380, Research-1379, the
CAMBI and SpEED pages, the CUDA backend page, the changelog fragments,
the CUDA AGENTS.md and the rebase notes replace the emulation-only
statements with these measurements.

* fix(ci): regenerate the ADR citation map after rebasing onto #1643

* fix(speed): take the device prescale rule from speed_prescale_resamples()

#1643 replaced speed_internal.c's SI_ALMOST_EQUAL with the shared speed_prescale_resamples() helper. The device geometry spelled the same rule out by hand with the removed macro; it now calls the helper, so the CPU and the device twins decide the resample from one function.
lusoris added a commit that referenced this pull request Oct 1, 2026
…s (ADR-1398)

The vmaf CLI formerly refused odd-sized raw YUV 4:2:0 and 4:2:2 inputs with
"odd width/height %d not allowed..." via validate_chroma_alignment()
(ADR-0461), whereas .y4m inputs were accepted because frame_w was padded
to a multiple of 16. The C engine and raw readers handle odd dimensions
with ceiling chroma (vmaf_chroma_extent(), picture_geometry.h, PR #1643,
PR #1664).

Per user decision on 2026-10-01 ("Accept both (Recommended)"), the CLI
now accepts raw YUV inputs with odd dimensions using ceiling chroma,
matching .y4m.

- core/tools/vmaf.cpp: validate_chroma_alignment() returns 0.
- core/tools/test/test_vmaf_raw_odd_dims.sh: add positive (19x19,
  1921x1081, 19x20, 20x19, 19x19 422), boundary (1x1 420/422), and
  negative (file size mismatch exits 2 cleanly) regression tests.
- core/tools/test/test_vmaf_option_dict_ownership.sh: test Case 2 with
  mismatched dimensions instead of odd height to preserve early-exit
  dictionary leak coverage.
- python/test/vmafx_cli_test.py: add unit tests asserting raw YUV scores
  match Y4M bit-identically for odd dimensions, 1x1 boundary, and file
  size error handling.
- docs: document odd dimensions support in docs/usage/cli.md and
  core/tools/AGENTS.md, record ADR-1398, update docs/state.md,
  docs/rebase-notes.md, changelog fragment, and citations registry.

Closes T-CLI-RAW-ODD-DIMENSIONS-REFUSED-2026-10-01.
lusoris added a commit that referenced this pull request Oct 1, 2026
…s (ADR-1398) (#1672)

* fix(tools): accept odd dimensions for raw YUV chroma-subsampled inputs (ADR-1398)

The vmaf CLI formerly refused odd-sized raw YUV 4:2:0 and 4:2:2 inputs with
"odd width/height %d not allowed..." via validate_chroma_alignment()
(ADR-0461), whereas .y4m inputs were accepted because frame_w was padded
to a multiple of 16. The C engine and raw readers handle odd dimensions
with ceiling chroma (vmaf_chroma_extent(), picture_geometry.h, PR #1643,
PR #1664).

Per user decision on 2026-10-01 ("Accept both (Recommended)"), the CLI
now accepts raw YUV inputs with odd dimensions using ceiling chroma,
matching .y4m.

- core/tools/vmaf.cpp: validate_chroma_alignment() returns 0.
- core/tools/test/test_vmaf_raw_odd_dims.sh: add positive (19x19,
  1921x1081, 19x20, 20x19, 19x19 422), boundary (1x1 420/422), and
  negative (file size mismatch exits 2 cleanly) regression tests.
- core/tools/test/test_vmaf_option_dict_ownership.sh: test Case 2 with
  mismatched dimensions instead of odd height to preserve early-exit
  dictionary leak coverage.
- python/test/vmafx_cli_test.py: add unit tests asserting raw YUV scores
  match Y4M bit-identically for odd dimensions, 1x1 boundary, and file
  size error handling.
- docs: document odd dimensions support in docs/usage/cli.md and
  core/tools/AGENTS.md, record ADR-1398, update docs/state.md,
  docs/rebase-notes.md, changelog fragment, and citations registry.

Closes T-CLI-RAW-ODD-DIMENSIONS-REFUSED-2026-10-01.

* docs: regenerate the indexes and the citation map after rebasing
lusoris added a commit that referenced this pull request Oct 2, 2026
…each with its size and the upstream change that ends it (ADR-1479 to ADR-1486)

The reference for code inherited from Netflix/vmaf is Netflix's source;
a difference needs an ADR. The upstream parity audit of 2026-10-02 found
deliberate differences that had none of their own, or whose ADR
(ADR-1033) names neither upstream's behaviour nor the size:

- ADR-1479 ciede on 4:2:2: chroma flags (fork PR #1050); 0.153 on 48 of
  48 frames; Netflix/vmaf#1611.
- ADR-1480 speed_temporal buffers at speed_prescale above 1 (#1643);
  up to 195, upstream segfaults on two fixtures; Netflix/vmaf#1627.
- ADR-1481 a failing extractor fails the run (#871); status only, 78
  probe runs where upstream is silent and 88 where it crashes.
- ADR-1482 integer adm on frames of 17 to 32 pixels (#1473, #1507);
  scale 3 up to 0.23; Netflix/vmaf#1599, #1600.
- ADR-1483 odd-sized chroma planes round up (4f08d32); psnr_cb / cr
  up to 0.684 / 0.826 dB, ciede 0.198.
- ADR-1484 float_ms_ssim magnitude before pow() (#641, ADR-1033 item 2);
  NaN upstream on the 10 px checkerboard; Netflix/vmaf#1665.
- ADR-1485 apsnr of a plane without error (#641, item 1); 114 against
  60 dB; Netflix/vmaf#1666.
- ADR-1486 float_motion scale-1 stride (#641, item 9); up to 25.1;
  Netflix/vmaf#1667.

Each ADR gives upstream's file and line at Netflix 9e48141b, the fork's
lines, the reason found in the fork's pull request, commit or code, and
the measured size from the audit. Documentation only.
lusoris added a commit that referenced this pull request Oct 7, 2026
… to #1668

Add a "Confirmed not-affected" row for each of the fork's open upstream pull
requests from #1631 to #1668 (25 rows), naming the fork file, test or ADR
that shows the fork already carries the fix, covers it another way or is not
affected. #1643 is the one open item (a test-only x87 comparison with the same
line in the fork). #1634 is recorded as closed: the fork keeps integer AIM
unclipped, as upstream defines it.

Also records that the ten pull requests that conflicted with upstream master
acdd9376e were rebased on 2026-10-07. Documentation only.
lusoris added a commit that referenced this pull request Oct 7, 2026
… to #1668 (#2404)

* docs(state): reconcile the fork's open Netflix/vmaf pull requests #1631 to #1668

Add a "Confirmed not-affected" row for each of the fork's open upstream pull
requests from #1631 to #1668 (25 rows), naming the fork file, test or ADR
that shows the fork already carries the fix, covers it another way or is not
affected. #1643 is the one open item (a test-only x87 comparison with the same
line in the fork). #1634 is recorded as closed: the fork keeps integer AIM
unclipped, as upstream defines it.

Also records that the ten pull requests that conflicted with upstream master
acdd9376e were rebased on 2026-10-07. Documentation only.

* docs: regenerate the indexes and the citation map after rebasing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type:bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants