Skip to content

perf(sycl): read the shared frame in psnr, psnr_hvs and motion_v2 - #1634

Merged
lusoris merged 2 commits into
masterfrom
perf/sycl-psnr-hvs-light-twins-4k
Sep 30, 2026
Merged

lusoris merged 2 commits into
masterfrom
perf/sycl-psnr-hvs-light-twins-4k

Conversation

@lusoris

@lusoris lusoris commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The SYCL psnr_hvs, psnr and motion_v2 twins now read the planes the SYCL state uploads once per frame instead of converting and uploading their own copies, and their kernels are reworked. At 3840x2160, psnr_hvs goes from 17.1 to 7.6 ms per frame on an Arc B580 and from 124 to 60 on a UHD 770; on the UHD 770 psnr goes from 25.3 to 12.3. Every per-frame score is bit-identical to master on both devices. Rebased onto #1624 and #1628; #1628 (ADR-1371) moved motion_v2_sycl onto the shared motion pipeline, so this PR keeps only its host-side change there. Design: ADR-1369; profile and evidence: Research-1369.

What the profile found (master, 4K, B580, event timing per phase): psnr_hvs_sycl spent 14.5 ms converting all six planes to float on the host and 7.2 ms uploading 99.5 MB for a 6.3 ms kernel; motion_v2_sycl re-uploaded the luma the shared frame already held; psnr_sycl and psnr_hvs_sycl each uploaded their own chroma. What changed:

  • Shared planes (core/src/sycl/common.cpp): opt-in Cb/Cr planes (vmaf_sycl_shared_chroma_init / _upload, vmaf_sycl_get_shared_plane), uploaded once per frame by the first twin that reads chroma, packed into pinned staging with one DMA per plane; vmaf_sycl_queue_after_upload for twins on their own queue; a device-side slot fence so an upload never overwrites a slot whose readers are still running (it matters for n_subsample).
  • psnr_hvs: raw samples read on the device, two work-items per 8x8 block, one dispatch for all planes; per-block float expressions unchanged. 9-/11-bit input now scores like the CPU (it was off by more than 20 dB).
  • motion_v2: runs the ADR-1371 SAD pipeline on the shared luma and keeps the frame through its cur_copy; no host copy, no private upload. (A kernel variant with the vertical taps once per tile column, 13.2 → 6.3 ms on the UHD 770, bit-exact, was dropped on rebase and is recorded as a pipeline follow-up.)
  • psnr: chroma from the shared planes; one atomic per work-group instead of one per pixel.

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. — Ran the parts this change touches, not the full targets: clang-format 23.1.1 on every changed C/C++ file; clang-tidy 22.1.8 through scripts/ci/clang-tidy-sycl.sh on the four SYCL TUs (common.cpp 60 findings on master and on this branch; the three feature TUs 0) and on the three changed tests (0); markdownlint on the changed docs; make docs-fragments-check; check-source-adr-citations.py; check-state-md-rows.sh; assertion-density.sh. No cppcheck (the CI job builds with -Denable_sycl=false, so none of these files are in its database).
  • Unit tests pass: python3 scripts/ci/run_meson_test.py -- -C build --suite fast on the AOT build before the fix(sycl): make motion_sycl bit-exact with the CPU motion #1628 rebase (235 OK, 1 skipped; test_gpu_picture_pool_uaf was SIGKILLed under memory pressure in the parallel run and passes alone). After the rebase, on both GPUs: test_sycl_shared_planes, test_sycl_psnr_hvs_parity{,_large}, test_sycl_psnr_parity, test_sycl_motion_v2_parity{,_large}, test_sycl_motion_tiny_frames, test_sycl_motion3_parity, test_sycl_twin_option_parity, test_sycl_init_unwind, and test_sycl_kernel_source_contract.py (12 passed).
  • If I touched any SIMD/GPU code path, I ran /cross-backend-diff and the worst ULP is ≤ 2. — Bit-identical to master (0 ULP) on the B580 and the UHD 770; psnr and motion_v2 equal the CPU; psnr_hvs keeps its ADR-1361 distance (table below).
  • 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. — Not breaking: internal common.h API only; public headers unchanged.
  • 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. Closed: T-SYCL-LIGHT-TWIN-HOST-ROUNDTRIPS-2026-09-29, T-SYCL-PSNR-HVS-ODD-BPC-SCALE-2026-09-29, T-SYCL-SHARED-SLOT-SUBSAMPLE-WAR-2026-09-29. Opened RC3: T-CUDA-PSNR-HVS-HOST-ROUNDTRIP-2026-09-29, T-HIP-PSNR-HVS-HOST-CONVERT-2026-09-29, T-GPU-MOTION-V2-INT64-VERTICAL-2026-09-29, T-HIP-TWIN-PRIVATE-PLANE-UPLOADS-2026-09-29, T-SYCL-PAGEABLE-UPLOAD-HOST-STAGING-2026-09-29, T-SYCL-PSNR-HVS-XE-LP-THROUGHPUT-2026-09-29; RC2: T-SYCL-SHARED-FRAME-STICKY-GEOMETRY-2026-09-29. The RC2 dispositions cell that master had emptied lists its open rows again.

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. — Not applicable; no CPU code changed.

Cross-backend numerical results

--precision max, every metric of every frame, this branch against master's SYCL twin and against --backend cpu:

fixture                         frames  new==master        max |new - cpu|
BBB 3840x2160 8-bit 4:2:0         22     all identical      psnr, motion_v2: 0 | psnr_hvs 7.63e-4 (psnr_hvs_y 8.42e-4; ADR-1361 gate 3.34e-3)
src01 576x324 8-bit 4:2:0         48     all identical      psnr, motion_v2: 0 | psnr_hvs 8.03e-5 (gate 5e-4)
src01 576x324 10/12-bit, 4:2:2    3/3/48 all identical      psnr, motion_v2: 0 | psnr_hvs <= 7.41e-5
sparks 480x270 10-bit              5     all identical      psnr, motion_v2: 0 | psnr_hvs <= 1.34e-5
=> 120/120 per-metric comparisons (Arc B580 + UHD 770 x 6 fixtures x 3 twins) identical to master 2d9d5b069
=> after the #1628 rebase: 4K, 576x324 8-bit and 10-bit, both GPUs, all three twins identical to master b20472dcb
psnr_hvs 9-bit (C API, 64x48): cpu 22.470312 | master sycl -1.568466 | this 22.470312
psnr_hvs 11-bit:               cpu 33.972263 | master sycl 10.431668 | this 33.972261

Performance (if perf or feat)

3840x2160 Big Buck Bunny, ms per frame, (t(22) − t(2)) / 20, median of 5 interleaved reps (base and new back to back), --backend sycl -n --feature <cpu-name>, WSL2, CPU shared with other builds:

twin CPU 16 thr B580 before B580 after UHD 770 before UHD 770 after
psnr_hvs 6.64 17.11 7.56 123.97 59.93
motion_v2 (before = master b20472dcb, ADR-1371 kernel) 4.44 6.07 7.42 15.36 15.41
psnr 5.44 7.90 7.17 25.32 12.33
float_psnr (unchanged twin) 6.30 9.19 10.26 10.44 10.76
motion (unchanged twin) 4.60 4.75 6.97 13.99 13.88
default model 29.25 45.21 47.53 73.29 73.62

The 22-frame differential swings by about ±3 ms on the B580 at 4K, where these twins sit at the CLI's frame-read rate. Over 100 frames (t(102) − t(2)) / 100, same method: motion_v2 against b20472dcb 6.11 → 5.39 on the B580 and 14.73 → 15.11 on the UHD 770 (median of 3; its kernel is ADR-1371's and dominates there), motion 6.57 → 6.82, float_psnr 7.52 → 7.35, default model 45.41 → 45.46 — no regression. The other rows compare against 2d9d5b069; #1624 and #1628 do not touch psnr_hvs, psnr, float_psnr or the default model's twins except motion. At 576x324 (48 frames) every wall-time difference is inside the timer noise; the per-phase profile shows each twin's per-frame work shrinking there too (psnr_hvs on the UHD 770: kernel 2.42 → 1.14 ms).

Per-phase profile, 4K, ms per frame (event timing; full tables in Research-1369):

phase psnr_hvs B580 before → after psnr_hvs UHD before → after motion_v2 B580 motion_v2 UHD psnr B580 psnr UHD
host conversion / repack 14.48 → 0 12.38 → 0 0.97 → 0 0.99 → 0 0.86 → 0 0.95 → 0
private upload (device) 7.18 → 0 3.43 → 0 0.60 → 0.03 (D2D) 0.20 → 0.20 (D2D) 0.61 → 0 0.23 → 0
shared chroma upload (host / device, once for all twins) — → 0.81 / 0.61 — → 1.12 / 0.24 — — — → 0.81 / 0.61 — → 1.12 / 0.24
kernels (device) 6.26 → 1.31 103.77 → 49.44 ADR-1371 pipeline (unchanged here) ADR-1371 pipeline (unchanged here) 0.29 → 0.14 16.12 → 3.35
result readback (device) 0.07 → 0.07 0.05 → 0.03 < 0.01 < 0.01 < 0.01 0.01
host wait in collect 0.66 → 0.08 2.31 → 0.82 0.08 → 0.05 0.01 → 1.07 0.06 → 0.02 0.02 → 0.73

A first version copied chroma straight from the pageable picture with ext_oneapi_memcpy2d; the timing pass caught it costing 5.1 ms per 576x324 frame on the B580 and 168 ms on the UHD 770, and the upload now packs into pinned staging.

Crash check for T-SYCL-PSNR-HVS-B580-SIGSEGV: the AOT build for bmg-g21,adl-s compiles the new kernel (also a scratch variant with reqd_sub_group_size(32)); a SPIR-V JIT-only build runs test_sycl_psnr_hvs_parity on both devices by default, with IGC_ForceOCLSIMDWidth=32 and with =16 (NEO_CACHE_PERSISTENT=0), and the JIT SIMD32 4K output equals the AOT output.

Deep-dive deliverables (ADR-0108)

  • Research digest — docs/research/1369-sycl-shared-planes-light-twins.md.
  • Decision matrix — ADR-1369 ## Alternatives considered.
  • AGENTS.md invariant note — core/src/sycl/AGENTS.md (shared planes, slot fence, no direct pageable chroma copy) and core/src/feature/sycl/AGENTS.md (psnr_hvs kernel shape, motion_v2 32-bit bounds, psnr reduction).
  • Reproducer / smoke-test command — below.
  • CHANGELOG fragment — changelog.d/changed/perf-sycl-shared-planes-light-twins.md, changelog.d/fixed/sycl-psnr-hvs-odd-bit-depth.md.
  • Rebase note — docs/rebase-notes.md entry perf/sycl-psnr-hvs-light-twins-4k.

Reproducer

# build (AOT for Arc B580 + Alder Lake iGPU), then the new and affected tests
CC=icx CXX=icpx meson setup build core -Denable_sycl=true -Denable_cuda=false \
  -Dsycl_icpx_aot_targets=bmg-g21,adl-s --buildtype=release -Db_lto=false
ninja -C build
for t in test_sycl_shared_planes test_sycl_psnr_hvs_parity test_sycl_psnr_parity \
         test_sycl_motion_v2_parity test_sycl_init_unwind test_sycl_twin_option_parity; do
  ./build/test/$t || echo "FAIL $t"; done
# per-frame parity against the CPU (psnr / motion_v2 must print 0.0)
for f in psnr psnr_hvs motion_v2; do
  for be in cpu sycl; do
    ./build/tools/vmaf -r src01_hrc00_576x324.yuv -d src01_hrc01_576x324.yuv -w 576 -h 324 -p 420 -b 8 \
      --backend $be -n --feature $f --precision max --json -o $f.$be.json; done
  python3 -c 'import json,sys; a,b=(json.load(open(f))["frames"] for f in sys.argv[1:]); print(max(abs((x["metrics"][k] or 0)-(y["metrics"][k] or 0)) for x,y in zip(a,b) for k in x["metrics"]))' $f.cpu.json $f.sycl.json
done

Known follow-ups

  • CUDA / HIP twins have the same overheads (read-only review, rows in docs/state.md with file:function, the SYCL design to port and a verify-and-time command for ryzen-4090-arc): psnr_hvs_cuda round-trips every plane device → host → float → device (integer_psnr_hvs_cuda.c upload_frame); psnr_hvs_hip converts on the host; both keep the thread-0-serial kernel; CUDA/HIP motion_v2 kernels recompute the vertical taps per pixel in 64 bits; every HIP twin uploads its own copy of the planes. psnr_cuda already reads the device picture and reduces per warp.
  • The ADR-1371 motion pipeline still computes the vertical taps per pixel; the measured variant (vertical once per tile column, 32-bit horizontal split, 32-bit block sum) took the kernel from 13.2 to 6.3 ms per 4K frame on the UHD 770, bit-exact — recorded in T-GPU-MOTION-V2-INT64-VERTICAL-2026-09-29 for SYCL, CUDA and HIP.
  • The remaining host cost of every SYCL run is the pageable luma upload (2.2–3.0 ms per 4K frame on the B580); a SYCL host-USM picture pool would remove it (T-SYCL-PAGEABLE-UPLOAD-HOST-STAGING-2026-09-29, touches the pool the CLI read-ahead work also changes).
  • psnr_hvs on the UHD 770 is still 49 ms of kernel per 4K frame; going further needs a per-block float order different from the CPU's (maintainer decision, T-SYCL-PSNR-HVS-XE-LP-THROUGHPUT-2026-09-29).
  • T-SYCL-SHARED-FRAME-STICKY-GEOMETRY-2026-09-29 (pre-existing, API only): a SYCL state reused for a second frame size keeps the first size's shared frame.
  • Unverified here: the zero-copy VA import path (no QSV/VA-API decode in WSL2), Windows native build, AdaptiveCpp.

🤖 Generated with Claude Code

@github-actions github-actions Bot added the type:perf Performance improvement label Sep 29, 2026
@lusoris
lusoris force-pushed the perf/sycl-psnr-hvs-light-twins-4k branch from f5ef1b1 to 07d617f Compare September 29, 2026 16:20
lusoris and others added 2 commits September 30, 2026 11:33
The SYCL psnr_hvs, psnr and motion_v2 twins now read the planes the SYCL
state uploads once per frame instead of converting and uploading their own
copies. Scores are bit-identical to the previous twins on an Arc B580 and a
UHD 770.

- Opt-in shared Cb/Cr planes in common.cpp (vmaf_sycl_shared_chroma_init /
  _upload, vmaf_sycl_get_shared_plane): the first chroma-reading twin of a
  frame packs the chroma into pinned staging and uploads it with one DMA per
  plane; later twins reuse it. Luma-only runs never allocate chroma.
- vmaf_sycl_queue_after_upload() gives twins on their own queue the input
  barriers the combined graph gets; a device-side slot fence orders each
  upload after the last readers of the slot it overwrites.
- psnr_hvs: no host float conversion or private upload; two work-items per
  8x8 block, one dispatch for all planes, per-block float expressions
  unchanged. 9- and 11-bit input now scores the raw sample like the CPU.
- motion_v2: runs the ADR-1371 pipeline on the shared luma and keeps the
  frame through its cur_copy; no host copy or private upload.
- psnr: chroma from the shared planes; one atomic per work-group.

At 3840x2160, psnr_hvs drops from 17.1 to 7.6 ms per frame on the B580 and
from 124 to 60 on the UHD 770, where psnr drops from 25.3 to 12.3.
ADR-1369, Research-1369.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge the RC2/RC3 disposition rows three-way by bug id: master (#1626)
had two RC3 rows from an earlier keep-both resolution, and this branch
adds three RC2 and six RC3 ids. Drop the open oneAPI B580 row #1629
closed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lusoris
lusoris force-pushed the perf/sycl-psnr-hvs-light-twins-4k branch from 07d617f to ed5b7b3 Compare September 30, 2026 09:45
@lusoris
lusoris merged commit 97daec4 into master Sep 30, 2026
121 of 122 checks passed
@lusoris
lusoris deleted the perf/sycl-psnr-hvs-light-twins-4k branch September 30, 2026 10:53
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:perf Performance improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant