目 录CONTENT

文章目录

GPU 资源调度:K8s + Volcano 在AI场景下的实践

PySuper
2025-07-26 / 0 评论 / 0 点赞 / 3 阅读 / 0 字
温馨提示:
所有牛逼的人都有一段苦逼的岁月。 但是你只要像SB一样去坚持,终将牛逼!!! ✊✊✊

作者:基础设施团队 | 发布日期: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: production

6.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 常见问题与解决

问题

原因

解决方案

Gang Scheduling 死锁

资源不足导致永远无法同时满足所有 Worker

降低 minMember,使用 minAvailable

GPU 碎片化

频繁调度导致资源分散

使用 binpack 策略,定期清理

抢占导致任务失败

抢占过于激进

调整抢占比例限制,设置最小保障

MIG 配置不生效

GPU 驱动版本不支持

检查 nvidia-device-plugin 版本

调度延迟高

Pod 数量过多

优化调度器参数,增加调度周期

队列资源浪费

保障资源过多

使用 reclaimable,合理分配保障

8.3 性能优化建议

  1. 调度器优化

  • 增加 scheduler 副本数

  • 优化 scheduling interval

  • 使用缓存减少 API 调用

  1. 资源分配优化

  • 合理设置 Queue 保障

  • 使用弹性保障 (Elastic Guarantee)

  • 定期清理过期 Job

  1. 监控与告警

  • 监控 GPU 利用率

  • 监控调度延迟

  • 设置资源使用告警


📚 相关资源

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区