使用cupti拦截多并发kernel launch
具体项目地址:https://github.com/Dylan887/CUDA/tree/main/intercept_launch
拦截原理
原理
拦截 CUDA 内核(kernel)执行的原理基于 CUPTI(CUDA Profiling Tools Interface)提供的回调机制。通过 CUPTI 提供的回调机制,开发者可以在内核执行之前和之后插入自定义代码,这就是所谓的拦截内核执行。其核心思想是通过注册回调函数,在 CUDA Driver API 或 Runtime API 函数调用时,捕获这些函数的执行时机,进而对 CUDA 内核启动进行拦截或监控。
本文的并发采取的是多流模式(在所有核函数使用同一个流也可以)
回调函数将拦截到的核函数指针指向null,所以核函数无法正常启动,批量释放的时候,重新获取原函数指针,所用的流也是原函数的流
流程
1. 注册回调函数
通过 cuptiSubscribe()
函数,开发者可以注册一个回调函数,该函数会在 CUDA 内核启动时被调用,监控cuda程序的行为。注册时,需要指定回调函数的域(domain),比如 CUPTI_CB_DOMAIN_DRIVER_API
表示 CUDA Driver API,或者 CUPTI_CB_DOMAIN_RUNTIME_API
表示 CUDA Runtime API。
CUDA Runtime API(运行时 API):
- 更高层次的抽象,更易于使用。
- 适合大多数开发者,提供了一些默认配置,简化了 CUDA 编程的难度。
- 通常用于与 CUDA 驱动层打交道较少的场景,例如标准的 CUDA 程序编写。
- 需要 CUDA 库(
libcudart.so
)的支持。
CUDA Driver API(驱动 API):
- 低层次的 API,更接近硬件,提供了更高的灵活性和控制。
- 对比 Runtime API 更复杂,但适合那些需要对设备和内存管理进行更细粒度控制的场景。
- 不需要 CUDA 运行时库的支持,可以直接与 CUDA 驱动打交道。
本文监控的是CUDA driver API
,检测cuda runtime API 时拦截不成功
2.启用回调功能
使用 cuptiEnableCallback()
启用对特定 CUDA API 的回调,例如 cuLaunchKernel
或 cudaLaunchKernel
。当这些 API 被调用时,CUPTI 会执行事先注册的回调函数。
cuptiEnableCallback(1, subscriber, CUPTI_CB_DOMAIN_DRIVER_API, CUPTI_DRIVER_TRACE_CBID_cuLaunchKernel);
3. 实现回调函数
回调函数的实现是拦截 CUDA 内核的核心。在回调函数中,可以访问即将启动的内核的参数。通过修改这些参数,可以控制内核的执行。
- 获取内核参数:通过
cbInfo->functionParams
可以获取内核的启动参数,例如网格维度、块维度、共享内存大小等。 - 修改内核参数:可以通过修改回调函数中的参数,来更改内核的启动行为,甚至可以通过将网格大小设置为零或将函数指针置为 NULL 来阻止内核执行。
示例代码:
void CUPTIAPI cuptiCallback(void *userdata, CUpti_CallbackDomain domain,
CUpti_CallbackId cbid, const CUpti_CallbackData *cbInfo) {
if (domain == CUPTI_CB_DOMAIN_DRIVER_API && cbid == CUPTI_DRIVER_TRACE_CBID_cuLaunchKernel) {