关于 spark-memory-thermal-ops
面向 DGX Spark GB10 训练用户的技能,指导统一内存预算、OOM 排查和长任务热状态监控,并说明并发负载的资源竞争。
主要功能
- 01按 free -g 规划内存余量
- 02按顺序处理统一内存 OOM
- 03记录温度与功耗变化
- 04评估训练与推理并发
适用场景
- 01启动前估算训练任务是否适配内存
- 02训练中 OOM 时逐步采取缓解措施
- 03长时间任务降速时检查热节流
- 04决定是否停止未限额的推理服务
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wshobson/ agents --skill spark-memory-thermal-ops安装后在 Claude Code 中输入/spark-memory-thermal-ops确认已加载。
附带文件assets/thermal-sample.shreferences/uma-accounting.md
详细介绍
Spark 内存与散热运维
DGX Spark 的 GB10 芯片拥有一个 128GB 的统一内存 (UMA) 池,由 CPU 和 GPU 共享,并且其持续功率上限远低于额定值。这两点都打破了独立 GPU 的假设:余量并非 nvidia-smi 所报告的那样,而且一次开始很快的运行会在任务中途变慢,却没有任何配置错误。本技能涵盖内存余量的规划、处理实际的 OOM,以及在长时间任务中监控散热。关于启动时的故障模式(ABI 不匹配、flash-attn、playbook 损坏),请参阅 spark-training-gotchas —— 本技能假设任务能够启动。
常见问题快速参考
| 情况 | 做法 |
|---|---|
| 启动前规划余量 | 依据 free -g 做预算,而非 nvidia-smi —— 参见 UMA 内存模型 |
| 任务在统一内存上 OOM | 按顺序执行 OOM 阶梯:先 flush,再调整 batch/pack,最后降级方法 |
| 运行中途吞吐量下降 | 在假定是配置问题之前先检查功率/温度日志 —— 参见散热监控 |
| 同时需要训练器和推理服务 | 一次只运行一个 —— 参见并发工作负载 |
何时使用本技能
- 在启动前针对 128GB 池为一次训练运行做规模评估 —— 这个模型、方法和 batch/pack 组合是否能装下。
- 一次运行在加载中途或步骤中途 OOM,而补救顺序很重要 —— 首先、其次、第三该尝试什么。
- 在多小时的作业中监控温度和功率,判断变慢是散热降频还是其他原因。
- 计划在同一台机器上让训练器与推理服务(vLLM、Ollama)并行运行。
UMA 内存模型
Spark 没有独立的 GPU 显存 —— GPU 和 CPU 共享一个 128GB 池。两个后果:
-
nvidia-smi和cudaMemGetInfo会低报压力 —— 或者根本不报告任何内容。 两者报告的都是 CUDA 分配器可见的内存,而非池的实际状态 —— 一台机器可能在nvidia-smi中显示有余量,却仍然 OOM,因为分配器看不到的页缓存和 mmap 页面消耗着同一个池。在某些驱动/配置下,内存查询会直接返回[N/A], [N/A]而不是数字 —— 在那里用 grep 查找数值的脚本会一无所获,而不是得到一个误导性的低估值(参见spark-training-gotchas的 gotcha G3)。 -
模型加载是一个瞬时峰值,而非稳态。 加载 safetensors 权重时会 mmap 该文件,然后复制到 CUDA 张量中 —— 在加载期间的一个窗口内,mmap 页面和 CUDA 副本会同时计入池。一个在训练时能装下的模型,如果在加载期间仍可能 OOM,是因为余量是按加载后的占用而非这个翻倍的瞬态来设定的。
用 free -g 而非 nvidia-smi 来规划和诊断:
free -g | awk 'NR==2 {print "free:", $4, "GB"}'
经验法则:取那个 free 值,减去几 GB 用于 OS/驱动开销,然后依据结果做预算 —— 而不是 128GB 的规格数字。
references/uma-accounting.md 中的工作表接受参数量、dtype 和方法作为输入,并返回一个内存估算值,用于与已知锚点进行比较。
规划顺序
启动前,按顺序完成以下步骤:
- 读取
free -g;减去 OS/驱动开销作为预算。 - 根据
references/uma-accounting.md估算权重 + 优化器 + 梯度 + 激活值。 - 与最接近的锚点(70B QLoRA、27B LoRA、9B 全量 FT)比较,而不是仅凭估算值。
- 如果估算值接近预算,就从更短的 packing 或更小的 batch 开始 —— 这比在运行中途触及 OOM 阶梯代价更低。
示例:为 70B QLoRA 运行做规模评估
对照 ≈40GB 锚点对工作表公式做一次合理性检查:
params = 70e9
weights_gb = params * 0.5 / 1e9 # NF4, step 1
adapter_gb = 0.5 # step 5, negligible
total_gb = weights_gb + adapter_gb # + activations
print(f"{total_gb:.0f}GB before activations")
仅权重就接近 ≈40GB 锚点 —— 对同一模型类别估算远高于此的计划,是一个需要重新检查 dtype 和方法的信号。
OOM 阶梯
当作业在统一内存上发生 OOM 时,请按顺序执行这套阶梯。每一步都比上一步更具破坏性——不要跳步: 减小 batch size 永远不是第 1 步。
-
清空缓冲区缓存。 上一次运行或读取大型数据集留下的页缓存,往往就占用了数 GB 的“缺失”余量。这一步除了重跑之外没有任何代价,也不会改动作业的配置:
sync; echo 3 > /proc/sys/vm/drop_caches需要 root 权限;这是运行之间的重置,而不是训练中途的步骤。关于这一步背后的完整诊断,参见
spark-training-gotchas(gotcha G3)。 -
减小 batch size 或 packing length。 只有在清空缓存仍未能释放出足够余量之后,才削减 batch size 或 packing length——这是第一个会改变运行行为的步骤。优先调整 packing length;在长上下文场景下,它更直接地决定激活内存占用。
-
降级方法:先 bf16 LoRA,再 QLoRA。 如果清空缓存并缩小 batch/pack 后仍然 OOM,就把方法降一档——下一步是 bf16 LoRA,而不是反过来。QLoRA 的 bitsandbytes 反量化缓冲区是临时的 CUDA 侧分配,可能在等效的 bf16 LoRA 运行尚未 OOM 之前就先 OOM,尽管 QLoRA 的稳态内存占用更小。QLoRA 出现 OOM 并不能证明模型放不下。
只有在以上三步全部尝试后作业仍然放不下时,才进一步退让(更小的模型、多台 Spark)。
热监控
长时间运行会触及 Spark 的持续功耗上限,远低于标称值——这是平台预期行为,不是需要解释掉的异常症状:
-
将温度与功耗采样与训练日志并行记录,而不是等到发现变慢之后才记录——每 30-60 秒采样一次,就能把吞吐下降与热事件关联起来。保持
assets/thermal-sample.sh写入的 CSV 输出格式,这样时间戳就能与日志对齐:bash assets/thermal-sample.sh 30 thermal.log -
持续约 100W 的功耗是平台上限,不是配置 bug。 不要为了“修复”一个在负载下正常表现的平台所产生的平台期而重新调整 batch size 或精度。如果温度上升而功耗在标称 240W 以下保持平稳,这就是需要识别的特征。
-
明确记录节流事件,而不是让运行在无记录的情况下悄悄变慢。一次运行如果每步耗时在两小时后翻倍,就应该在日志中体现出来,并与该时间戳的热采样相关联。完整的节流诊断:
spark-training-gotchas(gotcha G4)。
并发工作负载
由于 128GB 内存池是全局共享的,淘汰会发生在两个进程的日志都不显示 OOM 的情况下:
-
单重负载作业规则适用于未设上限或接近容量的工作负载——未设上限的训练器和推理服务(vLLM、Ollama)会争抢同一个内存池。一个规模小、设有上限的工作负载则不会:一个 <4GB 的 LoRA 微调可以与上限设为
gpu-memory-utilization<=0.5的 vLLM 良好共存——在停止另一个进程之前,要检查它的上限,而不只是它是否存在。 -
在未设上限/接近容量的争抢下,推理服务会悄悄淘汰训练器的页面,反之亦然——两者都不会记录错误,因此运行变慢或 KV cache 丢失都是需要排查的争抢症状。在长时间运行或占满内存池的运行之前,先停止无关的未设上限服务。
首先检查驻留 GPU 的进程:
ps aux | grep -E 'vllm|ollama|trl|axolotl' | grep -v grep
本流程与 spark-training-gotchas(gotcha G3、G4、G6)互补——那项技能涵盖启动阶段的失败;本流程涵盖运行中的作业。
内存计算工作表:
references/uma-accounting.md。