Skip to content

[Relax][ONNX] Make ReduceMax/ReduceMin NaN propagation order-independent(numpy semantics)#19755

Draft
cchung100m wants to merge 4 commits into
apache:mainfrom
cchung100m:issue-19572-reduce-max-min
Draft

[Relax][ONNX] Make ReduceMax/ReduceMin NaN propagation order-independent(numpy semantics)#19755
cchung100m wants to merge 4 commits into
apache:mainfrom
cchung100m:issue-19572-reduce-max-min

Conversation

@cchung100m

@cchung100m cchung100m commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Hi Committers,

This PR addresses the ReduceMax/ ReduceMin part of issue #19572. Any suggestions would be appreciated if you are available.

Root cause:

The ONNX frontend ReduceMax / ReduceMin converters return relax.op.max / relax.op.min. After legalization these map to topi.max / topi.min, which fold with a commutative reducer whose combiner is Max(x, y) / Min(x, y). In codegen, Max(a, b) lowers to select(a > b, a, b) using an ordered float comparison (fcmp ogt), which is false for NaN. As a left-fold (acc = Max(acc, elem)), NaN propagation becomes position-dependent - a later non-NaN element silently overwrites an earlier NaN.

Solution:

Adopt the well-defined, order-independent numpy/IEEE convention (matching numpy.max/min and torch.amax/amin): the reduction yields NaN whenever any reduced element is NaN. Minimal, ONNX-frontend-only change:

  • Add a shared helper _reduce_min_max_preserve_nan(reduce_op, data, axes, keepdims).
  • For floating-pint inputs, detect NaN along the reduced axes via sum(astype(isnan(data), dtype), axes, keepdims) > 0 and force those outputs to NaN with where(has_nan, nan, reduce(data)). The mask reduces over the same axes/keepdims, so it aligns in shape with the reduced result.
  • Keep non-floating(integer) inputs unchanged.
  • Route all reduce paths(_impl_v11and both reduce branches of _impl_v18) through the helper; the noop_with_empty_axes passthrough is left untouched since it performs no reduction.

Note on scope (re: #19589 ):

The underlying NaN behavior of Max/Min is the same family of ops discussed in #19589. Per review comments there, enforcing NaN semantics at the IR / LLVM-IR level is undesirable(backward-compat with older LLVM, and portability to CUDA/OpenCL/Vulkan), and a dedicated portable nanmin/nanmax TIRx intrinsic(like nearbyint) would be the preferred long-term mechanism. This PR deliberately:

  • does not touch the IR-level Max/Min lowering, and
  • does not rely on the bool reduction of the NaN mask - it uses sum(isnan) > 0, fully sidestepping Max/Min NaN behavior.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@cchung100m cchung100m changed the title [Relax][ONNX] Preserve NaN in ReduceMax/ReduceMin to align with ONNX Runtime [Relax][ONNX] Make ReduceMax/ReduceMin NaN propagation order-independent(numpy semantics) Jun 13, 2026
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