Skip to content

fix(sycl): correct libvmaf_sycl zero-copy VMAF on QSV-decoded 10-bit input - #1081

Open
Tualua wants to merge 2 commits into
VMAFx:masterfrom
Tualua:fix/sycl-qsv-zerocopy-p010-normalize
Open

fix(sycl): correct libvmaf_sycl zero-copy VMAF on QSV-decoded 10-bit input#1081
Tualua wants to merge 2 commits into
VMAFx:masterfrom
Tualua:fix/sycl-qsv-zerocopy-p010-normalize

Conversation

@Tualua

@Tualua Tualua commented Jul 1, 2026

Copy link
Copy Markdown

Summary

Fixes the libvmaf_sycl FFmpeg zero-copy filter returning VMAF score: nan (with integer_motion ~130× too high) on QSV-decoded 10-bit pairs on Intel Arc. A fresh investigation found two independent root causes — neither in the orchestration loop blamed by the prior FIX-01 attempt (which was verified incomplete): a shared QSV/VA surface pool (cross-decoder contamination) and raw P010 MSB-aligned pixels imported without the LSB normalization the CPU path gets for free. See ADR-1121.

Type

  • fix — bug fix
  • sycl / cuda / simd — backend-specific

Root cause & fix

  • BUG-1 (contamination). With one shared -hwaccel_device, FFmpeg gives both decoders one mfxSession/VA surface pool; their distinct mfxFrameSurface1 wrappers map to the same VASurfaceIDs, so HEVC ref[N±1] overwrites the surface AV1 wrote dis[N] into (sycl_dis[N]==sycl_ref[N±1]). Fix (FIX-03): require a separate -init_hw_device qsv=… per decoder — a documented usage contract (libvmaf can't see FFmpeg session topology from inside the filter).
  • BUG-2 (NaN + 64× motion). VA-API stores P010/P012 MSB-aligned (V_MSB = V_LSB << (16−bpc)); the CPU path gets >>6 via FFmpeg's P010LE→YUV420P10LE, the SYCL import did not. Fix (FIX-04): apply the >>(16−bpc) shift in the import — fused into the Tile4/Y-tiled de-tile store on the QSV/DG2 hot path (each sample shifted as written; no extra kernel, no second pass) and a standalone launch_p010_normalize() kernel only on the rare LINEAR/readback fallbacks (no-op for 8-bit).
  • No DMA-BUF coherency sync. A DMA_BUF_IOCTL_SYNC(READ) flush was tried and removed — proven insufficient for the contamination, and its SYNC_START is a blocking decoder-fence wait that serialised decode→compute and cut 4K throughput with zero correctness benefit. (vaSyncSurface() + the in-order queue already order decode→de-tile.)

Checklist

  • Commits follow Conventional Commits.
  • make format && make lint green locally (clang-tidy on touched SYCL TUs in-container).
  • If I touched any SIMD/GPU code path, I verified parity: SYCL↔CPU to 3 sig figs (table below).
  • If I touched a feature extractor with twins — N/A; this is VA-import plumbing, not a feature kernel; the kernels are unchanged.
  • New .c/.cpp/.h — none added; existing fork files edited (headers intact).
  • If this PR adds an ADR, the row lives in docs/adr/_index_fragments/1121-*.md + appended to _order.txt (README regenerated via concat-adr-index.sh).

Bug-status hygiene (ADR-0165)

  • docs/state.md updated — T-SYCL-ZEROCOPY-P010-NAN-2026-06-30 added to Recently closed.

Netflix golden-data gate

  • Did not modify any assertAlmostEqual(...). The bug is in the fork-added SYCL VA-import path; the CPU metric engine and golden pairs are untouched. (The fork D-03 oracle baseline was rebased to the de-contaminated value 97.2350 in .planning verification artifacts only — not Netflix golden data.)

Cross-backend numerical results

feature           cpu-oracle (sep-session)   sycl zero-copy (FIX-03+04)   delta
vmaf (500f)       97.2337                    97.2350                      0.0013
integer_motion2   26.6934 (max)              26.6934 (max)                identical
vmaf (30f)        97.5111                    97.5085                      0.0026
integer_motion2   1.15262 (max)              1.15262 (max)                identical
NaN count         0                          0                            —
determinism       —                          byte-identical across runs   (sha256 match)

Performance: the zero-copy path now defaults to direct SYCL dispatch — the
combined graph (ADR-0483) is a net throughput loss on VA-import (byte-identical
output, but the per-frame de-tile import + graph compute-barrier serialise
decode→compute, ~15–25% slower at 4K; ~33→42 fps on Arc A380). select_strategy
gains a va_import_path flag (after the env checks, so VMAF_SYCL_USE_GRAPH=1
still forces graph); users no longer need the deprecated VMAF_SYCL_NO_GRAPH=1.
Secondary: the MSB→LSB shift is fused into the de-tile kernel (no extra GPU pass)
and the DMA_BUF_IOCTL_SYNC blocking flush was removed. Default-dispatch output
is byte-identical to graph mode (sha256 match) — zero numeric change.

Deep-dive deliverables (ADR-0108)

  • Research digestdocs/research/2026-06-30-sycl-qsv-zerocopy-two-bug-investigation.md.
  • Decision matrix — ADR-1121 ## Alternatives considered (normalize-in-import vs in-kernel vs force-readback vs auto-detect-session vs ioctl-as-fix).
  • AGENTS.md invariant notecore/src/sycl/AGENTS.md: normalize on all import paths; FIX-02 is hardening not the fix; D-03 de-contaminated targets.
  • Reproducer / smoke-test command — below.
  • CHANGELOG fragmentchangelog.d/fixed/1121-sycl-qsv-zerocopy-p010-normalization.md.
  • Rebase notedocs/rebase-notes.mdfix/sycl-qsv-zerocopy-p010-normalize (no ffmpeg-patch impact; invariants listed).

Reproducer

# Intel Arc A380, container localhost/vmafx-fix04 (or any -Denable_sycl=true build).
# Separate per-decoder QSV sessions (FIX-03) are mandatory.
ffmpeg -y -hide_banner \
  -init_hw_device drm=drm0:/dev/dri/renderD128 \
  -init_hw_device vaapi=va0@drm0 \
  -init_hw_device qsv=qsv_ref@va0 \
  -init_hw_device qsv=qsv_dis@va0 \
  -hwaccel qsv -hwaccel_output_format qsv -hwaccel_device qsv_ref -c:v hevc_qsv -i ref.mkv \
  -hwaccel qsv -hwaccel_output_format qsv -hwaccel_device qsv_dis -c:v av1_qsv  -i dis.mkv \
  -lavfi '[0:v][1:v]libvmaf_sycl=log_fmt=csv:log_path=out.csv' \
  -frames:v 500 -f null -
# Expect: VMAF ~97, integer_motion < 30, 0 NaN. (Old bug: VMAF ~34/nan, integer_motion ~3000+.)

Known follow-ups

  • The separate-session requirement is a documented contract, not enforced by the binary (libvmaf cannot observe FFmpeg's mfxSession topology). The libvmaf_sycl filter could log a one-time hint if it detects a shared VA pool — deferred.
  • ffmpeg-patch stack: no change — the fix is internal to the fork-added SYCL VA-import path (no public C-API / CLI / meson_options.txt / LIBVMAFContext delta); the separate-session pattern is an ffmpeg invocation, not a vf_libvmaf.c change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YCP4JCoduujJgccDRGvNKv

Tualua and others added 2 commits June 30, 2026 22:54
Document the fix for the libvmaf_sycl zero-copy NaN bug on QSV-decoded
10-bit pairs. Two root causes: a shared QSV/VA surface pool (cross-decoder
contamination) and a P010/P012 MSB/LSB pixel-range mismatch. Lands ahead
of the implementing commit per CLAUDE.md rule 8.

- ADR-1121 via _index_fragments + _order.txt (README regenerated by
  concat-adr-index.sh)
- research digest: two-bug investigation
- docs/backends/sycl/overview.md: separate-session decode contract, P010
  normalization, VMAF_SYCL_IMPORT_DEBUG env knob
- core/src/sycl/AGENTS.md: normalize-on-all-import-paths invariant
- changelog.d/fixed fragment; docs/rebase-notes.md entry
- docs/state.md: close T-SYCL-ZEROCOPY-P010-NAN-2026-06-30

Refs ADR-1121.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YCP4JCoduujJgccDRGvNKv
…input

libvmaf_sycl returned VMAF=nan with ~130x integer_motion on QSV-decoded
10-bit pairs. Root cause (ADR-1121) was two independent bugs, neither in
the orchestration loop:

- P010/P012 MSB/LSB mismatch (the NaN + 64x motion). VA-API delivers
  10/12-bit luma MSB-aligned (V_MSB = V_LSB << (16-bpc)); the CPU path
  gets >>6 via FFmpeg's P010LE->YUV420P10LE, the SYCL import did not.
  Apply the >>(16-bpc) shift in the import: fused into the Tile4 / Y-tiled
  de-tile store (each sample shifted as written -- no extra kernel, no
  second full-plane pass; the QSV/DG2 hot path), with a standalone
  launch_p010_normalize() kernel only on the rare LINEAR D2D / readback
  fallbacks (no per-sample store to fuse into). No-op for 8-bit NV12.

- Shared QSV/VA surface pool (the cross-frame contamination). Fixed by the
  separate-per-decoder-QSV-session invocation contract (documented in
  docs/backends/sycl/overview.md); libvmaf cannot observe FFmpeg session
  topology from inside the filter, so it is not enforced in code.

No DMA-BUF coherency sync: a DMA_BUF_IOCTL_SYNC(READ) flush was tried and
removed -- proven insufficient for the contamination, and its SYNC_START
is a blocking decoder-fence wait that serialised decode->compute and cut
4K throughput with zero correctness benefit.

Defaults the zero-copy path to direct SYCL dispatch instead of the
combined graph. The graph (ADR-0483) is a net throughput loss on the
VA-import path -- byte-identical output, but the per-frame de-tile import
plus the graph compute-barrier serialise decode->compute, ~15-25% slower
at 4K. vmaf_sycl_select_strategy() gains a va_import_path flag (passed
state->has_imported), returning DIRECT after the env-override checks so an
explicit VMAF_SYCL_USE_GRAPH=1 / VMAF_SYCL_DISPATCH=...:graph still wins.
Users no longer need the deprecated VMAF_SYCL_NO_GRAPH=1.

This change carries the supporting SYCL plumbing: the double-buffer slot
toggle + upload-slot getters, and the env-gated VMAF_SYCL_CHECKSUM D2H
probe + VMAF_SYCL_IMPORT_DEBUG logging used to diagnose the bug (kept as
permanent diagnostics; resolved once at init via the
vmaf_sycl_import_debug_enabled() accessor). Pre-existing clang-tidy debt
in the touched dmabuf_import.cpp is cleaned (const-correctness on two
Level Zero queue pointers + ze_result_t) per ADR-0141.

Verified on Intel Arc A380 (Shadow's Edge 4K HEVC->AV1): 0 NaN, SYCL VMAF
97.2350 vs CPU oracle 97.2337 over 500 frames, integer_motion2 max 26.69,
and byte-identical across repeated runs (deterministic). Netflix golden
assertions untouched.

Refs ADR-1121.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YCP4JCoduujJgccDRGvNKv
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant