DGX Spark 训练故障排障手册:G1–G10 可运行检查命令与修复指南
NVIDIA DGX Spark 搭载 GB10(Grace Blackwell,SM121,aarch64 架构,128GB 统一内存,CUDA 13),围绕启动、内存、散热、带宽与精度存在十个反复出现的故障模式。本指南以 plugins/dgx-spark-ops 插件的 spark-training-gotchas 技能中编号 G1–G10 的检查命令(references/gotcha-checks.md)为核心,逐一给出每个故障的可运行检测命令、判定标准与修复手段,并交叉印证 preflight.sh 自动化脚本、环境搭建与 UMA 内存/散热技能中的底层依据。读完你可以在长训练任务启动前、或运行失败时按编号快速定位根因,避免"第 6 小时才发现问题"。
背景:GB10 硬件特性决定故障模式
DGX Spark 的 GB10 芯片有四个打破常规硬件假设的特征,十个 gotcha 全部由此派生:
- 128GB 统一内存(UMA):GPU 与 CPU 共享同一个内存池,
nvidia-smi只报告 CUDA 侧视图,无法反映真实内存压力(G3、G6); - CUDA 13 与 aarch64 生态较新:PyPI 上大量 wheel 仍链接
libcudart.so.12,与系统 ABI 不匹配,且 aarch64 + CUDA 13 的 wheel 生态仍在补齐中(G1、G2、G9); - 持续功耗上限远低于额定值:额定 240W,持续负载下实际被压到约 100W,长时间运行会触发降频甚至重启(G4);
- 内存带宽为实测上限而非规格值:273 GB/s 是规格天花板,实测稳定在 180–192 GB/s(G5)。
编号 G1–G10 是"承重"约定:技能面向的工具(如 preflight.sh 与 dgx-spark-ops-engineer agent)按编号输出与消费检查结果,任何 DGX Spark 专项诊断都必须引用其 G 编号。
症状速查表
| # | 症状 | 修复方向 |
|---|---|---|
| G1 | undefined symbol / 段错误 | 使用 cu130 wheel 或匹配的容器 |
| G2 | flash-attn 后端被错误选用 | 裸 pip 跳过构建;NGC 容器上使用 monkeypatch |
| G3 | 有剩余内存仍 OOM | 释放 page cache |
| G4 | 吞吐下降 / 重启 | 预期持续功耗约 100W 上限 |
| G5 | 内存受限步骤变慢 | 按 180–192 GB/s 预算 |
| G6 | 运行中途缓存被驱逐 | 同一时刻只跑一个重型 GPU 任务 |
| 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() 调用即段错误,无有效 traceback。
原因:大部分 PyPI wheel 链接 libcudart.so.12,而 Spark 出厂自带 CUDA 13。pip 的解析器只检查版本约束、从不检查 CUDA ABI,所以问题要到 import 或首次 kernel 启动时才暴露。
检查命令(见 gotcha-checks.md G1):
python3 -c "import torch; print(torch.version.cuda)"
python -c "import ctypes; ctypes.CDLL('libcudart.so.13')"
ldconfig -p | grep libcudart
判定要点:
torch.version.cuda是权威信号,应报告13.x;- 不要依赖
pip show torch | grep cu130:NGC 容器(如nvcr.io/nvidia/pytorch:25.11-py3)内部针对 CUDA 13 构建 torch、不带+cu130wheel tag,因此该命令查不到cu130本身不构成失败; ctypes加载用于确认libcudart.so.13确实存在于系统中:若抛出OSError,问题出在驱动/运行时安装而非 wheel;ldconfig -p列出所有已注册的 CUDA 运行时版本——libcudart.so.12与libcudart.so.13并存是早期安装留下的常见残留,也是 ABI 不匹配的最可能来源。
修复:从 download.pytorch.org/whl/cu130 重装(cu130 标签的 aarch64 构建),或改用已内建匹配构建的容器。更完整的五假设排查顺序(运行时/标志 → 设备可见性 → 权限 → CUDA init 状态 → ABI)见 stack-matrix.md,其中 ABI 是唯一一个 wheel 重装能解决的假设,应在排除前四项之后再动 wheel。
G2:flash-attn——跳过 pip 构建,警惕 Unsloth 的自动探测
症状:pip install flash-attn 依旧失败或挂起;或显式请求 SDPA 后 Unsloth 仍静默用 flash-attn 训练。
检查命令(见 gotcha-checks.md G2):
python -c "import flash_attn; print(flash_attn.__version__)" 2>&1 | tail -5
python -c "import torch; print(torch.backends.cuda.flash_sdp_enabled())"
判定要点(不要假设 import 一定失败,取决于环境):
- 裸 pip 环境:import 失败是预期行为——不存在 aarch64/sm_121 wheel,这恰好证明没有任何训练代码路径静默依赖 flash-attn;
- NGC PyTorch 容器(已在
nvcr.io/nvidia/pytorch:25.09-py3上确认):flash-attn 2.7.4.post1 预置,GB10(能力(12, 1))上实际调用flash_attn_funckernel 成功且输出形状正确——"没有 sm_121 kernel"的说法只适用于自己从源码构建,不适用于容器已携带的构建。
真正的陷阱(容器内 flash-attn 存在时):Unsloth 的 import 时补丁横幅报告 FA2 = True,并自动优先选择 flash-attn 而非 SDPA——即使调用方显式传了 attn_implementation="sdpa"。Unsloth 的 loader(unsloth/models/llama.py,2026.7.2)调用其内部 resolve_attention_implementation(...) 时,没有把调用方的 attn_implementation 作为该函数的 requested_attn_implementation 参数转发,随后用 # No need since we auto call it 注释直接弹出该 kwarg,静默丢弃调用方请求。已通过真实加载确认:显式传入 attn_implementation="sdpa" 仍解析为 model.config._attn_implementation == "flash_attention_2"。
该 Unsloth 版本上唯一可靠的覆盖方式(针对任何经由 Unsloth llama 架构 loader 路由的模型,且 flash-attn 可导入时),是在调用 from_pretrained 前打 monkeypatch:
import unsloth.models._utils as _unsloth_utils
_unsloth_utils.HAS_FLASH_ATTENTION = False
这迫使 resolve_attention_implementation 的自动解析走向 elif supports_sdpa: 分支而非 flash-attn 分支——已确认有效(monkeypatch 后 config._attn_implementation == "sdpa")。而在纯 TRL/PEFT 路径(完全绕过 Unsloth)下,AutoModelForCausalLM.from_pretrained 显式传入的 attn_implementation="sdpa" 会被正确遵守——该缺陷是 Unsloth 特有的,不是 TRL/transformers 的通用问题。
G3:UMA OOM——内存低于 128GB 就失败
症状:模型加载/训练期间 OOM,而 nvidia-smi 仍报告 128GB 上限之下有剩余内存;某些驱动/配置组合下干脆返回 [N/A]。
原因:统一内存下 mmap 与 CUDA allocator 在 safetensors 加载期间双重计数页面;QLoRA 的 bitsandbytes 反量化会产生瞬时分配,可能比 bf16 更早 OOM。
检查命令(见 gotcha-checks.md G3):
free -g
cat /proc/meminfo | grep -i huge
判定要点:
- 真实内存压力读
free -g,不是nvidia-smi——UMA 下 CUDA 分配与主机 RAM 共享一个池,nvidia-smi只报 CUDA 侧; - 某些驱动/设置组合下
nvidia-smi --query-gpu=memory.used,memory.total --format=csv不只是少报,而是整个 GPU 内存查询直接返回[N/A], [N/A]。在这种硬件上,据此 grep 数字的脚本什么都拿不到(而不是拿到误导数字)——不要把余量检查建在该查询上; - 若负载失败时
free -g显示 128GB 已被消耗大半,回收内存:
sync
echo 3 > /proc/sys/vm/drop_caches
需要 root,且会系统级刷新 page cache——机器上所有进程(不只是训练任务)都会失去缓存的文件读取。应在两次运行之间、内存看起来被陈旧 mmap 页钉住时执行,不要作为训练中的例行步骤。
G4:热降频(Thermal Throttling)
症状:多小时运行中途吞吐下降,或持续负载下机器自发重启。
原因:持续功耗被压在约 100W(相对额定 240W);长时间运行推入该上限后降频,极端情况下重启。
检查命令(见 gotcha-checks.md G4):
nvidia-smi --query-gpu=temperature.gpu,power.draw --format=csv -l 5
判定要点:
-l 5表示每 5 秒采样一次;在代表性负载上至少连续运行 10–15 分钟再下结论;- 额定功耗 240W:若功耗在约 100W 处持平、而温度持续攀升或已在高位持平,说明机器在降频;
- 温度上升但功耗仍接近峰值尚不是降频事件——继续观察;
- 完成时 Ctrl-C 中断即可,此命令不需要 root。
G5:带宽天花板(Bandwidth Ceiling)
症状:内存受限负载(尤其是 decode 密集的 RL 循环)吞吐远低于预期。
原因:273 GB/s 是规格天花板而非持续可达值,实测带宽在 180–192 GB/s。
检查命令(见 gotcha-checks.md G5):
注意:本检查是侵入性的,与本文其他检查不同——不要在活跃负载上运行。 它在 UMA 系统上分配约 4GB(GPU 与主机内存共享一个池),并反复 clone 该张量。只在空闲主机上运行;如果机器上已有任务占用了 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 在同一台机器上和平共存。
检查命令(见 gotcha-checks.md G6):
nvidia-smi
ps aux | grep -E "vllm|ollama|python.*train" | grep -v grep
判定要点:
- 启动长任务前列出每个 GPU 常驻进程。
nvidia-smi显示每个进程的内存;ps过滤能抓住 vLLM、Ollama 等推理服务——它们在统一内存下可能不会清晰出现在nvidia-smi输出里; - "一次一个重型任务"规则适用于未设上限或接近容量的负载:在需要完整 128GB 池、或与另一个未显式设内存上限的进程并行前,先停掉无关服务;
- 该规则不适用于小型、设了内存上限的共存——判断是否需要停止某进程前,检查的是对方进程自身的内存上限,而不只是"它存在"。
G7:SM121 上 NVFP4 反而比 FP8 慢
症状:把推理负载从 FP8 切换到 NVFP4 后更慢而非更快。
原因:SM121 缺少原生 cvt.e2m1x2 转换路径;未针对 sm_121a 目标编译的 NVFP4 kernel 会退回较慢路径,约比 FP8 慢 32%。
检查命令(见 gotcha-checks.md G7):
python -c "import torch; print(torch.cuda.get_device_capability())"
判定要点:GB10 上应输出 (12, 1)——确认 SM121。在假设 NVFP4 是本硬件上的更快选择之前,先检查 kernel 构建的目标架构(通常是 TORCH_CUDA_ARCH_LIST 或类似构建标志)。sm_121 与 sm_121a 的区别详见 stack-matrix.md 的 sm_121 vs sm_121a 一节:NVFP4 原生 cvt.e2m1x2 需要 sm_121a 超集目标。
G8:官方 Playbook 过时
症状:照抄官方 DGX Spark playbook 仍然失败,本地配置查不出原因。
原因:官方 playbook 曾出现过发布即损坏的情况,堆栈演进速度快于文档。
检查命令(见 gotcha-checks.md G8):
gh issue list --repo NVIDIA/dgx-spark-playbooks --state open --limit 20
需要已认证的 gh CLI,或用浏览器访问同一 URL 代替。在把某个 playbook 及其命令原样用于长时或昂贵运行之前,先扫一遍 open issues 确认其未受影响。这一点在 stack-matrix.md 与 spark-environment-setup SKILL.md 中同样被列为标准 preflight 步骤。
G9:容器优先,而非裸 pip
症状:昨天还能用的裸 pip 环境在一次无关的 pip install 后损坏;或两个"相同"环境表现不同。
原因:裸 pip 让 Triton、xformers、transformers 各自漂移,没有任何机制把它们钉在 GB10 的 SM121 目标上。
检查命令(见 gotcha-checks.md G9):
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 时才暴露。
容器优先的根本原因是钉定而非便利:Triton、xformers、transformers 与 GB10 的 SM121 目标和 CUDA 13 的交互很狭窄,容器把三者锁在与本硬件已验证的组合上。NGC 容器启动方式(含 --runtime=nvidia --gpus all、--ipc=host 与 memlock ulimit 等标志)见 container-workflow.md。若确实无法避免裸 pip,必须严格按 NVIDIA playbook 顺序安装,包括 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 与最后的 torchao 升级行都是不可省略的:前者避免 pip 在 aarch64 上重新解析出不兼容的 torch/triton 构建,后者因为 NGC 基础镜像自带的 torchao 过旧(低于 0.16.0 会直接 ImportError)。完整版本矩阵见 stack-matrix.md 的 Known-Good Version Matrix。
G10:双 Spark 只能用 DDP/FSDP
症状:跨两台 Spark 做张量并行(TP)启动时挂起、比单机明显更慢,或直接报错。
原因:ConnectX-7 对梯度/参数同步(DDP、FSDP)足够快,但对 TP 的细粒度通信来说带宽太薄。
检查命令(见 gotcha-checks.md G10):
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 *.ymlglob 会漏掉嵌套配置,而被重定向/压制的错误会被误读成"没有 TP 配置",实际只是"目录不对"; - 若工作负载的配置路径已知,直接显式传入,不要搜索;
- 双 Spark 任务中任何匹配项都是配置错误——启动前切到 DDP 或 FSDP;在本硬件的 ConnectX-7 链路上 TP 不可行。
自动化:preflight.sh 一键执行 G1/G3/G4/G7/G9
preflight.sh 是 G1–G10 检查中可自动化子集的实现,输出契约固定:每行结果以 G 编号开头。运行方式:
bash assets/preflight.sh
行为说明(直接来自脚本头部注释与实现):
- G1:读取
torch.version.cuda,以13*前缀判定,输出G1 PASS/FAIL/SKIP; - G3:
free -g的原始读数,输出G3 INFO: free/used (GB): ...(需人工判断,参考 SKILL.md G3); - G4:
nvidia-smi --query-gpu=temperature.gpu,power.draw快照,输出G4 INFO: ...,nvidia-smi不可用时输出G4 SKIP; - G7:
torch.cuda.get_device_capability()是否为(12, 1),输出G7 PASS/WARN——注意它不验证 kernel 目标架构(sm_121a),该步保持人工检查; - G9:容器标记文件 + cgroup 回退检查,输出
G9 PASS/UNKNOWN/SKIP,UNKNOWN 分支明确提示"这不证明是裸主机"。
不可自动化的 gotcha(G2 的 flash-attn 存在性/Unsloth 覆盖检查、G6 进程调查、G8 上游 issue 查询、G10 配置审查)不在脚本内,需按本指南对应小节人工执行。
这套检查与 spark-environment-setup(环境准备、ABI 规则、容器优先)、spark-memory-thermal-ops(运行期 UMA 与散热管理,含 OOM Ladder 与内存核算表 uma-accounting.md)共同构成完整的 DGX Spark 运维闭环:dgx-spark-ops-engineer agent 按 spark-preflight 命令 的流程先跑 preflight,再以 pass/fail/warn/skip/info 词汇逐项记录并输出 env-report.json,最终给出 ready / ready-with-warnings / blocked 三档结论。
快速分诊:跑这 3 条再看其他
在深入任何一项之前,先跑成本最低的三个检查(同样列于 SKILL.md 的 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
三条命令分别锁定 ABI、硬件身份与容器姿态三个最基础的变量,其中任何一条失败都会让后续所有检查失去意义。全部通过后,再按症状速查表定位到具体的 G 编号,执行对应的检查命令,并始终以该编号的 FIX 条目为准完成修复。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
video-shotcraftAI宣传片skill,使用 Remotion 制作电影级产品视频:提供106 张镜头配方卡和可复用的视频魔板。适用于 Claude Code 与 Codex以及所有其他智能体Markdown00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00