. The session was created, inference finished, the output was fine. No exception anywhere. ORT printed two warnings and carried on:
env.wasm.numThreads is set to 4, but this will not work unless you enable crossOriginIsolated modeWebAssembly multi-threading is not supported in the current environment. Falling back to single-threading.
WebGPU on the non-isolated page was 355 ms against 359 ms on the isolated one, so only the WASM path is affected. But that's exactly the path your users land on when their browser has no GPU adapter.
The screenshot is the non-isolated page. Read the crossOriginIsolated=false line first, then the two ORT warnings under it, then the run time. It's a single run, so the number on screen won't match the median exactly; what matters is that it sits near the single-thread figure, not the 4-thread one.
Why this would have shipped
Our monitoring collects exceptions and failed requests. A console warning shows up in neither, and a 3x slowdown on one code path won't fail a functional test. The failure only exists as a performance number, and only when a header got dropped somewhere upstream. That's a bad combination for a team that deploys through infrastructure it doesn't own.
For a reference, I used ImgIng (https://imging.ai/), which ships the same ORT build, on the same M4 Mac with Chromium 149. Its responses carry COOP same-origin and COEP require-corp, which is what makes crossOriginIsolated true there. Its cutout code picks Math.min(4, hardwareConcurrency) threads only when the page is isolated and not on mobile, and sets 1 otherwise. The upscaling worker uses a slightly different formula, min(4, cores - 1), which leaves a core for the main thread. The point for me was that the code decides the thread count itself instead of asking for 4 and hoping.
What I changed
I now set the thread count from crossOriginIsolated and report the degraded case myself:
const WASM_THREADS = 4;
function configureOrtThreads(env) {
const isolated = globalThis.crossOriginIsolated === true;
env.wasm.numThreads = isolated ? WASM_THREADS : 1;
if (!isolated) {
console.error('[ort] page is not cross-origin isolated; WASM will run on 1 thread');
}
return { isolated, threads: env.wasm.numThreads };
}
I ran this on both servers. The isolated page returned { isolated: true, threads: 4 } with a clean console. The non-isolated page returned { isolated: false, threads: 1 }, and the two ORT warnings were gone, because ORT is no longer asked for something it can't do. The only line left was our own error, which is the one our monitoring can pick up (in production that console.error becomes a call to our reporter). Next on my list is a deploy check that requests the app shell and fails if either header is missing.
What I haven't measured yet is the cost on our side. require-corp blocks cross-origin images and scripts that don't opt in with CORP or CORS, and our pages load plenty of those. That audit is the real work, and none of these numbers cover it.
If you use numThreads above 1, open your production page, type crossOriginIsolated into the console, and check that it says true.
fq78zr90b8losdduf1f.png)
Top comments (1)
The require-corp cost you flagged as unmeasured is the part I'd worry about more than the thread count itself, since that's the one that can break rendering outright rather than just degrade performance quietly.
One thing worth checking on the deploy check you're planning: does it request the app shell through the same path real users hit, or straight from origin? A CDN or edge cache in front of the gateway can strip or fail to forward COOP/COEP on cached responses even when the origin server sends them correctly, so a check that curls origin directly would pass while the page users actually get is still non-isolated. Worth hitting it through the CDN hostname specifically, ideally with a cache-busting query param so you're not just reading a stale cached response that happened to carry the headers from before someone changed the config.