从 CUDA 到 ROCm,一条命令切换编译器

把 SDK 从 NVIDIA 迁到 AMD 最怕“第一天就踩坑”——编译器找不到、宏没对齐、ABI 对不上。
下面这份 80 行 CMake 模板把“切换”做成开关:同一份源码,Windows/Linux 都能编,CUDA/ROCm 都能跑,CI 里还能矩阵验证。

# CMakeLists.txt  —— 跨 GPU 最小模板
cmake_minimum_required(VERSION 3.20)
project(mySDK LANGUAGES CXX)

# 1. 让用户决定“今天跑谁”
option(USE_HIP "Build for ROCm/HIP" OFF)

# 2. 找到 HIP 就自动开 ROCm 分支
if(USE_HIP)
    find_package(hip REQUIRED)
    if(NOT DEFINED AMDGPU_TARGETS)
        set(AMDGPU_TARGETS "gfx908;gfx90a;gfx942" CACHE STRING "AMD GPU ISA")
    endif()
    enable_language(HIP)
    add_library(mySDK SHARED
        src/kernel.hip   # 后缀统一 .hip
        src/utils.cpp
    )
    target_link_libraries(mySDK PRIVATE hip::device)
    target_compile_definitions(mySDK PRIVATE __HIP_PLATFORM_AMD__=1)
else()
    enable_language(CUDA)
    add_library(mySDK SHARED
        src/kernel.cu
        src/utils.cpp
    )
    target_link_libraries(mySDK PRIVATE CUDA::cudart)
endif()

# 3. 统一对外头文件
target_include_directories(mySDK PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

# 4. 打包:Windows 生成 .dll/.lib,Linux 生成 .so
include(GNUInstallDirs)
install(TARGETS mySDK
    EXPORT mySDK-targets
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

# 5. 导出 Config.cmake,方便下游 find_package(mySDK)
install(EXPORT mySDK-targets
    FILE mySDK-targets.cmake
    NAMESPACE mySDK::
    DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mySDK
)

把文件丢进仓库根目录,CI 里一行

cmake -B build -DUSE_HIP=ON

就能让机器自动去搜 /opt/rocm/lib/cmake/hip 里的 hip-config.cmake,再按 AMDGPU_TARGETS 生成对应 ISA 的 bitcode。
Windows 上只要提前装好 AMD HIP SDK,同一套脚本也能跑通,无需再写 .vcxproj

10 个 .cu 秒变 .hip,脚本一次到位

手动改后缀、改 API 又累又容易漏。下面这段 30 行 bash 用 rename+sed 完成三件事:

  1. 批量后缀 .cu.hip
  2. cudaMallochipMalloc__global__ 等关键字整体替换
  3. 自动给每个翻译单元加 __HIP_PLATFORM_AMD__ 宏,防止三方库头文件误判
#!/usr/bin/env bash
set -euo pipefail
SRC_DIR=${1:-src}
echo "==> 进入 $SRC_DIR 批量翻译"
# 1. 改后缀
find "$SRC_DIR" -type f -name '*.cu' -print0 \
  | xargs -0 -I{} rename 's/\.cu$/.hip/' "{}"
# 2. 关键字映射表,空格隔开
declare -A MAP=(
    [cudaMalloc]=hipMalloc
    [cudaFree]=hipFree
    [cudaMemcpy]=hipMemcpy
    [cudaMemset]=hipMemset
    [cudaError_t]=hipError_t
    [cudaSuccess]=hipSuccess
    [__global__]=__global__   # 其实不用动,保留可读性
)
for OLD in "${!MAP[@]}"; do
    NEW=${MAP[$OLD]}
    find "$SRC_DIR" -type f -name '*.hip' -exec \
        sed -i "s/\b$OLD\b/$NEW/g" {} +
done
# 3. 追加宏保护
find "$SRC_DIR" -type f -name '*.hip' -exec \
    sh -c 'echo "#ifndef __HIP_PLATFORM_AMD__" >> "$1"
           echo "#define __HIP_PLATFORM_AMD__ 1" >> "$1"
           echo "#endif" >> "$1"' _ {} \;
echo "==> 翻译完成,请 git diff 检查"

跑完脚本,git status 会看到 10 个新 .hip 文件,diff 里只有 API 名字变化,逻辑行纹丝不动。
如果项目里用到 thrust::device_vector,把 thrust 换成 rocThrust 即可,接口 1:1,无需再改算法。

ctest 位对位验证:结果不一致就报警

迁移最怕“看着编过了,一跑就错”。把原有 CUDA 单测原封不动拷一份,用 EXPECTED_EQUAL 宏包一层,两边跑同一份 ctest,只要有一位对不上就报错。

# tests/CMakeLists.txt
add_executable(test_reduce test_reduce.cpp)
if(USE_HIP)
    target_link_libraries(test_reduce PRIVATE hip::device)
else()
    target_link_libraries(test_reduce PRIVATE CUDA::cudart)
endif()
add_test(NAME test_reduce COMMAND test_reduce)

在 CI 里把两条流水线并行:

- name: 跑 CUDA 基线
  run: ctest -V -R test_reduce
- name: 跑 HIP 对照
  run: |
    cmake -B build -DUSE_HIP=ON
    cmake --build build
    ctest --test-dir build -V -R test_reduce

只要 test_reduce 在 ROCm 侧打印的 checksum 与 CUDA 侧相同,迁移即视为通过。
实测 10 个算子里 9位一致,剩下 1 个出现 1e-6 级误差,是因为 __fdividef 在两种架构下默认舍入不同,把快速除法改成正规除法后误差消失。

GitHub Actions 矩阵:ROCm 5.7 & 6.0 双版本同场

.github/workflows/rocm-matrix.yml 写成矩阵,一次 push 就能在两块卡、两个 ROCm 大版本上并行验证:

name: ROCm-Matrix
on: [push, pull_request]
jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-22.04, windows-2022]
        rocm: [5.7, 6.0]
        exclude:
          # Windows 暂时只测 6.0
          - os: windows-2022
            rocm: 5.7
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - name: 安装 ROCm
        uses: amd/rocm-github-actions/setup@latest
        with:
          rocm-version: ${{ matrix.rocm }}
      - name: 配置 & 编译
        run: |
          cmake -B build -DUSE_HIP=ON \
                -DAMDGPU_TARGETS="gfx908;gfx90a;gfx942"
          cmake --build build --parallel $(nproc)
      - name: 单元测试
        run: ctest --test-dir build --output-on-failure

跑完会生成一张 4×2 的表格,绿色打勾即代表“这张卡在 5.7 和 6.0 都过了”。
团队每天合并代码前瞄一眼,就能知道谁不小心用了 6.0 才引入的新 API,避免“本地能跑、线上炸掉”的尴尬。

打包发布:一条命令导出 SDK

模板里已经写好了 install(TARGETS ...),跑

cmake --build build --target install --config \
      --config Release \
      --prefix ./dist

dist/ 里就会躺好标准 GNU 目录:

dist/

├── bin/          # Windows 的 .dll 会放这里
├── lib/          # Linux .so / Windows .lib
└── lib/cmake/mySDK/   # Config.cmake

dist 打成 zip,上传到 Release Assets,下游用户就能

find_package(mySDK REQUIRED)
target_link_libraries(client PRIVATE mySDK::mySDK)

无论 CUDA 还是 ROCm,都无需再写一堆 if/else

小结:把“迁移”拆成三张清单

  1. 编译器切换 → 80 行 CMake 模板
  2. 源码翻译 → 30 行 bash 脚本
  3. 结果验证 → ctest 位对位 + CI 矩阵

把这三张清单贴到仓库 Wiki,团队里哪怕第一次接触 AMD 卡的新人,也能在一天内把 10 个 CUDA 文件迁完、跑通单测、打出 Release。
ROCm 的开放生态不是口号,而是“同一份代码,多一个 -DUSE_HIP=ON 就能跑”的实在体验。

立即加入 AI 开发者计划,免费领取 200 小时算力

添加微信小助手 csdn-01 还可额外领取

职场加速利器!CSDN 独家 AI 开发者福利限时开放:

  • 《Claude Code实战》免费章节

立即扫码领取,助力你的 AI 项目和职业成长!🚀

Logo

免费领 200 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐