一、Debugging tools
1. Using ltrace
ltrace
是一个标准的 Linux 工具,它可以在每次动态库调用时向标准错误输出一条消息。由于 ROCr(Radeon Open Compute 的 R 语言支持)和 ROCt(ROC 内核驱动的用户空间接口)都是动态库,使用 ltrace
可以轻松追踪这些库中的活动。追踪可以作为一种强大的方式,快速观察应用程序的流程,然后再使用命令行调试器深入了解细节。ltrace
是一个有助于可视化整个 ROCm 软件栈运行时行为的工具。追踪还可以显示与关键路径上意外调用昂贵 API 调用相关的性能问题(在性能上成本较高,可能是因为这些 API 调用涉及到大量的计算、内存使用、磁盘 I/O 或网络通信等,从而导致程序执行效率降低或响应时间延长)。
举个栗子:
2. Using ROCgdb
ROCgdb 是基于 GDB(GNU 源代码级调试器)开发的,专为 Linux 系统上的 ROCm 环境设计的源代码级调试器。它与 CUDA 开发者使用的 cuda-gdb 类似,都是用来调试 GPU 程序的。
举个栗子:
二、Debugging HIP Applications
三、Useful Environment Variables
HIP (Heterogeneous-compute Interface for Portability) 提供了一些环境变量,这些环境变量允许 HIP、hip-clang(HIP 的 C/C++ 编译器前端)或 HSA(Heterogeneous System Architecture)驱动程序禁用某些特性或优化。这些环境变量不是为了生产环境设计的,但它们在诊断应用程序(或驱动程序)中的同步问题时非常有用。
1. Kernel Enqueue Serialization
在 GPU 编程中,内核(Kernel)排队序列化是一个重要的概念,它可以帮助开发者控制内核命令的执行顺序。通过使用环境变量,开发者可以指定 HIP 运行时在执行内核或数据传输时的同步行为。以下是一些与内核排队序列化相关的环境变量:
-
AMD_SERIALIZE_KERNEL
:- 设置为
1
:在执行内核之前等待前一个内核命令完成。 - 设置为
2
:在执行内核之后等待当前内核命令完成。 - 设置为
3
:在执行内核之前和之后都等待命令完成。
- 设置为
-
AMD_SERIALIZE_COPY
:- 设置为
1
:在执行数据复制之前等待前一个复制命令完成。 - 设置为
2
:在执行数据复制之后等待当前复制命令完成。 - 设置为
3
:在执行数据复制之前和之后都等待命令完成。
- 设置为
这些环境变量主要用于调试和性能分析,它们可以强制 HIP 运行时在特定的点进行同步,从而帮助开发者诊断潜在的同步问题或性能瓶颈。
例如,如果你想要确保在执行每个内核之前,所有先前的内核命令都已经完成,你可以在 shell 中设置 AMD_SERIALIZE_KERNEL
环境变量:
export AMD_SERIALIZE_KERNEL=1
这将强制 HIP 运行时在每个内核执行前等待,直到 GPU 完成所有先前的内核命令。
同样地,如果你想要控制数据复制命令的同步行为,可以使用 AMD_SERIALIZE_COPY
环境变量:
export AMD_SERIALIZE_COPY=2
这将使得 HIP 运行时在每个数据复制命令执行后等待,直到复制操作完成。
请注意,过度使用同步可能会导致性能下降,因为它会阻止 GPU 利用其并行处理能力。因此,这些环境变量通常只在调试阶段使用,以确保程序的正确性。在生产环境中,应谨慎使用这些同步机制,以避免不必要的性能损失。
2. Making Device Visible
在具有多个设备的系统中,通过设置环境变量 HIP_VISIBLE_DEVICES
,可以控制 HIP 运行时只看到指定索引的设备。这个特性对于需要在特定设备上运行应用程序或者进行设备管理时非常有用。
以下是如何使用 HIP_VISIBLE_DEVICES
的一些示例:
-
在命令行中设置: 如果你想要在命令行中设置,可以如下操作,只让 HIP 看到索引为 0 和 1 的设备:
$ HIP_VISIBLE_DEVICES=0,1
-
在应用程序中设置: 在应用程序中,你可以使用
setenv
函数来设置环境变量。例如,如果你想要在程序中根据设备总数来设置可见设备,可以这样做:if (totalDeviceNum > 2) { setenv("HIP_VISIBLE_DEVICES", "0,1,2", 1); assert(getDeviceNumber(false) == 3); // 确保设置成功 // ... 其他代码 ... }
这里,
setenv
的第三个参数设置为1
,表示如果环境变量已经存在,则覆盖它。 -
环境变量的作用:
HIP_VISIBLE_DEVICES
环境变量指定了一个由逗号分隔的设备索引序列。HIP 运行时将只初始化和使用这些索引对应的设备。未包含在序列中的设备将对 HIP 运行时不可见。 -
注意事项:
- 设备索引通常从 0 开始。
- 确保在调用任何 HIP API 之前设置
HIP_VISIBLE_DEVICES
,因为一旦初始化完成,HIP 运行时可能就不会再响应环境变量的变更。 - 使用
assert
来验证环境变量的设置是否成功,这是一种良好的编程实践。
-
在程序中获取设备数量: 在示例代码中,
getDeviceNumber(false)
被用来获取当前 HIP 运行时可见的设备数量。参数false
表示不包括暂不支持的设备。
通过这种方式,开发者可以灵活地控制应用程序在哪些设备上运行,这对于资源分配、性能优化和多设备环境中的设备特定调试非常有用。
3. Dump code object
开发者可以通过设置环境变量 GPU_DUMP_CODE_OBJECT
来转储代码对象,以便分析与编译器相关的问题。这个环境变量通常用于调试和分析 HIP 程序的编译过程,帮助开发者理解编译器如何将源代码转换成可以在 GPU 上执行的指令。通过转储代码对象,开发者可以深入查看编译过程中的中间表示(IR)或者机器码,进而诊断可能存在的编译问题或优化机会。
4. HSA related environment variables
HSA(Heterogeneous System Architecture)环境变量提供了一种方法,帮助开发者分析和解决在驱动程序或硬件上遇到的问题。以下是一些与 HSA 相关的环境变量及其用途:
-
HSA_ENABLE_SDMA=0
:- 这个环境变量会禁用专用的DMA(Direct Memory Access)复制引擎,转而使用计算着色器(compute shader)blit内核来进行主机到设备以及设备到主机的内存复制操作。
- 计算着色器复制具有较低的延迟(通常小于5微秒)并且能够达到DMA引擎带宽的大约80%。
- 当怀疑硬件复制引擎存在问题时,这个变量可以用于隔离问题。
-
HSA_ENABLE_INTERRUPT=0
:- 禁用中断,改为使用基于内存的轮询来检测完成信号。
- 这个环境变量有助于诊断驱动程序中的中断风暴(interrupt storm)问题,中断风暴是指由于中断信号过多导致的性能问题。
使用这些环境变量可以帮助开发者在开发和调试阶段更好地理解程序的行为,尤其是在性能调优和硬件兼容性测试中。例如,如果开发者怀疑DMA引擎存在问题,可以通过设置 HSA_ENABLE_SDMA=0
来测试程序是否能够正常工作,或者观察性能是否有所下降。
同样,如果系统中出现了大量中断请求导致的性能问题,可以通过设置 HSA_ENABLE_INTERRUPT=0
来禁用中断,然后使用内存轮询作为替代,以确定是否中断是造成问题的原因。
使用这些环境变量时,通常在程序运行前通过命令行或脚本来设置它们。例如,在Linux系统中,可以在shell中设置环境变量,如下所示:
export HSA_ENABLE_SDMA=0
export HSA_ENABLE_INTERRUPT=0
然后运行你的应用程序,观察在这些变量被设置后的行为变化。这可以为进一步的调试和性能分析提供有价值的信息。
5. Summary of Environment Variables in HIP
四、General Debugging Tips
通用调试技巧可以帮助开发者更有效地识别和解决问题。以下是一些使用 GDB(GNU Debugger)和其他工具进行调试的技巧:
-
使用
gdb --args
: 启动 GDB 并传递可执行文件及其参数。这可以方便地在 GDB 中启动调试会话,而无需在 GDB 提示符下手动加载程序。gdb --args ./your_program arg1 arg2 ...
-
在 GDB 中设置环境变量: 使用
set env
命令在 GDB 中设置环境变量,注意命令中不使用=
符号。(gdb) set env AMD_SERIALIZE_KERNEL 3
-
分析异步命令生成的故障: 故障可能在运行时被捕获,但实际是由 GPU 上的异步命令生成的。GDB 回溯将显示运行时中的路径。
-
确定故障的真实位置: 通过设置环境变量
AMD_SERIALIZE_KERNEL=3
和AMD_SERIALIZE_COPY=3
,强制内核同步执行,这将迫使 HIP 运行时在返回之前等待内核执行完成。如果故障发生在内核执行期间,你可以在回溯中看到启动内核的代码。可能需要一些推测来确定哪个线程实际上导致了问题 - 通常是在libhsa-runtime64.so
中等待的线程。 -
内核中的虚拟内存故障可能由以下原因引起:
- 不正确的代码,例如一个循环超出了数组边界。
- 内存问题,例如无效的内核参数(空指针、未注册的主机指针、错误的指针)。
- 同步问题。
- 编译器问题,例如编译器生成的错误代码。
- 运行时问题。
-
其他调试技巧:
- 使用 GDB 的
backtrace
或bt
命令来查看当前线程的调用栈。 - 使用
info registers
查看当前线程的寄存器状态。 - 使用
watch
设置观察点,当特定变量的值改变时程序将暂停。 - 使用
print
或p
命令来打印变量的值或表达式的结果。
- 使用 GDB 的
-
性能分析:
- 使用性能分析工具(如
perf
)来识别性能瓶颈。 - 使用 GDB 的
profile
命令来收集性能数据。
- 使用性能分析工具(如
-
内存调试:
- 使用 GDB 的
check
命令来检查内存损坏。 - 使用
malloc
调试选项来跟踪内存分配。
- 使用 GDB 的
-
源代码和编译器优化:
- 确保源代码是最新的,并且使用适合调试的编译器优化级别(通常是
-O0 -g
)。
- 确保源代码是最新的,并且使用适合调试的编译器优化级别(通常是
-
文档和社区资源:
- 参考官方文档和社区论坛来获取特定于平台的调试技巧和最佳实践。
调试是一个复杂的过程,通常需要结合多种工具和技术。通过使用这些技巧,你可以更深入地了解程序的行为,并更有效地定位和解决问题。
五、HIP Version
HIP(Heterogeneous-compute Interface for Portability)是一种允许代码在多种GPU架构上运行的编程接口,它与ROCm(Radeon Open Compute)紧密集成。自ROCm v4.2版本起,HIP版本号的定义方式发生了变化,采用以下公式来表示版本号:
HIP_VERSION=HIP_VERSION_MAJOR * 10000000 + HIP_VERSION_MINOR * 100000 + HIP_VERSION_PATCH)
这里的三个组成部分代表:
- HIP_VERSION_MAJOR:主版本号,通常表示有重大更新或不向后兼容的改变。
- HIP_VERSION_MINOR:次版本号,表示向后兼容的功能更新或改进。
- HIP_VERSION_PATCH:补丁号,表示向后兼容的问题修复或小的改进。
要查询当前HIP的版本,可以使用HIP API提供的函数 hipRuntimeGetVersion
,其调用方式如下:
uint32_t runtimeVersion;
hipRuntimeGetVersion(&runtimeVersion);
这个函数将返回一个整数值,代表当前的HIP运行时版本,按照上述定义的公式解析。
需要注意的是,HIP运行时的版本定义与CUDA(Compute Unified Device Architecture)不同。HIP旨在提供跨不同GPU平台的兼容性,但是在AMD平台上,该函数返回的是HIP运行时的版本号;而在NVIDIA平台上,它返回的是CUDA运行时的版本号。因此,HIP版本和CUDA版本之间没有直接的映射或相关性。