CUDA运行时 Runtime(四)
一. 图
图为CUDA中的工作提交提供了一种新的模型。图是一系列操作,如内核启动,由依赖项连接,依赖项与执行分开定义。这允许定义一次图形,然后重复启动。将图的定义与其执行分离可以实现许多优化:第一,与流相比,CPU启动成本降低,因为大部分设置是提前完成的;第二,将整个工作流呈现给CUDA可以实现优化,而流的分段工作提交机制可能无法实现优化。
要查看图形可能的优化,请考虑流中发生的情况:将内核放入流中时,主机驱动程序执行一系列操作,以准备在GPU上执行内核。这些操作是设置和启动内核所必需的,它们是一种开销,必须为发布的每个内核支付。对于执行时间较短的GPU内核,这种开销可能是整个端到端执行时间的一个重要部分。
使用图的工作提交分为三个不同的阶段:定义、实例化和执行。
在定义阶段,程序将创建对图形中的操作及其依赖关系的描述。
实例化获取图形模板的快照,对其进行验证,并执行大部分设置和初始化工作,以最小化启动时需要执行的操作。结果实例称为可执行图。
一个可执行图可以被发送到一个流中,类似于任何其他CUDA工作。它可以在不重复实例化的情况下启动任意次数。
二. 图形结构
操作在图中形成一个节点。操作之间的依赖关系是边。这些依赖关系约束操作的执行顺序。
一旦操作所依赖的节点完成,就可以随时调度该操作。日程安排由CUDA系统决定。
三. 节点类型
图形节点可以是:
内核
CPU函数调用
内存复制
清零
空节点
子图:执行单独的嵌套图。见图11。
图11. 子图示例
四. 使用图形api创建图形
图形可以通过两种机制创建:显式API和流捕获。下面是创建和执行下图的示例。
图12. 用图形api创建图形示例
// Create the graph - it starts out empty
cudaGraphCreate(&graph, 0);
// For the purpose of this example, we'll create // the nodes separately from the dependencies to
// demonstrate that it can be done in two stages.
// Note that dependencies can also be specified
// at node creation.
cudaGraphAddKernelNode(&a, graph, NULL, 0, &nodeParams);
cudaGraphAddKernelNode(&b, graph, NULL, 0, &nodeParams);
cudaGraphAddKernelNode(&c, graph, NULL, 0, &nodeParams);
cudaGraphAddKernelNode(&d, graph, NULL, 0, &nodeParams);
// Now set up dependencies on each node
cudaGraphAddDependencies(graph, &a, &b, 1); //A->B
cudaGraphAddDependencies(graph, &a, &c, 1); //A->C
cudaGraphAddDependencies(graph, &b, &d, 1); //B->D
cudaGraphAddDependencies(graph, &c, &d, 1); //C->D
五. 使用流捕获创建图
流捕获提供了一种从现有的基于流的api创建图的机制。将工作启动到流(包括现有代码)中的一段代码可以用对cudaStreamBeginCapture()和cudastreamndcapture()的调用括起来。见下文。
cudaGraph_t graph; cudaStreamBeginCapture(stream);
kernel_A<<< ..., stream >>>(...);
kernel_B<<< ..., stream >>>(...);
libraryCall(stream); kernel_C<<< ..., stream >>>(...);
cudaStreamEndCapture(stream, &graph);
调用cudaStreamBeginCapture()会将流置于捕获模式。捕获流时,启动到流中的工作不会排队执行。它被附加到一个正在逐步建立的内部图中。然后通过调用cudastreamndcapture()返回此图,该函数也结束流的捕获模式。由流捕获主动构造的图称为捕获图。
流捕获可用于除cudaStreamLegacy以外的任何CUDA流(“空流”)。注意,它可以用于cudaStreamPerThread。如果程序正在使用遗留流,则可以将流0重新定义为每个线程的流,而无需更改函数。请参见默认流。
可以使用cudaStreamIsCapturing()查询是否正在捕获流。
六. 跨流依赖项和事件
流捕获可以处理用cudaEventRecord()和cudaStreamWaitEvent()表示的跨流依赖关系,前提是等待的事件被记录到同一个捕获图中。
当事件记录在处于捕获模式的流中时,它将导致捕获的事件。捕获的事件表示捕获图中的一组节点。
当捕获的事件被流等待时,如果流尚未处于捕获模式,则它会将该流置于捕获模式,流中的下一项将对捕获的事件中的节点具有额外的依赖关系。然后将这两个流捕获到同一个捕获图。
当流捕获中存在跨流依赖项时,仍必须在调用cudaStreamBeginCapture()的同一流中调用cudastreamndcapture();这是源流。由于基于事件的依赖关系,被捕获到同一捕获图的任何其他流也必须连接回原始流。如下所示。在cudaStreamEndCapture()上,所有捕获到同一捕获图的流都将退出捕获模式。未能重新加入原始流将导致整个捕获操作失败。
// stream1 is the origin stream
cudaStreamBeginCapture(stream1);
kernel_A<<< ..., stream1 >>>(...);
// Fork into stream2
cudaEventRecord(event1, stream1);
cudaStreamWaitEvent(stream2, event1); kernel_B<<< ..., stream1 >>>(...);
kernel_C<<< ..., stream2 >>>(...);
// Join stream2 back to origin stream (stream1)
cudaEventRecord(event2, stream2);
cudaStreamWaitEvent(stream1, event2);
kernel_D<<< ..., stream1 >>>(...);
// End capture in the origin stream
cudaStreamEndCapture(stream1, &graph);
// stream1 and stream2 no longer in capture mode
上述代码返回的图如图12所示。
注意:当流退出捕获模式时,流中的下一个未捕获项(如果有)仍将依赖于最新的先前未捕获项,尽管中间项已被移除。
七. 禁止和未处理的操作
同步或查询正在捕获的流或捕获的事件的执行状态是无效的,因为它们不表示计划执行的项。当任何关联的流处于捕获模式时,查询或同步包含活动流捕获(例如设备或上下文句柄)的更宽句柄的执行状态也是无效的。
当捕获同一上下文中的任何流时,并且该流不是使用cudaStreamNonBlocking创建的,则尝试使用遗留流的任何操作都是无效的。这是因为遗留流句柄始终包含这些其他流;加入遗留流队列将创建对正在捕获的流的依赖关系,查询或同步它将查询或同步正在捕获的流。
因此,在这种情况下调用同步api也是无效的。同步api,例如cudammcpy(),在返回之前将队列工作到遗留流并同步它。
注意:一般情况下,当依赖关系将被捕获的内容与未被捕获的内容连接起来并排队等待执行时,CUDA宁愿返回错误,而不是忽略依赖关系。将流置于捕获模式或置于捕获模式之外时会发生异常;这会切断在模式转换之前和之后添加到流中的项之间的依赖关系。
通过等待从正在捕获的流中捕获的事件来合并两个单独的捕获图是无效的,该流与事件之外的另一个捕获图相关联。等待正在捕获的流中的未捕获事件是无效的。
图中当前不支持将异步操作排队到流中的少数API,如果使用正在捕获的流(如cudastreamattachemasync())调用这些API,则会返回错误。
八. 无效
在流捕获期间尝试无效操作时,任何关联的捕获图都将无效。当捕获图失效时,进一步使用正在捕获的任何流或与该图相关联的已捕获事件是无效的,并且将返回错误,直到流捕获以cudastreamndcapture()结束。此调用将使关联的流退出捕获模式,但也将返回一个错误值和一个空图。
九. 使用图形API
CudaGraph_t对象不是线程安全的。用户有责任确保多个线程不会同时访问同一个cudaGraph。
cudaGraphExec不能与自身同时运行。cudaGraphExec_t的启动将在以前启动同一个可执行图形之后进行。
图的执行是在流中完成的,以便与其他异步工作一起排序。但是,流仅用于排序;它不约束图的内部并行性,也不影响图节点的执行位置。
请参见图形API。
十. 事件
运行时还提供了一种方法,通过让应用程序在程序中的任意点异步记录事件并查询这些事件何时完成,可以密切监视设备的进度,并执行准确的计时。当事件之前的所有任务(或者可选地,给定流中的所有命令)都已完成时,事件即已完成。流0中的事件在所有流中的所有先前任务和命令完成后完成。
十一. 创造与销毁
下面的代码示例创建两个事件:
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
它们是这样被销毁的:
cudaEventDestroy(start);
cudaEventDestroy(stop);
十二. 经过的时间
在创建和销毁中创建的事件可用于按以下方式计时创建和销毁的代码示例:
cudaEventRecord(start, 0);
for (int i = 0; i < 2; ++i)
{
cudaMemcpyAsync(inputDev + i * size, inputHost + i * size, size, cudaMemcpyHostToDevice, stream[i]);
MyKernel<<<100, 512, 0, stream[i]>>> (outputDev + i * size, inputDev + i * size, size);
cudaMemcpyAsync(outputHost + i * size, outputDev + i * size, size, cudaMemcpyDeviceToHost, stream[i]);
}
cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);
float elapsedTime;
cudaEventElapsedTime(&elapsedTime, start, stop);
十三. 同步调用
调用同步函数时,在设备完成请求的任务之前,不会将控件返回到主机线程。在主机线程执行任何其他CUDA调用之前,可以通过使用某些特定标志(有关详细信息,请参阅参考手册)调用cudaSetDeviceFlags()来指定主机线程是否会产生、阻塞或旋转。
十四. 多设备系统
十六. 设备标识
主机系统可以有多个设备。下面的代码示例演示如何枚举这些设备、查询它们的属性以及确定启用CUDA的设备的数量。
int deviceCount;
cudaGetDeviceCount(&deviceCount);
int device;
for (device = 0; device < deviceCount; ++device)
{
cudaDeviceProp deviceProp;
cudaGetDeviceProperties(&deviceProp, device);
printf("Device %d has compute capability %d.%d.\n", device, deviceProp.major, deviceProp.minor);
}
十七 同步调用
十八. 设备选择
主机线程可以通过调用cudaSetDevice()随时设置其操作的设备。在当前设置的设备上进行设备内存分配和内核启动;流和事件与当前设置的设备关联创建。如果未调用cudastedevice(),则当前设备为设备0。
下面的代码示例演示了设置当前设备如何影响内存分配和内核执行。
十九. 多设备系统
二十. 设备标识
主机系统可以有多个设备。下面的代码示例演示如何枚举这些设备、查询它们的属性以及确定启用CUDA的设备的数量。
size_t size = 1024 * sizeof(float);
cudaSetDevice(0);
// Set device 0 as current
float* p0;
cudaMalloc(&p0, size);
// Allocate memory on device 0
MyKernel<<<1000, 128>>>(p0);
// Launch kernel on device 0
cudaSetDevice(1);
// Set device 1 as current
float* p1;
cudaMalloc(&p1, size);
// Allocate memory on device 1
MyKernel<<<1000, 128>>>(p1);
// Launch kernel on device 1
二十一. 流和事件行为
如果将内核发送到与当前设备无关的流,则内核启动将失败,如下面的代码示例所示。
cudaSetDevice(0);
// Set device 0 as current
cudaStream_t s0;
cudaStreamCreate(&s0);
// Create stream s0 on device 0
MyKernel<<<100, 64, 0, s0>>>();
// Launch kernel on device 0 in s0
cudaSetDevice(1);
// Set device 1 as current
cudaStream_t s1; cudaStreamCreate(&s1);
// Create stream s1 on device 1
MyKernel<<<100, 64, 0, s1>>>();
// Launch kernel on device 1 in s1
// This kernel launch will fail:
MyKernel<<<100, 64, 0, s0>>>();
// Launch kernel on device 1 in s0
即使将内存副本发送到与当前设备无关的流,它也会成功。
如果输入事件和输入流与不同的设备关联,则cudaEventRecord()将失败。
如果两个输入事件关联到不同的设备,则cudaEventLapsedTime()将失败。
即使输入事件与不同于当前设备的设备关联,cudaEventSynchronize()和cudaEventQuery()也将成功。
即使输入流和输入事件关联到不同的设备,cudaStreamWaitEvent()也将成功。因此,可以使用cudaStreamWaitEvent()来同步多个设备。
每个设备都有自己的默认流(请参阅默认流),因此,向设备的默认流发出的命令可能会无序执行,或者与向任何其他设备的默认流发出的命令同时执行。
二十二. 对等内存访问
根据系统属性,特别是PCIe和/或NVLINK拓扑,设备能够寻址彼此的存储器(即,在一个设备上执行的内核可以解除对另一个设备存储器的指针的引用)。如果这两个设备的cudaDeviceCanAccessPeer()返回true,则在两个设备之间支持此对等内存访问功能。
对等内存访问仅在64位应用程序中受支持,必须通过调用cudaDeviceEnablePeerAccess()在两个设备之间启用,如下面的代码示例所示。在非NVSwitch启用的系统上,每个设备最多可支持8个系统范围的对等连接。
两个设备都使用统一的地址空间(请参阅统一虚拟地址空间),因此可以使用同一个指针对两个设备的内存进行寻址,如下面的代码示例所示。
cudaSetDevice(0);
// Set device 0 as current
float* p0;
size_t size = 1024 * sizeof(float);
cudaMalloc(&p0, size);
// Allocate memory on device 0
MyKernel<<<1000, 128>>>(p0);
// Launch kernel on device 0
cudaSetDevice(1);
// Set device 1 as current
cudaDeviceEnablePeerAccess(0, 0);
// Enable peer-to-peer access
// with device 0
// Launch kernel on device 1
// This kernel launch can access memory on device 0 at address p0
MyKernel<<<1000, 128>>>(p0);
二十三. Linux上的IOMMU
仅在Linux上,CUDA和显示驱动程序不支持启用IOMMU的裸机PCIe对等内存复制。但是,CUDA和显示驱动程序确实通过虚拟机传递支持IOMMU。因此,Linux上的用户在本机裸机系统上运行时,应该禁用IOMMU。应启用IOMMU,并将VFIO驱动程序用作虚拟机的PCIe直通。
在Windows上不存在上述限制。
另请参阅在64位平台上分配DMA缓冲区。
CUDA运行时 Runtime(四)的更多相关文章
- CUDA运行时 Runtime(一)
CUDA运行时 Runtime(一) 一. 概述 运行时在cudart库中实现,该库通过静态方式链接到应用程序库cudart.lib和libcudart.a,或动态通过cuda ...
- CUDA运行时 Runtime(三)
CUDA运行时 Runtime(三) 一.异步并发执行 CUDA将以下操作公开为可以彼此并发操作的独立任务: 主机计算: 设备计算: 从主机到设备的内存传输: 从设备到主机的存储器传输: 在给定设备的 ...
- CUDA运行时 Runtime(二)
CUDA运行时 Runtime(二) 一. 概述 下面的代码示例是利用共享内存的矩阵乘法的实现.在这个实现中,每个线程块负责计算C的一个方子矩阵C sub,块内的每个线程负责计算Csub的一个元素.如 ...
- iOS运行时Runtime浅析
运行时是iOS中一个很重要的概念,iOS运行过程中都会被转化为runtime的C代码执行.例如[target doSomething];会被转化成objc)msgSend(target,@select ...
- Deep Learning部署TVM Golang运行时Runtime
Deep Learning部署TVM Golang运行时Runtime 介绍 TVM是一个开放式深度学习编译器堆栈,用于编译从不同框架到CPU,GPU或专用加速器的各种深度学习模型.TVM支持来自Te ...
- “ compiler-rt”运行时runtime库
" compiler-rt"运行时runtime库 编译器-rt项目包括: Builtins-一个简单的库,提供了代码生成和其他运行时runtime组件所需的特定于目标的低级接口. ...
- 【原】iOS动态性(五)一种可复用且解耦的用户统计实现(运行时Runtime)
声明:本文是本人 编程小翁 原创,转载请注明. 为了达到更好的阅读效果,强烈建议跳转到这里查看文章. iOS动态性是我的关于iOS运行时的系列文章,由浅入深,从理论到实践.本文是第5篇.有兴趣可以看看 ...
- iOS 运行时runtime控制私有变量以及私有方法
OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法.利用runtime机制让我们可以在程序运行时动态修改类.对象中的所有属性.方法,就算是私有方法以及私有属性都是可以动 ...
- 【原】iOS动态性(二):运行时runtime初探(强制获取并修改私有变量,强制增加及修改私有方法等)
OC是运行时语言,只有在程序运行时,才会去确定对象的类型,并调用类与对象相应的方法.利用runtime机制让我们可以在程序运行时动态修改类.对象中的所有属性.方法,就算是私有方法以及私有属性都是可以动 ...
随机推荐
- reset 去掉margin和padding的 默认代码,其余根据自己的情况做调整
body, dl, dd, h2, h3, h4, h5, h6, p, form{margin:0;} ol,li,ul{margin:0; padding:0;} h1{margin:10px 0 ...
- HBASE-使用问题-split region
问题描述: HBASE表的管理以REGION分区为核心,通常面临如下几个问题: 1) 数据如何存储到指定的region分区,即rowkey设计,region splitkey设计 2)设计的split ...
- 7 IDEA连接数据库
IDEA连接数据库 连接成功后,选择数据库 查看数据库/表的内容就双击数据库 修改数据库--要点击DB才能保存 出现问题 错误描述 Server returns invalid timezone. G ...
- 【Redis】redis异步消息队列+Spring自定义注解+AOP方式实现系统日志持久化
说明: SSM项目中的每一个请求都需要进行日志记录操作.一般操作做的思路是:使用springAOP思想,对指定的方法进行拦截.拼装日志信息实体,然后持久化到数据库中.可是仔细想一下会发现:每次的客户端 ...
- 【opencv】VideoCapture打不开本地视频文件或者网络IP摄像头
1.前提:成功打开本地USB摄像头 // 创建VideoCapture对象 VideoCapture vc = new VideoCapture(); // 可以成功打开本地USB摄像头 // 参数可 ...
- springboot添加操作
更多精彩关注微信公众号 Mybaits技术连接数据库 resources #update tomcat port server.port=8888 #config datasource(mysql) ...
- 使用C#操作注册表
这节讲一下使用C#操作注册表. 首先来了解一下,什么是注册表,注册表是Windows中特有的一个东西,百度百科中对其解释如下:Windows注册表(Registry)实质上是一个庞大的数据库,它存储着 ...
- c语言常见编译问题
1 . warn.c:6:2: warning: implicit declaration of function 'strcpy' [-Wimplicit-function-declaration] ...
- 5.配置IP
静态IP配置 1.NAT模式设置 首先设置虚拟机中NAT模式的选项,打开VMware,点击"编辑"下的"虚拟网络编辑器",设置NAT参数 注意: VMware ...
- istio部署问题Q&A
端口绑定无权限 创建Gateway,提示绑定端口无权限. 2020-12-27T12:25:30.974288Z warning envoy config gRPC config for type.g ...