海光DCU接入Kubernetes与CubeStudio:从Device Plugin到vDCU虚拟化实战
手里有几台装了海光 DCU 的服务器第一反应是什么大概率是装个驱动、搭一个 device pluginKubernetes 里就能像用 NVIDIA GPU 一样直接申请卡资源了。实际做下来你会发现事情远没这么简单。DCU 的软件栈、设备命名、容器注入方式都和 CUDA 生态那套不一样更别提还要在 AI 平台上支持整卡、共享、vDCU 虚拟化再把 DeepSeek 这类大模型推理跑起来。这篇文章是我最近把海光 DCU 接入 Kubernetes 和 CubeStudio AI 平台的完整复盘从驱动、CDI 到 device plugin从整卡、共享、两种 vDCU 虚拟化到 DeepSeek 部署链路一条条拆开讲。手里正好有 DCU 资源、想上 K8s 做 AI 推理的同学可以照着这条链路逐层验证能少踩不少坑。1. 为什么海光 DCU 进 K8s 比想象中麻烦先看懂设备和驱动的脾气1.1 海光 DCU 和 NVIDIA GPU 在调度层的本质差异如果你之前的 GPU 上云经验全部来自 NVIDIA那接入海光 DCU 的第一课就是忘掉装上驱动就能跑这个惯性。NVIDIA 生态里nvidia-container-toolkit 负责把 GPU 暴露给容器nvidia-device-plugin 负责把 GPU 注册给 K8s调度器直接认nvidia.com/gpu这个扩展资源一套链路全球社区都在用踩坑资料多到看不完。海光 DCU 这边完全是另一套玩法。它底层是类 CDNA 架构软件栈基于 ROCm 生态做了自己的分支也就是 DTKDCU Toolkit。这意味着两件事第一你在 NVIDIA 上熟用的不少工具链在这里要换一套第二K8s 原生调度器根本不认识 DCU一切都得通过扩展资源注册。资源名、设备节点、运行库、调度方式每个环节都有自己的脾气。最直观的变化在工具链。显卡用nvidia-smiDCU 用dcu-smiNVIDIA 用 nvidia-container-runtime 做运行时注入DCU 这边要么走 OCI hook 要么走 CDIContainer Device InterfaceK8s 里的资源名通常写成hygon.com/dcu。听起来只是换个名字但实际排查问题的时候很多异常报错你搜不到现成答案只能顺着链路一层层翻日志。这也是我写这篇文章的初衷把链路讲清楚让你少走弯路。1.2 接入 K8s 的完整链路从 DTK 驱动到 device plugin我习惯把 DCU 进 K8s 的链路拆成六段每一段都能单独验证物理层板卡插在服务器上lspci 或系统日志能识别到设备驱动层DTK 安装后加载内核模块生成 /dev/dri/renderD* 或 /dev/kfd 这类设备节点运行库层DTK 的 HIP 运行时、配套库要装齐版本和驱动严格匹配容器注入层containerd 开启 CDI把设备节点和运行库挂载进容器资源注册层device plugin DaemonSet 把每张卡注册成 K8s 扩展资源调度与应用层Pod 声明hygon.com/dcu调度器分配后容器内通过环境变量拿到设备。这六段里最容易出问题的其实是第 4 段和第 5 段的衔接。CDI 文件明明生成了kubectl describe node也能看到资源数量但容器里就是找不到设备。这种问题往往要同时看 containerd 日志、device plugin 日志和 kubelet 日志才能定位。后面的内容就是围绕这条链路展开的每一段我都会给你一个可操作的验证方法。2. 环境准备驱动、容器运行时和 CDI一步都不能省2.1 DTK 驱动装好之后怎么确认容器能看到设备驱动安装有个原则版本必须跟板卡型号匹配同一批机器尽量锁同一个 DTK 版本不要出现 A 机器 4.0、B 机器 5.1 这种混装情况否则后面排查问题的时候你会怀疑人生。装完驱动后先在宿主机上跑dcu-smi确认能看到卡的数量、显存总量、驱动版本和利用率。这一步不过后面全白搭。接下来看设备节点ls /dev/dri/应该能看到 renderD128 之类的节点部分环境还需要/dev/kfd。如果设备节点不存在多半是内核模块没加载手动modprobe相应模块并且配置好开机自加载否则节点重启后设备又丢了。这一步我强烈建议写进你的运维手册它是后面 5.1 节那个坑的根源之一。容器运行时这边我用的是 containerd走 CDI 方案而不是老的 --device 硬映射。CDI 的好处是规范统一、可声明式管理设备节点、库文件、环境变量全写在一份 YAML 里Pod 声明哪个设备就用哪个。具体操作分三步先确认 containerd 版本支持 CDI 开关在 /etc/containerd/config.toml 里打开 enable_cdi 相关配置再用 DTK 自带的 CDI 生成工具生成规范文件到 /etc/cdi/ 目录这个工具不同版本叫法略有差异注意看 DTK 的 release notes最后花十分钟把生成的 YAML 读一遍。里面会列出每张卡的设备节点、需要挂载的库、要注入的环境变量后面容器里缺东西的时候你能直接从这份文件定位而不是瞎猜。2.2 CDI 配置生成与 K8s 设备注册CDI 文件就绪后部署 DCU 的 device plugin DaemonSet。这里要注意插件的资源命名要和 CDI spec 里一致别一个写hygon.com/dcu一个写hygon.com/gpu这种低级错误我浪费过半天。部署完执行kubectl describe node node-name | grep hygon看到hygon.com/dcu的数量和实际卡数一致说明资源注册成功。但我还是要强调资源注册成功不代表容器能用。我习惯立刻跑一个最小验证 Pod容器里执行hipInfo或者直接跑一轮 torch 看torch.cuda.is_available()是否返回 true。这一步能在一分钟内暴露驱动、CDI、运行时三者之间的隐性问题。apiVersion: v1 kind: Pod metadata: name: dcu-test spec: containers: - name: hip-test image: harbor.example.com/dcu/hip-test:latest command: [hipInfo] resources: limits: hygon.com/dcu: 1 restartPolicy: Never下面是几个 DCU 集群里高频出现的报错按现象 - 原因 - 排查点整理出来了现象可能原因排查点describe node 看不到 DCU 资源device plugin 没起来或资源名不一致看插件日志、核对资源名Pod 一直 Pending节点没有可用 DCU 资源或 taint 未容忍kubectl describe pod 看调度事件容器内无设备节点CDI 文件缺失、containerd 未开 CDIcat /etc/cdi/*.yaml、查 containerd 日志hipInfo 能跑但 torch 报错DTK 版本和 torch/dcu wheel 不匹配统一基础镜像和依赖版本3. CubeStudio 的 DCU 资源模型整卡、共享、两种 vDCU 怎么选3.1 整卡直通简单可靠但资源浪费很明显先把最简单的情况说清楚。CubeStudio 里选整卡平台底层就是 Pod 声明hygon.com/dcu: NN 从 1 到节点卡数上限。这个模式的好处是性能无损耗、实现简单、调度不挑节点非常适合训练任务或者对延迟极其敏感的在线服务。问题在于浪费。一张 32GB 显存的卡跑 7B 蒸馏模型权重加上 KV cache可能只用十几 GB算力更是不一定吃满。如果集群同时跑着大量中小模型推理任务整卡模式会让整体资源利用率非常难看。我见过不少团队一开始全用整卡结果卡利用率长期在 10%-20%然后被要求必须把利用率提上去——这就是上共享和 vDCU 的直接动力。所以我的建议是不要一上来就追求复杂的虚拟化先用整卡把链路跑通采集真实 workload 数据看看哪些任务浪费明显再决定要不要切模式。3.2 共享模式的两条技术路线算力切分 vs 时间片共享海光 DCU 的虚拟化我实际用下来是两个方向刚好对应标题里的两种 vDCU。第一种是算力切分型 vDCU。它把一张 DCU 的计算单元和显存切成若干个独立分区每个分区拥有固定的算力和显存配额类似物理切片。任务之间互不干扰一个分区崩溃或 OOM不会影响同卡上其他分区。这种模式适合多租户场景不同部门、不同项目的任务可以安全地共享一张卡资源边界清清楚楚。缺点是切分粒度有限单卡能切出的 vDCU 数量不多空着的分区也不能灵活借给邻居。第二种是时间片共享型 vDCU。多个 vDCU 共享整卡计算单元底层由调度器按时间片切换显存做配额隔离。这种模式的优势是单卡能承载的虚设备数量多适合大量并发小请求的推理场景比如线上问答、Agent 应用、代码助手。几十路请求挤在同一张卡上整体吞吐明显上升。缺点也明显性能隔离弱如果同卡上有个大任务把算力吃满其他 vDCU 的响应延迟会肉眼可见地抖动。3.3 两种 vDCU 的对比与选型建议把三种模式放在一起对比逻辑会更清楚维度整卡直通算力切分型 vDCU时间片共享型 vDCU隔离强度物理独立接近物理独立算力共享、显存隔离单卡虚设备数量12-4 左右8-16 甚至更多性能稳定性最稳定稳定高并发时波动明显典型场景训练、大模型多租户、混合负载高频小模型推理对调度器要求低较高较高选型建议就三条。训练和对延迟零容忍的任务直接整卡不要犹豫。多部门共用集群、有明确资源边界要求的用算力切分型 vDCU隔离性好账也算得清楚。纯在线推理、请求量大但单请求算力需求不高的用时间片共享型 vDCU把吞吐拉起来。CubeStudio 这类 AI 平台的价值在于它把这些模式封装成了用户可见的资源规格底层帮你处理 device plugin、调度策略、显存配额、超卖控制否则在裸 K8s 上这些都得自己写 scheduler extender工作量不小。4. DeepSeek 部署实操DCU 上跑大模型推理的真实流程4.1 模型选型从蒸馏版到完整版聊到 DeepSeek 部署先泼盆冷水DeepSeek-V3 / R1 完整版是 671B 的 MoE 模型FP8 量化后权重也在 700GB 这个量级算上 KV cache 和推理开销普通个位数 DCU 的配置根本转不动。完整版不是不能跑而是对集群规模要求高至少要凑齐十几张到二十几张 32GB 显存卡才谈得上实践。对绝大多数团队真正能落地、性价比最高的是蒸馏系列DeepSeek-R1-Distill-Qwen-7B / 14B / 32B / 70B。显存估算看下面这个表模型权重精度权重大小推荐 DCU 数量单卡 32GBR1-Distill-Qwen-7BBF16~14GB1R1-Distill-Qwen-14BBF16~28GB1-2R1-Distill-Qwen-32BBF16~64GB2-4R1-Distill-Qwen-70BBF16 / INT8~140GB / ~70GB4-6DeepSeek V3 / R1 完整版FP8~700GB16-32权重建议通过 ModelScope 拉取下载到共享存储NFS、CephFS 或对象存储挂载再挂载进推理容器。这里有个实操细节模型目录按版本命名比如 /models/DeepSeek-R1-Distill-Qwen-14B-v2下次切版本只需要改 Deployment 里的模型路径不用重新拉权重几十 GB 的传输时间能省则省。4.2 vLLM 推理服务在 K8s 里的部署清单推理框架我用 vLLM 的 DTK/ROCm 适配版本理由很直接社区活跃、OpenAI 兼容接口、后续接 Agent 工具方便。基础镜像直接基于海光的 DTK 镜像构建把 vLLM 和依赖装进去模型权重通过 PVC 挂载不打进镜像这样换模型版本不用重新构建镜像。下面这份 Deployment 是我这边实测跑通 14B 蒸馏版的模板直接抄基本能跑apiVersion: apps/v1 kind: Deployment metadata: name: deepseek-r1-distill-14b namespace: ai spec: replicas: 1 selector: matchLabels: app: deepseek-r1-distill template: metadata: labels: app: deepseek-r1-distill spec: nodeSelector: dcu.phy/node-type: dcu-gpu containers: - name: vllm image: harbor.example.com/dcu/vllm-dtk:v0.6.6 imagePullPolicy: IfNotPresent command: [vllm] args: - serve - /models/DeepSeek-R1-Distill-Qwen-14B - --tensor-parallel-size - 2 - --dtype - bfloat16 - --gpu-memory-utilization - 0.9 - --max-model-len - 8192 - --served-model-name - deepseek-r1 resources: requests: hygon.com/dcu: 2 limits: hygon.com/dcu: 2 volumeMounts: - name: model mountPath: /models volumes: - name: model persistentVolumeClaim: claimName: deepseek-models有几个参数值得专门说清楚为什么这么配。tensor-parallel-size必须和申请的 DCU 数量一致14B 用 2 卡就写 2写错了要么模型起不来要么部分卡空闲gpu-memory-utilization我一般写 0.9不拉满给显存碎片留缓冲否则高并发时容易 OOMmax-model-len直接决定 KV cache 大小业务只需要 8K 上下文就别写 32768白白吃掉大量显存。nodeSelector先用简单粗暴的节点标签确保任务落在装了 DCU 的节点上更复杂的亲和性场景放到第 5 节单独讲。device plugin 会自动注入HIP_VISIBLE_DEVICES这类环境变量并把物理设备映射进容器所以这里不需要手动指定设备。启动后创建一个简单 Service 把 8000 端口暴露出来集群内部就能用服务名访问apiVersion: v1 kind: Service metadata: name: deepseek-r1-svc namespace: ai spec: selector: app: deepseek-r1-distill ports: - port: 8000 targetPort: 80004.3 上线后的验证、压测与参数微调服务起来后先做功能验证。vLLM 暴露 OpenAI 兼容接口直接 curl/v1/chat/completionscurl http://deepseek-r1-svc.ai.svc.cluster.local:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: deepseek-r1, messages: [{role: user, content: 用一句话解释什么是海光 DCU}], max_tokens: 256 }拿到正常返回后再上压测工具。我主要盯三个指标TTFT首 token 延迟、生成吞吐tokens/s、并发下的 TPOT每个 token 生成间隔。如果用的是时间片共享型 vDCU务必测一下并发从 1 涨到 32、64 时延迟的变化曲线。曲线过早拐头说明这张卡上的 vDCU 开多了超卖倍数要降曲线很平说明还有余量可以把单卡虚设备数调上去。调优优先级一般是先动max-model-len和gpu-memory-utilization这两项对显存影响最大其次再考虑 FP8/INT8 量化。量化对 DCU 的加速效果经常比预期好显存占用直接减半对中小显存卡非常友好。5. 实测中踩过的坑设备丢失、OOM、调度错乱5.1 节点重启后 DCU 失联的完整排查链路这个坑几乎每个 DCU 集群都会遇到。节点维护重启后Pod 调度上去容器里却看不到 DCU但 kubectl 里节点明明显示 Ready。遇到这种情况别急着怀疑 CDI按从底向上的顺序逐层排查宿主机执行dcu-smi如果这里就看不到卡问题在硬件或内核驱动层ls /dev/dri/检查设备节点是否生成没生成说明内核模块没加载确认 DTK 的开机自启动服务和内核模块自加载配置是否生效看 device plugin 日志和 kubelet 日志确认设备重新注册是否完成看 Pod 内容器的设备节点和 CDI 注入是否正常。我遇到最多的是第 2 步和第 4 步模块没自动加载以及 device plugin 重启后没跟 kubelet 完成重新握手。解决思路分两部分一部分是在机器上配好内核模块自加载另一部分是确保 device plugin 有合理的重启策略和 liveness 探针必要时手动删除 Pod 让它重建通常就能完成重新注册。这个问题容易反反复复建议直接在部署里加 initContainer 或健康检查等设备就绪后再启动推理主容器。5.2 共享模式下显存超卖引发的 OOM第二个坑在共享和 vDCU 模式下特别容易踩。vDCU 的显存配额只是一个上限承诺如果平台允许超卖你会看到 Pod 一直是 Running但模型加载到一半就报显存不足更迷惑的是有些时候不是设备层报错而是容器被 cgroup 的 memory limit 杀掉了。原因在于显存配额和 Pod 的 memory limit 是两套独立的资源体系vLLM 申请显存的瞬间如果突破 cgroup 内存上限就会被直接 OOM Kill。解决思路是让两套体系联动起来。用 CubeStudio 这类平台时要确认平台是否支持显存配额到容器 memory limit 的自动换算不支持的话宁可自己手动给 Pod 配一个匹配的 memory limit。推理参数上把gpu-memory-utilization从 0.95 降到 0.85-0.9给显存碎片留出缓冲。上线前务必做一轮真实并发压测观察显存曲线能不能稳住而不是只看模型能加载就宣布部署成功。集群里超卖倍数开多大唯一依据就是这轮压测数据。5.3 多卡多节点调度时的亲和性调优第三个坑和调度策略有关。申请 2 卡或 4 卡跑 tensor parallel 时如果调度器没有亲和性约束两张卡很可能被分配到两台不同机器上。模型照样能起来但性能会断崖式下跌因为卡间通信从机内高速链路掉落到网络或 PCIe 交换延迟和带宽完全不是一个量级。我做过的对比测试里4 卡任务同节点和跨节点的推理吞吐差距接近一倍这不是小数字。如果你用 CubeStudio通常在资源规格里能找到单节点聚合这类开关开上就行。裸 K8s 就用 nodeSelector 或 topologySpreadConstraints 控制核心思想是让同一个张量并行任务的 N 张卡尽量落在同一节点。另外多机互联的网络选型也很关键跨节点场景下瓶颈常常不在 DCU 本身而在互联链路。大规模集群有条件尽量走高速互联方案别用千兆网卡跑分布式推理那体验会让你想摔键盘。5.4 给后来者的几条实在建议最后分享几条亲身攒下来的经验。第一初期不要过度设计先把整卡链路跑通用真实数据说话再决定是否上 vDCU避免平台没搭好反而被虚拟化本身的问题拖住。第二DCU 生态的公开踩坑资料远没有 NVIDIA 丰富遇到问题一定要学会看dcu-smi、dmesg、containerd 和 device plugin 日志这四个信息来源能解决绝大部分问题。第三每次升级 DTK 或切换 vLLM 版本时先在测试节点完整跑一遍模型部署再扩散到全集群这类跨版本的兼容问题最容易浪费大半天时间。我在实际部署中还发现一个容易被忽略的细节DCU 的显存和计算单元规划一定要一起考虑。有人只看显存够不够结果某个 vDCU 显存明明有余量算力却早就被其他任务占满了延迟照样高。所以资源申请不要只看装得下模型还要留点算力余量否则线上体验会很差。海光 DCU 这套体系确实不如 CUDA 生态省心但链路理清楚之后整卡、共享、vDCU 加 DeepSeek 部署这套组合拳是能在一个迭代周期内稳定跑起来的。