CHAPTER 37 / AMD · HIP 与 ROCm
HIP:从 CPU 数据到一次 kernel 调用
五个数怎样经历分配、搬运、执行与验证?
这一章要弄清楚
- 区分 host/device 生命周期
- 按 block/thread 检查索引
- 区分提交、完成与正确
先备知识:引用与const:副本和同一个对象 / 真实资源与RAII:独占拥有 / Host/device、传输与异步生命周期 / Kernel 索引、边界与 grid-stride loop
C++20 本机逻辑模型;不是设备仿真或性能测量。厂商语法片段未在设备或 SDK 执行,实际 API 以匹配版本的公开文档为准。
先把调用拆成可检查的步骤
我们有五个整数 2、4、6、8、10,希望每个数加一。普通 C++ 循环会得到 3、5、7、9、11。写 HIP 时,数学问题没有变,改变的是工作安排:CPU 负责准备与协调,kernel 是让设备执行的函数,多个逻辑线程各处理不同索引。先理解这三件事,再记 API 名字,才能在结果错误时知道应检查哪一段。
Host 是主程序运行的 CPU 一侧,device 是被调用的计算设备。设备指针不是“另一个普通 vector”:分配方式、可访问位置和释放时机由相应 API 约束。某些系统支持统一地址或内存机制,但不能因此假定任意 host 指针都能交给任意 kernel。最初使用明确的 host 输入、device 输入输出、host 回读结果,数据关系更容易检查。
先画所有权,再写调用
最小流程包括选择设备、分配设备空间、复制输入、启动 kernel、等待结果、复制输出、验证和释放。错误处理贯穿每一步。分配失败后不能继续使用未成功取得的地址;某一步失败也不能遗忘此前成功分配的资源。RAII 依然有用,不过资源的生命必须覆盖所有异步使用,析构函数不会自动解决跨设备依赖。
例一用两个独立 vector 表达两个存储区。复制后修改 device_model 不会改变 host_input;只有明确回读才得到更新结果。它只验证数据流,不能模拟 HIP runtime、地址空间、GPU 并行执行或传输开销。把模型输出称为 GPU 实测,会混淆正在检查的命题。
多出来的线程也有合同
设 block 大小为四,五个元素需要两个 block,产生八个逻辑线程。全局索引等于 block 编号乘 block 大小,再加线程编号。索引零到四做有效工作,五到七必须跳过。向下取整只启动一个 block 会漏掉第五个数;不检查边界则会访问数组以外的存储。空输入最好由 host 明确处理,不依赖零大小 launch 的行为。
逐项读 kernel 和 launch(补充,另估25分钟)
先把普通 C++ 函数读法扩展到这一个 HIP 片段。__global__ 标记由 host 启动、在 device 上执行的 kernel;本例返回类型为 void,结果写入 output。blockIdx.x 是当前块的 x 编号,blockDim.x 是每块 x 方向的线程数,threadIdx.x 是当前线程在块内的 x 编号;编号从零开始。这里的点号仍是成员访问,三个名字由 HIP 提供。
dim3 表示三个无符号维度;dim3(4) 是 (4, 1, 1),省略的 y、z 都是 1。下面注释中的 hipLaunchKernelGGL 按位置读取,不能把相邻的零随意理解成设备号:
| 位置 | 本例填写 | 作用 |
|---|---|---|
| 1 | plus_one |
要启动的 kernel |
| 2 | dim3((n+3)/4) |
grid:启动多少个块 |
| 3 | dim3(4) |
block:每块多少个线程 |
| 4 | 0 |
每块额外动态共享内存的字节数,本例不申请 |
| 5 | stream |
已准备好的执行队列,工作提交到这个 stream |
| 6 起 | input, output, n |
按 kernel 形参顺序传递的三个实参 |
**纸上走一次:**令 n=5。grid 为 (2,1,1),block 为 (4,1,1);块零覆盖索引 0–3,块一覆盖 4–7。i < n 让索引 4 写出 11,索引 5–7 跳过,最终五个数是 3、5、7、9、11。只把块大小改成 8 并同步修改 grid 的向上取整式,会得到一个块、仍有三个线程跳过。这里的加法式只用于小整数,不能忽略大 n 的溢出检查。
launch 配置没有表达“已经算完”。两个指针须指向该设备可访问且容量足够的存储,输入要先准备完成,输入、输出与相关资源还要存活到设备使用结束。下方是未执行的语法片段;上述数值是索引推演,CPU 示例不能证明 HIP 调用成功。读法核对自 HIP 7.1.52802 的 kernel 调用与 dim3,对应 docs-7.1.1,核对日期 2026-09-08。
// HIP 语法示意:未在 SDK/设备执行,不是完整 host 程序。
__global__ void plus_one(const int* input, int* output, int n) {
const int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) output[i] = input[i] + 1;
}
// 已分配和准备 input/output;n > 0,且取值满足 int 范围。
// hipLaunchKernelGGL(plus_one, dim3((n+3)/4), dim3(4),
// 0, stream, input, output, n);
这里使用很小的整数;真实输入还需检查长度转换、字节数乘法和加法是否溢出。四个线程只是索引算例,不是硬件配置建议。block 大小应结合 kernel 资源用量和设备限制选择,不能从示意图推导性能。
调用返回了,究竟证明什么
Host 发起工作后可能先继续执行。启动错误、异步执行错误和数值错误是三个层次:先检查 API 返回值及 launch 状态,再在规定同步点检查执行状态,最后与独立 reference 比较。打印输出指针只说明得到一串地址,不说明设备完成了写入。
本章固定一种输入类型、一个设备、一个显式 stream 和一次变换。后续再加入流水和 profiling。公开 HIP 文档还要匹配安装版本、目标架构和编译参数;本机例子不要求安装 SDK,也不证明任何目标设备支持情况。
五个数的存储迁移
输出尚未产生。
独立模型存储加一。
回读与oracle一致。
阅读完整推演文字
- 准备
host:2 4 6 8 10;device:未分配
输出尚未产生。
- 执行
host:2 4 6 8 10;model:3 5 7 9 11
独立模型存储加一。
- 验证
回读:3 5 7 9 11;oracle:3 5 7 9 11
回读与oracle一致。
跟着例子,走完一遍
两个存储区与回读
[2,4,6,8,10],仅模型设备存储加一。
- 复制到独立vector
- 逐个加一
- 回读并检查原输入
// Original CPU teaching model; not a device simulator or benchmark.
#include <iostream>
#include <vector>
#include <stdexcept>
void require(bool ok) { if (!ok) throw std::runtime_error("model check failed"); }
int main() {
const std::vector<int> host{2,4,6,8,10};
auto model=host; for(int& x:model) ++x;
const auto result=model;
require(host.front()==2 && result==std::vector<int>({3,5,7,9,11}));
std::cout<<"host="<<host.front()<<"\nresult=";
for(std::size_t i=0;i<result.size();++i) std::cout<<(i?" ":"")<<result[i];
std::cout<<'\n';
}
原输入首项2;结果3 5 7 9 11。
值复制建立不同对象。
在本机运行这个例子
下载后,在文件所在目录执行。需要支持 C++20 的编译器;POSIX 示例还需要章节说明中的系统条件。
clang++ -std=c++20 -Wall -Wextra -Wpedantic -Werror -pthread 33-a.cpp -o example && ./example预期标准输出:
host=2
result=3 5 7 9 11
八个索引处理五个数
n=5、block=4,统计每个元素访问次数。
- 向上取整得到两组
- 展开八个索引
- 仅i<n时访问
// Original CPU teaching model; not a device simulator or benchmark.
#include <iostream>
#include <vector>
#include <stdexcept>
void require(bool ok) { if (!ok) throw std::runtime_error("model check failed"); }
int main() {
constexpr std::size_t n=5, block=4;
const auto blocks=n/block+(n%block!=0);
std::vector<int> visits(n,0); std::size_t skipped=0;
for(std::size_t b=0;b<blocks;++b) for(std::size_t t=0;t<block;++t) {
const auto i=b*block+t; if(i<n) ++visits[i]; else ++skipped;
}
for(int count:visits) require(count==1);
require(skipped==3);
std::cout<<"blocks="<<blocks<<" valid="<<n<<" skipped="<<skipped<<'\n';
}
有效5,跳过3,每项访问一次。
边界保护保证覆盖,不提供性能结论。
在本机运行这个例子
下载后,在文件所在目录执行。需要支持 C++20 的编译器;POSIX 示例还需要章节说明中的系统条件。
clang++ -std=c++20 -Wall -Wextra -Wpedantic -Werror -pthread 33-b.cpp -o example && ./example预期标准输出:
blocks=2 valid=5 skipped=3
把提交当完成
启动后立即读取或释放数据。
修正思路:建立完成依赖,检查执行错误,再回读验证和释放。
轮到你动手
先写预测或代码,再按需打开提示。完整答案用于对照自己的推理。
练习 1
n=10、block=4,列出block 1和2的索引。
给我一点提示
- 编号从零开始
- 先算b*4再加线程号
查看答案与推理
block 1访问4–7;block 2访问8、9,跳过10、11。总共三组,不能省略最后一组。
练习 2
回读全零,设计三层检查。
给我一点提示
- 查执行
- 查字节和索引
- 查独立答案
查看答案与推理
检查API/launch及同步返回;核对初始化、元素数乘sizeof(T)、复制方向与输出指针;用n=1、5、8确定性数据对比oracle。每项定位不同故障,增加睡眠不能替代检查。
把理解说出来
先用中文讲清因果,再用英文回答。问题依据技能主题编写,并非公司内部题库。
device pointer 能直接当 host vector 的 data() 吗?
参考回答 / English answer
先说明分配所在内存及哪侧允许访问;不能靠相同数值地址推定可访问。
A pointer value does not establish accessibility. I check the allocation type and access rules for each execution context.n=9、block=4,哪些线程跳过?
参考回答 / English answer
三组产生十二个索引;0–8有效,9–11跳过。
Three blocks produce twelve logical indices. Indices nine through eleven must not access the arrays.launch 没报错,结果不对,查什么?
参考回答 / English answer
先确认同步与执行错误,再查复制长度、索引、输入初始化和reference。
I separate launch validation from completion. Then I inspect transfers, indexing, initialization, and the reference.释放输入前为什么考虑 stream?
参考回答 / English answer
提交后设备可能尚未读取,资源必须覆盖所有未完成使用。
The device may still be reading after submission. Resource lifetime must cover every outstanding use.两个 vector 的模型证明什么?
参考回答 / English answer
只证明独立存储、变换与回读的C++逻辑,不证明设备通信或性能。
This is a CPU model of copying and ownership. It is neither a device simulator nor a GPU measurement.空输入与超大长度在哪里处理?
参考回答 / English answer
host处理空输入并检查类型、字节乘法和launch范围;kernel保护不能修复已溢出参数。
I validate sizes before allocation and launch. Kernel bounds checks cannot repair host-side overflow.继续查证
- HIP programming model ↗
Host programming; hierarchical thread model
- HIP error handling ↗
API and asynchronous errors
公开资料用于查证;本章图解和例题是独立教学内容。CPU 逻辑模型不能证明设备性能。
接着看已有的图解
- GPU 执行层级与 wave 图解 ↗
辅助对照软件线程与硬件执行单位;旧调试题中的最后一段 wave 隐式同步不能作为正确实现,按本书第 31/36 章证明同步。
- HIP 基础:Grid、Block 与 Thread ↗
复习线程索引与 host/device 调用顺序,运行时与驱动的职责以本书第 35 章及修正版 Atlas 为准。
- ROCm 计算与 kernel 主题库 ↗
按内存、stream 和性能主题补充阅读;设备练习需满足该版本环境要求。
这些资料按主题补充本章内容。