资讯动态

昇腾NPU快速接入Kubernetes:驱动、运行时与调度全实践

发布时间:2026/9/17 3:53:56 来源:尧图企业网站定制
几个月前我把十几台带昇腾 310P 推理卡的服务器接进了已有的 Kubernetes 集群目标是让业务方像申请 CPU 一样直接在 Pod 里声明几张 NPU 就能跑推理和微调任务。当时网上关于昇腾 NPU K8s 的完整资料还比较零散驱动、CANN、Ascend Docker Runtime、device-plugin 这几个词拆开看都认识串起来做部署却绕了很多弯路。这篇文章就是把整套接入过程做一个复盘核心目标是帮你在自己的集群里也能顺利把昇腾 NPU 用起来少走我踩过的那些坑。先说明一点很多人习惯打“昇腾 GPU”严格说昇腾是 NPU神经网络处理器不是 GPU它的加速架构、编程模型和 CUDA 完全不同K8s 的接入路径自然也和 NVIDIA 那套不一样。昇腾 NPU 要能在 K8s 里被调度、被容器使用至少要打通驱动、CANN 运行环境、容器运行时、设备插件、监控这几层。下面我按实操顺序逐个拆开讲每一步都给了可复制的命令和配置也会解释为什么要这么做。1. 先想明白昇腾 NPU 上 K8s 到底需要哪些组件1.1 从容器里“看不到卡”说起很多第一次接触 NPU 容器化的同学会下意识以为宿主机上装好驱动容器里就能直接看到/dev/davinci0这些设备了。实际完全不是这样。容器默认只共享内核不共享宿主机的设备文件而且 Docker 默认不会主动把 NPU 相关的设备节点、驱动目录、运行库注入容器。所以你会遇到一种很奇怪的现象宿主机上npu-smi info能看到四张卡容器里却什么都看不到甚至ls /dev/davinci*直接报 No such file or directory。这就是昇腾 NPU 上 K8s 的第一步必须在容器创建过程中把 NPU 的设备文件和依赖组件“注入”进容器。这个动作本来有几种实现方式比如--privileged加手动挂设备但太粗暴而且没法配合 K8s 做资源调度。容器化的正规套路是在 Docker Runtime 这一层做钩子容器启动前自动探测并挂载设备、设置环境变量。这就是后面要讲的 Ascend Docker Runtime 的职责。1.2 四个组件的分工与配合昇腾 NPU 接入 K8s整个链路里的核心组件我总结成四个驱动与固件宿主机底层负责让操作系统能识别 NPU 设备并提供/dev/davinci*、DCMI 管理接口等。没有它上面一切免谈。CANNAscend Compute Architecture for Neural Network昇腾的软件开发套件类似 CUDA Toolkit。应用在跑推理或训练时会调用 CANN 的 API、算子库和 runtime。Ascend Docker Runtime容器运行时插件负责把 NPU 设备、驱动库、必要的环境变量注入容器。相当于 NVIDIA Container Toolkit 的角色。device-pluginK8s 的设备插件负责把 NPU 作为扩展资源上报给 kubelet让调度器知道这个节点有多少张卡、每张卡可用不可用。这四个组件不是“装好其中三个就够”的关系而是层层依赖。驱动和固件在宿主机提供设备CANN 给应用提供编程入口Docker Runtime 解决容器访问设备的问题device-plugin 解决 K8s 调度和资源上报的问题。缺了任何一个容器化后的 NPU 都跑不通。1.3 资源上报与调度链路把这条链路放到一次 Pod 调度里看会更容易理解。用户创建一个 Pod声明需要huawei.com/Ascend310P: 1这张“虚拟资源”。调度器看到这个请求后会去检查集群里哪个节点的可分配资源里包含了这个扩展资源有就调度过去。kubelet 收到调度结果后会通过 device-plugin 拿到的设备列表调用容器运行时创建容器运行时再通过 Ascend Docker Runtime 的钩子把具体的 NPU 设备文件挂载进去。最后容器里应用看到的就已经是能直接访问的/dev/davinci0了。所以你在排障时要有一个全局的判断顺序节点识别不到卡问题在驱动K8s 节点资源里看不到 NPU 数量问题在 device-plugin调度成功但容器里没设备问题大概率在 runtime 配置。后面所有实操都围绕这条主线展开。2. 环境准备硬件、操作系统与版本配套2.1 硬件与系统选型昇腾 NPU 的硬件形态很多从边缘小盒子到数据中心推理卡、训练卡都有。和 K8s 接入相关度最高的是两种Atlas 300I/300T 推理卡内部是昇腾 310P 芯片Atlas 800 / 800T 训练服务器内部是昇腾 910B 芯片。不同芯片对应不同的驱动包和 CANN 版本一定不能用错。操作系统方面昇腾官方支持得比较好的发行版是 openEuler、Ubuntu20.04/22.04、CentOS 7.6 这类。选系统时不要只看“能不能装”还要看“昇腾驱动包的配套表里有没有这个内核版本”。驱动安装时通常需要内核开发包kernel-devel或者 linux-headersUbuntu 上用 apt 装对应版本 headers 即可。我踩过一个坑节点是 Ubuntu 22.04但内核因为安全加固打过小版本补丁headers 版本和实际内核不一致导致驱动编译失败。所以装驱动前先确认uname -r和 headers 版本完全一致。2.2 驱动、固件、CANN 的三角关系昇腾的软件栈里版本配套是最大的隐性坑。驱动Driver和固件Firmware在官方 HDK 包里通常是一起提供的两者必须严格配套因为固件控制芯片底层的电源、时钟、内部通信驱动负责向上的接口抽象两者版本错位轻则性能异常重则设备起不来。CANN 则需要和宿主机驱动版本互相兼容。一般规律是CANN 大版本不要太老驱动也不能太新具体以安装包自带的《版本配套表》为准。这个配套表非常关键但很容易被忽略。我在生产中见到过一种典型报错CANN Toolkit 装的是 8.0驱动版本是 23.0.RC3结果容器里跑算子时报“aclrtSetDevice failed, error code: 10070”或者“toolkit version mismatch”。查了一圈才发现是配套问题最后统一降级 CANN 版本才解决。2.3 安装前置检查开始安装之前我建议你先做这几件事确认节点上已经识别到硬件lspci | grep -i ascend能看到类似“Processing accelerators”的设备。确认 BIOS 里没开 secure boot开了会导致驱动签名校验失败。实在关不掉需要自己给驱动模块做签名很麻烦不如直接关。确认磁盘空间足够驱动和 CANN 加起来大概 10GB 左右但后面还要留出容器镜像、日志空间。确认内核和内核头文件版本一致最简单的方式是rpm -qa | grep kernel或dpkg -l | grep linux-image和uname -r对照。这些检查看着琐碎但每一项都能在后续安装中省掉大量排障时间。很多时候驱动装不上、装上起不来都不是昇腾本身的问题而是操作系统层面的前置条件没满足。3. 底层安装驱动与 CANN 实操3.1 安装 HDK 驱动与固件昇腾的驱动和固件打包在 HDKHardware Development Kit里文件名类似Ascend-hdk-310p-npu-driver_*.run和Ascend-hdk-310p-npu-firmware_*.run。到昇腾社区下载对应架构的包后按顺序安装chmod x Ascend-hdk-*.run ./Ascend-hdk-310p-npu-driver_*.run --full --install ./Ascend-hdk-310p-npu-firmware_*.run --full --install注意--full参数不是所有版本都有如果报参数错误换成--install或先跑--help看看支持的选项。安装完成后驱动默认装到/usr/local/Ascend/driver固件也有一套自己的管理工具。最关键的一步是重启节点或者至少执行一遍驱动加载流程。有些版本会提示你 reboot我没当回事结果npu-smi info怎么都不出卡重启后一切正常。重启后用npu-smi info验证正常输出会列出每张卡的芯片型号、健康状态、温度、HBM 使用量。同时检查设备节点ls -l /dev/davinci* ls -l /dev/davinci_manager ls -l /dev/davinci_svm如果你发现/dev/davinci0存在但npu-smi info报错可以先执行npu-smi reset重置一下芯片再不行就去查/var/log/ascend下的驱动日志。3.2 安装 CANN ToolkitCANN Toolkit 是应用编译和运行所必需的。下载对应的Ascend-cann-toolkit_*.run后chmod x Ascend-cann-toolkit_*.run ./Ascend-cann-toolkit_*.run --install source /usr/local/Ascend/ascend-toolkit/set_env.shset_env.sh会把 CANN 的相关路径和库加到环境变量里包括ASCEND_HOME、LD_LIBRARY_PATH、PATH等。如果只是跑推理可以只装toolkit如果想做算子开发可能还需要nnrt、tfplugin等组件。我的建议是宿主机上按官方默认装全套 Toolkit容器镜像里则尽量精简只放推理或训练所需的最小 CANN 依赖避免镜像过大。安装完成后验证一下source /usr/local/Ascend/ascend-toolkit/set_env.sh python3 -c import acl; print(acl.__version__)能导入acl就说明 CANN 基本可用。这里有一个容易忽略的点set_env.sh只在当前 shell 里生效如果你每次登录都要手动 source说明没写入~/.bashrc。我习惯在/etc/profile.d/ascend.sh里放一个统一的 source这样所有用户登录后都能直接用。3.3 安装后验证与常见报错驱动和 CANN 装完最好立刻跑一个宿主机侧的验证确认整条推理链路是否通。昇腾社区提供了很多样例最简单的是用 CANN 自带的执行文件或跑一个 resnet50 的 Python 推理脚本。没有现成样例也不怕你可以用npu-smi info确认设备正常再用 CANN 的ascend-dmi工具查看芯片详情。我在安装阶段遇到最多的报错有两类。第一类是驱动安装时提示“kernel module build failed”多半是内核头文件缺失或版本不一致装上和当前内核完全对应的 kernel-devel/linux-headers 再重装即可。第二类是npu-smi info显示设备存在但状态是 abnormal这种要先看固件版本再看/usr/local/Ascend/driver下的version.info确认驱动和固件版本配套。实在不行就按固件在驱动之前还是之后装来排查某些版本要求先装固件再装驱动顺序反了会导致设备初始化异常。4. 打通容器Ascend Docker Runtime 的原理与配置4.1 Runtime 到底帮我们做了什么如果你只是想让某个容器临时用一下 NPU可以用--privileged加手动挂载设备的方式但那是手工作坊。正经做法是给 Docker 装一个自定义 runtime让容器创建过程自动注入 NPU 能力。Ascend Docker Runtime 就是干这个的它在容器启动前会做这几件事探测宿主机上的/dev/davinci*设备节点是否被允许给容器用把需要的设备文件映射进容器把驱动库目录通常是/usr/local/Ascend/driver/lib64和 CANN 库目录挂载进去再设置ASCEND_VISIBLE_DEVICES之类的环境变量。这个设计和 NVIDIA Container Toolkit 思路非常像核心价值是“按需注入”容器不用的卡不挂容器需要的库和驱动保持一致避免用--privileged带来的过大权限。理解这一点你就明白为什么宿主机上驱动必须装好因为 runtime 本身不负责装驱动它只是把宿主机上现成的东西在容器启动时“搬运”进去。4.2 安装与 daemon.json 配置Ascend Docker Runtime 的安装包通常是Ascend-docker-runtime-*.tar.gz或.run文件。解压或安装完成后把 runtime 放到一个固定路径比如/usr/local/Ascend/Ascend-Docker-Runtime。然后修改 Docker 配置/etc/docker/daemon.json{ runtimes: { ascend: { path: /usr/local/Ascend/Ascend-Docker-Runtime/runtime/ascend-docker-runtime, runtimeArgs: [] } } }改完执行systemctl restart docker。注意重启 Docker 会把节点上所有容器干掉生产环境尽量分批操作。验证 runtime 是否生效可以直接跑一个临时容器docker run --rm --runtime ascend --device/dev/davinci0 镜像 npu-smi info如果容器内能正常打印 NPU 信息说明 runtime 注入成功。这里我碰过一个小坑镜像里没有npu-smi命令结果容器一执行就报 command not found。npu-smi是驱动包提供的工具不一定在所有 AI 镜像里都有。遇到这种情况可以改用检查设备节点的方式验证docker run --rm --runtime ascend 镜像 ls -l /dev/davinci*。4.3 用 RuntimeClass 让容器真正用上 NPUDocker 层面配置好了 runtimeK8s 默认不会自动使用它。K8s 里要让 Pod 使用某个容器运行时需要定义 RuntimeClass 对象并在 Pod 中声明。这个环节容易被漏掉漏掉的直接表现就是Node 上已经有 NPU 扩展资源Pod 也能调度上去但容器里就是看不到/dev/davinci0。RuntimeClass 的定义很简单apiVersion: node.k8s.io/v1 kind: RuntimeClass metadata: name: ascend handler: ascend注意handler必须和 Docker 配置里的 runtime name 对应这里是ascend。之后在 Pod spec 里加上runtimeClassName: ascendK8s 调度到这个节点后CRI 就会用 ascend 运行时来创建容器设备注入才能走到 Ascend Docker Runtime 这一层。我遇到一种情况是节点上 K8s 的 CRI 不是 Docker而是 containerd。containerd 的 runtime handler 配置方式和 Docker 不同而且如果没配置好RuntimeClass 也会生效不了。昇腾对 containerd 的支持在逐步完善但最稳的路径还是让 K8s 节点使用 Docker CRI后续再平滑迁移到 containerd。这点在集群规划时要提前想清楚。5. 接入调度部署 device-plugin5.1 device-plugin 的工作原理K8s 的扩展资源调度不是由某个组件自己决定的而是通过 device plugin 机制device-plugin 以 DaemonSet 方式在每个 NPU 节点上运行启动时向 kubelet 注册上报自己管理的设备并将设备状态通过 socket 通知 kubelet。kubelet 把这些信息更新到 Node 的 capacity 和 allocatable 中调度器才能感知到这些资源。昇腾的 device-plugin 会对/dev/davinci*做探测把可用的 NPU 整理成设备列表。用户创建 Pod 时声明了huawei.com/Ascend310P: 1这类资源调度器找到有这个资源且数量充足的节点kubelet 最终通过 device-plugin 返回的具体设备 ID结合容器运行时完成设备挂载。5.2 DaemonSet 部署配置从昇腾社区的设备插件仓库拿到 YAML 或镜像后做如下调整。这是我在生产环境用的一个精简版apiVersion: apps/v1 kind: DaemonSet metadata: name: ascend-device-plugin namespace: kube-system spec: selector: matchLabels: app: ascend-device-plugin template: metadata: labels: app: ascend-device-plugin spec: hostNetwork: true tolerations: - operator: Exists containers: - name: device-plugin image: 你的镜像仓库/ascend-device-plugin:版本 imagePullPolicy: IfNotPresent securityContext: privileged: true volumeMounts: - name: device-plugin mountPath: /var/lib/kubelet/device-plugins - name: sys mountPath: /sys - name: dev mountPath: /dev - name: driver mountPath: /usr/local/Ascend/driver volumes: - name: device-plugin hostPath: path: /var/lib/kubelet/device-plugins - name: sys hostPath: path: /sys - name: dev hostPath: path: /dev - name: driver hostPath: path: /usr/local/Ascend/driverprivileged: true是因为插件需要访问宿主机设备目录和驱动目录。如果集群启用了 PodSecurityPolicy 或 PSA要提前放行这个特权 DaemonSet。hostNetwork: true不是必须但我习惯加上方便排查插件日志。部署完成后先看节点的资源上报情况kubectl describe node node-name正常情况下能看到类似Capacity: cpu: 32 memory: 128Gi huawei.com/Ascend310P: 4如果资源没有出现先看插件 Pod 日志重点看有没有“Registered device plugin with kubelet”这类信息。设备插件注册失败多半是/var/lib/kubelet/device-plugins权限不对或者 kubelet 没启用 DevicePlugins 特性开关新版 K8s 默认启用老版本需要确认。5.3 验证调度与资源分配设备插件上线后可以创建一个简单 Pod 验证全链路apiVersion: v1 kind: Pod metadata: name: npu-test spec: runtimeClassName: ascend containers: - name: npu-test image: 昇腾推理镜像 command: [npu-smi, info] resources: limits: huawei.com/Ascend310P: 1这里有一个细节资源请求最好同时写 requests 和 limits不要只写 limits。K8s 对扩展资源只支持限制不支持超出但如果只写 limits调度时会自动把 requests 等同为 limits这没问题问题是有些业务账号会在 Pod 里同时声明了别的资源总量计算容易出错。为了直观显式写出来最好。如果 Pod 能 Running 并且kubectl logs npu-test能正常打印 NPU 信息说明从调度到设备注入的整个链路已经打通。注意多张卡的情况只要声明了huawei.com/Ascend310P: 2容器内就能看到两个设备但具体是不是davinci0和davinci1取决于插件分配策略不重要。6. 监控体系NPU 指标如何接入 Prometheus6.1 为什么要单独采集 NPU 指标K8s 自带的 metrics-server 只采集 CPU 和内存对 NPU 的利用率、HBM 占用、温度、功耗一无所知。生产环境跑 NPU 推理任务这些指标恰恰最关键。芯片温度过高会触发降频直接导致推理延迟抖动HBM 占用快满可能让任务 OOM而且 NPU 的 OOM 不像 CPU 内存那样有 cgroup 报告的机制很多是算子直接报错排查起来极其痛苦。所以要单独部署一个 exporter把 NPU 指标暴露成 Prometheus 格式。昇腾官方和社区都有对应的 exporter核心原理基本一致通过读取 DCMI 接口或解析npu-smi输出来获取设备状态。6.2 部署 npu-exporternpu-exporter 通常也以 DaemonSet 方式部署。每个节点上跑一个 exporter从宿主机驱动接口获取所有 NPU 卡的状态然后以 HTTP 服务方式暴露/metrics。部署时挂载/usr/local/Ascend/driver目录是必须的因为 exporter 要调用驱动提供的接口来读设备数据。下面是一个典型的 DaemonSet 片段apiVersion: apps/v1 kind: DaemonSet metadata: name: npu-exporter namespace: monitoring spec: selector: matchLabels: app: npu-exporter template: metadata: labels: app: npu-exporter spec: hostNetwork: true containers: - name: npu-exporter image: 你的镜像仓库/npu-exporter:版本 ports: - containerPort: 9100 name: metrics volumeMounts: - name: driver mountPath: /usr/local/Ascend/driver - name: dev mountPath: /dev volumes: - name: driver hostPath: path: /usr/local/Ascend/driver - name: dev hostPath: path: /devhostNetwork: true是为了让 Prometheus 直接通过节点 IP 加端口抓取省去 Service 和 DNS 的复杂度。实际端口号以 exporter 镜像说明为准有 9100 的也有别的先用curl http://节点IP:9100/metrics验证。6.3 Prometheus 与 Grafana 配置建议Prometheus 侧配置抓取任务时我不太建议手动维护节点列表更推荐用 K8s 的服务发现scrape_configs: - job_name: ascend-npu kubernetes_sd_configs: - role: pod relabel_configs: - source_labels: [__meta_kubernetes_pod_label_app] action: keep regex: npu-exporter - source_labels: [__meta_kubernetes_pod_node_name] target_label: node这样新节点加入集群后只要 DaemonSet 调度上去Prometheus 就能自动发现。Grafana 面板方面社区有现成模板也可以自己画重点关注这几个面板NPU 利用率、HBM 使用量、温度、功耗。我用下来发现功耗和利用率结合起来看最有用能从曲线形态判断任务是计算密集还是访存密集对后续做性能调优非常有帮助。还要配几条基础告警温度超过某个阈值比如 85℃、持久化 NPU 利用率为 0可能是任务挂死或没调度上、HBM 使用量超过 90%。这些告警不一定马上通知但至少能在任务异常时留下线索。7. 实战排错我踩过的坑和排查思路7.1 调度成功但容器里没有设备这是最经典的坑我前文也提过。现象是Pod 已经 Runningkubectl exec进去执行ls /dev/davinci0却不存在。这时候第一件事不是检查 device-plugin而是检查 Pod 是否声明了runtimeClassName: ascend。如果没写这一行K8s 会用默认 runc 创建容器而 runc 根本不知道 NPU 设备的存在含义自然不会注入设备。还有一种情况是 RuntimeClass 写了但 handler 名称和 Docker 配置里的 runtime name 不一致。比如 daemon.json 里写的是ascendRuntimeClass 里 handler 写的却是ascend-runtime对不上就静默回退到默认运行时。排查时可以docker inspect 容器ID看Config.Labels或者运行时元数据确认实际用了哪个 runtime。7.2 版本不匹配导致的异常昇腾软件栈版本相关的报错花样很多有些错误信息还带有误导性。比如驱动和 CANN 版本不配套时可能报aclrtSetDevice failed、10070、E10004等网上搜答案五花八门很容易带着你在设备权限和内核模块上绕圈子。我建议排障流程固定成先查/usr/local/Ascend/driver/version.info和/usr/local/Ascend/ascend-toolkit/latest/version.cfg核对版本对应关系再考虑硬件问题。还有一个经常被忽略的是容器镜像里的 CANN 版本。宿主机驱动版本是新的但业务镜像里打包的 CANN 是旧的这种不配套不会在设备挂载阶段报错而会在真正调用算子时出现莫名其妙的性能下降或运行失败。所以 CI 构建镜像时一定要锁定 CANN 版本不能每次都拉 latest。7.3 设备状态与回收机制问题device-plugin 工作正常、资源也上报了但用了一段时间后某些节点上的 NPU 资源会一直显示被占用即使上面的 Pod 已经删了。这大概率是设备回收或者 Pod 清理不彻底。先看kubectl describe node里的 allocated resources 和实际运行的 Pod 是否一致再看有没有残留的 terminated Pod。如果确认是 device-plugin 状态和 kubelet 不一致只能重启 kubelet 或者重建 device-plugin Pod这是设备插件机制本身的一个痛点升级插件版本前要看变更说明。另外一个常见现象是 NPU 设备本身 hang 住npu-smi info显示 abnormalPod 里的任务报错。这种情况通常要npu-smi reset重置指定设备严重时得重启节点。我习惯在运维脚本里加一个针对 NPU 设备的健康巡检定时上报状态避免业务方用了好几天才发现设备故障。7.4 多节点多卡的扩展经验最后补充一点多节点经验的体会。NPU 数量上来以后我强烈建议按芯片型号拆分节点池或打 label 区分。比如 310P 推理节点和 910B 训练节点不要混在一个池子里否则 device-plugin 上报的资源名虽然不同但调度策略、镜像和 runtime 版本可能会互相污染。我自己就是因为混用后某次升级驱动导致训练节点的推理 Pod 全部异常花了整整一个下午复盘。后来用 label 分开后升级和扩缩容都清爽很多。昇腾 NPU 接入 K8s 这件事难度不在某个单一组件而在整个链路的串联。驱动装好只是开始Runtime 配好也只是开始真正能让人舒服的是当你看到 Pod 里的应用顺畅调用 NPU、Grafana 面板上指标平稳、业务方再也不用来回问“卡为什么用不了”的时候。这套基础部署做完之后后面还有一个更有意思的方向在 K8s 里跑昇腾推理服务的弹性伸缩以及用上了量化压缩后效率更高的大模型推理方案跟设备层的这套配合又是另一套玩法。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价