Skip to content

Fix shapeless compile eliding reductions on size-1 dimensions#3202

Open
ghstrider wants to merge 1 commit intoml-explore:mainfrom
ghstrider:fix/shapeless-reduce-noop
Open

Fix shapeless compile eliding reductions on size-1 dimensions#3202
ghstrider wants to merge 1 commit intoml-explore:mainfrom
ghstrider:fix/shapeless-reduce-noop

Conversation

@ghstrider
Copy link

@ghstrider ghstrider commented Mar 4, 2026

Summary

Fixes #3201mx.compile(shapeless=True) returns stale values when a reduction (sum, mean, max, etc.) operates on a dynamically-shaped input from take/gather.

Root Cause

compute_reduce_shape() reports is_noop=true when a reduced dimension is size 1 at trace time. This causes sum() et al. to skip creating the Reduce primitive entirely. On replay with a larger dimension, the missing reduction means the graph goes from GatherAxisSqueeze with no reduction in between, and only the first element survives.

Fix

Disable the is_noop optimisation during in_dynamic_tracing(), following the same pattern already used for no-op broadcast elision (lines 1489, 1543 of ops.cpp).

One-line change in compute_reduce_shape():

if (detail::in_dynamic_tracing()) {
    is_noop = false;
}

This fixes all 8 affected reduction operations (sum, prod, min, max, all, any, argmin, argmax) in one place.

Test Plan

  • Added test_shapeless_compile_reduce_after_gather covering sum, mean, and max with varying index sizes starting from size 1
  • Existing test_shapeless_compile_with_reduction continues to pass (it doesn't trigger the bug because its reduced dimensions are never size 1 at trace time)

During shapeless (dynamic) tracing, compute_reduce_shape() reported
is_noop=true when a reduced dimension happened to be size 1 at trace
time.  This caused sum/mean/max/min/prod/all/any/argmin/argmax to skip
creating the Reduce primitive entirely—returning just astype() or the
input directly.

On replay with a larger dimension, the missing Reduce meant the graph
went straight from GatherAxis (correctly producing [n] elements) to
Squeeze (dropping the dimension to scalar), and only the first element
was visible in the output.

The fix disables the is_noop optimisation when in_dynamic_tracing() is
true, following the same pattern already used for no-op broadcast
elision (lines 1489, 1543 of ops.cpp).

Fixes ml-explore#3201

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

[BUG] compile(shapeless=True): Reduce returns stale values on dynamically-shaped inputs from take/gather

1 participant