mlx-bun 0.0.4

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.
Files changed (210) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +362 -0
  3. package/THIRD_PARTY_LICENSES.md +41 -0
  4. package/bin/mlx-bun.mjs +32 -0
  5. package/docs/curve-designer.html +303 -0
  6. package/docs/design/adapters-end-to-end.md +201 -0
  7. package/docs/design/compat-cli-surface-design.md +227 -0
  8. package/docs/design/docs-reorg-plan.md +152 -0
  9. package/docs/design/hlg-sampling.md +340 -0
  10. package/docs/design/memory-system.md +339 -0
  11. package/docs/design/optimization_plan.md +344 -0
  12. package/docs/design/parallel-slots.md +515 -0
  13. package/docs/design/runtime-isolation.md +247 -0
  14. package/docs/design/segmented-backward-training.md +508 -0
  15. package/docs/investigations/chunk-finetune-experiment.md +84 -0
  16. package/docs/investigations/curve-bisector-routing.md +123 -0
  17. package/docs/investigations/curve-runs/bisector-route.json +98 -0
  18. package/docs/investigations/curve-runs/bisector-trace.json +1393 -0
  19. package/docs/investigations/curve-runs/curve-terrain.json +1179 -0
  20. package/docs/investigations/curve-runs/curve-terrain.svg +42 -0
  21. package/docs/investigations/curve-terrain.html +105 -0
  22. package/docs/investigations/expert-offload-single-user-moe.md +292 -0
  23. package/docs/investigations/hlg-ab.html +34 -0
  24. package/docs/investigations/hlg-explorer.html +238 -0
  25. package/docs/investigations/hlg-figs/cliff.svg +1 -0
  26. package/docs/investigations/hlg-figs/cross.svg +1 -0
  27. package/docs/investigations/hlg-figs/edges.svg +1 -0
  28. package/docs/investigations/hlg-figs/k20-grids.svg +1 -0
  29. package/docs/investigations/hlg-figs/k20-peaks.svg +1 -0
  30. package/docs/investigations/hlg-figs/lw-curve.svg +1 -0
  31. package/docs/investigations/hlg-figs/terrain.svg +1 -0
  32. package/docs/investigations/hlg-report.html +262 -0
  33. package/docs/investigations/hlg-runs/hlg-ab.json +149 -0
  34. package/docs/investigations/hlg-runs/hlg-blind-prompts.md +87 -0
  35. package/docs/investigations/hlg-runs/hlg-blind.json +107 -0
  36. package/docs/investigations/hlg-runs/hlg-cliff.json +197 -0
  37. package/docs/investigations/hlg-runs/hlg-cross.json +505 -0
  38. package/docs/investigations/hlg-runs/hlg-edge.json +388 -0
  39. package/docs/investigations/hlg-runs/hlg-eval1.json +25 -0
  40. package/docs/investigations/hlg-runs/hlg-frontier.json +86 -0
  41. package/docs/investigations/hlg-runs/hlg-grid.json +242 -0
  42. package/docs/investigations/hlg-runs/hlg-grid2.json +343 -0
  43. package/docs/investigations/hlg-runs/hlg-lw-coarse.json +110 -0
  44. package/docs/investigations/hlg-runs/hlg-map.json +383 -0
  45. package/docs/investigations/hlg-runs/run0-original.log +159 -0
  46. package/docs/investigations/hlg-runs/runA-recommended.log +131 -0
  47. package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +26 -0
  48. package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +18 -0
  49. package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +25 -0
  50. package/docs/investigations/hlg-runs/runAD-ab-samples.log +8 -0
  51. package/docs/investigations/hlg-runs/runAE-edge-e4b.log +51 -0
  52. package/docs/investigations/hlg-runs/runAF-logit-calibration.log +10 -0
  53. package/docs/investigations/hlg-runs/runAG-eval-config2.log +16 -0
  54. package/docs/investigations/hlg-runs/runB-gain-variants.log +192 -0
  55. package/docs/investigations/hlg-runs/runC-pivots.log +162 -0
  56. package/docs/investigations/hlg-runs/runD-oetf.log +132 -0
  57. package/docs/investigations/hlg-runs/runE-eotf.log +162 -0
  58. package/docs/investigations/hlg-runs/runF-shaper-working.log +86 -0
  59. package/docs/investigations/hlg-runs/runG-shaper-sweep.log +101 -0
  60. package/docs/investigations/hlg-runs/runH-wsweep.log +176 -0
  61. package/docs/investigations/hlg-runs/runI-asweep.log +176 -0
  62. package/docs/investigations/hlg-runs/runJ-knobmap.log +49 -0
  63. package/docs/investigations/hlg-runs/runK-frontier.log +19 -0
  64. package/docs/investigations/hlg-runs/runL-widemap.log +65 -0
  65. package/docs/investigations/hlg-runs/runM-frontier-emb.log +15 -0
  66. package/docs/investigations/hlg-runs/runN-verify.log +116 -0
  67. package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +15 -0
  68. package/docs/investigations/hlg-runs/runP-grid-AxSm.log +15 -0
  69. package/docs/investigations/hlg-runs/runQ-grid-tgW.log +15 -0
  70. package/docs/investigations/hlg-runs/runR-blind-transcript.log +79 -0
  71. package/docs/investigations/hlg-runs/runS-cross-coherence.log +33 -0
  72. package/docs/investigations/hlg-runs/runT-cross-cpm5.log +33 -0
  73. package/docs/investigations/hlg-runs/runU-cross-12b.log +33 -0
  74. package/docs/investigations/hlg-runs/runV-lw-e4b.log +22 -0
  75. package/docs/investigations/hlg-runs/runW-lw-12b.log +22 -0
  76. package/docs/investigations/hlg-runs/runX-lw-cpm5.log +22 -0
  77. package/docs/investigations/hlg-runs/runY-cliff-e4b.log +26 -0
  78. package/docs/investigations/hlg-runs/runZ-cliff-12b.log +26 -0
  79. package/docs/investigations/hlg-sampling-investigation.md +970 -0
  80. package/docs/investigations/lab-build-journal.md +157 -0
  81. package/docs/investigations/pi-builtin-investigation.md +308 -0
  82. package/docs/investigations/starter-model-port-handoff.md +323 -0
  83. package/docs/planning/IDEAS.md +26 -0
  84. package/docs/planning/PRODUCT_ROADMAP.md +426 -0
  85. package/docs/planning/ResearchTopics.md +119 -0
  86. package/docs/planning/chunk-task-roadmap.md +77 -0
  87. package/docs/planning/journal.md +155 -0
  88. package/docs/reference/distribution.md +121 -0
  89. package/docs/reference/embedding.md +168 -0
  90. package/docs/reference/library-api.md +153 -0
  91. package/docs/reference/server-api.md +224 -0
  92. package/docs/reference/server-config.md +243 -0
  93. package/docs/segmented-backward-handoff.md +148 -0
  94. package/package.json +68 -0
  95. package/src/anthropic.ts +491 -0
  96. package/src/chat-template.ts +186 -0
  97. package/src/cli.ts +1075 -0
  98. package/src/config.ts +229 -0
  99. package/src/curve-sampler.ts +122 -0
  100. package/src/dataset/generators.ts +1087 -0
  101. package/src/dataset/index.ts +12 -0
  102. package/src/dataset/job.ts +81 -0
  103. package/src/dataset/llm.ts +71 -0
  104. package/src/dataset/registry.ts +528 -0
  105. package/src/download.ts +283 -0
  106. package/src/eval/README.md +86 -0
  107. package/src/eval/kl-prompts.ts +29 -0
  108. package/src/eval/kl.ts +294 -0
  109. package/src/eval/quality-db.ts +92 -0
  110. package/src/eval/runner.ts +102 -0
  111. package/src/eval/score.ts +49 -0
  112. package/src/eval/tasks/bfcl.ts +500 -0
  113. package/src/eval/tasks/gsm8k.ts +85 -0
  114. package/src/eval/tasks/hashhop.ts +109 -0
  115. package/src/eval/tasks/humaneval.ts +252 -0
  116. package/src/eval/tasks/ifeval.ts +354 -0
  117. package/src/eval/tasks/mmlu.ts +145 -0
  118. package/src/evaldb.ts +199 -0
  119. package/src/expert-offload-build.ts +77 -0
  120. package/src/expert-offload.ts +61 -0
  121. package/src/expert-trace.ts +67 -0
  122. package/src/fit.ts +254 -0
  123. package/src/generate.ts +451 -0
  124. package/src/harness-pi.ts +177 -0
  125. package/src/hf-push.ts +628 -0
  126. package/src/index.ts +22 -0
  127. package/src/jobs/db.ts +139 -0
  128. package/src/jobs/index.ts +29 -0
  129. package/src/jobs/job-entry.ts +106 -0
  130. package/src/jobs/runner.ts +304 -0
  131. package/src/jobs/sse.ts +114 -0
  132. package/src/jobs/types.ts +58 -0
  133. package/src/kv-store.ts +185 -0
  134. package/src/lora.ts +314 -0
  135. package/src/md-modules.d.ts +7 -0
  136. package/src/mlx/array.ts +237 -0
  137. package/src/mlx/autograd.ts +267 -0
  138. package/src/mlx/checkpoint.ts +116 -0
  139. package/src/mlx/compile.ts +158 -0
  140. package/src/mlx/custom-vjp.ts +140 -0
  141. package/src/mlx/ffi.ts +433 -0
  142. package/src/mlx/metal-kernel.ts +130 -0
  143. package/src/mlx/ops.ts +707 -0
  144. package/src/mmap.ts +90 -0
  145. package/src/model/batched-mask.ts +204 -0
  146. package/src/model/batched-rotating.ts +300 -0
  147. package/src/model/compiled-decode.ts +681 -0
  148. package/src/model/factory.ts +27 -0
  149. package/src/model/fingerprint.ts +33 -0
  150. package/src/model/flash-attention.ts +674 -0
  151. package/src/model/fused-decode-kernel.ts +305 -0
  152. package/src/model/fused-geglu-kernel.ts +87 -0
  153. package/src/model/gemma4-base.ts +1616 -0
  154. package/src/model/gemma4.ts +1012 -0
  155. package/src/model/generated/gemma4-12b.ts +526 -0
  156. package/src/model/generated/gemma4-26b.ts +500 -0
  157. package/src/model/generated/gemma4-e4b.ts +757 -0
  158. package/src/model/generated/index.ts +18 -0
  159. package/src/model/minicpm5.ts +278 -0
  160. package/src/model/qwen3-delta.ts +224 -0
  161. package/src/model/qwen3_5.ts +429 -0
  162. package/src/model/support.ts +46 -0
  163. package/src/native-pack.ts +152 -0
  164. package/src/pi-provider.ts +95 -0
  165. package/src/pi-terminal.ts +264 -0
  166. package/src/pi-web.ts +924 -0
  167. package/src/preflight.ts +123 -0
  168. package/src/prompt-cache.ts +113 -0
  169. package/src/quantize/allocator.ts +627 -0
  170. package/src/quantize/calibration.ts +180 -0
  171. package/src/quantize/config-writer.ts +132 -0
  172. package/src/quantize/index.ts +57 -0
  173. package/src/quantize/job.ts +136 -0
  174. package/src/quantize/quantizer.ts +462 -0
  175. package/src/quantize/safetensors-writer.ts +171 -0
  176. package/src/quantize/sensitivity.ts +234 -0
  177. package/src/registry.ts +336 -0
  178. package/src/responses.ts +774 -0
  179. package/src/safetensors.ts +158 -0
  180. package/src/sampler.ts +517 -0
  181. package/src/serve/batch-scheduler.ts +362 -0
  182. package/src/serve/generation-gateway.ts +160 -0
  183. package/src/server.ts +1891 -0
  184. package/src/spec/drafter.ts +261 -0
  185. package/src/spec/generate.ts +221 -0
  186. package/src/status-page.html +497 -0
  187. package/src/tokenizer.ts +38 -0
  188. package/src/tool-call.ts +242 -0
  189. package/src/train/dataset.ts +461 -0
  190. package/src/train/export.ts +32 -0
  191. package/src/train/forward.ts +229 -0
  192. package/src/train/index.ts +43 -0
  193. package/src/train/job.ts +148 -0
  194. package/src/train/lora-params.ts +194 -0
  195. package/src/train/loss.ts +342 -0
  196. package/src/train/merge.ts +195 -0
  197. package/src/train/optimizer.ts +176 -0
  198. package/src/train/rank.ts +101 -0
  199. package/src/train/segmented.ts +520 -0
  200. package/src/train/trainer.ts +600 -0
  201. package/src/tui.ts +161 -0
  202. package/src/vision/embedder.ts +168 -0
  203. package/src/vision/preprocess.ts +247 -0
  204. package/src/vision/prompt.ts +158 -0
  205. package/src/vision/siglip.ts +599 -0
  206. package/src/web/app.html +2471 -0
  207. package/src/web/skills/web-research/SKILL.md +44 -0
  208. package/src/web/skills.ts +59 -0
  209. package/src/web-tools.ts +426 -0
  210. package/src/weights.ts +91 -0
package/src/fit.ts ADDED
@@ -0,0 +1,254 @@
1
+ // Memory contracts — `mlx-bun fit`. Every term is deterministic:
2
+ // weights — safetensors byte sizes (registry)
3
+ // KV bytes/token — config: layers × kv_heads × head_dim × bytes;
4
+ // sliding-window layers saturate at the window
5
+ // prefill transient — chunk size we choose × calibrated bytes/token
6
+ // machine — RAM (queried) + Metal wired ceiling fraction
7
+ //
8
+ // Calibration constants come from measured runs on the reference M4 Pro
9
+ // (see PLAN.md baselines; eval DB validates predictions against peaks).
10
+
11
+ import { totalmem } from "node:os";
12
+ import type { ModelConfig } from "./config";
13
+
14
+ /** Decode-efficiency vs theoretical bandwidth ceiling, measured on the
15
+ * reference machine (24.9 tok/s vs 30.3 ceiling @600 ctx). */
16
+ export const DECODE_EFFICIENCY = 0.82;
17
+ /** MoE decode efficiency vs the active-bytes ceiling. RECALIBRATED from
18
+ * the Phase 15 cleared-machine matrix: 26B-A4B measured 54.5 tok/s
19
+ * (python 55.7 — parity) vs ~71 tok/s raw ceiling → 0.76. The earlier
20
+ * 0.42 came from a session where BOTH stacks were memory-degraded
21
+ * (32.3/33.0 "parity" — equally wrong, mutually consistent). */
22
+ export const MOE_DECODE_EFFICIENCY = 0.76;
23
+ /** Prefill transient bytes per chunk token (measured: ~1.1 GB @ 2048). */
24
+ export const TRANSIENT_PER_TOKEN = 0.55e6;
25
+ /** Fraction of unified RAM usable as GPU working set (Metal's
26
+ * recommendedMaxWorkingSetSize is ~75% on consumer SKUs). */
27
+ export const WIRED_FRACTION = 0.75;
28
+ export const DEFAULT_CHUNK = 2048;
29
+ /** KV cache element size (bf16; quantized KV lands in Phase 6). */
30
+ const KV_BYTES = 2;
31
+
32
+ export interface MachineSpec {
33
+ name: string;
34
+ ramBytes: number;
35
+ bandwidthGBs: number;
36
+ }
37
+
38
+ /** Representative Apple Silicon SKUs (memory bandwidth GB/s). */
39
+ export const APPLE_SKUS: { chip: string; bandwidthGBs: number; ramOptions: number[] }[] = [
40
+ { chip: "M1", bandwidthGBs: 68, ramOptions: [8, 16] },
41
+ { chip: "M1 Pro", bandwidthGBs: 200, ramOptions: [16, 32] },
42
+ { chip: "M1 Max", bandwidthGBs: 400, ramOptions: [32, 64] },
43
+ { chip: "M1 Ultra", bandwidthGBs: 800, ramOptions: [64, 128] },
44
+ { chip: "M2", bandwidthGBs: 100, ramOptions: [8, 16, 24] },
45
+ { chip: "M2 Pro", bandwidthGBs: 200, ramOptions: [16, 32] },
46
+ { chip: "M2 Max", bandwidthGBs: 400, ramOptions: [32, 64, 96] },
47
+ { chip: "M2 Ultra", bandwidthGBs: 800, ramOptions: [64, 128, 192] },
48
+ { chip: "M3", bandwidthGBs: 100, ramOptions: [8, 16, 24] },
49
+ { chip: "M3 Pro", bandwidthGBs: 150, ramOptions: [18, 36] },
50
+ { chip: "M3 Max", bandwidthGBs: 400, ramOptions: [36, 48, 64, 96, 128] },
51
+ { chip: "M3 Ultra", bandwidthGBs: 819, ramOptions: [96, 256, 512] },
52
+ { chip: "M4", bandwidthGBs: 120, ramOptions: [16, 24, 32] },
53
+ { chip: "M4 Pro", bandwidthGBs: 273, ramOptions: [24, 48] },
54
+ { chip: "M4 Max", bandwidthGBs: 546, ramOptions: [36, 48, 64, 128] },
55
+ ];
56
+
57
+ /** Chip name from sysctl ("M1 Max") + its bandwidth from APPLE_SKUS. */
58
+ export function detectChip(): { name: string | null; bandwidthGBs: number | null } {
59
+ try {
60
+ const proc = Bun.spawnSync(["sysctl", "-n", "machdep.cpu.brand_string"]);
61
+ const name = proc.stdout.toString().trim().replace(/^Apple\s+/, "");
62
+ if (!name) return { name: null, bandwidthGBs: null };
63
+ const sku = APPLE_SKUS.find((s) => s.chip === name);
64
+ return { name, bandwidthGBs: sku?.bandwidthGBs ?? null };
65
+ } catch {
66
+ return { name: null, bandwidthGBs: null };
67
+ }
68
+ }
69
+
70
+ export function thisMachine(bandwidthGBs?: number): MachineSpec {
71
+ // Default bandwidth: the detected chip's table entry; 273 (M4 Pro, the
72
+ // original dev machine) only as the last resort.
73
+ const bw = bandwidthGBs ?? detectChip().bandwidthGBs ?? 273;
74
+ return { name: "this machine", ramBytes: totalmem(), bandwidthGBs: bw };
75
+ }
76
+
77
+ /** The default model everywhere — e4b. Fits comfortably on every supported
78
+ * Mac (including 24 GB), loads fast, and is a heavily-used primary model.
79
+ * Override with an explicit query (e.g. `mlx-bun serve 12B`). */
80
+ export const DEFAULT_REPO_ID = "mlx-community/gemma-4-e4b-it-OptiQ-4bit";
81
+
82
+ /** Recommended first model. e4b for every machine by default: the old
83
+ * per-tier sizing reached 12B/26B on larger Macs and pushed 24 GB machines
84
+ * into memory pressure (a 26B is ~18 GB). The tiered sizing is preserved in
85
+ * largestRecommendedRepoId for callers that explicitly want the biggest fit. */
86
+ export function recommendedRepoId(_ramBytes = totalmem()): string {
87
+ return DEFAULT_REPO_ID;
88
+ }
89
+
90
+ /** Largest Gemma a RAM tier can comfortably hold. NOT the default (see
91
+ * recommendedRepoId) — kept available for explicit "biggest model" opt-in. */
92
+ export function largestRecommendedRepoId(ramBytes = totalmem()): string {
93
+ const gb = ramBytes / 2 ** 30;
94
+ if (gb >= 48) return "mlx-community/gemma-4-26B-A4B-it-OptiQ-4bit";
95
+ if (gb >= 24) return "mlx-community/gemma-4-12B-it-OptiQ-4bit";
96
+ return DEFAULT_REPO_ID;
97
+ }
98
+
99
+ /** Fraction of RAM an *auto-selected* model may occupy and still leave the
100
+ * machine usable for other apps (browser, editor, Photoshop). Deliberately
101
+ * stricter than WIRED_FRACTION: auto-pick must never grab a "dedicate the
102
+ * whole machine" model — e.g. the 26B (~17 GB) on a 24 GB Mac — those stay
103
+ * an explicit `--query` choice. The same model clears this budget on a
104
+ * big-RAM Mac, so it remains auto-eligible there (RAM-relative by design). */
105
+ export const COEXIST_FRACTION = 0.6;
106
+
107
+ export interface AutoPickCandidate {
108
+ repoId: string;
109
+ sizeBytes: number;
110
+ }
111
+
112
+ /**
113
+ * Pick the model to auto-load when the user gives no query:
114
+ * 1. the default (e4b) if downloaded and it fits at all;
115
+ * 2. else the largest model that still leaves coexistence headroom;
116
+ * 3. else (last resort) the largest model that fits at all — so a user
117
+ * whose only model is a heavy one still gets it.
118
+ * Pure: the caller supplies the fit predicates, so this is unit-tested
119
+ * without touching the registry or loading configs.
120
+ */
121
+ export function chooseAutoModel<T extends AutoPickCandidate>(
122
+ candidates: T[],
123
+ defaultRepoId: string,
124
+ fitsFullBudget: (c: T) => boolean,
125
+ fitsCoexistBudget: (c: T) => boolean,
126
+ ): T | undefined {
127
+ const preferred = candidates.find((c) => c.repoId === defaultRepoId);
128
+ if (preferred && fitsFullBudget(preferred)) return preferred;
129
+ const bySizeDesc = [...candidates].sort((a, b) => b.sizeBytes - a.sizeBytes);
130
+ return bySizeDesc.find(fitsCoexistBudget) ?? bySizeDesc.find(fitsFullBudget);
131
+ }
132
+
133
+ export interface FitReport {
134
+ fits: boolean;
135
+ contextTokens: number;
136
+ weightsBytes: number;
137
+ kvBytes: number;
138
+ transientBytes: number;
139
+ totalBytes: number;
140
+ usableBytes: number;
141
+ maxSafeContext: number;
142
+ predictedDecodeTps: number;
143
+ }
144
+
145
+ interface KvGeometry {
146
+ fullLayers: number;
147
+ slidingLayers: number;
148
+ fullBytesPerToken: number;
149
+ slidingBytesPerToken: number;
150
+ window: number;
151
+ }
152
+
153
+ function kvGeometry(config: ModelConfig): KvGeometry {
154
+ const t = config.text;
155
+ const slidingLayers = t.layerTypes.filter((l) => l === "sliding_attention").length;
156
+ const fullLayers = t.numHiddenLayers - slidingLayers;
157
+ const kEqVFactor = t.attentionKEqV ? 2 : 2; // k and v stored separately either way
158
+ return {
159
+ fullLayers,
160
+ slidingLayers,
161
+ fullBytesPerToken: fullLayers * kEqVFactor * t.numGlobalKeyValueHeads * t.globalHeadDim * KV_BYTES,
162
+ slidingBytesPerToken: slidingLayers * 2 * t.numKeyValueHeads * t.headDim * KV_BYTES,
163
+ window: t.slidingWindow,
164
+ };
165
+ }
166
+
167
+ export function kvBytesAt(config: ModelConfig, ctx: number): number {
168
+ const g = kvGeometry(config);
169
+ return g.fullBytesPerToken * ctx + g.slidingBytesPerToken * Math.min(ctx, g.window);
170
+ }
171
+
172
+ export function fit(
173
+ config: ModelConfig,
174
+ weightsBytes: number,
175
+ ctx: number,
176
+ machine: MachineSpec = thisMachine(),
177
+ chunk: number = DEFAULT_CHUNK,
178
+ /** Bytes of `.experts.` tensors (registry). MoE decode reads only
179
+ * top_k/num_experts of them per token; residency still needs all. */
180
+ expertsBytes = 0,
181
+ /** Explicit memory budget in bytes (admission control). When set it
182
+ * replaces the machine-derived usable ceiling (ram × WIRED_FRACTION)
183
+ * outright — the budget IS the usable envelope. */
184
+ usableBytes?: number,
185
+ ): FitReport {
186
+ const usable = usableBytes ?? machine.ramBytes * WIRED_FRACTION;
187
+ const transient = Math.min(chunk, ctx) * TRANSIENT_PER_TOKEN;
188
+ const kv = kvBytesAt(config, ctx);
189
+ const total = weightsBytes + kv + transient;
190
+
191
+ // solve max context: weights + kv(ctx) + transient ≤ usable.
192
+ // Below the window both KV terms are linear in ctx; above it the
193
+ // sliding term saturates and only full-attention layers keep growing.
194
+ const g = kvGeometry(config);
195
+ const fixed = weightsBytes + chunk * TRANSIENT_PER_TOKEN;
196
+ let maxCtx = 0;
197
+ if (usable > fixed) {
198
+ const budget = usable - fixed;
199
+ const linear = Math.floor(budget / (g.fullBytesPerToken + g.slidingBytesPerToken));
200
+ maxCtx = linear <= g.window
201
+ ? linear
202
+ : Math.floor((budget - g.slidingBytesPerToken * g.window) / g.fullBytesPerToken);
203
+ maxCtx = Math.min(maxCtx, config.text.maxPositionEmbeddings);
204
+ }
205
+
206
+ // decode reads all weights + the KV cache once per token — except MoE
207
+ // expert weights, where only top_k of num_experts are touched per token
208
+ const t = config.text;
209
+ const isMoe = t.enableMoeBlock && t.numExperts > 0;
210
+ const expertsSkipped = isMoe
211
+ ? expertsBytes * (1 - t.topKExperts / t.numExperts)
212
+ : 0;
213
+ const bytesPerToken = weightsBytes - expertsSkipped + kv;
214
+ const predictedDecodeTps =
215
+ ((machine.bandwidthGBs * 1e9) / bytesPerToken) *
216
+ (isMoe ? MOE_DECODE_EFFICIENCY : DECODE_EFFICIENCY);
217
+
218
+ return {
219
+ fits: total <= usable,
220
+ contextTokens: ctx,
221
+ weightsBytes,
222
+ kvBytes: kv,
223
+ transientBytes: transient,
224
+ totalBytes: total,
225
+ usableBytes: usable,
226
+ maxSafeContext: maxCtx,
227
+ predictedDecodeTps,
228
+ };
229
+ }
230
+
231
+ /** The SKU matrix: which Apple Silicon configs run this model at `ctx`. */
232
+ export function skuMatrix(
233
+ config: ModelConfig, weightsBytes: number, ctx: number, expertsBytes = 0,
234
+ ): { sku: string; ramGB: number; fits: boolean; maxContext: number; decodeTps: number }[] {
235
+ const rows: ReturnType<typeof skuMatrix> = [];
236
+ for (const sku of APPLE_SKUS) {
237
+ for (const ram of sku.ramOptions) {
238
+ const m: MachineSpec = {
239
+ name: `${sku.chip} ${ram}GB`,
240
+ ramBytes: ram * 2 ** 30,
241
+ bandwidthGBs: sku.bandwidthGBs,
242
+ };
243
+ const r = fit(config, weightsBytes, ctx, m, DEFAULT_CHUNK, expertsBytes);
244
+ rows.push({
245
+ sku: sku.chip,
246
+ ramGB: ram,
247
+ fits: r.fits,
248
+ maxContext: r.maxSafeContext,
249
+ decodeTps: r.predictedDecodeTps,
250
+ });
251
+ }
252
+ }
253
+ return rows;
254
+ }