本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
计算和自动缩放
提示
通过 Amazon EKS 研讨会@@ 探索
GPU 资源优化和成本管理
使用 Well-Known 标签安排具有 GPU 要求的工作负载
对于对不同 GPU 特性敏感 AI/ML 的工作负载(例如 GPU、GPU 内存),我们建议使用与 Karpenter
示例
例如,在使用 Karpenter 时使用 GPU 名称节点选择器:
apiVersion: v1 kind: Pod metadata: name: gpu-pod-example spec: containers: - name: ml-workload image: <image> resources: limits: nvidia.com/gpu: 1 # Request one NVIDIA GPU nodeSelector: karpenter.k8s.aws/instance-gpu-name: "l40s" # Run on nodes with NVIDIA L40S GPUs
使用 Kubernetes 设备插件公开 GPU
要在节点上公开 GPU,必须将 NVIDIA GPU 驱动程序安装在节点的操作系统上,并将容器运行时配置为允许 Kubernetes 调度器向具有可用 GPU 的节点分配 Pod。NVIDIA Kubernetes 设备插件的设置过程取决于你正在使用的 EKS 加速 AMI:
-
Bottlerocket 加速的 AMI:这款 AMI 包含 NVIDIA GPU 驱动程序,NVIDIA Kubernetes 设备插件
已预先安装并随时可用,开箱即用 GPU 支持。无需额外配置即可向 Kubernetes 调度器公开 GPU。 -
AL2023 加速 AMI:此 AMI
包含 NVIDIA GPU 驱动程序,但未预装 NVIDIA Kubernetes 设备插件 。 AL2023 您必须单独安装和配置设备插件,通常是通过 DaemonSet。请注意,如果您使用 eksctl 创建集群并在中指定 GPU 实例类型(例如 g5.xlarge),则eksctl会自动选择相应的 ClusterConfig AMI 并安装 NVIDIA Kubernetes 设备插件。要了解更多信息,请参阅 eksctl 文档中的 GPU 支持。
如果你决定改用 EKS 加速 AMI 和 NVIDIA GPU 操作员来管理 NVIDIA
要验证 NVIDIA 设备插件是否处于活动状态以及 GPU 是否正确暴露,请运行:
kubectl describe node | grep nvidia.com/gpu
此命令检查nvidia.com/gpu资源是否在节点的容量和可分配的资源范围内。例如,应显示具有一个 GPU 的节点nvidia.com/gpu: 1。有关更多信息,请参阅 Kubernetes GPU 调度指南
使用许多不同的 EC2 实例类型
如本Kubernetes 数据平面节所述,尽可能多地使用不同的 EC2 实例类型是在 Amazon EKS 上实现可扩展性的重要最佳实践。此建议也适用于采用加速硬件(例如 GPU)的实例。如果您创建的集群仅使用一种实例类型,并尝试将节点数量扩展到该区域的容量之外,则可能会收到容量不足错误 (ICE),表示没有可用的实例。在进行任意多样化之前,了解 AI/ML 工作负载的独特特征非常重要。使用 EC2 Instance Type Explorer
加速计算实例以不同的购买模式提供,以适应短期、中期和稳定状态的工作负载。对于短期、灵活且容错的工作负载,如果您希望避免进行预留,请查看竞价型实例。容量块、 On-Demand 实例和节省计划允许您为中长期工作负载持续时间预置加速计算实例。为了增加在首选购买选项中成功访问所需容量的机会,建议使用不同的实例类型和可用区列表。或者,如果您遇到特定购买模式的 ICE,请重试使用其他型号。
示例以下示例说明如何让 Karpenter NodePool 能够配置大于第 3 代(例如 p3)的 G 和 P 实例。要了解更多信息,请参阅EKS 可扩展性最佳实践一节。
- key: karpenter.k8s.aws/instance-category operator: In values: ["g", "p"] # Diversifies across G-series and P-series - key: karpenter.k8s.aws/instance-generation operator: Gt values: ["3"] # Selects instance generations greater than 3
有关在 GPU 上使用竞价型实例的详细信息,请参阅下文的 “考虑使用带有 Karpenter 的 GPU 的 Amazon EC2 竞价型实例”。
考虑在 Karpenter 上使用适用于 GPU 的 Amazon EC2 竞价型实例
Amazon EC2 竞价型实例允许您利用 AWS 云中未使用的 EC2 容量,与 On-Demand 价格相比,可享受高达 90% 的折扣。当 EC2 需要恢复容量时,可以在两分钟内中断 Amazon EC2 竞价型实例。有关更多信息,请参阅 Amazon EC2 用户指南中的竞价型实例。Amazon EC2 Spot 是容错、无状态和灵活(时间和实例类型)工作负载的绝佳选择。要详细了解何时使用竞价型实例,请参阅 EC2 竞价型实例最佳实践。如果是,您也可以将竞价型实例用于 AI/ML 工作负载 Spot-friendly。
使用案例
Spot-friendly 工作负载可以是大数据、容器化工作负载 CI/CD、无状态 Web 服务器、高性能计算 (HPC) 和渲染工作负载。Spot 实例不适用于不灵活、有状态、不容错或实例节点之间紧密耦合的工作负载(例如,具有并行进程的工作负载,这些进程严重依赖彼此进行计算,需要持续的节点间通信,例如计算流体动力学等 MPI-based 高性能计算应用程序或具有复杂相互依赖关系的分布式数据库)。以下是我们推荐的具体用例(排名不分先后):
-
Real-time 在线推理:只要您的工作负载适合点位,就可使用竞价型实例对实时推理工作负载进行成本优化的扩展。换句话说,推理时间要么少于两分钟,要么应用程序对中断具有容错能力,并且可以在不同的实例类型上运行。通过实例多样性(例如,跨多个实例类型和可用区)或预留来确保高可用性,同时实施应用程序级容错以应对潜在的 Spot 中断。
-
Hyper-parameter 调整:使用 Spot 实例机会主义地运行探索性调整作业,因为可以容忍中断而不会造成重大损失,特别是对于持续时间较短的实验。
-
数据增强:使用 Spot 实例执行数据预处理和增强任务,如果中断,这些任务可以从检查点重新启动,因此非常适合 Spot 的可变可用性。
-
Fine-tuning 模型:使用竞价型实例通过强大的检查点机制进行微调,从上次保存的状态恢复,从而最大限度地减少实例中断的影响。
-
B@@ atch In ference:使用 Spot 实例以非实时方式处理大批量的离线推理请求,从而可以暂停和恢复作业,从而与 Spot 节省的成本保持最佳一致,并通过重试或多样化处理潜在的中断。
-
机会主义训练子集:将 Spot 实例用于边缘或实验性训练工作负载(例如,低于 1000 万个参数的小型模型),在这些工作负载中,中断是可以接受的,并且可以应用效率优化,例如跨实例类型或区域的多样化,尽管由于潜在的中断,不建议将其用于生产规模的训练。
注意事项
要在 Amazon EKS 上使用竞价型实例处理加速工作负载,需要考虑一些关键因素(排名不分先后):
-
使用 Karpenter 管理启用了高级整合的竞价型实例。通过指定木匠。 sh/capacity-在你的 Karpenter 中键入为 “spot” NodePool,Karpenter 将默认配置 Spot 实例,无需任何其他配置。但是,要启用高级 Spot-to-Spot 整合(用价格较低的竞价替代品取代未充分利用的竞价节点),您需要通过在 Karpenter 控制器参数中设置--fe at
ure-gates SpotToSpotConsolidation =true 或通过 FEATURE_GATES 环境变量来启用功能门。 SpotToSpotConsolidation Karpenter 使用价格容量优化的分配策略来配置 EC2 实例。根据 NodePool 要求和容器限制,Karpenter 将不可调度的容器装入垃圾箱,并将一组不同的实例类型发送到 Amazon EC2 队列 API。您可以使用 EC2 实例类型浏览器 工具生成符合您的特定计算要求的实例类型列表。 -
确保工作负载无状态、容错且灵活。工作负载必须是无状态的、容错的,而且大小必须灵活。 instance/GPU 这允许在 Spot 中断后无缝恢复,实例灵活性使您有可能在 Spot 上停留更长的时间。使用 AWS SQS 队列的名称配置 settings.InterruptionQueue Helm 值,以捕获竞价中断事件,从而在 Karpenter 中启用 Spot 中断处理
。例如,通过 Helm 安装时,使用--set “settings.interruptionQueue=$ {CLUSTER_NAME}”。要查看示例,请参阅 Karpenter 入门指南 。当 Karpenter 注意到 Spot 中断事件时,它会在中断事件发生之前自动封锁、污染、排空和终止节点,以最大限度地延长 Pod 的终止宽限期。同时,Karpenter 将立即启动一个新节点,这样它就可以尽快准备就绪。 -
避免过度限制实例类型选择。应尽量避免限制实例类型。通过不限制实例类型,以更低的成本获得竞价型实例中断频率的大规模竞价型容量的几率更高。例如,避免仅限于特定类型(例如 g5.xlarge)。考虑使用诸如 karpenter.k8s 之类的密钥指定一组不同的实例类别和生成。 aws/instance-类别和 karpenter.k8s。 aws/instance-世代。Karpenter 可以更轻松地在多个实例类型和可用区 (AZ) 之间实现按需实例和竞价型实例容量的多样化。此外,如果您的 AI/ML 工作负载需要特定或有限数量的加速器,但在不同区域之间具有灵活性,则可以在启动前使用 Spot Placement Score 动态确定部署工作负载的最佳区域。
-
扩大 NodePool 要求范围以包括更多类似的 EC2 实例系列。每个竞价型实例池都包含特定可用区 (AZ) 中特定实例类型的未使用的 EC2 实例容量。当 Karpenter 尝试配置新节点时,它会选择符合其要求 NodePool的实例类型。如果任何可用区中都没有兼容的实例类型具有 Spot 容量,则配置将失败。为避免此问题,请允许来自NVIDIA的更广泛的 g 系列实例(第 4 代或更高版本),跨大小和可用区 (AZ),同时考虑 GPU 内存或光线追踪等硬件需求。由于实例的类型可能不同,因此您需要确保您的工作负载能够在每种类型上运行,并且获得的性能可以满足您的需求。
-
利用一个区域中的所有可用区。可用容量因可用区 (AZ) 而异,特定实例类型可能在一个可用区不可用,但在另一个可用区却很多。实例类型和可用区域的每种唯一组合构成一个单独的竞价容量池。通过在符合您的 Karpenter NodePool 要求的区域内的所有可用区中请求容量,您实际上是在同时搜索更多存储池。这样可以最大限度地提高竞价容量池的数量,从而增加获取竞价容量的可能性。要实现此目的,请在 NodePool 配置中省略 topology.kubernetes。 io/zone 完全键入以允许 Karpenter 从该区域的所有可用区中进行选择,或者使用运算符:in 明确列出可用区并提供值(例如 us-west-2a)。
-
考虑使用竞价投放分数 (SPS) 来了解使用竞价型实例成功访问所需容量的可能性。竞价投放分数 (SPS) 是一种提供分数的工具,可帮助您评估竞价请求成功的可能性。使用 SPS 时,您首先要为竞价型实例指定计算要求,然后 Amazon EC2 会返回您的竞价请求可能成功的前 10 个区域或可用区 (AZ)。区域和可用区的评分范围为 1 至 10。分数为 10 表示您的竞价请求极有可能成功,但不能保证成功。分数为 1 则表示您的竞价请求几乎没有成功的可能。即使是不同的区域或可用区,也可能会返回相同的分数。要了解更多信息,请参阅在 AWS 上构建 Spot 投放分数追踪器控制面板的指南。由于 Spot 容量一直在波动,SPS 将帮助您确定哪种实例类型、可用区和区域组合最适合您的工作负载限制(即灵活性、性能、大小等)。如果您的 AI/ML 工作负载需要特定或有限数量的加速器,但在区域之间具有灵活性,则可以在启动前使用竞价放置分数来动态确定部署工作负载的最佳区域。为了帮助您自动找出获得 Spot 容量的可能性,我们提供了构建 SPS 跟踪器仪表板的指南。该解决方案使用 YAML 配置监控 SPS 分数以实现多样化设置(例如,包括 GPU 在内的实例需求),将指标存储在中 CloudWatch,并提供用于比较配置的仪表板。为每个工作负载定义控制面板以评估 vCPU、内存和 GPU 需求,确保为 EKS 集群提供最佳设置,包括考虑使用其他 AWS 区域。要了解更多信息,请参阅 Spot 展示位置分数的工作原理。
-
优雅地处理 Spot 中断并进行测试。对于终止时间超过两分钟的 Pod,旧节点将在重新调度 pod 之前中断,这可能会影响工作负载的可用性。在设计应用程序时,请考虑两分钟的 Spot 中断通知,在长时间运行的应用程序(例如,将进度保存到 Amazon S3 等永久存储)中断后恢复,延长 Pod 规范中的终止时间GracePeriodSeconds (默认为 30 秒)以留出更多时间进行优雅关闭,并在应用程序中使用 PreStop 生命周期挂钩 and/or SIGTERM 信号处理中断,以进行清理、状态保存和连接关闭等优雅关闭活动。对于实时工作负载,如果扩展时间很重要,而且工作负载需要超过两分钟的时间才能准备好为流量提供服务,可以考虑通过查看和应用程序扩展和性能最佳实践来优化容器启动仓储服务和机器学习模型加载时间。要测试替换节点,请使用 AWS 故障注入服务
(FIS) 来模拟 Spot 中断。
除了这些核心的 Spot 最佳实践外,在 Amazon EKS 上管理 GPU 工作负载时,还要考虑这些因素。与 CPU-based 工作负载不同,GPU 工作负载对硬件细节(例如 GPU 功能和可用的 GPU 内存)特别敏感。GPU 工作负载可能会受到其可以使用的实例类型的限制,与 CPU 相比,可用选项较少。首先,评估您的工作负载是否具有实例灵活性。如果您不知道您的工作负载可以使用多少实例类型,请分别对其进行测试以确保兼容性和功能性。确定在尽可能实现多元化方面的灵活性,同时确认多元化可以使工作负载正常运行,并了解任何性能影响(例如对吞吐量或完成时间)的影响。作为实现工作负载多样化的一部分,请考虑以下几点:
-
查看 CUDA 和框架兼容性。您的 GPU 工作负载可能会针对特定的硬件、GPU 类型(例如,p3 中的 V100 与 p4 中的 A100)进行优化,或者针对特定的 CUDA 版本编写 TensorFlow,例如库,因此请务必查看工作负载的兼容性。这种兼容性对于防止运行时错误、崩溃、GPU 加速失败(例如,CUDA 版本与类似 PyTorch 或 TensorFlow 可能阻止执行的框架不匹配)或利用精度等 FP16/INT8 硬件功能的能力至关重要。
-
GPU 内存。请务必使用诸如 DCGM
Exporter 之类的工具评估模型的内存需求并分析模型在运行时的内存使用情况,并在众所周知的标签(如 karpenter.k8s)中设置实例类型所需的最低 GPU 内存。 aws/instance-gpu-memory。GPU VRAM 因实例类型而异(例如,NVIDIA T4 有 16GB,A10G 有 24GB,V100 有 16-32GB),机器学习模型(例如大型语言模型)可能会超过可用内存,从而导致内存不足 (OOM) 错误或崩溃。对于 EKS 中的竞价型实例,这可能会限制多样化。例如,如果您的模型不适合,则不能包含较低的 VRAM 类型,这可能会限制对容量池的访问并增加中断风险。请注意,对于单个 GPU、单节点推理(例如,在同一个节点上调度多个 Pod 以利用其 GPU 资源),这可能会限制多样化,因为您只能在 Spot 配置中包含具有充足 VRAM 的实例类型。 -
Floating-point 精度和性能。并非所有 Nvidia GPU 架构都具有相同的浮点精度(例如 FP16/INT8)。评估您的工作负载所需的核心类型 (CUDA/Tensor/RT) 性能和浮点精度。在价格较低、性能较低的 GPU 上运行并不意味着性能更好,因此可以考虑根据在特定时间范围内完成的工作来评估性能,以了解多元化的影响。
场景:实时推理工作负载的多样化
对于竞价型实例的实时在线推理工作负载,您可以将 Karpenter NodePool 配置为在兼容的 GPU 实例系列和世代之间实现多样化。这种方法通过从多个 Spot 池中提取资源来确保高可用性,同时通过 GPU 功能、内存和架构的限制来保持性能。它支持在实例容量受限时使用替代方案,最大限度地减少中断并优化推理延迟。此示例 NodePool 说明使用大于 3 的 g 和 p 系列实例,它们的 GPU 内存超过 20GB。
示例
apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: gpu-inference-spot spec: template: metadata: labels: role: gpu-spot-worker spec: requirements: - key: karpenter.sh/capacity-type operator: In values: ["spot"] # Use Spot Instances - key: karpenter.k8s.aws/instance-category operator: In values: ["g", "p"] # Diversifies across G-series and P-series - key: karpenter.k8s.aws/instance-generation operator: Gt values: ["3"] # Selects instance generations greater than 3 - key: kubernetes.io/arch operator: In values: ["amd64"] # Specifies AMD64 architecture, compatible with NVIDIA GPUs - key: karpenter.k8s.aws/instance-gpu-memory operator: Gt values: ["20480"] # Ensures more than 20GB (20480 MiB) total GPU memory taints: - key: nvidia.com/gpu effect: NoSchedule nodeClassRef: name: gpu-inference-ec2 group: karpenter.k8s.aws kind: EC2NodeClass expireAfter: 720h limits: cpu: 100 memory: 100Gi disruption: consolidationPolicy: WhenEmptyOrUnderutilized consolidateAfter: 5m # Enables consolidation of underutilized nodes after 5 minutes
为长时间运行的训练作业实施检查点
Checkpointing 是一种容错技术,它涉及定期保存进程的状态,允许它在出现中断时从上次保存的点恢复。在机器学习中,它通常与训练相关联,在这种训练中,长时间运行的作业可以保存模型权重和优化器状态,以便在出现故障(例如硬件问题或竞价型实例中断)后恢复训练。
在训练期间,您可以使用检查点来保存机器学习 (ML) 模型的状态。检查点是模型的快照,可以通过机器学习框架的回调函数进行配置。您可以使用保存的检查点,根据上次保存的检查点重新启动训练作业。由于训练作业或实例意外中断,您可以使用检查点保存训练中的模型快照。这允许您在将来从检查点恢复对模型的训练。除了实施节点弹性系统外,我们还建议实施检查点以减轻中断的影响,包括硬件故障或 Amazon EC2 竞价型实例中断造成的中断的影响。
如果没有检查点,中断可能会导致计算时间浪费和进度损失,这对于长时间运行的训练作业来说代价高昂。Checkpointing 允许作业定期保存其状态(例如模型权重和优化器状态),并在中断后从上一个检查点(上次处理的批次)恢复。要实现检查点,请将您的应用程序设计为大批量处理数据,并在训练作业进行时通过 Mountpoint for Amazon S3 CSI 驱动程序将中间结果保存到永久存储中,例如通过 Mountpoint for Amazon S3 CSI 驱动程序保存到 Amazon S3 存储桶。
使用案例
检查点在特定场景中特别有用,可以平衡容错能力和性能开销。在以下情况下,可以考虑使用检查点功能:
-
Job 持续时间超过几个小时:对于长时间运行的训练作业(例如,对于小型模型,或者 days/weeks 对于具有数十亿个参数的大型基础模型,>1-2 小时),在这种作业中,由于中断而导致的进度损失是代价高昂的。较短的工作可能无法证明 I/O 开销是合理的。
-
对于 Spot 实例或硬件故障:在容易出现中断的环境中,例如 EC2 Spot(2 分钟通知)或硬件故障(例如 GPU 内存错误),检查点功能可以快速恢复,这使得Spot在容错工作负载中可以节省成本。
-
大规模分布式训练:对于使用加速器(例如 >100 GPU) hundreds/thousands 的设置,其中平均故障间隔时间随着规模的增加而线性缩短。用于 model/data 并行性以处理并行检查点访问并避免完全重启。
-
Large-scale 资源需求高的模型:在 PB 级的 LLM 训练中,由于集群规模,故障不可避免;分层方法(瞬态每隔 5-30 分钟进行一次快速本地化,重大故障每小时耐用一次)可以优化恢复时间与效率。
使用 ML 容量块为 P 和 Trainium 实例提供容量保障
机器学习容量块允许您预留备受追捧的 GPU 实例,特别是 P 实例(例如 p6-b200、p5、p5e、p5e、p5en、p4d、p4de)和 Trainium 实例(例如 trn1、trn2),以便几乎立即启动或在将来的某个日期启动,以支持您的短时机器学习 (ML) 工作负载。这些预留空间非常适合确保模型训练和微调等计算密集型任务的容量。EC2 容量块定价包括预留费和操作系统费。要了解有关定价的更多信息,请参阅适用于 ML 的 EC2 容量块定价
要在 Amazon EKS 上为 AI/ML 工作负载预留 GPU 以获得可预测的容量保障,我们建议使用机器学习容量块进行短期或使用On-Demand 容量预留 (ODCR) 进行通用容量保障。
-
ODCR 允许您在特定可用区内预留 EC2 实例容量(例如 g5 或 p5 等 GPU 实例),从而确保可用性,即使在需求旺盛时也是如此。ODCR 没有长期承诺,但您需要为预留容量付费,无论是已用容量还是闲置容 On-Demand 量。在 EKS 中,K arpenter 和托管节点组等节点类型支持 ODCR
。要在 Karpenter 中确定优先级 ODCR,请将配置为使用该字段 NodeClass 。 capacityReservationSelectorTerms请参阅 Karpenter 文档。 NodePools -
容量块是 GPU(例如 p5、p4d)或 Trainium(trn1、trn2)实例的专用预留机制,专为模型训练、微调或实验等短期机器学习工作负载而设计。您可以从将来的某个日期开始在定义的时间段(通常为 24 小时到 182 天)内预留容量,只需为预留的时间付费。它们是预付费的,需要对容量需求进行预先规划,并且不支持自动扩展,但它们托管在 EC2 UltraClusters 中以实现低延迟联网。他们仅在保留期内收费。要了解更多信息,请参阅查找和购买容量块,或者按照为机器学习创建带容量块的托管节点组中的说明开始使用容量块设置托管节点组。
通过 AWS 管理控制台预留容量,并将您的节点配置为使用 ML 容量块。根据工作负载计划计划预留并在暂存集群中进行测试。有关更多信息,请参阅容量块文档。
考虑一下 On-Demand G Amazon EC2 实例的 Amazon EC2 竞价或 On-Demand 容量预留 (ODCR)
对于 G Amazon EC2 实例,请考虑不同的购买选项 On-Demand,即 Amazon EC2 竞价型实例和 On-Demand 容量预留。ODCR 允许您在特定可用区域中预留 EC2 实例容量一段时间,即使在需求旺盛时也能确保可用性。与仅适用于 P 和 Trainium 实例的 ML 容量块不同,ODCR 可用于更广泛的实例类型,包括 G 实例,因此适用于需要不同 GPU 功能的工作负载,例如推理或图形。使用 Amazon EC2 竞价型实例时,能够在不同的实例类型、大小和可用区之间进行多样化是能够在竞价上停留更长时间的关键。
ODCR 没有长期承诺,但您需要为预留容量付费,无论是已用容量还是闲置容 On-Demand 量。可以创建 ODC 以供立即使用,也可以安排在将来的某个日期使用,这为容量规划提供了灵活性。在 Amazon EKS 中,KarpentercapacityReservationSelectorTerms请参阅 Karpenter 文档。 NodePools
考虑其他加速实例类型和大小
选择适当的加速实例和大小对于在 Amazon EKS 上优化机器学习工作负载的性能和成本至关重要。例如,不同的 GPU 实例系列具有不同的性能和功能,例如 GPU 内存。为了帮助您选择性价比最高的选项,请在 “加速计算” 下的 EC2 实例类型
如果您在 EKS 节点中使用 GPU 实例,则默认情况下,它的 nvidia-device-plugin-daemonset pod 将位于kube-system命名空间中。要快速了解你是否充分利用了实例中的 GPU,你可以使用 nvidia-smi
kubectl exec nvidia-device-plugin-daemonset-xxxxx \ -n kube-system -- nvidia-smi \ --query-gpu=index,power.draw,power.limit,temperature.gpu,utilization.gpu,utilization.memory,memory.free,memory.used \ --format=csv -l 5
-
如果
utilization.memory接近 100%,则您的代码很可能受限于内存。这意味着 GPU(内存)已得到充分利用,但可能表明应研究进一步的性能优化。 -
如果接近 100%,这并不一定意味着 GPU 已得到充分利用。
utilization.gpu更好的衡量标准是与的power.draw比率power.limit。如果此比率为 100% 或更高,则说明您的代码充分利用了 GPU 的计算容量。 -
该
-l 5标志显示每 5 秒输出一次指标。对于单个 GPU 实例类型,则不需要索引查询标志。
要了解更多信息,请参阅 AWS 文档中的 GPU 实例。
使用 Time-Slicing、MIG 和分数 GPU 分配优化 GPU 资源分配
Kubernetes 中的静态资源限制(例如 CPU、内存、GPU 数量)可能会导致过度配置或利用不足,对于推理等动态工作负载尤其如此。 AI/ML 选择正确的 GPU 很重要。对于低容量或尖峰工作负载,时间分片允许多个工作负载通过共享其计算资源共享单个 GPU,从而有可能提高效率并减少浪费。GPU 共享可以通过不同的选项实现:
-
利用节点选择器/节点亲和力来影响调度:确保已配置的节点和 Pod 调度到适合工作负载的 GPU 上(例如)
karpenter.k8s.aws/instance-gpu-name: "a100" -
Time-Slicing:调度工作负载以随着时间的推移共享 GPU 的计算资源,从而允许在不进行物理分区的情况下并发执行。这非常适合具有可变计算需求但可能缺乏内存隔离的工作负载。
-
Multi-Instance GPU (MIG):MIG 允许将单个 NVIDIA GPU 分成多个隔离的实例,并支持 NVIDIA Ampere(例如 A100 GPU)、NVIDIA Hopper(例如 H100 GPU)和 NVIDIA Blackwell(例如 Blackwell GPU)GPU。每个 MIG 实例都会获得专用的计算和内存资源,从而在多租户环境或需要资源保障的工作负载中实现资源共享,这使您可以优化 GPU 资源利用率,包括通过时间切片为具有不同批次大小的多个模型提供服务等场景。
-
GPU 分配:使用基于软件的调度将 GPU 的部分计算或内存分配给工作负载,从而为动态工作负载提供灵活性。作为该 Run:ai 平台的一部分,NVIDIA KAI 调度器
通过允许 pod 请求一部分 GPU 资源来实现这一点。
要在 EKS 中启用这些功能,您可以部署 NVIDIA 设备插件,该插件将 GPU 作为可调度资源公开,并支持时间分片和 MIG。要了解更多信息,请参阅 Kubernetes 中的 Time-Slicing GPU 以及使用 N
示例
例如,要使用 NVIDIA 设备插件启用时间切片,请执行以下操作:
apiVersion: v1 kind: ConfigMap metadata: name: nvidia-device-plugin-config namespace: kube-system data: config.yaml: | version: v1 sharing: timeSlicing: resources: - name: nvidia.com/gpu replicas: 4 # Allow 4 pods to share each GPU
示例
例如,要使用 KAI 调度器进行小部分 GPU 分配,请将其与 NVIDIA GPU 操作员一起部署,然后在 pod 规范中指定小部分 GPU 资源:
apiVersion: v1 kind: Pod metadata: name: fractional-gpu-pod-example annotations: gpu-fraction: "0.5" # Annotation for 50% GPU labels: runai/queue: "default" # Required queue assignment spec: containers: - name: ml-workload image: nvcr.io/nvidia/pytorch:25.04-py3 resources: limits: nvidia.com/gpu: 1 nodeSelector: nvidia.com/gpu: "true" schedulerName: kai-scheduler
节点弹性和训练 Job 管理
通过自动恢复实现节点运行状况检查
对于 Amazon EKS 上需要频繁进行节点间通信的分布式训练作业(例如跨多个节点的多 GPU 模型训练),GPU 或 EFA 故障等硬件问题可能会导致训练作业中断。这些中断可能导致训练进度损失和成本增加,对于依赖稳定硬件的长时间运行 AI/ML 的工作负载尤其如此。
为了帮助增强针对硬件故障(例如运行 GPU 工作负载的 EKS 集群中的 GPU 故障)的弹性,我们建议使用带自动修复功能的 EKS 节点监控代理或 Amazon SageMaker HyperPod。而带自动修复功能的 EKS 节点监控代理提供节点运行状况监控和使用标准 Kubernetes 机制进行自动修复等功能,同时 SageMaker HyperPod 还提供针对性的弹性和专为大规模机器学习训练设计的其他功能,例如深度运行状况检查和自动任务恢复。
-
带有节点自动修复功能的 EKS 节点监控代理通过读取日志和应用 NodeConditions(包括加速硬件的标准条件和特定条件)来持续监控节点的运行状况,以识别 GPU 或网络故障等问题。
Ready当节点被认为不健康时,Node Auto Repair 会封锁该节点并将其替换为新节点。Pod 的重新调度和作业的重启依赖于标准的 Kubernetes 机制和作业的重启策略。 -
SageMaker HyperPod
深度运行状况检查和运行状况监控代理持续监控 GPU 和 Trainium-based 实例的运行状况。它专为 AI/ML 工作负载量身定制,使用标签(例如节点健康状态)来管理节点运行状况。当节点被认为运行状况不佳时, HyperPod 会触发自动更换故障硬件(例如 GPU)。默认情况下,它通过基本的运行状况检查来检测 EFA 的网络相关故障,并支持对中断的训练作业自动恢复,允许作业从最后一个检查点继续进行,从而最大限度地减少大规模机器学习任务的中断。
对于具有自动修复功能的 EKS 节点监控代理和使用 EFA 的 SageMaker HyperPod 集群,要监控远程直接内存访问 (RDMA) 错误和数据包丢失等 EFA-specific 指标,请确保已安装 AWS EF A 驱动程序。此外,我们建议部署CloudWatch 可观察性 Add-on或使用带有 Prometheus 和 Grafana 的 DCGM Exporter 等工具来监控 EFA、GPU 以及与其功能相关的特定指标。 SageMaker HyperPod
对中断敏感型工作负载禁用 Karpenter 整合
对于对中断敏感的工作负载,例如处理、大规模 AI/ML 预测任务或训练,我们建议调整 Karpenter 整合策略
WhenEmptyOrUnderutilized整合策略可能会过早终止节点,从而导致更长的执行时间。例如,由于容器重新调度、数据重新加载,中断可能会延迟任务的恢复,这对于长时间运行的批量推理作业来说可能会付出高昂的代价。为了缓解这种情况,您可以将设置consolidationPolicy为WhenEmpty并配置consolidateAfter持续时间(例如 1 小时),以便在工作负载高峰期间保留节点。例如:
disruption: consolidationPolicy: WhenEmpty consolidateAfter: 60m
这种方法可以改善尖峰批量推理工作负载和其他对中断敏感的作业(例如实时在线推理数据处理或模型训练)的 pod 启动延迟,在这些作业中,中断成本大于节省的计算成本。Karpenter NodePool 干扰预算
使用 ttl 自动执行 Clean-Up Kub SecondsAfterFinished ernetes 作业
我们建议在 Amazon EKS 中将 Kubernetes 任务设置ttlSecondsAfterFinished为自动删除已完成的任务对象。延迟任务对象会消耗群集资源,例如 API 服务器内存,并且仪表板混乱(例如 Grafana、Amazon),从而使监控复杂化。 CloudWatch例如,将 TTL 设置为 1 小时可确保任务在完成后不久即被删除,从而保持集群整洁。有关更多详细信息,请参阅自动清理已完成的作业
配置 Low-Priority Job 抢占功能 Higher-Priority Jobs/workloads
对于 Amazon EKS 上的混合优先级 AI/ML 工作负载,您可以配置低优先级任务抢占以确保优先级较高的任务(例如实时推理)及时获得资源。如果没有抢占权,低优先级工作负载(例如批处理推理、数据处理)、非批处理服务(例如后台任务、cron 作业)或 CPU/memory-intensive 作业(例如 Web 服务)可能会通过占用节点来延迟关键 pod。抢占允许 Kubernetes 在高优先级 Pod 需要资源时驱逐低优先级 Pod,从而确保在具有 GPU、CPU 或内存的节点上高效分配资源。我们建议使用 Kubernetes PriorityClass 来分配优先级并控制驱PodDisruptionBudget逐行为。
apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: name: low-priority value: 100 --- spec: priorityClassName: low-priority
有关更多信息,请参阅 Kubernetes 优先级和抢占文档
应用程序扩展和性能
使用 Karpenter 或静态节点为机器学习工作负载量身定制计算容量
为了确保在 Amazon EKS 上为机器学习 (ML) 工作流程提供经济高效且响应迅速的计算容量,我们建议根据工作负载的特征和成本承诺量身定制节点配置策略。以下是两种需要考虑的方法:使用 Karpenter
-
Just-in-time 像 Karpenter 这样的数据平面缩放器:对于具有可变计算需求的动态机器学习工作流程(例如, GPU-based 推理后 CPU-based 绘图),我们建议使用像 Karpenter 这样的即时数据平面缩放器。
-
将@@ 静态节点组用于可预测的工作负载:对于可预测的稳定状态机器学习工作负载或使用预留实例时,EKS 托管节点组可以帮助确保预留容量得到充分配置和利用,从而最大限度地节省开支。这种方法非常适合通过 RI 或 ODCR 提交的特定实例类型。
示例
这是一个多元化 Karpenter 的示例 NodePoolg Amazon EC2 实例。
apiVersion: karpenter.sh/v1 kind: NodePool metadata: name: gpu-inference spec: template: spec: nodeClassRef: group: karpenter.k8s.aws kind: EC2NodeClass name: default requirements: - key: karpenter.sh/capacity-type operator: In values: ["on-demand"] - key: karpenter.k8s.aws/instance-category operator: In values: ["g"] - key: karpenter.k8s.aws/instance-generation operator: Gt values: ["3"] - key: kubernetes.io/arch operator: In values: ["amd64"] taints: - key: nvidia.com/gpu effect: NoSchedule limits: cpu: "1000" memory: "4000Gi" nvidia.com/gpu: "10" *# Limit the total number of GPUs to 10 for the NodePool* disruption: consolidationPolicy: WhenEmpty consolidateAfter: 60m expireAfter: 720h
示例
使用静态节点组处理训练工作负载的示例:
apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: ml-cluster region: us-west-2 managedNodeGroups: - name: gpu-node-group instanceType: p4d.24xlarge minSize: 2 maxSize: 2 desiredCapacity: 2 taints: - key: nvidia.com/gpu effect: NoSchedule
使用污点和容忍度来防止在加速实例上调度非加速工作负载
在 GPU 资源上调度非加速工作负载的计算效率不高,我们建议使用污点和容忍度来确保非加速工作负载 pod 不会调度到不合适的节点上。有关更多信息,请参阅 Kubernetes 文档
基于模型性能进行缩放
对于推理工作负载,我们建议使用 Kubernetes Event-Driven Autoscaling (KEDA) 根据模型性能指标(例如推理请求或令牌吞吐量)进行扩展,并设置适当的冷却时间。静态扩展策略可能会过度配置资源或资源不足,从而影响成本和延迟。要了解更多信息,请参阅 KEDA 文档
用于高级 GPU 管理的动态资源分配
动态资源分配 (DRA)
-
Fine-grained GPU 分配
-
高级共享机制,例如 Multi-Process 服务 (MPS) 和 Multi-Instance GPU (MIG)
-
支持下一代硬件架构,包括 NVIDIA GB200 UltraServers
传统的 GPU 分配将 GPU 视为不透明的整数资源,从而造成严重的利用率不足(在生产集群中通常为 30-40%)。之所以发生这种情况,是因为即使只需要少量资源,工作负载也可以独占访问整个 GPU。DRA 通过引入结构化的声明式分配来改变这种模型,让 Kubernetes 调度器能够完全了解硬件特征和工作负载要求。这可以实现明智的放置决策和高效的资源共享。
使用 DRA 代替 NVIDIA 设备插件的优势
NVIDIA 设备插件(从版本开始0.12.0)支持 GPU 共享机制,包括时间切片、MPS 和 MIG。但是,DRA 可以解决一些架构限制。
NVIDIA 设备插件限制
-
静态配置:GPU 共享配置(时间切片副本和 MPS 设置)需要在整个集群范围内进行预配置。
ConfigMaps这使得为不同的工作负载提供不同的共享策略变得困难。 -
精细选择有限:虽然设备插件通过节点标签公开 GPU 特性,但工作负载无法在调度决策中动态请求特定的 GPU 配置(内存大小和计算能力)。
-
没有跨节点资源协调:无法管理跨多个节点的分布式 GPU 资源,也无法表达复杂的拓扑要求,例如 NVIDIA GB200 等系统的 NVLink 域。
-
调度器限制:Kubernetes 调度器将 GPU 资源视为不透明的整数,从而限制了其做出拓扑感知决策或处理复杂资源依赖关系的能力。
-
配置复杂性:设置不同的共享策略需要多重
ConfigMaps且谨慎的节点标记,这增加了操作的复杂性。
使用 DRA 的解决方案
-
动态资源选择:DRA 允许工作负载在请求时指定详细要求(GPU 内存、驱动程序版本和特定属性)
resourceclaims。这样可以实现更灵活的资源匹配。 -
拓扑感知:通过结构化参数和设备选择器,DRA 可以处理复杂的需求,例如跨节点 GPU 通信和内存一致性互连。
-
Cross-node 资源管理:
computeDomains支持跨多个节点协调分布式 GPU 资源,这对于像 GB200 这样具有 IMEX 通道的系统至关重要。 -
Workload-specific 配置:每种策略和配置都
ResourceClaim指定了不同的共享策略和配置,允许对每个工作负载进行精细控制,而不是集群范围的设置。 -
增强的调度程序集成:DRA 为调度器提供详细的设备信息,并允许根据硬件拓扑和资源特征做出更明智的放置决策。
重要提示:DRA 并不能完全取代 NVIDIA 设备插件。NVIDIA DRA 驱动程序与设备插件配合使用,可提供增强功能。设备插件继续处理基本的 GPU 发现和管理,而 DRA 则增加了高级分配和调度功能。
DRA 支持的实例及其功能
DRA 支持因 Amazon EC2 实例系列和 GPU 架构而异,如下表所示。
| 实例系列 | GPU 类型 | Time-slicing | MIG 支持 | MPS 支持 | IMEX 支持 | 使用案例 |
|---|---|---|---|---|---|---|
|
G5 |
NVIDIA A10G |
是 |
否 |
是 |
否 |
推理和图形工作负载 |
|
G6 |
NVIDIA L4 |
是 |
否 |
是 |
否 |
AI 推理和视频处理 |
|
G6e |
英伟达 L40S |
是 |
否 |
是 |
否 |
训练、推理和图形 |
|
P4d/P4de |
NVIDIA A100 |
支持 |
是 |
是 |
否 |
Large-scale 培训和 HPC |
|
P5 |
NVIDIA H100 |
支持 |
是 |
是 |
否 |
基础模型训练 |
|
P6 |
英伟达 B200 |
支持 |
是 |
是 |
否 |
十亿或万亿参数模型、分布式训练和推理 |
|
p6e |
英伟达 GB200 |
支持 |
是 |
是 |
是 |
十亿或万亿参数模型、分布式训练和推理 |
以下是对表中每项功能的描述:
-
Time-slicing:允许多个工作负载随着时间的推移共享 GPU 计算资源。
-
Multi-Instance GPU (MIG):用于创建隔离的 GPU 实例的 Hardware-level 分区。
-
Multi-Process 服务 (MPS):允许在单个 GPU 上并发执行多个 CUDA 进程。
-
节点间内存交换 (IMEX):GB200 的跨节点 Memory-coherent 通信。 UltraServers
其他资源
有关 Kubernetes DRA 和 NVIDIA DRA 驱动程序的更多信息,请参阅以下资源: GitHub
为高级 GPU 管理设置动态资源分配
以下主题介绍如何为高级 GPU 管理设置动态资源分配 (DRA)。
先决条件
在 Amazon EKS 上实施 DRA 之前,请确保您的环境满足以下要求。
集群配置
-
运行版本
1.33或更高版本的 Amazon EKS 集群 -
Amazon EKS 托管节点组(目前仅支持 AL2023 和 Bottlerocket NVIDIA 优化了 AMI 的托管节点组,Karpenter 不支持 DRA)
AL2023 -
具有适当实例类型的 NVIDIA GPU-enabled 工作节点
必需的组件
-
NVIDIA 设备插件版本
0.17.1或更高版本 -
英伟达 DRA 驱动程序版本
25.3.0或更高版本
步骤 1:使用 eksctl 创建带有 DRA-enabled 节点组的集群
-
创建名为
dra-eks-cluster.yaml:的集群配置文件--- apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: dra-eks-cluster region: us-west-2 version: '1.33' managedNodeGroups: - name: gpu-dra-nodes amiFamily: AmazonLinux2023 instanceType: g6.12xlarge desiredCapacity: 2 minSize: 1 maxSize: 3 labels: node-type: "gpu-dra" nvidia.com/gpu.present: "true" taints: - key: nvidia.com/gpu value: "true" effect: NoSchedule -
创建集群:
eksctl create cluster -f dra-eks-cluster.yaml
第 2 步:部署 NVIDIA 设备插件
部署 NVIDIA 设备插件以启用基本的 GPU 发现:
-
添加 NVIDIA 设备插件 Helm 存储库:
helm repo add nvidia https://nvidia.github.io/k8s-device-plugin helm repo update -
为设备插件创建自定义值:
cat <<EOF > nvidia-device-plugin-values.yaml gfd: enabled: true nfd: enabled: true tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule EOF -
安装 NVIDIA 设备插件:
helm install nvidia-device-plugin nvidia/nvidia-device-plugin \ --namespace nvidia-device-plugin \ --create-namespace \ --version 0.17.1 \ --values nvidia-device-plugin-values.yaml
第 3 步:部署 NVIDIA DRA 驱动程序头盔图
-
为 DRA 驱动程序创建
dra-driver-values.yaml值文件:--- nvidiaDriverRoot: / gpuResourcesEnabledOverride: true resources: gpus: enabled: true computeDomains: enabled: true # Enable for GB200 IMEX support controller: tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule kubeletPlugin: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: "nvidia.com/gpu.present" operator: In values: ["true"] tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule -
添加 NVIDIA NGC Helm 存储库:
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia helm repo update -
安装 NVIDIA DRA 驱动程序:
helm install nvidia-dra-driver nvidia/nvidia-dra-driver-gpu \ --version="25.3.0-rc.2" \ --namespace nvidia-dra-driver \ --create-namespace \ --values dra-driver-values.yaml
步骤 4:验证 DRA 的安装
-
验证 DRA API 资源是否可用:
kubectl api-resources | grep resource.k8s.io/v1beta1预期的输出如下所示:
deviceclasses resource.k8s.io/v1beta1 false DeviceClass resourceclaims resource.k8s.io/v1beta1 true ResourceClaim resourceclaimtemplates resource.k8s.io/v1beta1 true ResourceClaimTemplate resourceslices resource.k8s.io/v1beta1 false ResourceSlice -
检查可用的设备类别:
kubectl get deviceclasses以下是预期输出的示例:
NAME AGE compute-domain-daemon.nvidia.com 4h39m compute-domain-default-channel.nvidia.com 4h39m gpu.nvidia.com 4h39m mig.nvidia.com 4h39m当新创建的 G6 GPU 实例加入启用了 DRA 的 Amazon EKS 集群时,会发生以下操作:
-
NVIDIA DRA 驱动程序会自动发现 A10G GPU 并在该节点
resourceslices上创建两个 GPU。 -
该
gpu.nvidia.com切片注册物理 A10G GPU 设备及其规格(内存、计算能力等)。 -
由于 A10G 不支持 MIG 分区,因此该
compute-domain.nvidia.com切片会创建一个代表 GPU 整个计算环境的单个计算域。 -
然后将
resourceslices它们发布到 Kubernetes API 服务器,从而使 GPU 资源可用于调度。resourceclaimsDRA 调度器现在可以智能地将此 GPU 分配给通过其请求 GPU 资源的 Pod
resourceclaimtemplates,与传统的设备插件方法相比,资源管理更加灵活。这会自动发生,无需人工干预。DRA 驱动程序完成资源发现和注册过程后,该节点即可用于 GPU 工作负载。当你运行以下命令时:
kubectl get resourceslices以下是预期输出的示例:
NAME NODE DRIVER POOL AGE ip-100-64-129-47.ec2.internal-compute-domain.nvidia.com-rwsts ip-100-64-129-47.ec2.internal compute-domain.nvidia.com ip-100-64-129-47.ec2.internal 35m ip-100-64-129-47.ec2.internal-gpu.nvidia.com-6kndg ip-100-64-129-47.ec2.internal gpu.nvidia.com ip-100-64-129-47.ec2.internal 35m
-
使用动态资源分配安排简单的 GPU 工作负载
要使用动态资源分配 (DRA) 安排简单的 GPU 工作负载,请执行以下步骤。在继续操作之前,请确保你已经遵循了为高级 GPU 管理设置动态资源分配。
-
使用名
ResourceClaimTemplate为以下文件创建 GPU 分配的基本配置basic-gpu-claim-template.yaml:--- apiVersion: v1 kind: Namespace metadata: name: gpu-test1 --- apiVersion: resource.k8s.io/v1beta1 kind: ResourceClaimTemplate metadata: namespace: gpu-test1 name: single-gpu spec: spec: devices: requests: - name: gpu deviceClassName: gpu.nvidia.com -
应用模板:
kubectl apply -f basic-gpu-claim-template.yaml -
验证状态:
kubectl get resourceclaimtemplates -n gpu-test1下面是示例输出:
NAME AGE single-gpu 9m16s -
创建一个使用的 Pod
ResourceClaimTemplate,其文件名为basic-gpu-pod.yaml:--- apiVersion: v1 kind: Pod metadata: namespace: gpu-test1 name: gpu-pod labels: app: pod spec: containers: - name: ctr0 image: ubuntu:22.04 command: ["bash", "-c"] args: ["nvidia-smi -L; trap 'exit 0' TERM; sleep 9999 & wait"] resources: claims: - name: gpu0 resourceClaims: - name: gpu0 resourceClaimTemplateName: single-gpu nodeSelector: NodeGroupType: gpu-dra nvidia.com/gpu.present: "true" tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule" -
应用并监控 Pod:
kubectl apply -f basic-gpu-pod.yaml -
检查 Pod 状态:
kubectl get pod -n gpu-test1以下是预期输出示例:
NAME READY STATUS RESTARTS AGE gpu-pod 1/1 Running 0 13m -
检查
ResourceClaim状态:kubectl get resourceclaims -n gpu-test1以下是预期输出示例:
NAME STATE AGE gpu-pod-gpu0-l76cg allocated,reserved 9m6s -
查看 Pod 日志以查看 GPU 信息:
kubectl logs gpu-pod -n gpu-test1以下是预期输出示例:
GPU 0: NVIDIA L4 (UUID: GPU-da7c24d7-c7e3-ed3b-418c-bcecc32af7c5)
继续阅读采用动态资源分配的 GPU 优化技术以了解使用 DRA 的更高级的 GPU 优化技术。
采用动态资源分配的 GPU 优化技术
现代 GPU 工作负载需要复杂的资源管理才能实现最佳利用率和成本效益。DRA 支持多种高级优化技术,可解决不同的用例和硬件功能:
-
Time-slicing允许多个工作负载随着时间的推移共享 GPU 计算资源,因此非常适合偶尔使用 GPU 的推理工作负载。有关示例,请参阅通过时间切片优化 GPU 工作负载。
-
Multi-Process 服务 (MPS) 支持在单个 GPU 上并发执行多个 CUDA 进程,隔离效果比时间切片更好。有关示例,请参阅使用 MPS 优化 GPU 工作负。
-
Multi-Instance GPU (MIG) 提供硬件级分区,使用专用的计算和内存资源创建隔离的 GPU 实例。有关示例,请参阅使用 GPU 优化 GP Multi-Instance U 工作负。
-
节点间内存交换 (IMEX) 支持跨节点的内存一致性通信,以便在 NVIDIA GB200 系统上进行分布式训练。有关示例,请参阅使用 GB200 P6e 实例通过 IMEX 优化 GPU 工作负载。
这些技术可以显著提高资源利用率。Organizations报告说,通过优化的共享策略,GPU利用率从传统分配的30-40%提高到80-90%。技术的选择取决于工作负载特性、隔离要求和硬件功能。
通过时间切片优化 GPU 工作负载
Time-slicing 通过调度多个工作负载在同一个物理 GPU 上按顺序运行,使它们能够共享 GPU 计算资源。它非常适合偶尔使用 GPU 的推理工作负载。
请执行以下步骤。
-
使用名为:的文件
ResourceClaimTemplate为时间切片定义一个timeslicing-claim-template.yaml--- apiVersion: v1 kind: Namespace metadata: name: timeslicing-gpu --- apiVersion: resource.k8s.io/v1beta1 kind: ResourceClaimTemplate metadata: name: timeslicing-gpu-template namespace: timeslicing-gpu spec: spec: devices: requests: - name: shared-gpu deviceClassName: gpu.nvidia.com config: - requests: ["shared-gpu"] opaque: driver: gpu.nvidia.com parameters: apiVersion: resource.nvidia.com/v1beta1 kind: GpuConfig sharing: strategy: TimeSlicing -
使用时间切片定义一个 Pod,文件名为:
timeslicing-pod.yaml--- # Pod 1 - Inference workload apiVersion: v1 kind: Pod metadata: name: inference-pod-1 namespace: timeslicing-gpu labels: app: gpu-inference spec: restartPolicy: Never containers: - name: inference-container image: nvcr.io/nvidia/pytorch:25.04-py3 command: ["python", "-c"] args: - | import torch import time import os print(f"=== POD 1 STARTING ===") print(f"GPU available: {torch.cuda.is_available()}") print(f"GPU count: {torch.cuda.device_count()}") if torch.cuda.is_available(): device = torch.cuda.current_device() print(f"Current GPU: {torch.cuda.get_device_name(device)}") print(f"GPU Memory: {torch.cuda.get_device_properties(device).total_memory / 1024**3:.1f} GB") # Simulate inference workload for i in range(20): x = torch.randn(1000, 1000).cuda() y = torch.mm(x, x.t()) print(f"Pod 1 - Iteration {i+1} completed at {time.strftime('%H:%M:%S')}") time.sleep(60) else: print("No GPU available!") time.sleep(5) resources: claims: - name: shared-gpu-claim resourceClaims: - name: shared-gpu-claim resourceClaimTemplateName: timeslicing-gpu-template nodeSelector: NodeGroupType: "gpu-dra" nvidia.com/gpu.present: "true" tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule --- # Pod 2 - Training workload apiVersion: v1 kind: Pod metadata: name: training-pod-2 namespace: timeslicing-gpu labels: app: gpu-training spec: restartPolicy: Never containers: - name: training-container image: nvcr.io/nvidia/pytorch:25.04-py3 command: ["python", "-c"] args: - | import torch import time import os print(f"=== POD 2 STARTING ===") print(f"GPU available: {torch.cuda.is_available()}") print(f"GPU count: {torch.cuda.device_count()}") if torch.cuda.is_available(): device = torch.cuda.current_device() print(f"Current GPU: {torch.cuda.get_device_name(device)}") print(f"GPU Memory: {torch.cuda.get_device_properties(device).total_memory / 1024**3:.1f} GB") # Simulate training workload with heavier compute for i in range(15): x = torch.randn(2000, 2000).cuda() y = torch.mm(x, x.t()) loss = torch.sum(y) print(f"Pod 2 - Training step {i+1}, Loss: {loss.item():.2f} at {time.strftime('%H:%M:%S')}") time.sleep(5) else: print("No GPU available!") time.sleep(60) resources: claims: - name: shared-gpu-claim-2 resourceClaims: - name: shared-gpu-claim-2 resourceClaimTemplateName: timeslicing-gpu-template nodeSelector: NodeGroupType: "gpu-dra" nvidia.com/gpu.present: "true" tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule -
应用模板和 Pod:
kubectl apply -f timeslicing-claim-template.yaml kubectl apply -f timeslicing-pod.yaml -
监控资源声明:
kubectl get resourceclaims -n timeslicing-gpu -w下面是示例输出:
NAME STATE AGE inference-pod-1-shared-gpu-claim-9p97x allocated,reserved 21s training-pod-2-shared-gpu-claim-2-qghnb pending 21s inference-pod-1-shared-gpu-claim-9p97x pending 105s training-pod-2-shared-gpu-claim-2-qghnb pending 105s inference-pod-1-shared-gpu-claim-9p97x pending 105s training-pod-2-shared-gpu-claim-2-qghnb allocated,reserved 105s inference-pod-1-shared-gpu-claim-9p97x pending 105s
First Pod (inference-pod-1)
-
州:
allocated,reserved -
含义:DRA 找到了可用的 GPU 并将其保留给这个 Pod
-
Pod 状态:立即开始运行
第二个吊舱 (training-pod-2)
-
州:
pending -
含义:等待 DRA 在同一 GPU 上配置时间切片
-
Pod 状态:等待调度
-
该州将从
pending变allocated,reserved为running
使用 MPS 优化 GPU 工作负
Multi-Process 服务 (MPS) 支持在单个 GPU 上并发执行多个 CUDA 上下文,隔离效果比时间切片更好。
请执行以下步骤。
-
使用名为
mps-claim-template.yaml:的文件为 MPS 定义ResourceClaimTemplate--- apiVersion: v1 kind: Namespace metadata: name: mps-gpu --- apiVersion: resource.k8s.io/v1beta1 kind: ResourceClaimTemplate metadata: name: mps-gpu-template namespace: mps-gpu spec: spec: devices: requests: - name: shared-gpu deviceClassName: gpu.nvidia.com config: - requests: ["shared-gpu"] opaque: driver: gpu.nvidia.com parameters: apiVersion: resource.nvidia.com/v1beta1 kind: GpuConfig sharing: strategy: MPS -
使用 MPS 定义一个 Pod,文件名为
mps-pod.yaml:--- # Single Pod with Multiple Containers sharing GPU via MPS apiVersion: v1 kind: Pod metadata: name: mps-multi-container-pod namespace: mps-gpu labels: app: mps-demo spec: restartPolicy: Never containers: # Container 1 - Inference workload - name: inference-container image: nvcr.io/nvidia/pytorch:25.04-py3 command: ["python", "-c"] args: - | import torch import torch.nn as nn import time import os print(f"=== INFERENCE CONTAINER STARTING ===") print(f"Process ID: {os.getpid()}") print(f"GPU available: {torch.cuda.is_available()}") print(f"GPU count: {torch.cuda.device_count()}") if torch.cuda.is_available(): device = torch.cuda.current_device() print(f"Current GPU: {torch.cuda.get_device_name(device)}") print(f"GPU Memory: {torch.cuda.get_device_properties(device).total_memory / 1024**3:.1f} GB") # Create inference model model = nn.Sequential( nn.Linear(1000, 500), nn.ReLU(), nn.Linear(500, 100) ).cuda() # Run inference for i in range(1, 999999): with torch.no_grad(): x = torch.randn(128, 1000).cuda() output = model(x) result = torch.sum(output) print(f"Inference Container PID {os.getpid()}: Batch {i}, Result: {result.item():.2f} at {time.strftime('%H:%M:%S')}") time.sleep(2) else: print("No GPU available!") time.sleep(60) resources: claims: - name: shared-gpu-claim request: shared-gpu # Container 2 - Training workload - name: training-container image: nvcr.io/nvidia/pytorch:25.04-py3 command: ["python", "-c"] args: - | import torch import torch.nn as nn import time import os print(f"=== TRAINING CONTAINER STARTING ===") print(f"Process ID: {os.getpid()}") print(f"GPU available: {torch.cuda.is_available()}") print(f"GPU count: {torch.cuda.device_count()}") if torch.cuda.is_available(): device = torch.cuda.current_device() print(f"Current GPU: {torch.cuda.get_device_name(device)}") print(f"GPU Memory: {torch.cuda.get_device_properties(device).total_memory / 1024**3:.1f} GB") # Create training model model = nn.Sequential( nn.Linear(2000, 1000), nn.ReLU(), nn.Linear(1000, 500), nn.ReLU(), nn.Linear(500, 10) ).cuda() criterion = nn.MSELoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # Run training for epoch in range(1, 999999): x = torch.randn(64, 2000).cuda() target = torch.randn(64, 10).cuda() optimizer.zero_grad() output = model(x) loss = criterion(output, target) loss.backward() optimizer.step() print(f"Training Container PID {os.getpid()}: Epoch {epoch}, Loss: {loss.item():.4f} at {time.strftime('%H:%M:%S')}") time.sleep(3) else: print("No GPU available!") time.sleep(60) resources: claims: - name: shared-gpu-claim request: shared-gpu resourceClaims: - name: shared-gpu-claim resourceClaimTemplateName: mps-gpu-template nodeSelector: NodeGroupType: "gpu-dra" nvidia.com/gpu.present: "true" tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule -
应用模板并创建多个 MPS 容器:
kubectl apply -f mps-claim-template.yaml kubectl apply -f mps-pod.yaml -
监控资源声明:
kubectl get resourceclaims -n mps-gpu -w下面是示例输出:
NAME STATE AGE mps-multi-container-pod-shared-gpu-claim-2p9kx allocated,reserved 86s
此配置演示了使用 NVIDIA Multi-Process 服务 (MPS) 通过动态资源分配 (DRA) 实现真正的 GPU 共享。与按顺序轮流使用 GPU 的工作负载的时间切片不同,MPS 允许两个容器在同一个物理 GPU 上同时运行。关键见解是,DRA MPS 共享需要在单个 Pod 中使用多个容器,而不是多个单独的 Pod。部署后,DRA 驱动程序会向 Pod 分配一个ResourceClaim,并自动配置 MPS 以允许推理容器和训练容器同时执行。
每个容器都有自己独立的 GPU 内存空间和计算资源,MPS 守护程序会协调对底层硬件的访问。您可以通过执行以下操作来验证它是否有效:
-
勾选
nvidia-smi,这会将两个容器显示为 M+C (MPS + Compute) 进程共享同一 GPU 设备。 -
监控来自两个容器的日志,这将显示交错的时间戳,证明可以同时执行。
这种方法允许互补的工作负载高效共享昂贵的 GPU 硬件,而不是让单个进程充分利用它,从而最大限度地提高 GPU 利用率。
容器 1:inference-container
root@mps-multi-container-pod:/workspace# nvidia-smi
Wed Jul 16 21:09:30 2025
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 570.158.01 Driver Version: 570.158.01 CUDA Version: 12.9 |
|-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA L4 On | 00000000:35:00.0 Off | 0 |
| N/A 48C P0 28W / 72W | 597MiB / 23034MiB | 0% E. Process |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| 0 N/A N/A 1 M+C python 246MiB |
+-----------------------------------------------------------------------------------------+
容器 2:training-container
root@mps-multi-container-pod:/workspace# nvidia-smi
Wed Jul 16 21:16:00 2025
+-----------------------------------------------------------------------------------------+
| NVIDIA-SMI 570.158.01 Driver Version: 570.158.01 CUDA Version: 12.9 |
|-----------------------------------------+------------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+========================+======================|
| 0 NVIDIA L4 On | 00000000:35:00.0 Off | 0 |
| N/A 51C P0 28W / 72W | 597MiB / 23034MiB | 0% E. Process |
| | | N/A |
+-----------------------------------------+------------------------+----------------------+
+-----------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=========================================================================================|
| 0 N/A N/A 1 M+C python 314MiB |
+-----------------------------------------------------------------------------------------+
使用 GPU 优化 GP Multi-Instance U 工作负
Multi-instance GPU (MIG) 提供硬件级分区,使用专用的计算和内存资源创建隔离的 GPU 实例。
使用具有各种配置文件的动态 MIG 分区需要 NVIDIA GPUWITH0—REBOOT=true在 MIG 管理器配置中启用对于成功部署 MIG 至关重要。
你需要 NVIDIA DRA 驱动程序
步骤 1:部署 NVIDIA GPU 操作员
-
添加 NVIDIA GPU 操作员存储库:
helm repo add nvidia https://nvidia.github.io/gpu-operator helm repo update -
创建
gpu-operator-values.yaml文件:driver: enabled: false mig: strategy: mixed migManager: enabled: true env: - name: WITH_REBOOT value: "true" config: create: true name: custom-mig-parted-configs default: "all-disabled" data: config.yaml: |- version: v1 mig-configs: all-disabled: - devices: all mig-enabled: false # P4D profiles (A100 40GB) p4d-half-balanced: - devices: [0, 1, 2, 3] mig-enabled: true mig-devices: "1g.5gb": 2 "2g.10gb": 1 "3g.20gb": 1 - devices: [4, 5, 6, 7] mig-enabled: false # P4DE profiles (A100 80GB) p4de-half-balanced: - devices: [0, 1, 2, 3] mig-enabled: true mig-devices: "1g.10gb": 2 "2g.20gb": 1 "3g.40gb": 1 - devices: [4, 5, 6, 7] mig-enabled: false devicePlugin: enabled: true config: name: "" create: false default: "" toolkit: enabled: true nfd: enabled: true gfd: enabled: true dcgmExporter: enabled: true serviceMonitor: enabled: true interval: 15s honorLabels: false additionalLabels: release: kube-prometheus-stack nodeStatusExporter: enabled: false operator: defaultRuntime: containerd runtimeClass: nvidia resources: limits: cpu: 500m memory: 350Mi requests: cpu: 200m memory: 100Mi daemonsets: tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule" nodeSelector: accelerator: nvidia priorityClassName: system-node-critical -
使用以下
gpu-operator-values.yaml文件安装 GPU 操作员:helm install gpu-operator nvidia/gpu-operator \ --namespace gpu-operator \ --create-namespace \ --version v25.3.1 \ --values gpu-operator-values.yaml此 Helm 图表部署了以下组件和多个 MIG 配置文件:
-
设备插件(GPU 资源调度)
-
DCGM 导出器(GPU 指标和监控)
-
节点功能发现(NFD-硬件标签)
-
GPU 功能发现(GFD- GPU-specific 标签)
-
MIG 管理器(Multi-instance GPU 分区)
-
容器工具包(GPU 容器运行时)
-
操作员控制器(生命周期管理)
-
-
验证部署 Pod:
kubectl get pods -n gpu-operator下面是示例输出:
NAME READY STATUS RESTARTS AGE gpu-feature-discovery-27rdq 1/1 Running 0 3h31m gpu-operator-555774698d-48brn 1/1 Running 0 4h8m nvidia-container-toolkit-daemonset-sxmh9 1/1 Running 1 (3h32m ago) 4h1m nvidia-cuda-validator-qb77g 0/1 Completed 0 3h31m nvidia-dcgm-exporter-cvzd7 1/1 Running 0 3h31m nvidia-device-plugin-daemonset-5ljm5 1/1 Running 0 3h31m nvidia-gpu-operator-node-feature-discovery-gc-67f66fc557-q5wkt 1/1 Running 0 4h8m nvidia-gpu-operator-node-feature-discovery-master-5d8ffddcsl6s6 1/1 Running 0 4h8m nvidia-gpu-operator-node-feature-discovery-worker-6t4w7 1/1 Running 1 (3h32m ago) 4h1m nvidia-gpu-operator-node-feature-discovery-worker-9w7g8 1/1 Running 0 4h8m nvidia-gpu-operator-node-feature-discovery-worker-k5fgs 1/1 Running 0 4h8m nvidia-mig-manager-zvf54 1/1 Running 1 (3h32m ago) 3h35m -
创建带有 P4de 托管节点组的 Amazon EKS 集群,用于测试 MIG 示例:
apiVersion: eksctl.io/v1alpha5 kind: ClusterConfig metadata: name: dra-eks-cluster region: us-east-1 version: '1.33' managedNodeGroups: # P4DE MIG Node Group with Capacity Block Reservation - name: p4de-mig-nodes amiFamily: AmazonLinux2023 instanceType: p4de.24xlarge # Capacity settings desiredCapacity: 0 minSize: 0 maxSize: 1 # Use specific subnet in us-east-1b for capacity reservation subnets: - us-east-1b # AL2023 NodeConfig for RAID0 local storage only nodeadmConfig: apiVersion: node.eks.aws/v1alpha1 kind: NodeConfig spec: instance: localStorage: strategy: RAID0 # Node labels for MIG configuration labels: nvidia.com/gpu.present: "true" nvidia.com/gpu.product: "A100-SXM4-80GB" nvidia.com/mig.config: "p4de-half-balanced" node-type: "p4de" vpc.amazonaws.com/efa.present: "true" accelerator: "nvidia" # Node taints taints: - key: nvidia.com/gpu value: "true" effect: NoSchedule # EFA support efaEnabled: true # Placement group for high-performance networking placementGroup: groupName: p4de-placement-group strategy: cluster # Capacity Block Reservation (CBR) # Ensure CBR ID matches the subnet AZ with the Nodegroup subnet spot: false capacityReservation: capacityReservationTarget: capacityReservationId: "cr-abcdefghij" # Replace with your capacity reservation IDNVIDIA GPU Operator 使用添加到节点的标签,
nvidia.com/mig.config: "p4de-half-balanced"并使用给定的配置文件对 GPU 进行分区。 -
登录实
p4de例。 -
运行如下命令:
nvidia-smi -L您应该会看到以下示例输出:
[root@ip-100-64-173-145 bin]# nvidia-smi -L GPU 0: NVIDIA A100-SXM4-80GB (UUID: GPU-ab52e33c-be48-38f2-119e-b62b9935925a) MIG 3g.40gb Device 0: (UUID: MIG-da972af8-a20a-5f51-849f-bc0439f7970e) MIG 2g.20gb Device 1: (UUID: MIG-7f9768b7-11a6-5de9-a8aa-e9c424400da4) MIG 1g.10gb Device 2: (UUID: MIG-498adad6-6cf7-53af-9d1a-10cfd1fa53b2) MIG 1g.10gb Device 3: (UUID: MIG-3f55ef65-1991-571a-ac50-0dbf50d80c5a) GPU 1: NVIDIA A100-SXM4-80GB (UUID: GPU-0eabeccc-7498-c282-0ac7-d3c09f6af0c8) MIG 3g.40gb Device 0: (UUID: MIG-80543849-ea3b-595b-b162-847568fe6e0e) MIG 2g.20gb Device 1: (UUID: MIG-3af1958f-fac4-59f1-8477-9f8d08c55029) MIG 1g.10gb Device 2: (UUID: MIG-401088d2-716f-527b-a970-b1fc7a4ac6b2) MIG 1g.10gb Device 3: (UUID: MIG-8c56c75e-5141-501c-8f43-8cf22f422569) GPU 2: NVIDIA A100-SXM4-80GB (UUID: GPU-1c7a1289-243f-7872-a35c-1d2d8af22dd0) MIG 3g.40gb Device 0: (UUID: MIG-e9b44486-09fc-591a-b904-0d378caf2276) MIG 2g.20gb Device 1: (UUID: MIG-ded93941-9f64-56a3-a9b1-a129c6edf6e4) MIG 1g.10gb Device 2: (UUID: MIG-6c317d83-a078-5c25-9fa3-c8308b379aa1) MIG 1g.10gb Device 3: (UUID: MIG-2b070d39-d4e9-5b11-bda6-e903372e3d08) GPU 3: NVIDIA A100-SXM4-80GB (UUID: GPU-9a6250e2-5c59-10b7-2da8-b61d8a937233) MIG 3g.40gb Device 0: (UUID: MIG-20e3cd87-7a57-5f1b-82e7-97b14ab1a5aa) MIG 2g.20gb Device 1: (UUID: MIG-04430354-1575-5b42-95f4-bda6901f1ace) MIG 1g.10gb Device 2: (UUID: MIG-d62ec8b6-e097-5e99-a60c-abf8eb906f91) MIG 1g.10gb Device 3: (UUID: MIG-fce20069-2baa-5dd4-988a-cead08348ada) GPU 4: NVIDIA A100-SXM4-80GB (UUID: GPU-5d09daf0-c2eb-75fd-3919-7ad8fafa5f86) GPU 5: NVIDIA A100-SXM4-80GB (UUID: GPU-99194e04-ab2a-b519-4793-81cb2e8e9179) GPU 6: NVIDIA A100-SXM4-80GB (UUID: GPU-c1a1910f-465a-e16f-5af1-c6aafe499cd6) GPU 7: NVIDIA A100-SXM4-80GB (UUID: GPU-c2cfafbc-fd6e-2679-e955-2a9e09377f78)
NVIDIA GPU Operator 已成功p4de-half-balanced将 MIG 配置文件应用于你的 P4DE 实例,按照配置创建了硬件级 GPU 分区。以下是分区的工作原理:
GPU 操作员应用了您的嵌入式 MIG 配置文件中的以下配置:
p4de-half-balanced:
- devices: [0, 1, 2, 3] # First 4 GPUs: MIG enabled
mig-enabled: true
mig-devices:
"1g.10gb": 2 # 2x small instances (10GB each)
"2g.20gb": 1 # 1x medium instance (20GB)
"3g.40gb": 1 # 1x large instance (40GB)
- devices: [4, 5, 6, 7] # Last 4 GPUs: Full GPUs
mig-enabled: false
根据你的nvidia-smi -L输出,GPU 操作员创建的内容如下:
-
MIG-enabled GPU (0-3):硬件已分区
-
GPU 0:英伟达 A100-SXM4-80GB
-
MIG 3g.40GB 设备 0 — 大型工作负载(40GB 内存,42 SM)
-
MIG 2g.20GB 设备 1 — 中型工作负载(20GB 内存,28 个 SM)
-
MIG 1g.10GB 设备 2 — 小型工作负载(10GB 内存,14 个 SM)
-
MIG 1g.10GB 设备 3 — 小型工作负载(10GB 内存,14 个 SM)
-
-
GPU 1:英伟达 A100-SXM4-80GB
-
MIG 3g.40GB 设备 0 — 分区布局相同
-
MIG 2g.20GB 设备 1
-
MIG 1g.10GB 设备 2
-
MIG 1g.10GB 设备 3
-
-
GPU 2 和 GPU 3 — 与 GPU 0 和 GPU 1 的模式相同
-
-
完整 GPU (4-7):没有 MIG 分区
-
GPU 4:NVIDIA A100-SXM4-80GB — 全 80GB GPU
-
GPU 5:NVIDIA A100-SXM4-80GB — 全 80GB GPU
-
GPU 6:NVIDIA A100-SXM4-80GB — 全 80GB GPU
-
GPU 7:NVIDIA A100-SXM4-80GB — 全 80GB GPU
-
NVIDIA GPU 操作员创建 MIG 分区后,NVIDIA DRA 驱动程序会自动检测这些硬件隔离的实例,并使其可用于在 Kubernetes 中进行动态资源分配。DRA 驱动程序会发现每个 MIG 实例及其特定配置文件(1g.10GB、2g.20GB、3g.40GB),并通过设备类别将其作为可调度资源公开。mig.nvidia.com
DRA 驱动程序持续监控 MIG 拓扑,并维护所有 GPU 上的可用实例清单。当 Pod 通过请求特定的 MIG 配置文件时ResourceClaimTemplate,DRA 驱动程序会智能地从任何可用的 GPU 中选择适当的 MIG 实例,从而实现真正的硬件级多租户。这种动态分配允许多个独立的工作负载在同一个物理 GPU 上同时运行,同时保持严格的资源边界和性能保证。
步骤 2:测试 MIG 资源分配
现在,让我们举一些示例,演示 DRA 如何动态地将 MIG 实例分配给不同的工作负载。部署resourceclaimtemplates并测试 pod,查看 DRA 驱动程序如何在可用的 MIG 分区中放置工作负载,从而允许多个容器在硬件级隔离下共享 GPU 资源。
-
创建
mig-claim-template.yaml以包含 MIG:resourceclaimtemplatesapiVersion: v1 kind: Namespace metadata: name: mig-gpu --- # Template for 3g.40gb MIG instance (Large training) apiVersion: resource.k8s.io/v1beta1 kind: ResourceClaimTemplate metadata: name: mig-large-template namespace: mig-gpu spec: spec: devices: requests: - name: mig-large deviceClassName: mig.nvidia.com selectors: - cel: expression: | device.attributes['gpu.nvidia.com'].profile == '3g.40gb' --- # Template for 2g.20gb MIG instance (Medium training) apiVersion: resource.k8s.io/v1beta1 kind: ResourceClaimTemplate metadata: name: mig-medium-template namespace: mig-gpu spec: spec: devices: requests: - name: mig-medium deviceClassName: mig.nvidia.com selectors: - cel: expression: | device.attributes['gpu.nvidia.com'].profile == '2g.20gb' --- # Template for 1g.10gb MIG instance (Small inference) apiVersion: resource.k8s.io/v1beta1 kind: ResourceClaimTemplate metadata: name: mig-small-template namespace: mig-gpu spec: spec: devices: requests: - name: mig-small deviceClassName: mig.nvidia.com selectors: - cel: expression: | device.attributes['gpu.nvidia.com'].profile == '1g.10gb' -
应用三个模板:
kubectl apply -f mig-claim-template.yaml -
运行如下命令:
kubectl get resourceclaimtemplates -n mig-gpu下面是示例输出:
NAME AGE mig-large-template 71m mig-medium-template 71m mig-small-template 71m -
创建
mig-pod.yaml以安排多个作业以利用这一点resourceclaimtemplates:--- # ConfigMap containing Python scripts for MIG pods apiVersion: v1 kind: ConfigMap metadata: name: mig-scripts-configmap namespace: mig-gpu data: large-training-script.py: | import torch import torch.nn as nn import torch.optim as optim import time import os print(f"=== LARGE TRAINING POD (3g.40gb) ===") print(f"Process ID: {os.getpid()}") print(f"GPU available: {torch.cuda.is_available()}") print(f"GPU count: {torch.cuda.device_count()}") if torch.cuda.is_available(): device = torch.cuda.current_device() print(f"Using GPU: {torch.cuda.get_device_name(device)}") print(f"GPU Memory: {torch.cuda.get_device_properties(device).total_memory / 1e9:.1f} GB") # Large model for 3g.40gb instance model = nn.Sequential( nn.Linear(2048, 1024), nn.ReLU(), nn.Linear(1024, 512), nn.ReLU(), nn.Linear(512, 256), nn.ReLU(), nn.Linear(256, 10) ).cuda() optimizer = optim.Adam(model.parameters()) criterion = nn.CrossEntropyLoss() print(f"Model parameters: {sum(p.numel() for p in model.parameters())}") # Training loop for epoch in range(100): # Large batch for 3g.40gb x = torch.randn(256, 2048).cuda() y = torch.randint(0, 10, (256,)).cuda() optimizer.zero_grad() output = model(x) loss = criterion(output, y) loss.backward() optimizer.step() if epoch % 10 == 0: print(f"Large Training - Epoch {epoch}, Loss: {loss.item():.4f}, GPU Memory: {torch.cuda.memory_allocated()/1e9:.2f}GB") time.sleep(3) print("Large training completed on 3g.40gb MIG instance") medium-training-script.py: | import torch import torch.nn as nn import torch.optim as optim import time import os print(f"=== MEDIUM TRAINING POD (2g.20gb) ===") print(f"Process ID: {os.getpid()}") print(f"GPU available: {torch.cuda.is_available()}") print(f"GPU count: {torch.cuda.device_count()}") if torch.cuda.is_available(): device = torch.cuda.current_device() print(f"Using GPU: {torch.cuda.get_device_name(device)}") print(f"GPU Memory: {torch.cuda.get_device_properties(device).total_memory / 1e9:.1f} GB") # Medium model for 2g.20gb instance model = nn.Sequential( nn.Linear(1024, 512), nn.ReLU(), nn.Linear(512, 256), nn.ReLU(), nn.Linear(256, 10) ).cuda() optimizer = optim.Adam(model.parameters()) criterion = nn.CrossEntropyLoss() print(f"Model parameters: {sum(p.numel() for p in model.parameters())}") # Training loop for epoch in range(100): # Medium batch for 2g.20gb x = torch.randn(128, 1024).cuda() y = torch.randint(0, 10, (128,)).cuda() optimizer.zero_grad() output = model(x) loss = criterion(output, y) loss.backward() optimizer.step() if epoch % 10 == 0: print(f"Medium Training - Epoch {epoch}, Loss: {loss.item():.4f}, GPU Memory: {torch.cuda.memory_allocated()/1e9:.2f}GB") time.sleep(4) print("Medium training completed on 2g.20gb MIG instance") small-inference-script.py: | import torch import torch.nn as nn import time import os print(f"=== SMALL INFERENCE POD (1g.10gb) ===") print(f"Process ID: {os.getpid()}") print(f"GPU available: {torch.cuda.is_available()}") print(f"GPU count: {torch.cuda.device_count()}") if torch.cuda.is_available(): device = torch.cuda.current_device() print(f"Using GPU: {torch.cuda.get_device_name(device)}") print(f"GPU Memory: {torch.cuda.get_device_properties(device).total_memory / 1e9:.1f} GB") # Small model for 1g.10gb instance model = nn.Sequential( nn.Linear(512, 256), nn.ReLU(), nn.Linear(256, 10) ).cuda() print(f"Model parameters: {sum(p.numel() for p in model.parameters())}") # Inference loop for i in range(200): with torch.no_grad(): # Small batch for 1g.10gb x = torch.randn(32, 512).cuda() output = model(x) prediction = torch.argmax(output, dim=1) if i % 20 == 0: print(f"Small Inference - Batch {i}, Predictions: {prediction[:5].tolist()}, GPU Memory: {torch.cuda.memory_allocated()/1e9:.2f}GB") time.sleep(2) print("Small inference completed on 1g.10gb MIG instance") --- # Pod 1: Large training workload (3g.40gb) apiVersion: v1 kind: Pod metadata: name: mig-large-training-pod namespace: mig-gpu labels: app: mig-large-training workload-type: training spec: restartPolicy: Never containers: - name: large-training-container image: nvcr.io/nvidia/pytorch:25.04-py3 command: ["python", "/scripts/large-training-script.py"] volumeMounts: - name: script-volume mountPath: /scripts readOnly: true resources: claims: - name: mig-large-claim resourceClaims: - name: mig-large-claim resourceClaimTemplateName: mig-large-template nodeSelector: node.kubernetes.io/instance-type: p4de.24xlarge nvidia.com/gpu.present: "true" tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule volumes: - name: script-volume configMap: name: mig-scripts-configmap defaultMode: 0755 --- # Pod 2: Medium training workload (2g.20gb) - can run on SAME GPU as Pod 1 apiVersion: v1 kind: Pod metadata: name: mig-medium-training-pod namespace: mig-gpu labels: app: mig-medium-training workload-type: training spec: restartPolicy: Never containers: - name: medium-training-container image: nvcr.io/nvidia/pytorch:25.04-py3 command: ["python", "/scripts/medium-training-script.py"] volumeMounts: - name: script-volume mountPath: /scripts readOnly: true resources: claims: - name: mig-medium-claim resourceClaims: - name: mig-medium-claim resourceClaimTemplateName: mig-medium-template nodeSelector: node.kubernetes.io/instance-type: p4de.24xlarge nvidia.com/gpu.present: "true" tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule volumes: - name: script-volume configMap: name: mig-scripts-configmap defaultMode: 0755 --- # Pod 3: Small inference workload (1g.10gb) - can run on SAME GPU as Pod 1 & 2 apiVersion: v1 kind: Pod metadata: name: mig-small-inference-pod namespace: mig-gpu labels: app: mig-small-inference workload-type: inference spec: restartPolicy: Never containers: - name: small-inference-container image: nvcr.io/nvidia/pytorch:25.04-py3 command: ["python", "/scripts/small-inference-script.py"] volumeMounts: - name: script-volume mountPath: /scripts readOnly: true resources: claims: - name: mig-small-claim resourceClaims: - name: mig-small-claim resourceClaimTemplateName: mig-small-template nodeSelector: node.kubernetes.io/instance-type: p4de.24xlarge nvidia.com/gpu.present: "true" tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule volumes: - name: script-volume configMap: name: mig-scripts-configmap defaultMode: 0755 -
应用这个规范,它应该部署三个 Pod:
kubctl apply -f mig-pod.yaml这些 Pod 应由 DRA 驱动程序调度。
-
查看 DRA 驱动程序 Pod 日志,您将看到类似于以下内容的输出:
I0717 21:50:22.925811 1 driver.go:87] NodePrepareResource is called: number of claims: 1 I0717 21:50:22.932499 1 driver.go:129] Returning newly prepared devices for claim '933e9c72-6fd6-49c5-933c-a896407dc6d1': [&Device{RequestNames:[mig-large],PoolName:ip-100-64-173-145.ec2.internal,DeviceName:gpu-0-mig-9-4-4,CDIDeviceIDs:[k8s.gpu.nvidia.com/device=**gpu-0-mig-9-4-4**],}] I0717 21:50:23.186472 1 driver.go:87] NodePrepareResource is called: number of claims: 1 I0717 21:50:23.191226 1 driver.go:129] Returning newly prepared devices for claim '61e5ddd2-8c2e-4c19-93ae-d317fecb44a4': [&Device{RequestNames:[mig-medium],PoolName:ip-100-64-173-145.ec2.internal,DeviceName:gpu-2-mig-14-0-2,CDIDeviceIDs:[k8s.gpu.nvidia.com/device=**gpu-2-mig-14-0-2**],}] I0717 21:50:23.450024 1 driver.go:87] NodePrepareResource is called: number of claims: 1 I0717 21:50:23.455991 1 driver.go:129] Returning newly prepared devices for claim '1eda9b2c-2ea6-401e-96d0-90e9b3c111b5': [&Device{RequestNames:[mig-small],PoolName:ip-100-64-173-145.ec2.internal,DeviceName:gpu-1-mig-19-2-1,CDIDeviceIDs:[k8s.gpu.nvidia.com/device=**gpu-1-mig-19-2-1**],}] -
验证
resourceclaims以查看 Pod 状态:kubectl get resourceclaims -n mig-gpu -w下面是示例输出:
NAME STATE AGE mig-large-training-pod-mig-large-claim-6dpn8 pending 0s mig-large-training-pod-mig-large-claim-6dpn8 pending 0s mig-large-training-pod-mig-large-claim-6dpn8 allocated,reserved 0s mig-medium-training-pod-mig-medium-claim-bk596 pending 0s mig-medium-training-pod-mig-medium-claim-bk596 pending 0s mig-medium-training-pod-mig-medium-claim-bk596 allocated,reserved 0s mig-small-inference-pod-mig-small-claim-d2t58 pending 0s mig-small-inference-pod-mig-small-claim-d2t58 pending 0s mig-small-inference-pod-mig-small-claim-d2t58 allocated,reserved 0s如你所见,所有 Pod 都从 “待处理” 移至
allocated,reservedDRA 驱动程序。 -
nvidia-smi从节点运行。你会注意到有三个 Python 处理器正在运行:root@ip-100-64-173-145 bin]# nvidia-smi +-----------------------------------------------------------------------------------------+ | NVIDIA-SMI 570.158.01 Driver Version: 570.158.01 CUDA Version: 12.8 | |-----------------------------------------+------------------------+----------------------+ | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |=========================================+========================+======================| | 0 NVIDIA A100-SXM4-80GB On | 00000000:10:1C.0 Off | On | | N/A 63C P0 127W / 400W | 569MiB / 81920MiB | N/A Default | | | | Enabled | +-----------------------------------------+------------------------+----------------------+ | 1 NVIDIA A100-SXM4-80GB On | 00000000:10:1D.0 Off | On | | N/A 56C P0 121W / 400W | 374MiB / 81920MiB | N/A Default | | | | Enabled | +-----------------------------------------+------------------------+----------------------+ | 2 NVIDIA A100-SXM4-80GB On | 00000000:20:1C.0 Off | On | | N/A 63C P0 128W / 400W | 467MiB / 81920MiB | N/A Default | | | | Enabled | +-----------------------------------------+------------------------+----------------------+ | 3 NVIDIA A100-SXM4-80GB On | 00000000:20:1D.0 Off | On | | N/A 57C P0 118W / 400W | 249MiB / 81920MiB | N/A Default | | | | Enabled | +-----------------------------------------+------------------------+----------------------+ | 4 NVIDIA A100-SXM4-80GB On | 00000000:90:1C.0 Off | 0 | | N/A 51C P0 77W / 400W | 0MiB / 81920MiB | 0% Default | | | | Disabled | +-----------------------------------------+------------------------+----------------------+ | 5 NVIDIA A100-SXM4-80GB On | 00000000:90:1D.0 Off | 0 | | N/A 46C P0 69W / 400W | 0MiB / 81920MiB | 0% Default | | | | Disabled | +-----------------------------------------+------------------------+----------------------+ | 6 NVIDIA A100-SXM4-80GB On | 00000000:A0:1C.0 Off | 0 | | N/A 52C P0 74W / 400W | 0MiB / 81920MiB | 0% Default | | | | Disabled | +-----------------------------------------+------------------------+----------------------+ | 7 NVIDIA A100-SXM4-80GB On | 00000000:A0:1D.0 Off | 0 | | N/A 47C P0 72W / 400W | 0MiB / 81920MiB | 0% Default | | | | Disabled | +-----------------------------------------+------------------------+----------------------+ +-----------------------------------------------------------------------------------------+ | MIG devices: | +------------------+----------------------------------+-----------+-----------------------+ | GPU GI CI MIG | Memory-Usage | Vol| Shared | | ID ID Dev | BAR1-Usage | SM Unc| CE ENC DEC OFA JPG | | | | ECC| | |==================+==================================+===========+=======================| | 0 2 0 0 | 428MiB / 40192MiB | 42 0 | 3 0 2 0 0 | | | 2MiB / 32767MiB | | | +------------------+----------------------------------+-----------+-----------------------+ | 0 3 0 1 | 71MiB / 19968MiB | 28 0 | 2 0 1 0 0 | | | 0MiB / 16383MiB | | | +------------------+----------------------------------+-----------+-----------------------+ | 0 9 0 2 | 36MiB / 9728MiB | 14 0 | 1 0 0 0 0 | | | 0MiB / 8191MiB | | | +------------------+----------------------------------+-----------+-----------------------+ | 0 10 0 3 | 36MiB / 9728MiB | 14 0 | 1 0 0 0 0 | | | 0MiB / 8191MiB | | | +------------------+----------------------------------+-----------+-----------------------+ | 1 1 0 0 | 107MiB / 40192MiB | 42 0 | 3 0 2 0 0 | | | 0MiB / 32767MiB | | | +------------------+----------------------------------+-----------+-----------------------+ | 1 5 0 1 | 71MiB / 19968MiB | 28 0 | 2 0 1 0 0 | | | 0MiB / 16383MiB | | | +------------------+----------------------------------+-----------+-----------------------+ | 1 13 0 2 | 161MiB / 9728MiB | 14 0 | 1 0 0 0 0 | | | 2MiB / 8191MiB | | | +------------------+----------------------------------+-----------+-----------------------+ | 1 14 0 3 | 36MiB / 9728MiB | 14 0 | 1 0 0 0 0 | | | 0MiB / 8191MiB | | | +------------------+----------------------------------+-----------+-----------------------+ | 2 1 0 0 | 107MiB / 40192MiB | 42 0 | 3 0 2 0 0 | | | 0MiB / 32767MiB | | | +------------------+----------------------------------+-----------+-----------------------+ | 2 5 0 1 | 289MiB / 19968MiB | 28 0 | 2 0 1 0 0 | | | 2MiB / 16383MiB | | | +------------------+----------------------------------+-----------+-----------------------+ | 2 13 0 2 | 36MiB / 9728MiB | 14 0 | 1 0 0 0 0 | | | 0MiB / 8191MiB | | | +------------------+----------------------------------+-----------+-----------------------+ | 2 14 0 3 | 36MiB / 9728MiB | 14 0 | 1 0 0 0 0 | | | 0MiB / 8191MiB | | | +------------------+----------------------------------+-----------+-----------------------+ | 3 1 0 0 | 107MiB / 40192MiB | 42 0 | 3 0 2 0 0 | | | 0MiB / 32767MiB | | | +------------------+----------------------------------+-----------+-----------------------+ | 3 5 0 1 | 71MiB / 19968MiB | 28 0 | 2 0 1 0 0 | | | 0MiB / 16383MiB | | | +------------------+----------------------------------+-----------+-----------------------+ | 3 13 0 2 | 36MiB / 9728MiB | 14 0 | 1 0 0 0 0 | | | 0MiB / 8191MiB | | | +------------------+----------------------------------+-----------+-----------------------+ | 3 14 0 3 | 36MiB / 9728MiB | 14 0 | 1 0 0 0 0 | | | 0MiB / 8191MiB | | | +------------------+----------------------------------+-----------+-----------------------+ +-----------------------------------------------------------------------------------------+ | Processes: | | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=========================================================================================| **| 0 2 0 64080 C python 312MiB | | 1 13 0 64085 C python 118MiB | | 2 5 0 64073 C python 210MiB |** +-----------------------------------------------------------------------------------------+
使用 GB200 P6e 实例通过 IMEX 优化 GPU 工作负载
IMEX(节点间内存交换)支持跨节点的内存一致性通信,以便在 NVIDIA GB200 上进行分布式训练。 UltraServers
请执行以下步骤。
-
使用名
ComputeDomainimex-compute-domain.yaml为以下的文件为多节点训练定义一个apiVersion: resource.nvidia.com/v1beta1 kind: ComputeDomain metadata: name: distributed-training-domain namespace: default spec: numNodes: 2 channel: resourceClaimTemplate: name: imex-channel-template -
使用 IMEX 通道定义一个 Pod,文件名为:
imex-pod.yamlapiVersion: v1 kind: Pod metadata: name: imex-distributed-training namespace: default labels: app: imex-training spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: nvidia.com/gpu.clique operator: Exists containers: - name: distributed-training image: nvcr.io/nvidia/pytorch:25.04-py3 command: ["bash", "-c"] args: - | echo "=== IMEX Channel Verification ===" ls -la /dev/nvidia-caps-imex-channels/ echo "" echo "=== GPU Information ===" nvidia-smi echo "" echo "=== NCCL Test (if available) ===" python -c " import torch import torch.distributed as dist import os print(f'CUDA available: {torch.cuda.is_available()}') print(f'CUDA device count: {torch.cuda.device_count()}') if torch.cuda.is_available(): for i in range(torch.cuda.device_count()): print(f'GPU {i}: {torch.cuda.get_device_name(i)}') # Check for IMEX environment variables imex_vars = [k for k in os.environ.keys() if 'IMEX' in k or 'NVLINK' in k] if imex_vars: print('IMEX Environment Variables:') for var in imex_vars: print(f' {var}={os.environ[var]}') print('IMEX channel verification completed') " # Keep container running for inspection sleep 3600 resources: claims: - name: imex-channel-0 - name: imex-channel-1 resourceClaims: - name: imex-channel-0 resourceClaimTemplateName: imex-channel-template - name: imex-channel-1 resourceClaimTemplateName: imex-channel-template tolerations: - key: nvidia.com/gpu operator: Exists effect: NoSchedule注意
这需要 P6e GB200 实例。
-
通过应用
ComputeDomain和模板部署 IMEX:kubectl apply -f imex-claim-template.yaml kubectl apply -f imex-compute-domain.yaml kubectl apply -f imex-pod.yaml -
检查
ComputeDomain状态。kubectl get computedomain distributed-training-domain -
监控 IMEX 守护程序的部署。
kubectl get pods -n nvidia-dra-driver -l resource.nvidia.com/computeDomain -
在 Pod 中查看 IMEX 频道:
kubectl exec imex-distributed-training -- ls -la /dev/nvidia-caps-imex-channels/ -
查看 Pod 日志:
kubectl logs imex-distributed-training以下是预期输出的示例:
=== IMEX Channel Verification === total 0 drwxr-xr-x. 2 root root 80 Jul 8 10:45 . drwxr-xr-x. 6 root root 380 Jul 8 10:45 .. crw-rw-rw-. 1 root root 241, 0 Jul 8 10:45 channel0 crw-rw-rw-. 1 root root 241, 1 Jul 8 10:45 channel1
有关更多信息,请参阅上的 NVIDIA 示例