From b0a75985d3c01158696c67d592a74785aae6d789 Mon Sep 17 00:00:00 2001 From: Shiyi Zheng Date: Tue, 14 Apr 2026 12:57:22 +0800 Subject: [PATCH 1/5] fix: enable RTR rewrite autoconf for QNN 6D Reshape models Fix two bugs preventing the ReshapeTransposeReshapeOverlyHighDimPattern rewrite from being auto-triggered during the optimize-analyze loop: 1. pattern_id mismatch: ReshapeTransposeReshapeOverlyHighDimPattern and LowDimPattern both inherited pattern_id from shared schema, producing 'SUBGRAPH/ReshapeTransposeReshapePattern' instead of the class-specific IDs expected by information rules. Added property overrides. 2. kebab/snake case mismatch: information rule emits 'highdimRTR-lowdimRTR' (kebab-case) but RewritePipe.build_config looks up python_name 'highdimRTR_lowdimRTR' (snake_case). Added key.replace('-', '_') normalization in AnalysisResult.get_optimization_config(). Models affected: Swin, Donut, Nougat (vision-encoder-decoder family) Root cause: Swin window partition creates 6D Reshape ops (e.g. [1,8,7,8,7,192]) which exceed QNN EP max tensor rank of 5D. --- src/winml/modelkit/analyze/analyzer.py | 5 ++++- src/winml/modelkit/pattern/transpose_patterns.py | 10 ++++++++++ tests/unit/analyze/core/test_unified_pattern_config.py | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/winml/modelkit/analyze/analyzer.py b/src/winml/modelkit/analyze/analyzer.py index edb119412..74ea86f47 100644 --- a/src/winml/modelkit/analyze/analyzer.py +++ b/src/winml/modelkit/analyze/analyzer.py @@ -393,7 +393,10 @@ def get_optimization_config(self, ep: str | None = None) -> WinMLOptimizationCon continue if action_item.optimization_options: - optim_options.update(action_item.optimization_options) + # Normalize kebab-case keys to snake_case (python_name) + # so they match the capability system's python_name format. + for key, value in action_item.optimization_options.items(): + optim_options[key.replace("-", "_")] = value # Create and return config from collected options return WinMLOptimizationConfig(**optim_options) diff --git a/src/winml/modelkit/pattern/transpose_patterns.py b/src/winml/modelkit/pattern/transpose_patterns.py index eab7cdea6..9c269ac14 100644 --- a/src/winml/modelkit/pattern/transpose_patterns.py +++ b/src/winml/modelkit/pattern/transpose_patterns.py @@ -312,6 +312,11 @@ def check_skeleton_result( return None return result + @property + def pattern_id(self) -> str: + """Return pattern ID matching the information rule configuration.""" + return "SUBGRAPH/ReshapeTransposeReshapeOverlyHighDimPattern" + def get_schema(self) -> PatternSchema: """Return the schema definition for ReshapeTransposeReshape pattern. @@ -609,6 +614,11 @@ def get_internal_constants_and_attributes( return internal_constants, internal_attributes + @property + def pattern_id(self) -> str: + """Return pattern ID matching the information rule configuration.""" + return "SUBGRAPH/ReshapeTransposeReshapeLowDimPattern" + def get_schema(self) -> PatternSchema: """Return the schema definition for ReshapeTransposeReshapeLowDim pattern.""" return _RESHAPE_TRANSPOSE_RESHAPE_SCHEMA diff --git a/tests/unit/analyze/core/test_unified_pattern_config.py b/tests/unit/analyze/core/test_unified_pattern_config.py index 7c0bf21b7..5e023f944 100644 --- a/tests/unit/analyze/core/test_unified_pattern_config.py +++ b/tests/unit/analyze/core/test_unified_pattern_config.py @@ -31,7 +31,7 @@ def test_load_default_config(self): "SUBGRAPH/GeluPattern", # Shared by Gelu1-4 "SUBGRAPH/GemmPattern", # MatMulAdd "SUBGRAPH/LayerNormalizationPattern", # Shared by Pow and Mul variants - "SUBGRAPH/ReshapeTransposeReshapePattern", + "SUBGRAPH/ReshapeTransposeReshapeOverlyHighDimPattern", } assert skeleton_pattern_ids == expected_ids, f"Pattern IDs mismatch: {skeleton_pattern_ids}" From 0e90915211fcf1c0a7defd046342026b34e457ed Mon Sep 17 00:00:00 2001 From: Shiyi Zheng Date: Tue, 14 Apr 2026 16:58:17 +0800 Subject: [PATCH 2/5] add test --- .../pattern/test_transpose_patterns.py | 29 +++++++++ tests/unit/analyze/test_analyzer.py | 63 +++++++++++++++++++ 2 files changed, 92 insertions(+) diff --git a/tests/unit/analyze/pattern/test_transpose_patterns.py b/tests/unit/analyze/pattern/test_transpose_patterns.py index b413fe75c..30df0bd4b 100644 --- a/tests/unit/analyze/pattern/test_transpose_patterns.py +++ b/tests/unit/analyze/pattern/test_transpose_patterns.py @@ -218,3 +218,32 @@ def test_unmerged_6d_rtr_is_matched(self) -> None: assert len(results) == 1, ( "Unmerged 6-D RTR must match ReshapeTransposeReshapeOverlyHighDimPattern" ) + + +class TestRTRPatternIdAlignment: + """Verify pattern_id values match the rule configuration expectations.""" + + def test_overly_high_dim_pattern_id(self) -> None: + """ReshapeTransposeReshapeOverlyHighDimPattern must have distinct pattern_id.""" + pattern = ReshapeTransposeReshapeOverlyHighDimPattern() + assert pattern.pattern_id == "SUBGRAPH/ReshapeTransposeReshapeOverlyHighDimPattern" + + def test_low_dim_pattern_id(self) -> None: + """ReshapeTransposeReshapeLowDimPattern must have distinct pattern_id.""" + pattern = ReshapeTransposeReshapeLowDimPattern() + assert pattern.pattern_id == "SUBGRAPH/ReshapeTransposeReshapeLowDimPattern" + + def test_pattern_ids_are_distinct(self) -> None: + """Both RTR subclasses must return different pattern_ids.""" + high_dim = ReshapeTransposeReshapeOverlyHighDimPattern() + low_dim = ReshapeTransposeReshapeLowDimPattern() + assert high_dim.pattern_id != low_dim.pattern_id + + def test_pattern_id_differs_from_schema_name(self) -> None: + """pattern_id must NOT fall back to the shared schema name.""" + high_dim = ReshapeTransposeReshapeOverlyHighDimPattern() + low_dim = ReshapeTransposeReshapeLowDimPattern() + schema_based = f"SUBGRAPH/{high_dim.get_schema().name}" + # Both classes share the same schema, so schema-based ID would be identical. + # The override ensures they are distinct. + assert high_dim.pattern_id != schema_based or low_dim.pattern_id != schema_based diff --git a/tests/unit/analyze/test_analyzer.py b/tests/unit/analyze/test_analyzer.py index 8898fa69b..07233a752 100644 --- a/tests/unit/analyze/test_analyzer.py +++ b/tests/unit/analyze/test_analyzer.py @@ -658,6 +658,69 @@ def test_get_optimization_config_custom_option(self, mock_output: AnalysisOutput # Should accept any custom option assert config.get("custom_fusion", False) is True + def test_get_optimization_config_normalizes_kebab_case( + self, mock_output: AnalysisOutput + ) -> None: + """Test get_optimization_config normalizes kebab-case keys to snake_case.""" + rtr_action = Action( + pattern_from_id="SUBGRAPH/ReshapeTransposeReshapeOverlyHighDimPattern", + pattern_to_id="SUBGRAPH/ReshapeTransposeReshapeLowDimPattern", + details="RTR optimization", + action_items=[ + ActionItem( + type="GraphOptimization", + optimization_options={"highdimRTR-lowdimRTR": True}, + ) + ], + ) + mock_output.results[0].information = [ + Information( + pattern_id="SUBGRAPH/ReshapeTransposeReshapeOverlyHighDimPattern", + explanation="RTR pattern detected", + actions=[rtr_action], + ) + ] + + result = AnalysisResult(output=mock_output) + config = result.get_optimization_config() + + # Kebab-case key should be normalized to underscore + assert config.get("highdimRTR_lowdimRTR", False) is True + # Original kebab-case key should NOT be present + assert "highdimRTR-lowdimRTR" not in config + + def test_get_optimization_config_mixed_kebab_and_snake( + self, mock_output: AnalysisOutput + ) -> None: + """Test get_optimization_config handles mix of kebab-case and snake_case keys.""" + action = Action( + pattern_from_id="SUBGRAPH/TestPattern", + pattern_to_id="OP/Test", + details="Mixed key test", + action_items=[ + ActionItem( + type="GraphOptimization", + optimization_options={ + "already_snake": True, + "kebab-style-key": True, + }, + ) + ], + ) + mock_output.results[0].information = [ + Information( + pattern_id="SUBGRAPH/TestPattern", + explanation="Test", + actions=[action], + ) + ] + + result = AnalysisResult(output=mock_output) + config = result.get_optimization_config() + + assert config.get("already_snake", False) is True + assert config.get("kebab_style_key", False) is True + class TestONNXStaticAnalyzer: """Tests for ONNXStaticAnalyzer.""" From 7d5e2c6be3df2ae29ac6dcea2721f1fea2eb948c Mon Sep 17 00:00:00 2001 From: Shiyi Zheng Date: Tue, 14 Apr 2026 20:05:04 +0800 Subject: [PATCH 3/5] fix comments --- src/winml/modelkit/pattern/transpose_patterns.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/winml/modelkit/pattern/transpose_patterns.py b/src/winml/modelkit/pattern/transpose_patterns.py index 9c269ac14..237dd24b0 100644 --- a/src/winml/modelkit/pattern/transpose_patterns.py +++ b/src/winml/modelkit/pattern/transpose_patterns.py @@ -315,7 +315,7 @@ def check_skeleton_result( @property def pattern_id(self) -> str: """Return pattern ID matching the information rule configuration.""" - return "SUBGRAPH/ReshapeTransposeReshapeOverlyHighDimPattern" + return f"SUBGRAPH/{type(self).__name__}" def get_schema(self) -> PatternSchema: """Return the schema definition for ReshapeTransposeReshape pattern. @@ -617,7 +617,7 @@ def get_internal_constants_and_attributes( @property def pattern_id(self) -> str: """Return pattern ID matching the information rule configuration.""" - return "SUBGRAPH/ReshapeTransposeReshapeLowDimPattern" + return f"SUBGRAPH/{type(self).__name__}" def get_schema(self) -> PatternSchema: """Return the schema definition for ReshapeTransposeReshapeLowDim pattern.""" From 941c62ca3710b8a4a24affe8327a691fca150af9 Mon Sep 17 00:00:00 2001 From: Shiyi Zheng Date: Tue, 14 Apr 2026 20:29:19 +0800 Subject: [PATCH 4/5] fix comments --- .../analyze/rules/information_rules/default_information.json | 2 +- .../analyze/rules/information_rules/qc_information.json | 2 +- tests/unit/analyze/models/test_information.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/winml/modelkit/analyze/rules/information_rules/default_information.json b/src/winml/modelkit/analyze/rules/information_rules/default_information.json index 7ed0eaa4f..b186254fc 100644 --- a/src/winml/modelkit/analyze/rules/information_rules/default_information.json +++ b/src/winml/modelkit/analyze/rules/information_rules/default_information.json @@ -56,7 +56,7 @@ { "type": "GraphOptimization", "optimization_options": { - "highdimRTR-lowdimRTR": true + "highdimRTR_lowdimRTR": true } } ], diff --git a/src/winml/modelkit/analyze/rules/information_rules/qc_information.json b/src/winml/modelkit/analyze/rules/information_rules/qc_information.json index 206e137a8..a834c88de 100644 --- a/src/winml/modelkit/analyze/rules/information_rules/qc_information.json +++ b/src/winml/modelkit/analyze/rules/information_rules/qc_information.json @@ -10,7 +10,7 @@ { "type": "GraphOptimization", "optimization_options": { - "attention-expandedattention": true + "attention_expandedattention": true } } ], diff --git a/tests/unit/analyze/models/test_information.py b/tests/unit/analyze/models/test_information.py index 2444ffaba..e2a6b3c8d 100644 --- a/tests/unit/analyze/models/test_information.py +++ b/tests/unit/analyze/models/test_information.py @@ -381,7 +381,7 @@ def test_default_information_json_has_reshape_transpose_reshape_entry(self): action_items = entry["actions"][0]["action_items"] assert len(action_items) == 1 assert action_items[0]["type"] == "GraphOptimization" - assert action_items[0]["optimization_options"] == {"highdimRTR-lowdimRTR": True} + assert action_items[0]["optimization_options"] == {"highdimRTR_lowdimRTR": True} def test_qc_information_json_has_transpose_attention_entry(self): """Test that qc_information.json has the QC-specific TransposeAttentionPattern entry.""" @@ -408,7 +408,7 @@ def test_qc_information_json_has_transpose_attention_entry(self): action_items = entry["actions"][0]["action_items"] assert len(action_items) == 1 assert action_items[0]["type"] == "GraphOptimization" - assert action_items[0]["optimization_options"] == {"attention-expandedattention": True} + assert action_items[0]["optimization_options"] == {"attention_expandedattention": True} def test_default_information_json_does_not_have_transpose_attention_entry(self): """Test that TransposeAttentionPattern is NOT in default_information.json (QC-specific).""" From 726a0b322800fc27ed1af80a959427ad48e3113f Mon Sep 17 00:00:00 2001 From: Shiyi Zheng Date: Tue, 14 Apr 2026 20:59:41 +0800 Subject: [PATCH 5/5] fix test --- .../pattern/test_transpose_patterns.py | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/tests/unit/analyze/pattern/test_transpose_patterns.py b/tests/unit/analyze/pattern/test_transpose_patterns.py index 30df0bd4b..b5aea72a0 100644 --- a/tests/unit/analyze/pattern/test_transpose_patterns.py +++ b/tests/unit/analyze/pattern/test_transpose_patterns.py @@ -233,17 +233,14 @@ def test_low_dim_pattern_id(self) -> None: pattern = ReshapeTransposeReshapeLowDimPattern() assert pattern.pattern_id == "SUBGRAPH/ReshapeTransposeReshapeLowDimPattern" - def test_pattern_ids_are_distinct(self) -> None: - """Both RTR subclasses must return different pattern_ids.""" - high_dim = ReshapeTransposeReshapeOverlyHighDimPattern() - low_dim = ReshapeTransposeReshapeLowDimPattern() - assert high_dim.pattern_id != low_dim.pattern_id - - def test_pattern_id_differs_from_schema_name(self) -> None: - """pattern_id must NOT fall back to the shared schema name.""" - high_dim = ReshapeTransposeReshapeOverlyHighDimPattern() - low_dim = ReshapeTransposeReshapeLowDimPattern() - schema_based = f"SUBGRAPH/{high_dim.get_schema().name}" - # Both classes share the same schema, so schema-based ID would be identical. - # The override ensures they are distinct. - assert high_dim.pattern_id != schema_based or low_dim.pattern_id != schema_based + def test_high_dim_pattern_id_differs_from_schema_name(self) -> None: + """HighDim pattern_id must NOT fall back to the shared schema name.""" + pattern = ReshapeTransposeReshapeOverlyHighDimPattern() + schema_based = f"SUBGRAPH/{pattern.get_schema().name}" + assert pattern.pattern_id != schema_based + + def test_low_dim_pattern_id_differs_from_schema_name(self) -> None: + """LowDim pattern_id must NOT fall back to the shared schema name.""" + pattern = ReshapeTransposeReshapeLowDimPattern() + schema_based = f"SUBGRAPH/{pattern.get_schema().name}" + assert pattern.pattern_id != schema_based