|
9 | 9 | from executorch.backends.arm._passes import UnsqueezeBeforeRepeatPass |
10 | 10 | from executorch.backends.arm.test import common |
11 | 11 | from executorch.backends.arm.test.tester.test_pipeline import PassPipeline |
| 12 | +from executorch.exir import EdgeCompileConfig, to_edge |
| 13 | +from executorch.exir.dialects._ops import ops as exir_ops |
12 | 14 |
|
13 | 15 | pipeline_input_t = Tuple[torch.Tensor, ...] |
14 | 16 | test_case_t = Tuple[ |
@@ -57,3 +59,80 @@ def test_unsqueeze_before_repeat_tosa_FP(test_data: test_case_t): |
57 | 59 | ) |
58 | 60 | pipeline.pop_stage(-1) # Do not compare output |
59 | 61 | pipeline.run() |
| 62 | + |
| 63 | + |
| 64 | +class _RepeatBlock(torch.nn.Module): |
| 65 | + """Provide an actual named module boundary for exported FX provenance.""" |
| 66 | + |
| 67 | + def forward(self, x: torch.Tensor) -> torch.Tensor: |
| 68 | + return x.repeat(2, 1, 1, 1) |
| 69 | + |
| 70 | + |
| 71 | +class _NestedRepeat(torch.nn.Module): |
| 72 | + def __init__(self) -> None: |
| 73 | + super().__init__() |
| 74 | + self.repeat_block = _RepeatBlock() |
| 75 | + |
| 76 | + def forward(self, x: torch.Tensor) -> torch.Tensor: |
| 77 | + return self.repeat_block(x) |
| 78 | + |
| 79 | + |
| 80 | +def test_unsqueeze_before_repeat_preserves_provenance() -> None: |
| 81 | + """The new view retains repeat provenance, but gets its own |
| 82 | + shape/qparams. |
| 83 | + """ |
| 84 | + model = _NestedRepeat().eval() |
| 85 | + inputs = (torch.randn(2, 3, 4),) |
| 86 | + ep = to_edge( |
| 87 | + torch.export.export(model, inputs), |
| 88 | + compile_config=EdgeCompileConfig(_check_ir_validity=False), |
| 89 | + ).exported_program() |
| 90 | + gm = ep.graph_module |
| 91 | + repeats = [ |
| 92 | + n for n in gm.graph.nodes if n.target == exir_ops.edge.aten.repeat.default |
| 93 | + ] |
| 94 | + assert len(repeats) == 1 |
| 95 | + repeat = repeats[0] |
| 96 | + |
| 97 | + original_stack = repeat.meta.get("nn_module_stack") |
| 98 | + assert original_stack, "Export must preserve Repeat module provenance" |
| 99 | + source_fn_stack = [("repeat_block", _RepeatBlock)] |
| 100 | + repeat.meta["source_fn_stack"] = source_fn_stack |
| 101 | + repeat.meta["stack_trace"] = "original repeat location" |
| 102 | + repeat.meta["debug_handle"] = 123456 |
| 103 | + # These refer to Repeat's inputs/output, not to the new view. Inheriting |
| 104 | + # them would assign an incorrect quantization annotation to the view. |
| 105 | + repeat.meta["input_qparams"] = {0: "repeat-input-qparams"} |
| 106 | + repeat.meta["output_qparams"] = {0: "repeat-output-qparams"} |
| 107 | + |
| 108 | + result = UnsqueezeBeforeRepeatPass()(gm) |
| 109 | + assert result is not None |
| 110 | + assert result.modified |
| 111 | + repeats_after = [ |
| 112 | + n |
| 113 | + for n in result.graph_module.graph.nodes |
| 114 | + if n.target == exir_ops.edge.aten.repeat.default |
| 115 | + ] |
| 116 | + assert len(repeats_after) == 1 |
| 117 | + view = repeats_after[0].args[0] |
| 118 | + assert isinstance(view, torch.fx.Node) |
| 119 | + assert view.target == exir_ops.edge.aten.view_copy.default |
| 120 | + assert view.args[1] == [1, 2, 3, 4] |
| 121 | + |
| 122 | + assert view.meta.get("nn_module_stack") == original_stack |
| 123 | + assert view.meta.get("source_fn_stack") == source_fn_stack |
| 124 | + assert view.meta.get("debug_handle") == 123456 |
| 125 | + assert "original repeat location" in view.meta.get("stack_trace", "") |
| 126 | + assert not view.meta.get("input_qparams") |
| 127 | + assert not view.meta.get("output_qparams") |
| 128 | + assert tuple(view.meta["val"].shape) == (1, 2, 3, 4) |
| 129 | + assert tuple(repeats_after[0].meta["val"].shape) == (2, 2, 3, 4) |
| 130 | + |
| 131 | + # Keep both semantics and idempotence unchanged. |
| 132 | + actual = result.graph_module(*inputs) |
| 133 | + if isinstance(actual, (tuple, list)): |
| 134 | + actual = actual[0] |
| 135 | + torch.testing.assert_close(actual, model(*inputs)) |
| 136 | + repeated = UnsqueezeBeforeRepeatPass()(result.graph_module) |
| 137 | + assert repeated is not None |
| 138 | + assert not repeated.modified |
0 commit comments