no available backend found is a symptom, not a diagnosis. A missing runtime asset, the wrong module MIME type, an early configuration read, or an unsupported threading setup can all prevent initialization. Use four gates: establish which bytes arrived before investigating what executed. These failures can occur locally too; a development server may simply hide some of them.
Revision, 2026-09-08: this update checks the site’s existing ONNX Runtime Web 1.20.1 bundle, runs four transfer controls and two model contract probes, and corrects the earlier claim that curl exits successfully after a timeout. Unsupported precision and speed rankings have been removed. The new synthetic tests are not a reconstruction of the original production incident.
Gate 1: A complete asset set and complete response bodies
This deployment uses ort.min.js, ort-wasm-simd-threaded.mjs, ort-wasm-simd-threaded.wasm, and isnet-anime-fp16.onnx. The observed sizes are below. The asset manifest records full filenames, SHA-256 hashes and accepted response types.
| Asset | Bytes |
|---|---|
| JavaScript entry | 446,284 |
| ESM glue | 24,618 |
| WASM binary | 11,246,032 |
| FP16 model | 88,070,593 |
The model is 88.07 MB, or about 83.99 MiB. Those units are not interchangeable. This manifest identifies the site’s files; it is not an upstream signature. Select matching runtime components from one build when upgrading, rather than verifying only the WASM file. Other builds and execution providers can require different assets; see the ONNX Runtime deployment guide.
The inspected deployment check contains curl ... || true and then prints the HTTP status and received byte count. It discards a failed exit status. It does not demonstrate that curl treats timeouts as successful transfers. HTTP 200 can arrive before a response body is interrupted. The curl manual identifies exit 18 as a partial-file transfer error and 28 as a timeout.
The companion transfer_faults.py starts a temporary loopback server. Its expected object is exactly bytes(range(100)). All four requests return HTTP 200; these are actual results from deliberately constructed fixtures, not archived incident logs.
| Case | Observed result |
|---|---|
| Partial | Announces 100 bytes, sends 25, closes. Curl exits 18. |
| Timeout | Sends 25 bytes, pauses. A one-second limit produces exit 28. |
| Wrong object | Announces and sends 25 bytes. Curl exits 0; the expected object does not match. |
| Complete | Sends all 100 bytes. Exit 0; expected length and hash match. |
Preserve the exit code, inspect the final response, and compare the body against a previously recorded, trusted reference. Computing a hash with nothing to compare it to only identifies the received bytes. A pipeline such as curl | sha256sum can also hide the download failure if only its last command’s status is checked.
The companion downloader writes a temporary file, retains normal TLS verification, permits HTTPS redirects only, and renames the file within its directory after all checks pass. It deletes that partial file and stops on failure. For production uploads, similarly stage and verify before an atomic replacement. Updating a publicly served file with rsync --inplace can expose incomplete bytes while the upload is in progress. This article update does not modify the site’s deployment script.
Gate 2: A file can exist without being executable
This runtime version loads its WASM through the corresponding .mjs module. Check a failed dynamic import for a missing file, an HTML login or error page, and an incorrect response type. Paths, CORS and CSP can also cause import failures; the error text alone does not identify the cause.
Module scripts require a JavaScript MIME type. The site’s current module download satisfies that check. Nginx’s extension mapping depends on the installed configuration, not just the version number. Inspect the final Content-Type and body instead of treating a successful HEAD request as proof. See the JavaScript modules guide.
Show the single-file Nginx example
# Example only: confirm the actual document root before use.
location = /public-downloads/anime-matting/ort-wasm-simd-threaded.mjs {
root /srv/personal-site/current;
types { text/javascript mjs; }
default_type text/javascript;
try_files $uri =404;
}
This deliberately matches one module. An inner types block replaces inherited mappings in that scope; using it for an entire static directory could affect other files. default_type is a fallback, not an override for an already matched, incorrect mapping. Check existing locations, the document root and inherited security headers before merging a rule, then run nginx -t through the normal deployment process. See Nginx types. The example does not add year-long immutable caching to a mutable filename or change site-wide headers.
Gate 3: Configuration must exist when it is read
In the inspected site source, the AI module is a dependency of the UI script, but personalSiteCharLibConfig is attached to the UI handle. A dependency can execute first. If its top-level code snapshots missing configuration and stores a boolean, assigning configuration later does not update that boolean. This complete Node example isolates the same ordering issue without WordPress or a model.
Show the complete config_order.cjs test
'use strict';
const assert = require('node:assert/strict');
const page = {};
const earlyPaths = (page.config || {}).paths || {};
const frozen = { available: Boolean(earlyPaths.runtime && earlyPaths.model) };
const lazy = {
get configured() {
const paths = (page.config || {}).paths || {};
return Boolean(paths.runtime && paths.model);
}
};
assert.equal(frozen.available, false);
assert.equal(lazy.configured, false);
page.config = { paths: { runtime: '/ort.min.js', model: '/model.onnx' } };
assert.equal(frozen.available, false);
assert.equal(lazy.configured, true);
console.log(JSON.stringify({ frozen_after_config: frozen.available,
lazy_after_config: lazy.configured, session_creation_tested: false }));
console.log('CONFIG_ORDER_OK');
The results are frozen_after_config: false and lazy_after_config: true. A getter reads current configuration; it does not prove URL availability, model compatibility or successful session creation. That is why the example calls the property configured. The site’s existing AI module already uses lazy reads and is unchanged by this update.
An alternative is wp_add_inline_script($consumer_handle, $code, 'before'): attach configuration to the script that actually consumes it, using wp_json_encode for data serialization. Placing it before the final UI script is not equivalent when a dependency consumes it earlier. Check actual execution order when async, defer or modules are involved; HTML byte offsets are not sufficient evidence. See WordPress inline scripts.
Gate 4: Threads, SIMD and isolation are separate checks
The site’s module sets ort.env.wasm.numThreads = 1 before session creation, as does the companion probe. Browser WASM multithreading requires runtime support and cross-origin isolation. SIMD is a separate capability: single-thread execution does not require disabling it, and setting a flag does not guarantee support in every browser or build. See the ONNX Runtime environment options.
A common isolation configuration combines COOP same-origin with COEP require-corp. COEP restricts cross-origin embedding that does not satisfy its requirements; whether CORS or CORP is needed depends on the request mode and resource policy. It is not a universal instruction to add the same header to every third-party file. Test fonts, login popups, analytics and advertising integrations in a separate environment before enabling isolation for a content site.
Record the page’s crossOriginIsolated value, runtime version, thread setting, execution provider, session creation outcome and first inference outcome separately. WebAssembly availability and successful downloads do not replace those last two checks. The Node probe below does not validate browser isolation or mobile compatibility.
What the current model probe actually establishes
The recorded run used an Apple M3, arm64 macOS and Node 26.4.0 with the Web runtime’s WASM execution provider, not the native CPU provider in onnxruntime-node. All four asset hashes were checked before loading. Input img is float32 [1,3,1024,1024]; output mask is float32 [1,1,1024,1024].
The inputs are constructed tensors, without image decoding or Canvas preprocessing. One is all zeros. For coordinates 0 through 1023, the second sets R=x/1023, G=y/1023 and B=(x+y)/2046 in NCHW layout. Both outputs contain 1,048,576 finite values in [0,1]. The zero fixture has maximum about 0.004766 and mean 0.0000277725; the RGB ramp has maximum about 0.004996 and mean 0.0000171765.
The raw record includes environment, input and output hashes. That run measured roughly 273 ms for session creation, 7.234 s for the first inference and 6.991 s for the second. Download and local model reading are excluded; creation includes WASM initialization. There were no repeated timing trials. These are not browser load times or a ranking of FP16, FP32 and INT8.
Both complete outputs are included, each 4,194,304 bytes after decompression, in little-endian float32. compare_outputs.py compares every value and reports bit identity separately from maximum absolute difference. Matching maxima and means do not establish bit-identical tensors. These synthetic inputs have no subjects or human labels, so they cannot establish matting accuracy, lossless FP16 conversion, correct browser preprocessing or real-user performance.
Download and reproduce
Download the ONNX deployment audit package and first run the two small tests. Python uses its standard library; curl and Node must be available. Output directories must not already exist.
python3 transfer_faults.py --output transfer-output
node config_order.cjs
# Optional: download about 95.16 MiB and run the existing model.
python3 fetch_assets.py --output assets
node probe_model.cjs assets probe-output
python3 compare_outputs.py reference probe-output
Success markers include TRANSFER_FAULTS_OK, CONFIG_ORDER_OK, ASSET_DOWNLOAD_OK, MODEL_CONTRACT_OK and OUTPUT_COMPARISON_OK. Inference working memory exceeds the model file size; use a desktop with available memory rather than a constrained phone for the Node experiment. The README describes the files and limits.
This audit establishes asset integrity, execution of two specified inputs and whether transfer failures are detected. It does not establish adequate model quality. Only the FP16 model is covered by this audit; without the original failure image, FP32/INT8 output tensors and timing logs, the old precision and speed comparison cannot be reproduced here. Keep deployment evidence separate from quality evidence before tuning the wrong part of the system.