首页
/ DGX Spark 训练避坑指南:G1–G10 十类已知故障的预检与诊断实战(基于 spark-training-gotchas 技能)

DGX Spark 训练避坑指南:G1–G10 十类已知故障的预检与诊断实战(基于 spark-training-gotchas 技能)

2026-09-09 12:23:59作者:董灵辛Dennis

DGX Spark 搭载 GB10 芯片(Grace Blackwell,SM121 架构,128GB 统一内存,aarch64),在启动、内存、散热、带宽和精度五个维度上存在十类反复出现的故障模式。本指南以当前仓库中 plugins/dgx-spark-ops/skills/spark-training-gotchas/SKILL.md 为骨架,逐条讲解这十个编号故障(G1–G10)的症状、根因、可运行的检查命令与修复手段,并结合 gotcha-checks.mdpreflight.sh 给出自动化预检方案。读完你将掌握:在任何多小时的 GB10 训练任务开始前,如何用几分钟定位 CUDA ABI 不匹配、UMA 假 OOM、热节流、带宽上限等隐患,而不是在跑到第六小时时才回头排查。

关键原则:G1–G10 的编号是"承重"的——仓库内用于跑这些检查的工具都按编号引用它们,诊断结论也应始终以 G 编号为准(dgx-spark-ops-engineer 智能体 明确要求"每个诊断必须标注 G 编号,缺少 G 编号的 Spark 故障视为未完成诊断")。请在长任务开始前阅读本文,而不是在第六小时之后。

何时使用本技能

spark-training-gotchas 面向以下典型场景,凡命中其一,都应先跑一遍 G1–G10 检查再动手:

  • 训练任务无法启动,抛出与真实原因无关的 import 错误或段错误(segfault);
  • 任务 OOM,而 nvidia-smi 仍显示内存有富余;
  • 任务正常启动后,运行中途吞吐量明显下降;
  • 任何多小时或多 epoch 的 GB10 任务启动之前;
  • 计划把两台 Spark 互联(dual-Spark)之前,需要先决定并行策略;
  • 需要在 FP8 与 NVFP4 之间为 Spark 上的推理/训练选型。

常见问题速查表

# 症状 修复
G1 undefined symbol / 段错误 改用 cu130 wheel 或匹配的容器
G2 flash-attn 使用了错误的 backend 跳过 pip 构建;在 NGC 容器上用 monkeypatch 覆盖
G3 明明有富余内存却 OOM 清空 page cache
G4 吞吐量下降 / 重启 预期约 100W 的持续功耗上限
G5 内存密集型 step 变慢 按 180–192 GB/s 做预算
G6 缓存中途被逐出 同一时间只跑一个重型任务
G7 NVFP4 比 FP8 更慢 除非编译目标为 sm_121a,否则继续用 FP8
G8 官方 playbook 直接失败 先查上游 issue
G9 安装后环境被破坏 使用容器
G10 双 Spark TP 挂起 只用 DDP/FSDP,绝不用 TP

G1:CUDA 12/13 ABI 不匹配

症状ImportError: undefined symbol 并指向某个 CUDA 函数,或第一次调用 .cuda() 时直接段错误。

根因:绝大多数 PyPI wheel 链接的是 libcudart.so.12,而 Spark 自带 CUDA 13。pip 的依赖解析器从不检查 CUDA ABI,因此问题只会在 import 或首次 kernel 启动时暴露——典型的"安装成功、加载失败"。

检查torch.version.cuda 是权威信号,应返回 13.x

python3 -c "import torch; print(torch.version.cuda)"
python -c "import ctypes; ctypes.CDLL('libcudart.so.13')"
ldconfig -p | grep libcudart

gotcha-checks.md G1 的说明:

  • 不要依赖 pip show torch | grep cu130:NGC 容器(如 nvcr.io/nvidia/pytorch:25.11-py3)内部自行用 CUDA 13 构建 torch,没有 +cu130 wheel tag,所以 pip show 里查不到 cu130 并不等于失败
  • ctypes 加载 libcudart.so.13 若抛出 OSError,说明是驱动/运行时安装问题,而不是 wheel 问题;
  • ldconfig -p 中同时出现 libcudart.so.12libcudart.so.13 是早期安装的常见残留,也是 ABI 不匹配的常见来源。

修复:从 download.pytorch.org/whl/cu130 重装(cu130 标记的 aarch64 构建),或使用已内置匹配构建的 NGC/Unsloth 容器。这也是 spark-environment-setup 中"ABI Rule"的核心结论:无论症状如何,修复方式都是让 wheel 的 CUDA tag 与系统匹配。

G2:flash-attn——跳过 pip 构建,留意 Unsloth 的自动探测

症状pip install flash-attn 依然失败或卡住;Unsloth 还可能在你显式指定 SDPA 的情况下静默训练 flash-attn。

根因:裸 pip 环境下没有 aarch64/sm_121 的 flash-attn wheel,但 NGC 容器自带可用的 SM121 flash-attn,而 Unsloth 会自动优先使用它,从而丢弃 attn_implementation="sdpa" 参数。

检查

python -c "import flash_attn; print(flash_attn.__version__)" 2>&1 | tail -5
python -c "import torch; print(torch.backends.cuda.flash_sdp_enabled())"

gotcha-checks.md G2 的记录:

  • 裸 pip:import 失败是预期行为(没有 aarch64/sm_121 wheel),反而可以确认没有训练代码路径隐式依赖 flash-attn;
  • NGC PyTorch 容器(已在 nvcr.io/nvidia/pytorch:25.09-py3 上验证):flash-attn 2.7.4.post1 预装,在 GB10(capability (12, 1))上调用 flash_attn_func 能成功执行且输出形状正确——"没有 sm_121 kernel"的论断只针对自己从源码构建,不适用于容器内已携带的版本;
  • 真正的陷阱:Unsloth 的 import 时 patch banner 报告 FA2 = True 并自动优先 flash-attn——即使调用方显式传了 attn_implementation="sdpa"。Unsloth 的 loader(unsloth/models/llama.py,2026.7.2)调用内部 resolve_attention_implementation(...) 时不转发调用方的 requested_attn_implementation 参数,并直接 pop 掉该 kwarg(源码里注释 # No need since we auto call it)。实测:显式传 attn_implementation="sdpa"model.config._attn_implementation 仍为 "flash_attention_2"

修复

  • 裸 pip:跳过 flash-attn,直接使用 SDPA(不用改任何代码);
  • NGC 容器:唯一可靠的覆盖方式是 monkeypatch,在调用 from_pretrained 之前执行:
import unsloth.models._utils as _unsloth_utils
_unsloth_utils.HAS_FLASH_ATTENTION = False

这会迫使 resolve_attention_implementationelif supports_sdpa: 分支而不是 flash-attn 分支,实测 monkeypatch 后 config._attn_implementation == "sdpa"。注意:绕开 Unsloth、直接用 TRL/PEFT 时,传给 AutoModelForCausalLM.from_pretrainedattn_implementation="sdpa" 是会被正常尊重的——这个缺陷是 Unsloth 特有的,不是 TRL/transformers 的通用问题。

G3:128GB 上限之下的 UMA OOM

症状:模型加载/训练时 OOM,但 nvidia-smi 仍报告 128GB 上限之下有富余内存;某些驱动/环境组合下甚至直接返回 [N/A] 而不是数字。

根因:safetensors 加载时 mmap 与 CUDA 分配器会对页面双重计数;QLoRA 可能比 bf16 更早 OOM,因为反量化会引入瞬时分配。

检查:看真实内存压力要用 free -g/proc/meminfo而不是 nvidia-smi

free -g
cat /proc/meminfo | grep -i huge

UMA 意味着 CUDA 分配与主机 RAM 共享同一个 128GB 池,而 nvidia-smi 只报告 CUDA 一侧。在部分驱动/环境组合下,nvidia-smi --query-gpu=memory.used,memory.total 不是少报,而是直接返回 [N/A], [N/A]——据此写 headroom 检查脚本会什么都 grep 不到。这也是 spark-memory-thermal-ops 中 UMA 内存模型的核心:加载模型是瞬时峰值而非稳态,mmap 页与 CUDA 拷贝在同一窗口内同时占池。

修复:清空 page cache(需要 root,属于两次运行之间的重置操作,绝不是训练中途的常规步骤):

sync
echo 3 > /proc/sys/vm/drop_caches

注意这会在系统范围内刷新 page cache,盒子上所有进程的缓存文件读取都会受影响。

G4:热节流

症状:多小时的运行中途吞吐量骤降,或盒子在持续负载下自发重启。

根因:持续功耗上限约 100W,远低于标称的 240W;长任务推入这个天花板后就会降频,有时直接重启。

检查:采样温度与功耗,用 -l 5 每 5 秒一次,至少对一个有代表性的负载观察 10–15 分钟再下结论:

nvidia-smi --query-gpu=temperature.gpu,power.draw --format=csv -l 5

gotcha-checks.md G4:标称功耗 240W;如果功耗稳定在 100W 附近而温度持续攀升(或已在高位平台期),盒子就是在节流。温度上升但功耗仍接近峰值,还不是节流事件——继续观察。该命令不需要 root,Ctrl-C 结束。

修复:如果功耗在 240W 之下平台化而温度持续爬升,就把节流当作原因:改善散热或缩短单次运行时长。不要反过来"调 batch size / 精度去修复一个本就是平台正常表现"的功耗平台——这一点在 spark-memory-thermal-ops 的 Thermal Monitoring 一节被反复强调:持续约 100W 是平台功耗上限,不是配置 bug。

G5:带宽天花板

症状:内存密集型负载——尤其是 decode-heavy 的 RL 循环——远低于预期吞吐量就进入平台期。

根因:273 GB/s 是规格峰值而非持续值;实测带宽在 180–192 GB/s 区间。

检查:实测 step 时间与测量区间对比,而不是与规格值对比。注意 G5 检查是侵入式的,是这份检查文件里唯一不能对活跃负载运行的一项:

  • 它会在 UMA 系统上分配约 4GB 并反复 clone 该 tensor;
  • 在 128GB 已被其他任务占用大部分余量的盒子上,可能 OOM 掉那个任务;
  • 只在空闲主机上运行。
python -c "
import torch, time
x = torch.randn(1_000_000_000, device='cuda', dtype=torch.float32)
torch.cuda.synchronize()
t0 = time.time()
for _ in range(20):
    y = x.clone()
torch.cuda.synchronize()
dt = time.time() - t0
gbps = (x.numel() * 4 * 2 * 20) / dt / 1e9
print(f'{gbps:.1f} GB/s')
del x, y
torch.cuda.empty_cache()
"

修复:按 180–192 GB/s 做吞吐量预算;如果原计划建立在 273 GB/s 的规格数字上,投入排期之前务必修正。

G6:全局 UMA 资源争用

症状:进程的 KV cache/权重在运行中途被静默逐出,而它自己的日志里没有任何 OOM 记录。

根因:统一内存是一个全局池;一个未设上限或接近容量的进程会与任何其他进程竞争并可能逐出对方。小型、有界的工作负载不会——例如 <4GB 的 LoRA 与 gpu-memory-utilization<=0.5 上限的 vLLM 可以共存。

检查:列出所有驻留 GPU 的进程:

nvidia-smi
ps aux | grep -E "vllm|ollama|python.*train" | grep -v grep

nvidia-smi 显示每进程内存;ps 过滤能抓到在统一内存下可能不清晰出现在 nvidia-smi 输出中的推理服务(vLLM、Ollama)。

修复:single-heavy-job 规则只适用于未设上限或接近容量的工作负载——先停止或限制无关服务。实测:<4GB 的 LoRA 微调与 --gpu-memory-utilization 0.5(或更低)启动的 vLLM 在同一台机器上能干净共存。判断是否要停掉另一个进程,要看它的内存上限,而不是只看它是否存在。

G7:SM121 上 NVFP4 比 FP8 更慢

症状:把 Spark 上的推理负载从 FP8 切到 NVFP4 反而变慢。

根因:除非 kernel 以 sm_121a 为目标编译,SM121 缺少 cvt.e2m1x2 指令;没有它 NVFP4 大约慢 32%。

检查

python -c "import torch; print(torch.cuda.get_device_capability())"

GB10 上应返回 (12, 1)——这确认了 SM121。SM121 没有原生的 cvt.e2m1x2 转换路径,未针对 sm_121a 目标编译的 NVFP4 kernel 会回退到慢路径,大约落后 FP8 32%。检查 kernel 构建的目标架构(通常是 TORCH_CUDA_ARCH_LIST 或类似构建标志),再假设 NVFP4 在这块硬件上是更快的选择。sm_121sm_121a 的差异在 stack-matrix.md 中有专门一节:sm_121a 是超集目标,NVFP4 的原生转换指令需要它。

修复:除非构建目标为 sm_121a,否则继续用 FP8。

G8:官方 playbook 过时

症状:严格照搬官方 DGX Spark playbook 仍然失败,本地配置找不到任何解释。

根因:官方 playbook 曾不止一次发布即损坏;技术栈演进速度快于文档。

检查

gh issue list --repo NVIDIA/dgx-spark-playbooks --state open --limit 20

需要已认证的 gh CLI,或用浏览器访问同 URL 替代。在按字面信任某个 recipe 跑长任务或昂贵任务之前,先扫一遍 open issue 中与你遵循的 playbook 和命令相关的条目。

修复:信任某条 recipe 用于昂贵运行之前,先查 NVIDIA/dgx-spark-playbooks 仓库的近期 issue。这条纪律同样写在 spark-environment-setupstack-matrix.md 中——后者明确列出 github.com/NVIDIA/dgx-spark-playbooks 等权威资源,并给出同样的告诫:"官方 playbook 发布过损坏版本。开始长任务之前先检查各仓库的近期 issue,而不是等它失败之后。"

G9:容器优先,而非裸 pip

症状:昨天还好用的裸 pip 环境,在一次无关的 pip install 之后坏掉;或两个"一模一样"的环境行为不同。

根因:裸 pip 让 Triton、xformers、transformers 各自漂移,没有任何机制把它们钉在 GB10 的 SM121 目标上。

检查

if [ -f /.dockerenv ] || [ -f /run/.containerenv ]; then
  echo "in container (marker file)"
elif grep -qE '(docker|containerd|kubepods)' /proc/1/cgroup 2>/dev/null; then
  echo "in container (cgroup marker)"
else
  echo "unknown — no container marker matched, this does not prove a bare host"
fi
pip list 2>/dev/null | grep -E "^(torch|triton|xformers|transformers) "

第一段检查 Docker 的 /.dockerenv 与 Podman 的 /run/.containerenv 标记文件,然后回退到 cgroup 字符串检查——单独 grep docker /proc/1/cgroup 并不可靠:cgroup v2 布局和部分运行时/命名空间会隐藏运行时名称,所以匹配失败只能算"unknown",永远不能证明是裸主机。第二段列出实际安装版本——裸 pip 时与 NGC 或 Unsloth 镜像中的锁定集对比,尽早发现漂移而不是等到 import 时报错。

修复:优先使用 NGC 容器(tag 选择见 spark-environment-setupcontainer-workflow.md),或 Unsloth 的容器。如果确实无法避免裸 pip,严格按 NVIDIA 的安装顺序执行,包括对 Unsloth 使用 --no-deps

pip install "transformers==5.13.1" "peft==0.19.1" "hf_transfer==0.1.9" "datasets==4.3.0" "trl==1.8.0"
pip install --no-deps "unsloth==2026.7.2" "unsloth_zoo==2026.7.2" "bitsandbytes==0.49.2"
pip install -U "torchao==0.17.0"

--no-deps 不是可选项——让 pip 在 aarch64 上重新解析 Unsloth 的依赖树,是拉入不兼容 torch/triton 构建的常见路径;第三行的 torchao==0.17.0 也非可选——NGC 基础镜像自带的 torchao 对当前 peft 的 LoRA-attach 路径太老(ImportError: ... torchao ... only versions above 0.16.0 are supported),这是硬阻断而非警告。每个 == pin 都是承重的,来自 stack-matrix.md 中带日期的 known-good 版本矩阵。

G10:双 Spark 只支持 DDP/FSDP

症状:跨两台 Spark 的 tensor-parallel 启动挂起、比单 Spark 慢得多、或直接报错。

根因:ConnectX-7 对梯度/参数同步(DDP、FSDP)足够快,但对 TP 的细粒度流量太薄。

检查:确认配置的并行策略:

python -c "
import os
print('WORLD_SIZE:', os.environ.get('WORLD_SIZE'))
print('parallelism strategy check: confirm config uses DDP or FSDP, not TP/tensor_parallel')
"
rg -l --iglob '*.yaml' --iglob '*.yml' -e 'tensor_parallel|tp_size|tensor-parallel' . \
  || grep -rlE "tensor_parallel|tp_size|tensor-parallel" --include='*.yaml' --include='*.yml' .

要递归搜索而非只搜当前目录——非递归的 *.yaml *.yml glob 会漏掉嵌套配置,而且被重定向/抑制的错误会读成"没有 TP 配置",其实可能只是"目录不对"。如果工作负载的配置路径已知,直接显式传路径搜索即可。任何在双 Spark 任务上的匹配都是配置错误。

修复:双 Spark 场景选择 DDP 或 FSDP,绝不用 tensor parallelism——TP 在这块硬件上仅限单节点。

快速分流(Fast Triage)

跑任何其他检查之前,先执行这三条最廉价的检查:

python3 -c "import torch; print(torch.version.cuda)"  # 期望 13.x (G1);NGC 构建没有 +cu130 tag——那不是失败
import torch; print(torch.cuda.get_device_capability())  # 期望 (12, 1) (G7)
{ [ -f /.dockerenv -o -f /run/.containerenv ] || grep -qE 'docker|containerd' /proc/1/cgroup; } 2>/dev/null && echo container || echo unknown  # G9

自动化预检:preflight.sh

[assets/preflight.sh](https://gitcode.com/GitHub_Trending/agents24/agents/blob/a30778f8c4e6b0a87567941b7cca4f534bf642b6/plugins/dgx-spark-ops/skills/spark-training-gotchas/assets/preflight.sh?utm_source=gitcode_repo_files) 对 G1–G10 中可自动化的子集做快速检查,覆盖 G1、G3、G4、G7、G9,其余(G2 的 flash-attn/Unsloth 覆盖检查、G6 进程普查、G8 上游 issue 查询、G10 配置审查)因需要人工判断不在此脚本覆盖范围,需回到 gotcha-checks.md 手动执行。用法:

bash preflight.sh

输出契约(从源码头注释可见,preflight.sh L1-L13)非常明确,这也是下游工具能解析它的基础:

  • 每一行结果都以 G 编号开头
  • G1/G9 输出 PASS/FAIL/WARN 判定;
  • G3/G4 输出 INFO: 原始读数(需要结合 SKILL.md 的人工判断);
  • G7 只对硬件 capability 输出 PASS/WARN——它验证 kernel 目标架构(sm_121a),那一步仍需手动,见 gotcha-checks.md G7;
  • 无法执行时输出 SKIP:(如 torch 不可导入、nvidia-smi 不可用、/proc/1/cgroup 不可读)。

脚本具体行为(preflight.sh L16-L49):

  • G1:读取 torch.version.cuda,为空输出 G1 SKIP: torch not importable;以 13 开头输出 G1 PASS;否则 G1 FAIL 并提示需要 13.x;
  • G3free -g 取第二行输出 G3 INFO: free/used (GB): <free> <used>
  • G4nvidia-smi --query-gpu=temperature.gpu,power.draw 单次快照输出 G4 INFO: ...,不可用时 G4 SKIP: nvidia-smi not available
  • G7:capability 等于 (12, 1) 输出 G7 PASS(附带"需手动验证 sm_121a"的说明),否则 G7 WARN: unexpected capability ...
  • G9:Docker/Podman 标记文件命中输出 G9 PASS: ... marker file present;cgroup 命中输出 G9 PASS: ... cgroup marker present/proc/1/cgroup 可读但无匹配输出 G9 UNKNOWN(并明确"这不证明是裸主机");不可读输出 G9 SKIP

在插件体系中的位置

该技能不是孤立的:spark-training-gotchasdgx-spark-ops 插件(dgx-spark-ops-engineer 智能体 + spark-preflight 命令)的核心执行部分。完整预检工作流是:

  1. spark-environment-setup 确认硬件身份(GB10/aarch64/CUDA 13,capability (12, 1))与栈状态;
  2. 运行本技能的 preflight.sh(自动覆盖 G1/G3/G4/G7/G9),并用 gotcha-checks.md 评估其余 G2/G5/G6/G8/G10,每个结论必须标注 G 编号;
  3. spark-memory-thermal-ops 的 UMA 核算表计算负载内存余量;
  4. 写出 env-report.json,verdict 为 ready / ready-with-warnings / blocked,blocked 时先给出对应 gotcha 的 FIX 再谈其他。

技能间的分工是:spark-training-gotchas 负责启动前的失败模式预检,spark-memory-thermal-ops 负责运行中的 UMA OOM 与热节流处置,spark-environment-setup 提供两者依赖的"已被验证可工作"的环境前提。三者的版本矩阵、容器 tag 与检查命令都以带日期的 known-good 快照为准——gotcha-checks.md 顶部注明"Last verified: 2026-07-14,CUDA、PyTorch 或 DGX Spark 栈发布新主版本时应刷新",使用前请留意该日期是否已过时。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.16 K
2.78 K
kernelkernel
deepin linux kernel
C
34
18
docsdocs
暂无描述
Markdown
904
5.83 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
932
1.86 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
862
1.36 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.95 K
1.03 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.38 K
1.47 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
535
606
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
549
398
leetcodeleetcode
🔥LeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer(第 2 版)》、《程序员面试金典(第 6 版)》题解
Markdown
77
23