CUDA MPS 实战:多进程共享 GPU 的小任务并发优化
摘要: 单卡 GPU 上跑几十个推理小任务,默认的进程级并发会互相"踩踏"——每个进程各自分配显存上下文、串行占用 SM。CUDA MPS(Multi-Process Service)让多个进程共享 CUDA 上下文并发执行,是 MIG 之外的另一种算力切分思路。本文讲清楚"什么时候用 MPS、怎么配、收益多大、坑在哪"。
0. 实验目标
- 理解 MPS 与 MIG 的定位差异(软件共享 vs 硬件隔离)
- 掌握 MPS 两种部署方式:控制台模式(交互实验)与守护进程模式(生产)
- 用并发实验量化 MPS 的吞吐收益(小任务场景)
- 知道 MPS 的边界与坑:故障隔离、显存上限、超时配置
1. 为什么需要 MPS:小任务并发的"踩踏"问题
原理讲解:
- 没有 MPS 时,GPU 上每个 CUDA 进程各自初始化一份完整的 CUDA context(几 GB 显存开销),且 SM 调度是进程间"轮流"的——小任务密集场景(几十路推理/预处理)吞吐上不去、显存浪费严重。
- MPS 的核心:多个进程通过 MPS server 共享同一个 CUDA context,GPU 调度粒度从"进程"变成"线程/任务",小任务能真正并行占满 SM,同时显存上下文只存一份。
MPS vs MIG(关键对照):
| 维度 | CUDA MPS | MIG(Multi-Instance GPU) |
|---|---|---|
| 隔离级别 | 软件级共享(context 共享) | 硬件级隔离(SM/显存/带宽切分) |
| 适合场景 | 几十路小任务、吞吐优先 | 多租户强隔离、故障隔离优先 |
| 显存 | 共享一份 context,省显存 | 按实例硬切分 |
| 故障影响 | 一个进程崩溃可能影响共享组 | 实例间完全隔离 |
| 适用卡 | 全部 NVIDIA 卡 | A100/H100 等 |
结论:MIG 是"切蛋糕",MPS 是"拼桌子"。混合算力池里两者配合:租户间用 MIG 硬隔离,租户内小任务用 MPS 提吞吐。
2. 控制台模式:一分钟上手
bash
# 查询 GPU 是否支持 MPS(默认返回 Enabled)
nvidia-smi -q | grep -A 2 "MPS"
# 控制台模式:把当前 shell 的所有 CUDA 进程纳入 MPS(实验/验证用)
export CUDA_MPS_PIPE_DIRECTORY=/tmp/nvidia-mps
export CUDA_MPS_LOG_DIRECTORY=/tmp/nvidia-mps-log
nvidia-cuda-mps-control -d # 后台启动 MPS 控制守护进程
# 验证:MPS 进程已运行
ps aux | grep "mps" | grep -v grep
# 实验结束退出
echo quit | nvidia-cuda-mps-control逐段拆解:
CUDA_MPS_PIPE_DIRECTORY:MPS 的控制管道目录,所有要共享的进程必须指向同一个目录——这是"加入同一个 MPS server"的关键。nvidia-cuda-mps-control -d:守护进程模式启动 server,之后任意设置了CUDA_MPS_PIPE_DIRECTORY的 CUDA 进程自动接入。- 注意:MPS 只对"设置了环境变量"的进程生效;没设置的在 MPS server 之外独立运行,两边互不干扰。
3. 守护进程模式:生产部署
bash
# systemd 管理 MPS 守护进程(生产标准做法)
cat > /etc/systemd/system/nvidia-mps.service <<'EOF'
[Unit]
Description=NVIDIA MPS control daemon
After=network.target
[Service]
Type=forking
ExecStart=/usr/bin/nvidia-cuda-mps-control -d
ExecStop=/usr/bin/nvidia-cuda-mps-control --kill
Restart=on-failure
Environment=CUDA_MPS_PIPE_DIRECTORY=/var/run/nvidia-mps
Environment=CUDA_MPS_LOG_DIRECTORY=/var/log/nvidia-mps
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now nvidia-mps工程启示:
- 生产环境用 systemd 托管 MPS 守护进程,开机自启、崩溃自动拉起。
- 接入方(推理服务容器/进程)统一注入
CUDA_MPS_PIPE_DIRECTORY=/var/run/nvidia-mps,K8s 里用环境变量或envFrom统一下发。 - MPS 的显存上限:默认情况下 MPS 客户端共享全部显存;可设
CUDA_MPS_ACTIVE_THREAD_PERCENTAGE限制单个客户端可占用的 SM 比例(0-100),用于粗粒度的配额控制。
4. 实验:小任务并发的 MPS 收益
bash
# 实验设计:16 路并发小推理(batch=1),对比 MPS 开关
# 开启前:nvidia-smi 看 GPU 利用率波动大、任务排队
# 开启后:SM 利用率稳定打满,总吞吐显著提升
nvidia-smi --query-gpu=utilization.gpu,memory.used --format=csv -l 1实验结果(示例量级 · 8 路推理进程 · 单卡):
| 场景 | GPU 利用率 | 总吞吐 | 显存上下文 |
|---|---|---|---|
| 无 MPS(默认) | ~35% 波动 | 基准 1.0× | 每进程一份(N 份) |
| 开启 MPS | ~92% 稳定 | ~2.3× | 共享一份 |
| MPS + 限制 SM 50% | ~50% 封顶 | ~1.4× | 共享一份 |
结论:小任务密集场景 MPS 收益显著(吞吐翻倍+);大任务(单请求占满整卡)不需要 MPS,反而建议 MIG 隔离或整卡独占。MPS 的 SM 比例限制适合"多租户共享一张卡 + 配额管理"。
5. 坑与边界(生产必看)
- 故障隔离弱:MPS 客户端崩溃可能影响同组其他进程(共享 context)。关键服务别和实验任务混在一个 MPS 组,或用 MIG 做硬隔离兜底。
- 不支持所有 API:部分 CUDA API(如某些多进程互操作、图形 API)在 MPS 下有兼容限制,部署前跑一遍应用自测。
- 多用户权限:不同用户要加入同一 MPS server 需要目录权限正确(
/var/run/nvidia-mps权限 777 或组授权),否则"找不到 MPS 管道"。 - 与 MIG 互斥:MIG 开启时 MPS 行为受实例约束(MPS 在 MIG 实例内可用但粒度不同);不要同时混用两套切分策略。
- 容器场景:K8s 里要共享 GPU 的 Pod 需挂载 MPS 管道目录(
/var/run/nvidia-mps+ socket),并用hostPID/共享卷打通;NVIDIA 官方 device plugin 有 MPS 支持。
6. 工程启示汇总
- 先分场景:小任务密集 → MPS;多租户强隔离 → MIG;两者可嵌套(MIG 租户内再开 MPS)。
- 生产用 systemd 托管 MPS 守护进程,开机自启 + 崩溃拉起。
- 接入方统一注入
CUDA_MPS_PIPE_DIRECTORY,K8s 用 envFrom 统一下发。 CUDA_MPS_ACTIVE_THREAD_PERCENTAGE做粗粒度配额,别裸奔全量共享。- 关键服务与实验任务分 MPS 组,故障隔离弱是 MPS 的天生短板。
- 验证先行:MPS 兼容性按应用自测,别等上线才发现 API 不支持。
- 监控 GPU 利用率:MPS 生效的标志是利用率从"波动"变"稳定打满"。