
1. 为什么非要把昇腾 NPU 塞进 Kubernetes先说个现象。这两年国产算力卡在训练和推理场景里出镜率越来越高尤其是昇腾系列很多团队手里已经握着几十张甚至上百张 Atlas 300I Duo、Atlas 800 这类设备。但问题也随之而来——大部分人的第一反应是“我直接在宿主机上装好驱动、跑 Python 脚本不就行了”结果用着用着就发现根本顶不住。原因很简单当任务一多、机器一多GPU 时代那套资源分配逻辑就失灵了。比如我有 8 张 NPU 卡但 5 个团队同时要申请资源谁能拿到哪张卡、用多长时间、怎么隔离、怎么回收手工运维的话一天光协调卡就得花掉大半天。而 Kubernetes 本来就是干这个的它有成熟的调度器、资源模型、健康检查、弹性伸缩机制唯一缺的就是“把 NPU 当成一种可调度资源”的适配层。所以“昇腾 NPU 接入 Kubernetes”本质上解决的是三件事让 K8s 认识 NPU、让 Pod 能用 NPU、让管理员能监控 NPU。听起来简单实际做起来涉及的组件却不少——从底层驱动到 CANN 运行时从容器 Runtime 到 device-plugin再到监控采集链路很长。这篇文章把我自己落地 CubeStudio 昇腾基础部署的完整过程整理出来包括驱动和 CANN 的安装、Ascend Docker Runtime 的接入、device-plugin 的部署、监控配置以及我踩过的坑和排查思路。无论你是刚接触昇腾不久还是已经上手但卡在某个环节这篇文章都能给你一条能直接照做的路径。这里先说明一下整套架构的形态Kubernetes 集群使用标准的 kubeadm 方式搭建独立部署或已有集群均可NPU 设备通过 device-plugin 以可调度资源的形式暴露给集群容器运行时采用 Docker Ascend Docker Runtime 的组合让容器能够访问宿主机上的昇腾设备监控层通过昇腾官方 Exporter 将 NPU 的利用率、温度、HBM 内存等指标接入 Prometheus。整体结构用一张简化的概念图来描述就是业务 Pod (挂载 NPU 资源) ↓ Ascend Docker Runtime (打通设备映射) ↓ 昇腾驱动 CANN Toolit (宿主机) ↓ Atlas NPU 硬件设备这个链路看着长但每一步都是绕不开的。下面从最基础的硬件和驱动开始说。2. 部署前准备硬件型号、操作系统与网络环境2.1 确认硬件型号和驱动适配关系昇腾设备目前的形态分两类一类是插在 x86 服务器 PCIe 槽位上的加速卡比如 Atlas 300I Duo、Atlas 300V Pro另一类是一体机形态的 Atlas 800 训练服务器。Kubernetes 接入方式没有本质区别都是先把设备驱动在宿主机上装上再通过 runtime 和 device-plugin 把设备透传给容器。但有个关键点必须先确认你的昇腾设备型号对应哪个固件版本和驱动版本。昇腾的驱动和固件是分开安装的固件版本还得跟硬件匹配装错了轻则功能异常重则设备起不来。我这边用的测试机是两台 Atlas 300I Duo 推理卡配套的驱动是 23.0.3 版本CANN 是 8.0 版本。如果你的设备是 Atlas 800 或者是更新的型号建议去昇腾社区官网查一下“昇腾硬件兼容性清单”再定版本。提示昇腾的驱动、固件和 CANN 三者之间有配套关系不要只想着“装最新版”。最稳妥的方式是查对应产品的版本配套表确保三个版本在一个兼容矩阵里。2.2 操作系统与内核要求昇腾对宿主机的操作系统有明确支持列表我调研下来Ubuntu 20.04/22.04、openEuler、CentOS 7.6/8.2 这些主流版本都在支持范围内但并不是所有内核版本都能跑。比如 CentOS 7.6 默认的 3.10 内核太老得换内核才能适配昇腾驱动。我的建议是直接用 Ubuntu 20.04 或 22.04省去各种内核适配的折腾。另外要注意的是驱动安装需要编译内核模块所以必须安装好 linux-headers 和 gcc、make 这些基础工具链。这一点很多人会忽视——比如你用的 Ubuntu Server 默认是最小化安装连 build-essential 都没有驱动安装脚本执行到一半就会报错。还有网络环境。昇腾驱动和 CANN 的软件包非常大比如 CANN Toolkit 解压出来有七八个 GB从昇腾社区下载的时候如果网速不理想会很痛苦。建议配置好内网镜像源或者提前把软件包下载到本地避免在安装过程中因为网络中断导致半途而废。2.3 Kubernetes 集群的基础状态在开始装 NPU 相关组件之前你的 Kubernetes 集群最好已经处于健康状态。不要求生产级高可用但至少保证以下几点控制平面和工作节点都能正常通信集群 DNSCoreDNS运行正常你计划接入 NPU 的节点上有足够的 CPU 和内存资源device-plugin 本身很轻量但 Pod 调度到该节点后会共享 CPU节点上的 Docker 或 containerd 版本不要太老我建议 Docker 20.10containerd 1.6太老的版本跟 Ascend Docker Runtime 的兼容性不好如果集群还没搭好可以用 kubeadm 先快速搭一套单节点集群来做测试。测试集群不需要追求高可用把核心链路跑通才是重点。3. 宿主机级部署驱动、固件与 CANN 的安装细节3.1 固件与驱动安装的先后关系昇腾的软件栈安装顺序是固定的先装固件再装驱动后装 CANN Toolkit。固件相当于设备的 BIOS驱动是操作系统跟设备通信的通道CANN 是上层的算子库和运行时。顺序错了后面很容易出现找不到设备或者算子报错的问题。安装包可以从昇腾社区官网下载需要选择对应的型号和版本。我这里用的包名大概是这样的格式固件包Ascend-hdk-310p-npu-firmware_7.3.0_linux-aarch64.run或 x86_64 版本驱动包Ascend-hdk-310p-npu-driver_23.0.3_linux-aarch64.runCANN 包Ascend-cann-toolkit_8.0.RC1_linux-aarch64.run注意包名里的架构标识不要拿 aarch64 的去 x86 机器上装。用 uname -m 先确认一下x86_64 就选 x86_64aarch64 就选 aarch64。3.2 固件安装实操在 Ubuntu 上安装固件前先把依赖工具装好sudo apt update sudo apt install -y gcc make linux-headers-$(uname -r) build-essential然后给安装包赋可执行权限执行固件安装chmod x Ascend-hdk-310p-npu-firmware_7.3.0_linux-x86_64.run sudo ./Ascend-hdk-310p-npu-firmware_7.3.0_linux-x86_64.run --full安装完成后建议重启一次系统让固件生效。这一步看似多此一举但如果不重启后续驱动安装时对设备的扫描可能不完整。有些资料说要断电重启我自己测试发现普通 reboot 就够了但如果你发现 npu-smi info 看不到设备拔电再开一次是最彻底的。3.3 驱动安装与验证驱动包安装命令和固件类似chmod x Ascend-hdk-310p-npu-driver_23.0.3_linux-x86_64.run sudo ./Ascend-hdk-310p-npu-driver_23.0.3_linux-x86_64.run --full安装过程会编译内核模块所以时间会比固件长一些。看到类似driver install success的输出就说明装上了。然后重启系统用昇腾自带的工具验证设备是否被识别npu-smi info正常情况下会输出设备列表包括芯片型号、温度、HBM 内存、算力利用率等信息。如果输出No device found大概率是固件和驱动的版本不匹配或者内核模块没有正确加载可以用 dmesg 查内核日志来定位。注意npu-smi 是昇腾驱动自带的命令行工具路径一般在/usr/local/Ascend/driver/tools/下可能需要将/usr/local/Ascend/driver/tools/加入 PATH 环境变量才能直接使用。3.4 CANN Toolkit 的安装与环境变量配置CANN 是昇腾的计算架构类似于 NVIDIA 的 CUDA提供了算子库、图编译引擎和运行时。容器内的应用运行时需要依赖它所以必须在宿主机上安装好。安装方式也是直接执行 .run 包但这里有个坑CANN Toolkit 默认安装目录是/usr/local/Ascend/ascend-toolkit如果你没有 root 权限或者后续想多版本共存建议通过--install-path参数指定目录。我这里是默认安装命令如下chmod x Ascend-cann-toolkit_8.0.RC1_linux-x86_64.run sudo ./Ascend-cann-toolkit_8.0.RC1_linux-x86_64.run --fullCANN Toolkit 安装完成后需要设置环境变量。官方提供了一条初始化脚本直接 source 就能搞定source /usr/local/Ascend/ascend-toolkit/set_env.sh最好把这行写进/etc/profile或~/.bashrc这样每次登录终端就不用手动执行了。验证 CANN 是否装好可以先看目录结构ls /usr/local/Ascend/ascend-toolkit/latest同时用 python 检查能否正常导入昇腾的 Python 包。昇腾提供了 torch_npu 和 mindspore 等框架插件但那些是后续业务层的事情这里先做一个最简单的算子验证即可python3 -c import torch; import torch_npu; print(torch_npu.npu.device_count())如果能输出设备数量说明 CANN 基础环境和 NPU 设备已经打通了。这里补充一下如果你在容器里跑训练大多数镜像会自带 CANN 环境宿主机上的 CANN 其实不是必需的但建议仍然装一份原因是后续排查问题时宿主机上有一份完整的 CANN 环境会方便得多。比如容器内算子报错你可以在宿主机上用同样的输入跑一遍快速判断是环境问题还是代码问题。4. Ascend Docker Runtime打通容器与 NPU 的桥梁4.1 为什么需要专门的 RuntimeDocker 默认情况下容器是看不到宿主机上的昇腾设备的。原因在于 NPU 设备在宿主机上不只是几个 /dev 节点它还需要访问 /dev/davinci* 设备文件、/dev/hisi_hdc 等管理设备同时依赖宿主机上的驱动库和 CANN 运行库。如果只是简单地把 /dev/davinci0 映射进容器运行时会因为缺少库文件或无法访问管理设备而报错。Ascend Docker Runtime 是华为官方提供的容器 Runtime 扩展作用就是自动化完成设备映射和运行库注入。它本质上是一个 Docker Runtime Shim在 runc 创建容器之前会修改 OCI 规范配置把设备节点、库文件、环境变量都注入进去。类比一下NVIDIA 那边有 nvidia-container-toolkitAscend 这边对应的就是 Ascend Docker Runtime。用惯了 NVIDIA 生态的同学理解起来会很快。4.2 安装与配置 Ascend Docker RuntimeAscend Docker Runtime 的安装包同样是一个 .run 文件当前版本的包名大概是Ascend-docker-runtime_2.0.0_linux-x86_64.run。安装命令chmod x Ascend-docker-runtime_2.0.0_linux-x86_64.run sudo ./Ascend-docker-runtime_2.0.0_linux-x86_64.run --full安装完成后需要修改 Docker 的 daemon.json 配置让 Docker 使用 Ascend Runtime。文件路径在/etc/docker/daemon.json修改后的内容类似{ runtimes: { ascend: { path: /usr/local/Ascend/Ascend-Docker-Runtime/x86_64/ascend-docker-runtime, runtimeArgs: [] } } }path 路径要根据实际安装位置做调整装完可以用 find 命令确认一下。然后重启 Dockersudo systemctl restart docker验证 Runtime 是否生效docker info | grep -i runtime如果能看见 ascend 字样说明已经注册成功。4.3 验证容器内能否正常访问 NPURuntime 配好之后用官方提供的昇腾推理镜像跑一个测试容器验证容器内能否看到 NPU 设备docker run --rm -it \ --runtime ascend \ --device/dev/davinci0 \ --device/dev/davinci_manager \ --device/dev/hisi_hdc \ -v /usr/local/Ascend/driver:/usr/local/Ascend/driver \ -v /usr/local/Ascend/ascend-toolkit:/usr/local/Ascend/ascend-toolkit \ --entrypoint /bin/bash \ ascendhub.huawei.com/public/ascend-foundation/ascend-pytorch:latest容器起来后执行npu-smi info能看到设备信息就说明 Runtime 映射成功了。这里有一点必须提醒不同版本的容器镜像需要的挂载和 --device 参数可能不同官方文档里写得很细不要照抄网上过时的命令。4.4 与 Kubernetes 的衔接方式集群里 Pod 使用 NPU 设备时其实不会直接通过docker run --runtime ascend这种方式而是靠 kubelet 在创建容器时自动选择 Runtime。kubelet 启动参数里需要配置--runtime-class或者通过 CRI 的 RuntimeClass 机制来指定。但昇腾官方给的 device-plugin 部署方案里默认是把 runtime 通过 kubelet 的配置指向了 ascend。具体来说有两种做法第一种是在 kubelet 的启动参数里添加--docker-runtimeascend但这个参数在一些新版本 K8s 里已经废弃了第二种是借助 RuntimeClass 资源对象在 Pod 的 YAML 中声明runtimeClassName: ascend。我更推荐第二种因为它的粒度更细可以让集群里同时存在使用普通 Runtime 的 Pod 和使用 Ascend Runtime 的 Pod互不干扰。不过要注意使用 RuntimeClass 的前提是 containerd 或 CRI-O 的 runtime 配置里已经注册了 ascend。如果节点上用的是 Docker 加 dockershimK8s 1.24 之前RuntimeClass 实际上是不生效的。这块跟集群版本强相关需要结合实际情况来选择方案。5. device-plugin 部署让 Kubernetes 认识 NPU 资源5.1 device-plugin 的职责Kubernetes 原生不认识“昇腾 NPU”这种资源它只认识 CPU 和内存。为了让 K8s 能够调度 NPU需要按照 Device Plugin 框架写一个插件这个插件会做三件事向 kubelet 注册节点上有多少张 NPU 卡在 Pod 需要 NPU 时告诉 kubelet 把哪个设备编号比如 /dev/davinci0分配给这个 Pod监控设备健康状态设备异常时从可用列表中摘除昇腾官方提供了一个 device-plugin 实现GitHub 仓库名是 Ascend/ascend-device-plugin里面有完整的部署 YAML 和二进制。这个插件以 DaemonSet 方式运行在每个 NPU 节点上这样每个节点的 kubelet 都能感知本地设备。5.2 安装步骤与资源名解析先拉取代码和镜像git clone https://github.com/Ascend/ascend-device-plugin.git cd ascend-device-plugin make build docker build -t ascend-device-plugin:latest .如果不想自己编译官方也提供了镜像可以把镜像标签改成你需要的版本直接拉取。但考虑到有些内网环境无法访问外网自己编译也算是一个必备技能。部署方式很简单官方提供的 YAML 里包含了 DaemonSet、ServiceAccount、ClusterRole 等资源直接 apply 即可kubectl apply -f device-plugin.yaml部署完成后device-plugin 会向 kubelet 注册一个新的资源名这个资源名是ascend.com/npu。注意它不是 GPU 那种nvidia.com/gpu所以在写 Pod 的资源申请时千万别写错。5.3 验证调度与资源分配用一个测试 Pod 验证调度是否正常。创建如下 YAMLapiVersion: v1 kind: Pod metadata: name: npu-test-pod spec: restartPolicy: OnFailure containers: - name: npu-test image: ascendhub.huawei.com/public/ascend-foundation/ascend-pytorch:latest command: [/bin/bash, -c, npu-smi info sleep 3600] resources: limits: ascend.com/npu: 1执行kubectl apply -f npu-test-pod.yaml kubectl describe pod npu-test-pod在 Events 里看到类似ascend.com/npu: 1的资源分配记录并且容器进入 Running 状态说明 device-plugin 已经生效。进入容器执行npu-smi info应该能看到一张 NPU 卡。这里有个比较容易踩的坑如果节点上有多张卡但 device-plugin 只注册了部分卡原因通常是 device-plugin 的配置文件里指定了设备列表。默认配置是自动发现所有设备但如果你改了AscendDeviceConfig文件就得确保里面的卡号是真实存在的。5.4 多卡分配与隔离机制昇腾设备插件支持两种分配模式整卡分配和虚拟化分片。默认是整卡也就是一个 Pod 申请ascend.com/npu: 1就会独占一张卡这张卡不能被其他 Pod 共享。如果卡少任务多可以考虑开启昇腾的 vNPU 功能把一张物理卡切成多个虚拟 NPU但前提是你的设备型号支持 vNPU且驱动和固件版本足够新。从我实际测试来看vNPU 的隔离效果跟硬件强相关。Atlas 300I Duo 这类推理卡对 vNPU 的支持目前还不是特别成熟偶尔会出现显存不隔离导致任务之间互相影响的情况。如果只是做推理服务部署我建议优先使用整卡模式简单且稳定。6. 监控 NPU 状态Prometheus 昇腾 Exporter6.1 为什么监控很重要NPU 设备跟 GPU 一样在高负载下容易发热降频HBM 内存也可能被 OOM。如果不做监控任务跑到一半变慢了、甚至挂了你都不知道原因是什么。在 Kubernetes 环境里监控数据还可以联动 HPA 做弹性伸缩比如当 NPU 利用率超过 80% 时自动扩容 Pod。昇腾提供了一套监控方案核心组件是ascend-exporter它是一个独立运行的二进制或容器通过昇腾驱动暴露的接口读取设备状态然后转换成 Prometheus 指标格式。6.2 部署 ascendent-exporter从 GitHub 拉取 ascendent-exporter 代码后构建镜像并部署git clone https://github.com/Ascend/ascend-exporter.git cd ascend-exporter make build docker build -t ascend-exporter:latest .运行方式可以直接作为 Linux 进程跑也可以在 K8s 里以 DaemonSet 方式部署。我建议 DaemonSet 方案因为这样每个有 NPU 的节点都会有一个 exporter 实例Prometheus 通过服务发现就能自动抓取所有节点的指标。给一个精简的 DaemonSet 示例apiVersion: apps/v1 kind: DaemonSet metadata: name: ascend-exporter namespace: monitoring spec: selector: matchLabels: app: ascend-exporter template: metadata: labels: app: ascend-exporter spec: hostNetwork: true containers: - name: ascend-exporter image: ascend-exporter:latest ports: - containerPort: 9100 hostPort: 9100 volumeMounts: - name: driver mountPath: /usr/local/Ascend/driver securityContext: privileged: true volumes: - name: driver hostPath: path: /usr/local/Ascend/driver这里用 hostNetwork 是为了让 Prometheus 直接通过节点 IP 加端口访问 Exporter避免额外的 Service 配置。如果节点上 9100 端口已被占用可以改成一个不冲突的端口。6.3 Prometheus 抓取配置与常用指标在 Prometheus 的配置文件里增加一个 jobscrape_configs: - job_name: ascend-npu static_configs: - targets: - 10.0.0.11:9100 - 10.0.0.12:9100当然如果你是用 Prometheus Operator更好的是添加一个 ServiceMonitor 或 PodMonitor 来做自动发现。常用的指标包括ascend_npu_utilizationNPU 算力利用率ascend_npu_memory_usageHBM 内存使用量ascend_npu_temperature芯片温度ascend_npu_power功耗Grafana 里导入昇腾官方提供的 dashboard JSON就能直接看到可视化界面。我在实测中发现温度的采集频率不需要太高15 秒一次足够频繁采集反而会增加 driver 的读取压力。7. 常见问题与排查技巧实录7.1 容器内看不到 /dev/davinci 设备这个问题七八成是 Ascend Docker Runtime 没配好。先检查 Docker daemon.json 里的路径是否正确再确认 runtime 是否注册成功。还有一种情况容器启动时没有通过--device映射设备或者在 K8s 里没有设置 RuntimeClass。排查命令docker info | grep -i runtime ls -l /dev/davinci*7.2 device-plugin 注册成功但 Pod 一直 Pending从现象上看Pod 一直处于 Pending 状态describe 之后看到有空资源但调度不上去。这种问题最常见的原因是 device-plugin 上报的资源名跟 Pod 请求的资源名不一致。比如 YAML 里写的是ascend.com/npu但插件实际注册的可能是ascend.com/vNPU开了虚拟化后。查一下节点资源kubectl describe node node-name | grep ascend如果节点上没有可分配的ascend.com/npu就是注册名对不上。7.3 任务跑起来后 npu-smi 显示利用率很高但实际速度很慢在高并发推理场景下这类问题一般不是硬件问题而是软件栈层面的算子编译开销太大。CANN 第一次执行某个算子时会触发 AOE 或者算子编译耗时可能达到几十秒甚至几分钟。解决办法是预先执行一次 warmup或者开启 CANN 的算子缓存功能把编译结果落到磁盘上。这个优化在正式上线前一定要做。7.4 突然发现某张卡设备状态变为 Abnormaldevice-plugin 本身有健康检查机制设备异常时会自动把这张卡从可用资源里摘除避免继续往上面调度任务。这时候要做的是登录节点用 npu-smi info 看具体错误码比如 HBM 报错、温度报警等。如果只是过热清理一下风扇积灰或者降低机房温度就能恢复如果是 HBM 报错这卡大概率得返修了。7.5 常见问题速查表问题现象可能原因解决方向宿主机找不到 NPU固件/驱动未装或版本不匹配按配套表重装固件和驱动docker run 找不到 runtimeAscend Docker Runtime 未注册检查 daemon.json 和重启 Docker容器内没有设备节点未映射设备或卸载了 Runtime确认 --device 参数和 RuntimeClassPod Pending 无法调度资源名不对或 device-plugin 未部署kubectl describe node 查看资源名NPU 利用率上不去算子编译开销大开启算子缓存或执行 warmupExporter 无指标输出driver 路径未挂载或端口冲突检查挂载点和端口占用温度过高导致降频散热不足或风道阻塞物理排查散热环境7.6 一条高效的排查链路我自己在实战中总结了一套排查逻辑先看硬件层driver 是否正常识别再看运行时层Runtime 是否注册成功然后看调度层device-plugin 是否正确上报资源最后看业务层容器内是否真正拿到设备并运行。这套顺序看似基础但真到了排障的时候大多数人是一上来就翻容器日志、改代码折腾半天发现是驱动没装好。从底层往上排查效率会高很多。8. 层面踩过的坑和最终建议部署到现在整个链路已经通了。最后说几个我自己的感悟也算是对后来者的一点建议。如果你只是手上有一两张卡想试点确实可以直接在宿主机上跑不用上 K8s。但一旦涉及多团队共享算力、需要弹性伸缩、需要统一监控这套 K8s 方案就是必须的。投入的时间成本虽然不低但长远看是划算的。实操时有一个容易被忽略的点昇腾的 Docker 镜像一般都非常大动辄十几个 GB拉了镜像之后节点磁盘就告急。建议提前规划好容器镜像存储空间或者配置镜像清理策略不然过两个月磁盘写满Pod 会全部 Evict。另外昇腾软件栈的版本迭代速度比 NVIDIA 快得多社区资料也比较零散。不要试图找到一个“万能教程”一次装好最靠谱的方法是每一步都按官方文档走验证成功再进入下一步。如果哪一步卡住了优先查昇腾社区官方 FAQ而不是在搜索引擎里随便找答案——因为版本一换很多网上步骤就不成立了。目前我的测试集群已经稳定跑了两周跑着几个 CV 推理任务和 PyTorch 训练任务NPU 利用率和温度都在正常范围。后续我打算尝试把昇腾的 vNPU 功能接进来测试一下多租户的隔离效果等有结果了再跟大家分享。