前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >DAY 84:阅读 Driver API和CUDA Context

DAY 84:阅读 Driver API和CUDA Context

作者头像
GPUS Lady
发布2018-11-23 10:33:11
3K0
发布2018-11-23 10:33:11
举报
文章被收录于专栏:GPUS开发者GPUS开发者
我们正带领大家开始阅读英文的《CUDA C Programming Guide》,今天是第84天,我们正在讲解Driver API,希望在接下来的16天里,您可以学习到原汁原味的CUDA,同时能养成英文阅读的习惯。

关注微信公众号,可以看到之前的阅读

本文共计879字,阅读时间30分钟

I. Driver API

This appendix assumes knowledge of the concepts described in CUDA C Runtime.

The driver API is implemented in the cuda dynamic library (cuda.dll or cuda.so) which is copied on the system during the installation of the device driver. All its entry points are prefixed with cu.

It is a handle-based, imperative API: Most objects are referenced by opaque handles that may be specified to functions to manipulate the objects.

The objects available in the driver API are summarized in Table 15.

Table 15. Objects Available in the CUDA Driver API

The driver API must be initialized with cuInit() before any function from the driver API is called. A CUDA context must then be created that is attached to a specific device and made current to the calling host thread as detailed in Context.

Within a CUDA context, kernels are explicitly loaded as PTX or binary objects by the host code as described in Module. Kernels written in C must therefore be compiled separately into PTX or binary objects. Kernels are launched using API entry points as described in Kernel Execution.

Any application that wants to run on future device architectures must load PTX, not binary code. This is because binary code is architecture-specific and therefore incompatible with future architectures, whereas PTX code is compiled to binary code at load time by the device driver.

Here is the host code of the sample from Kernels written using the driver API:

Full code can be found in the vectorAddDrv CUDA sample.

I.1. Context

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. Besides objects such as modules and texture or surface references, each context has its own distinct address space. As a result, CUdeviceptr values from different contexts reference different memory locations.

A host thread may have only one device context current at a time. When a context is created with cuCtxCreate(), it is made current to the calling host thread. CUDA functions that operate in a context (most functions that do not involve device enumeration or context management) will return CUDA_ERROR_INVALID_CONTEXT if a valid context is not current to the thread.

Each host thread has a stack of current contexts. cuCtxCreate() pushes the new context onto the top of the stack. cuCtxPopCurrent() may be called to detach the context from the host thread. The context is then "floating" and may be pushed as the current context for any host thread. cuCtxPopCurrent() also restores the previous current context, if any.

A usage count is also maintained for each context. cuCtxCreate() creates a context with a usage count of 1. cuCtxAttach() increments the usage count and cuCtxDetach() decrements it. A context is destroyed when the usage count goes to 0 when calling cuCtxDetach() or cuCtxDestroy().

Usage count facilitates interoperability between third party authored code operating in the same context. For example, if three libraries are loaded to use the same context, each library would callcuCtxAttach() to increment the usage count and cuCtxDetach() to decrement the usage count when the library is done using the context. For most libraries, it is expected that the application will have created a context before loading or initializing the library; that way, the application can create the context using its own heuristics, and the library simply operates on the context handed to it. Libraries that wish to create their own contexts - unbeknownst to their API clients who may or may not have created contexts of their own - would use cuCtxPushCurrent() andcuCtxPopCurrent() as illustrated in Figure 21.

Figure 21. Library Context Management

本文备注/经验分享:

今天这个章节是关于CUDA Driver API. 和大部分的人经常使用的简化版本的CUDA Runtime API不同,CUDA还有另外一个功能更强大,当然使用起来也更麻烦的API接口。就是今天我们所说的Driver API. Driver API将完整的CUDA功能展现给用户,实际上,我们之前所用到的CUDA Runtime API,只是构建在Driver API上的另外一层包装而已。虽然底层的这个版本功能更加强大,但是更加繁琐的用途限制了它的应用。所以你正常的日常生活中看到的总是runtime api。 而Driver API实际上类似于OpenCL。如果非要将使用难度列表的话,大致的 难度:OpenCL > CUDA (Drvier API) > CUDA (Runtime API) 所以你看,实际上还是比OpenCL容易一些的。 这也是我们为何之前做一些培训的时候,当底下的人闹场,说,“我们今天要听OpenCL”的时候,我们总是建议用户从CUDA Runtime API开始的原因。简单的东西能快速让你用上GPU。需要更强的功能,则建议逐步迁移到更复杂的API上。 好了。那么为何要用Driver API? 既然总是存在一个更易用的Runtime API的情况下? 主要原因有这么3点: (1)Runtime API太“C语言”化了: 特别是它引入的为了方便使用的混合编译(CPU上的C/C++代码和GPU上的CUDA C代码混合在一起编译)。使得宿主的语言几乎总是限定在C/C++上。 有的时候这点是无法忍受的,例如请想想一下一位VB用户需要使用CUDA的时候,难道要直接告诉他,你用不了? 而有了Driver API,任何只要存在和C二进制接口兼容的语言(例如VB,C#,Go,Python,等等。)都可以使用CUDA。 实际上,因为现在几乎所有的语言都提供了到C的二进制接口(即:至少能载入C编译生成的二进制静态或者动态库),所以学习使用Driver API,能将你的CUDA能力扩展到几乎所有平台),实际上,已经存在了很多C#,Go,Python等语言到CUDA的接口了。 (2)你需要更强的控制力。很多平台支持二次开发,以往这些平台或者软件上的二次开发好的代码,只能在CPU上运行。如果直接使用Runtime API的话,首先需要这些二次开发能target CUDA C,同时甚至还可能需要附带上符合CUDA C和CUDA Runtime API的编译器。 有的时候这个很难做到。而Driver API提供了更底层的接口,二次开发后,直接生成一种叫PTX的中间描述代码(纯文本格式的),就可以直接运行了。非常简单。 (3)点则是很多软件为了更好的稳定性,需要使用Driver API。因为Runtime API简化掉了Context管理,虽然说易用了很多,但是也降低了稳定性:试想这样一个场景,当用户调用了多个第三方的库,而该第三方的库使用了GPU代码,则如果这些库是用Runtime API开发的,则一旦任何一个库挂掉,都会影响到其他使用GPU代码的,链接到本应用中的库的。而使用Driver API。因为Driver API具有CUDA Context管理功能,可以隔离在同一个GPU上运行的多个不同的上下文,一定程度能抵抗这种互相的干扰。甚至更重要的,一个雷达控制的GPU应用,安全和稳定性的管理非常重要。为了避免本CPU进程中的一不小心某地方的kernel出错,而导致整体应用挂掉,使用Driver API无可避免(因为NV设计的时候,CUDA估计被设计的很脆弱,一旦runtime api中某步kernel出错,整体默认当前设备将无法继续运行,所有当前数据均将被自动销毁。对高稳定性要求的程序来说,这个是不能忍受的。) 然后既然知道了Drvier API具有这些优点(以及,难用的缺点),用户在下面的阅读中,心里需要有点数。我来根据本章节,简单的描述一下几个重要概念。(注意,本手册中的Driver API部分只是一个简单描述。想深入了解的用户应当充分阅读单独的Driver API手册). CUDA Driver API引入了这个几个东西: (1)Context, Module, Function 这三个东西以前在Runtime API中都不存在。或者说存在,但是全自动的。 Context和Module,这两个对应的是在Runtime API中,第一次调用任何常规Runtime API函数,所引入的那个初始化延迟。 一些Runtime API的用户可能会发现,在第一次调用某些CUDA Runtime函数的时候,会自动的有相当大的初始化开销。实际上这个开销就是Runtime在为你自动进行Context创建,Module载入之类的操作,只是这些操作中Runtime里面是全自动的。如今在Driver API中,它们均必须需要用户手工的建立载入等。但用户也换来了在更方便的实际创建它们的灵活性。各有利弊。 而这里的CUDA Function,实际上在Runtime API也不存在(或者说自动管理的),用户只要书写自己的函数的名字即可,然后用<<<>>>启动。 如何启动对应的这个名字的GPU kernel,在Runtime API上是全自动的。如今,也需要手工管理。 本章节的前面的部分有一段代码,演示了如何使用driver api: 你会看到这样几个有意思的过程:

// Create context

CUcontext cuContext;

cuCtxCreate(&cuContext, 0, cuDevice);

// Create module from binary file

CUmodule cuModule;

cuModuleLoad(&cuModule, "VecAdd.ptx");

这两个过程对来自Runtime API的用户来说,是新鲜的。 第一部分是创建CUDA Context,这个以前是自动的,如今多出来了,需要手写。 第二部分则是载入你的module,什么是module?里面含有了你需要用的静态全局数据,也含有你的GPU Kernel代码。 用户需要手工的从文件,或者加密的网络传输流,或者其他方面,得到GPU上的代码,并将它载入到GPU中。 以前这些过程也不存在:你之前是GPU代码自动嵌入在你的exe或者可执行文件中,不需要手工载入的。如今也需要手工载入了。而且这里还需要有明确的PTX和CUBIN之分(这个下次说)。 虽然很麻烦,但也换来了好处。 例如一些需要很好的加密的软件,可以将自己的GPU部分代码(kernel代码),放置到一个授权服务器,或者需要登录的服务器上,只有当有正确的用户名密码或者权限后,实际的GPU kernel,才会自动的从服务器上传输过来(通过加密的网络流例如),然后再会被加载到GPU中,然后才会可能执行。 这种比直接GPU Kernel嵌入在exe中之类的有时候要更好更安全。例如用户购买了3个月的授权,第4个月开始,服务器可以拒绝提供GPU Kernel。 类似这样的灵活性都是以前的Runtime API所没有的。 再往下看这些代码。这里一段也很有意思:

这段只相当于以前的Runtime API的一句话: VecAdd<<<>>>() 但是这里要明确的获取kernel的指针,然后通过指针来启动kernel。请注意这个虽然繁琐了很多,但已经是CUDA Driver API经过简化后的版本了,在2010年的时候,CUDA Driver API(在CUDA 3.2的时候)进行了一次化简,引入了一些不太兼容的改动,你今天看到的这样麻烦的Driver API代码,已经是简单好多的了。但是有失就有得,现在用户可以方便的将kernel指针在自己的代码中进行传递,甚至对kernel的签名进行描述,进行很多灵活的多的调用方式的。 还是很方便的。 这是今天的章节的综合描述部分。 用户可以看到,以前的最简单的代码,现在都需要用户自己来。 但这种操作却可能带来灵活性的多的应用领域。然后具体的这里面需要用户手工操作的概念,我们需要分成好几天来说明。

今天会说明一个基本的概念:CUDA Context,也就是本章节的后半部分。 首先说,这里引入了一个“用户不透明类型”的概念。这个概念相当于Win32 API中的句柄(HANDLE),或者用户可以直接理解成void *,也就是说用户不需要知道维护一个(例如CUDA Context)的类型具体是什么东西,只需要知道它存在即可。 例如CUDA Context只是一个CUcontext,具体的context是什么结构的,用户不需要知道。只要会用即可。(NV故意不告诉你) 回到正题,你看到现在很多类型是用CU或者很多函数是小写cu开头的,这是driver api的一个特点: 所有的RUNTIME API,都是4个字母cuda开头;而所有的Driver API,则都是2个字母cu开头。这样用户可以快速区分到自己在用什么(特别是有一些技巧允许你混用driver和runtime api的时候),至于以前用户天天问,cutil开头的是什么?这些以前我们的NV上海公司写的例子时候所用的开头,并非正式API! 请不要使用它们。 同时这些代码也总是容易引发一些问题,好在现在的CUDA附赠的例子中,含有这些开头的代码已经被删除了。你如果在维护老代码的时候看到它们,请尽量去除它们。这不是正式的API! 然后回到CUDA Context上。这里上去说明,这等价于CPU上的进程。这种解释其实让很多用户感到迷惑。因为并不是很多人知道一个基本操作系统概念:进程是资源分配的单位,线程是调度执行的单位的。所以这里我明确的说一下,CUDA Context是一种从一张具体的GPU上,切分下来的一部分资源。 在同一个GPU上,可能同时存在1个或者多个CUDA Context的。每个Context之间是隔离的,在一个Context中有效的东西(例如某个指针,指向一段显存;或者某个纹理对象),只能在这一个Context中使用。它在另外一个Context上(哪怕是同一个GPU的另外一个Context上)是不能用的。这种是CUDA Context之间的隔离性。好处是一个Context挂掉后,不会影响另外一个Context里的东西。因为实际上我们总是在使用多卡,现在的日子。 实际上一个应用中执行的过程它,如果是在多卡平台上,它(使用了Driver API后)可能会创建多个CUDA Context的,有N张GPU上,每张GPU只有1个Context的情况;也有1张GPU上,存在N个CUDA Context的情况;还会有N张GPU上,存在M个CUDA Context的情况。这些Context上,正常情况下,资源都是互相隔离的。(什么时候是可以互相共享的,后面再说) 然后这段里还提出了一个隐藏参数的概念,还记得刚才我说过,CUDA Driver API虽然要比CUDA Runtime API简单,但依然要比OpenCL复杂的话吗? 很多时候,很多函数操作需要一个Context对象,为了简化用户每次调用的时候,指定一个context参数的麻烦,

CUDA Driver API提出了一个当前context的概念,这个当前context,不需要每次调用的时候指定,只需要设定好,就可以连续使用。方便了很多。来自Runtime API用户一定程度的可以将它等效成cudaSetDevice, 注意这是一个cuda开头的runtime api函数,(但是存在1张卡上多个Context的例外情况,这里只是一种大致等效)。 这种设定好了就能用的方式,简化了不少调用的麻烦。但也带来了这个隐藏的概念的开销。用户需要知道如何设定context为当前context,以及设定后对具体某个cpu线程有效。本段落里简单的提到了有Set/Get方式设定,Push/Pop方式设定。前者适合你有明确的对象的时候使用(例如都是自己的代码),后者往往适合对象指向不明的时候(例如一个库代码,可以直接pop掉自己的context,返回调用者的CUDA Context)使用。但这不是绝对的。 注意本段落的这些只是让你有一些概念性的东西,具体它们怎么用,这里没说,你需要参考随着CUDA自带的Driver API手册去看它们,Driver API的Context章节。这章节大约不到20页吧。 也可以参考自带的例子中,含有drv字样的代码去看看。这些都是入门的第一手资料(没错。你看到的其他除了NV为你提供的手册和例子外的东西,都是别人根据前面这两个给你总结出来的二手货),然后这里还有一个引用计数的概念,这个东西大家可能都比较熟悉,可以直接看一下本章节的描述。 此外,本章节没说,但需要强调的是,一个CUDA Context中的任何一个kernel,挂掉后,则整个Context中的所有东西都会失效(例如所有的缓冲区,kernel对象,纹理对象,stream等等),这点需要注意。 高度可靠性的东西应当使用CUDA Context隔离开(也就是建立多个Context,一个挂了另外一个不影响),甚至有的时候需要使用多卡,在另外一张卡上建立Context,以便将可能的影响降低到最低。大致这样。更多内容我们后续继续说。 需要对用户说明的是,因为CUDA Driver API是先有的(2008甚至更早就有了),而OpenCL是后起的,所以你看到OpenCL和Driver API很多地方这么像,并非前者抄袭后者,很多人对我们说,CUDA Driver API抄袭OCL,这很让人啼笑皆非。(类似的说法还有BSD是抄袭自Linux的) 请注意出现的前后时间关系。但无论是否抄袭,这种相似性,带来了用户迁移的成本降低。如果一家单位的代码,必须从CUDA迁移到OpenCL。 我建议走这样的一个渐进的流程: 从CUDA Runtime API -> Driver API -> OCL 这样一定程度的能带来成本的降低。实际上,在学习完Driver API后,你基本上OCL也会了,太多的东西是类似的。

有不明白的地方,请在本文后留言

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-10-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 GPUS开发者 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • I. Driver API
    • I.1. Context
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档