mlx-bun 0.0.7 → 0.0.9

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 (138) hide show
  1. package/README.md +112 -20
  2. package/docs/design/batching-v2-plan.md +515 -0
  3. package/docs/design/bucketing-stage.md +196 -0
  4. package/docs/design/compat-cli-surface-design.md +11 -11
  5. package/docs/design/diffusion-gemma-port.md +602 -0
  6. package/docs/design/docs-reorg-plan.md +1 -1
  7. package/docs/design/dreaming-nightly-pipeline.md +291 -0
  8. package/docs/design/dspark-speculative-decoding.md +106 -0
  9. package/docs/design/generic-model-support.md +381 -0
  10. package/docs/design/memory-inference-path.md +422 -0
  11. package/docs/design/memory-synthesis.md +287 -0
  12. package/docs/design/minicpm5-decode-megakernel.md +484 -0
  13. package/docs/design/mlx-lm-tool-parity-plan.md +535 -0
  14. package/docs/design/orpo-dynamic-lambda.md +41 -0
  15. package/docs/design/orpo-training.md +35 -5
  16. package/docs/design/parallel-slots.md +12 -1
  17. package/docs/design/parity-tier-dag.md +13 -5
  18. package/docs/design/spec-decode-larger-targets.md +229 -0
  19. package/docs/design/tauri-desktop-app.md +2 -2
  20. package/docs/design/the-dreaming-handoff.md +141 -0
  21. package/docs/design/the-dreaming-master-plan.md +446 -0
  22. package/docs/design/write-pipeline-entity.md +468 -0
  23. package/docs/investigations/curve-bisector-routing.md +1 -1
  24. package/docs/investigations/decode-roofline-lookagain.md +262 -0
  25. package/docs/investigations/dspark-handoff.md +58 -0
  26. package/docs/investigations/kernel-perf-review-2026-07.md +92 -0
  27. package/docs/planning/PRODUCT_ROADMAP.md +2 -2
  28. package/docs/planning/curve-sampler-research-plan.md +331 -0
  29. package/docs/planning/memory-docs-and-dag-plan.md +185 -0
  30. package/docs/planning/pi-integration-review.md +178 -0
  31. package/docs/planning/release-notes-v0.0.9.md +122 -0
  32. package/docs/planning/web-ui-pass-plan.md +213 -0
  33. package/docs/planning/website-readme-pass-plan.md +224 -0
  34. package/docs/reference/cli.md +255 -0
  35. package/docs/reference/embedding.md +7 -2
  36. package/docs/reference/library-api.md +22 -3
  37. package/docs/reference/memory.md +5 -5
  38. package/docs/reference/models.md +137 -0
  39. package/docs/reference/orpo-quickstart.md +6 -0
  40. package/docs/reference/server-api.md +187 -9
  41. package/docs/reference/server-config.md +40 -9
  42. package/docs/reference/training.md +25 -11
  43. package/package.json +1 -1
  44. package/src/anthropic.ts +22 -0
  45. package/src/assets/curve-designer.html +1 -1
  46. package/src/chat-template.ts +25 -7
  47. package/src/cli.ts +1029 -86
  48. package/src/config.ts +54 -3
  49. package/src/diffusion/diffusion-generate.ts +436 -0
  50. package/src/download.ts +99 -8
  51. package/src/embed.ts +77 -0
  52. package/src/eval/perplexity.ts +164 -0
  53. package/src/eval/runner.ts +19 -7
  54. package/src/eval/tasks/bfcl.ts +1 -1
  55. package/src/generate.ts +196 -19
  56. package/src/harness-pi.ts +43 -17
  57. package/src/index.ts +2 -0
  58. package/src/lora.ts +2 -2
  59. package/src/memory/article.ts +558 -0
  60. package/src/memory/chunk-validation.ts +150 -0
  61. package/src/memory/chunk.ts +305 -12
  62. package/src/memory/cluster.ts +344 -17
  63. package/src/memory/coverage.ts +134 -0
  64. package/src/memory/crosslink.ts +0 -0
  65. package/src/memory/db.ts +184 -45
  66. package/src/memory/entity.ts +203 -0
  67. package/src/memory/gate.ts +187 -0
  68. package/src/memory/history.ts +152 -0
  69. package/src/memory/ingest.ts +324 -11
  70. package/src/memory/model.ts +321 -0
  71. package/src/memory/normalize.ts +671 -0
  72. package/src/memory/parse.ts +22 -0
  73. package/src/memory/pipeline.ts +242 -30
  74. package/src/memory/prompts.ts +42 -0
  75. package/src/memory/query.ts +334 -0
  76. package/src/memory/reconcile.ts +734 -0
  77. package/src/memory/redirect.ts +108 -0
  78. package/src/memory/reindex.ts +325 -0
  79. package/src/memory/resolve.ts +307 -0
  80. package/src/memory/route.ts +272 -0
  81. package/src/memory/schedule.ts +6 -4
  82. package/src/memory/stages.ts +774 -0
  83. package/src/memory/synthesize.ts +1629 -20
  84. package/src/memory/tools.ts +446 -96
  85. package/src/memory/vault.ts +462 -35
  86. package/src/memory/wikify.ts +1057 -16
  87. package/src/mlx/ffi.ts +11 -0
  88. package/src/mlx/ops.ts +113 -0
  89. package/src/model/compiled-decode.ts +43 -8
  90. package/src/model/diffusion-gemma.ts +1026 -0
  91. package/src/model/factory.ts +35 -8
  92. package/src/model/flash-attention.ts +4 -2
  93. package/src/model/fused-decode-kernel.ts +14 -7
  94. package/src/model/fused-mlp-kernel.ts +169 -0
  95. package/src/model/fused-swiglu-kernel.ts +240 -0
  96. package/src/model/gemma4-base.ts +62 -6
  97. package/src/model/gemma4.ts +33 -2
  98. package/src/model/minicpm5.ts +106 -8
  99. package/src/model/qwen3.ts +276 -0
  100. package/src/model/steel-linear-kernel.ts +96 -0
  101. package/src/model/support.ts +45 -7
  102. package/src/model/universal/archs.ts +332 -0
  103. package/src/model/universal/dense.ts +576 -0
  104. package/src/model/universal/modules.ts +163 -0
  105. package/src/model/universal/rope.ts +366 -0
  106. package/src/pi-provider.ts +1 -1
  107. package/src/pi-terminal.ts +29 -8
  108. package/src/pi-web.ts +19 -2
  109. package/src/quantize/config-writer.ts +1 -1
  110. package/src/registry.ts +215 -7
  111. package/src/responses.ts +23 -0
  112. package/src/sampler.ts +220 -9
  113. package/src/serve/batch-scheduler.ts +272 -80
  114. package/src/serve/generation-gateway.ts +90 -18
  115. package/src/server.ts +643 -50
  116. package/src/spec/drafter.ts +81 -49
  117. package/src/spec/dspark/data-dflash.ts +156 -0
  118. package/src/spec/dspark/data.ts +201 -0
  119. package/src/spec/dspark/generate-dflash.ts +193 -0
  120. package/src/spec/dspark/generate.ts +290 -0
  121. package/src/spec/dspark/loss.ts +213 -0
  122. package/src/spec/dspark/module-dflash.ts +361 -0
  123. package/src/spec/dspark/module.ts +540 -0
  124. package/src/spec/dspark/sample.ts +112 -0
  125. package/src/spec/generate.ts +40 -19
  126. package/src/status-page.html +1 -1
  127. package/src/tokenizer.ts +7 -0
  128. package/src/train/diffusion-lora.ts +216 -0
  129. package/src/train/flash-cce.ts +70 -15
  130. package/src/train/fuse.ts +243 -0
  131. package/src/train/index.ts +3 -0
  132. package/src/train/job.ts +8 -0
  133. package/src/train/loss.ts +289 -30
  134. package/src/train/prefix-shared.ts +33 -5
  135. package/src/train/segmented.ts +67 -11
  136. package/src/train/trainer.ts +92 -28
  137. package/src/vision/diffusion-vision.ts +620 -0
  138. package/src/web/skills/memory/SKILL.md +40 -17
@@ -0,0 +1,334 @@
1
+ // mlx-bun memory — the read-side navigation index + primitives.
2
+ //
3
+ // The read path FINDS an article deterministically — by category membership, by
4
+ // name/alias, or by following the [[wikilink]] graph — then hands the agent off
5
+ // to the read tools (TOC → section). It NEVER ranks by a vector and NEVER filters
6
+ // on an infobox field: the infobox is CONTENT the model reads, not a query
7
+ // target. There is deliberately no `byInfoboxField`, no numeric facet, no
8
+ // `gte/lte` here. Every lookup below touches only indexed names/categories/links;
9
+ // the embedding tripwire (src/embed.ts) MUST stay at zero across this module.
10
+ //
11
+ // `buildMemoryIndex` mirrors the same facts the reindex derives into the DB
12
+ // (aliases, categories, series, kind/type, infobox, lead, link graph) into plain
13
+ // in-memory maps, so the primitives are pure functions over a snapshot with no
14
+ // DB round-trip. The build is mtime-incremental: each file is parsed at most once
15
+ // per (path, mtime), so a rebuild after touching one article re-parses only that
16
+ // file and is otherwise byte-identical to the prior build.
17
+
18
+ import { readFileSync, readdirSync, statSync } from "node:fs";
19
+ import { join } from "node:path";
20
+
21
+ import {
22
+ deriveCategories,
23
+ infoboxAliases,
24
+ infoboxFieldValues,
25
+ parseInfobox,
26
+ parseLead,
27
+ parseSeriesBanner,
28
+ type EntityKind,
29
+ type Infobox,
30
+ } from "./article";
31
+ import { EntityResolver, nearNameMatch } from "./resolve";
32
+ import { articlesDir, extractWikilinkTargets, resolveWikilinkToStem, vaultRoot } from "./vault";
33
+
34
+ // ---- normalization (mirrors reindex.ts so resolveName agrees exactly) -
35
+
36
+ /** Casefold + collapse whitespace/underscores so title, stem, and alias forms of
37
+ * the same surface text key the alias index identically. */
38
+ function normalizeAlias(s: string): string {
39
+ return s.trim().toLowerCase().replace(/_/g, " ").replace(/\s+/g, " ").trim();
40
+ }
41
+
42
+ const LEADING_ARTICLE_RE = /^(?:the|a|an)\s+/;
43
+ const WIKILINK_RE = /\[\[([^\]]+)\]\]/;
44
+
45
+ /** The inner target of a `[[wikilink]]` value, before any `|alias`/`#anchor`. */
46
+ function wikilinkInner(value: string): string {
47
+ const m = WIKILINK_RE.exec(value);
48
+ if (!m) return "";
49
+ return m[1]!.split("|")[0]!.split("#")[0]!.trim();
50
+ }
51
+
52
+ // ---- per-article parse + the index ------------------------------------
53
+
54
+ /** One article's structured facts, parsed once and cached by (path, mtime). */
55
+ interface IndexedArticle {
56
+ stem: string;
57
+ infobox: Infobox | null;
58
+ lead: string | null;
59
+ categories: string[];
60
+ series: string | null;
61
+ kind: EntityKind;
62
+ type: string | null;
63
+ /** Normalized alias surfaces (stem, spaced stem, infobox aliases). */
64
+ aliases: string[];
65
+ /** Resolved outbound link targets (existing article stems only, deduped). */
66
+ outbound: string[];
67
+ }
68
+
69
+ export interface MemoryIndex {
70
+ root: string;
71
+ /** normalized alias → stem (first writer in sorted-stem order wins). */
72
+ aliasToStem: Map<string, string>;
73
+ /** category name → member stems (sorted). */
74
+ categoryToStems: Map<string, string[]>;
75
+ /** series name → member stems (sorted). */
76
+ seriesToStems: Map<string, string[]>;
77
+ /** entity kind → stems (sorted). */
78
+ kindToStems: Map<string, string[]>;
79
+ /** emergent `type:` label → stems (sorted). */
80
+ typeToStems: Map<string, string[]>;
81
+ /** stem → parsed infobox (null when the article has none). */
82
+ infoboxByStem: Map<string, Infobox | null>;
83
+ /** stem → lead abstract (null when absent). */
84
+ leadByStem: Map<string, string | null>;
85
+ /** stem → resolved outbound article stems (the link graph). */
86
+ outboundByStem: Map<string, string[]>;
87
+ /** Stems (re)parsed on the most recent build (mtime miss). */
88
+ parsed: string[];
89
+ /** Stems served from the parse cache on the most recent build (mtime hit). */
90
+ reused: string[];
91
+ }
92
+
93
+ interface CacheEntry {
94
+ mtimeMs: number;
95
+ article: IndexedArticle;
96
+ }
97
+
98
+ /** Module-level parse cache keyed by absolute file path. Memoizes per-file
99
+ * PARSING only (keyed by mtime); the aggregate maps are rebuilt every call so
100
+ * they always reflect the current file set. */
101
+ const ARTICLE_CACHE = new Map<string, CacheEntry>();
102
+
103
+ /** Drop the parse cache. A cold rebuild after this yields maps byte-identical to
104
+ * a warm one (the cache is a pure speedup, never a source of truth). */
105
+ export function resetMemoryIndexCache(): void {
106
+ ARTICLE_CACHE.clear();
107
+ }
108
+
109
+ /** Sorted article stems under `<root>/articles/` (`*.md`, no hidden files). */
110
+ function listArticleStems(root: string): string[] {
111
+ let names: string[];
112
+ try {
113
+ names = readdirSync(articlesDir(root));
114
+ } catch {
115
+ return [];
116
+ }
117
+ return names
118
+ .filter((n) => n.endsWith(".md") && !n.startsWith("."))
119
+ .map((n) => n.slice(0, -3))
120
+ .sort((a, b) => a.localeCompare(b));
121
+ }
122
+
123
+ /** Parse one article's markdown into its indexed facts (no I/O). */
124
+ function parseArticle(stem: string, md: string, stems: Set<string>): IndexedArticle {
125
+ const box = parseInfobox(md);
126
+
127
+ const aliasSet = new Set<string>();
128
+ for (const a of [stem, stem.replace(/_/g, " "), ...(box ? infoboxAliases(box) : [])]) {
129
+ const norm = normalizeAlias(a);
130
+ if (norm) aliasSet.add(norm);
131
+ }
132
+
133
+ // Outbound graph edges: prose [[links]] + infobox entity-link values + series
134
+ // banner, each resolved to an EXISTING article stem (red links are dropped,
135
+ // matching getArticleLinks). Self-edges excluded.
136
+ const outbound = new Set<string>();
137
+ const addEdge = (raw: string): void => {
138
+ const r = resolveWikilinkToStem(raw, stems);
139
+ if (r && r !== stem) outbound.add(r);
140
+ };
141
+ for (const t of extractWikilinkTargets(md)) addEdge(t);
142
+ if (box) {
143
+ for (const field of box.fields) {
144
+ for (const value of infoboxFieldValues(field)) {
145
+ if (WIKILINK_RE.test(value)) addEdge(wikilinkInner(value));
146
+ }
147
+ }
148
+ }
149
+ const series = parseSeriesBanner(md);
150
+ if (series) addEdge(series);
151
+
152
+ return {
153
+ stem,
154
+ infobox: box,
155
+ lead: parseLead(md),
156
+ categories: deriveCategories(md, stem).categories,
157
+ series,
158
+ kind: box ? box.entityKind : "thing",
159
+ type: box ? box.type : null,
160
+ aliases: [...aliasSet].sort((a, b) => a.localeCompare(b)),
161
+ outbound: [...outbound].sort((a, b) => a.localeCompare(b)),
162
+ };
163
+ }
164
+
165
+ /**
166
+ * Build the read-side navigation index for a vault. mtime-incremental: each
167
+ * article is parsed at most once per (path, mtime), so a rebuild after touching
168
+ * one file re-parses ONLY that file. Deterministic: stems are visited in sorted
169
+ * order and every map array is sorted, so two builds of the same vault produce
170
+ * byte-identical maps. Makes ZERO embedding calls.
171
+ */
172
+ export function buildMemoryIndex(root: string = vaultRoot()): MemoryIndex {
173
+ const stems = listArticleStems(root);
174
+ const stemSet = new Set(stems);
175
+ const dir = articlesDir(root);
176
+
177
+ const parsed: string[] = [];
178
+ const reused: string[] = [];
179
+ const articles: IndexedArticle[] = [];
180
+
181
+ for (const stem of stems) {
182
+ const path = join(dir, `${stem}.md`);
183
+ let mtimeMs: number;
184
+ try {
185
+ mtimeMs = statSync(path).mtimeMs;
186
+ } catch {
187
+ continue; // raced/deleted between listing and stat
188
+ }
189
+ const cached = ARTICLE_CACHE.get(path);
190
+ if (cached && cached.mtimeMs === mtimeMs) {
191
+ reused.push(stem);
192
+ articles.push(cached.article);
193
+ continue;
194
+ }
195
+ let md: string;
196
+ try {
197
+ md = readFileSync(path, "utf8");
198
+ } catch {
199
+ continue;
200
+ }
201
+ const article = parseArticle(stem, md, stemSet);
202
+ ARTICLE_CACHE.set(path, { mtimeMs, article });
203
+ parsed.push(stem);
204
+ articles.push(article);
205
+ }
206
+
207
+ const index: MemoryIndex = {
208
+ root,
209
+ aliasToStem: new Map(),
210
+ categoryToStems: new Map(),
211
+ seriesToStems: new Map(),
212
+ kindToStems: new Map(),
213
+ typeToStems: new Map(),
214
+ infoboxByStem: new Map(),
215
+ leadByStem: new Map(),
216
+ outboundByStem: new Map(),
217
+ parsed,
218
+ reused,
219
+ };
220
+
221
+ const pushInto = (map: Map<string, string[]>, key: string, stem: string): void => {
222
+ const list = map.get(key);
223
+ if (list) list.push(stem);
224
+ else map.set(key, [stem]);
225
+ };
226
+
227
+ for (const a of articles) {
228
+ for (const alias of a.aliases) {
229
+ if (!index.aliasToStem.has(alias)) index.aliasToStem.set(alias, a.stem); // first (sorted) writer wins
230
+ }
231
+ for (const cat of a.categories) pushInto(index.categoryToStems, cat, a.stem);
232
+ if (a.series) pushInto(index.seriesToStems, a.series, a.stem);
233
+ pushInto(index.kindToStems, a.kind, a.stem);
234
+ if (a.type) pushInto(index.typeToStems, a.type, a.stem);
235
+ index.infoboxByStem.set(a.stem, a.infobox);
236
+ index.leadByStem.set(a.stem, a.lead);
237
+ index.outboundByStem.set(a.stem, a.outbound);
238
+ }
239
+
240
+ return index;
241
+ }
242
+
243
+ // ---- navigation primitives (find + resolve + links) -------------------
244
+
245
+ /**
246
+ * Article stems that DECLARED `[[Category:<category>]]` — the only "articles
247
+ * about X" mechanism (zero substring false positives). Returns a sorted copy.
248
+ */
249
+ export function articlesInCategory(index: MemoryIndex, category: string): string[] {
250
+ return [...(index.categoryToStems.get(category) ?? [])];
251
+ }
252
+
253
+ /** Lazily-built near-name resolver per index (token-subset fuzzy over the alias
254
+ * map). Keyed by index identity; a fresh `buildMemoryIndex` yields a new object,
255
+ * so stale resolvers fall out of the WeakMap with their index. */
256
+ const NEAR_RESOLVER = new WeakMap<MemoryIndex, EntityResolver>();
257
+
258
+ function nearResolver(index: MemoryIndex): EntityResolver {
259
+ let r = NEAR_RESOLVER.get(index);
260
+ if (!r) {
261
+ r = EntityResolver.fromAliasMap(index.aliasToStem);
262
+ NEAR_RESOLVER.set(index, r);
263
+ }
264
+ return r;
265
+ }
266
+
267
+ /**
268
+ * Resolve an entity name or alias to its article stem via the alias index.
269
+ * Normalizes the query the same way aliases are stored (casefold, ws-collapse)
270
+ * and also retries with a leading article word ("the"/"a"/"an") stripped, so
271
+ * a "<the brand model>" surface and a "<model>" short form both reach the same stem.
272
+ *
273
+ * On an exact-alias miss, falls back to a conservative NEAR-NAME match
274
+ * ({@link nearNameMatch}) so a discussed sub-concept that is plainly the same
275
+ * subject as an existing article — the article title minus a generic suffix
276
+ * ("<subject>" for an "<subject> Theory" article) — still resolves to it, while a
277
+ * distinct subject or a purely-generic query does not. No model, no embedding.
278
+ */
279
+ export function resolveName(index: MemoryIndex, nameOrAlias: string): string | null {
280
+ const q = normalizeAlias(nameOrAlias);
281
+ for (const c of [q, q.replace(LEADING_ARTICLE_RE, "")]) {
282
+ if (!c) continue;
283
+ const stem = index.aliasToStem.get(c);
284
+ if (stem) return stem;
285
+ }
286
+ return nearNameMatch(nearResolver(index), q);
287
+ }
288
+
289
+ export interface Neighbors {
290
+ outbound: string[];
291
+ inbound: string[];
292
+ }
293
+
294
+ /**
295
+ * The article's resolved [[wikilink]] neighbors: `outbound` it points at,
296
+ * `inbound` that point at it — including infobox entity-link edges (e.g. an
297
+ * entity's `<entity_key>: [[Linked Entity]]`), which is the navigation hop the read
298
+ * path rides. Both lists are sorted; computed entirely from the in-memory graph.
299
+ */
300
+ export function neighbors(index: MemoryIndex, stem: string): Neighbors {
301
+ const outbound = [...(index.outboundByStem.get(stem) ?? [])];
302
+ const inbound: string[] = [];
303
+ for (const [from, targets] of index.outboundByStem) {
304
+ if (from !== stem && targets.includes(stem)) inbound.push(from);
305
+ }
306
+ inbound.sort((a, b) => a.localeCompare(b));
307
+ return { outbound, inbound };
308
+ }
309
+
310
+ // ---- deterministic snapshot (for tests / cache-equivalence checks) ----
311
+
312
+ /** A stable JSON serialization of the index maps (sorted keys), so a warm and a
313
+ * cold rebuild can be asserted byte-identical. Excludes the build accounting
314
+ * (`parsed`/`reused`), which is per-build by design. */
315
+ export function serializeMemoryIndex(index: MemoryIndex): string {
316
+ const dumpStringMap = (m: Map<string, string>): [string, string][] =>
317
+ [...m.entries()].sort((a, b) => a[0].localeCompare(b[0]));
318
+ const dumpListMap = (m: Map<string, string[]>): [string, string[]][] =>
319
+ [...m.entries()].sort((a, b) => a[0].localeCompare(b[0]));
320
+ const dumpInfobox = (m: Map<string, Infobox | null>): [string, Infobox | null][] =>
321
+ [...m.entries()].sort((a, b) => a[0].localeCompare(b[0]));
322
+ const dumpLeadMap = (m: Map<string, string | null>): [string, string | null][] =>
323
+ [...m.entries()].sort((a, b) => a[0].localeCompare(b[0]));
324
+ return JSON.stringify({
325
+ aliasToStem: dumpStringMap(index.aliasToStem),
326
+ categoryToStems: dumpListMap(index.categoryToStems),
327
+ seriesToStems: dumpListMap(index.seriesToStems),
328
+ kindToStems: dumpListMap(index.kindToStems),
329
+ typeToStems: dumpListMap(index.typeToStems),
330
+ infoboxByStem: dumpInfobox(index.infoboxByStem),
331
+ leadByStem: dumpLeadMap(index.leadByStem),
332
+ outboundByStem: dumpListMap(index.outboundByStem),
333
+ });
334
+ }