Skip to content

WASM: browser support for QuantEcon.py (Numba + JupyterLite) — tracking issue #925

Description

@mmcky

Background

On 11 August 2026 QuantStack announced Numba in the Browser (Anutosh Bhat): a genuine Numba JIT running entirely in the browser. Python functions are compiled through llvmlite to WebAssembly, linked in-process with LLD, and loaded as Emscripten side modules into a JupyterLite kernel — no server anywhere. Numba is now published on emscripten-forge (numba 0.67.0 + 9 emscripten patches; llvmlite 0.49.0 + 4 patches), and the announcement showcases the economics stack built on top of it (EconForge's interpolation.py and Dolo.py, PyMC/PyTensor). QuantEcon.py is the obvious next citizen of that ecosystem, and its authors explicitly invite the wider Numba ecosystem to validate their packages.

Concretely, "browser support" for QuantEcon.py means: works in JupyterLite with the xeus-python kernel against the emscripten-forge + conda-forge channels. This is not the classic Pyodide kernel — Numba remains unavailable there (see numba/numba#3284, pyodide/pyodide-recipes#192).

Where we stand

The starting position is strong. QuantEcon.py is pure Python (noarch on conda-forge, universal wheel), every hard dependency (numba, numpy, scipy, sympy) is already packaged for the browser, and all ~90 jitted callables are nopython-mode with lazy specialisation — exactly the model the WASM engine implements. The library's widespread cache=True discipline becomes an outright asset under JupyterLite's new persistent cross-session compilation cache. The requests, urllib, and sympy imports are already lazy, so import quantecon touches nothing browser-hostile.

The constraints of the in-browser Numba build, read from the nine emscripten-forge patches and the recipe's tests:

  • The runtime is single-threaded (OpenMP and TBB disabled at build time).
  • target='parallel' ufuncs silently fall back to 'cpu' on sys.platform == "emscripten" (patch 0007), but no equivalent fallback exists for @njit(parallel=True) (the ParallelAccelerator pass) — that path is expected to fail.
  • @njit(cache=True) gains a persistent WASM object cache across browser sessions (patch 0006), but cache=True on @vectorize/@guvectorize is force-disabled on Emscripten — ufuncs recompile every session.
  • wasm32 is a 32-bit platform: pointers, np.intp, and NumPy's default integer are 32 bits; addressable memory is capped at 2–4 GB shared with the whole kernel.

Gaps

# Gap Severity
1 gini_coefficient uses @njit(parallel=True) + prange; no browser fallback exists, so it is expected to fail at first call Blocker (one function)
2 _numba_linalg_solve calls LAPACK by raw function address via the private numba.np.linalg._LAPACK API; unverified in WASM; affects game_theory.support_enumeration High risk / unverified
3 import quantecon eagerly compiles four guvectorize ufuncs plus one eagerly-signed @jit function; ufunc caching is disabled on Emscripten so the cost recurs every session UX / performance
4 32-bit intp on wasm32: overflow guards trip ~4×10⁹ times earlier, simplex_index can silently wrap, int64 dtype assertions fail Correctness at the margins

Work plan

Phase Issue Work item
0 — Prove it #928 JupyterLite proof-of-concept deployment + browser smoke suite
1 — Code fixes #926 Gate @njit(parallel=True) in gini_coefficient on Emscripten
1 #927 Verify _numba_linalg_solve / _LAPACK in the browser; fallback solver if needed
1 #929 Audit 32-bit intp behaviour: overflow guards, simplex_index wrapping, dtype-agnostic tests
1 #930 Reduce import-time eager Numba compilation
1 #931 Document browser limitations; raise helpful platform-specific errors
2 — Distribution #932 Contribute a quantecon recipe to emscripten-forge
3 — CI #933 Add a WebAssembly/JupyterLite job to CI
4 — Ecosystem #934 Demo notebook, lectures without a kernel server, WASM SIMD exploration

Sequencing: Phase 0 comes first and converts every unknown into a known — #927 is gated on its findings, and #930 is calibrated by its import-time benchmark. #926 is independent and can land immediately. #932 is best contributed after the Phase 1 fixes are in a released version.

What does not need to change

No C extensions, no build step, no subprocess/multiprocessing/threading anywhere in the runtime package, no object-mode @jit, no @jitclass, no generated_jit, no threading-layer APIs. All five runtime dependencies resolve in the browser ecosystem today, and the test tooling (pytest, pandas, coverage) is itself packaged for WASM, so browser CI is a configuration exercise rather than an infrastructure project.

Sources

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions