深夜的后厨,主厨老 K 接到一张神秘订单:十道菜要在同一台“限量版烤箱”里完成,而且每道菜要求的火候互不相同。普通灶台到处都是,可这台烤箱全城只有几台,谁先用、用多久、能不能把烤箱隔成小格子分给不同的人——这些问题,正是 Kubernetes(K8s)在管理 GPU 时每天要回答的。GPU 之于 AI 训练,就像那台限量版烤箱之于后厨:昂贵、稀缺、抢手。K8s 里的 GPU 调度插件,就是那位负责登记烤箱、安排档期、决定“谁在几点用哪一格”的烤箱管家。这篇文章,我们跟着老 K 的后厨,把容器算力编排这出戏从头看一遍。

岗位关联

掌握 GPU 调度插件,你就能让昂贵的 AI 算力从“买得起用不上”变成“每一块都发光”,这是任何一家 AI 公司降本增效都绕不开的核心技能。

目录
  1. 可视化演示
  2. 设备插件:烤箱管家的登记册
  3. 调度主流程:从点单到上菜的六步
  4. 整卡分配:一人一炉的奢侈与浪费
  5. 共享调度:把烤箱切成格子、排成档期
  6. 显存隔离:一块显卡的“公寓”怎么管
  7. 抢占机制:VIP 客人来了怎么办
  8. 拓扑亲和性:多卡协作要住得近
  9. 混部实践:白天推理,夜间训练
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

设备插件:烤箱管家的登记册

后厨里那台限量版烤箱,并不会自己开口说话。主厨老 K 要知道它今天在不在、能不能用、能烤多大尺寸的食材,全靠一位专门负责打理它的“烤箱管家”。在 K8s 集群里,这个管家就是设备插件(Device Plugin)。GPU 是一种异构硬件,CPU 和内存可以用“核数”“吉字节”这种通用单位上报,但 GPU 的规格五花八门——显存多大、算力多强、是否支持某些特性,调度器根本听不懂。设备插件就是硬件与调度器之间的“翻译官”。

设备插件以 DaemonSet 方式跑在每个 GPU 节点上,它做两件核心的事。第一件叫 ListAndWatch:它持续盯着这台机器上的 GPU 数量与健康状态,一旦有 GPU 掉线或恢复,就立刻通知 kubelet(节点上的“分店店长”)。第二件叫 Allocate:当调度器决定把一个 Pod 放到这个节点时,插件负责真正把 GPU 设备“交到”容器手里——告诉容器运行时该挂载哪个设备文件、注入哪些环境变量。NVIDIA 官方提供的 nvidia-device-plugin,就是这样一个成熟管家。

调度主流程:从点单到上菜的六步

把整个调度过程拆开看,就像后厨处理一道菜:客人点单 → 前台记录 → 排菜总管安排灶台 → 厨师拿到食材和灶台 → 开火。K8s 里的 GPU 调度主流程同样清晰。

第一步,设备插件启动后,通过 Unix 套接字向本节点的 kubelet 注册,说:“我是 GPU 管家,我这有 4 块卡。”第二步,kubelet 把这个信息上报给集群大脑 API Server,节点容量里就多了 nvidia.com/gpu: 4 这一项。第三步,用户创建 Pod 时,在资源请求里写明“我要 1 块 GPU”,这相当于客人在菜单上勾选了“需要烤箱”。第四步,调度器(kube-scheduler)遍历所有节点,找到 GPU 余量足够的那个。第五步,调度器把 Pod 绑定到节点,kubelet 收到指令后调用设备插件的 Allocate 接口。第六步,插件把 GPU 设备文件挂载进容器,容器启动,烤箱正式点火。

这个过程看起来简单,但每个环节都有讲究。比如调度器在判断“GPU 余量”时,依据的是节点上剩余的可分配数量,而非物理总量——因为有些卡可能已被其他 Pod 占用。在 Kubernetes 中,GPU 是扩展资源(Extended Resource),它有一条铁一样的规矩:只能声明整数数量。你无法申请 0.5 张 GPU。

可调度条件:∑i∈Podsrequest(gpui)≤allocatable(gpu)−allocated(gpu)\text{可调度条件:}\quad \sum_{i \in \text{Pods}} \text{request}(gpu_i) \le \text{allocatable}(gpu) - \text{allocated}(gpu)
这个公式说明:一个节点上所有 Pod 申请的 GPU 总数,不能超过该节点可分配的 GPU 数量。扩展资源的整数语义,让每次申请都必须是“一整块”。
  1. 请求求和先把这个节点上所有 Pod 申请的 GPU 数量加起来。比如三个 Pod 各要 1 块,总和就是 3。
    ∑i∈Podsrequest(gpui)\displaystyle \sum_{i \in \text{Pods}} \text{request}(gpu_i)
  2. 计算余量用节点的可分配总量减去已经分配给现有 Pod 的数量,得到剩余可用数。
    allocatable(gpu)−allocated(gpu)\displaystyle \text{allocatable}(gpu) - \text{allocated}(gpu)
  3. 比较判断只有当“新请求 + 已分配 ≤ 总可分配”时,这个节点才放得下新 Pod。任何一侧超了,调度器都会把节点排除。

整卡分配:一人一炉的奢侈与浪费

老 K 后厨有 4 台烤箱,如果每道菜都需要完整一台,那当然谁来了给谁一台,互不干扰。这就是整卡分配——K8s 默认的 GPU 调度方式。整卡分配的优点非常直观:隔离性好,一个任务不会影响另一个;调度逻辑简单,调度器只需要做减法。但它的缺点同样致命:显存浪费。

假设一台 A100 GPU 有 80GB 显存,你的推理模型只需要 12GB 显存。在整卡模式下,这个 Pod 也必须独占一整张 A100,剩下的 68GB 全部闲置。对于动辄数万美元一块的 GPU,这是巨大的浪费。这在后厨里就像:为了热一碗汤,把整个烤箱都占用了,其他所有菜都得等着。

正是在这种浪费的驱动下,社区开始探索“把一个烤箱当多个用”的办法——也就是共享调度。整卡分配是基线,共享机制是优化。理解这一点很重要:K8s 本身不会自动知道 GPU 可以共享,它需要额外的插件和配置才能实现。

显存利用率=实际使用的显存GPU 总显存\text{显存利用率} = \frac{\text{实际使用的显存}}{\text{GPU 总显存}}
如果实际使用 12GB、总量 80GB,利用率只有 15%。整卡模式下,这 15% 就是常态。
  1. 实际使用显存指容器内进程真正申请并占用的显存大小,比如 12GB。
    实际使用的显存\text{实际使用的显存}
  2. GPU 总显存一张卡的物理显存总量,如 A100 的 80GB。
    GPU 总显存\text{GPU 总显存}
  3. 算比值两者相除就得到显存利用率。这个值越低,说明浪费越严重,越需要共享调度介入。

共享调度:把烤箱切成格子、排成档期

共享调度的思路,就像老 K 对烤箱的两种改造方案。方案一:排档期(时间分片)。 三份菜轮流用同一台烤箱,你烤 20 分钟,我烤 20 分钟,他再烤 20 分钟。在 GPU 领域,这就是 Time-slicing 时间分片:多个 Pod 共享一张 GPU,按时间片轮转执行。它的实现成本低,几乎不用改硬件,NVIDIA 官方插件通过环境变量就能开启。但缺点明显:如果几个 Pod 同时有计算需求,它们要排队,单个任务的完成时间会变长。

方案二:切格子(MIG)。 像把一个大烤箱内部砌上隔板,变成 4 个小烤箱,每个小烤箱有独立的温控和空间,互不干扰。这就是 Multi-Instance GPU(MIG):在 A100、H100 等新架构 GPU 上,硬件层面把 GPU 切分成多个独立实例,每个实例拥有独占的计算核心和显存带宽。MIG 的隔离性远好于时间分片,但实例大小是固定的(如 1g.5gb、2g.10gb 等规格),灵活性略差。

还有方案三:MPS(Multi-Process Service),它更像是“烤箱里站了多个厨师,大家共用火源但各做各的菜”。MPS 允许多个进程并发使用 GPU 的计算单元,能显著提高利用率,但显存依然是共享的——一个进程吃光显存,其他进程就会报错。三种方案没有绝对优劣,取决于场景:追求极致隔离选 MIG,追求简单共享选时间分片,追求计算并发但不在乎显存风险选 MPS。

GPU 利用率=实际计算时间总时间窗口×100%\text{GPU 利用率} = \frac{\text{实际计算时间}}{\text{总时间窗口}} \times 100\%
时间分片和 MPS 都是靠“填满空闲时间”来提高这个比值。MIG 则是靠“并行多个实例”来增加分母上的有效产出。
  1. 实际计算时间在一段时间窗口内,GPU 真正在跑计算(而不是空转等待)的时长。
    实际计算时间\text{实际计算时间}
  2. 总时间窗口你观察的一整段时间,比如 1 小时。
    总时间窗口\text{总时间窗口}
  3. 百分比换算两者相除再乘 100%,得到利用率。整卡模式下,GPU 常因等待数据而空转,利用率可能只有 30%;共享后,不同任务的空隙互相填充,利用率可提升至 80% 以上。

显存隔离:一块显卡的“公寓”怎么管

共享 GPU 带来的最大隐患,是显存安全。后厨里,如果两道菜同时放进一台烤箱,彼此的油汁和酱料可能会串味,甚至一个烤糊了把烤箱弄脏,另一道菜也遭殃。在 GPU 世界里,一个 Pod 如果发生显存溢出(OOM),可能导致整张卡的健康状态异常,其他共享这张卡的 Pod 跟着遭殃——这就是“一卡崩、全卡崩”。

因此,显存隔离是混部的生命线。MIG 提供了硬件级的显存隔离:每个实例的显存是物理隔离的,一个实例 OOM 不会影响其他实例。这是它最核心的价值。相比之下,MPS 不隔离显存——所有 MPS 客户端共享同一块显存池,没有任何边界保护。时间分片更不用说,它连计算都是轮流用,显存完全共享。

在工程实践上,选择哪条路取决于你的 GPU 型号和风险偏好。如果你跑的是对外提供服务的在线推理,业务不可中断,那就必须用 MIG 这类硬隔离方案;如果你跑的是离线批处理任务,容忍偶尔重试,时间分片也够用。总之,混部之前,先问显存安全不安全。

单实例显存上限=GPU 物理显存实例数量×隔离系数\text{单实例显存上限} = \frac{\text{GPU 物理显存}}{\text{实例数量}} \times \text{隔离系数}
MIG 的隔离系数接近 1(物理隔离),而 MPS 没有这个上限(共享池),所以系数为 0——没有隔离。
  1. GPU 物理显存整张卡的显存总量,如 A100 的 80GB。
    GPU 物理显存\text{GPU 物理显存}
  2. 实例数量MIG 切分的实例个数,比如切成 4 个。
    实例数量\text{实例数量}
  3. 隔离系数表示隔离的严格程度。MIG 为 1(硬隔离),MPS 为 0(无隔离)。系数越高,单实例可用的显存越有保障。

抢占机制:VIP 客人来了怎么办

老 K 的后厨有个规矩:如果一位 VIP 客人突然到店,点了一份必须立刻烤的招牌菜,而后厨烤箱全满,那就只好把正在烤的、没那么紧急的菜先撤下来,让给 VIP。这就是抢占式调度在 GPU 场景中的写照。

K8s 中的 Pod 可以设置 priorityClassName,即优先级。当高优先级 Pod 因资源不足而无法调度时,它会触发抢占:调度器先尝试找到某个节点,驱逐(Evict)一些低优先级的 Pod,把 GPU 资源腾出来给高优先级的 Pod。被驱逐的 Pod 会进入终止流程,如果是可重试的批处理任务,它们会在其他节点重新排队。

为什么说“抢占需谨慎”?因为驱逐不是免费的。被驱逐的 Pod 如果正在做模型训练,它的进度可能丢失、需要从头再来;如果是推理服务,则可能直接导致线上请求失败。所以,合理的做法是:只有真正的紧急任务(比如线上故障修复、高优训练任务)才设置高优先级,日常任务一律平级。抢占如同一把双刃剑:用好了保业务,用滥了毁生态。

调度优先级=priorityClass 数值×权重−驱逐代价\text{调度优先级} = \text{priorityClass 数值} \times \text{权重} - \text{驱逐代价}
调度器在做抢占决策时,不仅要看优先级数值,还要衡量“驱逐这个 Pod 的代价”——包括进度丢失、服务中断等。
  1. priorityClass 数值每个优先级类有一个整数,数值越大越优先。K8s 默认的 system-cluster-critical 优先级就极高。
    priorityClass 数值\text{priorityClass 数值}
  2. 权重可以理解为该业务的重要程度系数,比如核心交易 ×10,离线分析 ×1。
    ×权重\times \text{权重}
  3. 减去驱逐代价驱逐一个正在跑长任务的 Pod,损失可能非常大,所以要从总分里扣掉这部分代价。只有当净优先级远高于目标 Pod 时,抢占才划算。
    −驱逐代价- \text{驱逐代价}

拓扑亲和性:多卡协作要住得近

当 AI 模型大到一张 GPU 装不下,就需要把模型切到多张 GPU 上并行训练,这就产生了多卡通信。后厨里,两位厨师要配合做一道菜,如果他们在同一个灶台间,喊一声就能沟通;如果相隔两层楼,就得靠对讲机,效率大打折扣。GPU 之间也一样:同一节点内的显卡通过 PCIe 或 NVLink 直连,带宽每秒可达 600GB;跨节点的 GPU 通信则要经过网络,带宽往往只有几十 GB,差了不止一个数量级。

拓扑亲和性(Topology Affinity) 要解决的就是这个问题:调度器为多卡任务分配 GPU 时,尽量把它们放在同一节点上(Node Affinity),甚至同一节点的相邻插槽上,以减少跨节点通信。这在 K8s 里通过 nodeSelector、nodeAffinity 以及自定义调度插件来实现。

我们用一个效率公式来看通信对训练的影响:假设模型并行度是 k,每次迭代需要 k 次通信,通信量占总时间的比例是 c,那么多卡加速的实际效率并不是 k 倍,而是小于 k 倍。只有当通信开销足够低(c 小),加速比才接近理想值。因此,让多卡住得近,就是让训练跑得快。

多卡加速效率=11+c×(k−1)\text{多卡加速效率} = \frac{1}{1 + c \times (k - 1)}
其中 k 是 GPU 数量,c 是通信时间占比。k 越大、c 越大,效率下降越明显。同节点 NVLink 能让 c 从 0.3 降到 0.05,效率提升巨大。
  1. k 是 GPU 数量例如 8 卡训练时 k=8。理想情况下,8 卡应该比单卡快 8 倍,但实际上到不了。
    kk
  2. c 是通信时间占比假设一次迭代里,30% 的时间花在 GPU 之间的数据同步上,则 c=0.3。
    cc
  3. 代入公式如果 k=8、c=0.3,效率 = 1 / (1 + 0.3×7) ≈ 0.32,只有理想值的 32%。若用 NVLink 把 c 降到 0.05,效率 = 1 / (1+0.05×7) ≈ 0.74,接近 74%。这就是拓扑亲和性的价值。

混部实践:白天推理,夜间训练

最后我们把所有零件组装起来,看看真实世界里的“算力混部”长什么样。所谓混部(Co-location),指把不同类型的工作负载放在同一批 GPU 上运行,让每一块 GPU 都物尽其用。最常见的组合是“在线推理 + 离线训练”:白天流量高峰,GPU 优先服务推理请求;深夜流量低,训练任务大规模占用算力。

为了实现混部,工程团队通常组合使用时间分片与 MIG。比如一台 8 卡 A100 服务器,白天用 MIG 把其中 4 卡切成 16 个推理实例,支撑在线服务;另外 4 卡用时间分片跑小规模的增量训练。到了夜间,MIG 实例缩容,所有 GPU 切换给大模型训练。这套机制背后依赖的是 K8s 的弹性调度能力:通过自定义调度器或 Device Plugin 的扩展,实现“按需分配、动态伸缩”。

混部的收益非常可观:GPU 平均利用率从 30% 提升到 85%,成本几乎下降一半。但挑战也很大——显存隔离、优先级抢占、拓扑亲和性,每一步都需要精细配置。正如老 K 说的:“烤箱还是那台烤箱,但会排班的主厨,能同时服务三倍客人。”这就是 K8s GPU 调度插件的终极价值:让容器算力编排,从“一台一客”进化为“满座翻台”。

混部收益=混部后 GPU 利用率混部前 GPU 利用率=85%30%≈2.8\text{混部收益} = \frac{\text{混部后 GPU 利用率}}{\text{混部前 GPU 利用率}} = \frac{85\%}{30\%} \approx 2.8
混部不是魔法,而是把原来浪费的空闲时间、空闲显存重新组织起来,获得接近 3 倍的算力效率提升。
  1. 混部前利用率整卡分配下,GPU 大量时间空转,典型值 30%。
    混部前 GPU 利用率=30%\text{混部前 GPU 利用率} = 30\%
  2. 混部后利用率通过时间分片、MIG 和错峰调度,利用率可提升到 85%。
    混部后 GPU 利用率=85%\text{混部后 GPU 利用率} = 85\%
  3. 算比值85% ÷ 30% ≈ 2.8,意味着用同样的硬件,完成了近 3 倍的工作量。这就是公司降本增效的底气。

AI 应用场景

在线推理与离线训练的混部调度

阶段:模型部署与运维(推理服务 + 训练任务)

解决的问题:传统方式下,推理服务单独占用一批 GPU,夜间无流量时 GPU 大面积空闲;训练任务又另外申请 GPU,两边都浪费。AI 方案通过 K8s 设备插件暴露 GPU 资源,结合时间分片和 MIG 实现混部——白天推理 Pod 高优占用 GPU,夜间训练 Pod 通过优先级抢占获得算力,GPU 利用率从 30% 提升到 85%。

具体案例:一家内容推荐公司的线上推理服务需要 4 张 A100,离线 CTR 模型训练需要 4 张 A100。混部后,8 张卡白天跑推理、夜间跑训练,按 K8s PriorityClass 动态切换,硬件采购成本节省了约 45%,推理延迟仍然稳定在 20ms 以内。

大模型多卡并行训练的拓扑优化

阶段:模型训练

解决的问题:训练千亿参数大模型需要 32 张甚至更多 GPU,跨节点通信会严重拖慢训练速度。利用 K8s 节点亲和性将 Pod 调度到同一节点,配合 NVIDIA NVLink 直连,通信开销大幅降低。

具体案例:某大模型团队用 K8s 调度插件对 8 卡 A100 节点做拓扑感知调度,让训练 Pod 优先占满单节点。对比随机调度,每轮迭代时间从 3.2 秒降到 1.1 秒,训练吞吐提升近 3 倍。

MIG 切分实现多租户 GPU 隔离

阶段:资源管理与多租户服务

解决的问题:多个业务团队共享 GPU 集群时,互不信任、互相影响。通过 MIG 把一张 A100 切分为多个实例,每个团队获得独立显存与算力,既共享硬件又互不干扰。

具体案例:一家云厂商在其 ML PaaS 平台上,用 MIG 把 A100 切分为 1g.10gb 实例,按实例粒度售卖 GPU 算力。租户间完全隔离,单实例故障不影响其他租户,客户投诉率下降了 70%。

Python 演示

python84 行
class GpuDevice:    """模拟一张 GPU 卡."""    def __init__(self, gid, memory_gb):        self.gid = gid        self.memory_gb = memory_gb        self.busy = False     def allocate(self, pod):        if self.busy:            raise RuntimeError("GPU {} is busy".format(self.gid))        self.busy = True        self.pod_name = pod.name        return True     def free(self):        self.busy = False  class DevicePlugin:    """模拟设备插件:登记 GPU 并暴露给调度器."""    def __init__(self, node_name, gpus):        self.node_name = node_name        self.gpus = gpus        self.resource_name = "nvidia.com/gpu"     def list_and_watch(self):        """上报可用 GPU 数量."""        return sum(1 for g in self.gpus if not g.busy)     def allocate(self, pod):        for g in self.gpus:            if not g.busy:                g.allocate(pod)                return g.gid        return None  class Pod:    """模拟一个请求 GPU 的工作负载."""    def __init__(self, name, gpu_request, priority):        self.name = name        self.gpu_request = gpu_request        self.priority = priority  class Scheduler:    """模拟 kube-scheduler 的 GPU 调度逻辑."""    def __init__(self, plugins):        self.plugins = plugins     def schedule(self, pod):        best_node = None        best_free = -1        for plugin in self.plugins:            free_count = plugin.list_and_watch()            if free_count >= pod.gpu_request and free_count > best_free:                best_free = free_count                best_node = plugin        if best_node:            gpu_id = best_node.allocate(pod)            print("Pod {} 调度到 {} 的 GPU {}".format(                pod.name, best_node.node_name, gpu_id))            return True        print("Pod {} 等待:所有节点 GPU 不足".format(pod.name))        return False  def main():    plugin_a = DevicePlugin("node-a", [GpuDevice(0, 40), GpuDevice(1, 40)])    plugin_b = DevicePlugin("node-b", [GpuDevice(0, 80)])    scheduler = Scheduler([plugin_a, plugin_b])     pods = [        Pod("训练任务-1", 1, 100),        Pod("训练任务-2", 1, 100),        Pod("推理服务-高优", 1, 999),        Pod("离线任务-低优", 1, 10),    ]    for p in pods:        scheduler.schedule(p)  if __name__ == "__main__":    main()
目的:模拟 K8s 中 GPU 设备插件上报资源、调度器分配 GPU 的完整流程,展示“高优任务优先获得 GPU、低优任务等待”的调度逻辑。
  1. class DevicePlugin对应真实世界中的 NVIDIA Device Plugin,负责登记 GPU 容量和分配设备。
  2. list_and_watch()模拟 ListAndWatch 接口,返回当前可用 GPU 数量,供调度器决策。
  3. allocate(pod)模拟 Allocate 接口,找到一块空闲 GPU 并标记为 busy。
  4. class Scheduler模拟 kube-scheduler 的资源匹配逻辑。
  5. if free_count >= pod.gpu_request and free_count > best_free调度核心:节点空闲 GPU 必须不少于请求量,且优先选空闲最多的节点(简单负载均衡)。
  6. Pod("推理服务-高优", 1, 999)高优先级推理服务优先调度,体现抢占式调度的优先级语义。

常见误区

误区刚开始学 GPU 调度时,看到 MPS 和 MIG 都能让多个进程共享 GPU,就以为它们都提供了显存隔离,随便选一个都安全。

正解这是危险的误解。MIG 是硬件级隔离,每个实例有独立的显存和计算单元,一个实例崩溃不影响其他实例;而 MPS 只是软件层面的并发控制,所有进程共享同一块显存池,一个进程吃光显存会导致同卡其他进程 OOM。做混部时,如果对外提供在线服务,必须用 MIG 或专门的显存隔离机制。

为何常见:因为两者都叫作“GPU 共享”,教程截图看起来也差不多,初学者很容易把它们当成同一种东西的两种实现。

误区以为时间分片既然把 GPU 时间切成多段分给不同 Pod,那每个 Pod 的显存也自然被切开了,不会互相影响。

正解时间分片只切分“计算时间”,不切分“显存空间”。多个 Pod 共享同一块物理显存区域,A Pod 的显存溢出同样可能拖垮同卡的其他 Pod。时间分片的主要目的是提高计算利用率,而不是提供隔离安全边界。

为何常见:人们把 CPU 时间片的概念平移到了 GPU 上,但忘记了 GPU 显存和计算核心是两个维度的资源。

误区以为装了 NVIDIA Device Plugin 后,GPU 的分配决策都由这个插件完成,把它当成了调度器。

正解设备插件只负责两件事:向 kubelet“报告” GPU 数量和健康状态、在 Pod 被调度到节点后“执行”设备挂载。真正决定“Pod 放到哪个节点”的是 kube-scheduler(调度器)。设备插件不参与全局决策,它是节点上的“执行者”而非“决策者”。

为何常见:因为配置 GPU 的第一步就是装设备插件,大家误以为一切都是它在管,忽略了调度器的存在。

误区为了确保任务不被抢走,给所有 Pod 都设置最高优先级,以为这样最保险。

正解高优先级是一把双刃剑。太多高优先级 Pod 会导致频繁抢占,低优任务永远跑不完,系统整体吞吐反而下降。正确做法是分层设计:核心在线服务设高优,离线批处理设低优,普通任务居中,并且配合抢占代价评估。

为何常见:人们容易从“单任务视角”看问题,忘了集群是共享的生态系统,优先级是排序机制而非保险机制。

误区以为 GPU 亲和性只是让多卡任务尽量落在一个节点上,节点放不下就没办法了。

正解亲和性策略的维度比这丰富得多。除了节点亲和(Node Affinity),还有 NUMA 感知(让容器绑定 CPU 与 GPU 在同一 NUMA 节点)、PCIe 拓扑感知(选择通信路径最短的 GPU 组合)以及 Pod 间亲和与反亲和。高级调度插件可以通过扩展调度框架实现这些精细控制。

为何常见:初学看到“亲和”二字,容易只想到“挨在一起”这一层,忽略了硬件拓扑的复杂性。

面试题

Q1. 在 Kubernetes 中,NVIDIA Device Plugin 的主要职责是什么?

单选题 · easy

  • A.负责决定 Pod 调度到哪个节点
  • B.向集群上报 GPU 资源并挂载设备← 正确答案
  • C.直接执行 AI 模型的训练任务
  • D.替代 kubelet 管理所有容器

答案:B。设备插件的职责是“上报资源”(ListAndWatch)和“分配设备”(Allocate),它不决定 Pod 放在哪个节点——那是调度器的工作,也不执行 AI 任务。它像后厨的烤箱管家,负责登记和交付钥匙,而非决定谁来点菜。

Q2. GPU 作为扩展资源,其申请数量有什么限制?

单选题 · easy

  • A.可以申请小数 GPU
  • B.只能申请大于 1 的 GPU
  • C.只能申请整数的 GPU← 正确答案
  • D.可以申请负数作为释放

答案:C。K8s 扩展资源的语义要求整数申请,所以你不能申请 0.5 块 GPU。这就像烤箱不能借出半台——要么整台用,要么不用。这也是整卡分配显存浪费的根源之一,需要靠共享机制来弥补。

Q3. 以下哪种 GPU 共享方式提供硬件级的显存隔离?

单选题 · easy

  • A.时间分片(Time-slicing)
  • B.MPS(Multi-Process Service)
  • C.MIG(Multi-Instance GPU)← 正确答案
  • D.整卡独占

答案:C。MIG 在硬件层面把 GPU 切成独立实例,每个实例拥有私有显存,隔离性最好。时间分片和 MPS 都共享显存,一个 Pod 的显存溢出可能拖累同卡的其他 Pod。整卡独占虽然隔离,但谈不上“共享”。

Q4. 在 GPU 调度的主流程中,“调度器将 Pod 绑定到节点”之后,下一步发生什么?

单选题 · mid

  • A.设备插件直接启动容器
  • B.kubelet 调用设备插件 Allocate← 正确答案
  • C.Pod 立即进入 Running 状态
  • D.调度器重启 GPU 节点

答案:B。调度器只负责“决策”,执行由节点上的 kubelet 完成。绑定之后,kubelet 会调用节点上设备插件的 Allocate 接口,获取设备挂载信息,然后才启动容器。就像排菜总管定了灶台,还得厨师真正开火才算数。

Q5. 时间分片(Time-slicing)的主要缺点是什么?

单选题 · mid

  • A.需要昂贵的专用硬件支持
  • B.多个 Pod 对计算资源抢占导致任务变慢← 正确答案
  • C.无法提升 GPU 利用率
  • D.只能用于推理任务

答案:B。时间分片是轮流使用 GPU 计算单元,当多个 Pod 同时有计算需求时,它们会互相排队,单个任务的完成时间被拉长。但它不需要专用硬件,且能显著提升整体利用率,所以 B 是主要缺点,C 和 D 都不成立。

Q6. 关于 MPS 的叙述正确的是?

多选题 · mid

  • A.MPS 允许进程并发使用 GPU 计算单元← 正确答案
  • B.MPS 提供硬件级显存隔离
  • C.MPS 在显存层面依然是共享的← 正确答案
  • D.MPS 与 MIG 的隔离机制完全相同

答案:AC。MPS 让多个进程共享 GPU 计算单元,提高并发利用率,但显存共享、无隔离边界。安全性上它远不如 MIG——MIG 是硬件切分,MPS 只是软件调度。因此 A、C 正确,B、D 错误。

Q7. 抢占式调度中,高优先级 Pod 触发抢占时会发生什么?

单选题 · mid

  • A.GPU 上的任务自动暂停并排队
  • B.调度器驱逐节点上的低优先级 Pod← 正确答案
  • C.集群自动扩容新的 GPU 节点
  • D.低优先级 Pod 自动升级为高优先级

答案:B。当高优先级 Pod 找不到可用资源时,调度器会尝试驱逐目标节点上的低优先级 Pod,把资源腾出来。被驱逐的 Pod 会终止并可能在其他节点重新调度。抢占不涉及扩容,也不会暂停任务——是“请走”,不是“排队”。

Q8. 下列关于拓扑亲和性对多卡训练影响的分析正确的有?

多选题 · hard

  • A.同节点的 GPU 间通信因 PCIe/NVLink 而更快← 正确答案
  • B.跨节点的 GPU 通信通常需要经过网络← 正确答案
  • C.通信占比越小,多卡加速越接近理想值← 正确答案
  • D.拓扑亲和性只对推理任务有效

答案:ABC。同节点 GPU 通过 PCIe/NVLink 直连,带宽远超跨节点的网络通信。通信时间占比 c 越小,加速效率公式 1/(1+c(k-1)) 越接近理想值。拓扑亲和性对训练和推理都有价值,所以 D 错误。

Q9. 在显存隔离需求下,以下哪种混部组合策略最安全?

单选题 · hard

  • A.在线推理用时间分片,离线训练也用时间分片
  • B.在线推理用 MIG,离线训练用时间分片← 正确答案
  • C.在线推理用 MPS,离线训练用 MPS
  • D.所有任务都使用整卡独占模式

答案:B。在线推理对稳定性和响应时间要求高,必须用 MIG 做硬件级隔离,防止邻居 Pod 的显存或算力问题影响服务。离线训练容忍重试,可用时间分片提高利用率。整卡独占最安全但浪费资源,与混部目标相悖。

Q10. 配置 GPU 调度插件实现高利用率混部,需要考虑哪些因素的权衡?

多选题 · extreme

  • A.显存隔离强度决定故障爆炸半径← 正确答案
  • B.优先级设计影响低优任务饿死风险← 正确答案
  • C.拓扑亲和性决定多卡训练通信效率← 正确答案
  • D.所有场景都应优先选择 MIG 切分

答案:ABC。混部是系统工程:显存隔离决定一个 Pod 出问题会不会连累全卡;优先级设计决定低优任务是否永远等不到资源;拓扑亲和性决定多卡任务的通信开销。MIG 虽好,但实例规格固定且不是所有 GPU 都支持,所以 D 是绝对化错误。