Research-1395: Scratch memory in the SYCL kernels on an Arc A380 under the xe driver¶
Question¶
On the RC3 host's Arc A380, 16 SYCL tests fail on master and on every branch built from it, including #1630, which changes no kernel's control flow. Is the cause in vmafx or in the platform? Which kernels are affected, how can a build catch a new one, and how can a user on the affected platform find out?
Sources¶
- Host: Arc A380 (
dg2-g11, PCI ID 56a5) on Linux 7.2.8 withxe.force_probe=56a5andi915.force_probe=!56a5; Intel compute runtime 26.35.39758.10 and IGC 2.41.5 (both installed 2026-09-20); icpx 2026.0;ONEAPI_DEVICE_SELECTOR=level_zero:0. The boots up to 2026-09-27 bound the A380 to i915 (i915 0000:03:00.0: [drm] Found dg2/g11); the boot of 2026-09-30 binds it to xe. - SYCL 2020
info::kernel_device_specific::private_mem_size; the oneAPI extensionext::intel::info::kernel_device_specific::spill_memory_size(sycl/info/ext_intel_kernel_info_traits.def) and aspectext_intel_spill_memory_size; thesycl_ext_intel_grf_sizekernel property (sycl/ext/intel/experimental/grf_size_properties.hpp,grf_size<128|256>), all read from the installed oneAPI 2026.0 headers. - The meson test log of 2026-09-25 23:07 in the
agy-gpu-adm-aimworktree (i915, same runtime and compiler). - Measurement commands in the Findings below; all builds were
--buildtype=release -Db_lto=false, SPIR-V JIT unless noted.
Findings¶
The defect needs no vmafx code¶
Standalone SYCL kernels on the A380 under xe, each checked against the same function on the host, in work-groups of 256 work-items, 1, 8, 64 and 1024 of them per run:
| Probe | Scratch per thread | Wrong work-items |
|---|---|---|
| private array of 256 floats, SIMD-16 | 16384 B private | all, at every size |
| private array of 110 floats, SIMD-16 / SIMD-32 | 7040 / 14080 B private | all 256 in one work-group; 336 / 64 of 2048 with 8; 37746 / 4660 of 262144 with 1024 |
| private array of 32 floats, SIMD-16 | 2048 B private | none up to 8 work-groups; 16089 of 16384 with 64 |
| 96 live floats, SIMD-32 | 4096 B spill | all 256 in one work-group; 261888 of 262144 with 1024 |
| 8 floats; 48 or 160 live floats at SIMD-16; 96 at SIMD-8 | none | none |
96 live floats, SIMD-32, grf_size<256> (65536 work-items) | none | none |
The failures are the same for JIT and dg2-g11 AOT images, on Level Zero and on OpenCL, and every probe is correct on the OpenCL CPU device. The self-test's integer versions (core/src/sycl/scratch_check.cpp) give 256 of 256 wrong for the private-array probe (16384 B) and 256 of 256 for the SIMD-32 spill probe (4096 B).
Which kernels use scratch¶
A kernel audit (every sycl::get_kernel_ids() entry of libvmaf.so built for the A380 and queried for both sizes) finds 25 of 109 kernels with scratch on master 10f27efe2:
| Extractor | Kernels | Private / spill bytes per thread |
|---|---|---|
vif_sycl (SIMD-32 only) | launch_vif_hori_impl<0..3, 32>, launch_vif_fused_impl<0..3, 32> | spill 96 to 8832 |
float_vif_sycl | launch_compute<0>, launch_decimate<1> | private 14080, 2432 |
float_adm_sycl | launch_aim_cm, launch_csf_cm | private 896 each |
adm_sycl | launch_csf_den_cm | spill 864 |
psnr_hvs_sycl | launch_psnr_hvs | private 2432 |
speed_chroma_sycl, speed_temporal_sycl | launch_scale and launch_decimate for 8 and 16 bits, each also as a rounded-range wrapper (8 kernels) | private 832 to 3584 |
motion_sycl, motion_v2_sycl | submit_sad<int64_t> (above 15 bits per component) | spill 768 |
cambi_sycl | launch_reset and its rounded-range wrapper | private 1280, 896 |
IGC dumps of each test on master (IGC_ShaderDumpEnable) tie the 16 failing tests to these kernels: 15 of them build at least one, and the 16th, psnr_hvs_parity_simd32, is psnr_hvs_parity's executable run with IGC_ForceOCLSIMDWidth=32, so it runs the same launch_psnr_hvs kernel. No test that builds only scratch-free kernels fails. Ten passing tests also build a scratch kernel but either do not run it (motion's submit_sad<int64_t> only runs above 15 bits per component) or run it in small dispatches, where the probes above often pass too (cambi's launch_reset covers 2048 work-items; speed_temporal_parity passes and speed_temporal_parity_large fails). Under i915 on 2026-09-25, 13 of the 16 passed (psnr_hvs_parity_simd32, shared_planes and motion_tiny_frames did not exist yet).
The large register file¶
Rebuilding master's JIT image with SYCL_PROGRAM_COMPILE_OPTIONS=-ze-opt-large-register-file (256 registers per thread for every kernel) leaves 14 of the 25: all spills and float_vif's launch_compute<0> array go, the other private arrays stay.
Per kernel, grf_size<256> on the eight vif SIMD-32 kernels removes their spills; the audit then finds 15 kernels with scratch (with adm_sycl and psnr_hvs_sycl cleared in #1656 and #1657), the ratchet list. icpx -fsycl -fno-sycl-rdc -fsycl-targets=spir64_gen,spir64 with the default 19-target sycl_icpx_aot_targets list builds a kernel with grf_size<256> for every target.
vif_sycl at SIMD-32, before and after¶
Forced with VMAF_SYCL_VIF_SUBGROUP_SIZE. Max abs diff of the four integer_vif_scale* outputs against the CPU vif extractor at --precision max (Netflix pair, 48 frames; BBB 3840x2160, 50 frames), and ms/frame as (t(22) - t(2)) / 20, median of 3, load average 9 to 30 from other jobs:
| Mode | Before | After |
|---|---|---|
| SIMD-32, separate passes | frame 0: numerator = denominator = 0, run aborts | scale 0..3: 4.99e-8 / 1.04e-7 / 1.60e-7 / 3.87e-7 (576x324), 7.02e-8 / 8.86e-8 / 1.05e-7 / 1.47e-7 (4K); 23.43 ms/frame at 4K |
| SIMD-16, separate passes (default) | same diffs; 22.99 ms/frame | same diffs; 23.22 ms/frame |
SIMD-32, vif_fused=true | frame 0: 0 / 0, run aborts | 576x324 as above; 4K scales 1..3 up to 6.4e-4; 67.06 ms/frame |
SIMD-16, vif_fused=true | 576x324 as above; 4K scales 1..3 up to 6.6e-4; 25.38 ms/frame | same diffs; 24.57 ms/frame |
SIMD-16 and SIMD-32 outputs are bit-identical in the separate-pass mode (every value of 48 and 50 frames). The 4K vif_fused gap is older than this change and also occurs at SIMD-16; it varies between SIMD-32 runs. See the open questions.
Cost of the self-test¶
On the A380, vmaf --backend sycl with a two-frame psnr_sycl run takes 29 to 32 ms with VMAF_SYCL_SCRATCH_SELFTEST=0 and 33 to 37 ms with the self-test, with the compute runtime's kernel cache warm; the first run with a cold cache took 0.42 s.
Alternatives explored¶
- Refusing the device, or routing extractors to the CPU, when the self-test fails. Both hide the extractors that are correct on xe. See the ADR's alternatives table.
- The large register file for every kernel. Measured above: it removes the spills but leaves 14 kernels with private arrays, and it halves occupancy for kernels that do not need it.
- A static zeinfo check of the AOT images. IGC's zeinfo records
private_sizeandspill_sizeper kernel, but only for the AOT targets, not for the SPIR-V image that other devices compile at run time.
Open questions¶
- Whether the B580 and the UHD 770 list other scratch kernels: their register files differ, so the audit can find more there.
vif_fused=truereads each scale's input fromrd_ref/rd_disand writes the next scale's input into the same buffers in the same kernel. At 4K scales 1 to 3 are up to 6.6e-4 from the CPU on both sub-group sizes, and SIMD-32 runs differ from each other: this looks like a read/write race between work-groups. It is outside this change.- The defect has not been reported to Intel; a reduced reproducer is the private-array probe above.
Related¶
- ADRs: ADR-1395
- PRs: #1630 (records the A380 results on master and with its change)
- Issues: #1641 (RC3 handoff)