十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

海光DCU接入Kubernetes实战:从整卡到vDCU虚拟化与DeepSeek部署

海光DCU接入Kubernetes实战:从整卡到vDCU虚拟化与DeepSeek部署 我在给客户做 AI 平台适配的时候经常要回答同一个问题手里有海光 DCU 这种国产加速卡到底怎么接进 Kubernetes怎么让平台上的训练和推理任务真正用起来这个问题看着像是“装个驱动、写个 Device Plugin”的功夫活但真做起来牵扯到资源上报、调度策略、显存隔离、运行时注入一堆事情尤其是 vDCU 虚拟化这块方案选不对后面全是坑。这篇文章就用自己的实操经历来拆一遍 CubeStudio 平台接入海光 DCU 的完整链路从整卡到共享从两种 vDCU 虚拟化到 DeepSeek 模型部署把每一步怎么选、怎么做、踩过什么坑都交代清楚希望能给正在做国产算力适配的人省点弯路。1. 整体方案拆解DCU 进 K8s 到底要解决哪几件事1.1 为什么不能“装个驱动就完事”很多人刚接触海光 DCU 的时候第一反应是“它支持 ROCm那我把驱动装上容器里把/dev/dri和进程库挂进去不就能跑了吗”。单机单卡这么玩确实能跑通但一旦进了 Kubernetes事情就变成另一套逻辑了。K8s 的调度器根本不认识 DCU它只知道 CPU、内存这种原生资源设备要接入必须通过扩展资源Extended Resource的方式被“发现”并“上报”然后由调度器决定把 Pod 调度到哪些节点上。这里的核心问题有三个一是设备发现和资源上报节点上的 Kubelet 得知道这块节点有几张卡、每张卡多大显存然后实时报给 API Server二是调度感知用户申请 1 张卡或 2 张卡的时候调度器得能把这些请求映射到具体节点上还得避免同一张卡被多个 Pod 同时占用三是运行时的设备注入Pod 真正跑到节点上的时候容器里得能看到对应的设备文件、驱动库、环境变量否则程序根本调不到 DCU。海光 DCU 比较特殊的地方在于它并不像 NVIDIA 那样有原生的 MIG 或 vGPU 方案可以直接对接 K8s整卡模式下有成熟的社区路径但要做共享和虚拟化就需要一套自己的 vDCU 调度机制。对 AI 平台来说如果只支持整卡那么闲时算力会白白浪费这也正是 CubeStudio 这类平台要同时支持整卡和两种虚拟化模式的原因。1.2 CubeStudio 平台侧的适配框架我在这个项目里用的 AI 平台是 CubeStudio它本身是一个面向模型开发、训练和推理的云原生平台底层依赖 Kubernetes向上提供项目空间、资源队列、任务模板这些能力。它的资源管理层屏蔽了具体设备类型调度的时候只认 Resource Quota 和节点 label。所以适配 DCU 的思路是平台资源层按“抽象资源”来建模设备类型只是资源池的一个属性。具体的链路拆解下来是四段DCU 驱动和容器运行时 - Device Plugin资源上报与健康检查 - 调度插件vDCU 分配与绑卡 - CubeStudio 业务层队列、模板、配额每一段都有独立的坑。驱动段要解决的是“容器里能不能看到设备”Device Plugin 段要解决的是“K8s 怎么知道节点有卡”调度插件段要解决的是“请求怎么映射到具体物理卡或 vDCU 实例”业务层段要解决的是“用户提交任务时怎么声明资源、怎么选择模式”。缺任何一段整个闭环都跑不起来。2. 两种 vDCU 虚拟化模式到底有什么区别怎么选2.1 整卡模式最简单也最浪费先看最基本的整卡模式。在这个模式下资源模型就以“张”为单位一张物理卡等于一个调度单位。用户申请hygon.com/dcu: 1调度器就找有空闲卡的节点然后通过 Device Plugin 在容器里注入对应的/dev/dri/renderDxxx设备节点和 ROCm 用户态库Pod 起来之后整张卡被独占。这种模式的优势是隔离性好一个任务出问题不会影响同节点的其他任务性能也能打满缺点是资源利用率很低。我实际看过一个客户集群跑微调任务的时候 GPU 利用率平均只有 30%-40%但显存占着又不能给别的任务用很浪费。如果团队规模小、任务密度低整卡模式足够用但如果要做多租户、模型服务混部整卡模式就不够看了。整卡模式的配置相对简单发布一个 DaemonSet 形态的 Device Plugin 就行。Device Plugin 主要的逻辑是定期调用底层驱动接口拿到每张卡的健康状态和显存容量然后通过 ListAndWatch 接口上报给 Kubelet。我用的 DCU 驱动是 DTK 24.04.1设备节点路径是/dev/dri/renderD128开头的几个节点可以通过dcu-smi命令确认到底有几张卡。2.2 vDCU 虚拟化两条路线各有各的逻辑到了共享模式就需要引入 vDCU 虚拟化。所谓“两种 vDCU 虚拟化”我理解的落地形态分别对应显存隔离型和进程虚拟化型它们的核心逻辑和适用场景差别很大。第一种是基于显存分片的 vDCU。这种方案在物理卡上划分出多个显存区域每个区域配上独立的计算上下文配额类似把一张 64G 的卡切成 4 个 16G 的 vDCU 实例。每个 vDCU 实例有独立的设备句柄、独立的显存空间甚至可以在用户态层面模拟出独立的设备索引。这种方案的隔离性比较好显存不会互相越界适合跑模型推理和中小模型微调。缺点是对驱动和运行时依赖比较深如果 DCU 驱动不提供底层的分片能力就只能靠用户态库“伪装”显存上限隔离强度会打折。第二种是基于进程调度的 vDCU。这种方案把物理卡资源统一交给一个调度代理代理按策略把计算任务轮流分发到不同计算队列上同时用 cgroup 或在 ROCm 运行时层面做显存限制。打个比方就像一台物理服务器上开多个虚拟机CPU 和内存是超卖共享的但物理机本身只有一个。这种方案的好处是密度高可以支持几十个 Pod 共享几张卡坏处是性能隔离弱任务多了可能互相争抢显存带宽导致某个 Pod 的推理延迟突然飙高。CubeStudio 适配里这两种模式我都在用原则是在线推理服务和需要稳定资源的训练任务用第一种离线批处理任务和开发调试环境用第二种。两种模式在 K8s 里的实现方式完全不同第一种通常需要在 Device Plugin 之外再写一个“vDCU Manager”组件负责在节点上维护 vDCU 实例的分配关系第二种则像是一个用户态的调度器把扩展资源请求翻译成对底层队列的配额订阅。3. 实操从 DCU 驱动到 CubeStudio 跑通 DeepSeek3.1 环境准备驱动、运行时和工具链一次性装齐先说环境版本避免大家照抄的时候踩版本坑。我的这套环境是组件版本操作系统Ubuntu 22.04 LTS内核 5.15海光 DCU 驱动DTK 24.04.1容器运行时containerd 1.7.xKubernetes1.28.xCubeStudio2.x 版本ROCm 用户态rocm-dev 5.xDTK 自带适配安装驱动的过程这里不展开但有一点要强调驱动装完之后一定要确认容器里能调 DCU。很多人栽在主机上 dcu-smi 正常但容器里访问不了设备。确认的关键点是三样东西一是/dev/dri/renderDxxx设备节点有没有正确注入二是/opt/dtk或 ROCm 的librocclm.so这些用户态库能不能在容器里找到三是/proc/driver/hygon等内核模块信息能否在特权容器和普通容器里正确读取。我的做法是先起一个测试容器手动验证一遍docker run -it --rm \ --device/dev/dri \ -v /opt/dtk:/opt/dtk \ -e LD_LIBRARY_PATH/opt/dtk/lib:/opt/dtk/rocm/lib \ registry.example.com/dcu-runtime:base \ bash -c rocm-smi python3 -c import torch; print(torch.cuda.is_available())注意这里的torch.cuda.is_available()海光 DCU 的 PyTorch 支持走的是 ROCm 后端所以在安装了对应适配版本的 PyTorch 之后torch.cuda层面的 API 是通用的。如果你的镜像里 CUDA 相关环境变量和 ROCm 冲突了会出现“能 import torch但 is_available 一直是 False”的怪问题后面会单独说。3.2 K8s Device Plugin 部署让集群“看见”DCU 卡驱动和镜像验证通过之后第一步就是把 DCU 以扩展资源的形式注册进 K8s。这里不用自己从零写 Device Plugin海光 DTK 有配套的 plugin 插件。但就算是现成插件配置上还是有几个关键点。资源名称建议统一用hygon.com/dcu这个名称会直接成为用户在 CubeStudio 里申请资源的标识。Device Plugin 的 DaemonSet 必须配置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/dcu-device-plugin:dtk-24.04.1 securityContext: privileged: true volumeMounts: - name: device-plugin mountPath: /var/lib/kubelet/device-plugins - name: sys mountPath: /sys - name: dev mountPath: /dev env: - name: DCU_RESOURCE_NAME value: hygon.com/dcu volumes: - name: device-plugin hostPath: path: /var/lib/kubelet/device-plugins - name: sys hostPath: path: /sys - name: dev hostPath: path: /dev注意几个坑。第一必须在 Kubelet 的启动参数里配置--feature-gatesDevicePluginstrue一般云原生发行版默认是开着的但自建 K8s 容易漏。第二Device Plugin 启动顺序必须在 Kubelet 之后如果 DaemonSet 在节点初始化阶段就跑起来了而 Kubelet 还没注册设备插件插件进程会自动重试一般来说不用太担心。第三健康检查逻辑要看日志DCU 驱动如果加载失败Device Plugin 会把对应设备标记为不健康。部署完之后检查节点资源是否注册成功kubectl describe node dcu-node-01 | grep -i dcu正常情况下能看到类似hygon.com/dcu: 4的 allocatable 资源。如果看不到先看 Device Plugin 的日志再排查设备节点的权限。3.3 整卡模式首个 DCU Pod 跑起来资源注册好之后先跑一个整卡模式的 Pod 验证链路是否通。示例 Pod 配置apiVersion: v1 kind: Pod metadata: name: dcu-test spec: restartPolicy: Never containers: - name: dcu-test image: registry.example.com/dcu-runtime:base command: [/bin/bash, -c] args: - | rocm-smi python3 -c import torch; print(torch.cuda.get_device_name(0)) resources: limits: hygon.com/dcu: 1调度器看到hygon.com/dcu: 1的 request 之后只会把 Pod 调度到有 DCU 的节点。Pod 起来之后容器里看到/dev/dri/renderD129这类节点rocm-smi能列出一张卡PyTorch 也能拿到设备名说明整卡链路已经通了。整卡模式最需要注意的其实是resources.limits和resources.requests要写一致。Device Plugin 模式下扩展资源只有 limit 有实际意义request 只是给调度器看的如果你只写 requests 不写 limitsKubelet 不会帮你在容器里注入设备节点Pod 会因为拿不到设备而各种报错。3.4 共享模式两种 vDCU 的部署与调度配置整卡通了之后重点来了——共享模式。我这里把两种 vDCU 分别部署对应不同的平台配额池。先看显存分片型 vDCU。这种方案我采用的部署方式是“节点级 vDCU Manager 独立资源名”。也就是说每个 DCU 节点上除了 Device Plugin还跑一个 vDCU 管理组件它会读取一张卡的总显存按配置切分成多份并为每个 vDCU 实例分配独立的设备号和显存配额。资源上报的名称用hygon.com/vdcu-mem单位是“MiB”的整数值这样用户在 CubeStudio 里可以申请 16384MiB 这样的显存请求。配置上vDCU Manager 的关键参数是--device-ids0-3 # 管控的物理卡范围 --slice-modememory # 显存分片模式 --slice-size16384 # 每个 vDCU 的显存配额MiB按需调整 --resource-namehygon.com/vdcu-mem切片的时候要留一点余量。比如 64G 的卡标称 65536MiB实际驱动预留之后可分配显存不到那么多所以单卡一般切成 3 个 16G 或者 2 个 24G而不是贪心切 4 个 16G。切太满的话任务跑到峰值显存有 OOM 风险调度器还需要处理“机器有卡但显存不够”的尴尬局面。第二种进程调度型 vDCU 则不同。它不把显存硬切而是把整张卡抽象成一个“算力池”资源名称用hygon.com/vdcu-share调度器根据每个 Pod 声明的算力权重来分配底层计算队列的配额。CubeStudio 里的配额池可以配置“最多提交 N 个作业共享 1 张卡”实际落地的时候我见过比较稳的配置是共享比控制在 1:4 以内也就是一张卡最多同时 4 个 Pod再高就容易出现显存带宽瓶颈。3.5 CubeStudio 侧适配模板和队列双管齐下平台侧要做的事情相对聚合主要是在 CubeStudio 的管理后台里新增资源规格、配置队列、定义模型服务模板。首先是资源规格。CubeStudio 管理资源走的是“资源规格 - 队列 - 项目”的层级。我需要先定义几种规格dcu-whole整卡规格hygon.com/dcu: 1、dcu-vdcu-mem-16g显存分片规格hygon.com/vdcu-mem: 16384、dcu-vdcu-share-1进程调度规格hygon.com/vdcu-share: 1。然后把不同规格绑到不同队列的配额上。其次是容器运行时配置。CubeStudio 提交的训练任务或推理服务最终都会变成 K8s 的 Deployment/Job所以我需要在平台的“运行时模板”里把设备注入和环境变量固化下来。我给的模板核心内容如下resources: limits: hygon.com/dcu: 1 # 或者 hygon.com/vdcu-mem: 16384 env: - name: LD_LIBRARY_PATH value: /opt/dtk/lib:/opt/dtk/rocm/lib - name: HIP_VISIBLE_DEVICES value: 0 volumeMounts: - mountPath: /opt/dtk name: dtk volumes: - hostPath: path: /opt/dtk name: dtk这里有个容易踩的坑HIP_VISIBLE_DEVICES和CUDA_VISIBLE_DEVICES的行为不一样。CUDA 生态里很多框架会把CUDA_VISIBLE_DEVICES当作标准环境变量但海光 DCU 是 ROCm 生态识别的是HIP_VISIBLE_DEVICES。如果只设了 CUDA 变量部分框架会认为没有可用设备。最稳妥的办法是两个变量都设置或者干脆交给 Device Plugin 去注入不要在模板里写死。最后是模型服务模板。因为标题里明确要部署 DeepSeek我在 CubeStudio 里配置的是“vLLM 推理服务模板”镜像直接选用适配 DCU 的 vLLM 版本启动命令通过模板参数动态拼接。下文单独讲部署细节。3.6 DeepSeek 部署在共享和整卡上的两套方案DeepSeek 部署这块实际要看模型规模和可用卡的显存。满血版 DeepSeek-V3 是 671B 参数FP16 权重光模型文件就要 1.3TB 以上显然不是单机几卡能搞定的需要多节点并行那属于大规模集群部署的范畴。这次实操我主要部署的是 DeepSeek 蒸馏版模型以 DeepSeek-R1-Distill-Qwen-14B 为例FP16 权重约占 28GB 显存单张 32G 或 64G 的 DCU 可以跑推理但用共享模式跑这种规模的模型会比较吃力。先看整卡模式部署 vLLM。在 CubeStudio 里我建了一个服务模板核心参数如下apiVersion: apps/v1 kind: Deployment metadata: name: deepseek-r1-distill-qwen-14b spec: replicas: 1 selector: matchLabels: app: deepseek-14b template: metadata: labels: app: deepseek-14b spec: containers: - name: vllm image: registry.example.com/vllm-dcu:v0.6.6-dtk24.04 command: [python3, -m, vllm.entrypoints.openai.api_server] args: - --model/models/deepseek-r1-distill-qwen-14b - --served-model-namedeepseek-14b - --tensor-parallel-size1 - --gpu-memory-utilization0.9 - --max-model-len8192 - --port8000 resources: limits: hygon.com/dcu: 1 env: - name: LD_LIBRARY_PATH value: /opt/dtk/lib:/opt/dtk/rocm/lib - name: HIP_VISIBLE_DEVICES value: 0 volumeMounts: - name: model-storage mountPath: /models - name: dtk mountPath: /opt/dtkgpu-memory-utilization0.9的意思是让 vLLM 最多用 90% 的显存做 KV cache 和激活值这个值不要设成 1.0否则预分配显存的时候容易撞到驱动预留内存导致初始化失败。max-model-len8192也要根据实际显存调整如果业务需要更长的上下文14B 模型跑 8192 是够用的跑到 16384 就会开始紧张。再看共享模式部署推理。共享模式跑在线推理的核心问题是“显存被打爆”。我用显存分片型 vDCU 的时候会把模型压到很小的 batch size并且把gpu-memory-utilization调到 0.7 以下保证共享场景下多个服务实例之间不会互相挤占。如果你用的是进程调度型 vDCU建议不要部署重量级 LLM 服务更适合跑一些轻量的 embedding 或文生文中间件。DeepSeek 模型跑起来之后验证接口curl -s http://service-ip:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: deepseek-14b, messages: [{role: user, content: 你好请简单介绍下你自己}], max_tokens: 200 }返回正常的 JSON 响应说明从 DCU 驱动、Device Plugin、调度器到 CubeStudio 模板的完整链路都通了。4. 我踩过的坑和排查经验4.1 设备节点存在但容器内不可见这是最常见的第一课。主机上dcu-smi能正常列卡但容器里跑rocm-smi就是看不到。排查步骤是ls -l /dev/dri/renderD*看设备节点权限owner 是不是root:video容器进程的 GID 有没有落到video组看容器运行时是不是 containerdcontainerd 下要确保 Device Plugin 的 CDI 或 device 注入链路是通的不能沿用 Docker 时代--device那套思维用特权容器做对照实验特权容器可见就说明是设备注入问题不是驱动问题。我最后定位到的原因往往是 Pod 的securityContext里没有设置runAsGroup到video组GID 通常是 44。不加这个配置进程虽然有设备文件但权限不够打不开/dev/dri/renderDxxx。4.2 vDCU 显存隔离失效显存分片型 vDCU 最怕的事情是一个任务把显存打满连带把同卡其他任务一起干掉。从实际表现看用 DTK 24.04.1 自带的 vDCU 切分功能时显存隔离是有效的但如果你的方案只是“改了环境变量伪装显存上限”那不叫隔离随时可能崩。要验证隔离强度我提供一个简单方法在一个 vDCU 实例里写一个申请大显存的 PyTorch 脚本把显存打到接近配额上限然后在另一个 vDCU 实例里跑同样的脚本观察是否报 OOM。如果报 OOM 说明隔离生效如果直接把整卡搞挂说明隔离层没有真正拦截显存申请得换方案。4.3 torch.cuda.is_available() 返回 False这个问题的根因通常是镜像里的 PyTorch 与 DTK 的 ROCm 版本不匹配。海光 DCU 的官方容器镜像会配套一个指定版本的 PyTorch如果你从社区拉一个通用的 ROCm 版 PyTorch大概率能 import 但找不到设备。解决的思路是确认 DTK 版本和 pytorch 的 rocm 版本对齐。比如 DTK 24.04 对应的 ROCm 5.x那镜像里的 PyTorch 必须是针对该 ROCm 版本编译的。还有一种情况是环境变量被覆盖容器启动时如果设置了ROCR_VISIBLE_DEVICES而没有正确指定设备索引也可能出现 False。4.4 调度器超卖导致节点资源瞬间打满共享模式下如果 CubeStudio 里配的队列配额超过物理卡总能力就可能出现调度器把 Pod 分配到节点之后vDCU Manager 发现没有足够资源而拒绝的情况。排查时要分清是“调度层超卖”还是“运行时拒绝”。调度层超卖的原因在于我把hygon.com/vdcu-mem设计成了简单的整数计数型扩展资源K8s 调度器无法感知同一张物理卡上不同显存片之间的拓扑关系。比如一张 64G 卡按 16G 一份切理想情况是上报 4 份资源但如果 vDCU Manager 在创建实例的时候发现物理卡只剩 14G 可用就会拒绝。这种问题的解法是在 CubeStudio 侧限制同一张卡上的作业数或者让 vDCU Manager 定期反向纠正节点资源余量标记不健康的剩余资源。4.5 DeepSeek 加载慢、启动失败DeepSeek 蒸馏模型的权重文件动辄几十 GB从对象存储拉取到节点的时间可能占据整个启动流程的大头。一个实际建议是把模型权重提前预热到节点本地磁盘用 hostPath 挂载而不是每次启动都从远端拉取。CubeStudio 里我通常会配一个“模型预热 Job”集群空闲的时候把常用模型推到各 DCU 节点上。启动失败的另一个原因是权重文件损坏。vLLM 启动的时候如果safetensors文件校验不过去会直接抛异常。这种时候不要盲目重启先把模型目录的 checksum 和源文件比对一遍很多时候是传输中断导致的文件截断。5. 一些实操体会海光 DCU 接入 Kubernetes 这件事表面上看是一个设备插件的问题实际做下来你会发现真正的复杂度集中在“资源语义”的设计上。整卡、显存分片、进程调度三种模式本质上是三种不同的资源抽象整卡是离散计数显存分片是连续量配额进程调度是带权重的共享池。资源语义一旦定下来调度器、平台侧、运营运维策略全都围绕它展开后面改起来非常伤筋动骨。所以想给正在做类似适配的人一个建议动手之前先想清楚你的业务场景到底需要哪几种模式。如果主要跑大模型推理整卡加显存分片就够了如果要做多租户开发环境进程调度型 vDCU 的性价比会高很多。千万不要一开始就追求“全都要”虚拟化方案越复杂运维开销越大出问题的时候排查链路也越长。另外国产加速卡的社区工具链相比 NVIDIA 还是有不小差距遇到问题很多时候没有现成方案需要自己通过打日志、读源码、看驱动版本来定位。在这种条件下保持“最小化验证”的习惯很管用每次只改一个变量从单机到集群逐步加复杂度能少走很多弯路。最后分享一个我在多个项目里验证过的经验在部署 DeepSeek 等大模型推理服务时不要只盯着 DCU 的算力显存带宽和模型加载路径往往是瓶颈。把模型放在本地 NVMe 上预分配好页表设置合理的并发数比盲目追求更高的推理框架参数更实际。先把一个模型跑稳再迭代优化这条路走得最踏实。
返回列表