news 2026/7/14 12:44:35

GPU 节点亲和性:推理和训练不能放在同一批节点上

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
GPU 节点亲和性:推理和训练不能放在同一批节点上

GPU 节点亲和性:推理和训练不能放在同一批节点上

一、同一批节点上,训练任务和推理服务互相拖垮对方

在搭建 AI 平台初期,运维团队通常倾向于把所有 GPU 节点归入同一个节点池,让 Kubernetes 调度器自行分配。这种简单方案很快暴露出问题:训练任务和推理服务共用同一批节点时,两类负载的资源使用模式截然不同,却共享同一块 GPU 的显存和 PCIe 带宽。

训练任务的特点是显存占用固定且持久,SM 利用率接近 100%,显存带宽持续打满。推理服务则相反,显存占用一部分用于模型常驻,SM 利用率波动大,对延迟敏感。当一个训练 Job 和一个推理 Pod 同时调度到同一节点上时,推理请求会出现不可预测的尾延迟——这不是代码的问题,是物理资源的争抢。

更隐蔽的问题是 GPU 显存碎片化。训练任务释放 GPU 后,可能留下多个小块可用显存,下一个需要大块连续显存的训练任务将调度失败。基础设施不需要漂亮话,混部不是降低成本的捷径,它可能是在制造生产事故。

二、NodeSelector、Node Affinity 与 Taint/Toleration 的调度链路

Kubernetes 的调度器通过过滤(Filter)和打分(Score)两个阶段决定 Pod 落在哪个节点上。对于 GPU 工作负载,需要利用标签体系将节点按用途分组,再通过调度策略将不同工作负载绑定到对应的节点池。

graph TD Scheduler[Kubernetes Scheduler] --> Filter[过滤阶段] subgraph "节点池标签体系" TrainNode[训练节点<br/>gpu-type: training<br/>workload: batch] InferNode[推理节点<br/>gpu-type: inference<br/>workload: serving] MixedNode[混部节点<br/>gpu-type: mixed<br/>workload: both] end Filter --> NodeSelector{NodeSelector /<br/>Node Affinity} Filter --> TaintFilter{Taint 检查} NodeSelector --> TrainNode NodeSelector --> InferNode TaintFilter --> TrainTaint[训练节点 Taint<br/>workload=training:NoSchedule] TaintFilter --> InferTaint[推理节点 Taint<br/>workload=inference:NoSchedule] TrainTaint --> TrainPod[训练 Pod<br/>toleration: workload=training] InferTaint --> InferPod[推理 Pod<br/>toleration: workload=inference] subgraph "调度结果" Result1[训练 Job → 训练节点池 ✓] Result2[推理服务 → 推理节点池 ✓] Result3[无 Toleration Pod → Pending ✗] end TrainNode --> Result1 InferNode --> Result2 TrainTaint --> Result3 InferTaint --> Result3 style TrainNode fill:#ffcdd2,color:#333 style InferNode fill:#c8e6c9,color:#333 style MixedNode fill:#fff9c4,color:#333

NodeSelector 是最简单的匹配方式,但缺少灵活性。Node Affinity 支持requiredDuringSchedulingIgnoredDuringExecution(硬亲和)和preferredDuringSchedulingIgnoredDuringExecution(软亲和)两种模式。对于训练和推理的隔离诉求,硬亲和是必要的——一次不必要的调度可能造成数小时的训练作业失败。

Taint/Toleration 提供了反向排他性。当节点被打上workload=training:NoSchedule的污点时,只有带对应容忍的 Pod 才能调度上来。这比 Node Affinity 更强硬,因为它主动拒绝不符合条件的 Pod,而 Node Affinity 只是引导调度器的选择。

三、生产级调度配置:训练与推理的物理隔离

以下是一套在 GPU 集群中实施训练/推理隔离的完整配置,包含节点标签管理、Taint 策略和 Pod 的调度声明。

// node_pool_manager.go — GPU 节点池管理器 // 职责:根据节点 GPU 型号、用途标签将节点划入不同的节点池 // 注意:标签操作是幂等的,重复应用相同标签不会触发额外的 API 更新 package main import ( "context" "fmt" "os" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" ) // NodePoolType 定义节点池类型 type NodePoolType string const ( PoolTypeTraining NodePoolType = "training" PoolTypeInference NodePoolType = "inference" PoolTypeMixed NodePoolType = "mixed" ) // GPUNodeLabeler 管理 GPU 节点的标签和 Taint type GPUNodeLabeler struct { client kubernetes.Interface dryRun bool retryMax int retryWait time.Duration } // NewGPUNodeLabeler 初始化标签管理器 func NewGPUNodeLabeler(dryRun bool) (*GPUNodeLabeler, error) { config, err := rest.InClusterConfig() if err != nil { // 非集群内运行,尝试使用 kubeconfig kubeconfig := os.Getenv("KUBECONFIG") if kubeconfig == "" { kubeconfig = os.ExpandEnv("$HOME/.kube/config") } config, err = rest.InClusterConfig() if err != nil { // 最终降级到默认配置 config, err = clientcmd.BuildConfigFromFlags("", kubeconfig) if err != nil { return nil, fmt.Errorf("无法加载 Kubernetes 配置: %w", err) } } } clientset, err := kubernetes.NewForConfig(config) if err != nil { return nil, fmt.Errorf("创建 Kubernetes 客户端失败: %w", err) } return &GPUNodeLabeler{ client: clientset, dryRun: dryRun, retryMax: 3, retryWait: 2 * time.Second, }, nil } // AssignNodePool 将节点分配到指定的节点池 // 操作包括:添加工作负载标签、打 Taint、移除冲突标签 func (l *GPUNodeLabeler) AssignNodePool(ctx context.Context, nodeName string, pool NodePoolType) error { // 标签定义:gpu-pool 用于 Node Affinity 匹配 // workload-type 用于监控面板分组 labels := map[string]string{ "gpu-pool": string(pool), "workload-type": string(pool), } if pool == PoolTypeTraining { labels["batch-scheduling"] = "enabled" } // Taint 定义:不同类型使用不同 key // Effect: NoSchedule — 拒绝无 Toleration 的 Pod taints := []map[string]string{ { "key": fmt.Sprintf("workload-%s", pool), "value": "true", "effect": "NoSchedule", }, } if l.dryRun { fmt.Printf("[DRY-RUN] 将节点 %s 分配至 %s 节点池, 标签: %v, Taints: %v\n", nodeName, pool, labels, taints) return nil } // 获取 Node 对象 var lastErr error for retry := 0; retry < l.retryMax; retry++ { node, err := l.client.CoreV1().Nodes().Get(ctx, nodeName, metav1.GetOptions{}) if err != nil { lastErr = fmt.Errorf("获取节点 %s 失败: %w (重试 %d/%d)", nodeName, err, retry+1, l.retryMax) time.Sleep(l.retryWait) continue } // 合并标签 updated := node.DeepCopy() if updated.Labels == nil { updated.Labels = make(map[string]string) } for k, v := range labels { updated.Labels[k] = v } // 合并 Taint(避免重复) taintMap := make(map[string]bool) for _, t := range updated.Spec.Taints { taintMap[t.Key] = true } for _, t := range taints { if !taintMap[t["key"]] { updated.Spec.Taints = append(updated.Spec.Taints, corev1.Taint{ Key: t["key"], Value: t["value"], Effect: corev1.TaintEffect(t["effect"]), }) } } // Patch 更新 patchBytes, err := strategicpatch.CreateTwoWayMergePatch( convertNodeToBytes(node), convertNodeToBytes(updated), corev1.Node{}, ) if err != nil { return fmt.Errorf("生成 Patch 失败: %w", err) } _, err = l.client.CoreV1().Nodes().Patch(ctx, nodeName, types.StrategicMergePatchType, patchBytes, metav1.PatchOptions{}) if err != nil { lastErr = fmt.Errorf("Patch 节点 %s 失败: %w (重试 %d/%d)", nodeName, err, retry+1, l.retryMax) time.Sleep(l.retryWait) continue } fmt.Printf("节点 %s 已分配至 %s 节点池\n", nodeName, pool) return nil } return lastErr }

对应的 Kubernetes 资源 YAML 配置如下。训练 Job 通过 Node Affinity 和 Toleration 确保只调度到训练节点池:

# training-job.yaml — 训练 Job 的调度声明 apiVersion: batch/v1 kind: Job metadata: name: llama2-finetune spec: template: spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: gpu-pool operator: In values: - training - key: nvidia.com/gpu.product operator: In values: - NVIDIA-A100-SXM4-80GB tolerations: - key: workload-training operator: Exists effect: NoSchedule containers: - name: trainer image: pytorch/pytorch:2.1.0-cuda12.1 resources: limits: nvidia.com/gpu: 4

推理服务的配置类似,但 Node Affinity 指向gpu-pool: inference

# inference-deploy.yaml — 推理 Deployment 的调度声明 apiVersion: apps/v1 kind: Deployment metadata: name: llama2-inference spec: replicas: 3 template: spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: gpu-pool operator: In values: - inference tolerations: - key: workload-inference operator: Exists effect: NoSchedule containers: - name: vllm image: vllm/vllm-openai:latest resources: limits: nvidia.com/gpu: 1

四、物理隔离的成本和运维复杂度

将 GPU 节点按工作负载类型物理隔离后,资源利用率会下降。训练节点池在空闲时段 GPU 处于闲置状态,推理节点池在非高峰期同样如此。以 20 个 A100 节点的集群为例,如果按 5:5:10 分为训练 : 推理 : 预留,预留 10 台给突发任务,实际平均利用率可能只有 40%。

降低利用率的对冲策略是引入弹性扩容。训练节点池在低负载时通过 Cluster Autoscaler 缩容,推理节点池在流量波谷时通过 HPA 降低副本数。但这要求训练和推理节点的机器规格一致,否则缩容后无法互相承接溢出负载。

Gang Scheduling 的支持问题。当训练 Job 需要多节点同时调度(如 PyTorch DDP 的 4 节点训练),而训练节点池中只有 2 个可用节点时,调度器无法立即满足需求。需要引入 Volcano 或 Yunikorn 等批调度框架,支持队列等待和最小可用资源预留。

禁用的反模式:不要在推理和训练节点之间共享 GPU 型号但用 PriorityClass 做优先级抢占。PriorityClass + Preemption 在高负载下会导致推理服务被频繁驱逐,服务稳定性不可接受。优先级的差异无法替代物理隔离。

五、总结

GPU 节点按工作负载做物理隔离是保障服务稳定性的基础架构决策。核心落地原则:

  1. 标签体系必须标准化gpu-poolworkload-typenvidia.com/gpu.product三层标签提供从节点池到 GPU 型号的完整路由信息。
  2. Taint/Toleration 用于强制执行,Node Affinity 用于引导调度。两者不是二选一,而是互补。
  3. 弹性缩容对冲利用率下降,训练节点池在空闲时自动缩容,推理节点池按流量扩缩。
  4. 引入批调度框架处理多节点训练场景,Volcano 的 Gang Scheduling 确保训练 Job 的原子性调度。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/14 12:43:00

YOLOv8字母数字识别系统:从原理到实践的全流程指南

在文档数字化处理和自动化识别领域&#xff0c;字母数字识别一直是个技术难点。传统OCR技术对模糊、倾斜、光照不均的文档识别效果有限&#xff0c;而基于YOLOv8的检测系统能够实现端到端的字符定位与识别&#xff0c;准确率显著提升。本文将完整分享一套可落地的YOLOv8字母数字…

作者头像 李华
网站建设 2026/7/14 12:40:39

网盘直链下载助手:8大平台一键解析,告别限速烦恼

网盘直链下载助手&#xff1a;8大平台一键解析&#xff0c;告别限速烦恼 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘 …

作者头像 李华
网站建设 2026/7/14 12:40:19

闲鱼自动化监控系统:3步快速上手,轻松抓取最新商品信息

闲鱼自动化监控系统&#xff1a;3步快速上手&#xff0c;轻松抓取最新商品信息 【免费下载链接】idlefish_xianyu_spider-crawler-sender 闲鱼自动抓取/筛选/发送系统&#xff0c;xianyu spider crawler blablabla 项目地址: https://gitcode.com/gh_mirrors/id/idlefish_xia…

作者头像 李华
网站建设 2026/7/14 12:39:22

DLPC34xx控制器电源管理:时序、模式与PCB布局全解析

1. 项目概述&#xff1a;DLPC34xx控制器电源管理的核心价值在嵌入式显示系统&#xff0c;尤其是像Pico投影仪、AR眼镜这类对功耗和体积都极其敏感的便携设备里&#xff0c;电源管理远不止是“通电就能工作”那么简单。它更像是一个精密的交响乐团指挥&#xff0c;需要确保每一个…

作者头像 李华
网站建设 2026/7/14 12:39:03

基于YOLOv8的辣椒品种智能检测系统开发实践

1. 项目概述&#xff1a;辣椒品种检测的智慧农业实践在智慧农业领域&#xff0c;作物品种识别一直是提升农业生产效率的关键技术。传统人工鉴别辣椒品种的方式不仅效率低下&#xff0c;而且受主观因素影响大。我们基于YOLOv8构建的辣椒品种检测系统&#xff0c;通过深度学习技术…

作者头像 李华