[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M69AHF6985E28BM055PD4FX4":3,"skill-same:01M69AHF6985E28BM055PD4FX4":68},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":39,"linkOnly":40,"markdownContent":41,"markdownBody":42,"frontMatter":43,"refName":44,"commitSha":45,"skillDirectory":46,"forksCount":47,"repositoryHtmlUrl":48,"repositoryHomepageUrl":49,"ownerHtmlUrl":50,"ownerBlogUrl":51,"ownerDescription":52,"updatedAt":53,"install":54,"resources":67},"01M69AHF6985E28BM055PD4FX4","onnx-opset-bump-checklist",null,"Step-by-step checklist for bumping the pinned ONNX dependency \u002F opset in ONNX Runtime (e.g. ONNX 1.21 \u002F opset 26 → 1.22 \u002F opset 27). Use when integrating a new ONNX release or release-candidate, updating the cmake\u002Fdeps.txt onnx pin or the cmake\u002Fexternal\u002Fonnx submodule, regenerating cmake\u002Fpatches\u002Fonnx\u002Fonnx.patch, raising kMaxSupportedOpset, or adding a new opset's CPU kernels. Covers the file taxonomy, archive-hash procedures, patch rebase\u002Fmirror rules, the RC→formal strategy, and the optimizer\u002FEP gotchas that the automated audit script misses.","microsoft\u002Fonnxruntime","microsoft","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F6154722?v=4","01M698VYDXZ6JW5WH3CRV650R8","Microsoft","01M69AGY0EW9AJXAH7HK223SQZ",22300,"MIT","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fonnxruntime\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fonnx-opset-bump-checklist\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"onnx","bump","checklist","onnx opset bump checklist","strategy","archive","runtime","for","mirror","rebase","release","dependency","CPU","audit","optimizer","candidate","patch","when","cmake","deps","2026-11-10 10:12:30",false,"---\nname: onnx-opset-bump-checklist\ndescription: Step-by-step checklist for bumping the pinned ONNX dependency \u002F opset in ONNX Runtime (e.g. ONNX 1.21 \u002F opset 26 → 1.22 \u002F opset 27). Use when integrating a new ONNX release or release-candidate, updating the cmake\u002Fdeps.txt onnx pin or the cmake\u002Fexternal\u002Fonnx submodule, regenerating cmake\u002Fpatches\u002Fonnx\u002Fonnx.patch, raising kMaxSupportedOpset, or adding a new opset's CPU kernels. Covers the file taxonomy, archive-hash procedures, patch rebase\u002Fmirror rules, the RC→formal strategy, and the optimizer\u002FEP gotchas that the automated audit script misses.\n---\n\n# ONNX Opset \u002F Version Bump Checklist for ONNX Runtime\n\nRepeatable process for upgrading the ONNX dependency in ONNX Runtime. Recurs on every ONNX\nrelease. Canonical reference PRs: **#27601** (ONNX 1.21, incremental rc1→formal — best\nexample), #26579 (1.20.1), #25678 (1.19). See also `docs\u002FHow_To_Update_ONNX_Dev_Notes.md`.\n\n## 0. Strategy: RC → formal (incremental)\n\nONNX partner-validates each release candidate against ORT **before** publishing the formal\nrelease — that is the whole point of the integration issue. When the issue points at a\n`rel-X.Y.0` branch:\n\n1. **Integrate the RC first.** Pin by the release-branch **HEAD commit** (the `vX.Y.0rcN`\n   git *tag* usually does not exist yet). Run the full build + tests; file ONNX bugs upstream\n   (tag the ONNX release manager) for any ONNX-side defects found.\n2. **Re-pin per RC** (rc2, rc3, …). Usually only the Group-A version-plumbing files change.\n   **Drop any `onnx.patch` hunks that ONNX has merged upstream** in the new RC.\n3. **Re-pin to the formal tag** `vX.Y.0` once the GitHub Release is published. Note the\n   release can sit as a **draft** (no git tag, `git\u002Fref\u002Ftags\u002FvX.Y.0` → 404) for a while.\n\n```bash\ngh api repos\u002Fonnx\u002Fonnx\u002Fgit\u002Fref\u002Fheads\u002Frel-X.Y.0 --jq '.object.sha'              # RC branch HEAD\ncurl -sL https:\u002F\u002Fraw.githubusercontent.com\u002Fonnx\u002Fonnx\u002Frel-X.Y.0\u002FVERSION_NUMBER  # e.g. X.Y.0rcN\ngh api 'repos\u002Fonnx\u002Fonnx\u002Freleases?per_page=5' --jq '.[].tag_name'               # '' => draft\n```\n\n## 1. File taxonomy — what to change\n\nGrouped so parallel work is safe. **Group A must land first** (the tree must build before\nB\u002FC\u002FD can be validated). Throughout this section, bold letters in parentheses (e.g. gotcha\n**a**, **g**) refer to the lettered gotchas **a**–**i** defined in §4.\n\n### Group A — version plumbing (always required, mechanical)\n| File | Note |\n|---|---|\n| `cmake\u002Fdeps.txt` (`onnx;` line) | archive URL + **SHA1 of the `.zip`** (see §2). ⚠️ Before advancing this pin, verify the target commit still ships `onnx\u002Fbackend\u002Ftest\u002Fdata\u002Fnode\u002F` — see gotcha **p** (#7959 deletes the on-disk node-test corpus → silent-green CI). |\n| `cmake\u002Fexternal\u002Fonnx` (submodule) | `git -C cmake\u002Fexternal\u002Fonnx fetch && git -C cmake\u002Fexternal\u002Fonnx reset --hard \u003Csha> && git add cmake\u002Fexternal\u002Fonnx` |\n| `cmake\u002Fvcpkg-ports\u002Fonnx\u002Fvcpkg.json` | `version-semver`; reset `port-version` to 0 on a real version bump |\n| `cmake\u002Fvcpkg-ports\u002Fonnx\u002Fportfile.cmake` | `REF` + **SHA512 of the `.tar.gz`** (see §2). RC = bare commit as `REF`; formal = `REF \"v${VERSION}\"` |\n| `cmake\u002Fvcpkg-ports\u002Fonnx\u002Fbinskim.patch` | keep **byte-identical** to `onnx.patch` (see §3) |\n| `cmake\u002Fpatches\u002Fonnx\u002Fonnx.patch` | rebase to the new source (see §3) |\n| `cmake\u002Fvcpkg-ports\u002Fonnx\u002Ffix-dependency-protobuf.patch`, `fix-cmakelists.patch` | re-`diff` only if they fail to apply |\n| **The 7 `requirements.txt` files** with `onnx==X.Y.Z` | `onnxruntime\u002Ftest\u002Fpython\u002Frequirements.txt`; `tools\u002Fci_build\u002Fgithub\u002Flinux\u002Fpython\u002Frequirements.txt`; `tools\u002Fci_build\u002Fgithub\u002Fwindows\u002Fpython\u002Frequirements.txt`; `tools\u002Fci_build\u002Fgithub\u002Flinux\u002Fdocker\u002Fscripts\u002F{requirements.txt,manylinux\u002Frequirements.txt,lort\u002Frequirements.txt}`; `tools\u002Fci_build\u002Fgithub\u002Flinux\u002Fdocker\u002Finference\u002Faarch64\u002Fpython\u002Fcpu\u002Fscripts\u002Frequirements.txt`. Confirm with `git grep -n \"onnx==\u003COLD>\"` (e.g. `onnx==1.21`) — grep the **old** pin you are replacing, not bare `onnx==1`. ⚠️ **Do NOT bump all matches.** `git grep -n \"onnx==1\"` also lists 3 transformers-model files — `onnxruntime\u002Fpython\u002Ftools\u002Ftransformers\u002Fmodels\u002F{llama,phi2,stable_diffusion}\u002Frequirements.txt` — that are **intentionally frozen at `onnx==1.18.0`**. Leave those alone; only the 7 CI files above get the new pin. |\n| **The 3 QNN\u002Fandroid CI yaml + the minimal `requirements-materialize-onnx-node-tests.txt` they now use** (node-test materialization legs — NOT covered by the `requirements.txt` grep above) | `tools\u002Fci_build\u002Fgithub\u002Fazure-pipelines\u002Flinux-qnn-ci-pipeline.yml`; `tools\u002Fci_build\u002Fgithub\u002Fazure-pipelines\u002Fwin-qnn-arm64-ci-pipeline.yml`; `tools\u002Fci_build\u002Fgithub\u002Fazure-pipelines\u002Fandroid-arm64-v8a-QNN-crosscompile-ci-pipeline.yml`. Each does `pip install -r requirements-materialize-onnx-node-tests.txt` (repo-root file; the linux+android legs keep `--user`, the Windows leg omits it) feeding the `onnxruntime_MATERIALIZE_ONNX_NODE_TESTS` gate (see gotcha **p**). **Bump the pin IN the requirements file, in lockstep with `cmake\u002Fdeps.txt` + the numpy MIN pins** — do NOT re-inline. Confirm with `git grep -n \"onnx==\u003COLD>\"`. (The pins now live in a **dedicated minimal onnx+numpy `requirements*.txt` at the repo root** — the sole pip directory Dependabot scans — so Dependabot can track these two upgrades while the QNN\u002FAndroid legs still avoid dragging in the heavy docker `requirements.txt`. This REVERSES the earlier \"inline by design\" rationale: the pins must now stay pin-locked with the CMake gate, which HARD-FATALs on installed!=pinned.) |\n\n### Group B — opset enablement\n| File | Change |\n|---|---|\n| `onnxruntime\u002Fcore\u002Foptimizer\u002Ftranspose_optimization\u002Foptimizer_api.h` | `kMaxSupportedOpset` → new max opset (e.g. 26 → 27) |\n| `onnxruntime\u002Fcore\u002Fproviders\u002Fcpu\u002Fcpu_execution_provider.cc` | add `\u002F\u002F Opset N` forward-declares + `BuildKernelCreateInfo\u003C...>` entries for new\u002Fupdated CPU kernels; mirror the previous opset block exactly |\n| `onnxruntime\u002Fcore\u002Fgraph\u002Fcontrib_ops\u002Fcontrib_defs.h`, `dml_ops\u002Fdml_defs.h` | apply ONNX-header-driven `OpSchemaRegisterOnce` macro fixes **only if the build emits those errors** |\n\n> **🆕 Tradition: bump EVERY EP that registers the op, in the SAME PR.** When an op's kernel set\n> changes for the new opset (e.g. `Range` gaining fp16\u002Fbf16 at opset 27), version-split \u002F bump\n> that op's registration in **every** EP that registers it — **CPU and CUDA at minimum** — so no\n> EP silently lags behind CPU and the advertised opset boundaries stay consistent. **Even an\n> open-ended kernel that already binds the new opset** (e.g. CUDA `Range` at `SinceVersion(11)`,\n> which already matches opset-27 nodes) should still be **version-split** for convention\u002Fclarity\n> and to keep the kernel's advertised boundary matching the schema. Worked example — **PR #28754**\n> split `Range` into `[11,26]` + `27` in **both** CPU and CUDA (verified), keeping the same\n> numeric type set and deferring fp16\u002Fbf16 to ONNX function-expansion.\n\n**EP checklist when an op's kernel set changes for the new opset:**\n- [ ] For **each** EP, `grep -rn \"\u003COp>)\" onnxruntime\u002Fcore\u002Fproviders\u002F\u003Cep>\u002F` (and its `*_execution_provider.cc`) to find every registration of the changed op.\n- [ ] EPs that register ONNX kernels via the `ONNX_OPERATOR_[VERSIONED_]KERNEL[_CLASS_NAME]` macros — **cpu**, **cuda**, **js**, and **rocm** if it registers the op (rocm is often hipified from cuda): version-split each into `[prev_start, N-1]` + a new `N` registration (class forward-declare **and** `BuildKernelCreateInfo` entry).\n- [ ] EPs with their **own** registration systems assess per their conventions, not the macro split — **dml** (`REG_INFO(ver, Op, …)` in `OperatorRegistration.cpp`), **webgpu**, **coreml\u002Fnnapi\u002Fqnn\u002Fopenvino\u002Fmigraphx**. A partition\u002Fcapability check (e.g. MIGraphX's `optype == \"Range\"`) is **not** a kernel registration and needs no split.\n- [ ] Bump the EP `GetMaxSupportedOpSet` ceilings (**coreml\u002Fnnapi\u002Fvsinpu\u002Fwebnn**) in lockstep — see §4 gotcha **b**.\n\n> **IR version is NOT bumped manually.** ORT reads `ONNX_NAMESPACE::Version::IR_VERSION` from\n> the ONNX headers (`onnxruntime\u002Fcore\u002Fgraph\u002Fmodel.cc`); it follows the submodule automatically.\n\n### Group C — docs & test data (mostly auto-generated)\n| File | Change |\n|---|---|\n| `docs\u002FOperatorKernels.md` | regenerate (see gotcha **e** — needs a built ORT module): `python tools\u002Fpython\u002Fgen_opkernel_doc.py --output_path docs\u002FOperatorKernels.md` |\n| `js\u002Fweb\u002Fdocs\u002Fwebgl-operators.md` | `cd js\u002Fweb && npm install && npm run build:doc` (the WebAssembly CI \"Check out of dated documents\" stage fails otherwise) |\n| `onnxruntime\u002Ftest\u002Ftestdata\u002Fonnx_backend_test_series_filters.jsonc` | exclude backend tests for ops whose kernels are deferred (gotcha **g**) |\n| `onnxruntime\u002Ftest\u002Ftestdata\u002Fonnx_backend_test_series_overrides.jsonc` | tolerance overrides for new tests |\n| `onnxruntime\u002Ftest\u002Fonnx\u002FTestCase.cc` | `broken_tests` entries for genuinely-unsupported cases |\n\n### Group D — conditional (touch only if build\u002Ftests flag it — but see §4 gotchas)\n`onnxruntime\u002Fcore\u002Fframework\u002Fkernel_type_str_resolver_utils.cc`,\n`onnxruntime\u002Fcore\u002Foptimizer\u002Flayout_transformation\u002Flayout_transformation_potentially_added_ops.h`,\n`onnxruntime\u002Fcore\u002Foptimizer\u002Fqdq_transformer\u002Fqdq_util.cc`, EP `base_op_builder.h` max-opset\nguards (gotcha **b**), optimizer fusion path-matchers (gotcha **a**, **c**), and any CPU op\nfile hard-coding a `SinceVersion` ceiling for a changed op.\n\n## 2. Archive-hash procedures\n\n`cmake\u002Fdeps.txt` 3rd field = **SHA1 of the downloaded `.zip`** (verified: `sha1sum` of\n`v1.21.0.zip` equals the pinned `321d4acc...`):\n```bash\nURL=\"https:\u002F\u002Fgithub.com\u002Fonnx\u002Fonnx\u002Farchive\u002F\u003Ccommit-or-refs\u002Ftags\u002FvX.Y.0>.zip\"\ncurl -sL -o onnx.zip \"$URL\" && sha1sum onnx.zip      # paste:  onnx;$URL;\u003Csha1>\n```\nvcpkg `portfile.cmake` uses **SHA512 of the `.tar.gz`**:\n```bash\ncurl -sL -o onnx.tgz \"https:\u002F\u002Fgithub.com\u002Fonnx\u002Fonnx\u002Farchive\u002F\u003Ccommit-or-tag>.tar.gz\"\nsha512sum onnx.tgz\n```\nShortcut: pin a wrong hash and build — ORT\u002FFetchContent prints the expected one.\n\n> The submodule field (a git commit SHA) and the deps.txt field (a SHA1 of the archive\n> *contents*) are **different by design** — never interchange them.\n\n> **vcpkg artifact must be mirrored** to the Microsoft vcpkg store before `--use_vcpkg`\n> builds can download it (Terrapin upload — see below). Not self-service; coordinate with\n> infra. This is gotcha **f**.\n\n### Mirroring the ONNX archive to the MS vcpkg store (required before `--use_vcpkg` builds pass)\n\nThe SHA512 in `portfile.cmake` references a `.tar.gz` that vcpkg downloads from the **Microsoft\nvcpkg artifact mirror**, not from GitHub. Until that exact archive is uploaded to the mirror,\n`--use_vcpkg` builds fail with a download\u002Fhash error. The upload is done with the **Terrapin\nRetrieval Tool** and is a **Windows + `az` auth + internal-credential** step — it **cannot** run\nfrom a generic Linux CI host.\n\n**Step 1 — auth (PowerShell):**\n```powershell\n$authScope = 'https:\u002F\u002Fmspmecloud.onmicrosoft.com\u002FRebuildManager.Web\u002F.default'\n$env:TRT_UPLOAD_AUTH_TOKEN = $(az account get-access-token --scope $authScope --query 'accessToken' --output tsv)\n```\n\n**Step 2a — commit-version (RC phase, archive keyed by commit SHA):**\n```powershell\nC:\\local\\Terrapin\\TerrapinRetrievalTool.exe -b https:\u002F\u002Fvcpkg.storage.devpackages.microsoft.io\u002Fartifacts\u002F `\n  -a true -u Environment `\n  -p https:\u002F\u002Fgithub.com\u002Fonnx\u002Fonnx\u002Farchive\u002F\u003Ccommit-sha>.tar.gz `\n  -s \u003Csha512-of-tar.gz> `\n  -d \"\u003Cbuild>\\Windows\\vcpkg\\downloads\\onnx-onnx-\u003Ccommit-sha>.tar.gz.part\"\n```\n\n**Step 2b — tag-version (formal phase, archive keyed by release tag):**\n```powershell\nC:\\local\\Terrapin\\TerrapinRetrievalTool.exe -b https:\u002F\u002Fvcpkg.storage.devpackages.microsoft.io\u002Fartifacts\u002F `\n  -a true -u Environment `\n  -p https:\u002F\u002Fgithub.com\u002Fonnx\u002Fonnx\u002Farchive\u002Frefs\u002Ftags\u002Fv\u003Cversion>.tar.gz `\n  -s \u003Csha512> `\n  -d \"\u003Cbuild>\\Windows\\vcpkg\\downloads\\onnx-onnx-v\u003Cversion>.tar.gz\"\n```\n\nKey notes:\n- **(a)** `-s \u003Csha512>` **MUST equal** the `portfile.cmake` SHA512 (the `sha512sum` of the same\n  `.tar.gz` — see §2). A mismatch re-uploads the wrong blob and the hash check still fails.\n- **(b)** This is *why* `--use_vcpkg` builds fail with a download error until the upload lands —\n  the mirror has no copy of the new archive yet.\n- **(c)** Microsoft-internal infra step: coordinate with the infra owner (e.g. **@snnn**).\n  External \u002F Linux CI **cannot** perform it (needs Windows, the Terrapin tool, and `az` creds).\n- **(d)** The **`cmake\u002Fdeps.txt` path does NOT need this** — that build fetches the `.zip`\n  straight from GitHub. Only the vcpkg (`--use_vcpkg`) path depends on the mirror.\n- **(e)** The `.part` suffix on the 2a `-d` path is vcpkg's **in-progress-download** temp name\n  (vcpkg renames it to the final `.tar.gz` once the hash verifies). Match whatever the failing\n  `--use_vcpkg` build actually requests in its `downloads\u002F` dir — the RC run wrote a `.part`;\n  the formal-tag run wrote the bare `.tar.gz`. When unsure, copy the exact path from the build's\n  download error.\n- **(f) Why there is no GitHub fallback — `x-block-origin` (the actual failure mode).**\n  `tools\u002Fci_build\u002Fbuild.py` (`add_default_vcpkg_options`, the `--use_vcpkg_ms_internal_asset_cache`\n  branch) configures the asset cache as\n  `--x-asset-sources=x-azurl,https:\u002F\u002Fvcpkg.storage.devpackages.microsoft.io\u002Fartifacts\u002F;x-block-origin`\n  (or the Terrapin `x-script` form). The trailing **`x-block-origin` forbids vcpkg from falling\n  back to the GitHub origin** — so if the blob is absent the leg does **not** silently download\n  from GitHub, it **hard-fails**. The asset-cache key is the **bare lowercase SHA512 hex, no\n  extension**: the blob lives at `…\u002Fartifacts\u002F\u003Csha512>`. Quick probe (no auth needed, read is public):\n  ```bash\n  curl -s -o \u002Fdev\u002Fnull -w \"%{http_code}\\n\" \\\n    https:\u002F\u002Fvcpkg.storage.devpackages.microsoft.io\u002Fartifacts\u002F\u003Cportfile-sha512>\n  # 200 = already mirrored (legs will pass) ; 404 = NOT mirrored (every vcpkg leg will 404-fail)\n  ```\n- **(g) This recurs on EVERY archive bump, including each RC.** rc1→rc2→…→formal each produce a\n  *new* `.tar.gz` with a *new* SHA512, so each one is a distinct, un-mirrored blob. A green rc1 run\n  does **not** mean rc2 is mirrored. After every `portfile.cmake` REF\u002FSHA512 change, run the curl\n  probe above; if 404, the upload (steps 1–2) must happen again before any `--use_vcpkg` leg can pass.\n  Terrapin-enabled self-hosted Windows legs (`-a true`) self-seed the mirror as a side effect; the\n  read-only `x-azurl` legs (Linux\u002FmacOS\u002FGitHub-hosted) cannot and will 404 until that seed lands.\n- **(h) CI failure signature (how to recognize this in a red build).** Only the **vcpkg-based**\n  legs fail; the `cmake\u002Fdeps.txt` FetchContent legs stay green (they pull the `.zip` from GitHub,\n  mirror-independent — see (d)). The failing leg's log shows a vcpkg download error during the\n  `onnx` port install, e.g.:\n  ```\n  error: Failed to download from mirror set\n  error: https:\u002F\u002Fvcpkg.storage.devpackages.microsoft.io\u002Fartifacts\u002F\u003Csha512>: failed: status code 404\n  error: x-block-origin set, prohibiting access to the original source URL\n  ```\n  That `404` + `x-block-origin set` pair on a `…\u002Fartifacts\u002F\u003Csha512>` URL **is** this gotcha — the\n  `\u003Csha512>` in the error equals the `portfile.cmake` SHA512. (Confirmed live for ONNX 1.22.0rc2:\n  rc1 blob → HTTP 200, rc2 blob → HTTP 404.)\n- **(i) Fix options (in order of preference).**\n  1. **Self-seed via a Terrapin Windows leg** — trigger\u002Fre-run one internal Azure DevOps pipeline\n     whose Windows job runs on a self-hosted pool (has `C:\\local\\Terrapin\\TerrapinRetrievalTool.exe`)\n     and passes `--use_vcpkg_ms_internal_asset_cache`. Its `-a true` Terrapin fetches the archive\n     from origin and **writes it back to the mirror**; afterwards re-run the failing read-only legs.\n     (No infra ticket needed.) Manual Terrapin upload commands are in §2 steps 1–2 above.\n  2. **`az storage blob upload`** — anyone with write access to the `devpackages` storage account:\n     download the exact origin `.tar.gz`, verify `sha512sum` equals the portfile SHA512, then\n     `az storage blob upload --container-name artifacts --name \u003Csha512> --file \u003Ctar.gz> --auth-mode login`.\n     The blob **name must be the bare lowercase SHA512, no extension**.\n  3. **EngSys \u002F 1ES ticket** — if neither self-service path is available, ask the team that owns\n     `vcpkg.storage.devpackages.microsoft.io` to mirror the asset, giving them the origin URL + SHA512.\n  Verify any fix with the §2(f) curl probe returning **HTTP 200** before re-running CI.\n\n  > Detailed worked example (ONNX 1.22.0rc2 coordinates, live-probe evidence, full procedures):\n  > see the architect runbook artifact `architect-f1afcb8a\u002Fonnx-rc2-vcpkg-mirror-runbook.md`.\n\n## 3. onnx.patch rebase + binskim.patch mirror\n\nFor each hunk in `cmake\u002Fpatches\u002Fonnx\u002Fonnx.patch`, against the **new** ONNX source:\n- Applies cleanly → keep.\n- Context shifted → rebase (regenerate line numbers\u002Findices).\n- Fixed upstream in the new ONNX → **drop the hunk** (#27601 added a Slice `dim_value==0`\n  hunk for rc1\u002Frc2 then removed it at rc3 once ONNX merged it).\n- ONNX restructured the region → **rewrite the hunk.** Example (1.21 → 1.22): ONNX replaced\n  the `file(GLOB_RECURSE __tmp_srcs ...)` source-gathering block with an\n  `add_library(onnx_core OBJECT)` + `add_subdirectory(onnx)` model, so the `ONNX_MINIMAL_BUILD`\n  hunk had to switch from editing `set(ONNX_SRCS ...)` to\n  `target_sources(onnx_core PRIVATE \"${ONNX_ROOT}\u002Fonnx\u002Fdefs\u002Fdata_type_utils.cc\")`. Note\n  `data_type_utils.cc` lives in `onnx\u002Fdefs\u002F` (not `onnx\u002Fcommon\u002F`), and headers do **not**\n  belong in `target_sources` (they are not compile units).\n  > **Structural note (re-validate every bump):** in the `onnx_core` OBJECT-lib layout,\n  > minimal mode skips `add_subdirectory(onnx)`. That is only safe because\n  > `add_onnx_compile_options(onnx_core)` (include dirs + protobuf link) and\n  > `add_onnx_global_defines(onnx_core)` are **unconditional at the top-level CMakeLists**.\n  > If a future ONNX moves those into the `onnx\u002F` subdirectory, minimal builds will fail to\n  > configure\u002Flink — so never *assume* the minimal hunk still works; re-run the minimal build\n  > (§5) on every bump.\n\nThen **mirror the final `onnx.patch` byte-for-byte into\n`cmake\u002Fvcpkg-ports\u002Fonnx\u002Fbinskim.patch`** — they must stay identical. Verify:\n```bash\ngit apply --check cmake\u002Fpatches\u002Fonnx\u002Fonnx.patch\npatch --binary --ignore-whitespace -p1 --dry-run \u003C cmake\u002Fpatches\u002Fonnx\u002Fonnx.patch\nsha1sum cmake\u002Fpatches\u002Fonnx\u002Fonnx.patch cmake\u002Fvcpkg-ports\u002Fonnx\u002Fbinskim.patch  # must match\n```\n\n## 4. Gotchas (the expensive ones — hand-check these)\n\nThese are not caught by the automated audit and have bitten real integrations:\n\n**(a) The optimizer audit script misses fusion path-matchers.**\n`tools\u002Fpython\u002Ffind_optimizer_opset_version_updates_required.py` only inspects **direct kernel\nregistrations** — it does **not** see opset version lists embedded in optimizer\n`graph_utils::EdgeEndToMatch` **path-matchers**. When an op's opset changes, hand-grep the\nfusion files and extend the version list. Confirmed sites for **`Range` → 27**:\n- `onnxruntime\u002Fcore\u002Foptimizer\u002Fembed_layer_norm_fusion.cc` — `EdgeEndToMatch` entries\n  `{0, 0, \"Range\", {1, 11, 27}, kOnnxDomain}` (multiple `parent_path_*`).\n- `onnxruntime\u002Fcore\u002Foptimizer\u002Fgather_fusion.cc` — `IsSupportedOptypeVersionAndDomain(node,\n  \"Range\", {1, 11, 27})` (Range→Gather→Slice fusion).\n```bash\ngrep -rn '\u003CChangedOp>' onnxruntime\u002Fcore\u002Foptimizer\u002F*fusion*.cc | grep -iE 'EdgeEndToMatch|IsSupportedOptypeVersionAndDomain|\\{[0-9, ]+\\}'\n```\n\n**(b) EP `base_op_builder.h` `GetMaxSupportedOpSet` must be bumped in lockstep.**\nEach NPU\u002Fcoreml\u002Fweb EP caps the opset it will partition. Bump every one that returns the old\nmax:\n- `onnxruntime\u002Fcore\u002Fproviders\u002Fcoreml\u002Fbuilders\u002Fimpl\u002Fbase_op_builder.h`\n- `onnxruntime\u002Fcore\u002Fproviders\u002Fnnapi\u002Fnnapi_builtin\u002Fbuilders\u002Fimpl\u002Fbase_op_builder.h`\n- `onnxruntime\u002Fcore\u002Fproviders\u002Fvsinpu\u002Fbuilders\u002Fimpl\u002Fbase_op_builder.h`\n- `onnxruntime\u002Fcore\u002Fproviders\u002Fwebnn\u002Fbuilders\u002Fimpl\u002Fbase_op_builder.h`\n```bash\ngrep -rn 'GetMaxSupportedOpSet' onnxruntime\u002Fcore\u002Fproviders\u002F*\u002Fbuilders\u002Fimpl\u002Fbase_op_builder.h\n# all should return the NEW max opset (e.g. 27)\n```\n\n**(c) Fusion `IsSupportedOptypeVersionAndDomain` version lists** (same root cause as **a**) —\ngrep all of `onnxruntime\u002Fcore\u002Foptimizer\u002F` for the changed op, not just `*fusion*` files.\n\n**(d) The audit script crashes on placeholder macros.**\n`find_optimizer_opset_version_updates_required.py` has a pre-existing crash (a placeholder\n`'ver'` token gets parsed as an `int`). Expect it to throw on a clean main; don't treat the\ncrash as a signal from your change. Run it, but rely on the manual greps in **a–c**.\n\n**(e) `OperatorKernels.md` regeneration needs a built ORT Python module.**\n`gen_opkernel_doc.py` imports `onnxruntime`, so build + install the wheel first (or download\nthe regenerated markdown from a CI published-artifact, which the dev-notes recommend).\n\n**(f) vcpkg artifact mirroring** — see §2; `--use_vcpkg` builds fail to download until done.\nBecause the vcpkg path is mirror-gated (not self-service), **do not rely on it to validate the\n`onnx.patch` rebase.** Use the minimal build instead — it is mirror-independent (see §5 and **(i)**).\n\n**(g) Defer-and-filter brand-new, unimplemented ops — and why it is safe.**\nDo not block the version bump on full kernels for large new ops (e.g. stateful\nattention\u002Fconv). Exclude them in `onnx_backend_test_series_filters.jsonc` and track kernels as\nfollow-up PRs (#25678 deferred `TensorScatter`\u002F`Swish` this way). Keeps the bump PR reviewable.\nThis is safe because ORT failures here are **node-local, not model-load-blocking**:\n- ORT kernel matching is per-`(op, type-set)`. Registering an updated op for only a **subset**\n  of the new schema's types (e.g. `Range`-27 with the 5 common numeric types, deferring\n  fp16\u002Fbf16) does **not** break model load — the ONNX schema validates the model; only the\n  specific unsupported-type node fails kernel lookup with a clear \"kernel not found\".\n- Deferring a brand-new op entirely (no kernel) is likewise node-local: ORT advertises the new\n  opset because the **submodule** registers the schemas (`operator_sets.h` \u002F\n  `DomainToVersionRange::Map()`), independent of the transpose-optimizer `kMaxSupportedOpset`.\n  The model **loads**; the unimplemented node fails at kernel-bind. Precedent: `TensorScatter`\n  (24), `LpNormalization` (22, test name `test_l2normalization`), `TreeEnsemble` all sit in the filters file as \"not implemented\".\n- Optional new schema attributes whose default \"has no effect for other types\" (e.g.\n  `Range`-27 `stash_type`) can be ignored by the kernel for the registered types — safe to defer.\n\n**(h) Function-op test filters — don't over-filter the `_expanded` variant.**\nMany new\u002Fupdated ops are ONNX **functions** (have `SetContextDependentFunctionBodyBuilder`) —\ne.g. `Range`-27, `CausalConvWithState`, `LinearAttention`. ONNX emits two backend-test\nvariants: `test_\u003Cop>` and `test_\u003Cop>_expanded` (the decomposed primitive subgraph). ORT can\nusually **run** `_expanded` via the primitive decomposition even when the fused kernel is\ndeferred. An **unanchored** filter like `^test_range_float16_type` also drops `_expanded`,\nsilently losing real coverage. When deferring an op, in §5\u002FT7 run `onnx_test_runner` and check\nwhether the `_expanded` variants pass; if they do, **narrow the filter** (anchor it \u002F exclude\nonly the bare name) to keep coverage. Over-filtering is CI-safe but hides working paths.\n\n**(i) On a shared multi-agent branch, read clean `main`, not the working tree.**\nAnother agent may already have rebased `onnx.patch`. Inspect with\n`git show \u003Cmain-sha>:cmake\u002Fpatches\u002Fonnx\u002Fonnx.patch` rather than reading the file directly.\n\n**(j) A green Linux webgpu CI leg does NOT mean WebGPU ran the node tests.**\nNew ONNX backend node tests (`OnnxBackendNodeModelTest`) only **execute** on the\n**macOS-arm64** webgpu CI leg. The Linux webgpu leg (`.github\u002Fworkflows\u002Flinux_webgpu.yml`) is\n**build-only** — it compiles the WebGPU EP but runs no kernels. So a green Linux webgpu\nleg proves the build, not that any WebGPU op actually ran. To exercise WebGPU kernels\noff-Mac locally, use a software Vulkan adapter (Mesa lavapipe) — see the\n`webgpu-local-testing` skill.\n\n**(k) Filter vs. override — pick the right one for a newly-failing node test.**\nTwo test-data files handle failures differently; choosing wrong either hides a real bug\nor drops coverage:\n- `onnx_backend_test_series_filters.jsonc` = **SKIP** a test for a **real EP bug**.\n  Always cite the tracking issue **and** a removal condition (when the skip can be\n  deleted). When only the reference **decomposition** path is broken, skip just the\n  `_expanded` variant — not the bare test (see gotcha **h**).\n- `onnx_backend_test_series_overrides.jsonc` = **RELAX ATOL** for benign fp16\u002FULP\n  differences. This **keeps the test running** — prefer it over a skip whenever the\n  kernel is actually correct and the failure is only numeric tolerance. **Guardrail:**\n  relax atol only **after** root-causing the diff as ~1 ULP at the output magnitude or a\n  few elements (e.g. `5e-4` ≈ 1 fp16 ULP at `O(1)` values). Unexplained, large, or\n  **growing** diffs are bugs to **investigate**, not override.\n\n**(l) New upstream reference tests can EXPOSE latent EP bugs.**\nA bump pulls in new\u002Fupdated reference tests that may surface pre-existing EP bugs the\nold test set never hit. Example: #28969 — a WebGPU broadcast underflow that ONNX\n1.22's expanded-Attention reference tests exposed. Treat such failures as bugs to fix\n(or filter-with-issue per gotcha **k**), not as bump noise.\n\n**(m) After a FINAL release, re-seed the vcpkg MS-internal asset mirror or every `--use_vcpkg` leg 404s.**\nThe MS-internal vcpkg asset mirror (`vcpkg.storage.devpackages.microsoft.io`) is **not**\nself-service: the new onnx tag tarball must be **Terrapin-seeded** into it. Until that\nlands, every `--use_vcpkg` CI leg fails the download with an `x-block-origin` **404**\n(the mirror refuses to proxy an un-seeded asset). This bites at the **rc → FINAL** re-pin\ntoo — the released `vX.Y.0` tag tarball is a *different* asset hash than the RC, so the\nmirror must be re-seeded for the final tag. Treat \"seed the tag tarball to the vcpkg\nmirror\" as a required step of the Phase-2 final re-pin, not a follow-up. (See §2 for the\nmirror procedure; the `--minimal_build extended` gate in §9 is the mirror-independent\nstand-in until seeding completes.)\n\n**(n) A FINAL onnx release can still ship a map-max opset > last *release* opset.**\n\"FINAL\" does **not** imply the new opset is released. ONNX 1.22.0 ships\n`DomainToVersionRange` **map-max 27** while the last *released* opset is **26** — i.e.\nopset 27 stays **under development** for the entire 1.22 cycle. So strict legs (the\ndefault, or `ALLOW_RELEASED_ONNX_OPSET_ONLY=1`) still throw **\"Opset N under development\"**\nat model load on any `*CurrentOpset` test that builds at the map-max opset — even after\nthe bump is \"final\". Don't assume the under-development gating ends when the RC does.\n\n**(o) Prefer per-model `ModelOptions{allow_released_opsets_only=false}` over per-leg env flips or `GTEST_SKIP`.**\nFor `*CurrentOpset` tests caught by gotcha **n**, set the relaxation **per model** via\n`ModelOptions{\u002F*allow_released_opsets_only*\u002F false, …}` on the `Model::Load` \u002F\n`TestGraphTransformer` \u002F `TransformerTester` call. This is **leg-agnostic** (the test then\nexercises the new opset on *every* CI leg, not just the ones that happen to set the env\nvar) and **preserves opset coverage** (unlike `GTEST_SKIP`, which silently drops it).\nAvoid flipping a per-leg CI env var (`ALLOW_RELEASED_ONNX_OPSET_ONLY=0`) — it only fixes\nthe legs you remember to touch and leaves the default-strict legs red. Precedent on this\nbranch: `38f17243b` (GatherToSlice), generalized to all `*CurrentOpset` fusion tests.\nAnnotate each call site with a one-line WHY + the tracking issue so it can be removed once\nthe opset is released (#28966).\n\n**(p) A future onnx commit may DELETE the on-disk node-test corpus — a SILENT-GREEN C++ bump landmine.**\nORT's **C++** node-test coverage depends on ONNX **shipping pre-generated `.pb` artifacts\ninside the pinned archive**: `onnx_test_runner` reads `model.onnx` + `test_data_set_*\u002F*.pb`\nfrom `_deps\u002Fonnx-src\u002Fonnx\u002Fbackend\u002Ftest\u002Fdata\u002Fnode\u002F\u003Ctest>\u002F` (the FetchContent archive pinned by\nthe **SHA1 on the `onnx;` line of `cmake\u002Fdeps.txt` ~L40** — *not* the `cmake\u002Fexternal\u002Fonnx`\nsubmodule, which only the QNN CI `...\u002Fnode` path uses). **ONNX PR [onnx\u002Fonnx#7959] \"Remove\nnode test artifacts\" deletes that entire on-disk corpus** (~2992+ files) — it targets onnx\nmaster, so the first affected release is expected to be **onnx 1.23** (one past the current\n1.22 pin), consistent with the JS `NOTE(#7959)` \"onnx >= 1.23\" caveat — **and removes the\n`cmd_tools.py generate-data` node path**, replacing both with a **Python-only, in-memory**\nflow (`loader.load_node_model_tests()` → `runner.run_node()`, `model_dir=None`, nothing\nwritten to disk). Naming \u002F directory layout \u002F `.pb` format are **unchanged — the files are\nsimply gone.**\n- **⚠️ The C++ failure is SILENT, not loud.** Because names\u002Flayout\u002Fformat don't change, the\n  C++ skip contracts (`GetBrokenTests` \u002F `immutable_broken_tests` in `TestCase.cc` +\n  `main.cc`, names *without* the `test_` prefix) **don't throw or mismatch** — their target\n  paths just cease to exist. An empty `data\u002Fnode` yields **ZERO** collected cases; the\n  runner's directory BFS finds nothing, `ctest`\u002F`add_test` **exit 0**, and\n  `onnx_test_runner -e cuda ...\u002Fnode\u002F\u003Ctest>` prints nothing and **returns success.** C++\n  node-test **kernel guarding evaporates behind a GREEN CI with no failure signal** —\n  strictly worse than a hard break.\n- **The Python leg SURVIVES automatically — do not conflate it.** `onnx_backend_test_series.py`\n  does **zero** `.pb`\u002F`model.onnx` disk I\u002FO: it subclasses `onnx.backend.test.runner.Runner`\n  and delegates discovery+execution to the **installed pip `onnx`** package, whose base\n  `_add_model_test` already branches on `model_dir is None` → in-memory. ORT's only override\n  is a rtol\u002Fatol injector (`onnx_backend_test_series.py:60-71`), field-agnostic, no disk. So\n  the name-keyed **Python** contracts (`onnx_backend_test_series_filters.jsonc` `^test_`\n  regexes, `onnx_backend_test_series_overrides.jsonc` atol) keep resolving against the pip\n  package's in-memory tests and stay valid. **The mitigation surface is C++-only.** (Residual\n  Python risk is only if #7959 changes the public subclass contract — `Runner.__init__` \u002F\n  `_add_model_test` \u002F `assert_similar_outputs` sig \u002F `TestCase` field names.)\n- **Guardrail — verify the corpus survives BEFORE advancing the `cmake\u002Fdeps.txt` pin.** As of\n  this writing #7959 is **OPEN + CONFLICTING** (no onnx pin includes it yet), so this is\n  latent, not active. When re-pinning, confirm the target commit still ships the on-disk tree:\n  ```bash\n  # Does the target onnx commit\u002Ftag still contain the on-disk node-test corpus?\n  gh api \"repos\u002Fonnx\u002Fonnx\u002Fcontents\u002Fonnx\u002Fbackend\u002Ftest\u002Fdata\u002Fnode?ref=\u003Ctarget-commit-or-tag>\" \\\n    --jq 'length'      # >0 = corpus present (safe) ; 404 = DELETED (see #7959) -> STOP\n  # note: the contents API caps directory listings at 1000 entries (~1799 dirs exist today),\n  # so 'length' returns 1000, not the true count — fine here since we only test >0 vs 404.\n  # For an exact count use the git trees API instead:\n  #   gh api \"repos\u002Fonnx\u002Fonnx\u002Fgit\u002Ftrees\u002F\u003Ctarget-sha>?recursive=1\" --jq '[.tree[].path | select(startswith(\"onnx\u002Fbackend\u002Ftest\u002Fdata\u002Fnode\u002F\"))] | length'\n  # Or on a materialized archive\u002Fworktree:\n  ls _deps\u002Fonnx-src\u002Fonnx\u002Fbackend\u002Ftest\u002Fdata\u002Fnode | head   # empty => the landmine has landed\n  ```\n- **Mitigation if a bump is forced onto a #7959 commit (C++ leg only):** ORT must **own\n  node-test materialization** — a CMake `add_custom_command` (gated on\n  `onnxruntime_BUILD_UNIT_TESTS`) that imports the ONNX Python case generators (**confirmed\n  byte-identical \u002F untouched by #7959** — `onnx\u002Fbackend\u002Ftest\u002Fcase\u002Fnode\u002F*.py`'s `export.*` +\n  `expect(...)` remain; #7959 removes only the serialized `.pb` output, not the source) via\n  `load_node_model_tests()`, and re-serializes each `_NodeTestCase` back to the on-disk\n  `model.onnx` + `test_data_set_*\u002F{input,output}_N.pb` layout the C++ loader expects (exact\n  serialization detail below — **not** `from_array`-only). Then repoint the C++ consumers\n  (runner arg + QNN CI `...\u002Fnode` path). This also collapses the 3-independent-onnx-pins \u002F\n  dual-skip-list tangle into one materialized copy. **Reference implementation already exists — vendor it, do not\n  re-derive.** Vendor the ~85-line node branch of `onnx\u002Fbackend\u002Ftest\u002Fcmd_tools.py:generate_data`\n  (`cmd_tools.py:64-110` — the very disk-writer #7959 removes) into a build-time script:\n  - **Entry point: `collect_testcases(op_type=None)`** — pass `op_type=None` **explicitly**\n    to collect ALL ops (it is a required positional; a real `op_type` silently returns a\n    1-op subset — another silent-undercount path). It **self-calls `import_recursive`\n    internally** (`onnx\u002Fbackend\u002Ftest\u002Fcase\u002Fnode\u002F__init__.py:417`), so one call both populates\n    and returns `_NodeTestCases` — no separate import step, no empty-list footgun.\n  - **Per `TestCase`**: write `case.model.SerializeToString()` → `model.onnx`, and serialize\n    each input\u002Foutput into `test_data_set_0\u002F{input,output}_j.pb` using the reference's\n    **4-branch dispatch on `graph.input[j].type`** — `numpy_helper.from_dict` (map) \u002F\n    `from_list` (sequence) \u002F `from_optional` (optional) \u002F `from_array`-or-`SerializeToString`\n    (tensor). **Do NOT hand-simplify to `from_array`-only** — that silently mis-serializes\n    every Sequence\u002FMap\u002FOptional node test (`SequenceInsert`, `Optional*`, some Loop\u002FScan\n    fixtures). Dir name = `case.name` (already carries the `test_` prefix). Positional binding\n    is by the same `j` as the model's `graph.input`, so any `len(inputs) != len(graph.input)`\n    mismatch IndexErrors at BUILD time (fail-loud, never a bad corpus).\n  - **MANDATORY version parity (correctness, not just hygiene).** `expect()` stamps each\n    `model.onnx`'s `opset_import`\u002FIR from the **compiled onnx RUNTIME's** C++ schema registry\n    (`get_schema(op_type, domain).since_version`, `case\u002Fnode\u002F__init__.py:311-319`), *not* the\n    `.py` case source. So the shim MUST run under an installed onnx wheel whose\n    `onnx.__version__` **==** the `cmake\u002Fdeps.txt` pin, **hard-asserted as its first line**\n    (`assert onnx.__version__ == \u003Cdeps-derived>`) — a mismatched wheel bakes the WRONG opset\n    into `model.onnx` = silent corpus drift. Make this structural by **deriving the 6 CI\n    `requirements.txt` `onnx==` pins FROM `cmake\u002Fdeps.txt`** so wheel==archive by construction\n    (this is also what keeps the *Python* leg testing the pinned version). Under that parity\n    the installed wheel's `case\u002Fnode\u002F*.py` are byte-identical to the archive's, so the shim\n    uses the installed wheel directly — no `_deps\u002Fonnx-src` source plumbing needed.\n  - Also **assert `len(_NodeTestCases) > 0`** at shim start — fail the build on an empty\n    materialization (the build-time twin of the runtime tripwire below).\n- **Min-count TRIPWIRE (mitigation 0 — highest ROI, cause-AGNOSTIC, do this regardless of\n  #7959).** The deepest problem is that corpus absence is **silent-green**, so assert a floor\n  on discovered node-test count in **both** harnesses: C++ at `onnx\u002Fmain.cc:937` right after\n  `LoadTests` populates the vector — `ORT_ENFORCE(tests.size() >= floor, ...)` (add a\n  `--min_cases N` flag); Python in `onnx_backend_test_series.py` after `create_backend_test()`\n  asserting a floor on `len(backend_test.test_cases)`. This converts *any* future\n  corpus-absence regression (#7959, a bad archive, broken FetchContent, an over-matching\n  filter) from an invisible pass into a **loud red** — the single highest-leverage, lowest-cost\n  change here.\n- **Other on-disk node-test consumers to repoint when #7959 lands (track, don't lose).** Beyond the\n  C++ `onnx_test_runner` + QNN `...\u002Fnode` path, three more consumers read the ONNX on-disk\n  node-test data and will break\u002Fskip silently once #7959 deletes it — repoint\u002Fupdate each in the\n  same bump: **`csharp\u002Ftest\u002FMicrosoft.ML.OnnxRuntime.EndToEndTests\u002Fruntest.sh`** (C# backend test\n  runner pointed at the node dir), **`js\u002Fscripts\u002Fprepare-onnx-node-tests.ts`** (the JS\u002Fweb test\n  harness that stages the node corpus), and **`docs\u002Fpython\u002Fconf.py`** (Sphinx doc build that\n  references the node-test data). None are\n  covered by the C++ materialization mitigation above; each needs its own repoint to the\n  materialized tree (or removal) when the corpus source disappears.\n\n## 5. Build & validate locally\n\n**The canonical build + test command set lives in §9 (verification gauntlet)** — run those to\ndefine \"done\". This section keeps only the\n*rationale* for the highest-risk gate (don't duplicate the command list here — it drifts):\n\n> **MERGE GATE for the patch rebase (gotcha a\u002Fi + §3):** the `ONNX_MINIMAL_BUILD` hunk in\n> `onnx.patch`\u002F`binskim.patch` is the single highest-risk change and is **only** exercised when\n> `ONNX_MINIMAL_BUILD=ON` — set by `build.py --minimal_build` **and always by the vcpkg port**\n> (`tools\u002Fpython\u002Futil\u002Fvcpkg_helpers.py`). Since the vcpkg path is mirror-gated (gotcha f), the\n> `--minimal_build extended` command (§9 step 2) is the **cheap, mirror-independent gate**: it\n> pulls the ONNX archive from `cmake\u002Fdeps.txt` (no vcpkg mirror needed) and proves the rebased\n> minimal hunk **configures + compiles + links**. Make a green minimal build a required gate\n> before merging any `onnx.patch` rebase.\n\nThen update `TestCase.cc` \u002F `onnx_backend_test_series_*.jsonc` for newly-introduced failing\nnode tests. After the PR is up, manually queue every packaging pipeline on the branch, and ask\ninfra to deploy any new ONNX test data to CI machines (dev-notes).\n\n## 6. Quick checklist\n- [ ] Group A: deps.txt (zip SHA1), submodule, vcpkg.json, portfile.cmake (tar.gz SHA512), onnx.patch rebased, binskim.patch mirrored byte-identical, all 7 requirements.txt (NOT the 3 transformers-model files frozen at onnx==1.18.0)\n- [ ] Group B: `kMaxSupportedOpset`, cpu_execution_provider.cc opset block, **version-split the changed op in EVERY EP that registers it (cpu+cuda+js, rocm if present) — §1 Group B all-EP tradition**, (contrib\u002Fdml macros if build demands)\n- [ ] **Safety invariant (§11): every new no-kernel op MUST carry an ONNX function body — else its native kernel is a BLOCKER this PR, not a follow-up**\n- [ ] Gotchas: fusion path-matchers (embed_layer_norm_fusion.cc, gather_fusion.cc), all 4 EP `GetMaxSupportedOpSet`, run audit script (expect crash), defer-and-filter new ops (node-local & safe), narrow function-op `_expanded` filters\n- [ ] Group C: OperatorKernels.md (built module), webgl-operators.md, backend test filters\u002Foverrides\n- [ ] Validate: run the **§9 verification gauntlet** (full build, `--minimal_build extended` gate, onnxruntime_test_all, onnx_test_runner -e cpu; `--use_vcpkg` after artifact mirrored)\n\n## 7. Worked example — ONNX 1.21 → 1.22.0rc1 (a template to copy)\n\nThe real values from this session's bump. **Replace every value in `\u003C...>` for the next bump**;\nthe structure stays the same.\n\n| Field | This bump (1.21 → 1.22.0rc1) | Where it goes |\n|---|---|---|\n| Source pin | commit `bc3be77bec2f628788796dff60819186bacf49df` (`rel-1.22.0` HEAD; RC has **no git tag** yet) | submodule + deps.txt URL + portfile `REF` |\n| deps.txt SHA1 | `421e5a9afb6c41a54696e424e5b9a3796aab6821` (**SHA1 of the `.zip`**) | `cmake\u002Fdeps.txt` 3rd field |\n| portfile SHA512 | `e0c526f50767f376b8ad2ac3dc6b109c65f5b3ed20418fd3c4260a954b796d828a30cb5141a23db9b4db8e4db391bfe5042ef99d141f60bdbdbb991b1f3ce467` (**SHA512 of the `.tar.gz`**) | `cmake\u002Fvcpkg-ports\u002Fonnx\u002Fportfile.cmake` |\n| Opset | 26 → **27** | `kMaxSupportedOpset`, cpu_execution_provider.cc |\n| IR version | **13 (unchanged)** — auto from headers, do not touch | — |\n| New\u002Fupdated ops | `Range`-27 (updated, function), `CausalConvWithState` + `LinearAttention` (new, functions) | see §11 safety invariant |\n\n**Exact files touched this bump** (26 files — use as a coverage checklist):\n```\ncmake\u002Fdeps.txt                          # zip SHA1 + URL\ncmake\u002Fexternal\u002Fonnx                     # submodule -> bc3be77b\ncmake\u002Fpatches\u002Fonnx\u002Fonnx.patch           # rebase: 2 files \u002F 3 @@ hunks — CMakeLists.txt (option decl + ONNX_MINIMAL_BUILD src restructure) + onnx\u002Fdefs\u002Fnn\u002Fold.cc (GroupNormalization-18 .Deprecate() removal). Utils.cmake NOT touched (upstream-merged protobuf hunk dropped this bump)\ncmake\u002Fvcpkg-ports\u002Fonnx\u002Fbinskim.patch    # byte-identical mirror of onnx.patch\ncmake\u002Fvcpkg-ports\u002Fonnx\u002Fportfile.cmake   # REF bc3be77b + tar.gz SHA512\ncmake\u002Fvcpkg-ports\u002Fonnx\u002Fvcpkg.json       # version-semver + port-version 0\ndocs\u002FOperatorKernels.md                 # CPU-only surgical update (full regen = follow-up)\njs\u002Fweb\u002Fdocs\u002Fwebgl-operators.md          # npm run build:doc\nonnxruntime\u002Fcore\u002Foptimizer\u002Fembed_layer_norm_fusion.cc   # Range path-matcher {1,11,27} (gotcha a)\nonnxruntime\u002Fcore\u002Foptimizer\u002Fgather_fusion.cc             # Range {1,11,27} (gotcha c)\nonnxruntime\u002Fcore\u002Foptimizer\u002Ftranspose_optimization\u002Foptimizer_api.h   # kMaxSupportedOpset 26->27\nonnxruntime\u002Fcore\u002Fproviders\u002Fcpu\u002Fcpu_execution_provider.cc            # opset-27 kernel block\nonnxruntime\u002Fcore\u002Fproviders\u002Fcpu\u002Fgenerator\u002Frange.cc                   # Range-27 kernel\nonnxruntime\u002Fcore\u002Fproviders\u002F{coreml,nnapi,vsinpu,webnn}\u002F...\u002Fbase_op_builder.h  # GetMaxSupportedOpSet ->27 (gotcha b)\nonnxruntime\u002Ftest\u002Foptimizer\u002Fgraph_transform_test.cc                  # fusion test updates\nonnxruntime\u002Ftest\u002Ftestdata\u002Fonnx_backend_test_series_filters.jsonc    # defer-and-filter new ops\nonnxruntime\u002Ftest\u002Fpython\u002Frequirements.txt + 6 CI requirements.txt    # onnx==1.22.0rc1 (NOT the 3 frozen at 1.18.0)\nrequirements-materialize-onnx-node-tests.txt    # root minimal onnx+numpy pins for the QNN\u002Fandroid MATERIALIZE legs; MUST match the cmake gate + numpy MIN pins (gotcha p)\n```\n\n## 8. Hash-recompute one-liners\n\nBoth hashes are of the **GitHub source archive at the pinned ref**, but of **different archive\nformats** — keep them straight:\n\n```bash\nREF=bc3be77bec2f628788796dff60819186bacf49df      # commit (RC) — or  v1.22.0  (formal tag)\n\n# deps.txt 3rd field = SHA1 of the .ZIP:\ncurl -sL \"https:\u002F\u002Fgithub.com\u002Fonnx\u002Fonnx\u002Farchive\u002F${REF}.zip\"    | sha1sum\n\n# portfile.cmake SHA512 = SHA512 of the .TAR.GZ:\ncurl -sL \"https:\u002F\u002Fgithub.com\u002Fonnx\u002Fonnx\u002Farchive\u002F${REF}.tar.gz\" | sha512sum\n```\n> `.zip` → **SHA1** → deps.txt. `.tar.gz` → **SHA512** → portfile. Never cross them.\n> For a formal release use `refs\u002Ftags\u002Fv\u003Cversion>` in place of the bare commit in both URLs.\n\n> ⚠️ **RC commit and formal tag produce DIFFERENT hashes — recompute is MANDATORY when you\n> switch `REF`.** GitHub names the archive's top-level directory after the ref\n> (`onnx-\u003Ccommit-sha>\u002F` for a commit vs `onnx-\u003Cversion>\u002F` for `refs\u002Ftags\u002Fv\u003Cversion>`), so the\n> archive **bytes differ** even though the tree content is identical. **Never reuse the RC\n> commit's SHA1\u002FSHA512 for the formal tag** — re-run both one-liners above against the tag.\n\n## 9. Verification gauntlet — what \"done\" means\n\nRun all of these green before calling a bump complete:\n\n```bash\n# 1. Full build (deps.txt path):\n.\u002Fbuild.sh --config RelWithDebInfo --build_wheel --parallel\n# 2. Minimal-build gate (proves the rebased ONNX_MINIMAL_BUILD onnx.patch hunk; mirror-independent):\n.\u002Fbuild.sh --config RelWithDebInfo --minimal_build extended --parallel --skip_tests\n# 3. ORT unit tests:\n.\u002Fbuild\u002FLinux\u002FRelWithDebInfo\u002Fonnxruntime_test_all\n# 4. ONNX backend node tests for the new opset on CPU EP:\n.\u002Fbuild\u002FLinux\u002FRelWithDebInfo\u002Fonnx_test_runner -e cpu \\\n  build\u002FLinux\u002FRelWithDebInfo\u002F_deps\u002Fonnx-src\u002Fonnx\u002Fbackend\u002Ftest\u002Fdata\u002Fnode\n```\n- **(a)** `ALLOW_RELEASED_ONNX_OPSET_ONLY` is **ORT-side load-time validation**\n  (`onnxruntime\u002Fcore\u002Fgraph\u002Fmodel_load_utils.h`), **not** schema registration — the ONNX schemas\n  for the new opset are **always** compiled in from the submodule regardless. With the default\n  `=1`, ORT **rejects at model-load** any model whose opset is still \"under development\" (e.g.\n  opset 27 during the RC), so the new-opset node tests fail \u002F report \"not implemented\". Build and\n  run the gauntlet with **`ALLOW_RELEASED_ONNX_OPSET_ONLY=0`** so ORT loads and runs those\n  under-development-opset models. (Consistent with §4 gotcha **g**: schemas come from the\n  submodule; this flag only governs whether ORT *accepts* the model at load.)\n- **(b)** Expect the new-op node tests you intentionally deferred to be **filtered** in\n  `onnx_backend_test_series_filters.jsonc` — but confirm the `_expanded` variants still run\n  (gotcha **h**): a function op with no native kernel must still PASS via decomposition (§11).\n- **(c)** `--use_vcpkg` build is **not** a merge gate until the artifact is mirrored (§2); the\n  minimal build (step 2) is the mirror-independent stand-in that proves the patch rebase.\n\n## 10. PR-body template (fill in the blanks)\n\n```markdown\n### Bump ONNX to \u003Cversion> (opset \u003CN>)\n\nPin: onnx\u002Fonnx@\u003Ccommit-or-tag>. Opset \u003COLD> → \u003CN>. IR version \u003Cunchanged\u002Fnew>.\n\n**Standing caveats**\n- **EP `GetMaxSupportedOpSet` bumped to \u003CN>** (coreml\u002Fnnapi\u002Fvsinpu\u002Fwebnn). These EPs gain no\n  new op kernels in this PR; the bump only raises the support ceiling so existing ops on\n  opset-\u003CN> models partition. New-op kernels for these EPs are follow-ups.\n- **`OperatorKernels.md` updated for CPU EP only** (surgical edit). A full multi-EP regen needs\n  a built ORT module per EP and is tracked as a follow-up (gotcha **e**).\n- **Opset \u003CN> is under development** (RC): ORT load-time validation rejects opset-\u003CN> models\n  unless `ALLOW_RELEASED_ONNX_OPSET_ONLY=0` (schemas are always compiled in from the submodule\n  — see §9a\u002F§4g). Brand-new ops without kernels are **deferred + filtered**\n  in `onnx_backend_test_series_filters.jsonc` (node-local, safe — see skill §4 gotcha g).\n- **GPU EPs unverified**: only CPU EP was built\u002Ftested here. CUDA\u002FDML\u002Fetc. opset-\u003CN> coverage\n  is a follow-up.\n\n**Validation:** full build ✅, `--minimal_build extended` ✅, onnxruntime_test_all ✅,\nonnx_test_runner -e cpu (opset-\u003CN> node tests) ✅.\n```\n\n## 11. SAFETY INVARIANT — no-kernel ops MUST carry a function body\n\n> **🚨 For every new\u002Fupdated opset op that you ship WITHOUT a native ORT kernel, confirm the\n> op carries an ONNX FUNCTION BODY. If it has NO function body, a native kernel is MANDATORY in\n> THIS PR — it cannot be a follow-up. Otherwise the model loads but the node fails at session\n> initialization.**\n\n**\"Native kernel\" means** any registered kernel that **binds the opset-N node** — including an\n**open-ended existing kernel** (e.g. one registered `SinceVersion(13)` with no upper bound) that\nalready covers opset N. Such an op is **already kernel-covered** and does **not** trigger the\nblocker below; the blocker applies **only** when *no* registered kernel binds the new-opset node\n**and** the op has no function body.\n\n> **Convention (cross-EP consistency) — distinct from the binding-coverage rule above.** Even\n> when an open-ended kernel already binds opset N (so it is *not* a blocker), still\n> **version-split** it — and do so in **every** EP that registers the op (see the Group B all-EP\n> tradition). PR #28754 split `Range` `[11,26]`+`27` in **both** CPU and CUDA even though CUDA's\n> `SinceVersion(11)` kernel already bound opset 27, so the advertised boundary matches the schema\n> and no EP lags behind CPU. Splitting is about **clarity\u002Fconsistency**; binding-coverage is\n> about **correctness** — keep the two concerns distinct.\n\n**Why (the function-expansion mechanism):** an ONNX *function* op ships a reference\ndecomposition into primitive ops (`SetContextDependentFunctionBodyBuilder` \u002F\n`FunctionBodyHelper`). At graph-partition time ORT **inlines** (expands) any function-op node\nthat has no registered kernel into its primitive subgraph, which IS kernel-covered — so the op\nruns with zero native code. A **non-function** op has no such fallback: with no binding kernel,\nkernel binding fails at **session initialization** (fail-fast `kernel not found`), even though\nthe model loaded (schema came from the submodule — see §4 gotcha **g**).\n\n> ⚠️ **Scope: runtime function-inlining is a FULL-build behavior.** Minimal \u002F mobile \u002F\n> ORT-format builds do **not** runtime-inline function ops. The §9 gauntlet only proves the\n> minimal build **compiles** (`--skip_tests`); it never **runs** a function-op model in a\n> minimal build. So do **not** assume the function-inlining fallback gives minimal-build runtime\n> coverage — a function-only op with no native kernel will not execute in a minimal build.\n\nThis is exactly why the 1.22 bump could defer native kernels for **`CausalConvWithState`**,\n**`LinearAttention`**, and **`Range`-27 fp16\u002Fbf16** and still pass (in a full build): all three\nare ONNX functions, so ORT inlined them. Decision rule per new op:\n\n| A binding kernel covers the opset-N node? | Op has ONNX function body? | Action |\n|---|---|---|\n| Yes (incl. open-ended existing kernel) | — | Already covered; done |\n| No | **Yes** | Safe to defer kernel — ORT inlines (full build); verify the `_expanded` test passes |\n| No | **No** | **BLOCKER — write the kernel in this PR** (op would load then fail at session init) |\n\nQuick check: grep the ONNX op's `defs.cc` for `SetContextDependentFunctionBodyBuilder` or\n`FunctionBody`; if absent, it is not a function and needs a kernel.\n","# ONNX Opset \u002F Version Bump Checklist for ONNX Runtime\n\nRepeatable process for upgrading the ONNX dependency in ONNX Runtime. Recurs on every ONNX\nrelease. Canonical reference PRs: **#27601** (ONNX 1.21, incremental rc1→formal — best\nexample), #26579 (1.20.1), #25678 (1.19). See also `docs\u002FHow_To_Update_ONNX_Dev_Notes.md`.\n\n## 0. Strategy: RC → formal (incremental)\n\nONNX partner-validates each release candidate against ORT **before** publishing the formal\nrelease — that is the whole point of the integration issue. When the issue points at a\n`rel-X.Y.0` branch:\n\n1. **Integrate the RC first.** Pin by the release-branch **HEAD commit** (the `vX.Y.0rcN`\n   git *tag* usually does not exist yet). Run the full build + tests; file ONNX bugs upstream\n   (tag the ONNX release manager) for any ONNX-side defects found.\n2. **Re-pin per RC** (rc2, rc3, …). Usually only the Group-A version-plumbing files change.\n   **Drop any `onnx.patch` hunks that ONNX has merged upstream** in the new RC.\n3. **Re-pin to the formal tag** `vX.Y.0` once the GitHub Release is published. Note the\n   release can sit as a **draft** (no git tag, `git\u002Fref\u002Ftags\u002FvX.Y.0` → 404) for a while.\n\n```bash\ngh api repos\u002Fonnx\u002Fonnx\u002Fgit\u002Fref\u002Fheads\u002Frel-X.Y.0 --jq '.object.sha'              # RC branch HEAD\ncurl -sL https:\u002F\u002Fraw.githubusercontent.com\u002Fonnx\u002Fonnx\u002Frel-X.Y.0\u002FVERSION_NUMBER  # e.g. X.Y.0rcN\ngh api 'repos\u002Fonnx\u002Fonnx\u002Freleases?per_page=5' --jq '.[].tag_name'               # '' => draft\n```\n\n## 1. File taxonomy — what to change\n\nGrouped so parallel work is safe. **Group A must land first** (the tree must build before\nB\u002FC\u002FD can be validated). Throughout this section, bold letters in parentheses (e.g. gotcha\n**a**, **g**) refer to the lettered gotchas **a**–**i** defined in §4.\n\n### Group A — version plumbing (always required, mechanical)\n| File | Note |\n|---|---|\n| `cmake\u002Fdeps.txt` (`onnx;` line) | archive URL + **SHA1 of the `.zip`** (see §2). ⚠️ Before advancing this pin, verify the target commit still ships `onnx\u002Fbackend\u002Ftest\u002Fdata\u002Fnode\u002F` — see gotcha **p** (#7959 deletes the on-disk node-test corpus → silent-green CI). |\n| `cmake\u002Fexternal\u002Fonnx` (submodule) | `git -C cmake\u002Fexternal\u002Fonnx fetch && git -C cmake\u002Fexternal\u002Fonnx reset --hard \u003Csha> && git add cmake\u002Fexternal\u002Fonnx` |\n| `cmake\u002Fvcpkg-ports\u002Fonnx\u002Fvcpkg.json` | `version-semver`; reset `port-version` to 0 on a real version bump |\n| `cmake\u002Fvcpkg-ports\u002Fonnx\u002Fportfile.cmake` | `REF` + **SHA512 of the `.tar.gz`** (see §2). RC = bare commit as `REF`; formal = `REF \"v${VERSION}\"` |\n| `cmake\u002Fvcpkg-ports\u002Fonnx\u002Fbinskim.patch` | keep **byte-identical** to `onnx.patch` (see §3) |\n| `cmake\u002Fpatches\u002Fonnx\u002Fonnx.patch` | rebase to the new source (see §3) |\n| `cmake\u002Fvcpkg-ports\u002Fonnx\u002Ffix-dependency-protobuf.patch`, `fix-cmakelists.patch` | re-`diff` only if they fail to apply |\n| **The 7 `requirements.txt` files** with `onnx==X.Y.Z` | `onnxruntime\u002Ftest\u002Fpython\u002Frequirements.txt`; `tools\u002Fci_build\u002Fgithub\u002Flinux\u002Fpython\u002Frequirements.txt`; `tools\u002Fci_build\u002Fgithub\u002Fwindows\u002Fpython\u002Frequirements.txt`; `tools\u002Fci_build\u002Fgithub\u002Flinux\u002Fdocker\u002Fscripts\u002F{requirements.txt,manylinux\u002Frequirements.txt,lort\u002Frequirements.txt}`; `tools\u002Fci_build\u002Fgithub\u002Flinux\u002Fdocker\u002Finference\u002Faarch64\u002Fpython\u002Fcpu\u002Fscripts\u002Frequirements.txt`. Confirm with `git grep -n \"onnx==\u003COLD>\"` (e.g. `onnx==1.21`) — grep the **old** pin you are replacing, not bare `onnx==1`. ⚠️ **Do NOT bump all matches.** `git grep -n \"onnx==1\"` also lists 3 transformers-model files — `onnxruntime\u002Fpython\u002Ftools\u002Ftransformers\u002Fmodels\u002F{llama,phi2,stable_diffusion}\u002Frequirements.txt` — that are **intentionally frozen at `onnx==1.18.0`**. Leave those alone; only the 7 CI files above get the new pin. |\n| **The 3 QNN\u002Fandroid CI yaml + the minimal `requirements-materialize-onnx-node-tests.txt` they now use** (node-test materialization legs — NOT covered by the `requirements.txt` grep above) | `tools\u002Fci_build\u002Fgithub\u002Fazure-pipelines\u002Flinux-qnn-ci-pipeline.yml`; `tools\u002Fci_build\u002Fgithub\u002Fazure-pipelines\u002Fwin-qnn-arm64-ci-pipeline.yml`; `tools\u002Fci_build\u002Fgithub\u002Fazure-pipelines\u002Fandroid-arm64-v8a-QNN-crosscompile-ci-pipeline.yml`. Each does `pip install -r requirements-materialize-onnx-node-tests.txt` (repo-root file; the linux+android legs keep `--user`, the Windows leg omits it) feeding the `onnxruntime_MATERIALIZE_ONNX_NODE_TESTS` gate (see gotcha **p**). **Bump the pin IN the requirements file, in lockstep with `cmake\u002Fdeps.txt` + the numpy MIN pins** — do NOT re-inline. Confirm with `git grep -n \"onnx==\u003COLD>\"`. (The pins now live in a **dedicated minimal onnx+numpy `requirements*.txt` at the repo root** — the sole pip directory Dependabot scans — so Dependabot can track these two upgrades while the QNN\u002FAndroid legs still avoid dragging in the heavy docker `requirements.txt`. This REVERSES the earlier \"inline by design\" rationale: the pins must now stay pin-locked with the CMake gate, which HARD-FATALs on installed!=pinned.) |\n\n### Group B — opset enablement\n| File | Change |\n|---|---|\n| `onnxruntime\u002Fcore\u002Foptimizer\u002Ftranspose_optimization\u002Foptimizer_api.h` | `kMaxSupportedOpset` → new max opset (e.g. 26 → 27) |\n| `onnxruntime\u002Fcore\u002Fproviders\u002Fcpu\u002Fcpu_execution_provider.cc` | add `\u002F\u002F Opset N` forward-declares + `BuildKernelCreateInfo\u003C...>` entries for new\u002Fupdated CPU kernels; mirror the previous opset block exactly |\n| `onnxruntime\u002Fcore\u002Fgraph\u002Fcontrib_ops\u002Fcontrib_defs.h`, `dml_ops\u002Fdml_defs.h` | apply ONNX-header-driven `OpSchemaRegisterOnce` macro fixes **only if the build emits those errors** |\n\n> **🆕 Tradition: bump EVERY EP that registers the op, in the SAME PR.** When an op's kernel set\n> changes for the new opset (e.g. `Range` gaining fp16\u002Fbf16 at opset 27), version-split \u002F bump\n> that op's registration in **every** EP that registers it — **CPU and CUDA at minimum** — so no\n> EP silently lags behind CPU and the advertised opset boundaries stay consistent. **Even an\n> open-ended kernel that already binds the new opset** (e.g. CUDA `Range` at `SinceVersion(11)`,\n> which already matches opset-27 nodes) should still be **version-split** for convention\u002Fclarity\n> and to keep the kernel's advertised boundary matching the schema. Worked example — **PR #28754**\n> split `Range` into `[11,26]` + `27` in **both** CPU and CUDA (verified), keeping the same\n> numeric type set and deferring fp16\u002Fbf16 to ONNX function-expansion.\n\n**EP checklist when an op's kernel set changes for the new opset:**\n- [ ] For **each** EP, `grep -rn \"\u003COp>)\" onnxruntime\u002Fcore\u002Fproviders\u002F\u003Cep>\u002F` (and its `*_execution_provider.cc`) to find every registration of the changed op.\n- [ ] EPs that register ONNX kernels via the `ONNX_OPERATOR_[VERSIONED_]KERNEL[_CLASS_NAME]` macros — **cpu**, **cuda**, **js**, and **rocm** if it registers the op (rocm is often hipified from cuda): version-split each into `[prev_start, N-1]` + a new `N` registration (class forward-declare **and** `BuildKernelCreateInfo` entry).\n- [ ] EPs with their **own** registration systems assess per their conventions, not the macro split — **dml** (`REG_INFO(ver, Op, …)` in `OperatorRegistration.cpp`), **webgpu**, **coreml\u002Fnnapi\u002Fqnn\u002Fopenvino\u002Fmigraphx**. A partition\u002Fcapability check (e.g. MIGraphX's `optype == \"Range\"`) is **not** a kernel registration and needs no split.\n- [ ] Bump the EP `GetMaxSupportedOpSet` ceilings (**coreml\u002Fnnapi\u002Fvsinpu\u002Fwebnn**) in lockstep — see §4 gotcha **b**.\n\n> **IR version is NOT bumped manually.** ORT reads `ONNX_NAMESPACE::Version::IR_VERSION` from\n> the ONNX headers (`onnxruntime\u002Fcore\u002Fgraph\u002Fmodel.cc`); it follows the submodule automatically.\n\n### Group C — docs & test data (mostly auto-generated)\n| File | Change |\n|---|---|\n| `docs\u002FOperatorKernels.md` | regenerate (see gotcha **e** — needs a built ORT module): `python tools\u002Fpython\u002Fgen_opkernel_doc.py --output_path docs\u002FOperatorKernels.md` |\n| `js\u002Fweb\u002Fdocs\u002Fwebgl-operators.md` | `cd js\u002Fweb && npm install && npm run build:doc` (the WebAssembly CI \"Check out of dated documents\" stage fails otherwise) |\n| `onnxruntime\u002Ftest\u002Ftestdata\u002Fonnx_backend_test_series_filters.jsonc` | exclude backend tests for ops whose kernels are deferred (gotcha **g**) |\n| `onnxruntime\u002Ftest\u002Ftestdata\u002Fonnx_backend_test_series_overrides.jsonc` | tolerance overrides for new tests |\n| `onnxruntime\u002Ftest\u002Fonnx\u002FTestCase.cc` | `broken_tests` entries for genuinely-unsupported cases |\n\n### Group D — conditional (touch only if build\u002Ftests flag it — but see §4 gotchas)\n`onnxruntime\u002Fcore\u002Fframework\u002Fkernel_type_str_resolver_utils.cc`,\n`onnxruntime\u002Fcore\u002Foptimizer\u002Flayout_transformation\u002Flayout_transformation_potentially_added_ops.h`,\n`onnxruntime\u002Fcore\u002Foptimizer\u002Fqdq_transformer\u002Fqdq_util.cc`, EP `base_op_builder.h` max-opset\nguards (gotcha **b**), optimizer fusion path-matchers (gotcha **a**, **c**), and any CPU op\nfile hard-coding a `SinceVersion` ceiling for a changed op.\n\n## 2. Archive-hash procedures\n\n`cmake\u002Fdeps.txt` 3rd field = **SHA1 of the downloaded `.zip`** (verified: `sha1sum` of\n`v1.21.0.zip` equals the pinned `321d4acc...`):\n```bash\nURL=\"https:\u002F\u002Fgithub.com\u002Fonnx\u002Fonnx\u002Farchive\u002F\u003Ccommit-or-refs\u002Ftags\u002FvX.Y.0>.zip\"\ncurl -sL -o onnx.zip \"$URL\" && sha1sum onnx.zip      # paste:  onnx;$URL;\u003Csha1>\n```\nvcpkg `portfile.cmake` uses **SHA512 of the `.tar.gz`**:\n```bash\ncurl -sL -o onnx.tgz \"https:\u002F\u002Fgithub.com\u002Fonnx\u002Fonnx\u002Farchive\u002F\u003Ccommit-or-tag>.tar.gz\"\nsha512sum onnx.tgz\n```\nShortcut: pin a wrong hash and build — ORT\u002FFetchContent prints the expected one.\n\n> The submodule field (a git commit SHA) and the deps.txt field (a SHA1 of the archive\n> *contents*) are **different by design** — never interchange them.\n\n> **vcpkg artifact must be mirrored** to the Microsoft vcpkg store before `--use_vcpkg`\n> builds can download it (Terrapin upload — see below). Not self-service; coordinate with\n> infra. This is gotcha **f**.\n\n### Mirroring the ONNX archive to the MS vcpkg store (required before `--use_vcpkg` builds pass)\n\nThe SHA512 in `portfile.cmake` references a `.tar.gz` that vcpkg downloads from the **Microsoft\nvcpkg artifact mirror**, not from GitHub. Until that exact archive is uploaded to the mirror,\n`--use_vcpkg` builds fail with a download\u002Fhash error. The upload is done with the **Terrapin\nRetrieval Tool** and is a **Windows + `az` auth + internal-credential** step — it **cannot** run\nfrom a generic Linux CI host.\n\n**Step 1 — auth (PowerShell):**\n```powershell\n$authScope = 'https:\u002F\u002Fmspmecloud.onmicrosoft.com\u002FRebuildManager.Web\u002F.default'\n$env:TRT_UPLOAD_AUTH_TOKEN = $(az account get-access-token --scope $authScope --query 'accessToken' --output tsv)\n```\n\n**Step 2a — commit-version (RC phase, archive keyed by commit SHA):**\n```powershell\nC:\\local\\Terrapin\\TerrapinRetrievalTool.exe -b https:\u002F\u002Fvcpkg.storage.devpackages.microsoft.io\u002Fartifacts\u002F `\n  -a true -u Environment `\n  -p https:\u002F\u002Fgithub.com\u002Fonnx\u002Fonnx\u002Farchive\u002F\u003Ccommit-sha>.tar.gz `\n  -s \u003Csha512-of-tar.gz> `\n  -d \"\u003Cbuild>\\Windows\\vcpkg\\downloads\\onnx-onnx-\u003Ccommit-sha>.tar.gz.part\"\n```\n\n**Step 2b — tag-version (formal phase, archive keyed by release tag):**\n```powershell\nC:\\local\\Terrapin\\TerrapinRetrievalTool.exe -b https:\u002F\u002Fvcpkg.storage.devpackages.microsoft.io\u002Fartifacts\u002F `\n  -a true -u Environment `\n  -p https:\u002F\u002Fgithub.com\u002Fonnx\u002Fonnx\u002Farchive\u002Frefs\u002Ftags\u002Fv\u003Cversion>.tar.gz `\n  -s \u003Csha512> `\n  -d \"\u003Cbuild>\\Windows\\vcpkg\\downloads\\onnx-onnx-v\u003Cversion>.tar.gz\"\n```\n\nKey notes:\n- **(a)** `-s \u003Csha512>` **MUST equal** the `portfile.cmake` SHA512 (the `sha512sum` of the same\n  `.tar.gz` — see §2). A mismatch re-uploads the wrong blob and the hash check still fails.\n- **(b)** This is *why* `--use_vcpkg` builds fail with a download error until the upload lands —\n  the mirror has no copy of the new archive yet.\n- **(c)** Microsoft-internal infra step: coordinate with the infra owner (e.g. **@snnn**).\n  External \u002F Linux CI **cannot** perform it (needs Windows, the Terrapin tool, and `az` creds).\n- **(d)** The **`cmake\u002Fdeps.txt` path does NOT need this** — that build fetches the `.zip`\n  straight from GitHub. Only the vcpkg (`--use_vcpkg`) path depends on the mirror.\n- **(e)** The `.part` suffix on the 2a `-d` path is vcpkg's **in-progress-download** temp name\n  (vcpkg renames it to the final `.tar.gz` once the hash verifies). Match whatever the failing\n  `--use_vcpkg` build actually requests in its `downloads\u002F` dir — the RC run wrote a `.part`;\n  the formal-tag run wrote the bare `.tar.gz`. When unsure, copy the exact path from the build's\n  download error.\n- **(f) Why there is no GitHub fallback — `x-block-origin` (the actual failure mode).**\n  `tools\u002Fci_build\u002Fbuild.py` (`add_default_vcpkg_options`, the `--use_vcpkg_ms_internal_asset_cache`\n  branch) configures the asset cache as\n  `--x-asset-sources=x-azurl,https:\u002F\u002Fvcpkg.storage.devpackages.microsoft.io\u002Fartifacts\u002F;x-block-origin`\n  (or the Terrapin `x-script` form). The trailing **`x-block-origin` forbids vcpkg from falling\n  back to the GitHub origin** — so if the blob is absent the leg does **not** silently download\n  from GitHub, it **hard-fails**. The asset-cache key is the **bare lowercase SHA512 hex, no\n  extension**: the blob lives at `…\u002Fartifacts\u002F\u003Csha512>`. Quick probe (no auth needed, read is public):\n  ```bash\n  curl -s -o \u002Fdev\u002Fnull -w \"%{http_code}\\n\" \\\n    https:\u002F\u002Fvcpkg.storage.devpackages.microsoft.io\u002Fartifacts\u002F\u003Cportfile-sha512>\n  # 200 = already mirrored (legs will pass) ; 404 = NOT mirrored (every vcpkg leg will 404-fail)\n  ```\n- **(g) This recurs on EVERY archive bump, including each RC.** rc1→rc2→…→formal each produce a\n  *new* `.tar.gz` with a *new* SHA512, so each one is a distinct, un-mirrored blob. A green rc1 run\n  does **not** mean rc2 is mirrored. After every `portfile.cmake` REF\u002FSHA512 change, run the curl\n  probe above; if 404, the upload (steps 1–2) must happen again before any `--use_vcpkg` leg can pass.\n  Terrapin-enabled self-hosted Windows legs (`-a true`) self-seed the mirror as a side effect; the\n  read-only `x-azurl` legs (Linux\u002FmacOS\u002FGitHub-hosted) cannot and will 404 until that seed lands.\n- **(h) CI failure signature (how to recognize this in a red build).** Only the **vcpkg-based**\n  legs fail; the `cmake\u002Fdeps.txt` FetchContent legs stay green (they pull the `.zip` from GitHub,\n  mirror-independent — see (d)). The failing leg's log shows a vcpkg download error during the\n  `onnx` port install, e.g.:\n  ```\n  error: Failed to download from mirror set\n  error: https:\u002F\u002Fvcpkg.storage.devpackages.microsoft.io\u002Fartifacts\u002F\u003Csha512>: failed: status code 404\n  error: x-block-origin set, prohibiting access to the original source URL\n  ```\n  That `404` + `x-block-origin set` pair on a `…\u002Fartifacts\u002F\u003Csha512>` URL **is** this gotcha — the\n  `\u003Csha512>` in the error equals the `portfile.cmake` SHA512. (Confirmed live for ONNX 1.22.0rc2:\n  rc1 blob → HTTP 200, rc2 blob → HTTP 404.)\n- **(i) Fix options (in order of preference).**\n  1. **Self-seed via a Terrapin Windows leg** — trigger\u002Fre-run one internal Azure DevOps pipeline\n     whose Windows job runs on a self-hosted pool (has `C:\\local\\Terrapin\\TerrapinRetrievalTool.exe`)\n     and passes `--use_vcpkg_ms_internal_asset_cache`. Its `-a true` Terrapin fetches the archive\n     from origin and **writes it back to the mirror**; afterwards re-run the failing read-only legs.\n     (No infra ticket needed.) Manual Terrapin upload commands are in §2 steps 1–2 above.\n  2. **`az storage blob upload`** — anyone with write access to the `devpackages` storage account:\n     download the exact origin `.tar.gz`, verify `sha512sum` equals the portfile SHA512, then\n     `az storage blob upload --container-name artifacts --name \u003Csha512> --file \u003Ctar.gz> --auth-mode login`.\n     The blob **name must be the bare lowercase SHA512, no extension**.\n  3. **EngSys \u002F 1ES ticket** — if neither self-service path is available, ask the team that owns\n     `vcpkg.storage.devpackages.microsoft.io` to mirror the asset, giving them the origin URL + SHA512.\n  Verify any fix with the §2(f) curl probe returning **HTTP 200** before re-running CI.\n\n  > Detailed worked example (ONNX 1.22.0rc2 coordinates, live-probe evidence, full procedures):\n  > see the architect runbook artifact `architect-f1afcb8a\u002Fonnx-rc2-vcpkg-mirror-runbook.md`.\n\n## 3. onnx.patch rebase + binskim.patch mirror\n\nFor each hunk in `cmake\u002Fpatches\u002Fonnx\u002Fonnx.patch`, against the **new** ONNX source:\n- Applies cleanly → keep.\n- Context shifted → rebase (regenerate line numbers\u002Findices).\n- Fixed upstream in the new ONNX → **drop the hunk** (#27601 added a Slice `dim_value==0`\n  hunk for rc1\u002Frc2 then removed it at rc3 once ONNX merged it).\n- ONNX restructured the region → **rewrite the hunk.** Example (1.21 → 1.22): ONNX replaced\n  the `file(GLOB_RECURSE __tmp_srcs ...)` source-gathering block with an\n  `add_library(onnx_core OBJECT)` + `add_subdirectory(onnx)` model, so the `ONNX_MINIMAL_BUILD`\n  hunk had to switch from editing `set(ONNX_SRCS ...)` to\n  `target_sources(onnx_core PRIVATE \"${ONNX_ROOT}\u002Fonnx\u002Fdefs\u002Fdata_type_utils.cc\")`. Note\n  `data_type_utils.cc` lives in `onnx\u002Fdefs\u002F` (not `onnx\u002Fcommon\u002F`), and headers do **not**\n  belong in `target_sources` (they are not compile units).\n  > **Structural note (re-validate every bump):** in the `onnx_core` OBJECT-lib layout,\n  > minimal mode skips `add_subdirectory(onnx)`. That is only safe because\n  > `add_onnx_compile_options(onnx_core)` (include dirs + protobuf link) and\n  > `add_onnx_global_defines(onnx_core)` are **unconditional at the top-level CMakeLists**.\n  > If a future ONNX moves those into the `onnx\u002F` subdirectory, minimal builds will fail to\n  > configure\u002Flink — so never *assume* the minimal hunk still works; re-run the minimal build\n  > (§5) on every bump.\n\nThen **mirror the final `onnx.patch` byte-for-byte into\n`cmake\u002Fvcpkg-ports\u002Fonnx\u002Fbinskim.patch`** — they must stay identical. Verify:\n```bash\ngit apply --check cmake\u002Fpatches\u002Fonnx\u002Fonnx.patch\npatch --binary --ignore-whitespace -p1 --dry-run \u003C cmake\u002Fpatches\u002Fonnx\u002Fonnx.patch\nsha1sum cmake\u002Fpatches\u002Fonnx\u002Fonnx.patch cmake\u002Fvcpkg-ports\u002Fonnx\u002Fbinskim.patch  # must match\n```\n\n## 4. Gotchas (the expensive ones — hand-check these)\n\nThese are not caught by the automated audit and have bitten real integrations:\n\n**(a) The optimizer audit script misses fusion path-matchers.**\n`tools\u002Fpython\u002Ffind_optimizer_opset_version_updates_required.py` only inspects **direct kernel\nregistrations** — it does **not** see opset version lists embedded in optimizer\n`graph_utils::EdgeEndToMatch` **path-matchers**. When an op's opset changes, hand-grep the\nfusion files and extend the version list. Confirmed sites for **`Range` → 27**:\n- `onnxruntime\u002Fcore\u002Foptimizer\u002Fembed_layer_norm_fusion.cc` — `EdgeEndToMatch` entries\n  `{0, 0, \"Range\", {1, 11, 27}, kOnnxDomain}` (multiple `parent_path_*`).\n- `onnxruntime\u002Fcore\u002Foptimizer\u002Fgather_fusion.cc` — `IsSupportedOptypeVersionAndDomain(node,\n  \"Range\", {1, 11, 27})` (Range→Gather→Slice fusion).\n```bash\ngrep -rn '\u003CChangedOp>' onnxruntime\u002Fcore\u002Foptimizer\u002F*fusion*.cc | grep -iE 'EdgeEndToMatch|IsSupportedOptypeVersionAndDomain|\\{[0-9, ]+\\}'\n```\n\n**(b) EP `base_op_builder.h` `GetMaxSupportedOpSet` must be bumped in lockstep.**\nEach NPU\u002Fcoreml\u002Fweb EP caps the opset it will partition. Bump every one that returns the old\nmax:\n- `onnxruntime\u002Fcore\u002Fproviders\u002Fcoreml\u002Fbuilders\u002Fimpl\u002Fbase_op_builder.h`\n- `onnxruntime\u002Fcore\u002Fproviders\u002Fnnapi\u002Fnnapi_builtin\u002Fbuilders\u002Fimpl\u002Fbase_op_builder.h`\n- `onnxruntime\u002Fcore\u002Fproviders\u002Fvsinpu\u002Fbuilders\u002Fimpl\u002Fbase_op_builder.h`\n- `onnxruntime\u002Fcore\u002Fproviders\u002Fwebnn\u002Fbuilders\u002Fimpl\u002Fbase_op_builder.h`\n```bash\ngrep -rn 'GetMaxSupportedOpSet' onnxruntime\u002Fcore\u002Fproviders\u002F*\u002Fbuilders\u002Fimpl\u002Fbase_op_builder.h\n# all should return the NEW max opset (e.g. 27)\n```\n\n**(c) Fusion `IsSupportedOptypeVersionAndDomain` version lists** (same root cause as **a**) —\ngrep all of `onnxruntime\u002Fcore\u002Foptimizer\u002F` for the changed op, not just `*fusion*` files.\n\n**(d) The audit script crashes on placeholder macros.**\n`find_optimizer_opset_version_updates_required.py` has a pre-existing crash (a placeholder\n`'ver'` token gets parsed as an `int`). Expect it to throw on a clean main; don't treat the\ncrash as a signal from your change. Run it, but rely on the manual greps in **a–c**.\n\n**(e) `OperatorKernels.md` regeneration needs a built ORT Python module.**\n`gen_opkernel_doc.py` imports `onnxruntime`, so build + install the wheel first (or download\nthe regenerated markdown from a CI published-artifact, which the dev-notes recommend).\n\n**(f) vcpkg artifact mirroring** — see §2; `--use_vcpkg` builds fail to download until done.\nBecause the vcpkg path is mirror-gated (not self-service), **do not rely on it to validate the\n`onnx.patch` rebase.** Use the minimal build instead — it is mirror-independent (see §5 and **(i)**).\n\n**(g) Defer-and-filter brand-new, unimplemented ops — and why it is safe.**\nDo not block the version bump on full kernels for large new ops (e.g. stateful\nattention\u002Fconv). Exclude them in `onnx_backend_test_series_filters.jsonc` and track kernels as\nfollow-up PRs (#25678 deferred `TensorScatter`\u002F`Swish` this way). Keeps the bump PR reviewable.\nThis is safe because ORT failures here are **node-local, not model-load-blocking**:\n- ORT kernel matching is per-`(op, type-set)`. Registering an updated op for only a **subset**\n  of the new schema's types (e.g. `Range`-27 with the 5 common numeric types, deferring\n  fp16\u002Fbf16) does **not** break model load — the ONNX schema validates the model; only the\n  specific unsupported-type node fails kernel lookup with a clear \"kernel not found\".\n- Deferring a brand-new op entirely (no kernel) is likewise node-local: ORT advertises the new\n  opset because the **submodule** registers the schemas (`operator_sets.h` \u002F\n  `DomainToVersionRange::Map()`), independent of the transpose-optimizer `kMaxSupportedOpset`.\n  The model **loads**; the unimplemented node fails at kernel-bind. Precedent: `TensorScatter`\n  (24), `LpNormalization` (22, test name `test_l2normalization`), `TreeEnsemble` all sit in the filters file as \"not implemented\".\n- Optional new schema attributes whose default \"has no effect for other types\" (e.g.\n  `Range`-27 `stash_type`) can be ignored by the kernel for the registered types — safe to defer.\n\n**(h) Function-op test filters — don't over-filter the `_expanded` variant.**\nMany new\u002Fupdated ops are ONNX **functions** (have `SetContextDependentFunctionBodyBuilder`) —\ne.g. `Range`-27, `CausalConvWithState`, `LinearAttention`. ONNX emits two backend-test\nvariants: `test_\u003Cop>` and `test_\u003Cop>_expanded` (the decomposed primitive subgraph). ORT can\nusually **run** `_expanded` via the primitive decomposition even when the fused kernel is\ndeferred. An **unanchored** filter like `^test_range_float16_type` also drops `_expanded`,\nsilently losing real coverage. When deferring an op, in §5\u002FT7 run `onnx_test_runner` and check\nwhether the `_expanded` variants pass; if they do, **narrow the filter** (anchor it \u002F exclude\nonly the bare name) to keep coverage. Over-filtering is CI-safe but hides working paths.\n\n**(i) On a shared multi-agent branch, read clean `main`, not the working tree.**\nAnother agent may already have rebased `onnx.patch`. Inspect with\n`git show \u003Cmain-sha>:cmake\u002Fpatches\u002Fonnx\u002Fonnx.patch` rather than reading the file directly.\n\n**(j) A green Linux webgpu CI leg does NOT mean WebGPU ran the node tests.**\nNew ONNX backend node tests (`OnnxBackendNodeModelTest`) only **execute** on the\n**macOS-arm64** webgpu CI leg. The Linux webgpu leg (`.github\u002Fworkflows\u002Flinux_webgpu.yml`) is\n**build-only** — it compiles the WebGPU EP but runs no kernels. So a green Linux webgpu\nleg proves the build, not that any WebGPU op actually ran. To exercise WebGPU kernels\noff-Mac locally, use a software Vulkan adapter (Mesa lavapipe) — see the\n`webgpu-local-testing` skill.\n\n**(k) Filter vs. override — pick the right one for a newly-failing node test.**\nTwo test-data files handle failures differently; choosing wrong either hides a real bug\nor drops coverage:\n- `onnx_backend_test_series_filters.jsonc` = **SKIP** a test for a **real EP bug**.\n  Always cite the tracking issue **and** a removal condition (when the skip can be\n  deleted). When only the reference **decomposition** path is broken, skip just the\n  `_expanded` variant — not the bare test (see gotcha **h**).\n- `onnx_backend_test_series_overrides.jsonc` = **RELAX ATOL** for benign fp16\u002FULP\n  differences. This **keeps the test running** — prefer it over a skip whenever the\n  kernel is actually correct and the failure is only numeric tolerance. **Guardrail:**\n  relax atol only **after** root-causing the diff as ~1 ULP at the output magnitude or a\n  few elements (e.g. `5e-4` ≈ 1 fp16 ULP at `O(1)` values). Unexplained, large, or\n  **growing** diffs are bugs to **investigate**, not override.\n\n**(l) New upstream reference tests can EXPOSE latent EP bugs.**\nA bump pulls in new\u002Fupdated reference tests that may surface pre-existing EP bugs the\nold test set never hit. Example: #28969 — a WebGPU broadcast underflow that ONNX\n1.22's expanded-Attention reference tests exposed. Treat such failures as bugs to fix\n(or filter-with-issue per gotcha **k**), not as bump noise.\n\n**(m) After a FINAL release, re-seed the vcpkg MS-internal asset mirror or every `--use_vcpkg` leg 404s.**\nThe MS-internal vcpkg asset mirror (`vcpkg.storage.devpackages.microsoft.io`) is **not**\nself-service: the new onnx tag tarball must be **Terrapin-seeded** into it. Until that\nlands, every `--use_vcpkg` CI leg fails the download with an `x-block-origin` **404**\n(the mirror refuses to proxy an un-seeded asset). This bites at the **rc → FINAL** re-pin\ntoo — the released `vX.Y.0` tag tarball is a *different* asset hash than the RC, so the\nmirror must be re-seeded for the final tag. Treat \"seed the tag tarball to the vcpkg\nmirror\" as a required step of the Phase-2 final re-pin, not a follow-up. (See §2 for the\nmirror procedure; the `--minimal_build extended` gate in §9 is the mirror-independent\nstand-in until seeding completes.)\n\n**(n) A FINAL onnx release can still ship a map-max opset > last *release* opset.**\n\"FINAL\" does **not** imply the new opset is released. ONNX 1.22.0 ships\n`DomainToVersionRange` **map-max 27** while the last *released* opset is **26** — i.e.\nopset 27 stays **under development** for the entire 1.22 cycle. So strict legs (the\ndefault, or `ALLOW_RELEASED_ONNX_OPSET_ONLY=1`) still throw **\"Opset N under development\"**\nat model load on any `*CurrentOpset` test that builds at the map-max opset — even after\nthe bump is \"final\". Don't assume the under-development gating ends when the RC does.\n\n**(o) Prefer per-model `ModelOptions{allow_released_opsets_only=false}` over per-leg env flips or `GTEST_SKIP`.**\nFor `*CurrentOpset` tests caught by gotcha **n**, set the relaxation **per model** via\n`ModelOptions{\u002F*allow_released_opsets_only*\u002F false, …}` on the `Model::Load` \u002F\n`TestGraphTransformer` \u002F `TransformerTester` call. This is **leg-agnostic** (the test then\nexercises the new opset on *every* CI leg, not just the ones that happen to set the env\nvar) and **preserves opset coverage** (unlike `GTEST_SKIP`, which silently drops it).\nAvoid flipping a per-leg CI env var (`ALLOW_RELEASED_ONNX_OPSET_ONLY=0`) — it only fixes\nthe legs you remember to touch and leaves the default-strict legs red. Precedent on this\nbranch: `38f17243b` (GatherToSlice), generalized to all `*CurrentOpset` fusion tests.\nAnnotate each call site with a one-line WHY + the tracking issue so it can be removed once\nthe opset is released (#28966).\n\n**(p) A future onnx commit may DELETE the on-disk node-test corpus — a SILENT-GREEN C++ bump landmine.**\nORT's **C++** node-test coverage depends on ONNX **shipping pre-generated `.pb` artifacts\ninside the pinned archive**: `onnx_test_runner` reads `model.onnx` + `test_data_set_*\u002F*.pb`\nfrom `_deps\u002Fonnx-src\u002Fonnx\u002Fbackend\u002Ftest\u002Fdata\u002Fnode\u002F\u003Ctest>\u002F` (the FetchContent archive pinned by\nthe **SHA1 on the `onnx;` line of `cmake\u002Fdeps.txt` ~L40** — *not* the `cmake\u002Fexternal\u002Fonnx`\nsubmodule, which only the QNN CI `...\u002Fnode` path uses). **ONNX PR [onnx\u002Fonnx#7959] \"Remove\nnode test artifacts\" deletes that entire on-disk corpus** (~2992+ files) — it targets onnx\nmaster, so the first affected release is expected to be **onnx 1.23** (one past the current\n1.22 pin), consistent with the JS `NOTE(#7959)` \"onnx >= 1.23\" caveat — **and removes the\n`cmd_tools.py generate-data` node path**, replacing both with a **Python-only, in-memory**\nflow (`loader.load_node_model_tests()` → `runner.run_node()`, `model_dir=None`, nothing\nwritten to disk). Naming \u002F directory layout \u002F `.pb` format are **unchanged — the files are\nsimply gone.**\n- **⚠️ The C++ failure is SILENT, not loud.** Because names\u002Flayout\u002Fformat don't change, the\n  C++ skip contracts (`GetBrokenTests` \u002F `immutable_broken_tests` in `TestCase.cc` +\n  `main.cc`, names *without* the `test_` prefix) **don't throw or mismatch** — their target\n  paths just cease to exist. An empty `data\u002Fnode` yields **ZERO** collected cases; the\n  runner's directory BFS finds nothing, `ctest`\u002F`add_test` **exit 0**, and\n  `onnx_test_runner -e cuda ...\u002Fnode\u002F\u003Ctest>` prints nothing and **returns success.** C++\n  node-test **kernel guarding evaporates behind a GREEN CI with no failure signal** —\n  strictly worse than a hard break.\n- **The Python leg SURVIVES automatically — do not conflate it.** `onnx_backend_test_series.py`\n  does **zero** `.pb`\u002F`model.onnx` disk I\u002FO: it subclasses `onnx.backend.test.runner.Runner`\n  and delegates discovery+execution to the **installed pip `onnx`** package, whose base\n  `_add_model_test` already branches on `model_dir is None` → in-memory. ORT's only override\n  is a rtol\u002Fatol injector (`onnx_backend_test_series.py:60-71`), field-agnostic, no disk. So\n  the name-keyed **Python** contracts (`onnx_backend_test_series_filters.jsonc` `^test_`\n  regexes, `onnx_backend_test_series_overrides.jsonc` atol) keep resolving against the pip\n  package's in-memory tests and stay valid. **The mitigation surface is C++-only.** (Residual\n  Python risk is only if #7959 changes the public subclass contract — `Runner.__init__` \u002F\n  `_add_model_test` \u002F `assert_similar_outputs` sig \u002F `TestCase` field names.)\n- **Guardrail — verify the corpus survives BEFORE advancing the `cmake\u002Fdeps.txt` pin.** As of\n  this writing #7959 is **OPEN + CONFLICTING** (no onnx pin includes it yet), so this is\n  latent, not active. When re-pinning, confirm the target commit still ships the on-disk tree:\n  ```bash\n  # Does the target onnx commit\u002Ftag still contain the on-disk node-test corpus?\n  gh api \"repos\u002Fonnx\u002Fonnx\u002Fcontents\u002Fonnx\u002Fbackend\u002Ftest\u002Fdata\u002Fnode?ref=\u003Ctarget-commit-or-tag>\" \\\n    --jq 'length'      # >0 = corpus present (safe) ; 404 = DELETED (see #7959) -> STOP\n  # note: the contents API caps directory listings at 1000 entries (~1799 dirs exist today),\n  # so 'length' returns 1000, not the true count — fine here since we only test >0 vs 404.\n  # For an exact count use the git trees API instead:\n  #   gh api \"repos\u002Fonnx\u002Fonnx\u002Fgit\u002Ftrees\u002F\u003Ctarget-sha>?recursive=1\" --jq '[.tree[].path | select(startswith(\"onnx\u002Fbackend\u002Ftest\u002Fdata\u002Fnode\u002F\"))] | length'\n  # Or on a materialized archive\u002Fworktree:\n  ls _deps\u002Fonnx-src\u002Fonnx\u002Fbackend\u002Ftest\u002Fdata\u002Fnode | head   # empty => the landmine has landed\n  ```\n- **Mitigation if a bump is forced onto a #7959 commit (C++ leg only):** ORT must **own\n  node-test materialization** — a CMake `add_custom_command` (gated on\n  `onnxruntime_BUILD_UNIT_TESTS`) that imports the ONNX Python case generators (**confirmed\n  byte-identical \u002F untouched by #7959** — `onnx\u002Fbackend\u002Ftest\u002Fcase\u002Fnode\u002F*.py`'s `export.*` +\n  `expect(...)` remain; #7959 removes only the serialized `.pb` output, not the source) via\n  `load_node_model_tests()`, and re-serializes each `_NodeTestCase` back to the on-disk\n  `model.onnx` + `test_data_set_*\u002F{input,output}_N.pb` layout the C++ loader expects (exact\n  serialization detail below — **not** `from_array`-only). Then repoint the C++ consumers\n  (runner arg + QNN CI `...\u002Fnode` path). This also collapses the 3-independent-onnx-pins \u002F\n  dual-skip-list tangle into one materialized copy. **Reference implementation already exists — vendor it, do not\n  re-derive.** Vendor the ~85-line node branch of `onnx\u002Fbackend\u002Ftest\u002Fcmd_tools.py:generate_data`\n  (`cmd_tools.py:64-110` — the very disk-writer #7959 removes) into a build-time script:\n  - **Entry point: `collect_testcases(op_type=None)`** — pass `op_type=None` **explicitly**\n    to collect ALL ops (it is a required positional; a real `op_type` silently returns a\n    1-op subset — another silent-undercount path). It **self-calls `import_recursive`\n    internally** (`onnx\u002Fbackend\u002Ftest\u002Fcase\u002Fnode\u002F__init__.py:417`), so one call both populates\n    and returns `_NodeTestCases` — no separate import step, no empty-list footgun.\n  - **Per `TestCase`**: write `case.model.SerializeToString()` → `model.onnx`, and serialize\n    each input\u002Foutput into `test_data_set_0\u002F{input,output}_j.pb` using the reference's\n    **4-branch dispatch on `graph.input[j].type`** — `numpy_helper.from_dict` (map) \u002F\n    `from_list` (sequence) \u002F `from_optional` (optional) \u002F `from_array`-or-`SerializeToString`\n    (tensor). **Do NOT hand-simplify to `from_array`-only** — that silently mis-serializes\n    every Sequence\u002FMap\u002FOptional node test (`SequenceInsert`, `Optional*`, some Loop\u002FScan\n    fixtures). Dir name = `case.name` (already carries the `test_` prefix). Positional binding\n    is by the same `j` as the model's `graph.input`, so any `len(inputs) != len(graph.input)`\n    mismatch IndexErrors at BUILD time (fail-loud, never a bad corpus).\n  - **MANDATORY version parity (correctness, not just hygiene).** `expect()` stamps each\n    `model.onnx`'s `opset_import`\u002FIR from the **compiled onnx RUNTIME's** C++ schema registry\n    (`get_schema(op_type, domain).since_version`, `case\u002Fnode\u002F__init__.py:311-319`), *not* the\n    `.py` case source. So the shim MUST run under an installed onnx wheel whose\n    `onnx.__version__` **==** the `cmake\u002Fdeps.txt` pin, **hard-asserted as its first line**\n    (`assert onnx.__version__ == \u003Cdeps-derived>`) — a mismatched wheel bakes the WRONG opset\n    into `model.onnx` = silent corpus drift. Make this structural by **deriving the 6 CI\n    `requirements.txt` `onnx==` pins FROM `cmake\u002Fdeps.txt`** so wheel==archive by construction\n    (this is also what keeps the *Python* leg testing the pinned version). Under that parity\n    the installed wheel's `case\u002Fnode\u002F*.py` are byte-identical to the archive's, so the shim\n    uses the installed wheel directly — no `_deps\u002Fonnx-src` source plumbing needed.\n  - Also **assert `len(_NodeTestCases) > 0`** at shim start — fail the build on an empty\n    materialization (the build-time twin of the runtime tripwire below).\n- **Min-count TRIPWIRE (mitigation 0 — highest ROI, cause-AGNOSTIC, do this regardless of\n  #7959).** The deepest problem is that corpus absence is **silent-green**, so assert a floor\n  on discovered node-test count in **both** harnesses: C++ at `onnx\u002Fmain.cc:937` right after\n  `LoadTests` populates the vector — `ORT_ENFORCE(tests.size() >= floor, ...)` (add a\n  `--min_cases N` flag); Python in `onnx_backend_test_series.py` after `create_backend_test()`\n  asserting a floor on `len(backend_test.test_cases)`. This converts *any* future\n  corpus-absence regression (#7959, a bad archive, broken FetchContent, an over-matching\n  filter) from an invisible pass into a **loud red** — the single highest-leverage, lowest-cost\n  change here.\n- **Other on-disk node-test consumers to repoint when #7959 lands (track, don't lose).** Beyond the\n  C++ `onnx_test_runner` + QNN `...\u002Fnode` path, three more consumers read the ONNX on-disk\n  node-test data and will break\u002Fskip silently once #7959 deletes it — repoint\u002Fupdate each in the\n  same bump: **`csharp\u002Ftest\u002FMicrosoft.ML.OnnxRuntime.EndToEndTests\u002Fruntest.sh`** (C# backend test\n  runner pointed at the node dir), **`js\u002Fscripts\u002Fprepare-onnx-node-tests.ts`** (the JS\u002Fweb test\n  harness that stages the node corpus), and **`docs\u002Fpython\u002Fconf.py`** (Sphinx doc build that\n  references the node-test data). None are\n  covered by the C++ materialization mitigation above; each needs its own repoint to the\n  materialized tree (or removal) when the corpus source disappears.\n\n## 5. Build & validate locally\n\n**The canonical build + test command set lives in §9 (verification gauntlet)** — run those to\ndefine \"done\". This section keeps only the\n*rationale* for the highest-risk gate (don't duplicate the command list here — it drifts):\n\n> **MERGE GATE for the patch rebase (gotcha a\u002Fi + §3):** the `ONNX_MINIMAL_BUILD` hunk in\n> `onnx.patch`\u002F`binskim.patch` is the single highest-risk change and is **only** exercised when\n> `ONNX_MINIMAL_BUILD=ON` — set by `build.py --minimal_build` **and always by the vcpkg port**\n> (`tools\u002Fpython\u002Futil\u002Fvcpkg_helpers.py`). Since the vcpkg path is mirror-gated (gotcha f), the\n> `--minimal_build extended` command (§9 step 2) is the **cheap, mirror-independent gate**: it\n> pulls the ONNX archive from `cmake\u002Fdeps.txt` (no vcpkg mirror needed) and proves the rebased\n> minimal hunk **configures + compiles + links**. Make a green minimal build a required gate\n> before merging any `onnx.patch` rebase.\n\nThen update `TestCase.cc` \u002F `onnx_backend_test_series_*.jsonc` for newly-introduced failing\nnode tests. After the PR is up, manually queue every packaging pipeline on the branch, and ask\ninfra to deploy any new ONNX test data to CI machines (dev-notes).\n\n## 6. Quick checklist\n- [ ] Group A: deps.txt (zip SHA1), submodule, vcpkg.json, portfile.cmake (tar.gz SHA512), onnx.patch rebased, binskim.patch mirrored byte-identical, all 7 requirements.txt (NOT the 3 transformers-model files frozen at onnx==1.18.0)\n- [ ] Group B: `kMaxSupportedOpset`, cpu_execution_provider.cc opset block, **version-split the changed op in EVERY EP that registers it (cpu+cuda+js, rocm if present) — §1 Group B all-EP tradition**, (contrib\u002Fdml macros if build demands)\n- [ ] **Safety invariant (§11): every new no-kernel op MUST carry an ONNX function body — else its native kernel is a BLOCKER this PR, not a follow-up**\n- [ ] Gotchas: fusion path-matchers (embed_layer_norm_fusion.cc, gather_fusion.cc), all 4 EP `GetMaxSupportedOpSet`, run audit script (expect crash), defer-and-filter new ops (node-local & safe), narrow function-op `_expanded` filters\n- [ ] Group C: OperatorKernels.md (built module), webgl-operators.md, backend test filters\u002Foverrides\n- [ ] Validate: run the **§9 verification gauntlet** (full build, `--minimal_build extended` gate, onnxruntime_test_all, onnx_test_runner -e cpu; `--use_vcpkg` after artifact mirrored)\n\n## 7. Worked example — ONNX 1.21 → 1.22.0rc1 (a template to copy)\n\nThe real values from this session's bump. **Replace every value in `\u003C...>` for the next bump**;\nthe structure stays the same.\n\n| Field | This bump (1.21 → 1.22.0rc1) | Where it goes |\n|---|---|---|\n| Source pin | commit `bc3be77bec2f628788796dff60819186bacf49df` (`rel-1.22.0` HEAD; RC has **no git tag** yet) | submodule + deps.txt URL + portfile `REF` |\n| deps.txt SHA1 | `421e5a9afb6c41a54696e424e5b9a3796aab6821` (**SHA1 of the `.zip`**) | `cmake\u002Fdeps.txt` 3rd field |\n| portfile SHA512 | `e0c526f50767f376b8ad2ac3dc6b109c65f5b3ed20418fd3c4260a954b796d828a30cb5141a23db9b4db8e4db391bfe5042ef99d141f60bdbdbb991b1f3ce467` (**SHA512 of the `.tar.gz`**) | `cmake\u002Fvcpkg-ports\u002Fonnx\u002Fportfile.cmake` |\n| Opset | 26 → **27** | `kMaxSupportedOpset`, cpu_execution_provider.cc |\n| IR version | **13 (unchanged)** — auto from headers, do not touch | — |\n| New\u002Fupdated ops | `Range`-27 (updated, function), `CausalConvWithState` + `LinearAttention` (new, functions) | see §11 safety invariant |\n\n**Exact files touched this bump** (26 files — use as a coverage checklist):\n```\ncmake\u002Fdeps.txt                          # zip SHA1 + URL\ncmake\u002Fexternal\u002Fonnx                     # submodule -> bc3be77b\ncmake\u002Fpatches\u002Fonnx\u002Fonnx.patch           # rebase: 2 files \u002F 3 @@ hunks — CMakeLists.txt (option decl + ONNX_MINIMAL_BUILD src restructure) + onnx\u002Fdefs\u002Fnn\u002Fold.cc (GroupNormalization-18 .Deprecate() removal). Utils.cmake NOT touched (upstream-merged protobuf hunk dropped this bump)\ncmake\u002Fvcpkg-ports\u002Fonnx\u002Fbinskim.patch    # byte-identical mirror of onnx.patch\ncmake\u002Fvcpkg-ports\u002Fonnx\u002Fportfile.cmake   # REF bc3be77b + tar.gz SHA512\ncmake\u002Fvcpkg-ports\u002Fonnx\u002Fvcpkg.json       # version-semver + port-version 0\ndocs\u002FOperatorKernels.md                 # CPU-only surgical update (full regen = follow-up)\njs\u002Fweb\u002Fdocs\u002Fwebgl-operators.md          # npm run build:doc\nonnxruntime\u002Fcore\u002Foptimizer\u002Fembed_layer_norm_fusion.cc   # Range path-matcher {1,11,27} (gotcha a)\nonnxruntime\u002Fcore\u002Foptimizer\u002Fgather_fusion.cc             # Range {1,11,27} (gotcha c)\nonnxruntime\u002Fcore\u002Foptimizer\u002Ftranspose_optimization\u002Foptimizer_api.h   # kMaxSupportedOpset 26->27\nonnxruntime\u002Fcore\u002Fproviders\u002Fcpu\u002Fcpu_execution_provider.cc            # opset-27 kernel block\nonnxruntime\u002Fcore\u002Fproviders\u002Fcpu\u002Fgenerator\u002Frange.cc                   # Range-27 kernel\nonnxruntime\u002Fcore\u002Fproviders\u002F{coreml,nnapi,vsinpu,webnn}\u002F...\u002Fbase_op_builder.h  # GetMaxSupportedOpSet ->27 (gotcha b)\nonnxruntime\u002Ftest\u002Foptimizer\u002Fgraph_transform_test.cc                  # fusion test updates\nonnxruntime\u002Ftest\u002Ftestdata\u002Fonnx_backend_test_series_filters.jsonc    # defer-and-filter new ops\nonnxruntime\u002Ftest\u002Fpython\u002Frequirements.txt + 6 CI requirements.txt    # onnx==1.22.0rc1 (NOT the 3 frozen at 1.18.0)\nrequirements-materialize-onnx-node-tests.txt    # root minimal onnx+numpy pins for the QNN\u002Fandroid MATERIALIZE legs; MUST match the cmake gate + numpy MIN pins (gotcha p)\n```\n\n## 8. Hash-recompute one-liners\n\nBoth hashes are of the **GitHub source archive at the pinned ref**, but of **different archive\nformats** — keep them straight:\n\n```bash\nREF=bc3be77bec2f628788796dff60819186bacf49df      # commit (RC) — or  v1.22.0  (formal tag)\n\n# deps.txt 3rd field = SHA1 of the .ZIP:\ncurl -sL \"https:\u002F\u002Fgithub.com\u002Fonnx\u002Fonnx\u002Farchive\u002F${REF}.zip\"    | sha1sum\n\n# portfile.cmake SHA512 = SHA512 of the .TAR.GZ:\ncurl -sL \"https:\u002F\u002Fgithub.com\u002Fonnx\u002Fonnx\u002Farchive\u002F${REF}.tar.gz\" | sha512sum\n```\n> `.zip` → **SHA1** → deps.txt. `.tar.gz` → **SHA512** → portfile. Never cross them.\n> For a formal release use `refs\u002Ftags\u002Fv\u003Cversion>` in place of the bare commit in both URLs.\n\n> ⚠️ **RC commit and formal tag produce DIFFERENT hashes — recompute is MANDATORY when you\n> switch `REF`.** GitHub names the archive's top-level directory after the ref\n> (`onnx-\u003Ccommit-sha>\u002F` for a commit vs `onnx-\u003Cversion>\u002F` for `refs\u002Ftags\u002Fv\u003Cversion>`), so the\n> archive **bytes differ** even though the tree content is identical. **Never reuse the RC\n> commit's SHA1\u002FSHA512 for the formal tag** — re-run both one-liners above against the tag.\n\n## 9. Verification gauntlet — what \"done\" means\n\nRun all of these green before calling a bump complete:\n\n```bash\n# 1. Full build (deps.txt path):\n.\u002Fbuild.sh --config RelWithDebInfo --build_wheel --parallel\n# 2. Minimal-build gate (proves the rebased ONNX_MINIMAL_BUILD onnx.patch hunk; mirror-independent):\n.\u002Fbuild.sh --config RelWithDebInfo --minimal_build extended --parallel --skip_tests\n# 3. ORT unit tests:\n.\u002Fbuild\u002FLinux\u002FRelWithDebInfo\u002Fonnxruntime_test_all\n# 4. ONNX backend node tests for the new opset on CPU EP:\n.\u002Fbuild\u002FLinux\u002FRelWithDebInfo\u002Fonnx_test_runner -e cpu \\\n  build\u002FLinux\u002FRelWithDebInfo\u002F_deps\u002Fonnx-src\u002Fonnx\u002Fbackend\u002Ftest\u002Fdata\u002Fnode\n```\n- **(a)** `ALLOW_RELEASED_ONNX_OPSET_ONLY` is **ORT-side load-time validation**\n  (`onnxruntime\u002Fcore\u002Fgraph\u002Fmodel_load_utils.h`), **not** schema registration — the ONNX schemas\n  for the new opset are **always** compiled in from the submodule regardless. With the default\n  `=1`, ORT **rejects at model-load** any model whose opset is still \"under development\" (e.g.\n  opset 27 during the RC), so the new-opset node tests fail \u002F report \"not implemented\". Build and\n  run the gauntlet with **`ALLOW_RELEASED_ONNX_OPSET_ONLY=0`** so ORT loads and runs those\n  under-development-opset models. (Consistent with §4 gotcha **g**: schemas come from the\n  submodule; this flag only governs whether ORT *accepts* the model at load.)\n- **(b)** Expect the new-op node tests you intentionally deferred to be **filtered** in\n  `onnx_backend_test_series_filters.jsonc` — but confirm the `_expanded` variants still run\n  (gotcha **h**): a function op with no native kernel must still PASS via decomposition (§11).\n- **(c)** `--use_vcpkg` build is **not** a merge gate until the artifact is mirrored (§2); the\n  minimal build (step 2) is the mirror-independent stand-in that proves the patch rebase.\n\n## 10. PR-body template (fill in the blanks)\n\n```markdown\n### Bump ONNX to \u003Cversion> (opset \u003CN>)\n\nPin: onnx\u002Fonnx@\u003Ccommit-or-tag>. Opset \u003COLD> → \u003CN>. IR version \u003Cunchanged\u002Fnew>.\n\n**Standing caveats**\n- **EP `GetMaxSupportedOpSet` bumped to \u003CN>** (coreml\u002Fnnapi\u002Fvsinpu\u002Fwebnn). These EPs gain no\n  new op kernels in this PR; the bump only raises the support ceiling so existing ops on\n  opset-\u003CN> models partition. New-op kernels for these EPs are follow-ups.\n- **`OperatorKernels.md` updated for CPU EP only** (surgical edit). A full multi-EP regen needs\n  a built ORT module per EP and is tracked as a follow-up (gotcha **e**).\n- **Opset \u003CN> is under development** (RC): ORT load-time validation rejects opset-\u003CN> models\n  unless `ALLOW_RELEASED_ONNX_OPSET_ONLY=0` (schemas are always compiled in from the submodule\n  — see §9a\u002F§4g). Brand-new ops without kernels are **deferred + filtered**\n  in `onnx_backend_test_series_filters.jsonc` (node-local, safe — see skill §4 gotcha g).\n- **GPU EPs unverified**: only CPU EP was built\u002Ftested here. CUDA\u002FDML\u002Fetc. opset-\u003CN> coverage\n  is a follow-up.\n\n**Validation:** full build ✅, `--minimal_build extended` ✅, onnxruntime_test_all ✅,\nonnx_test_runner -e cpu (opset-\u003CN> node tests) ✅.\n```\n\n## 11. SAFETY INVARIANT — no-kernel ops MUST carry a function body\n\n> **🚨 For every new\u002Fupdated opset op that you ship WITHOUT a native ORT kernel, confirm the\n> op carries an ONNX FUNCTION BODY. If it has NO function body, a native kernel is MANDATORY in\n> THIS PR — it cannot be a follow-up. Otherwise the model loads but the node fails at session\n> initialization.**\n\n**\"Native kernel\" means** any registered kernel that **binds the opset-N node** — including an\n**open-ended existing kernel** (e.g. one registered `SinceVersion(13)` with no upper bound) that\nalready covers opset N. Such an op is **already kernel-covered** and does **not** trigger the\nblocker below; the blocker applies **only** when *no* registered kernel binds the new-opset node\n**and** the op has no function body.\n\n> **Convention (cross-EP consistency) — distinct from the binding-coverage rule above.** Even\n> when an open-ended kernel already binds opset N (so it is *not* a blocker), still\n> **version-split** it — and do so in **every** EP that registers the op (see the Group B all-EP\n> tradition). PR #28754 split `Range` `[11,26]`+`27` in **both** CPU and CUDA even though CUDA's\n> `SinceVersion(11)` kernel already bound opset 27, so the advertised boundary matches the schema\n> and no EP lags behind CPU. Splitting is about **clarity\u002Fconsistency**; binding-coverage is\n> about **correctness** — keep the two concerns distinct.\n\n**Why (the function-expansion mechanism):** an ONNX *function* op ships a reference\ndecomposition into primitive ops (`SetContextDependentFunctionBodyBuilder` \u002F\n`FunctionBodyHelper`). At graph-partition time ORT **inlines** (expands) any function-op node\nthat has no registered kernel into its primitive subgraph, which IS kernel-covered — so the op\nruns with zero native code. A **non-function** op has no such fallback: with no binding kernel,\nkernel binding fails at **session initialization** (fail-fast `kernel not found`), even though\nthe model loaded (schema came from the submodule — see §4 gotcha **g**).\n\n> ⚠️ **Scope: runtime function-inlining is a FULL-build behavior.** Minimal \u002F mobile \u002F\n> ORT-format builds do **not** runtime-inline function ops. The §9 gauntlet only proves the\n> minimal build **compiles** (`--skip_tests`); it never **runs** a function-op model in a\n> minimal build. So do **not** assume the function-inlining fallback gives minimal-build runtime\n> coverage — a function-only op with no native kernel will not execute in a minimal build.\n\nThis is exactly why the 1.22 bump could defer native kernels for **`CausalConvWithState`**,\n**`LinearAttention`**, and **`Range`-27 fp16\u002Fbf16** and still pass (in a full build): all three\nare ONNX functions, so ORT inlined them. Decision rule per new op:\n\n| A binding kernel covers the opset-N node? | Op has ONNX function body? | Action |\n|---|---|---|\n| Yes (incl. open-ended existing kernel) | — | Already covered; done |\n| No | **Yes** | Safe to defer kernel — ORT inlines (full build); verify the `_expanded` test passes |\n| No | **No** | **BLOCKER — write the kernel in this PR** (op would load then fail at session init) |\n\nQuick check: grep the ONNX op's `defs.cc` for `SetContextDependentFunctionBodyBuilder` or\n`FunctionBody`; if absent, it is not a function and needs a kernel.\n",{"name":5,"description":7},"main","adf4e9713c294a6e06bd322208cd5fd3155680d1",".github\u002Fskills\u002Fonnx-opset-bump-checklist",4443,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fonnxruntime","https:\u002F\u002Fonnxruntime.ai","https:\u002F\u002Fgithub.com\u002Fmicrosoft","https:\u002F\u002Fopensource.microsoft.com","Open source projects and samples from Microsoft","2026-11-14 02:35:07",{"source":8,"skillName":5,"commands":55,"prompt":65,"zipAvailable":66},[56,59,62],{"tool":57,"command":58},"npx","npx skills add microsoft\u002Fonnxruntime --skill onnx-opset-bump-checklist",{"tool":60,"command":61},"bunx","bunx skills add microsoft\u002Fonnxruntime --skill onnx-opset-bump-checklist",{"tool":63,"command":64},"pnpm","pnpm dlx skills add microsoft\u002Fonnxruntime --skill onnx-opset-bump-checklist","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fonnxruntime\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fonnx-opset-bump-checklist\u002FSKILL.md",true,[],[69,100,134,157,176,192],{"skillNo":70,"name":71,"nameZh":6,"description":72,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":73,"categories":74,"keywords":80,"publishedAt":39},"01M69AJ65WPPT5DS4JA8YWN37B","workspace-estimation-shared-header","Use when editing a boundary-safe shared header meant to be included by both in-tree and shared-provider\u002Fplugin-bridge code (e.g. include\u002Fonnxruntime\u002Fcore\u002Fframework\u002Fworkspace_requirement.h, or any future header supporting OrtKernelWorkspaceEstimateFunc \u002F DeclareWorkspaceRequirements per issue #29775's Phase-A workspace-estimation roadmap), or when a workspace-estimation math helper needs to be callable from a future plugin EP DLL. Covers a Node forward-declaration ODR trap and the math-helper vs. graph-parsing-wrapper reuse boundary discovered while implementing PR #29811 (MatMulNBits pilot).","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fonnxruntime\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fworkspace-estimation-shared-header\u002FSKILL.md",[75],{"categoryNo":76,"categoryName":77,"taxonomyType":78,"taxonomyTypeDesc":79},"01JBSEEDCAT000000000000031","产品经理","PROFESSION","适用职业",[81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,36],"workspace estimation shared header","workspace","estimation","shared","safe","pr","code","framework","graph","parsing","node","math","bridge","roadmap","Wrapper","issue","tree","implementing","phase",{"skillNo":101,"name":102,"nameZh":6,"description":103,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":104,"categories":105,"keywords":111,"publishedAt":39},"01M69AJ3F3NXDS7QP76KZ9EX5H","webgpu-local-testing","Build and run ONNX Runtime WebGPU provider tests on Linux WITHOUT a real GPU, using a software Vulkan adapter (Mesa lavapipe). Use when you need to exercise WebGPU EP kernels off-Mac — the Linux webgpu CI leg is build-only, so software Vulkan is how you actually run WebGPU correctness tests locally. SCOPE - lavapipe only validates host-side enforce\u002Fshape bugs and MatMul-free kernels; any graph containing MatMul (including the expanded-Attention node tests) crashes lavapipe and runs ONLY on macOS-arm64 Metal, which is the source of truth for those. Covers install (dnf on Azure Linux), the --use_webgpu build flag, the onnxruntime_provider_test target, VK_ICD_FILENAMES, and the lavapipe MatMul crash gotcha.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fonnxruntime\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fwebgpu-local-testing\u002FSKILL.md",[106],{"categoryNo":107,"categoryName":108,"taxonomyType":109,"taxonomyTypeDesc":110},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",[112,113,114,115,116,117,118,119,120,121,19,122,123,124,125,91,126,127,128,129,130,131,132,133,36],"testing","webgpu","webgpu local testing","local testing","local","macos","metal","vulkan","linux","test","run","ci","exercise","how","install","only","free","host","scope","you","vk","which",{"skillNo":135,"name":136,"nameZh":6,"description":137,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":138,"categories":139,"keywords":140,"publishedAt":39},"01M69AJ1ASRSCJ90HDWN05TW82","python-kwargs-setattr-security","When reviewing or fixing Python code that uses setattr() with user-controlled kwargs to configure C++ extension objects (SessionOptions, RunOptions, etc.) in ONNX Runtime. Use this to apply the allowlist pattern that prevents arbitrary file writes and other attacks via reflected property access.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fonnxruntime\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fpython-kwargs-setattr-security\u002FSKILL.md",[],[141,142,143,19,87,144,145,146,147,148,149,150,25,151,152,153,154,155,156,36],"python","security","python kwargs setattr security","file","access","configure","use","objects","pattern","c++","extension","via","with","attacks","python code","apply",{"skillNo":158,"name":159,"nameZh":6,"description":160,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":161,"categories":162,"keywords":163,"publishedAt":39},"01M69AHZ0M0Q90TP0KYMZ10HEP","ort-transformers-gpu-pytest","Run the ONNX Runtime transformers Python tests (onnxruntime\u002Ftest\u002Fpython\u002Ftransformers) against a GPU wheel, and confirm real cuDNN\u002Fflash SDPA dispatch. Use when a transformers pytest fails with ModuleNotFoundError onnxruntime.capi, when torch's bundled CUDA\u002FcuDNN libs shadow the ORT-built ones (wrong-version dispatch or load errors), or when you must PROVE a test exercised the cuDNN SDPA decode tier instead of silently skipping\u002Ffalling back to MATH.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fonnxruntime\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fort-transformers-gpu-pytest\u002FSKILL.md",[],[164,165,166,167,141,121,19,122,168,92,147,169,25,170,171,131,172,173,174,175,36],"ort transformers gpu pytest","gpu","Transformers","pytest","flash","back","tests","SDPA","errors","load","prove","decode",{"skillNo":177,"name":178,"nameZh":6,"description":179,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":180,"categories":181,"keywords":183,"publishedAt":39},"01M69AHW3EFVHBHB2S7103YA21","ort-test","Run ONNX Runtime tests. Use this skill when asked to run tests, debug test failures, or find and execute specific test cases in ONNX Runtime.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fonnxruntime\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fort-test\u002FSKILL.md",[182],{"categoryNo":107,"categoryName":108,"taxonomyType":109,"taxonomyTypeDesc":110},[121,184,185,186,187,19,188,122,189,190,147,25,170,191,36],"ort test","run tests","debug","debug test","debug test failures","find","execute","test cases",{"skillNo":193,"name":194,"nameZh":6,"description":195,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":196,"categories":197,"keywords":198,"publishedAt":39},"01M69AHSKXBSERP5NGAJB5E6XD","ort-release-notes","Draft ONNX Runtime release notes using preset configurations for full ORT or scoped component releases. Use when generating highlights with PR links, compiling human contributor acknowledgments from compile_contributors.py output, and applying preset path filtering.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fonnxruntime\u002Fblob\u002Fmain\u002F.github\u002Fskills\u002Fort-release-notes\u002FSKILL.md",[],[199,200,201,29,202,203,19,86,204,205,206,207,208,209,25,210,211,26,212,213,36],"notes","ort release notes","release notes","component","preset","compile","compiling","contributors","releases","human","full","links","path","scoped","contributor"]