Skip to content

Commit d4dd5dc

Browse files
v-GolubevCopilot
andauthored
[Skills][Transformations] Introduce debug-matcher-pass skill (openvinotoolkit#35127)
### Details: Introduces a new “debug-matcher-pass” skill that documents an end-to-end workflow for diagnosing why an OpenVINO MatcherPass transformation doesn’t fire, including a filled example diagnosis report to use as a quality reference. ### Usage Examples #### Example 1: Prompt: > I run the following command -- `./benchmark_app -d CPU -m <path>/openvino_model.xml -niter 0` and expect MoE3GeMMFusion matcher pass to be applied, but it looks like the matcher pass fail. Help me to investigate why this transformation is not applied. <details> <summary>The skill's output (click to expand):</summary> ## MatcherPass Diagnosis: MoE3GeMMFusion **Root cause:** All three `MatMul` nodes (gate, up, down projections) in the model's MoE subgraph have `transpose_b=false`, but the `MoE3GeMMFusion` pattern hard-requires `transpose_b=true` via an `attrs_match` predicate. No explicit `Transpose` node wraps the weight input either, so neither of the pattern's two matching paths can succeed. **Log evidence:** ``` { MATCHING PATTERN NODE: WrapType<MatMul>(WrapType, any_input) ├─ AGAINST GRAPH NODE: MatMul(Multiply, Reshape) ├─ PREDICATE `attrs_match({ transpose_b: YES, transpose_a: NO }) && consumers_count(1)` FAILED } NODES' TYPE MATCHED, but PREDICATE FAILED ``` This phrase appears **30 times** in the matcher log — once per MoE layer candidate — confirming all 30 match attempts fail at exactly the same point. **Failing node:** `ov::op::v0::MatMul` (down-projection, and symmetrically gate/up-projection MatMuls). Op type is correct; only the `transpose_b` attribute fails the predicate. **Resolution:** - File: `src/plugins/intel_cpu/src/transformations/cpu_opset/common/pass/moe_matmuls_fusion.cpp`, lines ~241, 248, 253 - Remove the `{"transpose_b", true}` constraint from all three `wrap_type<MatMul>` predicate maps and instead read the actual flag inside the callback via `gate_mm_node->get_transpose_b()`, then propagate it to `BatchGatherMatmul`. - Alternative: add a second pattern branch (via `pattern::op::Or`) covering `transpose_b=false`. ## Reproducer Test File: `src/plugins/intel_cpu/tests/unit/transformations/moe_matmuls_fusion_test.cpp` Test name: `MoE3GeMMFusion_TransposeBFalse_TestF.MoE3GeMMFusion_TransposeBFalse_NotApplied` Status before fix: **PASS (green)** — transformation does not fire; model unchanged equals the auto-cloned `model_ref`. Matcher log from the test run shows the identical `attrs_match({ transpose_b: YES })` predicate failure as the original model, confirming the reproducer is faithful. </details> #### Example 2: Prompt: > I need you to investigate why some transformations in the GPU plugin pipeline don't work. I run the following command: > `cd /home/guest/golubevv/openvino/bin/intel64/Release && ./benchmark_app -d GPU -m subgraph.xml -niter 0` > and see the issue which is most likely caused by the transformation pipeline problems. I know that the following transformations should fire: > > 1. FuseVectorizedMOE3GEMM > 2. ConvertMOEToMOECompressed > 3. FuseMOE3GemmCompressed > 4. KeepMOE3GemmConstPrecision > > But at least one of them didn't fire. Investigate the root-cause <details> <summary>The skill's output (click to expand):</summary> # MatcherPass Diagnosis: `FuseMOE3GemmCompressed` **Date:** 2026-04-03 **Model:** Trinity-Mini (`subgraph.xml`) **Run command:** `./benchmark_app -d GPU -m subgraph.xml -compile_only` --- ## Summary of all four passes | Pass | Result | |---|---| | `FuseVectorizedMOE3GEMM` | ✅ Fired (`CALLBACK SUCCEDED`) | | `ConvertMOEToMOECompressed` | ✅ Fired (`CALLBACK SUCCEDED`) — produced `MOECompressed` nodes | | **`FuseMOE3GemmCompressed`** | ❌ **Never matched** — root cause | | `KeepMOE3GemmConstPrecision` | ❌ Never matched — downstream effect: no `MOE3GemmFusedCompressed` exists because step 3 didn't fire; triggers crash `Input moecompressed: ... hasn't been found in primitive_ids map` | --- ## Root cause The sigmoid routing normalization branch in the model has an extra `Multiply(Divide, Constant)` node between the normalization `Divide` and the `Slice`. The `FuseMOE3GemmCompressed` pattern's `sig_slice` (defined at line 91 of [fuse_moe_3gemm_compressed.cpp](src/plugins/intel_gpu/src/plugin/transformations/fuse_moe_3gemm_compressed.cpp)) expects: ``` Slice( Divide(...), ... ) ``` But the graph has: ``` Slice( Multiply( Divide(...), Constant ), ... ) ``` The extra `Multiply` is a routing-weight scaling constant multiply inserted after normalization. It is present in the Trinity-Mini model but was not present in the models used when the pattern was originally written. --- ## Log evidence From both `/tmp/matcher.log` (original run) and the unit test reproducer: ``` { ARGUMENT 0: WrapType<Divide> MATCHING PATTERN NODE: WrapType<Divide>(WrapType, WrapType) AGAINST GRAPH NODE: Multiply(Divide, Constant) } NODES' TYPE DIDN'T MATCH. EXPECTED: WrapType<Divide>. OBSERVED: Multiply ``` **Location in the match tree:** `MOECompressed` → ARGUMENT 1 (`Unsqueeze`) → ARGUMENT 0 (`Reshape`) → ARGUMENT 0 (`Transpose`) → ARGUMENT 0 (`ScatterElementsUpdate`) → **ARGUMENT 2** (`patternOr(sm_norm_slice | sig_slice)`) → BRANCH 1 (`sig_slice`) → **ARGUMENT 0** (expected `WrapType<Divide>`, observed `Multiply`). **Failing graph node:** `Multiply(Divide, Constant)` — the routing-weight scaling step inserted between `sig_norm` (Divide) and `sig_slice` (Slice). --- ## Crash consequence Because `FuseMOE3GemmCompressed` never fires, `KeepMOE3GemmConstPrecision` finds no `MOE3GemmFusedCompressed` nodes and also does nothing. The GPU program builder then fails: ``` Exception from src/plugins/intel_gpu/src/plugin/program_builder.cpp:265: Input moecompressed:__module.model.layers.2.mlp/aten::sum/ReduceSum/MOECompressed hasn't been found in primitive_ids map ``` --- ## Resolution In [src/plugins/intel_gpu/src/plugin/transformations/fuse_moe_3gemm_compressed.cpp](src/plugins/intel_gpu/src/plugin/transformations/fuse_moe_3gemm_compressed.cpp) around lines 90–91, add an `optional<Multiply>` wrapper between `sig_norm` and `sig_slice`: ```cpp // Before: auto sig_norm = wrap_type<ov::op::v1::Divide>({sig_gather_el, sig_add_eps}, consumers_count(1)); auto sig_slice = wrap_type<ov::op::v8::Slice>({sig_norm, ANY, ANY, ANY, ANY}, consumers_count(1)); // After: auto sig_norm = wrap_type<ov::op::v1::Divide>({sig_gather_el, sig_add_eps}, consumers_count(1)); auto sig_norm_scaled = optional<ov::op::v1::Multiply>({sig_norm, ANY}); // absorbs optional scaling auto sig_slice = wrap_type<ov::op::v8::Slice>({sig_norm_scaled, ANY, ANY, ANY, ANY}, consumers_count(1)); ``` `optional<Multiply>` will match `Multiply(Divide, Constant)` when present, or fall through to `Divide` directly for models without the extra scaling — no change to the existing passing test cases. > **Note:** Once the pattern fires, also verify that `MOE3GemmFusedCompressed`'s kernel/op implementation correctly applies the routing-weight scaling factor. The `Multiply` constant is currently not passed as an explicit argument to the fused op — the kernel may need a new input or an internal constant to reproduce correct numerical output. --- ## Reproducer Test **File:** [src/plugins/intel_gpu/tests/unit/transformations/fuse_moe_3gemm_compressed_test.cpp](src/plugins/intel_gpu/tests/unit/transformations/fuse_moe_3gemm_compressed_test.cpp) **Test name:** `TransformationTestsF.FuseMOE3GemmCompressed_SigmoidBias_ScaledNorm` **Build target:** `ov_gpu_unit_tests` **Run command:** ```bash cd /home/guest/golubevv/openvino/bin/intel64/Release OV_MATCHER_LOGGING=true OV_MATCHERS_TO_LOG=FuseMOE3GemmCompressed \ ./ov_gpu_unit_tests \ --gtest_filter="*FuseMOE3GemmCompressed_SigmoidBias_ScaledNorm*" ``` **Status before fix:** PASS ✅ — the transformation does not fire so the model is unchanged and matches the auto-cloned `model_ref`. This confirms the bug is reproduced. The test log shows the identical failure phrase: ``` NODES' TYPE DIDN'T MATCH. EXPECTED: WrapType<Divide>. OBSERVED: Multiply AGAINST GRAPH NODE: Slice(Multiply, Constant, ShapeOf, Constant, Constant) ``` **After fix:** the test will FAIL because `model` is now transformed and no longer matches the auto-cloned ref. At that point, add an explicit `model_ref` block with the expected `MOE3GemmFusedCompressed` result graph to turn it into a proper regression guard. </details> ### Tickets: - *N\A* ### AI Assistance: - *yes* - *AI was used to improve the skill based on real usage examples* --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 0615733 commit d4dd5dc

2 files changed

Lines changed: 348 additions & 0 deletions

File tree

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
---
2+
name: debug-matcher-pass
3+
description: Debug why an OpenVINO MatcherPass transformation is not firing. Use this skill immediately when a user says a transformation is "not applied", a "pass has no effect", a "matcher never triggers", a pattern "doesn't match", a "callback never fires", "WrapType predicate is too strict", a subgraph "not fused" despite the pass being registered, or they see "END: PATTERN DIDN'T MATCH" in matcher logs. Also trigger when a MatcherPass works on one model but silently skips another, when the user wants to add a reproducer test for a transformation that should fire but doesn't, or when they suspect an opset version mismatch preventing a match. Do NOT trigger for: writing a new MatcherPass from scratch, debugging a pass that fires but produces wrong numerical results, crashes in pass registration, or general questions about what MatcherPass is.
4+
---
5+
6+
# Debug MatcherPass Skill
7+
8+
An end-to-end workflow for diagnosing why an OpenVINO `MatcherPass`-based transformation does not fire on a given model.
9+
10+
## Goal
11+
12+
Produce two deliverables before finishing:
13+
1. **Diagnosis report** — post in the chat using the template at the end of this skill. Do not save it to a file.
14+
2. **Reproducer test** — edit the existing test source file to add the new `TEST_F` case, then compile and confirm it reproduces the non-firing behavior.
15+
16+
The only filesystem change should be the test file edit. Do not create additional output files.
17+
18+
---
19+
20+
## Step 0: Gather Prerequisites
21+
22+
Before touching any files or logs, confirm:
23+
- The **transformation class name(s)** — one or more (e.g., `EliminateSplitConcat`, or a list of several).
24+
- The **run command** that exercises the failing model/path (benchmark_app invocation, a unit test binary, a Python script, etc.).
25+
- The **build directory** — default to `build/Release` if not specified by the user.
26+
27+
Ask the user only if the transformation name or run command is missing. The build directory is never a blocker.
28+
29+
**If the user provides multiple transformation names** (e.g., "these passes should fire but at least one didn't"), treat this as a **pipeline cascade** investigation:
30+
- Log all passes simultaneously: `OV_MATCHERS_TO_LOG=Pass1,Pass2,...` (comma-separated, one entry per pass).
31+
- After collecting logs, build a per-pass status table: `CALLBACK SUCCEDED` (spelling matches the literal matcher log output) = ✅ fired; phrase never appears = ❌ did not fire.
32+
- Identify the **root cause pass** — the first one that does not fire for a structural graph reason (wrong node type, extra node, failing predicate) — vs. **downstream casualties** — passes that don't fire purely because the node type they expect was never produced (because the root cause pass did not transform the graph).
33+
- Investigate and reproduce only the root cause pass. Note the casualties in the report with their reason (node absent because upstream pass didn't fire).
34+
35+
---
36+
37+
## Step 1: Verify Debug Build
38+
39+
The matcher logging macros are compiled in only when `ENABLE_OPENVINO_DEBUG=ON`. Check whether the current build already has it:
40+
41+
```bash
42+
grep -i "ENABLE_OPENVINO_DEBUG" build/*/CMakeCache.txt
43+
```
44+
45+
If the flag is absent or set to `OFF`, reconfigure CMake using the existing build directory and build type from Step 0:
46+
47+
```bash
48+
cmake -B <build_dir> -DENABLE_OPENVINO_DEBUG=ON
49+
cmake --build <build_dir> --parallel
50+
```
51+
52+
> **Note:** Logging also works in `Release` builds as long as `ENABLE_OPENVINO_DEBUG=ON` is set at configure time.
53+
54+
---
55+
56+
## Step 2: Collect Matcher Logs
57+
58+
Run the failing command with matcher logging enabled and redirect output to a file:
59+
60+
```bash
61+
# Log only the transformation of interest (replace TransformationName):
62+
OV_MATCHER_LOGGING=true \
63+
OV_MATCHERS_TO_LOG=TransformationName \
64+
<your_run_command> 2>&1 | tee matcher.log
65+
```
66+
67+
Additional env vars:
68+
- **`OV_MATCHERS_TO_LOG`** — comma-separated list of matcher names to filter (omit to log all matchers, which produces very large output).
69+
- **`OV_VERBOSE_LOGGING=true`** — prints additional node details (element type, shape, attributes); use when the basic log does not identify the failure clearly.
70+
71+
---
72+
73+
## Step 3: Analyze the Log — Identify Root Cause
74+
75+
Open the log file and search for the transformation name. The log uses a tree structure with `{` (open block) and `}` (close block) markers, and colors to signal success (green) or failure (red).
76+
77+
### 3a. Check if the matcher ran at all
78+
79+
Search the log for the transformation name:
80+
81+
- **Not found at all** → The matcher is either not registered in the pass manager for this execution path, or it runs on an already-transformed/empty graph. Verify registration:
82+
83+
```bash
84+
# Locate where the pass is registered in the pipeline:
85+
grep -rn "register_pass<.*TransformationName" src/ --include="*.cpp" --include="*.hpp"
86+
```
87+
88+
Check that the pipeline is reachable from your run command (e.g., the correct plugin, the correct compilation path). If the pass is registered, confirm the graph is non-empty before it runs by adding a temporary `ov::pass::VisualizeTree` pass immediately before it.
89+
90+
- **Found** → Proceed to 3b.
91+
92+
### 3b. Find the outermost failure
93+
94+
Search for:
95+
96+
```
97+
END: PATTERN DIDN'T MATCH
98+
```
99+
100+
This confirms the pattern attempted to match but failed. If this line is **never** followed by `END: PATTERN MATCHED`, the transformation never fires.
101+
102+
### 3c. Find the innermost (root) failure
103+
104+
Work inward through the nested blocks to find the deepest `}` labeled with a failure. The following table maps log phrases to root causes:
105+
106+
| Log phrase | Root cause |
107+
|---|---|
108+
| `NODES' TYPE DIDN'T MATCH. EXPECTED: X. OBSERVED: Y` | The op type in the graph does not match the `WrapType` in the pattern. Most commonly an unexpected node has been inserted between two nodes the pattern expects to be directly connected (e.g., a `Convert` or `Reshape` inserted by a prior pass); alternatively, the candidate is simply a different op type. In rare cases the cause is an opset version mismatch (e.g., pattern expects `opset3::ShapeOf`, graph has `opset1::ShapeOf`). |
109+
| `NODES' TYPE MATCHED, but PREDICATE FAILED` | The op type matches but the lambda inside `WrapType(...)` returned `false`. Inspect the predicate in the transformation source — common checks: element type, rank, dynamic shapes, consumer count. |
110+
| `PREDICATE \`name\` FAILED` | A named `pattern::op::Predicate` with this name returned `false`. Locate it by name in the transformation source file. |
111+
| `NUMBER OF ARGUMENTS DOESN'T MATCH. EXPECTED: N. OBSERVED: M` | The graph node has a different number of inputs than the pattern expects. The model graph has a different input structure. |
112+
| `ARGUMENT N DIDN'T MATCH` | The N-th input of the candidate node fails its sub-pattern recursively. |
113+
| `NONE OF OR BRANCHES MATCHED` | A `pattern::op::Or` exhausted all alternative branches. Check each `BRANCH N DIDN'T MATCH` sub-block. |
114+
| `NONE OF PERMUTATIONS MATCHED` | Commutative op (e.g., Add, Multiply): tried all argument orderings but none satisfied the pattern. |
115+
| `LABEL DIDN'T MATCH` | A captured label's value does not satisfy its constraint (e.g., shape symbol equality, consumer requirements). |
116+
| `BLOCK "name" DIDN'T MATCH` | A grouped pattern block failed — look inside the block for the deeper cause. |
117+
| `ATTRIBUTES MISMATCH: VALUE OF \`attr\` IS ... EXPECTED ...` *(verbose only)* | A specific attribute value (axis, group, mode, etc.) does not match the expected value in the pattern. |
118+
| `ANY INPUT DIDN'T MATCH BECAUSE OF PREDICATE` | A label wrapping `any_input()` has an attached predicate that failed. |
119+
120+
### 3d. Common root causes in OpenVINO transformations
121+
122+
1. **Unexpected intermediate node** — A prior pass inserted an extra node (e.g., `Convert`, `Reshape`, `Transpose`) between two nodes the pattern expects to be directly connected, so the type observed at that edge doesn't match the pattern.
123+
2. **Wrong op type** — The candidate node is simply a different operation than the pattern expects (e.g., `Multiply` vs `MatMul`).
124+
3. **Predicate type/shape check**`WrapType` lambda checks element type (`f32` vs `f16`), rank, dynamic vs static shapes, or broadcast type. The actual node doesn't qualify.
125+
4. **Consumer count constraint** — Pattern checks that a node has exactly one consumer (`node->output(0).get_target_inputs().size() == 1`), but the graph has multiple consumers.
126+
5. **Pass run order** — A prerequisite transformation (e.g., `ConstantFolding`) has not yet run.
127+
6. **Output index** — Pattern matches `node->output(0)` but the graph provides `node->output(1)`.
128+
7. **Attribute value mismatch** — Pattern constrains an attribute (e.g., `group == 1`, `axis == 0`) that doesn't match the actual node.
129+
8. **Transformation was already applied** — Graph was modified by a symmetric or overlapping pass earlier; the target op no longer exists.
130+
9. **Wrong opset version** — Pattern uses `opset::OpX` but the frontend or a prior pass has already replaced it with a different version or a decomposed form.
131+
132+
### 3e. No output at all from OV_MATCHER_LOGGING
133+
134+
If the log file is empty or contains no matcher output even though the flag is set:
135+
136+
- Confirm `ENABLE_OPENVINO_DEBUG=ON` in `CMakeCache.txt` for the binary you are actually running (not a different build directory).
137+
- Confirm the env var is exported in the same shell context as the run command: `export OV_MATCHER_LOGGING=true` or prefix it inline.
138+
- If calling through Python or a launcher script, the env var must survive into the child process — use `os.environ` or prefix the full command.
139+
- Verify you are running the freshly rebuilt binary, not a cached one from a different `bin/` location.
140+
141+
---
142+
143+
## Step 4: Locate Unit Tests for the Transformation
144+
145+
Search for existing tests using the transformation class name and/or its header:
146+
147+
```bash
148+
# By class name
149+
grep -rl "TransformationX" src/common/transformations/tests/
150+
grep -rl "TransformationX" src/plugins/*/tests/ tests/layer_tests/
151+
152+
# By header include
153+
grep -rl "transformations/path/to/transformation_x.hpp" \
154+
src/common/transformations/tests/ \
155+
src/plugins/*/tests/
156+
```
157+
158+
Common test locations:
159+
- `src/common/transformations/tests/common_optimizations/` — most shared passes
160+
- `src/common/transformations/tests/op_conversions/` — op-conversion passes
161+
- `src/plugins/<plugin>/tests/functional/` — plugin-specific transformations
162+
163+
---
164+
165+
## Step 5: Add a Reproducer Test Case
166+
167+
Once you have identified the failing scenario from the log (Step 3), add a minimal test to the existing test file for the transformation.
168+
169+
### Test structure template
170+
171+
Use `TransformationTestsF` (from `common_test_utils/ov_test_utils.hpp`), which provides `model`, `model_ref`, and `manager` members and automatically compares them after the pass runs:
172+
173+
```cpp
174+
TEST_F(TransformationTestsF, TransformationX_DescribeFailingScenario) {
175+
{
176+
// 1. Build the input model that should trigger TransformationX.
177+
// Replicate the exact op type / attribute values / graph topology
178+
// that appears in the log as the candidate but fails to match.
179+
auto data = std::make_shared<opset5::Parameter>(element::f32, Shape{2, 2});
180+
181+
// ... construct the ops that match (or almost match) the pattern ...
182+
183+
model = std::make_shared<Model>(OutputVector{/* output */}, ParameterVector{data});
184+
185+
// 2. Register the target pass
186+
manager.register_pass<ov::pass::InitNodeInfo>();
187+
// add any other prerequisite passes that should run first if needed
188+
manager.register_pass<ov::pass::TransformationX>();
189+
}
190+
// Do NOT define model_ref here — leave it unset so TransformationTestsF
191+
// auto-clones the input model as the reference.
192+
}
193+
```
194+
195+
`TransformationTestsF` runs `manager` on `model` and compares it against `model_ref`. When `model_ref` is not explicitly set, it is auto-cloned from the input `model` *before* the pass runs. This means: if the transformation does not fire (the bug is present), `model` is unchanged and equals the clone → **the test passes (green)**. A green test here is the confirmation that the bug is reproduced — the transformation did not alter the graph.
196+
197+
### Key tips
198+
199+
- **Mirror the root cause in the test**: if `NODES' TYPE DIDN'T MATCH` showed an unexpected `Convert` inserted between two ops, include that `Convert` in the input model so the test proves the transformation handles (or correctly skips) that topology.
200+
- **Name the test clearly**: include the scenario that was failing (e.g., `EliminateSplitConcat_DifferentSplitAxis`).
201+
202+
### Build and run the new test
203+
204+
Find the CMake test target that owns the test file:
205+
206+
```bash
207+
grep -rn "add_executable\|ov_add_test_target" \
208+
src/common/transformations/tests/CMakeLists.txt \
209+
src/plugins/*/tests/CMakeLists.txt | grep -i transformation
210+
```
211+
212+
Build only that target and run the new test:
213+
214+
```bash
215+
cmake --build <build_dir> --target <test_binary_name> --parallel
216+
217+
bin/intel64/<build_type>/<test_binary_name> --gtest_filter="*TransformationName_DescribeFailingScenario*"
218+
```
219+
220+
**Expected outcome before the fix:** the test passes (green). This is correct — the transformation did not fire, so the model is unchanged and matches the auto-cloned `model_ref`. A passing test here is a *necessary* but not *sufficient* confirmation that the bug is reproduced.
221+
222+
### Validate the reproducer with matcher logging
223+
224+
A green test alone is not enough: the test could be passing for the wrong reason (e.g., the transformation was never even attempted on the small test graph, rather than failing at the expected point). Always cross-check by re-running the test binary with matcher logging enabled and verifying that the log shows the **same failure phrase and the same node** as in the original model log from Step 3:
225+
226+
```bash
227+
OV_MATCHER_LOGGING=true \
228+
OV_MATCHERS_TO_LOG=TransformationName \
229+
bin/intel64/<build_type>/<test_binary_name> \
230+
--gtest_filter="*TransformationName_DescribeFailingScenario*" 2>&1 | tee matcher_test.log
231+
```
232+
233+
Compare `matcher_test.log` against the original `matcher.log`:
234+
- The same innermost failure phrase (e.g., `NODES' TYPE DIDN'T MATCH. EXPECTED: X. OBSERVED: Y`) must appear.
235+
- The failing node type must match.
236+
237+
If the logs diverge (e.g., the test log shows a different failure phrase, or `END: PATTERN DIDN'T MATCH` never appears at all), the test model does not faithfully reproduce the original bug — revise the test graph to closer match the topology identified in Step 3 and repeat.
238+
239+
Once both conditions are met (test is green **and** logs agree), the reproducer is valid. Record the relevant log excerpt in the diagnosis report.
240+
241+
Once the fix is implemented and the transformation fires correctly, the test will fail because `model` no longer matches the unmodified clone. At that point, add an explicit `model_ref` block with the expected transformed graph to turn it into a proper regression guard.
242+
243+
---
244+
245+
## Step 6: Propose Resolution Strategies
246+
247+
Based on the root cause identified in Step 3, suggest one or more of the following to the user:
248+
249+
| Root cause | Resolution |
250+
|---|---|
251+
| Unexpected intermediate node | Extend the pattern to optionally absorb the inserted node (e.g., wrap it in `pattern::op::Optional`), or ensure the pass responsible for inserting that node runs after this transformation |
252+
| Wrong op type | Verify the model topology; if the op is a valid alternative, add it to the `WrapType` list |
253+
| Predicate fails (type/shape) | Relax or correct the predicate; run shape/type inference before the pass if shapes are unresolved |
254+
| Consumer count check fails | The single-consumer guard is usually a correctness constraint: replacing a node that feeds multiple consumers would silently alter other paths in the graph. First verify whether skipping is the correct behavior for this model. If the transformation is provably safe for all consumers, remove the consumer-count check from the predicate. If the constraint is intentional, the model simply does not qualify and no fix is needed in the transformation. |
255+
| Pass not registered / wrong order | Register the pass in the correct pipeline position; use `grep -rn "register_pass"` to find the pipeline source |
256+
| Attribute mismatch | Either extend the pattern to cover the additional attribute values, or verify that the model is correct and not pathological |
257+
| Transformation already applied | Check the pass order — a duplicate or conflicting pass may be consuming the node earlier |
258+
| Output index mismatch | Adjust the pattern to match the correct output index, or add an `Or` branch covering both |
259+
| Wrong opset version in pattern | Update `WrapType` in the transformation to include the correct opset, e.g., `wrap_type<opset1::ShapeOf, opset3::ShapeOf>(predicate)` |
260+
261+
Always pair the resolution suggestion with:
262+
1. The exact file and line in the transformation source where the fix should be made.
263+
2. The test case added in Step 5 as the regression guard.
264+
265+
### When to stop and escalate
266+
267+
If after Steps 1–3 you find:
268+
- The target op is provably absent from the graph (confirmed via `VisualizeTree` before the pass) and no prior pass should have produced it → the problem is upstream of the matcher (wrong frontend conversion, wrong pass pipeline entry point). Diagnose the upstream issue separately.
269+
- The pass is registered, the matcher runs, but a structurally identical sub-graph is silently skipped alongside one that correctly fires → the model may have an intentional correctness guard (e.g., consumer count, const-foldability). Verify this is intentional before proposing a change.
270+
271+
---
272+
273+
## Diagnosis Report Template
274+
275+
When both deliverables are complete, post the following in the chat (do not save to a file).
276+
See [references/example-diagnosis-report.md](references/example-diagnosis-report.md) for a fully filled example — use it as a quality bar for level of detail, especially for *Log evidence* (must be a direct quote from the log, not paraphrased from source) and *Resolution* (must include file + line).
277+
278+
```
279+
## MatcherPass Diagnosis: <TransformationName>
280+
281+
<!-- If multiple passes were investigated, add this table: -->
282+
## Summary of passes
283+
| Pass | Result |
284+
|---|---|
285+
| `PassA` | ✅ Fired (`CALLBACK SUCCEEDED`) |
286+
| `PassB` | ❌ Did not fire — root cause |
287+
| `PassC` | ❌ Did not fire — downstream: PassB never produced its expected input |
288+
289+
**Root cause:** <one-sentence summary>
290+
**Log evidence:** `<exact log phrase that identified the failure>`
291+
**Failing node:** <op type, location in graph>
292+
**Resolution:** <what needs to change and where (file:line)>
293+
294+
## Reproducer Test
295+
File: <path to test file>
296+
Test name: `<TEST_F name>`
297+
Status before fix: PASS (transformation did not fire — model unchanged, matches auto-cloned ref; confirms bug reproduced)
298+
```
299+
300+
---
301+
302+
## References
303+
304+
- **Filled example report:** [references/example-diagnosis-report.md](references/example-diagnosis-report.md)
305+
- Matcher logging documentation: [src/common/transformations/docs/debug_capabilities/matcher_logging.md](../../../src/common/transformations/docs/debug_capabilities/matcher_logging.md)
306+
- Log macro definitions: [src/core/dev_api/openvino/core/log_util.hpp](../../../src/core/dev_api/openvino/core/log_util.hpp)
307+
- Matcher implementation: [src/core/src/pattern/matcher.cpp](../../../src/core/src/pattern/matcher.cpp)
308+
- Example transformation tests: [src/common/transformations/tests/common_optimizations/](../../../src/common/transformations/tests/common_optimizations/)

0 commit comments

Comments
 (0)