推荐场景GPU优化的探索与实践:CUDA Graph与多流并行的比较与分析

本文涉及的产品
实时计算 Flink 版,5000CU*H 3个月
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
实时数仓Hologres,5000CU*H 100GB 3个月
简介: RTP 系统(即 Rank Service),是一个面向搜索和推荐的 ranking 需求,支持多种模型的在线 inference 服务,是阿里智能引擎团队沉淀多年的技术产品。今年,团队在推荐场景的GPU性能优化上又做了新尝试——在RTP上集成了Multi Stream,改变了TensorFlow的单流机制,让多流的执行并行,作为增加GPU并行度的另一种选择。本文详细介绍与比较了CUDA Graph与多流并行这两个方案,以及团队的实践成果与心得。

作者:阿里巴巴控股集团-智能引擎事业部-董纪莹


背景&问题

RTP 系统(即 Rank Service),是一个面向搜索和推荐的 ranking 需求,支持多种模型的在线 inference 服务。在过去的几年里,我们对于RTP业务的GPU性能优化已经做了不少尝试,包括kernel fusion,CUDA Graph等。在此基础上,今年我们又在RTP上集成了Multi Stream,改变了TensorFlow的单流机制,让多流的执行并行,作为增加GPU并行度的另一种选择。

RTP执行的backend是TensorFlow。TensorFlow作为调度算子的框架应用在推荐业务的推理场景中,对GPU是比较不友好的。这是因为TensorFlow总是倾向于用大量的operator构建一个graph,每个operator在GPU上又会调用一个或者多个kernel;而同时,TensorFlow本身是一个单流模型,在一个进程内只有一个Stream group(包含一个Compute Stream,一个H2D Stream,一个D2H Stream和一个D2D Stream),kernel间的执行很难并行,可以说调度的效率是偏低的。

image.png

图1 TensorFlow Stream group


在GPU上,kernel的调用分为kernel launch和kernel执行两步,kernel launch负责准备好执行kernel需要的资源,通常在us级别;kernel执行则是实际上GPU完成的计算。一些比较简单的kernel执行时间可能会小于us,但却不得不等待数us的kernel launch,这就是所谓的kernel launch瓶颈。在我们的推理场景中,由于graph包含的kernel数量极多,单流模型的调度效率又低,几乎一定是kernel launch瓶颈的。

image.png

图2 典型的搜推模型运行timeline


缓解kernel launch瓶颈主要有两个思路,一个就是kernel fusion,通过减少kernel数量减少launch数量,同时也会带来访存和计算上的优化;另一个思路就是提高kernel launch的效率,减少每一次kernel launch的代价或者并行launch kernel。


CUDA Graph

熟悉RTP的同学可能了解到,在2020年,我们已经在RTP分支的TensorFlow里集成了CUDA Graph。CUDA Graph通过预先create或者capture一个graph(我们希望这尽可能是一个完整的GPU子图),将graph里数量众多的kernel launch转化成一次graph launch,以降低launch在device和host上的开销,几乎可以说是解决了kernel launch瓶颈的问题。

image.png

图3 kerne launch转化为graph launch


但实际应用CUDA Graph需要满足比较高的要求:

一个是CUDA Graph并不支持动态shape,而搜推场景的batch size大部分都是动态的。为了满足这个条件,我们的方案是预先capture多张不同batch size的子图供运行时的请求选择;要是请求的batch size超过预先capture的最大值,就back up到TensorFlow的实现。

这已经是一个相对合理的方案,但实际应用的时候还是会有不少问题。一个问题是,经过预先的填充,当前graph里只能有唯一一个动态的维度,且它的值必须是batch size,这也意味着,子图里一些诸如Concat,Gather,Split等可能会导致破坏这一条件的操作应当要被谨慎的排除出去。另一个问题是,对于batch size的选择依赖于模型输入的分布和实际硬件的显存(因为多份图当然占用了多份存储),这就依靠经验,或工具层自动的根据历史流量分布选择参数。

image.png

图4 动态batch size支持


第二个要求是对于CUDA Graph来说,必须保证Graph的输入输出地址固定。针对这个限制,当前我们的方案是将来自CPU的输入先放到GPU上,然后和在GPU上完成了一些计算的tensor一起作为CUDA Graph的输入,通过D2D copy到Graph里面。增加了一层数据传输必然会带来延时的增长。当然还可以有另一个方案,先保证整个GPU子图都可以被capture,然后将CPU输入拷贝到固定的host地址上,将原方案里的H2D+D2D转换成H2H+H2D。但无论如何,多一级Memcpy是不可避免的。

在这些限制下,我们对CUDA Graph的用法就变成了,先通过kernel fusion将整个GPU子图整理成一张结构干净,shape“固定”的子图,然后再capture整理完的子图,让CUDA Graph照顾一些手工的kernel fusion难以整理到位,但实际计算又很轻的计算,比如常见的elementwise操作等,让这些本身计算开销小的kernel的launch开销也几乎可以忽略不计。基于这种比较精细的用法,CUDA Graph的收益主要有:

  1. 将大量的kernel launch转化为一次graph launch,从而极大的节省了host和device开销;
  2. 多个CUDA Graph的执行是完全独立、可并行的,因此会直接被分配到多个Stream上,这种多Stream的并行也极大的提升了吞吐,很好的增强了单机服务能力。

不过这种能够保证CUDA Graph优化效果的用法事实上对工程同学提出了不低的要求,需要用户既熟悉模型结构(且能做一定程度的图优化),也熟悉模型流量分布,还要简单了解device arch(至少是不同型号的GPU memory大小)。这些要求稍不满足,便很容易得出一个效果不佳,提升有限的结论。但不得不承认的是,CUDA Graph可以称得上我们过去几年最重要的优化,在重要场景实现了平均2倍左右的吞吐提升,帮助千卡集群平稳度过多轮大促的流量洪峰。


Multi Stream

为了降低优化的应用门槛,提高适用性,今年我们的一个主要工作就是在TensorFlow里集成了Multi Stream。

MultiStream的实现来自Nvidia DevTech团队。它的基础思路非常简单:一个Stream的device利用率低,就分配多个Stream,并且是把整个GPU子图放到不同Stream上,让请求和请求并行。

image.png

图5 多Stream group并行


直接创建多个Stream group的性能提升是比较有限的。通过分析GPU timeline,会发现在每个Stream group内,都存在大量的cuEventRecord和cuEventQuery,这些Event大部分都来源于Compute Stream和 Memcpy Stream间的同步。在整个进程只有一个Stream group时,通过将计算和传输行为分配到多个Stream上以尽可能overlap,并通过必要的同步来保证行为当然是非常合理的。但当我们有多个Stream group后,是不是Stream group间的overlap就足以提升device利用率了呢?我们的实验证明,当整个device存在多个Compute Stream时,把相对应的Memcpy Stream合并到comput Stream中,可以有效减少Stream间的同步行为,提高GPU利用率。

此外,我们在GPU timeline中看到层出不穷的pthread_rwlock_wrlock,阻碍了kernel launch。这是因为GPU driver对cuda context有读写保护。当一个cuda context向多个Stream launch kernel时,driver会给kernel launch上比较重的锁。事实上这层锁随着driver更新在逐步减轻,driver510已经将读写锁改成读锁,这层限制大概率会随着驱动的升级进一步被弱化。但当前我们最好的方法还是直接把合并后的每个Stream都放到各自不同的context中去,并通过MPS实现context间的并行。MPS是Nvidia对于多process/context的优化方案,将多个process/context放到同一个control daemon下,共享一个context,是一个比较成熟,且相对易用的方案。这里提供一个文档(Nvidia MPS)。

还有一个相对简单的点是,开启多流后,为了避免多个thread向同一个Stream launch kernel的pthread_mutex_lock,我们也给每个Stream配了一个私有的GPU thread,让这一个thread去完成对应Stream上的所有kernel launch。当然这种做法依然无法避免多个thread一起H2D的launch竞争。我们也做了一点尝试,但都不是很成功,就不赘述了。

image.png

图6 Multi context + MPS在推荐业务上的timeline


到这里,我们能做的就基本上做完了。简单的在推荐的几个场景做了验证,测试下来多流的性能提升大概能够接近CUDA Graph的性能,如图6所示,创建了4个context,每个context各一个Stream,且对应一个thread,Stream与Stream间,计算与传输间,都可以比较好的overlap。在FY23双十一,多流的优化已经推广到了大部分的RTP业务上,非常显著的提升了性能,在大部分业务场景上取得了1-3倍的性能提升,降低了业务运行的硬件成本。


总结&致谢

最后我们简单的比较一下这两种方案:

  1. CUDA Graph作为有硬件支持的方案,将大量kernel launch转换为一次graph launch,可以同时节省host和device开销,在应用得当的前提下应当是最优性能的最佳选择;
  2. Multi Stream主要是通过创建多个Stream的做法增加了kernel执行的并行,从而更好的利用资源,在易用性上远超CUDA Graph。

我们的工作都是在与Nvidia DevTech团队的密切合作下完成的,非常感谢Nvidia DevTech团队对我们的技术支持。

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
3月前
|
缓存 并行计算 PyTorch
PyTorch CUDA内存管理优化:深度理解GPU资源分配与缓存机制
本文深入探讨了PyTorch中GPU内存管理的核心机制,特别是CUDA缓存分配器的作用与优化策略。文章分析了常见的“CUDA out of memory”问题及其成因,并通过实际案例(如Llama 1B模型训练)展示了内存分配模式。PyTorch的缓存分配器通过内存池化、延迟释放和碎片化优化等技术,显著提升了内存使用效率,减少了系统调用开销。此外,文章还介绍了高级优化方法,包括混合精度训练、梯度检查点技术及自定义内存分配器配置。这些策略有助于开发者在有限硬件资源下实现更高性能的深度学习模型训练与推理。
678 0
|
2月前
|
人工智能 并行计算 开发者
CUDA重大更新:原生Python可直接编写高性能GPU程序
NVIDIA在2025年GTC大会上宣布CUDA并行计算平台正式支持原生Python编程,消除了Python开发者进入GPU加速领域的技术壁垒。这一突破通过重新设计CUDA开发模型,引入CUDA Core、cuPyNumeric、NVMath Python等核心组件,实现了Python与GPU加速的深度集成。开发者可直接用Python语法进行高性能并行计算,显著降低门槛,扩展CUDA生态,推动人工智能、科学计算等领域创新。此更新标志着CUDA向更包容的语言生态系统转型,未来还将支持Rust、Julia等语言。
197 3
CUDA重大更新:原生Python可直接编写高性能GPU程序
|
5月前
|
人工智能 Linux iOS开发
exo:22.1K Star!一个能让任何人利用日常设备构建AI集群的强大工具,组成一个虚拟GPU在多台设备上并行运行模型
exo 是一款由 exo labs 维护的开源项目,能够让你利用家中的日常设备(如 iPhone、iPad、Android、Mac 和 Linux)构建强大的 AI 集群,支持多种大模型和分布式推理。
1119 100
|
4月前
|
并行计算 PyTorch 算法框架/工具
融合AMD与NVIDIA GPU集群的MLOps:异构计算环境中的分布式训练架构实践
本文探讨了如何通过技术手段混合使用AMD与NVIDIA GPU集群以支持PyTorch分布式训练。面对CUDA与ROCm框架互操作性不足的问题,文章提出利用UCC和UCX等统一通信框架实现高效数据传输,并在异构Kubernetes集群中部署任务。通过解决轻度与强度异构环境下的挑战,如计算能力不平衡、内存容量差异及通信性能优化,文章展示了如何无需重构代码即可充分利用异构硬件资源。尽管存在RDMA验证不足、通信性能次优等局限性,但该方案为最大化GPU资源利用率、降低供应商锁定提供了可行路径。源代码已公开,供读者参考实践。
286 3
融合AMD与NVIDIA GPU集群的MLOps:异构计算环境中的分布式训练架构实践
|
5月前
|
人工智能 负载均衡 算法
DeepSeek开源周第四弹之二!EPLB:专为V3/R1设计的专家并行负载均衡器,让GPU利用率翻倍!
EPLB 是 DeepSeek 推出的专家并行负载均衡器,通过冗余专家策略和负载均衡算法,优化大规模模型训练中的 GPU 资源利用率和训练效率。
220 1
DeepSeek开源周第四弹之二!EPLB:专为V3/R1设计的专家并行负载均衡器,让GPU利用率翻倍!
|
4月前
|
存储 文件存储 对象存储
AI 场景下,函数计算 GPU 实例模型存储最佳实践
AI 场景下,函数计算 GPU 实例模型存储最佳实践
105 0
|
6月前
|
存储 文件存储 对象存储
AI 场景下,函数计算 GPU 实例模型存储最佳实践
当前,函数计算 FC 已被广泛应用在各种 AI 场景下,函数计算支持通过使用容器镜像部署 AI 推理应用,并且提供多种选项来访问训练好的模型。为了帮助开发者高效地在函数计算上部署 AI 推理应用,并快速解决不同场景下的模型存储选型问题,本文将对函数计算的 GPU 模型存储的优缺点及适用场景进行对比分析,以期为您的模型存储决策提供帮助。
|
8月前
|
人工智能 并行计算 流计算
【AI系统】GPU 架构与 CUDA 关系
本文介绍了英伟达GPU硬件基础概念,重点解析了A100 GPU架构中的GPC、TPC、SM等组件及其功能。接着深入讲解了CUDA并行计算平台和编程模型,特别是CUDA线程层次结构。最后,文章探讨了如何根据CUDA核心数量、核心频率等因素计算GPU的算力峰值,这对于评估大模型训练的算力需求至关重要。
439 3
|
10月前
|
存储 并行计算 算法
CUDA统一内存:简化GPU编程的内存管理
在GPU编程中,内存管理是关键挑战之一。NVIDIA CUDA 6.0引入了统一内存,简化了CPU与GPU之间的数据传输。统一内存允许在单个地址空间内分配可被两者访问的内存,自动迁移数据,从而简化内存管理、提高性能并增强代码可扩展性。本文将详细介绍统一内存的工作原理、优势及其使用方法,帮助开发者更高效地开发CUDA应用程序。
|
并行计算 API 数据处理
GPU(图形处理单元)因其强大的并行计算能力而备受关注。与传统的CPU相比,GPU在处理大规模数据密集型任务时具有显著的优势。
GPU(图形处理单元)因其强大的并行计算能力而备受关注。与传统的CPU相比,GPU在处理大规模数据密集型任务时具有显著的优势。

热门文章

最新文章