CUDA Driver API 与 CUDA Runtime API
作者:罗上文,微信:Loken1,公众号:FFmpeg弦外之音
CUDA 里有两套 API,Driver API 与 Runtime API,如下:

Driver API 使用起来比较繁琐,但它允许我们更详细地控制GPU的行为。Runtime API 是基于 Driver API 封装后的高层接口,可以自动管理上下文,开发更简单。
这两套 API 有很多函数是类似的,如下:
| Driver API | Runtime API | 函数作用 |
|---|---|---|
| cuMemAlloc() | cudaMalloc() | 申请显卡内存 |
| cuMemFree() | cudaFree() | 释放显卡内存 |
| cuMemcpy() | cudaMemcpy() | 拷贝内存 |
市面上很多 CUDA 编程的书,例如《CUDA 编程基础与实践》,都是讲 Runtime API 的,讲 Driver API 的书相对较少,而 FFmpeg 里用的恰恰是 CUDA Driver API。
所以当你看完《CUDA 编程基础与实践》,想去 FFmpeg 代码里搜 cudaMalloc() 关键词,会发现搜不到,这是因为 FFmpeg 根本没用到 CUDA Runtime API。
通俗地理解 CUDA 的 Driver API 与 Runtime API
这两套 API 实际上就是两个库,如下:
| Windows | Linux | |
|---|---|---|
| Driver API | nvcuda.dll | libcuda.so |
| Runtime API | cudart64_101.dll | libcudart.so |
Driver API 库是在安装显卡驱动的时候装的,它的完整路径是 C:\Windows\System32\nvcuda.dll。Runtime API 库是在安装 CUDA Toolkit 的时候装的,它的完整路径是 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1\bin
提醒:显卡驱动 与 CUDA Toolkit 是两个东西。
CUDA Runtime API 入门示例
文件名 cuda_runtime.cu
#include <stdio.h>
#include <cuda_runtime.h>
int main(int argc, char **argv)
{
printf("CUDA Runtime API !\n");
float *d_A;
cudaMalloc((float**)&d_A, 1024);
return 0;
}
编译代码:
nvcc -G -Xcompiler "/Zi" -arch=sm_75 -lcudart -o cuda_runtime.exe cuda_runtime.cu
-G 选项是生成 CUDA 代码的调试信息
-Xcompiler "/Zi" 是让 nvcc 把这个 /Zi 选项传递给 MSVC 的 cl.exe 编译器,生产 C 代码的 PDB 调试信息
-lcudart 是链接 cudart64_101.dll 动态库,如果我们不加这个选项,它默认用的是 cudart_static.lib 静态库。
然后我们用 dumpbin 查看一下 cuda_runtime.exe 的依赖
dumpbin /dependents cuda_runtime.exe

可以看到,已经成功链接了 cudart64_101.dll。
我们用 WinDbg 来验证一下 cudaMalloc() 是不是 cudart64_101.dll 动态库里面的函数,WinDbg 命令如下:
bu cuda_runtime!main,在入口设置断点p,Step overt,跳进去cudaMalloc()函数内部k,展示调用栈

可以看到代码跑进了 cudart64_101.dll 动态库。
如果我们编译的时候去掉 -lcudart 选项,那就会使用 cudart_static.lib 静态库 来代替 cudart64_101.dll 动态库。cudaMalloc() 是以静态库的方式插入 exe 文件的,这样 cuda_runtime.exe 就不会依赖 cudart64_101.dll。
这时候 WinDbg 的 k 命令展示的调用栈如下:

去掉 -lcudart 选项 后, cudaMalloc() 函数是属于 cuda_runtime.exe 模块的,而不是属于 cudart64_101.dll 模块的
CUDA Driver API 入门示例
文件名 cuda_driver.cu
#include <stdio.h>
#include <cuda.h>
int main(int argc, char **argv)
{
printf("CUDA Driver API !\n");
CUdeviceptr d_A;
cuMemAlloc(&d_A, 1024);
return 0;
}
编译代码:
nvcc -G -Xcompiler "/Zi" -arch=sm_75 -lcuda -o cuda_driver.exe cuda_driver.cu
-lcuda 是链接 nvcuda.dll 动态库,CUDA Driver API 没有静态库,只有动态库。然后我们用 dumpbin 查看一下 cuda_driver.exe 的依赖
dumpbin /dependents cuda_driver.exe

可以看到,已经成功链接了 nvcuda.dll。
我们再用 WinDbg 来验证一下 cuMemAlloc() 是不是 nvcuda.dll 动态库里面的函数,WinDbg 命令如下:
bu cuda_driver!main,在入口设置断点p,Step overt,跳进去cuMemAlloc()函数内部k,展示调用栈

可以看到代码跑进了 nvcuda.dll 动态库。我们也可以用 lm a @rip 命令查看当前代码跑到哪个模块,可以看到 nvcuda.dll 的完整路径,如下:

虽然 CUDA Runtime API 好用,但它需要安装 CUDA Toolkit,依赖比较重。FFmpeg 代码里用的都是 CUDA Driver API,所以 FFmpeg 并不依赖 cudart64_101.dll
市面上关于 CUDA Driver API 的资料非常少,推荐两本书给读者学习。.
- 《CUDA专家手册:GPU编程权威指南》- 苏统华
- CUDA Driver API 官方手册