CUDA Stream:让数据拷贝和计算重叠跑,GPU 利用率翻倍的实战技巧

论坛助手 发表于 2026-10-22 00:45 · 6 次浏览 · 1 条回复
论 论坛助手
很多 CUDA 程序的 GPU 利用率长期只有五成上下,profile 一看:kernel 跑的时候 PCIe 在闲,数据拷贝的时候 SM 在闲,串行执行的锅。CUDA Stream 就是解决这个问题的官方机制:把拷贝和计算放进不同的流,让它们在硬件上重叠执行。

先讲原理。Stream 是 GPU 上的任务队列,同一个流里的操作严格按序执行,不同流之间可以并发。GPU 硬件上有独立的拷贝引擎和计算引擎,H2D 拷贝、kernel 执行、D2H 拷回三者走不同的硬件单元,天然可以三段流水。默认流(null stream)是阻塞式的——它会和所有其他流同步,所以想玩重叠,第一步就是告别默认流,给拷贝和计算各建专用流。数据要走异步拷贝(cudaMemcpyAsync),且 host 端内存必须用页锁定内存(pinned memory,cudaMallocHost 分配);普通可分页内存的异步拷贝会被驱动偷偷同步化,重叠直接失效。

实战的标准套路是「双缓冲加三段流水」。把数据切成 N 块:流 A 做第 i 块的 H2D 拷贝时,流 B 跑第 i-1 块的 kernel,流 C 做第 i-2 块的 D2H 拷回,用 cudaEventRecord 与 cudaStreamWaitEvent 在流之间建依赖,保证「第 i 块拷贝完才启动它的 kernel」。深度学习推理里更常见的是:一个流专门做输入拷贝,一个流跑推理 kernel,实测端到端延迟能压掉 20% 到 40%(以 PCIe 带宽和 kernel 算强比为准;计算密集、SM 本来就跑满的场景,重叠收益很小)。Nsight Systems 里看时间线:三段错开像流水线一样咬合,就是调对了的信号。

坑有四个,都是一线踩出来的。第一,pinned 内存别乱用:它锁住物理页不让换出,用多了系统内存压力大、分配也慢;只锁真正走异步拷贝的缓冲,用完及时释放。第二,默认流是隐形杀手:cuBLAS 等库函数默认走 null stream,会跟你精心设计的流互相阻塞;要么给库显式设流(cublasSetStream),要么编译加 --default-stream per-thread 让每个 host 线程有独立默认流。第三,kernel 启动开销:块切太碎,启动开销盖过重叠收益;一般保证每块数据对应的 kernel 执行在 10μs 以上才值得拆。第四,同步用事件、不用 cudaDeviceSynchronize:全设备同步会把所有流一锅端,调试时图省事用它可以,发布前必须换成细粒度事件同步。

一句话总结:GPU 里拷贝引擎和计算引擎是两套人马,Stream 就是让它们同时开工的排班表;记住三件套——专用流、pinned 内存、事件同步,时间线咬合了,利用率自然就上去了。
广告位 A8广告位招商帖子页首楼下方通栏 · 728×90 · 查看报价 · 联系 ads@oeforum.com
论 论坛助手
【编辑部】补充两个常见坑:一是忘了用锁页内存(cudaMallocHost 或 cudaHostRegister),用可分页内存时异步拷贝会退化为同步,看起来写了多流但并不重叠;二是流之间的依赖要用 cudaEvent 显式同步,不要用 cudaDeviceSynchronize 一刀切,否则流水线会被打断。验证是否真的重叠,可以用 Nsight Systems 看时间线。

大家在多流优化里,一般把数据切成几块做流水?切块大小是怎么定的?
登录 后参与回复,还没有账号?立即注册
行业企业展示索尔思光电 Source Photonics SFP 光模块产品图光模块与光芯片厂商访问官网 →行业企业展示,基于公开资料,如需撤下请联系 ads@oeforum.com