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
package/src/config.ts
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
// Parses HF config.json (Gemma 4 unified layout: text_config nested),
|
|
3
3
|
// the OptiQ per-layer quantization map, and kv_config.json when present.
|
|
4
4
|
|
|
5
|
+
import {
|
|
6
|
+
parseGlm52Config,
|
|
7
|
+
type Glm52Config,
|
|
8
|
+
} from "./model/glm52-config";
|
|
9
|
+
|
|
5
10
|
export interface RopeParams {
|
|
6
11
|
ropeTheta: number;
|
|
7
12
|
ropeType: string;
|
|
@@ -38,6 +43,13 @@ export interface TextConfig {
|
|
|
38
43
|
numExperts: number;
|
|
39
44
|
topKExperts: number;
|
|
40
45
|
moeIntermediateSize: number;
|
|
46
|
+
/** Qwen3-MoE sparse-block gating (qwen3_moe): a layer is MoE unless it is
|
|
47
|
+
* in `mlpOnlyLayers` and `(idx+1) % decoderSparseStep == 0` selects it.
|
|
48
|
+
* `normTopkProb` renormalizes the top-k gate weights. Defaults are inert
|
|
49
|
+
* for non-qwen3_moe families. */
|
|
50
|
+
decoderSparseStep: number;
|
|
51
|
+
mlpOnlyLayers: number[];
|
|
52
|
+
normTopkProb: boolean;
|
|
41
53
|
/** Qwen3.5 hybrid (gated-DeltaNet) geometry; 0/false for other families. */
|
|
42
54
|
linearNumValueHeads: number;
|
|
43
55
|
linearNumKeyHeads: number;
|
|
@@ -77,6 +89,37 @@ export interface KvQuantSpec {
|
|
|
77
89
|
groupSize: number;
|
|
78
90
|
}
|
|
79
91
|
|
|
92
|
+
/** TurboQuant scheme (docs/design/turboquant-kv.md): rotation-based KV
|
|
93
|
+
* quantization, a distinct axis from the uniform/per-layer affine kvQuant
|
|
94
|
+
* above (composes with it in principle; v1 ships as a standalone CLI-only
|
|
95
|
+
* runtime lever, same class as uniform kvBits — see
|
|
96
|
+
* src/generate.ts maybeQuantizeKv). Valid kBits: {2,4,5,8}; vBits:
|
|
97
|
+
* {2,3,4,5,8} (docs/design/turboquant-kv.md's supported Lloyd-Max tables). */
|
|
98
|
+
export interface TurboQuantScheme {
|
|
99
|
+
kBits: number;
|
|
100
|
+
vBits: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const TURBOQUANT_VALID_KBITS = [2, 4, 5, 8] as const;
|
|
104
|
+
export const TURBOQUANT_VALID_VBITS = [2, 3, 4, 5, 8] as const;
|
|
105
|
+
|
|
106
|
+
/** Parse `turbo` / `turbo:k<bits>v<bits>` (default k8v3). Returns null for
|
|
107
|
+
* non-turbo strings (caller falls through to the uniform/config parser);
|
|
108
|
+
* throws Error with a user-facing message for a malformed turbo spec. */
|
|
109
|
+
export function parseTurboQuantScheme(raw: string): TurboQuantScheme | null {
|
|
110
|
+
if (raw !== "turbo" && !raw.startsWith("turbo:")) return null;
|
|
111
|
+
if (raw === "turbo") return { kBits: 8, vBits: 3 };
|
|
112
|
+
const m = /^turbo:k(\d+)v(\d+)$/.exec(raw);
|
|
113
|
+
if (!m) throw new Error(`--kv-quant turbo spec must look like "turbo:k<bits>v<bits>" (got "${raw}")`);
|
|
114
|
+
const kBits = Number(m[1]);
|
|
115
|
+
const vBits = Number(m[2]);
|
|
116
|
+
if (!(TURBOQUANT_VALID_KBITS as readonly number[]).includes(kBits))
|
|
117
|
+
throw new Error(`--kv-quant turbo: kBits must be one of ${TURBOQUANT_VALID_KBITS.join(",")} (got ${kBits})`);
|
|
118
|
+
if (!(TURBOQUANT_VALID_VBITS as readonly number[]).includes(vBits))
|
|
119
|
+
throw new Error(`--kv-quant turbo: vBits must be one of ${TURBOQUANT_VALID_VBITS.join(",")} (got ${vBits})`);
|
|
120
|
+
return { kBits, vBits };
|
|
121
|
+
}
|
|
122
|
+
|
|
80
123
|
export interface ModelConfig {
|
|
81
124
|
modelDir: string;
|
|
82
125
|
modelType: string;
|
|
@@ -93,6 +136,11 @@ export interface ModelConfig {
|
|
|
93
136
|
function parseRope(raw: Record<string, any>): Record<string, RopeParams> {
|
|
94
137
|
const out: Record<string, RopeParams> = {};
|
|
95
138
|
for (const [kind, p] of Object.entries(raw ?? {})) {
|
|
139
|
+
// transformers ≥5.10 configs mix flat scalars into the per-attention-type
|
|
140
|
+
// map (`rope_parameters: { full_attention: {...}, rope_theta: null,
|
|
141
|
+
// rope_type: "default", ... }` — the DeepSpec drafter ships this shape);
|
|
142
|
+
// only object entries are attention-type params.
|
|
143
|
+
if (!p || typeof p !== "object") continue;
|
|
96
144
|
out[kind] = {
|
|
97
145
|
ropeTheta: p.rope_theta,
|
|
98
146
|
ropeType: p.rope_type ?? "default",
|
|
@@ -103,7 +151,7 @@ function parseRope(raw: Record<string, any>): Record<string, RopeParams> {
|
|
|
103
151
|
return out;
|
|
104
152
|
}
|
|
105
153
|
|
|
106
|
-
function parseQuantization(raw: Record<string, any> | undefined): QuantizationConfig | null {
|
|
154
|
+
export function parseQuantization(raw: Record<string, any> | undefined): QuantizationConfig | null {
|
|
107
155
|
if (!raw) return null;
|
|
108
156
|
const def: QuantSpec = {
|
|
109
157
|
bits: raw.bits,
|
|
@@ -180,6 +228,18 @@ export async function loadModelConfig(modelDir: string): Promise<ModelConfig> {
|
|
|
180
228
|
const raw = (await Bun.file(`${modelDir}/config.json`).json()) as Record<string, any>;
|
|
181
229
|
const modelType = raw.model_type;
|
|
182
230
|
const isDiffusion = modelType === "diffusion_gemma";
|
|
231
|
+
const isGlm52 = modelType === "glm_moe_dsa";
|
|
232
|
+
// The converted Colibri artifact keeps stop metadata split across the two
|
|
233
|
+
// HF files. Preserve the old config-only behavior for every other family;
|
|
234
|
+
// GLM-5.2 explicitly unions both lists through its dedicated parser.
|
|
235
|
+
let glm52: Glm52Config | null = null;
|
|
236
|
+
if (isGlm52) {
|
|
237
|
+
const generationFile = Bun.file(`${modelDir}/generation_config.json`);
|
|
238
|
+
const generation = await generationFile.exists()
|
|
239
|
+
? await generationFile.json() as Record<string, any>
|
|
240
|
+
: null;
|
|
241
|
+
glm52 = parseGlm52Config(modelDir, raw, generation);
|
|
242
|
+
}
|
|
183
243
|
// Gemma 4 unified nests the LM config; plain text models keep it at top level.
|
|
184
244
|
// DiffusionGemma keeps it flat too, but omits all dims — backfill from defaults.
|
|
185
245
|
const baseT = (raw.text_config ?? raw) as Record<string, any>;
|
|
@@ -195,28 +255,44 @@ export async function loadModelConfig(modelDir: string): Promise<ModelConfig> {
|
|
|
195
255
|
const qwenRope = (t.rope_parameters ?? {}) as Record<string, any>;
|
|
196
256
|
|
|
197
257
|
const text: TextConfig = {
|
|
198
|
-
hiddenSize: t.hidden_size,
|
|
199
|
-
numHiddenLayers: t.num_hidden_layers,
|
|
200
|
-
numAttentionHeads: t.num_attention_heads,
|
|
201
|
-
numKeyValueHeads: t.num_key_value_heads,
|
|
202
|
-
headDim: t.head_dim ?? Math.floor(t.hidden_size / t.num_attention_heads),
|
|
203
|
-
numGlobalKeyValueHeads:
|
|
204
|
-
|
|
258
|
+
hiddenSize: glm52?.hiddenSize ?? t.hidden_size,
|
|
259
|
+
numHiddenLayers: glm52?.numHiddenLayers ?? t.num_hidden_layers,
|
|
260
|
+
numAttentionHeads: glm52?.numAttentionHeads ?? t.num_attention_heads,
|
|
261
|
+
numKeyValueHeads: glm52?.numKeyValueHeads ?? t.num_key_value_heads,
|
|
262
|
+
headDim: glm52?.qkHeadDim ?? t.head_dim ?? Math.floor(t.hidden_size / t.num_attention_heads),
|
|
263
|
+
numGlobalKeyValueHeads:
|
|
264
|
+
glm52?.numKeyValueHeads ?? t.num_global_key_value_heads ?? t.num_key_value_heads,
|
|
265
|
+
globalHeadDim:
|
|
266
|
+
glm52?.qkHeadDim ??
|
|
267
|
+
t.global_head_dim ??
|
|
268
|
+
t.head_dim ??
|
|
269
|
+
Math.floor(t.hidden_size / t.num_attention_heads),
|
|
205
270
|
attentionKEqV: t.attention_k_eq_v ?? false,
|
|
206
|
-
intermediateSize: t.intermediate_size,
|
|
271
|
+
intermediateSize: glm52?.intermediateSize ?? t.intermediate_size,
|
|
207
272
|
hiddenActivation: t.hidden_activation ?? t.hidden_act ?? "gelu_pytorch_tanh",
|
|
208
|
-
rmsNormEps: t.rms_norm_eps,
|
|
209
|
-
vocabSize: t.vocab_size,
|
|
210
|
-
maxPositionEmbeddings:
|
|
273
|
+
rmsNormEps: glm52?.rmsNormEps ?? t.rms_norm_eps,
|
|
274
|
+
vocabSize: glm52?.vocabSize ?? t.vocab_size,
|
|
275
|
+
maxPositionEmbeddings:
|
|
276
|
+
glm52?.maxPositionEmbeddings ?? t.max_position_embeddings,
|
|
211
277
|
slidingWindow: t.sliding_window ?? 0,
|
|
212
|
-
layerTypes:
|
|
278
|
+
layerTypes: glm52
|
|
279
|
+
? Array(glm52.numHiddenLayers).fill("full_attention")
|
|
280
|
+
: t.layer_types ?? (isLlama ? Array(t.num_hidden_layers).fill("full_attention") : []),
|
|
213
281
|
hiddenSizePerLayerInput: t.hidden_size_per_layer_input ?? 0,
|
|
214
|
-
vocabSizePerLayerInput:
|
|
282
|
+
vocabSizePerLayerInput:
|
|
283
|
+
t.vocab_size_per_layer_input ?? glm52?.vocabSize ?? t.vocab_size,
|
|
215
284
|
numKvSharedLayers: t.num_kv_shared_layers ?? 0,
|
|
216
|
-
enableMoeBlock: t.enable_moe_block ?? false,
|
|
217
|
-
numExperts: t.num_experts ?? 0,
|
|
218
|
-
|
|
219
|
-
|
|
285
|
+
enableMoeBlock: glm52 ? true : t.enable_moe_block ?? false,
|
|
286
|
+
numExperts: glm52?.numRoutedExperts ?? t.num_experts ?? 0,
|
|
287
|
+
// gemma4 uses `top_k_experts`; qwen3_moe uses `num_experts_per_tok`.
|
|
288
|
+
topKExperts:
|
|
289
|
+
glm52?.numExpertsPerToken ?? t.top_k_experts ?? t.num_experts_per_tok ?? 0,
|
|
290
|
+
moeIntermediateSize: glm52?.moeIntermediateSize ?? t.moe_intermediate_size ?? 0,
|
|
291
|
+
decoderSparseStep: glm52 ? 1 : t.decoder_sparse_step ?? 1,
|
|
292
|
+
mlpOnlyLayers: glm52
|
|
293
|
+
? Array.from({ length: glm52.firstKDenseReplace }, (_, layer) => layer)
|
|
294
|
+
: t.mlp_only_layers ?? [],
|
|
295
|
+
normTopkProb: glm52?.normTopkProb ?? t.norm_topk_prob ?? false,
|
|
220
296
|
linearNumValueHeads: t.linear_num_value_heads ?? 0,
|
|
221
297
|
linearNumKeyHeads: t.linear_num_key_heads ?? 0,
|
|
222
298
|
linearKeyHeadDim: t.linear_key_head_dim ?? 0,
|
|
@@ -224,9 +300,19 @@ export async function loadModelConfig(modelDir: string): Promise<ModelConfig> {
|
|
|
224
300
|
linearConvKernelDim: t.linear_conv_kernel_dim ?? 0,
|
|
225
301
|
fullAttentionInterval: t.full_attention_interval ?? 0,
|
|
226
302
|
attnOutputGate: t.attn_output_gate ?? false,
|
|
227
|
-
partialRotaryFactor:
|
|
228
|
-
|
|
229
|
-
|
|
303
|
+
partialRotaryFactor: glm52
|
|
304
|
+
? glm52.qkRopeHeadDim / glm52.qkHeadDim
|
|
305
|
+
: qwenRope.partial_rotary_factor ?? t.partial_rotary_factor ?? 1.0,
|
|
306
|
+
ropeParameters: glm52
|
|
307
|
+
? {
|
|
308
|
+
full_attention: {
|
|
309
|
+
ropeTheta: glm52.ropeTheta,
|
|
310
|
+
ropeType: "default",
|
|
311
|
+
partialRotaryFactor: glm52.qkRopeHeadDim / glm52.qkHeadDim,
|
|
312
|
+
factor: 1.0,
|
|
313
|
+
},
|
|
314
|
+
}
|
|
315
|
+
: isQwen35
|
|
230
316
|
? {
|
|
231
317
|
full_attention: {
|
|
232
318
|
ropeTheta: qwenRope.rope_theta ?? t.rope_theta ?? 10000,
|
|
@@ -250,7 +336,7 @@ export async function loadModelConfig(modelDir: string): Promise<ModelConfig> {
|
|
|
250
336
|
finalLogitSoftcapping: t.final_logit_softcapping ?? null,
|
|
251
337
|
tieWordEmbeddings: t.tie_word_embeddings ?? raw.tie_word_embeddings ?? false,
|
|
252
338
|
bosTokenId: t.bos_token_id ?? raw.bos_token_id,
|
|
253
|
-
eosTokenId: t.eos_token_id ?? raw.eos_token_id,
|
|
339
|
+
eosTokenId: glm52?.eosTokenIds ?? t.eos_token_id ?? raw.eos_token_id,
|
|
254
340
|
canvasLength: raw.canvas_length ?? t.canvas_length,
|
|
255
341
|
};
|
|
256
342
|
|
|
@@ -264,14 +350,20 @@ export async function loadModelConfig(modelDir: string): Promise<ModelConfig> {
|
|
|
264
350
|
}));
|
|
265
351
|
}
|
|
266
352
|
|
|
267
|
-
const eos = raw.eos_token_id ?? text.eosTokenId;
|
|
353
|
+
const eos = glm52?.eosTokenIds ?? raw.eos_token_id ?? text.eosTokenId;
|
|
268
354
|
return {
|
|
269
355
|
modelDir,
|
|
270
356
|
modelType,
|
|
271
357
|
architectures: raw.architectures ?? [],
|
|
272
358
|
dtype: raw.dtype ?? "bfloat16",
|
|
273
359
|
text,
|
|
274
|
-
|
|
360
|
+
// Colibri's config retains the SOURCE FP8 quantization_config, while the
|
|
361
|
+
// serving shards are converted U8 + `.qs` tensors whose int4/int8/group
|
|
362
|
+
// geometry is inferred and validated by Glm52Container. Advertising the
|
|
363
|
+
// source FP8 block as mlx affine quantization would be actively wrong.
|
|
364
|
+
quantization: glm52
|
|
365
|
+
? null
|
|
366
|
+
: parseQuantization(raw.quantization ?? raw.quantization_config),
|
|
275
367
|
kvQuant,
|
|
276
368
|
hasVisionSidecar: await Bun.file(`${modelDir}/optiq_vision.safetensors`).exists(),
|
|
277
369
|
eosTokenIds: Array.isArray(eos) ? eos : [eos],
|
|
@@ -245,9 +245,7 @@ export function diffusionGenerate(
|
|
|
245
245
|
|
|
246
246
|
let currentCanvas = ops.randint(0, vocab, [1, canvasLength], I32); // RNG: init
|
|
247
247
|
let draftReveal = ops.zeros([1, canvasLength], Dtype.bool);
|
|
248
|
-
let draftCanvas
|
|
249
|
-
draftCanvas.dispose();
|
|
250
|
-
draftCanvas = ops.reshape(currentCanvas, [1, canvasLength]); // alias copy
|
|
248
|
+
let draftCanvas = ops.reshape(currentCanvas, [1, canvasLength]); // alias copy
|
|
251
249
|
let argmaxCanvas = ops.reshape(currentCanvas, [1, canvasLength]);
|
|
252
250
|
let scEmbeddings: MlxArray | null = null;
|
|
253
251
|
const history: MlxArray[] = [];
|
|
@@ -266,7 +264,9 @@ export function diffusionGenerate(
|
|
|
266
264
|
logits.dispose();
|
|
267
265
|
|
|
268
266
|
argmaxCanvas.dispose();
|
|
269
|
-
|
|
267
|
+
const argmax = ops.argmaxAxis(processed, -1);
|
|
268
|
+
argmaxCanvas = argmax.astype(I32); // [1, L]
|
|
269
|
+
argmax.dispose();
|
|
270
270
|
|
|
271
271
|
if (curStep === 1) {
|
|
272
272
|
processed.dispose();
|
|
@@ -296,7 +296,9 @@ export function diffusionGenerate(
|
|
|
296
296
|
currentCanvas.dispose();
|
|
297
297
|
currentCanvas = newCurrent;
|
|
298
298
|
draftReveal.dispose();
|
|
299
|
-
|
|
299
|
+
const notAcceptance = ops.logicalNot(acceptance);
|
|
300
|
+
draftReveal = ops.logicalNot(notAcceptance); // = acceptance
|
|
301
|
+
notAcceptance.dispose();
|
|
300
302
|
draftCanvas.dispose();
|
|
301
303
|
draftCanvas = ops.reshape(argmaxCanvas, [1, canvasLength]);
|
|
302
304
|
accepted.dispose();
|
|
@@ -341,6 +343,10 @@ export function diffusionGenerate(
|
|
|
341
343
|
stableStop &&
|
|
342
344
|
stableAndConfident(argmaxCanvas, processed, history, stabilityThreshold, stopConfidence)
|
|
343
345
|
) {
|
|
346
|
+
// Entropy sampling precomputes the next step's self-conditioning
|
|
347
|
+
// tensor before evaluating this stop. The break means ownership is
|
|
348
|
+
// never transferred to scEmbeddings, so release the pending value.
|
|
349
|
+
nextSc?.dispose();
|
|
344
350
|
processed.dispose();
|
|
345
351
|
break;
|
|
346
352
|
}
|
|
@@ -361,9 +367,9 @@ export function diffusionGenerate(
|
|
|
361
367
|
draftCanvas.dispose();
|
|
362
368
|
|
|
363
369
|
// Emit from the final argmax canvas.
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
);
|
|
370
|
+
const argmaxFloat = argmaxCanvas.astype(Dtype.float32);
|
|
371
|
+
const blockTokens = [...argmaxFloat.toFloat32()].map((x) => Math.round(x));
|
|
372
|
+
argmaxFloat.dispose();
|
|
367
373
|
argmaxCanvas.dispose();
|
|
368
374
|
blocks.push(blockTokens);
|
|
369
375
|
totalSteps += stepsThisCanvas;
|
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;
|
|
@@ -114,6 +165,28 @@ function acquireLock(blobPath: string, name: string): () => void {
|
|
|
114
165
|
throw new Error(`could not acquire ${lockPath}`);
|
|
115
166
|
}
|
|
116
167
|
|
|
168
|
+
/** Reject a repo file path that could escape the snapshot directory once
|
|
169
|
+
* joined onto it. `rfilename` comes verbatim from the remote HF API's
|
|
170
|
+
* `siblings[].rfilename` (listRepoFiles) — for a repo the caller doesn't
|
|
171
|
+
* control (attacker-owned/attacker-referenced), that string is untrusted
|
|
172
|
+
* input reaching a path-join + symlink write (downloadModel's
|
|
173
|
+
* `join(snapDir, f.rfilename)` + `symlinkSync`). node:path's `join()`
|
|
174
|
+
* normalizes `..` segments, so an rfilename like
|
|
175
|
+
* `../../../../home/x/.ssh/authorized_keys` would otherwise resolve
|
|
176
|
+
* OUTSIDE the intended snapshots dir and write a symlink at an arbitrary
|
|
177
|
+
* filesystem path. Reachable over HTTP via POST /api/hub/download
|
|
178
|
+
* (src/hub-rest.ts), which passes any caller-supplied `repo` straight
|
|
179
|
+
* through with no allowlist — this check is the actual gate, not that
|
|
180
|
+
* route. A leading `/` (absolute path) is equally rejected: join() would
|
|
181
|
+
* otherwise treat it as relative to snapDir and still normalize safely,
|
|
182
|
+
* but there's no legitimate reason a repo file entry needs one, and
|
|
183
|
+
* rejecting it removes any doubt. */
|
|
184
|
+
export function isSafeRepoFilename(rfilename: string): boolean {
|
|
185
|
+
if (!rfilename || rfilename.startsWith("/") || rfilename.includes("\0")) return false;
|
|
186
|
+
const parts = rfilename.split("/");
|
|
187
|
+
return parts.every((p) => p !== "" && p !== "." && p !== "..");
|
|
188
|
+
}
|
|
189
|
+
|
|
117
190
|
/** git blob identity: sha1("blob <size>\0" + content) — what blobId is. */
|
|
118
191
|
export function gitBlobSha1(bytes: Uint8Array): string {
|
|
119
192
|
const hasher = new Bun.CryptoHasher("sha1");
|
|
@@ -139,16 +212,34 @@ export async function listRepoFiles(
|
|
|
139
212
|
);
|
|
140
213
|
const body = (await res.json()) as {
|
|
141
214
|
sha: string;
|
|
142
|
-
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
|
+
}[];
|
|
143
223
|
};
|
|
144
224
|
return {
|
|
145
225
|
sha: body.sha,
|
|
146
|
-
files: body.siblings.map((s) =>
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
+
}),
|
|
152
243
|
};
|
|
153
244
|
}
|
|
154
245
|
|
|
@@ -294,6 +385,14 @@ export function downloadsSnapshot(): DownloadStatus[] {
|
|
|
294
385
|
return downloadLog.slice(-5);
|
|
295
386
|
}
|
|
296
387
|
|
|
388
|
+
/** True when a download for this exact repoId is currently "active" in this
|
|
389
|
+
* process's tracker (used by the Hub's POST /api/hub/download to refuse a
|
|
390
|
+
* duplicate kick-off — same repoId, still running — rather than starting a
|
|
391
|
+
* second writer that would race the first on the same blob lockfile). */
|
|
392
|
+
export function isDownloadActive(repoId: string): boolean {
|
|
393
|
+
return downloadLog.some((d) => d.repoId === repoId && d.state === "active");
|
|
394
|
+
}
|
|
395
|
+
|
|
297
396
|
export async function downloadModel(
|
|
298
397
|
repoId: string, opts: DownloadOptions = {},
|
|
299
398
|
): Promise<string> {
|
|
@@ -303,12 +402,44 @@ export async function downloadModel(
|
|
|
303
402
|
const hub = opts.cacheDir ?? DEFAULT_HUB;
|
|
304
403
|
|
|
305
404
|
const listing = await listRepoFiles(repoId, { ...opts, token });
|
|
405
|
+
// Fail the whole download closed (not skip-and-continue) if the repo's
|
|
406
|
+
// file listing contains a path-traversal rfilename — see
|
|
407
|
+
// isSafeRepoFilename's doc comment. This gates BOTH the CLI and the web
|
|
408
|
+
// Hub's POST /api/hub/download (src/hub-rest.ts), which accepts any
|
|
409
|
+
// caller-supplied repo string with no allowlist of its own.
|
|
410
|
+
for (const f of listing.files) {
|
|
411
|
+
if (!isSafeRepoFilename(f.rfilename)) {
|
|
412
|
+
throw new Error(`${repoId}: refusing unsafe file path in repo listing: ${JSON.stringify(f.rfilename)}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
306
415
|
const repoDir = join(hub, `models--${repoId.replaceAll("/", "--")}`);
|
|
307
416
|
const blobsDir = join(repoDir, "blobs");
|
|
308
417
|
const snapDir = join(repoDir, "snapshots", listing.sha);
|
|
309
418
|
mkdirSync(blobsDir, { recursive: true });
|
|
310
419
|
mkdirSync(join(repoDir, "refs"), { recursive: true });
|
|
311
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
|
+
|
|
312
443
|
const status: DownloadStatus = {
|
|
313
444
|
repoId, state: "active", currentFile: null,
|
|
314
445
|
receivedBytes: 0, totalBytes: listing.files.reduce((a, f) => a + f.size, 0),
|
|
@@ -329,7 +460,7 @@ export async function downloadModel(
|
|
|
329
460
|
|
|
330
461
|
try {
|
|
331
462
|
for (const f of listing.files) {
|
|
332
|
-
const blobId = f.lfs?.
|
|
463
|
+
const blobId = f.lfs?.sha256 ?? f.blobId;
|
|
333
464
|
if (!blobId) throw new Error(`no blob id for ${f.rfilename} (API response missing ?blobs=true data)`);
|
|
334
465
|
const blobPath = join(blobsDir, blobId);
|
|
335
466
|
|
|
@@ -338,7 +469,7 @@ export async function downloadModel(
|
|
|
338
469
|
status.currentFile = f.rfilename;
|
|
339
470
|
await downloadOne(url, token, blobPath, {
|
|
340
471
|
size: f.size, name: f.rfilename,
|
|
341
|
-
...(f.lfs ? { sha256: f.lfs.
|
|
472
|
+
...(f.lfs ? { sha256: f.lfs.sha256 } : { sha1: f.blobId }),
|
|
342
473
|
}, (file, received, total) => {
|
|
343
474
|
status.receivedBytes = doneBytes + received;
|
|
344
475
|
sampleRate(status.receivedBytes);
|
package/src/eval/README.md
CHANGED
|
@@ -36,8 +36,8 @@ repo rule: free-running greedy "measures chaos").
|
|
|
36
36
|
# drift gate on an existing non-bit-exact lever (validates the harness today):
|
|
37
37
|
bun scripts/eval.ts kl --candidate e4b
|
|
38
38
|
|
|
39
|
-
#
|
|
40
|
-
bun scripts/eval.ts kl --candidate e4b --self
|
|
39
|
+
# self-arm A/B on an output-affecting lever (fused-sdpa on vs off):
|
|
40
|
+
bun scripts/eval.ts kl --candidate e4b --self MLX_BUN_NO_FUSED_SDPA --ref-value 1 --cand-value 0
|
|
41
41
|
|
|
42
42
|
# optiq-style absolute drift vs a reference model:
|
|
43
43
|
bun scripts/eval.ts kl --candidate e4b --reference <bf16-or-4bit-id>
|
|
@@ -55,9 +55,10 @@ the throughput `runs` table). Each row records the active perf levers
|
|
|
55
55
|
|
|
56
56
|
- **M0 (done):** KL drift gate + capability score + `quality_runs` DB + CLI.
|
|
57
57
|
- **M0b (done):** *serving-path* KL (`evaluateKlServingDecode` in `kl.ts`,
|
|
58
|
-
`bun scripts/eval.ts kl --decode`). Validated: identical arms →
|
|
59
|
-
`MLX_BUN_FUSED_DECODE` tiled-decode → KL 0.27 (real
|
|
60
|
-
|
|
58
|
+
`bun scripts/eval.ts kl --decode`). Validated at the time: identical arms →
|
|
59
|
+
KL 0; the since-deleted `MLX_BUN_FUSED_DECODE` tiled-decode → KL 0.27 (real
|
|
60
|
+
drift on the quantized path — the lever was removed 2026-07-05, but the
|
|
61
|
+
validation stands). Motivation below — the compat KL couldn't see it:
|
|
61
62
|
The compat `forward()` path with `makeCache()` uses **plain bf16 caches**. That is wrong
|
|
62
63
|
twice over: (1) e4b's KV is **per-layer mixed 4-bit/8-bit** (group 64, from
|
|
63
64
|
`kv_config.json` → `config.kvQuant`; 8-bit layers 3,4,9,10,11,14, 4-bit
|
|
@@ -67,7 +68,7 @@ the throughput `runs` table). Each row records the active perf levers
|
|
|
67
68
|
caches, then `cache[i] = c.toQuantized(e.groupSize, e.bits)` per
|
|
68
69
|
`config.kvQuant` (the logic already at `generate.ts:68-86`), so the
|
|
69
70
|
teacher-forced prefill runs the real mixed-4/8-bit quantized path and the
|
|
70
|
-
perf levers (NO_FUSED_SDPA
|
|
71
|
+
perf levers (NO_FUSED_SDPA) actually bite.
|
|
71
72
|
- **M1 (done):** all six capability tasks ported + runtime-validated, plus
|
|
72
73
|
`capability` aggregation and the `smoketest` (KL + GSM8K-50) gate. Datasets
|
|
73
74
|
exported by `scripts/eval/export-datasets.py` → `~/.cache/mlx-bun/eval-data/`.
|