Skip to content

Commit 50b4efb

Browse files
authored
[MLX] Allow lowering EP's with unused constants (pytorch#23138)
Allow lowering EP's with unused constants.
1 parent 5a388a1 commit 50b4efb

2 files changed

Lines changed: 78 additions & 0 deletions

File tree

‎backends/mlx/builder/program_builder.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ def _mark_unsupported(self, nodes: Union[List[Node], Node], reason: str) -> None
453453

454454
def _make_io_slots(self): # noqa: C901
455455
from torch.export.graph_signature import (
456+
ConstantArgument,
456457
InputKind,
457458
OutputKind,
458459
SymIntArgument,
@@ -464,6 +465,11 @@ def _make_io_slots(self): # noqa: C901
464465
user_inputs = []
465466
user_outputs = []
466467
mutable_buffers = []
468+
unused_placeholders = {
469+
node.name
470+
for node in self.ep.graph.nodes
471+
if node.op == "placeholder" and not node.users
472+
}
467473

468474
for ospec in self.ep.graph_signature.output_specs:
469475
kind = ospec.kind
@@ -506,6 +512,10 @@ def _make_io_slots(self): # noqa: C901
506512
raise NotImplementedError(
507513
f"Support for input {arg} is not implemented"
508514
)
515+
elif isinstance(arg, ConstantArgument) and name in unused_placeholders:
516+
# Export can retain specialized arguments (including None) in the
517+
# signature even though the graph does not consume them.
518+
continue
509519
else:
510520
raise NotImplementedError(f"Support for input {arg} is not implemented")
511521

‎backends/mlx/test/test_partitioner.py‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,74 @@ def _run(model, inputs, transform_passes=None):
8282
return counts, delegates, (out - ref).abs().max().item()
8383

8484

85+
class TestMLXPartitionerConstantInputs(unittest.TestCase):
86+
class OptionalInput(nn.Module):
87+
def forward(self, x, optional, y):
88+
return x + y
89+
90+
def test_unused_constants_preserve_signature(self):
91+
for value in (None, True, 3, 1.5, "unused"):
92+
with self.subTest(value=value):
93+
ep = export(
94+
self.OptionalInput(),
95+
(torch.randn(4), value, torch.randn(4)),
96+
strict=False,
97+
)
98+
signature = str(ep.graph_signature)
99+
placeholder = next(n for n in ep.graph.nodes if n.name == "optional")
100+
self.assertFalse(placeholder.users)
101+
102+
builder = MLXProgramBuilder(ep)
103+
builder.check_support_only()
104+
self.assertTrue(
105+
all(info.supported for info in builder.node_info.values())
106+
)
107+
built = MLXProgramBuilder(ep).build()
108+
self.assertEqual(built.num_input_tensors, 2)
109+
self.assertEqual(len(built.input_map), 2)
110+
self.assertEqual(str(ep.graph_signature), signature)
111+
112+
def test_unused_constants_lower_and_execute(self):
113+
# The Python runtime does not accept string inputs; cover those above.
114+
for value in (None, True, 3, 1.5):
115+
with self.subTest(value=value):
116+
_, delegates, error = _run(
117+
self.OptionalInput(),
118+
(torch.randn(4), value, torch.randn(4)),
119+
)
120+
self.assertGreater(delegates, 0)
121+
self.assertLess(error, 1e-6)
122+
123+
def test_string_constant_lowers(self):
124+
program = _lower(
125+
self.OptionalInput(),
126+
(torch.randn(4), "unused", torch.randn(4)),
127+
)
128+
self.assertGreater(_delegate_count(program), 0)
129+
130+
def test_consumed_constant_still_raises(self):
131+
ep = export(
132+
self.OptionalInput(),
133+
(torch.randn(4), 3, torch.randn(4)),
134+
strict=False,
135+
)
136+
placeholder = next(n for n in ep.graph.nodes if n.name == "optional")
137+
add = next(n for n in ep.graph.nodes if n.op == "call_function")
138+
add.target = torch.ops.aten.add.Scalar
139+
add.args = (add.args[0], placeholder)
140+
ep.graph_module.recompile()
141+
ep.validate()
142+
143+
for check_only in (True, False):
144+
with self.subTest(check_only=check_only):
145+
builder = MLXProgramBuilder(ep)
146+
with self.assertRaisesRegex(NotImplementedError, "ConstantArgument"):
147+
if check_only:
148+
builder.check_support_only()
149+
else:
150+
builder.build()
151+
152+
85153
class Sdpa(nn.Module):
86154
def __init__(self, is_causal: bool = False):
87155
super().__init__()

0 commit comments

Comments
 (0)