You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
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), and since patch 0010 (Fallback Numba parallel JIT to serial on Emscripten emscripten-forge/recipes#6293, merged 2026-08-18, shipped as numba 0.67.0 build 1 on emscripten-forge-4x) @njit(parallel=True) is likewise demoted to the serial pipeline: options['parallel'] is forced to False, prange keeps range semantics and NUMBA_NUM_THREADS is pinned to 1. Deployments still on build 0 have no such fallback and that path fails there.
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. 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)
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
Upstream the jitted-generator hang with a minimal repro; decide whether support_enumeration / vertex_enumeration need Emscripten-gated non-generator paths
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
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.
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 realsupport_enumerationhang is jitted generators (#927); the 32-bitintpaudit is documentation-only (#929); and the persistent cache has a warm-session bug (#944, see emscripten-forge/recipes#6309) that makescache=Truea 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 (
noarchon 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 quanteconalready works in the xeus-python kernel, and_numba_linalg_solve/np.linalg.solveinside@njitreturn correct results there (#927). Therequests,urllib, andsympyimports are already lazy, soimport quantecontouches nothing browser-hostile.The library's widespread
cache=Truediscipline 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: acache=Truefunction compiled in a session where acache=Truecallee was restored from a previous session crashes withRuntimeError: no compiled object yet(#944). This affects every cached call chain (simplex_grid, the pivoting-based solvers includinglemke_howson), not justcomb_jit.The constraints of the in-browser Numba build, read from the ten emscripten-forge patches and the recipe's tests:
target='parallel'ufuncs silently fall back to'cpu'onsys.platform == "emscripten"(patch 0007), and since patch 0010 (Fallback Numba parallel JIT to serial on Emscripten emscripten-forge/recipes#6293, merged 2026-08-18, shipped as numba 0.67.0 build 1 on emscripten-forge-4x)@njit(parallel=True)is likewise demoted to the serial pipeline:options['parallel']is forced toFalse,prangekeepsrangesemantics andNUMBA_NUM_THREADSis pinned to 1. Deployments still on build 0 have no such fallback and that path fails there.@njit(cache=True)gains a persistent WASM object cache across browser sessions (patch 0006), butcache=Trueon@vectorize/@guvectorizeis force-disabled on Emscripten by patch 0007 — ufuncs recompile every session. Patch 0006 currently has the warm-session linking bug described above (Numba:RuntimeError: no compiled object yetemscripten-forge/recipes#6309, WASM: cache=True functions that link a cache-restored callee fail with "no compiled object yet" (simplex_grid → num_compositions_jit → comb_jit) #944).np.intp, and NumPy's default integer are 32 bits; addressable memory is capped at 2–4 GB shared with the whole kernel.Gaps
gini_coefficientuses@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_numba_linalg_solve/_LAPACKverified working on Emscripten (#927).support_enumerationandvertex_enumerationstill hang because jitted generator functions hang on the emscripten-forge build (numba requests the symbolnumba_make_generatorbut patch 0002 exports onlyNumba_make_generator);_support_enumeration_genand_vertex_enumeration_genare both@njitgenerators. No upstream report exists yetimport quanteconeagerly compiles fourguvectorizeufuncs plus one eagerly-signed@jitfunction; 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 measuredintpon wasm32: thecomb_jit/simplex_gridoverflow guard trips at 2³¹−1 instead of 2⁶³−1. This is correct by design (the value is an array size);simplex_indexis pure Python and cannot wrap, and the tests are alreadyintp-keyed. Only the docstrings need to state the platform-dependent boundary (#929). The actual in-browsersimplex_gridfailure is Gap 5cache=Truefunction compiled in a session where acache=Truecallee was restored from the persistent cache fails withRuntimeError: no compiled object yet(patch 0006, emscripten-forge/recipes#6309). Deterministic forsimplex_gridvianum_compositions_jit → comb_jit; also exposesk_array_rank_jit → comb_jitand the_pivoting/_lex_min_ratio_testcallers (lemke_howson,linprog_simplex,lcp_lemke). Only reproduces on a warm cache, never in a cold single-session run (#944)Work plan
ci/wasm/smoke_test.pyand the Playwright harness merged in PR #938 (481947d); the browser runner job is parked in #933; the results table is not yet postedgini_coefficientwith the O(n log n) sorted closed formsupport_enumeration/vertex_enumerationneed Emscripten-gated non-generator pathsintpoverflow boundary in thecomb_jit/num_compositions_jit/simplex_griddocstrings (docs only)mainthat drops #942's int64 commit and keepsintp; no import-time measurements yetcache=Truecaller linking a cache-restored callee fails with "no compiled object yet" (simplex_grid)quanteconrecipe to emscripten-forgequanteconalready installs from conda-forgenoarchtest_simplex_gridguard and the gini expectations inverted before its first browser runPR 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 ontomain→ #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, nogenerated_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
recipes/recipes_emscripten/{numba,llvmlite,scipy,sympy,pytensor-base}/, especially numba patches 0006 (persistent WASM cache), 0007 (parallel-ufunc fallback; ufunc cache disabled) and 0010 (serial fallback for@njit(parallel=True), added in Fallback Numba parallel JIT to serial on Emscripten emscripten-forge/recipes#6293, 2026-08-18)RuntimeError: no compiled object yetemscripten-forge/recipes#6309 — warm-session "no compiled object yet" bug in patch 0006, reported by @oyamad (tracked here as WASM: cache=True functions that link a cache-restored callee fail with "no compiled object yet" (simplex_grid → num_compositions_jit → comb_jit) #944)main@ 13b436b (audited 13 Aug 2026); re-checked against c4bef77 and 481947d (21 Aug 2026):draw(rng=Generator)(ENH: Extend random.draw to accept a random number generator #917) and the exec-generated jit inutil/indexing.py(ENH: Add action_values to DiscreteDP; add util.index_dict #940) are new and not yet covered by the smoke suite. File references are in the sub-issues