mlx-bun 0.0.11 → 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 +43 -13
- package/THIRD_PARTY_LICENSES.md +9 -0
- package/docs/design/colibri-glm52-port.md +1479 -0
- package/docs/design/pre-colibri-stabilization.md +329 -0
- package/docs/investigations/colibri-oracle-pin.md +1284 -0
- package/docs/investigations/indexshare-performance-spike.md +357 -0
- package/docs/planning/release-notes-v0.0.12.md +80 -0
- package/docs/reference/cli.md +28 -2
- package/docs/reference/distribution.md +25 -1
- package/docs/reference/embedding.md +5 -2
- package/docs/reference/features-matrix.md +4 -2
- package/docs/reference/library-api.md +41 -13
- package/docs/reference/models.md +49 -2
- package/docs/reference/server-api.md +90 -9
- package/docs/reference/server-config.md +21 -14
- package/docs/reference/training.md +10 -1
- package/package.json +1 -1
- package/src/anthropic.ts +54 -44
- package/src/chat-template.ts +155 -10
- package/src/cli.ts +127 -8
- package/src/config.ts +71 -26
- package/src/diffusion/diffusion-generate.ts +14 -8
- package/src/download.ts +103 -12
- package/src/eval/ifeval.ts +63 -120
- package/src/eval/runner.ts +14 -5
- package/src/eval/tasks/ifeval.ts +167 -31
- package/src/expert-io.ts +391 -0
- package/src/expert-residency.ts +1134 -0
- package/src/expert-usage.ts +415 -0
- package/src/generate.ts +114 -17
- package/src/hf-push.ts +1 -1
- package/src/index.ts +14 -2
- package/src/jobs/runner.ts +37 -18
- package/src/kv-store.ts +147 -8
- package/src/lora.ts +77 -14
- package/src/media-fetch.ts +198 -56
- package/src/mlx/array.ts +43 -0
- package/src/mlx/autograd.ts +51 -14
- package/src/mlx/checkpoint.ts +46 -13
- package/src/mlx/custom-vjp.ts +83 -35
- package/src/model/factory.ts +96 -5
- package/src/model/gemma4-base.ts +32 -0
- 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/support.ts +8 -1
- package/src/native/expert_io.c +523 -0
- package/src/native-pack.ts +6 -4
- package/src/responses.ts +62 -49
- package/src/serve/batch-scheduler.ts +95 -16
- package/src/serve/generation-gateway.ts +41 -16
- package/src/server.ts +362 -81
- package/src/spec/glm52-mtp-source.ts +392 -0
- package/src/spec/serve-loop.ts +99 -18
- package/src/spec/source.ts +31 -6
- package/src/ssd-cache.ts +6 -2
- package/src/tool-call.ts +30 -2
- package/src/train/job.ts +0 -10
- package/src/train/lora-params.ts +17 -6
- package/src/train/trainer.ts +59 -26
- package/src/web/app.html +13 -2
- package/src/web/app.js +30 -3
- package/src/web/src/status.ts +51 -3
- package/src/web-tools.ts +54 -2
- package/src/weights.ts +82 -19
package/src/download.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// lore"): Xet stalls (we speak plain HTTPS resolve/CDN), no resume on
|
|
5
5
|
// flaky links (we Range-resume partial blobs), and silent corruption
|
|
6
6
|
// (every blob is verified — sha256 for LFS files against the API's
|
|
7
|
-
//
|
|
7
|
+
// normalized LFS digest, git-blob sha1 for small files against blobId).
|
|
8
8
|
//
|
|
9
9
|
// Writes the exact huggingface_hub cache layout so the registry and
|
|
10
10
|
// loaders find the result with zero changes:
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import {
|
|
19
19
|
mkdirSync, existsSync, statSync, createWriteStream, renameSync, rmSync,
|
|
20
20
|
symlinkSync, readFileSync, openSync, closeSync, readSync, writeSync,
|
|
21
|
+
statfsSync,
|
|
21
22
|
} from "node:fs";
|
|
22
23
|
import { dirname, join } from "node:path";
|
|
23
24
|
import { DEFAULT_HUB } from "./registry";
|
|
@@ -29,8 +30,8 @@ export interface RepoFile {
|
|
|
29
30
|
size: number;
|
|
30
31
|
/** git blob sha1 (non-LFS identity, and the blob filename for non-LFS). */
|
|
31
32
|
blobId: string;
|
|
32
|
-
/** LFS sha256 + size; present for large files.
|
|
33
|
-
lfs?: {
|
|
33
|
+
/** LFS sha256 + size; present for large files. sha256 is the blob filename. */
|
|
34
|
+
lfs?: { sha256: string; size: number };
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export interface RepoListing {
|
|
@@ -47,6 +48,56 @@ export interface DownloadOptions {
|
|
|
47
48
|
onProgress?: (file: string, received: number, total: number) => void;
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
export interface DownloadSpacePlan {
|
|
52
|
+
/** Bytes still absent after crediting verified blobs and resumable prefixes. */
|
|
53
|
+
remainingBytes: number;
|
|
54
|
+
/** Files that still need at least one byte. */
|
|
55
|
+
remainingFiles: number;
|
|
56
|
+
/** Files already complete in the shared HF blob store. */
|
|
57
|
+
completeFiles: number;
|
|
58
|
+
/** Existing `.incomplete` bytes that will be rehashed and resumed. */
|
|
59
|
+
resumableBytes: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** One GiB for filesystem metadata, the final refs write, and normal host
|
|
63
|
+
* operation. The artifact bytes themselves are exact; this is deliberately a
|
|
64
|
+
* small fixed guard rather than a percentage that would add tens of GiB to a
|
|
65
|
+
* Colibri download. */
|
|
66
|
+
export const DOWNLOAD_DISK_RESERVE_BYTES = 2 ** 30;
|
|
67
|
+
|
|
68
|
+
/** Compute the exact remaining payload without opening or hashing any blob.
|
|
69
|
+
* A valid-size final blob is already complete; an in-range `.incomplete`
|
|
70
|
+
* prefix is credited because downloadOneLocked resumes it in place. */
|
|
71
|
+
export function planDownloadSpace(
|
|
72
|
+
files: readonly RepoFile[],
|
|
73
|
+
blobsDir: string,
|
|
74
|
+
): DownloadSpacePlan {
|
|
75
|
+
let remainingBytes = 0;
|
|
76
|
+
let remainingFiles = 0;
|
|
77
|
+
let completeFiles = 0;
|
|
78
|
+
let resumableBytes = 0;
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
const blobId = file.lfs?.sha256 ?? file.blobId;
|
|
81
|
+
if (!blobId) throw new Error(`no blob id for ${file.rfilename}`);
|
|
82
|
+
const blobPath = join(blobsDir, blobId);
|
|
83
|
+
if (existsSync(blobPath) && statSync(blobPath).size === file.size) {
|
|
84
|
+
completeFiles++;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const partPath = `${blobPath}.incomplete`;
|
|
88
|
+
const partBytes = existsSync(partPath) ? statSync(partPath).size : 0;
|
|
89
|
+
const credited = partBytes >= 0 && partBytes <= file.size ? partBytes : 0;
|
|
90
|
+
resumableBytes += credited;
|
|
91
|
+
remainingBytes += file.size - credited;
|
|
92
|
+
remainingFiles++;
|
|
93
|
+
}
|
|
94
|
+
return { remainingBytes, remainingFiles, completeFiles, resumableBytes };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function formatGib(bytes: number): string {
|
|
98
|
+
return `${(bytes / 2 ** 30).toFixed(2)} GiB`;
|
|
99
|
+
}
|
|
100
|
+
|
|
50
101
|
/** HF auth: explicit > env > the file `hf auth login` writes. */
|
|
51
102
|
export function hfToken(): string | null {
|
|
52
103
|
if (process.env.HF_TOKEN) return process.env.HF_TOKEN;
|
|
@@ -161,16 +212,34 @@ export async function listRepoFiles(
|
|
|
161
212
|
);
|
|
162
213
|
const body = (await res.json()) as {
|
|
163
214
|
sha: string;
|
|
164
|
-
siblings: {
|
|
215
|
+
siblings: {
|
|
216
|
+
rfilename: string;
|
|
217
|
+
size?: number;
|
|
218
|
+
blobId?: string;
|
|
219
|
+
// The current HF API calls this field `sha256`. Accept the older `oid`
|
|
220
|
+
// spelling too, but normalize it before the download path sees it.
|
|
221
|
+
lfs?: { sha256?: string; oid?: string; size: number };
|
|
222
|
+
}[];
|
|
165
223
|
};
|
|
166
224
|
return {
|
|
167
225
|
sha: body.sha,
|
|
168
|
-
files: body.siblings.map((s) =>
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
226
|
+
files: body.siblings.map((s) => {
|
|
227
|
+
let lfs: RepoFile["lfs"];
|
|
228
|
+
if (s.lfs) {
|
|
229
|
+
const sha256 = s.lfs.sha256 ?? s.lfs.oid;
|
|
230
|
+
if (!sha256 || !/^[0-9a-f]{64}$/i.test(sha256))
|
|
231
|
+
throw new Error(`HF API returned an invalid LFS sha256 for ${s.rfilename}`);
|
|
232
|
+
lfs = { sha256: sha256.toLowerCase(), size: s.lfs.size };
|
|
233
|
+
}
|
|
234
|
+
if (!lfs && (!s.blobId || !/^[0-9a-f]{40}$/.test(s.blobId)))
|
|
235
|
+
throw new Error(`HF API returned an invalid git blob SHA-1 for ${s.rfilename}`);
|
|
236
|
+
return {
|
|
237
|
+
rfilename: s.rfilename,
|
|
238
|
+
size: lfs?.size ?? s.size ?? 0,
|
|
239
|
+
blobId: s.blobId ?? "",
|
|
240
|
+
lfs,
|
|
241
|
+
};
|
|
242
|
+
}),
|
|
174
243
|
};
|
|
175
244
|
}
|
|
176
245
|
|
|
@@ -349,6 +418,28 @@ export async function downloadModel(
|
|
|
349
418
|
mkdirSync(blobsDir, { recursive: true });
|
|
350
419
|
mkdirSync(join(repoDir, "refs"), { recursive: true });
|
|
351
420
|
|
|
421
|
+
// Preflight before the first network payload. This matters for direct
|
|
422
|
+
// Colibri artifacts (hundreds of GiB): discovering a full volume after the
|
|
423
|
+
// 140th shard is not a usable failure mode. Existing verified blobs and
|
|
424
|
+
// partial prefixes are credited exactly, so rerunning after interruption is
|
|
425
|
+
// both resumable and progressively cheaper.
|
|
426
|
+
const space = planDownloadSpace(listing.files, blobsDir);
|
|
427
|
+
if (space.remainingBytes > 0) {
|
|
428
|
+
const fs = statfsSync(blobsDir);
|
|
429
|
+
const availableBytes = fs.bavail * fs.bsize;
|
|
430
|
+
const requiredBytes = space.remainingBytes + DOWNLOAD_DISK_RESERVE_BYTES;
|
|
431
|
+
if (requiredBytes > availableBytes) {
|
|
432
|
+
throw new Error(
|
|
433
|
+
`insufficient disk space for ${repoId}: ${formatGib(space.remainingBytes)} ` +
|
|
434
|
+
`remains across ${space.remainingFiles} files, but only ` +
|
|
435
|
+
`${formatGib(availableBytes)} is available in ${hub} ` +
|
|
436
|
+
`(${formatGib(DOWNLOAD_DISK_RESERVE_BYTES)} safety reserve). ` +
|
|
437
|
+
`Free space or choose another cache directory, then rerun; ` +
|
|
438
|
+
`${formatGib(space.resumableBytes)} of partial data will resume.`,
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
352
443
|
const status: DownloadStatus = {
|
|
353
444
|
repoId, state: "active", currentFile: null,
|
|
354
445
|
receivedBytes: 0, totalBytes: listing.files.reduce((a, f) => a + f.size, 0),
|
|
@@ -369,7 +460,7 @@ export async function downloadModel(
|
|
|
369
460
|
|
|
370
461
|
try {
|
|
371
462
|
for (const f of listing.files) {
|
|
372
|
-
const blobId = f.lfs?.
|
|
463
|
+
const blobId = f.lfs?.sha256 ?? f.blobId;
|
|
373
464
|
if (!blobId) throw new Error(`no blob id for ${f.rfilename} (API response missing ?blobs=true data)`);
|
|
374
465
|
const blobPath = join(blobsDir, blobId);
|
|
375
466
|
|
|
@@ -378,7 +469,7 @@ export async function downloadModel(
|
|
|
378
469
|
status.currentFile = f.rfilename;
|
|
379
470
|
await downloadOne(url, token, blobPath, {
|
|
380
471
|
size: f.size, name: f.rfilename,
|
|
381
|
-
...(f.lfs ? { sha256: f.lfs.
|
|
472
|
+
...(f.lfs ? { sha256: f.lfs.sha256 } : { sha1: f.blobId }),
|
|
382
473
|
}, (file, received, total) => {
|
|
383
474
|
status.receivedBytes = doneBytes + received;
|
|
384
475
|
sampleRate(status.receivedBytes);
|
package/src/eval/ifeval.ts
CHANGED
|
@@ -1,138 +1,81 @@
|
|
|
1
|
-
//
|
|
2
|
-
// al. 2023, "Instruction-Following Evaluation for Large Language Models"). Each
|
|
3
|
-
// instance pairs a prompt with one or more *verifiable* instructions; a response
|
|
4
|
-
// is scored by deterministic checks (no LLM judge), so before/after ORPO
|
|
5
|
-
// comparisons are exact and reproducible.
|
|
1
|
+
// Compatibility facade for the canonical IFEval scorer.
|
|
6
2
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// instruction-level accuracy — both derived from `scoreInstance`.
|
|
3
|
+
// The complete verifier registry and strict/loose aggregation contract live in
|
|
4
|
+
// `tasks/ifeval.ts`, which is also used by `scripts/eval.ts`. Keep this module
|
|
5
|
+
// as the stable import surface used by `scripts/run-ifeval.ts` and older
|
|
6
|
+
// callers; it must not grow a second verifier implementation.
|
|
12
7
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
8
|
+
import {
|
|
9
|
+
scoreIfevalInstance,
|
|
10
|
+
scoreIfevalPairs,
|
|
11
|
+
type IfevalInstance,
|
|
12
|
+
type IfevalResult,
|
|
13
|
+
} from "./tasks/ifeval";
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
SUPPORTED_INSTRUCTIONS,
|
|
17
|
+
looseClean,
|
|
18
|
+
scoreIfevalInstance,
|
|
19
|
+
scoreIfevalPairs,
|
|
20
|
+
stripThinking,
|
|
21
|
+
verifyResponse,
|
|
22
|
+
type IfevalCoverage,
|
|
23
|
+
type IfevalInstance,
|
|
24
|
+
type IfevalInstanceScore,
|
|
25
|
+
type IfevalPair,
|
|
26
|
+
type IfevalResult,
|
|
27
|
+
type IfevalVerification,
|
|
28
|
+
} from "./tasks/ifeval";
|
|
29
|
+
|
|
30
|
+
/** Historical spelling retained for callers of this module. */
|
|
31
|
+
export type IFEvalInstance = IfevalInstance;
|
|
21
32
|
|
|
22
33
|
export interface InstanceResult {
|
|
23
|
-
/**
|
|
34
|
+
/** Unknown instructions are true here per the canonical prompt contract. */
|
|
24
35
|
perInstruction: boolean[];
|
|
25
|
-
/** Strict: all instructions passed. */
|
|
26
36
|
followedAll: boolean;
|
|
37
|
+
/** Consult this field rather than interpreting unknown IDs as verified. */
|
|
38
|
+
unhandled: string[];
|
|
27
39
|
}
|
|
28
40
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
case "exactly": return n === target;
|
|
44
|
-
default: return n >= target; // IFEval default for "around"/unspecified is lenient ≥
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/** The verifiable-instruction registry. Keyed by IFEval instruction_id. */
|
|
49
|
-
export const INSTRUCTIONS: Record<string, Checker> = {
|
|
50
|
-
// ---- keywords ----
|
|
51
|
-
"keywords:existence": (r, kw) =>
|
|
52
|
-
(kw.keywords as string[]).every((k) => norm(r).includes(norm(k))),
|
|
53
|
-
"keywords:frequency": (r, kw) => {
|
|
54
|
-
const k = norm(kw.keyword as string);
|
|
55
|
-
const count = norm(r).split(k).length - 1;
|
|
56
|
-
return cmp(count, kw.relation as string, kw.frequency as number);
|
|
57
|
-
},
|
|
58
|
-
"keywords:forbidden_words": (r, kw) =>
|
|
59
|
-
!(kw.forbidden_words as string[]).some((k) => norm(r).includes(norm(k))),
|
|
60
|
-
|
|
61
|
-
// ---- length constraints ----
|
|
62
|
-
"length_constraints:number_words": (r, kw) =>
|
|
63
|
-
cmp(words(r).length, kw.relation as string, kw.num_words as number),
|
|
64
|
-
"length_constraints:number_sentences": (r, kw) =>
|
|
65
|
-
cmp(sentences(r).length, kw.relation as string, kw.num_sentences as number),
|
|
66
|
-
"length_constraints:number_paragraphs": (r, kw) =>
|
|
67
|
-
cmp(paragraphs(r).length, kw.relation as string, kw.num_paragraphs as number),
|
|
68
|
-
|
|
69
|
-
// ---- detectable format ----
|
|
70
|
-
"detectable_format:number_bullet_lists": (r, kw) => {
|
|
71
|
-
const bullets = r.split("\n").filter((l) => /^\s*[*\-]\s+/.test(l)).length;
|
|
72
|
-
return cmp(bullets, kw.relation as string, kw.num_bullets as number);
|
|
73
|
-
},
|
|
74
|
-
"detectable_format:number_highlighted_sections": (r, kw) => {
|
|
75
|
-
const hi = (r.match(/\*[^*\n]+\*/g) ?? []).length;
|
|
76
|
-
return cmp(hi, kw.relation as string, kw.num_highlights as number);
|
|
77
|
-
},
|
|
78
|
-
"detectable_format:title": (r) => /<<[^>\n]+>>/.test(r),
|
|
79
|
-
"detectable_format:json_format": (r) => {
|
|
80
|
-
const t = r.trim().replace(/^```(?:json)?\s*|\s*```$/g, "").trim();
|
|
81
|
-
try { JSON.parse(t); return true; } catch { return false; }
|
|
82
|
-
},
|
|
83
|
-
|
|
84
|
-
// ---- case ----
|
|
85
|
-
"change_case:english_lowercase": (r) => r === r.toLowerCase(),
|
|
86
|
-
"change_case:english_capital": (r) => r === r.toUpperCase(),
|
|
87
|
-
|
|
88
|
-
// ---- start / end ----
|
|
89
|
-
"startend:end_checker": (r, kw) =>
|
|
90
|
-
norm(r.trimEnd()).endsWith(norm((kw.end_phrase as string).trim())),
|
|
91
|
-
"startend:quotation": (r) => {
|
|
92
|
-
const t = r.trim();
|
|
93
|
-
return t.startsWith('"') && t.endsWith('"') && t.length >= 2;
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
// ---- punctuation ----
|
|
97
|
-
"punctuation:no_comma": (r) => !r.includes(","),
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
/** Score one IFEval instance (strict). Unknown instruction ids count as failed
|
|
101
|
-
* (so coverage gaps never inflate the score) — filter the dataset to the
|
|
102
|
-
* supported ids, or extend INSTRUCTIONS, to avoid that. */
|
|
103
|
-
export function scoreInstance(instance: IFEvalInstance, response: string): InstanceResult {
|
|
104
|
-
const perInstruction = instance.instruction_id_list.map((id, i) => {
|
|
105
|
-
const check = INSTRUCTIONS[id];
|
|
106
|
-
if (!check) return false;
|
|
107
|
-
try { return check(response, instance.kwargs[i] ?? {}, instance); } catch { return false; }
|
|
108
|
-
});
|
|
109
|
-
return { perInstruction, followedAll: perInstruction.every(Boolean) };
|
|
41
|
+
/**
|
|
42
|
+
* Historical strict-only helper, now delegated to the canonical scorer.
|
|
43
|
+
* New code should prefer `scoreIfevalInstance` for both strict and loose modes.
|
|
44
|
+
*/
|
|
45
|
+
export function scoreInstance(
|
|
46
|
+
instance: IFEvalInstance,
|
|
47
|
+
response: string,
|
|
48
|
+
): InstanceResult {
|
|
49
|
+
const { strict } = scoreIfevalInstance(instance, response);
|
|
50
|
+
return {
|
|
51
|
+
perInstruction: strict.instructionPasses.map((result) => result ?? true),
|
|
52
|
+
followedAll: strict.pass,
|
|
53
|
+
unhandled: strict.unhandled,
|
|
54
|
+
};
|
|
110
55
|
}
|
|
111
56
|
|
|
112
|
-
export interface IFEvalReport {
|
|
57
|
+
export interface IFEvalReport extends IfevalResult {
|
|
58
|
+
/** Historical alias for nTotal. */
|
|
113
59
|
n: number;
|
|
114
|
-
/**
|
|
60
|
+
/** Historical alias for strictAcc (prompt-level strict). */
|
|
115
61
|
promptAccuracy: number;
|
|
116
|
-
/**
|
|
62
|
+
/** Historical alias for strictInstructionAcc. */
|
|
117
63
|
instructionAccuracy: number;
|
|
118
64
|
}
|
|
119
65
|
|
|
120
|
-
/**
|
|
121
|
-
*
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
66
|
+
/**
|
|
67
|
+
* Historical aggregation helper with unambiguous canonical fields plus aliases.
|
|
68
|
+
* Unknown instructions do not fail prompt accuracy, are excluded from
|
|
69
|
+
* instruction accuracy, and remain visible in `coverage`.
|
|
70
|
+
*/
|
|
71
|
+
export function aggregate(
|
|
72
|
+
pairs: Array<{ instance: IFEvalInstance; response: string }>,
|
|
73
|
+
): IFEvalReport {
|
|
74
|
+
const result = scoreIfevalPairs(pairs);
|
|
130
75
|
return {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
76
|
+
...result,
|
|
77
|
+
n: result.nTotal,
|
|
78
|
+
promptAccuracy: result.strictAcc,
|
|
79
|
+
instructionAccuracy: result.strictInstructionAcc,
|
|
134
80
|
};
|
|
135
81
|
}
|
|
136
|
-
|
|
137
|
-
/** Instruction ids this implementation supports (filter datasets to these). */
|
|
138
|
-
export const SUPPORTED_INSTRUCTIONS = new Set(Object.keys(INSTRUCTIONS));
|
package/src/eval/runner.ts
CHANGED
|
@@ -9,8 +9,12 @@ import { generate, type GenerateOptions } from "../generate";
|
|
|
9
9
|
import * as ops from "../mlx/ops";
|
|
10
10
|
import { ChatTemplate } from "../chat-template";
|
|
11
11
|
import { loadModelConfig, type ModelConfig } from "../config";
|
|
12
|
-
import {
|
|
13
|
-
|
|
12
|
+
import {
|
|
13
|
+
openModel,
|
|
14
|
+
type Glm52RuntimeOpenOptions,
|
|
15
|
+
type RuntimeModel,
|
|
16
|
+
} from "../model/factory";
|
|
17
|
+
import { isGlm52Config } from "../model/support";
|
|
14
18
|
import { DiffusionGemmaModel } from "../model/diffusion-gemma";
|
|
15
19
|
import { loadTokenizer, type LoadedTokenizer } from "../tokenizer";
|
|
16
20
|
import { resolveModelDir } from "./kl";
|
|
@@ -65,11 +69,16 @@ export interface TaskModel {
|
|
|
65
69
|
activeAdapters?: string[];
|
|
66
70
|
}
|
|
67
71
|
|
|
68
|
-
export async function loadTaskModel(
|
|
72
|
+
export async function loadTaskModel(
|
|
73
|
+
query: string,
|
|
74
|
+
adapterDir?: string,
|
|
75
|
+
runtimeOptions: Glm52RuntimeOpenOptions = {},
|
|
76
|
+
): Promise<TaskModel> {
|
|
69
77
|
const dir = resolveModelDir(query);
|
|
70
78
|
const config = await loadModelConfig(dir);
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
if (adapterDir && isGlm52Config(config))
|
|
80
|
+
throw new Error("GLM-5.2 does not support LoRA adapters");
|
|
81
|
+
const model = await openModel(dir, runtimeOptions);
|
|
73
82
|
const activeAdapters: string[] = [];
|
|
74
83
|
if (adapterDir) {
|
|
75
84
|
// Mount a trained LoRA adapter so the eval measures base+adapter (e.g. the
|