1. Context是什么?
context类似于CPU上的进程,管理由Driver层分配的资源的生命周期
多线程分配调用的GPU资源同属一个context下,通常与CPU的一个进程对应。
Context上下文是设备与特定进程相关联的所有状态
例如Kernel Code会对GPU的使用造成不同的状态如:内存映射、分配、加载等
Context主要用来保存所管理数据来控制和使用设备
GPU中的Context相当于CPU的Program,一块GPU上可以有多个Context,但他们之间是相互间隔的
它表示执行某些任务(例如 CUDA 计算、图形、H.264 编码等)共同需要和实例化的所有状态(数据、变量、条件等)
CUDA 上下文被实例化以在 GPU 上执行 CUDA 计算事件,由 CUDA 运行时 API 隐式或由 CUDA 驱动程序 API 显式执行。
2. Context能做什么?
- 持有分配的内存列表
- 持有加载进该设备的Kernel Code
- CPU与GPU之间的Unified Memory
3. 如何使用Context?
CUDA Driver需要Context
开始时使用cuCtxCreate() 创建,结束时cuCtxDestory销毁
用粗Device PrimaryctxRetain()创建上下文
cuCtxGetCurrent()获取当前上下文
可以使用堆栈管理多个上下文cuCtxPushCurrent()压入cuCtxPopCurrent()压出
对ctxA使用cuCtxPushCurrent()和cuCtxCreate()都相当于将ctxA放在栈顶(让他成为current context)
CUDA runtime可以自动创建,是基于cuDevicePrimaryCtxRetain()创建的
示例代码
#include <cuda.h>
#include <stdio.h>
#include <string.h>
#define checkDriver(op) __check_cuda_driver((op), #op, __FILE__, __LINE__)
bool __check_cuda_driver(CUresult code, const char* op, const char* file, int line){
if(code != CUresult::CUDA_SUCCESS){ // 条件成立, 返回值为flase
const char* err_name = nullptr;
const char* err_message = nullptr;
cuGetErrorName(code, &err_name);
cuGetErrorString(code, &err_message);
printf("%s:%d %s failed. \n code = %s, message = %s\n", file, line, op, err_name, err_message);
return false;
}
return true;
}
int main(){
// 检查cuda driver的初始化
checkDriver(cuInit(0));
// 为设备创建上下文
CUcontext ctxA = nullptr; // CUcontext 其实是 struct CUctx_st*(是一个指向结构体CUctx_st的指针)
CUcontext ctxB = nullptr;
CUdevice device = 0;
checkDriver(cuCtxCreate(&ctxA, CU_CTX_SCHED_AUTO, device)); // 这一步相当于告知要某一块设备上的某块地方创建 ctxA 管理数据
checkDriver(cuCtxCreate(&ctxB, CU_CTX_SCHED_AUTO, device));
printf("ctxA = %p\n", ctxA);
printf("ctxB = %p\n", ctxB);
/*
contexts 栈:
ctxB -- top <--- current_context
ctxA
...
*/
// 获取当前上下文信息
CUcontext current_context = nullptr;
checkDriver(cuCtxGetCurrent(¤t_context)); // 这个时候current_context 就是上面创建的context
printf("current_context = %p\n", current_context);
// 可以使用上下文堆栈对设备管理多个上下文
// 压入当前context
checkDriver(cuCtxPushCurrent(ctxA)); // 将这个 ctxA 压入CPU调用的thread上。专门用一个thread以栈的方式来管理多个contexts的切换
checkDriver(cuCtxGetCurrent(¤t_context)); // 获取current_context (即栈顶的context)
printf("after pushing, current_context = %p\n", current_context);
/*
contexts 栈:
ctxA -- top <--- current_context
ctxB
...
*/
// 弹出当前context
CUcontext popped_ctx = nullptr;
checkDriver(cuCtxPopCurrent(&popped_ctx)); // 将当前的context pop掉,并用popped_ctx承接它pop出来的context
checkDriver(cuCtxGetCurrent(¤t_context)); // 获取current_context(栈顶的)
printf("after poping, popped_ctx = %p\n", popped_ctx); // 弹出的是ctxA
printf("after poping, current_context = %p\n", current_context); // current_context是ctxB
checkDriver(cuCtxDestroy(ctxA));
checkDriver(cuCtxDestroy(ctxB));
// 更推荐使用cuDevicePrimaryCtxRetain获取与设备关联的context
// 注意这个重点,以后的runtime也是基于此, 自动为设备只关联一个context
checkDriver(cuDevicePrimaryCtxRetain(&ctxA, device)); // 在 device 上指定一个新地址对ctxA进行管理
printf("ctxA = %p\n", ctxA);
checkDriver(cuDevicePrimaryCtxRelease(device));
return 0;
}
参考文献
- https://www.cs.cmu.edu/afs/cs/academic/class/15668-s11/www/cuda-doc/html/group__CUDA__CTX_g65dc0012348bc84810e2103a40d8e2cf.html