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.
- package/LICENSE +21 -0
- package/README.md +362 -0
- package/THIRD_PARTY_LICENSES.md +41 -0
- package/bin/mlx-bun.mjs +32 -0
- package/docs/curve-designer.html +303 -0
- package/docs/design/adapters-end-to-end.md +201 -0
- package/docs/design/compat-cli-surface-design.md +227 -0
- package/docs/design/docs-reorg-plan.md +152 -0
- package/docs/design/hlg-sampling.md +340 -0
- package/docs/design/memory-system.md +339 -0
- package/docs/design/optimization_plan.md +344 -0
- package/docs/design/parallel-slots.md +515 -0
- package/docs/design/runtime-isolation.md +247 -0
- package/docs/design/segmented-backward-training.md +508 -0
- package/docs/investigations/chunk-finetune-experiment.md +84 -0
- package/docs/investigations/curve-bisector-routing.md +123 -0
- package/docs/investigations/curve-runs/bisector-route.json +98 -0
- package/docs/investigations/curve-runs/bisector-trace.json +1393 -0
- package/docs/investigations/curve-runs/curve-terrain.json +1179 -0
- package/docs/investigations/curve-runs/curve-terrain.svg +42 -0
- package/docs/investigations/curve-terrain.html +105 -0
- package/docs/investigations/expert-offload-single-user-moe.md +292 -0
- package/docs/investigations/hlg-ab.html +34 -0
- package/docs/investigations/hlg-explorer.html +238 -0
- package/docs/investigations/hlg-figs/cliff.svg +1 -0
- package/docs/investigations/hlg-figs/cross.svg +1 -0
- package/docs/investigations/hlg-figs/edges.svg +1 -0
- package/docs/investigations/hlg-figs/k20-grids.svg +1 -0
- package/docs/investigations/hlg-figs/k20-peaks.svg +1 -0
- package/docs/investigations/hlg-figs/lw-curve.svg +1 -0
- package/docs/investigations/hlg-figs/terrain.svg +1 -0
- package/docs/investigations/hlg-report.html +262 -0
- package/docs/investigations/hlg-runs/hlg-ab.json +149 -0
- package/docs/investigations/hlg-runs/hlg-blind-prompts.md +87 -0
- package/docs/investigations/hlg-runs/hlg-blind.json +107 -0
- package/docs/investigations/hlg-runs/hlg-cliff.json +197 -0
- package/docs/investigations/hlg-runs/hlg-cross.json +505 -0
- package/docs/investigations/hlg-runs/hlg-edge.json +388 -0
- package/docs/investigations/hlg-runs/hlg-eval1.json +25 -0
- package/docs/investigations/hlg-runs/hlg-frontier.json +86 -0
- package/docs/investigations/hlg-runs/hlg-grid.json +242 -0
- package/docs/investigations/hlg-runs/hlg-grid2.json +343 -0
- package/docs/investigations/hlg-runs/hlg-lw-coarse.json +110 -0
- package/docs/investigations/hlg-runs/hlg-map.json +383 -0
- package/docs/investigations/hlg-runs/run0-original.log +159 -0
- package/docs/investigations/hlg-runs/runA-recommended.log +131 -0
- package/docs/investigations/hlg-runs/runAA-cliff-cpm5.log +26 -0
- package/docs/investigations/hlg-runs/runAB-grid2-e4b.log +18 -0
- package/docs/investigations/hlg-runs/runAC-peak-e4b-partial.log +25 -0
- package/docs/investigations/hlg-runs/runAD-ab-samples.log +8 -0
- package/docs/investigations/hlg-runs/runAE-edge-e4b.log +51 -0
- package/docs/investigations/hlg-runs/runAF-logit-calibration.log +10 -0
- package/docs/investigations/hlg-runs/runAG-eval-config2.log +16 -0
- package/docs/investigations/hlg-runs/runB-gain-variants.log +192 -0
- package/docs/investigations/hlg-runs/runC-pivots.log +162 -0
- package/docs/investigations/hlg-runs/runD-oetf.log +132 -0
- package/docs/investigations/hlg-runs/runE-eotf.log +162 -0
- package/docs/investigations/hlg-runs/runF-shaper-working.log +86 -0
- package/docs/investigations/hlg-runs/runG-shaper-sweep.log +101 -0
- package/docs/investigations/hlg-runs/runH-wsweep.log +176 -0
- package/docs/investigations/hlg-runs/runI-asweep.log +176 -0
- package/docs/investigations/hlg-runs/runJ-knobmap.log +49 -0
- package/docs/investigations/hlg-runs/runK-frontier.log +19 -0
- package/docs/investigations/hlg-runs/runL-widemap.log +65 -0
- package/docs/investigations/hlg-runs/runM-frontier-emb.log +15 -0
- package/docs/investigations/hlg-runs/runN-verify.log +116 -0
- package/docs/investigations/hlg-runs/runO-frontier-seed5000.log +15 -0
- package/docs/investigations/hlg-runs/runP-grid-AxSm.log +15 -0
- package/docs/investigations/hlg-runs/runQ-grid-tgW.log +15 -0
- package/docs/investigations/hlg-runs/runR-blind-transcript.log +79 -0
- package/docs/investigations/hlg-runs/runS-cross-coherence.log +33 -0
- package/docs/investigations/hlg-runs/runT-cross-cpm5.log +33 -0
- package/docs/investigations/hlg-runs/runU-cross-12b.log +33 -0
- package/docs/investigations/hlg-runs/runV-lw-e4b.log +22 -0
- package/docs/investigations/hlg-runs/runW-lw-12b.log +22 -0
- package/docs/investigations/hlg-runs/runX-lw-cpm5.log +22 -0
- package/docs/investigations/hlg-runs/runY-cliff-e4b.log +26 -0
- package/docs/investigations/hlg-runs/runZ-cliff-12b.log +26 -0
- package/docs/investigations/hlg-sampling-investigation.md +970 -0
- package/docs/investigations/lab-build-journal.md +157 -0
- package/docs/investigations/pi-builtin-investigation.md +308 -0
- package/docs/investigations/starter-model-port-handoff.md +323 -0
- package/docs/planning/IDEAS.md +26 -0
- package/docs/planning/PRODUCT_ROADMAP.md +426 -0
- package/docs/planning/ResearchTopics.md +119 -0
- package/docs/planning/chunk-task-roadmap.md +77 -0
- package/docs/planning/journal.md +155 -0
- package/docs/reference/distribution.md +121 -0
- package/docs/reference/embedding.md +168 -0
- package/docs/reference/library-api.md +153 -0
- package/docs/reference/server-api.md +224 -0
- package/docs/reference/server-config.md +243 -0
- package/docs/segmented-backward-handoff.md +148 -0
- package/package.json +68 -0
- package/src/anthropic.ts +491 -0
- package/src/chat-template.ts +186 -0
- package/src/cli.ts +1075 -0
- package/src/config.ts +229 -0
- package/src/curve-sampler.ts +122 -0
- package/src/dataset/generators.ts +1087 -0
- package/src/dataset/index.ts +12 -0
- package/src/dataset/job.ts +81 -0
- package/src/dataset/llm.ts +71 -0
- package/src/dataset/registry.ts +528 -0
- package/src/download.ts +283 -0
- package/src/eval/README.md +86 -0
- package/src/eval/kl-prompts.ts +29 -0
- package/src/eval/kl.ts +294 -0
- package/src/eval/quality-db.ts +92 -0
- package/src/eval/runner.ts +102 -0
- package/src/eval/score.ts +49 -0
- package/src/eval/tasks/bfcl.ts +500 -0
- package/src/eval/tasks/gsm8k.ts +85 -0
- package/src/eval/tasks/hashhop.ts +109 -0
- package/src/eval/tasks/humaneval.ts +252 -0
- package/src/eval/tasks/ifeval.ts +354 -0
- package/src/eval/tasks/mmlu.ts +145 -0
- package/src/evaldb.ts +199 -0
- package/src/expert-offload-build.ts +77 -0
- package/src/expert-offload.ts +61 -0
- package/src/expert-trace.ts +67 -0
- package/src/fit.ts +254 -0
- package/src/generate.ts +451 -0
- package/src/harness-pi.ts +177 -0
- package/src/hf-push.ts +628 -0
- package/src/index.ts +22 -0
- package/src/jobs/db.ts +139 -0
- package/src/jobs/index.ts +29 -0
- package/src/jobs/job-entry.ts +106 -0
- package/src/jobs/runner.ts +304 -0
- package/src/jobs/sse.ts +114 -0
- package/src/jobs/types.ts +58 -0
- package/src/kv-store.ts +185 -0
- package/src/lora.ts +314 -0
- package/src/md-modules.d.ts +7 -0
- package/src/mlx/array.ts +237 -0
- package/src/mlx/autograd.ts +267 -0
- package/src/mlx/checkpoint.ts +116 -0
- package/src/mlx/compile.ts +158 -0
- package/src/mlx/custom-vjp.ts +140 -0
- package/src/mlx/ffi.ts +433 -0
- package/src/mlx/metal-kernel.ts +130 -0
- package/src/mlx/ops.ts +707 -0
- package/src/mmap.ts +90 -0
- package/src/model/batched-mask.ts +204 -0
- package/src/model/batched-rotating.ts +300 -0
- package/src/model/compiled-decode.ts +681 -0
- package/src/model/factory.ts +27 -0
- package/src/model/fingerprint.ts +33 -0
- package/src/model/flash-attention.ts +674 -0
- package/src/model/fused-decode-kernel.ts +305 -0
- package/src/model/fused-geglu-kernel.ts +87 -0
- package/src/model/gemma4-base.ts +1616 -0
- package/src/model/gemma4.ts +1012 -0
- package/src/model/generated/gemma4-12b.ts +526 -0
- package/src/model/generated/gemma4-26b.ts +500 -0
- package/src/model/generated/gemma4-e4b.ts +757 -0
- package/src/model/generated/index.ts +18 -0
- package/src/model/minicpm5.ts +278 -0
- package/src/model/qwen3-delta.ts +224 -0
- package/src/model/qwen3_5.ts +429 -0
- package/src/model/support.ts +46 -0
- package/src/native-pack.ts +152 -0
- package/src/pi-provider.ts +95 -0
- package/src/pi-terminal.ts +264 -0
- package/src/pi-web.ts +924 -0
- package/src/preflight.ts +123 -0
- package/src/prompt-cache.ts +113 -0
- package/src/quantize/allocator.ts +627 -0
- package/src/quantize/calibration.ts +180 -0
- package/src/quantize/config-writer.ts +132 -0
- package/src/quantize/index.ts +57 -0
- package/src/quantize/job.ts +136 -0
- package/src/quantize/quantizer.ts +462 -0
- package/src/quantize/safetensors-writer.ts +171 -0
- package/src/quantize/sensitivity.ts +234 -0
- package/src/registry.ts +336 -0
- package/src/responses.ts +774 -0
- package/src/safetensors.ts +158 -0
- package/src/sampler.ts +517 -0
- package/src/serve/batch-scheduler.ts +362 -0
- package/src/serve/generation-gateway.ts +160 -0
- package/src/server.ts +1891 -0
- package/src/spec/drafter.ts +261 -0
- package/src/spec/generate.ts +221 -0
- package/src/status-page.html +497 -0
- package/src/tokenizer.ts +38 -0
- package/src/tool-call.ts +242 -0
- package/src/train/dataset.ts +461 -0
- package/src/train/export.ts +32 -0
- package/src/train/forward.ts +229 -0
- package/src/train/index.ts +43 -0
- package/src/train/job.ts +148 -0
- package/src/train/lora-params.ts +194 -0
- package/src/train/loss.ts +342 -0
- package/src/train/merge.ts +195 -0
- package/src/train/optimizer.ts +176 -0
- package/src/train/rank.ts +101 -0
- package/src/train/segmented.ts +520 -0
- package/src/train/trainer.ts +600 -0
- package/src/tui.ts +161 -0
- package/src/vision/embedder.ts +168 -0
- package/src/vision/preprocess.ts +247 -0
- package/src/vision/prompt.ts +158 -0
- package/src/vision/siglip.ts +599 -0
- package/src/web/app.html +2471 -0
- package/src/web/skills/web-research/SKILL.md +44 -0
- package/src/web/skills.ts +59 -0
- package/src/web-tools.ts +426 -0
- package/src/weights.ts +91 -0
package/src/download.ts
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
// Resumable HF downloads with checksum verification (Phase 5).
|
|
2
|
+
//
|
|
3
|
+
// The Python downloader failure modes this replaces (PLAN "Context /
|
|
4
|
+
// lore"): Xet stalls (we speak plain HTTPS resolve/CDN), no resume on
|
|
5
|
+
// flaky links (we Range-resume partial blobs), and silent corruption
|
|
6
|
+
// (every blob is verified — sha256 for LFS files against the API's
|
|
7
|
+
// oid, git-blob sha1 for small files against blobId).
|
|
8
|
+
//
|
|
9
|
+
// Writes the exact huggingface_hub cache layout so the registry and
|
|
10
|
+
// loaders find the result with zero changes:
|
|
11
|
+
// <hub>/models--{org}--{name}/blobs/<sha> (verified bytes)
|
|
12
|
+
// <hub>/models--{org}--{name}/snapshots/<commit>/<file> → ../../blobs/<sha>
|
|
13
|
+
// <hub>/models--{org}--{name}/refs/<revision> (commit sha)
|
|
14
|
+
//
|
|
15
|
+
// Files download sequentially (decode is bandwidth-bound, the network
|
|
16
|
+
// rarely is; resumability matters more than parallelism here).
|
|
17
|
+
|
|
18
|
+
import { mkdirSync, existsSync, statSync, createWriteStream, renameSync, rmSync, symlinkSync, readFileSync } from "node:fs";
|
|
19
|
+
import { dirname, join } from "node:path";
|
|
20
|
+
import { DEFAULT_HUB } from "./registry";
|
|
21
|
+
|
|
22
|
+
const ENDPOINT = "https://huggingface.co";
|
|
23
|
+
|
|
24
|
+
export interface RepoFile {
|
|
25
|
+
rfilename: string;
|
|
26
|
+
size: number;
|
|
27
|
+
/** git blob sha1 (non-LFS identity, and the blob filename for non-LFS). */
|
|
28
|
+
blobId: string;
|
|
29
|
+
/** LFS sha256 + size; present for large files. oid is the blob filename. */
|
|
30
|
+
lfs?: { oid: string; size: number };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface RepoListing {
|
|
34
|
+
/** Commit sha the revision resolves to (snapshot dir name). */
|
|
35
|
+
sha: string;
|
|
36
|
+
files: RepoFile[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface DownloadOptions {
|
|
40
|
+
revision?: string;
|
|
41
|
+
cacheDir?: string;
|
|
42
|
+
endpoint?: string;
|
|
43
|
+
token?: string | null;
|
|
44
|
+
onProgress?: (file: string, received: number, total: number) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** HF auth: explicit > env > the file `hf auth login` writes. */
|
|
48
|
+
export function hfToken(): string | null {
|
|
49
|
+
if (process.env.HF_TOKEN) return process.env.HF_TOKEN;
|
|
50
|
+
try {
|
|
51
|
+
return readFileSync(`${process.env.HOME}/.cache/huggingface/token`, "utf8").trim() || null;
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** git blob identity: sha1("blob <size>\0" + content) — what blobId is. */
|
|
58
|
+
export function gitBlobSha1(bytes: Uint8Array): string {
|
|
59
|
+
const hasher = new Bun.CryptoHasher("sha1");
|
|
60
|
+
hasher.update(`blob ${bytes.length}\0`);
|
|
61
|
+
hasher.update(bytes);
|
|
62
|
+
return hasher.digest("hex");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function listRepoFiles(
|
|
66
|
+
repoId: string, opts: DownloadOptions = {},
|
|
67
|
+
): Promise<RepoListing> {
|
|
68
|
+
const endpoint = opts.endpoint ?? ENDPOINT;
|
|
69
|
+
const revision = opts.revision ?? "main";
|
|
70
|
+
const token = opts.token === undefined ? hfToken() : opts.token;
|
|
71
|
+
const url = `${endpoint}/api/models/${repoId}/revision/${encodeURIComponent(revision)}?blobs=true`;
|
|
72
|
+
const res = await fetch(url, {
|
|
73
|
+
headers: token ? { authorization: `Bearer ${token}` } : {},
|
|
74
|
+
});
|
|
75
|
+
if (!res.ok)
|
|
76
|
+
throw new Error(`HF API ${res.status} for ${repoId}@${revision}: ${(await res.text()).slice(0, 200)}`);
|
|
77
|
+
const body = (await res.json()) as {
|
|
78
|
+
sha: string;
|
|
79
|
+
siblings: { rfilename: string; size?: number; blobId?: string; lfs?: { oid: string; size: number } }[];
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
sha: body.sha,
|
|
83
|
+
files: body.siblings.map((s) => ({
|
|
84
|
+
rfilename: s.rfilename,
|
|
85
|
+
size: s.lfs?.size ?? s.size ?? 0,
|
|
86
|
+
blobId: s.blobId ?? "",
|
|
87
|
+
lfs: s.lfs,
|
|
88
|
+
})),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Follow redirects manually so the Authorization header is DROPPED at
|
|
93
|
+
* the CDN hop — presigned S3/CloudFront URLs reject requests that
|
|
94
|
+
* carry it (the classic hub-client footgun). Range survives. */
|
|
95
|
+
async function fetchBlob(
|
|
96
|
+
url: string, token: string | null, rangeStart: number,
|
|
97
|
+
): Promise<Response> {
|
|
98
|
+
const range: Record<string, string> = {};
|
|
99
|
+
if (rangeStart > 0) range["range"] = `bytes=${rangeStart}-`;
|
|
100
|
+
const authed: Record<string, string> = { ...range };
|
|
101
|
+
if (token) authed["authorization"] = `Bearer ${token}`;
|
|
102
|
+
let res = await fetch(url, { redirect: "manual", headers: authed });
|
|
103
|
+
let hops = 0;
|
|
104
|
+
while (res.status >= 300 && res.status < 400 && hops < 5) {
|
|
105
|
+
const loc = res.headers.get("location");
|
|
106
|
+
if (!loc) break;
|
|
107
|
+
// no auth past the redirect — presigned URLs reject it
|
|
108
|
+
res = await fetch(new URL(loc, url).href, { redirect: "manual", headers: range });
|
|
109
|
+
hops++;
|
|
110
|
+
}
|
|
111
|
+
return res;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function downloadOne(
|
|
115
|
+
url: string, token: string | null, blobPath: string,
|
|
116
|
+
expected: { size: number; sha256?: string; sha1?: string; name: string },
|
|
117
|
+
onProgress?: DownloadOptions["onProgress"],
|
|
118
|
+
): Promise<void> {
|
|
119
|
+
const partPath = `${blobPath}.incomplete`;
|
|
120
|
+
let offset = existsSync(partPath) ? statSync(partPath).size : 0;
|
|
121
|
+
if (offset > expected.size) {
|
|
122
|
+
rmSync(partPath); // stale junk; start over
|
|
123
|
+
offset = 0;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Streaming verification — resume re-hashes the existing prefix so the
|
|
127
|
+
// final digest always covers every byte on disk.
|
|
128
|
+
const sha256 = new Bun.CryptoHasher("sha256");
|
|
129
|
+
const sha1 = new Bun.CryptoHasher("sha1");
|
|
130
|
+
sha1.update(`blob ${expected.size}\0`);
|
|
131
|
+
if (offset > 0) {
|
|
132
|
+
const existing = await Bun.file(partPath).arrayBuffer();
|
|
133
|
+
sha256.update(existing);
|
|
134
|
+
sha1.update(existing);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (offset < expected.size) {
|
|
138
|
+
const res = await fetchBlob(url, token, offset);
|
|
139
|
+
if (res.status === 200 && offset > 0) {
|
|
140
|
+
// server ignored Range — start the file (and hashes) over
|
|
141
|
+
rmSync(partPath);
|
|
142
|
+
return downloadOne(url, token, blobPath, expected, onProgress);
|
|
143
|
+
}
|
|
144
|
+
if (res.status !== 200 && res.status !== 206)
|
|
145
|
+
throw new Error(`download failed (${res.status}) for ${expected.name}`);
|
|
146
|
+
const out = createWriteStream(partPath, { flags: offset > 0 ? "a" : "w" });
|
|
147
|
+
try {
|
|
148
|
+
for await (const chunk of res.body as ReadableStream<Uint8Array>) {
|
|
149
|
+
sha256.update(chunk);
|
|
150
|
+
sha1.update(chunk);
|
|
151
|
+
await new Promise<void>((resolve, reject) =>
|
|
152
|
+
out.write(chunk, (e) => (e ? reject(e) : resolve())),
|
|
153
|
+
);
|
|
154
|
+
offset += chunk.length;
|
|
155
|
+
onProgress?.(expected.name, offset, expected.size);
|
|
156
|
+
}
|
|
157
|
+
} finally {
|
|
158
|
+
await new Promise<void>((resolve) => out.end(() => resolve()));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (expected.size === 0 && !existsSync(partPath)) await Bun.write(partPath, "");
|
|
163
|
+
if (offset !== expected.size) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`short download for ${expected.name}: ${offset} of ${expected.size} bytes ` +
|
|
166
|
+
`(.incomplete kept — rerun to resume)`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
const digestOk = expected.sha256
|
|
170
|
+
? sha256.digest("hex") === expected.sha256
|
|
171
|
+
: expected.sha1
|
|
172
|
+
? sha1.digest("hex") === expected.sha1
|
|
173
|
+
: true;
|
|
174
|
+
if (!digestOk) {
|
|
175
|
+
rmSync(partPath); // corrupt — never resume from it
|
|
176
|
+
throw new Error(`checksum mismatch for ${expected.name} — partial removed, rerun to retry`);
|
|
177
|
+
}
|
|
178
|
+
renameSync(partPath, blobPath);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Download (or complete) a model snapshot. Returns the snapshot path.
|
|
182
|
+
* Idempotent: verified blobs are never re-fetched; partial blobs
|
|
183
|
+
* resume with a Range request. */
|
|
184
|
+
/** Process-global download tracker — the status page's /downloads
|
|
185
|
+
* endpoint reads this. Only downloads performed by THIS process are
|
|
186
|
+
* visible (a `mlx-bun get` in another terminal is not). */
|
|
187
|
+
export interface DownloadStatus {
|
|
188
|
+
repoId: string;
|
|
189
|
+
state: "active" | "done" | "error";
|
|
190
|
+
currentFile: string | null;
|
|
191
|
+
receivedBytes: number;
|
|
192
|
+
totalBytes: number;
|
|
193
|
+
filesDone: number;
|
|
194
|
+
filesTotal: number;
|
|
195
|
+
/** Rolling-window transfer rate (server-measured, ~5 s window). */
|
|
196
|
+
bytesPerSec: number;
|
|
197
|
+
startedAt: number;
|
|
198
|
+
finishedAt: number | null;
|
|
199
|
+
error?: string;
|
|
200
|
+
}
|
|
201
|
+
const downloadLog: DownloadStatus[] = [];
|
|
202
|
+
export function downloadsSnapshot(): DownloadStatus[] {
|
|
203
|
+
return downloadLog.slice(-5);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function downloadModel(
|
|
207
|
+
repoId: string, opts: DownloadOptions = {},
|
|
208
|
+
): Promise<string> {
|
|
209
|
+
const endpoint = opts.endpoint ?? ENDPOINT;
|
|
210
|
+
const revision = opts.revision ?? "main";
|
|
211
|
+
const token = opts.token === undefined ? hfToken() : opts.token;
|
|
212
|
+
const hub = opts.cacheDir ?? DEFAULT_HUB;
|
|
213
|
+
|
|
214
|
+
const listing = await listRepoFiles(repoId, { ...opts, token });
|
|
215
|
+
const repoDir = join(hub, `models--${repoId.replaceAll("/", "--")}`);
|
|
216
|
+
const blobsDir = join(repoDir, "blobs");
|
|
217
|
+
const snapDir = join(repoDir, "snapshots", listing.sha);
|
|
218
|
+
mkdirSync(blobsDir, { recursive: true });
|
|
219
|
+
mkdirSync(join(repoDir, "refs"), { recursive: true });
|
|
220
|
+
|
|
221
|
+
const status: DownloadStatus = {
|
|
222
|
+
repoId, state: "active", currentFile: null,
|
|
223
|
+
receivedBytes: 0, totalBytes: listing.files.reduce((a, f) => a + f.size, 0),
|
|
224
|
+
filesDone: 0, filesTotal: listing.files.length,
|
|
225
|
+
bytesPerSec: 0, startedAt: Date.now(), finishedAt: null,
|
|
226
|
+
};
|
|
227
|
+
downloadLog.push(status);
|
|
228
|
+
let doneBytes = 0;
|
|
229
|
+
// Rolling ~5 s window of (time, receivedBytes) samples for the rate.
|
|
230
|
+
const samples: Array<[number, number]> = [];
|
|
231
|
+
const sampleRate = (bytes: number) => {
|
|
232
|
+
const now = performance.now();
|
|
233
|
+
samples.push([now, bytes]);
|
|
234
|
+
while (samples.length > 2 && now - samples[0]![0] > 5000) samples.shift();
|
|
235
|
+
const [t0, b0] = samples[0]!;
|
|
236
|
+
status.bytesPerSec = now > t0 ? ((bytes - b0) / (now - t0)) * 1000 : 0;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
for (const f of listing.files) {
|
|
241
|
+
const blobId = f.lfs?.oid ?? f.blobId;
|
|
242
|
+
if (!blobId) throw new Error(`no blob id for ${f.rfilename} (API response missing ?blobs=true data)`);
|
|
243
|
+
const blobPath = join(blobsDir, blobId);
|
|
244
|
+
|
|
245
|
+
if (!existsSync(blobPath) || statSync(blobPath).size !== f.size) {
|
|
246
|
+
const url = `${endpoint}/${repoId}/resolve/${listing.sha}/${f.rfilename}`;
|
|
247
|
+
status.currentFile = f.rfilename;
|
|
248
|
+
await downloadOne(url, token, blobPath, {
|
|
249
|
+
size: f.size, name: f.rfilename,
|
|
250
|
+
...(f.lfs ? { sha256: f.lfs.oid } : { sha1: f.blobId }),
|
|
251
|
+
}, (file, received, total) => {
|
|
252
|
+
status.receivedBytes = doneBytes + received;
|
|
253
|
+
sampleRate(status.receivedBytes);
|
|
254
|
+
opts.onProgress?.(file, received, total);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
doneBytes += f.size;
|
|
258
|
+
status.receivedBytes = doneBytes;
|
|
259
|
+
status.filesDone++;
|
|
260
|
+
status.currentFile = null;
|
|
261
|
+
|
|
262
|
+
const linkPath = join(snapDir, f.rfilename);
|
|
263
|
+
mkdirSync(dirname(linkPath), { recursive: true });
|
|
264
|
+
if (!existsSync(linkPath)) {
|
|
265
|
+
// relative target, depth-aware for nested rfilenames
|
|
266
|
+
const depth = f.rfilename.split("/").length - 1;
|
|
267
|
+
symlinkSync(join("../".repeat(depth + 2), "blobs", blobId), linkPath);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
status.state = "done";
|
|
271
|
+
status.bytesPerSec = 0;
|
|
272
|
+
status.finishedAt = Date.now();
|
|
273
|
+
} catch (e) {
|
|
274
|
+
status.state = "error";
|
|
275
|
+
status.bytesPerSec = 0;
|
|
276
|
+
status.error = e instanceof Error ? e.message : String(e);
|
|
277
|
+
status.finishedAt = Date.now();
|
|
278
|
+
throw e;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
await Bun.write(join(repoDir, "refs", revision), listing.sha);
|
|
282
|
+
return snapDir;
|
|
283
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Quality evals — the acceptance gate for non-bit-exact perf work
|
|
2
|
+
|
|
3
|
+
Fusing kernels (GELU, norm+add, online-softmax attention) reorders bf16
|
|
4
|
+
float ops, so the model is **no longer bit-exact to the mlx-lm oracle**.
|
|
5
|
+
Logit parity stops being a usable gate. This harness is the replacement —
|
|
6
|
+
a faithful port of **optiq's eval framework**
|
|
7
|
+
(`optiq/eval/` in the oracle venv): KL divergence as the continuous
|
|
8
|
+
acceptance gate, plus the six-task capability score as the ship gate.
|
|
9
|
+
|
|
10
|
+
## The gate, in two tiers (optiq methodology)
|
|
11
|
+
|
|
12
|
+
1. **Smoketest (~5 min):** KL divergence (64 prompts × 256 tok, mean/median/p95)
|
|
13
|
+
+ GSM8K-50. Fast triage — "did the distribution drift?"
|
|
14
|
+
2. **Capability suite (~2 h):** MMLU · GSM8K · IFEval · BFCL · HumanEval ·
|
|
15
|
+
**HashHop@12k**. `Capability_Score = unweighted mean` of the six
|
|
16
|
+
(`score.ts`). HashHop@12k is the long-context guard — where online-softmax
|
|
17
|
+
error (opportunity A) is worst.
|
|
18
|
+
|
|
19
|
+
KL math (`kl.ts`, port of `kl.py`): `KL(ref‖cand)` per token =
|
|
20
|
+
`Σ softmax(ref)·(logsoftmax(ref) − logsoftmax(cand))`. **Teacher-forced** —
|
|
21
|
+
same fixed prompt to both arms, compare per-position distributions (the
|
|
22
|
+
repo rule: free-running greedy "measures chaos").
|
|
23
|
+
|
|
24
|
+
## KL reference modes
|
|
25
|
+
|
|
26
|
+
- **self-flag (default — the kernel-drift gate):** one weight load; forward
|
|
27
|
+
each prompt twice with a perf lever set to its ref vs cand value. Directly
|
|
28
|
+
measures the drift a lever introduces. Fits in 24 GB (no second model).
|
|
29
|
+
`KL(compat ‖ fused)` is literally "measure the output difference."
|
|
30
|
+
- **two-model (optiq-style absolute quality):** a separate reference model
|
|
31
|
+
(bf16 if it fits, else uniform-4bit), both resident.
|
|
32
|
+
|
|
33
|
+
## Run (on a cleared machine — loads models, not from an agent session)
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
# drift gate on an existing non-bit-exact lever (validates the harness today):
|
|
37
|
+
bun scripts/eval.ts kl --candidate e4b
|
|
38
|
+
|
|
39
|
+
# once a fused kernel is wired for e4b, its drift vs compat:
|
|
40
|
+
bun scripts/eval.ts kl --candidate e4b --self MLX_BUN_PERF_KERNEL --ref-value 0 --cand-value 1
|
|
41
|
+
|
|
42
|
+
# optiq-style absolute drift vs a reference model:
|
|
43
|
+
bun scripts/eval.ts kl --candidate e4b --reference <bf16-or-4bit-id>
|
|
44
|
+
|
|
45
|
+
# higher-fidelity prompts (optiq's calibration mix):
|
|
46
|
+
bun scripts/eval.ts kl --candidate e4b --prompts-file \
|
|
47
|
+
/Users/joshrossi/Code/mlx-lm/.venv/.../optiq/calibration/data/optiq.jsonl
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Rows land in `~/.cache/mlx-bun/evals.sqlite` → `quality_runs` (separate from
|
|
51
|
+
the throughput `runs` table). Each row records the active perf levers
|
|
52
|
+
(`config_json`) so it's self-describing: which head-to-head arm produced it.
|
|
53
|
+
|
|
54
|
+
## Status / milestones
|
|
55
|
+
|
|
56
|
+
- **M0 (done):** KL drift gate + capability score + `quality_runs` DB + CLI.
|
|
57
|
+
- **M0b (done):** *serving-path* KL (`evaluateKlServingDecode` in `kl.ts`,
|
|
58
|
+
`bun scripts/eval.ts kl --decode`). Validated: identical arms → KL 0;
|
|
59
|
+
`MLX_BUN_FUSED_DECODE` tiled-decode → KL 0.27 (real drift on the quantized
|
|
60
|
+
path). Motivation below — the compat KL couldn't see it:
|
|
61
|
+
The compat `forward()` path with `makeCache()` uses **plain bf16 caches**. That is wrong
|
|
62
|
+
twice over: (1) e4b's KV is **per-layer mixed 4-bit/8-bit** (group 64, from
|
|
63
|
+
`kv_config.json` → `config.kvQuant`; 8-bit layers 3,4,9,10,11,14, 4-bit
|
|
64
|
+
elsewhere), NOT bf16; (2) bf16 caches fail the generated class's `#matches`
|
|
65
|
+
guard, so the model silently falls back to the monolith — a path that
|
|
66
|
+
doesn't exist in production. M0b must reproduce the serve setup: build
|
|
67
|
+
caches, then `cache[i] = c.toQuantized(e.groupSize, e.bits)` per
|
|
68
|
+
`config.kvQuant` (the logic already at `generate.ts:68-86`), so the
|
|
69
|
+
teacher-forced prefill runs the real mixed-4/8-bit quantized path and the
|
|
70
|
+
perf levers (NO_FUSED_SDPA, perf-kernel) actually bite.
|
|
71
|
+
- **M1 (done):** all six capability tasks ported + runtime-validated, plus
|
|
72
|
+
`capability` aggregation and the `smoketest` (KL + GSM8K-50) gate. Datasets
|
|
73
|
+
exported by `scripts/eval/export-datasets.py` → `~/.cache/mlx-bun/eval-data/`.
|
|
74
|
+
Tasks run through the real quantized-KV path (`runner.ts`). HumanEval executes
|
|
75
|
+
in a sandbox-exec + venv-python subprocess; HashHop reconstructs Magic AI's
|
|
76
|
+
generator (`MultiHopEval` isn't on PyPI). **Open quality checks** (need a real
|
|
77
|
+
`--n 100+` run to confirm the port, not the harness): MMLU read ~chance at n=5
|
|
78
|
+
(raw-completion port may underperform for instruct models — consider chat
|
|
79
|
+
template or logit-scoring); BFCL produced no-calls (gemma-4 `<|tool_call>`
|
|
80
|
+
format vs the textual-fallback prompt).
|
|
81
|
+
- **M2 (done):** compile-vs-fused paired A/B (`scripts/bench-compile-vs-fused.ts`).
|
|
82
|
+
Plumbing validated; the `fused` arm is a placeholder (compile-off) until the
|
|
83
|
+
kernels land — edit `ARMS.fused` to add each kernel's lever.
|
|
84
|
+
- **M3–M4 (next):** the fused kernels themselves (E → B/C → D → A), each gated
|
|
85
|
+
+ measured (KL/capability for drift, the A/B for speed) before it ships.
|
|
86
|
+
- **M5:** head-to-head @600/@8k + KL/capability → decide.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Default KL calibration prompts. A small, domain-diverse set (code,
|
|
2
|
+
// prose, math, QA, instructions) so the drift signal isn't specialized to
|
|
3
|
+
// one register. Tiled to `n` prompts by the KL eval, matching optiq's
|
|
4
|
+
// repeat-to-fill behavior.
|
|
5
|
+
//
|
|
6
|
+
// For higher fidelity against the distribution the quantizer was tuned
|
|
7
|
+
// for, pass --prompts-file pointing at optiq's bundled calibration mix
|
|
8
|
+
// (optiq/calibration/data/optiq.jsonl in the oracle venv) — jsonl with
|
|
9
|
+
// {"text": ...} or {"messages": [...]} per line. We do NOT vendor that
|
|
10
|
+
// file (provenance), so this bundled set is the self-contained default.
|
|
11
|
+
|
|
12
|
+
export const DEFAULT_KL_PROMPTS: string[] = [
|
|
13
|
+
"The history of the printing press begins in the fifteenth century, when Johannes Gutenberg combined movable type with the screw press. Explain how this changed the spread of information across Europe.",
|
|
14
|
+
"Write a Python function that takes a list of integers and returns the longest strictly increasing contiguous subsequence. Include a docstring and handle the empty-list case.",
|
|
15
|
+
"A train leaves Chicago travelling east at 60 miles per hour. Two hours later a second train leaves the same station travelling east at 80 miles per hour. How long after the second train departs does it catch the first?",
|
|
16
|
+
"Summarize the central argument of the following claim and then give one objection: economic growth and ecological sustainability are fundamentally incompatible over the long run.",
|
|
17
|
+
"Describe, step by step, how a TCP connection is established and torn down, including the purpose of each flag in the three-way handshake and the role of TIME_WAIT.",
|
|
18
|
+
"In the kitchen, photosynthesis of ideas rarely occurs, yet a careful cook learns to balance acid, fat, salt, and heat. Continue this passage in the same reflective tone for several sentences.",
|
|
19
|
+
"Given a 4x4 matrix of integers, write pseudocode to rotate it 90 degrees clockwise in place, and explain why the in-place approach avoids allocating a second matrix.",
|
|
20
|
+
"Explain the difference between supervised, unsupervised, and reinforcement learning to a curious high-school student, using a single running example for all three.",
|
|
21
|
+
"Translate the following sentence into formal written English and then into a casual spoken register: 'the meeting got moved cause half the team was out sick.'",
|
|
22
|
+
"What are the trade-offs between depth-first and breadth-first search for finding the shortest path in an unweighted graph? When does each fail or waste work?",
|
|
23
|
+
"A baker has 3 cups of flour and each loaf needs 3/4 of a cup. After making as many loaves as possible, how much flour is left, and how many loaves were made? Show the arithmetic.",
|
|
24
|
+
"Outline the plot of a short story in which a lighthouse keeper discovers that the light has been guiding something other than ships. Keep it to four sentences.",
|
|
25
|
+
"Explain what a hash collision is, why it matters for hash tables, and how separate chaining and open addressing each respond to one. Be precise about average-case behavior.",
|
|
26
|
+
"Compare the causes commonly cited for the fall of the Western Roman Empire, and explain why historians treat single-cause explanations with suspicion.",
|
|
27
|
+
"Refactor the idea of 'technical debt' for a non-engineering executive: define it, give a concrete example, and describe one healthy and one unhealthy way teams accumulate it.",
|
|
28
|
+
"Derive the formula for the sum of the first n positive integers two different ways: once by pairing terms, and once by induction. State the result clearly.",
|
|
29
|
+
];
|