Skip to content

feat: EP registration + monitoring — subprocess isolation, structured failures, universal op-tracing dispatch, lazy CLI startup#1019

Open
tezheng wants to merge 43 commits into
mainfrom
feat/ep-registration-and-monitoring
Open

feat: EP registration + monitoring — subprocess isolation, structured failures, universal op-tracing dispatch, lazy CLI startup#1019
tezheng wants to merge 43 commits into
mainfrom
feat/ep-registration-and-monitoring

Conversation

@tezheng

@tezheng tezheng commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR completes the EP registration, device-selection, session, and monitoring work while closing the CI and review regressions found during integration.

  • EP discovery and registration: isolates plugin registration when needed, preserves structured failure attribution, rolls back failed native registrations, enumerates plugins before querying ORT devices, and validates the actual OrtEpDevice class before selecting an automatic EP/device pair.
  • Device and build policy: preserves explicit EP/device choices, retries viable catalog candidates, retains vendor-free CPU/DML fallbacks when hardware probing fails, validates precision/device compatibility through the shared catalog, and restores ONNX content/config cache keys.
  • Compiler and session lifecycle: restores provider/session options, JIT persistence, QAIRT deferred compilation, shared EP contexts, resolved-device artifact finalization, caller-owned EPConfig immutability, and canonical compile --list behavior.
  • Monitoring and performance: keeps aggregate GPU telemetry separate from the selected adapter, fixes live chart labels, hardens QNN CSV/schematic validation and freshness handling, forwards resolved state to composite benchmarks, and restores op-tracing dispatch.
  • Compatibility and CLI: lazily installs the Transformers/Optimum compatibility shim, restores GenAI plugin registration, isolates import-state tests, and brings CLI coverage into the unit-test matrix.
  • Review cleanup: addresses or technically resolves all review feedback; the PR currently has no unresolved review threads.

Validation

  • uv run pytest tests\unit\ -q: 7451 passed, 77 skipped, 3 xfailed
  • uv run ruff check src\ tests\: passed
  • uv run mypy -p winml.modelkit: passed for 409 source files
  • Post-final CodeQL cleanup: uv run pytest tests\unit\sysinfo\ -q — 89 passed

@tezheng
tezheng requested a review from a team as a code owner July 2, 2026 02:30
@timenick

timenick commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Let’s exclude the .md files from this pr.

@xieofxie

xieofxie commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

please

  • merge conflicts
  • clean up commit-analysis and docs folder
  • consider split pr into self-contained features like
    • update transformers to 5
    • speed up cold-start
    • EP related
    • OP tracing related

Thanks

@tezheng
tezheng force-pushed the feat/ep-registration-and-monitoring branch from d242cd6 to cc7a665 Compare July 6, 2026 03:53
@tezheng

tezheng commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

E2E verification checklist

Runnable verification protocol for this branch — see E2E-VERIFICATION.md at repo root (added in f084ca7f).

Verified on Intel/OpenVINO NPU host (author). Other platforms — please pick up the delta.

Minimum smoke (~5 min with warm cache)

uv run winml sys --format json
uv run winml perf -m microsoft/resnet-50 --device auto --ep auto --monitor
uv run winml perf -m ./convnext/model_opt_qdq.onnx --ep qnn --device npu --op-tracing basic
uv run winml perf -m t5-small --device cpu

Platforms wanted

Host Priority checks not yet covered by author
Snapdragon (Qualcomm NPU) perf + analyze with --ep qnn --device npu --op-tracing basic — currently ENV on author's box (Intel AI Boost NPU, not Qualcomm)
AMD (MIGraphX GPU) sys --list-ep should surface MIGraphX rows; perf against --device gpu --ep migraphx
NVIDIA (TensorRT / TensorRTRTX GPU) perf against --device gpu --ep nv_tensorrt_rtx
Fresh Windows install (WinML Catalog only) Verify sys --list-ep renders Catalog source tag; perf with --ep openvino

How to classify failures

  • REGRESSION: click parse error, ImportError, NameError, TypeError, or Python traceback originating from src/winml/modelkit/…. Block ship.
  • ENVIRONMENTAL: hardware/EP unavailable on your host (No source for QNNExecutionProvider/npu exposed device class 'NPU', CompiledModel was not initialized, etc.). Doc has an ENV triage table. Not a ship blocker.
  • DOWNSTREAM: known composite ONNX export issue (onnx_nodes: 0tuple index out of range on t5-small). Separate concern, unrelated to this commit's B3/B6 fixes.
  • DOC-BUG: command runs but the doc's expected-output snippet doesn't match your platform's actual render. File a follow-up so the doc can be tuned.

Anti-pattern grep

Every regression signature has a grep pattern in the doc's grep table. If you find EP: ( in any output, T-01 tuple leak regressed. If you find TypeError: from_pretrained, B3 regressed. Full list in the doc.

Author's results (two runs, Intel host)

  • Run 1 (initial doc): 42 PASS / 0 REG / 10 ENV
  • Run 2 (revised doc): 39 PASS / 0 REG / 7 ENV / 1 DOWNSTREAM / 5 DOC-BUG (all format/casing polish; substantive claims verified)

Raw command outputs under commit-analysis/cc7a6650/e2e-results{,-v2}/ (untracked, session artifacts).

If you're picking up a platform

  1. Run the minimum smoke first — that's the fastest signal.
  2. If clean, run the section of the full matrix specific to your hardware.
  3. Post results back here with PASS/REG/ENV counts.

@tezheng

tezheng commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

PR #1019 E2E Verification Plan

Branch: feat/ep-registration-and-monitoring (winml-cli, formerly ModelKit)
PR: #1019 — "feat: EP registration + monitoring — subprocess isolation, structured failures, universal op-tracing dispatch, lazy CLI startup"
Verification target commit: cc7a6650 ("feat(session): unified-source EP refactor + P0 fixes + simplification pass")

Context / why this doc exists

PR #1019's own "Test plan" checklist references a three-commit chain
(9a8993f4, 0e2b5f05, e6be7659) and a baseline of "reproduce on
15861f75". That history no longer exists — the branch was rewritten
upstream into a single squashed commit, cc7a6650, which the local repo
has already been synced to (git reset --hard, no force-push performed).
The PR description was not updated to match, so its stated expected
numbers (1149 passed, 6 pre-existing baseline failures, 8 legit skips)
are not guaranteed to still hold and must be re-derived, not assumed.

A prior manual check on cc7a6650 already found 6 failures in
tests/unit/commands/test_perf_optracing.py, all related to OpenVINO
op-tracing dispatch (RuntimeError: Op-tracing --ep openvino is not currently supported) — a different failure set than the two categories
the PR describes as pre-existing/out-of-scope. Task 1 below must
determine whether these 6 are old-known, newly-introduced, or something
else, before any task can be marked "matches expectation."

High-level goal

Independently re-verify every item in PR #1019's Test plan checklist
against the actual current branch tip (cc7a6650), using measured
results — not the PR body's (now possibly stale) claimed numbers — and
produce a clear per-item verdict: PASS (behavior matches what the
PR claims or a reasonable equivalent), FAIL (behavior contradicts
the claim, needs investigation/fix), or DEVIATION (behavior differs
from the claim but is explainable/documented, e.g. the OpenVINO
failures above).

High-level passing criteria

All of the following must be true to close this verification:

  1. Every task below has a recorded PASS/FAIL/DEVIATION verdict with the
    actual command output (or a summary of it) as evidence — no verdict
    without evidence.
  2. Any FAIL is either fixed (with a follow-up commit) or explicitly
    escalated to the user with a clear description of the discrepancy —
    never silently ignored or reclassified as DEVIATION to make the plan
    "pass."
  3. The final report reconciles the PR body's stale claims (commit list,
    baseline failure count) against what's actually true on cc7a6650,
    so whoever reads the PR next isn't misled by outdated text.

Tasks

Each task is independent and can be picked up in any order, but Task 1
should run first since its finding affects how to interpret Task 2/3's
failure counts.


Task 1 — Full unit test suite baseline

Goal: establish the true current pass/fail/skip counts on cc7a6650,
and classify every failure as pre-existing/known vs. new.

Command:

uv run pytest tests/unit/ tests/unit/cli/ --timeout=60

Pass criteria:

  • Command completes (no collection errors, no hangs).
  • Every failing test is individually classified: matches one of the
    PR's two stated pre-existing categories (_LAZY_IMPORTS[winml.modelkit.onnx]
    drift, TestSysCommand shape drift), OR matches the 6
    already-found OpenVINO op-tracing dispatch failures, OR is genuinely
    new (→ FAIL, needs a bug report or fix).
  • Skip count and reasons are sane (hardware-gated skips only, no
    accidental skips of things that should run).

Verdict recorded as: PASS if all failures fall into a known
category; FAIL if any failure is unclassified and looks like a real
regression; DEVIATION if the counts differ from the PR's claim but
every individual failure is still explainable.


Task 2 — OpenVINO monitor unit tests

Goal: confirm the second op-tracing monitor implementation
(OpenVINOMonitor) is fully green in isolation, independent of the
dispatch-layer failures found in Task 1/3.

Command:

uv run pytest tests/unit/session/monitor/test_openvino_monitor.py

Pass criteria: all tests pass (PR claims 27). If the count differs,
record the actual count — a differing count alone is not a FAIL as
long as every test present passes.


Task 3 — Perf/op-tracing dispatch unit tests

Goal: confirm commands/perf.py's _resolve_ep_monitor dispatch
logic (NPU/auto → try monitor A, fall back to B; GPU → B; explicit
--ep; --op-tracing detail rejection) works as designed.

Command:

uv run pytest tests/unit/commands/test_perf_optracing.py

Pass criteria: all tests pass (PR claims 45, 7 new for dispatch).
Known current state: 6 failures already found here, all
RuntimeError: Op-tracing --ep openvino is not currently supported /
related dispatch errors — these must be root-caused as part of this
task, not just re-confirmed. Determine: is OpenVINO op-tracing support
actually wired up on this branch, or does _resolve_ep_monitor have a
bug that never dispatches to it? The PR's own summary text explicitly
claims OpenVINO dispatch is implemented ("NPU/auto → try monitor A,
fall back to B") — if the tests say otherwise, that's a real
implementation/test mismatch to report, not paper over.


Task 4 — CLI cold-start performance

Goal: confirm winml --help no longer eagerly imports transformers
(PEP 562 lazy-dispatch regression fix).

Commands:

uv run winml --help

then verify transformers is absent from a fresh subprocess's
sys.modules (the PR's own TestWallClock guardrail test does this —
running it directly is an acceptable substitute for a manual check):

uv run pytest tests/unit/cli/test_import_time.py tests/unit/cli/test_main.py -k "WallClock or cold"

Pass criteria: winml --help returns sub-second; transformers is
not in sys.modules for that invocation.


Task 5 — winml sys with directory-sourced EP override (vendor matrix: QNN / OpenVINO / VitisAI)

Goal: confirm EP registration hardening (subprocess isolation,
structured failure attribution, PE fallback-version reads) produces
truthful output when pointed at a real EP DLL directory — for
whichever EP vendor(s) the test machine actually has hardware for
.
The original version of this task only exercised OpenVINO (Intel), which
silently assumes an Intel test machine. Per
src/winml/modelkit/session/ep_device.py's EP_DEVICE_SPECS catalog,
the NPU device has three vendor-optimal EPs in preference order — QNN
(Qualcomm) → OpenVINO (Intel) → VitisAI (AMD) — so this task should be
run against whichever of the three matches the tester's actual hardware,
not assumed to be OpenVINO/Intel by default.

Vendor → hardware mapping (pick the row(s) matching the test
machine; run more than one row if the machine has multiple EP builds
available):

Vendor EP name Typical hardware Directory to point WINMLCLI_EP_PATH at
Qualcomm QNNExecutionProvider Snapdragon/Copilot+ NPU (arm64/arm64ec) A QNN EP build dir, e.g. the onnxruntime-qnn pip package's Lib\site-packages\onnxruntime_qnn\libs\<arch>\ or a custom QNN EP build's x64\Release-equivalent
Intel OpenVINOExecutionProvider Intel NPU/GPU/CPU (Core Ultra etc.) A compiled OpenVINO EP build directory, <path>\x64\Release
AMD VitisAIExecutionProvider AMD Ryzen AI NPU A compiled VitisAI EP build directory, <path>\x64\Release

Command (adjust path to an actual available EP build directory for
the vendor being tested — ask the user for the correct
WINMLCLI_EP_PATH target if none is on hand; do not fabricate a path):

$env:WINMLCLI_EP_PATH="<path-to-vendor-EP-build-dir>"; uv run winml sys

Pass criteria (apply per vendor tested): N truthful <vendor> row
versions surface (N = however many EP DLL variants exist in that
directory); Architecture/Driver columns render for NPU/GPU rows; any
[failed] rows show a compact Win32 reason string and a PE-read
fallback version rather than a blank/opaque failure — for whichever
vendor(s) were actually exercised.

Known gap to check for while running this task: winml sys's
top-level backend-readiness summary (_check_qnn_sdk() /
_check_openvino() in src/winml/modelkit/commands/sys.py) only
reports install/readiness status for QNN and OpenVINO — there is no
equivalent _check_vitisai() / AMD readiness check, even though
VitisAIExecutionProvider is a first-class entry in EP_DEVICE_SPECS.
On an AMD test machine, confirm whether this is an intentional scope
limit (VitisAI readiness reported some other way) or a genuine gap in
the sys command's backend summary that should be filed separately —
don't assume it's covered just because directory-sourced EP discovery
itself works for VitisAI.

Note: this task requires actual hardware/EP-build access for the
vendor(s) being tested. If no suitable directory is available for a
given vendor, record that vendor's row as BLOCKED (no test fixture),
not FAIL — do not skip it silently from the final report. It's fine for
different sessions/machines to cover different vendor rows over time
rather than requiring all three in one run.


Task 6 — winml perf baseline (no monitor)

Goal: confirm the EP registration/dispatch changes haven't
regressed baseline inference performance or exit codes.

Command (device/EP auto-detected by default; optionally force a
specific vendor with --ep qnn, --ep openvino, or --ep vitisai to
explicitly cover a particular combination rather than relying on
whatever the test machine auto-picks — useful when a machine has more
than one EP installed):

uv run winml perf -m microsoft/resnet-50

Pass criteria: exit code 0; throughput > 300 samples/sec. The
~2.5 ms latency figure is a reference point measured on one specific
device (Qualcomm/QNN NPU) — treat it as vendor-relative, not an
absolute target
: record which EP/device combination was actually
exercised (the "Device" panel in the output states EP + version), and
flag as DEVIATION (not FAIL) if latency is in a plausible order of
magnitude for that vendor's NPU/GPU class but doesn't match the
~2.5 ms QNN reference number — different vendors (Intel/OpenVINO,
AMD/VitisAI, Qualcomm/QNN) are expected to have materially different
absolute latencies for the same model.


Task 7 — winml perf --monitor (hardware monitor)

Goal: confirm the HW monitor path still renders correctly and
doesn't throw monitor/EP errors after the registration changes.

Command:

uv run winml perf -m microsoft/resnet-50 --monitor

Pass criteria: HW monitor output renders (CPU/RAM/NPU utilization
etc.); no monitor- or EP-related errors or tracebacks in output.


Task 8 — winml perf --help wording check

Goal: confirm the --op-tracing help text was generalized away
from vendor-specific (QNN-only) language now that OpenVINO is a second
supported monitor.

Command:

uv run winml perf --help

Pass criteria: help text for --op-tracing mentions "Auto-selects
the monitor for the chosen EP" (or equivalent EP-agnostic phrasing) —
no leftover QNN-specific or vendor-specific wording implying only one
EP is supported.


Task 9 — Reconcile PR description with actual state

Goal: not a test — a documentation fix. After Tasks 1–8 are done,
summarize the discrepancies between the PR body's stated commit list /
baseline-failure claims and what's actually true on cc7a6650.

Pass criteria: a clear written summary (in the final report, not
necessarily a PR edit unless the user asks for one) of what changed
between what the PR says and what's actually on the branch now,
so a reviewer isn't misled.

Final report format

For each task: task number, verdict (PASS / FAIL / DEVIATION / BLOCKED),
one-line evidence summary, and a pointer to full command output if
retained. Close with the high-level goal's three passing criteria,
explicitly confirmed or not.

tezheng pushed a commit that referenced this pull request Jul 7, 2026
Re-verifying PR #1019's Test Plan on top of cc7a665 surfaced six
independent regressions and stale tests. This closes all six.

- Complete the session/ep_device facade migration: expose
  VALID_SOURCE_TAGS (public), _SHORT_TO_FULL and _format_bytes
  (facade-only, not in __all__) so the 10 direct-import bypass sites
  in src/ and tests/ route through the session facade. Satisfies
  test_ep_device_import_rule.py's architecture fitness function.
- Update test_static_analyzer_cli.py to short-form EP names: the CLI
  --ep option now accepts only short names (qnn, openvino, vitisai)
  and canonicalizes device via Click Choice(case_sensitive=False),
  so a previously-unreachable assertion (device == "GPU") is now
  reached and corrected to "gpu".
- Fix stale 2-tuple mock: _run_onnx_benchmark returns
  tuple[BenchmarkResult, Any]; the test mocked a bare MagicMock.
- Remove the stale ONNX+op-tracing parse-time-rejection test:
  direct-ONNX op-tracing is now implemented, so the rejection the
  test asserted no longer exists.
- Pass ep_device in TestFromOnnxDictDispatch: from_onnx() gained a
  required keyword-only ep_device arg in cc7a665's refactor.
- Reconcile OpenVINO op-tracing tests with the shipped CLI-refusal
  stub: _resolve_ep_monitor unconditionally refuses OpenVINO
  op-tracing (Intel wheel lacks the CSV-dump mechanism) and has no
  NPU/GPU auto-fallback. Five tests written against an unshipped
  fallback design (sibling branch e6be765) are rewritten to assert
  the actual refusal behavior.

Also on this branch:
- onnx/__init__.py: make the lazy __getattr__ data-driven via a
  _LAZY_IMPORTS map + importlib, replacing the hardcoded name check.
- qnn_monitor.py: fix a stale docstring reference to a nonexistent
  self._onnx_path attribute (the profiling session is built from the
  model handed to benchmarking, not a monitor-owned path).

Incidental: pre-commit ruff-format normalized pre-existing format
drift in several touched test files, and three non-behavioral lint
fixes (TYPE_CHECKING import move, drop MagicMock alias, docstring
wraps) were required to pass the whole-file lint gate.

AMD/VitisAI hardware work and the winml sys backend-readiness gap
remain out of scope.
tezheng pushed a commit that referenced this pull request Jul 9, 2026
…tale-test alignment

Follow-up fixes on PR #1019 (branch feat/ep-registration-and-monitoring),
found while re-verifying the Test Plan on top of cc7a665 / 5f101e0.

Source hardening (commands/sys.py, session/ep_device.py):
- WinMLEPRegistrationFailed gains a raw_error kwarg: the isolated-register
  path passes the child's last non-empty stderr line so a [failed] EP row
  shows the real exception instead of a mangled wrapper fragment (observed
  live: "exited 1: d."). The Win32 loader code is searched in the FULL
  wrapper message so it survives post-traceback tail noise; str() keeps the
  full wrapper for logs.
- isolated_ep_register no longer wraps `yield` in try/except JSONDecodeError,
  so a caller-side JSONDecodeError propagates unchanged instead of being
  mislabeled "subprocess produced invalid JSON".
- Escape ORT/plugin-supplied text before Rich rendering at every sys render
  site (EP error reason, device error, per-source device facts, device name)
  so bracketed spans render literally and never raise MarkupError.

Stale-test alignment (tests/):
- test_main.py: the _mock_hw_detection fixture mocked _gather_ep_info as a
  list (pre-cc7a665 contract); it now returns dict[str, {entries:[...]}].
  Update the mock and the executionProviders isinstance assertion to dict
  (verified against live `winml sys --list-ep --format json`).
- test_ep_path.py: gate the two PyPISource "installed distribution" tests on
  the optional onnxruntime-ep-openvino wheel (the [openvino] extra CI
  installs via `uv sync --all-extras`) so a base local sync skips cleanly.

All fixes developed test-first. Full unit suite: 5162 passed, 0 failed,
77 skipped. E2E CLI matrix re-verified on AMD (VitisAI/MIGraphX/DML/CPU).
tezheng added a commit that referenced this pull request Jul 13, 2026
Consolidated src/ changes spanning cc7a665..fa9986f + fix:

- cc7a665 feat(session): unified-source EP refactor + P0 fixes +
  simplification pass
- 5f101e0 fix: close out PR #1019 regressions and stale tests
- 057e00b fix(sys/ep): truthful isolated-register errors, hardened
  rendering, stale-test alignment
- (local) refactor(session): rename _ep_short_or_none -> ep_short_or_none;
  shrink session facade __all__ (drop SessionState, InferenceError —
  session-lifecycle types now sourced from session.session where tested).
  Kept in facade: EPDeviceSpec, UnknownListingPick, WinMLEPMonitorMismatch,
  default_device_for_ep, known_ep_short_names, lookup_device_spec — the
  test_ep_device_import_rule fitness function requires them there.

Test updates and non-src changes (docs, pyproject.toml, CI workflow)
land in follow-up commits.
@tezheng
tezheng force-pushed the feat/ep-registration-and-monitoring branch from 057e00b to e17db79 Compare July 13, 2026 02:31
Comment thread src/winml/modelkit/models/auto.py Outdated
Comment thread src/winml/modelkit/session/session.py Outdated
Comment thread src/winml/modelkit/config/precision.py Outdated
Comment thread src/winml/modelkit/config/build.py
Comment thread src/winml/modelkit/session/ep_registry.py

@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 EP-registration + monitoring changes. Most of it holds up well — the subprocess isolation in sys.py, the structured WinMLEPRegistrationFailed parsing, ep_path.py discovery/precedence, the QNN + op-metrics parsing, and the lazy transformers meta_path hook all look solid. A handful of things to flag before merge, the main one being a cache-key regression in from_onnx that can silently serve the wrong cached artifact. Details inline.

Comment thread src/winml/modelkit/models/auto.py Outdated
Comment thread src/winml/modelkit/commands/perf.py Outdated
Comment thread src/winml/modelkit/inference/engine.py Outdated
Comment thread src/winml/modelkit/models/auto.py Outdated
Comment thread tests/unit/session/monitor/qnn/test_csv_parser.py
Comment thread src/winml/modelkit/session/ep_registry.py Outdated
Comment thread src/winml/modelkit/inference/engine.py Outdated
@tezheng

tezheng commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator Author

E2E-verification run (f1086f2b — post-squash)

Ran the full E2E-VERIFICATION.md matrix twice via three parallel scoping agents (sys/help/hygiene/analyze — config/compile/build — perf/composite/op-tracing/directory-EP). Both runs produced the same verdicts (the second run was against the post-squash tree, which is bit-identical to the pre-squash state).

Totals: 29 PASS / 11 ENV / 0 regression.

Host: Windows 11, Intel Core Ultra with Intel AI Boost NPU + Intel Iris Xe GPU (no Qualcomm NPU, no CUDA, no VitisAI). uv run from repo root.

Group A — sys + help + hygiene + analyze (12 PASS / 3 ENV)

# Command Verdict
1 uv run winml --version PASS
2 uv run winml --help PASS
3 uv run winml (no args) PASS
4 uv run winml sys --help PASS
5 uv run winml perf --help PASS
6 uv run winml sys PASS
7 uv run winml sys --format json PASS
8 uv run winml sys --format compact PASS
9 uv run winml sys --list-ep PASS
10 uv run winml sys --list-ep --format json PASS
11 uv run winml sys --list-device PASS
12 uv run winml sys --verbose PASS
13 uv run winml analyze -m microsoft/resnet-50 ENV — analyze -m is click.Path(exists=True); HF id rejected (pre-existing doc gap)
14 uv run winml analyze -m ./convnext/model_opt.onnx ENV — parquet rule fixtures not installed in this checkout
15 uv run winml analyze -m ./convnext/model_opt_qdq.onnx --device npu --ep qnn ENV — same missing parquet fixtures

Group B — config + compile + build (10 PASS / 3 ENV)

# Command Verdict
16 uv run winml config -m ./convnext/model_opt.onnx --device npu -o ./tmp/config-npu.json PASS
17 uv run winml config -m ./convnext/model_opt.onnx --device cpu --ep qnn -o ./tmp/config-cpu-qnn.json PASS
18 uv run winml config -m ./convnext/model_opt.onnx --device cpu --ep qnn@pypi -o ./tmp/config-reject.json PASS (source-tag pin cleanly rejected — intentional scaffolding)
19 uv run winml compile -m ./convnext/model_opt.onnx --ep qnn --device npu ENV — no Qualcomm NPU on host
20 uv run winml compile -m ./convnext/model_opt.onnx --ep openvino --device cpu PASS (compiled in 1.24s)
21 uv run winml compile --list --device npu PASS
22 uv run winml compile --list --device gpu PASS
23 uv run winml compile -m ./convnext/model_opt.onnx --ep qnn@pypi --device npu ENV — @pypi stripped correctly; no QNN NPU
24 uv run winml config -m ./convnext/model_opt.onnx --device cpu -o ./tmp/build.json PASS (prereq for build)
25 uv run winml build -c ./tmp/build.json -m microsoft/resnet-50 -o ./tmp/hf ENV — HF build path requires config.export (pre-existing assert)
26 uv run winml build -c ./tmp/build.json -m ./convnext/model_opt.onnx -o ./tmp/onnx PASS
27 uv run winml build -c ./tmp/build.json -m t5-small -o ./tmp/t5 PASS — composite (encoder + decoder) built end-to-end in 35.4s
28 uv run winml build -c ./tmp/build.json -m ./convnext/model_opt.onnx --ep qnn@pypi -o ./tmp/reject PASS (source-tag pin cleanly rejected)

Group C — perf + composite + op-tracing + directory-EP (7 PASS / 5 ENV)

# Command Verdict
29 uv run winml perf -m microsoft/resnet-50 --device auto --ep auto --monitor PASS (315 samp/s, 4-row monitor rendered)
30 uv run winml perf -m ./convnext/model_opt.onnx --device auto --ep auto PASS (102 samp/s)
31 uv run winml perf -m ./convnext/model_opt_qdq.onnx --device auto PASS (38 samp/s)
32 uv run winml perf -m microsoft/resnet-50 --ep qnn@pypi --device npu ENV — no Qualcomm NPU
33 uv run winml perf -m microsoft/resnet-50 --ep openvino@pypi --device cpu ENV — OpenVINO CPU plugin "Could not deserialize by device xml header" on this Intel
34 uv run winml perf -m ./convnext/model_opt_qdq.onnx --ep qnn --device npu --op-tracing basic ENV — no QNN NPU
35 uv run winml perf -m ./convnext/model_opt.onnx --ep openvino --op-tracing basic PASS (correctly refused with expected "OpenVINO wheels don't implement CSV-dump" message)
36 $env:WINMLCLI_EP_PATH='./x64/Release'; uv run winml perf -m microsoft/resnet-50 --device auto PASS (directory-EP resolves to openvino@directory, 409 samp/s)
37 uv run winml perf -m microsoft/resnet-50 --device gpu --monitor ENV — OpenVINO GPU "CompiledModel was not initialized" on Iris Xe
38 uv run winml perf -m microsoft/resnet-50 --iterations 100 --warmup 10 --batch-size 4 PASS (throughput knobs applied; static batch=1 fallback noted)
39 uv run winml perf -m t5-small --device cpu PASS — composite dispatch, 307 samp/s
40 uv run winml compile -m ./tmp/t5/encoder.onnx --ep openvino --device cpu ENV — doc says encoder.onnx, on-disk artifact is encoder_model.onnx (doc/fixture mismatch)

ENV breakdown

Category Count Rows
No Qualcomm NPU on host (need Snapdragon) 4 19, 23, 32, 34
OpenVINO on this Intel host (CPU deser / GPU init) 2 33, 37
Missing parquet rule fixtures 2 14, 15
Pre-existing doc/code gaps on this branch 2 13 (analyze click.Path), 25 (HF build asserts config.export)
Doc/fixture path mismatch 1 40 (encoder.onnx vs encoder_model.onnx)

Not exercised

  • Anything gated on WINML_TEST_NPU=1 on a Snapdragon host
  • CUDA / VitisAI / MIGraphX EP paths (SDKs not installed)
  • Interactive TTY output (no PTY in agent runs)

Bottom line: no command that was expected to succeed on this hardware regressed. The 11 ENV failures are unchanged from prior runs on the same machine and none are caused by the merge or the post-merge cleanup.

@tezheng
tezheng force-pushed the feat/ep-registration-and-monitoring branch from 100ec0c to f1086f2 Compare July 15, 2026 03:19
Comment thread src/winml/modelkit/build/hf.py Fixed
Comment thread src/winml/modelkit/build/onnx.py Fixed
Comment thread src/winml/modelkit/commands/_ep_arg.py Fixed
Comment thread src/winml/modelkit/commands/perf.py Fixed
Comment thread src/winml/modelkit/config/precision.py Fixed
Comment thread src/winml/modelkit/transformers_compat.py Fixed
Comment thread src/winml/modelkit/utils/cli.py Fixed
Comment thread src/winml/modelkit/winml.py Fixed
Comment thread tests/_helpers.py Fixed
Comment thread tests/integration/ep_path/test_live_msix_openvino.py Fixed
Comment thread src/winml/modelkit/transformers_compat.py Outdated
Comment thread src/winml/modelkit/build/common.py
Comment thread src/winml/modelkit/models/auto.py Outdated
Comment thread src/winml/modelkit/models/auto.py
Comment thread src/winml/modelkit/models/auto.py
Comment thread src/winml/modelkit/session/monitor/qnn/_internal.py Outdated
Comment thread src/winml/modelkit/session/monitor/qnn_monitor.py
Comment thread src/winml/modelkit/commands/perf.py Outdated
Comment thread src/winml/modelkit/session/monitor/op_metrics.py
Comment thread src/winml/modelkit/session/monitor/openvino_monitor.py Outdated
tezheng added a commit that referenced this pull request Jul 16, 2026
Main advanced 9 commits (telemetry, recipes, --export-type #1104), leaving
PR #1019 conflicting — which blocks the pull_request merge ref and therefore
all CI. Re-merge to make the PR mergeable again.

Conflicts, all in commands/build.py + its test (the genai-bundle path #1104
touched):
- Keep origin's --export-type optimized restructure of _maybe_build_genai_bundle
  (want_optimized vs implicit-shortcut), but graft this branch's
  session.resolve_device(EPDeviceTarget) API into it and into the new
  _resolve_optimized_target helper (origin still called the removed
  sysinfo.resolve_device).
- Keep this branch's defer-EP comment on build()'s ep-resolution (matches the
  actual resolve_device code; origin's resolve_check_device_ep comment did not).
- test_build_genai_bundle.py: restore MagicMock import (origin's new _fake_ctx
  needs it), and repoint origin's new --export-type tests from the removed
  sysinfo.resolve_device / resolve_check_device_ep onto session.resolve_device.

All 26 genai-bundle tests pass; build imports + ruff clean.

Claude-Session: https://claude.ai/code/session_01CRVeL37EPZvGhMpQNj1hTF
Comment thread src/winml/modelkit/config/precision.py Fixed
@timenick
timenick dismissed their stale review July 17, 2026 02:05

Review state was submitted in error; retaining the inline comments as comments only.

Comment thread src/winml/modelkit/analyze/analyzer.py
Comment thread src/winml/modelkit/commands/_ep_arg.py Fixed
Comment thread src/winml/modelkit/compiler/stages/compile.py
Comment thread src/winml/modelkit/models/winml/genai_bundle.py
Comment thread src/winml/modelkit/models/winml/genai_bundle.py
Comment thread src/winml/modelkit/transformers_compat.py
… failures, universal op-tracing dispatch, lazy CLI startup

Squashed single-commit form of PR #1019 (feat/ep-registration-and-monitoring),
rebased onto current main.

Highlights:
- EP registration + monitoring: unified-source EP refactor, WinMLEPRegistry /
  EPDeviceTarget resolution (session.resolve_device), catalog-driven EP paths,
  structured EP failures, per-op QNN/OpenVINO/VitisAI monitoring.
- Universal op-tracing dispatch relocated under session/monitor; lazy CLI
  startup (no eager transformers import on --help).
- Integrated with mainline features carried through repeated main merges:
  WinMLManifest, dynamic export controls, genai-bundle build (winml build /
  perf), --export-type, pipeline_tag task fallback.

Quality gates green: `ruff check src/ tests/` clean; `mypy -p winml.modelkit`
= "Success: no issues found in 402 files" (all 108 type errors resolved with
type-level fixes only). License headers present.

Known-remaining: ~136 pre-existing unit-test failures (EP-refactor test drift,
tracked separately) and two latent runtime bugs flagged for follow-up
(genai_session._register_eps missing registry methods; perf --module ep-tuple).

Claude-Session: https://claude.ai/code/session_01CRVeL37EPZvGhMpQNj1hTF
@tezheng
tezheng force-pushed the feat/ep-registration-and-monitoring branch from f28c9fc to b9d13d8 Compare July 17, 2026 06:26
Comment thread src/winml/modelkit/session/ep_device.py
tezheng added 2 commits July 21, 2026 15:22
…typing

Move to transformers>=5,<6 via a [tool.uv] override and neutralize the
transitive standard onnxruntime (pulled by onnxruntime-qnn) with an
always-false `sys_platform == "unobtainium"` marker so it never overwrites
the onnxruntime-windowsml module on disk. Gate the Windows-only winml deps
behind `sys_platform == "win32"` and relax onnxruntime-windowsml to a range
(>=1.24.5,<1.25) to absorb the PEP 440 .post/date version mismatch.

Fix all mypy errors surfaced by the transformers 4->5 API changes:
- Cache.layers is now list[CacheLayerMixin | LinearAttentionCacheLayerMixin];
  narrow to CacheLayerMixin (WinML caches build StaticLayer) via a _layer()
  helper / casts in kv_cache, decoder_wrapper, qwen, marian, mu2, t5, bart.
- resolver: IMAGE_PROCESSOR_MAPPING_NAMES entries became dicts keyed by
  backend; extract the canonical processor name (real tf5 bug fix).
- transformers_compat: pass the new q_offset arg to _ignore_causal_mask_sdpa
  (real tf5 bug fix); annotate monkey-patches.
- sam/bart config-type narrowing; qwen_transformer_only ClassVar assign ignore.
Resolve conflicts preferring branch features, with three deliberate
exceptions where main had the stronger change:

- check_ops.py / op_utils.py: drop the dead `sys_info` writer parameter
  (main's cleanup; it was collected but never persisted per its own comment).
- _perf_genai.py: adopt main's device-native EP preference (#1138) but return
  via the branch's `short_ep_name`/`EPNameOrAlias` idiom; cast str->EPName for
  the EP_SUPPORTED_DEVICES lookup.
- check_patterns.py: keep the branch's single-source-of-truth re-export of
  EP checkers from check_ops (required by test_ep_checker_dedup) plus main's
  RuntimeCheckerRulesPrefilter body; include main's additive NvTensorRTRTX
  choice (a GPU EP, outside the branch's NPU carve-out).

Also fix a pre-existing branch bug (fails identically on the pre-merge tip):
the winml-genai perf path passed the raw EpAtSourceParamType `(ep, source)`
tuple straight into GenaiPerfConfig.ep instead of the EP name. Unpack it like
the ONNX path does. Restores test_explicit_ep_overrides_device /
test_explicit_ep_without_device.

mypy (405 files) and ruff (src/ tests/) are green. This merge introduces zero
new test failures; the 20 remaining failures in analyze/build/perf CLI tests
are pre-existing and identical on the pre-merge tip (stale EP/device-resolution
mocks + one `tuple.device` bug in the build EP path).
Comment thread src/winml/modelkit/commands/_perf_genai.py
Comment thread src/winml/modelkit/models/hf/bart.py
Comment thread src/winml/modelkit/models/hf/decoder_wrapper.py
Comment thread src/winml/modelkit/models/hf/sam.py
Comment thread src/winml/modelkit/models/hf/t5.py
tezheng and others added 4 commits July 21, 2026 20:39
…e key

transformers>=5 dropped the lenient fallback that let AutoConfig.from_pretrained
load a config lacking a `model_type` key (older Hub models such as
prajjwal1/bert-tiny); it now raises "Unrecognized model". transformers 4
returned a base PretrainedConfig in that case.

Add a shared `load_hf_config(auto_config, model_id, ...)` helper that tries
AutoConfig.from_pretrained first and falls back to PretrainedConfig.from_pretrained
only when the model omits model_type (re-raising the original AutoConfig error
otherwise). No architecture-specific inference — the generic config carries the
raw fields and downstream resolution keys off the task / model_class.

The helper takes the caller's own AutoConfig reference as an argument so each
call site's module-level `AutoConfig` stays monkeypatchable in tests. Routed the
loader config-load paths (config.resolve_loader_config, resolution.resolve_composite_components /
resolve_composite_load_task, hf.load_hf_model) through it.

Restores the 8 TestExportPriority[task] tests (regressed by the transformers-5
upgrade); zero new failures vs the pre-fix tip.
## Summary

Add an opt-in `static-split-to-slice` algebraic rewrite that replaces
eligible ONNX `Split` nodes with equivalent input-form `Slice` nodes
while preserving output tensor names.

The rewrite only applies when the input shape, axis, and split
boundaries are statically known. It supports explicit split sizes and
equal splits, avoids malformed or dynamic cases, handles name
collisions, preserves nested-subgraph captures, removes dead generated
nodes and constants, and remains idempotent through the public
optimization path.

## Why this is not a pattern rewrite

The existing pattern framework is designed for fixed-topology
replacements with compatible source and target schemas. Its match result
exposes a single pattern output, and `PatternRewriter` constructs the
replacement using that fixed output mapping.

A `Split` node is variadic: one matched node may have any number of
outputs, and the replacement must generate one `Slice` node plus four
boundary initializers for each output. The replacement topology and
constants therefore depend on the specific matched node rather than on a
fixed target pattern. Representing this correctly as a pattern would
first require broader framework support for variadic output mappings and
match-parameterized replacement topology.

For that reason, this transformation is implemented as a focused
algebraic rewrite rather than adding model-specific behavior or forcing
it into the current fixed-shape pattern abstraction.

---------

Co-authored-by: hualxie <hualxie@microsoft.com>
…pes (#1148)

Add verified CPU (fp32 + fp16) recipes for
MIT/ast-finetuned-audioset-10-10-0.4593, the Audio Spectrogram
Transformer fine-tuned on AudioSet for audio event classification.
Effort L0 (recipe-only); Goal ceiling L2 reached — numeric parity with
PyTorch confirmed (cosine = 1.0). L3 is CLI-BLOCKED because `winml eval`
does not yet support the `audio-classification` task.

## Model metadata

### What the model does

Audio Spectrogram Transformer (AST) fine-tuned on AudioSet-10-10 for
multi-label audio event classification across 527 categories. Processes
mel-spectrogram patches (1024 x 128) through a ViT-style encoder and
outputs per-class logits.

- Evidence: HuggingFace model card, `config.json` (`num_labels: 527`,
`architectures: ["ASTForAudioClassification"]`)
- Confidence: verified

### Primary user stories

- A developer supplies a raw audio spectrogram to obtain class
probabilities for 527 AudioSet event categories for audio tagging /
content analysis.
- Evidence: checkpoint task (`audio-classification`), model card
- Confidence: verified

### Supported tasks

| Task | Support surfaces | Evidence | Confidence |
|---|---|---|---|
| audio-classification | checkpoint, transformers, optimum-onnx, winml |
Optimum vendor-native; `winml inspect` confirms default support |
verified |

### Model architecture

```text
ASTForAudioClassification
├── Embeddings
│   ├── Patch embedding (Conv2d 16x16, stride 10x10 → 768-d)
│   ├── CLS + distillation tokens
│   └── Positional embedding (interpolated, 1024+2 positions)
├── Encoder x 12
│   ├── Self-attention (12 heads, 768-d)
│   ├── Feed-forward (768 → 3072 → 768, GELU)
│   └── Residual + LayerNorm
└── Classification head (768 → 527 logits, mean of CLS + distill)
```

- Source/confidence: `ASTForAudioClassification` source and pinned
checkpoint config (`verified`).

## Validation and support evidence

### Baseline

- **origin/main**: `38767add6f91c7b10b6394fae3af6f437e02effd`
- **winml version**: 0.2.0
- **Optimum probe**: VENDOR-ONLY — `audio-classification` and
`feature-extraction` registered by Optimum vendor for
`audio-spectrogram-transformer`; winml adds nothing.
- **Build**: PASS (22.5s, 557 ONNX nodes, 100% tagged)
- **Perf**: PASS (367ms avg, 2.72 samples/s, RAM +540MB)
- **Eval**: CLI-BLOCKED (`audio-classification` not in `winml eval`
supported tasks)
- **Goal floor**: L1 (baseline builds and perfs)
- **Starting recipe**: auto-config output identical to shipped recipe
(no delta)

### Goal

- **Effort**: L0 (recipe-only — Optimum vendor-native, auto-config
correct, no code change)
- **Goal ceiling**: L2 (numeric vs PyTorch — CPU-reachable; L3
CLI-BLOCKED)
- **Outcome**: L0 (recipe + report)
- **Success definition**: L0 = build PASS; L1 = perf runs without crash;
L2 = cosine similarity >= 0.999 and max abs diff < 1e-3 vs PyTorch

### Outcome

- **Highest reached**: L2 PASS on both (cpu, cpu, fp32) and (cpu, cpu,
fp16)
- **Coverage**: full (all target tuples passed or CLI-BLOCKED)
- **Deferred tuples**: none
- **Shipped recipes**:
-
`examples/recipes/MIT_ast-finetuned-audioset-10-10-0.4593/cpu/cpu/audio-classification_fp32_config.json`
-
`examples/recipes/MIT_ast-finetuned-audioset-10-10-0.4593/cpu/cpu/audio-classification_fp16_config.json`

### Per-EP/device/precision results

| Tier | EP / Device | Precision | Verdict | Mean | p50 | Throughput |
RAM delta | Task metric |
|---|---|---|---|---|---|---|---|---|
| L0 (build) | CPUExecutionProvider / cpu | fp32 | PASS | — | — | — | —
| — |
| L0 (build) | CPUExecutionProvider / cpu | fp16 | PASS | — | — | — | —
| — |
| L1 (perf) | CPUExecutionProvider / cpu | fp32 | PASS | 438.76 ms |
433.28 ms | 2.28 samples/s | +538.4 MB | — |
| L1 (perf) | CPUExecutionProvider / cpu | fp16 | PASS | 409.30 ms |
411.60 ms | 2.44 samples/s | +539.8 MB | — |
| L2 (numeric) | CPUExecutionProvider / cpu | fp32 | PASS | — | — | — |
— | cosine=1.00000000, max_abs=1.03e-05 |
| L2 (numeric) | CPUExecutionProvider / cpu | fp16 | PASS | — | — | — |
— | cosine=1.00000000, max_abs=1.03e-05 |
| L3 (eval) | CPUExecutionProvider / cpu | fp32 | CLI-BLOCKED | — | — |
— | — | `audio-classification` not in eval task list |
| L3 (eval) | CPUExecutionProvider / cpu | fp16 | CLI-BLOCKED | — | — |
— | — | same as above |

### Delta

Recipe is identical to `winml config` auto-config output — no field
overrides. Filed for verified `cpu/cpu` coverage claim that the
out-of-the-box auto-build does not formally establish.
`examples/recipes/README.md` remains untouched.

### Analyze summary — component level and op level

ANALYZE-UNAVAILABLE: `winml analyze` exited with error "No runtime rule
parquet files were found" — rule parquet files are not present in this
installation. This is an environment gap, not a model issue. Static rule
analysis was not possible on this host.

### Reproduce commands

```powershell
# L0 — build fp32
winml build -m MIT/ast-finetuned-audioset-10-10-0.4593 -c examples/recipes/MIT_ast-finetuned-audioset-10-10-0.4593/cpu/cpu/audio-classification_fp32_config.json -o $OUT/fp32 --ep cpu --device cpu --rebuild

# L0 — build fp16
winml build -m MIT/ast-finetuned-audioset-10-10-0.4593 -c examples/recipes/MIT_ast-finetuned-audioset-10-10-0.4593/cpu/cpu/audio-classification_fp16_config.json -o $OUT/fp16 --ep cpu --device cpu --rebuild

# L1 — perf fp32
winml perf -m $OUT/fp32/model.onnx --ep cpu --device cpu

# L1 — perf fp16
winml perf -m $OUT/fp16/model.onnx --ep cpu --device cpu

# L2 — numeric comparison vs PyTorch (Python, seed=42)
python -c "
import numpy as np, torch, onnxruntime as ort
from transformers import ASTForAudioClassification
model = ASTForAudioClassification.from_pretrained('MIT/ast-finetuned-audioset-10-10-0.4593'); model.eval()
np.random.seed(42); x = np.random.uniform(-1, 1, (1, 1024, 128)).astype(np.float32)
pt = model(torch.from_numpy(x)).logits.numpy()
for tag, path in [('fp32','$OUT/fp32/model.onnx'), ('fp16','$OUT/fp16/model.onnx')]:
    ox = ort.InferenceSession(path, providers=['CPUExecutionProvider']).run(None, {'input_values': x})[0]
    cos = np.dot(pt.flatten(), ox.flatten()) / (np.linalg.norm(pt) * np.linalg.norm(ox))
    print(f'{tag}: cosine={cos:.8f}, max_abs={np.max(np.abs(pt - ox)):.2e}')
"
```

Co-authored-by: Yong Yue <yongyue@microsoft.com>
…t-qa (#1093)

This PR adds `impira/layoutlm-document-qa` support to the LayoutLM
export path in `winml-cli`, along with flat fp32 and w8a16 document-QA
recipes. It ships at Outcome `L1` (`effort=L1-light`, `goal_ceiling=L2`)
and the supplied tester verdicts reached `L2 PASS`. The main caveat is
that raw `winml perf` with random dummy inputs crashes on this
checkpoint because `token_type_ids` can contain `1` while
`type_vocab_size=1`; the tester therefore used the valid-input fallback
path for L1 measurements.

Implementation summary: this adds `LayoutLMQAIOConfig` registration for
(`layoutlm`, `question-answering`), a
`ZeroTokenTypeLayoutLMTextInputGenerator` that forces all-zero
`token_type_ids`, generic `bbox` ordering support in
`src\winml\modelkit\onnx\io.py`, README indexing, fp32/w8a16 recipes,
and unit coverage in `tests/unit/export/test_onnx_config_overrides.py`
and `tests/unit/export/test_pytorch_export.py`.

1. **Recipe path(s)**
-
`examples/recipes/impira_layoutlm-document-qa/question-answering_fp32_config.json`
-
`examples/recipes/impira_layoutlm-document-qa/question-answering_w8a16_config.json`
- Code paths in scope: `src/winml/modelkit/models/hf/layoutlm.py`,
`src/winml/modelkit/models/hf/__init__.py`,
`src/winml/modelkit/onnx/io.py`,
`tests/unit/export/test_onnx_config_overrides.py`,
`tests/unit/export/test_pytorch_export.py`

2. **README row**
   - Added row in `examples/recipes/README.md`: **yes**.

3. **Build output dir**
- Refreshed isolated baseline against current `origin/main`
`f102649b63e6bf013a180523d15e6cab75906dd9`.
- Baseline output dir:
`temp\impira_layoutlm-document-qa\baseline_offline`
   - fp32 output dir: `temp\impira_layoutlm-document-qa\fp32`
   - w8a16 output dir: `temp\impira_layoutlm-document-qa\w8a16`
   - Exact refreshed baseline command:
`C:\Users\qiowu\source\repos\winml-cli\.venv\Scripts\winml.exe build -m
impira/layoutlm-document-qa -o
temp\impira_layoutlm-document-qa\baseline_offline --ep cpu --device cpu
--no-analyze --no-optimize --no-quant --no-compile --rebuild`
- Refreshed baseline result: `FAIL` with exact evidence `Error:
Unrecognized configuration class <class
'transformers.models.layoutlm.configuration_layoutlm.LayoutLMConfig'>
for this kind of AutoModel: AutoModelForNextSentencePrediction.`
- Engineering delta claimed by this PR: current `origin/main` fails at
the refreshed isolated baseline above, while the branch-specific
LayoutLM QA registration/input-generation changes below produce
successful fp32 and w8a16 builds.

4. **Build log**
- fp32 build command: `./.venv/Scripts/winml.exe build -c
examples/recipes/impira_layoutlm-document-qa/question-answering_fp32_config.json
-m impira/layoutlm-document-qa -o temp\impira_layoutlm-document-qa\fp32
--ep cpu --device cpu --rebuild`
     - Result: `✅ Build complete in 278.1s`
     - Artifact: `temp\impira_layoutlm-document-qa\fp32\model.onnx`
- I/O: inputs `input_ids`, `bbox`, `attention_mask`, `token_type_ids` as
INT32 with shapes `[1,512]`, `[1,512,4]`, `[1,512]`, `[1,512]`; outputs
`start_logits`, `end_logits` as FLOAT `[1,512]`
     - Initializer histogram: `FLOAT=206, INT64=63`
     - Build quant: `null`
- w8a16 build command: `./.venv/Scripts/winml.exe build -c
examples/recipes/impira_layoutlm-document-qa/question-answering_w8a16_config.json
-m impira/layoutlm-document-qa -o temp\impira_layoutlm-document-qa\w8a16
--rebuild`
- Result: `✅ Quantize 82.3s` / `Precision: uint8/uint16
(weight/activation)` / `✅ Build complete in 392.4s`
     - Artifact: `temp\impira_layoutlm-document-qa\w8a16\model.onnx`
     - I/O: same contract as fp32
- Initializer histogram: `FLOAT=411, INT32=196, INT64=63, UINT16=225,
UINT8=196`
- Build quant: `mode=static, weight_type=uint8, activation_type=uint16`

5. **Appended findings**
- Lane A only (not staged in this PR): local skill-repo commit
`26e1b7b1d83919d53c673c142854dbda16c6eb84` appended `layoutlm-001`,
`layoutlm-002`, and `layoutlm-003` to
`copilot-skills/dev_skill/adding-model-support/model_knowledge/layoutlm.json`.
- Methodology finding `_meta-060` was also appended locally in the skill
repo; it is declared here for traceability but intentionally excluded
from this model PR diff.

6. **Optimum-coverage probe**
- Vendor registrations:
`['feature-extraction','fill-mask','text-classification','token-classification']`
- After WinML changes:
`['feature-extraction','fill-mask','text-classification','token-classification','question-answering']`
   - Added by WinML: `['question-answering']`
- Verdict: branch state adds `question-answering` coverage for LayoutLM
beyond the vendor baseline.

7. **Claimed (Effort, Goal, Outcome)**
   - `model_id`: `impira/layoutlm-document-qa`
   - `model_type`: `layoutlm`
   - `effort`: `L1-light`
   - `goal_ceiling`: `L2`
   - `outcome`: `L1`
   - `target_eps`: `cpu`
- Planner facts carried into the implementation:
`architectures=["LayoutLMForQuestionAnswering"]`,
`model_type="layoutlm"`, `is_encoder_decoder=false`,
`type_vocab_size=1`, `max_position_embeddings=514`,
`max_2d_position_embeddings=1024`; the critical finding was that vendor
dummy generation can emit `token_type_ids=1`, so this checkpoint
required forcing all-zero `token_type_ids`.
- The claimed delta is specifically from refreshed-current-main baseline
`FAIL` (item 3) to branch-state recipe builds/L1/L2 `PASS` evidence
(items 4, 8, 10, 11).

8. **Goal-ladder verdict table**

Catalog gate summary: refreshed-current-main `baseline_build=FAIL`,
`verdict=real-engineering`, `file_pr=true`.

   | Tier | Verdict | Evidence |
   |---|---|---|
| L0 | PASS | The refreshed isolated baseline against current
`origin/main` failed with `Error: Unrecognized configuration class
<class
'transformers.models.layoutlm.configuration_layoutlm.LayoutLMConfig'>
for this kind of AutoModel: AutoModelForNextSentencePrediction.` After
the branch's LayoutLM QA export/input-generation changes, both recipe
builds succeeded. fp32 produced
`temp\impira_layoutlm-document-qa\fp32\model.onnx` with INT32 inputs
(`input_ids`, `bbox`, `attention_mask`, `token_type_ids`) and FLOAT
outputs (`start_logits`, `end_logits`), histogram `FLOAT=206, INT64=63`,
and `build_quant=null`. w8a16 produced
`temp\impira_layoutlm-document-qa\w8a16\model.onnx` with the same I/O
contract, histogram `FLOAT=411, INT32=196, INT64=63, UINT16=225,
UINT8=196`, and `build_quant={mode: static, weight_type: uint8,
activation_type: uint16}`. |
| L1 | PASS | Providers: `['AzureExecutionProvider',
'CPUExecutionProvider']`. Raw `winml perf` on both artifacts crashed
with `Error: Benchmark failed: Inference failed | Context:
error=[ONNXRuntimeError] : 2 : INVALID_ARGUMENT : Non-zero status code
returned while running Gather node.
Name:'/layoutlm/embeddings/token_type_embeddings/Gather' Status Message:
indices element out of data bounds, idx=1 must be within the inclusive
range [-1,0]'`. Tester then used the `_meta-017` valid-input fallback
script; per-(EP,device) results are in item 10. |
| L2 | PASS | fp32 vs PT: `start_logits` cosine `0.9999999999219783`,
max_abs `0.0022459030151367188`, mean_abs `2.549774944782257e-05`;
`end_logits` cosine `0.9999999999193658`, max_abs
`0.0023813247680664062`, mean_abs `2.7379952371120453e-05`. w8a16 vs PT:
`start_logits` cosine `0.9522225190737116`, max_abs
`13.425172805786133`, mean_abs `2.4731171131134033`; `end_logits` cosine
`0.9597506819201421`, max_abs `12.746326446533203`, mean_abs
`2.3781232833862305`. |

9. **Methodology-evolution declaration**
- `_meta-060` (`silent-failure`) was appended locally in the Lane A
skill repo: explicit `winml build` overrides such as `--device cpu --ep
cpu` can silently clear `config.quant`, yielding a successful build of
the wrong precision for a quant recipe.
- Reviewer/tester guidance was updated locally so quant recipes are
validated from the preserved quant config and artifact, not from a
superficially successful overridden build.

10. **Perf & eval data**

Raw `winml perf` on both artifacts crashed on random dummy inputs with
the Gather/token-type error quoted in item 8, so the tester used the
valid-input fallback path for the L1 measurements below.

| EP / Device | Precision | Verdict | Mean | p50 | Throughput | RAM Δ |
Task metric |
   |---|---|---|---|---|---|---|---|
| CPUExecutionProvider / cpu | fp32 | PASS | `5005.385395001213 ms` |
`5076.764100005676 ms` | `0.19978481597015124 samples/s` | `+467062784 B
total (+358121472 B session init)` | `N/A (goal ceiling L2)` |
| CPUExecutionProvider / cpu | w8a16 | PASS | `5581.969174998085 ms` |
`5745.492499998363 ms` | `0.1791482483420097 samples/s` | `+311275520 B
total (+25878528 B session init)` | `N/A (goal ceiling L2)` |

11. **Component / op-level data**
- Analyze command: `./.venv/Scripts/winml.exe analyze --model
temp\impira_layoutlm-document-qa\fp32\model.onnx --ep all --output
temp\impira_layoutlm-document-qa\analyze_fp32.json`
   - Artifact: `temp\impira_layoutlm-document-qa\analyze_fp32.json`
- Model summary: `395` total operators across `16` unique operator
types.
   - Per-EP classification:
- `QNNExecutionProvider / NPU`: `16 supported / 0 partial / 0
unsupported / 0 unknown` (`395` total ops covered)
- `OpenVINOExecutionProvider / NPU`: `15 supported / 1 partial / 0
unsupported / 0 unknown`; partial op: `OP/ai.onnx/Slice`
- `VitisAIExecutionProvider / NPU`: `0 supported / 0 partial / 0
unsupported / 16 unknown` (no rule data)

12. **Reproducible commands**

```powershell
C:\Users\qiowu\source\repos\winml-cli\.venv\Scripts\winml.exe build -m impira/layoutlm-document-qa -o temp\impira_layoutlm-document-qa\baseline_offline --ep cpu --device cpu --no-analyze --no-optimize --no-quant --no-compile --rebuild
.\.venv\Scripts\winml.exe build -c examples\recipes\impira_layoutlm-document-qa\question-answering_fp32_config.json -m impira/layoutlm-document-qa -o temp\impira_layoutlm-document-qa\fp32 --ep cpu --device cpu --rebuild
python temp\impira_layoutlm-document-qa\layoutlm_qa_checks.py inspect --model temp\impira_layoutlm-document-qa\fp32\model.onnx --build-config temp\impira_layoutlm-document-qa\fp32\winml_build_config.json --label fp32
.\.venv\Scripts\winml.exe build -c examples\recipes\impira_layoutlm-document-qa\question-answering_w8a16_config.json -m impira/layoutlm-document-qa -o temp\impira_layoutlm-document-qa\w8a16 --rebuild
python temp\impira_layoutlm-document-qa\layoutlm_qa_checks.py inspect --model temp\impira_layoutlm-document-qa\w8a16\model.onnx --build-config temp\impira_layoutlm-document-qa\w8a16\winml_build_config.json --label w8a16
.\.venv\Scripts\winml.exe perf --model temp\impira_layoutlm-document-qa\fp32\model.onnx --ep cpu --device cpu
.\.venv\Scripts\winml.exe perf --model temp\impira_layoutlm-document-qa\w8a16\model.onnx --ep cpu --device cpu
python temp\impira_layoutlm-document-qa\layoutlm_qa_checks.py perf --model temp\impira_layoutlm-document-qa\fp32\model.onnx --model-id impira/layoutlm-document-qa --label fp32
python temp\impira_layoutlm-document-qa\layoutlm_qa_checks.py perf --model temp\impira_layoutlm-document-qa\w8a16\model.onnx --model-id impira/layoutlm-document-qa --label w8a16
python temp\impira_layoutlm-document-qa\layoutlm_qa_checks.py compare --model temp\impira_layoutlm-document-qa\fp32\model.onnx --model-id impira/layoutlm-document-qa --label fp32
python temp\impira_layoutlm-document-qa\layoutlm_qa_checks.py compare --model temp\impira_layoutlm-document-qa\w8a16\model.onnx --model-id impira/layoutlm-document-qa --label w8a16
.\.venv\Scripts\winml.exe analyze --model temp\impira_layoutlm-document-qa\fp32\model.onnx --ep all --output temp\impira_layoutlm-document-qa\analyze_fp32.json
python temp\impira_layoutlm-document-qa\layoutlm_qa_checks.py summarize-analyze --analyze-json temp\impira_layoutlm-document-qa\analyze_fp32.json
```

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
github-actions Bot and others added 26 commits July 23, 2026 13:10
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ing' into dingmaomaobjtu-fix-pr-1019-review

# Conflicts:
#	tests/unit/session/test_winml_session.py
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Comment thread src/winml/modelkit/session/ep_device.py
Comment thread src/winml/modelkit/session/ep_device.py
Comment thread src/winml/modelkit/session/session.py
Comment thread src/winml/modelkit/sysinfo/device.py Fixed
Comment thread src/winml/modelkit/utils/constants.py
Comment thread src/winml/modelkit/utils/constants.py
Comment thread src/winml/modelkit/utils/constants.py
github-actions Bot and others added 3 commits July 24, 2026 02:15
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
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.

6 participants