microsoft / microsoft/winml-cli

Deduplicate per-opset-version data in static analyzer rules

Open
#351 0 comments 0 reactions 1 assignee View on GitHub

@fangyangci is already working on this.

Since Apr 15, 2026.

feature scale P2 refactor static-analyzer triaged
Dominant language
Python
Stars
40
Forks
11
Avg merge
1d 8h
Merged PRs (30d)
50

Description

Summary

Remove duplicate data across per-opset-version rule ZIP files in the static analyzer. Currently, each opset version ships as a fully independent ZIP, even though the majority of operator rules are identical across consecutive versions. This wastes significant storage and makes rule updates error-prone.

Context

The static analyzer runtime check rules live in src/winml/modelkit/analyze/rules/runtime_check_rules/. Each EP+device+domain+opset combination is a separate ZIP file (e.g., QNNExecutionProvider_NPU_ai.onnx_opset11.zip through opset22.zip). Every ZIP contains 4 JSON files: *_negative_rules.json, *_negative_rules_qdq.json, *_tables.json, and *_tables_qdq.json.

Analysis shows that many operators have byte-for-byte identical rule data across all 12 opset versions (e.g., And, Conv, ConvTranspose, GlobalAveragePool, GlobalMaxPool, InstanceNormalization, LRN). For QNN NPU alone, there are 12 ZIP files totaling ~284 MB, with substantial overlap. The lookup table files (*_tables.json) are even larger and likely have even higher duplication ratios.

Current State

  • Rule storage: 20+ ZIP files in src/winml/modelkit/analyze/rules/runtime_check_rules/, one per EP/device/domain/opset combination
  • Rule loading: src/winml/modelkit/analyze/core/runtime_checker_query.py:918-946 — loads the ZIP matching the model's exact opset version, no sharing across versions
  • Rule generation: src/winml/modelkit/analyze/runtime_checker/result_processor.py:378build_op_query_negative_rules_and_table() produces per-operator rules that get bundled per opset
  • Data models: src/winml/modelkit/analyze/models/runtime_checks.py
  • Example duplication: QNN NPU ai.onnx domain — opset11 ZIP is ~9 MB, opset22 ZIP is ~27 MB, but operators like Conv are identical across all 12 versions

Desired State

A storage format that eliminates redundant operator data across opset versions. Possible approaches (to be evaluated during implementation):

  1. Delta/inheritance model: Store a base set of rules and only store per-opset deltas (new operators or changed constraints)
  2. Per-operator deduplication: Store each unique operator rule blob once and reference it by hash from each opset version
  3. Merged format with version ranges: A single file per EP/device/domain that annotates each operator rule with the opset version range it applies to (e.g., "opset_range": [11, 22])

The loading code in runtime_checker_query.py must be updated to reconstruct the full rule set for a given opset version from the deduplicated format.

Acceptance Criteria

  • Duplicate operator data across opset versions is eliminated in the stored rule files
  • Rule loading (runtime_checker_query.py) correctly resolves the full rule set for any supported opset version
  • No change in analyzer output — existing tests must pass with identical results
  • Rule generation pipeline (if applicable) outputs the new deduplicated format
  • Storage size of runtime_check_rules/ directory is measurably reduced
  • Tests cover the new loading logic, including edge cases (first opset, last opset, opset with unique operators)

Technical Notes

  • The _LazyNegRules and LazyDomainTables classes in runtime_checker_query.py use lazy loading — any new format should preserve this behavior for memory efficiency
  • The *_tables.json files are the largest contributors to size — prioritize deduplicating these
  • Information rules (information_rules/) already use a non-per-opset format and do NOT need changes
  • Consider backward compatibility: if the rule generation pipeline is a separate step, the new format needs a migration path from the current per-opset ZIPs
  • The com.microsoft domain ZIPs are tiny (~1 KB) and may not need deduplication, but should use the same format for consistency

Related Files

  • src/winml/modelkit/analyze/core/runtime_checker_query.py:918-946 — Current per-opset ZIP loading logic
  • src/winml/modelkit/analyze/core/runtime_checker_query.py_LazyNegRules, LazyDomainTables classes
  • src/winml/modelkit/analyze/runtime_checker/result_processor.py:378 — Rule generation entry point
  • src/winml/modelkit/analyze/models/runtime_checks.py — Rule data models
  • src/winml/modelkit/analyze/rules/runtime_check_rules/ — All rule ZIP files
  • src/winml/modelkit/analyze/utils/rule_loader.py — Rule loading utilities

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.