WASM: reduce import-time eager Numba compilation
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
- #943 di @kp992 — chiusa senza merge
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 35/100
- Tipo di issue
- Refactoring
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Ferma
- Stack tecnologico
- jupyter-notebook, python, wasm
- Ambito
- performance, testing
Direzione di ricerca
Inizia con le definizioni eager di Numba in quantecon/random/utilities.py, quantecon/game_theory/vertex_enumeration.py e quantecon/util/numba.py, quindi esamina la PR #957 e il browser harness a cui si fa riferimento in #933. Esegui innanzitutto o aggiungi la misurazione dei tempi di importazione di JupyterLite prima/dopo con warm-cache; l’attività è completata quando il risultato è stato registrato e la firma pubblica di probvec è stata preservata.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
[!NOTE]
Updated 2026-08-21. Added a Status section: PR #943 implements Option 1 but is stacked on #938 (now merged) and on the superseded #942, and the "measure first" gate has not been performed. Corrected the claim thatcomb_jitbenefits from the persistent cache — per #944 and emscripten-forge/recipes#6309 itscache=Trueis currently part of a failing cached→cached chain on warm sessions. The original text is preserved in the edit history.
[!NOTE]
Updated 2026-09-09. PR #943 is closed; #957 replaces it, opening @oyamad's implementation from the
wasm-930-dynamic-gufuncsbranch (7c512ed) rebased ontomain. #957 is deliberately "Part of #930"
rather than "Closes", because acceptance criterion 1 below is still unmet. The Status section is
updated accordingly.
Part of #925 (Phase 1). Measure first: the Phase 0 deployment (#928) was meant to produce cold/warm import-time benchmarks — if warm import lands under a couple of seconds, deprioritise this issue. As of 2026-08-21 that measurement has not been made; see Status below.
Problem
import quantecon triggers eager Numba compilation of five callables:
| Callable | Where | Why eager |
|---|---|---|
_probvec_parallel |
random/utilities.py#L94 | module-level guvectorize with explicit signature |
_probvec_cpu |
random/utilities.py#L98 | same |
sample_without_replacement gufunc |
random/utilities.py#L162 | same |
_ints_arr_to_bits |
vertex_enumeration.py#L305 | same |
comb_jit |
util/numba.py#L80 | eager @jit signature intp(intp, intp) |
Natively this costs milliseconds and nobody notices. In the browser, each eager compile is a full LLVM optimisation + WASM object emission + in-process LLD link + side-module load, and the emscripten-forge Numba patch 0007 force-disables cache=True for @guvectorize/@vectorize — so the four gufunc compiles are not amortised by the persistent cache and are paid every session, on the critical path of every notebook.
comb_jit is an ordinary dispatcher with cache=True, but on the emscripten-forge build that is currently a liability rather than an asset. Per #944 and emscripten-forge/recipes#6309, a cache=True caller that is a cache miss fails with RuntimeError: no compiled object yet when it links a cache=True callee restored from the persistent cache in the same session, and comb_jit is exactly such a callee for num_compositions_jit → simplex_grid and for k_array_rank_jit. The eager signature only makes comb_jit a guaranteed cache hit at import in every warm session, which is why the failure is deterministic today.
Options, in increasing order of ambition
- Convert the eagerly-signed gufuncs to lazy compilation — drop the explicit signature lists (dynamic gufuncs), or construct the parallel/cpu variants on first use inside
probvec. - Replace the small gufuncs with
@njit(cache=True)loop implementations exposed through@overload— converts them from never-cached to persistently cached on Emscripten. - Defer heavy submodule imports in
quantecon/__init__.pyvia module__getattr__(PEP 562) — also improves native import time.
Status (2026-08-21)
Implementation (updated 2026-09-09). PR #957 implements Option 1, taking @oyamad's wasm-930-dynamic-gufuncs branch (7c512ed, 23 Aug) rebased onto main: the two _probvec gufuncs keep their explicit signatures but are built by memoised factories on first call, while _sample_without_replacement and _ints_arr_to_bits become dynamic (signature-less) gufuncs with the output array supplied by the caller. It records the numba#10128 constraint that forces the explicit signature on the _probvec pair. Native warm-cache import quantecon measures 0.70 s on main against 0.65 s on the branch, best of 7; that is not the browser measurement criterion 1 asks for. Options 2 and 3 are untouched. PR #943 (@kp992) previously implemented Option 1 in its "construct on first use" form: the explicit gufunc signatures are retained, the four gufuncs are built lazily behind module-level sentinels on first call, and comb_jit becomes a lazy @jit(nopython=True, cache=True). Options 2 and 3 are untouched.
Rebase required before review. #943 is stacked on #938 (merged 2026-08-21, now on main) and on #942's commit 97518e0, so its diff still carries #942's int64 widening of comb_jit — the np.int64() casts and INT64_MAX, the docstrings in util/numba.py and _gridtools.py, the MAX_INT64/test_max_int64 renames in test_numba.py, and the smoke test test_simplex_grid_comb_int64 — and its body cites "the int64 arithmetic guarantees from #929". The rewritten #929 concluded that intp is intentional (the result is an array size) and #942 is being closed unmerged. #943 therefore has to be rebased onto main, dropping 97518e0 and all int64 residue and keeping a lazy @jit(nopython=True, cache=True) over the unchanged intp body; its "Depends on #942" line should be retracted.
The "measure first" gate is unmet. No JupyterLite import quantecon timing exists anywhere in #928, #938 or #943: #928's timing item is unchecked, #938's native test_import_time only asserts elapsed < 30 s, the browser harness test_import_quantecon is untimed, and the Emscripten runner is parked in #933. import quantecon does work in the xeus-python kernel (#944), so a manual measurement is feasible today. The before/after warm-cache import timing (acceptance criterion 1) is a pre-merge requirement for #943 unless a maintainer explicitly waives it in a comment here; merging #943 as written would auto-close this issue with that criterion unmet. The number can come from a manual JupyterLite run now, or from the #933 runner once the browser harness records cold and warm import times.
What Option 1 does and does not buy. Patch 0007 forces cache=False for every @vectorize/@guvectorize on Emscripten, so after #943 the four gufuncs are still never cached in the browser: the compile cost moves from import to first call but is paid once per session. Only Option 2 (@njit(cache=True) loops behind @overload) would make them persistently cached. Whether that is worth doing is what the measurement above should tell us.
Relation to #944. #943 changes the comb_jit decorator that #944 is about, but it does not fix #944. The upstream reproducer (emscripten-forge/recipes#6309) fails with two plain lazy @njit(cache=True) functions whenever a cache-miss caller links a cache-hit callee, so a lazy comb_jit restored from the persistent cache still breaks the first compile of num_compositions_jit/simplex_grid or k_array_rank_jit in that session. #943 narrows the exposure — the literal qe.simplex_grid(3, 4) reproducer passes in cold sessions and in warm sessions where the caller is also cached — but the failure remains whenever comb_jit's cache entry is a hit and the caller's is a miss. #944's decision (wait for upstream) stands, and #943 should not be cited as resolving it.
Acceptance criteria
- Warm-cache
import quantecontime in JupyterLite measured before and after; result recorded here (pre-merge requirement for #943 unless explicitly waived) - No public API change (
qe.random.probvec(..., parallel=...)keeps its signature)
Related
- #957 — implements Option 1 from @oyamad's
wasm-930-dynamic-gufuncsbranch; open, still needs the import timing above - #943 — the earlier implementation of Option 1 (@kp992); closed 2026-09-09 in favour of #957
- #942 —
int64widening ofcomb_jit; superseded by #929 and being closed unmerged; #943 currently carries its commit - #929 —
intpincomb_jitis intentional; documentation-only - #944 —
cache=Truecallers of cache-restored functions fail with "no compiled object yet" (reported upstream as emscripten-forge/recipes#6309); not addressed by #943 - #928 — Phase 0 results table; its cold/warm import-timing item feeds this issue and is still unchecked
- #938 (merged) and #933 — browser smoke suite and its parked Emscripten runner; the natural home for a recorded import timing
- Lingua principale
- Python
- Stelle
- 2.4k
- Fork
- 2.3k
- Merge medio
- 4g 19h
- PR unite (30g)
- 10
Preparare l'ambiente
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di QuantEcon/QuantEcon.py
-
TST: seed the starting point in test_rotated_hyper_ellipsoidForse già presa @jadhavgaurav l’ha presa 2 giorni fa. Apertatests
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 94/100
QuantEcon/QuantEcon.py#993 ·
I maintainer di solito rispondono entro 1 giorno
-
documentation
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 92/100
QuantEcon/QuantEcon.py#988 ·
I maintainer di solito rispondono entro 1 giorno
-
BUG: solve_discrete_riccati_system returns NaN instead of raising when the iteration overflowsForse già presa @mayuriphad l’ha presa 5 giorni fa. Apertabug
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
QuantEcon/QuantEcon.py#986 ·
I maintainer di solito rispondono entro 1 giorno
-
DOC: Document the commit-message prefix conventionForse già presa @nicolasdmolina l’ha presa 41 giorni fa. Apertadocumentation good first issue low-priority
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
QuantEcon/QuantEcon.py#902 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
documentation security
Difficoltà 2/5 1-2 giorni Idoneità per principianti 68/100
QuantEcon/QuantEcon.py#891 ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di QuantEcon/QuantEcon.py
Issue simili
-
bug good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
yunaremaia/proto-drift#22 ·
-
bug good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
yunaremaia/memwatch#96 ·
-
bug good first issue
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 91/100
yunaremaia/ci-gate-watch#34 ·
-
bug good first issue
Difficoltà 2/5 1-3 ore Idoneità per principianti 85/100
yunaremaia/agent-capability-attestation#64 ·
I maintainer di solito rispondono entro 1 giorno
-
bug good first issue
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 86/100
yunaremaia/agent-call-graph#43 ·
I maintainer di solito rispondono entro 1 giorno