PyTorch 2.0 疑難排解¶
我們正在積極開發除錯工具、效能分析器,並改進我們的錯誤和警告訊息。以下是可用工具及其典型用途的表格。如需更多說明,請參閱診斷執行階段錯誤。
| 工具 | 用途 | 用法 | 
|---|---|---|
| 資訊記錄 | 檢視編譯的摘要步驟 | 
 | 
| 除錯記錄 | 檢視編譯的詳細步驟(列印追蹤的每條指令) | 
 | 
| 任何後端的最小化程式 | 尋找可重現任何後端錯誤的最小子圖 | 設定環境變數  | 
| 
 | 如果已知錯誤發生在  | 設定環境變數  | 
| 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" 並尋找 步驟 #: ... 輸出。記錄會在每個步驟的開始和結束時進行,因此發生錯誤的步驟應該是最近記錄的、尚未記錄其結束的步驟。這些步驟對應於堆疊的以下部分
| 步驟 | 元件 | 
|---|---|
| 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 lowering 稱為將 AOTAutograd 追蹤的圖形 lowering 的階段來消除歧義。
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 將前向圖和後向圖 lowering 為 GPU 程式碼或 C++ 期間,可能會在此追蹤期間以及期間發生錯誤。模型通常可以包含數百或數千個 FX 節點,因此縮小發生此問題的確切節點範圍可能非常困難。幸運的是,有一些工具可以自動將這些輸入圖縮小到導致問題的節點。第一步是確定錯誤是發生在使用 AOTAutograd 追蹤後向圖期間,還是發生在 TorchInductor lowering 期間。如上文步驟 2 中所述,"aot_eager" 後端可用於單獨執行 AOTAutograd,而無需 lowering。如果使用此後端時仍然發生錯誤,則表示錯誤發生在 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 lowering 過程中,而不是在 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 pass 後的 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 的快取限制設定為 1 的過程,並在僅限觀察的「編譯器」下執行你的程式碼,該「編譯器」會記錄任何防護失敗的原因。你應該確保執行程式碼的時間(迭代次數)至少與遇到問題時執行的時間一樣長,設定檔會在此期間累積統計資料。
如果你的程式碼表現出有限的動態性,你或許可以調整 TorchDynamo 快取限制,以便編譯和快取每個變異,但如果快取限制過高,你可能會發現重新編譯的成本超過任何最佳化效益。
torch._dynamo.config.cache_size_limit = <your desired cache limit>
TorchDynamo 計劃支援許多常見的動態張量形狀案例,例如變化的批次大小或序列長度。它不打算支援秩動態性。在此期間,可以將特定的快取限制與分桶技術結合使用,以在某些動態模型中實現可接受的重新編譯次數。
from torch._dynamo.utils import CompileProfiler
def my_model():
    ...
with CompileProfiler() as prof:
    profiler_model = torch.compile(my_model, backend=prof)
    profiler_model()
    print(prof.report())
準確性除錯¶
如果你設定環境變數 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,這將覆蓋任何其他快取組態選項並停用所有編譯時間快取。