[Web] ort.min.js loads the JSEP wasm for executionProviders: ['wasm'], and WebKit then uses 5-7 GB (still in 1.30.0, follow-up to #26827) #33516
Description
Describe the issue
With the default bundle ort.min.js, every release from 1.21.0 (checked up to 1.30.0) loads ort-wasm-simd-threaded.jsep.wasm, even when the session only asks for the CPU wasm execution provider. In WebKit, the web content process then needs 5.6-6.0 GB for one session.run() of a 36 MB model, and memory keeps growing after the run. In longer runs it reached 12 GB and once crashed the page.
The same release with the WASM-only bundle ort.wasm.min.js, which loads the plain ort-wasm-simd-threaded.wasm, stays at 0.9-1.0 GB. That's also what 1.20.1 uses, since 1.20.1's ort.min.js still loads the plain build. Chromium and Firefox are unaffected with either build.
This looks like the problem in #26827, which was closed by the stale bot in July without a fix and is still getting reports. Comments there point to WebKit's OMG JIT compiling the large JSEP functions, and link WebKit bug https://bugs.webkit.org/show_bug.cgi?id=304810. Whatever WebKit does, there may be a fix on the ONNX Runtime side: ort.min.js doesn't need to load the JSEP build when no JSEP execution provider is requested. Today, Safari users only avoid it if they know to switch to ort.wasm.min.js, or to stay on 1.20.x as several people in #26827 did.
Measurements, all from the reproduction page below (headless, Linux), peak RSS of the largest browser process:
| Engine | Release + bundle | WASM build loaded | During load + run | 5 s after |
|---|---|---|---|---|
| WebKit 18.2 | 1.30.0 ort.min.js |
.jsep.wasm |
5676 MB | 6043 MB |
| WebKit 18.2 | 1.30.0 ort.wasm.min.js |
.wasm |
955 MB | 907 MB |
| WebKit 18.2 | 1.20.1 ort.min.js |
.wasm |
920 MB | 880 MB |
| Chromium 156 | 1.30.0 ort.min.js |
.jsep.wasm |
702 MB | 636 MB |
| Chromium 156 | 1.30.0 ort.wasm.min.js |
.wasm |
661 MB | 618 MB |
| Firefox 157 | 1.30.0 ort.min.js |
.jsep.wasm |
736 MB | 736 MB |
| Firefox 157 | 1.30.0 ort.wasm.min.js |
.wasm |
671 MB | 671 MB |
In the full application (the same model on real audio, with ort.env.wasm.proxy = true), WebKit used 4.8-7.6 GB with ort.min.js for every release I tried: 1.21.0, 1.21.1, 1.22.0, 1.24.3, 1.27.0 and 1.30.0. 1.20.1 used 1.1 GB. With ort.env.wasm.proxy = false, 1.22.0 still used 4.8 GB, so the proxy worker isn't the cause. All configurations returned the same detections.
Possible changes:
- When
executionProviderscontains only'wasm'/'cpu', haveort.min.jsload the plain WASM build instead of the JSEP build. - Otherwise, document
ort.wasm.min.js(or theonnxruntime-web/wasmexport) as the recommended bundle for CPU-only use in Safari / iOS. - Reopen [Web] Severe CPU and memory issues in Safari/WebKit 26 with JSEP mode #26827 or keep this issue open, since the problem persists in 1.30.0.
To reproduce
A single HTML file with no other code. The model (36 MB, opset 17, uses Loop and NonMaxSuppression) is public and served with Access-Control-Allow-Origin: *.
<!doctype html>
<html><head><meta charset="utf-8"></head>
<body><pre id="log"></pre>
<script>
// ?v=<onnxruntime-web version>&bundle=<ort.min.js | ort.wasm.min.js>
const params = new URLSearchParams(location.search);
const version = params.get("v") || "1.30.0";
const bundle = params.get("bundle") || "ort.min.js";
const MODEL_URL =
"https://raw.githubusercontent.com/birdnet-team/BirdBox/main/docs/models/Just-Bird_fp16.onnx";
const log = (msg) => { document.getElementById("log").textContent += msg + "\n"; };
const script = document.createElement("script");
script.src = `https://cdn.jsdelivr.net/npm/onnxruntime-web@${version}/dist/${bundle}`;
script.onload = async () => {
ort.env.wasm.wasmPaths = `https://cdn.jsdelivr.net/npm/onnxruntime-web@${version}/dist/`;
log(`onnxruntime-web ${ort.env.versions.web}, bundle ${bundle}`);
const model = await (await fetch(MODEL_URL)).arrayBuffer();
const session = await ort.InferenceSession.create(model, { executionProviders: ["wasm"] });
// 60 s of deterministic noise at 32 kHz.
const audio = new Float32Array(60 * 32000);
let seed = 1;
for (let i = 0; i < audio.length; i += 1) {
seed = (seed * 1103515245 + 12345) >>> 0;
audio[i] = ((seed / 4294967296) - 0.5) * 0.2;
}
const t0 = performance.now();
const out = await session.run({
audio: new ort.Tensor("float32", audio, [audio.length]),
conf: new ort.Tensor("float32", Float32Array.from([0.15]), [1]),
song_gap: new ort.Tensor("float32", Float32Array.from([0.05]), [1]),
});
log(`DONE run ${((performance.now() - t0) / 1000).toFixed(1)} s, ` +
`${out[session.outputNames[0]].dims[0]} detections`);
};
document.head.appendChild(script);
</script></body></html>- Serve the file over HTTP and open it in Safari / WebKit with
?v=1.30.0&bundle=ort.min.js. - Watch the web content process (Activity Monitor on macOS). It rises to about 5.7 GB during the run, and memory doesn't come back down after
DONE. - Open it with
?v=1.30.0&bundle=ort.wasm.min.jsor?v=1.20.1&bundle=ort.min.js. The run gives the same output, and memory peaks at about 0.9-1.0 GB.
The page isn't cross-origin isolated, so the WASM backend runs single-threaded. The behavior is the same with the ort.env.wasm.proxy worker on or off.
Urgency
Not blocking for us: our browser demo now loads ort.wasm.min.js (birdnet-team/BirdBox#31). But ort.min.js is the default bundle in the docs and examples. For anyone who doesn't know about ort.wasm.min.js, it makes CPU-only inference unusable on iOS, where tabs are killed at about 1-2 GB.
Platform
Web Browser
OS Version
WebKit 18.2 (Playwright's WPE MiniBrowser build webkit_ubuntu20.04_x64_special-2092) on Ubuntu 20.04 x64. Not yet confirmed on Safari macOS/iOS by us, but #26827 reports the same pattern on Safari 26.2 (macOS, iOS).
ONNX Runtime Installation
Released Package
ONNX Runtime Version or Commit ID
1.30.0 (bisected: 1.20.1 good, 1.21.0 first bad; checked 1.21.0, 1.21.1, 1.22.0, 1.24.3, 1.27.0, 1.30.0)
ONNX Runtime API
JavaScript
Architecture
X64
Execution Provider
'wasm'/'cpu' (WebAssembly CPU)
Activity
chenyujia1024 commentedon Oct 22, 2026
Hi, I'd be interested in helping investigate this.
A possible starting point is to trace how the default
ort.min.jsbundle selects and loads the JSEP versus plain WASM artifact when onlyexecutionProviders: ['wasm']is requested, then add a regression test verifying that the JSEP artifact is not loaded on the WASM-only path.Would a narrowly scoped fix along those lines be welcome, or is the bundle composition intentional for another compatibility reason? I haven't reproduced the WebKit memory behavior locally yet.
Thanks!
qjia7 commentedon Oct 23, 2026
jsep is deprecated. Please try the wasm+webgpu ep to see whether the issue still exists. See https://github.com/microsoft/onnxruntime/blob/main/docs/JSEP_Deprecation.md
chenyujia1024 commentedon Oct 23, 2026
Thanks for the suggestion. I tested the original model with the published
onnxruntime-web1.30.0 release using both/webgpuESM bundles. The network logs confirm that they loadort-wasm-simd-threaded.asyncify.wasm, rather than the JSEP artifact.However, the original model fails during session creation, before inference can run. With
executionProviders: ['wasm'], both Chromium 149 on Windows and Playwright's Windows WebKit 26.0 report:Could not find an implementation for Cast(13) node with name 'Cast_170'With
executionProviders: ['webgpu', 'wasm'], Chromium reports that the node's provider type is unset. Windows WebKit reports that GPU is unavailable and the fallback also fails.I found a likely explanation in the v1.30.0 build configuration. The native WebGPU build applies
reduced_size_build_args, includingonnxruntime/wasm/reduced_types.config, which excludesfloat64(DOUBLE). The JSEP build does not use the same reduced-size arguments. Inspection of the original model shows thatCast_170consumes a DOUBLE constant and casts it to FLOAT.This strongly suggests a type-support gap in the native build, although I have not yet verified it using a full-type native build.
For comparison, both the default JSEP bundle and the WASM-only bundle complete inference and produce the expected output shape
[0, 8]. On Windows WebKit, their sampled peak process RSS was approximately 1.36 GB and 0.96 GB, respectively. I did not reproduce the original Linux WPE environment, so these results do not establish whether the reported 5–7 GB memory issue is fixed.Is there a supported full-type native WebGPU build I could use to test this unmodified model? Alternatively, would you prefer that I report the native build's type-support gap as a separate issue?
Thanks!
El1as42 commentedon Oct 23, 2026
Thanks Jiajia Qin (@qjia7) and CYJ (@chenyujia1024). Short version: the native WebGPU build has the same WebKit problem, I found which functions cause it, and a build-only change (
ASYNCIFY_REMOVE) brings both the JSEP and the native WebGPU build from ~6 GB down to ~1.7 GB in WebKit 18.2, with identical outputs.Native WebGPU build. We don't use WebGPU; the session asks only for
executionProviders: ['wasm']. I see the sameCast(13)failure as CYJ (@chenyujia1024) with the published 1.30.0ort.webgpu.min.js(.asyncify.wasm). Their explanation checks out: inlinux-wasm-ci-build-and-test-workflow.yml, both native WebGPU builds getreduced_size_build_args, which includeonnxruntime/wasm/reduced_types.config, and that config leaves outdouble. The plain and JSEP builds don't use it. A v1.30.0 native WebGPU build with the same flags minusreduced_size_build_argsruns the model, so I used that build for the native WebGPU numbers below. CYJ (@chenyujia1024), a separate issue for the type gap makes sense to me.Cause. WebKit's optimizing wasm tier (OMG) needs a lot of memory for some mid-size functions, which is WebKit bug 304810.
JSC_useOMGJIT=0removes the problem, and loweringJSC_maximumOMGCandidateCostto 10000 nearly does. I padded function bodies (with never-executednops) so OMG skips them, then bisected over the 271 functions of 10-40 KB in the 1.30.0jsep.wasm. Most of the excess comes from one function,onnxruntime::Graph::InferAndVerifyTypeMatch, followed by theGraphViewerconstructor; the rest is spread out. That function is 16.5 KB in the plain build, 30.6 KB in JSEP and 28.8 KB in the native WebGPU build. All of the growth is Asyncify instrumentation (~460 call sites). Whole-programASYNCIFY=1instruments every function that makes an indirect call, even though graph resolution, ONNX shape inference and protobuf parsing never reach an async import. The only async imports are the GPU download, WebNN and the external data loader.Fix. Exclude that code from Asyncify. New file
onnxruntime/wasm/asyncify_remove.txt:plus one link option in
cmake/onnxruntime_webassembly.cmake, in theASYNCIFYbranch:"SHELL:-s ASYNCIFY_REMOVE=@${ONNXRUNTIME_ROOT}/wasm/asyncify_remove.txt". The JSPI build doesn't use Asyncify and is unaffected.Own v1.30.0 builds (CI flags), peak RSS of the web content process, same reproduction page:
ASYNCIFY_REMOVEASYNCIFY_REMOVEThe code section shrinks by about 1.2 MB in both builds. Newer WebKit has mostly fixed the problem on its side, which matches CYJ (@chenyujia1024)'s 1.36 GB vs 0.96 GB on Windows WebKit 26.0. Shipping Safari 26.6.1 still reproduces it according to WebKit bug 304810, and on newer WebKit the change still removes most of the remaining gap.
Correctness. Our model gives the same detections with and without the change: 3 x 60 s in WebKit and Chromium, and a 600 s file in Chromium. On the GPU path, which is where a wrongly removed function would break (Asyncify unwind during the readback), a small MLP ran fully on WebGPU (SwiftShader in Chromium) for 20 runs, each with a GPU readback. Outputs were identical for both the JSEP and the native WebGPU build, and the counted
mapAsync/pipeline calls show the GPU really ran. Not tested: WebNN (not available headless), models with external data, and real Safari. Caveat: the WebKit numbers are Playwright's WPE builds on Linux x64, not Safari.I can open a PR with this against
mainif that approach is welcome. The original request still stands too:ort.min.jswith onlywasmrequested could load the plain build, or the docs could recommendonnxruntime-web/wasmfor CPU-only use on Safari.chenyujia1024 commentedon Oct 23, 2026
Thanks for the detailed investigation and for confirming the native-build failure I reported.
The full-type native build result is particularly useful because it separates the
Cast(13)/DOUBLE compatibility issue from the WebKit memory issue. TheASYNCIFY_REMOVEapproach looks promising, especially given the substantial RSS reduction reported for both JSEP and native WebGPU builds.From my side, I think this approach is worth submitting as a PR for maintainer review. The remaining validation gaps around external data, WebNN, and real Safari would be useful to document in the PR.
I'll keep the reduced-type compatibility issue separate so that it can be investigated independently of the memory optimization. Thanks again for the thorough analysis.