Skip to content

Reject negative dimensions in broadcast_to and random shapes - #4046

Merged
zcbenz merged 2 commits into
ml-explore:mainfrom
ayaangazali:reject-negative-dims
Aug 8, 2026
Merged

zcbenz merged 2 commits into
ml-explore:mainfrom
ayaangazali:reject-negative-dims

Conversation

@ayaangazali

Copy link
Copy Markdown
Contributor

Proposed changes

mx.broadcast_to and every mx.random.* sampler accept negative dimensions and hand back an array whose shape is negative. The size is then computed as an unsigned product, so it underflows:

>>> mx.broadcast_to(mx.array(1.0), (2, -3)).size
18446744073709551610
>>> mx.random.uniform(shape=(2, -3)).shape
(2, -3)

Reachable without writing a negative literal, since scale factors flow into shapes:

>>> nn.Upsample(scale_factor=-2)(mx.zeros((1, 4, 4, 1))).shape
(1, -8, -8, 1)

Everything else in the library already rejects this. mx.zeros, mx.ones and mx.full raise [full] Negative dimensions not allowed., mx.reshape raises, and numpy raises all elements of broadcast shape must be non-negative for the same broadcast_to call. These two paths are the ones that missed the check.

Added the same guard full already uses, at the two places a user supplied shape enters:

  • broadcast_to in mlx/ops.cpp
  • random::bits in mlx/random.cpp, which every sampler (uniform, normal, randint, gumbel, laplace, truncated_normal) routes through
>>> mx.broadcast_to(mx.array(1.0), (2, -3))
ValueError: [broadcast_to] Negative dimensions not allowed.
>>> mx.random.uniform(shape=(2, -3))
ValueError: [bits] Negative dimensions not allowed.

Zero sized dimensions are untouched: mx.broadcast_to(mx.ones((3,)), (0, 3)) and mx.random.uniform(shape=(0,)) still work.

Checklist

Put an x in the boxes that apply.

  • I have read the CONTRIBUTING document
  • I have run pre-commit run --all-files to format my code / installed pre-commit prior to committing changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the necessary documentation (if needed)

Tested on a CPU only build (MLX_BUILD_METAL=OFF). test_ops.py, test_random.py, test_array.py, test_nn.py and test_autograd.py all pass; the two new assertions fail on main.


freshman contributor, i use Claude Code alongside my own digging. found this by feeding deliberately bad shapes to the array constructors and comparing which ones complained, then traced the two that did not back to the missing check.

@ayaangazali

Copy link
Copy Markdown
Contributor Author

Rebased on main and added as_strided, which had the same hole:

>>> mx.as_strided(mx.arange(6.0), (-2, 3), (3, 1), 0).size
18446744073709551610

Only the shape is checked, negative strides are still allowed since as_strided uses them for reversal (there is an existing test for that at test_ops.py:2226).

@ayaangazali
ayaangazali force-pushed the reject-negative-dims branch from 60caca2 to a7265bd Compare August 7, 2026 17:09
@zcbenz
zcbenz merged commit 11fa2c8 into ml-explore:main Aug 8, 2026
28 checks passed
@BrewTestBot BrewTestBot mentioned this pull request Aug 18, 2026
1 task done
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.

2 participants