用途:该测试用同一 token 的两条路径——一次作为 prefill 输入 logprob、一次作为 decode 输出 logprob——进行比对。两条路径运行不同 kernel、不同 shape,因此一致意味着状态正确,而非回答质量。它能捕捉 radix-cache 前缀未复现 fresh prefill、陈旧的 conv/mamba checkpoint、SWA 池驱逐了仍需内容等问题。gsm8k 通过不能证明这一点,精度对少量损坏 token 不敏感,KL 检查则敏感。
零结果需要两个独立条件:其一,路径上每个算子都具备批量不变性,token 结果不依赖同批次 token 数量。注意这是跨两条路径的性质,而非单个 kernel 自身的性质。其二,两条路径计算同一函数,decode 在某一位置的上下文和状态须等于 fresh prefill 在此计算的结果,即相同 KV 集合、滑动窗口、conv/mamba 状态、可复现重算前缀的恢复缓存。二者独立,一种测量可区分它们:条件一满足时,match 与 decode_cache_hit 读数为精确 0,而前缀恢复错误时 prefill_cache_hit 非零。
三个 helper 对缓存的接触不同:..._match_helper 两侧 flush、无缓存;..._match_prefill_cache_hit_helper 先 prefill 预热缓存,随后生成 prefill 从缓存恢复;..._match_decode_cache_hit_helper 的 decode 侧在预热缓存上运行。仅某一个 helper 出现偏差具有诊断意义:match 干净但 prefill_cache_hit 脏,说明恢复路径错误而非算术问题。
用法:按 CI 方式运行。KLDivergenceMixin 默认 max_samples=32、max_new_tokens=512,不要用更少样本做表征。avg_kl_div 是 k3 估计量 exp(logr) - 1 - logr,对尾部指数敏感,均值由少数 token 决定;4 个样本时同一配置测得 0.049 到 0.158,3 倍离散度使 A/B 比较无效。表征而非门禁时应报告尾部统计,如超过阈值的 token 比例与最大值,而非均值。若模型有滑动窗口,应生成超过窗口长度,使 decode 经历从提示 token 到生成 token 的交接。
条件一细节:确定性指相同输入、形状每次结果相同;批量不变性指 token 结果不依赖同批次其它 token 数量。KL 检查比较数千 token 的 prefill 与单 token 的 decode 步骤,衡量的是后者。--enable-deterministic-inference 会替换 aten kernel 为固定归约版本并固定 NCCL 算法和通道数,但仅覆盖其涵盖的 kernel,未到达 aten 算子的自定义 kernel 仍依赖 shape。按 token 的算子(GEMM、norm、激活、router 线性)只需固定归约顺序;跨 token 归约的算子(KV 范围上的注意力、集合通信)需固定 shape 而不只是顺序;跨调用携带状态的算子(conv 窗口、SSM checkpoint)属于条件二。MoE 会放大:top-k 路由是对接近分数做的离散决策,门权重 1e-8 差异即可翻转专家路由,42 层复合后产生 20-37 nat 差异与 0.177 的 KL;不能按稠密模型的阈值类比校准 MoE 阈值。
条件二细节:条件一成立后剩余即为状态 bug,出现的 helper 指明了路径。签名读取:偏差集中在批次中少数请求,其余逐位相同,说明由这些请求触发,应比较其 prompt 长度、cached_tokens、page 与 checkpoint-interval 余数;从首个生成 token 起连续偏差说明生成开始时状态已错,问题在前缀恢复;生成中途开始偏差指向 decode 期间发生的窗口交接、checkpoint 轮换;在不同配置(page size、TP degree、buffer strategy)下重跑逐位相同说明是确定性逻辑错误而非竞态。若模型有滑动窗口应生成超过窗口,交接处才会实际运行驱逐与 checkpoint 轮换。
阈值选择:路径上每个 kernel 具备批量不变性后,prefill 与 decode 逐位一致,诚实断言应为 stray-ulp 下限而非容差,例如 KL_DIV_THRESHOLD = 1e-9(实测 0)。宽松阈值会同时容忍浮点噪声与小的逻辑错误,使状态复用 bug 隐藏。建议单独在确定性服务器上运行 KL 用例并断言接近零,精度用例保留生产数值。阈值按(模型, tp)区分,tp=1 校准值不可迁移,因其无 all-reduce。
定位偏差:消融只能回答是否改变,无法回答在哪。forward-hook dumper 直接指向算子:运行一次,读出输入逐位相同而输出首次不同的层。使用 DUMPER_ENABLE=0、DUMPER_SERVER_PORT=reuse、DUMPER_NON_INTRUSIVE_MODE=all、DUMPER_DIR=/path/to/dumps,命令为 python3 -m sglang.launch_server ... --disable-cuda-graph --disable-prefill-cuda-graph,然后 curl -X POST localhost:PORT/dumper/configure -d '{"enable": true, "exp_name": "dec"}'。五项设置缺一不可且静默失败:DUMPER_ENABLE=0 加 DUMPER_SERVER_PORT=reuse,端口哨兵使 may_enable 为 true 从而注册钩子,而 enable=0 避免预热转储;启动时启用会转储每次预热 prefill,曾写出 1.8T 并占满共享磁盘,应加看门狗在剩余空间低于下限时终止;DUMPER_NON_INTRUSIVE_MODE=all,默认 core 只写 positions、seq_lens、req_pool_indices、input_ids、rids,不含模块张量且无报错;DUMPER_SERVER_PORT=reuse 是字面哨兵而非端口号,/dumper/{method} 路由仅对该确切值注册;在 --disable-cuda-graph 之上还要 --disable-prefill-cuda-graph,因为某些模型默认把 prefill 放到 CUDA graph,Python forward 钩子在重放中不运行。优先用 dumper.py 而非 --debug-tensor-dump-*,后者断言顶层模块名 model,多模态封装没有。读取 diff 前先证明对齐:decode 第 k 步与 prefill 第 plen + k 行消费同一 token,embedding 输出必须逐位相同,否则行错位、下游数字无意义。结果为:某模块输入逐位相同而输出不同的第一层即为该算子,其后全部继承。
需要 CUDA graph 的偏差:仅出现在捕获图中的偏差会击败两种常规探针且均静默失败。dumper 钩子在重放中不运行;为收集 dump 而禁用 graph 也会移除偏差,因此干净的逐层 diff 无意义,需确认在你 dump 所用确切参数下 bug 仍可复现。任何同步到主机的操作(.item()、float()、.tolist())在捕获期间会失败,应用 torch.cuda.is_current_stream_capturing() 保护探针,否则服务器无法启动。被捕获 kernel 的 Python 封装在重放时不被调用,对其插桩只会记录保持 eager 的阶段,应视作证据:kernel 以捕获时绑定的参数运行,每次重放新传入的张量对它不可见。可行做法是在图外探测被复用的状态:请求捐赠 checkpoint 时记录 slot id、声称 checkpoint 的长度、存储状态的 abs().max(),打开和关闭 graph 各跑一次并按 slot 对比。让探针证明其触发:未被走过的代码路径不打印任何内容,与“已测量无差异”无法区分,应断言最小命中次数或在入口无条件记录,并插桩所有调用者都会经过的单一咽喉点。
确认机制而非推断:两种最耗时的失败模式均可避免。一是开关无效果:切换前后逐位相同说明标志未生效,可能是分派被隐藏条件保护或该配置下路径从未走过,应先检查保护条件再断定组件无辜。二是测量了别的东西的测试夹:应通过 helper 自身函数捕获而非重建其输入,重建曾把一次生成追加两次并得出貌似合理却错误的结论,另一次不同的 num_samples 通过 get_input_ids 缓存键静默选择了不同提示集。logprob 数组按绝对位置索引:logprob_start_len=0 时,input_token_logprobs 每个输入 token 一项,首项为 None,第 k 项为 input_ids[k] 打分;helper 切片尾部落在生成区间,按绝对位置索引的分析须与此一致,否则一位偏差会读到相邻 token,其 logprob 常接近到看似真实信号。对孤立主张应缩减为独立复现:十行脚本在 M=1 与 M=288 调用可疑算子即可在数秒内判定批量不变性,应放入 PR 且先于任何端到端数字。读代码找可疑点最慢:一次调查先后推翻八个内在一致的代码推导假设,直到直接测量被复用状态才一轮定位缺陷。优先顺序为:隔离触发器的单变量 A/B、反推错误输出是哪一问题的正确答案、探测被复用状态本身,最后才读机制解释所测现象。