境配置到踩坑排錯的實戰(zhàn)經(jīng)驗)
大概兩年前我第一次接觸TVM的時候光是環(huán)境搭建就折騰了整整一個周末當時差點勸退。后來陸續(xù)給同事、學生在不同機器上裝了十幾次把踩過的坑一條條記錄下來才慢慢摸清楚這個框架的脾氣。這篇博客就是我這些次安裝經(jīng)驗的完整匯總涵蓋了從代碼拉取、編譯配置到Python環(huán)境調(diào)試的全部過程按順序操作基本能一次過希望對第一次接觸TVM的讀者有實際幫助。1. 寫在前面TVM到底是個什么東西為什么安裝這么講究先說清楚TVM是干嘛的。它全稱是Apache TVM本質(zhì)上是一個深度學習編譯器棧解決的問題是“模型寫好了但跑不快”。它接收PyTorch、TensorFlow、ONNX等格式的訓練模型經(jīng)過計算圖優(yōu)化、算子調(diào)度和代碼生成之后輸出能在CPU、GPU、FPGA甚至專用加速器上高效運行的機器碼。簡單說你把一個訓練好的模型交給TVM它幫你針對具體硬件狠狠優(yōu)化一把推理速度往往比原框架直接跑要快不少。那為什么安裝環(huán)節(jié)這么容易翻車原因在于TVM的項目結(jié)構(gòu)比較特別它既有C寫的核心編譯引擎又要通過Python前端來調(diào)用安裝過程至少涉及編譯工具鏈、LLVM依賴、GPU驅(qū)動和Python包管理這四套體系。任何一環(huán)版本不匹配后面跑起來就是一堆莫名其妙的問題。而且TVM本身更新節(jié)奏很快幾乎每個月都有新改動老版本經(jīng)常遇到“某個依賴庫升了級舊版TVM就不兼容了”的情況。所以你會看到我在標題里反復強調(diào)一個建議軟件版本盡量用最高的、最新的。這不只是懶人思路而是TVM這個項目確實對“落后版本”很不友好。舊版本不僅API和最新的Python、LLVM、CUDA對不上連官方文檔里的示例代碼都可能跑不通。用最新版本社區(qū)踩過的坑相對少搜問題也能搜到更多近期有效的答案。這篇文章適合誰來參考主要是兩類人一是科研和工程場景中需要部署模型、做推理加速的開發(fā)者二是想搞懂TVM編譯流程的學生。文章會從一個干凈的Linux環(huán)境開始講到如何完整地把TVM源碼版裝好并給出我在實操中遇到過的經(jīng)典報錯和排查方法。2. 安裝前的環(huán)境評估與版本選型思路2.1 先摸清自己的硬件和系統(tǒng)底細有一句老話叫做“TVM本身不挑機器但你機器上已有的環(huán)境一定挑TVM的版本”。動手之前先把下面幾項信息查清楚記錄下來。這幾條命令在Ubuntu/Debian系的系統(tǒng)上直接執(zhí)行即可。# 查看操作系統(tǒng)版本 cat /etc/os-release # 查看CPU信息 lscpu | grep Model name # 查看GPU型號與驅(qū)動版本 nvidia-smi # 查看顯卡驅(qū)動支持的CUDA版本 nvidia-smi | head -20 # 查看當前gcc、g版本 gcc --version g --version # 查看cmake版本 cmake --version # 查看python版本 python3 --version以我實機為例常用的一套環(huán)境是Ubuntu 22.04 GCC 11.4 CMake 3.22 Python 3.10 CUDA 12.2最后TVM在0.16及以上版本跑得都很順。如果你用的是Ubuntu 20.04 Python 3.8 GCC 9也完全能裝但建議把CMake至少升到3.16以上太老的CMake不支持TVM構(gòu)建腳本里一些新語法。這里想強調(diào)一個點不要為了“穩(wěn)定”故意用舊版本編譯器。TVM的大量代碼依賴較新的C17標準GCC版本低于7基本沒戲GCC 8、9能編但有機會報奇怪的模板錯誤。我在Ubuntu 18.04的機器上踩過GCC 7.5編譯TVM 0.14時模板實例化失敗的坑后來升級到GCC 9才解決。所以標題里“版本盡量新”這一條第一步就體現(xiàn)在系統(tǒng)編譯器上。2.2 依賴組件哪些是必裝、哪些是可以先跳過的TVM的完整依賴列表很長但對我們大多數(shù)用途來說真正必須的沒有想象中那么多。我按“不裝一定跑不了”“不裝也能跑但性能受限”“完全可選”三檔列一下依賴項必裝程度說明CMake必要構(gòu)建系統(tǒng)的核心版本建議3.16以上GCC/G必要C編譯器用于編譯TVM的C核心Python 3必要TVM的Python前端依賴3.7以上均可LLVM強烈推薦用于CPU代碼生成優(yōu)化有它性能好非常多CUDA Toolkit可選但推薦如果你要用NVIDIA GPU加速則必裝cuDNN可選某些GPU算子的加速庫非必須Vulkan/OpenCL可選特定硬件后端需要日??梢韵炔谎bNode.js不需要那是TVM前端網(wǎng)頁版才用的東西這里重點說LLVM。很多教程會告訴你“USE_LLVM那里的路徑可以留空”意思是TVM能在沒有LLVM的情況下照常編譯。這話沒錯但代價很大——沒有LLVM后端TVM在CPU上的代碼生成能力會被砍掉一大截很多優(yōu)化沒法做甚至部分模型直接編譯失敗。所以我的建議是LLVM必須裝而且盡量新版。安裝LLVM最簡單的方式是用apt直接裝sudo apt update sudo apt install llvm-18 llvm-18-dev clang-18如果系統(tǒng)源里沒有LLVM 18可以先去apt源看有哪些版本apt-cache search llvm再選擇。理論上LLVM 10以上都能配TVM但LLVM 15以下可能會遇到底層API變化導致的告警甚至編譯錯誤。實測LLVM 17、18都很穩(wěn)。2.3 為什么我堅持建議“版本盡量新”——一條血的教訓可能有人覺得我在偷懶遇事不決就讓人升級。這里分享一個親身經(jīng)歷有一次我在一臺服務器上裝TVM 0.12當時的最新版配的是LLVM 10和GCC 9編一次花了快40分鐘最后跑模型的時候報了一個LLVM代碼生成器的內(nèi)部錯誤隨機出現(xiàn)在某些算子上。找遍GitHub issues也沒看到完全一致的報錯后來我干了一件笨事把TVM升到0.14同樣的模型同樣的硬件同樣的配置一次通過。事后分析原因是TVM低版本某個C代碼里用到了LLVM的某個接口那個接口在LLVM 10上有邊界情況沒有處理干凈高版本修復了編譯器端的問題TVM新版本又適配了新LLVM的接口兩層疊加就順理成章了。從那以后我養(yǎng)成了一個習慣裝TVM之前先檢查一遍所有依賴組件有沒有自己能升到的最新穩(wěn)定版能升就升升完再編譯TVM。這其實幫我在后續(xù)的部署里省下了大量排查時間。3. 從源碼編譯TVM的完整實操流程3.1 拉取代碼別漏了子模塊這是第一道坑TVM的代碼托管在GitHub上官方源碼倉庫是apache/tvm。編譯源碼版的第一步當然是clone倉庫但這里就藏著第一道最常見的坑。直接用下面這行命令是不夠的git clone https://github.com/apache/tvm.git為什么不夠因為TVM依賴幾個外部子模塊包括dmlc-core核心工具庫、dlpack張量數(shù)據(jù)交換協(xié)議和rang第三方頭文件庫等這些不會跟著主倉庫自動下載。如果漏了子模塊編譯的時候會報“找不到dmlc/core/io.h”之類的錯誤或者CMake配置階段直接失敗。正確命令是加一個--recursive參數(shù)git clone --recursive https://github.com/apache/tvm.git如果你已經(jīng)用了不帶參數(shù)的方式clone了也不用重新來一遍在倉庫根目錄執(zhí)行下面的命令即可補救git submodule init git submodule update --recursive需要提醒的是子模塊拉取過程中網(wǎng)絡不穩(wěn)定很容易失敗失敗后繼續(xù)操作可能導致子模塊目錄為空。建議拉取完成后檢查一下3rdparty/dmlc-core/include/dmlc目錄下是否有io.h文件存在有才說明沒問題。另外我個人習慣clone之后切到一個具體的release tag而不是直接留在master分支上。TVM的master分支是開發(fā)版雖然功能最新但偶爾會有API調(diào)整帶來的不穩(wěn)定性。我用release分支更穩(wěn)cd tvm git checkout v0.17.0 # 如果提示子模塊版本不匹配再執(zhí)行一次 git submodule update --recursive如果你是在官方發(fā)布某個版本后才clone的默認的master分支就是最新代碼也可以直接用問題不大。3.2 編譯配置config.cmake里最關(guān)鍵的幾個開關(guān)進入TVM倉庫的根目錄創(chuàng)建一個build目錄存放編譯配置和中間產(chǎn)物。官方的默認配置會從cmake/config.cmake復制過來但默認配置里LLVM是關(guān)閉的所以必須手動改。cd tvm mkdir -p build cp cmake/config.cmake build/然后用文本編輯器打開build/config.cmake重點檢查并修改下面這幾處# 找到這行將OFF改成ON這樣LLVM后端才會開啟 set(USE_LLVM OFF) # 改成類似下面這樣具體路徑以你系統(tǒng)里的llvm-config為準 set(USE_LLVM /usr/lib/llvm-18/bin/llvm-config) # 如果要用NVIDIA GPU把下面這行改為ON set(USE_CUDA OFF) # 改成 set(USE_CUDA ON) # 建議把編譯類型設為Release這會開啟編譯優(yōu)化TVM自身的運行性能顯著提升 set(CMAKE_BUILD_TYPE Release)這里重點聊一下USE_LLVM的設置方式。很多教程寫的是“l(fā)lvm-config -prefix”之類的自動探測其實你直接把llvm-config的完整路徑填進去就行TVM的CMake腳本會自動調(diào)用它獲取編譯參數(shù)。前提是你要裝了llvm-dev相關(guān)的包否則只有l(wèi)lvm-config沒有開發(fā)頭文件也是白搭。我自己在探究LLVM版本兼容性時發(fā)現(xiàn)TVM官方對LLVM版本的支持范圍其實相當寬LLVM 12到18都認但舊TVM配新LLVM或者新TVM配舊LLVM都會有些邊緣問題。所以最省心的組合是新TVM 新LLVM。比如TVM 0.16以上配LLVM 17或18是當下最穩(wěn)的組合。CUDA那個開關(guān)如果開了CMake會自動去找系統(tǒng)中已安裝的CUDA Toolkit如果找到的版本不對可以在配置里手動指定CUDA路徑set(USE_CUDA /usr/local/cuda)如果機器上沒有NVIDIA顯卡這一項保持OFF即可不用糾結(jié)。開CUDA之后編譯時間會變長這個要有心理準備。3.3 編譯與Python包安裝等一條命令跑完配置改好后開始正式的編譯構(gòu)建。在build目錄下執(zhí)行下面的命令cd build cmake .. make -j$(nproc)nproc會讀取當前機器的CPU核數(shù)-j參數(shù)的作用是并行編譯這樣可以大幅縮短編譯時間。但也不建議在低配機器上無腦用所有核內(nèi)存不夠會導致編譯中途被系統(tǒng)殺掉。我的經(jīng)驗是8核16G內(nèi)存的機器用-j6或-j8都沒問題2核4G的輕量服務器用-j2就好穩(wěn)妥第一。整個編譯過程少則十幾分鐘多則四五十分鐘取決于機器的CPU性能和是否開啟了CUDA、Vulkan等冗余后端。期間屏幕上會刷大量C編譯輸出看到[100%]或者Built target tvm之類的字樣就說明編譯完成了。編譯完成后TVM的核心C庫會在build/目錄生成libtvm.so和libtvm_runtime.so這兩個文件Python前端需要找到這個庫才能工作。接著安裝Python包裝包cd .. pip install python/tvm # 或者如果你想以開發(fā)模式安裝改動代碼即時生效 # pip install -e python/tvm這里有一個非常常見的坑pip install和pip install -e的區(qū)別。普通安裝是把TVM的Python代碼復制到site-packages里之后你修改源碼里的Python文件不會生效開發(fā)模式-e則是建立一個軟鏈接代碼改動立即生效。做深度學習模型開發(fā)的人通常用普通安裝就夠了但如果想改TVM源碼做研究就用開發(fā)模式。還有一個依賴需要注意TVM的Python包運行需要numpy、decorator、attrs、typing_extensions、psutil、scipy等庫。pip install python/tvm的時候這些依賴一般會自動裝上但如果你用了--no-deps或者系統(tǒng)里存在版本沖突就需要手動逐個補。我最常碰到的是numpy版本問題比如系統(tǒng)里已有一個numpy 1.xTVM新版本要求numpy 2.x解決方式很簡單統(tǒng)一升級到numpy 2.x即可但要注意個別其他庫可能還不兼容numpy 2.x這需要權(quán)衡。3.4 快速驗證如何判斷TVM真的裝好了裝好以后不要急著跑模型先做一個最基礎(chǔ)的健康檢查。打開Python終端輸入下面的代碼import tvm print(tvm.__version__) print(tvm.__file__)能打印出版本號和源碼路徑說明Python包和核心庫的動態(tài)鏈接已經(jīng)通了。這時候可以再試一次簡單的張量操作驗證編譯鏈路是否正常import tvm from tvm import te n 1024 A te.placeholder((n,), nameA) B te.compute((n,), lambda i: A[i] * 2, nameB) s te.create_schedule(B.op) f tvm.build(s, [A, B], targetllvm) print(build succeeded)如果tvm.build沒有報錯說明LLVM后端已經(jīng)被正確調(diào)用TVM已經(jīng)能夠完成一個完整的編譯流程這是整個安裝成功的最有力證據(jù)。4. 安裝Python前端tvmc和上層工具鏈之間的關(guān)系4.1 tvmc是什么要不要單獨裝在TVM源碼倉庫里python/tvm/driver/目錄下有一個叫tvmc的命令行工具全稱是TVM Command Line也就是TVM的“命令行用戶界面”。它允許你通過命令行的方式完成模型編譯和調(diào)優(yōu)而不用寫Python代碼。安裝TVM的同時tvmc就會一并裝好。驗證方式tvmc --help如果能打印出幫助信息說明命令行通道已經(jīng)打通。tvmc支持很多子命令比如tvmc compile編譯模型、tvmc run運行模型、tvmc tune自動調(diào)優(yōu)等熟練之后能極大提升工作效率不需要每次都進入Python交互環(huán)境。不過在日常開發(fā)里我更習慣直接用Python API。因為TVM的大部分高級功能比如Relay IR的操作、AutoTVM的配置調(diào)整還是Python接口寫得比較完整命令行工具適合快速驗證但不適合做深度定制。4.2 用TVM加載一個ONNX模型走一遍完整推理鏈裝完之后最關(guān)心的問題就是“能不能直接用”。這里我分享一個最簡單的完整鏈路示例把PyTorch的模型先導出成ONNX再用TVM編譯推理import torch import torchvision.models as models import onnx from tvm import relay # 以ResNet18為例 model models.resnet18(pretrainedFalse) model.eval() # 導出ONNX dummy_input torch.randn(1, 3, 224, 224) torch.onnx.export(model, dummy_input, resnet18.onnx, opset_version11) # 加載ONNX到TVM onnx_model onnx.load(resnet18.onnx) input_name input.1 shape_list [(input_name, (1, 3, 224, 224))] mod, params relay.frontend.from_onnx(onnx_model, shape_list) # 編譯目標為LLVM target llvm with tvm.transform.PassContext(opt_level3): lib relay.build(mod, targettarget, paramsparams) print(compile done)這段代碼把整個流程串了一遍從ONNX前端解析Relay表達式到Relay IR優(yōu)化再到后端代碼生成。如果這一步能順利完成說明TVM的編譯管線是完全通的。后面的模型推理只是把編譯后的lib加載到runtime再跑問題不大。有的讀者可能在這一步遇到“ONNX包沒有安裝”的報錯直接用pip install onnx即可不用特意指定版本。需要注意的倒是PyTorch和TVM版本之間的兼容性老版本TVM對PyTorch新導出的ONNX算子支持可能不完整這又是一個“用新版TVM更省心”的理由。5. 踩坑實錄與排查技巧這幾類問題占了我裝機問題的八成安裝過程中最磨人的往往不是大方向出錯而是一些看起來不起眼的小問題。這里我把過去踩過的坑做成一個表格方便大家快速對照。表格后面挑三個最典型的問題單獨展開講。錯誤現(xiàn)象根本原因解法提示找不到dmlc/io.hclone時漏了子模塊git submodule update --recursiveCMake階段提示找不到llvm-configLLVM開發(fā)包沒裝apt install llvm-18 llvm-18-devimport tvm后提示找不到.so文件Python包和動態(tài)庫不在同一路徑確認編譯生成的libtvm.so在build/下且環(huán)境變量PYTHONPATH沒覆蓋tvm.build報“LLVM version mismatch”LLVM相關(guān)頭文件與庫文件版本不一致完整升級LLVM到同一版本清理build目錄重新配置編譯過程中內(nèi)存不足被kill并行編譯任務太多降低-j參數(shù)比如-j2Python運行時報numpy版本不滿足TVM新版本與舊numpy不兼容pip install -U numpy啟用CUDA后編譯報錯找不到cuda_runtime.h系統(tǒng)CUDA未安裝或PATH未設置安裝CUDA Toolkit配置/usr/local/cuda路徑TVM運行時提示“Cannot find libtvm_runtime.so”runtime庫路徑未加入LD_LIBRARY_PATH在.bashrc中導出LD_LIBRARY_PATH/path/to/tvm/build:$LD_LIBRARY_PATH5.1 找不到LLVM一半以上的安裝失敗都栽在這里我見過太多人在CMake階段卡住報錯類似于Could NOT find LLVM (missing: LLVM_CONFIG_EXECUTABLE)這個問題的第一反應不要是去改配置而是先確認系統(tǒng)里到底裝沒裝LLVM開發(fā)版。llvm-config --version如果提示找不到命令說明連基礎(chǔ)LLVM都沒裝。這時候執(zhí)行sudo apt install llvm-18 llvm-18-dev如果llvm-config存在但版本打印出來是10、11之類的舊版我依然建議升級到LLVM 17以上的版本。舊版LLVM不僅可能觸發(fā)TVM編譯告警更關(guān)鍵的是代碼生成質(zhì)量不如新版。在CPU推理這種性能敏感的場景下用新版LLVM調(diào)優(yōu)后的代碼可能比舊版快5%到15%。裝好之后在config.cmake里寫set(USE_LLVM /usr/lib/llvm-18/bin/llvm-config)注意路徑要以llvm-config結(jié)尾而不是填一個目錄。這是CMake的約定很多新手會在這里填成/usr/lib/llvm-18導致CMake依然找不到LLVM。5.2 編譯成功了但運行模型時各種“內(nèi)部錯誤”的排查思路編譯通過不等于萬事大吉運行時錯誤同樣讓人頭疼。比如我遇到過這樣的報錯Check failed: ret 0: TVMError: Internal compiler error這種問題有兩種常見來源。第一類是TVM自身的bug尤其是在舊版本上遇到新LLVM的情況第二類是GPU相關(guān)算子在CUDA版本不匹配時的內(nèi)核編譯失敗。排查思路按順序做先確認LLVM版本。在Python里執(zhí)行print(tvm.target.codegen.llvm_version())看看TVM實際檢測到的LLVM版本如果和llvm-config --version對不上就是要重新編譯TVM的信號。清理build目錄重新編譯。不要在原build目錄上直接覆蓋把build文件夾整個刪掉重新cp cmake/config.cmake build/再做一遍能排除舊配置緩存干擾。用官方的最小復現(xiàn)代碼測試。比如跑到GitHub上找對應版本文檔里的示例排除是自己代碼的問題。如果還不行去GitHub Issues搜原話報錯。TVM社區(qū)很活躍搜到相同問題基本就搜到了答案。我遇到過的最折騰的一次是TVM 0.13配合LLVM 15時編譯模型老觸發(fā)一個很小概率的Segment Fault最后解決辦法是升級TVM到0.16問題徹底消失。所以遇到這種編譯內(nèi)部錯誤最快速的解法往往是“升級到最新TVM版本重新試一把”而不是苦找根源除非你有必須鎖定舊版本的理由。5.3 版本兼容性的體系化檢查一條命令幫你看清全局既然強調(diào)了一路的“新版本原則”最后分享一個我自用的小腳本可以把TVM及其關(guān)鍵依賴的版本一次性打印出來排查版本沖突時特別有用import tvm import sys print(Python version:, sys.version) print(TVM version:, tvm.__version__) try: print(LLVM version:, tvm.target.codegen.llvm_version()) except Exception as e: print(LLVM version detection failed:, e) import numpy print(NumPy version:, numpy.__version__) import torch print(PyTorch version:, torch.__version__)如果LLVM version打印出來是空或者報錯那大概率LLVM沒配好。PyTorch版本如果不是2.x建議升級因為舊版PyTorch導出的ONNX模型在TVM的新版前端上偶發(fā)兼容問題。6. 再補充一些個人建議與經(jīng)驗細節(jié)6.1 要不要用Docker鏡像來省事海外用戶使用官方Docker鏡像很方便但有些地區(qū)網(wǎng)絡拉取鏡像會遇到困難加上Docker里掛載GPU還需要額外配置對新手來說門檻并不低。我的觀點是如果只是學習驗證直接用源碼編譯一次也好能幫你理解整個依賴關(guān)系如果是多臺機器重復部署Docker方案效率會高很多。Docker方案也有一個坑鏡像里的LLVM、CUDA版本和你宿主機上的驅(qū)動版本可能不匹配跑模型時依然會有各種莫名其妙的問題。除非你用了與宿主機驅(qū)動完全匹配的鏡像版本否則真不如老老實實在物理機里編譯一次干凈。6.2 雙環(huán)境隔離多版本Python會讓排查難度翻倍在裝TVM之前我建議用conda創(chuàng)建一個全新的虛擬環(huán)境隔離系統(tǒng)自帶的Pythonconda create -n tvm python3.10 conda activate tvm這樣即使系統(tǒng)的Python環(huán)境被其他項目搞得亂七八糟TVM這一套東西也不會受影響。尤其在服務器上多環(huán)境共存的場景十分常見虛擬環(huán)境能幫你少掉一半的報錯。6.3 最后一個建議把常見命令存成一個變量省點時間如果你需要經(jīng)常在TVM的build目錄和python目錄之間切換可以像我一樣把下面的變量寫進~/.bashrcexport TVM_HOME~/tvm export PYTHONPATH$TVM_HOME/python:$TVM_HOME/build:$PYTHONPATH export LD_LIBRARY_PATH$TVM_HOME/build:$LD_LIBRARY_PATHPYTHONPATH幫Python找到TVM的Python前端LD_LIBRARY_PATH幫動態(tài)鏈接器找到libtvm.so。這兩個如果缺了即使編譯成功后面也會出現(xiàn)導入或運行時找不到庫的錯誤。我自己實際操作中發(fā)現(xiàn)很多人在這一步漏了LD_LIBRARY_PATH的配置導致模型編譯明明是好的一運行就說找不到libtvm_runtime.so。這個問題排查起來一點都不難但第一次遇到時確實很困惑。7. 個人踩坑后的一點真心話整個TVM安裝過程說到底是和環(huán)境打交道的過程。如果以前沒接觸過C編譯鏈、動態(tài)庫、CMake這些底層概念第一次會被各種報錯整得有些頭大。但這些報錯其實都在做同一件事告訴你某個環(huán)節(jié)的版本對不上。你只要記住一個核心原則——盡量使用最新的穩(wěn)定版本能省下至少一半的排查時間。為什么這么說因為TVM這種快速迭代的項目新版本會持續(xù)修復編譯器與外部庫的兼容問題也會跟進最新的第三方依賴接口。追新版本不等于盲目而是在這個特定項目上性價比最高的選擇。按照這篇文章的步驟操作理論上從零到跑通一個模型半個工作日應該夠用。過程中如果遇到本文沒有覆蓋的報錯先不要慌去GitHub Issues搜索原話大部分情況都能找到解決方案。如果實在搜不到就把完整報錯貼到社區(qū)問通常很快會有人回應。TVM安裝只是入門的第一道坎裝好之后還有更多有趣的東西等著你去探索。祝順利地跨過這道坎接下來就好好享受編譯優(yōu)化帶來的性能提升吧。這篇文章基于個人實際安裝經(jīng)驗整理具體版本號隨時間推移會有變化建議以 TVM官方文檔 發(fā)布的最新安裝指南為準。