From bb25c2cfd10c05cd29b3b5d873e02955d678e433 Mon Sep 17 00:00:00 2001 From: seongwoo Date: Tue, 28 Jul 2026 17:10:25 +0900 Subject: [PATCH] [circle] Update CLI for remove-redundant-layout-ops This commit updates the cli for newly introduced pass. TICO-DCO-1.0-Signed-off-by: seongwoo --- test/unit_test/circle/test_cli.py | 10 ++++++---- tico/circle/README.md | 29 ++++++++++++++++++++++------- tico/circle/cli/main.py | 27 ++++++++++++++++++--------- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/test/unit_test/circle/test_cli.py b/test/unit_test/circle/test_cli.py index 35bca126..da2df503 100644 --- a/test/unit_test/circle/test_cli.py +++ b/test/unit_test/circle/test_cli.py @@ -16,6 +16,7 @@ from tico.circle.cli.main import _build_parser, _parse_passes from tico.circle.passes.cleanup import CompactIndicesPass, DeadCodeEliminationPass +from tico.circle.passes.optimization import RemoveRedundantLayoutOpsPass class CircleCLITest(unittest.TestCase): @@ -38,8 +39,9 @@ def test_extract_accepts_tensor_patterns_without_marker_flag(self): self.assertEqual(args.from_tensor, ["input"]) self.assertEqual(args.to_tensor, ["output"]) - def test_cleanup_pass_names_are_resolved(self): - passes = _parse_passes("dce,compact") + def test_optimization_and_cleanup_pass_names_are_resolved(self): + passes = _parse_passes("remove-redundant-layout-ops,dce,compact") - self.assertIsInstance(passes[0], DeadCodeEliminationPass) - self.assertIsInstance(passes[1], CompactIndicesPass) + self.assertIsInstance(passes[0], RemoveRedundantLayoutOpsPass) + self.assertIsInstance(passes[1], DeadCodeEliminationPass) + self.assertIsInstance(passes[2], CompactIndicesPass) diff --git a/tico/circle/README.md b/tico/circle/README.md index c4dff3d5..2c9031df 100644 --- a/tico/circle/README.md +++ b/tico/circle/README.md @@ -20,6 +20,7 @@ CircleDocument ├── inspect stable summaries and text output ├── operations.extract workflow-level graph extraction └── passes composable Circle-to-Circle rewrites + ├── RemoveRedundantLayoutOpsPass ├── DeadCodeEliminationPass └── CompactIndicesPass ``` @@ -124,10 +125,10 @@ result = extract_by_tensor_patterns( result.document.save("attention.circle") ``` -### Run cleanup passes +### Run optimization and cleanup passes ```python -from tico.circle.passes import CirclePassManager +from tico.circle.passes import CirclePassManager, RemoveRedundantLayoutOpsPass from tico.circle.passes.cleanup import ( CompactIndicesPass, DeadCodeEliminationPass, @@ -135,15 +136,21 @@ from tico.circle.passes.cleanup import ( pipeline = CirclePassManager( [ + RemoveRedundantLayoutOpsPass(), DeadCodeEliminationPass(), CompactIndicesPass(), ] ) result = pipeline.run(model) print(result.changes) -model.save("model.cleaned.circle") +model.save("model.optimized.circle") ``` +`RemoveRedundantLayoutOpsPass` rewires consecutive Reshape operations and inverse +Transpose pairs so their redundant operators become dead. Run dead-code elimination +after it to remove those operators, then compact the remaining tensor, buffer, and +operator-code indices. + By default, `CirclePassManager` verifies the document after every pass. Set `CirclePassContext(verify_after_each_pass=False)` only when a multi-step transformation intentionally has a temporary invalid state and performs @@ -265,24 +272,31 @@ Signatures for untouched subgraphs remain intact when `--keep-other-subgraphs` i ```bash tico-circle optimize model.circle \ - --passes dce,compact \ - -o model.cleaned.circle + --passes remove-redundant-layout-ops,dce,compact \ + -o model.optimized.circle ``` -Available first-stage passes: +Available passes: | Name | Implementation | Behavior | |---|---|---| +| `remove-redundant-layout-ops` | `RemoveRedundantLayoutOpsPass` | Rewires consecutive Reshape operations and consecutive inverse Transpose pairs so redundant operators can be removed | | `dce` | `DeadCodeEliminationPass` | Removes operators that cannot contribute to graph outputs and prunes unused graph inputs | | `compact` | `CompactIndicesPass` | Removes unused tensors, buffers, and operator codes and remaps all supported references | +`--passes` defaults to `dce,compact`. To remove redundant layout patterns, select the +three-pass pipeline shown above. Its order matters: the layout pass rewires dataflow, +`dce` removes the newly dead operators, and `compact` removes and remaps unused objects. + ### Standard input and output Use `-` for a binary stream: ```bash tico-circle extract model.circle --ops 0-100 -o - \ - | tico-circle optimize - --passes dce,compact -o output.circle + | tico-circle optimize - \ + --passes remove-redundant-layout-ops,dce,compact \ + -o output.circle ``` Do not redirect `inspect` text into a Circle transformation command; `inspect` writes text by design. @@ -346,6 +360,7 @@ Important test scenarios include: - graph producer and consumer indexing - operator and tensor-boundary selection +- redundant Reshape and inverse Transpose elimination - dead branch elimination - signature tensor-map remapping - shared buffer preservation across two subgraphs diff --git a/tico/circle/cli/main.py b/tico/circle/cli/main.py index 59830ab3..8f27a5d8 100644 --- a/tico/circle/cli/main.py +++ b/tico/circle/cli/main.py @@ -29,12 +29,23 @@ extract_by_tensor_patterns, SignaturePolicy, ) -from tico.circle.passes import CirclePass, CirclePassContext, CirclePassManager +from tico.circle.passes import ( + CirclePass, + CirclePassContext, + CirclePassManager, + RemoveRedundantLayoutOpsPass, +) from tico.circle.passes.cleanup import CompactIndicesPass, DeadCodeEliminationPass from tico.circle.selector import parse_operator_spec LOGGER = logging.getLogger("tico.circle.cli") +_PASS_REGISTRY: dict[str, type[CirclePass]] = { + "remove-redundant-layout-ops": RemoveRedundantLayoutOpsPass, + "dce": DeadCodeEliminationPass, + "compact": CompactIndicesPass, +} + def _build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser( @@ -137,7 +148,7 @@ def _build_parser() -> argparse.ArgumentParser: extract_parser.set_defaults(handler=_extract_command) optimize_parser = subparsers.add_parser( - "optimize", help="Run cleanup passes over a Circle model." + "optimize", help="Run optimization and cleanup passes over a Circle model." ) optimize_parser.add_argument("input", help="Input .circle path or '-' for stdin.") optimize_parser.add_argument( @@ -146,7 +157,9 @@ def _build_parser() -> argparse.ArgumentParser: optimize_parser.add_argument( "--passes", default="dce,compact", - help="Comma-separated passes. Available values: dce, compact.", + help=( + "Comma-separated passes. Available values: " f"{', '.join(_PASS_REGISTRY)}." + ), ) optimize_parser.add_argument( "--no-verify", @@ -238,21 +251,17 @@ def _extract_command(args: argparse.Namespace) -> int: def _parse_passes(value: str) -> list[CirclePass]: - registry: dict[str, type[CirclePass]] = { - "dce": DeadCodeEliminationPass, - "compact": CompactIndicesPass, - } passes: list[CirclePass] = [] for raw_name in value.split(","): name = raw_name.strip().lower() if not name: continue try: - passes.append(registry[name]()) + passes.append(_PASS_REGISTRY[name]()) except KeyError as error: raise ValueError( f"Unknown Circle pass {name!r}; available passes are " - f"{sorted(registry)}." + f"{sorted(_PASS_REGISTRY)}." ) from error if not passes: raise ValueError("At least one Circle pass must be selected.")