2 cuda_context
1 前言
在上一篇文章《1-cuda_API》中降到cuda Driver API
主要的一个作用就是管理cuda Context
,那么这里讲解一下什么是cuda Context
2 cuda context 简介
官方文档17.1. Context一段话如下:
1 | A CUDA context is analogous to a CPU process. All resources and actions performed within the driver API are encapsulated inside a CUDA context, and the system automatically cleans up these resources when the context is destroyed. |
翻译过来就是
1 | CUDA 上下文类似于 CPU 进程。驱动程序 API 中执行的所有资源和操作都封装在 CUDA 上下文中,当上下文被破坏时,系统会自动清理这些资源。 |
1 | CUDA work occurs within a process space for a particular GPU known as a context. The context encapsulates kernel launches and memory allocations for that GPU as well as supporting constructs such as the page tables. The context is explicit in the CUDA Driver API but is entirely implicit in the CUDA Runtime API, which creates and manages contexts automatically. |
翻译过来就是
1 | CUDA 工作发生在称为上下文的特定 GPU 的进程空间内。上下文封装了 GPU 的内核启动和内存分配以及页表等支持结构。上下文在 CUDA 驱动程序 API 中是显式的,但在 CUDA 运行时 API 中是完全隐式的,它会自动创建和管理上下文。 |
总结下来就是:
context 是一种上下文,可以关联对 GPU 的所有操作
在任何给定的时间,一个 GPU 上只能有一个活动的 CUDA context。(关于cuda context的调度策策略我没有找到)
虽然 CUDA context 在运行时可以被创建和销毁,但同一时间只有一个 context 可以与 GPU 进行交互。当你在同一 GPU 上创建一个新的 CUDA context 时,它会覆盖之前的 context。
在单 GPU 环境下,多个任务通常使用 CUDA streams 来实现并发性,而无需创建多个 CUDA context。CUDA streams 允许在同一个 context 中并发地执行多个任务,而不需要切换 context,从而提高了 GPU 的利用率。
context 与一块显关联,一个显卡可以被多个 context 关联
在单 GPU 环境下,
cuCtxCreate
可以用来创建一个 CUDA 上下文,然后在该上下文中执行 CUDA 的计算任务。这是 CUDA 编程中常见的用法(在单 GPU 环境下,通常使用多个 CUDA stream 可以更有效地进行并发计算,而不太需要显式地创建多个 CUDA context。在单 GPU 上切换 CUDA context 确实可能涉及到一些开销,而多个 stream 可以通过异步执行来提高并发性,而不引入额外的上下文切换开销。)。在多 GPU 环境下,如果系统中有多个 GPU 设备,每个 GPU 设备都有自己的 primary context。你可以使用
cuCtxCreate
来创建多个上下文,每个上下文关联到不同的 GPU 设备上。这样,你就可以在多个 GPU 上并行地执行 CUDA 计算任务。如果您有多进程使用 GPU,通常会在该 GPU 上创建多个上下文。正如您所发现的,可以从单个进程创建多个上下文,但通常没有必要。
总的来说,
cuCtxCreate
主要用于创建 CUDA 上下文,而在多 GPU 环境下,可以用它来创建多个上下文以便在多个 GPU 上并行执行任务。每个线程都有一个栈结构储存 context,栈顶是当前使用的 context,对应有 push、pop 函数操作 context 的栈,所有 api 都以当前 context 为操作目标
由于是高频操作,是一个线程基本固定访问一个显卡不变,且只使用一个 context,很少会用到多 context
CreateContext、PushCurrent、PopCurrent 这种多 context 管理就显得麻烦,还得再简单
因此推出了 cuDevicePrimaryCtxRetain,为设备关联主 context,分配、释放、设置、栈都不用你管
2.1 关于cuDevicePrimaryCtxRetain
cuDevicePrimaryCtxRetain
函数的作用是增加一个 GPU 设备的 primary context 的引用计数。在多线程环境下,如果一个线程先后使用了 GPU,并在释放资源时调用了 cuCtxDestroy
来销毁上下文,如果其他线程仍需要使用这个 GPU,它们就会面临没有有效上下文可用的问题。
cuDevicePrimaryCtxRetain
的作用就是为了避免这个问题。它会增加 primary context 的引用计数,使得即使一个线程销毁了它的上下文,其他线程仍然可以继续使用。当其他线程不再需要 GPU 时,它们可以通过调用 cuCtxDestroy
来减少引用计数,当引用计数降为零时,真正地释放资源。
这样,使用 cuDevicePrimaryCtxRetain
可以确保在多线程环境中,一个线程使用完 GPU 后,其他线程仍能够在同一个 GPU 上使用有效的 CUDA 上下文,而不受到一个线程释放上下文的影响。
2.2 管理cuda context
如何管理上下文:
- 在 cuda driver 同样需要显示管理上下文
- 开始时 cuCtxCreate() 创建上下文,结束时 cuCtxDestroy 销毁上下文。像文件管理一样需手动开关。
- 用 cuDevicePrimaryCtxRetain() 创建上下文更好!
- cuCtxGetCurrent() 获取当前上下文
- 可以使用堆栈管理多个上下文 cuCtxPushCurrent() 压入,cuCtxPopCurrent() 推出
- 对 ctxA 使用 cuCtxPushCurrent() 和 cuCtxCreate() 都相当于将 ctxA 放到栈顶(让它成为 current context)
- cuda runtime 可以自动创建,是基于 cuDevicePrimaryCtxRetain() 创建的
切换cuda context的示例代码如下:
1 | // CUDA驱动头文件cuda.h |
运行结果如下
2.3 测试 cuda context
上面提到,在一个GPU上创建多个cuda context会引入一部分的开销,我理解就像CPU切换进程一样。上面也提到了,一个 GPU 上只能有一个活动的 CUDA context。
下面针对单GPU上单个cuda context和多个cuda context进行测试验证。
代码如下(需要注意的是如果调用了cuda driver 的API,那么需要添加头文件cuda.h 和链接库 libcuda.so)
1 |
|
MULTIPLE_CUDA_CONTEXT 为1 时,测试多cuda context的运行结果如下:
1 | ctxA = 0x556f4f61e800 streamA=0x556f4f9e5860 |
MULTIPLE_CUDA_CONTEXT 为0 时,测试单cuda context的运行结果如下:
1 | ctxA = 0x56205823f800 streamA=0x562058606970 |
可以看出多个cuda context整体的运行时间会长一点,使用nsys system分析结果如下(如果使用nsys的命令行可以传入参数—gpuctxsw=true)
下图是多个cuda context的分析,可以看出cuda context切换需要时间,我这边测试是60us左右。
下图是单个cuda context的分析