作者:基础设施团队 | 发布日期:2025-10-15 | 标签:Kubernetes、Volcano、GPU调度、资源管理、AI Infra
一、背景与痛点
1.1 GPU 资源调度的特殊性
在说 Volcano 之前,我想先聊聊 GPU 资源调度到底有什么特殊的。
CPU 调度的核心问题是"计算能力分配",而 GPU 调度的核心问题是"稀缺资源的高效利用"。这听起来差不多,但实际差异巨大:
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ CPU vs GPU 调度差异 │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ CPU 资源特点: GPU 资源特点: │
│ ───────────────── ────────────────── │
│ │
│ ✓ 通用性强 ✗ 专用性强 │
│ ✓ 资源丰富 ✗ 资源稀缺 │
│ ✓ 切分灵活 (可以精确到毫核) ✗ 切分困难 (通常是整卡或MIG) │
│ ✓ 共享友好 ✗ 共享复杂 (CUDA上下文) │
│ ✓ 虚拟化成熟 ✗ 虚拟化仍在发展中 │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ AI 训练场景的特殊需求 │ │
│ │ │ │
│ │ 1. 多节点通信: │ │
│ │ - GPU间需要高速互联 (NVLink/NVSwitch) │ │
│ │ - 需要亲和性调度(同节点/同交换机下) │ │
│ │ │ │
│ │ 2. 长时间运行: │ │
│ │ - 训练任务可能运行数天/数周 │ │
│ │ - 需要容错和检查点支持 │ │
│ │ │ │
│ │ 3. 资源抢占: │ │
│ │ - 紧急任务需要优先调度 │ │
│ │ - 低优先级任务需要被抢占 │ │
│ │ │ │
│ │ 4. 碎片化问题: │ │
│ │ - 训练需要4卡,但只有2+1+1分布 │ │
│ │ - 无法形成连续的4卡资源 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘1.2 我们的痛点
在没有 Volcano 之前,我们遇到了这些问题:
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 痛点一:资源碎片化 │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 假设我们有8个节点,每个节点4卡,共32卡: │
│ │
│ 节点1: [■■■■] ████ 节点5: [■■■■] ████ │
│ 节点2: [■■■░] ███空闲 节点6: [■■░░] ██空闲 │
│ 节点3: [■■■■] ████ 节点7: [■■■■] ████ │
│ 节点4: [■░░░] █空闲 节点8: [■■■■] ████ │
│ │
│ 总计: 32卡 - 3卡使用 = 29卡可用 │
│ │
│ 但是!要运行一个8卡训练任务: │
│ ✗ 节点1+节点2:需要跨节点,网络延迟高 │
│ ✗ 节点1+节点2+节点3+节点4:跨3个节点,开销更大 │
│ ✗ 无法在单一节点内完成 │
│ │
│ 结果:任务排队等待,GPU利用率低 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 痛点二:调度策略单一 │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ K8s 默认调度器的局限: │
│ │
│ ✗ 只支持 Binpack / Spread 两种策略 │
│ ✗ 无法区分训练任务和推理服务 │
│ ✗ 无法根据任务优先级抢占资源 │
│ ✗ 无法处理gang scheduling │
│ ✗ 无法支持资源公平分配 (Fair Share) │
│ │
│ 实际场景需求: │
│ ✓ 训练任务需要gang scheduling(全有或全无) │
│ ✓ 推理服务需要弹性扩缩容 │
│ ✓ 高优先级任务可以抢占低优先级 │
│ ✓ 不同团队/项目的资源配额 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 痛点三:GPU共享困难 │
├─────────────────────────────────────────────────────────────────────────────┬───────────┤
│ │ │
│ 推理服务场景: │ │
│ │ │
│ ┌───────────────────────────────────────────────────────────────────┐ │ │
│ │ │ │ │
│ │ 模型A (小) 模型B (中) 模型C (大) │ │ │
│ │ ┌───────┐ ┌─────────┐ ┌─────────────┐ │ │ │
│ │ │ 1GB │ │ 5GB │ │ 15GB │ │ │ │
│ │ │ QPS=5│ │ QPS=20 │ │ QPS=50 │ │ │ │
│ │ └───────┘ └─────────┘ └─────────────┘ │ │ │
│ │ 4GB 20GB 60GB │ │ │
│ │ ════════════════════════════════════════════════ │ │ A100 40GB│
│ │ │ │ │
│ └───────────────────────────────────────────────────────────────────┘ │ │
│ │ │
│ 需求: 一个GPU上运行多个推理服务,按实际使用分配资源 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘1.3 为什么选 Volcano
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ Volcano 是什么 │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Volcano 官方定位: │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Volcano 是一个 Kubernetes 原生的高性能批处理调度器, │ │
│ │ 专为 AI/HPC/大数据等高性能计算场景设计 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 核心特性: │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ ✓ Gang Scheduling (作业调度) ✓ Priority-based Scheduling │ │
│ │ 所有Pod同时调度或全不调度 基于优先级的调度与抢占 │ │
│ ├─────────────────────────────────────────────────────────────────────────────┤ │
│ │ ✓ Fair Share (公平分享) ✓ Queue Management │ │
│ │ 多租户/多队列资源公平分配 队列化管理 │ │
│ ├─────────────────────────────────────────────────────────────────────────────┤ │
│ │ ✓ Reclaim & Preemption ✓ Resource Reservation │ │
│ │ 资源共享与抢占 资源预留 │ │
│ ├─────────────────────────────────────────────────────────────────────────────┤ │
│ │ ✓ binpack & spread ✓ Task Topology │ │
│ │ 装箱和分散调度 GPU拓扑感知调度 (NVLink) │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 竞品对比: │
│ │
│ ┌────────────────┬──────────────┬──────────────┬──────────────┬──────────────┐ │
│ │ 特性 │ K8s默认 │ Volcano │ YuniKube │ Fleet │ │
│ ├────────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ │
│ │ Gang Scheduling │ ✗ │ ✓ │ ✓ │ ✗ │ │
│ │ Priority │ △ │ ✓ │ ✓ │ ✓ │ │
│ │ Fair Share │ ✗ │ ✓ │ ✗ │ ✓ │ │
│ │ GPU Topology │ ✗ │ ✓ │ ✗ │ ✗ │ │
│ │ Maturity │ ★★★★★ │ ★★★★ │ ★★★ │ ★★ │ │
│ │ Community │ K8s │ 华为 │ 快手 │ 字节 │ │
│ └────────────────┴──────────────┴──────────────┴──────────────┴──────────────┘ │
│ │
│ △ = 基础支持,但功能有限 │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘二、Volcano 架构详解
2.1 整体架构
╔═══════════════════════════════════════════════════════════════════════════════════════════╗
║ Volcano 架构 ║
╠═══════════════════════════════════════════════════════════════════════════════════════════╣
║ ║
║ ┌──────────────────────────────────────────────────────────────────────────────────┐ ║
║ │ Kubernetes API Server │ ║
║ │ (与 Volcano 共享 API Server) │ ║
║ └────────────────────────────────┬────────────────────────────────────────────────┘ ║
║ │ ║
║ ┌────────────────────────────────┼────────────────────────────────────────────────┐ ║
║ │ │ │ ║
║ │ ┌────────────────────────────┴────────────────────────────┐ │ ║
║ │ │ │ │ ║
║ │ │ ┌────────────────┐ ┌────────────────┐ │ │ ║
║ │ │ │ Volcano │ │ Volcano │ │ │ ║
║ │ │ │ Controller │ │ Scheduler │ │ │ ║
║ │ │ │ │ │ │ │ │ ║
║ │ │ │ • Job │ │ • Scheduling │ │ │ ║
║ │ │ │ Controller │ │ Framework │ │ │ ║
║ │ │ │ • Queue │ │ • Plugins │ │ │ ║
║ │ │ │ Controller │ │ • Actions │ │ │ ║
║ │ │ │ • VCJob │ │ │ │ │ ║
║ │ │ │ CRD │ │ │ │ │ ║
║ │ │ └────────────────┘ └────────────────┘ │ │ ║
║ │ │ │ │ ║
║ │ └──────────────────────────────────────────────────────────┘ │ ║
║ │ │ │ ║
║ └─────────────────────────────────────┼───────────────────────────────────────────────┘ ║
║ │ ║
║ ▼ ║
║ ┌──────────────────────────────────────────────────────────────────────────────────┐ ║
║ │ Volcano Scheduler Plugins │ ║
║ │ │ ║
║ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ ║
║ │ │ Gang │ │ Priority │ │ Queue │ │ Fair │ │ binpack │ │ ║
║ │ │ Scheduling│ │ │ │ │ │ Share │ │ │ │ ║
║ │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ ║
║ │ │ ║
║ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ ║
║ │ │ Topology│ │ Preempt │ │ Reclaim │ │ Resource │ │ Node │ │ ║
║ │ │ (GPU拓扑) │ │ (抢占) │ │ (回收) │ │Reservation │ │ Affinity │ │ ║
║ │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ ║
║ └──────────────────────────────────────────────────────────────────────────────────┘ ║
║ │ ║
║ ▼ ║
║ ┌──────────────────────────────────────────────────────────────────────────────────┐ ║
║ │ Kubernetes Cluster │ ║
║ │ │ ║
║ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ ║
║ │ │ GPU Node 1 │ │ GPU Node 2 │ │ GPU Node 3 │ │ ║
║ │ │ ┌───────────┐ │ │ ┌───────────┐ │ │ ┌───────────┐ │ │ ║
║ │ │ │ A100 x4 │ │ │ │ A100 x4 │ │ │ │ A100 x4 │ │ │ ║
║ │ │ │ NVLink │ │ │ │ NVLink │ │ │ │ NVLink │ │ │ ║
║ │ │ └───────────┘ │ │ └───────────┘ │ │ └───────────┘ │ │ ║
║ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ ║
║ └──────────────────────────────────────────────────────────────────────────────────┘ ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════════════════════╝2.2 核心概念
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ Volcano 核心概念 │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. Queue (队列) │
│ ───────────── │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ kind: Queue │ │
│ │ metadata: │ │
│ │ name: gpu-high-priority │ │
│ │ spec: │ │
│ │ weight: 50 # 权重 │ │
│ │ guarantee: │ │
│ │ resource.cpu: 10 # 最小保障 │ │
│ │ resource.memory: 40Gi │ │
│ │ resource.nvidia.com/gpu: 4 │ │
│ │ reclaimable: true # 是否可回收 │ │
│ │ status: │ │
│ │ state: Open # Open / Closed / Closing │ │
│ │ allocated: │ │
│ │ resource.cpu: 20 │ │
│ │ running: 5 # 正在运行的任务数 │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 2. PodGroup (Pod组) │
│ ───────────── │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ kind: PodGroup │ │
│ │ spec: │ │
│ │ minMember: 4 # Gang Scheduling:最少需要4个Pod │ │
│ │ minAvailable: 2 # 最少需要2个Pod才能运行 │ │
│ │ queue: gpu-high-priority # 所属队列 │ │
│ │ priority: 100 # 优先级 │ │
│ │ lifecycle: PodGroupLifecycle # Pending / Running / Unknown /Succeeded│ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 3. Job (VCJob - Volcano Job) │
│ ────────────────── │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ kind: VCJob │ │
│ │ spec: │ │
│ │ tasks: │ │
│ │ - name: worker │ │
│ │ replicas: 4 # 4个Worker │ │
│ │ template: │ │
│ │ spec: │ │
│ │ containers: │ │
│ │ - name: training │ │
│ │ image: pytorch:2.0 │ │
│ │ resources: │ │
│ │ limits: │ │
│ │ nvidia.com/gpu: 1 │ │
│ │ command: [python, train.py] │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘2.3 调度流程
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ Volcano 调度流程 │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 调度周期 (Scheduling Cycle): │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Open │───>│ Enqueue │───>│ Allocate │───>│ Preempt │───>│ Backoff │ │
│ │ Session │ │ │ │ │ │ │ │ │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ 详细流程: │
│ │
│ 1. Session Open │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ • 从 K8s API 获取所有 Node、Pod 信息 │ │
│ │ • 从 Volcano API 获取 Queue、PodGroup 信息 │ │
│ │ • 构建调度缓存 (Session Cache) │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 2. Enqueue (入队) │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ • 遍历所有待调度的 PodGroup │ │
│ │ • 检查资源是否满足 (Queue 配额、minMember 等) │ │
│ │ • 通过验证的 PodGroup 进入待调度队列 │ │
│ │ • 未通过的 PodGroup 记录原因,等待下一周期 │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 3. Allocate (分配) │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ • 按 Queue 权重和优先级排序 PodGroup │ │
│ │ • 调用各 Plugin 计算得分 │ │
│ │ • 选择最优节点分配 Pod │ │
│ │ • 支持 binpack (集中) / spread (分散) │ │
│ │ • GPU 拓扑感知调度 │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 4. Preempt (抢占) │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ • 检查是否有高优先级任务等待资源 │ │
│ │ • 如果有,驱逐低优先级 Pod │ │
│ │ • 确保 Gang Scheduling 完整性 │ │
│ │ • 遵循公平策略 │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 5. Backoff (回退) │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ • 调度失败的 Pod 进入 Backoff │ │
│ │ • 等待指数退避时间后重试 │ │
│ │ • 避免频繁调度失败 │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘三、训练 vs 推理:调度策略差异
3.1 训练任务调度策略
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 训练任务调度策略 │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 训练任务特点: │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ✓ 长时间运行 (数小时 ~ 数周) │ │
│ │ ✓ 需要多卡/多节点协同 (数据并行/模型并行) │ │
│ │ ✓ 需要 Gang Scheduling (所有 Worker 同时启动) │ │
│ │ ✓ 需要 NVLink 拓扑感知 (减少通信开销) │ │
│ │ ✓ Checkpoint 支持 (容错恢复) │ │
│ │ ✓ 优先级调度 (紧急实验优先) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ Gang Scheduling 示意: │
│ │
│ 普通调度: Gang Scheduling: │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ Worker1 ✓ │ │ Worker1 Worker2 │ │
│ │ Worker2 ✓ │ │ ✓ ✓ │ │
│ │ Worker3 等待... │ │ │ │ │ │
│ │ Worker4 等待... │ │ │ │ │ │
│ │ (3,4 等待资源,1,2 运行)│ │ Worker3 Worker4 │ │
│ │ 资源利用率低 │ │ ✓ ✓ │ │
│ └─────────────────────────┘ │ All or Nothing │ │
│ └─────────────────────────┘ │
│ │
│ NVLink 拓扑感知: │
│ │
│ A100 8卡拓扑: │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ GPU0 ──── NVLink ──── GPU1 │ │
│ │ │ │ │ │
│ │ NVLink NVLink │ │
│ │ │ │ │ │
│ │ NVLink ─────┬───── NVLink │ │
│ │ │ │ │ │ │
│ │ │ ┌────┴────┐ │ │ │
│ │ │ │ NVSwitch │ │ │ │
│ │ │ └────┬────┘ │ │ │
│ │ NVLink │ NVLink │ │
│ │ │ │ │ │ │
│ │ GPU2 ──── NVLink ──── GPU3 │ │
│ │ │ │
│ │ 最佳策略: 连续4卡 (GPU0-3) 优先于 分散4卡 (GPU0,2,4,6) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘3.2 推理服务调度策略
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 推理服务调度策略 │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 推理服务特点: │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ✓ 短时请求 (毫秒 ~ 秒级) │ │
│ │ ✓ 通常单卡或部分 GPU │ │
│ │ ✓ 需要高可用 (多副本部署) │ │
│ │ ✓ 需要弹性扩缩容 (基于 QPS / GPU利用率) │ │
│ │ ✓ 支持 A/B Testing (多版本并存) │ │
│ │ ✓ 资源超卖 (共享 GPU) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 推理服务部署模式: │
│ │
│ 模式1: 独享模式 模式2: 共享模式 │
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
│ │ ┌─────────────────────┐ │ │ ┌─────────────────────┐ │ │
│ │ │ 模型A Pod │ │ │ │ 模型A Pod │ │ │
│ │ │ GPU: 1 (独占) │ │ │ │ GPU Memory: 10GB │ │ │
│ │ └─────────────────────┘ │ │ └─────────────────────┘ │ │
│ │ ┌─────────────────────┐ │ │ ┌─────────────────────┐ │ │
│ │ │ 模型B Pod │ │ │ │ 模型B Pod │ │ │
│ │ │ GPU: 1 (独占) │ │ │ │ GPU Memory: 20GB │ │ │
│ │ └─────────────────────┘ │ │ └─────────────────────┘ │ │
│ │ ┌─────────────────────┐ │ │ ┌─────────────────────┐ │ │
│ │ │ 模型C Pod │ │ │ │ 模型C Pod │ │ │
│ │ │ GPU: 1 (独占) │ │ │ │ GPU Memory: 5GB │ │ │
│ │ └─────────────────────┘ │ │ └─────────────────────┘ │ │
│ │ GPU 0 │ GPU 0 │
│ └─────────────────────────┘ └─────────────────────────┘ │
│ │
│ KEDA 弹性扩缩容: │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ kind: ScaledObject │ │
│ │ metadata: │ │
│ │ name: inference-scaler │ │
│ │ spec: │ │
│ │ scaleTargetRef: │ │
│ │ name: inference-deployment │ │
│ │ minReplicaCount: 1 │ │
│ │ maxReplicaCount: 10 │ │
│ │ triggers: │ │
│ │ - type: prometheus │ │
│ │ metricType: AverageValue │ │
│ │ metadata: │ │
│ │ serverAddress: http://prometheus:9090 │ │
│ │ metricName: gpu_utilization_avg │ │
│ │ threshold: "70" # GPU 利用率 > 70% 时扩容 │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘3.3 调度策略对比
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 训练 vs 推理调度策略对比 │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────┬─────────────────────────┬─────────────────────────┐ │
│ │ 维度 │ 训练任务 │ 推理服务 │ │
│ ├────────────────┼─────────────────────────┼─────────────────────────┤ │
│ │ 调度策略 │ Gang + 拓扑感知 │ binpack + 共享 │ │
│ │ ──────────── │ │ │ │
│ │ Gang │ 必须 (All/None) │ 不需要 │ │
│ │ 拓扑感知 │ 必须 (NVLink优先) │ 可选 │ │
│ │ 资源分配 │ 独占 │ 可超卖 │ │
│ │ 扩缩容 │ 静态 (任务开始/结束) │ 动态 (KEDA) │ │
│ │ 优先级 │ 高 (可抢占) │ 中等 │ │
│ │ 生命周期 │ 小时~周 │ 秒~天 │ │
│ │ Checkpoint │ 必须 │ 不需要 │ │
│ │ 队列隔离 │ 必须 (多租户) │ 可选 │ │
│ └────────────────┴─────────────────────────┴─────────────────────────┘ │
│ │
│ 调度策略选择建议: │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 训练任务: │ │
│ │ • 小规模 (< 8卡): 优先 binpack,利用 NVLink │ │
│ │ • 大规模 (> 8卡): 优先 spread,避免单节点过载 │ │
│ │ • 紧急任务: 开启抢占,高优先级 │ │
│ │ │ │
│ │ 推理服务: │ │
│ │ • 在线服务: binpack,提高资源利用率 │ │
│ │ • 批量推理: 离线队列,低优先级 │ │
│ │ • 弹性场景: KEDA + HPA │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘四、GPU 共享与切分
4.1 GPU 共享方案对比
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ GPU 共享方案对比 │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────┬──────────────┬──────────────┬──────────────┬──────────────┐ │
│ │ 方案 │ MIG │ Time-Share │ vGPU │ 容器共享 │ │
│ │ (NVIDIA) │ │ │ (虚拟GPU) │ │ │
│ ├────────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ │
│ │ 隔离级别 │ 硬件级 │ 软件级 │ 硬件级 │ 软件级 │ │
│ │ ──────────── │ │ │ │ │ │
│ │ 内存隔离 │ ✓ │ ✗ │ ✓ │ ✗ │ │
│ │ 算力隔离 │ ✓ │ △ │ ✓ │ ✗ │ │
│ │ 通信隔离 │ ✓ │ ✗ │ ✓ │ ✗ │ │
│ ├────────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ │
│ │ 灵活性 │ 静态切片 │ 动态共享 │ 动态切片 │ 最灵活 │ │
│ │ 开销 │ 低 │ 中 │ 中 │ 低 │ │
│ │ 适用场景 │ 生产推理 │ 开发测试 │ 多租户 │ 同团队 │ │
│ │ 复杂度 │ 中 │ 低 │ 高 │ 低 │ │
│ └────────────────┴──────────────┴──────────────┴──────────────┴──────────────┘ │
│ │
│ △ = 部分支持 │
│ │
│ 推荐方案: │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ A100 (80GB): │ │
│ │ • MIG: 7 x 10GB + 1 x 20GB (生产推理) │ │
│ │ • Time-share: 开发测试环境 │ │
│ │ │ │
│ │ H100: │ │
│ │ • MIG: 7 x 10GB (生产) │ │
│ │ • Time-share: 开发环境 │ │
│ │ │ │
│ │ A10 / T4: │ │
│ │ • Time-share: 主要使用 │ │
│ │ • 容器级隔离: cgroup + CUDA MPS │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘4.2 MIG 配置实践
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ MIG 配置详解 │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ A100 MIG 切片规格 (官方推荐): │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 40GB 版本: 80GB 版本: │ │
│ │ ─────────── ────────── │ │
│ │ • 7 x 5GB (1/7 slice) • 7 x 10GB │ │
│ │ • 3 x 10GB (1/4 slice) • 3 x 20GB │ │
│ │ • 2 x 20GB (1/2 slice) • 2 x 40GB │ │
│ │ • 1 x 40GB (full GPU) • 1 x 80GB │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ MIG 启用与配置: │
│ │
│ 1. 节点标签 (node-config): │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ apiVersion: v1 │ │
│ │ kind: ConfigMap │ │
│ │ metadata: │ │
│ │ name: nvidia-mig-config │ │
│ │ namespace: volcano-system │ │
│ │ data: │ │
│ │ config.yaml: | │ │
│ │ # All GPUs support MIG mode │ │
│ │ mig.strategy: mixed │ │
│ │ # GPU configuration │ │
│ │ +gpu: │ │
│ │ - devices: [0,1,2,3] │ │
│ │ migEnabled: true │ │
│ │ migDevices: │ │
│ │ "1g.5gb": 2 # 2 x 5GB + 5GB │ │
│ │ "2g.10gb": 1 # 1 x 10GB + 10GB │ │
│ │ "3g.20gb": 1 # 1 x 20GB │ │
│ │ "7g.40gb": 0 # 0 x 40GB (full) │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 2. 应用 MIG 配置: │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ # 查看当前 MIG 配置 │ │
│ │ nvidia-smi mig -lgip │ │
│ │ │ │
│ │ # 应用新配置 │ │
│ │ kubectl label nodes <node-name> \ │ │
│ │ nvidia.com/mig.config=all-balanced │ │
│ │ │ │
│ │ # 验证配置 │ │
│ │ nvidia-smi │ │
│ │ +-----------------------------------------------------------------------------+| │
│ │ | GPU 0: A100-SXM4-40GB | || │
│ │ | MIG-Gifted Instance 5GiB | || │
│ │ +-----------------------------------------------------------------------------+| │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 3. K8s 中使用 MIG: │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ # 请求 MIG 切片 │ │
│ │ apiVersion: v1 │ │
│ │ kind: Pod │ │
│ │ spec: │ │
│ │ containers: │ │
│ │ - name: inference │ │
│ │ resources: │ │
│ │ limits: │ │
│ │ nvidia.com/gpu: 1 │ │
│ │ # 或者使用 MIG 实例: │ │
│ │ nvidia.com/mig-1g.5gb: 1 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘4.3 Time-Sharing GPU 共享
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ Time-Sharing GPU 共享 │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ CUDA MPS (Multi-Process Service): │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 原理: │ │
│ │ 多个进程共享同一个 GPU 上下文,按时间片轮转执行 │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ GPU Timeline: │ │ │
│ │ │ │ │ │
│ │ │ ┌────────┐┌────────┐┌────────┐┌────────┐┌────────┐┌────────┐ │ │ │
│ │ │ │ Proc A ││ Proc B ││ Proc A ││ Proc C ││ Proc B ││ Proc A │ │ │ │
│ │ │ │ 10ms ││ 10ms ││ 10ms ││ 10ms ││ 10ms ││ 10ms │ │ │ │
│ │ │ └────────┘└────────┘└────────┘└────────┘└────────┘└────────┘ │ │ │
│ │ │ │ │ │
│ │ │ Procs A, B, C 交替使用 GPU │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ MPS DaemonSet 配置: │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ kind: DaemonSet │ │
│ │ metadata: │ │
│ │ name: nvidia-device-plugin-mps │ │
│ │ namespace: volcano-system │ │
│ │ spec: │ │
│ │ template: │ │
│ │ spec: │ │
│ │ hostPID: true │ │
│ │ containers: │ │
│ │ - name: mps │ │
│ │ image: nvidia/cuda:11.8.0-base-ubuntu22.04 │ │
│ │ securityContext: │ │
│ │ capabilities: │ │
│ │ add: [SYS_ADMIN] │ │
│ │ command: │ │
│ │ - bash │ │
│ │ - -c │ │
│ │ - | │ │
│ │ nvidia-cuda-mps-control -d 2>/dev/null || true │ │
│ │ resources: │ │
│ │ limits: │ │
│ │ memory: 64Mi │ │
│ │ cpu: 100m │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 容器使用 MPS: │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ apiVersion: v1 │ │
│ │ kind: Pod │ │
│ │ spec: │ │
│ │ containers: │ │
│ │ - name: inference │ │
│ │ env: │ │
│ │ - name: CUDA_MPS_ACTIVE_THREAD_PERCENTAGE │ │
│ │ value: "33" # 占用 1/3 算力 │ │
│ │ resources: │ │
│ │ limits: │ │
│ │ nvidia.com/gpu: 1 │ │
│ │ command: ["/bin/bash", "-c", "nvidia-cuda-mps-control; python ..."] │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘五、优先级与抢占策略
5.1 优先级队列设计
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 优先级队列设计 │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 队列结构: │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Priority 100: production-training (产品训练) │ │ │
│ │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │
│ │ │ │ weight: 40 │ │ │ │
│ │ │ │ guarantee: 50% GPU │ │ │ │
│ │ │ │ reclaimable: false │ │ │ │
│ │ │ └────────────────────────────────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Priority 80: experiment-training (实验训练) │ │ │
│ │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │
│ │ │ │ weight: 30 │ │ │ │
│ │ │ │ guarantee: 30% GPU │ │ │ │
│ │ │ │ reclaimable: true │ │ │ │
│ │ │ └────────────────────────────────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Priority 50: offline-batch (离线批处理) │ │ │
│ │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │
│ │ │ │ weight: 20 │ │ │ │
│ │ │ │ guarantee: 10% GPU │ │ │ │
│ │ │ │ reclaimable: true │ │ │ │
│ │ │ │ minResources: {nvidia.com/gpu: 1} │ │ │ │
│ │ │ └────────────────────────────────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ Priority 20: dev-test (开发测试) │ │ │
│ │ │ ┌────────────────────────────────────────────────────────────────┐ │ │ │
│ │ │ │ weight: 10 │ │ │ │
│ │ │ │ reclaimable: true │ │ │ │
│ │ │ │ state: Closed (不允许新任务) │ │ │ │
│ │ │ └────────────────────────────────────────────────────────────────┘ │ │ │
│ │ └─────────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 资源分配示意 (假设100卡): │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Total: 100 GPU │ │
│ │ ═════════════════════════════════════════════════════════════════════ │ │
│ │ │ │
│ │ Guarantee (最小保障): │ │
│ │ ┌────────────────┬────────────────┬────────────────┬──────────────┐ │ │
│ │ │ 50 GPU │ 30 GPU │ 10 GPU │ 10 GPU │ │ │
│ │ │ production │ experiment │ offline-batch │ dev-test │ │ │
│ │ └────────────────┴────────────────┴────────────────┴──────────────┘ │ │
│ │ │ │
│ │ 实际使用 (动态): │ │
│ │ ┌────────────────┬────────────────┬────────────────┬──────────────┐ │ │
│ │ │ 70 GPU │ 20 GPU │ 5 GPU │ 5 GPU │ │ │
│ │ │ production │ experiment │ offline-batch │ dev-test │ │ │
│ │ └────────────────┴────────────────┴────────────────┴──────────────┘ │ │
│ │ ▲ │ │
│ │ │ │ │
│ │ 空闲 5 GPU 可分配 │ │
│ │ │ │
│ │ 抢占场景: │ │
│ │ production 紧急任务需要 10 GPU,只能从 experiment 抢占 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘5.2 抢占策略配置
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 抢占策略配置 │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Volcano 抢占配置: │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ apiVersion: scheduling.volcano.sh/v1beta1 │ │
│ │ kind: Queue │ │
│ │ metadata: │ │
│ │ name: production-queue │ │
│ │ spec: │ │
│ │ weight: 50 │ │
│ │ reclaimable: true # 允许抢占其他队列 │ │
│ │ capability: │ │
│ │ nvidia.com/gpu: 100 │ │
│ │ guarantee: │ │
│ │ nvidia.com/gpu: 40 # 最小保障 │ │
│ │ status: │ │
│ │ state: Open │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ PodGroup 优先级配置: │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ apiVersion: scheduling.volcano.sh/v1beta1 │ │
│ │ kind: PodGroup │ │
│ │ metadata: │ │
│ │ name: urgent-training-job │ │
│ │ namespace: default │ │
│ │ labels: │ │
│ │ queue: production-queue │ │
│ │ spec: │ │
│ │ minMember: 8 # 需要8个Worker │ │
│ │ minAvailable: 4 # 最少4个才能运行 │ │
│ │ priority: 100 # 高优先级 │ │
│ │ queue: production-queue │ │
│ │ ttl: 3600 # 等待超时(秒) │ │
│ │ minZoneUsage: # 区域分布 │ │
│ │ nvidia.com/gpu: 2 │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 抢占策略: │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 抢占条件: │ │
│ │ 1. 新任务的 priority > 正在运行任务的 priority │ │
│ │ 2. 新任务所属队列有权使用被抢占的资源 │ │
│ │ 3. 被抢占任务不是 Gang Scheduling 的必需成员 │ │
│ │ 4. 抢占后,被抢占任务仍能保持 Gang完整性 │ │
│ │ │ │
│ │ 抢占顺序: │ │
│ │ 1. 同队列: 按 priority 从低到高抢占 │ │
│ │ 2. 跨队列: 按 queue priority 从低到高抢占 │ │
│ │ 3. 优先抢占运行时间最长的任务 │ │
│ │ │ │
│ │ 抢占限制: │ │
│ │ 1. 单次抢占不超过总资源的 30% │ │
│ │ 2. 单任务抢占不超过其请求的 50% │ │
│ │ 3. 被抢占任务会被标记,等待调度器重新分配 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘六、完整配置示例
6.1 Volcano 集群配置
volcano-cluster-config.yaml - 集群级配置
# Volcano 集群配置
# 用于定义全局调度策略和资源分配
---
# 命名空间隔离
apiVersion: v1
kind: Namespace
metadata:
name: volcano-training
labels:
volcano.sh/queue-namespace: "true"
---
apiVersion: v1
kind: Namespace
metadata:
name: volcano-inference
labels:
volcano.sh/queue-namespace: "true"
---
# GPU 节点配置
apiVersion: v1
kind: ConfigMap
metadata:
name: volcano-scheduler-config
namespace: volcano-system
data:
# 调度器配置
volcano.conf: |
# 调度器名称
scheduler.name: volcano-scheduler
# 调度周期配置
scheduler.hierarchical.pods: ""
# 队列发现
queue.conf: |
queue.plugin: [gang, drf, podgroup, binpack, priority, task-topology]
# Gang 调度配置
gang.scheduling.together: true
gang.scheduling.enable: true
# 抢占配置
reclaim.conf: |
reclaimable: true
allowNext: true
# 优先级配置
priority.conf: |
defaultPriority: 1
classAndPriors:
- class: High
priority: 100
- class: Normal
priority: 50
- class: Low
priority: 10
# 调度插件配置
gpu-topology-config.json: |
{
"gpuTopologyAwareness": true,
"nvlinkPreferred": true,
"pciePreferred": false,
" nicTopology": {
"allowedDDGZZones": ["同一节点", "同一交换机"],
"intraNodePenalty": 0,
"interNodePenalty": 50
}
}
---
# GPU 节点污点和标签
apiVersion: v1
kind: Node
metadata:
name: gpu-node-1
labels:
# GPU 标签
nvidia.com/gpu.model: "A100-SXM4-40GB"
nvidia.com/gpu.count: "4"
nvidia.com/gpu.present: "true"
# Volcano 标签
volcano.sh/gpu-memory: "160Gi"
volcano.sh/gpu-core: "4"
# 拓扑标签
volcano.sh/gpu-topology: "node-1"
# 用途标签
volcano.sh/node-pool: "training-gpu"
annotations:
# MIG 配置
nvidia.com/device.config: '{"mode":"mixed","devices":[1,2,3]}'
---
# 类似配置其他节点...
---
# GPU 资源定义
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
name: v1.volcano.sh
# ... (省略)6.2 Queue 队列配置
volcano-queues.yaml - 队列配置
# Volcano 队列配置
# 定义多租户资源分配和调度策略
---
# 高优先级队列 - 产品训练
apiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
name: production-training
annotations:
description: "产品训练任务,高优先级"
spec:
# 权重 (与其他队列竞争时)
weight: 40
# 资源能力上限
capability:
cpu: "200"
memory: 800Gi
nvidia.com/gpu: 80
# 最小保障 (即使没有高优先级任务也保留)
guarantee:
cpu: "100"
memory: 400Gi
nvidia.com/gpu: 40
# 是否可被抢占
reclaimable: false
# 队列状态
state: Open
---
# 实验队列 - 算法实验
apiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
name: experiment-training
annotations:
description: "算法实验任务,中优先级"
spec:
weight: 30
capability:
cpu: "100"
memory: 400Gi
nvidia.com/gpu: 40
guarantee:
cpu: "50"
memory: 200Gi
nvidia.com/gpu: 20
# 可被高优先级队列抢占
reclaimable: true
state: Open
---
# 离线批处理队列
apiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
name: offline-batch
annotations:
description: "离线批处理任务,低优先级,可抢占"
spec:
weight: 20
capability:
cpu: "50"
memory: 200Gi
nvidia.com/gpu: 20
guarantee:
nvidia.com/gpu: 0 # 无保障,利用空闲资源
reclaimable: true
# 最大资源限制
minResources:
nvidia.com/gpu: 1 # 单任务最少1卡
state: Open
---
# 开发测试队列 (限制使用时间)
apiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
name: dev-test
annotations:
description: "开发测试使用,不允许生产任务"
spec:
weight: 10
capability:
cpu: "20"
memory: 80Gi
nvidia.com/gpu: 8
reclaimable: true
# 限制可用时间 (9:00-18:00)
timePolicies:
- weekdays: "1-5" # 周一到周五
hours: "9-18" # 9点到18点
state: Open
---
# 推理服务队列
apiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
name: inference-service
annotations:
description: "推理服务,高可用"
spec:
weight: 50
capability:
cpu: "100"
memory: 400Gi
nvidia.com/gpu: 20
guarantee:
cpu: "50"
memory: 200Gi
nvidia.com/gpu: 10
reclaimable: false
state: Open
---
# 队列绑定 (将用户/团队绑定到队列)
apiVersion: volcano.sh/v1beta1
kind: QueueBinding
metadata:
name: team-algorithm-production
spec:
queue: production-training
# 通过标签匹配任务
selector:
matchLabels:
team: algorithm
env: production6.3 训练任务配置
training-job.yaml - 训练任务配置
# 分布式训练任务 (PyTorch)
apiVersion: batch.volcano.sh/v1alpha1
kind: Job
metadata:
name: bert-finetune-training
namespace: default
labels:
# 队列选择
queue: production-training
team: algorithm
env: production
model: bert
task-type: nlp-training
spec:
# 优先级 (队列内)
priority: 100
# 最小 Worker 数 (Gang Scheduling)
minMember: 4
# 最小可用数
minAvailable: 4
# 队列
queue: production-training
# 任务TTL
ttlSeconds: 86400 # 24小时后自动清理
# Gang 调度策略
policies:
- event: PodFailed
action: RestartJob
- event: QueueUnderused
action: KillJob
# 任务定义
tasks:
# Chief (Master) Worker
- name: chief
replicas: 1
template:
metadata:
labels:
volcano.sh/task-spec: chief
annotations:
# GPU 拓扑偏好
volcano.sh/preferred-topology: "full"
spec:
# Gang 调度
schedulerName: volcano
restartPolicy: Never
# 资源请求
containers:
- name: training
image: pytorch/pytorch:2.0.1-cuda11.7-cudnn8-runtime
imagePullPolicy: IfNotPresent
command:
- bash
- -c
- |
# NCCL 配置
export NCCL_DEBUG=INFO
export NCCL_IB_DISABLE=0
export NCCL_NET_GDR_LEVEL=2
# 启动 Chief
python -m torch.distributed.run \
--nnodes=4 \
--nproc_per_node=1 \
--master_addr=$(POD_IP) \
--master_port=29500 \
--node_rank=$(POD_INDEX) \
--rdzv_id=123456 \
--rdzv_backend=c10d \
train.py \
--epochs 100 \
--batch-size 32 \
--learning-rate 2e-5
env:
# Volcano 注入的环境变量
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: POD_INDEX
valueFrom:
fieldRef:
fieldPath: metadata.annotations['volcano.sh/task-spec-index']
- name: NCCL_DEBUG
value: "INFO"
- name: DATA_PATH
value: "/data/bert/corpus"
- name: MODEL_PATH
value: "/outputs/model"
resources:
requests:
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "1"
limits:
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "1"
volumeMounts:
- name: data
mountPath: /data
- name: output
mountPath: /outputs
# 健康检查
livenessProbe:
exec:
command:
- nvidia-smi
initialDelaySeconds: 60
periodSeconds: 300
volumes:
- name: data
persistentVolumeClaim:
claimName: training-data-pvc
- name: output
persistentVolumeClaim:
claimName: training-output-pvc
# 节点亲和性 (同节点调度)
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
volcano.sh/job-name: bert-finetune-training
topologyKey: kubernetes.io/hostname
# 容忍
tolerations:
- key: "gpu"
operator: "Exists"
effect: "NoSchedule"
# Worker
- name: worker
replicas: 3
template:
metadata:
labels:
volcano.sh/task-spec: worker
spec:
schedulerName: volcano
restartPolicy: Never
containers:
- name: training
image: pytorch/pytorch:2.0.1-cuda11.7-cudnn8-runtime
imagePullPolicy: IfNotPresent
command:
- bash
- -c
- |
export NCCL_DEBUG=INFO
python -m torch.distributed.run \
--nnodes=4 \
--nproc_per_node=1 \
--master_addr=$(CHIEF_SERVICE) \
--master_port=29500 \
--node_rank=$(POD_INDEX) \
--rdzv_id=123456 \
--rdzv_backend=c10d \
train.py \
--epochs 100 \
--batch-size 32
env:
- name: CHIEF_SERVICE
value: "bert-finetune-training-chief-0"
- name: POD_INDEX
valueFrom:
fieldRef:
fieldPath: metadata.annotations['volcano.sh/task-spec-index']
resources:
requests:
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "1"
limits:
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "1"
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: training-data-pvc
tolerations:
- key: "gpu"
operator: "Exists"
effect: "NoSchedule"
---
# 推理服务 (实时服务)
apiVersion: apps/v1
kind: Deployment
metadata:
name: bert-inference-service
namespace: inference
labels:
queue: inference-service
model: bert
version: v1.0.0
spec:
replicas: 3
selector:
matchLabels:
app: bert-inference
template:
metadata:
labels:
app: bert-inference
queue: inference-service
annotations:
# 启用 GPU 共享
nvidia.com/device.compute-mode: "default"
spec:
schedulerName: volcano
containers:
- name: inference
image: bert-inference:v1.0.0
imagePullPolicy: Always
ports:
- name: http
containerPort: 8000
# 资源请求 (MIG 或共享)
resources:
requests:
cpu: "4"
memory: 16Gi
nvidia.com/gpu: "1" # 或使用 MIG: nvidia.com/mig-1g.5gb: 1
limits:
cpu: "8"
memory: 32Gi
nvidia.com/gpu: "1"
env:
- name: MODEL_PATH
value: "/models/bert"
- name: MAX_BATCH_SIZE
value: "32"
- name: GPU_MEMORY_FRACTION
value: "0.8"
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 60
periodSeconds: 30
tolerations:
- key: "gpu"
operator: "Exists"
effect: "NoSchedule"
---
# HPA 自动扩缩容
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: bert-inference-hpa
namespace: inference
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: bert-inference-service
minReplicas: 2
maxReplicas: 10
metrics:
- type: External
external:
metric:
name: gpu_utilization_avg
selector:
matchLabels:
deployment: bert-inference-service
target:
type: AverageValue
averageValue: "70" # GPU 利用率平均 70% 时扩容6.4 GPU 监控配置
gpu-monitoring.yaml - GPU 监控配置
# GPU 监控配置
# 使用 DCGM Exporter + Prometheus + Grafana
---
# DCGM Exporter DaemonSet
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: gpu-monitoring
namespace: volcano-system
labels:
app: gpu-monitoring
spec:
selector:
matchLabels:
app: gpu-monitoring
template:
metadata:
labels:
app: gpu-monitoring
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9400"
prometheus.io/path: "/metrics"
spec:
# 容忍污点
tolerations:
- key: "gpu"
operator: "Exists"
effect: "NoSchedule"
containers:
- name: exporter
image: nvcr.io/nvidia/k8s/dcgm-exporter:3.1.5-3.1.4-ubuntu20.04
securityContext:
capabilities:
add: ["SYS_ADMIN"]
env:
- name: DCGM_EXPORTER_INTERVAL
value: "15" # 采集间隔 15秒
- name: DCGM_EXPORTER_COLLECTORS
value: "/etc/dcgm-exporter/dcgm-metrics.csv"
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 500m
memory: 128Mi
volumeMounts:
- name: pod-gpu-metrics
mountPath: /etc/dcgm-exporter/
- name: nvidia-metrics
mountPath: /var/lib/dcgm/
volumes:
- name: pod-gpu-metrics
configMap:
name: dcgm-exporter-config
- name: nvidia-metrics
hostPath:
path: /var/lib/dcgm/
---
# Prometheus 配置
apiVersion: v1
kind: ConfigMap
metadata:
name: prometheus-gpu-rules
namespace: monitoring
data:
gpu-alerts.yaml: |
groups:
- name: gpu-alerts
interval: 30s
rules:
# GPU 利用率低告警
- alert: GPUUtilizationLow
expr: |
DCGM_FI_DEV_GPU_UTIL > 0 and
DCGM_FI_DEV_GPU_UTIL < 10
for: 30m
labels:
severity: warning
annotations:
summary: "GPU 利用率持续低于 10%"
description: "节点 {{ $labels.instance }} 的 GPU {{ $labels.gpu }} 利用率为 {{ $value }}%"
# GPU 内存使用率高
- alert: GPUMemoryUsageHigh
expr: |
DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_FREE > 0.9
for: 5m
labels:
severity: warning
annotations:
summary: "GPU 内存使用率超过 90%"
description: "节点 {{ $labels.instance }} 的 GPU {{ $labels.gpu }} 内存使用率为 {{ $value | humanizePercentage }}"
# GPU 温度高
- alert: GPUTemperatureHigh
expr: DCGM_FI_DEV_GPU_TEMP > 85
for: 5m
labels:
severity: critical
annotations:
summary: "GPU 温度超过 85°C"
description: "节点 {{ $labels.instance }} 的 GPU {{ $labels.gpu }} 温度为 {{ $value }}°C"
# GPU 错误
- alert: GPUDeviceErrors
expr: DCGM_FI_DEV_XID_ERRORS > 0
for: 1m
labels:
severity: critical
annotations:
summary: "GPU 发生 XID 错误"
description: "节点 {{ $labels.instance }} 的 GPU {{ $labels.gpu }} 发生错误,错误码: {{ $value }}"
# GPU 不可用
- alert: GPUNotAvailable
expr: DCGM_FI_DEV_COUNT == 0
for: 1m
labels:
severity: critical
annotations:
summary: "GPU 设备不可用"
description: "节点 {{ $labels.instance }} 没有可用的 GPU 设备"
---
# Grafana Dashboard ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-gpu-dashboard
namespace: monitoring
labels:
grafana_dashboard: "1"
data:
gpu-overview.json: |
{
"dashboard": {
"title": "GPU Cluster Overview",
"panels": [
{
"title": "GPU Utilization",
"type": "graph",
"targets": [
{
"expr": "avg(DCGM_FI_DEV_GPU_UTIL) by (instance)",
"legendFormat": "{{instance}}"
}
]
},
{
"title": "GPU Memory Usage",
"type": "graph",
"targets": [
{
"expr": "DCGM_FI_DEV_FB_USED / DCGM_FI_DEV_FB_FREE * 100",
"legendFormat": "{{instance}} - GPU {{gpu}}"
}
]
},
{
"title": "GPU Temperature",
"type": "gauge",
"targets": [
{
"expr": "DCGM_FI_DEV_GPU_TEMP",
"legendFormat": "{{instance}} - GPU {{gpu}}"
}
],
"fieldConfig": {
"defaults": {
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "green", "value": null},
{"color": "yellow", "value": 75},
{"color": "red", "value": 85}
]
},
"unit": "celsius"
}
}
},
{
"title": "GPU Allocation",
"type": "piechart",
"targets": [
{
"expr": "count(DCGM_FI_DEV_GPU_UTIL) by (queue)",
"legendFormat": "{{queue}}"
}
]
}
]
}
}七、Python 提交训练任务客户端
submit_training_job.py - 训练任务提交客户端
"""
训练任务提交客户端
封装 Volcano Job 提交逻辑
Author: Infrastructure Team
Date: 2025-10-15
"""
import os
import time
import json
import yaml
import argparse
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field, asdict
from enum import Enum
import logging
import yaml
from kubernetes import client, config
from kubernetes.client.rest import ApiException
import requests
logger = logging.getLogger(__name__)
class TaskPriority(Enum):
"""任务优先级"""
LOW = 10
NORMAL = 50
HIGH = 80
CRITICAL = 100
@dataclass
class ResourceSpec:
"""资源规格"""
cpu: str = "2"
memory: str = "8Gi"
gpu: int = 1
gpu_type: str = "nvidia" # nvidia, amd
storage: str = "50Gi"
@dataclass
class TrainingJobConfig:
"""训练任务配置"""
name: str
image: str
command: List[str]
replicas: int = 1
# 资源配置
resources: ResourceSpec = field(default_factory=ResourceSpec)
# 调度配置
queue: str = "default"
priority: TaskPriority = TaskPriority.NORMAL
min_available: int = None # Gang Scheduling 最小可用数
# 环境变量
env_vars: Dict[str, str] = field(default_factory=dict)
# 存储
data_volume: str = None
output_volume: str = None
# 其他
labels: Dict[str, str] = field(default_factory=dict)
annotations: Dict[str, str] = field(default_factory=dict)
node_selector: Dict[str, str] = field(default_factory=dict)
tolerations: List[Dict] = field(default_factory=list)
# TTL
ttl_seconds: int = 86400 # 24小时
def __post_init__(self):
"""初始化后处理"""
if self.min_available is None:
self.min_available = self.replicas
class VolcanoJobSubmitter:
"""
Volcano 训练任务提交器
使用 Python 客户端提交分布式训练任务到 Volcano 调度器
"""
def __init__(
self,
namespace: str = "default",
kubeconfig: str = None,
context: str = None
):
"""
初始化提交器
Args:
namespace: K8s 命名空间
kubeconfig: kubeconfig 路径(默认使用 ~/.kube/config)
context: K8s context 名称
"""
self.namespace = namespace
# 加载 K8s 配置
try:
if kubeconfig:
config.load_kube_config(
config_file=kubeconfig,
context=context
)
else:
config.load_incluster_config()
except Exception as e:
logger.warning(f"无法加载 K8s 配置: {e}")
raise
self.batch_api = client.BatchV1Api()
self.core_api = client.CoreV1Api()
self.custom_api = client.CustomObjectsApi()
# 默认配置
self.default_image = "pytorch/pytorch:2.0.1-cuda11.7-cudnn8-runtime"
logger.info(f"VolcanoJobSubmitter 初始化完成,namespace: {namespace}")
def submit_job(self, config: TrainingJobConfig) -> Dict:
"""
提交训练任务
Args:
config: 任务配置
Returns:
提交结果
"""
job_name = self._generate_job_name(config.name)
# 构建 Volcano Job
job = self._build_volcano_job(config, job_name)
try:
# 提交到 K8s
self.custom_api.create_namespaced_custom_object(
group="batch.volcano.sh",
version="v1alpha1",
namespace=self.namespace,
plural="jobs",
body=job
)
logger.info(f"训练任务提交成功: {job_name}")
return {
"success": True,
"job_name": job_name,
"namespace": self.namespace,
"status": "pending"
}
except ApiException as e:
logger.error(f"提交任务失败: {e}")
return {
"success": False,
"error": str(e)
}
def submit_from_yaml(self, yaml_path: str) -> Dict:
"""
从 YAML 文件提交任务
Args:
yaml_path: YAML 文件路径
Returns:
提交结果
"""
with open(yaml_path, 'r') as f:
job = yaml.safe_load(f)
try:
self.custom_api.create_namespaced_custom_object(
group="batch.volcano.sh",
version="v1alpha1",
namespace=job.get('metadata', {}).get('namespace', 'default'),
plural="jobs",
body=job
)
job_name = job.get('metadata', {}).get('name')
logger.info(f"任务提交成功: {job_name}")
return {
"success": True,
"job_name": job_name,
"status": "pending"
}
except ApiException as e:
return {
"success": False,
"error": str(e)
}
def get_job_status(self, job_name: str) -> Dict:
"""
获取任务状态
Args:
job_name: 任务名称
Returns:
状态信息
"""
try:
job = self.custom_api.get_namespaced_custom_object(
group="batch.volcano.sh",
version="v1alpha1",
namespace=self.namespace,
plural="jobs",
name=job_name
)
status = job.get('status', {})
# 解析状态
state = status.get('state', {})
phase = state.get('phase', 'Pending')
return {
"job_name": job_name,
"phase": phase,
"running": status.get('running', 0),
"succeeded": status.get('succeeded', 0),
"failed": status.get('failed', 0),
"pending": status.get('pending', 0),
"total_tasks": status.get('totalTasks', 0)
}
except ApiException as e:
if e.status == 404:
return {
"job_name": job_name,
"phase": "NotFound"
}
raise
def list_jobs(
self,
queue: str = None,
status: str = None,
label_selector: str = None
) -> List[Dict]:
"""
列出任务
Args:
queue: 按队列过滤
status: 按状态过滤
label_selector: 标签选择器
Returns:
任务列表
"""
field_selector = None
if queue:
field_selector = f"spec.queue={queue}"
try:
jobs = self.custom_api.list_namespaced_custom_object(
group="batch.volcano.sh",
version="v1alpha1",
namespace=self.namespace,
plural="jobs",
field_selector=field_selector,
label_selector=label_selector
)
result = []
for job in jobs.get('items', []):
status_info = job.get('status', {})
state = status_info.get('state', {}).get('phase', 'Unknown')
# 状态过滤
if status and state != status:
continue
result.append({
"name": job['metadata']['name'],
"namespace": job['metadata']['namespace'],
"queue": job['spec'].get('queue', 'default'),
"priority": job['spec'].get('priority', 0),
"phase": state,
"created_at": job['metadata'].get('creationTimestamp'),
"min_available": job['spec'].get('minAvailable', 0),
"running": status_info.get('running', 0)
})
return result
except ApiException as e:
logger.error(f"列出任务失败: {e}")
return []
def cancel_job(self, job_name: str) -> bool:
"""
取消任务
Args:
job_name: 任务名称
Returns:
是否成功
"""
try:
self.custom_api.delete_namespaced_custom_object(
group="batch.volcano.sh",
version="v1alpha1",
namespace=self.namespace,
plural="jobs",
name=job_name,
body=client.V1DeleteOptions()
)
logger.info(f"任务已取消: {job_name}")
return True
except ApiException as e:
logger.error(f"取消任务失败: {e}")
return False
def get_job_logs(
self,
job_name: str,
task_name: str = None,
tail_lines: int = 100
) -> str:
"""
获取任务日志
Args:
job_name: 任务名称
task_name: Task 名称(可选,默认获取第一个 task)
tail_lines: 返回最近 N 行
Returns:
日志内容
"""
# 获取 Pod 名称
if task_name:
pod_name = f"{job_name}-{task_name}-0"
else:
# 获取第一个 Pod
pods = self.core_api.list_namespaced_pod(
namespace=self.namespace,
label_selector=f"volcano.sh/job-name={job_name}"
)
if not pods.items:
return "No pods found"
pod_name = pods.items[0].metadata.name
try:
logs = self.core_api.read_namespaced_pod_log(
name=pod_name,
namespace=self.namespace,
tail_lines=tail_lines
)
return logs
except ApiException as e:
return f"获取日志失败: {e}"
def watch_job(
self,
job_name: str,
timeout: int = 3600,
poll_interval: int = 5
) -> Dict:
"""
等待任务完成
Args:
job_name: 任务名称
timeout: 超时时间(秒)
poll_interval: 轮询间隔(秒)
Returns:
最终状态
"""
start_time = time.time()
while True:
status = self.get_job_status(job_name)
phase = status.get('phase')
logger.info(f"任务状态: {phase}")
if phase in ['Completed', 'Succeeded']:
return {"status": "success", **status}
if phase in ['Failed', 'Terminated']:
return {"status": "failed", **status}
# 超时检查
if time.time() - start_time > timeout:
return {"status": "timeout", **status}
time.sleep(poll_interval)
def _build_volcano_job(
self,
config: TrainingJobConfig,
job_name: str
) -> Dict:
"""构建 Volcano Job 对象"""
# 清理任务名(K8s 不允许某些字符)
job_name = job_name.replace('_', '-')
# 构建 Task 模板
task_template = {
"replicas": config.replicas,
"template": {
"spec": {
"restartPolicy": "Never",
"containers": [
{
"name": "training",
"image": config.image or self.default_image,
"command": config.command,
"env": self._build_env_vars(config.env_vars),
"resources": {
"requests": {
"cpu": config.resources.cpu,
"memory": config.resources.memory,
f"{config.resources.gpu_type}.com/gpu": str(config.resources.gpu)
},
"limits": {
"cpu": config.resources.cpu,
"memory": config.resources.memory,
f"{config.resources.gpu_type}.com/gpu": str(config.resources.gpu)
}
},
"volumeMounts": self._build_volume_mounts(config)
}
],
"volumes": self._build_volumes(config),
"tolerations": config.tolerations or [
{"key": "gpu", "operator": "Exists", "effect": "NoSchedule"}
]
}
}
}
# 构建 Job
job = {
"apiVersion": "batch.volcano.sh/v1alpha1",
"kind": "Job",
"metadata": {
"name": job_name,
"namespace": self.namespace,
"labels": {
"app": "volcano-training",
**config.labels
},
"annotations": config.annotations
},
"spec": {
"queue": config.queue,
"priority": config.priority.value,
"minMember": config.min_available,
"minAvailable": config.min_available,
"tasks": [task_template],
"ttlSecondsAfterFinished": config.ttl_seconds
}
}
return job
def _build_env_vars(self, env_vars: Dict[str, str]) -> List[Dict]:
"""构建环境变量"""
result = []
# 添加默认环境变量
default_vars = {
"PYTHONUNBUFFERED": "1",
"NVIDIA_VISIBLE_DEVICES": "all"
}
for key, value in {**default_vars, **env_vars}.items():
result.append({
"name": key,
"value": str(value)
})
return result
def _build_volume_mounts(self, config: TrainingJobConfig) -> List[Dict]:
"""构建存储挂载"""
mounts = []
if config.data_volume:
mounts.append({
"name": "data",
"mountPath": "/data"
})
if config.output_volume:
mounts.append({
"name": "output",
"mountPath": "/outputs"
})
return mounts
def _build_volumes(self, config: TrainingJobConfig) -> List[Dict]:
"""构建存储卷"""
volumes = []
if config.data_volume:
volumes.append({
"name": "data",
"persistentVolumeClaim": {
"claimName": config.data_volume
}
})
if config.output_volume:
volumes.append({
"name": "output",
"persistentVolumeClaim": {
"claimName": config.output_volume
}
})
return volumes
def _generate_job_name(self, name: str) -> str:
"""生成唯一的任务名称"""
timestamp = int(time.time())
short_id = timestamp % 100000
# 清理名称,只保留字母、数字和短横线
clean_name = ''.join(c if c.isalnum() or c == '-' else '-' for c in name)
return f"{clean_name[:50]}-{short_id}"
def main():
"""命令行入口"""
parser = argparse.ArgumentParser(description="Volcano 训练任务提交工具")
# 基本参数
parser.add_argument("--name", "-n", required=True, help="任务名称")
parser.add_argument("--image", "-i", required=True, help="训练镜像")
parser.add_argument("--command", "-c", nargs="+", required=True, help="启动命令")
# 资源配置
parser.add_argument("--replicas", "-r", type=int, default=1, help="Worker 数量")
parser.add_argument("--cpu", type=str, default="2", help="CPU 请求")
parser.add_argument("--memory", type=str, default="8Gi", help="内存请求")
parser.add_argument("--gpu", type=int, default=1, help="GPU 数量")
# 调度配置
parser.add_argument("--queue", "-q", default="default", help="队列名称")
parser.add_argument("--priority", "-p", type=str, default="normal",
choices=["low", "normal", "high", "critical"],
help="任务优先级")
# 命名空间
parser.add_argument("--namespace", "-ns", default="default", help="命名空间")
# 操作
parser.add_argument("--submit", action="store_true", help="提交任务")
parser.add_argument("--status", action="store_true", help="查看状态")
parser.add_argument("--logs", action="store_true", help="查看日志")
parser.add_argument("--cancel", action="store_true", help="取消任务")
parser.add_argument("--watch", action="store_true", help="等待任务完成")
args = parser.parse_args()
# 创建提交器
submitter = VolcanoJobSubmitter(namespace=args.namespace)
if args.submit:
# 构建配置
priority_map = {
"low": TaskPriority.LOW,
"normal": TaskPriority.NORMAL,
"high": TaskPriority.HIGH,
"critical": TaskPriority.CRITICAL
}
config = TrainingJobConfig(
name=args.name,
image=args.image,
command=args.command,
replicas=args.replicas,
resources=ResourceSpec(
cpu=args.cpu,
memory=args.memory,
gpu=args.gpu
),
queue=args.queue,
priority=priority_map[args.priority]
)
# 提交
result = submitter.submit_job(config)
print(json.dumps(result, indent=2))
elif args.status:
result = submitter.get_job_status(args.name)
print(json.dumps(result, indent=2))
elif args.logs:
logs = submitter.get_job_logs(args.name)
print(logs)
elif args.cancel:
success = submitter.cancel_job(args.name)
print(f"取消{'成功' if success else '失败'}")
elif args.watch:
result = submitter.watch_job(args.name)
print(json.dumps(result, indent=2))
if __name__ == "__main__":
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
main()八、总结与最佳实践
8.1 调度策略总结
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ 调度策略总结 │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ 场景与策略对照表 │ │
│ ├─────────────────────────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ 训练任务 (大规模/长时间): │ │
│ │ ├── Gang Scheduling: 必须开启 │ │
│ │ ├── GPU 拓扑感知: 必须开启 (NVLink > PCIe > 网络) │ │
│ │ ├── 队列隔离: 必须开启 │ │
│ │ ├── 抢占: 按需开启 │ │
│ │ └── 策略: spread (分散到多节点,避免单点故障) │ │
│ │ │ │
│ │ 推理服务 (小规模/实时): │ │
│ │ ├── Gang Scheduling: 不需要 │ │
│ │ ├── GPU 拓扑感知: 可选 │ │
│ │ ├── GPU 共享: 推荐 (提高利用率) │ │
│ │ ├── 弹性扩缩容: 必须 (KEDA + HPA) │ │
│ │ └── 策略: binpack (集中到少量节点,提高本地性) │ │
│ │ │ │
│ │ 批处理任务 (低优先级): │ │
│ │ ├── Gang Scheduling: 可选 │ │
│ │ ├── 队列: 专用低优先级队列 │ │
│ │ ├── 抢占: 可被抢占 │ │
│ │ └── 策略: 闲时调度 │ │
│ │ │ │
│ │ 开发测试: │ │
│ │ ├── Gang Scheduling: 不需要 │ │
│ │ ├── 队列: 专用测试队列 │ │
│ │ ├── 时间限制: 工作时间可用 │ │
│ │ └── 配额限制: 资源上限 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘8.2 常见问题与解决
8.3 性能优化建议
调度器优化
增加 scheduler 副本数
优化 scheduling interval
使用缓存减少 API 调用
资源分配优化
合理设置 Queue 保障
使用弹性保障 (Elastic Guarantee)
定期清理过期 Job
监控与告警
监控 GPU 利用率
监控调度延迟
设置资源使用告警
📚 相关资源
Volcano 官方文档:https://volcano.sh/zh/docs/
NVIDIA MIG 用户指南:https://docs.nvidia.com/datacenter/dcgm/user-guide/
内部实践文档:[内部Wiki链接]
版本:Volcano v1.8+
评论区