中标麒麟上CUDA环境搭建与深度学习推理实战
1. 为什么要在中标麒麟上折腾CUDA中标麒麟这套系统接触过的人心里都有数。它在不少单位的办公终端和服务器上跑了好些年界面用的是MATE桌面底层是Linux内核包管理走的是RPM那一套。平时拿它写文档、跑脚本、做点轻量级开发都没问题可一旦你想在上面跑深度学习训练或者推理事情就变得有意思了——因为CUDA这套东西官方支持列表里压根就没提过它。我这次接到的需求很直接一台装了中标麒麟的服务器配了两块计算卡要求把CUDA环境搭起来能跑通一个图像分类的推理Demo。听起来简单但实际操作下来从驱动到工具链再到框架适配每一步都有坑。这篇文章就是把这整个过程拆开揉碎讲清楚包括我为什么这么选、每一步背后的逻辑是什么、遇到问题怎么排查。如果你手头也有类似的环境或者你正在国产化平台上做AI相关的适配工作这篇内容应该能帮你省下不少时间。先说清楚一个前提中标麒麟的版本差异很大不同版本对应的内核版本、GCC版本、glibc版本都不一样。我这次操作的是中标麒麟高级服务器版V7内核版本是4.14GCC默认是7.3。如果你的版本跟这个差得比较远后面的步骤可能需要微调但整体思路是通用的。另外要说明的是CUDA本身对操作系统的要求主要集中在内核版本、GCC版本和glibc版本这三个维度上。中标麒麟虽然不在官方支持列表里但只要这三个维度满足CUDA的最低要求理论上就能跑起来。我实测下来也确实如此关键是要把依赖关系理清楚不能上来就闷头装驱动。2. 环境摸底与方案选型2.1 先搞清楚系统到底给了什么动手之前我习惯先把系统的底细摸一遍。这一步很多人会跳过觉得浪费时间但实际上它能帮你避免后面80%的莫名其妙的问题。中标麒麟的发行版信息跟标准的CentOS不太一样有些命令的输出格式有差异所以得用几个不同的角度去确认。我一般会跑这么几条命令cat /etc/os-release uname -r gcc --version ldd --version第一条看发行版标识第二条看内核版本第三条看GCC版本第四条看glibc版本。这四条信息决定了你能装哪个版本的CUDA。具体来说CUDA 10.2要求内核不低于3.10GCC不低于4.8glibc不低于2.17。CUDA 11.x的要求会更高一些GCC至少要6以上glibc至少要2.23。我这次的环境是内核4.14、GCC 7.3、glibc 2.28所以CUDA 11.x是可以上的。还有一个容易被忽略的点中标麒麟默认可能装了多个版本的GCCgcc --version看到的未必是编译器实际调用的那个。我建议用which gcc确认一下路径再用ls -l /usr/bin/gcc看看它到底指向哪个版本。这个细节在后面编译CUDA样例的时候会很重要。2.2 驱动版本和CUDA版本怎么配对这是整个方案里最关键的一步。很多人装CUDA失败根本原因就是驱动版本和CUDA版本不匹配。NVIDIA的驱动和CUDA之间有一个最低版本对应关系比如CUDA 11.4要求驱动不低于470.42.01CUDA 11.8要求驱动不低于520.61.05。如果你先装了CUDA再装驱动或者驱动版本太低就会出现CUDA driver version is insufficient for CUDA runtime version这种报错。我的做法是反过来的先确定要用的CUDA版本再根据CUDA版本去查它要求的最低驱动版本然后装一个比最低版本稍高一点的驱动。这样做的原因是驱动是向下兼容的高版本驱动可以跑低版本CUDA但反过来不行。具体到我这次的情况我选的是CUDA 11.8因为它对GCC和glibc的要求跟我的环境刚好匹配而且11.x系列在国产化平台上的适配案例比较多社区里能查到的资料也相对丰富。对应的最低驱动版本是520.61.05我最终装的是525.105.17留了一点余量。这里有一个经验之谈如果你不确定选哪个CUDA版本优先选11.x系列里比较靠后的版本比如11.6、11.7、11.8。这些版本对系统的要求相对宽松而且PyTorch和TensorFlow对这些版本的支持都比较成熟。太老的版本比如10.2虽然要求低但很多新框架已经不支持了太新的版本比如12.x对GCC和glibc的要求又比较高在中标麒麟上容易出问题。2.3 安装方式的选择runfile还是包管理器CUDA的安装方式主要有两种一种是NVIDIA官方提供的runfile就是一个.run的可执行文件运行之后有图形界面或者命令行交互另一种是通过包管理器比如yum或者apt。在中标麒麟上我强烈建议用runfile方式。原因有三个第一中标麒麟的软件源里没有NVIDIA的官方仓库你得自己加但加了之后依赖关系未必能自动解决第二runfile方式可以把驱动和CUDA工具链分开装控制粒度更细第三runfile方式在安装过程中会检查系统兼容性如果有问题会提前告诉你而不是装到一半才报错。不过runfile方式也有它的麻烦之处就是它默认会尝试安装驱动。如果你已经装好了驱动需要在安装CUDA的时候把驱动选项去掉。这个在后面的实操部分我会详细说。3. 驱动安装的完整实操3.1 准备工作禁用nouveau和关闭图形界面这一步是装NVIDIA驱动的标准前置操作在中标麒麟上也不例外。nouveau是Linux内核自带的开源NVIDIA驱动它和NVIDIA官方驱动是冲突的必须先在系统层面禁掉。具体操作是编辑/etc/modprobe.d/blacklist.conf文件在末尾加上blacklist nouveau options nouveau modeset0然后执行dracut --force重新生成initramfs再重启系统。重启之后用lsmod | grep nouveau确认一下如果没有输出说明禁用成功了。接下来是关闭图形界面。中标麒麟默认启动到图形界面而NVIDIA驱动的安装程序需要在没有X Server运行的情况下才能正常工作。你可以用systemctl set-default multi-user.target把默认启动级别改成命令行然后重启。或者如果你不想重启可以用systemctl stop lightdm临时停掉显示管理器。我一般选择前者因为更彻底不容易出问题。注意关闭图形界面之前确保你知道怎么在命令行下操作。如果你对命令行不太熟悉建议先用systemctl isolate multi-user.target临时切换确认没问题之后再改默认启动级别。3.2 安装驱动参数选择和过程解读驱动安装文件从NVIDIA官网下载选择对应的产品系列和操作系统。这里有一个小技巧操作系统选项里没有中标麒麟选“Linux 64-bit”就行不要选具体的发行版因为选了之后下载的驱动可能带有特定发行版的依赖。下载下来是一个.run文件比如NVIDIA-Linux-x86_64-525.105.17.run。给它加上执行权限chmod x NVIDIA-Linux-x86_64-525.105.17.run然后运行./NVIDIA-Linux-x86_64-525.105.17.run --kernel-source-path/usr/src/kernels/$(uname -r) --no-opengl-files这里有两个参数需要解释一下。--kernel-source-path是指定内核源码的路径因为驱动安装的时候需要编译内核模块必须有内核头文件。中标麒麟默认可能没装内核开发包你需要先确认/usr/src/kernels/下面有没有对应版本的目录。如果没有得先安装kernel-devel包。--no-opengl-files这个参数是告诉安装程序不要安装OpenGL相关的文件。为什么要加这个因为中标麒麟的图形界面依赖系统自带的OpenGL库如果你让NVIDIA驱动覆盖了这些库重启之后图形界面可能会起不来。加上这个参数之后驱动只安装计算相关的部分不影响图形界面。安装过程中会问你几个问题比如是否安装DKMS、是否更新X配置等。我的建议是DKMS选否X配置选否。DKMS是动态内核模块支持在中标麒麟上不一定能正常工作而且每次内核更新都要重新编译比较麻烦。X配置不更新是因为我们前面已经禁用了图形界面暂时不需要。安装完成之后用nvidia-smi验证一下。如果能看到显卡信息和驱动版本说明驱动装好了。如果报错说找不到设备可能是内核模块没加载试试modprobe nvidia手动加载一下。3.3 驱动安装后的验证与常见报错nvidia-smi能跑通只是第一步我还会做几个额外的检查。首先是确认内核模块确实加载了lsmod | grep nvidia应该能看到nvidia、nvidia_uvm、nvidia_drm这几个模块。其中nvidia_uvm是CUDA要用到的如果它没加载后面跑CUDA程序会报错。然后是检查设备节点ls -l /dev/nvidia*正常情况下应该有/dev/nvidia0、/dev/nvidia1对应两块卡、/dev/nvidiactl、/dev/nvidia-uvm这几个设备文件。如果缺少/dev/nvidia-uvm可以手动创建mknod /dev/nvidia-uvm c 510 0不过更推荐的做法是写一个启动脚本在系统启动的时候自动加载模块和创建设备节点。这个脚本可以放在/etc/rc.local里或者写成一个systemd服务。常见的报错我整理了一个表报错信息可能原因解决方法NVIDIA-SMI has failed because it couldnt communicate with the NVIDIA driver内核模块未加载执行modprobe nvidia检查dmesg看是否有报错Failed to initialize NVML: Driver/library version mismatch驱动版本和内核模块版本不一致重启系统或者卸载后重新安装驱动No devices were found显卡未被识别检查lspci是否能看到的显卡确认BIOS里显卡没有被禁用Unable to determine the device handle for GPU权限问题确认当前用户对/dev/nvidia*有读写权限4. CUDA工具链的安装与配置4.1 安装CUDA去掉驱动选项驱动装好之后CUDA的安装就相对简单了。同样是从NVIDIA官网下载runfile比如cuda_11.8.0_520.61.05_linux.run。运行的时候加一个--toolkit参数只安装工具链不安装驱动./cuda_11.8.0_520.61.05_linux.run --toolkit --silent --override--silent是静默安装不需要交互。--override是忽略系统兼容性检查因为中标麒麟不在官方支持列表里不加这个参数安装程序会直接退出。安装路径默认是/usr/local/cuda-11.8同时会创建一个软链接/usr/local/cuda指向它。安装完成之后需要配置环境变量。编辑/etc/profile.d/cuda.sh加入export PATH/usr/local/cuda/bin:$PATH export LD_LIBRARY_PATH/usr/local/cuda/lib64:$LD_LIBRARY_PATH然后source /etc/profile.d/cuda.sh让配置生效。用nvcc -V验证一下如果能看到CUDA版本信息说明工具链装好了。4.2 编译样例程序验证工具链完整性装完CUDA之后我习惯跑一下官方样例来验证工具链是否完整。CUDA安装包里自带了一些样例但默认不安装。你可以从/usr/local/cuda/samples找到它们或者从GitHub上单独下载。编译样例之前先确认make和gcc的版本。CUDA 11.8要求GCC版本在6到12之间如果系统默认的GCC版本太高或者太低需要手动指定。比如你的系统默认GCC是13但CUDA 11.8只支持到12那就需要安装一个GCC 12然后在编译的时候指定make CUDAHOSTCXX/usr/bin/gcc-12我这次编译的是deviceQuery这个样例它能列出所有CUDA设备的信息。编译成功之后运行./deviceQuery如果输出里能看到两块卡的信息并且最后显示Result PASS说明CUDA工具链完全正常。4.3 环境变量配置的细节与坑点环境变量这块有几个细节容易出问题。首先是LD_LIBRARY_PATH的顺序一定要把CUDA的lib64放在前面否则系统可能会优先加载自带的旧版本库。其次是PATH里CUDA的bin目录也要放在前面避免nvcc调用到其他版本的编译器。还有一个坑是如果你在/etc/profile.d/下面加了脚本但用的是非登录shell比如通过su切换用户这些脚本可能不会被自动执行。保险的做法是在~/.bashrc里也加上同样的配置或者直接用/etc/bashrc。另外如果你同时装了多个版本的CUDA可以通过修改软链接/usr/local/cuda的指向来切换版本。比如从11.8切到11.6rm -rf /usr/local/cuda ln -s /usr/local/cuda-11.6 /usr/local/cuda然后重新source一下环境变量脚本就行。5. 深度学习框架的适配与验证5.1 PyTorch的安装与版本选择CUDA装好之后下一步就是装深度学习框架。我这次选的是PyTorch因为它在国产化平台上的适配案例最多社区支持也最好。安装方式是用pip从官方源安装但要注意选择跟CUDA版本匹配的包。PyTorch官网会给出不同CUDA版本对应的安装命令比如CUDA 11.8对应的是pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118但这里有一个问题中标麒麟的pip源默认可能是内网的访问不了外网。你需要先确认pip的源配置如果在内网环境可能需要把包下载下来再离线安装。离线安装的时候要注意依赖关系torch依赖numpy、typing-extensions等包最好用pip download把所有依赖都下下来。安装完成之后用一段简单的代码验证import torch print(torch.__version__) print(torch.cuda.is_available()) print(torch.cuda.device_count()) print(torch.cuda.get_device_name(0))如果torch.cuda.is_available()返回True并且能正确显示显卡型号说明PyTorch已经能正常调用CUDA了。5.2 跑通一个完整的推理Demo验证完基本功能之后我跑了一个完整的图像分类推理Demo。用的是ResNet-50的预训练模型输入是一张测试图片输出是分类结果。这个Demo虽然简单但它涵盖了模型加载、数据预处理、GPU推理、结果后处理整个流程能比较全面地验证环境是否可用。代码大致是这样的import torch import torchvision.models as models import torchvision.transforms as transforms from PIL import Image model models.resnet50(pretrainedTrue) model model.cuda() model.eval() transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) img Image.open(test.jpg) input_tensor transform(img).unsqueeze(0).cuda() with torch.no_grad(): output model(input_tensor) probabilities torch.nn.functional.softmax(output[0], dim0) top5_prob, top5_catid torch.topk(probabilities, 5) print(top5_prob)跑通之后我注意到一个现象第一次推理的时候耗时比较长大概有2到3秒但后续推理就快很多单张图片只要几十毫秒。这是因为第一次推理的时候CUDA需要初始化上下文、加载内核这些都是一次性的开销。如果你在生产环境部署建议在服务启动的时候先跑一次预热推理避免第一个请求超时。5.3 多卡环境的配置与注意事项我这次的环境有两块卡所以顺便验证了一下多卡推理。PyTorch提供了DataParallel和DistributedDataParallel两种方式前者适合单机多卡后者适合多机多卡。对于推理场景DataParallel就够用了model models.resnet50(pretrainedTrue) model torch.nn.DataParallel(model) model model.cuda()但这里有一个坑DataParallel默认会把输入batch平均分配到每块卡上如果你的batch size是1那只有第一块卡会被用到。所以多卡推理的时候要么增大batch size要么用DistributedDataParallel配合多进程。另外多卡环境下nvidia-smi的显存占用会显示两块卡都有占用但实际计算可能只在一块卡上。你可以用torch.cuda.current_device()确认当前使用的设备或者用CUDA_VISIBLE_DEVICES环境变量指定可见的显卡。6. 常见问题排查与避坑经验6.1 驱动和CUDA版本不匹配的典型表现这个问题我遇到过好几次表现方式有好几种。最直接的是nvidia-smi报错说驱动版本和库版本不匹配。还有一种比较隐蔽的情况是nvidia-smi能正常显示但跑CUDA程序的时候报CUDA driver version is insufficient。这是因为nvidia-smi用的是内核模块的版本而CUDA程序用的是用户态库的版本两者可能不一致。解决方法是先确认内核模块的版本cat /proc/driver/nvidia/version再确认用户态库的版本nvidia-smi | grep Driver Version如果两者不一致说明驱动安装有问题需要卸载后重新安装。卸载的命令是./NVIDIA-Linux-x86_64-525.105.17.run --uninstall然后重新安装安装之前记得把旧的内核模块清理干净。6.2 编译CUDA程序时的GCC版本问题CUDA对GCC版本有严格的要求太高或者太低都不行。中标麒麟默认的GCC版本可能不在CUDA的支持范围内这时候需要安装一个兼容的GCC版本然后在编译的时候指定。具体操作是先用yum install gcc-12安装一个兼容版本然后在编译的时候通过CUDAHOSTCXX环境变量指定export CUDAHOSTCXX/usr/bin/gcc-12或者在make命令里直接指定make CUDAHOSTCXX/usr/bin/gcc-12还有一个更彻底的方法是在/usr/local/cuda/bin/nvcc里把默认的编译器路径改掉但我不推荐这么做因为会影响所有CUDA程序的编译。6.3 显存不足和内存泄漏的排查思路显存不足是跑深度学习程序时最常见的问题之一。表现是程序报CUDA out of memory然后退出。排查思路是先用nvidia-smi看当前显存占用确认是不是有其他进程占着显存没释放。如果有用kill -9把进程杀掉然后等几秒钟让显存回收。如果是自己的程序显存不够可以考虑几个方向减小batch size、用混合精度训练、用梯度累积、或者用模型并行。推理场景下还可以用torch.cuda.empty_cache()手动清理缓存但这个操作比较耗时不建议频繁调用。内存泄漏的排查稍微麻烦一些。可以用torch.cuda.memory_summary()查看显存分配情况或者用nvidia-smi -l 1持续监控显存变化。如果发现显存持续增长不下降大概率是有张量没释放检查一下是不是在循环里不断创建新的张量而没有释放旧的。6.4 系统更新后驱动失效的预防措施中标麒麟的系统更新可能会升级内核版本而NVIDIA驱动是跟内核版本绑定的内核一升级驱动就可能失效。预防措施有两个一是用DKMS让驱动在每次内核更新后自动重新编译二是锁定内核版本不让系统自动升级。DKMS的配置方法是在安装驱动的时候选择启用DKMS或者手动安装dkms包然后重新配置。锁定内核版本的方法是编辑/etc/yum.conf在[main]段里加上excludekernel*这样yum update的时候就不会升级内核了。但这个方法也有副作用就是安全补丁可能也装不上需要权衡。我个人的做法是在生产环境锁定内核版本在测试环境用DKMS。这样既能保证生产环境的稳定性又能在测试环境验证新内核的兼容性。7. 一些实操心得和后续扩展整个流程走下来我最大的体会是在中标麒麟上装CUDA难点不在技术本身而在于信息不对称。官方文档不会告诉你中标麒麟该怎么配社区里的资料又比较零散很多细节需要自己摸索。我的建议是动手之前先把系统信息摸清楚然后严格按照“驱动→CUDA→框架”的顺序来装每一步都验证通过之后再进入下一步。还有一个心得是尽量用runfile方式安装虽然比包管理器麻烦一点但可控性更强。包管理器安装的时候依赖关系是自动解决的但中标麒麟的软件源跟标准CentOS有差异自动解决的结果未必是你想要的。runfile方式虽然需要手动处理依赖但每一步都在你的掌控之中。后续如果要在生产环境部署我建议把整个安装过程脚本化写成一个自动化安装脚本。这样在新机器上部署的时候只需要跑一遍脚本就行不用重复踩坑。脚本里要包含系统检查、依赖安装、驱动安装、CUDA安装、环境变量配置、框架安装、验证测试这几个环节每个环节都要有错误处理和日志输出。另外如果你需要在容器里跑CUDA程序还需要额外安装nvidia-container-toolkit这个工具能让容器访问宿主机的GPU。安装方法跟CUDA类似也是从NVIDIA的仓库下载对应的包然后配置Docker或者containerd的运行时。这块内容比较多有机会再单独写一篇。最后分享一个小技巧如果你不确定某个CUDA版本能不能在中标麒麟上跑可以先在一个测试环境里用--override参数试着装一下看看能不能编译通过。如果能编译通过基本就能跑如果编译报错根据报错信息判断是缺依赖还是版本不兼容。这个方法比查文档快得多而且更准确。