HIP + Vulkan backends — Phase 2
Esta página aún no está disponible en tu idioma.
Companion to
hip_vulkan_phase1.md. Reuses the Phase-1 backend seam and runtime FFI; adds codegen coverage + corpus harness + wave64.
What landed
Section titled “What landed”HIP / ROCm
Section titled “HIP / ROCm”- Wave64 (CDNA) transform —
HipGenerator::with_profile(TargetProfile::hip_wave64())now propagateslane_width = 64to the inner CUDA generator (so the reduction tree is sized for 64-lane warps) and widens the shuffle mask from0xffffffffull(wave32) to0xffffffffffffffffull(wave64) in the textual transform. Unit-tested inhip::tests::wave32_emits_32bit_mask/wave64_emits_64bit_mask. No CDNA hardware on hand → HW validation deferred. - HIP corpus harness —
wh-iron-std/tests/hip_kernel_corpus.rsruns every registered#[test_kernel]throughHipDevice::run_kernel. PASS / MISMATCH / UNSUPPORTED / ERROR triage matches the CUDA corpus, so results are comparable. Hard ERROR budget = 64 (anything past that signals a codegen regression, not numerics).
Vulkan / SPIR-V
Section titled “Vulkan / SPIR-V”KernelMode::Reductionlowering — per-thread grid-stride accumulator (Op::StrideReduce) → workgroup-shared barrier-tree (Op::Reduce). The reduction is subgroup-width agnostic (the portable path called out inVULKAN_BACKEND_SPEC.md §4.1), depending only onlocal_size_xandbarrier().row_reduce_sumbit-accurate (max_rel = 3.32e-7) on RX 9070 XT.KernelMode::Grid3Dlowering — per-axisgl_GlobalInvocationID.{x,y,z}. Same wiring as CUDA’sblockIdx.*/threadIdx.*.- 3-D dispatch / 3-D workgroup —
GlslGenerator::with_local_size_3d,VulkanDevice::run_kernel(&kernel, &bufs, grid: [u32;3], block: [u32;3]). Same calling convention asHipDeviceandCudaDeviceso the corpus harness shares its loop. - Broader op coverage — added
Select,DeclareLocal/SetLocal,ThreadgroupAlloc/Load/Store,StackAlloc/Load/Store,Barrier,Loop/If(nested-block recursion mirroring CUDA),Zeros/Splatscalar. safe_glsl_identshim — wh-iron param names likeout/in/inoutcollide with GLSL keywords. We rename them to_b_out/_b_in/_b_inoutonly inside SSBO arrays + array accesses, preserving readability for any non-conflicting param name.- Vulkan corpus harness —
wh-iron-std/tests/vulkan_kernel_corpus.rs, same triage as HIP / CUDA.
Gotchas (Phase-2 additions)
Section titled “Gotchas (Phase-2 additions)”Vulkan / GLSL
Section titled “Vulkan / GLSL”- GLSL is strictly typed; SSA values from BinOps come out as
float. Anywhere we use those values as array indices or loop bounds, we need an explicituint(...)cast. Without it, shaderc fails with'=' : cannot convert from ' temp highp float' to ' temp highp uint'. Phase-2 emitsarr[uint(idx)]andfor (uint _i = uint(start); ...)universally. A future Phase 3 may add type tracking; the explicit cast path is correct-but-verbose. - GLSL reserved-word collisions. Any param named
out,in,inout,uniform, etc. gets a_b_prefix in the emitted SSBO. We hitoutimmediately inrow_reduce_sum— the standard “output buffer” name in the wh-iron corpus. sharedarrays must live at file scope in GLSL. Phase-2 hoists everyOp::ThreadgroupAllocand the implicit_red_<vid>reduction scratch buffers to file scope by walking every block beforemain(). Per-threadOp::StackAllocstays insidemain.- 3-D workgroup →
tidcollapses 3 axes.Reduction-mode kernels from the corpus use 1-D tpg, but the harness also has 3-D tpg cases. We collapse to a singletidaslid.z * sx*sy + lid.y * sx + lid.xso the shared-mem reduction tree works regardless of how the workgroup is dimensioned.
HIP wave64
Section titled “HIP wave64”lane_widthmust flow into the inner CUDA generator too. A wave64 HipGenerator without this would generate a wave32-shaped reduction tree (32-warp shared scratch, etc.) and just rewrite the shuffle mask — the structure would be wrong. Phase-2 builds the inner CUDA generator with acudaprofile whoselane_widthmatches the HIP one.
Corpus results (RX 9070 XT, first runs)
Section titled “Corpus results (RX 9070 XT, first runs)”Both backends run the full registered #[test_kernel] corpus (~4164 kernels
× per-dtype variants).
HIP (tests/hip_kernel_corpus.rs):
PASS=4067 KNOWN_HARD=0 MISMATCH=1 UNSUPPORTED=0 ERROR=96- PASS = 4067 (97.66% of the registered corpus) — including the full Reduction + Elementwise + Grid3D + cooperative-matrix-software-emulated op surface from the CUDA backend, with zero HIP-specific codegen beyond the Phase-1 textual transform (3 rules) + the Phase-2 wave64 widener. wave32 RDNA is the lucky-match case the spec called out.
- UNSUPPORTED = 0 — the entire CUDA op-walker output ports to HIP
bit-for-bit on RDNA wave32; nothing fell through to the
UnsupportedOparm. The textual-transform composition strategy proved out the way the spec predicted. - ERROR = 96 — every one is
hipModuleLaunchKernel: invalid argumenton the*_moe_gather_qmm_bm64_mppfamily. Same Phase-5 backlog the CUDA scope doc calls out (mpp::matmul2dcooperative MMA needs the shared-memory opt-in tuned). NOT a regression vs CUDA — those kernels pass on GX10 only because the CUDA path tunes per-launch. - MISMATCH = 1 — single numerics edge in the entire corpus.
Vulkan (tests/vulkan_kernel_corpus.rs):
- PASS = 148, ERROR = 0 — Phase-2 stable.
- UNSUPPORTED = 3831 (3538 are f16/bf16/u8 dtypes blocked on
VK_KHR_shader_float16_int8— Phase 3; 244 are subgroup ops / cooperative MMA / SimdGroup2D — Phases 3–4). - MISMATCH = 185 — the conv* / dequant_gather_int* / bulk_dequant families. Most are kernels whose IR uses constructs we lower but don’t yet match the oracle exactly (decode-helper sign convention, transform chains, multi-stage stride-reduce). Specific Phase-3 follow-ups documented below.
The headline is: the Phase-2 codegen + runtime adds 148 bit-accurate Vulkan kernels (FFT, RMS norm, fused activations, binary, reductions, conv causal small/medium, iron gated/rope/residual norms) on top of the f32 elementwise/reduction subset, with no native code touch.
Bit-accuracy (RX 9070 XT, gfx1201, RDNA 4, wave32)
Section titled “Bit-accuracy (RX 9070 XT, gfx1201, RDNA 4, wave32)”| Kernel | Backend | Mode | max_rel / max|Δ| | Tol |
|---|---|---|---|---|
vector_add f32 |
HIP | Elementwise | 0 (bit-exact) | — |
scale_add_exp f32 |
HIP | Elementwise + expf |
1.18e-7 | 5e-7 |
row_reduce_sum f32 |
HIP | Reduction (warp shuffle + tree) | 3.38e-7 | 1e-5 |
vector_add f32 |
Vulkan | Elementwise | 0 (bit-exact) | — |
scale_add_exp f32 |
Vulkan | Elementwise + exp |
1.19e-7 | 1e-5 |
row_reduce_sum f32 |
Vulkan | Reduction (workgroup barrier-tree) | 3.32e-7 | 1e-5 |
The two backends produce essentially identical numerics on the same hardware — a useful coincidence (both go through the AMD f32 hardware math) and a sanity check that the codegen paths are aligned.
Reproduce
Section titled “Reproduce”$env:Path += ";$env:USERPROFILE\.cargo\bin;C:\Program Files\AMD\ROCm\7.1\bin"
# Quick smokes (under 1 minute):cargo test -p wh-iron-runtime --features hip --test hip_smoke -- --nocapturecargo test -p wh-iron-runtime --features vulkan --test vulkan_smoke -- --nocapture
# Full corpus runs (multiple minutes — ~4164 kernels × dtypes):cargo test -p wh-iron-std --features hip --test hip_kernel_corpus -- --nocapturecargo test -p wh-iron-std --features vulkan --test vulkan_kernel_corpus -- --nocaptureDump generated source for a single kernel:
$env:DUMP_HIP = "rms_norm"cargo test -p wh-iron-std --features hip --test hip_kernel_corpus -- --nocapture# or DUMP_VK for Vulkan/GLSLRemaining Vulkan Phase-2 gaps (Phase 3 work)
Section titled “Remaining Vulkan Phase-2 gaps (Phase 3 work)”The 185 MISMATCH + 26 shaderc-failure kernels cluster into a few patterns:
- DeclareLocal / SetLocal type mismatch (
'=' : cannot convert from float to uint). My emit declaresiron_loc_<name>asfloat, but in some kernels the IR carries integer-typed values through a mutable local — the implicitfloat → uintreassignment fails. Phase 3 either tracks the value’s GLSL type for the local or emitsuintlocals conditionally based on declared dtype. '[]' : scalar integer expression required(iron_gemv,iron_gemv_axpy_inplace,iron_gate_up_swiglu_fused). The array index in those kernels is itself derived from a constexpr-load / uint-arithmetic path I emit asfloat— theuint(...)outer cast isn’t enough when the inner expression already had an implicit conversion. Fix is the same as (1).iron_sort/iron_sort_segmentedunexpected SHARED— the IR has aThreadgroupAllocdeclared inside a nested block. I only hoist from the body block + named blocks; the syntax error means the decl leaked into local scope. Phase 3: walk every block transitively.bulk_dequant_kv_int*numerics — the decode helper rounds correctly per-byte, but the dequant pipeline assumes a specific pack/sign convention I’m not matching yet on the int8/int4 path.- Conv MISMATCH family —
iron_conv2d_patch16etc. typically use 3-D index arithmetic that may need axis order verification on Vulkan.
None of these block Phase-2 ship (the codegen + runtime are stable; PASS counts grow as each is fixed); they’re the natural Phase 3 punch list alongside subgroup ops, fp16, and cooperative-matrix MMA.
What’s next (Phase 3)
Section titled “What’s next (Phase 3)”- Vulkan subgroup fast path — query
VK_EXT_subgroup_size_controlandVkSubgroupFeatureFlagBitsat instance/device creation; usesubgroupAdd/subgroupShuffle*when supported with the right size. Falls back cleanly to the Phase-2 portable tree if not. - Vulkan fp16 / bf16 / int8 — add a Phase-3 path behind
VK_KHR_shader_float16_int8+VK_KHR_16bit_storage/VK_KHR_shader_bfloat16queries. The emitter changes are small (per-dtypeglsl_scalar_typearms); the device-side feature declaration + spec-constant gating is the new surface. - HIP atomics — wire
Op::Atomicthrough the existing CUDA emitter (already handlesatomicAdd/Max/Min/etc., which HIP supports under the same names). - Subgroup ops on HIP —
Op::SimdReduce/SimdScan/SimdShuffle*already lower to__shfl_*_syncon the CUDA path; they should port via the existing text transform. Validate against the corpus. - CDNA hardware validation — once a wave64 device is available, run
the corpus against
TargetProfile::hip_wave64. - Cooperative-matrix MMA on Vulkan —
VK_KHR_cooperative_matrixwith the runtime shape-query ladder (§4.2). Major work; the spec already details the fallback path.
