Skip to content

Commit 9e69b6f

Browse files
Jah-yeemetascroy
andauthored
Add MLX op handler for aten.isinf (#18988)
Good day ## Summary This PR adds a decomposed MLX op handler for `aten.isinf` to the pytorch/executorch project. ### Motivation The MLX delegate converts PyTorch aten ops into MLX graph nodes during export. When an aten op has no handler, it falls back to CPU execution, breaking the GPU acceleration pipeline. Adding a handler for `aten.isinf` enables it to run on Metal GPU via MLX. ### Implementation The handler uses a decomposed approach: ```python isinf(x) = abs(x) == inf ``` This uses existing `AbsNode` and `EqualNode` which are already supported, avoiding the need for a dedicated MLX isinf op. ### Changes - **backends/mlx/ops.py**: Added `_isinf_handler` function registered for `torch.ops.aten.isinf.default` - **backends/mlx/test/test_ops.py**: Added `isinf` to `_UNARY_OP_TESTS` with standard test configuration ### Testing The handler can be tested with: ```bash python -m executorch.backends.mlx.test.run_all_tests -k isinf ``` Thank you for your attention. If there are any issues or suggestions, please leave a comment and I will address them promptly. Warmly, RoomWithOutRoof Co-authored-by: Scott Roy <161522778+metascroy@users.noreply.github.com>
1 parent 63b2b5c commit 9e69b6f

2 files changed

Lines changed: 51 additions & 0 deletions

File tree

backends/mlx/ops.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,41 @@ def _isnan_handler(P: MLXProgramBuilder, n: Node) -> Slot:
666666
return out
667667

668668

669+
@REGISTRY.register(target=[torch.ops.aten.isinf.default])
670+
def _isinf_handler(P: MLXProgramBuilder, n: Node) -> Slot:
671+
"""Handle aten.isinf - check for infinite values element-wise.
672+
673+
isinf(x) is equivalent to abs(x) == inf.
674+
"""
675+
args = P.args(n)
676+
require_args(args, 1, 1, "aten.isinf")
677+
require_kwargs(P.kwargs(n), set(), "aten.isinf")
678+
x = args[0]
679+
680+
# Create abs(x)
681+
_, abs_tmp = P.make_tmp_slot()
682+
P.emit(
683+
AbsNode(
684+
x=P.slot_to_tid(x),
685+
out=P.slot_to_tid(abs_tmp),
686+
)
687+
)
688+
689+
# Create inf constant (float32; EqualNode handles type promotion to match input dtype)
690+
inf_slot = emit_lifted_constant(P, float("inf"), torch.float32)
691+
692+
# Compare abs(x) == inf
693+
out = P.make_or_get_slot(n)
694+
P.emit(
695+
EqualNode(
696+
a=P.slot_to_tid(abs_tmp),
697+
b=P.slot_to_tid(inf_slot),
698+
out=P.slot_to_tid(out),
699+
)
700+
)
701+
return out
702+
703+
669704
_BINARY_OPS: List[Tuple[List[Any], Any, str, bool]] = [
670705
(
671706
[torch.ops.aten.mul.Tensor, torch.ops.aten.mul.Scalar],

backends/mlx/test/test_ops.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4737,6 +4737,21 @@ def fn(shape, dtype):
47374737
return fn
47384738

47394739

4740+
def _inf_input_fn():
4741+
"""Return a callable(shape, dtype) that generates inputs with some inf values."""
4742+
4743+
def fn(shape, dtype):
4744+
x = torch.randn(shape, dtype=dtype)
4745+
# Insert ~20% +inf and ~10% -inf using non-overlapping masks
4746+
mask_pos = torch.rand(shape) > 0.8 # ~20% -> +inf
4747+
mask_neg = (~mask_pos) & (torch.rand(shape) > 0.9) # ~10% of remaining -> -inf
4748+
x[mask_pos] = float("inf")
4749+
x[mask_neg] = float("-inf")
4750+
return (x,)
4751+
4752+
return fn
4753+
4754+
47404755
# Standard shape and dtype configs used by unary tests.
47414756
_SHAPES_3 = [(16,), (4, 4), (2, 3, 4)]
47424757
_SHAPES_2 = [(16,), (4, 4)]
@@ -4830,6 +4845,7 @@ def create_model(self) -> nn.Module:
48304845
{"op_name": "logical_not","op_fn": torch.logical_not, "shapes": [(2, 3, 4), (10,), (4, 8)], "dtypes": [torch.bool], "input_fn": _bool_input_fn()},
48314846
{"op_name": "bitwise_not_int", "op_fn": torch.bitwise_not, "shapes": _SHAPES_3, "dtypes": [torch.int32, torch.int64], "input_fn": _int_input_fn()},
48324847
{"op_name": "isnan", "op_fn": torch.isnan, "shapes": _SHAPES_3, "dtypes": [torch.float32, torch.float16, torch.bfloat16], "input_fn": _nan_input_fn()},
4848+
{"op_name": "isinf", "op_fn": torch.isinf, "shapes": _SHAPES_3, "dtypes": [torch.float32, torch.float16, torch.bfloat16], "input_fn": _inf_input_fn()},
48334849
# activations
48344850
{"op_name": "relu", "op_fn": torch.relu, "shapes": [(2, 3, 4), (10,), (4, 8), (2, 8, 16), (1, 128, 64)], "dtypes": [torch.float32], "input_fn": _input_fn(scale=2, offset=-1)},
48354851
{"op_name": "sigmoid", "op_fn": torch.sigmoid, "shapes": [(2, 3, 4), (10,), (4, 8), (2, 8, 16), (1, 1, 128)], "dtypes": [torch.float32], "input_fn": _input_fn(scale=2)},

0 commit comments

Comments
 (0)