摘要
test_rmsnorm_persistent_simtvf.py(ub-to-fragment 与 gm-to-fragment 两条路径)在 feat-persistent-unroll-promotion 分支(PR #1341)上编译失败:
Error: VF_SIMT size patch: missing ELF callee symbol 'main_kernel_simt_1_simt_entry'
同一份 TileLang 生成的 kernel 源码(经逐字节 source-diff 验证一致,且该 kernel 不使用 T.unroll——循环是 T.SimtVF 内的 T.Parallel lane 循环)在当前 main(PTOAS 0ee47471)上编译、运行均正常,因此这是 PR #1341 分支引入的回归。
环境
复现
TileLang 侧(tilelang 仓库):
pytest examples/ascend/test_rmsnorm_persistent_simtvf.py
# main PTOAS 构建:2 passed
# PR #1341 PTOAS 构建:2 failed,报上述 VF_SIMT size patch 错误
kernel 是带 persistent weight fragment 的 RMSNorm:T.alloc_buffer(..., scope="local.fragment") 式 weight 定义在 SIMT region 之外,在一个 section 初始化、另一个 section 消费,with T.SimtVF(threads=256) 内是 T.Parallel(TILE) 循环。
两种 codegen 下生成的 PTODSL 均保持 SIMT body 循环为 trace 期 pto.static_range(source-diff 逐字节一致),因此差异完全在 PTOAS 对 persistent fragment + SIMT size patch 路径的处理上。
分析
PR #1341 新增的 pto-promote-persistent-fragment-loops 会把 touching llvm.alloca {pto.persistent} 的循环改写为 pto.unroll = "full" 并加 pto.persistent_unroll 标记。对这个 kernel,promotion 把包裹整个 pto.section.simt 的 kernel 级 tile 循环(trip 64)也展开了:
- 展开把 SIMT section 按 trip 克隆 64 份,SIMT outlining 为每个克隆生成独立的 entry 函数(加上 init entry 共 65 个),每个都是单调用点的
linkonce_odr simt_entry 函数(noinline 已设置——并非普通的内联候选);
- BiSheng 对这些单调用点 ODR 克隆做折叠/内联,其中一部分的
_simt_entry ELF symbol 消失。两次相同 IR 的编译缺失的 symbol 还不同(一次缺 main_kernel_simt_1、一次缺 main_kernel_simt_3),说明折叠行为非确定;
VFSIMTSizePatcher 的 manifest 来自 bisheng 编译前的 LLVM module,要求每个 callee symbol 存在,于是 fail-fast 报 missing ELF callee symbol;发射模块同时膨胀 45 倍(37k 行 vs 811 行)。
关键事实:物化只要求 section 内 的 persistent access 解析到静态 slot。失败 kernel 中所有 persistent GEP 索引都是常量(从不引用 kernel 级循环变量),展开包裹 section 的循环对 slot 静态化没有任何贡献——它只制造了脆弱的克隆结构。而 section 内部(或不包裹 section 的)循环的展开与提升不受影响,仍然必要且保留。
修复(分支本地提交,见后续评论)
pto-promote-persistent-fragment-loops 在 enclosing-loop 收集时跳过体内包含 pto.section.simt region 的循环,pass 头注释、lit case 6 与设计文档同步更新。验证(A5,CANN 9.1.0-beta.3):
- lit:
promote_persistent_loops(RUN1/UNROLL/NOMETA)、_invalid、_dynamic_unroll(全部硬错误契约)、_materialize 全部 PASS;
- TileLang persistent rmsnorm 两路径数值 2/2 PASS,发射结构恢复为 PR 前的双 entry 形态(811 行);
- 全量 PTO 回归(rmsnorm/mhc/intrinsics 含 unroll hint 用例)26 passed;
- review 要求的 hint 变体(
kernel_handwritten_unroll_hint.py / kernel_auto_promoted_unroll.py):均编译通过;无 hint 变体上 promotion 恰好提升 section 内两个循环、tile 循环不被触碰、物化发出 96 处 keep/resume。
后续评论补充了"手写 unroll="full" 在 kernel 级循环上同样失败"的对照证据:该形态在本工具链上从未可用,属于 SIMT outlining(单调用点 ODR 克隆)+ BiSheng(折叠)的上游能力缺口,需要 outlining/bisheng 侧保持 entry symbol 稳定(如对 simt entry 克隆禁用 ODR 折叠)才能真正支持。
摘要
test_rmsnorm_persistent_simtvf.py(ub-to-fragment与gm-to-fragment两条路径)在feat-persistent-unroll-promotion分支(PR #1341)上编译失败:同一份 TileLang 生成的 kernel 源码(经逐字节 source-diff 验证一致,且该 kernel 不使用
T.unroll——循环是T.SimtVF内的T.Parallellane 循环)在当前 main(PTOAS0ee47471)上编译、运行均正常,因此这是 PR #1341 分支引入的回归。环境
340650086(feat-persistent-unroll-promotion,PR feat(pto): auto-promote persistent fragment loops to full unroll #1341 代码)0ee47471构建——同一 kernel 通过asc@505acc0f;kernel 源码在两个 codegen 变体间逐字节一致复现
TileLang 侧(tilelang 仓库):
kernel 是带 persistent weight fragment 的 RMSNorm:
T.alloc_buffer(..., scope="local.fragment")式 weight 定义在 SIMT region 之外,在一个 section 初始化、另一个 section 消费,with T.SimtVF(threads=256)内是T.Parallel(TILE)循环。两种 codegen 下生成的 PTODSL 均保持 SIMT body 循环为 trace 期
pto.static_range(source-diff 逐字节一致),因此差异完全在 PTOAS 对 persistent fragment + SIMT size patch 路径的处理上。分析
PR #1341 新增的
pto-promote-persistent-fragment-loops会把 touchingllvm.alloca {pto.persistent}的循环改写为pto.unroll = "full"并加pto.persistent_unroll标记。对这个 kernel,promotion 把包裹整个pto.section.simt的 kernel 级 tile 循环(trip 64)也展开了:linkonce_odrsimt_entry函数(noinline已设置——并非普通的内联候选);_simt_entryELF symbol 消失。两次相同 IR 的编译缺失的 symbol 还不同(一次缺main_kernel_simt_1、一次缺main_kernel_simt_3),说明折叠行为非确定;VFSIMTSizePatcher的 manifest 来自 bisheng 编译前的 LLVM module,要求每个 callee symbol 存在,于是 fail-fast 报missing ELF callee symbol;发射模块同时膨胀 45 倍(37k 行 vs 811 行)。关键事实:物化只要求 section 内 的 persistent access 解析到静态 slot。失败 kernel 中所有 persistent GEP 索引都是常量(从不引用 kernel 级循环变量),展开包裹 section 的循环对 slot 静态化没有任何贡献——它只制造了脆弱的克隆结构。而 section 内部(或不包裹 section 的)循环的展开与提升不受影响,仍然必要且保留。
修复(分支本地提交,见后续评论)
pto-promote-persistent-fragment-loops在 enclosing-loop 收集时跳过体内包含pto.section.simtregion 的循环,pass 头注释、lit case 6 与设计文档同步更新。验证(A5,CANN 9.1.0-beta.3):promote_persistent_loops(RUN1/UNROLL/NOMETA)、_invalid、_dynamic_unroll(全部硬错误契约)、_materialize全部 PASS;kernel_handwritten_unroll_hint.py/kernel_auto_promoted_unroll.py):均编译通过;无 hint 变体上 promotion 恰好提升 section 内两个循环、tile 循环不被触碰、物化发出 96 处 keep/resume。后续评论补充了"手写
unroll="full"在 kernel 级循环上同样失败"的对照证据:该形态在本工具链上从未可用,属于 SIMT outlining(单调用点 ODR 克隆)+ BiSheng(折叠)的上游能力缺口,需要 outlining/bisheng 侧保持 entry symbol 稳定(如对 simt entry 克隆禁用 ODR 折叠)才能真正支持。