如何实现CUDA驱动程序API库的句柄?

Mar*_*o13 15 cuda api-design jcuda

注意:问题已更新,以解决评论中提出的问题,并强调问题的核心是运行时和驱动程序API之间的相互依赖性

CUDA运行时库(如CUBLAS或CUFFT)通常使用"句柄"的概念来概括这种库的状态和上下文.使用模式非常简单:

// Create a handle
cublasHandle_t handle;
cublasCreate(&handle);

// Call some functions, always passing in the handle as the first argument
cublasSscal(handle, ...);

// When done, destroy the handle
cublasDestroy(handle);
Run Code Online (Sandbox Code Playgroud)

但是,有许多关于这些句柄如何与Driver-和Runtime上下文以及多个线程和设备进行互操作的细微细节.该文档列出了有关上下文处理的若干分散细节:

然而,一些信息似乎并不完全是最新的(例如,我认为应该使用cuCtxSetCurrent而不是cuCtxPushCurrentcuCtxPopCurrent?),其中一些似乎是在"主要上下文"处理通过驱动程序API公开之前的一段时间,有些部分过于简单,因为它们只显示最简单的使用模式,只制作关于多线程的模糊或不完整的语句,或者不能应用于运行时库中使用的"句柄"的概念.


我的目标是实现一个提供自己的"句柄"类型的运行时库,并允许在上下文处理和线程安全方面与其他运行时库等效的使用模式.

对于只能使用Runtime API在内部实现库的情况,事情可能很清楚:上下文管理完全由用户负责.如果他创建自己的驱动程序上下文,则将适用有关运行时和驱动程序上下文管理文档中陈述的规则.否则,Runtime API函数将负责主要上下文的处理.

但是,可能存在库在内部必须使用Driver API的情况.例如,为了将PTX文件作为CUmodule对象加载,并从中获取CUfunction对象.当库 - 应该 - 对于用户 - 表现得像运行时库,但内部必须使用驱动程序 API时,会出现一些关于如何在"引擎盖下"实现上下文处理的问题.

到目前为止我想到的是在这里勾勒出来的.

(它是"伪代码",因为它省略了错误检查和其他细节,并且...所有这些应该用Java实现,但这不应该在这里相关)

1. "句柄"基本上是一个包含以下信息的类/结构:

class Handle 
{
    CUcontext context;
    boolean usingPrimaryContext;
    CUdevice device;
}
Run Code Online (Sandbox Code Playgroud)

2.创建时,必须涵盖两种情况:当驱动程序上下文对于调用线程是最新的时,可以创建它.在这种情况下,它应该使用此上下文.否则,它应该使用当前(运行时)设备的主要上下文:

Handle createHandle()
{
    cuInit(0);

    // Obtain the current context
    CUcontext context;
    cuCtxGetCurrent(&context);

    CUdevice device;

    // If there is no context, use the primary context
    boolean usingPrimaryContext = false;
    if (context == nullptr)
    {
        usingPrimaryContext = true;

        // Obtain the device that is currently selected via the runtime API
        int deviceIndex;
        cudaGetDevice(&deviceIndex);

        // Obtain the device and its primary context
        cuDeviceGet(&device, deviceIndex);
        cuDevicePrimaryCtxRetain(&context, device));
        cuCtxSetCurrent(context);
    }
    else
    {
        cuCtxGetDevice(device);
    }

    // Create the actual handle. This might internally allocate
    // memory or do other things that are specific for the context
    // for which the handle is created
    Handle handle = new Handle(device, context, usingPrimaryContext);
    return handle;
}
Run Code Online (Sandbox Code Playgroud)

3.在调用库的内核时,关联句柄的上下文对于调用线程是最新的:

void someLibraryFunction(Handle handle)
{
    cuCtxSetCurrent(handle.context);
    callMyKernel(...);
}
Run Code Online (Sandbox Code Playgroud)

在这里,人们可以争辩说调用者负责确保所需的上下文是最新的.但是,如果为主要上下文创建了句柄,则此上下文将自动变为当前上下文.

4.当句柄被销毁时,这意味着cuDevicePrimaryCtxRelease必须被调用,但当上下文是主要上下文时:

void destroyHandle(Handle handle)
{
    if (handle.usingPrimaryContext)
    {
        cuDevicePrimaryCtxRelease(handle.device);
    }
}
Run Code Online (Sandbox Code Playgroud)

从我到目前为止的实验来看,这似乎暴露了与CUBLAS句柄相同的行为.但我彻底测试这种情况的可能性是有限的,因为我只有一个设备,因此无法测试关键情况,例如有两个上下文,两个设备各一个.

所以我的问题是:

  • 是否有任何已建立的模式来实施这样的"处理"?
  • 是否存在任何使用模式(例如,具有多个设备和每个设备一个上下文),这些模式无法用上面概述的方法覆盖,但是将被CUBLAS的"句柄"实现覆盖?
  • 更一般地说:有没有关于如何改进当前"处理"实施的建议?
  • 修辞:CUBLAS句柄处理的源代码是否可用?

(我还看了一下tensorflow中的上下文处理,但我不确定是否可以从中获取有关如何为运行时库实现句柄的建议...)

(这里删除了"更新",因为它是为了回应评论而添加的,并且不再相关)

ein*_*ica 1

很抱歉我没有早点注意到这个问题 - 因为我们可能在这方面进行了一些合作。另外,我不太清楚这个问题是否属于这里,在 codereview.SX 还是在programmers.SX,但让我们忽略所有这些。

我现在已经完成了您想要做的事情,而且可能更普遍。因此,我可以提供一个如何处理“句柄”的示例,而且还提出了根本不必实现此操作的前景。

该库是cuda-api-wrappers的扩展,还涵盖了驱动程序 API 和 NVRTC;它还不是发布级,但在这个分支上处于测试阶段。

现在,回答您的具体问题:

围绕原始“句柄”编写类的模式

是否有任何既定的模式来实现这样的“句柄”?

是的。如果您阅读:

句柄、指针和引用之间有什么区别

您会注意到句柄被定义为“对对象的不透明引用”。它与指针有一些相似之处。因此,一个相关的模式是PIMPL 习惯用法的变体:在常规 PIMPL 中,您编写一个实现类,并且面向外的类仅保存指向实现类的指针并将方法调用转发给它。当您在某些第三方库或驱动程序中拥有不透明对象的不透明句柄时,您可以使用该句柄将方法调用转发到该实现。

这意味着,您的面向外的类不是句柄,它代表您拥有句柄的对象。

通用性和灵活性

是否有任何使用模式(例如,多个设备和每个设备一个上下文)无法用上面概述的方法覆盖,但可以用 CUBLAS 的“句柄”实现覆盖?

我不确定 CUBLAS 到底在幕后做什么(说实话,我几乎从未使用过 CUBLAS),但如果它设计良好并实现良好,它将创建自己的上下文,并尽量不影响你的代码的其余部分,即它总是会这样做:

  1. 将我们的 CUBLAS 上下文推到堆栈顶部
  2. 做实事
  3. 弹出上下文堆栈的顶部。

你们班不这样做。

更一般地说:对于如何改进当前的“Handle”实施有什么建议吗?

是的:

  • 只要有可能且相关,就使用 RAII。如果您的创建代码分配了资源(例如通过 CUDA 驱动程序) - 您返回的对象的析构函数应该安全地释放这些资源。
  • 允许句柄的引用类型和值类型使用,即它可能是我创建的句柄,但它也可能是我从其他地方获得的句柄,这不是我的责任。如果你让用户来释放资源,这很简单,但如果你承担这个责任,那就有点棘手了
  • 您假设如果存在任何当前上下文,则这就是您的句柄需要使用的上下文。谁说的?至少,如果用户愿意,可以让他们传递上下文。
  • 除非确实必要,否则请避免自己编写低级部分。您很可能会错过一些事情(推送和弹出并不是您可能错过的唯一事情),并且您正在重复许多实际上是通用的工作,而不是特定于您的应用程序或库。我在这里可能有偏见,但你现在可以使用漂亮的、RAII-ish 的 CUDA 上下文、流、模块、设备等包装器,甚至不需要知道任何东西的原始句柄。

反问:CUBLAS 句柄处理的源代码在某处可用吗?

据我所知,NVIDIA 尚未发布它。