I·Infra Notes AI Infra 工程实践指南 ← 返回主博客
排障、适配与前沿报告 / READING NOTES

19.1 从最终输出回溯到第一处分歧

逐层逐 Token 对比与第一处分歧定位

先确认 tokenizer、权重、位置、mask、采样和模型模式一致。固定输入做 deterministic forward,比较 logits;采样 token 不同可能只是两个几乎相等的 logits 排序翻转。不能只用 torch.allclose 的布尔值:同时记录最大/平均绝对误差、相对误差、NaN/Inf 个数、argmax 差异与误差分布,并按 dtype/量化粒度设置容差。

import torch

def compare(name, ref, test, atol=1e-3, rtol=1e-3):
    assert ref.shape == test.shape, (name, ref.shape, test.shape)
    a, b = ref.detach().float(), test.detach().float()
    diff = (a - b).abs()
    denom = a.abs().clamp_min(1e-8)
    print(name, 'allclose=', torch.allclose(a, b, atol=atol, rtol=rtol),
          'max_abs=', diff.max().item(), 'max_rel=', (diff / denom).max().item(),
          'nan=', torch.isnan(b).sum().item(), 'inf=', torch.isinf(b).sum().item())

for layer_id, (ref, test) in enumerate(zip(ref_layers, candidate_layers)):
    compare(f'layer_{layer_id}', ref, test)
最终 logits 不一致 → Token-by-Token 比较,找第一枚不同 token
  → 该 token 的 Layer-by-Layer 比较,发现 Layer 17 开始发散
  → 比较 attention input/output 与 MLP input/output
  → Attention 异常 → Q/K/V、position_id、RoPE、mask、KV slot
  → 修 position_id → 重跑短序列/长序列/Prefix/抢占回归

典型分歧:全 NaN 先看除零、exp 溢出、全 mask 行、未初始化读取;FP16/BF16 差异先看累加 dtype 与运算顺序;量化后退化先看 scale 轴、zero-point、打包顺序、校准样本,再看任务指标;KV 错误先比较每步新写入 K/V 与无缓存 reference。Layer 17 是诊断示例,不意味着固定故障层。做随机测试时覆盖 1、block_size-1、block_size、block_size+1 的长度、非连续输入、GQA head 比例及不同 batch 顺序。

追问:为什么只比较最终生成文本不足以验收 Kernel?采样放大微小 logit 差异,同时也可能在错误尚未影响 argmax 时掩盖问题;应先比较中间张量和 logits,再测任务表现。