動(dòng)、工具鏈與深度學(xué)習(xí)環(huán)境管理)
1. 為什么在Ubuntu 20.04上更換CUDA版本是個(gè)高頻剛需如果你在Ubuntu 20.04上折騰過(guò)深度學(xué)習(xí)或者GPU計(jì)算大概率會(huì)遇到一個(gè)讓人頭疼的問(wèn)題項(xiàng)目A需要CUDA 11.1項(xiàng)目B卻只認(rèn)CUDA 10.2而系統(tǒng)里默認(rèn)安裝的版本可能一個(gè)都對(duì)不上。這絕不是個(gè)例而是幾乎所有GPU開(kāi)發(fā)者都會(huì)踩的坑。Ubuntu 20.04 LTS作為一款長(zhǎng)期支持、穩(wěn)定性極佳的操作系統(tǒng)是許多實(shí)驗(yàn)室、公司和個(gè)人開(kāi)發(fā)者的首選環(huán)境。然而NVIDIA的CUDA Toolkit版本迭代非常快不同版本的深度學(xué)習(xí)框架如PyTorch、TensorFlow對(duì)CUDA版本又有嚴(yán)格的依賴(lài)關(guān)系這就導(dǎo)致了“一個(gè)系統(tǒng)多個(gè)CUDA”的復(fù)雜需求。很多人第一次嘗試更換CUDA時(shí)會(huì)直接去NVIDIA官網(wǎng)下載一個(gè).run安裝包運(yùn)行sudo sh cuda_xxx.run然后發(fā)現(xiàn)系統(tǒng)里多了一堆文件但nvcc --version命令顯示的版本紋絲不動(dòng)或者更糟把圖形界面給搞崩了。這背后的原因在于CUDA不僅僅是一個(gè)編譯器nvcc它是一整套包含驅(qū)動(dòng)、工具鏈、庫(kù)文件的生態(tài)系統(tǒng)。在Ubuntu上尤其是使用apt包管理器的情況下更換CUDA版本涉及到多個(gè)軟件源的優(yōu)先級(jí)、環(huán)境變量的精確配置以及潛在的系統(tǒng)級(jí)沖突。今天我就以一個(gè)踩過(guò)無(wú)數(shù)次坑的老兵身份帶你徹底理清在Ubuntu 20.04上安全、干凈、可逆地切換CUDA版本的全套流程和底層邏輯。這不是一篇簡(jiǎn)單的命令羅列而是讓你明白每一個(gè)步驟背后的“為什么”從而能舉一反三從容應(yīng)對(duì)任何版本兼容性問(wèn)題。2. 理解CUDA生態(tài)驅(qū)動(dòng)、工具鏈與運(yùn)行時(shí)庫(kù)的三角關(guān)系在動(dòng)手之前我們必須先拆解清楚CUDA到底是什么。很多人誤以為CUDA就是一個(gè)軟件換版本就是覆蓋安裝。這種理解是導(dǎo)致后續(xù)一系列混亂的根源。實(shí)際上在Ubuntu系統(tǒng)中與CUDA相關(guān)的核心組件可以分為三層它們之間存在著松耦合但又相互制約的關(guān)系。2.1 顯卡驅(qū)動(dòng)地基中的地基最底層是NVIDIA顯卡驅(qū)動(dòng)。你可以把它理解為讓系統(tǒng)認(rèn)識(shí)并能夠指揮你那塊GPU硬件的“翻譯官”和“指揮官”。沒(méi)有正確的驅(qū)動(dòng)GPU就是一塊磚。驅(qū)動(dòng)版本有一個(gè)最低要求它必須與你想要安裝的CUDA Toolkit版本兼容。例如CUDA 11.x系列通常需要450.xx版本以上的驅(qū)動(dòng)而CUDA 10.2可能只需要440.xx。但這里有一個(gè)關(guān)鍵點(diǎn)更高版本的驅(qū)動(dòng)通常向下兼容多個(gè)CUDA Toolkit版本。這意味著你完全可以安裝一個(gè)較新的驅(qū)動(dòng)比如470版然后同時(shí)安裝CUDA 11.4和CUDA 10.2的工具鏈并根據(jù)需要切換使用。因此我們的策略往往是安裝一個(gè)足夠新、能覆蓋你所有目標(biāo)CUDA版本的驅(qū)動(dòng)而不是為每個(gè)CUDA版本去更換驅(qū)動(dòng)。檢查當(dāng)前驅(qū)動(dòng)版本的命令是nvidia-smi輸出右上角顯示的“Driver Version”就是你的驅(qū)動(dòng)版本。記住這個(gè)數(shù)字它是我們后續(xù)操作的基準(zhǔn)。2.2 CUDA Toolkit開(kāi)發(fā)者的工具箱中間層是我們常說(shuō)的CUDA Toolkit。這才是我們真正想要“更換”的主角。它主要包含nvcc編譯器將CUDA C/C代碼編譯為GPU可執(zhí)行的代碼。CUDA運(yùn)行時(shí)庫(kù)libcudart為CUDA程序提供運(yùn)行時(shí)的支持。各種數(shù)學(xué)庫(kù)如cuBLAS線(xiàn)性代數(shù)、cuFFT快速傅里葉變換、cuDNN深度神經(jīng)網(wǎng)絡(luò)的前端等。頭文件和示例代碼。當(dāng)我們通過(guò)apt安裝cuda-toolkit-11-6這樣的包時(shí)主要安裝的就是這一層。多個(gè)不同版本的Toolkit可以共存于系統(tǒng)因?yàn)樗鼈兺ǔ1话惭b到不同的目錄例如/usr/local/cuda-11.6/和/usr/local/cuda-10.2/。2.3 CUDA運(yùn)行時(shí)與兼容性最上層是具體的應(yīng)用程序及其依賴(lài)的CUDA運(yùn)行時(shí)庫(kù)。一個(gè)用CUDA 11.1編譯的PyTorch程序在運(yùn)行時(shí)需要找到對(duì)應(yīng)版本的libcudart.so.11.1。系統(tǒng)如何找到它這就是環(huán)境變量LD_LIBRARY_PATH和動(dòng)態(tài)鏈接器的工作了。這三層的關(guān)系決定了我們的操作思路穩(wěn)定驅(qū)動(dòng)多版本共存Toolkit通過(guò)環(huán)境變量靈活切換運(yùn)行時(shí)鏈接。接下來(lái)所有的步驟都圍繞這個(gè)核心思路展開(kāi)。3. 徹底清理為多版本共存掃清障礙在安裝新版本之前一個(gè)干凈的起點(diǎn)至關(guān)重要。如果你之前通過(guò)多種方式.run文件、apt、conda安裝或嘗試安裝過(guò)CUDA系統(tǒng)里可能殘留著沖突的軟件包和混亂的符號(hào)鏈接。我們的目標(biāo)是使用Ubuntu原生的apt包管理器來(lái)管理CUDA這是最穩(wěn)定、最易于維護(hù)的方式。首先讓我們檢查系統(tǒng)已安裝的與CUDA和NVIDIA相關(guān)的包dpkg -l | grep -E nvidia|cuda | awk {print $2}這會(huì)列出一長(zhǎng)串包名例如cuda-toolkit-11-6libcudnn8nvidia-driver-470等。關(guān)鍵操作完全移除舊版CUDA Toolkit保留驅(qū)動(dòng)我們只想移除Toolkit不動(dòng)驅(qū)動(dòng)。假設(shè)我們要清理舊版CUDA 11.6而保留驅(qū)動(dòng)和其他庫(kù)如cuDNN可以這樣做sudo apt-get purge --auto-remove cuda-toolkit-11-6 cuda-runtime-11-6 cuda-demo-suite-11-6 cuda-11-6注意包名可能略有不同請(qǐng)根據(jù)上一步dpkg -l查到的實(shí)際名稱(chēng)進(jìn)行替換。purge命令不僅刪除軟件還會(huì)清理配置文件比remove更徹底。--auto-remove會(huì)同時(shí)移除那些作為依賴(lài)被安裝但現(xiàn)在不再需要的包。重要提示切勿執(zhí)行sudo apt-get purge ‘nvidia-’或類(lèi)似的模糊匹配這可能會(huì)刪除你的顯卡驅(qū)動(dòng)導(dǎo)致桌面環(huán)境崩潰只能通過(guò)恢復(fù)模式或命令行重裝驅(qū)動(dòng)。接下來(lái)清理可能存在的殘留符號(hào)鏈接。系統(tǒng)通常使用/usr/local/cuda這個(gè)符號(hào)鏈接指向當(dāng)前“活躍”的CUDA版本。sudo rm -f /usr/local/cuda同時(shí)檢查/usr/local/目錄下是否有直接由.run安裝包產(chǎn)生的cuda-xx.x文件夾如果確定不再需要可以手動(dòng)刪除sudo rm -rf /usr/local/cuda-11.6 # 請(qǐng)?zhí)鎿Q成你確定要?jiǎng)h除的舊版本路徑完成清理后建議更新一下包列表sudo apt-get update4. 添加官方倉(cāng)庫(kù)與精準(zhǔn)安裝目標(biāo)版本NVIDIA為Ubuntu維護(hù)了官方的CUDA倉(cāng)庫(kù)這是獲取經(jīng)過(guò)兼容性測(cè)試的CUDA包的最佳途徑。我們將通過(guò)添加這個(gè)倉(cāng)庫(kù)然后像安裝任何其他軟件一樣用apt安裝特定版本的CUDA Toolkit。首先添加NVIDIA CUDA倉(cāng)庫(kù)的GPG密鑰和倉(cāng)庫(kù)地址。對(duì)于Ubuntu 20.04代號(hào)focal命令如下# 下載并添加GPG密鑰 wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-keyring_1.0-1_all.deb sudo dpkg -i cuda-keyring_1.0-1_all.deb # 更新軟件源列表 sudo apt-get update現(xiàn)在你可以搜索可用的CUDA Toolkit版本了apt-cache search cuda-toolkit | grep -E ‘^cuda-toolkit-[0-9]’你會(huì)看到類(lèi)似cuda-toolkit-11-6cuda-toolkit-11-7cuda-toolkit-11-8的包名。這里的11-6代表主版本11次版本6。假設(shè)我們需要安裝CUDA 11.7執(zhí)行安裝命令sudo apt-get install cuda-toolkit-11-7apt會(huì)自動(dòng)處理這個(gè)工具包的所有依賴(lài)包括特定版本的CUDA運(yùn)行時(shí)庫(kù)。安裝完成后對(duì)應(yīng)的文件會(huì)被放置到/usr/local/cuda-11.7/目錄下。這里有一個(gè)至關(guān)重要的細(xì)節(jié)這個(gè)安裝命令不會(huì)自動(dòng)為你安裝或更改NVIDIA顯卡驅(qū)動(dòng)。它會(huì)依賴(lài)于系統(tǒng)當(dāng)前已安裝的驅(qū)動(dòng)。如果驅(qū)動(dòng)版本過(guò)低與CUDA 11.7不兼容apt可能會(huì)報(bào)錯(cuò)或拒絕安裝。這就是為什么我們之前強(qiáng)調(diào)要先確認(rèn)驅(qū)動(dòng)版本。如果驅(qū)動(dòng)確實(shí)需要升級(jí)你應(yīng)該單獨(dú)安裝nvidia-driver-xxx包例如sudo apt-get install nvidia-driver-520 # 安裝一個(gè)較新的驅(qū)動(dòng)版本安裝完成后必須重啟系統(tǒng)以使新驅(qū)動(dòng)生效。5. 環(huán)境變量配置切換版本的核心魔法安裝了多個(gè)版本的CUDA Toolkit后系統(tǒng)如何知道當(dāng)前你要用哪一個(gè)答案就是環(huán)境變量。我們通過(guò)修改用戶(hù)shell的配置文件如~/.bashrc來(lái)動(dòng)態(tài)地切換指向。打開(kāi)你的~/.bashrc文件nano ~/.bashrc在文件末尾你會(huì)看到或需要添加類(lèi)似下面的內(nèi)容。我強(qiáng)烈建議使用一種靈活的管理方式而不是寫(xiě)死一個(gè)路徑。例如你可以這樣設(shè)置# CUDA Path Switching export CUDA_HOME/usr/local/cuda-11.7 # 默認(rèn)使用11.7可根據(jù)需要手動(dòng)修改 export PATH${CUDA_HOME}/bin:${PATH} export LD_LIBRARY_PATH${CUDA_HOME}/lib64:${LD_LIBRARY_PATH}CUDA_HOME是一個(gè)自定義變量指向你當(dāng)前想用的CUDA目錄。PATH變量前面加上${CUDA_HOME}/bin是為了讓系統(tǒng)優(yōu)先找到該版本下的nvcc等命令。LD_LIBRARY_PATH前面加上${CUDA_HOME}/lib64是為了讓運(yùn)行時(shí)鏈接器能夠找到對(duì)應(yīng)版本的CUDA動(dòng)態(tài)庫(kù)如libcudart.so。更優(yōu)雅的方案使用符號(hào)鏈接和腳本手動(dòng)編輯CUDA_HOME還是有點(diǎn)麻煩。一個(gè)更常見(jiàn)的做法是讓/usr/local/cuda這個(gè)符號(hào)鏈接指向當(dāng)前激活的版本。我們可以寫(xiě)一個(gè)小腳本來(lái)管理切換# 創(chuàng)建一個(gè)切換腳本比如叫 cuda-switch sudo nano /usr/local/bin/cuda-switch腳本內(nèi)容如下#!/bin/bash if [ -z “$1” ]; then echo “Usage: sudo cuda-switch version” echo “Example: sudo cuda-switch 11.7” exit 1 fi VERSION“$1” TARGET“/usr/local/cuda-${VERSION}” LINK“/usr/local/cuda” if [ ! -d “${TARGET}” ]; then echo “Error: Directory ${TARGET} does not exist.” exit 1 fi # 刪除舊鏈接創(chuàng)建新鏈接 sudo rm -f ${LINK} sudo ln -s ${TARGET} ${LINK} echo “Switched CUDA symlink to ${TARGET}”然后賦予執(zhí)行權(quán)限sudo chmod x /usr/local/bin/cuda-switch之后在.bashrc中將CUDA_HOME設(shè)置為這個(gè)符號(hào)鏈接export CUDA_HOME/usr/local/cuda export PATH${CUDA_HOME}/bin:${PATH} export LD_LIBRARY_PATH${CUDA_HOME}/lib64:${LD_LIBRARY_PATH}現(xiàn)在當(dāng)你需要切換到CUDA 11.7時(shí)只需執(zhí)行sudo cuda-switch 11.7然后重新打開(kāi)終端或執(zhí)行source ~/.bashrc所有環(huán)境變量就自動(dòng)更新了。一個(gè)必須注意的坑LD_LIBRARY_PATH的副作用過(guò)度依賴(lài)LD_LIBRARY_PATH有時(shí)會(huì)干擾系統(tǒng)其他程序。對(duì)于像PyTorch這樣通過(guò)conda安裝的框架它自帶了一套完整的CUDA運(yùn)行時(shí)庫(kù)在其環(huán)境內(nèi)例如~/miniconda3/envs/pytorch_env/lib/。在這種情況下conda環(huán)境內(nèi)的庫(kù)路徑優(yōu)先級(jí)更高你系統(tǒng)級(jí)的LD_LIBRARY_PATH設(shè)置可能不會(huì)生效。此時(shí)切換CUDA版本更有效的方式是使用不同版本的PyTorch或TensorFlowconda環(huán)境或者使用框架官方提供的、針對(duì)特定CUDA版本的pip安裝命令。系統(tǒng)級(jí)的CUDA切換更多是為了編譯原生CUDA代碼或供一些直接從系統(tǒng)路徑鏈接CUDA的應(yīng)用程序使用。6. 驗(yàn)證與故障排查確保一切就緒配置完成后必須進(jìn)行驗(yàn)證。打開(kāi)一個(gè)新的終端窗口或執(zhí)行source ~/.bashrc依次執(zhí)行以下命令驗(yàn)證nvcc版本nvcc --version輸出應(yīng)顯示與你剛安裝/切換的版本一致例如 “release 11.7, V11.7.99”。驗(yàn)證驅(qū)動(dòng)和GPU狀態(tài)nvidia-smi這個(gè)命令顯示的是驅(qū)動(dòng)層面的信息。頂部會(huì)顯示驅(qū)動(dòng)版本和CUDA Version。注意這里顯示的“CUDA Version”是你當(dāng)前安裝的驅(qū)動(dòng)所支持的最高CUDA運(yùn)行時(shí)API版本不是你通過(guò)nvcc選擇的工具鏈版本。只要這個(gè)數(shù)字大于等于你工具鏈的版本就沒(méi)有問(wèn)題。例如驅(qū)動(dòng)顯示“CUDA Version: 12.0”你完全可以同時(shí)使用CUDA 11.7的工具鏈。編譯并運(yùn)行一個(gè)簡(jiǎn)單的CUDA樣例 進(jìn)入CUDA示例目錄如果安裝時(shí)帶了demo包c(diǎn)d /usr/local/cuda-11.7/samples/1_Utilities/deviceQuery # 請(qǐng)根據(jù)你的路徑調(diào)整 sudo make ./deviceQuery如果最后輸出 “Result PASS”恭喜你從驅(qū)動(dòng)到運(yùn)行時(shí)再到編譯器的整個(gè)鏈路都是通的。常見(jiàn)問(wèn)題排查nvcc: command not found這說(shuō)明PATH環(huán)境變量沒(méi)有設(shè)置正確。檢查~/.bashrc中的PATH是否包含了${CUDA_HOME}/bin并確認(rèn)CUDA_HOME指向的目錄確實(shí)存在且包含bin/nvcc。執(zhí)行echo $PATH和echo $CUDA_HOME來(lái)查看。運(yùn)行程序時(shí)報(bào)錯(cuò)error while loading shared libraries: libcudart.so.11.7: cannot open shared object file這說(shuō)明LD_LIBRARY_PATH沒(méi)有設(shè)置正確或者包含了錯(cuò)誤路徑。檢查~/.bashrc中的LD_LIBRARY_PATH是否包含了${CUDA_HOME}/lib64。可以用ldd /path/to/your/program命令查看程序依賴(lài)的庫(kù)都從哪里加載。nvidia-smi可以運(yùn)行但nvcc --version報(bào)錯(cuò)或版本不對(duì)這典型是環(huán)境變量沖突。可能是你在多個(gè)地方如~/.profile~/.bash_profile 或某個(gè)conda環(huán)境的activate腳本中重復(fù)設(shè)置了CUDA路徑且優(yōu)先級(jí)混亂。使用which nvcc命令查看當(dāng)前生效的nvcc來(lái)自哪個(gè)路徑然后順著這個(gè)路徑去檢查環(huán)境變量。安裝后桌面環(huán)境崩潰、循環(huán)登錄這極有可能是在清理或安裝過(guò)程中誤操作了顯卡驅(qū)動(dòng)。此時(shí)需要進(jìn)入恢復(fù)模式或文本終端重新安裝正確的驅(qū)動(dòng)。記住在Ubuntu上使用apt安裝nvidia-driver-xxx是最安全的方式它會(huì)自動(dòng)處理與內(nèi)核模塊的編譯和圖形服務(wù)器的配置。7. 與深度學(xué)習(xí)框架的協(xié)同實(shí)戰(zhàn)中的版本管理對(duì)于大多數(shù)深度學(xué)習(xí)開(kāi)發(fā)者來(lái)說(shuō)更換系統(tǒng)CUDA版本的最終目的是為了適配PyTorch或TensorFlow。這里有一個(gè)更高效、更少副作用的原則盡量使用框架官方推薦的、隔離的安裝方式而非強(qiáng)改系統(tǒng)環(huán)境。對(duì)于PyTorch 訪(fǎng)問(wèn) PyTorch官網(wǎng) 使用其提供的安裝命令生成器。它會(huì)根據(jù)你選擇的PyTorch版本、CUDA版本給出對(duì)應(yīng)的conda或pip命令。例如# Conda 安裝 PyTorch 1.13 CUDA 11.7 conda create -n pytorch_1.13_cuda11.7 python3.9 conda activate pytorch_1.13_cuda11.7 conda install pytorch torchvision torchaudio pytorch-cuda11.7 -c pytorch -c nvidia這種方式會(huì)在conda環(huán)境內(nèi)部安裝匹配的CUDA運(yùn)行時(shí)庫(kù)與系統(tǒng)CUDA隔離互不干擾。你只需要確保系統(tǒng)驅(qū)動(dòng)足夠新即可。對(duì)于TensorFlow TensorFlow 2.x之后其GPU版本與CUDA/cuDNN的綁定也非常嚴(yán)格。務(wù)必查閱 TensorFlow官方安裝指南 中的版本對(duì)應(yīng)表。同樣推薦使用conda安裝因?yàn)閏onda可以自動(dòng)解決所有復(fù)雜的依賴(lài)。# Conda 安裝 TensorFlow 2.10 CUDA 11.2 conda create -n tf_2.10_cuda11.2 python3.9 conda activate tf_2.10_cuda11.2 conda install tensorflow-gpu2.10 cudatoolkit11.2 cudnn -c conda-forge核心心得將系統(tǒng)CUDA視為一個(gè)“基礎(chǔ)供給”和“編譯工具”而將深度學(xué)習(xí)框架所需的CUDA環(huán)境通過(guò)conda或docker進(jìn)行隔離管理。系統(tǒng)層面保持一個(gè)較新、穩(wěn)定的驅(qū)動(dòng)和一個(gè)你常用的CUDA Toolkit版本用于編譯自定義CUDA擴(kuò)展。具體的項(xiàng)目環(huán)境通過(guò)創(chuàng)建獨(dú)立的conda環(huán)境來(lái)匹配其所需的精確版本。這樣你可以在不同項(xiàng)目間無(wú)縫切換而無(wú)需反復(fù)修改系統(tǒng)的.bashrc文件徹底告別版本沖突的噩夢(mèng)。這套組合拳是我在多年維護(hù)多GPU服務(wù)器和開(kāi)發(fā)環(huán)境后總結(jié)出的最穩(wěn)定、最高效的實(shí)踐。