mlx-bun 0.0.10 → 0.0.12
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 +107 -32
- package/THIRD_PARTY_LICENSES.md +10 -0
- package/docs/archive/mlx-bun-lab-report.html +275 -0
- package/docs/archive/wwdc-mlx-bun.html +848 -0
- package/docs/design/audio-input-plan.md +356 -0
- package/docs/design/batching-perf-path.md +58 -35
- package/docs/design/batching-v2-plan.md +23 -9
- package/docs/design/bucketing-stage.md +2 -1
- package/docs/design/colibri-glm52-port.md +1479 -0
- package/docs/design/compat-cli-surface-design.md +4 -3
- package/docs/design/decode-speed-program.md +172 -0
- package/docs/design/docs-reorg-plan.md +1 -1
- package/docs/design/dspark-serving-program.md +371 -0
- package/docs/design/dspark-speculative-decoding.md +167 -5
- package/docs/design/faithful-l1-consolidation.md +310 -0
- package/docs/design/generic-model-support.md +8 -5
- package/docs/design/grammar-spec-batching-integration.md +301 -0
- package/docs/design/minicpm5-decode-megakernel.md +16 -11
- package/docs/design/mlx-lm-tool-parity-plan.md +11 -1
- package/docs/design/omlx-adoption-map.md +14 -6
- package/docs/design/orpo-training.md +34 -30
- package/docs/design/paged-kv-cache.md +214 -0
- package/docs/design/parallel-slots.md +82 -49
- package/docs/design/parity-tier-dag.md +43 -33
- package/docs/design/pre-colibri-stabilization.md +329 -0
- package/docs/design/repo-cleanup-plan.md +189 -0
- package/docs/design/runtime-isolation.md +19 -3
- package/docs/design/segmented-backward-training.md +3 -2
- package/docs/design/ssd-kv-cold-tier.md +130 -0
- package/docs/design/structured-output.md +484 -0
- package/docs/design/the-dreaming-master-plan.md +2 -3
- package/docs/design/turboquant-kv.md +235 -0
- package/docs/design/unified-engine-frontier-plan.md +774 -0
- package/docs/design/web-chat-beat-matrix.md +315 -0
- package/docs/design/web-chat-redesign.md +1112 -0
- package/docs/investigations/colibri-oracle-pin.md +1284 -0
- package/docs/investigations/dspark-handoff.md +182 -16
- package/docs/investigations/indexshare-performance-spike.md +357 -0
- package/docs/investigations/lab-build-journal.md +2 -2
- package/docs/investigations/openwiki-evaluation.md +249 -0
- package/docs/planning/IDEAS.md +14 -0
- package/docs/planning/PRODUCT_ROADMAP.md +2 -3
- package/docs/planning/chat-ui-vision-memory-first.md +199 -0
- package/docs/planning/chat-ui-vision.md +50 -0
- package/docs/planning/memory-docs-and-dag-plan.md +15 -6
- package/docs/planning/release-notes-v0.0.11.md +239 -0
- package/docs/planning/release-notes-v0.0.12.md +80 -0
- package/docs/reference/cli.md +63 -18
- package/docs/reference/distribution.md +37 -6
- package/docs/reference/embedding.md +40 -16
- package/docs/reference/features-matrix.md +99 -0
- package/docs/reference/library-api.md +69 -20
- package/docs/reference/memory.md +26 -6
- package/docs/reference/models.md +60 -3
- package/docs/reference/orpo-quickstart.md +3 -4
- package/docs/reference/server-api.md +717 -24
- package/docs/reference/server-config.md +265 -129
- package/docs/reference/training.md +24 -16
- package/package.json +5 -3
- package/src/anthropic.ts +67 -44
- package/src/audio/conformer.ts +647 -0
- package/src/audio/decode.ts +133 -0
- package/src/audio/features.ts +202 -0
- package/src/audio/transcode.ts +67 -0
- package/src/chat-template.ts +155 -10
- package/src/cli.ts +483 -113
- package/src/config.ts +116 -24
- package/src/diffusion/diffusion-generate.ts +14 -8
- package/src/download.ts +143 -12
- package/src/eval/README.md +7 -6
- package/src/eval/ifeval.ts +63 -120
- package/src/eval/kl.ts +59 -3
- package/src/eval/quality-db.ts +2 -2
- package/src/eval/runner.ts +17 -8
- package/src/eval/tasks/ifeval.ts +167 -31
- package/src/expert-io.ts +391 -0
- package/src/expert-offload.ts +15 -2
- package/src/expert-residency.ts +1134 -0
- package/src/expert-usage.ts +415 -0
- package/src/flags.ts +13 -0
- package/src/generate.ts +438 -35
- package/src/grammar.ts +585 -0
- package/src/hf-push.ts +1 -1
- package/src/hub-rest.ts +242 -0
- package/src/index.ts +14 -2
- package/src/jobs/runner.ts +37 -18
- package/src/kv-store.ts +586 -93
- package/src/lora.ts +85 -14
- package/src/md-modules.d.ts +42 -0
- package/src/media-fetch.ts +426 -0
- package/src/memory/rest.ts +245 -0
- package/src/memory/vault.ts +74 -0
- package/src/mlx/array.ts +149 -19
- package/src/mlx/autograd.ts +53 -16
- package/src/mlx/checkpoint.ts +46 -13
- package/src/mlx/custom-vjp.ts +83 -35
- package/src/mlx/ffi.ts +27 -3
- package/src/mlx/metal-kernel.ts +7 -4
- package/src/mlx/ops.ts +153 -23
- package/src/mlx/turboquant-ops.ts +831 -0
- package/src/mlx/turboquant-tables.ts +237 -0
- package/src/model/batched-mask.ts +62 -1
- package/src/model/batched-quant.ts +206 -0
- package/src/model/batched-rotating-quant.ts +346 -0
- package/src/model/batched-rotating.ts +46 -1
- package/src/model/compiled-decode.ts +4 -15
- package/src/model/factory.ts +103 -6
- package/src/model/gemma4-base.ts +395 -39
- package/src/model/gemma4.ts +122 -33
- package/src/model/generated/gemma4-12b.ts +9 -13
- package/src/model/generated/gemma4-26b.ts +10 -5
- package/src/model/generated/gemma4-e4b.ts +14 -9
- package/src/model/glm52-atlas.ts +511 -0
- package/src/model/glm52-cache.ts +706 -0
- package/src/model/glm52-config.ts +262 -0
- package/src/model/glm52-container.ts +305 -0
- package/src/model/glm52-coupling.ts +388 -0
- package/src/model/glm52-dsa.ts +602 -0
- package/src/model/glm52-expert-layout.ts +253 -0
- package/src/model/glm52-layout.ts +152 -0
- package/src/model/glm52-memory.ts +473 -0
- package/src/model/glm52-mla.ts +786 -0
- package/src/model/glm52-moe.ts +223 -0
- package/src/model/glm52-pilot.ts +537 -0
- package/src/model/glm52-reference.ts +445 -0
- package/src/model/glm52-residency.ts +557 -0
- package/src/model/glm52-resident-weights.ts +135 -0
- package/src/model/glm52-streamed-experts.ts +453 -0
- package/src/model/glm52-streamed-metal.ts +610 -0
- package/src/model/glm52-weights.ts +503 -0
- package/src/model/glm52.ts +1153 -0
- package/src/model/minicpm5.ts +58 -102
- package/src/model/paged-kv.ts +253 -0
- package/src/model/qwen3-delta.ts +76 -15
- package/src/model/qwen3-moe.ts +404 -0
- package/src/model/qwen3.ts +9 -8
- package/src/model/qwen3_5.ts +88 -23
- package/src/model/support.ts +19 -1
- package/src/model/universal/dense.ts +40 -5
- package/src/model/universal/rope.ts +36 -21
- package/src/native/expert_io.c +523 -0
- package/src/native-pack.ts +6 -4
- package/src/pi-web.ts +1299 -82
- package/src/preflight.ts +27 -8
- package/src/prompt-cache.ts +293 -33
- package/src/quantize/quantizer.ts +16 -1
- package/src/registry.ts +57 -2
- package/src/responses.ts +62 -49
- package/src/serve/batch-scheduler.ts +1050 -78
- package/src/serve/generation-gateway.ts +252 -24
- package/src/serve/isolate.ts +417 -0
- package/src/serve/lane-registry.ts +44 -0
- package/src/serve/session-search.ts +326 -0
- package/src/server.ts +1701 -175
- package/src/spec/assistant-source.ts +150 -0
- package/src/spec/deepspec-source.ts +143 -0
- package/src/spec/dflash-source.ts +124 -0
- package/src/spec/dspark/ab-stats.ts +136 -0
- package/src/spec/dspark/calibration.ts +112 -0
- package/src/spec/dspark/deepspec-module.ts +801 -0
- package/src/spec/dspark/generate-dflash.ts +72 -20
- package/src/spec/dspark/loader.ts +23 -0
- package/src/spec/dspark/module-dflash.ts +278 -36
- package/src/spec/dspark/quantize-drafter.ts +91 -0
- package/src/spec/generate.ts +59 -25
- package/src/spec/glm52-mtp-source.ts +392 -0
- package/src/spec/ngram-source.ts +132 -0
- package/src/spec/serve-loop.ts +617 -0
- package/src/spec/source.ts +118 -0
- package/src/spec/two-model.ts +171 -0
- package/src/ssd-cache.ts +111 -35
- package/src/tokenizer.ts +288 -2
- package/src/tool-approvals.ts +131 -0
- package/src/tool-call.ts +438 -22
- package/src/train/flash-cce.ts +2 -2
- package/src/train/job.ts +0 -12
- package/src/train/lora-params.ts +17 -6
- package/src/train/trainer.ts +60 -30
- package/src/vision/prompt.ts +217 -48
- package/src/web/app.html +1197 -1791
- package/src/web/app.js +5694 -0
- package/src/web/icon.svg +13 -0
- package/src/web/manifest.webmanifest +24 -0
- package/src/web/src/adapters-panel.ts +288 -0
- package/src/web/src/api.ts +57 -0
- package/src/web/src/assistant.ts +485 -0
- package/src/web/src/chat.ts +1050 -0
- package/src/web/src/composer.ts +959 -0
- package/src/web/src/dataset.ts +132 -0
- package/src/web/src/finetune.ts +267 -0
- package/src/web/src/hub.ts +428 -0
- package/src/web/src/main.ts +98 -0
- package/src/web/src/markdown.ts +0 -0
- package/src/web/src/memory-panel.ts +572 -0
- package/src/web/src/model-picker.ts +159 -0
- package/src/web/src/palette.ts +346 -0
- package/src/web/src/protocol.ts +62 -0
- package/src/web/src/quantize.ts +167 -0
- package/src/web/src/rag.ts +274 -0
- package/src/web/src/sessions.ts +469 -0
- package/src/web/src/shell.ts +804 -0
- package/src/web/src/status.ts +300 -0
- package/src/web/src/ui-catalog.ts +92 -0
- package/src/web/sw.js +71 -0
- package/src/web/vendor/README.md +61 -0
- package/src/web/vendor/hljs-LICENSE +29 -0
- package/src/web/vendor/hljs-theme.css +59 -0
- package/src/web/vendor/hljs.js +26 -0
- package/src/web-tools.ts +54 -2
- package/src/weights.ts +82 -19
- package/src/model/fused-decode-kernel.ts +0 -312
- package/src/model/fused-geglu-kernel.ts +0 -198
- package/src/model/fused-mlp-kernel.ts +0 -169
- package/src/model/fused-swiglu-kernel.ts +0 -240
- package/src/model/steel-linear-kernel.ts +0 -96
- package/src/status-page.html +0 -497
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// Memory REST wrappers — thin loopback JSON routes over src/memory/vault.ts
|
|
2
|
+
// for the web chat's Memory panel (docs/design/web-chat-redesign.md §5.5,
|
|
3
|
+
// §9 Phase 2). These are HTTP-only conveniences for the frontend: the
|
|
4
|
+
// agent-tool surface (src/memory/tools.ts) stays read-only and unchanged,
|
|
5
|
+
// and every route here is itself read-only except /init, which delegates to
|
|
6
|
+
// the same `setupVault` the CLI's `mlx-bun memory init` calls (never
|
|
7
|
+
// duplicated logic).
|
|
8
|
+
//
|
|
9
|
+
// Every handler returns a Response and never throws — a missing vault or a
|
|
10
|
+
// bad param degrades to `{ ok: false, error }` (never a 500), matching the
|
|
11
|
+
// rest of server.ts's /api/* conventions. Route dispatch (matching
|
|
12
|
+
// url.pathname + method) lives in server.ts; this file is the pure handler
|
|
13
|
+
// bodies so they're unit-testable without booting a model context (see
|
|
14
|
+
// tests/memory-rest.test.ts).
|
|
15
|
+
|
|
16
|
+
import { realpath } from "node:fs/promises";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
import { dirname, resolve, sep } from "node:path";
|
|
19
|
+
import {
|
|
20
|
+
vaultRoot, vaultStatus, listArticles, listReferenceDocs, readArticle,
|
|
21
|
+
searchArticles, getArticleLinks, resolveArticleRelPath, articleHistory, articleDiff,
|
|
22
|
+
setupVault, type VaultStatus,
|
|
23
|
+
} from "./vault";
|
|
24
|
+
import { parseInfobox, parseLead, parseSeriesBanner, articleStructure } from "./article";
|
|
25
|
+
|
|
26
|
+
function jsonOk<T extends object>(body: T, init?: ResponseInit): Response {
|
|
27
|
+
return Response.json({ ok: true, ...body }, init);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function jsonErr(error: string, status = 400): Response {
|
|
31
|
+
return Response.json({ ok: false, error }, { status });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** `{ ok:false, enabled:false }` shape shared by every route when no vault
|
|
35
|
+
* exists yet — never a throw, never a 404: the frontend uses this to
|
|
36
|
+
* render the first-run consent card instead of an error toast. */
|
|
37
|
+
function noVault(root: string): Response {
|
|
38
|
+
return Response.json({ ok: false, enabled: false, error: "no memory vault yet", root });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ---- GET /api/memory/status -------------------------------------------
|
|
42
|
+
|
|
43
|
+
export async function handleMemoryStatus(): Promise<Response> {
|
|
44
|
+
const st = await vaultStatus();
|
|
45
|
+
if (!st.exists) return noVault(st.root);
|
|
46
|
+
return jsonOk({ enabled: true, status: st satisfies VaultStatus });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---- GET /api/memory/list ----------------------------------------------
|
|
50
|
+
|
|
51
|
+
export async function handleMemoryList(): Promise<Response> {
|
|
52
|
+
const root = vaultRoot();
|
|
53
|
+
const st = await vaultStatus(root);
|
|
54
|
+
if (!st.exists) return noVault(root);
|
|
55
|
+
const [articles, reference] = await Promise.all([listArticles(root), listReferenceDocs(root)]);
|
|
56
|
+
return jsonOk({ articles, reference });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---- GET /api/memory/search?q=&scope= -----------------------------------
|
|
60
|
+
|
|
61
|
+
export async function handleMemorySearch(url: URL): Promise<Response> {
|
|
62
|
+
const root = vaultRoot();
|
|
63
|
+
const st = await vaultStatus(root);
|
|
64
|
+
if (!st.exists) return noVault(root);
|
|
65
|
+
const q = (url.searchParams.get("q") ?? "").trim();
|
|
66
|
+
if (!q) return jsonErr("q is required");
|
|
67
|
+
const scopeParam = url.searchParams.get("scope");
|
|
68
|
+
const scope = scopeParam === "articles" || scopeParam === "reference" ? scopeParam : "all";
|
|
69
|
+
const limitParam = Number(url.searchParams.get("limit") ?? "50");
|
|
70
|
+
const limit = Number.isFinite(limitParam) && limitParam > 0 ? Math.min(limitParam, 200) : 50;
|
|
71
|
+
try {
|
|
72
|
+
const { summaries, hits } = await searchArticles(root, q, { limit, scope });
|
|
73
|
+
return jsonOk({ summaries, hits });
|
|
74
|
+
} catch (e) {
|
|
75
|
+
return jsonErr((e as Error).message);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---- GET /api/memory/article?name= --------------------------------------
|
|
80
|
+
//
|
|
81
|
+
// Rendered source + metadata: the article's raw Markdown plus the
|
|
82
|
+
// deterministic structure the panel needs to render it without re-parsing
|
|
83
|
+
// client-side (infobox, lead, series banner, section skeleton).
|
|
84
|
+
|
|
85
|
+
export async function handleMemoryArticle(url: URL): Promise<Response> {
|
|
86
|
+
const root = vaultRoot();
|
|
87
|
+
const st = await vaultStatus(root);
|
|
88
|
+
if (!st.exists) return noVault(root);
|
|
89
|
+
const name = (url.searchParams.get("name") ?? "").trim();
|
|
90
|
+
if (!name) return jsonErr("name is required");
|
|
91
|
+
try {
|
|
92
|
+
const { path, content } = await readArticle(root, name);
|
|
93
|
+
return jsonOk({
|
|
94
|
+
name,
|
|
95
|
+
path,
|
|
96
|
+
content,
|
|
97
|
+
infobox: parseInfobox(content),
|
|
98
|
+
lead: parseLead(content),
|
|
99
|
+
series: parseSeriesBanner(content),
|
|
100
|
+
structure: articleStructure(content),
|
|
101
|
+
});
|
|
102
|
+
} catch (e) {
|
|
103
|
+
return jsonErr((e as Error).message, 404);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---- GET /api/memory/links?name= -----------------------------------------
|
|
108
|
+
|
|
109
|
+
export async function handleMemoryLinks(url: URL): Promise<Response> {
|
|
110
|
+
const root = vaultRoot();
|
|
111
|
+
const st = await vaultStatus(root);
|
|
112
|
+
if (!st.exists) return noVault(root);
|
|
113
|
+
const name = (url.searchParams.get("name") ?? "").trim();
|
|
114
|
+
if (!name) return jsonErr("name is required");
|
|
115
|
+
try {
|
|
116
|
+
const links = await getArticleLinks(root, name);
|
|
117
|
+
return jsonOk({ name, ...links });
|
|
118
|
+
} catch (e) {
|
|
119
|
+
return jsonErr((e as Error).message, 404);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---- GET /api/memory/history?name= ---------------------------------------
|
|
124
|
+
|
|
125
|
+
export async function handleMemoryHistory(url: URL): Promise<Response> {
|
|
126
|
+
const root = vaultRoot();
|
|
127
|
+
const st = await vaultStatus(root);
|
|
128
|
+
if (!st.exists) return noVault(root);
|
|
129
|
+
const name = (url.searchParams.get("name") ?? "").trim();
|
|
130
|
+
if (!name) return jsonErr("name is required");
|
|
131
|
+
const limitParam = Number(url.searchParams.get("limit") ?? "50");
|
|
132
|
+
const limit = Number.isFinite(limitParam) && limitParam > 0 ? Math.min(limitParam, 200) : 50;
|
|
133
|
+
try {
|
|
134
|
+
const relPath = await resolveArticleRelPath(root, name); // throws if not found — validates `name`
|
|
135
|
+
const entries = await articleHistory(root, relPath, limit);
|
|
136
|
+
return jsonOk({ name, isGitRepo: st.isGitRepo, entries });
|
|
137
|
+
} catch (e) {
|
|
138
|
+
return jsonErr((e as Error).message, 404);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---- GET /api/memory/diff?name=&rev= -------------------------------------
|
|
143
|
+
|
|
144
|
+
/** Full 40-hex or an abbreviated (≥4 hex) commit hash — never a ref
|
|
145
|
+
* expression (`HEAD~1`, `main^`, `refs/...`), which could otherwise be
|
|
146
|
+
* abused to walk outside the intended commit or (in principle) smuggle
|
|
147
|
+
* option-like text into the argv. `rev` is passed to Bun.spawn as its own
|
|
148
|
+
* argv element (never through a shell), but this check is the defense
|
|
149
|
+
* against a caller trying to pass anything other than a bare commit id. */
|
|
150
|
+
const REV_RE = /^[0-9a-f]{4,40}$/;
|
|
151
|
+
|
|
152
|
+
export async function handleMemoryDiff(url: URL): Promise<Response> {
|
|
153
|
+
const root = vaultRoot();
|
|
154
|
+
const st = await vaultStatus(root);
|
|
155
|
+
if (!st.exists) return noVault(root);
|
|
156
|
+
const name = (url.searchParams.get("name") ?? "").trim();
|
|
157
|
+
const rev = (url.searchParams.get("rev") ?? "").trim();
|
|
158
|
+
if (!name) return jsonErr("name is required");
|
|
159
|
+
if (!rev) return jsonErr("rev is required");
|
|
160
|
+
if (!REV_RE.test(rev)) return jsonErr("rev must be a commit hash (4-40 hex chars)");
|
|
161
|
+
try {
|
|
162
|
+
const relPath = await resolveArticleRelPath(root, name); // throws if not found — validates `name`
|
|
163
|
+
const diff = await articleDiff(root, rev, relPath);
|
|
164
|
+
if (diff === null) return jsonErr("commit not found or no diff for this article at that revision", 404);
|
|
165
|
+
return jsonOk({ name, rev, diff });
|
|
166
|
+
} catch (e) {
|
|
167
|
+
return jsonErr((e as Error).message, 404);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ---- POST /api/memory/init { path?: string } -----------------------------
|
|
172
|
+
//
|
|
173
|
+
// The consent-card backend: wraps the exact same `setupVault` the CLI's
|
|
174
|
+
// `mlx-bun memory init` calls (src/cli.ts, `case "memory":` → `sub === "init"`)
|
|
175
|
+
// — same idempotent create-dirs/README/Meta-pages/git-init behavior, minus
|
|
176
|
+
// the CLI's interactive extras (seed-from-existing-vault prompt, nightly
|
|
177
|
+
// schedule prompt), which stay CLI-only/TTY-gated and are out of scope here.
|
|
178
|
+
//
|
|
179
|
+
// Unlike the CLI (which only ever calls setupVault(vaultRoot()) — a
|
|
180
|
+
// user-supplied path there is a read-only *seed source*, never the
|
|
181
|
+
// setupVault target), this route takes `path` straight from an untrusted
|
|
182
|
+
// loopback caller. setupVault() mkdir -p's subdirectories into it and, if
|
|
183
|
+
// it isn't already a git repo, runs `git init && git add . && git commit`
|
|
184
|
+
// there — pointed at an arbitrary directory that's a destructive foot-gun
|
|
185
|
+
// (force-commits whatever's already there into a fresh history). There is
|
|
186
|
+
// no product reason for the web panel to redirect the vault root outside
|
|
187
|
+
// the machine's own tmp/vault trees, so `path` is confined to those before
|
|
188
|
+
// it ever reaches setupVault; anything else 400s instead of writing files.
|
|
189
|
+
|
|
190
|
+
/** realpath() of the deepest existing ancestor of `path`, with the
|
|
191
|
+
* non-existent tail (e.g. a not-yet-created vault dir) re-appended
|
|
192
|
+
* literally. Needed because macOS resolves `/var/folders/...` (os.tmpdir())
|
|
193
|
+
* through a `/private` symlink: a brand-new mkdtemp path under it won't
|
|
194
|
+
* literal-prefix-match the realpath'd tmpdir unless we walk up to a real
|
|
195
|
+
* segment the same way. */
|
|
196
|
+
async function realpathNearest(path: string): Promise<string> {
|
|
197
|
+
let cur = resolve(path);
|
|
198
|
+
const tail: string[] = [];
|
|
199
|
+
while (true) {
|
|
200
|
+
try {
|
|
201
|
+
return [await realpath(cur), ...tail.reverse()].join(sep);
|
|
202
|
+
} catch {
|
|
203
|
+
const parent = dirname(cur);
|
|
204
|
+
if (parent === cur) return resolve(path); // hit the fs root without finding anything real
|
|
205
|
+
tail.push(cur.slice(parent.length + 1));
|
|
206
|
+
cur = parent;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Is `candidate` equal to or a descendant of `ancestor`, after resolving
|
|
212
|
+
* symlinks on both sides? */
|
|
213
|
+
async function isUnder(candidate: string, ancestor: string): Promise<boolean> {
|
|
214
|
+
const realAncestor = await realpathNearest(ancestor);
|
|
215
|
+
const realCandidate = await realpathNearest(candidate);
|
|
216
|
+
return realCandidate === realAncestor || realCandidate.startsWith(realAncestor + sep);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Confine an init target to the machine's own vault/tmp trees — the only
|
|
220
|
+
* legitimate destinations for this route (default vault root, or a tmp dir
|
|
221
|
+
* for tests). Rejects anything else rather than handing setupVault() an
|
|
222
|
+
* arbitrary, possibly pre-existing directory to mkdir/git-init into. */
|
|
223
|
+
async function isAllowedVaultTarget(path: string): Promise<boolean> {
|
|
224
|
+
return (await isUnder(path, vaultRoot())) || (await isUnder(path, tmpdir()));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export async function handleMemoryInit(request: Request): Promise<Response> {
|
|
228
|
+
let body: { path?: string } = {};
|
|
229
|
+
try {
|
|
230
|
+
body = (await request.json()) as { path?: string };
|
|
231
|
+
} catch {
|
|
232
|
+
// empty body is fine — defaults to vaultRoot()
|
|
233
|
+
}
|
|
234
|
+
const root = body.path && body.path.trim() ? body.path.trim() : vaultRoot();
|
|
235
|
+
if (!(await isAllowedVaultTarget(root))) {
|
|
236
|
+
return jsonErr("path must be under the memory vault root or a temp directory", 400);
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
const res = await setupVault(root);
|
|
240
|
+
const st = await vaultStatus(res.root);
|
|
241
|
+
return jsonOk({ result: res, status: st });
|
|
242
|
+
} catch (e) {
|
|
243
|
+
return jsonErr((e as Error).message, 500);
|
|
244
|
+
}
|
|
245
|
+
}
|
package/src/memory/vault.ts
CHANGED
|
@@ -218,6 +218,17 @@ export async function readArticle(root: string, article: string): Promise<{ path
|
|
|
218
218
|
return { path, content: await readFile(path, "utf8") };
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
/** Vault-relative path (`articles/<stem>.md` or `Reference/<stem>.md`) for an
|
|
222
|
+
* existing article/reference doc — the safe argv piece for git plumbing
|
|
223
|
+
* (articleHistory/articleDiff), resolved via the same name normalization/
|
|
224
|
+
* existence check as readArticle so a caller can't smuggle a path-traversal
|
|
225
|
+
* or shell-metacharacter name through. Throws if the doc doesn't exist. */
|
|
226
|
+
export async function resolveArticleRelPath(root: string, article: string): Promise<string> {
|
|
227
|
+
const doc = normalizeMemoryDocId(article);
|
|
228
|
+
await resolveArticlePath(root, article); // existence check, reuses the same normalization
|
|
229
|
+
return doc.kind === "reference" ? join("Reference", `${doc.stem}.md`) : join("articles", `${doc.stem}.md`);
|
|
230
|
+
}
|
|
231
|
+
|
|
221
232
|
/** Filename stems for every `*.md` under articles/, sorted alphabetically. */
|
|
222
233
|
export async function listArticles(root: string): Promise<string[]> {
|
|
223
234
|
let names: string[];
|
|
@@ -1013,6 +1024,69 @@ function runGit(args: string[], cwd: string): Promise<boolean> {
|
|
|
1013
1024
|
});
|
|
1014
1025
|
}
|
|
1015
1026
|
|
|
1027
|
+
// ---- read-only git plumbing (per-article history/diff) ----------------
|
|
1028
|
+
//
|
|
1029
|
+
// Used by the memory REST surface (GET /api/memory/history|diff) to render
|
|
1030
|
+
// "watch it self-heal" — the article's commit history and a specific
|
|
1031
|
+
// commit's diff. Read-only plumbing only (log/show), args always an
|
|
1032
|
+
// explicit argv array via Bun.spawn (never a shell string), and callers
|
|
1033
|
+
// MUST validate `rev`/`name` before calling these (see server.ts) — this
|
|
1034
|
+
// layer trusts its `relPath`/`rev` inputs are already safe.
|
|
1035
|
+
|
|
1036
|
+
export interface ArticleHistoryEntry {
|
|
1037
|
+
hash: string;
|
|
1038
|
+
date: string;
|
|
1039
|
+
subject: string;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/** `git log` for one file, newest first, argv-only (never a shell string).
|
|
1043
|
+
* Returns [] when the vault isn't a git repo, the file has no history, or
|
|
1044
|
+
* git is unavailable. `relPath` must already be a repo-relative path (no
|
|
1045
|
+
* `..`, not absolute) — callers validate before calling. */
|
|
1046
|
+
export async function articleHistory(root: string, relPath: string, limit = 50): Promise<ArticleHistoryEntry[]> {
|
|
1047
|
+
if (!(await pathExists(join(root, ".git")))) return [];
|
|
1048
|
+
try {
|
|
1049
|
+
const proc = Bun.spawn(
|
|
1050
|
+
["git", "log", `--max-count=${limit}`, "--date=short", "--pretty=format:%H%x1f%ad%x1f%s%x1e", "--", relPath],
|
|
1051
|
+
{ cwd: root, stdout: "pipe", stderr: "ignore" },
|
|
1052
|
+
);
|
|
1053
|
+
const out = await new Response(proc.stdout).text();
|
|
1054
|
+
if ((await proc.exited) !== 0) return [];
|
|
1055
|
+
return out
|
|
1056
|
+
.split("\x1e")
|
|
1057
|
+
.map((rec) => rec.trim())
|
|
1058
|
+
.filter((rec) => rec.length > 0)
|
|
1059
|
+
.map((rec) => {
|
|
1060
|
+
const [hash = "", date = "", subject = ""] = rec.split("\x1f");
|
|
1061
|
+
return { hash, date, subject };
|
|
1062
|
+
})
|
|
1063
|
+
.filter((e) => e.hash.length > 0);
|
|
1064
|
+
} catch {
|
|
1065
|
+
return [];
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/** `git show <rev> -- <relPath>` — the diff for one commit touching one file.
|
|
1070
|
+
* `rev` must already be validated as a hex commit hash by the caller (never
|
|
1071
|
+
* a ref expression like `HEAD~1` or `main^`, which could be abused as an
|
|
1072
|
+
* argument-injection vector); `relPath` must already be repo-relative.
|
|
1073
|
+
* Returns null when the vault isn't a git repo or the commit/diff can't be
|
|
1074
|
+
* produced. */
|
|
1075
|
+
export async function articleDiff(root: string, rev: string, relPath: string): Promise<string | null> {
|
|
1076
|
+
if (!(await pathExists(join(root, ".git")))) return null;
|
|
1077
|
+
try {
|
|
1078
|
+
const proc = Bun.spawn(
|
|
1079
|
+
["git", "show", rev, "--pretty=format:", "--", relPath],
|
|
1080
|
+
{ cwd: root, stdout: "pipe", stderr: "ignore" },
|
|
1081
|
+
);
|
|
1082
|
+
const out = await new Response(proc.stdout).text();
|
|
1083
|
+
if ((await proc.exited) !== 0) return null;
|
|
1084
|
+
return out;
|
|
1085
|
+
} catch {
|
|
1086
|
+
return null;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1016
1090
|
export interface SetupResult {
|
|
1017
1091
|
root: string;
|
|
1018
1092
|
created: string[];
|
package/src/mlx/array.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// MlxArray: ownership wrapper around an mlx_array handle.
|
|
2
2
|
// Explicit .dispose() is the contract; a FinalizationRegistry backstop
|
|
3
|
-
// frees leaked handles on GC (verified in spikes/phase0-memory.ts).
|
|
3
|
+
// frees leaked handles on GC (verified in lab/spikes/phase0-memory.ts).
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { dlopen, ptr, toArrayBuffer } from "bun:ffi";
|
|
6
6
|
import { C, Dtype, DTYPE_NAMES, type MlxHandle, optInt, outArray, takeMlxError } from "./ffi";
|
|
7
7
|
import type { SafetensorsDtype } from "../safetensors";
|
|
8
8
|
|
|
@@ -24,17 +24,39 @@ export const SAFETENSORS_TO_MLX: Record<SafetensorsDtype, Dtype> = {
|
|
|
24
24
|
|
|
25
25
|
// --- zero-copy pinning ---------------------------------------------------
|
|
26
26
|
// Buffers handed to mlx via new_data_managed_payload must outlive the mlx
|
|
27
|
-
// array.
|
|
28
|
-
//
|
|
29
|
-
//
|
|
27
|
+
// array. fromView pins the JS view in this map (a GC root, nothing more);
|
|
28
|
+
// release is EXPLICIT and JS-thread-only via unpinHostBuffer() — never an
|
|
29
|
+
// mlx dtor. See the hazard note below.
|
|
30
30
|
|
|
31
31
|
const pinned = new Map<number, Uint8Array>();
|
|
32
32
|
let nextPinId = 1;
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)
|
|
34
|
+
// HAZARD (root cause of the 2026-07-06 restart-restore hang): mlx releases
|
|
35
|
+
// buffer Data wherever the LAST shared_ptr drops — often the Metal
|
|
36
|
+
// COMPLETION thread (gpu::eval retains buffers until the command buffer
|
|
37
|
+
// finishes, i.e. past dispose()). A bun:ffi JSCallback dtor invoked from
|
|
38
|
+
// that thread deadlocks when the JS thread is inside a blocking FFI eval
|
|
39
|
+
// (completion waits on JS, JS waits on completion) and SIGTRAPs mid-GC.
|
|
40
|
+
// So NO array here ever gets a JS dtor: fromPointer AND fromView both hand
|
|
41
|
+
// mlx the native no-op dtor below (payload 0), and host-buffer lifetime is
|
|
42
|
+
// the CALLER's contract, released only from the JS thread (unpinHostBuffer)
|
|
43
|
+
// or never (process-lifetime mmaps: expert-offload).
|
|
44
|
+
|
|
45
|
+
// Native no-op dtor: payload is always 0, and libc free(NULL) is defined to
|
|
46
|
+
// do nothing — a dtor mlx can safely call from ANY thread. The pointed-to
|
|
47
|
+
// memory's lifetime is the CALLER's contract (weight mmaps live for the
|
|
48
|
+
// process; restored KV is COPIED at load since 2026-07-07 — see kv-store.ts
|
|
49
|
+
// loadKvCache — so no restore mapping needs pinning anymore). The address
|
|
50
|
+
// comes via dlsym because
|
|
51
|
+
// bun:ffi's symbol .ptr returns the pointer bit-cast to float64
|
|
52
|
+
// (lab/repro/bun-ffi-f64) — passing that back truncates to NULL.
|
|
53
|
+
const libcDlsym = dlopen("/usr/lib/libSystem.B.dylib", {
|
|
54
|
+
dlsym: { args: ["u64", "ptr"], returns: "u64" },
|
|
55
|
+
});
|
|
56
|
+
const FREE_NAME = Buffer.from("free\0");
|
|
57
|
+
const RTLD_DEFAULT = 0xfffffffffffffffen;
|
|
58
|
+
const freeFnAddr = Number(libcDlsym.symbols.dlsym(RTLD_DEFAULT, ptr(FREE_NAME)));
|
|
59
|
+
if (!freeFnAddr) throw new Error("dlsym(free) failed — zero-copy arrays need a native no-op dtor");
|
|
38
60
|
|
|
39
61
|
export function pinnedBufferCount(): number {
|
|
40
62
|
return pinned.size;
|
|
@@ -50,31 +72,61 @@ export class MlxArray {
|
|
|
50
72
|
#handle: MlxHandle;
|
|
51
73
|
#disposed = false;
|
|
52
74
|
#token = {};
|
|
75
|
+
#pinId = 0; // fromView only: key into the pinned-view map (0 = no pin)
|
|
53
76
|
|
|
54
77
|
constructor(handle: MlxHandle) {
|
|
55
78
|
this.#handle = handle;
|
|
56
79
|
registry.register(this, handle, this.#token);
|
|
57
80
|
}
|
|
58
81
|
|
|
59
|
-
/** Zero-copy: wrap an existing buffer
|
|
60
|
-
*
|
|
82
|
+
/** Zero-copy: wrap an existing JS buffer. The view is pinned (GC-rooted)
|
|
83
|
+
* in a process-side map; mlx gets the NATIVE no-op dtor (free(NULL)) —
|
|
84
|
+
* never a JS callback, so the last release may happen on the Metal
|
|
85
|
+
* completion thread without deadlock (see hazard note above). Buffer
|
|
86
|
+
* lifetime is therefore the CALLER's contract:
|
|
87
|
+
* - mmap-backed views (toArrayBuffer aliases): the mapping owns the
|
|
88
|
+
* memory; prefer fromPointer, which skips the pin entirely.
|
|
89
|
+
* - JS-heap buffers: the pin keeps the buffer alive; call
|
|
90
|
+
* unpinHostBuffer() only after a JS-side sync point proves mlx is
|
|
91
|
+
* done with it (everything depending on the array evaluated AND the
|
|
92
|
+
* array disposed), or never (process-lifetime, e.g. short scripts). */
|
|
61
93
|
static fromView(view: Uint8Array, shape: number[], dtype: Dtype): MlxArray {
|
|
62
94
|
const sb = shapeBuf(shape);
|
|
63
95
|
const id = nextPinId++;
|
|
64
96
|
pinned.set(id, view);
|
|
65
97
|
const handle = C.mlx_array_new_data_managed_payload(
|
|
66
|
-
ptr(view), ptr(sb), shape.length, dtype,
|
|
98
|
+
ptr(view), ptr(sb), shape.length, dtype, 0, freeFnAddr,
|
|
67
99
|
);
|
|
68
|
-
|
|
100
|
+
const arr = new MlxArray(handle);
|
|
101
|
+
arr.#pinId = id;
|
|
102
|
+
return arr;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Release the GC root fromView placed on this array's host buffer.
|
|
106
|
+
* JS-thread-only by construction (it's a plain method) — this is the
|
|
107
|
+
* replacement for the deadlocking JSCallback unpin dtor. Idempotent;
|
|
108
|
+
* no-op for non-fromView arrays. Caller contract: only call once mlx can
|
|
109
|
+
* no longer touch the buffer (see fromView doc), or when the memory is
|
|
110
|
+
* owned elsewhere (an mmap kept mapped past every use). */
|
|
111
|
+
unpinHostBuffer(): void {
|
|
112
|
+
if (this.#pinId !== 0) {
|
|
113
|
+
pinned.delete(this.#pinId);
|
|
114
|
+
this.#pinId = 0;
|
|
115
|
+
}
|
|
69
116
|
}
|
|
70
117
|
|
|
71
118
|
/** Zero-copy: wrap a raw pointer (e.g. into an mmap'd weight shard).
|
|
72
|
-
* The caller guarantees the memory outlives
|
|
73
|
-
*
|
|
119
|
+
* The caller guarantees the memory outlives EVERY mlx
|
|
120
|
+
* reference to the array — including GPU command buffers that retain the
|
|
121
|
+
* buffer past dispose() — i.e. mmaps backing these arrays stay mapped for
|
|
122
|
+
* the process. The dtor is free(NULL) (payload 0): native + no-op, so
|
|
123
|
+
* mlx may run it from the Metal completion thread without touching the
|
|
124
|
+
* JS runtime (the old JSCallback dtor deadlocked serving / SIGTRAPed
|
|
125
|
+
* under GC when that thread held the last reference — 2026-07-06). */
|
|
74
126
|
static fromPointer(dataPtr: number, shape: number[], dtype: Dtype): MlxArray {
|
|
75
127
|
const sb = shapeBuf(shape);
|
|
76
128
|
const handle = C.mlx_array_new_data_managed_payload(
|
|
77
|
-
dataPtr, ptr(sb), shape.length, dtype, 0,
|
|
129
|
+
dataPtr, ptr(sb), shape.length, dtype, 0, freeFnAddr,
|
|
78
130
|
);
|
|
79
131
|
return new MlxArray(handle);
|
|
80
132
|
}
|
|
@@ -138,7 +190,7 @@ export class MlxArray {
|
|
|
138
190
|
return Number(C.mlx_array_ndim(this.handle));
|
|
139
191
|
}
|
|
140
192
|
|
|
141
|
-
// NOTE on the DFG stale-read bug (repro/bun-ffi-f64/ISSUE.md): the
|
|
193
|
+
// NOTE on the DFG stale-read bug (lab/repro/bun-ffi-f64/ISSUE.md): the
|
|
142
194
|
// toArrayBuffer readbacks below (shape/rawBytes/toFloat32) are safe.
|
|
143
195
|
// The hazard is reading a *pre-existing* typed array after an FFI call
|
|
144
196
|
// wrote through its pointer — the JIT forwards a stale value from before
|
|
@@ -183,6 +235,18 @@ export class MlxArray {
|
|
|
183
235
|
|
|
184
236
|
/** Raw bytes of the evaluated array (copy). bf16/f16/f32 only. */
|
|
185
237
|
rawBytes(): Uint8Array {
|
|
238
|
+
return this.rawBytesView().slice();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** ZERO-COPY view of the evaluated array's bytes — aliases the mlx
|
|
242
|
+
* buffer directly, no JS-heap copy. Valid only while THIS array is
|
|
243
|
+
* alive and unmutated: do not retain past dispose(). Built for
|
|
244
|
+
* bulk-write paths (kv-store's streamed writer) where per-tensor
|
|
245
|
+
* rawBytes() copies pile up in the JS heap faster than GC collects
|
|
246
|
+
* them — the residual A7 whole-entry RSS spike (2026-07-07). The
|
|
247
|
+
* array must be contiguous (ops.contiguous first for views), same
|
|
248
|
+
* contract as rawBytes. */
|
|
249
|
+
rawBytesView(): Uint8Array {
|
|
186
250
|
this.eval();
|
|
187
251
|
const dt = this.dtype;
|
|
188
252
|
const p =
|
|
@@ -190,9 +254,32 @@ export class MlxArray {
|
|
|
190
254
|
: dt === Dtype.float16 ? C.mlx_array_data_float16(this.handle)
|
|
191
255
|
: dt === Dtype.bfloat16 ? C.mlx_array_data_bfloat16(this.handle)
|
|
192
256
|
: dt === Dtype.uint32 ? C.mlx_array_data_uint32(this.handle)
|
|
257
|
+
: dt === Dtype.uint8 ? C.mlx_array_data_uint8(this.handle)
|
|
258
|
+
: dt === Dtype.int8 ? C.mlx_array_data_int8(this.handle)
|
|
193
259
|
: null;
|
|
194
|
-
if (p === null) throw new Error(`
|
|
195
|
-
return new Uint8Array(toArrayBuffer(p!, 0, this.nbytes))
|
|
260
|
+
if (p === null) throw new Error(`rawBytesView: unsupported dtype ${this.dtypeName}`);
|
|
261
|
+
return new Uint8Array(toArrayBuffer(p!, 0, this.nbytes));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** Read back an INTEGER array without enqueueing a cast kernel: eval +
|
|
265
|
+
* direct buffer read for uint32/int32. A cast (`astype`) is a NEW op that
|
|
266
|
+
* queues BEHIND everything already dispatched on the GPU stream — on the
|
|
267
|
+
* batched decode hot path, reading the pipeline register via toFloat32
|
|
268
|
+
* stalled the "overlapped" token read for a FULL step of GPU work every
|
|
269
|
+
* token (the core of the Phase-0 B=1 gap; unified-engine plan Phase 2).
|
|
270
|
+
* Non-integer dtypes fall back to the rounded float readback. */
|
|
271
|
+
toIntTokens(): number[] {
|
|
272
|
+
if (this.dtype === Dtype.uint32) {
|
|
273
|
+
this.eval();
|
|
274
|
+
const p = C.mlx_array_data_uint32(this.handle);
|
|
275
|
+
return [...new Uint32Array(toArrayBuffer(p!, 0, this.size * 4))];
|
|
276
|
+
}
|
|
277
|
+
if (this.dtype === Dtype.int32) {
|
|
278
|
+
this.eval();
|
|
279
|
+
const p = C.mlx_array_data_int32(this.handle);
|
|
280
|
+
return [...new Int32Array(toArrayBuffer(p!, 0, this.size * 4))];
|
|
281
|
+
}
|
|
282
|
+
return [...this.toFloat32()].map((x) => Math.round(x));
|
|
196
283
|
}
|
|
197
284
|
|
|
198
285
|
/** Read back as float32 (casts on GPU if needed, then copies out). */
|
|
@@ -205,6 +292,49 @@ export class MlxArray {
|
|
|
205
292
|
return out;
|
|
206
293
|
}
|
|
207
294
|
|
|
295
|
+
/** Read an f32/f16/bf16 array numerically as float32 without enqueueing a
|
|
296
|
+
* GPU cast. This evaluates the ORIGINAL array, then expands 16-bit values
|
|
297
|
+
* on the host. Use this for small readbacks after later GPU work has
|
|
298
|
+
* already been dispatched: toFloat32() would append astype(float32) behind
|
|
299
|
+
* that work and turn the readback into an accidental full-step barrier. */
|
|
300
|
+
toFloat32Host(): Float32Array {
|
|
301
|
+
this.eval();
|
|
302
|
+
const dt = this.dtype;
|
|
303
|
+
if (dt === Dtype.float32) {
|
|
304
|
+
const p = C.mlx_array_data_float32(this.handle);
|
|
305
|
+
return new Float32Array(toArrayBuffer(p!, 0, this.size * 4)).slice();
|
|
306
|
+
}
|
|
307
|
+
if (dt === Dtype.bfloat16) {
|
|
308
|
+
const p = C.mlx_array_data_bfloat16(this.handle);
|
|
309
|
+
const src = new Uint16Array(toArrayBuffer(p!, 0, this.size * 2));
|
|
310
|
+
const words = new Uint32Array(src.length);
|
|
311
|
+
for (let i = 0; i < src.length; i++) words[i] = src[i]! << 16;
|
|
312
|
+
return new Float32Array(words.buffer);
|
|
313
|
+
}
|
|
314
|
+
if (dt === Dtype.float16) {
|
|
315
|
+
const p = C.mlx_array_data_float16(this.handle);
|
|
316
|
+
const src = new Uint16Array(toArrayBuffer(p!, 0, this.size * 2));
|
|
317
|
+
const out = new Float32Array(src.length);
|
|
318
|
+
for (let i = 0; i < src.length; i++) {
|
|
319
|
+
const bits = src[i]!;
|
|
320
|
+
const sign = bits & 0x8000;
|
|
321
|
+
const exponent = (bits >>> 10) & 0x1f;
|
|
322
|
+
const fraction = bits & 0x03ff;
|
|
323
|
+
let value: number;
|
|
324
|
+
if (exponent === 0) {
|
|
325
|
+
value = fraction === 0 ? 0 : fraction * 2 ** -24;
|
|
326
|
+
} else if (exponent === 0x1f) {
|
|
327
|
+
value = fraction === 0 ? Infinity : NaN;
|
|
328
|
+
} else {
|
|
329
|
+
value = (1 + fraction / 1024) * 2 ** (exponent - 15);
|
|
330
|
+
}
|
|
331
|
+
out[i] = sign ? -value : value;
|
|
332
|
+
}
|
|
333
|
+
return out;
|
|
334
|
+
}
|
|
335
|
+
throw new Error(`toFloat32Host: unsupported dtype ${this.dtypeName}`);
|
|
336
|
+
}
|
|
337
|
+
|
|
208
338
|
/** Basic slice: start/stop per dimension (stride 1). */
|
|
209
339
|
slice(start: number[], stop: number[], stream: MlxHandle = gpuStream): MlxArray {
|
|
210
340
|
const s = new Int32Array(start);
|