这份文档解决一个问题:当一个训练任务出问题时,你应该先判断它卡在哪一层,而不是一上来就随机翻日志。
训练任务的主链路可以记成一句话:
text
用户提交任务 -> 平台接单 -> K8s 创建资源 -> 调度找机器 -> 节点启动容器 -> 容器拿到 GPU -> 训练进程启动 -> 日志/指标/模型回收
对应到系统组件:
text
用户/CLI/Web
-> Platform API
-> Kubernetes API / CRD / Job / Pod
-> Scheduler / Queue
-> kubelet / containerd / CNI / Volume
-> NVIDIA Device Plugin / GPU Runtime
-> PyTorch / torchrun / NCCL
-> Prometheus / Loki / Artifact / Metadata
你后面学习 Linux、Kubernetes、GPU、NCCL、Prometheus,都可以挂到这条链路上。
0. 先看总图
text
┌─────────────────────────────────────────────────────────────────────┐
│ 1. 用户提交任务 │
│ 镜像、命令、CPU、内存、GPU、数据路径、输出路径、队列、优先级 │
└──────────────────────────────┬──────────────────────────────────────┘
│
v
┌─────────────────────────────────────────────────────────────────────┐
│ 2. 平台 API │
│ 鉴权、参数校验、配额校验、任务入库、生成 K8s 资源 │
└──────────────────────────────┬──────────────────────────────────────┘
│
v
┌─────────────────────────────────────────────────────────────────────┐
│ 3. Kubernetes 资源 │
│ Job / Pod / CRD / ConfigMap / Secret / PVC / Service │
└──────────────────────────────┬──────────────────────────────────────┘
│
v
┌─────────────────────────────────────────────────────────────────────┐
│ 4. 调度层 │
│ Scheduler / Queue / Quota / Priority / Gang Scheduling │
└──────────────────────────────┬──────────────────────────────────────┘
│
v
┌─────────────────────────────────────────────────────────────────────┐
│ 5. 节点运行层 │
│ kubelet / containerd / CNI / Volume / cgroup │
└──────────────────────────────┬──────────────────────────────────────┘
│
v
┌─────────────────────────────────────────────────────────────────────┐
│ 6. GPU 容器层 │
│ NVIDIA Device Plugin / GPU Runtime / CUDA / nvidia-smi │
└──────────────────────────────┬──────────────────────────────────────┘
│
v
┌─────────────────────────────────────────────────────────────────────┐
│ 7. 训练运行层 │
│ torchrun / PyTorch DDP / rank / NCCL / 数据读取 / checkpoint │
└──────────────────────────────┬──────────────────────────────────────┘
│
v
┌─────────────────────────────────────────────────────────────────────┐
│ 8. 回收和观测层 │
│ 日志、指标、事件、模型产物、任务状态、失败原因 │
└─────────────────────────────────────────────────────────────────────┘
1. 用户提交任务
这一层做什么
用户通过 Web、CLI、SDK 或 API 提交训练任务。平台收到的是一组训练意图,而不是一个已经能运行的容器。
典型输入:
| 字段 | 含义 | 常见问题 |
|---|---|---|
| 镜像 | 运行环境,包含 CUDA、Python、PyTorch、代码依赖 | 镜像不存在、版本不对、太大拉取慢 |
| 启动命令 | 容器启动后执行什么 | 命令写错、入口脚本缺失 |
| CPU/内存/GPU | 任务资源规格 | 超配额、规格不合法、GPU 型号不匹配 |
| 数据路径 | 训练数据在哪里 | 路径不存在、无权限、挂载失败 |
| 输出路径 | checkpoint 和模型保存位置 | 无写权限、存储满、上传失败 |
| 队列/项目 | 使用哪个团队的资源 | 队列无资源、配额不足 |
| 环境变量 | 分布式训练、NCCL、业务参数 | 缺失或配置错误 |
怎么判断是不是这一层的问题
如果任务在平台页面或 API 层就创建失败,通常还没到 Kubernetes。
优先确认:
text
任务有没有 task uid?
平台有没有返回参数错误?
用户选择的 namespace / project / queue 是否正确?
资源规格是否超过限制?
数据和输出路径是否有权限?
下一步去哪查
如果任务连 UID 都没有:查平台 API 请求和返回。
如果任务有 UID,但 Kubernetes 里没有资源:进入第 2 层,查平台 API 到 K8s 的创建逻辑。
2. 平台 API 层
这一层做什么
平台 API 把用户请求转换成平台内部任务和 Kubernetes 资源。
它通常负责:
text
鉴权:用户能不能提交任务
参数校验:镜像、命令、资源、路径是否合法
配额校验:CPU、内存、GPU 是否还有额度
任务入库:记录 task uid、用户、namespace、状态
生成配置:环境变量、volume、secret、启动脚本
创建资源:Job、Pod、CRD、ConfigMap、Secret、PVC
状态返回:让用户能查任务状态、日志和指标
常见故障
text
API 5xx 或超时
鉴权失败
namespace 不存在
配额不足
任务入库成功但 K8s 资源没创建
K8s 资源创建成功但平台状态没更新
Controller 没消费任务
怎么判断是不是这一层的问题
看三个事实:
text
平台任务是否存在?
平台任务状态是什么?
Kubernetes 里是否有对应 Job/Pod/CRD?
常用命令:
bash
kubectl get pod -n <namespace> | grep <task-name>
kubectl get job -n <namespace> | grep <task-name>
kubectl get all -n <namespace> | grep <task-name>
判断:
text
平台有任务,K8s 没资源:平台 API 或 controller 问题。
平台没任务,K8s 也没资源:请求可能没成功。
平台有任务,K8s 也有资源:继续看 Pod 状态。
3. Kubernetes 资源层
这一层做什么
Kubernetes 接到平台创建的对象后,进入声明式控制流程。
常见对象:
| 对象 | 作用 |
|---|---|
| Pod | 容器真正运行的最小单元 |
| Job | 一次性任务,适合训练和数据处理 |
| CRD | 自定义任务类型,例如 PyTorchJob、RayJob、MPIJob |
| ConfigMap | 普通配置 |
| Secret | token、ak/sk、镜像密钥等敏感配置 |
| PVC | 挂载数据或输出存储 |
| Service | 分布式 rendezvous 或服务访问 |
Controller 的职责是让"期望状态"变成"实际状态"。
例子:
text
期望状态:我要一个 8 卡 PyTorch 分布式训练任务。
实际状态:现在还没有 worker Pod。
Controller 动作:创建对应 Pod,并写入 rank、环境变量和资源请求。
常见故障
text
CRD 创建了,但 Controller 没处理
Job 创建了,但 Pod 没生成
Pod 生成了,但 ConfigMap/Secret 缺失
PVC 未绑定
任务状态没有回写
怎么判断是不是这一层的问题
看资源是否完整:
bash
kubectl get pod -n <namespace> | grep <task-name>
kubectl get job -n <namespace> | grep <task-name>
kubectl get events -n <namespace> --sort-by=.lastTimestamp | tail -100
kubectl describe pod <pod> -n <namespace>
kubectl describe job <job> -n <namespace>
如果是 CRD:
bash
kubectl get pytorchjob -n <namespace>
kubectl describe pytorchjob <name> -n <namespace>
判断:
text
没有 Pod:查平台 API、CRD controller、Job controller。
有 Pod 但 Pending:进入调度层。
有 Pod 但 ContainerCreating:进入节点运行层。
有 Pod Running:进入 GPU 容器或训练运行层。
4. 调度层
这一层做什么
Scheduler 决定 Pod 放到哪台节点。
它会检查:
text
CPU 是否够
内存是否够
GPU 是否够
GPU 型号是否匹配
节点污点和 Pod 容忍是否匹配
节点亲和性是否满足
PVC 是否能挂载
队列是否准入
配额是否足够
优先级和抢占策略
AI 训练里最容易踩的是 GPU 资源碎片。
text
任务要求单机 8 卡。
集群空闲 GPU 总数是 8 张。
但分散在 4 台机器上,每台 2 张。
结果:总量够,单机不够,任务仍然 Pending。
Gang Scheduling 是什么
分布式训练经常需要多个 Pod 一起启动。Gang Scheduling 就是"成组调度":
text
要么一组 Pod 全部调度成功,要么一个都不启动。
原因很简单:
text
16 个 worker 的训练任务,如果只启动 5 个 worker,任务也跑不了。
这 5 个 worker 还会占住 GPU,浪费资源。
常见故障
text
Insufficient cpu
Insufficient memory
Insufficient nvidia.com/gpu
node affinity conflict
had untolerated taint
PVC not bound
quota exceeded
queue not admitted
怎么判断是不是这一层的问题
Pod 状态通常是:
text
Pending
核心命令:
bash
kubectl describe pod <pod> -n <namespace>
kubectl get events -n <namespace> --sort-by=.lastTimestamp | tail -100
kubectl describe node <node>
kubectl get node -o wide
你重点看 Events 里 scheduler 给出的原因。
5. 节点运行层:kubelet / containerd
这一层做什么
Pod 被调度到某台节点后,本机 kubelet 开始接管。kubelet 会调用 containerd、CNI、volume plugin,把容器真正拉起来。
kubelet 大致做:
text
获取 Pod spec
创建 Pod 网络
拉取镜像
创建 Pod sandbox
挂载 volume
准备 ConfigMap 和 Secret
设置 cgroup 资源限制
启动容器
持续上报状态
containerd 负责更底层的容器生命周期:
text
拉镜像
管理镜像层
创建容器
启动容器进程
记录容器状态和日志
常见状态和含义
| Pod 状态 | 通常含义 |
|---|---|
| ImagePullBackOff | 镜像拉不下来 |
| ErrImagePull | 镜像地址、权限或网络错误 |
| ContainerCreating | 网络、挂载、runtime、sandbox 创建中或卡住 |
| CrashLoopBackOff | 容器启动后很快退出并反复重启 |
| OOMKilled | 容器内存超过 limit,被 cgroup kill |
| Error | 容器进程异常退出 |
怎么判断是不是这一层的问题
常用命令:
bash
kubectl describe pod <pod> -n <namespace>
kubectl logs <pod> -n <namespace>
kubectl logs <pod> -n <namespace> --previous
到节点上查:
bash
journalctl -u kubelet -f
journalctl -u containerd -f
crictl ps -a
crictl inspect <container_id>
crictl logs <container_id>
快速判断:
text
ImagePullBackOff:查镜像、registry、Secret、网络。
ContainerCreating:查 CNI、PVC、volume、containerd、runtime。
CrashLoopBackOff:查启动命令、环境变量、应用日志、退出码。
OOMKilled:查内存 limit、模型下载、解压、数据预处理、sidecar。
6. GPU 容器层
这一层做什么
容器 Running 后,还要确认 GPU 真正可用。
GPU 容器依赖:
text
宿主机 GPU 和驱动正常
NVIDIA Device Plugin 正常上报 GPU
Pod 正确 request nvidia.com/gpu
container runtime 正确注入 GPU 设备和驱动库
容器内 CUDA 能正常访问 GPU
关键判断
这三句话很重要:
text
Pod Running 不等于 GPU 可用。
GPU 可见不等于训练能跑。
训练能启动不等于分布式通信成功。
常见故障
text
宿主机有 GPU,但容器内看不到 GPU
device plugin 没有上报 GPU
节点 GPU 掉卡
驱动异常
CUDA 和驱动版本不兼容
Pod 没有 request nvidia.com/gpu
GPU 分配了,但训练进程没用起来
怎么判断是不是这一层的问题
Kubernetes 侧:
bash
kubectl describe pod <pod> -n <namespace> | grep -A5 -B5 nvidia.com/gpu
kubectl describe node <node> | grep -A8 -B8 nvidia.com/gpu
kubectl exec -it <pod> -n <namespace> -- nvidia-smi
kubectl logs -n kube-system ds/nvidia-device-plugin-daemonset
节点侧:
bash
nvidia-smi
nvidia-smi topo -m
nvidia-smi -q
dmesg -T | grep -i xid
7. 训练运行层:PyTorch / torchrun / NCCL
这一层做什么
容器启动后,真正开始训练。
分布式训练常见结构:
text
torchrun 启动多个训练进程
每个进程叫一个 rank
每个 rank 通常绑定一张 GPU
所有 rank 组成一个 world
PyTorch DDP 负责分布式训练逻辑
NCCL 负责 GPU 间通信
关键变量:
| 名称 | 含义 |
|---|---|
| WORLD_SIZE | 总进程数 |
| RANK | 当前进程的全局编号 |
| LOCAL_RANK | 当前进程在本节点内的编号 |
| MASTER_ADDR | 主节点地址 |
| MASTER_PORT | 主节点端口 |
NCCL 负责:
text
GPU 间 all-reduce
参数广播
梯度同步
节点内 NVLink/PCIe 通信
节点间 RDMA/TCP 通信
常见故障
text
rank 数不一致
某些 worker 没启动
master 地址不可达
master 端口不通
NCCL 初始化卡住
NCCL timeout
NCCL 选错网卡
RDMA 异常
某个 rank OOM 导致其他 rank 等待
数据加载慢导致 GPU 空转
checkpoint 写入慢导致周期性卡顿
怎么判断是不是这一层的问题
常用环境变量:
bash
NCCL_DEBUG=INFO
NCCL_DEBUG_SUBSYS=INIT,NET,GRAPH,COLL
TORCH_DISTRIBUTED_DEBUG=DETAIL
排查问题:
text
所有 rank 是否都启动?
每个 rank 的日志是否一致?
最早报错的是哪个 rank?
NCCL 选择了哪张网卡?
节点间网络是否互通?
GPU 是否有 Xid/ECC 错误?
CPU、存储、网络是否成为瓶颈?
快速判断:
text
卡在 init_process_group:查 rank、master、网络、端口。
NCCL timeout:查某个 rank 是否退出、RDMA、网卡、GPU 异常。
CUDA OOM:查显存、batch size、模型大小、优化器状态。
GPU 利用率低:查数据加载、CPU、存储、通信、batch size。
checkpoint 慢:查存储吞吐、写入策略、checkpoint 大小。
8. 回收和观测层
这一层做什么
训练平台不能只把任务跑起来,还要把状态、日志、指标和产物收回来。
通常回收:
text
容器日志
Kubernetes Event
任务状态
Pod 状态
GPU 利用率
GPU 显存
CPU/内存
网络和存储指标
训练 step 指标
loss / accuracy 等业务指标
checkpoint
最终模型文件
评估结果
失败原因
任务结束后还要做:
text
更新任务状态
保存模型和 artifact
上传 checkpoint 或最终模型
记录任务运行时间和资源用量
释放资源
回写 metadata
生成失败诊断
触发后续 Pipeline 节点
常见故障
text
训练实际成功,但平台状态没有更新
Pod 已删除,日志查不到
模型上传失败
checkpoint 没归档
任务完成后资源没释放
指标缺失
失败原因被吞掉
怎么判断是不是这一层的问题
先分清"真实状态"和"平台展示状态":
text
真实状态:Kubernetes Pod / Job / CRD 到底怎么样。
展示状态:平台页面、平台 API、数据库里显示怎么样。
如果两者不一致,重点查状态同步、日志采集、产物上传、metadata 回写。
9. 按 Pod 状态快速定位
这张表是日常排障最常用的。
| 现象 | 优先怀疑层 | 重点排查 |
|---|---|---|
| 平台任务创建失败 | 用户提交层 / 平台 API | 参数、权限、配额、API 日志 |
| 平台有任务但没有 Pod | 平台 API / Controller | 是否创建 K8s 资源,controller 是否报错 |
| Pod Pending | 调度层 | GPU、CPU、内存、队列、配额、亲和性、污点、PVC |
| Pod ImagePullBackOff | 节点运行层 | 镜像地址、registry、Secret、网络 |
| Pod ContainerCreating | 节点运行层 | CNI、PVC、volume、containerd、runtime |
| Pod CrashLoopBackOff | 容器启动层 | 命令、环境变量、依赖服务、应用日志 |
| Pod OOMKilled | Linux/cgroup 层 | 内存 limit、模型下载、数据处理、sidecar |
| Pod Running 但无训练日志 | 容器/GPU/训练启动层 | 启动脚本、数据准备、模型下载、GPU、rendezvous |
| 有日志但卡住 | 训练运行层 | rank、NCCL、RDMA、数据加载、checkpoint |
| 训练完成但平台没更新 | 回收和观测层 | 状态同步、产物上传、metadata 回写 |
10. 标准排障顺序
以后遇到训练任务问题,按这个顺序问:
text
1. 任务 UID、namespace、name 是什么?
2. 平台任务有没有创建成功?
3. Kubernetes 资源有没有生成?
4. Pod 有没有创建?
5. Pod 当前状态是什么?
6. 如果 Pending,scheduler event 是什么?
7. 如果 ContainerCreating,镜像、网络、挂载、runtime 是否正常?
8. 如果 Running,容器进程有没有启动?
9. 容器内能不能看到 GPU?
10. 训练进程有没有启动?
11. 如果是分布式训练,所有 rank 是否都启动?
12. NCCL 初始化是否成功?
13. 数据读取是否正常?
14. checkpoint 和模型输出是否正常?
15. 平台状态、日志、指标、产物是否回写?
11. 两个例子
例子 A:8 卡任务一直 Pending
不要先看训练日志,因为训练还没开始。
排查路径:
text
Pod Pending
-> 看 describe pod 的 Events
-> 如果是 Insufficient nvidia.com/gpu
-> 看任务要求单机 8 卡还是分布式 8 卡
-> 看节点 allocatable GPU
-> 看队列和配额
-> 看是否 GPU 资源碎片
常用命令:
bash
kubectl describe pod <pod> -n <namespace>
kubectl get events -n <namespace> --sort-by=.lastTimestamp | tail -100
kubectl describe node <node> | grep -A8 -B8 nvidia.com/gpu
例子 B:Pod Running,但没有进入训练日志
这时不能直接判断是代码问题。
排查路径:
text
Pod Running
-> 看容器日志是否完全没有输出
-> 看启动脚本是否卡在数据准备或模型下载
-> exec 进去看进程是否存在
-> 看容器内 nvidia-smi 是否正常
-> 如果是分布式任务,看所有 rank 是否都启动
-> 看是否卡在 torchrun rendezvous 或 NCCL init
常用命令:
bash
kubectl logs <pod> -n <namespace>
kubectl exec -it <pod> -n <namespace> -- ps -ef
kubectl exec -it <pod> -n <namespace> -- nvidia-smi
12. 最终记忆版
把训练任务排障记成这条链:
text
有没有任务
-> 有没有 K8s 资源
-> 有没有 Pod
-> Pod 是什么状态
-> 有没有分到节点
-> 容器有没有启动
-> GPU 能不能看到
-> 训练进程有没有起来
-> 分布式通信有没有成功
-> 数据和产物有没有正常读写
-> 平台状态有没有回写
这就是训练平台排障的主干。后面所有技术点,都是在补强这条链上的某一段。