Skip to content

[BUG] mx.pad with mode="reflect"/"symmetric" hangs in an infinite loop when a padded axis is empty #4221

Description

@codeAnqiang-ma

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions