Skip to content

[Bug] PR #1341 导致 persistent fragment SIMT kernel 编译失败:VF_SIMT size patch 缺失 ELF callee symbol(rmsnorm_persistent_simtvf) #1379

Description

@jimmychou0

摘要

test_rmsnorm_persistent_simtvf.pyub-to-fragmentgm-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 分支引入的回归。

环境

  • PTOAS:340650086feat-persistent-unroll-promotion,PR feat(pto): auto-promote persistent fragment loops to full unroll #1341 代码)
  • 对照:PTOAS main-latest 0ee47471 构建——同一 kernel 通过
  • TileLang:asc@505acc0f;kernel 源码在两个 codegen 变体间逐字节一致
  • 机器:A5 板卡(CANN 9.1.0-beta.3),LLVM 19.1.7 feature-vpto

复现

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)也展开了:

  1. 展开把 SIMT section 按 trip 克隆 64 份,SIMT outlining 为每个克隆生成独立的 entry 函数(加上 init entry 共 65 个),每个都是单调用点linkonce_odr simt_entry 函数(noinline 已设置——并非普通的内联候选);
  2. BiSheng 对这些单调用点 ODR 克隆做折叠/内联,其中一部分的 _simt_entry ELF symbol 消失。两次相同 IR 的编译缺失的 symbol 还不同(一次缺 main_kernel_simt_1、一次缺 main_kernel_simt_3),说明折叠行为非确定;
  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 折叠)才能真正支持。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions