• 教學 >
  • Inductor CPU 後端除錯和效能分析
捷徑

Inductor CPU 後端除錯和效能分析

建立於:2023 年 7 月 1 日 | 最後更新:2025 年 1 月 8 日 | 最後驗證:2024 年 11 月 5 日

作者Xuan LiaoHaozhe ZhuJiong GongWeihan Wang

概觀

PyTorch 2.0 引入了名為 torch.compile 的編譯 API。這個新功能透過預設 Inductor 後端提供的圖層級最佳化,提供比 Eager 模式執行更顯著的加速。

本教學旨在深入介紹 Inductor CPU 後端的除錯和效能分析,深入探討 torch.compile 的複雜性。

同時,您也可以找到有關 torch.compile 的相關教學,例如 基本用法、全面的疑難排解和特定於 GPU 的知識,例如 GPU 效能分析

我們將從一個激勵範例開始進行除錯,該範例透過展示除錯過程以找出問題來觸發編譯問題和準確性問題。

透過啟用記錄並探索底層產生的程式碼,您可以學習如何逐步縮小故障範圍,並最終找出根本原因。

接下來,我們將討論如何分析編譯後的程式碼,並透過與 Eager 模式的效能比較,闡述為什麼與 Eager 模式相比,torch.compile 可以提供額外的效能提升。

除錯

這是一個簡單的範例,可以使用 Inductor 執行 torch.compile,並將其結果與 Eager 模式進行比較

import torch

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

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

compiled_foo1 = torch.compile(foo1)
result = compiled_foo1(x1, x2)

cpp codegen 中 neg 的正確實作如下

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

為了演示除錯,我們稍後會將該函數修改為錯誤的函數。

取得更多記錄資訊

如果您預設執行這個簡單的範例,則不會提供任何除錯資訊。為了獲得更有用的除錯和記錄資訊,我們通常會新增一個 TORCH_COMPILE_DEBUG 環境變數,如下所示

TORCH_COMPILE_DEBUG=1 python xx.py

這將在輸出日誌中列印更多除錯資訊,並傾印 codegen 過程中產生的中間 IR。您可以在日誌中找到傾印的檔案路徑,如下所示

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

在這個目錄中,會儲存以下檔案以進行除錯

檔案

描述

fx_graph_runnable.py

可執行的 FX 圖,分解後,模式比對前

fx_graph_transformed.py

轉換後的 FX 圖,模式比對後

ir_pre_fusion.txt

融合前的 Inductor IR

ir_post_fusion.txt

融合後的 Inductor IR

output_code.py

為圖產生的 Python 程式碼,帶有 C++/Triton 核心

請注意,fx_graph_runnable.pyoutput_code.py 都是可執行且可編輯的,以便更輕鬆地進行除錯。以下是從檔案中提取的程式碼的主要部分,我們將 C++ 產生的行與 FX 程式碼行相關聯。

fx_graph_runnable:

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

output_code 中的 C++ 核心

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

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

確定錯誤元件

遇到錯誤或準確性問題時,找到錯誤的簡單解決方案是縮小問題範圍。首先要做的是確定發生錯誤的元件。幸運的是,只需變更 torch.compile 的後端即可輕鬆實現。

程式碼

描述

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

啟用 Dynamo

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

啟用 Dynamo + AOT Autograd

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

啟用 Dynamo + AOT Autograd + Inductor

如果當後端設定為 eageraot_eager 時模型可以成功執行,而使用 inductor 時卻失敗,我們可以將故障範圍縮小到 Inductor。

編譯錯誤

如我們所知,圖層級最佳化的演進鏈如下所示

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

如果遇到編譯錯誤,則在輸出程式碼中編譯 C++ 核心時出現問題。這種類型的錯誤表示在將 IR 節點降低到輸出程式碼時引入了錯誤。編譯錯誤的根本原因通常顯示在追蹤記錄中。

舉例來說,可以這樣修改 neg 函式

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

記錄檔會產生以下編譯錯誤,並提供相當清楚的原因。

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

讓我們也看看輸出程式碼和 IR 節點中對應的 C++ 核心。

C++ 核心

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

IR 節點

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

根據追蹤記錄,編譯錯誤是由 max_propagate_nan 輸入的資料類型不一致所引起的。 檢查 C++ 核心後,我們知道在進行 - 運算後,tmp2 不再是 long,因為 tmp0long。我們可以很容易地將 C++ 核心中的 -max_propagate_nan 分別與 IR 節點中的 ops.negops.maximum 相對應。

現在我們成功地發現根本原因是 cpp 程式碼產生器中 ops.neg 的實作,它會在進行 neg 運算時,悄悄地更改資料類型。

精確度偵錯

否則,如果模型執行時出現其他錯誤或精確度問題,您可以使用名為 Minifier 的 PyTorch 偵錯工具。

Minifier 的核心思想是不斷移除圖的節點和輸入,直到找到最小的問題圖。它有助於透過 4 種策略自動產生精簡的問題圖:截斷後綴、delta 偵錯、消除無效程式碼和移除未使用的輸入。

我們現在將展示在 Minifer 的幫助下,針對精確度問題進行偵錯的過程。精確度問題是指 backends eager 和 inductor 的輸出不同的情況。

例如,我們像這樣修改範例

from torch._dynamo.utils import same

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

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

expected_result = foo2(x1, x2)

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

assert same(expected_result, actual_result) == True

並且也修改 neg 函式

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

會產生如下的精確度問題

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

要使用 Minifier 偵錯精確度問題,需要兩個環境變數

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

這會提供我們記錄資訊,顯示精簡的步驟

Started off with 6 nodes

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

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

執行後,我們得到最終的精簡圖,其中目標節點為 neg

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

有關 Minifier 的更多使用細節,請參閱故障排除

效能分析

在本節中,我們將展示對使用 Inductor CPU 後端編譯的模型進行效能分析的過程。 在下面的範例中,我們對一個 Hugging Face Transformer 模型 MobileBertForQuestionAnswering 進行基準測試,分別使用 eager 模式和 Inductor 圖模式。 基準測試後會印出 Inductor 的執行時間和加速比。 我們使用 Intel(R) Xeon(R) Platinum 8358 CPU @ 2.60GHz,並在第一個插槽上運行基準測試,以展示本節中的最佳化。 我們設定以下環境變數,作為在 Intel(R) CPU 上進行基準測試的最佳實踐。

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

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

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

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

輸出

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

在我們自己的測試中,我們發現 Inductor CPU 後端將模型加速了約 2.355 倍。

接下來,讓我們深入研究操作層面的效能,以了解加速來自何處。 Pytorch Profiler 是一個很好的工具來幫助我們。 Inductor CPU 後端支援使用 enable_kernel_profile 組態選項,將融合核心的時間報告給 profiler

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

按照 Pytorch Profiler 中的步驟,我們可以取得效能分析表和追蹤檔案。

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

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

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

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

我們取得 eager 模式模型的以下效能分析表(省略了一些欄位)

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

類似地,我們也取得使用 Inductor 編譯模型的表格(省略了一些欄位)

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

從 eager 模型的效能分析表中,我們可以看到耗時最多的操作是 [aten::addmm, aten::add, aten::copy_, aten::mul, aten::clamp_min, aten::bmm]。 與 inductor 模型效能分析表相比,我們注意到一個 mkl::_mkl_linear 條目和多個融合核心,其形式為 graph_0_cpp_fused_*。 這些是 inductor 模型正在進行的主要最佳化。 讓我們分別討論它們。

(1) 關於 mkl::_mkl_linear:您可能會注意到對此核心的呼叫次數為 362,這與 eager 模型效能分析表中的 aten::linear 完全相同。 aten::linear 的 CPU 總時間為 376.888 毫秒,而 mkl::_mkl_linear 的 CPU 總時間為 231.573 毫秒。 這表示 “linear” 部分的加速比約為 1.63 倍。 這種加速主要來自於將權重張量打包到區塊記憶體格式,並在 Inductor CPU 後端中調用 cblas_sgemm_compute,以在 GEMM 計算期間獲得更好的快取行為。

(2) 關於其他記憶體密集型操作:在我們的測試中,eager/inductor 模型的端到端延遲為 802/339 毫秒。 因此,我們可以粗略地推斷出其他記憶體密集型操作的加速比約為 3.94 倍。 讓我們閱讀產生的程式碼,以了解 inductor 如何實現這種令人印象深刻的最佳化。 您可以透過在 output_code.py 中搜尋 cpp_fused__mkl_linear_add_mul_relu_151 來找到產生的程式碼

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

從上面產生的程式碼中,我們可以看到此核心在 [add, add, mul, add] 上進行了典型的迴圈融合。 這是一個記憶體受限的瓶頸,會阻礙良好的效能。 為了對這種最佳化有更直觀的感覺,我們可以推斷輸入的大小和步幅,並進一步基準測試這種 [add, add, mul, add] 模式。

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

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

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

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

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

輸出

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

這只是一個範例。效能分析表顯示,在此模型中,所有元素級操作都會在 inductor 中自動融合。您可以在output_code.py中閱讀更多核心

結論

本文檔對 Inductor CPU 後端進行了深入的教學。

透過動機明確的範例,我們將逐步介紹偵錯和效能分析的過程。主要概念是縮小問題範圍。

我們將逐步示範如何深入探討問題,並借助偵錯日誌和 Minifier 工具找出失敗的根本原因。首先確定哪個元件發生故障,然後嘗試產生可以重現故障的最小程式碼片段。

當 Inductor 的效能優於 eager 模式時,我們會提供可靠的效能分析方法。我們將展示如何使用 PyTorch Profiler 找出耗時的熱點,並找出運算子層級或核心層級的原因來解釋這種現象。

腳本總執行時間: ( 8 分鐘 47.254 秒)

由 Sphinx-Gallery 產生

文件

訪問 PyTorch 的全面開發人員文檔

查看文檔

教學

取得適合初學者和高級開發人員的深入教學

查看教學

資源

尋找開發資源並獲得您的問題解答

查看資源