mlx-bun 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -588
- package/docs/README.md +91 -0
- package/docs/{design → archive/investigations}/audio-input-plan.md +4 -4
- package/docs/{design → archive/investigations}/diffusion-gemma-port.md +3 -3
- package/docs/{design → archive/investigations}/faithful-l1-consolidation.md +4 -4
- package/docs/{design → archive/investigations}/minicpm5-decode-megakernel.md +11 -11
- package/docs/{design → archive/investigations}/optimization_plan.md +1 -1
- package/docs/{design → archive/investigations}/pre-colibri-stabilization.md +5 -5
- package/docs/{design → archive/investigations}/the-dreaming-handoff.md +2 -2
- package/docs/design/batching.md +569 -0
- package/docs/design/decode-speed-program.md +3178 -169
- package/docs/design/dreaming-nightly-pipeline.md +675 -282
- package/docs/design/generic-model-support.md +579 -334
- package/docs/design/kv-cache.md +680 -0
- package/docs/design/orpo-training.md +673 -1239
- package/docs/design/speculative-decoding.md +1627 -0
- package/docs/design/turboquant.md +2102 -0
- package/docs/design/unified-engine-frontier-plan.md +1831 -727
- package/docs/design/web-chat-redesign.md +666 -1083
- package/docs/planning/PRODUCT_ROADMAP.md +34 -1
- package/docs/planning/release-notes-v0.3.0.md +35 -0
- package/docs/planning/video-series.md +2 -2
- package/docs/reference/benchmarks.md +1162 -0
- package/docs/reference/cli.md +394 -159
- package/docs/reference/distribution.md +321 -182
- package/docs/reference/environment.md +348 -0
- package/docs/reference/glossary.md +39 -0
- package/docs/reference/library-api.md +566 -168
- package/docs/reference/memory.md +227 -64
- package/docs/reference/models.md +104 -6
- package/docs/reference/server-api.md +886 -425
- package/docs/reference/server-config.md +638 -330
- package/docs/reference/training.md +393 -254
- package/docs/reference/troubleshooting.md +166 -0
- package/package.json +9 -4
- package/src/audio/decode.ts +2 -2
- package/src/audio/features.ts +2 -2
- package/src/backends/mlx/autoregressive.ts +108 -0
- package/src/backends/mlx/batch-group.ts +1646 -0
- package/src/backends/mlx/diffusion.ts +39 -0
- package/src/backends/mlx/draft-target.ts +47 -0
- package/src/backends/mlx/evaluation.ts +105 -0
- package/src/backends/mlx/fill-append.ts +45 -0
- package/src/backends/mlx/gateway-binding.ts +71 -0
- package/src/backends/mlx/graph.ts +38 -0
- package/src/backends/mlx/memory-client.ts +189 -0
- package/src/backends/mlx/memory-guard.ts +40 -0
- package/src/backends/mlx/methods.ts +90 -0
- package/src/backends/mlx/model-prompt.ts +164 -0
- package/src/backends/mlx/model-serving.ts +79 -0
- package/src/backends/mlx/prefill.ts +33 -0
- package/src/backends/mlx/request-policy.ts +20 -0
- package/src/backends/mlx/rollback.ts +51 -0
- package/src/backends/mlx/serial-executor.ts +273 -0
- package/src/backends/mlx/speculative.ts +114 -0
- package/src/backends/mlx/state-views.ts +26 -0
- package/src/backends/mlx/text-engine.ts +73 -0
- package/src/cli.ts +58 -25
- package/src/client.ts +66 -0
- package/src/config.ts +21 -4
- package/src/contracts/completion.ts +16 -0
- package/src/contracts/execution.ts +49 -0
- package/src/contracts/generation.ts +103 -0
- package/src/contracts/host.ts +7 -0
- package/src/contracts/jobs.ts +66 -0
- package/src/contracts/pi-provider.ts +30 -0
- package/src/contracts/pi.ts +259 -0
- package/src/contracts/resources.ts +18 -0
- package/src/contracts/scheduling.ts +27 -0
- package/src/contracts/tsconfig.json +14 -0
- package/src/diffusion/diffusion-generate.ts +218 -153
- package/src/embed.ts +1 -1
- package/src/engine/admission.ts +59 -0
- package/src/engine/cancellation.ts +38 -0
- package/src/engine/engine.ts +56 -0
- package/src/engine/execution-coordinator.ts +70 -0
- package/src/engine/execution-plan.ts +51 -0
- package/src/engine/resources.ts +42 -0
- package/src/engine/scheduler.ts +67 -0
- package/src/engine/session.ts +247 -0
- package/src/eval/README.md +44 -1
- package/src/eval/kl.ts +236 -3
- package/src/eval/reference-logits.ts +207 -0
- package/src/eval/runner.ts +8 -96
- package/src/fill/echo-index.ts +188 -0
- package/src/fill/fill-session.ts +452 -0
- package/src/fill/proposal.ts +68 -0
- package/src/fill/schema-rows.ts +506 -0
- package/src/fill/tool-boundary.ts +103 -0
- package/src/fit.ts +1 -1
- package/src/generate.ts +656 -268
- package/src/grammar.ts +12 -7
- package/src/harness-pi.ts +14 -33
- package/src/hub-rest.ts +4 -4
- package/src/index.ts +7 -0
- package/src/inference/denoising.ts +17 -0
- package/src/inference/graph.ts +33 -0
- package/src/inference/prefill.ts +39 -0
- package/src/inference/rollback.ts +7 -0
- package/src/jobs/db.ts +4 -0
- package/src/jobs/index.ts +2 -0
- package/src/jobs/job-entry.ts +12 -12
- package/src/jobs/runner.ts +98 -13
- package/src/jobs/task-client.ts +19 -0
- package/src/jobs/types.ts +2 -58
- package/src/kv-store.ts +109 -49
- package/src/lab/README.md +18 -0
- package/src/{assets → lab/curve}/curve-designer.html +2 -2
- package/src/{curve-sampler.ts → lab/curve/curve-sampler.ts} +2 -2
- package/src/{expert-trace.ts → lab/expert-trace/expert-trace.ts} +5 -5
- package/src/{model → lab/paged-kv}/paged-kv.ts +9 -6
- package/src/library.ts +40 -0
- package/src/lora.ts +18 -0
- package/src/media-fetch.ts +1 -1
- package/src/memory/article.ts +1 -1
- package/src/memory/chunk.ts +1 -1
- package/src/memory/model.ts +30 -169
- package/src/memory/prompts.ts +1 -1
- package/src/memory/rest.ts +1 -1
- package/src/memory/schedule.ts +1 -1
- package/src/memory/stages.ts +4 -1
- package/src/memory/vault.ts +1 -3
- package/src/mlx/array.ts +9 -10
- package/src/mlx/compile.ts +12 -2
- package/src/mlx/ffi.ts +58 -11
- package/src/mlx/materialize.ts +23 -0
- package/src/mlx/metal-kernel.ts +10 -11
- package/src/mlx/ops.ts +33 -13
- package/src/mlx/quantized-rows.ts +36 -0
- package/src/mlx/token-bitmask.ts +52 -0
- package/src/mlx/turboquant-kv-decode.ts +82 -0
- package/src/mlx/turboquant-kv-inverse.ts +122 -0
- package/src/mlx/turboquant-ops.ts +33 -3
- package/src/mlx/turboquant-tables.ts +2 -2
- package/src/model/batched-mask.ts +8 -2
- package/src/model/batched-quant.ts +1 -1
- package/src/model/batched-rotating-quant.ts +2 -2
- package/src/model/batched-rotating.ts +1 -1
- package/src/model/compiled-decode.ts +1 -1
- package/src/model/diffusion-gemma.ts +8 -8
- package/src/model/factory.ts +76 -33
- package/src/model/fingerprint.ts +1 -1
- package/src/model/flash-attention.ts +3 -3
- package/src/model/gemma4-base.ts +26 -9
- package/src/model/gemma4.ts +6 -6
- package/src/model/generated/gemma4-12b.ts +2 -2
- package/src/model/generated/gemma4-26b.ts +2 -2
- package/src/model/generated/gemma4-e4b.ts +2 -2
- package/src/model/glm52-dsa.ts +13 -9
- package/src/model/glm52-memory.ts +1 -1
- package/src/model/implementation.ts +69 -0
- package/src/model/minicpm5.ts +6 -8
- package/src/model/profile.ts +12 -2
- package/src/model/qwen-append.ts +12 -0
- package/src/model/qwen-conv.ts +52 -0
- package/src/model/qwen3_5-checkpoint.ts +164 -0
- package/src/model/qwen3_5.ts +140 -56
- package/src/model/support.ts +1 -1
- package/src/model/trellis-balanced-scatter.ts +51 -0
- package/src/model/trellis-linear.ts +672 -0
- package/src/model/trellis-shared-m.ts +151 -0
- package/src/model/trellis-shared-scatter.ts +144 -0
- package/src/model/trellis-splitk-prefill.ts +158 -0
- package/src/model/trellis-tiled-prefill.ts +136 -0
- package/src/model/trellis-vector-expand.ts +62 -0
- package/src/model/trellis-wide-prefill.ts +94 -0
- package/src/model/universal/dense.ts +1 -1
- package/src/model/universal/modules.ts +8 -1
- package/src/native-pack.ts +6 -5
- package/src/pi-provider.ts +6 -64
- package/src/pi-web.ts +8 -315
- package/src/prompt-cache.ts +96 -53
- package/src/quantize/allocator.ts +1 -1
- package/src/quantize/config-writer.ts +10 -3
- package/src/quantize/rotate.ts +4 -4
- package/src/quantize/trellis.ts +586 -0
- package/src/responses.ts +28 -1
- package/src/runtime-config.ts +18 -4
- package/src/sampler.ts +4 -4
- package/src/serve/batch-scheduler.ts +8 -1671
- package/src/serve/chat-request.ts +391 -0
- package/src/serve/chat-stage.ts +195 -0
- package/src/serve/checkpoint-identity.ts +64 -0
- package/src/serve/completion-executor.ts +15 -7
- package/src/serve/discovery-routes.ts +12 -13
- package/src/serve/generation-gateway.ts +154 -193
- package/src/serve/http.ts +159 -0
- package/src/serve/inference-request.ts +79 -0
- package/src/serve/isolate.ts +297 -105
- package/src/serve/lab-routes.ts +3 -0
- package/src/serve/lane-registry.ts +2 -1
- package/src/serve/model-admin-routes.ts +7 -5
- package/src/serve/model-binding.ts +43 -0
- package/src/serve/model-host.ts +512 -0
- package/src/serve/openai-wire.ts +103 -0
- package/src/serve/parent-application.ts +56 -0
- package/src/serve/pipeline.ts +33 -0
- package/src/serve/preparation.ts +33 -0
- package/src/serve/request-plan.ts +19 -15
- package/src/serve/request-prep.ts +346 -0
- package/src/serve/response-lease.ts +24 -0
- package/src/serve/responses-client.ts +69 -0
- package/src/serve/session-completion-engine.ts +110 -0
- package/src/serve/session-search.ts +1 -1
- package/src/serve/static-routes.ts +5 -5
- package/src/serve/text-completion-stage.ts +104 -0
- package/src/serve/token-streams.ts +519 -0
- package/src/server.ts +317 -2649
- package/src/spec/assistant-source.ts +10 -44
- package/src/spec/deepspec-source.ts +6 -6
- package/src/spec/dflash-source.ts +8 -8
- package/src/spec/dspark/ab-stats.ts +3 -3
- package/src/spec/dspark/calibration.ts +2 -2
- package/src/spec/dspark/deepspec-module.ts +2 -2
- package/src/spec/dspark/generate-dflash.ts +2 -2
- package/src/spec/dspark/generate.ts +2 -2
- package/src/spec/dspark/loader.ts +1 -1
- package/src/spec/dspark/module-dflash.ts +4 -3
- package/src/spec/dspark/module.ts +3 -3
- package/src/spec/dspark/quantize-drafter.ts +1 -1
- package/src/spec/generate.ts +1 -1
- package/src/spec/glm52-mtp-source.ts +1 -1
- package/src/spec/ngram-source.ts +1 -1
- package/src/spec/prefix-state.ts +45 -0
- package/src/spec/qwen-mtp-source.ts +166 -69
- package/src/spec/serve-loop.ts +154 -156
- package/src/spec/source.ts +42 -10
- package/src/spec/two-model.ts +1 -1
- package/src/ssd-cache.ts +80 -12
- package/src/ssd-durability.ts +25 -13
- package/src/tool-call.ts +51 -14
- package/src/train/flash-cce.ts +2 -2
- package/src/train/forward.ts +6 -0
- package/src/train/loss.ts +1 -1
- package/src/train/prefix-shared.ts +5 -1
- package/src/train/segmented.ts +5 -3
- package/src/train/trainer.ts +6 -6
- package/src/vision/prompt.ts +1 -1
- package/src/vision/qwen3vl-tower.ts +52 -10
- package/src/web/app.html +1 -1
- package/src/web/app.js +5 -3
- package/src/web/src/adapters-panel.ts +3 -3
- package/src/web/src/assistant.ts +2 -36
- package/src/web/src/chat.ts +4 -4
- package/src/web/src/composer.ts +4 -4
- package/src/web/src/hub.ts +1 -1
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +1 -1
- package/src/web/src/model-picker.ts +1 -1
- package/src/web/src/palette.ts +1 -1
- package/src/web/src/protocol.ts +3 -39
- package/src/web/src/sessions.ts +1 -1
- package/src/web/src/ui-catalog.ts +4 -18
- package/src/web-assets.ts +47 -0
- package/src/weights.ts +55 -9
- package/docs/archive/investigations/curve-runs/bisector-route.json +0 -98
- package/docs/archive/investigations/curve-runs/bisector-trace.json +0 -1393
- package/docs/archive/investigations/curve-runs/curve-terrain.json +0 -1179
- package/docs/archive/investigations/curve-runs/curve-terrain.svg +0 -42
- package/docs/archive/investigations/hlg-ab.html +0 -34
- package/docs/archive/investigations/hlg-explorer.html +0 -238
- package/docs/archive/investigations/hlg-figs/cliff.svg +0 -1
- package/docs/archive/investigations/hlg-figs/cross.svg +0 -1
- package/docs/archive/investigations/hlg-figs/edges.svg +0 -1
- package/docs/archive/investigations/hlg-figs/k20-grids.svg +0 -1
- package/docs/archive/investigations/hlg-figs/k20-peaks.svg +0 -1
- package/docs/archive/investigations/hlg-figs/lw-curve.svg +0 -1
- package/docs/archive/investigations/hlg-figs/terrain.svg +0 -1
- package/docs/archive/investigations/hlg-report.html +0 -262
- package/docs/archive/investigations/hlg-runs/hlg-ab.json +0 -149
- package/docs/archive/investigations/hlg-runs/hlg-blind-prompts.md +0 -87
- package/docs/archive/investigations/hlg-runs/hlg-blind.json +0 -107
- package/docs/archive/investigations/hlg-runs/hlg-cliff.json +0 -197
- package/docs/archive/investigations/hlg-runs/hlg-cross.json +0 -505
- package/docs/archive/investigations/hlg-runs/hlg-edge.json +0 -388
- package/docs/archive/investigations/hlg-runs/hlg-eval1.json +0 -25
- package/docs/archive/investigations/hlg-runs/hlg-frontier.json +0 -86
- package/docs/archive/investigations/hlg-runs/hlg-grid.json +0 -242
- package/docs/archive/investigations/hlg-runs/hlg-grid2.json +0 -343
- package/docs/archive/investigations/hlg-runs/hlg-lw-coarse.json +0 -110
- package/docs/archive/investigations/hlg-runs/hlg-map.json +0 -383
- package/docs/archive/investigations/hlg-runs/run0-original.log +0 -159
- package/docs/archive/investigations/hlg-runs/runA-recommended.log +0 -131
- package/docs/archive/investigations/hlg-runs/runAA-cliff-cpm5.log +0 -26
- package/docs/archive/investigations/hlg-runs/runAB-grid2-e4b.log +0 -18
- package/docs/archive/investigations/hlg-runs/runAC-peak-e4b-partial.log +0 -25
- package/docs/archive/investigations/hlg-runs/runAD-ab-samples.log +0 -8
- package/docs/archive/investigations/hlg-runs/runAE-edge-e4b.log +0 -51
- package/docs/archive/investigations/hlg-runs/runAF-logit-calibration.log +0 -10
- package/docs/archive/investigations/hlg-runs/runAG-eval-config2.log +0 -16
- package/docs/archive/investigations/hlg-runs/runB-gain-variants.log +0 -192
- package/docs/archive/investigations/hlg-runs/runC-pivots.log +0 -162
- package/docs/archive/investigations/hlg-runs/runD-oetf.log +0 -132
- package/docs/archive/investigations/hlg-runs/runE-eotf.log +0 -162
- package/docs/archive/investigations/hlg-runs/runF-shaper-working.log +0 -86
- package/docs/archive/investigations/hlg-runs/runG-shaper-sweep.log +0 -101
- package/docs/archive/investigations/hlg-runs/runH-wsweep.log +0 -176
- package/docs/archive/investigations/hlg-runs/runI-asweep.log +0 -176
- package/docs/archive/investigations/hlg-runs/runJ-knobmap.log +0 -49
- package/docs/archive/investigations/hlg-runs/runK-frontier.log +0 -19
- package/docs/archive/investigations/hlg-runs/runL-widemap.log +0 -65
- package/docs/archive/investigations/hlg-runs/runM-frontier-emb.log +0 -15
- package/docs/archive/investigations/hlg-runs/runN-verify.log +0 -116
- package/docs/archive/investigations/hlg-runs/runO-frontier-seed5000.log +0 -15
- package/docs/archive/investigations/hlg-runs/runP-grid-AxSm.log +0 -15
- package/docs/archive/investigations/hlg-runs/runQ-grid-tgW.log +0 -15
- package/docs/archive/investigations/hlg-runs/runR-blind-transcript.log +0 -79
- package/docs/archive/investigations/hlg-runs/runS-cross-coherence.log +0 -33
- package/docs/archive/investigations/hlg-runs/runT-cross-cpm5.log +0 -33
- package/docs/archive/investigations/hlg-runs/runU-cross-12b.log +0 -33
- package/docs/archive/investigations/hlg-runs/runV-lw-e4b.log +0 -22
- package/docs/archive/investigations/hlg-runs/runW-lw-12b.log +0 -22
- package/docs/archive/investigations/hlg-runs/runX-lw-cpm5.log +0 -22
- package/docs/archive/investigations/hlg-runs/runY-cliff-e4b.log +0 -26
- package/docs/archive/investigations/hlg-runs/runZ-cliff-12b.log +0 -26
- package/docs/archive/mlx-bun-lab-report.html +0 -275
- package/docs/archive/wwdc-mlx-bun.html +0 -848
- package/docs/design/adapters-end-to-end.md +0 -205
- package/docs/design/batching-perf-path.md +0 -255
- package/docs/design/batching-v2-plan.md +0 -529
- package/docs/design/bucketing-stage.md +0 -197
- package/docs/design/compat-cli-surface-design.md +0 -230
- package/docs/design/docs-reorg-plan.md +0 -165
- package/docs/design/dspark-serving-program.md +0 -371
- package/docs/design/dspark-speculative-decoding.md +0 -318
- package/docs/design/grammar-spec-batching-integration.md +0 -301
- package/docs/design/memory-inference-path.md +0 -422
- package/docs/design/memory-synthesis.md +0 -287
- package/docs/design/memory-system.md +0 -237
- package/docs/design/mlx-lm-serving-execution-seam.md +0 -81
- package/docs/design/mlx-lm-tool-parity-plan.md +0 -545
- package/docs/design/omlx-adoption-map.md +0 -98
- package/docs/design/orpo-dynamic-lambda.md +0 -220
- package/docs/design/orpo-future-enhancements.md +0 -340
- package/docs/design/paged-kv-cache.md +0 -214
- package/docs/design/parallel-slots.md +0 -567
- package/docs/design/parity-tier-dag.md +0 -161
- package/docs/design/repo-cleanup-plan.md +0 -231
- package/docs/design/runtime-isolation.md +0 -263
- package/docs/design/segmented-backward-training.md +0 -543
- package/docs/design/spec-decode-larger-targets.md +0 -229
- package/docs/design/ssd-kv-cold-tier.md +0 -415
- package/docs/design/structured-output.md +0 -484
- package/docs/design/tauri-desktop-app.md +0 -127
- package/docs/design/the-dreaming-master-plan.md +0 -445
- package/docs/design/turboquant-kv.md +0 -235
- package/docs/design/turboquant-weights.md +0 -467
- package/docs/design/w4a16-compute-precision-spike.md +0 -232
- package/docs/design/web-chat-beat-matrix.md +0 -315
- package/docs/design/write-pipeline-entity.md +0 -468
- package/docs/planning/IDEAS.md +0 -69
- package/docs/reference/embedding.md +0 -197
- package/docs/reference/features-matrix.md +0 -101
- package/docs/reference/orpo-quickstart.md +0 -135
- package/src/flags.ts +0 -11
- /package/docs/{design → archive/investigations}/colibri-glm52-port.md +0 -0
- /package/docs/{planning → archive/planning}/chunk-task-roadmap.md +0 -0
- /package/docs/{planning → archive/planning}/curve-sampler-research-plan.md +0 -0
- /package/docs/{planning → archive/planning}/memory-docs-and-dag-plan.md +0 -0
- /package/docs/{planning → archive/planning}/pi-integration-review.md +0 -0
- /package/docs/{planning → archive/planning}/release-notes-v0.2.0.md +0 -0
- /package/src/{memory-plan.ts → admission-plan.ts} +0 -0
- /package/{docs/archive/investigations → src/serve/assets}/curve-terrain.html +0 -0
- /package/{docs/dag → src/serve/assets}/training-inference-map.html +0 -0
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
# Parity-tier DAG → meaningful CLI flags
|
|
2
|
-
|
|
3
|
-
**Status:** vision / roadmap (2026-06-21). The first artifact exists — a zoomable,
|
|
4
|
-
tier-tagged map of the training + inference DAGs at
|
|
5
|
-
[`docs/dag/training-inference-map.html`](../dag/training-inference-map.html). This
|
|
6
|
-
doc records where it goes and *why*: to turn a sprawling pile of CLI flags into a
|
|
7
|
-
small, coherent, tier-organized surface.
|
|
8
|
-
|
|
9
|
-
## The framework (recap)
|
|
10
|
-
|
|
11
|
-
Every node in the compute graph sits in the **lowest parity tier whose oracle it
|
|
12
|
-
matches bit-for-bit**:
|
|
13
|
-
|
|
14
|
-
- **L1** — drop-in for **mlx-lm** (its op is the oracle; bit-exact or it's a bug).
|
|
15
|
-
- **L2** — drop-in for **mlx-optiq** (optiq's behavior is the oracle).
|
|
16
|
-
- **the Lab** (formerly the `--l3` tier — deleted as a product mode 2026-07-05,
|
|
17
|
-
[unified-engine-frontier-plan.md §6](unified-engine-frontier-plan.md)) — **our
|
|
18
|
-
originals** (flash-CCE head, ORPO loss, prefix-share, segmented backward, HLG
|
|
19
|
-
sampler, expert offload). **No oracle ships**, so correctness is gated by
|
|
20
|
-
finite-difference / KL / quality, *not* parity. Output-changing inference
|
|
21
|
-
experiments live here as env-flag items with a bench + expiry, until one beats
|
|
22
|
-
the L1 baseline in a paired A/B.
|
|
23
|
-
|
|
24
|
-
**The gate is the oracle.** A node can only live in a lower tier if that oracle
|
|
25
|
-
exists AND it matches bit-exactly; otherwise it bubbles up. Note the subtlety the
|
|
26
|
-
map already shows: an *optimization* that still matches the oracle stays low —
|
|
27
|
-
`compiled-decode` is ours but is L1 because it replays the same ops bit-for-bit.
|
|
28
|
-
Only nodes with **no** oracle (flash-CCE) are *forced* into the Lab. (Background +
|
|
29
|
-
post-mortem: [orpo-flash-cce-pin-leak.md](../archive/investigations/orpo-flash-cce-pin-leak.md).)
|
|
30
|
-
|
|
31
|
-
## Two axes: parity gates performance
|
|
32
|
-
|
|
33
|
-
Every optimization has **two independent coordinates**, and conflating them is what
|
|
34
|
-
made the flag surface unreadable:
|
|
35
|
-
|
|
36
|
-
1. **Parity** — *which reference does it reproduce bit-for-bit?* This sets the
|
|
37
|
-
**lowest level** it may live in: matches mlx-lm → L1; matches mlx-optiq but not
|
|
38
|
-
mlx-lm → L2; matches neither → no-oracle, Lab-gated (KL + tests).
|
|
39
|
-
2. **Performance** — *is it the fastest correct way?* This sets whether it's the
|
|
40
|
-
**default (on)** within its level, or a kept-but-**off** opt-in.
|
|
41
|
-
|
|
42
|
-
**Parity gates performance.** A faster kernel that breaks mlx-lm parity is simply
|
|
43
|
-
*not in L1* — it bubbles up to whatever level its parity allows. You never trade the
|
|
44
|
-
guarantee for speed silently; you change levels, and the user opts in. The level is
|
|
45
|
-
your floor of trust; the default within it is the best way to honor that floor.
|
|
46
|
-
|
|
47
|
-
### Decision procedure for any new optimization
|
|
48
|
-
|
|
49
|
-
1. **Measure its parity** (run it both ways, compare — "one flag, one measurement
|
|
50
|
-
per lever"). The lowest oracle it matches bit-for-bit is its level ceiling.
|
|
51
|
-
2. **Measure its performance** vs that level's current default:
|
|
52
|
-
- **faster AND holds the guarantee** → it becomes the **default (on)** for that level.
|
|
53
|
-
- **slower but still correct** → **keep it as a documented default-off flag**
|
|
54
|
-
(optionality for A/B; never the default, never deleted).
|
|
55
|
-
- **breaks the guarantee but offers a real tradeoff** (e.g. 2× tok/s at KL
|
|
56
|
-
0.0015) → a **Lab item** (env-flag, bench + expiry); it earns a place only
|
|
57
|
-
by beating the L1 baseline in a paired A/B.
|
|
58
|
-
|
|
59
|
-
Worked examples:
|
|
60
|
-
- *5% less memory, still bit-for-bit with mlx-lm* → **default-on in L1.**
|
|
61
|
-
- *2× tok/s, but KL 0.0015 to mlx-lm* → **Lab item** (proves itself or expires).
|
|
62
|
-
- *another way, 3% slower, still correct* → **kept, off in every level.**
|
|
63
|
-
|
|
64
|
-
### Our decode levers under this rule (today)
|
|
65
|
-
|
|
66
|
-
| lever | parity | perf | placement |
|
|
67
|
-
|---|---|---|---|
|
|
68
|
-
| `compiled-decode` | bit-exact w/ uncompiled (`compiled-decode.test`) | faster | **default-on, every level** |
|
|
69
|
-
| `perf-kernel` | envelope-gated, no bit-exact oracle (was gated ≥56/64 teacher-forced argmax vs our own frozen compat trajectory) | regressed e4b 0.62–0.93× | **DELETED 2026-07-05, [unified-engine-frontier-plan.md §6](unified-engine-frontier-plan.md)** (kernel + frozen-oracle tests removed) |
|
|
70
|
-
| `fused-sdpa` | matches optiq bit-for-bit, not mlx-lm (`fused-sdpa.test` tier-a goldens) | faster | **default follows `--kv-quant`** (on for `config` — the L2 bridge; off for uniform/bf16) |
|
|
71
|
-
| `fused-decode` | (no oracle) | ~1.00× (`optimization_plan.md`) | **DELETED 2026-07-05, [unified-engine-frontier-plan.md §6](unified-engine-frontier-plan.md)** |
|
|
72
|
-
|
|
73
|
-
So `--l1/--l2` is the **parity** axis (the guarantee), and the defaults within
|
|
74
|
-
each tier are the **performance** axis (the fastest kernel that still holds it).
|
|
75
|
-
2026-07-05 outcome ([unified-engine-frontier-plan.md §6](unified-engine-frontier-plan.md)):
|
|
76
|
-
no output-changing decode kernel beat the L1 baseline in a paired A/B, so `--l3`
|
|
77
|
-
was deleted as a product mode (the flag now hard-errors) along with its kernels
|
|
78
|
-
(perf-kernel, fused-decode). The no-oracle territory — HLG sampler, expert
|
|
79
|
-
offload, batched mixed-precision — is now the **Lab**: env-flag experiments with
|
|
80
|
-
a bench + expiry, gated by KL + quality, off the decode axis.
|
|
81
|
-
|
|
82
|
-
> History: commit `f1bf5cc` put the perf kernel in the L2 preset claiming
|
|
83
|
-
> "perf-kernel-oracle.test asserts the fused kernel == the frozen optiq goldens".
|
|
84
|
-
> Both halves were wrong — that golden is frozen from mlx-bun's own compat engine,
|
|
85
|
-
> and the gate is argmax-agreement, not equality — so 2026-07-01 restored the tier
|
|
86
|
-
> rule: the bare tier is the guarantee; perf kernels are opt-ins within it.
|
|
87
|
-
> Deletion outcome: the perf kernel never beat the L1 baseline in a paired A/B
|
|
88
|
-
> (regressed e4b 0.62–0.93×; only win 12B@16k +6% with a KL WARN), so it was
|
|
89
|
-
> DELETED 2026-07-05 with its frozen-oracle tests
|
|
90
|
-
> ([unified-engine-frontier-plan.md §6](unified-engine-frontier-plan.md)).
|
|
91
|
-
|
|
92
|
-
## Why this matters for CLI flags
|
|
93
|
-
|
|
94
|
-
We are accumulating a **lot** of flags (`--grad-accum`, `--grad-clip`, `--seg`,
|
|
95
|
-
`--no-flash`, `--no-prefix`, `--no-segment`, `--lambda`, `--val-size`, kv-quant
|
|
96
|
-
knobs, sampler knobs, …). The problem isn't any one flag — it's the **combinatorics**
|
|
97
|
-
and the fact that **some are never really a choice** (always-on guards, or the only
|
|
98
|
-
viable route). The tier-tagged DAG makes each flag legible by asking: **what route
|
|
99
|
-
does this flag push the computation down, and what tier is that route?**
|
|
100
|
-
|
|
101
|
-
A flag falls into exactly one of three buckets:
|
|
102
|
-
|
|
103
|
-
| flag selects… | tier effect | what it IS | action |
|
|
104
|
-
|---|---|---|---|
|
|
105
|
-
| the only viable route (no alternative node) | — | not a choice | **always-on default; drop the flag** |
|
|
106
|
-
| between two routes at the SAME tier / same oracle | none | cosmetic / safety guard | **default it; expose only if it ever fails** |
|
|
107
|
-
| between a **lower-tier** route and a **no-oracle (Lab)** route | **L1/L2 ↔ Lab** | a real **parity ⇄ optimization** knob | **keep, document as such, group by tier** |
|
|
108
|
-
|
|
109
|
-
So the DAG lets us answer "**what flags push down THAT route**" for any path, and
|
|
110
|
-
from that:
|
|
111
|
-
|
|
112
|
-
1. **Prune always-on flags to defaults** — anything whose alternative route is
|
|
113
|
-
never selected, or whose toggle doesn't change the tier, shouldn't be a flag.
|
|
114
|
-
2. **Surface the real axis.** The flags that genuinely matter all toggle
|
|
115
|
-
*bit-exact-lower-tier ⇄ our-no-oracle-optimization*. Those could collapse into a small
|
|
116
|
-
number of intent-level switches (e.g. a single `--exact` that forces every node
|
|
117
|
-
down to its lowest tier, vs the optimized default) instead of N independent
|
|
118
|
-
kernel toggles the user has to understand individually.
|
|
119
|
-
3. **Group the rest by what they trade** — memory (`--seg`, all bit-exact → not a
|
|
120
|
-
parity choice, a memory↔compute knob), hyperparameters (`--lambda`, `--lr` →
|
|
121
|
-
training knobs, orthogonal to tier), etc.
|
|
122
|
-
|
|
123
|
-
### Worked classification (current training flags)
|
|
124
|
-
|
|
125
|
-
- `--grad-clip 1.0` → standard guard, L1, **always-on default** (not a route choice).
|
|
126
|
-
- `--seg N` / `--no-segment` → memory↔compute, **bit-exact either way** → a *memory*
|
|
127
|
-
knob, **not** a parity choice. Should read as "memory budget," not "exactness."
|
|
128
|
-
- `--grad-accum N` → changes the accumulation route (L1; smoother grads) → a real
|
|
129
|
-
*training-quality* knob.
|
|
130
|
-
- `--no-flash` → **no-oracle flash-CCE → L2 MLX fused head** → a genuine *parity ⇄
|
|
131
|
-
perf* fallback (the one place the head leaves the no-oracle class).
|
|
132
|
-
- `--no-prefix` → **no-oracle prefix-share → bit-exact two-forward** → a *perf* knob
|
|
133
|
-
with no numerical cost.
|
|
134
|
-
- `--lambda` → ORPO hyperparameter (pure no-oracle) → a *training* knob.
|
|
135
|
-
|
|
136
|
-
The pattern: most flags are **memory/training knobs or always-on guards**; only a
|
|
137
|
-
couple are true **parity ⇄ optimization** toggles. Today they're a flat list the
|
|
138
|
-
user must reason about individually; tier-tagging lets us present them as what they
|
|
139
|
-
actually are.
|
|
140
|
-
|
|
141
|
-
## Roadmap to make it executable
|
|
142
|
-
|
|
143
|
-
The map is hand-authored today. To make the framework *enforce* itself:
|
|
144
|
-
|
|
145
|
-
1. **Derive the graph from code** — extract the call graph + per-node op/oracle tags
|
|
146
|
-
so it stays in sync and is **queryable** ("every no-oracle node", "paths that
|
|
147
|
-
cross into the Lab", "the pure-L1 subgraph", "flags that select route X").
|
|
148
|
-
2. **CI-enforce the gate** — a node tagged L1 must have a passing bit-exact parity
|
|
149
|
-
test vs mlx-lm; if it can't, it's a bug or must be re-tagged no-oracle. "The
|
|
150
|
-
oracle is the gate" becomes a check, not a principle.
|
|
151
|
-
3. **Shrink the no-oracle surface** — audit whether anything in the Lab island is
|
|
152
|
-
actually bit-exact-able and just unproven (e.g. the prefix-shared *forward* is
|
|
153
|
-
already bit-exact — `prefix-shared-parity.ts` — so only the encode-once *fold*
|
|
154
|
-
is truly no-oracle). Minimizing it minimizes what needs KL/quality gates.
|
|
155
|
-
4. **Generate the flag surface from the graph** — once flags map to routes, the CLI
|
|
156
|
-
help + defaults can be derived: always-on routes have no flag, parity⇄opt routes
|
|
157
|
-
collapse to intent switches, knobs group by what they trade.
|
|
158
|
-
|
|
159
|
-
End state: instead of a growing flat list of flags the user memorizes, a small set
|
|
160
|
-
of intent-level switches whose meaning is exactly "which tier route does the compute
|
|
161
|
-
take," backed by a DAG that's checked in CI.
|
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
# Repo cleanup plan
|
|
2
|
-
|
|
3
|
-
Status: PLANNED (2026-07-02, written post-v0.0.10). The audit behind it:
|
|
4
|
-
`.git` was 179 MB, driven by 497 machine-specific golden `.bin` blobs
|
|
5
|
-
(~290 MB working tree, every regen compounding history); root has ~30
|
|
6
|
-
entries of mixed tracked/untracked provenance; `repro/` was undocumented.
|
|
7
|
-
Already done in v0.0.10: `.bin` goldens untracked + gitignored +
|
|
8
|
-
`goldens/README.md` policy; CLAUDE.md root-layout map. This plan is the
|
|
9
|
-
rest, phased so each step is safe, reversible (except C1, which is the
|
|
10
|
-
point), and independently shippable.
|
|
11
|
-
|
|
12
|
-
Principle: the hygiene discipline IS the product edge — every phase ends
|
|
13
|
-
with a guardrail so the mess can't re-form, not just a one-time sweep.
|
|
14
|
-
|
|
15
|
-
## What can actually be lost? Nothing. (Read this before re-litigating.)
|
|
16
|
-
|
|
17
|
-
Every binary this plan untracks or purges survives in MULTIPLE independent
|
|
18
|
-
places, so "we might lose the goldens/fixtures" is not a reason to stall:
|
|
19
|
-
|
|
20
|
-
1. **Both dev laptops' working trees** — `git rm --cached` and `.gitignore`
|
|
21
|
-
never touch disk; the files sit exactly where they were.
|
|
22
|
-
2. **Git history** — until Phase C runs, every previously-committed byte is
|
|
23
|
-
still in `.git` and recoverable with `git show <rev>:<path>`.
|
|
24
|
-
3. **The Phase-C mirror backup** — `scripts/history-rewrite.sh` refuses to
|
|
25
|
-
rewrite before writing `~/mlx-bun-history-backup-<date>.tar` (a full
|
|
26
|
-
pre-rewrite mirror, kept forever). Even after the purge, every historical
|
|
27
|
-
blob is one `tar -x` away.
|
|
28
|
-
4. **Regen scripts** — goldens regenerate from `scripts/regen-*` + the
|
|
29
|
-
oracle venv; the universal-rope fixtures from their gen script.
|
|
30
|
-
5. **Pinned-hash fetch** — the two LoRA adapter fixtures are sha256-pinned
|
|
31
|
-
in `scripts/fetch-test-fixtures.sh`, sourced from the `test-fixtures-v1`
|
|
32
|
-
GitHub release (one command to create — see the script header) or
|
|
33
|
-
either laptop.
|
|
34
|
-
|
|
35
|
-
And no test can break: every suite that reads a binary fixture gates on
|
|
36
|
-
FILE PRESENCE and skips cleanly when it's absent (goldens via
|
|
37
|
-
tests/goldens.ts, LoRA via `haveAdapters`, universal-rope via its own
|
|
38
|
-
check). Untracked ≠ deleted ≠ unrunnable.
|
|
39
|
-
|
|
40
|
-
## Phase A — root consolidation (30 min, zero risk) — DONE 2026-07-02
|
|
41
|
-
|
|
42
|
-
- `[x]` **A1. `lab/`**: `git mv repro lab/repro && git mv spikes lab/spikes`.
|
|
43
|
-
One home for "code that proves a point but isn't the product": upstream
|
|
44
|
-
bug reproductions and feasibility probes. Update the CLAUDE.md layout map
|
|
45
|
-
+ the two docs that reference `repro/` (grep first: `grep -rn "repro/"
|
|
46
|
-
docs/ *.md`).
|
|
47
|
-
- Also updated every path reference (CLAUDE.md, PLAN.md, PLAN-archive.md,
|
|
48
|
-
docs/archive/investigations/lab-build-journal.md, docs/design/docs-reorg-plan.md,
|
|
49
|
-
and the `repro/`-pointing comments in src/mlx/{ffi,array,autograd,ops}.ts
|
|
50
|
-
+ tests/{ffi-jit,train-autograd}.test.ts + the repro's own ISSUE.md).
|
|
51
|
-
- `[x]` **A2. root strays** (all untracked; no git surgery): delete
|
|
52
|
-
`train.log`; move `benchmarks-e4b-flag-matrix-2026-06-15.json` into
|
|
53
|
-
`reports/`; confirm `.DS_Store` is gitignored globally.
|
|
54
|
-
- `train.log` deleted; benchmark json moved to `reports/`; `.DS_Store`
|
|
55
|
-
already globally gitignored (`.gitignore`).
|
|
56
|
-
- `[x]` **A3. `archive/` → `docs/archive/`**: the two frozen HTML reports
|
|
57
|
-
are docs, not a root concern. `git mv`, update CLAUDE.md map.
|
|
58
|
-
- Exit: root = code (`src tests scripts`), product surfaces (`docs website
|
|
59
|
-
packaging bin extensions`), fixtures (`goldens fixtures lab`), meta
|
|
60
|
-
(README/PLAN/STATUS/CLAUDE + configs), and gitignored working dirs. Every
|
|
61
|
-
tracked root entry appears in the CLAUDE.md layout map.
|
|
62
|
-
|
|
63
|
-
## Phase B — tracked-content policy + guardrail (1 h) — DONE 2026-07-02 (B2 closed same day)
|
|
64
|
-
|
|
65
|
-
- `[x]` **B1. Binary-in-git gate**: `scripts/check-hygiene.ts` — FAILS on
|
|
66
|
-
any tracked file that is (a) >1 MB, or (b) matches `*.bin|*.safetensors|
|
|
67
|
-
*.dylib|*.gguf|*.metallib` — outside an explicit, size-capped, justified
|
|
68
|
-
allowlist. Wired into `scripts/test.sh` (runs before tests) AND CI
|
|
69
|
-
(`.github/workflows/ci.yml` `hygiene` job, gates typecheck). Supports
|
|
70
|
-
`--staged` for pre-commit use. This is the "can't re-form" guarantee.
|
|
71
|
-
- `[x]` **B2. fixtures/ audit**: 13 MB, 2 tracked `.safetensors`.
|
|
72
|
-
Decision per file:
|
|
73
|
-
- `fixtures/adapters/{upper,french}/adapters.safetensors` (6.6 MB each):
|
|
74
|
-
KEEP tracked, on the gate's explicit allowlist (cap 7 MB). They are
|
|
75
|
-
stable one-time-trained LoRA adapter INPUTS (trained 2026-06-10, never
|
|
76
|
-
churned) for the opt-in `MLX_BUN_TEST_LORA` hot-swap test, which also
|
|
77
|
-
requires the e4b base. The tracked `adapter_config.json` + `data-*/`
|
|
78
|
-
jsonl fully describe how to reproduce them; a bit-exact regen script
|
|
79
|
-
(trainer-of-record: optiq `lora train` vs mlx-lm — the saved config is
|
|
80
|
-
ambiguous) is a Josh task to confirm + smoke. NOT untracked now because
|
|
81
|
-
an untested untrack would break the lora hot-swap parity test, and these
|
|
82
|
-
do not churn (the goldens-bloat problem was *churning* artifacts). This
|
|
83
|
-
is a deliberate allowlist exception, not the default `fixtures/ ≤ 2 MB`
|
|
84
|
-
policy.
|
|
85
|
-
- **CLOSED later the same day**: untracked + gitignored after pinning the
|
|
86
|
-
bytes by sha256 in `scripts/fetch-test-fixtures.sh` (fetch from the
|
|
87
|
-
`test-fixtures-v1` release / either laptop / pre-rewrite history; the
|
|
88
|
-
gated LoRA test skips cleanly when absent — verified). The regen-
|
|
89
|
-
ambiguity concern was real but orthogonal: preservation is by pinned
|
|
90
|
-
original bytes, not by retraining. Allowlist entry removed; gate green
|
|
91
|
-
with ZERO multi-MB binaries tracked.
|
|
92
|
-
- `[x]` **B3. tests/ binary audit**: 27 tracked `.bin` under
|
|
93
|
-
`tests/fixtures/universal-rope/` (4–8 KB each, ~108 KB total). All are
|
|
94
|
-
model-free CI-load-bearing bit-exact oracle fixtures (used by
|
|
95
|
-
`tests/universal-rope.test.ts`, which skips cleanly if absent),
|
|
96
|
-
regenerated by `scripts/gen-universal-rope-fixtures.py`. STAY, on the
|
|
97
|
-
allowlist (cap 16 KB each). Also allowlisted: `tests/fixtures/qwen-delta-golden.json`
|
|
98
|
-
(1.08 MB text JSON golden, model-free, regen script exists).
|
|
99
|
-
- Exit: `git ls-files | grep -E '\.(bin|safetensors|dylib)$'` returns only
|
|
100
|
-
allowlisted entries; the gate enforces it forever.
|
|
101
|
-
|
|
102
|
-
## Phase C — history rewrite (the 179 MB) — DONE 2026-07-02 (Josh go)
|
|
103
|
-
|
|
104
|
-
Executed 2026-07-02 on Josh's explicit go. Result: `.git` **182 MB →
|
|
105
|
-
20 MB**. Notes on what actually ran (vs the sketch below):
|
|
106
|
-
|
|
107
|
-
- C1: dry-run `--analyze` confirmed goldens `.bin` = the entire bloat.
|
|
108
|
-
Actual invocation, narrower than sketched (no size threshold needed):
|
|
109
|
-
`git filter-repo --invert-paths --path-glob 'goldens/*.bin'
|
|
110
|
-
--path-glob 'goldens/*/*.bin'` — both globs needed because git
|
|
111
|
-
pathspec `**` doesn't span the flat + one-deep layout; covers all 497
|
|
112
|
-
historical blobs. Verified pre-push: 343 commits in/out, HEAD tree
|
|
113
|
-
bit-identical to original, zero `.bin` left anywhere in goldens
|
|
114
|
-
history, tags remapped.
|
|
115
|
-
- C2: force-pushed main + branches + tags. Mirror backup at
|
|
116
|
-
`~/mlx-bun-mirror-backup-2026-07-02.tar.gz` (143 MB) — delete once
|
|
117
|
-
both boxes verified. M1 Max done: fetch --force + reset --hard,
|
|
118
|
-
surviving branches remapped, stale agent-session worktrees/branches
|
|
119
|
-
deleted (Josh: only main matters), stray `refs/codex`/`refs/original`
|
|
120
|
-
pruned, reflog expired, gc'd to 20 MB. **M4 Pro remaining (Josh)**:
|
|
121
|
-
`git fetch origin --tags --force --prune && git reset --hard
|
|
122
|
-
origin/main` (or fresh clone).
|
|
123
|
-
- C3: `v0.0.10` resolves; release assets untouched (they live outside
|
|
124
|
-
git history); hygiene gate green; regen path proven live —
|
|
125
|
-
`bun scripts/regen-fused-sdpa-goldens.ts` rewrote
|
|
126
|
-
`goldens/apple-m1-max/fused-sdpa.{json,bin}`. The full suite could not
|
|
127
|
-
be run from the executing session (tooling restriction); run it once
|
|
128
|
-
per box as the final check.
|
|
129
|
-
|
|
130
|
-
**Now ONE COMMAND**: `scripts/history-rewrite.sh` (dry-run by default,
|
|
131
|
-
`MLX_BUN_REWRITE_GO=1` to execute) — it enforces every precondition below,
|
|
132
|
-
writes the permanent mirror backup first, purges goldens `.bin` + the
|
|
133
|
-
adapter safetensors from history, force-pushes, and prints the re-clone
|
|
134
|
-
steps for the other laptop.
|
|
135
|
-
|
|
136
|
-
One-time, destructive-by-design, ~15 min total. Preconditions: BOTH
|
|
137
|
-
laptops fully pushed (verify `git log origin/main..HEAD` empty on each);
|
|
138
|
-
no open PRs; a fresh `git clone --mirror` tarball kept as belt-and-
|
|
139
|
-
suspenders until both machines are re-cloned.
|
|
140
|
-
|
|
141
|
-
- `[x]` **C1. rewrite**: `git filter-repo --strip-blobs-bigger-than 200K
|
|
142
|
-
--path-glob 'goldens/**/*.bin' --invert-paths` (exact invocation to be
|
|
143
|
-
dry-run first with `--analyze`; target: drop all historical goldens
|
|
144
|
-
`.bin` while keeping every text file's history intact).
|
|
145
|
-
- `[x]` **C2. force-push** `main` (+ tags), re-clone on BOTH laptops
|
|
146
|
-
(M1 Max + M4 Pro), re-link local-only assets (nothing tracked is lost —
|
|
147
|
-
the blobs being dropped are regenerable and already untracked at HEAD).
|
|
148
|
-
- `[x]` **C3. verify**: `du -sh .git` (expect ~15–25 MB), `bash
|
|
149
|
-
scripts/test.sh` green, one `scripts/regen-*` smoke to prove the regen
|
|
150
|
-
path, tag `v0.0.10` still resolves + release assets untouched (GitHub
|
|
151
|
-
releases store assets outside git history).
|
|
152
|
-
- NOT doing LFS: 290 MB of churning machine-specific artifacts is exactly
|
|
153
|
-
what LFS quotas punish, and the regen scripts make the artifacts
|
|
154
|
-
reproducible — a fixture server is overkill for a two-laptop project.
|
|
155
|
-
- Exit: fresh clone < 30 MB ✓ (20 MB); M1 Max on rewritten history ✓;
|
|
156
|
-
M4 Pro pending its one-line reset (see notes above).
|
|
157
|
-
|
|
158
|
-
## Phase D — docs debt (follow-the-map, 1–2 h, independent) — gate DONE 2026-07-02
|
|
159
|
-
|
|
160
|
-
- `[~]` **D1.** PLAN.md is 139 KB — move closed phases older than Phase 17
|
|
161
|
-
into PLAN-archive.md (the established pattern; keep PLAN.md scannable).
|
|
162
|
-
- **Finding (2026-07-02):** no candidates currently. Every phase <17 in
|
|
163
|
-
PLAN.md (6, 7, 12, 13, 14, 15, 16) is marked `[~]` or `[ ]` — none are
|
|
164
|
-
`[x]`/CLOSED, and the established archive pattern archives only
|
|
165
|
-
`[x]`-marked phases. Phases 6 and 16 have *all* their checkboxes
|
|
166
|
-
checked but carry a deliberate `[~]`; reclassifying them to closed +
|
|
167
|
-
archiving is a Josh state-call (they may have ongoing narrative
|
|
168
|
-
threads). The closed `oMLX` phase (2026-07-02) and Phase 19 are
|
|
169
|
-
*newer* than 17, so they stay in PLAN.md as recent history per the
|
|
170
|
-
criterion. Revisit D1 when a <17 phase is confirmed closed.
|
|
171
|
-
- `[x]` **D2.** `docs/archive/investigations/curve-runs/` + two untracked HTML
|
|
172
|
-
strays in docs/investigations: gitignore or move under `reports/`.
|
|
173
|
-
- Already satisfied: `docs/archive/investigations/curve-runs/` is gitignored
|
|
174
|
-
(`.gitignore`), and the four HTML strays (`curve-terrain.html`,
|
|
175
|
-
`hlg-ab.html`, `hlg-report.html`, `hlg-explorer.html`) are gitignored
|
|
176
|
-
via `docs/archive/investigations/*.html`. No action needed.
|
|
177
|
-
- `[x]` **D3.** The CLAUDE.md doc-map lists are hand-maintained; add a
|
|
178
|
-
`scripts/check-hygiene.ts` assertion that every `docs/**/*.md` appears in
|
|
179
|
-
the map (same guardrail spirit as B1).
|
|
180
|
-
- Implemented in `check-hygiene.ts` (`docs-map` check). First run caught
|
|
181
|
-
10 drifted docs (3 design, 1 investigation, 6 planning) — all added to
|
|
182
|
-
the CLAUDE.md map. Gate now green; CI enforces it on every push/PR.
|
|
183
|
-
|
|
184
|
-
## Ordering & ownership
|
|
185
|
-
|
|
186
|
-
A and D anytime (safe, agent-executable solo) — DONE 2026-07-02. B gate
|
|
187
|
-
DONE 2026-07-02 (B2 adapter-untrack deferred to Josh — see B2 finding).
|
|
188
|
-
C executed 2026-07-02 on Josh's explicit go — only remaining tail is the
|
|
189
|
-
M4 Pro reset + deleting the backup tarball once both boxes are green.
|
|
190
|
-
|
|
191
|
-
## Phase D — work-artifact hygiene + rules (2026-08-18) — DONE
|
|
192
|
-
|
|
193
|
-
Trigger: Josh — "this is a software project; we should not be adding all
|
|
194
|
-
these random work things into it." Compared against ml-explore/mlx-lm's
|
|
195
|
-
root (9 files, 4 dirs, zero work artifacts).
|
|
196
|
-
|
|
197
|
-
- **D1 root sweep**: six tracked `benchmarks-serve-2026-07-0*.md` dumps
|
|
198
|
-
git-rm'd (they leaked past a gitignore pattern that only covered
|
|
199
|
-
`benchmarks-h2h-*`); seven untracked root bench artifacts moved to
|
|
200
|
-
`reports/bench-archive/`; `tmp/` deleted. `.gitignore` now blocks
|
|
201
|
-
`/benchmarks-*`, `/bench-*`, `/tmp/`, `/*.log` at root.
|
|
202
|
-
- **D2 guardrail**: `scripts/check-hygiene.ts` gained a third gate —
|
|
203
|
-
tracked ROOT files must be on an explicit allowlist (16 meta files).
|
|
204
|
-
- **D3 CONTRIBUTING.md**: the rules (what-goes-where table; no dated
|
|
205
|
-
artifacts; models → `~/models`; docs lifecycle; PLAN/STATUS discipline;
|
|
206
|
-
external oracle venv is not a repo).
|
|
207
|
-
- **D4 docs archive**: all 20 `docs/investigations/` →
|
|
208
|
-
`docs/archive/investigations/` (investigations document finished work);
|
|
209
|
-
5 release-notes + journal + 2 chat-ui visions + 2 pass-plans →
|
|
210
|
-
`docs/archive/planning/`; `hlg-sampling.md` (self-declared superseded) →
|
|
211
|
-
`docs/archive/`. All inbound links rewritten; docs-map gate green.
|
|
212
|
-
- **D5 journal trim (rule 4)**: STATUS.md 1648 → 106 lines (Colibri
|
|
213
|
-
gate-history + all "where we were" + the stale July ranked backlog →
|
|
214
|
-
PLAN-archive under "STATUS archive 2026-08-18"); PLAN.md 3815 → 2679
|
|
215
|
-
(8 closed `[x]` phase blocks → PLAN-archive with one-line pointers).
|
|
216
|
-
- Model artifacts moved out of the tree entirely (`~/models/`) earlier
|
|
217
|
-
the same day.
|
|
218
|
-
|
|
219
|
-
Remaining (curated, not mechanical — needs judgment per file):
|
|
220
|
-
- `docs/design/` still holds 46 docs; many describe LANDED work and
|
|
221
|
-
should archive under rule 3 (candidates: optimization_plan,
|
|
222
|
-
faithful-base superseded pair, diffusion/audio/omlx/dspark docs whose
|
|
223
|
-
phases closed). Do this per-doc with link fixes, not as a bulk move.
|
|
224
|
-
- `docs/planning/` leftovers (chunk-task-roadmap, curve-sampler-research-
|
|
225
|
-
plan, memory-docs-and-dag-plan, pi-integration-review) — keep-or-archive
|
|
226
|
-
per Josh.
|
|
227
|
-
- `scripts/experiments/` = 199 files; prune ones whose findings are fully
|
|
228
|
-
recorded in archived docs (keep regen/oracle scripts).
|
|
229
|
-
- `runs/` is 32 GB machine-local (Colibri evidence + quant lab arms) —
|
|
230
|
-
delete lab arms once the TQ/GPTQ program closes; Colibri evidence dirs
|
|
231
|
-
are cited from the archive and stay.
|
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
# Runtime isolation — the AI may crash, the UI never may
|
|
2
|
-
|
|
3
|
-
Status: **P1 LANDED 2026-07-05** (opt-in `--isolate`; default flip after
|
|
4
|
-
bake). Phase 1 (yields) fully landed same day (the gateway onToken hop
|
|
5
|
-
existed; prefill chunk yields added). **ARCHITECTURE DECISION (2026-07-05,
|
|
6
|
-
deviates from the IPC sketch below):** the engine child is the ENTIRE
|
|
7
|
-
existing server on a unix domain socket; the parent is a thin reverse
|
|
8
|
-
proxy (src/serve/isolate.ts). Rationale: the gateway-as-IPC-client cut
|
|
9
|
-
would serialize grammar controllers (WASM), vision embeddings (GPU
|
|
10
|
-
arrays), sampler closures, and prompt-cache hooks over a message protocol
|
|
11
|
-
— weeks of re-plumbing; HTTP-over-UDS reuses the whole serving stack
|
|
12
|
-
verbatim, streams SSE for free, and cancellation is abort propagation.
|
|
13
|
-
The inter-process API is the existing /v1 surface — no second protocol.
|
|
14
|
-
**Measured (M1 Max, cpm5, paired)**: proxied 197.2 vs direct 197.9 tok/s
|
|
15
|
-
(−0.4%, noise), TTFT +2 ms, SSE per-token granularity preserved (96 reads
|
|
16
|
-
/ 96 tokens), parent answers in 0.6 ms mid-decode (criterion: <50 ms),
|
|
17
|
-
child kill → 502 + respawn (gated model-free + E2E:
|
|
18
|
-
tests/isolate-proxy.test.ts, tests/isolate-e2e.test.ts). Not proxied yet:
|
|
19
|
-
/ws/chat (501 + pointer). P2 = child-per-model pool (multi-model switch by
|
|
20
|
-
spawn-overlap); P3 = GPU-lease unification. The original design follows.
|
|
21
|
-
Owner: serving layer / runtime
|
|
22
|
-
Default today: inference runs in-process unless `--isolate` is passed.
|
|
23
|
-
|
|
24
|
-
## Goal
|
|
25
|
-
|
|
26
|
-
One `mlx-bun` process plays three or four roles at once:
|
|
27
|
-
|
|
28
|
-
1. **Serves the web UI** (static assets, the chat page).
|
|
29
|
-
2. **Serves the API** (`/v1/*` for the web UI, the CLI, and external
|
|
30
|
-
agents).
|
|
31
|
-
3. **Runs the AI** (weight load, prefill, decode — the heavy GPU work).
|
|
32
|
-
|
|
33
|
-
One executable doing all of this is the product's whole charm, and for
|
|
34
|
-
most people, most of the time, it is perfect. It breaks in one specific
|
|
35
|
-
way: when the AI is working hard — say an agentic pipeline classifying a
|
|
36
|
-
photo library — the **web UI lags**. Page refreshes stall, `/status`
|
|
37
|
-
polls hang, chat messages queue behind the GPU.
|
|
38
|
-
|
|
39
|
-
Waiting for a *chat reply* is fine and expected (local inference takes
|
|
40
|
-
time; hosted services take up to a minute too). A **laggy UI is neither
|
|
41
|
-
normal nor acceptable.** The UI must stay responsive no matter what the
|
|
42
|
-
AI is doing — and if the AI process dies (an uncatchable GPU OOM), the UI
|
|
43
|
-
must survive and report it, not go down with it.
|
|
44
|
-
|
|
45
|
-
## Root cause — blocking work on the event-loop thread
|
|
46
|
-
|
|
47
|
-
The server is a single Bun event loop. The governing principle of an
|
|
48
|
-
event loop is: **the loop thread may only ever do non-blocking work.**
|
|
49
|
-
We violate it by running the synchronous MLX FFI eval loop *on* that
|
|
50
|
-
thread.
|
|
51
|
-
|
|
52
|
-
Concretely:
|
|
53
|
-
|
|
54
|
-
- **The per-token GPU read is a blocking FFI call.** Each decode step
|
|
55
|
-
ends with `ops.itemUint32(cur)` → `a.eval()` → `mlx_eval`
|
|
56
|
-
(`src/mlx/ops.ts:471`), which parks the single JS thread until the GPU
|
|
57
|
-
returns. Compute is dispatched async (`asyncEvalAll` + decode
|
|
58
|
-
pipelining, `src/generate.ts:386`) — good — but reading the value back
|
|
59
|
-
over `bun:ffi` is a hard, synchronous wait on the main thread. There is
|
|
60
|
-
no thread handoff.
|
|
61
|
-
- **The loop yields only *microtasks*, not *macrotasks*.** Bun services
|
|
62
|
-
I/O — accepting connections, answering `/status`, serving the page —
|
|
63
|
-
only at macrotask boundaries. A bare `yield` in the async generator
|
|
64
|
-
plus an `await` of an already-resolved value drains the microtask
|
|
65
|
-
queue without ever reaching the I/O phase.
|
|
66
|
-
- **Streaming partially hides this; non-streaming does not.** The SSE
|
|
67
|
-
path's `onToken` returns `new Promise(r => setImmediate(r))` ~every
|
|
68
|
-
25 ms (`src/server.ts:1341`) — a real macrotask yield, so the page
|
|
69
|
-
breathes. The **non-streaming** path's `onToken` returns `undefined`
|
|
70
|
-
every token (`src/server.ts:1407`), so the whole completion is one
|
|
71
|
-
blocking-FFI-plus-microtask chain and the loop is starved start to
|
|
72
|
-
finish.
|
|
73
|
-
- **Prefill never yields at all.** The chunked prompt eval calls
|
|
74
|
-
`ops.evalAll(...)` synchronously with no `await` between chunks
|
|
75
|
-
(`src/generate.ts:294`). A long prompt freezes the page for the full
|
|
76
|
-
prefill *even in streaming mode*.
|
|
77
|
-
|
|
78
|
-
### Why yielding alone is not the fix
|
|
79
|
-
|
|
80
|
-
A `setImmediate` yield can interleave I/O *between* tokens, but it
|
|
81
|
-
**cannot interrupt an in-flight `mlx_eval`** — that C call is atomic from
|
|
82
|
-
JS's view. Each token blocks for tens of ms; a prefill blocks one big
|
|
83
|
-
chunk (hundreds of ms to seconds) with no yield point possible inside the
|
|
84
|
-
kernel. So perfect yielding gives the UI thin inter-token slices, not
|
|
85
|
-
isolation. Adequate for "chat plus occasional"; inadequate for "agentic
|
|
86
|
-
pipeline running flat-out while I refresh the page." For true isolation
|
|
87
|
-
the blocking work must leave the thread.
|
|
88
|
-
|
|
89
|
-
## Decision — subprocess, not worker thread
|
|
90
|
-
|
|
91
|
-
Move the AI runtime off the event-loop thread. Two ways:
|
|
92
|
-
|
|
93
|
-
- **Worker thread** (Bun `Worker`): shares the process, cheapest IPC
|
|
94
|
-
(a `SharedArrayBuffer` token ring). But a GPU OOM / SIGTRAP — which our
|
|
95
|
-
own notes flag as **uncatchable** (CLAUDE.md "async GPU exec OOM is
|
|
96
|
-
uncatchable") — takes down the whole process, **including the UI
|
|
97
|
-
server**.
|
|
98
|
-
- **Subprocess** (`Bun.spawn`): crash isolation. An MLX OOM kills the
|
|
99
|
-
inference process; the UI/API server survives, reports it, and
|
|
100
|
-
respawns. Heavier IPC, but on a single-user local box a pipe/socket is
|
|
101
|
-
trivially fast next to token cadence.
|
|
102
|
-
|
|
103
|
-
**Decision: subprocess.** The product rule is "the AI may crash, the UI
|
|
104
|
-
never may," and only address-space isolation delivers that against an
|
|
105
|
-
uncatchable fault. It also reuses machinery the repo has already proven.
|
|
106
|
-
|
|
107
|
-
## We already built the blueprint — for jobs
|
|
108
|
-
|
|
109
|
-
`src/jobs/runner.ts` already does exactly this for the *other* heavy GPU
|
|
110
|
-
consumers (quantize / finetune):
|
|
111
|
-
|
|
112
|
-
- `submitSubprocess()` creates a job row and `Bun.spawn`s the binary as a
|
|
113
|
-
child (`src/jobs/runner.ts:210`, entry `src/jobs/job-entry.ts`).
|
|
114
|
-
- A single global **GPU lease** (`gpuLeaseHolder`) serializes GPU access;
|
|
115
|
-
`isGpuBusy()` / `currentGpuJob()` (`src/jobs/runner.ts:159`) expose it,
|
|
116
|
-
**and the server already gates token generation on it** so
|
|
117
|
-
quantize/finetune don't fight live inference.
|
|
118
|
-
- IPC is a file-backed SQLite job store + stdout/stderr line pumping into
|
|
119
|
-
a log + an SSE tail (`src/jobs/sse.ts`).
|
|
120
|
-
|
|
121
|
-
So the pattern — heavy GPU work → its own process → server thread stays
|
|
122
|
-
free, serialized by one lease — is established and tested. **Inference is
|
|
123
|
-
the one heavy consumer still running inline on the event-loop thread.**
|
|
124
|
-
It is the exception, and it is the one people hit on every message.
|
|
125
|
-
|
|
126
|
-
## The seam already exists
|
|
127
|
-
|
|
128
|
-
Request handlers do not call `generate()` directly — they call
|
|
129
|
-
`gateway.run(promptIds, options, onToken, vision, shape)`
|
|
130
|
-
(`src/serve/generation-gateway.ts:108`, call sites `src/server.ts:1331`
|
|
131
|
-
and `:1407`). The `GenerationGateway` is the abstraction boundary between
|
|
132
|
-
the HTTP layer and execution. **We replace what is behind it** (inline
|
|
133
|
-
`generate()` → an IPC client that talks to the inference subprocess); the
|
|
134
|
-
HTTP/SSE layer, the `onToken` contract, and the `false`-halts-generation
|
|
135
|
-
semantics are unchanged.
|
|
136
|
-
|
|
137
|
-
## Target architecture
|
|
138
|
-
|
|
139
|
-
- **Main process = pure reactor.** `Bun.serve` does static UI + the API
|
|
140
|
-
surface + routing + tokenization/templating/SSE encoding (all cheap,
|
|
141
|
-
non-blocking JS). **Zero MLX calls.** Once generation is off-thread,
|
|
142
|
-
nothing else on the main thread blocks — static serves and `/status`
|
|
143
|
-
are already async; they lag today only because the loop is
|
|
144
|
-
monopolized. So this alone makes the UI instant.
|
|
145
|
-
- **Inference runtime = a persistent subprocess** owning the model +
|
|
146
|
-
Metal context, doing prefill/decode, streaming tokens back. Persistent
|
|
147
|
-
(not spawn-per-request): weight load is the cost, can't pay it per
|
|
148
|
-
request. One process, one model, one GPU.
|
|
149
|
-
- **One GPU lease governs all consumers** — live inference, agentic API
|
|
150
|
-
calls, quantize, finetune. The GPU is the single serialized resource
|
|
151
|
-
(matching the single-user reality: chat waiting behind the photo
|
|
152
|
-
pipeline is the *acceptable* kind of waiting), while UI/API I/O is
|
|
153
|
-
fully parallel and never waits.
|
|
154
|
-
|
|
155
|
-
```
|
|
156
|
-
┌──────────────────────────────────────────────┐
|
|
157
|
-
│ main process (event loop — never blocks) │
|
|
158
|
-
│ Bun.serve: static UI · /v1/* API · routing │
|
|
159
|
-
│ tokenize · template · SSE encode │
|
|
160
|
-
│ │ gateway.run() = IPC client │
|
|
161
|
-
└────────┼──────────────────────────────────────┘
|
|
162
|
-
│ request ↓ ↑ token stream
|
|
163
|
-
┌────────┼──────────────────────────────────────┐
|
|
164
|
-
│ inference subprocess (may block freely) │
|
|
165
|
-
│ owns model + Metal · prefill · decode │
|
|
166
|
-
│ holds the GPU lease while generating │
|
|
167
|
-
└────────────────────────────────────────────────┘
|
|
168
|
-
(crash here ⇒ parent respawns; UI untouched)
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
## IPC sketch
|
|
172
|
-
|
|
173
|
-
- **Transport:** Bun supports a structured `ipc` channel on `Bun.spawn`
|
|
174
|
-
(parent ⇄ child message passing); a unix domain socket is the
|
|
175
|
-
fallback. Tokens are slow relative to message passing, so per-token
|
|
176
|
-
messages are fine; batch a few if needed. (Jobs use stdout-line + the
|
|
177
|
-
SQLite store, which is right for fire-and-forget long jobs but too
|
|
178
|
-
coarse for low-latency per-token streaming — inference wants the
|
|
179
|
-
message channel.)
|
|
180
|
-
- **Request → child:** `{ promptIds, options, shape }`. Tokenization and
|
|
181
|
-
template rendering stay in the parent (cheap); the child receives
|
|
182
|
-
token ids.
|
|
183
|
-
- **Child → parent:** a stream of `{ token }` messages, then a final
|
|
184
|
-
`{ stats }`. The parent's gateway client invokes the handler's existing
|
|
185
|
-
`onToken` per message — per-row SSE fan-out keeps working unchanged.
|
|
186
|
-
- **Cancellation / backpressure:** `onToken` returning `false` (stop
|
|
187
|
-
sequence) or a client disconnect must abort the in-flight generation —
|
|
188
|
-
send a cancel message; the child checks it at the decode-loop boundary
|
|
189
|
-
(the natural place `generate()` already yields).
|
|
190
|
-
- **Crash handling:** child `exited` non-zero ⇒ fail any in-flight
|
|
191
|
-
request with a clean 5xx, respawn the child, reload the model. The UI
|
|
192
|
-
server never noticed.
|
|
193
|
-
|
|
194
|
-
## Lease reconciliation (the one genuinely new problem)
|
|
195
|
-
|
|
196
|
-
Jobs are *transient* (spawn → run → exit, lease released on exit). The
|
|
197
|
-
inference worker is *persistent* (resident, holds the model in RAM). They
|
|
198
|
-
must share one GPU coherently:
|
|
199
|
-
|
|
200
|
-
- A quantize/finetune job needs the GPU while the inference worker is
|
|
201
|
-
resident. Options: (a) the inference worker **drains and yields** the
|
|
202
|
-
lease for the job's duration (finish current gen, pause admission,
|
|
203
|
-
release), then resumes; (b) the persistent worker **also runs the
|
|
204
|
-
jobs** (one GPU owner, no cross-process lease at all). (b) is cleaner
|
|
205
|
-
long-term but couples job lifecycle into the inference process; (a)
|
|
206
|
-
keeps jobs as-is. **Open — pick during phase 3.**
|
|
207
|
-
- On 24 GB, the resident model already dominates memory; a concurrent
|
|
208
|
-
finetune that also needs multi-GB is an OOM footgun regardless of
|
|
209
|
-
process layout. The lease must remain *exclusive*, never truly
|
|
210
|
-
concurrent on the GPU.
|
|
211
|
-
|
|
212
|
-
## What this unlocks / simplifies
|
|
213
|
-
|
|
214
|
-
- **Delete the yield hacks.** Once the loop blocks only in the child, the
|
|
215
|
-
`setImmediate` throttle (`src/server.ts:1341`) and any prefill
|
|
216
|
-
chunk-yielding (`src/generate.ts:294`) are no longer needed for UI
|
|
217
|
-
responsiveness — the hot path gets cleaner and marginally faster.
|
|
218
|
-
- **Folds into model hot-swap** (see PRODUCT_ROADMAP "Model hot-swap from
|
|
219
|
-
the UI"): the model lives in the worker, so `loadContext` and swap
|
|
220
|
-
happen there while the main thread serves a live "swapping…" state.
|
|
221
|
-
These two items converge on "the model is not on the main thread."
|
|
222
|
-
- **Flags the next blocker:** vision preprocessing (`Bun.Image` decode,
|
|
223
|
-
`src/vision/preprocess.ts`) is also main-thread CPU work and should
|
|
224
|
-
ride into the worker, or it becomes the next source of lag.
|
|
225
|
-
|
|
226
|
-
## Staging (incremental, not big-bang)
|
|
227
|
-
|
|
228
|
-
- **Phase 1 — stop the bleeding (in-process).** Macrotask-yield the
|
|
229
|
-
non-streaming `onToken` (`src/server.ts:1407`) and the prefill chunk
|
|
230
|
-
loop (`src/generate.ts:294`), mirroring what streaming already does.
|
|
231
|
-
Trivial change; kills the acute hang for the common case. Does **not**
|
|
232
|
-
isolate the UI under sustained load — that's Phase 2.
|
|
233
|
-
- **Phase 2 — inference subprocess behind the gateway.** Stand up the
|
|
234
|
-
persistent inference child + IPC; make `GenerationGateway` an IPC
|
|
235
|
-
client. Main process makes zero MLX calls. The HTTP layer is untouched.
|
|
236
|
-
This is the real fix.
|
|
237
|
-
- **Phase 3 — unify the GPU lease.** Bring inference under the same lease
|
|
238
|
-
as quantize/finetune so all GPU consumers schedule coherently; resolve
|
|
239
|
-
the persistent-vs-transient reconciliation above.
|
|
240
|
-
|
|
241
|
-
## Exit criteria
|
|
242
|
-
|
|
243
|
-
- Under a sustained generation load (e.g. a tight `/v1` loop), a parallel
|
|
244
|
-
web-UI page load and `/status` poll return in **< 50 ms** (today: stalls
|
|
245
|
-
for the full completion in non-streaming, inter-token in streaming).
|
|
246
|
-
- Killing the inference process mid-generation leaves the UI server up; a
|
|
247
|
-
fresh page load and `/status` still succeed, and the in-flight request
|
|
248
|
-
returns a clean error rather than a hang.
|
|
249
|
-
- No throughput regression vs the in-process path on the single-stream
|
|
250
|
-
decode benchmark (token IPC overhead is below noise).
|
|
251
|
-
|
|
252
|
-
## Open questions
|
|
253
|
-
|
|
254
|
-
- Bun `ipc` channel vs unix socket vs `SharedArrayBuffer` ring for the
|
|
255
|
-
token stream — measure latency/overhead before committing.
|
|
256
|
-
- One inference worker only (single GPU, single model) — confirmed by the
|
|
257
|
-
single-user model. Multiple models = future, and pairs with hot-swap.
|
|
258
|
-
- Lease reconciliation strategy (a) drain-and-yield vs (b) worker runs
|
|
259
|
-
jobs — decide in Phase 3.
|
|
260
|
-
- Where does prompt-cache live — in the worker (it holds KV bytes, must be
|
|
261
|
-
byte-capped per CLAUDE.md). Almost certainly the worker.
|
|
262
|
-
- Startup UX: parent up and serving the UI immediately; worker model load
|
|
263
|
-
happens behind a "loading…" state the parent reports.
|