整体痕量分析简介¶
创建时间: 2024 年 1 月 2 日 |上次更新时间: 2024 年 1 月 5 日 |上次验证: Nov 05, 2024
作者: Anupam Bhatnagar
在本教程中,我们将演示如何使用 Holistic Trace Analysis (HTA) 分析来自分布式训练作业的跟踪。要开始使用,请按照以下步骤操作 下面。
安装 HTA¶
我们建议使用 Conda 环境安装 HTA。要安装 Anaconda,请参阅 Anaconda 官方文档。
使用 pip 安装 HTA:
pip install HolisticTraceAnalysis
(可选和推荐)设置 Conda 环境:
# create the environment env_name conda create -n env_name # activate the environment conda activate env_name # When you are done, deactivate the environment by running ``conda deactivate``
开始¶
启动 Jupyter 笔记本并将变量设置为跟踪的位置。trace_dir
from hta.trace_analysis import TraceAnalysis
trace_dir = "/path/to/folder/with/traces"
analyzer = TraceAnalysis(trace_dir=trace_dir)
时间划分¶
为了有效利用 GPU,了解它们的支出方式至关重要 特定作业的时间。他们主要从事计算、通信、 memory 事件,还是它们处于空闲状态?时间细分功能提供了详细的 分析在这三个类别中花费的时间。
空闲时间 - GPU 处于空闲状态。
计算时间 - GPU 用于矩阵乘法或向量运算。
非计算时间 - GPU 用于通信或内存事件。
为了实现高训练效率,代码应最大限度地利用计算时间,并且 最大限度地减少空闲时间和非计算时间。以下函数生成一个 dataframe 提供每个等级的临时使用情况的详细细分。
analyzer = TraceAnalysis(trace_dir = "/path/to/trace/folder")
time_spent_df = analyzer.get_temporal_breakdown()
当参数在 get_temporal_breakdown 函数中设置为 时,它还会生成一个条形图,表示按排名划分的划分。visualize
True
空闲时间细分¶
深入了解 GPU 空闲的时间量,以及 其背后的原因可以帮助指导优化策略。GPU 是 当没有内核运行时,被视为 idle。我们开发了一种 算法将空闲时间分为三个不同的类别:
主机等待:指 GPU 上的空闲时间,由 CPU 没有足够快地将内核排队以保持 GPU 得到充分利用。 这些类型的低效率可以通过检查 CPU 来解决 导致速度变慢的 Operator,增加 Batch 大小并应用 Operator Fusion。
内核等待:这是指与启动相关的短暂开销 连续的内核。归属于此类别的空闲时间 可以通过使用 CUDA 图形优化来最小化。
其他等待:此类别包括当前无法的空闲时间 由于信息不足而被归因。可能的原因包括 使用 CUDA 事件和 CUDA 流之间的同步和启动延迟 内核。
主机等待时间可以解释为 GPU 因 GPU 而停止的时间 添加到 CPU。要将空闲时间归因于内核等待,我们使用以下命令 启发式:
连续内核之间的差距 < 阈值
默认阈值为 30 纳秒,可以使用 参数进行配置。默认情况下,空闲时间细分为
仅针对排名 0 计算。为了计算其他等级的细分,
在 get_idle_time_breakdown 函数中使用参数。空闲时间细分可以按如下方式生成:consecutive_kernel_delay
ranks
analyzer = TraceAnalysis(trace_dir = "/path/to/trace/folder")
idle_time_df = analyzer.get_idle_time_breakdown()
该函数返回一个 DataFrame 元组。第一个 DataFrame 包含 每个排名的每个流上按类别划分的空闲时间。
当设置为 时,将生成第二个 DataFrame。它包含每个流的空闲时间的摘要统计信息
在每个等级上。show_idle_interval_stats
True
提示
默认情况下,空闲时间细分显示每个
空闲时间类别。将参数设置为 ,
该函数在 y 轴上使用绝对时间进行渲染。visualize_pctg
False
内核分解¶
内核细分功能会细分每种内核类型所花费的时间。 例如通信 (COMM)、计算 (COMP) 和内存 (MEM),跨越所有 对每个类别中花费的时间比例进行排名和显示。这是 以饼图形式显示的每个类别中逗留的时间百分比:
内核细分可以按以下方式计算:
analyzer = TraceAnalysis(trace_dir = "/path/to/trace/folder")
kernel_type_metrics_df, kernel_metrics_df = analyzer.get_gpu_kernel_breakdown()
该函数返回的第一个 DataFrame 包含用于 生成饼图。
内核持续时间分布¶
get_gpu_kernel_breakdown 返回的第二个 DataFrame 包含每个内核的持续时间摘要统计信息。特别是,此 包括 Count (计数)、 Min(最小值)、Max(最大值)、Average(平均值)、Standard Deviation(标准差)、Sum (总和) 和 Kernel 类型(kernel type) 对于每个等级上的每个内核。
使用这些数据,HTA 可以创建许多可视化来识别性能 瓶颈。
每个等级的每种内核类型的顶级内核的饼图。
每个排名靠前的所有等级的平均持续时间的条形图 kernels 和每种 kernel type 的 Kernel。
提示
所有图像均使用 plotly 生成。将鼠标悬停在图表上会显示 模式栏,允许用户缩放、平移、选择和 下载图表。
上面的饼图显示了前 5 个计算、通信和内存
内核。将为每个等级生成类似的饼图。饼图可以是
配置为使用传递的参数显示前 k 个内核
添加到get_gpu_kernel_breakdown函数。此外,该参数可用于调整
需要分析。如果 和 都是
指定,则优先。num_kernels
duration_ratio
num_kernels
duration_ratio
num_kernels
上面的条形图显示了 NCCL AllReduce 内核的平均持续时间 在所有级别中。黑线表示最小和最长时间 在每个等级上。
警告
使用 jupyter-lab 时,将 “image_renderer” 参数值设置为 “jupyterlab”,否则图形将不会在 Notebook 中呈现。
有关此功能的详细演练,请参阅 gpu_kernel_breakdown notebook 的 Notebook 中。
通信计算重叠¶
在分布式训练中,大量时间花在通信上 以及 GPU 之间的同步事件。要实现高 GPU 效率(例如 TFLOPS/GPU),保持 GPU 与计算的超额订阅至关重要 内核。换句话说,不应因未解析的数据而阻塞 GPU 依赖。一种衡量计算被 数据依赖性是计算通信计算重叠。高等 如果通信事件与计算事件重叠,则会观察到 GPU 效率。 缺乏通信和计算重叠会导致 GPU 空闲, 导致效率低下。 总而言之,更高的通信计算重叠是可取的。要计算 对于每个等级的重叠百分比,我们衡量以下比率:
(通信时计算所花费的时间)/(通信所花费的时间)
通信计算重叠可以按以下方式计算:
analyzer = TraceAnalysis(trace_dir = "/path/to/trace/folder")
overlap_df = analyzer.get_comm_comp_overlap()
该函数返回一个包含重叠百分比的数据帧 对于每个等级。
当参数设置为 True 时,get_comm_comp_overlap 函数还会生成一个条形图,表示按排名划分的重叠。visualize
增强计数器¶
内存带宽和队列长度计数器¶
内存带宽计数器测量复制时使用的内存复制带宽 通过内存复制 (memcpy) 和内存集 (memset) 从 H2D、D2H 和 D2D 获取数据 事件。HTA 还会计算每个 CUDA 上未完成操作的数量 流。我们将其称为队列长度。当流上的队列长度 为 1024 或更大,则无法在该流上安排新事件,并且 CPU 将停止,直到 GPU 流上的事件处理完毕。
generate_trace_with_counters API 会输出一个新的跟踪文件,其中包含内存带宽和队列长度
计数器。新的跟踪文件包含指示内存的 track
memcpy/memset 操作使用的带宽,并跟踪队列长度
每个流。默认情况下,这些计数器是使用秩 0 生成的
trace 文件,并且新文件的名称中包含后缀。
用户可以选择使用 API 中的参数为多个排名生成计数器。_with_counters
ranks
generate_trace_with_counters
analyzer = TraceAnalysis(trace_dir = "/path/to/trace/folder")
analyzer.generate_trace_with_counters()
带有增强计数器的生成的跟踪文件的屏幕截图。
HTA 还提供内存复制带宽和队列长度的摘要 counters 以及 profiled 部分的 counter 的时间序列 使用以下 API 的代码:
要查看摘要和时间序列,请使用:
# generate summary
mem_bw_summary = analyzer.get_memory_bw_summary()
queue_len_summary = analyzer.get_queue_length_summary()
# get time series
mem_bw_series = analyzer.get_memory_bw_time_series()
queue_len_series = analyzer.get_queue_length_series()
摘要包含计数、最小值、最大值、平均值、标准差、第 25 个、第 50 个、 和第 75 个百分位数。
时间序列仅包含值更改时的点。一旦值为 观察到的时间序列保持不变,直到下一次更新。内存 bandwidth 和 queue length 时间序列函数返回一个字典,其键 是排名,值是该排名的时间序列。默认情况下, 时间序列仅针对排名 0 进行计算。
CUDA 内核启动统计¶
对于在 GPU 上启动的每个事件,在
CPU,例如 、 、 。
这些事件通过跟踪中的通用相关 ID 链接 - 请参阅图
以上。此功能计算 CPU 运行时事件的持续时间,即其对应的 GPU
内核和启动延迟,例如 GPU 内核启动和
CPU 运算符结尾。内核启动信息可以按如下方式生成:CudaLaunchKernel
CudaMemcpyAsync
CudaMemsetAsync
analyzer = TraceAnalysis(trace_dir="/path/to/trace/dir")
kernel_info_df = analyzer.get_cuda_kernel_launch_stats()
下面给出了生成的 DataFrame 的屏幕截图。
CPU op 的持续时间、GPU 内核和启动延迟允许我们找到 以下内容:
短 GPU 内核 - 持续时间小于相应 CPU 运行时事件。
运行时事件异常值 - 持续时间过长的 CPU 运行时事件。
Launch delay outliers (启动延迟异常值) - 计划时间过长的 GPU 内核。
HTA 为上述三个类别中的每一个类别生成分布图。
短 GPU 内核
通常,CPU 端的启动时间范围为 5-20 微秒。在一些 的情况下,GPU 执行时间低于启动时间本身。图表 下面可以帮助我们找到此类实例在代码中出现的频率。
运行时事件离群值
运行时异常值取决于用于对异常值进行分类的截止值,因此
get_cuda_kernel_launch_stats API 提供用于配置值的参数。runtime_cutoff
启动延迟异常值
启动延迟异常值取决于用于对异常值进行分类的截止值。
因此,get_cuda_kernel_launch_stats API 提供了用于配置值的参数。launch_delay_cutoff
结论¶
在本教程中,您学习了如何安装和使用 HTA、 一种性能工具,使您能够分析分布式 训练工作流。了解如何使用 HTA 工具执行跟踪 diff 分析,请参阅使用 Holistic Trace Analysis 进行 Trace Diff。