2
DCU技术达人
写CUDA的人,如何“无缝”切换到DCU?——HIP Runtime API深度解读
2026-08-03 16:07:04
49
说到用DCU写程序,很多人的第一反应可能是:“这玩意儿跟CUDA差多少?代码要不要重写?”答案是:**比你想象的小得多**。 要理解为什么,关键就在于**HIP Runtime API**。它是DCU上最核心的编程接口,也是理解DCU软件生态的钥匙。这篇文章不谈虚的,直接以《DTK 26.04 HIP Runtime API开发手册》为蓝本,带你看看这套API到底长什么样、怎么用,以及它背后的设计逻辑。 --- ## 一、HIP到底是什么?一句话讲清楚 HIP的全称是**Heterogeneous-compute Interface for Portability**(异构计算可移植接口)。你可以把它理解为:**一套长得像CUDA、但能跑在AMD/DCU上的编程接口**。 HIP的Runtime API提供了一套C++风格的接口,涵盖了你写GPU程序需要的所有能力:设备管理、内存分配、Kernel启动、流和事件、图执行……基本你在CUDA里见过的,HIP里都有对应的版本。 区别在哪?**CUDA分了两套API**:Driver API(`cu`开头,细粒度控制)和Runtime API(`cuda`开头,自动管理)。**HIP把两者统一成了一套Runtime API**,同时保留了`hipModule`和`hipCtx`这类底层接口。换句话说,你写一套代码,就能覆盖CUDA Driver和Runtime两层的能力。 ## 二、从“Hello DCU”开始:最简代码长什么样? 先看一个最简的DCU程序骨架: ```cpp #include // 注意:不是 cuda_runtime.h int main() { int deviceCount; hipGetDeviceCount(&deviceCount); // 获取DCU数量【手册1.22】 int deviceId; hipGetDevice(&deviceId); // 获取当前设备【手册1.21】 hipSetDevice(0); // 切换到0号设备【手册1.30】 float *d_data; hipMalloc(&d_data, 1024 * sizeof(float)); // 在DCU上分配内存【手册10.17】 // ... 启动Kernel、拷贝数据 ... hipFree(d_data); // 释放DCU内存【手册10.5】 return 0; } ``` **注意到什么了吗?** 除了头文件和函数名前缀从`cuda`换成`hip`,几乎一模一样。这就是HIP设计的核心哲学:**用最少的改动,让CUDA代码跑在DCU上**。 ## 三、API设计的三层逻辑 《DTK 26.04 HIP Runtime API开发手册》里列出了280多个API接口,看似庞大,但梳理清楚背后的设计逻辑,你会发现它们其实遵循着清晰的层次: ### 第一层:设备管理——告诉DCU“用哪块卡” 多卡场景下,首先要管理设备。HIP提供了一组简洁的设备管理API【手册第1章】: - `hipGetDeviceCount()`:系统里有多少块DCU【手册1.22】 - `hipGetDevice()` / `hipSetDevice()`:当前线程用哪块卡【手册1.21】【手册1.30】 - `hipGetDeviceProperties()`:查这块卡的显存大小、计算单元数量等详细信息【手册1.24】 - `hipDeviceSynchronize()`:让CPU等待当前设备上所有任务完成【手册1.20】 这些API的逻辑非常直观——**先查卡、再选卡、最后用卡**。多卡之间还能通过`hipDeviceCanAccessPeer()`检测是否支持直接访问对方显存【手册14.1】,以及`hipDeviceEnablePeerAccess()`开启P2P访问【手册14.3】——这些在分布式训练和多卡科学计算中非常关键。 ### 第二层:内存管理——数据怎么搬上DCU? GPU编程最大的不同在于**显存是独立的**。数据得先从CPU内存搬到DCU显存,计算完再搬回来。HIP的内存管理API几乎1:1复刻了CUDA的命名【手册第10章】: **分配与释放:** - `hipMalloc()`:在DCU上分配显存【手册10.17】 - `hipFree()`:释放显存【手册10.5】 - `hipHostMalloc()`:分配**锁页内存**(pinned memory),这种内存可以被DCU直接访问,异步传输必备【手册10.21】 - `hipHostFree()`:释放锁页内存【手册10.7】 **数据搬运:** - `hipMemcpy()`:同步拷贝,CPU会等拷贝完成再继续【手册10.30】 - `hipMemcpyAsync()`:异步拷贝,需要配合流(Stream)使用【手册10.42】 - `hipMemcpyPeer()`:DCU之间的直接拷贝,不需要经过CPU内存【手册10.45】 **高级特性:** - `hipMallocManaged()`:统一内存,CPU和DCU共享同一块地址空间,系统自动管理数据迁移【手册10.22】 - `hipMemAdvise()`:给内存管理提建议(比如“这块数据主要被哪个设备访问”)【手册10.25】 - `hipMemPrefetchAsync()`:主动把数据预取到指定设备【手册10.27】 注意`hipFree()`有一个容易被忽略的行为:**它会隐式调用`hipDeviceSynchronize()`**。这意味着调用`hipFree()`时CPU会等待DCU上所有任务完成,如果不小心在性能敏感路径上频繁分配释放内存,可能会成为性能瓶颈。 ### 第三层:执行控制——Kernel怎么跑起来? 在HIP里启动一个Kernel(在DCU上运行的函数),最常用的方式是`hipLaunchKernel`宏【手册7.10】: ```cpp hipLaunchKernel(myKernel, // 要运行的函数 dim3(blocks), // 网格大小(多少个Block) dim3(blockSize), // 每个Block里多少个线程 0, // 动态共享内存大小 0, // Stream(0表示默认流) arg1, arg2, ...); // 函数参数 ``` 如果你需要更精细的控制,还有`hipLaunchKernelExC`,支持传入额外的启动属性(比如`hipLaunchAttributeCooperative`标记协作式启动)【手册7.13】。 Kernel执行前后的同步靠两类API: - `hipDeviceSynchronize()`:等待当前设备上**所有**任务完成【手册1.20】 - `hipStreamSynchronize()`:等待**指定Stream**里的任务完成【手册4.19】 ## 四、进阶能力:流、事件与图 如果只掌握了上面三层,你已经能写DCU程序了。但要写出**高性能**的DCU程序,必须理解这三个概念。 ### 流(Stream):让任务并行起来 Stream可以理解为**DCU上的任务队列**。不同Stream里的任务可以**并行执行**,同一Stream里的任务**按顺序执行**。 ```cpp hipStream_t stream1, stream2; hipStreamCreate(&stream1); // 创建两个流【手册4.6】 hipStreamCreate(&stream2); // 在两个流里同时执行不同的Kernel,它们可以在DCU上并行 hipLaunchKernel(kernelA, ..., stream1); hipLaunchKernel(kernelB, ..., stream2); hipStreamSynchronize(stream1); // 等待stream1完成【手册4.19】 hipStreamSynchronize(stream2); hipStreamDestroy(stream1); // 销毁流【手册4.9】 ``` Stream还可以设置优先级(`hipStreamCreateWithPriority`)【手册4.8】,甚至绑定到指定的计算单元(`hipExtStreamCreateWithCUMask`)【手册26.3】——这种细粒度控制在科学计算中非常有用。 ### 事件(Event):测时间、做同步 Event主要有两个用途:**测时间**和**做同步**。 测时间: ```cpp hipEvent_t start, stop; hipEventCreate(&start); // 创建事件【手册5.1】 hipEventCreate(&stop); hipEventRecord(start, stream); // 在stream里记录开始【手册5.6】 // ... 执行Kernel ... hipEventRecord(stop, stream); hipEventSynchronize(stop); // 等待事件完成【手册5.8】 float ms; hipEventElapsedTime(&ms, start, stop); // 算时间差(毫秒)【手册5.4】 ``` 做同步:让一个Stream等待另一个Stream里的某个事件完成,实现Stream间的依赖关系。 ```cpp hipStreamWaitEvent(stream2, event1, 0); // stream2等待event1完成【手册4.21】 ``` ### 图(Graph):把多个操作打包执行 Graph是HIP里相对较新的特性【手册第20章】,它允许你把多个Kernel、内存拷贝、内存设置操作**预先定义成一个有依赖关系的图**,然后一次性提交执行。好处是**减少CPU启动开销**,特别适合重复执行相同计算模式的场景。 基本用法: ```cpp hipGraph_t graph; hipGraphCreate(&graph, 0); // 创建图【手册20.22】 hipGraphNode_t kernelNode; hipKernelNodeParams params = {...}; hipGraphAddKernelNode(&kernelNode, graph, // 添加Kernel节点【手册20.12】 nullptr, 0, ¶ms); hipGraphExec_t exec; hipGraphInstantiate(&exec, graph, // 实例化图【手册20.53】 nullptr, nullptr, 0); hipGraphLaunch(exec, stream); // 启动图【手册20.60】 hipGraphExecDestroy(exec); // 销毁【手册20.31】 hipGraphDestroy(graph); // 销毁【手册20.24】 ``` DTK 25.04.1还新增了`hipGraphAddNode`通用接口【手册20.91】和`hipGraphInstantiateWithParams`【手册20.94】,让图构建更加灵活。 ## 五、DCU专属的“定制化API” 标准的HIP API已经很强大了,但海光DCU还提供了一套**定制化API**(`du`前缀),让你能触达更底层的硬件能力【手册第28章】: - `duDeviceGetHwQueueNum()`:查询当前DCU有多少个硬件队列【手册28.1】 - `duStreamGetHwQueue()`:查询某个Stream绑定到了哪个硬件队列【手册28.2】 - `duExtStreamCreateWithCUMask()`:创建Stream时指定使用哪些计算单元(CU)【手册26.3】 - `duDeviceFlushCache()`:主动刷新DCU缓存【手册28.4】 - `duKernelGetAql()` / `duKernelAqlLaunch()`:更底层的AQL包管理,可以实现批量Kernel启动【手册28.9】【手册28.10】 - `duGraphGetNodeByLabel()`:通过标签在图中查找节点【手册28.13】 这些API提供了**比标准HIP更细粒度的控制能力**,适合对性能有极致要求的场景——比如需要在多个DCU之间精细调配计算资源的科学计算任务。 ## 六、错误处理:别忽视返回值 HIP API的返回值都是`hipError_t`类型。**每个API调用都应该检查返回值**,这是写出健壮DCU程序的第一步。 ```cpp hipError_t err = hipMalloc(&d_data, size); if (err != hipSuccess) { printf("hipMalloc failed: %s\n", hipGetErrorString(err)); // 获取错误描述【手册3.2】 return -1; } ``` 常用的错误处理辅助函数: - `hipGetErrorName(err)`:返回错误码的名字,如`hipErrorOutOfMemory`【手册3.1】 - `hipGetErrorString(err)`:返回人类可读的错误描述【手册3.2】 - `hipGetLastError()`:返回上一个API调用的错误,并自动重置【手册3.3】 - `hipPeekAtLastError()`:同上,但不重置【手册3.4】 ## 七、从CUDA到HIP:迁移有多简单? 说了这么多,回到最开始的问题:**从CUDA迁移到DCU到底有多麻烦?** 答案是:**比你想象的小得多**。 CUDA和HIP的API在函数名、参数、语义上都高度一致。大部分情况下,你只需要: 1. 把头文件从`cuda_runtime.h`换成`hip/hip_runtime.h` 2. 把所有`cuda`前缀换成`hip` 3. 编译器从`nvcc`换成`hipcc` AMD官方提供了**HIPIFY工具**,可以自动完成大部分CUDA代码到HIP代码的转换。海光的光合开发者社区也提供了“CUDA转码”的AI技能支持。 当然,深度优化时还是会遇到差异(比如warp size、内存层次结构等),但对于**绝大多数应用层代码,迁移成本极低**。 ## 结语 DTK 26.04的HIP Runtime API已经覆盖了从设备管理、内存管理、流和事件到图执行的完整编程模型。280多个API接口,每一行都在回答同一个问题:**怎么让开发者用最熟悉的方式,把计算任务搬到DCU上**。 对于写过CUDA的开发者来说,HIP的学习曲线几乎是一条平线——换几个前缀、改几个头文件,代码就能跑起来。对于没写过GPU程序的开发者来说,HIP的API设计直观、文档齐全(光合开发者社区提供了完整的API参考手册),入门门槛也远低于预期。 国产加速卡能不能用、好不好用,**API是第一步,也是最重要的一步**。从DTK 26.04的HIP Runtime API来看,这一步走得扎实。
全部评论(2)
只看楼主
暂无数据
--
楼主
Level.2
0 帖子
楼主热帖
暂无数据