基准测试工具 - torch.utils.benchmark¶
-
class
torch.utils.benchmark.Timer(stmt='pass', setup='pass', global_setup='', timer=<built-in function perf_counter>, globals=None, label=None, sub_label=None, description=None, env=None, num_threads=1, language=<Language.PYTHON: 0>)[source]¶ 用于测量 PyTorch 语句执行时间的辅助类。
要了解如何使用此类的完整教程,请参阅: https://pytorch.org/tutorials/recipes/recipes/benchmark.html
PyTorch 计时器基于 timeit.Timer(实际上内部使用了 timeit.Timer),但有几个关键区别:
- Runtime aware:
Timer 将执行预热(由于 PyTorch 的某些元素是惰性初始化的,因此这一点很重要),设置线程池大小以确保比较具有可比性,并在必要时同步异步 CUDA 函数。
- Focus on replicates:
当测量代码,尤其是复杂的内核/模型时,运行间的差异是一个重要的干扰因素。应确保所有测量都包括重复实验以量化噪声并允许计算中位数,这比平均值更稳健。为此,此类别在概念上合并了 timeit.Timer.repeat 和 timeit.Timer.autorange,从而偏离了 timeit API。(确切的算法在方法文档字符串中讨论。)timeit 方法用于那些不希望使用自适应策略的情况。
- Optional metadata:
当定义一个计时器时,可以选择性地指定 label, sub_label, description, 和 env。(稍后定义)这些字段包含在 结果对象的表示中,并由 Compare 类用于分组 和显示比较结果。
- Instruction counts
除了墙钟时间(wall times)之外,Timer 还可以在 Callgrind 下运行语句,并报告执行的指令数。
与 timeit.Timer 构造函数参数直接对应:
stmt, setup, timer, globals
PyTorch 定时器特定的构造函数参数:
label, sub_label, description, env, num_threads
- Parameters
stmt – 要在循环中运行并计时的代码片段。
设置 – 可选的设置代码。用于定义在 stmt 中使用的变量
global_setup – (仅限 C++) 用于文件顶层的代码,例如 #include 语句。
timer – 可调用对象,用于返回当前时间。如果 PyTorch 是在没有 CUDA 的情况下构建的,或者没有 GPU 存在,则默认为 timeit.default_timer;否则它将在测量时间之前同步 CUDA。
全局变量 – 一个字典,用于在执行 stmt 时定义全局变量。这是为 stmt 提供变量的另一种方法。
标签 – 用于总结 stmt 的字符串。例如,如果 stmt 是 “torch.nn.functional.relu(torch.add(x, 1, out=out))” 可以将标签设置为 “ReLU(x + 1)” 以提高可读性。
sub_label –
为具有相同 stmt 或 label 的测量提供补充信息以消除歧义。例如,在我们上面的示例中,sub_label 可能是“float”或“int”,这样就很容易区分: “ReLU(x + 1): (float)”
“ReLU(x + 1): (int)” 当打印测量值或使用 Compare 进行摘要时。
描述 –
用于区分具有相同标签和子标签的测量值。将 description 主要用于向 Compare 指示数据列。例如,可以根据输入大小设置它,以创建如下形式的表格:
| n=1 | n=4 | ... ------------- ... ReLU(x + 1): (float) | ... | ... | ... ReLU(x + 1): (int) | ... | ... | ...
使用 Compare。在打印 Measurement 时也包含它。
env – 此标签表示否则相同的任务在不同的环境中运行,因此不被视为等价,例如在对内核进行A/B测试时。 Compare 在合并重复运行时会将具有不同 env 规格的测量视为不同。
num_threads – 在执行 stmt 时 PyTorch 线程池的大小。单线程性能非常重要,因为它既是关键的推理工作负载,也是衡量算法内在效率的良好指标,因此默认值设置为一。这与 PyTorch 默认线程池大小试图利用所有核心的做法形成对比。
-
blocked_autorange(callback=None, min_run_time=0.2)[source]¶ 在尽量减少计时器开销的同时,测量多个重复样本。
从高层次来看,blocked_autorange 执行以下伪代码:
`setup` total_time = 0 while total_time < min_run_time start = timer() for _ in range(block_size): `stmt` total_time += (timer() - start)注意内部循环中的变量 block_size。块大小的选择对测量质量很重要,必须在两个相互竞争的目标之间取得平衡:
A small block size results in more replicates and generally better statistics.
A large block size better amortizes the cost of timer invocation, and results in a less biased measurement. This is important because CUDA syncronization time is non-trivial (order single to low double digit microseconds) and would otherwise bias the measurement.
blocked_autorange 通过运行一个预热阶段来设置 block_size, 逐步增加块大小,直到计时器开销小于整体计算的 0.1%。 然后将此值用于主测量循环。
- Returns
一个 Measurement 对象,包含测量的运行时间和重复次数,并可用于计算统计信息。 (平均值、中位数等)
-
collect_callgrind(number=100, *, repeats=None, collect_baseline=True, retain_out_file=False)[source]¶ 使用 Callgrind 收集指令计数。
与墙钟时间不同,指令计数是确定性的(除非程序本身存在不确定性以及Python解释器带来的一些微小抖动)。这使得它们非常适合进行详细的性能分析。此方法在单独的进程中运行 stmt,以便Valgrind可以对程序进行插桩。由于插桩的原因,性能会严重下降,但通常只需要少量迭代即可获得良好的测量结果,因此这一问题得到了缓解。
为了使用此方法,必须安装 valgrind、callgrind_control 和 callgrind_annotate。
由于调用者(本进程)和stmt执行之间存在进程边界,globals不能包含任意的内存数据结构。(与计时方法不同)相反,全局变量被限制为内置对象、nn.Modules以及TorchScript函数/模块,以减少序列化和随后反序列化带来的意外。关于此主题,GlobalsBridge类提供了更多细节。特别注意nn.Modules:它们依赖于pickle,并且可能需要向setup添加导入语句才能正确传输。
默认情况下,将收集并缓存一个空语句的性能分析数据,以表明有多少条指令来自驱动 stmt 的 Python 循环。
- Returns
一个 CallgrindStats 对象,它提供指令计数功能,并具备一些用于分析和操作结果的基本工具。
-
timeit(number=1000000)[source]¶ 与 timeit.Timer.timeit() 的语义相同。
执行主语句 (stmt) number 次。 https://docs.python.org/3/library/timeit.html#timeit.Timer.timeit
-
class
torch.utils.benchmark.Measurement(number_per_run, raw_times, task_spec, metadata=None)[source]¶ Timer 测量结果。
此类用于存储给定语句的一个或多个度量。它具有可序列化性,并为下游使用者提供了多种便捷方法(包括详细的 __repr__)。
-
static
merge(measurements)[source]¶ 用于合并重复项的便捷方法。
Merge 将外推时间到 number_per_run=1,并且不会传递任何元数据。(因为它们可能在重复实验之间不同)
-
property
significant_figures¶ 近似有效数字估计。
此属性旨在提供一种便捷的方法来估计测量的精度。它仅使用四分位间距区域来估算统计量,以尝试减轻尾部偏差的影响,并采用静态z值1.645,因为不期望将其用于较小的n值,因此z可以近似为t。
有效数字估计与 trim_sigfig 方法结合使用,以提供更易于人类理解的数据摘要。 __repr__ 不使用此方法;它只是显示原始值。 有效数字估计旨在用于 Compare。
-
static
-
class
torch.utils.benchmark.CallgrindStats(task_spec, number_per_run, built_with_debug_symbols, baseline_inclusive_stats, baseline_exclusive_stats, stmt_inclusive_stats, stmt_exclusive_stats, stmt_callgrind_out)[source]¶ Timer 收集的 Callgrind 结果的顶级容器。
操作通常使用 FunctionCounts 类完成,该类通过调用 CallgrindStats.stats(…) 获得。还提供了一些便捷方法;最重要的是 CallgrindStats.as_standardized()。
-
as_standardized()[source]¶ 从函数字符串中去除库名称和一些前缀。
在比较两组不同的指令计数时,路径前缀可能会成为一个障碍。Callgrind 在报告函数时会包含完整的文件路径(这是正确的做法)。然而,在对性能分析结果进行差异比较时,这可能会引发问题。如果像 Python 或 PyTorch 这样的关键组件在这两个性能分析中被构建在不同的位置,就可能导致类似以下情况:
23234231 /tmp/first_build_dir/thing.c:foo(...) 9823794 /tmp/first_build_dir/thing.c:bar(...) ... 53453 .../aten/src/Aten/...:function_that_actually_changed(...) ... -9823794 /tmp/second_build_dir/thing.c:bar(...) -23234231 /tmp/second_build_dir/thing.c:foo(...)
去除前缀可以通过规范化字符串并在进行差异比较时使等效调用站点更好地抵消,从而缓解这一问题。
-
-
class
torch.utils.benchmark.FunctionCounts(_data, inclusive, truncate_rows=True, _linewidth=None)[source]¶ 用于操作 Callgrind 结果的容器。
- It supports:
通过加法和减法组合或比较结果。
类似元组的索引。
一个 denoise 函数,用于去除已知非确定性且噪声较大的CPython调用。
两种高阶方法(filter 和 transform)用于自定义操作。