【CUDA】Stream and Event

目录

一、stream

1.1 认识stream

1.2 stream调度

1.2.1 伪依赖

1.2.2 Hyper-Q

1.2.3 stream优先级

1.3 使用示例

1.4 流同步

1.4.1 阻塞与非阻塞stream

1.4.2 隐式同步

1.4.3 显示同步

二、Event

2.1 创造与销毁

2.2 记录事件和测量经过的时间

2.3 可配置Events


一、stream

一般而言,cuda并行性表现在下面两个层面上:

  • Kernel level
  • Grid level

kernel level,即一个 kernel 或者一个 task 由许多 thread 并行的执行在GPU上。Stream的概念是相对于后者来说的,Grid level是指多个 kernel 在一个 device 上同时执行

1.1 认识stream

流是指一系列指令,且 CUDA 具有默认流。默认情况下,CUDA 核函数会在默认流中运行

在一个流中排队的所有命令都必须在该流中的下一个命令开始执行之前完成(或者至少达到一个可以安全执行下一个命令的状态)。CUDA流提供了一种方式来组织命令的执行顺序,确保一个流中的命令按顺序执行,而不同的流可以并发执行,但每个流内部保持顺序性

不同的非默认流中的核函数可并发执行。 默认流较为特殊,其执行任何操作期间,任何非默认流中皆不可同时执行任何操作,默认流将等待非默认流全部执行完毕后再开始运行,在其执行完毕后,其他非默认流才能开始执行

异步且基于 stream 的 kernel 执行和数据传输能够实现以下几种类型的并行:

  • Host 运算操作和 device 运算操作并行
  • Host 运算操作和 host 到 device 的数据传输并行
  • Host 到 device 的数据传输和 device 运算操作并行
  • device 内的运算并行

下面代码是之前常见的使用形式,使用默认 stream: 

cudaMemcpy(..., cudaMemcpyHostToDevice);
kernel<<<grid, block>>>(...);
cudaDeviceSynchronize();
// ... ... host计算
cudaMemcpy(..., cudaMemcpyDeviceToHost);

从 device 角度看,所有者三个操作都是使用的默认stream,并且按照代码从上到下的顺序依次执行,device 本身是不知道其他的 host 操作怎样执行的

从 host 角度来看,数据传输都是同步的并且会一直等待,直到操作完成。不过不同于数据传输,kernel 的 launch 是异步的,host 立刻就能重新得到控制权,不用管 kernel 是否执行完毕,从而进行下一步动作。很明显,这种异步行为有助于重叠device和host之间的运算时间

数据传输也是可以异步执行的,使用时必须显示的声明一个 stream 来分派执行

cudaError_t cudaMemcpyAsync(void* dst, const void* src, size_t count,cudaMemcpyKind kind, cudaStream_t stream = 0);

注意新增加的最后一个参数。在 host issue 了这个函数给 device 执行后,控制权可以立刻返还给 host。上面代码使用了默认 stream,若要创建一个新的 stream 则使用下面的API:

cudaError_t cudaStreamCreate(cudaStream_t* pStream);

注意:使用该函数的一个比较常见的错误,或者说容易引起混乱的地方是,这个函数返回的 error code 可能是上一次调用异步函数产生的

当执行一次异步数据传输时,必须使用pinned(或者non-pageable)memory

cudaError_t cudaMallocHost(void **ptr, size_t size);
cudaError_t cudaHostAlloc(void **pHost, size_t size, unsigned int flags);

通过在将该内存 pin 到 host 的虚拟内存上,就可以将该 memory 的物理位置强制分配到CPU内存中以便使之在整个程序生命周期中保持不变。否则的话,操作系统可能会在任意时刻改变该 host 端的虚拟内存对应的物理地址

若异步数据传输函数没有使用 pinned host memory,操作系统就可能将数据从一块物理空间移动到另一块物理空间(因为是异步的,CPU在执行其他的动作就可能影响这块数据),而此时 cuda runtime 正在执行数据的传输,这会导致不确定的行为

执行 kernel 时要想设置 stream 只需加一个stream参数即可

kernel_name<<<grid, block, sharedMemSize, stream>>>(argument list);
// 非默认的stream声明
cudaStream_t stream;
// 初始化
cudaStreamCreate(&stream);
// 资源释放
cudaError_t cudaStreamDestroy(cudaStream_t stream);

当执行资源释放的时候,若仍然有 stream 的工作未完成,那么虽然该函数仍然会立刻返回,但相关的工作做完后,这些资源才会自动释放

由于所有 stram 的执行都是异步的,就需要一些API在必要时进行同步:

cudaError_t cudaStreamSynchronize(cudaStream_t stream);
cudaError_t cudaStreamQuery(cudaStream_t stream);

第一个会强制 host 阻塞等待,直至 stream 中所有操作完成为止;第二个会检查 stream 中的操作是否全部完成,即使有操作没完成也不会阻塞 host。若所有操作都完成了,则返回 cudaSuccess,否则返回 cudaErrorNotReady

for (int i = 0; i < nStreams; i++) {
    int offset = i * bytesPerStream;
    cudaMemcpyAsync(&d_a[offset], &a[offset], bytePerStream, streams[i]);
    kernel<<<grid, block, 0, streams[i]>>>(&d_a[offset]);
    cudaMemcpyAsync(&a[offset], &d_a[offset], bytesPerStream, streams[i]);
}

for (int i = 0; i < nStreams; i++)
    cudaStreamSynchronize(streams[i]);

上述代码中使用了三个stream,数据传输和kernel运算都被分配在了这几个并发的stre

### CUDA 流的概念 CUDA 流(Stream)是一种用于管理 GPU 上操作顺序和并发性的机制。通过使用多个流,开发者可以在不同流之间实现任务的重叠执行,从而提高硬件利用率和程序性能。 #### 基本概念 - **默认流**:每个 CUDA 上下文中都有一个默认流(Default Stream),它负责串行化所有提交给它的操作。如果未显式指定流,则这些操作会进入默认流[^1]。 - **异步行为**:除了默认流外,其他流都具有异步特性,这意味着当某个流中的操作被发起后,CPU 可以立即继续处理后续的任务而无需等待该操作完成。 #### 创建与销毁流 可以通过 `cudaStreamCreate` 函数创建一个新的流实例,并用 `cudaStreamDestroy` 销毁不再使用的流资源: ```c++ cudaStream_t stream; cudaStreamCreate(&stream); // 使用流... cudaStreamDestroy(stream); ``` #### 数据传输与核函数调用 在实际应用中,通常需要将主机内存的数据复制到设备端或者反过来;同时也可能涉及多次调用同一个或不同的 kernel 来完成复杂的计逻辑。借助于自定义流,我们可以让上述过程更加高效地并行起来。例如下面的例子展示了如何利用两个独立的 streams 同时上传数据至GPU 并运行各自的 kernels: ```cpp float *d_A, *d_B; // Device pointers float *h_A, *h_B; // Host buffers filled with data... cudaMalloc((void**)&d_A, size); cudaMalloc((void**)&d_B, size); // Create two separate streams. cudaStream_t s1,s2; cudaStreamCreate(&s1); cudaStreamCreate(&s2); // Asynchronously copy A to device on first stream cudaMemcpyAsync(d_A,h_A,size,cudaMemcpyHostToDevice,s1); myKernel<<<blocksPerGridA,threadsPerBlockA,0,s1>>>(...parameters...) // Similarly do async operations related B but now using second stream 's2' cudaMemcpyAsync(d_B,h_B,size,cudaMemcpyHostToDevice,s2); anotherKernel<<<blocksPerGridB,threadsPerBlockB,0,s2>>>(...params...) // Cleanup resources after all work done ... cudaFree(d_A); cudaFree(d_B); cudaStreamDestroy(s1); cudaStreamDestroy(s2); ``` 这里需要注意的是,尽管来自不同streams 的指令可能会交错被执行,但是属于同一stream内的各项命令仍然保持其相对次序不变——即先发出的操作总是会在之后启动的那个之前结束。 #### 同步控制 为了协调多条流之间的相互作用关系,有时还需要引入额外的同步手段。这可通过事件(event)对象来达成目的。简单来说就是记录特定时刻的状态以便稍后再查询确认是否达到预期进展程度。 ```c++ cudaEvent_t event; cudaEventCreate(&event); someOperation<<<numBlocks,numThreads,sharedMemSize,customStream>>>(); cudaEventRecord(event, customStream); // Do other stuff here possibly involving different streams etc. // Later somewhere else where synchronization needed before proceeding further.. cudaEventSynchronize(event); cudaEventDestroy(event); ``` 以上介绍了关于CUDA Streams的一些基础知识及其典型应用场景下的编码实践方法论等内容[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GG_Bond21

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值