ADR-1217: The GPU float-VIF kernels read vif_sigma_nsq and vif_enhn_gain_limit from their options¶
- Status: Proposed
- Date: 2026-09-07
- Deciders: Lusoris
- Tags:
cuda,sycl,hip,correctness,feature-extractor,testing
Context¶
float_vif exposes vif_sigma_nsq (alias snsq, neural noise variance, default 2.0, range 0.0–5.0) and vif_enhn_gain_limit (alias egl, enhancement-gain clamp, default 100.0, range 1.0–100.0) as VMAF_OPT_FLAG_FEATURE_PARAM options. The CPU threads both through vif_statistic_s() into the per-pixel statistic in core/src/feature/vif_tools.c.
The CUDA, SYCL and HIP twins all declare both options in their option tables — with the same names, aliases, defaults and ranges as the CPU — but their compute kernels hardcoded the default values as local constants:
const float vif_sigma_nsq = 2.0f;
const float vif_egl = 100.0f;
const float sigma_max_inv = (2.0f * 2.0f) / (255.0f * 255.0f);
The host never forwarded the option values, and init() on all three validates only vif_kernelscale. A non-default value is therefore accepted, range-checked, and folded into the derived feature name (ADR-1183) — and then discarded.
This is reachable from a shipped model. model/vmaf_float_v0.6.1neg.json sets "vif_enhn_gain_limit": 1.0 on all four VIF-scale features, which is precisely what makes it the "NEG" (no-enhancement-gain) model. On the CPU that clamps g to 1.0; on any GPU backend the kernel clamped to 100.0 instead. The resulting non-NEG scores were published under the NEG feature keys, so the divergence was invisible in the output schema.
float_vif_hip had no parity test at all — test_hip_vif_parity.c covers the integer vif_hip twin — and the CUDA and SYCL float-VIF parity tests both ran with NULL options, where the hardcoded constants are the correct values.
Decision¶
We will pass vif_sigma_nsq, vif_enhn_gain_limit and the derived sigma_max_inv into the CUDA, SYCL and HIP float-VIF compute kernels as arguments, computed on the host from the extractor state, and delete the hardcoded constants. sigma_max_inv is derived exactly as the CPU derives it in vif_statistic_s() — powf(nsq, 2.0f) evaluated in float, divided by 255.0 * 255.0 in double, narrowed to float — so the default path stays bit-identical. Each backend's float-VIF parity test gains a variant that pins the NEG options and asserts parity on the derived key; HIP gets its first float-VIF parity test.
Alternatives considered¶
| Option | Pros | Cons | Why not chosen |
|---|---|---|---|
| Pass the values into the kernels (chosen) | The advertised surface starts working on GPU; the NEG model produces NEG scores on every backend; default path unchanged | Three more kernel arguments per launch | — |
Reject non-default values in init() with -EINVAL | Small, honest, no numerical risk | Turns a silent wrong answer into a hard failure for the shipped NEG model on every GPU backend — the model would stop loading rather than start being right | Removes a working user surface to avoid implementing it |
| Drop the two options from the GPU option tables | Makes the twins' declared surface match their behaviour | The derived feature name would then differ from the CPU's for the same model, so model lookup would miss; and it deletes a documented option | Breaks ADR-1183 name derivation and removes a surface |
Recompute sigma_max_inv in the kernel from vif_sigma_nsq | One fewer argument | powf in device code is not guaranteed to round identically to the host powf, risking a default-path drift for no benefit | Needless bit-exactness risk |
Consequences¶
- Positive:
vmaf_float_v0.6.1neg.jsonnow produces NEG scores on CUDA, SYCL and HIP, not just on CPU.--feature float_vif_<backend>:snsq=<x>and:egl=<x>do what they say. - Negative: any GPU run of the NEG model recorded before this change is invalid and must be re-scored. No in-tree snapshot or golden fixture covers a GPU NEG run, so nothing in the repo needs regenerating.
- Neutral / follow-ups: the default path is unchanged — the kernel arguments carry exactly the constants that were inlined before, and the CUDA / SYCL default-options parity tests stay green. The Metal float-VIF twin was checked and already threads both options through; it needs no change.
References¶
- Findings 135 / 136 / 137 from the twin-drift sweep: the CUDA, HIP and SYCL float-VIF kernels hardcode
vif_sigma_nsq = 2.0fandvif_enhn_gain_limit = 100.0feven though both are advertised as settable options with non-degenerate ranges; the host never forwards them andinit()never rejects non-default values. model/vmaf_float_v0.6.1neg.json— sets"vif_enhn_gain_limit": 1.0on all four VIF-scale features.- ADR-0214 — the GPU parity CI gate and its places=4 cross-backend tolerance.
- ADR-1183 — the derived feature-name key (
vif_scale0_egl_1_snsq_1.5) the parity variants read. - ADR-1216 and research digest 2033 (both land in PR #1375, one feature earlier) — the same default-options blind spot: an option whose default makes the CPU and GPU paths coincide is not covered by a default-options parity test. Referenced by number rather than by link because this branch does not carry those files.