DeepInfer/H20-DEBUG-PLAN.md
loopyt 74ba468e76 fix(debug): move debug_dump out of h20_ops so other platforms can import it
The dump hooks live in five platform-neutral files -- `sampler.py`, `mlp.py`, `deepseek.py`,
`decoder_layer.py`, `model_runner.py` -- and all of them imported
`deepinfer.h20_ops.debug_dump`. That is wrong on every platform except SM90: `h20_ops/__init__.py`
calls `extend_import_patches(override=True, ...)` at module scope, so importing it from shared code
installs the SM90 import shims globally. On an SM100 machine that replaces
`flash_mla.decode_mla_mqa_sparse`, `flash_mla.prefill_attn_varlen_sparse` and
`deep_select.interface.topk` with the Hopper implementations, and on NPU it would do the same
before the Ascend shims get a chance to matter.

`debug_dump` itself has nothing platform-specific in it -- `os`, `pathlib`, `numpy`, `torch` --
so it moves to `deepinfer/debug_dump.py` and the five imports follow. Verified that importing it
no longer pulls `h20_ops` into `sys.modules`, and re-ran the end-to-end check on H20: same output
token for token, `[17607, 2793, 3139, 666, 6127, 666, 320, 1]`.

This bug is already in the open PR, so it needs to land before that PR is worth reviewing further.
2026-10-21 22:37:49 +08:00

22 KiB
Raw Blame History

H20 / SM90 数值正确性排查计划

目标:让 DeepInfer 在 8×H20 上对同一个小 case 给出和官方参考一致的输出。

固定用例(每次测试都用它,约 4~5 分钟/次,权重加载占大部分):

prompt : 问题:中国的首都是哪里?\n答案:      (token ids: [2056,768,17607,2793,3139,12108,6692,7624,768])
greedy : temperature = 0
length : max_new_tokens = 6
期望   : 北京 (6127) 开头
当前   : 'EVAL - 暂时不会 - '  = [33795,2570,565,223,22807,4555]   ❌
  • 我方入口:scripts/h20_ask.py --max_tokens 6
  • 参考入口:官方 /data/models/DeepSeek-V4.1-Flash-1017/inference/generate.py (TP8 权重 + config_fp8.json;其 generate() 是 argmax,天然 greedy、可复现)

P0 固化基准(已完成)

  • P0.1 官方 generate.py 跑通:Completion: 中国的首都是北京。
  • P0.2 发现官方套了 chat 模板(encode_messages),两侧输入其实不同, 直接比文本是无效对比。已拿到官方真实输入 ids: [0,128803,2056,768,17607,2793,3139,12108,1148,128804,128822](11 token,chat 模式)
  • P0.3 新增 scripts/ref_next_token.py:吃任意 ids、输出 greedy next token + top5, 绕开 prompt 形式差异。我方 scripts/h20_ask.py --ids ... 对称。
  • P0.4 顺手清理磁盘:/tmp 曾 100% 满导致 nvcc JIT 失败;删旧容器+未用镜像后 0 → 75GB 可用。

结论:官方 greedy 目标 = [17607, 2793, 3139, 6127, 320] = 「中国的首都是北京。」


已完成:P1 prefill 因果掩码 —— 已修,首 token 变对

现象(修复前 → 修复后,同为 11-token 官方输入):

tokens 文本
官方 17607, 2793, 3139, 6127, 320 中国的首都是北京。
我方(修 P1 前) 33795, 2570, ... EVAL - 暂时不会 -
我方(修 P1 后) 17607, 41175, 389, 666 中国的首都是**

首 token 17607(中国的)已与官方一致 ⇒ prefill 主路径通了。 前 3 个 token 只是分词不同(首/都是 vs 首都/是),拼起来都是「中国的首都是」。

关键新证据(已确认,不靠推测):把 14 个 token 直接当 prefill 输入 (官方前缀 + 17607,2793,3139),我方预测 666(**),而官方在同样前缀下 生成的是 6127(北京)。说明分歧发生在 prefill 内,不是 decode 累积的, 且发生在第 12~14 个位置附近(11 token 时还对)。

👉 下一步收敛为:逐层 dump,找首个偏离层,见 P2。


P2 分层对拍 —— 已完成,定位到第 0 层的 FFN 阶段

方法:scripts/ref_layer_dump.py(官方,forward hook)+ DEEPINFER_DUMP_LAYERS(我方, h20_ops/debug_dump.py),两侧都取最后位置、都按 hc_mult 求均值,用 scripts/compare_layers.py 对比。输入固定为那条 14-token ids。

踩过的两个坑(重要,否则会得出错误结论):

  1. 我方 decoder_layer 返回的 hidden_states 只是子层输出(2D),完整残差流在 residual (HCLazyPostData,hc_mult=4)里。必须 hc_post(hidden_states, residual) 之后再比, 否则第一版对比得到的 cos≈0.02 是假的。
  2. 引擎在分配 KV cache 前会跑一次 dummy profile forward,token 数与真实 prompt 不同。 必须用 DEEPINFER_DUMP_TOKENS=<n> 门控,否则 dump 到的是 dummy 输入。

结果(14-token 输入,cos / rel_err / 范数比):

位置 cos rel_err ‖mine‖/‖ref‖
layer0 attention 子层输出 0.99897 4.71e-02 0.986
layer0 整层输出 0.99190 1.27e-01 1.000
layer1 整层 0.97707 2.29e-01 1.062
layer1 attention 子层 0.99324 1.21e-01 0.959
layer2 0.96411 2.76e-01 1.039
layer5 0.93275 3.61e-01 0.943
layer11 0.86515 5.28e-01 1.030

结论:

  1. 稀疏 MLA attention 本身是对的:第 0 层 attention 子层 cos=0.999,误差 4.7%, 在 FP8/量化口径下属于合理量级。之前怀疑的 TileLang kernel 可以排除。
  2. 误差主要在第 0 层的 FFN/MoE 阶段引入:attention 4.7% → 整层 12.7%, 即 FFN 贡献了主要偏差。
  3. 误差单调累积,到第 11 层 cos 已降到 0.865,最终把 top-1 从「北京」翻成「**」。

下一步:在 layer0 的 FFN 内再切一刀 —— 分别 dump routed_experts_forward 的输出、 shared expert 输出、以及 swiglu/route 权重,看是路由(top-6 选中了不同专家)还是 专家计算本身偏。engram 在第 1/14 层,本轮已可排除其为首个偏离点。

P3 已完成:根因是专家权重量化的 scale 广播错误 —— 已修复,答案正确

根因

h20_ops/quant.py::requant_expert_fp4 里用了 _expand_blocks(sf, n_out, k, 32) 去展开 UE8M0 scale。但两者的布局前提不同:

scale 形状 需要展开的维度
dense 权重(requant_dense_fp8) (n_out/32, k/32) 两个维度
专家权重(requant_expert_fp4) (n_out, k/32) 只有 k 维度

_expand_blocks 对两个维度都做 repeat_interleave(32) 再截断,于是 (2304, 160) 被当成 (72, 5) 来展开 —— 全部 2304 个输出通道都拿到了前 72 行的 scale, 整个专家权重被打乱。这精确解释了 P2 观察到的「shared 对、routed 错」的非对称: shared expert 走 requant_dense_fp8(用法正确),routed experts 走这条错误路径。

修复:只沿 contraction 轴展开,并加断言锁住布局。

assert tuple(sf.shape) == (n_out, k // FP4_BLOCK)
x = _unpack_fp4(packed) * sf.to(torch.float32).repeat_interleave(FP4_BLOCK, dim=1)

验证(scripts/h20_expert_weight_check.py,与官方 inference/convert.py

的 cast_e2m1fn_to_e4m3fn 逐元素对拍)

权重 修复前 cos / rel_err 修复后 cos / rel_err
w1 0.8936 / 5.35e-1 1.0000 / 2.84e-2
w3 0.8960 / 5.26e-1 0.9997 / 2.69e-2
w2 0.8964 / 5.20e-1 0.9996 / 2.65e-2

剩余 2.7% 是 per-32 → per-128 的量化损失(FP8 e4m3 三位尾数,理论量级 ~3%), 不是 bug。官方 cast_e2m1fn_to_e4m3fn 是无损的(把 per-32 的相对 offset 折进 FP8 数值), 如果要进一步压掉这 2.7% 可以照搬,见下方 P3.6。

端到端结果

修复前:  [17607, 41175, 389, 666]                    -> 中国的首都是**
修复后:  [17607, 2793, 3139, 666, 6127, 666, 320, 1] -> 中国的首都是**北京**。
官方:    [17607, 2793, 3139, 6127, 320]              -> 中国的首都是北京。

语义已正确(「北京」),前 3 个 token 与官方逐 token 一致。差异只剩 markdown 加粗 **(我方多出 666/666 两个 token),说明仍有残余数值差异让 top-1 在个别位置偏移。

下一步(可选,按收益排序)

  • P3.6 改用官方无损重化:照 cast_e2m1fn_to_e4m3fn 的做法,把 per-32 e8m0 相对 offset 折进 FP8、scale 保持 e8m0 per-32,理论上可把 2.7% 降到 0。需要确认 SM90 DeepGEMM 是否接受 e8m0 per-32 的 expert scale(当前配方是 fp32 per-128)。
  • P3.7 重跑 P2 分层对拍:确认 layer0 整层 rel_err 从 1.27e-1 降到 ~3e-2 量级。
  • P3.8 ** 的来源:定位是哪个位置翻的 top-1,判断是否还有别的量化口径差异。

后续跟踪(P3.6 起)已迁移到 H20-PORTING-JOURNAL.md 第 6 节,那里有完整的 漂移量化数据、建议顺序和前置条件。本文件的 P4 / P5 保留为方法清单。

P4 decode 专项 —— 完成:** 已定量解释为量化噪声,非缺陷

为什么锁定 decode

P3 之后重跑对拍(H20-DRIFT-ANALYSIS.md 有完整数据):

top-1 margin(6127 北京 − 666 **)
官方,14 token prefill 6127 +3.112
我方,14 token prefill 6127 ✅ +0.256

prefill 选中了正确的 token,误差只把 margin 压缩了 12 倍。但端到端跑 (11 token 起,增量 decode 到第 14 个位置)给的是 666。

同一个 14-token 上下文,一次 prefill 给 6127,增量 decode 给 666。

当时的推断是「剩余漂移在 decode 路径」。P4.2~P4.4 做完后,这个推断需要精确化为: 差额确实只在 decode 出现,但原因不是 decode 有 bug,而是 decode 经过量化 KV cache 往返、噪声略增,恰好碰上模型自己在那个位置只有 1.64 的 margin。

判据(先定死,避免事后解释;P4.2 之后已修正)

事后修正:下面这套判据假设「decode 与 prefill 应当基本一致」,因此把 rel(A,B) > 3e-2 判为「decode 自身缺陷」。P4.2 证明这个前提错了 —— 官方自己 prefill→decode 也让 margin 变化约 2 倍,因为 KV cache 走量化往返。 真正有效的判据是Δmargin 的量级 vs 该位置的 margin,而不是 rel_err 的绝对值。 判据保留在下面作为过程记录,最终结论见「最终结论」一节。

设第 14 个位置上:

  • A = 我方 prefill 路径的 logits
  • B = 我方 decode 路径的 logits
  • R = 官方 decode 路径的 logits
量 含义 阈值
rel(A, B) 我方两条路径自身是否一致 > 3e-2 即为 decode 自身缺陷
rel(A, R) prefill 残余误差 已知约 3e-2(量化地板)
rel(B, R) decode 总误差 明显大于 rel(A,R) 说明 decode 额外引入误差
  • 若 rel(A,B) 很小而 rel(B,R) 大 → decode 没有额外缺陷,是量化误差累积,** 属可接受
  • 若 rel(A,B) 大 → decode 有 bug,按 P4.3 → P4.4 往下查

步骤

  • P4.1 同位置 prefill vs decode 对拍 —— 完成(结论已被 P4.2 修正,见下)

    四条路径走到同一个 14-token 上下文
    `[0,128803,2056,768,17607,2793,3139,12108,1148,128804,128822,17607,2793,3139]`:
    
    | 路径 | 下一个 token | 结果 |
    |---|---|---|
    | 官方 prefill(一次喂 14 个) | 6127 = 北京 | ✅ |
    | 官方 decode(喂 11 + 逐 token forward) | 6127 = 北京 | ✅ |
    | **我方 prefill(一次喂 14 个)** | **6127 = 北京** | ✅ |
    | **我方 decode(喂 11 + 增量)** | **666 = `**`** | ❌ |
    
    前两条来自 `ref_next_token.py`(prefill 一次 / `--force_ids` 增量),后两条来自
    `h20_ask.py`:`--ids <14个> --max_tokens 1` 给 `6127`,`--ids <11个> --max_tokens 7`
    的第 4 个 token 是 `666`。
    
    **结论:prefill 路径正确,差额只由 decode 引入。**(注意:P4.3/P4.4 之后已知
    decode 的 KV cache 与 indexer 都精确,所以「差额由 decode 引入」不等于「decode 有
    离散 bug」——见下方「量化后的真实图景」。)
    这把 P3 之后「margin 被压缩到 0.256,decode 再推一把就翻」的推断变成了实证。
    
    补充:官方自己 prefill 与 decode 的 margin 也不同(3.112 vs 1.638),所以
    「两条路径不完全相等」是框架固有特性(KV cache 量化往返),但**方向不该翻**。
    - 路径 1:直接 14 token prefill(`--ids <14个>`,dump `logits_prompt`)—— 已有
    - 路径 2:11 token prefill + 3 步贪心 decode(贪心本来就给对了
      17607/2793/3139,不需要强制喂 token),dump 第 3 步 decode 的 logits
    - 参考侧同样跑增量:`ref_next_token.py` 逐步喂 11→12→13→14 个 token,
      每一步的记录就是官方 decode 路径的 logits
    产出:三条曲线的 margin 与 top-1 逐步对照。
    
  • P4.2 decode 逐步误差曲线 —— 完成,结论收敛:没有 decode 专属 bug

    关键前提:**必须 `DEEPINFER_USE_NEXTN_DECODE=0`**。它默认是 `5`,MTP 投机解码默认开启,
    每步产生多行 draft/verify,`logits[0]` 不再对应你以为的那个位置(我第一次跑就是因此
    错位,把 pos15 的向量当成 pos14 的,白折腾了一轮)。关掉之后一步一行,完全对齐。
    
    11 token 起,逐步对照(`nomtp` run,8 个 decode step):
    
    | pos | 我方 top1 | 官方 top1 | 我方 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。** 7/8 位置 top-1 正确,
    唯一的翻转在 pos14 —— 而那里的**官方 margin 只有 1.638**,其余位置是 9.9~17.9。
    
    换句话说:**噪声幅度(std 1.63)和那个位置的 margin(1.64)一样大,是硬币。**
    这不是 decode 的 bug,是量化噪声碰上模型自己也没把握的位置。
    
    官方在 pos14 选了 `6127`(北京),我方选了 `666`(`**`),于是渲染成
    `**北京**` —— 语义完全等价,只是 markdown 加粗的位置不同。
    
    顺带排除:MTP 不是原因。`DEEPINFER_USE_NEXTN_DECODE=0` 前后输出完全相同
    (`[17607, 2793, 3139, 666, 6127, 666, 320, 1]`)。而官方 `generate.py` 本来
    就没有 MTP,所以把 MTP 关掉才是与参考对等的比法。
    
    从第 1 步到第 8 步,每步记 `margin`、`top-1 是否与官方一致`、`rel(B_t, R_t)`。
    用来判断误差是「一开始就有」还是「某一步突然放大」——后者意味着踩到了某个
    batch/位置相关的边界(例如 page 边界、SWA window 回绕)。
    
  • P4.3 KV cache 写入/读回一致性 —— 完成,干净,排除

    两层验证,都通过。
    
    其一,仓库自带的单测(`store_kv_cache.py` / `load_paged_kv_cache.py`,与各自的 ref
    逐元素比),过滤 SM90 实际使用的 fp32-sf index 布局:**各 6 passed**。
    全布局跑一遍是 48 passed / 12 failed,失败**全部**落在 `ue8m0` 的 index 布局
    (`FP4_INDEX_KV_FORMAT`、`MXFP8_INDEX_KV_FORMAT`),而 SM90 不用这两个。失败点在
    ref 自身:它用 `PLATFORM.fp8_sf_type`(SM90 是 fp32)去重解释 ue8m0 字节,
    `index_copy_` 撞 dtype。属 SM90 上 ue8m0 分支的遗留缺口,不影响本任务。
    
    其二,闭环 round-trip(`scripts/h20_kv_roundtrip.py`):写入已知 bf16 → 读回 → 与
    原值比,不依赖任何 ref 实现。覆盖 SM90 实际用的两种布局、block 32/64、1/233/1029 token:
    
    | 布局 | bytes/token | rel_err | cos |
    |---|---|---|---|
    | `V41_FORMAT`(main/swa KV) | 528 | 0.0266–0.0295 | 0.9996 |
    | `FP8_FLOAT32_SF_INDEX_KV_FORMAT`(index KV) | **132** | 0.0266–0.0327 | 0.9996 |
    
    正是 FP8 的理论地板,与权重那 2.7% 同量级。**KV cache 不是 decode 误差的来源。**
    P3.9 遗留的 132B 字节布局问题就此关闭。
    
    坑:这个脚本第一版把「原始 fp8 值」直接和 bf16 比,得到 rel=126 —— 因为 index 布局
    的 load **不解量化**(文档写明「返回量化值 + scale」,消费方是 kernel)。
    按 `量化值 × scale` 解量化后才是 0.027。差 127 倍的假警报来自测试脚本,不是实现。
    
  • P4.4 _decode_p_sum_sm90 vs 参考实现 —— 完成,数值精确,排除

    `scripts/h20_index_decode_check.py` 用一份**刻意不共享代码**的 float64 参考实现
    (按显式偏移逐 token 切片,而非 strided view)对照:
    
    ```
    page=32  lens=[1,64]      rel=1.4e-07  cos=1.00000
    page=32  lens=[31,32]     rel=1.5e-07  cos=1.00000   (页边界)
    page=32  lens=[96,65]     rel=1.2e-07  cos=1.00000   (跨页)
    page=32  lens=[7,33,159]  rel=9.4e-08  cos=1.00000   (3 行 32 头)
    page=64  ...              rel≈1.3e-07  cos=1.00000
    ```
    
    rel 就是 fp32 舍入量级。Q 的解量化用 `q_sf.reshape(rows, heads, -1)[..., :1]` 也没问题:
    `head_dim == 128 == gran`,每个 (row, head) 恰好一个 scale(源码里有
    `assert self.n_head <= 32 and self.head_dim == 128` 兜住)。
    **decode 的 indexer 路径不是误差来源。**
    
  • P4.5 采样器 / logits processor 排除 —— 无需再查

    pos14 我方 top1=666、top2=6127,差值 1.480。如果 6127 被任何 mask / blacklist 抑制,
    它就不会是第二名。所以 pos14 的差异来自 logits 本身,不是采样后处理。
    (`token_logprobs` 恒为 0 的 P5 问题仍然独立存在,与本题无关。)
    

最终结论:** 是量化噪声在「模型自己也没把握」的位置上的一次抛硬币

三条线索合起来:

  1. 噪声幅度是恒定的约 1.6 logit(Δmargin std = 1.63,跨 7 个 decode step), 与 prefill 的残余误差同量级(pos11 的 Δmargin +0.835,14-token prefill 的 top-1 误差 −3.30)。它来自 per-128 量化的累积 —— SM90 的 DeepGEMM 强制的那条路。
  2. 参考模型的 margin 分布很宽:9.86 / 10.33 / 11.95 / 1.64 / 15.11 / 17.87 / 10.40 / 11.09。 只有 1.64 那个位置会被 1.6 的噪声翻掉。
  3. 只有 pos14 翻转,其余全部正确。 而 pos14 正是 1.64 那个。

所以:

  • 不是 KV cache 的问题(P4.3 闭环 round-trip 通过,rel≈0.027)
  • 不是 decode indexer 的问题(P4.4 与独立 float64 参考 rel≈1e-7)
  • 不是 MTP 的问题(开关 MTP 输出相同)
  • 不是采样后处理的问题(6127 在 pos14 是第二名,没被 mask)

是「在 SM90 上必须用 per-128 量化」这件事本身。它在多数位置无感,只在模型自己 接近 50/50 的地方改变结果。而模型在这里确实接近 50/50 —— **北京** 的两个位置 (先 ** 还是先 北京)在语义上没有区别,参考模型也只是略偏一边。

要继续压这个噪声,唯一的杠杆是回到 P3.6 那条路,而它在 SM90 上被 DeepGEMM 的 gran_k == 128 断言封死了(见 H20-DRIFT-ANALYSIS.md)。所以本项调查到此收敛: 剩余差异是硬件量化口径的必然结果,不是实现缺陷。

P4.1 的工具坑(都已修,但 dump 仍建议配合黑盒交叉验证)

坑 现象 修法
多 DP 进程写同一文件 8 个 engine 都是单进程组,RANK 全为 0,互相覆盖 文件名带 _rank<R>_pid<P>
decode 的 batch 有 padding 行 取 logits[-1] 读到占位 token 的垃圾(恒定 top1=69) 取第 0 行(第一个真实请求)
follower rank 的 logits 是占位值 同一向量被重复记录几十次,淹没真实步 判据 driver_inputs is not None
profile forward 用合成 token 抓到 profile 的数据 debug_dump.profiling() 挂起

即便都修了,逐 step dump 与端到端 token 序列仍无法可靠对齐(max_tokens=7 的 run 里 出现 75 次 sampler 调用)。所以 P4.1 的结论以黑盒四格对照为准,dump 只作辅助。

另一个必须记住的坑:max_tokens > 1 的运行里,logits_prompt 也会被污染 —— mysteps6(max_tokens=7)的 logits_prompt 算出 cos=0.68、top-1 误差 16.5, 而 mylogits3(max_tokens=1)同一输入是 cos=0.9846、误差 3.30。差 5 倍。 比较 prefill logits 只能用 max_tokens=1 的运行。

已备好的工具

  • debug_dump.record(..., once=True) + logits_prompt / logits_decode 两个键
  • debug_dump.profiling():profile forward 期间整体挂起,避免抓到合成 token
  • ref_next_token.py:吃 token ids,给官方 greedy next token + top5;--force_ids 走增量 decode,--logits_out 存每一步的完整 logits
  • scripts/h20_kv_roundtrip.py:KV cache 闭环 round-trip(不依赖 ref)
  • scripts/h20_index_decode_check.py:_decode_p_sum_sm90 的独立数值对照

P5 logprob 异常(已排查,未修,非阻塞)

当前 8 个 token 的 token_logprobs 全是 0.0(概率=1),不正常。

已排除(约 20 分钟静态排查 + 单测)

嫌疑 结论
map_lookup kernel 无罪。单测 6 passed,by_key + float32 + force_topk=4096 全覆盖,与 map_lookup_ref 逐元素相等
sampler 全部 kernel 无罪。sampler.py -m "not benchmark" → 74 passed / 0 failed
SampledInfo.from_reqcnt 的 torch.zeros 死代码,全仓库无调用点;update(is_final, candidate) 也无调用点
sampler→engine 字段传递 直通,llm_engine.py:747 原样传 output.token_logprobs

一个反向线索:map_lookup 里 out = torch.empty,键未命中时本应写 -inf 而不是 0。 所以 0.0 更像「上层根本没把值带下来」,而不是「查表查空了」。

剩余候选与判定路径

  • process_sampling 返回的 logprobs 本身为 0(fused_sample 的 logprob 输出路径; 现有单测只覆盖 kernel 本身,没有覆盖「整个 forward 的 logprob 输出」)
  • build_request_outputs 里逐 token 对象的属性回填

判定只需跑 1 次整机(~5 min):在 sampler 里打印 logprobs.max() 与 token_logprobs_cur。 sampler 层就是 0 → 定位在 fused_sample;sampler 层正常 → 定位在 output 回填。

为什么留着不修

不影响推理正确性,只影响 logprob 字段的可用性(AIME 打分、perplexity 这类评估用途)。 预估典型 1 h、最坏 2 h(若牵涉 MTP 异步 logprob 回填),与本次 SM90 支持的目标无关。


备注:当前生效的绕过项(都会影响数值,需在收尾时复核)

绕过 原因 对数值的影响
DEEPINFER_ENFORCE_EAGER=1 CUDA graph capture 期断言失败 无
DeepSelect topk → torch.topk SM90 launch 失败 无(契约等价)
decode indexer → _decode_p_sum_sm90 SM90 paged kernel 要求 64-token page 需 P3.2 验证
关闭共享层候选块优化 fp8_fp4_sparse_mqa_logits SM100-only 无(稠密等价)
mega_mhc / mega_moe 关闭 SM100-only 无