你是一家深度学习餐饮连锁的总厨。招牌菜谱(PyTorch 框架)在 NVIDIA 的商场里开得风生水起——那里有现成的燃气管道、排烟系统和水电接口,这套标准叫 CUDA。可最近商场的租金水涨船高,隔壁 AMD 商场给出了诱人的免租期,唯一的问题是:它家的厨房插座、灶台火力、货架尺寸全都自成一套。你要做的,不是把菜谱改成另一本菜谱,而是用上 AMD 商场提供的“厨房改造包”——ROCm,让总店的菜谱原封不动地在新厨房里照样出菜。这篇文章就是你的《AMD 分店开业指南》:从认识 ROCm 生态里每个部件是什么,到安装调试、把食材(张量)在两个厨房间搬运、把每道菜的做法翻译成新厨房的操作手册(算子编译)、管理冷库货架(显存池)、优化厨房动线(性能),再到出问题时怎么调监控(性能剖析)。跟着这条线走完,你就能底气十足地说:换厨房,不换菜谱。

岗位关联

对 AI 从业者来说,掌握 PyTorch ROCm 后端,意味着你在 GPU 紧缺、算力涨价的年代多了一把打开 AMD 算力的钥匙,能无缝迁移训练与推理任务,成为团队里稀缺的跨平台 AI 工程人才。

目录
  1. 可视化演示
  2. ROCm生态全景
  3. 环境安装配置
  4. 设备与张量流动
  5. 算子编译执行
  6. 显存池化管理
  7. 性能优化路径
  8. 调试性能剖析
  9. 工程落地实践
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示ROCm技术栈架构、CUDA与ROCm接口差异映射及算子覆盖度性能三种核心模式,直观呈现PyTorch ROCm后端从API层到硬件层的跨平台GPU编程方案。

本节配有交互动效(进入视口后自动加载,约 3KB)。

ROCm技术栈架构 PyTorch经ROCm运行时HIP驱动层直达AMD GPU硬件 四层抽象实现跨平台GPU编程统一接口 PyTorch → ROCm → HIP → GPU ROCm是AMD GPU生态的CUDA替代方案

ROCm生态全景

想象你是一家连锁 AI 餐厅的总厨,正打算在 AMD 这家新商场开分店。商场招商经理递给你一张《基础设施清单》:燃气管道走哪堵墙、水电表装在几楼、排烟通道是否合规。这套清单对应到 GPU 计算世界,就是 ROCm——AMD 为自家显卡准备的一整套“厨房基础设施方案”。

我们知道 NVIDIA 的厨房标准叫 CUDA,老分店全靠它。而 AMD 商场不认 CUDA 的接口,它有自己的标准叫 ROCm(Radeon Open Compute)。ROCm 不是单个软件,而是一个从底层驱动到高层算子库的完整软件栈,对标 NVIDIA 的 CUDA 全家桶。

从下往上看,ROCm 分三层:驱动层负责最底层硬件控制,由 amdgpu 内核模块和 ROCk(ROCm Kernel Driver)组成,相当于大楼的总水管和总电闸;运行时层是 HIP Runtime,通过 ROCclr 桥接驱动,提供 hipMalloc、hipLaunchKernel 等 API——HIP 的语法与 CUDA 高度相似,这也是 PyTorch 能“源码级”移植的前提;算子库层则是现成的“半成品食材包”,包括 rocBLAS(线性代数)、MIOpen(卷积等深度学习算子)、rocFFT、RCCL(通信库),分别对标 cuBLAS、cuDNN、NCCL。

就像开分店必须先确认燃气、水电、排烟三项到位,理解 ROCm 的三层结构,是后面一切安装、调试、优化工作的地基。下一节,我们就拿着这份清单开始“装修”。

环境安装配置

基础设施确认了,接下来是“进厨具”。总店的菜谱是 PyTorch,好消息是——菜谱不用改,但得买专供 AMD 厨房的“定制菜谱版本”:PyTorch 官方发布的 ROCm 轮子(wheel 包,相当于预装好依赖的安装包)。

为什么不能直接 pip install torch?因为官方默认 wheel 捆绑的是 CUDA 运行时,在 AMD 厨房里属于“异国标准”。安装 ROCm 版要指定独立下载源,例如:

安装成功不等于能用。三个版本的匹配至关重要:Linux 内核 → AMD 驱动/ROCm 运行库 → PyTorch wheel 里链接的 ROCm 库 SOVERSION(库版本标识),任何一层对不上都可能 import 崩溃。装好后用三个命令验证可用性:rocm-smi 看 GPU 状态;python -c "import torch; print(torch.cuda.is_available())" 返回 True 才代表后端真通了;python -c "print(torch.cuda.get_device_name(0))" 确认能看到 MI210 之类的 AMD 卡名。

是不是很像买锅碗瓢盆?品牌型号对不上,锅盖就盖不上灶台。搞定安装,接下来就看食材怎么在两个厨房之间流动。

bash1 行
pip install torch --index-url https://download.pytorch.org/whl/rocm6.0

设备与张量流动

食材(模型权重和中间张量)平时住在 CPU 的“大冰箱”里,做菜时必须先搬进 GPU 的“灶台冷库”。两个厨房的储物柜物理上不互通,搬运就成了每次计算前最直观的开销。

PyTorch 的精妙之处在于“设备统一抽象”:无论底层是 NVIDIA 还是 AMD,你写的都是 tensor.to('cuda')。在 ROCm 后端,这个 'cuda' 设备会被映射到 HIP(AMD 的类 CUDA 编程接口)设备,上层代码几乎察觉不到换了厨房。

张量搬运调用的是 HIP 的异步拷贝(hipMemcpyAsync),它把任务丢进 GPU 队列就立即返回,并不等数据到达。如果你要“确认收到”,得调用 torch.cuda.synchronize()。这个异步语义也是很多“看起来没算完” bug 的来源——测速时少一次同步,数字就会虚高。真正可感知的搬运速度,要用有效带宽来衡量:

所以测搬运效率别只盯着数据手册上的理论带宽——就像请搬家师傅,装车和验货的时间也算钱。掌握了搬运开销,下一步我们看看真正“做菜”时发生了什么。

BWeff=DTcopy+Tsync\displaystyle BW_{\text{eff}} = \frac{D}{T_{\text{copy}} + T_{\text{sync}}}
有效带宽 = 数据量 ÷(拷贝时间 + 同步等待时间)。墙钟表上看到的速度,才是你真正得到的速度。
  1. 分子 D(数据量)待搬运张量的字节数,例如 1 GB 的模型权重,单位是字节。
    D=bytes(tensor)D = \text{bytes(tensor)}
  2. 拷贝时间 T_copyHIP 引擎真正执行 memcpy 的耗时,受限于 PCIe 或 HBM 带宽。
    Tcopy=D/BWpeakT_{\text{copy}} = D / BW_{\text{peak}}
  3. 同步时间 T_sync调用 synchronize 等待异步队列排空的时间,相当于搬运后的“验货”环节。
    Tsync=tend−tsubmitT_{\text{sync}} = t_{\text{end}} - t_{\text{submit}}
  4. 有效带宽 BW_eff数据量除以两者之和,得到实际可感知的搬运速率,呼应了“异步≠免费”这个直觉。
    BWeff=DTcopy+TsyncBW_{\text{eff}} = \frac{D}{T_{\text{copy}} + T_{\text{sync}}}

算子编译执行

同一句“大火颠勺”,在 NVIDIA 厨房是灶台 A 的操作,到了 AMD 厨房得翻译成灶台 B 的操作。PyTorch 里每一个算子(卷积、矩阵乘、归一化)在 ROCm 上都面临一次“翻译”。

这个翻译是“惰性”的:不是装好环境就全翻译完,而是第一次用哪个算子,才翻译哪个。翻译发生在运行时——PyTorch 的前台接待员(dispatcher)收到算子调用后,把它派发到 ROCm 后端的底层算子库 ATen 实现,再由 HIP 编译器生成 AMD GPU 内核。

首次执行某类算子时,还可能触发算法自动调优(例如 MIOpen 对不同卷积形状 benchmark,挑选最快的实现);调优结果连同编译产物一起进入缓存。之后的调用直接命中缓存,Python 侧成本趋近于零——这就是“训练前几步慢、之后变快”的原因。平均时延能把这笔账算清楚:

翻译手册再厚,只要后面复印几千份,单价就趋近于零。所以别被“首次执行慢”吓到——只要训练循环够长,编译成本自然消失。翻译完了,食材也在灶台上了,剩下的问题是“菜放哪儿”。

T‾=TcompileN+Texec\displaystyle \overline{T} = \frac{T_{\text{compile}}}{N} + T_{\text{exec}}
平均每次调用的耗时 = 一次性编译成本 ÷ 调用次数 + 单次执行耗时。跑得越多,编译成本被摊得越薄。
  1. 一次性编译成本 T_compile首次调用时的源码翻译加自动调优耗时,可能高达数十秒,但只发生一次。
    Tcompile=tfirst−TexecT_{\text{compile}} = t_{\text{first}} - T_{\text{exec}}
  2. 调用次数 N同一算子在训练循环中的执行次数,通常等于 step 数乘以网络层数。
    N≈steps×layersN \approx \text{steps} \times \text{layers}
  3. 单次执行时间 T_exec内核真正在 GPU 上的运行时间,与编译无关,是“做菜本身”的耗时。
    Texec=ttotal−TcompileNT_{\text{exec}} = \frac{t_{\text{total}} - T_{\text{compile}}}{N}
  4. 摊薄效果当 N 趋向无穷时,平均时延收敛到 T_exec,编译成本被完全摊平。
    lim⁡N→∞T‾=Texec\lim_{N \to \infty} \overline{T} = T_{\text{exec}}

显存池化管理

后厨的冷库货架有限,每道菜的食材用完就关一次库门很浪费。于是总店设了个“缓存货架”:食材用完不立即退租仓库,先摆在货架上,下道菜同样食材直接拿。

PyTorch 的缓存分配器(caching allocator)就是这个货架。在 ROCm 后端它同样生效:当你 torch.randn(..., device='cuda') 申请显存时,它先从自己的内存池里划一块给你,而不是直接调用昂贵的 hipMalloc。hipMalloc 相当于每次都要找房东签约,耗时巨大。

池子按显存块大小分桶管理,同一形状的张量反复申请/释放时极易命中。命中率 HR 直接决定分配开销:

还要注意碎片问题:形状忽大忽小的张量会让池子里的块无法拼接成大块。torch.cuda.empty_cache() 可以把池中闲置块归还给驱动,但代价是下次申请会重新走 hipMalloc。冷库管理好了,出菜速度就稳;但真正拉开差距的,是厨房动线——性能优化。

τeff=HR×τhit+(1−HR)×τmiss\displaystyle \tau_{\text{eff}} = HR \times \tau_{\text{hit}} + (1 - HR) \times \tau_{\text{miss}}
有效分配时延 = 命中率 × 命中时延 + 未命中率 × 未命中时延。命中率越高,分配越快。
  1. 命中率 HR请求的显存块在池中直接找到的比例,稳定 shape 的训练循环里接近 1。
    HR=HH+MHR = \frac{H}{H + M}
  2. 命中时延 tau_hit从池中划块的时间,通常微秒级,相当于货架上直接取货。
    τhit=O(1)\tau_{\text{hit}} = O(1)
  3. 未命中时延 tau_miss被迫调用 hipMalloc 向驱动申请的时间,比命中贵几个数量级。
    τmiss≈102×τhit\tau_{\text{miss}} \approx 10^2 \times \tau_{\text{hit}}
  4. 加权平均两者按命中率加权,就是实际感受到的分配速度,HR 越接近 1 越接近“秒取”。
    τeff=HR×τhit+(1−HR)×τmiss\tau_{\text{eff}} = HR \times \tau_{\text{hit}} + (1 - HR) \times \tau_{\text{miss}}

性能优化路径

同样一桌菜,新手后厨来回跑 50 趟,熟手大厨设计动线后只跑 10 趟。GPU 计算里,“跑”的主要是内核启动和数据搬运。

PyTorch ROCm 后端的性能优化有三板斧:算子融合、混合精度、图捕获。算子融合把多个小算子合并成一个大内核,减少内核启动次数和中间张量的显存读写;混合精度用 float16/bfloat16 让单次搬运的数据量减半,对带宽瓶颈特别有效(torch.cuda.amp 在 ROCm 后端同样可用);图捕获(torch.cuda.graphs)把一串算子序列一次性捕获成可重放的计算图,把 CPU 侧的调度开销摊到零。

这三板斧能带来多少收益,服从 Amdahl 定律:

动线再优化,端菜出门那几步永远省不掉——先定位你自己的"1−p"在哪儿,再动手优化,才是最高效的路径。

S=1(1−p)+pN\displaystyle S = \frac{1}{(1 - p) + \frac{p}{N}}
整体加速比 = 1 ÷(串行比例 + 可并行比例 ÷ 优化倍数)。串行部分永远卡住你的加速上限。
  1. 可优化占比 p被融合、精度、图捕获覆盖的时间占比,例如矩阵乘占训练总耗时的 85%。
    p=ToptimizableTtotalp = \frac{T_{\text{optimizable}}}{T_{\text{total}}}
  2. 优化倍数 N单点优化带来的加速,比如带宽需求减半相当于 N=2。
    N=ToldTnewN = \frac{T_{\text{old}}}{T_{\text{new}}}
  3. 串行部分 1-p数据搬运、同步、CPU 调度等难以消除的开销,是“端菜必走的那几步路”。
    1−p=TserialTtotal1 - p = \frac{T_{\text{serial}}}{T_{\text{total}}}
  4. 理论上限 S_max当 N 无限大时,加速比收敛到 1/(1-p),串行部分决定天花板。
    Smax⁡=lim⁡N→∞S=11−pS_{\max} = \lim_{N \to \infty} S = \frac{1}{1 - p}

调试性能剖析

出餐慢了、菜烧糊了,总得看监控才知道是切菜工位堵了还是灶台火力不够。GPU 后厨的监控,就是 ROCm 的 profiling 工具链。

调试分两层:一是“报错了怎么定位”,二是“没报错但慢,慢在哪”。常见报错定位包括:版本不匹配通常在 import 阶段就炸;显存不足会报 OOM 并给出分配栈;内核静默出错时,可以用环境变量 HIP_LAUNCH_BLOCKING=1 关闭异步执行,让每个内核串行运行并精确报出错行。

性能剖析则要看数据:rocprof 输出内核级时间线,rocprof-compute(Omniperf)读取硬件计数器,rocm-smi 看实时功耗与温度;PyTorch 侧可用 torch.profiler 对算子打点。总耗时可以精确拆成两项:

监控装好了,才知道后厨瓶颈在哪儿。而要把这套经验固化到每家分店,就得靠最后一节的标准化作业。

Ttotal=∑i=1mTkernel,i+∑j=1nTgap,j\displaystyle T_{\text{total}} = \sum_{i=1}^{m} T_{\text{kernel},i} + \sum_{j=1}^{n} T_{\text{gap},j}
总耗时 = 所有内核执行时间之和 + 所有间隙(启动、同步、空闲)时间之和。profiler 的价值就是帮你把这两项拆开。
  1. 内核项 T_kernelGPU 真正执行计算的时间总和,是“有效做功”的部分。
    Tkernel=∑i=1mTiT_{\text{kernel}} = \sum_{i=1}^{m} T_i
  2. 间隙项 T_gapCPU 启动内核、等待同步、数据搬运之间的空档,是“等菜时间”。
    Tgap=∑j=1nΔTjT_{\text{gap}} = \sum_{j=1}^{n} \Delta T_j
  3. 总和两项相加就是墙上时钟看到的总时间,剖析目标就是把间隙压到最低。
    Ttotal=Tkernel+TgapT_{\text{total}} = T_{\text{kernel}} + T_{\text{gap}}

工程落地实践

一家分店开好不算本事,能把“开分店”复制到十家商场才是连锁品牌的护城河。工程落地就是把 PyTorch ROCm 后端的经验写进标准化手册。

工程落地的三个关键词:兼容性注意点、CI 矩阵测试、容器化部署。兼容性方面,务必建立自己的版本兼容矩阵:内核版本 → AMD 驱动 → ROCm 版本 → PyTorch wheel → 依赖算子库(MIOpen/rocBLAS),任何升级都要在矩阵里联动验证,而不是“赌一把”。

CI 方面,同一套测试用例可以同时在 CUDA 节点与 ROCm 节点上跑(例如 GitLab CI 的双 runner),用 HIP_VISIBLE_DEVICES 控制设备可见性。容器化方面,官方镜像 rocm/pytorch 已预装好环境,运行时注入设备即可启动:

还有两个实操提醒:ROCm wheel 体积大,下载要预留磁盘和带宽;多卡场景用 RCCL 替代 NCCL 做集合通信。标准手册在手,无论下个月商场把哪块 GPU 摆上货架,你都能淡定地说:换厨房,不换菜谱。

bash1 行
docker run --device=/dev/kfd --device=/dev/dri rocm/pytorch:latest python -c "import torch; print(torch.cuda.is_available())"

AI 应用场景

场景一:低成本推理集群的“换厨房”迁移

阶段:模型部署与推理

解决的问题:NVIDIA GPU 价格高、交付周期长,而 AMD MI210/MI300 算力相当但采购成本低 30%-40%;团队担心“换卡如换代码”,迟迟不敢迁移。PyTorch ROCm 后端让大部分模型代码零改动落地,迁移风险大幅下降。

具体案例:某 AI 绘画创业公司在算力紧张时,将 Stable Diffusion 推理服务从 A100 迁移到 MI210 集群。做法很简洁:安装 ROCm 版 torch 轮子、核对版本兼容矩阵、把 Docker 镜像换成 rocm/pytorch 基础镜像,启动命令加入 --device 参数。最终推理吞吐保持 90% 以上,硬件总成本下降约 35%,整个迁移只花了两个工程师一周。

场景二:多厂商硬件的 CI 矩阵测试

阶段:模型开发与测试

解决的问题:算法团队交付的代码要同时支持 NVIDIA 与 AMD 客户,人工在两套机器上反复测试效率极低,且容易漏测后端差异。

具体案例:某自动驾驶算法团队在 GitLab CI 中配置双 runner:一个 CUDA 节点、一个 ROCm 节点,同一份 pytest 用例矩阵并行执行;通过 HIP_VISIBLE_DEVICES 控制测试卡,把版本兼容矩阵写成 CI 配置文件,任何依赖升级都自动触发全量回归。两周内把回归周期从 3 天压到 6 小时。

场景三:千卡级大模型训练的异构容灾

阶段:模型训练

解决的问题:超算中心同时采购 NVIDIA 与 AMD 节点,训练任务可能因硬件故障需要在异构池内迁移;若代码绑死 CUDA,迁移意味着重写。

具体案例:某高校智算中心用 PyTorch 在 CUDA 与 ROCm 两套节点上拉起同一套 LLM 预训练任务。ROCm 后端通过 RCCL 完成跨节点集合通信,断点续训的 checkpoint 可直接在异构节点间恢复。在一次显卡故障演练中,任务在 10 分钟内从 NVIDIA 节点漂移到 AMD 节点继续训练,损失步数不到 20。

Python 演示

python48 行
import torchimport time  def check_rocm():    if not torch.cuda.is_available():        print("ROCm 后端不可用: 请检查内核/驱动与 torch 轮子版本是否匹配")        return False    props = torch.cuda.get_device_properties(0)    print("可用设备:", torch.cuda.get_device_name(0))    print("显存总量: %.1f GB" % (props.total_memory / 1024**3))    return True  def test_transfer():    for mb in (64, 256, 1024):        n = mb * 1024 * 1024 // 4        src = torch.randn(n, device="cpu")        t0 = time.time()        dst = src.to("cuda")        torch.cuda.synchronize()        dt = time.time() - t0        bw = mb / 1024 / dt        print("%d MB 搬运耗时 %.4f s, 有效带宽 %.2f GB/s" % (mb, dt, bw))        del dst  def test_pool():    torch.cuda.empty_cache()    n = 256 * 1024 * 1024  # 1 GB 浮点数    t0 = time.time()    for _ in range(5):        x = torch.randn(n, device="cuda")        del x    torch.cuda.synchronize()    dt = (time.time() - t0) / 5    print("显存池平均分配时延: %.3f ms" % (dt * 1000))  def main():    if not check_rocm():        return    test_transfer()    test_pool()  if __name__ == "__main__":    main()
目的:在 ROCm 版 PyTorch 上验证三件事——后端是否可用、CPU→GPU 搬运的有效带宽(呼应第 3 节公式)、显存池化分配器的复用时延(呼应第 5 节)。
  1. if not torch.cuda.is_available():在 ROCm 轮子里 torch.cuda 命名空间被映射到 HIP 后端,这个调用实际检查的是 AMD GPU 与 ROCm 运行时是否就绪。
  2. src.to("cuda")张量从 CPU 搬到 AMD 显存,底层走 hipMemcpyAsync 异步拷贝,与普通 CUDA 完全同款 API。
  3. torch.cuda.synchronize()排队等待异步操作完成,测速必须先同步,否则时间统计会“虚低”。
  4. bw = mb / 1024 / dt用墙钟时间算有效带宽,正是第 3 节公式的代码形态。
  5. torch.cuda.empty_cache()把缓存分配器池中的闲置块归还给驱动,确保后续计时不受历史残留影响。
  6. torch.randn(n, device="cuda")向缓存分配器申请显存并执行生成内核;循环里的申请/释放高度命中池子。
  7. for _ in range(5)多次取均值,避免单次调度抖动。
  8. if __name__ == "__main__":可执行入口,保证脚本既可被 import 也可直接运行。

常见误区

误区我以为从 NVIDIA 换到 AMD,模型代码和数据处理都得用新语言新 API 重写,相当于把整个项目推翻重来。

正解PyTorch 已经把设备差异吸收进了框架抽象层。ROCm 版轮子上,你仍然写 torch.nn、.to('cuda'),连 torch.cuda 命名空间都保留(内部映射 HIP)。需要真正动手移植的只是手写的 CUDA kernel——用 hipify-perl 做一次源码级转换即可。就像换厨房不用改菜谱,只需要把“用哪个灶眼”的说明换成新厨房版本。

为何常见:CUDA 时代“换平台 = 换语言”的印象太深,忽略了 PyTorch 这类框架早已把平台差异封装在底层运行时里。

误区我一看显存占用高或出现碎片,就在每个 step 里调 torch.cuda.empty_cache(),觉得把池子清空、还给系统,性能就上来了。

正解empty_cache() 只是把缓存分配器池中的空闲块归还给驱动。下一次显存申请又得重新调用 hipMalloc,一来一回反而比直接从池中命中更贵。显存池存在的意义就是“不归还、反复用”。真正的碎片问题应在代码层面解决:统一张量形状、复用 buffer、减少动态 shape,而不是频繁清池。

为何常见:函数名带着“empty“和”cache“,直觉上”清理=提速”,没有意识到池化分配器牺牲一点显存换取的正是分配速度。

误区我觉得像 NVIDIA 一样,在 Windows 装好官方驱动,然后 pip install torch 就能用 AMD 卡训练模型。

正解ROCm 官方主战场是 Linux;Windows 上只有实验性的 WSL2 支持,且部分算子库和通信库(如 RCCL)支持并不完整。生产环境跑 PyTorch ROCm,默认请选择 Ubuntu 等受支持的 Linux 发行版 + 官方 rocm/pytorch 容器镜像。

为何常见:NVIDIA 的 Windows 体验太好了,容易让人默认 AMD 也是“装驱动即用”,忽略了 ROCm 目前对 Linux 的强依赖。

误区我拿到新机器就装最新版 ROCm、最新版 PyTorch,认为新版本一定更快更稳,结果训练时各种不兼容报错。

正解GPU 软件栈是强耦合系统:内核、AMD 驱动、ROCm 运行库、PyTorch wheel 的 SOVERSION 必须互相匹配,官方兼容矩阵才是唯一标准。最新 ROCm 往往要求最新内核与驱动,强行搭配旧 wheel 会导致 import 崩溃或静默错误。

为何常见:普通软件“升级无害”的直觉不适用于 GPU 栈;大家都忽视了版本强绑定带来的复杂连锁反应。

面试题

Q1. PyTorch 的 ROCm 后端主要对标 NVIDIA 的哪套 GPU 计算生态?

单选题 · easy

  • A.英伟达的 CUDA 生态← 正确答案
  • B.开源的 OpenCL 框架
  • C.微软的 DirectML 生态
  • D.苹果的 Metal 图形生态

答案:A。ROCm 是 AMD 对 NVIDIA CUDA 的完整替代方案,HIP 甚至能在源码层兼容 CUDA 写法;OpenCL 是通用异构标准但 PyTorch 不走它;DirectML 与 Metal 分别是微软和苹果的方案,与本题无关。

Q2. 在 ROCm 生态中,HIP 最核心的职责是什么?

单选题 · mid

  • A.把 CUDA 内核翻译给 AMD 硬件执行← 正确答案
  • B.把网络请求路由到多张显卡上
  • C.把 Python 代码编译成汇编指令
  • D.把显存数据定期备份到磁盘中

答案:A。HIP 是“可移植异构接口”,提供类 CUDA 的 API,上游写一套代码即可在 AMD 上编译运行;B 描述的是负载均衡,C 是通用编译器,D 是数据备份,都与 HIP 无关。

Q3. 下列哪些属于 ROCm 生态的核心组件?

多选题 · mid

  • A.rocBLAS 线性代数算子库← 正确答案
  • B.MIOpen 卷积加速算子库← 正确答案
  • C.NCCL 英伟达通信算子库
  • D.rocprof 性能剖析工具族← 正确答案

答案:ABD。rocBLAS、MIOpen、rocprof 都是 ROCm 官方组件,分别对标 cuBLAS、cuDNN、nvprof;NCCL 是 NVIDIA 的集合通信库,AMD 阵营对应的是 RCCL,所以 C 错。

Q4. 在 PyTorch ROCm 后端中,把张量从 CPU 搬到 AMD 显存的惯用写法是?

单选题 · easy

  • A.调用张量的 cuda 方法← 正确答案
  • B.调用张量的 cpu 方法
  • C.调用张量的 numpy 方法
  • D.调用张量的 clone 方法

答案:A。在 ROCm 版 PyTorch 中 .to('cuda') 与 .cuda() 都可用,内部映射到 HIP 设备搬运;.cpu() 是反向搬运;numpy() 只转换不涉及 GPU;clone() 只复制数据不改变设备。

Q5. 某个算子在 AMD GPU 上首次执行耗时较长,随后明显变快,最可能的原因是?

单选题 · mid

  • A.GPU 温度下降性能回升
  • B.首次执行触发 HIP 编译缓存← 正确答案
  • C.网络带宽波动造成误判
  • D.显存池把数据移入更快层

答案:B。首次执行触发 HIP 编译与 MIOpen 自动调优,产物缓存后后续直接复用,因此明显变快;GPU 温度只会随负载上升而非下降;带宽波动与显存池移动都不能解释“首次慢、后面快”的稳定模式。

Q6. torch.cuda.empty_cache() 在 ROCm 后端下的实际行为是?

单选题 · mid

  • A.把池中缓存归还驱动并留空← 正确答案
  • B.删除模型已学习的全部参数
  • C.触发一次完整的垃圾回收
  • D.关闭设备并释放全部显存

答案:A。empty_cache() 把所有缓存块归还给驱动并清空池子,下次申请会走更慢的 hipMalloc;它不会删除模型参数(那是优化器的职责)、不会触达 Python 对象(GC 是解释器的事)、更不会关闭设备。

Q7. 在 PyTorch ROCm 后端,以下哪些手段能有效提升训练吞吐?

多选题 · hard

  • A.将多个小算子融合成大算子← 正确答案
  • B.使用混合精度降低带宽压力← 正确答案
  • C.每次迭代加入大量调试打印
  • D.用图捕获减少内核启动开销← 正确答案

答案:ABD。算子融合减少内核启动与中间张量读写;混合精度把带宽压力减半;图捕获把 CPU 调度开销摊平。大量 print 会强制同步并拖慢吞吐,属于反面操作。

Q8. 在 ROCm 环境,控制 PyTorch 可见 GPU 的 HIP 原生环境变量是?

单选题 · hard

  • A.HIP_VISIBLE_DEVICES← 正确答案
  • B.CUDA_VISIBLE_DEVICES
  • C.ROCM_VISIBLE_DEVICES
  • D.AMD_VISIBLE_DEVICES

答案:A。HIP 原生的设备可见性控制变量是 HIP_VISIBLE_DEVICES,例如设为 "0,2" 表示只让进程看到物理卡 0 和 2;CUDA_VISIBLE_DEVICES 在部分构建里为兼容会被识别,但 HIP 原生变量是 A;其余两个变量并不存在。

Q9. MIOpen 在 PyTorch ROCm 后端主要负责加速哪类算子?

单选题 · easy

  • A.卷积与池化等视觉算子← 正确答案
  • B.文本分词与编码算子
  • C.梯度下降的更新算子
  • D.数据增强的图像裁剪

答案:A。MIOpen 是 AMD 的深度学习原语库(对标 cuDNN),覆盖卷积、池化、RNN 等算子的高性能实现;文本分词是 torchtext 的职责,梯度更新是优化器的职责,图像裁剪属于数据增强工具链。

Q10. 关于 PyTorch ROCm 后端,下列说法正确的有哪些?

多选题 · hard

  • A.内核驱动与 ROCm 版本需匹配← 正确答案
  • B.上层代码与 CUDA 版保持一致← 正确答案
  • C.不支持卷积神经网络的推理
  • D.torch.cuda 命名空间继续可用← 正确答案

答案:ABD。ROCm 强绑定内核、驱动与库版本,所以 A 正确;PyTorch 的 ROCm 版保持同一套上层 Python API,B 正确;torch.cuda 命名空间在 ROCm 版中映射 HIP 后端,D 正确。MIOpen 广泛支持 CNN 的前向与反向,因此 C 明显错误。