• Tutorials >
  • Inductor CPU backend debugging and profiling
Shortcuts

电感器CPU后端调试和分析

创建于:2023年7月1日 | 最后更新:2024年7月23日 | 最后验证:2024年11月5日

作者: Xuan Liao, Haozhe Zhu, Jiong Gong, Weihan Wang

概述

PyTorch 2.0 引入了一个名为 torch.compile 的编译 API。 这个新功能通过默认的 Inductor 后端进行图级优化,相比急切模式执行提供了显著的加速。

本教程旨在通过深入探讨torch.compile的复杂性,提供关于Inductor CPU后端调试和性能分析的深入介绍。

同时,您还可以找到关于torch.compile的相关教程,包括基本用法、全面的故障排除以及如GPU性能分析等GPU特定知识。

我们将从一个触发编译问题和准确性问题的激励性示例开始调试,通过展示调试过程来精确定位问题。

通过启用日志记录并探索底层生成的代码,您可以学习如何逐步缩小故障范围,最终找出根本原因。

接下来,我们将继续讨论如何对编译后的代码进行分析,并通过与即时模式的性能比较,详细说明为什么torch.compile相比即时模式能够提供额外的性能提升。

调试

这里是一个简单的例子,使用Inductor运行torch.compile并将其结果与eager模式进行比较:

import torch

def foo1(x1, x2):
    a = torch.neg(x1)
    b = torch.maximum(x2, a)
    y = torch.cat([b], dim=0)
    return y

x1 = torch.randint(256, (1, 8), dtype=torch.uint8)
x2 = torch.randint(256, (8390, 8), dtype=torch.uint8)

compiled_foo1 = torch.compile(foo1)
result = compiled_foo1(x1, x2)
/usr/local/lib/python3.10/dist-packages/onnxscript/converter.py:820: FutureWarning:

'onnxscript.values.Op.param_schemas' is deprecated in version 0.1 and will be removed in the future. Please use '.op_signature' instead.

/usr/local/lib/python3.10/dist-packages/onnxscript/converter.py:820: FutureWarning:

'onnxscript.values.OnnxFunction.param_schemas' is deprecated in version 0.1 and will be removed in the future. Please use '.op_signature' instead.

cpp代码生成中,neg的正确实现如下:

def neg1(x):
    return f"decltype({x})(-{x})"

为了演示调试过程,我们稍后将修改函数为一个错误的版本。

获取更多日志信息

默认情况下,运行这个简单的示例不会提供调试信息。为了获取更多有用的调试和日志信息,我们通常会添加一个TORCH_COMPILE_DEBUG环境变量,如下所示:

TORCH_COMPILE_DEBUG=1 python xx.py

这将在输出日志中打印更多的调试信息,并且还会转储在代码生成过程中生成的中间IR。您可以在日志中找到转储的文件路径,如下所示:

torch._inductor.debug: [WARNING] model___20 debug trace: /tmp/torchinductor_root/rx/crxfi2ybd7yp5sbj2pnhw33wfhtdw7wumvrobyp5sjvdui5ktjc2.debug

在此目录中,以下文件被保存用于调试目的:

文件

描述

fx_graph_runnable.py

可执行的FX图,分解后,模式匹配前

fx_graph_transformed.py

转换后的FX图,经过模式匹配后

ir_pre_fusion.txt

融合前的电感器IR

ir_post_fusion.txt

融合后的电感IR

output_code.py

生成的用于图形的Python代码,带有C++/Triton内核

请注意,fx_graph_runnable.pyoutput_code.py 都是可运行和可编辑的,以便于调试。 以下是文件中提取的主要代码部分,我们将生成的 C++ 代码行与 FX 代码行进行关联。

fx_graph_runnable:

def forward1(self, arg0_1, arg1_1):
    neg = torch.ops.aten.neg.default(arg0_1);  arg0_1 = None
    maximum = torch.ops.aten.maximum.default(arg1_1, neg);  arg1_1 = neg = None
    clone = torch.ops.aten.clone.default(maximum);  maximum = None
    return (clone,)

C++ 内核在 output_code 中:

import torch
from torch._inductor.async_compile import AsyncCompile
async_compile = AsyncCompile()

cpp_fused_cat_maximum_neg_0 = async_compile.cpp('''
#include "/tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h"
extern "C" void kernel(const unsigned char* in_ptr0,
                       const unsigned char* in_ptr1,
                       unsigned char* out_ptr0)
{
    {
        #pragma GCC ivdep
        for(long i0=static_cast<long>(0L); i0<static_cast<long>(8390L); i0+=static_cast<long>(1L))
        {
            #pragma GCC ivdep
            for(long i1=static_cast<long>(0L); i1<static_cast<long>(8L); i1+=static_cast<long>(1L))
            {
                auto tmp0 = in_ptr0[static_cast<long>(i1 + (8L*i0))];
                auto tmp1 = in_ptr1[static_cast<long>(i1)];
                // Corresponding FX code line: neg = torch.ops.aten.neg.default(arg0_1);  arg0_1 = None
                auto tmp2 = decltype(tmp1)(-tmp1);
                // Corresponding FX code line: maximum = torch.ops.aten.maximum.default(arg1_1, neg);  arg1_1 = neg = None
                auto tmp3 = max_propagate_nan(tmp0, tmp2);
                // Corresponding FX code line: clone = torch.ops.aten.clone.default(maximum);  maximum = None
                out_ptr0[static_cast<long>(i1 + (8L*i0))] = tmp3;
            }
        }
    }
}''')

确定错误的组成部分

当遇到错误或准确性问题时,一个直接的解决方案是缩小问题范围。首先要做的是确定错误发生的组件。幸运的是,通过更改torch.compile的后端可以简单地实现这一点。

代码

描述

torch.compile(fn, backend="eager")

启用Dynamo

torch.compile(fn, backend="aot_eager")

启用 Dynamo + AOT Autograd

torch.compile(fn, backend="inductor")

启用Dynamo + AOT自动求导 + Inductor

如果模型在后端设置为eageraot_eager时能够成功运行,而在inductor时失败,我们可以将失败原因缩小到Inductor。

编译错误

正如我们所知,图级优化的演进链如下:

torch.neg (Python) -> torch.ops.aten.neg.default (within FX graph) -> ops.neg (within IR node) -> tmp2 = -tmp1 (within C++ kernel)

如果您遇到编译错误,说明在输出代码中编译C++内核时出现了问题。 这种类型的错误表明在将IR节点降级为输出代码时引入了错误。 编译错误的根本原因通常会在回溯日志中显示。

例如,neg 函数是这样修改的:

def neg2(x):
    return f"-{x}"

日志记录给出了以下编译错误,原因相当明确。

 torch._dynamo.exc.BackendCompilerFailed: backend='inductor' raised:
 CppCompileError: C++ compile error
 /tmp/torchinductor_root/xg/cxga5tk3b4lkwoxyigrtocjp5s7vc5cg2ikuscf6bk6pjqip2bhx.cpp: In function ‘void kernel(const unsigned char*, const unsigned char*, unsigned char*)’:
 /tmp/torchinductor_root/xg/cxga5tk3b4lkwoxyigrtocjp5s7vc5cg2ikuscf6bk6pjqip2bhx.cpp:17:57: error: no matching function for call to ‘max_propagate_nan(unsigned char&, int&)’
   17 |                 auto tmp3 = max_propagate_nan(tmp0, tmp2);
        |                                                         ^
 In file included from /tmp/torchinductor_root/xg/cxga5tk3b4lkwoxyigrtocjp5s7vc5cg2ikuscf6bk6pjqip2bhx.cpp:2:
 /tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h:27:17: note: candidate: ‘template<class scalar_t> scalar_t max_propagate_nan(scalar_t, scalar_t)’
 27 | inline scalar_t max_propagate_nan(scalar_t a, scalar_t b) {
      |                 ^~~~~~~~~~~~~~~~~
 /tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h:27:17: note:   template argument deduction/substitution failed:
/tmp/torchinductor_root/xg/cxga5tk3b4lkwoxyigrtocjp5s7vc5cg2ikuscf6bk6pjqip2bhx.cpp:17:57: note:   deduced conflicting types for parameter ‘scalar_t’ (‘unsigned char’ and ‘int’)
 17 |                 auto tmp3 = max_propagate_nan(tmp0, tmp2);
      |                                                         ^

让我们也看看输出代码和IR节点中对应的C++内核。

C++ 内核:

include "/tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h"
extern "C" void kernel(const unsigned char* in_ptr0,
                    const unsigned char* in_ptr1,
                    unsigned char* out_ptr0)
{
    {
        #pragma GCC ivdep
        for(long i0=static_cast<long>(0L); i0<static_cast<long>(8390L); i0+=static_cast<long>(1L))
        {
            #pragma GCC ivdep
            for(long i1=static_cast<long>(0L); i1<static_cast<long>(8L); i1+=static_cast<long>(1L))
            {
                auto tmp0 = in_ptr0[static_cast<long>(i1 + (8L*i0))];
                auto tmp1 = in_ptr1[static_cast<long>(i1)];
                auto tmp2 = -tmp1;
                auto tmp3 = max_propagate_nan(tmp0, tmp2);
                out_ptr0[static_cast<long>(i1 + (8L*i0))] = tmp3;
            }
        }
    }
}

IR节点:

buf0: SchedulerNode(ComputedBuffer)
buf0.writes = [MemoryDep('buf0', c0, {c0: 67120})]
buf0.unmet_dependencies = []
buf0.met_dependencies =
    [   MemoryDep('arg0_1', c1, {c0: 8390, c1: 8}),
        MemoryDep('arg1_1', c0, {c0: 67120})]
buf0.users = [NodeUser(node=OUTPUT, can_inplace=False)]
buf0.group.device = cpu
buf0.group.iteration = ((8390, 8), ())
buf0.sizes = ([8390, 8], [])
class buf0_loop_body:
    var_ranges = {z0: 8390, z1: 8}
    index0 = 8*z0 + z1
    index1 = z1
    def body(self, ops):
        get_index = self.get_index('index0')
        load = ops.load('arg1_1', get_index)
        get_index_1 = self.get_index('index1')
        load_1 = ops.load('arg0_1', get_index_1)
        neg = ops.neg(load_1)
        maximum = ops.maximum(load, neg)
        get_index_2 = self.get_index('index0')
        store = ops.store('buf0', get_index_2, maximum, None)
        return store

根据回溯日志,编译错误是由max_propagate_nan输入的数据类型不一致引起的。 通过检查C++内核,我们知道在执行-操作后,tmp2不再是long类型,因为tmp0long类型。 我们可以轻松地将C++内核中的-max_propagate_nan分别与IR节点中的ops.negops.maximum匹配。

现在我们成功地发现根本原因是cpp代码生成中ops.neg的实现,它在执行neg时默默地更改了数据类型。

准确性调试

否则,如果模型运行出现其他错误或准确性问题,你可以使用名为Minifier的PyTorch调试工具。

Minifier的核心思想是不断移除图的节点和输入,直到找到带有问题的最小图。 它通过四种策略帮助自动生成一个最小化的问题图:截断后缀、增量调试、消除死代码和移除未使用的输入。

我们现在将展示在Minifer的帮助下解决精度问题的调试过程。 精度问题指的是后端eager和inductor的输出不同的情况。

例如,我们这样修改示例:

from torch._dynamo.utils import same

def foo2(x1, x2):
    a = torch.neg(x1)
    b = torch.maximum(x2, a)
    y = torch.cat([b], dim=0)
    return y

x1 = torch.randn((1, 8), dtype=torch.float32)
x2 = torch.randn((8390, 8), dtype=torch.float32)

expected_result = foo2(x1, x2)

compiled_foo2 = torch.compile(foo2)
actual_result = compiled_foo2(x1, x2)

assert same(expected_result, actual_result) == True

并且修改neg函数:

def neg3(x):
    return f"decltype({x})(2 * {x})"

准确性问题将如下所示:

torch._dynamo.utils: [ERROR] Accuracy failed: allclose not within tol=0.0001
Traceback (most recent call last):
  File "test_script.py", line 18, in <module>
    assert same(expected_result, actual_result) == True
AssertionError

要调试Minifier的准确性问题,需要两个环境变量:

TORCHDYNAMO_REPRO_AFTER="aot" TORCHDYNAMO_REPRO_LEVEL=4 python xx.py

这为我们提供了日志信息,展示了压缩的步骤:

Started off with 6 nodes

Trying granularity 2
Strategy: Truncate suffix (G: 2) (6 nodes, 2 inputs)
SUCCESS: Went from 6 to 4 nodes

Trying granularity 4
Strategy: Remove unused inputs (G: 4) (4 nodes, 2 inputs)
SUCCESS: Went from 4 to 3 nodes

运行后,我们得到了带有目标节点 neg 的最终简化图:

def forward2(self, arg0_1):
    neg = torch.ops.aten.neg.default(arg0_1);  arg0_1 = None
    return (neg,)

有关Minifier的更多使用详情,请参阅Troubleshooting

性能分析

在本节中,我们将展示如何对使用Inductor CPU后端编译的模型进行性能分析。 在下面的示例中,我们对Hugging Face Transformer模型MobileBertForQuestionAnswering进行了基准测试,包括eager模式和Inductor图模式。 基准测试后,打印了Inductor的执行时间和加速比。 我们使用Intel(R) Xeon(R) Platinum 8358 CPU @ 2.60GHz并在第一个插槽上运行基准测试,以展示本节中的优化。 我们设置了以下环境变量作为在Intel(R) CPU上进行基准测试的最佳实践。

export KMP_BLOCKTIME=1
export KMP_SETTINGS=1
export KMP_AFFINITY=granularity=fine,compact,1,0
export LD_PRELOAD=${CONDA_PREFIX:-"$(dirname $(which conda))/../"}/lib/libiomp5.so:${CONDA_PREFIX:-"$(dirname $(which conda))/../"}/lib/libjemalloc.so
export MALLOC_CONF="oversize_threshold:1,background_thread:true,metadata_thp:auto,dirty_decay_ms:-1,muzzy_decay_ms:-1"
numactl -C 0-31 -m 0 python bench.py
# bench.py
from transformers import MobileBertForQuestionAnswering
# Initialize an eager model
model = MobileBertForQuestionAnswering.from_pretrained("csarron/mobilebert-uncased-squad-v2")
seq_length = 128
bs = 128
vocab_size = model.config.vocab_size
input = torch.randint(0, vocab_size, (bs, seq_length), dtype=torch.int64)
input_dict = {"input_ids": input}

# Initialize the inductor model
compiled_model = torch.compile(model)
with torch.no_grad():
    compiled_model(**input_dict)

NUM_ITERS=50
import timeit
with torch.no_grad():
    # warmup
    for _ in range(10):
        model(**input_dict)
    eager_t = timeit.timeit("model(**input_dict)", number=NUM_ITERS, globals=globals())

with torch.no_grad():
    # warmup
    for _ in range(10):
        compiled_model(**input_dict)
    inductor_t = timeit.timeit("compiled_model(**input_dict)", number=NUM_ITERS, globals=globals())
# print(f"eager use: {eager_t * 1000 / NUM_ITERS} ms/iter")
# print(f"inductor use: {inductor_t * 1000 / NUM_ITERS} ms/iter")
# print(f"speed up ratio: {eager_t / inductor_t}")
/usr/local/lib/python3.10/dist-packages/numpy/core/getlimits.py:518: UserWarning:

The value of the smallest subnormal for <class 'numpy.float32'> type is zero.

/usr/local/lib/python3.10/dist-packages/numpy/core/getlimits.py:89: UserWarning:

The value of the smallest subnormal for <class 'numpy.float32'> type is zero.

输出:

eager use: 802.1023553796113 ms/iter
inductor use: 339.95180135127157 ms/iter
speed up ratio: 2.359459053287382

在我们自己的测试中,我们发现Inductor CPU后端将模型速度提高了约2.355倍。

接下来,让我们深入探讨操作层面的性能,以了解速度提升的来源。 Pytorch Profiler 是一个帮助我们实现这一目标的好工具。 Inductor CPU 后端支持通过 enable_kernel_profile 配置选项向分析器报告融合内核的时间:

from torch._inductor import config
config.cpp.enable_kernel_profile = True

按照Pytorch Profiler中的步骤,我们能够获取性能分析表和跟踪文件。

# bench.py
from torch.profiler import profile, schedule, ProfilerActivity
RESULT_DIR = "./prof_trace"
my_schedule = schedule(
    skip_first=10,
    wait=5,
    warmup=5,
    active=1,
    repeat=5)

def trace_handler(p):
    output = p.key_averages().table(sort_by="self_cpu_time_total", row_limit=20)
    # print(output)
    p.export_chrome_trace(f"{RESULT_DIR}/{p.step_num}.json")

for _ in range(10):
    model(**input_dict)  # compiled_model(**input_dict) to get inductor model profiling

total = 0
with profile(
    activities=[ProfilerActivity.CPU],
    schedule=my_schedule,
    on_trace_ready=trace_handler
) as p:
    for _ in range(50):
        model(**input_dict)  # compiled_model(**input_dict) to get inductor model profiling
        p.step()

我们得到了以下性能分析表(省略了一些列):

-------------------------  ------------  ------------  ------------
                     Name   CPU total %     CPU total    # of Calls
-------------------------  ------------  ------------  ------------
              aten::addmm        45.73%     370.814ms           362
                aten::add        19.89%     161.276ms           363
              aten::copy_        14.97%     121.416ms           488
                aten::mul         9.02%      73.154ms           194
          aten::clamp_min         8.81%      71.444ms            96
                aten::bmm         5.46%      44.258ms            48
            ProfilerStep*       100.00%     810.920ms             1
                aten::div         2.89%      23.447ms            24
           aten::_softmax         1.00%       8.087ms            24
             aten::linear        46.48%     376.888ms           362
              aten::clone         2.77%      22.430ms            98
                  aten::t         0.31%       2.502ms           362
               aten::view         0.14%       1.161ms           850
          aten::transpose         0.17%       1.377ms           386
       aten::index_select         0.12%     952.000us             3
             aten::expand         0.12%     986.000us           458
             aten::matmul         8.31%      67.420ms            48
                aten::cat         0.09%     703.000us             1
         aten::as_strided         0.08%     656.000us           963
               aten::relu         8.86%      71.864ms            96
-------------------------  ------------  ------------  ------------
Self CPU time total: 810.920ms

同样地,我们也得到了使用Inductor编译模型的表格(省略了一些列):

-----------------------------------------------  ------------  ------------  ------------
                                           Name   CPU total %     CPU total    # of Calls
-----------------------------------------------  ------------  ------------  ------------
                               mkl::_mkl_linear        68.79%     231.573ms           362
                                      aten::bmm         8.02%      26.992ms            48
                                  ProfilerStep*       100.00%     336.642ms             1
  graph_0_cpp_fused_constant_pad_nd_embedding_0         0.27%     915.000us             1
                                    aten::empty         0.27%     911.000us           362
 graph_0_cpp_fused__mkl_linear_add_mul_relu_151         0.27%     901.000us             1
 graph_0_cpp_fused__mkl_linear_add_mul_relu_226         0.27%     899.000us             1
 graph_0_cpp_fused__mkl_linear_add_mul_relu_361         0.27%     898.000us             1
 graph_0_cpp_fused__mkl_linear_add_mul_relu_121         0.27%     895.000us             1
  graph_0_cpp_fused__mkl_linear_add_mul_relu_31         0.27%     893.000us             1
  graph_0_cpp_fused__mkl_linear_add_mul_relu_76         0.26%     892.000us             1
 graph_0_cpp_fused__mkl_linear_add_mul_relu_256         0.26%     892.000us             1
 graph_0_cpp_fused__mkl_linear_add_mul_relu_346         0.26%     892.000us             1
 graph_0_cpp_fused__mkl_linear_add_mul_relu_241         0.26%     891.000us             1
 graph_0_cpp_fused__mkl_linear_add_mul_relu_316         0.26%     891.000us             1
  graph_0_cpp_fused__mkl_linear_add_mul_relu_91         0.26%     890.000us             1
 graph_0_cpp_fused__mkl_linear_add_mul_relu_106         0.26%     890.000us             1
 graph_0_cpp_fused__mkl_linear_add_mul_relu_211         0.26%     890.000us             1
  graph_0_cpp_fused__mkl_linear_add_mul_relu_61         0.26%     889.000us             1
 graph_0_cpp_fused__mkl_linear_add_mul_relu_286         0.26%     889.000us             1
-----------------------------------------------  ------------  ------------  ------------
Self CPU time total: 336.642ms

从急切模型的性能分析表中,我们可以看到最耗时的操作是[aten::addmm, aten::add, aten::copy_, aten::mul, aten::clamp_min, aten::bmm]。 与电感模型的性能分析表相比,我们注意到一个mkl::_mkl_linear条目和多个以graph_0_cpp_fused_*形式出现的融合内核。它们是电感模型进行的主要优化。让我们分别讨论它们。

(1) 关于 mkl::_mkl_linear:你可能会注意到这个内核的调用次数是362次,这与急切模型分析表中的 aten::linear 完全相同。 aten::linear 的CPU总时间为376.888毫秒,而 mkl::_mkl_linear 为231.573毫秒。这表明“线性”部分的速度提升了约1.63倍。 速度提升主要来自于 将权重张量打包为块内存格式 以及在Inductor CPU后端中调用 cblas_sgemm_compute 以在GEMM计算期间获得更好的缓存行为。

(2) 关于其他内存密集型操作:在我们的测试中,eager/inductor模型的端到端延迟分别为802/339毫秒。因此,我们可以大致推断出其他内存密集型操作的加速比约为3.94倍。 让我们阅读生成的代码,以了解inductor如何实现这一令人印象深刻的优化。你可以通过在output_code.py中搜索cpp_fused__mkl_linear_add_mul_relu_151来找到生成的代码。

cpp_fused__mkl_linear_add_mul_relu_151 = async_compile.cpp('''
#include <ATen/record_function.h>
#include "/tmp/torchinductor_root/lr/clrlgu27q4ggd472umdzwsu6qcpqxcuusjxqvx2hwitjbujiiz7z.h"
extern "C" void kernel(float* in_out_ptr0,
                       const float* in_ptr0,
                       const float* in_ptr1,
                       const float* in_ptr2,
                       const float* in_ptr3)
{
    RECORD_FUNCTION("graph_0_cpp_fused__mkl_linear_add_mul_relu_151", c10::ArrayRef<c10::IValue>({}));
    #pragma omp parallel num_threads(32)
    {
        {
            #pragma omp for
            for(long i0=static_cast<long>(0L); i0<static_cast<long>(16384L); i0+=static_cast<long>(1L))
            {
                for(long i1=static_cast<long>(0L); i1<static_cast<long>(512L); i1+=static_cast<long>(8L))
                {
                    auto tmp0 = at::vec::Vectorized<float>::loadu(in_ptr0 + static_cast<long>(i1 + (512L*i0)));
                    auto tmp1 = at::vec::Vectorized<float>::loadu(in_ptr1 + static_cast<long>(i1));
                    auto tmp3 = at::vec::Vectorized<float>::loadu(in_out_ptr0 + static_cast<long>(i1 + (512L*i0)));
                    auto tmp5 = at::vec::Vectorized<float>::loadu(in_ptr2 + static_cast<long>(i1));
                    auto tmp7 = at::vec::Vectorized<float>::loadu(in_ptr3 + static_cast<long>(i1));
                    auto tmp2 = tmp0 + tmp1;
                    auto tmp4 = tmp2 + tmp3;
                    auto tmp6 = tmp4 * tmp5;
                    auto tmp8 = tmp6 + tmp7;
                    tmp8.store(in_out_ptr0 + static_cast<long>(i1 + (512L*i0)));
                }
            }
        }
    }
}''')

从上面生成的代码中,我们可以看到这个内核在[add, add, mul, add]上完成了一个典型的Loop Fusion。 这是一个内存限制的瓶颈,阻碍了良好的性能。为了更直观地感受这种优化, 我们可以推断输入的大小和步幅,并进一步对这个[add, add, mul, add]模式进行基准测试。

# bench.py
def func(arg_0, arg_1, arg_2, arg_3, arg_4):
    add_0 = arg_0 + arg_1
    add_1 = add_0 + arg_2
    mul_1 = add_1 * arg_3
    add_2 = mul_1 + arg_4
    arg_2 = add_2
    return arg_2

arg_0 = torch.rand(16384, 512)
arg_1 = torch.rand(1, 512)
arg_2 = torch.zeros(16384, 512)
arg_3 = torch.rand(1, 512)
arg_4 = torch.rand(1, 512)

input = (arg_0, arg_1, arg_2, arg_3, arg_4)
inductor_func = torch.compile(func)
with torch.no_grad():
    inductor_func(*input)

import timeit
NUM_ITERS=100
with torch.no_grad():
    # warmup
    for _ in range(10):
        func(*input)
    eager_t = timeit.timeit("func(*input)", number=NUM_ITERS, globals=globals())

with torch.no_grad():
    # warmup
    for _ in range(10):
        inductor_func(*input)
    inductor_t = timeit.timeit("inductor_func(*input)", number=NUM_ITERS, globals=globals())
# print(f"eager use: {eager_t * 1000 / NUM_ITERS} ms/iter")
# print(f"inductor use: {inductor_t * 1000 / NUM_ITERS} ms/iter")
# print(f"speed up ratio: {eager_t / inductor_t}")

输出:

eager use: 5.780875144992024 ms/iter
inductor use: 0.9588955780491233 ms/iter
speed up ratio: 6.0286805751604735

这只是一个示例。分析表显示在此模型中,所有元素操作都在电感器中自动融合。您可以在output_code.py中阅读更多内核。

结论

本文档提供了关于Inductor CPU后端的深入教程。

通过激励性的例子,我们逐步讲解调试和分析的过程。主要思路是缩小问题的范围。

我们逐步演示了如何深入探讨问题并找到故障的根本原因,借助调试日志记录和工具Minifier的帮助。 首先确定故障发生在哪个组件中,然后尝试生成能够重现故障的最小代码片段。

当使用Inductor的性能优于eager模式时,我们提供了一种可靠的分析方法来进行性能分析。 我们展示了如何使用PyTorch Profiler找到耗时的热点,并找出操作符级别或内核级别的原因来解释这一现象。

脚本总运行时间: (8分钟 48.727秒)

Gallery generated by Sphinx-Gallery

优云智算