打开跟踪之前先选好一个问题
一次跟踪更能回答一个具体问题:计算在等数据吗,某个复制被重复了吗,还是某个小算子被调用得过于频繁?界定你的步骤的输入、输出和边界。对于训练,要明确它是否包含反向传播、优化器更新和读取批次。对于推理,要把模型加载和请求分开。
保留一个你知道其正确性的短场景。记录形状、批次、精度、模型模式、是否编译以及数据来源。简化输入有助于隔离某种行为,但它不再必然代表完整负载。请在记录中说明这一差异。
确定最终单位:每步毫秒数,或每秒处理的样本数。事件之和不能替代经过的时间。比较具有相同读取和传输边界的窗口。
区分 CPU 时间、GPU 工作和等待
CPU 准备并启动操作;GPU 可能稍后执行它们。因此一个 Python 区间可能包含启动、等待,或两者兼有。PyTorch 的 CUDA 文档指出,精确测量必须考虑这种异步性,尤其是通过与该范围相符的同步或事件。
对于有针对性的 GPU 时长测量,CUDA 事件可能适用。对于端到端延迟,要等到该延迟所包含的工作结束,并测量整个请求。不要把这两种单位混在一起。在每个操作之间添加同步可能会消除真实的重叠,并改变你试图理解的那个程序。
在性能分析器中,算子的自身时间和包含子操作的时间也回答不同的问题。先把时间线看清楚,再去加总表格中的各行。同时或在时间上嵌套的活动并不代表互不重叠的已用时间部分。
预留一个启动阶段和一个活动窗口
第一次运行可能包含初始化、加载或编译。确定你的问题针对的是这个启动过程还是一个已经稳定的阶段。当两者对使用都很重要时,请保留这两项观察,而不是把初始成本从一个号称全局的平均值中抹掉。
schedule 函数可以区分等待、收集准备和活动窗口。性能分析器的预热并不能证明你的模型已达到稳定状态。还要检查遇到的形状和缓存状态。对于输入可变的应用程序,在多次迭代后可能会遇到新的路径。
下面给出的教学计划会等待一步、准备一步并捕获两步。四步足以解释其机制,但不足以建立性能分布。在实际测试中,请选择有依据的窗口,并在分析之后在性能分析器之外重复测量。
建议示例:具有清晰边界的四个步骤
以下代码片段未被执行。它假定 model、optimizer、loss_fn、loader 和 device 均已存在,模型已位于 device 上,且加载器至少提供四个 x、y 批次对。它会执行更新:请使用为此准备的实验状态,而不是需要保留权重的会话。
标签将 CPU 读取、传输和训练分开。迭代器在窗口之前创建,这会将其启动的一部分排除在范围之外。文件名特意选择新的,以避免覆盖已有记录。该示例在 GPU 采集不可用时直接拒绝,而不是悄悄将 CPU 记录当作 GPU 分析呈现。
step() 信号会在每一步之后推进调度计划。官方教程描述了迭代与采集之间的这种关联。在 ROCm 平台上,PyTorch 的设备名仍然是 cuda;但加速器采集是否可用取决于构建版本及其工具。请检查结果中实际出现的活动。
from pathlib import Path
import torch
from torch.profiler import (
profile, schedule, record_function,
ProfilerActivity, supported_activities,
)
trace = Path("trace-etape.json")
if trace.exists():
raise FileExistsError("Choisissez un nouveau nom de trace")
activities = [ProfilerActivity.CPU]
if device.type == "cuda":
if ProfilerActivity.CUDA not in supported_activities():
raise RuntimeError("Collecte GPU indisponible dans ce build")
activities.append(ProfilerActivity.CUDA)
iterator = iter(loader)
model.train()
with profile(
activities=activities,
schedule=schedule(wait=1, warmup=1, active=2, repeat=1),
record_shapes=False, profile_memory=False, with_stack=False,
on_trace_ready=lambda p: p.export_chrome_trace(str(trace)),
) as prof:
for _ in range(4):
with record_function("lecture_batch_cpu"):
x, y = next(iterator)
with record_function("transfert"):
x, y = x.to(device), y.to(device)
with record_function("entrainement"):
optimizer.zero_grad(set_to_none=True)
loss = loss_fn(model(x), y)
loss.backward()
optimizer.step()
prof.step()
print(prof.key_averages().table(
sort_by="self_cpu_time_total", row_limit=8,
))将观察转化为可验证的假设
首先查看活动窗口,确认预期的标签是否出现。然后检查各活动之间的空隙、拷贝和重复。lecture_batch_cpu 中的长时间等待指向输入管道;它并不直接衡量存储。频繁拷贝提示需要检查张量的放置位置,但这并不能证明它没有必要。
先提出单一假设,然后提出一项受控修改。例如,如果某个常量在每一步都被重新构建并传输,检查它的生命周期能否在不改变结果的情况下覆盖多个步骤。如果某个算子看起来占主导,先检查它的形状和调用次数,再寻找替代方案。
以下几行是可能的解读,并非来自已执行追踪的实际结论。此处不宣称任何时长或加速。有用的结论是一个后续实验,它可以证实或推翻所提出的原因。
左右滚动表格即可查看所有列。| 可能的观察 | 假设 | 后续检查 |
|---|---|---|
| 读取期间 GPU 无活动 | 输入管道跟不上 | 在批次已准备好的情况下执行相同计算 |
| 常量的反复拷贝 | 放置位置或生命周期不当 | 移动一次,检查输出 |
| 大量小型启动 | 工作碎片化 | 检查分组与整体开销 |
| 单个耗时较长的算子 | 形状或算法起决定作用 | 比较同一算子及其输入 |
限制插桩的开销与信息量
从简短且选项较少的采集开始。仅在某个问题需要时才启用形状、调用栈或内存采集。PyTorch API 明确指出这些信息会增加开销;形状采集甚至可能保留对张量的引用。因此,详细的性能分析可能会改变程序的时长或内存占用。
跟踪记录可能包含算子名称、形状,以及根据所选选项还会包含代码路径。在分享之前请先检查。区域标签应描述步骤,不应包含邮箱、令牌、私有路径或输入内容。请选择适合您环境的跟踪查看器,并将采集置于您的控制之下。
如果预期的 GPU 事件缺失,不要用零填补它们的时间。应说明这些事件未被观测到,并检查采集支持情况。工具中没有事件并不等于没有计算的证据。
在分析器之外验证优化效果
从同一相关状态重新出发,比较修改前后的修正效果。对于训练而言,多一步就会改变权重;连续进行两次快照未必构成等价的比较。保留代码、参数和起点,以便能够解释差异。
接着在无详细采集的情况下测量该场景,使用相同的预热和多次迭代。报告测量范围、原始值及其离散程度。局部改善可能在完整循环中消失,或导致质量下降:两者都应纳入决策。
最后,利用这些观察结果来明确实际所需的资源。您本机的跟踪记录既不能对 Kernodeck 的套餐进行排序,也无法证明租用服务器的宿主处理器或网络情况。GPU 比较需要在相关资源上具备可比的负载、条件和结果。