在 arm64 平台上做 NVIDIA 驱动安装和你在 x86 笔记本上敲一句apt install nvidia-driver的体验完全不是一回事。x86 那边闭着眼睛装、大不了重装系统的操作换到 ARM 上就变成官方.run包分成 x86_64 和 aarch64 两个互不通用的版本仓库里能不能搜到驱动包取决于你跑的是哪一类 ARM 系统内核模块还得针对当前正在运行的那个内核现场编译。任何一环对不上屏幕给你的就是那句让人血压升高的nvidia-smi has failed because it couldnt communicate with the NVIDIA driver。这篇内容写给三类人手里管着 ARM 服务器、要给挂在 PCIe 上的专业卡装驱动的一线运维在 Jetson 这类 SoC 集成平台上做开发、差点把桌面版驱动包刷上去的工程师以及在麒麟、统信这类企业级 Linux 上做离线部署、被断网环境逼着走本地包路线的实施人员。我不按第一步第二步的说明书节奏写而是按我实际跑过的顺序把为什么这么选、哪一步最容易翻车、翻车后怎么定位讲透命令都给到能直接抄的程度。1. arm64 上装 NVIDIA 驱动的现实处境1.1 从一句报错看架构差异到底差在哪很多人第一次在 ARM 机器上碰到nvidia-smi has failed because it couldnt communicate with the NVIDIA driver第一反应是驱动没装好于是重复安装、换版本、重装系统折腾两天。其实这句话的准确含义是用户态工具nvidia-smi找不到可以对话的内核模块。它描述的是用户态和内核态之间断了联系而不是驱动包没下载成功。在 arm64 上这条链路比 x86 长得多断点也多得多。第一个差异是二进制不通用。aarch64 的 ELF 格式、调用约定、寄存器传参规则和 x86_64 完全不同NVIDIA 官网下载页上标的Linux aarch64和Linux 64-bit是两个独立产物拿错直接报cannot execute binary file。第二个差异是内核编译参数其中**页大小PAGE_SIZE**最容易被忽略。ARM64 内核允许 4K、16K、64K 三种页大小模块在加载时会校验这个值用getconf PAGESIZE就能看到当前系统用的是哪种。页大小不一致的模块insmod阶段就会拒绝dmesg里留下invalid module format之类的记录。x86 上你几乎不用操心这件事因为那边基本只有 4K 一种选择。第三个差异来自生态x86 上发行版仓库里躺着十年份的驱动包ARM 上这条路要看平台脸色。所以第一步不是急着装而是先搞清楚我手上这台机器属于哪一类。1.2 三类 arm64 设备三条完全不同的路径按我的经验把 arm64 平台分成三类选型思路会立刻清晰。第一类是 Tegra 集成平台比如 Jetson 系列。GPU 直接做在 SoC 里面不在 PCI 总线上lspci根本看不到它。这类平台的驱动是随 L4TLinux for TegraBSP 一起刷进去的nvidia-smi在部分版本上甚至不存在官方提供的监控工具是tegrastats。我见过最常见的翻车方式就是有人从官网下载通用的 aarch64.run包直接在 Jetson 上跑安装脚本把 BSP 自带的驱动和图形栈覆盖掉结果设备起不来、需要重新刷机。这类平台的原则只有一句驱动跟着 BSP 走不要单独装升级走 JetPack 或 SDK Manager 的官方流程。第二类是 ARM 服务器搭配 PCIe 独立卡典型形态是 ARM CPU 加专业计算卡走 SBSAServer Base System Architecture路径。这类机器上 GPU 是标准 PCIe 设备lspci能看到驱动来源是 CUDA 仓库的sbsa分支或者官方 aarch64.run包本篇文章后续的实操主要针对这一类。第三类是企业级 Linux 发行版上的整机交付机器是 ARM 架构系统是麒麟、统信这一类通常由整机厂商提供适配好的驱动包或者安装脚本。这类环境往往断网走离线包或.run包路线依赖项需要自己提前备齐。1.3 三条路线的选型对照选路线之前先看清楚代价这张表是我自己决策时用的路线适用平台优势主要风险典型入口BSP / JetPackJetson 等 Tegra 平台与内核、图形栈整体匹配单独覆盖会破坏整机环境SDK Manager、L4T 升级包发行版或 CUDA 仓库联网 ARM 服务器依赖自动解析可 DKMS 自动重建仓库版本偏旧或架构路径选错apt install cuda-drivers官方 aarch64.run离线环境、特殊内核版本自由无需外网依赖手工解决升级内核后要手动重建NVIDIA-Linux-aarch64-*.run厂商适配包企业级发行版整机与系统内核经过验证版本更新慢绑定厂商支持厂商提供的 deb / rpm我个人在能联网的服务器上坚定选仓库路线理由很实在内核一升级DKMS 会在新内核的 postinst 阶段自动重建模块你不用守夜。.run包的优势是版本可控代价是每次内核升级都得自己记着重建忘一次就是一次线上故障。2. 上机前的信息采集与前置准备2.1 五分钟摸清家底别急着敲安装命令信息采集这一步我见过太多人跳过然后对着报错猜了两天。按顺序跑下面这几条把结果抄在一个文本文件里后面排查全靠它。uname -m # 必须输出 aarch64 uname -r # 运行中的内核版本记下来 cat /etc/os-release # 发行版与版本号 getconf PAGESIZE # 4K/16K/64K模块必须匹配 lspci -nn | grep -i -E vga|3d|display # 能否看到 NVIDIA 设备及其 ID lspci -k -d ::0300 # 当前绑定的是哪个内核驱动 lsmod | grep -E nvidia|nouveau # 现有模块加载情况 cat /proc/device-tree/model 2/dev/null # 平台型号Jetson 上必看 cat /etc/nv_tegra_release 2/dev/null # Jetson 上的 L4T 版本 dpkg -l | grep -i nvidia # 系统里已存在的 NVIDIA 相关包这几条命令里信息量最大的是lspci -k。它输出的Kernel driver in use字段会告诉你这块卡此刻归谁管如果是nouveau说明开源驱动还占着位置如果是vfio-pci说明卡被直通给虚拟机了如果是空的说明还没有任何驱动认领它。这三种状态对应的处理方式完全不同先看清楚再动手能省掉大量无用功。还有一条容易漏的apt policy nvidia-driver-535把版本号换成你想装的。仓库方式安装时这条命令告诉你当前源里有没有这个包、能装哪个版本、要不要从别的分支拉。arm64 上经常出现包名存在但架构不匹配的情况apt-cache madison nvidia-driver-535的输出里带不带arm64一眼就能判断。2.2 编译工具链与内核头文件这是 DKMS 的地基不管走仓库还是走.run包只要用到 DKMS就必须有编译环境。最小集合是三项编译器、make、以及与当前运行内核版本完全一致的头文件包。sudo apt update sudo apt install -y build-essential dkms sudo apt install -y linux-headers-$(uname -r)最后那条命令是重点。linux-headers-$(uname -r)里展开出来的内核版本必须和你uname -r的结果一模一样不能差一个小版本号。我踩过一次坑系统里有linux-headers-5.15.0-91-generic但当前跑的其实是5.15.0-88因为有人锁了内核版本不升级。DKMS 编译时找不到对应的头文件目录日志里报Unable to find the kernel source tree但安装过程的其他步骤全部显示成功最后nvidia-smi照样报那句communicate错误。头文件对不上前面所有步骤都是白做。假设你确实找不到匹配的头文件包可以检查一下/lib/modules/$(uname -r)/build这个软链接是否存在且指向真实目录。这个链接断了症状和头文件缺失一模一样。离线环境下的准备工作要提前做在能联网的同架构机器上用apt-get download把build-essential、dkms、linux-headers-*及其全部依赖的 deb 包拉到本地。注意apt-rdepends或者apt-get install --download-only才能把依赖链一次性拉全只下载单个包必然在离线机上卡在缺依赖。2.3 让 nouveau 让路把 Secure Boot 的事提前处理掉nouveau是内核自带的开源 NVIDIA 驱动它会在系统启动时抢先绑定设备。专有驱动安装时如果发现设备被占用轻则安装脚本警告重则模块加载失败。sudo tee /etc/modprobe.d/blacklist-nouveau.conf /dev/null EOF blacklist nouveau options nouveau modeset0 EOF sudo update-initramfs -u写完记得update-initramfs -u否则黑名单配置进不了 initramfs重启后 nouveau 还是会先加载。验证方法是重启后跑lsmod | grep nouveau输出为空才算干净。Secure Boot 是另一个高频拦路虎。开启状态下未签名或签名不匹配的内核模块会被内核直接拒绝加载dmesg里能看到Loading of unsigned module is rejected或者Key was rejected by service。先确认状态mokutil --sb-state如果显示SecureBoot enabled你有两条路。第一条是关闭 Secure Boot在 UEFI 设置里操作ARM 服务器一般也是这套界面物理接触或者带外管理都能改。第二条是走签名流程Ubuntu 系的 DKMS 有一定概率能配合系统自带的 MOK 密钥自动完成签名前提是/var/lib/shim-signed/mok/下的密钥存在且已用mokutil --import导入过。我个人的建议是生产环境如果安全策略允许直接关掉更省事因为它能同时规避后续每一次内核升级带来的重新签名风险如果必须保留就要在建机器阶段一次性把 MOK 流程走通别等出问题再补。注意关闭 Secure Boot 属于改动固件安全配置涉及服务器资产时请先确认所在环境的变更流程不要自己拍板就改。3. 仓库方式安装有网环境下的主路径3.1 驱动版本怎么挑别只看数字大小版本选择这件事规律其实很清晰只是网上教程大多不讲。NVIDIA 的 Linux 驱动按分支组织编号首段代表分支比如 470、535、550、570 这些都是不同的分支每个分支有自己的生命周期。选版本时先想清楚三件事显卡型号是否被这个分支支持、CUDA 工具链要求哪个最低版本、以及发行版内核能不能和它配合。一个实用的判断顺序是先定 CUDA 版本如果这是台计算节点再根据 CUDA 版本反查驱动最低要求最后在仓库里找满足这个最低要求的最老分支。选满足要求的最老分支是有道理的——老分支经过更长时间的线上验证出问题的概率更低而新的功能对你来说大概率用不上。仓库入口分两种。发行版自带源适合通用场景命令简单sudo apt update sudo apt install -y nvidia-driver-535CUDA 官方仓库适合服务器场景包更新更快、分支更全aarch64 上要注意选对路径ARM 服务器走sbsa分支# 以 Ubuntu 22.04 arm64 服务器为例注意路径中的 sbsa wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/sbsa/cuda-keyring_1.1-1_all.deb sudo dpkg -i cuda-keyring_1.1-1_all.deb sudo apt update sudo apt install -y cuda-drivers这里有个细节值得说cuda-drivers是一个元包它会拉取当前分支里最新的一组驱动组件如果你想要固定版本应该装nvidia-driver-xxx而不是元包否则某天一次apt upgrade就悄悄把你升级到了新分支。至于ubuntu-drivers devices这个工具aarch64 上不一定存在别把排查方向押在它身上。3.2 安装过程拆解重点看 DKMS 编译日志apt install跑起来后屏幕上大部分输出是 apt 在解包和配置真正决定成败的是 DKMS 那几行。安装结束后立刻做两件事dkms status ls /var/lib/dkms/nvidia/dkms status正常应该输出形如nvidia/535.154.05, 5.15.0-91-generic, aarch64: installed的状态行。如果显示built但没有installed说明编译过了但模块没装进内核模块目录如果连状态行都没有说明 DKMS 根本没被触发。编译失败时日志在/var/lib/dkms/nvidia/版本/build/make.log。这个文件是排查的主力常见的失败原因有三种头文件不匹配报找不到include/linux/...、编译器版本不支持某些语法用太老的 gcc 编新驱动、以及并发编译内存不足ARM 机器内存小的时候make -j崩掉日志里会出现被 kill 的痕迹。第三种情况可以在 DKMS 配置里限制并发数或者临时加大 swap。3.3 装完必须做的三项验证不验证就宣布装好是大忌。三项验证按顺序做任何一项不过后面都不用看lsmod | grep nvidia # 1. 模块是否真的加载进内核 cat /proc/driver/nvidia/version # 2. 内核模块版本是否与预期一致 nvidia-smi # 3. 用户态与内核态是否打通第一项看到nvidia、nvidia_modeset、nvidia_uvm、nvidia_drm几个模块才算真正加载。第二项能读出内容说明 procfs 接口正常注册了。第三项是最直观的能打出显卡型号、驱动版本、显存占用和进程列表就说明整条链路通了。还有两个小检查值得顺手做ls /dev/nvidia*应该能看到/dev/nvidia0、/dev/nvidiactl、/dev/nvidia-uvm这些设备节点lspci -k -d ::0300里的Kernel driver in use应该变成了nvidia。这两条能帮你区分驱动装好了但设备没被接管和整条链路都正常。4. 离线安装runfile 与本地包4.1 离线环境要提前打包的清单断网环境的安装成败取决于准备阶段。我整理过一份最小清单按这个准备基本不会缺东西类别内容说明编译工具build-essential、gcc、make版本要与系统匹配架构必须是 arm64内核支持linux-headers-$(uname -r)、dkms头文件版本必须完全一致驱动本体.run包或本地 deb 包架构标识必须是 aarch64依赖库libc6、libpcre、zlib等用dpkg -I查驱动包的依赖配套组件容器场景需要nvidia-container-toolkit的 arm64 包只在用容器时准备备份旧驱动包、/etc/modprobe.d下的配置回滚要用准备 deb 包时用apt-get install --download-only配合--reinstall把依赖链一次性拉到本地目录或者在能联网的同架构同版本系统上装一台镜像机把整个/var/cache/apt/archives拷过去。后一种做法虽然笨但最不容易漏。4.2 runfile 安装的完整流程与参数解释.run包安装必须在没有图形界面运行的状态下做因为安装脚本要替换 GL 相关的库文件X 服务或者 Wayland 组合器正在跑的话会直接冲突。# 1. 切到多用户目标停掉图形会话 sudo systemctl isolate multi-user.target # 2. 确认没有进程占用设备 sudo fuser -v /dev/nvidia* 2/dev/null # 3. 添加可执行权限并安装 chmod x NVIDIA-Linux-aarch64-535.154.05.run sudo ./NVIDIA-Linux-aarch64-535.154.05.run \ --dkms \ --silent \ --no-x-check \ --no-nouveau-check几个参数逐个说清楚。--dkms让安装脚本把模块注册到 DKMS 框架里这样内核升级后能自动重建这个参数强烈建议加上不加的话每次内核更新都要重新跑一遍安装脚本。--silent是无人值守模式日志写到/var/log/nvidia-installer.log适合脚本化部署但首次安装不建议用因为你看不到交互式提问里的关键信息。--no-x-check跳过对 X 服务的检查在服务器上没装图形界面时很有用。--no-nouveau-check跳过 nouveau 检测前提是你已经确认 nouveau 被黑名单挡住了。还有两个参数视场景而定--no-opengl-files不安装 OpenGL 相关的库文件服务器上如果要保留系统自带的 Mesa 软件渲染能力加这个参数可以避免覆盖--no-drm不安装内核 DRM 模块只有在完全不涉及显示输出、纯计算场景下才考虑加了之后nvidia-drm.modeset那套就没法用了。安装完看日志尾部出现Installation of the kernel module for the NVIDIA Accelerated Graphics Driver且没有 error再重启。卸载用同一个包执行sudo ./NVIDIA-Linux-aarch64-535.154.05.run --uninstall比手工删文件靠谱得多。注意.run包和发行版仓库的 deb 包不要混用。先用仓库装过、再用.run覆盖很容易留下一堆互相冲突的库文件和 dpkg 记录后续升级会出各种莫名其妙的问题。要换路线先彻底卸载再换。4.3 内核升级之后的重建动作这是.run路线最需要纪律性的地方。系统每次升级内核旧的内核模块目录会保留但新内核目录下没有对应模块默认启动项切到新内核后nvidia-smi立刻报communicate错误症状和驱动没装一模一样。配了--dkms的话dkms autoinstall通常会被内核包的 postinst 钩子自动触发你不用管。但触发失败的情况不少见所以我的做法是主动加一道保险写个定时检查脚本核心逻辑就是比对当前内核下有没有nvidia.ko#!/bin/bash KDIR/lib/modules/$(uname -r) if ! find $KDIR -name nvidia.ko* | grep -q .; then dkms autoinstall -k $(uname -r) || \ echo nvidia module rebuild failed for $(uname -r) | tee -a /var/log/nvidia-rebuild.log fi不用做成定时任务也行把它挂在配置管理工具的收尾步骤里或者干脆在升级内核前手动跑一遍dkms status确认状态。关键是意识到内核升级和驱动失效之间的因果关系很多线上事故就是运维只做了内核升级、没做验证造成的。5. 报错排查实录5.1 nvidia-smi 报错的六条排查线同一句报错背后可能是六种完全不同的原因。我把它整理成一张速查表按检查成本从低到高排序号检查命令典型输出根因判断1lsmod | grep nvidia无输出模块没加载看 DKMS 状态2dkms status无 nvidia 条目或 built编译失败或未注册查 make.log3dmesg | grep -i nvidiaversion magic ... should be模块与运行内核不匹配需重建4dmesg | grep -i -E secure|keyKey was rejectedSecure Boot 拒绝加载签名或关闭5lspci -k -d ::0300Kernel driver in use: vfio-pci设备被直通占用6ls /dev/nvidia*目录为空设备节点未创建模块与设备未绑定第一、二条覆盖了绝大多数情况先跑这两条八成问题就能定位。第三条那个version magic报错特别值得说它意味着磁盘上的模块是针对另一个内核版本编译的通常是内核升级后没重建导致的解决办法就是dkms autoinstall -k $(uname -r)或者重跑.run安装脚本。第四条在 ARM 服务器上出现频率比 x86 高因为不少 ARM 固件默认开启 Secure Boot。第五条只在虚拟化环境出现GPU 被直通给虚拟机后宿主机就看不到它了这时候在宿主机上装驱动是徒劳的要先确认卡的归属。第六条常见于模块加载了但设备初始化失败dmesg里通常有NVRM开头的详细错误需要结合具体硬件信息看。5.2 图形栈相关的模块加载报错如果在 Xorg 日志里看到Failed to load module glxserver_nvidia (module does not exist, 0)注意这条报错和硬件驱动本身没关系它说的是 Xorg 找不到 NVIDIA 提供的 GLX 扩展模块。成因主要有两种一是驱动版本太老而发行版的 Xorg 服务端版本较新老驱动不再提供这个路径下的模块文件二是驱动只装了内核部分用户态图形库没装全比如用了--no-opengl-files参数又需要图形加速。处理思路是先确认文件是否真的存在find /usr/lib -name libglxserver_nvidia* 2/dev/null找不到就说明驱动包没提供需要换一个支持当前 Xorg 版本的驱动分支或者补齐用户态组件。纯计算节点其实不需要关心这个报错因为它只影响图形渲染对 CUDA 计算毫无影响。还有个相关参数值得记住nvidia-drm.modeset1。这个内核模块参数在需要用 DRM 接口做显示输出的场景下必须打开配置方式是在/etc/default/grub的GRUB_CMDLINE_LINUX里追加然后更新引导配置并重启。在新版桌面环境上跑图形加速不加这个参数会出现画面撕裂、合成器回退到软件渲染之类的现象。5.3 容器、虚拟化与设备直通带来的额外变量容器场景的坑在于容器里没有内核模块它用的是宿主机的。所以容器里跑nvidia-smi报错问题大概率在宿主机或者在于设备节点和用户态库没被挂进去。aarch64 上的容器工具链要装对应架构的版本sudo apt install -y nvidia-container-toolkit sudo systemctl restart docker docker run --rm --gpus all nvidia/cuda:12.2.0-base-ubuntu22.04 nvidia-smi如果这条命令在 x86 上跑通了换成 arm64 镜像却失败先检查三件事镜像本身是不是 arm64 架构docker inspect看 Architecture 字段、宿主机的nvidia-container-cli版本是否支持当前驱动、以及/dev/nvidia*节点在--gpus all模式下有没有被正确挂载。我遇到过一次失败案例原因是宿主机的驱动版本比 toolkit 支持的上限高出一大截换成新版 toolkit 就通了。GPU 直通场景要额外注意顺序宿主机必须先加载vfio-pci并把设备绑给它加载了 nvidia 驱动的话设备会被抢走。反过来如果卡是用在宿主机上的就一定不能让它绑到vfio-pci。这两者互斥配置前先想清楚卡归谁用。还有一类容易被误判的情况把国产 ARM 平台 国产显卡的组合当成 NVIDIA 场景来排查。如果lspci里看到的厂商 ID 不是10de那它压根不是 NVIDIA 设备nvidia-smi当然用不了得用厂商自己的工具链。排查前先确认厂商 ID能省掉一整天的无效折腾。6. 版本维护与几条实操经验6.1 锁定版本把能不动就不动落到命令上生产环境的驱动版本管理我的原则很简单装完之后立刻锁死除非有明确的新功能需求或者安全公告否则不主动升级。锁定用 apt 的标记功能sudo apt-mark hold nvidia-driver-535 cuda-drivers apt-mark showholdshowhold的输出就是你当前锁定的清单装在机器上的驱动、CUDA 运行时、容器工具链都值得进这个清单。相应地内核也应该锁因为内核一动驱动模块就要重建重建就有失败概率。整套逻辑是内核和驱动作为一个整体版本单元来管理要升级就一起升、升完立刻验证不要今天升内核、下周再想起来升驱动。升级顺序建议是先在测试机上走一遍完整流程确认nvidia-smi和实际业务负载都正常再动生产。升级动作本身不要用apt upgrade一把梭明确指定包名和版本号更安全。6.2 我踩过的坑整理成一张速查表现象我当时的误判真实原因处置方式nvidia-smi报 communicate 错误以为驱动包损坏内核升级后模块没重建dkms autoinstall -k $(uname -r)安装日志全绿但功能不可用以为装好了头文件版本与内核不一致模块没真编出来装匹配的linux-headers重启后驱动失效以为系统有 bugSecure Boot 拒绝未签名模块关闭 Secure Boot 或走签名Jetson 上 GPU 认不到以为驱动没装平台驱动随 BSP不在 PCI 总线上用 BSP 自带方式别覆盖容器内nvidia-smi失败以为容器工具没装镜像架构不对拉了 x86 镜像换 arm64 镜像再挂设备页面卡顿、合成器异常以为显卡驱动坏了nvidia-drm.modeset未开启内核参数追加后重启这张表里的每一条都是真实踩出来的。其中最冤的是第二条因为安装过程没有任何报错全靠事后验证才发现。6.3 有些问题其实和驱动没关系排查时先划清边界能省掉大量时间。下面这些现象经常被误认成驱动故障其实不搭边。第一类是串口和调试器的驱动比如 CH340、FT232 这类 USB 转串口芯片还有 ST-Link、J-Link 这类仿真器。它们和 GPU 驱动共享的只有内核模块这个词报错信息完全不同。串口驱动装不上应该查lsusb、dmesg和 tty 设备节点跟 NVIDIA 一点关系没有。第二类是 GPU 内部的 SRAM 相关显示项。在某些监控工具里能看到 SRAM 使用率那是芯片内部的片上存储属于硬件运行指标不是驱动安装问题看到它异常别往驱动上想。第三类是 Windows 平台的AppData\Local\NVIDIA\DXCache目录。这是 Windows 上 DirectX 着色器缓存的位置缓存损坏时的处理方式是删掉目录让系统重新编译。如果你现在做的是 arm64 上的 Linux 驱动安装这个路径和你完全无关搜索结果里混进来纯属关键词撞车不要被带偏方向。第四类是 FPGA 工具链的板卡识别问题比如一些 EDA 工具在 ARM 上找不到下载器那查的是 udev 规则和 USB 权限nvidia-smi在这类问题上给不出任何有用信息。6.4 长期维护时我会固定做的几件事最后说几条我在长期维护里固定下来的习惯不复杂但真能减少故障。每次改动之前先记录状态nvidia-smi的输出、dkms status、lsmod结果存一份到变更记录里出问题时能对比出差异。这不是形式主义我靠这份对比定位过一次驱动版本被同事悄悄升级的问题。内核和驱动升级后一定要跑一遍真实业务负载做验证只跑nvidia-smi不够。nvidia-smi通了只说明管理接口通了实际计算任务能不能跑、显存分配有没有异常、多卡之间的通信是否正常都得靠业务侧的任务来验证。机器上保留一份可回滚的旧驱动包和当前内核的模块备份。ARM 平台上驱动装崩了又没网、又没备份基本只能重装系统代价太大。还有一点如果这台机器同时承担图形显示和计算任务尽量把两者的驱动需求想清楚再装因为图形侧需要的那几个用户态模块和计算侧的需求不完全一致先规划好能避免后期推翻重来。