MLOps 是一组在生产环境中部署、监视和管理机器学习模型的做法。
本文中介绍的关键 MLOps 最佳做法:
- 基础结构即代码 (IaC)
- 容器化
- 模型管理和版本控制
- 自动化
- 可伸缩性和资源管理
- 长时间运行的批处理作业可靠性
- 安全性与符合性
本文介绍在 AKS 中使用 MLOps 时要记住的最佳做法和注意事项。 有关 MLOps 的详细信息,请参阅用于 AI 和机器学习工作流的机器学习运营 (MLOps)。
基础结构即代码 (IaC)
关键要点:为 AI 管道的每个阶段定义和版本 IaC 模板,以确保一致性、成本效益和更快的部署。
IaC 可为各种应用类型实现一致且可重现的基础设施预配和管理。 随着应用部署方式的不同,你的 IaC 实施方案可能会在整个 AI 流水线中发生变化,因为推理、模型服务、训练和模型微调所需的算力和资源可能各不相同。 为 AI 开发人员团队定义和版本控制 IaC 模板有助于确保跨作业类型的一致性和成本效益,同时阐明硬件要求并加快部署过程。
在 AKS 标准版中,IaC 通常包括更明确的群集平台设置,例如网络、缩放和操作配置选项。
容器化
关键要点:容器映像中的包模型权重、元数据和配置,以实现可移植性、简化版本控制并减少存储成本。
通过在容器映像中管理模型权重、元数据和配置,可实现可移植性、简化版本控制并随着时间的推移降低存储成本。 通过容器化,可:
- 使用存储在安全容器注册表中的现有容器镜像,尤其是用于参数规模从数百万到数十亿的大型语言模型(LLM)和 Stable Diffusion 模型的容器镜像。
- 通过使用多个分别包含各任务所需特定依赖项的轻量级容器,而不是维护一个大型镜像,来避免流水线中的单点故障。
- 将大型文本和图像数据集存储在基本容器映像之外,并在运行时根据需要引用它们。
ML 工作负载的 Dockerfile 模式:
对于 GPU 训练工作负荷,请使用启用了 CUDA 的显式基础映像标记(例如,pytorch/pytorch:2.4.1-cuda12.4-cudnn9-runtime),而不要使用未带标记的基础映像引用。 在生产环境中将镜像固定到摘要,以确保构建可重现,并使 CUDA/cuDNN 的行为可预测。 维护彼此独立的 CPU 和 GPU 镜像变体,以便调度位置和运行时依赖关系保持明确。
模型管理和版本控制
关键要点:系统地对模型进行版本控制,以在整个环境中保持一致性,并使用参数高效的微调方法实现更快的迭代。
模型管理和版本控制对于跟踪模型随时间的更改至关重要。 通过对模型进行版本控制,可:
- 保持模型容器之间的一致性,以便于在不同环境中部署。
- 采用参数高效微调 (PEFT) 方法在模型权重子集上更快地迭代,并在轻量容器中维护新版本。
在 AKS 标准版中,团队通常需要通过平台和部署配置来更明确地确保一致性。
自动化
关键要点:自动执行数据引入、模型性能监视和重新训练管道,以减少手动错误并确保 ML 生命周期的一致性。
自动化有助于减少手动错误、提高效率并确保 ML 生命周期的一致性。 通过自动执行任务,可:
- 集成警报工具,以在新的数据流流入应用程序时触发向量引入流。
- 设置模型性能阈值,以跟踪降级并触发重新训练管道。
在 AKS 标准版中,除了模型质量触发器外,还包括策略验证、配置偏移检测和发布治理的自动化。
可伸缩性和资源管理
关键要点:通过分布式计算、自动缩放和灾难恢复计划优化资源使用情况,以经济高效地处理各种 AI 管道需求。
可伸缩性和资源管理对于确保 AI 管道可处理应用程序的需求至关重要。 通过优化资源使用状况,可:
- 通过分布式计算和多个级别的并行度(例如数据、模型和管道并行度),集成有效使用分配的 CPU、GPU 和内存资源的工具。
- 对计算资源启用自动缩放,以支持高峰时段的高模型请求量,并在非高峰时段纵向缩减。
- 遵循 AKS 复原和可靠性最佳做法规划灾难恢复。
运行长时间运行的批处理任务
关键要点:将有限任务作为 Kubernetes Job 运行,将进度持久化到 Pod 外部,处理终止信号,并假定该作业可能会启动多次。
节点升级、重新启动、缩减事件、资源压力、抢占和基础结构故障可能会中断长时间运行的批处理作业。 Kubernetes Job 将替换失败或删除的 Pod,但替换操作将在另一个节点上启动,而无需之前 Pod 的本地文件或进程内存。 将应用程序设计为能够从持久化状态恢复,并安全地重复执行任务。
将这些做法用于普通 CPU、内存或 I/O 密集型批处理作业。 当分布式工作负载需要多个工作节点同时启动,或者团队需要共享配额时,适用组调度和工作负载队列指南。 单工作节点或独立并行的批处理作业通常不需要组调度。
配置检查点、重试和终止
以下示例处理一系列工作项,并将下一个项记录到 Azure 文件存储 持久卷中。 它会在 Pod 替换后恢复检查点,并在进程接收到 SIGTERM 时保存进度:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: batch-checkpoints
spec:
accessModes:
- ReadWriteMany
storageClassName: azurefile-csi
resources:
requests:
storage: 10Gi
---
apiVersion: batch/v1
kind: Job
metadata:
name: checkpointed-batch
spec:
backoffLimit: 6
activeDeadlineSeconds: 86400
ttlSecondsAfterFinished: 86400
podFailurePolicy:
rules:
- action: Ignore
onPodConditions:
- type: DisruptionTarget
template:
metadata:
labels:
app: checkpointed-batch
spec:
restartPolicy: Never
terminationGracePeriodSeconds: 120
containers:
- name: worker
image: ubuntu:24.04
command:
- /bin/bash
- -c
- |
set -euo pipefail
CHECKPOINT=/checkpoints/next-item
NEXT_ITEM=1
if [[ -f "${CHECKPOINT}" ]]; then
NEXT_ITEM="$(cat "${CHECKPOINT}")"
echo "Resuming at item ${NEXT_ITEM}."
fi
save_checkpoint() {
printf '%s\n' "${NEXT_ITEM}" > "${CHECKPOINT}.tmp"
mv "${CHECKPOINT}.tmp" "${CHECKPOINT}"
echo "Saved checkpoint for item ${NEXT_ITEM}."
}
terminate() {
echo "Received a termination signal."
save_checkpoint
exit 143
}
trap terminate TERM INT
while (( NEXT_ITEM <= 1000 )); do
echo "Processing item ${NEXT_ITEM}."
sleep 10
NEXT_ITEM=$((NEXT_ITEM + 1))
save_checkpoint
done
echo "Batch completed."
resources:
requests:
cpu: "1"
memory: 1Gi
limits:
cpu: "1"
memory: 1Gi
volumeMounts:
- name: checkpoints
mountPath: /checkpoints
volumes:
- name: checkpoints
persistentVolumeClaim:
claimName: batch-checkpoints
将示例循环替换为批处理应用程序,并选择满足其吞吐量和恢复要求的存储。 当应用程序将检查点直接写入 Azure Blob 存储或其他 Azure 服务时,请使用工作负载标识,而不要使用内嵌凭据。
有意应用这些可靠性控件:
-
检查点和幂等性:按满足恢复点目标的时间间隔保存进度。 将检查点和提交的输出存储在持久性存储上,而不是容器文件系统
emptyDir或节点本地存储。 请使用原子写入、幂等键、事务性输出或去重机制,因为同一个 Job 程序有时会启动多次。 -
重启行为:作业允许
restartPolicy: Never或OnFailure。 使用Never时,容器失败会导致 Pod 失败,而 Job 控制器会创建一个替代 Pod。 借助OnFailure,kubelet 可以在同一个 Pod 中重启容器。 当将失败的 Pod 和日志分开能让诊断更容易,并且无论选择哪种路径都能安全恢复时,请使用Never。 -
重试限制:根据工作负荷可以容忍的暂时性故障数进行设置
backoffLimit。 使用podFailurePolicy可在遇到已知的不可重试退出代码时立即失败,或者如示例所示,防止主动中断耗尽重试预算。 中断事件仍然会导致 Pod 停止;该策略只会改变 Job 如何统计这次失败。 -
截止时间:设置
activeDeadlineSeconds作业在最大总运行时之后必须停止的时间。 截止时间包括重试,并且优先于backoffLimit。 不要将截止时间设置为短于预期的运行时加上重试和恢复时间。 失败的作业不会自动作为新作业重新启动。 -
正常终止:在应用程序中处理
SIGTERM并设置terminationGracePeriodSeconds足够长的时间以停止接受新工作、完成或放弃当前单元、刷新输出并保存检查点。 如果进程超过宽限期,Kubernetes 会发送SIGKILL,因此定期检查点仍是必需的。 -
清理:设置
ttlSecondsAfterFinished或配置 CronJob 历史记录限制,以防止已完成的作业和 Pod 累积。 保留足够长的作业记录,以便进行日志收集和事件调查。
规划逐出和节点维护
不要将阻止驱逐视为长期运行作业的主要恢复策略。 对于可重启的批处理作业,通常不会创建 PodDisruptionBudget (PDB);作业控制器在自愿中断后创建替换 Pod。 PDB 只能防止自愿逐出,不能防止节点故障、因资源压力导致的逐出或抢占。 允许零中断的 PDB 还可以阻止节点耗尽和延迟 AKS 升级。
仅当并行批处理工作负载必须保留最小数量的并发运行工作进程,且已对其腾空行为进行过测试时,才使用 PDB。 同样, cluster-autoscaler.kubernetes.io/safe-to-evict: "false" 仅用于特殊、不可启动的工作。 批注可以防止纵向缩减和增加成本,但它不会防止节点故障或每次维护事件。
AKS 在替换旧节点之前升级封锁并清空旧节点。 配置 计划内维护时段 ,使支持的 AKS 维护与影响较低的时段保持一致,但保持工作负荷可重启,因为维护时间不会消除计划外中断。 对于 AKS 标准层,如果批处理作业需要单独的 VM 大小、扩缩限制、污点或升级时间安排,请在专用用户节点池上运行这些作业。 避免将无法从抢占终止中恢复的工作部署到 Spot 节点池中。 在节点维护之前,请验证最近的检查点是否可用,以及另一个符合条件的节点池是否有足够的配额和容量来运行替换 Pod。
监视作业进度和恢复
排查长时间运行的作业时,请结合使用 Kubernetes 状态信息、事件和日志:
kubectl get job checkpointed-batch --watch
kubectl describe job checkpointed-batch
kubectl get pods -l batch.kubernetes.io/job-name=checkpointed-batch
kubectl logs job/checkpointed-batch
启用适用于 Prometheus 和 Container Insights 的 Azure Monitor 托管服务,以保留日志,并将作业状态与 Pod 重启、驱逐、处于 Pending 状态的时间、节点运行状况和资源利用率关联起来。 使用业务进度信号(例如项目已完成、项目失败、上次成功检查点时间、处理速率、重试计数和估计完成时间)检测应用程序。 对以下情况发出警报:作业失败、作业持续时间超出预期、在恢复点目标规定时间内没有检查点、Pod 被重复替换、Pod 长时间处于 Pending 状态,以及处理吞吐量停滞。
安全性与符合性
关键要点:实施 CVE 扫描、审核线索和合规性控制来保护数据,并满足 SOC 2、HIPAA 和 GDPR 等法规要求。
安全性和符合性对于保护数据并确保 AI 管道满足法规要求至关重要。 通过实现安全性和符合性最佳做法,可:
- 集成对常见漏洞和暴露(CVE)的扫描,以检测开源模型容器映像上的常见漏洞。
- 将 Microsoft Defender for Containers 用于 Azure 容器注册表中存储的模型容器映像。
- 维护对引入的数据、模型更改和指标的审核线索,以保持符合你的组织策略。
- 支持合规性框架(例如 SOC 2(通过Azure的审核日志记录和访问控制)、HIPAA(通过静态加密和传输、网络隔离)和 GDPR(通过数据驻留选项和访问管理策略)。
计划 GPU 工作负荷
关键要点:通过 NVIDIA 设备插件公开 GPU 资源,显式请求 GPU,并使用标签、污点和容忍度将昂贵的 GPU 节点隔离开来。
Kubernetes 将 GPU 作为扩展资源进行调度。 NVIDIA 设备插件在节点上注册 GPU 后,Pod 会通过在 nvidia.com/gpu 和 resources.requests 中均设置 resources.limits 来请求 GPU。 以下 Pod 请求一个 GPU,并以带有标签 accelerator=nvidia 的节点池为目标:
apiVersion: v1
kind: Pod
metadata:
name: gpu-training-pod
labels:
app: gpu-training
spec:
restartPolicy: Never
nodeSelector:
accelerator: nvidia
tolerations:
- key: sku
operator: Equal
value: gpu
effect: NoSchedule
containers:
- name: trainer
image: nvcr.io/nvidia/cuda:12.4.1-runtime-ubuntu22.04
command:
- /bin/bash
- -c
- |
set -e
nvidia-smi
echo "GPU is available to the training container."
resources:
requests:
cpu: "1"
memory: 2Gi
nvidia.com/gpu: 1
limits:
cpu: "1"
memory: 2Gi
nvidia.com/gpu: 1
扩展资源未被过度分配。 Kubernetes 仅在指定限制时将 GPU 限制视为 GPU 请求,但设置这两个字段会使工作负荷意向明确,并帮助策略验证。
预配 AKS GPU 节点池
对于 AKS 标准版,请创建具有受支持的 NVIDIA GPU VM 大小的专用用户节点池。 以下 Azure CLI 示例创建一个自动缩放的 Standard_NC4as_T4_v3 节点池,应用前述 Pod 使用的标签,并为这些节点添加污点,以排斥未显式容忍 GPU 节点的工作负载:
RESOURCE_GROUP=myResourceGroup
AKS_CLUSTER=myAKSCluster
az aks nodepool add \
--resource-group "$RESOURCE_GROUP" \
--cluster-name "$AKS_CLUSTER" \
--name gpunp \
--mode User \
--node-vm-size Standard_NC4as_T4_v3 \
--node-count 0 \
--enable-cluster-autoscaler \
--min-count 0 \
--max-count 4 \
--labels accelerator=nvidia workload=training \
--node-taints sku=gpu:NoSchedule
在创建节点池之前,请验证 VM SKU 是否在群集区域中可用,以及订阅是否具有足够的区域和 VM 系列配额。 根据训练框架所需的 GPU 内存、GPU 计数、CPU 与 GPU 比率、本地存储、网络和 CUDA 功能选择 NC 系列大小。 例如,NCas T4 v3 大小适用于许多单 GPU 和较小的训练工作负荷,而 NC A100 v4 大小支持更大的模型和多实例 GPU 配置。
验证或安装 NVIDIA 设备插件
AKS GPU 配置可以提供托管的 GPU 驱动程序和设备插件集成。 在安装另一个插件之前验证有效配置:
kubectl get nodes -L accelerator,kubernetes.azure.com/agentpool
kubectl get daemonsets --all-namespaces | grep -i nvidia
kubectl describe node | grep -A5 -E "Capacity:|Allocatable:|nvidia.com/gpu"
不要在同一节点上运行多个 NVIDIA 设备插件 DaemonSet。 如果 AKS 配置不管理插件,则以下独立 DaemonSet 向 kubelet 注册 NVIDIA GPU。 安装 NVIDIA 驱动程序和 Kubernetes 版本支持的设备插件版本:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nvidia-device-plugin
namespace: kube-system
labels:
app.kubernetes.io/name: nvidia-device-plugin
spec:
selector:
matchLabels:
app.kubernetes.io/name: nvidia-device-plugin
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
app.kubernetes.io/name: nvidia-device-plugin
spec:
priorityClassName: system-node-critical
nodeSelector:
accelerator: nvidia
tolerations:
- operator: Exists
containers:
- name: nvidia-device-plugin
image: nvcr.io/nvidia/k8s-device-plugin:v0.17.1
args:
- --fail-on-init-error=false
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
volumeMounts:
- name: device-plugin
mountPath: /var/lib/kubelet/device-plugins
volumes:
- name: device-plugin
hostPath:
path: /var/lib/kubelet/device-plugins
type: Directory
在提交训练作业之前,确认 nvidia.com/gpu 是否显示在每个 GPU 节点的可分配资源下。
使用 MIG 对 A100 和 H100 GPU 进行分区
NVIDIA 多实例 GPU(MIG)可以将支持的 A100 或 H100 GPU 分区为独立的 GPU 实例。 MIG 对于超参数优化和不需要整个物理 GPU 的较小微调作业非常有用。 对于需要全部 GPU 内存、最大互连带宽,或所安装 GPU 不支持的配置的工作负载,请使用完整的 GPU。
如果需要声明性 MIG 生命周期管理,请使用 NVIDIA GPU 操作员和 MIG 管理器。 操作员应拥有受影响节点的设备插件和 MIG 配置;不要将其与第二个独立的设备插件合并。 确切的配置名称和实例数量取决于 GPU 型号和内存容量。 以下配置在支持的 80 GB A100 或 H100 配置上创建 1g.10gb 7 个实例:
apiVersion: v1
kind: Namespace
metadata:
name: gpu-operator
---
apiVersion: v1
kind: ConfigMap
metadata:
name: mig-parted-config
namespace: gpu-operator
data:
config.yaml: |
version: v1
mig-configs:
all-disabled:
- devices: all
mig-enabled: false
hptuning-1g10gb:
- devices: all
mig-enabled: true
mig-devices:
"1g.10gb": 7
---
apiVersion: v1
kind: Namespace
metadata:
name: ml-training
---
apiVersion: batch/v1
kind: Job
metadata:
name: mig-hyperparameter-trial
namespace: ml-training
labels:
workload: hyperparameter-tuning
spec:
backoffLimit: 2
template:
metadata:
labels:
workload: hyperparameter-tuning
spec:
restartPolicy: Never
nodeSelector:
accelerator: nvidia
nvidia.com/mig.config: hptuning-1g10gb
nvidia.com/mig.config.state: success
tolerations:
- key: sku
operator: Equal
value: gpu
effect: NoSchedule
containers:
- name: trial
image: nvcr.io/nvidia/cuda:12.4.1-runtime-ubuntu22.04
command:
- /bin/bash
- -c
- |
set -e
nvidia-smi
echo "Starting one hyperparameter trial on a MIG instance."
sleep 30
resources:
requests:
cpu: "2"
memory: 8Gi
nvidia.com/mig-1g.10gb: 1
limits:
cpu: "2"
memory: 8Gi
nvidia.com/mig-1g.10gb: 1
将 GPU 操作员的 MIG 管理器配置为使用 mig-parted-config ConfigMap,在工作负荷请求命名 MIG 配置文件时使用 mixed MIG 策略,然后标记目标节点:
kubectl label nodes \
-l accelerator=nvidia \
nvidia.com/mig.config=hptuning-1g10gb \
--overwrite
更改 MIG 布局会中断已使用 GPU 的工作负载。 将目标节点设为不可调度并将其腾空,在维护窗口期间应用该配置,并在提交作业之前确认所请求的配置档案存在于节点的可分配资源中。
使用污点和容忍度隔离 GPU 工作负载
NoSchedule 污点使普通应用 Pod 远离昂贵的 GPU 节点。 节点池创建示例适用 sku=gpu:NoSchedule。 以下完整 Job 包含匹配的容忍度和节点选择器:
apiVersion: batch/v1
kind: Job
metadata:
name: isolated-gpu-training
spec:
backoffLimit: 3
template:
metadata:
labels:
app: isolated-gpu-training
spec:
restartPolicy: Never
nodeSelector:
accelerator: nvidia
workload: training
tolerations:
- key: sku
operator: Equal
value: gpu
effect: NoSchedule
containers:
- name: trainer
image: nvcr.io/nvidia/cuda:12.4.1-runtime-ubuntu22.04
command:
- /bin/bash
- -c
- |
set -e
nvidia-smi
sleep 60
resources:
requests:
cpu: "4"
memory: 16Gi
nvidia.com/gpu: 1
limits:
cpu: "4"
memory: 16Gi
nvidia.com/gpu: 1
容忍度允许进行调度,但不会强制将 Pod 调度到 GPU 节点上。 将容忍度与 GPU 资源请求以及节点选择器或节点亲和性结合使用。 确保所需的平台 DaemonSet(例如网络、监控、存储和设备插件)能够容忍 GPU 污点。
运行分布式训练工作负荷
关键要点:使用培训操作员来管理辅助角色标识和作业生命周期,验证多节点网络通信,并在所有辅助角色必须共同启动时使用帮派录取。
分布式训练使用多个进程来划分数据、模型状态或管道阶段。 在 Kubernetes 上,操作员可以创建辅助角色 Pod、注入会合配置、跟踪副本状态、重启失败的辅助角色以及清理作业。 通过平台的 IaC 和发布过程安装并版本培训操作员,而不是允许单个团队安装未覆盖的群集范围的 CRD。
生成可移植 CUDA 训练映像
下面的 Dockerfile 扩展了“容器化”章节中介绍的 Dockerfile 模式。 该映像包含 CUDA 运行时和 PyTorch 库,而兼容的 NVIDIA 驱动程序仍保留在 AKS GPU 节点上:
FROM pytorch/pytorch:2.4.1-cuda12.4-cudnn9-runtime
WORKDIR /workspace
COPY requirements.txt .
RUN python -m pip install --no-cache-dir -r requirements.txt
COPY train.py .
ENTRYPOINT ["python", "/workspace/train.py"]
在生产环境中使用不可变摘要固定基础镜像。 将数据集和频繁变化的检查点置于映像之外,并在将生成的映像推送到 Azure 容器注册表 之前对其进行扫描。
运行 PyTorchJob
Kubeflow 培训操作员提供 PyTorchJob CRD。 在应用此清单之前,请安装与 Kubernetes 版本兼容的训练操作员版本。 以下两副本示例在一个主节点和一个工作节点之间执行 NCCL all-reduce 操作:
apiVersion: v1
kind: Namespace
metadata:
name: ml-training
labels:
purpose: ml-training
---
apiVersion: kubeflow.org/v1
kind: PyTorchJob
metadata:
name: pytorch-nccl-example
namespace: ml-training
spec:
runPolicy:
cleanPodPolicy: None
pytorchReplicaSpecs:
Master:
replicas: 1
restartPolicy: OnFailure
template:
metadata:
labels:
training-job: pytorch-nccl-example
spec:
nodeSelector:
accelerator: nvidia
tolerations:
- key: sku
operator: Equal
value: gpu
effect: NoSchedule
containers:
- name: pytorch
image: pytorch/pytorch:2.4.1-cuda12.4-cudnn9-runtime
command:
- python
- -c
- |
import os
import torch
import torch.distributed as dist
torch.cuda.set_device(0)
dist.init_process_group(backend="nccl")
value = torch.tensor(
[float(dist.get_rank() + 1)],
device="cuda"
)
dist.all_reduce(value)
print(
f"rank={dist.get_rank()} "
f"world_size={dist.get_world_size()} "
f"all_reduce_sum={value.item()}"
)
dist.destroy_process_group()
env:
- name: NCCL_DEBUG
value: INFO
- name: NCCL_SOCKET_IFNAME
value: eth0
- name: NCCL_IB_DISABLE
value: "1"
- name: TORCH_NCCL_ASYNC_ERROR_HANDLING
value: "1"
resources:
requests:
cpu: "4"
memory: 16Gi
nvidia.com/gpu: 1
limits:
cpu: "4"
memory: 16Gi
nvidia.com/gpu: 1
Worker:
replicas: 1
restartPolicy: OnFailure
template:
metadata:
labels:
training-job: pytorch-nccl-example
spec:
nodeSelector:
accelerator: nvidia
tolerations:
- key: sku
operator: Equal
value: gpu
effect: NoSchedule
containers:
- name: pytorch
image: pytorch/pytorch:2.4.1-cuda12.4-cudnn9-runtime
command:
- python
- -c
- |
import os
import torch
import torch.distributed as dist
torch.cuda.set_device(0)
dist.init_process_group(backend="nccl")
value = torch.tensor(
[float(dist.get_rank() + 1)],
device="cuda"
)
dist.all_reduce(value)
print(
f"rank={dist.get_rank()} "
f"world_size={dist.get_world_size()} "
f"all_reduce_sum={value.item()}"
)
dist.destroy_process_group()
env:
- name: NCCL_DEBUG
value: INFO
- name: NCCL_SOCKET_IFNAME
value: eth0
- name: NCCL_IB_DISABLE
value: "1"
- name: TORCH_NCCL_ASYNC_ERROR_HANDLING
value: "1"
resources:
requests:
cpu: "4"
memory: 16Gi
nvidia.com/gpu: 1
limits:
cpu: "4"
memory: 16Gi
nvidia.com/gpu: 1
对于生产环境训练,请将内联测试替换为已版本化的训练镜像和脚本。 使副本计数与训练框架所需的 GPU 数和进程数保持一致。
运行 TFJob
培训操作员还提供 TFJob CRD。 以下示例通过使用 MultiWorkerMirroredStrategy 在两个 GPU 工作器上运行同步 TensorFlow 训练:
apiVersion: v1
kind: Namespace
metadata:
name: ml-training
labels:
purpose: ml-training
---
apiVersion: kubeflow.org/v1
kind: TFJob
metadata:
name: tensorflow-multiworker-example
namespace: ml-training
spec:
runPolicy:
cleanPodPolicy: None
tfReplicaSpecs:
Worker:
replicas: 2
restartPolicy: OnFailure
template:
metadata:
labels:
training-job: tensorflow-multiworker-example
spec:
nodeSelector:
accelerator: nvidia
tolerations:
- key: sku
operator: Equal
value: gpu
effect: NoSchedule
containers:
- name: tensorflow
image: tensorflow/tensorflow:2.16.1-gpu
command:
- python
- -c
- |
import tensorflow as tf
strategy = tf.distribute.MultiWorkerMirroredStrategy()
print("workers:", strategy.num_replicas_in_sync)
with strategy.scope():
model = tf.keras.Sequential([
tf.keras.layers.Input(shape=(32,)),
tf.keras.layers.Dense(64, activation="relu"),
tf.keras.layers.Dense(1)
])
model.compile(
optimizer="adam",
loss="mean_squared_error"
)
features = tf.random.normal([4096, 32])
labels = tf.random.normal([4096, 1])
dataset = (
tf.data.Dataset.from_tensor_slices((features, labels))
.shuffle(4096)
.repeat()
.batch(64)
)
model.fit(dataset, epochs=2, steps_per_epoch=32)
resources:
requests:
cpu: "4"
memory: 16Gi
nvidia.com/gpu: 1
limits:
cpu: "4"
memory: 16Gi
nvidia.com/gpu: 1
对于参数服务器训练,请根据 TensorFlow 分发策略定义ChiefWorker和PS副本类型。 在增加副本数之前,先评估由此产生的网络和参数服务器瓶颈。
配置 NCCL 与 AKS CNI 的通信
NVIDIA 集体通信库 (NCCL) 处理集体操作,例如 all-reduce。 对于 AKS CNI 上可移植的 TCP 基线,请使用 pod 网络接口(通常为 eth0),并从 NCCL_IB_DISABLE=1 开始。 对于受支持的 RDMA 功能 VM 大小,请使用文档中所述的 NVIDIA 和 Azure RDMA 配置,并在设置 NCCL_IB_DISABLE=0 之前验证该配置。
使用以下基线环境变量:
-
NCCL_SOCKET_IFNAME=eth0选择 Pod 网络接口。 -
NCCL_DEBUG=INFO在验证期间提供诊断。 调优后降低日志级别。 -
NCCL_IB_DISABLE=1在未配置 RDMA 时选择 TCP 套接字。 -
TORCH_NCCL_ASYNC_ERROR_HANDLING=1可帮助 PyTorch 在异步通信失败后退出,而不是无限期地挂起。
如果启用了网络策略,则允许副本之间所有必需的 rendezvous 通信和 NCCL 流量。 NCCL 可以协商使用动态端口,因此过窄的固定端口范围策略可能会导致训练作业挂起。 以下策略仅允许在 PyTorch 作业的副本之间进行不受限制的 Pod 到 Pod 通信,并允许 DNS 解析:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-pytorch-nccl
namespace: ml-training
spec:
podSelector:
matchLabels:
training-job: pytorch-nccl-example
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
training-job: pytorch-nccl-example
egress:
- to:
- podSelector:
matchLabels:
training-job: pytorch-nccl-example
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
在进行横向扩展之前,先独立于模型训练对 NCCL 进行基准测试。比较不同工作节点数量下的 All-Reduce 带宽、延迟、GPU 利用率和训练吞吐量。 当通信或数据加载成为瓶颈时,更多的工作人员可以降低性能。
使用帮派计划和工作负荷队列
关键要点:将分布式工作节点作为一个整体准入,并强制执行租户 GPU 配额,以避免部分已调度的作业在等待其余工作节点时占用 GPU。
默认的 Kubernetes 调度器会逐个放置 Pod。 对于在所有工作节点都运行起来之前无法取得进展的分布式作业,部分部署可能会浪费 GPU 资源。 Kueue 在 Pod 开始运行前提供准入控制和配额管理。 Volcano 提供调度器和基于 PodGroup 的全有或全无调度模型。
AKS Automatic 提供默认的 Kubernetes 调度程序和自动节点预配,作为起点。 提交具有准确资源请求的普通作业或操作员管理的训练作业,让平台预配符合条件的容量。 这种行为并不能保证每个工作线程的准入都是原子性的。 当工作负荷需要帮派计划、排队、团队配额、公平共享或自定义抢占行为时,请安装 Kueue 或 Volcano。
使用 Kueue 分配多租户 GPU 配额
安装与 Kubernetes 版本兼容的 Kueue 版本,并为使用的工作负载类型启用集成。 以下清单文件将创建:
- 与标记为
ResourceFlavor的节点关联的 GPUaccelerator=nvidia。 - 团队 A 的四 GPU
ClusterQueue。 - 供 B 团队使用的双 GPU
ClusterQueue。 - 每个团队的
LocalQueue命名空间范围。 - 仅当该 Job 所请求的资源可用时,Kueue 才会准入这个具有两个 worker 的 Job。
apiVersion: v1
kind: Namespace
metadata:
name: team-a
---
apiVersion: v1
kind: Namespace
metadata:
name: team-b
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
name: nvidia-gpu
spec:
nodeLabels:
accelerator: nvidia
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
name: team-a-gpu
spec:
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: team-a
queueingStrategy: BestEffortFIFO
resourceGroups:
- flavors:
- name: nvidia-gpu
resources:
- name: cpu
nominalQuota: "64"
- name: memory
nominalQuota: 256Gi
- name: nvidia.com/gpu
nominalQuota: "4"
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
name: team-b-gpu
spec:
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: team-b
queueingStrategy: BestEffortFIFO
resourceGroups:
- flavors:
- name: nvidia-gpu
resources:
- name: cpu
nominalQuota: "32"
- name: memory
nominalQuota: 128Gi
- name: nvidia.com/gpu
nominalQuota: "2"
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
name: gpu-queue
namespace: team-a
spec:
clusterQueue: team-a-gpu
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
name: gpu-queue
namespace: team-b
spec:
clusterQueue: team-b-gpu
---
apiVersion: batch/v1
kind: Job
metadata:
name: team-a-two-gpu-training
namespace: team-a
labels:
kueue.x-k8s.io/queue-name: gpu-queue
spec:
suspend: true
completions: 2
parallelism: 2
backoffLimit: 2
template:
metadata:
labels:
app: team-a-two-gpu-training
spec:
restartPolicy: Never
tolerations:
- key: sku
operator: Equal
value: gpu
effect: NoSchedule
containers:
- name: worker
image: nvcr.io/nvidia/cuda:12.4.1-runtime-ubuntu22.04
command:
- /bin/bash
- -c
- |
set -e
nvidia-smi
echo "Kueue admitted this worker."
sleep 120
resources:
requests:
cpu: "4"
memory: 16Gi
nvidia.com/gpu: 1
limits:
cpu: "4"
memory: 16Gi
nvidia.com/gpu: 1
配额ClusterQueue是计划配额,而不是Azure订阅配额。 确保 AKS 群集可以预配基础 VM 容量,并且Azure GPU 配额足以满足聚合允许的工作负荷。 当团队之间可以相互借用未使用的配额时,可使用群组和 Kueue 公平共享功能。
使用火山作为替代方法
当你需要一个具备组调度、队列和作业生命周期策略的专用批处理调度器时,Volcano 可作为一种替代方案。 在应用 Volcano 的 CRD 之前,请先安装与集群兼容的 Volcano 版本。 以下 Volcano 作业要求两个 GPU 工作节点在作业运行前都处于可用状态:
apiVersion: batch.volcano.sh/v1alpha1
kind: Job
metadata:
name: volcano-gpu-training
namespace: default
spec:
minAvailable: 2
schedulerName: volcano
policies:
- event: PodEvicted
action: RestartJob
- event: PodFailed
action: RestartJob
tasks:
- replicas: 2
name: worker
template:
metadata:
labels:
app: volcano-gpu-training
spec:
restartPolicy: OnFailure
nodeSelector:
accelerator: nvidia
tolerations:
- key: sku
operator: Equal
value: gpu
effect: NoSchedule
containers:
- name: worker
image: nvcr.io/nvidia/cuda:12.4.1-runtime-ubuntu22.04
command:
- /bin/bash
- -c
- |
set -e
nvidia-smi
echo "All Volcano workers were admitted."
sleep 120
resources:
requests:
cpu: "4"
memory: 16Gi
nvidia.com/gpu: 1
limits:
cpu: "4"
memory: 16Gi
nvidia.com/gpu: 1
除非你有经过验证的互操作设计方案,否则应统一采用同一种主要的队列系统和成组调度系统。 否则,多个准入控制器或调度器可能会使待定状态和抢占行为难以理解。
配置训练和推理优先级
延迟敏感推理通常需要比可中断的批处理训练更高的优先级。 以下 PriorityClass 资源允许推理 Pod 抢占较低优先级的 Pod,同时防止批处理训练 Pod 抢占其他工作负载:
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: training-batch
value: 10000
globalDefault: false
preemptionPolicy: Never
description: Batch training can wait and can be preempted by higher-priority workloads.
---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: inference-critical
value: 100000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: Latency-sensitive production inference can preempt lower-priority workloads.
在 Pod 模板中通过 spec.priorityClassName 指定该类。 Kubernetes 中的 Pod 优先级和 Kueue 工作负载优先级会影响不同阶段:Kueue 控制队列准入,而 Kubernetes 优先级则会在准入后影响调度和 Pod 抢占。 协调这两个策略,以便它们表达相同的业务优先级。
抢占会终止优先级较低的 Pod。 可被抢占的训练工作负载必须将检查点写入持久存储,能够容忍重复工作,并且能够在不依赖节点本地文件的情况下恢复。
配置共享内存和资源管理
关键要点:替换容器运行时中较小的默认值 /dev/shm,根据观测到的利用率设置资源请求,并根据 GPU 工作负载的完整需求配置节点伸缩。
增加 ML 数据加载器的 /dev/shm
PyTorch 数据加载程序、TensorFlow 输入管道、NCCL 和Python多处理可以使用 POSIX 共享内存。 对于多进程训练,容器中 /dev/shm 的默认值往往过小,这可能导致工作进程崩溃、总线错误,或者看似训练卡住。
将基于内存的 emptyDir 挂载到 /dev/shm,并显式设置 sizeLimit:
apiVersion: batch/v1
kind: Job
metadata:
name: shared-memory-training
spec:
backoffLimit: 2
template:
metadata:
labels:
app: shared-memory-training
spec:
restartPolicy: Never
nodeSelector:
accelerator: nvidia
tolerations:
- key: sku
operator: Equal
value: gpu
effect: NoSchedule
containers:
- name: trainer
image: pytorch/pytorch:2.4.1-cuda12.4-cudnn9-runtime
command:
- /bin/bash
- -c
- |
set -e
df -h /dev/shm
python -c '
import multiprocessing as mp
import torch
def worker(index):
value = torch.ones(1024, 1024)
print(f"worker={index}, sum={value.sum().item()}")
if __name__ == "__main__":
processes = [mp.Process(target=worker, args=(i,)) for i in range(4)]
for process in processes:
process.start()
for process in processes:
process.join()
if process.exitcode != 0:
raise SystemExit(process.exitcode)
'
volumeMounts:
- name: shared-memory
mountPath: /dev/shm
resources:
requests:
cpu: "8"
memory: 16Gi
nvidia.com/gpu: 1
limits:
cpu: "8"
memory: 16Gi
nvidia.com/gpu: 1
volumes:
- name: shared-memory
emptyDir:
medium: Memory
sizeLimit: 8Gi
基于内存的 emptyDir 使用量会计入 Pod 的内存消耗。 设置容器内存限制时,包括预期的共享内存使用量上限。 如果 /dev/shm 的内存使用量超过有效内存预算,则 Pod 可能会因超出其内存限制而被驱逐或终止。
根据观测到的利用率确定 CPU 和内存配置
从基于代表性模型、批大小、序列长度、数据加载程序并发、扩充和检查点行为的基准开始。 然后使用监视数据来调整:
- 设置足够高的 CPU 请求,以保持 GPU 输入管道的供应。 永久性 GPU 空闲期可以指示 CPU 或存储不足。
- 将内存请求设置为接近稳定的工作集使用量加上安全边距。 包括
/dev/shm、页面缓存行为、框架分配器开销和检查点序列化峰值。 - 在观察到的峰值上方设置内存限制。 与其在未查明原因的情况下反复提高限制,不如调查
OOMKilled事件的原因。 - 在使用严格的 CPU 限制之前,先评估 CPU 节流情况。 即使平均 CPU 使用率似乎可以接受,太低的 CPU 限制也会降低 GPU 利用率。
- 当保证服务质量比节点打包更重要时,为可预测的高价值训练作业设置请求和限制。
- 分析模型大小、精度、批大小、工作器计数和数据管道的每个重大更改。
使用基于完整训练过程的百分位数,而不是短期平均值。 启动、验证、检查点和数据混洗阶段通常具有不同的资源需求峰值。
为 GPU 节点池配置群集自动缩放
对于 AKS 标准版,请在 GPU 用户节点池上启用群集自动缩放程序,并定义最小和最大容量:
RESOURCE_GROUP=myResourceGroup
AKS_CLUSTER=myAKSCluster
GPU_NODE_POOL=gpunp
az aks nodepool update \
--resource-group "$RESOURCE_GROUP" \
--cluster-name "$AKS_CLUSTER" \
--name "$GPU_NODE_POOL" \
--enable-cluster-autoscaler \
--min-count 0 \
--max-count 8
你可以在群集级别调整群集自动缩放程序配置文件中的受支持设置。 针对训练和服务工作负载测试配置文件更改:
az aks update \
--resource-group "$RESOURCE_GROUP" \
--name "$AKS_CLUSTER" \
--cluster-autoscaler-profile \
scan-interval=20s \
scale-down-unneeded-time=10m \
scale-down-delay-after-add=15m \
max-graceful-termination-sec=120
只有当待调度的 GPU Pod 的全部调度要求与节点池模板相匹配时,才会触发扩容。 当未发生扩容时,请检查 GPU 资源请求、VM 容量、标签、污点、容忍度、亲和性、拓扑约束、持久卷拓扑、最大节点数以及 Azure 配额。
缩容到零适用于可中断的训练池,但它也会带来 VM 预配、镜像拉取、挂载数据集和模型初始化方面的延迟。 当启动延迟对服务目标产生重大影响时,请保持非零最小值。 Pod 中断预算、不可逐出 Pod、本地存储以及较长的终止宽限期都可能导致缩容延迟。
存储数据集和检查点
关键要点:根据访问模式、吞吐量、共享和恢复要求选择存储,并在节点外部保留检查点,以便训练可以从逐出或抢占中恢复。
不要将大型数据集、模型工件和检查点放入容器镜像中。 根据工作负荷选择存储接口:
- 对大型对象数据集、模型项目和高容量数据存储库使用Azure Blob 存储。
- 当多个工作器节点需要共享 POSIX 样式文件系统时,请使用Azure 文件存储。
- 使用 Azure 磁盘实现高性能单节点读写检查点或缓存工作负载(如果
ReadWriteOnce足够)。 - 仅对可重建的缓存和临时文件使用节点本地临时存储。
使用 Azure Blob CSI 驱动程序访问大型数据集
如果尚未启用,请在 AKS 标准版上启用 Azure Blob CSI 驱动程序:
RESOURCE_GROUP=myResourceGroup
AKS_CLUSTER=myAKSCluster
az aks update \
--resource-group "$RESOURCE_GROUP" \
--name "$AKS_CLUSTER" \
--enable-blob-driver
以下示例动态创建通过 NFS 公开的高级 Azure Blob 容器,并将其装载到训练 Pod 中。 对于现有的受治理数据集,请使用具有适当身份和专用网络控制的静态定义卷或 BlobFuse 配置,而不是创建空的动态容器:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ml-azureblob-nfs
provisioner: blob.csi.azure.com
parameters:
protocol: nfs
skuName: Premium_LRS
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: Immediate
mountOptions:
- -o attr_timeout=120
- -o entry_timeout=120
- -o negative_timeout=120
- -o actimeo=120
- -o noresvport
- -o nconnect=4
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: blob-training-dataset
namespace: default
spec:
accessModes:
- ReadWriteMany
storageClassName: ml-azureblob-nfs
resources:
requests:
storage: 1Ti
---
apiVersion: batch/v1
kind: Job
metadata:
name: inspect-blob-dataset
namespace: default
spec:
backoffLimit: 2
template:
metadata:
labels:
app: inspect-blob-dataset
spec:
restartPolicy: Never
containers:
- name: dataset-reader
image: ubuntu:24.04
command:
- /bin/bash
- -c
- |
set -e
echo "Mounted dataset path:"
df -h /mnt/dataset
find /mnt/dataset -maxdepth 2 -type f | head -100
volumeMounts:
- name: dataset
mountPath: /mnt/dataset
volumes:
- name: dataset
persistentVolumeClaim:
claimName: blob-training-dataset
根据具有代表性的分片大小和访问模式对协议和装载选项进行基准测试。 多个工作进程读取大量小文件,可能会产生与按顺序流式读取大型分片不同的结果。 考虑将数据集预先处理成大小合适的分片,并在重复训练轮次否则会重复下载相同数据对象的情况下,使用节点本地缓存空间。
与Azure 文件存储共享训练数据
Azure 文件存储 支持ReadWriteMany,这使得不同节点上的分布式工作进程能够挂载同一个文件共享。 当工作负荷需要可预测的文件系统性能并且所选区域支持所需的冗余选项时,高级Azure 文件存储是合适的。
以下清单创建一个高级 Azure 文件共享,并将其挂载到两个并行的工作 Pod 中:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ml-azurefile-premium
provisioner: file.csi.azure.com
parameters:
skuName: Premium_LRS
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: Immediate
mountOptions:
- dir_mode=0770
- file_mode=0660
- uid=1000
- gid=1000
- mfsymlinks
- cache=strict
- actimeo=30
- nosharesock
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: shared-training-data
namespace: default
spec:
accessModes:
- ReadWriteMany
storageClassName: ml-azurefile-premium
resources:
requests:
storage: 100Gi
---
apiVersion: batch/v1
kind: Job
metadata:
name: shared-data-workers
namespace: default
spec:
completions: 2
parallelism: 2
completionMode: Indexed
backoffLimitPerIndex: 2
template:
metadata:
labels:
app: shared-data-workers
spec:
restartPolicy: Never
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
containers:
- name: worker
image: ubuntu:24.04
command:
- /bin/bash
- -c
- |
set -e
WORKER_INDEX="${JOB_COMPLETION_INDEX:-0}"
echo "worker=${WORKER_INDEX}" \
> "/mnt/shared/worker-${WORKER_INDEX}.txt"
ls -la /mnt/shared
volumeMounts:
- name: shared-data
mountPath: /mnt/shared
volumes:
- name: shared-data
persistentVolumeClaim:
claimName: shared-training-data
避免让每个辅助角色重复枚举包含数百万文件的目录。 使用清单或确定性的分片分配方式,以便每个工作进程都知道自己应读取哪些对象。
持久存储的检查点
检查点应包含足够的状态才能正确恢复,例如模型权重、优化器状态、计划程序状态、缩放程序状态、纪元或步数、随机数生成器状态,以及支持时的数据加载程序位置。 定期和正常终止前写入检查点。
以下作业会将 PyTorch 检查点以原子方式写入 Azure 文件存储,在容器重启或 Pod 被替换后恢复最新检查点,并处理 SIGTERM,以便在发生抢占时保留最近的进度:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ml-checkpoints-azurefile
provisioner: file.csi.azure.com
parameters:
skuName: Premium_LRS
reclaimPolicy: Retain
allowVolumeExpansion: true
volumeBindingMode: Immediate
mountOptions:
- dir_mode=0770
- file_mode=0660
- uid=1000
- gid=1000
- mfsymlinks
- cache=strict
- actimeo=30
- nosharesock
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: model-checkpoints
namespace: default
spec:
accessModes:
- ReadWriteMany
storageClassName: ml-checkpoints-azurefile
resources:
requests:
storage: 100Gi
---
apiVersion: batch/v1
kind: Job
metadata:
name: checkpointed-pytorch-training
namespace: default
spec:
backoffLimit: 6
template:
metadata:
labels:
app: checkpointed-pytorch-training
spec:
restartPolicy: OnFailure
terminationGracePeriodSeconds: 120
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
containers:
- name: trainer
image: pytorch/pytorch:2.4.1-cuda12.4-cudnn9-runtime
command:
- python
- -c
- |
import os
import signal
import sys
import time
import torch
checkpoint_path = "/checkpoints/latest.pt"
temporary_path = "/checkpoints/latest.pt.tmp"
state = {"epoch": 0, "value": torch.tensor([0.0])}
if os.path.exists(checkpoint_path):
state = torch.load(checkpoint_path, map_location="cpu")
print(f"Restored epoch {state['epoch']}", flush=True)
def save_checkpoint():
torch.save(state, temporary_path)
os.replace(temporary_path, checkpoint_path)
print(f"Saved epoch {state['epoch']}", flush=True)
def terminate(signum, frame):
print(f"Received signal {signum}", flush=True)
save_checkpoint()
sys.exit(143)
signal.signal(signal.SIGTERM, terminate)
signal.signal(signal.SIGINT, terminate)
for epoch in range(state["epoch"] + 1, 21):
state["epoch"] = epoch
state["value"] += 1
time.sleep(10)
if epoch % 2 == 0:
save_checkpoint()
save_checkpoint()
print("Training completed.", flush=True)
volumeMounts:
- name: checkpoints
mountPath: /checkpoints
resources:
requests:
cpu: "1"
memory: 2Gi
limits:
cpu: "1"
memory: 2Gi
volumes:
- name: checkpoints
persistentVolumeClaim:
claimName: model-checkpoints
对生产检查点采用 Retain 回收策略或独立管理的存储账户,以免删除 PVC 时无意中删除唯一的恢复点。 对于分布式数据并行训练,通常排名为零的写入全局检查点,或使用框架支持的分片检查点格式。 防止多个 rank 同时写入同一个文件。
在一次非生产环境运行中,故意删除一个 worker Pod,以测试恢复能力。 验证控制器是否重新创建工作负载、新 Pod 是否挂载相同的持久卷,并从预期步骤恢复训练。
监视 GPU 利用率和训练吞吐量
关键要点:一起监视 GPU 计算、GPU 内存、数据管道吞吐量、步骤持续时间和检查点活动,以便区分计算饱和度与 CPU、网络或存储瓶颈。
为 Prometheus 和 Container Insights 启用Azure Monitor托管服务,以关联 Kubernetes 状态、容器日志、节点运行状况和 Prometheus 指标。 在设计警报、保留策略和运维仪表板时,请采用AKS 监控最佳做法。
收集 NVIDIA GPU 指标
NVIDIA 数据中心 GPU 管理器 (DCGM) 导出程序公开 NVIDIA GPU 的 Prometheus 指标。 如果 NVIDIA GPU 操作员已部署 DCGM 导出程序,请不要部署重复的导出程序。 配置Azure Monitor托管 Prometheus 以擦除现有导出程序。
下面的PodMonitor使用 Azure Monitor 托管的 Prometheus CRD,并以 gpu-operator 命名空间中的 DCGM Exporter Pod 为目标。 调整标签选择器以匹配已安装的导出程序使用的标签:
apiVersion: azmonitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: nvidia-dcgm-exporter
namespace: gpu-operator
spec:
selector:
matchLabels:
app: nvidia-dcgm-exporter
podMetricsEndpoints:
- port: metrics
interval: 30s
scrapeTimeout: 10s
在应用监视器之前,请验证导出程序服务或 Pod 端口名称。 常见的 DCGM 指标包括:
| Metric | Purpose |
|---|---|
DCGM_FI_DEV_GPU_UTIL |
GPU 处于活动状态的时间百分比 |
DCGM_FI_DEV_FB_USED |
已用帧缓冲区内存 |
DCGM_FI_DEV_FB_FREE |
空闲帧缓冲内存 |
DCGM_FI_DEV_MEM_COPY_UTIL |
内存复制引擎利用率 |
DCGM_FI_DEV_POWER_USAGE |
GPU 能耗 |
DCGM_FI_DEV_GPU_TEMP |
GPU 温度 |
DCGM_FI_DEV_XID_ERRORS |
NVIDIA XID 错误事件 |
指标可用性取决于 GPU、驱动程序、DCGM 和导出程序版本。
导出训练吞吐量指标
使用工作负荷级指标检测训练应用程序。 有用的指标包括:
-
ml_training_samples_total用于已处理的累积样本或令牌。 -
ml_training_steps_total表示已完成的优化器步数。 -
ml_training_step_duration_seconds用于表示步骤延迟。 -
ml_training_checkpoint_duration_seconds用于检查点延迟。 -
ml_training_data_wait_seconds等待输入数据的时间。 - 模型特定的损失、学习速率、渐变规范和验证指标。
以下可运行的示例在端口 8000上公开综合训练指标。 用实际训练循环输出的指标替换模拟:
apiVersion: v1
kind: Namespace
metadata:
name: ml-observability
---
apiVersion: v1
kind: ConfigMap
metadata:
name: training-metrics-server
namespace: ml-observability
data:
server.py: |
import http.server
import threading
import time
state = {
"samples": 0,
"steps": 0,
"last_step_duration": 0.0,
}
def train():
while True:
started = time.time()
time.sleep(1)
state["samples"] += 256
state["steps"] += 1
state["last_step_duration"] = time.time() - started
class MetricsHandler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
if self.path != "/metrics":
self.send_response(404)
self.end_headers()
return
body = (
"# HELP ml_training_samples_total Samples processed.\n"
"# TYPE ml_training_samples_total counter\n"
f"ml_training_samples_total{{job_name=\"metrics-demo\"}} "
f"{state['samples']}\n"
"# HELP ml_training_steps_total Training steps completed.\n"
"# TYPE ml_training_steps_total counter\n"
f"ml_training_steps_total{{job_name=\"metrics-demo\"}} "
f"{state['steps']}\n"
"# HELP ml_training_step_duration_seconds "
"Duration of the most recent training step.\n"
"# TYPE ml_training_step_duration_seconds gauge\n"
f"ml_training_step_duration_seconds"
f"{{job_name=\"metrics-demo\"}} "
f"{state['last_step_duration']}\n"
).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "text/plain; version=0.0.4")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def log_message(self, format, *args):
return
threading.Thread(target=train, daemon=True).start()
http.server.ThreadingHTTPServer(("0.0.0.0", 8000), MetricsHandler).serve_forever()
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: training-metrics-demo
namespace: ml-observability
spec:
replicas: 1
selector:
matchLabels:
app: training-metrics-demo
template:
metadata:
labels:
app: training-metrics-demo
spec:
containers:
- name: metrics
image: python:3.12-slim
command:
- python
- /app/server.py
ports:
- name: metrics
containerPort: 8000
readinessProbe:
httpGet:
path: /metrics
port: metrics
initialDelaySeconds: 2
periodSeconds: 10
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
volumeMounts:
- name: application
mountPath: /app
readOnly: true
volumes:
- name: application
configMap:
name: training-metrics-server
---
apiVersion: azmonitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: training-metrics-demo
namespace: ml-observability
spec:
selector:
matchLabels:
app: training-metrics-demo
podMetricsEndpoints:
- port: metrics
path: /metrics
interval: 30s
scrapeTimeout: 10s
对于真正的分布式作业,包括稳定的标签,例如模型名称、模型版本、作业名称、运行 ID、副本角色和团队。 不要使用未绑定的标签,例如示例 ID、请求 ID 或原始数据集路径,因为高基数标签会增加监视成本和查询延迟。
生成 GPU 和吞吐量仪表板
将如下 PromQL 查询用作起点,并根据你的 exporter 验证指标标签:
avg by (namespace, pod, gpu) (
avg_over_time(DCGM_FI_DEV_GPU_UTIL[5m])
)
以下查询按百分比计算帧缓冲区内存使用量:
100 *
DCGM_FI_DEV_FB_USED
/
(DCGM_FI_DEV_FB_USED + DCGM_FI_DEV_FB_FREE)
以下查询用于计算每秒处理的训练样本数:
sum by (job_name) (
rate(ml_training_samples_total[5m])
)
以下查询显示平均步骤持续时间:
avg by (job_name) (
ml_training_step_duration_seconds
)
将这些信号解释在一起:
- 低 GPU 利用率和高数据等待时间通常表示存储、网络、预处理或 CPU 不足。
- 具有预期吞吐量的高 GPU 利用率表示加速器正在有效使用。
- GPU 显存占用过高且反复出现内存不足错误,表明需要调整批大小、序列长度、激活内存占用、优化器状态或内存碎片。
- 在 GPU 利用率保持稳定的情况下,吞吐量下降可能表明序列变长、通信开销、热特性或模型计算发生了变化。
- 已分配 GPU 的低利用率表明存在闲置容量,可考虑缩减、采用不同的排队方式处理,或使用 MIG 进行分区。
- 检查点耗时过长会使抢占代价高昂,并增加恢复后需要重复执行的工作量。
为 GPU 持续利用率偏低、GPU 内存接近容量上限、XID 错误、训练吞吐量停滞、工作器失败、处于挂起状态的 Gang 调度工作负载,以及未在预期恢复点目标内完成的检查点创建警报。
常见问题 (FAQ)
如何在 AKS 中对 ML 模型进行版本控制?
通过将模型权重、元数据和配置打包到容器镜像中,并使用语义化版本标签,来对模型进行版本控制。 将这些映像存储在Azure 容器注册表中,并在 Kubernetes 部署清单中引用特定版本,以确保环境中的一致性。
如何在 AKS 上计划 GPU 工作负荷?
确保 NVIDIA 设备插件可用,以便 GPU 节点通告 nvidia.com/gpu。 在 AKS 标准版上,使用受支持的 VM 大小(例如 Standard_NC 系列 SKU)预配专用 GPU 用户节点池,并配置节点标签、NoSchedule 污点和群集自动缩放器限制。 为每个训练 Pod 请求 nvidia.com/gpu,选择一个符合条件的 GPU 节点,并添加匹配的容忍度。 AKS Automatic 可根据 Pod 请求预配符合条件的 GPU 容量,具体取决于受支持的 SKU、区域可用性、调度约束以及 Azure 订阅配额。
如何跨多个团队管理 GPU 配额?
安装 Kueue,并定义带有 CPU、内存和 ClusterQueue 配额的 nvidia.com/gpu 资源,然后通过一个 LocalQueue 将每个团队的命名空间映射到其分配的配额。 当团队可以借用未使用的容量时,请使用 Kueue 队列或公平共享。 当你需要支持工作节点全有或全无准入的批处理调度器时,Volcano 是一种可选方案。 将队列优先级与 Kubernetes PriorityClass 资源协调起来,使延迟敏感的推理能够抢占可中断训练,并确保可被抢占的训练作业能够从持久存储中的检查点恢复。
在 AKS 上运行长时间运行的批处理作业时,最佳做法是什么?
将 Kubernetes Job 用于有限任务,将 CronJob 用于计划任务。 将检查点和已提交的输出持久化到 Pod 外部,使处理具备幂等性,处理 SIGTERM,并设置明确的资源请求、重试限制、截止期限和清理策略。 假设节点维护或故障可能导致 Pod 被替换,并测试替换后的 Pod 是否能正确恢复其检查点状态。 监控 Job 状态、Pod 事件、日志、检查点存在时长、吞吐量和重试行为。 普通可重启作业不需要帮派计划或 PDB。 仅当并行工作线程必须同时启动,或需要保持在经过验证的最低并发水平时,才使用这些控制项。
相关内容
了解 AKS 上应用程序部署和运营的其他领域的最佳做法: