Skip to content

feat(pto/ptodsl): restore unroll="enable" metadata hint (issue #1242 Req 2) - #1339

Open
jimmychou0 wants to merge 1 commit into
hw-native-sys:mainfrom
jimmychou0:feat-unroll-enable-hint
Open

feat(pto/ptodsl): restore unroll="enable" metadata hint (issue #1242 Req 2)#1339
jimmychou0 wants to merge 1 commit into
hw-native-sys:mainfrom
jimmychou0:feat-unroll-enable-hint

Conversation

@jimmychou0

@jimmychou0 jimmychou0 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

概述

恢复 unroll="enable" hint 的 LLVM metadata 通道,闭环 issue #1242 Requirement 2。

PR #1295 在评审后移除了整个阶段一(metadata 透传),但 #1242 Req2 的验收标准要求的正是这条路径:

  • public pto.for_ 可以携带 unroll-enable hint;
  • hint 能到达 LLVM/BiSheng,而不是在 PTODSL/PTOAS 前端强制 full unroll。

full/unroll_factor 的原生展开与 enable 语义互斥(前者是前端强制展开,后者是把 full/partial 的决定留给编译器 cost model),无法互相替代。本 PR 以最小子集恢复该通道:只恢复 enabledisable 不再恢复,full/factor 的现有语义完全不变。

实现

  • pto-lower-loop-hints(精简版,仅 enable):接在两个 VPTO emitter 管线的 convert-scf-to-cf 之前。把 {pto.unroll = "enable"} 翻译成 #llvm.loop_annotation<unroll = <disable = false>>,最终成为 !llvm.loop.unroll.enable metadata(LLVM ForceEnable 语义:full/partial 由 cost model 选择,预算否决权被解除)。已有 llvm.loop_annotation 时合并而非覆盖(已有 unroll 条目被替换时 warning)。

  • 完整 SCF→CF 转换:LLVM 19 的 convert-scf-to-cf 不把 llvm.loop_annotation 传到 latch,因此本 pass 接管该函数的整个 SCF→CF 转换——运行上游 conversion patterns,外加一个更高 benefit 的 pattern 处理带注解 loop(镜像上游 ForLowering),注解以 ODS 裸名 loop_annotation 挂到 latch cf.br(BrOp::getLoopAnnotationAttr() 按裸名查找)。

    为什么必须整体转换(review 意见闭环):只降级带注解的 loop 会把新生成的 condition/body/latch/exit blocks 留在外层 single-block region 内(无 hint 的外层 scf.forscf.if 等),在 stock 转换运行前就触发该 op 的 SingleBlock verifier 失败。因此本 pass 替代两条 emitter pipeline 中的 createConvertSCFToCFPass,而不是排在它前面。

  • pto-unroll-loops:enable 校验为合法并原样放行(不消费);disable 及其他未知值仍为硬错误。两者职责互斥:每个 loop 的 attr 只被一个 pass 消费,[Feature] 支持Loop Unroll Hint #1000 的重复展开担忧在构造上不存在。其 fixpoint 现在会传播 UnrollOutcome::Error(此前硬错误 loop 被当作 unchanged,emitError 后 pass 仍返回成功)。

  • 前端:normalize_unroll_hint 恢复 "enable" 为合法值(仅 "disable" 仍不支持);pto.for_/pto.range/tile-template 路径自动获得支持。

for i in pto.range(0, n, unroll="enable"): ...      # AST rewrite
with pto.for_(0, n, step=1, unroll="enable") as i: ...
loop = pto.for_(0, n, step=1, unroll="enable").carry(acc=acc)

测试与验证

  • lit:lower_loop_hints.pto 覆盖 enable 翻译、iter_args 穿线、嵌套 enable、annotation 合并/覆盖 warning、空 body 降级、仅内层带注解(上述 single-block 隐患的回归用例)、scf.if 内带注解 loop、scf.while 内带注解 loop、无 hint 函数仍被完整转换,以及 ptoas --emit-vpto-llvm-ir 端到端验证 !llvm.loop.unroll.enable 到达最终 LLVM IR;unroll_loops.pto 锁定 Pass A 对 enable 的透传行为;unroll_loops_invalid.pto 保留其余硬错误。
  • ST:test/dsl-st/unroll_hint_numeric.py 恢复 enable 数值用例(out[3]=28):带 enable 的 loop 在设备上正常执行、数值正确。
  • 144(CANN 模拟器):全量 lit 通过,仅 3 个 element/predicate/vsts offset-index 用例失败——在本 pass 完全禁用时同样失败,属于 main 上 Enable VPTO soft post-update by default #1330(Enable VPTO soft post-update by default)与该验证树基线不一致,与本改动无关;前端 test_loop_unroll_hintstest_ptoas_frontend_verify 全过;dsl-st 模拟器全套 PASS(含 enable 数值用例);合规检查 errors=0。
  • 144(CANN 模拟器)dsl-st 全套 17/17 PASS(含 unroll_hint_numeric 的 enable 数值用例)。A5 硬件验证以模拟器覆盖为准(验证期间 238 设备处于 Alarm 被占状态)。

提交列表

  1. c427632b7 feat(pto/ptodsl): restore unroll="enable" metadata hint (issue [Feature][PTODSL][SIMTVF] 增加 per-element vector constructor 与 loop-unroll hint #1242 Req 2)

// ---------------------------------------------------------------------------

std::unique_ptr<Pass> mlir::pto::createPTOLowerLoopHintsPass() {
return std::make_unique<PTOLowerLoopHints>();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTOLowerLoopHintsPass虽然用于传递 unroll="enable" hint,但实现中还会将带 hint 的 scf.for提前降低成多个cf block,以便把 llvm.loop_annotation挂到loop latch

当只有内层 loop 带 hint 时:

scf.for %i = ... {                    // 外层无 hint
  scf.for %j = ... {                  // 内层 enable
    ...
  } {pto.unroll = "enable"}
}

该 pass 只lower内层 loop,内层生成的 condition、body、latch、exit blocks会被放入外层scf.for的 region,使外层 region 包含多个 block,违反scf.for的 SingleBlock 约束。相同问题也可
能发生在scf.ifsingle-block structured operation中。

由于emitterPassManager开启了 enableVerifier(),验证会在 PTOLowerLoopHintsPass结束后、标准 ConvertSCFToCFPass执行前。因此 pipeline 会直接失败报错。

当前 test case 的内外层都带unroll enable,两层都会在当前 pass 中降低,因此没有覆盖这个问题。

建议不要在独立 hint pass 中局部降低 annotated loop。可以将 hint 处理合并到一个 PTOAS-specific 的完整 SCF-to-CF pass

  • annotated scf.for使用自定义 lowering,将 llvm.loop.unroll.enable放到对应 latch;
  • 无 hint 的外层scf.for、scf.if、scf.while使用标准 lowering;
  • 同一个 pass 内完成所有结构转换,结束时不残留 multi-block SCF operation。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已按方案改为单 pass 完成完整 SCF→CF 转换。

}

if (unrollAttr)
if (unrollAttr) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pto-unroll-loops在识别unroll="enable"之前,会先检查empty bodyinduction variable类型。非 index loop会进入不支持分支,导致 enable 属性被删除,无法继续传递为llvm.loop.unroll.enable

这些限制只应作用于fullunroll_factor,因为只有它们调用 PTOAS 的native-unroll utilityenable只负责metadata透传,不应受native-unroll能力限制。

建议将 enable 判断移动到所有 native-unroll guard 之前,并增加非 index loop 的属性保留。PTODSL 通常生成 index loop,因此影响范围有限,但当前行为违反了 PR 定义的“enable 由 metadata pass 独占消费”契约。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

核对代码后发现影响面比预估稍大:除了非 index loop,空 body 的 enable loop 也会被吃掉 hint——两个 guard 都在 enable 判断之前,且无条件 removeAttr(pto.unroll)

@jimmychou0
jimmychou0 force-pushed the feat-unroll-enable-hint branch 2 times, most recently from c427632 to 175f30e Compare August 25, 2026 10:44
VPTO emission pipeline(两个 emitter):
[Pass B] pto-lower-loop-hints
└─ "enable" → #llvm.loop_annotation,
自行 SCF→CF 降级并挂 latch cf.br

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

方案文档建议也要完整更新下

@@ -0,0 +1,278 @@
// Copyright (c) 2026 Huawei Technologies Co., Ltd.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

建议名字为PTOConvertSCFToCFWithLoopHintsPass,因为现在已经不仅处理hint了,也会真正处理控制流。

…-native-sys#1242 Req 2)

PR hw-native-sys#1295 removed the metadata forwarding stage together with the
cost-model-delegating hints, but hw-native-sys#1242 Req2's acceptance criteria require
exactly that path: "public pto.for_ can carry an unroll-enable hint" and
"the hint reaches LLVM/BiSheng instead of being force-unrolled by the
frontend".  Restore it as a slimmed-down single channel - enable only;
disable stays unsupported; full/factor semantics unchanged.

Backend (pto-convert-scf-to-cf-with-loop-hints):
- The new pass owns this pipeline's SCF-to-CF conversion (it replaces
  createConvertSCFToCFPass in both VPTO emitters) and adds the one thing
  the stock pass cannot do on LLVM 19: carrying
  {pto.unroll = "enable"} over to the loop latch as
  #llvm.loop_annotation<unroll = <disable = false>>, which the
  MLIR-to-LLVM-IR translation turns into !llvm.loop.unroll.enable (LLVM's
  ForceEnable semantics: the cost model picks full/partial unroll with its
  budget veto lifted).  An existing llvm.loop_annotation is merged, not
  overwritten (a pre-existing unroll entry is replaced with a warning).
- It runs populateSCFToControlFlowConversionPatterns plus a benefit=2
  pattern for annotated loops under one applyPartialConversion.
  Converting the whole function is required for correctness: lowering an
  annotated loop in isolation leaves its new condition/body/latch/exit
  blocks inside whatever enclosing single-block region held it (an outer
  unannotated scf.for, an scf.if, ...) and fails that op's verifier before
  the stock conversion ever runs.
- The annotation is stored on the latch cf.br under the bare ODS name
  loop_annotation, which BrOp::getLoopAnnotationAttr() looks up during
  translation; convert-cf-to-llvm forwards branch attributes verbatim.
- pto-unroll-loops validates "enable" as legal and passes it through
  untouched, checked before every native-unroll guard (empty body,
  non-index induction variable): those guards exist because
  loopUnrollByFactor cannot handle such loops, which is irrelevant for a
  hint that only becomes metadata.  "disable" and every other unknown
  value remain hard errors, and the fixpoint now propagates
  UnrollOutcome::Error instead of treating a hard-error loop as unchanged.
- pto-test-opt registers the LLVM dialect so tests can write
  llvm.loop_annotation inputs.

Frontend:
- normalize_unroll_hint accepts "enable" again (only "disable" stays
  unsupported); pto.for_ / pto.range / tile-template paths get it for
  free; docstrings updated.

Tests:
- lit convert_scf_to_cf_with_loop_hints.pto covers the enable
  translation, iter_args threading, nested enable loops, annotation
  merge/overwrite warning, empty-body lowering, an inner-only annotated
  loop (the single-block hazard above), annotated loops inside scf.if and
  scf.while, a non-index annotated loop, a hint-free function still being
  converted, and an end-to-end ptoas --emit-vpto-llvm-ir check that
  !llvm.loop.unroll.enable reaches the final LLVM IR.
- unroll_loops.pto locks the pass-through, including on the loop shapes
  the native-unroll guards reject; unroll_loops_invalid.pto keeps the
  remaining hard errors.
- Frontend tests: enable attribute emission; rejection now covers only
  "disable".
- dsl-st unroll_hint_numeric: enable case back (out[3], golden 28) - the
  hinted loop executes normally and computes correctly.

Docs:
- Design doc v4: the metadata channel is restored (enable only) and the
  pass is documented as the pipeline's SCF-to-CF conversion; semantics
  matrix, architecture diagram, pass section, diagnostics and test plans
  updated bilingually.
- User guide: enable back in the hint table; the no-semantic-change rule
  distinguishes the metadata hint from the native-unroll hints.

Validation (144): full lit suite green except three
element/predicate/vsts offset-index tests that fail identically with this
pass disabled - they belong to main's hw-native-sys#1330 and are unrelated.  Frontend
test_loop_unroll_hints / test_ptoas_frontend_verify pass; CANN simulator
dsl-st suite passes with no failures; compliance check reports errors=0.
@jimmychou0
jimmychou0 force-pushed the feat-unroll-enable-hint branch from 175f30e to 5acad2f Compare August 25, 2026 12:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants