ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

昇腾NPU接入Kubernetes完整实践:从驱动到device-plugin

昇腾NPU接入Kubernetes完整实践:从驱动到device-plugin 上个月接到一个需求要把机房那批 Atlas 800 服务器上的昇腾 NPU 接入到 Kubernetes 集群里。算法团队手里有基于 CANN 的推理服务要上容器还准备跑 Swift Megatron 这类大模型训练任务不能再像以前那样手动指定--device、靠机器标记绑定算力了。折腾了大概一周把驱动、CANN、Ascend Docker Runtime、device-plugin、监控全部串起来之后发现昇腾 NPU 进 K8s 的整套链路其实已经有比较成熟的做法只是资料散在各处版本匹配又特别敏感坑不少。这篇文章就把这次接入的完整过程拆开讲清楚。我会先从整体架构讲为什么需要这几层组件再逐层说明安装部署时的要点和验证方法最后把我踩过的坑和排查思路整理成速查表。整个过程不局限于某一种特定的容器引擎或集群发行版只要你的节点是 x86_64 或 aarch64 的主流 Linux 发行版基本都能照做。1. 接入前的整体设计与思路拆解1.1 昇腾 NPU 在 K8s 里到底算什么资源在 Kubernetes 的调度模型里CPU 和内存是第一类资源NPU、GPU 这类设备属于扩展资源也就是 Extended Resource。K8s 本身不关心这个资源到底是什么它只关心两件事节点上这个资源有多少、某个 Pod 需要多少。真正让 K8s 感知到 NPU 存在的工作是由 device-plugin 完成的。所以昇腾 NPU 接入 K8s 的第一步不是跑一堆 YAML而是想清楚这条链路里每层组件各自承担什么职责。我们可以把过程拆成四层规约层device-plugin 把昇腾设备注册到 kubelet以huawei.com/Ascend910这类资源名暴露给调度器。运行时层K8s 创建 Pod 后kubelet 调用容器运行时容器运行时通过 Ascend Docker Runtime 把 NPU 设备、驱动目录、环境变量注入到容器。用户态依赖CANN Toolkit 提供算子库和运行时依赖容器的业务进程靠它来调用昇腾硬件。内核态底座驱动和固件让昇腾设备在宿主机上可见、可用。一句话描述就是驱动让节点看到设备CANN 让应用能用设备Runtime 让容器能挂设备device-plugin 让 K8s 能调度设备。任何一层断了现象可能都不一样。我见过很多人只装了驱动就在 K8s 里跑容器结果 Pod 报一堆 RuntimeError最后发现是 CANN 没装。1.2 为什么不能像 GPU 那样直接挂设备用过 NVIDIA GPU 的朋友可能觉得昇腾接入的套路应该和 nvidia-device-plugin 差不多。思路确实像但有一点关键差异不能忽视NVIDIA 那套生态有 libnvidia-container 做了很完整的容器隔离而昇腾的 Ascend Docker Runtime 是一个基于 runc 的 OCI runtime 实现它做的事情更接近“把设备目录和设备文件动态挂进容器”。具体来说Ascend Docker Runtime 会在容器启动前动态注入这些内容/dev/davinci*设备文件、/dev/davinci_manager、/dev/hisi_hdc、/dev/devmm_svm以及/usr/local/Ascend/driver下面的驱动依赖。如果你跳过 Runtime 直接手动挂载也能跑起来但设备的分配、权限控制、多容器隔离就完全失控了。这也是为什么我不建议走“手工挂载设备”这条捷径。短时间跑通很容易等你要做资源池化、配额控制、多租户隔离的时候完全没有抓手。1.3 版本选型先想清楚否则后面全白干昇腾的软件栈版本联动非常严格这可能是整套部署里最让人头大的事情。驱动、固件、CANN 三个东西并不是越新越好而是要互相匹配。官方每个版本会出配套关系表我这次用的是一套相对稳定的组合具体版本号就不贴了避免误导但选型的原则可以分享先确认硬件型号。Atlas 300I 推理卡、Atlas 300T 训练卡、Atlas 800 训练服务器、Atlas 910B/A2 这类训练芯片对应的驱动和 CANN 版本策略都不一样。查驱动和固件的配套表。驱动和固件必须成套升级不能只动其中一个。再查驱动和 CANN 的兼容矩阵。CANN 版本太新或太老都可能出现接口对不上。最后确认 Ascend Docker Runtime 和 device-plugin 兼容的驱动范围。另一个容易忽略的事情是操作系统和内核。昇腾官方驱动对操作系统内核版本有明确限制尤其是 aarch64 架构 特殊内核经常需要匹配特定补丁。如果宿主机是定制化内核建议先在非生产节点上验一下npu-smi info能不能正常输出再往下走。2. 宿主机环境准备驱动与 CANN 安装2.1 驱动与固件安装的完整顺序昇腾驱动安装常见的坑是“固件乱刷”。官方把驱动和固件打包成Ascend HDK安装包里面包含驱动、固件和配套工具。安装时有几个要点安装包建议以 root 用户执行或者使用具有 sudo 权限的账号。安装前卸载旧版本卸载顺序和安装相反先卸载固件再卸载驱动。安装命令大致是chmod x Ascend-hdk-*.run ./Ascend-hdk-*.run --full --install安装完成后用npu-smi info验证驱动是否正常。正常情况下会列出 npu 设备的编号、型号、温度、HBM 使用率、AI Core 的利用率等信息。如果提示设备不存在或者报 HwHiAiUser 权限问题先检查驱动是否成功加载ls /dev/davinci* ls /usr/local/Ascend/driver这里有第一个常见坑驱动安装完后设备文件可能挂在/dev/davinci0这些节点上但如果容器运行时没有权限还是会报错。建议先用npu-smi info在宿主机上确认每张卡都能看到。2.2 CANN Toolkit 的安装与定位CANN 是昇腾的计算架构包含 AscendCL、GE 图引擎、算子库、推理/训练 runtime 等组件。装 CANN 之前驱动必须已经能正常识别设备否则装完 CANN 也无法工作。CANN Toolkit 安装包是.run文件默认安装到/usr/local/Ascend/ascend-toolkit/目录下会带一个latest软链接指向当前版本。安装命令chmod x Ascend-cann-toolkit_*.run ./Ascend-cann-toolkit_*.run --install安装完后需要设置环境变量。我习惯把这部分变量固化到/etc/profile.d/ascend.sh然后 source 它export ASCEND_HOME/usr/local/Ascend/ascend-toolkit/latest export LD_LIBRARY_PATH$ASCEND_HOME/lib64:$ASCEND_HOME/lib64/plugin/opskernel:$ASCEND_HOME/lib64/plugin/nnengine:$LD_LIBRARY_PATH export PATH$ASCEND_HOME/bin:$ASCEND_HOME/compiler/ccec_compiler/bin:$PATH export PYTHONPATH$ASCEND_HOME/python/site-packages:$PYTHONPATH export ASCEND_AICPU_PATH$ASCEND_HOME export ASCEND_OPPER_PATH$ASCEND_HOME/opp注意宿主机上装 CANN 主要是为了验证驱动和跑 npu-smi 工具真正的算法依赖通常是通过镜像把 CANN 打包进去。不过为了简化镜像构建也可以用“统一基础镜像 宿主机 CANN 映射”的方式这在昇腾场景里很常见。2.3 快速验证宿主机环境是否可用环境准备完成后最有效的验证方式是跑一个 Python 小脚本调用 AscendCL 初始化设备import acl ret acl.init() print(acl.init ret , ret) ret acl.finalize()如果返回不为 0基本就是 CANN 安装有问题或者环境变量不对。还有一个小技巧用npu-smi info查看设备拓扑时可以顺手记录一下每张卡的 NUMA 节点。后面做 K8s 调度时如果要追求极致性能通常要结合 NUMA 亲和性把容器绑到正确的 CPU 核心上这个信息后面有用。3. 接入容器生态Ascend Docker Runtime 部署3.1 Runtime 在容器启动时到底帮你做了什么Ascend Docker Runtime 的作用是在容器运行时阶段自动为容器注入 NPU 设备相关的资源。它本质是一个 OCI runtime wrapper启动容器时会做动作解析环境变量ASCEND_VISIBLE_DEVICES指定容器能看到哪几张卡。根据设备编号找到对应的/dev/davinciX设备文件。挂载/usr/local/Ascend/driver下的必要库文件到容器内。注入设备管理相关的/dev节点和必要内核模块路径。你可以把它理解成 NVIDIA Container Toolkit 的昇腾版本只是它的实现更偏“传统跑runC”的路子。安装方式是在宿主机上装一个.run的运行时包然后分别给 Docker 或 containerd 配置 runtime。3.2 Docker 侧安装配置昇腾官方提供了安装脚本也可以手动下载.run包安装。安装完成后需要在 Docker 的 daemon.json 里增加 runtime 配置mkdir -p /etc/docker cat /etc/docker/daemon.json EOF { runtimes: { ascend: { path: /usr/local/Ascend/Ascend-Docker-Runtime/ascend-docker-runtime, runtimeArgs: [] } } } EOF systemctl restart docker验证方式也很直观先看宿主机的 NPU IDnpu-smi info假设显示 4 张卡编号 0 到 3下面这条命令可以把 0 号卡映射到容器里docker run --runtime ascend -e ASCEND_VISIBLE_DEVICES0 \ -it ascend/cann:latest npu-smi info进去之后npu-smi info如果能看到卡而且设备编号和宿主机一致说明 Runtime 工作正常。这里有个我认为很重要的细节ASCEND_VISIBLE_DEVICES的语义是“容器内可见的设备编号”你可以填0,1也可以填物理卡号还可以填-1表示不映射任何设备。但这个环境变量必须显式传给容器device-plugin 会自动生成它手工 docker run 时则必须手动指定。3.3 containerd 集群怎么处理现在很多 Ingress 和容器集群默认跑 containerd不再用 docker。昇腾 Runtime 对 containerd 的支持也做了插件适配需要在/etc/containerd/config.toml里声明一个 runtime。常见的 containerd 配置片段是这样的version 2 [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.ascend] runtime_type io.containerd.runc.v2 runtime_path /usr/local/Ascend/Ascend-Docker-Runtime/ascend-docker-runtime不同 containerd 版本配置路径略有差异我这边用的是 containerd 1.7 的格式。配置完成后要重启 containerdsystemctl restart containerd验证 containerd 场景下有没有生效最直接的方法是用 crictl 创建一个带资源声明的 Pod。如果 Pod 被调度到节点后一直处于 ContainerCreating大概率 runtime 没配对看 kubelet 日志时会出现unknown runtime ascend之类的关键字。4. 让 K8s 认识 NPUdevice-plugin 部署与验证4.1 device-plugin 暴露资源的机制K8s 从 1.10 开始支持设备插件机制device-plugin 是运行在节点上的一个 gRPC 服务。它通过/var/lib/kubelet/device-plugins/kubelet.sock和 kubelet 通信做三件事上报设备列表告诉 kubelet 这个节点有哪些昇腾卡。扩展资源把设备数量注册到capacity和allocatable。分配设备Pod 调度到节点后kubelet 让 device-plugin 返回需要注入的环境变量和设备文件列表。昇腾官方的k8s-device-plugin项目在 Gitee 上有开源里面提供了 ConfigMap 和 DaemonSet 样例。它支持按设备型号暴露不同资源名也支持 vNPU 虚拟化设备。4.2 部署 device-plugin 的具体 YAML我这次用的是 DaemonSet 方式部署保证每个有昇腾卡的节点都跑一个 plugin 实例。下面是一份可参考的 YAML注意我把驱动路径、日志目录等常见需要的挂载都配了进去apiVersion: v1 kind: ConfigMap metadata: name: ascend-device-plugin-config namespace: kube-system data: config.json: | { version: 1.0.0, plugins: [ { name: ascend-toolkit, version: 6.2, device-type: Ascend910, resource-name: huawei.com/Ascend910, ascend-visible: all } ] } --- 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:latest imagePullPolicy: IfNotPresent securityContext: privileged: true env: - name: ASCEND_PLUGIN_CONF value: /etc/ascend/config.json volumeMounts: - name: dp mountPath: /var/lib/kubelet/device-plugins - name: sys mountPath: /sys - name: driver mountPath: /usr/local/Ascend/driver - name: config mountPath: /etc/ascend readOnly: true - name: log mountPath: /var/log volumes: - name: dp hostPath: path: /var/lib/kubelet/device-plugins - name: sys hostPath: path: /sys - name: driver hostPath: path: /usr/local/Ascend/driver - name: config configMap: name: ascend-device-plugin-config - name: log hostPath: path: /var/log部署命令就是kubectl apply -f ascend-device-plugin.yaml部署完成后看 DaemonSet 状态kubectl -n kube-system get pods | grep ascend如果 Pod 状态正常马上看一下节点资源里有没有昇腾资源kubectl get node node-name -o json | jq .status.allocatable预期在 allocatable 里能看到类似这样的字段{ huawei.com/Ascend910: 4 }如果不能看到先看 device-plugin Pod 日志通常是因为配置文件里的device-type和宿主机实际型号不一致或者挂载的驱动路径不对。4.3 提交一个真正使用 NPU 的 Pod资源注册成功之后写一个简单的测试 Pod确认调度和资源申请链路是通的。在 K8s 里申请 NPU 资源不需要再像手工运行时那样写ASCEND_VISIBLE_DEVICESdevice-plugin 会自动处理apiVersion: v1 kind: Pod metadata: name: ascend-test spec: restartPolicy: Never containers: - name: ascend-test image: ascend/cann:latest command: [npu-smi, info] resources: limits: huawei.com/Ascend910: 1注意 limits 下写的是huawei.com/Ascend910这个资源名必须和 ConfigMap 里注册的名字完全一致。如果写错Pod 会一直 Pending 或者调度不到对应节点。成功运行后kubectl logs ascend-test的输出里应该能看到一张昇腾卡的详细信息。4.4 多卡共享和 vNPU 虚拟化怎么处理默认情况下huawei.com/Ascend910: 1表示分配整张物理卡。昇腾也支持把物理卡切成多个 vNPU 设备类似于 NVIDIA 的 MIG 功能。使用 vNPU 时ConfigMap 里的 device-plugin 配置要改成 vNPU 类型同时资源名和粒度的定义也要变。这里有一个实际操作中容易搞混的点物理卡申请和 vNPU 申请不要在同一个节点上混用至少我测下来这样会出现设备冲突。建议要么整卡要么全走虚拟化。5. 监控与排障5.1 昇腾设备的监控指标从哪来NPU 的监控数据和 GPU 不太一样你没法直接用 DCGM 那套。最底层的监控来源是昇腾驱动自带的npu-smi info但它输出的是文本不适合直接给 Prometheus 采集。实践中常见的做法是部署一个 Ascend Exporter它会周期性查询驱动接口然后把指标暴露成 Prometheus 格式。常见的监控指标大概有这几类AI Core 利用率反映计算单元忙闲程度。HBM 显存使用量和总量这个和 GPU 显存一样关键大模型训练经常卡在显存不足。芯片温度、功耗、电压用于机房散热和功耗管理。设备状态比如离线、降频、错误等。5.2 将监控接入 Prometheus 与 Grafana我这边用 DaemonSet 方式部署 exporter让它在宿主机网络下运行然后配置 Prometheus 抓取。Prometheus 的抓取配置大致如下scrape_configs: - job_name: ascend-npu static_configs: - targets: [node-ip:9100]当然如果集群规模大、节点多更推荐用 Kubernetes SD 配置自动发现节点。拿到指标后在 Grafana 里可以按节点、按 NPU ID 分组看每张卡的 AI Core 利用率和 HBM 用量。接入 Prometheus 之后最有价值的一件事是在做调度测试时你能清楚看到每张卡的真实负载然后判断要不要在 device-plugin 层做负载感知调度。默认 device-plugin 的资源分配是“先到先得”不会管卡和卡之间负载是否均衡。我曾经遇到过 0 号卡跑满、1 号卡完全空闲但新 Pod 照样被排到 0 号卡的情况。5.3 常见问题速查表这里把这次部署里最常踩的几个问题直接整理成表格方便排查现象大概率原因排查方法驱动安装后npu-smi info找不到设备固件和驱动版本不匹配或内核模块未加载查看ls /dev/davinci*比对官方驱动固件配套表容器内看不到设备节点Runtime 配置未生效或容器缺权限确认 daemon.json runtime 路径确认 privilegedPod 一直 Pending资源名写错或节点没有对应资源查看kubectl describe node的 allocatablePod 报AscendCL init failedCANN 环境变量缺失或版本不兼容检查镜像内/usr/local/Ascend/ascend-toolkit/latest是否存在device-plugin Pod crash配置文件不规范或驱动路径挂载错误查看 device-plugin 日志核对/usr/local/Ascend/driver多容器同卡性能互相影响整卡被多个容器共享确认huawei.com/Ascend910是否按整卡申请必要时用 vNPU5.4 几个实测中容易踩的坑第一个坑是“改了资源声明但调度没变”。K8s 的节点 allocatable 一旦上报不会因为你改了 device-plugin 配置就实时更新。这时要重启 kubelet或者删除节点上的 device-plugin Pod 让它重新注册。第二个坑是镜像里的驱动目录和宿主机的驱动目录冲突。因为我用 HostPath 把/usr/local/Ascend/driver挂进了容器所以容器镜像里不应该再自己带一套驱动。如果镜像里已经存在同名目录容器启动时会互相覆盖表现就是某些版本能跑某些版本不行。第三个坑是内核升级。Linux 内核更新后昇腾驱动模块经常需要重新编译或重装。我遇到过一次宿主机内核自动更新结果重启后所有 NPU 设备全部消失。如果你用了自动安全更新建议把内核包固定住或者设置恢复快照。第四个坑是 NUMA 亲和性。昇腾卡挂在某个 NUMA 节点下如果容器绑核绑到远端 CPU性能损耗可能达到一到两成。训练任务尤其明显。建议在 Pod 里结合 CPU manager 和拓扑管理器做一致性调度先把cpuManagerPolicystatic打开再测试。另外如果你在 K8s 里同时跑普通业务和 NPU 业务建议给昇腾节点打上专用标签然后给 NPU 任务加 nodeSelector。这样能避免普通任务把核都占满影响 NPU 任务的 CPU 性能。6. 一些实地部署后的个人体会整套昇腾 NPU 接入 K8s 的流程走下来我的感受是组件其实不多核心链路就是驱动、CANN、Runtime、device-plugin、exporter 这五件事但每一层都对版本非常敏感。最省心的方式是把硬件型号、操作系统、内核版本、驱动版本、固件版本、CANN 版本、Runtime 版本、device-plugin 版本全部记下来做成一张版本清单以后每次变更都对照着查。实际操作中我最推荐的落地顺序是先在单个节点上完全不碰 K8s把驱动、CANN、Docker Runtime 跑通再部署 device-plugin最后再上 Prometheus 监控。如果一开始就直接在集群里铺开出问题时很难判断是调度问题还是底层驱动问题。最后再分享一个小经验昇腾这块的官方文档和社区资料更新很快不同版本之间命令参数、配置文件位置都会有变化。遇到版本不一致导致的报错不要只看报错本身先确认所有组件的版本都在官方兼容矩阵内。我这次有一半以上的时间都花在版本对齐上真正部署和改配置反而很快。希望这篇实操记录能帮你少走点弯路。后面如果大家需要我可以继续把昇腾设备在 K8s 上如何做弹性伸缩、如何结合负载感知调度做更精细的资源分配以及大模型训练场景下多机多卡如何通过宿主网络和高性能存储配合调优逐个展开聊聊。
返回列表