1. 为什么要在中标麒麟上折腾CUDA
中标麒麟这个系统,接触过的人都知道,它是基于Linux内核的国产操作系统,主要面向政企和关键行业场景。日常办公、基础服务跑起来没什么问题,但一旦涉及到GPU加速计算,尤其是深度学习训练或者高性能计算任务,事情就变得微妙起来了。CUDA是英伟达的并行计算平台,官方支持列表里主要覆盖的是Ubuntu、CentOS、RHEL这些主流发行版,中标麒麟并不在列。这就意味着,你没法直接照搬官方文档一步步走下来,中间会遇到各种依赖冲突、驱动不匹配、内核版本对不上的问题。
我这次的实际场景是这样的:手头有一台服务器,显卡是某型号的NVIDIA GPU,系统装的是中标麒麟桌面版,内核版本和glibc版本都跟主流发行版有差异。目标是在上面把CUDA工具链跑通,让基于GPU的计算任务能正常执行。整个过程踩了不少坑,从驱动编译到环境变量配置,再到验证环节,每一步都有值得记录的东西。这篇文章就是把这套流程完整梳理出来,给有同样需求的人一个可参考的路径。
需要提前说明的是,中标麒麟有多个版本分支,内核版本从3.x到4.x甚至5.x都有,不同版本之间的差异很大。我这次操作的版本内核是4.x系列,glibc版本相对较新,但依然存在不少兼容性问题。如果你用的是更早的版本,部分步骤可能需要额外调整。另外,显卡型号也会影响驱动选择,老卡和新卡的驱动分支不一样,这个后面会具体说。
注意:整个操作过程中涉及系统级驱动安装和内核模块编译,操作不当可能导致系统无法正常启动。建议在操作前对系统做完整备份,或者至少在虚拟机/测试环境中先跑一遍流程。
2. 动手之前必须摸清的底细
2.1 系统版本与内核信息的确认
很多人拿到机器就急着下载驱动,结果装到一半发现内核版本对不上,或者gcc版本太老编译不过去。我建议在动手之前,先把下面这几条命令跑一遍,把系统底细摸清楚:
# 查看系统版本信息 cat /etc/os-release uname -a cat /proc/version # 查看gcc版本 gcc --version # 查看glibc版本 ldd --version # 查看是否已安装NVIDIA驱动 lsmod | grep nvidia nvidia-smi # 查看显卡型号 lspci | grep -i nvidia这几条命令的输出信息非常关键。uname -a能看到内核版本,这个决定了后面驱动编译时需要的kernel-devel包版本。gcc --version决定了你能不能编译NVIDIA的内核模块,如果gcc版本太老(比如低于4.8),编译过程大概率会报错。ldd --version看glibc版本,这个影响CUDA Toolkit的运行时库能不能正常加载。
我这次遇到的情况是,系统自带的gcc是4.8.5,勉强够用,但kernel-devel包没有预装,需要从系统源里手动安装。另外,系统默认的nouveau开源驱动是加载状态,这个必须在装NVIDIA官方驱动之前禁用掉,否则会冲突。
2.2 显卡型号与驱动分支的匹配
NVIDIA的驱动分支大致可以分成几个系列:对于较新的显卡(比如RTX 20系列及以上),需要用470以上的驱动版本;对于老卡(比如Tesla K系列、M系列),可能需要用到418或者440分支。选错驱动分支的后果就是,装完之后nvidia-smi能跑,但CUDA程序一跑就报错,或者干脆驱动加载失败。
查看显卡型号的命令是lspci | grep -i nvidia,输出里会显示具体的型号代码。拿到型号之后,去NVIDIA官网上查对应的驱动分支。我这次用的卡属于中等偏新的型号,最终选的是470分支的驱动版本。
还有一个细节需要注意:CUDA Toolkit的版本和驱动版本之间有对应关系。比如CUDA 11.x要求驱动版本不低于450,CUDA 10.2要求不低于440。如果你装的驱动版本太低,CUDA Toolkit装上去也用不了。所以正确的顺序是:先确定你要用的CUDA版本,然后根据CUDA版本反推需要的最低驱动版本,再去下载对应的驱动。
2.3 依赖包的提前准备
中标麒麟的软件源里有一些包是必须提前装好的,否则后面编译驱动或者安装CUDA时会卡住。我整理了一个清单:
| 依赖包 | 用途 | 是否必须 |
|---|---|---|
| kernel-devel | 编译NVIDIA内核模块 | 必须 |
| kernel-headers | 内核头文件 | 必须 |
| gcc | 编译器 | 必须 |
| gcc-c++ | C++编译器 | 必须 |
| make | 编译工具 | 必须 |
| dkms | 动态内核模块支持 | 建议 |
| libGLvnd | OpenGL库 | 建议 |
| mesa-libGL | 图形库 | 建议 |
安装命令直接用系统包管理器:
sudo yum install kernel-devel kernel-headers gcc gcc-c++ make dkms这里有个坑:kernel-devel的版本必须和当前运行的内核版本完全一致。如果你之前更新过内核但没有重启,那么uname -r显示的版本和kernel-devel的版本可能对不上。解决办法是重启系统,确保运行的内核和devel包版本一致。
3. 驱动安装:从禁用nouveau到编译内核模块
3.1 禁用nouveau开源驱动
nouveau是Linux社区为NVIDIA显卡开发的开源驱动,系统默认会加载它。但NVIDIA官方驱动和nouveau是互斥的,必须先把nouveau禁用掉。操作步骤如下:
# 查看nouveau是否加载 lsmod | grep nouveau # 创建禁用配置文件 sudo vim /etc/modprobe.d/blacklist-nouveau.conf在文件里写入以下内容:
blacklist nouveau options nouveau modeset=0然后更新initramfs并重启:
sudo dracut --force sudo reboot重启之后再用lsmod | grep nouveau确认一下,如果没有输出,说明禁用成功了。这一步看起来简单,但实际操作的坑在于:有些中标麒麟版本用的是mkinitrd而不是dracut,命令不一样。如果dracut命令不存在,可以试试sudo mkinitrd -f /boot/initramfs-$(uname -r).img $(uname -r)。
3.2 安装NVIDIA官方驱动
驱动安装有两种方式:一种是下载.run文件手动安装,另一种是通过包管理器安装。在中标麒麟上,我推荐用.run文件手动安装,因为包管理器里的驱动版本往往比较老,而且依赖关系复杂。
首先去NVIDIA官网下载对应版本的驱动文件,然后切换到文本模式(关闭图形界面):
sudo systemctl isolate multi-user.target # 或者 sudo init 3然后给驱动文件加执行权限并运行:
chmod +x NVIDIA-Linux-x86_64-470.xx.xx.run sudo ./NVIDIA-Linux-x86_64-470.xx.xx.run安装过程中会问几个问题,比如是否安装32位兼容库、是否更新Xorg配置等。我的建议是:32位兼容库可以不装(除非你有32位程序需要GPU加速),Xorg配置让它自动更新就行。
安装完成后,用nvidia-smi验证:
nvidia-smi如果能看到显卡信息表格,说明驱动装好了。如果报错说找不到设备,大概率是nouveau没禁用干净,或者驱动版本和显卡不匹配。
3.3 内核模块编译失败的常见原因
驱动安装过程中最容易出问题的环节就是内核模块编译。常见的报错和解决办法:
- 报错:找不到kernel headers。原因是
kernel-devel没装或者版本不匹配。解决办法是确认uname -r和rpm -qa | grep kernel-devel的版本一致。 - 报错:gcc版本不支持。NVIDIA驱动对gcc版本有要求,太老或者太新都可能出问题。如果gcc版本太老,可以尝试升级gcc;如果太新,可以在安装时加
--override参数跳过检查。 - 报错:签名验证失败。如果系统开启了Secure Boot,NVIDIA的内核模块需要签名才能加载。解决办法是在BIOS里关闭Secure Boot,或者手动给模块签名。
我这次遇到的是gcc版本问题,系统自带的gcc 4.8.5在编译某个内核模块时提示不支持某个编译选项。解决办法是安装了一个更高版本的gcc,然后用CC环境变量指定编译器路径。
4. CUDA Toolkit的安装与环境配置
4.1 选择CUDA版本的原则
CUDA Toolkit的版本选择主要看两个因素:一是你的GPU驱动版本支持的最高CUDA版本,二是你的应用框架(比如PyTorch、TensorFlow)要求的CUDA版本。一般来说,驱动版本决定了CUDA的上限,而应用框架决定了CUDA的下限。
查看驱动支持的CUDA版本:
nvidia-smi输出表格右上角会显示"CUDA Version: xx.x",这个就是当前驱动支持的最高CUDA版本。比如显示"CUDA Version: 11.4",那么你可以安装CUDA 11.4及以下的版本。
我这次选的是CUDA 11.0,原因是应用框架对11.x系列支持比较好,而且11.0对驱动版本的要求相对宽松。
4.2 用runfile方式安装CUDA
CUDA Toolkit的安装方式也有两种:runfile和包管理器。在中标麒麟上,我同样推荐runfile方式,因为包管理器安装会引入大量依赖,而且可能和系统自带的库冲突。
下载CUDA runfile之后,执行:
chmod +x cuda_11.0.xx_linux.run sudo ./cuda_11.0.xx_linux.run安装过程中会有一个交互界面,需要注意几点:
- 如果之前已经装了NVIDIA驱动,在安装选项里要把Driver那一项的勾去掉,否则会覆盖掉已经装好的驱动。
- CUDA Toolkit和CUDA Samples可以都装上,Samples用来做验证很方便。
- 安装路径默认是
/usr/local/cuda-11.0,建议保持默认。
安装完成后,需要配置环境变量。编辑~/.bashrc或者/etc/profile,添加:
export PATH=/usr/local/cuda-11.0/bin:$PATH export LD_LIBRARY_PATH=/usr/local/cuda-11.0/lib64:$LD_LIBRARY_PATH export CUDA_HOME=/usr/local/cuda-11.0然后执行source ~/.bashrc让配置生效。
4.3 验证CUDA是否真正可用
装完之后一定要验证,不能只看安装过程没报错就认为成功了。验证分三步:
第一步,检查nvcc编译器:
nvcc --version如果输出CUDA版本信息,说明编译器路径配置正确。
第二步,编译并运行CUDA Samples里的deviceQuery:
cd /usr/local/cuda-11.0/samples/1_Utilities/deviceQuery sudo make ./deviceQuery如果输出里显示"Result = PASS",说明CUDA运行时和驱动都正常。
第三步,跑一个简单的矩阵运算测试:
cd /usr/local/cuda-11.0/samples/0_Simple/matrixMul sudo make ./matrixMul这个测试会实际调用GPU做矩阵乘法,如果能正常输出结果和性能数据,说明整个CUDA链路是通的。
提示:如果deviceQuery报错说找不到libcudart.so,检查
LD_LIBRARY_PATH是否包含CUDA的lib64目录。如果matrixMul报错说找不到GPU设备,检查驱动是否加载正常。
5. 那些官方文档不会告诉你的坑
5.1 glibc版本冲突导致CUDA程序无法启动
这个问题我折腾了很久才定位到。现象是:nvcc能正常编译,deviceQuery也能跑,但自己写的CUDA程序一运行就报错:
error while loading shared libraries: libcudart.so.11.0: cannot open shared object file或者更隐蔽的:
symbol lookup error: undefined symbol: __cudaRegisterFatBinary原因在于中标麒麟自带的glibc版本和CUDA Toolkit编译时依赖的glibc版本不一致。CUDA 11.0是在较新的glibc环境下编译的,而中标麒麟的glibc可能偏老,导致符号解析失败。
解决办法有两个:一是升级系统的glibc(风险较大,可能影响系统稳定性),二是安装一个更高版本的CUDA Toolkit(比如CUDA 11.4对glibc的要求更低)。我最终选择了降级CUDA到10.2,因为10.2对老系统的兼容性更好。
5.2 内核更新后驱动失效的处理
中标麒麟有时候会自动更新内核,更新之后重启,发现nvidia-smi报错说找不到驱动。这是因为NVIDIA的内核模块是针对特定内核版本编译的,内核更新后旧模块无法加载。
解决办法是重新编译NVIDIA内核模块:
sudo dkms status sudo dkms rebuild nvidia如果dkms没有配置,可以重新运行NVIDIA驱动的安装程序,它会检测到内核变化并重新编译模块。
预防措施是:在系统设置里关闭内核自动更新,或者每次内核更新后手动重新编译驱动模块。
5.3 多版本CUDA共存时的路径管理
有时候你需要在同一台机器上装多个CUDA版本(比如一个用于训练,一个用于推理)。这时候环境变量的管理就很重要。我的做法是:
- 把不同版本的CUDA装在不同的目录下,比如
/usr/local/cuda-10.2和/usr/local/cuda-11.0。 - 用
update-alternatives来管理默认的CUDA版本:
sudo update-alternatives --install /usr/local/cuda cuda /usr/local/cuda-10.2 10 sudo update-alternatives --install /usr/local/cuda cuda /usr/local/cuda-11.0 20 sudo update-alternatives --config cuda这样切换版本的时候只需要运行update-alternatives --config cuda,选择对应的编号就行,不用手动改环境变量。
5.4 显卡被占用导致CUDA初始化失败
有时候nvidia-smi显示显卡正常,但CUDA程序一跑就报"device busy"或者"all CUDA-capable devices are busy"。这种情况通常是因为有别的进程占用了显卡,比如Xorg图形界面、或者之前跑的程序没有正常退出。
排查方法:
sudo fuser -v /dev/nvidia*这条命令会列出所有占用NVIDIA设备的进程。如果有残留进程,用kill命令清理掉。如果是Xorg占用了显卡,可以切换到文本模式再跑CUDA程序。
6. 跑通之后的性能调优与日常维护
6.1 确认GPU工作状态与功耗模式
CUDA跑通之后,建议用nvidia-smi -q看一下显卡的详细状态,重点关注几个参数:
- Persistence Mode:建议开启,可以避免频繁的驱动加载卸载开销。命令是
sudo nvidia-smi -pm 1。 - Power Limit:如果显卡功耗被限制在较低水平,可以通过
sudo nvidia-smi -pl <瓦数>来调整。 - Clock Speed:查看当前的核心频率和显存频率,确认没有降频运行。
这些参数在长时间跑训练任务时影响很大。我遇到过显卡因为功耗限制导致训练速度只有预期一半的情况,调整功耗上限之后恢复正常。
6.2 监控GPU使用率的常用命令
日常运维中,几个常用的监控命令:
# 实时监控GPU使用率(每秒刷新) nvidia-smi -l 1 # 查看GPU利用率和显存占用 nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total --format=csv # 查看具体进程的GPU占用 nvidia-smi --query-compute-apps=pid,process_name,used_memory --format=csv这些命令可以组合成脚本,定时采集GPU状态写入日志,方便后续分析性能瓶颈。
6.3 系统更新时的注意事项
中标麒麟的系统更新有时候会升级内核或者glibc,这两个东西一变动,CUDA环境大概率会出问题。我的建议是:
- 在系统更新之前,先用
nvidia-smi和deviceQuery确认当前环境正常。 - 更新之后,第一时间重新验证CUDA环境。
- 如果更新导致驱动失效,按照前面说的方法重新编译内核模块。
- 如果更新导致glibc版本变化,可能需要重新安装CUDA Toolkit。
另外,建议把整个CUDA环境的配置步骤写成一个脚本,包括环境变量设置、驱动检查、CUDA验证等。这样每次系统更新后,跑一遍脚本就能快速恢复环境。
7. 一些实际使用中的体会
整个流程走下来,最深的感受是:在中标麒麟上搞CUDA,核心难点不在于技术本身,而在于信息不对称。官方文档不会告诉你glibc版本会冲突,不会告诉你内核更新后驱动会失效,也不会告诉你nouveau禁用命令在不同版本上不一样。这些东西只能靠实际踩坑积累。
另外一个体会是,版本选择比操作步骤更重要。选对了驱动版本和CUDA版本,后面的事情顺风顺水;选错了版本,后面每一步都是坑。所以动手之前花半小时把系统信息摸清楚,比后面花半天排错要划算得多。
最后说一个细节:CUDA Samples里的deviceQuery和matrixMul这两个测试程序,建议每次环境变动后都跑一遍。它们能覆盖驱动加载、运行时库加载、GPU计算能力检测这几个关键环节,是最轻量级的验证手段。如果这两个都跑不通,后面的应用框架就不用试了,肯定是环境有问题。