Skip to content

fix unknow pattern case - #1162

Open
fangyangci wants to merge 11 commits into
mainfrom
fangyangci/fix_unknow_patterns
Open

fix unknow pattern case#1162
fangyangci wants to merge 11 commits into
mainfrom
fangyangci/fix_unknow_patterns

Conversation

@fangyangci

@fangyangci fangyangci commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

related check results and rules parquet:
https://github.com/gim-home/ModelKitArtifacts/pull/156

@fangyangci
fangyangci requested a review from a team as a code owner July 21, 2026 08:04

@DingmaomaoBJTU DingmaomaoBJTU left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the fix for the "unknown pattern" cases. The overall approach is sound: broaden the RTR transpose input-generator to cover 3-D outputs and merged_transpose_dim=5, and make the Gemm generator treat only scalar biases as value-constraints so vector/tensor biases stay shape-constrained (instead of emitting value-based rules that real models never match). I hand-checked the added transpose combinations — they're all element-count consistent, and the PixelShuffle case does resolve to merged_transpose_dim=5.

A few things to look at (details inline):

  • The Gemm-side behavioral change (_is_scalar_constraint_value / C_is_value_constraint) has no unit test — only the transpose generator got new tests.
  • pattern: Pattern lost its type annotation in GemmPatternInputGenerator; that looks unrelated to this fix and now contradicts the comment directly above it.
  • The new transpose tests assert the hard-coded fixture rather than the derivation → rule-matching path that actually produced "unknown".
  • End-to-end: the matcher loads precomputed rule tables (parquet/JSON pulled from a release, per rules/runtime_check_rules/README.md), so these generator changes won't change real analyses until the runtime-check rules are regenerated and re-released. Is that regeneration planned as part of landing this?

Note: I couldn't run the suite locally (ARM64 host vs. the AMD64/linux lockfile), so please rely on CI to confirm the new tests pass.

Comment thread src/winml/modelkit/pattern/gemm_patterns.py
Comment thread src/winml/modelkit/pattern/gemm_patterns.py Outdated
Comment thread src/winml/modelkit/pattern/gemm_patterns.py
Comment thread src/winml/modelkit/pattern/transpose_patterns.py
Comment thread tests/unit/analyze/pattern/test_transpose_patterns.py
@fangyangci
fangyangci force-pushed the fangyangci/fix_unknow_patterns branch 3 times, most recently from 3bfb39d to a2c09c0 Compare July 27, 2026 02:22
@fangyangci
fangyangci force-pushed the fangyangci/fix_unknow_patterns branch from a2c09c0 to 95acbe5 Compare July 27, 2026 03:00

@DingmaomaoBJTU DingmaomaoBJTU left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall solid fix for unknown patterns with good test coverage. A few concerns below, primarily about the Attention domain and the base.py shape change.

# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
# --------------------------------------------------------------------------
"""Input generator for ai.onnx Attention operator (opset 23+).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring says ai.onnx Attention operator (opset 23+) but available_ops_all.json lists Attention with start_version: 99999 and valid_for_gene: false - this is a com.microsoft contrib op, not a standard ai.onnx op.

Should this class set op_domain = "com.microsoft" so it registers under the domain-qualified key com.microsoft::Attention (same pattern as the Gelu generator)? Without it, the op registers under the default ai.onnx domain which seems incorrect.

{
"Q": InputShapeConstraint((2, 4, 8, 16)),
"K": InputShapeConstraint((2, 4, 8, 16)),
"V": InputShapeConstraint((2, 4, 8, 16)),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

scale: 1.0 / 16 is a raw float mixed with InputShapeConstraint entries in the dict. Worth adding a comment clarifying that non-InputShapeConstraint keys are forwarded as op attributes, since the other entries are all shapes and it's not immediately obvious.


output_tensor = helper.make_tensor_value_info(output_name, elem_type, None)
# Keep shape present for ONNX checker while leaving dimensions unknown.
output_tensor = helper.make_tensor_value_info(output_name, elem_type, [None])

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changes the output shape from None (unknown rank) to [None] (rank-1 with one unknown dim). For patterns whose output is multi-dimensional (e.g., matmul -> 2-D, conv -> 4-D), a hardcoded rank-1 shape declaration is semantically incorrect and may cause ONNX checker warnings.

Consider using [None] * expected_output_rank derived from pattern metadata, or documenting why rank-1 is always safe here.

},
{
"data": InputShapeConstraint((1, 256, 256, 96)),
"transpose_shape": (1, 32, 8, 32, 8, 96),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This method grew from 2 entries to 20+. Many entries share the same perm=(0,1,3,2,4,5) and only vary spatial/hidden dims and output shape. Consider generating these programmatically from a compact (spatial, hidden_size, output_dims) spec to reduce maintenance burden.

# Distinguish scalar literal constraints from shape-based constraints.
# Vector/tensor constants should remain shape-constrained (not value-constrained).
c_value = item.get("C_value")
# Keep the shape gate as a defensive consistency check in case value/shape

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Nice fix - the old check incorrectly treated vector constants as value-constrained. The new scalar-only gate with the C_dim == 0 defensive check is correct and well-tested.

None,
)

new_nodes = list(model.graph.node)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: new_nodes = list(model.graph.node) is the same expression that was already inlined. The extra local variable doesn't seem to serve any purpose - was there an intent to mutate it before passing?

assert len(get_registered_operators()) == 120
registered_ops = get_registered_operators()

# Keep a lower bound so additive registrations do not break this test.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Good change - >= 120 lower bound + uniqueness check makes this test resilient to additive registrations without masking accidental removals.

@fangyangci

Copy link
Copy Markdown
Contributor Author

Tracking issue: #1236

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants