Skip to content

[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

Open

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:

  1. When executionProviders contains only 'wasm'/'cpu', have ort.min.js load the plain WASM build instead of the JSEP build.
  2. Otherwise, document ort.wasm.min.js (or the onnxruntime-web/wasm export) as the recommended bundle for CPU-only use in Safari / iOS.
  3. 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>
  1. Serve the file over HTTP and open it in Safari / WebKit with ?v=1.30.0&bundle=ort.min.js.
  2. 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.
  3. Open it with ?v=1.30.0&bundle=ort.wasm.min.js or ?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

  1. added
    .NETPull requests that update .net code
    api:Javascriptissues related to the Javascript API
    platform:mobileissues related to ONNX Runtime mobile; typically submitted using template
    platform:webissues related to ONNX Runtime web; typically submitted using template
    on Oct 21, 2026
  2. chenyujia1024 commented on Oct 22, 2026

    @chenyujia1024

    Hi, I'd be interested in helping investigate this.

    A possible starting point is to trace how the default ort.min.js bundle selects and loads the JSEP versus plain WASM artifact when only executionProviders: ['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!

  3. qjia7 commented on Oct 23, 2026

    @qjia7
    Contributor

    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

  4. chenyujia1024 commented on Oct 23, 2026

    @chenyujia1024

    Thanks for the suggestion. I tested the original model with the published onnxruntime-web 1.30.0 release using both /webgpu ESM bundles. The network logs confirm that they load ort-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, including onnxruntime/wasm/reduced_types.config, which excludes float64 (DOUBLE). The JSEP build does not use the same reduced-size arguments. Inspection of the original model shows that Cast_170 consumes 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!

  5. El1as42 commented on Oct 23, 2026

    @El1as42
    Author

    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 same Cast(13) failure as CYJ (@chenyujia1024) with the published 1.30.0 ort.webgpu.min.js (.asyncify.wasm). Their explanation checks out: in linux-wasm-ci-build-and-test-workflow.yml, both native WebGPU builds get reduced_size_build_args, which include onnxruntime/wasm/reduced_types.config, and that config leaves out double. The plain and JSEP builds don't use it. A v1.30.0 native WebGPU build with the same flags minus reduced_size_build_args runs 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=0 removes the problem, and lowering JSC_maximumOMGCandidateCost to 10000 nearly does. I padded function bodies (with never-executed nops) so OMG skips them, then bisected over the 271 functions of 10-40 KB in the 1.30.0 jsep.wasm. Most of the excess comes from one function, onnxruntime::Graph::InferAndVerifyTypeMatch, followed by the GraphViewer constructor; 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-program ASYNCIFY=1 instruments 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:

    onnxruntime::Graph::*
    onnxruntime::GraphViewer::*
    onnx::*
    google::protobuf::*
    

    plus one link option in cmake/onnxruntime_webassembly.cmake, in the ASYNCIFY branch: "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:

    Build WebKit 18.2 (Linux WPE) WebKit 27.2 (Linux WPE) Chromium
    JSEP, unchanged 5.9 GB 1.56 GB 0.70 GB
    JSEP + ASYNCIFY_REMOVE 1.7 GB 1.24 GB 0.70 GB
    Native WebGPU (full types), unchanged 6.2 GB 1.49 GB -
    Native WebGPU (full types) + ASYNCIFY_REMOVE 1.7 GB 1.21 GB -
    Plain wasm (reference) 0.9 GB 0.92 GB 0.66 GB

    The 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 main if that approach is welcome. The original request still stands too: ort.min.js with only wasm requested could load the plain build, or the docs could recommend onnxruntime-web/wasm for CPU-only use on Safari.

  6. chenyujia1024 commented on Oct 23, 2026

    @chenyujia1024

    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. The ASYNCIFY_REMOVE approach 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    .NETPull requests that update .net codeapi:Javascriptissues related to the Javascript APIplatform:mobileissues related to ONNX Runtime mobile; typically submitted using templateplatform:webissues related to ONNX Runtime web; typically submitted using template

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions