:版本匹配與踩坑記錄)
1. 在16.04上做這件事前先想清楚三件事1.1 為什么Ubuntu 16.04折騰GLIBC和Python生態(tài)的年代差先說個現(xiàn)實問題Ubuntu 16.04發(fā)布于2016年自帶GLIBC 2.23而mmdetection的現(xiàn)代版本和配套編譯工具鏈很多編譯產(chǎn)物都依賴更高的GLIBC版本。之前我在一臺老機器上直接照搬新教程結果在import torch那一步就報GLIBC_2.29 not found當時整個人是懵的。這個報錯的意思很直白你想用的PyTorch或mmcv編譯時是基于新版系統(tǒng)的動態(tài)庫鏈接的GLIBC符號在你的系統(tǒng)里根本不存在。解決辦法不是去網(wǎng)上找一堆不安全的二進制替換libc而是老老實實選擇一套和系統(tǒng)年代匹配的版本組合。同樣的問題也出現(xiàn)在Python層面。16.04系統(tǒng)自帶的Python 3.5已經(jīng)非常老了很多新版本的mmdet、mmcv都不再兼容Python 3.5。我在實際配置中把它鎖定在Python 3.7不追新原因后面會展開說。所以入手16.04裝mmdetection的第一認知就是不要試圖全上最新版本而是要構建一套“古董但自洽”的版本矩陣。誰先接受“老系統(tǒng)配老版本”這個現(xiàn)實誰就能少踩三分之二的坑。適合看這篇內容的人我總結一下手里正好有16.04的老機器、或者因為顯卡驅動原因只能留在16.04、再或者在某些工控設備上必須用老內核的人。如果你的機器可以隨便裝20.04以上系統(tǒng)我建議你直接上新系統(tǒng)別回頭。1.2 顯卡、驅動、CUDA三角約束先搞定在裝任何軟件之前先把顯卡型號確認清楚。在16.04這種老系統(tǒng)上顯卡驅動版本和CUDA版本是互相鎖死的順序不能亂。我當時的做法是三步走。第一步用lspci | grep -i nvidia查看顯卡型號第二步去NVIDIA官網(wǎng)看該型號支持的最新驅動版本第三步根據(jù)驅動版本反推CUDA版本。實際中遇到過這樣一個例子一臺機器是GTX 1080 Ti驅動版本390.xx這個驅動最高只支持到CUDA 9.2左右。這意味著你最多也就用PyTorch 1.0左右的版本mmdetection基本沒法跑。這時候就得考慮升級驅動到440以上才能用CUDA 10.2。顯卡驅動版本可用的CUDA版本可搭配的PyTorch建議390.xxCUDA 9.2及以下PyTorch 1.0以下不推薦跑mmdetection418/430CUDA 10.1/10.2PyTorch 1.5~1.8450CUDA 11.0/11.3PyTorch 1.8~1.10這里額外提一句如果你連顯卡都沒有只想用CPU跑一跑示例那也請保持CPU版PyTorch的版本對應關系不要混裝。1.3 是不是非16.04不可很多人的掙扎在于周圍教程都是18.04、20.04自己這臺機器偏偏是16.04。這時候我通常給出的建議排序是能上20.04就上20.04這是最優(yōu)解一套新版本組合隨便配。因為老顯卡或軟件依賴無法升級系統(tǒng)的選擇16.04 老版本組合也就是本文的主題。既不能換系統(tǒng)、又想用新功能——可以考慮用Docker容器在Docker里跑新版Ubuntu鏡像。但這對顯卡直通和nvidia-container-toolkit有要求而且16.04主機內核老Docker的gpu參數(shù)兼容性需要專門測試我試下來反倒是裸裝更快。我個人的傾向是如果確定留在16.04就干脆把心氣放平把本文推薦的老版本組合當標準答案不要再惦記mmdetection最新的3.0版本。2. 驅動、CUDA與conda虛擬環(huán)境一鍋端式的準備方案2.1 禁用nouveau安裝NVIDIA驅動16.04自帶的nouveau開源驅動和NVIDIA閉源驅動之間的沖突是環(huán)境安裝的第一道坎。不把它禁掉裝完驅動重啟后大概率出現(xiàn)黑屏或循環(huán)登錄。操作順序是這樣的編輯配置文件sudo vim /etc/modprobe.d/blacklist-nouveau.conf寫入下面兩行內容blacklist nouveau options nouveau modeset0更新內核配置并重啟sudo update-initramfs -u sudo reboot重啟后先確認nouveau沒被加載lsmod | grep nouveau如果沒有任何輸出說明禁用成功。接著去NVIDIA官網(wǎng)下載對應顯卡的runfile安裝包直接.run文件安裝比apt源里的版本更可控特別是老環(huán)境。安裝命令chmod x NVIDIA-Linux-x86_64-450.142.run sudo ./NVIDIA-Linux-x86_64-450.142.run -no-x-check -no-nouveau-check裝完用nvidia-smi驗證。如果顯示驅動版本和顯存信息說明驅動沒問題。這一步看似基礎但我見過太多人跳過后在mmdet訓練時報各種CUDA相關崩潰回頭查才發(fā)現(xiàn)是驅動層的cuda版本根本不兼容。2.2 CUDA與cuDNN安裝盡量用runfile而不是deb包CUDA和cuDNN的安裝方式會影響后期mmcv編譯的順利程度。16.04上用deb包安裝有個問題它會把驅動也一并更新掉導致你辛辛苦苦裝好的驅動版本被覆蓋。所以我的方案是驅動用runfile裝CUDA也用只裝工具庫的方式不動驅動。CUDA下載頁里選擇Linux、x86_64、Ubuntu 16.04版本對應的runfile。假設我們選用CUDA 10.2wget https://developer.download.nvidia.com/compute/cuda/10.2/Prod/local_installers/cuda_10.2.89_440.33.01_linux.run sudo sh cuda_10.2.89_440.33.01_linux.run安裝時注意在提示是否安裝驅動時選No只安裝CUDA Toolkit。然后設置環(huán)境變量把下面內容加到~/.bashrcexport PATH/usr/local/cuda-10.2/bin:$PATH export LD_LIBRARY_PATH/usr/local/cuda-10.2/lib64:$LD_LIBRARY_PATHcuDNN的安裝更簡單把下載的cudnn-10.2-linux-x64-v8.2.1.32.tgz解壓然后把文件內容拷貝到CUDA目錄下tar -xzvf cudnn-10.2-linux-x64-v8.2.1.32.tgz sudo cp cuda/include/cudnn*.h /usr/local/cuda/include/ sudo cp cuda/lib64/libcudnn* /usr/local/cuda/lib64/這一步完成后記得同步一下軟鏈接否則編譯時可能出現(xiàn)libcudnn.so.8 not found。這是老系統(tǒng)上非常經(jīng)典的坑因為16.04不太會主動刷新動態(tài)庫緩存。2.3 conda環(huán)境與Python 3.7給所有依賴建一個隔離區(qū)到這一步我強烈建議裝Anaconda或Miniconda絕對不要直接用系統(tǒng)Python。16.04自帶Python 3.5且很多系統(tǒng)工具鏈依賴它直接覆蓋會出大問題。Miniconda對老系統(tǒng)更友好安裝命令wget https://repo.anaconda.com/miniconda/Miniconda3-py37_4.12.0-Linux-x86_64.sh bash Miniconda3-py37_4.12.0-Linux-x86_64.sh安裝時一路yes最后初始化conda。創(chuàng)建虛擬環(huán)境conda create -n mmdet python3.7 -y conda activate mmdet為什么鎖Python 3.7因為mmdetection 2.x時代的官方README和大量預編譯的mmcv wheel都明確支持Python 3.7而且3.7在AGES工具箱里兼容性最高。整個mmdet全家桶對這個Python版本支持得最穩(wěn)實際測試中3.8也可以但在16.04的GLIBC限制下3.7的輪子最聽話。3. mmcv與mmdetection的版本匹配系統(tǒng)成敗的分水嶺3.1 版本對應關系別信任何單點教程mmcv和mmdetection的版本匹配關系極其嚴格不同大版本之間API有變化連配置字段都可能改過名。在16.04上我最終是裝成了mmdetection 2.25.1 mmcv-full 1.7.5 PyTorch 1.10.0 CUDA 10.2這一組合。這個搭配的思考邏輯是Python 3.7 PyTorch 1.10 CUDA 10.2在老驅動能用、且版本不要太老之間取了平衡。mmcv-full 1.7.5這個版本提供豐富的預編譯包選項支持PyTorch 1.10。mmdetection 2.25.1是2.x系列的后期版本功能足夠且配置組織方式成熟。這里還需要看一個關鍵對應表mmcv與mmdet官方支持關系mmdetection版本mmcv全版本2.25.1mmcv-full 1.5.1, 1.8.02.18.1mmcv-full 1.4.6, 1.7.02.10.0mmcv-full 1.3.8, 1.4.0別小看這些區(qū)間mmcv版本裝高了或裝低了輕則警告重則直接報AttributeError或KeyError比如mmdet里用到的某個函數(shù)在mmcv新版本里被改名了。3.2 安裝PyTorch與mmcv-full的完整命令先裝PyTorch 1.10.0對應CUDA 10.2直接用conda安裝最省事conda install pytorch1.10.0 torchvision0.11.0 torchaudio0.10.0 cudatoolkit10.2 -c pytorch -y這里有個易錯點如果你預先下載了本地wheel安裝必須保證wheel是CUDA 10.2編譯的。我曾經(jīng)貪快用了pip install torch1.10.0默認源結果拉到了CPU版本訓練時torch.cuda.is_available()一直返回False排查了半天才發(fā)現(xiàn)基礎裝錯了。接著裝mmcv-full。有兩個選擇一是直接用官方預編譯wheel二是源碼編譯。在16.04上如果預編譯wheel能匹配到你的Python和CUDA版本那是最快路徑如果報找不到對應的wheel就老老實實從源碼編譯。預編譯方式pip install mmcv-full1.7.5 -f https://download.openmmlab.com/mmcv/dist/cu102/torch1.10/index.html源碼編譯方式預編譯失敗時的備選git clone -b v1.7.5 https://github.com/open-mmlab/mmcv.git cd mmcv pip install -r requirements/optional.txt MMCV_WITH_OPS1 FORCE_CUDA1 pip install -e .注意源碼編譯時MMCV_WITH_OPS1這個環(huán)境變量不能省不然只會裝純Python版本后續(xù)運行檢測時會報一些算子在自定義CUDA擴展上缺失的錯誤。3.3 驗證安裝是否真的可用裝完后我建議不止做import測試而是直接跑一次簡單測試看CUDA是否可用python -c import torch; print(torch.__version__, torch.cuda.is_available()) python -c import mmcv; print(mmcv.__version__) python -c import mmdet; print(mmdet.__version__)同時在Python里跑一個小算子import torch from mmcv.ops import nms boxes torch.tensor([[0, 0, 10, 10, 0.9]], dtypetorch.float32).cuda() keep nms(boxes, torch.tensor([0.5]).cuda(), 0.4) print(keep)如果這段輸出正常說明整個CUDA編譯鏈路都是通的可以進入數(shù)據(jù)準備階段了。很多人在這一步直接卡住有的報torch.cuda.is_available() False有的報nms算子加載失敗。前者多半是PyTorch版本和CUDA不匹配后者多半是mmcv未安裝帶ops的版本。4. 數(shù)據(jù)集準備與配置文件改動訓練前最后的硬骨頭4.1 COCO格式目錄結構就是規(guī)則mmdetection默認推薦COCO格式數(shù)據(jù)集。如果你手上是VOC或自建數(shù)據(jù)集推薦先轉成COCO格式因為后續(xù)一切可視化腳本、評測接口都基于這個格式省得繞路。目錄組織如下data/ coco/ annotations/ instances_train2017.json instances_val2017.json train2017/ 000001.jpg 000002.jpg ... val2017/ 000001.jpg ...自建數(shù)據(jù)集時你需要寫一個腳本把標注轉換為COCO json最少包含categories、images、annotations三個節(jié)點。提醒一句images里的id和annotations里的image_id必須一致否則訓練時數(shù)據(jù)加載會直接報找不到圖片的index這個初學最容易漏。4.2 配置文件繼承體系從base到sub-configmmdetection的配置成熟度很高“拿來改”比“從零寫”靠譜太多。它的邏輯是先有基礎配置再逐層覆蓋字段。實際訓練時主要改三個文件configs/_base_/datasets/coco_detection.py改數(shù)據(jù)路徑、訓練圖片尺度、數(shù)據(jù)增強參數(shù)。configs/_base_/schedules/schedule_1x.py改學習率、step、總epoch數(shù)。configs/_base_/default_runtime.py改checkpoint保存間隔、日志配置、工作目錄。比如我們訓練SSD或Faster R-CNN通常會創(chuàng)建一個新配置文件_base_ [ ../_base_/datasets/coco_detection.py, ../_base_/schedules/schedule_1x.py, ../_base_/default_runtime.py ] model dict( typeFasterRCNN, backbonedict(...), ... ) data dict( samples_per_gpu2, # 每個GPU的batch大小 workers_per_gpu2, # 每個GPU的DataLoader線程數(shù) ) optimizer dict(lr0.02/8) # 如果samples_per_gpu調整過學習率也要等比調整我當時自作主張把samples_per_gpu調成4卻沒動學習率結果損失直接NAN后來參照官方文檔里“l(fā)inear scaling rule”把學習率調小才恢復。4.3 數(shù)據(jù)增強與類別匹配中的隱藏要求模型頭和數(shù)據(jù)集類別數(shù)必須匹配。COCO默認是80類如果你用自建數(shù)據(jù)集只有3個類別必須在配置里顯式覆蓋model dict( roi_headdict( bbox_headdict( num_classes3, )), ... )這個問題很隱蔽因為不覆蓋也不會報錯默認80類會和你的標注id對不上導致所有框都往錯誤的類別上套。我在某次實驗里完全沒注意訓練了很久mAP一直很低直到可視化檢測結果才發(fā)現(xiàn)類別全亂這屬于白跑幾個小時的經(jīng)典失誤。數(shù)據(jù)增強方面mmdet的默認pipeline已經(jīng)包含隨機翻轉、隨機縮放、多尺度resize。在16.04老機器上如果顯存不寬裕建議把img_scale從(1333, 800)改成(1000, 600)訓練速度會提升明顯mAP損失在接受范圍內。5. 訓練流程實操從命令行到恢復斷點5.1 單卡訓練命令與常見參數(shù)一切就緒后進入訓練conda activate mmdet cd /path/to/mmdetection python tools/train.py configs/faster_rcnn_r50_fpn_1x_coco.py --work-dir work_dirs/my_experiment參數(shù)說明--work-dir保存日志和checkpoint的目錄不設置時會自動生成在work_dirs/下。--cfg-options臨時修改配置比如--cfg-options optimizer.lr0.01適合做小實驗時用。--resume-from從指定checkpoint恢復。--auto-resume自動從最近一次中斷點恢復這個參數(shù)在2.x版本里很實用。啟動后日志會輸出每個epoch的loss、bbox_mAP等指標。這里有個小習慣啟動訓練后別急著離開先等兩三個iteration確認loss在合理范圍比如0.5到2之間震蕩如果一開始loss就NAN立刻CtrlC回去查配置別心疼這幾分鐘。5.2 多卡訓練與GPU選擇如果你的老機器插了多張顯卡可以用官方腳本bash tools/dist_train.sh configs/faster_rcnn_r50_fpn_1x_coco.py 2 --work-dir work_dirs/my_experiment其中2是GPU卡數(shù)。但是16.04機器多為老平臺PCIe帶寬和CPU都可能是瓶頸。實測下來雙卡加速往往達不到2倍甚至會因為通信開銷拖慢。所以老機器上我反而推薦先單卡跑通再考慮分布式的test-time augmentation或推理加速。顯存不夠是另一個高頻問題。如果訓練時報CUDA out of memory優(yōu)先做三件事把samples_per_gpu降到1。把img_scale進一步縮小。開啟梯度累積在配置里使用optimizer_config dict(grad_clipNone, typeGradientCumulativeOptimizerHook, cumulative_iters4)。這個梯度累積技巧在顯存受限時堪稱救星等效增大batch size又不炸顯存。5.3 訓練中途崩潰怎么續(xù)訓練跑著跑著因為機房斷電、SSH斷開、顯存溢出被殺進程是常態(tài)。mmdetection的斷點續(xù)訓機制很完善做法是python tools/train.py configs/faster_rcnn_r50_fpn_1x_coco.py --resume-from work_dirs/my_experiment/epoch_10.pth它會從epoch 10繼續(xù)訓練優(yōu)化器狀態(tài)、學習率調度都會一并恢復。如果不想手動指定--auto-resume會自動找最新的checkpoint。迭代過程中還建議定期把配置文件復制到work_dirs里留檔比如cp configs/faster_rcnn_r50_fpn_1x_coco.py work_dirs/my_experiment/否則后續(xù)想回溯當時實驗用的哪套參數(shù)會非常被動。評測模型時用python tools/test.py configs/faster_rcnn_r50_fpn_1x_coco.py work_dirs/my_experiment/epoch_12.pth --eval bbox注意--eval bbox只評測檢測框如果要評測分割結果要改成segm參數(shù)用錯了不會報錯但輸出metric缺失容易誤判。6. 我在16.04上踩過的坑編譯、訓練中的典型報錯與排查鏈路6.1 編譯報錯別只看最后一行要看完整日志源碼編譯mmcv時最常見的報錯是error: command gcc failed with exit status 1這個錯誤信息太籠統(tǒng)90%以上的人卡在這里。我當時的排查思路是往上翻日志找到真正的根源。通常有幾種情況缺少python頭文件需要先conda install python-dev或conda install python3.7-dev。缺少CUDA頭文件確認/usr/local/cuda/include/cuda.h存在。gcc版本太老如果CUDA 10.2自帶驅動的約束官方要求gcc 5.4沒問題但如果升級到CUDA 115.4就不夠了需要gcc 7.5。說到gcc16.04默認gcc是5.4。解決辦法是apt安裝gcc-7sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo apt update sudo apt install gcc-7 g-7 sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 70 sudo update-alternatives --install /usr/bin/g g /usr/bin/g-7 70注意如果換gcc版本之前已經(jīng)編譯過的CUDA工具鏈不一定需要重裝但重新編譯mmcv時一定要確認gcc --version是預期版本。6.2 訓練中出現(xiàn)NAN的排查鏈路NAN是訓練階段最常見的臟數(shù)據(jù)類問題在16.04老環(huán)境里還多了一個特殊性硬件穩(wěn)定性降低某個PCIe插槽接觸不良或電源供電不足也可能隨機產(chǎn)生NAN。我的排查順序是先排除數(shù)據(jù)如果隨機抽樣幾百張驗證圖片loss都在合理范圍那數(shù)據(jù)本身沒問題。再看學習率線性縮放規(guī)則batch減半那么lr也要減半否則NAN概率極高。檢查BN層的num_batches_tracked低batch size下BN統(tǒng)計不穩(wěn)定可以在配置里norm_cfg dict(typeBN, requires_gradTrue)改成SyncBN或減小lr進一步觀察。硬件層面查dmesg | grep -i error看有沒有GPU相關報錯用nvidia-smi -pl 200給老卡降一點功耗墻訓練穩(wěn)定性會好很多。這里強調一下環(huán)境越老越不能把NAN直接歸因到算法要按層、按數(shù)據(jù)、按硬件一步步隔離才能找到真兇。6.3 GPU利用率上不去、CPU爆滿的老機器優(yōu)化16.04老機器CPU核數(shù)普遍不多訓練時GPU利用率經(jīng)常只在30%左右波動這是因為DataLoader在瘋狂做圖像解碼和resizeCPU成了瓶頸。解法data dict( samples_per_gpu2, workers_per_gpu4, # 適當調大worker數(shù) )workers_per_gpu從2調到4或6通常能讓GPU利用率明顯回升。但也不要盲目開太大worker太多會造成內存占用飆升老機器內存不夠時反而觸發(fā)swap速度暴跌。我的一般經(jīng)驗是內存16G配2~4個worker32G以上配8個也沒問題。還有一個容易被忽略的點可以用torch.backends.cudnn.benchmark True在輸入尺寸固定時開啟cudnn自動調優(yōu)對小數(shù)據(jù)集訓練有明顯加速效果。老機器上跑訓練最忌諱看著GPU沒吃滿就著急換大batch很多時候瓶頸不在GPU而在數(shù)據(jù)管線和IO。先診斷再優(yōu)化才是正道。最后再分享一個實際操作中的體會Ubuntu 16.04 mmdetection這套配置如果你能接受“不追新、版本鎖死、一切按部就班”的心態(tài)其實是可以很穩(wěn)定地跑起來的。我多次在重裝環(huán)境后照著一份自制的版本清單裝基本半小時內能恢復到昨天的工作狀態(tài)。建議你也把最終成功的版本組合和安裝命令寫成一份備忘而不是依賴記憶。老系統(tǒng)的容錯率低但確定性高一次配好持續(xù)使用體驗反而比每次升級都要折騰的人舒暢得多。