From 3cef1a5ed023ad85ddc6f8410b0990d7bf9f9b30 Mon Sep 17 00:00:00 2001 From: "Liao, Xuan" Date: Tue, 6 Jun 2023 10:26:31 +0000 Subject: [PATCH 01/43] [Inductor] [Doc] Add debugging document for inductor cpu backend --- index.rst | 9 + intermediate_source/inductor_debug_cpu.rst | 316 +++++++++++++++++++++ 2 files changed, 325 insertions(+) create mode 100644 intermediate_source/inductor_debug_cpu.rst diff --git a/index.rst b/index.rst index 6e6d687d0c..a1a407b734 100644 --- a/index.rst +++ b/index.rst @@ -3,6 +3,7 @@ Welcome to PyTorch Tutorials What's new in PyTorch tutorials? +* `Inductor CPU Backend Debugging and Profiling `__ * `Implementing High Performance Transformers with Scaled Dot Product Attention `__ * `torch.compile Tutorial `__ * `Per Sample Gradients `__ @@ -564,6 +565,13 @@ What's new in PyTorch tutorials? :link: intermediate/torch_compile_tutorial.html :tags: Model-Optimization +.. customcarditem:: + :header: Inductor CPU Backend Debugging and Profiling + :card_description: Learn the usage, debugging and performance profiling for ``torch.compile`` with Inductor CPU backend. + :image: _static/img/thumbnails/cropped/generic-pytorch-logo.png + :link: intermediate/inductor_debug_cpu.html + :tags: Model-Optimization + .. customcarditem:: :header: (beta) Implementing High-Performance Transformers with SCALED DOT PRODUCT ATTENTION :card_description: This tutorial explores the new torch.nn.functional.scaled_dot_product_attention and how it can be used to construct Transformer components. @@ -962,6 +970,7 @@ Additional Resources intermediate/nvfuser_intro_tutorial intermediate/ax_multiobjective_nas_tutorial intermediate/torch_compile_tutorial + intermediate/inductor_debug_cpu intermediate/scaled_dot_product_attention_tutorial .. toctree:: diff --git a/intermediate_source/inductor_debug_cpu.rst b/intermediate_source/inductor_debug_cpu.rst new file mode 100644 index 0000000000..a08f2dbf45 --- /dev/null +++ b/intermediate_source/inductor_debug_cpu.rst @@ -0,0 +1,316 @@ +Inductor CPU backend debugging and profiling +============================================== + +**Author**: `Liao Xuan `_, `Zhu Haozhe `_ + +Usage +-------------- + +Start with an example +^^^^^^^^^^^^^^^^^^^ + +Here is a simple example to run the ``torch.compile`` with Inductor. + +.. code-block:: python + + import torch + + def fn(x): + return torch.neg(x) + + x = torch.randn((2, 4, 28)) + compiled_fn = torch.compile(fn) # backend=inductor as default + result = compiled_fn(x) + +Get more loggings +^^^^^^^^^^^^^^^^^^^ + +However, the above code would not give any debugging info. If we want to get more useful logging, one way is to add an environment variable. + +.. code:: shell + + TORCH_COMPILE_DEBUG=1 python xx.py + +The time taken in each step is shown. This also does the graph visualization and prints the output code. In logging, a temperate debug tracing directory like this can be found. + +.. code:: shell + + torch._inductor.debug: [WARNING] model___20 debug trace: /tmp/torchinductor_root/rx/crxfi2ybd7yp5sbj2pnhw33wfhtdw7wumvrobyp5sjvdui5ktjc2.debug + +The directory saves several files for debugging. + ++-------------------------+----------------------------------------------------------+ +| fx_graph_readable.py | Readable FX graph, post decomps | ++-------------------------+----------------------------------------------------------+ +| fx_graph_runnable.py | Executable FX graph, post decomps, pre pattern match | ++-------------------------+----------------------------------------------------------+ +| fx_graph_transformed.py | Transformed FX graph, post pattern match | ++-------------------------+----------------------------------------------------------+ +| ir_post_fusion.txt | Inductor IR before fusion | ++-------------------------+----------------------------------------------------------+ +| ir_pre_fusion.txt | Inductor IR after fusion | ++-------------------------+----------------------------------------------------------+ +| output_code.py | Generated Python code for graph, with cpp/triton kernels | ++-------------------------+----------------------------------------------------------+ + + +``fx_graph_runnable.py`` and ``output_code.py`` are both runnable and editable in order to make debugging easier. + + +Here is another way to print logging for Inductor. + +.. code:: shell + + TORCH_LOGS="+inductor,output_code,schedule" python xx.py + ++--------------+-------------------------------------------------------------+ +| +inductor | Set the logging level of Inductor to DEBUG, default is INFO | ++--------------+-------------------------------------------------------------+ +| +output_code | Print output code with cpp/triton kernels | ++--------------+-------------------------------------------------------------+ +| +schedule | Print reasons for not doing vectorization in cpp kernels | ++--------------+-------------------------------------------------------------+ + +Configs to do deeper analysis +^^^^^^^^^^^^^^^^^^^ + +Moreover, there are several config parameters helping the analysis. + ++--------------------------------------------------+---------------------------------------------------------------------+ +| torch._inductor.config.max_fusion_size | Set the maximum number of nodes allowed in one fusion | ++--------------------------------------------------+---------------------------------------------------------------------+ +| torch._inductor.config.cpp.simdlen | Specify the bit width for cpp vectorization | ++--------------------------------------------------+---------------------------------------------------------------------+ +| torch._inductor.config.cpp.min_chunk_size | Set the minimum number of workloads one thread should at least take | ++--------------------------------------------------+---------------------------------------------------------------------+ +| torch._inductor.config.cpp.enable_kernel_profile | Allow cpp kernel performance profiling via profiler | ++--------------------------------------------------+---------------------------------------------------------------------+ + + +Debugging +-------------- + +Determine component of error +^^^^^^^^^^^^^^^^^^^ + +When encountering errors or accuracy problem, a straightforward solution to find the bug is to narrow down the problem. The first thing to do is to determine the component where error occurs. Luckily, it can be simply achieved by changing the backend of ``torch.compile``. + ++----------------------------------------+-----------------------------------------+ +| torch.compile(fn, backend="eager") | Enable Dynamo | ++----------------------------------------+-----------------------------------------+ +| torch.compile(fn, backend="aot_eager") | Enable Dynamo + AOT autograd | ++----------------------------------------+-----------------------------------------+ +| torch.compile(fn, backend="inductor") | Enable Dynamo + AOT autograd + Inductor | ++----------------------------------------+-----------------------------------------+ + +If the model can successfully run when backend is eager or aot_eager while it fails with inductor, we can narrow down the failure to Inductor. + + +Example +^^^^^^^^^^^^^^^^^^^ + +Here is an example for the subsequent debugging. + +.. code-block:: python + + import torch + from torch._dynamo.utils import same + + def foo(x1, x2): + a = torch.neg(x1) + b = torch.maximum(x2, a) + y = torch.cat([b], dim=0) + return y + + x1 = torch.randint(256, (1,), dtype=torch.uint8) + x2 = torch.randint(256, (8390,), dtype=torch.uint8) + + expected_result = fn(x1, x2) + + compiled_fn = torch.compile(fn) + actual_result = compiled_fn(x1, x2) + + assert same(expected_result, actual_result) == True + + +The implementation of ``neg`` in cpp codegen is as follows. + +.. code-block:: python + + def neg(x): + return f"decltype({x})(-{x})" + + +In order to demonstrate the debugging, we will modify the function to a wrong one later. + +Errors debugging +^^^^^^^^^^^^^^^^^^^ + +If it occurs a compile error, the root cause is usually shown in traceback log. + +For example, the ``neg`` function is modified like this. + +.. code-block:: python + + def neg(x): + return f"-{x}" + + +The logging gives the following compile error with a rather clear reason. In this case, the root cause is that data types of maximum's inputs are inconsistent. + +.. code:: shell + + … + torch._dynamo.exc.BackendCompilerFailed: backend='inductor' raised: + CppCompileError: C++ compile error + … + /tmp/torchinductor_root/2x/c2xgxsooklulr4u54etfnnha7dsu6xzbwdscttvs7dkpba3uwkem.cpp: In function ‘void kernel(const unsigned char*, const unsigned char*, unsigned char*)’: + /tmp/torchinductor_root/2x/c2xgxsooklulr4u54etfnnha7dsu6xzbwdscttvs7dkpba3uwkem.cpp:14:53: error: no matching function for call to ‘max_propagate_nan(unsigned char&, int&)’ + 14 | auto tmp3 = max_propagate_nan(tmp0, tmp2); + | ^ + In file included from /tmp/torchinductor_root/2x/c2xgxsooklulr4u54etfnnha7dsu6xzbwdscttvs7dkpba3uwkem.cpp:2: + /tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h:27:17: note: candidate: ‘template 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/2x/c2xgxsooklulr4u54etfnnha7dsu6xzbwdscttvs7dkpba3uwkem.cpp:14:53: note: deduced conflicting types for parameter ‘scalar_t’ (‘unsigned char’ and ‘int’) + 14 | auto tmp3 = max_propagate_nan(tmp0, tmp2); + | ^ + + +Otherwise, if the model runs with other errors, we can do the model code reduction until finding the minimum code snippet with failure. Thus, the target operators and kernels are located. + + +Accuracy debugging +^^^^^^^^^^^^^^^^^^^ + +The accuracy problem refers the case where outputs of backends eager and inductor are different. As FX graph is generated before Inductor and output code is generated after Inductor, we can narrow down the problem by comparing their outputs. + +If a model has several graphs, the first step is to compare the final outputs of FX graph and output code for each graph, given the same input. The target is to find the first graph occurring error or with different outputs. Binary search is suggested to use for efficiency. + +When a model has only one graph or the problematic graph has been found with the above step, compare the intermediate outputs of FX graph and output code in each graph, given the same input. The idea is to continuously narrow down the problem. + +For example, we modify the ``neg`` function like this. + +.. code-block:: python + + def neg(x): + return f"decltype({x})(2 * {x})" + + +An accuracy problem would be raised as follows. + +.. code:: shell + + torch._dynamo.utils: [ERROR] Accuracy failed: allclose not within tol=0.0001 + Traceback (most recent call last): + File "test_script.py", line 18, in + assert same(expected_result, actual_result) == True + AssertionError + + +By comparing the intermediate outputs of FX graph and output code, it would be found that outputs are already different after doing ``torch.neg``. + +Specifically, the modifications of FX graph and output code are attached. + +*Change of FX graph* + +.. code-block:: python + + # Before + class Repro(torch.nn.Module): + def __init__(self): + super().__init__() + + def forward(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,) + + # After + class Repro(torch.nn.Module): + def __init__(self): + super().__init__() + + def forward(self, arg0_1, arg1_1): + neg = torch.ops.aten.neg.default(arg0_1); arg0_1 = None + return (neg,) + + +*Change of output code* + +.. code-block:: python + + # Before + cpp_fused_cat_maximum_neg_0 = async_compile.cpp(''' + #include "/tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h" + extern "C" void kernel(const long* in_ptr0, + const long* in_ptr1, + long* out_ptr0) + { + { + #pragma GCC ivdep + for(long i0=static_cast(0L); i0(8390L); i0+=static_cast(1L)) + { + auto tmp0 = in_ptr0[static_cast(i0)]; + auto tmp1 = in_ptr1[static_cast(0L)]; + auto tmp2 = decltype(tmp1)(2 * tmp1); + auto tmp3 = max_propagate_nan(tmp0, tmp2); + out_ptr0[static_cast(i0)] = tmp3; + } + } + } + ''') + + def call(args): + arg0_1, arg1_1 = args + args.clear() + buf0 = empty_strided((8390, ), (1, ), device='cpu', dtype=torch.int64) + cpp_fused_cat_maximum_neg_0(c_void_p(arg1_1.data_ptr()), c_void_p(arg0_1.data_ptr()), c_void_p(buf0.data_ptr())) + del arg0_1 + del arg1_1 + return (buf0, ) + + # After + cpp_fused_cat_maximum_neg_0 = async_compile.cpp(''' + #include "/tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h" + extern "C" void kernel(const long* in_ptr0, + const long* in_ptr1, + long* out_ptr0) + { + { + auto tmp1 = in_ptr1[static_cast(0L)]; + auto tmp2 = decltype(tmp1)(2 * tmp1); + out_ptr0[static_cast(0L)] = tmp2; + } + } + ''') + + def call(args): + arg0_1, arg1_1 = args + args.clear() + buf0 = empty_strided((1, ), (1, ), device='cpu', dtype=torch.int64) + cpp_fused_cat_maximum_neg_0(c_void_p(arg1_1.data_ptr()), c_void_p(arg0_1.data_ptr()), c_void_p(buf0.data_ptr())) + del arg0_1 + del arg1_1 + return (buf0, ) + + +Note that there exists a debugging tool provided by PyTorch, called `Minifier `_. It helps us automatically generate a minified problematic graph. + + +Performance profiling +-------------- +TODO: Haozhe + + +Future work +-------------- + +Implement and up-stream the debug tools + 1. **Cosim**: Merge graphs of a model into a single large graph. Thus, graphs can be compared quickly between different versions of PyTorch. `#102958 `_ + 2. **Graph matching**: In order to know what each kernel does, this tool matches cpp kernel with FX graph operators and adds corresponding operators before each kernel in cpp output code. `#102958 `_ + 3. **Save inputs and outputs**: For the purpose of reproducing rapidly the failure of a large model, it is necessary to add serializations for the inputs and outputs among graphs and intermediate outputs in graphs. + 4. **Test case generation**: When a user has found the operators which are inefficient with cpp kernels, a tool is needed to automatically write a test case. Specifically, one test case can be generated for each kernel, with the corresponding small FX graph and input. + 5. **Minifier optimization**: Keep refining Minifier and make it adapted for more scenarios. From 5117de6a0d291e32664075a56b7eda052e781a61 Mon Sep 17 00:00:00 2001 From: "haozhe.zhu" Date: Tue, 6 Jun 2023 13:14:01 +0800 Subject: [PATCH 02/43] add profile part --- intermediate_source/inductor_debug_cpu.rst | 286 ++++++++++++++++++++- 1 file changed, 285 insertions(+), 1 deletion(-) diff --git a/intermediate_source/inductor_debug_cpu.rst b/intermediate_source/inductor_debug_cpu.rst index a08f2dbf45..d2c13715f4 100644 --- a/intermediate_source/inductor_debug_cpu.rst +++ b/intermediate_source/inductor_debug_cpu.rst @@ -302,7 +302,291 @@ Note that there exists a debugging tool provided by PyTorch, called `Minifier ` +We can enable kernel profile in inductor by: +.. code-block:: python + from torch._inductor import config + config.cpp.enable_kernel_profile = True + +Following the steps in `Pytorch Profiler` +we can get the profiling table and trace files. +.. code-block:: python + from torch.profiler import profile, schedule, ProfilerActivity + 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(RESULT_DIR + "/" + str(p.step_num) + ".json") + + for _ in range(nwarmup): + model(**input_dict) + + total = 0 + with profile( + activities=[ProfilerActivity.CPU], + schedule=my_schedule, + on_trace_ready=trace_handler + ) as p: + for _ in range(100): + begin = time.time() + model(**input_dict) + end=time.time() + total += (end - begin) + p.step() + print("latency: {} ms".format(1000*(total)/100)) + +We can get following profile tables for eager model +.. code-block:: shell + ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ + Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls + ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ + aten::mm 33.33% 138.616ms 33.33% 138.616ms 1.429ms 97 + aten::add_ 19.38% 80.596ms 19.38% 80.596ms 4.242ms 19 + aten::bmm 18.78% 78.104ms 18.78% 78.104ms 2.170ms 36 + aten::_softmax 11.32% 47.082ms 11.32% 47.082ms 2.616ms 18 + aten::copy_ 3.89% 16.190ms 3.89% 16.190ms 103.121us 157 + ProfilerStep* 3.53% 14.702ms 100.00% 415.949ms 415.949ms 1 + aten::add 2.37% 9.849ms 2.39% 9.958ms 144.319us 69 + aten::mul 1.13% 4.693ms 1.14% 4.726ms 65.639us 72 + aten::clamp_min 0.85% 3.541ms 0.85% 3.541ms 295.083us 12 + aten::index_select 0.84% 3.480ms 1.06% 4.401ms 1.100ms 4 + aten::linear 0.63% 2.637ms 33.95% 141.194ms 1.456ms 97 + aten::pow 0.61% 2.520ms 0.61% 2.554ms 79.812us 32 + aten::matmul 0.50% 2.067ms 56.53% 235.132ms 1.768ms 133 + aten::select 0.22% 900.000us 0.22% 910.000us 113.750us 8 + aten::log 0.18% 740.000us 0.18% 740.000us 370.000us 2 + aten::_unsafe_view 0.17% 718.000us 0.17% 718.000us 3.840us 187 + aten::sum 0.17% 715.000us 0.20% 831.000us 25.969us 32 + aten::transpose 0.15% 642.000us 0.18% 741.000us 3.963us 187 + aten::reshape 0.15% 622.000us 3.66% 15.241ms 88.098us 173 + aten::fill_ 0.15% 613.000us 0.15% 613.000us 15.718us 39 + ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ + Self CPU time total: 415.949ms +And for inductor model +.. code-block:: shell + ------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ + Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls + ------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ + mkl::_mkl_linear 28.24% 133.979ms 28.39% 134.689ms 1.389ms 97 + aten::bmm 15.65% 74.250ms 15.65% 74.251ms 2.063ms 36 + graph_0_cpp_fused__softmax_7 4.24% 20.123ms 4.24% 20.123ms 20.123ms 1 + graph_0_cpp_fused__softmax_42 4.17% 19.773ms 4.17% 19.773ms 19.773ms 1 + graph_0_cpp_fused__softmax_35 4.16% 19.751ms 4.16% 19.751ms 19.751ms 1 + graph_0_cpp_fused__softmax_21 4.15% 19.674ms 4.15% 19.674ms 19.674ms 1 + graph_0_cpp_fused__softmax_14 4.14% 19.654ms 4.14% 19.654ms 19.654ms 1 + graph_0_cpp_fused__softmax_28 4.13% 19.576ms 4.13% 19.576ms 19.576ms 1 + graph_0_cpp_fused__softmax_56 2.83% 13.404ms 2.83% 13.404ms 13.404ms 1 + graph_0_cpp_fused__softmax_80 2.82% 13.371ms 2.82% 13.371ms 13.371ms 1 + graph_0_cpp_fused__softmax_68 2.81% 13.323ms 2.81% 13.323ms 13.323ms 1 + graph_0_cpp_fused__softmax_92 2.80% 13.297ms 2.80% 13.297ms 13.297ms 1 + graph_0_cpp_fused__softmax_104 2.78% 13.208ms 2.78% 13.208ms 13.208ms 1 + graph_0_cpp_fused__softmax_2 2.63% 12.468ms 2.63% 12.468ms 12.468ms 1 + ProfilerStep* 1.61% 7.616ms 100.00% 474.360ms 474.360ms 1 + graph_0_cpp_fused__softmax_73 0.49% 2.320ms 0.49% 2.320ms 2.320ms 1 + graph_0_cpp_fused__softmax_85 0.49% 2.309ms 0.49% 2.309ms 2.309ms 1 + graph_0_cpp_fused__softmax_97 0.48% 2.283ms 0.48% 2.283ms 2.283ms 1 + graph_0_cpp_fused__softmax_61 0.48% 2.268ms 0.48% 2.268ms 2.268ms 1 + graph_0_cpp_fused__softmax_49 0.48% 2.255ms 0.48% 2.255ms 2.255ms 1 + ------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ + Self CPU time total: 474.360ms + +We can search the most time consuming `graph_0_cpp_fused__softmax_7` in `output_code.py` to see the generated code: +.. code-block:: python + cpp_fused__softmax_7 = async_compile.cpp(''' + #include + #include "/tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h" + extern "C" void kernel(float* in_out_ptr0, + const float* in_ptr1, + float* out_ptr0, + float* out_ptr1) + { + RECORD_FUNCTION("graph_0_cpp_fused__softmax_7", c10::ArrayRef({})); + auto in_ptr0 = in_out_ptr0; + #pragma omp parallel num_threads(32) + { + { + #pragma omp for collapse(2) + for(long i0=static_cast(0L); i0(4L); i0+=static_cast(1L)) + { + for(long i1=static_cast(0L); i1(8L); i1+=static_cast(1L)) + { + #pragma GCC ivdep + for(long i2=static_cast(0L); i2(1024L); i2+=static_cast(1L)) + { + { + float tmp_acc0 = -std::numeric_limits::infinity(); + for(long i3=static_cast(0L); i3(1024L); i3+=static_cast(1L)) + { + auto tmp0 = in_ptr0[static_cast(i3 + (1024L*i2) + (1048576L*i1) + (8388608L*i0))]; + auto tmp1 = static_cast(i3 + ((-1L)*i2)); + auto tmp2 = static_cast(0); + auto tmp3 = tmp1 > tmp2; + auto tmp4 = static_cast(tmp3); + auto tmp5 = static_cast(16); + auto tmp6 = decltype(tmp4)(tmp4 * tmp5); + auto tmp7 = tmp6 + tmp2; + auto tmp8 = std::abs(tmp1); + auto tmp9 = static_cast(8); + auto tmp10 = tmp8 < tmp9; + auto tmp11 = static_cast(tmp8); + auto tmp12 = static_cast(8.0); + auto tmp13 = tmp11 / tmp12; + auto tmp14 = std::log(tmp13); + auto tmp15 = static_cast(2.772588722239781); + auto tmp16 = tmp14 / tmp15; + auto tmp17 = decltype(tmp16)(tmp16 * tmp12); + auto tmp18 = static_cast(tmp17); + auto tmp19 = tmp18 + tmp9; + auto tmp20 = static_cast(15); + auto tmp21 = min_propagate_nan(tmp19, tmp20); + auto tmp22 = tmp10 ? tmp8 : tmp21; + auto tmp23 = tmp7 + tmp22; + auto tmp24 = in_ptr1[static_cast(i1 + (8L*tmp23))]; + auto tmp25 = static_cast(0.0); + auto tmp26 = tmp24 + tmp25; + auto tmp27 = tmp0 + tmp26; + tmp_acc0 = max_propagate_nan(tmp_acc0, tmp27); + } + out_ptr0[static_cast(i2 + (1024L*i1) + (8192L*i0))] = tmp_acc0; + } + } + } + } + } + { + #pragma omp for collapse(2) + for(long i0=static_cast(0L); i0(4L); i0+=static_cast(1L)) + { + for(long i1=static_cast(0L); i1(8L); i1+=static_cast(1L)) + { + #pragma GCC ivdep + for(long i2=static_cast(0L); i2(1024L); i2+=static_cast(1L)) + { + #pragma GCC ivdep + for(long i3=static_cast(0L); i3(1024L); i3+=static_cast(1L)) + { + auto tmp0 = in_out_ptr0[static_cast(i3 + (1024L*i2) + (1048576L*i1) + (8388608L*i0))]; + auto tmp28 = out_ptr0[static_cast(i2 + (1024L*i1) + (8192L*i0))]; + auto tmp1 = static_cast(i3 + ((-1L)*i2)); + auto tmp2 = static_cast(0); + auto tmp3 = tmp1 > tmp2; + auto tmp4 = static_cast(tmp3); + auto tmp5 = static_cast(16); + auto tmp6 = decltype(tmp4)(tmp4 * tmp5); + auto tmp7 = tmp6 + tmp2; + auto tmp8 = std::abs(tmp1); + auto tmp9 = static_cast(8); + auto tmp10 = tmp8 < tmp9; + auto tmp11 = static_cast(tmp8); + auto tmp12 = static_cast(8.0); + auto tmp13 = tmp11 / tmp12; + auto tmp14 = std::log(tmp13); + auto tmp15 = static_cast(2.772588722239781); + auto tmp16 = tmp14 / tmp15; + auto tmp17 = decltype(tmp16)(tmp16 * tmp12); + auto tmp18 = static_cast(tmp17); + auto tmp19 = tmp18 + tmp9; + auto tmp20 = static_cast(15); + auto tmp21 = min_propagate_nan(tmp19, tmp20); + auto tmp22 = tmp10 ? tmp8 : tmp21; + auto tmp23 = tmp7 + tmp22; + auto tmp24 = in_ptr1[static_cast(i1 + (8L*tmp23))]; + auto tmp25 = static_cast(0.0); + auto tmp26 = tmp24 + tmp25; + auto tmp27 = tmp0 + tmp26; + auto tmp29 = tmp27 - tmp28; + in_out_ptr0[static_cast(i3 + (1024L*i2) + (1048576L*i1) + (8388608L*i0))] = tmp29; + } + } + } + } + } + { + #pragma omp for + for(long i0=static_cast(0L); i0(33554432L); i0+=static_cast(16L)) + { + auto tmp0 = at::vec::Vectorized::loadu(in_out_ptr0 + static_cast(i0)); + auto tmp1 = tmp0.exp(); + tmp1.store(in_out_ptr0 + static_cast(i0)); + } + } + { + #pragma omp for + for(long i0=static_cast(0L); i0(32768L); i0+=static_cast(1L)) + { + { + #pragma omp declare reduction(+:at::vec::Vectorized:omp_out += omp_in) initializer(omp_priv={{0}}) + float tmp_acc0 = 0; + auto tmp_acc0_vec = at::vec::Vectorized(tmp_acc0); + for(long i1=static_cast(0L); i1(1024L); i1+=static_cast(16L)) + { + auto tmp0 = at::vec::Vectorized::loadu(in_out_ptr0 + static_cast(i1 + (1024L*i0))); + tmp_acc0_vec += tmp0; + } + tmp_acc0 += at::vec::vec_reduce_all([](at::vec::Vectorized& x, at::vec::Vectorized&y) {return x + y;}, tmp_acc0_vec); + out_ptr1[static_cast(i0)] = tmp_acc0; + } + } + } + } + } + ''') +With the kernel name `cpp_fused__softmax_*` and considering the profile +results together, we may suspect the generated code for 'softmax' is +inefficient. We encourage you to report an issue with all you findings above. Future work From c06809f5a355a64c472883001279e6f24bc48b93 Mon Sep 17 00:00:00 2001 From: zhuhaozhe Date: Tue, 6 Jun 2023 13:57:27 +0800 Subject: [PATCH 03/43] fix format (#2) --- intermediate_source/inductor_debug_cpu.rst | 35 ++++++++++++++++------ 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.rst b/intermediate_source/inductor_debug_cpu.rst index d2c13715f4..5c2d0f46f5 100644 --- a/intermediate_source/inductor_debug_cpu.rst +++ b/intermediate_source/inductor_debug_cpu.rst @@ -302,11 +302,13 @@ Note that there exists a debugging tool provided by PyTorch, called `Minifier ` -We can enable kernel profile in inductor by: +To deep dive op-level performance, we can use `Pytorch Profiler `_ + +To enable kernel profile in inductor, we need set ``enable_kernel_profile`` by: + .. code-block:: python + from torch._inductor import config config.cpp.enable_kernel_profile = True -Following the steps in `Pytorch Profiler` -we can get the profiling table and trace files. +Following the steps in `Pytorch Profiler `_ +we are able to get the profiling table and trace files. + .. code-block:: python + from torch.profiler import profile, schedule, ProfilerActivity my_schedule = schedule( skip_first=10, @@ -388,8 +397,10 @@ we can get the profiling table and trace files. p.step() print("latency: {} ms".format(1000*(total)/100)) -We can get following profile tables for eager model +We will get following profile tables for eager model + .. code-block:: shell + ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ @@ -415,8 +426,11 @@ We can get following profile tables for eager model aten::fill_ 0.15% 613.000us 0.15% 613.000us 15.718us 39 ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ Self CPU time total: 415.949ms -And for inductor model + +And get above table for inductor model + .. code-block:: shell + ------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls ------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ @@ -443,8 +457,10 @@ And for inductor model ------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ Self CPU time total: 474.360ms -We can search the most time consuming `graph_0_cpp_fused__softmax_7` in `output_code.py` to see the generated code: +We can search the most time consuming ``graph_0_cpp_fused__softmax_7`` in ``output_code.py`` to see the generated code: + .. code-block:: python + cpp_fused__softmax_7 = async_compile.cpp(''' #include #include "/tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h" @@ -584,8 +600,9 @@ We can search the most time consuming `graph_0_cpp_fused__softmax_7` in `output_ } } ''') -With the kernel name `cpp_fused__softmax_*` and considering the profile -results together, we may suspect the generated code for 'softmax' is + +With the kernel name ``cpp_fused__softmax_*`` and considering the profile +results together, we may suspect the generated code for ``softmax`` is inefficient. We encourage you to report an issue with all you findings above. From a0b5ec092f69847e6bb72b70edd00ce17d405a65 Mon Sep 17 00:00:00 2001 From: "Liao, Xuan" Date: Tue, 6 Jun 2023 15:02:18 +0000 Subject: [PATCH 04/43] rename cosim --- index.rst | 1 - intermediate_source/inductor_debug_cpu.rst | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/index.rst b/index.rst index a1a407b734..7dcc57380f 100644 --- a/index.rst +++ b/index.rst @@ -3,7 +3,6 @@ Welcome to PyTorch Tutorials What's new in PyTorch tutorials? -* `Inductor CPU Backend Debugging and Profiling `__ * `Implementing High Performance Transformers with Scaled Dot Product Attention `__ * `torch.compile Tutorial `__ * `Per Sample Gradients `__ diff --git a/intermediate_source/inductor_debug_cpu.rst b/intermediate_source/inductor_debug_cpu.rst index 5c2d0f46f5..af49ec59f9 100644 --- a/intermediate_source/inductor_debug_cpu.rst +++ b/intermediate_source/inductor_debug_cpu.rst @@ -66,9 +66,9 @@ Here is another way to print logging for Inductor. +--------------+-------------------------------------------------------------+ | +inductor | Set the logging level of Inductor to DEBUG, default is INFO | +--------------+-------------------------------------------------------------+ -| +output_code | Print output code with cpp/triton kernels | +| output_code | Print output code with cpp/triton kernels | +--------------+-------------------------------------------------------------+ -| +schedule | Print reasons for not doing vectorization in cpp kernels | +| schedule | Print reasons for not doing vectorization in cpp kernels | +--------------+-------------------------------------------------------------+ Configs to do deeper analysis @@ -138,7 +138,7 @@ The implementation of ``neg`` in cpp codegen is as follows. .. code-block:: python def neg(x): - return f"decltype({x})(-{x})" + return f"decltype({x})(-{x})" In order to demonstrate the debugging, we will modify the function to a wrong one later. @@ -153,7 +153,7 @@ For example, the ``neg`` function is modified like this. .. code-block:: python def neg(x): - return f"-{x}" + return f"-{x}" The logging gives the following compile error with a rather clear reason. In this case, the root cause is that data types of maximum's inputs are inconsistent. @@ -195,7 +195,7 @@ For example, we modify the ``neg`` function like this. .. code-block:: python def neg(x): - return f"decltype({x})(2 * {x})" + return f"decltype({x})(2 * {x})" An accuracy problem would be raised as follows. @@ -610,7 +610,7 @@ Future work -------------- Implement and up-stream the debug tools - 1. **Cosim**: Merge graphs of a model into a single large graph. Thus, graphs can be compared quickly between different versions of PyTorch. `#102958 `_ + 1. **Graph merger**: Merge graphs of a model into a single large graph. Thus, graphs can be compared quickly between different versions of PyTorch. `#102958 `_ 2. **Graph matching**: In order to know what each kernel does, this tool matches cpp kernel with FX graph operators and adds corresponding operators before each kernel in cpp output code. `#102958 `_ 3. **Save inputs and outputs**: For the purpose of reproducing rapidly the failure of a large model, it is necessary to add serializations for the inputs and outputs among graphs and intermediate outputs in graphs. 4. **Test case generation**: When a user has found the operators which are inefficient with cpp kernels, a tool is needed to automatically write a test case. Specifically, one test case can be generated for each kernel, with the corresponding small FX graph and input. From 9d3d73701350ce2140eaa118d3b73551df09fd9b Mon Sep 17 00:00:00 2001 From: "Liao, Xuan" Date: Wed, 7 Jun 2023 10:04:21 +0000 Subject: [PATCH 05/43] change .rst to .py and fix_typos --- intermediate_source/inductor_debug_cpu.py | 607 ++++++++++++++++++++ intermediate_source/inductor_debug_cpu.rst | 617 --------------------- 2 files changed, 607 insertions(+), 617 deletions(-) create mode 100644 intermediate_source/inductor_debug_cpu.py delete mode 100644 intermediate_source/inductor_debug_cpu.rst diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py new file mode 100644 index 0000000000..d0385a9e4c --- /dev/null +++ b/intermediate_source/inductor_debug_cpu.py @@ -0,0 +1,607 @@ +# -*- coding: utf-8 -*- + +""" +Inductor CPU backend debugging and profiling +============================================ + +**Author**: `Liao Xuan `_, `Zhu Haozhe `_ +""" + +######################################################################### +# Overview +# -------- + +# This document is intended to introduce the usage, debugging and performance profiling for ``torch.compile`` with Inductor CPU backend. You can learn how to print useful loggings, how to debug errors and what to do when the performance is not good. + +# Here is a simple example to run the ``torch.compile`` with Inductor. + +import torch + +def fn(x): + return torch.neg(x) + +x = torch.randn((2, 4, 28)) +compiled_fn = torch.compile(fn) # backend=inductor as default +result = compiled_fn(x) + +# Get more loggings +# ^^^^^^^^^^^^^^^^^ + +# The simple example above would not give any debugging info. If you'd want to get more useful logging, you can add a ``TORCH_COMPILE_DEBUG`` environment variable: + +# .. code:: shell + +# TORCH_COMPILE_DEBUG=1 python xx.py + +# The time taken in each step is shown. This also does the graph visualization and prints the output code. In logging, a temporary debug tracing directory like this can be found. + +# .. code:: shell + +# torch._inductor.debug: [WARNING] model___20 debug trace: /tmp/torchinductor_root/rx/crxfi2ybd7yp5sbj2pnhw33wfhtdw7wumvrobyp5sjvdui5ktjc2.debug + +# In this directory, the following files are saved for debugging purposes. + +# +-------------------------+----------------------------------------------------------+ +# | File | Description | +# +-------------------------+----------------------------------------------------------+ +# | fx_graph_runnable.py | Executable FX graph, post decomps, pre pattern match | +# +-------------------------+----------------------------------------------------------+ +# | fx_graph_transformed.py | Transformed FX graph, post pattern match | +# +-------------------------+----------------------------------------------------------+ +# | ir_post_fusion.txt | Inductor IR before fusion | +# +-------------------------+----------------------------------------------------------+ +# | ir_pre_fusion.txt | Inductor IR after fusion | +# +-------------------------+----------------------------------------------------------+ +# | output_code.py | Generated Python code for graph, with cpp/triton kernels | +# +-------------------------+----------------------------------------------------------+ + + +# ``fx_graph_runnable.py`` and ``output_code.py`` are both runnable and editable in order to make debugging easier. + + +# Here is another way to print logging for Inductor: + +# .. code:: shell + +# TORCH_LOGS="+inductor,output_code,schedule" python xx.py + +# +--------------+-------------------------------------------------------------+ +# | Parameter | Description | +# +--------------+-------------------------------------------------------------+ +# | +inductor | Set the logging level of Inductor to DEBUG, default is INFO | +# +--------------+-------------------------------------------------------------+ +# | output_code | Print output code with cpp/triton kernels | +# +--------------+-------------------------------------------------------------+ +# | schedule | Print reasons for not doing vectorization in cpp kernels | +# +--------------+-------------------------------------------------------------+ + +# Conducting an in-depth analysis +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +# Moreover, there are several config parameters helping the analysis. + +# +--------------------------------------------------+---------------------------------------------------------------------+ +# | Parameter | Description | +# +--------------------------------------------------+---------------------------------------------------------------------+ +# | torch._inductor.config.max_fusion_size | Set the maximum number of nodes allowed in one fusion | +# +--------------------------------------------------+---------------------------------------------------------------------+ +# | torch._inductor.config.cpp.simdlen | Specify the bit width for cpp vectorization | +# +--------------------------------------------------+---------------------------------------------------------------------+ +# | torch._inductor.config.cpp.min_chunk_size | Set the minimum number of workloads one thread should at least take | +# +--------------------------------------------------+---------------------------------------------------------------------+ +# | torch._inductor.config.cpp.enable_kernel_profile | Allow cpp kernel performance profiling via profiler | +# +--------------------------------------------------+---------------------------------------------------------------------+ + + +###################################################################### +# Debugging +# --------- + +# Determine component of error +# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +# When encountering errors or an accuracy problem, a straightforward solution to find the bug is to narrow down the problem. The first thing to do is to determine the component where the error occurs. Luckily, it can be simply achieved by changing the backend of ``torch.compile``. + +# +----------------------------------------+-----------------------------------------+ +# | Code | Description | +# +----------------------------------------+-----------------------------------------+ +# | torch.compile(fn, backend="eager") | Enable Dynamo | +# +----------------------------------------+-----------------------------------------+ +# | torch.compile(fn, backend="aot_eager") | Enable Dynamo + AOT autograd | +# +----------------------------------------+-----------------------------------------+ +# | torch.compile(fn, backend="inductor") | Enable Dynamo + AOT autograd + Inductor | +# +----------------------------------------+-----------------------------------------+ + +# If the model can successfully run when the backend is set to ``eager`` or ``aot_eager`` while it fails with ``inductor``, we can narrow down the failure to Inductor. + + +# Example +# ^^^^^^^ + +# Here is an example for the subsequent debugging: + +import torch +from torch._dynamo.utils import same + +def foo(x1, x2): + a = torch.neg(x1) + b = torch.maximum(x2, a) + y = torch.cat([b], dim=0) + return y + +x1 = torch.randint(256, (1,), dtype=torch.uint8) +x2 = torch.randint(256, (8390,), dtype=torch.uint8) + +expected_result = fn(x1, x2) + +compiled_fn = torch.compile(fn) +actual_result = compiled_fn(x1, x2) + +assert same(expected_result, actual_result) == True + +# The implementation of ``neg`` in the ``cpp`` codegen is as follows: + +def neg(x): + return f"decltype({x})(-{x})" + +# In order to demonstrate the debugging, we will modify the function to a wrong one later. + + +# Errors debugging +# ^^^^^^^^^^^^^^^^ + +# If a compile error occurs, the root cause is usually shown in the traceback log. + +# For example, the ``neg`` function is modified like this: + +def neg(x): + return f"-{x}" + + +# The logging gives the following compile error with a rather clear reason. In this case, the root cause is that data types of maximum's inputs are inconsistent. + +# .. code:: shell + +# … +# torch._dynamo.exc.BackendCompilerFailed: backend='inductor' raised: +# CppCompileError: C++ compile error +# … +# /tmp/torchinductor_root/2x/c2xgxsooklulr4u54etfnnha7dsu6xzbwdscttvs7dkpba3uwkem.cpp: In function ‘void kernel(const unsigned char*, const unsigned char*, unsigned char*)’: +# /tmp/torchinductor_root/2x/c2xgxsooklulr4u54etfnnha7dsu6xzbwdscttvs7dkpba3uwkem.cpp:14:53: error: no matching function for call to ‘max_propagate_nan(unsigned char&, int&)’ +# 14 | auto tmp3 = max_propagate_nan(tmp0, tmp2); +# | ^ +# In file included from /tmp/torchinductor_root/2x/c2xgxsooklulr4u54etfnnha7dsu6xzbwdscttvs7dkpba3uwkem.cpp:2: +# /tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h:27:17: note: candidate: ‘template 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/2x/c2xgxsooklulr4u54etfnnha7dsu6xzbwdscttvs7dkpba3uwkem.cpp:14:53: note: deduced conflicting types for parameter ‘scalar_t’ (‘unsigned char’ and ‘int’) +# 14 | auto tmp3 = max_propagate_nan(tmp0, tmp2); +# | ^ + + +# Otherwise, if the model runs with other errors, we can do the model code reduction until finding the minimum code snippet with failure. Thus, the target operators and kernels are located. + + +# Accuracy debugging +# ^^^^^^^^^^^^^^^^^^^ + +# The accuracy problem refers the case where outputs of backends eager and inductor are different. As FX graph is generated before Inductor and output code is generated after Inductor, we can narrow down the problem by comparing their outputs. + +# If a model has several graphs, the first step is to compare the final outputs of FX graph and output code for each graph, given the same input. The target is to find the first graph occurring error or with different outputs. Binary search is suggested to use for efficiency. + +# When a model has only one graph or the problematic graph has been found with the above step, compare the intermediate outputs of FX graph and output code in each graph, given the same input. The idea is to continuously narrow down the problem. + +# For example, we modify the ``neg`` function like this: + +def neg(x): + return f"decltype({x})(2 * {x})" + + +# An accuracy problem would be raised as follows. + +# .. code:: shell + +# torch._dynamo.utils: [ERROR] Accuracy failed: allclose not within tol=0.0001 +# Traceback (most recent call last): +# File "test_script.py", line 18, in +# assert same(expected_result, actual_result) == True +# AssertionError + + +# By comparing the intermediate outputs of FX graph and output code, it would be found that outputs are already different after doing ``torch.neg``. + +# Here are the modifications of FX graph and output code: + +# **Changes of teh FX graph:** + +# Before +class Repro(torch.nn.Module): + def __init__(self): + super().__init__() + + def forward(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,) + +# After +class Repro(torch.nn.Module): + def __init__(self): + super().__init__() + + def forward(self, arg0_1, arg1_1): + neg = torch.ops.aten.neg.default(arg0_1); arg0_1 = None + return (neg,) + + +# **Changes of the output code:** + +# Before +cpp_fused_cat_maximum_neg_0 = async_compile.cpp(''' +#include "/tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h" +extern "C" void kernel(const long* in_ptr0, + const long* in_ptr1, + long* out_ptr0) +{ + { + #pragma GCC ivdep + for(long i0=static_cast(0L); i0(8390L); i0+=static_cast(1L)) + { + auto tmp0 = in_ptr0[static_cast(i0)]; + auto tmp1 = in_ptr1[static_cast(0L)]; + auto tmp2 = decltype(tmp1)(2 * tmp1); + auto tmp3 = max_propagate_nan(tmp0, tmp2); + out_ptr0[static_cast(i0)] = tmp3; + } + } +} +''') + +def call(args): + arg0_1, arg1_1 = args + args.clear() + buf0 = empty_strided((8390, ), (1, ), device='cpu', dtype=torch.int64) + cpp_fused_cat_maximum_neg_0(c_void_p(arg1_1.data_ptr()), c_void_p(arg0_1.data_ptr()), c_void_p(buf0.data_ptr())) + del arg0_1 + del arg1_1 + return (buf0, ) + +# After +cpp_fused_cat_maximum_neg_0 = async_compile.cpp(''' +#include "/tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h" +extern "C" void kernel(const long* in_ptr0, + const long* in_ptr1, + long* out_ptr0) +{ + { + auto tmp1 = in_ptr1[static_cast(0L)]; + auto tmp2 = decltype(tmp1)(2 * tmp1); + out_ptr0[static_cast(0L)] = tmp2; + } +} +''') + +def call(args): + arg0_1, arg1_1 = args + args.clear() + buf0 = empty_strided((1, ), (1, ), device='cpu', dtype=torch.int64) + cpp_fused_cat_maximum_neg_0(c_void_p(arg1_1.data_ptr()), c_void_p(arg0_1.data_ptr()), c_void_p(buf0.data_ptr())) + del arg0_1 + del arg1_1 + return (buf0, ) + + +# You can use the PyTorch debugging tool called `Minifier `_. It helps to automatically generate a minified problematic graph. + + +###################################################################### +# Performance profiling +# --------------------- + +# For this part, we will describe how to analyze the inductor model performance. +# Firsly, we choose an eager model as a baseline. We set up a benchmark to compare the end to end performance between eager model and inductor model. + +from transformers import T5ForConditionalGeneration +# init an eager model +eager_model = T5ForConditionalGeneration.from_pretrained("t5-small") +seq_length = 1024 +bs = 4 +vocab_size = model.config.vocab_size +input = torch.randint(0, vocab_size, (bs, seq_length), dtype=torch.int64) +input_dict = {"input_ids": input} +input_dict["decoder_input_ids"] = input +# init inductor model +inductor_model = torch.compile(model) +compiled(**input_dict) +eager_t = 0 +inductor_t = 0 +for _ in range(100): + model(**input_dict) +for _ in range(1000): + eager_start = time.time() + model(**input_dict) + eager_end = time.time() + eager_t += eager_end - eager_start + +for _ in range(100): + model(**input_dict) +for _ in range(1000): + inductor_start = time.time() + compiled(**input_dict) + inductor_end = time.time() + inductor_t += inductor_end - inductor_start + +print(model.__class__) +print("eager use:", eager_t) +print("inductor use:", inductor_t) +print("ratio:", eager_t / inductor_t) + +# Output: + +# .. code-block:: shell + +# eager use: 410.12550354003906 +# inductor use: 478.59081745147705 +# ratio: 0.8569439458198976 + +# We see that the inductor model execution time is longer than the eager model, which does not meet our expectation. +# To deep dive op-level performance, we can use `Pytorch Profiler `_ + +# To enable kernel profile in inductor, we need set ``enable_kernel_profile`` by: + +from torch._inductor import config +config.cpp.enable_kernel_profile = True + +# Following the steps in `Pytorch Profiler `_ +# we are able to get the profiling table and trace files. + +from torch.profiler import profile, schedule, ProfilerActivity +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(RESULT_DIR + "/" + str(p.step_num) + ".json") + +for _ in range(nwarmup): + model(**input_dict) + +total = 0 +with profile( + activities=[ProfilerActivity.CPU], + schedule=my_schedule, + on_trace_ready=trace_handler +) as p: + for _ in range(100): + begin = time.time() + model(**input_dict) + end=time.time() + total += (end - begin) + p.step() +print("latency: {} ms".format(1000*(total)/100)) + +# We will get the following profile tables for eager model: + +# .. code-block:: shell + +# ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ +# Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls +# ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ +# aten::mm 33.33% 138.616ms 33.33% 138.616ms 1.429ms 97 +# aten::add_ 19.38% 80.596ms 19.38% 80.596ms 4.242ms 19 +# aten::bmm 18.78% 78.104ms 18.78% 78.104ms 2.170ms 36 +# aten::_softmax 11.32% 47.082ms 11.32% 47.082ms 2.616ms 18 +# aten::copy_ 3.89% 16.190ms 3.89% 16.190ms 103.121us 157 +# ProfilerStep* 3.53% 14.702ms 100.00% 415.949ms 415.949ms 1 +# aten::add 2.37% 9.849ms 2.39% 9.958ms 144.319us 69 +# aten::mul 1.13% 4.693ms 1.14% 4.726ms 65.639us 72 +# aten::clamp_min 0.85% 3.541ms 0.85% 3.541ms 295.083us 12 +# aten::index_select 0.84% 3.480ms 1.06% 4.401ms 1.100ms 4 +# aten::linear 0.63% 2.637ms 33.95% 141.194ms 1.456ms 97 +# aten::pow 0.61% 2.520ms 0.61% 2.554ms 79.812us 32 +# aten::matmul 0.50% 2.067ms 56.53% 235.132ms 1.768ms 133 +# aten::select 0.22% 900.000us 0.22% 910.000us 113.750us 8 +# aten::log 0.18% 740.000us 0.18% 740.000us 370.000us 2 +# aten::_unsafe_view 0.17% 718.000us 0.17% 718.000us 3.840us 187 +# aten::sum 0.17% 715.000us 0.20% 831.000us 25.969us 32 +# aten::transpose 0.15% 642.000us 0.18% 741.000us 3.963us 187 +# aten::reshape 0.15% 622.000us 3.66% 15.241ms 88.098us 173 +# aten::fill_ 0.15% 613.000us 0.15% 613.000us 15.718us 39 +# ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ +# Self CPU time total: 415.949ms + +# Similarly, get the table for the inductor model: + +# .. code-block:: shell + +# -------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ +# Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls +# -------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ +# mkl::_mkl_linear 28.24% 133.979ms 28.39% 134.689ms 1.389ms 97 +# aten::bmm 15.65% 74.250ms 15.65% 74.251ms 2.063ms 36 +# graph_0_cpp_fused__softmax_7 4.24% 20.123ms 4.24% 20.123ms 20.123ms 1 +# graph_0_cpp_fused__softmax_42 4.17% 19.773ms 4.17% 19.773ms 19.773ms 1 +# graph_0_cpp_fused__softmax_35 4.16% 19.751ms 4.16% 19.751ms 19.751ms 1 +# graph_0_cpp_fused__softmax_21 4.15% 19.674ms 4.15% 19.674ms 19.674ms 1 +# graph_0_cpp_fused__softmax_14 4.14% 19.654ms 4.14% 19.654ms 19.654ms 1 +# graph_0_cpp_fused__softmax_28 4.13% 19.576ms 4.13% 19.576ms 19.576ms 1 +# graph_0_cpp_fused__softmax_56 2.83% 13.404ms 2.83% 13.404ms 13.404ms 1 +# graph_0_cpp_fused__softmax_80 2.82% 13.371ms 2.82% 13.371ms 13.371ms 1 +# graph_0_cpp_fused__softmax_68 2.81% 13.323ms 2.81% 13.323ms 13.323ms 1 +# graph_0_cpp_fused__softmax_92 2.80% 13.297ms 2.80% 13.297ms 13.297ms 1 +# graph_0_cpp_fused__softmax_104 2.78% 13.208ms 2.78% 13.208ms 13.208ms 1 +# graph_0_cpp_fused__softmax_2 2.63% 12.468ms 2.63% 12.468ms 12.468ms 1 +# ProfilerStep* 1.61% 7.616ms 100.00% 474.360ms 474.360ms 1 +# graph_0_cpp_fused__softmax_73 0.49% 2.320ms 0.49% 2.320ms 2.320ms 1 +# graph_0_cpp_fused__softmax_85 0.49% 2.309ms 0.49% 2.309ms 2.309ms 1 +# graph_0_cpp_fused__softmax_97 0.48% 2.283ms 0.48% 2.283ms 2.283ms 1 +# graph_0_cpp_fused__softmax_61 0.48% 2.268ms 0.48% 2.268ms 2.268ms 1 +# graph_0_cpp_fused__softmax_49 0.48% 2.255ms 0.48% 2.255ms 2.255ms 1 +# -------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ +# Self CPU time total: 474.360ms + +# We can search the most time consuming ``graph_0_cpp_fused__softmax_7`` in ``output_code.py`` to see the generated code: + + +cpp_fused__softmax_7 = async_compile.cpp(''' +#include +#include "/tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h" +extern "C" void kernel(float* in_out_ptr0, + const float* in_ptr1, + float* out_ptr0, + float* out_ptr1) +{ + RECORD_FUNCTION("graph_0_cpp_fused__softmax_7", c10::ArrayRef({})); + auto in_ptr0 = in_out_ptr0; + #pragma omp parallel num_threads(32) + { + { + #pragma omp for collapse(2) + for(long i0=static_cast(0L); i0(4L); i0+=static_cast(1L)) + { + for(long i1=static_cast(0L); i1(8L); i1+=static_cast(1L)) + { + #pragma GCC ivdep + for(long i2=static_cast(0L); i2(1024L); i2+=static_cast(1L)) + { + { + float tmp_acc0 = -std::numeric_limits::infinity(); + for(long i3=static_cast(0L); i3(1024L); i3+=static_cast(1L)) + { + auto tmp0 = in_ptr0[static_cast(i3 + (1024L*i2) + (1048576L*i1) + (8388608L*i0))]; + auto tmp1 = static_cast(i3 + ((-1L)*i2)); + auto tmp2 = static_cast(0); + auto tmp3 = tmp1 > tmp2; + auto tmp4 = static_cast(tmp3); + auto tmp5 = static_cast(16); + auto tmp6 = decltype(tmp4)(tmp4 * tmp5); + auto tmp7 = tmp6 + tmp2; + auto tmp8 = std::abs(tmp1); + auto tmp9 = static_cast(8); + auto tmp10 = tmp8 < tmp9; + auto tmp11 = static_cast(tmp8); + auto tmp12 = static_cast(8.0); + auto tmp13 = tmp11 / tmp12; + auto tmp14 = std::log(tmp13); + auto tmp15 = static_cast(2.772588722239781); + auto tmp16 = tmp14 / tmp15; + auto tmp17 = decltype(tmp16)(tmp16 * tmp12); + auto tmp18 = static_cast(tmp17); + auto tmp19 = tmp18 + tmp9; + auto tmp20 = static_cast(15); + auto tmp21 = min_propagate_nan(tmp19, tmp20); + auto tmp22 = tmp10 ? tmp8 : tmp21; + auto tmp23 = tmp7 + tmp22; + auto tmp24 = in_ptr1[static_cast(i1 + (8L*tmp23))]; + auto tmp25 = static_cast(0.0); + auto tmp26 = tmp24 + tmp25; + auto tmp27 = tmp0 + tmp26; + tmp_acc0 = max_propagate_nan(tmp_acc0, tmp27); + } + out_ptr0[static_cast(i2 + (1024L*i1) + (8192L*i0))] = tmp_acc0; + } + } + } + } + } + { + #pragma omp for collapse(2) + for(long i0=static_cast(0L); i0(4L); i0+=static_cast(1L)) + { + for(long i1=static_cast(0L); i1(8L); i1+=static_cast(1L)) + { + #pragma GCC ivdep + for(long i2=static_cast(0L); i2(1024L); i2+=static_cast(1L)) + { + #pragma GCC ivdep + for(long i3=static_cast(0L); i3(1024L); i3+=static_cast(1L)) + { + auto tmp0 = in_out_ptr0[static_cast(i3 + (1024L*i2) + (1048576L*i1) + (8388608L*i0))]; + auto tmp28 = out_ptr0[static_cast(i2 + (1024L*i1) + (8192L*i0))]; + auto tmp1 = static_cast(i3 + ((-1L)*i2)); + auto tmp2 = static_cast(0); + auto tmp3 = tmp1 > tmp2; + auto tmp4 = static_cast(tmp3); + auto tmp5 = static_cast(16); + auto tmp6 = decltype(tmp4)(tmp4 * tmp5); + auto tmp7 = tmp6 + tmp2; + auto tmp8 = std::abs(tmp1); + auto tmp9 = static_cast(8); + auto tmp10 = tmp8 < tmp9; + auto tmp11 = static_cast(tmp8); + auto tmp12 = static_cast(8.0); + auto tmp13 = tmp11 / tmp12; + auto tmp14 = std::log(tmp13); + auto tmp15 = static_cast(2.772588722239781); + auto tmp16 = tmp14 / tmp15; + auto tmp17 = decltype(tmp16)(tmp16 * tmp12); + auto tmp18 = static_cast(tmp17); + auto tmp19 = tmp18 + tmp9; + auto tmp20 = static_cast(15); + auto tmp21 = min_propagate_nan(tmp19, tmp20); + auto tmp22 = tmp10 ? tmp8 : tmp21; + auto tmp23 = tmp7 + tmp22; + auto tmp24 = in_ptr1[static_cast(i1 + (8L*tmp23))]; + auto tmp25 = static_cast(0.0); + auto tmp26 = tmp24 + tmp25; + auto tmp27 = tmp0 + tmp26; + auto tmp29 = tmp27 - tmp28; + in_out_ptr0[static_cast(i3 + (1024L*i2) + (1048576L*i1) + (8388608L*i0))] = tmp29; + } + } + } + } + } + { + #pragma omp for + for(long i0=static_cast(0L); i0(33554432L); i0+=static_cast(16L)) + { + auto tmp0 = at::vec::Vectorized::loadu(in_out_ptr0 + static_cast(i0)); + auto tmp1 = tmp0.exp(); + tmp1.store(in_out_ptr0 + static_cast(i0)); + } + } + { + #pragma omp for + for(long i0=static_cast(0L); i0(32768L); i0+=static_cast(1L)) + { + { + #pragma omp declare reduction(+:at::vec::Vectorized:omp_out += omp_in) initializer(omp_priv={{0}}) + float tmp_acc0 = 0; + auto tmp_acc0_vec = at::vec::Vectorized(tmp_acc0); + for(long i1=static_cast(0L); i1(1024L); i1+=static_cast(16L)) + { + auto tmp0 = at::vec::Vectorized::loadu(in_out_ptr0 + static_cast(i1 + (1024L*i0))); + tmp_acc0_vec += tmp0; + } + tmp_acc0 += at::vec::vec_reduce_all([](at::vec::Vectorized& x, at::vec::Vectorized&y) {return x + y;}, tmp_acc0_vec); + out_ptr1[static_cast(i0)] = tmp_acc0; + } + } + } + } +} +''') + +# With the kernel name ``cpp_fused__softmax_*`` and considering the profile +# results together, we may suspect the generated code for ``softmax`` is +# inefficient. We encourage you to report an issue with all you findings above. + + +###################################################################### +# Future work +# ----------- + +# Implement and upstream the debug tools +# 1. **Graph merger**: Merge graphs of a model into a single large graph. Thus, graphs can be compared quickly between different versions of PyTorch. `#102958 `_ +# 2. **Graph matching**: In order to know what each kernel does, this tool matches C++ kernel with FX graph operators and adds corresponding operators before each kernel in the ``.cpp`` output code. `#102958 `_ +# 3. **Save inputs and outputs**: For the purpose of reproducing rapidly the failure of a large model, it is necessary to add serializations for the inputs and outputs among graphs and intermediate outputs in graphs. +# 4. **Test case generation**: When a user has found the operators which are inefficient with cpp kernels, a tool is needed to automatically write a test case. Specifically, one test case can be generated for each kernel, with the corresponding small FX graph and input. +# 5. **Minifier optimization**: Keep refining Minifier and make it adapted for more scenarios. diff --git a/intermediate_source/inductor_debug_cpu.rst b/intermediate_source/inductor_debug_cpu.rst deleted file mode 100644 index af49ec59f9..0000000000 --- a/intermediate_source/inductor_debug_cpu.rst +++ /dev/null @@ -1,617 +0,0 @@ -Inductor CPU backend debugging and profiling -============================================== - -**Author**: `Liao Xuan `_, `Zhu Haozhe `_ - -Usage --------------- - -Start with an example -^^^^^^^^^^^^^^^^^^^ - -Here is a simple example to run the ``torch.compile`` with Inductor. - -.. code-block:: python - - import torch - - def fn(x): - return torch.neg(x) - - x = torch.randn((2, 4, 28)) - compiled_fn = torch.compile(fn) # backend=inductor as default - result = compiled_fn(x) - -Get more loggings -^^^^^^^^^^^^^^^^^^^ - -However, the above code would not give any debugging info. If we want to get more useful logging, one way is to add an environment variable. - -.. code:: shell - - TORCH_COMPILE_DEBUG=1 python xx.py - -The time taken in each step is shown. This also does the graph visualization and prints the output code. In logging, a temperate debug tracing directory like this can be found. - -.. code:: shell - - torch._inductor.debug: [WARNING] model___20 debug trace: /tmp/torchinductor_root/rx/crxfi2ybd7yp5sbj2pnhw33wfhtdw7wumvrobyp5sjvdui5ktjc2.debug - -The directory saves several files for debugging. - -+-------------------------+----------------------------------------------------------+ -| fx_graph_readable.py | Readable FX graph, post decomps | -+-------------------------+----------------------------------------------------------+ -| fx_graph_runnable.py | Executable FX graph, post decomps, pre pattern match | -+-------------------------+----------------------------------------------------------+ -| fx_graph_transformed.py | Transformed FX graph, post pattern match | -+-------------------------+----------------------------------------------------------+ -| ir_post_fusion.txt | Inductor IR before fusion | -+-------------------------+----------------------------------------------------------+ -| ir_pre_fusion.txt | Inductor IR after fusion | -+-------------------------+----------------------------------------------------------+ -| output_code.py | Generated Python code for graph, with cpp/triton kernels | -+-------------------------+----------------------------------------------------------+ - - -``fx_graph_runnable.py`` and ``output_code.py`` are both runnable and editable in order to make debugging easier. - - -Here is another way to print logging for Inductor. - -.. code:: shell - - TORCH_LOGS="+inductor,output_code,schedule" python xx.py - -+--------------+-------------------------------------------------------------+ -| +inductor | Set the logging level of Inductor to DEBUG, default is INFO | -+--------------+-------------------------------------------------------------+ -| output_code | Print output code with cpp/triton kernels | -+--------------+-------------------------------------------------------------+ -| schedule | Print reasons for not doing vectorization in cpp kernels | -+--------------+-------------------------------------------------------------+ - -Configs to do deeper analysis -^^^^^^^^^^^^^^^^^^^ - -Moreover, there are several config parameters helping the analysis. - -+--------------------------------------------------+---------------------------------------------------------------------+ -| torch._inductor.config.max_fusion_size | Set the maximum number of nodes allowed in one fusion | -+--------------------------------------------------+---------------------------------------------------------------------+ -| torch._inductor.config.cpp.simdlen | Specify the bit width for cpp vectorization | -+--------------------------------------------------+---------------------------------------------------------------------+ -| torch._inductor.config.cpp.min_chunk_size | Set the minimum number of workloads one thread should at least take | -+--------------------------------------------------+---------------------------------------------------------------------+ -| torch._inductor.config.cpp.enable_kernel_profile | Allow cpp kernel performance profiling via profiler | -+--------------------------------------------------+---------------------------------------------------------------------+ - - -Debugging --------------- - -Determine component of error -^^^^^^^^^^^^^^^^^^^ - -When encountering errors or accuracy problem, a straightforward solution to find the bug is to narrow down the problem. The first thing to do is to determine the component where error occurs. Luckily, it can be simply achieved by changing the backend of ``torch.compile``. - -+----------------------------------------+-----------------------------------------+ -| torch.compile(fn, backend="eager") | Enable Dynamo | -+----------------------------------------+-----------------------------------------+ -| torch.compile(fn, backend="aot_eager") | Enable Dynamo + AOT autograd | -+----------------------------------------+-----------------------------------------+ -| torch.compile(fn, backend="inductor") | Enable Dynamo + AOT autograd + Inductor | -+----------------------------------------+-----------------------------------------+ - -If the model can successfully run when backend is eager or aot_eager while it fails with inductor, we can narrow down the failure to Inductor. - - -Example -^^^^^^^^^^^^^^^^^^^ - -Here is an example for the subsequent debugging. - -.. code-block:: python - - import torch - from torch._dynamo.utils import same - - def foo(x1, x2): - a = torch.neg(x1) - b = torch.maximum(x2, a) - y = torch.cat([b], dim=0) - return y - - x1 = torch.randint(256, (1,), dtype=torch.uint8) - x2 = torch.randint(256, (8390,), dtype=torch.uint8) - - expected_result = fn(x1, x2) - - compiled_fn = torch.compile(fn) - actual_result = compiled_fn(x1, x2) - - assert same(expected_result, actual_result) == True - - -The implementation of ``neg`` in cpp codegen is as follows. - -.. code-block:: python - - def neg(x): - return f"decltype({x})(-{x})" - - -In order to demonstrate the debugging, we will modify the function to a wrong one later. - -Errors debugging -^^^^^^^^^^^^^^^^^^^ - -If it occurs a compile error, the root cause is usually shown in traceback log. - -For example, the ``neg`` function is modified like this. - -.. code-block:: python - - def neg(x): - return f"-{x}" - - -The logging gives the following compile error with a rather clear reason. In this case, the root cause is that data types of maximum's inputs are inconsistent. - -.. code:: shell - - … - torch._dynamo.exc.BackendCompilerFailed: backend='inductor' raised: - CppCompileError: C++ compile error - … - /tmp/torchinductor_root/2x/c2xgxsooklulr4u54etfnnha7dsu6xzbwdscttvs7dkpba3uwkem.cpp: In function ‘void kernel(const unsigned char*, const unsigned char*, unsigned char*)’: - /tmp/torchinductor_root/2x/c2xgxsooklulr4u54etfnnha7dsu6xzbwdscttvs7dkpba3uwkem.cpp:14:53: error: no matching function for call to ‘max_propagate_nan(unsigned char&, int&)’ - 14 | auto tmp3 = max_propagate_nan(tmp0, tmp2); - | ^ - In file included from /tmp/torchinductor_root/2x/c2xgxsooklulr4u54etfnnha7dsu6xzbwdscttvs7dkpba3uwkem.cpp:2: - /tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h:27:17: note: candidate: ‘template 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/2x/c2xgxsooklulr4u54etfnnha7dsu6xzbwdscttvs7dkpba3uwkem.cpp:14:53: note: deduced conflicting types for parameter ‘scalar_t’ (‘unsigned char’ and ‘int’) - 14 | auto tmp3 = max_propagate_nan(tmp0, tmp2); - | ^ - - -Otherwise, if the model runs with other errors, we can do the model code reduction until finding the minimum code snippet with failure. Thus, the target operators and kernels are located. - - -Accuracy debugging -^^^^^^^^^^^^^^^^^^^ - -The accuracy problem refers the case where outputs of backends eager and inductor are different. As FX graph is generated before Inductor and output code is generated after Inductor, we can narrow down the problem by comparing their outputs. - -If a model has several graphs, the first step is to compare the final outputs of FX graph and output code for each graph, given the same input. The target is to find the first graph occurring error or with different outputs. Binary search is suggested to use for efficiency. - -When a model has only one graph or the problematic graph has been found with the above step, compare the intermediate outputs of FX graph and output code in each graph, given the same input. The idea is to continuously narrow down the problem. - -For example, we modify the ``neg`` function like this. - -.. code-block:: python - - def neg(x): - return f"decltype({x})(2 * {x})" - - -An accuracy problem would be raised as follows. - -.. code:: shell - - torch._dynamo.utils: [ERROR] Accuracy failed: allclose not within tol=0.0001 - Traceback (most recent call last): - File "test_script.py", line 18, in - assert same(expected_result, actual_result) == True - AssertionError - - -By comparing the intermediate outputs of FX graph and output code, it would be found that outputs are already different after doing ``torch.neg``. - -Specifically, the modifications of FX graph and output code are attached. - -*Change of FX graph* - -.. code-block:: python - - # Before - class Repro(torch.nn.Module): - def __init__(self): - super().__init__() - - def forward(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,) - - # After - class Repro(torch.nn.Module): - def __init__(self): - super().__init__() - - def forward(self, arg0_1, arg1_1): - neg = torch.ops.aten.neg.default(arg0_1); arg0_1 = None - return (neg,) - - -*Change of output code* - -.. code-block:: python - - # Before - cpp_fused_cat_maximum_neg_0 = async_compile.cpp(''' - #include "/tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h" - extern "C" void kernel(const long* in_ptr0, - const long* in_ptr1, - long* out_ptr0) - { - { - #pragma GCC ivdep - for(long i0=static_cast(0L); i0(8390L); i0+=static_cast(1L)) - { - auto tmp0 = in_ptr0[static_cast(i0)]; - auto tmp1 = in_ptr1[static_cast(0L)]; - auto tmp2 = decltype(tmp1)(2 * tmp1); - auto tmp3 = max_propagate_nan(tmp0, tmp2); - out_ptr0[static_cast(i0)] = tmp3; - } - } - } - ''') - - def call(args): - arg0_1, arg1_1 = args - args.clear() - buf0 = empty_strided((8390, ), (1, ), device='cpu', dtype=torch.int64) - cpp_fused_cat_maximum_neg_0(c_void_p(arg1_1.data_ptr()), c_void_p(arg0_1.data_ptr()), c_void_p(buf0.data_ptr())) - del arg0_1 - del arg1_1 - return (buf0, ) - - # After - cpp_fused_cat_maximum_neg_0 = async_compile.cpp(''' - #include "/tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h" - extern "C" void kernel(const long* in_ptr0, - const long* in_ptr1, - long* out_ptr0) - { - { - auto tmp1 = in_ptr1[static_cast(0L)]; - auto tmp2 = decltype(tmp1)(2 * tmp1); - out_ptr0[static_cast(0L)] = tmp2; - } - } - ''') - - def call(args): - arg0_1, arg1_1 = args - args.clear() - buf0 = empty_strided((1, ), (1, ), device='cpu', dtype=torch.int64) - cpp_fused_cat_maximum_neg_0(c_void_p(arg1_1.data_ptr()), c_void_p(arg0_1.data_ptr()), c_void_p(buf0.data_ptr())) - del arg0_1 - del arg1_1 - return (buf0, ) - - -Note that there exists a debugging tool provided by PyTorch, called `Minifier `_. It helps us automatically generate a minified problematic graph. - - -Performance profiling --------------- - -For this part, we are going to describe how to analyze torchinductor model performance. -Firsly, we choose an eager model as a baseline. We set up a benchmark to compare -the end to end performance between eager model and inductor model. - -.. code-block:: python - - from transformers import T5ForConditionalGeneration - # init an eager model - eager_model = T5ForConditionalGeneration.from_pretrained("t5-small") - seq_length = 1024 - bs = 4 - vocab_size = model.config.vocab_size - input = torch.randint(0, vocab_size, (bs, seq_length), dtype=torch.int64) - input_dict = {"input_ids": input} - input_dict["decoder_input_ids"] = input - # init inductor model - inductor_model = torch.compile(model) - compiled(**input_dict) - eager_t = 0 - inductor_t = 0 - for _ in range(100): - model(**input_dict) - for _ in range(1000): - eager_start = time.time() - model(**input_dict) - eager_end = time.time() - eager_t += eager_end - eager_start - - for _ in range(100): - model(**input_dict) - for _ in range(1000): - inductor_start = time.time() - compiled(**input_dict) - inductor_end = time.time() - inductor_t += inductor_end - inductor_start - - print(model.__class__) - print("eager use:", eager_t) - print("inductor use:", inductor_t) - print("ratio:", eager_t / inductor_t) - -Output: - -.. code-block:: shell - - eager use: 410.12550354003906 - inductor use: 478.59081745147705 - ratio: 0.8569439458198976 - -We see inductor model spent more time than eager model, which does not meet our expectation. -To deep dive op-level performance, we can use `Pytorch Profiler `_ - -To enable kernel profile in inductor, we need set ``enable_kernel_profile`` by: - -.. code-block:: python - - from torch._inductor import config - config.cpp.enable_kernel_profile = True - -Following the steps in `Pytorch Profiler `_ -we are able to get the profiling table and trace files. - -.. code-block:: python - - from torch.profiler import profile, schedule, ProfilerActivity - 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(RESULT_DIR + "/" + str(p.step_num) + ".json") - - for _ in range(nwarmup): - model(**input_dict) - - total = 0 - with profile( - activities=[ProfilerActivity.CPU], - schedule=my_schedule, - on_trace_ready=trace_handler - ) as p: - for _ in range(100): - begin = time.time() - model(**input_dict) - end=time.time() - total += (end - begin) - p.step() - print("latency: {} ms".format(1000*(total)/100)) - -We will get following profile tables for eager model - -.. code-block:: shell - - ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ - Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls - ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ - aten::mm 33.33% 138.616ms 33.33% 138.616ms 1.429ms 97 - aten::add_ 19.38% 80.596ms 19.38% 80.596ms 4.242ms 19 - aten::bmm 18.78% 78.104ms 18.78% 78.104ms 2.170ms 36 - aten::_softmax 11.32% 47.082ms 11.32% 47.082ms 2.616ms 18 - aten::copy_ 3.89% 16.190ms 3.89% 16.190ms 103.121us 157 - ProfilerStep* 3.53% 14.702ms 100.00% 415.949ms 415.949ms 1 - aten::add 2.37% 9.849ms 2.39% 9.958ms 144.319us 69 - aten::mul 1.13% 4.693ms 1.14% 4.726ms 65.639us 72 - aten::clamp_min 0.85% 3.541ms 0.85% 3.541ms 295.083us 12 - aten::index_select 0.84% 3.480ms 1.06% 4.401ms 1.100ms 4 - aten::linear 0.63% 2.637ms 33.95% 141.194ms 1.456ms 97 - aten::pow 0.61% 2.520ms 0.61% 2.554ms 79.812us 32 - aten::matmul 0.50% 2.067ms 56.53% 235.132ms 1.768ms 133 - aten::select 0.22% 900.000us 0.22% 910.000us 113.750us 8 - aten::log 0.18% 740.000us 0.18% 740.000us 370.000us 2 - aten::_unsafe_view 0.17% 718.000us 0.17% 718.000us 3.840us 187 - aten::sum 0.17% 715.000us 0.20% 831.000us 25.969us 32 - aten::transpose 0.15% 642.000us 0.18% 741.000us 3.963us 187 - aten::reshape 0.15% 622.000us 3.66% 15.241ms 88.098us 173 - aten::fill_ 0.15% 613.000us 0.15% 613.000us 15.718us 39 - ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ - Self CPU time total: 415.949ms - -And get above table for inductor model - -.. code-block:: shell - - ------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ - Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls - ------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ - mkl::_mkl_linear 28.24% 133.979ms 28.39% 134.689ms 1.389ms 97 - aten::bmm 15.65% 74.250ms 15.65% 74.251ms 2.063ms 36 - graph_0_cpp_fused__softmax_7 4.24% 20.123ms 4.24% 20.123ms 20.123ms 1 - graph_0_cpp_fused__softmax_42 4.17% 19.773ms 4.17% 19.773ms 19.773ms 1 - graph_0_cpp_fused__softmax_35 4.16% 19.751ms 4.16% 19.751ms 19.751ms 1 - graph_0_cpp_fused__softmax_21 4.15% 19.674ms 4.15% 19.674ms 19.674ms 1 - graph_0_cpp_fused__softmax_14 4.14% 19.654ms 4.14% 19.654ms 19.654ms 1 - graph_0_cpp_fused__softmax_28 4.13% 19.576ms 4.13% 19.576ms 19.576ms 1 - graph_0_cpp_fused__softmax_56 2.83% 13.404ms 2.83% 13.404ms 13.404ms 1 - graph_0_cpp_fused__softmax_80 2.82% 13.371ms 2.82% 13.371ms 13.371ms 1 - graph_0_cpp_fused__softmax_68 2.81% 13.323ms 2.81% 13.323ms 13.323ms 1 - graph_0_cpp_fused__softmax_92 2.80% 13.297ms 2.80% 13.297ms 13.297ms 1 - graph_0_cpp_fused__softmax_104 2.78% 13.208ms 2.78% 13.208ms 13.208ms 1 - graph_0_cpp_fused__softmax_2 2.63% 12.468ms 2.63% 12.468ms 12.468ms 1 - ProfilerStep* 1.61% 7.616ms 100.00% 474.360ms 474.360ms 1 - graph_0_cpp_fused__softmax_73 0.49% 2.320ms 0.49% 2.320ms 2.320ms 1 - graph_0_cpp_fused__softmax_85 0.49% 2.309ms 0.49% 2.309ms 2.309ms 1 - graph_0_cpp_fused__softmax_97 0.48% 2.283ms 0.48% 2.283ms 2.283ms 1 - graph_0_cpp_fused__softmax_61 0.48% 2.268ms 0.48% 2.268ms 2.268ms 1 - graph_0_cpp_fused__softmax_49 0.48% 2.255ms 0.48% 2.255ms 2.255ms 1 - ------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ - Self CPU time total: 474.360ms - -We can search the most time consuming ``graph_0_cpp_fused__softmax_7`` in ``output_code.py`` to see the generated code: - -.. code-block:: python - - cpp_fused__softmax_7 = async_compile.cpp(''' - #include - #include "/tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h" - extern "C" void kernel(float* in_out_ptr0, - const float* in_ptr1, - float* out_ptr0, - float* out_ptr1) - { - RECORD_FUNCTION("graph_0_cpp_fused__softmax_7", c10::ArrayRef({})); - auto in_ptr0 = in_out_ptr0; - #pragma omp parallel num_threads(32) - { - { - #pragma omp for collapse(2) - for(long i0=static_cast(0L); i0(4L); i0+=static_cast(1L)) - { - for(long i1=static_cast(0L); i1(8L); i1+=static_cast(1L)) - { - #pragma GCC ivdep - for(long i2=static_cast(0L); i2(1024L); i2+=static_cast(1L)) - { - { - float tmp_acc0 = -std::numeric_limits::infinity(); - for(long i3=static_cast(0L); i3(1024L); i3+=static_cast(1L)) - { - auto tmp0 = in_ptr0[static_cast(i3 + (1024L*i2) + (1048576L*i1) + (8388608L*i0))]; - auto tmp1 = static_cast(i3 + ((-1L)*i2)); - auto tmp2 = static_cast(0); - auto tmp3 = tmp1 > tmp2; - auto tmp4 = static_cast(tmp3); - auto tmp5 = static_cast(16); - auto tmp6 = decltype(tmp4)(tmp4 * tmp5); - auto tmp7 = tmp6 + tmp2; - auto tmp8 = std::abs(tmp1); - auto tmp9 = static_cast(8); - auto tmp10 = tmp8 < tmp9; - auto tmp11 = static_cast(tmp8); - auto tmp12 = static_cast(8.0); - auto tmp13 = tmp11 / tmp12; - auto tmp14 = std::log(tmp13); - auto tmp15 = static_cast(2.772588722239781); - auto tmp16 = tmp14 / tmp15; - auto tmp17 = decltype(tmp16)(tmp16 * tmp12); - auto tmp18 = static_cast(tmp17); - auto tmp19 = tmp18 + tmp9; - auto tmp20 = static_cast(15); - auto tmp21 = min_propagate_nan(tmp19, tmp20); - auto tmp22 = tmp10 ? tmp8 : tmp21; - auto tmp23 = tmp7 + tmp22; - auto tmp24 = in_ptr1[static_cast(i1 + (8L*tmp23))]; - auto tmp25 = static_cast(0.0); - auto tmp26 = tmp24 + tmp25; - auto tmp27 = tmp0 + tmp26; - tmp_acc0 = max_propagate_nan(tmp_acc0, tmp27); - } - out_ptr0[static_cast(i2 + (1024L*i1) + (8192L*i0))] = tmp_acc0; - } - } - } - } - } - { - #pragma omp for collapse(2) - for(long i0=static_cast(0L); i0(4L); i0+=static_cast(1L)) - { - for(long i1=static_cast(0L); i1(8L); i1+=static_cast(1L)) - { - #pragma GCC ivdep - for(long i2=static_cast(0L); i2(1024L); i2+=static_cast(1L)) - { - #pragma GCC ivdep - for(long i3=static_cast(0L); i3(1024L); i3+=static_cast(1L)) - { - auto tmp0 = in_out_ptr0[static_cast(i3 + (1024L*i2) + (1048576L*i1) + (8388608L*i0))]; - auto tmp28 = out_ptr0[static_cast(i2 + (1024L*i1) + (8192L*i0))]; - auto tmp1 = static_cast(i3 + ((-1L)*i2)); - auto tmp2 = static_cast(0); - auto tmp3 = tmp1 > tmp2; - auto tmp4 = static_cast(tmp3); - auto tmp5 = static_cast(16); - auto tmp6 = decltype(tmp4)(tmp4 * tmp5); - auto tmp7 = tmp6 + tmp2; - auto tmp8 = std::abs(tmp1); - auto tmp9 = static_cast(8); - auto tmp10 = tmp8 < tmp9; - auto tmp11 = static_cast(tmp8); - auto tmp12 = static_cast(8.0); - auto tmp13 = tmp11 / tmp12; - auto tmp14 = std::log(tmp13); - auto tmp15 = static_cast(2.772588722239781); - auto tmp16 = tmp14 / tmp15; - auto tmp17 = decltype(tmp16)(tmp16 * tmp12); - auto tmp18 = static_cast(tmp17); - auto tmp19 = tmp18 + tmp9; - auto tmp20 = static_cast(15); - auto tmp21 = min_propagate_nan(tmp19, tmp20); - auto tmp22 = tmp10 ? tmp8 : tmp21; - auto tmp23 = tmp7 + tmp22; - auto tmp24 = in_ptr1[static_cast(i1 + (8L*tmp23))]; - auto tmp25 = static_cast(0.0); - auto tmp26 = tmp24 + tmp25; - auto tmp27 = tmp0 + tmp26; - auto tmp29 = tmp27 - tmp28; - in_out_ptr0[static_cast(i3 + (1024L*i2) + (1048576L*i1) + (8388608L*i0))] = tmp29; - } - } - } - } - } - { - #pragma omp for - for(long i0=static_cast(0L); i0(33554432L); i0+=static_cast(16L)) - { - auto tmp0 = at::vec::Vectorized::loadu(in_out_ptr0 + static_cast(i0)); - auto tmp1 = tmp0.exp(); - tmp1.store(in_out_ptr0 + static_cast(i0)); - } - } - { - #pragma omp for - for(long i0=static_cast(0L); i0(32768L); i0+=static_cast(1L)) - { - { - #pragma omp declare reduction(+:at::vec::Vectorized:omp_out += omp_in) initializer(omp_priv={{0}}) - float tmp_acc0 = 0; - auto tmp_acc0_vec = at::vec::Vectorized(tmp_acc0); - for(long i1=static_cast(0L); i1(1024L); i1+=static_cast(16L)) - { - auto tmp0 = at::vec::Vectorized::loadu(in_out_ptr0 + static_cast(i1 + (1024L*i0))); - tmp_acc0_vec += tmp0; - } - tmp_acc0 += at::vec::vec_reduce_all([](at::vec::Vectorized& x, at::vec::Vectorized&y) {return x + y;}, tmp_acc0_vec); - out_ptr1[static_cast(i0)] = tmp_acc0; - } - } - } - } - } - ''') - -With the kernel name ``cpp_fused__softmax_*`` and considering the profile -results together, we may suspect the generated code for ``softmax`` is -inefficient. We encourage you to report an issue with all you findings above. - - -Future work --------------- - -Implement and up-stream the debug tools - 1. **Graph merger**: Merge graphs of a model into a single large graph. Thus, graphs can be compared quickly between different versions of PyTorch. `#102958 `_ - 2. **Graph matching**: In order to know what each kernel does, this tool matches cpp kernel with FX graph operators and adds corresponding operators before each kernel in cpp output code. `#102958 `_ - 3. **Save inputs and outputs**: For the purpose of reproducing rapidly the failure of a large model, it is necessary to add serializations for the inputs and outputs among graphs and intermediate outputs in graphs. - 4. **Test case generation**: When a user has found the operators which are inefficient with cpp kernels, a tool is needed to automatically write a test case. Specifically, one test case can be generated for each kernel, with the corresponding small FX graph and input. - 5. **Minifier optimization**: Keep refining Minifier and make it adapted for more scenarios. From 851f3da3f6dac5f4c7a46bc74213a1f754940962 Mon Sep 17 00:00:00 2001 From: "Liao, Xuan" Date: Wed, 7 Jun 2023 14:40:28 +0000 Subject: [PATCH 06/43] Add overview --- intermediate_source/inductor_debug_cpu.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index d0385a9e4c..087c63af97 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -11,7 +11,16 @@ # Overview # -------- -# This document is intended to introduce the usage, debugging and performance profiling for ``torch.compile`` with Inductor CPU backend. You can learn how to print useful loggings, how to debug errors and what to do when the performance is not good. +# This document is intended to introduce the usage, debugging and performance profiling for ``torch.compile`` with Inductor CPU backend. +# 1. For the usage, we will show how to print debugging loggings and how to inductor an in-depth analysis with config parameters. +# 2. For debugging, we will demonstrate the process to debug a functional failure. There are usually two types of functional failure. +# One is the error occurring during running. It prevents a model from giving the final result, such as compilation error and runtime error. +# The other is the accuracy problem. The model gives a final result, but the value is wrong. We usually compare the result of inductor with that of eager. +# The main idea of debugging is to narrow down the problem. We firstly determine that the failure occurs in inductor and then try to find the minimum code snippet with failure. +# 3. For the profiling, we will show what to do when the performance is not good. +# This tutorial will walk you through the process of profiling, including how to find the time-consuming hotpot and determine the root cause. There are two typical scenarios for performance profiling. +# One is the case where the execution time with inductor is longer than that of eager. The other is the model regression between two PyTorch versions where both FX graph and output code could change. +# 4. In the final part, we will propose several debugging tools to be implemented and upstreamt in the future. # Here is a simple example to run the ``torch.compile`` with Inductor. From 136f933d4fb9b742d6abc28738bb2194cb5bbc54 Mon Sep 17 00:00:00 2001 From: "Liao, Xuan" Date: Wed, 7 Jun 2023 14:52:17 +0000 Subject: [PATCH 07/43] fix py format --- intermediate_source/inductor_debug_cpu.py | 130 +++++++++++----------- 1 file changed, 68 insertions(+), 62 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index 087c63af97..df3f0acca9 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -10,7 +10,7 @@ ######################################################################### # Overview # -------- - +# # This document is intended to introduce the usage, debugging and performance profiling for ``torch.compile`` with Inductor CPU backend. # 1. For the usage, we will show how to print debugging loggings and how to inductor an in-depth analysis with config parameters. # 2. For debugging, we will demonstrate the process to debug a functional failure. There are usually two types of functional failure. @@ -21,7 +21,7 @@ # This tutorial will walk you through the process of profiling, including how to find the time-consuming hotpot and determine the root cause. There are two typical scenarios for performance profiling. # One is the case where the execution time with inductor is longer than that of eager. The other is the model regression between two PyTorch versions where both FX graph and output code could change. # 4. In the final part, we will propose several debugging tools to be implemented and upstreamt in the future. - +# # Here is a simple example to run the ``torch.compile`` with Inductor. import torch @@ -35,21 +35,21 @@ def fn(x): # Get more loggings # ^^^^^^^^^^^^^^^^^ - +# # The simple example above would not give any debugging info. If you'd want to get more useful logging, you can add a ``TORCH_COMPILE_DEBUG`` environment variable: - +# # .. code:: shell - +# # TORCH_COMPILE_DEBUG=1 python xx.py - +# # The time taken in each step is shown. This also does the graph visualization and prints the output code. In logging, a temporary debug tracing directory like this can be found. - +# # .. code:: shell - +# # torch._inductor.debug: [WARNING] model___20 debug trace: /tmp/torchinductor_root/rx/crxfi2ybd7yp5sbj2pnhw33wfhtdw7wumvrobyp5sjvdui5ktjc2.debug - +# # In this directory, the following files are saved for debugging purposes. - +# # +-------------------------+----------------------------------------------------------+ # | File | Description | # +-------------------------+----------------------------------------------------------+ @@ -63,17 +63,15 @@ def fn(x): # +-------------------------+----------------------------------------------------------+ # | output_code.py | Generated Python code for graph, with cpp/triton kernels | # +-------------------------+----------------------------------------------------------+ - - +# # ``fx_graph_runnable.py`` and ``output_code.py`` are both runnable and editable in order to make debugging easier. - - +# # Here is another way to print logging for Inductor: - +# # .. code:: shell - +# # TORCH_LOGS="+inductor,output_code,schedule" python xx.py - +# # +--------------+-------------------------------------------------------------+ # | Parameter | Description | # +--------------+-------------------------------------------------------------+ @@ -83,12 +81,12 @@ def fn(x): # +--------------+-------------------------------------------------------------+ # | schedule | Print reasons for not doing vectorization in cpp kernels | # +--------------+-------------------------------------------------------------+ - +# # Conducting an in-depth analysis # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - +# # Moreover, there are several config parameters helping the analysis. - +# # +--------------------------------------------------+---------------------------------------------------------------------+ # | Parameter | Description | # +--------------------------------------------------+---------------------------------------------------------------------+ @@ -105,12 +103,12 @@ def fn(x): ###################################################################### # Debugging # --------- - +# # Determine component of error # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - +# # When encountering errors or an accuracy problem, a straightforward solution to find the bug is to narrow down the problem. The first thing to do is to determine the component where the error occurs. Luckily, it can be simply achieved by changing the backend of ``torch.compile``. - +# # +----------------------------------------+-----------------------------------------+ # | Code | Description | # +----------------------------------------+-----------------------------------------+ @@ -120,13 +118,13 @@ def fn(x): # +----------------------------------------+-----------------------------------------+ # | torch.compile(fn, backend="inductor") | Enable Dynamo + AOT autograd + Inductor | # +----------------------------------------+-----------------------------------------+ - +# # If the model can successfully run when the backend is set to ``eager`` or ``aot_eager`` while it fails with ``inductor``, we can narrow down the failure to Inductor. - - +# +# # Example # ^^^^^^^ - +# # Here is an example for the subsequent debugging: import torch @@ -148,29 +146,32 @@ def foo(x1, x2): assert same(expected_result, actual_result) == True +###################################################################### # The implementation of ``neg`` in the ``cpp`` codegen is as follows: def neg(x): return f"decltype({x})(-{x})" +###################################################################### # In order to demonstrate the debugging, we will modify the function to a wrong one later. - - +# +# # Errors debugging # ^^^^^^^^^^^^^^^^ - +# # If a compile error occurs, the root cause is usually shown in the traceback log. - +# # For example, the ``neg`` function is modified like this: def neg(x): return f"-{x}" +###################################################################### # The logging gives the following compile error with a rather clear reason. In this case, the root cause is that data types of maximum's inputs are inconsistent. - +# # .. code:: shell - +# # … # torch._dynamo.exc.BackendCompilerFailed: backend='inductor' raised: # CppCompileError: C++ compile error @@ -187,41 +188,42 @@ def neg(x): # /tmp/torchinductor_root/2x/c2xgxsooklulr4u54etfnnha7dsu6xzbwdscttvs7dkpba3uwkem.cpp:14:53: note: deduced conflicting types for parameter ‘scalar_t’ (‘unsigned char’ and ‘int’) # 14 | auto tmp3 = max_propagate_nan(tmp0, tmp2); # | ^ - - +# +# # Otherwise, if the model runs with other errors, we can do the model code reduction until finding the minimum code snippet with failure. Thus, the target operators and kernels are located. - - +# +# # Accuracy debugging # ^^^^^^^^^^^^^^^^^^^ - +# # The accuracy problem refers the case where outputs of backends eager and inductor are different. As FX graph is generated before Inductor and output code is generated after Inductor, we can narrow down the problem by comparing their outputs. - +# # If a model has several graphs, the first step is to compare the final outputs of FX graph and output code for each graph, given the same input. The target is to find the first graph occurring error or with different outputs. Binary search is suggested to use for efficiency. - +# # When a model has only one graph or the problematic graph has been found with the above step, compare the intermediate outputs of FX graph and output code in each graph, given the same input. The idea is to continuously narrow down the problem. - +# # For example, we modify the ``neg`` function like this: def neg(x): return f"decltype({x})(2 * {x})" +###################################################################### # An accuracy problem would be raised as follows. - +# # .. code:: shell - +# # torch._dynamo.utils: [ERROR] Accuracy failed: allclose not within tol=0.0001 # Traceback (most recent call last): # File "test_script.py", line 18, in # assert same(expected_result, actual_result) == True # AssertionError - - +# +# # By comparing the intermediate outputs of FX graph and output code, it would be found that outputs are already different after doing ``torch.neg``. - +# # Here are the modifications of FX graph and output code: - +# # **Changes of teh FX graph:** # Before @@ -244,7 +246,7 @@ def forward(self, arg0_1, arg1_1): neg = torch.ops.aten.neg.default(arg0_1); arg0_1 = None return (neg,) - +###################################################################### # **Changes of the output code:** # Before @@ -301,14 +303,14 @@ def call(args): del arg1_1 return (buf0, ) - +###################################################################### # You can use the PyTorch debugging tool called `Minifier `_. It helps to automatically generate a minified problematic graph. ###################################################################### # Performance profiling # --------------------- - +# # For this part, we will describe how to analyze the inductor model performance. # Firsly, we choose an eager model as a baseline. We set up a benchmark to compare the end to end performance between eager model and inductor model. @@ -346,23 +348,25 @@ def call(args): print("eager use:", eager_t) print("inductor use:", inductor_t) print("ratio:", eager_t / inductor_t) - -# Output: +###################################################################### +# Output: +# # .. code-block:: shell - +# # eager use: 410.12550354003906 # inductor use: 478.59081745147705 # ratio: 0.8569439458198976 - +# # We see that the inductor model execution time is longer than the eager model, which does not meet our expectation. # To deep dive op-level performance, we can use `Pytorch Profiler `_ - +# # To enable kernel profile in inductor, we need set ``enable_kernel_profile`` by: from torch._inductor import config config.cpp.enable_kernel_profile = True +###################################################################### # Following the steps in `Pytorch Profiler `_ # we are able to get the profiling table and trace files. @@ -396,10 +400,11 @@ def trace_handler(p): p.step() print("latency: {} ms".format(1000*(total)/100)) +###################################################################### # We will get the following profile tables for eager model: - +# # .. code-block:: shell - +# # ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ # Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls # ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ @@ -425,11 +430,11 @@ def trace_handler(p): # aten::fill_ 0.15% 613.000us 0.15% 613.000us 15.718us 39 # ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ # Self CPU time total: 415.949ms - +# # Similarly, get the table for the inductor model: - +# # .. code-block:: shell - +# # -------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ # Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls # -------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ @@ -455,7 +460,7 @@ def trace_handler(p): # graph_0_cpp_fused__softmax_49 0.48% 2.255ms 0.48% 2.255ms 2.255ms 1 # -------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ # Self CPU time total: 474.360ms - +# # We can search the most time consuming ``graph_0_cpp_fused__softmax_7`` in ``output_code.py`` to see the generated code: @@ -599,6 +604,7 @@ def trace_handler(p): } ''') +###################################################################### # With the kernel name ``cpp_fused__softmax_*`` and considering the profile # results together, we may suspect the generated code for ``softmax`` is # inefficient. We encourage you to report an issue with all you findings above. @@ -607,7 +613,7 @@ def trace_handler(p): ###################################################################### # Future work # ----------- - +# # Implement and upstream the debug tools # 1. **Graph merger**: Merge graphs of a model into a single large graph. Thus, graphs can be compared quickly between different versions of PyTorch. `#102958 `_ # 2. **Graph matching**: In order to know what each kernel does, this tool matches C++ kernel with FX graph operators and adds corresponding operators before each kernel in the ``.cpp`` output code. `#102958 `_ From 1f268d78c8f970b87049fe688d0ba06c1b68c63c Mon Sep 17 00:00:00 2001 From: "Liao, Xuan" Date: Wed, 7 Jun 2023 15:50:04 +0000 Subject: [PATCH 08/43] fix py format --- intermediate_source/inductor_debug_cpu.py | 1 + 1 file changed, 1 insertion(+) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index df3f0acca9..40bc736334 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -33,6 +33,7 @@ def fn(x): compiled_fn = torch.compile(fn) # backend=inductor as default result = compiled_fn(x) +######################################################################### # Get more loggings # ^^^^^^^^^^^^^^^^^ # From c30de315821fff33e94173652cae20637cdca25a Mon Sep 17 00:00:00 2001 From: zhuhaozhe Date: Thu, 8 Jun 2023 14:42:47 +0800 Subject: [PATCH 09/43] replace the profiling example (#4) --- intermediate_source/inductor_debug_cpu.py | 400 ++++++++++------------ 1 file changed, 174 insertions(+), 226 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index 40bc736334..d418deda18 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -313,65 +313,66 @@ def call(args): # --------------------- # # For this part, we will describe how to analyze the inductor model performance. -# Firsly, we choose an eager model as a baseline. We set up a benchmark to compare the end to end performance between eager model and inductor model. +# First, we choose an eager model as a baseline. We set up a benchmark to compare the end-to-end performance between the eager model and the inductor model. -from transformers import T5ForConditionalGeneration +from transformers import MobileBertForQuestionAnswering +import torch # init an eager model -eager_model = T5ForConditionalGeneration.from_pretrained("t5-small") -seq_length = 1024 -bs = 4 +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} -input_dict["decoder_input_ids"] = input + # init inductor model inductor_model = torch.compile(model) -compiled(**input_dict) -eager_t = 0 -inductor_t = 0 -for _ in range(100): - model(**input_dict) -for _ in range(1000): - eager_start = time.time() - model(**input_dict) - eager_end = time.time() - eager_t += eager_end - eager_start - -for _ in range(100): - model(**input_dict) -for _ in range(1000): - inductor_start = time.time() - compiled(**input_dict) - inductor_end = time.time() - inductor_t += inductor_end - inductor_start - -print(model.__class__) -print("eager use:", eager_t) -print("inductor use:", inductor_t) -print("ratio:", eager_t / inductor_t) +with torch.no_grad(): + inductor_model(**input_dict) + +NUM_ITERS=100 +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): + inductor_model(**input_dict) + inductor_t = timeit.timeit("inductor_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}") + ###################################################################### # Output: # # .. code-block:: shell # -# eager use: 410.12550354003906 -# inductor use: 478.59081745147705 -# ratio: 0.8569439458198976 +# eager use: 802.1023553796113 ms/iter +# inductor use: 339.95180135127157 ms/iter +# speed up ratio: 2.359459053287382 # -# We see that the inductor model execution time is longer than the eager model, which does not meet our expectation. -# To deep dive op-level performance, we can use `Pytorch Profiler `_ +# The inductor model speed-up is 2.58x. # -# To enable kernel profile in inductor, we need set ``enable_kernel_profile`` by: +# +# Secondly, we can deep dive into op-level performance to understand where is the speed-up comes from. +# `Pytorch Profiler `_ is a good tool to help us. +# To enable kernel profile with inductor model, we need to set ``enable_kernel_profile`` by: from torch._inductor import config config.cpp.enable_kernel_profile = True ###################################################################### # Following the steps in `Pytorch Profiler `_ -# we are able to get the profiling table and trace files. +# We are able to get the profiling table and trace files. from torch.profiler import profile, schedule, ProfilerActivity +RESULT_DIR = "./prof_trace" my_schedule = schedule( skip_first=10, wait=5, @@ -382,10 +383,10 @@ def call(args): def trace_handler(p): output = p.key_averages().table(sort_by="self_cpu_time_total", row_limit=20) print(output) - p.export_chrome_trace(RESULT_DIR + "/" + str(p.step_num) + ".json") + p.export_chrome_trace(f"{RESULT_DIR}/{p.step_num}.json") -for _ in range(nwarmup): - model(**input_dict) +for _ in range(10): + model(**input_dict) # inductor_model(**input_dict) to get inductor model profiling total = 0 with profile( @@ -394,210 +395,109 @@ def trace_handler(p): on_trace_ready=trace_handler ) as p: for _ in range(100): - begin = time.time() - model(**input_dict) - end=time.time() - total += (end - begin) + model(**input_dict) # inductor_model(**input_dict) to get inductor model profiling p.step() -print("latency: {} ms".format(1000*(total)/100)) ###################################################################### -# We will get the following profile tables for eager model: +# We will get the following profile table for the eager model: # # .. code-block:: shell # -# ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ -# Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls -# ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ -# aten::mm 33.33% 138.616ms 33.33% 138.616ms 1.429ms 97 -# aten::add_ 19.38% 80.596ms 19.38% 80.596ms 4.242ms 19 -# aten::bmm 18.78% 78.104ms 18.78% 78.104ms 2.170ms 36 -# aten::_softmax 11.32% 47.082ms 11.32% 47.082ms 2.616ms 18 -# aten::copy_ 3.89% 16.190ms 3.89% 16.190ms 103.121us 157 -# ProfilerStep* 3.53% 14.702ms 100.00% 415.949ms 415.949ms 1 -# aten::add 2.37% 9.849ms 2.39% 9.958ms 144.319us 69 -# aten::mul 1.13% 4.693ms 1.14% 4.726ms 65.639us 72 -# aten::clamp_min 0.85% 3.541ms 0.85% 3.541ms 295.083us 12 -# aten::index_select 0.84% 3.480ms 1.06% 4.401ms 1.100ms 4 -# aten::linear 0.63% 2.637ms 33.95% 141.194ms 1.456ms 97 -# aten::pow 0.61% 2.520ms 0.61% 2.554ms 79.812us 32 -# aten::matmul 0.50% 2.067ms 56.53% 235.132ms 1.768ms 133 -# aten::select 0.22% 900.000us 0.22% 910.000us 113.750us 8 -# aten::log 0.18% 740.000us 0.18% 740.000us 370.000us 2 -# aten::_unsafe_view 0.17% 718.000us 0.17% 718.000us 3.840us 187 -# aten::sum 0.17% 715.000us 0.20% 831.000us 25.969us 32 -# aten::transpose 0.15% 642.000us 0.18% 741.000us 3.963us 187 -# aten::reshape 0.15% 622.000us 3.66% 15.241ms 88.098us 173 -# aten::fill_ 0.15% 613.000us 0.15% 613.000us 15.718us 39 -# ----------------------- ------------ ------------ ------------ ------------ ------------ ------------ -# Self CPU time total: 415.949ms +# ------------------------- ------------ ------------ ------------ ------------ ------------ ------------ +# Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls +# ------------------------- ------------ ------------ ------------ ------------ ------------ ------------ +# aten::addmm 33.36% 270.520ms 45.73% 370.814ms 1.024ms 362 +# aten::add 19.89% 161.276ms 19.89% 161.276ms 444.287us 363 +# aten::copy_ 14.97% 121.416ms 14.97% 121.416ms 248.803us 488 +# aten::mul 9.02% 73.151ms 9.02% 73.154ms 377.082us 194 +# aten::clamp_min 8.81% 71.444ms 8.81% 71.444ms 744.208us 96 +# aten::bmm 5.46% 44.258ms 5.46% 44.258ms 922.042us 48 +# ProfilerStep* 3.00% 24.362ms 100.00% 810.920ms 810.920ms 1 +# aten::div 2.85% 23.071ms 2.89% 23.447ms 976.958us 24 +# aten::_softmax 1.00% 8.087ms 1.00% 8.087ms 336.958us 24 +# aten::linear 0.32% 2.624ms 46.48% 376.888ms 1.041ms 362 +# aten::clone 0.23% 1.859ms 2.77% 22.430ms 228.878us 98 +# aten::t 0.14% 1.162ms 0.31% 2.502ms 6.912us 362 +# aten::view 0.14% 1.161ms 0.14% 1.161ms 1.366us 850 +# aten::transpose 0.12% 938.000us 0.17% 1.377ms 3.567us 386 +# aten::index_select 0.12% 933.000us 0.12% 952.000us 317.333us 3 +# aten::expand 0.11% 865.000us 0.12% 986.000us 2.153us 458 +# aten::matmul 0.10% 808.000us 8.31% 67.420ms 1.405ms 48 +# aten::cat 0.09% 701.000us 0.09% 703.000us 703.000us 1 +# aten::as_strided 0.08% 656.000us 0.08% 656.000us 0.681us 963 +# aten::relu 0.05% 420.000us 8.86% 71.864ms 748.583us 96 +# ------------------------- ------------ ------------ ------------ ------------ ------------ ------------ +# Self CPU time total: 810.920ms # # Similarly, get the table for the inductor model: # # .. code-block:: shell # -# -------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ -# Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls -# -------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ -# mkl::_mkl_linear 28.24% 133.979ms 28.39% 134.689ms 1.389ms 97 -# aten::bmm 15.65% 74.250ms 15.65% 74.251ms 2.063ms 36 -# graph_0_cpp_fused__softmax_7 4.24% 20.123ms 4.24% 20.123ms 20.123ms 1 -# graph_0_cpp_fused__softmax_42 4.17% 19.773ms 4.17% 19.773ms 19.773ms 1 -# graph_0_cpp_fused__softmax_35 4.16% 19.751ms 4.16% 19.751ms 19.751ms 1 -# graph_0_cpp_fused__softmax_21 4.15% 19.674ms 4.15% 19.674ms 19.674ms 1 -# graph_0_cpp_fused__softmax_14 4.14% 19.654ms 4.14% 19.654ms 19.654ms 1 -# graph_0_cpp_fused__softmax_28 4.13% 19.576ms 4.13% 19.576ms 19.576ms 1 -# graph_0_cpp_fused__softmax_56 2.83% 13.404ms 2.83% 13.404ms 13.404ms 1 -# graph_0_cpp_fused__softmax_80 2.82% 13.371ms 2.82% 13.371ms 13.371ms 1 -# graph_0_cpp_fused__softmax_68 2.81% 13.323ms 2.81% 13.323ms 13.323ms 1 -# graph_0_cpp_fused__softmax_92 2.80% 13.297ms 2.80% 13.297ms 13.297ms 1 -# graph_0_cpp_fused__softmax_104 2.78% 13.208ms 2.78% 13.208ms 13.208ms 1 -# graph_0_cpp_fused__softmax_2 2.63% 12.468ms 2.63% 12.468ms 12.468ms 1 -# ProfilerStep* 1.61% 7.616ms 100.00% 474.360ms 474.360ms 1 -# graph_0_cpp_fused__softmax_73 0.49% 2.320ms 0.49% 2.320ms 2.320ms 1 -# graph_0_cpp_fused__softmax_85 0.49% 2.309ms 0.49% 2.309ms 2.309ms 1 -# graph_0_cpp_fused__softmax_97 0.48% 2.283ms 0.48% 2.283ms 2.283ms 1 -# graph_0_cpp_fused__softmax_61 0.48% 2.268ms 0.48% 2.268ms 2.268ms 1 -# graph_0_cpp_fused__softmax_49 0.48% 2.255ms 0.48% 2.255ms 2.255ms 1 -# -------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ -# Self CPU time total: 474.360ms -# -# We can search the most time consuming ``graph_0_cpp_fused__softmax_7`` in ``output_code.py`` to see the generated code: - - -cpp_fused__softmax_7 = async_compile.cpp(''' +# ------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ +# Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls +# ------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ +# mkl::_mkl_linear 68.52% 230.662ms 68.79% 231.573ms 639.704us 362 +# aten::bmm 8.02% 26.991ms 8.02% 26.992ms 562.333us 48 +# ProfilerStep* 3.35% 11.292ms 100.00% 336.642ms 336.642ms 1 +# graph_0_cpp_fused_constant_pad_nd_embedding_0 0.27% 915.000us 0.27% 915.000us 915.000us 1 +# aten::empty 0.27% 911.000us 0.27% 911.000us 2.517us 362 +# graph_0_cpp_fused__mkl_linear_add_mul_relu_151 0.27% 901.000us 0.27% 901.000us 901.000us 1 +# graph_0_cpp_fused__mkl_linear_add_mul_relu_226 0.27% 899.000us 0.27% 899.000us 899.000us 1 +# graph_0_cpp_fused__mkl_linear_add_mul_relu_361 0.27% 898.000us 0.27% 898.000us 898.000us 1 +# graph_0_cpp_fused__mkl_linear_add_mul_relu_121 0.27% 895.000us 0.27% 895.000us 895.000us 1 +# graph_0_cpp_fused__mkl_linear_add_mul_relu_31 0.27% 893.000us 0.27% 893.000us 893.000us 1 +# graph_0_cpp_fused__mkl_linear_add_mul_relu_76 0.26% 892.000us 0.26% 892.000us 892.000us 1 +# graph_0_cpp_fused__mkl_linear_add_mul_relu_256 0.26% 892.000us 0.26% 892.000us 892.000us 1 +# graph_0_cpp_fused__mkl_linear_add_mul_relu_346 0.26% 892.000us 0.26% 892.000us 892.000us 1 +# graph_0_cpp_fused__mkl_linear_add_mul_relu_241 0.26% 891.000us 0.26% 891.000us 891.000us 1 +# graph_0_cpp_fused__mkl_linear_add_mul_relu_316 0.26% 891.000us 0.26% 891.000us 891.000us 1 +# graph_0_cpp_fused__mkl_linear_add_mul_relu_91 0.26% 890.000us 0.26% 890.000us 890.000us 1 +# graph_0_cpp_fused__mkl_linear_add_mul_relu_106 0.26% 890.000us 0.26% 890.000us 890.000us 1 +# graph_0_cpp_fused__mkl_linear_add_mul_relu_211 0.26% 890.000us 0.26% 890.000us 890.000us 1 +# graph_0_cpp_fused__mkl_linear_add_mul_relu_61 0.26% 889.000us 0.26% 889.000us 889.000us 1 +# graph_0_cpp_fused__mkl_linear_add_mul_relu_286 0.26% 889.000us 0.26% 889.000us 889.000us 1 +# ------------------------------------------------------- ------------ ------------ ------------ ------------ ------------ ------------ +# Self CPU time total: 336.642ms +# +# From the profiling table of the eager model, we can see the most time consumption ops are [aten::addmm, aten::add, aten::copy_, aten::mul, aten::clamp_min, aten::bmm]. +# Comparing with the inductor model profiling table, we notice there are ``mkl::_mkl_linear`` and fused kernel called ``graph_0_cpp_fused_*``. They are the major +# optimization that the inductor model is doing. Let us discuss them separately. +# (1) Regard to ``mkl::_mkl_linear```: You may notice the number of calls to this kernel is 362, which is exactly the same as ``aten::linear``` in the eager model profiling table. +# The CPU total of ``aten::linear`` is 376.888ms, at the mean time it is 231.573ms for ``mkl::_mkl_linear``. This suggests inductor model speed up ~1.63x for the "linear" part. +# (2) Regarding non-linear part: The end-to-end latency for the eager/inductor model is 802/339ms. The speed up for the non-linear part is ~3.94x. +# Let's read the generated code to understand how the inductor achieves this impressive optimization. You are able to find the generated code by +# searching ``cpp_fused__mkl_linear_add_mul_relu_151`` in ``output_code.py`` +# + + +cpp_fused__mkl_linear_add_mul_relu_151 = async_compile.cpp(''' #include -#include "/tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h" +#include "/tmp/torchinductor_root/lr/clrlgu27q4ggd472umdzwsu6qcpqxcuusjxqvx2hwitjbujiiz7z.h" extern "C" void kernel(float* in_out_ptr0, - const float* in_ptr1, - float* out_ptr0, - float* out_ptr1) + const float* in_ptr0, + const float* in_ptr1, + const float* in_ptr2, + const float* in_ptr3) { - RECORD_FUNCTION("graph_0_cpp_fused__softmax_7", c10::ArrayRef({})); - auto in_ptr0 = in_out_ptr0; + RECORD_FUNCTION("graph_0_cpp_fused__mkl_linear_add_mul_relu_151", c10::ArrayRef({})); #pragma omp parallel num_threads(32) { - { - #pragma omp for collapse(2) - for(long i0=static_cast(0L); i0(4L); i0+=static_cast(1L)) - { - for(long i1=static_cast(0L); i1(8L); i1+=static_cast(1L)) - { - #pragma GCC ivdep - for(long i2=static_cast(0L); i2(1024L); i2+=static_cast(1L)) - { - { - float tmp_acc0 = -std::numeric_limits::infinity(); - for(long i3=static_cast(0L); i3(1024L); i3+=static_cast(1L)) - { - auto tmp0 = in_ptr0[static_cast(i3 + (1024L*i2) + (1048576L*i1) + (8388608L*i0))]; - auto tmp1 = static_cast(i3 + ((-1L)*i2)); - auto tmp2 = static_cast(0); - auto tmp3 = tmp1 > tmp2; - auto tmp4 = static_cast(tmp3); - auto tmp5 = static_cast(16); - auto tmp6 = decltype(tmp4)(tmp4 * tmp5); - auto tmp7 = tmp6 + tmp2; - auto tmp8 = std::abs(tmp1); - auto tmp9 = static_cast(8); - auto tmp10 = tmp8 < tmp9; - auto tmp11 = static_cast(tmp8); - auto tmp12 = static_cast(8.0); - auto tmp13 = tmp11 / tmp12; - auto tmp14 = std::log(tmp13); - auto tmp15 = static_cast(2.772588722239781); - auto tmp16 = tmp14 / tmp15; - auto tmp17 = decltype(tmp16)(tmp16 * tmp12); - auto tmp18 = static_cast(tmp17); - auto tmp19 = tmp18 + tmp9; - auto tmp20 = static_cast(15); - auto tmp21 = min_propagate_nan(tmp19, tmp20); - auto tmp22 = tmp10 ? tmp8 : tmp21; - auto tmp23 = tmp7 + tmp22; - auto tmp24 = in_ptr1[static_cast(i1 + (8L*tmp23))]; - auto tmp25 = static_cast(0.0); - auto tmp26 = tmp24 + tmp25; - auto tmp27 = tmp0 + tmp26; - tmp_acc0 = max_propagate_nan(tmp_acc0, tmp27); - } - out_ptr0[static_cast(i2 + (1024L*i1) + (8192L*i0))] = tmp_acc0; - } - } - } - } - } - { - #pragma omp for collapse(2) - for(long i0=static_cast(0L); i0(4L); i0+=static_cast(1L)) - { - for(long i1=static_cast(0L); i1(8L); i1+=static_cast(1L)) - { - #pragma GCC ivdep - for(long i2=static_cast(0L); i2(1024L); i2+=static_cast(1L)) - { - #pragma GCC ivdep - for(long i3=static_cast(0L); i3(1024L); i3+=static_cast(1L)) - { - auto tmp0 = in_out_ptr0[static_cast(i3 + (1024L*i2) + (1048576L*i1) + (8388608L*i0))]; - auto tmp28 = out_ptr0[static_cast(i2 + (1024L*i1) + (8192L*i0))]; - auto tmp1 = static_cast(i3 + ((-1L)*i2)); - auto tmp2 = static_cast(0); - auto tmp3 = tmp1 > tmp2; - auto tmp4 = static_cast(tmp3); - auto tmp5 = static_cast(16); - auto tmp6 = decltype(tmp4)(tmp4 * tmp5); - auto tmp7 = tmp6 + tmp2; - auto tmp8 = std::abs(tmp1); - auto tmp9 = static_cast(8); - auto tmp10 = tmp8 < tmp9; - auto tmp11 = static_cast(tmp8); - auto tmp12 = static_cast(8.0); - auto tmp13 = tmp11 / tmp12; - auto tmp14 = std::log(tmp13); - auto tmp15 = static_cast(2.772588722239781); - auto tmp16 = tmp14 / tmp15; - auto tmp17 = decltype(tmp16)(tmp16 * tmp12); - auto tmp18 = static_cast(tmp17); - auto tmp19 = tmp18 + tmp9; - auto tmp20 = static_cast(15); - auto tmp21 = min_propagate_nan(tmp19, tmp20); - auto tmp22 = tmp10 ? tmp8 : tmp21; - auto tmp23 = tmp7 + tmp22; - auto tmp24 = in_ptr1[static_cast(i1 + (8L*tmp23))]; - auto tmp25 = static_cast(0.0); - auto tmp26 = tmp24 + tmp25; - auto tmp27 = tmp0 + tmp26; - auto tmp29 = tmp27 - tmp28; - in_out_ptr0[static_cast(i3 + (1024L*i2) + (1048576L*i1) + (8388608L*i0))] = tmp29; - } - } - } - } - } { #pragma omp for - for(long i0=static_cast(0L); i0(33554432L); i0+=static_cast(16L)) - { - auto tmp0 = at::vec::Vectorized::loadu(in_out_ptr0 + static_cast(i0)); - auto tmp1 = tmp0.exp(); - tmp1.store(in_out_ptr0 + static_cast(i0)); - } - } - { - #pragma omp for - for(long i0=static_cast(0L); i0(32768L); i0+=static_cast(1L)) + for(long i0=static_cast(0L); i0(16384L); i0+=static_cast(1L)) { + for(long i1=static_cast(0L); i1(512L); i1+=static_cast(8L)) { - #pragma omp declare reduction(+:at::vec::Vectorized:omp_out += omp_in) initializer(omp_priv={{0}}) - float tmp_acc0 = 0; - auto tmp_acc0_vec = at::vec::Vectorized(tmp_acc0); - for(long i1=static_cast(0L); i1(1024L); i1+=static_cast(16L)) - { - auto tmp0 = at::vec::Vectorized::loadu(in_out_ptr0 + static_cast(i1 + (1024L*i0))); - tmp_acc0_vec += tmp0; - } - tmp_acc0 += at::vec::vec_reduce_all([](at::vec::Vectorized& x, at::vec::Vectorized&y) {return x + y;}, tmp_acc0_vec); - out_ptr1[static_cast(i0)] = tmp_acc0; + auto tmp0 = at::vec::Vectorized::loadu(in_ptr0 + static_cast(i1 + (512L*i0))); + auto tmp1 = at::vec::Vectorized::loadu(in_ptr1 + static_cast(i1)); + auto tmp3 = at::vec::Vectorized::loadu(in_out_ptr0 + static_cast(i1 + (512L*i0))); + auto tmp5 = at::vec::Vectorized::loadu(in_ptr2 + static_cast(i1)); + auto tmp7 = at::vec::Vectorized::loadu(in_ptr3 + static_cast(i1)); + auto tmp2 = tmp0 + tmp1; + auto tmp4 = tmp2 + tmp3; + auto tmp6 = tmp4 * tmp5; + auto tmp8 = tmp6 + tmp7; + tmp8.store(in_out_ptr0 + static_cast(i1 + (512L*i0))); } } } @@ -606,9 +506,57 @@ def trace_handler(p): ''') ###################################################################### -# With the kernel name ``cpp_fused__softmax_*`` and considering the profile -# results together, we may suspect the generated code for ``softmax`` is -# inefficient. We encourage you to report an issue with all you findings above. +# From the generated code above, we can see this kernel has done a typical `Loop Fusion `_ on [add, add, mul, add]. +# We can infer the sizes and stride of the inputs and further bench this [add, add, mul, add] pattern. + +import torch +def func(x0, x1, x3, x5, x7): + x2 = x0 + x1 + x4 = x2 + x3 + x6 = x4 * x5 + x8 = x6 + x7 + x3 = x8 + return x3 + +x0 = torch.rand(16384, 512) +x1 = torch.rand(1, 512) +x3 = torch.zeros(16384, 512) +x5 = torch.rand(1, 512) +x7 = torch.rand(1, 512) + +input = (x0, x1, x3, x5, x7) +inductor_func = torch.compile(func) +with torch.no_grad(): + inductor_func(*input) + +import timeit +NUM_ITERS=1000 +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}") +###################################################################### +# Output: +# +# .. code-block:: shell +# +# eager use: 5.780875144992024 ms/iter +# inductor use: 0.9588955780491233 ms/iter +# speed up ratio: 6.0286805751604735 + + +# This is just an example. The profiling table shows all element-wise op are fused within the inductor automatically in this model. You can read more kernels in +# `output_code.py` ###################################################################### From 8012cf15115af5fb290e1d46cc4e161e65c01797 Mon Sep 17 00:00:00 2001 From: zhuhaozhe Date: Thu, 8 Jun 2023 15:46:00 +0800 Subject: [PATCH 10/43] fix format (#5) --- intermediate_source/inductor_debug_cpu.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index d418deda18..6f050bedbd 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -462,8 +462,10 @@ def trace_handler(p): # From the profiling table of the eager model, we can see the most time consumption ops are [aten::addmm, aten::add, aten::copy_, aten::mul, aten::clamp_min, aten::bmm]. # Comparing with the inductor model profiling table, we notice there are ``mkl::_mkl_linear`` and fused kernel called ``graph_0_cpp_fused_*``. They are the major # optimization that the inductor model is doing. Let us discuss them separately. -# (1) Regard to ``mkl::_mkl_linear```: You may notice the number of calls to this kernel is 362, which is exactly the same as ``aten::linear``` in the eager model profiling table. -# The CPU total of ``aten::linear`` is 376.888ms, at the mean time it is 231.573ms for ``mkl::_mkl_linear``. This suggests inductor model speed up ~1.63x for the "linear" part. +# +# (1) Regard to ``mkl::_mkl_linear``: You may notice the number of calls to this kernel is 362, which is exactly the same as ``aten::linear`` in the eager model profiling table. +# The CPU total of ``aten::linear`` is 376.888ms, at the mean time it is 231.573ms for ``mkl::_mkl_linear``. This suggests inductor model speed up ~1.63x for the "linear" part. +# # (2) Regarding non-linear part: The end-to-end latency for the eager/inductor model is 802/339ms. The speed up for the non-linear part is ~3.94x. # Let's read the generated code to understand how the inductor achieves this impressive optimization. You are able to find the generated code by # searching ``cpp_fused__mkl_linear_add_mul_relu_151`` in ``output_code.py`` @@ -553,8 +555,8 @@ def func(x0, x1, x3, x5, x7): # eager use: 5.780875144992024 ms/iter # inductor use: 0.9588955780491233 ms/iter # speed up ratio: 6.0286805751604735 - - +# +# # This is just an example. The profiling table shows all element-wise op are fused within the inductor automatically in this model. You can read more kernels in # `output_code.py` From 49688b7037bbaf8cc3488926caf701fb7f16d4d5 Mon Sep 17 00:00:00 2001 From: zhuhaozhe Date: Thu, 8 Jun 2023 16:49:12 +0800 Subject: [PATCH 11/43] use picture to show prof (#6) --- _static/img/eager_prof.png | Bin 0 -> 86538 bytes _static/img/inductor_prof.png | Bin 0 -> 106315 bytes intermediate_source/inductor_debug_cpu.py | 56 +--------------------- 3 files changed, 2 insertions(+), 54 deletions(-) create mode 100644 _static/img/eager_prof.png create mode 100644 _static/img/inductor_prof.png diff --git a/_static/img/eager_prof.png b/_static/img/eager_prof.png new file mode 100644 index 0000000000000000000000000000000000000000..336ccabbeb55f19ef91b5eec3f89bc2472eff628 GIT binary patch literal 86538 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%0A;M`=pTY9 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 a2Q<>L8lNVK5Sd>a%f3~5Qz2m-_`d)@pCaG@ literal 0 HcmV?d00001 diff --git a/_static/img/inductor_prof.png b/_static/img/inductor_prof.png new file mode 100644 index 0000000000000000000000000000000000000000..0d3683bece181e3d8fa6102a57f1e42e5a3a8120 GIT binary patch literal 106315 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 Date: Thu, 8 Jun 2023 17:14:56 +0000 Subject: [PATCH 12/43] add overview & conclusion and modify debugging --- intermediate_source/inductor_debug_cpu.py | 455 ++++++++++++---------- 1 file changed, 249 insertions(+), 206 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index 79fc952b89..b93f16ffd8 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -10,40 +10,62 @@ ######################################################################### # Overview # -------- +# +# PyTorch 2.0 introduced the compilation API ``torch.compile``. +# This new feature offers a significant speedup over eager mode execution through graph-level optimization powered by the default Inductor backend. +# Currently, the existing tutorials primarily focus on `basic usage `_, +# `comprehensive troubleshooting `_ +# and GPU-specific knowledge like `GPU performance profiling `_. +# However, there is a lack of an in-depth tutorial specifically designed for the Inductor CPU backend. +# Therefore, this tutorial is intended to introduce the debugging and performance profiling on Inductor CPU backend by delving into the intricacies of ``torch.compile``. +# We will start with a motivating example and demonstrate the process of debugging a failure, including the errors and the accuracy problem. +# By enabling loggings and exploring the underlying generated code, you can learn how to narrow down the failure step by step and finally figure out the route cause. +# For the case without failures, we will focus on the analysis of performance behavior. +# Comparing to eager mode, we are interested in why Inductor backend performs better or worse, maybe due to cpp vectorization or nodes fusion. +# We will show how to conducting the performance profiling by comparing the time costs between different modes and deeply analyzing the operator-level performance. + + +###################################################################### +# Debugging +# --------- # -# This document is intended to introduce the usage, debugging and performance profiling for ``torch.compile`` with Inductor CPU backend. -# 1. For the usage, we will show how to print debugging loggings and how to inductor an in-depth analysis with config parameters. -# 2. For debugging, we will demonstrate the process to debug a functional failure. There are usually two types of functional failure. -# One is the error occurring during running. It prevents a model from giving the final result, such as compilation error and runtime error. -# The other is the accuracy problem. The model gives a final result, but the value is wrong. We usually compare the result of inductor with that of eager. -# The main idea of debugging is to narrow down the problem. We firstly determine that the failure occurs in inductor and then try to find the minimum code snippet with failure. -# 3. For the profiling, we will show what to do when the performance is not good. -# This tutorial will walk you through the process of profiling, including how to find the time-consuming hotpot and determine the root cause. There are two typical scenarios for performance profiling. -# One is the case where the execution time with inductor is longer than that of eager. The other is the model regression between two PyTorch versions where both FX graph and output code could change. -# 4. In the final part, we will propose several debugging tools to be implemented and upstreamt in the future. -# -# Here is a simple example to run the ``torch.compile`` with Inductor. +# Here is a simple example to run the ``torch.compile`` using Inductor and compare its result with eager mode. import torch +from torch._dynamo.utils import same + +def foo(x1, x2): + a = torch.neg(x1) + b = torch.maximum(x2, a) + y = torch.cat([b], dim=0) + return y -def fn(x): - return torch.neg(x) +x1 = torch.randint(256, (1, 8), dtype=torch.uint8) +x2 = torch.randint(256, (8390, 8), dtype=torch.uint8) -x = torch.randn((2, 4, 28)) -compiled_fn = torch.compile(fn) # backend=inductor as default -result = compiled_fn(x) +compiled_foo = torch.compile(foo) +result = compiled_foo(x1, x2) -######################################################################### +###################################################################### +# The correct implementation of ``neg`` in the cpp codegen is as follows: + +def neg(x): + return f"decltype({x})(-{x})" + +###################################################################### +# In order to demonstrate the debugging, we will modify the function to a wrong one later. +# +# # Get more loggings # ^^^^^^^^^^^^^^^^^ # -# The simple example above would not give any debugging info. If you'd want to get more useful logging, you can add a ``TORCH_COMPILE_DEBUG`` environment variable: +# The simple example above would not give any debugging info. To get more useful debugging logging, we usually add a ``TORCH_COMPILE_DEBUG`` environment variable: # # .. code:: shell # # TORCH_COMPILE_DEBUG=1 python xx.py # -# The time taken in each step is shown. This also does the graph visualization and prints the output code. In logging, a temporary debug tracing directory like this can be found. +# It will do the graph visualization and print the output code. In logging, a temporary debug tracing directory like this can be found: # # .. code:: shell # @@ -53,7 +75,7 @@ def fn(x): # # +-------------------------+----------------------------------------------------------+ # | File | Description | -# +-------------------------+----------------------------------------------------------+ +# +=========================+==========================================================+ # | fx_graph_runnable.py | Executable FX graph, post decomps, pre pattern match | # +-------------------------+----------------------------------------------------------+ # | fx_graph_transformed.py | Transformed FX graph, post pattern match | @@ -62,57 +84,57 @@ def fn(x): # +-------------------------+----------------------------------------------------------+ # | ir_pre_fusion.txt | Inductor IR after fusion | # +-------------------------+----------------------------------------------------------+ -# | output_code.py | Generated Python code for graph, with cpp/triton kernels | +# | output_code.py | Generated Python code for graph, with C++/triton kernels | # +-------------------------+----------------------------------------------------------+ # -# ``fx_graph_runnable.py`` and ``output_code.py`` are both runnable and editable in order to make debugging easier. -# -# Here is another way to print logging for Inductor: -# -# .. code:: shell -# -# TORCH_LOGS="+inductor,output_code,schedule" python xx.py -# -# +--------------+-------------------------------------------------------------+ -# | Parameter | Description | -# +--------------+-------------------------------------------------------------+ -# | +inductor | Set the logging level of Inductor to DEBUG, default is INFO | -# +--------------+-------------------------------------------------------------+ -# | output_code | Print output code with cpp/triton kernels | -# +--------------+-------------------------------------------------------------+ -# | schedule | Print reasons for not doing vectorization in cpp kernels | -# +--------------+-------------------------------------------------------------+ +# Note that ``fx_graph_runnable.py`` and ``output_code.py`` are both runnable and editable in order to make debugging easier. # -# Conducting an in-depth analysis -# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # -# Moreover, there are several config parameters helping the analysis. -# -# +--------------------------------------------------+---------------------------------------------------------------------+ -# | Parameter | Description | -# +--------------------------------------------------+---------------------------------------------------------------------+ -# | torch._inductor.config.max_fusion_size | Set the maximum number of nodes allowed in one fusion | -# +--------------------------------------------------+---------------------------------------------------------------------+ -# | torch._inductor.config.cpp.simdlen | Specify the bit width for cpp vectorization | -# +--------------------------------------------------+---------------------------------------------------------------------+ -# | torch._inductor.config.cpp.min_chunk_size | Set the minimum number of workloads one thread should at least take | -# +--------------------------------------------------+---------------------------------------------------------------------+ -# | torch._inductor.config.cpp.enable_kernel_profile | Allow cpp kernel performance profiling via profiler | -# +--------------------------------------------------+---------------------------------------------------------------------+ +# **Fx_graph_runnable:** + +def forward(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++ kernel in output_code:** + +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(0L); i0(8390L); i0+=static_cast(1L)) + { + #pragma GCC ivdep + for(long i1=static_cast(0L); i1(8L); i1+=static_cast(1L)) + { + auto tmp0 = in_ptr0[static_cast(i1 + (8L*i0))]; + auto tmp1 = in_ptr1[static_cast(i1)]; + auto tmp2 = decltype(tmp1)(-tmp1); + auto tmp3 = max_propagate_nan(tmp0, tmp2); + out_ptr0[static_cast(i1 + (8L*i0))] = tmp3; + } + } + } +} +''') ###################################################################### -# Debugging -# --------- -# # Determine component of error # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ # -# When encountering errors or an accuracy problem, a straightforward solution to find the bug is to narrow down the problem. The first thing to do is to determine the component where the error occurs. Luckily, it can be simply achieved by changing the backend of ``torch.compile``. +# When encountering errors or accuracy problems, a straightforward solution to find the bug is to narrow down the problem. The first thing to do is to determine the component where the error occurs. Luckily, it can be simply achieved by changing the backend of ``torch.compile``. # # +----------------------------------------+-----------------------------------------+ # | Code | Description | -# +----------------------------------------+-----------------------------------------+ +# +========================================+=========================================+ # | torch.compile(fn, backend="eager") | Enable Dynamo | # +----------------------------------------+-----------------------------------------+ # | torch.compile(fn, backend="aot_eager") | Enable Dynamo + AOT autograd | @@ -123,53 +145,25 @@ def fn(x): # If the model can successfully run when the backend is set to ``eager`` or ``aot_eager`` while it fails with ``inductor``, we can narrow down the failure to Inductor. # # -# Example -# ^^^^^^^ -# -# Here is an example for the subsequent debugging: - -import torch -from torch._dynamo.utils import same - -def foo(x1, x2): - a = torch.neg(x1) - b = torch.maximum(x2, a) - y = torch.cat([b], dim=0) - return y - -x1 = torch.randint(256, (1,), dtype=torch.uint8) -x2 = torch.randint(256, (8390,), dtype=torch.uint8) - -expected_result = fn(x1, x2) - -compiled_fn = torch.compile(fn) -actual_result = compiled_fn(x1, x2) - -assert same(expected_result, actual_result) == True - -###################################################################### -# The implementation of ``neg`` in the ``cpp`` codegen is as follows: - -def neg(x): - return f"decltype({x})(-{x})" - -###################################################################### -# In order to demonstrate the debugging, we will modify the function to a wrong one later. +# Compilation error +# ^^^^^^^^^^^^^^^^^ # +# As we know, the evolved chain of graph-level optimization is +# :: # -# Errors debugging -# ^^^^^^^^^^^^^^^^ +# User-input Python code -> FX graph -> IR nodes -> Output code with C++ kernels # -# If a compile error occurs, the root cause is usually shown in the traceback log. +# If you encounter a compilation error, there is something wrong when compiling C++ kernels in the output code. +# This type of error indicates that bugs are introduced when lowering IR nodes to output code. +# The root cause of compilation error is usually shown in the traceback log. # # For example, the ``neg`` function is modified like this: def neg(x): return f"-{x}" - ###################################################################### -# The logging gives the following compile error with a rather clear reason. In this case, the root cause is that data types of maximum's inputs are inconsistent. +# The logging gives the following compile error with a rather clear reason. # # .. code:: shell # @@ -177,38 +171,117 @@ def neg(x): # torch._dynamo.exc.BackendCompilerFailed: backend='inductor' raised: # CppCompileError: C++ compile error # … -# /tmp/torchinductor_root/2x/c2xgxsooklulr4u54etfnnha7dsu6xzbwdscttvs7dkpba3uwkem.cpp: In function ‘void kernel(const unsigned char*, const unsigned char*, unsigned char*)’: -# /tmp/torchinductor_root/2x/c2xgxsooklulr4u54etfnnha7dsu6xzbwdscttvs7dkpba3uwkem.cpp:14:53: error: no matching function for call to ‘max_propagate_nan(unsigned char&, int&)’ -# 14 | auto tmp3 = max_propagate_nan(tmp0, tmp2); -# | ^ -# In file included from /tmp/torchinductor_root/2x/c2xgxsooklulr4u54etfnnha7dsu6xzbwdscttvs7dkpba3uwkem.cpp:2: -# /tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h:27:17: note: candidate: ‘template 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/2x/c2xgxsooklulr4u54etfnnha7dsu6xzbwdscttvs7dkpba3uwkem.cpp:14:53: note: deduced conflicting types for parameter ‘scalar_t’ (‘unsigned char’ and ‘int’) -# 14 | auto tmp3 = max_propagate_nan(tmp0, tmp2); -# | ^ -# -# -# Otherwise, if the model runs with other errors, we can do the model code reduction until finding the minimum code snippet with failure. Thus, the target operators and kernels are located. +# /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 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); +# | ^ +# +# +# Let us also see the corresponding C++ kernel in output code and IR node. +# **C++ kernel:** + +#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(0L); i0(8390L); i0+=static_cast(1L)) + { + #pragma GCC ivdep + for(long i1=static_cast(0L); i1(8L); i1+=static_cast(1L)) + { + auto tmp0 = in_ptr0[static_cast(i1 + (8L*i0))]; + auto tmp1 = in_ptr1[static_cast(i1)]; + auto tmp2 = -tmp1; + auto tmp3 = max_propagate_nan(tmp0, tmp2); + out_ptr0[static_cast(i1 + (8L*i0))] = tmp3; + } + } + } +} + +###################################################################### +# **IR node:** + +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 + +###################################################################### +# According to the traceback logging, the compilation error is caused by the data type inconsistency of ``max_propagate_nan``'s inputs. +# By checking the C++ kernel, we know that ``tmp2`` is no longer ``long`` after doing ``-`` as ``tmp0`` is ``long``. +# We can easily match ``-`` and ``max_propagate_nan`` in C++ kernel with ``ops.neg`` and ``ops.maximum`` in IR node respectively. +# Now we sucessfully find that the root cause is the implementation of ``ops.neg`` in cpp codegen, which silently changes the data type when doing ``neg``. # # # Accuracy debugging # ^^^^^^^^^^^^^^^^^^^ # -# The accuracy problem refers the case where outputs of backends eager and inductor are different. As FX graph is generated before Inductor and output code is generated after Inductor, we can narrow down the problem by comparing their outputs. +# Otherwise, if the model runs with other errors or accuracy problem, you can use the PyTorch debugging tool called `Minifier `_. +# The core idea of ``Minifier`` is to keep removing the nodes and inputs of graph until finding the minimal graph with problem. +# It helps to automatically generate a minified problematic graph through 4 strategies: truncating suffix, delta debugging, eliminating dead code and removing unused inputs. # -# If a model has several graphs, the first step is to compare the final outputs of FX graph and output code for each graph, given the same input. The target is to find the first graph occurring error or with different outputs. Binary search is suggested to use for efficiency. # -# When a model has only one graph or the problematic graph has been found with the above step, compare the intermediate outputs of FX graph and output code in each graph, given the same input. The idea is to continuously narrow down the problem. +# We will now show the debugging process for the accuracy problem with the help of ``Minifer``. +# The accuracy problem refers to the case where outputs of backends eager and inductor are different. # -# For example, we modify the ``neg`` function like this: +# For instance, we modify the example like this: +import torch +from torch._dynamo.utils import same + +def foo(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 = foo(x1, x2) + +compiled_foo = torch.compile(foo) +actual_result = compiled_foo(x1, x2) + +assert same(expected_result, actual_result) == True + +###################################################################### +# And also the ``neg`` function: def neg(x): return f"decltype({x})(2 * {x})" - ###################################################################### # An accuracy problem would be raised as follows. # @@ -220,92 +293,61 @@ def neg(x): # assert same(expected_result, actual_result) == True # AssertionError # +# To debug an accuracy problem with Minifier, two environment variables are needed: +# .. code:: shell # -# By comparing the intermediate outputs of FX graph and output code, it would be found that outputs are already different after doing ``torch.neg``. +# TORCHDYNAMO_REPRO_AFTER="aot" TORCHDYNAMO_REPRO_LEVEL=4 python xx.py # -# Here are the modifications of FX graph and output code: +# Which gives us such loggings showing the steps of minifying: # -# **Changes of teh FX graph:** - -# Before -class Repro(torch.nn.Module): - def __init__(self): - super().__init__() - - def forward(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,) - -# After -class Repro(torch.nn.Module): - def __init__(self): - super().__init__() - - def forward(self, arg0_1, arg1_1): - neg = torch.ops.aten.neg.default(arg0_1); arg0_1 = None - return (neg,) - -###################################################################### -# **Changes of the output code:** - -# Before -cpp_fused_cat_maximum_neg_0 = async_compile.cpp(''' -#include "/tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h" -extern "C" void kernel(const long* in_ptr0, - const long* in_ptr1, - long* out_ptr0) -{ - { - #pragma GCC ivdep - for(long i0=static_cast(0L); i0(8390L); i0+=static_cast(1L)) - { - auto tmp0 = in_ptr0[static_cast(i0)]; - auto tmp1 = in_ptr1[static_cast(0L)]; - auto tmp2 = decltype(tmp1)(2 * tmp1); - auto tmp3 = max_propagate_nan(tmp0, tmp2); - out_ptr0[static_cast(i0)] = tmp3; - } - } -} -''') - -def call(args): - arg0_1, arg1_1 = args - args.clear() - buf0 = empty_strided((8390, ), (1, ), device='cpu', dtype=torch.int64) - cpp_fused_cat_maximum_neg_0(c_void_p(arg1_1.data_ptr()), c_void_p(arg0_1.data_ptr()), c_void_p(buf0.data_ptr())) - del arg0_1 - del arg1_1 - return (buf0, ) - -# After -cpp_fused_cat_maximum_neg_0 = async_compile.cpp(''' -#include "/tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h" -extern "C" void kernel(const long* in_ptr0, - const long* in_ptr1, - long* out_ptr0) -{ - { - auto tmp1 = in_ptr1[static_cast(0L)]; - auto tmp2 = decltype(tmp1)(2 * tmp1); - out_ptr0[static_cast(0L)] = tmp2; - } -} -''') +# .. code:: shell +# +# Trying granularity 2 +# +# Strategy: Eliminate dead code (G: 2) (3 nodes, 1 inputs) +# FAIL: Eliminate dead code +# +# Strategy: Remove unused inputs (G: 2) (3 nodes, 1 inputs) +# FAIL: Remove unused inputs +# +# Strategy: Consolidate Inputs (G: 2) (3 nodes, 1 inputs) +# FAIL: Consolidate Inputs +# +# Strategy: Truncate suffix (G: 2) (3 nodes, 1 inputs) +# FAIL: Truncate suffix +# +# Strategy: Delta Debugging (G: 2) (3 nodes, 1 inputs) +# +# >> Loading inputs: 100%|██████████| 1/1 [00:00<00:00, 1333.64it/s] +# +# FAIL: Delta Debugging +# Trying granularity 1 +# +# Strategy: Truncate suffix (G: 1) (3 nodes, 1 inputs) +# FAIL: Truncate suffix +# +# Strategy: Delta Debugging (G: 1) (3 nodes, 1 inputs) +# +# >> Loading inputs: 100%|██████████| 1/1 [00:00<00:00, 1382.43it/s] +# +# FAIL: Delta Debugging +# +# Strategy: Remove outputs (G: 1) (3 nodes, 1 inputs) +# FAIL: Remove outputs +# +# >> Loading inputs: 100%|██████████| 1/1 [00:00<00:00, 1256.53it/s] +# >> torch._dynamo.utils: [ERROR] RMSE (res-fp64): 2.39615, (ref-fp64): 0.00000 and shape=torch.Size([1, 8]) +# +# Made 10 queries +# +# We also get the final minified graph only with ``neg``: -def call(args): - arg0_1, arg1_1 = args - args.clear() - buf0 = empty_strided((1, ), (1, ), device='cpu', dtype=torch.int64) - cpp_fused_cat_maximum_neg_0(c_void_p(arg1_1.data_ptr()), c_void_p(arg0_1.data_ptr()), c_void_p(buf0.data_ptr())) - del arg0_1 - del arg1_1 - return (buf0, ) +def forward(self, arg0_1): + neg = torch.ops.aten.neg.default(arg0_1); arg0_1 = None + return (neg,) ###################################################################### -# You can use the PyTorch debugging tool called `Minifier `_. It helps to automatically generate a minified problematic graph. +# For more usage details about Minifier, please refer to ``Troubleshooting ``_ ###################################################################### @@ -509,13 +551,14 @@ def func(x0, x1, x3, x5, x7): # `output_code.py` -###################################################################### -# Future work -# ----------- -# -# Implement and upstream the debug tools -# 1. **Graph merger**: Merge graphs of a model into a single large graph. Thus, graphs can be compared quickly between different versions of PyTorch. `#102958 `_ -# 2. **Graph matching**: In order to know what each kernel does, this tool matches C++ kernel with FX graph operators and adds corresponding operators before each kernel in the ``.cpp`` output code. `#102958 `_ -# 3. **Save inputs and outputs**: For the purpose of reproducing rapidly the failure of a large model, it is necessary to add serializations for the inputs and outputs among graphs and intermediate outputs in graphs. -# 4. **Test case generation**: When a user has found the operators which are inefficient with cpp kernels, a tool is needed to automatically write a test case. Specifically, one test case can be generated for each kernel, with the corresponding small FX graph and input. -# 5. **Minifier optimization**: Keep refining Minifier and make it adapted for more scenarios. +######################################################################### +# Conclusion +# ---------- +# +# The document gives an in-depth tutorial for the Inductor CPU backend. +# With motivating examples, we walk through the process of debugging and profiling. +# The main idea is to narrow down the problem. +# We demonstrate step by step the way to delve deeper the issue and find the root cause of failures, with the help of debugging loggings and the tool Minifier. +# Firstly determine which component the failure occurs in and then try to generate the smallest snippet of code that can reproduce the failure. +# When the performance with Inductor is better or worse than that of eager mode, we give a solid analytical method for performance profiling. +# We show how to find the time-consuming hotspot with PyTorch Profiler and figure out the operator-level or kernel-level reason to explain the phenomenon. \ No newline at end of file From 4eb0da1c0fb1a801504c0c0090e87935b7f2576d Mon Sep 17 00:00:00 2001 From: "Liao, Xuan" Date: Thu, 8 Jun 2023 17:52:55 +0000 Subject: [PATCH 13/43] fix py format --- intermediate_source/inductor_debug_cpu.py | 145 ++++++++++++---------- 1 file changed, 82 insertions(+), 63 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index b93f16ffd8..a6b522e5f5 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -4,7 +4,7 @@ Inductor CPU backend debugging and profiling ============================================ -**Author**: `Liao Xuan `_, `Zhu Haozhe `_ +**Authors**: `Liao Xuan `_, `Zhu Haozhe `_ """ ######################################################################### @@ -13,13 +13,17 @@ # # PyTorch 2.0 introduced the compilation API ``torch.compile``. # This new feature offers a significant speedup over eager mode execution through graph-level optimization powered by the default Inductor backend. +# # Currently, the existing tutorials primarily focus on `basic usage `_, -# `comprehensive troubleshooting `_ +# comprehensive `troubleshooting `_ # and GPU-specific knowledge like `GPU performance profiling `_. # However, there is a lack of an in-depth tutorial specifically designed for the Inductor CPU backend. +# # Therefore, this tutorial is intended to introduce the debugging and performance profiling on Inductor CPU backend by delving into the intricacies of ``torch.compile``. +# # We will start with a motivating example and demonstrate the process of debugging a failure, including the errors and the accuracy problem. # By enabling loggings and exploring the underlying generated code, you can learn how to narrow down the failure step by step and finally figure out the route cause. +# # For the case without failures, we will focus on the analysis of performance behavior. # Comparing to eager mode, we are interested in why Inductor backend performs better or worse, maybe due to cpp vectorization or nodes fusion. # We will show how to conducting the performance profiling by comparing the time costs between different modes and deeply analyzing the operator-level performance. @@ -29,7 +33,7 @@ # Debugging # --------- # -# Here is a simple example to run the ``torch.compile`` using Inductor and compare its result with eager mode. +# Here is a simple example to run the ``torch.compile`` using Inductor and compare its result with eager mode: import torch from torch._dynamo.utils import same @@ -71,7 +75,7 @@ def neg(x): # # torch._inductor.debug: [WARNING] model___20 debug trace: /tmp/torchinductor_root/rx/crxfi2ybd7yp5sbj2pnhw33wfhtdw7wumvrobyp5sjvdui5ktjc2.debug # -# In this directory, the following files are saved for debugging purposes. +# In this directory, the following files are saved for debugging purposes: # # +-------------------------+----------------------------------------------------------+ # | File | Description | @@ -87,10 +91,10 @@ def neg(x): # | output_code.py | Generated Python code for graph, with C++/triton kernels | # +-------------------------+----------------------------------------------------------+ # -# Note that ``fx_graph_runnable.py`` and ``output_code.py`` are both runnable and editable in order to make debugging easier. -# +# Note that ``fx_graph_runnable.py`` and ``output_code.py`` are both runnable and editable in order to make debugging easier. +# Here are the main parts of code extracted from the files: # -# **Fx_graph_runnable:** +# Fx_graph_runnable: def forward(self, arg0_1, arg1_1): neg = torch.ops.aten.neg.default(arg0_1); arg0_1 = None @@ -99,7 +103,10 @@ def forward(self, arg0_1, arg1_1): return (clone,) ###################################################################### -# **C++ kernel in output_code:** +# C++ kernel in output_code: + +from torch._inductor.codecache import AsyncCompile +async_compile = AsyncCompile() cpp_fused_cat_maximum_neg_0 = async_compile.cpp(''' #include "/tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h" @@ -151,7 +158,7 @@ def forward(self, arg0_1, arg1_1): # As we know, the evolved chain of graph-level optimization is # :: # -# User-input Python code -> FX graph -> IR nodes -> Output code with C++ kernels +# User's Python code -> FX graph -> IR nodes -> Output code with C++ kernels # # If you encounter a compilation error, there is something wrong when compiling C++ kernels in the output code. # This type of error indicates that bugs are introduced when lowering IR nodes to output code. @@ -186,62 +193,68 @@ def neg(x): # # # Let us also see the corresponding C++ kernel in output code and IR node. -# **C++ kernel:** - -#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(0L); i0(8390L); i0+=static_cast(1L)) - { - #pragma GCC ivdep - for(long i1=static_cast(0L); i1(8L); i1+=static_cast(1L)) - { - auto tmp0 = in_ptr0[static_cast(i1 + (8L*i0))]; - auto tmp1 = in_ptr1[static_cast(i1)]; - auto tmp2 = -tmp1; - auto tmp3 = max_propagate_nan(tmp0, tmp2); - out_ptr0[static_cast(i1 + (8L*i0))] = tmp3; - } - } - } -} +# +# C++ kernel: +# +# .. code:: 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(0L); i0(8390L); i0+=static_cast(1L)) +# { +# #pragma GCC ivdep +# for(long i1=static_cast(0L); i1(8L); i1+=static_cast(1L)) +# { +# auto tmp0 = in_ptr0[static_cast(i1 + (8L*i0))]; +# auto tmp1 = in_ptr1[static_cast(i1)]; +# auto tmp2 = -tmp1; +# auto tmp3 = max_propagate_nan(tmp0, tmp2); +# out_ptr0[static_cast(i1 + (8L*i0))] = tmp3; +# } +# } +# } +# } ###################################################################### -# **IR node:** - -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 +# IR node: +# +# :: +# +# 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 ###################################################################### # According to the traceback logging, the compilation error is caused by the data type inconsistency of ``max_propagate_nan``'s inputs. # By checking the C++ kernel, we know that ``tmp2`` is no longer ``long`` after doing ``-`` as ``tmp0`` is ``long``. # We can easily match ``-`` and ``max_propagate_nan`` in C++ kernel with ``ops.neg`` and ``ops.maximum`` in IR node respectively. +# # Now we sucessfully find that the root cause is the implementation of ``ops.neg`` in cpp codegen, which silently changes the data type when doing ``neg``. # # @@ -249,12 +262,13 @@ def body(self, ops): # ^^^^^^^^^^^^^^^^^^^ # # Otherwise, if the model runs with other errors or accuracy problem, you can use the PyTorch debugging tool called `Minifier `_. +# # The core idea of ``Minifier`` is to keep removing the nodes and inputs of graph until finding the minimal graph with problem. # It helps to automatically generate a minified problematic graph through 4 strategies: truncating suffix, delta debugging, eliminating dead code and removing unused inputs. # # # We will now show the debugging process for the accuracy problem with the help of ``Minifer``. -# The accuracy problem refers to the case where outputs of backends eager and inductor are different. +# The accuracy problem refers to the case where the outputs of backends eager and inductor are different. # # For instance, we modify the example like this: @@ -278,12 +292,13 @@ def foo(x1, x2): assert same(expected_result, actual_result) == True ###################################################################### -# And also the ``neg`` function: +# And also modify the ``neg`` function: + def neg(x): return f"decltype({x})(2 * {x})" ###################################################################### -# An accuracy problem would be raised as follows. +# An accuracy problem would be raised as follows: # # .. code:: shell # @@ -294,6 +309,7 @@ def neg(x): # AssertionError # # To debug an accuracy problem with Minifier, two environment variables are needed: +# # .. code:: shell # # TORCHDYNAMO_REPRO_AFTER="aot" TORCHDYNAMO_REPRO_LEVEL=4 python xx.py @@ -340,14 +356,14 @@ def neg(x): # # Made 10 queries # -# We also get the final minified graph only with ``neg``: +# After running, we get the final minified graph with the target node ``neg``: def forward(self, arg0_1): neg = torch.ops.aten.neg.default(arg0_1); arg0_1 = None return (neg,) ###################################################################### -# For more usage details about Minifier, please refer to ``Troubleshooting ``_ +# For more usage details about Minifier, please refer to `Troubleshooting `_. ###################################################################### @@ -556,9 +572,12 @@ def func(x0, x1, x3, x5, x7): # ---------- # # The document gives an in-depth tutorial for the Inductor CPU backend. +# # With motivating examples, we walk through the process of debugging and profiling. # The main idea is to narrow down the problem. +# # We demonstrate step by step the way to delve deeper the issue and find the root cause of failures, with the help of debugging loggings and the tool Minifier. # Firstly determine which component the failure occurs in and then try to generate the smallest snippet of code that can reproduce the failure. +# # When the performance with Inductor is better or worse than that of eager mode, we give a solid analytical method for performance profiling. # We show how to find the time-consuming hotspot with PyTorch Profiler and figure out the operator-level or kernel-level reason to explain the phenomenon. \ No newline at end of file From e49a0275d1386dab62cf4a8a7a9796cafb46fdce Mon Sep 17 00:00:00 2001 From: "Liao, Xuan" Date: Fri, 9 Jun 2023 10:02:19 +0000 Subject: [PATCH 14/43] modify logging of minifier --- intermediate_source/inductor_debug_cpu.py | 45 ++++++++++++++++------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index a6b522e5f5..d55fc4920d 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -318,43 +318,60 @@ def neg(x): # # .. code:: shell # +# Started off with 6 nodes +# Trying granularity 4 +# Strategy: Eliminate dead code (G: 4) (6 nodes, 2 inputs) +# FAIL: Eliminate dead code +# Strategy: Remove unused inputs (G: 4) (6 nodes, 2 inputs) +# FAIL: Remove unused inputs +# Strategy: Consolidate Inputs (G: 4) (6 nodes, 2 inputs) +# FAIL: Consolidate Inputs +# Strategy: Truncate suffix (G: 4) (6 nodes, 2 inputs) +# FAIL: Truncate suffix +# Strategy: Delta Debugging (G: 4) (6 nodes, 2 inputs) +# >> Loading inputs: 100%|██████████| 1/1 [00:00<00:00, 1212.93it/s] +# >> Loading inputs: 100%|██████████| 1/1 [00:00<00:00, 1137.28it/s] +# FAIL: Delta Debugging +# Trying granularity 2 +# Strategy: Truncate suffix (G: 2) (6 nodes, 2 inputs) +# >> Loading inputs: 100%|██████████| 2/2 [00:00<00:00, 1929.75it/s] +# >> torch._dynamo.utils: [ERROR] RMSE (res-fp64): 2.39615, (ref-fp64): 0.00000 and shape=torch.Size([1, 8]) +# SUCCESS: Went from 6 to 4 nodes +# >> Loading inputs: 100%|██████████| 2/2 [00:00<00:00, 1885.93it/s] +# >> torch._dynamo.utils: [ERROR] RMSE (res-fp64): 2.39615, (ref-fp64): 0.00000 and shape=torch.Size([1, 8]) +# Trying granularity 4 +# Strategy: Eliminate dead code (G: 4) (4 nodes, 2 inputs) +# FAIL: Eliminate dead code +# Strategy: Remove unused inputs (G: 4) (4 nodes, 2 inputs) +# >> Loading inputs: 100%|██████████| 1/1 [00:00<00:00, 1294.94it/s] +# >> torch._dynamo.utils: [ERROR] RMSE (res-fp64): 2.39615, (ref-fp64): 0.00000 and shape=torch.Size([1, 8]) +# SUCCESS: Went from 4 to 3 nodes +# >> Loading inputs: 100%|██████████| 1/1 [00:00<00:00, 1341.75it/s] +# >> torch._dynamo.utils: [ERROR] RMSE (res-fp64): 2.39615, (ref-fp64): 0.00000 and shape=torch.Size([1, 8]) # Trying granularity 2 -# # Strategy: Eliminate dead code (G: 2) (3 nodes, 1 inputs) # FAIL: Eliminate dead code -# # Strategy: Remove unused inputs (G: 2) (3 nodes, 1 inputs) # FAIL: Remove unused inputs -# # Strategy: Consolidate Inputs (G: 2) (3 nodes, 1 inputs) # FAIL: Consolidate Inputs -# # Strategy: Truncate suffix (G: 2) (3 nodes, 1 inputs) # FAIL: Truncate suffix -# # Strategy: Delta Debugging (G: 2) (3 nodes, 1 inputs) -# # >> Loading inputs: 100%|██████████| 1/1 [00:00<00:00, 1333.64it/s] -# # FAIL: Delta Debugging # Trying granularity 1 -# # Strategy: Truncate suffix (G: 1) (3 nodes, 1 inputs) # FAIL: Truncate suffix -# # Strategy: Delta Debugging (G: 1) (3 nodes, 1 inputs) -# # >> Loading inputs: 100%|██████████| 1/1 [00:00<00:00, 1382.43it/s] -# # FAIL: Delta Debugging -# # Strategy: Remove outputs (G: 1) (3 nodes, 1 inputs) # FAIL: Remove outputs -# # >> Loading inputs: 100%|██████████| 1/1 [00:00<00:00, 1256.53it/s] # >> torch._dynamo.utils: [ERROR] RMSE (res-fp64): 2.39615, (ref-fp64): 0.00000 and shape=torch.Size([1, 8]) -# # Made 10 queries +# Wrote minimal repro out to repro.py # # After running, we get the final minified graph with the target node ``neg``: From a392c37ca79fa5d7c51d92e3a6ebb3b5ae8cd1b0 Mon Sep 17 00:00:00 2001 From: zhuhaozhe Date: Fri, 9 Jun 2023 13:52:29 +0800 Subject: [PATCH 15/43] Apply suggestions from code review Co-authored-by: Jiong Gong --- intermediate_source/inductor_debug_cpu.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index d55fc4920d..aa2f8dd27a 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -387,8 +387,8 @@ def forward(self, arg0_1): # Performance profiling # --------------------- # -# For this part, we will describe how to analyze the inductor model performance. -# First, we choose an eager model as a baseline. We set up a benchmark to compare the end-to-end performance between the eager model and the inductor model. +# Within this section, we will demonstrate the process of conducting performance analysis for a model that has been compiled using the Inductor CPU backend. +# In the example below, we benchmark a Huggingface Transformer model ``MobileBertForQuestionAnswering`` with both the eager mode and the Inductor graph mode. The execution time and the speedup ratio of Inductor are printed after the benchmark. from transformers import MobileBertForQuestionAnswering import torch @@ -435,9 +435,8 @@ def forward(self, arg0_1): # The inductor model speed-up is 2.58x. # # -# Secondly, we can deep dive into op-level performance to understand where is the speed-up comes from. -# `Pytorch Profiler `_ is a good tool to help us. -# To enable kernel profile with inductor model, we need to set ``enable_kernel_profile`` by: +# Next, let's dive deep into the performance at the operation level to understand where the speed-up comes from. +# `Pytorch Profiler `_ is a good tool to help us. Inductor CPU backend has the support to report the time of the fusion kernels to the profiler with the ``enable_kernel_profile`` configuration option: from torch._inductor import config config.cpp.enable_kernel_profile = True @@ -474,11 +473,11 @@ def trace_handler(p): p.step() ###################################################################### -# We will get the following profile table for the eager model: +# We get the following performance profiling table for the eager-mode model: # # .. image:: ../_static/img/eager_prof.png # -# Similarly, get the table for the inductor model: +# Similarly, we also get the table for the compiled model with Inductor: # # .. image:: ../_static/img/inductor_prof.png # From 08259dcecbf4ce4f1113316906fbdb818cc42d04 Mon Sep 17 00:00:00 2001 From: Xuan Liao Date: Fri, 9 Jun 2023 13:55:14 +0800 Subject: [PATCH 16/43] Apply suggestions from code review Co-authored-by: Jiong Gong --- intermediate_source/inductor_debug_cpu.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index aa2f8dd27a..11abdf9787 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -63,13 +63,13 @@ def neg(x): # Get more loggings # ^^^^^^^^^^^^^^^^^ # -# The simple example above would not give any debugging info. To get more useful debugging logging, we usually add a ``TORCH_COMPILE_DEBUG`` environment variable: +# No debugging information would be provided if you run this simple example by default. In order to get more useful debugging and logging information, we usually add a ``TORCH_COMPILE_DEBUG`` environment variable like below: # # .. code:: shell # # TORCH_COMPILE_DEBUG=1 python xx.py # -# It will do the graph visualization and print the output code. In logging, a temporary debug tracing directory like this can be found: +# This would print more debug information in the output logs and also dump the intermediate IRs generated during the codegen process. You can find the dumped file paths in the log like below: # # .. code:: shell # From 23dd617f09ea741cb33f2012607ecba0fd1d93b7 Mon Sep 17 00:00:00 2001 From: "Liao, Xuan" Date: Fri, 9 Jun 2023 14:21:17 +0000 Subject: [PATCH 17/43] modify for debugging --- intermediate_source/inductor_debug_cpu.py | 89 +++++++---------------- 1 file changed, 26 insertions(+), 63 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index 11abdf9787..485ad6c9e9 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -4,29 +4,33 @@ Inductor CPU backend debugging and profiling ============================================ -**Authors**: `Liao Xuan `_, `Zhu Haozhe `_ +**Authors**: `Liao Xuan `_, `Zhu Haozhe `_, `Gong Jiong `_ """ ######################################################################### # Overview # -------- # -# PyTorch 2.0 introduced the compilation API ``torch.compile``. +# PyTorch 2.0 introduced the compilation API called ``torch.compile``. # This new feature offers a significant speedup over eager mode execution through graph-level optimization powered by the default Inductor backend. # -# Currently, the existing tutorials primarily focus on `basic usage `_, -# comprehensive `troubleshooting `_ +# This tutorial is intended to provide an in-depth introduction on the debugging +# and performance profiling on Inductor CPU backend by delving into the intricacies of ``torch.compile``. +# +# Meanwhile, you may also find related tutorials about ``torch.compile`` +# around `basic usage `_, +# comprehensive `troubleshooting `_ # and GPU-specific knowledge like `GPU performance profiling `_. -# However, there is a lack of an in-depth tutorial specifically designed for the Inductor CPU backend. # -# Therefore, this tutorial is intended to introduce the debugging and performance profiling on Inductor CPU backend by delving into the intricacies of ``torch.compile``. +# We will start debugging with a motivating example that triggers compilation issues and accuracy problems +# by demonstrating the process of debugging to pinpoint the problems. # -# We will start with a motivating example and demonstrate the process of debugging a failure, including the errors and the accuracy problem. -# By enabling loggings and exploring the underlying generated code, you can learn how to narrow down the failure step by step and finally figure out the route cause. +# By enabling loggings and exploring the underlying generated code, +# you can learn how to narrow down the failure step by step and finally figure out the route cause. # -# For the case without failures, we will focus on the analysis of performance behavior. -# Comparing to eager mode, we are interested in why Inductor backend performs better or worse, maybe due to cpp vectorization or nodes fusion. -# We will show how to conducting the performance profiling by comparing the time costs between different modes and deeply analyzing the operator-level performance. +# Following that, we will proceed to discuss how to profile the compiled code and, +# through a performance comparison with eager mode, +# elaborate on the reasons why ``torch.compile`` can provide an additional performance boost compared to its eager counterpart. ###################################################################### @@ -92,7 +96,7 @@ def neg(x): # +-------------------------+----------------------------------------------------------+ # # Note that ``fx_graph_runnable.py`` and ``output_code.py`` are both runnable and editable in order to make debugging easier. -# Here are the main parts of code extracted from the files: +# Here are the main parts of code extracted from the files and we correlate the C++ generated line with the FX code line. # # Fx_graph_runnable: @@ -123,8 +127,11 @@ def forward(self, arg0_1, arg1_1): { auto tmp0 = in_ptr0[static_cast(i1 + (8L*i0))]; auto tmp1 = in_ptr1[static_cast(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(i1 + (8L*i0))] = tmp3; } } @@ -155,10 +162,11 @@ def forward(self, arg0_1, arg1_1): # Compilation error # ^^^^^^^^^^^^^^^^^ # -# As we know, the evolved chain of graph-level optimization is +# As we know, the evolved chain of graph-level optimization is like: +# # :: # -# User's Python code -> FX graph -> IR nodes -> Output code with C++ kernels +# torch.neg (Python) -> torch.ops.aten.neg.default (within FX graph) -> ops.neg (within IR node) -> tmp2 = -tmp1 (within C++ kernel) # # If you encounter a compilation error, there is something wrong when compiling C++ kernels in the output code. # This type of error indicates that bugs are introduced when lowering IR nodes to output code. @@ -319,59 +327,14 @@ def neg(x): # .. code:: shell # # Started off with 6 nodes -# Trying granularity 4 -# Strategy: Eliminate dead code (G: 4) (6 nodes, 2 inputs) -# FAIL: Eliminate dead code -# Strategy: Remove unused inputs (G: 4) (6 nodes, 2 inputs) -# FAIL: Remove unused inputs -# Strategy: Consolidate Inputs (G: 4) (6 nodes, 2 inputs) -# FAIL: Consolidate Inputs -# Strategy: Truncate suffix (G: 4) (6 nodes, 2 inputs) -# FAIL: Truncate suffix -# Strategy: Delta Debugging (G: 4) (6 nodes, 2 inputs) -# >> Loading inputs: 100%|██████████| 1/1 [00:00<00:00, 1212.93it/s] -# >> Loading inputs: 100%|██████████| 1/1 [00:00<00:00, 1137.28it/s] -# FAIL: Delta Debugging +# # Trying granularity 2 # Strategy: Truncate suffix (G: 2) (6 nodes, 2 inputs) -# >> Loading inputs: 100%|██████████| 2/2 [00:00<00:00, 1929.75it/s] -# >> torch._dynamo.utils: [ERROR] RMSE (res-fp64): 2.39615, (ref-fp64): 0.00000 and shape=torch.Size([1, 8]) # SUCCESS: Went from 6 to 4 nodes -# >> Loading inputs: 100%|██████████| 2/2 [00:00<00:00, 1885.93it/s] -# >> torch._dynamo.utils: [ERROR] RMSE (res-fp64): 2.39615, (ref-fp64): 0.00000 and shape=torch.Size([1, 8]) +# # Trying granularity 4 -# Strategy: Eliminate dead code (G: 4) (4 nodes, 2 inputs) -# FAIL: Eliminate dead code # Strategy: Remove unused inputs (G: 4) (4 nodes, 2 inputs) -# >> Loading inputs: 100%|██████████| 1/1 [00:00<00:00, 1294.94it/s] -# >> torch._dynamo.utils: [ERROR] RMSE (res-fp64): 2.39615, (ref-fp64): 0.00000 and shape=torch.Size([1, 8]) # SUCCESS: Went from 4 to 3 nodes -# >> Loading inputs: 100%|██████████| 1/1 [00:00<00:00, 1341.75it/s] -# >> torch._dynamo.utils: [ERROR] RMSE (res-fp64): 2.39615, (ref-fp64): 0.00000 and shape=torch.Size([1, 8]) -# Trying granularity 2 -# Strategy: Eliminate dead code (G: 2) (3 nodes, 1 inputs) -# FAIL: Eliminate dead code -# Strategy: Remove unused inputs (G: 2) (3 nodes, 1 inputs) -# FAIL: Remove unused inputs -# Strategy: Consolidate Inputs (G: 2) (3 nodes, 1 inputs) -# FAIL: Consolidate Inputs -# Strategy: Truncate suffix (G: 2) (3 nodes, 1 inputs) -# FAIL: Truncate suffix -# Strategy: Delta Debugging (G: 2) (3 nodes, 1 inputs) -# >> Loading inputs: 100%|██████████| 1/1 [00:00<00:00, 1333.64it/s] -# FAIL: Delta Debugging -# Trying granularity 1 -# Strategy: Truncate suffix (G: 1) (3 nodes, 1 inputs) -# FAIL: Truncate suffix -# Strategy: Delta Debugging (G: 1) (3 nodes, 1 inputs) -# >> Loading inputs: 100%|██████████| 1/1 [00:00<00:00, 1382.43it/s] -# FAIL: Delta Debugging -# Strategy: Remove outputs (G: 1) (3 nodes, 1 inputs) -# FAIL: Remove outputs -# >> Loading inputs: 100%|██████████| 1/1 [00:00<00:00, 1256.53it/s] -# >> torch._dynamo.utils: [ERROR] RMSE (res-fp64): 2.39615, (ref-fp64): 0.00000 and shape=torch.Size([1, 8]) -# Made 10 queries -# Wrote minimal repro out to repro.py # # After running, we get the final minified graph with the target node ``neg``: @@ -595,5 +558,5 @@ def func(x0, x1, x3, x5, x7): # We demonstrate step by step the way to delve deeper the issue and find the root cause of failures, with the help of debugging loggings and the tool Minifier. # Firstly determine which component the failure occurs in and then try to generate the smallest snippet of code that can reproduce the failure. # -# When the performance with Inductor is better or worse than that of eager mode, we give a solid analytical method for performance profiling. -# We show how to find the time-consuming hotspot with PyTorch Profiler and figure out the operator-level or kernel-level reason to explain the phenomenon. \ No newline at end of file +# When the performance with Inductor is better than that of eager mode, we provide a solid analytical method for performance profiling. +# We show how to find the time-consuming hotspot with PyTorch Profiler and figure out the operator-level or kernel-level reason to explain the phenomenon. From 098047118cc8dbdcbe91e122a96cb5d60642aa5e Mon Sep 17 00:00:00 2001 From: zhuhaozhe Date: Fri, 9 Jun 2023 16:04:47 +0800 Subject: [PATCH 18/43] modify variable name (#7) --- intermediate_source/inductor_debug_cpu.py | 55 +++++++++++++---------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index 485ad6c9e9..6394d650fe 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -351,7 +351,8 @@ def forward(self, arg0_1): # --------------------- # # Within this section, we will demonstrate the process of conducting performance analysis for a model that has been compiled using the Inductor CPU backend. -# In the example below, we benchmark a Huggingface Transformer model ``MobileBertForQuestionAnswering`` with both the eager mode and the Inductor graph mode. The execution time and the speedup ratio of Inductor are printed after the benchmark. +# In the example below, we benchmark a Huggingface Transformer model ``MobileBertForQuestionAnswering`` with both the eager mode and the Inductor graph mode. +# The execution time and the speedup ratio of Inductor are printed after the benchmark. from transformers import MobileBertForQuestionAnswering import torch @@ -364,9 +365,9 @@ def forward(self, arg0_1): input_dict = {"input_ids": input} # init inductor model -inductor_model = torch.compile(model) +compiled_model = torch.compile(model) with torch.no_grad(): - inductor_model(**input_dict) + compiled_model(**input_dict) NUM_ITERS=100 import timeit @@ -379,8 +380,8 @@ def forward(self, arg0_1): with torch.no_grad(): # warmup for _ in range(10): - inductor_model(**input_dict) - inductor_t = timeit.timeit("inductor_model(**input_dict)", number=NUM_ITERS, globals=globals()) + 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}") @@ -399,7 +400,8 @@ def forward(self, arg0_1): # # # Next, let's dive deep into the performance at the operation level to understand where the speed-up comes from. -# `Pytorch Profiler `_ is a good tool to help us. Inductor CPU backend has the support to report the time of the fusion kernels to the profiler with the ``enable_kernel_profile`` configuration option: +# `Pytorch Profiler `_ is a good tool to help us. +# Inductor CPU backend has the support to report the time of the fusion kernels to the profiler with the ``enable_kernel_profile`` configuration option: from torch._inductor import config config.cpp.enable_kernel_profile = True @@ -423,7 +425,7 @@ def trace_handler(p): p.export_chrome_trace(f"{RESULT_DIR}/{p.step_num}.json") for _ in range(10): - model(**input_dict) # inductor_model(**input_dict) to get inductor model profiling + model(**input_dict) # compiled_model(**input_dict) to get inductor model profiling total = 0 with profile( @@ -432,7 +434,7 @@ def trace_handler(p): on_trace_ready=trace_handler ) as p: for _ in range(100): - model(**input_dict) # inductor_model(**input_dict) to get inductor model profiling + model(**input_dict) # compiled_model(**input_dict) to get inductor model profiling p.step() ###################################################################### @@ -450,6 +452,9 @@ def trace_handler(p): # # (1) Regard to ``mkl::_mkl_linear``: You may notice the number of calls to this kernel is 362, which is exactly the same as ``aten::linear`` in the eager model profiling table. # The CPU total of ``aten::linear`` is 376.888ms, at the mean time it is 231.573ms for ``mkl::_mkl_linear``. This suggests inductor model speed up ~1.63x for the "linear" part. +# The speed-up majorly comes from we "packed" the ``weight`` tensor to `block memory format `_ +# and invoking `cblas_sgemm_compute `_ within Inductor CPU backend +# to have a better cache beviour during GEMM computation. # # (2) Regarding non-linear part: The end-to-end latency for the eager/inductor model is 802/339ms. The speed up for the non-linear part is ~3.94x. # Let's read the generated code to understand how the inductor achieves this impressive optimization. You are able to find the generated code by @@ -494,24 +499,25 @@ def trace_handler(p): ###################################################################### # From the generated code above, we can see this kernel has done a typical `Loop Fusion `_ on [add, add, mul, add]. -# We can infer the sizes and stride of the inputs and further bench this [add, add, mul, add] pattern. +# This is a memory-bound bottle neck preventing good performance. To get a more intuitive feeling about this optimization, +# we can infer the sizes and stride of the inputs and further benchmark this [add, add, mul, add] pattern. import torch -def func(x0, x1, x3, x5, x7): - x2 = x0 + x1 - x4 = x2 + x3 - x6 = x4 * x5 - x8 = x6 + x7 - x3 = x8 - return x3 - -x0 = torch.rand(16384, 512) -x1 = torch.rand(1, 512) -x3 = torch.zeros(16384, 512) -x5 = torch.rand(1, 512) -x7 = torch.rand(1, 512) - -input = (x0, x1, x3, x5, x7) +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) @@ -532,6 +538,7 @@ def func(x0, x1, x3, x5, x7): 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}") + ###################################################################### # Output: # From 7e20a223f2c897af7011f186a42e11651f541fe6 Mon Sep 17 00:00:00 2001 From: zhuhaozhe Date: Sat, 10 Jun 2023 09:45:42 +0800 Subject: [PATCH 19/43] Apply suggestions from code review Co-authored-by: William Wen --- intermediate_source/inductor_debug_cpu.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index 6394d650fe..d566c20253 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -446,18 +446,18 @@ def trace_handler(p): # # .. image:: ../_static/img/inductor_prof.png # -# From the profiling table of the eager model, we can see the most time consumption ops are [aten::addmm, aten::add, aten::copy_, aten::mul, aten::clamp_min, aten::bmm]. -# Comparing with the inductor model profiling table, we notice there are ``mkl::_mkl_linear`` and fused kernel called ``graph_0_cpp_fused_*``. They are the major -# optimization that the inductor model is doing. Let us discuss them separately. +# From the profiling table of the eager model, we can see the most time consumption ops are [``aten::addmm``, ``aten::add``, ``aten::copy_``, ``aten::mul``, ``aten::clamp_min``, ``aten::bmm``]. +# Comparing with the inductor model profiling table, we notice an ``mkl::_mkl_linear`` entry and multiple fused kernels in the form ``graph_0_cpp_fused_*``. They are the major +# optimizations that the inductor model is doing. Let us discuss them separately. # -# (1) Regard to ``mkl::_mkl_linear``: You may notice the number of calls to this kernel is 362, which is exactly the same as ``aten::linear`` in the eager model profiling table. -# The CPU total of ``aten::linear`` is 376.888ms, at the mean time it is 231.573ms for ``mkl::_mkl_linear``. This suggests inductor model speed up ~1.63x for the "linear" part. -# The speed-up majorly comes from we "packed" the ``weight`` tensor to `block memory format `_ -# and invoking `cblas_sgemm_compute `_ within Inductor CPU backend -# to have a better cache beviour during GEMM computation. +# (1) Regarding ``mkl::_mkl_linear``: You may notice the number of calls to this kernel is 362, which is exactly the same as ``aten::linear`` in the eager model profiling table. +# The CPU total of ``aten::linear`` is 376.888ms, while it is 231.573ms for ``mkl::_mkl_linear``. This suggests a ~1.63x for the "linear" part. +# The speedup mainly comes "packing" the ``weight`` tensor to `block memory format `_ +# and invoking `cblas_sgemm_compute `_ within the Inductor CPU backend +# to have a better cache behavior during GEMM computation. # # (2) Regarding non-linear part: The end-to-end latency for the eager/inductor model is 802/339ms. The speed up for the non-linear part is ~3.94x. -# Let's read the generated code to understand how the inductor achieves this impressive optimization. You are able to find the generated code by +# Let's read the generated code to understand how the inductor achieves this impressive optimization. You can find the generated code by # searching ``cpp_fused__mkl_linear_add_mul_relu_151`` in ``output_code.py`` # From cebd517b9d202ac5cce56fb45e501722d109fa16 Mon Sep 17 00:00:00 2001 From: zhuhaozhe Date: Mon, 12 Jun 2023 10:11:50 +0800 Subject: [PATCH 20/43] using in our testing for speed up numbers and other statement fix (#8) --- intermediate_source/inductor_debug_cpu.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index d566c20253..dbc5add797 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -396,7 +396,7 @@ def forward(self, arg0_1): # inductor use: 339.95180135127157 ms/iter # speed up ratio: 2.359459053287382 # -# The inductor model speed-up is 2.58x. +# In our own testing, we find the Inductor CPU backend speed up the model by around 2.355x. # # # Next, let's dive deep into the performance at the operation level to understand where the speed-up comes from. @@ -452,11 +452,11 @@ def trace_handler(p): # # (1) Regarding ``mkl::_mkl_linear``: You may notice the number of calls to this kernel is 362, which is exactly the same as ``aten::linear`` in the eager model profiling table. # The CPU total of ``aten::linear`` is 376.888ms, while it is 231.573ms for ``mkl::_mkl_linear``. This suggests a ~1.63x for the "linear" part. -# The speedup mainly comes "packing" the ``weight`` tensor to `block memory format `_ +# The speedup mainly comes `packing the weight tensor to block memory format `_ # and invoking `cblas_sgemm_compute `_ within the Inductor CPU backend # to have a better cache behavior during GEMM computation. # -# (2) Regarding non-linear part: The end-to-end latency for the eager/inductor model is 802/339ms. The speed up for the non-linear part is ~3.94x. +# (2) Regarding other memory-intensive ops: The end-to-end latency for the eager/inductor model is 802/339ms in our testing. So we can roughly infer that the speed up for the other memory-intensive ops is around 3.94x. # Let's read the generated code to understand how the inductor achieves this impressive optimization. You can find the generated code by # searching ``cpp_fused__mkl_linear_add_mul_relu_151`` in ``output_code.py`` # From 502093f99ad5569d728a4c6a27fadb3cf33a8ae1 Mon Sep 17 00:00:00 2001 From: zhuhaozhe Date: Mon, 12 Jun 2023 10:17:55 +0800 Subject: [PATCH 21/43] change link for pack (#9) --- intermediate_source/inductor_debug_cpu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index dbc5add797..580e13b452 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -452,7 +452,7 @@ def trace_handler(p): # # (1) Regarding ``mkl::_mkl_linear``: You may notice the number of calls to this kernel is 362, which is exactly the same as ``aten::linear`` in the eager model profiling table. # The CPU total of ``aten::linear`` is 376.888ms, while it is 231.573ms for ``mkl::_mkl_linear``. This suggests a ~1.63x for the "linear" part. -# The speedup mainly comes `packing the weight tensor to block memory format `_ +# The speedup mainly comes from `packing the weight tensor to block memory format `_ # and invoking `cblas_sgemm_compute `_ within the Inductor CPU backend # to have a better cache behavior during GEMM computation. # From 3cd1e068902c6683e7cb59e8b86bcfc081f043d0 Mon Sep 17 00:00:00 2001 From: zhuhaozhe Date: Mon, 12 Jun 2023 12:59:37 +0800 Subject: [PATCH 22/43] add co-author (#10) --- intermediate_source/inductor_debug_cpu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index 580e13b452..e3017360b8 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -4,7 +4,7 @@ Inductor CPU backend debugging and profiling ============================================ -**Authors**: `Liao Xuan `_, `Zhu Haozhe `_, `Gong Jiong `_ +**Authors**: `Liao Xuan `_, `Zhu Haozhe `_, `Gong Jiong `_, `Wang Weihan `_ """ ######################################################################### From accf7f18719ef90a1487382b1b93e3ebca553d7f Mon Sep 17 00:00:00 2001 From: zhuhaozhe Date: Tue, 13 Jun 2023 08:57:39 +0800 Subject: [PATCH 23/43] Apply suggestions from code review Co-authored-by: Svetlana Karslioglu --- intermediate_source/inductor_debug_cpu.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index e3017360b8..1565eb9fb7 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -351,12 +351,12 @@ def forward(self, arg0_1): # --------------------- # # Within this section, we will demonstrate the process of conducting performance analysis for a model that has been compiled using the Inductor CPU backend. -# In the example below, we benchmark a Huggingface Transformer model ``MobileBertForQuestionAnswering`` with both the eager mode and the Inductor graph mode. +# In the example below, we benchmark a Hugging Face Transformer model ``MobileBertForQuestionAnswering`` with both the eager mode and the Inductor graph mode. # The execution time and the speedup ratio of Inductor are printed after the benchmark. from transformers import MobileBertForQuestionAnswering import torch -# init an eager model +# Initialize an eager model model = MobileBertForQuestionAnswering.from_pretrained("csarron/mobilebert-uncased-squad-v2") seq_length = 128 bs = 128 @@ -364,7 +364,7 @@ def forward(self, arg0_1): input = torch.randint(0, vocab_size, (bs, seq_length), dtype=torch.int64) input_dict = {"input_ids": input} -# init inductor model +# Initialize the inductor model compiled_model = torch.compile(model) with torch.no_grad(): compiled_model(**input_dict) @@ -498,9 +498,9 @@ def trace_handler(p): ''') ###################################################################### -# From the generated code above, we can see this kernel has done a typical `Loop Fusion `_ on [add, add, mul, add]. +# From the generated code above, we can see this kernel has done a typical `Loop Fusion `_ on ``[add, add, mul, add]``. # This is a memory-bound bottle neck preventing good performance. To get a more intuitive feeling about this optimization, -# we can infer the sizes and stride of the inputs and further benchmark this [add, add, mul, add] pattern. +# we can infer the sizes and stride of the inputs and further benchmark this ``[add, add, mul, add]`` pattern. import torch def func(arg_0, arg_1, arg_2, arg_3, arg_4): From 60551d0c0f021ccaea07fc20983a9d3e7fd17796 Mon Sep 17 00:00:00 2001 From: Xuan Liao Date: Tue, 13 Jun 2023 09:55:04 +0800 Subject: [PATCH 24/43] Apply suggestions from code review Co-authored-by: Svetlana Karslioglu --- intermediate_source/inductor_debug_cpu.py | 48 +++++++++++------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index 1565eb9fb7..c1b907de8c 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -64,7 +64,7 @@ def neg(x): # In order to demonstrate the debugging, we will modify the function to a wrong one later. # # -# Get more loggings +# Get more logging information # ^^^^^^^^^^^^^^^^^ # # No debugging information would be provided if you run this simple example by default. In order to get more useful debugging and logging information, we usually add a ``TORCH_COMPILE_DEBUG`` environment variable like below: @@ -84,15 +84,15 @@ def neg(x): # +-------------------------+----------------------------------------------------------+ # | File | Description | # +=========================+==========================================================+ -# | fx_graph_runnable.py | Executable FX graph, post decomps, pre pattern match | +# | ``fx_graph_runnable.py`` | Executable FX graph, post decompositions, pre pattern match | # +-------------------------+----------------------------------------------------------+ -# | fx_graph_transformed.py | Transformed FX graph, post pattern match | +# | ``fx_graph_transformed.py`` | Transformed FX graph, post pattern match | # +-------------------------+----------------------------------------------------------+ -# | ir_post_fusion.txt | Inductor IR before fusion | +# | ``ir_post_fusion.txt`` | Inductor IR before fusion | # +-------------------------+----------------------------------------------------------+ -# | ir_pre_fusion.txt | Inductor IR after fusion | +# | ``ir_pre_fusion.txt`` | Inductor IR after fusion | # +-------------------------+----------------------------------------------------------+ -# | output_code.py | Generated Python code for graph, with C++/triton kernels | +# | ``output_code.py`` | Generated Python code for graph, with C++/Triton kernels | # +-------------------------+----------------------------------------------------------+ # # Note that ``fx_graph_runnable.py`` and ``output_code.py`` are both runnable and editable in order to make debugging easier. @@ -149,11 +149,11 @@ def forward(self, arg0_1, arg1_1): # +----------------------------------------+-----------------------------------------+ # | Code | Description | # +========================================+=========================================+ -# | torch.compile(fn, backend="eager") | Enable Dynamo | +# | ``torch.compile(fn, backend="eager")`` | Enable Dynamo | # +----------------------------------------+-----------------------------------------+ -# | torch.compile(fn, backend="aot_eager") | Enable Dynamo + AOT autograd | +# | ``torch.compile(fn, backend="aot_eager")`` | Enable Dynamo + AOT Autograd | # +----------------------------------------+-----------------------------------------+ -# | torch.compile(fn, backend="inductor") | Enable Dynamo + AOT autograd + Inductor | +# | ``torch.compile(fn, backend="inductor")`` | Enable Dynamo + AOT Autograd + Inductor | # +----------------------------------------+-----------------------------------------+ # # If the model can successfully run when the backend is set to ``eager`` or ``aot_eager`` while it fails with ``inductor``, we can narrow down the failure to Inductor. @@ -182,21 +182,21 @@ def neg(x): # # .. code:: shell # -# … -# 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&)’ +# … +# 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 scalar_t max_propagate_nan(scalar_t, scalar_t)’ -# 27 | inline scalar_t max_propagate_nan(scalar_t a, scalar_t b) { +# | ^ +# In file included from /tmp/torchinductor_root/xg/cxga5tk3b4lkwoxyigrtocjp5s7vc5cg2ikuscf6bk6pjqip2bhx.cpp:2: +# /tmp/torchinductor_root/gv/cgv6n5aotqjo5w4vknjibhengeycuattfto532hkxpozszcgxr3x.h:27:17: note: candidate: ‘template 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/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); +# 17 | auto tmp3 = max_propagate_nan(tmp0, tmp2); # | ^ # # @@ -263,7 +263,7 @@ def neg(x): # By checking the C++ kernel, we know that ``tmp2`` is no longer ``long`` after doing ``-`` as ``tmp0`` is ``long``. # We can easily match ``-`` and ``max_propagate_nan`` in C++ kernel with ``ops.neg`` and ``ops.maximum`` in IR node respectively. # -# Now we sucessfully find that the root cause is the implementation of ``ops.neg`` in cpp codegen, which silently changes the data type when doing ``neg``. +# Now we successfully find that the root cause is the implementation of ``ops.neg`` in cpp codegen, which silently changes the data type when doing ``neg``. # # # Accuracy debugging @@ -320,9 +320,9 @@ def neg(x): # # .. code:: shell # -# TORCHDYNAMO_REPRO_AFTER="aot" TORCHDYNAMO_REPRO_LEVEL=4 python xx.py +# TORCHDYNAMO_REPRO_AFTER="aot" TORCHDYNAMO_REPRO_LEVEL=4 python xx.py # -# Which gives us such loggings showing the steps of minifying: +# Which gives us logging information that demonstrates the steps of minifying: # # .. code:: shell # From ce1c2b8244aef14dfc51a9256d3958f017e2013d Mon Sep 17 00:00:00 2001 From: "Liao, Xuan" Date: Tue, 13 Jun 2023 09:56:36 +0000 Subject: [PATCH 25/43] fix format of table --- intermediate_source/inductor_debug_cpu.py | 26 +++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index c1b907de8c..8d411dbb34 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -81,19 +81,19 @@ def neg(x): # # In this directory, the following files are saved for debugging purposes: # -# +-------------------------+----------------------------------------------------------+ -# | File | Description | -# +=========================+==========================================================+ -# | ``fx_graph_runnable.py`` | Executable FX graph, post decompositions, pre pattern match | -# +-------------------------+----------------------------------------------------------+ -# | ``fx_graph_transformed.py`` | Transformed FX graph, post pattern match | -# +-------------------------+----------------------------------------------------------+ -# | ``ir_post_fusion.txt`` | Inductor IR before fusion | -# +-------------------------+----------------------------------------------------------+ -# | ``ir_pre_fusion.txt`` | Inductor IR after fusion | -# +-------------------------+----------------------------------------------------------+ -# | ``output_code.py`` | Generated Python code for graph, with C++/Triton kernels | -# +-------------------------+----------------------------------------------------------+ +# +-----------------------------+-------------------------------------------------------------+ +# | File | Description | +# +=============================+=============================================================+ +# | ``fx_graph_runnable.py`` | Executable FX graph, post decompositions, pre pattern match | +# +-----------------------------+-------------------------------------------------------------+ +# | ``fx_graph_transformed.py`` | Transformed FX graph, post pattern match | +# +-----------------------------+-------------------------------------------------------------+ +# | ``ir_post_fusion.txt`` | Inductor IR before fusion | +# +-----------------------------+-------------------------------------------------------------+ +# | ``ir_pre_fusion.txt`` | Inductor IR after fusion | +# +-----------------------------+-------------------------------------------------------------+ +# | ``output_code.py`` | Generated Python code for graph, with C++/Triton kernels | +# +-----------------------------+-------------------------------------------------------------+ # # Note that ``fx_graph_runnable.py`` and ``output_code.py`` are both runnable and editable in order to make debugging easier. # Here are the main parts of code extracted from the files and we correlate the C++ generated line with the FX code line. From 6472df5dcf5e7ff061dc97dbb8589c518a96003d Mon Sep 17 00:00:00 2001 From: "Liao, Xuan" Date: Tue, 13 Jun 2023 09:56:36 +0000 Subject: [PATCH 26/43] fix format of table --- intermediate_source/inductor_debug_cpu.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index 8d411dbb34..d18ec7a8c0 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -146,15 +146,15 @@ def forward(self, arg0_1, arg1_1): # # When encountering errors or accuracy problems, a straightforward solution to find the bug is to narrow down the problem. The first thing to do is to determine the component where the error occurs. Luckily, it can be simply achieved by changing the backend of ``torch.compile``. # -# +----------------------------------------+-----------------------------------------+ -# | Code | Description | -# +========================================+=========================================+ +# +--------------------------------------------+-----------------------------------------+ +# | Code | Description | +# +============================================+=========================================+ # | ``torch.compile(fn, backend="eager")`` | Enable Dynamo | -# +----------------------------------------+-----------------------------------------+ +# +--------------------------------------------+-----------------------------------------+ # | ``torch.compile(fn, backend="aot_eager")`` | Enable Dynamo + AOT Autograd | -# +----------------------------------------+-----------------------------------------+ +# +--------------------------------------------+-----------------------------------------+ # | ``torch.compile(fn, backend="inductor")`` | Enable Dynamo + AOT Autograd + Inductor | -# +----------------------------------------+-----------------------------------------+ +# +--------------------------------------------+-----------------------------------------+ # # If the model can successfully run when the backend is set to ``eager`` or ``aot_eager`` while it fails with ``inductor``, we can narrow down the failure to Inductor. # From d5e5729b074214a9087ee1b7c5d23d05f1da82d5 Mon Sep 17 00:00:00 2001 From: "Liao, Xuan" Date: Wed, 14 Jun 2023 10:05:25 +0000 Subject: [PATCH 27/43] fix format to pass CI --- intermediate_source/inductor_debug_cpu.py | 50 +++++++++++------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index d18ec7a8c0..a1f929fb38 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -25,7 +25,7 @@ # We will start debugging with a motivating example that triggers compilation issues and accuracy problems # by demonstrating the process of debugging to pinpoint the problems. # -# By enabling loggings and exploring the underlying generated code, +# By enabling logging and exploring the underlying generated code, # you can learn how to narrow down the failure step by step and finally figure out the route cause. # # Following that, we will proceed to discuss how to profile the compiled code and, @@ -55,7 +55,7 @@ def foo(x1, x2): result = compiled_foo(x1, x2) ###################################################################### -# The correct implementation of ``neg`` in the cpp codegen is as follows: +# The correct implementation of ``neg`` in the ``cpp`` codegen is as follows: def neg(x): return f"decltype({x})(-{x})" @@ -69,36 +69,36 @@ def neg(x): # # No debugging information would be provided if you run this simple example by default. In order to get more useful debugging and logging information, we usually add a ``TORCH_COMPILE_DEBUG`` environment variable like below: # -# .. code:: shell +# :: # # TORCH_COMPILE_DEBUG=1 python xx.py # # This would print more debug information in the output logs and also dump the intermediate IRs generated during the codegen process. You can find the dumped file paths in the log like below: # -# .. code:: shell +# :: # # torch._inductor.debug: [WARNING] model___20 debug trace: /tmp/torchinductor_root/rx/crxfi2ybd7yp5sbj2pnhw33wfhtdw7wumvrobyp5sjvdui5ktjc2.debug # # In this directory, the following files are saved for debugging purposes: # -# +-----------------------------+-------------------------------------------------------------+ -# | File | Description | -# +=============================+=============================================================+ -# | ``fx_graph_runnable.py`` | Executable FX graph, post decompositions, pre pattern match | -# +-----------------------------+-------------------------------------------------------------+ -# | ``fx_graph_transformed.py`` | Transformed FX graph, post pattern match | -# +-----------------------------+-------------------------------------------------------------+ -# | ``ir_post_fusion.txt`` | Inductor IR before fusion | -# +-----------------------------+-------------------------------------------------------------+ -# | ``ir_pre_fusion.txt`` | Inductor IR after fusion | -# +-----------------------------+-------------------------------------------------------------+ -# | ``output_code.py`` | Generated Python code for graph, with C++/Triton kernels | -# +-----------------------------+-------------------------------------------------------------+ +# +-----------------------------+----------------------------------------------------------------+ +# | File | Description | +# +=============================+================================================================+ +# | ``fx_graph_runnable.py`` | Executable FX graph, after decomposition, before pattern match | +# +-----------------------------+----------------------------------------------------------------+ +# | ``fx_graph_transformed.py`` | Transformed FX graph, after pattern match | +# +-----------------------------+----------------------------------------------------------------+ +# | ``ir_post_fusion.txt`` | Inductor IR before fusion | +# +-----------------------------+----------------------------------------------------------------+ +# | ``ir_pre_fusion.txt`` | Inductor IR after fusion | +# +-----------------------------+----------------------------------------------------------------+ +# | ``output_code.py`` | Generated Python code for graph, with C++/Triton kernels | +# +-----------------------------+----------------------------------------------------------------+ # # Note that ``fx_graph_runnable.py`` and ``output_code.py`` are both runnable and editable in order to make debugging easier. # Here are the main parts of code extracted from the files and we correlate the C++ generated line with the FX code line. # -# Fx_graph_runnable: +# ``fx_graph_runnable``: def forward(self, arg0_1, arg1_1): neg = torch.ops.aten.neg.default(arg0_1); arg0_1 = None @@ -107,7 +107,7 @@ def forward(self, arg0_1, arg1_1): return (clone,) ###################################################################### -# C++ kernel in output_code: +# C++ kernel in ``output_code``: from torch._inductor.codecache import AsyncCompile async_compile = AsyncCompile() @@ -180,7 +180,7 @@ def neg(x): ###################################################################### # The logging gives the following compile error with a rather clear reason. # -# .. code:: shell +# :: # # … # torch._dynamo.exc.BackendCompilerFailed: backend='inductor' raised: @@ -263,7 +263,7 @@ def neg(x): # By checking the C++ kernel, we know that ``tmp2`` is no longer ``long`` after doing ``-`` as ``tmp0`` is ``long``. # We can easily match ``-`` and ``max_propagate_nan`` in C++ kernel with ``ops.neg`` and ``ops.maximum`` in IR node respectively. # -# Now we successfully find that the root cause is the implementation of ``ops.neg`` in cpp codegen, which silently changes the data type when doing ``neg``. +# Now we successfully find that the root cause is the implementation of ``ops.neg`` in ``cpp`` codegen, which silently changes the data type when doing ``neg``. # # # Accuracy debugging @@ -308,7 +308,7 @@ def neg(x): ###################################################################### # An accuracy problem would be raised as follows: # -# .. code:: shell +# :: # # torch._dynamo.utils: [ERROR] Accuracy failed: allclose not within tol=0.0001 # Traceback (most recent call last): @@ -318,13 +318,13 @@ def neg(x): # # To debug an accuracy problem with Minifier, two environment variables are needed: # -# .. code:: shell +# :: # # TORCHDYNAMO_REPRO_AFTER="aot" TORCHDYNAMO_REPRO_LEVEL=4 python xx.py # # Which gives us logging information that demonstrates the steps of minifying: # -# .. code:: shell +# :: # # Started off with 6 nodes # @@ -562,7 +562,7 @@ def func(arg_0, arg_1, arg_2, arg_3, arg_4): # With motivating examples, we walk through the process of debugging and profiling. # The main idea is to narrow down the problem. # -# We demonstrate step by step the way to delve deeper the issue and find the root cause of failures, with the help of debugging loggings and the tool Minifier. +# We demonstrate step by step the way to delve deeper the issue and find the root cause of failures, with the help of debugging logging and the tool Minifier. # Firstly determine which component the failure occurs in and then try to generate the smallest snippet of code that can reproduce the failure. # # When the performance with Inductor is better than that of eager mode, we provide a solid analytical method for performance profiling. From 72873944a66fffe803b4c9dc743229a46e504078 Mon Sep 17 00:00:00 2001 From: "Liao, Xuan" Date: Wed, 14 Jun 2023 10:05:25 +0000 Subject: [PATCH 28/43] fix format to pass CI --- intermediate_source/inductor_debug_cpu.py | 30 +++++++++-------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index a1f929fb38..ba5bcff14b 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -40,9 +40,8 @@ # Here is a simple example to run the ``torch.compile`` using Inductor and compare its result with eager mode: import torch -from torch._dynamo.utils import same -def foo(x1, x2): +def foo1(x1, x2): a = torch.neg(x1) b = torch.maximum(x2, a) y = torch.cat([b], dim=0) @@ -51,13 +50,13 @@ def foo(x1, x2): x1 = torch.randint(256, (1, 8), dtype=torch.uint8) x2 = torch.randint(256, (8390, 8), dtype=torch.uint8) -compiled_foo = torch.compile(foo) -result = compiled_foo(x1, x2) +compiled_foo1 = torch.compile(foo1) +result = compiled_foo1(x1, x2) ###################################################################### # The correct implementation of ``neg`` in the ``cpp`` codegen is as follows: -def neg(x): +def neg1(x): return f"decltype({x})(-{x})" ###################################################################### @@ -100,7 +99,7 @@ def neg(x): # # ``fx_graph_runnable``: -def forward(self, arg0_1, arg1_1): +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 @@ -174,7 +173,7 @@ def forward(self, arg0_1, arg1_1): # # For example, the ``neg`` function is modified like this: -def neg(x): +def neg2(x): return f"-{x}" ###################################################################### @@ -182,10 +181,8 @@ def neg(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); @@ -280,10 +277,9 @@ def neg(x): # # For instance, we modify the example like this: -import torch from torch._dynamo.utils import same -def foo(x1, x2): +def foo2(x1, x2): a = torch.neg(x1) b = torch.maximum(x2, a) y = torch.cat([b], dim=0) @@ -292,17 +288,17 @@ def foo(x1, x2): x1 = torch.randn((1, 8), dtype=torch.float32) x2 = torch.randn((8390, 8), dtype=torch.float32) -expected_result = foo(x1, x2) +expected_result = foo2(x1, x2) -compiled_foo = torch.compile(foo) -actual_result = compiled_foo(x1, x2) +compiled_foo2 = torch.compile(foo2) +actual_result = compiled_foo2(x1, x2) assert same(expected_result, actual_result) == True ###################################################################### # And also modify the ``neg`` function: -def neg(x): +def neg3(x): return f"decltype({x})(2 * {x})" ###################################################################### @@ -338,7 +334,7 @@ def neg(x): # # After running, we get the final minified graph with the target node ``neg``: -def forward(self, arg0_1): +def forward2(self, arg0_1): neg = torch.ops.aten.neg.default(arg0_1); arg0_1 = None return (neg,) @@ -355,7 +351,6 @@ def forward(self, arg0_1): # The execution time and the speedup ratio of Inductor are printed after the benchmark. from transformers import MobileBertForQuestionAnswering -import torch # Initialize an eager model model = MobileBertForQuestionAnswering.from_pretrained("csarron/mobilebert-uncased-squad-v2") seq_length = 128 @@ -502,7 +497,6 @@ def trace_handler(p): # This is a memory-bound bottle neck preventing good performance. To get a more intuitive feeling about this optimization, # we can infer the sizes and stride of the inputs and further benchmark this ``[add, add, mul, add]`` pattern. -import torch def func(arg_0, arg_1, arg_2, arg_3, arg_4): add_0 = arg_0 + arg_1 add_1 = add_0 + arg_2 From 521cb24cb9e6ee608485d74870f4e0a0708b1c34 Mon Sep 17 00:00:00 2001 From: Svetlana Karslioglu Date: Wed, 14 Jun 2023 15:02:15 -0700 Subject: [PATCH 29/43] Update intermediate_source/inductor_debug_cpu.py --- intermediate_source/inductor_debug_cpu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index ba5bcff14b..95c295061f 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -136,7 +136,7 @@ def forward1(self, arg0_1, arg1_1): } } } -''') +}''') ###################################################################### From c67da4646514bdd78cf7125c1341ce16de0c7f71 Mon Sep 17 00:00:00 2001 From: Svetlana Karslioglu Date: Wed, 14 Jun 2023 15:06:42 -0700 Subject: [PATCH 30/43] Apply suggestions from code review --- intermediate_source/inductor_debug_cpu.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index 95c295061f..6ac666511f 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -135,7 +135,6 @@ def forward1(self, arg0_1, arg1_1): } } } -} }''') @@ -489,8 +488,7 @@ def trace_handler(p): } } } -} -''') +}''') ###################################################################### # From the generated code above, we can see this kernel has done a typical `Loop Fusion `_ on ``[add, add, mul, add]``. From eb4abc903c6b4094524e1319cdd9f1fdfae32503 Mon Sep 17 00:00:00 2001 From: zhuhaozhe Date: Fri, 16 Jun 2023 11:00:03 +0800 Subject: [PATCH 31/43] use original table for profile (omitting some columns) (#11) --- _static/img/eager_prof.png | Bin 86538 -> 0 bytes _static/img/inductor_prof.png | Bin 106315 -> 0 bytes intermediate_source/inductor_debug_cpu.py | 61 ++++++++++++++++++++-- 3 files changed, 57 insertions(+), 4 deletions(-) delete mode 100644 _static/img/eager_prof.png delete mode 100644 _static/img/inductor_prof.png diff --git a/_static/img/eager_prof.png b/_static/img/eager_prof.png deleted file mode 100644 index 336ccabbeb55f19ef91b5eec3f89bc2472eff628..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86538 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%0A;M`=pTY9 z1IM06k+u4lUL)m+J;=LOzrkSv7jE$Aflwt4`HtKfX5e+}#)6e(DB7(f&)1xEBrJRd27yDnm7n;RMSh*Ape|UaF4l5 z!*GJ}hEs~<-`H6kTtF;?9uJ&+(i}ouY{N3AVbRd*$yszx;~dG~ksx_Z8HJPO@3<$| zqW(u|{N~0F&`q_`)ke=3}wyLixvT|>RryW;W@^Sbbd?3NTc_QL(N7XlevQGQ+^KO$rJQnL*RNc@Z9U|K5P3lN zmHwM8A`hC>h=2JoO2Y2KS;Rfa@wfk+!F;vdA%;vWxXc&=oV$$w*fTRTv+(xDztb0B z^e2*Q&!|-Tfe`*F%Ba)G_zQNt3!%gwUP-{aK!B{67lTx9e8K10zQlM>5&^2TU`bis zocbsY4a+OotGlZ zpic?W_fWgVX!aFmI3Ut|^ol{j7XHav_@46_kM78;E8$@RLZz?Dp-Y?kIrLrNsxQpU z+a8nr^$O-b&E7H5wq@*xc}Ysozhnx@quThc>etoF|E#U^twKTMHEqhM8|K!dG~sII z#k!CY?c0hC zpgX@(zD51y{#^T)w3wk7B(P3~Va}G?RXCl}V$a|jH;MFrTV8C1{X?fplK-OZ3I6~8 zaZ(`tSSJ795b9P>u= zzGxgYFg|L+I26d52qkze)a)dOOaTKmjmZtRHX6yvs<8V4lT}!^m1u)!*RTUIHnUP+ zHs$c^S42S+_6->u834f6*jc%*@%}DM>0x1EAvqfNL~kgLIUA)Vyl zP1~iVTpE*L-DEF{yfu4CA9gPO;u(AD>+^QqCFa=uOsY^}EAT2@OM3eW(`UKJ@R17T z3`}C(W7kiV7rwLS2l}RGyImV8iR!URj*6U$6#o7t)?>t5uB4UHX^})HJw6UEn)%m) zb)(69CGWg*As8B%A~fas92tZdZyrvJlc1Ie2M>#%;PcSKFt9O!qp@B3QuKjJmbZO9 zwm4!({g-d;DhSCur5B;vV}4YAI?7$dg5siVQztHULl zBnscI;(TIR=`(tRGpu<`iTC)gHf5WU%;z+m2!@#M@avkx=3nnAUChOre|u~k`@p91 zM`JK%Zmp5&o+IS2WCDlh#w_se*i&qSo?uTpp&YPr5hR=W7Kxd?QrRfRQZ$r9yq#ZL zTd=g*MhG^)$XK&`oUCxo@T5M+UCoE%EgdM02O+S{ra9EmfQu7s6d+U8vz8Ee=8m}L z9&N8cYC!W@w`DC7Dp!}u@D8B996^q0s{?LYE4Nm?POFup7XdAg^V!KZSK}tU%c_1u ztYF8?%=3wKW6cVDT9Y3Vm;Ag?{bvzwHW}8R0ygX@*U|3lFMlu_EjI>QOfDweY#!ks|@p`>_dBBPceuD5Iu6MOR%l(B~mCEEa0T;*AmxNjlOFLPs^nveJW15c+Pxn{!LVDYU*vfNrf?DH-6|G2FB<0_a3Cc72aPf=? zE;-h?Xn7}5%l0=mr5JX8;=H zK2vwy4Uc@QX~GPZ*fQqfj+pfZ>$Bue9BR`rtSU<`2Bm1<3a5+lMG+!LDsrIx=HcR9 zdfKwyOuX5x=D8`|*xVpzlVr%nhqvL=wH3St8Z@cej~|`*T);70pP)tFZ4trkF=!&b z2kS{vV!Lg=ddVFIB}9BrpEXaF6Q4Di`5Yi^+f~|Z{nY+a<{I(iJov<=uA3Nm*lp?A z-IKR+tXk^kcQzJa86Kk~8@V7U>r2)3w%^M6VT^-gCS|&H7ns5xIaKQM%I*;lvOTg6kd>;~1k*xOTrUYv&$_fV&xlQ8@N0 z|J<>!sOa*ov%XsJr25o4HD*xg=@HDnye*r_Z0me4~L@VzJ}-5oE_ zefHqI)$&Aq4ssc;r+Ugcq4w_AcXdKNCVz|31*Fz3YxiS>9^X&dOcTfY&}wsslP#^e z!(X=YIWvwhZ_d+w0E$^XvHl&gdO89vRstf13`-iis_x#S8Zgxec5VwDc|uFc^B(z9 z(xq_+pFf#haW9nMuLSqvor|x3XwTRPyq@9+R1q)UwO%^5P#)QfuWp@X%xc@|VEhwtQ@rx$iZ0JImiQDYTWG{R9UUh+DSQw#=PI{y92?62DCg z@LX^-_+dew$Dj)Rfl&GRX`r(^trusU$6sS6FxWxHu4@!0M>9{M{3XeQJs8)a>Tczn zfsm-6-?Yz~%GcIJN7)GkoC2iB`9SuWXW{XvccCECY8s9E%F0Z3?wr!$n*KQjvRR>D zq->neCD1-PVIKCKtdeb!MTFLKpB`aX+W515_N@7FxzCuMU#fztEhc)g-OM~GCD(npb;&JtEGJP(jlJ;c z#d+myR>+hR|gC_YqN0ff6qHA>`~m?6r$-v`|~n&?(Bb@{5DEWI)NqTOG#-&Y-js z7h#|}BC*_)y>h+yvcI%JR&S*%H^F}nk2vxBVhw28iFo~;Zm&tQs&Jtf*3(w6@`_YM z7^aU7;Pb6uEw8{FSi}^@wmjlP4lGfb$tbcFjx!v0UXj&9vWq=NL)(D#3X!64y)kPeybKQHJ2l-TpiNlOi7r>989IU>Q8^z`Ocg5#q5GiB#4EPjxCUbb7jjjSRvwaFFexy zeDNaihOC7s^1=MPUvCZvo;MxwRvsb~+R?gv!v~Il5H>pf6;o}AVH4K5&o+gSPn~rn ze%T{`P5v?o*Jg{En(8zVfOfvxmXi2Q+=fNNN|efJI==*olJn>xB6l zKA@4|eP2uL5N>Wo)Wb>y1*j1-2|+^500@|IZZNXJ4aXlE%N!UHfg+aMcKxu<(Oe*| zO5;5=3kjf03^*EKCWfg%{>D!2kftrLFro@AVx%?Noofdh4Br53mw;pg1)vQa^`g11 zc8RpKc>Dy<%11E=1my?MXk`RJ_yHY67GLj!5~H=}tYj`-{h&a@NJ+4fCgZ(rG;sk9 zijA`_;iDdn`_cIUvBeS*mMI_%Nu#@o0{+S4w>mI|aj#bNLgYCQf z-eP($n8k{~^}X^FAEo0jE2^FTTmXnS8?)y%T$00?xuDYqr5G9h0l@zbaN}q3qu}Cs zxo20P(wuy*Sc7asHH$M=B$>*-?A3mfq2Z*tF))-H&mwCykD5 z?(a}=kgLO_28pO3XIRS0(R_un#iu>{M_TV>)gW8RN5)D(61l@+MqB3zJl9SuB7jIQG89;NIj^$*aICt zTVD16AfeGt%W0p z>y1D5;??ZO;k}iTXIATNQ?`@8U}IaBrx{mcLP9{@(11bgxnRW{{k_pyb;l!mkMrg{ z^yus4kkgywXnO!*ZO9*Dt}f&^3GaJv%ewGIgraRrbGr| zFVs4}{`Z}B5n<3k$@CaisGbejf8Mm?Kv4$CNB!Yx)}V;__j~m_!0rC|GYCk`|McQJ z>djTx^*%NEzuO})!u)3%(*N`#(52MsbSJG>{JQri#>_;4q0@``b=~uLy4TlTRrmYN z=5FZxxM@Nj%Te?7<6`1o_hL$5mugY;YLPA$L6H{KAqlw;J}>b9=+FPF%JaWF$La!} zJ}uBQ6BL2BF)!Yw@wwPZ8xI|xP^bXA3%%~dGSh~#hL*<)TB`21pD}qjhfjDnMb`Fu zrR!=Z8+rjUbv^8I_aA)sw}k{??;(T_YHaLOjy4^y{!;^iY|ta$;|!%n!_m>TUAA!~d5j*R?^oitd4Y@*hjEv>>e+IyqEIUAkE>)6#PwKLs0!Fb#8Crv34q0obF61J$@ zMU?2d%uYM;M8T7S?)6pi&9-5zn$;RfA;pQV_~5H@iW+(UEkJ@`eBl^~G)2YS11DLxwhp5?}_jWm=3-+Oi6!r?I z=gq@R2%+CIkZ7v$!L2C3+)RJ*KozJYu6$*!E~5!+l`kolKd42em83cvGj%CYo>g)A zkjyr0$>~s(dm4i8`h(yY=AYO5reAMtfB1Ijud@O=TdI2}d|M&5DqETG^7Y?*_h(r@ z2&UeoJgvnZ8g8f$g~Lxh<=QlpRVZYOmeX;G6!X->apxYn=UlGNf-6G^o3|8Bk!b3u zBY;^;s=wtgT*^|6x#8Ugs4i`Ja<=G-LhFX8= zwA9Gb$JZux=*Pz}S!l{|NPtnP! zBf==PJ3D6WE7lp`M|O(E;)St*C_eJ)ksoZzW!4bt*{^pyUq6{S>zh~fS??8l#7>i| z@o2PRdbbIV*^&93=y*=WP=M&9<+6S*j_0CVjEO9CM;yTtE-e30h1E4QPS6$c1r92s zf$awa)wrfw8QOZ!vr2G-jmqV_U4gvp6@eA5C87`qmPX!sd&4(K%oekPj~3AArKU5! z$%XcBsVev_XM58)90&Z#Y`REcX4^?!Kf*Zf-brgNGZ%r*;G6MH`s{{a)icx~Ak6ZiEm z$i-@J-TgY^2HA{UruBY4#!gLHoCro5wK3S4IMwg=&z$R!UgKlrW#96ZO2nk@v6>R0 zY-+Hv0L)n~ie5JLIE$^sS&8Eocn80Anvc<lzBf24{t{LFOLxL+! zy$^}6MOUw3fMHrPDD8EgH9IeyEK7d>uqYyCQ1K?)a}Q4KWIprLBf!l!j5#EcrU0^p znHx=wI~@UP6m5tV!Q?bU-|XUdR5*rIo;NZ+!f9^@CxMZK*kubwo0ojgThW3eLzWu= zs|>U{tIM-77y_^TUIO@B%wZ#}za~;Agh)=b+s>z8u20=|n+}!qNdxPZK$W&--=9?g z$`7RT&n*xX0t1g6_1I}fT^{sHJbjEIUD%ze$xJd0zpeEb!{)4_l@;Bg--l=TStYlk+$5mEB3~U%h4ZC5(FY`h z6(^!05!RJ8kQ&p__%DEwN5mGBa->?Lt+PMjEJ<4mm=r^6BUnhral{Pr$ZhMgpmv+9 z6I)x6hIJJ4N zP|4VLm5#gHHf>ce0e$qV0WIVJ(IAPDMR1L^Sv1g52K1lj=iA-~+!@%=TRiQ`nCj&f zQ>u&iB6EjfqIuY{H^DumN0r%x#16L?@Du&CEzfA~Rh6!@47xsjuQ!RRR68Fp1h!R@ zZtgjME&)X8v%l8ZrP9gVZfE=w2_~W0e7_8$lkgZK=4qoP4%YI5=p|`=l@wUSRWwRP zn+?%Z^|H@f^J*%d#poLe$99 zf#KwHX_wTz1m~Lg%+bBPu&T%uq7Qs6`0Pq`wEQKcyM6DmBJoH`Yy_@nTE}p=T@5_M zDrjg!oJ&p2&nj504}QwuZa&#a%O)l5EBi}@ZpMwS+U0j{lzisSFtwB)S1oYCX2VdpU*QyqI@EZ3!D0*l|0K zx$?2=k5WS@u1wl1hxwC1ys2(9gS28jO5vW*D_Mu1WMwlh3~Aqd4n~pe9kDjcB!NEL zKq`ud4#AK>Q8hB~Oi0YSD0-aUNjAp4_=VDiql&1+u9eOp2)iEViXwsB=8}KuJY#|(^(#B^!{DGi8Q_F%G zCXEj@W8y>ZznEYZA?Eyt!9PV$*H;LZEM-%!q{LiX{Pl@p9f2B(5|Sb%CO)&R9>mJkUEL{*{xSHz|$85G>piY^MY1V>fEwktsd20 zDUjVsw5xQVXeUJ`)Fka_HjO8}{AW}aX8Nl4f@u5BL$%_Lohx(XD&GnI!b}Tig0A+l z_%h?s#2+liulN^>&o+wFziSkqH=tj18LQTTQf_WRyW{| zTn!9LlYmbxQf4xANej->5eTelK}~X0{a6(fvsI`QpZaA|*{_Vb?4USjtS8|0uu!b0 z1fZZ{wc}QxZa%lY?z&%_x8A535GMU^g8~9=a-VC){?5X6UEhSu&+}kOMzZFJr0poB(tTnqsu49z{`#mKfhM) z5qQ^c>J^a7dJV*3`oguUbHrm)Tsha^aKrWIU{4btY2+! zTj_501+UP~ zfb?6MOv^bMwI;i026qCktfZTVj{%edL;N}a#Z=?;fy95qc0qvsr?_xn0F3Z>>rJDC z0wwT&no$E8VZh+Pz~KJ)0N)A}GhxJC5%$W%t4=`q88)I`@2-GFpl*?^B+Op^83%m_3%Iow-kDJ3f z(~2zK%0wPq;N_Jsh4CP(b;-OOW7KEQD1smHuGf$O2qGOzVDuOIvM!#fT+3dc^QZ0( zJcv65kkp8+OTh#NzYpMHPVT`~eswrqFFL24TMr#1WxP3FfdPz&c?Yqv4{H zns~-{_F3^*tvLGl3>1LdJQ4ja1>D$N1%bNIZrp##z*xmVvh`~nZy0>S;c>KU8g&Qq zc}_P-&*1V?&=$u#MNsh#euojY^VHp3Tmz|7yb4 z)$>zl*~jbs+13fO#!U8WKtfgvpRU2QsQzlwi7VzE#o}o$NvmEWU&lMw*e)-3mKSN@ zy>1}H2CYmN?PxHU!|}c|JRHoQ4whIkG=+-gro`1=-*xEfM@~$(W{i5vEkLJ|D{{8W_?kWTKBpGczQ3K< zic}o-+fyh(%_$(^oj2Q9|A)dBers@Wpm@am^GI>@l$MtNd9gd;q;n2_S&86;j};2 zb9RyS8E;t%iQf_AF@Hx|oIsh+9Ee`;?Ze@UMdRMnTK?wscnAMEX@IXgCViW~(y;wB z0qTNYjU;W1!mx#AU1g?kift{zt!!-|ASzcf;v(v^l`bG=C;rzPP(WD>%yy}}O|)yn zLC5gXJmeHxB%U-V_>vt2SWyI({BnvRkjuJKSH-(%L8yga;& zlT>LY6mS5wP@vW^$i^0VrY?gzRlAj2fq>_xAesR*7!;gW%0FMA)4Q0ZcbnuX(H8>+ z<`hce&oyn>)maaTXjP6iuaNU{VtzS@LrCjQ9GV|$oZ>u9J%MBrniU)z60 z)Hb$59wv2%=t2SzQg;@UkMEeq#GhrjCXFa_y3*PQR56|=6eW?_TAu2DVxTiS`NCR0 zgfYLwPnEXPu6BGls)l3C&L&gS zu(KzT2Tzyj!icl6pDcs+V_K6HUjAuBxb&y3vQ5lZ)Nre9^!-9{ej1Q@tz}oy|1fU? z9K*{1depcgw7^~6*$;@g-{h9n=RV_rH2EIJnwTYAdgxyd!9`k0(o4`iN!W1qMi9Kl zrin=9riZr!5ICRpVvMvoRU%f&bMJPOZ*Oex+ZN19=fYlSgB#_%pF^>&g9UN?v?Wh0 zVC@dl1u+#-@Vv)eW*^H;`&mXj@F0NI**FG^IS$3bU)7J z3WfIbnX9@Zxb>mpT5pjk7|w4h^6&EWNxgHv1O(PqK~u7NmI2wktg>dRxX_dJO--Uz zg!`rzO$eN~Cy1X|qX81;q2s_oP)(MaxFX2gjZ+L#?NI8$cnaic`AAyz_(hizorP)S zC{Js(YNt!tFh#<2n=5LfE_M{!1!?Z_sw)E`xhrZ1{l1dU)yuXEtl3NqO=RvV)eOW9 z%&_+yh;%0&@qh7fsz5SuG&GX5-l?&LUfSi+YwYy>VVOILddF>lL%#rfaN5G@vO_m3HGQ*0cen+-(4wi& zgHk)AjW9jfX`Y0jQn#JA2%*{N{n&o)W^TB4?HL*fV<}g*;zNXjzz7s`=9EN_N~Lcm zp=T?v2CUg+JW{eca264-3DX3 z{ZwtWuq2ZY;##|2?SL%N9;h7+`HPBuCa6wV1La)E&Khx&)yVM^7_a7Dos1g2p}w*% z*O*vfl;KwCjK(eRExz`WdMU<55jJ;KBbmQ*oPwCpasDhjOgIpB>gpQpbtm(fs&?n( zJHZRl^uKZ;fU&2*(=i@%cjK3emVNKOz;NF3Oz;N9i(mJMSy*GlixcFhqPthZ((V>@ z+nx;GzgVdsK+13)ykM)iX>%1AA7vXLM3*=59$3{i~?0 zymouvr;Gqdw`!16OxQO>hDP@RxMs2YBT^-mN6quZtN-V(eRH!t{# z+!)en3hEf3|I!h(?8tgtX}rlb=0k)5J^*4hvrXW7&Z_6?jo}FoOwUrHP{Sut8o`!& z`Hz^^U{--fzz@SjO{ypy7P>U6t^!lsCu3TbMZmC zrH>N`z_dc%di2;hC<8wJW7Zq{F_m z%Mfog)?vk;)%b3?#lSVXNza16aY!HSVzoU;f=9`+PSON;57oRPI>ab-sCLPaD7<2RwqeQ5_C97&{qI#lHC)$l;LJ>>7~G=dbmC}ds}NLQEkJt6J3BNjUNw`< zL#4OIHaUP(pxo_?UykrC&8H(kc?asE_L*Gb1kesHgdPK~am+$j@uAwWV8%@6e!-DBox(3=cfjX?bI_ z!$V&oA=cTe(KVVV7chv)rK8~TVi<_LR%mm4h8Wxp8CIw(daxHMu&oB!b|Z z!tA!Q1hu&6jW;`~NJ!1drh{4D={*8C$& z|AopvttPTIi?Wsl?17^)7?v!`&6u@YXfE9|kD~!|;YHt(`*@bF&ean+n`(t=b6_s# z;aO=t-GhzuR#<_PlaYUS@*?W5`WCA3{W~vX*gk!6y$w0b0S9aPeAErT@qLmIx~7cw5qA@kKE5@xIjl@N@*IBAV-8=VA{WV&I|! zgSmv@2Bm=ScF@^q-MgbvJSi5Fxzz0uJ#G9(_jnC178aaN8NbRtZml?v5YIK~NfoU; z&qipdyo;u`7Vw*X)@dZ;k{PpoxuLz@_CHU0;?acqj&rj)6B2`Pdhngw<(sYHrVUqu z%V_9b&bnVjTsIM$siFo;Dcq%M`BeTcf8|!}%@(tNjWUtAg_8Ewc+>M*PnF|BG`Y&+Sd8rI1_Srwm#7y{jU ze#Ah1IHh_kH~D-|+Gzd$m%@^X$t&A~ANGrJaYAp~*+eiqF7_p~1#jgXP#^|g#HtIj zbWXqiQEH95g8sH_@ z{x2BLXZ=6ij7464Zn2A*@Q=bPu&xOdfJ--j%e_2oHWQdrzRM+u(~7pd3> zWP`o`;%2}94L9ff;bs%7Zzsjwq?_&E@9E!hPg{-t6P`nTujWlhmq@vo(5k8`eHtN- zQGiX$TiB;78s1s*+NCKczs#Vo#r1ZRcj!yDjpg=Zj1;B|#suQtmcXL$V zuQs3;ia@oyE%^r*VW$puB3{JL|E{vB#4)2C`CR@QM;t5r~_g=c={LZiwI@ zoKf`MvEgx=%i&hjOcTg2US_{Y!(#gT?N`ZmM}Fs;?%iyw>L9>{r3-X{C357D=0Z_af1`YqL;r zptO*kog~>HkDW?=It+j1-j0}j!(B@N&2t=sFCL)?6e4*-WN5k8GwWTA!8#T_7~s|S zxSboPbr_4p>UwSJNY`v;=5e2RTW$*_TwisY-5A|eHHgGy3}q^64UA zVfka;1~p=T^{Wa?adaZ6WWnpmb^@+IYORrbu7$G>zCU`Gcofx2lFPl9rKk5m2I|@T zo4v+yjk9~4n_^ye2`%SK_KSe0dl09tueIXh^l>bKrmW!uq7{}45X(Kd%W43Q%e}^O z1)Enosa?6?or4BR+?m@9!kB0sieJ0OjdSLK>hb~%Bo_L2Tr1dl4sT1o(m4+urN`H5 z_%Exc4pe3sotN7g*QT#j6Y%q5nkN<>_3gdXy2T@pmed1(>CB#Mds>~sJ?>*xpI%J? zNbH4;bCm0f;2^+9Vwjcr^o#s4rHY5ZQfVCRNF|W5o3EcSYp>HzoPeJ9@F4Ows>-Y! z?40L@WA$rN<0-9l8B6fc>yqCsYZKS!dBn+5hCc|7*>W0J3&qN^?=CJVh46pZ`VOO)UipH#nxDkf)ETFiqBNwsa_fIL`KX&0Rchks}{^S6EEEkW% zn;CU;=4|0IpaZ(5ree{azX9)JjG!*LI9yZe4)&JW>l5Mh?0vRzowPEq1Cek)Y^vURL^m_Jy(~R!kbA~y<6mB7H@exyY#m)++-vnsr*MAc znirr>`&(OZm)m{0j0Wt2nW^Brjzuz^>Qp$O9yG=~=(y1}qc#L=+%r!Bo3gqu&Du8I zevni&v~9TO>p`AbQmiJaw)#BQ*Br(HdW{yKG5{xdwbblp^D5iA zOaxS&mgYf{nB!W&)>Fm3#ARIt(~ct0p~CMKuZ3;dL*1=Qna@FF85F!u{;Chu9=GN; z13wx;*GWlZH}nrv5L&yd_e^^!s(&UMG>vvK)Y@Lr{(e)} z>)04Qtva@OA#b2)bwND=-|G=UmCMSR(5j%mx8(by-ah${X?vgz=-%b!Em*_JvSZ^+ zj@Frgpj&qUs+1@?*NUUgKS`uUKLj~iPb(XFR@;6UW}J3PF=tH zY}o*Z4YD2HK9RpV;gwJOp zrr<;c4P{eXpBCuwAu1VkiEIxBVI0i}$_J{f1i_BU&!(rIDlOBN0|>ggsS})IS#-^Y zm#yCL7Dz%g!$smj??KnJ*(I|K=c-2`BsM7tU3Ww?i@18LK?`4=pWV&WF=jmx^>{eZ zfKUBK%g<*dR-iw_t!CLc%gdgpwA|)nb=lmcc@B9`)|?(!2}3yZ-BK-(j~e^&lXwF+ zOC>meT=7{bFt(+l4or)QKW4h5Oc=}~&*or9Zx&E9MSm`DnDu4k_MXVX`l z#A`E~_{Ern{f~`11#*jgXXnhtkvMb(v$JB?G9#Qxmg=Mg>Dm+9i#4l&X8?w;RL-iC z$BD*o)L&{r-0-k9@(estbZ&3%A&z&?a<;rx>Z>my*-}?af!oj6r-24P>CI8xJef;; zGiQu1&> z^UdktXOZwkW8@QjwO4b=%|~CErU&4(4>!Fu=zOG0Hv|;PC+0Th#(fsa%KtQVvxv!C z)>j>~&T?9jXXt#VbzBVUIPM1B$GZ$~#p71?MG5yf_1Le0L4_dB!OBi!^s^y}do; z%j58KoHup=Gw}BJ0jRQeq0EUa6&R)`c(bGw#b6E7m-sfBChqsJKPwp7dW*XY<+W6d z_k%}gVAYATX0GIKVpWdeZi6J%r;^V?>5-hSzk-yc$?|>2F&dyUuiiw~dfU`KhaHz6YeuwjcTmKZ`A z6W|R&VKQomV~pj4dLIj!9OmMk0F9N`*DZFMTJLC&z8rY-@>!B}!dgOZ(J^7vfnJ?}&28t`#~02xX8+*QG@>?0%)48{o~5iRsBYf_FM zAH8vtwB|t8KXJI!B&R%+F%nPBfqhj1_nVKhMw#2@Lq(W6agF@G=!V9(@Xx6cO9pcn zdSxYF-hh5+Xxm<%SZE==Wt4dN$P-~Ju^SvhZ9UozvB5e=f6Zc^M^Kp@_o6NxjP`o{ zh^^`Sj!qrWByV(B>65%CXG9Ta9PS~hXydDho0v6j?G~W!%_tIaRa+4djB9ibV?I@Y zb~bU*hlaBKW>SsI!UNX=__Dv zl$i@{3~FZnndRJ(Fq9>~%&N3Qh;kO~oRgE!vrB|tE~9%KJW6D50m)tFr`fQ!PyVPS z_f<1F@p4JChVuoqlb-lx)MxM?Zx)un-LNqF+AjOo%3-ONr{K+qhlAlq4J%>3N~S3V zM^73z6$0y@_z8T>IVHmY{XVV@dYKmziX`x7p6#mz*t)OziJ&jW>cG7o*}4??-#T_3 zfv6_aljo7|exLK5Jri=15g4(ooJa=47W@$W(E{=R6!(@valGxiFB04WB*7s8f(0kI zLxQ^ocMBTahCvb}NP@e&6Wj+va2ecTAh_FLgY8N3&-<>mcAd4?J|E8MDyW*8?yjDv zr+fN-uIqQ*l-TYp)$Y?rS7c(lwx>~box#+m>`tV!D7^NwK{#&iw{a2|JVDrp`IX)9 zCcBp_VxO*GV)}>(?%I8{cJ+dbfqcC`(@U!^r+Q^~B}EC7oqaAh9g}WS4N@bcPjBHK z*lXuBj6L;CLF0gLfa8Vkg9Z_=%j$ckMOk17z_TJre zM0g0^pP%WbQhS3q`5Ph`w*%7x7l(FV=Tp9YcBYmq$8Wfwcz*h$L3YV~1~P1C31@2F z>STsPgOUt>i87Djy3F5Y{6EX(h$w&GfNSUf;}@euSK2Rsi{gK!cjHO;l)=At_1{{@ z;j0CIgp1(Ya0_^zQKjObxFx)5lBe+5laa?oK#)qFppPW?yv#R)4tyjxFR%*#uUdF+ z+yAD8k2M;F8+xr0Q#1miGeC>cW($$B-UsLrC zVtXq!j`zhYSQEIv!{n*nUH5?pE#cR-R1T^ zZ{tV1AhaiyYCLY&1McXq4sF$?cz^d;q~(jz(Yf2s7kN4nzgU^y9+QP66(VofXReUU z#D+RIk}(!}gD=sP^=weY?H9{Qb05p+SssS1L~rIE4DKr|e0|gIFB~5d?pLYq#s6Rx z@C%3}@(l*gbMAG9S6O?m}!;cC>es%NkL3Bwoxc9+# z6};o|;F*5k4@$~sdl)sE05(t1s`Q}tf%0fFRi#FOS2VCl(R&xzGTuQU@EW2#YIT2A zxdUEj<`Gk(YOIWieNZsc-nu=6021!ciA0ni){pM>FE1xTKw_~}M)^jnmkviu%A#k< zny@9%bGHMlonlav+{3QugLc#SSLcUHO$ry`7BP2?v-V*L>2 zcM>CDwWF??7_x(=MUN2Z8T09~xLEtnSB% zwtoW2f8CH&FHBm+K7VfS=RPyab%DYQ{5lp_Q5G1rJB_RAY{%J`a%i?OS==a+#h~0a+(fuo>w${zetgpoYitxa6~_2k)_wjc86y) zt1Q|+IT;lwf}r;}K@kiLu)NAtV8|RwV!=nAKFNi8&D?`&(D>IDpq3=)oP=99>5)zne?5p*n|H`zKM@lz z!8f+4gKbq{_939+EN`vCDWTxcJc{inrA#5I5E&9yy7!J1z@?MsV(#e)VeVG8f_VUKNoVcXiEo+g-pZAp8o+0oJTQBx^a?by7eON?d4 zWV?#fNSCYrsY+`4gx3T{Rs? z85%PfBjJ_28`iETq$2gcodVMc07%Kr(}7_&A4R<=Hd%oQ+H@!I+7_a)1>C;Q$)~*? zW1AL^KRQILX`kO-^TDiBK5Q~L3^s#`rBs9etyM)K%+LN9&x($ zP4J*hb`bL$`I*x#LE%ztIg3#PCw0vYp(yevNWz3HKVQ^-@X7b0D8BIHX#`Oo*-e%J zBRNuL;p4}%w%FH}reN809P#lL@+6k+=8fcEGn_tdQ^`_&r9pS&`(=D;^L^02tQ1! z$PGEb0wBP;CzTG_aBkecz;EF~y0t%F;}S^ELiY|(elou%G^weHJQ_tAp;woAmEmrJZTU&~TbspgJI zXiQ+!npQoF&_Qa>76+ZXuNe*Yex-_5xG|Yy>}o~cKM``c$hv!H=bVF(=NsoicIJ{1 z^gZ$4W(;b7vEAQ%leSZ$l~eLA6qy?yiQZNZcuDdlc1snEQKgoImoGCIVD^ z%)6%rWcfqau#=;I&gck~kw=f=Fk42g`$SbJ7Z0KfP6rKet^Ir2E@_XiyoyOHFiIc6kbq~8l<;dM+)+x4?@aq?mzVXoj) zk1aW!eNEzw4^R`BD0+%3`0lWWSL$k9SGwH{-b>&Dw|OV60ow6&mUTM1Qf-uHWp#N* z?bO%T!1gvQ2b&jtT(u`qg?(d7!(&ugL740>hHa=?YcRmEV?{tbUV{#|g6Qs@e>=g=|XK9yB~LfHrZe!3fLULsO@eeY7s8=Decw z9pDJ|YKQ4iv@_F@& zhB0%RAb5^Gc3CaK=G=?JO_Ahzp=GEB$Z*jSTyE&V<;ozbz~<&OEpJ;U-ZscF>AFNb z_ZX{o2lj>Ev~HH8sHfrR6E!J8QmzhOV6(` zgS6N)Xycg=6&B)t7&H2Nd#PGC1s|=Dy;%N&SoBmek}LPA%Bl|kW7XGMipSYxTj;J` zaC3J+)pu z$|bm51bud84U?VuZ$sP#f=P3G-58{aIgliC%Pi^ZZzy19+lpNtzBzXjXZ(bRQ}}=7sVPxY{{Mnzh>}RKF!@0ZLbvaZMnUv#Fu^HQyV%S`bgOh+G4wf{+hb^ zT8;fC{!Ra;yk<>mnp*LMi$N~!%`buahlb-KPuiPA0nd+!;$;4zBwu^j+2BFHFJ>7g z1o$%uqbJ+lHlq0O3EnZYfQVySA#xLNfLYOceCxf;jxwd!V-7rc+EoU;sJ(t+axLXA zARS|)pZHmE`vxwKVtL^+nk5^Ua|EE^GDNC5%}swu%Kka}$zfyfS^D`K_#NX}&lR5q zVdXEu`8c%SN@F%mpvEyhfx6l3p72@Ox6EPFIRA~z!1rJcsjXaGuh;StCNqnlK-8<* zaR`~}F_!QBQmkV#+;Y2jn=3z!t=Sz2k$vvn?wFTzHDhd>kzC7cL5kEd$WAROS#le) zefFR>U3*f88S3S4E}!SmHCyF#{1plu2R>U zvTh4*cY~nhJvn}@0srtr2ex3ZrVlz(?pg(WH@ZcqCzghX%THww7HStGnrB+1HRrtO z$_cn6>x;867z*Jo>~#F}mr8relrZlYz+)Wd;L9zNGuCRldh(K}xo8dVxf{(-5v;p0 zt1zMb3bH}JpC?)cgR#GyYH!8j4c1j>Yd{`o6uxcJs!aJkKA2mS{=UX|n@H$wFS!GpOhOP9ZRG+59}bRnG&~r3Wfv zk{s#3ZHzX#YLX(s>mla)yxwsTuGF038=~GTDXoa*WFfZA+5J@4GV3^^B(-_dw9<@G z|4R^oTymeFmJk@onHw*ClA=X2pqIaXtH>aJbi9z?O0{mEv%qSKU>l@DLYYtnnFct( z+(xIBKuj%1Dki;ZQ=Vqk9Q5XEdg!7a7+^(fL8F2ny?O>WHoF7m$CX{276I-)oUh0q zf}^b;$`vFQY^u)&Hk{pnm zyr(GSRaJP7bH&f=V4MX?3p2%{(!pbw0u4h4?^{x}c_J7B6ceP}jBQ8>mxwz$k641* zss1V7{5yZ~!+)3mgSYvAw%V7_FQWIJS)#>!yO)16tl8iFhpJS}?7w|sT=QER281mG zRvA-bC8uh)FGD_=teI^q$(BqeAvf-A;Yi~?=~J5wB)eDZ5FrvavBzXcn2X1 zrOemPSN&fP1iEP%=C#V&Ym03L|y6~S2)8u=&OZ2!Emz(@z`0kU+!=p*9`5V8nVy_8ocLb%{4V-^T!LO1oT!WBa zc`XP4+{3^NP0uZM41n`YAJ41)z{aiL)>w>bV7CPh1?vVdVz*U<6%K*8r@c0WA#oQt zgOqGsvoX-J12vesXVmv-;R+V^eArSAULG@brPk63=cGm?M25oV=UR*N&A_eR6^9T%iK+_TDz)Mgvr z-{{Mz_FyA1dl8e;*)%KGo){Y}2OZjsEw(=!)7=_FKcerki4drDH(K^kga;It0x73l zB3{d_h9iJ+a5wfL+oQAX{Xg@(p}_&*jXxf=?5~r<#bpG=m=|M_}IuKkJg6R3`G%*rbUt24|>e zxM;&Ys&yfow8KJm03)HI`F%j4(635rs$KmieD0Ys790td-&5v#-Cd)soz9Wnw~0U!DqRnF@Pf;PE}j~4 z!IQCKE@{w1CoSp|Uf(G2o9Hjr10C8oCnt0Ljtsyo4Mv*-b$cesaV8SAN*={enULRG?eifPw2wT!43_#p5en{w;P)U!y@&ZCL>OK2HP z>B>%yb^A#-v$46k7=yfLaLx1F(slA}MjQJ?F*V?4v-G%Bs&7KOqop%i*1HT1g5a-4 z*)26RPu?kzgItskRnMR8s%E}UR#X1%L0qx@wzU4mCifxtv@;f^$Nf=oMx@a~!XVa$ zcm=`j30W3>k_M>hX_UcskgC*RrS9CMeI{3|Qf;fyXaxPECzKqzd@|4-{-}2)GbkDH z`(-Cj;#yU|21;56Jp_JG@sv3Qb8zU?YYIcf&-UT+VYxCq*F&~s7M z8W|iVb9+ky{h}*8bX~2d(Vev(Lo==^8?@)j+-z=|k%hAlA_DLyWBsBCUZU`bs}k?E zyQOW%9D;CEmMa})h<}$`!tGpk<|)Xi^qHIix$NhP>1VC>H+uH6@nrE&bO;&V>5>(4 zRhT?MeP+Sai&GYiny5M{S0TKnFWr_x`mV=BC%an#-u7Nd!F9L~Pf_hg?(rKW$%q83 zo5V=ws=k_Xzn>T1OcC<6Mgk~fX5BGp&*?uUmuod|#_iTfN0{y8cXMhb7Q;(0A+><+ z$&*s9c#WI1z7Y(Hoj1v9@+rTl3Y8z##J?hYn50;#6#P5Qog1qW2I-u=dHodcJTwG{ z*8yQh0WO)Z{U~Y;DC@bA0qo<%*H5W+dJ}$#5an=bPhGY$CnNg%f6sj*E!)K%c=Fnv z5W7+G3_l<*KBhq=9RDRl0EU#u3fuy{=$;mLW1GPdJCUsoG4Fgr6!?tnm?7^cgT?!C zFW=6B!Iijv%wbd4dj8FM3%B*G_`2LHN$3_!3MrdWp)m0Jy+xZf(>$*>I-|W+Zrn(U zkW^xgcQC4J7j8!5S02tkSa1`=K8TyxOnhDjEo#2DZ{Xf<>>ZznVZ^{iM1K~eypg4( z=4JBkH+~IkNxeuDUH1rTO{#Jdfi)&&-lFf7m)fpQFYuW9W^E3#xG}Cy!jY5JrAuqd zCG>SHC_-zP@fdk8V&`YLu*;FN2i*LY63;oB;Bav42KlcHtwb?7ASYbx^+pm0Y&{1% zxE!~bmz9d}>IQT}Pkau2@0&2LT%0;FnDPeVbpMoc)bEd;24btR_UfP_3X_BAjPoik z#p>D)l!b~XGH?Rtae68xE>n{z(BR|FEUaR`&HTx9$hl}yROZO#*5IWJ?TYcmPo^W6 zJ0!$U(tS3DAkD{*DozK+Bytt5bh?YxpC8Cu)?nd&%dEE&uscd0|Ah!+{J{mu)h{ej zfqneAa#i0tz(ywQ#1;mJPe}72@Sc55ZuE!=5^}|l(5dBw=i67ACFK_SJ-%m!H~C$= z__M;pv`C(N2g{Pw_k3e>+~X{ZZql=STRoTC@s54Kc-n3~FD`gwef1^(C%>`?>VExq zi!;`UV@g9c}W){}O()QVr!x?!@5*{7n z6U?y>udsrJm)aIiY!d>id~0tWyRxWD{GP}8)SQ4@^5`N1qD*|o%%g|Jc!=-K<7dCX zGOEMd$97@czS-7!H6;TIoBc3~mW!j@WB86InR)sG_+hI##>ER&T{n}LrzA6_8{euFCeuV6GR? z7$o7w=GUL?NQ(@6Ix%VM8^rZ>T9O1XkPt1Nj6Y_1c~FYXa?LEMw(vGC)E-QS?>-t) z4B+fCzX{08yf))P%-`=lFOyj=XkI$F8m)<-OYO}ti;INiF1hz0k-ehZ&id_}jvo)8 z@A4D*7}uX2CqkK~PW~lAuq9FEw_jSy&vUK00>CX-f8a`vE`>(uLjRqQ_Ryv1!Y_D3 zQiV;{85bG_=VZkcftqyXatkJtCe3JV!mUr+n>3+!Wo&1P{8;# z(H>1ipmDMd1;N@~(0Mmt>0F=qSYqZa6~$-DPYW;>9uAc~}Ksg1o7 z8X6nK6?9tQ4q-AxWoHe!`35Y7m15l@|6%+k{a4(h>jVz1T=qO4s{^^MQS}3k0d8!SOB5wM0+qC8g@qiGR=p7YL{PK zq&5<;tUBY4Q?##~<0Ds*dKoEH)hGJ2A9TvTT~~cqaD7!bqUEH-%bEZ2PX+pIY8Tq} zL18FwNaj(Zj~rfaCBb+`+IgT{@!E{|eDf6vw6k7PnOXHKNqNz}%S-5Yt9_(jKW*0? z+7gHPIxi!rKReV(mgNrQDDrIVyoo2Ibn}fIWn$81l?N2~FE17_mD8mfSRilzkwGgv z#igUvDWFV4Lt?5|6=&HJ%_>R;9od*>+f8oI>W|(?FD-r`h@Dr8MbqIEz8@0Nlsdo6 zLg}gRVD0)~`J_5I%km5^0#FWwBjg^<*z~^!@;Bd^5PCxGZkcq* zE4;SO+!DSTWW@6L3IAZNoPxl^oD|!=T4E9Ni;i6I*U6V%$|v1-3I9@`|5PWrA$U{+ z(f6EvH#o4l*Y&F#EQLa6%AM(RM040GSaJHYTnTS0i{>U}be6mWWsOyz`45?j~W4)4|-XMu+P$j9Q2#SSv-$#@aqOP*AJ zbOjhE5zN2y?L*&t?pskJaEdWs0d0lVpY^13-kLhY!!fWp>AylEfE27}aOd{Mn$bFk zg)PIDn4`>ZUbr=r{rrw`yl3ui-^vf@exs;Qgh%GK#(OJdaFGxduaH4E5J&KJlktJn~pbX^(`wThNR&dhz~}bnzYo!2bOaAgg4!?Pe_2@-#_qJ=HFCq$JhT{ z({es}WX@p6^1Zf$&tfO+ZxIWFL%sjMQNnikN}0C=Z8zW5U(+8Uq9+SFM=S$m&k4`H z?R9ZE36;n6c4zgZb)69q*axfu*%qA~-WC$LoD(%~uYVmsH2N=Y-uev!7f82PyF#4v zK-i~#xwm#hSw}Nz|4|SftKK#timmyuMlB3%qRAvQ@VsMUq2QOYa%Yv%8JbP5+KNQi0k}Qe zj^q{1^KyCj!mK+iow<8m3Ovu+3i<31@`a>Q z{WOYk4{cT0UUa7Sa%pVMRPMaRimLB2A6)5xEUasX7b0fqx#b`cOl0BO&!kNmbN zZDIMfjgh>j!L!Pl;VYJ1N=eW1J}(nc`1VZ^C3034*T9X#EEsLB@VEHw`NLY$8gGeN zwK*4TE$~S)lHpeWxyOQ(slJNCI&n7WkP!u-koGZ2wZA9EjU3t}kX7YSA#57Jpv>qU zoK!rB+;GOjz7^wGby;@sCR)&9RBEr$wu;Bvs3C2k@0!d#BKIfhhi<>|u=6w@c0sQ#QX4zAXA5c6CMMi*(^iR=Ve1Lxbd7O z0E=#OT@Ln3RpwmtP^Rvy98T=SWl+g(sP$TBGk?X-XsKiS8p-Y%)lxQ&CjGCNY0CPg zo!9~P^*Po;Kd^y8R^&8gO6=>iyo=$CevW6_zi8l+rAwk3wT@>JbxSq&Bh>GlEI(>l ztJYCOmVUFwrTyTYz-6hmt!D7>w6J)YT?0@y>>U&rZIhN{Po`ijdDF;IRPzB;6tOoG z*_yOaCrPDymzN4l&Li9}V`bwT$%vV1n5xrVyB>N$b}sF~P&k^Xs>-xM&Oq?{TX5lY zI}UeIYqV4@C0^ohMh#i(9l|uN_DfPfG|{=scF;(u;v#&#yw=ZPfCE9hdKHT{d0a3w zB+M*ORbV_fjX>S0WKp=8y_-ymBF{8vBWLDiHBvmyGLY zQ3=omIfx?YeQpeejzFJThdHhOtlQ8~bEE2Rb}t7hBS+uG56|{8@v)kEb%=?i>rRRu zNKNFtlH+!KURy8*-c@OZ^t&AD4s>y-i2Ia|94yqL^;JD1f{U#znd*TH-)5TlEWektC(PGGRV{V8^Oqm4*Gd^$jcgse>A70hGg*(ajNB9ns3a6K7A zSa>sYv7D&gM9DFikbpF>tim8XS`*S&qqmp!zl}$6}pc5 zaN7E0($&>H{QU$W`L>8@nm_x~)CC_KcyMzl`Dmq6jbXCa)~{+dFrYhFro(BiC!WBn z{cTI{l&GdaJwfd1n}N-hde66lCv-HlHR?aEjHoRL8|{gbYl{+G?BJvFlcc6L+~U!t zaNCaqfLkRq!~1>ew%dVdWIM9R7^P8-TF3H{?bR!8aYjl|Js`Z3EH=Ghe?)ztSk#m* z@O>hu0(e)Jn~fa@fDqVE5I$VbNxK^XuYHlW@p2HpbDMqWo#Wd|`%WP#U7%yqh#E^O zUZ%li^6I;X98TFH-`?mF_nkDp?NaBDxT7q@^$P+axk}!0fe% zXbHl5_l+}a{7K@G#PJ3C0n7-1jYThv$8}aiQnmxpcDoVQlYFA3$a({-e0@;`3m#8% zW(utN6H(+q%ypLA3e2fWg4DT0@9ctyRN%CuolPon)7Sbu=8@ZtSC&5|>$+b|uXd_2 z5c+GZod?A5)*mbzX5l#glvJhPf+jh_+cjNsaB>DBlTMqAj6Btu31UtzSA2Uzw;N#? z331svNLnXSe)qxY1vV2yOEj|R=i;D}#a&2Ig{81Vy6Buy=PN=eVk4*^v6I|0MzH*O zyXG`FbvHsJ5PmYSx}huuKR!%LR1a|^kVsRKYhArZw4<&zc`~gm%qr#L6Z{6(YdFKU z&*l?N0G_R}Q=Y)91|`)t;zykN^bEn1;wp|~&yi<^v}{W6bFK0qHJ_KE5IWQ}ZYTG_ z2-jz6pGmM-!?ET)St_ozaf^p4m=7BOCFVL{_7~zYzNkzu`U=u+zYi&c=SP-}mU=Pd zwm%$1wB%h(0bgC$4`T3byYsFsqpGpjDHN(}#!3!c6DYtM zycIr#3Y-9~2$3l;Zu&rPC58&g7Kb$aS-QGtvb!2PHDmF4SxaDK)dGrZdOF&LozwPs8uY zrk-4mUnowJw2^^cR41e#43!-!K3+gYx4U3rQ)a6DA{cgjfuwj$Kv$?RiY9i6pR7ON z8@jeuTLO!VM~-xr%7iPYP?U20^jfpw+A!RZWb~dBR$2RmT0fe!r9`TeIz(*Xk}F5b z!mIt~tZwM>rX0mHUhhi)PpQ`gzQ3z$XMN`M^*r?{F3~LcAHOk|tWCK=CP_}*U1LV{ z$W0s1v+pPPUA9I%S`j9BzY5&*@6L}qDFw_?2U599aH~&Z5YJv=MkiCf1r$Av3*+ETC{f>QQR`N*GE(1wjodP z=pfY12FpBZuba83&rC(IU}Uo_g#ci;{w^i$j1JShy1S2sOZ=xuP&&tqJj;0oDa~Vj zcSd{2oAj}o6TP=0Bn62^$Ea;3iVCAiy!bqG`0Cy^h8We%G#&`qc>XKQjuVxa({b<6 z?UHHJ$AEA>2U+4{HjOWfdjgxlbAm9#t`Y1D6In5~fqiuX6%*4vHg zMIUI-?(<+S7vZ>qlU62>dDhtv26dC2#`E9g^~Cl=J=r-&7m_6z8Uu#j@E6)+k(_zw z(4w>c$moQJYGtT)QRurDTSI;{?=M|NW)Bon_?BwZqN{MKs@Ru)LVUE1{J0A>CI~^V ze1sSqEk;EZvdy}V8IQv_o z-#{IY1(3C}2J~Jgbwy$?+3N5`4r3Fa@mm~`G|?3XqDssUImsmtd~AKTLJrUh*4qvZ z&F%mhs`45ivewK#gtCL=?%ou(Y(29dwpJ%-<)o0pCqeH53$hAA#f?iX5qGdMG-g*!aczJ6LA$wbP8y-O;-_#mf zt4yaXKR+Ga5bQ}7a#?2kB`|FQ5DI+yxzc`qAqog!?LP}XeWN~o6#Vt$1rFn6{%Gn+ zDvx_Er+{a$sh8S10=$C+%~VwJI7tu+A`0Ta-M-H$n7#hizzF{bDfaJr|J`b&cXqUY z*F!)E!v25dG5*&M{x5vV{}0>zZbn^vo8D&v>jB;}2`|Moq~O3-ctHxF3j?oGzT0!h z32jkPx+82dAdCfeT4@yR@p%H0ch-A3K(@MeG}>Qz7u9OgE&&mGczX(lCAGVp+Qwya z&s#_s;88m`DX5azrB|@bG3zaHb5W%rZ1!WX z*vex5^Twg50UWvhyTG4_|5Jq7zfa8njVJr>-u}Pa=AU5|HpZRcy4|M#$x ztsc*S6pij~lUmh%Zz%+>>mbp*xg*-!saqf!AHrQ5AJe_wQx3q^`iyl86mIia}t!ASvy2do$>HMv;4*`iN|H$-XFjBJ6@qSW&4@;_SqSe#dre zJyK+gew(e!xzFRMWsB}bK>X1RigdlM*o(SF3}}fx3ZZ8+@1n4c8e1YCEX#q+E zf;kq`961|i34?rN=rk6Vyw0|4&V8I`v)y(tcjyb(6}!Fa9^Lt@DTCe`-JctqYegc4 zRQjF?0XPJ4;7_2x7k}lomw5YSBwI(c=|YTb8)6UNc|p`x(2&YovyD05+mKXe>sfI* zMnSa4)nLd?(gmfcGUOg+at&2I5rlFZ(DAwHAyFt?;cr|$U^QQqZk`j#-)z$ZuNxTT zn^m2%R+5eG8DTe~b6;uRy0V*WWsYX{yWQ_AhhDTE`@BBMb(WyEv?BoYUMxXw{kPAI z&Tm-J+FLn}DPWD~uL0b|$O1{nO?caS&gG7G#op^iFdo2~)yAVg2j5X&Xd<@CK72s+ zO5g%=#|?8fN~9z&SjvnQZP|eGp4;v99--iR8O(tJk?m(3W3%`CnwSXK#AB8{WyY}{vtzL}>_i9cJ$o+xnQr0(crb;H}BBU*A67Dy3!#zkJ0gstk1c~q^ zTCwO~pF;}p1k9k&lY3O$e?WsSNsm-cKL?LMmtn?}t({x18}HhhRDrJSGeiUHTJp{+ zC}6{>u7`0@<_JYdy=?F8MfB{=+ro;G30~#YiCNX#0+-v{Ysbx~%$~`!2GD(cS-Y2% zE?DdA+GG>Gu%%_jHbo~!-+^u)`F=~l?Bka`sLw{(eT>T?@HQCI``wvnHyfi~KXpG= z5Ewz3D4N`xd4+%e0Llg>KJV%hz4B_lxJcbp#DEEkUWNLtusU9i(bVqM>_Un&Ppc`I zuIHAZA8&V@H+(Lq9x}6*>cGjFE?3f1@d7~EGl!J}tF3K%SD)05phKgQ$ad2kk_PQ* z-oSdsT7y~TKc^3&OQy^J`%_rmkzZNbUg1t@yb_{dr%`@p<2j3LsaECXA+J*MNZQY$ zv-x%1nAPLE7K4K`_VXr=4de2$nARf!ET4m)`rcQ zS>^+EByc8;tNwv#n@Hp`#JdUg;I64G1C#N@_Xu1pU{9bS!`iTe?EMAh#;eMA-J?B$ ziq{spAq-mNzS}RlU+cc*0vxqLyVQ_4E6CSa}rxg?AgI{6Xfs2 zc!}2Dcel?g3ZIBl9%W0@+Bzn{oR=51O4QkY`ON3e;?3@qT_*lN-*T-yui)=Zm5d?& z>8Kl}Oj{~~mjROAS9_w;K7v%EjxDL3=`C661;C#mc@MK8m z1poNbBO27;R6w@SBFL9bzXxA7tO1dlcd1Q!TciV2yTVGp<9>~f#?flQsBL{eZCNcf z>GF`Oc#(Z{hY7eU1&Mq3b==V1`qgKC4*@#qD9Nm%11t+~i&tI}fPGgH?{r+vdP&;_ zm4U_Wwc7m0UQ3h(j%ln8oV(eHyvLi7M0rM6gW;}^cdcf&^~P{mdAEbgGK*Ult7n{M z1uI16vbdiS|M{f;(K#(I@m9b zMwBE1qQN{?GIFgqcCh?Jvb?+>ypopzUx=`9H;-`Q$HDTCAAS08m+z;4hs*_M+|3Pmo_Tn`@lF4de8l_$HzV;slO)}vaC zp2H5&Hb^jIggZKmgswb39r{6TyWQ?JN5qc(5WgSj`H8kXw8``-B89p0V*x0Fqgbjlp05n9-bH^D6X`^n!U_Oe$u`?xXZdVW)p4!iwg4@f1h+Yo1X`f zX9GX@^J2IrywPBJ*cO$x{<#HA@$=mAt(Oj5W(!DUR7lSgR^F4867t9V}6&i$bJu*csaO_jg^HYFe_;IQokyKxXLtP*b3A~2q%b1bRz4?j*}0(+p!Nb223A5 zirlQ3l@L6XU5}%E>4HXPF6}W}Rr!8uw+X_C_D`Pc`w(=03TiY#y}@GZE{oOJ&mg&) z`scAiDkvBcWh3+n&v!1R@Rs0*fy#$$XHCk30xuNTU2gDK)Kvac1^X^VjEApQx@~p4 zC5C)%t*@-)tLb4?Ujn{ddv$yR1M8%1wx%}PPmS)?n(|8U8r@#)onzgoZbu>yUd%99 zCLTB653_j9?pF%6%Hk!2`dt+Qqv_HV3H2WK3wJ_xc!aOSaPy8kz0L@uMIVl8j3PX5 z;dhmT>wVOv>)fAgZ_MRI-`|wkeU|Msq=U_YtS=fE2HU?4K2h2N+#b&W12P<%eCS*U zH+f|K8FM>|fAWEB%!|iyTXjrL7?A~E?>9C&%{MDnegUvPspwVudTZK2m#FY0P1P{P zM7@1u?hvF6vI>;#O62_*_k!mA-n3NW4bN8Y{dm$EPa5L+;?Ms_-wn!=vvaW_;f?o z|5Er$mJMf%=k8NWHuX`>F%lXHHQ(S%D8c841IQR9Jh)lWDIL48oyPuIeL3V=@PKiI z$8eSX9m2;MaO(%N`CW|Sv*iokzjv%0um3>PEo8LsXa9Kz{@;J>f6nRsYoq_^GxW#p a2Q<>L8lNVK5Sd>a%f3~5Qz2m-_`d)@pCaG@ diff --git a/_static/img/inductor_prof.png b/_static/img/inductor_prof.png deleted file mode 100644 index 0d3683bece181e3d8fa6102a57f1e42e5a3a8120..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 106315 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 Date: Fri, 16 Jun 2023 13:03:57 +0800 Subject: [PATCH 32/43] fix format --- intermediate_source/inductor_debug_cpu.py | 1 + 1 file changed, 1 insertion(+) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index 6dc1b5c7b1..8d118687fc 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -462,6 +462,7 @@ def trace_handler(p): # ------------------------- ------------ ------------ ------------ ------------ # Self CPU time total: 810.920ms +###################################################################### # # Similarly, we also get the table for the compiled model with Inductor (omitting some columns): # From 4ee02e6d7f24b786f9716dc9930a49d13c86e4b5 Mon Sep 17 00:00:00 2001 From: zhuhaozhe Date: Fri, 16 Jun 2023 13:06:04 +0800 Subject: [PATCH 33/43] fix format (#12) --- intermediate_source/inductor_debug_cpu.py | 1 + 1 file changed, 1 insertion(+) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index 6dc1b5c7b1..8d118687fc 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -462,6 +462,7 @@ def trace_handler(p): # ------------------------- ------------ ------------ ------------ ------------ # Self CPU time total: 810.920ms +###################################################################### # # Similarly, we also get the table for the compiled model with Inductor (omitting some columns): # From 6a6bac3888b7850aa84e829117057edea9b8a168 Mon Sep 17 00:00:00 2001 From: "Liao, Xuan" Date: Tue, 20 Jun 2023 22:15:53 +0000 Subject: [PATCH 34/43] fix author name --- intermediate_source/inductor_debug_cpu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index 8d118687fc..05196bbb93 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -4,7 +4,7 @@ Inductor CPU backend debugging and profiling ============================================ -**Authors**: `Liao Xuan `_, `Zhu Haozhe `_, `Gong Jiong `_, `Wang Weihan `_ +**Authors**: `Xuan Liao `_, `Haozhe Zhu `_, `Jiong Gong `_, `Weihan Wang `_ """ ######################################################################### From de49bc910257f731deb9de37ec7a2a8234dafbec Mon Sep 17 00:00:00 2001 From: Nikita Shulga Date: Thu, 29 Jun 2023 08:30:44 -0700 Subject: [PATCH 35/43] Update intermediate_source/inductor_debug_cpu.py --- intermediate_source/inductor_debug_cpu.py | 1 + 1 file changed, 1 insertion(+) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index 05196bbb93..f264d416e8 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -461,6 +461,7 @@ def trace_handler(p): # aten::relu 8.86% 71.864ms 748.583us 96 # ------------------------- ------------ ------------ ------------ ------------ # Self CPU time total: 810.920ms +# ###################################################################### # From 75875e544963f8defe87cd79832fed25eaff7c45 Mon Sep 17 00:00:00 2001 From: Svetlana Karslioglu Date: Thu, 29 Jun 2023 09:00:14 -0700 Subject: [PATCH 36/43] Update intermediate_source/inductor_debug_cpu.py --- intermediate_source/inductor_debug_cpu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index f264d416e8..4a6fa54097 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -178,7 +178,7 @@ def neg2(x): ###################################################################### # The logging gives the following compile error with a rather clear reason. # -# :: +# .. code-block:: # # torch._dynamo.exc.BackendCompilerFailed: backend='inductor' raised: # CppCompileError: C++ compile error From 357a5cc22bb21d5bbe5cbe770e8b27a64bb84be0 Mon Sep 17 00:00:00 2001 From: Nikita Shulga Date: Thu, 29 Jun 2023 09:33:30 -0700 Subject: [PATCH 37/43] Update intermediate_source/inductor_debug_cpu.py --- intermediate_source/inductor_debug_cpu.py | 1 + 1 file changed, 1 insertion(+) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index 4a6fa54097..4a6e730ebf 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -223,6 +223,7 @@ def neg2(x): # } # } # } +# ###################################################################### # IR node: From 210c50f14cf58c9d797655c32b58f31678d1b1d8 Mon Sep 17 00:00:00 2001 From: Svetlana Karslioglu Date: Thu, 29 Jun 2023 09:46:09 -0700 Subject: [PATCH 38/43] Apply suggestions from code review --- intermediate_source/inductor_debug_cpu.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index 4a6e730ebf..cfe577c60d 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -68,13 +68,13 @@ def neg1(x): # # No debugging information would be provided if you run this simple example by default. In order to get more useful debugging and logging information, we usually add a ``TORCH_COMPILE_DEBUG`` environment variable like below: # -# :: +# .. code-block:: shell # # TORCH_COMPILE_DEBUG=1 python xx.py # # This would print more debug information in the output logs and also dump the intermediate IRs generated during the codegen process. You can find the dumped file paths in the log like below: # -# :: +# .. code-block:: shell # # torch._inductor.debug: [WARNING] model___20 debug trace: /tmp/torchinductor_root/rx/crxfi2ybd7yp5sbj2pnhw33wfhtdw7wumvrobyp5sjvdui5ktjc2.debug # From 24e58524215f9e8935fbe58850378cd9cf581d4a Mon Sep 17 00:00:00 2001 From: "haozhe.zhu" Date: Fri, 30 Jun 2023 14:19:05 +0800 Subject: [PATCH 39/43] add perf env setting and comment out print to not generate output --- intermediate_source/inductor_debug_cpu.py | 76 ++++++++++++++--------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index cfe577c60d..f46a670395 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -349,7 +349,19 @@ def forward2(self, arg0_1): # Within this section, we will demonstrate the process of conducting performance analysis for a model that has been compiled using the Inductor CPU backend. # In the example below, we benchmark a Hugging Face Transformer model ``MobileBertForQuestionAnswering`` with both the eager mode and the Inductor graph mode. # The execution time and the speedup ratio of Inductor are printed after the benchmark. +# We use Intel(R) Xeon(R) Platinum 8358 CPU @ 2.60GHz and run benchmark on the first socket to demonstrate the optimization within this section. +# We set following environment variable as a best practice to benchmark on Intel(R) CPU. +# .. code-block:: shell +# +# 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") @@ -377,9 +389,9 @@ def forward2(self, arg0_1): 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}") +# 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}") ###################################################################### @@ -405,6 +417,7 @@ def forward2(self, arg0_1): # Following the steps in `Pytorch Profiler `_ # We are able to get the profiling table and trace files. +# bench.py from torch.profiler import profile, schedule, ProfilerActivity RESULT_DIR = "./prof_trace" my_schedule = schedule( @@ -416,7 +429,7 @@ def forward2(self, arg0_1): def trace_handler(p): output = p.key_averages().table(sort_by="self_cpu_time_total", row_limit=20) - print(output) + # print(output) p.export_chrome_trace(f"{RESULT_DIR}/{p.step_num}.json") for _ in range(10): @@ -437,30 +450,30 @@ def trace_handler(p): # # .. code-block:: shell # -# ------------------------- ------------ ------------ ------------ ------------ -# Name CPU total % CPU total CPU time avg # of Calls -# ------------------------- ------------ ------------ ------------ ------------ -# aten::addmm 45.73% 370.814ms 1.024ms 362 -# aten::add 19.89% 161.276ms 444.287us 363 -# aten::copy_ 14.97% 121.416ms 248.803us 488 -# aten::mul 9.02% 73.154ms 377.082us 194 -# aten::clamp_min 8.81% 71.444ms 744.208us 96 -# aten::bmm 5.46% 44.258ms 922.042us 48 -# ProfilerStep* 100.00% 810.920ms 810.920ms 1 -# aten::div 2.89% 23.447ms 976.958us 24 -# aten::_softmax 1.00% 8.087ms 336.958us 24 -# aten::linear 46.48% 376.888ms 1.041ms 362 -# aten::clone 2.77% 22.430ms 228.878us 98 -# aten::t 0.31% 2.502ms 6.912us 362 -# aten::view 0.14% 1.161ms 1.366us 850 -# aten::transpose 0.17% 1.377ms 3.567us 386 -# aten::index_select 0.12% 952.000us 317.333us 3 -# aten::expand 0.12% 986.000us 2.153us 458 -# aten::matmul 8.31% 67.420ms 1.405ms 48 -# aten::cat 0.09% 703.000us 703.000us 1 -# aten::as_strided 0.08% 656.000us 0.681us 963 -# aten::relu 8.86% 71.864ms 748.583us 96 -# ------------------------- ------------ ------------ ------------ ------------ +# ------------------------- ------------ ------------ ------------ +# 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 # @@ -551,6 +564,7 @@ def trace_handler(p): # This is a memory-bound bottle neck preventing good performance. To get a more intuitive feeling about this optimization, # we can infer the sizes and stride of the inputs and further benchmark this ``[add, add, mul, add]`` pattern. +# 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 @@ -583,9 +597,9 @@ def func(arg_0, arg_1, arg_2, arg_3, arg_4): 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}") +# 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}") ###################################################################### # Output: From fd01f57f435a03ef4e5a3347f8757a18b95bcb2c Mon Sep 17 00:00:00 2001 From: "haozhe.zhu" Date: Fri, 30 Jun 2023 14:23:56 +0800 Subject: [PATCH 40/43] fix format and reduce benchmark iters --- intermediate_source/inductor_debug_cpu.py | 102 +++++++++++----------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index f46a670395..d241529852 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -376,7 +376,7 @@ def forward2(self, arg0_1): with torch.no_grad(): compiled_model(**input_dict) -NUM_ITERS=100 +NUM_ITERS=50 import timeit with torch.no_grad(): # warmup @@ -441,7 +441,7 @@ def trace_handler(p): schedule=my_schedule, on_trace_ready=trace_handler ) as p: - for _ in range(100): + for _ in range(50): model(**input_dict) # compiled_model(**input_dict) to get inductor model profiling p.step() @@ -450,30 +450,30 @@ def trace_handler(p): # # .. code-block:: shell # -# ------------------------- ------------ ------------ ------------ -# 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 -# ------------------------- ------------ ------------ ------------ +# ------------------------- ------------ ------------ ------------ +# 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 # @@ -483,30 +483,30 @@ def trace_handler(p): # # .. code-block:: shell # -# ----------------------------------------------- ------------ ------------ ------------ -# 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 -# ----------------------------------------------- ------------ ------------ ------------ +# ----------------------------------------------- ------------ ------------ ------------ +# 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 # # From the profiling table of the eager model, we can see the most time consumption ops are [``aten::addmm``, ``aten::add``, ``aten::copy_``, ``aten::mul``, ``aten::clamp_min``, ``aten::bmm``]. @@ -585,7 +585,7 @@ def func(arg_0, arg_1, arg_2, arg_3, arg_4): inductor_func(*input) import timeit -NUM_ITERS=1000 +NUM_ITERS=100 with torch.no_grad(): # warmup for _ in range(10): From 9068837ad7f14dfc8e83c1a03802b214dc49a8c9 Mon Sep 17 00:00:00 2001 From: "haozhe.zhu" Date: Fri, 30 Jun 2023 15:29:48 +0800 Subject: [PATCH 41/43] add empty line --- intermediate_source/inductor_debug_cpu.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index d241529852..ea7f234064 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -352,6 +352,7 @@ def forward2(self, arg0_1): # We use Intel(R) Xeon(R) Platinum 8358 CPU @ 2.60GHz and run benchmark on the first socket to demonstrate the optimization within this section. # We set following environment variable as a best practice to benchmark on Intel(R) CPU. + # .. code-block:: shell # # export KMP_BLOCKTIME=1 @@ -361,6 +362,7 @@ def forward2(self, arg0_1): # 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 From 7b0b00b84f79700c6254d86b542245721564beae Mon Sep 17 00:00:00 2001 From: Svetlana Karslioglu Date: Fri, 30 Jun 2023 10:36:00 -0700 Subject: [PATCH 42/43] Update intermediate_source/inductor_debug_cpu.py --- intermediate_source/inductor_debug_cpu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index ea7f234064..15ffc459af 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -352,7 +352,7 @@ def forward2(self, arg0_1): # We use Intel(R) Xeon(R) Platinum 8358 CPU @ 2.60GHz and run benchmark on the first socket to demonstrate the optimization within this section. # We set following environment variable as a best practice to benchmark on Intel(R) CPU. - +######################################################### # .. code-block:: shell # # export KMP_BLOCKTIME=1 From 9609cdc8da23ef3066f02a916bbe92e05fddcad7 Mon Sep 17 00:00:00 2001 From: Svetlana Karslioglu Date: Fri, 30 Jun 2023 10:36:40 -0700 Subject: [PATCH 43/43] Update intermediate_source/inductor_debug_cpu.py --- intermediate_source/inductor_debug_cpu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intermediate_source/inductor_debug_cpu.py b/intermediate_source/inductor_debug_cpu.py index 15ffc459af..b534c432d8 100644 --- a/intermediate_source/inductor_debug_cpu.py +++ b/intermediate_source/inductor_debug_cpu.py @@ -361,7 +361,7 @@ def forward2(self, arg0_1): # 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