Skip to content

Add dynamo export support and make it the default exporter#1156

Open
DingmaomaoBJTU wants to merge 15 commits into
mainfrom
dingmaomaobjtu-dynamo-export-design
Open

Add dynamo export support and make it the default exporter#1156
DingmaomaoBJTU wants to merge 15 commits into
mainfrom
dingmaomaobjtu-dynamo-export-design

Conversation

@DingmaomaoBJTU

@DingmaomaoBJTU DingmaomaoBJTU commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Why

winml export previously rejected --dynamo even though PyTorch's dynamo ONNX exporter is the actively developed path. This PR makes dynamo a supported default while preserving the module hierarchy metadata used by inspection, reporting, benchmarking, and optimization tooling.

What changed

  • Dynamo is the default: WinMLExportConfig and winml export now default to dynamo=True. --no-dynamo remains available for the legacy TorchScript exporter, and generated submodule configs preserve an explicit parent exporter choice.
  • Hierarchy recovery from ONNX metadata: DynamoMetadataTagger reads generic pkg.torch.onnx.name_scopes and pkg.torch.onnx.class_hierarchy metadata. It handles sparse scopes and same-class siblings without architecture-, operator-, or tensor-specific logic.
  • Source-aware export pipeline: dynamo skips the hook-based TorchScript trace and reconstructs the hierarchy after export. Reports and metadata distinguish onnx_metadata recovery from trace, use discovery-order wording for dynamo, and source output names from the exported ONNX graph.
  • Visible hierarchy degradation: a non-empty dynamo graph with no recoverable hierarchy metadata emits one concise warning and continues with root fallback tags; partial recovery remains valid without a warning.
  • Requested and actual opsets: the requested opset is passed directly to PyTorch with no additional WinML conversion. The actual produced opset is read from the ONNX file and reported. Mismatches use exporter- and direction-aware warnings; detailed torch/onnxscript downgrade traces remain hidden unless verbose logging is enabled.
  • Compatibility: existing JSON field names are retained, all checked-in recipes continue to pin dynamo=false, and --no-dynamo preserves the existing TorchScript path.

Validation

  • Export unit suite: 476 passed, 6 skipped, 1 xfailed.
  • Broader affected unit scopes: 681 passed, 6 skipped, 1 xfailed.
  • Mypy: 406 source files clean; Ruff clean.
  • ResNet-50 end to end: ONNX and HTP metadata generated, 200/286 modules recovered, requested opset 17 and actual opset 18 reported with the concise WinML warning.

Flip `winml export` to torch's dynamo-based ONNX exporter by default
(--dynamo/--no-dynamo, default True). Add DynamoMetadataTagger that reads
torch's native pkg.torch.onnx.class_hierarchy / name_scopes node metadata
into the existing winml.hierarchy.* tag contract, so module-hierarchy tags
survive without a separate TorchScript trace.

- config + CLI default flipped to dynamo=True; stale "not supported" warning removed
- DynamoMetadataTagger drop-in tagger (same interface as ONNXNodeTagger)
- HTP exporter skips the redundant TorchScript trace under dynamo and sets
  verbose=False to avoid a UnicodeEncodeError on Windows consoles
- TorchScript-path-specific tests pin dynamo=False; docs updated
@DingmaomaoBJTU
DingmaomaoBJTU requested a review from a team as a code owner July 21, 2026 03:46
Comment thread tests/unit/core/test_dynamo_metadata_tagger.py Fixed
Comment thread docs/commands/export.md Outdated
Comment thread src/winml/modelkit/commands/export.py
Comment thread src/winml/modelkit/commands/export.py
github-actions Bot added 3 commits July 22, 2026 10:12
- test_dynamo_metadata_tagger: consolidate onnx imports to a single 'from onnx import ...' (CodeQL: module imported with both import and import-from)

- docs/help: 'QNN-safe' reworded to name the real differentiator (opset/op decomposition, not shapes, since both exporters default to static shapes); add a static-shape export example
The dynamo exporter does not default to a higher final opset; winml passes
the same opset (17) to both paths. torch's dynamo op library only implements
ops at a minimum opset of 18, so it builds at 18 and then down-converts to the
requested opset. Reword the export docs and concept doc to describe that
lowering-and-conversion pass accurately instead of claiming a "newer opset"
default.
torch's dynamo exporter targets a minimum opset of 18 and does not always
lower to a smaller requested version (e.g. ResNet's ReduceMean keeps an
opset-18-only attribute), so the export metadata previously misreported the
requested opset (17) while the real graph was opset 18.

- exporter: read the produced model's default-domain opset from the exported
  file and record that in the export monitor/metadata; warn when it differs
  from the requested opset. No post-export conversion is performed -- the
  requested opset is passed straight to the exporter and we report what it
  actually produced.
- docs: correct the dynamo vs TorchScript opset wording (dynamo exports at
  opset 18, TorchScript at the configured opset 17) with the ResNet head
  decomposition example.
- tests: add unit coverage for _read_default_opset.
Comment thread src/winml/modelkit/export/htp/exporter.py Fixed
Comment thread src/winml/modelkit/export/htp/exporter.py Fixed
github-actions Bot added 2 commits July 22, 2026 11:47
When the dynamo exporter cannot down-convert a model to the requested
opset, onnxscript's version_converter logs a WARNING with a full call
stack (e.g. ResNet's ReduceMean during an 18->17 attempt). winml already
surfaces a concise opset warning for that case, so the raw traceback is
redundant noise in normal output.

Add onnxscript.version_converter to the noisy-library logger floor so it
is pinned at ERROR by default and only follows the CLI level once the
user passes -v/-vv, matching how optimum's chatter is handled.
Resolves the CodeQL double-import alert on exporter.py: _read_default_opset
now reads the proto through winml's own load_onnx helper
(load_weights=False, validate=False) instead of a bare `import onnx`,
matching the runtime onnx-loading style already used elsewhere in the file.

Also floors torch's one-line "Setting ONNX exporter to use operator set
version 18 ..." notice (logger torch.onnx._internal.exporter._compat) at
ERROR by default, alongside the onnxscript version-converter traceback.
winml already surfaces a concise opset warning, so both are redundant in
normal output and only follow the CLI level at -v/-vv.
Comment thread src/winml/modelkit/core/onnx_node_tagger.py Outdated
Comment thread src/winml/modelkit/export/htp/exporter.py
Two dynamo-path fidelity fixes from review:

- DynamoMetadataTagger now folds each module's local scope name into its
  tag segment (Class.local) for every level, not just numeric indices, so
  same-class named siblings (an attention block's query/key/value Linears)
  stay independently addressable instead of collapsing to a single
  /.../Linear tag. Indexed children keep the existing Class.N form.

- Add DynamoMetadataTagger.build_module_hierarchy to reconstruct the flat
  module hierarchy from node metadata. The exporter now recovers it after
  loading the exported ONNX and issues the deferred HIERARCHY monitor
  update, so the report/console/metadata module tree and the
  hierarchy_modules stat are populated for dynamo exports (previously 0).
  Output names for the dynamo path are sourced from the ONNX graph outputs
  instead of the empty traced outputs.

Adds same-class-sibling tag tests and build_module_hierarchy unit tests.
Comment thread src/winml/modelkit/export/htp/exporter.py Outdated
Comment thread src/winml/modelkit/core/onnx_node_tagger.py
Comment thread src/winml/modelkit/core/onnx_node_tagger.py
Comment thread docs/commands/export.md Outdated
Round-2 review follow-ups on the reconstructed dynamo module hierarchy. The
tagger-level fixes from the previous commit were correct, but the shared tree
writers could still drop or collapse modules:

- metadata_writer: same-class named siblings (an attention block's
  query/key/value Linears) collapsed to one tree entry because children were
  keyed by bare class name. Key indexed children as Class.N (unchanged),
  disambiguate colliding same-class named siblings as Class.local, and keep the
  bare Class key for the unique case. Two-pass collision detection makes the
  keys order-independent, with a final full-remainder fallback for safety.

- hierarchy_utils.find_immediate_children: rewrite traversal around a
  nearest-present-ancestor helper so a sparse hierarchy (an indexed child whose
  ModuleList container never emitted its own scope, e.g. blocks.0 with no
  blocks) attaches to its nearest real ancestor instead of being orphaned.
  metadata_writer now reuses this shared traversal, fixing the same root-scope
  limitation in the JSON report.

- exporter: normalize the TorchScript output_names branch so output_names is
  always list[str] (infer_output_names returns list[str] | None), fixing the
  mypy union at the merge point.

- docs: the dynamo path does not always export at opset 18. It targets a
  minimum opset of 18 and attempts to down-convert to a lower requested opset,
  which may succeed or fail; winml reports the opset actually produced. Update
  export.md and load-and-export.md accordingly.

Add tests/unit/export/test_dynamo_hierarchy_report.py exercising the real
MetadataWriter tree: all same-class siblings serialize and the sparse-root
subtree is present; plus find_immediate_children sparse/dense nesting.
Comment thread docs/commands/export.md
Comment thread src/winml/modelkit/export/htp/exporter.py Outdated
github-actions Bot and others added 7 commits July 22, 2026 17:36
The dynamo export path reconstructs the module hierarchy from ONNX node
metadata after export, but the reporting pipeline still described it as a
TorchScript forward trace: the console/report claimed "Tracing module
execution", the export summary said "Traced modules", and the metadata's
execution_steps was set to the reconstructed-module count (a fabricated
step total). Docs and the CLI docstring likewise presented forward-hook
tracing as the default behaviour.

Thread an explicit hierarchy source through the reporting pipeline so
writers use source-appropriate wording and never report a trace that did
not run:

- step_data: add HIERARCHY_SOURCE_TRACE / HIERARCHY_SOURCE_ONNX_METADATA;
  HierarchyData.execution_steps is now int | None (None = no trace) and
  carries a source field.
- exporter: tag the TorchScript HIERARCHY update as trace and the dynamo
  one as onnx_metadata, dropping the fabricated execution_steps count.
- console/markdown writers: dynamo shows "Reconstructing/Recovered" with
  no execution-step line; the export summary label switches between
  "Recovered modules" and "Traced modules" by source.
- metadata: record source + the real producer (DynamoMetadataTagger vs
  TracingHierarchyBuilder) and emit execution_steps=0 for dynamo instead
  of the module count; document the new source field in the schema.
- docs + CLI docstring: distinguish the default dynamo metadata recovery
  from the --no-dynamo forward-hook trace.

Add source-wording tests (console, markdown, and metadata) for both paths.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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.

3 participants