CUDA Graphs 是一种将 GPU 运算定义为图形(而非一系列流启动)并将其批量处理的方法。CUDA Graph 将一组 CUDA 内核和其他 CUDA 运算分组在一起,并使用指定的依赖关系树执行这些运算。它将与 CUDA 内核启动和 CUDA API 调用相关的驱动程序活动结合起来,从而加快工作流程。它还可以执行与硬件加速的依赖关系,而不是在可能的情况下仅依赖 CUDA 流和事件。
CUDA 图形对于 AI 框架尤为重要,因为它们使您能够捕获和回放一系列 CUDA 操作,从而降低 CPU 开销并提高性能。借助最新的改进,您现在可以更好地利用 CUDA 图形来加速 AI 工作负载。
在 CUDA 工具包 11.8 和 CUDA 工具包 12.6 以及随附的驱动程序版本之间,NVIDIA 在几个方面提高了 CUDA 图形的性能:
- 图形构建和实例化时间
- CPU 启动用度
- 几种边缘情况下的一般性能
在本文中,我们为各种图形提供了一些微基准测试数据,并讨论了自首次发布以来的改进。其中包括基准测试代码。与往常一样,性能特征取决于 CPU、GPU 和时钟设置。在测试中,我们使用了 Intel Xeon Silver 4208 处理器 (@ 2.10 GHz) 和 NVIDIA GeForce RTX 3060 GPU。操作系统是 Ubuntu 22.04。本文中的结果是使用默认时钟设置收集的。
这是 CUDA 12.6 以来性能改进的快照,因此未来 CUDA 版本可能无法展示与本文所述相同的性能。此快照假设您熟悉 CUDA 图形及其性能特征对应用程序的影响。有关更多信息,请参阅 Getting Started with CUDA Graphs。
定义
以下是在本博文后面讨论的一些图形拓扑术语和性能特征。
- 直线内核图形:完全由内核节点组成的 CUDA 图形,其中每个节点都只有一个依赖节点,除了最后一个节点。
- 并行直线:宽度为入口点数量的 CUDA 图形。每个入口点后都有自己的一组长度,即以直线形式排列的节点数。这在图表和源代码中也称为并行链。
- 首次启动:首次启动图形,其中还包括将实例化图形上传到设备。
- 重复启动:启动已上传至设备的图表。
- 启动的 CPU 开销:调用
CUDAGraphLaunch
到完成函数之间的 CPU 时间。 - 设备端运行时:设备上运行操作的时间量。
- 重复启动设备端运行时:之前上传的图形在设备上运行所需的时间。
- 解决问题所需时间:从启动调用到完成在设备上运行的图形的时间。
重复启动所需的恒定时间 CPU 开销
突出显示的一个显著改进与在 NVIDIA Ampere 架构上重复启动的 CPU 开销有关。对于直线内核图形,重复启动期间花费的时间显著减少。具体来说,时间从每个节点额外 2μs + 200 ns 缩短到几乎恒定的 2.5μs + (约 1ns per node),这表明具有 10 个或更多节点的图形的速度持续增强。
在 NVIDIA Ampere 架构之前的硬件上,启动仍然需要 时间。
由于这里测量的并行直线图形有四个根节点,因此使用一个节点重复启动需要6μs,而不是2μs。对于这个拓扑,链长对CPU启动开销的影响仍然可以忽略不计。本处不分析拥有多个根节点的确切扩展,尽管预期影响与根节点数量呈线性。同样,非核节点的图形具有不同的性能特征。
其他可衡量的性能提升
性能的其他几个方面对于从图形中受益的应用程序可能很重要:
- 实例化时间
- 首次启动 CPU 用度
- 重复启动设备运行时
- 重复图形启动到空流的端到端时间
指标 | 拓扑 | 拓扑长度 | 11.8 (r520.61.05) | 12.6 (r560.28.03) | 加速百分比 |
实例化 | 直线 | 10 | 20 uS | 16 uS | 25% |
100 | 168 uS | 127 uS | 32% | ||
1025 | 2143 uS | 1526 uS | 40% | ||
4 个并行链 | 10 | 71 uS | 58 uS | 22% | |
100 | 695 uS | 552 uS | 26% | ||
首次启动 CPU 开销 | 直线 | 10 | 4 uS | 4uS | 距离 0 公里 |
100 | 25 uS | 15 uS | 66% | ||
1025 | 278 uS | 175 uS | 59% | ||
4 个并行链 | 100 | 73 uS | 67 uS | 9 小时以上 | |
重复启动设备运行时 | 直线 | 10 | 7 uS | 7 uS | 改善 0% |
100 | 61 uS | 53 uS | 性能提升 15% | ||
1025 | 629 uS | 567 uS | 性能提升 11% | ||
重复图形启动到空流的端到端时间 | 直线 | 10 | 12 uS | 9 uS | 性能提升 30% |
100 | 69 uS | 55 uS | 性能提升 25% | ||
1025 | 628 uS | 567 uS | 性能提升 11% |
实例化时间
图形实例化所需的时间量在采用图形的运行时成本中占主导地位。这是一次图形实例化的成本。图形创建时间通常出现在应用程序启动的关键路径上,或者对于在 GPU 上没有运行工作时需要定期创建新图形的工作流而言。此处的改进可能会影响应用程序的启动时间,因为这些应用程序在程序启动时准备了许多图形,并最大限度地减少了对其他应用程序的端到端延迟损失,从而在其他工作执行背后隐藏图形构建延迟。
首次启动 CPU 用度
由于首次启动需要负责将 CUDA 图形的工作描述上传至 GPU,因此仍需消耗 CPU 资源。此资源在首次启动时支付,无需偿还,但是在更新图形时除外。大部分资源可以通过执行单独的上传操作来支付,本文并未讨论此资源,因为 CUDA Graphs 用户已可使用此资源。
重复启动设备运行时
即使运行空内核也需要时间。针对直线图形优化内核间延迟可缩短设备时间,高达每节点 60 ns
重复图形启动到空流的端到端时间
CUDA 图形包含的逻辑允许在启动操作处理图形中的所有节点之前开始执行,从而隐藏部分启动成本。因此,在完成工作的时间内,启动开销优势并不完全可见。
相反,您可以看到,在求解直线图形时,每个节点的解决时间提高了大约 60ns。
根据节点创建顺序进行调度
在 CUDA 工具包 12.0 中,CUDA 开始关注节点创建顺序,将其作为调度决策的一种启发式方法。CUDA 开始倾向于为先前创建的节点调度节点操作,而不是为稍后创建的节点调度节点操作。
具体来说,根节点的顺序固定了,并且删除了按广度优先遍历顺序排列节点的通道。该逻辑最初的设计目的是将根节点保持在图形节点列表的开头,并优先为GPU运行提供并行工作。
在将 memcopy 操作分配给同一复制引擎并对其进行错误序列化的情况下,旧的启发式算法通常会在无意中对首个计算项目所需的 memcopy 操作进行序列化,而这些操作是后续计算任务仅需执行的 memcopy 操作。这导致图形在可用于计算引擎的工作中出现气泡。因此,开发者通常会刻意按顺序创建图形节点:首先创建图形早期所需的节点,然后创建稍后所需的节点。
通过在做出一些调度决策时关注节点创建顺序,CUDA 可以为您提供更好地满足您直观期望的调度。
微基准测试源代码
已将微基准测试代码添加到/NVIDIA/cuda-samples 中,作为 cudaGraphsPerfScaling
应用程序。编译应用程序并运行包含 GPU 名称和 CUDA 驱动程序版本的 dataCollection.bash
脚本,以生成数据。
总结
对 CUDA 图形的新优化提出了令人信服的论据,表明它们是否在使用图形的应用程序中转化为实际性能改进。
在本文中,我们讨论了 CUDA 11.8 之后的 CUDA 图形性能改进。这些改进包括图形构建和实例化时间、CPU 启动开销,以及各种场景中的整体性能方面的增强。
有关更多信息,请参阅以下资源: