上下文类似于CPU中的进程。除了少数例外,它是管理CUDA程序中所有对象生命周期的容器,包括以下部分:
CUDA runtime不提供对CUDA上下文的直接访问,它通过延迟初始化(deferred initialization)来创建上下文。每一个 CUDART库调用和内核调用都会检查上下文是否是当前使用的,如果必要的话,创建CUDA上下文(使用之前用 cudaSetDevice, cudaSetDeviceFlags(),cudaGLSetGLDevice()等函数设置状态)。
许多应用程序倾向于控制初始化延迟时间。为了让 CUDARN进行没有任何负面效果的初始化,调用cudaFree(0)
CUDA运行时应用程序可以通过驱动程序API访问当前上下文栈。
对驱动程序API中的每个指定上下文状态的函数,CUDA运行时把上下文和设备同等对待。
对于驱动程序API函数 cuCtxSynchronize(),CUDA运行时用 cudaDeviceSynchronize()取而代之;对驱动程序API函数 cuCtxSetCacheConfig(),CUDA运行时中则有 cudaDeviceSetCacheConfig()对应。
当前上下文
为了代替当前上下文栈,CUDA运行时提供cudaSetDevice()函数,为调用的线程设置当前上下文。一个设备可以是多个CPU线程的当前上下文。
所有与CUDA上下文相关的分配资源都在上下文被销毁的同时被销毁,除了少数例外,给定CUDA上下文创建的资源可能不能被其他的CUDA上下文使用,这一限制不仅适用于内存,也同样适用于CUDA流与CUDA事件等对象。
CUDA尽力去避免“懒惰分配”( lazy allocation)。懒惰分配只分配需要的资源以避免因缺少资源导致的失败操作。例如,换页内存复制不会因内存不足而失败,因为机器通过分配锁页中转缓冲区以执行换页内存复制,而这个操作发生在上下文创建时。如果CUDA不能分配这些缓冲区,上下文创建就失败了。
在少量情况下,CUDA不会预分配一个给定操作所需的全部资源。内核启动所需的本地内存数量可能被限制,因此CUDA不会预分配最大理论数量的内存。所以,当它需要比默认分配给CUDA上下文的内存值更多的本地内存时,内核启动可能失败。
除了在上下文销毁时被自动销毁的(清理)对象外,另一个上下文中的重要抽象是地址空间:一组私有的虚拟内存地址,它可以分配线性设备内存或用以映射锁页主机内存。这些地址每一个上下文都不相同。同一地址对不同上下文可能有效也可能无效,当然也不会解析到相同的地址空间除非有特殊规定。CUDA上下文的地址空间是与CUDA主机代码使用的CPU地址空间独立的。事实上,不同于共享内存的多CPU,多GPU中的CUDA上下文并不共享一个内存空间。当系统使用统一虚拟地址时,CPU和GPU共享相同的地址空间,其中的每个分配都有进程中唯一的地址。但在特殊情况下,CPU和GPU只能读写各自的内存区,像映射的锁页内存(参见5.1.3小节)或点对点内存(参见9.2.2小节)。
大多数CUDA入口点并不含有上下文参数。取而代之的,它们在CPU线程的“当前上下文”上进行操作,它存储在线程的本地存储句柄里。在驱动程序API中,每一个CPU线程有一个当前上下文栈,创建一个上下文的同时将这个新上下文压入栈。
当前上下文栈有3个主要功能:
为CUDA建立当前上下文的最初动机是使单线程CUDA应用程序可以驱动多个CUDA上下文。在创建并初始化每个CUDA上下文后,程序可以将其中的某个上下文从栈中弹出,使其变成一个飘浮上下文。由于对一个CPU线程,只有一个当前上下文,所以一个单线程CUDA应用程序可以在中依次压入和弹出上下文,使其在任何时间点上,只有一个飘浮上下文,实现对多个上下文的驱动。
在多数的驱动程序架构中,压入和弹出上下文的成本足够廉价,所以一个单线程应用程序可以保持多个GPU同时忙碌。对只在 Windows Vista和其后续版本中的WDDM驱动程序,弹出当前上下文操作只在没有GPU命令挂起的时候才能快速运行。当有命令挂起,驱动程序会引发内核转换,以保证在弹出CUDA上下文之前提交挂起的命令。这一花销不仅仅局限在驱动程序API或当前上下文栈上,当有命令挂起时,调用 cudaSetDevice来换设备同样会引发一次WDDM上的内核转换。