跳到內容

如何除錯 vLLM-torch.compile 整合

TL;DR

  • 使用 tlparse 獲取 torch.compile 日誌。在提交 bug 報告和/或尋求支援時,請包含這些日誌。
  • vLLM-torch.compile 整合包含多個元件。vLLM 提供了 flags 來關閉每個元件。
線上 Flag 離線 Flag 結果
--enforce-eager enforce_eager=True 關閉 torch.compile 和 CUDAGraphs
-cc.mode=0 mode=CompilationMode.NONE 僅關閉 torch.compile
-cc.cudagraph_mode=NONE compilation_config=CompilationConfig(cudagraph_mode=CUDAGraphMode.NONE) 僅關閉 CUDAGraphs
-cc.backend=eager compilation_config=CompilationConfig(backend='eager') 關閉 TorchInductor

vLLM-torch.compile 概覽

為了提高效能,vLLM 利用 torch.compile 和 CUDAGraphs 來加速。torch.compile 為 PyTorch 程式碼生成最佳化核心,而 CUDAGraphs 則消除了開銷。特別地,vLLM-compile 不是 torch.compile,而是一個使用內部 PyTorch Compile API 構建的自定義編譯器。

vLLM-compile diagram

  • 給定一個模型,我們透過 TorchDynamo 進行一次完整的圖捕獲,該捕獲是動態的(相對於 batch size,即 token 數量)。
  • vLLM 然後可選地分割和/或特化此圖,然後使用 TorchInductor 將每個圖編譯成一個編譯後的產物。此步驟可能使用 vLLM 的自定義 Inductor 傳遞來進一步最佳化圖。
  • 編譯後的產物會被儲存在 vLLM 的 compile 快取中,以便將來載入。
  • vLLM 應用 CUDAGraphs 來減少 CPU 開銷。

在所有四個步驟中都可能出現問題。當出現問題時,請儘量隔離出現問題的子系統——這將允許您關閉最少量的元件,以在最小化效能影響的同時保持可靠性目標,並且在您開啟 bug 報告時也有助於我們(vLLM)。

有關設計的更多詳細資訊,請參閱以下資源:

使用 tlparse

使用 tlparse 獲取 torch.compile 日誌。這些日誌顯示了編譯過程的所有階段,包括 torch.compile 生成的融合核心。如果可能,我們建議將這些日誌或其中的一部分與任何 bug 報告一起傳送——它們非常有幫助。

安裝 tlparse

pip install tlparse

用法(離線推理)

TORCH_TRACE=~/trace_dir python my_script.py
tlparse ~/trace_dir/<the_first_log_file>

用法(服務)

TORCH_TRACE=~/trace_dir vllm serve
# ctrl-c out of the server
tlparse ~/trace_dir/<the_first_log_file>

tlparse 命令會輸出一些 HTML 檔案(可能輸出到例如 ./tl_out/index.html)。開啟它即可檢視日誌。它看起來會像這樣:

tlparse example

關閉 vLLM-torch.compile 整合

傳遞 --enforce-eager 引數以關閉 vLLM-torch.compile 整合,並完全以 eager 模式執行。這包括關閉 CUDAGraphs。

# Online
vllm serve --enforce-eager
# Offline
LLM(model, enforce_eager=True)

要僅關閉 torch.compile,請將 mode = NONE 傳遞給 compilation config。(-cc--compilation_config 的縮寫)

# Online
vllm serve -cc.mode=0
# Offline
from vllm.config.compilation import CompilationConfig, CompilationMode
LLM(model, compilation_config=CompilationConfig(mode=CompilationMode.NONE))

要僅關閉 CUDAGraphs,請傳遞 cudagraph_mode = NONE

# Online
vllm serve -cc.cudagraph_mode=NONE
# Offline
from vllm.config.compilation import CompilationConfig, CUDAGraphMode
LLM(model, compilation_config=CompilationConfig(cudagraph_mode=CUDAGraphMode.NONE))

除錯 TorchDynamo

vLLM 要求模型程式碼能夠透過 TorchDynamo(torch.compile 的前端)捕獲成一個完整的圖。TorchDynamo 不支援 Python 的所有功能。如果在 fullgraph 模式下遇到不支援的特性,它會報錯(有時被稱為圖中斷)。

如果您遇到圖中斷,請在 pytorch/pytorch 上提交一個 issue,以便 PyTorch 開發人員可以優先處理。然後,請盡力重寫程式碼以避免圖中斷。有關更多資訊,請參閱此Dynamo 指南

除錯動態形狀完整圖捕獲

vLLM 要求模型的 forward pass 能夠捕獲成一個對 batch size(即 token 數量)動態的完整圖。它(預設情況下)將此單個圖編譯成一個產物,併為所有 batch size 使用此產物。

如果您的程式碼無法用動態形狀捕獲,您可能會遇到靜默的錯誤、明顯的錯誤或 CUDA 非法記憶體訪問。例如,以下程式碼無法捕獲成單個圖:

if data.size[0] % 128 == 0:
    foo(...)
else:
    bar(...)

這個問題很容易診斷。使用 tlparse 並點選 compilation_metrics:它會告訴您關於 batch size 的符號約束。如果存在任何限制 batch size 的約束,那麼我們就遇到了問題。

Bad tlparse example

為避免此問題,請執行以下任一操作:

  1. 避免基於 token 數量進行分支。
  2. 將分支邏輯封裝到自定義運算元中。TorchDynamo 不會跟蹤進入自定義運算元。

除錯約束衝突和動態形狀 guard 問題

動態形狀 guard 是 Dynamo guard 的一個特定類別。它們是 torch.compile 應用於動態維度(例如 seq_len)的約束,以確保編譯後的產物保持有效。這些 guard 通常在框架程式碼、自定義傳遞或使用者程式碼基於動態形狀值進行分支時出現。

示例

if x > 10:
    # path A
else:
    # path B

這會建立一個 guard x > 10x <= 10,具體取決於跟蹤的路徑。

vLLM 的假設: vLLM 假設所有由 torch.compile 新增的 guard 都可以安全地刪除,並且不會將編譯後的圖約束為特定的輸入形狀。當此假設被違反時,可能會導致使用者需要除錯的問題。一些表明此假設被違反的副作用是執行時錯誤或 ConstraintViolationErrors

如果動態形狀被約束為單個值,則會丟擲 ConstraintViolationErrors。如果您遇到約束衝突錯誤或懷疑動態形狀 guard 被錯誤地新增,您可以使用更嚴格的動態形狀模式來幫助除錯問題。

# Online - using unbacked mode
vllm serve meta-llama/Llama-3.2-1B -cc.dynamic_shapes_config.type=unbacked

# Online - using backed_size_oblivious mode
vllm serve meta-llama/Llama-3.2-1B -cc.dynamic_shapes_config.type=backed_size_oblivious
# Offline - using unbacked mode
from vllm.config.compilation import CompilationConfig, DynamicShapesConfig, DynamicShapesType
LLM(model, compilation_config=CompilationConfig(
    dynamic_shapes_config=DynamicShapesConfig(type=DynamicShapesType.UNBACKED)
))

# Offline - using backed_size_oblivious mode
from vllm.config.compilation import CompilationConfig, DynamicShapesConfig, DynamicShapesType
LLM(model, compilation_config=CompilationConfig(
    dynamic_shapes_config=DynamicShapesConfig(type=DynamicShapesType.BACKED_SIZE_OBLIVIOUS)
))

這些模式更嚴格,減少或消除了對動態形狀 guard 的需求,這有助於隔離問題。

  • unbacked:使用 unbacked symints,它們不允許 guard,從而更容易識別 guard 被錯誤新增的位置。
  • backed_size_oblivious:使用一種對 guarding 更嚴格的模式。

有關動態形狀模式的更多詳細資訊,請參閱 動態形狀和 vLLM guard 刪除

列印 guards

要檢視編譯過程中新增的所有 guard,您可以使用 TORCH_LOGS=+dynamic

TORCH_LOGS=+dynamic vllm serve meta-llama/Llama-3.2-1B

在日誌中查詢 [guard added] 以檢視 guard 的新增位置。這可以幫助您識別哪些操作導致 guard 被錯誤新增。

除錯 TorchInductor

TorchInductor 接收一個捕獲的圖,然後將其編譯成一些 Python 程式碼,這些程式碼可能會呼叫 1+ 個 triton 核心。在罕見(但不幸)的情況下,它可能會生成一個不正確的 triton 核心。這可能表現為靜默的錯誤、CUDA 非法記憶體訪問或明顯的錯誤。

要除錯 TorchInductor 是否有問題,您可以透過將 backend='eager' 傳遞給 compilation config 來停用它。

# online
vllm serve -cc.backend=eager
# offline
LLM(compilation_config=CompilationConfig(backend='eager'))

如果 Inductor 有問題,請向 PyTorch 提交 bug。如果您想嘗試一下,可以除錯 Inductor 輸出程式碼中的 triton 核心(您可以透過使用 tlparse 找到它們)。

tlparse example

您還可以使用 TORCH_LOGS=output_code <command> 來列印 Inductor 輸出程式碼。

可編輯的 TorchInductor 程式碼

您可以透過設定 VLLM_COMPILE_CACHE_SAVE_FORMAT=unpacked 或傳遞 -cc.compile_cache_save_format=unpacked 來編輯執行的 TorchInductor 程式碼。預設是 binary,這意味著它不可編輯。

這是一種有用的技術:您可以在輸出程式碼中設定斷點(例如 torch.distributed.breakpoint())和列印語句。

除錯 vLLM-compile 快取

vLLM 為 torch.compile 產物構建了自己的快取。其理念是,產物可以被編譯一次,然後在將來重複使用。這是對 torch.compile 的編譯器快取 的一層封裝。

雖然 torch.compile 的編譯器快取非常穩定,但 vLLM 的編譯器快取不幸的是並不總是正確的。您可以透過設定 VLLM_DISABLE_COMPILE_CACHE=1 來停用它。

您也可以手動刪除此快取。

  • 使用 rm -rf ~/.cache/vllm 刪除 vLLM 的編譯快取(檢視日誌以瞭解位置是否已更改)。
  • 使用 rm -rf /tmp/torchinductor_$(whoami) 刪除 torch.compile 的內建快取。

vLLM 的快取是將快取鍵對映到編譯後產物的。vLLM 透過組合多個因素(例如,配置 flags 和模型名稱)來計算快取鍵。如果 vLLM 的編譯快取不正確,這通常意味著缺少某個因素。請參閱 此示例,瞭解 vLLM 如何計算快取鍵的一部分。

除錯 CUDAGraphs

CUDAGraphs 是一項允許您執行以下操作的功能:

  • 將啟動 1+ 個 CUDA 核心的可呼叫物件捕獲到 CUDAGraph 中。
  • 重放 CUDAGraph。

捕獲的 CUDAGraph 包含捕獲過程中使用的所有記憶體。CUDAGraph 的重放會讀寫完全相同的記憶體區域。

這導致了一些限制:

  1. 為了在新資料上使用 CUDAGraphs,您需要將資料複製到 CUDAGraph 正在讀取的緩衝區中。
  2. CUDAGraphs 只捕獲 CUDA 核心,它們不捕獲在 CPU 上完成的工作。

vLLM 使用原始的 CUDAGraphs API,這在不正確使用時是不安全的。

要僅關閉 CUDAGraphs,請傳遞 cudagraph_mode = NONE

# Online
vllm serve -cc.cudagraph_mode=NONE
# Offline
from vllm.config.compilation import CompilationConfig, CUDAGraphMode
LLM(model, compilation_config=CompilationConfig(cudagraph_mode=CUDAGraphMode.NONE))