- [How CUDA Patterns Map to HIP: Common Language and API Differences]
- [Avoiding Memory-Access Pitfalls: Memory Model, Synchronization, and Thread Mapping]
- [Squeezing RDNA/GCN: Performance-Tuning Techniques for AMD GPUs]
- [Practical Toolchain: hipify, rocprof, and Debugging Workflows]
- [Validation and Benchmarks: Platform-Specific Pitfalls and What to Watch]
- [Practical Porting Checklist — Step-by-Step Protocol]
Porting CUDA kernels to HIP is usually quick at the surface level, but the real work starts when you re-optimize for AMD silicon: wavefront width, register pressure, and the memory hierarchy determine whether a port will merely run or actually perform. Treat the port as a hardware-aware re-architecture rather than a pure mechanical translation.
Your build completes, tests pass, and yet your kernels throughput lags the reference — low GPU utilization, long stall times in the memory unit, and kernel runtimes that don’t improve despite obvious CPU-side tweaks. That is the symptom set this guide addresses: the port is functionally correct but misaligned with AMD execution and memory primitives, which means profiling, targeted rewrites, and platform-aware compile options are the only path to peak performance.
How CUDA Patterns Map to HIP: Common Language and API Differences
Keep the first rule simple: hip is a portability layer and a language dialect — it maps a large portion of CUDA’s runtime and kernel syntax, but small differences matter for correctness and for performance.
Use
hipify-clang/hipify-perlto translate code as a first pass.hipify-clangparses CUDA into an AST and does the safest translation for complex code;hipify-perlis faster for trivial replacements but less robust for templates and macros. Use the clangen-based tool as your baseline for non-trivial code.-
Kernel-launch mapping:
- HIP supports the
<<<>>>syntax andhipLaunchKernelGGL. When HIP useshipLaunchKernelGGL, the macro requires the first five launcher parameters:kernelName,gridDim,blockDim,dynamicShared,stream. That difference matters when you rely on optional<<<...>>>args in CUDA.HIP_KERNEL_NAMEwrappers may be injected by hipify for templated kernels.
- HIP supports the
Example — minimal CUDA → HIP translation (before / after):
// CUDA
__global__ void saxpy(float a, const float *x, float *y, int n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) y[i] = a * x[i] + y[i];
}
cudaMalloc(&d_x, n*sizeof(float));
cudaMemcpy(d_x, h_x, n*sizeof(float), cudaMemcpyHostToDevice);
saxpy<<<(n+255)/256, 256>>>(a, d_x, d_y, n);
cudaDeviceSynchronize();
// HIP
#include <hip/hip_runtime.h>
__global__ void saxpy(float a, const float *x, float *y, int n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) y[i] = a * x[i] + y[i];
}
hipMalloc(&d_x, n*sizeof(float));
hipMemcpy(d_x, h_x, n*sizeof(float), hipMemcpyHostToDevice);
hipLaunchKernelGGL(saxpy, dim3((n+255)/256), dim3(256), 0, 0, a, d_x, d_y, n);
hipDeviceSynchronize();
API mapping cheat-sheet (common items):
| CUDA | HIP | Notes |
|---|---|---|
cudaMalloc |
hipMalloc |
Same semantics; check return hipError_t
|
cudaFree |
hipFree |
— |
cudaMemcpy |
hipMemcpy |
Same direction enums map (hipMemcpyHostToDevice) |
cudaMemcpyAsync |
hipMemcpyAsync |
Same stream semantics |
cudaStream_t |
hipStream_t |
Replace directly |
cudaGetLastError() |
hipGetLastError() |
HIP semantics differ — check immediately after launch. |
cuBLAS |
rocBLAS/hipBLAS
|
Library mappings exist; see porting guide. |
Practical notes:
- Dynamic parallelism (device-launched kernels) is not supported in HIP on many targets — plan to flatten control where present.
- Avoid assuming CUDA's behavior for
cudaGetLastError—hipGetLastErrormay only reflect the immediately preceding runtime call; therefore call and check it right after launches during debug.
Avoiding Memory-Access Pitfalls: Memory Model, Synchronization, and Thread Mapping
Memory-bound kernels fail on AMD for different reasons than they fail on NVIDIA. Pay attention to access patterns, on-chip scratch (LDS), and wavefront behavior.
Architecture reality check: AMD hardware exposes differing wavefront sizes (the unit analogous to CUDA’s warp). Older GCN targets use wave64; RDNA and newer GPUs frequently use a native wave32 execution but many devices support 32 or 64; you cannot assume
warpSize == 32. Test the device and write lanes generically. Hardware specs and per‑GPU wave sizes are documented in ROCm device tables.Unified/managed memory is supported on many AMD product lines (Vega and later), but behavior depends on kernel-mode driver and HMM/XNACK configuration. Use
hipMallocManaged()only after checkinghipDeviceAttributeManagedMemory, and setHSA_XNACK=1for system-allocator-managed unified memory where required. Treat page-migration behavior as an explicit test case rather than a drop-in replacement.
Code snippet to detect managed-memory support:
int managed = 0;
hipDeviceGetAttribute(&managed, hipDeviceAttributeManagedMemory, device_id);
if (managed) {
hipMallocManaged(&ptr, N * sizeof(float));
}
-
Synchronization and warp/wave intrinsics:
-
__syncthreads()exists and behaves as expected for block-level barriers. - Cross-lane intrinsics (shuffle, ballot, vote) exist in HIP, but
__ballotreturns a 64-bit mask on AMD; do not assume a 32-bit result. PreferwarpSize-aware code and testhasWarpShuffle/hasWarpBallotdevice properties during runtime guard.
-
-
Fences and cache control:
-
__threadfence_systemsemantics differ and may not flush L2 the same way on all ROCm toolchains. The porting guide warns thatthreadfence_systemfunctionality may be unavailable; workarounds (likeHSA_DISABLE_CACHE=1) exist but carry costs. Profile before and after any such global cache-control changes.
-
Important: During port debugging call
hipGetLastError()immediately after kernel launches; the semantics differ fromcudaGetLastError()and failing to check it timely will hide launch-time errors.
Squeezing RDNA/GCN: Performance-Tuning Techniques for AMD GPUs
Getting the last 10–50% is where you earn your cred as a kernel engineer. AMD throughput depends on how you feed the vector ALUs across wavefronts and how you manage per-wave registers and LDS.
-
Start from the hardware constraints:
- Wavefront width (32/64) controls how many lanes must be busy to avoid serializing divergent work. Choose block sizes that are multiples of the native wave width when possible.
- VGPR (vector GPR) and SGPR pressure limit concurrent waves per CU; excessive per-thread registers reduce occupancy. Use compiler feedback and
rocprofto see active wave counts.
-
Compiler flags that help tuning:
- Use
hipcc --offload-arch=gfx90a(or the targetgfxvalue for your GPU family) to generate code for the right GPU, and iterate with-O2/-O3.hipccis a wrapper around HIP-Clang/amdclang and accepts--offload-arch. - On RDNA you may toggle
-mwavefrontsize64/-mno-wavefrontsize64to select wave64 vs wave32 for codegen experiments, and-mcumodeto test CU vs WGP scheduling modes where available. Use these flags to experiment and re-profile.
- Use
-
Practical tuning levers (ordered by expected impact):
-
Memory-layout and alignment — convert AoS to SoA for vector math, pack loads into vector types (e.g.,
float4) where you can, and ensure contiguous accesses across lanes. Avoid strided per-lane access patterns that break cache-line locality. -
Stage data into LDS (HIP
__shared__) for multi-lane reuse — tile-based GEMM and convolution benefit heavily from careful LDS tiling. - Reduce register pressure — hoist temporaries to shared memory when that reduces per-thread VGPRs enough to increase active waves per CU.
-
Prefer compute-friendly intrinsics — use
__shfl*/__ballot-style operations for reductions and scans inside a wave to avoid global atomics. -
Micro-benchmark — single-kernel microbenchmarks help isolate memory vs ALU bottlenecks; use
rocprofcounters to measureMemUnitStalledandVALUInsts.
-
Memory-layout and alignment — convert AoS to SoA for vector math, pack loads into vector types (e.g.,
-
Watch for platform-specific throughput quirks:
- RDNA’s SIMD32 execution sometimes makes fewer registers per wave preferable compared with legacy wave64 code patterns; rebalancing work per thread (more work per thread, fewer threads per block) can help with fewer waves but higher per-thread throughput.
Practical Toolchain: hipify, rocprof, and Debugging Workflows
A pragmatic toolchain and repeatable profiling loop will save you weeks of guesswork.
- hipify: automatic port
- Use
hipify-clangas the default porting tool; run it with acompile_commands.jsonso translation understands your build flags and include paths. Use--print-statsto see what translated cleanly and what needs manual attention.
- Use
Example:
hipify-clang -p build/compile_commands.json src/module.cu -o src/module.hip.cpp --print-stats
- Build with hipcc / amdclang:
- For AMD targets prefer
hipcc(wrapper) or invokeamdclang++directly to get fine-grained flags. Always set an explicit target:--offload-arch=gfx90a(orgfx1030,gfx1100, …). Use-O3for production runs and keep-g -O0for debugs.
- For AMD targets prefer
Example:
hipcc -std=c++17 --offload-arch=gfx90a -O3 -o myapp module.hip.cpp
To test RDNA32 vs RDNA64 codegen:
hipcc -O3 --offload-arch=gfx1030 -mno-wavefrontsize64 -o myapp32 module.hip.cpp
hipcc -O3 --offload-arch=gfx1030 -mwavefrontsize64 -o myapp64 module.hip.cpp
- Profile with
rocprof:- Use
rocprof --statsor--hip-traceto collect kernel timings and activity. For counter-based profiling use an input file describing thepmccounters to collect. Outputs includeresults.stats.csvand trace JSONs you can visualize.
- Use
Example:
# input.txt: a small list of perf counters
rocprof -i input.txt ./myapp
rocprof --stats --hip-trace ./myapp # quick overview traces and CSVs
rocprof outputs results.stats.csv (per-kernel durations and averages) and results.hip_stats.csv (HIP runtime API stats). Use those to find hot kernels and disproportionate memcpy time.
- Debug with ROCgdb:
- For source-level GPU stepping and register dumps use
rocgdb. It mimicsgdband supports dumping wavefront registers (info registers) and stepping into device code on supported platforms. Run on a node with ROCm installed; ensure any SELinux/containers are configured so ROCgdb has device access.
- For source-level GPU stepping and register dumps use
Example:
rocgdb ./myapp
(gdb) break main
(gdb) run
(gdb) info registers # dumps wavefront registers
- Iterate: edit → build → profile → measure. Use the profiler CSVs as your source of truth and limit changes to one knob at a time.
Validation and Benchmarks: Platform-Specific Pitfalls and What to Watch
Validation and benchmarking are a discipline: functional correctness first, then microbench correctness, then performance budgets.
-
Library mapping and numerical parity:
- Replace CUDA libraries with their ROCm counterparts:
cuBLAS→rocBLAS(orhipBLASwrapper),cuFFT→rocFFT/hipFFT,cuDNN→MIOpen. HIPIFY automates many calls but validate math results and tolerances (FP32 reductions may differ slightly between implementations).
- Replace CUDA libraries with their ROCm counterparts:
Common pitfalls checklist (quick reference):
| Symptom | Likely cause | Quick check / fix |
|---|---|---|
| Silent kernel failure |
hipGetLastError() semantics; error swallowed |
Insert if (hipGetLastError() != hipSuccess) { ... } immediately after kernel. |
| Slow first-run kernel | Managed-memory page faults / migration | Warm pages (prefetch) or use hipMemPrefetchAsync, or enable correct HMM/XNACK settings. |
| Low occupancy despite many threads | High VGPR/SGPR use or big shared usage | Review compiler feedback, reduce in-kernel temporaries, split kernels. |
| Inconsistent perf across machines | Offload-arch mismatch or wrong HIP_PLATFORM
|
Ensure --offload-arch matches device and HIP_PLATFORM=amd is set in CI where required. |
-
Benchmarking protocol:
- Build with
-O3and--offload-archfor the target GPU. - Run microbenchmarks that isolate memory vs compute (e.g., simple vector add / memcpy / GEMM).
- Collect
rocprof --statsand inspectresults.stats.csvfor per-kernel average durations andresults.hip_stats.csvfor host-side API overhead. - Use derived metrics: achieved GB/s (bytes processed / kernel time) and GFLOPS (flops / kernel time) to compare against theoretical bandwidth/compute for the target GPU (found on ROCm spec pages).
- Build with
-
Platform-specific sandboxing:
- ROCm tools require appropriate kernel modules,
/dev/kfddevice access, and matchingROCM_PATH/HIP_CLANG_PATHin the environment to produce reliable builds and profiling runs.hipccand ROCgdb behavior depends on these paths.
- ROCm tools require appropriate kernel modules,
Practical Porting Checklist — Step-by-Step Protocol
-
Inventory & baseline:
- Run your CUDA test-suite and record golden outputs and runtimes on NVIDIA (if available).
- Add
compile_commands.jsonfor your build (CMake:CMAKE_EXPORT_COMPILE_COMMANDS=ON).
-
Automated port:
- Run
hipify-clangwith the compile DB and--print-stats. Inspect files for unsupported constructs and missing library mappings.
- Run
hipify-clang -p build/compile_commands.json src/foo.cu -o src/foo.hip.cpp --print-stats
-
Manual fixes:
- Replace driver-API-only uses with runtime equivalents or rework the logic.
- Swap CUDA-specific libraries to ROCm libs or
hipwrappers (check function availability). - Fix kernel launch argument order when hipify used
hipLaunchKernelGGLincorrectly for templates.
-
Compile & smoke-test:
- Build with
hipcctargeting your GPU:
- Build with
hipcc -std=c++17 --offload-arch=gfx90a -O3 -o myapp src/foo.hip.cpp
- For debug builds use
-g -O0so ROCgdb can step into device code.
-
Sanity profiling:
- Run
rocprof --statsto get the first pass timings and CSVs. Identify the top 3 kernels by total time.
- Run
-
Micro-optimize kernels:
- For each hot kernel: reduce register temporaries, stage reused data into
__shared__, vectorize loads/stores, and align block/thread sizes to the device wavefront width. Rebuild with-mno-wavefrontsize64vs-mwavefrontsize64experiments on RDNA to decide best codegen.
- For each hot kernel: reduce register temporaries, stage reused data into
-
Counter-based profiling:
- Create a
rocprofinput file listingpmccounters (e.g.,MemUnitStalled,VALUInsts) and runrocprof -i counters.txt ./myapp. Inspectinput.csvandresults.stats.csvto quantify memory stalls vs ALU utilization.
- Create a
-
Regression & numeric validation:
- Compare outputs against golden datasets with tolerances. When behavior differs between
rocBLASandcuBLAS, investigate algorithmic differences and test different solver/plan options.
- Compare outputs against golden datasets with tolerances. When behavior differs between
-
CI & packaging:
- Pin
ROCM_PATHand add--offload-archorGPU_TARGETSsettings to your CMake files so build servers produce reproducible binaries. NoteGPU_TARGETSis the current recommended CMake variable name for ROCm builds.
- Pin
-
Finalize:
- Sweep for error handling: ensure
hipGetLastError()checks exist and convertcudaDeviceSynchronize()checks intohipDeviceSynchronize()while checking returned errors.
- Sweep for error handling: ensure
Sources
HIPIFY: Convert CUDA to Portable C++ Code - Official HIPIFY GitHub repository and documentation; used for guidance on hipify-clang vs hipify-perl and practical hipification workflow.
GPU hardware specifications — ROCm Documentation - Per-GPU tables listing wavefront size, LDS, and cache characteristics; used to pick wave sizes and hardware constraints.
Using rocprof — ROCProfiler Documentation - rocprof usage, trace modes, and output formats (results.stats.csv); used for profiling commands and interpreting CSV outputs.
Unified memory management — HIP Runtime API (HIP docs) - hipMallocManaged, __managed__, and HMM/XNACK behavior and requirements for managed memory on AMD GPUs.
ROCm compiler reference (rocmcc / hipcc) - hipcc/amdclang flags including --offload-arch, -mwavefrontsize64 / -mno-wavefrontsize64, -mcumode, and environment variables affecting compilation.
Using El Capitan Systems: Known Issues — LLNL HPC docs - Practical debugging note: call hipGetLastError() immediately after kernel launches because its semantics differ from cudaGetLastError().
Kernel Language Syntax — HIP Documentation - hipLaunchKernelGGL parameter ordering, kernel qualifiers, and language differences between CUDA and HIP.
Kernel Language Syntax — HIP (intrinsics notes) - Cross-lane intrinsics, __ballot return width, and warp/wave cautions; used for shuffle/ballot semantics.
ROCgdb quick start — ROCgdb Documentation - How to use ROCgdb for heterogeneous (CPU+GPU) debugging, including info registers on wavefronts.
HIP porting guide — HIP Documentation - Library mapping guidance (cuBLAS → rocBLAS/hipBLAS, cuDNN → MIOpen), feature coverage, and portability notes.
Top comments (0)