Skip to content

fix(dsl): Reject cross-path kwargs in unified ops instead of dropping them - #2267

Merged
Hzfengsy merged 2 commits into
hw-native-sys:mainfrom
Hzfengsy:issue-2264-reject-cross-path-kwargs
Aug 4, 2026
Merged

fix(dsl): Reject cross-path kwargs in unified ops instead of dropping them#2267
Hzfengsy merged 2 commits into
hw-native-sys:mainfrom
Hzfengsy:issue-2264-reject-cross-path-kwargs

Conversation

@Hzfengsy

@Hzfengsy Hzfengsy commented Aug 3, 2026

Copy link
Copy Markdown
Member

Fixes #2264

Problem

The unified pl.* wrappers dispatch on operand type and accept the union of both levels' kwargs. A kwarg that only the other path could honour was silently discarded — no error, no warning, no IR trace.

For pl.matmul this is a correctness bug. pl.matmul(tile_a, tile_b, b_trans=True) compiled A @ B:

  • With a square B the shapes stayed consistent and the kernel computed the wrong math cleanly.
  • With a non-square B it failed deep in C++ type deduction (lhs K=512 and rhs K=128) without ever naming b_trans.

That is how it was found in the wild — a kernel whose matmul operands were migrated from Tensor to Tile (pl.create_l1pl.create_tile, tensor slice → pl.load) while the pl.matmul(..., b_trans=True) line was left untouched.

This is correct IR design, not an IR bug. A tensor value carries no layout, so a flag is the only place transposition can live; at tile level it is a type property expressed by the zero-copy tile.transpose_view, and tile.matmul maps 1:1 onto pto.tmatmul, which has no transpose attribute. PRs #1776#1866#1883 deliberately removed transpose parameters from the tile layer. The defect was that the frontend accepted a request it could not honour and stayed silent — the @overload declarations already encoded the right contract for matmul/matmul_acc; only the runtime failed to enforce it.

Change

Twelve silent-drop sites now raise, each only on a non-default value — spelling out b_trans=False stays a no-op:

Site Dropped kwarg New behaviour
matmul / matmul_acc a_trans, b_trans, c_matrix_nz Raise on the Tile path, pointing at pl.tile.transpose_view
matmul out_dtype Verified, not dropped — see below
rsqrt high_precision Raise on the Tile path; tile.rsqrt selects precision by taking a scratch tile, not by an attribute
9 reductions tmp_tile Raise on the Tensor path

Reductions covered: row_max, row_sum, row_min, row_prod, col_sum, row_argmax, row_argmin, col_argmax, col_argmin. For col_sum the drop was strategy-meaningful — the requested binary-tree reduction (O(log M) depth, better FP accumulation) was discarded and the scratch tile allocated but never used. For the other eight the conversion pass synthesizes its own scratch, so results matched, but the user's tile was silently dead while still consuming UB budget. The in-file comment already claimed this family rejected it.

Ten functions that had no @overload declarations gain the split, so the static contract matches the runtime one. The existing _reject_tmp_for_tensor helper moves to a shared-guards section and takes the parameter name so the reductions reuse it for tmp_tile.

out_dtype is verified rather than rejected outright

tile.matmul hardcodes its result dtype — FP32 for float operands, INT32 for int (src/ir/op/tile_ops/matmul.cpp:88-89) — in a 4-byte-fractal Acc (L0C) tile. The hardware has no FP16 matmul output.

Tensor-level out_dtype=FP16 compiles because the narrowing is absorbed downstream, not by the matmul. The conversion rule discards the kwarg outright ((void)kwargs;, src/ir/transforms/op_conversion_registry.cpp:1067); out_dtype only sets the declared tensor type, and the conversion rides on the consuming store:

```python
t__tile: pl.Tile[[32, 128], pl.FP32, pl.Mem.Acc] = pl.tile.matmul(a_mat, b_mat) # always FP32
ret0__store: pl.Tensor[[32, 128], pl.FP16] = pl.tile.store(t__tile, [0, 0], ret0__out)
```

So on the Tile path a matching out_dtype is a harmless redundant assertion and a mismatched one is unfulfillable. The guard compares against the dtype read off the built call rather than re-deriving the C++ rule in Python (which would silently rot), accepts the match, and raises otherwise naming `pl.cast`.

Auto-inserting a `tile.cast` was considered and rejected: cast is a Vec-unit op while matmul is Cube, so it would hide a real instruction plus a UB buffer behind a kwarg, against the tile layer's explicit-ops design.

Before / after

```python
at = pl.load(a, [0, 0], [32, 128], target_memory=pl.MemorySpace.Mat)
bt = pl.load(b, [0, 0], [128, 128], target_memory=pl.MemorySpace.Mat)
c = pl.matmul(at, bt, b_trans=True, out_dtype=pl.FP32)
```

Before — no diagnostic at any stage, wrong math:

```python
c: pl.Tile[[32, 128], pl.FP32, pl.Mem.Acc] = pl.tile.matmul(at, bt)
```

After:

```text
InvalidOperationError: pl.matmul: 'b_trans' is not supported for Tile operands.
At tile level a transposed operand is an explicit zero-copy view, not an op flag:
wrap the operand with pl.tile.transpose_view(...) and pass it directly.
```

The suggested remedy compiles and yields the correct `[32, 128]` result:

```python
c = pl.matmul(at, pl.tile.transpose_view(bt), out_dtype=pl.FP32)

-> c: pl.Tile[[32, 128], pl.FP32, pl.Mem.Acc] = pl.tile.matmul(at, pl.tile.transpose_view(bt))

```

Compatibility

An AST sweep of `tests/`, `examples/`, and `python/` found only two Tile-path sites passing a Tensor-only kwarg — `tests/st/runtime/ops/test_matmul.py:700,703`, both `out_dtype=pl.FP32` on BF16 operands, i.e. exactly the dtype the accumulator deduces. Both keep working unchanged; I built that kernel directly to confirm it still lowers identically. The other 121 matching call sites are Tensor-path.

Tests

  • `TestUnifiedOpsCrossPathKwargs` in `tests/ut/language/test_unified_ops.py` — 33 tests covering every guard, the explicit-defaults no-op path, the matching/mismatched `out_dtype` split, and the Tensor path staying byte-identical to the explicit `pl.tensor.*` IR.
  • Parser-level regression test in `tests/ut/language/parser/test_op_validation.py` reproducing the issue's Repro 1, asserting the message names `b_trans` and `transpose_view` and that the call-site span propagates.

Full unit suite: 8638 passed. Docs updated in both `docs/en` and `docs/zh` with the per-path signatures and the general rule (and a previously missing `row_min` row).

Scope

Silent-drop class only, per the issue's own "Suggested shape of the fix". Deliberately not included, each deserving its own review: the dead-end-to-end `c_matrix_nz` attr, the tensor-vs-tile `out_dtype` default divergence, and the five positional-misbind signature divergences (`random`, `ir.transpose`, `ir.scatter_mask`, `scatter`/`gather`, `mrgsort`).

Copilot AI review requested due to automatic review settings August 3, 2026 11:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Aug 3, 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: b2fe0dc8-db81-4f81-adb1-5393048bcf19

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

The unified operation layer now validates Tensor- and Tile-specific keyword arguments instead of silently ignoring them. Matrix operations, reductions, arg-reductions, and rsqrt expose separate overloads. English and Chinese references document the updated contracts, and tests cover rejection and valid defaults.

Changes

Unified operation contracts

Layer / File(s) Summary
Dispatch and matrix operation validation
python/pypto/language/op/unified_ops.py, tests/ut/language/parser/test_op_validation.py, tests/ut/language/test_unified_ops.py
Tile matrix operations and rsqrt reject unsupported Tensor-only options. Tile matmul validates out_dtype. Tests cover errors, defaults, and Tensor behavior.
Reduction scratch-buffer validation
python/pypto/language/op/unified_ops.py, tests/ut/language/test_unified_ops.py
Reduction and arg-reduction overloads distinguish Tensor and Tile scratch-buffer handling. Tests cover rejection and Tile col_sum strategy selection.
Tensor and Tile API documentation
docs/en/user/02-operation_reference.md, docs/zh/user/02-operation_reference.md
Both operation references document separate overloads, path-specific arguments, scratch-buffer requirements, dtype rules, and TypeError behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related issues

Possibly related PRs

Poem

A rabbit checks each kwarg in line,
No hidden drops, no tricks malign.
Tiles keep scratch and dtypes true,
Tensors retain their flags anew.
Clear errors hop where silence ends—
The API and tests are friends.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: rejecting unsupported cross-path kwargs in unified operations.
Description check ✅ Passed The description directly explains the silent-kwarg bug, implemented validation, tests, documentation, and scope.
Linked Issues check ✅ Passed The PR satisfies issue #2264 by adding runtime guards, preserving defaults, aligning overloads, and providing actionable diagnostics.
Out of Scope Changes check ✅ Passed The code, tests, and documentation changes support issue #2264, while unrelated signature and dtype issues remain excluded.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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.

@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: 8af9826af4

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread python/pypto/language/op/unified_ops.py Outdated
Hzfengsy added a commit to Hzfengsy/pypto that referenced this pull request Aug 3, 2026
Keep the documented default values accepted by the overloads.

The new Tile / Tensor overloads omitted the cross-path kwarg entirely, so a
caller spelling out the documented default — `pl.rsqrt(tile,
high_precision=False)`, `pl.row_max(tensor, None)` — was rejected statically
even though the runtime guard and the docs both promise it stays a no-op.

Declare those parameters with `Literal[False]` / `None` defaults on the path
that cannot honour them, so the static contract matches the runtime exactly:
the default type-checks, a non-default value is a static error as well as a
runtime one. Verified with a pyright probe — all seven documented-default calls
pass, all five non-default calls error.

This also sharpens the diagnostic. `pl.matmul(tile, tile, b_trans=True)`
previously mis-resolved to the Tensor overload and reported "Tile cannot be
assigned to parameter lhs of type Tensor"; it now points at the offending kwarg
directly, mirroring the runtime message.

Adds runtime coverage for both flagged forms: `rsqrt(tile,
high_precision=False)` and the nine reductions with an explicit
`tmp_tile=None`.
Copilot AI review requested due to automatic review settings August 3, 2026 11:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

… them

Fixes hw-native-sys#2264

The unified `pl.*` wrappers dispatch on operand type and accept the union of
both levels' kwargs. A kwarg that only the other path could honour was silently
discarded — no error, no warning, no IR trace.

For `pl.matmul` this was a correctness bug. `pl.matmul(tile_a, tile_b,
b_trans=True)` compiled `A @ B`: with a square B the shapes stayed consistent
and the kernel computed the wrong math cleanly; with a non-square B it failed
in C++ type deduction ("lhs K=512 and rhs K=128") without ever naming
`b_trans`. That is how it was found — a kernel whose matmul operands were
migrated from Tensor to Tile while the `b_trans=True` line was left behind.

This is correct IR design, not an IR bug. A tensor value carries no layout, so
a flag is the only place transposition can live; at tile level it is a type
property expressed by the zero-copy `tile.transpose_view`, and `tile.matmul`
maps 1:1 onto `pto.tmatmul`, which has no transpose attribute. Tile ops must
not gain transpose args. The defect was that the frontend accepted a request it
could not honour and stayed silent — the `@overload` declarations already
encoded the right contract for matmul/matmul_acc; only the runtime failed to
enforce it.

Guards added, each raising only on a non-default value so spelling out
`b_trans=False` stays a no-op:

- `matmul` / `matmul_acc`: `a_trans`, `b_trans`, `c_matrix_nz` on the Tile path
  raise and point at `pl.tile.transpose_view`.
- `matmul`: `out_dtype` on the Tile path is verified rather than dropped. The
  Cube accumulator fixes the result dtype (FP32 for float operands, INT32 for
  int), so a matching request is honoured and a mismatched one raises naming
  `pl.cast`. The deduced dtype is read off the built call instead of
  re-deriving the C++ rule in Python.
- `rsqrt`: `high_precision=True` on the Tile path raised nothing before and
  silently yielded the low-precision path, since `tile.rsqrt` selects precision
  by taking a scratch tile rather than by an attribute.
- The nine `tmp_tile` reductions (`row_max`/`row_sum`/`row_min`/`row_prod`,
  `col_sum`, `row_argmax`/`row_argmin`/`col_argmax`/`col_argmin`): a
  `tmp_tile` on the Tensor path now raises. For `col_sum` the drop was
  strategy-meaningful — the requested binary-tree reduction was discarded and
  the scratch tile allocated but never used; for the rest the user's tile was
  silently dead while still consuming UB budget. The in-file comment already
  claimed this family rejected it.

Ten functions that had no `@overload` declarations gain the split, so the
static contract matches the runtime one. The existing `_reject_tmp_for_tensor`
helper moves to the shared-guards section and takes the parameter name, so the
reductions reuse it for `tmp_tile`.

No existing caller changes behaviour: the only Tile-path sites passing a
Tensor-only kwarg are two `out_dtype=pl.FP32` matmuls whose operands are BF16,
which is exactly the dtype the accumulator deduces.
Keep the documented default values accepted by the overloads.

The new Tile / Tensor overloads omitted the cross-path kwarg entirely, so a
caller spelling out the documented default — `pl.rsqrt(tile,
high_precision=False)`, `pl.row_max(tensor, None)` — was rejected statically
even though the runtime guard and the docs both promise it stays a no-op.

Declare those parameters with `Literal[False]` / `None` defaults on the path
that cannot honour them, so the static contract matches the runtime exactly:
the default type-checks, a non-default value is a static error as well as a
runtime one. Verified with a pyright probe — all seven documented-default calls
pass, all five non-default calls error.

This also sharpens the diagnostic. `pl.matmul(tile, tile, b_trans=True)`
previously mis-resolved to the Tensor overload and reported "Tile cannot be
assigned to parameter lhs of type Tensor"; it now points at the offending kwarg
directly, mirroring the runtime message.

Adds runtime coverage for both flagged forms: `rsqrt(tile,
high_precision=False)` and the nine reductions with an explicit
`tmp_tile=None`.
Copilot AI review requested due to automatic review settings August 4, 2026 02:35
@Hzfengsy
Hzfengsy force-pushed the issue-2264-reject-cross-path-kwargs branch from 350a5ed to 66821f1 Compare August 4, 2026 02:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@Hzfengsy
Hzfengsy merged commit bfd2437 into hw-native-sys:main Aug 4, 2026
15 checks passed
@Hzfengsy
Hzfengsy deleted the issue-2264-reject-cross-path-kwargs branch August 4, 2026 03:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[Bug] Unified DSL ops silently drop Tensor-only kwargs on the Tile path — pl.matmul(..., b_trans=True) compiles silently wrong math

3 participants