CHAPTER 39 / AMD · HIP 与 ROCm
ROCm 各层:一次请求由谁负责
HIP、ROCr、KFD、amdgpu 和硬件分别处理什么?
这一章要弄清楚
- 区分编译路径与执行路径
- 解释用户态队列及驱动职责
- 按故障层次提出定位证据
先备知识:进程、系统调用与文件描述符 / 虚拟内存、页表与 mmap / HIP:从 CPU 数据到一次 kernel 调用
C++20 本机逻辑模型;不是设备仿真或性能测量。厂商语法片段未在设备或 SDK 执行,实际 API 以匹配版本的公开文档为准。
同一张图里其实有两条路径
看到HIP、LLVM、ROCr、KFD这些缩写时,先问正在讨论“把程序变成可执行代码”,还是“让现有代码在设备运行”。编译路径处理源代码、类型、优化、目标指令和编译产物;执行路径处理内存、装载、队列、同步和错误。两条路径有关联,却不是每次加法都依次经过所有软件层。
以输出数组加一为例,编译器事先生成目标代码。运行时选择合适产物,准备kernel参数和相关资源,将工作提交到设备。数据准备失败、找不到匹配产物、提交失败、执行访问错误、算法答案错误,分别出现在不同位置。先给失败分类,才能决定看编译日志、API返回值、系统日志还是结果数组。
高层接口到系统资源
HIP提供应用常用的内存、启动和同步接口。在AMD的软件栈中,ROCr是HSA runtime实现,提供agent、queue、signal等更接近派发机制的抽象。把它们区分开,不是要求第一周就绕过HIP写底层队列,而是理解高层API下面仍有资源管理和同步合同。
Linux的amdgpu及相关计算支持负责设备和系统资源管理;KFD涉及计算进程与GPU资源的连接。内存映射、调度与异常恢复等职责不会因为用户态能提交某些队列工作而消失。具体职责还随内核、runtime、平台和操作变化,不应把软件栈画成永远固定的九层流水线。
队列比“调用一次函数”多了什么
例一是一格任务队列的CPU逻辑模型:先写任务内容,再把ready标记置真,消费者才能读任务。它有意不创建线程,因此没有实现真实内存序或硬件doorbell。它只提醒你,发布通知和准备内容是两件事。真实队列协议还需遵守规定的packet格式、内存顺序、完成信号和资源生命周期。
教学上的因果顺序:
准备参数与工作内容 → 按协议发布 → 通知可消费 → 观察完成
这不是可复制的HSA packet写入配方。
例二把“分配失败、产物缺失、答案错误”映射到不同排查入口。应用仍负责检查所有返回值并保存上下文。不能看到设备端失败就一律说“driver bug”,也不能因为API在用户态返回,就断言内核没有参与其资源准备或异常路径。
用反事实问题检查理解
如果kernel根本没有出现在trace里,优先确认编译产物、设备选择、提交状态与过滤条件;如果kernel已经完成,但最后一个元素错误,索引和边界比驱动更值得先查。如果同一输入在某版本失败,另一个版本成功,先固定硬件、依赖、flags和最小复现,再判断是否是实现差异。
最好的排查记录包括具体调用、错误码、输入规模、版本、目标架构和最早失败点。不要只保存一张“执行失败”截图。应用层的错误处理越清楚,向runtime或driver维护者报告的问题越有价值。
本章怎样连接岗位
Runtime岗位关注派发、并发资源、依赖、生命周期和故障传播;driver岗位还需要系统调用、虚拟内存、设备管理及内核调试实践;compiler岗位关注如何产生合法且有效率的代码。认识软件层次是共同前置,阅读一张图却不等于实现过这些层。当前练习只要求你能沿一次请求提出具体问题,不要求直接改内核。
公开ROCr介绍与Linux驱动文档各自解释不同边界。阅读时记录你研究的是正常稳态派发、初始化还是异常处理;同一句“驱动是否参与”在这些场景中的答案可能不同,不能用一个口号覆盖全部路径。
任务17的发布因果
消费者不能读取。
内容17已写,尚未发布。
发布后消费17。
阅读完整推演文字
- 空槽
payload:0;ready:false
消费者不能读取。
- 准备
payload:17;ready:false
内容17已写,尚未发布。
- 消费
ready:true;consumer:17
发布后消费17。
跟着例子,走完一遍
先写内容,再发布
一格队列初始空;提交任务值17。
- 填任务内容
- 标ready
- 消费者检查后读取
// Original CPU teaching model; not a device simulator or benchmark.
#include <iostream>
#include <stdexcept>
void require(bool ok) { if (!ok) throw std::runtime_error("model check failed"); }
int main() {
struct Slot { int payload=0; bool ready=false; } slot;
slot.payload=17; slot.ready=true;
require(slot.ready); const int consumed=slot.payload; slot.ready=false;
require(consumed==17 && !slot.ready);
std::cout<<"consumed="<<consumed<<'\n';
}
消费者读取17。
模型解释发布顺序,不实现并发或HSA协议。
在本机运行这个例子
下载后,在文件所在目录执行。需要支持 C++20 的编译器;POSIX 示例还需要章节说明中的系统条件。
clang++ -std=c++20 -Wall -Wextra -Wpedantic -Werror -pthread 35-a.cpp -o example && ./example预期标准输出:
consumed=17
故障定位入口
分类 allocation、code_object、wrong_value 三类问题。
- 资源错误查API与环境
- 产物错误查target与装载
- 数值错误查oracle和索引
// Original CPU teaching model; not a device simulator or benchmark.
#include <iostream>
#include <map>
#include <string>
#include <stdexcept>
void require(bool ok) { if (!ok) throw std::runtime_error("model check failed"); }
int main() {
const std::map<std::string,std::string> next{
{"allocation","API/environment"},{"code_object","build/target"},{"wrong_value","indexing/oracle"}};
require(next.at("wrong_value")=="indexing/oracle");
for(const auto& [kind,path]:next) std::cout<<kind<<": "<<path<<'\n';
}
打印三个不同排查方向。
分类缩小下一步,不自动判定根因。
在本机运行这个例子
下载后,在文件所在目录执行。需要支持 C++20 的编译器;POSIX 示例还需要章节说明中的系统条件。
clang++ -std=c++20 -Wall -Wextra -Wpedantic -Werror -pthread 35-b.cpp -o example && ./example预期标准输出:
allocation: API/environment
code_object: build/target
wrong_value: indexing/oracle
把所有错误叫driver bug
API涉及设备就直接归因内核。
修正思路:先按产物、资源、派发、执行、数值分层检查最早失败点。
轮到你动手
先写预测或代码,再按需打开提示。完整答案用于对照自己的推理。
练习 1
把分配、优化、发出kernel、缺页恢复、结果比较分别归到路径。
给我一点提示
- 编译产生代码
- 执行路径仍包括系统支持
查看答案与推理
优化属于编译;分配、派发、缺页恢复属于执行相关活动,涉及不同软件层;结果比较通常由应用完成。不是简单的一项对应一层永久规则。
练习 2
设计一条可交给维护者的最小错误记录。
给我一点提示
- 保存最早失败调用
- 排除不必要业务代码
查看答案与推理
记录硬件、OS、runtime/compiler版本、flags、最小输入、精确命令、错误码和同步位置,附预期与实际。先验证错误可重复,再推断层次;不得仅附一句GPU崩了。
把理解说出来
先用中文讲清因果,再用英文回答。问题依据技能主题编写,并非公司内部题库。
LLVM与ROCr在同一请求中职责相同吗?
参考回答 / English answer
不同。前者参与编译及目标代码生成,后者提供运行时资源与派发等接口。
Compilation produces code for a target. The runtime manages execution resources and dispatch.用户态队列意味着驱动永远不参与吗?
参考回答 / English answer
不是。稳态提交方式不能排除初始化、内存管理、调度、缺页或恢复中的驱动职责。
A user-mode queue does not eliminate the driver. Setup, memory management, scheduling, and recovery still require system support.kernel没出现在trace里,能判为未执行吗?
参考回答 / English answer
还不能。确认trace范围、过滤、采集配置,再查提交状态和产物。
I first check tracing scope and filters. Missing trace data alone does not prove that the kernel never ran.发布ready再填payload,逻辑上哪里错?
参考回答 / English answer
消费者可能先看到ready,再读到旧内容;真实并发还需正确内存序。
The consumer may observe readiness before valid data exists. A concurrent implementation also needs the correct memory-order protocol.最后一个元素错,先改驱动合理吗?
参考回答 / English answer
先检查索引、尾部处理、复制长度和oracle,用最小复现缩小问题。
I start with indexing, tail handling, transfers, and the oracle. A minimal reproducer is more useful than changing the driver.怎样描述自己的ROCm学习范围?
参考回答 / English answer
可以说能解释编译/执行路径与定位策略;没有真实实现就不声称driver或runtime开发经验。
I can explain the stack and a debugging strategy. That is different from having implemented a driver or runtime.继续查证
- What is ROCR ↗
User-mode runtime, queues and signals; historical version explicitly identified
- Linux amdgpu documentation ↗
Driver overview, memory and compute support
公开资料用于查证;本章图解和例题是独立教学内容。CPU 逻辑模型不能证明设备性能。
接着看已有的图解
- ROCm 内核与 KFD 主题库 ↗
继续了解队列、内存和内核边界,避免把稳态直接派发描述为每次都进入内核。
这些资料按主题补充本章内容。