首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >华为昇腾 310B芯片K8s统一 NPU调度实战:从踩坑到填坑的全过程

华为昇腾 310B芯片K8s统一 NPU调度实战:从踩坑到填坑的全过程

作者头像
编码如写诗
发布2026-06-25 16:39:52
发布2026-06-25 16:39:52
4740
举报
文章被收录于专栏:编码如写诗编码如写诗

一、背景

随着 AI 推理和训练场景的普及,如何在 Kubernetes 集群中统一管理和调度异构算力芯片成为一个日益迫切的需求。Kubernetes 提供了 Device Plugin 机制,允许厂商通过标准的 gRPC 接口将专用硬件(GPU、NPU、FPGA 等)注册到集群中,实现与 CPU/内存一致资源的调度体验。

华为昇腾生态提供了 ascend-device-plugin(隶属于 MindX DL 项目),用于将昇腾 NPU 注册到 K8s。本文以 Atlas 200I A2 加速模块(搭载 310B1 芯片) 为目标硬件,记录完整的部署实战过程。

环境概览

组件

版本

节点系统

openEuler (aarch64)

NPU 型号

Ascend 310B1 (Atlas 200I A2)

DCMI

24.1.rc1

CANN

8.0.RC1

K8s 运行时

Docker

目标设备插件

ascend-device-plugin v26.0.0

二、先确保驱动能通

在接入 K8s 之前,第一步一定是先在 Docker 中验证基础功能可用。这一步我们的目标是确认 device-plugin 容器能正确调用宿主机的 DCMI 接口,成功识别出 310B1 芯片。

2.1 确认宿主 NPU 状态

npu-smi info

驱动正常,芯片健康。

2.2 跑官方 device-plugin 镜像

按照官方文档:https://www.hiascend.com/developer/ascendhub/detail/a592da7bd2ab4dffa8864abd4eac5068

使用device-plugin:v7.3.1版本一直报错,即使加入了所有得依赖,最终依然报错HDC初始化失败,不论是否使用Ascend Docker Runtime都不行。最终决定直接使用docker运行device-plugin镜像,依然失败:

最终通过翻看官方文档,找到一处关键地方,如下,需要映射hdcBasic.cfg文件。加入此映射后,docker容器不再报错hdc初始化失败。

代码语言:javascript
复制
docker run --rm -it --privileged --network=host \
  -v /usr/local/Ascend/driver:/usr/local/Ascend/driver \
  -v /usr/lib64:/usr/lib64:ro \
  -v /dev:/dev -v /sys:/sys \
  -v /etc/hdcBasic.cfg:/etc/hdcBasic.cfg:ro \
  -v /etc/sys_version.conf:/etc/sys_version.conf:ro \
  -v /etc/ascend_install.info:/etc/ascend_install.info:ro \
  -v /var/slogd:/var/slogd:ro \
  -v /var/dmp_daemon:/var/dmp_daemon:ro \
  -v /var/queue_schedule:/var/queue_schedule:ro \
  -v /dev/shm:/dev/shm \
  -e LD_LIBRARY_PATH=/usr/lib64:/usr/local/Ascend/driver/lib64/... \
  ascend-k8sdeviceplugin:v7.3.1 \
  /usr/lib64/ld-linux-aarch64.so.1 /usr/local/bin/device-plugin -logLevel=0

这里有几个不寻常的细节需要注意:

  • /var/slogd/var/dmp_daemon/var/queue_schedule 是可执行文件,不是目录也不是 socket,挂载时需要 type: File + readOnly
  • /dev/shm 必须用 hostPath,不要用 emptyDir——dmp_daemon/dev/shm/iam//dev/shm/dmp/ 下有共享内存通信文件
  • 启动命令必须用宿主机ld-linux-aarch64.so.1 前缀,因为容器内 glibc 版本与宿主机不兼容,直接执行会触发 SIGBUS 崩溃

运行结果:

代码语言:javascript
复制
chipName: 310B1, devType: Ascend310B
[ERROR] get chip aicore count failed, err: invalid ai core num 1.000000

芯片识别到了,但报错 invalid ai core num 1.000000——这就是本文的核心问题。

三、源码分析

3.1 问题现象

错误日志非常明确:

代码语言:javascript
复制
devicefactory/entry.go:37    init device manager failed
server/manager.go:162    get chip aicore count failed, err: invalid ai core num 1.000000

3.2 源码追踪

server/manager.go:162 开始追踪调用链:

调用链 1:入口

代码语言:javascript
复制
// server/manager.go:162
cnt, err := hdm.manager.GetChipAiCoreCount()

调用链 2:DCMI 查询

代码语言:javascript
复制
// pkg/device/ascendcommon.go:1309
func (m *AscendManager) GetChipAiCoreCount() (int64, error) {
    chipAICore := float64(m.TotalResource.Computing.Aic)  // DCMI 返回 1.000000
    cnt, err := getAiCoreCount(chipAICore)
    ...
}

调用链 3:致命 range check

代码语言:javascript
复制
// pkg/device/ascendcommon.go:1335
func getAiCoreCount(chipAICore float64) (int64, error) {
    intAICore := int64(chipAICore)
    if intAICore < common.MinAICoreNum || intAICore > common.MaxAICoreNum {
        return 0, fmt.Errorf("invalid ai core num %f", chipAICore)
    }
    ...
}

调用链 4:常量定义(问题所在)

代码语言:javascript
复制
// pkg/common/constants.go:340
MinAICoreNum = 8
MaxAICoreNum = 36

3.3 根因总结

问题出在 constants.go 的第 340 行:

代码语言:javascript
复制
MinAICoreNum = 8

DCMI 24.1.rc1 对 310B1 芯片返回的 AI Core 数量是 浮点数1.000000(310B1 只有 1 个物理 AI Core)。getAiCoreCount 函数将其转为 int64(1),然后检查 [8, 36] 的合法范围——1 < 8,直接被拒绝。

换句话说:device-plugin 的 AI Core 数量范围默认假设芯片至少有 8 个 AI Core,而 310B1 只有 1 个。这是一个代码层面没有考虑 310B1 这类低功耗推理芯片的兼容性 bug。

四、修复方案

4.1 代码修复

pkg/common/constants.go 中,将 MinAICoreNum8 改为 1

代码语言:javascript
复制
// 修复前
MinAICoreNum = 8

// 修复后——310B1 只有 1 个 AI Core
MinAICoreNum = 1

仅需一行代码改动。但问题在于:device-plugin 的编译依赖 CGO(通过 dlopen 动态加载 DCMI 库),而且最终需要运行在 aarch64 架构上。

4.2 多阶段 Dockerfile

我们采用多阶段构建策略:第一阶段用 Go 交叉编译,第二阶段用轻量 Ubuntu 做运行时:

代码语言:javascript
复制
# ===== 阶段 1:编译 =====
FROM golang:1.23 AS builder

WORKDIR /build
COPY ascend-device-plugin/ /build/
COPY ascend-common/ /ascend-common/

RUN go env -w GOPROXY=https://goproxy.cn,direct && \
    go mod download && \
    CGO_ENABLED=1 GOOS=linux GOARCH=arm64 \
    go build -ldflags="-s -w" -o device-plugin .

# ===== 阶段 2:运行时 =====
FROM ubuntu:22.04

COPY --from=builder /build/device-plugin /usr/local/bin/
RUN chmod 550 /usr/local/bin/device-plugin

说明:基础镜像分别需要 golang:1.23ubuntu:22.04,在国内环境建议通过 DaoCloud 代理拉取:

代码语言:javascript
复制
docker pull docker.m.daocloud.io/library/golang:1.23
docker pull docker.m.daocloud.io/library/ubuntu:22.04
docker tag docker.m.daocloud.io/library/golang:1.21 golang:1.21
docker tag docker.m.daocloud.io/library/ubuntu:22.04 ubuntu:22.04

五、部署验证

5.1 构建镜像

代码语言:javascript
复制
tar -xzf device-plugin-310B1-fixed.tar.gz
bash build.sh
# 输出: Successfully tagged ascend-k8sdeviceplugin:v26.0.0.fixed

5.2 部署到 K8s

代码语言:javascript
复制
kubectl apply -f device-plugin-310B1.yaml

输出:

代码语言:javascript
复制
serviceaccount/ascend-device-plugin created
clusterrole.rbac.authorization.k8s.io/ascend-device-plugin created
clusterrolebinding.rbac.authorization.k8s.io/ascend-device-plugin created
daemonset.apps/ascend-device-plugin-daemonset created

5.3 查看日志

代码语言:javascript
复制
kubectl logs -n kube-system -l name=ascend-device-plugin-ds --tail=20

关键日志行:

代码语言:javascript
复制
deviceManager get cardList is [0], cardList length equal to cardNum: 1
chipName: 310B1, devType: Ascend310B
init device manager success              ← 初始化成功!
update node label success                ← 节点标签更新成功!
register Ascend310 to kubelet success    ← 向 kubelet 注册成功!
Ascend310-0 Healthy                      ← 设备状态健康!

5.4 验证节点资源

代码语言:javascript
复制
$ kubectl describe node node1 | grep Ascend310

Capacity:
  huawei.com/Ascend310:  1

huawei.com/Ascend310: 1——NPU 资源已经成功注册到 Kubernetes,可以像 CPU 和内存一样被调度使用了。

六、踩坑要点总结

在本次实战中,我们踩过的主要坑和对应的解决方案:

#

踩坑点

现象

解决方案

1

MinAICoreNum = 8

invalid ai core num 1.000000

修改源码为 MinAICoreNum = 1

2

可执行文件挂载为目录

MountVolume 失败

type: File + readOnly: true

3

/dev/shm 用 emptyDir

dm_send_msg fail:2

改为 hostPath(共享内存文件在宿主机上)

4

容器 glibc 不兼容

SIGBUS 退出码 135

用宿主 ld-linux-aarch64.so.1 前缀启动

5

RBAC 权限不足

cannot get resource "nodes"

创建 SA + ClusterRole + ClusterRoleBinding

6

ConfigMap 权限缺失

cannot update resource "configmaps"

ClusterRole 添加 configmaps CRUD 权限

7

kubelet API 无法访问

host ip is invalid

设置 NODE_IP env var (status.hostIP)

8

Golang 基础镜像拉取慢

Docker Hub 超时

通过 DaoCloud 代理 docker.m.daocloud.io

七、生产化部署 checklist

如果你的环境有多台 310B1 节点需要接入 K8s,按以下顺序操作:

  • 每台节点安装 CANN 驱动、DCMI,确认 npu-smi info 正常
  • 准备基础镜像(golang:1.23 + ubuntu:22.04
  • 构建修复后的 device-plugin 镜像(build.sh
  • 给节点打 label:kubectl label node <node> accelerator=huawei-Ascend310
  • kubectl apply -f device-plugin-310B1.yaml
  • 验证 kubectl describe node <node> | grep Ascend310
  • 部署测试 Pod,声明 huawei.com/Ascend310: 1 验证容器内可调用 NPU

八、写在最后

这次实战经历可以总结为三个层次的工作:

  1. 适配层:理解 NPU 驱动(DCMI)和 device-plugin 之间的数据交互协议,发现浮点数格式差异
  2. 源码****层:深入 device-plugin 源码,定位 AI Core 数量校验逻辑的硬编码边界,一行代码修复
  3. 工程层:处理 YAML 配置、挂载、RBAC、glibc 兼容性等一系列 K8s 落地细节

国产 AI 芯片 + 云原生的结合正在快速发展,生态工具也在持续迭代。希望本文的实战记录能为走在这条路上的朋友节省一些排查时间。如果本文对你有帮助,欢迎分享给更多的同行。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-06-24,如有侵权请联系 cloudcommunity@tencent.com 删除
目录
  • 环境概览
  • 二、先确保驱动能通
    • 2.1 确认宿主 NPU 状态
    • 2.2 跑官方 device-plugin 镜像
  • 三、源码分析
    • 3.1 问题现象
    • 3.2 源码追踪
    • 3.3 根因总结
  • 四、修复方案
    • 4.1 代码修复
    • 4.2 多阶段 Dockerfile
  • 五、部署验证
    • 5.1 构建镜像
    • 5.2 部署到 K8s
    • 5.3 查看日志
    • 5.4 验证节点资源
  • 六、踩坑要点总结
  • 七、生产化部署 checklist
  • 八、写在最后
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档