NVIDIA Omniverse NuRec 是一种神经重建工作流,用于根据摄像头和激光雷达等多传感器数据构建真实世界环境的高保真 3D 表示。它用于将智能汽车 (智能汽车) 和机器人平台捕获的动态场景重建为仿真就绪型数字环境,可在 NVIDIA Omniverse 和相关仿真工作流中进行渲染、回放和分析。
这些重建在物理 AI 和自主系统的开发中发挥着至关重要的作用。工程师可以捕捉真实的驾驶或机器人场景,重建环境,然后检查和回放场景。这使他们能够更好地理解模型行为、验证感知结果、生成合成视角或为下游机器学习工作流程创建训练数据。
NuRec 将 Gaussian splatting 等神经渲染技术与 GPU 加速的渲染和仿真管线相结合,可生成高度逼真的场景重建。但是,这种保真度会带来巨大的计算成本。重建和渲染工作负载涉及大量传感器数据、基于 PyTorch 的复杂训练循环,以及会大幅增加 GPU 资源的高度专业化 CUDA 内核。
本文将介绍一个示例,展示如何使用 NVIDIA Nsight 开发者工具 优化 NuRec 神经重建工作流。
解决性能优化挑战
性能对于 NuRec 工作流程至关重要,因为重建周转时间直接影响工程生产力。常见的工作流包括识别有趣或有问题的智能汽车运行 (例如,感知或规划堆栈发生意外行为的场景) ,并启动重建,以便工程师能够尽快检查场景。等待数小时进行重建会显著降低迭代和调试速度。
在此优化工作开始时,根据场景和配置,重建即使是相对较短的截图也可能需要一个多小时到几个小时的时间。该团队的长期目标更为宏大:实现实时重建性能,在大约 30 秒内重建 30 秒的捕获画面。
性能的重要性不仅限于重建本身。重建场景后,纯渲染工作流可能会生成大量帧,用于强化学习 (RL) 、合成数据生成 (SDG) 和大规模模拟。在这种规模下,即使性能稍有提升,也可以直接显著减少 GPU 时间和基础设施成本。
为了应对这些挑战,他们使用了 NVIDIA 分析和优化工具 (主要是 NVIDIA Nsight Systems 和 NVIDIA Nsight Compute) 来分析 NuRec 工作负载、识别软件堆栈中的瓶颈,并以迭代方式优化应用级工作流程和底层 CUDA 内核。
使用 Nsight Systems 进行分析和优化
Nsight Systems 是一款平台分析工具,可帮助您可视化和理解 CPU、GPU、存储、网络等工作负载的性能行为和资源利用率。许多性能优化工作流程的第一步是运行 Nsight Systems 配置文件以建立基准,并尝试识别一些初始瓶颈或需要改进的关键领域。
为了优化训练循环,我们使用 PyTorch 中包含的 Nsight Systems 内置功能支持和 NVIDIA Tools Extension SDK (NVTX) 来放大图 1 所示的单次正向传递迭代。最初的假设是,渲染内核将占用大部分运行时,并且是优化的最佳起点。但是,顶部的 CUDA 硬件时间轴显示,在大多数情况下,GPU 未得到充分利用或根本未使用。请注意,第一行没有显示蓝色。该应用使用的内核也比预期的要多得多。
图 1. 一次前向传递迭代的 Nsight Systems 配置文件时间轴
完成这一初步实现后,必须深入了解前向传递的各个阶段,以确定时间在哪里被使用,以及 GPU 在哪些阶段未得到充分利用。代码中添加了其他 NVTX 标注,以描绘各个阶段和功能。新的配置文件 (图 2) 显示,在渲染开始之前,collect_gaussian_parameters 占用了大部分时间,并且在每次前向传递中都会多次调用。
图 2. 将 collect_gaussian_parameters 识别为执行的大部分
深入挖掘后发现,interpolate 函数会使用多个时间 ( 4.148 毫秒) 并调用许多阻碍 GPU 运行的小型内核和内存操作,如图 3 底部 CUDA API 行所示。
图 3. 确定插值函数的许多小内核作为优化机会
我们在插值函数下深入研究了代码,专注于融合小内核并将更大的工作块提交给 GPU。我们能够将所有这些工作压缩到单个内核中,从而将插值函数从 4.184 毫秒减少到 83.81 毫秒 (图 4) 。速度提高了近 50 倍。
图 4. 在 CUDA API 行上使用单个融合内核插补函数
接下来,我们确定了长 cudaStreamSynchronize API (在时间轴上显示为绿色条) ,这些 API 会在 GPU 处于活动状态时延迟 CPU 对许多小核函数进行排队。这导致在调度和启动小型内核时返回 synchronize API 后,顶部 CUDA 硬件行中显示的 GPU 利用率不均匀 (图 5) 。
图 5. 长 cudaStreamSynchronize (底部绿色行) ,然后 GPU 执行不完整 (顶部蓝色行)
删除一个同步点后,线下的其他同步点将成为瓶颈。此过程一直持续到足以让 CPU 在 GPU 繁忙时高效地排队等待。这使得微小的内核能够紧凑地运行,因为它们不再受 CPU 启动时的限制。
图 6. 删除同步点后,GPU 利用率更低 (顶部的蓝色行)
通过减少收集参数和删除导致瓶颈的同步点所花费的时间,我们得以深入研究一些内核优化。Nsight Systems 可让您识别哪些内核是最热门的。在这种情况下,renderBackward 内核显然是首选。
图 7. Nsight Systems 中的主要内核 (按执行时间)
使用 Nsight Compute 进行内核优化
Nsight Compute 是分析和优化单个内核的出色工具。它可以使用各种类型的硬件计数器、软件补丁和仪器,自动回放内核,以非常精细的粒度收集大量性能数据。它包括内置规则系统和引导式分析,可帮助用户识别和理解问题。
renderBackward 内核用于摄像头和激光雷达数据处理。使用 Nsight Compute 分析此内核的多个实例后发现,其占用率只有约 15%,并且此内核的行为和资源要求存在显著差异,具体取决于正在处理的输入项。
最长的三个 renderBackward 内核来自激光雷达数据,另外三个来自摄像头数据。尽管存在这些差异,但两者都为每个线程分配了 167 个寄存器 (图 8) 。
图 8. Nsight Compute 中 renderBackward 内核的六个分析实例
将顶部的激光雷达内核设置为 Nsight Compute 基准 并比较相机内核会自动发现,虽然两者在共享内存中都拥有绝大多数访问权限,但即使内核的激光雷达和摄像头实例以静态方式为每个块分配相同数量的共享内存,相机内核发出的请求也减少了约 75%。
图 9. 激光雷达和摄像头数据内核之间的共享内存访问差异
注意到 renderBackward 内核用于处理摄像头数据还是激光雷达数据之间的行为差异,以及寄存器和共享内存分配对于两者都是静态的且相同的事实,下一步是尝试根据处理摄像头还是激光雷达数据来拆分内核。
对于内核的每个版本,团队都使用 launch_bounds 限定符和我们为每个块分配的共享内存量进行了试验和调整寄存器分配。 cudaFuncSetCacheConfig 运行时 API 用于将两个内核的首选项设置为拥有更大的共享内存和更小的 L1 缓存。
经过此次测试和优化后,激光雷达和摄像头内核将其寄存器分配需求分别从 167 个降低到 64 个和 128 个,并且两者都能够使用最初分配的共享内存的大约一半高效运行。这将占用率从 < 15% 提高到 30-50% 之间,并显著缩短了整体运行时间,最长的激光雷达内核从 31 毫秒减少到 18 毫秒。
图 10. 分割激光雷达和摄像头处理后的内核性能和配置
但仍有改进的空间。在发布时,我们正在研究确定的下一个问题,即工作负载失衡导致的内核中的长尾效应。这可以在 Nsight Compute 的 PM 采样部分中看到 (图 11) 。内核的上半部分显示,平均有 32 个活动线程束开始逐渐变细,而在过去的几毫秒内,每个周期的活动线程束不到一个。理想情况下,所有线程束对整个内核都处于活动状态。
图 11. Nsight Compute 中显示的长尾效应表明负载不平衡
开始使用 NVIDIA Nsight 开发者工具
性能分析和优化是一个迭代过程,包括运行配置文件、识别问题、修复问题,然后重新启动。您可以使用 Nsight Systems 和 Nsight Compute 等工具,更轻松地在 NVIDIA GPU 上进行开发和优化。这两种工具都是免费的 – 下载 Nsight Systems 和 Nsight Compute,并使用自己的用例进行试用。如果您有任何疑问或想要分享发现的内容,请在 NVIDIA 开发者论坛 上发表评论。
致谢
特别感谢 NVIDIA 贡献者 Francois Trudel、Joey Lai 和 Rodolfo Lima。