Skip to content

feat(cgc): inspect D3D12 adapters and the MLIR patterns their drivers… - #1454

Merged
Zac (tezheng) merged 2 commits into
release/v0.4.0from
patch/release_v0.4.0-cgc_adapter
Sep 23, 2026
Merged

Zac (tezheng) merged 2 commits into
release/v0.4.0from
patch/release_v0.4.0-cgc_adapter

Conversation

@tezheng

Copy link
Copy Markdown
Collaborator

… declare

Two questions get asked on every new machine and after every driver update: which D3D12 adapters are here, and what does this driver's MLIR-program implementation claim it can match. Both are answered through public Windows APIs with ctypes, so nothing has to be built and no extra package installed.

winml cgc adapters # which adapters are here
winml cgc patterns [-a ADAPTER] [-v] # what a driver declares
winml cgc patterns -a ADAPTER --dump [--overwrite] # keep it on disk
winml cgc patterns [-a ADAPTER] --open [FILE] # look at a dump

Listing adapters needs nothing beyond Windows. Anything touching MLIR patterns needs an Agility SDK D3D12Core.dll of SDK 720 or newer, named by --d3d12-dir or $WINML_D3D12_DIR/$D3D12_DIR, or dropped in bin/ beside the venv or the install; the inbox runtime does not serve the preview feature. Which core answered opens every run on the redist: line, because the same driver reports different things through the 720 and 721 exchange shapes. When none can be found, the MLIR column reads ? rather than no -- the question could not be asked -- and the failure names every place that was searched.

With no -a, patterns asks every adapter and prints one line each; a driver that claims the exchange and then refuses it costs only its own line, on stderr, and the run exits 1 at the end. A -a number is an index when one exists and otherwise a description substring, so -a 5060 finds the RTX 5060.

A dump is filed under patterns/// and holds patterns.mlir, metadata.txt (the six keys dxcgc-dump-driver-patterns writes, plus which redist and ABI produced it and how the stored payload differs from the one received) and patterns.json: one record per pattern -- index, name, kind, benefit, rule count, source file, kernel, line count and its own text -- plus a summary, per-kind and per-source grouping and a format tag, so nothing downstream has to parse MLIR. Text payloads are normalised once, where the exchange returns them: the trailing NUL is dropped and every line break becomes LF, so the counts, patterns.mlir and patterns.json describe the same bytes. Bytecode is left alone.

Counting treats a pattern as one cgc_pattern.pattern declaration and a rule as one flat match alternative after any_of expansion, because comparing declaration counts alone across drivers can read a loss where the matching surface grew. Comments are blanked rather than deleted so offsets stay valid, the displayed text keeps its comments, and a brace inside a comment cannot unbalance a slice.

--open shows a dump in the CGC Pattern Atlas, a self-contained page that ships inside the package and is resolved with importlib.resources, so it works from a wheel, an editable install and a checkout alike. A dump is embedded into a copy of the page named after it, with every "<" escaped so a pattern's own text cannot close the script element. --open FILE shows a dump taken earlier and asks no driver anything; anything that is not a patterns.json is a usage error.

Verified on an RTX 5060 (driver 32.0.16.3004), an AMD 880M and WARP against the SDK 720 and 721 redists in DXCGC-Drop10.0-x64 and against Drop11: 51 patterns / 63 rules on the 720 path, byte-identical dumps across refactors, and the documented exit codes on every failure path. docs/commands/cgc.md covers both commands, every flag and scenario, the dump layout and patterns.json. TODO(tests) markers record the gaps left untested.

… declare

Two questions get asked on every new machine and after every driver update:
which D3D12 adapters are here, and what does this driver's MLIR-program
implementation claim it can match. Both are answered through public Windows
APIs with ctypes, so nothing has to be built and no extra package installed.

  winml cgc adapters                                 # which adapters are here
  winml cgc patterns [-a ADAPTER] [-v]               # what a driver declares
  winml cgc patterns -a ADAPTER --dump [--overwrite] # keep it on disk
  winml cgc patterns [-a ADAPTER] --open [FILE]      # look at a dump

Listing adapters needs nothing beyond Windows. Anything touching MLIR patterns
needs an Agility SDK D3D12Core.dll of SDK 720 or newer, named by --d3d12-dir or
$WINML_D3D12_DIR/$D3D12_DIR, or dropped in bin/ beside the venv or the install;
the inbox runtime does not serve the preview feature. Which core answered opens
every run on the redist: line, because the same driver reports different things
through the 720 and 721 exchange shapes. When none can be found, the MLIR column
reads ? rather than no -- the question could not be asked -- and the failure
names every place that was searched.

With no -a, `patterns` asks every adapter and prints one line each; a driver that
claims the exchange and then refuses it costs only its own line, on stderr, and
the run exits 1 at the end. A -a number is an index when one exists and otherwise
a description substring, so `-a 5060` finds the RTX 5060.

A dump is filed under patterns/<adapter-slug>/<driver-version>/ and holds
patterns.mlir, metadata.txt (the six keys dxcgc-dump-driver-patterns writes, plus
which redist and ABI produced it and how the stored payload differs from the one
received) and patterns.json: one record per pattern -- index, name, kind, benefit,
rule count, source file, kernel, line count and its own text -- plus a summary,
per-kind and per-source grouping and a format tag, so nothing downstream has to
parse MLIR. Text payloads are normalised once, where the exchange returns them:
the trailing NUL is dropped and every line break becomes LF, so the counts,
patterns.mlir and patterns.json describe the same bytes. Bytecode is left alone.

Counting treats a pattern as one cgc_pattern.pattern declaration and a rule as
one flat match alternative after any_of expansion, because comparing declaration
counts alone across drivers can read a loss where the matching surface grew.
Comments are blanked rather than deleted so offsets stay valid, the displayed
text keeps its comments, and a brace inside a comment cannot unbalance a slice.

--open shows a dump in the CGC Pattern Atlas, a self-contained page that ships
inside the package and is resolved with importlib.resources, so it works from a
wheel, an editable install and a checkout alike. A dump is embedded into a copy
of the page named after it, with every "<" escaped so a pattern's own text cannot
close the script element. --open FILE shows a dump taken earlier and asks no
driver anything; anything that is not a patterns.json is a usage error.

Verified on an RTX 5060 (driver 32.0.16.3004), an AMD 880M and WARP against the
SDK 720 and 721 redists in DXCGC-Drop10.0-x64 and against Drop11: 51 patterns /
63 rules on the 720 path, byte-identical dumps across refactors, and the
documented exit codes on every failure path. docs/commands/cgc.md covers both
commands, every flag and scenario, the dump layout and patterns.json.
TODO(tests) markers record the gaps left untested.

Claude-Session: https://claude.ai/code/session_01AUbPTmiBrAd6G1MFudgbkn
Comment thread tests/unit/commands/test_cgc.py Fixed
Comment thread src/winml/modelkit/commands/cgc.py Fixed
Use consistent module-qualified ctypes imports and keep hardware-test adapter access in the successful enumeration path. Preserve existing hardware skips, runtime behavior, and lambdas without suppressing either CodeQL alert.
@tezheng
Zac (tezheng) marked this pull request as ready for review September 23, 2026 09:23
@tezheng
Zac (tezheng) requested a review from a team as a code owner September 23, 2026 09:23
@tezheng
Zac (tezheng) merged commit b8d9073 into release/v0.4.0 Sep 23, 2026
9 checks passed
@tezheng
Zac (tezheng) deleted the patch/release_v0.4.0-cgc_adapter branch September 23, 2026 09:24
Zac (tezheng) added a commit to tezheng/winml-cli that referenced this pull request Sep 24, 2026
Pattern parsing:
- Quoted text is no longer read as structure. A "}" inside a string ended a
  pattern early and changed its kind; a quoted declaration followed by a real
  brace became a phantom record; a quoted any_of multiplied the rule count. The
  payload is now read through two views built in one pass (_blank_views):
  values, with only comments blanked, because drivers name a pattern's kind and
  kernel inside strings; and structure, with string contents blanked too, which
  alone decides where declarations, braces and any_of blocks are. One brace
  walker, _block, replaces _balanced and _any_of_branches.
- The rule count stays the cross product of a pattern's any_of groups: the
  dialect allows any_of only at the top level of a pattern (cgc_pattern_ops.td,
  AnyOfOp: HasParent<PatternOp>). The docs now say so.

Dumps:
- A dump is no longer lost to a reader that stops early. report_patterns
  collects its whole answer and prints it only once the dump is on disk, so
  `--dump | head -1` with --overwrite leaves a complete dump. What is printed,
  and its order, is unchanged.
- A dump that cannot be written -- a read-only or locked destination, a full
  disk -- is reported as "could not write the dump", exit 1, not a traceback.

--open:
- The seeded atlas page is named from the dump's resolved path plus a hash of
  it, so two captures under different roots no longer share a page, and one
  dump opened by two spellings of its path reuses one.
- A path the OS refuses to stat is a usage error (exit 2); an unreadable file
  says "could not be read", a malformed one "is not JSON"; a UTF-8 BOM is
  accepted. --dump with an --open filename is a usage error, and the docs say
  so instead of claiming the new dump is shown.

Adapters:
- list_adapters releases everything it acquired when any call after the factory
  fails, including CreateAdapterList and Sort, and tries the GENERIC_ML then
  CORE_COMPUTE list in one loop.

Cleanup, no change to output: one-caller helpers (is_mlir_bytecode,
clear_dump, write_patterns_json, redist_line, adapter_type, adapter_attrs,
make_version_number) are inlined; CGC_MAX_IR_VERSION is stored packed; vcall
builds its prototype from the vtable slot index; --d3d12-dir is declared once;
hashlib and webbrowser load only for --open.

Tests cover each of the above, drive helpers through their callers, and are
clean under pyright strict and default mode on cgc.py and test_cgc.py; mypy and
ruff are clean. Checked on an RTX 5060: the listing and the sweep print as
before, and a dump -- also through `| head -1` -- is byte-identical, 51
patterns / 63 rules.

Claude-Session: https://claude.ai/code/session_01Vx4R8Vb5rTZruzRTWJzWtR
Zac (tezheng) added a commit to tezheng/winml-cli that referenced this pull request Sep 24, 2026
Pattern parsing:
- Quoted text is no longer read as structure. A "}" inside a string ended a
  pattern early and changed its kind; a quoted declaration followed by a real
  brace became a phantom record; a quoted any_of multiplied the rule count. The
  payload is now read through two views built in one pass (_blank_views):
  values, with only comments blanked, because drivers name a pattern's kind and
  kernel inside strings; and structure, with string contents blanked too, which
  alone decides where declarations, braces and any_of blocks are. One brace
  walker, _block, replaces _balanced and _any_of_branches.
- The rule count stays the cross product of a pattern's any_of groups: the
  dialect allows any_of only at the top level of a pattern (cgc_pattern_ops.td,
  AnyOfOp: HasParent<PatternOp>). The docs now say so.

Dumps:
- A dump is no longer lost to a reader that stops early. report_patterns
  collects its whole answer and prints it only once the dump is written, or
  has failed, so `--dump | head -1` with --overwrite leaves a complete dump.
  What is printed, and its order, is unchanged.
- A write that fails part-way through a dump -- a read-only destination, a
  locked file, a full disk -- is reported as "could not write the dump", exit
  1, not a traceback, and the driver's answer is still printed.

--open:
- The seeded atlas page is named from the dump's resolved path plus a hash of
  it, so two captures under different roots no longer share a page, and one
  dump opened by two spellings of its path reuses one.
- A path the OS refuses to stat is a usage error (exit 2); an unreadable file
  says "could not be read", a malformed one "is not JSON"; a UTF-8 BOM is
  accepted. --dump with an --open filename is a usage error, and the docs
  (prose, flags table and exit codes) say so instead of claiming the new dump
  is shown.

Adapters:
- list_adapters releases everything it acquired when anything after the
  factory raises, Ctrl-C included, never releases a list twice, and tries the
  GENERIC_ML then CORE_COMPUTE list in one loop.

Cleanup, no change to output: one-caller helpers (is_mlir_bytecode,
clear_dump, write_patterns_json, redist_line, adapter_type, adapter_attrs,
make_version_number) are inlined; CGC_MAX_IR_VERSION is stored packed; vcall
builds its prototype from the vtable slot index; --d3d12-dir is declared once;
hashlib and webbrowser load only for --open.

Tests cover the parsing, dump and --open changes and drive helpers through
their callers; list_adapters needs DXCore and has no unit test. pyright (strict
and default), mypy and ruff are clean on cgc.py and test_cgc.py.

Checked on an RTX 5060 before the final error-path fixes, which leave the
success path as it was: the listing and the sweep print as before, and a dump
-- also through `| head -1` -- is byte-identical, 51 patterns / 63 rules.

Claude-Session: https://claude.ai/code/session_01Vx4R8Vb5rTZruzRTWJzWtR
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.

2 participants