境搭建實(shí)戰(zhàn):源碼編譯避坑與依賴版本選型指南)
1. 為什么今天還要折騰Caffe環(huán)境如果你最近打算復(fù)現(xiàn)一篇老論文或者接手了一個(gè)遺留的深度學(xué)習(xí)項(xiàng)目打開代碼倉庫發(fā)現(xiàn)里面全是.prototxt和.solver文件那你大概率繞不開Caffe。這東西在深度學(xué)習(xí)框架的上古時(shí)代幾乎是統(tǒng)治級(jí)的存在雖然現(xiàn)在大家張口閉口都是PyTorch和TensorFlow但工業(yè)界很多已經(jīng)落地跑了好幾年的模型底層依然是Caffe訓(xùn)練出來的。我身邊就有做安防和工業(yè)質(zhì)檢的朋友產(chǎn)線上的模型到現(xiàn)在還在用Caffe推理原因很簡(jiǎn)單穩(wěn)定、快、部署鏈路成熟。但問題也恰恰出在這里。Caffe的黃金時(shí)代是2014到2017年那個(gè)年代的編譯環(huán)境、依賴版本、CUDA版本跟現(xiàn)在完全不是一回事。你拿一臺(tái)2024年新裝的機(jī)器照著當(dāng)年的教程一步步敲大概率會(huì)在某個(gè)依賴報(bào)錯(cuò)上卡住半天。我自己第一次搭Caffe環(huán)境的時(shí)候光是protobuf版本沖突就折騰了整整一個(gè)下午最后發(fā)現(xiàn)是系統(tǒng)自帶的版本和Caffe要求的版本打架。所以這篇內(nèi)容的核心目標(biāo)很明確幫你在當(dāng)前環(huán)境下用最少的彎路把Caffe跑起來。我會(huì)從依賴選型講起把每個(gè)關(guān)鍵決策背后的原因說清楚然后給出經(jīng)過實(shí)測(cè)的完整步驟最后把幾個(gè)高頻踩坑點(diǎn)單獨(dú)拎出來講。適合兩類人看一是需要復(fù)現(xiàn)老項(xiàng)目但沒接觸過Caffe的開發(fā)者二是以前用過Caffe但換機(jī)器后重新搭建時(shí)被環(huán)境問題卡住的人。整個(gè)過程我會(huì)盡量用為什么這么做來解釋每一步而不是甩一堆命令讓你照抄。需要提前說明的是Caffe的安裝方式有好幾種——源碼編譯、Docker鏡像、conda安裝。這三種我都試過各有各的適用場(chǎng)景后面會(huì)詳細(xì)對(duì)比。本文主要走源碼編譯這條路因?yàn)樗抢斫釩affe依賴關(guān)系最透徹的方式也是遇到問題時(shí)最容易排查的方式。如果你只是想快速跑個(gè)推理Docker可能是更省事的選擇我也會(huì)給出對(duì)應(yīng)的思路。2. 編譯之前必須想清楚的幾件事2.1 CPU版還是GPU版這個(gè)決定影響后面所有步驟很多人一上來就問怎么裝Caffe但更該先問的是我要用CPU還是GPU。這個(gè)選擇直接決定了你的依賴清單、編譯參數(shù)和后續(xù)能不能跑訓(xùn)練。GPU版需要CUDA和cuDNN編譯出來的Caffe可以利用顯卡做并行計(jì)算訓(xùn)練速度比CPU快幾十倍甚至上百倍。但代價(jià)是CUDA版本和Caffe的兼容性非常挑剔。Caffe官方最后活躍的版本大概支持到CUDA 8到9左右如果你機(jī)器上裝的是CUDA 11或12直接編譯大概率會(huì)報(bào)一堆API不匹配的錯(cuò)誤。我實(shí)測(cè)下來CUDA 8.0配合cuDNN 5或6是最穩(wěn)的組合但這個(gè)版本的CUDA在新顯卡上又裝不了因?yàn)樾嘛@卡的驅(qū)動(dòng)要求更高版本的CUDA。這就形成一個(gè)尷尬的局面新機(jī)器裝不了老CUDA老CUDA配不了新顯卡。所以如果你用的是比較新的顯卡比如30系、40系我的建議是先老老實(shí)實(shí)裝CPU版把流程跑通確認(rèn)代碼邏輯沒問題之后再考慮用Docker或者找一臺(tái)老機(jī)器來跑GPU訓(xùn)練。CPU版雖然慢但編譯過程干凈不會(huì)因?yàn)镃UDA的各種版本問題把你勸退。判斷方法很簡(jiǎn)單打開終端敲nvcc --version如果有輸出說明裝了CUDA再看看版本號(hào)。如果版本在9以下可以嘗試GPU編譯如果是10以上建議直接走CPU路線或者Docker路線。2.2 依賴庫的版本選擇為什么最新版往往是坑Caffe的依賴清單不算長(zhǎng)但每一個(gè)都有版本講究。我列一個(gè)表把關(guān)鍵依賴和推薦版本說清楚依賴庫作用推薦版本踩坑說明protobuf定義網(wǎng)絡(luò)結(jié)構(gòu)的數(shù)據(jù)格式2.6.1 或 3.6.x3.0以上某些API有變化容易編譯報(bào)錯(cuò)boost提供C基礎(chǔ)庫支持1.65以下1.70以上部分頭文件路徑變了glog日志記錄0.3.5版本要求不嚴(yán)但別用太新的gflags命令行參數(shù)解析2.2.2相對(duì)穩(wěn)定hdf5數(shù)據(jù)存儲(chǔ)格式支持1.8.x 或 1.10.x1.12以上有API變動(dòng)opencv圖像處理3.x4.x的某些接口不兼容BLAS矩陣運(yùn)算加速OpenBLAS或MKL選一個(gè)就行別同時(shí)裝這張表里的推薦版本不是我隨便寫的是反復(fù)編譯失敗后總結(jié)出來的。核心邏輯是Caffe的代碼寫于那個(gè)年代它調(diào)用的API就是那個(gè)年代的API。你用最新版的庫頭文件路徑變了、函數(shù)簽名變了、甚至某些功能被移除了編譯自然過不去。舉個(gè)具體的例子。protobuf從3.0開始把google/protobuf/stubs/common.h里的一些宏定義改了而Caffe的代碼里大量引用了這些宏。你裝個(gè)protobuf 3.20編譯到一半就會(huì)報(bào)未定義的標(biāo)識(shí)符。解決辦法要么是降級(jí)protobuf要么是手動(dòng)改Caffe源碼打補(bǔ)丁。前者省事后者費(fèi)勁我一般選前者。還有一個(gè)容易忽略的點(diǎn)系統(tǒng)自帶的庫和手動(dòng)安裝的庫可能沖突。比如Ubuntu系統(tǒng)里可能預(yù)裝了某個(gè)版本的protobuf你用apt又裝了一個(gè)然后手動(dòng)編譯又裝了一個(gè)最后編譯器找到的是哪個(gè)版本完全看運(yùn)氣。所以我的習(xí)慣是編譯Caffe之前先用apt list --installed | grep protobuf查一下系統(tǒng)里有什么該卸的卸干凈避免多個(gè)版本打架。2.3 三種安裝路線的取舍在動(dòng)手之前先把三條路擺出來對(duì)比一下你根據(jù)自己的情況選源碼編譯最靈活能改代碼、能調(diào)參數(shù)、能精確控制依賴版本。缺點(diǎn)是耗時(shí)長(zhǎng)第一次編譯加上解決依賴問題兩三個(gè)小時(shí)很正常。適合需要深入定制或者要理解Caffe內(nèi)部機(jī)制的人。Docker鏡像最省事拉一個(gè)別人配好的鏡像里面環(huán)境都是現(xiàn)成的。缺點(diǎn)是鏡像質(zhì)量參差不齊有些鏡像里的Caffe版本很老有些缺少GPU支持。適合只想快速跑通、不想折騰環(huán)境的人。Conda安裝通過conda-forge頻道可以裝到Caffe命令就一行。缺點(diǎn)是conda版的Caffe可能缺少某些功能模塊而且和系統(tǒng)里的Python環(huán)境容易沖突。適合做快速原型驗(yàn)證。我個(gè)人的建議是第一次接觸Caffe走源碼編譯。雖然麻煩但走完一遍之后你對(duì)Caffe的依賴關(guān)系、編譯流程、配置文件都會(huì)有清晰的認(rèn)識(shí)后面遇到問題也知道從哪查。如果你已經(jīng)用過Caffe只是換機(jī)器要重新搭那Docker是更高效的選擇。3. 從零開始的完整編譯流程3.1 系統(tǒng)環(huán)境確認(rèn)與基礎(chǔ)依賴安裝我以Ubuntu 18.04為例來走一遍這個(gè)版本是Caffe編譯成功率比較高的。Ubuntu 20.04也可以但有些包的名字變了需要微調(diào)。先確認(rèn)系統(tǒng)版本lsb_release -a然后安裝基礎(chǔ)編譯工具sudo apt-get update sudo apt-get install -y build-essential cmake git pkg-config這幾樣是編譯任何C項(xiàng)目都需要的。build-essential里包含了gcc、g和makecmake用來生成編譯配置pkg-config幫編譯器找到庫文件的位置。接下來裝Caffe的核心依賴。這里我給的命令是經(jīng)過篩選的把版本沖突的風(fēng)險(xiǎn)降到最低sudo apt-get install -y libprotobuf-dev libleveldb-dev libsnappy-dev \ libopencv-dev libhdf5-serial-dev protobuf-compiler sudo apt-get install -y --no-install-recommends libboost-all-dev sudo apt-get install -y libgflags-dev libgoogle-glog-dev liblmdb-dev注意libhdf5-serial-dev這個(gè)包名在有些系統(tǒng)上叫l(wèi)ibhdf5-dev如果報(bào)找不到就換一個(gè)試試。--no-install-recommends是為了避免boost裝一堆用不上的推薦包能省不少空間。BLAS的選擇上我推薦用OpenBLAS安裝簡(jiǎn)單且性能不錯(cuò)sudo apt-get install -y libopenblas-dev如果你有Intel的CPU并且追求極致性能可以換成MKL但MKL的安裝稍微麻煩一些需要去官網(wǎng)下載并配置環(huán)境變量。對(duì)于大多數(shù)場(chǎng)景OpenBLAS夠用了。3.2 獲取Caffe源碼與配置文件調(diào)整源碼從官方倉庫克隆git clone https://github.com/BVLC/caffe.git cd caffe克隆下來之后先別急著編譯。Caffe的編譯配置是通過Makefile.config控制的倉庫里給了一個(gè)示例文件Makefile.config.example你需要復(fù)制一份并修改cp Makefile.config.example Makefile.config然后打開這個(gè)文件逐項(xiàng)檢查。以下是幾個(gè)必須改的地方CPU/GPU開關(guān)如果你走CPU路線把# CPU_ONLY : 1前面的注釋去掉變成CPU_ONLY : 1。這一行決定了編譯時(shí)是否鏈接CUDA庫。BLAS配置找到BLAS : atlas這一行改成BLAS : open因?yàn)槲覀冄b的是OpenBLAS。同時(shí)確認(rèn)BLAS_INCLUDE和BLAS_LIB的路徑指向正確OpenBLAS一般裝在/usr/include和/usr/lib下。Python路徑如果你需要Python接口比如用pycaffe寫腳本要確保Python的include和lib路徑正確。找到PYTHON_INCLUDE和PYTHON_LIB部分根據(jù)你的Python版本調(diào)整。比如Python 3.6的話路徑大概是/usr/include/python3.6m和/usr/lib/x86_64-linux-gnu/libpython3.6m.so。HDF5路徑這是個(gè)高頻坑點(diǎn)。Ubuntu 18.04的HDF5頭文件在/usr/include/hdf5/serial/庫文件在/usr/lib/x86_64-linux-gnu/hdf5/serial/。你需要在Makefile.config里把這兩個(gè)路徑加到INCLUDE_DIRS和LIBRARY_DIRS里。具體做法是找到這兩行在末尾追加路徑INCLUDE_DIRS : $(PYTHON_INCLUDE) /usr/local/include /usr/include/hdf5/serial LIBRARY_DIRS : $(PYTHON_LIB) /usr/local/lib /usr/lib /usr/lib/x86_64-linux-gnu/hdf5/serial這一步不做的話編譯到使用HDF5的模塊時(shí)會(huì)報(bào)找不到hdf5.h。3.3 編譯過程中的參數(shù)調(diào)優(yōu)與并行加速配置改好之后就可以編譯了。但在敲make之前先做一件事確認(rèn)你的CPU核心數(shù)用nproc命令查看。然后編譯時(shí)加上-j參數(shù)來并行編譯能大幅縮短時(shí)間make all -j$(nproc) make test -j$(nproc) make runtest -j$(nproc)make all編譯核心庫和工具make test編譯測(cè)試程序make runtest跑測(cè)試。如果只是自己用make all就夠了后面兩個(gè)可選。但建議至少跑一下make test確保編譯出來的東西是完整的。編譯過程中如果報(bào)錯(cuò)不要慌先看錯(cuò)誤信息的第一行。Caffe的編譯錯(cuò)誤往往是一連串的但根因通常在最開始那幾行。常見的錯(cuò)誤類型和處理方式我整理一下找不到頭文件說明某個(gè)依賴的include路徑?jīng)]配好回到Makefile.config檢查對(duì)應(yīng)的INCLUDE_DIRS。未定義的引用鏈接階段找不到庫文件檢查L(zhǎng)IBRARY_DIRS和LDFLAGS。protobuf相關(guān)錯(cuò)誤大概率是版本沖突用protoc --version確認(rèn)版本必要時(shí)降級(jí)。內(nèi)存不足被殺并行編譯時(shí)如果內(nèi)存不夠系統(tǒng)會(huì)殺掉編譯進(jìn)程。減少-j后面的數(shù)字比如改成-j2。我實(shí)測(cè)在一臺(tái)8核16G的機(jī)器上make all -j8大概需要15到20分鐘。如果超過半小時(shí)還沒完可能是某個(gè)模塊卡住了檢查一下是不是在編譯OpenCV相關(guān)的代碼那部分特別慢。3.4 編譯后的驗(yàn)證怎么確認(rèn)真的裝好了編譯完成后在caffe目錄下會(huì)生成build/文件夾里面有l(wèi)ib/和tools/。驗(yàn)證分三步第一步檢查核心庫文件是否存在ls build/lib/應(yīng)該能看到libcaffe.so、libcaffe.so.1.0.0這些文件。第二步運(yùn)行Caffe自帶的命令行工具./build/tools/caffe如果輸出一堆用法說明說明工具編譯成功了。如果報(bào)error while loading shared libraries說明動(dòng)態(tài)庫路徑?jīng)]配好需要把build/lib加到LD_LIBRARY_PATH里或者直接寫進(jìn)/etc/ld.so.conf.d/。第三步如果你編譯了Python接口驗(yàn)證一下cd python export PYTHONPATH$(pwd):$PYTHONPATH python -c import caffe; print(caffe.__version__)能打印出版本號(hào)就說明pycaffe可用了。如果報(bào)ImportError: No module named caffe檢查PYTHONPATH是否設(shè)置正確以及caffe/python/caffe/目錄下是否有_caffe.so文件。4. 那些讓我熬夜的報(bào)錯(cuò)與解決思路4.1 protobuf版本沖突的完整排查鏈路這是我遇到最多的坑也是很多人卡住的地方。癥狀通常是編譯時(shí)報(bào)一堆undefined reference to google::protobuf::...或者運(yùn)行時(shí)報(bào)This program was compiled against version X of the Protocol Buffer runtime library, which is not compatible with the installed version Y。排查思路是這樣的先確認(rèn)系統(tǒng)里裝了哪些protobuf版本。用dpkg -l | grep protobuf看apt裝的用which protoc看當(dāng)前用的是哪個(gè)用protoc --version看版本號(hào)。如果發(fā)現(xiàn)apt裝了一個(gè)版本手動(dòng)編譯又裝了一個(gè)版本那基本就是沖突了。解決辦法有兩種。第一種是統(tǒng)一版本把手動(dòng)裝的卸掉只用apt的版本或者反過來。第二種是讓Caffe明確使用某個(gè)版本在Makefile.config里把PROTOBUF_INCLUDE和PROTOBUF_LIB指向你想要的版本路徑。我個(gè)人的做法是編譯Caffe之前先把系統(tǒng)里的protobuf清理干凈然后手動(dòng)編譯安裝一個(gè)2.6.1版本。雖然老但和Caffe的兼容性最好。安裝步驟是下載源碼、./configure、make、make install然后ldconfig刷新庫緩存。4.2 OpenCV 4.x帶來的接口不兼容如果你系統(tǒng)里裝的是OpenCV 4.x編譯Caffe時(shí)大概率會(huì)遇到CV_LOAD_IMAGE_COLOR未定義的錯(cuò)誤。這個(gè)宏在OpenCV 4里被改成了cv::IMREAD_COLOR。Caffe的代碼里用的是老寫法所以編譯不過。解決辦法有兩個(gè)一是降級(jí)到OpenCV 3.x二是手動(dòng)改Caffe源碼。我選的是后者因?yàn)榻导?jí)OpenCV可能影響系統(tǒng)里其他依賴OpenCV的程序。具體改法是找到報(bào)錯(cuò)的文件通常是src/caffe/util/io.cpp和src/caffe/layers/image_data_layer.cpp把CV_LOAD_IMAGE_COLOR替換成cv::IMREAD_COLOR把CV_LOAD_IMAGE_GRAYSCALE替換成cv::IMREAD_GRAYSCALE。改完之后重新編譯即可。這個(gè)坑的教訓(xùn)是Caffe的代碼和現(xiàn)代庫之間存在代溝遇到類似問題不要死磕要么降版本要么改代碼兩條路都能走通。4.3 鏈接階段的未定義引用怎么定位鏈接錯(cuò)誤比編譯錯(cuò)誤更難查因?yàn)閳?bào)錯(cuò)信息往往是一大串符號(hào)名看不出是哪個(gè)庫的問題。我的經(jīng)驗(yàn)是從報(bào)錯(cuò)信息里找關(guān)鍵詞。比如看到cblas_開頭的符號(hào)說明BLAS庫沒鏈接上看到H5開頭的說明HDF5沒配好看到cv::開頭的說明OpenCV路徑有問題。定位到是哪個(gè)庫之后回到Makefile.config檢查對(duì)應(yīng)的LIBRARY_DIRS和LDFLAGS。有時(shí)候庫文件確實(shí)存在但名字不對(duì)比如你裝的是libopenblas.so但Makefile里找的是libcblas.so那就需要建一個(gè)軟鏈接或者改Makefile。還有一個(gè)隱蔽的情況庫的架構(gòu)不匹配。比如你的系統(tǒng)是64位的但某個(gè)庫只有32位版本鏈接時(shí)就會(huì)報(bào)錯(cuò)。用file命令檢查庫文件的架構(gòu)確認(rèn)是ELF 64-bit。5. 環(huán)境跑通之后的一些實(shí)用建議5.1 把Caffe集成到你的工作流里編譯完只是第一步真正用起來還需要一些配置。如果你用Python寫腳本建議把Caffe的Python路徑寫進(jìn).bashrcexport CAFFE_ROOT/path/to/your/caffe export PYTHONPATH$CAFFE_ROOT/python:$PYTHONPATH export LD_LIBRARY_PATH$CAFFE_ROOT/build/lib:$LD_LIBRARY_PATH這樣每次打開終端都自動(dòng)生效不用手動(dòng)export。注意LD_LIBRARY_PATH這一行很多人只配了PYTHONPATH結(jié)果運(yùn)行時(shí)報(bào)找不到libcaffe.so就是漏了這個(gè)。如果你用CMake管理自己的項(xiàng)目可以在CMakeLists.txt里這樣引用Caffefind_package(Caffe REQUIRED) include_directories(${Caffe_INCLUDE_DIRS}) target_link_libraries(your_project ${Caffe_LIBRARIES})前提是Caffe安裝時(shí)生成了CaffeConfig.cmake如果沒有就需要手動(dòng)指定路徑。5.2 模型轉(zhuǎn)換與跨框架協(xié)作的現(xiàn)實(shí)問題現(xiàn)在純用Caffe訓(xùn)新模型的人已經(jīng)不多了更常見的場(chǎng)景是拿PyTorch訓(xùn)練好的模型轉(zhuǎn)成Caffe格式去部署。這個(gè)轉(zhuǎn)換過程有不少坑比如層類型對(duì)不上、參數(shù)命名不一致、某些算子Caffe不支持等。我的建議是轉(zhuǎn)換之前先確認(rèn)目標(biāo)Caffe版本支持哪些層。Caffe的層類型是固定的那些PyTorch里一些新奇的算子比如各種注意力機(jī)制在Caffe里根本沒有對(duì)應(yīng)實(shí)現(xiàn)。遇到這種情況要么用Caffe的自定義層功能自己寫要么換部署方案。轉(zhuǎn)換工具方面有開源的pytorch2caffe之類的腳本但質(zhì)量參差不齊很多只支持基本的卷積網(wǎng)絡(luò)。我試過幾個(gè)對(duì)ResNet這種標(biāo)準(zhǔn)結(jié)構(gòu)還行稍微復(fù)雜一點(diǎn)的網(wǎng)絡(luò)就各種報(bào)錯(cuò)。所以如果你的模型結(jié)構(gòu)比較新做好手動(dòng)改轉(zhuǎn)換腳本的心理準(zhǔn)備。5.3 關(guān)于Caffe是否還值得學(xué)的個(gè)人看法最后說點(diǎn)實(shí)在的。Caffe作為一個(gè)框架它的生命周期確實(shí)已經(jīng)進(jìn)入尾聲了官方倉庫好幾年沒更新了。但這不代表學(xué)它沒意義。理解Caffe的架構(gòu)設(shè)計(jì)對(duì)理解深度學(xué)習(xí)框架的演進(jìn)很有幫助。它的prototxt定義網(wǎng)絡(luò)的方式、blob作為數(shù)據(jù)載體的設(shè)計(jì)、layer作為計(jì)算單元的組織方式這些思想在后來的框架里都能看到影子。而且現(xiàn)實(shí)情況是很多工業(yè)項(xiàng)目還在用Caffe你接手了就得會(huì)。與其抱怨怎么還在用這老古董不如花半天時(shí)間把環(huán)境搭好把流程跑通。我自己的經(jīng)驗(yàn)是Caffe環(huán)境搭一次之后后面換機(jī)器照著筆記走半小時(shí)就能搞定。真正花時(shí)間的是第一次踩坑的過程而這個(gè)過程一旦走完你對(duì)Linux編譯、依賴管理、動(dòng)態(tài)鏈接這些底層知識(shí)的理解會(huì)上一個(gè)臺(tái)階。如果你在搭建過程中遇到了本文沒覆蓋的報(bào)錯(cuò)我的建議是去翻Caffe的GitHub Issues里面幾乎能找到所有常見問題的討論。搜索的時(shí)候用報(bào)錯(cuò)信息里的關(guān)鍵詞不要用整句話搜命中率更高。另外編譯日志一定要保存下來make all 21 | tee build.log出問題的時(shí)候可以慢慢翻。