<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>Jimmy Song – Kubernetes GPU 平台架构——多租、共享与隔离实践</title><link>https://jimmysong.io/zh/book/gpu-multi-tenancy-platforms/</link><description>Recent content in Kubernetes GPU 平台架构——多租、共享与隔离实践 on Jimmy Song</description><generator>Hugo -- gohugo.io</generator><language>zh</language><managingEditor>Jimmy Song</managingEditor><webMaster>Jimmy Song</webMaster><follow_challenge><feedId>51621818828612637</feedId><userId>59800919738273792</userId></follow_challenge><lastBuildDate>Sun, 24 May 2026 13:21:34 +0800</lastBuildDate><atom:link href="https://jimmysong.io/zh/book/gpu-multi-tenancy-platforms/index.xml" rel="self" type="application/rss+xml"/><item><title>基础：GPU 如何与 Kubernetes 结合</title><link>https://jimmysong.io/zh/book/gpu-multi-tenancy-platforms/foundations/</link><pubDate>Sat, 23 May 2026 06:46:30 +0000</pubDate><author>Jimmy Song</author><guid>https://jimmysong.io/zh/book/gpu-multi-tenancy-platforms/foundations/</guid><description>理解容器与 GPU 的基础结合方式，包括系统调用、cgroups、命名空间、设备插件，以及整数资源问题对 GPU 调度的影响。</description><content:encoded>
&lt;p&gt;本章将讨论以下几个问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;容器如何通过系统调用、cgroups 和命名空间工作&lt;/li&gt;
&lt;li&gt;为什么 GPU 打破了容器资源隔离中的若干基本假设&lt;/li&gt;
&lt;li&gt;设备插件如何将 GPU 接入 Kubernetes&lt;/li&gt;
&lt;li&gt;整数资源问题及其影响&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;理解这些问题，需要先回到 Linux 内核提供容器隔离能力的基本机制。&lt;/p&gt;
&lt;h2 id="系统调用内核的-api-接口"&gt;系统调用：内核的 API 接口&lt;/h2&gt;
&lt;p&gt;在 Linux 中，用户空间应用程序无法直接与硬件交互，所有访问都必须经过 Linux 内核，而系统调用就是这条路径上唯一合法的入口。系统调用本质上是内核对外暴露的一组预定义接口：应用程序请求文件、网络、进程或内存服务时，最终都要通过这些入口点进入内核。例如，当 Java 应用访问文件系统中的文件时，它实际发起的就是一次系统调用。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-1.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-1.webp" alt="图 1: 应用程序通过系统调用访问 Linux 内核并读取底层存储" data-caption="图 1: 应用程序通过系统调用访问 Linux 内核并读取底层存储"
width="2336"
height="946"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 1: 应用程序通过系统调用访问 Linux 内核并读取底层存储&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;内核作为应用程序和硬件之间的中介。当你的 Java 应用程序访问文件系统上的文件时，它向 Linux 内核发起一个系统调用。&lt;/li&gt;
&lt;li&gt;内核知道如何访问底层存储，并让你的应用程序检索文件。你可以把系统调用看作 API 调用。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;同样，Node.js 应用要发起网络连接，也必须通过系统调用进入内核，由内核代表它访问网络硬件。无论语言和运行时如何变化，真正与硬件交互的始终是内核，而不是用户空间中的应用代码。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-2.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-2.webp" alt="图 2: 通过 Linux 内核发起的网络连接请求以访问网络硬件" data-caption="图 2: 通过 Linux 内核发起的网络连接请求以访问网络硬件"
width="1838"
height="1206"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 2: 通过 Linux 内核发起的网络连接请求以访问网络硬件&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;从抽象上看，系统调用可以视为 Linux 内核的 API：它包含 &lt;a href="https://makelinux.github.io/kernel/map/" target="_blank" rel="noopener"&gt;300 多个按功能分组的入口点&lt;/a&gt;，有的用于文件操作，有的用于进程管理，也有的处理网络和内存。每个系统调用都有唯一的编号和明确的参数约定，因此即使是高级语言中的简单操作，例如 Node.js 里的一次网络请求，底层也会落到具体的 socket 系统调用上。正因为用户空间可以借助系统调用触达如此广泛的内核能力，系统调用既是功能入口，也是攻击面来源；尤其当操作涉及特权能力时，内核必须进一步约束应用能够消耗和影响的资源范围，这就引出了控制组，即 cgroups。&lt;/p&gt;
&lt;h2 id="控制组cgroups强制执行资源限制"&gt;控制组（cgroups）：强制执行资源限制&lt;/h2&gt;
&lt;p&gt;控制组是内核级机制，用于限制单个进程或一组进程可以消耗的 CPU、内存、I/O 等资源。假设一台机器上同时运行一个 JVM 应用和一个 Node.js 应用，你可以把它们分别放入不同的 cgroup 中，并为前者设置 256MB 内存上限和单核 CPU 配额，为后者设置另一组独立约束。每个 cgroup 都对应一个明确的资源边界，而边界的执行由内核负责。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-3.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-3.webp" alt="图 3: 通过 cgroups 为 JVM 和 Node.js 进程分别设置 CPU 与内存限制" data-caption="图 3: 通过 cgroups 为 JVM 和 Node.js 进程分别设置 CPU 与内存限制"
width="1790"
height="1368"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 3: 通过 cgroups 为 JVM 和 Node.js 进程分别设置 CPU 与内存限制&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;在这个例子中，我为 JVM 创建了一个控制组。&lt;/li&gt;
&lt;li&gt;我可以创建一个控制组，限制对 CPU、内存、网络带宽等的访问。&lt;/li&gt;
&lt;li&gt;每个进程都可以有自己的控制组。我可以为 Node.js 应用创建第二个控制组。&lt;/li&gt;
&lt;li&gt;我可以微调新控制组的设置，进一步限制该进程可用的资源。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;这意味着资源限制不是建议值，而是物理上被强制执行的边界。下面的配置展示了 cgroups 如何限制 CPU 与内存：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# CPU 限制 - 一个核心的 50%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="m"&gt;50000&lt;/span&gt; &amp;gt; /sys/fs/cgroup/cpu/myapp/cpu.cfs_quota_us
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="m"&gt;100000&lt;/span&gt; &amp;gt; /sys/fs/cgroup/cpu/myapp/cpu.cfs_period_us
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 内存限制 - 256MB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="m"&gt;268435456&lt;/span&gt; &amp;gt; /sys/fs/cgroup/memory/myapp/memory_limit_in_bytes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;借助这些限制，工作负载可以在资源层面被硬性隔离。不过，cgroups 解决的是“能用多少”的问题，而不是“能看到什么、能接触什么”的问题；后者依赖的则是另一类内核原语，即命名空间。&lt;/p&gt;
&lt;h2 id="命名空间隔离进程的可见性"&gt;命名空间：隔离进程的可见性&lt;/h2&gt;
&lt;p&gt;cgroups 控制进程可以消耗多少资源，命名空间则控制进程能看到什么。命名空间决定了一个进程认为自己处于系统中的哪个位置。例如，在网络命名空间下，进程只能看到自己的网络接口和流量，看不到命名空间之外的套接字或数据包；在挂载命名空间下，进程看到的是一个私有文件系统视图，它可能以为自己访问的是 /etc、/var 和 /home，但实际接触到的只是容器专属的 overlay 文件系统。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-4.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-4.webp" alt="图 4: 挂载命名空间隔离进程文件系统视图的工作方式" data-caption="图 4: 挂载命名空间隔离进程文件系统视图的工作方式"
width="2208"
height="1744"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 4: 挂载命名空间隔离进程文件系统视图的工作方式&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;自内核版本 5.6 起，有八种命名空间，挂载命名空间（mount namespace）就是其中之一。&lt;/li&gt;
&lt;li&gt;通过挂载命名空间，你可以让进程以为自己可以访问主机上的所有目录，但实际上并非如此。&lt;/li&gt;
&lt;li&gt;挂载命名空间用于隔离资源——在这个例子中是文件系统。&lt;/li&gt;
&lt;li&gt;每个进程可以看到相同的文件系统，但仍然与其他进程相互隔离。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;从 Linux 内核 5.6 开始，系统一共有八类命名空间，每一类隔离系统标识的一个维度。这样一来，同一台机器上的多个进程既可以拥有各自的资源限制，也可以拥有各自的系统视图，并在主观上“以为”自己独占整台机器。cgroups 与命名空间共同构成了容器隔离的基础，只是手工配置这些原语既繁琐又容易出错，这也是 Docker 等工具存在的原因。&lt;/p&gt;
&lt;h2 id="从内核原语到-docker"&gt;从内核原语到 Docker&lt;/h2&gt;
&lt;p&gt;Docker 提供了一种更适合开发者使用的容器管理方式：你不必手工编排系统调用、cgroups 和命名空间，只需定义镜像并启动容器，运行时便会在后台完成隔离环境的建立。当然，Docker 并不是唯一选择，&lt;a href="https://github.com/containers/podman" target="_blank" rel="noopener"&gt;Podman&lt;/a&gt; 和 &lt;a href="https://github.com/cri-o/cri-o" target="_blank" rel="noopener"&gt;CRI-O&lt;/a&gt; 等工具也在抽象这些内核能力。无论具体实现如何变化，这些平台的本质都是构建在系统调用、控制组和命名空间之上的编排层，而这些内核原语共同构成了容器轻量、隔离且安全的运行环境。在进入 GPU 主题之前，还需要先回顾内核如何管理 CPU 和内存，因为后文的对比正建立在这一点上。&lt;/p&gt;
&lt;h2 id="cpu-抢占式多任务处理"&gt;CPU 抢占式多任务处理&lt;/h2&gt;
&lt;p&gt;Linux 内核通过抢占式多任务处理来管理 CPU 调度。它快速中断正在运行的程序，以公平地分配执行时间并保持系统的响应性。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-5.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-5.webp" alt="图 5: CPU 抢占式多任务调度示意：单核时间片与多核并行" data-caption="图 5: CPU 抢占式多任务调度示意：单核时间片与多核并行"
width="2366"
height="1578"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 5: CPU 抢占式多任务调度示意：单核时间片与多核并行&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;在单核抢占式多任务处理中，CPU 一次执行一个任务。&lt;/li&gt;
&lt;li&gt;调度器抢占任务 A 并切换上下文到任务 B（橙色）。每个任务获得一个 CPU 时间片。&lt;/li&gt;
&lt;li&gt;随着更多任务的加入，调度器继续在它们之间轮转。任务 C（蓝色）现在加入了队列。&lt;/li&gt;
&lt;li&gt;有了多核处理器，真正的并行执行成为可能。任务可以在不同的核心上同时运行。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;当内核在任务之间切换时，会进行一次上下文切换：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;它保存正在运行的程序的完整状态，包括其寄存器、栈和内存映射，以便稍后可以从上次中断的地方精确恢复。&lt;/li&gt;
&lt;li&gt;然后，它加载下一个程序的已保存状态，并恢复执行，就像它从未暂停过一样。&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-6.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-6.webp" alt="图 6: 两个进程之间保存与恢复状态的上下文切换过程" data-caption="图 6: 两个进程之间保存与恢复状态的上下文切换过程"
width="1788"
height="1308"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 6: 两个进程之间保存与恢复状态的上下文切换过程&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;进程 1（上方）正在主动执行，而进程 2（下方）处于空闲状态，等待轮到自己执行。&lt;/li&gt;
&lt;li&gt;进程 1 保存其当前状态（步骤 1），然后进程 2 加载其之前保存的状态（步骤 2）以恢复执行。&lt;/li&gt;
&lt;li&gt;进程 2 先保存其状态（步骤 1），然后进程 1 加载其保存的状态（步骤 2）以从上次中断的地方继续。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;这套机制在 CPU 上既快速又可靠，因为 CPU 的硬件结构就是围绕可中断、可恢复、可调度而设计的。典型的支持能力包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;中断控制器来暂停执行&lt;/li&gt;
&lt;li&gt;用于保存/恢复状态的硬件支持&lt;/li&gt;
&lt;li&gt;用于调度的复杂控制逻辑&lt;/li&gt;
&lt;li&gt;用于加速切换的精密缓存&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;正因如此，现代 CPU 上的上下文切换通常只需几微秒，足以让多个进程表现出近似同时运行的效果。&lt;/p&gt;
&lt;h2 id="内存按页分配"&gt;内存：按页分配&lt;/h2&gt;
&lt;p&gt;内存分配则通过内核与内存管理单元（MMU）协同完成。当进程请求一段内存时：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;malloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 请求 1KB
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Linux 内核会执行如下步骤：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在物理内存中找到空闲页&lt;/li&gt;
&lt;li&gt;将它们映射到进程的虚拟地址空间&lt;/li&gt;
&lt;li&gt;返回一个指向虚拟地址的指针&lt;/li&gt;
&lt;li&gt;在页表中跟踪每一次分配&lt;/li&gt;
&lt;/ol&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-7.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-7.webp" alt="图 7: MMU 将虚拟地址转换为物理地址的页表映射过程" data-caption="图 7: MMU 将虚拟地址转换为物理地址的页表映射过程"
width="1752"
height="1376"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 7: MMU 将虚拟地址转换为物理地址的页表映射过程&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;CPU 尝试访问虚拟地址 123456，但如果没有 MMU，它不知道如何在物理内存中定位数据。&lt;/li&gt;
&lt;li&gt;CPU 将虚拟地址 123456 发送到 MMU，MMU 包含用于地址转换的页表。&lt;/li&gt;
&lt;li&gt;MMU 使用页表成功将虚拟地址 123456 转换为物理地址。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;对进程而言，它拿到的是一段连续的虚拟内存；在物理 RAM 中，这些页却可能是离散分布的。内核跟踪每一页的映射关系，负责回收未使用页面、换出到磁盘，并借助 cgroups 强制执行内存限制。如果某个进程超过了自己的内存 cgroup 配额：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="m"&gt;104857600&lt;/span&gt; &amp;gt; /sys/fs/cgroup/memory/myapp/memory.limit_in_bytes &lt;span class="c1"&gt;# 100MB 限制&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;内核的 OOM killer 会立即终止该进程。到这里为止，我们实际上回顾了容器所依赖的那套经典内核控制模型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;系统调用提供了对内核功能的受控访问&lt;/li&gt;
&lt;li&gt;cgroups 在内核的支持下强制执行硬性资源限制&lt;/li&gt;
&lt;li&gt;命名空间为进程可见的内容创建了隔离边界&lt;/li&gt;
&lt;li&gt;CPU 上下文切换在微秒级别完成，实现了公平的时间共享&lt;/li&gt;
&lt;li&gt;内存按页分配，具有完整的内核可见性和控制能力&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这套模型之所以有效，是因为 Linux 内核既看得见，也管得住，还能在资源越界时直接强制执行限制。问题在于，GPU 的工作方式并不遵循这套假设，而后续所有困难几乎都源于这一差异。&lt;/p&gt;
&lt;h2 id="cuda-基础理解上下文核函数和内存"&gt;CUDA 基础：理解上下文、核函数和内存&lt;/h2&gt;
&lt;p&gt;GPU 应用并不是一开始就运行在 GPU 上；它首先仍是普通的 CPU 进程，只是在需要执行大规模并行计算时，才通过 CUDA 与 GPU 通信。而这类通信需要管理一整套状态信息：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用哪块 GPU（如果有多块的话）&lt;/li&gt;
&lt;li&gt;GPU 上的内存分配&lt;/li&gt;
&lt;li&gt;已编译的 GPU 代码（核函数）&lt;/li&gt;
&lt;li&gt;CPU 与 GPU 之间的同步&lt;/li&gt;
&lt;li&gt;配置和设置&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些状态被 CUDA 打包为“上下文”。一个进程想要使用 GPU 时，首先要创建自己的 CUDA 上下文；这个上下文可以理解为进程与 GPU 之间建立的一次会话，其中包含：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;该进程特有的内存分配&lt;/li&gt;
&lt;li&gt;已加载的核函数代码（GPU 程序）&lt;/li&gt;
&lt;li&gt;GPU 状态（配置、设置）&lt;/li&gt;
&lt;li&gt;执行流和事件&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GPU 只能处理已经位于其设备内存中的数据，因此数据必须显式地从主机内存复制到与该上下文关联的 GPU 内存中；这一点与 CPU 直接访问系统内存的习惯完全不同。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-8.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-8.webp" alt="图 8: CPU 进程创建 CUDA 上下文并将数据复制到 GPU 内存" data-caption="图 8: CPU 进程创建 CUDA 上下文并将数据复制到 GPU 内存"
width="1820"
height="1404"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 8: CPU 进程创建 CUDA 上下文并将数据复制到 GPU 内存&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;在 CPU 上运行的进程 A 需要在 GPU 上执行计算。进程存在于 CPU 域中，但需要 GPU 资源进行并行处理。&lt;/li&gt;
&lt;li&gt;当进程 A 想在 GPU 上执行时，内存被复制过去，并为该进程创建了一个 GPU 上下文（Context A）。&lt;/li&gt;
&lt;li&gt;GPU 可以管理多个上下文，允许不同的进程通过上下文切换来利用 GPU 资源。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;下面的例子展示了数据在 CPU 与 GPU 之间如何显式移动：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cupy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;cp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 这些数据存在于系统内存中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;cpu_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 这将数据复制到 GPU 内存中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;gpu_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cpu_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 数据传输在此发生&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 现在 GPU 可以处理它了&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gpu_data&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="c1"&gt;# 计算在 GPU 上进行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 要获取结果，需要再次复制&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;cpu_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# 传回系统内存&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：multiplication.py&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;每一次 CPU 与 GPU 之间的数据移动都需要穿过两者之间的物理互连，因此会带来额外延迟。这也是 GPU 编程普遍强调“减少数据搬运”的原因：很多时候，真正昂贵的不是计算本身，而是把数据送到 GPU 和再取回来的过程。&lt;/p&gt;
&lt;h2 id="cuda-核函数gpu-程序"&gt;CUDA 核函数：GPU 程序&lt;/h2&gt;
&lt;p&gt;“核函数”（kernel）这个词在这里很容易与 Linux 内核混淆，但在 GPU 语境中，它指的是运行在 GPU 上的函数。比如下面这段代码：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 这段 Python 代码在 CPU 上运行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cupy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;cp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 这会在 GPU 上触发一个核函数启动&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="c1"&gt;# 加法操作通过核函数在 GPU 上执行&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：add.py&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;这里的加法操作会触发一个预先实现好的 GPU 核函数，它通常经历以下过程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;从上下文的核函数缓存中加载&lt;/li&gt;
&lt;li&gt;在数千个 GPU 核心上并行执行&lt;/li&gt;
&lt;li&gt;运行到完成，不会被中断&lt;/li&gt;
&lt;li&gt;将结果返回给 CPU&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果同一个上下文中连续发起多个操作，情况如下：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 这段 Python 代码在 CPU 上运行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cupy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;cp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 这会在 GPU 上触发一个核函数启动&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="c1"&gt;# 核函数 1：加法核函数执行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 这会触发另一个核函数启动&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="c1"&gt;# 核函数 2：减法核函数执行&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：add.py&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;每个操作（+、-、* 等）都会触发一个独立的 CUDA 核函数，而这些核函数通常在同一个 CUDA 上下文中依次执行。这个上下文在 Python 进程的生命周期内持续存在，并保存如下状态：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你所有的 GPU 内存分配（x、y、z、w 数组）&lt;/li&gt;
&lt;li&gt;已编译的核函数（加法、减法）&lt;/li&gt;
&lt;li&gt;执行状态&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;与 CPU 不同的是，一旦某个核函数被提交到 GPU 执行，它通常会一直运行到结束，在此期间不会像 CPU 线程那样被轻易中断。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-9.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-9.webp" alt="图 9: 多个 CUDA 上下文在 GPU 上轮转执行核函数" data-caption="图 9: 多个 CUDA 上下文在 GPU 上轮转执行核函数"
width="1794"
height="1226"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 9: 多个 CUDA 上下文在 GPU 上轮转执行核函数&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;上下文 A 正在 GPU 上主动执行一个核函数。&lt;/li&gt;
&lt;li&gt;上下文切换发生：上下文 A 继续执行，然后来自上下文 B（橙色）的核函数被调度并执行。&lt;/li&gt;
&lt;li&gt;上下文 A（黄色）、上下文 B（橙色）和上下文 C（绿色）轮流执行它们的核函数。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;如果习惯了 CPU 调度模型，这一点会显得反直觉，因为 CPU 可以通过时间片在单核上“并发”运行大量进程：内核快速轮转它们，给每个进程分配几毫秒执行窗口。&lt;/p&gt;
&lt;div class="alert alert-tip-container"&gt;
&lt;div class="alert-tip-title px-2"&gt;
并发 ≠ 并行
&lt;/div&gt;
&lt;div class="alert-tip px-2"&gt;
&lt;p&gt;通过时间片机制，操作系统可以在一个 CPU 核心上并发运行数百个进程。内核会快速在这些进程之间切换，给每个进程分配几毫秒的执行时间，从而让它们看起来像是在同时运行。&lt;/p&gt;
&lt;p&gt;在单核 CPU 上，任意时刻实际上只有一个进程正在执行；所谓“同时运行”，本质上是内核通过抢占式调度和上下文切换实现的时间复用（time-sharing）。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;但在 GPU 上，来自不同进程的核函数通常不能同时执行，至少默认情况下如此；这是上下文级别的串行化。每个进程会创建自己的 CUDA 上下文，而同一时刻通常只有一个上下文处于活动状态，因此不同上下文中的核函数并不真正并行。另一方面，在单个 CUDA 上下文内部，可以创建多个流（stream）作为独立的 GPU 操作队列，例如：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cupy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;cp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 不使用流 - 顺序执行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 这些操作一个接一个执行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;result1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="c1"&gt;# 核函数 1 执行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;result2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="c1"&gt;# 核函数 2 等待核函数 1 完成&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 使用流 - 并发执行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;stream1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;stream2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;stream1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="c1"&gt;# 核函数 1 在 stream1 中&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;stream2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;D&lt;/span&gt; &lt;span class="c1"&gt;# 核函数 2 在 stream2 中 - 与核函数 1 并发运行！&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：streams.py&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;这种并发执行依赖于 &lt;strong&gt;Hyper-Q&lt;/strong&gt;，它为现代 GPU 提供多个硬件工作队列；在 Kepler 及以后的架构上，通常有 32 个队列。没有 Hyper-Q 时，即使处于不同流中的核函数，也常因底层硬件队列受限而被串行化。即便如此，流和 MPS 也没有改变一个根本事实：单个核函数仍然通常不可抢占。要理解原因，需要回到 GPU 的硬件架构本身。&lt;/p&gt;
&lt;h2 id="为什么-gpu-不支持核函数抢占"&gt;为什么 GPU 不支持核函数抢占&lt;/h2&gt;
&lt;p&gt;为什么 GPU 不能像 CPU 一样随时暂停和恢复执行，核心原因在于硬件设计目标不同。理解这一点，需要先看 ALU（算术逻辑单元）：它负责执行加法、乘法和比较等数学运算。CPU 和 GPU 都依赖 ALU 计算，但两者为这些 ALU 配套的控制逻辑差异极大。CPU 要运行浏览器、数据库、操作系统和其他高度通用的程序，这些程序包含大量分支、不可预测的内存访问以及复杂依赖，因此即便只是让 2 到 4 个 ALU 持续忙碌，也需要非常复杂的控制逻辑来：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;预测代码将走哪个分支&lt;/li&gt;
&lt;li&gt;重新排序指令以避免停顿&lt;/li&gt;
&lt;li&gt;处理数据未就绪时的缓存未命中&lt;/li&gt;
&lt;li&gt;管理中断和上下文切换&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这也是 CPU 不能简单堆叠更多 ALU 的原因。要让大量 ALU 在通用代码上保持高利用率，就必须配套成倍增加分支预测、指令缓冲、缓存管理和乱序执行能力，而这些控制逻辑本身会迅速膨胀。即使在只有 2 到 4 个 ALU 的情况下，CPU 也已经把大量晶体管花在分支预测器、指令解码器、重排序缓冲区和缓存上；如果继续扩展到 8 个 ALU，相关控制复杂度也会同步上升。GPU 的设计前提则完全不同：它主要处理可预测的并行计算，例如矩阵乘法或像素处理，在这些场景中，大量数据执行的是同一种操作，分支和不可预测内存访问都大幅减少。正因如此，一个 GPU 流式多处理器中的 64 到 128 个 ALU 可以共享较少的控制逻辑，由单一控制单元把同一条指令广播给所有 ALU。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-10.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-10.webp" alt="图 10: CPU 与 GPU 的架构对比" data-caption="图 10: CPU 与 GPU 的架构对比"
width="1484"
height="1194"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 10: CPU 与 GPU 的架构对比&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;这就是为什么 CPU 核心往往只有 2 到 4 个 ALU，而 GPU 的流式多处理器却可以拥有 64 到 128 个。GPU 把更多晶体管预算投入计算单元而不是控制单元，代价则是缺少 CPU 那样成熟的中断、抢占和暂停恢复能力。换句话说，GPU 之所以能够塞入如此多的 ALU，恰恰是因为它放弃了支持通用抢占所需的大量控制逻辑。&lt;/p&gt;
&lt;h2 id="gpu-内存完全不同的模型"&gt;GPU 内存：完全不同的模型&lt;/h2&gt;
&lt;p&gt;如果说 GPU 的执行模型已经偏离了 CPU 的直觉，那么 GPU 的内存模型则进一步打破了系统管理员对“内存管理”的既有理解。首先，物理 GPU 内存就是显卡上的 VRAM，例如 Tesla T4 提供 16GB，A100 则可能是 40GB 或 80GB，这与服务器中的 RAM 一样构成绝对容量上限。不同之处在于，系统内存由 Linux 内核逐页跟踪，而 GPU 内存管理主要发生在 NVIDIA 驱动内部，对 Linux 内核几乎不可见。进程一旦创建 CUDA 上下文并开始分配显存，每个上下文都会看到整块 GPU 的总内存空间，例如 T4 的 16GB，但它实际上只能访问自己分配到的那一部分。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-11.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-11.webp" alt="图 11: 多个进程上下文共享同一 GPU 总显存视图但彼此隔离" data-caption="图 11: 多个进程上下文共享同一 GPU 总显存视图但彼此隔离"
width="2138"
height="894"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 11: 多个进程上下文共享同一 GPU 总显存视图但彼此隔离&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;进程 A 在 GPU 上创建上下文 A，16GB 总内存可用。上下文看到全部 16GB 都可分配。&lt;/li&gt;
&lt;li&gt;进程 B 尝试在上下文 A 仍在驻留时创建上下文 B。虽然两个上下文都看到 16GB 可分配，但它们无法访问其他上下文的内存。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;假设有两个进程先后启动：&lt;/p&gt;
&lt;p&gt;进程 A 启动，创建上下文 A：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;看到：16GB 总 GPU 内存&lt;/li&gt;
&lt;li&gt;分配：4GB 用于神经网络权重&lt;/li&gt;
&lt;li&gt;可访问：仅它自己的 4GB&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;进程 B 启动，创建上下文 B：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;看到：16GB 总 GPU 内存（与 A 相同的视图！）&lt;/li&gt;
&lt;li&gt;分配：2GB 用于图像处理&lt;/li&gt;
&lt;li&gt;可访问：仅它自己的 2GB&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;此时真实状态是：总共用了 7GB，剩余 9GB，但两个进程都“以为”自己面对的是完整的 16GB 地址空间。对于 Linux 内核而言，这部分状态并不存在于标准内存管理视图中，没有 GPU 版的 /proc/meminfo，没有对应的 cgroup 统计，也没有可以直接强制执行的内存上限。更进一步地，CUDA 的分配方式还不是“按请求精确分配”，而是为了性能使用池化分配器：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cupy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;cp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 第一次分配 - 你请求 8KB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;arr1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 1000 个浮点数 x 8 字节 = 8KB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 但 CUDA 实际上从 GPU 预留了一个 2MB 的池&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# nvidia-smi 现在显示使用了 2MB，而不是 8KB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 第二次分配&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;arr2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 另一个 8KB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 这使用了现有的池 - 没有新的 GPU 分配&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# nvidia-smi 仍然显示使用了 2MB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 很久以后...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;huge_array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000_000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 需要 80MB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 现在 CUDA 预留了另一个大块，可能是 128MB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# nvidia-smi 显示使用了 130MB（2MB + 128MB）&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：memory.py&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;这种池化策略从性能角度看完全合理，因为 GPU 内存分配本身开销很高，驱动倾向于先拿到较大的内存块，再在内部复用。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-12.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-12.webp" alt="图 12: GPU 内存池化图示，展示 CUDA 如何以大块（池）而非精确请求大小来分配内存" data-caption="图 12: GPU 内存池化图示，展示 CUDA 如何以大块（池）而非精确请求大小来分配内存"
width="1942"
height="1356"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 12: GPU 内存池化图示，展示 CUDA 如何以大块（池）而非精确请求大小来分配内存&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;但对监控和容量规划而言，这会造成严重偏差：你可能在 nvidia-smi 中看到 2GB 已分配，而应用真实持有的有效数据只有 100MB。也就是说，GPU 内存的“可见使用量”与“逻辑有效载荷”之间，天然存在一个由驱动和分配器引入的落差。&lt;/p&gt;
&lt;h2 id="关键差异"&gt;关键差异&lt;/h2&gt;
&lt;p&gt;到这里，可以更清楚地对比 CPU/内核模型与 GPU/驱动模型之间的差异。&lt;/p&gt;
&lt;p&gt;当你的 CPU 代码运行时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个操作都通过系统调用传递给 Linux 内核&lt;/li&gt;
&lt;li&gt;Linux 内核可以在任何微秒抢占你的进程&lt;/li&gt;
&lt;li&gt;进程之间的上下文切换只需几微秒&lt;/li&gt;
&lt;li&gt;Linux 内核能看到每一条指令、每一次内存访问&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当你的 GPU 代码运行时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CUDA API 调用传递给 NVIDIA 驱动（专有的、闭源的）&lt;/li&gt;
&lt;li&gt;核函数运行到完成，无法被抢占&lt;/li&gt;
&lt;li&gt;上下文切换需要几毫秒（比 CPU 慢 1000 倍）&lt;/li&gt;
&lt;li&gt;Linux 内核对 GPU 上发生的事情完全不可见&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当 Linux 内核管理系统内存时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;按页分配（通常 4KB 的页）&lt;/li&gt;
&lt;li&gt;每次分配都在页表中跟踪&lt;/li&gt;
&lt;li&gt;cgroups 可以强制执行硬性限制&lt;/li&gt;
&lt;li&gt;可以换出到磁盘、压缩、回收&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;相比之下，GPU 内存由驱动管理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;以大池分配（一次数兆字节）&lt;/li&gt;
&lt;li&gt;驱动跟踪分配，Linux 内核看不到它们&lt;/li&gt;
&lt;li&gt;不可能有 cgroup 强制执行&lt;/li&gt;
&lt;li&gt;内存不能被换出或回收&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也正因为如此，容器赖以成立的许多内核原语，在 GPU 上都没有直接对应物：&lt;/p&gt;
&lt;p&gt;没有 GPU cgroups：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 这个不存在&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; 4G &amp;gt; /sys/fs/cgroup/gpu/myapp/gpu-memory_limit&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Linux 内核之所以无法限制 GPU 内存，是因为它根本看不到这些分配。同样，也没有 GPU 命名空间可言，&lt;code&gt;/dev/nvidia0&lt;/code&gt; 对所有能访问该设备文件的进程都是同一个对象，内核无法为不同进程构造独立的虚拟 GPU 视图。当进程发起 CUDA 调用时，Linux 内核能观察到的往往只有对 NVIDIA 驱动的系统调用，例如 &lt;code&gt;ioctl()&lt;/code&gt;；进入驱动内部之后，GPU 的调度、内存与执行状态都变得不透明。于是，Linux 内核无法像管理 CPU 和内存那样跟踪 GPU 利用率、显存占用、资源公平性或服务质量。这正是问题的根部：容器依赖的是一套在 GPU 上并不存在的内核控制原语，而 Kubernetes 中的许多限制也都由此而来。&lt;/p&gt;
&lt;h2 id="kubernetes-与容器运行时接口"&gt;Kubernetes 与容器运行时接口&lt;/h2&gt;
&lt;p&gt;有了这些背景，再看 Kubernetes 中的容器运行路径就会更清晰。当你部署一个 Pod 时，真正负责在节点上把它跑起来的是 kubelet，但 kubelet 并不直接创建容器，而是通过容器运行时接口（CRI）与 containerd、CRI-O 等运行时通信。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-13.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-13.webp" alt="图 13: kubelet 通过 CRI 在节点上创建并启动 Pod 的流程" data-caption="图 13: kubelet 通过 CRI 在节点上创建并启动 Pod 的流程"
width="2058"
height="1608"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 13: kubelet 通过 CRI 在节点上创建并启动 Pod 的流程&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;部署请求被发送到 Kubernetes API（由船舵图标表示），一个空节点准备好接收工作负载。&lt;/li&gt;
&lt;li&gt;节点上的 kubelet 轮询 Kubernetes API。API 服务器响应该节点上需要调度的 Pod 规格。&lt;/li&gt;
&lt;li&gt;kubelet 接收 Pod 规格并与 CRI/CSI/CNI 交互。&lt;/li&gt;
&lt;li&gt;容器（以粉红/红色显示）现在运行在节点上。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;当一个 Pod 被调度到节点后，kubelet 会从 API 服务器读取其规格，并把这个抽象描述转化为真实的容器环境。它先调用 CRI 创建 Pod sandbox，为同一个 Pod 下的多个容器建立共享环境，包括共享的网络命名空间、主机名和卷。然后，kubelet 针对 Pod 中的每个容器依次完成镜像检查、镜像拉取、层解压和文件系统准备，并把资源请求翻译成实际的 cgroup 配置。例如，当 Pod 规格包含如下限制时：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;500m&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 半个 CPU 核心&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;256Mi&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 256 MiB 内存&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：resources.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;CRI 创建相应的 cgroups：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# CPU cgroup - 限制为一个核心的 50%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/sys/fs/cgroup/cpu/kubepods/pod-UUID/container-id/cpu.cfs_quota_us: &lt;span class="m"&gt;50000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/sys/fs/cgroup/cpu/kubepods/pod-UUID/container-id/cpu.cfs_period_us: &lt;span class="m"&gt;100000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Memory cgroup - 限制为 256MiB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/sys/fs/cgroup/memory/kubepods/pod-UUID/container-id/memory_limit_in_bytes: &lt;span class="m"&gt;268435456&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;CRI 会进一步通过命名空间建立隔离：每个容器拥有自己的挂载命名空间和 PID 命名空间，同时加入所属 Pod 的共享网络命名空间。最后，运行时执行容器入口点，进程开始运行，而内核在整个生命周期中持续强制执行前面建立好的 cgroup 与命名空间边界。换言之，CRI 的职责就是把 Kubernetes 中的 Pod 规格翻译成 Linux 内核真正能够理解和执行的原语。&lt;/p&gt;
&lt;h2 id="kubernetes-中的-gpu-问题"&gt;Kubernetes 中的 GPU 问题&lt;/h2&gt;
&lt;p&gt;GPU 的情况则不同，它在 Kubernetes 中带来两个根本问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;调度器需要知道哪些节点有可用的 GPU。但 Kubernetes 不知道什么是 GPU，因为它原生只理解 CPU 和内存。&lt;/li&gt;
&lt;li&gt;即使我们将 Pod 调度到 GPU 节点，容器也是被隔离的。它看不到 &lt;code&gt;/dev/nvidia0&lt;/code&gt; 或其他 GPU 设备文件。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，系统必须同时解决三个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使 GPU 设备文件在容器内可见&lt;/li&gt;
&lt;li&gt;加载正确的库来与 GPU 通信&lt;/li&gt;
&lt;li&gt;设置 CUDA 上下文&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从实现角度看，GPU 集成可以视为一个四层结构，每一层都解决一个明确问题，同时也引入新的复杂性。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-14.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-14.webp" alt="图 14: 四层 GPU 集成架构图，展示从 GPU 硬件到 Kubernetes 调度的每一层" data-caption="图 14: 四层 GPU 集成架构图，展示从 GPU 硬件到 Kubernetes 调度的每一层"
width="1246"
height="1196"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 14: 四层 GPU 集成架构图，展示从 GPU 硬件到 Kubernetes 调度的每一层&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;最底层是 GPU 硬件及其驱动。以下是本书所使用的 minikube 环境中的实际硬件与运行时信息：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ minikube ssh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Mon Aug &lt;span class="m"&gt;4&lt;/span&gt; 12:35:11 &lt;span class="m"&gt;2025&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-----------------------------------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; NVIDIA-SMI 550.54.15 Driver Version: 550.54.15 CUDA Version: 12.4 &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt;-----------------------------------------------------------------------------&lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; GPU Name Persistence-M &lt;span class="p"&gt;|&lt;/span&gt; Bus-Id Disp.A &lt;span class="p"&gt;|&lt;/span&gt; Volatile Uncorr. ECC &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; Fan Temp Perf Pwr:Usage/Cap &lt;span class="p"&gt;|&lt;/span&gt; Memory-Usage &lt;span class="p"&gt;|&lt;/span&gt; GPU-Util Compute M. &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; MIG M. &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="o"&gt;=============================================================================&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; Tesla T4 Off &lt;span class="p"&gt;|&lt;/span&gt; 00000000:00:04.0 Off &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; N/A 37C P8 9W / 70W &lt;span class="p"&gt;|&lt;/span&gt; 0MiB / 15360MiB &lt;span class="p"&gt;|&lt;/span&gt; 0% Default &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-----------------------------------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; N/A &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+---------------------------------------------------------+----------------------+----------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-----------------------------------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; Processes: &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; GPU GI CI PID Type Process name GPU Memory &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; ID ID Usage &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="o"&gt;=============================================================================&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; No running processes found &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-----------------------------------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-container-runtime --version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NVIDIA Container Runtime version 1.13.5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;commit: 6b8589dcb4dead72ab64f14a5912886e6165c079
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;spec: 1.1.0-rc.2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;runc version 1.1.5+ds1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;commit: 1.1.5+ds1-1+deb12u1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;spec: 1.0.2-dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go: go1.19.8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;libseccomp: 2.5.4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可以看到，节点上有一块 Tesla T4，并且 NVIDIA 驱动、容器运行时工具链都已经就绪。NVIDIA 驱动在这里承担的是桥梁角色：它把 GPU 硬件暴露为 Linux 能够处理的设备接口。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ls -la /dev/nvidia*
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;crw-rw-rw- &lt;span class="m"&gt;1&lt;/span&gt; root root 195, &lt;span class="m"&gt;254&lt;/span&gt; Aug &lt;span class="m"&gt;4&lt;/span&gt; 11:56 /dev/nvidia-modeset
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;crw-rw-rw- &lt;span class="m"&gt;1&lt;/span&gt; root root 241, &lt;span class="m"&gt;0&lt;/span&gt; Aug &lt;span class="m"&gt;4&lt;/span&gt; 11:40 /dev/nvidia-uvm
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;crw-rw-rw- &lt;span class="m"&gt;1&lt;/span&gt; root root 241, &lt;span class="m"&gt;1&lt;/span&gt; Aug &lt;span class="m"&gt;4&lt;/span&gt; 11:40 /dev/nvidia-uvm-tools
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;crw-rw-rw- &lt;span class="m"&gt;1&lt;/span&gt; root root 195, &lt;span class="m"&gt;0&lt;/span&gt; Aug &lt;span class="m"&gt;4&lt;/span&gt; 11:40 /dev/nvidia0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;crw-rw-rw- &lt;span class="m"&gt;1&lt;/span&gt; root root 195, &lt;span class="m"&gt;255&lt;/span&gt; Aug &lt;span class="m"&gt;4&lt;/span&gt; 11:40 /dev/nvidiactl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/dev/nvidia-caps:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;total &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;drwxr-xr-x &lt;span class="m"&gt;2&lt;/span&gt; root root &lt;span class="m"&gt;80&lt;/span&gt; Aug &lt;span class="m"&gt;4&lt;/span&gt; 11:40 .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;drwxr-xr-x &lt;span class="m"&gt;15&lt;/span&gt; root root &lt;span class="m"&gt;3240&lt;/span&gt; Aug &lt;span class="m"&gt;4&lt;/span&gt; 11:56 ..
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cr-------- &lt;span class="m"&gt;1&lt;/span&gt; root root 244, &lt;span class="m"&gt;1&lt;/span&gt; Aug &lt;span class="m"&gt;4&lt;/span&gt; 11:40 nvidia-cap1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cr--r--r-- &lt;span class="m"&gt;1&lt;/span&gt; root root 244, &lt;span class="m"&gt;2&lt;/span&gt; Aug &lt;span class="m"&gt;4&lt;/span&gt; 11:40 nvidia-cap2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;其中，&lt;code&gt;/dev/nvidia0&lt;/code&gt; 对应 Tesla T4 设备本身，&lt;code&gt;/dev/nvidiactl&lt;/code&gt; 负责跨 GPU 的控制操作。CUDA Driver API 正是通过这些设备接口与驱动协同工作，从而把 GPU 计算能力提供给应用程序。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-15.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-15.webp" alt="图 15: NVIDIA 驱动架构图，展示内核驱动如何通过设备文件暴露 CUDA Driver API" data-caption="图 15: NVIDIA 驱动架构图，展示内核驱动如何通过设备文件暴露 CUDA Driver API"
width="1722"
height="1190"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 15: NVIDIA 驱动架构图，展示内核驱动如何通过设备文件暴露 CUDA Driver API&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;问题在于，这些设备文件只存在于主机上，而容器运行时本身并不理解 GPU，也不会主动把它们暴露给容器。为了解决这一点，需要 &lt;a href="https://github.com/NVIDIA/nvidia-container-toolkit" target="_blank" rel="noopener"&gt;NVIDIA Container Toolkit&lt;/a&gt; 介入。GPU 工作负载的特殊性在于：容器本应与主机隔离，但 GPU 使用又要求一定程度的直接硬件访问。NVIDIA Container Toolkit 通过拦截容器创建流程，并有选择地把 &lt;code&gt;/dev/nvidia0&lt;/code&gt; 之类的设备文件、驱动库以及 &lt;code&gt;NVIDIA_VISIBLE_DEVICES&lt;/code&gt; 等环境变量挂载或注入到容器中，来完成这种“受控突破”。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-16.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-16.webp" alt="图 16: NVIDIA Container Toolkit 工作流，展示它如何拦截容器创建以挂载 GPU 设备文件、驱动库并设置环境变量" data-caption="图 16: NVIDIA Container Toolkit 工作流，展示它如何拦截容器创建以挂载 GPU 设备文件、驱动库并设置环境变量"
width="1884"
height="1358"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 16: NVIDIA Container Toolkit 工作流，展示它如何拦截容器创建以挂载 GPU 设备文件、驱动库并设置环境变量&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;这样一来，应用代码本身无需修改，却能够在容器中直接使用主机上的 Tesla T4。不过，容器运行时只解决了“如何访问 GPU”的问题，Kubernetes 仍然需要先知道“GPU 存不存在、该把 Pod 调度到哪里”。这正是&lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/" target="_blank" rel="noopener"&gt;设备插件框架&lt;/a&gt;的职责。设备插件是 Kubernetes 处理非原生硬件资源的标准方式，而集群中的 &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/" target="_blank" rel="noopener"&gt;NVIDIA 设备插件&lt;/a&gt;对外实现的是一个简单的 gRPC 接口：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// 设备插件 gRPC 服务&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DevicePlugin&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;ListAndWatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Empty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;returns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stream&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ListAndWatchResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Allocate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AllocateRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;returns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AllocateResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：device-plugin-interface.go&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;这个插件充当 Tesla T4 硬件与 Kubernetes 资源模型之间的翻译层。它通过 NVML 发现节点上的 GPU，再把这些设备公告为可调度资源；注册完成后，插件通过 ListAndWatch 流持续向 kubelet 报告设备状态。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-17.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-17.webp" alt="图 17: 设备插件工作流程图" data-caption="图 17: 设备插件工作流程图"
width="2034"
height="1614"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 17: 设备插件工作流程图&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;DaemonSet 将 NVIDIA Device Plugin 部署到所有有 GPU 资源的节点。&lt;/li&gt;
&lt;li&gt;注册过程建立设备插件与 kubelet 之间的通信以管理 GPU 资源。&lt;/li&gt;
&lt;li&gt;设备插件发现节点上的 GPU，但不会立即报告给 kubelet。&lt;/li&gt;
&lt;li&gt;kubelet 从设备插件查询 GPU。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;kubelet 接收到这些信息后，会相应更新节点容量和可分配资源：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;allocatable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：resources.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;于是，原本物理存在的一块 Tesla T4，就在 Kubernetes 里被表示为 &lt;code&gt;nvidia.com/gpu: 1&lt;/code&gt;。设备完成注册后，Pod 便可以像请求其他资源一样请求它：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：resources.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;从此以后，Kubernetes 就可以把 GPU 当作一种扩展资源来调度。不过，与 CPU 和内存不同，GPU 在默认语义下不是可细粒度共享的资源，而是整数资源；一个 Pod 要么拿到 1，要么拿不到。这种设计也让设备插件获得了比普通资源控制更大的权力：在 CPU 和内存场景下，最终裁决权在 Linux 内核和 cgroups 手中；而在 GPU 场景下，插件本身成为硬件分配的核心权威。它负责资源发现，通过 ListAndWatch 持续上报健康状态；它负责分配决策，通过 Allocate 精确构造容器所需的设备、挂载和环境变量。例如，当 Pod 请求 &lt;code&gt;nvidia.com/gpu: 1&lt;/code&gt; 时，Allocate 返回的并不只是一个“分配成功”标记，而是类似下面这样的具体配置：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Allocate 响应示例&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;AllocateResponse&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ContainerResponses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ContainerAllocateResponse&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Devices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;DeviceSpec&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ContainerPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/dev/nvidia0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HostPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/dev/nvidia0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ContainerPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/dev/nvidiactl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HostPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/dev/nvidiactl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Envs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;NVIDIA_VISIBLE_DEVICES&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：device-plugin-interface.go&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;这种控制并不止于分配本身，还延伸到资源生命周期的清理阶段。与 Linux 内核管理 CPU 时间片不同，设备插件需要自行编排整个 GPU 使用过程：挂载设备、设置环境变量、退出时清理状态。它的局限性也同样明显：Kubernetes 核心组件，例如调度器、kubelet 与资源配额控制器，看到的只有 &lt;code&gt;nvidia.com/gpu: 1&lt;/code&gt; 这样的抽象数量，而无法理解显存使用量、无法设置服务质量策略、无法做真正的抢占与公平共享，也无法直接观测 GPU 的真实利用率。&lt;/p&gt;
&lt;h2 id="运行-gpu-pod端到端"&gt;运行 GPU Pod：端到端&lt;/h2&gt;
&lt;p&gt;有了前面的组件背景，现在可以顺着一次实际部署过程，把 GPU Pod 从创建到真正访问 GPU 的路径完整串起来。下面是一个最小测试 Pod：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gpu-test&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cuda-container&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nvidia/cuda:11.0-base&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;nvidia-smi&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：pod.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;当这个 Pod 被提交到集群后，Kubernetes 调度器会立即开始工作。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-18.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-18.webp" alt="图 18: Kubernetes 从 API Server 到调度器完成 GPU Pod 调度的流程" data-caption="图 18: Kubernetes 从 API Server 到调度器完成 GPU Pod 调度的流程"
width="2128"
height="1600"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 18: Kubernetes 从 API Server 到调度器完成 GPU Pod 调度的流程&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;部署请求被发送到 Kubernetes API 服务器。&lt;/li&gt;
&lt;li&gt;API 服务器接收部署请求，并将资源存储在 etcd 中。&lt;/li&gt;
&lt;li&gt;控制器管理器继续创建 ReplicaSet 和 Pod。Pod 处于待定状态并被添加到调度器的队列中。&lt;/li&gt;
&lt;li&gt;调度器经过两个阶段：过滤谓词来决定将节点分配到哪里。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;调度器看到 Pod 请求 &lt;code&gt;nvidia.com/gpu: 1&lt;/code&gt;，便把它当作扩展资源进行过滤和匹配：它遍历节点的可分配资源，寻找至少有 1 个可用 &lt;code&gt;nvidia.com/gpu&lt;/code&gt; 的节点，最终发现 minikube 节点满足条件，并通过 API 服务器把该 Pod 绑定到这个节点。节点上的 kubelet 监视到新的绑定事件后，会读取 Pod 规格，并注意到该 Pod 请求了一个并非原生的资源，于是转而向设备插件发起 Allocate 请求：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;AllocateRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ContainerRequests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ContainerAllocateRequest&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DevicesIDs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;&amp;#34;GPU-abc123&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 特定的 GPU 标识符&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：device-plugin-interface.go&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;NVIDIA 设备插件收到请求后，会返回一组足以让容器访问 GPU 的精确指令：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;AllocateResponse&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ContainerResponses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ContainerAllocateResponse&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Devices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;DeviceSpec&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 挂载 GPU 设备文件&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ContainerPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/dev/nvidia0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HostPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/dev/nvidia0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 挂载控制接口&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ContainerPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/dev/nvidiactl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HostPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/dev/nvidiactl&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 挂载统一内存接口&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;ContainerPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/dev/nvidia-uvm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;HostPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/dev/nvidia-uvm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Envs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 告诉 NVIDIA 运行时使用哪块 GPU&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;NVIDIA_VISIBLE_DEVICES&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 启用 CUDA 支持&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;NVIDIA_DRIVER_CAPABILITIES&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;compute,utility&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：device-plugin-interface.go&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;kubelet 随后把这些分配结果连同原始 Pod 规格一起交给 CRI。容器运行时此时需要创建一个既满足标准隔离要求、又具备 GPU 访问能力的容器环境：先建立 CPU 和内存 cgroups，再设置文件系统与进程命名空间，然后把主机上的 GPU 设备文件挂载到容器中，并写入相关环境变量，告诉 NVIDIA 运行时这个容器应当使用哪块 GPU。到了容器即将启动的时刻，另一个关键组件开始工作，即 NVIDIA 容器运行时钩子。它位于 containerd 与容器之间，会在启动阶段自动执行。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-19.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/foundations/f1-19.webp" alt="图 19: kubelet、设备插件与容器运行时协作注入 GPU 访问能力" data-caption="图 19: kubelet、设备插件与容器运行时协作注入 GPU 访问能力"
width="2110"
height="1592"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 19: kubelet、设备插件与容器运行时协作注入 GPU 访问能力&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;kubelet 轮询需要 GPU 资源的新 Pod 分配。&lt;/li&gt;
&lt;li&gt;节点上的 NVIDIA Device Plugin 准备为传入的 Pod 处理 GPU 资源分配。&lt;/li&gt;
&lt;li&gt;kubelet 通过 CRI 创建容器&lt;/li&gt;
&lt;li&gt;工具包为容器启用 GPU 设备挂载、NVIDIA 驱动库、环境变量注入和设备 cgroups 管理。&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;这个运行时钩子在容器启动前会完成如下工作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;检查 &lt;code&gt;NVIDIA_VISIBLE_DEVICES&lt;/code&gt; 环境变量。如果不存在，它什么都不做，让容器正常启动&lt;/li&gt;
&lt;li&gt;如果变量存在，它会查询主机上安装的 GPU 库，确定与当前驱动匹配的 CUDA 库版本，并修改容器规格以添加挂载&lt;/li&gt;
&lt;li&gt;将必要的库注入容器。&lt;/li&gt;
&lt;li&gt;配置库路径，使容器能够找到它们。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果没有这一步，容器即使已经拥有 &lt;code&gt;/dev/nvidia0&lt;/code&gt; 设备文件，也依然缺少实际调用 GPU 所需的 CUDA 库，效果相当于“看得见显卡，却没有驱动”。这一机制利用的是运行时规范中的 prestart &lt;a href="https://opencontainers.org" target="_blank" rel="noopener"&gt;OCI（开放容器倡议）&lt;/a&gt; 钩子，即在容器创建完成但应用尚未启动之前插入逻辑。NVIDIA 正是借助这个标准扩展点，在最后一刻把 GPU 支持注入到一个原本并不包含任何 NVIDIA 组件的标准镜像中。下面是 containerd 启用相关集成的一个示例：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;io/containerd.grpc.v1.cri&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;enable_cdi&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;cdi_specdirs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/etc/cdi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/var/run/cdi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：/etc/containerd/config.toml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;在 Kubernetes 环境中，这些组件通常由 NVIDIA 提供的 DaemonSet 一并安装和配置。经过前述步骤之后，容器中的应用终于可以真正访问 GPU；例如，当它执行 nvidia-smi 时，就能够观察到设备，并进一步执行以下操作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;创建 CUDA 上下文（200-300ms 初始化）&lt;/li&gt;
&lt;li&gt;分配 GPU 内存（以大池为单位）&lt;/li&gt;
&lt;li&gt;启动核函数（不可抢占的执行）&lt;/li&gt;
&lt;li&gt;在数千个核心上处理数据&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但这里的关键限制也随之显现：这个 Pod 拿到的是整块 GPU，而不是其中一小部分。即使应用只消耗了部分算力或少量显存，Kubernetes 视角下它仍然独占了这块设备。&lt;/p&gt;
&lt;h2 id="共享挑战"&gt;共享挑战&lt;/h2&gt;
&lt;p&gt;到这里，我们已经完成了 GPU 工作负载在 Kubernetes 中的基本运行路径，但代价也很清楚：每个 Pod 默认获得的是整块 GPU。无论是 Tesla T4 还是更昂贵的 H100，都可能被单个只使用了 10% 算力或 1GB 显存的 Pod 独占。只要你开始考虑跨团队或跨工作负载共享 GPU，新的问题就会立刻出现：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如何防止一个工作负载独占 GPU？&lt;/li&gt;
&lt;li&gt;当多个 Pod 尝试使用相同的 GPU 内存时会发生什么？&lt;/li&gt;
&lt;li&gt;如何在没有内核支持的情况下确保公平调度？&lt;/li&gt;
&lt;li&gt;不同团队的工作负载之间的安全边界怎么办？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些问题将我们引向 GPU 共享机制及其产生的根本信任问题，这正是下一章的主题。&lt;/p&gt;
&lt;h2 id="关键要点"&gt;关键要点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;容器之所以成立，前提是内核既提供系统调用接口，也能通过 cgroups 和命名空间观察并强制执行资源边界。&lt;/li&gt;
&lt;li&gt;GPU 运行在这套内核控制模型之外：CUDA 上下文替代了进程视角，核函数不可抢占，显存通过内存池管理，很多关键状态对内核不可见。&lt;/li&gt;
&lt;li&gt;设备插件只是把 GPU 接入 Kubernetes 资源模型：它们能发现设备、暴露资源、配置容器访问，但不能复制 CPU 和系统内存那种细粒度控制能力。&lt;/li&gt;
&lt;li&gt;现有 Kubernetes GPU 集成是可用的，但能力边界非常明确：Pod 可以申请并使用 GPU，却仍然只能按整卡或整数资源单元消费，缺少原生共享、显存配额和服务质量保障。&lt;/li&gt;
&lt;li&gt;一旦进入多工作负载共享场景，问题就会从“如何接入 GPU”转向“谁来控制 GPU”，这也是后续章节要继续展开的核心矛盾。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>为什么 GPU 多租户如此困难</title><link>https://jimmysong.io/zh/book/gpu-multi-tenancy-platforms/challenges/</link><pubDate>Sat, 23 May 2026 07:47:25 +0000</pubDate><author>Jimmy Song</author><guid>https://jimmysong.io/zh/book/gpu-multi-tenancy-platforms/challenges/</guid><description>深入分析 GPU 多租户面临的核心挑战，包括传统隔离机制的失效、MPS 与时间分片的工作原理，以及隐蔽通道等安全问题。</description><content:encoded>
&lt;p&gt;本章将讨论以下几个问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;传统 Kubernetes 隔离机制（命名空间、cgroups、RBAC）如何工作以及为什么它们对 GPU 失效&lt;/li&gt;
&lt;li&gt;跨租户共享 GPU 时出现的具体安全漏洞&lt;/li&gt;
&lt;li&gt;如何根据你的信任模型评估不同的 GPU 共享方案（MPS、时间分片、MIG、vGPU）&lt;/li&gt;
&lt;li&gt;一个实用的决策框架，用于为你的组织选择合适的 GPU 共享策略&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为了理解 GPU 多租户为什么困难，首先需要回到一个更基础的问题：在不涉及 GPU 的情况下，Kubernetes 是如何实现多租户的。&lt;/p&gt;
&lt;h2 id="传统-kubernetes-隔离行之有效的基础"&gt;传统 Kubernetes 隔离：行之有效的基础&lt;/h2&gt;
&lt;p&gt;在典型的 Kubernetes 集群中，工作负载往往来自不同的团队、部门，甚至不同客户，它们需要共享同一批物理节点，却又不能彼此干扰。Kubernetes 之所以能够支撑这种模式，依赖的是三类相互配合的隔离机制：命名空间负责逻辑划分，cgroups 在 Linux 内核层面强制执行物理资源边界，RBAC 控制谁可以通过 API 访问和操作哪些对象。三者组合起来，形成了一个相当稳固的多租户模型：工作负载可以并行运行，但在可见性、可消耗资源以及可操作范围上彼此隔离。&lt;/p&gt;
&lt;h2 id="命名空间控制面分离"&gt;命名空间：控制面分离&lt;/h2&gt;
&lt;p&gt;命名空间将 Kubernetes 资源划分为逻辑域。每个租户——无论是团队、部门还是客户——都有自己的命名空间。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get ns
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME STATUS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fraud-detection Active
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;data-analytics Active
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;marketing Active
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;engineering Active&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;从表面上看，这像是一种强隔离；但命名空间本质上只存在于 Kubernetes API 这一层。它规定的是谁可以通过控制面查看、列举或管理资源，而不是谁在内核或硬件层面真正与谁隔离。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-1.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-1.webp" alt="图 19: Kubernetes 命名空间不提供隔离，可以跨越多个物理节点" data-caption="图 19: Kubernetes 命名空间不提供隔离，可以跨越多个物理节点"
width="1824"
height="1154"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 19: Kubernetes 命名空间不提供隔离，可以跨越多个物理节点&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;下面用一个简单的例子说明这一点：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pods -A -o wide
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAMESPACE NAME STATUS NODE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fraud-detection fraud-inference Running minikube
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;data-analytics analytics-job Running minikube
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;marketing campaign-analysis Running minikube
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;engineering model-training Running minikube&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;虽然这些 Pod 分属不同命名空间，但它们实际上运行在同一个物理节点上。&lt;code&gt;fraud-detection&lt;/code&gt; 命名空间中的 Pod 无法通过 API 列出 &lt;code&gt;data-analytics&lt;/code&gt; 中的 Pod，不过只要它知道对方的 IP 地址，依然可以直接通过网络访问它们，除非集群额外配置了网络策略。换句话说，命名空间并不隔离 CPU、内存或设备，它只是一个控制面上的 API 构造。因此，真正阻止工作负载相互干扰的并不是命名空间本身，而是更底层的机制。&lt;/p&gt;
&lt;h2 id="cgroups内核始终掌控一切"&gt;Cgroups：内核始终掌控一切&lt;/h2&gt;
&lt;p&gt;真正的物理强制执行来自 Linux cgroups。当你在 Pod 中声明资源限制时：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;500m&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;1Gi&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：resources.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;kubelet 将这些值写入内核：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ minikube ssh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;cd&lt;/span&gt; /sys/fs/cgroup/kubepods.slice
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat ../cpu.max
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;50000&lt;/span&gt; &lt;span class="m"&gt;100000&lt;/span&gt; &lt;span class="c1"&gt;# 每 100ms 中 50ms 的 CPU 时间&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat ../memory.max
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;1073741824&lt;/span&gt; &lt;span class="c1"&gt;# 1 GiB&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这些限制不是建议值，而是由内核直接执行的硬边界。如果进程试图突破 CPU 配额，内核会对其进行限速；如果它试图分配超过 1 GiB 的内存，OOM killer 会直接终止该进程。对于 CPU 周期和内存页而言，内核拥有完整可见性，因此也能在租户之间强制执行公平性。这正是 Kubernetes 可以安全地把多个工作负载打包到同一节点上的关键前提。&lt;/p&gt;
&lt;h2 id="rbac访问控制边界"&gt;RBAC：访问控制边界&lt;/h2&gt;
&lt;p&gt;命名空间定义逻辑边界，RBAC 则定义谁可以操作这些边界中的资源。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-2.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-2.webp" alt="图 20: Kubernetes 中的 RBAC：用户、角色和绑定" data-caption="图 20: Kubernetes 中的 RBAC：用户、角色和绑定"
width="1926"
height="1418"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 20: Kubernetes 中的 RBAC：用户、角色和绑定&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;例如，可以先在 &lt;code&gt;tenant-a&lt;/code&gt; 中创建一个服务账号：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl create sa tenant-a-user -n tenant-a&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后测试它对另一个命名空间的访问能力：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl auth can-i get pods --namespace&lt;span class="o"&gt;=&lt;/span&gt;tenant-b --as&lt;span class="o"&gt;=&lt;/span&gt;system:serviceaccount:tenant-a:tenant-a-user
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;no
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl auth can-i list secrets --namespace&lt;span class="o"&gt;=&lt;/span&gt;tenant-b --as&lt;span class="o"&gt;=&lt;/span&gt;system:serviceaccount:tenant-a:tenant-a-user
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;no&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;RBAC 的确阻止了通过 API 进行跨命名空间操作，但这种限制只覆盖控制面交互，并不会自动转化为运行时网络隔离。举例来说，只要知道 IP 地址，&lt;code&gt;tenant-a&lt;/code&gt; 中的 Pod 仍然可以直接联系 &lt;code&gt;tenant-b&lt;/code&gt; 中的 Pod：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 首先，在 tenant-b 中创建一个简单的 Web 服务&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f - &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: Pod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: web-service
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; namespace: tenant-b
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; containers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - name: web
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; image: nginx:alpine
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; ports:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - containerPort: 80
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 获取 Pod 的 IP 地址&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pod web-service -n tenant-b -o &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{.status.podIP}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;10.244.0.15&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 现在从 tenant-a 中的一个 Pod，我们可以直接访问 tenant-b 的服务&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f - &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: Pod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: client
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; namespace: tenant-a
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; containers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - name: client
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; image: curlimages/curl:latest
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; command: [&amp;#34;sh&amp;#34;, &amp;#34;-c&amp;#34;, &amp;#34;curl http://10.244.0.15 &amp;amp;&amp;amp; sleep 3600&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 检查日志 - 连接成功&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl logs client -n tenant-a
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;!DOCTYPE html&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;html&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;head&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;title&amp;gt;Welcome to nginx!&amp;lt;/title&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;…
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;连接是成功的。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-3.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-3.webp" alt="图 21: 在 Kubernetes 中，任何 Pod 只要知道 IP 地址就可以与其他 Pod 通信" data-caption="图 21: 在 Kubernetes 中，任何 Pod 只要知道 IP 地址就可以与其他 Pod 通信"
width="1680"
height="1354"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 21: 在 Kubernetes 中，任何 Pod 只要知道 IP 地址就可以与其他 Pod 通信&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;这说明了一个根本限制：命名空间和 RBAC 保护的是 API，cgroups 保护的是 CPU 和内存，而网络流量则需要额外的网络策略来约束。即便如此，凭借这三层配合，Kubernetes 依然为 CPU 和内存建立起了有效的多租户模型。其根本原因在于，真正决定资源边界的内核始终处于控制环路之中。&lt;/p&gt;
&lt;h2 id="为什么这种多租户模型有效"&gt;为什么这种多租户模型有效&lt;/h2&gt;
&lt;p&gt;这种多租户模型之所以成立，是因为 Linux 内核对 CPU 和系统内存拥有完整的可见性与执行能力。每个进程都属于某个 cgroup，内核直接维护这种关系；当 &lt;code&gt;tenant-a&lt;/code&gt; 的进程请求 CPU 时间或分配内存时，内核会检查相应计数器，并据此决定是否允许、限速或终止。租户超过 CPU 配额时，调度器会限制它；超过内存边界时，OOM killer 会介入。关键不在于 Kubernetes 声明了什么，而在于内核能看到每一条指令和每一个字节，并能立刻执行限制。问题在于，一旦共享对象从 CPU 和系统内存变成 GPU，这套模型就开始失效，因为在 GPU 场景中，内核在许多关键环节上是“盲”的。&lt;/p&gt;
&lt;h2 id="为什么这个模型对-gpu-失效"&gt;为什么这个模型对 GPU 失效&lt;/h2&gt;
&lt;p&gt;第一个断裂点出现在 GPU 内存上。分配系统内存时，内核会逐页跟踪变化，cgroups 也会立刻反映并强制执行限制；但分配 GPU 内存时，请求通常不会进入内核的常规内存管理路径，而是在用户空间的 NVIDIA 驱动内部被处理。下面这个 Pod 同时分配系统 RAM 与 GPU RAM，可以清楚展示这种差异：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f - &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: Pod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: gpu-memory-test
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; containers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - name: tester
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; image: pytorch/pytorch:2.0.0-cuda11.7-cudnn8-runtime
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; command: [&amp;#34;python&amp;#34;, &amp;#34;-c&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; args:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - |
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; import torch, os
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; def cgroup_mem():
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; try:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; with open(&amp;#34;/sys/fs/cgroup/memory.current&amp;#34;) as f:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; return int(f.read().strip()) / 1024**2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; except:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; return -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(&amp;#34;System memory before:&amp;#34;, cgroup_mem(), &amp;#34;MB&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; system_data = bytearray(100 * 1024 * 1024)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(&amp;#34;System memory after 100MB allocation:&amp;#34;, cgroup_mem(), &amp;#34;MB&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(&amp;#34;GPU memory before:&amp;#34;, torch.cuda.memory_allocated() / 1024**2, &amp;#34;MB&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; gpu_data = torch.zeros(25 * 1024 * 1024, device=&amp;#34;CUDA&amp;#34;) # ~100MB
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(&amp;#34;GPU memory after 100MB allocation:&amp;#34;, torch.cuda.memory_allocated() / 1024**2, &amp;#34;MB&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(&amp;#34;System memory after GPU allocation:&amp;#34;, cgroup_mem(), &amp;#34;MB&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;运行这个 Pod 时，你会看到 cgroup 计数器在分配 100MB 系统内存后增加，但在分配 100MB GPU 内存后保持不变；与此同时，&lt;code&gt;torch.cuda.memory_allocated()&lt;/code&gt; 却明确表明 GPU 显存已经被消耗。也就是说，GPU 内存确实发生了变化，但内核对此没有可见性。这个问题不仅影响计量，也直接延伸到调度与公平性控制。&lt;/p&gt;
&lt;h2 id="gpu-调度发生在内核之外"&gt;GPU 调度发生在内核之外&lt;/h2&gt;
&lt;p&gt;在 CPU 上，Linux 内核可以在几微秒内抢占进程、保存状态并恢复执行，因此能够在租户之间实施公平共享；而在 GPU 上，核函数通常通过 NVIDIA 驱动启动，一旦执行便会运行到结束，Linux 内核既无法中断它，也无法像 CPU 一样对其实施细粒度时间分片。结果是，当两个租户共享同一块 GPU 时，其中一个可以持续占用显存、提交长时间运行的核函数，从而在另一个租户几乎无法察觉的情况下让后者资源饥饿。适用于 CPU 和系统内存的那套强制执行模型，在这里已不再成立。下面这个 Pod 同时执行 CPU 与 GPU 任务，可以直观看到差异：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f - &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: Pod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: scheduler-demo
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; containers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - name: tasks
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; image: pytorch/pytorch:2.0.0-cuda11.7-cudnn8-runtime
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; command: [&amp;#34;python&amp;#34;, &amp;#34;-c&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; args:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - |
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; import torch, threading, time
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; def cpu_task():
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(&amp;#34;Starting CPU task...&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; start = time.time()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; sum(i*i for i in range(30_000_000))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(f&amp;#34;CPU task finished in {time.time() - start:.2f}s&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; def gpu_task():
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(&amp;#34;Starting GPU task...&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; start = time.time()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; x = torch.rand(2000, 2000, device=&amp;#34;CUDA&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; for _ in range(50):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; torch.matmul(x, x.T)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; torch.cuda.synchronize()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(f&amp;#34;GPU task finished in {time.time() - start:.2f}s&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; t1 = threading.Thread(target=cpu_task)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; t2 = threading.Thread(target=gpu_task)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; t1.start(); t2.start()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; t1.join(); t2.join()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; resources:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; limits:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; cpu: &amp;#34;200m&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; memory: &amp;#34;512Mi&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; nvidia.com/gpu: 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; restartPolicy: Never
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;检查日志时，通常会观察到如下现象：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPU 任务在内核控制下运行。因为容器被限制为 200m（一个核心的 20%），任务需要几秒钟才能完成。内核完全按配置限制它。&lt;/li&gt;
&lt;li&gt;GPU 任务不到一秒就完成了，不受 CPU 配额或内存限制的影响。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;原因并不神秘：GPU 调度发生在另一个几乎独立于 Linux 资源控制平面的世界里。&lt;/p&gt;
&lt;h2 id="cuda-上下文跨越容器"&gt;CUDA 上下文跨越容器&lt;/h2&gt;
&lt;p&gt;第三个断裂点来自 CUDA 上下文本身的工作方式。CUDA 上下文是驱动为单个进程建立的私有执行环境，其中包含该进程已经分配的显存、已加载的核函数以及执行流。每个使用 CUDA 的进程都会创建自己的上下文，而驱动负责在这些上下文之间切换。当容器请求 &lt;code&gt;nvidia.com/gpu: 1&lt;/code&gt; 时，Kubernetes 的实际动作是把整块 GPU 设备挂载到 Pod 中；进入 Pod 之后，CUDA、PyTorch 或 TensorFlow 等框架便直接与 GPU 驱动通信。驱动才是与物理设备交互、创建上下文并管理执行状态的真正组件。&lt;/p&gt;
&lt;p&gt;在 Linux 中，安装 NVIDIA 驱动后，GPU 会以 &lt;code&gt;/dev/&lt;/code&gt; 下的设备文件形式暴露出来，其中最关键的入口点之一是 &lt;code&gt;/dev/nvidia0&lt;/code&gt;。从内核角度看，它只是一个可被进程打开的文件；而当 PyTorch 或 TensorFlow 开始使用 CUDA 时，它做的事情也不过是像打开普通文件一样获取一个文件描述符。如果两个容器都能访问 &lt;code&gt;/dev/nvidia0&lt;/code&gt;，它们就都能建立 CUDA 上下文。之后的显存分配、核函数加载和执行调度，都发生在 NVIDIA 驱动内部。下面这个 Pod 展示了两个容器如何在同一物理设备上分别建立自己的 CUDA 上下文：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f - &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: Pod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: context-demo
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; containers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - name: container-a
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; image: pytorch/pytorch:2.0.0-cuda11.7-cudnn8-runtime
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; command: [&amp;#34;python&amp;#34;, &amp;#34;-c&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; args:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - |
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; import torch, os
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(f&amp;#34;Container A PID: {os.getpid()}&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; a = torch.full((1000, 1000), 1.0, device=&amp;#34;CUDA&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(f&amp;#34;Container A allocated {torch.cuda.memory_allocated()} bytes of GPU memory&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - name: container-b
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; image: pytorch/pytorch:2.0.0-cuda11.7-cudnn8-runtime
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; command: [&amp;#34;python&amp;#34;, &amp;#34;-c&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; args:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - |
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; import torch, os
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(f&amp;#34;Container B PID: {os.getpid()}&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; b = torch.full((1000, 1000), 2.0, device=&amp;#34;CUDA&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(f&amp;#34;Container B allocated {torch.cuda.memory_allocated()} bytes of GPU memory&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; restartPolicy: Never
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;从驱动视角看，这不过是两个打开了 &lt;code&gt;/dev/nvidia0&lt;/code&gt; 的进程，它并不知道，也并不关心它们是否处于不同的容器中。也就是说，容器边界并没有自然延伸到 CUDA 上下文这一层。&lt;/p&gt;
&lt;h2 id="kubernetes-仅在-api-层面强制调度"&gt;Kubernetes 仅在 API 层面强制调度&lt;/h2&gt;
&lt;p&gt;因此，当你在 Kubernetes 中请求 GPU 时，系统并不是像管理 CPU 那样直接管理底层硬件；它主要做的是调度与记账。对一个 &lt;code&gt;nvidia.com/gpu: 1&lt;/code&gt; 的请求而言，实际发生的过程大致如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;调度器将 &lt;code&gt;nvidia.com/gpu&lt;/code&gt; 视为一个数字。&lt;/li&gt;
&lt;li&gt;它找到一个公告至少一个可用 GPU 的节点。&lt;/li&gt;
&lt;li&gt;它使用正常的打分规则将 Pod 分配到该节点。&lt;/li&gt;
&lt;li&gt;该节点上的 kubelet 接收 Pod 规格。&lt;/li&gt;
&lt;li&gt;kubelet 调用 NVIDIA 设备插件，因为这不是原生资源。&lt;/li&gt;
&lt;li&gt;插件选择一块 GPU 并返回要挂载的设备文件。&lt;/li&gt;
&lt;li&gt;kubelet 将这些文件挂载到容器中，并将 GPU 标记为已占用。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这里最重要的是注意那些“没有发生”的事情：Kubernetes 从未直接检查 GPU 硬件，也没有真正理解 GPU 的内部状态；对调度器来说，&lt;code&gt;nvidia.com/gpu: 1&lt;/code&gt; 与 &lt;code&gt;example.com/device: 1&lt;/code&gt; 本质上没有区别。真实的设备选择发生在节点侧，并且由设备插件而不是 Linux 内核完成。Kubernetes 随后做的，是更新自己的内部计数器，以阻止其他 Pod 再来请求同一个逻辑上的 GPU。换句话说，Kubernetes 真正执行的是“一块 GPU 只分配给一个 Pod”这条控制面规则，而不是对物理硬件本身建立不可绕过的执行边界。在硬件层面，GPU 仍然是共享设备；任何能够打开 &lt;code&gt;/dev/nvidia0&lt;/code&gt; 的进程，理论上都可以与其交互。控制面规则与硬件实际能力之间的落差，也正是安全和性能问题出现的起点。&lt;/p&gt;
&lt;h2 id="实际威胁场景"&gt;实际威胁场景&lt;/h2&gt;
&lt;p&gt;上述架构缺陷并不是抽象概念，它们会直接演化为传统 Kubernetes 边界无法阻止的安全和可用性问题。下面用三个典型场景说明这一点。第一个场景是最直观的资源饥饿：在共享 GPU 环境中，一个租户很容易压垮另一个租户。假设 &lt;code&gt;tenant-a&lt;/code&gt; 先启动了一个稳定的训练工作负载：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f - &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: Pod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: stable-training
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; namespace: tenant-a
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; containers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - name: training
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; image: nvidia/cuda:11.0-base
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; command: [&amp;#34;python3&amp;#34;, &amp;#34;-c&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; args:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - |
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; import time
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; for epoch in range(100):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(f&amp;#34;Epoch {epoch+1}/100: loss=0.{234-epoch*2}, memory=2.1GB&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(&amp;#34;Training stable at 45ms per batch&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; time.sleep(2)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; resources:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; limits:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; nvidia.com/gpu: 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;训练运行顺利：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl logs -f stable-training -n tenant-a
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Epoch 1/100: &lt;span class="nv"&gt;loss&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.234, &lt;span class="nv"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2.1GB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Training stable at 45ms per batch
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Epoch 2/100: &lt;span class="nv"&gt;loss&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.232, &lt;span class="nv"&gt;memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2.1GB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Training stable at 45ms per batch&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;此时训练运行正常，但 &lt;code&gt;tenant-b&lt;/code&gt; 随后部署一个显存吞噬者：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f - &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: Pod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: memory-hog
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; namespace: tenant-b
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; containers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - name: hog
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; image: nvidia/cuda:11.0-base
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; command: [&amp;#34;python3&amp;#34;, &amp;#34;-c&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; args:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - |
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(&amp;#34;Allocating 8GB GPU memory...&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; # 实际上，这会分配大量 GPU 数组
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(&amp;#34;Allocation complete. Holding memory.&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; import time
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; time.sleep(3600)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; resources:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; limits:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; nvidia.com/gpu: 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果这两个 Pod 最终在物理上共享同一块 GPU，那么 &lt;code&gt;tenant-a&lt;/code&gt; 的训练进程可能会直接因为显存耗尽而失败：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;CUDA out of memory error: tried to allocate 2.10GB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Training failed.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;命名空间和 RBAC 都无法阻止这种情况，因为问题根本不发生在它们能够施加约束的层面。更严重的是，这种风险并不只停留在资源争夺上。当 GPU 内存没有在进程退出后被充分清理时，敏感数据甚至可能跨租户泄露。设想一家金融服务公司在 GPU 上处理专有模型与敏感数据：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f - &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: Pod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: financial-model
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; namespace: tenant-a
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; containers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - name: model
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; image: pytorch/pytorch
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; command: [&amp;#34;python3&amp;#34;, &amp;#34;-c&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; args:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - |
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; import torch
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; import os
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; # 将敏感数据加载到 GPU
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; secret_weights = torch.tensor([0.7234, -0.3456, 0.8891], device=&amp;#39;CUDA&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; customer_ssn = torch.tensor([123456789, 987654321], device=&amp;#39;CUDA&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(&amp;#34;Processing sensitive financial data...&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; # 模拟崩溃 - 没有清理！
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; os._exit(139) # Segmentation fault
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; # 这段代码永远不会运行：
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; # torch.cuda.empty_cache()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; resources:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; limits:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; nvidia.com/gpu: 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这个 Pod 因段错误崩溃，没有执行任何显存清理逻辑。随后，另一个租户可能启动一个“数据扫描器”，尝试从未初始化的 GPU 内存中读取残留内容：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f - &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: Pod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: data-scanner
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; namespace: tenant-b
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; containers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - name: scanner
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; image: python/pytorch
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; command: [&amp;#34;python3&amp;#34;, &amp;#34;-c&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; args:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - |
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; import torch
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; import numpy as np
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; # 分配内存但不初始化
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; # torch.empty() 不会将内存清零（不像 torch.zeros()）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; dirty_memory = torch.empty(1000000, dtype=torch.float32, device=&amp;#39;CUDA&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; # 检查这些&amp;#34;空&amp;#34;内存中有什么
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; cpu_copy = dirty_memory.cpu().numpy()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; non_zero = cpu_copy[cpu_copy != 0]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; if len(non_zero) &amp;gt; 0:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(f&amp;#34;Found {len(non_zero)} non-zero values in &amp;#39;empty&amp;#39; memory!&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;pre&gt;&lt;code&gt; 扫描器运行在完全不同的命名空间中，但仍可能恢复到前一个租户遗留在显存中的数据。第三个场景则是服务质量问题：长时间运行的核函数或批处理作业，会直接拖垮时间敏感型工作负载。假设有一个批处理作业先占住 GPU：
&lt;/code&gt;&lt;/pre&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f - &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: Pod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: batch-processor
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; namespace: engineering
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; containers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - name: batch
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; image: nvidia/cuda:11.0-base
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; command: [&amp;#34;python3&amp;#34;, &amp;#34;-c&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; args:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - |
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; import time
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(&amp;#34;Starting 60-minute matrix computation...&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; for i in range(60):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; print(f&amp;#34;Progress: {i+1}/60 minutes completed&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; time.sleep(60)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; resources:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; limits:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; nvidia.com/gpu: 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;随后，一个需要低延迟响应的实时推理服务尝试启动：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f - &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: Pod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: realtime-inference
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; namespace: marketing
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; containers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - name: inference
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; image: nvidia/cuda:11.0-base
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; command: [&amp;#34;nvidia-smi&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; resources:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; limits:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; nvidia.com/gpu: 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pods -A &lt;span class="p"&gt;|&lt;/span&gt; grep -E &lt;span class="s2"&gt;&amp;#34;batch|realtime&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;engineering batch-processor 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 10m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;marketing realtime-inference 0/1 Pending &lt;span class="m"&gt;0&lt;/span&gt; 5m&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;结果是，推理服务可能整整一小时都处于 Pending 状态。对于需要 SLA 保证的系统而言，这种行为意味着既无法承诺启动时延，也无法承诺吞吐稳定性。资源隔离失败在这里不再只是“公平性变差”，而是直接演变为业务层面的不可用。&lt;/p&gt;
&lt;h2 id="gpu-共享选项寻找合适的平衡"&gt;GPU 共享选项：寻找合适的平衡&lt;/h2&gt;
&lt;p&gt;既然问题无法靠传统 Kubernetes 隔离机制自然解决，那么接下来的关键问题就是：应当如何共享 GPU，以及在什么条件下共享才是可接受的。NVIDIA 提供了几种常见方案，但它们本质上都在利用率、隔离性和复杂度之间做不同权衡。最直接的一种方式是&lt;a href="https://docs.nvidia.com/deploy/mps/latest/index.html" target="_blank" rel="noopener"&gt;多进程服务（MPS）&lt;/a&gt;。它在 Kubernetes 中几乎不需要额外资源模型，只要在 GPU 节点上启用，多个进程就可以同时使用同一块 GPU。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-4.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-4.webp" alt="图 22: 多进程服务（MPS）是共享 GPU 最简单的方式。" data-caption="图 22: 多进程服务（MPS）是共享 GPU 最简单的方式。"
width="1896"
height="1340"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 22: 多进程服务（MPS）是共享 GPU 最简单的方式。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;MPS 的核心思路是让多个进程共享同一个 CUDA 上下文。通常情况下，每个 CUDA 进程都会创建自己的上下文，其中包含私有的显存分配、已加载的核函数和执行状态；驱动必须像操作系统切换进程那样，在这些上下文之间不断切换，而每一次切换都会带来额外开销。MPS 通过引入一个共享的 MPS 服务器进程，把多个进程提交的工作收敛到同一个上下文中，从而基本消除了这部分切换成本，因此经常带来明显的性能收益：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# 不使用 MPS - 高方差，因上下文切换而较慢
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App1 Average: 0.0847s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App1 StdDev: 0.0234s # 上下文切换导致的高方差
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App2 Average: 0.0891s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App2 StdDev: 0.0267s # 同样高方差&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;当你启用 MPS 时，性能显著提升：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 启用 MPS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi -c EXCLUSIVE_PROCESS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-cuda-mps-control -d&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# 使用 MPS - 稳定，更快
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App1 Average: 0.0623s # 快 26%
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App1 StdDev: 0.0031s # 方差减少 87%
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App2 Average: 0.0629s # 快 29%
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App2 StdDev: 0.0028s # 方差减少 90%&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;不过，性能收益并不意味着 MPS 适合多租户环境。它的第一个问题是缺乏内存跟踪与强制执行能力：多个进程共享同一个上下文时，所有显存都来自同一个池，系统无法精确回答“是谁用了多少”。如果进程 A 已经分配了 8GB，而进程 B 又试图在一块 16GB GPU 上分配 10GB，进程 B 只会收到 OOM 错误，却无法明确指出进程 A 就是原因。第二个问题是可观测性差。&lt;code&gt;nvidia-smi&lt;/code&gt; 等工具看到的往往只是聚合后的 GPU 使用情况，而不是每个进程的清晰明细，这让调试和容量规划都变得非常困难。第三个问题也是最关键的：所有进程处于同一内存空间语义之下，安全边界极其薄弱。例如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 一个恶意行为者影响所有人&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl logs memory-corruption-test
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Process A: Running normally...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Process B: Corrupting shared memory...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Process A: Segmentation fault
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Process B: Segmentation fault
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MPS Server: Fatal error, restarting...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;一个进程的崩溃或内存损坏，可能连带影响同一块 GPU 上的所有其他进程。因此，MPS 更适合同一团队内部的可信工作负载，在这种场景下，性能和吞吐往往比隔离更重要；但如果你至少希望 Kubernetes 能够在一定程度上记录“哪些 Pod 正在使用 GPU”，那么就需要另一种机制，即时间分片。&lt;/p&gt;
&lt;h2 id="时间分片gpu-数量倍增"&gt;时间分片：GPU 数量倍增&lt;/h2&gt;
&lt;p&gt;时间分片是共享 GPU 时更进一步的一种做法。与 MPS 让多个进程共享同一个上下文不同，时间分片的目标是让一块物理 GPU 在 Kubernetes 中表现为多个逻辑 GPU，从而允许多个 Pod 被同时调度上来。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-5.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-5.webp" alt="图 23: 时间分片&amp;#34;使一块物理 GPU 显示为多个逻辑 GPU" data-caption="图 23: 时间分片&amp;#34;使一块物理 GPU 显示为多个逻辑 GPU"
width="1630"
height="1396"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 23: 时间分片&amp;quot;使一块物理 GPU 显示为多个逻辑 GPU&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;做法是在 NVIDIA 设备插件中配置多个副本：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ConfigMap&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nvidia-device-plugin-config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;config.yaml&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; version: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sharing:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; timeSlicing:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; replicas: 4 # 一块 GPU 变成四块&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：configmap.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;应用此配置后：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl describe node minikube &lt;span class="p"&gt;|&lt;/span&gt; grep nvidia.com/gpu
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvidia.com/gpu: &lt;span class="m"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvidia.com/gpu: &lt;span class="m"&gt;4&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这样，一块 Tesla T4 在 Kubernetes 看起来就像四块可调度的 GPU，因而可以让多个 Pod 同时进入 Running 状态：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pods
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME READY STATUS GPU-REQUEST
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workload-1 1/1 Running nvidia.com/gpu: &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workload-2 1/1 Running nvidia.com/gpu: &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workload-3 1/1 Running nvidia.com/gpu: &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;workload-4 1/1 Running nvidia.com/gpu: &lt;span class="m"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在这种模式下，每个 Pod 都拥有自己的 CUDA 上下文，而 GPU 驱动则负责在这些上下文之间轮流切换。这里最关键的一点是：这些上下文属于仍在运行的进程，它们在 Kubernetes 里会同时处于 Running 状态。也就是说，当 &lt;code&gt;workload-1&lt;/code&gt; 启动后，它会建立自己的 CUDA 上下文并可能分配大量显存；&lt;code&gt;workload-2&lt;/code&gt; 随后启动时，也会创建自己的上下文。虽然任意时刻通常只有一个上下文真正占用 GPU 执行，但多个 Pod 却是同时活着、同时持有状态和显存的。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-6.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-6.webp" alt="图 24: 进程轮流在 GPU 上执行核函数" data-caption="图 24: 进程轮流在 GPU 上执行核函数"
width="1836"
height="1142"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 24: 进程轮流在 GPU 上执行核函数&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;GPU 驱动通过时间分片给每个上下文轮流执行核函数的机会，但上下文本身并不会随着时间片结束而消失，因为对应进程仍然存活。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-7.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-7.webp" alt="图 25: 每个进程在需要时都有一个专用的上下文" data-caption="图 25: 每个进程在需要时都有一个专用的上下文"
width="1912"
height="1350"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 25: 每个进程在需要时都有一个专用的上下文&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;当轮到上下文 B 执行时，上下文 A 不是被卸载，而只是暂停；它已经分配的显存依然保留，因为对应进程预期下一次获得时间片时还能继续使用这些数据。与 MPS 相比，时间分片的关键改进在于上下文彼此分离：上下文 A 分配的内存不会被上下文 B 直接读取或写入，因此至少建立了一个基础的数据隔离边界。但这种隔离并不意味着资源互不影响，因为多个上下文仍然竞争同一个物理显存池。如果 &lt;code&gt;workload-1&lt;/code&gt; 已经为模型分配了 12GB 显存，那么只要它还活着，这 12GB 就会一直被占用，剩下的工作负载只能共享其余 4GB，并可能因此失败。&lt;/p&gt;
&lt;h2 id="mig硬件分区"&gt;MIG：硬件分区&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://docs.nvidia.com/datacenter/tesla/mig-user-guide/" target="_blank" rel="noopener"&gt;多实例 GPU（MIG）&lt;/a&gt;提供的是更强的硬件级隔离，不过它只适用于 &lt;a href="https://www.nvidia.com/en-us/data-center/a100/" target="_blank" rel="noopener"&gt;A100&lt;/a&gt; 和 &lt;a href="https://www.nvidia.com/en-us/data-center/h100/" target="_blank" rel="noopener"&gt;H100&lt;/a&gt; 这类支持 MIG 的 GPU。MIG 的核心做法不是在软件层轮转时间片，而是直接把一块 GPU 物理切成多个彼此隔离的实例：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 在 A100 上：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi mig -cgi 1g.5gb,2g.10gb,3g.20gb
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi -L
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GPU 0: NVIDIA A100-SXM4-40GB &lt;span class="o"&gt;(&lt;/span&gt;UUID: GPU-0DCF3380&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; MIG 3g.20gb Device 0: &lt;span class="o"&gt;(&lt;/span&gt;UUID: MIG-fb9af708&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; MIG 2g.10gb Device 1: &lt;span class="o"&gt;(&lt;/span&gt;UUID: MIG-a0d4e8e4&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; MIG 1g.5gb Device 2: &lt;span class="o"&gt;(&lt;/span&gt;UUID: MIG-52a84557&lt;span class="o"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;每个 MIG 实例都具有自己独占的一组资源：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;专用的计算单元（SM）&lt;/li&gt;
&lt;li&gt;隔离的内存（5GB、10GB 或 20GB）&lt;/li&gt;
&lt;li&gt;独立的内存带宽&lt;/li&gt;
&lt;li&gt;独立的故障域&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-8.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-8.webp" alt="图 26: MIG 将 GPU 物理分割为隔离的实例" data-caption="图 26: MIG 将 GPU 物理分割为隔离的实例"
width="1790"
height="1280"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 26: MIG 将 GPU 物理分割为隔离的实例&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;在 Kubernetes 中，这些实例会被视为不同类型的 GPU 资源：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl describe node &lt;span class="p"&gt;|&lt;/span&gt; grep nvidia.com/mig
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvidia.com/mig-1g.5gb: &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvidia.com/mig-2g.10gb: &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvidia.com/mig-3g.20gb: &lt;span class="m"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Pod 请求特定的切片：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/mig-2g.10gb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：resources.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;MIG 的优势在于它提供了真正的硬件边界：一个切片既不能访问另一个切片的显存，也不能直接干扰其计算资源。不过，它的代价也同样明确：你必须拥有支持 MIG 的 A100/H100 级别硬件，而且单卡切片数量上限有限，通常最多只有七个实例。&lt;/p&gt;
&lt;h2 id="vgpu企业级方案"&gt;vGPU：企业级方案&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://docs.nvidia.com/grid/latest/grid-vgpu-user-guide/" target="_blank" rel="noopener"&gt;虚拟 GPU（vGPU）&lt;/a&gt;则把共享进一步提升到虚拟机监控器层面，试图提供与传统虚拟化环境相近的隔离保证。不同于 MPS 在进程层共享、时间分片在调度层共享，vGPU 的基本思路是为每个租户提供一个虚拟机，而每个虚拟机都“以为”自己拥有一块专用 GPU；真正的时间与内存分配，则由虚拟机监控器根据预设配置文件完成。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-9.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/challenges/f2-9.webp" alt="图 27: vGPU 创建虚拟机，每个虚拟机都认为自己拥有专用的 GPU" data-caption="图 27: vGPU 创建虚拟机，每个虚拟机都认为自己拥有专用的 GPU"
width="1784"
height="1312"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 27: vGPU 创建虚拟机，每个虚拟机都认为自己拥有专用的 GPU&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;这种方案的代价是显著的基础设施投入。你需要 NVIDIA vGPU 软件许可证，它通常按 GPU 每年收费，成本可能接近硬件本身；你还需要受支持并带有相应补丁的虚拟机监控器，例如 &lt;a href="https://www.vmware.com/products/cloud-infrastructure/vsphere" target="_blank" rel="noopener"&gt;VMware vSphere&lt;/a&gt;、&lt;a href="https://www.citrix.com/glossary/what-is-hypervisor.html" target="_blank" rel="noopener"&gt;Citrix XenServer&lt;/a&gt; 或 &lt;a href="https://linux-kvm.org/page/Main_Page" target="_blank" rel="noopener"&gt;KVM&lt;/a&gt;。此外，并非所有 GPU 都支持 vGPU，通常需要 A40、&lt;a href="https://www.nvidia.com/en-us/data-center/a100/" target="_blank" rel="noopener"&gt;A100&lt;/a&gt; 等数据中心级产品。&lt;/p&gt;
&lt;p&gt;一旦部署完成，每个虚拟机看到的都是一块具有固定显存容量的标准 NVIDIA 设备。应用程序可以在其中安装驱动、运行 CUDA 程序、使用 &lt;code&gt;nvidia-smi&lt;/code&gt; 等工具，而不必知道自己实际运行在共享硬件上。虚拟机监控器会在虚拟机之间强制执行严格边界：如果某个虚拟机试图分配超过配额的 GPU 内存，它会收到 OOM 错误；如果某个虚拟机内部的 GPU 驱动崩溃，其他虚拟机仍可继续运行。这种隔离是在操作系统之下执行的，因此比容器级共享更强。vGPU 还启用了许多裸金属 GPU 难以提供的运维能力：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;你可以在主机之间实时迁移虚拟机而不中断 GPU 工作负载&lt;/li&gt;
&lt;li&gt;你可以快照 GPU 状态用于备份和恢复&lt;/li&gt;
&lt;li&gt;你可以根据工作负载需求动态调整 GPU 配置文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于运行关键业务负载的企业而言，这些运维收益往往可以抵消复杂性带来的负担；但这种隔离并非没有代价。虚拟机监控器会引入额外开销，与裸金属相比通常会损失 20% 到 30% 的性能；许可证模型本身也较为复杂，不同用途可能对应不同许可证。再加上共享存储、vCenter 或同类管理工具，以及熟悉虚拟化与 GPU 技术的运维团队，vGPU 更像是一套企业级平台方案，而不是轻量级共享技巧。&lt;/p&gt;
&lt;h2 id="信任层级选择你的策略"&gt;信任层级：选择你的策略&lt;/h2&gt;
&lt;p&gt;GPU 共享只有在信任边界被明确识别之后，才可能设计得合理。原则很简单：信任越弱，隔离就必须越强。如果所有用户都属于同一团队，目标一致，能够通过人工方式协调资源，那么时间分片或 MPS 通常已经足够；在这种高信任环境中，人们往往愿意容忍偶发干扰，以换取更高利用率。相反，如果集群服务的是不同部门，且它们有彼此竞争的优先级，那么没有硬件边界时，意外干扰就很容易发生，这类场景往往更适合 MIG 或专用 GPU。再往下，如果面对的是需要明确 SLA 的付费客户，那么虚拟机级隔离甚至专用节点才更接近合理选择，因为客户预期的是可保证的资源与完整的隔离。对于受监管行业或带有对抗性质的工作负载，最保守也往往最现实的答案，是根本不共享：每个租户使用专用物理 GPU，从根源上避免交叉污染。&lt;/p&gt;
&lt;h2 id="比较所有方案"&gt;比较所有方案&lt;/h2&gt;
&lt;p&gt;以上几种机制的差异，可以用下表概括：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方案&lt;/th&gt;
&lt;th&gt;隔离性&lt;/th&gt;
&lt;th&gt;性能&lt;/th&gt;
&lt;th&gt;复杂度&lt;/th&gt;
&lt;th&gt;硬件要求&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MPS&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;优秀&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;任何 GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;时间分片&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;良好&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;任何 GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MIG&lt;/td&gt;
&lt;td&gt;硬件级&lt;/td&gt;
&lt;td&gt;良好&lt;/td&gt;
&lt;td&gt;中&lt;/td&gt;
&lt;td&gt;仅 A100/H100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vGPU&lt;/td&gt;
&lt;td&gt;虚拟机级&lt;/td&gt;
&lt;td&gt;一般（70-80%）&lt;/td&gt;
&lt;td&gt;高&lt;/td&gt;
&lt;td&gt;许可证 + 特定 GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;专用&lt;/td&gt;
&lt;td&gt;完全&lt;/td&gt;
&lt;td&gt;完美&lt;/td&gt;
&lt;td&gt;低&lt;/td&gt;
&lt;td&gt;任何 GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="关键要点"&gt;关键要点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;GPU 多租户困难的根源，不是 Kubernetes 缺少资源对象，而是 GPU 资源管理本身不服从内核主导的控制模型。&lt;/li&gt;
&lt;li&gt;对 CPU 和系统内存有效的命名空间、cgroups 和 RBAC，在 GPU 上都失去了关键前提：内核既看不全状态，也无法细粒度强制执行。&lt;/li&gt;
&lt;li&gt;没有 GPU cgroups、没有可靠的内核级可见性、没有细粒度可抢占执行，意味着很多控制权实际落在驱动和厂商运行时内部。&lt;/li&gt;
&lt;li&gt;因而 GPU 共享从来不只是一个技术选型问题，而是一个由信任模型驱动的架构问题：信任越低，越需要硬件边界或更强隔离。&lt;/li&gt;
&lt;li&gt;时间分片、MPS、MIG、vGPU 和专用 GPU 都只是不同约束下的折中方案，没有任何一种机制可以脱离信任等级、成本和性能目标单独成立。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>GPU 共享编排：Kubernetes 如何管理轮转</title><link>https://jimmysong.io/zh/book/gpu-multi-tenancy-platforms/orchestration/</link><pubDate>Sat, 23 May 2026 10:12:39 +0000</pubDate><author>Jimmy Song</author><guid>https://jimmysong.io/zh/book/gpu-multi-tenancy-platforms/orchestration/</guid><description>介绍 GPU 共享编排的核心概念，包括自动上下文切换、MPS、时间分片调度，以及如何在 Kubernetes 中管理 GPU 轮转。</description><content:encoded>
&lt;p&gt;本章将讨论以下几个问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GPU 如何通过自动上下文切换始终支持多个进程&lt;/li&gt;
&lt;li&gt;为什么 GPU 内存不像 CPU 内存那样在进程之间分区&lt;/li&gt;
&lt;li&gt;KAI-Scheduler 如何使用&amp;quot;预留 Pod&amp;quot;来欺骗 Kubernetes 实现 GPU 共享&lt;/li&gt;
&lt;li&gt;NVIDIA&amp;quot;时间分片&amp;quot;实际做了什么（剧透：它不是时间分片）&lt;/li&gt;
&lt;li&gt;为什么两种方案都不能提供真正的隔离或公平性保证&lt;/li&gt;
&lt;li&gt;编排共享与强制执行共享之间的关键区别&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;理解 Kubernetes 中的 GPU 共享，首先要澄清一个常见误解：GPU 并不是到了 Kubernetes 时代才开始支持“共享”，真正新出现的只是围绕这种共享建立的编排方法。&lt;/p&gt;
&lt;h2 id="隐藏的真相gpu-早已支持共享"&gt;隐藏的真相：GPU 早已支持共享&lt;/h2&gt;
&lt;p&gt;GPU 从很早开始就能够被多个进程共同使用。下面是在测试机器（Tesla T4）上的一个简单演示：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ python matrix_multiply.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Starting matrix operations on GPU 0...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Iteration 1: 1.23s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Iteration 2: 1.21s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Iteration 3: 1.24s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;在它运行的同时，再打开另一个终端：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ python vector_add.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Starting vector addition on GPU 0...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Iteration 1: 0.89s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Iteration 2: 1.45s &lt;span class="c1"&gt;# 更慢 - 与矩阵乘法竞争&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Iteration 3: 0.91s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;两个进程都在使用同一块 GPU。检查 nvidia-smi：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-----------------------------------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; Processes: &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; GPU GI CI PID Type Process name GPU Memory &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="o"&gt;=============================================================================&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; N/A N/A &lt;span class="m"&gt;4521&lt;/span&gt; C python matrix_multiply.py 2341MiB &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; N/A N/A &lt;span class="m"&gt;4627&lt;/span&gt; C python vector_add.py 1122MiB &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-----------------------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里已经能看到关键事实：一块 GPU、两个进程、同时处于运行状态，而且不需要任何额外配置。这并不是 Kubernetes、MPS 或某个调度器带来的新能力，而是 CUDA 生态本来就具备的基础行为。问题不在于“能否共享”，而在于这种共享究竟以什么方式发生，以及它为什么与人们熟悉的 CPU 共享模型差异如此之大。&lt;/p&gt;
&lt;h2 id="理解-gpu-上下文切换"&gt;理解 GPU 上下文切换&lt;/h2&gt;
&lt;p&gt;当多个进程使用同一块 GPU 时，它们的执行方式并不像 CPU 上的线程调度那样“同时向前推进”。GPU 的运行模型与 CPU 根本不同：一旦某个 CUDA 核函数启动，它通常会独占相关计算资源直到执行结束，中途不能像 CPU 任务那样被任意暂停、保存状态并切换出去。因此，多个进程之所以都能“使用”同一块 GPU，并不是因为它们的核函数被精细地交错执行，而是因为它们在驱动层面轮流提交和运行工作。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/orchestration/f3-1.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/orchestration/f3-1.webp" alt="图 7: GPU 上的核函数轮流执行" data-caption="图 7: GPU 上的核函数轮流执行"
width="1414"
height="980"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 7: GPU 上的核函数轮流执行&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;换言之，每个核函数都是运行到完成后才让出执行机会；进程 A 的核函数结束，进程 B 的核函数才能上场。NVIDIA 驱动负责维护不同进程提交的工作队列，并按自己的策略一个接一个地调度它们，这就是 GPU 世界中的驱动级上下文切换。它会自动发生，但这并不意味着 GPU 具备了 CPU 那种强抢占、公平时间片和细粒度资源配额的能力。更重要的是，这种“轮流执行”的表象很容易让人误以为内存也会像执行时间一样被干净切分，而事实恰恰相反。&lt;/p&gt;
&lt;h2 id="内存幻觉"&gt;内存幻觉&lt;/h2&gt;
&lt;p&gt;GPU 共享最容易误导人的地方在于显存模型。假设你有一块 24GB 的 GPU，打算同时运行：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;需要约 16GB 的应用 A&lt;/li&gt;
&lt;li&gt;需要约 8GB 的应用 B&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;直觉上，这看起来似乎正好可以放下，因为 $16 + 8 = 24$。但实际情况比这个算术题复杂得多：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 启动应用 A&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ python app_a.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Allocating 16GB of GPU memory...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Allocated successfully. GPU memory available: 8GB remaining
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 启动应用 B&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ python app_b.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Allocating 8GB of GPU memory...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Error: CUDA out of memory. Tried to allocate 8GB &lt;span class="o"&gt;(&lt;/span&gt;8GB free&lt;span class="o"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;表面上还有 8GB 空闲，为什么仍然失败？因为应用 B 需要的并不只是那 8GB 数据本身，它还要为如下部分预留空间：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CUDA 上下文开销（约 300-500MB）&lt;/li&gt;
&lt;li&gt;核函数代码&lt;/li&gt;
&lt;li&gt;临时缓冲区&lt;/li&gt;
&lt;li&gt;驱动分配&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;更重要的是，每个应用都维护着自己的 CUDA 上下文和各自的显存分配。上下文本身是分离的，但它们并不是从互不相干的私有显存池中取内存，而是共同从同一块物理显存中提取资源。当应用 A 分配了 16GB 显存后，这些显存会被保留在它的上下文里，只要上下文还活着，就不会自动回到公共池中。因此，应用 B 并不能“借用”那 16GB；它只能在剩余空间里继续申请，而这就意味着所有活动上下文的显存占用总和必须同时塞进物理 GPU 的总容量中。&lt;/p&gt;
&lt;p&gt;这里还有一个更容易让人忽略的细节：显存占用不仅来自“长期持有”的数据，还来自核函数执行期间的临时工作空间。例如：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 两个应用都预先分配了内存&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App A: 16GB 已分配
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App B: 7GB 已分配
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Total: 23GB 已使用, 1GB 可用
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 核函数执行期间&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Time T1: App A 核函数运行 - 需要 500MB 临时空间 &lt;span class="o"&gt;(&lt;/span&gt;失败 - 可用空间不足&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Time T2: App B 核函数运行 - 在其 7GB 内成功运行&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这意味着，上下文中长期持有的显存和核函数运行时需要的临时工作区必须一起被计算在内；如果临时空间无法满足，核函数仍然会失败，即使该上下文已经拥有一大块“静态”分配的显存。为了把这个问题说得更具体，下面回到第一章中出现过的一个简单计算例子：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 这段 Python 代码在 CPU 上运行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cupy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;cp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 在 GPU 上创建数组 - 每个数组分配内存&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c1"&gt;# 为 x 分配内存&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c1"&gt;# 为 y 分配内存&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 这会在 GPU 上触发核函数启动&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="c1"&gt;# 核函数 1：加法核函数执行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 这会触发另一个核函数启动&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="c1"&gt;# 核函数 2：减法核函数执行&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：two-kernels.py&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;在这个例子中，显存变化过程是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;当我们创建 x 和 y 时，CuPy 为每个数组分配 GPU 内存&lt;/li&gt;
&lt;li&gt;当我们计算 z = x + y 时，需要内存来存储结果&lt;/li&gt;
&lt;li&gt;变量 z 不仅仅是一个临时计算——它是需要 GPU 内存的实际数据&lt;/li&gt;
&lt;li&gt;当我们计算 w = x - z 时，需要更多内存来存储结果&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果配合显存跟踪来看，变化会更直观：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cupy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;cp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 检查初始 GPU 内存&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Initial GPU memory: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MemoryPool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;used_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; bytes&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 分配一些大数组&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 4MB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;After x: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MemoryPool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;used_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; MB&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 另一个 4MB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;After y: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MemoryPool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;used_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; MB&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 计算 z = x + y（需要内存存储结果）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;After z = x + y: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MemoryPool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;used_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; MB&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 计算 w = x * z（需要更多内存）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;After w = x * z: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cuda&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MemoryPool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;used_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; MB&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Output:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Initial GPU memory: 0 bytes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# After x: 4.00 MB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# After y: 8.00 MB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# After z = x + y: 12.00 MB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# After w = x * z: 16.00 MB&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：memory.py&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;这里最关键的事实是：每一个中间结果都会消耗显存，而且只要相应变量还存在，这部分显存就会保持占用。把这种模式扩展到多应用场景，问题就会迅速变得复杂：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# 应用 A 启动
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App A: 分配 8GB 用于模型权重
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App A: 分配 2GB 用于输入数据
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App A: 需要 3GB 用于中间计算
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;应用 A 总计：13GB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# 应用 B 尝试在 16GB GPU 上启动
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App B: 分配 2GB 用于其模型
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App B: 分配 1GB 用于数据
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;App B: 尝试分配 2GB 用于中间结果
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;崩溃：内存不足 (13GB + 2GB + 1GB + 2GB = 18GB &amp;gt; 16GB)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;即使应用 B 从业务角度看“只需要”3GB 常驻显存，用于中间结果的临时空间也足以让总需求越过物理上限。这就是 GPU 显存管理比 CPU 内存直觉更难把握的原因：应用不只需要存放数据，还需要为计算过程留出生存空间；而 GPU 显存又不像 CPU 内存那样可以被内核换出、压缩或延迟回收。要么所有长期分配和临时工作区都能同时放下，要么核函数直接失败。理解了这一点，才能看懂 Kubernetes 在 GPU 编排上究竟试图解决什么问题，又为什么只能解决其中一部分。&lt;/p&gt;
&lt;h2 id="kubernetes-编排问题"&gt;Kubernetes 编排问题&lt;/h2&gt;
&lt;p&gt;Kubernetes 对 CPU 和系统内存的共享处理得相当优雅。例如，当你写下：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2Gi&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;500m&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 0.5 个 CPU 核心&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;4Gi&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 1 个 CPU 核心&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：resources.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Kubernetes 理解分数资源，也能借助 cgroups 强制执行限制，并据此完成调度与隔离。但 GPU 完全打破了这套模型。当你写下：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：resources.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;你请求的就是整块 GPU，而不是它的一部分。你不能写出类似下面这样的语义：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 你不能这样做：&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 错误：必须是整数&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu-memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;2Gi &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 错误：没有这种资源&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：resources.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;这意味着 Kubernetes 并没有原生的 GPU 共享概念。如果你希望两个 Pod 共享同一块 GPU，就必须额外解决以下问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;让两个 Pod 都调度到同一节点&lt;/li&gt;
&lt;li&gt;让两个 Pod 都看到同一块物理 GPU&lt;/li&gt;
&lt;li&gt;防止 Kubernetes 将该 GPU 分配给其他 Pod&lt;/li&gt;
&lt;li&gt;跟踪谁在使用什么（即使你无法强制执行）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果手工处理这些细节，几乎不可维护。因此，现实世界里通常会借助额外的编排机制自动化这一过程。下面考察两种典型方案：KAI-Scheduler 与 NVIDIA 设备插件提供的“时间分片”。&lt;/p&gt;
&lt;h2 id="kai-scheduler-的预留-pod-策略"&gt;KAI-Scheduler 的预留 Pod 策略&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/kai-scheduler/KAI-Scheduler" target="_blank" rel="noopener"&gt;KAI-Scheduler&lt;/a&gt; 解决 GPU 共享问题的方式，本质上是一个非常巧妙的变通：既然 Kubernetes 只理解整块 GPU，而用户却想请求某个“分数”或某个显存量，那么就先用一个占位对象把整块 GPU 预留下来，再在这个预留之上做内部共享。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/orchestration/f3-2.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/orchestration/f3-2.webp" alt="图 8: KAI 调度器部署一个占位 Pod 来预留整块 GPU。" data-caption="图 8: KAI 调度器部署一个占位 Pod 来预留整块 GPU。"
width="1306"
height="1140"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 8: KAI 调度器部署一个占位 Pod 来预留整块 GPU。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;假设现在有一个只需要 2GB GPU 显存的推理服务。由于 Kubernetes 本身不理解 GPU 显存请求，KAI-Scheduler 定义了自己的注解体系：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;inference-service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;gpu-memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 以 MiB 为单位 - KAI 的自定义注解&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;schedulerName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kai-scheduler &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 必须使用 KAI，不是默认调度器&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;inference&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;inference:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 注意：resources 中没有 nvidia.com/gpu，因为 KAI 将处理 GPU 共享&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：pod.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;pre&gt;&lt;code&gt;这里的 `gpu-memory` 注解以 MiB 为单位，充当了 KAI 对“GPU 显存请求”这一缺失能力的补丁。当这个 Pod 被提交后，KAI-Scheduler 会先寻找一块仍有至少 2GB 可用显存的 GPU；假设它选中了 `node-1` 上的一块 Tesla T4（16GB 总显存），随后会创建一个特殊的“预留 Pod”：
&lt;/code&gt;&lt;/pre&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gpu-reservation-node1-abc123&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kai-resource-reservation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runai-gpu-group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;group-xyz-789 &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 将此 GPU 链接到用户 Pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nodeName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;node-1 &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 显式放置在 node-1 上&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runtimeClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nvidia &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# KAI 要求&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;resource-reservation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kai-resource-reservation:latest &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# KAI 的发现组件&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 声明整块 GPU&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：reservation-pod.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;这个预留 Pod 会向 Kubernetes 声明整块 GPU，因此在默认调度器看来，这块 GPU 已经被完全占用，不应再分配给其他普通 Pod。问题在于，KAI-Scheduler 还需要知道：这个预留 Pod 最终究竟拿到了哪一块物理 GPU。于是，当预留 Pod 启动后，kubelet 与设备插件会实际给它分配一块具体的 GPU，例如 &lt;code&gt;GPU-abc-def-123&lt;/code&gt;，而预留 Pod 内部运行的 KAI 控制器则会主动完成发现与回写：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 NVML 发现它被分配了哪块 GPU&lt;/li&gt;
&lt;li&gt;用 GPU 索引更新自己的 Pod 注解&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 在预留 Pod 的控制器内部&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi -L
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GPU 0: Tesla T4 &lt;span class="o"&gt;(&lt;/span&gt;UUID: GPU-abc-def-123&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 控制器更新 Pod 注解&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl annotate pod gpu-reservation-node1-abc123 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; run.ai/reserve_for&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;gpu_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;GPU-abc-def-123&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;一旦这个映射关系建立，KAI-Scheduler 就能够继续完成用户 Pod 的精细编排：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将你的 &lt;code&gt;inference-service&lt;/code&gt; Pod 调度到 &lt;code&gt;node-1&lt;/code&gt;（与预留同一个节点）&lt;/li&gt;
&lt;li&gt;添加 &lt;code&gt;runai-gpu-group&lt;/code&gt; 标签将其链接到预留 Pod&lt;/li&gt;
&lt;li&gt;设置环境变量 &lt;code&gt;NVIDIA_VISIBLE_DEVICES=GPU-abcdef-123&lt;/code&gt;，确保它看到同一块 GPU&lt;/li&gt;
&lt;li&gt;更新其内部跟踪：&amp;ldquo;GPU-abc-def-123 已分配 2GB，空闲 13GB&amp;rdquo;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果随后又有一个 Pod 请求 4GB GPU 显存，例如：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;gpu-memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;4000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 以 MiB 为单位&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：pod.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;那么 KAI-Scheduler 会重复同样的流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;看到 GPU-abc-def-123 有 13GB 空闲&lt;/li&gt;
&lt;li&gt;将这个 Pod 调度到 node-1&lt;/li&gt;
&lt;li&gt;添加相同的 &lt;code&gt;runai-gpu-group&lt;/code&gt; 标签将其链接到现有预留&lt;/li&gt;
&lt;li&gt;设置 &lt;code&gt;NVIDIA_VISIBLE_DEVICES=GPU-abc-def-123&lt;/code&gt;（同一块 GPU！）&lt;/li&gt;
&lt;li&gt;更新跟踪：&amp;ldquo;GPU-abc-def-123 已分配 6GB，空闲 10GB&amp;rdquo;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最终，两个 Pod 都会看到同一块 GPU。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/orchestration/f3-3.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/orchestration/f3-3.webp" alt="图 9: KAI 调度器会跟踪分配情况并将 Pod 调度到正确的节点。" data-caption="图 9: KAI 调度器会跟踪分配情况并将 Pod 调度到正确的节点。"
width="1294"
height="1134"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 9: KAI 调度器会跟踪分配情况并将 Pod 调度到正确的节点。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;从应用视角看，它们现在都能访问同一个物理设备，并像前面的手工示例一样轮流运行核函数。也就是说，KAI-Scheduler 解决的不是 GPU 的底层共享机制，而是“如何让 Kubernetes 同意并协调这种共享”。&lt;/p&gt;
&lt;h2 id="kai巧妙之处与局限性"&gt;KAI：巧妙之处与局限性&lt;/h2&gt;
&lt;p&gt;KAI-Scheduler 的真正巧妙之处，在于它弥合了“用户真正想要表达的资源需求”和“Kubernetes 原生能理解的资源模型”之间的缺口。用户想表达的是“这个推理服务需要 2GB 显存，那个训练任务需要 8GB 显存”；Kubernetes 理解的却只有“要一整块 GPU 还是不要”。KAI 通过预留 Pod 与自定义注解，把这种差距转化为可操作的调度逻辑：它能跟踪每块 GPU 已分配了多少显存，根据剩余容量决定把新 Pod 放到哪里，也能把多个小型推理任务打包进同一块 GPU，或者把大多数资源留给大型训练作业。&lt;/p&gt;
&lt;p&gt;但理解 KAI 的价值时，必须同样清楚它没有做什么。首先，它没有显存强制执行能力。一个声称自己只需要 2GB 的 Pod，如果实际申请了 15GB 显存，KAI 无法阻止它；KAI 可以记录“这个 Pod 本该只拿 2GB”，却无法阻止它在运行时把所有可用显存都吞掉。其次，它也没有计算隔离能力。一个 Pod 完全可以启动一个持续 30 秒的长核函数，从而阻塞其他 Pod 的执行；KAI 既不能中断这个核函数，也不能确保公平轮转。进一步说，它甚至没有真正的公平性保证。GPU 时间最终仍由底层驱动和核函数提交模式决定，谁先提交、谁更频繁提交、谁的核函数更长，都会影响其他工作负载的体验。因此，KAI-Scheduler 做的是“编排共享”，而不是“强制执行共享”。即便它还支持 &lt;code&gt;gpufraction: &amp;quot;0.5&amp;quot;&lt;/code&gt; 这类更细粒度的表达，或者通过其他注解请求多个分数 GPU，底层现实依旧没有改变：它协调了共享，但没有创造出新的硬件隔离机制。&lt;/p&gt;
&lt;h2 id="nvidia-时间分片误导性的名称"&gt;NVIDIA &amp;ldquo;时间分片&amp;rdquo;：误导性的名称&lt;/h2&gt;
&lt;p&gt;NVIDIA 的设备插件还提供了一个名为“时间分片”的能力。这个名字非常具有诱导性，因为它会让人自然联想到 CPU 时间片模型：好像 GPU 会像 CPU 一样，把执行时间公平地切成若干小片，轮流分发给不同 Pod。然而，这种理解是错误的。先看配置方式：&lt;/p&gt;
&lt;p&gt;configmap.yaml&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ConfigMap&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nvidia-device-plugin-config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kube-system&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;config.yaml&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; version: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sharing:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; timeSlicing:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; resources:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; - name: nvidia.com/gpu
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; replicas: 4 # 让 1 块 GPU 看起来像 4 块&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;应用这段配置并重启设备插件后，节点上展示出来的资源会变成：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl describe node node-1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Capacity:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvidia.com/gpu: &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="c1"&gt;# 原来是 1，现在报告 4！&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Allocatable:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvidia.com/gpu: &lt;span class="m"&gt;4&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;也就是说，一块物理 GPU 现在在 Kubernetes 看起来像四块 GPU。这里真正发生的事情并不是 GPU 被物理切开，而是设备插件向 Kubernetes 报告了一个“放大后的资源视图”。换句话说，物理上仍然只有一块 GPU，但 Kubernetes 被告知这里有四个可调度单元。于是，当你部署四个 Pod、每个都请求 &lt;code&gt;nvidia.com/gpu: 1&lt;/code&gt; 时：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Pod 1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 认为自己获得了整块 GPU&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Pod 2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 也认为自己获得了整块 GPU&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Pod 3 和 4：同样如此&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Kubernetes 会理直气壮地把这四个 Pod 都调度到同一节点上，而且每个 Pod 都会认为自己得到了“一个完整的 GPU”。然而，真正进入容器内部之后，看到的却是同一个物理设备：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; pod-1 -- nvidia-smi -L
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GPU 0: Tesla T4 &lt;span class="o"&gt;(&lt;/span&gt;UUID: GPU-abc-def-123&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; pod-2 -- nvidia-smi -L
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GPU 0: Tesla T4 &lt;span class="o"&gt;(&lt;/span&gt;UUID: GPU-abc-def-123&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 同一块 GPU！&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; pod-3 -- nvidia-smi -L
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GPU 0: Tesla T4 &lt;span class="o"&gt;(&lt;/span&gt;UUID: GPU-abc-def-123&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 同一块 GPU！&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; pod-4 -- nvidia-smi -L
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GPU 0: Tesla T4 &lt;span class="o"&gt;(&lt;/span&gt;UUID: GPU-abc-def-123&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 同一块 GPU！&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;因此，这四个 Pod 看到的其实是同一块 Tesla T4，它们仍然只能像前面手工运行 &lt;code&gt;matrix_multiply.py&lt;/code&gt; 与 &lt;code&gt;vector_add.py&lt;/code&gt; 那样，依靠底层驱动轮流执行核函数。所谓“时间分片”在这里并没有创造新的执行机制，它只是改变了 Kubernetes 对资源数量的认知。&lt;/p&gt;
&lt;h2 id="为什么时间分片具有误导性"&gt;为什么&amp;quot;时间分片&amp;quot;具有误导性&lt;/h2&gt;
&lt;p&gt;“时间分片”这个名字之所以危险，是因为它会制造关于公平性和隔离性的虚假预期。在 CPU 调度中，时间分片具有非常明确的技术含义：每个进程获得固定时长的运行窗口，时间一到，内核就强制暂停它并切换到下一个进程。正因为内核能够强制中断和切换，CPU 时间片才真正构成了公平性机制。GPU 的所谓“时间分片”完全没有这些性质。你也许会以为，当副本数为 4 时，每个 Pod 会按照轮询方式获得大致相等的执行窗口，例如每次 250ms；但实际情况并非如此，GPU 上的轮转依旧发生在核函数自然结束之后，而不是由某个更高层的调度器强制切换。&lt;/p&gt;
&lt;p&gt;因此，它既没有真正的公平调度，也没有真正的抢占能力。如果 Pod A 启动了一个持续 5 秒的长核函数，那么 Pod B、C、D 只能等它完整跑完；Pod A 不会因为“已经用完自己的公平时间”而被强制让出 GPU，它完全可以通过不断提交长核函数来主导整块设备。&lt;strong&gt;这不是设备插件实现不够好，而是 GPU 架构本身的限制：核函数一旦启动，就通常只能运行到完成，软件层无法把它像 CPU 任务那样中途剥离。&lt;/strong&gt; 从这个意义上说，NVIDIA 所谓的“时间分片”更准确的名字应该是“GPU 副本模式”或“GPU 多路复用视图”。它做的事情是让一块 GPU 在 Kubernetes 看起来像多块 GPU，而不是对时间进行真正的切片与管理。&lt;/p&gt;
&lt;h2 id="时间分片的关键局限性"&gt;时间分片的关键局限性&lt;/h2&gt;
&lt;p&gt;这就引出了时间分片最关键的局限性。首先，它依然不允许你请求具体数量的显存：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 你不能这样做：&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;gpu-memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;4000&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 时间分片不支持这个&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 你只能做：&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 仍然是&amp;#34;一块 GPU&amp;#34;（实际上是 1/4）&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;虽然在概念上每个 Pod 似乎拿到了一个 1/N 的份额，但从运行时视角看，它们依然都看见完整的 GPU。更严重的问题是，时间分片完全没有显存跟踪能力。相比之下，KAI-Scheduler 至少还维护了某种内部状态，知道某块 GPU 已经“分配了多少显存、还剩多少”，并据此避免把明显放不下的工作负载继续塞进去；运维人员也可以从这些状态中获得一些容量规划线索。时间分片则没有任何这类智能。它既不知道某个 Pod 实际需要 100MB 还是 10GB 显存，也不会记录当前每个 Pod 实际消耗了多少显存。你无法询问“Pod A 现在用了多少显存”，因为系统根本没有跟踪这些信息。你所知道的只是：这块 GPU 看起来有 4 个插槽，其中 4 个都被占满了。除此之外，几乎一无所知。这种缺乏显存核算和观测能力的特征，使时间分片在生产环境中特别危险，因为你几乎是在盲飞，直到工作负载开始因 OOM 而崩溃，问题才会显性化。&lt;/p&gt;
&lt;h2 id="比较两种方案相同的现实不同的技巧"&gt;比较两种方案：相同的现实，不同的技巧&lt;/h2&gt;
&lt;p&gt;从这个角度看，KAI-Scheduler 与 NVIDIA“时间分片”虽然表面形式不同，但底层现实非常接近。它们的差异可以概括为：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方面&lt;/th&gt;
&lt;th&gt;KAI-Scheduler&lt;/th&gt;
&lt;th&gt;NVIDIA &amp;ldquo;时间分片&amp;rdquo;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;如何欺骗 Kubernetes&lt;/td&gt;
&lt;td&gt;预留 Pod 声明整块 GPU&lt;/td&gt;
&lt;td&gt;设备插件报告 N 个虚假 GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;节点放置&lt;/td&gt;
&lt;td&gt;自动（KAI 处理）&lt;/td&gt;
&lt;td&gt;手动（你确保 Pod 共置）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;内存感知&lt;/td&gt;
&lt;td&gt;有（通过注解跟踪）&lt;/td&gt;
&lt;td&gt;无（固定插槽）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;灵活打包&lt;/td&gt;
&lt;td&gt;有（2GB + 4GB + 8GB 的 Pod）&lt;/td&gt;
&lt;td&gt;无（N 个相等插槽）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;实际 GPU 行为&lt;/td&gt;
&lt;td&gt;上下文切换&lt;/td&gt;
&lt;td&gt;上下文切换&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;内存隔离&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;计算隔离&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;公平调度&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;td&gt;无&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这张表最值得注意的地方，不是它们在哪些方面不同，而是它们在哪些关键方面完全相同：两种方案最终都只是让多个进程在一块 GPU 上轮流执行；两者都不提供真正的内存隔离、计算隔离或公平调度。换言之，它们只是两种不同的编排技巧，而不是两种不同的强制执行机制。&lt;/p&gt;
&lt;h2 id="实际示例调度器对决"&gt;实际示例：调度器对决&lt;/h2&gt;
&lt;p&gt;为了更具体地看出差异，设想下面这个实际场景。你有一块 Tesla T4（16GB 显存），需要同时运行：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2 个推理服务（每个 2GB）&lt;/li&gt;
&lt;li&gt;1 个训练作业（8GB）&lt;/li&gt;
&lt;li&gt;1 个数据预处理作业（3GB）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;按纸面数字计算，总请求正好是 16GB，看起来似乎可以完美放下。先看 KAI-Scheduler 会如何处理。假设我们用内存注解（MiB）提交这四个 Pod：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f inference-1.yaml &lt;span class="c1"&gt;# gpu-memory: &amp;#34;2000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f inference-2.yaml &lt;span class="c1"&gt;# gpu-memory: &amp;#34;2000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f training.yaml &lt;span class="c1"&gt;# gpu-memory: &amp;#34;8000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f preprocessing.yaml &lt;span class="c1"&gt;# gpu-memory: &amp;#34;3000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pods
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME READY STATUS NODE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;inference-1 1/1 Running node
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;inference-2 1/1 Running node
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;training 1/1 Running node
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;preprocessing 1/1 Running node&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;KAI 会把它们全部调度到同一节点，并通过同一个预留 Pod 共享一块 GPU。就编排层面而言，它成功了：内存请求被记录下来，资源看起来也被高效打包了。但真正进入运行时之后，情况可能是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl logs training
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Epoch 1: Training: Allocated 11GB &lt;span class="k"&gt;for&lt;/span&gt; gradients
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 使用了超过&amp;#34;请求&amp;#34;的 8GB！&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl logs preprocessing
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ERROR: CUDA out of memory
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 因为训练使用了额外的内存而崩溃&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里的问题很清楚：KAI 编排了共享，却无法强制执行声明时写下的显存上限。现在再用时间分片重复同样的实验：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 在 node-1 上配置了四个副本的时间分片&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 每个 Pod 请求一个&amp;#34;GPU&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f inference-1.yaml &lt;span class="c1"&gt;# nvidia.com/gpu: 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f inference-2.yaml &lt;span class="c1"&gt;# nvidia.com/gpu: 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f training.yaml &lt;span class="c1"&gt;# nvidia.com/gpu: 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f preprocessing.yaml &lt;span class="c1"&gt;# nvidia.com/gpu: 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pods -o wide
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME READY STATUS NODE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;inference-1 1/1 Running node-1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;inference-2 1/1 Running node-1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;training 1/1 Running node-1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;preprocessing 1/1 Running node-1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;所有 Pod 同样会顺利调度到 &lt;code&gt;node-1&lt;/code&gt;，因为时间分片已经让节点对外宣称自己拥有 4 块 GPU。区别在于，这里连 KAI 那种最起码的内存感知都没有。系统不会理解每个 Pod 希望使用多少显存，也不会据此做出更保守的摆放决策。每个 Pod 都认为自己获得了一块完整 GPU，于是训练作业完全可能直接申请 12GB 甚至 15GB 显存，并把其他工作负载顶爆：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl logs training
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Loading model... Allocated 12GB &lt;span class="k"&gt;for&lt;/span&gt; weights and gradients
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Training epoch 1/100...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl logs preprocessing
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ERROR: CUDA out of memory
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Crashed because training consumed most of the GPU memory&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="编排共享总结"&gt;编排共享总结&lt;/h2&gt;
&lt;p&gt;到这里，可以对这两类方案下一个清晰结论：它们都是编排层能力，而不是隔离机制。它们试图回答的问题是“如何让 Kubernetes 接受多个 Pod 共享同一块 GPU”，却没有真正回答“当这些 Pod 真正共享 GPU 时，如何保证它们和谐共处”。因此，这两种方案最适合用于高信任环境，也就是下列条件大体成立的场景：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;应用程序行为良好&lt;/li&gt;
&lt;li&gt;开发人员协调资源使用&lt;/li&gt;
&lt;li&gt;内存需求可预测&lt;/li&gt;
&lt;li&gt;核函数执行时间合理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一旦进入对抗性更强的多租户环境，仅仅编排共享就不够了，你需要真正的强制执行能力。这也正是 HAMi 这类通过 CUDA API 拦截实现控制的方案，或 MIG 这种通过硬件分区提供边界的方案开始变得重要的原因。不过，这已经超出了本章范围。&lt;/p&gt;
&lt;h2 id="关键要点"&gt;关键要点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;理解 Kubernetes 中的 GPU 共享，必须把“编排谁共享”与“运行时如何执行”明确区分开来。&lt;/li&gt;
&lt;li&gt;多个工作负载共享一块 GPU 并不新鲜，驱动层早就能通过上下文切换实现；真正困难的是显存和执行管线并不会因此自然分区。&lt;/li&gt;
&lt;li&gt;KAI-Scheduler 和 NVIDIA 所谓的“时间分片”本质上都在做编排，而不是创造新的隔离机制；它们解决的是资源声明与调度表达，不是运行时强制执行。&lt;/li&gt;
&lt;li&gt;在这些方案下，底层仍然是多个上下文在同一块 GPU 上轮流执行，因此既没有可靠的内存边界，也没有真正的公平性保证。&lt;/li&gt;
&lt;li&gt;“时间分片”这类名称很容易误导判断，因为它并不真正切开物理 GPU，只是改变了 Kubernetes 所看到的资源数量和调度表象。&lt;/li&gt;
&lt;li&gt;所以问题从来不是“能不能共享”，而是“能不能在共享时仍然做到安全、可控且公平”，这正是下一步必须进入强制执行层的原因。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>硬件隔离与强制执行</title><link>https://jimmysong.io/zh/book/gpu-multi-tenancy-platforms/hardware-isolation/</link><pubDate>Sun, 24 May 2026 10:48:43 +0000</pubDate><author>Jimmy Song</author><guid>https://jimmysong.io/zh/book/gpu-multi-tenancy-platforms/hardware-isolation/</guid><description>讲解如何通过 MIG（多实例 GPU）和 HAMi 的软件强制执行实现 GPU 隔离与资源控制，以及各自的优势与限制。</description><content:encoded>
&lt;p&gt;本章将讨论以下几个问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;如何通过 MIG（多实例 GPU）实现真正的并行 GPU 执行而非时间共享&lt;/li&gt;
&lt;li&gt;为什么通过 HAMi 的软件强制执行可以在任何 GPU 上工作，而不仅仅是昂贵的 GPU&lt;/li&gt;
&lt;li&gt;何时硬件隔离的成本是合理的，相较于软件替代方案&lt;/li&gt;
&lt;li&gt;如何实现 MIG 和 HAMi&lt;/li&gt;
&lt;li&gt;硬件分区与 API 拦截之间的运维权衡&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;到这一章为止，问题已经不再是“能不能让多个工作负载共用一块 GPU”，而是“当它们共享时，谁来阻止失控行为”。训练作业再次崩溃，不是因为 KAI-Scheduler 没有记录分配，也不是因为时间分片没有把 GPU 切成看似整齐的插槽，而是因为这些方案都停留在编排层：它们决定谁可以进入共享关系，却无法在运行时强制约束每个参与者。只要系统仍然建立在“请友好合作”的假设之上，多租户冲突就迟早会出现。要解决这个问题，就必须走向隔离或强制执行。本章讨论的正是两条方向截然不同的路径：一种依赖 NVIDIA 在硬件中直接构建边界，另一种则通过软件拦截 GPU API 来补上缺失的执行能力。&lt;/p&gt;
&lt;h2 id="根本区别并行-vs-顺序"&gt;根本区别：并行 vs 顺序&lt;/h2&gt;
&lt;p&gt;在进入具体方案之前，先需要明确一个会影响后文所有判断的区别：并行执行与顺序执行。到目前为止已经讨论过的手动共享、时间分片和 KAI-Scheduler，本质上都仍然属于顺序执行模型：&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/hardware-isolation/f4-1.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/hardware-isolation/f4-1.webp" alt="图 3: GPU 上的核函数轮流执行" data-caption="图 3: GPU 上的核函数轮流执行"
width="1562"
height="1054"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 3: GPU 上的核函数轮流执行&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;在这种模型中，Pod 并不是同时使用 GPU，而是轮流提交并等待核函数执行完成。当 Pod A 的核函数在运行时，Pod B 只能等待。这带来的根本问题是，只要有一个行为不当或工作负载特征不合适的 Pod，它就可能实质上独占 GPU。下面这个例子足以说明问题：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Pod A 启动一个 30 秒的矩阵运算&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl logs pod-a
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Starting 30-second computation
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Pod B 尝试运行推理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl logs pod-b
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Waiting &lt;span class="k"&gt;for&lt;/span&gt; GPU...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Waiting &lt;span class="k"&gt;for&lt;/span&gt; GPU...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Waiting &lt;span class="k"&gt;for&lt;/span&gt; GPU...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt; 秒后终于运行&lt;span class="o"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;即使内存边界能够被完美隔离，Pod B 仍然会被困在等待中，因为问题出在执行顺序本身，而不是单纯的显存分配。于是，真正重要的问题变成了：能否让多个工作负载在硬件层面同时推进，而不是只是排队轮转？&lt;/p&gt;
&lt;h2 id="mignvidia-决定在芯片中解决这个问题"&gt;MIG：NVIDIA 决定在芯片中解决这个问题&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://docs.nvidia.com/datacenter/tesla/mig-user-guide/" target="_blank" rel="noopener"&gt;多实例 GPU（MIG）&lt;/a&gt;是 NVIDIA 对共享问题给出的硬件答案。它不再试图让多个工作负载顺序地共享同一块执行引擎，而是直接把一块 GPU 物理切分成若干彼此独立的处理实例。可以把时间分片想象成多个团队轮流使用同一个会议室，而 MIG 更像是在同一栋楼里竖起永久隔墙，分出多个独立办公室；这样一来，团队之间不再排队，而是并行工作。先从一个反例开始：在 Tesla T4 上，MIG 根本不可用。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi -L
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GPU 0: Tesla T4 &lt;span class="o"&gt;(&lt;/span&gt;UUID: GPU-abc-123-def&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi mig -lgip
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;No MIG-capable GPUs found.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这揭示了 MIG 的第一个现实约束：它并不是通用能力，只在少数高端、昂贵的 GPU 上可用。切换到 H100 之后，情况就完全不同了：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# On an H100-equipped node&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi -L
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GPU 0: NVIDIA H100 80GB HBM3 &lt;span class="o"&gt;(&lt;/span&gt;UUID: GPU-xyz-789&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Check MIG capability&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi mig -lgip
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-----------------------------------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; GPU Instance Profiles: &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; GPU Name ID Instances Memory P2P SM DEC ENC &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; Free/Total GiB CE JPEG OFA &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="o"&gt;=============================================================================&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; MIG 1g.10gb &lt;span class="m"&gt;19&lt;/span&gt; 7/7 9.75 No &lt;span class="m"&gt;16&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; MIG 2g.20gb &lt;span class="m"&gt;14&lt;/span&gt; 3/3 19.62 No &lt;span class="m"&gt;32&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; MIG 3g.40gb &lt;span class="m"&gt;9&lt;/span&gt; 2/2 39.50 No &lt;span class="m"&gt;48&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; MIG 4g.40gb &lt;span class="m"&gt;5&lt;/span&gt; 1/1 39.50 No &lt;span class="m"&gt;64&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; MIG 7g.80gb &lt;span class="m"&gt;0&lt;/span&gt; 1/1 79.00 No &lt;span class="m"&gt;112&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-----------------------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;从这里可以看到，H100 支持多种不同的切分配置，而 1g.10gb 配置文件显示 &lt;code&gt;7/7&lt;/code&gt; 个可用实例，意味着整块 GPU 最多可以被切成七个独立的切片。&lt;/p&gt;
&lt;h2 id="为什么是七个切片"&gt;为什么是七个切片？&lt;/h2&gt;
&lt;p&gt;这里的数字七并不是随意挑选出来的，它来自 GPU 内部硬件组织方式。H100 SXM5 具有 132 个流式多处理器（SM）。SM 可以理解为 GPU 内部的一个“迷你处理器簇”：它不是单个 ALU，而是一组计算单元与配套资源的集合，其中包含 CUDA 核心、共享内存、寄存器文件以及调度逻辑。第一章提到 GPU 通过大规模 ALU 获得并行能力，但这些 ALU 实际上正是按 SM 为单位组织起来的。&lt;/p&gt;
&lt;p&gt;以 H100 SXM5 为例，每个 SM 含有 128 个用于单精度计算的 CUDA 核心，因此整块 GPU 共有 $132 \times 128 = 16{,}896$ 个 FP32 计算核心。当我们说 MIG 把 H100 分成七个切片时，本质上是在把这些 SM 及其相关资源切成七组可独立使用的硬件分区。对于最小的 1g.10gb 配置文件，在 H100 PCIe 变体上会获得 16 个 SM，也就是大约 $16 \times 128 = 2048$ 个 CUDA 核心，这仍然是一份具备相当计算能力的独立资源。NVIDIA 的硬件之所以能够把 GPU 干净地分成七个隔离单元，是因为它同时对以下资源做了分区：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个实例获得专用的一部分 SM&lt;/li&gt;
&lt;li&gt;每个实例获得专用的内存控制器&lt;/li&gt;
&lt;li&gt;每个实例获得独立的缓存分区&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;换句话说，硬件不是只切开了计算核心，而是切开了一整套足以独立运行的执行域。七个基本单元随后可以按照不同配置灵活组合，只要总数不超过七即可：&lt;/p&gt;
&lt;p&gt;H100 上的有效组合：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;7× 1g.10gb（7 个小实例）&lt;/li&gt;
&lt;li&gt;3× 2g.20gb + 1× 1g.10gb（3 个中 + 1 个小 = 7 个单元）&lt;/li&gt;
&lt;li&gt;2× 3g.40gb + 1× 1g.10gb（2 个大 + 1 个小 = 7 个单元）&lt;/li&gt;
&lt;li&gt;1× 7g.80gb（1 个完整 GPU = 7 个单元）&lt;/li&gt;
&lt;li&gt;1× 4g.40gb + 1× 3g.40gb（4 个单元 + 3 个单元 = 7 个单元）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;无效组合：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;4× 2g.20gb（需要 8 个单元，只有 7 个可用）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;每个配置文件消耗特定数量的基本单元：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1g.10gb = 1 个单元&lt;/li&gt;
&lt;li&gt;2g.20gb = 2 个单元&lt;/li&gt;
&lt;li&gt;3g.40gb = 3 个单元&lt;/li&gt;
&lt;li&gt;4g.40gb = 4 个单元&lt;/li&gt;
&lt;li&gt;7g.80gb = 7 个单元&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;把它理解为把一张披萨切成七块会比较直观：你可以任意组合这些切片，但永远不可能凭空再切出第八块。&lt;/p&gt;
&lt;h2 id="创建-mig-实例"&gt;创建 MIG 实例&lt;/h2&gt;
&lt;p&gt;在节点上启用 MIG 后，就可以把一块 H100 切成多个实例：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 启用 MIG 模式（需要 GPU 重置）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi -mig &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Enabled MIG Mode &lt;span class="k"&gt;for&lt;/span&gt; GPU 00000000:00:04.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;All &lt;span class="k"&gt;done&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 创建实例：2 个小的 (1g.10gb) 和 1 个大的 (3g.40gb)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi mig -cgi 19,19,9 -C
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Successfully created GPU instance ID &lt;span class="m"&gt;1&lt;/span&gt; on GPU &lt;span class="m"&gt;0&lt;/span&gt; using profile MIG 1g.10gb &lt;span class="o"&gt;(&lt;/span&gt;ID 19&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Successfully created GPU instance ID &lt;span class="m"&gt;2&lt;/span&gt; on GPU &lt;span class="m"&gt;0&lt;/span&gt; using profile MIG 1g.10gb &lt;span class="o"&gt;(&lt;/span&gt;ID 19&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Successfully created GPU instance ID &lt;span class="m"&gt;3&lt;/span&gt; on GPU &lt;span class="m"&gt;0&lt;/span&gt; using profile MIG 3g.40gb &lt;span class="o"&gt;(&lt;/span&gt;ID 9&lt;span class="o"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;查看结果时，可以看到原本一块物理 GPU 已经暴露为多个独立 MIG 设备：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi -L
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GPU 0: NVIDIA H100 80GB HBM3 &lt;span class="o"&gt;(&lt;/span&gt;UUID: GPU-xyz-789&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MIG 1g.10gb Device 0: &lt;span class="o"&gt;(&lt;/span&gt;UUID: MIG-aaa-111&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MIG 1g.10gb Device 1: &lt;span class="o"&gt;(&lt;/span&gt;UUID: MIG-bbb-222&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MIG 3g.40gb Device 2: &lt;span class="o"&gt;(&lt;/span&gt;UUID: MIG-ccc-333&lt;span class="o"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;从软件视角看，这已经不再是一块 GPU 上的多个上下文，而更像是三块独立 GPU：每个实例都有自己的 UUID、显存边界和计算资源。把工作负载分别放到不同实例上时，这种差异会非常明显：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 终端 1：在 MIG 实例 0 上运行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nv"&gt;CUDA_VISIBLE_DEVICES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;MIG-aaa-111 python matrix_multiply.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Starting matrix operations
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Consistent performance: 1.20s, 1.21s, 1.20s, 1.21s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 终端 2：同时在 MIG 实例 1 上运行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nv"&gt;CUDA_VISIBLE_DEVICES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;MIG-bbb-222 python inference.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Running inference
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Consistent latency: 45ms, 44ms, 46ms, 45ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 终端 3：在 MIG 实例 2 上运行&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nv"&gt;CUDA_VISIBLE_DEVICES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;MIG-ccc-333 python training.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Training model
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Stable throughput: &lt;span class="m"&gt;1000&lt;/span&gt; samples/sec&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这三个任务会同时运行，而不是排队轮转。没有额外等待，也没有因为共享执行管线而互相拖慢。再看监控输出时，这一点更加直观：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-----------------------------------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; MIG devices: &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="o"&gt;=============================================================================&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; GPU GI CI MIG &lt;span class="p"&gt;|&lt;/span&gt; Memory-Usage &lt;span class="p"&gt;|&lt;/span&gt; Vol&lt;span class="p"&gt;|&lt;/span&gt; Shared &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; ID ID Dev &lt;span class="p"&gt;|&lt;/span&gt; BAR1-Usage &lt;span class="p"&gt;|&lt;/span&gt; Unc&lt;span class="p"&gt;|&lt;/span&gt; CE ENC DEC OFA &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; Err&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="o"&gt;=============================================================================&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; 3847MiB / 4864MiB &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;14&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; 1923MiB / 4864MiB &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;14&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; 15234MiB / 20096MiB &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;42&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-----------------------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;每个实例都展示独立的显存和利用率指标，这说明这里发生的是真正的并行执行，而不是软件层面伪装出来的轮流共享。MIG 与 Kubernetes 的结合之所以相对优雅，也正是因为每个实例都可以作为独立资源被暴露出来。理论上，你可以手工在节点上完成这一切：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 手动 MIG 设置（在节点上）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi -mig &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;# 启用 MIG 模式&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi mig -cgi 19,14,9 -C &lt;span class="c1"&gt;# 创建配置文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 然后手动配置设备插件、更新节点标签等&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;但在生产环境中，更常见的方式是借助 GPU Operator 自动化整个流程：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nvidia.com/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ClusterPolicy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gpu-cluster-policy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mixed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 允许每个节点使用不同的 MIG 配置文件&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;devicePlugin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;default&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;device-plugin-config&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：gpu-cluster-policy.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;然后通过 ConfigMap 定义节点应采用的 MIG 配置：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ConfigMap&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;device-plugin-config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gpu-operators&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; version: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; mig-strategy: none&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mig-mixed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; version: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; mig-strategy: mixed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; mig-devices:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;1g.5gb&amp;#34;: 2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;3g.20gb&amp;#34;: 1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：mig-config.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;为节点打上对应标签后：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl label node gpu-node-1 nvidia.com/mig.config&lt;span class="o"&gt;=&lt;/span&gt;mig-mixed&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a href="https://github.com/NVIDIA/gpu-operator" target="_blank" rel="noopener"&gt;GPU Operator&lt;/a&gt; 自动：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;启用 MIG 模式&lt;/li&gt;
&lt;li&gt;创建指定的实例&lt;/li&gt;
&lt;li&gt;配置设备插件以暴露它们&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="在-pod-中使用-mig-实例"&gt;在 Pod 中使用 MIG 实例&lt;/h2&gt;
&lt;p&gt;MIG 的一个重大优势在于，它最终会表现为原生 Kubernetes 资源：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;$ kubectl describe node gpu-node-1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Allocatable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/mig-1g.5gb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/mig-3g.20gb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;因此，Pod 侧的使用方式也和请求其他资源一样自然：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;inference-pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;inference&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;inference:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/mig-1g.5gb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：inference-pod.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;这里不需要额外注解，也不需要调度器技巧；MIG 暴露出来的就是带硬件边界的原生资源。但它与 KAI-Scheduler 的差异也非常明显：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# KAI-Scheduler - 灵活但仅限软件的注解：&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;annotation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;gpu-memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3500&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 可以请求任意数量&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# MIG - 硬件强制执行但配置文件固定&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/mig-1g.5gb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 5GB 内存，14 个 SM&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：kai-pod.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;你无法请求一个并不存在的 &lt;code&gt;mig-1g.3.5gb&lt;/code&gt;。使用 MIG 时，资源形态由硬件预定义，调度只是从已有配置文件里做选择。想要 3.5GB 显存？那就只能在 1g.5gb 与 2g.10gb 之间二选一，而不能像软件注解那样声明一个任意值。这种不灵活正是硬件边界的代价。另一方面，它也带来了真正的强制执行能力。下面这个例子可以直接证明：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;memory-test&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;test&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cuda:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;python&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-c&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;import cupy as cp
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;print(&amp;#39;Attempting to allocate 8GB...&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;try:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; x = cp.zeros((1024, 1024, 1024), dtype=np.float64) # 8GB
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; print(&amp;#39;Success?!&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;except Exception as e:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; print(f&amp;#39;Failed as expected: {e}&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/mig-1g.5gb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 获得约 5GB 限制&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：memory-test-pod.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;运行该 Pod 后：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f memory-test-pod.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl logs memory-test
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Attempting to allocate 8GB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Failed as expected: cupy.cuda.memory.OutOfMemoryError: Out of memory allocating 8,589,934,592 bytes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里重要的不是日志中的错误本身，而是错误由谁触发：它不是某个软件代理“善意地”拒绝了请求，而是硬件边界直接使越界分配不可能发生。你不需要 API 拦截，也不需要假设工作负载会自觉合作。&lt;/p&gt;
&lt;h2 id="mig-的致命弱点成本与架构权衡"&gt;MIG 的致命弱点：成本与架构权衡&lt;/h2&gt;
&lt;p&gt;MIG 几乎是 GPU 共享中的理想方案，但代价也同样真实。先看最直观的一点：成本。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Azure 上的 H100 80GB（截至 2024 年）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Standard_NC40adsH100_v5: ~$6.98/小时 = ~$5,026/月
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# Tesla T4 用于比较
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Standard_NC4as_T4_v3: ~$0.53/小时 = ~$382/月&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;一块 &lt;a href="https://www.nvidia.com/en-us/data-center/h100/" target="_blank" rel="noopener"&gt;H100&lt;/a&gt; 的租用成本会比 &lt;a href="https://www.nvidia.com/en-sg/data-center/tesla-t4/" target="_blank" rel="noopener"&gt;T4&lt;/a&gt; 高出十余倍，但真正需要思考的并不仅仅是单卡价格，而是更深的架构选择：如果目标是为多个租户提供隔离资源，你究竟应该买一块支持 MIG 的 H100，还是直接购买七块 T4？对比如下：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方案&lt;/th&gt;
&lt;th&gt;一块带 MIG 的 H100&lt;/th&gt;
&lt;th&gt;七块 T4 GPU&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;成本&lt;/td&gt;
&lt;td&gt;$5,026/月&lt;/td&gt;
&lt;td&gt;$2,674/月&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;总内存&lt;/td&gt;
&lt;td&gt;80GB 共享&lt;/td&gt;
&lt;td&gt;112GB 总计 (7×16GB)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pod 放置&lt;/td&gt;
&lt;td&gt;都在同一节点&lt;/td&gt;
&lt;td&gt;跨 7 个节点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;网络带宽&lt;/td&gt;
&lt;td&gt;3.35TB/s（共享）&lt;/td&gt;
&lt;td&gt;各 320GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;故障域&lt;/td&gt;
&lt;td&gt;一个节点故障影响全部&lt;/td&gt;
&lt;td&gt;隔离的故障&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;调度灵活性&lt;/td&gt;
&lt;td&gt;固定在一个节点&lt;/td&gt;
&lt;td&gt;可跨可用区分布&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这个比较揭示了一个常被忽略的事实：在 H100 上启用 MIG，某种程度上就是在花高价把一张高端 GPU 变成若干更小的 GPU。它当然带来真实的硬件隔离，这是面对低信任或不可信工作负载时极具价值的特性；同时，H100 也确实具备更高的管理集中度、更适合紧耦合工作负载的高带宽互联，以及非常可观的片上带宽优势。但从系统设计角度看，七块 T4 带来的并不只是更低成本。它们还提供了更天然的故障隔离：一台 T4 节点宕机，不会像单节点 H100 那样把所有实例一起带倒；它们也提供了更高的地理分布灵活性，可以跨可用区部署，从而得到更真实的容灾能力。对于调度器来说，分布式小 GPU 还可能带来更灵活的装箱与节点选择，而单节点 H100 则天然把所有工作负载固定在同一个故障域和热点中。&lt;/p&gt;
&lt;p&gt;还有一个不太令人愉快但必须面对的事实：&lt;a href="https://docs.nvidia.com/datacenter/tesla/mig-user-guide/latest/" target="_blank" rel="noopener"&gt;MIG&lt;/a&gt; 只存在于 NVIDIA 最昂贵的一批 GPU 上，例如 &lt;a href="https://www.nvidia.com/en-us/data-center/a100/" target="_blank" rel="noopener"&gt;A100&lt;/a&gt;、&lt;a href="https://www.nvidia.com/en-us/data-center/h100/" target="_blank" rel="noopener"&gt;H100&lt;/a&gt; 以及 A30。许多组织已经拥有的是 T4，而不是这些高端型号；它们无法为 H100 的采购或租用成本找到合理性，却依然需要某种强制执行能力。正因为如此，单纯把 MIG 视为“标准答案”并不现实，软件强制执行方案才会变得重要。&lt;/p&gt;
&lt;h2 id="hami软件强制执行的革命"&gt;HAMi：软件强制执行的革命&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/Project-HAMi/HAMi" target="_blank" rel="noopener"&gt;HAMi&lt;/a&gt; 走的是另一条路线：既然硬件没有提供像 MIG 那样的边界，那么就把控制点前移到软件栈中，在每一个关键 GPU API 调用进入驱动之前做检查与限制。要理解 HAMi 的工作方式，首先要回顾 CUDA 程序的调用路径。一个 CUDA 应用并不是直接与 GPU 对话，它通常会先调用用户态 CUDA 库，再通过驱动与内核和设备交互：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;你的 Python 代码 → CUDA 运行时 (libcudart.so) → CUDA 驱动 (libcuda.so) → 内核驱动 → GPU&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Linux 提供了一个极其有用的机制，叫做 &lt;code&gt;LD_PRELOAD&lt;/code&gt;。它允许你在程序加载其他动态库之前，先插入自己的共享库。其基本过程是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Linux 加载你的程序&lt;/li&gt;
&lt;li&gt;在加载 CUDA 库之前，它检查 &lt;code&gt;LD_PRELOAD&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;如果设置了，它先加载该库&lt;/li&gt;
&lt;li&gt;该库可以替换（拦截）后续加载的库中的任何函数&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从效果上看，当程序说“调用 &lt;code&gt;cuMemAlloc&lt;/code&gt;”时，Linux 本来会把这次调用路由到真正的 CUDA 驱动库；HAMi 则利用 &lt;code&gt;LD_PRELOAD&lt;/code&gt; 先行注入自己的库 &lt;code&gt;libvgpu.so&lt;/code&gt;，从而在调用真正进入 &lt;a href="https://docs.nvidia.com/cuda/cuda-driver-api/index.html" target="_blank" rel="noopener"&gt;CUDA Driver API&lt;/a&gt; 之前先截获它：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;你的代码 → CUDA 运行时 → [HAMi 在此拦截] → CUDA 驱动 → GPU&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这与 cgroups 处理系统内存和 CPU 请求的方式有一种很强的类比关系：进程申请系统资源时，内核会在放行前检查 cgroup 限制；HAMi 做的事情，则是对 GPU 调用执行类似检查，只不过控制点不在内核系统调用层，而是在 CUDA 驱动 API 层。实际效果如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 没有 HAMi - 直接 CUDA 访问&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ python -c &lt;span class="s2"&gt;&amp;#34;import torch; x = torch.zeros(2048, 2048, 512).cuda()&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# 8GB 分配&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 成功 - 抓取了 8GB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 使用 HAMi - 被拦截并限制&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;LD_PRELOAD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/path/to/libvgpu.so
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;CUDA_DEVICE_MEMORY_LIMIT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4g &lt;span class="c1"&gt;# 4GB 限制&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ python -c &lt;span class="s2"&gt;&amp;#34;import torch; x = torch.zeros(2048, 2048, 512).cuda()&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# 8GB 分配&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# RuntimeError: CUDA out of memory&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;HAMi 的核心作用类似一个持续记账的会计系统：它追踪每一次显存分配，并在超出预算时立即拒绝。应用程序申请显存时，大致会经历如下过程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;应用程序请求内存：&amp;ldquo;我需要 8GB 用于这个张量&amp;rdquo;&lt;/li&gt;
&lt;li&gt;HAMi 拦截：在请求到达 CUDA 驱动之前，HAMi 检查：
&lt;ul&gt;
&lt;li&gt;这个 Pod 的内存限制是多少？（4GB）&lt;/li&gt;
&lt;li&gt;已经使用了多少？（1GB）&lt;/li&gt;
&lt;li&gt;这个分配是否会超过限制？（1GB + 8GB &amp;gt; 4GB）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HAMi 决定：
&lt;ul&gt;
&lt;li&gt;如果在限制内：将请求传递给 CUDA 驱动&lt;/li&gt;
&lt;li&gt;如果超过限制：立即返回内存不足错误&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;跟踪：如果分配成功，HAMi 将其记录在表中：
&lt;ul&gt;
&lt;li&gt;内存地址、分配大小&lt;/li&gt;
&lt;li&gt;该进程的运行总计&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;清理：当内存被释放时，HAMi 更新其记录&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这正是它与 cgroups 的相似之处：系统内存由内核在 &lt;code&gt;malloc()&lt;/code&gt; 背后做硬限制，GPU 显存则由 HAMi 在 &lt;code&gt;cuMemAlloc()&lt;/code&gt; 之前进行检查。如果请求会超出 Pod 限制，HAMi 直接返回 OOM，而根本不把调用传给真正的 CUDA 驱动。更进一步地，对于那些会主动查询可用显存的应用程序，HAMi 还采用了一个非常关键的技巧：它会“虚构”一个受限后的设备视图。&lt;/p&gt;
&lt;p&gt;当应用程序询问“这张 GPU 还有多少内存可用”时，HAMi 返回的不是物理 GPU 的真实答案，而是对该 Pod 而言的受限答案：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;真实 GPU：&amp;ldquo;总共 16GB，空闲 12GB&amp;rdquo;&lt;/li&gt;
&lt;li&gt;HAMi 回答：&amp;ldquo;总共 4GB，空闲 3GB&amp;rdquo;（基于 Pod 的限制）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;于是，行为良好的应用程序会误以为自己运行在一张更小的 GPU 上，并自然调整自身行为，从而避免连超限尝试都发生。&lt;/p&gt;
&lt;h2 id="计算限流令牌桶算法实战"&gt;计算限流：令牌桶算法实战&lt;/h2&gt;
&lt;p&gt;显存限制相对直接，因为它天然是一个“允许或拒绝”的问题；计算限流则复杂得多。GPU 上的核函数一旦启动，HAMi 既不能在中途停止它，也不能在事后限制它到底占用了多少 SM。因此，当用户配置 &lt;code&gt;nvidia.com/gpucores: 30&lt;/code&gt; 时，HAMi 实际表达的是一个目标 SM 利用率或计算吞吐比例，而不是“只允许使用固定数量的 SM”。为了逼近这个目标，HAMi 采用了类似网络限速的令牌桶算法，并把它挂接在 &lt;code&gt;cuLaunchKernel&lt;/code&gt; 这样的核函数启动路径上。整个机制大致如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;令牌计算&lt;/strong&gt;：每次核函数启动根据网格块数量（gridDimX × gridDimY × gridDimZ）消耗令牌&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;令牌检查&lt;/strong&gt;：在启动前，HAMi 检查是否有足够的令牌&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;阻塞等待&lt;/strong&gt;：如果令牌不足，线程阻塞直到令牌积累&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;令牌消耗&lt;/strong&gt;：一旦有可用令牌，扣除令牌，核函数启动&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;持续补充&lt;/strong&gt;：后台线程根据实际 GPU 利用率补充令牌&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其简化逻辑可以表示为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;当请求核函数启动时&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="n"&gt;intercepted_kernel_launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;grid_dimensions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block_dimensions&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;计算这个核函数需要多少令牌&lt;/span&gt;&lt;span class="err"&gt;（&lt;/span&gt;&lt;span class="n"&gt;基于网格大小&lt;/span&gt;&lt;span class="err"&gt;）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;token_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;grid_dimensions&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;实际上只是&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;不是&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;等待直到有足够的令牌&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;available_tokens&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;token_cost&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;阻塞线程&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;原子地消耗令牌&lt;/span&gt;&lt;span class="err"&gt;（&lt;/span&gt;&lt;span class="n"&gt;在实际实现中使用&lt;/span&gt; &lt;span class="n"&gt;CAS&lt;/span&gt;&lt;span class="err"&gt;）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;available_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;available_tokens&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;token_cost&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;启动实际的核函数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;real_cuda_kernel_launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;grid_dimensions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;block_dimensions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;后台线程持续调整令牌&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="n"&gt;token_manager&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;current_gpu_utilization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;measure_gpu_utilization&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;target_utilization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CUDA_DEVICE_SM_LIMIT&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;例如&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_gpu_utilization&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;target_utilization&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;我们低于配额&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;添加更多令牌&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tokens_to_add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculate_increase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;available_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;available_tokens&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;tokens_to_add&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;我们超过配额&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;减少令牌&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;tokens_to_remove&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculate_decrease&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;available_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;available_tokens&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;tokens_to_remove&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="n"&gt;可配置的检查间隔&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这里真正巧妙的地方在于反馈回路。后台线程会通过 NVML（&lt;a href="https://developer.nvidia.com/management-library-nvml" target="_blank" rel="noopener"&gt;NVIDIA 管理库&lt;/a&gt;）持续观察实际 GPU 利用率，并动态调整令牌补充速度，从而把 Pod 的长期计算占比逐步拉向目标值。尽管如此，物理约束依然存在：如果一个核函数本身需要连续运行 30 秒，它仍然会完整运行 30 秒，HAMi 不能像 CPU 调度器那样在中途切断它。令牌桶所能做到的，是让这个 Pod 在消耗完令牌之后，必须等待令牌重新补充，才能继续提交下一批核函数。因此，它对大量短小核函数组成的工作负载更有效，对偶尔启动超长核函数的应用则效果有限。HAMi 也为此提供了几种策略选项：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;默认策略&lt;/strong&gt;：具有自适应补充的普通令牌桶&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;强制策略&lt;/strong&gt;：严格将利用率限制在配置百分比以下&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;禁用策略&lt;/strong&gt;：完全关闭计算限制&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;注意：如果在 HAMi 配置中 &lt;code&gt;nvidia.disablecorelimit&lt;/code&gt; 设置为 true，则无论 Pod 设置如何，计算限制都会被全局禁用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;因此，HAMi 对计算的强制执行从本质上仍然是“尽力而为”的：它不能违反 GPU 架构本身的物理规律，但它确实比简单地在核函数启动之间插入延迟要精细得多。由于 HAMi 不依赖特定高端 GPU，它的部署路径也相对直接：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 添加 HAMi helm 仓库&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ helm repo add hami-charts https://project-hami.github.io/HAMi/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 安装 HAMi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ helm install hami hami-charts/hami &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--namespace kube-system &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--set scheduler.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--set devicePlugin.enabled&lt;span class="o"&gt;=&lt;/span&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;安装后先验证组件状态：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pods -n kube-system &lt;span class="p"&gt;|&lt;/span&gt; grep hami
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hami-device-plugin-ds-xvnbg 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 2m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hami-scheduler-7b9d8954-kjplw 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 2m&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;安装成功后，HAMi 会向 Kubernetes 暴露新的资源类型：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;inference-with-limits&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;inference&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;inference:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 需要 GPU 访问&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpumem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 4GB 内存限制（强制执行！）&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpucores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 30% 计算（尽力而为）&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：pod.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;与 KAI 的注解不同，这里已经是正式的 Kubernetes 资源模型。HAMi 对 &lt;code&gt;nvidia.com/gpumem&lt;/code&gt; 提供真正的内存强制执行，而对 &lt;code&gt;nvidia.com/gpucores&lt;/code&gt; 提供尽力而为的计算限流。下面这个例子展示了它如何阻止超量申请：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;hami-test-1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;memory-limited&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tensorflow/tensorflow:latest-gpu&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpumem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2048&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 2GB 限制&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;python&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-c&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;import tensorflow as tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;print(&amp;#39;Allocated memory limit: 2048MB&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;print(&amp;#39;Attempting to allocate 3GB...&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;try:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;# Try to allocate 3GB
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;x = tf.random.normal([1024, 768, 1024])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;print(&amp;#39;Should not see this message!&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;except Exception as e:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;print(f&amp;#39;Blocked by HAMi: {e}&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;hami-test-2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;compute-limited&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tensorflow/tensorflow:latest-gpu&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpumem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4096&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpucores&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;20&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 20% 计算限制&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;python&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-c&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;import time
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;import tensorflow as tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;print(&amp;#39;Running with 20% compute limit&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;start = time.time()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;Heavy computation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;for i in range(100):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; x = tf.random.normal([2048, 2048])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; y = tf.matmul(x, x)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;duration = time.time() - start
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;print(f&amp;#39;Duration: {duration:.2f}s (throttle by HAMi)&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：hami-test.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;部署并检查：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f hami-test.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl logs hami-test-1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Allocated memory limit: 2048MB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Attempting to allocate 3GB...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Blocked by HAMi: ResourceExhaustedError: OOM when allocating tensor
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl logs hami-test-2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Running with 20% compute limit
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Duration: 45.23s &lt;span class="o"&gt;(&lt;/span&gt;throttled by HAMi&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 没有限流的话，这大约需要 10 秒&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这说明，即使在 Tesla T4 这类并不支持 MIG 的设备上，HAMi 也能实际约束显存和计算使用。&lt;/p&gt;
&lt;h2 id="软件强制执行的现实"&gt;软件强制执行的现实&lt;/h2&gt;
&lt;p&gt;不过，HAMi 再强，也仍然是一种软件技巧，而不是像 MIG 那样的硬件边界。它最直接的风险之一是版本敏感性。CUDA API 会不断演进，新版本会引入新的分配函数或调用路径；一旦 HAMi 尚未适配这些变化，拦截链条就可能出现空洞。例如，在 CUDA 12.5 中出现新的内存分配函数 &lt;code&gt;cuMemAllocAsync_v2&lt;/code&gt; 时，就可能出现如下情况：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Driver Version: 535.104.12 CUDA Version: 12.5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl logs hami-device-plugin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ERROR: Unknown CUDA &lt;span class="k"&gt;function&lt;/span&gt; cuMemAllocAsync_v2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;WARNING: Cannot intercept new allocation API&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这意味着使用新 API 的应用有可能绕过 HAMi 的控制。更进一步地，&lt;code&gt;LD_PRELOAD&lt;/code&gt; 本身终究只是运行时注入机制，而不是不可绕过的安全边界。具备足够知识的恶意用户理论上可以尝试绕过它：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 恶意用户绕过 LD_PRELOAD&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;unset&lt;/span&gt; LD_PRELOAD
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ python gpu_memorygrab.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 成功抓取了所有 GPU 内存&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;还可以使用静态链接规避动态加载器，或通过其他方式绕开预期调用路径。严格说来，拦截模型默认假设大多数工作负载是合作型的；面对蓄意规避时，它天然比硬件边界更脆弱。当然，HAMi 本身也实现了额外的 PID 跟踪和共享内存机制，使绕过不只是“取消一个环境变量”那么简单，但这并不会改变它是软件拦截层的事实。另一方面，性能开销也是必须接受的现实，因为每一次 CUDA 调用现在都要多经过一层逻辑：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# 没有 HAMi 的基准测试
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Inference latency: 12.3ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# 使用 HAMi 的基准测试
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Inference latency: 14.1ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# 拦截带来约 15% 的开销&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;对于每秒触发成千上万次 CUDA 调用的训练任务，这种开销会积累成可感知的性能损失。但即便如此，HAMi 仍然在一个极其重要的维度上提供了价值：在没有硬件隔离可用的地方，它把“真正的强制执行”带到了现有 GPU 之上。对很多团队而言，这种现实主义的收益已经足够重要。&lt;/p&gt;
&lt;h2 id="生产环境注意事项"&gt;生产环境注意事项&lt;/h2&gt;
&lt;p&gt;无论选择 MIG 还是 HAMi，只要进入生产环境，就会暴露出概念验证阶段不明显的运维成本。对于 MIG 而言，最令人头疼的问题之一是配置文件锁定。MIG 切片配置一旦建立，后续想调整配置文件，通常需要先把节点彻底排空：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 不能不排空节点就更改 MIG 配置文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl drain gpu-node-1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi mig -dci
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi mig -dgi
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 用新配置文件重新创建&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 重新部署工作负载&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;假设你现在把 H100 配置成七个 1g.10gb 实例来承载推理业务，半年后团队需要运行一个要求 3g.40gb 的更大模型时，你不能像改一个调度策略那样平滑调整，而必须驱逐运行中的工作负载，销毁现有 MIG 实例，重新创建新的切分布局，再把应用部署回来。这意味着维护窗口、工作负载迁移以及生产停机都将成为现实问题。这种刚性还会直接影响资源利用率，因为 MIG 配置必须以那七个基本单元为粒度进行组合：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 这完美工作&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;mig-devices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;1g.10gb&amp;#34;: &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 使用所有 7/7 单元&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# 这导致资源闲置&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;mig-devices&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;3g.40gb&amp;#34;: &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 只使用 6/7 单元&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# 你 GPU 的 1/7（H100 的 14%！）闲置着&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;也就是说，最后那个 $1/7$ 单元可能长期闲置，而你仍然要为这部分昂贵硬件埋单。HAMi 则有另一类运维痛点。它不受固定硬件切片约束，但会把驱动升级变成一片雷区：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 驱动更新后&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl logs hami-device-plugin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Warning: CUDA 12.5 detected, HAMi tested up to 12.2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Some interceptions may fail
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 解决方案：等待 HAMi 更新或固定驱动版本&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;每一次 NVIDIA 驱动升级都可能让 HAMi 的拦截逻辑失效，于是团队不得不在“继续停留在旧驱动版本”与“冒着 HAMi 兼容性风险升级驱动”之间做取舍。很多团队因此会维护详细的驱动兼容性矩阵，并在每次升级前做大量验证。与此同时，监控体系也会变得复杂，因为物理 GPU 视图与 HAMi 的逻辑配额视图并不总是一致：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# nvidia-smi 显示物理使用情况&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Memory-Usage: 15GB / 16GB &lt;span class="c1"&gt;# 看起来几乎满了！&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 但 HAMi 的视图不同&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; hami-monitor -- hami-status
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Pod A: 2GB used of 4GB limit
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Pod B: 3GB used of 4GB limit
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Pod C: 4GB used of 4GB limit
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Pod D: 6GB used of 8GB limit &lt;span class="c1"&gt;# 过度配置！&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 总计：已分配 15GB，但限制总和是 20GB！&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;当 &lt;code&gt;nvidia.deviceMemoryScaling &amp;gt; 1&lt;/code&gt; 时，这种“逻辑限制总和大于物理内存”的现象甚至是被故意允许的，类似于 Linux 中对内存进行过量分配。于是问题就变成：你该相信哪个监控系统？告警阈值应该基于物理显存使用量，还是基于 HAMi 的逻辑限制视图？这些已经不只是技术细节，而是需要团队培训、文档体系和运维流程共同消化的组织性问题。&lt;/p&gt;
&lt;h2 id="gpu-共享的演进"&gt;GPU 共享的演进&lt;/h2&gt;
&lt;p&gt;回顾整条演进路径，可以看到 GPU 共享已经从最初“多个容器无协调地共同访问一块设备”，发展到通过 MPS、时间分片和 KAI-Scheduler 进行编排，再进一步发展到 HAMi 这种软件强制执行，以及 MIG 这种真正的硬件分区。路径的每一步都在试图回答同一个问题：共享能否被控制、被观察、被信任。但无论最终选择 MIG 的硬件隔离，还是 HAMi 的软件拦截，你都不可避免地进入同一个新的运维阶段：如何确认这些机制真的在工作？如何跨不同共享模型统一监控 GPU 利用率与显存占用？又如何在度量方式彼此不同的前提下做出合理的资源优化？这正是后续章节要继续讨论的方向。&lt;/p&gt;
&lt;h2 id="关键要点"&gt;关键要点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;一旦编排层无法再约束共享关系，就必须引入真正的强制执行机制，否则共享只会停留在“登记了资源”而不是“控制了资源”。&lt;/li&gt;
&lt;li&gt;MIG 提供的是最接近理想状态的答案，因为它把并行执行、显存隔离和故障边界都落实为硬件事实。&lt;/li&gt;
&lt;li&gt;MIG 的代价同样明确：它只存在于少数高端 GPU 上，资源粒度固定，配置调整刚性强，成本和架构约束都不可忽视。&lt;/li&gt;
&lt;li&gt;HAMi 代表的是软件强制执行路线，它通过 API 拦截把类似 cgroup 的控制能力带到现有 GPU 上，因此更适合已经部署大量 T4 等设备的现实环境。&lt;/li&gt;
&lt;li&gt;HAMi 不如 MIG 绝对，它必须面对版本兼容、绕过风险和额外开销，但在缺少硬件隔离条件时，往往是更可落地的治理手段。&lt;/li&gt;
&lt;li&gt;最终的选择不该由“哪项技术更高级”决定，而应由信任模型、成本约束、工作负载特征以及可接受的运维复杂度共同决定。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>GPU 集群监控</title><link>https://jimmysong.io/zh/book/gpu-multi-tenancy-platforms/monitoring/</link><pubDate>Sun, 24 May 2026 13:07:50 +0000</pubDate><author>Jimmy Song</author><guid>https://jimmysong.io/zh/book/gpu-multi-tenancy-platforms/monitoring/</guid><description>介绍 GPU 集群监控的复杂性，包括 nvidia-smi、DCGM、Kubernetes 指标的差异，以及如何构建有效的 GPU 可观测性体系。</description><content:encoded>
&lt;p&gt;本章将讨论以下几个问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;为什么 nvidia-smi、Kubernetes 指标和实际 GPU 使用量会对同一块硬件讲出三个不同的故事&lt;/li&gt;
&lt;li&gt;为什么 GPU 利用率百分比常常误导人，让人把“忙碌”误以为“高效”&lt;/li&gt;
&lt;li&gt;为什么僵尸进程会在 Pod 被删除后仍然占据 GPU 内存&lt;/li&gt;
&lt;li&gt;如何使用 DCGM 弥合“你以为正在发生的事情”和“实际正在发生的事情”之间的差距&lt;/li&gt;
&lt;li&gt;哪些模式可以帮助你在生产工作负载崩溃前预测 GPU 故障&lt;/li&gt;
&lt;li&gt;为什么许多团队会把 60% 到 70% 的 GPU 预算浪费在闲置或未充分利用的资源上&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GPU 集群监控之所以困难，不在于指标太少，而在于指标太多却彼此割裂。表面上看，一切都可能是健康的：节点在线，Pod 正在运行，显卡温度正常，&lt;code&gt;nvidia-smi&lt;/code&gt; 甚至还报告出一个相当可观的利用率数字。但与此同时，训练任务可能已经两个小时没有推进，推理服务在持续返回错误，调度器后面还堆着几十个 Pending 的 Pod，而这些异常并不会自然地出现在同一个仪表板上。GPU 监控的核心挑战，正是如何在这些彼此不一致的信号之间建立可解释的联系。&lt;/p&gt;
&lt;p&gt;先看一个典型的场景：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pods
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME READY STATUS RESTARTS AGE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;training-job-42 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 3h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;inference-svc-1 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 5h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;inference-svc-2 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 5h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GPU 0: Tesla T4, 15360MiB, 42°C
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 2156MiB / 15360MiB used
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; GPU-Util: 78%&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果只看这两段输出，几乎所有人都会得出“系统正常，只是应用自身有问题”的结论。但真正令人头痛的地方在于，这类判断往往恰恰是错的。GPU 世界里最危险的情况之一，就是所有基础监控都显示绿色，而真正的容量冲突、调度拥塞或运行时泄漏却藏在这些“看起来正常”的数字背后。&lt;/p&gt;
&lt;h2 id="gpu-现实的三种视图"&gt;GPU 现实的三种视图&lt;/h2&gt;
&lt;p&gt;CPU 监控之所以相对直接，是因为操作系统内核天然掌握 CPU 调度、内存分配和进程运行的全貌。GPU 并不是这样。对同一块显卡，至少存在三种彼此脱节的现实视图：硬件视图、Kubernetes 资源视图以及进程或容器级的实际使用视图。理解 GPU 集群监控，首先必须接受这三层现实并不自动一致。&lt;/p&gt;
&lt;p&gt;第一层是 &lt;code&gt;nvidia-smi&lt;/code&gt; 呈现的物理现实，也就是绝大多数人排障时第一眼看到的东西：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-----------------------------------------------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; NVIDIA-SMI 535.104.12 Driver Version: 535.104.12 CUDA Version: 12.4 &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-------------------------------+----------------------+----------------------+------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; GPU Name Persistence-M&lt;span class="p"&gt;|&lt;/span&gt; Bus-Id Disp.A &lt;span class="p"&gt;|&lt;/span&gt; Volatile Uncorr. ECC &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; Fan Temp Perf Pwr:Usage/Cap&lt;span class="p"&gt;|&lt;/span&gt; Memory-Usage &lt;span class="p"&gt;|&lt;/span&gt; GPU-Util Compute M. &lt;span class="p"&gt;|&lt;/span&gt; MIG M. &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="o"&gt;===============================&lt;/span&gt;+&lt;span class="o"&gt;======================&lt;/span&gt;+&lt;span class="o"&gt;======================&lt;/span&gt;+&lt;span class="o"&gt;============&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; Tesla T4 Off &lt;span class="p"&gt;|&lt;/span&gt; 00000000:00:04.0 Off&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; N/A N/A &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; N/A 42C P0 28W / 70W &lt;span class="p"&gt;|&lt;/span&gt; 8234MiB / 15360MiB &lt;span class="p"&gt;|&lt;/span&gt; 78% Default &lt;span class="p"&gt;|&lt;/span&gt; N/A &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-------------------------------+----------------------+----------------------+------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-----------------------------------------------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; Processes: &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; GPU GI CI PID Type Process name GPU Memory Usage &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="o"&gt;=========================================================================================&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; N/A N/A &lt;span class="m"&gt;2847&lt;/span&gt; C python train.py 4234MiB &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; N/A N/A &lt;span class="m"&gt;3923&lt;/span&gt; C python inference.py 2896MiB &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; N/A N/A &lt;span class="m"&gt;4102&lt;/span&gt; C python notebook.py 1104MiB &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-----------------------------------------------------------------------------------------+&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这份输出显得信息丰富，却并不意味着它足以解释系统当前的行为。最典型的误导来自其中的 GPU 利用率。很多人看到 &lt;code&gt;GPU-Util: 78%&lt;/code&gt;，会自然理解为“GPU 完成了 78% 负载的有效工作”；但这个数字真正表达的并不是完成了多少计算，而是“在采样窗口里，GPU 是否至少执行过某个核函数”。这两者之间差异极大。&lt;/p&gt;
&lt;h2 id="为什么-gpu-利用率会撒谎"&gt;为什么 GPU 利用率会撒谎&lt;/h2&gt;
&lt;p&gt;下面这个看似荒谬的程序，正好能说明问题：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cupy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;cp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 创建一个只有一个元素的微小数组&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 执行一个微不足道的 GPU 操作：给单个元素加 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 这会启动一个约 1 微秒的 GPU 核函数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# CPU 线程休眠 999 微秒&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# GPU 在这段时间内闲置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.000999&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：terrible_gpu_code.py&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;这个程序每轮循环只让 GPU 做一件微不足道的事：给一个单元素数组加一。核函数大约只运行 $1\mu s$，随后 CPU 休眠约 $999\mu s$。换言之，在每个毫秒周期中，GPU 真正工作的时间只有千分之一，理论上的实际利用率只有 0.1%。然而，当你运行它时，&lt;code&gt;nvidia-smi&lt;/code&gt; 却可能报告一个极高的利用率：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ python terrible_gpu_code.py &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;1&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="m"&gt;2847&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi --query-gpu&lt;span class="o"&gt;=&lt;/span&gt;utilization.gpu --format&lt;span class="o"&gt;=&lt;/span&gt;csv,noheader
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;87&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 87% 的利用率！&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 但我们几乎什么都没做&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这并不是驱动“坏了”，而是 &lt;code&gt;nvidia-smi&lt;/code&gt; 统计口径与人们的直觉完全不同。它并不衡量 GPU 做了多少有效工作，而是在固定采样窗口里回答一个二元问题：这段时间里是否存在任何核函数执行。要看懂这件事，必须进一步理解 &lt;code&gt;nvidia-smi&lt;/code&gt; 的采样方式。&lt;/p&gt;
&lt;h2 id="nvidia-smi-实际如何测量"&gt;nvidia-smi 实际如何测量&lt;/h2&gt;
&lt;p&gt;GPU 驱动并不会持续、逐周期地跟踪每一个核函数到底占用了多少时间。更准确地说，它会以固定时间窗口做采样，通常大约每 $1/6$ 秒，即约 166ms 询问一次状态。每次采样时，它只判断在这个窗口内有没有检测到核函数活动：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;时间：0ms 166ms 332ms
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;核函数：[50μs 核函数]----等待----[2μs]----等待----[100μs]----等待----
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;采样： &amp;lt;-------- 采样 1--------&amp;gt; &amp;lt;-------- 采样 2--------&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;结果： &amp;#34;检测到活动&amp;#34; &amp;#34;检测到活动&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;由此就会出现一个非常反直觉的结果：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;采样 1 中只要有一个 50 微秒的核函数运行，整个 166ms 窗口就会被记作“活跃”&lt;/li&gt;
&lt;li&gt;采样 2 中哪怕只出现一个 100 微秒的核函数，整个窗口同样会被记作“活跃”&lt;/li&gt;
&lt;li&gt;最终 &lt;code&gt;nvidia-smi&lt;/code&gt; 会给出 100% 利用率，因为两个采样窗口都检测到了活动&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但如果从真实执行时间来算：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;核函数总运行时间：50μs + 2μs + 100μs = 152μs&lt;/li&gt;
&lt;li&gt;总经过时间：332ms&lt;/li&gt;
&lt;li&gt;实际利用率：0.152ms / 332ms = 0.046%&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这就是 GPU 监控中第一个必须牢记的结论：&lt;code&gt;nvidia-smi&lt;/code&gt; 的 GPU 利用率更像“活跃窗口比例”，而不是“有效计算占比”。因此，一个持续提交大量极短核函数的程序可以显示接近 100% 的利用率，同时实际上几乎没有完成任何有意义的工作。GPU 在统计口径上很忙，却没有产出。&lt;/p&gt;
&lt;h2 id="三层内存"&gt;三层内存&lt;/h2&gt;
&lt;p&gt;显存监控同样不能按表面数字理解。对许多开发者而言，显存似乎只有“已用”和“未用”两种状态；但实际运行时，至少存在三层不同含义的内存状态：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;空闲内存：没有被任何进程占用&lt;/li&gt;
&lt;li&gt;预留内存：被 CUDA 或框架的内存池拿走，但尚未被真实张量使用&lt;/li&gt;
&lt;li&gt;已分配内存：真正被数据对象占据的显存&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以下示例展示了这一点：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cupy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;cp&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 在任何 GPU 操作之前&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# GPU 总共有 16GB，全部空闲&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;array1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 512MB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# CUDA 预留了一个 2GB 的池（默认行为）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 但只为实际数据分配了 512MB&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：memory.py&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;这时如果仅用 &lt;code&gt;nvidia-smi&lt;/code&gt; 检查：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi --query-gpu&lt;span class="o"&gt;=&lt;/span&gt;memory.used,memory.free --format&lt;span class="o"&gt;=&lt;/span&gt;csv,noheader
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;512&lt;/span&gt; MiB, &lt;span class="m"&gt;15848&lt;/span&gt; MiB &lt;span class="c1"&gt;# Shows only allocated, not reserved&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;你会误以为系统还几乎完整地空着。但如果随后立刻尝试再分配 15GB，大概率会失败，因为 CUDA 已经先行预留了一部分显存给内存池。也就是说，决定后续分配是否成功的，并不只是“当前真正被张量占用的显存”，还包括运行库为了性能而预抓取的那部分内存。真正可用的容量可能是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;总显存：16GB&lt;/li&gt;
&lt;li&gt;CUDA 预留：2GB&lt;/li&gt;
&lt;li&gt;剩余可用：14GB&lt;/li&gt;
&lt;li&gt;下一次申请：15GB&lt;/li&gt;
&lt;li&gt;结果：失败&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;遗憾的是，&lt;code&gt;nvidia-smi&lt;/code&gt; 并不会直接把这种“框架已预留但尚未真正存放业务数据”的层次显式呈现出来。要看懂这一层，只能回到进程内部去读内存池指标：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_default_memory_pool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Allocated: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;used_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1e9&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; GB&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 0.51 GB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Reserved: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;1e9&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; GB&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 2.00 GB&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：memory.py&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;从监控角度说，这意味着单看节点级显存占用永远不够。你还必须知道框架内部的内存池策略，否则就无法分辨“真的快用满了”与“只是框架为后续性能预留了一块空间”之间的差别。&lt;/p&gt;
&lt;h2 id="僵尸进程问题"&gt;僵尸进程问题&lt;/h2&gt;
&lt;p&gt;在生产环境中，比显存池更令人烦躁的是僵尸进程。最典型的场景如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl delete pod training-job-42
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pod &lt;span class="s2"&gt;&amp;#34;training-job-42&amp;#34;&lt;/span&gt; deleted
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;No running processes found
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 但是...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi --query-gpu&lt;span class="o"&gt;=&lt;/span&gt;memory.used --format&lt;span class="o"&gt;=&lt;/span&gt;csv,noheader
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;4234&lt;/span&gt; MiB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 内存仍然被占用！&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Pod 已经删除，进程列表里也不再显示活跃任务，但数 GB 的显存却仍然挂在设备上。进一步排查时，常常会看到这样的输出：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ fuser -v /dev/nvidia0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;USER PID ACCESS COMMAND
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/dev/nvidia0:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;root kernel mount /dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;65534&lt;/span&gt; &lt;span class="m"&gt;27341&lt;/span&gt; F...m python &amp;lt;defunct&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;从 Linux 进程视角看，这个任务已经结束；但从 GPU 驱动视角看，与它绑定的上下文和显存状态却未必已经被正确清理。于是，一个看似已经消失的进程，仍可能继续占据 GPU 内存。&lt;/p&gt;
&lt;h2 id="为什么僵尸进程能占据-gpu-内存"&gt;为什么僵尸进程能占据 GPU 内存&lt;/h2&gt;
&lt;p&gt;理解这个问题的关键，在于 CPU 进程生命周期与 GPU 驱动状态机并不是完全同步的。正常情况下，Python 脚本结束时，运行时会触发对象析构，CUDA 清理例程得以执行，驱动接收到显式释放请求，于是上下文关闭，显存归还给设备池。问题在于，容器在真实生产环境里很少总是“正常退出”。它可能因为达到 CPU 或系统内存限制而被 OOMKilled，可能因为节点压力而被驱逐，也可能被开发者用 &lt;code&gt;kubectl delete pod --force --grace-period=0&lt;/code&gt; 粗暴终止。&lt;/p&gt;
&lt;p&gt;在这些情况下，内核向进程发送的是 &lt;code&gt;SIGKILL&lt;/code&gt;。这个信号不会给用户态代码留下收尾时间，进程会被立刻剥离出系统资源视图：CPU 时间被回收，系统内存被释放，文件描述符被关闭。但 GPU 驱动并不是以内核相同的生命周期模型管理显存。驱动内部维护着自己的进程与上下文表，它仍然可能在等待那个永远不会再到来的清理调用。于是，内核已经认为进程不存在，而 GPU 驱动仍然保留着该 PID 曾经持有的显存分配记录。&lt;/p&gt;
&lt;p&gt;这也是 GPU 资源管理与普通 CPU 内存管理最不同、也最容易让 Kubernetes 运维者感到失控的地方之一。面对这种僵尸显存，你能采取的手段通常都很痛苦：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;重新加载 NVIDIA 驱动模块，强制清空所有上下文，但代价是该节点上的所有 GPU 工作负载都会中断&lt;/li&gt;
&lt;li&gt;直接重启整台节点，以换取一个彻底干净的设备状态，但这意味着节点级停机和 Pod 重调度&lt;/li&gt;
&lt;li&gt;尝试 &lt;code&gt;nvidia-smi --gpu-reset&lt;/code&gt;，但只在驱动认为该 GPU 不再被任何进程占用时才可能成功&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这正是为什么 GPU 显存泄漏在 Kubernetes 里如此阴险。它们不会像普通应用错误那样立即爆炸，而是缓慢吞噬可用显存，直到新的 Pod 明明被调度到了“看起来还有空间”的 GPU 上，却反复因为 OOM 失败。&lt;/p&gt;
&lt;h2 id="kubernetes-看到的是另一种现实"&gt;Kubernetes 看到的是另一种现实&lt;/h2&gt;
&lt;p&gt;从硬件视角转向编排层时，现实又会发生一次断裂。Kubernetes 并不理解物理 GPU 内部到底发生了什么，它只知道设备插件向它暴露了多少“资源单位”：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl describe node gpu-node-1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Capacity:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvidia.com/gpu: &lt;span class="m"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Allocatable:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvidia.com/gpu: &lt;span class="m"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Allocated resources:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvidia.com/gpu: &lt;span class="m"&gt;4&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这段输出看起来像是在说“节点上有四块 GPU，四块都被用了”。但如果你实际上运行的是一块开启了 4 个副本时间分片的 T4，那么这四个资源单位并不是四块彼此独立的物理 GPU，而只是设备插件向 Kubernetes 暴露出来的四个可分配槽位。继续往下看 Pod 视角：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pods -A -o custom-columns&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME:.metadata.name, &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GPU_REQUEST:.spec.containers&lt;span class="o"&gt;[&lt;/span&gt;*&lt;span class="o"&gt;]&lt;/span&gt;.resources.limits.nvidia&lt;span class="se"&gt;\.&lt;/span&gt;com/gpu
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME GPU_REQUEST
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;training-job-1 &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;training-job-2 &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;inference-svc &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;notebook-3 &lt;span class="m"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;四个 Pod 都会“以为”自己拿到了一块独立 GPU。Kubernetes 也会把它们记作四个完整分配。但物理世界中，它们可能全都在争同一块 T4。这说明 Kubernetes 的 GPU 视图天然是抽象化、离散化的，它只适合回答“还能不能调度”，却不适合解释“为什么现在性能很差”。&lt;/p&gt;
&lt;p&gt;在 MIG 场景中，这种断裂甚至更严重。节点视图可能是这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl describe node gpu-node-2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Capacity:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvidia.com/mig-1g.5gb: &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvidia.com/mig-2g.10gb: &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvidia.com/mig-3g.20gb: &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Allocated resources:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvidia.com/mig-1g.5gb: &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvidia.com/mig-2g.10gb: &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvidia.com/mig-3g.20gb: &lt;span class="m"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Kubernetes 看到的是四种资源类型，而不是四个具名的、可追踪的 MIG 实例。于是，当一个 Pod 请求 &lt;code&gt;nvidia.com/mig-1g.5gb: 1&lt;/code&gt; 时，Kubernetes 知道它拿到了一份 &lt;code&gt;1g.5gb&lt;/code&gt; 资源，但它并不知道那到底是 &lt;code&gt;MIG-aaa-111&lt;/code&gt; 还是 &lt;code&gt;MIG-bbb-222&lt;/code&gt;。这种缺失在排障时会立刻变成问题：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pod inference-1 -o yaml &lt;span class="p"&gt;|&lt;/span&gt; grep gpu
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvidia.com/mig-1g.5gb: &lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi -L
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MIG 1g.5gb Device 0: &lt;span class="o"&gt;(&lt;/span&gt;UUID: MIG-aaa-111&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MIG 1g.5gb Device 1: &lt;span class="o"&gt;(&lt;/span&gt;UUID: MIG-bbb-222&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Kubernetes 不跟踪 UUID 映射&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;从 Kubernetes API 中，你只知道 Pod 拿到了一份 &lt;code&gt;mig-1g.5gb&lt;/code&gt;；但从实际硬件上看，这个节点上有两个相同规格、状态却可能截然不同的实例。假设此时用户抱怨推理延迟从 50ms 恶化到 200ms，你检查监控后看到：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 用户抱怨：“我的推理很慢！”&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 你检查他们的 Pod：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pod slow-inference -o yaml &lt;span class="p"&gt;|&lt;/span&gt; grep gpu
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvidia.com/mig-1g.5gb: &lt;span class="s2"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 你检查 GPU 指标：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MIG 1g.5gb Device 0: Utilization 15%, Temperature 45°C
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;MIG 1g.5gb Device 1: Utilization 99%, Temperature 85°C&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果 Pod 绑定在 Device 0，那么问题多半不在 GPU 饱和，而在应用代码、网络、批处理队列或数据加载链路；如果它绑定在 Device 1，那么高利用率与高温已经足以解释延迟膨胀。问题在于，Kubernetes 自身并没有把这条 Pod 到具体 MIG UUID 的映射公开出来。也就是说，Kubernetes 提供的抽象恰恰在你最需要理解硬件行为的时候失效了。&lt;/p&gt;
&lt;h2 id="请求与实际使用之间的巨大鸿沟"&gt;请求与实际使用之间的巨大鸿沟&lt;/h2&gt;
&lt;p&gt;第三种现实视图，是 Pod 请求值与实际运行行为之间的差距。这既是监控问题，也是组织行为问题。一个典型的机器学习 Pod 往往在 YAML 中写出这样的资源声明：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Pod 请求 8GB GPU 内存&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpumem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8192&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：resources.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;但进入容器内部查看实际使用时，得到的可能是另一番景象：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; training-job -- python -c &lt;span class="s2"&gt;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;import cupy as cp
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;print(f&amp;#39;Allocated: {cp.get_default_memory_pool().used_bytes() / 1e9:.2f} GB&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;print(f&amp;#39;Reserved: {cp.get_default_memory_pool().total_bytes() / 1e9:.2f} GB&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Allocated: 2.34 GB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Reserved: 4.00 GB&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;于是，同一个 Pod 会同时拥有三组数字：Kubernetes 看见它请求了 8GB，CUDA 内存池实际预留了 4GB，而业务数据真正占据的显存只有 2.34GB。三组数字都不是假的，却分别描述了三个完全不同的语义层面。开发者之所以写下 8GB，往往是因为半年前模型曾在 7.5GB 左右崩过一次，于是他们加上一层保险；KAI 或其他调度组件会忠实地把这 8GB 视为已被占据的容量，不再对其他人开放；CUDA 出于性能考虑，又会先抓 4GB 池子备用；而实际训练数据也许只用到 2GB 多一点。&lt;/p&gt;
&lt;p&gt;这类差异直接造成可观的浪费。对调度器而言，其他需要 3GB 的 Pod 无法放进来，因为“8GB 已经没了”；对物理 GPU 而言，那块 4GB 未被真正使用的空间却谁也不能碰。开发者以为自己是在保守行事，平台以为自己是在严格保障，最终的结果却是所有人一起把昂贵的显存锁死在低效配置里。&lt;/p&gt;
&lt;h2 id="gpu-囤积的级联效应"&gt;GPU 囤积的级联效应&lt;/h2&gt;
&lt;p&gt;这种浪费并不只体现在 GPU 本身，还会向整个节点级资源扩散。一个典型 GPU 节点可能有 64 个 CPU 核、256GB 内存和 4 块 Tesla T4。如果一个非常轻量的 GPU Pod 进入系统：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;8Gi&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;它表面上只需要少量 CPU 与系统内存，却会直接占掉节点 25% 的 GPU 配额。于是，资源不平衡开始向外扩散：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl describe node gpu-node-1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Allocatable:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cpu: &lt;span class="m"&gt;64&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; memory: 256Gi
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvidia.com/gpu: &lt;span class="m"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Allocated:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cpu: &lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="c1"&gt;# 3% 使用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; memory: 8Gi &lt;span class="c1"&gt;# 3% 使用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvidia.com/gpu: &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;# 25% 使用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Remaining:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cpu: &lt;span class="m"&gt;62&lt;/span&gt; &lt;span class="c1"&gt;# 97% 浪费&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; memory: 248Gi &lt;span class="c1"&gt;# 97% 浪费&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; nvidia.com/gpu: &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="c1"&gt;# 只能再放 3 个 GPU Pod&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;如果再来三个类似的轻量 Pod，节点四块 GPU 会全部“分配完毕”，而 CPU 和系统内存的绝大多数仍然闲置。很多组织又会把 GPU 节点和普通 CPU 节点分开管理，因此这些剩余 CPU 与 RAM 也不会自然被其他业务吃掉。结果就是，你的监控面板可能显示“GPU 100% 已分配”，团队开始申请更多 GPU 节点；但真实问题并不是物理 GPU 不够，而是整块 GPU 作为最小分配单位时把大量伴随资源一并锁死了。&lt;/p&gt;
&lt;p&gt;如果按月成本来算，问题会变得更刺眼。设一个 GPU 节点月成本约 2000 美元，而最终只有 8 个 CPU 核、32GB RAM 和 4 块 GPU 被真正用到，那么 CPU 和系统内存层面大约 87% 的资源价值都被无效捆绑到了 GPU 分配上。很多团队之所以感到 GPU 集群“极贵”，并不只是因为 GPU 本身贵，更因为这种以 GPU 为瓶颈的资源囤积把整台节点拖入了低效状态。&lt;/p&gt;
&lt;h2 id="dcgm弥合现实差距"&gt;DCGM：弥合现实差距&lt;/h2&gt;
&lt;p&gt;到这里可以看到，仅靠 &lt;code&gt;nvidia-smi&lt;/code&gt; 和 Kubernetes API 已经无法解释真实运行状况。要把硬件视图、编排视图和容器使用视图连接起来，你需要一个中间层。这就是 NVIDIA 的 &lt;a href="https://developer.nvidia.com/dcgm" target="_blank" rel="noopener"&gt;数据中心 GPU 管理器（DCGM）&lt;/a&gt; 的价值所在。它提供进程级、容器级乃至 Pod 级的 GPU 指标，使你终于可以把“谁在用、用了多少、在哪块设备上用”串起来。&lt;/p&gt;
&lt;p&gt;部署 &lt;a href="https://github.com/NVIDIA/dcgm-exporter" target="_blank" rel="noopener"&gt;DCGM exporter&lt;/a&gt; 的过程通常并不复杂：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ helm repo add nvidia https://nvidia.github.io/dcgm-exporter/helm-charts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ helm install dcgm-exporter nvidia/dcgm-exporter &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --namespace monitoring &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set serviceMonitor.enabled&lt;span class="o"&gt;=&lt;/span&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Exporter 启动后，就可以把它暴露出来的指标作为 Prometheus 目标抓取。先做一个最简单的检查：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl port-forward -n monitoring svc/dcgm-exporter 9400:9400 &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -s localhost:9400/metrics &lt;span class="p"&gt;|&lt;/span&gt; grep DCGM_FI_DEV_GPU_UTIL
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DCGM_FI_DEV_GPU_UTIL&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;gpu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;,UUID&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;GPU-abc-123-def&amp;#34;&lt;/span&gt;,pod&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;training-job-1&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 45.2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DCGM_FI_DEV_GPU_UTIL&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;gpu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;,UUID&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;GPU-abc-123-def&amp;#34;&lt;/span&gt;,pod&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;inference-svc&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 12.8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DCGM_FI_DEV_GPU_UTIL&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;gpu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;,UUID&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;GPU-abc-123-def&amp;#34;&lt;/span&gt;,pod&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;notebook-3&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; 0.0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;与 &lt;code&gt;nvidia-smi&lt;/code&gt; 那个单一的节点级数字相比，DCGM 的价值在于可归因性。你终于可以看到同一块 GPU 上，哪个 Pod 在消耗多少利用率。更重要的是，DCGM 暴露的并不只是一个简单的“GPU 忙碌百分比”，还包括显存使用、内存带宽、Tensor Core 活动等更具解释力的指标。尤其关键的是，要把 GPU 活跃度与 SM 活动区分开来：某个 Pod 也许在 &lt;code&gt;nvidia-smi&lt;/code&gt; 上表现为 90% GPU 利用率，但如果 DCGM 显示它的 SM 活动只有 20%，那就说明 GPU 虽然一直有核函数驻留，却并没有把计算单元高效用起来。&lt;/p&gt;
&lt;p&gt;对于 MIG，这种可归因能力甚至会成为唯一可靠的真相来源。DCGM 能提供按 UUID 归属的切片级指标：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -s localhost:9400/metrics &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="s2"&gt;&amp;#34;MIG-&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DCGM_FI_DEV_GPUUTIL&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;MIG-aaa-111&amp;#34;&lt;/span&gt;,pod&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;inference-1&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="m"&gt;67&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DCGM_FI_DEV_GPUUTIL&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;MIG-bbb-222&amp;#34;&lt;/span&gt;,pod&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;inference-2&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="m"&gt;89&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DCGM_FI_DEV_GPUUTIL&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;MIG-ccc-333&amp;#34;&lt;/span&gt;,pod&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;training&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="m"&gt;34&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DCGM_FI_DEV_FBUSED&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;MIG-aaa-111&amp;#34;&lt;/span&gt;,pod&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;inference-1&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="m"&gt;2147483648&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DCGM_FI_DEV_FBUSED&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;MIG-bbb-222&amp;#34;&lt;/span&gt;,pod&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;inference-2&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="m"&gt;3221225472&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DCGM_FI_DEV_FBUSED&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;MIG-ccc-333&amp;#34;&lt;/span&gt;,pod&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;training&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="m"&gt;8589934592&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这样一来，前面 Kubernetes 无法回答的问题终于有了答案：哪个 Pod 正在用哪个 MIG 实例，以及它在那个实例上到底用了多少算力和显存。有人抱怨性能时，你不再需要猜测它是不是落在了高温高利用率的切片上，而是可以直接从指标中看到 Pod 与具体 MIG UUID 的映射关系。&lt;/p&gt;
&lt;h2 id="分配与利用率之间的差距"&gt;分配与利用率之间的差距&lt;/h2&gt;
&lt;p&gt;一旦拥有了 DCGM，你就可以开始把“分配出去的资源”与“真正被消费的资源”做系统化对比。首先要找的，往往不是最忙的 Pod，而是最会囤积资源的 Pod。下面这个 PromQL 查询的目标，就是找出持有 GPU 配额却几乎没怎么使用的工作负载：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-promql" data-lang="promql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;pod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;DCGM_FI_DEV_GPUUTIL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;pod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;kube_pod_container_resource_limits&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;#34;&lt;/span&gt;&lt;span class="s"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;&amp;#34;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：Pod GPU 囤积查询&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;结果可能长这样：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{namespace=&amp;#34;team-ml&amp;#34;, pod=&amp;#34;notebook-23&amp;#34;} 0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{namespace=&amp;#34;team-ml&amp;#34;, pod=&amp;#34;notebook-24&amp;#34;} 3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{namespace=&amp;#34;research&amp;#34;, pod=&amp;#34;experiment-old&amp;#34;} 0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{namespace=&amp;#34;research&amp;#34;, pod=&amp;#34;debug-session&amp;#34;} 0&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这些 Pod 占着 GPU，却几乎没做任何工作。进一步地，你还可以把浪费直接折算成金钱：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-promql" data-lang="promql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;namespace&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;kube_pod_container_resource_limits&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;#34;&lt;/span&gt;&lt;span class="s"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;&amp;#34;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;clamp_min&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;DCGM_FI_DEV_GPUUTIL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.45&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;示例结果如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{namespace=&amp;#34;team-ml&amp;#34;} $67.20/小时
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{namespace=&amp;#34;research&amp;#34;} $43.50/小时
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{namespace=&amp;#34;inference&amp;#34;} $8.70/小时&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;当浪费被表达为每小时、每天或每周的金钱流失时，团队才更容易意识到“低利用率”不是抽象优化问题，而是实打实的预算泄漏。显存层面的浪费通常更严重。对比 Pod 请求值与 DCGM 中实际显存占用时，能看到更清晰的失配：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ join -t&lt;span class="s1"&gt;$&amp;#39;\t&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;kubectl get pods -A -o json &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; jq -r &lt;span class="s1"&gt;&amp;#39;.items[] |
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; select(.spec.containers[].resources.limits.&amp;#34;nvidia.com/gpu&amp;#34; != null) |
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;\(.metadata.namespace)/\(.metadata.name)\t\(.spec.containers[].resources.limits.&amp;#34;nvidia.com/gpu&amp;#34;)&amp;#34;&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; sort&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;&lt;span class="o"&gt;(&lt;/span&gt;curl -s localhost:9400/metrics &lt;span class="p"&gt;|&lt;/span&gt; grep DCGM_FI_DEV_FB_USED &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; awk &lt;span class="s1"&gt;&amp;#39;{print $1}&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; sed &lt;span class="s1"&gt;&amp;#39;s/.*pod=&amp;#34;//&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; sed &lt;span class="s1"&gt;&amp;#39;s/&amp;#34;.*//&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; awk &lt;span class="s1"&gt;&amp;#39;{getline val; print $0 &amp;#34;\t&amp;#34; int(val/1048576)}&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; sort&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;team-ml/training-1 &lt;span class="m"&gt;8192&lt;/span&gt; &lt;span class="m"&gt;2340&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;team-ml/training-2 &lt;span class="m"&gt;8192&lt;/span&gt; &lt;span class="m"&gt;1890&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;research/model-test &lt;span class="m"&gt;16384&lt;/span&gt; &lt;span class="m"&gt;4530&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;inference/service-1 &lt;span class="m"&gt;4096&lt;/span&gt; &lt;span class="m"&gt;1200&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这些数字的模式非常熟悉：许多训练与推理任务会请求实际所需显存的三到四倍。这并不一定意味着开发者不负责任，反而通常是理性的防御行为。他们曾经因为批量大小上升、输入长度变长或实验参数变化而在后期训练中突然 OOM，于是自然会倾向于“多要一些，以防万一”。可是在 GPU 内存不能像 CPU 一样轻松超额订阅的前提下，这种行为会直接把集群的可用容量锁死。于是你看到的是 90% 的显存已经分配，却只有 25% 到 30% 真正在被使用。&lt;/p&gt;
&lt;h2 id="真正重要的监控模式"&gt;真正重要的监控模式&lt;/h2&gt;
&lt;p&gt;只有在 DCGM 补齐了可归因指标之后，GPU 集群里的许多故障模式才首次变得可见。第一个应该监控的是持续增长的显存占用，也就是 GPU 内存泄漏。与普通 CPU 程序不同，GPU 侧的内存分配往往不会被自动垃圾回收；如果框架、库或用户代码没有显式释放，那些分配会持续存在并逐步累积，直到最后触发 OOM。识别这种风险的一个实用查询如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-promql" data-lang="promql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;rate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;DCGM_FI_DEV_FB_used&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;1h&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;DCGM_FI_DEV_FB_USED&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;DCGM_FI_DEV_FB_FREE&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这个表达式的思路很直接：过去一小时显存还在持续增长，同时当前显存占用已经超过可用容量的 80%，说明这个 Pod 已处于逼近 OOM 的危险区。将其转成告警规则时，可以这样写：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;monitoring.coreos.com/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;PrometheusRule&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gpu-memory-leak&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gpu&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;GPUMemoryLeak&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; rate(DCGM_FI_DEV_FB_USED[1h]) &amp;gt; 1048576
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; and
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; DCGM_FI_DEV_FB_USED / (DCGM_FI_DEV_FB_USED + DCGM_FI_DEV_FB_FREE) &amp;gt; 0.8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;在 {{ $labels.pod }} 上检测到 GPU 内存泄漏&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;内存使用量以 {{ $value | humanize }}B/小时增长&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：gpu-memory-leak.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;这种告警的价值，在于它把问题前移到真正崩溃之前。如果没有这类规则，团队往往只有在 Pod 已经 OOM、训练进度丢失之后，才第一次意识到内存一直在泄漏。&lt;/p&gt;
&lt;p&gt;第二类值得重点监控的模式是热降频。GPU 在温度上升到一定阈值后会主动降低频率，以防止硬件受损。对用户而言，现象通常表现为“昨天两小时跑完的训练今天要三小时”，而节点看上去一切正常。通过 &lt;code&gt;nvidia-smi -q -d TEMPERATURE&lt;/code&gt; 可以看到相关阈值：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi -q -d TEMPERATURE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GPU Current Temp : &lt;span class="m"&gt;83&lt;/span&gt; C
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GPU Shutdown Temp : &lt;span class="m"&gt;96&lt;/span&gt; C
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GPU Slowdown Temp : &lt;span class="m"&gt;93&lt;/span&gt; C
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GPU Max Operating Temp : &lt;span class="m"&gt;85&lt;/span&gt; C&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这些阈值随 GPU 型号而不同，但模式基本一致：接近 85°C 时开始降速，接近 93°C 时会更明显地降低频率，96°C 左右则进入保护性关闭。对这类问题，一个非常实用的查询是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-promql" data-lang="promql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;DCGM_FI_DEV_GPU_TEMP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;rate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;DCGM_FI_DEV_GPU_TEMP&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;5m&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这个查询识别的是“已经很热而且还在继续升温”的 GPU。它比单纯盯住一个温度阈值更有意义，因为温度变化率能帮助你更早捕获风扇故障、散热受阻或异常负载飙升。&lt;/p&gt;
&lt;p&gt;第三类模式与共享过载有关，也就是时间分片或多进程共享引发的上下文切换开销。当太多进程竞争同一块 GPU 时，GPU 在不同上下文之间保存和恢复状态的开销会变得可见：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 统计每个 GPU 的进程数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi --query-compute-apps&lt;span class="o"&gt;=&lt;/span&gt;pid --format&lt;span class="o"&gt;=&lt;/span&gt;csv,noheader &lt;span class="p"&gt;|&lt;/span&gt; wc -l
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;12&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 检查上下文切换开销&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ nvidia-smi dmon -s u -c &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# gpu sm mem enc dec&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;45&lt;/span&gt; &lt;span class="m"&gt;23&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;# 上下文切换&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;67&lt;/span&gt; &lt;span class="m"&gt;45&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;# 上下文切换&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;23&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这些全为零的行并不等于 GPU 真正空闲，而是在做上下文切换。每次切换也许只要几毫秒，但如果有十几个进程争抢同一块卡，这些毫秒最终会积累成明显的吞吐损失。对应的一个简单 PromQL 规则是：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-promql" data-lang="promql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;gpu_id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;DCGM_FI_DEV_GPUUTIL&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;当同一块 GPU 上活跃进程数长期超过某个阈值时，通常意味着你需要重新审视时间分片策略、调整共享粒度，或者把部分工作负载迁走。&lt;/p&gt;
&lt;h2 id="现实检验"&gt;现实检验&lt;/h2&gt;
&lt;p&gt;一旦建立起真正可归因的 GPU 可观测性，很多团队最终都会发现一些相似的事实：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;大约 30% 到 40% 的 GPU 分配长期处于完全闲置状态&lt;/li&gt;
&lt;li&gt;另外约 30% 的分配虽然“在用”，但利用率低于 20%&lt;/li&gt;
&lt;li&gt;显存请求值通常比真实使用高出 3 到 4 倍&lt;/li&gt;
&lt;li&gt;交互式 Notebook 会把 GPU 持有数天，而真正使用时间也许只有几分钟&lt;/li&gt;
&lt;li&gt;失败实验或废弃调试会话会继续运行，因为没人持续检查&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;监控本身并不会替你解决这些问题，但它会把原本可以长期隐藏在绿色仪表板背后的浪费和故障模式强行暴露出来。一旦问题可以被归因、量化并与成本挂钩，治理才真正开始变得可能。&lt;/p&gt;
&lt;h2 id="关键要点"&gt;关键要点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;GPU 集群监控不能依赖单一视图，因为 &lt;code&gt;nvidia-smi&lt;/code&gt;、Kubernetes 和实际运行中的容器使用情况描述的是三层不同现实。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;nvidia-smi&lt;/code&gt; 告诉你物理设备层面的状态，Kubernetes 告诉你资源被如何声明和分配，但只有 DCGM 才能把硬件使用与具体 Pod、容器和 MIG 实例真正关联起来。&lt;/li&gt;
&lt;li&gt;GPU 利用率并不等于效率；一个 GPU 可以在统计口径上非常忙碌，却几乎没有完成多少有效计算。&lt;/li&gt;
&lt;li&gt;显存请求值与真实使用值之间长期存在巨大落差，很多团队会以“防 OOM”为理由请求实际所需三到四倍的资源。&lt;/li&gt;
&lt;li&gt;真正有价值的监控体系，不是把仪表板做得更漂亮，而是要能揭示浪费、识别归因、提前预警，并把问题直接转化为故障风险与成本信号。&lt;/li&gt;
&lt;li&gt;当浪费可以被持续观测并用资金损失表达出来时，GPU 资源治理才会从可选优化变成平台层面的必需能力。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>使用 vCluster 构建多租户 GPU 平台</title><link>https://jimmysong.io/zh/book/gpu-multi-tenancy-platforms/vcluster-platforms/</link><pubDate>Sun, 24 May 2026 13:07:36 +0000</pubDate><author>Jimmy Song</author><guid>https://jimmysong.io/zh/book/gpu-multi-tenancy-platforms/vcluster-platforms/</guid><description>通过 vCluster 实战构建多租户 GPU 平台，涵盖架构设计、租户隔离、资源分配与企业级部署最佳实践。</description><content:encoded>
&lt;p&gt;到前几章为止，本书已经分别讨论了 GPU 共享的基础机制、编排方式、硬件隔离与监控问题。现在需要把这些能力放进一个更真实的平台场景中：如果你不是在为单个团队配置一套 GPU 环境，而是在为整个组织设计一套能够长期服务多个 AI 团队的平台，那么你到底应该如何组织集群、分配 GPU，并在隔离与效率之间取得平衡？这一章要回答的，就是这个问题。&lt;/p&gt;
&lt;p&gt;先从一个典型企业场景开始。假设组织拥有 5 台裸金属 GPU 节点和 5 台裸金属 CPU 节点，无论这些节点位于本地数据中心还是云环境，约束本质上都相同：硬件数量有限，但内部 AI/ML 团队对 Kubernetes 集群和 GPU 资源的需求却持续增长。这些团队可能在做基于 LLM 的智能体应用、训练模型、构建 RAG 流程或提供在线推理服务。现在，组织里有 10 个相互独立的团队，每个团队都希望获得自己的 Kubernetes 集群，以便拥有足够的自主性、隔离性与变更节奏。&lt;/p&gt;
&lt;p&gt;如果你使用 &lt;code&gt;kubeadm&lt;/code&gt; 之类的传统方式来创建集群，那么每个集群至少需要一个控制平面节点、一个 CPU 节点和一个 GPU 节点。只要做一个最粗略的算术，你就会立刻发现问题：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每个团队至少需要 3 个节点&lt;/li&gt;
&lt;li&gt;10 个团队总共需要 30 个节点&lt;/li&gt;
&lt;li&gt;但你手中只有 10 个节点&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;哪怕退一步说，每个团队只给 2 个节点，实际需求仍然远超现有容量。换句话说，问题不是“是否愿意给每个团队独立集群”，而是“在硬件总量固定的前提下，这根本做不到”。这迫使平台工程师回到一个更本质的问题：如何让多个团队共享同一个基础设施，而不牺牲他们所需要的隔离与自主性？&lt;/p&gt;
&lt;h2 id="替代方案"&gt;替代方案&lt;/h2&gt;
&lt;p&gt;在 Kubernetes 世界中，最先想到的答案通常是命名空间。命名空间是 Kubernetes 默认提供的多租户原语，它允许你在同一集群内按逻辑边界划分资源。很多 Kubernetes 原生对象，例如 Deployment、Service、ConfigMap 等，本来就是命名空间级资源。因此，一个自然的第一反应往往是：既然有 10 个团队，那就给每个团队创建一个命名空间。&lt;/p&gt;
&lt;p&gt;从操作上看，这种方案非常直接：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;提供一个共享 Kubernetes 集群&lt;/li&gt;
&lt;li&gt;向其中加入 CPU 和 GPU 节点&lt;/li&gt;
&lt;li&gt;创建 10 个命名空间，每个团队一个&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;为了进一步强化治理，你还可以继续叠加控制措施：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用 &lt;code&gt;NetworkPolicy&lt;/code&gt; 限制跨团队通信&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;ResourceQuota&lt;/code&gt; 防止资源囤积&lt;/li&gt;
&lt;li&gt;用 &lt;code&gt;LimitRange&lt;/code&gt; 约束默认资源声明&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在一个规模较小、工作负载较简单的环境里，这看起来几乎是一个合理解法。但只要场景进入真实 AI 多租户平台，命名空间很快就会暴露出结构性不足。&lt;/p&gt;
&lt;p&gt;首先，命名空间只能提供相对弱的租户隔离。它能把资源“分组”，却不能把控制平面真正切开。一个团队即使只是在自己的命名空间中创建了海量 Secret、ConfigMap，或者过度消耗了共享 GPU，也仍然可能对其他团队造成影响，因为所有人共享的是同一个 API 服务器、同一套控制器以及同一个宿主控制平面。这意味着命名空间多租户天然存在“吵闹邻居”问题，而且问题发生的位置并不只在数据平面，也会出现在控制平面。&lt;/p&gt;
&lt;p&gt;其次，命名空间无法提供真正的租户自主性。团队很可能需要安装自己的 CRD、部署自定义 Operator、试验新的 GPU Operator 版本，或者使用与宿主集群不同的控制器与策略组合。但这些对象大量属于集群级别，而不是命名空间级别。结果就是，一个共享集群通常只能有一套 CRD 定义、一套 Operator 版本、一套准入控制逻辑。每个团队都不得不被约束在同一版本与同一平台假设之下，而这恰恰与 AI/ML 团队常见的实验需求相冲突。&lt;/p&gt;
&lt;p&gt;第三，命名空间很难满足更严格的合规和监管要求。对于有审计、数据驻留、安全隔离要求的组织来说，弱隔离通常不足以过审。命名空间无法天然提供独立的审计日志、租户级 API 服务器策略、深度可分离的控制面访问模型。即使你能通过大量附加组件拼装出类似效果，其运维复杂度也会急剧上升，最终平台团队反而承担了更多脆弱的治理逻辑。&lt;/p&gt;
&lt;p&gt;最后，命名空间并不会减少平台团队的实际负担。相反，很多集群级操作仍然只能由平台团队完成：安装 CRD、升级 Operator、调整控制器、维护集群范围策略。平台团队不仅没有从“每个团队都来提需求”的状态中解脱出来，反而成了所有变更请求的瓶颈。于是，命名空间虽然提供了最基础的逻辑隔离，却无法成为面向企业级 AI 多租户平台的最终答案。&lt;/p&gt;
&lt;p&gt;这也是为什么仅靠命名空间通常不够，尤其是在以下条件同时成立时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GPU 隔离和资源公平性至关重要&lt;/li&gt;
&lt;li&gt;团队需要租户特定的 CRD 或 Operator&lt;/li&gt;
&lt;li&gt;自主性和运营独立性是目标之一&lt;/li&gt;
&lt;li&gt;合规和审计要求不可妥协&lt;/li&gt;
&lt;li&gt;平台团队不能长期充当所有集群变更的人工网关&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果命名空间不够，那么是否应该退回到另一端，即给每个团队专门配置一个独立集群？传统行业中的确常常这样做。为每个团队提供单租户集群，能把控制平面和数据平面完全隔离开来，也最容易从安全和治理角度获得清晰边界。但问题同样明显：一旦团队数量增长，平台团队就不得不维护大量独立集群，集群生命周期管理、版本升级、策略分发、监控、备份与成本控制都会迅速变成沉重负担。资源碎片化也会随之出现，尤其是 GPU 这类昂贵资源，很容易在多个小集群之间形成“每个都不够满，但谁也借不到”的局面。&lt;/p&gt;
&lt;p&gt;平台团队于是陷入一个典型的两难：专用集群带来强隔离，却以碎片化和高运维成本为代价；命名空间共享集群带来较高利用率，却牺牲了团队自主性与隔离质量。vCluster 的意义，就在于为这两个极端之间提供一种新的中间解。&lt;/p&gt;
&lt;p&gt;vCluster 提供的是轻量级、隔离的控制平面，而不要求你为每个团队重复创建完整宿主集群。一个虚拟集群本质上是运行在宿主集群某个命名空间内的、完整但轻量的 Kubernetes 控制平面。从租户视角看，它就是一个独立 Kubernetes 集群：有自己的 API 服务器、控制器管理器、数据存储和资源状态；从平台视角看，它只是宿主集群上的一个轻量工作负载实例。这样一来，团队获得了近似独立集群的体验，而平台团队不必为每个团队都付出一整套物理或云端集群的代价。&lt;/p&gt;
&lt;h2 id="理解-vcluster-架构入门指南"&gt;理解 vCluster 架构：入门指南&lt;/h2&gt;
&lt;p&gt;可以把 vCluster 理解为“在一个真实 Kubernetes 集群内部，切出多个彼此隔离的虚拟 Kubernetes 集群”。每个团队可以获得自己的虚拟集群和独立的 &lt;code&gt;kubeconfig&lt;/code&gt;，因此他们与自己的控制平面交互，而不是直接接触宿主集群或其他团队的环境。这种模型最重要的价值，在于它把“共享底层硬件”和“隔离控制平面体验”同时保留下来。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-1.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-1.webp" alt="图 23: 图表展示 vCluster 如何通过一个包含三个虚拟集群的宿主集群实现灵活的多租户，每个集群都有隔离的团队访问各自的 kubeconfig，展示了跨团队和企业的灵活多租户。" data-caption="图 23: 图表展示 vCluster 如何通过一个包含三个虚拟集群的宿主集群实现灵活的多租户，每个集群都有隔离的团队访问各自的 kubeconfig，展示了跨团队和企业的灵活多租户。"
width="762"
height="1234"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 23: 图表展示 vCluster 如何通过一个包含三个虚拟集群的宿主集群实现灵活的多租户，每个集群都有隔离的团队访问各自的 kubeconfig，展示了跨团队和企业的灵活多租户。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;从实现上看，一个 vCluster 通常由两个核心部分构成，并以 StatefulSet 或 Deployment 的形式部署在宿主集群某个命名空间内。第一部分是虚拟控制平面。这是整个架构的中心：它包含自己的 Kubernetes API 服务器、控制器管理器和状态存储，因此租户的所有 API 请求、资源对象和 CRD 状态都首先在这里被处理。默认情况下，vCluster 使用嵌入式 SQLite 以降低资源占用；但如果需要更高可用性或更强的生产稳定性，也可以外接 etcd 或 PostgreSQL 等数据存储。&lt;/p&gt;
&lt;p&gt;虚拟控制平面的价值，不只是“把控制器进程多跑一份”这么简单，而是它真正建立了一条租户边界。租户在其虚拟集群里安装 CRD、调整 RBAC、部署自定义 Operator，这些操作首先影响的是自己的控制平面和自己的状态数据库，而不会直接污染宿主集群或其他租户的控制面空间。这正是命名空间做不到的事情。&lt;/p&gt;
&lt;p&gt;第二部分是 Syncer。虚拟集群本身并不拥有独立的工作节点，也不会真的创建一套新的物理网络或 kubelet 体系。Syncer 的作用，就是在虚拟控制平面和宿主集群之间建立一座桥梁：当租户在自己的 vCluster 中创建 Pod、PVC 等资源时，Syncer 会把这些对象翻译并同步到宿主集群对应的命名空间中，使真正的调度与执行发生在宿主集群的数据平面上。&lt;/p&gt;
&lt;p&gt;这种同步不是简单复制，而是一种带转换的映射。举例来说，租户在 vCluster 中的 &lt;code&gt;ai-team&lt;/code&gt; 命名空间里创建了一个名为 &lt;code&gt;ollama&lt;/code&gt; 的 Pod，Syncer 在宿主集群中可能会把它转换成一个带唯一前缀或后缀的名称，如 &lt;code&gt;ollama-xai-team-x-my-vcluster&lt;/code&gt;。这样做的目的，是在多个租户的相同命名空间名称、相同资源名称之间建立去冲突的隔离映射。&lt;/p&gt;
&lt;p&gt;Syncer 更有价值的地方，在于它还承担着平台策略下沉的作用。在传统共享集群中，平台团队常常通过全局准入控制器、Webhook 或集群级策略组件去统一控制所有租户行为，但这种方式复杂、脆弱，而且对整个宿主集群都带来潜在风险。vCluster 则提供了更柔性的做法：平台团队可以在 vCluster 模板或同步配置中定义补丁规则，让 Syncer 在资源从虚拟集群同步到宿主集群时自动施加平台策略。例如，你可以让所有请求 GPU 的 Pod 在同步到宿主时自动附带特定的 &lt;code&gt;runtimeClassName&lt;/code&gt;，或注入统一的节点选择、资源标签和安全配置。对租户而言，这些规则通常是透明的；对平台而言，它们则构成了一种更细粒度、更安全的治理机制。&lt;/p&gt;
&lt;p&gt;下表概括了 vCluster 架构中的核心组件：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;组件&lt;/th&gt;
&lt;th&gt;功能&lt;/th&gt;
&lt;th&gt;关键好处&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;虚拟控制平面&lt;/td&gt;
&lt;td&gt;处理租户 API 请求并为每个 vCluster 存储独立状态。&lt;/td&gt;
&lt;td&gt;提供 API 级与 CRD 级隔离，避免租户直接共享宿主控制平面。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Syncer&lt;/td&gt;
&lt;td&gt;将 Pod 等资源从虚拟集群转换并同步到宿主集群。&lt;/td&gt;
&lt;td&gt;让虚拟集群无需独立数据平面也能运行真实工作负载，并可在同步时施加策略。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;数据存储&lt;/td&gt;
&lt;td&gt;持久化虚拟集群的 Kubernetes 资源状态。&lt;/td&gt;
&lt;td&gt;可以轻量运行，也可以外部化以支持更强的高可用和生产扩展能力。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;调度器（可选）&lt;/td&gt;
&lt;td&gt;在虚拟集群侧为工作负载提供调度逻辑。&lt;/td&gt;
&lt;td&gt;默认可复用宿主调度器节约资源，也可按需引入自定义调度策略。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;换个角度来看，vCluster 做的不是让每个团队都拥有真正独占的硬件，而是让每个团队都拥有“看起来像独占集群”的控制平面体验，同时把真实的资源执行统一沉到一个共享宿主集群上。这样一来，多租户平台既能提升 GPU 利用率，又能把租户自主性提升到传统命名空间模型难以达到的程度。&lt;/p&gt;
&lt;h2 id="从专用集群走向共享平台"&gt;从专用集群走向共享平台&lt;/h2&gt;
&lt;p&gt;为了更直观地理解这一点，不妨把前面讨论过的架构演化路径放到一个连续场景里。先看基础设施本身：&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-2.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-2.webp" alt="图 24: 基础设施图显示一个 Kubernetes 集群，包含 10 台裸金属节点——9 台 CPU 节点和 1 台 GPU 节点，展示了可用的硬件资源。" data-caption="图 24: 基础设施图显示一个 Kubernetes 集群，包含 10 台裸金属节点——9 台 CPU 节点和 1 台 GPU 节点，展示了可用的硬件资源。"
width="1254"
height="1262"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 24: 基础设施图显示一个 Kubernetes 集群，包含 10 台裸金属节点——9 台 CPU 节点和 1 台 GPU 节点，展示了可用的硬件资源。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;你手里只有这一批裸金属或云节点。最直觉的方案，是先为不同团队各自创建独立 Kubernetes 集群。即使在云环境里，这通常也意味着为每个团队配置单独集群，再为其附加不同节点池。看上去这最能满足团队的独立性：&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-3.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-3.webp" alt="图 25: 架构图显示 Team 1 和 Team 2 的两个独立 Kubernetes 集群，每个都有 CPU 和 GPU 节点，其中 Team 1 的集群包含 NVIDIA Operator。" data-caption="图 25: 架构图显示 Team 1 和 Team 2 的两个独立 Kubernetes 集群，每个都有 CPU 和 GPU 节点，其中 Team 1 的集群包含 NVIDIA Operator。"
width="1228"
height="1228"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 25: 架构图显示 Team 1 和 Team 2 的两个独立 Kubernetes 集群，每个都有 CPU 和 GPU 节点，其中 Team 1 的集群包含 NVIDIA Operator。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;但很快你会发现，节点数量开始告急。仅为了建立一个具备基本可用性的 Kubernetes 集群，你至少就要消耗一个控制平面节点、一个 CPU 节点和一个 GPU 节点。对资源总量有限的组织来说，这种模式不可能扩展到很多团队。于是问题从“如何建集群”转向“如何分割既有基础设施”。&lt;/p&gt;
&lt;p&gt;此时，另一个障碍也会逐渐显现：即使你成功给团队提供了集群，默认 Kubernetes 调度器也并不是为 AI/ML 工作负载设计的。AI 作业不只是“要一个 GPU”这么简单，它往往还隐含着更多要求，例如特定 GPU 型号、MIG 切片、时间分片、特定驱动与运行时、足够的显存、快速本地存储、高带宽网络，以及 Gang 调度、公平队列、检查点恢复等高级调度能力。默认调度器把 &lt;code&gt;1 GPU&lt;/code&gt; 仅仅视作一个资源整数，而不会天然理解显存边界、拓扑位置、GPU 碎片整理、公平共享或训练作业的整体协同需求。&lt;/p&gt;
&lt;p&gt;这就导致一系列熟悉的问题：GPU 被碎片化地占用，分布式训练作业只启动了一半就卡住，不同团队之间缺乏公平共享，数据局部性和 NUMA/PCIe 拓扑被忽略，很多 Pod 虽然声明正确却始终停留在 Pending。最终的用户体验是：队列很长，GPU 利用率却不高，工程师不断追问“为什么我的作业还没有被调度起来”。&lt;/p&gt;
&lt;p&gt;这正是像 &lt;a href="https://github.com/kai-scheduler/KAI-Scheduler" target="_blank" rel="noopener"&gt;Kai Scheduler&lt;/a&gt;、&lt;a href="https://github.com/Project-HAMi/HAMi" target="_blank" rel="noopener"&gt;HAMi&lt;/a&gt; 和 &lt;a href="https://kueue.sigs.k8s.io" target="_blank" rel="noopener"&gt;Kueue&lt;/a&gt; 这样的项目出现的背景。它们试图弥补默认调度器在 AI 工作负载上的认知不足。在本章里，我们重点使用 Kai Scheduler 作为示例，因为它更适合展示 GPU 队列、公平共享与工作负载打包的能力。&lt;/p&gt;
&lt;p&gt;Kai Scheduler 可以被理解为默认 Kubernetes 调度器之上的专用增强层，它为 AI/ML 集群提供更丰富的调度语义，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;基于 DRF 的分层队列与公平共享机制，用于在 &lt;code&gt;org/team/user&lt;/code&gt; 等层级之间分配资源&lt;/li&gt;
&lt;li&gt;面向分布式训练的 Gang 调度，确保一组 Pod 要么一起启动，要么都不启动&lt;/li&gt;
&lt;li&gt;GPU 分数共享与时间分片，减少小任务对整卡的浪费&lt;/li&gt;
&lt;li&gt;抢占与整合能力，用于为更高优先级工作负载腾挪资源&lt;/li&gt;
&lt;li&gt;对 DRA、ResourceClaim 和拓扑约束更友好的支持&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果你在每个团队独立集群中都部署 Kai Scheduler，当然可以改善各自集群内部的 GPU 使用效率：&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-4.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-4.webp" alt="图 26: 两个团队的裸金属 Kubernetes 设置，显示 Team 1 和 Team 2 的集群，每个都有控制平面、Kai Scheduler、Pod 和专用 CPU/GPU 节点，Team 2 还有 NVIDIA GPU Operator。" data-caption="图 26: 两个团队的裸金属 Kubernetes 设置，显示 Team 1 和 Team 2 的集群，每个都有控制平面、Kai Scheduler、Pod 和专用 CPU/GPU 节点，Team 2 还有 NVIDIA GPU Operator。"
width="1662"
height="1132"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 26: 两个团队的裸金属 Kubernetes 设置，显示 Team 1 和 Team 2 的集群，每个都有控制平面、Kai Scheduler、Pod 和专用 CPU/GPU 节点，Team 2 还有 NVIDIA GPU Operator。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;但这仍然没有解决根本矛盾：GPU 资源仍被锁在不同独立集群中，利用率依旧受碎片化影响。一个团队可能过度占用 GPU，而另一个团队的 GPU 长期空闲：&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-5.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-5.webp" alt="图 27: 多租户架构显示租户 A 和租户 B 各自有独立的命名空间、Kai Scheduler 和共享 GPU 节点，以红色（已使用）和绿色（可用）部分显示利用率。" data-caption="图 27: 多租户架构显示租户 A 和租户 B 各自有独立的命名空间、Kai Scheduler 和共享 GPU 节点，以红色（已使用）和绿色（可用）部分显示利用率。"
width="1678"
height="1154"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 27: 多租户架构显示租户 A 和租户 B 各自有独立的命名空间、Kai Scheduler 和共享 GPU 节点，以红色（已使用）和绿色（可用）部分显示利用率。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-6.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-6.webp" alt="图 28: GPU 利用率图显示租户 A 的 GPU 节点利用率&amp;#34;差&amp;#34;（部分红/绿使用），租户 B 在两个节点上更好地分配了 GPU 使用。" data-caption="图 28: GPU 利用率图显示租户 A 的 GPU 节点利用率&amp;#34;差&amp;#34;（部分红/绿使用），租户 B 在两个节点上更好地分配了 GPU 使用。"
width="1578"
height="874"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 28: GPU 利用率图显示租户 A 的 GPU 节点利用率&amp;quot;差&amp;quot;（部分红/绿使用），租户 B 在两个节点上更好地分配了 GPU 使用。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-7.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-7.webp" alt="图 29: 类似的多租户设置显示其专用 GPU 节点上的低效 GPU 利用率模式。" data-caption="图 29: 类似的多租户设置显示其专用 GPU 节点上的低效 GPU 利用率模式。"
width="1592"
height="826"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 29: 类似的多租户设置显示其专用 GPU 节点上的低效 GPU 利用率模式。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;于是你又回到了那个问题：要不要退回共享集群加命名空间的方式？&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-8.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-8.webp" alt="图 30: 基于命名空间的多租户架构，显示租户 A 和租户 B 分别使用 tenant-ns-1 和 tenant-ns-b 命名空间，由 Kai Scheduler 管理 GPU 节点分配。" data-caption="图 30: 基于命名空间的多租户架构，显示租户 A 和租户 B 分别使用 tenant-ns-1 和 tenant-ns-b 命名空间，由 Kai Scheduler 管理 GPU 节点分配。"
width="1520"
height="1260"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 30: 基于命名空间的多租户架构，显示租户 A 和租户 B 分别使用 tenant-ns-1 和 tenant-ns-b 命名空间，由 Kai Scheduler 管理 GPU 节点分配。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;如果这样做，命名空间的那些老问题会重新出现：控制平面仍共享，租户自主性依然有限，集群级扩展和治理仍掌握在平台团队手里。于是，真正更合理的模式就浮现出来了：在一个共享的大型宿主集群上，利用 vCluster 为每个团队提供隔离的虚拟控制平面，同时让 GPU 节点池在底层被统一共享与调度。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-9.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-9.webp" alt="图 31: vCluster 架构显示租户 A 和租户 B 各自拥有自己的 vCluster（a 和 b），由带有 NVIDIA 集成的 Kai Scheduler 管理，在四个 GPU 节点之间分配工作负载。" data-caption="图 31: vCluster 架构显示租户 A 和租户 B 各自拥有自己的 vCluster（a 和 b），由带有 NVIDIA 集成的 Kai Scheduler 管理，在四个 GPU 节点之间分配工作负载。"
width="1638"
height="1168"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 31: vCluster 架构显示租户 A 和租户 B 各自拥有自己的 vCluster（a 和 b），由带有 NVIDIA 集成的 Kai Scheduler 管理，在四个 GPU 节点之间分配工作负载。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;在这种模式下，平台团队只需要维护一个包含 CPU 和 GPU 节点的大型宿主 Kubernetes 集群，再为每个团队创建对应的虚拟集群。团队得到的是自己的 API 入口、自己的控制平面体验和自己的租户边界；平台得到的是统一的硬件池和统一的宿主治理面。新租户到来时，也不再需要等待新的物理集群或新的云环境交付，而只需要在现有宿主集群中创建另一个 vCluster 即可。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-10.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-10.webp" alt="图 32: 数据中心架构显示多个虚拟集群、一个带有共享节点池的大型 Kubernetes 集群，以及节点利用率为 70% 的物理服务器。" data-caption="图 32: 数据中心架构显示多个虚拟集群、一个带有共享节点池的大型 Kubernetes 集群，以及节点利用率为 70% 的物理服务器。"
width="1520"
height="990"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 32: 数据中心架构显示多个虚拟集群、一个带有共享节点池的大型 Kubernetes 集群，以及节点利用率为 70% 的物理服务器。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;这样，数据中心或云平台最终呈现的形态就是：底层是一个大型共享宿主集群，上层是多个面向团队的虚拟 Kubernetes 集群。平台不再被“集群数量”本身拖垮，而租户也不必退回到命名空间级的弱隔离模型。&lt;/p&gt;
&lt;h2 id="演示使用-vcluster-和-kai-scheduler-实现-gpu-分数共享"&gt;演示：使用 vCluster 和 Kai Scheduler 实现 GPU 分数共享&lt;/h2&gt;
&lt;p&gt;下面通过一个更具体的场景来说明这一模式如何落地。假设你在一个共享 GPU 环境中，需要支持不同团队使用 Ollama 部署基于 RAG 的应用。每个团队的数据集不同、模型选择不同、扩展需求不同，但它们都希望获得一定程度的自主环境和受控 GPU 使用。目标是同时实现三件事：租户隔离、公平调度和 GPU 分数共享。&lt;/p&gt;
&lt;figure class="mx-auto text-center"&gt;
&lt;img src="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-11.webp" data-img="https://assets.jimmysong.io/images/book/gpu-multi-tenancy-platforms/vcluster-platforms/f6-11.webp" alt="图 33: Kai Scheduler 与 vCluster 在单个 GPU 节点上的架构图，显示 GPU 分数分配，有两个 vCluster。" data-caption="图 33: Kai Scheduler 与 vCluster 在单个 GPU 节点上的架构图，显示 GPU 分数分配，有两个 vCluster。"
width="1570"
height="1082"
loading="lazy" decoding="async" class="image-loading"
onload="this.classList.remove('image-loading'); this.classList.add('image-loaded');"
onerror="handleImageError(this); this.classList.remove('image-loading');"&gt;
&lt;figcaption&gt;图 33: Kai Scheduler 与 vCluster 在单个 GPU 节点上的架构图，显示 GPU 分数分配，有两个 vCluster。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="前提条件"&gt;前提条件&lt;/h3&gt;
&lt;p&gt;要复现实验环境，需要满足以下前提：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;带有 NVIDIA GPU 的 Kubernetes 集群，可以是裸金属，也可以是带有 GPU 节点池的 GKE、AKS 或 EKS&lt;/li&gt;
&lt;li&gt;已安装 &lt;code&gt;kubectl&lt;/code&gt;（建议 v1.28+）并配置好上下文&lt;/li&gt;
&lt;li&gt;已安装 &lt;code&gt;helm&lt;/code&gt;（建议 v3.10+）&lt;/li&gt;
&lt;li&gt;已安装 &lt;code&gt;vcluster&lt;/code&gt; CLI&lt;/li&gt;
&lt;li&gt;至少有一个 GPU 节点，例如 &lt;code&gt;g4dn.xlarge&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在本示例中，我们使用一个包含单个 GPU 节点的 EKS 集群。为了简化说明，先用 &lt;code&gt;eksctl&lt;/code&gt; 创建一个带 GPU 节点池的集群：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;eksctl.io/v1alpha5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ClusterConfig&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;vcluster-gpu&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;us-east-2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1.32&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;sandbox&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;vcluster&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cidr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10.1.0.0&lt;/span&gt;&lt;span class="l"&gt;/16&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;autoAllocateIPv6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;resource-name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;clusterEndpoints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;publicAccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;privateAccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;managedNodeGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;vcluster-gpu&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;amiFamily&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Ubuntu2404&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;desiredCapacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;instanceTypes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;g4dn.xlarge&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ssh&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;allow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;publicKeyName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;vcluster&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：config.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;使用以下命令创建 EKS 集群：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;eksctl create cluster --config-file eks-config-gpu.yaml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;集群创建完成后，拉取 kubeconfig 并更新本地上下文：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws eks update-kubeconfig --region us-east-2 --name vcluster-gpu --kubeconfig ./vcluster-kubeconfig&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;确认集群已就绪：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get nodes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME STATUS ROLES AGE VERSION
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ip-192-168-12-34.ec2.internal Ready &amp;lt;none&amp;gt; 19h v1.32.x&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="nvidia-gpu-operator-设置"&gt;NVIDIA GPU Operator 设置&lt;/h3&gt;
&lt;p&gt;有了 GPU 节点之后，下一步是安装 GPU Operator，并把 GPU 配置为时间分片模式，以支持分数共享。先安装 Operator：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ helm repo update
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ helm install gpu-operator nvidia/gpu-operator -n gpu-operator --create-namespace
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME: gpu-operator
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;LAST DEPLOYED: Tue Sep &lt;span class="m"&gt;2&lt;/span&gt; 12:07:02 &lt;span class="m"&gt;2025&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAMESPACE: gpu-operator
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;STATUS: deployed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;REVISION: &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;TEST SUITE: None&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;安装完成后，确认相关组件都已正常启动：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pods -n gpu-operator
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME READY STATUS RESTARTS AGE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpu-feature-discovery-svqzb 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 5m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpu-operator-5798b5b564-rw57d 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 5m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpu-operator-node-feature-discovery-gc-86f6495b55-dsvfk 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 5m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpu-operator-node-feature-discovery-master-694467d5db-cc2zj 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 5m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpu-operator-node-feature-discovery-worker-p8k7n 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 5m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvidia-container-toolkit-daemonset-s4vg4 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 5m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvidia-cuda-validator-sqfp2 0/1 Completed &lt;span class="m"&gt;0&lt;/span&gt; 2m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvidia-dcgm-exporter-2bx94 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 5m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvidia-device-plugin-daemonset-tvvpr 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 5m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvidia-driver-daemonset-6wjnt 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 5m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvidia-operator-validator-s4wxv 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 5m&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;随后，在 &lt;code&gt;gpu-operator&lt;/code&gt; 命名空间中创建时间分片配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF | kubectl apply -f -
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: ConfigMap
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: time-slicing-config
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; namespace: gpu-operator
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;data:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; time-slicing-config.yaml: |
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; version: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; flags:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; migStrategy: none
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; sharing:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; timeSlicing:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; resources:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - name: nvidia.com/gpu
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; replicas: 2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后重启设备插件，使配置生效：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl rollout restart daemonset nvidia-device-plugin-daemonset -n gpu-operator&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最后确认单个物理 GPU 已暴露为两个逻辑可分配单元：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get nodes -o json &lt;span class="p"&gt;|&lt;/span&gt; jq &lt;span class="s1"&gt;&amp;#39;.items[].status.capacity.&amp;#34;nvidia.com/gpu&amp;#34;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;2&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这意味着底层只有一张物理 GPU，但在资源视图上，它现在可以被分成两个可共享的逻辑设备。&lt;/p&gt;
&lt;h3 id="安装-kai-scheduler"&gt;安装 KAI-Scheduler&lt;/h3&gt;
&lt;p&gt;接下来，在宿主 Kubernetes 集群上安装 Kai Scheduler，并打开全局 GPU 共享能力：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ helm upgrade -i kai-scheduler &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; oci://ghcr.io/nvidia/kai-scheduler/kai-scheduler &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -n kai-scheduler --create-namespace &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --version v0.8.4 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set &lt;span class="nv"&gt;gpuSharing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;确认组件已正常运行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pods -n kai-scheduler
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME READY STATUS RESTARTS AGE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;binder-5ddb78b47-75hbq 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 2m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kai-admission-649bf8cbd7-gjs4t 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 2m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;podgroup-controller-8ff79c87f-8p7wr 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 2m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;podgrouper-85cfbb79b5-dgjx4 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 2m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;queuecontroller-658499d99c-v6wjt 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 2m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;scheduler-56f6f568b9-6gpgl 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 2m&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Kai Scheduler 生效后，还需要创建一个队列，让后续来自 vCluster 的工作负载能够通过 &lt;code&gt;kai.scheduler/queue&lt;/code&gt; 标签进入该队列：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f - &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;apiVersion: scheduling.run.ai/v2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: Queue
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: test
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; parentQueue: default
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; resources:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; cpu:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; quota: -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; limit: -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; overQuotaWeight: 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; gpu:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; quota: -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; limit: -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; overQuotaWeight: 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; memory:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; quota: -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; limit: -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; overQuotaWeight: 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;创建完成后，可以验证队列层级：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get queue
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME PRIORITY PARENT CHILDREN DISPLAYNAME
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;test&lt;/span&gt; default&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="创建带有-kai-集成的-vcluster"&gt;创建带有 Kai 集成的 vCluster&lt;/h3&gt;
&lt;p&gt;宿主侧调度能力准备就绪后，就可以为租户创建虚拟集群。首先定义一个最基本的同步配置，关闭 owner reference，并启用 &lt;code&gt;runtimeClass&lt;/code&gt; 同步：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat &amp;gt; values.yaml &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;experimental:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; syncSettings:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; setOwner: false
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;sync:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; fromHost:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; runtimeClasses:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; enabled: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后安装 &lt;code&gt;vcluster&lt;/code&gt; CLI，并在 &lt;code&gt;team-legal&lt;/code&gt; 命名空间中创建一个名为 &lt;code&gt;rag-legal&lt;/code&gt; 的虚拟集群：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ curl -L -o vcluster &lt;span class="s2"&gt;&amp;#34;https://github.com/loft-sh/vcluster/releases/latest/download/vcluster-linux-amd64&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sudo install -c -m &lt;span class="m"&gt;0755&lt;/span&gt; vcluster /usr/local/bin &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; rm -f vcluster
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ vcluster create rag-legal --namespace team-legal -f values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;11:32:16 &lt;span class="k"&gt;done&lt;/span&gt; Successfully created virtual cluster rag-legal in namespace team-legal
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;11:32:15 info Waiting &lt;span class="k"&gt;for&lt;/span&gt; vcluster to come up...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;11:34:45 &lt;span class="k"&gt;done&lt;/span&gt; Switched active kube context to vcluster_rag-legal-team-legal_minikube&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;确认 vCluster 已经正常运行：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ vcluster list
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME NAMESPACE STATUS VERSION CONNECTED AGE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rag-legal team-legal Running 0.27.0 True 3m25s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;这一步的含义在于：团队现在拥有了自己的 Kubernetes 入口和控制平面体验，但并没有新增任何真实 GPU 节点。租户的自主环境与宿主物理资源已经被解耦开来。&lt;/p&gt;
&lt;h3 id="部署应用"&gt;部署应用&lt;/h3&gt;
&lt;p&gt;现在可以在虚拟集群中部署一个简单的 GPU 工作负载。这个 Pod 会通过 Kai Scheduler 提交到宿主队列中，并通过注解申请半块 GPU 的分数共享。把下面的内容保存为 &lt;code&gt;podkai2.yaml&lt;/code&gt;：&lt;/p&gt;
&lt;figure class="code-block-figure"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gpu-sharing-pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kai.scheduler/queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;test&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu-fraction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0.5&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;schedulerName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kai-scheduler&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runtimeClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nvidia&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nodeSelector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu-present&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ollama-rag&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ollama/ollama:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ollama&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;serve&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figcaption class="code-block-caption"&gt;代码片段：podkai2.yaml&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;将该文件应用到 vCluster：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vcluster connect rag-legal --namespace team-legal -- kubectl apply -f podkai2.yaml&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Pod 启动后，向其拉取一个模型：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; -it -n team-legal gpu-sharing-pod-x-default-x-rag-legal -- ollama pull mistral
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulling f5074b1221da: 100% ████████████████████ 4.4 GB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulling 43070e2d4e53: 100% ████████████████████ &lt;span class="m"&gt;11&lt;/span&gt; KB
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulling 1ff5b64b61b9: 100% ████████████████████ &lt;span class="m"&gt;799&lt;/span&gt; B
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulling ed11eda7790d: 100% ████████████████████ &lt;span class="m"&gt;30&lt;/span&gt; B
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pulling 1064e17101bd: 100% ████████████████████ &lt;span class="m"&gt;487&lt;/span&gt; B
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;verifying sha256 digest
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;writing manifest
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;success&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;然后把端口转发到本地：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl port-forward pod/gpu-sharing-pod-x-default-x-rag-legal -n team-legal 11434:11434
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Forwarding from 127.0.0.1:11434 -&amp;gt; &lt;span class="m"&gt;11434&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;最后发送一次测试请求：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -s http://localhost:11434/api/chat &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s2"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -d &lt;span class="s1"&gt;&amp;#39;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;model&amp;#34;: &amp;#34;mistral&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;stream&amp;#34;: false,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;messages&amp;#34;: [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; {&amp;#34;role&amp;#34;: &amp;#34;user&amp;#34;, &amp;#34;content&amp;#34;: &amp;#34;Summarize Kubernetes in one line&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; jq -r &lt;span class="s1"&gt;&amp;#39;.message.content&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Kubernetes is an open &lt;span class="nb"&gt;source&lt;/span&gt; platform &lt;span class="k"&gt;for&lt;/span&gt; automating deployment, scaling, and management of containerized applications.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;对其他团队重复同样的流程时，你并不需要再去准备新的独立宿主集群；只要继续创建新的 vCluster 并把工作负载接入 Kai Scheduler 即可。&lt;/p&gt;
&lt;h3 id="最终结果"&gt;最终结果&lt;/h3&gt;
&lt;p&gt;完成上述配置后，你得到的并不是一个简单的共享集群，而是一套更接近内部 AI 平台的结构：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;多个租户各自拥有独立的 vCluster，例如法律研究团队和客户支持团队&lt;/li&gt;
&lt;li&gt;每个租户都能在自己的控制平面中运行隔离的 Ollama/RAG 工作负载&lt;/li&gt;
&lt;li&gt;每个工作负载都可通过 Kai Scheduler 获取分数 GPU 访问，例如 0.5 GPU&lt;/li&gt;
&lt;li&gt;平台团队保持对底层宿主集群、GPU Operator、策略模板和共享资源池的统一控制&lt;/li&gt;
&lt;li&gt;租户获得接近独立集群的体验，而无需为每个团队单独维持完整硬件与控制面&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从平台架构角度看，这个演示的价值不在于 Ollama 或 RAG 本身，而在于它展示了以下组合如何协同工作：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;vCluster&lt;/code&gt; 提供租户级控制平面隔离、自定义策略空间和 API 自主性&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Kai Scheduler&lt;/code&gt; 提供公平 GPU 共享、队列治理和作业优先级控制&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NVIDIA GPU Operator&lt;/code&gt; 提供 GPU 资源可见性、驱动与运行时管理，以及时间分片能力&lt;/li&gt;
&lt;li&gt;应用层工作负载，例如 Ollama + RAG，则验证了这套平台能够支撑真实 AI 使用场景&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="关键要点"&gt;关键要点&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;面向多团队的 AI 平台建设，真正的约束通常不是“能不能建更多集群”，而是“在硬件数量有限时，如何同时获得隔离、自主性和高 GPU 利用率”。&lt;/li&gt;
&lt;li&gt;命名空间是共享集群的必要基础，但不足以成为企业级 AI 多租户平台的最终形态，因为它无法提供强控制平面隔离、充分租户自主性和简洁的合规治理边界。&lt;/li&gt;
&lt;li&gt;专用集群虽然提供最强隔离，却会带来集群数量膨胀、运维开销上升和 GPU 资源碎片化的问题，尤其不适合团队数量快速增长的环境。&lt;/li&gt;
&lt;li&gt;vCluster 的关键价值在于把“独立控制平面体验”与“共享宿主资源池”结合起来，使租户像拥有独立集群一样工作，而平台团队只需维护一个统一宿主集群。&lt;/li&gt;
&lt;li&gt;当 vCluster 与 Kai Scheduler、NVIDIA GPU Operator 结合时，平台不仅能提供隔离，还能在共享 GPU 环境中实现分数资源调度、公平队列和更高的硬件利用率。&lt;/li&gt;
&lt;li&gt;因此，vCluster 并不是单纯的多租户工具，而是一种让组织在不维护成百上千个独立集群的前提下，构建企业级 GPU 平台的现实方法。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>