上个月帮一家单位做AI平台规划的时候,我发现一个特别典型的现象:机房里躺着一批还没拆封的海光DCU服务器,采购的时候大家对标的是A100,可真到算法团队上手那一步,K8s完全不认这张卡。作业调度上去直接CrashLoopBackOff,日志翻来翻去就是找不到设备,更别提用DeepSeek这类大模型做推理了。
很多人以为国产加速卡接入Kubernetes就是装个驱动的事,实际走下来你会发现,从设备识别、资源上报、调度策略到CubeStudio这类AI平台的配额管理,是一条完整的技术链路。这篇文章我就把海光DCU接K8s、接平台的实操过程完整拆开,覆盖整卡独占、共享模式、两种vDCU虚拟化方案,以及在DCU上部署DeepSeek推理服务的完整流程。内容偏实操,所有命令和配置都以我这次实际跑通为准,版本差异会单独说明。
1. 先弄懂 DCU 接入 K8s 的完整链路,再动手
1.1 从“卡在服务器里”到“任务跑在资源池”的三层结构
海光DCU接入K8s,表面看是装驱动、部署几个组件的事,但它涉及三个完全不同的层次。
第一层是物理设备层。DCU通过PCIe插在服务器上,操作系统里能看到设备节点,通过dcu-smi能看到卡的健康状态、显存占用、算力使用率。这一层解决的是“驱动能不能识别卡”的问题。
第二层是集群资源层。K8s本身并不知道什么叫“GPU”或“DCU”,它只认识CPU、内存这种原生资源。要让调度器能够感知DCU、并按需分配给Pod,必须借助Device Plugin机制。Device Plugin是由K8s提供的一套标准扩展接口,设备厂商实现这个接口,就能把自己的硬件抽象成一种“扩展资源”(Extended Resource),比如hygon.com/dcu。调度器看到这个资源名,就会把它当成一种可计数的整数资源来调度。
第三层是平台应用层。K8s只管资源分发,但算法工程师并不想面对YAML和命令行。CubeStudio这类AI平台会封装一层,把资源池、配额、镜像、Notebook、训练作业这些概念做成可视化界面,用户点几下就能申请到一张卡或一块vDCU。
这三层必须打通,任何一层掉链子,最终表现都是“任务跑不起来”。我见过不少团队卡在第二层和第三层的衔接上:底层Device Plugin起来了,资源也上报了,但平台的资源配额模型不认这个资源名,用户界面里依然看不到DCU。
1.2 整卡、共享、vDCU 三种模式到底在调度什么
接入方式的选择,直接决定了资源池的利用率和隔离效果。我这次实际验证了三种调度方式:整卡独占、共享型vDCU、切分型vDCU。很多人第一次接触这个概念会有点绕,我用大白话拆一下。
整卡独占最简单,一张物理DCU同一时刻只分配给一个Pod。这种模式没有性能干扰问题,排查故障也容易,但问题在于浪费。如果一个小模型的推理服务只需要4GB显存,你却给它一整张32GB的卡,剩下28GB就闲置了。很多单位的DCU利用率上不去,根因就在这里。
共享型vDCU解决的是浪费问题。一张物理DCU可以虚拟出多个vDCU实例,多个Pod共用同一张卡的计算单元。这种共享通常是基于时间片或权重调度的——类似操作系统里的多进程轮流用CPU。优点是利用率高,缺点是无法做到强隔离,一个跑满算力的任务会把同卡的其他任务拖慢。
切分型vDCU则走另一个方向:把物理DCU的计算单元和显存切成多个互不干扰的分区,每个分区有独立的算力配额和显存配额,硬件层面隔离。这种方式类似把一整层写字楼隔成独立的小办公室,每个租户有自己的门锁,互不侵犯。
三种模式的对比我放在后面章节详细讲,这里先建立一个整体认知:整卡是基础兜底方案,共享型vDCU适合推理和低频任务,切分型vDCU适合多租户场景和训练任务。
2. 环境准备:驱动、DTK 容器镜像与集群前置条件
2.1 宿主机驱动与 DTK 版本对齐
海光DCU的软件栈核心是DTK,也就是DCU Toolkit。它提供类似CUDA的工具链、运行时库和HIP编程接口。在接入K8s之前,宿主机上的驱动和DTK版本必须对齐,否则后面容器里跑程序会出现各种匪夷所思的报错。
我的建议是先看官方版本的兼容矩阵,确认三件事:内核版本的兼容范围、容器运行时版本、以及你要跑的AI框架(比如PyTorch)所需的DTK版本。这个步骤不要跳。我这次吃过亏,宿主机驱动版本较新,但某一个AI框架镜像要求的DTK版本较旧,结果算出来的结果都是错的。
驱动安装完成之后,用dcu-smi验证设备状态,能看到每张卡的型号、驱动版本、温度、显存总量就基本OK。
$ dcu-smi +-----------------------------------------------------------------------+ | DCU Summary: | +-----------------------------------------------------------------------+ | 0 Hygon Z100 Driver Version: 6.2 Memory: 32768MiB | | 1 Hygon Z100 Driver Version: 6.2 Memory: 32768MiB | +-----------------------------------------------------------------------+2.2 容器镜像体系:跑 DCU 任务到底该用哪层镜像
这部分是新手最容易晕的地方。K8s里运行DCU任务,容器里必须包含两样东西:DTK/HIP的运行时库,以及你需要的AI框架(PyTorch、vLLM等)。
有两个常见做法。第一,直接用海光官方提供的DTK基础镜像,里面预装了HIP运行时和相关库,然后在上面叠加PyTorch等框架。第二,使用ROCm系的镜像改造,因为DCU的编程模型和ROCm生态是对齐的,很多ROCm镜像经过调整可以直接用。但我不建议自己在基础Ubuntu镜像里手动装DTK库,依赖关系太复杂,踩坑成本很高。
更稳妥的方式是提前在一个基准节点上测试镜像,确认容器内能识别DCU设备。判断标准很简单——在容器里执行dcu-smi或rocm-smi能正常输出。下面这个验证是我每次都会做的:
$ docker run --rm --device=/dev/dri --group-add video \ registry.example.com/dtk:6.2-py3.10-torch2.1 \ bash -c "dcu-smi -L"这里有两个细节。第一,需要把设备节点映射进容器,如果用了Device Plugin或特定的CRI运行时,这个映射是自动完成的,手动docker run时就要用--device参数指定。第二,用户需要加入video组才能访问GPU设备节点,在K8s里通常通过configmap或runtimeclass配置。
2.3 集群侧前置条件:节点标签与调度标识
集群侧的准备相对简单,但有一个容易被忽略的点:建议给所有DCU节点打上统一的标签,比如gpu=hygon-dcu。这样在调度时可以通过nodeSelector精确控制哪些负载可以跑在DCU节点上,避免普通CPU任务占用了这些珍贵的机器。
$ kubectl label node dcu-node-01 gpu=hygon-dcu $ kubectl label node dcu-node-02 gpu=hygon-dcu另外一个需要提前确认的点是:集群里的资源配额和LimitRange会不会影响到DCU设备的申请。有些平台会为namespace设置默认的资源限制,如果默认了CPU和内存上限,但DCU设备不算在配额里,就可能出现用户申请了4张卡,却因为CPU配额超限而调度失败,排查起来非常隐蔽。
3. 整卡模式接入实操:Device Plugin 与 Extended Resource 逐步落地
3.1 部署 DCU Device Plugin:把物理卡变成资源数字
整卡接入的核心工作就是部署Device Plugin。Device Plugin是K8s官方提供的设备管理机制,它运行在每个节点上,由Kubelet调用,负责向API Server上报节点上有多少张DCU卡,以及响应Pod调度时的设备分配请求。
海光官方提供了Device Plugin的部署文件,通常是一个DaemonSet。部署之前要确认kubelet的--feature-gates=DevicePlugins=true已经开启。新版K8s默认开启,但如果集群是从老版本升级上来的,这个开关可能还关着,会导致插件注册失败。
部署文件的核心部分大致长这样,实际使用时以官方release为准:
apiVersion: apps/v1 kind: DaemonSet metadata: name: dcu-device-plugin namespace: kube-system spec: selector: matchLabels: name: dcu-device-plugin template: metadata: labels: name: dcu-device-plugin spec: hostNetwork: true containers: - name: dcu-device-plugin image: registry.example.com/hygon/dcu-device-plugin:latest imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false capabilities: drop: ["ALL"] volumeMounts: - name: device-plugin mountPath: /var/lib/kubelet/device-plugins - name: sys mountPath: /sys volumes: - name: device-plugin hostPath: path: /var/lib/kubelet/device-plugins - name: sys hostPath: path: /sys部署完之后,查看Pod状态和日志,确认没有报权限错误或注册失败。
$ kubectl -n kube-system get pods -l name=dcu-device-plugin $ kubectl -n kube-system logs dcu-device-plugin-xxxxx3.2 验证资源上报:从 node 状态看卡片
Device Plugin正常注册后,K8s会以扩展资源的形式在节点状态里展示DCU数量。用kubectl describe node就能看到:
$ kubectl describe node dcu-node-01 | grep -A5 "Capacity" Capacity: cpu: 96 memory: 754929384Ki hygon.com/dcu: 8 Allocatable: hygon.com/dcu: 8看到hygon.com/dcu: 8就说明整卡资源上报成功。这里的资源名并不是标准规定死的,具体的名字取决于Device Plugin的实现,可能是hygon.com/dcu、dcu.hygon.com/gpu或其他写法。在平台对接时,这个资源名就是唯一的ID,后面配置配额和模板都会用到它。
这一步有个常见坑:节点状态里看不到资源。绝大多数情况是Device Plugin与kubelet的socket通信出了问题,或者插件在注册时上报的资源名重复。可以先看Device Plugin的日志,再去节点上检查socket文件是否存在。不要一上来就重启kubelet,重启kubelet虽然经常能解决,但会影响节点上所有Pod,动静太大。
3.3 用整卡跑第一个 Pod:验证设备映射链路
资源上报成功以后,写一个最简单的Pod验证完整链路。注意,Pod里必须显式申请DCU资源,调度器才会把Pod分配到有DCU的节点,并触发Device Plugin分配设备。
apiVersion: v1 kind: Pod metadata: name: dcu-test spec: nodeSelector: gpu: hygon-dcu containers: - name: dcu-test image: registry.example.com/dtk:6.2-py3.10-torch2.1 command: ["/bin/bash", "-c"] args: - dcu-smi -L && python -c "import torch; print(torch.cuda.device_count())" resources: limits: hygon.com/dcu: "1"如果设备映射链路正常,日志里会打印出DCU型号,并且PyTorch识别到的设备数为1。这里要注意一点,在容器里PyTorch是否识别DCU,取决于你安装的PyTorch是否带DCU支持。海光社区和官方都有专门适配过的PyTorch发行版,直接用官方镜像最省事。
整卡模式本身没有太多技术含量,但它是一切复杂方案的地基。如果整卡模式都不稳定,后面所有虚拟化和共享的方案都没有意义。所以我会建议,任何单位在尝试vDCU之前,先把整卡模式完整跑通,包括在多个节点上同时运行Pod、扩容缩容、节点重启后的自动恢复等。
4. vDCU 两种虚拟化模式:共享切分的原理、配置与实战选择
4.1 vDCU 到底解决了整卡模式的什么痛点
整卡模式的浪费问题非常突出。我之前在测试环境做过统计,一个中型团队跑深度学习训练,单卡的利用率平均不到30%,推理服务更低。大多数任务是短时、低频、小显存占用,却占着整卡无法释放。
vDCU的价值就是把这个资源粒度做细。但“虚拟化”这个词听上去很美好,落实到调度器上就面临一个尖锐的矛盾:K8s扩展资源的调度单位是整数,不能调度半张卡。要让多张vDCU共享一张物理卡,必须引入自定义调度逻辑——这正是两种vDCU虚拟化模式分岔的地方。
4.2 模式 A:算力共享型 vDCU(时间片/权重复用)
第一种vDCU模式走的是“算力共享”路线。多个vDCU实例映射到同一张物理DCU上,共享计算单元和显存,底层驱动按时间片或权重进行调度。你可以把它理解为同一台电脑上多个虚拟机共享CPU——每个vDCU看到的是一张完整的卡,但实际计算资源是按配额轮转的。
这种模式的配置通常在Device Plugin或调度器扩展里完成。通常会有一个CRD定义vDCU模板,大意如下:
apiVersion: dcu.hygon.com/v1 kind: VirtualDCU metadata: name: vdcu-shared-small spec: type: shared physicalSelector: matchLabels: gpu: hygon-dcu schedulingPolicy: policy: weight weight: 20在用这个vDCU模板创建Pod时,资源申请变成hygon.com/vdcu: 1。调度器会检查目标节点上有没有物理DCU、当前共享比例是否饱和,如果超卖阈值不允许,就排队等待。
优点很明显:实现相对简单,一张卡可以被无限多个轻量任务复用(当然有上限),适合大量小推理任务、开发调试、批量评测这类负载。缺点也很致命:无法隔离故障和性能。当一个任务把算力跑满,同卡的其他任务时延会显著上升,甚至出现“一颗老鼠屎坏了一锅汤”的局面。
我实际测试下来,共享型vDCU适合内部研发环境,不建议在生产级对外服务上用。如果你要为多个客户提供服务,算力干扰问题会让你难以承诺SLA。
4.3 模式 B:资源切分型 vDCU(硬件级隔离分区)
第二种vDCU模式走的是“空间切分”路线。把物理DCU的计算单元和显存切分成多个独立分区,每个vDCU独占一个分区,硬件层面隔离。这种模式对应的概念大家更熟悉,NVIDIA的MIG、A100的多实例GPU就是这种思路。
切分型vDCU的配置逻辑和共享型完全不一样。它需要指定显存大小、算力比例,并且物理卡在切分前可能处于某种“整卡模式”,切分后需要重置设备才能生效。配置示例大致长这样:
apiVersion: dcu.hygon.com/v1 kind: VirtualDCU metadata: name: vdcu-slice-16g spec: type: sliced memory: 16GiB compute: 50这里compute: 50表示这个分区占用物理卡50%的计算资源,memory: 16GiB表示独立分配16GB显存。当你用到两张slice vDCU,物理卡上就同时存在两个独立分区,互不可见。
切分模式的优点非常突出:强隔离。一个分区跑满算力甚至程序崩溃,不会影响其他分区;显存配额是硬限制,不会出现某个任务把显存吃光导致同卡其他任务OOM的情况。缺点是灵活性差:切分粒度受限,不能像共享模式那样任意超卖,而且切分后的物理卡如果跑大模型,单分区显存可能不够用。
4.4 两种 vDCU 模式的选择框架
我整理了一张对比表,方便你做技术选型参考:
| 维度 | 共享型 vDCU | 切分型 vDCU |
|---|---|---|
| 隔离级别 | 逻辑隔离,算力共享 | 物理隔离,算力/显存独立 |
| 显存视图 | 所有实例共享整卡显存 | 每个实例独享固定显存 |
| 算力保证 | 权重抢占,不保证稳定时延 | 稳定,但上限固定 |
| 超卖能力 | 支持,可大幅超卖 | 不支持,切多少用多少 |
| 适用负载 | 开发调试、低频推理、批处理 | 生产推理、多租户训练 |
| 故障隔离 | 无,一个任务可能拖垮同卡任务 | 有,某个分区异常不影响其他分区 |
| 配置复杂度 | 较低 | 较高,涉及物理卡切分 |
选择时我的经验是:先看业务类型,再看团队运维能力。如果你的团队是自研产品的算法团队,任务是训练和评测,推荐优先用切分型vDCU,稳定和省心;如果你的场景是大量小体量推理请求,且能接受偶发性能波动,共享型vDCU能帮你大幅提升利用率。混合部署也常见——同一批物理卡,一部分切成隔离分区给重要业务,一部分留给共享池做弹性。
5. CubeStudio 平台适配:从纳管 DCU 到算法工程师自助使用
5.1 CubeStudio 在 AI 平台中的定位
到这里,底层已经打通:K8s能看到DCU资源,Pod能申请到卡片,整卡、vDCU都能按需调度。但如果你直接把这个K8s集群丢给算法工程师,他们大概率会崩溃。K8s的YAML语法、镜像构建、资源配额管理、数据卷挂载,每一项都是学习成本。
CubeStudio这类AI平台做的工作就是把K8s包装成“像云主机一样”的自助服务出口。从我的理解来看,它通常覆盖三件事:算力资源池的可视化纳管、开发环境的快速创建(比如Notebook)、训练和推理作业的编排与监控。DCU的接入,实际上是让CubeStudio能够识别并调度前面部署好的DCU扩展资源。
5.2 配置 DCU 资源池与配额分配
在CubeStudio里,管理员通常需要先创建一个“资源池”或“GPU资源组”,把带有DCU标签的节点纳管进去。这一步是对接的关键,本质上就是把K8s的节点标签和资源类型映射到平台的资源池定义里。
我的建议是拆分成两个池子:一个是整卡/切分型vDCU池,用于正式训练和生产推理;另一个是共享型vDCU池,用于开发和测试。两个池子的配额策略完全不同,前一个走“按卡数申请、独占使用”的逻辑,后一个走“按vDCU份数申请、共享使用”的逻辑。
配额这块,我强烈建议在平台层就设置好上限,不要依赖K8s命名空间的ResourceQuota。原因是很多平台的配额逻辑和K8s原生的Quota并不完全一致,用户界面显示有卡,实际调度却失败,会非常打击使用信心。平台层的配额管理能让用户在页面上直观看到:自己还能申请几张卡,已经用了多少。
5.3 资源模板设计:如何把 vDCU 暴露给不同团队
CubeStudio这类平台通常支持“资源模板”或“套餐”的概念。管理员可以预置几种规格,让用户按需选择。我在实际项目中是这样设计的:
- 开发调试型:1个共享型vDCU,限制使用时长最多8小时,适合Dev环境调试
- 标准训练型:1个切分型vDCU,32GB显存,适合中号模型训练
- 大模型训练型:4个切分型vDCU,独享整卡,适合百亿级参数微调
- 推理服务型:自动匹配共享型vDCU,允许水平扩容,挂载已部署模型路径
这种模板的好处是:用户不需要理解“什么是vDCU”或“什么是Extended Resource”,只需要选择“多大显存、几张卡、跑多久”。底层调度时,平台把模板翻译成K8s的资源请求和被调度资源名。
5.4 Notebook 和训练任务真正跑起来的链路
从用户点击“创建Notebook”到容器真正跑在DCU上,中间经过的链路值得每一个平台运维人员搞清楚:
用户请求到达CubeStudio后端,平台根据用户选择的资源模板生成K8s Pod定义,在Pod的resources里写入DCU或vDCU的申请量。Pod被提交给K8s API Server后,调度器根据可分配资源、节点标签、亲和性策略,选择一个匹配的DCU节点。Kubelet收到Pod绑定信息,调用容器运行时创建容器。如果挂载了Device Plugin,设备节点会在容器启动时自动注入。容器内部,DTK运行时库识别到设备,PyTorch/vLLM等框架完成初始化。
这条链路里的每一个环节都可能出问题。我排过的最隐蔽的问题是在第4步——平台生成的Pod里没有带nodeSelector,调度器把Pod扔到了一个没有DCU的CPU节点上,容器启动后找不到设备,报错信息却显示“CUDA driver version is insufficient”,让人完全摸不着头脑。所以平台侧的模板定义里,必须显式带上nodeSelector: gpu=hygon-dcu,不给调度器自由发挥的空间。
6. 在 DCU 上部署 DeepSeek:蒸馏模型的推理实操
6.1 选型:为什么用 R1-Distill-Qwen-32B 而不是 671B
DeepSeek现在是一个绕不开的话题,但部署哪个模型需要冷静考虑。DeepSeek-V3/R1这种671B参数的MoE模型,推理需要大规模多卡集群,对卡间互联带宽要求非常高,一般团队很难在生产环境跑好。更现实的选择是DeepSeek-R1的蒸馏版本,比如基于Qwen的7B、14B、32B蒸馏模型,参数量从70亿到320亿不等,单机多卡甚至单卡就能推理。
我这次选择的是DeepSeek-R1-Distill-Qwen-32B,量化为AWQ 4bit后,模型权重大约18GB,加上KV Cache和运行开销,两张32GB的DCU用张量并行可以稳定跑起来。7B和14B的蒸馏版当然更轻量,但推理质量有明显差距,特别在复杂推理任务上,32B的表现接近可用的水平。
6.2 推理框架与镜像准备
DCU上的推理框架选型,目前比较可行的是vLLM和SGLang,但都要选择带ROCm/DCU支持的版本。镜像的构建思路是:以DTK基础镜像为底座,安装对应框架的ROCm分支,再把模型文件挂载进去。
模型权重我建议提前使用ModelScope魔搭社区下载到本地,然后通过hostPath或PVC挂载进容器,不要在每次Pod启动时现场下载,既慢又容易失败。下载命令大致如下,具体参数以ModelScope当前CLI为准:
# 在节点或能访问存储的跳板机上执行 pip install modelscope modelscope download --model deepseek-ai/DeepSeek-R1-Distill-Qwen-32B-AWQ --local_dir /data/models/DeepSeek-R1-Distill-Qwen-32B-AWQ6.3 部署一个大模型推理服务:Deployment + Service 完整示例
下面这个Deployment是我实际调试后简化出来的版本。注意两点:申请的是两张整卡(hygon.com/dcu: 2),因为张量并行需要独占卡,不能用共享型vDCU;模型目录用hostPath直接指向节点上的下载目录。
apiVersion: apps/v1 kind: Deployment metadata: name: deepseek-r1-32b namespace: ai-prod spec: replicas: 1 selector: matchLabels: app: deepseek-r1-32b template: metadata: labels: app: deepseek-r1-32b spec: nodeSelector: gpu: hygon-dcu containers: - name: vllm image: registry.example.com/dtk/vllm:dtk6.2-rocm command: ["/bin/sh", "-c"] args: - vllm serve /models/DeepSeek-R1-Distill-Qwen-32B-AWQ --served-model-name deepseek-r1-32b --tensor-parallel-size 2 --max-model-len 8192 --gpu-memory-utilization 0.9 --port 8000 ports: - containerPort: 8000 resources: limits: hygon.com/dcu: "2" requests: hygon.com/dcu: "2" volumeMounts: - name: models mountPath: /models env: - name: NCCL_DEBUG value: "WARN" volumes: - name: models hostPath: path: /data/models type: Directory然后把推理服务暴露给外部:
apiVersion: v1 kind: Service metadata: name: deepseek-r1-32b-service namespace: ai-prod spec: type: NodePort selector: app: deepseek-r1-32b ports: - name: http port: 8000 targetPort: 8000 nodePort: 32080部署完成后,用一行命令验证服务是否可用:
$ curl http://<node-ip>:32080/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "deepseek-r1-32b", "messages": [{"role": "user", "content": "解释一下什么是Kubernetes的Device Plugin"}], "max_tokens": 512 }'如果返回了包含content字段的JSON,说明整条链路已经通了。
6.4 性能观察:吞吐、时延与显存占用
服务跑起来之后,一定要做一次基础的性能摸底,否则无法判断这张卡“够不够用”。我在实测中主要关注三个指标:首Token延迟、生成吞吐量、显存占用率。
首Token延迟在满负载下如果大于5秒,对于对话类体验就会明显变差;吞吐量决定了你能同时支撑多少个并发请求;显存占用率则告诉你当前配置是否还有余量去加大--max-model-len或提升并发。
观察手段再强调一次:dcu-smi可以看实时的显存和算力使用率;框架自带的/metrics端点配合Prometheus可以长期监控。我建议任何团队在模型上线前,至少压测24小时,确认没有内存泄漏和偶发超时,再对外开放服务。
7. 现场踩过的坑与调优经验
7.1 镜像里缺 HIP 库:initContainer 拷贝驱动的正确姿势
第一个坑,也是最典型的:容器起来后报找不到libamdhip64.so。原因很简单,你用了纯PyTorch基础镜像,里面没有DCU的HIP运行时库。
网上很多教程会让你在镜像里重新安装DTK,但这样镜像会特别大,而且版本更新以后难以维护。更优雅的方式是用initContainer从DTK镜像里拷贝运行时库到共享卷,业务容器启动时挂载这个卷。这样业务镜像保持了最小化,驱动和运行时库的版本管理也集中在initContainer里。
initContainers: - name: copy-dtk-libs image: registry.example.com/dtk:6.2-minimal command: ["cp", "-r", "/opt/dtk/lib", "/opt/dtk/lib64", "/dcu-libs/"] volumeMounts: - name: dcu-libs mountPath: /dcu-libs这个方案还有个好处:宿主机驱动升级时,只需更新initContainer的镜像tag,业务Pod重建后自动用上新版本,不用重新构建每个算法镜像。
7.2 vDCU 显存切分后 OOM 却查不到日志
切分型vDCU模式下,遇到的一个隐蔽问题是:任务在容器内报OOM(显存不够),但dcu-smi显示整卡显存还有剩余,业务的日志里也没有明确错误。原因在于,切分型vDCU给任务分配的是“显存分区”,dcu-smi默认显示的是物理整卡的显存总量和已用量,而不是当前分区视角。
解决方法是使用vDCU视角的工具或命令查看当前上下文里的显存分配。这个细节文档里容易忽略,实际运营时却非常影响排查效率。我的建议是:在平台侧把“vDCU实例”和“物理卡”的监控指标分开展示,任务报错时先看它在哪个vDCU分区,再去看该分区的配额和占用,而不是盯着整卡的显存。
7.3 多卡任务调度到同一节点:集合通信库的隐性问题
部署DeepSeek用了--tensor-parallel-size 2,要求调度器把任务调度到同一节点的两张卡上。K8s原生调度器并不懂这个逻辑。如果两张卡分布在两个节点,模型初始化时集合通信就会失败或极慢。
解决办法是在Pod上配置Pod亲和性,让同一个多卡任务的所有容器都调度到同一个节点。相对于先调度第一个容器再调度第二个,官方推荐的做法是给同类任务打上相同的标签,然后用podAffinity强制同节点。
spec: affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - deepseek-r1-32b topologyKey: kubernetes.io/hostname如果你用的CubeStudio或自研平台支持多卡任务,一定要确认平台层是否正确生成了这个亲和性配置。我见过不止一次,底层K8s已经有DCU,平台也显示有卡,但大模型任务一直起不来,最后发现是多卡跨节点导致的。
7.4 调度器层面的“卡型亲和”与碎片整理
最后一个经验是关于资源碎片。切分型vDCU模式下,一张32GB物理卡如果先分配了一个12GB的vine,再分配一个16GB的vDCU,剩余空间只有4GB,既不够再分一个12GB,也无法组成16GB,形成碎片。时间一长,整张卡看起来“有资源”,但实际能分配的vDCU组合越来越少。
这个问题目前没有完美的自动解法。我的经验是:在平台层面建立“整卡回收”机制,每隔一段时间,把同一节点上散落的小vDCU迁移整合,让碎片空间重新聚合成大段连续资源。配合监控看板的“可分配vDCU组合”指标来发现碎片严重的节点。这个过程需要谨慎操作,迁移前必须确认任务有断点续训能力,否则不要轻易动正在运行的任务。
另外一个比较土但有效的办法是:给不同大小的vDCU规划不同的节点池。比如把32GB的卡统一切成4个8GB,只让8GB任务在这个节点池跑;另一个节点池保持整卡模式,专门接大模型训练。这样虽然牺牲了一些灵活性,但运维大脑负担会轻很多,问题的可预测性大大提升。
最后说几句实际的体会
整套海光DCU接K8s和AI平台的方案走下来,我的核心感受是:技术链路不算短,但每一步都有迹可循,真正考验人的不是某个单独环节,而是整个系统的整体设计和排查能力。
选择什么样的虚拟化和调度策略,没有一个放之四海而皆准的答案,最终还是回到你的业务形态上。如果你的团队以训练为主,老老实实整卡加切分型vDCU,稳定压倒一切;如果以推理为主,共享型vDCU能帮你省下大把硬件成本,前提是你能接受时延波动,并且愿意在监控和隔离上多花心思。
另外想提醒一句:不要迷信某一种“官方推荐”配置,机器到手后一定先做一轮真实业务负载的压测,用你的模型、你的数据、你的并发量去说话。很多坑是压测压出来的,不是在方案评审时想出来的。希望这篇实操记录能帮你少走一些弯路。