Describe the bug
mx.pad with mode="reflect" or mode="symmetric" never returns when a padded axis has size 0: it spins in an infinite loop at ~100% CPU during graph construction (no eval involved), so the process hangs unrecoverably. numpy raises a ValueError for the same input.
To Reproduce
import mlx.core as mx
mx.pad(mx.array([]), 2, mode="reflect") # never returns
mx.pad(mx.array([]), 2, mode="symmetric") # never returns
mx.pad(mx.zeros((0, 3)), [(1, 1), (0, 0)], mode="reflect") # any padded size-0 axis hangs
Each of these was verified with a 10 s watchdog: the process is still alive and spinning at ~100% CPU when the watchdog fires.
Expected behavior
Raise an error the way numpy does:
>>> np.pad(np.array([]), 2, mode="reflect")
ValueError: can't extend empty axis 0 using modes other than 'constant' or 'empty'
mode="constant" on an empty axis already works in mlx and matches numpy, so only reflect/symmetric misbehave. A zero-width pad on an empty axis (e.g. mx.pad(mx.zeros((0, 3)), [(0, 0), (2, 1)], mode="reflect")) works today and should keep working — numpy allows it too.
Desktop (please complete the following information):
- OS Version: macOS 26.5.2 (Apple Silicon)
- Version: CPU-only source build at 7729d58 (0.32.1.dev); the loop is in graph construction (
reflect_pad in mlx/ops.cpp), so it is backend-independent
Additional context
In reflect_pad, a padded axis with n = 0 gives tile = n - offset = 0, so chunk = std::min(remaining, tile) is always 0 and remaining -= chunk never decreases — both the low-side and high-side fill loops spin forever. n == 1 is special-cased (n > 1) but n == 0 is not. Introduced with the new reflect/symmetric modes (#3608).
While reading this code I also noticed two adjacent issues that are only reachable from the C++ pad(a, axes, low_pad_size, high_pad_size, ...) overload (the Python binding always passes normalized, full axes), flagging them here for maintainers rather than bundling them into my fix: (1) pad normalizes negative axes for the output shape but passes the raw axes through, and reflect_pad / edge_pad index starts[ax] with them, so a negative axis writes out of bounds; (2) edge_pad ignores its axes argument entirely and iterates all dims by position, which goes wrong when axes is a strict subset or reordered. Happy to file these separately if useful.
I have a minimal fix (raise std::invalid_argument for a non-zero pad on an empty axis, matching numpy) with a regression test ready and will open a PR shortly.
Describe the bug
mx.padwithmode="reflect"ormode="symmetric"never returns when a padded axis has size 0: it spins in an infinite loop at ~100% CPU during graph construction (noevalinvolved), so the process hangs unrecoverably. numpy raises aValueErrorfor the same input.To Reproduce
Each of these was verified with a 10 s watchdog: the process is still alive and spinning at ~100% CPU when the watchdog fires.
Expected behavior
Raise an error the way numpy does:
mode="constant"on an empty axis already works in mlx and matches numpy, so only reflect/symmetric misbehave. A zero-width pad on an empty axis (e.g.mx.pad(mx.zeros((0, 3)), [(0, 0), (2, 1)], mode="reflect")) works today and should keep working — numpy allows it too.Desktop (please complete the following information):
reflect_padinmlx/ops.cpp), so it is backend-independentAdditional context
In
reflect_pad, a padded axis withn = 0givestile = n - offset = 0, sochunk = std::min(remaining, tile)is always0andremaining -= chunknever decreases — both the low-side and high-side fill loops spin forever.n == 1is special-cased (n > 1) butn == 0is not. Introduced with the new reflect/symmetric modes (#3608).While reading this code I also noticed two adjacent issues that are only reachable from the C++
pad(a, axes, low_pad_size, high_pad_size, ...)overload (the Python binding always passes normalized, full axes), flagging them here for maintainers rather than bundling them into my fix: (1)padnormalizes negative axes for the output shape but passes the rawaxesthrough, andreflect_pad/edge_padindexstarts[ax]with them, so a negative axis writes out of bounds; (2)edge_padignores itsaxesargument entirely and iterates all dims by position, which goes wrong whenaxesis a strict subset or reordered. Happy to file these separately if useful.I have a minimal fix (raise
std::invalid_argumentfor a non-zero pad on an empty axis, matching numpy) with a regression test ready and will open a PR shortly.