Research digest: Real integer_ssim GPU kernels (ADR-0564)¶
Correctness investigation¶
The root cause was traced by diffing the provided_features field across the three GPU backend extractors registered for "ssim":
integer_ssim_cuda.c(pre-ADR-0564):provided_features = {"float_ssim", NULL}. The kernel wasssim_score.cu, an 11-tap floating-point Gaussian.integer_ssim_hip.c(pre-ADR-0564): state struct held fivefloat *device buffers (d_ref_mu,d_cmp_mu, etc.) — the float_ssim intermediate layout.integer_ssim_sycl.cpp(pre-ADR-0564): novmaf_fex_integer_ssim_syclsymbol at all; the lookup silently fell through to the CPU twin.
In every case, calling vmaf_use_feature("ssim", NULL) on a GPU backend dispatched either the wrong metric or the CPU fallback.
CPU algorithm analysis¶
The CPU integer_ssim (integer_ssim.c) computes per-pixel moments using a 9-tap Gaussian with integer weights [2,9,28,55,68,55,28,9,2] (sum=256). It uses int64_t throughout the accumulation to avoid floating-point rounding. The SSIM formula:
c1 = (K1 * samplemax * w)^2 // w = per-pixel accumulated weight
c2 = (K2 * samplemax * w)^2
num = (2*mux*muy + c1) * (2*(xy*w - mux*muy) + c2)
den = (mux^2 + muy^2 + c1) * (x2*w - mux^2 + y2*w - muy^2 + c2)
ssim = w * num / den
is computed in double after the int64 moment accumulation. Boundary-truncation (not clamping) means pixels near the edge use a reduced kernel; w accumulates only the in-bounds tap weights.
CUDA two-pass design rationale¶
Separating horizontal and vertical passes avoids large shared-memory tiles. Each pass processes one 16×8 block:
- Pass 1 writes six
int64_tarrays of sizeW×H(one per moment: mux, muy, x2, xy, y2, w). - Pass 2 reads those arrays, applies the 9-tap vertical kernel, computes the SSIM formula in
double, and accumulates per-block double partial sums. - The host accumulates all partial sums and divides by the sum of partial weights.
Warp reduction uses paired int32 shuffles for int64 lane values (pre-sm70 compatible because __shfl_down_sync is used on 32-bit halves of the 64-bit value).
Parity result (CUDA): all 48 frames of the Netflix 576×324 8bpc golden fixture produce diff=0.00e+00 vs CPU. Pooled mean ssim: 0.86138600 (CPU) = 0.86138600 (CUDA).
HIP wavefront-64 design¶
The HIP kernel (integer_ssim_score.hip, pre-existing from ADR-0533) uses wavefront-64 shuffles (GCN/RDNA) with ISSIM_WARP_SIZE=64. Int64 lane sums use issim_warp_sum_i64 which packs hi/lo into paired int32 shuffles — analogous to the CUDA paired-shuffle approach.
The host glue rewrite (integer_ssim_hip.c) changes:
- 5
float *buffers → 6int64_t *device buffers - Readback: two slots (double partials, int64 weights)
collect:ssim = total_ssim / (double)total_wgt
SYCL fp64-free constraint¶
Intel Arc A380 Level Zero rejects entire SPIR-V modules that use fp64 inside kernel lambdas (as opposed to merely not advertising the fp64 capability — the driver treats the entire .spv as invalid). ADR-0220 documents this.
The SYCL extractor uses float for the SSIM formula inside the kernel. Int64 moment accumulation is retained (int64 is supported). The int64 moments are converted to float before the SSIM formula step. This introduces at most 1 ULP of rounding vs the CPU's double formula, limiting agreement to places=4–5.
reduce_over_group aggregates both float d_partials and int64_t d_wgt across work groups. The host divides sum(float_partials) by (double)sum(int64_wgt).
Accepted precision targets¶
| Backend | Algorithm | Expected precision vs CPU |
|---|---|---|
| CUDA | int64 moments + double SSIM | places=6 (confirmed: diff=0) |
| HIP | int64 moments + double SSIM | places=6 (hardware gate pending) |
| SYCL | int64 moments + float32 SSIM | places=4–5 (fp64-free constraint, ADR-0220) |
Files touched¶
core/src/feature/cuda/integer_ssim/integer_ssim_score.cu(new)core/src/feature/cuda/ssim_cuda.c(new)core/src/feature/cuda/ssim_cuda.h(new)core/src/feature/hip/integer_ssim_hip.c(rewritten)core/src/feature/sycl/integer_ssim_sycl.cpp(appended extractor)core/src/feature/feature_extractor.c(extern + list entries)core/src/meson.build(new PTX and C source entries)