调试手册¶
调试目标是找到第一个语义分叉点。先固定输入、版本和随机状态,再沿数据 → 目标 → 数值 → 分布式 → kernel → 运行时逐层缩小;不要同时改变多项配置期待异常消失。
通用起点¶
保存一个可重放 failure capsule:
- 原始样本与 token IDs;
- tokenizer、template、model 与 adapter 哈希;
- global step、token count、data cursor 与 RNG;
- 完整 shape、stride、dtype、device 和 placement;
- 解析后的配置;
- 首个异常 tensor 前后的统计;
- 单 rank/reference 与优化路径输出;
- 错误、日志、trace 和环境版本。
先在最小 shape 上复现,再逐项恢复真实长度、batch、精度、并行和融合。
Loss 异常¶
一开始就不对¶
按顺序检查:
- labels 是否正确 shift;
- padding、prompt、tool observation 与文档边界是否 mask;
- tokenizer 和 chat template 是否与 checkpoint 匹配;
- vocabulary、special token 和 weight tying;
- loss 用 token sum 还是 mean;
- 初始化、学习率和 optimizer state;
- logits 是否已有 NaN/Inf。
用手撕 token CE 对一个 batch 计算 numerator、denominator 与逐 token loss。
训练中 spike¶
同时记录:
- 具体数据样本与长度;
- loss、grad norm、update norm 与参数 norm;
- learning rate、weight decay 与 scaler;
- 每层 activation/logit 范围;
- collective 前后 global norm;
- 是否刚切 mixture、context length 或 checkpoint;
- 哪个 rank 首先出现非有限值。
不要直接跳过 spike batch;先确认它是坏数据、真实困难样本、数值溢出还是分布式缩放错误。
Loss 正常但能力退化¶
检查训练目标与评测任务是否同分布、chat template 是否漂移、response mask 是否学到用户文本、数据混合是否造成遗忘,以及 benchmark 是否更换 harness。平均 loss 可能掩盖某个域或长度 slice 的退化。
梯度与优化器¶
unscale → finite check → global norm → clip → step顺序是否正确;- overflow 时参数和 optimizer moments 是否都不更新;
- AdamW decay 是否错误施加到 norm/bias;
- gradient accumulation 是否除以真实 global token 数;
- mixed precision master weight 与 state dtype;
- activation checkpoint 前后 dropout RNG;
- LoRA merge 状态是否在训练时重复计入;
- frozen 参数是否真的没有 grad 与 optimizer state。
在 FP64 小模型上比较一步手写 AdamW 与框架实现,再扩大到目标 dtype。
分布式 Hang¶
- 收集所有 rank 的最后一个 collective 序号;
- 比较 process group、op、count、dtype 与 tensor shape;
- 检查条件分支是否让部分 rank 跳过调用;
- 检查 async handle、stream event 与 buffer 生命周期;
- 检查 dataloader 是否让 rank 步数不同;
- 检查某 rank 先 OOM 或异常退出;
- 从两 rank、单 collective 的最小复现开始。
不要通过无限增大 timeout 掩盖顺序错误。集合通信和系统韧性给出全局契约。
OOM¶
先做显存账本:
定位 OOM 发生在 forward、backward、optimizer step、all-gather、checkpoint staging、prefill 还是 decode。峰值前后抓 allocator snapshot;只看 nvidia-smi 无法解释短暂峰值。
训练侧检查 sequence/batch、重计算、sharding 与 all-gather;推理侧检查 admission、输出上限、KV page、prefix refcount 和取消回收。
Kernel 不一致¶
每次只比较一个 kernel:
- 固定输入和 mask;
- FP64 或框架 reference;
- forward;
- backward;
- 极端值与全 mask;
- 非连续 stride、尾块与非整除 shape;
- mixed precision;
- graph capture;
- 真实 shape 性能。
声明 storage、input、accumulator、reduction 与 output dtype。同步仅用于测量,不要误放进热路径。
训练与增量推理不一致¶
优先检查:
- causal mask 在 \(T_q\ne T_k\) 时的 offset;
- RoPE absolute position 与扩长参数;
- cache 保存旋转前还是旋转后 K;
- GQA query 到 KV head 映射;
- full prompt、chunked prefill 与逐 token decode;
- padding、position IDs 和 sequence boundary;
- train/eval、dropout 和 norm epsilon;
- quantized cache scale 与 layout;
- tokenizer、template 和 stop token。
完整 Transformer reference 包含 full 与 cached logits 对照。
服务尾延迟¶
把 TTFT 拆成:
同时切片:
- prompt/output 长度;
- cache hit/miss;
- prefill/decode 混合;
- compile bucket;
- tenant/priority;
- worker、GPU、机架与网络;
- preemption、migration 和 retry;
- 冷启动与稳态。
吞吐上升而 p99 变差通常是 queue、batch 或干扰变化,不应只调模型 kernel。Goodput 调度给出 admission 与 SLO 视角。
KV 泄漏与错乱¶
检查:
- block refcount 是否等于 live owner;
- shared partial block 写前是否 COW;
- GPU event 完成前是否复用;
- cancel/finish 是否双重释放;
- cache key 是否绑定 model/tokenizer/template/RoPE/dtype/security domain;
- prefix 命中长度是否与精确 token 一致;
- P/D descriptor、checksum 与 install 是否原子。
用手撕 allocator 建立小规模状态机,再注入取消、共享、OOM 和 stale version。
RAG 错误¶
按阶段保存 top candidates:
- 原文是否正确解析和切分;
- ACL 与时间过滤是否正确;
- exact sparse/dense 是否命中;
- ANN 是否额外丢失;
- fusion/reranker 是否压低 gold;
- context 是否截断、去重或错序;
- claim 是否被证据支持;
- 引用是否定位到正确 span。
正确答案配错误引用说明模型可能依赖参数记忆;检索命中但答案错则继续检查上下文和生成,不要反复调 embedding。
Agent 卡住或误操作¶
- 候选动作是否通过 schema、业务和权限三层校验;
- 相同失败是否在无新证据下循环;
- timeout 后副作用是 failed 还是 unknown;
- idempotency key 是否稳定;
- 工具结果自由文本是否被当作控制命令;
- 成功谓词是否读取环境状态;
- cancel 是否传播并完成资源清理;
- memory 是否召回过期或跨域状态。
重放时替换真实副作用工具为 simulator,不能让调试再次发送、支付或删除。智能体运行时与智能体安全给出状态边界。
多模态¶
常见首个分叉点:
- resize/crop 后坐标未同步;
- tile、patch 或 tubelet 顺序错误;
- image/audio/video padding mask;
- 模态 token loss 未分别归一;
- VQ latent scale 或 codebook collapse;
- diffusion 参数化与 sampler 不匹配;
- streaming audio state 断裂;
- 视频采样漏掉短事件或泄漏未来帧。
使用多模态原语逐一对照 patch、bbox、mask、VQ、noise 和 flow 边界。
修复后的门禁¶
修复不以“这次没报错”为终点。补充:
- 能在旧代码上失败、新代码上通过的最小回归;
- 同类退化和相邻 shape;
- 生产路径与 reference 的等价性;
- 性能没有不可接受回退;
- 故障注入和恢复;
- 文档中的不变量、限制和版本更新。