最近干了一件事: 把一套原本跑在 NVIDIA A100 上的训练和推理代码,整个儿搬到了 AMD Instinct™ MI300X + ROCm™ 上。

说实话,动工之前我也犹豫过——毕竟"换平台"听着就像给自己找麻烦。但真走完一遍才发现,没有想象中那么折腾。这篇就把过程里的坑和经验摊开讲讲,希望你看完能少绕点弯路。文末我放了个 200 小时免费云算力 的领取入口,看完正好拿去实测。

一、先放下一个老偏见

不少人一提 ROCm,第一反应还是"生态弱、文档少、容易踩雷"。这话放在几年前没毛病,但以 ROCm 6.x 为节点,情况真不一样了:

  • PyTorch 官方已经发了 ROCm 原生版本,不是社区魔改的那种

  • vLLM、SGLang、TGI 这些主流推理框架,基本都适配了;

  • MI300X 单卡就有 192GB 显存,跑 70B 甚至更大的模型,几乎不用纠结怎么切分。

所以这次迁移,我给自己定了个最关键的心法:

把 "CUDA" 当成一个可以换的后端,而不是绑死的运行时。

PyTorch 早就帮你把这层抽象掉了。真正要动手改的地方,主要是环境个别底层算子,其余的代码基本不用动。

二、第一步:换镜像,别跟驱动死磕

我的第一条建议——千万别在宿主机上手动装驱动。装到怀疑人生不说,版本一冲突能查半天。直接用官方 Docker 镜像,干净利落:

# ROCm 6.2 + PyTorch 2.4 官方镜像
docker pull rocm/pytorch:rocm6.2_ubuntu22.04_py3.11_pytorch_release-2.4.0
​
docker run -it --device=/dev/kfd --device=/dev/dri \
  --group-add=video --ipc=host --cap-add=SYS_PTRACE \
  -v $(pwd):/workspace rocm/pytorch:rocm6.2_ubuntu22.04_py3.11_pytorch_release-2.4.0

进容器后,先做个最简单的"冒烟测试",确认显卡被认出来了:

import torch
print(torch.__version__)                 # 2.4.0+rocm62
print(torch.cuda.is_available())         # True —— 没看错,ROCm 下也是 True
print(torch.cuda.device_count())         # 你的卡数
print(torch.cuda.get_device_name(0))     # AMD Instinct MI300X

这里有个反直觉、但故意这么设计的点: ROCm 版的 PyTorch 保留了 torch.cuda 这个名字。也就是说,你以前写的成千上万行 x.cuda()model.to('cuda')一行都不用改——cuda 在这里其实就是 AMD 显卡(HIP 设备)的别名。

光这一点,就帮你省下了大把改代码的时间。

三、监控命令也得换一套

原来天天敲的 nvidia-smi,到 AMD 这边要换成 rocm-smi。我做了张对照表,建议直接存下来:

你想干啥 NVIDIA AMD / ROCm
看显卡和显存 nvidia-smi rocm-smi
持续刷新 watch -n1 nvidia-smi rocm-smi --watch
看进程占用 nvidia-smi pmon rocm-smi --showpid
看多卡拓扑 nvidia-smi topo -m rocm-smi --showtopo
详细诊断 nvidia-smi -q rocm-smi --showall

记不住没关系,关键盯住两个数: VRAM%(显存占比) 和 MEM-USE(实际显存),对应的就是以前 nvidia-smi 里的 Memory-Usage

四、训练代码: 九成情况直接能跑

举个最常见的分布式训练例子。原来的 CUDA 写法长这样:

model = MyModel().cuda()
model = torch.nn.parallel.DistributedDataParallel(
    model, device_ids=[local_rank]
)

搬到 ROCm 上,原样跑就行。要留意的只有两个小细节:

一是启动方式。 torchrun 直接能用;但如果你以前依赖的是 apex 做分布式,建议换成原生的 DDP + torchrun,因为 apex 在 ROCm 上支持不太好。

二是别纠结逐位数值对齐。 换了平台,浮点累加顺序变了,单个 tensor 的数值会有微小差异。验收的时候别去逐个比对,看最终的 loss 和 accuracy 曲线是不是在一条收敛轨迹上就行。

至于偶尔冒出来的 "operator not implemented" 报错,别慌——大概率是下面第五节要讲的情况。

五、真正要改代码的地方: 自定义算子

前面几节都在说"基本不用改",那到底什么时候必须动手?答案是:你自己写的 CUDA Kernel

好消息是,迁移规律特别机械,几乎就是一套"查找替换":

// 原来(CUDA)
#include <ATen/cuda/CUDAContext.h>
at::cuda::CUDAGuard guard(device);
my_kernel<<<grid, block, 0, at::cuda::getCurrentCUDAStream()>>>(x, y, n);
// 现在(ROCm / HIP)
#include <ATen/hip/HIPContext.h>
at::hip::HIPGuard guard(device);
my_kernel<<<grid, block, 0, at::hip::getCurrentHIPStream()>>>(x, y, n);

说白了就是把名字里的 cuda 换成 hip:

CUDA HIP / ROCm
cuda hip
ATen/cuda/ ATen/hip/
CUDAGuard HIPGuard
cudaMalloc hipMalloc
cudaMemcpy hipMemcpy

懒得一个个换?有两个偷懒办法。

第一个,写段宏,让同一份代码在两边都能编译:

#ifdef __HIP_PLATFORM_AMD__
  #define CUDA_PREFIX(name) hip##name
#else
  #define CUDA_PREFIX(name) cuda##name
#endif

第二个更省事——AMD 自带一个 hipify-perl 工具,一条命令就能把整个文件批量转好:

hipify-perl my_kernel.cu > my_kernel.hip

六、推理更省心: 直接上 vLLM

训练搞定了,推理这块反而更简单。vLLM 在 ROCm 上已经是"一等公民"级别的支持,一条命令就能把服务拉起来:

# 单卡跑 Qwen2.5-72B,MI300X 显存完全装得下
vllm serve Qwen/Qwen2.5-72B-Instruct \
  --tensor-parallel-size 1 \
  --gpu-memory-utilization 0.9 \
  --max-model-len 32768

调优的时候,我主要盯这三个地方:

  • 显存利用率可以大胆调高,MI300X 显存大,给到 0.9 甚至 0.95 都没问题;

  • 偶尔遇到 attention 算子不稳定,试试关掉 triton 版本的 FA(设 VLLM_USE_TRITON_FLASH_ATTN=0),换成 CK(Composable Kernel) 通常更稳;

  • 跑长文本时,先开 --enforce-eager 建一条"基准线",再逐步打开图优化。这样万一性能退化了,你能一眼定位是哪步的锅。


七、迁移完不算完,得拿数据说话

搬到新平台,不能凭感觉说"差不多了",得摆数据。我一般会整理这么一张对比表(同等 batch、同代显卡):

指标 A100 (CUDA) MI300X (ROCm) 怎么看
训练吞吐(samples/s) 基准 +x% 看显存吃满后的步进
推理吞吐(tok/s) 基准 +x% 重点看长文本的下限
显存占用 基准 -x% MI300X 大显存的主场
冷启动时间 基准 框架加载,差距通常很小

这里有个特别常见的坑: 第一次跑发现 ROCm 比 CUDA 慢,立马下结论"AMD 不行"。其实很多时候,慢只是因为第一次在编译 kernel,第二次开始就正常了。所以计时之前,一定记得先 warm-up 跑几轮。


八、报错速查表(收藏备用)

整理了几类高频报错,建议存着,遇到了直接对号入座:

报错关键字 啥原因 怎么解决
MIOpen(HIP) 相关 卷积 kernel 没找到 MIOPEN_FIND_MODE=3,走启发式
hipErrorNoBinaryForGpu 镜像和显卡架构对不上 按卡型加 HSA_OVERRIDE_GFX_VERSION=9.4.2
peer access is not available 多卡之间没法直连 检查 --device=/dev/kfd 和 IOMMU 设置
显存报 OOM,但 rocm-smi 显示没用满 显存碎片 / PyTorch 缓存 PYTORCH_HIP_ALLOC_CONF=garbage_collection_threshold:0.6

九、写在最后

整条路走下来,最大的感受是: 真正要改代码的部分,可能还不到 5%。剩下 95%,都是"换换环境、换个思路"的事。

ROCm 在 6.x 之后,确实跨过了"能用"这道坎。再加上 Instinct 那块大得惊人的显存,对长文本、大模型的推理场景来说,吸引力是实打实的。

如果你也想试试,其实不用急着去买卡——AMD 开发者云直接就能开 Instinct 实例,这套脚本原样丢上去就能跑。

🚀 想动手?先领 200 小时免费云算力

别让"没有硬件"成了你动手的借口。

现在 AMD AI 开发者计划开放注册,注册就送 200 小时免费云算力,AMD Instinct™ GPU + ROCm™ 全栈环境开箱即用,不用自己配机器、装驱动、查冲突。

👉 点这里,加入计划并领取 200 小时免费云算力: https://s.csdn.cn/ik9E3m

把这篇文章里的 Docker 镜像、迁移脚本、vLLM 命令原样搬上去,今天就能跑通你的第一个 ROCm 任务。试过之后,欢迎回来聊聊你的实测数据 🚀

Logo

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

更多推荐