基准测试工具 - 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)[源代码]¶
用于测量 PyTorch 语句执行时间的辅助类。
有关如何使用此类的完整教程,请参阅: https://pytorch.org/tutorials/recipes/recipes/benchmark.html
PyTorch Timer 基于 timeit.Timer(实际上在内部使用了 timeit.Timer),但有几个关键区别:
- Runtime aware:
计时器将执行预热(这很重要,因为PyTorch的某些元素是延迟初始化的),设置线程池大小以便进行公平比较,并在必要时同步异步CUDA函数。
- Focus on replicates:
在测量代码时,特别是复杂的内核/模型,运行间的变化是一个重要的混杂因素。预期所有测量都应包括重复以量化噪声并允许计算中位数,这比平均值更为稳健。为此,此类在概念上将timeit.Timer.repeat和timeit.Timer.autorange合并,从而偏离了timeit API。(确切的算法在方法文档字符串中讨论。)timeit方法被复制用于不需要自适应策略的情况。
- Optional metadata:
在定义一个计时器时,可以选择性地指定标签、子标签、描述和环境。(稍后定义)这些字段包含在结果对象的表示中,并通过比较类用于分组和显示比较结果。
- Instruction counts
除了墙钟时间,Timer 还可以在 Callgrind 下运行语句并报告执行的指令数。
与timeit.Timer构造函数参数直接类似:
stmt, setup, timer, globals
PyTorch 计时器特定的构造函数参数:
标签, 子标签, 描述, 环境, 线程数
- Parameters
stmt (str) – 要在循环中运行并计时的代码片段。
设置 (str) – 可选的设置代码。用于定义在stmt中使用的变量
global_setup (str) – (仅限C++) 放置在文件顶层的代码,用于诸如#include语句。
计时器 (可调用[[], 浮点数]) – 返回当前时间的可调用对象。如果 PyTorch 是构建时没有 CUDA 或者没有 GPU 存在,这默认是 timeit.default_timer;否则它将在测量时间之前同步 CUDA。
globals (可选[字典[字符串, 任意]]) – 一个字典,定义了当stmt被执行时的全局变量。这是提供stmt所需变量的另一种方法。
标签 (可选[字符串]) – 用于总结stmt的字符串。例如,如果stmt是 “torch.nn.functional.relu(torch.add(x, 1, out=out))” 可以将其标签设置为“ReLU(x + 1)”以提高可读性。
提供补充信息以消除具有相同语句或标签的测量的歧义。例如,在我们的示例中,子标签可能是“浮点数”或“整数”,以便于区分:
“ReLU(x + 1): (浮点数)”
”ReLU(x + 1): (整数)”
当打印测量结果或使用比较进行汇总时。
用于区分具有相同标签和子标签的测量的字符串。描述的主要用途是向比较信号数据列。例如,可以根据输入大小设置它,以创建如下形式的表格:
| n=1 | n=4 | ... ------------- ... ReLU(x + 1): (float) | ... | ... | ... ReLU(x + 1): (int) | ... | ... | ...
使用比较。在打印测量时也会包含它。
env (可选[字符串]) – 此标签表示在不同环境中运行的相同任务,因此它们并不等同,例如在进行内核更改的A/B测试时。比较将在合并重复运行时将具有不同env规范的测量视为不同。
num_threads (int) – 执行 stmt 时 PyTorch 线程池的大小。单线程性能非常重要,因为它是关键推理工作负载的良好指标,也是内在算法效率的良好指标,因此默认设置为1。这与 PyTorch 默认线程池大小不同,后者试图利用所有核心。
- adaptive_autorange(threshold=0.1, *, min_run_time=0.01, max_run_time=10.0, callback=None)[源代码]¶
类似于 blocked_autorange,但还会检查测量中的变异性,并重复直到 iqr/median 小于 threshold 或达到 max_run_time。
在高层次上,adaptive_autorange 执行以下伪代码:
`setup` times = [] while times.sum < max_run_time start = timer() for _ in range(block_size): `stmt` times.append(timer() - start) enough_data = len(times)>3 and times.sum > min_run_time small_iqr=times.iqr/times.mean
- blocked_autorange(callback=None, min_run_time=0.2)[源代码]¶
在保持计时器开销最小化的同时测量多个重复样本。
在高层次上,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。块大小的选择对测量质量至关重要,并且必须在两个相互竞争的目标之间取得平衡:
较小的块大小会导致更多的副本,并且通常会得到更好的统计结果。
较大的块大小更好地分摊了计时器调用的成本,并导致测量结果的偏差较小。这一点很重要,因为CUDA同步时间并非微不足道(通常为单到低双位数的微秒级),否则会偏差测量结果。
blocked_autorange 通过运行一个预热期来设置 block_size,增加块大小直到计时器开销小于总体计算的 0.1%。然后使用此值进行主测量循环。
- Returns
一个包含测量运行时间和重复次数的测量对象,可用于计算统计数据(如均值、中位数等)。
- Return type
- collect_callgrind(number: int, *, repeats: None, collect_baseline: bool, retain_out_file: bool) CallgrindStats [源代码]¶
- collect_callgrind(number: int, *, repeats: int, collect_baseline: bool, retain_out_file: bool) Tuple[CallgrindStats, ...]
使用 Callgrind 收集指令计数。
与挂钟时间不同,指令计数是确定性的(模程序本身的非确定性和Python解释器产生的小量抖动)。这使得它们非常适合进行详细的性能分析。此方法在单独的进程中运行stmt,以便Valgrind可以对程序进行检测。由于检测,性能会严重下降,但通过少量迭代通常足以获得良好测量的这一事实得到了缓解。
为了使用此方法,必须安装 valgrind、callgrind_control 和 callgrind_annotate。
因为调用者(此进程)和stmt执行之间存在进程边界,globals不能包含任意的内存数据结构。(与计时方法不同)相反,全局变量被限制为内置函数、nn.Modules以及TorchScripted函数/模块,以减少从序列化和后续反序列化带来的意外因素。GlobalsBridge类提供了关于此主题的更多详细信息。特别要注意nn.Modules:它们依赖于pickle,您可能需要为它们添加一个导入到setup中,以确保它们能够正确传输。
默认情况下,将收集并缓存一个空语句的配置文件,以指示有多少指令来自驱动stmt的Python循环。
- Returns
一个 CallgrindStats 对象,提供指令计数以及一些用于分析和操作结果的基本功能。
- timeit(number=1000000)[源代码]¶
镜像了 timeit.Timer.timeit() 的语义。
执行主语句 (stmt) number 次。 https://docs.python.org/3/library/timeit.html#timeit.Timer.timeit
- Return type
- class torch.utils.benchmark.Measurement(number_per_run, raw_times, task_spec, metadata=None)[源代码]¶
计时器测量的结果。
此类存储给定语句的一个或多个测量结果。它是可序列化的,并为下游消费者提供了几个便利方法(包括详细的 __repr__)。
- 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)[源代码]¶
用于存储由计时器收集的Callgrind结果的顶级容器。
操作通常使用FunctionCounts类进行,该类通过调用CallgrindStats.stats(…)获得。还提供了几个便利方法;其中最重要的是CallgrindStats.as_standardized()。
- as_standardized()[源代码]¶
从函数字符串中去除库名称和一些前缀。
在比较两组不同的指令计数时,路径前缀可能是一个绊脚石。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(...)
去除前缀可以通过规范化字符串并导致在差异比较时更好地消除等效调用站点来改善这个问题。
- Return type
- class torch.utils.benchmark.FunctionCounts(_data, inclusive, truncate_rows=True, _linewidth=None)[源代码]¶
用于操作Callgrind结果的容器。
- It supports:
加法和减法来合并或区分结果。
类似元组的索引。
一个去噪函数,它会去除已知为非确定性和噪声较大的CPython调用。
两个高阶方法(filter 和 transform)用于自定义操作。