PyTorch 2.0 疑難排解 (舊版)¶
注意
本文件已過時,現在主要是有關如何執行 torch.compile
最小化工具的主要資源。請參閱更新的疑難排解文件。還有更全面的 torch.compile 手冊可供參考。
我們正積極開發偵錯工具、效能分析器,並改進我們的錯誤和警告訊息。以下是可用工具及其典型用法的表格。如需更多協助,請參閱診斷執行階段錯誤。
工具 |
目的 |
用法 |
---|---|---|
資訊記錄 |
檢視編譯的摘要步驟 |
|
偵錯記錄 |
檢視編譯的詳細步驟 (列印追蹤的每個指令) |
|
任何後端的最小化工具 |
尋找可重現任何後端錯誤的最小子圖 |
設定環境變數 |
|
如果已知錯誤發生在 |
設定環境變數 |
Dynamo 準確性最小化工具 |
當您懷疑問題出在 |
|
Inductor 準確性最小化工具 |
當您懷疑問題出在後端 (例如,inductor) 時,尋找可重現 eager 模式模型和最佳化模型之間準確性問題的最小子圖。如果此方法無效,請改為嘗試 Dynamo 準確性最小化工具。 |
|
|
尋找圖形中斷並顯示其原因 |
|
記錄/重播 |
記錄和重播在圖形擷取期間重現錯誤的影格 |
|
TorchDynamo 函數名稱篩選 |
僅編譯具有指定名稱的函數,以減少偵錯問題時的雜訊 |
設定環境變數 |
TorchInductor 偵錯記錄 |
列印一般 TorchInductor 偵錯資訊和產生的 Triton/C++ 程式碼 |
|
TorchInductor 追蹤 |
顯示每個 TorchInductor 階段所花費的時間 + 輸出程式碼和圖形視覺化 |
設定環境變數 TORCH_COMPILE_DEBUG=1 或 |
除了資訊和偵錯記錄外,您還可以使用 torch._logging 進行更精細的記錄。
診斷執行階段錯誤¶
在高階層次上,TorchDynamo 堆疊包含來自 Python 程式碼的圖形擷取 (TorchDynamo) 和後端編譯器。例如,後端編譯器可能包含反向圖形追蹤 (AOTAutograd) 和圖形降低 (TorchInductor)*。錯誤可能發生在堆疊的任何元件中,並會提供完整的堆疊追蹤。
若要判斷錯誤發生在哪個元件中,您可以使用資訊層級記錄 torch._logging.set_logs(dynamo = logging.INFO)
或 TORCH_LOGS="dynamo"
,並尋找 Step #: ...
輸出。記錄會在每個步驟的開始和結束時建立,因此錯誤應對應的步驟是最近記錄的步驟,且其結束尚未記錄。這些步驟對應於堆疊的以下部分
步驟 |
元件 |
---|---|
1 |
TorchDynamo |
2 |
編譯器後端 |
3 |
TorchInductor |
如果資訊記錄不足,您可以使用可用的後端選項。這些選項包括
"eager"
:僅執行 TorchDynamo 正向圖形擷取,然後使用 PyTorch 執行擷取的圖形。這表示 TorchDynamo 是否正在引發錯誤。"aot_eager"
:執行 TorchDynamo 以擷取正向圖形,然後執行 AOTAutograd 以追蹤反向圖形,而無需任何額外的後端編譯器步驟。然後將使用 PyTorch eager 來執行正向和反向圖形。這對於將問題縮小到 AOTAutograd 非常有用。
縮小問題範圍的一般程序如下
使用
"eager"
後端執行您的程式。如果錯誤不再發生,則問題出在正在使用的後端編譯器中 (如果使用 TorchInductor,請繼續步驟 2。如果沒有,請參閱本節)。如果使用"eager"
後端時錯誤仍然發生,則表示是執行 torchdynamo 時發生錯誤。只有在
TorchInductor
用作後端編譯器時,才需要此步驟。使用"aot_eager"
後端執行模型。如果此後端引發錯誤,則表示錯誤發生在 AOTAutograd 追蹤期間。如果使用此後端時錯誤不再發生,則錯誤出在 TorchInductor* 中。
以下各節將分析這些案例。
注意
TorchInductor 後端包含 AOTAutograd 追蹤和 TorchInductor 編譯器本身。我們將透過將 TorchInductor
稱為後端來消除歧義,並將 TorchInductor 降低稱為降低 AOTAutograd 追蹤圖形的階段。
Torchdynamo 錯誤¶
如果使用 "eager"
後端時發生錯誤,則 TorchDynamo 很可能是錯誤來源。以下是一個範例程式碼,會產生錯誤。
import torch
import torch._dynamo as dynamo
def test_assertion_error():
y = torch.ones(200, 200)
z = {y: 5}
return z
compiled_test_assertion_error = torch.compile(test_assertion_error, backend="eager")
compiled_test_assertion_error()
以上程式碼會產生以下錯誤
torch._dynamo.convert_frame: [ERROR] WON'T CONVERT test_assertion_error /scratch/mlazos/torchdynamo/../test/errors.py line 26
due to:
Traceback (most recent call last):
File "/scratch/mlazos/torchdynamo/torchdynamo/symbolic_convert.py", line 837, in BUILD_MAP
assert isinstance(k, ConstantVariable) or (
AssertionError
from user code:
File "/scratch/mlazos/torchdynamo/../test/errors.py", line 34, in test_assertion_error
z = {y: 5}
Set torch._dynamo.config.verbose=True for more information
==========
如訊息所示,您可以設定 torch._dynamo.config.verbose=True
以取得 TorchDynamo 和使用者程式碼中錯誤的完整堆疊追蹤。除了此標誌之外,您還可以透過 torch._logging.set_logs(dynamo = logging.INFO)
或 TORCH_LOGS="dynamo"
設定 TorchDynamo 的 log_level
。這些層級包括
logging.DEBUG
或TORCH_LOGS="+dynamo"
:除了以下列出的所有記錄層級外,還會列印遇到的每個指令。logging.INFO
:除了以下列出的所有記錄層級外,還會列印編譯的每個函數 (原始和修改後的位元組碼) 以及擷取的圖形。logging.WARNING
(預設):除了以下列出的所有記錄層級外,還會列印圖形中斷。logging.ERROR
:僅列印錯誤。
如果模型非常大,記錄可能會變得難以負荷。如果錯誤發生在模型 Python 程式碼的深處,則僅執行發生錯誤的影格以啟用更輕鬆的偵錯可能會很有用。有兩種工具可用於啟用此功能
將環境變數
TORCHDYNAMO_DEBUG_FUNCTION
設定為所需的函數名稱,將僅對具有該名稱的函數執行 torchdynamo。啟用記錄/重播工具 (設定
torch._dynamo.config.replay_record_enabled = True
),該工具會在遇到錯誤時傾印執行記錄。然後可以重播此記錄,以僅執行發生錯誤的影格。
診斷 TorchInductor 錯誤¶
如果使用 "eager"
後端時未發生錯誤,則後端編譯器是錯誤來源 (錯誤範例)。不同選擇適用於 TorchDynamo 的後端編譯器,其中 TorchInductor 符合大多數使用者的需求。本節重點介紹 TorchInductor 作為動機範例,但某些工具也可用於其他後端編譯器。
以下是我們重點關注的堆疊部分
使用 TorchInductor 作為選定的後端,AOTAutograd 用於從 torchdynamo 擷取的正向圖形產生反向圖形。請務必注意,錯誤可能發生在此追蹤期間,也可能發生在 TorchInductor 將正向和反向圖形降低為 GPU 程式碼或 C++ 時。模型通常可以包含數百或數千個 FX 節點,因此縮小問題發生的確切節點範圍可能非常困難。幸運的是,有些工具可用於自動最小化這些輸入圖形,以找出導致問題的節點。第一步是判斷錯誤是否發生在 AOTAutograd 反向圖形的追蹤期間,還是發生在 TorchInductor 降低期間。如步驟 2 中所述,"aot_eager"
後端可用於僅隔離執行 AOTAutograd,而無需降低。如果使用此後端時錯誤仍然發生,則表示錯誤發生在 AOTAutograd 追蹤期間。
以下是一個範例
import torch
import torch._dynamo as dynamo
model = torch.nn.Sequential(*[torch.nn.Linear(200, 200) for _ in range(5)])
def test_backend_error():
y = torch.ones(200, 200)
x = torch.ones(200, 200)
z = x + y
a = torch.ops.aten._foobar(z) # dummy function which errors
return model(a)
compiled_test_backend_error = torch.compile(test_backend_error, backend="inductor")
compiled_test_backend_error()
執行此程式碼應會產生此錯誤,下方有更長的堆疊追蹤
Traceback (most recent call last):
File "/scratch/mlazos/torchdynamo/torchinductor/graph.py", line 246, in call_function
return lowerings[target](*args, **kwargs)
File "/scratch/mlazos/torchdynamo/torchinductor/lowering.py", line 185, in wrapped
return decomp_fn(*args, **kwargs)
File "/scratch/mlazos/torchdynamo/torchinductor/lowering.py", line 810, in _foobar
assert False
AssertionError
...
如果您然後將 torch.compile(backend="inductor")
變更為 torch.compile(backend="aot_eager")
,它將在沒有錯誤的情況下執行,因為問題出在 TorchInductor 降低過程中,而不是在 AOTAutograd 中。
最小化 TorchInductor 錯誤¶
從這裡開始,讓我們執行最小化工具以取得最小的可重現範例。設定環境變數 TORCHDYNAMO_REPRO_AFTER="aot"
(或直接設定 torch._dynamo.config.repro_after="aot"
) 將產生一個 Python 程式,該程式會將 AOTAutograd 產生的圖形縮減為可重現錯誤的最小子圖。(請參閱下方的範例,我們在其中最小化 TorchDynamo 產生的圖形) 使用此環境變數執行程式應會顯示幾乎相同的輸出,並額外一行指出 minifier_launcher.py
已寫入的位置。輸出目錄可透過將 torch._dynamo.config.base_dir
設定為有效的目錄名稱來設定。最後一步是執行最小化工具,並檢查它是否成功執行。成功的執行看起來像這樣。如果最小化工具成功執行,它會產生可執行的 python 程式碼,以重現確切的錯誤。對於我們的範例,這是以下程式碼
import torch
from torch import tensor, device
import torch.fx as fx
from torch._dynamo.testing import rand_strided
from math import inf
from torch.fx.experimental.proxy_tensor import make_fx
# torch version: 1.13.0a0+gitfddfc44
# torch cuda version: 11.6
# torch git version: fddfc4488afb207971c54ad4bf58130fdc8a4dc5
# CUDA Info:
# nvcc: NVIDIA (R) Cuda compiler driver
# Copyright (c) 2005-2022 NVIDIA Corporation
# Built on Thu_Feb_10_18:23:41_PST_2022
# Cuda compilation tools, release 11.6, V11.6.112
# Build cuda_11.6.r11.6/compiler.30978841_0
# GPU Hardware Info:
# NVIDIA A100-SXM4-40GB : 8
from torch.nn import *
class Repro(torch.nn.Module):
def __init__(self):
super().__init__()
def forward(self, add):
_foobar = torch.ops.aten._foobar.default(add); add = None
return (_foobar,)
args = [((200, 200), (200, 1), torch.float32, 'cpu')]
args = [rand_strided(shape, stride, dtype, device) for shape, stride, dtype, device in args]
mod = make_fx(Repro())(*args)
from torch._inductor.compile_fx import compile_fx_inner
compiled = compile_fx_inner(mod, args)
compiled(*args)
Repro
模組的 forward
方法包含導致問題的確切運算。在提出問題時,請包含任何最小化的可重現範例,以協助偵錯。
最小化後端編譯器錯誤¶
使用 TorchInductor 以外的後端編譯器,尋找導致錯誤的子圖的過程與TorchInductor 錯誤中的程序幾乎相同,但有一個重要的注意事項。也就是說,最小化工具現在將在 TorchDynamo 追蹤的圖形上執行,而不是 AOTAutograd 的輸出圖形。讓我們逐步完成一個範例。
import torch
import torch._dynamo as dynamo
model = torch.nn.Sequential(*[torch.nn.Linear(200, 200) for _ in range(5)])
# toy compiler which fails if graph contains relu
def toy_compiler(gm: torch.fx.GraphModule, _):
for node in gm.graph.nodes:
if node.target == torch.relu:
assert False
return gm
def test_backend_error():
y = torch.ones(200, 200)
x = torch.ones(200, 200)
z = x + y
a = torch.relu(z)
return model(a)
compiled_test_backend_error = torch.compile(test_backend_error, backend=toy_compiler)
compiled_test_backend_error()
為了在 TorchDynamo 追蹤正向圖形後執行程式碼,您可以使用 TORCHDYNAMO_REPRO_AFTER
環境變數。使用 TORCHDYNAMO_REPRO_AFTER="dynamo"
(或 torch._dynamo.config.repro_after="dynamo"
) 執行此程式應會產生此輸出,以及 {torch._dynamo.config.base_dir}/repro.py
中的以下程式碼。
注意
TORCHDYNAMO_REPRO_AFTER 的另一個選項是 "aot"
,它將在產生反向圖形後執行最小化工具。
import torch
import torch._dynamo as dynamo
from torch import tensor, device
import torch.fx as fx
from torch._dynamo.testing import rand_strided
from math import inf
from torch._dynamo.debug_utils import run_fwd_maybe_bwd
from torch.nn import *
class Repro(torch.nn.Module):
def __init__(self):
super().__init__()
def forward(self, add):
relu = torch.relu(add); add = None
return (relu,)
mod = Repro().cuda()
opt_mod = torch.compile(mod, backend="None")
args = [((200, 200), (200, 1), torch.float32, 'cpu', False)]
args = [rand_strided(sh, st, dt, dev).requires_grad_(rg) for (sh, st, dt, dev, rg) in args]
with torch.cuda.amp.autocast(enabled=False):
ref = run_fwd_maybe_bwd(mod, args)
res = run_fwd_maybe_bwd(opt_mod, args)
最小化工具已成功將圖形縮減為 toy_compiler
中引發錯誤的運算。與TorchInductor 錯誤中程序的另一個不同之處在於,在遇到後端編譯器錯誤後,最小化工具會自動執行。成功執行後,最小化工具會將 repro.py
寫入 torch._dynamo.config.base_dir
。
效能分析¶
存取 TorchDynamo 效能分析器¶
TorchDynamo 具有內建的統計函數,用於收集和顯示每個編譯階段所花費的時間。執行 Torch._Dynamo 後,可以呼叫 torch._dynamo.utils.compile_times()
來存取這些統計資料。根據預設,這會傳回每個 TorchDynamo 函數名稱的編譯時間字串表示。
使用 TORCH_COMPILE_DEBUG 偵錯 TorchInductor¶
TorchInductor 具有內建的統計和追蹤函數,用於顯示每個編譯階段所花費的時間、輸出程式碼、輸出圖形視覺化和 IR 傾印。這是一種偵錯工具,旨在讓您更輕鬆地了解和疑難排解 TorchInductor 的內部結構。
讓我們使用以下測試程式 (repro.py
) 執行一個範例
import torch
@torch.compile()
def test_model(x):
model = torch.nn.Sequential(
torch.nn.Linear(10, 10),
torch.nn.LayerNorm(10),
torch.nn.ReLU(),
)
return model(x)
y = test_model(torch.ones(10, 10))
設定環境變數 TORCH_COMPILE_DEBUG=1
將會建立偵錯追蹤目錄,根據預設,此目錄將位於目前目錄中,並命名為 torch_compile_debug (這可以在 torchdynamo 設定欄位 debug_dir_root
以及 env var TORCH_COMPILE_DEBUG_DIR
中覆寫)。在此目錄中,每次執行都會有一個單獨的資料夾,以執行的時間戳記和程序 ID 命名
$ env TORCH_COMPILE_DEBUG=1 python repro.py
$ cd torch_compile_debug
$ ls
run_2023_03_01_08_20_52_143510-pid_180167
在執行資料夾中,將會有一個 torchdynamo
目錄,其中包含偵錯記錄,以及一個 torchinductor
資料夾,其中包含每個編譯核心的子資料夾,以及 inductor 偵錯成品。
$ cd
run_2023_03_01_08_20_52_143510-pid_180167
$ ls
torchinductor torchdynamo
進一步移至 torchinductor
目錄中,*.log
檔案是來自編譯的 AOT Autograd 階段的記錄,model__0_forward_1.0
包含 inductor 偵錯成品。
$ cd torchinductor
$ ls
aot_model___0_debug.log model__0_forward_1.0
$ cd model__0_forward_1.0
$ ls
debug.log fx_graph_readable.py fx_graph_runnable.py fx_graph_transformed.py ir_post_fusion.txt ir_pre_fusion.txt output_code.py
以下是內容摘要
fx_graph_readable.py
和fx_graph_runnable.py
是 inductor 接收的fx_graph
的可讀和可執行版本。fx_graph_transformed.py
是 inductor 執行所有 fx 傳遞後的 fx 圖形。ir\*.txt
是融合前後的 inductor ir。output_code.py
是子圖的已編譯 triton 核心。
以下是測試程式的偵錯目錄內容範例
import torch
@torch.compile()
def test_model(x):
model = torch.nn.Sequential(
torch.nn.Linear(10, 10),
torch.nn.LayerNorm(10),
torch.nn.ReLU(),
)
return model(x)
y = test_model(torch.ones(10, 10))
偵錯追蹤中的每個檔案都可以透過 torch._inductor.config.trace.*
啟用和停用。效能分析和圖表預設為停用,因為它們的產生成本很高。
此新偵錯格式中的單一節點看起來像這樣
buf1: SchedulerNode(ComputedBuffer)
buf1.writes =
{ MemoryDep(name='buf1', index=0, size=()),
MemoryDep(name='buf1', index=0, size=(s0,))}
buf1.unmet_dependencies = {MemoryDep(name='buf0', index=c0, size=(s0,))}
buf1.met_dependencies = {MemoryDep(name='primals_2', index=c0, size=(s0,))}
buf1.group.device = cuda:0
buf1.group.iteration = (1, s0)
buf1.sizes = ([], [s0])
class buf1_loop_body:
var_ranges = {z0: s0}
index0 = z0
index1 = 0
def body(self, ops):
get_index = self.get_index('index0')
load = ops.load('buf0', get_index, False)
get_index_1 = self.get_index('index0')
load_1 = ops.load('primals_2', get_index_1, False)
add = ops.add(load, load_1)
get_index_2 = self.get_index('index1')
reduction = ops.reduction('buf1', torch.float32, torch.float32, 'sum', get_index_2, add)
return reduction
請參閱偵錯目錄輸出範例以取得更多範例。
圖形中斷¶
給定如下程式
def some_fun(x):
...
compiled_fun = torch.compile(some_fun, ...)
...
TorchDynamo 會嘗試將 some_fun 內的所有 torch/tensor 運算編譯成單一 FX 圖形,但它可能無法將所有內容擷取到一個圖形中。
有些圖形中斷原因對於 TorchDynamo 來說是無法克服的,而且不容易修復。 - 呼叫 torch 以外的 C 擴充功能對於 torchdynamo 來說是不可見的,並且可能會執行任意操作,而 TorchDynamo 無法引入必要的防護措施 (請參閱使 Dynamo 聲音:防護) 以確保編譯後的程式可以安全地重複使用。如果產生的片段很小,圖形中斷可能會阻礙效能。為了最大化效能,盡可能減少圖形中斷非常重要。
識別圖形中斷的原因¶
若要識別程式中的所有圖形中斷以及中斷的相關原因,可以使用 torch._dynamo.explain
。此工具會在提供的函數上執行 TorchDynamo,並匯總遇到的圖形中斷。以下是一個範例用法
import torch
import torch._dynamo as dynamo
def toy_example(a, b):
x = a / (torch.abs(a) + 1)
print("woo")
if b.sum() < 0:
b = b * -1
return x * b
explanation = dynamo.explain(toy_example)(torch.randn(10), torch.randn(10))
print(explanation_verbose)
"""
Graph Count: 3
Graph Break Count: 2
Op Count: 5
Break Reasons:
Break Reason 1:
Reason: builtin: print [<class 'torch._dynamo.variables.constant.ConstantVariable'>] False
User Stack:
<FrameSummary file foo.py, line 5 in toy_example>
Break Reason 2:
Reason: generic_jump TensorVariable()
User Stack:
<FrameSummary file foo.py, line 6 in torch_dynamo_resume_in_toy_example_at_5>
Ops per Graph:
...
Out Guards:
...
"""
輸出包括
out_guards
- 列表的列表,其中每個子列表包含必須通過以確保追蹤圖形有效的防護措施。graphs
- 成功追蹤的圖形模組列表。ops_per_graph
- 列表的列表,其中每個子列表包含在圖形中執行的運算。
若要在遇到的第一個圖形中斷時擲回錯誤,請使用 fullgraph
模式。此模式會停用 TorchDynamo 的 Python 回退,並且僅在整個程式可轉換為單一圖形時才會成功。範例用法
def toy_example(a, b):
...
compiled_toy = torch.compile(toy_example, fullgraph=True, backend=<compiler>)(a, b)
過度重新編譯¶
當 TorchDynamo 編譯函數 (或函數的一部分) 時,它會對區域變數和全域變數做出某些假設,以便允許編譯器最佳化,並將這些假設表示為在執行階段檢查特定值的防護措施。如果任何這些防護措施失敗,Dynamo 將重新編譯該函數 (或部分函數) 最多 torch._dynamo.config.cache_size_limit
次。如果您的程式達到快取限制,您首先需要判斷哪個防護措施失敗,以及程式的哪個部分觸發了它。
如果您的程式展現出有限的動態性,您或許可以調整 TorchDynamo 快取限制,以允許編譯和快取每個變體,但如果快取限制太高,您可能會發現重新編譯的成本超過了任何最佳化優勢。
torch._dynamo.config.cache_size_limit = <your desired cache limit>
TorchDynamo 計劃支援許多常見的動態張量形狀案例,例如變化的批次大小或序列長度。它不計劃支援秩動態性。同時,快取限制可以與分桶技術協調使用,以針對某些動態模型實現可接受的重新編譯次數。
準確性偵錯¶
如果您設定環境變數 TORCHDYNAMO_REPRO_LEVEL=4
,也可以最小化準確性問題,它以類似的 git bisect 模型運作,完整的可重現範例可能類似 TORCHDYNAMO_REPRO_AFTER="aot" TORCHDYNAMO_REPRO_LEVEL=4
,我們需要這樣做的原因是下游編譯器將產生程式碼,無論是 Triton 程式碼還是 C++ 後端,這些下游編譯器的數值可能以細微的方式不同,但對您的訓練穩定性產生巨大影響。因此,準確性偵錯器對於我們偵測程式碼產生或後端編譯器中的錯誤非常有用。
如果您想要確保跨 torch 和 triton 的隨機數產生是相同的,則可以啟用 torch._inductor.config.fallback_random = True
擴展偵錯¶
可以使用以下實驗性標誌啟用擴展偵錯。
TORCHDYNAMO_EXTENDED_DEBUG_GUARD_ADDED
- 如果防護措施的字串表示與此標誌值相符,則提供擴展偵錯資訊。例如,將其設定為 “Ne(s0, 10)” 以在每次發出防護措施時產生完整的 Python 和 C++ 回溯。TORCHDYNAMO_EXTENDED_DEBUG_CREATE_SYMBOL
- 在分配特定符號時提供擴展偵錯資訊。例如,將其設定為 “u2” 以在每次建立此符號時產生完整的 Python 和 C++ 回溯。TORCHDYNAMO_EXTENDED_DEBUG_CPP
- 為所有擴展偵錯設定以及錯誤提供擴展偵錯資訊 (C++ 回溯)。例如,將其設定為 “1”。C++ 回溯速度慢且非常冗長,因此預設情況下不包含在擴展偵錯中。
冷啟動計時和快取損壞偵錯¶
為了測量冷啟動編譯時間或偵錯快取損壞,可以傳遞 TORCHINDUCTOR_FORCE_DISABLE_CACHES=1
或設定 torch._inductor.config.force_disable_caches = True
,這將覆寫任何其他快取設定選項,並停用所有編譯時間快取。