From 7fed531ec2fda2b59809b000754b10af1b09d2de Mon Sep 17 00:00:00 2001 From: loopyt Date: Wed, 21 Oct 2026 22:40:18 +0800 Subject: [PATCH] docs: close the drift investigation in the analysis note MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The note still ended at "next: P4" with an open checklist. Records the outcome instead: the three decode candidates (KV cache, decode indexer, MTP) are all ruled out with measurements, and the remaining flip is quantified -- noise std 1.63 against a reference margin of 1.638 at the one position that differs, out of eight. Also renumbers the sections, which had a stray unnumbered conclusion between 四 and 五. --- H20-DRIFT-ANALYSIS.md | 59 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 10 deletions(-) diff --git a/H20-DRIFT-ANALYSIS.md b/H20-DRIFT-ANALYSIS.md index e81baec..5632e01 100644 --- a/H20-DRIFT-ANALYSIS.md +++ b/H20-DRIFT-ANALYSIS.md @@ -116,7 +116,7 @@ margin 彻底翻转。 --- -## 四、下一步:P4 decode 专项 +## 四、P4 decode 专项:三个候选全部排除 定位已经收窄到 decode,建议按这个顺序: @@ -128,20 +128,55 @@ margin 彻底翻转。 - [ ] **P4.2 decode 逐步误差曲线** 从第 1 步到第 8 步,每步记录 top-1 和 margin,找出误差开始放大的步数。 -- [ ] **P4.3 KV cache 写入/读回一致性** - 写入已知向量后读回,与 bf16 原值比。这是 P3.9 遗留项,也是最可能的嫌疑人: - decode 用 `_decode_p_sum_sm90` 走 torch 兜底算 indexer 分数,而 prefill 走 - TileLang kernel —— 两条路径从未对拍过。 +- [x] **P4.3 KV cache 写入/读回一致性** —— **干净,排除** + 闭环 round-trip(`scripts/h20_kv_roundtrip.py`,不依赖任何 ref 实现): + `V41_FORMAT` 528 B/token 与 `FP8_FLOAT32_SF_INDEX_KV_FORMAT` 132 B/token, + block 32/64,1/233/1029 token,rel 0.0266~0.0327、cos 0.9996 —— FP8 地板。 + P3.9 遗留的 132 B 布局问题就此关闭。 -- [ ] **P4.4 `_decode_p_sum_sm90` vs prefill 的 MQA logits** - 同一个 KV cache、同一个位置,两条 indexer 路径的分数差。 +- [x] **P4.4 `_decode_p_sum_sm90` vs 独立参考** —— **数值精确,排除** + `scripts/h20_index_decode_check.py` 用一份刻意不共享代码的 float64 参考 + (显式偏移切片 vs strided view):page 32/64、页边界、跨页、32 头, + 全部 rel≈1e-7、cos=1.00000。 -判据:如果 P4.1 的差距 < 1e-3,说明 decode 无误,`**` 另有来源(回到采样器/logits -processor 查);如果差距明显,就从 P4.3/P4.4 里找。 +- [x] **P4.2 decode 逐步误差曲线** —— **完成,结论收敛** + 见下节。 --- -## 五、两个工具修正(否则上面的数据拿不到) +## 五、结论:`**` 是量化噪声在「模型自己也没把握」的位置上的一次抛硬币 + +前提:必须 `DEEPINFER_USE_NEXTN_DECODE=0`。它默认是 `5`,MTP 投机解码默认开启,每步多行 +draft/verify,`logits[0]` 不再对应你以为的位置。官方 `generate.py` 本来没有 MTP,关掉才是 +对等比法。关掉后一步一行,逐步对照 11 token 输入、8 个 decode step: + +| pos | 我方 | 官方 | 我方 margin | 官方 margin | Δmargin | +|---|---|---|---|---|---| +| 11(prefill) | 17607 | 17607 | 10.695 | 9.861 | +0.835 | +| 12 | 2793 | 2793 | 8.448 | 10.328 | −1.880 | +| 13 | 3139 | 3139 | 15.276 | 11.946 | +3.330 | +| **14** | **666** | **6127** | **1.480** | **1.638** | −0.157 | +| 15 | 6127 | 6127 | 15.950 | 15.109 | +0.842 | +| 16 | 666 | 666 | 20.118 | 17.874 | +2.244 | +| 17 | 320 | 320 | 9.829 | 10.402 | −0.573 | +| 18 | 1 | 1 | 12.262 | 11.091 | +1.171 | + +**Δmargin:mean +0.711,std 1.628,max 3.330。8 个位置里 7 个 top-1 正确。** +唯一的翻转在 pos14,而那里的**官方 margin 只有 1.638**,其余位置是 9.9~17.9。 + +**噪声 std 1.63 ≈ 该位置 margin 1.64,是硬币。** 官方先写 `北京` 再写 `**`,我方先写 +`**` 再写 `北京`,两边渲染成同样的加粗文本,语义一致。 + +四个嫌疑全部排除:KV cache(P4.3)、decode indexer(P4.4)、MTP(开关前后输出逐字节相同)、 +采样后处理(pos14 我方 6127 是第二名,被 mask 的话不可能是第二)。 + +**所以剩余差异是「在 SM90 上必须用 per-128 量化」这件事本身**,它只在模型自己接近 50/50 +的位置改变结果。要压它只能回到第一节那条无损路,而那被 DeepGEMM 的 `gran_k == 128` +断言封死。本项调查到此收敛:不是实现缺陷。 + +--- + +## 六、三个工具修正(否则上面的数据拿不到) 1. **`ref_layer_dump.py` 的 logits 索引**:`logits[0, -1]` → `logits[0]`。之前所有 dump 里的 `final_row_logits` 都是 0 维标量,等于没有。 @@ -150,3 +185,7 @@ processor 查);如果差距明显,就从 P4.3/P4.4 里找。 而不是 prompt 长度,门控不适用,于是抓到的是 profile 的垃圾(第一版得到 cos = 0.34)。 现在用 `debug_dump.profiling()` 上下文在 profile 期间整体挂起,并且把 logits 拆成 `logits_prompt` / `logits_decode` 两个键 —— 只有前者能和 prefill-only 的参考 dump 比较。 + +3. **`max_tokens > 1` 时 `logits_prompt` 也会被污染**:`mysteps6`(`max_tokens=7`)算出 + cos=0.68、top-1 误差 16.5,而 `mylogits3`(`max_tokens=1`)同一输入是 0.9846 / 3.30, + 差 5 倍。**比较 prefill logits 只能用 `max_tokens=1` 的运行。**