如果你在跑一个Kubernetes平台,大概率至少有一块Grafana面板,用来看工作负载的资源占用。它显示容器申请了多少、实际用了多少,想找出那个申请了四个核却只用了半个核的部署,并不难。按命名空间拆开看,还能大致判断哪个团队在消耗什么。这套方法在CPU和内存上用了好多年,基本够用。

然后大家开始把工作负载往GPU上搬,很多原本以为能顺理成章延续下来的东西,全都断了。你在CPU和内存上习以为常的几乎每一项可见性,到了GPU这里,都得自己动手建。自己的指标、自己的精度、自己的分账规则。最扎心的地方在于,GPU恰恰是账单上最贵的那一行。

打开网易新闻 查看精彩图片

指标得自己造

CPU和内存这块,内核已经帮你按容器追踪用量,拿到每个Pod的数字几乎是免费的。GPU不给这个。所有人起步都会用的工具,NVIDIA的DCGM exporter,默认是在物理设备层面汇报的。一个Pod独占一块GPU时没问题,可一旦你开始跨Pod共享一块GPU,用时间切片或者MPS,DCGM会给共享这块GPU的每个Pod返回同一个值。

三个Pod共享一块GPU,三个看起来都在用满整块卡,加起来就是300%的GPU——一块根本不存在的卡。时间切片或MPS模式下,驱动只为整块GPU暴露一组计数器,DCGM没有按Pod的数据可以归属,也就无法区分哪个工作负载消耗了什么。数字画在图上依然很漂亮,可团队基于这些明显错误、又极难察觉的数值,建起了完整的看板和报表。

算账的数学也得自己定

CPU这边我们习惯了毫核、超卖、突发,CPU限额顶多让你被限流,内存限额会让你被OOM杀掉,但GPU把这些基本都推翻了。GPU在K8s里是一个整数资源,nvidia.com/gpu: 1。你申请整块GPU,request和limit必须相等,调度器层面没有超卖。推理需要的分数共享,是在调度器底下通过时间切片或MIG实现的,Kubernetes的请求对不上。

于是最基本的rightsizing前提——拿工作负载申请的和实际用的做对比,把差额收回来——在GPU上起步阶段几乎不存在。你得先搞清楚一个工作负载到底拿到了多少GPU容量,才谈得上浪费不浪费。

护栏也得自己搭

NVIDIA容器运行时会看一个叫NVIDIA_VISIBLE_DEVICES的环境变量,基础CUDA镜像默认把它设成all。正常配置下,设备插件会覆盖它,只注入Pod实际分配到的GPU。但如果没有任何东西覆盖它,或者容器运行时被配置为尊重容器里的这个环境变量,一个Pod就可能看到并抓走机器上所有的GPU,不管Kubernetes以为分出去了什么。

调度器记录的分配,和硬件上真正跑的,可能差得很远。想要真实数字,你得去看进程层面发生了什么,再和Kubernetes以为它做了什么对齐。

利用率的定义也得自己下

CPU图说80%,你基本知道自己在看什么。GPU利用率要模糊得多。常用指标告诉你的是,在采样窗口的某个片段里,有kernel在GPU上跑。它不会告诉你实际烧掉了多少算力或内存带宽。你能看到GPU卡在100%利用率,而计算几乎没干什么活。很多时候最先耗尽的是内存,因为像vLLM这样的框架启动时就把大部分GPU抓走,不管自己需不需要。

早点想清楚哪个信号对你的工作负载有意义——是计算占用、内存占用、带宽还是功耗。只盯着那个大数字,可能得出错误结论。

归属也得自己算

NVIDIA的NVML库会告诉你,一块GPU上每个进程占了多少内存,你可以把每个进程追溯回拥有它的Pod和团队。数字可靠,但有一个星号:它们加起来不会等于整卡总量,因为驱动给自己留了一块内存,不属于任何工作负载。

计算这块就崩了,值得弄明白为什么。想象一个共享厨房:内存是台面,你能看到什么食材放在哪。计算是灶台,大家以毫秒为单位轮流用。NVIDIA给你的不是“每个厨师分到了多少灶台时间”,更像是一面每秒被检查很多次的旗子,问“这个厨师现在在干活吗”。两个人同时干活,两面旗子几乎一直显示“是”,于是你得到两个看起来都90%忙的任务,这是真的,也什么都没告诉你谁干得更多。

这种测量只在一种配置下可用:GPU严格地一次只在一个任务之间轮流,这时轮询DCGM的accounting模式或nvidia-smi的PID级采样,可以近似估算每个任务的份额。它在任务真正并行运行的配置下就失效了,而这恰恰是人们为了更好利用率会选的配置。如果两个团队共享一块GPU,今天没有任何现成手段能告诉你谁烧了70%、谁烧了30%。所以实际的做法是,按内存来计费,因为那里的数据站得住,把计算当作一个信号,用来驱动chargeback。现实是,当你想在团队之间实现公平,准确性确实重要。