Skip to content

feat(ir): add LegalizeTileCast for hardware-unsupported cast pairs - #2141

Merged
lyfne123 merged 1 commit into
hw-native-sys:mainfrom
lwDavid:lwDavid/a5-legalize-cast-and-early-resolve-gate
Jul 28, 2026
Merged

lyfne123 merged 1 commit into
hw-native-sys:mainfrom
lwDavid:lwDavid/a5-legalize-cast-and-early-resolve-gate

Conversation

@lwDavid

@lwDavid lwDavid commented Jul 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds a LegalizeTileCast pass that expands tile.cast dtype pairs the target
ISA cannot emit as a single pto.tcvt into the shortest chain of native casts.

Ported from the fork branch lwDavid/a5-early-resolve-guard (7f702b39 +
aab3ac06), which was never merged.

Motivation

pto.tcvt supports only a profile-dependent subset of (src, dst) dtype pairs,
and the subset differs per architecture. A5 (Ascend950) has no native
INT32 -> FP16
; A2/A3 provides it as a deq instruction. So a DSL-level
pl.cast(x_i32, pl.FP16, mode="round") compiles on A2/A3 and fails the kernel
C++ compile on A5:

tcvt_common.hpp:2774: error: no matching function for call to
  'castData_2D_NoPostUpdate'
note: candidate not viable: no known conversion from
  '__ubuf__ int *' to '__ubuf__ float *'

DeepSeek V4-Pro's INT8 quantization chain is
fp32 -> i32(rint) -> fp16(round) -> i8(trunc) at 14 sites across the model, so
without this pass none of those kernels can be built for A5 at all.

Approach

Each non-native tile.cast is rewritten into the shortest chain of native casts,
found by BFS over a per-arch ISA adjacency table — on A5, INT32 -> FP16 becomes
INT32 -> FP32 -> FP16. Among equal-length paths it prefers "same byte-width →
float, then adjust width". Already-native casts, including FIXPIPE-foldable
FP32 -> BF16/FP16 with mode=rint, are left untouched.

Position: after FlattenTileNdTo2D (so casts that pass inserts are legalized
too), before AutoTileMatmulL0.

Numerics are unchanged for the case that motivated it: both hops are exact over
the |q| <= 127 range the quantization chain produces (INT32 -> FP32 exact
below 2^24, FP32 -> FP16 exact for integers below 2^11).

A census of the generated .pto for the DeepSeek kernels shows they emit 8
distinct dtype pairs, of which INT32 -> FP16 is the only non-native A5 pair, so
coverage is complete for those models; post-fix builds contain zero i32 -> f16.

Faithfulness. Shortest alone is not enough: an intermediate that cannot hold
what the destination can would silently drop values a direct conversion keeps. A5
has no native UINT32 -> FP32 and every shortest route passes through
INT16/UINT16/UINT8, so 40000 — exactly representable in FP32 — would come
back as garbage. Intermediates that provably narrow relative to the destination
are excluded from the search, and a pair with no faithful chain is reported rather
than lowered to a lossy one. Only provable narrowing is rejected, so an unfamiliar
dtype stays admissible instead of turning a working lowering into a hard failure.

Arch resolution. The profile comes from the PassContext BackendHandler,
falling back to the global BackendConfig, and finally to the A2A3 table. The
fallback matters: several existing codegen tests drive passes with neither
configured, and the A2A3 table is a superset of the pairs in question, so the
pass is a no-op there rather than an error.

Complexity is O(N) in IR size. PassProperties is empty (the pass neither
requires nor invalidates a property).

Testing

  • python -m pytest tests/ut/ → 7710 passed, 2 skipped.
  • New tests/ut/ir/transforms/test_legalize_tile_cast.py (8 cases): A5 bridge
    expansion, A2/A3 left native, FP16 -> BF16 via FP32, native pair untouched,
    idempotency on an already-bridged chain, factory smoke test, plus the two
    review-driven cases below — a rejected narrowing bridge and the no-backend
    fallback.
  • tests/ut/ir/transforms/test_pass_manager.py: "LegalizeTileCast" added to
    both expected pipeline lists.

On a real Ascend 950 host, all 27 single-card models/deepseek/v4-pro/*.py
from pypto-lib 56b59dd, run serially on one card with ptoas 0.48:

passing
this PR, as pinned (runtime = 8cdb306c) 7 / 27
this PR + the runtime bump described below 26 / 27

This pass is necessary but not sufficient on its own: it unblocks the kernel
compile, after which the remaining kernels fail later in the pipeline for two
reasons that live in the pinned runtime, not here. Of the 20 still failing in the
first row: 15 fail the orchestration compile on set_allow_early_resolve, 4 are
numerical, and 1 is the known-flaky prefill_sparse_attn (AICPU 507018, ~1 pass
in 5, unrelated to this change).

Follow-up: the runtime pin

The two remaining causes are both already fixed on simpler main, after our
pinned 8cdb306c (2026-07-20):

  • simpler#1446 (d4071fe1) enables -mllvm -cce-vf-aa-between-iters=true for
    A5. Without that stricter inter-iteration alias analysis, BiSheng VF fusion
    reorders same-V vector ops (expand / abs / sinkhorn) and silently miscompiles
    ~15 V4-Pro kernels — they build and run and return wrong values (hc_pre's
    comb comes back all-NaN). That is the 4 numerical failures above plus others
    masked behind the orchestration failure.
  • simpler#1423 (13197445) ports allow_early_resolve / early-dispatch to
    the A5 tmr runtime, fixing the 15 orchestration-compile failures.

I originally included that bump here and backed it out: it fails
dist-system-tests, because the same span contains simpler#1436
("simplify NEXT_LEVEL scheduling with explicit targets"), whose new contract is
"the runtime never selects a NEXT_LEVEL worker on the caller's behalf".
pypto's src/codegen/distributed/distributed_codegen.cpp:1087 still emits -1
("you pick") for comm-less dispatches, which the new API rejects:

ValueError: worker must be a non-negative NEXT_LEVEL worker id
TypeError: submit_next_level() missing 1 required keyword-only argument: 'worker'

11 L3 tests fail that way. Porting pypto's L3 dispatch to explicit targets is a
design decision about where a comm-less dispatch should land, and #1436 precedes
#1446 so it cannot be avoided by picking an earlier revision. It belongs in its
own PR, from someone who can exercise the distributed path — the host I used has
no working HCCL. Happy to file the issue if that helps.

Doc numbering

The pass runs between FlattenTileNdTo2D (doc 13) and AutoTileMatmulL0, so per
.claude/rules/pass-doc-ordering.md it takes 14 and the existing 14..42 shift
to 15..43 in both languages. Slot 43 was freed by d64380cb removing
43-insert_comm_fence.md, so the cascade terminates without a collision.

That accounts for most of the diff: 60 renames (29 per language plus the new file),
247 cross-reference updates, the ordering table in the rule itself (numbers, ordinals,
and two in-prose "pass 18" references that now mean 19), and the properties table plus
pipeline list in 00-pass_manager.md in both languages.

Verified: all 392 internal doc links resolve, the pipeline range is contiguous 01..43
with no gaps, en/zh parity holds (82 paired paths), and no 14a reference survives.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026 •

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 471618da-2630-4687-83b2-9729530ff6f1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

LegalizeTileCast adds backend-aware cast-chain legalization, exposes it through C++ and Python, inserts it into optimization pipelines, and adds focused tests. Pass documentation and cross-references are updated for the new pipeline numbering.

Changes

LegalizeTileCast

Layer / File(s) Summary
Cast-chain planning and IR rewrite
src/ir/transforms/legalize_tile_cast_pass.cpp
Selects the backend cast profile, finds safe shortest native cast chains, preserves native casts, and rewrites unsupported casts into sequential assignments.
Public API and pipeline integration
include/pypto/ir/transforms/*, python/bindings/modules/passes.cpp, python/pypto/ir/pass_manager.py, python/pypto/pypto_core/passes.pyi, CMakeLists.txt
Registers pass properties, C++ and Python factories, build sources, and default optimization-pipeline placement.
Validation
tests/ut/ir/transforms/test_legalize_tile_cast.py, tests/ut/ir/transforms/test_pass_manager.py
Covers architecture-specific native and bridged casts, safety rejection, idempotency, backend fallback, factory registration, and pipeline lists.
Pipeline documentation and references
docs/en/dev/passes/*, docs/zh-cn/dev/passes/*, docs/en/dev/codegen/*, docs/zh-cn/dev/codegen/*, docs/en/dev/*, docs/zh-cn/dev/*, docs/en/user/*, docs/zh-cn/user/*, .claude/rules/pass-doc-ordering.md
Adds LegalizeTileCast documentation and updates pass numbering and cross-references through the documented pipeline.
Supporting documentation comments
include/pypto/ir/transforms/utils/attrs.h, tests/ut/ir/transforms/test_materialize_tensor_strides_pass.py
Updates pass-documentation references to the renumbered pages.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PassManager
  participant LegalizeTileCast
  participant BackendHandler
  participant IR
  PassManager->>LegalizeTileCast: run tile.cast legalization
  LegalizeTileCast->>BackendHandler: resolve backend cast profile
  LegalizeTileCast->>LegalizeTileCast: find safe shortest native chain
  LegalizeTileCast->>IR: replace unsupported cast with native assignments
Loading

Poem

I’m a rabbit with casts in a neat little row,
Bridging each dtype wherever they go.
Native ones stay, unsupported ones bend,
Through shortest safe pathways from start to the end.
The pipeline now hops with a carrot-filled cheer!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding the LegalizeTileCast IR pass for unsupported cast pairs.
Description check ✅ Passed The description is detailed and directly matches the PR’s new pass, architecture handling, pipeline placement, and tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lwDavid lwDavid self-assigned this Jul 25, 2026
@lwDavid lwDavid added the bug Something isn't working label Jul 25, 2026
@lwDavid lwDavid moved this to In Progress in pto project Jul 25, 2026
@lwDavid
lwDavid marked this pull request as draft July 25, 2026 08:44

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ff61142f7f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/ir/transforms/legalize_tile_cast_pass.cpp
Comment thread src/ir/transforms/legalize_tile_cast_pass.cpp Outdated
Comment thread docs/en/dev/passes/14-legalize_tile_cast.md
@lwDavid lwDavid changed the title feat(a5): legalize non-native tile.cast chains and gate the early-resolve hint feat(a5): legalize non-native tile.cast chains; bump simpler for A5 fixes Jul 25, 2026
@lwDavid
lwDavid force-pushed the lwDavid/a5-legalize-cast-and-early-resolve-gate branch 4 times, most recently from 8966190 to 1804057 Compare July 25, 2026 10:26
@lwDavid lwDavid changed the title feat(a5): legalize non-native tile.cast chains; bump simpler for A5 fixes feat(ir): add LegalizeTileCast for hardware-unsupported cast pairs Jul 25, 2026
@lwDavid
lwDavid force-pushed the lwDavid/a5-legalize-cast-and-early-resolve-gate branch 5 times, most recently from d0aac23 to 6a362ad Compare July 27, 2026 02:46
@lwDavid

lwDavid commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Conversions this PR newly makes compilable

Each pair below previously lowered to a single pto.tcvt for a (src, dst) the
target ISA does not provide — the same failure mode as the INT32 -> FP16 case in
the description. They now lower to a chain of native casts instead.

(n) = number of tcvt instructions in the emitted chain, so (2) means one
intermediate dtype.

A5 (Ascend950) — 84 pairs

from newly-supported targets
fp32 fp4(2), int8(2), uint16(2), uint8(2)
fp16 fp4(3), bf16(2), fp8e4m3(2), fp8e5m2(2), uint16(2)
bf16 fp8e4m3(2), fp8e5m2(2), hf8(2), int16(2), int8(2), uint16(2), uint8(2)
fp8e4m3 fp4(3), int8(3), uint16(3), uint8(3), bf16(2), fp16(2), fp8e5m2(2), hf8(2), int16(2)
fp8e5m2 fp4(3), int8(3), uint16(3), uint8(3), bf16(2), fp16(2), fp8e4m3(2), hf8(2), int16(2)
hf8 fp4(3), int8(3), uint16(3), uint8(3), bf16(2), fp16(2), fp8e4m3(2), fp8e5m2(2), int16(2)
fp4 fp8e4m3(3), fp8e5m2(3), hf8(3), int8(3), uint8(3)
int64 fp4(3), int8(3), bf16(2), fp16(2), fp8e4m3(2), fp8e5m2(2), hf8(2), int16(2), uint16(2), uint8(2)
int32 fp4(3), int8(3), bf16(2), fp16(2), fp8e4m3(2), fp8e5m2(2), hf8(2)
int16 fp4(3), bf16(2), fp8e4m3(2), fp8e5m2(2), hf8(2), int8(2), uint16(2)
int8 fp4(4), fp8e4m3(3), fp8e5m2(3), hf8(2), uint16(2), uint8(2)
uint32 int8(3)
uint8 fp4(4), fp8e4m3(3), fp8e5m2(3), hf8(2), int8(2)

A2A3 (Ascend910B) — 29 pairs

from newly-supported targets
fp32 int4(2), int8(2), uint8(2)
fp16 bf16(2)
bf16 int4(3), int8(3), uint8(3), fp16(2), int16(2)
int64 int4(3), int8(3), uint8(3), bf16(2), fp16(2), int16(2)
int32 bf16(2), int4(2), int8(2), uint8(2)
int16 bf16(2), int4(2), int8(2), uint8(2)
int8 int4(2), uint8(2)
int4 int8(2), uint8(2)
uint8 int4(2), int8(2)

Example chains (A5):

 int32 -> fp16  :  int32 -> fp32 -> fp16
 int32 -> int8  :  int32 -> fp32 -> fp16 -> int8
  fp16 -> bf16  :  fp16 -> fp32 -> bfloat16

Nothing that already worked changed

The pass only rewrites pairs the arch table marks non-native, so natively-supported
conversions are left byte-identical. I checked the A2A3 table against pto-isa's own
SUPPORTED CONVERSIONS list in include/pto/npu/a2a3/TCvt.hpp — the two sets are
equal, 22 pairs each, with both difference directions empty:

  • ISA-supported but expanded by the pass: 0 (no working conversion was rerouted)
  • claimed native by the pass but absent from the ISA list: 0

In particular int32 -> fp16 stays a single deq instruction on A2A3; only A5, which
lacks that edge, routes it through fp32.

Pairs that remain unreachable are now rejected in pypto with src/dst/arch named,
instead of failing later in the kernel C++ compile.

Measured by running the pass over all ordered pairs of
fp32 fp16 bf16 fp8e4m3 fp8e5m2 hf8 fp4 int64 int32 int16 int8 int4 uint32 uint16 uint8
on each backend and counting the emitted tile.cast ops.

@lwDavid
lwDavid marked this pull request as ready for review July 27, 2026 03:36
@lwDavid lwDavid moved this from In Progress to Done in pto project Jul 27, 2026
@lwDavid
lwDavid requested review from YunjiQin and lyfne123 July 27, 2026 03:36

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6a362ade38

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/ir/transforms/legalize_tile_cast_pass.cpp

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (1)
tests/ut/ir/transforms/test_legalize_tile_cast.py (1)

210-212: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Weak exception-message assertion.

"cast" in str(excinfo.value).lower() is satisfied by almost any exception mentioning casting (including the pass's own name, "LegalizeTileCast"), so this wouldn't actually catch a regression where the narrowing-rejection path breaks and some unrelated exception is raised instead. Consider asserting on a more specific substring from the actual CHECK_SPAN message (e.g. "no native cast path").

♻️ Suggested tightening
     with pytest.raises(Exception) as excinfo:
         _run(Before, BackendType.Ascend950)
-    assert "cast" in str(excinfo.value).lower()
+    assert "no native cast path" in str(excinfo.value).lower()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/ut/ir/transforms/test_legalize_tile_cast.py` around lines 210 - 212,
Strengthen the exception assertion in the test around `_run(Before,
BackendType.Ascend950)` by checking for the specific narrowing-rejection message
from the `CHECK_SPAN` path, such as “no native cast path,” instead of the
generic “cast” substring. Keep the `pytest.raises(Exception)` expectation
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/en/dev/passes/24-normalize_return_order.md`:
- Around line 298-300: Update the pipeline pass ordering documentation near
NormalizeReturnOrder to list SkewCrossCorePipeline as the intervening pass and
identify LowerPipelineLoops as the pass that immediately follows it, while
preserving the existing DeriveCallDirections entry.

In `@docs/zh-cn/dev/codegen/01-orchestration_codegen.md`:
- Around line 110-114: 更新该文档段落及其后续示例,使示例明确反映 MaterializeRuntimeScopes 会为每个
for/if 体插入显式 RuntimeScopeStmt,并由 codegen 生成嵌套 PTO2_SCOPE。确保普通 for 示例不再暗示缺少嵌套
scope,并保持 manual scope 的降级说明一致。

In `@docs/zh-cn/dev/passes/10-convert_tensor_to_tile_ops.md`:
- Line 198: Correct the pass numbering in the paragraph describing
LowerAutoVectorSplit and ExpandMixedKernel: change the LowerAutoVectorSplit
reference to pass 19 and the ExpandMixedKernel reference to pass 20, matching
their linked or renamed documentation pages while leaving the described
transformation unchanged.

In `@docs/zh-cn/dev/passes/15-auto_tile_matmul_l0.md`:
- Line 13: Synchronize the documented pass ordering: in
docs/zh-cn/dev/passes/15-auto_tile_matmul_l0.md:13, state that AutoTileMatmulL0
follows LegalizeTileCast; in docs/en/dev/passes/17-infer_tile_memory_space.md:18
and docs/zh-cn/dev/passes/17-infer_tile_memory_space.md:18, remove the claim
that InferTileMemorySpace immediately follows FlattenTileNdTo2D and reflect the
intervening LegalizeTileCast, AutoTileMatmulL0, and CanonicalizeTileSlice
passes.

In `@docs/zh-cn/dev/passes/20-expand_mixed_kernel.md`:
- Line 56: Synchronize pass-number references in the specified documentation:
update LowerAutoVectorSplit from pass 18 to pass 19 in
docs/zh-cn/dev/passes/20-expand_mixed_kernel.md lines 56 and 172-173,
docs/en/dev/passes/20-expand_mixed_kernel.md lines 79-82 and 207-210, and update
both LowerAutoVectorSplit to 19 and ExpandMixedKernel to 20 in
docs/en/dev/passes/10-convert_tensor_to_tile_ops.md line 207.

In `@docs/zh-cn/dev/passes/33-fold_no_op_reshape.md`:
- Around line 41-42: Synchronize the pipeline-order descriptions by removing or
correcting the contradictory hard-coded ordinals: update FoldNoOpReshape in
docs/zh-cn/dev/passes/33-fold_no_op_reshape.md lines 41-42 and
FuseCreateAssembleToSlice in
docs/zh-cn/dev/passes/34-fuse_create_assemble_to_slice.md line 16. Prefer the
current pipeline indices, or omit numbers and describe the neighboring pass
order using AllocateMemoryAddr and FuseCreateAssembleToSlice.

In `@docs/zh-cn/dev/passes/37-expand_manual_phase_fence.md`:
- Around line 34-35: Update the pipeline diagram near ExpandManualPhaseFence to
include the subsequent MaterializeDistTensorCtx and MaterializeRuntimeScopes
passes after Simplify, and remove the “最终” label from Simplify if it is no
longer the terminal pass. Keep the documented ordering consistent with the
corresponding pass documentation.

---

Nitpick comments:
In `@tests/ut/ir/transforms/test_legalize_tile_cast.py`:
- Around line 210-212: Strengthen the exception assertion in the test around
`_run(Before, BackendType.Ascend950)` by checking for the specific
narrowing-rejection message from the `CHECK_SPAN` path, such as “no native cast
path,” instead of the generic “cast” substring. Keep the
`pytest.raises(Exception)` expectation unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: da74cb6c-c812-43ae-ade5-8c38ba1c47ca

📥 Commits

Reviewing files that changed from the base of the PR and between 2e54cd6 and 6a362ad.

📒 Files selected for processing (90)
  • .claude/rules/pass-doc-ordering.md
  • CMakeLists.txt
  • docs/en/dev/codegen/00-pto_codegen.md
  • docs/en/dev/codegen/01-orchestration_codegen.md
  • docs/en/dev/distributed_ops.md
  • docs/en/dev/ir/02-types.md
  • docs/en/dev/language/00-python_syntax.md
  • docs/en/dev/passes/00-pass_manager.md
  • docs/en/dev/passes/08-outline_incore_scopes.md
  • docs/en/dev/passes/10-convert_tensor_to_tile_ops.md
  • docs/en/dev/passes/14-legalize_tile_cast.md
  • docs/en/dev/passes/15-auto_tile_matmul_l0.md
  • docs/en/dev/passes/16-canonicalize_tile_slice.md
  • docs/en/dev/passes/17-infer_tile_memory_space.md
  • docs/en/dev/passes/18-resolve_backend_op_layouts.md
  • docs/en/dev/passes/19-lower_auto_vector_split.md
  • docs/en/dev/passes/20-expand_mixed_kernel.md
  • docs/en/dev/passes/21-inject_gm_pipe_buffer.md
  • docs/en/dev/passes/22-split_vector_kernel.md
  • docs/en/dev/passes/23-stamp_tfree_split.md
  • docs/en/dev/passes/24-normalize_return_order.md
  • docs/en/dev/passes/25-skew_cross_core_pipeline.md
  • docs/en/dev/passes/26-lower_pipeline_loops.md
  • docs/en/dev/passes/27-canonicalize_io_order.md
  • docs/en/dev/passes/28-materialize_tensor_strides.md
  • docs/en/dev/passes/29-init_memref.md
  • docs/en/dev/passes/30-materialize_semantic_aliases.md
  • docs/en/dev/passes/31-memory_reuse.md
  • docs/en/dev/passes/32-allocate_memory_addr.md
  • docs/en/dev/passes/33-fold_no_op_reshape.md
  • docs/en/dev/passes/34-fuse_create_assemble_to_slice.md
  • docs/en/dev/passes/35-derive_call_directions.md
  • docs/en/dev/passes/36-auto_derive_task_dependencies.md
  • docs/en/dev/passes/37-expand_manual_phase_fence.md
  • docs/en/dev/passes/38-synthesize_allreduce_signals.md
  • docs/en/dev/passes/39-materialize_comm_domain_scopes.md
  • docs/en/dev/passes/40-lower_host_tensor_collectives.md
  • docs/en/dev/passes/41-materialize_dist_tensor_ctx.md
  • docs/en/dev/passes/42-materialize_runtime_scopes.md
  • docs/en/dev/passes/43-classify_iter_arg_carry.md
  • docs/en/user/01-language_guide.md
  • docs/zh-cn/dev/codegen/00-pto_codegen.md
  • docs/zh-cn/dev/codegen/01-orchestration_codegen.md
  • docs/zh-cn/dev/distributed_ops.md
  • docs/zh-cn/dev/ir/02-types.md
  • docs/zh-cn/dev/language/00-python_syntax.md
  • docs/zh-cn/dev/passes/00-pass_manager.md
  • docs/zh-cn/dev/passes/08-outline_incore_scopes.md
  • docs/zh-cn/dev/passes/10-convert_tensor_to_tile_ops.md
  • docs/zh-cn/dev/passes/14-legalize_tile_cast.md
  • docs/zh-cn/dev/passes/15-auto_tile_matmul_l0.md
  • docs/zh-cn/dev/passes/16-canonicalize_tile_slice.md
  • docs/zh-cn/dev/passes/17-infer_tile_memory_space.md
  • docs/zh-cn/dev/passes/18-resolve_backend_op_layouts.md
  • docs/zh-cn/dev/passes/19-lower_auto_vector_split.md
  • docs/zh-cn/dev/passes/20-expand_mixed_kernel.md
  • docs/zh-cn/dev/passes/21-inject_gm_pipe_buffer.md
  • docs/zh-cn/dev/passes/22-split_vector_kernel.md
  • docs/zh-cn/dev/passes/23-stamp_tfree_split.md
  • docs/zh-cn/dev/passes/24-normalize_return_order.md
  • docs/zh-cn/dev/passes/25-skew_cross_core_pipeline.md
  • docs/zh-cn/dev/passes/26-lower_pipeline_loops.md
  • docs/zh-cn/dev/passes/27-canonicalize_io_order.md
  • docs/zh-cn/dev/passes/28-materialize_tensor_strides.md
  • docs/zh-cn/dev/passes/29-init_memref.md
  • docs/zh-cn/dev/passes/30-materialize_semantic_aliases.md
  • docs/zh-cn/dev/passes/31-memory_reuse.md
  • docs/zh-cn/dev/passes/32-allocate_memory_addr.md
  • docs/zh-cn/dev/passes/33-fold_no_op_reshape.md
  • docs/zh-cn/dev/passes/34-fuse_create_assemble_to_slice.md
  • docs/zh-cn/dev/passes/35-derive_call_directions.md
  • docs/zh-cn/dev/passes/36-auto_derive_task_dependencies.md
  • docs/zh-cn/dev/passes/37-expand_manual_phase_fence.md
  • docs/zh-cn/dev/passes/38-synthesize_allreduce_signals.md
  • docs/zh-cn/dev/passes/39-materialize_comm_domain_scopes.md
  • docs/zh-cn/dev/passes/40-lower_host_tensor_collectives.md
  • docs/zh-cn/dev/passes/41-materialize_dist_tensor_ctx.md
  • docs/zh-cn/dev/passes/42-materialize_runtime_scopes.md
  • docs/zh-cn/dev/passes/43-classify_iter_arg_carry.md
  • docs/zh-cn/user/01-language_guide.md
  • include/pypto/ir/transforms/pass_properties.h
  • include/pypto/ir/transforms/passes.h
  • include/pypto/ir/transforms/utils/attrs.h
  • python/bindings/modules/passes.cpp
  • python/pypto/ir/pass_manager.py
  • python/pypto/pypto_core/passes.pyi
  • src/ir/transforms/legalize_tile_cast_pass.cpp
  • tests/ut/ir/transforms/test_legalize_tile_cast.py
  • tests/ut/ir/transforms/test_materialize_tensor_strides_pass.py
  • tests/ut/ir/transforms/test_pass_manager.py

Comment thread docs/en/dev/passes/24-normalize_return_order.md
Comment thread docs/zh-cn/dev/codegen/01-orchestration_codegen.md
Comment thread docs/zh-cn/dev/passes/10-convert_tensor_to_tile_ops.md Outdated
Comment thread docs/zh-cn/dev/passes/15-auto_tile_matmul_l0.md Outdated
Comment thread docs/zh-cn/dev/passes/20-expand_mixed_kernel.md Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 7

🧹 Nitpick comments (1)
tests/ut/ir/transforms/test_legalize_tile_cast.py (1)

210-212: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Weak exception-message assertion.

"cast" in str(excinfo.value).lower() is satisfied by almost any exception mentioning casting (including the pass's own name, "LegalizeTileCast"), so this wouldn't actually catch a regression where the narrowing-rejection path breaks and some unrelated exception is raised instead. Consider asserting on a more specific substring from the actual CHECK_SPAN message (e.g. "no native cast path").

♻️ Suggested tightening
     with pytest.raises(Exception) as excinfo:
         _run(Before, BackendType.Ascend950)
-    assert "cast" in str(excinfo.value).lower()
+    assert "no native cast path" in str(excinfo.value).lower()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/ut/ir/transforms/test_legalize_tile_cast.py` around lines 210 - 212,
Strengthen the exception assertion in the test around `_run(Before,
BackendType.Ascend950)` by checking for the specific narrowing-rejection message
from the `CHECK_SPAN` path, such as “no native cast path,” instead of the
generic “cast” substring. Keep the `pytest.raises(Exception)` expectation
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/en/dev/passes/24-normalize_return_order.md`:
- Around line 298-300: Update the pipeline pass ordering documentation near
NormalizeReturnOrder to list SkewCrossCorePipeline as the intervening pass and
identify LowerPipelineLoops as the pass that immediately follows it, while
preserving the existing DeriveCallDirections entry.

In `@docs/zh-cn/dev/codegen/01-orchestration_codegen.md`:
- Around line 110-114: 更新该文档段落及其后续示例,使示例明确反映 MaterializeRuntimeScopes 会为每个
for/if 体插入显式 RuntimeScopeStmt,并由 codegen 生成嵌套 PTO2_SCOPE。确保普通 for 示例不再暗示缺少嵌套
scope,并保持 manual scope 的降级说明一致。

In `@docs/zh-cn/dev/passes/10-convert_tensor_to_tile_ops.md`:
- Line 198: Correct the pass numbering in the paragraph describing
LowerAutoVectorSplit and ExpandMixedKernel: change the LowerAutoVectorSplit
reference to pass 19 and the ExpandMixedKernel reference to pass 20, matching
their linked or renamed documentation pages while leaving the described
transformation unchanged.

In `@docs/zh-cn/dev/passes/15-auto_tile_matmul_l0.md`:
- Line 13: Synchronize the documented pass ordering: in
docs/zh-cn/dev/passes/15-auto_tile_matmul_l0.md:13, state that AutoTileMatmulL0
follows LegalizeTileCast; in docs/en/dev/passes/17-infer_tile_memory_space.md:18
and docs/zh-cn/dev/passes/17-infer_tile_memory_space.md:18, remove the claim
that InferTileMemorySpace immediately follows FlattenTileNdTo2D and reflect the
intervening LegalizeTileCast, AutoTileMatmulL0, and CanonicalizeTileSlice
passes.

In `@docs/zh-cn/dev/passes/20-expand_mixed_kernel.md`:
- Line 56: Synchronize pass-number references in the specified documentation:
update LowerAutoVectorSplit from pass 18 to pass 19 in
docs/zh-cn/dev/passes/20-expand_mixed_kernel.md lines 56 and 172-173,
docs/en/dev/passes/20-expand_mixed_kernel.md lines 79-82 and 207-210, and update
both LowerAutoVectorSplit to 19 and ExpandMixedKernel to 20 in
docs/en/dev/passes/10-convert_tensor_to_tile_ops.md line 207.

In `@docs/zh-cn/dev/passes/33-fold_no_op_reshape.md`:
- Around line 41-42: Synchronize the pipeline-order descriptions by removing or
correcting the contradictory hard-coded ordinals: update FoldNoOpReshape in
docs/zh-cn/dev/passes/33-fold_no_op_reshape.md lines 41-42 and
FuseCreateAssembleToSlice in
docs/zh-cn/dev/passes/34-fuse_create_assemble_to_slice.md line 16. Prefer the
current pipeline indices, or omit numbers and describe the neighboring pass
order using AllocateMemoryAddr and FuseCreateAssembleToSlice.

In `@docs/zh-cn/dev/passes/37-expand_manual_phase_fence.md`:
- Around line 34-35: Update the pipeline diagram near ExpandManualPhaseFence to
include the subsequent MaterializeDistTensorCtx and MaterializeRuntimeScopes
passes after Simplify, and remove the “最终” label from Simplify if it is no
longer the terminal pass. Keep the documented ordering consistent with the
corresponding pass documentation.

---

Nitpick comments:
In `@tests/ut/ir/transforms/test_legalize_tile_cast.py`:
- Around line 210-212: Strengthen the exception assertion in the test around
`_run(Before, BackendType.Ascend950)` by checking for the specific
narrowing-rejection message from the `CHECK_SPAN` path, such as “no native cast
path,” instead of the generic “cast” substring. Keep the
`pytest.raises(Exception)` expectation unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: da74cb6c-c812-43ae-ade5-8c38ba1c47ca

📥 Commits

Reviewing files that changed from the base of the PR and between 2e54cd6 and 6a362ad.

📒 Files selected for processing (90)
  • .claude/rules/pass-doc-ordering.md
  • CMakeLists.txt
  • docs/en/dev/codegen/00-pto_codegen.md
  • docs/en/dev/codegen/01-orchestration_codegen.md
  • docs/en/dev/distributed_ops.md
  • docs/en/dev/ir/02-types.md
  • docs/en/dev/language/00-python_syntax.md
  • docs/en/dev/passes/00-pass_manager.md
  • docs/en/dev/passes/08-outline_incore_scopes.md
  • docs/en/dev/passes/10-convert_tensor_to_tile_ops.md
  • docs/en/dev/passes/14-legalize_tile_cast.md
  • docs/en/dev/passes/15-auto_tile_matmul_l0.md
  • docs/en/dev/passes/16-canonicalize_tile_slice.md
  • docs/en/dev/passes/17-infer_tile_memory_space.md
  • docs/en/dev/passes/18-resolve_backend_op_layouts.md
  • docs/en/dev/passes/19-lower_auto_vector_split.md
  • docs/en/dev/passes/20-expand_mixed_kernel.md
  • docs/en/dev/passes/21-inject_gm_pipe_buffer.md
  • docs/en/dev/passes/22-split_vector_kernel.md
  • docs/en/dev/passes/23-stamp_tfree_split.md
  • docs/en/dev/passes/24-normalize_return_order.md
  • docs/en/dev/passes/25-skew_cross_core_pipeline.md
  • docs/en/dev/passes/26-lower_pipeline_loops.md
  • docs/en/dev/passes/27-canonicalize_io_order.md
  • docs/en/dev/passes/28-materialize_tensor_strides.md
  • docs/en/dev/passes/29-init_memref.md
  • docs/en/dev/passes/30-materialize_semantic_aliases.md
  • docs/en/dev/passes/31-memory_reuse.md
  • docs/en/dev/passes/32-allocate_memory_addr.md
  • docs/en/dev/passes/33-fold_no_op_reshape.md
  • docs/en/dev/passes/34-fuse_create_assemble_to_slice.md
  • docs/en/dev/passes/35-derive_call_directions.md
  • docs/en/dev/passes/36-auto_derive_task_dependencies.md
  • docs/en/dev/passes/37-expand_manual_phase_fence.md
  • docs/en/dev/passes/38-synthesize_allreduce_signals.md
  • docs/en/dev/passes/39-materialize_comm_domain_scopes.md
  • docs/en/dev/passes/40-lower_host_tensor_collectives.md
  • docs/en/dev/passes/41-materialize_dist_tensor_ctx.md
  • docs/en/dev/passes/42-materialize_runtime_scopes.md
  • docs/en/dev/passes/43-classify_iter_arg_carry.md
  • docs/en/user/01-language_guide.md
  • docs/zh-cn/dev/codegen/00-pto_codegen.md
  • docs/zh-cn/dev/codegen/01-orchestration_codegen.md
  • docs/zh-cn/dev/distributed_ops.md
  • docs/zh-cn/dev/ir/02-types.md
  • docs/zh-cn/dev/language/00-python_syntax.md
  • docs/zh-cn/dev/passes/00-pass_manager.md
  • docs/zh-cn/dev/passes/08-outline_incore_scopes.md
  • docs/zh-cn/dev/passes/10-convert_tensor_to_tile_ops.md
  • docs/zh-cn/dev/passes/14-legalize_tile_cast.md
  • docs/zh-cn/dev/passes/15-auto_tile_matmul_l0.md
  • docs/zh-cn/dev/passes/16-canonicalize_tile_slice.md
  • docs/zh-cn/dev/passes/17-infer_tile_memory_space.md
  • docs/zh-cn/dev/passes/18-resolve_backend_op_layouts.md
  • docs/zh-cn/dev/passes/19-lower_auto_vector_split.md
  • docs/zh-cn/dev/passes/20-expand_mixed_kernel.md
  • docs/zh-cn/dev/passes/21-inject_gm_pipe_buffer.md
  • docs/zh-cn/dev/passes/22-split_vector_kernel.md
  • docs/zh-cn/dev/passes/23-stamp_tfree_split.md
  • docs/zh-cn/dev/passes/24-normalize_return_order.md
  • docs/zh-cn/dev/passes/25-skew_cross_core_pipeline.md
  • docs/zh-cn/dev/passes/26-lower_pipeline_loops.md
  • docs/zh-cn/dev/passes/27-canonicalize_io_order.md
  • docs/zh-cn/dev/passes/28-materialize_tensor_strides.md
  • docs/zh-cn/dev/passes/29-init_memref.md
  • docs/zh-cn/dev/passes/30-materialize_semantic_aliases.md
  • docs/zh-cn/dev/passes/31-memory_reuse.md
  • docs/zh-cn/dev/passes/32-allocate_memory_addr.md
  • docs/zh-cn/dev/passes/33-fold_no_op_reshape.md
  • docs/zh-cn/dev/passes/34-fuse_create_assemble_to_slice.md
  • docs/zh-cn/dev/passes/35-derive_call_directions.md
  • docs/zh-cn/dev/passes/36-auto_derive_task_dependencies.md
  • docs/zh-cn/dev/passes/37-expand_manual_phase_fence.md
  • docs/zh-cn/dev/passes/38-synthesize_allreduce_signals.md
  • docs/zh-cn/dev/passes/39-materialize_comm_domain_scopes.md
  • docs/zh-cn/dev/passes/40-lower_host_tensor_collectives.md
  • docs/zh-cn/dev/passes/41-materialize_dist_tensor_ctx.md
  • docs/zh-cn/dev/passes/42-materialize_runtime_scopes.md
  • docs/zh-cn/dev/passes/43-classify_iter_arg_carry.md
  • docs/zh-cn/user/01-language_guide.md
  • include/pypto/ir/transforms/pass_properties.h
  • include/pypto/ir/transforms/passes.h
  • include/pypto/ir/transforms/utils/attrs.h
  • python/bindings/modules/passes.cpp
  • python/pypto/ir/pass_manager.py
  • python/pypto/pypto_core/passes.pyi
  • src/ir/transforms/legalize_tile_cast_pass.cpp
  • tests/ut/ir/transforms/test_legalize_tile_cast.py
  • tests/ut/ir/transforms/test_materialize_tensor_strides_pass.py
  • tests/ut/ir/transforms/test_pass_manager.py
🛑 Comments failed to post (2)
docs/zh-cn/dev/passes/33-fold_no_op_reshape.md (1)

41-42: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Synchronize the hard-coded Default pipeline ordinals.

The two adjacent pass pages assign contradictory numbers after renumbering:

  • docs/zh-cn/dev/passes/33-fold_no_op_reshape.md#L41-L42: replace or correct the FoldNoOpReshape pass-29 claim.
  • docs/zh-cn/dev/passes/34-fuse_create_assemble_to_slice.md#L16-L16: replace or correct the FuseCreateAssembleToSlice pass-27 claim.

Prefer the current pipeline index or omit ordinal numbers and link the neighboring stages.

📍 Affects 2 files
  • docs/zh-cn/dev/passes/33-fold_no_op_reshape.md#L41-L42 (this comment)
  • docs/zh-cn/dev/passes/34-fuse_create_assemble_to_slice.md#L16-L16
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/zh-cn/dev/passes/33-fold_no_op_reshape.md` around lines 41 - 42,
Synchronize the pipeline-order descriptions by removing or correcting the
contradictory hard-coded ordinals: update FoldNoOpReshape in
docs/zh-cn/dev/passes/33-fold_no_op_reshape.md lines 41-42 and
FuseCreateAssembleToSlice in
docs/zh-cn/dev/passes/34-fuse_create_assemble_to_slice.md line 16. Prefer the
current pipeline indices, or omit numbers and describe the neighboring pass
order using AllocateMemoryAddr and FuseCreateAssembleToSlice.
docs/zh-cn/dev/passes/37-expand_manual_phase_fence.md (1)

34-35: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Include the post-Expand passes in the pipeline diagram.

Lines [34-35] stop at Simplify(最终), but the documented pipeline continues through MaterializeDistTensorCtx and MaterializeRuntimeScopes. This contradicts docs/zh-cn/dev/passes/41-materialize_dist_tensor_ctx.md and docs/zh-cn/dev/passes/42-materialize_runtime_scopes.md; either extend the diagram or remove the “final” label.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/zh-cn/dev/passes/37-expand_manual_phase_fence.md` around lines 34 - 35,
Update the pipeline diagram near ExpandManualPhaseFence to include the
subsequent MaterializeDistTensorCtx and MaterializeRuntimeScopes passes after
Simplify, and remove the “最终” label from Simplify if it is no longer the
terminal pass. Keep the documented ordering consistent with the corresponding
pass documentation.

@lwDavid
lwDavid force-pushed the lwDavid/a5-legalize-cast-and-early-resolve-gate branch 3 times, most recently from daa9bd5 to 2c8ea49 Compare July 27, 2026 06:17
@lwDavid
lwDavid requested a review from Hzfengsy July 27, 2026 06:46
@lwDavid
lwDavid force-pushed the lwDavid/a5-legalize-cast-and-early-resolve-gate branch 2 times, most recently from 49cb757 to f953ce9 Compare July 27, 2026 07:44
pto.tcvt only supports a profile-dependent subset of (src, dst) dtype
pairs, and the subset differs per architecture. A5 (Ascend950) has no
native INT32 -> FP16 conversion, while A2/A3 provides it as a deq
instruction. A DSL-level pl.cast(x_i32, pl.FP16) therefore compiles fine
on A2/A3 but fails the kernel C++ compile on A5 with

    error: no matching function for call to 'castData_2D_NoPostUpdate'

LegalizeTileCast rewrites each non-native tile.cast into the shortest
chain of native casts, found by BFS over a per-arch ISA adjacency table
(A5 INT32 -> FP16 becomes INT32 -> FP32 -> FP16). Among equal-length
paths it prefers "same byte-width -> float, then adjust width". Casts
that are already native, including FIXPIPE-foldable FP32 -> BF16/FP16
with mode=rint, are left untouched. It runs after FlattenTileNdTo2D so
casts inserted by that pass are legalized too, and before
AutoTileMatmulL0.

This unblocks the DeepSeek V4-Pro INT8 quantization chain
(fp32 -> i32(rint) -> fp16(round) -> i8(trunc)) on A5, which is used at
14 sites across the model and previously could not be compiled at all.

Shortest is not sufficient on its own: an intermediate that cannot hold
what the destination can would silently drop values a direct conversion
would have kept. A5 has no native UINT32 -> FP32, and the shortest
routes all pass through INT16 / UINT16 / UINT8, so 40000 -- exactly
representable in FP32 -- would come back as garbage. Intermediates that
provably narrow relative to the destination are therefore excluded from
the search, and a pair with no faithful chain is reported as an error
rather than lowered to a lossy one. The check only rejects what it can
prove, so an unfamiliar dtype stays admissible instead of turning a
working lowering into a hard failure.

The native-conversion table is a backend fact, so it lives on
BackendHandler as GetTcvtAdjacency() and each handler owns its own --
per pass-context-config.md, passes never branch on the backend. The pass
resolves the handler from the PassContext, falling back to the global
BackendConfig, and holds no architecture knowledge of its own: a new
backend ships a table and this pass is unchanged. Previously it carried a
private CastArch enum recovered from GetPtoTargetArch() string equality,
which silently gave any unrecognised arch the A2A3 table -- and A2A3 has
edges A5 lacks, so a third backend would have seen casts wrongly declared
native and failed in the kernel C++ compile, the exact error this pass
exists to prevent.

With no backend configured the pass is now a no-op rather than defaulting
to A2A3; several codegen tests drive passes without one, and leaving the
IR untouched is the honest behaviour when there is no table to legalize
against. Both lookups CHECK-fail when no backend is set, so
BackendConfig::IsConfigured() is probed first.

The move is behaviour-preserving: over all 420 (arch, src, dst) triples
across both backends and 15 dtypes, the emitted chain is identical before
and after.

Docs are numbered by execution order per pass-doc-ordering.md. The pass runs
between FlattenTileNdTo2D (13) and AutoTileMatmulL0, so it takes 14 and the
existing 14..42 shift to 15..43 in both languages; slot 43 was freed by
d64380c removing 43-insert_comm_fence.md, so the cascade terminates without a
collision. Cross-references, the rule's own ordering table, and the
00-pass_manager.md properties table and pipeline list are updated to match, as
are pass numbers written out in prose and the adjacency claims that inserting a
pass between FlattenTileNdTo2D and AutoTileMatmulL0 invalidates.

The pass docs carry per-architecture tables of which (src, dst) pairs are a
single hardware tcvt and which expand into a chain, with the hop count, so the
performance and rounding consequences are visible to users rather than implicit.
A chain is bit-identical to a direct conversion only when each intermediate is
exact over the destination's range, so the cases that do double-round are named
with their measured rate. The language guide points at the tables from pl.cast.
@lwDavid
lwDavid force-pushed the lwDavid/a5-legalize-cast-and-early-resolve-gate branch from f953ce9 to 270cd81 Compare July 27, 2026 08:24
@lwDavid lwDavid added enhancement New feature or request and removed bug Something isn't working labels Jul 27, 2026
@lyfne123
lyfne123 merged commit 525f1b6 into hw-native-sys:main Jul 28, 2026
12 checks passed
YunjiQin pushed a commit that referenced this pull request Jul 30, 2026
…-level CommRemoteOffset helper (#2168)

> **Rebased onto `main` (`a55399d4`); #2135 is closed, so its 5
toolchain commits (`update PTOAS to v0.51` … `fix(runtime): Keep the
_ptoas_binary seam`) now land here.** The PTOAS pin moved on to
**v0.54** in the process.

## What

1. Pin PTOAS **v0.54**.
2. Reland the `InsertCommFence` pass (reverts #2138).
3. Collapse the `system.cacheinvalid` codegen down to a single path.
4. Restore the module-level `@CommRemoteOffset_<dtype>` helper that
#2161 inlined.

## 1. PTOAS v0.54

`toolchain/versions.env` is the single source of truth — every workflow
reads it through the `toolchain` lead job, so the bump needs no workflow
change. Both digests were verified by downloading the release assets and
hashing them locally:

| Arch | sha256 |
| ---- | ------ |
| aarch64 |
`011e980dbc46c796e31a1b213051d943ba8eb4c67d356ae6bda2148fe512964a` |
| x86_64 |
`4e3acb9623384c18fe264610525777210095f2ba24f6c52b9823bf1cb81d7a99` |

The release tarball layout changed at v0.51 (`<root>/ptoas` went from an
executable launcher script to a Python package directory).
`python/pypto/backend/_ptoas_locate.py` probes each candidate for being
an executable file rather than keying off the release version, so it is
version-agnostic and needed no change for v0.52 or v0.54.

## 2. Reland InsertCommFence (reverts #2138)

#2138 backed out #2076 because ptoas could not lower the publish-side
region `cacheinvalid`:

| ptoas | `pto.cmo.cacheinvalid <partition_tensor_view>
single_cache_line` |
| ----- |
--------------------------------------------------------------- |
| 0.50 | parsed, but **no call emitted** — the marker never reached the
device |
| 0.51 | emitted `PTOAS__DCCI_SINGLE_CACHE_LINE(<GlobalTensor>)`, whose
body casts to `__gm__ void*` — a conversion `GlobalTensor` does not
have, so every kernel carrying the op failed to compile |

hw-native-sys/PTOAS#1001 fixes this with a `GlobalTensor` overload that
takes the address via `tensor.data()`, shipped in v0.52 and carried by
the v0.54 pin. Verified against the real 0.52 binary — the emitted C++
now carries both overloads and binds the object one:

```cpp
static AICORE inline void PTOAS__DCCI_SINGLE_CACHE_LINE(
    pto::GlobalTensor<Element, Shape, Stride, TensorLayout> &tensor) {
  dcci((__gm__ void*)tensor.data(), cache_line_t::SINGLE_CACHE_LINE);
}
```

This is a plain revert of #2138 except for three deliberate deviations:

- `toolchain/versions.env` stays on this branch's pin (now v0.54); the
revert's restore of the v0.50 pin is dropped.
- The pass doc is renumbered **43 → 44**. #2141 landed
`LegalizeTileCast` at slot 14 and shifted everything below it, so
`classify_iter_arg_carry` now owns 43 and `InsertCommFence` — still dead
last in the pipeline — takes 44.
- The revert's CommRemoteOffset inlining is **not** relanded, because
#2161 landed the same inlining on `main` independently. See §4 — this PR
takes that emission the other way.

`44-insert_comm_fence.md` is wired into the mkdocs nav,
`passes/index.md` and `00-pass_manager.md` (en + zh). The docs became an
MkDocs site in #2193, and `mkdocs build --strict` fails on a page absent
from the nav; `docs/zh-cn/` was also renamed to `docs/zh/` there, which
the rebase picked up.

## 3. Route every `cacheinvalid` region through `partition_view`

The scalar-write branch of `system.cacheinvalid` codegen emitted a bare
pointer operand. Measured against ptoas 0.52:

| operand | result |
| ------- | ------ |
| `!pto.ptr`, no type annotation ← **what we emitted** | parse error:
`expected ':'` |
| `!pto.ptr`, with type annotation | lowering: `addptr must feed
make_tensor_view, ...` |
| `!pto.tensor_view<1xf32>` | compiles |
| `!pto.partition_tensor_view<1x1xf32>` | compiles |

So that branch has never produced working code. It is reachable from the
DSL (`pl.system.cacheinvalid(t, [1, 1], off)`) and, with this reland,
from `InsertCommFence` too — `MakeCacheInvalid` uses the target's full
shape, so any published tensor that is itself 1x1 lands there.

The special case only existed because the region path was broken on
ptoas <= 0.51. Now that it lowers correctly, a 1x1 `partition_view` is
right: verified on the real binary, a `[1, 1]` region at offsets `[0,
8]` over a `[16, 16]` f32 tensor emits `GlobalTensor<float,
Shape<1,1,1,1,1>, Stride<16,16,16,16,1>>` at `v1 + 8`. End-to-end,
pypto's own generated `.pto` for that case now compiles where it
previously hit the parse error.

The branch is therefore deleted — one construction, one emit site.
`GetFlatOffsetSSA` and `GetTensorBasePtr` remain in use elsewhere, so
nothing is orphaned.

**This supersedes #2137**, which fixes the same bug by routing the
scalar case through `tensor_view<1xT>` and converging the two branches
on a shared emit. Both work on 0.52; this one removes the branch
entirely. Closing one of the two is a call for the authors — see "Open
questions".

`test_cacheinvalid_scalar_write_emits_ptr` asserted the broken form
(`"partition_tensor_view" not in cmo_line`) and could not survive the
fix as written. It is folded into a parametrized
`test_cacheinvalid_region_emits_partition_view` covering both sizes; the
dynamic-offset test now asserts the partition-view operand instead of
`pto.addptr`.

## 4. Restore the module-level CommRemoteOffset helper

#2161 ("Complete arbitrary-length allreduce support") inlined the
distributed peer-address calculation at every remote-op call site and
deleted the per-dtype `@CommRemoteOffset_<dtype>` helper. Its stated
reason was that ptoas' `pto-memory-consistency` pass rejected a
`func.call` to a callee holding the CommContext `pto.load_scalar` reads.

**ptoas no longer performs that check**, so the helper form is viable
again, and this PR restores it:

- Op lowering registers the dtype via
`PTOCodegen::RegisterCommRemoteOffsetHelper` and emits one `func.call
@CommRemoteOffset_<dtype>(ctx, peer) -> index`.
- `EmitCommRemoteOffsetHelpers` flushes one `func.func private` body per
registered dtype at module end; MLIR resolves the forward references
whole-module.
- Sharing the CommContext field reads and the byte→element division
across call sites keeps the emitted kernels smaller than the inlined
form.

`pto.addptr` and `pto.make_tensor_view` stay at the call site, as they
did both before and after #2161 — PTOAS verifies per-function that
`addptr` feeds `make_tensor_view`, and a tensor view cannot cross a func
boundary because its lowered memref is strided. Returning the element
offset is the only shape that satisfies both constraints.

**Everything else from #2161 is preserved**: ragged-tail handling, DN /
column-vector stride derivation, the tightened valid-shape inference,
and the `allow_physical_tail_padding` attr on `pld.tile.remote_load`.
Docs (en/zh), op descriptions, DSL docstrings, binding comments and the
codegen unit-test assertions move back to the helper wording in
lockstep.

## 5. Document that ptoas does not gate the markers

The pass doc claimed that removing the wait-side `cacheinvalid all` from
the ring-allreduce `.pto` "is rejected". That was measured on 0.50 and
no longer holds. Re-run on 0.52 against the same `ring_step` kernel from
`tests/st/distributed/collectives/test_l3_allreduce_ring.py`:

| variant | ptoas 0.52 |
| ------- | ---------- |
| unmodified | accepted |
| wait-side `cacheinvalid all` removed | accepted |
| every `cacheinvalid` **and** `system.fence` removed | accepted |

All three compile with no diagnostic; the instructions are simply absent
from the generated C++. The doc now qualifies the original claim with
the version it was measured on and adds a section stating there is no
compile-time gate — a missing marker is a data race, not a build error —
with an explicit warning against reading a green test run as evidence a
marker is unnecessary.

## Testing

- [x] Full unit suite on the rebased tree: `pytest tests/ut/ -n auto` →
**8116 passed, 2 skipped**
- [x] One pre-existing failure unrelated to this branch:
`test_benchmark.py::test_benchmark_l3_ignores_prepare_setup_groups` —
the test double's `prepare()` predates the `persistent=` kwarg added by
#2163; `bench.py` and that test file are byte-identical to `main` here
- [x] `tests/ut/ir/transforms/test_insert_comm_fence.py` +
`tests/ut/codegen/distributed/` → 95 passed
- [x] `ruff check` / `ruff format --check` / `clang-format --dry-run
--Werror` clean on the diff
- [x] Docs updated en + zh; new pass doc wired into the nav and both
indices
- [x] The ptoas claims in §2/§3/§5 were measured against the real v0.52
binary, not inferred
- [ ] **`dist-system-tests` not run** — needs a 2-device host. This is
the meaningful acceptance gate: those 36 cases are what #2138 backed the
pass out for, and they are also what would exercise the restored
`func.call` form from §4 on device.

## Open questions

1. **Overlap with #2137.** Same bug, same file, two fixes. One should
close.
2. **The pass's benefit is inferred, not observed.** Under the 0.50 pin
the publish-side region `cacheinvalid` emitted no call at all
(hw-native-sys/PTOAS#995), so it never reached the device — and the
distributed suite was green throughout. Combined with §5 (ptoas does not
check for the markers), no existing test demonstrates what this pass
fixes. Confirming it needs a case built to expose the race — large
transfers, multiple ranks, repeated runs.

Relates to #2076, #2138, #2161, hw-native-sys/PTOAS#995,
hw-native-sys/PTOAS#1001
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants