已关闭
[RFC]: MULTI_STREAM_MEMORY_REUSE默认值修改为2 #4195
zzzzzzk创建于 8月19日关闭于 8月27日
8月19日 添加了label:triage-review
TorchNPU-Bot
8月19日 评论:
8月19日 评论:
issue待分派,添加triage-review标签


8月19日 添加了label:rfc
此处折叠了32条事件消息 查看更多
9月24日 issue状态由 TODO 改变为 DONE
issue待分派,添加triage-review标签


状态(Status): Draft / Reviewing / Approved / Rejected / Superseded
作者(Authors): @Your_community
创建日期(Created): YYYY-MM-DD
更新日期(Updated): YYYY-MM-DD
相关 Issue/PR: #123(关联 Issue/PR 以便追踪背景)
1. 概述
简介
本提案通过 MULTI_STREAM_MEMORY_REUSE 控制计算流与 HCCL 通信流之间的内存复用策略,解决集合通信相关内存释放不及时、内存峰值偏高的问题。核心价值是在保证通信正确和内存安全的前提下,提高设备内存利用率,支持更大模型或更大 Batch Size 的分布式训练。
动机
在大模型分布式训练中,计算和通信通常在不同流上并行执行。为保证通信期间 Tensor 不被提前释放,系统需要记录跨流依赖,但这也会导致部分已经使用完的内存不能及时返回内存池。当通信频繁或任务下发较快时,内存会持续滞留,增加峰值内存,甚至引发 OOM。
因此,需要在确认通信安全完成后及时解除不再需要的内存依赖,同时覆盖 Tensor 提前释放、兼容性回退等场景。不实施本提案,用户可能需要降低 Batch Size、缩小模型规模或增加设备资源,从而影响训练效率和资源利用率。
目标
在保证通信正确性和内存安全的前提下,提高多流内存复用率。
及时回收集合通信使用完毕的内存,降低训练峰值内存。
无需修改训练代码,通过环境变量即可控制和定位问题。
减少内存复用event的创建和使用
2. 用例分析
1.4 用例分析
本提案主要面向以下场景:
all_reduce等异步通信,并能够在使用通信结果前明确调用work.wait()。recordStream可能造成内存延迟回收。Mode 2 在通信同步后释放强引用,使内存返回内存池。3. 方案设计
3.1 总体方案
Mode 2 面向 TorchNPU 分布式集合通信场景,采用“强引用托管 Tensor 生命周期”的设计:通信开始时不执行
recordStream,而是由通信任务保存输入、输出 Tensor 的强引用;达到安全同步点后释放引用,使内存可以返回缓存内存池。整体设计涉及以下模块:
MULTI_STREAM_MEMORY_REUSE=2。TensorShelf保存 Tensor 强引用。wait()时,在检测到通信完成后触发延迟清理。核心流程如下:
sequenceDiagram participant U as 用户程序 participant PG as ProcessGroupHCCL participant W as WorkHCCL/TensorShelf participant H as HCCL通信流 participant A as NPU缓存分配器 U->>PG: 发起异步集合通信 PG->>PG: 检查 Mode 2 PG->>W: 保存通信Tensor强引用 PG->>H: 下发通信任务 PG-->>U: 返回Work对象 alt 用户先调用 work.wait() U->>W: work.wait() W->>H: 当前计算流等待HCCL结束事件 W->>W: 释放Tensor强引用 U->>A: 释放Tensor alt Tensor无其他引用 A-->>A: 内存返回缓存池 else Tensor仍有其他引用 A-->>U: 等待其他引用释放 end else Tensor在wait前释放 U->>U: 释放Tensor W-->>W: Work中的强引用继续保持Tensor存活 Note over W,H: HCCL通信期间内存不会被提前回收 U->>W: work.wait() W->>H: 当前计算流等待HCCL结束事件 W->>W: 释放Tensor强引用 A-->>A: Tensor无其他引用,内存返回缓存池 end如果用户未调用
work.wait(),Watchdog 检测到通信完成后,会将待释放的TensorShelf交给主线程,并在后续调用 ProcessGroupHCCL 接口时完成清理。因此,推荐用户及时调用wait(),以获得更及时、可预测的内存释放效果。限制和约束:
work.wait()。recordStream机制。wait()调用较晚,可能增加而不是降低峰值内存。mode0、mode1和mode2的区别
mode2从mode0和mode1演化而来,mode0不感知work.wait()中插入的流间依赖,总是会插入event,保证内存回收在集合通信完成之后,核心流程如下:
sequenceDiagram participant U as 用户程序 participant PG as ProcessGroupHCCL participant W as WorkHCCL participant H as HCCL通信流 participant A as NPU缓存分配器 U->>PG: 发起异步集合通信 PG->>PG: 检查 Mode 0 PG->>A: recordStream记录HCCL通信流 PG->>H: 下发通信任务 PG-->>U: 返回Work对象 opt 用户调用 work.wait() U->>W: work.wait() W->>H: 当前计算流等待HCCL结束事件 Note over W,A: 仅建立流间同步<br/>不执行eraseStream end U->>A: Tensor释放 A->>A: 检查block->stream_uses A->>H: 记录回收事件 A->>A: 将Block加入待处理事件队列 U->>A: 后续申请新的NPU内存 A->>A: malloc()调用process_events() A->>H: 查询回收事件是否完成 alt 回收事件已完成 A->>A: event_count减一 A->>A: 从待处理队列移除Event A-->>U: Block返回缓存池并可复用 else 回收事件未完成 A-->>A: Block继续保留,暂不可复用 endmode1 在mode0的基础上增加了work.wait()对流同步的感知,一旦用户调用了work.wait(),流间依赖得到保证,那么就会调用erase_stream(),这样在tensor释放的时候不回去创建event,核心流程如下:
sequenceDiagram participant U as 用户程序 participant PG as ProcessGroupHCCL participant W as WorkHCCL participant H as HCCL通信流 participant A as NPU缓存分配器 U->>PG: 发起异步集合通信 PG->>PG: 检查 Mode 1 PG->>A: recordStream记录HCCL通信流 PG->>W: 保存Tensor弱引用 PG->>H: 下发通信任务 PG-->>U: 返回Work对象 alt 用户先调用 work.wait() U->>W: work.wait() W->>H: 当前计算流等待HCCL结束事件 W->>A: eraseStream清除跨流标记 U->>A: 释放Tensor A-->>A: 内存返回缓存池 else Tensor在wait前释放 U->>A: Tensor析构,触发内存释放 A->>A: 检查block->stream_uses A->>H: 记录回收事件 A->>A: 将Block加入待处理事件队列 U->>A: 后续申请新的NPU内存 A->>A: malloc()调用process_events() A->>H: 查询回收事件是否完成 alt HCCL通信已完成 A->>A: event_count减一 A-->>U: Block返回缓存池并可复用 else HCCL通信未完成 A-->>A: Block继续保留,暂不可复用 end end3.2 技术选型
本提案选择“避免
recordStream,由 WorkHCCL 持有 Tensor 强引用”的方案。其他方案对比如下:recordStream;实现简单;不需要访问分配器内部 BlockMode 2 的主要优势是不需要修改缓存分配器的 Block 管理逻辑,能够以较小的实现复杂度保证通信期间的内存安全。
3.3 功能与性能设计
功能实现包括:
2解析为AVOID_RECORD_STREAM。recordStream。TensorShelf。work.wait()后,先建立计算流对通信流的事件等待关系,再清空TensorShelf。wait(),由 Watchdog 在通信完成后登记待清理对象,并由后续主线程调用完成释放。wait(),因此回退到常规内存保护机制。影响范围:
ProcessGroupHCCL集合通信处理流程。WorkHCCL生命周期管理。flowchart TD A[异步通信提交<br/>TensorShelf保存强引用] --> B[Work加入Watchdog监控列表] B --> C{HCCL通信完成?} C -->|否| B C -->|是| D[Watchdog转移TensorShelf<br/>并删除已完成Work] D --> E[下一次主线程提交通信任务] E --> F[调用unstash<br/>清空TensorShelf] F --> G[释放Tensor强引用] G --> H[无其他引用时<br/>内存进入NPU缓存分配器]性能设计:
recordStream及相关事件管理开销,使通信同步后的内存释放路径更加直接。TensorShelf加锁操作。work.wait()。3.4 安全隐私与DFX设计
安全与可靠性:
wait()引发越界访问或数据错误。3.5 编程与调用设计
Mode 2 不新增 Python 或 C++ API。开发者通过环境变量启用功能,并继续使用现有
torch.distributed编程模型。3.5.1 编程模型基本设计
开发环境设计:明确好开发者使用的软/硬件环境、开发&调试工具链、编程框架、要提供的加速库或算子等。
开发约束:开发者使用过程中的约束和限制说明,如硬件平台、编程语言限制等。
可验收设计:提供相应功能、性能指标等的验收环境、标准或用例设计,保证最终的实现可达成既定目标。
3.5.2 接口定义与设计
本提案不新增编程 API,仅提供环境变量配置接口。
3.5.2.1 MULTI_STREAM_MEMORY_REUSE
接口描述:配置 TorchNPU 多流内存复用策略;取值
2时启用强引用托管方案。接口原型:
export MULTI_STREAM_MEMORY_REUSE=2输入参数:
MULTI_STREAM_MEMORY_REUSE0、1、2、3;本提案使用2返回参数:无。
异常处理:设置为有效范围之外的数值时,初始化或首次读取配置时抛出参数错误。
约束说明:
变更说明:不新增或修改
torch.distributedAPI,仅增加对应的内部内存管理策略。调用参考:
export MULTI_STREAM_MEMORY_REUSE=2 torchrun --nproc_per_node=8 train.py3.5.3 编程手册设计
为了帮助开发者能快速上手开发,要设计好本提案相关特性/功能的《编程手册》,要包含哪些内容和章节,单独输出还是共用,在已有的手册中更新还是输出等。确保最后输出的《编程手册》中有相关变更内容。
4. 测试设计
4.1 端到端测试
端到端测试应选择具有代表性的分布式训练任务,例如 DDP 大模型训练。
选择的模型:deepseek_16b
开启:DP =2,FSDP=2,TP=2,EP=2,确保all_reduce, all_gather, reduce_scatter, all_to_all都能运行到
测试步骤:
验收内容:
wait()后,通信 Tensor 不再被无期限持有。5. 缺点和风险
work.wait(),相关内存将长期无法回收,峰值内存可能上升。wait(),直接使用 Mode 2 存在提前释放或长期持有风险。wait(),强引用可能长时间保留。此外该需求依赖开启watchdog6. 现有技术
本提案主要参考 PyTorch
ProcessGroupNCCL的多流内存管理机制。在 CUDA 多流场景中,PyTorch 通常使用
record_stream防止 Tensor 内存在异步任务完成前被复用。该机制安全性较高,但会增加事件查询开销,并使内存复用时机不够可预测。PyTorch 官方也指出:当框架能够管理 Tensor 完整生命周期和流间同步时,可以通过显式同步替代record_stream。PyTorch record_stream 文档ProcessGroupNCCL采用了与 Mode 2 类似的方案:不执行recordStream,而是通过TensorShelf保存通信 Tensor 的强引用;在用户计算流等待 NCCL 通信结束事件后,再释放这些引用,从而兼顾跨流安全和内存复用。当前 PyTorch 主线已将该行为设为默认,并将TORCH_NCCL_AVOID_RECORD_STREAMS标记为废弃配置。ProcessGroupNCCL 设计、ProcessGroupNCCL 实现7. 未解决问题
待社区讨论/决策的开放问题,如硬件适配范围、参数默认值等(需在RFC通过前解决)。
附录
欢迎加入社区,感谢您对社区的贡献 🎉!