Kubernetes 上的生成式 AI——Kubernetes 和 GPUs

0 阅读37分钟

从根本上说,生成式 AI 涉及高强度的数学计算,尤其是张量乘法等线性代数运算。这些运算需要巨大的算力和内存容量,才能处理大规模数据集以及参数规模从数百亿到上千亿不等的模型。幸运的是,一类被称为图形处理器(GPU)的专用硬件已经出现,用于优化并加速这类计算负载。

GPU 最初是为了图形渲染和打造沉浸式游戏体验而设计的,但由于其大规模并行架构,很快就在 AI 领域找到了位置。这种能力与 AI 和机器学习中大量依赖线性代数的任务需求完美契合。

如今,GPU 已成为 AI 领域最主流的加速器类型,其中 NVIDIA 以明显优势占据市场主导地位,AMD 和 Intel 则是其主要竞争者。虽然 GPU 占据主流,但也存在其他替代技术,各自拥有不同的优势和适用场景。例如,Google 的 Tensor Processing Unit(TPU)在性能上颇具吸引力,但通常局限于 Google 生态之内。此外,专门面向 AI 的专用集成电路(ASIC),如 Cerebras 和 Graphcore 开发的产品,以及现场可编程门阵列(FPGA),也代表着正在兴起但仍相对小众的替代方案。

GPU 之所以依然是标准选择,主要原因在于其生态成熟、可获得性广、扩展能力经过验证。对于在生产环境中部署 LLM 而言,由于这些模型对显存和计算能力都有巨大的要求,GPU 已经成为不可或缺的基础设施。

默认情况下,Kubernetes 内置支持 CPU 和内存这类标准计算资源。然而,要利用 GPU 这类专用硬件,则需要额外机制。Kubernetes 通过可插拔扩展框架——设备插件(device plug-ins)——来解决这一问题。设备插件接口使 Kubernetes 能够集成外部硬件资源并管理其生命周期,从而有效扩展 Kubernetes API,使之能够纳入这些专用设备。

不过,GPU 需要特别关注。它们在 Kubernetes 中需要特定的发现机制和调度条件,同时还依赖专门的软件栈,例如 NVIDIA 的 CUDA 库,才能正常工作。

本章将围绕 Kubernetes 中高效访问和管理 GPU 的设备集成机制展开,重点聚焦于 NVIDIA GPU,因为它们在这一领域占据主导地位。

我们将首先讨论 Kubernetes 如何借助 Node Feature Discovery 和 NVIDIA 专用的 GPU Feature Discovery 来发现 GPU 资源。接下来,我们会介绍 Kubernetes 设备插件机制这一基础能力,并概览正在兴起的 Dynamic Resource Allocation(DRA)特性,它为更灵活的 GPU 分配提供了新路径。

GPU 工作负载调度需要仔细权衡,以确保资源得到高效利用。我们将讨论基于资源的 GPU 调度,以及基于标签的调度策略;随后进一步介绍借助 NVIDIA GPU Operator 进行高级 GPU 管理。这包括诸如时间切片(time slicing)和 Multi-Instance GPU(MIG)这样的精细 GPU 分区机制,以及诸如 Data Center GPU Manager(DCGM) exporter 这样的全面 GPU 监控方案。

本章还会讨论多 GPU 推理,重点关注单块 GPU 已无法满足需求的场景。我们将在这里介绍张量并行(tensor parallelism)和流水线并行(pipeline parallelism)等多种技术,以便在单节点或多节点上高效利用多块 GPU。最后,我们将总结一系列最佳实践与优化策略,帮助你更高效地管理 GPU 资源,避免资源碎片化,并最大化 Kubernetes 集群的整体性能。

下面,我们先从 Kubernetes 如何识别并标记 GPU 资源开始,这将为后续有效管理 GPU 打下基础。

GPU 发现

在 Kubernetes 能够有效管理 GPU 之前,它首先必须可靠地识别出哪些节点拥有 GPU,并弄清这些 GPU 的能力。只有做到精确的硬件检测,才能保证工作负载被调度到具备合适 GPU 资源的节点上。

Kubernetes 提供了一个通用方案,叫做 Node Feature Discovery,它可以检测硬件特性并为节点打上相应标签。虽然 Node Feature Discovery 提供了基础的硬件发现能力,但 NVIDIA 还额外提供了一个专用工具,叫做 GPU Feature Discovery。这个工具建立在 Node Feature Discovery 之上,通过补充更细粒度、GPU 专属的标签,让调度与资源管理能够针对 NVIDIA GPU 进行更精细的控制。

我们先来看 Node Feature Discovery 的工作方式,然后再看 NVIDIA 的 GPU Feature Discovery。

Node Feature Discovery

Kubernetes 集群中的节点很少是完全一致的。相反,它们通常在硬件能力上存在差异——尤其是在引入 GPU 这类专用硬件时更是如此。在这类异构环境中,无论是云上部署、混合环境还是裸金属集群,想要高效调度,都高度依赖于对这些硬件能力的准确识别。

正如前面提到的,Node Feature Discovery(NFD)项目提供了这种能力。NFD 会检测每个节点的硬件特征,并自动为集群中的对应节点资源打上标签。这些标签为 Kubernetes 调度器提供了关键信息,使其能够根据可用硬件智能地放置工作负载。

NFD 的运行方式,是在整个集群中部署一个 DaemonSet,确保每个节点上都运行一个代理。该代理会检查节点的硬件和软件配置,识别诸如 CPU 细节、网络接口,以及 GPU 这类 PCI 设备等属性。识别完成后,NFD 会在 Kubernetes API 中为节点资源打上描述性标签。

下面来看如何部署 NFD。你有多种选择,其中最简单的是通过 Kustomize 或 Helm 进行部署,如示例 3-1 所示。

示例 3-1. 使用 Kustomize 安装 NFD

NFD_REPO=https://github.com/kubernetes-sigs/node-feature-discovery
kubectl apply -k $NFD_REPO/deployment/overlays/default

另一种方式是使用 NFD operator,它能提供更集成化的管理体验,借助 Kubernetes operator 简化生命周期管理,这在生产环境中特别有价值。

一旦运行起来,NFD 会自动给节点打标签。要查看这些标签,可以使用示例 3-2 中的命令。

示例 3-2. 查看 NFD 添加到节点上的标签

kubectl get node <node-name> -o yaml | yq .metadata.labels

feature.node.kubernetes.io/pci-0300_1d0f.present: "true"   
feature.node.kubernetes.io/pci-0302_10de.present: "true"   
feature.node.kubernetes.io/cpu-hardware_multithreading: "true"
feature.node.kubernetes.io/cpu-model.family: "6"
feature.node.kubernetes.io/cpu-model.id: "85"
feature.node.kubernetes.io/cpu-model.vendor_id: Intel
feature.node.kubernetes.io/kernel-selinux.enabled: "true"
feature.node.kubernetes.io/kernel-version.full: 5.14.0-427.62.1.el9_4.x86_64
...

表示一个 AWS 设备的 PCI ID(厂商 ID 为 1d0f,设备类为 0300),通常是 AWS EC2 节点上典型的 VGA 兼容显示控制器。

表示存在一块 NVIDIA GPU(厂商 ID 为 10de,设备类为 0302)。

NFD 标签遵循统一命名规范,以 feature.node.kubernetes.io/ 开头,后面接硬件类别和具体特性。默认情况下,NFD 在这类标签中采用 <class>_<vendor> 的格式。其中,PCI class 表示设备的一般类型,vendor 则通过标准 PCI 厂商 ID 表示。PCI 类别码 0302 表示 3D 控制器,例如 GPU;而常见厂商 ID 则包括 NVIDIA 的 10de、AMD 的 1002,以及 Intel 的 8086。
如果你需要更细粒度选择,也可以在 NFD 配置中自定义这些标签,例如增加设备 ID 或子系统 ID 等信息。

不过,NFD 标签主要只是表明某类硬件设备“存在”,并不会描述更详细的 GPU 规格,比如 GPU 型号、显存大小或 CUDA 能力。若要获取更深入的 GPU 专属信息,NVIDIA 提供了一个专门工具——GPU Feature Discovery,下面我们就来看它。

GPU Feature Discovery

虽然 NFD 提供了基础的硬件标签能力,但 NVIDIA 提供了一个更适合深度 GPU 信息的专用方案:GPU Feature Discovery(GFD)。

GFD 是 NVIDIA GPU Operator 的一部分(我们会在“NVIDIA GPU Operator”一节中详细讨论),它是一个轻量级工具,专门用来检测更细粒度的 GPU 特征,并将其暴露为节点标签,以支持高级调度。

与 NFD 类似,GFD 也以 DaemonSet 的形式运行在带 GPU 的节点上。它会检查每个节点上的 GPU,利用 nvidia-smi 等工具收集详细信息,例如 GPU 型号、显存容量、CUDA 版本以及 Multi-Instance GPU 能力。随后,GFD 会把这些 GPU 专属数据作为 Kubernetes 节点标签打上去。

表 3-1 列出了一些由 GFD 添加的重要标签,以及它们的说明和示例。

表 3-1. GFD 添加的标签

标签描述示例
nvidia.com/gpu.count节点上 GPU 或 MIG 实例的数量4
nvidia.com/gpu.productNVIDIA GPU 的型号名或 MIG profile 名称。在 MIG 模式下,可能包含 MIG profile;在时间切片模式下,可能带有 -SHARED 后缀A100-SXM4-40GB
nvidia.com/gpu.memory每个 GPU 或 MIG 实例的总显存(单位 MiB)40537
nvidia.com/gpu.familyGPU 架构家族(如 Ampere、Hopper、Turing)ampere
nvidia.com/cuda.driver-version.full安装的 NVIDIA GPU 驱动完整版本号525.105.17
nvidia.com/cuda.runtime.version.full可用 CUDA runtime 的完整版本号12.2
nvidia.com/mig.capable表示 GPU 是否支持 MIG 分区true
nvidia.com/mig.strategyMIG 分区策略(single、mixed,若未使用 MIG 则为空)single
nvidia.com/gpu.replicas启用时间切片(GPU 共享)时,每块物理 GPU 对应的虚拟 GPU 数量8
nvidia.com/mig-<profile>.count某种特定 MIG profile 的 MIG 分区数量(仅在 mixed 策略下出现)2(例如 nvidia.com/mig-1g.5gb.count
nvidia.com/gpu.machine带 GPU 节点的机器类型或标识符dgx-a100
nvidia.com/gpu.compute.majorGPU 的 CUDA compute capability 主版本号8
nvidia.com/gpu.compute.minorGPU 的 CUDA compute capability 次版本号0

GFD 提供的这些细粒度标签,使得调度决策能够超越基础 NFD 标签所支持的能力。例如,你可以用一个 node selector,像 nvidia.com/gpu.product: A100-SXM4-40GB-SHARED,来专门选择那些已配置为时间共享模式的 GPU 节点(见“时间切片”)。反过来,你也可以通过显式避开带有 -SHARED 后缀的节点,来确保获得独占 GPU 访问权。

在实际使用中,这些细粒度标签通常更多是由 NVIDIA GPU Operator 的组件内部使用,比如 NVIDIA device plug-in。通常,用户只需要像“基于资源的调度”中描述的那样,在 Pod 规范中直接通过资源请求声明需要 GPU 即可。尽管如此,理解这些标签仍然能帮助你更好地把握 Kubernetes 的 GPU 调度机制,尤其是在复杂 GPU 部署环境中。

接下来,我们来看 Kubernetes 是如何让 GPU 可被启用并对外公布的,从 Kubernetes 的 device plug-in 框架开始。

Kubernetes GPU 设备插件

当你已经明确识别并标记了 GPU 能力之后,下一步就是要把 GPU 作为 Kubernetes 中可调度、可分配的资源暴露出来。Kubernetes 通过 device plug-in 框架实现这一点,它允许外部硬件无缝集成进 Kubernetes 的资源模型中。

Kubernetes 从一开始就被设计为可扩展系统。虽然 CPU 和内存是原生支持的,但 Kubernetes 还提供了一个标准化且可扩展的框架——device plug-ins,用于集成 GPU、TPU 以及其他加速器等专用硬件资源。

这些插件会在每个节点上向 Kubelet 注册,并直接向 Kubernetes 报告设备可用性和健康状态,从而使调度器能够感知资源,同时也保障工作负载隔离。

device plug-in 接口支持广泛的专用硬件,包括 FPGA、网络加速器、存储控制器、加密模块、多媒体处理器以及机器人硬件。对于生成式 AI 来说,最重要的是,它同样支持 GPU 以及其他 AI 加速器。

device plug-in 机制主要通过四项核心功能运作:

设备发现
插件会检测节点上的硬件设备,并将其清单上报给 Kubelet。

资源分配
当某个工作负载需要特定硬件资源(例如 GPU)时,device plug-in 负责进行独占式分配。它会设置运行时环境、暴露必要的设备文件,并注入环境变量。

健康监控
插件会持续监控设备健康状态,确保 Kubernetes 能够感知到不健康的硬件,以便做出调度决策。

与调度器集成
device plug-in 将硬件以 Kubernetes 的标准扩展资源形式暴露出来(例如 nvidia.com/gpu)。Pod 通过资源声明显式请求这些资源,从而保证它们只会被调度到有可用设备的节点上。关于更多基于资源的调度内容,见“基于资源的调度”。

有几种常见的 device plug-in 为特定厂商硬件提供 Kubernetes 支持。它们都为各自厂商的加速器提供 Kubernetes 集成,使工作负载能够像使用 Kubernetes 一等公民资源一样使用 GPU 或 TPU:

nvidia-device-plugin
NVIDIA 官方插件,用于在 Kubernetes 中暴露支持 CUDA 的 GPU,是 GPU 加速 AI 工作负载的关键组件。我们将在“NVIDIA GPU Operator”一节中进一步讨论它。

amd-device-plugin
AMD 官方插件,用于集成基于 ROCm 的 GPU,适用于高性能计算与 AI 工作负载。

intel-gpu-plugin
Intel 提供的插件,支持集成显卡和独立显卡,使 GPU 资源能够在 Kubernetes 环境中被使用。

google-cloud-tpu-device-plugin
Google 提供的插件,用于集成 TPU——一种专门针对机器学习优化的硬件,仅在 Google Kubernetes Engine(GKE)中可用。

如今,device plug-in 已成为 Kubernetes 集成 GPU 的基础构件。但这种方式也存在局限。device plug-in 通常会把设备独占分配给单个 Pod,这往往会导致资源利用率不足。此外,资源分配是静态的,在调度时就已经确定,这对于资源需求会动态变化的工作负载而言不够灵活。

为了解决这些问题,Kubernetes 正在演进 toward Dynamic Resource Allocation,我们会在“Dynamic Resource Allocation”一节中讨论它。在那之前,大多数生产环境仍会继续依赖成熟稳定的资源请求模型来进行 GPU 调度。

现在,让我们更仔细地看一看,Kubernetes 是如何利用基于资源和基于标签这两类技术,来决定 GPU 工作负载的放置位置的。

GPU 工作负载调度

Kubernetes 为 GPU 工作负载提供了三种相互补充的放置方式。第一种通过节点标签和亲和性规则来引导 Pod 的调度;第二种完全依赖数值化的资源请求;第三种则是基于 Dynamic Resource Allocation(DRA),它会对 GPU 工作负载的自动放置产生影响。我们将分别讨论它们,以便清晰展现每种方法的优势。

先从基于标签的调度开始,这种方式为 GPU 工作负载的放置提供了细粒度控制。

基于标签的调度

当一个集群中同时存在多种不同类型的 GPU,或者运维人员希望把 GPU 节点隔离出来、不让普通工作负载随意进入时,标签就成了“方向盘”。Kubernetes 为此提供了三种紧密相关的机制,每一种都带来不同层次的控制能力:nodeSelector、节点亲和性(node affinity),以及 taint / toleration。

nodeSelector

nodeSelector 是最直接的方式。你给所有符合某种特征的节点打上固定标签,然后在 Pod 规范的 nodeSelector 字段中重复使用同样的键值对。与其手动创建自定义标签,不如直接利用 NFD 和 GFD 自动打到节点上的 GPU 专属标签。关于可用的 GPU 相关标签,见“GPU 发现”。

如示例 3-3 所示,nodeSelector 的优点在于简单。只需一行配置,就能把工作负载固定到目标节点池上,几乎不会给调度器增加额外负担。但这条规则也是绝对的:只要标签缺失或拼写错误,Pod 就无法被调度。而且 nodeSelector 无法表达“候选项”,它只能是“要么 T4,要么什么都不是”。

示例 3-3. 用 node selector 直接选择节点

apiVersion: v1
kind: Pod
metadata:
  name: t4-inference
spec:
  containers:
  - name: server
    image: myrepo/llm-server:latest
  nodeSelector:
    # Select only nodes that are labelled for a Tesla T4 GPU
    nvidia.com/gpu.product: Tesla-T4

节点亲和性

节点亲和性是在相同思路上的增强版,它允许更复杂的表达式和软偏好。required 条件像扩展版 selector,而 preferred 条件则允许你在多个满足硬约束的节点中,倾向于更优的那个。示例 3-4 展示了这种灵活性。该示例依赖于 GFD 添加的标签,详见“GPU Feature Discovery”。

示例 3-4. 用 node affinity 做更细粒度选择

apiVersion: v1
kind: Pod
metadata:
  name: a100-preferred
spec:
  containers:
  - name: llm
    image: myrepo/mt-server:latest
    resources:
      limits:
        nvidia.com/gpu: 4
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: nvidia.com/gpu.memory   
            operator: Gt
            values: ["40000"]
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:   
          - key: nvidia.com/gpu.family
            operator: In
            values: ["hopper"]

被视作调度目标节点所必须满足的条件。在本例中,GPU 至少要有 40 GB 显存。

优先选择 NVIDIA H100,但如果没有 H100,也可以接受其他符合条件的 GPU。

借助 affinity,你既可以要求最低显存门槛,又可以表达对 Hopper(H100)优于 Ampere(A100)的软偏好。如果当前没有空闲 Hopper 节点,Kubernetes 就会把 Pod 调度到满足显存要求的 Ampere 节点上。
缺点则是配置比较冗长:复杂的 matchExpressions 很容易让 manifest 变得臃肿,而过多硬约束也可能让工作负载“饿死”,迟迟调度不上去。

Taints 和 Tolerations

有时,运维人员会反过来处理问题:直接把某些节点标记为“默认禁止调度”,除非 Pod 明确表示自己可以接受。管理员加上的 taint 会排斥所有 Pod,只有携带匹配 toleration 的 Pod 才能被调度进去。

在示例 3-5 中,我们给所有带有 nvidia.com/gpu.count 标签的节点加上 nvidia.com/gpu=true:NoSchedule 这个 taint。只有显式容忍 nvidia.com/gpu 约束的 Pod,才能被调度到这些节点上。

示例 3-5. 给节点加 taint,使其默认不参与调度

# cluster-admin permission required
kubectl taint nodes -l nvidia.com/gpu.count nvidia.com/gpu=true:NoSchedule

相应的 toleration 如示例 3-6 所示。它允许 Pod 被调度到带有 nvidia.com/gpu taint 的节点上,而不管 taint 的值是什么。

示例 3-6. 带有 nvidia.com/gpu taint 容忍的 Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: gpu-serving
spec:
  replicas: 2
  template:
    spec:
      containers:
      - name: tgi
        image: ghcr.io/huggingface/tgi:latest
        resources:
          limits:   
            nvidia.com/gpu: 1
      tolerations:
      - key: "nvidia.com/gpu"   
        operator: "Exists"
        effect: "NoSchedule"

通过 NVIDIA device plug-in 暴露的 nvidia.com/gpu 资源。我们会在“基于资源的调度”中解释这类调度方式。

不管 taint 的值是什么,只要是 nvidia.com/gpu,该 toleration 都会忽略它,因此这个 Deployment 能够部署到这些节点上。

Taint 非常适合把昂贵的 GPU 节点专门留给 GPU 工作负载,或者在节点维护期间把节点隔离出去。它通常会和 affinity 或 selector 搭配使用:taint 先把普通 Pod 挡在门外,而 affinity 再决定这些剩下的 GPU 节点中,哪一个最合适。

关于影响 Kubernetes 调度决策的更多方法,可以参考 Kubernetes Patterns 中的 Automated Placement 模式。那在什么上下文下该用哪种放置技术呢?可以概括如下:

nodeSelector 最适合小规模、同质化的 GPU 集群,在这种环境下一个标签就足够了。

当集群中混用了不同代际 GPU、不同显存大小,或者不同可用区时,Node affinity 就会成为首选工具。

Taint 在集群范围内保护 GPU 资源池不被普通负载占用,并且天然适合与前两种方式组合使用,实现更精细的放置。

这三种方式有一个共同限制:它们都依赖静态标签。这些标签要么由管理员手工维护,要么由我们在“GPU 发现”中介绍的 NFD / GFD 这类发现型 operator 自动添加。

虽然基于标签的方式提供了精细控制,但它要求你清楚知道并维护特定节点标签。对于更简单的场景,如果“任何可用 GPU 都可以”,那就有一种更直接的方式:基于资源的调度。

基于资源的调度

在 Kubernetes 中,调度 GPU 工作负载最简单的方法,就是直接在工作负载规范里声明需要 GPU。只要节点上运行着 NVIDIA device plug-in,它就会把每块 GPU 作为一种扩展资源发布出来——通常是 nvidia.com/gpu。像示例 3-7 那样,在容器里设置资源 limit,就等于告诉 Kubernetes 调度器:去找一个至少还空出一块 GPU 的节点。

示例 3-7. 请求一块 NVIDIA GPU

resources:
  limits:
    nvidia.com/gpu: 1

调度器只会根据 device plug-in 提供的数值可用性来判断,然后把 Pod 绑定到一个符合条件的节点。Kubelet 则会把该节点上的一块 GPU 独占分配给容器。这里不需要额外管理标签,不需要记住 node selector,也不需要安装其他控制器。整个机制完全集成进了 Kubernetes 熟悉的 requests / limits 资源模型里,因此用起来就像申请 CPU 或内存一样,只不过资源名换成了 GPU。

它最大的优点就是简单。Pod 规范里只要有一个字段,就足以在设备文件层面隔离 GPU,阻止其他 Pod 访问它,并让 CUDA 应用在几乎无需额外配置的情况下直接运行。对于只有一种 GPU 的小集群,或者“有 GPU 就行”的开发环境来说,通常完全不需要更复杂的机制。

缺点则是它不够精确。即便集群中同时存在 V100、A100 甚至消费级显卡,在调度器眼里它们也都长得一样。一个在 80 GB A100 上游刃有余的模型,未必能在 16 GB T4 上跑起来,但如果只写 nvidia.com/gpu: 1,调度器会把它们视为同类资源。
同样,也没有内建方式来要求某种特定的 compute capability、限制 Pod 只能跑在启用了 MIG 的 GPU 上,或者要求“某个节点上、带特定互连拓扑的多块 GPU”。

在实践中,团队通常会结合 GPU device plug-in 添加的标签(如 nvidia.com/gpu.product),或者给节点打上自定义标签(如 gpu-type=A100),然后把资源请求与 nodeSelectornodeAffinity 一起使用,以把工作负载引导到兼容硬件上。
这是一种非常有力的做法,但代价是需要额外协调节点库存信息和工作负载定义。

一种更优雅的方式,是使用“Dynamic Resource Allocation”中描述的 DRA 机制。有了 DRA,你不必硬编码标签约定,也不必依赖静态资源请求。你只需要声明“想要什么样的 GPU”,然后由 Kubernetes 动态分配符合条件的设备。

Dynamic Resource Allocation

Kubernetes 的 device plug-in 机制,使得 GPU 和其他专用硬件能够以可调度资源的方式暴露出来。有了它,工作负载可以请求 nvidia.com/gpu 这样的加速器资源,而 Kubernetes 会在调度时把它们独占分配给某个 Pod。
这种模式在很多场景中都有效,但它本质上是静态的:调度器无法区分设备差异,分配在调度时就被固定下来,而且默认总是独占分配。实际结果往往是资源利用率不足、调度粒度过粗,而且在面对更高级的 GPU 配置(比如我们在“子 GPU 分配”中会讨论的 MIG 或时间切片)时会受到限制。

DRA 是一项让 Kubernetes 中的设备调度变得更灵活、更可组合、更动态的工作。从 Kubernetes 1.34 开始,它已经成为一项稳定的核心特性。
DRA 不再把设备分配直接绑死在 Pod 规范里的资源字段上,而是引入了一套新的资源类型抽象。这些抽象将重点从“要多少个设备”转移到“工作负载到底需要什么类型的设备”。它的设计思路类似于 Kubernetes 中的卷供应:用户声明期望的资源,由平台去完成匹配和分配。

在 DRA 下,工作负载通过 ResourceClaimTemplate 这类资源来声明其设备需求。这些模板本质上是“意图声明”。Kubernetes 控制平面和安装好的 DRA 驱动会在调度时解析它们。
这使得很多静态 device plug-in 难以实现的能力成为可能。例如,一个 Pod 可以请求某种特定设备类别,比如一块至少有 40 GB(约等于 37 GiB)显存的 A100 GPU。调度器只会把这个 Pod 放到能够满足该条件的节点上。分配过程发生在真正调度时刻,因此可以做出更智能、更高效的资源决策。

这种灵活性在 LLM 推理场景中尤其有价值,因为这类工作负载常常依赖特定 GPU 类型,例如 80 GB 显存的 A100。有了 DRA,你就可以精确请求这种配置,而不必依赖节点标签或手工指定 Pod 放置位置。

示例 3-8 展示了这种更高级的用法。我们定义了一个 ResourceClaimTemplate,请求属于 A100 类别、并且至少有 40 GB 显存的 GPU,同时还明确给出了分配模式和数量。这种细粒度能力,是传统资源请求无法表达的。

示例 3-8. 为 Deployment Pod 定义 GPU 需求的 ResourceClaimTemplate

apiVersion: resource.k8s.io/v1beta1
kind: ResourceClaimTemplate
metadata:
  name: a100-claim-template
spec:
  spec:
    devices:
      requests:
        - name: high-memory-gpu
          deviceClassName: gpu.nvidia.com/a100   
          allocationMode: ExactCount
          count: 1   
          parameters:
            minMemory: "40Gi"   
            migMode: "disabled"

请求一块 NVIDIA A100 GPU。

需要一块 GPU。

GPU 至少要有 40 Gi 显存,并且要求禁用 MIG 模式。

在示例 3-8 中,我们引用了一个逻辑设备类 gpu.nvidia.com/a100。这一抽象层可以涵盖不同配置的 A100。我们还要求至少 40 GB 显存,并显式关闭 MIG 模式,以确保获得完整 GPU 访问权。

然后,示例 3-9 中定义的工作负载会在资源声明中引用这个模板。Kubernetes 只有在调度时,并且只有当某个节点上确实存在匹配设备时,才会完成实际 GPU 分配。

示例 3-9. 使用 ResourceClaimTemplate 进行 GPU 分配的 Deployment

apiVersion: batch/v1
kind: Deployment
metadata:
  name: inference-server
spec:
  template:
    spec:
      containers:
      - name: model-runner
        image: myorg/llm-inference:latest
        resources:
          claims:
          - name: high-memory-gpu   
      resourceClaims:
      - name: high-memory-gpu   
        resourceClaimTemplateName: a100-claim-template

引用一个资源声明,供 Deployment 在创建一个或多个 Pod 时使用。

引用示例 3-8 中定义的 ResourceClaimTemplate

把“需求声明”和“实际分配”分离开来,正是 DRA 强大的根源。它为按需、动态的 GPU 供应打开了大门,而这正是传统 device plug-in 很难甚至无法做到的。
它也让驱动有机会在底层实现更智能的分配策略。分配过程不再只是简单地“挑第一块空闲 GPU”,而是可以综合考虑当前使用情况、功耗、显存压力,甚至其他节点级约束。

当然,也有一些现实限制。DRA 虽然前景很好,但在生产环境中的支持度还不高。到 2026 年初为止,核心 DRA API 自 Kubernetes 1.34 起已经 GA 并默认启用,但围绕它的生态仍在追赶中。NVIDIA 的 GPU DRA 驱动虽然已经存在,但目前仍被标记为技术预览,还不适合生产环境。诸如部分 GPU 请求、细粒度 MIG 分区、基于拓扑感知的调度等能力,仍在持续成熟之中,并且高度依赖驱动实现和平台支持。另外,它与 cluster autoscaler 或 quota enforcement 的集成也还比较有限。

不过,它的潜力已经非常清晰。DRA 让 Kubernetes 有机会支持更智能的 GPU 放置策略,支持无需“技巧性绕路”的设备共享,并把 accelerator provisioning 带入更声明式的范式。你不再需要依赖静态节点标签、taint,或者各种特定资源计数,来表达工作负载真正想要的硬件。你只需要表达意图,让 Kubernetes 和驱动去处理剩下的事情。

随着 DRA 持续成熟,它很可能会成为 Kubernetes 中处理高级硬件资源的标准方式。我们也预计 NVIDIA 这样的厂商,会把自己的 device plug-in、GPU operator 和 runtime library 与 DRA 更深度整合,为推理与训练工作负载提供无缝体验。

不过,在 DRA 真正达到生产可用之前,资源请求结合基于标签的调度,仍然是 GPU 调度的标准方案。

NVIDIA GPU Operator

前面几节已经展示了 Kubernetes device plug-in 如何把 GPU 暴露为可调度资源(“Kubernetes GPU 设备插件”),以及 GFD 如何为节点补充详细的 NVIDIA 专属标签(“GPU Feature Discovery”)。
NVIDIA GPU Operator 正是在这两者基础上进一步封装,补齐了在生产环境中稳定运行 NVIDIA GPU 工作负载所需的一切。它负责安装驱动、容器运行时钩子、监控代理,并通过统一的声明式接口提供两种子 GPU 共享机制。

这个 operator 自动化了所有必要组件的安装与配置,使 GPU 工作负载能够高效、可靠地运行。

NVIDIA GPU Operator 包含以下组件:

NVIDIA 驱动(内核模块和 CUDA)

GPU 可用性的核心是 NVIDIA 驱动——包括内核模块和用户态库,它们共同支撑 CUDA 与 GPU 加速能力。GPU Operator 可以通过在每个 GPU 节点上运行一个特权驱动容器,来部署官方 NVIDIA 驱动。这个容器会针对节点当前内核编译驱动;如果有可用的预编译版本,也可以直接拉取。
通过把驱动安装容器化,operator 保证了所有 GPU 节点都能自动拥有所需的驱动版本,而无需人工逐台安装。

NOTE
如果你希望在所有节点上统一依赖 operator 的驱动容器,那么 GPU 节点最好运行相同版本的操作系统内核。若节点内核版本混杂,可能就需要手动预装驱动。
稍后在示例 3-11 中出现的 ClusterPolicy CR 支持按需自定义驱动版本,或使用预编译二进制。

GPU Feature Discovery

我们已经在“GPU Feature Discovery”中介绍过 GFD。GPU Operator 会把 GFD 作为一个 DaemonSet 部署,这样你就无需自己手动安装。

Kubernetes GPU device plug-in

Operator 还会把 NVIDIA device plug-in 作为 DaemonSet 部署到 GPU 节点上。
我们已经在“Kubernetes GPU Device Plug-Ins”中介绍了 Kubernetes device plug-in 架构,以及它是如何引入 nvidia.com/gpu 这一扩展资源的;后者正是“基于资源的调度”中使用的资源名。
NVIDIA 的 Kubernetes device plug-in 同时还支持子 GPU 分配,后面“子 GPU 分配”一节会继续展开。
需要注意的是,NVIDIA device plug-in 是一个关键组件;一旦它没有运行或工作异常,你的 Pod 就会一直卡在 Pending,因为 Kubernetes 会认为当前没有可用资源。

NVIDIA Container Toolkit(运行时)

要让容器真正用上 GPU,它们还需要 NVIDIA 容器运行时(它是 NVIDIA Container Toolkit 的一部分)。GPU Operator 会在节点上部署这套工具。
这个容器运行时本质上是对 CRI-O 和 containerd 的扩展,它知道在容器请求 GPU 时,如何把 GPU 驱动和设备文件注入进容器。Operator 会自动管理这层容器运行时,从而省去手工为容器运行时配置 NVIDIA 支持的麻烦。
最终效果就是:任何请求 GPU 的容器,都会自动拥有类似 /dev/nvidia0 这样的设备文件以及 GPU 驱动。
不过,容器镜像本身仍然需要包含应用实际所依赖的 CUDA 库。

Multi-Instance GPU(MIG)Manager

对于支持 MIG 的 GPU(例如 NVIDIA A100 或 H100),operator 还会包含一个 MIG Manager 组件。这个服务会监控节点的 MIG 配置,并根据期望状态重新配置 GPU 的 MIG 分区。
默认情况下,它会运行在所有支持 MIG 的节点上,并应用你在 ClusterPolicy 中配置的 MIG 策略。我们会在“子 GPU 分配”一节介绍 GPU 共享选项时继续解释。
MIG Manager 能确保:如果某个节点应该工作在 MIG 模式,并切分出特定 profile,它就会在节点启动时或配置变化时自动完成。没有它的话,管理员就必须远程登录节点并手工使用 nvidia-smi 配置 MIG 分区。GPU Operator 则把这件事变成声明式运维。

利用 DCGM Exporter 进行 GPU 监控

为了完整性,GPU Operator 通常还会把 Data Center GPU Manager(DCGM) Exporter 作为 DaemonSet 部署出去。DCGM 会轮询每块 GPU 的利用率、显存压力、ECC 错误、温度、功耗以及大量其他指标,并将它们转换为 Prometheus 指标。
大多数用户会通过集群的 Prometheus 栈抓取这些指标,并在 Grafana 中展示图表。
如果你已经按照第 5 章的可观测性建议搭建了监控体系,那么 operator 只不过是把 GPU 侧这部分自动接了进去。

用 ClusterPolicy 配置 Operator

NVIDIA GPU Operator 适用于多种 Kubernetes 发行版,包括 OpenShift——在 OpenShift 中,它可以通过 OperatorHub 目录直接开箱即用。
它也可以在标准 Kubernetes 集群上通过 NVIDIA 提供的 Helm chart 或自定义 manifest 安装,如示例 3-10 所示。

示例 3-10. 使用 Helm 安装 GPU Operator

helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update
helm install gpu-operator nvidia/gpu-operator \
  --namespace gpu-operator \
  --create-namespace

你通过 ClusterPolicy 这个自定义资源来配置 operator。这个资源控制 GPU operator 组件的方方面面,包括 device plug-in 配置、时间切片开关,以及 MIG 策略等。
ClusterPolicy 还可以引用一个自定义 ConfigMap,以便微调 device plug-in 行为,例如启用时间切片或其他 GPU 共享机制。
ClusterPolicy 中,你可以指定 ConfigMap 中某个键作为默认配置,当 GPU 节点本身没有通过 nvidia.com/device-plugin.config=<configmap-key> 标签指定配置时,就使用这个默认值。

示例 3-11 展示了一个配置示例:它指定了一个用于配置 device plug-in 的自定义 ConfigMap、启用了 GFD,并把 MIG 策略设为 mixed。

示例 3-11. NVIDIA GPU Operator 的配置示例

apiVersion: nvidia.com/v1
kind: ClusterPolicy
metadata:
  name: gpu-cluster-policy
spec:
  gfd:   
    enabled: true
  devicePlugin:
    config:   
      name: gpu-sharing-config
      default: sharing   
  mig:
    strategy: mixed   

启用 GPU Feature Discovery。

指向名为 gpu-sharing-config 的 ConfigMap,其中包含额外配置,例如时间切片设置。

default 指向 ConfigMap 中的某个 key。若设为空字符串,则不使用默认值,此时节点必须显式带有 nvidia.com/device-plugin.config=<configmap-key> 标签,才能生效对应的 device plug-in 配置。

把 MIG 策略设为 mixed。关于 MIG 的更多内容,见“Multi-Instance GPU”。

子 GPU 分配

NVIDIA GPU Operator 支持一些高级 GPU 特性,允许你把单块 GPU 切分或共享给多个工作负载。虽然对子 GPU 资源的分配,在 LLM 场景中的重要性未必像完整 GPU 分配那样高,但理解它依然很重要,因为它关系到如何最大化 GPU 利用率。

Operator 支持两种模式,而且它们可以组合使用:

Time slicing
允许多个容器通过时间片共享一块 GPU。

MIG
在支持的 GPU(如 A100、H100)上,把一块物理 GPU 划分成多个相互隔离的实例。

NVIDIA GPU Operator 带来的一个强大能力,就是允许多种工作负载共享一块 GPU。
这在 AI 推理场景中特别 relevant,例如同时服务多个模型时,并不是每个进程都需要一整块 GPU。

下面我们把这两种机制讲清楚。

时间切片

默认情况下,只要一个 Pod 在资源声明中请求了一块 GPU(例如 nvidia.com/gpu: 1),Kubernetes 就会为它分配一整块物理 GPU 的独占访问权。
时间切片是一种允许 GPU 过量订阅(oversubscription)的能力——也就是说,把每块物理 GPU 广告成多个虚拟 GPU,从而使调度器可以把多个 Pod 同时放到同一块 GPU 上。
配置正确的 NVIDIA device plug-in 会为每一块真实 GPU 创建若干副本。比如,你可以把一块物理 GPU 表示成 4 个逻辑设备,这样最多就能有 4 个 Pod,每个都“以为”自己拿到了一块 GPU,但实际上它们都在同一块硬件上运行。
这些 Pod 的 GPU 工作负载会在时间维度上交替执行,就像 CPU 时间共享一样:如果你有一颗 16 核 CPU,你完全可以同时调度超过 16 个 CPU-bound 进程,通过上下文切换让它们轮流使用核心。GPU 时间切片也是同样逻辑。

要在集群中启用 GPU 时间切片,需要通过 ConfigMap 配置 device plug-in,并由 operator 引用,正如示例 3-11 所示。对应的 ConfigMap 如示例 3-12 所示。

示例 3-12. 时间切片配置示例

apiVersion: v1
kind: ConfigMap
metadata:
  name: gpu-sharing-config
  namespace: gpu-operator-resources
data:
  sharing: |
    version: v1
    sharing:
      timeslicing:
        renameByDefault: true   
        resources:
        - name: nvidia.com/gpu
          replicas: 8   

如果设为 true,资源名会从 nvidia.com/gpu 改为 nvidia.com/gpu.shared,从而更容易区分共享 GPU 和独占 GPU。

过量订阅级别,也就是每块物理 GPU 对外提供 8 个虚拟 GPU。

我们需要指定使用哪个资源名(例如 nvidia.com/gpunvidia.com/gpu.shared),以及副本数。
如果把 nvidia.com/gpu 设成 replicas: 8,那么每块物理 GPU 就会暴露成 8 个可调度单元。这样一来,节点上的 nvidia.com/gpu 标签会显示总虚拟 GPU 数量(例如 1 块 GPU 就是 8,10 块 GPU 就是 80)。同时,节点上还会新增一个 gpu.replicas=8 标签,用于表明这种过量订阅。
另外,如果把 renameByDefault 设为 true,共享资源还可以改名成 nvidia.com/gpu.shared,用来明确区分真正独占的 GPU。

与 MIG 不同,时间切片不会在共享 GPU 的 Pod 之间提供显存隔离或故障隔离。
同一块物理 GPU 上的所有 Pod 都可以访问整个 GPU 显存,并且共享同一个故障域。这意味着,如果某个进程导致 GPU 崩溃(比如非法内存访问引发 GPU reset),同一块 GPU 上的其他工作负载也会一起受到影响。
同样,如果某个 Pod 占用了大量 GPU 显存,其他 Pod 可能就无法再分配显存。时间切片只保证计算时间份额,不保证显存配额。
正因为缺少显存隔离,所以你必须非常小心地规划工作负载规模,确保它们加在一起仍能放进 GPU 显存。

WARNING
在共享模式下,一个 Pod 请求多个 time-sliced GPU(例如 nvidia.com/gpu: 2)并不会让你获得单 GPU 两倍的性能。相反,它只会拿到两块不同物理 GPU 上的共享份额,而每块 GPU 本身还在和其他工作负载共享,这通常并没有实际意义。
为了避免这种误解,device plug-in 可以被配置成在共享模式下拒绝“请求超过 1 块 GPU”的请求。
时间切片本来就是为“每个 Pod 请求 exactly 1 块 GPU”而设计的,只不过这个“1 块 GPU”其实只是某块物理 GPU 的一部分时间份额。对于真正的多 GPU 工作负载,应继续使用独占 GPU,或者使用 MIG。

时间切片非常适合在工作负载轻量或突发的环境中对 GPU 做过量订阅。
例如,你有一块非常强的 GPU,但上面跑的都是许多小型推理任务或交互式 Notebook,就可以让多个任务同时挂在这块 GPU 上,从而提高利用率。
如果它们真的在同一时刻发生竞争,那么每个任务会稍微变慢一些,但整体吞吐量却可能提高。
时间切片也使得那些不支持 MIG 的旧 GPU 也能被共享。如果你在实验室集群中使用 NVIDIA T4 或 V100,并运行许多较小模型,那么时间切片可能让你在一块 GPU 上同时跑两到三个模型,从而充分利用硬件。
当然,你必须注意显存不隔离的问题:所有模型加起来必须能塞进 GPU RAM。
坦率地说,在 LLM 场景下,时间切片的价值可能没那么大,因为 LLM 通常本身就非常大,往往需要吃满整块 GPU 的物理显存。

如果你的场景更需要“强隔离”和“固定显存份额”,NVIDIA 还提供了一种硬件级方案,叫做 Multi-Instance GPU(MIG)。

Multi-Instance GPU

NVIDIA 的 Ampere 及更新一代 GPU(例如 A100、A30、H100 以及 Blackwell B100/B200)都支持 MIG。
MIG 允许你把一块物理 GPU 切分成多个硬件隔离的实例。
每个实例(或称 MIG slice)都拥有自己独立的计算核心、显存切片,甚至独立的引擎上下文——就像在一张卡里放了多张更小的 GPU。
例如,一块 A100 40 GB GPU 最多可以被切成 7 个 MIG 实例。最小配置是 1g.5gb,也就是一个 GPU slice 搭配 5 GB 显存。每个 MIG 设备都像一块迷你 GPU,拥有独立保证的显存和 Streaming Multiprocessor(SM)资源。

NVIDIA device plug-in 可以通过两种方式把 MIG 分区暴露为可调度资源:

单一 MIG 策略

一个节点上的所有 MIG 实例都以通用的 nvidia.com/gpu 资源形式对外暴露(就像普通 GPU 一样)。这种策略假设每块 GPU 都采用相同的 MIG 划分方式。
例如,如果节点上的每块 A100 都被切成 7 个 5 GB 实例,那么一个拥有两块 A100 的节点就会报告 nvidia.com/gpu: 14
当一个 Pod 请求一块 GPU 时,它实际上拿到的是一个 MIG slice(5 GB)。
节点标签(如 gpu.productgpu.count 等)也会随之调整,反映 MIG 状态(在这个例子里,你会看到 gpu.product = ...-MIG-1g.5gb,以及 gpu.count = 14)。
这种方法对用户来说最简单,但要求节点上所有 GPU 采用相同的 MIG 配置。

混合 MIG 策略

MIG 实例会以不同资源类型的形式暴露出来,资源名直接包含其 MIG profile,例如 nvidia.com/mig-1g.5gbnvidia.com/mig-4g.20gb 等。
在这种模式下,如果一块 A100 被切出了多种不同大小的 MIG slice,那么节点就会同时暴露多种资源。
用户可以在 Pod 规范中通过请求对应的资源名来指定某种 MIG 大小。比如,一个 Pod 可以请求 nvidia.com/mig-2g.10gb: 1,从而拿到一个大约 10 GB 的 MIG 实例。
这种策略更灵活(节点上的 GPU 可以采用不同切法,甚至有些保持完整不切分),但调度时也更复杂,因为用户必须清楚自己要申请哪一种 MIG 类型。

无论是哪种模式,GPU Operator 中的 MIG manager 都会根据 ClusterPolicy 中配置的 mig.strategy(single 或 mixed)自动完成 GPU 分区,正如示例 3-11 所示。如果 MIG 模式关闭(none 策略),则 GPU 完全不做分区。

与时间切片不同,MIG 提供的是强隔离。
每个 MIG 实例都拥有固定比例的 GPU 显存,不能超额使用,也就不会出现某个工作负载把其他工作负载显存“抢光”的情况。
故障隔离也更强:如果一个 MIG 实例崩溃或被 reset,其他实例仍然可以继续运行,不受影响。
这使得 MIG 对多租户或生产环境非常有吸引力,尤其是当你希望安全地在同一块物理 GPU 上运行多个不同应用时。
如果每个模型服务只需要几 GB 显存,MIG 就非常理想。

它的代价在于粒度和灵活性。
你只能使用 NVIDIA 预定义好的 MIG profile,不能随意切出一个 6 GB slice,只能选显卡支持的固定规格。
而且,如果某个作业本来在某些时刻可以利用整块 GPU,那么 MIG 划分后它就只能永远被限制在自己的那一小块份额内,不能借用别人闲置的资源。
与之相比,时间切片在别人空闲时,反而允许某个 Pod 暂时“独享”更多显存和算力,而 MIG 永远不会允许这种事情发生。

因此,MIG 最适合这样一种场景:你有多个相对稳定、长期并行运行的 GPU 用途,而且每个用途都恰好能放进一个固定分区中。
对 LLM 和生成式 AI 工作负载来说,MIG 特别适合推理服务场景,或者同时运行许多较小实验。
如果你有一个很大的模型(比如需要超过 40 GB 显存),那 MIG 帮不上忙——你仍然需要整块 GPU,甚至多块 GPU。
但如果你要托管多个较小模型(比如 7 个不同语言模型,每个大约需要 5 GB 显存),MIG 就会非常好用,相当于给每个模型都分配了一块拥有保底显存的“虚拟 GPU”。

有意思的是,时间切片和 MIG 并不是互斥关系。
你甚至可以对 MIG 实例继续做时间切片。
例如,把一块 GPU 先切成两个 MIG 实例,然后再对每个 MIG 实例做 2 倍时间切片,这样每块 GPU 最终就能呈现 4 个可调度单元。
这是非常高级、只在少数边缘场景才需要的玩法,但 operator 确实支持(当两者同时启用时,它会在 MIG 设备 product 标签后面加上 -SHARED)。
例如,你可能既想保留 MIG 带来的显存隔离(比如切成两个较大分区),又希望在某些时候在单个分区中同时跑两个 Pod。
不过,对大多数人来说,实际选择通常是二选一:要么用 MIG,要么用时间共享,而不是两者一起上,因为性能管理会变得很复杂。

总结一下二者差异:
MIG 是把一块 GPU 切成多个固定大小、具备独立显存和算力的专属分区;
而时间切片则是把整块 GPU 看成一个共享池,由多个作业轮流使用,保证的是时间份额,而不是显存切分。
MIG 提供隔离性和可预测性;时间切片则提供灵活性,以及在负载不总是满的情况下潜在更高的利用率。
对于 LLM 训练(通常会吃满整块 GPU,甚至多块 GPU),一般既不会用 MIG,也不会用时间切片——而是直接独占 GPU。
但对于 LLM 推理及其相关负载(例如微调较小模型、跑很多实验、服务多个模型),MIG 和时间切片都非常有价值。
一种常见模式是:在多租户严格隔离或生产 QA 测试中用 MIG,在开发环境或对性能波动不那么敏感的批处理任务中用时间切片。
为了验证你的 GPU 共享配置是否按预期工作,nvidia-smi 会是你的首选诊断工具。

用 NVIDIA-SMI 做 GPU 诊断

nvidia-smi 是 NVIDIA 的系统管理接口工具,用于对 NVIDIA GPU 设备进行实时监控与管理。
它可以提供 GPU 利用率、显存使用量、温度、功耗以及活动进程等信息。
只要执行 nvidia-smi,用户就能快速看到系统中所有 GPU 当前状态的快照。
如果需要持续监控,还可以加 -l 参数按固定时间间隔刷新输出(例如 nvidia-smi -l 5 表示每 5 秒更新一次)。
这个工具在诊断性能问题、确认 GPU 是否工作在最佳状态、以及验证应用是否正确使用 GPU 资源时,都非常有价值。
此外,它也有助于发现诸如热降频或异常显存占用之类的问题,从而帮助你在 GPU 加速环境中主动排障。

如示例 3-13 所示,你甚至可以直接通过 kubectl 来运行它。

示例 3-13. 在带 GPU 的 Kubernetes 节点上直接运行 nvidia-smi

patch=$(cat <<EOT
[{
  "op":"add",
  "path":"/spec/containers/0/resources",
  "value":{"limits":{"nvidia.com/gpu":1}}
}]
EOT
)

kubectl run --rm -it gpu-pod \
  --image=nvidia/cuda:12.8.1-base-ubi9 \
  --restart=Never \
  --overrides=$patch --override-type=json -- nvidia-smi

像时间切片和 MIG 这类子 GPU 技术,适合在同一张卡上挤进很多小型或中型模型,但 LLM 往往并不属于这一类。
在实践中,真正的瓶颈通常不是“怎样把一块 GPU 切得更细”,而是“即便最大的单卡也仍然不够用”。
这就引出了反向问题:我们不再是让多份工作负载共享一块 GPU,而是必须让一份工作负载分布到多块 GPU 上。
多 GPU 推理正是为了解决这个问题而存在,它通过协调多块 GPU,有时甚至跨多个节点,来共同服务一个大型模型。

多 GPU 推理

在上一节中,我们学习了如何把一块物理 GPU 切分给多个工作负载。
而对于 LLM 来说,更常见的情况恰恰相反:推理往往需要的显存和算力,已经超过了单块 GPU 能提供的上限。

在使用多块 GPU 做 LLM 推理时,通常有两种根本性的思路,每一种都针对不同问题。图 3-1 展示了多 GPU 并行策略的分类。

image.png

图 3-1. 多 GPU 并行的分类

数据并行(data parallelism)使用多块 GPU 承载同一个模型的多个副本,让它们并行处理不同请求,从而提升整体 QPS。
相比之下,当一个模型本身已经大到单块 GPU 放不下时,就必须使用模型并行(model parallelism):模型会被拆分到多块 GPU 上,每块 GPU 只持有模型的一部分,多个 GPU 协同完成一次推理请求。
模型并行又可以进一步分为张量并行(tensor parallelism)和流水线并行(pipeline parallelism):前者把单层内部的计算拆到多块 GPU 上,通常发生在单节点内;后者则把整个模型的不同层分布到多个 GPU 上,甚至多个节点上。

下面我们逐一展开,从数据并行开始。

数据并行

数据并行通过运行多个完整模型副本来提升整体吞吐量。在这种模式下,每块 GPU 都持有完整模型,并并发服务不同请求。
如果模型大到需要一组 GPU 通过模型并行协作才能装下,那么每一组 GPU 也依然可以再运行多个独立副本。
这种方式并不会降低单次查询的延迟,但它能让更多请求并行处理,从而提高 QPS。
例如,如果你有 4 块 GPU,而某个中等大小的 LLM 可以单独装进一块 GPU,那么你就可以部署 4 个模型实例,每块 GPU 跑一个,从而承载 4 倍流量。
在 Kubernetes 中,更原生的做法是运行多个副本 Pod,每个 Pod 请求 1 块 GPU,并通过一个负载均衡 Service 对外暴露模型服务,自动把请求分散出去。
另一种方式是,有些推理框架会在单个 Pod 中运行一个多线程 server,把请求调度到多块 GPU 上,不过在 Kubernetes 中,这种做法对 GPU 工作负载并不那么常见。

图 3-2 展示了如何把同一个模型扩展成多个副本,以便同时处理大量并发请求。

image.png

图 3-2. 数据并行:通过扩副本提升吞吐

当你需要同时服务大量用户或 API 请求,而且模型本身能装进单块 GPU 显存时,数据并行是理想方案。
例如,一个 7B 参数的 LLM 通常可以被量化到大约 8 GB,从而放进一块 16 GB GPU。你就可以在 8 块 GPU 上各跑一个副本,同时处理大量对话请求。
它的限制在于:这完全不会降低单条查询的延迟。
每条查询在不做模型并行的前提下,仍然是由一块 GPU 从头到尾处理。所以如果一个特别大的请求在单块 GPU 上要跑 10 秒,那么增加更多 GPU 做数据并行,并不会让这条请求更快,只是能同时处理其他请求。
事实上,让同一个请求在多个完整模型副本上同时执行,反而是浪费,因为你消耗了多块 GPU,却没有减少延迟——这种场景真正需要的是“模型并行”,见“模型并行”。

另一个限制是资源占用:运行 N 个副本,就意味着在显存中存 N 份完整模型权重。如果模型很大而显存又紧张,这会变得低效。
有些框架支持在单个模型实例上做多流批处理,以提升利用率(例如 “vLLM” 中介绍的 vLLM 就能在一块 GPU 上对多个到达请求进行动态批处理,以提高吞吐),这就构成了“完整副本复制”之外的另一种思路。
总的来说,基于多块 GPU 的数据并行非常直接,通常表现为 Pod 的水平扩展;但要注意,它会让显存占用线性增长,而且只有当你的并发负载真的足够高时,才能把所有 GPU 吃满。
如果请求速率很低,那么这些额外 GPU 可能会闲着——这时就应该考虑把工作负载收敛到更少 GPU 上,甚至通过“子 GPU 分配”中介绍的时间切片或 MIG 来共享 GPU。
对于动态负载,你还可以利用 Kubernetes 自动扩缩容,根据需求自动调整副本数(参见 Kubernetes Patterns 中的 Elastic Scale 模式)。

模型并行

使用多 GPU 推理的第二个核心动机,是让一个单独的大模型由多块 GPU 协同服务。
与复制完整模型的数据并行不同,模型并行是把一个模型拆到多块 GPU 上——这对于参数量达到几十亿甚至数千亿的现代 LLM 来说是必须的,因为它们已经超出了单块 GPU 的显存容量。
这之所以可行,是因为 LLM 本身具有分层结构,由一系列顺序排列的 Transformer 层组成。
这种结构允许从两个方向拆模型:张量并行把每一层内部的计算拆分到多块 GPU;流水线并行则把不同层分配到不同 GPU。
这两种方式也可以组合使用,用于超大规模部署。在任何一种方式中,每块 GPU 都只持有神经网络的一部分,并只负责前向传播中的一部分计算。
模型并行关注的重点,是通过多设备协作降低单块 GPU 的显存压力,并在某些情况下缩短单次推理延迟,但代价是 GPU 之间需要额外通信。
因此,高带宽互连(例如后面侧栏会讲到的 NVIDIA NVLink 或 NVSwitch)通常至关重要,它们能够避免 GPU 间频繁交换数据时出现瓶颈。

NVLINK 与 NVSWITCH:NVIDIA GPU 互连技术

NVIDIA 开发了两种互补技术,用于实现模型并行场景下的高速 GPU 间通信:
NVLink 负责点对点直连,NVSwitch 则负责交换式网络互连。

NVLink 是一种高速点对点互连技术,可在单台服务器节点内部提供 GPU 之间的直接通信。
第五代 NVLink 5.0(随 Blackwell 架构推出)每块 GPU 提供高达 1.8 TBps 的双向带宽,方式是通过 18 条链路、每条链路 100 GBps 实现。
这相比上一代 NVLink 4.0(H100 上为 900 GBps)提升了 2 倍,相比 PCIe Gen5 的带宽则高出 14 倍以上。
早期 NVLink 世代通常只能连接 4 到 8 块 GPU;现代实现最多可扩展到 576 块 GPU,不过实际部署中更常见的是每节点 8 块 GPU。

NVSwitch 则是一种高性能交换网络,用于把 NVLink 扩展成一个全连接、无阻塞的 mesh,使任意 GPU 都能够在满速 NVLink 带宽下同时与任意其他 GPU 通信。
Blackwell 系统中的 NVSwitch 4.0 每颗芯片提供 72 个 NVLink 5.0 端口;双芯片交换托盘则提供 144 个端口、总计 14.4 TBps 的交换能力。
NVIDIA HGX H100 / H200 系统使用 4 颗 NVSwitch 3.0 芯片互连 8 块 GPU,而机架级的 GB200 NVL72 系统则利用带 144 个端口的 NVLink Switch,在多台服务器之间连接 72 块 GPU,总 GPU 带宽达到 130 TBps。

关键区别在于:NVLink 提供物理连接链路,NVSwitch 提供交换基础设施,从而把这些连接扩展到更多 GPU。
在多服务器集群中做跨节点通信时,系统通常会把节点内通信交给 NVLink / NVSwitch,把跨节点流量交给 InfiniBand 或 RoCE 网络。
而 GPUDirect RDMA 则把这两层桥接起来,使跨网络边界的 GPU 到 GPU 数据传输能够绕过 CPU 直接进行。

这些技术当然代价不菲。基于 NVSwitch 的系统往往价格高达数百万美元,并且要求非常可观的供电和散热能力。
但对于训练 LLM 或对超出单 GPU 显存容量的大模型做推理而言,NVLink 和 NVSwitch 提供的带宽与低延迟特性往往是获得可接受性能的必要条件。

张量并行

张量并行会把每一层内部的计算切分到多块 GPU 上,如图 3-3 所示。
在这种方案中,每块 GPU 持有该层权重的一部分(例如把一个大权重矩阵按列或按行切分),并处理该层输入的一部分。
随后,多块 GPU 会交换部分结果,以拼出该层的完整输出。
举例来说,如果某个全连接层的权重矩阵大到单块 GPU 放不下,那么它就可以被拆成多个切片。每块 GPU 执行输入与自己那部分权重的矩阵乘法,然后把局部输出拼接或求和,得到最终完整输出。
这种方式可以让所有 GPU 在同一层上同时工作(从而改善单 token 延迟),并且通过并行使用多块 GPU,等效地提升可用显存带宽。

image.png

图 3-3. 张量并行

张量并行直接降低了单块 GPU 的显存负担,使超大模型得以加载。
例如,把一个 70B 参数模型拆到 2 到 4 块 GPU 上,那么每块 GPU 只需要持有 35B 到 17.5B 参数。
由于多个 GPU 并行计算,它还能够降低每个 token 的延迟。

这些优势使张量并行对大模型非常有吸引力,但它的开销也很高。
张量并行会引入频繁的通信开销——GPU 必须在每层处理完成后,甚至在每个 attention head 处理后进行同步。
如果互连不够快,那么通信成本很容易主导整体运行时间(在切分不佳的情况下,通信甚至会占掉 50% 到 70% 的推理时间)。

因此,张量并行最好局限在单节点内部,并依赖高带宽互连(例如带 NVLink 或 NVSwitch 的 PCIe 系统,见“NVLink and NVSwitch: NVIDIA GPU Interconnects”)。
事实上,在普通网络条件下,跨多节点做细粒度张量并行并不明智,因为延迟代价太高。
我们会在后面的“单节点与多节点推理”中更详细讨论单节点和多节点使用 GPU 的差别。
在实践中,张量并行的最大并行度通常就是单台服务器中的 GPU 数量(例如,4 块 GPU 的节点做四路张量并行)。
超过这个规模之后,通常要么换更大机器,要么转向跨节点的流水线并行,后者正是下一节要讨论的内容。

流水线并行

流水线并行是按层把模型“纵向切开”,把不同的连续层分配给不同 GPU,如图 3-4 所示。
在这种方式中,输入序列先由 GPU 0 上的前若干层处理,再把中间激活值传给 GPU 1,交给后续层继续处理,依此类推,整个过程像一条装配线。
因此,流水线并行需要在阶段之间保存和传输中间激活值,但不像张量并行那样每层都频繁交换结果——它只在每个流水线阶段切换处(即每次前向传播)通信一次。

image.png

图 3-4. 流水线并行

流水线并行最大的优点,是大幅降低 GPU 间通信频率。
每个流水线阶段在一次前向传播中只需要做一次激活值交接,因此它对慢速互连更有容忍度。
正如前面提到的,这特别适用于 GPU 横跨不同服务器、或者没有 NVLink 这类高速互连的环境。
它还能扩展到那些即便整台多 GPU 节点也放不下的模型(例如把一个 175B 模型拆到两个节点上)。
不过,流水线并行并不会降低单次请求的延迟——实际上,由于阶段顺序执行,它甚至可能增加延迟。
流水线还会带来空闲时间,因为流水线后面的 GPU,必须等前一个 GPU 完成前一个 token 的处理之后,才能开始处理下一个 token 的输入。
因此,如果不做精细管理,一个简单的流水线会导致多块 GPU 出现闲置。

为了缓解这个问题,框架通常会使用 microbatching 或调度技巧:把输入 batch 或序列拆成更小的 microbatch,并以错峰方式送入流水线,这样每个阶段都能保持忙碌。
例如,NVIDIA 的 FasterTransformer 和 vLLM 都实现了带自动 microbatch 调度的流水线机制,用来避免空转。
归根结底,流水线并行最适合多节点扩展和高吞吐批处理场景——在这些场景中,单次请求延迟不是最重要的指标。

混合并行

虽然张量并行和流水线并行各自解决不同问题,但在生产部署中,它们完全可以组合使用,以获得最大扩展性。
很多系统采用的是混合并行:节点内使用张量并行,节点间使用流水线并行。
这种方式利用了单节点内的高速互连来做细粒度切分,又通过流水线把模型跨机器扩展出去,从而避免了过多跨节点通信。
经验法则是:如果节点间网络较慢,就在节点间用流水线并行、节点内用张量并行;如果节点间互连非常快,那么张量并行也可以扩展到跨节点。

无论哪种方式,分布式推理都需要协调——GPU 必须通过集体通信操作(如 all-reduce、all-gather、send/receive 等)交换中间结果,通常借助 NVIDIA 的 NCCL 库(见“什么是 NCCL 和 RDMA?”)和高速互连来完成。
这种协调开销意味着,相比单 GPU 推理,总会有一定效率损失,但它换来的能力是:能够服务前所未有的大模型。
还需要注意的一点是:如果模型并行组中的一块 GPU 或其所在节点发生故障,那么整个推理就会失败;并不存在“少一块 shard 也能继续工作”的容错能力。
因此,在 Kubernetes 中部署模型并行推理时,通常会结合 pod affinity / anti-affinity 规则(让 GPU 尽量同置,或跨故障域分布),并配套适当的健康检查,一旦其中一部分挂掉,就重启整个分组。下面的侧栏会解释这一点。

控制多 GPU 工作负载的 Pod 放置

Kubernetes 的亲和性和反亲和性规则,允许你控制 Pod 彼此相对于彼此的落点,这对多 GPU 部署至关重要。

Affinity 会把 Pod 尽量放到同一节点、机架或可用区。
这适合模型并行推理,因为 GPU 之间需要频繁通信。
让张量并行的 Pod 共置在同一节点,可以利用 NVLink 这样的高速本地互连,把延迟压到最低。

Anti-affinity 则会刻意把 Pod 分散到不同节点或可用区。
这适合“以吞吐扩展”为目标的部署,因为不同模型副本之间彼此独立,应避免共担单点故障风险。
如果一台节点宕机,其他节点上的副本仍然可以继续服务。

这两种机制都支持硬约束和软约束:
硬约束表示“不满足就绝不调度”;
软约束表示“尽量满足,但如果做不到也允许退而求其次”。
硬约束适用于正确性要求,例如必须让模型 shard 落在一起;软约束则更适合在可能的情况下优化性能。

更详细的配置和示例,可参考 Kubernetes Patterns 中的 Automated Placement 模式。

在实践中,很多团队只要条件允许,就会尽量把模型并行部署限制在单节点内,因为这样故障处理更简单。只有在模型真的巨大到单节点放不下时,才会上多节点。
这种倾向自然引出一个关键问题:虽然我们已经讨论了如何用张量并行和流水线并行把模型分布到多块 GPU 上,但从 Kubernetes 部署拓扑角度看,我们还没有真正回答“这些 GPU 应该放在哪里”这个问题。
是应该把所有 GPU 都集中在一台物理节点上,还是分散到多台节点?
这个拓扑决策独立于并行策略本身,却会深刻影响性能、可靠性与运维复杂度。
下面几节就会专门从节点拓扑与 Kubernetes 资源约束的角度,重新审视这一问题。

单节点与多节点推理

当你在 Kubernetes 上部署模型并行推理时,会面临一个根本性的拓扑选择:
是把多块 GPU 集中在一个节点上,还是把它们分散到多个节点上。
两种方式各有不同权衡。

单节点多 GPU 推理,意味着模型或模型副本所用的全部 GPU 都位于同一台服务器中。
正如前面提到的,这种方式的最大优势是本地高速互连。
在同一台机器内,GPU 通常通过 PCIe 通信;如果是高端 GPU 服务器,则 GPU 之间往往还通过 NVLink 或 NVSwitch 互联。
例如,NVIDIA DGX 级别节点会通过 NVSwitch 把 8 块 GPU 全互联起来,带宽可高达 900 GBps,这使得节点内通信远快于典型网络链路。
因此,那些需要频繁通信的并行策略(例如张量并行)在单节点内部通常效果非常好。
在 Kubernetes 中,使用单节点多 GPU 也非常直接:只要在 Pod 规范中请求相应数量的 nvidia.com/gpu 资源,调度器就会把 Pod 放到某个拥有足够空闲 GPU 的节点上。
容器随后可以看到分配给它的所有 GPU(通常通过环境变量 CUDA_VISIBLE_DEVICES 等方式)。
你的推理服务器或代码就可以在这些设备之间初始化模型并行。

图 3-5 展示了在单节点上快速访问多块 GPU 的方式。

image.png

图 3-5. 单节点上的多块 GPU

虽然单节点多 GPU 部署具备简单和高速互连的优势,但它受限于单台服务器的 GPU 容量。
相比之下,多节点多 GPU 推理则意味着把模型或服务负载拆分到多个不同机器上的 GPU 中。
当模型大到任何一台单机都容不下时(例如一些团队曾将 175B+ 参数模型部署到两台或更多节点上,每台节点配 8 块 A100 80 GB),这就是唯一选择。
在这种情况下,GPU 间通信将通过节点之间的网络接口完成,比如 InfiniBand 或 Ethernet。
正如在“多 GPU 推理”中讲张量并行时提到的,节点间网络带宽(例如 100 Gbit Ethernet 约 12.5 GBps)相比节点内 NVLink(高达 900 GBps)通常要慢一个数量级,因此跨节点时更适合流水线并行,因为它通信次数更少、单次传输更大。

在流水线并行下,每个节点会在把结果交给下一个节点之前,先独立完成较大一部分计算,因此它对网络延迟更不敏感。
此外,如果必须做多节点部署,建议使用尽可能快的网络,并确保 NCCL(NVIDIA Collective Communication Library)能够在条件允许时启用 RDMA(Remote Direct Memory Access)。
NCCL 可以基于 socket,也可以基于 InfiniBand 工作;在 Kubernetes 中,还必须确保 Pod 能够彼此发现地址(有时会使用 Kubernetes Service IP,或者为性能起见直接使用主机网络)。

什么是 NCCL 和 RDMA?

NCCL 是一个面向多 GPU 和多节点环境的高性能 GPU 通信库。
它提供诸如 all-reduce、broadcast、reduce-scatter 和 all-gather 等优化过的集体通信原语,这些操作在张量并行和流水线并行中,用于同步模型参数或中间结果,是不可或缺的。
终端用户通常不会直接使用 NCCL;相反,它一般由推理运行时(例如 vLLM)以及 PyTorch 这类框架在底层调用,并通过更高层 API 把复杂度屏蔽掉。
不过,在分布式 Kubernetes 部署中,高级用户有时需要调优 NCCL 相关设置(例如 NCCL_SOCKET_IFNAME),以确保其能够在指定网络接口上获得最佳性能。
当硬件支持时,NCCL 还可以借助 RDMA,绕过 CPU,直接访问远程节点上的 GPU 显存,从而显著降低多节点推理的延迟,并提升带宽。
RDMA 通常要求专门的加速网络设备,例如 InfiniBand 或支持 RoCE 的网卡。
正确配置的 NCCL + RDMA,是实现跨多 GPU、多节点的大规模高吞吐 LLM 推理的关键。

对于多节点推理,典型方式是每个节点运行一个 Pod,然后由外部协调它们。
一些主流推理运行时会借助编排框架来简化这一过程。
例如,在多节点部署中,vLLM 会使用 Ray——一个自带调度器的分布式计算框架——来编排跨节点推理。在 Kubernetes 上,Ray 通过 KubeRay operator 运行在 Pod 中。
Kubernetes 依然负责调度和重启 Pod,而 Ray runtime 则负责协调分布式 vLLM worker,处理任务分配、节点发现,以及部分容错问题。
还有一些运行时,比如 Hugging Face 的 TGI,则更偏向依赖 Kubernetes 原生对象(如 StatefulSet 或 Deployment):其中一个 Pod 充当协调者(通常被称为 “rank-0”),负责管理不同 Pod 上模型分片之间的通信。

图 3-6 展示了这种多节点架构:多个节点,每个节点上都有多块 GPU,并由 vLLM 协同完成推理。

image.png

图 3-6. 多节点上的多块 GPU

无论采用哪种编排方式,管理 Pod affinity(确保 Pod 落到不同 GPU 节点,或落到性能最优位置)以及 service discovery(让 Pod 能通过名称或 IP 地址互相发现)都至关重要。
Kubernetes 提供了诸如 Pod hostname 和 subdomain 之类的内建机制来支持 Pod 发现。
而 NCCL 则负责 GPU 通信,它在单节点内通常能自动完成拓扑发现,但在跨节点时,通常需要显式配置网络接口。

由于节点内与节点间在延迟和带宽上差异巨大,从单节点扩展到多节点时,扩展效率(吞吐或加速比)往往会下降。
在单节点内部,通常可以接近线性加速(例如,对于优化良好的模型,4 块 GPU 可以带来大约 3.5 倍于单 GPU 的吞吐);
但一旦跨节点,网络就可能成为瓶颈,收益会逐渐递减。
另外,跨节点的集体通信(如 all-reduce)需要同步进行——如果某个节点稍微慢一点,或者它的网络延迟更高,就可能拖慢其他节点。
这使得整体性能变得更难预测,而且“最慢节点决定整体节奏”:例如,如果一个节点上的 GPU 正忙于其他任务,或者发生了一次垃圾回收停顿,它就可能卡住整个流水线。

在单节点多 GPU 推理中,节点故障会自然导致 Pod 终止,而 Kubernetes 对这种情况的处理非常直接。
而在多节点模型并行推理中,复杂度要高得多,因为只要参与的某个 Pod 挂掉,整个推理作业通常就会中断——因为模型分区已经不完整了。
恢复方式通常是重启整组 Pod,因为这类分布式推理在调度和恢复上都具有“要么全部成功、要么全部失败”的语义(这就是所谓的 gang scheduling,我们会在第 7 章讲作业调度时详细讨论)。
Kubernetes 中像 PodDisruptionBudget 这样的机制,可以帮助减少计划内维护带来的干扰。
某些更高级的系统可能会考虑 checkpoint 方案来减轻这类故障,不过对于无状态推理而言,这类技巧并不常见,更多见于训练场景(见第 6 章)。

总之,由于复杂度低、效率高,单节点部署通常仍是模型并行推理的首选。
但如果模型体量迫使你必须跨节点部署,那么就应选择像流水线并行、专家并行(适用于 MoE 模型)这类更节省网络的方式,并结合 Ray.io 或 Kubernetes 原生部署模式这类成熟编排方案,以确保大规模推理的可靠与高效。

除了选择正确的并行策略和部署架构之外,要真正把 GPU 基础设施效率榨干,还必须认真对待资源管理和优化。

GPU 资源优化

这一节将汇总生产部署中的关键 GPU 优化策略,包括本章前面讲到的 MIG 和时间切片,以及其他一些最佳实践。

在生产级 LLM 推理中,最大化 GPU 利用率、避免显存浪费非常关键,因为 GPU 是昂贵资源。下面是一些在 Kubernetes 环境中优化 GPU 使用的最佳实践和注意事项:

GPU 显存碎片整理

随着模型不断加载和卸载,或者动态推理工作负载不断分配显存(例如序列长度变化),GPU 的内存分配器会逐渐产生碎片。
也就是说,空闲显存虽然总量看似足够,却被分割成许多小块,而不是一个连续大块。
这会导致大模型无法加载,或者在理论上“总空闲显存够用”的情况下,依然出现 out-of-memory 错误,只因为没有足够大的连续块。
因此,通常最好预先分配大块显存(例如在启动时一次性加载完整模型权重,并使用内存池管理临时空间),以避免堆碎片化。
像 PyTorch 这样的框架提供了缓存分配器(caching allocator)来缓解这一问题,但长时间运行的 Pod 仍然可能随着时间推移出现碎片。
如果你发现 GPU 显存占用不断升高,或者在服务大量请求后突然频繁 OOM,那么一个常见策略就是定期重启 Pod,以清理碎片。
PyTorch 还提供了 “expandable segments” 功能,通过让分配器扩展已有内存段而不是频繁创建新段,来减少碎片。
在推理侧,vLLM 的 PagedAttention 本质上就是一种 KV cache 碎片整理技术。
要识别显存碎片问题,完善的监控至关重要。如果你看到服务很多请求之后,可用显存越来越少,很可能就是发生了显存碎片化。

GPU 共享与整合

尽量让 GPU 保持忙碌——闲置 GPU 就是在烧钱。
如果你的 LLM 只吃掉了一块 GPU 30% 的算力和显存,那就应该考虑在同一块 GPU 上运行多个模型实例或其他工作负载。
在支持的硬件上,可以通过 MIG 来实现明确隔离(例如,把一块 80 GB 的 A100 切成两个 40 GB MIG slice,各跑一个 6B 参数模型),也可以通过时间切片共享。
更多细节见“子 GPU 分配”。
另一种方式,是运行一个多模型服务端,在同一块 GPU 上同时加载多个模型,并根据请求路由到对应模型(前提是它们都能放进显存)。
有些 serving 框架,比如 NVIDIA Triton 或 AWS Multi-Model Server,就支持在同一块 GPU 上托管多个模型,并在需要时动态卸载不常用模型。
最佳实践是先做画像:如果某个模型只用了 50% GPU 显存,那么剩下的 50% 理论上就可以承载另一个较小模型,或者再加载一个副本,把吞吐翻倍。
当然,不要把显存压得太满——最好留一点余量,因为驱动开销和碎片会悄悄吃掉几个百分点。
Kubernetes 本身并不知道一块 GPU “只用了半块”,因此是否能高效打包,取决于你是否善用 MIG,或者是否在同一节点上合理放置多个 Pod。

量化与编译优化

优化模型本身也能减少 GPU 需求。
例如,把权重量化到 4-bit 或 8-bit,可以大幅减少每个模型副本所占显存(当然要接受一定精度损失)。
如果一个 LLM 能在质量几乎不受影响的前提下,从 16-bit 量化到 8-bit,那么你理论上就可以把所需 GPU 数量减半。
现在很多开源模型都已经有 8-bit 或 4-bit 版本。
比如,一个 70B 模型在 4-bit 模式下,可能就能装进一块 48 GB GPU;如果用全精度,它则需要 280 GB(70B × 每参数 4 字节),而 4-bit 模式下只需要大约 35 GB(70B × 每参数 0.5 字节)。
vLLM 和 TGI 都支持加载这类量化模型。
此外,还应考虑使用优化过的 runtime,以提高单位 GPU 的推理速度。模型越快,你就能用同样硬件处理更多负载,也就意味着更高的利用率。

自动扩缩容

对模型并行的多 GPU 部署做自动扩缩容是比较棘手的。
如果一个模型被切分到 4 块 GPU 上,那么你不能把它缩到 2 块 GPU,也不能“平滑扩到 6 块 GPU”。
你只能按完整副本单元来扩缩:要么减掉整整一组 4 块 GPU,要么再新增一组完整的 4 块 GPU。
但对于以吞吐为目标的扩展,基于 Kubernetes 的自动扩缩容(例如 KEDA、HPA 或 Knative),按 RPS、并发数或延迟来调副本数,仍然非常有效。
关于 Kubernetes 自动扩缩容模式与最佳实践的完整讨论,可以参考 Kubernetes Patterns 中的 Elastic Scale 模式。

放置与亲和性

对于多 GPU 节点,了解拓扑非常重要。
在某些 8 GPU 服务器中,并不是所有 GPU 都直接互联——有时会以 mesh 方式连接,或者形成若干小组。例如,一台 NVIDIA DGX A100 有 NVSwitch 做全互联,而其他系统则可能只是 4 块 GPU 一组。
如果你的模型并行使用 4 块 GPU,那么如果这 4 块刚好位于同一个 NVLink group 中,性能往往会更好。
你可以用 nvidia-smi topo -m 来查看这种互联分组。
Kubernetes 默认不会自动把这类拓扑考虑进去,但你可以利用节点硬件知识,并借助 device plug-in 能力,按 GPU index 精确选择特定 GPU。
手动选 GPU index 属于高级优化——大多数情况下,Kubernetes 只是随意分配任意 4 块 GPU;但如果你特别在意节点内延迟,就可能会手工把它固定为 GPU0-3,前提是你知道它们恰好位于同一 NVSwitch 集群中。

优化 I/O 与初始化

大模型从磁盘或网络加载进 GPU 显存需要时间。
如果 Pod 经常起起停停,你每次都得为这一步付费。
所以,只要可能,就尽量让 Pod 保持 warm。
关于更高效的模型加载技术,我们会在第 2 章中详细介绍。

监控 GPU 健康状态

GPU 可能会遇到 ECC 错误或过热降频等问题。
因此,必须确保有节点级监控和告警机制来捕捉这些事件。
如果 GPU 开始报错但还没彻底挂掉,Kubernetes 并不会自动帮你迁移 Pod——你可能需要一个守护进程持续用 nvidia-smi 检查错误,然后通过给节点加 taint 或重启 Pod 来处理。
运行 NVIDIA DCGM 并与 Kubernetes 节点健康机制集成,会很有帮助。
在模型并行部署中,一块“不稳定”的 GPU 可能导致错误结果,甚至直接让整个推理组崩溃,因此尽早发现硬件问题非常重要。

通过遵循这些最佳实践——整理显存碎片、聪明地共享 GPU、善用扩缩容与优化工具、并密切监控,你就能在 Kubernetes 上实现多 GPU LLM 推理的高利用率和高可靠性。

至此,我们完成了对 GPU 资源优化策略的讨论。
本章一路覆盖了 GPU 发现、调度、operator 管理以及多 GPU 推理策略,现在让我们退后一步,总结一下最关键的结论。

经验总结

本章探讨了 Kubernetes 如何通过 device plug-ins、feature discovery 和更高级的管理能力来集成 GPU 资源,以支持 AI 工作负载。

Kubernetes 通过 device plug-in 框架,把自身能力从原生的 CPU / 内存调度扩展到了 GPU 这类专用资源。
Node Feature Discovery(NFD)和 GPU Feature Discovery(GFD)可以自动检测 GPU 能力并打标签,从而支持基于 GPU 型号、驱动版本和硬件特性的工作负载调度。
这层发现能力,为简单的基于资源调度和复杂的拓扑感知放置提供了基础。

GPU 调度与传统工作负载有着不同需求。
基于资源的调度,把 GPU 当作可计数资源来分配;
而基于标签的调度,则通过 node selector、affinity 和 taint,让你能够根据 GPU 特征做精确放置。
正在兴起的 Dynamic Resource Allocation(DRA)API 承诺带来更灵活的资源处理方式,但目前对大多数生产部署而言,device plug-in 仍然是更成熟可用的标准。

当完整 GPU 分配超出工作负载实际需要时,子 GPU 分配技术能够提高硬件利用率。
时间切片允许多个工作负载通过轮流使用 GPU 的方式共享同一块卡,适合 GPU 使用呈间歇性的推理负载;
Multi-Instance GPU(MIG)则提供硬件级分区和显存隔离,把一块 GPU 切成多个拥有保底资源和隔离性能的小分片。
不同方案在隔离性、开销和调度复杂度之间各有权衡。

当模型超过单块 GPU 显存容量时,就必须使用多 GPU 推理。
张量并行把单个操作拆到多块 GPU 上,要求高带宽互连和紧密同步;
流水线并行把模型层分布到不同 GPU 上,在计算分布和顺序依赖导致的空泡开销之间做平衡;
数据并行则是在多块 GPU 上复制完整模型,同时处理不同 batch。
这些策略都要求跨 Pod、跨节点的精细编排:Kubernetes 提供调度原语,而具体的协调逻辑由运行时框架负责。

NVIDIA GPU Operator 把 GPU 管理整合进一个统一 operator 中,负责部署 device plug-in、feature discovery、监控(DCGM)和运行时组件。
这种通过 ClusterPolicy 资源进行声明式配置的方式,使 GPU 集群配置更简单,并保证所有节点上的 GPU 软件栈一致部署,相比手工安装各个组件,显著降低了运维复杂度。

在下一章中,我们会在本章 GPU 与基础设施内容的基础上,继续深入到生产可用性:包括部署策略、扩缩容模式、性能优化,以及大规模可靠运行 LLM 工作负载所需的运维最佳实践。

注释

  1. GPU 厂商(如 NVIDIA)通常以十进制 GB(base-10)来标称显存,而 Kubernetes 往往使用二进制 GiB(base-2)。二者差异不大,但值得注意:40 GB ≈ 37.25 GiB,80 GB ≈ 74.5 GiB。本章中统一使用 GB,以符合行业习惯。
  2. Expert parallelism 是一种专门用于 Mixture-of-Experts(MoE)架构的并行策略,其中不同的“专家”子网络会被分布到不同设备上。在 MoE 模型中,一个路由网络会把输入的不同部分送往不同专家,因此每次输入只会激活模型的一部分。虽然这种技术对某些模型架构很强大,但它超出了本书讨论范围。
  3. 参见 PyTorch 关于优化 CUDA 显存使用的官方文档。