重点:两套API层级关系、驱动API完整流程、上下文Context、Module、多GPU管理;面试高频考点。
一、两套API总览
CUDA对外两套接口:Runtime API(运行时) 、Driver API(驱动)
| 特性 | Runtime API(日常写的) | Driver API |
|---|---|---|
| 头文件 | <cuda_runtime.h> |
<cuda.h> |
| 依赖库 | cudart |
cuda |
| 抽象层级 | 高层,自动管理上下文、模块加载 | 底层,全部手动管理 |
| 易用度 | 简单,<<<>>>直接启动kernel |
繁琐,所有步骤显式调用 |
| 底层关系 | Runtime API 是对 Driver API 的封装,底层依然调用驱动API | 原生驱动接口 |
| 适用场景 | 绝大多数业务CUDA程序 | 框架开发、JIT、动态加载cubin/ptx、精细资源控制 |
注意:可以混合使用两套API,但必须保证上下文一致。Runtime会自动创建Primary Context主上下文。
二、Runtime API回顾
我们平时写的代码全部是Runtime:
cudaGetDeviceCount(&cnt);
cudaSetDevice(0);
cudaMalloc / cudaMemcpy / cudaFree
cudaDeviceSynchronize();
特点:
cudaSetDevice()会隐式创建主上下文(Primary Context),线程绑定这个上下文;<<<>>>kernel启动语法,runtime自动完成模块加载、参数打包调用;- 不用手动管理
CUcontext、CUmodule。
三、Driver API完整执行流程(7步)
1. cuInit(0) // 初始化驱动库
2. cuDeviceGet(&dev, idx) // 获取GPU设备句柄
3. cuCtxCreate(&ctx, flags, dev) // 创建上下文(核心!)
4. cuModuleLoad(&mod, "xxx.cubin") // 加载cubin/ptx模块
5. cuModuleGetFunction(&func, mod, "kernel_name") // 获取kernel函数句柄
6. cuLaunchKernel(...) // 显式启动kernel
7. cuModuleUnload(); cuCtxDestroy(); // 资源释放
关键概念
- CUdevice:物理GPU设备,只读描述硬件;
- CUcontext 上下文 :GPU资源容器(显存、流、模块、事件)。一个线程必须绑定有效的上下文,才能执行GPU操作 。
cuCtxCreate():新建独立上下文;cuDevicePrimaryCtxRetain():拿到Runtime使用的主上下文(两套API混用时用);cuCtxSetCurrent(ctx):切换当前线程的上下文,多GPU必备。
- CUmodule 模块 :对应编译产物 cubin / PTX;可以从文件加载,也可以内存加载(JIT场景
cuModuleLoadDataEx)。 - CUfunction:模块内的kernel函数句柄。
⚠️ kernel名字修饰坑
C++编译kernel会做名字mangle!直接写原始函数名会找不到。
cuModuleGetFunction(&kernel, module, "_Z9vectorAddPKfS0_Pfi");
解决方法:
- 用
extern "C"包裹__global__函数,关闭mangle;- 或者用
cuobjdump --dump-namelist xxx.cubin查看mangle后的真实符号名。
cuLaunchKernel 参数格式
void *args[] = { &d_a, &d_b, &d_c, &N };
cuLaunchKernel(func,
gridX,gridY,gridZ,
blockX,blockY,blockZ,
sharedMemBytes, stream,
args, nullptr
);
args:把kernel每一个参数取地址,存入指针数组;sharedMemBytes:block共享内存字节数;stream:流,填nullptr代表默认流。
内存分配(Driver API)
不用cudaMalloc,使用:
cuMemAlloc((CUdeviceptr*)&d_ptr, bytes);
cuMemcpyHtoD / cuMemcpyDtoH
cuMemFree((CUdeviceptr)d_ptr);
CUdeviceptr是驱动API的设备地址类型,不能直接和void*混用,需要强制转换。
四、什么时候选择Driver API?
- AI框架后端开发(如Triton、PyTorch CUDA后端),需要精细控制;
- JIT编译场景:运行时加载PTX,动态编译执行kernel;
- 插件架构:动态加载/卸载cubin模块;
- 需要直接操作上下文、细粒度驱动控制;
普通应用不要盲目用Driver API,代码量大,容易资源泄漏。
五、多GPU管理
-
Runtime方式 :
cudaSetDevice(devId),隐式切换主上下文; -
Driver API方式 :每个GPU创建自己独立
CUcontext,通过cuCtxSetCurrent()在线程中来回切换上下文。cuDeviceGet(&dev0,0);
cuDeviceGet(&dev1,1);
cuCtxCreate(&ctx0,0,dev0);
cuCtxCreate(&ctx1,0,dev1);cuCtxSetCurrent(ctx0);
// 在GPU0上执行操作cuCtxSetCurrent(ctx1);
// 在GPU1上执行操作
重要:上下文是绑定线程的!不同线程可以绑定不同context;同一个线程同一时刻只能有一个current上下文。
六、两套API混合使用(面试常考)
场景:主程序用Runtime,某一部分逻辑调用Driver API。
CUcontext ctx;
cudaCtxGetCurrent(&ctx); // 获取runtime正在使用的primary context
cuCtxSetCurrent(ctx);
// 之后就可以调用cuXXX系列驱动函数
风险:不要随意
cuCtxDestroy主上下文,会把Runtime搞崩。
课后练习重点
- 驱动API查询设备信息:显卡名、算力、显存;
- 将向量加法编译cubin,Driver API完整加载启动,校验结果;
cuModuleLoadDataEx内存加载PTX模拟JIT;- Runtime+Driver混合调用;
- 多GPU,两个context来回切换,分别跑kernel。
面试简答考点
Q:Context上下文是什么?
A:GPU资源的容器,显存、流、模块都隶属于上下文;线程必须绑定上下文才能操作GPU。Runtime自动创建Primary Context;Driver需要手动创建、切换、销毁。
Q:Primary Context主上下文?A:Runtime API自动为每个GPU创建的上下文;Driver API可以拿到这个上下文实现两套API混用。
Q:Driver API加载kernel找不到函数符号?C++名字改编mangle,要么extern "C",要么查看cubin导出符号表。
下一节预告
第6板块第3节:错误处理与调试工具 compute‑sanitizer、cuda‑gdb
- CUDA_CHECK、CUDA_LAUNCH_CHECK、CUDA_SYNC_CHECK 宏
- compute‑sanitizer:内存越界、use‑uninit、racecheck数据竞争
- cuda‑gdb调试kernel,切换GPU线程上下文