Skip to content

[Fix][Relax][Frontend][Torch] Make any and prod follow torch: missing overloads and result dtypes - #20376

Open
hiyufan wants to merge 1 commit into
apache:mainfrom
hiyufan:fix/relax-torch-any-prod
Open

hiyufan wants to merge 1 commit into
apache:mainfrom
hiyufan:fix/relax-torch-any-prod

Conversation

@hiyufan

@hiyufan hiyufan commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Problem

any and prod were each half-wired: one overload missing, and the overload that existed returning the wrong dtype (and, for any, the wrong value).

expression input torch frontend on main
x.any() any bool Unsupported function types ['any.default']
x.any(1) int32 [[0, 0, 5], [0, 0, 0]] bool [True, False] int32 [5, 0]
x.any(0) float32 bool float32 (the column maxima)
x.prod(0) / x.prod(1, keepdim=True) any Unsupported function types ['prod.dim_int']
x.prod() int32 [2²⁰, 2²⁰, 2] int64 2199023255552 int32 (overflows)
x.prod() bool int64 bool
  • _any computed max(x) and only cast back to bool for a bool input. For every other dtype it returned the maximum in the input dtype — the largest value, not "is any element non-zero". torch.any is always bool.
  • _prod had no int64 accumulation for bool / integer inputs, which _sum in the same file already implements for the identical torch rule.

Found by the same differential sweep as #20375; these were 27 of the 90 raising programs, plus the silent wrong-dtype rows above.

Fix

  • _any: x != 0 for a non-bool input, cast the mask to int8 (relax's max does not take bool), reduce with max, cast back to bool. any.default dispatches to it; any.dim / any.dims unchanged in dispatch. Empty dim list means every axis.
  • _prod: same int64 rule as _sum for bool / integer inputs, honours an explicit dtype=, empty dim list means every axis. prod.dim_int dispatches to it.

Verification

Sweep: any goes from 14 raising programs to 0, prod from 13 to 1 (the 0-d () input — a separate issue across all reductions, out of scope). test_frontend_from_exported_program.py and test_frontend_from_fx.py: failure sets identical before and after apart from the new tests (test_prod[float32|bool] and test_dtypes[*] fail identically on main in my environment — a TVMScript parser issue with parametrised variables, not this change). ruff check / ruff format --check (v0.12.3) clean.

Tests

  • test_prod_dim_and_integer_accumulation — IR-level: int32 x.prod(1) emits astype int64 then R.prod(…, axis=[1]).
  • test_prod_valuesprod(), prod(0), prod(1, keepdim=True) over bool, int32 (overflowing input), int64, float32; dtype and values.
  • test_any_returns_bool_for_every_dtype — IR-level for int32 x.any(1) (not_equal → astype int8 → max → astype bool); numeric any(), any(1), any(0, keepdim=True) over bool, int32, int64, float32 with an input whose second row is all zeros so both truth values appear.

All six fail against the previous head:

test_prod_dim_and_integer_accumulation   Unsupported function types ['prod.dim_int']
test_prod_values[bool]                   assert 'bool' == 'int64'
test_prod_values[int32]                  assert 'int32' == 'int64'
test_any_returns_bool_for_every_dtype    StructuralEqual check failed (… dtype)

Independent of #20372 / #20373 / #20374 / #20375 (branched from main).


This change was prepared with AI assistance (Claude). I have reviewed and verified it, and can speak to it in review.

… overloads and result dtypes

Two reductions were half-wired:

- `any.default` (`x.any()`) had no converter. `any.dim` had one, but for anything
  other than a bool input it returned `max(x)` in the input dtype -- the largest
  value, not a truth value: `int32 [[0, 0, 5], [0, 0, 0]].any(1)` came back as int32
  `[5, 0]` where torch gives bool `[True, False]`. torch.any asks whether any element
  is non-zero and is always bool. The converter now reduces the non-zero mask (`x != 0`
  for a non-bool input) with max in int8, since relax's max does not take bool, and
  casts back; `any.default` dispatches to it.
- `prod.dim_int` (`x.prod(dim)`, with or without keepdim) had no converter, and
  `prod.default` kept the input dtype for a bool or integer input where torch
  accumulates in int64, exactly as `_sum` here already handles: `int32 [2^20, 2^20, 2]
  .prod()` overflowed to int32 where torch gives int64 2199023255552. `_prod` now
  applies the same rule as `_sum`, honours an explicit `dtype=`, treats an empty dim
  list as every axis, and serves both overloads.

In the differential sweep of the frontend against torch.export (88 ops x 7 input
shapes x static/dynamic), `any` goes from 14 raising programs to none and `prod`
from 13 to one -- the 0-d input, which is a separate issue across all reductions.

Tests: IR-level checks that `int32 x.prod(1)` casts to int64 before `R.prod` and
that `int32 x.any(1)` lowers to not_equal -> int8 -> max -> bool; numeric checks of
prod over every axis, one axis and keepdim (bool, int32 with an overflowing input,
int64, float32), and of any in the same three forms over the same dtypes with an
input whose second row is all zeros so both truth values appear. All six tests fail
against the previous head.

An explicit `dtype=` that already matches the input emits no cast, so
`torch.prod(x, dtype=torch.float32)` on a float32 input still lowers to a bare
`R.prod`, as `test_prod` in both frontend test files expects.
@hiyufan
hiyufan force-pushed the fix/relax-torch-any-prod branch from a0bc562 to 9cb5ff9 Compare September 17, 2026 14:24
@hiyufan

hiyufan commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

The first CI run went red on cputest_prod[float32] and test_prod[bool] in both frontend test files — and that was this PR, fixed in 9cb5ff9.

_prod emitted R.astype(x, dtype=...) for an explicit dtype= even when it already matched the input, so torch.prod(x, dtype=torch.float32) on a float32 input lowered to astype + prod where the test expects a bare R.prod. It now skips the cast when the dtype is unchanged; the int64 promotion for integer inputs without a dtype= is untouched (test_prod_dim_and_integer_accumulation still pins the astype int64).

I should say why my before/after check missed it: test_prod cannot run in my local environment (Python 3.14, the TVMScript parser does not see the parametrised relax_dtype name), so it is in the "pre-existing failure" set on main and on this branch alike, and a same-set comparison is blind to a test that never ran. CI was the first place it could fail. Force-pushed the amended commit; the body's verification section otherwise stands.

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.

1 participant