microsoft / microsoft/winml-cli
Deduplicate per-opset-version data in static analyzer rules
@fangyangci is already working on this.
Since Apr 15, 2026.
- 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:378—build_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.onnxdomain — opset11 ZIP is ~9 MB, opset22 ZIP is ~27 MB, but operators likeConvare 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):
- Delta/inheritance model: Store a base set of rules and only store per-opset deltas (new operators or changed constraints)
- Per-operator deduplication: Store each unique operator rule blob once and reference it by hash from each opset version
- 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
_LazyNegRulesandLazyDomainTablesclasses inruntime_checker_query.pyuse lazy loading — any new format should preserve this behavior for memory efficiency - The
*_tables.jsonfiles 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.microsoftdomain 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 logicsrc/winml/modelkit/analyze/core/runtime_checker_query.py—_LazyNegRules,LazyDomainTablesclassessrc/winml/modelkit/analyze/runtime_checker/result_processor.py:378— Rule generation entry pointsrc/winml/modelkit/analyze/models/runtime_checks.py— Rule data modelssrc/winml/modelkit/analyze/rules/runtime_check_rules/— All rule ZIP filessrc/winml/modelkit/analyze/utils/rule_loader.py— Rule loading utilities
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.