评价此页

Benchmark Utils - torch.utils.benchmark#

创建于: 2020年11月02日 | 最后更新于: 2025年06月12日

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.ac.cn/tutorials/recipes/recipes/benchmark.html

PyTorch Timer 基于 timeit.Timer(实际上是内部使用 timeit.Timer),但有几处关键区别

  1. 运行时感知

    Timer 将执行预热(这对于 PyTorch 的一些元素惰性初始化很重要),设置线程池大小以进行公平比较,并在必要时同步异步加速器函数。

  2. 注重副本

    在测量代码,特别是复杂内核/模型时,运行-运行变化是一个重要的混淆因素。预计所有测量都应包含副本以量化噪声并允许中值计算,这比均值更稳健。为此,此类不同于 timeit API,通过概念上合并 timeit.Timer.repeattimeit.Timer.autorange。 (确切算法在方法文档字符串中讨论。) timeit 方法被复制,以用于不需要自适应策略的情况。

  3. 可选元数据

    定义 Timer 时,可以选择指定 labelsub_labeldescriptionenv。(稍后定义)这些字段包含在结果对象的表示中,并且 Compare 类用于分组和显示结果以进行比较。

  4. 指令计数

    除了挂钟时间之外,Timer 还可以使用 Callgrind 运行语句并报告执行的指令数。

直接对应于 timeit.Timer 构造函数参数

stmtsetuptimerglobals

PyTorch Timer 特定的构造函数参数

labelsub_labeldescriptionenvnum_threads

参数:
  • stmt (str) – 要在循环中运行并计时的代码片段。

  • setup (str) – 可选的设置代码。用于定义 stmt 中使用的变量。

  • global_setup (str) – (仅限 C++) 放在文件顶层的代码,用于例如 #include 语句。

  • timer (Callable[[], float]) – 返回当前时间的调用对象。如果 PyTorch 的构建没有加速器或不存在加速器,则默认为 timeit.default_timer;否则,它将在测量时间之前同步加速器。

  • globals (dict[str, Any] | None) – 在执行 stmt 时定义全局变量的字典。这是提供 stmt 所需变量的另一种方法。

  • label (str | None) – 总结 stmt 的字符串。例如,如果 stmt 是 “torch.nn.functional.relu(torch.add(x, 1, out=out))”,则可以将 label 设置为 “ReLU(x + 1)” 以提高可读性。

  • sub_label (str | None) –

    提供补充信息以区分具有相同 stmt 或 label 的测量。例如,在上面的示例中,sub_label 可能是 “float” 或 “int”,以便于区分:“ReLU(x + 1): (float)”

    “ReLU(x + 1): (int)” 在打印 Measurement 或使用 Compare 进行总结时。

  • description (str | None) –

    用于区分具有相同 label 和 sub_label 的测量的字符串。 description 的主要用途是向 Compare 指示数据列。例如,可以根据输入大小设置它以创建如下表

                            | n=1 | n=4 | ...
                            ------------- ...
    ReLU(x + 1): (float)    | ... | ... | ...
    ReLU(x + 1): (int)      | ... | ... | ...
    

    使用 Compare。打印 Measurement 时也会包含它。

  • env (str | None) – 此标记指示在不同环境中运行了相同的任务,因此它们不等效,例如在 A/B 测试内核更改时。Compare 在合并副本运行时会将具有不同 env 指定的 Measurement 视为不同的。

  • num_threads (int) – 执行 stmt 时 PyTorch 线程池的大小。单线程性能很重要,因为它既是关键的推理工作负载,也是内在算法效率的良好指标,因此默认设置为一。这与默认的 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<threshold

    if enough_data and small_iqr:
        break
参数:
  • threshold (float) – 停止的 iqr/median 阈值。

  • min_run_time (float) – 检查 threshold 前所需的总运行时间。

  • max_run_time (float) – 所有测量的总运行时间,无论 threshold 如何。

返回:

一个 Measurement 对象,包含测量的运行时间和重复次数,可用于计算统计数据(均值、中值等)。

返回类型:

Measurement

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 变量。 block size 的选择对测量质量很重要,并且必须平衡两个相互竞争的目标

  1. 较小的 block size 会导致更多的副本,通常具有更好的统计数据。

  2. 较大的 block size 更好地分摊了 timer 调用的成本,并且可以减少测量偏差。这很重要,因为加速器同步时间并非微不足道(数量级为微秒到低双位数微秒),否则会偏颇测量。

blocked_autorange 通过运行预热期来设置 block_size,并增加 block size 直到计时器开销小于总计算时间的 0.1%。然后,此值用于主测量循环。

返回:

一个 Measurement 对象,包含测量的运行时间和重复次数,可用于计算统计数据(均值、中值等)。

返回类型:

Measurement

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 可以对程序进行插装。然而,由于插装而严重降低了性能,但可以通过一个事实来缓解,即少量迭代通常足以获得良好的测量结果。

要使用此方法,必须安装 valgrindcallgrind_controlcallgrind_annotate

由于调用者(此进程)和 stmt 执行之间存在进程边界,因此 globals 不能包含任意内存中的数据结构。(与计时方法不同)相反,globals 限制为内置函数、nn.Modules 和 TorchScripted 函数/模块,以减少序列化和后续反序列化的意外因素。GlobalsBridge 类提供了更多关于此主题的详细信息。要特别注意 nn.Modules:它们依赖于 pickle,您可能需要在 setup 中添加导入才能使它们正确传输。

默认情况下,将收集并缓存一个空语句的配置文件,以指示驱动 stmt 的 Python 循环产生了多少指令。

返回:

一个 CallgrindStats 对象,提供指令计数以及一些用于分析和操作结果的基本工具。

timeit(number=1000000)[源码]#

镜像 timeit.Timer.timeit() 的语义。

执行主语句(stmtnumber 次。 https://docs.pythonlang.cn/3/library/timeit.html#timeit.Timer.timeit

返回类型:

Measurement

class torch.utils.benchmark.Measurement(number_per_run, raw_times, task_spec, metadata=None)[源码]#

Timer 测量结果。

此类存储给定语句的一个或多个测量结果。它可序列化,并为下游使用者提供多个便捷方法(包括详细的 __repr__)。

static merge(measurements)[源码]#

用于合并副本的便捷方法。

Merge 将时间外推到 number_per_run=1,并且不会传输任何元数据。(因为它们可能在副本之间有所不同)

返回类型:

list[Measurement]

property significant_figures: int#

近似的有效数字估计。

此属性旨在提供一种便捷的方式来估算测量的精度。它仅使用四分位距来估算统计数据,以尽量减少尾部偏斜的影响,并使用静态 z 值 1.645,因为它不用于小的 n 值,因此 z 可以近似 t

有效数字估计与 trim_sigfig 方法结合使用,以提供更易于人类理解的数据摘要。 __repr__ 不使用此方法;它仅显示原始值。有效数字估计是为 Compare 设计的。

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)[源码]#

Timer 收集的 Callgrind 结果的顶级容器。

操作通常使用 FunctionCounts 类完成,该类通过调用 CallgrindStats.stats(…) 获取。还提供了几个便捷方法;其中最重要的是 CallgrindStats.as_standardized()

as_standardized()[源码]#

剥离函数字符串中的库名和一些前缀。

比较两组不同的指令计数时,一个绊脚石可能是路径前缀。 Callgrind 在报告函数时包含完整的文件路径(理应如此)。但是,这在 diffing 配置文件时可能会导致问题。如果两个配置文件中的一个关键组件(如 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(...)

剥离前缀可以通过正则化字符串来缓解此问题,并在 diffing 时更好地消除等效调用站点。

返回类型:

CallgrindStats

counts(*, denoise=False)[源码]#

返回执行的总指令数。

有关 denoise 参数的说明,请参阅 FunctionCounts.denoise()

返回类型:

int

delta(other, inclusive=False)[源码]#

Diff 两个计数集。

收集指令计数的一个常见原因是确定特定更改对执行单位工作所需的指令数的影响。如果更改增加了该数字,那么接下来的逻辑问题是“为什么”。这通常涉及查看代码的哪个部分增加了指令计数。此函数可以自动化此过程,以便轻松 diff 计数,无论是包含性还是排他性。

返回类型:

FunctionCounts

stats(inclusive=False)[源码]#

返回详细的函数计数。

从概念上讲,返回的 FunctionCounts 可以被视为 (count, path_and_function_name) 元组的元组。

inclusive 匹配 callgrind 的语义。如果为 True,则计数包括子节点执行的指令。 inclusive=True 对于识别代码中的热点很有用; inclusive=False 对于在 diffing 两次运行的计数时减少噪声很有用。(有关更多详细信息,请参阅 CallgrindStats.delta(…))

返回类型:

FunctionCounts

class torch.utils.benchmark.FunctionCounts(_data, inclusive, truncate_rows=True, _linewidth=None)[源码]#

用于操作 Callgrind 结果的容器。

它支持
  1. 加法和减法以组合或 diff 结果。

  2. 元组式索引。

  3. 一个 denoise 函数,用于剥离已知会产生大量噪声的 CPython 调用。

  4. 两个高阶方法(filtertransform)用于自定义操作。

denoise()[源码]#

删除已知有噪声的指令。

CPython 解释器中的许多指令相当有噪声。这些指令涉及 Unicode 到字典查找,Python 使用这些查找来映射变量名。 FunctionCounts 通常是一个与内容无关的容器,但由于获得可靠结果的原因如此重要,因此值得例外。

返回类型:

FunctionCounts

filter(filter_fn)[源码]#

仅保留 filter_fn 应用于函数名时返回 True 的元素。

返回类型:

FunctionCounts

transform(map_fn)[源码]#

map_fn 应用于所有函数名。

这可用于正则化函数名(例如,剥离文件路径的无关部分),通过将多个函数映射到同一名称来合并条目(在这种情况下,计数会相加),等等。

返回类型:

FunctionCounts

class torch.utils.benchmark.Compare(results)[源码]#

用于将许多测量结果显示在格式化表中的辅助类。

表格式基于 torch.utils.benchmark.Timer 中提供的字段信息(descriptionlabelsub_labelnum_threads 等)。

可以使用 print() 直接打印表,或将其转换为 str

有关如何使用此类,请参阅完整教程: https://pytorch.ac.cn/tutorials/recipes/recipes/benchmark.html

参数:

results (list[Measurement]) – 要显示的 Measurement 列表。

colorize(rowwise=False)[源码]#

对格式化表进行着色。

默认按列着色。

extend_results(results)[源]#

将结果追加到已存储的结果中。

所有添加的结果都必须是 Measurement 的实例。

highlight_warnings()[源]#

在构建格式化表时启用警告高亮显示。

print()[源]#

打印格式化表。

trim_significant_figures()[源]#

在构建格式化表时启用有效数字截断。