GPU 时间片调度与多租户推理隔离工程实践
当单个 H100 的价值超过 25,000 美元,推理集群的 GPU 利用率每提升 1% 就能为中型 AI 服务商每年节省数十万美元。本文深入剖析从硬件分区到软件时间片调度的多层 GPU 共享技术分析栈,覆盖 NVIDIA MPS、MIG、时间片轮转、多优先级队列抢占等关键机制,以及它们在真实推理部署中的工程权衡。
一、为什么 GPU 共享是推理系统的核心问题
AI 推理的 GPU 利用率困境是一个被严重低估的工程挑战。与训练任务长期满载不同,在线推理请求具有两个根本性特征:
脉冲式请求负载: 用户的推理请求到达时间不可预测,存在明显的潮汐效应。低峰时段 GPU 利用率经常低于 15%,而高峰期又面临 SLO 违约风险。
长尾延迟敏感: 生成式模型(LLM)的自回归特性使得请求服务时间高度不均匀——短查询可能几十毫秒完成,而长序列生成(如 4K token 输出)可能占用 GPU 数百毫秒甚至数秒。少数"慢请求"会拖累整个批次的调度效率。
NVIDIA 白皮书数据显示,典型在线推理集群的平均 GPU 利用率在 18-35% 之间,离线批处理场景可达 60-80%。而云服务商的定价模型中,GPU 实例按整机计费,因此将多个低优先级工作负载"塞"进同一块 GPU 的动机非常强烈。
GPU 共享技术栈从底向上可分为四层:
┌─────────────────────────────────────────────┐
│ Layer 4: 应用层调度 │
│ (推理引擎批处理、请求路由、SLO 优先级队列) │
├─────────────────────────────────────────────┤
│ Layer 3: 虚拟化层 │
│ (MIG 硬件分区、SR-IOV 虚拟功能) │
├─────────────────────────────────────────────┤
│ Layer 2: 运行时层 │
│ (MPS 多进程共享、CUDA Time-Slice Scheduling) │
├─────────────────────────────────────────────┤
│ Layer 1: 硬件层 │
│ (GPU 上下文切换、GMMU 地址空间隔离) │
└─────────────────────────────────────────────┘
每一层都有不同的隔离保证和开销模型,实际生产系统往往是多层叠加使用。
二、NVIDIA MPS:轻量级多进程共享
2.1 原理与架构
NVIDIA Multi-Process Service (MPS) 是 CUDA 提供的用户态运行时组件,它允许不同进程的 CUDA 上下文在同一 GPU 上并发执行 kernel。与传统 CUDA 上下文串行执行(通过时间片轮转切换完整上下文)不同,MPS 的核心机制是:
单一上下文共享: MPS Server 作为特权进程维护一个"虚拟 GPU 上下文",所有 Client 进程通过 UNIX domain socket 连接到 MPS Server,将自己的 kernel 提交到共享流水线。
并发 kernel 执行: MPS Client 提交的 kernel 可以在 GPU SM(流式多处理器)上真正并行执行——前提是 SM 资源不冲突。这与传统模式下 kernel 严格串行化有本质区别。
内存隔离: 不同 Client 的显存分配通过 CUDA 虚拟地址管理保持隔离,一个进程无法访问其他进程的显存。
┌──────────────┐
│ MPS Server │
│ (特权上下文) │
└──────┬───────┘
│ UNIX Socket
┌────────────┼────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Process A │ │ Process B │ │ Process C │
│ (LLM 推理) │ │(Embedding)│ │ (批处理) │
└──────────┘ └──────────┘ └──────────┘
2.2 配置与调优
MPS 的配置主要通过环境变量控制:
# 启动 MPS 控制守护进程
export CUDA_VISIBLE_DEVICES=0
export CUDA_MPS_PIPE_DIRECTORY=/tmp/nvidia-mps
export CUDA_MPS_LOG_DIRECTORY=/tmp/nvidia-log
nvidia-cuda-mps-control -d
# 关键调优参数
# 1. 线程百分比:限制 MPS Client 可占用的 SM 比例
export CUDA_MPS_ACTIVE_THREAD_PERCENTAGE=50 # 最多使用 50% SM 资源
# 2. 限制并发 Client 数(通过连接数控制)
# 默认无明确限制,生产中建议配合 cgroup 限制 PID
2.3 MPS 的限制与陷阱
MPS 虽然在轻量级共享场景表现优秀,但有几个关键限制使其无法胜任所有多租户推理场景:
1. 单点故障风险: 如果 MPS Server 崩溃,所有依赖它的 Client 进程的 CUDA 上下文将全部失效。在一个 Client 触发 GPU 内存越界时,可能导致整个 MPS 实例挂掉。
2. 弱隔离性: MPS 没有显存使用量的硬限制。一个 Client 可以分配超过其"公平份额"的显存,导致其他 Client 的显存分配失败(OOM)。
3. 计算抢占粒度有限: MPS 的 Client 并发是协作式的——如果一个 Client 提交了大量长时间 kernel,其他 Client 的延迟 SLO 可能无法保证。
4. 仅支持同构 GPU: MPS 仅允许同一物理 GPU 上的进程共享,不支持跨设备共享。
适用场景:同一组优先级相近、信任边界明确的推理任务共享,如多个轻量级 Embedding 模型共享同一 GPU。
三、MIG 与硬件级隔离
3.1 MIG 分区机制
Multi-Instance GPU (MIG) 是 A100/H100 提供的硬件级分区功能,将物理 GPU 划分为最多 7 个相互隔离的 GPU Instance。
MIG 的关键特性是硬件级内存隔离:每个 GPU Instance 拥有独立的显存地址空间,一个分区的显存访问错误不会影响其他分区。同时 SM(计算单元)也是物理分区而非时间片共享。
3.2 MIG 的分区组合
H100 支持的分区配置(profile)包括:
| Profile | SM 数 | 显存(GB) | 最大并行数 |
|---|---|---|---|
| 1g.10gb | 14 | 10 | 7 |
| 2g.20gb | 28 | 20 | 3 |
| 3g.40gb | 42 | 40 | 2 |
| 4g.40gb | 56 | 40 | 1 |
| 7g.80gb | 112 (全卡) | 80 | 1 |
选择合适的 MIG profile 是一个核心约束优化问题:GPU Instance 越大,单请求性能越好,但并发数越低。
3.3 MIG 的工程困境
实际部署中 MIG 面临的挑战超出预期:
分区重建代价高: 更改 MIG 配置需要先将 GPU 上所有进程停止,重新分区,然后重启服务。这导致 MIG 配置不适合需要弹性伸缩的场景。
显存碎片化: 一个 7B 参数的 FP16 模型需要 ~14GB 显存,只能运行在 2g.20gb 或更大分区上。如果集群需要同时运行 3 个这样的模型,至少需要 3 个 2g.20gb 分区,共分配 60GB 显存(实际只需 42GB),浪费率高达 30%。
调度复杂度高: Kubernetes 设备插件对 MIG 的 "Mixed Strategy" 支持有限。调度器需要同时考虑分区类型匹配和节点亲和性,间接增加了 bin-packing 问题的复杂度。
四、CUDA 时间片调度:GPU 上下文抢占
4.1 上下文切换机制
现代 GPU(Volta 架构起)支持 Preemption 级别从 Device-Level 到 Thread-Level 的演进,但 CUDA 运行时的默认行为触发了最粗粒度的抢占——当时间片耗尽时,当前运行的所有 block 被逐出 SM,上下文保存到显存。
时间片调度的关键参数:
┌─────────────────────────────────────────────────────────┐
│ GPU 时间片调度示意 │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Context A │ │ Context B │ │ Context C │ │
│ │ (高优先级) │ │ (中优先级) │ │ (低优先级) │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ ═════╪══════════════╪══════════════╪═════════▶ 时间 │
│ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ │
│ │ QA: 2ms │ │ QB: 1ms │ │ QC: 3ms │ │
│ │ (TTFT) │ │ (Gen) │ │ (后台) │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ 时间片长度:通常 10-100μs 级别(取决于 kernel 粒度) │
└─────────────────────────────────────────────────────────┘
4.2 Stream Priority 与抢占控制
CUDA Stream 支持优先级调度,但没有标准 API 直接设定抢占时机。推理引擎通常采用以下模式:
// 创建不同优先级的 CUDA Stream
cudaStream_t high_priority_stream, low_priority_stream;
cudaStreamCreateWithPriority(&high_priority_stream, cudaStreamNonBlock, -1); // 高优先级
cudaStreamCreateWithPriority(&low_priority_stream, cudaStreamNonBlock, 0); // 默认优先级
// 高优先级推理请求走独立 stream
launch_llm_inference(high_priority_stream, input_tokens, output_tokens);
// 低优先级批处理/后台任务
launch_background_batch(low_priority_stream, batch_data);
// 同步:等待高优先级完成
cudaStreamSynchronize(high_priority_stream);
五、推理引擎级调度:SLO 感知的请求调度
5.1 两级调度架构
现代 LLM 推理引擎(如 vLLM、TensorRT-LLM)采用两级调度架构应对多租户 SLO。
5.2 Tag-Based Fairness 调度
这是 vLLM v0.4+ 引入的核心调度机制,解决"饥饿"问题:
class TagBasedScheduler:
"""基于 Tag 的公平调度器,防止某一租户独占 GPU"""
def __init__(self, num_tenant_tags: int):
self.tag_credits = {tag: MAX_TOKENS_PER_ROUND
for tag in range(num_tenant_tags)}
self.tag_budgets = {}
def schedule(self, waiting_queue, running_batch):
"""
每轮迭代的选择逻辑:
1. 按 Tag 轮转,确保每个租户每轮都至少获得一次调度机会
2. 同一 Tag 内的请求按 FIFO 处理
3. 如果某个 Tag 请求为空/预算耗尽,跳过
"""
scheduled = []
# 按 Tag 分组
tag_groups = defaultdict(list)
for req in waiting_queue:
tag_groups[req.tenant_tag].append(req)
# 轮询 Tag 分配 token 配额
for tag in sorted(tag_groups.keys()):
credit = self.tag_credits[tag]
if credit <= 0:
continue # 该 Tag 本已超用信用
for req in tag_groups[tag]:
req_tokens = req.prompt_len + req.max_new_tokens
if credit >= req_tokens:
scheduled.append(req)
credit -= req_tokens
self.tag_credits[tag] = credit
if credit <= MIN_TOKEN_THRESHOLD:
break
return scheduled
5.3 Preemption 与 KV Cache 驱逐
当 GPU 显存不足以容纳新请求时,推理引擎执行抢占:
Recomputation Preemption: 优先级低的请求直接丢弃,KV Cache 释放,请求重新排队。适用于短请求。
Swapping Preemption: 低优先级请求的 KV Cache 复制到 CPU 内存,释放 GPU 显存给高优先级请求。适用于长请求恢复成本低廉场景(但 LLM 的 KV Cache 体积巨大,swap 代价很高)。
抢占决策树:
新请求到达(P0 优先级)
│
▼
剩余显存足够? ──Yes──▶ 直接准入
│
No
▼
查找最低优先级运行请求(P_n 最低)
│
P_n < P_new?
/ \
Yes No
│ │
▼ ▼
Swap KV 加入等待队列
到 CPU (保留当前批)
│
▼
KV Cache 重新计算 vs 恢复
(L_prefill >> L_swap → 重算更优)
六、生产级多租户推理架构实战
6.1 SumoCity 的多层隔离模型
结合前述技术栈,一个典型的中型 AI 服务商多租户推理架构如下:
┌──────────────────────────────────────────────────────────────┐
│ Ingress Network │
│ (TLS termination, Rate limiting) │
└──────────────────────┬───────────────────────────────────────┘
│
┌────────▼────────┐
│ API Gateway │
│ (Tenant Auth, │
│ Quota Check) │
└────────┬────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Tenant A │ │ Tenant B │ │ Tenant C │
│ Gold Tier │ │Silver Tier│ │ Bronze │
│ P99<200ms │ │ P99<500ms │ │Best Effort│
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
┌─────▼─────────────▼─────────────▼─────┐
│ Global Scheduler │
│ (Bin-packing + SLO feasibility check) │
└─────────────────┬─────────────────────┘
│
┌────────────────┼────────────────┐
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│ H100 │ │ H100 │ │ H100 │
│ GPU 0 │ │ GPU 1 │ │ GPU 2 │
│ │ │ │ │ │
│ ┌────┐ │ │ ┌────┐ │ │ ┌────┐ │
│ │MIG1│ │ │ │MPS │ │ │ │MPS │ │
│ │P0 │ │ │ │Share│ │ │Share│ │
│ └────┘ │ │ └────┘ │ │ └────┘ │
│ ┌────┐ │ │ │ │ │
│ │MIG2│ │ │ │ │ │
│ │P1 │ │ │ │ │ │
│ └────┘ │ │ │ │ │
└────────┘ └────────┘ └────────┘
6.2 关键 SLO 公式
在设计调度策略时,需要量化几个关键约束:
Little's Law 并发估算:
L = lambda_rate * W
# 其中:
# - L = GPU 中同时服务的平均请求数
# - lambda_rate = 请求到达率 (req/s)
# - W = 平均服务时间 (s)
# 示例:λ=100 req/s, W=0.5s → L=50 并发请求
# 单 H100 (184GB HBM,含 KV Cache ~14GB/并发请求)
# → 最多支持 13 并发请求 → 需 4 台 H100
抢占恢复开销模型:
# Recompute 模式:
T_recovery = T_kv_recompute
# T_kv_recompute = L × prefill_time_per_token
# ≈ 0.3ms/token × L
# Swap 模式:
T_swap = KV_size / PCIe_bandwidth
# ≈ (2 × L × d_model × 2bytes) / 32 GB/s
# ≈ (2 × 4096 × 4096 × 2) / 32e9
# ≈ 2ms (for L=4096)
# 结论:当已计算前缀 token 数 > 约 7000 时,Swap 优于 Recompute
6.3 KV Cache 水量监控
class KVCacheWatermarkMonitor:
"""实时监控 KV Cache 水位,触发抢占决策"""
def __init__(self, gpu_memory_total, high_watermark=0.85, low_watermark=0.70):
self.total = gpu_memory_total
self.high_wm = int(gpu_memory_total * high_watermark)
self.low_wm = int(gpu_memory_total * low_watermark)
self.current_usage = 0
self.preempting = False
def on_allocate(self, size_bytes):
"""请求显存分配,返回是否允许"""
if self.current_usage + size_bytes > self.high_wm:
if not self.preempting:
self._trigger_preemption()
return False # 拒绝分配,请求入等待队列
self.current_usage += size_bytes
return True
def on_free(self, size_bytes):
"""请求结束,释放显存"""
self.current_usage -= size_bytes
if self.preempting and self.current_usage < self.low_wm:
self._resume_waiting()
def _trigger_preemption(self):
"""触发抢占:选择内存占用最大的低优先级请求驱逐"""
self.preempting = True
victim = self._select_victim()
victim.preempt(preemption_mode=PreemptionMode.RECOMPUTE)
def _select_victim(self):
"""选择抢占目标:优先级最低且 KV Cache 最大的请求"""
candidates = [r for r in self.running_requests if r.priority < Priority.HIGH]
return max(candidates, key=lambda r: r.kv_cache_size)
七、2026 年前沿趋势
7.1 Blackwell 架构的调度革新
NVIDIA Blackwell (B200/GB200) 引入了第五代 NVLink 和增强的引擎间通信,其 Fifth Gen Tensor Core 支持更细粒度的计算任务划分,理论上可实现推理级上下文切换延迟从毫秒级降至亚毫秒级别。
7.2 计算配额与 QoS 标准化的探索
AI 工作负载的 QoS 标准化仍在探索中。目前运营商侧主要依赖:
- Kubernetes Extended Resource + 自定义调度器
- NVIDIA Triton Inference Model Groups 的优先级设定
- DRA (Dynamic Resource Allocation) API 的 Alpha 支持
7.3 异构推理的共存调度
CPU-offloading + GPU 联合推理场景的调度成为新热点——当 GPU 处理不了全量请求时,将部分 Layer offload 到 CPU,同时利用 GPU 处理高吞吐的 FFN 层。这种异构共存调度的目标是同时满足延迟 SLO 和吞吐最大化。
八、总结:GPU 共享技术选型决策树
开始
│
▼
需要强隔离(安全/错误隔离)?
├── 是 → MIG 硬件分区
│ │
│ ▼
│ 分区大小是否可固定?
│ ├── 是 → 静态 MIG Profile
│ └── 否 → MIG + MPS 混合
│
└── 否 → 是否需要 SLO 保障?
├── 是 → MPS + Stream Priority
│ + 推理引擎 Tag-Based Scheduler
│
└── 否 → 默认时间片轮转
+ 应用层队列优先级
GPU 多租户共享不是一项单一技术的标签,而是一个从硬件到应用层的系统级工程挑战。MPS 提供了轻量级的进程并发但不保证 SLO;MIG 提供了强隔离但缺乏弹性;时间片调度提供了优先级但不提供内存隔离;推理引擎调度填补了应用层的公平性缺口。
核心原则:没有免费的午餐——每种共享技术在提升利用率的同时都在延迟确定性上做出让步。 工程设计者的任务是根据租户的 SLO 等级和信任边界,组合这些技术构建多层防御体系。
关键术语表:
- SM (Streaming Multiprocessor):GPU 流式多处理器,包含 CUDA Core、Tensor Core、寄存器和 L1 Cache
- TTFT (Time To First Token):首 token 延迟,衡量推理响应速度的核心指标
- KV Cache:自回归推理中已计算 token 的 Key/Value 张量缓存,避免重复计算
- Preemption:抢占,中断当前任务执行更高优先级任务的行为
- Bin-packing:装箱问题,将不同大小的资源请求调度到有限节点上的组合优化问题
- Little's Law:排队论基本定理 L=λW,关联系统中的平均项目数与吞吐/延迟

发表评论 取消回复