DeepInfer/TODO.md
loopyt dca7f959cf docs(h20): record the torch-reference bring-up path and what it measured
Section 6 writes down the three eager imports that blocked the plan, the annotation trap that
looks like it should solve it and does not, and the comparison table showing the quantisation
chain is bitwise equal -- plus, plainly, what is still missing so the next step is not mistaken
for a finished port.
2026-10-22 16:08:54 +08:00

38 KiB
Raw Blame History

H20 / SM90 遗留问题总清单

截止 2026-10-22 10:00。分两条线:

  • TileLang PR 版 = 分支 h20-sm90-support → 上游 PR #180(OPEN / MERGEABLE / CLEAN,0 review 0 comment 0 CI)
  • Triton 版 = 分支 h20-sm90-triton(基于 PR 分支,HEAD 615664e),把推理路径上的 TileLang kernel 换成 Triton

两条线共用同一个上游分支底座,所以「底座问题」两边都有,先单列。


零、两条线共有的底座问题(不是任何一条线引入的)

C1. CUDA graph capture 直接挂 —— 默认配置跑不起来 🔴 P0

现象:graph 开启(默认)时 warm_up_model → capture_model 8 rank 全挂:

deepinfer/h20_ops/engram_local.py:108  torch.bincount(owner)
RuntimeError: Cannot copy between CPU and CUDA tensors during CUDA graph capture
unless the CPU tensor is pinned.

两处根因(都在我们新写的 LocalEngramGather,为了替掉 SM90 上没有的 DeepEP RDMA gather):

  1. torch.bincount(owner) 里 owner 在 GPU,输出默认是 CPU tensor → capture 期 D2H。
  2. _exchange_sizes 用 dist.all_gather_into_tensor + .tolist()(必然 host sync), 下游 all_to_all_single(output_split_sizes=...) 用动态切分 → 结构上不可 capture。

当前绕过:DEEPINFER_ENFORCE_EAGER=1(关掉整个 decode graph)。

两条修法:

  • C1a 快速版:SM90 且 engram 表存在时,自动关 graph 并打一行日志说明原因。 1 行判断 + 1 行日志,立刻让默认配置可用。代价:decode 没有 graph(慢多少没测,见 C5)。
  • C1b 正解:把 engram gather 改成 capture-safe —— 每行开头的 counts 走 固定 shape 的 padded 交换([world, world] 计数矩阵常驻显存,kernel 内算前缀和), 避免 host sync 与动态 split。代价:prefill 流量 ×world,需要重新量带宽。

C2. 必须手动设 EP_DISABLE_GIN=1 🟡 P1

容器的 /dev/infiniband 不存在(host 上有 4 个 uverbs*,容器没挂), DeepEP 查到 props.ginType == NCCL_GIN_TYPE_NONE 就 assert:

vendor/DeepEP/csrc/kernels/comm/context.cpp:125

SM90 单机走的是 scale-up 路径,本来不需要 GIN,所以关掉无副作用(已在 userbuffer.py:80 用 allow_hybrid_mode=not PLATFORM.is_sm90 关了一半)。

  • C2 在 SM90 单节点下自动 EP_DISABLE_GIN=1(或检测 /dev/infiniband 缺失即关), 而不是要求使用者记得加 env。否则「按 README 跑」必挂。

C3. 2.7% logits 漂移 —— 硬件量化地板,不修 ⚪ 已收敛

DeepGEMM 2.8.2 csrc/apis/layout.hpp:100:

DG_HOST_ASSERT((arch_major == 9 and gran_k == 128 and k_alignment == 128) or ...)

SM90 强制 per-128 + fp32 scale,checkpoint 是 per-32 + e8m0。 gran_k == 128 断言封死了「无损重化」这条路(H20-DRIFT-ANALYSIS.md §1 逐条排除)。

  • 结论:不是 bug。答案稳健性已验 —— pos14 官方 margin 只有 1.638,噪声 std 1.63,硬币。
  • C3-doc PR 描述里必须写清「自然提问答案正确(首 token 6127 北京)」, 别让人误读成「移植答不对」。H20-DRIFT-ANALYSIS.md §5 末尾已写,PR 正文还没搬。

C4. token_logprobs 恒为 0 🟡 P2(历史遗留,与 Triton 无关)

所有历史日志(含纯 TileLang 时期)都是 [0.0] * n。

已排除:map_lookup kernel(6 passed)、sampler 全部 kernel(74 passed)、 SampledInfo.from_reqcnt 的 zeros(死代码)、sampler→engine 字段传递(直通)。

剩余候选:process_sampling 返回的 logprobs 本身为 0(fused_sample 的 logprob 输出路径), 或 build_request_outputs 的属性回填。

  • C4 跑 1 次整机(~5 min):在 sampler 里打印 logprobs.max() 与 token_logprobs_cur。 sampler 层就是 0 → 定位在 fused_sample;正常 → 定位在 output 回填。预估 1 h,最坏 2 h。 影响面:AIME 打分、perplexity 这类评估用途。

C5. 性能没有任何系统数据 🔴 未开工

H20-PORTING-JOURNAL.md §6.4:只测过两个点 —— 稀疏 MLA prefill 104 TFLOP/s、decode 0.23 ms/step(batch=8, topk=1024)。

  • C5 端到端吞吐(prefill / decode / 不同 seq len)、首 token 延迟、 以及 vs vLLM / SGLang 的对比。两条线都要,Triton 版还要 Triton vs TileLang 逐 kernel 对比。

C6. index KV 的 ue8m0 布局单测 12 failed ⚪ 已知,不影响 SM90

store_kv_cache / load_paged_kv_cache 全布局跑 48 passed / 12 failed, 失败全部落在 FP8_INDEX_KV_FORMAT、MXFP8_INDEX_KV_FORMAT 两种 ue8m0 index 布局, 而 SM90 不用这两个(用 FP8_FLOAT32_SF_INDEX_KV_FORMAT)。

失败点在 ref 自身:它用 PLATFORM.fp8_sf_type(SM90 是 fp32)去重解释 ue8m0 字节, index_copy_ 撞 dtype。

  • C6 可选:修 ref 的 dtype 推断,让 SM90 上跑全布局也全绿。属「别留红灯」的整洁性工作。

一、TileLang PR 版(h20-sm90-support / PR #180)

规模

48 files changed, 4562 insertions(+), 78 deletions(-)

新增:deepinfer/h20_ops/(7 文件,SM90 后端替身)、deepinfer/debug_dump.py、 14 个 scripts/h20_*.py / ref_*.py 验证脚本、4 份设计/调查文档。

已完成(可作为 PR 的验证证据)

  • prefill 因果掩码 bug(首 token 从 EVAL - 变回 17607)
  • 专家权重 scale 广播 bug(requant_expert_fp4 把 (n_out, k/32) 当 (n_out/32, k/32) 展开) → w1/w3/w2 的 cos 从 0.89 到 0.9997~1.0000
  • layer0 整层 rel_err 1.27e-1 → 2.88e-2,整条误差曲线压平一个数量级
  • KV cache 闭环 round-trip(rel 0.027,FP8 地板)
  • _decode_p_sum_sm90 vs 独立 float64 参考(rel 1e-7)
  • decode 逐步误差曲线(Δmargin mean +0.711 / std 1.628,8 位置 7 个 top-1 正确)
  • 自然文本提问答案完全正确(北京。,8 rank 一致)
  • FlashMLA SM100-only → 自己写 SM90 稀疏 MLA 后端 + DeepGEMM sparse MQA logits 替身

遗留

# 问题 优先级 状态
1.1 继承 C1:CUDA graph capture 挂,必须 eager 🔴 P0 未修
1.2 继承 C2:必须 EP_DISABLE_GIN=1 🟡 P1 未修
1.3 继承 C4:token_logprobs 恒 0 🟡 P2 未修,有判定路径
1.4 继承 C5:性能无系统数据 🔴 未开工 —
1.5 绕过项需要逐个 review 后再进主干(见下表) 🟡 P1 未做
1.6 PR 无 reviewer / 无 CI,也没写「怎么验」的 checklist 🟡 P1 未做
1.7 4 份 MD(~1700 行)是否都该进仓库,还是压成 1 份 + PR 正文 ⚪ P3 待定

1.5 绕过项明细(H20-DEBUG-PLAN.md 备注 + §6.4)

绕过 原因 数值风险 待办
DEEPINFER_ENFORCE_EAGER=1 C1 无(仅性能) 见 C1a/C1b
EP_DISABLE_GIN=1 C2 无(改走 NVLink all_to_all,已单测) 见 C2
DeepSelect topk → torch.topk SM90 launch 失败 无(契约等价,h20_topk_check.py 已验) 查能否修 kernel 而不用 torch 兜底
decode indexer → _decode_p_sum_sm90 SM90 paged kernel 要求 64-token page 已验(P4.4 rel 1e-7) 关闭
关闭共享层候选块优化 fp8_fp4_sparse_mqa_logits SM100-only 无(稠密等价) 记入 PR 说明
mega_mhc / mega_moe 关闭 SM100-only 无(有单测) 记入 PR 说明
ViT → SDPA FA-4 是 SM100-only 无(多模态路径,本轮不跑) 记入 PR 说明

建议动手顺序(PR 线)

  1. C1a(30 分钟)→ 默认配置终于能起来,别人的第一印象取决于这个
  2. 1.6 PR 正文 + 验证 checklist(半天,纯文档)
  3. C2 自动检测(1 小时)
  4. 1.5 把 6 个绕过的「数值风险」在 PR 里逐条落字(半天)
  5. C4 logprob(1~2 小时)
  6. C5 性能(1 天)
  7. C1b engram capture-safe 重写(1~2 天,收益是 decode graph)
  8. 1.7 文档收敛(半天)

二、Triton 版(h20-sm90-triton)

规模与成果

  • 新增 tilelang_ops/triton/ 19 个模块,与 cuda/ 平级,ops.py 按 DEEPINFER_OPS_BACKEND 选后端
  • 全量 A/B:22 个 kernel、约 140 用例,全绿(scripts/h20_ops_ab.py)
  • 非 MTP 路径:20/20 kernel 全 Triton,回退提示 0 条,token 与纯 TileLang 逐 token 相同
  • MTP 开:16/20 Triton,5 条回退提示,token 同样一致
  • 采样器:逆推出 cuRAND Philox 的 seeding(20/20 bit-identical),engine 配置下 token 100% 一致

遗留

# 问题 优先级 状态
2.1 继承 C1 / C2 / C4 / C5(同底座) 见零章 —
2.2 MTP 4 个 kernel 未移植 🟡 P1 ✅ 已完成(2026-10-22)
2.3 采样器 rejection-sampling 变体未实现 🟡 P1 显式委托 cuda
2.4 16 个可选路径 kernel 缺失(导入期报 lacks 16 kernel(s)) 🟡 P1 见 2.2b
2.5 窄 cut 采样器 token 差 9~13% ⚪ P3 已定量解释,不修
2.6 Triton 后端 0 个 pytest 用例 🔴 P1 未做
2.7 没有 Triton vs TileLang 的性能对比 🔴 未开工 —
2.8 PR 正文没写;H20-TRITON-PLAN.md 307 行需收敛 ⚪ P3 —
2.9 scripts/h20_state_check.py 未提交 ⚪ 已生成,待 commit
2.10 Triton-only 环境跑不通(见第四章) 🟡 P1 部分已修
2.11 国产卡(is_sm90=False)上 candidate_ 变成每层热路径* 🔴 P1 未做,见 2.2b

2.2 MTP 4 个 kernel(✅ 2026-10-22 全部完成)

kernel 结果 说明
get_accept_result_tl ✅ 9 个 A/B 用例(has_golden × golden 混合比例 × 跨 block)。前缀扫描在 Triton 里用「block 内最长 span」做上界,短 span 的 lane 在多余迭代上保值
finalize_verification_tl ✅ 10 个变体(has_sum_smaller_probs × has_confidence × num_steps 0/1/5 × topk 0/8/130)。batch_confidence_error 改成 program 内归约 + 单次 atomic(原实现逐行 atomic,Triton 里标量指针配向量值不合法),因此浮点求和顺序与 baseline 不同,用例标 exact=False
get_fused_dflash_get_input_tl ✅ 9 个用例(2 个 mask 开关 × padding 请求 × kv_len 16/128/232)。padded_kv_len 是 64 的倍数但不是 128 的倍数,列循环必须向上取整否则整行尾部不写
remap_draft_topk_tl ✅ 5 个用例(含 force_topk=4096 / vocab=129280)。算法换了:baseline 用 shared 内存池按 vocab 分块建表再查表,Triton 无 shared 原语,改成 target×draft 直接两两匹配(draft index 唯一 → 每行至多一个匹配,取 max 即可)。复杂度从 O(vocab/pool_size × topk) 变成 O(topk²),force_topk 默认 0(不在必经路径),需要时再优化

2.2b 剩下的 16 个:按「国产卡上会不会崩」排序

从 20 降到 16。这 16 个没有一个无条件必经,但分布很不均匀:

优先级 kernel 条件 为什么
🔴 国产卡热路径 candidate_block_max_tl
get_candidate_compact_lens_tl
restore_candidate_indices_inplace_tl
not PLATFORM.is_sm90 且 checkpoint 的 candidate_source_layer_id 非 null 本仓库的 V4.1 checkpoint 配的是 candidate_source_layer_id=20 / 40 层。H20 上 is_sm90=True 把这条路径硬关掉了(index_attn.py:126-129),所以从来没测到;国产卡 is_sm90=False,第 20 层之后的 19 层每步都要走 restore_candidate_indices_inplace。⚠️ 但前置阻塞更硬:这条路径同时要 deep_gemm.fp8_fp4_sparse_mqa_logits,那是 SM100-only(arch_major==10),光移植这 3 个 kernel 没有意义
🟡 服务场景会撞 apply_token_bitmask_by_step_inplace_tl 请求带 schema / tool call(so_indices is not None) 唯一「普通用户一开就崩」的:与部署配置无关,纯请求参数驱动
🟡 batch_downsample_tl
replace_input_embeds_tl
rgb_pad_to_patches_tl
input_metadata.images_raw is not None 三件套,有图请求必然全走,无降级
⚪ 显式开关 prepare_cp_mirror_tl CP>1
⚪ sum_smaller_probs_tl DEEPINFER_COMPUTE_SUM_SMALLER_PROBS(默认 False)
⚪ compute_seqlens_q_by_confidence_rates_tl DEEPINFER_ENABLE_MTP_VARLEN(默认 False)+ ZOS
⚪ copy_dflash_decode_input_tl DFlash CUDA Graph(DEEPINFER_ENFORCE_EAGER=1 时反而不走) 关掉优化就能绕过,可作为国产卡首轮降级开关
⚪ yoco_cp_boundary_fused_tl CP>1 + DEEPINFER_YOCO_PREFILL_TRUNCATION_MODE=compact(默认 off)
⚫ 生产不可达 detect_outlier_and_abort_tl
load_state_to_cache_tl
select_state_slice_tl
random_sampling_tl
无 前三个连测试都没有;load_state_to_cache/select_state_slice 的宿主方法全仓库无调用方,只在 tests/worker/test_state_cache_ops.py 用到。可以一直不做

2.10 Triton-only 环境(vendor 镜像 / CI 镜像)

「技术栈换成 Triton」的隐含目标是「只装 Triton 也能跑」,但当前做不到,因为 TileLang 概念漏到了接口层。已在第四章验证并修掉第一批: ops.py 加 DEEPINFER_OPS_STRICT、tile_kernels / tilelang 的 import 移进参考实现与单测函数。

2.10b ⚠️ 「没有 TileLang」的真实阻塞面比 16 个 kernel 大得多(2026-10-22 实测)

做了一次「假装这两个包不存在」的实测:用 sitecustomize.py 在 meta_path 上拦 tilelang / tile_kernels / tvm_ffi(必须走 sitecustomize,因为 8 个 DP rank 是 spawn 出来的,父进程的拦截不继承),然后 import deepinfer。

结果:import 就失败了,根本走不到那 16 个 kernel。

hugging_face/weights.py:38 → model_executor/layers/linear.py:4 → import tile_kernels
ModuleNotFoundError: No module named 'tile_kernels'

为什么:tilelang_ops 那层抽象只包住了「kernel 库」里的 44 个 kernel,但模型前向 直接调 tile_kernels(它是 FlagOS 的另一个包,tile_kernels/__init__.py:1 就是 import tilelang,140 个 py 里 166 处引用 → 留着 tile_kernels 等于留着 tilelang)。

AST 扫描结果(排除 tilelang_ops/cuda/ 与 asc/):

项 数量 分布
模块级 import tile_kernels 25 处 / 13 个文件 linear rotary_embedding deepseek v4_modules/{attn,engram,engram_metadata,hc,mhc_ops,mhc_modeling,mlp} vision_encoder/vit parallel_utils/userbuffer
直接调用的 API 23 个 见下

按移植成本分三类:

类别 API 说明
琐碎 set_num_sms set_pdl set_token_alignment is_ascend utils.dtype_to_str use_deterministic_algorithms 配置开关与字符串工具,几行代码替代
热路径真 kernel quant.per_token_cast(FP8 per-token 量化,linear/mlp/attn/hc 四处都用)
quant.swiglu_forward_and_per_token_cast(融合 SwiGLU+量化)
moe.moe_topk_gate
transform.apply_rotary
quant.swiglu_forward
quant.batched_transpose_weight_sf
每 token 每层都过,必须真写 Triton,纯 torch 版本会慢到不可用
DeepSeek 专有重 kernel 9 个 mhc.*(expand / head_compute_mix / norm_fn / post / pre_apply_mix / pre_big_fuse / pre_split_mixes / sinkhorn)+ engram.engram_hash MHC 注意力与 Engram,语义复杂,工作量最大

唯一现成的退路:tile_kernels/torch/ 是一整套纯 torch 参考实现(cast engram mhc moe norm reduce_fused rope swiglu topk,0 处 tilelang),导出 engram_hash_ref、8 个 mhc_*_ref、moe_topk_gate_forward、swiglu_forward、 rotary_embedding_ref、cast/cast_back、batched_transpose_weight_sf、 norm_forward_ref 等,几乎覆盖上表所有重 API。但 tile_kernels/__init__.py 第一行 import tilelang 会挡住 import tile_kernels.torch,需要把 __init__ 改成 lazy (或把 torch/ 抽成独立包)。

→ 这条能给国产卡一个「正确但慢」的 bring-up 桥:先用 *_ref 跑通数值,再按 profile 把热路径逐个换成 Triton。flag_gems 不能直接替代(903 个 ATen 级算子,没有这些定制融合算子)。

结论:不要再把「国产卡上不能有 tilelang」说成「只差 16 个 kernel」。 16 个 kernel 是小头,模型前向对 tile_kernels 的直接依赖是大头。

2.2 MTP 4 个 kernel(✅ 2026-10-22 全部完成,见下表)

2.3 采样器 verify 变体

非 MTP 路径的 fused_sample_tl 已移植(单 tile,vocab=4096),但 rejection sampling 变体 (MTP verify 路径)没实现,现在显式委托给 TileLang 并打印一行说明。

注意它是显式委托而不是静默回退 —— fused_sample 是模块导出函数,_FallbackNotice 覆盖不到,所以工厂自己打印。要做需要:accept/reject 比较、residual mass、拒绝后第二次 draw。

2.4 缺失的 16 个 kernel = 全可选路径

对着 cuda/ 的 44 个 *_tl 与 triton/ 的 28 个做差集得到,与导入期那行 lacks 16 kernel(s) 逐个对得上(first = apply_token_bitmask_by_step_inplace_tl)。 逐项的条件与优先级见上面 2.2b。

模块 kernel 何时会用到
structure_output apply_token_bitmask_by_step_inplace 引导解码 / 工具调用 / JSON 约束 ← 最可能真被用到
image_embed_ops batch_downsample / replace_input_embeds / rgb_pad_to_patches 多模态
candidate_blocks candidate_block_max / get_candidate_compact_lens / restore_candidate_indices_inplace 共享层候选块优化(国产卡上会打开,见 2.2b)
prepare_cp_mirror prepare_cp_mirror / yoco_cp_boundary_fused CP > 1
state_cache load_state_to_cache / select_state_slice 生产不可达
schedule_mtp_varlen compute_seqlens_q_by_confidence_rates varlen MTP
dflash_copy copy_dflash_decode_input dflash CUDA Graph
outlier_detection detect_outlier_and_abort 生产不可达
random_sampling random_sampling 生产不可达
sum_smaller_probs sum_smaller_probs 采样后处理

4 个 MTP:remap_draft_topk / get_accept_result / finalize_verification / get_fused_dflash_get_input(见 2.2)。

建议:重点补 structure_output(唯一在正常服务里可能被打开的功能),其余按需。

2.5 窄 cut 采样器的 9~13% 差异(结论已定,不建议修)

r = draw % total,  total = sum(floor(p_i * 2^31))

baseline 拒采样上界 (2^32-1) // total * total 在 total 跨 2^31 时从 2*total 阶跃到 1*total。 total 因概率和为 1 就贴着 2^31,归约顺序差几个单位就会换 draw。

  • engine 实际配置(top_p=0.99):100%(128/128)
  • top_k=128:91.4%,top_p=0.5:86.7%,top_p=0.9+top_k=256:87.5%
  • 保留集合逐列一致,logprobs 差 1e-5
  • clamp total 无效(baseline 自己两个方向都会跨,clamp 只是把不一致换到别的行:11 → 30)

2.6 Triton 后端没有 pytest 用例 🔴

tests/ 下 0 个文件提到 DEEPINFER_OPS_BACKEND 或 triton。140 个 A/B 用例全在 scripts/h20_ops_ab.py 里,是「我在跑」,不是「CI 在跑」。

  • 2.6 把 scripts/ab_cases.py 的用例搬成 tests/ 下的参数化 pytest(需要 GPU, 按 PLATFORM.is_sm90 skip),至少覆盖 20 个已移植 kernel 的 smoke。 这是这个 PR 能不能长期活下来的关键 —— 否则下一个人改 cuda/ 侧,Triton 侧静默漂掉。

建议动手顺序(Triton 线)

  1. C1a(先解决共用 P0,否则默认配置跑不起来)
  2. 2.9 提交 h20_state_check.py(10 分钟)
  3. 2.6 pytest 化(1 天)← 优先级高于继续移植
  4. 2.4 补 structure_output(半天)
  5. 2.3 采样器 verify 变体(1 天,Philox 已破,剩下是拒绝逻辑)
  6. 2.2 MTP 4 个(remap_draft_topk 是硬骨头,单独排 1~2 天)
  7. 2.7 + C5 性能对比(1~2 天)
  8. 2.8 PR 正文(半天)

三、两条线的关系

main
 └── h20-sm90-support        ← PR #180(TileLang,SM90 能跑)
      └── h20-sm90-triton    ← 本清单第二条线(kernel 换 Triton)
  • h20-sm90-triton 包含 PR #180 的全部内容,所以 PR #180 的遗留问题它全都有。
  • 建议 先把 C1/C2 在 h20-sm90-support 上修掉再往下走,否则 Triton 版的每次端到端 都得记着加两个 env,容易把「配置问题」误判成「Triton 问题」。
  • Triton 版真正的增量风险只有 2.2 / 2.3 / 2.4 / 2.5 / 2.6。

四、在 FlagTree / FlagOS 镜像里跑 Triton(2026-10-22 实测)

目标:确认 Triton 版能不能直接放进 vendor 镜像(FlagOS / FlagTree),不开 FlagGems。

4.0 先厘清概念:FlagTree 就是 Triton 的一个 fork

这一点先说清楚,否则「FlagTree 编不过」是个误会。镜像里:

flagtree.__version__ == triton.__version__ == 3.6.0

因为 flagtree/__init__.py 只有一行 from triton import __version__, flagtree-0.7.0.dist-info 的 Summary 也写明 「forked from triton-lang/triton」。

所以:

  • FlagTree 不是一个需要「替代 Triton」的独立东西,它就是 Triton,fork 出来加多芯片后端。 发行名是 tokenspeed-triton,但 import 名仍然是 triton(为了源码兼容)。
  • flagtree 这个 Python 包只装了 FlagPrism(调试器/性能分析附件),编译能力在 triton 包里。
  • 因此「用 FlagTree 跑我们的 Triton kernel」= 用那个 fork 过的 triton 包跑,这正是它的主场。

FlagTree 编 Triton kernel 从来没有失败过。 下面 §4.3 的「跑不起来」全部是 与 Triton 无关的 C++/CUDA 扩展(_C、deep_gemm、deep_ep)和 TileLang 包 (tile_kernels),见 §4.4 表格逐项标注了「谁在编」。

4.1 三套环境

容器 镜像 torch triton
di-h20 vllm/vllm-openai:v0.32.0 2.13.0+cu130 3.7.1(标准)
zjr-flWithTree-0930 flagtree/flagtree-py312-torch2.11.0_cu130 2.11.0+cu130 3.6.0 = FlagTree(tokenspeed-triton 3.8.10.post20260721)
flagos moonlight-16b-a3b-...-tree_0.5.0_3.5-gems_5.0.2 2.9.0+cu128 3.5.1

4.2 结论一:FlagTree 的 Triton 能编我们的 kernel ✅

scripts/flagtree_triton_probe.py 覆盖 DeepInfer 真正用到的 7 类特性,两个镜像都 7/7 通过:

特性 为什么单独测 结果
elementwise add 基本盘 max abs err 0.00e+00
tl.dot matmul tensor core 路径 rel 3e-4(fp16 输入、fp32 累加)
tl.gather on tile compressor 选行、没有对应原语时最容易挂 行与 pool[idx] 全等
tl.gather 做 RoPE 配对 lanes ^ 1 / lanes ^ half 逐元素相等
fp8e4nv cast + block scale 全是 FP8 权重 rel 3.2~3.4%(e4m3 理论量级)
int64 CDF 比较 uint32 走有符号是已知坑 完全相等
PTX target 确认真的出 sm_90a .version 8.7 / .target sm_90a

⚠️ FlagTree 镜像(cu13)需要补一条: LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/root/.triton/nvidia/cudart/cuda_cudart-linux-x86_64-12.8.57-archive/lib, 否则报 OSError('libcudart.so.12: cannot open shared object file')。Triton 自己下载了 cudart 但没把它加进搜索路径,跟 FlagTree 无关,是 cu13 环境的通病。

4.3 结论二:19 个 kernel 的输出在三套 Triton 上逐元素一致 ✅

新增 scripts/h20_ops_ab.py --dump DIR / --compare DIR:在没有 TileLang 基线的机器上, 先把选中后端的输出存盘,再到另一台机器上逐元素比对。

flagtree (triton 3.6.0)  --dump  → 115 用例,2 个 kernel 跳过
di-h20   (triton 3.7.1)  --dump  → 125 用例
flagtree --compare di-h20 的 dump → AB_OK
flagos   (triton 3.5.1)  --compare di-h20 的 dump → AB_OK

跳过的是 fused_sample_tl 和 map_lookup_tl,因为它们要 deepinfer._C.engine (sampling_inputs 的结构体)。两个镜像上都是同一个原因,不是 Triton 的问题。

顺带确认:di-h20 上全量 A/B 仍是 AB_OK,本次改动零回归。

4.4 结论三:端到端推理在两个镜像里都跑不起来 ❌(Triton 无辜)

先看「谁在编」,否则容易误判成 Triton 不行:

依赖 谁来编译 跟 Triton 有关吗
19 个 Triton kernel FlagTree / triton 就是它自己,7/7 + 115 用例全过
deepinfer/_C g++ + nvcc(torch C++ 扩展) ❌ 无关
deep_gemm / deep_ep g++ + nvcc(vendor CUDA 扩展) ❌ 无关
tile_kernels TileLang(另一个编译器栈) ❌ 无关

三层依赖,按难度排序:

# 缺什么 影响 修法
1 deepinfer/_C 的 ABI flagtree(torch 2.11) 报 undefined symbol: at::TensorBase::const_data_ptr<int>(),flagos(torch 2.9) 直接找不到模块 用对应 torch 版本重编 _C;官方 pin 的是 torch==2.13.0+cu132,两个镜像都不是
2 deep_gemm / deep_ep 缺 vendor CUDA 库,MoE/GEMM 无法执行。PyPI 上的 deep-gemm 1.0.0 不是 2.8.2 定制版,必须从 vendor/ 编译 在目标镜像里 scripts/build_vendor.sh(需 NCCL 2.32.3 + nvcc,flagtree 工具链齐全)
3 tile_kernels 要 tilelang>=0.1.15 flagtree 的 tilelang 是 0.1.9,缺 tilelang.cuda 模块。但这只影响非 strict 模式:设 DEEPINFER_OPS_STRICT=1 后完全不碰 TileLang 栈,已实测 norm 可 import 升级 tilelang,或彻底去掉对它的依赖(见 4.5,本轮已做到)

关键判断:「Triton 化」不等于「只装 Triton 就能跑」。 真正卡住的是 ①torch 版本绑定的 C++ 扩展、②两个 vendor CUDA 库。这两个跟 Triton 无关,换任何 kernel 语言都躲不开 ——它们跟原来用 TileLang 时是同一份 C++ 代码。

4.4b 在 flagtree 镜像里重编:四个目标全部完成(2026-10-22 11:45)

在 harbor.baai.ac.cn/flagtree/...:202609-3.6-vllm0.24.0(torch 2.11 + cu13 + gcc 13.3 + nvcc 13.0)里建了个专用构建容器 h20-ft-build,vendor/ 用现成 checkout,源码 tar 过去。

目标 结果 踩到并修掉的东西
deepinfer._C ✅ 编出来了,import 成功,34 个符号(含 LogitsTransformInputs) ①setup.py 强制要 CONDA_PREFIX/VIRTUAL_ENV → 建 --system-site-packages venv;②scripts/build.sh 要 python 而非 python3 → 补软链;③镜像根分区只剩 3G → 删无人使用的 seed-oss 镜像释放 37G;④macOS 打包带进 ._* 文件让 compileall 报错 → 无碍
deep_gemm ✅ 2.8.2 ①缺 elfutils/libdwfl.h → apt install libdw-dev libelf-dev;②缺 cusparse.h(镜像只装 CUDA 运行时不带头)→ 把 nvidia/cu13/include/*.h 拷进 /usr/local/cuda/include
deep_ep ✅ 2.5.0 缺 NCCL 2.32.3(ncclCommProperties / ginMinStride 是新 API),镜像只有 2.28.9 → 阿里源下 305MB wheel(pip 下载会超时,用 curl -C - 续传)
deep_select ✅ 1.0.0 ①setup.py 顶层 import tests.kernelkit → 要 triton;②要 lspci → apt install pciutils git

验证(单卡 H20,strict 模式):

torch 2.11.0+cu130 cap (9,0) | triton 3.6.0 (= FlagTree 3.6.0)
vendor: deep_gemm 2.8.2 | deep_ep 2.5.0 | deep_select 1.0.0 | nccl 2.32.3
_C: 34 symbols | ops backend triton strict True, 24 kernels, 0 inherited
gather kernel: exact
全量 A/B --dump: 125 用例,0 跳过(fused_sample / map_lookup 不再缺 _C)
全量 A/B --compare di-h20(3.7.1) 的 dump: AB_OK —— 逐元素一致

所以「vendor 镜像里跑 Triton 版」这条路已经通了。

4.4c 8 卡端到端:跑通了,且与参考镜像逐 token 相同 ✅(2026-10-22 12:25)

在 flagtree 镜像里 --ep 8 --max_tokens 32,两个 prompt:

=== continuation: '。\n问题:法国的首都是哪里?\n答案:巴黎。\n问题:请介绍一下中国的首都。\n
                    答案:北京是中国的首都,是中国的政治、文化和国际'
=== continuation: '北京。\n问题:中国的首都是哪里?\n答案:北京。\n问题:中国的首都是哪里?\n
                    答案:北京。\n问题:中国的首都是哪里?\n答案'
检查项 flagtree 镜像 di-h20 参考镜像
exit code 0 0
每个 prompt 的 token 序列出现次数 8(8 rank 全一致) 8
distinct token 序列 2(= 2 个 prompt) 2
token ids 逐位对比 完全一致(diff 无输出) 基线

回退情况与 di-h20 完全相同,符合已知的 P1/P2 遗留:

[ops] backend 'triton' lacks 20 kernel(s), falling back to 'cuda' (first: apply_token_bitmask_by_step_inplace_tl)
[ops] kernel 'remap_draft_topk_tl'        used from 'cuda'   ← MTP
[ops] kernel 'get_accept_result_tl'       used from 'cuda'   ← MTP
[ops] kernel 'finalize_verification_tl'   used from 'cuda'   ← MTP
[ops] kernel 'get_fused_dflash_get_input_tl' used from 'cuda' ← MTP
[ops] fused_sample: rejection sampling (MTP verify) is not implemented in the triton backend

良性差异(非 Triton、与 torch 版本有关):

stats_logger: nccl_memory stats_unavailable=AttributeError(
    "'ProcessGroupNCCL' object has no attribute 'memory_stats'")

torch 2.11 没有 ProcessGroupNCCL.memory_stats(2.13 才有),只影响一行统计日志。

跑端到端还需要的额外依赖(A/B 单测不需要,引擎 import 链需要): tilelang 0.1.15 + tile_kernels 2.1.0 + tvm_ffi 0.1.11(后者被前者依赖,镜像自带 0.1.9 缺 tvm_ffi.dataclasses.Enum),以及 requirements.txt 里的 19 个包。

⚠️ 这个机器上 pip 装大包会卡死(posix_ipc 的 wheel 12 分钟没下来)。可行的办法是 docker cp 从已装好的参考镜像拷 site-packages 再挑包复制。pillow 反而要用镜像自带的 (12.2),拷 di-h20 的 11.3 会缺 pillow.libs 里的 libtiff-*.so。

4.4d 上面这些已经写成脚本(可复现,已从零验证)

scripts/setup_flagtree_image.sh 把 §4.4b~4.4d 的全部踩坑固化下来,三阶段:

# 宿主机上先备好参考包(docker cp 只在容器外可用)
docker cp <flagtree镜像>:/usr/local/lib/python3.12/dist-packages/. /data/ftbuild/ref-triton/
docker cp <tilelang镜像>:/usr/local/lib/python3.12/dist-packages/. /data/ftbuild/ref-tilelang/
docker cp <引擎镜像>:/usr/local/lib/python3.12/dist-packages/.     /data/ftbuild/ref-site/

# 容器内
bash scripts/setup_flagtree_image.sh env      # apt / venv / cusparse 头 / NCCL / triton / tilelang / py 依赖
bash scripts/setup_flagtree_image.sh build    # _C + DeepGEMM + DeepEP + DeepSelect
bash scripts/setup_flagtree_image.sh verify   # import 与 ops 后端自检

配套 scripts/copy_site_packages.py(按 distribution 的文件清单复制,所以 pillow→PIL、 pyzmq→zmq 这种名字不一致的也能带全;--keep-system pillow 保留镜像自带的)。

从零验证过:新建一个空 venv 跑完 env→build→verify,再跑 scripts/h20_e2e_report.sh:

env            19 个依赖全部就位,triton 3.6.0 = flagtree 3.6.0,tilelang 0.1.15
build          _C + deep_gemm 2.8.2 + deep_ep 2.5.0 + deep_select 1.0.0(3m33s)
verify         torch 2.11.0+cu130 cap (9,0) | _C 34 symbols | triton strict, 24 kernels
e2e report     exit=0,两个 prompt 的 token 序列各出现 8 次,无 traceback

也就是说这些步骤不是「在我这个容器里碰巧能跑」,换一个空 venv 同样成立。

4.5 本轮为「Triton-only 环境」修掉的东西

原来 import deepinfer 会顺带拉起整个 TileLang 栈,在只装 Triton 的机器上直接 ImportError。 本轮把 TileLang 概念从接口层剥出去:

文件 改动
ops.py 新增 DEEPINFER_OPS_STRICT=1:不 import 默认后端、不 fallback,缺 kernel 直接抛。这是「纯 Triton 环境」的前提
norm.py tilelang.language as T 只在取 dtype 句柄时用 → 新增 _tl_dtype(),没装 TileLang 就返回字符串(Triton 工厂本来就忽略这个参数);tile_kernels 移进 test_norm
compress.py _enable_fast_math 改成「有 TileLang 才设 pass_configs」,Triton 工厂的空 dict 直接跳过
cache_common.py tile_kernels 移进 ref_cast_from_bf16 / ref_cast_to_bf16(参考实现专用)
fused_transform.py 同上,移进 ..._ref()
load_paged_kv_cache.py 同上,移进 test_load_paged_kv_cache
scripts/ab_cases.py 所有接口模块改函数内 import,缺包时该组 case 报 skip 而不是整个脚本起不来
scripts/h20_ops_ab.py --dump / --compare;无基线后端时降级为「只跑选中后端」

这是有实际价值的重构:tile_kernels 原本只在参考实现和单测里用到,却卡在生产模块的 import 路径上。修完之后 import deepinfer 在纯 Triton 环境不再需要 TileLang。

4.6 剩下的 Triton-only 障碍

  • 4.6a deepinfer/_C 仍是硬依赖:sampling_inputs → _C.engine。 fused_sample / map_lookup 两组用例因此在两个镜像上都跳过。要么按目标 torch 重编, 要么给 sampling_inputs 加纯 Python 回退(对 Triton 后端够用,只是慢)。
  • 4.6b deepinfer/platform.py → deep_cluster.hardware,ops.py 顶层就要它。 在 flagos 里第一次跑是 ModuleNotFoundError: deep_cluster,把 deep_cluster 也带上才行。 应改成可选依赖。
  • 4.6c 三套 triton 的 autotune / num_stages 行为不同,性能未测(对应 2.7)。

五、一句话结论

  • TileLang PR 版:语义正确、数值已收敛到硬件地板,卡在「默认配置跑不起来」(C1)+ 没有性能数据(C5)+ 没有 CI/验证 checklist(1.6)。
  • Triton 版:非 MTP 路径已经是纯 Triton 且 token 逐位一致,卡在 MTP 4 个 kernel + 采样器 verify 变体 + 0 个 pytest。
  • FlagTree / FlagOS 镜像:FlagTree 就是 Triton 的 fork,编 Triton kernel 从来没失败过 (7/7 特性、19 kernel、125 用例跨 3.5.1/3.6.0/3.7.1 逐元素一致)。原先跑不起来的是 _C + vendor 的 C++/CUDA 扩展,已在 flagtree 镜像里全部重编成功,并且 8 卡端到端已跑通,输出与参考镜像逐 token 相同。剩下的回退就是 P1/P2 那几条遗留, 在两个镜像上一模一样。
  • 两边共同的硬骨头只有 C1(engram gather 不可 capture),其余都是收尾工作。

六、国产卡 bring-up:走 tile_kernels.torch(2026-10-22 实测)

目标:在没有 TileLang 的卡上先把模型跑起来,先用纯 torch 参考实现保正确性,再按 profile 把热路径逐个换成 Triton。§2.10b 说明了大头在哪(模型前向直接调 tile_kernels 的 quant/MoE/MHC/engram,25 处模块级 import / 23 个 API)。

6.1 第一步已通:tile_kernels.torch 能在无 TileLang 下导入

原本不行,因为三层 eager import 把纯 Python 的参考实现和编译栈绑在一起:

层 问题 处理
tile_kernels/__init__.py from . import (config, quant, …) 拖起全部编译 kernel 改成 PEP 562 惰性 __getattr__
tile_kernels/quant/__init__.py 13 个 kernel 模块 eager re-export;import quant.common 会先执行它 同样惰性化(AST 解析原文件生成导出表,带 __all__ 保证 import * 仍可用)
tile_kernels/quant/common.py from tilelang import language as T + 模块级 @T.macro tilelang 变可选;@T.macro 段包进 if T is not None:;4 个 host 侧注解加引号;3 个返回 dtype 句柄的成员改成调用时再解析
tile_kernels/config.py 只为 3 个常量 T.max_value(...) 而 import tilelang 常量改字面量(448.0 / 6.0),逐位校验一致

⚠️ 不能用 from __future__ import annotations:它把 @T.macro 体内的注解也变成字符串, tilelang 的 macro 解析器自己去 eval 时会报 NameError: name 'float32' is not defined。 必须只把 host 侧那 4 个注解单独加引号。

脚本:scripts/enable_torch_kernel_refs.sh(幂等,--revert 可逆)

  • scripts/tile_kernels_patch/{patch_tile_kernels,patch_quant_common,lazify_package_init}.py
  • scripts/verify_tile_kernels_patch.py(证明有 tilelang 时接口面完全不变:子模块属性、 from pkg import sub、config 旋钮、kernel 侧的 from .common import *、dir()、 未知属性仍报 AttributeError → PATCH_SAFE)

6.2 关键未知项已消除:参考实现数值等价 ✅

scripts/tile_kernels_patch/compare_with_cuda.py,用 DeepInfer 在 SM90 上的真实参数 (fmt="e4m3", num_per_channels=128, round_sf=True, use_packed_ue8m0=False, use_tma_aligned_col_major_sf=True):

op 结论
quant.per_token_cast bitwise 一致(data + sf,4 组 shape 含 m=1/257、n=128/7168)
quant.cast_back rel 0.000e+00
quant.swiglu_forward_and_per_token_cast bitwise 一致
engram.engram_hash bitwise 一致(溢出敏感,必须是 int64 池算术)
transform.apply_rotary 65536 元素里 1 个差 1.17/1.33 ULP(bf16 末位舍入),可接受

这是整条桥的前提:量化链 bitwise 相同意味着「先跑通」不会引入 token 级偏差。

6.3 还没做的(下一步)

  1. 路由层:把 deepinfer 里 25 处 import tile_kernels 改成走一个 compat 包 (deepinfer/tk_compat/{quant,moe,transform,mhc,engram,config}.py),按「有无 tilelang / DEEPINFER_KERNELS=ref」选 CUDA 实现还是 _ref。改动最小形式是只换 import 那一行。
  2. MHC 8 个 + MoE gate 未做 A/B:mhc_* 与 moe_topk_gate 还没验证(语义更复杂), 做之前不能假设等价。
  3. 性能:纯 torch 的 per_token_cast 每层都过,预计是数量级退化。桥的作用是 「先正确」,之后按 profile 换 Triton。
  4. 上游:以上 3 个补丁应该提到 FlagOS,否则每次重装都要重跑脚本。

(flag_gems 不是替代品:903 个 ATen 级算子替换,没有这些定制融合算子。)