多作业与多租户管理——让集群被所有人高效使用 / Multi-Job and Multi-Tenant Cluster Management
📅 创建时间:2026-06-03 🏷️ 标签:#AI-Infra #集群侧 #多租户 #配额 #调度 #计费 #FairShare 📚 前置知识:[[04-job-scheduling]](作业调度系统)[[00-cluster-infra-overview]](集群全景) 📚 相关知识:[[09-cluster-operations]](集群运营)[[03-gpu-virtualization]](GPU 虚拟化)
场景:10 个团队、512 张卡、每天 50 个作业,调度器怎么分配?
┌─────────────────────────────────────────────────────────────┐
│ │
│ 某公司 AI 平台团队,512 张 H100 共享集群,10 个业务团队: │
│ │
│ 团队 A:基础研究,申请 256 卡跑预训练,需要 2 周 │
│ 团队 B:产品团队,申请 32 卡跑微调,今天要上线 │
│ 团队 C:算法团队,申请 64 卡跑实验,想快速迭代 │
│ 团队 D:数据团队,申请 8 卡跑预处理,随时来随时跑 │
│ 团队 E:领导特批项目,申请 512 卡,今天就要 │
│ │
│ 实际情况: │
│ → 当前运行:团队 A 的作业(200 卡,预训练第 3 天) │
│ → 空闲:312 张卡 │
│ │
│ 调度器必须回答: │
│ → 团队 E 的 512 卡申请怎么处理?(超过空闲量) │
│ → 团队 B 紧急任务要插队,怎么操作? │
│ → 团队 A 已经跑了 3 天,资源配额用完了吗? │
│ → 团队 C 每天都提交很多小作业,公平吗? │
│ │
│ 这就是多租户管理的核心问题: │
│ 如何在有限资源下,满足不同团队的不同需求? │
│ │
└─────────────────────────────────────────────────────────────┘第1节:多租户模型
多租户架构设计
┌─────────────────────────────────────────────────────────────┐
│ GPU 集群多租户架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 租户层级: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Level 1: Account(账户/部门) │ │
│ │ Level 2: Project(项目) │ │
│ │ Level 3: Job(作业) │ │
│ │ │ │
│ │ 示例: │ │
│ │ Account: "基础研究部" │ │
│ │ └── Project: "LLM预训练" │ │
│ │ └── Job: "Llama3-70B 训练" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Slurm 多租户实现: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Account = Slurm Account(sacctmgr add account) │ │
│ │ Project = Slurm Association(Account + User + Partition)│ │
│ │ Job = srun / sbatch(带 --account 参数) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Kubernetes 多租户实现: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Account = Kubernetes Namespace + RBAC │ │
│ │ Project = ResourceQuota + LimitRange │ │
│ │ Job = Job/PyTorchJob(带 namespace 标签) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘租户配额模型
┌─────────────────────────────────────────────────────────────┐
│ 配额设计:资源分配的三个维度 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 配额维度 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 维度 │ 说明 │ 单位 │ │
│ │ ──────────────┼────────────────────┼──────────────│ │
│ │ 作业数限制 │ 同时运行多少作业 │ 个(count) │ │
│ │ GPU 小时总量 │ 每天/每周能用多少 │ 卡·时 │ │
│ │ 最大作业规模 │ 单个作业最多多少卡 │ 卡(count) │ │
│ │ 运行时长限制 │ 作业最多跑多久 │ 小时 │ │
│ │ 存储空间 │ Checkpoint + 数据 │ TB │ │
│ │ 优先级 │ 调度优先级 │ 1-100 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 2. 配额配置示例(Slurm): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ $ sacctmgr add account research ParentName=root │ │
│ │ $ sacctmgr add user user1 Account=research \ │ │
│ │ GrpTRESMins=cpu=1000,gres/gpu=500 \ │ │
│ │ GrpJobs=5 \ │ │
│ │ MaxJobs=10 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 3. 配额配置示例(Kubernetes): │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ apiVersion: v1 │ │
│ │ kind: ResourceQuota │ │
│ │ metadata: │ │
│ │ name: research-quota │ │
│ │ namespace: research-team │ │
│ │ spec: │ │
│ │ hard: │ │
│ │ pods: "50" │ │
│ │ nvidia.com/gpu: "100" │ │
│ │ requests.nvidia.com/gpu: "100" │ │
│ │ requests.storage: "50Ti" │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘第2节:公平调度与抢占
Fair Share 调度
┌─────────────────────────────────────────────────────────────┐
│ Fair Share:让资源使用少的团队优先 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 背景:FIFO 调度的问题 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 团队 A:第1天提交大作业,占用 512 卡 7 天 │ │
│ │ 团队 B:第2天提交紧急任务,无法运行(等待 7 天) │ │
│ │ 团队 C:第3天提交小作业,无法运行 │ │
│ │ │ │
│ │ → FIFO 让大作业饿死小作业 │ │
│ │ → 先提交 ≠ 应该先跑 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Fair Share 算法: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 概念:每个账户有"公平份额"(Fair Share) │ │
│ │ │ │
│ │ 计算公式: │ │
│ │ Priority = Weight / (1 + Usage/Factor) │ │
│ │ │ │
│ │ 其中: │ │
│ │ → Weight:账户权重(人工设定) │ │
│ │ → Usage:账户已使用资源(历史累积) │ │
│ │ → Factor:公平因子(控制历史权重) │ │
│ │ │ │
│ │ 示例: │ │
│ │ → 团队 A:已使用 5000 卡·时,Usage 高,Priority 低│ │
│ │ → 团队 B:已使用 100 卡·时,Usage 低,Priority 高 │ │
│ │ → 结果:团队 B 的作业优先调度 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘抢占调度
┌─────────────────────────────────────────────────────────────┐
│ 抢占调度:高优先级作业可以打断低优先级 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 抢占的触发条件: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 高优先级作业申请资源,当前资源不足 │ │
│ │ 2. 调度器发现有低优先级作业正在运行 │ │
│ │ 3. 调度器发送 SIGTERM(优雅终止)给低优先级作业 │ │
│ │ 4. 等待 Checkpoint(如果配置了) │ │
│ │ 5. 超时后发送 SIGKILL(强制终止) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 抢占的粒度: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 整作业抢占:Slurm 原生方式 │ │
│ │ → 一次性释放整个作业的所有资源 │ │
│ │ → 优点:简单,资源一次性释放 │ │
│ │ → 缺点:已跑 5 天的大作业被抢 = 损失 5 天 │ │
│ │ │ │
│ │ 部分抢占:Kubernetes 原生方式(不直接支持 GPU) │ │
│ │ → 只释放部分资源,让作业降级运行 │ │
│ │ → 优点:减少损失 │ │
│ │ → 缺点:实现复杂,通常需要自定义调度器 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 抢占的成本: │
│ → 被抢作业必须 Checkpoint(否则前功尽弃) │
│ → 频繁抢占会降低集群整体效率 │
│ → 建议:抢占只用于紧急情况,日常用优先级区分即可 │
│ │
└─────────────────────────────────────────────────────────────┘Slurm 抢占配置
bash
# slurm.conf 中启用抢占
PreemptType=preempt/priority
PreemptMode=SUSPEND,GANG
PriorityTier=10 # 数值越高,优先级越高
# Partition 中设置优先级
PartitionName=urgent PriorityTier=50 # 紧急队列,优先级最高
PartitionName=train PriorityTier=20 # 训练队列
PartitionName=debug PriorityTier=10 # 调试队列
# 账户优先级
# sacctmgr modify user set PriorityTier=30 where user=user1
# 查看抢占状态
squeue --relations第3节:资源预留与作业排队
资源预留
┌─────────────────────────────────────────────────────────────┐
│ 资源预留:为重要作业保障资源 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题场景: │
│ → 周五下午提交大作业(512 卡,7 天) │
│ → 预计下周三开始有足够资源(当前被占用) │
│ → 如何确保到时候一定有资源? │
│ │
│ 解决方案:Slurm 资源预留(Reservation) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ $ scontrol create reservation \ │ │
│ │ ReservationName=project_leadership \ │ │
│ │ StartTime=2026-06-10T08:00:00 \ │ │
│ │ EndTime=2026-06-17T08:00:00 \ │ │
│ │ NodeCnt=64 \ │ │
│ │ gres=gpu:h100:8 \ │ │
│ │ Accounts=leadership \ │ │
│ │ Flags=MIGATION_TARGET │ │
│ │ │ │
│ │ 效果: │ │
│ │ → 6 月 10 日前,这些 512 张卡被保留 │ │
│ │ → 其他作业不能使用这些资源 │ │
│ │ → leadership 账户在指定时间一定有资源可跑 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 预留的代价: │
│ → 预留 = 资源锁定 = 临时放弃利用率 │
│ → 512 卡预留 7 天 = 86,016 卡·时的资源被锁定 │
│ → 建议:预留只用于真正的关键任务,且时间尽量精确 │
│ │
└─────────────────────────────────────────────────────────────┘作业排队优化
┌─────────────────────────────────────────────────────────────┐
│ 作业排队优化:减少"假等待" │
├─────────────────────────────────────────────────────────────┤
│ │
│ 问题:虚假等待(False Wait) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 作业 A:申请 512 卡,当前有 500 卡空闲 │ │
│ │ → 等待 12 卡释放,但 12 卡来自不同节点 │ │
│ │ → 调度器只分配整节点,无法凑齐 512 卡 │ │
│ │ → 实际上集群总体利用率只有 60%! │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 解决方案 1:放宽调度约束 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 允许作业跨节点部分分配: │ │
│ │ → 500 卡先跑,12 卡到位后动态加入 │ │
│ │ → DeepSpeed Elastic / Elastic Horovod 支持 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 解决方案 2:Backfill 填充空隙 │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 策略: │ │
│ │ → 大作业 A 等待 512 卡,当前有 500 卡空 │ │
│ │ → 调度器找到小作业 B(需 8 卡)能塞进空隙 │ │
│ │ → B 先跑,占满集群,不影响 A 的等待时间 │ │
│ │ → 集群利用率 100% │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 解决方案 3:作业分片(Elastic Training) │
│ → 作业可以 256 卡启动,后续动态扩展到 512 卡 │
│ → TF Job + ElasticPolicy / PyTorchJob + Elastic Horovod │
│ │
└─────────────────────────────────────────────────────────────┘第4节:GPU 利用率监控与计费
监控指标体系
┌─────────────────────────────────────────────────────────────┐
│ 多租户 GPU 利用率监控体系 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 核心监控指标: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 指标 │ 定义 │ 目标值 │ │
│ │ ──────────────────┼────────────────────┼───────────│ │
│ │ 集群总利用率 │ 实际用卡 / 总卡数 │ > 85% │ │
│ │ 租户利用率 │ 某租户用卡 / 配额 │ 参考值 │ │
│ │ 作业 MFU │ 实际算力 / 峰值 │ > 50% │ │
│ │ 作业 GPU 利用率 │ sm util 采样均值 │ > 70% │ │
│ │ 作业显存利用率 │ 显存使用 / 显存总量│ > 60% │ │
│ │ 等待时间 │ 作业入队到启动 │ < 4 小时 │ │
│ │ 作业成功率 │ 完成 / 总提交 │ > 95% │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ DCGM + Prometheus + Grafana 实现: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ # DCGM Exporter 导出作业级标签 │ │
│ │ DCGMExporter --job-stats=true --job-k8s-label=true│ │
│ │ │ │
│ │ # Prometheus 查询(按 Account 聚合 GPU 使用) │ │
│ │ sum by (account)(DCGM_FI_DEV_GPU_UTIL) │ │
│ │ │ │
│ │ # Grafana Dashboard(每个租户一个 Panel) │ │
│ │ → 各租户 GPU 使用率对比(饼图) │ │
│ │ → 各租户作业平均 MFU(柱状图) │ │
│ │ → 队列等待时间趋势(折线图) │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘计费模型
┌─────────────────────────────────────────────────────────────┐
│ GPU 集群计费模型 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 计费维度: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 模型 1:GPU 小时计费(最常用) │ │
│ │ → 费用 = Σ(卡数 × 运行时间 × 单价) │ │
│ │ → A100: ¥5/卡·时,H100: ¥10/卡·时 │ │
│ │ │ │
│ │ 模型 2:有效算力计费(MFU 加权) │ │
│ │ → 费用 = Σ(卡数 × 运行时间 × MFU × 单价) │ │
│ │ → 低利用率作业付费少,激励优化利用 │ │
│ │ │ │
│ │ 模型 3:存储 + 计算 分开计费 │ │
│ │ → 计算:GPU 小时 × 单价 │ │
│ │ → 存储:Checkpoint 占用 TB × 天 × 单价 │ │
│ │ → 鼓励及时清理不需要的 Checkpoint │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 计费与公平的平衡: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 场景:租户 A 白天用 256 卡,夜间释放 │ │
│ │ 租户 B 夜间用 256 卡,白天释放 │ │
│ │ → 如果按固定配额(512 卡/天),两租户费用相同 │ │
│ │ → 如果按实际使用(错峰),B 的费用更低 │ │
│ │ → 错峰计费可以激励用户避开高峰期 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘第5节:多租户隔离策略
存储与网络隔离
┌─────────────────────────────────────────────────────────────┐
│ 多租户隔离:存储、网络、作业 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 存储隔离: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Kubernetes: PVC + StorageClass │ │
│ │ → 每个租户有独立的 PVC,互不干扰 │ │
│ │ → 默认 StorageClass 设置 Quota │ │
│ │ │ │
│ │ Slurm: 目录级权限 │ │
│ │ → /home/team-a/:team-a 的数据 │ │
│ │ → /home/team-b/:team-b 的数据 │ │
│ │ → 目录权限 700,互相不可见 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 网络隔离: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Kubernetes: NetworkPolicy │ │
│ │ → Pod 之间默认隔离,需要显式放行 │ │
│ │ → 租户间网络流量通过 CNI 插件隔离 │ │
│ │ │ │
│ │ Slurm: 网络通常共享(依赖 IB 网络 ACL) │ │
│ │ → 不同租户的作业可以互相通信(NCCL 正常工作) │ │
│ │ → 外部访问通过防火墙规则控制 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ 作业隔离: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 共享集群(多数情况): │ │
│ │ → 不同租户作业共享 GPU(通过调度器分配) │ │
│ │ → 使用 MIG 实现硬件级隔离(生产环境推荐) │ │
│ │ │ │
│ │ 专属集群(大型租户): │ │
│ │ → 某些大团队有专属的节点池 │ │
│ │ → 专属节点只运行该团队的作业 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘升华:公平是幻觉,效率是目标
┌─────────────────────────────────────────────────────────────┐
│ 多租户管理的工程哲学 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 完美公平是不可能的: │
│ → 不同团队的需求差异巨大,无法用同一个标准衡量 │
│ → 配额只是减少冲突的机制,不是消除冲突 │
│ │
│ 2. 多租户管理的目标是"可接受的公平 + 最高的整体效率": │
│ → 如果为了公平让 50% 的资源闲置,损害的是所有团队 │
│ → 好的调度策略让大多数时候大多数团队满意 │
│ │
│ 3. 计费是管理的工具,不是惩罚的手段: │
│ → 合理的计费让团队主动优化利用率 │
│ → 过高的计费让团队转向地下(绕过平台) │
│ │
│ 4. 抢占应该谨慎使用: │
│ → 每次抢占都是有代价的(Checkpoint 损失) │
│ → 预留 + 优先级分层是更优雅的方案 │
│ │
│ 一句话总结: │
│ 多租户管理的本质是在"谁都想用更多"的欲望面前, │
│ 建立一套让所有人都能接受的规则,而不是让所有人满意。 │
│ │
└─────────────────────────────────────────────────────────────┘"AI 可查 vs 必须理解"清单
AI 可查:
✅ slurm.conf 的完整配置参数
✅ sacctmgr 的每个子命令
✅ Kubernetes ResourceQuota 的详细 YAML 字段
✅ 各类计费模型的具体实现代码
必须理解:
🔴 多租户的三个层级:Account → Project → Job
🔴 Fair Share 调度算法:为什么"用得少的优先"
🔴 抢占调度的代价:Checkpoint 是抢占的前提
🔴 Backfill 策略如何提升集群利用率
🔴 GPU 利用率监控的四个核心指标(集群 / 租户 / MFU / 等待时间)
🔴 计费模型的选择:GPU 小时 vs MFU 加权 vs 存储分离学习状态:🟡 开始学习