news 2026/9/18 4:13:32

海光DCU接入Kubernetes全攻略:从Device Plugin到vDCU虚拟化与DeepSeek部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
海光DCU接入Kubernetes全攻略:从Device Plugin到vDCU虚拟化与DeepSeek部署

上个月我们接到一个内部任务:把新到的一批海光 DCU 服务器接入现有 Kubernetes 集群,再通过 CubeStudio 把这些算力以整卡、共享以及两种 vDCU 虚拟化模式开放给算法团队,最后还要在集群里直接拉起 DeepSeek 推理服务。整套做下来,踩了不少文档里没写清楚的坑,也摸清了海光 DCU 在云原生环境里的脾气。这篇文章把我这段实操经验整理成一份可以直接照着做的接入笔记,覆盖从驱动、Device Plugin、整卡调度,到 vDCU 虚拟化和 CubeStudio 平台纳管,再到 vLLM 部署 DeepSeek 的全链路,适合正在做 DCU 集群建设、AI 平台适配或者模型推理服务化的同学参考。

1. 接入链路全景:DCU 从驱动到 K8s 调度要过哪几道关

1.1 容器里能看到卡,和“能被调度”是两回事

很多第一次接触加速卡接入 Kubernetes 的同学,第一反应是“把/dev/dri/dev/kfd直接挂进容器不就完事了”。真实情况是,这样确实能让容器里的程序访问到卡,但调度器完全不知道集群里谁有卡、有几张卡、每张卡剩多少显存。结果是运维手工指定节点,算法同学排队等,资源利用率全靠缘分。

Kubernetes 解决这个问题靠的是扩展资源(Extended Resource)加 Device Plugin 这套机制。扩展资源负责“上报”和“声明”,比如节点上有几张 DCU,就通过 kubelet 上报为hygon.com/dcu: 2。Device Plugin 则负责两件事:一是把卡设备注册给 kubelet,二是当 Pod 申请了扩展资源并被调度到节点后,由它把对应的设备文件、环境变量真正注入到容器里。

这里面有一个容易被忽略的细节:扩展资源的调度是按“个数”来的,不是按“显存大小”来的。如果你申请了hygon.com/dcu: 1,调度器只会保证你拿到一张卡,不保证这张卡还剩多少显存。这也是后面 vDCU 虚拟化要解决的问题之一。

1.2 海光 DCU 接入 K8s 的完整四层链路

以我们集群的成功实践来看,从底层到应用层大概是这么一条链路:

  • 第一层是节点驱动。海光 DCU 需要装对应的内核驱动模块,通常装完后/dev/dri/下会出现renderD128/dev/hygon/或者类似目录下会暴露卡设备,同时/dev/kfd也会出现,这一块和 ROCm 生态的 AMD 设备路径很像。
  • 第二层是用户态运行时。容器里跑的进程要能调用 DCU,需要 HIP/ROCm 运行库被打进镜像,或者通过注入LD_LIBRARY_PATH的方式挂进去。我们实践下来更推荐直接把运行库打进基础镜像,避免每次部署都依赖宿主机的库版本。
  • 第三层是 Device Plugin。它作为 DaemonSet 跑在每个节点上,负责把设备信息上报给 kubelet,同时处理设备注入。这一步做不好,后面全是问题,所以要先确认插件版本和你装的驱动版本匹配。
  • 第四层是平台调度。Device Plugin 上报后,Kubernetes 自带的 kube-scheduler 就能靠扩展资源完成基本调度。CubeStudio 这类 AI 平台做的事情是在这之上再封装资源池、配额和任务提交逻辑,让算法同学不需要手写 YAML。

这四层每一层都可能成为接入失败的原因。所以我建议不要急着把平台接进来,先把前三层用命令行手工验证通,再让平台层介入。

2. 环境摸底与版本对齐:别急着部署插件

2.1 节点侧检查清单和版本匹配表

海光 DCU 的接入,最忌讳的就是驱动、运行时、插件三个版本各自为政。我们第一批节点就踩过驱动版本和 Plugin 版本不匹配的坑,导致插件报错,节点容量一直刷不出来。下面这张表是我们在实际环境中稳定运行的一组版本匹配关系,可以作为参考:

组件检查项目常见问题
操作系统Kernel 版本与驱动兼容,建议 4.19+内核太老导致驱动编译失败
DCU 驱动节点上执行hygon-smirocm-smi能否看到卡看不到卡先查dmesg,多半是驱动没加载
HIP/ROCm 运行时/opt/rocm/bin/hipcc --version版本与驱动匹配版本不匹配时容器内hipSetDevice失败
Device Plugin资源名、设备路径与驱动实际暴露路径一致路径写错会导致节点有卡但 Pod 起不来
容器运行时containerd / docker 能访问设备节点未放行设备时容器内无权限

我建议把这张表做成你的“环境基线”,每次换驱动版本都要重新对齐一次。

2.2 用动态探测确认节点上的设备路径

在部署 Device Plugin 之前,先用最简单的方式确认设备到底在哪个路径。我们在节点上执行:

ls /dev/dri ls /dev/kfd hygon-smi --show

如果hygon-smi能显示每张卡的 PCIe ID、显存大小和利用率,说明驱动层是健康的。再看/dev/dri/renderD128是否存在,这个节点文件是容器运行时注入设备时的关键路径。

确认完设备路径后,还需要给节点打上标签。标签的主要目的是让 scheduler 和 CubeStudio 能识别“这台机器是 DCU 节点,而且是某型号的 DCU”。我们使用的标签格式如下:

kubectl label node dcunode01 accelerator/hygon-dcu=true kubectl label node dcunode01 dcu-model=Z100 kubectl label node dcunode01 dcu-memory=32G

这些标签在后面做节点亲和性、资源池划分时会非常有用,建议一开始就定好命名规范,别等平台接入了再补。

3. 整卡接入:Device Plugin 上报和第一个 DCU Pod

3.1 部署海光 DCU Device Plugin

整卡模式是所有接入方式的基础。先把整卡跑通,再去做 vDCU 虚拟化,排查问题会轻松很多。

海光 DCU 的 Device Plugin 一般以 DaemonSet 方式部署,关键点在于挂载目录和资源名的配置。我提供一个简化后的部署 YAML,实际安装包以官方提供的为准,但结构和下面这个类似:

apiVersion: apps/v1 kind: DaemonSet metadata: name: hygon-dcu-device-plugin namespace: kube-system spec: selector: matchLabels: name: hygon-dcu-device-plugin template: metadata: labels: name: hygon-dcu-device-plugin spec: hostNetwork: true containers: - name: device-plugin image: registry.example.com/hygon/device-plugin:v1.0 env: - name: NODE_NAME valueFrom: fieldRef: fieldPath: spec.nodeName securityContext: privileged: true volumeMounts: - name: device-plugin mountPath: /var/lib/kubelet/device-plugins - name: sys mountPath: /sys readOnly: true - name: dev mountPath: /dev volumes: - name: device-plugin hostPath: path: /var/lib/kubelet/device-plugins - name: sys hostPath: path: /sys - name: dev hostPath: path: /dev

这里privileged: true是必须的,因为插件需要访问宿主机的设备节点。hostNetwork: true不是绝对需要,但我们遇到有些版本插件要用节点 IP 做健康检查,开着更省事。

部署完成后,在节点上执行:

kubectl get pods -n kube-system | grep hygon-dcu

如果 Pod 是 Running 状态,就去检查节点容量有没有更新。

3.2 验证节点容量是否报告正确

检查节点容量我习惯用kubectl describe node,重点关注CapacityAllocatable两段:

kubectl describe node dcunode01 | grep -A 20 "Capacity"

正常情况下你应该能看到类似这样的字段:

hygon.com/dcu: 2

这里有两个容易踩的坑。第一,扩展资源名必须符合 DNS 命名规范,不能有下划线,不能大写,必须是域名/资源名的格式。第二,Allocatable里的数量可能会比Capacity少,因为 kubelet 默认会考虑给系统预留的资源,这属于正常现象。

如果Capacity一直没出现,别急着改插件配置,先把插件日志翻出来看:

kubectl logs -n kube-system $(kubectl get pods -n kube-system | grep hygon-dcu | awk '{print $1}')

日志里通常会有很明确的报错信息,比如“找不到设备”或者“socket 连接失败”。这类问题大概率是插件容器没有权限访问/dev目录,检查 DaemonSet 的securityContext配置即可。

3.3 第一个 DCU Pod 应该怎么定义

节点容量刷新后,直接提交一个最小化的测试 Pod。这个 Pod 只做一件事:进入容器后执行rocm-smihygon-smi,确认容器内能看到卡。

apiVersion: v1 kind: Pod metadata: name: dcu-test spec: restartPolicy: Never nodeSelector: accelerator/hygon-dcu: "true" containers: - name: dcu-test image: registry.example.com/hygon/rocm-base:5.7.0 command: ["/bin/bash", "-c"] args: - rocm-smi; sleep 30 resources: limits: hygon.com/dcu: 1

提交后观察事件:

kubectl describe pod dcu-test

如果 Pod 正常调度并看到rocm-smi输出了一张卡的型号和显存,说明整卡链路已经通了。这一步结束,Kubernetes 已经能把 DCU 当普通扩展资源来做最基本的调度了。

4. vDCU 虚拟化的两种模式:整卡之外的选择题

4.1 为什么整卡模式不够用

整卡模式虽然简单,但实际使用中资源浪费相当可观。我们内部跑过一批小型推理任务,模型本身的显存占用只有 6-8GB,却被迫占用一整张 32GB 的卡;还有算法同学想在开发环境做快速实验,每天都在“抢卡”而不是“用卡”。

vDCU 的作用就是把一张物理 DCU 拆成多个虚拟设备,让多个任务共享同一张卡。市面上不同厂商实现方式各异,但从用户视角看,海光 DCU 的 vDCU 大体分两种模式:独占模式和共享模式。理解这两种模式的本质差异,直接决定你如何配置资源。

4.2 vDCU 独占模式:显存切分,但彼此透明

独占模式可以理解为“显存层面的硬切分”。一张 32GB 的 DCU,可以按配置切出 4 个 vDCU,每个 vDCU 独享 8GB。每个 vDCU 之间在显存和计算单元上尽量隔离,某个任务崩溃不会导致同卡的其他 vDCU 直接不可用。

在 Kubernetes 里使用这种方式,通常需要在 Device Plugin 或者配套的 vDCU Manager 中配置分片方案。比如我们的配置里,有半张卡、四分之一张卡这样的规格,资源名也做了区分:

limits: hygon.com/vdcu-exclusive: 1

配套的 vDCU 管理组件会根据 Pod 请求的规格,找到一张剩余资源足够的物理卡,然后分配对应的虚拟设备并注入容器。容器里看到的是一张 8GB“逻辑卡”,跟整卡的使用方式没有区别。

这种模式适合对显存边界要求严格、需要避免互相干扰的场景,比如小模型微调、对稳定性要求高的推理服务。

4.3 vDCU 共享模式:时间片与动态调度

共享模式则更接近“超卖 + 时分复用”。多个 vDCU 可以跑在同一张物理卡上,共享显存和算力,由驱动层或运行时负责任务的切换。

共享模式的优点是利用率最高,尤其在大量短生命周期任务并存的场景下,一张卡可以同时承载好几个 Pod,空转时间大幅降低。代价是性能确定性差,某几个任务同时打满算力时,其他任务会明显变慢甚至出现超时。

如果你要让算法团队用它来跑 Notebook、调试代码、批量离线推理,共享模式体验很好;但如果跑的是在线服务,建议还是用独占模式来兜底。

4.4 两种模式的对照选型

我把两种模式的差异整理成一张表,方便你在平台资源池划分时做决策:

对比维度vDCU 独占模式vDCU 共享模式
显存隔离固定切分,互不侵占动态共享,可能互相挤占
性能确定性高,适合在线推理低,波动大,适合离线任务
利用率中,碎片化后仍有浪费高,适合大量小任务
部署复杂度需要显式规划分片规格需要关注超卖和 OOM
典型场景模型微调、在线推理Notebook、批量实验、流水线测试

如果你问我怎么选,我的建议是:整卡 + vDCU 独占 + vDCU 共享三种规格同时开放,对应不同作业类型。整卡给大模型训练,独占 vDCU 给推理和小规模微调,共享 vDCU 给算法同学日常开发。这样平台资源池能错开,不会出现一个跑批任务把在线推理打挂的问题。

5. CubeStudio 平台纳管:让算法同学不碰 YAML 也能用卡

5.1 平台如何拿到 DCU 资源信息

CubeStudio 这类 AI 平台和 Kubernetes 之间的对接,核心是通过 kube-apiserver 读取集群资源和事件。平台需要有权限访问节点信息、Pod 信息、CRD 信息,才能把算力情况展示到 UI 上。

我们在接入时,先在集群里给 CubeStudio 所在的服务账号(ServiceAccount)绑定了一个只读权限的 ClusterRole,让它能看到 DCU 节点上的扩展资源。

接下来最重要的一个步骤是让平台能识别“DCU 资源”和“CPU/内存”不同。绝大多数平台在展示节点资源时,默认只关心 CPU、内存和 GPU,如果插件的资源名是hygon.com/dcu,而平台的资源类型枚举里没有这个字段,UI 上是不会显示算力规格的。我们的做法是在平台配置里增加“自定义资源”映射,把hygon.com/dcuhygon.com/vdcu-exclusivehygon.com/vdcu-shared都注册成 GPU 类资源。

5.2 资源池、队列和 Quota 设计

平台层把算力暴露给用户前,一定要先划好资源池。我们的划分逻辑是按作业类型分队列:

  • 整卡队列(queue-dcu-whole),默认最大允许申请 8 卡,主要跑大模型预训练、微调任务。
  • vDCU 独占队列(queue-dcu-exclusive),资源配额为 8 个 8G vDCU,跑在线推理。
  • vDCU 共享队列(queue-dcu-shared),面向 Notebook 和短任务,总量很大但允许超卖。

Namespace 配额用来兜底。我们在每个队列对应的 Namespace 下都配置了 ResourceQuota:

apiVersion: v1 kind: ResourceQuota metadata: name: quota-dcu-exclusive namespace: ai-exclusive spec: hard: hygon.com/vdcu-exclusive: "8"

这样能防止算法同学在提交任务时一次性把整个队列的资源都申请走,其他任务永远排不上。

5.3 Notebook 任务和训练任务的提交方式

CubeStudio 通常自带 Notebook 模块,算法同学直接在页面上选择“资源规格:DCU 共享 8G”,平台后端会帮他们生成一个 Jupyter Pod。我们当时给的模板简化后大概长这样:

apiVersion: v1 kind: Pod metadata: name: notebook-abc namespace: ai-shared labels: app.kubernetes.io/component: notebook spec: containers: - name: jupyter image: registry.example.com/hygon/dcu-pytorch:2.1.0 resources: limits: hygon.com/vdcu-shared: 1 ...

训练任务的提交则复杂一些。如果是单机多卡任务,比如申请 4 张整卡,Kubernetes 默认调度器会尽量把 Pod 放到同一节点,但严格保证前四张卡都在同一节点通常还要配置节点亲和性或者在平台侧做调度约束。我们在 CubeStudio 里对多卡任务做了“张数匹配”,申请 4 张整卡的任务会寻找剩余整卡数大于等于 4 的节点,再通过 nodeAffinity 锁到具体节点上。

这里有一个经验:在平台资源足够充裕之前,少用“平台自动分配节点”的模式,尽量对整卡多卡任务做节点亲和性约束,否则常见问题是一个任务跨了两台节点,算法同学还跑来问为什么多卡通信这么慢。

6. 用 vLLM 在集群里跑 DeepSeek:从镜像到 API

6.1 选型确认:vLLM 的 ROCm 路线能吃下 DeepSeek 吗

集群 DCU 的调度和虚拟化都打通之后,我们第一个正式业务是部署 DeepSeek 系列推理服务。选型上没有太多悬念,直接用了 vLLM 的 ROCm 版本。

原因很简单:vLLM 有 OpenAI 兼容的 API,DeepSeek 系列模型在 HuggingFace 上有标准权重,vLLM 社区对 DeepSeek 的模型结构适配也比较及时。在 DCU 上跑 vLLM,本质是依赖海光 DCU 对 ROCm 生态的兼容性,所以镜像选择上优先看有没有rocm标签的 vLLM 镜像。

如果没有现成镜像,可以基于官方 vLLM 源码自行编译,编译时需要指定 HIP 平台。我们内部是直接从厂商提供的 ROCm 5.7 基础镜像上构建的。构建完成后,记得在镜像里加上HSA_OVERRIDE_GFX_VERSION这种和计算架构相关的环境变量,因为不同型号的 DCU 对应的 GFX 版本不同。关于这一点,建议你根据实际 DCU 型号向驱动厂商确认,不要盲目抄网上的值,设错会导致程序运行时报错或者性能极差。

6.2 DeepSeek 推理 Deployment 的完整 YAML

选定推理框架后,我把我们的 DeepSeek 推理服务 YAML 简化了一下。下面是核心部分,注意资源限制那一段,直接声明 DCU 资源:

apiVersion: apps/v1 kind: Deployment metadata: name: deepseek-vllm namespace: ai-exclusive spec: replicas: 1 selector: matchLabels: app: deepseek-vllm template: metadata: labels: app: deepseek-vllm spec: nodeSelector: accelerator/hygon-dcu: "true" containers: - name: vllm image: registry.example.com/vllm/vllm-openai:rocm-latest command: ["/bin/bash", "-c"] args: - >- python -m vllm.entrypoints.openai.api_server --model /models/deepseek-ai/DeepSeek-R1-Distill-Qwen-7B --served-model-name deepseek-r1-7b --tensor-parallel-size 1 --max-model-len 32768 --gpu-memory-utilization 0.85 --port 8000 ports: - containerPort: 8000 env: - name: HIP_VISIBLE_DEVICES value: "0" resources: limits: hygon.com/dcu: 1 readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 120 periodSeconds: 10

有几个细节值得说明。

HIP_VISIBLE_DEVICES在这里是控制容器内看到第几张卡,Device Plugin 整卡模式下一般会自动注入,但显式声明能避免某些镜像默认枚举设备时出错。

--max-model-len一定要根据显存来评估。我们第一次设成 65536,结果单卡 32GB 显存根本放不下,初始化直接 OOM;后来降到 32768 才稳定。字段太长背后的计算逻辑并不复杂,模型权重加 KV Cache 的总占用必须小于你申请到的显存,共享模式下尤其要谨慎。

.85gpu-memory-utilization是经验值,给运行时和碎片留一点余量,整卡跑可以到 0.9,vDCU 独占 8G 的小规格任务我建议降到 0.8。

6.3 服务暴露和 API 验证

Deployment 创建后,再创建一个 Service,让平台内部其他服务都能访问:

apiVersion: v1 kind: Service metadata: name: deepseek-vllm-svc namespace: ai-exclusive spec: selector: app: deepseek-vllm ports: - port: 8000 targetPort: 8000 type: ClusterIP

验证时直接用 curl 调 OpenAI 兼容接口:

curl -X POST http://deepseek-vllm-svc.ai-exclusive:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-r1-7b", "messages": [{"role": "user", "content": "你好,做一句自我介绍"}], "temperature": 0.7 }'

如果能正常返回文本,说明 DCU 上的推理链路已经彻底跑通。这个服务地址还可以直接配到 vscode 的 Continue 插件、Codex 等工具的 OpenAI 兼容 Base URL 里,等于给团队提供了一个内网可用的 DeepSeek API。

6.4 共享模式和 vDCU 场景下的稳定性问题

最后再说一个我们实际遇到的问题。最初为了让更多人同时试用 DeepSeek,我把一部分推理实例放到了共享模式 vDCU 上。白天低峰期一切正常,等跑批任务一上来,分担到同一张物理卡上的多个推理请求延迟直接翻了几倍,甚至出现客户端超时。

排查后发现是共享模式下算力竞争导致的。vDCU 共享模式适合短任务,但不适合长时间占用的服务。我们把所有在线推理服务迁到整卡和独占 vDCU 后,问题就消失了。这也验证了我前面的观点:在线服务和离线批量任务,最好从一开始就在队列层面隔离。

给做同样事情的同学一个建议,在平台设置里加上“服务类型:在线/离线”的标记,在线服务默认只能选整卡或 vDCU 独占规格,离线作业默认走 vDCU 共享规格。这个规则比任何事后人工干预都有效。

7. 整个适配过程里最值得记住的几个经验

整个项目做下来,我最大的体会有三点。

第一,DCU 接入 Kubernetes 本身并不复杂,复杂的是版本环境。驱动、ROCm runtime、Device Plugin、PyTorch/vLLM 镜像,任何一个版本不匹配都会浪费你好几天时间。所以每次变更前,先记录四张表:节点系统版本表、驱动版本表、插件版本表、镜像基础版本表。靠记录而不是靠记忆。

第二,扩展资源只是“可用性”的入口,不是“合理性”的保证。Kubernetes 原生的调度器不会感知显存碎片、不会感知卡间拓扑、不会感知 vDCU 竞争,这些都需要你在平台侧或调度策略上补足。如果你只做简单的 Demo,靠原生调度器就够了;如果面对几十上百个任务,最好还是通过 CubeStudio 这类平台把资源池、队列、Quota 管起来。

第三,不要在共享模式上跑在线服务。这个坑我说过很多次,但每次都有同事想挑战一下,最后都灰头土脸改回独占模式。不是说共享模式没用,而是它更适合“容错度高”的任务。明确这条边界,你的集群稳定性会直接上一个台阶。

最后再分享一个小技巧:每次改完 Device Plugin 或者 vDCU 配置,不要直接大批量提交任务,先起一个sleep 600的测试 Pod 占住资源,然后手动跑一遍rocm-smi确认显存和算力符合预期,再删掉。这一步虽然简单,但能帮你过滤掉至少一半的“任务跑到一半 OOM”问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 4:12:52

国产操作系统落地实践:从选型到部署避坑指南

国产操作系统这几个字,在IT圈已经喊了很多年,但直到最近两年我才真正觉得它到了“能干活”的阶段。身边不少朋友一听到这个词,第一反应是一个抽象的概念,等打开虚拟机装上统信UOS或者银河麒麟,才发现它其实是一套基于L…

作者头像 李华
网站建设 2026/9/18 4:12:31

TB67S531FTG+STM32F723ZE工业级步进电机控制方案实战解析

1. 为什么选 TB67S531FTG 搭配 STM32F723ZE先说结论:这套组合是工业级步进控制里“性价比”和“可靠性”平衡得相当好的方案。TB67S531FTG 是东芝近年力推的两相双极步进驱动芯片,内部集成了 PWM 斩波恒流控制、电流检测放大器和多种衰减模式&#xff1b…

作者头像 李华
网站建设 2026/9/18 4:11:25

STM32固件烧录实测:SWD比UART快6.8倍,选型与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 4:11:03

Agent-Reach:打造智能体触达层,让Agent真正够得着业务系统

做AI应用落地有一段时间了,我越来越觉得——大部分号称智能的Agent,其实只是"嘴上智能"。你问它什么它都能答,但真要让它去查个订单、改个配置、调个接口,它就卡住了。问题往往不在大模型本身,而在Agent根本…

作者头像 李华
网站建设 2026/9/18 4:10:19

Dedekind切割:用有理数缝隙构造实数的静态方法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华