diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6423320..9da2212 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -10,6 +10,8 @@ env:
# ONNX Runtime dylib for the reference backend. rustnn pins ort 2.0.0-rc.13
# with the `api-27` feature, so this must be >= 1.27 (any newer 1.x works).
ORT_VERSION: 1.29.0
+ RUSTNN_REPO: rustnn/rustnn
+ RUSTNN_REF: main
jobs:
test:
@@ -69,7 +71,8 @@ jobs:
id: rustnn
uses: actions/checkout@v4
with:
- repository: rustnn/rustnn
+ repository: ${{ env.RUSTNN_REPO }}
+ ref: ${{ env.RUSTNN_REF }}
path: rustnn
- name: Install Rust
@@ -150,3 +153,16 @@ jobs:
O2W_MODEL_TEST_JOBS: ${{ matrix.model_test_jobs || 2 }}
O2W_MODEL_TEST_SKIP_HEAVY: 1
run: cargo test --release --locked ${{ matrix.cargo_flags }}
+
+ - name: Validate curated full model
+ if: runner.os != 'Windows'
+ env:
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
+ O2W_ONNX_CACHE: ${{ runner.temp }}/onnx2webnn-ci-models
+ O2W_WEBNN_CACHE: ${{ runner.temp }}/onnx2webnn-ci-webnn
+ run: |
+ target/release/onnx2webnn validate-models \
+ --manifest tests/models/ci-validation.json \
+ --selection all \
+ --weights real \
+ --jobs 1
diff --git a/.github/workflows/model-validation.yml b/.github/workflows/model-validation.yml
new file mode 100644
index 0000000..dcbd604
--- /dev/null
+++ b/.github/workflows/model-validation.yml
@@ -0,0 +1,119 @@
+name: Full model validation
+
+on:
+ workflow_dispatch:
+
+env:
+ ORT_VERSION: 1.29.0
+ RUSTNN_REPO: rustnn/rustnn
+ RUSTNN_REF: main
+
+jobs:
+ validate:
+ name: Full real-weight sweep
+ runs-on: [self-hosted, linux, x64, onnx2webnn-validation]
+ timeout-minutes: 360
+ defaults:
+ run:
+ shell: bash
+ working-directory: onnx2webnn
+ steps:
+ - name: Checkout onnx2webnn
+ uses: actions/checkout@v4
+ with:
+ path: onnx2webnn
+
+ - name: Checkout rustnn
+ uses: actions/checkout@v4
+ with:
+ repository: ${{ env.RUSTNN_REPO }}
+ ref: ${{ env.RUSTNN_REF }}
+ path: rustnn
+
+ - name: Check runner contract
+ working-directory: ${{ github.workspace }}
+ run: |
+ cache=/var/cache/onnx2webnn
+ test -d "$cache"
+ test -w "$cache"
+ filesystem_bytes=$(df -PB1 "$cache" | awk 'NR == 2 { print $2 }')
+ memory_bytes=$(awk '/MemTotal/ { print $2 * 1024 }' /proc/meminfo)
+ minimum_filesystem=$((100 * 1024 * 1024 * 1024))
+ minimum_memory=$((30 * 1024 * 1024 * 1024))
+ if [ "$filesystem_bytes" -lt "$minimum_filesystem" ]; then
+ echo "Runner filesystem must be at least 100 GiB" >&2
+ exit 1
+ fi
+ if [ "$memory_bytes" -lt "$minimum_memory" ]; then
+ echo "Runner memory must be approximately 32 GiB (30 GiB minimum)" >&2
+ exit 1
+ fi
+
+ - name: Install Rust
+ uses: dtolnay/rust-toolchain@stable
+ with:
+ components: rustfmt
+
+ - name: Install protobuf compiler
+ uses: arduino/setup-protoc@v3
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Cache cargo registry
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.cargo/registry
+ ~/.cargo/git
+ key: full-validation-cargo-${{ hashFiles('onnx2webnn/Cargo.lock') }}
+ restore-keys: full-validation-cargo-
+
+ - name: Download ONNX Runtime ${{ env.ORT_VERSION }}
+ run: |
+ archive=onnxruntime-linux-x64-${ORT_VERSION}
+ url=https://github.com/microsoft/onnxruntime/releases/download/v${ORT_VERSION}/${archive}.tgz
+ mkdir -p "$RUNNER_TEMP/ort"
+ curl -fsSL "$url" -o "$RUNNER_TEMP/ort/archive.tgz"
+ tar -xzf "$RUNNER_TEMP/ort/archive.tgz" -C "$RUNNER_TEMP/ort"
+ dylib="$RUNNER_TEMP/ort/$archive/lib/libonnxruntime.so"
+ if [ ! -f "$dylib" ]; then
+ dylib=$(find "$RUNNER_TEMP/ort/$archive/lib" -maxdepth 1 -name 'libonnxruntime.so.*' -print -quit)
+ fi
+ test -f "$dylib"
+ echo "ORT_DYLIB_PATH=$dylib" >> "$GITHUB_ENV"
+
+ - name: Build release binary
+ run: cargo build --release --locked
+
+ - name: Run diagnostic full sweep
+ id: validation
+ env:
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
+ O2W_ONNX_CACHE: /var/cache/onnx2webnn
+ O2W_WEBNN_CACHE: ${{ runner.temp }}/onnx2webnn-webnn
+ run: |
+ set +e
+ target/release/onnx2webnn validate-models \
+ --manifest tests/models/manifest.json \
+ --selection all \
+ --weights real \
+ --jobs 1 2>&1 | tee "$RUNNER_TEMP/model-validation.log"
+ status=${PIPESTATUS[0]}
+ set -e
+ echo "exit_code=$status" >> "$GITHUB_OUTPUT"
+ if [ "$status" -eq 0 ]; then
+ echo "## Full model validation: passed" >> "$GITHUB_STEP_SUMMARY"
+ else
+ echo "::warning::Full model validation reported failures (exit code $status); see the summary and uploaded log."
+ echo "## Full model validation: diagnostic failures" >> "$GITHUB_STEP_SUMMARY"
+ echo "The validator exited with code $status. This manually dispatched sweep is diagnostic and does not fail the workflow." >> "$GITHUB_STEP_SUMMARY"
+ fi
+
+ - name: Upload validation log
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: full-model-validation-real
+ path: ${{ runner.temp }}/model-validation.log
+ if-no-files-found: warn
+ retention-days: 14
diff --git a/.gitignore b/.gitignore
index 955e513..c6030c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,7 @@ __pycache__/
.vscode/
.idea/
.DS_Store
+
+# Model caches
+.webnn-cache/
+.onnx-cache/
diff --git a/AGENTS.md b/AGENTS.md
index f12c8f1..dd450cd 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -25,20 +25,23 @@ independent layers:
|-------|----------------|-------|-----------------|
| **1. WebNN spec** | Ops the W3C API defines | [webnn spec](https://www.w3.org/TR/webnn/) | ~105 |
| **2. Name mapping** | ONNX op name ↔ WebNN `MLGraphBuilder` method | `webnn-onnx-utils/src/operation_names.rs` | ~90 |
-| **3. Exporter implementation** | ONNX ops with a Rust lowering handler | `src/onnx/ops/*.rs` + `scripts/webnn_onnx_ops.py` | ~59 |
+| **3. Exporter implementation** | Advertised ONNX names with a lowering or preprocessing path | `src/onnx/ops/*.rs` + `scripts/webnn_onnx_ops.py` | 135 |
An ONNX op can be:
-- **No WebNN target** — e.g. `If`, `Loop`, `Scan` (control flow), `StringConcat` (no string
- tensors), `Compress`, `Einsum`, `Attention`. WebNN graphs are static DAGs; these are permanently
- rejected. See `docs/operator-conversion-plan.md` Stage 3.
-- **Mapped but not implemented** — e.g. `BatchNormalization`, `InstanceNormalization`, `ArgMax`,
- `GatherND`, `Resize`. Listed in `operation_names.rs` but no handler in `src/onnx/ops/` yet.
- Tests expect `UnsupportedOp` until a handler lands.
-- **Implemented** — listed in `scripts/webnn_onnx_ops.py` and handled by `OpRegistry`. Tests
- expect `Success` (convert + ORT vs rustnn output match).
+- **No current lowering** — for example `Loop`, `Scan`, string operators, and general
+ sequence operators. Some have no portable WebNN representation.
+- **Pattern-only or partial** — for example constant-condition `If`, the narrow
+ `SplitToSequence` + `SequenceAt` export pattern, static `OneHot`, and restricted
+ `Einsum`. The handler rejects unsupported forms.
+- **Direct or decomposed** — advertised in `scripts/webnn_onnx_ops.py`. A decomposed op may be
+ functionally supported while remaining slower or narrower than a native backend kernel.
-The test manifest (`webnn_onnx_ops.py`) mirrors layer 3 only. Regenerate tests after changing it.
+The manifest contains 135 accepted names. At opset 26, 127 are standard `ai.onnx` names;
+adding the registry-only `SplitToSequence`/`SequenceAt` pattern gives 129/198 (65.2%) standard
+names with at least one executable lowering. Of those, 96/198 (48.5%) have a direct standardized
+WebNN primitive path. Name presence is not a promise of every ONNX schema variant. See
+`docs/operator-conversion-status.md` for the complete inventory and WebNN-imposed limitations. Regenerate tests after changing the manifest.
## Build and test
@@ -104,9 +107,10 @@ Operators with multiple pre-9 schema revisions skip sub-opset-9 structure bands
until legacy attribute normalization lands; newer bands use the true latest schema
revision in range (e.g. `MaxRoiPool` at model opset 22, `Pad` at 9/17/26).
-Operator dispatch and the unsupported-op pre-scan key on **`op_type` only** (standard
-`ai.onnx` domain). Custom-domain ops are not supported yet; adding them requires
-domain-aware handler registration and matching pre-scan logic in `convert.rs`.
+Operator dispatch and the unsupported-op pre-scan key on **`op_type` only**, not on the ONNX
+domain. This currently permits selected `com.microsoft` fused operators such as MoE/QMoE, but
+it is not general custom-domain support and cannot distinguish same-named operators across
+domains. Proper domain-aware support requires matching registry and pre-scan changes.
## Layout (committed)
@@ -147,7 +151,8 @@ history and tests the highest buildable opset in each band — e.g. `Pad` at ops
(attribute `pads` vs input `pads` vs optional `axes` input). Ops with unchanged structure get a
single test at the newest opset. Unbuildable ops get an `#[ignore]` stub.
-See [docs/operator-conversion-plan.md](docs/operator-conversion-plan.md) for the full operator rollout workflow.
+See [docs/operator-conversion-status.md](docs/operator-conversion-status.md) for the complete
+per-operator capability matrix.
## Dependencies (`Cargo.toml`)
diff --git a/Cargo.lock b/Cargo.lock
index 59f099f..1d16dab 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -82,6 +82,15 @@ version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
+[[package]]
+name = "approx"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
+dependencies = [
+ "num-traits",
+]
+
[[package]]
name = "aquamarine"
version = "0.1.12"
@@ -95,6 +104,29 @@ dependencies = [
"syn 1.0.109",
]
+[[package]]
+name = "arrayvec"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
+
+[[package]]
+name = "async-trait"
+version = "0.1.92"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.4",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
[[package]]
name = "atty"
version = "0.2.14"
@@ -133,7 +165,7 @@ dependencies = [
"bitflags",
"cexpr",
"clang-sys",
- "itertools 0.12.1",
+ "itertools 0.10.5",
"log",
"proc-macro2",
"quote",
@@ -216,12 +248,41 @@ dependencies = [
"thiserror 1.0.69",
]
+[[package]]
+name = "aws-lc-rs"
+version = "1.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b281d307588d634de920874890732659e2e7672f72b5e10e81badc1a8a83621e"
+dependencies = [
+ "aws-lc-sys",
+ "zeroize",
+]
+
+[[package]]
+name = "aws-lc-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bff6c3b54fad79a2e60b8102caf565819711497c1f5f092f49508e2f5c31b27"
+dependencies = [
+ "cc",
+ "cmake",
+ "dunce",
+ "fs_extra",
+ "pkg-config",
+]
+
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+[[package]]
+name = "base64"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
+
[[package]]
name = "base64ct"
version = "1.8.3"
@@ -237,7 +298,7 @@ dependencies = [
"bitflags",
"cexpr",
"clang-sys",
- "itertools 0.12.1",
+ "itertools 0.10.5",
"log",
"prettyplease",
"proc-macro2",
@@ -254,6 +315,19 @@ version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
+[[package]]
+name = "blake3"
+version = "1.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae"
+dependencies = [
+ "arrayvec",
+ "cc",
+ "cfg-if",
+ "constant_time_eq",
+ "cpufeatures 0.3.1",
+]
+
[[package]]
name = "block"
version = "0.1.6"
@@ -269,6 +343,15 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "block-buffer"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
+dependencies = [
+ "hybrid-array",
+]
+
[[package]]
name = "bs58"
version = "0.5.1"
@@ -278,6 +361,17 @@ dependencies = [
"tinyvec",
]
+[[package]]
+name = "bstr"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
+dependencies = [
+ "memchr",
+ "regex-automata",
+ "serde_core",
+]
+
[[package]]
name = "bumpalo"
version = "3.20.3"
@@ -329,6 +423,23 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+[[package]]
+name = "cfg_aliases"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
+
+[[package]]
+name = "chacha20"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.1",
+ "rand_core",
+]
+
[[package]]
name = "chrono"
version = "0.4.44"
@@ -336,8 +447,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [
"iana-time-zone",
+ "js-sys",
"num-traits",
"serde",
+ "wasm-bindgen",
"windows-link",
]
@@ -392,6 +505,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
+[[package]]
+name = "cmake"
+version = "0.1.58"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
+dependencies = [
+ "cc",
+]
+
[[package]]
name = "codespan-reporting"
version = "0.13.1"
@@ -409,6 +531,62 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
+[[package]]
+name = "colored"
+version = "3.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
+dependencies = [
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "combine"
+version = "4.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
+
+[[package]]
+name = "const-str"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18f12cc9948ed9604230cdddc7c86e270f9401ccbe3c2e98a4378c5e7632212f"
+
+[[package]]
+name = "const_panic"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9603f79528ece8163c496f8932121cb36cfe46259e9c907bb3d8205139d7caa3"
+dependencies = [
+ "typewit",
+]
+
+[[package]]
+name = "constant_time_eq"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
[[package]]
name = "core-foundation"
version = "0.10.1"
@@ -425,6 +603,21 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+[[package]]
+name = "core_detect"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"
+
+[[package]]
+name = "countio"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9702aee5d1d744c01d82f6915644f950f898e014903385464c773b96fefdecb"
+dependencies = [
+ "futures-io",
+]
+
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -434,6 +627,39 @@ dependencies = [
"libc",
]
+[[package]]
+name = "cpufeatures"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
+
[[package]]
name = "crunchy"
version = "0.2.4"
@@ -450,6 +676,25 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "crypto-common"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "ctor"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d"
+dependencies = [
+ "link-section",
+ "linktime-proc-macro",
+]
+
[[package]]
name = "cudarc"
version = "0.11.9"
@@ -603,8 +848,19 @@ version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
- "block-buffer",
- "crypto-common",
+ "block-buffer 0.10.4",
+ "crypto-common 0.1.7",
+]
+
+[[package]]
+name = "digest"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
+dependencies = [
+ "block-buffer 0.12.1",
+ "const-oid",
+ "crypto-common 0.2.2",
]
[[package]]
@@ -639,6 +895,12 @@ dependencies = [
"syn 2.0.117",
]
+[[package]]
+name = "dunce"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
+
[[package]]
name = "dyn-clone"
version = "1.0.20"
@@ -651,6 +913,21 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
+[[package]]
+name = "encoding_rs"
+version = "0.8.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5ef0006ac9ab233c38522f5ae99cae3625151de8f706cacee1cba4b8e2832a"
+dependencies = [
+ "cfg-if",
+ "core_detect",
+ "multiversion",
+ "multiversion_no_op",
+ "rustversion",
+ "scopeguard",
+ "simdutf8",
+]
+
[[package]]
name = "env_logger"
version = "0.9.3"
@@ -690,7 +967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -711,6 +988,12 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
[[package]]
name = "foldhash"
version = "0.1.5"
@@ -747,12 +1030,88 @@ dependencies = [
"percent-encoding",
]
+[[package]]
+name = "fs4"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4"
+dependencies = [
+ "rustix",
+ "tokio",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "fs_extra"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
+
+[[package]]
+name = "futures"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
[[package]]
name = "futures-core"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
+[[package]]
+name = "futures-executor"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
+
[[package]]
name = "futures-task"
version = "0.3.32"
@@ -765,12 +1124,23 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
+ "futures-channel",
"futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
"futures-task",
+ "memchr",
"pin-project-lite",
"slab",
]
+[[package]]
+name = "gearhash"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "616e8f476a586f1b078d9eece21f7a071f27997e709e2b471f5697deea53bda9"
+
[[package]]
name = "generic-array"
version = "0.14.7"
@@ -788,8 +1158,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
+ "js-sys",
"libc",
- "wasi",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+ "wasm-bindgen",
]
[[package]]
@@ -799,21 +1171,76 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [
"cfg-if",
+ "js-sys",
"libc",
"r-efi",
+ "rand_core",
"wasip2",
"wasip3",
+ "wasm-bindgen",
]
[[package]]
-name = "glob"
-version = "0.3.4"
+name = "git-version"
+version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
+checksum = "1ad568aa3db0fcbc81f2f116137f263d7304f512a1209b35b85150d3ef88ad19"
+dependencies = [
+ "git-version-macro",
+]
[[package]]
-name = "half"
-version = "2.7.1"
+name = "git-version-macro"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
+
+[[package]]
+name = "globset"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd"
+dependencies = [
+ "aho-corasick",
+ "bstr",
+ "log",
+ "regex-automata",
+ "regex-syntax 0.8.10",
+]
+
+[[package]]
+name = "h2"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "http",
+ "indexmap 2.14.0",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "half"
+version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
dependencies = [
@@ -856,6 +1283,12 @@ version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+[[package]]
+name = "heapify"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0049b265b7f201ca9ab25475b22b47fe444060126a51abe00f77d986fc5cc52e"
+
[[package]]
name = "heck"
version = "0.5.0"
@@ -883,6 +1316,31 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+[[package]]
+name = "hf-xet"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c237ef4fb0ce1962a5117f8bd8c74454b41629826a9df17d14a1840ca18f0754"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "bytes",
+ "http",
+ "more-asserts",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.18",
+ "tokio",
+ "tokio-util",
+ "tokio_with_wasm",
+ "tracing",
+ "uuid",
+ "xet-client",
+ "xet-core-structures",
+ "xet-data",
+ "xet-runtime",
+]
+
[[package]]
name = "hmac-sha256"
version = "1.1.14"
@@ -899,18 +1357,138 @@ dependencies = [
"itoa",
]
+[[package]]
+name = "http-body"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+[[package]]
+name = "huggingface-hub"
+version = "0.0.1"
+source = "git+https://github.com/huggingface/huggingface_hub_rust.git?rev=20acfd61a767dcf7d6a0cb7f4a02ac8406fd8a14#20acfd61a767dcf7d6a0cb7f4a02ac8406fd8a14"
+dependencies = [
+ "async-trait",
+ "base64 0.22.1",
+ "bytes",
+ "fs4",
+ "futures",
+ "globset",
+ "hf-xet",
+ "pathdiff",
+ "reqwest",
+ "reqwest-middleware",
+ "reqwest-retry",
+ "serde",
+ "serde_json",
+ "sha2 0.10.9",
+ "thiserror 2.0.18",
+ "tokio",
+ "tokio-util",
+ "tracing",
+ "typed-builder",
+ "url",
+]
+
[[package]]
name = "humantime"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424"
+[[package]]
+name = "hybrid-array"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
+dependencies = [
+ "typenum",
+]
+
+[[package]]
+name = "hyper"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.27.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53"
+dependencies = [
+ "http",
+ "hyper",
+ "hyper-util",
+ "rustls",
+ "tokio",
+ "tokio-rustls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
+ "pin-project-lite",
+ "socket2",
+ "system-configuration",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "windows-registry",
+]
+
[[package]]
name = "iana-time-zone"
version = "0.1.65"
@@ -1079,6 +1657,12 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306"
+[[package]]
+name = "ipnet"
+version = "2.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0"
+
[[package]]
name = "is-terminal"
version = "0.4.17"
@@ -1087,7 +1671,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
dependencies = [
"hermit-abi 0.5.2",
"libc",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -1116,9 +1700,9 @@ dependencies = [
[[package]]
name = "itertools"
-version = "0.12.1"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
dependencies = [
"either",
]
@@ -1129,6 +1713,55 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+[[package]]
+name = "jni"
+version = "0.22.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
+dependencies = [
+ "cfg-if",
+ "combine",
+ "jni-macros",
+ "jni-sys",
+ "log",
+ "simd_cesu8",
+ "thiserror 2.0.18",
+ "walkdir",
+ "windows-link",
+]
+
+[[package]]
+name = "jni-macros"
+version = "0.22.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "simd_cesu8",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
+dependencies = [
+ "jni-sys-macros",
+]
+
+[[package]]
+name = "jni-sys-macros"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
+dependencies = [
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "jobserver"
version = "0.1.35"
@@ -1151,6 +1784,29 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "konst"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f660d5f887e3562f9ab6f4a14988795b694099d66b4f5dedc02d197ba9becb1d"
+dependencies = [
+ "const_panic",
+ "konst_proc_macros",
+ "typewit",
+]
+
+[[package]]
+name = "konst_proc_macros"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e037a2e1d8d5fdbd49b16a4ea09d5d6401c1f29eca5ff29d03d3824dba16256a"
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
[[package]]
name = "leb128fmt"
version = "0.1.0"
@@ -1201,6 +1857,18 @@ dependencies = [
"cc",
]
+[[package]]
+name = "link-section"
+version = "0.19.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39c29a617ce3df32c08497bdc1ab6e2376e0b17948ac166a2fbe5977c5954cd9"
+
+[[package]]
+name = "linktime-proc-macro"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e57c38c1e860fd37c604281cdfb1dd2216977fd76a50f85ba2f388ef3219616"
+
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
@@ -1213,12 +1881,36 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
[[package]]
name = "log"
version = "0.4.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f"
+[[package]]
+name = "lru-slab"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f"
+
+[[package]]
+name = "lz4_flex"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef0d4ed8669f8f8826eb00dc878084aa8f253506c4fd5e8f58f5bce72ddb97e"
+dependencies = [
+ "twox-hash",
+]
+
[[package]]
name = "lzma-rust2"
version = "0.15.8"
@@ -1234,6 +1926,15 @@ dependencies = [
"libc",
]
+[[package]]
+name = "matchers"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
+dependencies = [
+ "regex-automata",
+]
+
[[package]]
name = "matrixmultiply"
version = "0.3.10"
@@ -1273,12 +1974,45 @@ dependencies = [
"syn 2.0.117",
]
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "mime_guess"
+version = "2.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
+dependencies = [
+ "mime",
+ "unicase",
+]
+
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
+[[package]]
+name = "mio"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
+dependencies = [
+ "libc",
+ "wasi 0.11.1+wasi-snapshot-preview1",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "more-asserts"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e"
+
[[package]]
name = "moveit"
version = "0.6.0"
@@ -1294,6 +2028,33 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
+[[package]]
+name = "multiversion"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ca4bea16ffc3f443cf7d866912118196bfef4c6a1556ca00f9f9b00bb43f7c"
+dependencies = [
+ "multiversion-macros",
+]
+
+[[package]]
+name = "multiversion-macros"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d416831a7317ef4b08bee00b69cbbb9c8763da7959a7026244d6266869f9c83"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn 3.0.4",
+]
+
+[[package]]
+name = "multiversion_no_op"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d"
+
[[package]]
name = "native-tls"
version = "0.2.18"
@@ -1336,6 +2097,24 @@ dependencies = [
"minimal-lexical",
]
+[[package]]
+name = "ntapi"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.50.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "num-complex"
version = "0.4.6"
@@ -1380,17 +2159,45 @@ dependencies = [
]
[[package]]
-name = "objc_exception"
-version = "0.1.2"
+name = "objc2-core-foundation"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
+checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
- "cc",
+ "bitflags",
]
[[package]]
-name = "once_cell"
-version = "1.21.4"
+name = "objc2-io-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15"
+dependencies = [
+ "libc",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-system-configuration"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7216bd11cbda54ccabcab84d523dc93b858ec75ecfb3a7d89513fa22464da396"
+dependencies = [
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc_exception"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
@@ -1400,6 +2207,12 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
+[[package]]
+name = "oneshot"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "269bca4c2591a28585d6bf10d9ed0332b7d76900a1b02bec41bdc3a2cdcda107"
+
[[package]]
name = "onnx2webnn"
version = "0.1.0"
@@ -1407,12 +2220,15 @@ dependencies = [
"anyhow",
"bytemuck",
"clap",
+ "dirs",
"half",
+ "huggingface-hub",
"pretty_env_logger",
"prost",
"rustnn",
"serde",
"serde_json",
+ "sha2 0.10.9",
"tempfile",
"thiserror 1.0.69",
"ureq 2.12.1",
@@ -1494,6 +2310,44 @@ dependencies = [
"ureq 3.3.0",
]
+[[package]]
+name = "os_str_bytes"
+version = "6.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link",
+]
+
+[[package]]
+name = "pathdiff"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
+
[[package]]
name = "pem-rfc7468"
version = "1.0.0"
@@ -1549,7 +2403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220"
dependencies = [
"pest",
- "sha2",
+ "sha2 0.10.9",
]
[[package]]
@@ -1562,12 +2416,38 @@ dependencies = [
"indexmap 2.14.0",
]
+[[package]]
+name = "pin-project"
+version = "1.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
[[package]]
name = "pkg-config"
version = "0.3.33"
@@ -1675,7 +2555,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
dependencies = [
"bytes",
"heck",
- "itertools 0.12.1",
+ "itertools 0.10.5",
"log",
"multimap",
"once_cell",
@@ -1683,6 +2563,8 @@ dependencies = [
"prettyplease",
"prost",
"prost-types",
+ "pulldown-cmark",
+ "pulldown-cmark-to-cmark",
"regex",
"syn 2.0.117",
"tempfile",
@@ -1695,7 +2577,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
dependencies = [
"anyhow",
- "itertools 0.12.1",
+ "itertools 0.10.5",
"proc-macro2",
"quote",
"syn 2.0.117",
@@ -1710,6 +2592,83 @@ dependencies = [
"prost",
]
+[[package]]
+name = "pulldown-cmark"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b"
+dependencies = [
+ "bitflags",
+ "memchr",
+ "unicase",
+]
+
+[[package]]
+name = "pulldown-cmark-to-cmark"
+version = "10.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0194e6e1966c23cc5fd988714f85b18d548d773e81965413555d96569931833d"
+dependencies = [
+ "pulldown-cmark",
+]
+
+[[package]]
+name = "quinn"
+version = "0.11.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11"
+dependencies = [
+ "bytes",
+ "cfg_aliases",
+ "pin-project-lite",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls",
+ "socket2",
+ "thiserror 2.0.18",
+ "tokio",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9746dbde176634f4f2f1faf2404e30a31b2bc1e9cafb5329c95d8177a18c9fc"
+dependencies = [
+ "aws-lc-rs",
+ "bytes",
+ "getrandom 0.4.2",
+ "lru-slab",
+ "rand",
+ "rand_pcg",
+ "ring",
+ "rustc-hash",
+ "rustls",
+ "rustls-pki-types",
+ "slab",
+ "thiserror 2.0.18",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-udp"
+version = "0.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
+dependencies = [
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2",
+ "tracing",
+ "windows-sys 0.52.0",
+]
+
[[package]]
name = "quote"
version = "1.0.45"
@@ -1725,12 +2684,56 @@ version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+[[package]]
+name = "rand"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af"
+dependencies = [
+ "chacha20",
+ "getrandom 0.4.2",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
+
+[[package]]
+name = "rand_pcg"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
+dependencies = [
+ "rand_core",
+]
+
[[package]]
name = "rawpointer"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
+[[package]]
+name = "redb"
+version = "3.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ba239c1c1693315d3cc0e601db3b3965543afbf48c41730fdca2f069f510f4a"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags",
+]
+
[[package]]
name = "redox_users"
version = "0.5.2"
@@ -1831,6 +2834,96 @@ dependencies = [
"syn 1.0.109",
]
+[[package]]
+name = "reqwest"
+version = "0.13.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029"
+dependencies = [
+ "base64 0.23.1",
+ "bytes",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-rustls",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "mime",
+ "mime_guess",
+ "percent-encoding",
+ "pin-project-lite",
+ "quinn",
+ "rustls",
+ "rustls-pki-types",
+ "rustls-platform-verifier",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-rustls",
+ "tokio-util",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-streams",
+ "web-sys",
+]
+
+[[package]]
+name = "reqwest-middleware"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07bc3f1384cffa4f274dad2d4ddd73aed32fed8f786d96c6be8aa4e5fd3c3b58"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "http",
+ "reqwest",
+ "serde",
+ "thiserror 2.0.18",
+ "tower-service",
+]
+
+[[package]]
+name = "reqwest-retry"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe2412db2af7d2268e7a5406be0431f37d9eb67ff390f35b395716f5f06c2eaa"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "futures",
+ "getrandom 0.2.17",
+ "http",
+ "hyper",
+ "reqwest",
+ "reqwest-middleware",
+ "retry-policies",
+ "thiserror 2.0.18",
+ "tokio",
+ "tracing",
+ "wasmtimer",
+]
+
+[[package]]
+name = "retry-policies"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc05fbf560421a0357a750cbe78c7ca19d4923918490daabba313d5dbc871e47"
+dependencies = [
+ "rand",
+]
+
[[package]]
name = "ring"
version = "0.17.14"
@@ -1851,6 +2944,15 @@ version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
[[package]]
name = "rustix"
version = "1.1.4"
@@ -1861,7 +2963,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -1870,6 +2972,7 @@ version = "0.23.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
dependencies = [
+ "aws-lc-rs",
"log",
"once_cell",
"ring",
@@ -1879,21 +2982,62 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "rustls-native-certs"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d"
+dependencies = [
+ "openssl-probe",
+ "rustls-pki-types",
+ "schannel",
+ "security-framework",
+]
+
[[package]]
name = "rustls-pki-types"
version = "1.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
dependencies = [
+ "web-time",
"zeroize",
]
+[[package]]
+name = "rustls-platform-verifier"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
+dependencies = [
+ "core-foundation 0.10.1",
+ "core-foundation-sys",
+ "jni",
+ "log",
+ "once_cell",
+ "rustls",
+ "rustls-native-certs",
+ "rustls-platform-verifier-android",
+ "rustls-webpki",
+ "security-framework",
+ "security-framework-sys",
+ "webpki-root-certs",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rustls-platform-verifier-android"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
+
[[package]]
name = "rustls-webpki"
version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
+ "aws-lc-rs",
"ring",
"rustls-pki-types",
"untrusted",
@@ -1903,7 +3047,7 @@ dependencies = [
name = "rustnn"
version = "0.5.11"
dependencies = [
- "base64",
+ "base64 0.22.1",
"block",
"bytemuck",
"bytes",
@@ -1921,7 +3065,6 @@ dependencies = [
"prost-build",
"prost-types",
"regex",
- "safetensors",
"seahash",
"serde",
"serde_json",
@@ -1940,6 +3083,18 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
+[[package]]
+name = "ryu"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
+
+[[package]]
+name = "safe-transmute"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3944826ff8fa8093089aba3acb4ef44b9446a99a16f3bf4e74af3f77d340ab7d"
+
[[package]]
name = "safetensors"
version = "0.7.0"
@@ -1951,6 +3106,15 @@ dependencies = [
"serde_json",
]
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
[[package]]
name = "schannel"
version = "0.1.29"
@@ -1984,6 +3148,12 @@ dependencies = [
"serde_json",
]
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
[[package]]
name = "scratch"
version = "1.0.9"
@@ -2003,7 +3173,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
"bitflags",
- "core-foundation",
+ "core-foundation 0.10.1",
"core-foundation-sys",
"libc",
"security-framework-sys",
@@ -2068,13 +3238,36 @@ dependencies = [
"zmij",
]
+[[package]]
+name = "serde_repr"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.4",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
[[package]]
name = "serde_with"
version = "3.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e72c1c2cb7b223fafb600a619537a871c2818583d619401b785e7c0b746ccde2"
dependencies = [
- "base64",
+ "base64 0.22.1",
"bs58",
"chrono",
"hex",
@@ -2107,8 +3300,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
- "cpufeatures",
- "digest",
+ "cpufeatures 0.2.17",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "sha2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.1",
+ "digest 0.11.3",
+]
+
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
+[[package]]
+name = "shellexpand"
+version = "3.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8"
+dependencies = [
+ "bstr",
+ "dirs",
+ "os_str_bytes",
]
[[package]]
@@ -2123,6 +3347,22 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+[[package]]
+name = "simd_cesu8"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520"
+dependencies = [
+ "rustc_version",
+ "simdutf8",
+]
+
+[[package]]
+name = "simdutf8"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
+
[[package]]
name = "slab"
version = "0.4.12"
@@ -2135,6 +3375,16 @@ version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
+[[package]]
+name = "socket2"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "socks"
version = "0.3.4"
@@ -2152,6 +3402,22 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "statrs"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e"
+dependencies = [
+ "approx",
+ "num-traits",
+]
+
[[package]]
name = "strsim"
version = "0.11.1"
@@ -2164,6 +3430,12 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+[[package]]
+name = "symlink"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a"
+
[[package]]
name = "syn"
version = "1.0.109"
@@ -2197,6 +3469,15 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
[[package]]
name = "synstructure"
version = "0.13.2"
@@ -2208,6 +3489,41 @@ dependencies = [
"syn 2.0.117",
]
+[[package]]
+name = "sysinfo"
+version = "0.38.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f"
+dependencies = [
+ "libc",
+ "memchr",
+ "ntapi",
+ "objc2-core-foundation",
+ "objc2-io-kit",
+ "windows",
+]
+
+[[package]]
+name = "system-configuration"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
+dependencies = [
+ "bitflags",
+ "core-foundation 0.9.4",
+ "system-configuration-sys",
+]
+
+[[package]]
+name = "system-configuration-sys"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
[[package]]
name = "tempfile"
version = "3.27.0"
@@ -2218,7 +3534,7 @@ dependencies = [
"getrandom 0.4.2",
"once_cell",
"rustix",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -2270,6 +3586,15 @@ dependencies = [
"syn 2.0.117",
]
+[[package]]
+name = "thread_local"
+version = "1.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070"
+dependencies = [
+ "cfg-if",
+]
+
[[package]]
name = "time"
version = "0.3.47"
@@ -2315,34 +3640,232 @@ dependencies = [
name = "tinyvec"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
+checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
+dependencies = [
+ "tinyvec_macros",
+]
+
+[[package]]
+name = "tinyvec_macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+
+[[package]]
+name = "tokio"
+version = "1.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.4",
+]
+
+[[package]]
+name = "tokio-retry"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a129d95275ebf4c493ec53bf0f8cd95f5ac161bc4f381700809a54f595d4470"
+dependencies = [
+ "pin-project-lite",
+ "rand",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "libc",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "tokio_with_wasm"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34e40fbbbd95441133fe9483f522db15dbfd26dc636164ebd8f2dd28759a6aa6"
+dependencies = [
+ "js-sys",
+ "tokio",
+ "tokio_with_wasm_proc",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "tokio_with_wasm_proc"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d01145a2c788d6aae4cd653afec1e8332534d7d783d01897cefcafe4428de992"
+dependencies = [
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "tower"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
+dependencies = [
+ "bitflags",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "url",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-appender"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c"
+dependencies = [
+ "crossbeam-channel",
+ "symlink",
+ "thiserror 2.0.18",
+ "time",
+ "tracing-subscriber",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
- "tinyvec_macros",
+ "once_cell",
+ "valuable",
]
[[package]]
-name = "tinyvec_macros"
-version = "0.1.1"
+name = "tracing-log"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
[[package]]
-name = "tracing"
-version = "0.1.44"
+name = "tracing-serde"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1"
dependencies = [
- "pin-project-lite",
+ "serde",
"tracing-core",
]
[[package]]
-name = "tracing-core"
-version = "0.1.36"
+name = "tracing-subscriber"
+version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
dependencies = [
+ "matchers",
+ "nu-ansi-term",
"once_cell",
+ "regex-automata",
+ "serde",
+ "serde_json",
+ "sharded-slab",
+ "smallvec",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+ "tracing-serde",
]
[[package]]
@@ -2376,18 +3899,62 @@ dependencies = [
"regex",
]
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "twox-hash"
+version = "2.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5283634e518fe9e82c7b20520bb4bc209009fd16c82077c802f8111ecbb0117a"
+
+[[package]]
+name = "typed-builder"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd9d30e3a08026c78f246b173243cf07b3696d274debd26680773b6773c2afc7"
+dependencies = [
+ "typed-builder-macro",
+]
+
+[[package]]
+name = "typed-builder-macro"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c36781cc0e46a83726d9879608e4cf6c2505237e263a8eb8c24502989cfdb28"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.117",
+]
+
[[package]]
name = "typenum"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
+[[package]]
+name = "typewit"
+version = "1.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "214ca0b2191785cbc06209b9ca1861e048e39b5ba33574b3cedd58363d5bb5f6"
+
[[package]]
name = "ucd-trie"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
+[[package]]
+name = "unicase"
+version = "2.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
+
[[package]]
name = "unicode-ident"
version = "1.0.24"
@@ -2424,7 +3991,7 @@ version = "2.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
dependencies = [
- "base64",
+ "base64 0.22.1",
"log",
"once_cell",
"rustls",
@@ -2439,7 +4006,7 @@ version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0"
dependencies = [
- "base64",
+ "base64 0.22.1",
"der",
"log",
"native-tls",
@@ -2457,7 +4024,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c"
dependencies = [
- "base64",
+ "base64 0.22.1",
"http",
"httparse",
"log",
@@ -2475,6 +4042,12 @@ dependencies = [
"serde",
]
+[[package]]
+name = "urlencoding"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
+
[[package]]
name = "utf8-zero"
version = "0.8.1"
@@ -2493,6 +4066,23 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+[[package]]
+name = "uuid"
+version = "1.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce"
+dependencies = [
+ "getrandom 0.4.2",
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "valuable"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+
[[package]]
name = "vcpkg"
version = "0.2.15"
@@ -2505,12 +4095,40 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+[[package]]
+name = "wasi"
+version = "0.14.7+wasi-0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
+dependencies = [
+ "wasip2",
+]
+
[[package]]
name = "wasip2"
version = "1.0.3+wasi-0.2.9"
@@ -2529,6 +4147,15 @@ dependencies = [
"wit-bindgen 0.51.0",
]
+[[package]]
+name = "wasite"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66fe902b4a6b8028a753d5424909b764ccf79b7a209eac9bf97e59cda9f71a42"
+dependencies = [
+ "wasi 0.14.7+wasi-0.2.4",
+]
+
[[package]]
name = "wasm-bindgen"
version = "0.2.122"
@@ -2542,6 +4169,16 @@ dependencies = [
"wasm-bindgen-shared",
]
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.72"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.122"
@@ -2596,6 +4233,19 @@ dependencies = [
"wasmparser",
]
+[[package]]
+name = "wasm-streams"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb"
+dependencies = [
+ "futures-util",
+ "js-sys",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
[[package]]
name = "wasmparser"
version = "0.244.0"
@@ -2608,13 +4258,47 @@ dependencies = [
"semver",
]
+[[package]]
+name = "wasmtimer"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b"
+dependencies = [
+ "futures",
+ "js-sys",
+ "parking_lot",
+ "pin-utils",
+ "slab",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.99"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
[[package]]
name = "webnn-graph"
version = "0.3.0"
-source = "git+https://github.com/rustnn/webnn-graph?branch=main#075e3c2f8b78d67f5b0ffbc51d6bc81162397d43"
+source = "git+https://github.com/rustnn/webnn-graph?branch=main#0bcd517d30cf740f55bf19386be254e37010ab2b"
dependencies = [
"anyhow",
- "base64",
+ "base64 0.22.1",
"clap",
"half",
"pest",
@@ -2669,6 +4353,19 @@ dependencies = [
"rustls-pki-types",
]
+[[package]]
+name = "whoami"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c"
+dependencies = [
+ "libc",
+ "libredox",
+ "objc2-system-configuration",
+ "wasite",
+ "web-sys",
+]
+
[[package]]
name = "winapi"
version = "0.3.9"
@@ -2691,7 +4388,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -2700,6 +4397,27 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+[[package]]
+name = "windows"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
+dependencies = [
+ "windows-collections",
+ "windows-core",
+ "windows-future",
+ "windows-numerics",
+]
+
+[[package]]
+name = "windows-collections"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
+dependencies = [
+ "windows-core",
+]
+
[[package]]
name = "windows-core"
version = "0.62.2"
@@ -2713,6 +4431,17 @@ dependencies = [
"windows-strings",
]
+[[package]]
+name = "windows-future"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
+dependencies = [
+ "windows-core",
+ "windows-link",
+ "windows-threading",
+]
+
[[package]]
name = "windows-implement"
version = "0.60.2"
@@ -2741,6 +4470,27 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+[[package]]
+name = "windows-numerics"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
+dependencies = [
+ "windows-core",
+ "windows-link",
+]
+
+[[package]]
+name = "windows-registry"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
+dependencies = [
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
[[package]]
name = "windows-result"
version = "0.4.1"
@@ -2768,6 +4518,15 @@ dependencies = [
"windows-targets",
]
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
[[package]]
name = "windows-sys"
version = "0.61.2"
@@ -2793,6 +4552,15 @@ dependencies = [
"windows_x86_64_msvc",
]
+[[package]]
+name = "windows-threading"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
+dependencies = [
+ "windows-link",
+]
+
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
@@ -2941,6 +4709,147 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
+[[package]]
+name = "xet-client"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3b8da8cc70aa2e3c500c0400e012df82c656ab9fca47f9f939fffc5afd89aca"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "base64 0.22.1",
+ "bytes",
+ "crc32fast",
+ "futures",
+ "http",
+ "hyper",
+ "more-asserts",
+ "rand",
+ "redb",
+ "reqwest",
+ "reqwest-middleware",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "statrs",
+ "tempfile",
+ "thiserror 2.0.18",
+ "tokio",
+ "tokio-retry",
+ "tokio_with_wasm",
+ "tracing",
+ "url",
+ "urlencoding",
+ "web-time",
+ "xet-core-structures",
+ "xet-runtime",
+]
+
+[[package]]
+name = "xet-core-structures"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73503c223783dccc864abde22115e09d12f190448a0baf58ab2c54bc709e2f99"
+dependencies = [
+ "async-trait",
+ "base64 0.22.1",
+ "blake3",
+ "bytemuck",
+ "bytes",
+ "countio",
+ "futures",
+ "futures-util",
+ "getrandom 0.4.2",
+ "heapify",
+ "itertools 0.14.0",
+ "lz4_flex",
+ "more-asserts",
+ "rand",
+ "regex",
+ "safe-transmute",
+ "serde",
+ "static_assertions",
+ "thiserror 2.0.18",
+ "tokio",
+ "tokio-util",
+ "tracing",
+ "uuid",
+ "web-time",
+ "xet-runtime",
+]
+
+[[package]]
+name = "xet-data"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c89052ec5dec2187cad30b86af92cc24fd61c4a57a795f1ff7ff5f38d49184eb"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "bytes",
+ "chrono",
+ "gearhash",
+ "http",
+ "itertools 0.14.0",
+ "more-asserts",
+ "rand",
+ "serde",
+ "serde_json",
+ "sha2 0.11.0",
+ "tempfile",
+ "thiserror 2.0.18",
+ "tokio",
+ "tokio-util",
+ "tokio_with_wasm",
+ "tracing",
+ "url",
+ "uuid",
+ "web-time",
+ "xet-client",
+ "xet-core-structures",
+ "xet-runtime",
+]
+
+[[package]]
+name = "xet-runtime"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af5c60d5eed38ab4c576f4421bae835e7bd07631fb381705605529d2015c106b"
+dependencies = [
+ "anyhow",
+ "async-trait",
+ "bytes",
+ "chrono",
+ "colored",
+ "const-str",
+ "ctor",
+ "dirs",
+ "futures",
+ "git-version",
+ "humantime",
+ "konst",
+ "libc",
+ "more-asserts",
+ "oneshot",
+ "pin-project",
+ "rand",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "shellexpand",
+ "sysinfo",
+ "thiserror 2.0.18",
+ "tokio",
+ "tokio-util",
+ "tokio_with_wasm",
+ "tracing",
+ "tracing-appender",
+ "tracing-subscriber",
+ "web-time",
+ "whoami",
+ "winapi",
+]
+
[[package]]
name = "yoke"
version = "0.8.2"
diff --git a/Cargo.toml b/Cargo.toml
index 1237077..ead083a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,17 +24,20 @@ trtx = ["rustnn/trtx-runtime"]
[dependencies]
anyhow = "1.0"
clap = { version = "4.5", features = ["derive"] }
+dirs = "6.0"
half = "2.7.1"
prost = "0.12"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
-thiserror = "1.0"
+thiserror = "1.0"
+ureq = { version = "2.10", default-features = false, features = ["tls"] }
+sha2 = "0.10"
rustnn = { path = "../rustnn", default-features = false, features = ["onnx-runtime"] }
bytemuck = "1.15"
webnn-onnx-utils = { git = "https://github.com/rustnn/webnn-onnx-utils", branch = "main" }
+huggingface-hub = { git = "https://github.com/huggingface/huggingface_hub_rust.git", rev = "20acfd61a767dcf7d6a0cb7f4a02ac8406fd8a14", version = "0.0.1", features = ["blocking", "xet"] }
[dev-dependencies]
-pretty_env_logger = "0.5"
+pretty_env_logger = "0.5"
tempfile = "3.8"
-ureq = { version = "2.10", default-features = false, features = ["tls"] }
diff --git a/README.md b/README.md
index 01688ec..8ca4ce6 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,51 @@
# onnx2webnn
+> **Last edited:** `2026-09-22T21:07:28Z`
+> **Checkout:** `fkrall/cache-backed-validation` at `a582b89`
+>
+> **Freshness:** Use this document only when this provenance is recent relative to the relevant
+> code and commits; otherwise verify the implementation, tests, and Git history before relying
+> on it.
+
ONNX → WebNN lowering crate extracted from [webnn-graph](../webnn-graph). ONNX operators lower
directly to [rustnn](../rustnn) `MLGraphBuilder`; full-graph validation runs via ORT CPU
-`build()` (`onnx-runtime` feature). There is no JSON IR and no on-disk graph export — success
-means `builder.build()` returns `Ok(MLGraph)`.
+`build()` (`onnx-runtime` feature). There is no intermediate JSON IR. Without `--output`,
+success means `builder.build()` returns `Ok(MLGraph)`; `--output` additionally writes a
+reloadable `.webnn` graph and sibling Safetensors archive.
+
+Decomposed attention is not promised to be bit-identical to a source backend fused-attention
+kernel. This matters for quantized models that immediately feed attention into
+`DynamicQuantizeLinear`: small legal floating-point differences can cross Uint8 bucket boundaries
+and compound through later layers. FastVLM prefill has this limitation; its fixed sequence-1 decode
+specialization passes numerical validation.
Supported ONNX opset range: **1–26** (see `MIN_SUPPORTED_OPSET` / `MAX_SUPPORTED_OPSET` in
`src/onnx/convert.rs`).
## Build
+### Prerequisites
+
+- A stable Rust toolchain.
+- The Protocol Buffers compiler (`protoc`). On Debian/Ubuntu install
+ `protobuf-compiler`; on macOS install `protobuf` with Homebrew. Cargo uses it
+ while building the ONNX and CoreML protobuf bindings.
+- A sibling `rustnn` checkout at `../rustnn`. This is the path dependency used
+ by `Cargo.toml` while the stacked RustNN changes are under review.
+- ONNX Runtime 1.27 or newer when running conversions or tests with the ORT
+ backend. RustNN can download the pinned version into its own ignored
+ `target/onnxruntime` directory:
+
+```powershell
+make -C ../rustnn onnxruntime-download
+```
+
+Set `ORT_DYLIB_PATH` to the downloaded `libonnxruntime` shared library before
+running the CLI or tests. The exact filename is platform-specific; examples
+are `libonnxruntime.so.1.29.0` on Linux, `libonnxruntime.1.29.0.dylib` on macOS,
+and `onnxruntime.dll` on Windows. The CI workflow in `.github/workflows/ci.yml`
+contains the complete cross-platform provisioning sequence.
+
```powershell
cargo build
# or
@@ -36,6 +72,24 @@ cargo run -- convert --input model.onnx `
If `model.dims.json` sits beside the ONNX file and no overrides were passed on the CLI, dimension
bindings are loaded from that sidecar (`freeDimensionOverrides` or a flat JSON object).
+With `--output`, artifacts are overwritten below the operating system's cache directory in
+`onnx2webnn/onnx` and `onnx2webnn/webnn`; add `--validate` to reload the saved pair immediately and
+compare deterministic execution against native ORT. Set `O2W_CACHE_DIR` to relocate both caches,
+or `O2W_ONNX_CACHE` and `O2W_WEBNN_CACHE` to override either cache independently. RustNN stores
+logical Int4/Uint4 constants as their original packed nibble bytes in a
+versioned U8 Safetensors extension while the `.webnn` declaration retains the logical dtype and
+shape. This is a RustNN archive convention, not a native Safetensors 4-bit dtype.
+
+`MatMulNBits` is lowered to `dequantizeLinear` followed by ordinary floating-point `matmul` because
+WebNN has no fused low-bit matmul operation. The dequantized weights and activations therefore use
+the scale dtype (`float16` or `float32`; the validated q4 models use `float32`). Native ORT may use
+a fused packed-weight kernel instead. In particular, `MatMulNBits accuracy_level=4` permits ORT to
+quantize activations to Int8 internally, which the WebNN lowering cannot represent; such models are
+recorded as validation-blocked rather than numerically supported.
+
+`--validate-cached` reuses existing cache artifacts and is exclusive with `--output` and
+`--validate`.
+
Merged decoders (optimum's `decoder_model_merged*.onnx`) branch at runtime on `use_cache_branch`.
WebNN has no runtime `If`, so pin the input and convert each branch separately:
@@ -49,6 +103,12 @@ cargo run -- convert --input decoder_model_merged.onnx --optimize `
Pinned inputs become constants, the chosen `If` branch is inlined, and inputs the branch never
reads (e.g. the KV cache in the prefill branch) and zero-size dummy outputs are dropped.
+Supporting both cache modes therefore produces two independent artifact pairs: one `.webnn` file
+and one Safetensors file for prefill, and another pair for cached decoding. Shared decoder weights
+are not deduplicated between the pairs. Loading both compiled graphs concurrently may duplicate
+those weights in RAM and VRAM; applications can instead keep only the active graph resident and
+manage graph selection and KV-cache handoff themselves.
+
| Flag | Purpose |
|------|---------|
| `--input` | Input `.onnx` path (required) |
@@ -59,6 +119,9 @@ reads (e.g. the KV cache in the prefill branch) and zero-size dummy outputs are
| `--allow-missing-external-data` | Zero-fill external tensors whose data file is absent (weight-stripped skeleton models) |
| `--experimental-dynamic-inputs` | Preserve unresolved symbolic dims as dynamic metadata |
| `--debug` | Verbose conversion logging (global) |
+| `--output` | Overwrite cache-backed `.onnx`, `.webnn`, and Safetensors artifacts |
+| `--validate` | After `--output`, reload and numerically compare against native ORT |
+| `--validate-cached` | Validate existing cache artifacts without reconverting |
On success the CLI prints `✓ ORT graph build succeeded for …`.
@@ -66,12 +129,54 @@ On success the CLI prints `✓ ORT graph build succeeded for …`.
`tests/models/manifest.json` lists the transformers.js exports the converter is expected to handle,
with their dimension overrides and pinned inputs; `tests/model_skeletons.rs` converts each entry and
-builds it in ORT. No weights are downloaded: the test reads each export from the Hugging Face Hub with
+builds it in ORT. This broad manifest is generated by `scripts/generate_manifest.py` and intentionally
+tracks the publisher repositories through their default `main` revisions. It is diagnostic coverage,
+not an immutable CI gate.
+
+No weights are downloaded by the skeleton test: it reads each export from the Hugging Face Hub with
HTTP range requests, keeps the graph and small constants, and points every large initializer at a file
that does not exist, which the converter zero-fills. A 1.4 GB export becomes a ~0.2 MB skeleton for
~10 MB of traffic. Skeletons are kept in `target/model-skeletons` (or `O2W_SKELETON_CACHE`), about
40 MB for the whole manifest, and CI caches that directory keyed on the manifest and scanner source.
+### Numerical-validation CI
+
+Every Linux pull-request job numerically validates the real-weight models in the hand-maintained
+`tests/models/ci-validation.json`; the experimental, non-blocking macOS/CoreML job runs the same
+set. Windows does not run numerical model validation. Each curated entry must be small enough for a
+hosted runner, pass on both ORT and CoreML, and declare an immutable 40-character Hugging Face
+commit `revision` plus the lowercase SHA-256 of its primary ONNX file. Downloads, external-data
+sidecars, skeletons, and cache identities all use that revision. The primary
+model digest is checked after download and whenever a cached model is reused.
+
+Full-model validation downloads through the official Hugging Face Rust client and uses the standard
+`HF_HUB_CACHE`/`HF_HOME` cache by default, allowing Python and Rust tools to reuse the same blobs.
+`O2W_ONNX_CACHE`, followed by `O2W_CACHE_DIR/onnx`, takes precedence when set and is interpreted as
+the Hub cache root. onnx2webnn stores only its completion records in the private
+`.onnx2webnn-validation` directory there; model files retain the standard `blobs` and `snapshots`
+layout. Xet-backed downloads use chunked transfer, while ordinary HTTP downloads are retried but
+are not guaranteed to resume from a partial byte offset.
+
+To add required CI coverage, resolve the model repository to a commit, download the exact ONNX file
+from that revision, compute its SHA-256 (for example with `sha256sum`), and add the file, revision,
+digest, and fixed dimension/input settings to `tests/models/ci-validation.json`. Do not add these
+pins to `tests/models/manifest.json`: regenerating the broad manifest does not read or write the
+curated file. Verify the candidate on Linux and macOS with:
+
+```bash
+target/release/onnx2webnn validate-models \
+ --manifest tests/models/ci-validation.json \
+ --selection all --weights real --jobs 1
+```
+
+The manually dispatched `Full model validation` workflow runs every entry from the generated main
+manifest with real publisher weights. It requires a dedicated runner labeled
+`self-hosted`, `linux`, `x64`, and `onnx2webnn-validation`, a writable
+`/var/cache/onnx2webnn`, network access, a filesystem of at least 100 GiB, and approximately 32 GiB
+of RAM. Downloaded source models persist in that directory; exported WebNN artifacts remain
+temporary. Setup, build, and runner-contract failures fail the workflow, while model failures are
+reported as diagnostic warnings and retained in a full log artifact for 14 days.
+
`O2W_MODELS` selects the source: `hub` (the default when `CI` is set), `dir=` for full local
downloads laid out as `--/onnx/.onnx`, or `strip=` to run local files through
the skeleton scanner. Unset outside CI, the sweep is skipped. `O2W_MODEL_FETCH_JOBS` (default 8) sets
diff --git a/docs/model-validation-status.md b/docs/model-validation-status.md
new file mode 100644
index 0000000..7f8be0e
--- /dev/null
+++ b/docs/model-validation-status.md
@@ -0,0 +1,277 @@
+# Full-model numerical validation status
+
+> **Last edited:** `2026-09-23T11:35:58Z`
+> **Checkout:** `fkrall/cache-backed-validation` at `ac6d5ca`
+>
+> **Freshness:** Use this document only when this provenance is recent relative to the relevant
+> code and commits; otherwise verify the implementation, tests, and Git history before relying
+> on it.
+
+This document tracks the **latest recorded** full-model numerical-validation sweep and a concise
+history of changes that materially altered coverage. Only the latest sweep keeps a complete
+per-case ledger; older ledgers and duplicated historical failure tables are intentionally omitted.
+
+The skeleton sweep establishes broad graph-construction coverage. The manifest contains only model
+selection and execution configuration; numerical status and blocker triage live in this document and
+the linked failure analysis. Numerical validation additionally
+exports the converted graph, reloads its `.webnn` and Safetensors artifacts, executes both the
+original ONNX model and the reloaded graph on CPU ONNX Runtime, and compares matching outputs.
+
+Proper dynamic cache-shape support is not currently available. Validation cases use fixed
+dimension overrides and execute as independent snapshots, so they do not validate a complete
+prefill-to-decode loop or growing KV-cache reuse. A decode artifact that
+accepts a fixed past length `N` normally produces a cache of length `N + 1`, which cannot be fed
+back into that same fixed-shape artifact. Repeated decoding therefore still requires proper dynamic
+cache shapes, separately specialized artifacts, or a fixed-capacity cache with an explicit position.
+
+## Broad manifest selection
+
+[`tests/models/manifest.json`](../tests/models/manifest.json) is not a global top-50 list. The
+[`scripts/generate_manifest.py` generator](../scripts/generate_manifest.py) queries Hugging Face for
+repositories tagged `transformers.js`, groups them by pipeline task, and by default selects the
+single most-liked repository in each task (`--top 1`). Tokenizer-only repositories, repositories
+without usable ONNX exports, and repositories or components listed in the
+[Transformers.js excluded-model list](transformersjs_excluded_models.md) are omitted.
+
+For each selected repository, the generator includes the ONNX components that Transformers.js
+would load for that model class, such as both encoder and merged decoder graphs for a sequence-to-
+sequence model. It follows the model's `transformers.js_config` dtype when present; otherwise the
+default generator mode mirrors WASM and prefers q8 exports. Missing preferred variants fall back to
+an available export. Models with a `use_cache_branch` input receive separate prefill and decode
+cases, while other multipart models contribute one case per required graph. Consequently the
+current manifest contains 28 repositories, 44 unique ONNX files, and 51 configured cases.
+
+The generator assigns bounded dimensions using onnx2webnn's naming policy and carries forward
+existing per-file dimension overrides, pinned inputs, and other manual fields. It inspects remote
+ONNX structure using HTTP range requests rather than downloading full weights. Regeneration tracks
+publisher `main` revisions and is therefore diagnostic coverage, not an immutable model set. The
+current population was last generated around early September 2026 and entered Git history on
+13 September 2026.
+
+## Validation automation
+
+Pull requests use `tests/models/ci-validation.json` as the required numerical-validation contract.
+Linux/ORT is blocking; macOS/CoreML runs the identical set as an experimental, non-blocking job;
+Windows does not run numerical validation. Membership is defined by the curated file itself and the
+runner invokes it with `--selection all`. Every entry must be suitable for hosted runners and include
+a full immutable Hugging Face commit revision and a lowercase SHA-256 for its primary ONNX file.
+The downloader verifies that digest both after download and on cache reuse. Full-model downloads
+use the standard Hugging Face cache by default so Python and Rust clients can share immutable blobs;
+`O2W_ONNX_CACHE` and `O2W_CACHE_DIR/onnx` remain explicit overrides. Completion records stay
+onnx2webnn-specific and do not replace the manifest digest check.
+
+To add a required model, first confirm it passes on ORT and CoreML, then add its exact file, commit
+revision, downloaded-file digest, fixed dimension overrides, and any pinned inputs to
+`tests/models/ci-validation.json`. This file is hand-maintained. The
+[`scripts/generate_manifest.py`](../scripts/generate_manifest.py) generator only regenerates
+`tests/models/manifest.json` and must not be used to update or carry CI pins.
+
+The `Full model validation` workflow is manually dispatched for real publisher weights.
+It runs the entire generated manifest sequentially on a runner labeled `self-hosted`, `linux`,
+`x64`, and `onnx2webnn-validation`. The runner must provide network access, a writable persistent
+`/var/cache/onnx2webnn`, at least a 100 GiB filesystem, and approximately 32 GiB RAM.
+Downloaded model sources persist; WebNN exports are temporary. Provisioning, storage checks, and
+build failures are fatal. Per-model validation failures remain diagnostic: the workflow emits a
+warning and uploads its complete log for 14 days. There is no scheduled or extended-tier sweep.
+
+### Runtime dtypes and comparison tolerances
+
+WebNN graphs are not globally limited to floating point: RustNN records integer tensors and integer
+operations where their operator contracts allow them. Matrix multiplication is different. The
+portable WebNN `matmul` contract accepts matching `float16` or `float32` operands, and WebNN has no
+fused equivalent of ORT's `com.microsoft.MatMulNBits`.
+
+For q4 models, the original ONNX path gives native ORT packed Uint4 weights and a fused
+`MatMulNBits` node. The reloaded path restores the same packed bytes, widens them for RustNN's
+temporary ORT graph, applies `dequantizeLinear`, and runs ordinary matmul in the scale dtype. The
+models in this manifest use Float32 scales and activations, so their reconstructed matmuls are
+Float32. Different fused/decomposed accumulation orders are mathematically equivalent but not
+bit-identical.
+
+The validator therefore uses these output tolerances:
+
+| Output/path | Comparison |
+|-------------|------------|
+| Float32 without `MatMulNBits` | `1e-5 + 1e-4 * abs(reference)` |
+| Float32 from a model containing q4 `MatMulNBits` | `1e-3 + 1e-3 * abs(reference)` |
+| Float32 from a model containing q8 `MatMulNBits` | `2e-3 + 2e-3 * abs(reference)` |
+| Float16 | `1e-3 + 1e-2 * abs(reference)` |
+| Integer and boolean | Exact |
+
+The q4 Float32 envelope is based on a complete SmolLM2 output scan whose worst normalized
+difference was `9.488e-4`. The q8 envelope is selected from each source graph's
+`MatMulNBits.bits` attribute; it covers the measured Qwen output delta while remaining distinct
+from q4. It does not make ORT's `accuracy_level=4` equivalent to WebNN:
+level 4 permits internal Int8 activation quantization, while the WebNN lowering keeps Float32
+activations. Voxtral uses level 4 on all 211 `MatMulNBits` nodes and remains blocked for this reason.
+
+## Latest recorded sweep
+
+- Real sweep: 2026-09-22 on `fkrall/cache-backed-validation` at `a582b89` plus the uncommitted Hugging Face cache migration worktree
+- RustNN: `38022044`
+- ORT: repository-local Linux x64 1.29.0 build
+- Manifest: `tests/models/manifest.json` (51 cases, 44 unique ONNX files)
+- Execution: one validation worker; ORT may use multiple CPU threads inside a case
+- Skeleton verification: 51/51 passed; the test took 81.1s (133.2s including the release build)
+- Real verification: 47/51 passed in one complete cold-cache process; wall time was 10m 43.6s and peak RSS was 30.3 GiB. No case was skipped or inferred without execution.
+
+All 51 current manifest cases were executed. A newer checkout is not considered the tested baseline
+until the real sweep is rerun and this section is replaced.
+
+```bash
+ORT_DYLIB_PATH=../rustnn/target/onnxruntime/onnxruntime-linux-x64-1.29.0/lib/libonnxruntime.so.1.29.0 \
+ target/release/onnx2webnn validate-models \
+ --selection all --weights real --jobs 1
+```
+
+### Summary
+
+| Weight mode | Pass | Fail | Download skipped | Result |
+|-------------|-----:|-----:|-----------------:|--------|
+| Real | 47 | 4 | 0 | Complete (51/51) |
+
+Two cases exercise unsupported cross-backend execution modes: FastVLM precision-sensitive GQA/DQL
+prefill and Voxtral level-4 quantized matmul. The two Chronos publisher artifacts are rejected by
+native ORT before conversion comparison.
+
+The real-weight failures, exact affected cases, current diagnosis, and suggested ownership are
+tracked in [Real-weight validation failures](real-weight-validation-failures.md).
+
+### Current failure families
+
+| Code | Cases | Stage | Current cause / next action |
+|------|------:|-------|-----------------------------|
+| Q1 | 1 | Comparison | Unsupported execution semantics: Voxtral sets `MatMulNBits accuracy_level=4`, allowing native ORT to quantize activations to Int8; WebNN lowers to Float32 dequantize-plus-matmul. |
+| U1 | 1 | Comparison | Unsupported strict equivalence: FastVLM prefill repeatedly quantizes attention results whose legal floating-point rounding differs between fused ORT kernels and the portable WebNN decomposition; decode passes. |
+| O1 | 2 | Native ORT load | Upstream-blocked: both Chronos paths resolve to the same publisher artifact, which feeds a float ConstantOfShape result to Gather indices. |
+
+Real totals: no export, reload, cached-interface, or native-ORT input failures; 2 native-ORT
+model-load failures, 2 unsupported execution-mode mismatches, and 47 passes.
+
+### Current case ledger
+
+`PASS` means export, reload, both executions, and numerical comparison completed. Any other value
+is the current first-blocker code from the table above.
+
+`WebNN If split` identifies merged decoders whose runtime `use_cache_branch` condition must be
+pinned because WebNN has no `If` operation. Each prefill/decode entry produces its own independent
+`.webnn`/Safetensors pair. Loading both graphs concurrently may duplicate their shared weights in
+RAM and VRAM. This is distinct from ordinary repeated cases that differ only in fixed dimensions.
+
+| # | Manifest case | Real | WebNN If split |
+|---:|---------------|------|----------------|
+| 0 | `briaai--RMBG-1.4 :: model_quantized.onnx` | PASS | — |
+| 1 | `openai--privacy-filter :: model_quantized.onnx` | PASS | — |
+| 2 | `nomic-ai--nomic-embed-text-v1.5 :: model_quantized.onnx` | PASS | — |
+| 3 | `mixedbread-ai--mxbai-embed-large-v1 :: model_quantized.onnx` | PASS | — |
+| 4 | `HuggingFaceTB--SmolLM2-1.7B-Instruct :: model_q4.onnx` (`sequence=64`, `past=0`) | PASS | — |
+| 5 | `HuggingFaceTB--SmolLM2-1.7B-Instruct :: model_q4.onnx` (`sequence=1`, `past=64`) | PASS | — |
+| 6 | `distil-whisper--distil-large-v2 :: encoder_model_quantized.onnx` | PASS | — |
+| 7 | `distil-whisper--distil-large-v2 :: decoder_model_merged_quantized.onnx` (`cache=0`) | PASS | Prefill pair (1/2) |
+| 8 | `distil-whisper--distil-large-v2 :: decoder_model_merged_quantized.onnx` (`cache=1`) | PASS | Decode pair (2/2) |
+| 9 | `jinaai--jina-reranker-v2-base-multilingual :: model_quantized.onnx` | PASS | — |
+| 10 | `onnx-community--FastVLM-0.5B-ONNX :: embed_tokens_quantized.onnx` | PASS | — |
+| 11 | `onnx-community--FastVLM-0.5B-ONNX :: decoder_model_merged_quantized.onnx` (`sequence=64`, `past=0`) | U1 | — |
+| 12 | `onnx-community--FastVLM-0.5B-ONNX :: decoder_model_merged_quantized.onnx` (`sequence=1`, `past=64`) | PASS | — |
+| 13 | `onnx-community--FastVLM-0.5B-ONNX :: vision_encoder_quantized.onnx` | PASS | — |
+| 14 | `Marqo--marqo-fashionSigLIP :: text_model_quantized.onnx` | PASS | — |
+| 15 | `Marqo--marqo-fashionSigLIP :: vision_model_quantized.onnx` | PASS | — |
+| 16 | `AdamCodd--vit-base-nsfw-detector :: model_quantized.onnx` | PASS | — |
+| 17 | `Xenova--nllb-200-distilled-600M :: encoder_model_quantized.onnx` | PASS | — |
+| 18 | `onnx-community--Janus-Pro-1B-ONNX :: language_model_q4.onnx` | PASS | — |
+| 19 | `onnx-community--Janus-Pro-1B-ONNX :: lm_head.onnx` | PASS | — |
+| 20 | `onnx-community--Janus-Pro-1B-ONNX :: gen_head.onnx` | PASS | — |
+| 21 | `onnx-community--Janus-Pro-1B-ONNX :: gen_img_embeds.onnx` | PASS | — |
+| 22 | `onnx-community--Janus-Pro-1B-ONNX :: image_decode.onnx` | PASS | — |
+| 23 | `Xenova--musicgen-small :: text_encoder_quantized.onnx` | PASS | — |
+| 24 | `Xenova--musicgen-small :: decoder_model_merged_quantized.onnx` (`cache=0`) | PASS | Prefill pair (1/2) |
+| 25 | `Xenova--musicgen-small :: decoder_model_merged_quantized.onnx` (`cache=1`) | PASS | Decode pair (2/2) |
+| 26 | `Mozilla--distilvit :: encoder_model_quantized.onnx` | PASS | — |
+| 27 | `onnx-community--Voxtral-Mini-3B-2507-ONNX :: embed_tokens_fp16.onnx` | PASS | — |
+| 28 | `onnx-community--Voxtral-Mini-3B-2507-ONNX :: decoder_model_merged_q4.onnx` | Q1 | — |
+| 29 | `onnx-community--Voxtral-Mini-3B-2507-ONNX :: audio_encoder_quantized.onnx` | PASS | — |
+| 30 | `Xenova--LaMini-Flan-T5-783M :: encoder_model_quantized.onnx` | PASS | — |
+| 31 | `Xenova--LaMini-Flan-T5-783M :: decoder_model_merged_quantized.onnx` (`cache=0`) | PASS | Prefill pair (1/2) |
+| 32 | `Xenova--LaMini-Flan-T5-783M :: decoder_model_merged_quantized.onnx` (`cache=1`) | PASS | Decode pair (2/2) |
+| 33 | `Xenova--detr-resnet-50 :: model_quantized.onnx` | PASS | — |
+| 34 | `Xenova--donut-base-finetuned-docvqa :: encoder_model_quantized.onnx` | PASS | — |
+| 35 | `Xenova--donut-base-finetuned-docvqa :: decoder_model_merged_quantized.onnx` (`cache=0`) | PASS | Prefill pair (1/2) |
+| 36 | `Xenova--donut-base-finetuned-docvqa :: decoder_model_merged_quantized.onnx` (`cache=1`) | PASS | Decode pair (2/2) |
+| 37 | `onnx-community--dinov3-vits16-pretrain-lvd1689m-ONNX :: model.onnx` | PASS | — |
+| 38 | `Xenova--distilbart-cnn-6-6 :: encoder_model_quantized.onnx` | PASS | — |
+| 39 | `Xenova--distilbart-cnn-6-6 :: decoder_model_merged_quantized.onnx` (`cache=0`) | PASS | Prefill pair (1/2) |
+| 40 | `Xenova--distilbart-cnn-6-6 :: decoder_model_merged_quantized.onnx` (`cache=1`) | PASS | Decode pair (2/2) |
+| 41 | `prithivMLmods--Common-Voice-Gender-Detection-ONNX :: model_quantized.onnx` | PASS | — |
+| 42 | `Xenova--bert-base-multilingual-cased :: model_quantized.onnx` | PASS | — |
+| 43 | `Xenova--distilbert-base-cased-distilled-squad :: model_quantized.onnx` | PASS | — |
+| 44 | `onnx-community--vitpose-base-simple :: model_quantized.onnx` | PASS | — |
+| 45 | `kashif--chronos-2-onnx :: encoder_model.onnx` | O1 | — |
+| 46 | `kashif--chronos-2-onnx :: decoder_model_merged.onnx` | O1 | — |
+| 47 | `huggingworld--Qwen2.5-VL-3B-Instruct-ONNX :: embed_tokens_quantized.onnx` | PASS | — |
+| 48 | `huggingworld--Qwen2.5-VL-3B-Instruct-ONNX :: decoder_model_merged_quantized.onnx` | PASS | — |
+| 49 | `onnx-community--timesformer-base-finetuned-k400 :: model_quantized.onnx` | PASS | — |
+| 50 | `Xenova--tiny-random-RoFormerForMultipleChoice :: model_quantized.onnx` | PASS | — |
+
+### Current timing and storage
+
+| Run | Cases | Wall time |
+|-----|------:|---------------:|
+| Real, cold standard HF cache | 51 | 10m 43.6s |
+
+The official Rust client populated 44 completion records, one per unique ONNX file. A subsequent
+offline `match=privacy-filter` run verified warm reuse of both the primary ONNX and its external-data
+sidecar.
+
+After the sweep, the standard Hugging Face cache occupied 18 GB and temporary WebNN artifacts
+occupied 32 GB; 122 GiB remained free. No selected case was skipped for download or storage.
+
+### Current repair order
+
+1. Track WebNN/backend capabilities that could make U1 or Q1 executable with comparable fused precision semantics.
+2. Monitor the upstream Chronos repository for corrected reference exports.
+
+## Coverage change history
+
+This history records only changes that explain coverage movement or establish that an apparent
+movement was not a functional improvement. Detailed obsolete ledgers are available through Git.
+
+| Date / tested revisions | Change | Comparable coverage effect |
+|-------------------------|--------|----------------------------|
+| 2026-09-17 — validation-tier cleanup after `25ea94c` | Removed manifest validation tiers and the hand-added FP32 RoFormer smoke case, restoring the generator-owned upstream population. Status and blocker ownership now live only in these validation documents; the CLI selects `all` or `match=`. | Population changed **52 → 51** by removing one passing duplicate-model variant. Comparable coverage is unchanged: skeleton **51/51** and real validation **47/51** with the same four blockers. |
+| 2026-09-17 — onnx2webnn `25ea94c`, RustNN `28fb3bbe`, post-rebase complete rerun | Rebased both feature stacks onto `rustnn/onnx2webnn:main` and `rustnn/rustnn:main`, then ran all 52 skeleton cases and all 52 real-weight cases from warm caches, including heavy entries. | No regression: skeleton remained **52/52**; real validation remained **48/52** with the identical FastVLM U1, Voxtral Q1, and two Chronos O1 blockers. |
+| 2026-09-17 — onnx2webnn `d4d350b`, RustNN `724d076b`, targeted FastVLM probes | Exposed layer boundaries and compared ORT CPU flash/non-flash GQA against the WebNN decomposition. Layer 0 and external rotary matched; the first failing boundary followed layer-1 GQA dynamic quantization and output projection. Native ORT first-logit output changed from `0.669124` to `1.066261` when only its GQA kernel changed, while WebNN remained `1.211192`. | Reclassified FastVLM prefill from generic N1 to unsupported U1. Coverage remains **48/52**; no tolerance was weakened. |
+| 2026-09-17 — onnx2webnn `6f6ad8a` plus current Cast/Slice/comparison worktree, RustNN `65e76e67` plus Slice-backend worktree | Normalized ONNX numeric-to-Bool Cast through comparison, preserved positive Slice strides with WebNN extent semantics, and selected q4/q8 comparison envelopes from `MatMulNBits.bits`. | DETR, Donut encoder, and Qwen passed; real coverage rose **45 → 48** on the same 52 cases. FastVLM prefill, Voxtral level 4, and the two invalid Chronos artifacts remain blocked. |
+| 2026-09-16 — onnx2webnn `24fdd50` plus current validator/manifest worktree, RustNN `7f07a5e1` plus packed-4-bit archive worktree | Measured complete q4 output error distributions and applied a `1e-3` absolute/relative Float32 envelope only to source graphs containing `MatMulNBits`. Classified Voxtral's `accuracy_level=4` Int8-activation execution mode as unsupported. | SmolLM2 prefill/decode and Janus passed; Voxtral remained Q1. Real passes rose **42 → 45** on the same 52 cases. |
+| 2026-09-16 — onnx2webnn `24fdd50` plus current manifest/tests worktree, RustNN `7f07a5e1` plus packed-4-bit archive worktree | Stored packed Int4/Uint4 constants as versioned U8 Safetensors payloads while preserving logical dtype and shape in `.webnn`, then restored and executed them on reload. Marked the identical invalid Chronos publisher artifacts as upstream-blocked. | E1 was eliminated from the real sweep: all four q4 cases reached comparison and exposed N1. The aggregate remained **42/52** because those cases do not yet match numerically. |
+| 2026-09-15 — onnx2webnn `ec5ba275` plus current validator worktree, RustNN `7f07a5e1` | Reconciled native ONNX interfaces with branch-specialized cached WebNN interfaces, dispatching only retained inputs and accepting omitted outputs only when native ORT proves they are empty. | Real passes rose **32 → 42** on the same 52 cases. All ten V1 cases passed numerically and no new blocker family appeared. |
+| 2026-09-14 — onnx2webnn `aaa33e2` plus current validator worktree, RustNN `7f07a5e1` | Made standard `token_type_ids` zero, `attention_mask` one, and preserved zero-element input buffers without treating scalars as empty. | Real passes rose **28 → 32** on the same 52 cases. I1-I3 were eliminated: four cases passed, five exposed V1, and two exposed N1. |
+| 2026-09-12 — onnx2webnn `4926c3e`, RustNN `7f07a5e1` | RustNN `2e22b3db` unified MLGraphBuilder recording and GraphJSON loader inference, replacing the loader-only string-based inference loop. Typed inference added the previously missing/rejected Resample2d, RoundEven/Clamp, Conv2d, LogicalNot, and LogicalAnd reload paths. | On the same 52-case manifest, real passes rose **6 → 28**. Former reload families R1–R5 were eliminated; newly reachable cases exposed N1 and V1 instead. |
+| 2026-09-12 — RustNN `7f07a5e1` | Made `[]` unambiguously scalar and required completed GraphInfo descriptors to have known shapes. | Hardened the shared inference path but did not itself add the dispatch that cleared R1–R5; no separate coverage gain is attributed without a bisect. |
+| 2026-09-09 — onnx2webnn `e5a5f88c`, RustNN `2783a191` | Rebased onto the newer upstream model manifest and converter work. The manifest changed from 63 to 52 cases: 21 retained, 31 added, 42 removed. | Retained cases preserved their outcomes. Real coverage changed to 6 passes because the population changed, not because comparable coverage improved. One real Voxtral download was skipped. |
+| 2026-09-08/09 — onnx2webnn `f1ca548`, RustNN `b64cf495` | First complete real-weight full-model baseline with cache-backed export, reload, deterministic execution, and comparison. | 7/63 real cases passed. Every case that reached comparison matched; failures established the original reload, serialization, and input blocker families. |
+
+### RustNN inference-refactor attribution
+
+RustNN `2e22b3db` removed the separate ten-pass GraphJSON shape-inference implementation. Both
+normal graph construction and GraphJSON loading now pass typed operations through
+`GraphRecorder::record_operation` and `infer_operation_descriptors`. This directly removed:
+
+- R1: missing Resample2d output-shape inference.
+- R2: inconsistent RoundEven spelling and missing Clamp inference.
+- R3: missing Conv2d output-shape inference.
+- R4: inconsistent LogicalNot spelling.
+- R5: inconsistent LogicalAnd spelling.
+
+The full manifest was tested at `7f07a5e1`, not bisected at `2e22b3db`; attribution is based on
+the code changes between the recorded revisions. A focused reload benchmark found the changed
+graph reconstruction/inference stage about 6% faster on seven cached graphs, while end-to-end
+weight-heavy loading was effectively unchanged.
+
+## Maintenance rule
+
+When a new complete sweep is recorded:
+
+1. Replace the latest metadata, summary, failure counts, timing, and the **single** case ledger.
+2. Add one concise history row only when a code/revision change explains coverage movement.
+3. Do not append another historical case ledger or duplicate obsolete failure-family tables.
+4. Distinguish manifest/population changes from improvements on comparable cases.
diff --git a/docs/operator-conversion-plan.md b/docs/operator-conversion-plan.md
deleted file mode 100644
index 704b23b..0000000
--- a/docs/operator-conversion-plan.md
+++ /dev/null
@@ -1,483 +0,0 @@
-# ONNX → WebNN Operator Conversion Plan
-
-Phased plan for **onnx2webnn** to convert every **ai.onnx** operator at its **latest schema
-version** within the current ONNX standard. Baseline: **opset 26** (ONNX package 1.21.0,
-`onnx.defs.onnx_opset_version()`).
-
-Each stage below is a **conversion category**, not a calendar milestone. Work within a stage can
-proceed in parallel once opset-26 schema audits and the per-op workflow (see [Implementation
-workflow](#implementation-workflow)) are in place.
-
----
-
-## Goals
-
-1. Accept models declaring **ai.onnx opset 26** (raise `MIN/MAX_SUPPORTED_OPSET` from 11–18).
-2. Convert every ONNX operator that has a WebNN target — directly, via decomposition, or via
- convert-time constant folding.
-3. Reject or pre-fold operators with no WebNN equivalent, with explicit error messages.
-4. Track progress against a regenerated opset inventory (`webnn_exporter_supported` column).
-
-## Non-goals
-
-- Supporting multiple ONNX opsets simultaneously (target opset 26 only after completion).
-- Executing converted graphs at runtime (conversion validates via rustnn ORT `build()` only).
-- Implementing ONNX control flow (`If`, `Loop`, `Scan`) as WebNN graphs.
-
----
-
-## Coverage baseline (opset 26)
-
-| Category | Count | Share |
-|----------|------:|------:|
-| Total `ai.onnx` operators at opset 26 | 198 | 100% |
-| **Stage 1 — Direct mappable** | ~95 | ~48% |
-| **Stage 2 — Complex mappable** | ~19 | ~10% |
-| **Stage 3 — Impossible to map** | ~84 | ~42% |
-
-| Implementation status (today) | Count |
-|-------------------------------|------:|
-| Handlers in `src/onnx/ops/*.rs` | 59 |
-| Stage 1 backlog (direct, not yet exported) | ~36 |
-| Stage 2 backlog (complex) | ~19 |
-
-Regenerate counts after each handler lands:
-
-```powershell
-.\.venv\Scripts\python.exe scripts\generate_onnx_opsets.py --min 26 --max 26 -o docs\onnx-opsets
-.\.venv\Scripts\python.exe -c "import csv; r=list(csv.DictReader(open('docs/onnx-opsets/opset-26.csv'))); print(sum(1 for x in r if x['webnn_exporter_supported']=='yes'))"
-```
-
-`generate_onnx_opsets.py` defaults to `../webnn-graph/docs/onnx-opsets/`; pass `-o docs\onnx-opsets` to keep
-inventory CSVs in this repo.
-
-**Related artifacts**
-
-| Artifact | Path |
-|----------|------|
-| Operator inventory (regenerate for opset 26) | `docs/onnx-opsets/opset-26.csv` (or `../webnn-graph/docs/onnx-opsets/`) |
-| Exporter manifest | `scripts/webnn_onnx_ops.py` |
-| Op handlers (Rust) | `src/onnx/ops/*.rs`, registry in `src/onnx/ops/mod.rs` |
-| Opset gate | `src/onnx/convert.rs` (`MIN/MAX_SUPPORTED_OPSET`) |
-| ONNX ↔ WebNN name map | `webnn-onnx-utils/src/operation_names.rs` |
-| Per-op conversion tests | `tests/onnx_ops/{category}/{op}.rs` (auto-generated) |
-| Test generator | `scripts/generate_rust_op_conversion_tests.py` |
-| Model audit CLI | `scripts/onnx_ops_to_csv.py --check-webnn` |
-| Opset upgrade (ad hoc) | `scripts/upgrade_onnx_opset.py` |
-
-**Python scripts (`scripts/`)**
-
-| Script | Role |
-|--------|------|
-| `webnn_onnx_ops.py` | Supported-op manifest; keep in sync with `src/onnx/ops/*.rs` |
-| `generate_rust_op_conversion_tests.py` | Regenerate `tests/onnx_ops/` and `tests/onnx_op_tests.rs` |
-| `onnx_fixture_builders.py`, `onnx_test_builders.py`, `rust_model_emitter.py` | Libraries used by the test generator |
-| `onnx_ops_to_csv.py` | Audit a model's operators and WebNN support |
-| `upgrade_onnx_opset.py` | Upgrade a model to a target `ai.onnx` opset |
-| `generate_onnx_opsets.py` | Per-opset operator inventory CSVs (`webnn_exporter_supported` column) |
-
-Install deps from repo root: `pip install -r requirements.txt` (needs `onnx`, `numpy`).
-
----
-
-## Three conversion stages
-
-```
-ONNX model (opset 26, latest schema per op)
- │
- ▼
-┌───────────────────────┐
-│ Opset gate │ convert.rs → opset 26 only
-└───────────┬───────────┘
- ▼
-┌───────────────────────┐
-│ Stage 1: Direct │ 1 ONNX node → 1 WebNN op (± trivial attrs)
-└───────────┬───────────┘
- ▼
-┌───────────────────────┐
-│ Stage 2: Complex │ fold, decompose, or multi-node subgraph
-└───────────┬───────────┘
- ▼
-┌───────────────────────┐
-│ Stage 3: Impossible │ reject (or fold-only for metadata ops)
-└───────────────────────┘
-```
-
-### Stage 1 — Direct mappable operations
-
-ONNX operators that map to a **single** [WebNN `MLGraphBuilder`
-method](https://www.w3.org/TR/webnn/#operators) with straightforward attribute and I/O translation.
-No multi-node decomposition required; optional schema-version audit for ops whose active schema
-changed at opset 22/25/26.
-
-#### 1a. Implemented today (59)
-
-| Group | Operators | Rust module |
-|-------|-----------|-------------|
-| Linear | MatMul, Gemm | `matmul.rs` |
-| Convolution | Conv, ConvTranspose | `conv.rs` |
-| Pooling | MaxPool, AveragePool, GlobalMaxPool, GlobalAveragePool | `pool.rs` |
-| Elementwise binary | Add, Sub, Mul, Div, Pow, Min, Max | `elementwise.rs` |
-| Comparison | Greater, Less, Equal, GreaterOrEqual, LessOrEqual | `comparison.rs` |
-| Conditional | Where | `conditional.rs` |
-| Normalization | LayerNormalization, Softmax | `normalization.rs` |
-| Shape | Reshape, Transpose, Concat, Split, Unsqueeze, Squeeze, Tile, Expand, Flatten | `reshape.rs` |
-| Conversion | Cast, Constant | `conversion.rs` |
-| Utility | Shape, Gather, Slice, ConstantOfShape, Range, Trilu, OneHot | `utility.rs` |
-| Reduction | ReduceMean, ReduceSum, ReduceMax, ReduceMin | `reduction.rs` |
-| Activation / unary | Relu, Gelu, Tanh, Sigmoid, Sqrt, Exp, Log, Abs, Neg, Erf, Cos, Sin, Identity | `activation.rs` |
-| Scatter | ScatterND | `scatter.rs` |
-| Pad | Pad | `pad.rs` |
-
-#### 1b. Direct backlog — not yet exported (~36)
-
-Implement as single-op handlers. Suggested Rust home in parentheses.
-
-| Group | ONNX → WebNN | Module |
-|-------|--------------|--------|
-| Normalization | BatchNormalization → `batchNormalization()` | `normalization.rs` |
-| Normalization | InstanceNormalization → `instanceNormalization()` | `normalization.rs` |
-| Gather | GatherND → `gatherND()` | `utility.rs` |
-| Gather | GatherElements → `gatherElements()` | `utility.rs` |
-| Scatter | ScatterElements → `scatterElements()` | `scatter.rs` |
-| Resample | Resize → `resample2d()` | new `resize.rs` |
-| Activation | Elu → `elu()`, LeakyRelu → `leakyRelu()`, PRelu → `prelu()` | `activation.rs` |
-| Activation | HardSigmoid → `hardSigmoid()`, HardSwish → `hardSwish()` | `activation.rs` |
-| Activation | Softplus → `softplus()`, Softsign → `softsign()` | `activation.rs` |
-| Unary math | Ceil → `ceil()`, Floor → `floor()`, Reciprocal → `reciprocal()` | `activation.rs` |
-| Unary math | Sign → `sign()`, Tan → `tan()`, Round → `roundEven()` | `activation.rs` |
-| Clamp | Clip → `clamp()` | `activation.rs` |
-| Logical | Not → `logicalNot()`, And → `logicalAnd()`, Or → `logicalOr()`, Xor → `logicalXor()` | `comparison.rs` |
-| Predicate | IsNaN → `isNaN()`, IsInf → `isInfinite()` | `comparison.rs` |
-| Reduction | ArgMin → `argMin()`, ArgMax → `argMax()` | `reduction.rs` |
-| Reduction | ReduceL1 → `reduceL1()`, ReduceL2 → `reduceL2()` | `reduction.rs` |
-| Reduction | ReduceLogSum → `reduceLogSum()`, ReduceLogSumExp → `reduceLogSumExp()` | `reduction.rs` |
-| Reduction | ReduceProd → `reduceProduct()`, ReduceSumSquare → `reduceSumSquare()` | `reduction.rs` |
-| Reduction | CumSum → `cumulativeSum()` | `reduction.rs` |
-| Pool | LpPool → `l2Pool2d()`, GlobalLpPool → `globalAveragePool` + L2 (verify spec) | `pool.rs` |
-| Sequence | ReverseSequence → `reverse()` | `utility.rs` |
-
-**Stage 1 exit criteria:** ~95 operators with `webnn_exporter_supported=yes`; all direct-backlog
-fixtures convert; Birds EfficientNet and a small transformer ONNX convert without unsupported-op
-errors for Stage 1 ops.
-
-**Suggested implementation order (by model impact):**
-
-1. BatchNormalization, InstanceNormalization, GatherND, GatherElements, ScatterElements, Resize
-2. Remaining activations and unary math (Elu, LeakyRelu, PRelu, Clip, Ceil, Floor, …)
-3. Reduction family completion (ArgMin/Max, ReduceL1/L2/…, CumSum)
-4. Logical ops (Not, And, Or, Xor, IsNaN, IsInf)
-5. LpPool, ReverseSequence
-
----
-
-### Stage 2 — Complex mappable operations
-
-Operators **can** be lowered to WebNN, but conversion requires one or more of:
-
-- **Convert-time constant folding** (no runtime graph node)
-- **Multi-node decomposition** into Stage 1 ops
-- **Non-trivial attribute / mode mapping** (document limitations and reject unsupported modes early)
-- **Schema-version-specific inputs** (e.g. opset 25 `pads` tensor on Pad)
-
-#### 2a. Convert-time fold only (no runtime WebNN op)
-
-These appear in graphs but become constants or metadata during `--optimize` / conversion — not
-`MLGraphBuilder` nodes at inference time.
-
-| Operator | Strategy | Notes |
-|----------|----------|-------|
-| Constant | Inline tensor data into graph const pool | Already handled; audit opset 25 schema |
-| ConstantOfShape | Fold when shape is static | Audit opset 25 `value` attribute |
-| Range | Fold when start/limit/delta are constant | |
-| Shape | Fold when input shape is known | |
-| Size | Fold to scalar product of static dimensions | Reject if shape is dynamic |
-
-#### 2b. Decompose into Stage 1 subgraphs
-
-| Operator | Decomposition sketch | Priority |
-|----------|---------------------|----------|
-| CastLike | Type-infer `to` from second input → `Cast` | P1 |
-| Mean | `ReduceMean` over all axes (or specified) | P2 |
-| Sum | `ReduceSum` over all axes (or specified) | P2 |
-| LogSoftmax | `Softmax` + `Log` | P2 |
-| Hardmax | `Softmax` + one-hot style mask (or reject axis edge cases) | P3 |
-| Swish | `Sigmoid` × input (`Mul`) | P2 |
-| Celu | `Elu` variant with alpha (after Elu lands) | P3 |
-| Selu | scale × ELU-like piecewise | P3 |
-| Mish | `Softplus` + `Tanh` + `Mul` (after Softplus lands) | P3 |
-| ThresholdedRelu | `Relu` + comparison + `Where` | P3 |
-| GroupNormalization | Mean/var over group dims + `Sub`/`Div`/`Mul`/`Add` | P1 — common in vision |
-| RMSNormalization | L2 norm over axes + scale (new in opset 23) | P1 — common in LLMs |
-| SimplifiedLayerNormalization | Alias of RMSNormalization | Implemented — standard/exporter fusion |
-| SkipSimplifiedLayerNormalization | Residual/bias add + RMSNormalization | Implemented — com.microsoft fusion |
-| RotaryEmbedding | Gather caches + rotate pairs + concatenate | Implemented — standard and com.microsoft |
-| MatMulNBits | uint4/uint8 reinterpret + dequantizeLinear + transpose + matmul | Implemented — bits=4/8; reject g_idx |
-| MatMulInteger | centered float `matmul` + `int32` cast (mirrors ConvInteger) | Implemented — scalar or 1-D zero points |
-| Einsum | reduceSum + transpose/reshape + batched `matmul` | Implemented — ≤2 inputs; reject ellipsis/diagonals |
-| MatMulBnb4 | packed uint8 constant, nibbles unpacked with int32 div/sub, codebook `gather`, per-block absmax `mul`, `matmul` | Implemented; weight stays 4-bit in the graph, dense fallback when N*K is not a whole number of blocks |
-| GroupQueryAttention | reshape/transpose + KV-cache concat + scaled matmul + static causal mask + softmax | Implemented — unpadded batch assumption; reject packed QKV/do_rotary/softcap/local window |
-| MoE | dense expert evaluation (batched matmul) + iterative top-k mask + masked softmax blend | Implemented — fused interleaved swiglu/relu/gelu/sigmoid; reject fc3/sparse mixer; num_experts/k× FLOP overhead |
-| QMoE | uint4/uint8 expert constants with blockwise `dequantizeLinear`, then the MoE lowering | Implemented; same restrictions as MoE, dense fallback when `in` is not a whole number of blocks |
-| GatherBlockQuantized | `gather` packed rows, scales and zero points, unpack nibbles, `dequantizeLinear` on the gathered slice | Implemented; 2-D tables along axis 0 (uint8, INT4, UINT4), other layouts dequantize at conversion time |
-| SplitToSequence + SequenceAt | per-element slices registered as pseudo-operands, SequenceAt aliases by constant index | Implemented — torch split() export pattern; sequence graph outputs rejected |
-| If (constant condition) | branch inlined into the outer graph before conversion; a boolean graph input gate (optimum's `use_cache_branch`) becomes constant via `--pin-input` | Implemented — data-dependent conditions remain unsupported |
-| LSTM/GRU (direction=bidirectional/reverse) | WebNN native direction support + [dirs,...] bias split and Y layout | Implemented — LSTM initial_h/initial_c wired; sequence_lens/peephole rejected |
-| CumProd | `cumulativeSum` on log + `Exp`, or iterative multiply subgraph | P3 — new in opset 26 |
-
-#### 2c. Advanced single-entry WebNN ops (high attribute complexity)
-
-Treat as Stage 2 because spec coverage and testing cost exceed typical Stage 1 ops.
-
-| Operator | WebNN target | Key risks |
-|----------|--------------|-----------|
-| GRU | `gru()` / `gruCell()` | Layout, direction, hidden size, optional inputs |
-| LSTM | `lstm()` / `lstmCell()` | Same as GRU; bidirectional |
-| RNN | Partial via GRU/LSTM or reject | No dedicated WebNN `rnn()` |
-| QuantizeLinear | `quantizeLinear()` | Scale/zero-point types, axis |
-| DequantizeLinear | `dequantizeLinear()` | Pair with quant models |
-| Pad | `pad()` | Opset 25: `pads` as input tensor; verify `pad.rs` |
-| Resize | `resample2d()` | Coordinate transformation modes, cubic exclusion |
-| Gemm | `gemm()` | Already exported; audit transA/transB, alpha/beta at opset 13+ |
-| Conv / Pool | `conv2d()` / pools | Audit opset 22 dilations, auto_pad, ceil_mode |
-
-**Stage 2 exit criteria:** All fold/decompose paths documented in lowering notes; GroupNormalization
-and RMSNormalization decompositions tested on at least one real model; GRU/LSTM/quant handlers
-pass unit tests with documented limitations.
-
----
-
-### Stage 3 — Impossible to map operations
-
-No WebNN `MLGraphBuilder` equivalent exists. **Do not** add graph handlers. Strategy: **reject**
-at conversion with a clear message, unless the op can be eliminated earlier (optimizer, training
-vs inference export).
-
-#### 3a. Control flow and graph structure
-
-| Operators | Reason |
-|-----------|--------|
-| If, Loop, Scan | WebNN has no conditional or recurrent graph execution |
-
-#### 3b. Sequence and optional types
-
-| Operators | Reason |
-|-----------|--------|
-| SequenceAt, SequenceConstruct, SequenceEmpty, SequenceErase, SequenceInsert, SequenceLength, SequenceMap, SplitToSequence, ConcatFromSequence | WebNN tensors only; no sequence type |
-| Optional, OptionalGetElement, OptionalHasElement | No optional type in WebNN |
-
-#### 3c. Strings and NLP utilities
-
-| Operators | Reason |
-|-----------|--------|
-| StringConcat, StringSplit, StringNormalizer, RegexFullMatch, TfIdfVectorizer | No string tensors in WebNN |
-
-#### 3d. Random and stochastic
-
-| Operators | Reason |
-|-----------|--------|
-| Bernoulli, Multinomial, RandomNormal, RandomNormalLike, RandomUniform, RandomUniformLike | Non-deterministic; no WebNN RNG ops |
-
-#### 3e. Training, loss, and dropout
-
-| Operators | Reason |
-|-----------|--------|
-| Dropout, NegativeLogLikelihoodLoss, SoftmaxCrossEntropyLoss | Training-only; inference graphs should fold or omit |
-
-#### 3f. Detection, vision, and spatial extras
-
-| Operators | Reason |
-|-----------|--------|
-| NonMaxSuppression, RoiAlign, DeformConv, GridSample, ImageDecoder, MaxRoiPool, MaxUnpool, AffineGrid, CenterCropPad, Col2Im, DepthToSpace, SpaceToDepth | No WebNN ops; post-processing or custom CUDA territory |
-
-#### 3g. Signal processing and windows
-
-| Operators | Reason |
-|-----------|--------|
-| DFT, STFT, MelWeightMatrix, BlackmanWindow, HammingWindow, HannWindow | No WebNN signal ops |
-
-#### 3h. Quantized conv / matmul (integer)
-
-| Operators | Reason |
-|-----------|--------|
-| QLinearConv, QLinearMatMul | Fused quantized ops not in WebNN; use explicit float + quantization decomposition. `MatMulInteger` is now implemented via centered float matmul |
-
-`ConvInteger` is supported through centered float `conv2d` followed by an `int32` cast,
-`DynamicQuantizeLinear` is decomposed into reductions, scale/zero-point arithmetic, and WebNN
-`quantizeLinear`, and `MatMulNBits` (bits=4) follows ORT's WebNN EP path:
-`dequantizeLinear` → reshape → transpose → `matmul` (+ optional bias).
-
-#### 3i. Bitwise and type reinterpretation
-
-| Operators | Reason |
-|-----------|--------|
-| BitwiseAnd, BitwiseOr, BitwiseXor, BitwiseNot, BitShift, **BitCast** (new opset 26) | No bitwise ops in WebNN |
-
-#### 3j. Linear algebra and einsum
-
-| Operators | Reason |
-|-----------|--------|
-| Det | No determinant in WebNN. `Einsum` is now implemented for one/two-input equations without ellipsis or repeated per-term labels (lowered to reduceSum/transpose/reshape/matmul) |
-
-#### 3k. Indexing and sorting extras
-
-| Operators | Reason |
-|-----------|--------|
-| TopK, Unique, Compress, NonZero, OneHot, EyeLike | No WebNN equivalents |
-| **TensorScatter** (opset 24) | No WebNN op; use ScatterND/ScatterElements subset only |
-
-#### 3l. Attention and transformer extras (recent ONNX)
-
-| Operators | Reason |
-|-----------|--------|
-| **Attention** (opset 24) | Composite op; no single WebNN op — could be Stage 2 decomposition in future, but out of scope until spec stabilizes |
-| **MoE** (com.microsoft) | Requires TopK expert routing and efficient sparse dispatch, neither available in WebNN |
-
-#### 3m. Miscellaneous
-
-| Operators | Reason |
-|-----------|--------|
-| Acos, Acosh, Asin, Asinh, Atan, Atanh, Cosh, Sinh | No inverse/hyperbolic unary in WebNN |
-| LRN, LpNormalization, MeanVarianceNormalization | Legacy normalization; decompose or reject |
-| Mod, Shrink | No direct WebNN op |
-| Mod, Mish (as dedicated op) | Prefer Stage 2 decomposition via Swish/Mish subgraph if needed |
-
-**Stage 3 handling**
-
-| Strategy | When | Example |
-|----------|------|---------|
-| Reject | No WebNN op and not foldable | If, Loop, Attention |
-| Pre-fold / strip | Training artifact in inference export | Dropout → Identity |
-| Document workaround | Rare; manual graph rewrite | Replace TopK with external post-process |
-
----
-
-## Opset 26 schema revisions
-
-Operators whose **active schema version** changed at or after opset 21 need a fixture pass even when
-already exported. Priority: schema v25 and v26.
-
-| Operator | Active schema | Exporter today | Action |
-|----------|--------------:|----------------|--------|
-| Cast | 25 | yes | Audit `to` / saturate rules |
-| Constant | 25 | yes | Verify sparse / value attributes |
-| ConstantOfShape | 25 | yes | Verify optional `value` |
-| Flatten | 25 | yes | Verify `axis` default |
-| Identity | 25 | yes | Smoke test |
-| Reshape | 25 | yes | Verify `allowzero` |
-| Shape | 25 | yes | Constant-fold path |
-| Squeeze | 25 | yes | Verify axes input |
-| Transpose | 25 | yes | Verify optional `perm` |
-| Unsqueeze | 25 | yes | Verify axes input |
-| Pad | 25 | yes | Confirm `pads` tensor input |
-| If, Loop, Scan | 25 | no | Reject |
-| CastLike | 25 | no | Stage 2 decompose |
-| QuantizeLinear, DequantizeLinear | 25 | no | Stage 2 |
-| Conv, ConvTranspose, MaxPool, AveragePool, … | 22 | partial | Audit dilations, auto_pad |
-| **BitCast** | 26 | no | Stage 3 reject |
-| **CumProd** | 26 | no | Stage 2 decompose |
-
-Regenerate per-op Rust conversion tests (fixture opset 26, converter gate opset 18 today):
-
-```powershell
-.\.venv\Scripts\python.exe scripts\generate_rust_op_conversion_tests.py --fixture-opset 26 --test-opset 18
-cargo test --test onnx_op_tests
-```
-
----
-
-## Implementation workflow
-
-Use this checklist for **every** operator added in Stages 1–2:
-
-- [ ] **Audit real models** — `scripts/onnx_ops_to_csv.py model.onnx --check-webnn`
-- [ ] **Read ONNX schema** — `onnx.defs.get_schema(op, 26)` or opset-26 sidecar fixture
-- [ ] **Read WebNN spec** — method signature, options, `opSupportLimits`
-- [ ] **Implement handler** — `supports()` + `convert()` in `src/onnx/ops/*.rs`
-- [ ] **Shape inference** — update `src/onnx/shape_inference.rs` if needed
-- [ ] **Register** — add to `OpRegistry` in `src/onnx/ops/mod.rs`
-- [ ] **Sync manifest** — add to `scripts/webnn_onnx_ops.py`
-- [ ] **Update name map** — `webnn-onnx-utils/src/operation_names.rs` if new mapping
-- [ ] **Regenerate inventory** — `scripts/generate_onnx_opsets.py --min 26 --max 26 -o docs/onnx-opsets`
-- [ ] **Regenerate tests** — `scripts/generate_rust_op_conversion_tests.py --fixture-opset 26 --test-opset 18`
-- [ ] **Unit tests** — handler tests; `cargo test --test onnx_op_tests {op_snake}::opset26`
-- [ ] **Convert smoke test** — `cargo run -- convert --input model.onnx --optimize` on a real model using the op
-
----
-
-## Rollout phases
-
-Cross-cutting milestones that span the three stages:
-
-### Phase 0 — Foundation
-
-| Task | Files |
-|------|-------|
-| Regenerate opset-26 inventory | `scripts/generate_onnx_opsets.py` → `docs/onnx-opsets/` |
-| Set opset gate to 26 only | `src/onnx/convert.rs`, `scripts/webnn_onnx_ops.py` |
-| Opset 25/26 schema audit for 59 implemented ops | handlers + fixtures |
-| Sync `webnn_onnx_ops.py` with Rust registry | `scripts/webnn_onnx_ops.py` |
-
-**Exit:** Converter accepts opset 26, rejects 18; 59 ops pass v26 fixture conversion.
-
-### Phase 1 — Stage 1 high-impact gaps
-
-BatchNormalization, InstanceNormalization, GatherND, GatherElements, ScatterElements, Resize, Pad
-v25 verification.
-
-**Exit:** EfficientNet-class vision models convert; inventory ≥ 66 supported ops.
-
-### Phase 2 — Stage 1 activations, unary, logical, reductions
-
-Complete Stage 1 backlog table in [§1b](#1b-direct-backlog--not-yet-exported-36).
-
-**Exit:** ~95 supported ops; Stage 1 complete.
-
-### Phase 3 — Stage 2 decomposition and advanced ops
-
-GroupNormalization, RMSNormalization, CastLike, Mean/Sum/LogSoftmax, Swish; then GRU/LSTM/quant.
-
-**Exit:** Documented decomposition paths; optional advanced coverage for seq2seq and int8.
-
-### Phase 4 — Documentation and CI
-
-- Maintain supported/reject lists in lowering docs
-- CI: `scripts/onnx_ops_to_csv.py model.onnx --check-webnn` on checked-in ONNX models
-- Optional: generate `webnn_onnx_ops.py` from Rust `supports()` lists
-
----
-
-## Progress tracking
-
-| Milestone | Ops exported | Opset gate | Notes |
-|-----------|-------------:|------------|-------|
-| **Today** | 59 | 11–18 | ~30% of opset 26 |
-| Phase 0 done | 59 | 26 only | v25/v26 schema audit |
-| Phase 1 done | ~66 | 26 only | Vision models |
-| Phase 2 done | ~95 | 26 only | Stage 1 complete |
-| Phase 3 done | ~95+ | 26 only | Stage 2 decompositions |
-| Stage 3 | — | 26 only | 84 ops permanently rejected |
-
----
-
-## Risk register
-
-| Risk | Mitigation |
-|------|------------|
-| Schema v26 differs from v18 for same op name | Mandatory opset-26 fixture per op; read `schema_since_version` |
-| WebNN `resample2d` ≠ ONNX `Resize` | Document mode/coordinate mapping; reject unsupported modes |
-| `webnn_onnx_ops.py` drifts from Rust | Regenerate CSV after every op; optional codegen from `supports()` |
-| GroupNormalization / RMSNormalization in new models | Stage 2 decomposition before reject |
-| Attention / RotaryEmbedding in LLM exports | Stage 3 reject until decomposition designed |
-| Dynamic shapes | `--optimize` constant folding; static shapes at runtime |
-| MLGraphBuilder migration | Align handlers with [mlgraphbuilder-migration-plan.md](./mlgraphbuilder-migration-plan.md) |
-
----
-
-## References
-
-- [W3C WebNN specification](https://www.w3.org/TR/webnn/)
-- [ONNX Operator Schemas](https://github.com/onnx/onnx/blob/main/docs/Operators.md)
-- [webnn-onnx-utils operation_names.rs](../../webnn-onnx-utils/src/operation_names.rs)
-- [webnn-graph opset 21 plan (historical)](../../webnn-graph/docs/onnx-opsets/opset21-implementation-plan.md)
-- [onnx2webnn AGENTS.md](../AGENTS.md)
diff --git a/docs/operator-conversion-status.md b/docs/operator-conversion-status.md
new file mode 100644
index 0000000..a6ade0f
--- /dev/null
+++ b/docs/operator-conversion-status.md
@@ -0,0 +1,302 @@
+# ONNX → WebNN operator conversion status
+
+> **Last edited:** `2026-09-18T13:48:47Z`
+> **Checkout:** `fkrall/cache-backed-validation` at `558f641`
+>
+> **Freshness:** Use this document only when this provenance is recent relative to the relevant
+> code and commits; otherwise verify the implementation, tests, and Git history before relying
+> on it.
+
+This document is a capability inventory for the current **onnx2webnn** branch. It contains no
+implementation phases or rollout plan. The baseline is the 198 non-deprecated standard
+`ai.onnx` operator names active at opset 26.
+
+An operator marked implemented has at least one executable form. It does not imply that every
+schema revision, attribute combination, data type, rank, optional input/output, or dynamic-shape
+form is supported. The scope column records known material restrictions.
+
+## Status categories
+
+| Category | Meaning |
+|----------|---------|
+| **Direct WebNN** | At least one supported form primarily maps to a standardized WebNN builder primitive. |
+| **Decomposed** | Executes through multiple WebNN primitives because no matching native primitive exists. |
+| **Folded/static** | Resolved or materialized while converting. |
+| **Pattern-only** | Executes only when a specific static export pattern can be eliminated or inlined. |
+| **Unsupported** | No executable lowering exists on this branch. |
+
+Direct WebNN is the spec-level subset of executable support. Decomposed, folded, and pattern-only
+operators execute, but are not native WebNN operator mappings.
+
+### Estimated performance impact
+
+The performance-impact column is populated only for **Decomposed** operators. These estimates are
+derived from the current emitted WebNN graph, not from cross-backend benchmarks; actual cost depends
+on tensor shapes, backend fusion, memory movement, and hardware.
+
+| Tier | Meaning |
+|------|---------|
+| **minimal** | A small number of cheap elementwise, cast, or shape operations; backend fusion can often remove most overhead. |
+| **moderate** | Multiple full-tensor passes, reductions, or materialized intermediates are expected to add noticeable overhead. |
+| **high** | The lowering loses a fused, quantized, integer-accumulation, attention, or sparse-dispatch path and can materially increase compute or memory traffic. |
+| **not inferred** | Impact varies too strongly with the accepted equation, input count, shape, or backend to estimate from the lowering alone. |
+
+## Coverage
+
+| Opset-26 standard ONNX coverage | Count | Share |
+|---------------------------------|------:|------:|
+| Total non-deprecated `ai.onnx` names | 198 | 100% |
+| **Executable for at least one supported form** | **129** | **65.2%** |
+| └ Direct WebNN/spec-level subset | **96** | **48.5%** |
+| └ Decomposed, folded, or pattern-only | **33** | **16.7%** |
+| Unsupported | 69 | 34.8% |
+
+The 129 executable names comprise 127 standard names advertised by
+`scripts/webnn_onnx_ops.py`, plus the narrow registry-only `SplitToSequence` and `SequenceAt`
+pattern. The 96 direct names comprise 86 mappings in the pinned `webnn-onnx-utils` name map plus
+ten direct handlers not yet mirrored there: `CumSum`, `GatherElements`, `GatherND`, `IsInf`,
+`IsNaN`, `LpPool`, `ReverseSequence`, `Round`, `ScatterElements`, and `ScatterND`.
+
+## Complete opset-26 status matrix
+
+| ONNX operator | Status | Execution scope and WebNN limitation | Estimated perf impact |
+|---------------|--------|--------------------------------------|-----------------------|
+| `Abs` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Acos` | Unsupported | No current executable lowering. |
+| `Acosh` | Decomposed | Elementwise decomposition; WebNN has no native acosh. | moderate |
+| `Add` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `AffineGrid` | Unsupported | No current lowering; the required vision/spatial primitive is absent or not implemented. |
+| `And` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `ArgMax` | Direct WebNN | Direct WebNN argMax; select_last_index is not implemented. |
+| `ArgMin` | Direct WebNN | Direct WebNN argMin; select_last_index is not implemented. |
+| `Asin` | Unsupported | No current executable lowering. |
+| `Asinh` | Decomposed | Elementwise decomposition; WebNN has no native asinh. | moderate |
+| `Atan` | Unsupported | No current executable lowering. |
+| `Atanh` | Decomposed | Elementwise decomposition; WebNN has no native atanh. | moderate |
+| `Attention` | Unsupported | No native WebNN attention; no standard Attention decomposition is implemented. |
+| `AveragePool` | Direct WebNN | Direct WebNN pool; 1-D is emulated, higher spatial ranks rejected, and some ONNX padding modes are restricted. |
+| `BatchNormalization` | Direct WebNN | Direct WebNN primitive; inference mode only. |
+| `Bernoulli` | Unsupported | WebNN has no graph random-number operators. |
+| `BitCast` | Unsupported | WebNN has no corresponding bitwise/reinterpretation primitive. |
+| `BitShift` | Unsupported | WebNN has no corresponding bitwise/reinterpretation primitive. |
+| `BitwiseAnd` | Unsupported | WebNN has no corresponding bitwise/reinterpretation primitive. |
+| `BitwiseNot` | Unsupported | WebNN has no corresponding bitwise/reinterpretation primitive. |
+| `BitwiseOr` | Unsupported | WebNN has no corresponding bitwise/reinterpretation primitive. |
+| `BitwiseXor` | Unsupported | WebNN has no corresponding bitwise/reinterpretation primitive. |
+| `BlackmanWindow` | Unsupported | WebNN has no corresponding signal-processing primitive. |
+| `Cast` | Direct WebNN | Direct WebNN cast where the target type supports it; numeric-to-Boolean casts normalize nonzero values through a WebNN comparison. Unsupported ONNX/string element types are rejected. |
+| `CastLike` | Decomposed | Infers the second input's type at conversion time, then emits WebNN cast. | minimal |
+| `Ceil` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Celu` | Decomposed | Elementwise decomposition; no native WebNN celu. | moderate |
+| `CenterCropPad` | Unsupported | No current lowering; the required vision/spatial primitive is absent or not implemented. |
+| `Clip` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Col2Im` | Unsupported | No current lowering; the required vision/spatial primitive is absent or not implemented. |
+| `Compress` | Unsupported | No current lowering; missing primitive and/or data-dependent output shape. |
+| `Concat` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `ConcatFromSequence` | Unsupported | WebNN has no sequence operand type. |
+| `Constant` | Folded/static | Creates an inline WebNN constant; no runtime ONNX operation remains. |
+| `ConstantOfShape` | Folded/static | Materialized or expanded from statically resolvable shape/value data. |
+| `Conv` | Direct WebNN | Direct WebNN conv2d; 1-D is emulated and 3-D is unsupported. |
+| `ConvInteger` | Decomposed | Centers values in float, executes conv2d, then casts to int32; WebNN lacks integer accumulation, so large sums may lose exactness. | high |
+| `ConvTranspose` | Direct WebNN | Direct WebNN convTranspose2d; 1-D is emulated and 3-D is unsupported. |
+| `Cos` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Cosh` | Decomposed | Elementwise decomposition; WebNN has no native cosh. | moderate |
+| `CumProd` | Decomposed | exp(cumulativeSum(log(x))); valid only for positive floating-point inputs. Zero and negative inputs are not generally correct. | moderate |
+| `CumSum` | Direct WebNN | Direct WebNN cumulativeSum; axis must be constant. |
+| `DFT` | Unsupported | WebNN has no corresponding signal-processing primitive. |
+| `DeformConv` | Unsupported | No current lowering; the required vision/spatial primitive is absent or not implemented. |
+| `DepthToSpace` | Unsupported | No current lowering; the required vision/spatial primitive is absent or not implemented. |
+| `DequantizeLinear` | Direct WebNN | Direct WebNN primitive for supported scalar/effectively-scalar or full-rank block parameters; axis must be 1. |
+| `Det` | Unsupported | No current executable lowering. |
+| `Div` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Dropout` | Unsupported | No current inference lowering; exporters may remove applicable training artifacts. |
+| `DynamicQuantizeLinear` | Decomposed | Reduction/arithmetic decomposition followed by WebNN quantizeLinear. | moderate |
+| `Einsum` | Decomposed | Static reduce/transpose/reshape/matmul decomposition; at most two inputs, no ellipsis or repeated labels within a term. | not inferred |
+| `Elu` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Equal` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Erf` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Exp` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Expand` | Direct WebNN | Direct WebNN expand; target shape must be build-time resolvable. |
+| `EyeLike` | Unsupported | No current lowering; missing primitive and/or data-dependent output shape. |
+| `Flatten` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Floor` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `GRU` | Direct WebNN | Direct WebNN recurrent primitive with layout adaptation; sequence_lens and unsupported optional inputs are rejected. |
+| `Gather` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `GatherElements` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `GatherND` | Direct WebNN | Direct WebNN gatherND; ONNX batch_dims is currently ignored. |
+| `Gelu` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Gemm` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `GlobalAveragePool` | Direct WebNN | Direct WebNN pool; supported for the implemented 1-D/2-D spatial forms. |
+| `GlobalLpPool` | Unsupported | No current executable lowering. |
+| `GlobalMaxPool` | Direct WebNN | Direct WebNN pool; supported for the implemented 1-D/2-D spatial forms. |
+| `Greater` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `GreaterOrEqual` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `GridSample` | Unsupported | No current lowering; the required vision/spatial primitive is absent or not implemented. |
+| `GroupNormalization` | Decomposed | Reduction and elementwise decomposition; relevant ranks and dimensions must be known. | moderate |
+| `HammingWindow` | Unsupported | WebNN has no corresponding signal-processing primitive. |
+| `HannWindow` | Unsupported | WebNN has no corresponding signal-processing primitive. |
+| `HardSigmoid` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `HardSwish` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Hardmax` | Decomposed | Comparison/mask decomposition; requires known input shape. | moderate |
+| `Identity` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `If` | Folded/static | Only constant or pinned-constant conditions; the selected branch is resolved and inlined during conversion because WebNN has no control flow. Runtime-selectable models therefore require a separate `.webnn`/Safetensors pair per branch. Shared weights are not deduplicated, so keeping both compiled graphs resident may duplicate them in RAM and VRAM. |
+| `ImageDecoder` | Unsupported | No current lowering; the required vision/spatial primitive is absent or not implemented. |
+| `InstanceNormalization` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `IsInf` | Direct WebNN | Direct WebNN isInfinite; non-default ONNX detect_negative/detect_positive filtering is not implemented. |
+| `IsNaN` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `LRN` | Unsupported | No current executable lowering. |
+| `LSTM` | Direct WebNN | Direct WebNN recurrent primitive with layout adaptation; sequence_lens and peepholes are rejected. |
+| `LayerNormalization` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `LeakyRelu` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Less` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `LessOrEqual` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Log` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `LogSoftmax` | Decomposed | Softmax/log decomposition; no native WebNN logSoftmax. | moderate |
+| `Loop` | Unsupported | No WebNN runtime loop/control-flow representation. |
+| `LpNormalization` | Unsupported | No current executable lowering. |
+| `LpPool` | Direct WebNN | Direct WebNN l2Pool2d only when p=2; 1-D is emulated and higher spatial ranks rejected. |
+| `MatMul` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `MatMulInteger` | Decomposed | Centers values in float, executes matmul, then casts to int32; WebNN lacks integer accumulation, so large sums may lose exactness. | high |
+| `Max` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `MaxPool` | Direct WebNN | Direct WebNN pool; indices output is unsupported, 1-D is emulated, and higher spatial ranks are rejected. |
+| `MaxRoiPool` | Unsupported | No current lowering; the required vision/spatial primitive is absent or not implemented. |
+| `MaxUnpool` | Unsupported | No current lowering; the required vision/spatial primitive is absent or not implemented. |
+| `Mean` | Decomposed | Variadic binary fold; operation order and floating-point rounding can differ. | not inferred |
+| `MeanVarianceNormalization` | Unsupported | No current executable lowering. |
+| `MelWeightMatrix` | Unsupported | WebNN has no corresponding signal-processing primitive. |
+| `Min` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Mish` | Decomposed | Softplus/tanh/multiply decomposition; no native WebNN mish. | moderate |
+| `Mod` | Decomposed | A - B * q(A/B) decomposition for both fmod modes; no WebNN remainder primitive. | moderate |
+| `Mul` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Multinomial` | Unsupported | WebNN has no graph random-number operators. |
+| `Neg` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `NegativeLogLikelihoodLoss` | Unsupported | No current inference lowering; exporters may remove applicable training artifacts. |
+| `NonMaxSuppression` | Unsupported | No current lowering; the required vision/spatial primitive is absent or not implemented. |
+| `NonZero` | Unsupported | No current lowering; missing primitive and/or data-dependent output shape. |
+| `Not` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `OneHot` | Decomposed | Static comparison/mask decomposition; depth and values must be constant and indices shape known. | moderate |
+| `Optional` | Unsupported | WebNN has no optional operand type. |
+| `OptionalGetElement` | Unsupported | WebNN has no optional operand type. |
+| `OptionalHasElement` | Unsupported | WebNN has no optional operand type. |
+| `Or` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `PRelu` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Pad` | Direct WebNN | Direct WebNN pad; rank and non-negative pads must be known at build time. |
+| `Pow` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `QLinearConv` | Unsupported | No fused WebNN quantized linear-algebra primitive; float decomposition is not implemented. |
+| `QLinearMatMul` | Unsupported | No fused WebNN quantized linear-algebra primitive; float decomposition is not implemented. |
+| `QuantizeLinear` | Direct WebNN | Direct WebNN primitive for supported scalar/effectively-scalar or full-rank block parameters; axis must be 1. |
+| `RMSNormalization` | Decomposed | Reduction and elementwise decomposition; relevant ranks and dimensions must be known. | moderate |
+| `RNN` | Unsupported | No current executable lowering. |
+| `RandomNormal` | Unsupported | WebNN has no graph random-number operators. |
+| `RandomNormalLike` | Unsupported | WebNN has no graph random-number operators. |
+| `RandomUniform` | Unsupported | WebNN has no graph random-number operators. |
+| `RandomUniformLike` | Unsupported | WebNN has no graph random-number operators. |
+| `Range` | Folded/static | Implemented for constant scalar or supported bounded-symbolic forms. |
+| `Reciprocal` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `ReduceL1` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `ReduceL2` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `ReduceLogSum` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `ReduceLogSumExp` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `ReduceMax` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `ReduceMean` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `ReduceMin` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `ReduceProd` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `ReduceSum` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `ReduceSumSquare` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `RegexFullMatch` | Unsupported | WebNN has no string tensor/operation support. |
+| `Relu` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Reshape` | Direct WebNN | Direct WebNN reshape; target shape must be build-time resolvable. |
+| `Resize` | Direct WebNN | Maps to WebNN resample2d; nearest/linear supported forms only, with 1-D emulation. Cubic and unsupported coordinate/axis modes are rejected. |
+| `ReverseSequence` | Direct WebNN | Partial mapping to WebNN reverse: sequence_lens and batch_axis semantics are ignored, so only full-axis reversal is equivalent. |
+| `RoiAlign` | Unsupported | No current lowering; the required vision/spatial primitive is absent or not implemented. |
+| `RotaryEmbedding` | Decomposed | Gather/rotate/concat decomposition; concrete dimensions and compatible cache shapes required. | moderate |
+| `Round` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `STFT` | Unsupported | WebNN has no corresponding signal-processing primitive. |
+| `Scan` | Unsupported | No WebNN runtime loop/control-flow representation. |
+| `ScatterElements` | Direct WebNN | Direct WebNN scatterElements; only reduction=none. |
+| `ScatterND` | Direct WebNN | Direct WebNN scatterND; only reduction=none. |
+| `Selu` | Decomposed | Elementwise decomposition; no native WebNN selu. | moderate |
+| `SequenceAt` | Pattern-only | Only a constant index into the converter's SplitToSequence pseudo-sequence. |
+| `SequenceConstruct` | Unsupported | WebNN has no sequence operand type. |
+| `SequenceEmpty` | Unsupported | WebNN has no sequence operand type. |
+| `SequenceErase` | Unsupported | WebNN has no sequence operand type. |
+| `SequenceInsert` | Unsupported | WebNN has no sequence operand type. |
+| `SequenceLength` | Unsupported | WebNN has no sequence operand type. |
+| `SequenceMap` | Unsupported | WebNN has no sequence operand type. |
+| `Shape` | Folded/static | Resolved from known graph shape metadata. |
+| `Shrink` | Decomposed | Elementwise comparison/select decomposition; no native WebNN shrink. | moderate |
+| `Sigmoid` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Sign` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Sin` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Sinh` | Decomposed | Elementwise decomposition; WebNN has no native sinh. | moderate |
+| `Size` | Unsupported | No current executable lowering. |
+| `Slice` | Direct WebNN | Direct WebNN slice/reverse; starts, sizes, axes, and steps must resolve at build time. Positive steps are preserved as strides; negative steps support only the full-axis reverse pattern. |
+| `Softmax` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `SoftmaxCrossEntropyLoss` | Unsupported | No current inference lowering; exporters may remove applicable training artifacts. |
+| `Softplus` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Softsign` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `SpaceToDepth` | Unsupported | No current lowering; the required vision/spatial primitive is absent or not implemented. |
+| `Split` | Direct WebNN | Direct WebNN split; split sizes or the split dimension must be resolvable. |
+| `SplitToSequence` | Pattern-only | Only the static split pattern consumed by constant-index SequenceAt; no sequence graph output. |
+| `Sqrt` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Squeeze` | Direct WebNN | Direct WebNN squeeze; axes must be resolvable. |
+| `StringConcat` | Unsupported | WebNN has no string tensor/operation support. |
+| `StringNormalizer` | Unsupported | WebNN has no string tensor/operation support. |
+| `StringSplit` | Unsupported | WebNN has no string tensor/operation support. |
+| `Sub` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Sum` | Decomposed | Variadic binary fold; operation order and floating-point rounding can differ. | not inferred |
+| `Swish` | Decomposed | Sigmoid/multiply decomposition; no native WebNN swish. | minimal |
+| `Tan` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Tanh` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `TensorScatter` | Unsupported | No current lowering; missing primitive and/or data-dependent output shape. |
+| `TfIdfVectorizer` | Unsupported | WebNN has no string tensor/operation support. |
+| `ThresholdedRelu` | Decomposed | Comparison/select decomposition; no native WebNN thresholdedRelu. | minimal |
+| `Tile` | Direct WebNN | Direct WebNN tile; repeats must be constant. |
+| `TopK` | Unsupported | No current lowering; missing primitive and/or data-dependent output shape. |
+| `Transpose` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Trilu` | Direct WebNN | Direct WebNN triangular; k must be resolvable. |
+| `Unique` | Unsupported | No current lowering; missing primitive and/or data-dependent output shape. |
+| `Unsqueeze` | Direct WebNN | Direct WebNN unsqueeze; axes must be resolvable. |
+| `Where` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+| `Xor` | Direct WebNN | Direct standardized WebNN primitive; handler schema checks and WebNN operand-type limits apply. |
+
+## Additional contrib and fused operators
+
+These eight advertised names are executable but are not standard `ai.onnx` opset-26 names, so
+they are excluded from the 198-op percentages.
+
+| Operator | Status | Execution scope and WebNN limitation | Estimated perf impact |
+|----------|--------|--------------------------------------|-----------------------|
+| `GatherBlockQuantized` | Decomposed | Gathers packed rows/scales/zero points and dequantizes only the selected slice for supported 2-D axis-0 tables; other layouts fall back at conversion time. | moderate |
+| `GroupQueryAttention` | Decomposed | Static attention/cache subgraph. WebNN has no fused attention; runtime sequence metadata is ignored and softcap/local-window modes are rejected. Fused backend kernels and the portable decomposition need not be bit-identical, so an immediately following dynamic quantizer can amplify legal rounding differences. | high |
+| `MatMulBnb4` | Decomposed | Packed NF4/FP4 block path where representable; incompatible tails become dense constants, losing low-bit memory/performance benefits. | high |
+| `MatMulNBits` | Decomposed | Supported 4/8-bit constant layouts dequantize into a matmul path; g_idx is rejected. WebNN has no low-bit matmul kernel. | high |
+| `MoE` | Decomposed | All experts execute densely because WebNN has no TopK or sparse dispatch. Approximate compute overhead is num_experts/k; supported activation/fusion forms only. | high |
+| `QMoE` | Decomposed | Supported 4/8-bit expert weights feed the same dense all-expert lowering; block-layout fallbacks may dequantize at conversion time. | high |
+| `SimplifiedLayerNormalization` | Decomposed | RMS-style normalization decomposition; no matching WebNN fused primitive. | moderate |
+| `SkipSimplifiedLayerNormalization` | Decomposed | Residual/bias plus simplified normalization decomposition; no matching WebNN fused primitive. | moderate |
+
+### MoE interpretation
+
+The previous documentation called MoE impossible because **native sparse MoE execution** cannot be
+represented in standard WebNN. That limitation remains. This branch nevertheless executes the
+supported `MoE` and `QMoE` forms by evaluating every expert and blending the selected outputs.
+This is functional fallback support, not sparse-dispatch performance parity.
+
+## Reproducing the inventory
+
+```bash
+python scripts/generate_onnx_opsets.py --min 26 --max 26 -o docs/onnx-opsets
+python scripts/onnx_ops_to_csv.py model.onnx --check-webnn
+```
+
+Sources of truth: `scripts/webnn_onnx_ops.py`, the handler registry in
+`src/onnx/ops/mod.rs`, handler restrictions in `src/onnx/ops/*.rs`, and the opset gate in
+`src/onnx/convert.rs`. Numerical model results are tracked separately in
+[model-validation-status.md](./model-validation-status.md).
+
+## References
+
+- [W3C WebNN specification](https://www.w3.org/TR/webnn/)
+- [ONNX operator schemas](https://github.com/onnx/onnx/blob/main/docs/Operators.md)
+- [ONNX Runtime MoE/QMoE notes](https://github.com/microsoft/onnxruntime/blob/main/docs/contrib_ops/cuda/moe_qmoe.md)
diff --git a/docs/real-weight-validation-failures.md b/docs/real-weight-validation-failures.md
new file mode 100644
index 0000000..87501e5
--- /dev/null
+++ b/docs/real-weight-validation-failures.md
@@ -0,0 +1,118 @@
+# Real-weight validation failures
+
+> **Last edited:** `2026-09-17T16:52:20Z`
+> **Checkout:** `fkrall/cache-backed-validation` at `25ea94c`
+>
+> **Freshness:** Use this document only when this provenance is recent relative to the relevant
+> code and commits; otherwise verify the implementation, tests, and Git history before relying
+> on it.
+
+This document is the current triage record for failures from full-model validation with publisher
+weights. Generated-weight failures are intentionally out of scope. The summary and complete case
+ledger remain in [Full-model numerical validation status](model-validation-status.md).
+
+## Recorded run
+
+- Date: 2026-09-17
+- Tested executable: onnx2webnn `25ea94c`
+- RustNN: `28fb3bbe`
+- Manifest: `tests/models/manifest.json` (51 current cases; verified within a completed 52-case superset)
+- Runtime: CPU ONNX Runtime 1.29.0
+- Cache state: complete; no model downloads or skips
+- Disk guard: minimum observed free space 78.7 GiB; stop threshold 15 GiB was not approached
+- Result for the current manifest: 47 passed and 4 failed
+- Recorded 52-case superset wall time: 7m 2.3s, one validation worker; peak RSS 26.4 GiB
+
+```bash
+ORT_DYLIB_PATH=../rustnn/target/onnxruntime/onnxruntime-linux-x64-1.29.0/lib/libonnxruntime.so.1.29.0 \
+ target/release/onnx2webnn validate-models \
+ --selection all --weights real --jobs 1
+```
+
+The current manifest is the tested 52-case run minus the passing FP32 Tiny RoFormer case; every
+retained case was executed in that run.
+
+The recorded result for each case is its first blocker. A case that fails before comparison may
+contain further converter, serialization, reload, execution, or numerical issues that are not yet
+observable.
+
+## Failure groups
+
+| Code | Cases | Furthest stage | Classification | Current ownership |
+|------|------:|----------------|----------------|-------------------|
+| U1 | 1 | Output comparison | Unsupported strict fused/decomposed attention equivalence across dynamic quantization boundaries | FastVLM prefill remains blocked; its decode specialization passes. |
+| Q1 | 1 | Output comparison | Unsupported `MatMulNBits accuracy_level=4` execution semantics | Requires a WebNN/backend mechanism for Int8-quantized activations with packed q4 weights; do not relax tolerance to hide it. |
+| O1 | 2 | Native ORT model load | The publisher ONNX is rejected before conversion can be compared | Blocked upstream pending a corrected publisher artifact. |
+
+The real sweep has no deterministic-input or cached-interface failures. U1 and Q1 both reach
+comparison, but the source and reconstructed graphs do not promise the same numerical execution
+mode. O1 fails in the reference model itself.
+
+## U1: precision-sensitive fused attention followed by dynamic quantization
+
+| # | Case | Comparison diagnostics |
+|--:|------|------------------------|
+| 11 | `onnx-community--FastVLM-0.5B-ONNX :: decoder_model_merged_quantized.onnx` (`sequence=64`, `past=0`) | `9,702,133 / 9,705,344` logits exceed tolerance; maximum absolute error `10.0572`, mean absolute error `0.760946`, RMSE `1.05325`. |
+
+Boundary probes rule out a shape, rotary, cache-layout, or serialization defect. Layer 0 query
+projection, external `RotaryEmbedding`, decomposed `GroupQueryAttention`, normalization, and MLP
+all pass the normal Float32 envelope. Layer 1 GQA output also remains within that envelope, but
+the immediately following `DynamicQuantizeLinear` makes the small difference discrete: the output
+projection is the first failing observed boundary (`825 / 57,344` values, maximum absolute error
+`0.001003`). Repeating this pattern through 24 layers produces the final logit disagreement above.
+
+ORT 1.29 CPU GQA has multiple valid kernels. With its default tiled flash-attention kernel the
+first native logit is `0.669124`; setting `ORT_GQA_DISABLE_FLASH_ATTENTION=1` selects ORT non-flash
+fallback and changes that same native logit to `1.066261`. The decomposed WebNN result is stable at
+`1.211192`. The source backend therefore materially disagrees with itself after repeated
+quantization when only its legal attention kernel changes.
+
+WebNN has no fused GQA primitive, and a decomposition into `matmul`, mask, `softmax`, and `matmul`
+cannot guarantee bit-identical rounding to either backend-specific fused kernel. Weakening the
+final tolerance would hide different Uint8 quantization decisions rather than accommodate ordinary
+output accumulation error. This fixed prefill specialization is therefore recorded as unsupported
+for strict cross-backend numerical validation; the sequence-1 decode specialization passes.
+
+## Q1: unsupported `MatMulNBits accuracy_level=4`
+
+Voxtral's decoder sets `accuracy_level=4` on all 211 `MatMulNBits` nodes. Native ORT may quantize
+Float32 activations to Int8 internally in that mode. WebNN has no fused low-bit matmul operator, so
+onnx2webnn emits packed Uint4 weights followed by `dequantizeLinear` and ordinary Float32 matmul.
+The two paths therefore do not promise the same numerical algorithm.
+
+This is not a packed-weight serialization problem. All 211 source weight payloads matched their
+saved q4 tensors byte-for-byte by SHA-256; archive marker, U8 storage shape, nibble ordering, and
+generated `0x88` zero points were also correct. Direct output analysis found mean absolute error
+`0.0401`, maximum error `0.367`, and correlation `0.999502`. Changing only the source attributes
+to `accuracy_level=0` reduced those figures to `9.66e-6`, `9.32e-5`, and `0.99999999997`.
+
+This document records case 28 as Q1. Both `all` and `match` attempt it normally, so a future
+implementation becomes visible. It is not counted as numerically supported.
+
+## O1: source ONNX rejected by native ORT
+
+Both Chronos files fail during native ORT model loading. A `Gather` indices input is the float
+output of `ConstantOfShape`; Gather indices must be integer.
+
+| # | Case |
+|--:|------|
+| 45 | `kashif--chronos-2-onnx :: encoder_model.onnx` |
+| 46 | `kashif--chronos-2-onnx :: decoder_model_merged.onnx` |
+
+The two manifest paths resolve to the same invalid publisher artifact. This document records them
+as O1; `all` and `match` still attempt them normally, so a future publisher correction will be
+visible. Until the source model runs in native ORT, these cases cannot
+provide a numerical oracle and are not evidence for or against onnx2webnn correctness.
+
+## Repair order
+
+1. Track U1 until WebNN or a backend-specific fusion can provide comparable attention precision
+ across the dynamic-quantization boundaries.
+2. Track Q1 until WebNN or a backend-specific fusion can express Int8 activations with packed q4 weights.
+3. Monitor the upstream Chronos repository for corrected reference exports.
+
+## Maintenance rule
+
+This file describes only the latest complete real-weight run. Replace counts, cases, diagnoses,
+and exact errors when a new run changes first blockers; do not append obsolete full failure tables.
+Record revision-attributed coverage changes in the main status file's history section.
diff --git a/tests/models/UNSUPPORTED_OPS.md b/docs/transformersjs_excluded_models.md
similarity index 74%
rename from tests/models/UNSUPPORTED_OPS.md
rename to docs/transformersjs_excluded_models.md
index f25f0fa..f2aff0f 100644
--- a/tests/models/UNSUPPORTED_OPS.md
+++ b/docs/transformersjs_excluded_models.md
@@ -1,8 +1,27 @@
-# Unsupported ONNX Operators by Hugging Face Repository
-
-This document records operators currently unsupported by `onnx2webnn`, grouped by Hugging Face repository.
-
-`scripts/generate_manifest.py` also reads it when regenerating `manifest.json`: a `## [org/repo](https://huggingface.co/org/repo)` section keeps that repo out of the sweep, and backticked `onnx/*.onnx` references inside the section narrow the exclusion to those components (the rest of the repo stays in).
+# Transformers.js Excluded Models
+
+> **Last edited:** `2026-09-23T11:35:58Z`
+> **Checkout:** `fkrall/cache-backed-validation` at `ac6d5ca`
+>
+> **Freshness:** Use this document only when this provenance is recent relative to the relevant
+> code and commits; otherwise verify the implementation, tests, and Git history before relying
+> on it.
+
+This document records model exports currently excluded from onnx2webnn's generated sweep because
+of unsupported operators, unsupported operator forms, unresolved dimensions, or conversion defects.
+For the complete operator-level capability matrix, see
+[Operator conversion status](operator-conversion-status.md).
+
+The [`scripts/generate_manifest.py` generator](../scripts/generate_manifest.py) reads this document
+when regenerating [`tests/models/manifest.json`](../tests/models/manifest.json). A
+`## [org/repo](https://huggingface.co/org/repo)` section identifies a repository. Backticked
+`onnx/*.onnx` references exclude only those component stems; a section without any such
+reference excludes the whole repository. The explanatory operator and error text is not parsed.
+
+An exclusions-disabled skeleton audit on 23 September 2026 found no listed component that became
+fully supported on the current branch. Generated cases retained a blocker, except that NLLB's
+decode/cache branch passed while its prefill branch still failed. Entries requiring unresolved or
+model-specific realistic dimensions were not fully re-audited and retain their previous diagnosis.
## [onnx-community/dpt-dinov2-small-kitti](https://huggingface.co/onnx-community/dpt-dinov2-small-kitti)
@@ -87,4 +106,6 @@ This document records operators currently unsupported by `onnx2webnn`, grouped b
## [Xenova/nllb-200-distilled-600M](https://huggingface.co/Xenova/nllb-200-distilled-600M)
-- `onnx/decoder_model_merged*.onnx` — the shared output-embedding bias reshape mis-derives its target shape from `decoder_sequence_length` instead of the vocab size, producing an incompatible broadcast (`decoder_sequence_length` vs. 256206); open (encoder_model converts)
+- `onnx/decoder_model_merged*.onnx` — the shared output-embedding bias reshape mis-derives its target shape from `decoder_sequence_length` instead of the vocab size, producing an incompatible broadcast (`decoder_sequence_length` vs. 256206); open. The decode/cache
+ branch builds, but the prefill branch still fails, so the component remains excluded
+ (encoder_model converts).
diff --git a/scripts/generate_manifest.py b/scripts/generate_manifest.py
index db03698..cf73d47 100644
--- a/scripts/generate_manifest.py
+++ b/scripts/generate_manifest.py
@@ -9,7 +9,7 @@
`transformers.js` (--top N for more per task; the ranking is fetched from
the Hub and cached under target/, --refresh-ranking re-fetches), minus
repos and components documented as unconvertible in
- tests/models/UNSUPPORTED_OPS.md. Tasks with nothing to convert
+ docs/transformersjs_excluded_models.md. Tasks with nothing to convert
(`(tokenizer-only)`) are skipped; models without onnx/*.onnx files are
reported.
2. Which files: what transformers.js itself loads for that repo -- the
@@ -69,7 +69,7 @@
SCRIPT_DIR = Path(__file__).resolve().parent
REPO_ROOT = SCRIPT_DIR.parent
DEFAULT_MANIFEST = REPO_ROOT / "tests" / "models" / "manifest.json"
-DEFAULT_UNSUPPORTED_FILE = REPO_ROOT / "tests" / "models" / "UNSUPPORTED_OPS.md"
+DEFAULT_UNSUPPORTED_FILE = REPO_ROOT / "docs" / "transformersjs_excluded_models.md"
DEFAULT_OVERRIDES = REPO_ROOT / "tests" / "models" / "manifest-overrides.json"
# target/ is gitignored: the ranking snapshot and the review copy live there.
DEFAULT_MODELS_JSON = REPO_ROOT / "target" / "transformers-js-models.json"
@@ -277,7 +277,7 @@ def select_repos(models: list[dict], top: int, only: list[str]) -> list[dict]:
return picked
-# ---- UNSUPPORTED_OPS.md: repos and components known not to convert ----
+# ---- transformersjs_excluded_models.md: repos and components known not to convert ----
SECTION_RE = re.compile(r"^## \[[^\]]+\]\(https://huggingface\.co/([^)]+)\)\s*$", re.MULTILINE)
ONNX_REF_RE = re.compile(r"`onnx/([A-Za-z0-9_]+?)\*?(?:\.onnx)?`")
@@ -799,7 +799,7 @@ def __init__(self, existing: list[dict]):
for name, value in (e.get("override_dims") or {}).items():
if self._branch_invariant(name):
self.repo_dims.setdefault(repo, {}).setdefault(name, value)
- extra = {k: v for k, v in e.items() if k not in ("file", "heavy", "pin_inputs", "override_dims")}
+ extra = {k: v for k, v in e.items() if k not in ("file", "heavy", "pin_inputs", "override_dims", "validation")}
if extra:
self.manual_fields[key] = extra
diff --git a/scripts/test_generate_manifest.py b/scripts/test_generate_manifest.py
new file mode 100755
index 0000000..9123f26
--- /dev/null
+++ b/scripts/test_generate_manifest.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+
+import sys
+import types
+import unittest
+
+# This focused test does not use ONNX.
+sys.modules.setdefault("onnx", types.ModuleType("onnx"))
+
+import generate_manifest as manifest
+
+
+class LegacyValidationMetadataTests(unittest.TestCase):
+ def test_validation_metadata_is_not_preserved(self):
+ file = "org--repo/onnx/model.onnx"
+ baselines = manifest.Baselines(
+ [
+ {
+ "file": file,
+ "validation": {"tier": "blocked", "reason": "legacy"},
+ "coreml_slow": "preserve unrelated metadata",
+ }
+ ]
+ )
+
+ self.assertEqual(
+ baselines.manual_fields[(file, ())],
+ {"coreml_slow": "preserve unrelated metadata"},
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/src/cache.rs b/src/cache.rs
new file mode 100644
index 0000000..72aeb80
--- /dev/null
+++ b/src/cache.rs
@@ -0,0 +1,91 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+//! Platform-appropriate cache locations shared by the CLI and model validator.
+
+use std::path::PathBuf;
+
+const APPLICATION_DIR: &str = "onnx2webnn";
+
+/// Return the directory for cached source ONNX models.
+///
+/// `O2W_ONNX_CACHE` overrides only this cache. `O2W_CACHE_DIR` relocates all
+/// onnx2webnn caches while retaining their `onnx`/`webnn` subdirectories.
+pub fn onnx_cache_dir() -> Result {
+ cache_dir("O2W_ONNX_CACHE", "onnx")
+}
+
+/// Return the directory for exported WebNN graphs and Safetensors archives.
+///
+/// `O2W_WEBNN_CACHE` overrides only this cache. `O2W_CACHE_DIR` relocates all
+/// onnx2webnn caches while retaining their `onnx`/`webnn` subdirectories.
+pub fn webnn_cache_dir() -> Result {
+ cache_dir("O2W_WEBNN_CACHE", "webnn")
+}
+
+fn cache_dir(specific_env: &str, kind: &str) -> Result {
+ resolve_cache_dir(
+ nonempty_env(specific_env),
+ nonempty_env("O2W_CACHE_DIR"),
+ dirs::cache_dir(),
+ kind,
+ )
+ .ok_or_else(|| {
+ format!(
+ "cannot determine the operating-system cache directory; set {specific_env} or O2W_CACHE_DIR"
+ )
+ })
+}
+
+fn nonempty_env(name: &str) -> Option {
+ std::env::var_os(name)
+ .filter(|value| !value.is_empty())
+ .map(PathBuf::from)
+}
+
+fn resolve_cache_dir(
+ specific: Option,
+ shared: Option,
+ os_cache: Option,
+ kind: &str,
+) -> Option {
+ specific.or_else(|| {
+ shared
+ .or_else(|| os_cache.map(|root| root.join(APPLICATION_DIR)))
+ .map(|root| root.join(kind))
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::path::Path;
+
+ #[test]
+ fn cache_path_precedence_is_specific_shared_then_os() {
+ let specific = PathBuf::from("/specific");
+ let shared = PathBuf::from("/shared");
+ let os = PathBuf::from("/os-cache");
+
+ assert_eq!(
+ resolve_cache_dir(
+ Some(specific.clone()),
+ Some(shared.clone()),
+ Some(os.clone()),
+ "onnx"
+ ),
+ Some(specific)
+ );
+ assert_eq!(
+ resolve_cache_dir(None, Some(shared), Some(os.clone()), "onnx"),
+ Some(Path::new("/shared/onnx").to_path_buf())
+ );
+ assert_eq!(
+ resolve_cache_dir(None, None, Some(os), "webnn"),
+ Some(Path::new("/os-cache/onnx2webnn/webnn").to_path_buf())
+ );
+ assert_eq!(resolve_cache_dir(None, None, None, "onnx"), None);
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 001302d..1571d5e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,13 +4,20 @@
* SPDX-License-Identifier: Apache-2.0
*/
+pub mod cache;
pub mod debug;
+pub mod model_validation;
pub mod protos;
+pub mod validation;
pub mod onnx;
pub use onnx::convert::{
- convert_model_proto, convert_onnx, ConvertOptions, OnnxError, UnsupportedOpEntry,
- ValidatedGraph,
+ cache_onnx_model, convert_model_proto, convert_onnx, ConvertOptions, OnnxError,
+ UnsupportedOpEntry, ValidatedGraph,
};
pub use onnx::test_models;
+pub use validation::{
+ validate_cached_model, validate_cached_model_with_options,
+ validate_cached_model_with_overrides, ValidationSummary,
+};
diff --git a/src/main.rs b/src/main.rs
index bdcf2fe..40af7f9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -18,10 +18,12 @@
use clap::{Parser, Subcommand};
use std::collections::HashMap;
-use std::path::Path;
+use std::path::{Path, PathBuf};
-use onnx2webnn::convert_onnx;
+use onnx2webnn::model_validation::manifest::Selection;
+use onnx2webnn::model_validation::{run_manifest_validation, RunOptions, WeightMode};
use onnx2webnn::ConvertOptions;
+use onnx2webnn::{cache_onnx_model, convert_onnx, validate_cached_model_with_overrides};
#[derive(Parser)]
#[command(name = "onnx2webnn")]
@@ -71,9 +73,66 @@ enum Command {
/// Preserve unresolved symbolic input dims as dynamic metadata (experimental)
#[arg(long)]
experimental_dynamic_inputs: bool,
+
+ /// Save the converted graph and self-contained ONNX model to the OS cache directory
+ #[arg(long)]
+ output: bool,
+
+ /// Reload cached WebNN artifacts, dispatch deterministic inputs, and compare with native ORT
+ #[arg(long, requires = "output")]
+ validate: bool,
+
+ /// Validate existing cache artifacts only; does not read or convert the source ONNX.
+ #[arg(long, conflicts_with_all = ["validate", "output"])]
+ validate_cached: bool,
+ },
+
+ /// Numerically validate manifest models against reloaded WebNN artifacts
+ ValidateModels {
+ /// Manifest selection: all or match=
+ #[arg(long)]
+ selection: String,
+
+ /// Weight source. Only publisher weights are currently supported.
+ #[arg(long, default_value_t = WeightMode::Real)]
+ weights: WeightMode,
+
+ /// Override tests/models/manifest.json
+ #[arg(long)]
+ manifest: Option,
+
+ /// Number of parallel light-model workers; heavy models remain sequential
+ #[arg(long, default_value_t = 1)]
+ jobs: usize,
},
}
+fn cache_paths(input: &Path) -> anyhow::Result<(PathBuf, PathBuf)> {
+ let canonical = input.canonicalize().unwrap_or_else(|_| input.to_path_buf());
+ let mut hash = 0xcbf29ce484222325u64;
+ for byte in canonical.to_string_lossy().bytes() {
+ hash ^= u64::from(byte);
+ hash = hash.wrapping_mul(0x100000001b3);
+ }
+ let stem = input
+ .file_stem()
+ .and_then(|value| value.to_str())
+ .unwrap_or("model");
+ let safe_stem: String = stem
+ .chars()
+ .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
+ .collect();
+ let key = format!("{safe_stem}-{hash:016x}");
+ Ok((
+ onnx2webnn::cache::onnx_cache_dir()
+ .map_err(anyhow::Error::msg)?
+ .join(format!("{key}.onnx")),
+ onnx2webnn::cache::webnn_cache_dir()
+ .map_err(anyhow::Error::msg)?
+ .join(format!("{key}.webnn")),
+ ))
+}
+
fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
if cli.debug {
@@ -89,6 +148,9 @@ fn main() -> anyhow::Result<()> {
allow_missing_external_data,
optimize,
experimental_dynamic_inputs,
+ output,
+ validate,
+ validate_cached,
} => {
let mut free_dim_overrides = if let Some(path) = override_dims_file {
let content = std::fs::read_to_string(&path)?;
@@ -144,18 +206,86 @@ fn main() -> anyhow::Result<()> {
pinned_inputs.insert(name, value);
}
+ let validation_overrides = free_dim_overrides.clone();
+ if validate_cached {
+ let (onnx_cache, webnn_cache) = cache_paths(input_path)?;
+ if !onnx_cache.exists() || !webnn_cache.exists() {
+ return Err(anyhow::anyhow!(
+ "cached validation requires {} and {}; run with --output first",
+ onnx_cache.display(),
+ webnn_cache.display()
+ ));
+ }
+ let summary = validate_cached_model_with_overrides(
+ &onnx_cache,
+ &webnn_cache,
+ &validation_overrides,
+ )
+ .map_err(|e| anyhow::anyhow!("{e}"))?;
+ println!(
+ "✓ cached deterministic validation passed ({} inputs, {} outputs)",
+ summary.input_count, summary.output_count
+ );
+ return Ok(());
+ }
+
+ let cache_paths = output.then(|| cache_paths(input_path)).transpose()?;
+ if let Some((onnx_cache, _)) = &cache_paths {
+ cache_onnx_model(input_path, onnx_cache, allow_missing_external_data)
+ .map_err(|e| anyhow::anyhow!("{e}"))?;
+ }
+
let options = ConvertOptions {
free_dim_overrides,
optimize,
experimental_dynamic_inputs,
pinned_inputs,
zero_fill_missing_external_data: allow_missing_external_data,
+ output_path: cache_paths.as_ref().map(|(_, webnn)| webnn.clone()),
};
let _validated = convert_onnx(input_path.to_str().unwrap(), options)
.map_err(|e| anyhow::anyhow!("{e}"))?;
// stdout (not stderr): PowerShell treats native stderr as NativeCommandError
println!("✓ ORT graph build succeeded for {}", input);
+ if let Some((onnx_cache, webnn_cache)) = &cache_paths {
+ println!("✓ cached ONNX at {}", onnx_cache.display());
+ println!("✓ cached WebNN at {}", webnn_cache.display());
+ if validate {
+ let summary = validate_cached_model_with_overrides(
+ onnx_cache,
+ webnn_cache,
+ &validation_overrides,
+ )
+ .map_err(|e| anyhow::anyhow!("{e}"))?;
+ println!(
+ "✓ deterministic validation passed ({} inputs, {} outputs)",
+ summary.input_count, summary.output_count
+ );
+ }
+ }
+ }
+ Command::ValidateModels {
+ selection,
+ weights,
+ manifest,
+ jobs,
+ } => {
+ let selection = Selection::parse(&selection).map_err(anyhow::Error::msg)?;
+ let mut options = RunOptions::new(selection, weights).map_err(anyhow::Error::msg)?;
+ options.jobs = jobs;
+ if let Some(manifest) = manifest {
+ options.manifest = manifest;
+ }
+ let summary = run_manifest_validation(options).map_err(anyhow::Error::msg)?;
+ println!("{summary}");
+ if summary.has_failures() {
+ return Err(anyhow::anyhow!(
+ "{} of {} model validations failed",
+ summary.failed.len(),
+ summary.selected
+ ));
+ }
}
}
diff --git a/src/model_validation/full_model.rs b/src/model_validation/full_model.rs
new file mode 100644
index 0000000..e8767c1
--- /dev/null
+++ b/src/model_validation/full_model.rs
@@ -0,0 +1,538 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+//! Full Hugging Face model downloads for the manual numerical sweep.
+
+use super::manifest::Entry;
+use crate::protos::onnx::{GraphProto, ModelProto};
+use huggingface_hub::{HFClient, HFClientSync, HFRepositorySync, RepoDownloadFileParams};
+use prost::Message;
+use serde::{Deserialize, Serialize};
+use sha2::{Digest, Sha256};
+use std::collections::BTreeSet;
+use std::fs;
+use std::io::Read;
+use std::path::{Component, Path, PathBuf};
+
+const CACHE_FORMAT: u32 = 3;
+const METADATA_DIR: &str = ".onnx2webnn-validation";
+
+#[derive(Debug, Deserialize, Serialize)]
+struct CacheMetadata {
+ format: u32,
+ repository: String,
+ revision: String,
+ sha256: Option,
+ primary: String,
+ files: Vec,
+}
+
+#[derive(Debug, Deserialize, Serialize)]
+struct CachedFile {
+ path: String,
+ length: u64,
+}
+
+pub fn cache_root() -> Result {
+ resolve_cache_root(
+ nonempty_env("O2W_ONNX_CACHE"),
+ nonempty_env("O2W_CACHE_DIR"),
+ nonempty_env("HF_HUB_CACHE"),
+ nonempty_env("HUGGINGFACE_HUB_CACHE"),
+ nonempty_env("HF_HOME"),
+ dirs::cache_dir(),
+ )
+ .ok_or_else(|| {
+ "cannot determine the Hugging Face cache directory; set O2W_ONNX_CACHE or HF_HUB_CACHE"
+ .to_string()
+ })
+}
+
+fn resolve_cache_root(
+ o2w_onnx: Option,
+ o2w_shared: Option,
+ hf_hub: Option,
+ legacy_hf_hub: Option,
+ hf_home: Option,
+ os_cache: Option,
+) -> Option {
+ o2w_onnx
+ .or_else(|| o2w_shared.map(|path| path.join("onnx")))
+ .or(hf_hub)
+ .or(legacy_hf_hub)
+ .or_else(|| hf_home.map(|path| path.join("hub")))
+ .or_else(|| os_cache.map(|path| path.join("huggingface").join("hub")))
+}
+
+fn nonempty_env(name: &str) -> Option {
+ std::env::var_os(name)
+ .filter(|value| !value.is_empty())
+ .map(PathBuf::from)
+}
+
+pub fn cache_full_model(entry: &Entry) -> Result {
+ let cache_root = cache_root()?;
+ fs::create_dir_all(&cache_root).map_err(|e| format!("create {}: {e}", cache_root.display()))?;
+ let revision = entry.revision();
+ let (repository, repository_path) = parse_manifest_file(&entry.file)?;
+ let (owner, name) = repository
+ .split_once('/')
+ .ok_or_else(|| format!("invalid Hugging Face repository {repository:?}"))?;
+ let client = HFClient::builder()
+ .cache_dir(&cache_root)
+ .build()
+ .map_err(|e| format!("create Hugging Face client: {e}"))?;
+ let client = HFClientSync::from_api(client)
+ .map_err(|e| format!("create blocking Hugging Face client: {e}"))?;
+ let repository_client = client.model(owner, name);
+ let metadata_path = metadata_path(&cache_root, entry);
+ let refresh = std::env::var_os("O2W_MODEL_CACHE_REFRESH").is_some();
+
+ if !refresh {
+ if let Some(model_path) = complete_cache(
+ &repository_client,
+ &metadata_path,
+ &repository,
+ revision,
+ entry.sha256.as_deref(),
+ )? {
+ return Ok(model_path);
+ }
+ }
+
+ if metadata_path.exists() {
+ fs::remove_file(&metadata_path)
+ .map_err(|e| format!("remove stale {}: {e}", metadata_path.display()))?;
+ }
+
+ let mut model_path = download_hub_file(
+ &repository_client,
+ &repository_path,
+ revision,
+ refresh,
+ false,
+ )?;
+ if let Some(expected) = entry.sha256.as_deref() {
+ if let Err(error) = verify_sha256(&model_path, expected) {
+ if refresh {
+ return Err(error);
+ }
+ model_path =
+ download_hub_file(&repository_client, &repository_path, revision, true, false)?;
+ verify_sha256(&model_path, expected)?;
+ }
+ }
+
+ let model_bytes =
+ fs::read(&model_path).map_err(|e| format!("read {}: {e}", model_path.display()))?;
+ let model = ModelProto::decode(model_bytes.as_slice())
+ .map_err(|e| format!("decode {}: {e}", model_path.display()))?;
+ let locations = external_locations(&model)?;
+ let repository_parent = Path::new(&repository_path)
+ .parent()
+ .unwrap_or_else(|| Path::new(""));
+ let mut files = vec![cached_file(&repository_path, &model_path)?];
+ for location in locations {
+ let repository_sidecar = repository_parent.join(&location);
+ let repository_sidecar = path_for_url(&repository_sidecar)?;
+ let sidecar_path = download_hub_file(
+ &repository_client,
+ &repository_sidecar,
+ revision,
+ refresh,
+ false,
+ )?;
+ files.push(cached_file(&repository_sidecar, &sidecar_path)?);
+ }
+
+ let metadata = CacheMetadata {
+ format: CACHE_FORMAT,
+ repository,
+ revision: revision.to_string(),
+ sha256: entry.sha256.clone(),
+ primary: repository_path,
+ files,
+ };
+ write_metadata(&metadata_path, &metadata)?;
+ Ok(model_path)
+}
+
+fn download_hub_file(
+ repository: &HFRepositorySync,
+ path: &str,
+ revision: &str,
+ force_download: bool,
+ local_files_only: bool,
+) -> Result {
+ repository
+ .download_file(
+ &RepoDownloadFileParams::builder()
+ .filename(path)
+ .revision(revision)
+ .force_download(force_download)
+ .local_files_only(local_files_only)
+ .build(),
+ )
+ .map_err(|e| format!("download {path} at revision {revision}: {e}"))
+}
+
+fn cached_file(repository_path: &str, local_path: &Path) -> Result {
+ let length = fs::metadata(local_path)
+ .map_err(|e| format!("inspect {}: {e}", local_path.display()))?
+ .len();
+ Ok(CachedFile {
+ path: repository_path.to_string(),
+ length,
+ })
+}
+
+fn parse_manifest_file(file: &str) -> Result<(String, String), String> {
+ let (org_repo, relative) = file
+ .split_once('/')
+ .ok_or_else(|| format!("{file}: expected --/"))?;
+ let (org, repo) = org_repo
+ .split_once("--")
+ .ok_or_else(|| format!("{file}: expected --/"))?;
+ if org.is_empty() || repo.is_empty() {
+ return Err(format!(
+ "{file}: empty Hugging Face organization or repository"
+ ));
+ }
+ let relative = path_for_url(&safe_relative_path(relative)?)?;
+ Ok((format!("{org}/{repo}"), relative))
+}
+
+fn safe_relative_path(path: impl AsRef) -> Result {
+ let path = path.as_ref();
+ if path.as_os_str().is_empty() || path.is_absolute() {
+ return Err(format!("unsafe external-data path '{}'", path.display()));
+ }
+ let mut clean = PathBuf::new();
+ for component in path.components() {
+ match component {
+ Component::Normal(part) => clean.push(part),
+ _ => return Err(format!("unsafe external-data path '{}'", path.display())),
+ }
+ }
+ Ok(clean)
+}
+
+fn path_for_url(path: &Path) -> Result {
+ let safe = safe_relative_path(path)?;
+ Ok(safe
+ .components()
+ .map(|component| component.as_os_str().to_string_lossy())
+ .collect::>()
+ .join("/"))
+}
+
+fn metadata_path(cache_root: &Path, entry: &Entry) -> PathBuf {
+ let digest = Sha256::digest(entry.source_key().as_bytes());
+ let key = digest
+ .iter()
+ .map(|byte| format!("{byte:02x}"))
+ .collect::();
+ cache_root
+ .join(METADATA_DIR)
+ .join(format!("{key}.complete.json"))
+}
+
+fn complete_cache(
+ repository_client: &HFRepositorySync,
+ metadata_path: &Path,
+ repository: &str,
+ revision: &str,
+ expected_sha256: Option<&str>,
+) -> Result