GPU 驅動(NVIDIA 專有)—— 版本漂移、升級與內顯¶
GPU 工作站的 sysadmin 問題:什麼都沒改,為什麼 nvidia-smi 突然壞了?要怎麼阻止背景更新在訓練跑到一半時再做一次?
本頁記錄 nvidia-driver-drift-check 輔助工具、52unattended-upgrades-local 黑名單,以及這台機器的顯示拓撲。它是 hardware.md 往上一層(到驅動)的姊妹篇,也對應 scheduled-jobs.md(觸發那次升級的就是它)。
僅適用於 Linux + NVIDIA 專有驅動。Nouveau 與純 AMD 機器不受本頁任何內容影響。
一張表看懂故障模式¶
NVIDIA 專有驅動是必須版本一致的兩半,而執行中只有其中一半能被替換:
userspace(libcuda.so、libnvidia-ml.so) |
kernel module(nvidia.ko) |
|
|---|---|---|
| 執行時的本體 | 磁碟上的檔案,各 process 各自 mmap | 載入 kernel 位址空間的程式碼 |
| 能否多版本並存 | ✅ 可以 —— 每個 process 映射自己那份 | ❌ 不行 —— 只有一份,獨佔 PCI 裝置 |
apt upgrade 做了什麼 |
unlink + 建新檔。unlink 只刪名字,只要還有 process 映射著,inode 就活著 |
什麼都沒做 —— 記憶體裡那份原封不動 |
| 如何換版 | 不需任何動作,新 process 自然拿到新檔 | rmmod,需要 refcount 歸零 |
所以升級對所有已在執行的 process 完全透明,對之後啟動的每一個 process 都是致命的:
CUDA 的 cuInit 回傳 804(CUDA_ERROR_COMPAT_NOT_SUPPORTED_ON_DEVICE —— 不是 803,803 才是 CUDA_ERROR_SYSTEM_DRIVER_MISMATCH)。
唯一的修復是重新開機。 在螢幕由同一張 GPU 驅動的機器上,沒有「重載模組」這條路 —— Xorg、gnome-shell、mutter 以及每一個 GPU 加速的應用程式都開著 /dev/nvidia*,modprobe -r nvidia 只會回 Module nvidia is in use,refcount 是好幾百。
讓人意外的不對稱性¶
往回走不需要重開機。把新 process 指向舊的 userspace,它就會跟仍然載入著的舊模組一致:
# 暫時性、per-process,不安裝到系統
mkdir -p /tmp/nvshim && cd /tmp/nvshim
apt-get download nvidia-utils-580=<舊版本> libnvidia-compute-580=<舊版本>
for f in *.deb; do dpkg-deb -x "$f" root/; done
LD_LIBRARY_PATH=$PWD/root/usr/lib/x86_64-linux-gnu root/usr/bin/nvidia-smi
這能救回 nvidia-smi,也能讓新的 CUDA process 啟動,且完全不影響執行中的 job。但它修不了 OpenGL/EGL/Vulkan(shim 裡沒有 libGLX_nvidia/libEGL_nvidia),而且 nvtop 會靜默降級成只顯示內顯。請把它當成監控用的權宜之計,不是修復 —— 每一個用它啟動的 process 都會再 pin 住一個已刪除的 inode,把「不能重開機」的窗口愈拉愈長。
舊版本很快就會從 archive pool 消失。當
apt-get download <pkg>=<舊版>開始回 404 時,snapshot.ubuntu.com是可靠的來源。
/proc/driver/nvidia/version一變就要立刻拆掉 shim,而不是「重開機之後」—— 一旦模組換新,shim 會造成反方向的不一致,而錯誤訊息長得一模一樣。
漂移偵測¶
nvidia-driver-drift-check 比對載入中的模組與磁碟上的 userspace,不一致時回傳非零。
nvidia-driver-drift-check # 完整 banner + 列出重開機會殺掉哪些 process
nvidia-driver-drift-check --quiet # 只印一行,便宜到可以掛在 shell 啟動
| 讀取 | /sys/module/nvidia/version(fallback /proc/driver/nvidia/version)對比 /usr/lib/*-linux-gnu/libnvidia-ml.so.<ver> |
| exit 0 | 一致,或根本沒載入 NVIDIA 驅動(所以在任何機器上無條件執行都安全) |
| exit 1 | 漂移 —— 新的 CUDA process 會失敗 |
| 消音單一漂移 | echo "580.159.03->580.173.02" > ~/.cache/nvidia-drift-ack —— 任一版本改變就自動恢復警告 |
為什麼 --quiet 不列 process:fuser /dev/nvidia0 要走遍整個 /proc(本機約 240 ms)。漂移在重開機前無法解除,所以每開一個 terminal 就噴 17 行 banner,只會訓練你忽略它。一行,不跑 fuser。
透過 ~/.config/zsh/tools/60_nvidia_drift.zsh 掛到 shell 啟動。不要放 /etc/update-motd.d/ —— 腳本寫的是 stderr,而 run-parts 只把 stdout 收進 /run/motd.dynamic,banner 根本不會出現,卻在每次登入多花 240 ms。
預防:讓 unattended-upgrades 別碰驅動¶
/etc/apt/apt.conf.d/52unattended-upgrades-local 把驅動整套列入黑名單。用編號較高的 drop-in,而不是改 50unattended-upgrades —— 後者是 conffile,每次升級都會跳出詢問。
四個很容易寫錯的語法事實:
| 規則 | 原因 |
|---|---|
| apt.conf 的 list 是附加,永遠不是覆蓋 | 50unattended-upgrades 附的 Package-Blacklist 是空的,所以 drop-in 就是最終清單。#clear 才是刪除。 |
開頭不要 ^ |
u-u 會把每條轉成 APT pin '/^' + regex + '/' → 變成 /^^nvidia-/ |
結尾不要 $ |
multiarch 名稱帶 :i386(如 libnvidia-compute-580:i386),$ 比不到 |
pattern 走 Python re.match |
只錨定開頭,不錨定結尾 |
驗證最終生效的清單,並用真實套件名模擬比對:
apt-config dump Unattended-Upgrade::Package-Blacklist # 沒輸出 = 語法錯
sudo unattended-upgrade --dry-run --debug 2>&1 | grep -iE 'blacklist|nvidia'
--dry-run對執行中的訓練是安全的 —— 所有 commit 點都包在if not dry_run:裡,它只設Debug::pkgDPkgPM=1。
刻意不列入黑名單的:nvidia-settings、nvidia-prime、libnvidia-egl-wayland1 是 Ubuntu main 的套件,版本與驅動分支無關 —— 它們該繼續拿安全更新。用大範圍的 ^nvidia-/^libnvidia- 會把它們一起凍住。
這只擋得住 unattended 路徑。 PackageKit / gnome-software 不會讀 Unattended-Upgrade::Package-Blacklist;桌面更新程式若列出 nvidia 套件,請取消它,改走下面的儀式。apt-mark hold 兩邊都能擋,代價是連你自己有意識的升級也一起擋掉,而且每次 apt 都會印 "kept back"。
真要用
apt-mark hold,清單請用${binary:Package}而不是${Package}—— 後者會丟掉 arch 限定詞,靜默地讓每個:i386套件沒被 hold 到,而那正好是驅動的一半。
安全升級儀式¶
裝了黑名單之後,驅動更新會靜默累積。每月做一次:
sudo apt update && apt list --upgradable 2>/dev/null | grep -i nvidia
fuser -v /dev/nvidia* # 排空:不能有使用者程序
sudo apt full-upgrade
dkms status | grep nvidia # 必須涵蓋你接下來要開的那個 kernel
sudo reboot # <- 永遠被跳過的那一步
nvidia-driver-drift-check && nvidia-smi
第 5 步才是重點。升級在 06:02 落地、重開機兩週後才發生,就是本頁存在的原因。
第 3 步別寫成
apt install --only-upgrade '~nnvidia'——~n是不錨定的子字串比對,會掃進所有名稱含 "nvidia" 的套件,transaction 比看起來大得多。
長時間執行的 GPU 工作¶
從終端機模擬器啟動的多日訓練,活在那個終端機的 cgroup 裡(/user.slice/.../app-gnome-alacritty-<pid>.scope)。關掉視窗或登出就沒了 —— nohup 只擋 SIGHUP。
tmux new -s train 'bash run.sh'
# 或
systemd-run --user --scope --unit=train bash run.sh
loginctl enable-linger $USER
按下重開機之前,先確認代價:
這台機器的顯示拓撲(David-Ubuntu)¶
| dGPU | 01:00.0 NVIDIA GA102 [RTX 3090] 10de:2204 —— 驅動唯一有接線的輸出(card1-HDMI-A-1) |
| iGPU | 0c:00.0 AMD Raphael 1002:13c0 —— BIOS 有開,沒接任何螢幕,所有輸出皆 disconnected |
prime-select |
on-demand |
| Session | X11 |
沒有 iGPU fallback。 若 NVIDIA 那一套無法拉起 X,這台機器就完全沒有畫面。這正是「就地升級驅動」在這裡特別危險的原因:從升級到重開機之間,Xorg 握著約 94 個已刪除的 mapping(libglxserver_nvidia.so.<舊>、libEGL_nvidia.so.<舊>…),而任何 X 重啟 —— 登出、切換使用者、systemctl restart gdm3、suspend —— 回來時都會用新的函式庫去配舊的模組,直接黑畫面。
amdgpu 從來沒壞過 —— blacklist 已移除¶
2026-07-25 因為一個 -22 probe 失敗,這台機器曾加過
/etc/modprobe.d/blacklist-amdgpu.conf。後來查明那是開在 recovery mode
造成的假象,不是驅動的 bug:
amdgpu 0000:0c:00.0: probe with driver amdgpu failed with error -22
$ cat /proc/cmdline
… ro recovery nomodeset dis_ucode_ldr # <- nomodeset 才是原因
2026-07-29 拆掉 blacklist、在正常 cmdline 下手動載入模組,一次定讞:
$ sudo modprobe amdgpu
[drm] Initialized amdgpu 3.64.0 for 0000:0c:00.0 on minor 0
… ring comp_1.3.1 / kiq_0.2.1.0 / sdma0 / vcn_dec_0 / vcn_enc_0.0 / jpeg_dec
amdgpu 0000:0c:00.0: [drm] Cannot find any crtc or sizes # 預期:沒接螢幕
零錯誤、兩顆 GPU 都列舉出來、nvidia 不受影響。那幾行看起來很嚇人的
ATPX / vga_switcheroo 訊息,在健康的開機一樣會出現。詳見
pitfalls/recovery-mode-resume-keeps-nomodeset-in-cmdline.md。
載入 GPU 驅動會重啟 X session。 執行中的 Xorg 會透過 udev 接手新的 DRM 裝置,約 10 秒後 gdm 直接重建整個 session —— 所有圖形程式都會死。當成測試手段, 它只有「牆鐘時間」比重開機短,中斷程度並沒有比較輕。
57 分鐘的那次開機仍未解釋¶
另外有一次在正常 cmdline 下的開機,從 17:17:53 跑到 18:14:38 都沒進到可用的桌面。 amdgpu 現在已被排除。已確立的事實:
- amdgpu 在 17:17:55 乾淨初始化,而且今天也能隨時正常載入。
dbus-daemon … Failed to activate service 'org.freedesktop.Notifications': timed out (service_start_timeout=120000ms),從 17:54 起每約 2 分 10 秒重試一次。- 那串
amdgpu … [drm] *ERROR* Not enough memory for command submission!的時間戳是 18:14:38,也就是那次開機的最後一秒(強制重開的當下),不是開機過程中。 - 那次 GDM 起的是
gdm-wayland-session,而在它之前跑了 12 天的 session、 以及之後每一次 session,全都是 X11。nvidia_drm modeset=1有設,/etc/gdm3/custom.conf也沒有WaylandEnable=false,所以在 kernel + 驅動 雙雙跳版之後,沒有任何東西阻止 GDM 選擇 Wayland。
X11 → Wayland 的翻轉是唯一存活的假說。若長時間開機重演,請釘住 session 類型, 不要動 GPU 驅動:
……或直接在登入畫面的齒輪選單選「Ubuntu on Xorg」。
把螢幕改插到內顯會比較好嗎?¶
那會把桌面的 VRAM 從 3090 上釋放出來,但那是 24576 MiB 裡的約 464 MiB(1.9%)—— Xorg 112、gnome-remote-desktop 260、gnome-shell 24,其餘都是個位數。不會是 OOM 與否的分水嶺。
真正的理由是隔離:訓練 OOM 不再把桌面一起拖走、桌面合成不再搶 SM 時間, 以及最關鍵的一點 —— 可以重啟 X 而不碰 GPU,這會徹底消除上面那個黑畫面陷阱。
現在障礙已排除:amdgpu 正常運作,card0/card1 都列舉得到。剩下的步驟是把線接到
主機板輸出、並在 BIOS 設定主要顯示裝置;目前內顯的所有輸出都是 disconnected。
相關¶
- scheduled-jobs.md ——
apt-daily-upgrade.timer,執行 unattended-upgrades 的就是它 - hardware.md —— 機殼/主機板感測器,再往下一層
~/.dotfiles/bin/nvidia-driver-drift-check/etc/apt/apt.conf.d/52unattended-upgrades-local/etc/modprobe.d/blacklist-amdgpu.conf