指南:记录与剖析 `deepspeed.comm` 全量通信调用)
DeepSpeed 通信日志Comms Logging指南记录与剖析deepspeed.comm全量通信调用【免费下载链接】DeepSpeedDeepSpeed is a deep learning optimization library that makes distributed training and inference easy, efficient, and effective.项目地址: https://gitcode.com/GitHub_Trending/de/DeepSpeed本教程面向分布式训练中需要精确量化通信开销的开发者系统讲解 DeepSpeed 通信日志Communication Logging的开启方式、verbose 逐条打印与log_summary()汇总两种查看手段以及debug与 straggler掉队效应分析等进阶能力。读完本文你将能在自己的训练脚本里通过配置与两行 Python 代码获得每个通信算子的消息大小、端到端时延、算法带宽与总线带宽统计并借助源码了解其计时与带宽换算的底层原理。通信日志能解决什么问题在大规模分布式训练中all_reduce、all_gather、reduce_scatter等集合通信collective占据了训练和推理的显著时间。判断网络是否被充分利用通常需要回答三个问题发了多少次通信、每个算子的消息多大、一次调用耗时多少、带宽利用率如何。DeepSpeed 的通信日志模块会自动检测并记录所有经由deepspeed.comm发起的通信操作解决手工插桩torch.cuda.Event繁琐且难以跨 rank 归并的问题。需要特别留意的是所有被记录的通信调用都会被同步synchronize以获得准确的计时信息。若你的模型重度依赖异步通信async_opTrue的isend/irecv/all_reduce等开启日志可能对性能产生可见影响。这一点同时被官方文档与源码中的Make op blocking for accurate logging注释所印证。工作机制与代码路径概览通信日志由 deepspeed/comm/comm.py 与 deepspeed/utils/comms_logging.py 协同实现在 comm.py 的 timed_op 装饰器中broadcast、all_gather、reduce_scatter_tensor、all_to_all_single、send/recv、barrier等函数均被timed_op包装见 comm.py 中各 API 定义计时结束后调用comms_logger.append(...)把raw_name原始算子名、log_name可附加调用方函数、时延与消息大小记录进内存comms_logging.py 的 CommsLogger 负责按算子 消息大小为粒度聚合计数与时延并在verbose开启时逐条打印同时调用 calc_bw_log 完成带宽换算。其中deepspeed.comm的集体通信与点对点pt2ptAPI 与torch.distributed完全一致可无缝替换。配置方式comms_logger字段通信日志通过 DeepSpeed 配置文件中的comms_logger字典开启整体配置模型定义见 deepspeed/comm/config.py各字段默认值在 deepspeed/comm/constants.py 中声明字段含义默认值enabled是否启用通信日志falseverbose是否在每个通信操作完成时立即打印到控制台用于细粒度调试falseprof_all是否对所有通信操作做剖析truedebug是否为每个通信操作的log_name追加调用方函数名falseprof_ops仅剖析列表中指定的通信操作此时prof_all应设为false[]推荐的全局剖析配置comms_logger: { enabled: true, verbose: false, prof_all: true, debug: false }仅剖析指定操作如只关心all_reduce与all_gather的配置见 config-json.mdcomms_logger: { enabled: true, verbose: false, prof_all: false, debug: false, prof_ops: [all_reduce, all_gather] }prof_all、prof_ops与verbose、debug的组合判断逻辑体现在 comm.py 的 timed_op当comms_logger.enabled为真时满足调用显式传profTrue、prof_allTrue、或传入的log_name命中prof_ops列表任一条件即记录该次调用。运行期间也可用 comm.py 的 configure 函数或CommsLogger的start_profiling_comms()/stop_profiling_comms()/start_profiling_op()等接口见 comms_logging.py在代码中动态切换剖析范围。verbose 模式逐条打印每次通信启用verbose: true后每次通信操作完成后会立即打印一行日志。该模式面向深入调试不推荐大多数用户日常开启因为吞吐较大且每条都伴随一次同步。示例输出[INFO] [logging.py:69:log_dist]表明经由deepspeed.utils.log_dist仅由 Rank 0 打印[2022-06-26 01:39:55,722] [INFO] [logging.py:69:log_dist] [Rank 0] rank0 | comm op: reduce_scatter_tensor | time (ms): 9.46 | msg size: 678.86 MB | algbw (Gbps): 1204.52 | busbw (Gbps): 1129.23 [2022-06-26 01:39:56,470] [INFO] [logging.py:69:log_dist] [Rank 0] rank0 | comm op: all_gather_into_tensor | time (ms): 0.11 | msg size: 6.0 MB | algbw (Gbps): 954.41 | busbw (Gbps): 894.76 [2022-06-26 01:39:56,471] [INFO] [logging.py:69:log_dist] [Rank 0] rank0 | comm op: all_gather_into_tensor | time (ms): 0.08 | msg size: 6.0 MB | algbw (Gbps): 1293.47 | busbw (Gbps): 1212.63每行包含四类指标time (ms)本次调用的端到端耗时msg size消息体大小由 convert_size 格式化为 B/KB/MB/GB 等易读单位algbw (Gbps)算法带宽algorithmic bandwidth即数据量 / 耗时busbw (Gbps)总线带宽bus bandwidth即把多 rank 间实际传输量折算进等效链路后的带宽。对高级用户开启debug: true会把每个通信操作的调用方函数名拼接到其log_name上从而区分同一算子在不同代码路径如初始化广播 vs 训练中广播中的开销来源。从源码看debug默认值由 constants.py 的get_caller_func机制在定义各 API 时即捕获一次调用栈函数名再在每次记录时通过get_debug_log_name合并到记录名中。带宽换算规则源码级calc_bw_log 按算子类型采用不同的换算公式all_to_all_singletput size/durationbusbw tput * (n-1)/nall_gather/all_gather_into_tensor/reduce_scatter/reduce_scatter_tensor先size * n还原总传输量再busbw tput * (n-1)/nall_reduce/all_reduce_coalescedtput size*2/duration双向两倍流量busbw tput * 2*(n-1)/nbroadcast、send/recv、barrier等单点通信tput busbw size/duration。其中n为dist.get_world_size()。最后统一*8 / 1e6换算为 Gbps 单位。日志汇总deepspeed.comm.log_summary()推荐verbose日志逐条刷屏难以横向比较。更推荐在训练里程碑如每个 epoch 结束、每 N 次迭代后调用deepspeed.comm.log_summary()由模块自动按通信算子 消息大小聚合输出一张高密度汇总表。添加汇总的完整步骤在 DeepSpeed 配置文件中按需修改comms_logger各字段可选若你的应用里有想被记录的torch.distributed调用把import torch.distributed as dist改为import deepspeed.comm as dist——注意deepspeed.comm的 collective 与 pt2pt API 与torch.distributed逐参数一致因此已用import torch.distributed as dist编写的既有通信调用可保持原样仍会被自动记录deepspeed.comm底层即委托给 torch backend在需要的位置调用dist.log_summary()也等价于deepspeed.comm.log_summary()。以下片段改编自 DeepSpeedExamples 的 cifar 示例model_engine为deepspeed.initialize返回的引擎对象# Step 2: (Optional) 将 import torch.distributed 替换为 deepspeed.comm import deepspeed.comm as dist # 注意任何仍使用 import torch.distributed as dist 的既有通信调用 # 可以保持不动DeepSpeed 会自动记录其通信行为 dist.all_reduce(tensor) for epoch in range(2): running_loss 0.0 for i, data in enumerate(trainloader): pre time.time() inputs, labels data[0].to(model_engine.local_rank), data[1].to( model_engine.local_rank) if fp16: inputs inputs.half() outputs model_engine(inputs) loss criterion(outputs, labels) model_engine.backward(loss) model_engine.step() post time.time() # Step 3: 每个 epoch 结束后调用 dist.log_summary() dist.log_summary()汇总表输出示例下面是在 Megatron-DeepSpeed ZeRO-3 训练 10 个迭代后调用log_summary()的截断输出行首为操作类型缩进行为该操作下不同消息大小的统计Comm. Op Message Size Count Total Latency(ms) Avg Latency(ms) tput_avg (Gbps) busbw_avg (Gbps) broadcast 2.0 KB 146 11.12 0.08 0.43 0.41 98.25 MB 1 8317.12 8317.12 0.20 0.19 reduce_scatter_tensor 678.86 MB 40 602.29 9.69 1468.06 1376.31可以看到 ZeRO-3 训练中的典型特征小尺寸broadcast权重广播被频繁调用 146 次而reduce_scatter_tensor则以数百 MB 的大消息出现这正是 ZeRO 梯度切分在通信层面的真实写照。同一配置下开启debug: true后再次调用log_summary()Comm. Op列会以| [Caller Func: xxx]后缀标注调用来源Comm. Op Message Size Count Total Latency(ms) Avg Latency(ms) tput_avg (Gbps) busbw_avg (Gbps) broadcast | [Caller Func: _broadcast_model] 2.0 KB 146 9.39 0.06 0.52 0.48 98.25 MB 1 8540.60 8540.60 0.19 0.18 reduce_scatter_tensor | [Caller Func: reduce_scatter_fn] 678.86 MB 80 1527.17 13.94 1211.75 1136.01其中[Caller Func: _broadcast_model]表明这些广播来自模型参数广播流程[Caller Func: reduce_scatter_fn]则来自 ZeRO 的梯度归约封装函数帮助定位瓶颈所在代码段。汇总统计口径源码级汇总并非简单平均log_all 在计算平均时延与带宽时使用trim_mean(..., 0.1)即去掉两端各 10% 的离群样本后再取均值避免个别抖动掩盖真实水平Total Latency则为该 (算子, 消息大小) 分组内的时延求和。数据在内存中以{record_name: {msg_size: [count, [latency...], [algbw...], [busbw...]]}}的结构累积见 append 方法支持调用get_raw_data()/get_operation_summary()/reset_data()等接口做程序化读取或清理。相关回归测试见 tests/unit/comm/test_comms_logger.py其中覆盖了trim_mean不就地排序修改原始记录、stop_profiling_comms()能真正关闭全局剖析等边界行为。以字典形式获取统计数据除打印外log_summary还支持return_dictTrue参数返回结构化统计含summary、可选的straggler_analysis与metadata便于接入自研监控或 TensorBoard 等可视化管线import deepspeed.comm as dist # 打印汇总同时获取字典供上层分析 stats dist.log_summary(show_stragglerTrue, return_dictTrue) if stats and all_reduce in stats[summary]: all_reduce_stats stats[summary][all_reduce]实现上log_summary 会先执行一次barrier(log_namelog_summary_barrier)同步所有 rank保证各进程的数据口径一致再仅由 Rank 0 打印所有 rank 都可返回字典且字典内容一致。进阶straggler effect掉队效应分析多卡集群中每个 rank 未必能同时发起同一通信慢 rank 会拖住其他 rank使其白白等待。通过向log_summary()传入可选参数show_stragglerTrue可以量化这一掉队效应。Straggler effect 定义为一个 rank 等待最慢 rank 开始通信所消耗的时间官方公式为straggler sum(t_collectives - allreduce(t_collectives, MIN))即对每个集合通信先在所有 rank 上做一次MIN归约得到该集合通信的最短最快 rank耗时再用当前 rank 的实际耗时减去该最小值并求和即为当前 rank 的相对等待量。在示例代码中启用dist.log_summary(show_stragglerTrue)输出会在原汇总表后追加Breakdown with straggler effect段落_______________________________ Breakdown with straggler effect ------------------------------- Comm. Op Message Size Count Total comm lat(ms) Total straggler(ms) Avg comm lat(ms) Avg straggler(ms)从 log_all 的 straggler 分支可看到具体实现它将各样本时延向量与自身同时放入all_reduce(..., opReduceOp.MIN)以全局最小样本为基准计算total_straggler sum(lats - min_lats)与剪枝后的均值。需要注意的是show_stragglerTrue本身会引入额外的all_reduce通信且在分布式环境下可能轻微影响性能。小结与使用建议综合以上内容给出实践建议日常训练性能分析优先使用log_summary()而非verbose并在 epoch 结束等固定里程碑调用避免日志爆炸与逐条同步对异步通信的干扰定位具体代码路径为comms_logger开启debug: true通过Caller Func列区分同一算子不同来源聚焦特定算子用prof_all: falseprof_ops: [...]只剖析关心的通信类型降低同步与开销排查负载不均对耗时异常的集合通信调用log_summary(show_stragglerTrue)判断是否由慢 rank 引发。通信日志的完整字段说明与 JSON 示例位于 DeepSpeed 配置文件手册的 Communication Logging 一节核心实现可继续查阅 deepspeed/comm/comm.py、deepspeed/utils/comms_logging.py 与对应单测 tests/unit/comm/test_comms_logger.py。【免费下载链接】DeepSpeedDeepSpeed is a deep learning optimization library that makes distributed training and inference easy, efficient, and effective.项目地址: https://gitcode.com/GitHub_Trending/de/DeepSpeed创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考