Skip to content

Commit a588b26

Browse files
authored
Fix PruneEmptyTensorsPass crash on data-dependent shapes (pytorch#20809)
### Summary PruneEmptyTensorsPass crashes during export when cat inputs have data-dependent shapes (e.g. from conservatively keep the tensor. This is safe because the pass is purely an optimization; keeping an extra tensor in cat is a no-op at runtime. This removes the need for HuggingFace's _patch_remove_empty_tensors_from_cat monkey-patch in their ExecuTorch exporter:(https://github.com/huggingface/transformers/blob/main/src/transformers/exporters/exporter_executorch.py#L456-L485). ### Test plan Existing tests pass: test_empty_tensor_removed_from_cat, test_cat_removed_all_empty. New test test_unbacked_symint_numel_does_not_crash exports a model using torch.nonzero (produces unbacked SymInt shapes) and verifies the pass completes without crashing Verified guard_or_true returns True for undecidable expressions and correct concrete values for decidable ones python -m pytest exir/tests/test_prune_empty_tensors_pass.py -v
1 parent 5f6d1aa commit a588b26

2 files changed

Lines changed: 35 additions & 1 deletion

File tree

exir/passes/prune_empty_tensors_pass.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from executorch.exir.dialects._ops import ops as exir_ops
1212
from executorch.exir.pass_base import ExportPass, PassResult
1313
from torch.fx import GraphModule, Node
14+
from torch.fx.experimental.symbolic_shapes import guard_or_true
1415

1516
# This is a list of ops that are No Ops if used with an empty tensor.
1617
# Which means that if we remove the empty tensor as input to this op,
@@ -35,7 +36,7 @@ def remove_empty_tensors_from_cat(
3536
pruned_concat_list = []
3637
for input_arg in concat_list:
3738
input_arg_tensor = input_arg.meta["val"]
38-
if input_arg_tensor.numel() != 0:
39+
if guard_or_true(input_arg_tensor.numel() != 0):
3940
pruned_concat_list.append(input_arg)
4041

4142
cat_node.args = (pruned_concat_list,) + cat_node.args[1:]

exir/tests/test_prune_empty_tensors_pass.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from executorch.exir.capture._config import ExecutorchBackendConfig
1313
from executorch.exir.dialects._ops import ops as exir_ops
1414
from executorch.exir.passes import MemoryPlanningPass
15+
from executorch.exir.passes.prune_empty_tensors_pass import PruneEmptyTensorsPass
1516

1617

1718
class TestCat(nn.Module):
@@ -25,6 +26,16 @@ def get_example_inputs(self):
2526
return (torch.rand(5, 6), torch.rand(5, 6), torch.rand(5, 6))
2627

2728

29+
class UnbackedCatModel(nn.Module):
30+
__test__ = False
31+
32+
def forward(self, x, routing_weights):
33+
expert_mask = routing_weights > 0.5
34+
indices = torch.nonzero(expert_mask)
35+
selected = x[indices[:, 0]]
36+
return torch.cat([x[:0], selected], dim=0)
37+
38+
2839
class TestPruneEmptyTensors(unittest.TestCase):
2940
def test_empty_tensor_removed_from_cat(self) -> None:
3041
model = TestCat()
@@ -77,3 +88,25 @@ def test_cat_removed_all_empty(self) -> None:
7788
reference = model(*example_inputs)
7889

7990
self.assertTrue(torch.allclose(actual, reference))
91+
92+
def test_unbacked_symint_numel_does_not_crash(self) -> None:
93+
"""PruneEmptyTensorsPass must not crash on tensors whose numel() is an
94+
unbacked SymInt (e.g. from torch.nonzero in MoE expert routing).
95+
The pass should conservatively keep such tensors."""
96+
model = UnbackedCatModel()
97+
model.eval()
98+
ep = torch.export.export(
99+
model,
100+
(torch.randn(4, 8), torch.randn(4, 2)),
101+
strict=False,
102+
)
103+
result = PruneEmptyTensorsPass()(ep.graph_module)
104+
found_cat = False
105+
for node in result.graph_module.graph.nodes:
106+
if node.target == torch.ops.aten.cat.default:
107+
found_cat = True
108+
cat_inputs = node.args[0]
109+
self.assertEqual(len(cat_inputs), 1)
110+
val = cat_inputs[0].meta["val"]
111+
self.assertGreater(len(val.shape), 0)
112+
self.assertTrue(found_cat)

0 commit comments

Comments
 (0)