Research-0519: HIP vmaf_hip_import_state library-side wiring¶
Status: Investigation closed, fix landed (ADR-0519). Date: 2026-05-18. Author: Claude.
Question¶
After ADR-0514 unblocked the container-side HIP exposure (added -DHAVE_HIP=1 to core/tools/meson.build), vmaf --backend hip still failed with "problem during vmaf_hip_import_state" + exit 255 on an AMD gfx1036 host inside the vmaf-dev-mcp container. What is the minimum library-side change to make HIP a fully working backend on AMD?
Method¶
- Grep
vmaf_hip_import_stateacross the tree. Found: - Declaration:
core/include/libvmaf/libvmaf_hip.h:80. - Definition:
core/src/hip/common.c:149returning-ENOSYSwith a comment "stays unwired until the first feature kernel lands (T7-10c)". -
Callers:
core/tools/vmaf.c:641(the CLI--backend hippath),core/test/test_hip_smoke.c:152(explicit -ENOSYS assertion),core/test/test_hip_motion3_parity.c:159(uses the function but the test was never functional on master because it also depends onvmaf_fex_integer_motion_hipbeing registered, which it isn't). -
Compare against the four other GPU backends'
_import_statefunctions: vmaf_cuda_import_state(core/src/libvmaf.c:307): by-value copyvmaf->cuda.state = *cu_state;— historic ownership-transfer semantics.vmaf_sycl_import_state(core/src/libvmaf.c:458): pointer-stashvmaf->sycl.state = sycl_state;— caller-owned opaque state.vmaf_vulkan_import_state(core/src/libvmaf.c:571): pointer-stash, identical shape.-
vmaf_metal_import_state(core/src/libvmaf.c:677): pointer-stash, identical shape. -
Check whether the HIP-flagged extractors actually need the stashed state.
core/src/hip/AGENTS.mdrebase-sensitive invariants section pins: "vmaf_fex_psnr_hipis registered without theVMAF_FEATURE_EXTRACTOR_HIPflag bit set (fork-local, ADR-0241). The flag bit (1 << 6) is reserved in the enum but the consumer does not set it, because the picture buffer-type check invmaf_feature_extractor_context_extractwould route a HIP-flagged extractor through a (not-yet-existing) HIP buffer-type branch." Every HIP feature extractor follows the same posture (grepped: 8 files cite the "VMAF_FEATURE_EXTRACTOR_HIP flag is intentionally absent" or "cleared until picture buffer-type" comment). The dispatch layer therefore routes HIP-named extractors through their CPU twins; the stashed state is captured for the future buffer-type plumbing, not for the current frame loop. -
Confirm the end-to-end behaviour matches expectations: on the host build (where
vmaf_hip_import_statewas historically a silent no-op success),vmaf --backend hipproduced VMAF = 76.66783 — bit-exact equal to CPU. This is consistent with "HIP dispatch routes to CPU paths, returning CPU-identical scores", and is what the cross-backend e2e probe batch indocs/state.mdrecorded for the host build prior to the container regression.
Findings¶
The minimum library-side change is three edits:
-
Move
vmaf_hip_import_statefromcore/src/hip/common.cintocore/src/libvmaf.c. The function needsVmafContextfield-level access; placing it next to the CUDA / SYCL / Vulkan / Metal twins keeps the four "stash the borrowed state pointer on the context" implementations in one file where future maintainers can pattern-match across all of them. -
Add a
hipsubstruct toVmafContextholding aVmafHipState *statepointer, gated by#ifdef HAVE_HIP, appended after the existingmetalsubstruct. Mirrors the SYCL / Vulkan / Metal field shapes exactly — the CUDAVmafCudaCookie+VmafGpuPicturePoolring-buffer fields are not needed yet because no HIP-flagged extractor consumes a HIP device picture today. -
Clear the pointer in
vmaf_closewithout freeing — the caller owns the state and frees it viavmaf_hip_state_freeaftervmaf_close. Same lifetime contract as SYCL / Vulkan / Metal; documented in the existing comments cloned from the Vulkan block.
The smoke test (core/test/test_hip_smoke.c) must replace its test_import_state_returns_enosys assertion with two new ones: NULL-argument validation (-EINVAL) and a device-bound happy-path that exercises vmaf_init → vmaf_hip_import_state → vmaf_close → vmaf_hip_state_free on a host with at least one visible AMD GPU (skips cleanly otherwise, matching the Vulkan-on-lavapipe-less-CI pattern).
Verification¶
End-to-end repro inside vmaf-dev-mcp:
vmaf --reference /workspace/python/test/resource/yuv/src01_hrc00_576x324.yuv \
--distorted /workspace/python/test/resource/yuv/src01_hrc01_576x324.yuv \
--width 576 --height 324 --pixel_format 420 --bitdepth 8 \
--backend hip --hip_device 0 --json --output /tmp/hip.json
Pre-fix: "problem during vmaf_hip_import_state" + exit 255. Post-fix: VMAF = 76.66783, exit 0.
CPU baseline on the same pair: VMAF = 76.66783 (delta = 0, within the places=4 cross-backend gate from ADR-0214 with room to spare).
Smoke test (test_hip_smoke): 22 / 22 pass (was 21 / 22 pass + 1 explicit -ENOSYS assertion that was the stop-gate).
Out of scope¶
- Promoting
VMAF_FEATURE_EXTRACTOR_HIPon the HIP-flagged extractors. Requires addingVMAF_PICTURE_BUFFER_TYPE_HIP_DEVICEto the picture buffer-type dispatch + a HIP picture pool — a separate, larger PR. - Registering
vmaf_fex_integer_motion_hipincore/src/feature/feature_extractor.c'sfeature_extractor_list[]. The extractor isextern-declared but never added to the list, sovmaf_use_feature("motion_hip", ...)returns-EINVAL. This is a pre-existing latent bug from PR #1167 (the test_hip_motion3_parity gate was added in the same PR but the registration was missing). Unblocking that gate needs the registration row + a check of every other HIP extractor's registration status; the fix is mechanical but out of scope for the import-state PR. - The
test_cli_parse_long_only_argsfailure on HIP-enabled builds (--threads abcexits via a signal instead of exit(1) inside the test'sfork()child). Pre-existing on any HIP-enabled build; reproduces on master without my changes once-Denable_hip=true -Denable_hipcc=trueis set. Likely a HIP-runtime static-constructor interaction with the test's pipe-redirected stderr; needs its own investigation.
References¶
- ADR-0519 — the resulting decision and rollout sequence.
- ADR-0514 — the predecessor that closed the container-side HIP exposure gap.
- ADR-0468 — first real HIP feature kernel (
float_adm_hip); the predecessor PR whose existence invalidated the "stays unwired until the first feature kernel lands" comment. - ADR-0212 — HIP backend scaffold + header-purity invariant.
- ADR-0214 —
places=4(1e-4) cross-backend numeric gate. docs/state.mdrowT-HIP-IMPORT-STATE-ENOSYS-2026-05-18— the Open bug closed by this work.