Skip to content

[Fix][Reduction] align constructor dim defaults + dim=[] noop on All/Any#1435

Merged
lcy-seso merged 10 commits into
tile-ai:mainfrom
lcy-seso:fix/reduction/issue-1434
May 12, 2026
Merged

[Fix][Reduction] align constructor dim defaults + dim=[] noop on All/Any#1435
lcy-seso merged 10 commits into
tile-ai:mainfrom
lcy-seso:fix/reduction/issue-1434

Conversation

@lcy-seso
Copy link
Copy Markdown
Collaborator

@lcy-seso lcy-seso commented May 12, 2026

Closes #1434

⚠️ BREAKING CHANGE

Constructor dim default flips from -1 to None for the ten ops whose manifest declares default: null:
SumFwdOp, MeanFwdOp, AmaxFwdOp, AminFwdOp, VarFwdOp, StdFwdOp, VarMeanFwdOp, AllFwdOp, AnyFwdOp, CountNonzeroFwdOp.

ProdFwdOp keeps dim=-1 and narrows the type to int (its manifest declares dim: int, default: -1). ProdFwdOp(dim=None | list | tuple) raises TypeError.

Callers depending on the old default must pass dim=-1 explicitly. No deprecation cycle — the flip is the spec alignment.

Summary

  • Constructor defaults dim=-1 → None in _ReduceOpBase, _WelfordReduceOp, AllFwdOp, AnyFwdOp, CountNonzeroFwdOp.
  • EmptyDimPolicy extended to Literal["reject", "full", "noop"]. AllFwdOp / AnyFwdOp use "noop", so dim=[] and dim=() short-circuit to output.shape == input.shape, dtype == bool, values == x.bool(). The short-circuit runs CUDA / dtype / ndim validation and binds _last_roofline_mn=(numel, 1) so the roofline still accounts for the read+write data movement.
  • ProdFwdOp.__init__ narrowed to dim: int = -1 with a _validate_dim override rejecting non-int.
  • Base _validate_dim rejects bool (scalar and inside list/tuple) — a boolean is never a valid axis.
  • dim annotation on the other constructors widened to Union[int, List[int], Tuple[int, ...], None] to match the existing normalize_dim runtime path.
  • No edits to tileops/manifest/ or tileops/kernels/.

Test plan

New tests in tests/ops/test_reduction_defaults.py (25 nodes) cover: per-op default-reduction shape, dim=[] / dim=() noop for All/Any, EmptyDimPolicy / normalize_dim / class-policy bindings, noop input validation + roofline binding, ProdFwdOp dim: int enforcement, base bool rejection.

Copilot AI review requested due to automatic review settings May 12, 2026 07:31
@github-actions github-actions Bot added the fix Auto-created by issue labeler label May 12, 2026
@lcy-seso lcy-seso added automated PR produced by an autonomous agent pipeline needs-review Awaiting human review before merge labels May 12, 2026
@lcy-seso
Copy link
Copy Markdown
Collaborator Author

/gemini review

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Aligns the Python reduction-op implementations with the manifest’s declared interface by (1) making dim=None the default for ops whose manifest declares default: null (full reduction), while preserving ProdFwdOp(dim=-1), and (2) implementing the manifest/PyTorch no-op semantics for AllFwdOp / AnyFwdOp when dim=[] / dim=().

Changes:

  • Flips constructor defaults dim=-1 → None for the affected reduce-op bases and logical/count reduction ops; adds an explicit ProdFwdOp(dim=-1) override.
  • Extends EmptyDimPolicy with "noop" and adds an op-layer no-op short-circuit for empty-dim reductions (casting output dtype for All/Any).
  • Updates/extends tests to pin default-dim and empty-dim semantics and to make prior dim=-1 expectations explicit at call sites.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tileops/ops/reduction/reduce.py Changes default dim to None, adds empty-dim no-op short-circuit, and preserves ProdFwdOp(dim=-1) via an explicit __init__.
tileops/ops/reduction/_multidim.py Extends EmptyDimPolicy with "noop" and updates normalize_dim() behavior for empty dims under that policy.
tileops/ops/reduction/all_op.py Sets _empty_dim_policy="noop", updates default dim=None, and ensures noop path outputs bool.
tileops/ops/reduction/any_op.py Sets _empty_dim_policy="noop", updates default dim=None, and ensures noop path outputs bool.
tileops/ops/reduction/count_nonzero.py Updates default dim=None (full reduction) while retaining keepdim=False behavior.
tests/ops/test_reduction_defaults.py Adds regression coverage for default dim semantics and All/Any empty-dim no-op behavior.
tests/ops/test_reduce.py Updates tests to pass dim=-1 explicitly where that behavior is intended.
tests/ops/test_logical_reduce.py Updates logical-reduce tests to pass dim=-1 explicitly where that behavior is intended.
tests/ops/test_reduce_multidim.py Updates the All empty-dim test to assert the new no-op semantics.
Comments suppressed due to low confidence (1)

tileops/ops/reduction/_multidim.py:32

  • normalize_dim is documented/used as accepting dim=[] and dim=() (tuple), but the type annotation is Union[int, list[int], None]. This is now inconsistent with both the docstring and call sites that pass tuples; please widen the annotation (e.g., to include tuple[int, ...] or a Sequence[int]) so the public helper matches the supported API surface.
def normalize_dim(
    dim: Union[int, list[int], None],
    ndim: int,
    *,
    empty_dim_policy: EmptyDimPolicy = "reject",
) -> list[int]:

Comment thread tileops/ops/reduction/reduce.py
Comment thread tests/ops/test_reduce.py
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request updates the default behavior of reduction operations to perform a full reduction (dim=None) by default, aligning with standard PyTorch semantics, while ProdFwdOp retains its dim=-1 default. It introduces a no-op contract for AllFwdOp and AnyFwdOp when an empty dimension list is provided, allowing them to return the input tensor cast to boolean. The implementation includes a new short-circuit mechanism in _ReduceOpBase and updates to dimension normalization logic. A comprehensive set of regression tests has been added to verify these constructor defaults and empty-dimension behaviors. Feedback was provided to refactor duplicated input validation logic into a shared private method to improve code maintainability.

Comment thread tileops/ops/reduction/reduce.py Outdated
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request updates the default reduction dimension from -1 to None (full reduction) for the majority of reduction operations, with ProdFwdOp being the exception. It also introduces a 'noop' contract for AllFwdOp and AnyFwdOp when dim=[] is passed, returning the input cast to boolean. Feedback points out that the change in default dimension is a breaking change for external consumers and suggests that the roofline model for no-op reductions should be adjusted to better account for input memory bandwidth.

Comment thread tileops/ops/reduction/reduce.py Outdated
Comment thread tileops/ops/reduction/reduce.py Outdated
Copilot AI review requested due to automatic review settings May 12, 2026 08:59
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

tileops/ops/reduction/_multidim.py:32

  • normalize_dim() now explicitly documents/supports dim=() and callers pass tuples, but the function’s type annotation is still Union[int, list[int], None]. This makes the public typing inconsistent with actual behavior and the manifest interface; please widen it to include tuple[int, ...] (or Sequence[int]).
def normalize_dim(
    dim: Union[int, list[int], None],
    ndim: int,
    *,
    empty_dim_policy: EmptyDimPolicy = "reject",
) -> list[int]:

Comment thread tileops/ops/reduction/reduce.py
Comment thread tileops/ops/reduction/reduce.py
Comment thread tileops/ops/reduction/reduce.py Outdated
lcy-seso and others added 5 commits May 12, 2026 17:27
Aligns the ten reduction ops whose manifest declares ``default: null`` on
``dim`` with the manifest spec by switching their constructor default from
``-1`` to ``None`` (full reduction). ProdFwdOp keeps its documented
``dim=-1`` default via an explicit subclass override.

AllFwdOp / AnyFwdOp opt into a new ``EmptyDimPolicy="noop"`` branch so
that ``dim=[]`` / ``dim=()`` returns the input cast to bool, matching
PyTorch's identity semantics. normalize_dim() learns the third policy
value; _ReduceOpBase grows a _maybe_noop short-circuit in forward().

Test callsites that omitted ``dim=`` and relied on the old ``-1`` default
are updated to pass ``dim=-1`` explicitly, preserving test intent.
The empty-dim noop short-circuit on AllFwdOp / AnyFwdOp was returning
the input directly without running the standard CUDA/dtype/ndim
validation and without binding _last_roofline_mn. As a result a cpu
tensor or wrong-dtype tensor with dim=[] silently passed through, and
eval_roofline() after a noop forward raised RuntimeError because the
shape state was never populated.

_maybe_noop now mirrors the validation that _prepare_input runs, and
binds _last_roofline_mn = (numel, 0) so the public forward contract is
preserved (bad inputs raise; eval_roofline() works after a noop).

Adds six regression tests covering both ops: cpu-tensor rejection,
wrong-dtype rejection, and roofline-binding after a successful noop
forward.
… tests

- Widen `dim` type annotation to include `tuple[int, ...]` in
  `_ReduceOpBase`, `ProdFwdOp`, `_WelfordReduceOp`, `AllFwdOp`,
  `AnyFwdOp`, `CountNonzeroFwdOp` (runtime path already accepts tuples
  via normalize_dim; annotation now matches manifest tuple[int, ...]
  token).
- Pass `dim=-1` explicitly at the six remaining last-axis callsites in
  `tests/ops/test_welford_non_aligned.py` (lines 204/219/234/249/262/275),
  matching the audit-list policy and the file's own "single-dim, dim=-1"
  header comment.
- Extract `_validate_input_tensor(x)` helper shared by `_prepare_input`
  and `_maybe_noop`, removing duplicated CUDA/dtype/ndim checks.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…-zero

The dim=[] noop short-circuit previously bound _last_roofline_mn =
(numel, 0). Under the per-op-kind formulas in eval_roofline(), N=0
collapses both flops and the M*N input-read term to zero, under-counting
the actual data movement: the noop still reads every input element and
writes an equal-shape result (cast to bool for All/Any/CountNonzero,
identity for Sum-style ops).

Bind (numel, 1) instead: model the noop as a degenerate reduction over
an axis of length 1. Per the existing formulas this yields
mem_bytes ~ numel * elem_bytes + numel (read + output term) rather than
zero, putting the noop on the correct bandwidth scale.

Update test_all_empty_dim_noop_binds_roofline /
test_any_empty_dim_noop_binds_roofline to assert mem_bytes is at least
the input-read term and at most 2x read + output write -- catches future
regressions to N=0.
Surface the BREAKING CHANGE trailer so release-notes tooling
(conventional-commits / semantic-release) picks up the constructor-
default flip for the ten reduce ops whose manifest declares
`default: null`. The original implementation commit (a4408e9) shipped
without the trailer; an explicit follow-up trailer keeps the PR
self-describing without rewriting published history.

BREAKING CHANGE: Constructor `dim` default changes from `-1` (last
axis) to `None` (full reduction) for SumFwdOp, MeanFwdOp, AmaxFwdOp,
AminFwdOp, VarFwdOp, StdFwdOp, VarMeanFwdOp, AllFwdOp, AnyFwdOp, and
CountNonzeroFwdOp -- the ten ops whose manifest entry declares
`default: null`. ProdFwdOp preserves `dim=-1` (manifest declares
`default: -1`). Callers depending on the old default MUST pass
`dim=-1` explicitly. Per design, no deprecation cycle is introduced:
this PR's mandate is strict alignment with the manifest spec, and
the spec is authoritative under the project's design-first /
spec-driven model.
@lcy-seso lcy-seso force-pushed the fix/reduction/issue-1434 branch from 903a691 to 7a4e4dd Compare May 12, 2026 09:28
Copilot AI review requested due to automatic review settings May 12, 2026 09:39
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

tileops/ops/reduction/_multidim.py:32

  • normalize_dim() is documented/used as accepting dim=() (see docstring and tests), and the implementation supports tuples via list(dim), but the type annotation is still Union[int, list[int], None]. Updating the annotation to include tuple[int, ...] (or a Sequence[int]) would keep the public helper signature consistent with actual supported inputs and the manifest’s tuple[int, ...] form.
def normalize_dim(
    dim: Union[int, list[int], None],
    ndim: int,
    *,
    empty_dim_policy: EmptyDimPolicy = "reject",
) -> list[int]:

Comment thread tileops/ops/reduction/reduce.py Outdated
Comment thread tileops/ops/reduction/reduce.py Outdated
@lcy-seso lcy-seso marked this pull request as ready for review May 12, 2026 09:43
@lcy-seso lcy-seso requested a review from a team May 12, 2026 09:43
Copy link
Copy Markdown
Contributor

@Ibuki-wind Ibuki-wind left a comment

Choose a reason for hiding this comment

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

Overall

One remaining reduction API-contract mismatch needs to be closed before this can safely unblock the status-flip PRs.

Comment thread tileops/ops/reduction/reduce.py Outdated
@lcy-seso lcy-seso changed the title [Fix][Reduction] align constructor dim defaults + dim=[] noop on All/Any (#1434) [Fix][Reduction] align constructor dim defaults + dim=[] noop on All/Any May 12, 2026
lcy-seso and others added 2 commits May 12, 2026 18:20
Co-Authored-By: Ibuki 🍃 — a wind born from GPTs <[email protected]>
Co-Authored-By: Ibuki 🍃 — a wind born from GPTs <[email protected]>
Copilot AI review requested due to automatic review settings May 12, 2026 10:31
Copy link
Copy Markdown
Contributor

@Ibuki-wind Ibuki-wind left a comment

Choose a reason for hiding this comment

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

Overall

One constructor contract change still breaks the current reduction test surface.

Comment thread tileops/ops/reduction/reduce.py
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

tileops/ops/reduction/_multidim.py:33

  • normalize_dim() is now documented/used with dim=() (tuple) and returns [] for the new empty_dim_policy="noop", but its type annotation still only allows Union[int, list[int], None]. Please widen the annotation to include tuple[int, ...] (or a Sequence[int]), and consider explicitly rejecting bool (since isinstance(True, int) is true) so dim=True/dim=[True] can’t be accepted as a valid dimension spec.
def normalize_dim(
    dim: Union[int, list[int], None],
    ndim: int,
    *,
    empty_dim_policy: EmptyDimPolicy = "reject",
) -> list[int]:
    """Normalize and validate a dim specification.

Comment thread tileops/ops/reduction/reduce.py
Co-Authored-By: Ibuki 🍃 — a wind born from GPTs <[email protected]>
Copy link
Copy Markdown
Contributor

@Ibuki-wind Ibuki-wind left a comment

Choose a reason for hiding this comment

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

Overall

Benchmarks still rely on the old implicit last-dimension default, so this PR can report invalid benchmark comparisons until those callsites are migrated.

Comment thread tileops/ops/reduction/reduce.py
Co-Authored-By: Ibuki 🍃 — a wind born from GPTs <[email protected]>
Copilot AI review requested due to automatic review settings May 12, 2026 11:24
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

tileops/ops/reduction/_multidim.py:32

  • normalize_dim() is documented/used with dim=() (tuple) and upstream call sites pass Tuple[int, ...], but its type annotation still only allows list[int] and it will also accept bool (since bool is an int subclass) and silently treat True as axis 1. Please widen the dim type to include tuple[int, ...] (or Sequence[int]) and add an explicit bool rejection (e.g., guard before the isinstance(dim, int) branch) to match the op-layer dim validation and avoid surprising behavior in other users like _softmax_base.
EmptyDimPolicy = Literal["reject", "full", "noop"]


def normalize_dim(
    dim: Union[int, list[int], None],
    ndim: int,
    *,
    empty_dim_policy: EmptyDimPolicy = "reject",
) -> list[int]:

Copy link
Copy Markdown
Contributor

@Ibuki-wind Ibuki-wind left a comment

Choose a reason for hiding this comment

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

Clean — no issues.

@lcy-seso lcy-seso merged commit 64dc0b6 into tile-ai:main May 12, 2026
18 checks passed
@lcy-seso lcy-seso deleted the fix/reduction/issue-1434 branch May 12, 2026 12:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated PR produced by an autonomous agent pipeline fix Auto-created by issue labeler needs-review Awaiting human review before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Fix][Reduction] align op defaults and empty-dim policy with manifest spec

3 participants