海光DCU接入Kubernetes与CubeStudio平台实战:从驱动到DeepSeek推理
说实话国产加速卡接入 Kubernetes 这件事网上的资料基本靠猜。我这次完整地把海光 DCU 接进了 Kubernetes 和 CubeStudio AI 平台从最基础的驱动安装到最后的 DeepSeek 推理服务上线前后折腾了两周中间踩了不少官方文档里根本没有的坑。这篇文章就把整个适配过程原原本本写出来重点讲清楚整卡、共享、两种 vDCU 虚拟化这几种资源形态到底该怎么选、怎么配以及最后怎么在 CubeStudio 平台上把 DeepSeek 平稳跑起来。如果你是平台工程师、算法工程师或者正在给公司做国产化算力方案选型这篇文章应该能帮你少走一大半弯路。1. 这套方案到底在解决什么问题1.1 国产算力与 K8s 之间的“最后一公里”先说个现实问题Kubernetes 对 GPU 的支持已经很成熟了NVIDIA 有 device plugin、有 MIG、有 vGPU社区生态非常完善。但换到海光 DCU 这类国产加速卡情况立刻变得不一样。DCU 虽然兼容 ROCm 生态底层编程模型是 HIP但 K8s 并不知道 DCU 是什么东西默认情况下 kubelet 只能识别 CPU 和内存DCU 对它来说就是一块陌生的 PCIe 设备。这就产生了一个关键的“最后一公里”问题算法工程师希望像用 GPU 一样在 YAML 里写一行dcu: 1就能申请到算力但底层平台得有人把 DCU 的设备发现、资源上报、调度分配、运行时注入这一整条链路打通。没有这一步国产算力卡得再死也只能在同一台物理机上手动跑任务根本谈不上规模的弹性调度。1.2 CubeStudio 在架构里扮演什么角色CubeStudio 这类 AI 平台本质上是把 Kubernetes 的复杂度封装起来给算法工程师一个浏览器界面。它主要负责三件事一是把 K8s 集群抽象成算力资源池二是把训练、推理、Notebook 这些任务封装成平台上的可创建对象三是提供模型管理、日志、监控这些周边能力。所以我们这次的工作量实际上分成了两个层面底层是让 K8s 能感知和调度 DCU上层是让 CubeStudio 能把这些 DCU 资源池化并且把 DeepSeek 这类模型变成一键可部署的服务。这篇文章的核心就是解决这两个层面的问题。1.3 三种调度路径的思路对比在我这次适配的过程中DCU 的接入方式可以分成三大类整卡、共享、vDCU 虚拟化。其中 vDCU 又分硬切和软切两种实现所以严格来说是四种资源形态。整卡模式最简单逻辑上就是把一张 DCU 卡当作一个可调度的单位适合大模型训练这种需要独占显存和算力的场景。共享模式则是把一张卡的显存和算力切分成多个更小的单位适合推理服务、开发调试这种负载。vDCU 硬切依赖硬件层面的分区能力隔离性最强接近物理卡的效果vDCU 软切则更灵活、粒度更细可以在驱动和 agent 层面动态分配显存和算力但隔离性略弱。这几种方式不是替代关系而是适用不同场景。我在下面会把这四种形态的可观测性、隔离强度、配置复杂度全部摊开来讲。2. 环境准备驱动、容器运行时与 Device Plugin2.1 海光 DCU 软件栈速览海光 DCU 的软件栈和 AMD ROCm 高度兼容核心组件包括驱动、HIP 运行时、ROCm 数学库以及上层工具链。我们这次使用的是海光发布的 DCSDeep Computing Software Stack软件栈它在 ROCm 基础上做了针对 DeepSeek 这类大模型的优化还自带了一些调度和虚拟化相关的增强功能。装驱动之前要先确认操作系统版本和内核版本。我们这边是 CentOS 7.9内核 3.10和部分 Ubuntu 20.04内核 5.4混部的海光的驱动对这两类系统支持都比较完善。如果你用的是更新的内核比如 5.15 以上建议先查看海光官方的兼容列表否则编译 DKMS 模块时容易出问题。2.2 驱动安装与容器运行时配置驱动安装本身不复杂就是标准的 RPM/DEB 安装流程。装完后关键的一步是重启节点然后执行hy-smi确认卡是否被识别。hy-smi正常情况下列表里能看到每张 DCU 卡的型号、显存总量、温度、利用率类似 NVIDIA 的nvidia-smi。如果这里看不到卡后面全是白搭。接下来是容器运行时。Kubernetes 要调度 DCU容器里必须能访问到 DCU 设备。最简单的做法是使用 Docker 的 device 映射加上--group-add video让容器内进程有权限访问/dev/dcu*设备。但手工写docker run参数显然不符合 K8s 的使用方式所以我们真正要做的是在 kubelet 层解决设备注入。海光官方提供了自己的容器运行时插件也可以直接复用 containerd 的 device plugin 机制。如果你用的是 containerd建议装好驱动之后在 containerd 配置里增加 DCU runtime 相关的配置段确保最终 Pod 创建时会自动挂载/dev/dcu设备节点和必要的库文件。我在实际环境中还发现光挂设备是不够的容器里往往还需要/opt/hygon下的运行库、/etc/ld.so.conf.d/下的库路径配置。这一块最好通过 DaemonSet 在节点上初始化一个基础镜像把 DCS 软件栈的运行时目录挂载进所有 Pod否则容器里经常出现libhsa-runtime64.so找不到的报错。2.3 Device Plugin 部署与资源上报有了驱动和运行时接下来就是在 K8s 里注册 DCU 资源。Kubernetes 的 device plugin 机制允许我们自定义一种 Extended Resource把 DCU 上报给 kubelet这样调度器在调度 Pod 的时候就能感知到 DCU 的存在。我整理的 device plugin 部署配置大概是这样的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: containers: - name: dcu-device-plugin image: registry.internal/hygon/dcu-device-plugin:v1.0 securityContext: privileged: true env: - name: DEVICE_MODE value: fullcard volumeMounts: - name: device-plugin mountPath: /var/lib/kubelet/device-plugins - name: sys mountPath: /sys - name: dcu-dev mountPath: /dev/dcu volumes: - name: device-plugin hostPath: path: /var/lib/kubelet/device-plugins - name: sys hostPath: path: /sys - name: dcu-dev hostPath: path: /dev/dcu这个 DaemonSet 会在每个有 DCU 的节点上启动向 kubelet 注册资源名称并上报数量。例如hygon.com/dcu的可用数量就是节点上 DCU 卡的张数。注册成功之后可以用下面这个命令确认资源已经生效kubectl describe node node-name | grep -A5 hygon.com/dcu提示DEVICE_MODE 环境变量很关键。整卡和 vDCU 模式上报的资源类型不一样后续如果要切换模式只需要改这个变量并重启 device plugin不需要重装驱动。共享模式的资源上报逻辑则更复杂需要 device plugin 配合虚拟化服务动态调整上报数量。3. 三种调度形态整卡、共享、两种 vDCU 虚拟化3.1 整卡模式最直观但最浪费整卡模式是最先被我验证通过的。在这种模式下device plugin 把每张物理卡当作一个资源单位上报Pod 里申请hygon.com/dcu: 1调度器就会把 Pod 调度到占用率较低的节点上并且通过 device plugin 向容器注入对应卡的设备节点。配置示例很简单resources: requests: hygon.com/dcu: 1 limits: hygon.com/dcu: 1这里有个容易踩的坑如果只配requests不配limitsK8s 不会为这个资源建立 QoS 保障device plugin 的分配逻辑也可能出现偏差。所以我建议 DCU 相关的请求一定要requests和limits都写上而且数值保持一致。整卡模式的问题也很明显如果只是跑一个 7B 参数的推理服务显存根本用不满但整张卡被独占其他任务只能等着资源利用率非常难看。在探索混合部署的时候我逐步意识到单靠整卡模式搞算力池是有瓶颈的必须引入更细粒度的切分方式。3.2 共享模式如何实现显存级切分共享模式解决的就是整卡浪费的问题。它的目标是把一张 DCU 卡的显存动态划分给多个 Pod 使用每个 Pod 只能看到自己分配到的显存区域和对应的算力配额。实现上共享模式通常由设备插件和一个节点侧的虚拟化 agent 配合完成。agent 负责在节点上创建虚拟设备device plugin 负责向 kubelet 上报这些虚拟设备并响应 Pod 的分配请求。用户在申请资源的时候除了指定虚拟设备数量还可以通过自定义资源指定显存大小比如resources: limits: hygon.com/vdcu: 1 hygon.com/vdcu-memory: 8 # 单位 GB表示申请 8GB 显存这个模式下比较关键的细节是显存分配策略。Drivers 可能会在分配时预留一部分显存作为上下文所以如果你申请了 8GB实际容器内可用可能是 7.5GB 左右。我第一次跑 DeepSeek 推理时max-model-len 设置得稍微大了一点结果直接 OOM。后面我学乖了申请显存时会在模型实际峰值需求基础上多留 20% 的 buffer。3.3 两种 vDCU 虚拟化方案横向对比vDCU 是海光在共享基础上做的更完整的虚拟化方案。我这次分别验证了两种实现第一种是硬件分区模式可以理解成“硬 vDCU”。底层的 DCU 卡支持把一张物理卡切分为多个有独立显存和计算单元的分区每个分区分配固定的显存和算力配额分区之间在硬件层面做了隔离。这种模式的好处是隔离性很强一个分区内的任务发生 OOM 或者算力跑满不会影响同卡的其他分区特别适合多租户的生产环境。缺点是切分粒度是固定的比如一张卡被切成 4 个 vDCU每个分区就是整卡的 1/4不能任意调整。第二种是软件动态模式也就是“软 vDCU”。它通过驱动层和虚拟化 agent 动态划分显存再配合调度策略限制算力。这种方式可以做到很细的分配粒度显存可以精确到 1GB算力也可以配置为百分比比如“这张 vDCU 最多使用整卡 50% 的算力”。灵活性比硬切好很多但隔离性相对弱一些。极端情况下如果同一块物理卡上的某个任务把显存带宽占满了其他软 vDCU 里的任务会感知到性能波动。我把两种方式的差别整理成了一个表方便对照维度硬 vDCU硬件分区软 vDCU软件动态切分粒度固定如 1/2、1/4灵活可指定显存和算力百分比显存隔离强硬件级隔离中依赖驱动调度算力隔离强独立计算单元中时间/频率限制切换成本需要重启设备插件或重配分区动态在线可调整适用场景多租户、生产级隔离要求高内部研发、推理混部、弹性容量注意不管用哪种 vDCU节点上报的资源和整卡完全不一样。硬 vDCU 模式下device plugin 上报的通常是固定实例数比如hygon.com/vdcu: 8表示整卡被切成了 8 个实例软 vDCU 模式下上报的数量可能会随着显存分配动态变化。这就是为什么前面说 DEVICE_MODE 切换时要特别小心分配逻辑完全不同。3.4 调度器如何配合资源命名与扩展调度DCU 设备插件把资源上报之后调度器侧的配合就变得非常重要。默认的 kube-scheduler 在调度 Extended Resource 时只会做“数量是否满足”的匹配不会去做“显存是否充足”的判断。举例来说如果一张卡有 32GB 显存已经被切成了 3 个 10GB 的 vDCU但你的 Pod 申请的是 8GB默认调度器仍然可能只检查 vDCU 的数量是否足够而忽略了总显存已经超卖。要解决这个问题有两种做法。一种是把显存作为单独的 Extended Resource 上报比如hygon.com/vdcu-memory然后让调度器同时检查hygon.com/vdcu和hygon.com/vdcu-memory两个资源。另一种是部署自定义调度器或者给 kube-scheduler 挂一个 extender让 DCU 的虚拟化服务来精确判断某个节点是否有能力再容纳一个新的 vDCU。我实际推荐的做法是把这两种结合起来。用 Kubectl 提交 Pod 的时候硬性约束走 K8s 自带的 Extended Resource 校验精细判断走调度 extender 的后端校验。这样既保证了基本的资源合法性又避免了显存超卖导致的运行期 OOM。4. CubeStudio 接入实操4.1 平台侧的资源接入流程CubeStudio 接入 DCU 资源池的过程核心就是让它能够读取到前面注册的hygon.com/dcu和hygon.com/vdcu等资源。平台通常有“资源池管理”或者“集群管理”的入口你需要把 K8s 集群的 kubeconfig 配置到 CubeStudio 上然后平台会通过 Kubernetes API 拉取集群的节点信息、资源信息。这里要注意一个细节CubeStudio 展示的资源统计往往不是直接读取 Extended Resource而是读取节点的 allocatable 信息。如果你用 device plugin 注册的是hygon.com/dcu那么平台界面上是否展示这一项取决于 CubeStudio 的版本和配置。我这次用的版本需要在平台配置中心手动添加自定义资源的名字否则界面上看不到 DCU 资源只会显示 CPU 和内存。添加之后你就能在平台的资源池页面看到类似“dcu 资源总量 4 张 / 已分配 2 张”的统计了。这时候就可以开始创建资源池和配额。4.2 集群接入与资源池划分资源池划分是平台侧最重要的一步。我的做法是创建三个资源池第一个是整卡池调度策略限定为“独占”用来跑大模型训练和需要完整算力的任务。第二个是共享/vDCU 池允许超卖用来跑推理服务、开发测试。第三个是根据不同业务部门划分的配额池限制每个团队最多能申请多少 DCU 资源。在实际配置时需要重点确认 CubeStudio 是否支持按资源类型设定调度策略。比如有的平台一个资源池只能绑定一种资源类型。如果你的平台也是这种情况就建议把整卡池和 vDCU 池拆开不要混在同一个池里否则后面创建任务的时候会出现明明选了 vDCU但平台给你调度到了整卡节点上的情况。4.3 创建训练与推理任务资源池准备就绪之后在 CubeStudio 上创建任务就变成了一件相对简单的事情。新建一个 Jupyter Notebook 任务时在资源申请区域选择刚才创建的 vDCU 资源池显存填写 16GB平台就会自动帮我们生成对应的 Pod YAML并且通过调度器把 Pod 调度到合适的节点上。从我的实际体验来看CubeStudio 底层还是通过 Kubernetes API 创建 Pod只是把复杂的资源申请细节封装成了表单。这意味着即便你在平台上看到的选项没有直接暴露hygon.com/vdcu-memory这个字段也可以通过修改任务模板或创建自定义资源类型来扩展。对于平台二次开发比较熟悉的朋友可以直接在 CubeStudio 的模板里注入自定义的 YAML 片段。5. DeepSeek 部署实操从模型下载到服务上线5.1 模型与推理框架选型这次部署 DeepSeek我选的是 DeepSeek-R1-Distill-Qwen-7B 这个蒸馏版本。选择它主要有两个原因一是 7B 规模对显存和算力的要求适中单张 DCU 就能跑起来演示效果好二是这个模型在 HuggingFace 上有完整的权重方便快速验证。推理框架方面我优先推荐 vLLM。vLLM 对 ROCm/HIP 生态的兼容性已经做得比较成熟支持 PagedAttention显存利用效率比原生 Transformers 库高不少。另外一个可选的方案是 SGLang它在长文本场景下性能也很不错但当时在海光 DCU 上的踩坑记录比 vLLM 少所以我这次主打 vLLM。如果你只是想在开发环境快速验证也可以先用 llama.cpp 或者 Ollama。Ollama 支持通过环境变量指定使用 ROCm 后端配置起来非常快适合做功能连通性测试。但生产级部署我还是建议 vLLM后续并发上来之后差距会非常明显。5.2 使用 vLLM 在 DCU 上启动 DeepSeek把模型权重放到节点本地目录比如/data/models/DeepSeek-R1-Distill-Qwen-7B然后启动推理服务。先做一个简单的容器内验证docker run -it --rm \ --device/dev/dcu \ --group-add video \ -v /data/models:/models \ registry.internal/hygon/dcs-transformers:latest \ python -c import torch; print(torch.cuda.device_count())这里有个容易疏忽的点海光 DCU 在 ROCm 生态里的设备名不是cuda但在 PyTorch 经过适配之后通常会保持cuda这个逻辑名称不变。所以上面代码里torch.cuda.device_count()是能用的。如果输出显示设备数量不对先检查容器内/dev/dcu设备节点有几个。验证通过之后用 vLLM 启动服务。vLLM 启动时通过环境变量HIP_VISIBLE_DEVICES控制对哪些设备可见。这跟 CUDA 的CUDA_VISIBLE_DEVICES是一个逻辑。python -m vllm.entrypoints.openai.api_server \ --model /models/DeepSeek-R1-Distill-Qwen-7B \ --tensor-parallel-size 1 \ --max-model-len 4096 \ --gpu-memory-utilization 0.85 \ --served-model-name deepseek-r1-distill \ --port 8000几个参数简单解释一下--tensor-parallel-size默认设 1如果你要跨多张卡跑模型并行就按卡数调大--gpu-memory-utilization控制当前容器最多使用多少比例的显存默认是 0.9我通常改成 0.85给自己留一点余量--max-model-len要结合申请到的显存来算申请 16GB 的时候设 4096 是安全的如果显存小就往下调。服务起来之后直接 curl 测试curl http://127.0.0.1:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: deepseek-r1-distill, messages: [{role: user, content: 用一句话解释什么是 Kubernetes}], max_tokens: 256 }能正常返回说明 DCU 上的推理链路已经通了。5.3 将 DeepSeek 接入 CubeStudio 任务模板单机手动跑通只是第一步真正要把 DeepSeek 变成平台上的一个可重复部署的服务还需要封装成 CubeStudio 的任务模板。我的做法是构造一个自定义推理服务模板模板里指定了容器镜像、启动命令、健康检查端口以及资源请求段apiVersion: apps/v1 kind: Deployment metadata: name: deepseek-r1-distill labels: app: deepseek spec: replicas: 1 selector: matchLabels: app: deepseek template: metadata: labels: app: deepseek spec: containers: - name: vllm image: registry.internal/hygon/vllm-dcu:latest command: [python] args: - -m - vllm.entrypoints.openai.api_server - --model - /models/DeepSeek-R1-Distill-Qwen-7B - --tensor-parallel-size - 1 - --max-model-len - 4096 - --gpu-memory-utilization - 0.85 - --served-model-name - deepseek-r1-distill - --port - 8000 ports: - containerPort: 8000 resources: limits: hygon.com/vdcu: 1 hygon.com/vdcu-memory: 16 volumeMounts: - name: models mountPath: /models readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 30 periodSeconds: 10 volumes: - name: models hostPath: path: /data/models部署起来之后可以直接在 CubeStudio 的服务列表里看到这个服务的状态、日志和监控指标。这样算法工程师就不需要自己登录节点敲命令直接在平台上就能完成后续的模型更新和扩缩容操作。6. 常见问题与排错实录6.1 设备发现不了、资源报 0最常遇到的问题是 device plugin 部署了但kubectl describe node看不到任何 DCU 资源。我排查的时候总结了三个检查点。第一确认节点上是否真的能看到 DCU 设备hy-smi或者ls /dev/dcu*。第二看 device plugin 的日志重点关注它是否成功向 kubelet 注册了资源如果注册失败通常是 socket 通信权限问题。第三检查 device plugin 是否和 kubelet 的启动参数--feature-gatesDevicePluginstrue相匹配虽然新版本 K8s 默认开着但有些发行版会把它关掉。如果以上都没问题资源还显示为 0多半是 Pod 的 resources 请求写错了资源名。K8s 对 Extended Resource 的名称有严格要求必须是“域名/资源名”的格式而且资源名不能有大写字母。我之前就犯过把资源名写成hygon.com/DCU的低级错误导致调度器一直匹配不上。6.2 显存 OOM 与应用卡死vLLM 启动的时候提示 CUDA out of memory这个问题的根源往往不是模型真的大到装不下而是申请到的 vDCU 显存小于模型的峰值需求。我后来总结了一个简单的显存规划公式申请显存 模型权重大小 x 1.2 推理上下文预算 2GB 系统开销。比如 7B 模型用 FP16 存储权重大约 14GB那 16GB 显存是底线有条件的直接申请 20GB 以上。还有一类卡死问题很隐蔽同一张物理卡上跑了多个软 vDCU其中某个任务的显存申请超卖导致驱动层分配内存时长时间阻塞。遇到这种问题第一步用hy-smi看每张卡的实际显存占用第二步检查是不是存在多个任务同时申请大显存的情况。如果是就需要在调度器层面限制同一张卡的 vDCU 总显存不能只看数量。6.3 性能不达标怎么办DeepSeek 部署完之后如果你发现并发上来一点响应时间就剧烈抖动首先要检查算力隔离是不是没有生效。软 vDCU 模式下如果算力限制没有配置多个推理服务共享一个物理卡时会互相抢占计算资源表现就是 tail latency 飙升。其次是显存带宽问题。DCU 本身显存带宽很宽但访问模式不对照样会打折扣。建议推理容器启动时设置 CPU 和内存绑核尽量让推理进程的 NUMA 节点和 DCU 所在 NUMA 节点一致。我在配置/etc/systemd/system里的 CPUAffinity 和参数--cpu-rt-runtime时踩过不少坑最后是直接在 Pod YAML 里显式加了resources.limits.cpu和memory再配合spec.nodeSelector把 Pod 绑定到指定节点上效果才稳定下来。提示在 K8s 里要真正做到 NUMA 亲和建议安装 topology-manager 插件并在 kubelet 配置里开启TopologyManagerPolicy: best-effort或single-numa-node。这会显著改善多卡通信和显存访问的一致性。6.4 日志排错速查表我把这次遇到的几个高频问题整理成一个速查表方便你直接对照排查现象直接原因排查动作节点资源中无 dcu 资源device plugin 注册失败或资源名错误检查 device plugin 日志、kubelet 参数、资源名大小写Pod 一直 Pending调度器无法满足资源申请kubectl describe pod查看调度失败原因检查资源请求量容器启动即退出运行时库缺失或设备权限不足用docker run -it交互进入容器手动执行hy-smi检查库路径推理时提示 HIP 错误多卡之间通信初始化失败检查HIP_VISIBLE_DEVICES是否指向正确卡号服务启动成功但请求超时健康检查配置不当或显存不足调大initialDelaySeconds查看 vLLM 启动日志同一张卡上的任务互相影响软 vDCU 算力隔离未生效检查虚拟化 agent 是否配置了算力限额最后的一点心得这次把海光 DCU 接进 K8s 和 CubeStudio我最大的感受是硬件层面其实没那么可怕真正耗时的是把整个链路串起来的那些“软件胶水”。Device Plugin 的注册、资源调度策略的调整、运行时库的注入、vDCU 的算力限制每一环都值得花时间去验证。方案选型上我也建议你在做大规模规划之前先在测试集群上把整卡、硬 vDCU、软 vDCU 三种形态全部验证一遍结合你自己的业务负载类型来决定最终的资源池划分比例。还有一个小技巧想分享给准备做生产环境的朋友尽量把 Device Plugin、虚拟化 Agent、调度 Extender 这几个组件的日志采集到集中的日志系统里。DCU 相关的排错很多时候要靠日志里的蛛丝马迹把这些日志管好了后面做容量规划、性能分析、问题定位都会事半功倍。如果你也在折腾海光 DCU 接入 K8s 的适配问题希望这篇文章能帮你减少一些“从零踩坑”的成本。后面等我把 DeepSeek 的微调流程完整跑通再单独写一篇关于“国产 DCU 上做模型训练”的实操记录到时候我们再继续聊。