Add AMD GPU (ROCm/HIP) support to the Caspar backend - #465
Conversation
Adds AMD GPU support to Caspar so its generated kernels and runtime also
build and run on AMD GPUs via HIP/ROCm, while leaving the default NVIDIA
CUDA build unchanged (enabled with use_hip=True / -DUSE_HIP=ON; off by
default).
Review in this order:
1. symforce/caspar/source/runtime/cuda_to_hip.h (new): a compatibility
header that maps the CUDA spellings Caspar emits and uses (cudaMalloc,
__syncthreads, cooperative-group reductions, CUB primitives, the
runtime API) onto their HIP equivalents, and supplies device-side
fallbacks where HIP lacks a cg:: primitive (reduce, labeled_partition).
2. code_generation/library.py and source/templates/*.jinja: the codegen
gains a use_hip/hip_arch path; the generated build file and kernel
templates emit the compat include and HIP-correct spellings, so the
symbolic kernel definitions are unchanged.
3. source/runtime/*.cu, memops.cuh, pybind_array_tools.{cc,h}: the
runtime HIP-compat (shared-memory atomics, the cooperative-group
reduction fallback, and a host-side pointer-attribute lookup).
4. A small Windows build fix (C++17, git path separator) for the
all-clang ROCm toolchain.
5. README: how to build a generated library for AMD GPUs.
Authored with assistance from Claude.
Test Plan:
The full code-generation pipeline (generate -> HIP-compile -> execute on
GPU -> verify numerical output) was exercised on real AMD hardware,
Linux CDNA2 (gfx90a) and RDNA3 (gfx1100) and Windows RDNA4 (gfx1201):
compile_caspar_library(caslib, out_dir, use_hip=True, hip_arch="gfx90a")
# generated kernel executes on GPU; output matches the CUDA path.
The default NVIDIA CUDA build (use_hip=False) is unchanged.
The generated CMake template pinned CMAKE_HIP_ARCHITECTURES to gfx90a when unset, but the pin ran after project(... LANGUAGES ... HIP), which already enables the HIP language and resolves the architecture. The block was dead code that could only mislead, or become a live footgun if file order ever changed. project(... LANGUAGES ... HIP) already honors an explicit -DCMAKE_HIP_ARCHITECTURES, otherwise auto-detects the host GPU, and errors on a no-GPU build host. Removing the pin lets that single mechanism decide the arch, so a user on a non-gfx90a card no longer risks a silently mistargeted default. The hip_arch argument still threads through as -DCMAKE_HIP_ARCHITECTURES. This work was authored with the assistance of the Claude AI assistant.
|
Thanks for the contribution, this looks interesting. I'm a little torn on whether we can commit to the maintenance burden of an AMD implementation here especially given the lack of tests. I think I'm going to leave this open and see how much interest there is from users in AMD support? |
|
At minimum I could add a build-only github action workflow. Would that be sufficient? |
|
I've been building on top of this PR (running it on gfx1151 / ROCm 7.2) and hit a real data race in Caspar's solver runtime that's worth flagging here, since it's in the HIP-specific code path this PR introduces. Bug: On gfx1151 this reliably manifested as a real race: intermittent NaN/-nan in Suggested fix — a single trailing if (group.thread_rank() == 0) {
shared_tmp[offset] = tot;
}
}
+ __syncthreads();
}Verification: with the fix, the same determinism test (5 within-build reruns + 3 fresh-build reruns) produced the bit-identical, correct result ( Honest caveat on mechanism: I initially attributed this to a write-after-read hazard across the I also independently checked Happy to open this as a proper PR against your branch if that's easier to review/merge than a diff in a comment — just let me know which you'd prefer. |
This PR adds AMD GPU support to Caspar so its generated kernels and runtime build and run on AMD GPUs through ROCm/HIP, while keeping the default NVIDIA CUDA build unchanged. It is enabled with
compile_caspar_library(..., use_hip=True, hip_arch=...)(or-DUSE_HIP=ONin the generated build); when off, the build is exactly as before.The CUDA spellings Caspar emits and uses --
cudaMalloc,__syncthreads, cooperative-group reductions, CUB primitives, and the runtime API -- are mapped to their HIP equivalents through a small compatibility header (source/runtime/cuda_to_hip.h). On an NVIDIA build the header is a transparent passthrough; on a ROCm build it aliases thecuda*symbols tohip*and supplies device-side fallbacks where HIP lacks a cooperative-groups primitive (cg::reduce,cg::labeled_partition). Because the mapping lives in one header and the codegen templates emit it, the symbolic kernel definitions are unchanged.The code generation gains a HIP path:
code_generation/library.pytakesuse_hip/hip_arch, and the Jinja build-file and kernel templates emit the HIP-correct includes and theUSE_HIPCMake option. The runtime sources get the corresponding HIP-compat (shared-memory atomics, the reduction fallback, and a host-side pointer-attribute lookup in the pybind layer). A small Windows build fix (C++17, git path separator) is included for the all-clang ROCm toolchain.How to build for AMD GPUs
Pass
use_hip=Trueand the target architecture when compiling a generated library:Set
hip_archto the target AMD GPU (for examplegfx90afor CDNA2 orgfx1100for RDNA3). The ROCm build needs a HIP-enabled compiler (hipcc/amdclang++) and thehipandhipcubpackages.Validation
The full code-generation pipeline (generate, HIP-compile, execute on GPU, verify numerical output) was exercised on real AMD hardware: Linux CDNA2 (gfx90a) and RDNA3 (gfx1100), and Windows RDNA4 (gfx1201). The generated kernels (cooperative-group reductions, shared-memory atomics, scatter/gather) produce results matching the CUDA path. The default NVIDIA CUDA build is unchanged.