Skip to content

Commit c5f2f17

Browse files
authored
[MLX]: Add flip op handler for mlx backend (pytorch#22173)
### Summary Add flip op handler for the mlx backend. Implemented it with the flip handler in the [MLX op library](https://ml-explore.github.io/mlx/build/html/cpp/ops.html#_CPPv44flipRK5arrayRKNSt6vectorIiEE14StreamOrDevice). Fixes pytorch#19609 ### Test plan Wrote a test model for Flip and wrote a FlipTest with it. Tested it with: ``` python3 -m executorch.backends.mlx.test.run_all_tests --rebuild flip ``` I have an M3 pro so I was able to run the tests
1 parent 4663da6 commit c5f2f17

4 files changed

Lines changed: 76 additions & 0 deletions

File tree

‎backends/mlx/ops.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
ExpandDimsNode,
8282
Expm1Node,
8383
ExpNode,
84+
FlipNode,
8485
FloatOrVid,
8586
FloorDivideIntNode,
8687
FloorDivideNode,
@@ -5453,6 +5454,28 @@ def emit_reverse(in_slot, out_slot):
54535454
return output_slots
54545455

54555456

5457+
@REGISTRY.register(target=[torch.ops.aten.flip.default])
5458+
def _flip_handler(P: MLXProgramBuilder, n: Node) -> Slot:
5459+
args = P.args(n)
5460+
require_args(args, 2, 2, "aten.flip")
5461+
require_kwargs(P.kwargs(n), set(), "aten.flip")
5462+
x, dims = args
5463+
5464+
require_static_ints(dims, "dims", "aten.flip")
5465+
5466+
out = P.make_or_get_slot(n)
5467+
5468+
P.emit(
5469+
FlipNode(
5470+
x=P.slot_to_tid(x),
5471+
out=P.slot_to_tid(out),
5472+
axes=list(dims),
5473+
)
5474+
)
5475+
5476+
return out
5477+
5478+
54565479
# ---------------------------------------------------------------------------
54575480
# In-place variants for ops with bespoke functional handlers (clamp, pow,
54585481
# activations). Each reuses its functional handler via a passthrough that

‎backends/mlx/runtime/MLXInterpreter.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,11 @@ exec_transpose(const TransposeNode& n, ExecutionState& st, StreamOrDevice s) {
837837
st.set_tensor(n.out, transpose(st.const_tensor_ref(n.x), n.perm, s));
838838
}
839839

840+
inline void exec_flip(const FlipNode& n, ExecutionState& st, StreamOrDevice s) {
841+
std::vector<int> axes(n.axes.begin(), n.axes.end());
842+
st.set_tensor(n.out, flip(st.const_tensor_ref(n.x), axes, s));
843+
}
844+
840845
inline void
841846
exec_as_strided(const AsStridedNode& n, ExecutionState& st, StreamOrDevice s) {
842847
const auto& x = st.const_tensor_ref(n.x);
@@ -2162,6 +2167,9 @@ class Interpreter {
21622167
case OpCode::TRANSPOSE:
21632168
ops::exec_transpose(std::get<TransposeNode>(instr.node), st, s);
21642169
break;
2170+
case OpCode::FLIP:
2171+
ops::exec_flip(std::get<FlipNode>(instr.node), st, s);
2172+
break;
21652173
case OpCode::AS_STRIDED:
21662174
ops::exec_as_strided(std::get<AsStridedNode>(instr.node), st, s);
21672175
break;

‎backends/mlx/serialization/schema.fbs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,6 +1056,12 @@ table MetalKernelNode {
10561056
init_value: float = null;
10571057
}
10581058

1059+
table FlipNode {
1060+
x: Tid (required);
1061+
out: Tid (required);
1062+
axes: [int32] (required);
1063+
}
1064+
10591065
// =============================================================================
10601066
// Union of all op types
10611067
// =============================================================================
@@ -1198,6 +1204,7 @@ union OpNode {
11981204
RandomBitsNode,
11991205
UpdateAndAttendNode,
12001206
TruncNode,
1207+
FlipNode
12011208
// BC: Add new op nodes here (append only)
12021209
}
12031210

‎backends/mlx/test/test_ops.py‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8587,3 +8587,41 @@ def compute_expected_outputs(self, model, test_inputs):
85878587
return model(*test_inputs)
85888588
finally:
85898589
REGISTRY.uninstall(key)
8590+
8591+
8592+
class FlipModel(nn.Module):
8593+
def __init__(self, dims: List[int]):
8594+
super().__init__()
8595+
self.dims = dims
8596+
8597+
def forward(self, x: torch.Tensor) -> torch.Tensor:
8598+
return torch.flip(x, self.dims)
8599+
8600+
8601+
@register_test
8602+
class FlipTest(OpTestCase):
8603+
name = "flip"
8604+
8605+
def __init__(self, shape: Tuple[int, ...], dims: List[int]):
8606+
self.shape = shape
8607+
self.dims = dims
8608+
dims_str = "_".join(str(d) for d in dims)
8609+
shape_str = "x".join(str(s) for s in shape)
8610+
self.name = f"flip_{shape_str}_dims{dims_str}"
8611+
8612+
@classmethod
8613+
def get_test_configs(cls) -> List["FlipTest"]:
8614+
return [
8615+
cls(shape=(4, 5), dims=[0]),
8616+
cls(shape=(4, 5), dims=[1]),
8617+
cls(shape=(4, 5), dims=[0, 1]),
8618+
cls(shape=(3, 4, 5), dims=[-1]),
8619+
cls(shape=(3, 4, 5), dims=[0, 2]),
8620+
cls(shape=(3, 4, 5), dims=[0, 1, 2]),
8621+
]
8622+
8623+
def create_model(self) -> nn.Module:
8624+
return FlipModel(self.dims)
8625+
8626+
def create_inputs(self) -> Tuple[torch.Tensor, ...]:
8627+
return (torch.randn(self.shape),)

0 commit comments

Comments
 (0)