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
@@ -0,0 +1,123 @@
1
+ // Machine-state preflight for fair benchmarks (Phase 15 method rules).
2
+ // Every failure mode here was MEASURED on this machine (PLAN findings):
3
+ // - 6.4 GB swap residue: 26B decode 32 tok/s → 0.02 tok/s
4
+ // - session drift: identical bf16 runs 15.5 vs 14.3 tok/s an hour apart
5
+ // - cold page cache: prefill timer dominated by SSD page-in
6
+ // The harness refuses to record headline numbers when checks fail;
7
+ // the state snapshot is stored in every eval-DB row either way.
8
+
9
+ export interface MachineState {
10
+ swapUsedMB: number;
11
+ freePercent: number;
12
+ /** pmset -g therm CPU_Speed_Limit (100 = no thermal throttle; -1 if
13
+ * unavailable on this platform). */
14
+ cpuSpeedLimit: number;
15
+ /** iogpu.wired_limit_mb sysctl (0 = macOS default ≈75% of unified
16
+ * memory). A raised ceiling helps every stack equally but MUST be
17
+ * recorded — results depend on it. Resets on reboot. */
18
+ gpuWiredLimitMB: number;
19
+ /** 1-minute load average — post-boot indexing (Spotlight etc.) shows
20
+ * up here; let it settle before benchmarking. */
21
+ loadAvg1m: number;
22
+ /** Foreign processes holding > bigRssMB resident (excludes kernel,
23
+ * WindowServer, and our own bun/python benchmark processes). */
24
+ bigProcesses: { rssMB: number; command: string }[];
25
+ ok: boolean;
26
+ problems: string[];
27
+ at: string;
28
+ }
29
+
30
+ export interface PreflightLimits {
31
+ maxSwapUsedMB?: number; // default 512
32
+ minFreePercent?: number; // default 35
33
+ bigRssMB?: number; // default 2048
34
+ }
35
+
36
+ function sh(cmd: string[]): string {
37
+ const r = Bun.spawnSync(cmd, { stdout: "pipe", stderr: "pipe" });
38
+ return r.exitCode === 0 ? r.stdout.toString() : "";
39
+ }
40
+
41
+ export function checkMachine(limits: PreflightLimits = {}): MachineState {
42
+ const maxSwap = limits.maxSwapUsedMB ?? 512;
43
+ const minFree = limits.minFreePercent ?? 35;
44
+ const bigRss = limits.bigRssMB ?? 2048;
45
+ const problems: string[] = [];
46
+
47
+ // swap: "vm.swapusage: total = 7168.00M used = 6413.06M free = ..."
48
+ const swapOut = sh(["sysctl", "vm.swapusage"]);
49
+ const swapUsedMB = Number(swapOut.match(/used = ([\d.]+)M/)?.[1] ?? -1);
50
+ if (swapUsedMB < 0) problems.push("could not read vm.swapusage");
51
+ else if (swapUsedMB > maxSwap)
52
+ problems.push(`swap in use: ${swapUsedMB.toFixed(0)} MB (> ${maxSwap} MB) — reboot before benchmarking`);
53
+
54
+ // free memory: "System-wide memory free percentage: 34%"
55
+ const memOut = sh(["memory_pressure", "-Q"]);
56
+ const freePercent = Number(memOut.match(/free percentage: (\d+)/)?.[1] ?? -1);
57
+ if (freePercent < 0) problems.push("could not read memory_pressure");
58
+ else if (freePercent < minFree)
59
+ problems.push(`free memory ${freePercent}% (< ${minFree}%) — close apps / reboot`);
60
+
61
+ // thermal: "CPU_Speed_Limit = 100"
62
+ const thermOut = sh(["pmset", "-g", "therm"]);
63
+ const speedMatch = thermOut.match(/CPU_Speed_Limit\s*=\s*(\d+)/);
64
+ const cpuSpeedLimit = speedMatch ? Number(speedMatch[1]) : -1;
65
+ if (cpuSpeedLimit !== -1 && cpuSpeedLimit < 100)
66
+ problems.push(`thermal throttle active: CPU speed limit ${cpuSpeedLimit}% — let the machine cool`);
67
+
68
+ // GPU wired ceiling: 0 = stock (~75% of unified). Recorded, not judged
69
+ // — raising it (sudo sysctl iogpu.wired_limit_mb=N) helps all stacks
70
+ // equally, but rows measured under different ceilings don't compare.
71
+ const wiredOut = sh(["sysctl", "-n", "iogpu.wired_limit_mb"]);
72
+ const gpuWiredLimitMB = Number(wiredOut.trim() || 0) || 0;
73
+
74
+ // load average: "{ 4.96 3.21 1.90 }" — post-boot indexing settles fast
75
+ const loadOut = sh(["sysctl", "-n", "vm.loadavg"]);
76
+ const loadAvg1m = Number(loadOut.match(/([\d.]+)/)?.[1] ?? -1);
77
+ if (loadAvg1m > 8)
78
+ problems.push(`1-min load ${loadAvg1m.toFixed(1)} — post-boot indexing still running; wait a minute`);
79
+
80
+ // big foreign processes (rss in KB from ps)
81
+ const SELF_PATTERN = /(bun|\.venv\/bin\/python|mlx_lm|optiq)/;
82
+ const SYSTEM_PATTERN = /(kernel_task|WindowServer|launchd|mds|spotlight)/i;
83
+ const bigProcesses: { rssMB: number; command: string }[] = [];
84
+ for (const line of sh(["ps", "axo", "rss=,command="]).split("\n")) {
85
+ const m = line.match(/^\s*(\d+)\s+(.*)$/);
86
+ if (!m) continue;
87
+ const rssMB = Number(m[1]) / 1024;
88
+ const command = m[2]!.slice(0, 80);
89
+ if (rssMB < bigRss) continue;
90
+ if (SELF_PATTERN.test(command) || SYSTEM_PATTERN.test(command)) continue;
91
+ bigProcesses.push({ rssMB: Math.round(rssMB), command });
92
+ }
93
+ for (const p of bigProcesses)
94
+ problems.push(`big process resident: ${p.rssMB} MB — ${p.command}`);
95
+
96
+ return {
97
+ swapUsedMB, freePercent, cpuSpeedLimit, gpuWiredLimitMB, loadAvg1m,
98
+ bigProcesses,
99
+ ok: problems.length === 0,
100
+ problems,
101
+ at: new Date().toISOString(),
102
+ };
103
+ }
104
+
105
+ /** Compact JSON for the eval-DB machine_state column. Includes machine
106
+ * identity — rows from different Macs must never be confused (the
107
+ * eval DB is per-machine, but rendered tables travel in git). */
108
+ export function machineStateJson(s: MachineState): string {
109
+ const host = (sh(["hostname", "-s"]).trim() || "unknown");
110
+ const chip = (sh(["sysctl", "-n", "machdep.cpu.brand_string"]).trim() || "unknown");
111
+ const ramGB = Math.round(Number(sh(["sysctl", "-n", "hw.memsize"]).trim() || 0) / 2 ** 30);
112
+ return JSON.stringify({
113
+ host, chip, ram_gb: ramGB,
114
+ swap_mb: Math.round(s.swapUsedMB),
115
+ free_pct: s.freePercent,
116
+ cpu_limit: s.cpuSpeedLimit,
117
+ gpu_wired_mb: s.gpuWiredLimitMB,
118
+ load1m: s.loadAvg1m,
119
+ big_procs: s.bigProcesses.length,
120
+ ok: s.ok,
121
+ at: s.at,
122
+ });
123
+ }
@@ -0,0 +1,113 @@
1
+ // Byte-capped LRU prompt cache.
2
+ //
3
+ // The mlx-lm lesson (PLAN.md): a count-capped cache of multi-GB KV
4
+ // entries is an OOM footgun. Ours accounts bytes (sum of KV array bytes
5
+ // per entry) and evicts least-recently-used until under the cap.
6
+ //
7
+ // Usage pattern (single generation queue, so take/reinsert is race-free):
8
+ // const hit = cache.take(promptIds); // longest strict-prefix match
9
+ // generate(model, promptIds, { cache: hit?.caches ?? fresh })
10
+ // cache.put([...promptIds, ...generated], caches); // extended entry
11
+
12
+ import type { Cache } from "./model/gemma4";
13
+
14
+ export interface PromptCacheEntry {
15
+ tokens: number[];
16
+ caches: Cache[];
17
+ /** Namespace key — adapter spec for LoRA requests ("" = base model).
18
+ * KV computed under one adapter must never seed another's prefill. */
19
+ ns: string;
20
+ }
21
+
22
+ function cacheBytes(caches: Cache[]): number {
23
+ let total = 0;
24
+ for (const c of caches) for (const a of c.state()) total += a.nbytes;
25
+ return total;
26
+ }
27
+
28
+ /** Length of the longest common prefix of two token sequences. */
29
+ function commonPrefixLength(a: number[], b: number[]): number {
30
+ const n = Math.min(a.length, b.length);
31
+ let i = 0;
32
+ while (i < n && a[i] === b[i]) i++;
33
+ return i;
34
+ }
35
+
36
+ export class PromptCache {
37
+ readonly maxBytes: number;
38
+ #entries: { entry: PromptCacheEntry; bytes: number; lastUsed: number }[] = [];
39
+ #clock = 0;
40
+ hits = 0;
41
+ misses = 0;
42
+
43
+ constructor(maxBytes: number) {
44
+ this.maxBytes = maxBytes;
45
+ }
46
+
47
+ get totalBytes(): number {
48
+ return this.#entries.reduce((a, e) => a + e.bytes, 0);
49
+ }
50
+
51
+ get size(): number {
52
+ return this.#entries.length;
53
+ }
54
+
55
+ /** Find the entry with the longest usable common prefix of `prompt`,
56
+ * remove it, trim its caches to that prefix if needed (and possible),
57
+ * and hand over ownership (caller must put() it back — possibly
58
+ * extended — or dispose it).
59
+ *
60
+ * Usable prefix = common prefix capped at prompt.length - 1 (at least
61
+ * one token must be forwarded to produce logits). Entries longer than
62
+ * the prefix need cache.trim(); ring caches lose trimability once
63
+ * wrapped — those entries only match in full. */
64
+ take(prompt: number[], ns = ""): PromptCacheEntry | null {
65
+ let bestIdx = -1;
66
+ let bestLen = 0;
67
+ for (let i = 0; i < this.#entries.length; i++) {
68
+ const e = this.#entries[i]!.entry;
69
+ if (e.ns !== ns) continue;
70
+ const p = Math.min(commonPrefixLength(e.tokens, prompt), prompt.length - 1);
71
+ if (p <= bestLen) continue;
72
+ const trimNeeded = e.tokens.length - p;
73
+ if (trimNeeded > 0 && !e.caches.every((c) => c.isTrimmable())) continue;
74
+ bestLen = p;
75
+ bestIdx = i;
76
+ }
77
+ if (bestIdx === -1) {
78
+ this.misses++;
79
+ return null;
80
+ }
81
+ this.hits++;
82
+ const { entry } = this.#entries.splice(bestIdx, 1)[0]!;
83
+ const trimNeeded = entry.tokens.length - bestLen;
84
+ if (trimNeeded > 0) {
85
+ for (const c of entry.caches) c.trim(trimNeeded);
86
+ entry.tokens = entry.tokens.slice(0, bestLen);
87
+ }
88
+ return entry;
89
+ }
90
+
91
+ /** Insert (or reinsert) an entry; evicts LRU entries over the byte cap.
92
+ * If the entry itself exceeds the cap it is disposed, not stored. */
93
+ put(tokens: number[], caches: Cache[], ns = ""): void {
94
+ const bytes = cacheBytes(caches);
95
+ if (bytes > this.maxBytes) {
96
+ for (const c of caches) c.dispose();
97
+ return;
98
+ }
99
+ this.#entries.push({ entry: { tokens, caches, ns }, bytes, lastUsed: ++this.#clock });
100
+ while (this.totalBytes > this.maxBytes && this.#entries.length > 1) {
101
+ let lruIdx = 0;
102
+ for (let i = 1; i < this.#entries.length; i++)
103
+ if (this.#entries[i]!.lastUsed < this.#entries[lruIdx]!.lastUsed) lruIdx = i;
104
+ const [evicted] = this.#entries.splice(lruIdx, 1);
105
+ for (const c of evicted!.entry.caches) c.dispose();
106
+ }
107
+ }
108
+
109
+ clear(): void {
110
+ for (const e of this.#entries) for (const c of e.entry.caches) c.dispose();
111
+ this.#entries = [];
112
+ }
113
+ }