DEV Community

xiaoye duan
xiaoye duan

Posted on

A 130 MB WASM model on Cloudflare Pages

A 130 MB WASM model on Cloudflare Pages: cross-origin isolation, a 25 MiB wall, and headers that lie

Four failures from shipping a pthreads WASM audio model on Cloudflare Pages. Three of them produce no usable error message, and one produces the opposite of the truth.

favicon audiofilter.net

Top comments (3)

Collapse
 
raknaos profile image
Baptiste Le Bouquin

Shipping a real ML model to the browser at 130 MB is a rare feat — and the silence from the platform on failures makes it harder than the inference itself.

The pthreads + WASM combination is where it gets brutal: the errors arrive with no useful message, or worse, a message that is the opposite of what happened. I've been burned by the same thing the other direction — a worker that spawns but the main thread never hears back, so the "it works" is a lie until you actually exercise the shared memory path.

A couple of things I'd be curious about: how much of that 130 MB is the model weights and how much is compiled runtime overhead, and did COOP/COEP headers matter for the SharedArrayBuffer path on Pages, or does Cloudflare's static hosting already carry what you need? The "execute on the visitor's own CPU, no GPU bill" framing is the future I keep hoping for.

Collapse
 
xiaoye_duan_e52cefbc278d2 profile image
xiaoye duan

Thanks — and your instinct about the shared-memory path is the right one. The worker constructing successfully tells you almost nothing; mine came up fine and died the moment it tried to hand a SharedArrayBuffer across, which is how I ended up with a DataCloneError pointing at nothing in particular.

▎ The split, measured on what actually ships:

▎ | | wire (brotli) | decoded |
▎ |-----------------------------------|---------------|-----------|
▎ | Model weights | 74.7 MiB | 157.4 MiB |
▎ | Compiled runtime (wasm + JS glue) | 3.2 MiB | 14.2 MiB |
▎ | First load | 77.9 MiB | 171.6 MiB |

▎ So the runtime is 4.2% of the wire cost — weights are 95.8% of it. That surprised me too. Two things drive it:

▎ - The wasm compresses far better than the weights do: 14.16 MiB → 3.18 MiB, 22.5%. It is emscripten output — repetitive, highly structured. The four weight tensors land at 34.2%, 35.6%, 44.1% and 56.3%, and the largest one (82.04 MiB) is the worst of them at 56.3%.
▎ - There is exactly one runtime, not one per model. Three engines share it.

▎ The corollary is that runtime overhead is not the thing to optimise. I spent time on the wasm size early on and it was almost entirely wasted; the whole win was in the weights, and specifically in getting brotli applied to them at all, since Cloudflare will not compress application/octet-stream.

▎ One correction while I am here: the title says 130 MB and that number is stale — it was true of an earlier model configuration. On the current deploy the weights are 157.4 MiB decoded. I would rather post the number that is reproducible today than defend the one in the headline.

▎ On COOP/COEP: no, Pages carries none of it. You get nothing by default and you have to declare all three yourself in _headers, including Cross-Origin-Resource-Policy: cross-origin — which is looser than instinct suggests, and which most write-ups get wrong. Emscripten's pthread workers and the nested worker the engine spawns fetch their subresources from what the browser treats as an opaque origin, and same-origin blocks those.

▎ And the part that cost the most: Pages merges same-named headers from two matching rules rather than letting the more specific one win. Declaring both /prefix/

▎ cross-origin-opener-policy: same-origin, same-origin
▎ COOP is a single token, so that fails to parse and the browser silently falls back to unsafe-none. * matches the empty string, so /prefix/* already covered /prefix/ — the "safe" extra rule was the bug.

▎ Worth adding, since it bears on the isolation question: only the TTS engine needs any of this. The source-separation model is single-threaded wasm and runs without SharedArrayBuffer at all, so the isolation headers are scoped to the one path prefix that needs them. CORP: cross-origin also lets any site embed those resources, which is not something to apply site-wide by reflex.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.