CUDA编程 - 如何使用 CUDA 流在 GPU 设备上并发执行多个内核 - 如何应用到自己的项目中 - concurrentKernels

如何使用 CUDA 流在 GPU 设备上并发执行多个内核

  • 一、完整代码与例程目的
    • 1.1、通过现实场景来理解多任务协作:
    • 1.2、完整代码:
  • 二、代码拆解与复用
    • 2.1、编程模版

一、完整代码与例程目的

项目地址:https://github.com/NVIDIA/cuda-samples/tree/v11.8/Samples/0_Introduction/concurrentKernels

此代码演示了​​多流并发执行​​多个内核、流间依赖管理​​,核心是通过CUDA流和事件实现以下目标:

  • 并发执行多个内核​​:创建多个流(nstreams = nkernels + 1),每个流独立执行clock_block核函数。
  • ​​流间同步​​:通过cudaStreamWaitEvent让最后一个流等待所有其他流的完成事件,确保正确依赖关系。
  • ​​性能测量​​:使用cudaEventElapsedTime统计总执行时间,对比串行与并发的效率差异

1.1、通过现实场景来理解多任务协作:

场景:快餐店厨房的并发任务处理

假设一个快餐店需要同时处理多个订单,每个订单包含汉堡、薯条和饮料。
厨房有不同的工作站(流),员工需要高效协作。

  1. ​​多订单并行处理(多个CUDA流)​​ ​​场景​​:
    同时来了8个订单(对应nkernels=8),每个订单的汉堡制作由不同厨师(CUDA流)并行处理。
    ​​代码映射​​:每个clock_block内核模拟一个汉堡制作任务,分配到不同的流中并发执行:
    clock_block<<<1, 1,0, streams[i]>>>(&d_a[i], time_clocks);
    每个流独立工作,就像不同厨师同时做汉堡,互不干扰。
  1. ​​任务完成通知(事件记录)​​ ​​场景​​:每个厨师完成汉堡后按铃(记录事件),通知前台。 ​​
    代码映射​​:每个流完成任务后记录事件: cudaEventRecord(kernelEvent[i], streams[i]);
    这类似于厨师按铃告知汉堡完成
  1. ​​等待所有订单完成(事件等待)​​ ​​场景​​:配餐员(最后一个流)需等所有汉堡做好后才能打包。 ​​
    代码映射​​:最后一个流通过cudaStreamWaitEvent等待所有事件:
    cudaStreamWaitEvent(streams[nstreams-1], kernelEvent[i], 0);
    配餐员直到所有铃响(事件完成)才开始打包。
  1. ​​汇总与交付(归约和内存拷贝)​​ ​​场景​​:配餐员汇总所有食品,检查无误后交给顾客。
    ​​代码映射​​:执行sum内核对结果归约,并拷贝回主机: sum<<<1,32,0,streams[nstreams-1]>>>(d_a,nkernels); cudaMemcpyAsync(a, d_a, …, streams[nstreams-1]);
    归约操作相当于统计所有汉堡是否完成,拷贝则是交付订单
  1. ​​性能对比(时间测量)​​ ​​场景​​:串行处理8个订单需8倍时间,而并发处理仅需1倍(理想情况下)。 ​​
    代码验证​​:测量实际耗时,对比预期值: printf(“Measured time for sample = %.3fs\n”,elapsed_time/1000.0f);
    厨房通过优化流程缩短总时间,类似GPU通过并发提升效率。

1.2、完整代码:

#include <cooperative_groups.h>
#include <stdio.h>namespace cg = cooperative_groups;
#include <helper_cuda.h>
#include <helper_functions.h>// This is a kernel that does no real work but runs at least for a specified
// number of clocks
__global__ void clock_block(clock_t *d_o, clock_t clock_count) {unsigned int start_clock = (unsigned int)clock();clock_t clock_offset = 0;while (clock_offset < clock_count) {unsigned int end_clock = (unsigned int)clock();// The code below should work like// this (thanks to modular arithmetics)://// clock_offset = (clock_t) (end_clock > start_clock ?//                           end_clock - start_clock ://                           end_clock + (0xffffffffu - start_clock));//// Indeed, let m = 2^32 then// end - start = end + m - start (mod m).clock_offset = (clock_t)(end_clock - start_clock);}d_o[0] = clock_offset;
}// Single warp reduction kernel
__global__ void sum(clock_t *d_clocks, int N) {// Handle to thread block groupcg::thread_block cta = cg::this_thread_block();__shared__ clock_t s_clocks[32];clock_t my_sum = 0;for (int i = threadIdx.x; i < N; i += blockDim.x) {my_sum += d_clocks[i];}s_clocks[threadIdx.x] = my_sum;cg::sync(cta);for (int i = 16; i > 0; i /= 2) {if (threadIdx.x < i) {s_clocks[threadIdx.x] += s_clocks[threadIdx.x + i];}cg::sync(cta);}d_clocks[0] = s_clocks[0];
}int main(int argc, char **argv) {int nkernels = 8;             // number of concurrent kernelsint nstreams = nkernels + 1;  // use one more stream than concurrent kernelint nbytes = nkernels * sizeof(clock_t);  // number of data bytesfloat kernel_time = 10;                   // time the kernel should run in msfloat elapsed_time;                       // timing variablesint cuda_device = 0;printf("[%s] - Starting...\n", argv[0]);// get number of kernels if overridden on the command lineif (checkCmdLineFlag(argc, (const char **)argv, "nkernels")) {nkernels = getCmdLineArgumentInt(argc, (const char **)argv, "nkernels");nstreams = nkernels + 1;}// use command-line specified CUDA device, otherwise use device with highest// Gflops/scuda_device = findCudaDevice(argc, (const char **)argv);cudaDeviceProp deviceProp;checkCudaErrors(cudaGetDevice(&cuda_device));checkCudaErrors(cudaGetDeviceProperties(&deviceProp, cuda_device));if ((deviceProp.concurrentKernels == 0)) {printf("> GPU does not support concurrent kernel execution\n");printf("  CUDA kernel runs will be serialized\n");}printf("> Detected Compute SM %d.%d hardware with %d multi-processors\n",deviceProp.major, deviceProp.minor, deviceProp.multiProcessorCount);// allocate host memoryclock_t *a = 0;  // pointer to the array data in host memorycheckCudaErrors(cudaMallocHost((void **)&a, nbytes));// allocate device memoryclock_t *d_a = 0;  // pointers to data and init value in the device memorycheckCudaErrors(cudaMalloc((void **)&d_a, nbytes));// allocate and initialize an array of stream handlescudaStream_t *streams =(cudaStream_t *)malloc(nstreams * sizeof(cudaStream_t));for (int i = 0; i < nstreams; i++) {checkCudaErrors(cudaStreamCreate(&(streams[i])));}// create CUDA event handlescudaEvent_t start_event, stop_event;checkCudaErrors(cudaEventCreate(&start_event));checkCudaErrors(cudaEventCreate(&stop_event));// the events are used for synchronization only and hence do not need to// record timings this also makes events not introduce global sync points when// recorded which is critical to get overlapcudaEvent_t *kernelEvent;kernelEvent = (cudaEvent_t *)malloc(nkernels * sizeof(cudaEvent_t));for (int i = 0; i < nkernels; i++) {checkCudaErrors(cudaEventCreateWithFlags(&(kernelEvent[i]), cudaEventDisableTiming));}//// time execution with nkernels streamsclock_t total_clocks = 0;
#if defined(__arm__) || defined(__aarch64__)// the kernel takes more time than the channel reset time on arm archs, so to// prevent hangs reduce time_clocks.clock_t time_clocks = (clock_t)(kernel_time * (deviceProp.clockRate / 100));
#elseclock_t time_clocks = (clock_t)(kernel_time * deviceProp.clockRate);
#endifcudaEventRecord(start_event, 0);// queue nkernels in separate streams and record when they are donefor (int i = 0; i < nkernels; ++i) {clock_block<<<1, 1, 0, streams[i]>>>(&d_a[i], time_clocks);total_clocks += time_clocks;checkCudaErrors(cudaEventRecord(kernelEvent[i], streams[i]));// make the last stream wait for the kernel event to be recordedcheckCudaErrors(cudaStreamWaitEvent(streams[nstreams - 1], kernelEvent[i], 0));}// queue a sum kernel and a copy back to host in the last stream.// the commands in this stream get dispatched as soon as all the kernel events// have been recordedsum<<<1, 32, 0, streams[nstreams - 1]>>>(d_a, nkernels);checkCudaErrors(cudaMemcpyAsync(a, d_a, sizeof(clock_t), cudaMemcpyDeviceToHost, streams[nstreams - 1]));// at this point the CPU has dispatched all work for the GPU and can continue// processing other tasks in parallel// in this sample we just wait until the GPU is donecheckCudaErrors(cudaEventRecord(stop_event, 0));checkCudaErrors(cudaEventSynchronize(stop_event));checkCudaErrors(cudaEventElapsedTime(&elapsed_time, start_event, stop_event));printf("Expected time for serial execution of %d kernels = %.3fs\n", nkernels,nkernels * kernel_time / 1000.0f);printf("Expected time for concurrent execution of %d kernels = %.3fs\n",nkernels, kernel_time / 1000.0f);printf("Measured time for sample = %.3fs\n", elapsed_time / 1000.0f);bool bTestResult = (a[0] > total_clocks);// release resourcesfor (int i = 0; i < nkernels; i++) {cudaStreamDestroy(streams[i]);cudaEventDestroy(kernelEvent[i]);}free(streams);free(kernelEvent);cudaEventDestroy(start_event);cudaEventDestroy(stop_event);cudaFreeHost(a);cudaFree(d_a);if (!bTestResult) {printf("Test failed!\n");exit(EXIT_FAILURE);}printf("Test passed\n");exit(EXIT_SUCCESS);
}

二、代码拆解与复用

2.1、编程模版

可以用下面的模版来填写自己的任务,详细的注释在代码里面:

#include <cstdio>
#include <cuda_runtime.h>
#include <cooperative_groups.h> // 如需使用协作组namespace cg = cooperative_groups;// 1. 定义任务参数
const int NUM_STREAMS = 4;       // 流数量(根据硬件调整)
const int NUM_KERNELS = 3;       // 每个流的任务数(示例:每个流3个任务)
const size_t DATA_SIZE = 1024;   // 数据大小// 2. 错误检查宏(必须包含)
#define CHECK_CUDA(call) {                                 \cudaError_t err = call;                                 \if (err != cudaSuccess) {                               \printf("CUDA Error at %s:%d - %s\n", __FILE__, __LINE__, cudaGetErrorString(err)); \exit(EXIT_FAILURE);                                 \}                                                       \
}// 3. 核函数示例(自定义具体任务)
__global__ void exampleKernel(float* d_data, int size, int step) {int idx = blockIdx.x * blockDim.x + threadIdx.x;if (idx < size) {d_data[idx] += step; // 示例操作:数据递增}
}// 4. 主函数模板
int main() {// ===================== 初始化阶段 =====================// 4.1 分配主机和设备内存float *h_data, *d_data[NUM_STREAMS];CHECK_CUDA(cudaMallocHost(&h_data, DATA_SIZE * sizeof(float))); // 页锁定内存for (int i = 0; i < NUM_STREAMS; ++i) {CHECK_CUDA(cudaMalloc(&d_data[i], DATA_SIZE * sizeof(float)));}// 4.2 创建流和事件cudaStream_t streams[NUM_STREAMS];cudaEvent_t events[NUM_STREAMS][NUM_KERNELS]; // 每个任务一个事件for (int i = 0; i < NUM_STREAMS; ++i) {CHECK_CUDA(cudaStreamCreate(&streams[i]));for (int j = 0; j < NUM_KERNELS; ++j) {// 事件无需计时,仅用于同步CHECK_CUDA(cudaEventCreateWithFlags(&events[i][j], cudaEventDisableTiming));}}// ===================== 任务分发阶段 =====================for (int i = 0; i < NUM_STREAMS; ++i) {// 4.3 异步内存拷贝(主机到设备)CHECK_CUDA(cudaMemcpyAsync(d_data[i], h_data, DATA_SIZE * sizeof(float),cudaMemcpyHostToDevice, streams[i]));// 4.4 提交多个任务到当前流(示例:3个连续任务)for (int j = 0; j < NUM_KERNELS; ++j) {// 定义内核参数(自定义)dim3 block(256);dim3 grid((DATA_SIZE + block.x - 1) / block.x);// 执行内核(示例:每个任务增加数据)exampleKernel<<<grid, block, 0, streams[i]>>>(d_data[i], DATA_SIZE, j+1);// 记录事件,标记任务完成CHECK_CUDA(cudaEventRecord(events[i][j], streams[i]));// 可选:后续任务等待当前事件(创建依赖)// 例如:下一任务需等待当前任务完成if (j < NUM_KERNELS - 1) {CHECK_CUDA(cudaStreamWaitEvent(streams[i], events[i][j], 0));}}// 4.5 异步拷贝回主机(设备到主机)CHECK_CUDA(cudaMemcpyAsync(h_data, d_data[i], DATA_SIZE * sizeof(float),cudaMemcpyDeviceToHost, streams[i]));}// ===================== 同步与清理阶段 =====================// 5.1 同步所有流for (int i = 0; i < NUM_STREAMS; ++i) {CHECK_CUDA(cudaStreamSynchronize(streams[i]));}// 5.2 释放资源CHECK_CUDA(cudaFreeHost(h_data));for (int i = 0; i < NUM_STREAMS; ++i) {CHECK_CUDA(cudaFree(d_data[i]));CHECK_CUDA(cudaStreamDestroy(streams[i]));for (int j = 0; j < NUM_KERNELS; ++j) {CHECK_CUDA(cudaEventDestroy(events[i][j]));}}printf("Execution completed successfully.\n");return 0;
}

模板关键点说明

  • ​​1、参数调整区​​:

    • NUM_STREAMS:根据GPU并发能力调整(通常等于SM数量)。
    • NUM_KERNELS:每个流中的任务数,用于创建依赖链。
    • DATA_SIZE:根据实际数据规模调整。
  • ​​​​2、任务自定义区​​:

    • ​​核函数​​:替换exampleKernel为实际计算任务。
    • ​​内核配置​​:调整block和grid维度,优化执行配置。
  • 3、依赖管理​​:

    • ​​事件等待​​:通过cudaStreamWaitEvent显式定义任务依赖关系。
    • ​​流水线示例​​:每个流的任务按顺序执行,通过事件确保顺序。
  • 4、异步操作​​:

    • ​​使用cudaMemcpyAsync实现数据传输与计算重叠。
    • ​​每个流的操作独立,最大化并行度。

技术总结

  • ​​CUDA流​​:类似厨房的工作站,允许多任务并行。
  • ​​事件同步​​:类似订单完成的通知机制,确保依赖任务正确执行顺序。
  • ​​性能提升​​:在支持并发内核的GPU上,8个clock_block可重叠执行,总时间接近单个任务耗时,而非8倍。

此模式适用于需要并行独立任务且后续步骤依赖所有结果的应用

  • 视频处理流水线​​:多个帧同时处理(每帧解码→滤波→编码在不同流中)。
  • ​​数值模拟​​:多个独立参数组的并发计算,最后汇总结果。
  • ​​机器学习推理​​:批量数据分到不同流,并行执行预处理→推理→后处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/77850.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue3 打字机效果

打字机效果 因后端返回的数据也是通过microsoft/fetch-event-source 一句一句流式返回 但是前端展示效果想要实现打字机效果 代码如下 <template><div><div class"text-container"><span class"text-content">{{ displayText }…

线上JVM调优与全栈性能优化 - Java架构师面试实战

线上JVM调优与全栈性能优化 - Java架构师面试实战 本文通过一场互联网大厂的Java架构师面试&#xff0c;深入探讨了线上JVM调优、OOM定位、死锁定位、内存和CPU调优、线程池调优、数据库调优、缓存调优、网络调优、微服务调优及分布式调优等关键领域。 第一轮提问 面试官&am…

【Android】轻松实现实时FPS功能

文章目录 实时FPS 实时FPS 初始化 choreographer Choreographer.getInstance();lastFrameTimeNanos System.nanoTime();choreographer.postFrameCallback(frameCallback);监听并显示 Choreographer.FrameCallback frameCallback new Choreographer.FrameCallback() {Overri…

GD32F407单片机开发入门(十九)DMA详解及ADC-DMA方式采集含源码

文章目录 一.概要二.GD32F407VET6单片机DMA外设特点三.GD32单片机DMA内部结构图四.DMA各通道请求五.GD32F407VET6单片机ADC-DMA采集例程六.工程源代码下载七.小结 一.概要 基本概念&#xff1a; DMA是Direct Memory Access的首字母缩写,是一种完全由硬件执行数据交换的工作方式…

vue报错:Error: Cannot find module ‘is-stream‘

此错误提示 Cannot find module ‘is-stream’ 表明 Node.js 无法找到 is-stream 模块。一般而言&#xff0c;这是由于项目中未安装该模块所导致的。 解决方案: //npm npm install is-stream //yarn yarn add is-stream安装后检查 安装完成之后&#xff0c;你可以再次运行项目…

全局事件总线EventBus的用法

全局事件总线 EventBus 在前端开发中是一种用于实现组件间通信的机制&#xff0c;适用于兄弟组件或跨层级组件间的数据传递。 1. 创建全局 EventBus 实例 在前端项目中&#xff0c;先创建一个全局的 EventBus 实例。在 Vue 中&#xff0c;可以通过创建一个新的 Vue 实例来实现…

SpringBoot 设置HTTP代理访问

SpringBoot 设置HTTP代理访问 遇到这样的一个场景&#xff0c;代码部署到私有服务器上去之后&#xff0c;这台私有服务器a无法直接访问公网&#xff0c;需要通过代理转发到另外一台专门访问公网的服务器b, 让服务器b去请求对应的公网ip&#xff0c;于是就需要设置Http代理。 …

在C# WebApi 中使用 Nacos01:基础安装教程和启动运行

一、JDK的安装 Nacos需要依赖JAVA环境运行,所以需要先安装JDK 1.检查是否安装 可用命令行检查是否安装JDK 直接win+r,cmd: java -version 出现这个说明安装成功 2.下载JDK 访问官网点击下载:

cURL 入门:10 分钟学会用命令行发 HTTP 请求

curl初识 curl 通过 URL 传输数据的命令行工具和库是一个非常强大的命令行工具&#xff0c;用于在网络上传输数据。它支持众多的协议&#xff0c;像 dict file ftp ftps gopher gophers http https imap imaps ipfs ipns ldap ldaps mqtt pop3 pop3s rtsp smb smbs smtp smtps…

Redis应用场景实战:穿透/雪崩/击穿解决方案与分布式锁深度剖析

一、缓存异常场景全解与工业级解决方案 1.1 缓存穿透&#xff1a;穿透防御的三重门 典型场景 恶意爬虫持续扫描不存在的用户ID 参数注入攻击&#xff08;如SQL注入式查询&#xff09; 业务设计缺陷导致无效查询泛滥 解决方案进化论 第一层防护&#xff1a;布隆过滤器&am…

C# 高效操作excel文件

C#高效操作Excel文件指南 一、主流Excel处理方案对比 方案类型特点适用场景​​EPPlus​​第三方库功能全面&#xff0c;性能好&#xff0c;支持.xlsx复杂Excel操作&#xff0c;大数据量​​NPOI​​第三方库支持.xls和.xlsx&#xff0c;功能全面兼容旧版Excel文件​​Closed…

Rust 学习笔记:结构体(struct)

Rust 学习笔记&#xff1a;结构体&#xff08;struct&#xff09; Rust 学习笔记&#xff1a;结构体&#xff08;struct&#xff09;结构体的定义和实例化使用字段初始化简写用 Struct Update 语法从其他实例创建实例使用没有命名字段的元组结构来创建不同的类型没有任何字段的…

Dify Agent节点的信息收集策略示例

Dify Agent节点的信息收集策略示例 0. 安装"对话 Agent"插件1. 创建一个 Chatflow2. 创建一个 Agent 节点3. 创建一个条件分支节点4. 在IF分支创建一个LLM节点5. 创建一个直接回复节点6. 在ELSE分支创建一个直接回复节点7. 分布并预览 0. 安装"对话 Agent"…

Qt/C++开发监控GB28181系统/获取设备信息/设备配置参数/通道信息/设备状态

一、前言 设备注册成功后&#xff0c;接下来要做的就是获取设备的信息&#xff0c;尤其是通道信息&#xff0c;根据国标协议&#xff0c;永远只有两个层级&#xff0c;一个是设备&#xff0c;然后就是设备下面多个通道&#xff0c;设备编码在整个系统中唯一&#xff0c;通道编…

金融风控的“天眼”:遥感技术的创新应用

在金融市场的复杂博弈中&#xff0c;风险管控一直是金融机构的核心竞争力。然而&#xff0c;传统的风控手段在应对现代金融市场的快速变化时&#xff0c;往往显得捉襟见肘。 如今&#xff0c;遥感技术的创新应用为金融风控带来了全新的视角和手段。星图云开放平台的遥感金融立体…

HFI笔记

高频分量&#xff1a; 载波频率的一半 选择alfabeta轴进行计算的原因 最终结果&#xff1a; 观测器方程 采样加减分离法-&#xff08;高低频分离&#xff09; 高频信号的评论高频载波 转子极性辨识

halcon关闭图形窗口

1、dev_close_window () 调用一次这个函数关闭一个图形窗口&#xff0c;并且先关闭最后打开的那个图形窗口&#xff0c;如果一共打开了N个图形窗口&#xff0c;那么就需要调用dev_close_window N次。

每日算法-250430

每日算法 - 2025年4月30日 记录下今天解决的两道题目。 870. 优势洗牌 (Advantage Shuffle) 题目描述 解题思路与方法 核心思想&#xff1a;贪心策略 (田忌赛马) 这道题的目标是对于 nums1 中的每个元素&#xff0c;找到 nums2 中一个比它小的元素进行配对&#xff08;如果…

【MySQL】增删改查(CRUD)

目录 一. CRUD是什么 二. Create&#xff08;新增数据&#xff09; 2.1 单行数据全列插入 2.2 单行数据指定列插入 2.3 多行数据指定列插入 三. Retrieve &#xff08;检索/查询&#xff09; 3.1 全列查询 3.2 指定列查询 3.3 查询字段为表达式 3.4 为查询结果指定别名 3…

电商平台 API 开发实战:京东商品详情数据实时获取接口对接教程

在电商行业竞争日益激烈的当下&#xff0c;实时获取商品详情数据对于市场分析、竞品监控、商品推荐等业务场景至关重要。京东作为国内领先的电商平台&#xff0c;提供了强大的 API 接口&#xff0c;允许开发者获取丰富的商品信息。本文将详细介绍京东商品详情数据实时获取接口的…