AgentSkillsCN

onnx-doctor

onnx-doctor ONNX模型linter的开发规范。在新增规则、编写提供者,或修改linter代码库时使用此功能。

SKILL.md
--- frontmatter
name: onnx-doctor
description: Development conventions for the onnx-doctor ONNX model linter. Use this when adding rules, writing providers, or modifying the linter codebase.

ONNX Doctor — Development Skills

Package Structure

  • Package: onnx_doctor at src/onnx_doctor/
  • Core modules: _rule.py, _rule_registry.py, _loader.py, _checker.py, _cli.py, _formatter.py, _message.py, _diagnostics.py
  • Providers: diagnostics_providers/ — each provider is a subpackage or module
  • Tests: src/onnx_doctor/tests/
  • Docs: docs/ (Sphinx + MyST markdown, furo theme)

Rule Numbering Convention

PrefixCode RangeCategoryDescription
ONNX001–099specONNX spec compliance rules
ONNX101–199irIR-specific rules (issues unique to onnx_ir)
PB001+protobufProtobuf-specific rules
SIM001+specSimplification / dead code elimination rules
ORT001+specONNX Runtime compatibility
SP001+specSparsity analysis
  • Spec rules that apply to both protobuf and IR use ONNX001ONNX099.
  • IR-only rules (e.g., duplicate Value object identity) use ONNX101+.
  • Protobuf-only rules (impossible in IR by construction) use PB prefix.
  • Simplification rules (unused functions/nodes/opsets) use SIM prefix.

Adding a New Rule

  1. Define in YAML (spec.yaml or provider-specific YAML):

    yaml
    - code: ONNX036
      name: kebab-case-name
      category: spec
      severity: error
      message: Short description of the issue.
      suggestion: How to fix it.
      explanation: |
        ## Details
        Extended markdown explanation.
    
  2. Implement check in the provider (e.g., onnx_spec/__init__.py):

    python
    # ONNX036: kebab-case-name
    if condition:
        yield _emit(_rule("ONNX036"), "node", node, message=f"...")
    
  3. Add a test in tests/test_onnx_spec_provider.py:

    python
    def test_kebab_case_name(self):
        model = _make_model(...)
        messages = _diagnose(model)
        self.assertIn("ONNX036", _codes(messages))
    

Build & Test

bash
pip install -e .                              # Editable install
pip install -r requirements-dev.txt           # Dev dependencies
python -m pytest src/onnx_doctor/tests/       # Run tests
ruff check src/                               # Lint
ruff format src/                              # Format
onnx-doctor check model.onnx                  # CLI

Code Style

  • Every .py file must start with from __future__ import annotations.
  • Google-style docstrings. Target Python 3.9.
  • Ruff enforced (see pyproject.toml for full config).
  • Private modules prefixed with _ (e.g., _rule.py, _checker.py).

Key Dependencies

  • onnx_ir: The linter operates on IR objects (ir.Model, ir.Graph, etc.), not protobuf directly.
  • onnx: Used for op schema lookups (onnx.defs.get_schema).
  • pyyaml: Rule definitions loaded from YAML files.
  • rich: CLI output formatting.

Architecture Notes

  • _checker.py walks the IR tree and dispatches to providers. It threads a _location string through the traversal for human-readable paths (e.g., graph:node/3(MatMul)).
  • _loader.py has a lazy singleton get_default_registry() that loads all YAML rule files on first access.
  • Providers yield DiagnosticsMessage objects via generator functions (check_model, check_graph, check_node, check_value, check_tensor, check_function).

Autofix Architecture

  • Fix = Callable[[], None] — a no-arg callable that mutates the IR in place. Stored on DiagnosticsMessage.fix.
  • Rules marked fixable: true in YAML should attach a fix callable via the _emit() helper.
  • CLI --fix applies all fixes, saves the model, then re-diagnoses to show remaining issues.
  • CLI --diff shows a unified diff of what --fix would change, without writing.
  • Fix deduplication: _apply_fixes() deduplicates by callable identity (id(fix)) to avoid running the same pass multiple times.

Available IR Passes for Fixes

From onnx_ir.passes.common (all take model: ir.Model, return PassResult):

PassUsed byDescription
NameFixPassONNX003, ONNX103Auto-names all unnamed values and nodes
OutputFixPassONNX009Inserts Identity nodes for invalid output configurations
RemoveUnusedFunctionsPassSIM001Removes unreferenced functions
RemoveUnusedNodesPassSIM003Removes dead nodes and unused initializers
RemoveUnusedOpsetsPassSIM002Removes unused opset imports

Adding a Fixable Rule

  1. Mark fixable: true in YAML.

  2. In the provider, pass fix= to _emit():

    python
    yield _emit(
        _rule("ONNX004"), "graph", graph,
        fix=graph.sort,
    )
    
  3. For model-level passes, use the module-level helper functions:

    python
    yield _emit(
        _rule("ONNX003"), "graph", graph,
        fix=lambda: _apply_name_fix(self._model),
    )
    

Provider Structure

ProviderModuleRulesNotes
OnnxSpecProviderdiagnostics_providers/onnx_spec/ONNX001–ONNX103 (YAML)Default, always enabled
(protobuf rules)diagnostics_providers/onnx_spec/PB001–PB013 (YAML)Registered but no Python checker yet
SimplificationProviderdiagnostics_providers/simplification/SIM001–SIM003 (YAML)Default, always enabled
OnnxRuntimeCompatibilityLinterdiagnostics_providers/onnxruntime_compatibility/ORT001–ORT005 (Python)Opt-in via --ort flag
SparsityAnalyzerdiagnostics_providers/sparsity.pySP001 (Python)Example provider, not registered