注意
點擊這裡下載完整的範例程式碼
Inductor CPU 後端除錯和效能分析¶
建立於:2023 年 7 月 1 日 | 最後更新:2025 年 1 月 8 日 | 最後驗證:2024 年 11 月 5 日
作者:Xuan Liao、Haozhe Zhu、Jiong Gong、Weihan 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 圖,分解後,模式比對前 |
|
轉換後的 FX 圖,模式比對後 |
|
融合前的 Inductor IR |
|
融合後的 Inductor IR |
|
為圖產生的 Python 程式碼,帶有 C++/Triton 核心 |
請注意,fx_graph_runnable.py
和 output_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
的後端即可輕鬆實現。
程式碼 |
描述 |
---|---|
|
啟用 Dynamo |
|
啟用 Dynamo + AOT Autograd |
|
啟用 Dynamo + AOT Autograd + Inductor |
如果當後端設定為 eager
或 aot_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
,因為 tmp0
是 long
。我們可以很容易地將 C++ 核心中的 -
和 max_propagate_nan
分別與 IR 節點中的 ops.neg
和 ops.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 秒)