Skip to content

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

Description

@mmcky

Note

Updated 2026-08-21. The gap table, constraints and work plan below have been brought into line with what the sub-issues and upstream have since established: @njit(parallel=True) is now demoted to serial by numba patch 0010 (emscripten-forge/recipes#6293), so Gap 1 is a performance issue rather than a blocker; LAPACK works in the browser and the real support_enumeration hang is jitted generators (#927); the 32-bit intp audit is documentation-only (#929); and the persistent cache has a warm-session bug (#944, see emscripten-forge/recipes#6309) that makes cache=True a liability for now. #944 is added to Phase 1 and a PR status column records where each work item stands. The original text is preserved in the edit history.

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 build 1 + 10 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. import quantecon already works in the xeus-python kernel, and _numba_linalg_solve / np.linalg.solve inside @njit return correct results there (#927). The requests, urllib, and sympy imports are already lazy, so import quantecon touches nothing browser-hostile.

The library's widespread cache=True discipline should become an asset under JupyterLite's persistent cross-session compilation cache (patch 0006), but until emscripten-forge/recipes#6309 is resolved it is a liability: a cache=True function compiled in a session where a cache=True callee was restored from a previous session crashes with RuntimeError: no compiled object yet (#944). This affects every cached call chain (simplex_grid, the pivoting-based solvers including lemke_howson), not just comb_jit.

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

Gaps

# Gap Severity
1 gini_coefficient uses @njit(parallel=True) + prange. Since numba 0.67.0 build 1 (patch 0010, emscripten-forge/recipes#6293, 2026-08-18) this no longer fails: the decorator is silently demoted to the serial pipeline, leaving browser users on a single-threaded O(n²) loop. #926 / PR #937 replace it with an O(n log n) rewrite Performance (one function)
2 _numba_linalg_solve / _LAPACK verified working on Emscripten (#927). support_enumeration and vertex_enumeration still hang because jitted generator functions hang on the emscripten-forge build (numba requests the symbol numba_make_generator but patch 0002 exports only Numba_make_generator); _support_enumeration_gen and _vertex_enumeration_gen are both @njit generators. No upstream report exists yet Blocked on upstream (two functions)
3 import quantecon eagerly compiles four guvectorize ufuncs plus one eagerly-signed @jit function; ufunc caching is disabled on Emscripten (patch 0007) so the cost recurs every session. #930 / PR #943 defer them to first use; import time has not yet been measured UX / performance
4 32-bit intp on wasm32: the comb_jit / simplex_grid overflow guard trips at 2³¹−1 instead of 2⁶³−1. This is correct by design (the value is an array size); simplex_index is pure Python and cannot wrap, and the tests are already intp-keyed. Only the docstrings need to state the platform-dependent boundary (#929). The actual in-browser simplex_grid failure is Gap 5 Documentation
5 A cache=True function compiled in a session where a cache=True callee was restored from the persistent cache fails with RuntimeError: no compiled object yet (patch 0006, emscripten-forge/recipes#6309). Deterministic for simplex_grid via num_compositions_jit → comb_jit; also exposes k_array_rank_jit → comb_jit and the _pivoting / _lex_min_ratio_test callers (lemke_howson, linprog_simplex, lcp_lemke). Only reproduces on a warm cache, never in a cold single-session run (#944) Blocked on upstream (every cached→cached chain)

Work plan

Phase Issue Work item Status (2026-08-21)
0 — Prove it #928 JupyterLite proof-of-concept deployment + browser smoke suite Environment config, ci/wasm/smoke_test.py and the Playwright harness merged in PR #938 (481947d); the browser runner job is parked in #933; the results table is not yet posted
1 — Code fixes #926 Replace the O(n²) parallel loop in gini_coefficient with the O(n log n) sorted closed form PR #937 approved 2026-08-14, all checks green, awaiting merge; gate PR #935 closed 2026-08-14
1 #927 Upstream the jitted-generator hang with a minimal repro; decide whether support_enumeration / vertex_enumeration need Emscripten-gated non-generator paths Blocked on upstream; no report filed yet
1 #929 Document the platform-dependent intp overflow boundary in the comb_jit / num_compositions_jit / simplex_grid docstrings (docs only) PR #942 (int64 widening) superseded — to be closed unmerged
1 #930 Reduce import-time eager Numba compilation PR #943 (Option 1, lazy compilation) open; needs a rebase onto main that drops #942's int64 commit and keeps intp; no import-time measurements yet
1 #931 Document browser limitations; raise helpful platform-specific errors Not started; no PR
1 #944 cache=True caller linking a cache-restored callee fails with "no compiled object yet" (simplex_grid) Blocked on emscripten-forge/recipes#6309; decision 2026-08-21: wait for upstream, no library-side change
2 — Distribution #932 Contribute a quantecon recipe to emscripten-forge No recipe yet; quantecon already installs from conda-forge noarch
3 — CI #933 Add a WebAssembly/JupyterLite job to CI Native job merged with PR #938; browser runner job parked here, needs triggers, hang guards for the generator tests, a test_simplex_grid guard and the gini expectations inverted before its first browser run
4 — Ecosystem #934 Demo notebook, lectures without a kernel server, WASM SIMD exploration After the intermediate release and #932

PR status: #937 approved, awaiting merge; #938 merged (481947d); #943 open, rebase needed; #942 to be closed unmerged; #935 closed.

Sequencing: Phase 0's configuration and smoke suite are on main (PR #938), but nothing has yet run in the browser from CI — the #927 verdict came from the public numba-ecosystem demo, and the browser runner is parked in #933. #926 is independent and PR #937 can merge now. #930 is still calibrated by an import-time benchmark that has not been recorded; PR #943 waits on that (or an explicit waiver of the gate) and on its rebase. #927 and #944 are blocked on upstream and must not gate the intermediate release; #929 and #931 are documentation. The release-gating code changes are therefore #926 (PR #937) and #930 (PR #943). Merge order that resolves the PR stacking: #937 → close #942 → rebase #943 onto main#943. #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