Repository navigation
WASM: JupyterLite proof-of-concept deployment and browser smoke suite #928
Description
Note
Updated 2026-08-21. Phase 0 was overtaken by ad-hoc browser runs: the _LAPACK question (#927) was answered on the public numba-ecosystem demo, the intp question (#929) was settled in the #942 review, and PR #938 (merged as 481947d) has landed the deployment config and smoke suite under ci/wasm/. The checklist below is reworded to match those findings and the upstream Numba change that removed the expected gini_coefficient failure (patch 0010, emscripten-forge/recipes#6293); items with recorded browser evidence are ticked and a results table is maintained in the comments. The original text is preserved in the edit history.
Part of #925 (Phase 0). This issue was meant to gate #927 and calibrate #929 and #930; #927 was resolved by ad-hoc browser runs and #929 by the #942 review instead (see the checklist), so its remaining gating value is the cold/warm import timing for #930 and the first full-suite browser run via #933.
Goal
Prove the stack end-to-end with zero code changes, and convert every "should work" in the audit into a known. Because quantecon is noarch: python on conda-forge, there is a real chance it installs and imports in JupyterLite today with no packaging work.
Setup
Build a minimal JupyterLite deployment with jupyterlite-xeus:
name: qe-lite
channels:
- https://prefix.dev/emscripten-forge-4x
- https://prefix.dev/conda-forge
dependencies:
- xeus-python
- numba
- quanteconThis file is now committed as ci/wasm/environment.yml (PR #938). numba is unpinned, so a fresh build resolves the current emscripten-forge-4x build — numba 0.67.0 build 1 as of 2026-08-18, which carries patch 0010 (emscripten-forge/recipes#6293) forcing @njit(parallel=True) to the serial pipeline on Emscripten. Note that the browser results recorded so far did not come from this deployment: they came from the public numba-ecosystem demo (Numba 0.66.0) and the #942 review, so the versions per result are recorded in the table.
Smoke checklist
One representative function per Numba feature class used in the library:
-
import quantecon— works (implicitly confirmed by the_numba_linalg_solvereproducer in WASM: support_enumeration and vertex_enumeration hang in the browser — root cause is jitted generators (numba.np.linalg._LAPACK verified working) #927 and thesimplex_gridreproducer in WASM: use int64 in comb_jit for consistent overflow guards on wasm32 #942; no timing recorded) -
import quantecon— timed, cold cache and warm cache (feeds WASM: reduce import-time eager Numba compilation #930; WASM: reduce import-time eager Numba compilation #930's "measure first" gate and PR WASM: defer eager Numba gufunc/jit compilation to first use (#930) #943 are currently proceeding without this number) -
qe.tauchen— plain lazy@njit(_fill_tauchen);qe.rouwenhorstis pure NumPy with no Numba at all, kept as a no-JIT control case -
MarkovChain.simulate— lazy@njitkernel writing into a Python-allocated array (NRT allocation inside jitted code is exercised bysimplex_gridandnelder_meadbelow) -
qe.random.probvec— exercises the parallel-ufunc → cpu fallback (emscripten patch 0007) -
qe.random.sample_without_replacement— eagerguvectorize, expliciti8signature -
optimize:nelder_mead,brent_max,newton -
game_theory.lemke_howson -
game_theory.vertex_enumeration—numba.typed.Dict; also a jitted generator (_vertex_enumeration_gen), so expected to hang for the same reason assupport_enumeration(WASM: support_enumeration and vertex_enumeration hang in the browser — root cause is jitted generators (numba.np.linalg._LAPACK verified working) #927) — not yet run -
game_theory.support_enumeration— FAILS: first call never returns. The_LAPACKquestion is answered (LAPACK works, see the next two items); the cause is the jitted generator_support_enumeration_gen— emscripten-forge patch 0002 exports onlyNumba_make_generator, not the lowercasenumba_make_generatorthe generator wrapper imports (WASM: support_enumeration and vertex_enumeration hang in the browser — root cause is jitted generators (numba.np.linalg._LAPACK verified working) #927, not yet reported upstream) - minimal
@njitgenerator (for i in range(n): yield i) — FAILS (hangs on first call), recorded 2026-08-19 in WASM: support_enumeration and vertex_enumeration hang in the browser — root cause is jitted generators (numba.np.linalg._LAPACK verified working) #927; isolates the generator mechanism from quantecon's code -
np.linalg.solveinside a scratch@njit— works (correct result on Numba 0.66.0;_numba_linalg_solvealso works), recorded in WASM: support_enumeration and vertex_enumeration hang in the browser — root cause is jitted generators (numba.np.linalg._LAPACK verified working) #927 on 2026-08-15 and 2026-08-19 -
qe.gini_coefficient— expected to work, serially, on emscripten-forge numba ≥ 0.67.0 build 1 (patch 0010, Fallback Numba parallel JIT to serial on Emscripten emscripten-forge/recipes#6293, 2026-08-18); the@njit(parallel=True)failure mode this item originally expected no longer exists. Confirm it runs and time it — the serial O(n²) path is why the O(n log n) rewrite in WASM: remove @njit(parallel=True) from gini_coefficient via an O(n log n) rewrite #926 / PR Rewrite Gini coefficient in O(n log n) #937 still matters. No browser run of gini is recorded anywhere yet -
simplex_grid— FAILS on a warm persistent cache:RuntimeError: no compiled object yet for <Library 'comb_jit'>atqe.simplex_grid(3, 4)(WASM: cache=True functions that link a cache-restored callee fail with "no compiled object yet" (simplex_grid → num_compositions_jit → comb_jit) #944; upstream Numba:RuntimeError: no compiled object yetemscripten-forge/recipes#6309, open). The 32-bitintpboundary is now a documentation question (WASM: audit 32-bit intp behaviour on wasm32 (overflow guards, simplex_index wrapping, dtypes) #929 is doc-only); left unticked because the boundary check itself was never reached and stays blocked behind WASM: cache=True functions that link a cache-restored callee fail with "no compiled object yet" (simplex_grid → num_compositions_jit → comb_jit) #944 -
quantecon.util.array.searchsorted— the deprecatedobjmode()shim
Deliverables
- Results recorded in this issue as a table: function → works / fails / notes — started in the comments on 2026-08-21 with the rows that already had evidence (import,
np.linalg.solve,support_enumeration, bare generator,simplex_grid); the remaining items are not yet run - The deployment config committed somewhere reusable — done:
ci/wasm/environment.ymlplus the suiteci/wasm/smoke_test.py(16 tests mapping onto the 13 checklist items, all passing natively), the Playwright harnessci/wasm/test_jupyterlite.pyand a native gate in.github/workflows/ci_wasm_smoke.yml(PR WASM: JupyterLite environment config and browser smoke suite #938, merged 2026-08-21 as 481947d). The Emscripten runner job is parked in WASM: add a WebAssembly/JupyterLite smoke-test job to CI #933
Coverage of the browser harness
What actually runs on Emscripten today is test_jupyterlite.py (driven by the job parked in #933), and it covers 5 of the 13 checklist items: import quantecon (untimed), tauchen, bare np.linalg.solve in @njit, support_enumeration and gini_coefficient. smoke_test.py is not yet shipped into the kernel, so rouwenhorst, MarkovChain.simulate, probvec, sample_without_replacement, the three optimize routines, lemke_howson, vertex_enumeration, simplex_grid, searchsorted and the import timing have no browser test until #933 wires in-kernel execution of smoke_test.py. A first green run of the parked job therefore fills at most 5 rows of the table, not 13.
Several of the committed tests also encode expectations that are already out of date; #933 (the runner job) currently notes only the test_support_enumeration hang, so the rest are being handed to it as follow-ups: test_gini_coefficient (xfail(IS_EMSCRIPTEN, strict=True)) and test_gini_fails_on_emscripten expect a failure that patch 0010 has removed; test_support_enumeration and test_vertex_enumeration have no hang guard; test_simplex_grid cites #929 where it should cite #944.
Related
- WASM: support_enumeration and vertex_enumeration hang in the browser — root cause is jitted generators (numba.np.linalg._LAPACK verified working) #927 — jitted-generator hang (
support_enumeration,vertex_enumeration);_LAPACKverified working - WASM: cache=True functions that link a cache-restored callee fail with "no compiled object yet" (simplex_grid → num_compositions_jit → comb_jit) #944 —
simplex_gridfails withno compiled object yet for comb_jiton a warm cache; decision is to wait for upstream (Numba:RuntimeError: no compiled object yetemscripten-forge/recipes#6309) - WASM: remove @njit(parallel=True) from gini_coefficient via an O(n log n) rewrite #926 / PR Rewrite Gini coefficient in O(n log n) #937 —
gini_coefficientO(n log n) rewrite; the browser confirmation of gini belongs here, not in WASM: remove @njit(parallel=True) from gini_coefficient via an O(n log n) rewrite #926's acceptance criteria - WASM: reduce import-time eager Numba compilation #930 / PR WASM: defer eager Numba gufunc/jit compilation to first use (#930) #943 — import-time eager compilation; WASM: reduce import-time eager Numba compilation #930's gate still calls for the cold/warm import timing from this checklist, and PR WASM: defer eager Numba gufunc/jit compilation to first use (#930) #943 is proceeding without it
- WASM: add a WebAssembly/JupyterLite smoke-test job to CI #933 — Emscripten runner job; the test fixes listed above are to be recorded there
- PR WASM: JupyterLite environment config and browser smoke suite #938 — merged 481947d; delivered the config, suite and harness
Activity
WASM: JupyterLite environment config and browser smoke suite (#938)
mmcky commentedon Aug 21, 2026
Results table as of 2026-08-21, one row per checklist item. Only recorded browser evidence is entered; everything else is "not yet run". None of these came from the
ci/wasm/environment.ymldeployment — they came from the public numba-ecosystem demo (https://notebook.link/@anutosh491/numba-ecosystem) and from the #942 review — so the environment column matters: a freshqe-litebuild now resolves numba 0.67.0 build 1 (patch 0010, emscripten-forge/recipes#6293), which is newer than anything tested so far.import quantecon_numba_linalg_solveandsimplex_gridreproducers below import it first); cold/warm timing not yet runqe.tauchen/qe.rouwenhorstMarkovChain.simulateqe.random.probvecqe.random.sample_without_replacementoptimize:nelder_mead,brent_max,newtongame_theory.lemke_howsongame_theory.vertex_enumeration_vertex_enumeration_genis a jitted generator, same mechanism assupport_enumeration)game_theory.support_enumeration@njitgenerator (for i in range(n): yield i)numba_make_generatoris not exported by_dynfunc(onlyNumba_make_generatoris); redirecting the name inPythonAPI._get_functionmakes it return[0, 1, 2]np.linalg.solveinside a scratch@njit[2., -2.](correct);_numba_linalg_solvealso worksqe.gini_coefficientsimplex_gridRuntimeError: no compiled object yet for <Library 'comb_jit'>atqe.simplex_grid(3, 4); per the upstream diagnosis it needs a warm persistent cache (cache-misscache=Truecaller linking a cache-hit callee); theintpboundary check was never reachedquantecon.util.array.searchsortedThe parked #933 harness (
test_jupyterlite.py) can fill five of these rows (import, tauchen,np.linalg.solve, support_enumeration, gini); the rest wait onsmoke_test.pybeing shipped into the kernel under #933.mmcky commentedon Sep 9, 2026
Results table delta — 2026-09-09. One new row of browser evidence, two rows whose expectations have changed, and a correction. The 2026-08-21 table above is otherwise unchanged; this comment is the delta rather than a re-typing of all fourteen rows.
New row. The network utilities were never on the checklist, because #931 asserted they could not work. They do, and this is now the sixth row of recorded browser evidence.
GAMReader.from_urlandrequests.getfrom_urlread a.gamfile from raw.githubusercontent.com and returned a full 3-player game;requests.getreturned 200 with 4117 bytesTwo rows whose expectations changed, both still unrun.
qe.gini_coefficient— PR #937 merged 2026-09-09, so the function is now the O(n log n) closed form and no longer uses@njit(parallel=True)at all; the row should expect a pass plus a timing, andsmoke_test.py'sstrict=Truexfail on it will report a failure on the first browser run until it is removed (raised in #933).quantecon.util.array.searchsorted— this row is more interesting than it looked: it exerciseswith objmode():inside a nopython jit, which lifts back into the CPython interpreter and needs the GIL, and no emscripten patch mentions it. It is now Gap 6 in #925 rather than something assumed fine.Correction to the count. The tally has been quoted elsewhere today as "4 of 14 rows with evidence, 10 not yet run". The table above actually has 5 of 14 with evidence and 9 not yet run; with the new row it is 6 of 15 and 9. #925, and the notes on #931 and #933, are corrected.
The nine unrun rows are what #950 and #933 exist to fill, and every one of the six recorded results came from a public demo run by hand rather than from anything this repository controls.