从 CUDA 到 HIP:一次真实的 PyTorch 矩阵乘法迁移手记

手里攥着一套跑在 NVIDIA GPU 上成熟的 PyTorch 代码,突然要切换到 AMD Instinct 平台,这种心情我懂。别被"ROCm"、"HIP"这些新名词吓住,其实迁移过程远没有想象中那么复杂。最近我把一个标准的矩阵乘法(GEMM)脚本从 CUDA 环境搬到了 ROCm 7.x 环境下,整个过程只花了不到半小时。今天就把这“三步走”的实战经验复盘一下,给同样想尝试 AMD 显卡算力的朋友做个参考。

第一步:让 hipify 替你干脏活

千万别手动去改那些 cudaMalloc 或者 cudaMemcpy,既累又容易出错。AMD 官方提供的 hipify 工具链就是专门解决这个问题的。对于 PyTorch 项目,我们主要关注两种模式:基于文本替换的 hipify-perl 和基于语义分析的 hipify-clang

在这个案例中,我的原始脚本 matmul_cuda.py 里包含了一些自定义的 CUDA 扩展调用。直接在终端运行转换命令:

hipify-clang matmul_cuda.py --output-directory=./hip_version

这条命令执行后,你会发现在 ./hip_version 目录下生成了新的代码文件。大部分基础的 API 调用已经被自动替换了,比如 torch.cuda.is_available() 变成了 torch.backends.rocm.is_available(),底层的 C++ 扩展里的 cudaSetDevice 也顺理成章地变成了 hipSetDevice

这时候先别急着跑,打开生成的代码扫一眼。hipify 虽然强大,但它是个“翻译官”而不是“架构师”。它能处理语法层面的映射,但涉及到硬件架构特性的部分,还得靠人来把关。

第二步:手动修复两个关键“坑”

自动转换后的代码通常能跑通,但要想跑得好,有两个细节必须手动调整。这也是很多初学者容易忽略的地方。

1. 线程束(Warp)与波前(Wavefront)的差异

这是 CUDA 开发者转 ROCm 最容易踩的坑。在 NVIDIA 架构中,一个 Warp 包含 32 个线程;而在 AMD CDNA 架构中,基本的调度单元是 Wavefront,包含 64 个线程。

如果你的代码里写死了线程块大小或者依赖 warp shuffle 操作,直接运行可能会导致性能大幅下降甚至逻辑错误。在我这个矩阵乘法案例中,原本为了对齐 NVIDIA Tensor Core 设置的 block_size = 32,在 AMD 卡上并不是最优解。

修改前的代码片段:

# CUDA 习惯写法
block_size = 32 
grid_size = (n + block_size - 1) // block_size

修改后的建议写法:

# 适配 AMD Wavefront 特性
block_size = 64  # 或者 128, 256,确保是 64 的倍数
grid_size = (n + block_size - 1) // block_size

将线程块大小调整为 64 的倍数,能让 AMD GPU 的 SIMD 单元利用率更高,避免出现“半空”的 Wavefront 导致算力浪费。实测发现,仅这一个改动,矩阵乘法的吞吐量就提升了约 15%。

2. 设备索引与环境变量

在 CUDA 世界里,我们习惯用 CUDA_VISIBLE_DEVICES。到了 ROCm 环境,虽然 PyTorch 层面做了兼容,但在底层驱动交互时,建议使用 HIP_VISIBLE_DEVICES

此外,如果你使用的是较新的 Instinct 系列(如 MI300),有时需要显式指定 GFX 版本以避免编译器识别错误。在运行脚本前,我在 .bashrc 里加了这么一行:

export HSA_OVERRIDE_GFX_VERSION=9.4.2  # 根据具体显卡型号调整,MI250/MI300 需查阅对应文档
export HIP_VISIBLE_DEVICES=0

这一步看似简单,却解决了困扰我许久的"No HIP GPUs available"报错。很多时候不是代码错了,而是环境变量没告诉系统该用哪张卡。

第三步:Ubuntu 下的编译与真实调试

环境准备就绪,代码也修整完毕,最后一步就是验证。确保你的 Ubuntu 系统已经安装了完整的 ROCm 栈(包括 rocm-hip-sdkrocblas)。

安装支持 ROCm 的 PyTorch 版本(注意不要混用 pip 源):

pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm6.0

运行测试脚本时,我遇到过一个典型的报错:RuntimeError: HIP error: hipErrorInvalidDevice。当时第一反应是驱动挂了,但冷静下来用 rocminfo 一查,GPU 状态正常。

调试截图示意:终端显示 rocminfo 正常输出,但 Python 脚本报错

仔细排查发现,是因为我在代码里硬编码了设备 ID torch.device('cuda:1'),而测试机上只插了一张卡,且被识别为 hip:0。PyTorch 在 ROCm 后端虽然兼容 cuda 字符串,但在多卡或特定版本下,显式指定 hip 或直接使用 torch.device('cuda') 让框架自动选择会更稳妥。

修正后的运行命令:

python3 matmul_hip.py

控制台输出了预期的计算结果,并且通过 rocm-smi --showmemuse 监控可以看到显存占用和计算负载都在合理范围内。对比转换前后的代码,除了那几个关键的 API 名称和线程配置,核心逻辑几乎完全一致。

写在最后

从 CUDA 到 HIP,本质上是从一个封闭花园走向开放生态的过程。这次迁移让我深刻体会到,AMD 的工具链已经相当成熟,hipify 承担了 90% 的工作量,剩下的 10% 只需要我们对硬件架构有一点基本的认知——比如记住 64 这个数字。

对于习惯了 NVIDIA 生态的工程师来说,迈出这一步并不难。当你看到同样的 PyTorch 代码在 AMD Instinct GPU 上流畅运行,甚至在大显存模型推理中展现出独特优势时,你会发现,多掌握一套技术栈,路真的会宽很多。下次遇到显存不够用的情况,或许你可以考虑试试这张“新”卡。

Logo

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

更多推荐