Roadmap: plan the async/multithreaded runtime transition - #21
Merged
Conversation
Adds the P5 item for retiring the fully-sync library surface. Grounded in what is actually sync today (generate_loop's FnMut callback, blocking ureq, pollster::block_on around wgpu's adapter probe, ModelSlot's Mutex - and zero async fn / zero .await / one thread::spawn, in a test) rather than stated as a goal. Two things the item establishes that change the design, both probed against burn 0.21 this session with a deliberate Rc<u8> control to prove the check was real: - Every model in the zoo and the KV cache are Send AND Sync, so inference state can cross threads and one owning worker thread per device is available today. That is the opposite of what cache.rs currently claims. - burn-store's TensorSnapshot holds an Rc<dyn Fn>, so the whole import pipeline is !Send and cannot cross a thread boundary or be held across an .await. Loads must run to completion on one thread. So the first slice is correcting cache.rs's stale "Param is not Sync" premise and pinning the real facts as compile-time assertions, so a burn bump that revokes them fails the build instead of the design. The item also states the constraint that most limits the design space: Mummu is shared by two apps, so picking tokio and spawning inside the library would impose that runtime on both - the app-coupling the North Star forbids, and unfixable downstream. Return futures/streams, never spawn, keep sync as the base. And the honest counterweight: decode is dispatch-bound, so moving work off the submit thread attacks the measured bottleneck - but async makes no single decode step faster, and a naive multithreaded executor makes it worse. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new P5 roadmap entry that documents Mummu’s current fully-synchronous surface area and lays out a staged plan (with constraints and gating criteria) for transitioning toward an async/multithreaded architecture without coupling the library to an application runtime.
Changes:
- Adds a detailed P5 checklist item describing today’s blocking decode/import/runtime touchpoints and why async/multithreading is a lever (and where it isn’t).
- Records key type-system constraints (thread-mobile inference vs
!Sendimport pipeline) and calls out a stale premise to fix early. - Proposes shippable slices (a–e) plus a determinism/parity gate for the transition.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+1413
to
+1415
| unfixable downstream. So: return `impl Future` / `impl Stream` and **never spawn**; keep the sync | ||
| API as the base rather than a wrapper over an async one (sync-over-async deadlocks; async-over-sync | ||
| does not); put any runtime dependency behind a non-default feature, the `jinja-template` precedent. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a P5 item for moving Mummu off its fully-sync library surface, per request.
Written to the roadmap's usual standard — grounded in what is actually sync today rather than stated as an aspiration:
decode::generate_loopis a blocking driver handing tokens to anFnMut(u32) -> ControlFlow<()>callbackureqis blocking HTTP ("sync like the rest of the library surface", per its ownCargo.tomlnote)backend.rs:126wraps wgpu's async adapter enumeration inpollster::block_onModelSlot::withserializes inference behind aMutexasync fn, zero.await, and onethread::spawn— in a testTwo facts probed this session (burn 0.21)
Verified with a compile probe carrying a deliberate
Rc<u8>control, so a silently-passing check would have been caught — only the control failed.Qwen2/Qwen3/Lfm2/Olmoe,GpuandCpu) and the KV cacheVec<LayerKv<Gpu>>areSendandSync. Inference state can cross threads, and one owning worker thread per device is available today.burn_store::TensorSnapshotholds anRc<dyn Fn() -> Result<TensorData, _>>(burn-store-0.21.0/src/tensor_snapshot.rs:53), so the whole import pipeline is!Send— it cannot cross a thread boundary or be held across an.await. A load must run to completion on one thread.This inverts the naive expectation: the inference side is thread-mobile, the import side is pinned.
It also means
cache.rs's header comment is stale — it says "Burn'sParamis notSync, so the loaded value lives behind aMutex", and the probe saysParamisSyncat 0.21. Serializing one GPU is still fine as policy, but the comment currently reads as a type-level prohibition that does not exist. Correcting that, and pinning the real facts as compile-time assertions so a burn bump that revokes them fails the build, is slice (a).Constraints the item records
App-agnostic. Mummu is shared by laurelane and Nanna. A library that picks
tokioand spawns its own tasks imposes that runtime on both — the app-coupling the North Star forbids, and unfixable downstream. So: returnimpl Future/impl Stream, never spawn, keep sync as the base (sync-over-async deadlocks; async-over-sync does not), runtime deps behind a non-default feature per thejinja-templateprecedent.Honest counterweight. Decode is dispatch-bound, not bandwidth-bound (established three ways, including a ~30 % swing from host CPU load alone), so moving work off the submit thread attacks the measured bottleneck — unusual for an async change. But async makes no single decode step faster, and a naive multithreaded executor makes it worse. The item says both.
Slices
(a) fix the stale premise + pin
Send/Syncas assertions · (b) pull-based streaming shape beside the callback, no runtime adopted · (c) one owning worker thread per device with a channel API — shared with P6's multi-GPU execution item · (d) async I/O for the downloader · (e) concurrent serving / continuous batching, its own item once (c) lands.Gate: parity byte-identical,
bench/BASELINE.mdheld, and specifically thatqwen2_sampled_streaming_is_seeded_deterministic_and_cancellabledoes not become flaky — a multithreaded executor is exactly where determinism dies.Docs-only; no code change. Not merged — that's your call.