Apache TVM 是一個深度的深度學習編譯框架,適用於 CPU、GPU 和各種機器學習加速芯片。更多 TVM 中文文檔可訪問 →https://tvm.hyper.ai/
摘要
對於任何支持的 runtime,TVM 都應該輸出正確的數字結果。因此,在編寫驗證數字輸出的單元測試時,這些單元測試應該在所有支持的 runtime 上都能正常運行。由於這是一個非常常見的用例,TVM 的輔助函數可以對所有單元測試進行參數化,從而便於單元測試在所有啓用並具有兼容設備的 target 上運行。
測試套件的單個 Python 函數,可以擴展為幾個參數化單元測試,每個單元測試一個 target 設備。為了保證測試正常運行,以下所有條件必須為 True:
- 測試存在於已經傳遞給 pytest 的文件或目錄中。
- 應用於函數的 pytest 標記,無論是顯式還是通過 target 參數化,都必須與傳遞給 pytest 的 -m 參數的表達式兼容。
- 對於使用 target fixture 的參數化測試,target 必須出現在環境變量 TVM_TEST_TARGETS 中。
- 對於使用 target fixture 的參數化測試,config.cmake 中的構建配置必須啓用相應的 runtime。
單元測試文件內容
在多個 target 上運行測試,推薦方法是通過參數化測試。對於一個固定的 target 列表,可以通過用 @tvm.testing.parametrize_targets('target_1', 'target_2', ...) 修飾同時接受 target 或 dev 作為函數參數來顯式地完成。
該函數將為列出的每個 target 都運行一遍,並單獨報告每個 target 的運行結果(成功/失敗)。如果一個 target 因為在 config.cmake 中被禁用而無法運行,或者因為沒有合適的硬件存在,那麼這個 target 將被報告為跳過。
# 顯式列出要使用的 target
@tvm.testing.parametrize_target('llvm', 'cuda')
def test_function(target, dev):
# 測試代碼寫在這裏
對於在所有 target 上都能正常運行的測試,可以省略裝飾器。任何接收 target 或 dev 參數的測試,都將自動在 TVM_TEST_TARGETS 指定的所有 target 上進行參數化。參數化為每個 target 提供了相同的成功/失敗/跳過報告,同時允許輕鬆擴展測試套件,以覆蓋額外的 target。
# 隱式參數化以運行在所有 target 上
# 在環境變量 TVM_TEST_TARGETS 裏
def test_function(target, dev):
# 測試代碼寫在這裏
@tvm.testing.parametrize_targets 也可以用作裸裝飾器(bare decorator)來顯式地進行參數化,但沒有額外的效果。
# 隱式參數化以運行在所有 target 上
# 在環境變量 TVM_TEST_TARGETS 裏
@tvm.testing.parametrize_targets
def test_function(target, dev):
# 測試代碼寫在這裏
可以使用 @tvm.testing.exclude_targets 或 @tvm.testing.known_failing_targets 裝飾器,將特定 target 排除或標記為預期失敗。更多信息,請參閲文檔字符串。
在某些情況下,可能需要跨多個參數進行參數化。例如,可能存在一些待測試的 target-specific 實現方法,其中一些 target 的實現方法還不止一個。這可以通過顯式地參數化參數元組來完成,如下所示。在這種情況下,只有顯式地列出的 target 會運行,但它們仍會應用適當的 @tvm.testing.requires_RUNTIME 標記。
pytest.mark.parametrize('target,impl', [
('llvm', cpu_implementation),
('cuda', gpu_implementation_small_batch),
('cuda', gpu_implementation_large_batch),
])
def test_function(target, dev, impl):
# 測試代碼寫在這裏
參數化功能是在 pytest 標記之上實現的。每個測試函數都可以用 pytest 標記 裝飾以包含元數據。最常用的標記如下:
@pytest.mark.gpu- 將函數標記為使用 GPU 功能。這本身是沒有效果的,但可以與命令行參數-m gpu或-m 'not gpu'搭配使用,從而限制 pytest 要執行哪些測試。這不應該單獨調用,而應該是單元測試中使用的其他標記的一部分。@tvm.testing.uses_gpu- 應用@pytest.mark.gpu。用於標記可能使用 GPU 的單元測試(如果有)。只有在顯式循環tvm.testing.enabled_targets()的測試中,才需要這個裝飾器,不過這已經不是編寫單元測試的首選方法了(見下文)。使用tvm.testing.parametrize_targets()時,此裝飾器對於 GPU target 是隱式的,不需要顯式地應用。@tvm.testing.requires_gpu- 應用@tvm.testing.uses_gpu,如果沒有 GPU,還要標記這個測試應該被跳過(@pytest.mark.skipif)。@tvfm.testing.requires_RUNTIME- 幾個裝飾器(例如@tvm.testing.requires_cuda),如果指定 runtime 不可用,每個裝飾器都會跳過測試。runtime 如果在config.cmake中被禁用,或是不存在兼容設備時,則該 runtime 不可用。對於使用 GPU 的 runtime,包含@tvm.testing.requires_gpu。
使用參數化 target 時,每個測試運行都是用跟正在使用的 target 相對應的 @tvm.testing.requires_RUNTIME 修飾的。因此,如果某個 target 在 config.cmake 中被禁用,或沒有合適的硬件可以運行,它將被顯式列為跳過。
還有 tvm.testing.enabled_targets(),根據環境變量 TVM_TEST_TARGETS、構建配置和存在的物理硬件,返回所有在當前機器上啓用和可運行的 target。大多數當前測試顯式循環是 enabled_targets() 返回 target,但它無法應用於新測試。這種類型的 pytest 輸出會自動跳過在 config.cmake 中禁用,或者沒有運行設備的 runtime。此外,測試會在第一個失敗的 target 上停止,這對於判斷錯誤是發生在某一個還是所有 target 上,都很困難。
# 老的風格, 已棄用。
def test_function():
for target,dev in tvm.testing.enabled_targets():
# 測試代碼寫在這裏
本地運行
要在本地運行 Python 單元測試,可以使用 ${TVM_HOME} 目錄中的命令 pytest。
-
環境變量
-
TVM_TEST_TARGETS應該是一個用分號分隔的待運行 target 列表。如果未設置,默認是tvm.testing.DEFAULT_TEST_TARGETS中定義的 target。注意:如果
TVM_TEST_TARGETS不包含任何已啓用且具有該類型可訪問設備的 target,則測試將回退到僅在llvmtarget 上運行。 TVM_LIBRARY_PATH應該是libtvm.so庫的路徑。例如,這可以用來藉助調試版本運行測試。如果未設置,將搜索相對於 TVM 源目錄的libtvm.so。
-
-
命令行參數
- 傳遞文件夾或文件的路徑,將僅在該文件夾或文件中運行單元測試。這一點很實用,例如,避免在未安裝特定前端的系統上,運行位於
tests/python/frontend中的測試。 -
-m參數僅運行帶有特定 pytest 標記的單元測試。最常見的用法是使用m gpu僅運行標有@pytest.mark.gpu的測試,並使用 GPU 運行。通過傳遞m 'not gpu',它也可以用於僅運行不使用 GPU 的測試。注意:此過濾發生在基於
TVM_TEST_TARGETS環境變量選定 target 之後。即使指定了-m gpu,如果TVM_TEST_TARGETS不包含 GPU target,也不會運行任何 GPU 測試。
- 傳遞文件夾或文件的路徑,將僅在該文件夾或文件中運行單元測試。這一點很實用,例如,避免在未安裝特定前端的系統上,運行位於
在本地 Docker 容器中運行
與在 CI 中的用法類似,docker/bash.sh 腳本可用於在同一 Docker 鏡像中運行單元測試。第一個參數應指定要運行的 Docker 鏡像(例如 docker/bash.sh ci_gpu)。允許的鏡像名稱在位於 TVM 源目錄的 Jenkinsfile 頂部定義,並映射到 tlcpack 中的鏡像。
如果沒有給出額外的參數,Docker 鏡像將被載入一個交互式 bash 會話。如果腳本作為可選參數傳遞(例如 docker/bash.sh ci_gpu tests/scripts/task_python_unittest.sh),則該腳本將在 Docker 鏡像中執行。
注意:Docker 鏡像包含所有系統依賴項,但不包括這些系統的 build/config.cmake 配置文件。 TVM 源目錄用作 Docker 鏡像的主目錄,因此這將默認使用與本地配置相同的 config/build 目錄。一種解決方案是單獨維護 build_local 和 build_docker 目錄,並在進入/退出 Docker 時,創建從 build 到相應文件夾的符號鏈接。
在 CI 中運行
CI 中的所有內容都從 Jenkinsfile 中的任務定義開始的。這包括定義使用哪個 Docker 鏡像,編譯時配置是什麼,以及哪些階段都各自包含哪些測試。
-
Docker 鏡像
Jenkinsfile 的每個任務(例如 'BUILD: CPU')都會調用
docker/bash.sh。調用 docker/bash.sh 後面的參數定義了 CI 中的 Docker 鏡像,就本地類似。 -
Compile-time 配置
Docker 鏡像沒有內置
config.cmake文件,因此這是每個BUILD任務的第一步。這一步是使用tests/scripts/task_config_build_*.sh腳本完成的。使用哪個腳本取決於正在測試的構建,還需要在 Jenkinsfile 中指定。每個
BUILD任務都以打包一個供以後測試使用的庫而結束。 -
運行哪些測試
Jenkinsfile 的
Unit Test和Integration Test階段決定了如何調用pytest。每個任務都是先解壓一個編譯庫,這個庫在先前的BUILD階段已經編譯過了。接下來運行測試腳本(如tests/script/task_python_unittest.sh)。這些腳本可以設定文件/文件夾,以及傳遞給pytest的命令行選項。其中一些腳本包含
-m gpu選項,該選項將測試限制為僅運行包含@pytest.mark.gpu標記的測試。