跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
PythonAI算法

深度优化 TorchRec:提升 PyTorch 推荐系统性能

针对 PyTorch 推荐系统库 TorchRec 的深度优化方案。通过在 16 个 DGX H100 节点上针对 MLPerf DLRM 基准进行测试,实现了 2.25 倍的加速比,将单次迭代耗时从 7.6 毫秒降低至 3.4 毫秒。优化工作主要涵盖 CPU 启动延迟和 Input Dist 两个方向,具体包括利用 CUDA Graph 减少 Kernel 启动开销、采用多线程 Kernel 启动解决单线程阻塞问题、使用 Pinned Memory 优化 D2H 传输效率、通过环境变量 TORCH_NCCL_AVOID_RECORD_STREAMS 消除 NCCL 轮询开销,以及通过重计算和批大小复用减少 Input Dist 阶段的通信次数。这些优化在不破坏原有 API 和架构的前提下,有效隐藏了流水线延迟,显著提升了大规模推荐系统的训练性能。

星星泡饭发布于 2025/2/7更新于 2026/10/26101 浏览
深度优化 TorchRec:提升 PyTorch 推荐系统性能

深度优化 TorchRec:提升 PyTorch 推荐系统性能

本文深入分享了针对 PyTorch 官方推荐系统库 TorchRec 的性能优化工作。TorchRec 是 PyTorch 生态中用于构建大规模推荐系统的核心组件,支持在 PyTorch 框架上进行高效的 Embedding 训练与管理。本次优化的目标非常明确:首先,针对 MLPerf 基准测试中的 DLRM 模型进行专项优化,该场景涉及 16 个 DGX 节点,对扩容性要求极高;其次,确保优化过程不破坏 TorchRec 现有的 API 接口,保持向后兼容性;再次,尽量不改动其底层架构,倾向于在高层级进行性能调优;最后,部分优化如 CUDA Graph 的使用,对模型结构本身有一定约束条件。

TorchRec 整体架构解析

TorchRec 的整体架构设计清晰,主要包含三层结构。最上层是 TorchRec 的 API 层,提供简单易用的封装接口,用户可以利用这些 API 快速配置不同的 Embedding 策略,实现数据分片(Sharding)以及在训练过程中构建流水线(Pipeline)。中间一层是 TorchRec 内部的 Module 层,包含了具体的 Sharding 实现逻辑,将 Embedding 功能划分为 BaseSparseFeatureDist、BaseEmbeddingDist 和 BaseEmbeddingLookup 三个核心部分。这一层主要由 Python 代码构成,基于 PyTorch 的 nn.Module 机制实现。最底层则是 FBGM(Facebook Graph Machine Learning),这是一个 C++ 层面的库,其中包含了在 GPU 上高效实现的稀疏相关算子,为上层提供了高性能的计算基础。

典型使用流程

用户使用 TorchRec 通常遵循三个标准步骤。第一步是使用 EmbeddingBagConfig 和 EmbeddingBagCollection 这两个核心 API 来构建 Embedding 配置,定义特征与 Embedding 表的映射关系。第二步,使用 DistributedModelParallel 完成模型并行(Model Parallel)的分片处理。TorchRec 默认会在 Embedding 层面进行模型并行,将巨大的 Embedding 表切分到每一个 GPU 上,同时也提供了定制化开发的接口,允许用户对特定部分进行精细化的模型并行控制。第三步,使用 TrainPipelineSparseDist API 构建一个完整的训练流水线。在这个流水线中,可以进行流水线调度以及预取(Prefetch)操作,从而显著提升整体性能。由于我们的优化目标是高层级的性能提升,因此重点主要集中在 TrainPipelineSparseDist API 的实现细节中。

优化成果展示

经过一系列优化措施后,我们在 MLPerf DLRM-DCNv2 基准测试中取得了显著成果。测试环境为 16 个 DGX H100 节点。数据显示,优化前 TorchRec 每个迭代(Iteration)耗时约为 7.6 毫秒,优化后降至 3.4 毫秒左右,实现了 2.25 倍的加速比。当前 MLPerf 的世界纪录为 2.3 毫秒,我们的优化方案已非常接近这一极限水平。接下来将详细拆解这一优化方案的具体实施路径。

训练时间线分解

为了定位性能瓶颈,我们将 TorchRec 构建出的 DLRM 整体训练时间线进行了详细分解,总共包括五个关键部分。第一部分为 Input Feature Dist(输入特征分发)。当输入特征从数据库或硬盘读取到 GPU 显存后,需要将其从数据并行(Data Parallel)模式转换为模型并行(Model Parallel)模式,以便后续进行 Embedding 处理。第二部分是在获得 Model Parallel 的特征之后,执行 Embedding Forward。这一步在当前 GPU 上进行 Embedding 表的查询,并执行 Embedding All-to-All 通信。经过 All-to-All 操作后,每个 GPU 能够获取到数据并行的 Embedding 向量,方便后续进行数据并行的 MLP 计算。第三部分是 MLP 的前向传播(Forward)、反向传播(Backward)以及梯度 Allreduce 操作,包括底部 MLP(BMLP)和顶部 MLP(TMLP)。第四部分在完成 MLP 部分的计算后,需要进行 Embedding Backward,这是 Embedding Forward 的反向过程,即先执行 All-to-All 通信,再进行反向传播,最后更新 Embedding 表。最后是第五部分,MLP 的参数更新。这五个部分之间存在严格的依赖关系,②/③/④/⑤在开启流水线后可以顺序执行,它们之间相互依赖,必须按序运行。但是①与其他部分没有直接依赖关系,因此①可以独立进入流水线,例如当前迭代的①可以与下一个迭代的②/③/④重叠执行,从而实现流水线的隐藏延迟效果。

核心优化方案详解

本次优化工作主要总结为两大方向:一是优化 CPU 启动延迟(CPU Launch Latency),二是优化 Input Dist 部分,去除不必要的中间操作。

1. CUDA Graph 降低 CPU 开销

我们使用了 CUDA Graph 技术将 MLP 和 Allreduce 部分的操作捕获下来。通过创建 CUDA Graph Object,我们可以捕获中间的 Kernel 启动过程。在后续执行时,无需再经历 CPU 的调用序列,可以直接调用 CUDA Graph 对象执行,从而节省了大量的 CPU 时间。使用 CUDA Graph 有一个关键前提,就是必须保证 CUDA Graph 的内存地址不能发生变化。对于 TorchRec 而言,Embedding 部分(包括输入特征分发、Embedding Forward、Embedding Backward)都依赖于动态变化的 Embedding 输入特征数量,因此无法直接应用 CUDA Graph。但对于 MLP 计算部分(即上述时间线中的③),由于其计算图相对固定,是可以对其进行 CUDA Graph 捕获的。这是第一个关键的优化点。

2. 多线程 Kernel 启动解决阻塞

TorchRec 原生实现在同一时间只使用一个线程来启动 Kernel。Kernel 启动的顺序通常是②③①④⑤。之所以采用这样的顺序,是因为在①阶段中有 3 次 All-to-All 通信操作。我们希望尽早启动①,这样可以让①中的三次通信与③中的计算更好地重叠。然而,这种顺序导致了一个问题:①是在中间阶段启动的,但①中包含的两次 D2H(Device to Host)加同步操作会导致 CPU 等待 GPU 完成数据传输。在这个过程中,由于它是单线程启动,CPU 的时间会被浪费,进而导致④和⑤的 Kernel 启动被阻塞。我们的优化方案是采用一个单独的线程去启动①。这样,即使它在单独线程上因 D2H+Sync 而阻塞,也不会影响 TorchRec 主线程上的④和⑤的 Kernel 启动。这样可以充分利用 D2H+Sync 的时间窗口来并发启动其他 Kernel,最大化硬件利用率。

3. Pinned Memory 优化 D2H Copy

在 PyTorch 本身的实现中,默认使用 Tensor API 进行 D2H 传输。在这个 API 中,默认使用的内存页不是常驻在虚拟内存上面的(Unpinned),这会导致在 CPU 层面产生额外的性能开销,因为操作系统需要频繁地进行页面交换。我们的优化是不直接使用默认的 to_list API,而是手动创建一份 Host 上的 Tensor,这个 Tensor 使用 Pinned Memory(固定内存)。然后先完成 Pinned Memory 的拷贝操作,之后再转换为 List。Pinned Memory 允许 DMA(Direct Memory Access)设备直接访问,减少了 CPU 介入和数据搬运的开销,从而显著减少 CPU 层面的性能损耗。

4. 环境变量减少 NCCL 开销

我们使用了 TORCH_NCCL_AVOID_RECORD_STREAMS 这个环境变量来减少 CPU 上的额外开销。具体使用场景是当 PyTorch 使用多个 Stream 时需要保证内存安全性。例如,如果在当前 Stream 创建了一个张量 a,将其放入 Stream s 中使用,然后删除 a,这时就会引入竞态条件(Race Condition)。因为删除 a 时,它可能正在被 s 异步使用,这会产生安全隐患。PyTorch 本身提供了一个 API 叫做 record_stream,用于保证删除 a 时去做轮询,确保内存只有在被使用完之后才会被释放。虽然 record_stream 提供了安全性保证,但它的工作状态必须不断地去轮询,这会造成一定的性能开销。因此,对于某些安全但并不高效的场景,我们寻求替代方案。另一种方式是当使用 a 时,不要在 s 里面做 record_stream,相反我们做 Stream 之间的同步。在使用完 s 后,使用当前的 Stream 等待 s 完成,就可以保证 delete a 操作不会在 s 被使用结束之前去执行。这样避免了使用 record_stream 时的轮询开销,性能更好。PyTorch 本身底层的 NCCL 库不知道用户的具体使用方式,所以默认进行 record_stream,这会引入性能损失。如果我们像场景二那样使用了 Stream Synchronize,就可以通过将 TORCH_NCCL_AVOID_RECORD_STREAMS 设为 1,去掉默认的 record_stream,并且对内存安全性没有任何负面影响。

5. 优化 Input Dist 减少通信

最后一个优化是去掉了①阶段中的一些中间操作,具体来说就是 Input All-to-All 和 D2H+Sync。在①阶段中有 3 个 All-to-All 操作,分别是 a2a0、a2a1 和 a2a2。其中,a2a0 用来传输 a2a1 和 a2a2 传输需要的元数据。我们发现,这些元数据并不是必须要通过网络通信来传输的,而是可以在本地通过计算得到的。因此,我们可以通过重计算(Recomputation)的方式,把 3 次 All-to-All 合并成 2 次,节省一次昂贵的网络通信。此外,我们发现 a2a0 传递的信息,只会因为每个 GPU 上的 Batch Size 变化而产生改变。如果 Batch Size 没有变化,就没有必要重复做 a2a0 的同步,结果是不会改变的。因此,我们的优化策略是把 a2a0 和相关的 D2H+Sync 去掉。当 Batch Size 没有变化时,尝试复用上一个 Iteration 的结果,只有在 Batch Size 发生变化的特殊情况下,才会重新执行这个 a2a0 操作。在训练的大部分时间内,Batch Size 都是固定的,因此可以从中获得持续的性能收益。

性能指标分析

最后展示一下各项优化带来的具体性能结果。我们挑选了 3 个对性能影响最大的优化项进行对比展示,包括 CUDA Graph、去掉一次 All-to-All 通信,以及多线程的 Kernel 启动。这里展示了 5 个关键指标,包括 GPU 上每一个 Iteration 的训练时间,即 CPU 上 Iteration 从开始到结束的时间,去掉了 CPU 同步 GPU 的时间,纯粹反映 CPU 上的时间开销。然后是 Exposed Input Dist,用来描述 Input Dist Prefetch 暴露给 CPU 的时间,最好的情况是可以完全隐藏掉这部分开销。将这些优化拆开来看,所有的优化大致上都可以减少在 CPU 上的 Overhead,同时能够减少在 GPU 端对端的训练总时间。最主要的收益来自于 Input Dist 部分的优化,我们的改进使 Input Dist 部分被更好地隐藏在流水线之下,减少了 CPU 等待时间。

总结与适用性

这些优化主要针对 Embedding 本身的一些通用特性,可适用于大部分推荐系统场景。CUDA Graph 部分与网络结构有关,它不会依赖动态的输入,如果是像 Transformer 等依赖动态输入的情况,则需要使用其它方法去做优化。总体而言,通过上述多层面的系统级优化,我们成功在保持 TorchRec 原有架构稳定性的前提下,显著提升了大规模推荐系统的训练效率,为工业界落地提供了有力的性能保障。

目录

  1. 深度优化 TorchRec:提升 PyTorch 推荐系统性能
  2. TorchRec 整体架构解析
  3. 典型使用流程
  4. 优化成果展示
  5. 训练时间线分解
  6. 核心优化方案详解
  7. 1. CUDA Graph 降低 CPU 开销
  8. 2. 多线程 Kernel 启动解决阻塞
  9. 3. Pinned Memory 优化 D2H Copy
  10. 4. 环境变量减少 NCCL 开销
  11. 5. 优化 Input Dist 减少通信
  12. 性能指标分析
  13. 总结与适用性

更多推荐文章

查看全部
  • ChatGPT 结构化 Prompt 高级应用指南
  • VSCode GitHub Copilot 插件无法加载模型解决方案
  • Python 基础语法精讲(一):常量、变量与运算符
  • 基于 Vue 和 Python 的摄影作品分享系统设计与实现
  • 人工智能常见面试题及答案汇总
  • Cursor 集成 MCP 服务使用指南
  • Meta DINOv3 视觉基础模型:下载与使用指南
  • Raphael AI:用 Flux 模型驱动的免费图片生成器,无需注册无限制
  • Testsigma 开源自动化测试平台新手入门指南
  • 宇树 G1 机器人 ROS1 建图与定位配置实战
  • C++ 图论:三种经典最短路径算法解析
  • Stable Diffusion 提示词高阶用法实战:从精准控制到风格迁移
  • 云开发 Copilot:AI 如何重塑开发流程
  • 2026 年 AI 领域十大热点话题深度解析
  • 具身智能与视觉:机器人如何“看懂”世界?
  • 机器人数值优化基础(一)从理论到实战
  • 3DMAX VR渲染器局部渲染设置教程
  • llama.cpp 大模型部署指南:CPU/GPU 兼容与 Docker 快速启动
  • GitHub 十大必装 Claude Skill 推荐
  • AI评估建议可信度:破解决策迷局

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online