活动还没正式开始签到处旁边已经围了一圈人。大家都在问同一个问题手里的DCU卡能不能像NVIDIA的vGPU那样一张卡切给好几个任务用这场交流会的主办方是某国产加速芯片厂商主题是HAMi在DCU上的软件虚拟化从基础概念一路讲到实际落地。我全程记了笔记会后也和不少做平台的同学聊了聊这篇算是一份比较完整的回顾给当时没到场、或者到场了但没来得及记的朋友。先说结论DCU的软件虚拟化并不是把一张卡“分成好几张卡”那么简单它真正要解决的是两件事——怎么把空闲资源安全地共享出去以及共享之后怎么保证每个任务不互相拖垮。前者靠调度和显存切入后者靠算力限制和隔离策略。整场分享其实是围绕这两条线展开的。1. 一场Meetup为什么会聊“从基础到实践”1.1 现场的真实需求利用率太低排队太长开场没多久主持人让大家做了一圈自我介绍结果我发现现场大概一半人来自AI平台组或者基础架构组另一半是算法团队的负责人。有个同学提出问题的方式很直接“我们手上有八张DCU卡二十个训练任务在排队但GPU平均利用率只有百分之三十几。任务都在等卡卡大部分时间又闲着这个局面怎么破”这个问题其实特别典型。训练任务对显存的要求差别很大有的任务只要4GB就能跑有的任务必须占满整张卡的80GB。如果按“一个任务独占一张卡”来规划小任务会把大卡浪费得很厉害。虚拟化想做的事情很简单把一张物理卡的时间和空间都拆开按需分配而不是按卡分配。所以这场Meetup的第一个价值点就是把“你为什么需要虚拟化”这个问题讲透了。不是赶时髦而是资源利用率这个账算不过去。1.2 GPU虚拟化到底指的是什么很多人一听到“虚拟化”就想到虚拟机、容器想到Hypervisor。但加速卡的虚拟化和CPU虚拟化不是一回事。CPU虚拟化主要靠指令翻译和特权隔离而GPU/DCU虚拟化关注的是三个资源维度显存、算力、带宽。显存维度对应空间切分相当于把一张卡的内存分成几段任务A用前20GB任务B用后20GB。算力维度对应时间切分相当于限制某个任务最多只能用整卡算力的50%哪怕它后面还有空闲算力。带宽维度对应数据传输调度这个最容易被忽略但多任务共享时显存带宽争抢往往比算力争抢更隐蔽。整场分享反复强调了一个概念虚拟化不是“把卡变慢”而是“把浪费的资源释放出来”。关键是隔离做得好不好。1.3 DCU比起NVIDIA的特殊之处用过NVIDIA的同学对vGPU、MIG、时间片这些词可能都不陌生但DCU生态里没有那么多现成的商业虚拟化方案。DCU本身兼容ROCm生态很多CUDA迁移过来的模型可以直接跑但底层工具链、调度插件、资源上报机制都比NVIDIA的成熟度要低一些。这就带来一个很现实的问题在DCU上做虚拟化不能用NVIDIA那一套现成方案但又不能自己从零造轮子。于是开源社区的方案就成了一个很自然的选择。HAMi在NVIDIA卡上已经有比较成熟的实践现在社区把DCU的支持也纳入进来了这等于把之前积累的调度经验直接复用到新的硬件平台上。我在现场的感受是基础部分虽然讲的是概念但没有停留在概念上每个概念后面都接着一个实际场景这点是这场分享比较扎实的地方。2. 先搞清楚三个基础角色调度器、设备插件、运行时注入2.1 把加速卡“出租”给多个租户的最小模型如果你在Kubernetes里跑AI任务那你通常已经见过Device Plugin。K8s本身不知道什么GPU、DCU它只认cpu、memory这种内置资源。厂商或者社区通过Device Plugin把加速卡包装成一种扩展资源上报给Kubelet这样Pod就能像申请CPU一样申请加速卡。但在虚拟化场景里光有Device Plugin还不够。因为Device Plugin上报的是“节点上有几张整卡”而虚拟化要上报的是“节点上有多少显存、多少算力可分配”。所以HAMi这类方案通常由三个关键组件构成Device Plugin负责把节点上可供虚拟化的资源数量上报给K8s比如“本节点有8张卡总共640GB显存可按需切分”。调度器扩展组件Scheduler Extender当Pod要申请“20GB显存50%算力”时默认调度器不知道该怎么选节点需要扩展组件来做过滤和打分挑出能容纳这个虚拟设备请求的节点。运行时注入组件Pod启动后需要让容器里的应用以为自己在使用一张真实的DCU卡同时又把它的显存访问限制在申请范围内这一步靠的是API拦截和底层库替换。这三个角色一拆开整个虚拟化的脉络就清楚了对外伪装成标准资源对内强制执行配额。2.2 显存隔离的实现逻辑硬隔离与软拦截现场有人问了一个很好的问题“显存切分到底是物理切还是逻辑切”这个问题很关键。物理切分通常靠硬件特性实现比如NVIDIA MIG每一块隔离的显存和算力是硬隔离的互不可见。但DCU上目前没有完全对应的硬件虚拟化单元所以更常见的做法是用户态拦截。大概工作原理是应用调用显存申请函数时底层拦截层会记录每个容器申请的显存块地址和大小并在容器视图里隐藏掉其他容器占用的显存区间。这样每个容器都觉得“我自己拥有30GB”但实际上这30GB只是物理卡显存的一个子集。这个方案的优点是实现成本低、扩展性强缺点也很明显——如果依赖的加速卡驱动本身不提供硬隔离那么极端情况下某个任务的内存越界或者驱动异常还是可能影响到同卡的其他任务。所以在生产环境里显存隔离只能算“软隔离”它不是物理级别的堡垒而是逻辑级别的隔断。我在笔记本上画了个粗糙的比喻显存隔离有点像合租房里给每个房间加了独立门锁但墙壁还是共用的。正常居住没问题但如果有人在墙上打洞就管不住了。2.3 算力限制的实现路径时间片、配额与QoS显存隔离讲完之后分享进入算力限制环节。为什么显存能切了还要限制算力因为一个训练任务如果只看显存可能申请30GB但利用率波动很大某些阶段把整卡计算单元全部占住另一些阶段几乎是空的。如果不限制算力上限一个任务可以把同卡上其他任务的性能拖到惨不忍睹。算力限制的实现方式一般有两种底层硬件配额通过驱动或者管理工具设置某个进程组的算力上限比如最多只能占50%的SM/CU单元。这种方式最可靠但依赖芯片厂商提供接口。用户态时间片机制类似操作系统调度把GPU时间切成片段按照权重分配。这种方式灵活但精细度不如硬件配额。在HAMi的DCU实践里算力限制通过计算单元配额和显存配额一起绑定进行。你会看到类似dcu.com/gpu-mem和dcu.com/gpu-core这样的资源名前者管显存大小后者管算力百分比。两者合在一起才构成一个完整的“虚拟设备”。分享里给了一个非常接地气的建议算力限制要按场景定策略不要一刀切。离线训练任务可以放宽算力上限跑得慢一点问题不大在线推理任务必须预留足够的算力并且要设置更高的优先级。这个建议在后面的实践部分又反复出现。3. HAMi为什么会被选中机制拆解与选型逻辑3.1 为什么是开源方案而不是各自造轮子“为什么用HAMi”这个问题分享者给的理由很实在团队自己做了一套内部虚拟化方案维护成本太高。具体来说自研方案要同时维护调度器、设备插件、API拦截、监控告警、版本兼容相当于一个小型创业公司的研发工作量。而HAMi已经在多个厂商的GPU架构上验证过上游社区迭代速度比自己内部维护快得多。这个是很多团队容易踩的坑。一听到“虚拟化”就觉得自己可以写一个Device Plugin不复杂API拦截也不复杂但组合起来做稳定、做到生产可用就完全是另一回事了。尤其是DCU这种还在快速迭代的硬件平台驱动版本一变自研方案就要跟着改改不完。HAMi的另一个优势是K8s原生集成。它不需要改动Pod的调度主流程不需要替换Kubelet而是在现有调度器旁边挂一个扩展组件风险小、回滚方便。对于已经有K8s集群的团队来说接入HAMi不需要推倒重来。3.2 从设备上报到资源过滤的完整链路分享里用一条请求链路把机制串了起来我整理了一下大概是这样的Pod创建声明dcu.com/gpu-mem: 20Gi和dcu.com/gpu-core: 50。默认调度器发现这个资源请求不是内置资源就调用配置好的Scheduler Extender。扩展组件读取集群里所有节点的设备信息这一信息来自各节点Device Plugin的上报过滤掉显存不足或算力配额不够的节点。调度器选中一个节点后Kubelet调用Device Plugin的Allocate接口让它为容器准备环境。Device Plugin在容器里注入相关环境变量和拦截库。容器启动后应用申请的显存会被拦截层记录并限制算力使用也被约束在配额内。这条链路的核心思想是把一切资源决策都放在调度阶段解决运行时只做强制执行。如果调度阶段把节点选错了运行时再强扭就不现实了。3.3 多卡通信和虚拟设备之间的边界问题分享后半段有一个部分很硬核多卡通信和虚拟设备的关系。这里有个很微妙的问题如果两个任务共享同一张物理卡每个任务分别申请了一半的显存那它们之间会不会因为硬件资源争抢而互相干扰答案是会所以算力限制才显得重要。但反过来如果任务A跨两张物理卡做分布式训练而这两张卡各自又被其他虚拟化任务占了一部分那通信效率就很可能打折扣。因为虚拟设备之间的通信路径和独占物理设备时的通信路径并不是完全一样的Peer-to-Peer或者RDMA直通在某些虚拟化场景下可能会被禁用或降级。分享者给了一个很务实的建议多卡通信敏感的任务尽量申请完整的物理卡不要切成碎片单卡就能跑的小任务再走虚拟化。这个建议说得很直白也很管用。4. 从基础到实践一套可复用的部署流程4.1 版本匹配最容易翻车也最容易被忽略实践部分是从“版本匹配”开始的。这一点我必须多说两句因为太多人拿到文档就照着装结果卡在版本兼容性上。HAMi对接DCU时涉及K8s版本、容器运行时版本、DCU驱动版本、ROCm版本、HAMi自身版本任何一个不匹配都可能出现设备无法上报或者调度异常的情况。一个可参考的版本组合是组件建议版本备注Kubernetes1.24及以上需要支持Device Plugin的标准接口容器运行时支持Device Plugin即可常见运行时均可DCU驱动5.10及以上需要支持算力配额接口ROCm5.7及以上依赖底层运行时库HAMiv2.x及以上需要包含DCU支持版本这里我不展开截图因为各个版本迭代太快了但你只要记住一条铁律先查官方Release Note确认DCU支持列表再动手部署。我见过太多人抱怨“部署完没有虚拟GPU资源”结果查到最后是装了一个还不支持DCU的旧版本。4.2 部署HAMi与配置DCU资源池实际操作分三步走。第一步给节点打标签。HAMi默认只会管理打了特定标签的节点避免影响集群里其他GPU节点。比如kubectl label node node-name dcuhami第二步部署控制面和设备组件。如果你使用Helm流程会简化很多如果不用Helm也可以直接应用YAML。核心是起一个调度器扩展组件的Deployment和每个节点上的DaemonSet。helm install hami hami/hami -n kube-system --set schedulerNamehami-scheduler第三步验证资源上报。这是最容易漏的一步。部署完成后不能直接跑任务要先确认Kubelet真的看到了可切分的资源kubectl describe node node-name | grep -A 10 Capacity如果看到类似dcu.com/gpu-mem这样的资源说明设备上报成功。这一步过了整个控制面基本就是通的。4.3 提交第一个虚拟GPU任务并验证隔离效果资源池就绪之后提交任务就很直白了。一个申请20GB显存和50%算力的Pod大概长这样apiVersion: v1 kind: Pod metadata: name: dcu-virtual-test spec: containers: - name: train image: rocm/training:latest resources: limits: dcu.com/gpu-mem: 20Gi dcu.com/gpu-core: 50启动之后建议做三件事验证隔离效果看任务是否正常调度到打了标签的节点上。进入容器执行类似rocm-smi的命令看看应用看到的显存是不是20GB。在宿主机上观察空闲任务和虚拟任务的算力干扰情况确认50%的算力上限真的生效了。现场演示的时候有个细节我印象很深第一次跑测试的人很容易忘了在limits里同时写dcu.com/gpu-mem和dcu.com/gpu-core只写一个。这时候HAMi默认会取一个全量值表现就是明明申请了20GB调度器却把它当成要占满整卡来处理。所以这两个字段要同时出现缺一个都会出问题。5. 实践中遇到的三个典型问题与完整排查链路5.1 Pod一直Pending节点资源没上报的定位过程分享者讲了一个真实案例有个团队部署完HAMi之后提交的Pod始终是Pending状态怎么看都找不到原因。排查过程很典型第一步看Pod事件。kubectl describe pod显示没有节点满足条件但看节点资源又没有异常。第二步看调度器扩展组件的日志。如果调度器扩展组件根本没收到请求那问题就不在调度策略而在更上游。第三步发现节点上Device Plugin的Pod反复重启。查看日志后发现Device Plugin启动时去探测DCU设备结果因为驱动版本太低设备列表返回为空。最后解决方式是升级驱动重启节点上的DaemonSet资源马上就上报了。这个案例的参考价值在于很多调度类问题根因不在调度器而在设备发现层。遇到Pending先确认Device Plugin是不是真的看到了物理卡再看调度器扩展组件有没有收到请求最后才检查策略配置。顺序乱了排查效率会很低。5.2 显存限制生效了但算力限制不生效第二个案例更常见Pod起来了显存也确实被限制在20GB但GPU利用率一直跑满100%说好的50%算力上限完全没反应。这个问题排查起来比第一个要深一层。显存限制靠的是用户态拦截实现相对简单但算力限制需要驱动层的监控和配额模块介入。如果驱动版本没有启用算力管理相关的组件或者加载驱动时没有打开对应参数用户态层面再怎么截也拦不住底层的计算单元调度。分享者给了一个很直接的排查建议先在宿主机上用管理工具确认能否看到实时的算力使用数据。如果宿主机的管理工具都看不到每个进程的算力占用那大概率是驱动侧的算力统计模块没启用这时候去调虚拟化的配置是没有意义的。这个案例也解释了我前面说的算力隔离比显存隔离难得多它依赖的层次更深。在生产环境上算力限制之前一定要先在宿主机层面验证驱动是否支持。5.3 设备重启后显存状态不一致回收机制的边界第三个案例来自一个跑了一段时间的集群节点上的DCU卡重启过之后会发现节点上报的空闲显存比实际少了一块。比如总共有80GB显存节点上报只剩下60GB可分配重启也没有自动恢复。这个问题的本质是显存分配的回收机制没有覆盖到异常退出场景。正常情况Pod退出时Device Plugin的回收流程会释放显存记录。但如果节点重启、容器被强制杀掉或者引导程序崩溃回收流程可能没跑完设备插件重启后重新计算可用显存时把旧的分配状态残留了下来。解决方案并不复杂清掉节点上残留的设备插件缓存状态重启Device Plugin Pod或者执行显存重置操作。关键是意识到虚拟化层的状态是易失的需要依赖一套回收机制来兜底。上了生产之后这类问题会时不时冒出来所以一定要提前了解重置换设备状态的操作路径。6. 现场讨论里我认同的三个判断6.1 “虚拟化会让性能变差”要分场景看现场有个同学提出质疑“虚拟化之后任务性能肯定会有损耗吧”这个观点不能一概而论。如果一张卡本来只有一个小任务在跑虚拟化当然没有意义还白白加了拦截层。但如果卡上有闲置资源虚拟化带来的提升远大于损耗。对延迟极度敏感的在线推理可以通过预留算力和优先级机制来保证服务质量对离线训练即使损失几个点的性能换来的资源利用率提升也完全划算。6.2 先用好显存隔离再逐步上算力限制这一点我特别认同。刚才说的部署流程、问题排查大部分复杂度集中在算力限制这一层。一个新接手的团队我建议先从纯显存切分开始跑把一个“多任务共享一张卡”的最小场景跑通确认调度、隔离、回收都正常了再一步步开启算力限制。一步到位往往会同时遇到多类问题排查起来分不清因果关系。6.3 开源社区路线值得长期押注最后说说我对HAMi这个方案的整体感受。国产加速卡的硬件性能这些年追得很快但生态和工具链的成熟度不是一天能补上的。像HAMi这种由社区驱动、支持多种硬件平台、又愿意把DCU纳入主线的方案等于帮所有使用者摊薄了适配成本。你在社区踩过的坑下一个人不用再踩上游修复了驱动适配你升级一下版本就能获得。这类Meetup让我比较触动的一点是现在已经很少人问“DCU能不能做虚拟化”了问的都是“怎么做隔离更稳”“怎么在共享场景下保证SLA”。这说明整个行业对国产加速卡的使用正在从“能用”走向“用好”。如果你手里正好有DCU资源又面临利用率和排队的矛盾我的建议是不要犹豫先找一张测试卡把一个最小的共享场景跑通。跑通之后你会发现后面所有关于调度的优化、关于配额的设计有了实际的抓手就不再是纸上谈兵了。