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.
Files changed (81) hide show
  1. package/README.md +43 -13
  2. package/THIRD_PARTY_LICENSES.md +9 -0
  3. package/docs/design/colibri-glm52-port.md +1479 -0
  4. package/docs/design/pre-colibri-stabilization.md +329 -0
  5. package/docs/investigations/colibri-oracle-pin.md +1284 -0
  6. package/docs/investigations/indexshare-performance-spike.md +357 -0
  7. package/docs/planning/release-notes-v0.0.12.md +80 -0
  8. package/docs/reference/cli.md +28 -2
  9. package/docs/reference/distribution.md +25 -1
  10. package/docs/reference/embedding.md +5 -2
  11. package/docs/reference/features-matrix.md +4 -2
  12. package/docs/reference/library-api.md +41 -13
  13. package/docs/reference/models.md +49 -2
  14. package/docs/reference/server-api.md +90 -9
  15. package/docs/reference/server-config.md +21 -14
  16. package/docs/reference/training.md +10 -1
  17. package/package.json +1 -1
  18. package/src/anthropic.ts +54 -44
  19. package/src/chat-template.ts +155 -10
  20. package/src/cli.ts +127 -8
  21. package/src/config.ts +71 -26
  22. package/src/diffusion/diffusion-generate.ts +14 -8
  23. package/src/download.ts +103 -12
  24. package/src/eval/ifeval.ts +63 -120
  25. package/src/eval/runner.ts +14 -5
  26. package/src/eval/tasks/ifeval.ts +167 -31
  27. package/src/expert-io.ts +391 -0
  28. package/src/expert-residency.ts +1134 -0
  29. package/src/expert-usage.ts +415 -0
  30. package/src/generate.ts +114 -17
  31. package/src/hf-push.ts +1 -1
  32. package/src/index.ts +14 -2
  33. package/src/jobs/runner.ts +37 -18
  34. package/src/kv-store.ts +147 -8
  35. package/src/lora.ts +77 -14
  36. package/src/media-fetch.ts +198 -56
  37. package/src/mlx/array.ts +43 -0
  38. package/src/mlx/autograd.ts +51 -14
  39. package/src/mlx/checkpoint.ts +46 -13
  40. package/src/mlx/custom-vjp.ts +83 -35
  41. package/src/model/factory.ts +96 -5
  42. package/src/model/gemma4-base.ts +32 -0
  43. package/src/model/glm52-atlas.ts +511 -0
  44. package/src/model/glm52-cache.ts +706 -0
  45. package/src/model/glm52-config.ts +262 -0
  46. package/src/model/glm52-container.ts +305 -0
  47. package/src/model/glm52-coupling.ts +388 -0
  48. package/src/model/glm52-dsa.ts +602 -0
  49. package/src/model/glm52-expert-layout.ts +253 -0
  50. package/src/model/glm52-layout.ts +152 -0
  51. package/src/model/glm52-memory.ts +473 -0
  52. package/src/model/glm52-mla.ts +786 -0
  53. package/src/model/glm52-moe.ts +223 -0
  54. package/src/model/glm52-pilot.ts +537 -0
  55. package/src/model/glm52-reference.ts +445 -0
  56. package/src/model/glm52-residency.ts +557 -0
  57. package/src/model/glm52-resident-weights.ts +135 -0
  58. package/src/model/glm52-streamed-experts.ts +453 -0
  59. package/src/model/glm52-streamed-metal.ts +610 -0
  60. package/src/model/glm52-weights.ts +503 -0
  61. package/src/model/glm52.ts +1153 -0
  62. package/src/model/support.ts +8 -1
  63. package/src/native/expert_io.c +523 -0
  64. package/src/native-pack.ts +6 -4
  65. package/src/responses.ts +62 -49
  66. package/src/serve/batch-scheduler.ts +95 -16
  67. package/src/serve/generation-gateway.ts +41 -16
  68. package/src/server.ts +362 -81
  69. package/src/spec/glm52-mtp-source.ts +392 -0
  70. package/src/spec/serve-loop.ts +99 -18
  71. package/src/spec/source.ts +31 -6
  72. package/src/ssd-cache.ts +6 -2
  73. package/src/tool-call.ts +30 -2
  74. package/src/train/job.ts +0 -10
  75. package/src/train/lora-params.ts +17 -6
  76. package/src/train/trainer.ts +59 -26
  77. package/src/web/app.html +13 -2
  78. package/src/web/app.js +30 -3
  79. package/src/web/src/status.ts +51 -3
  80. package/src/web-tools.ts +54 -2
  81. package/src/weights.ts +82 -19
@@ -0,0 +1,253 @@
1
+ import type {
2
+ ColibriGlm52Container,
3
+ ColibriQuantTensorInfo,
4
+ ColibriTensorInfo,
5
+ } from "./glm52-container";
6
+ import type { Glm52Config } from "./glm52-config";
7
+
8
+ export const GLM52_EXPERT_SLOT_ALIGNMENT = 16 * 1024;
9
+ export const GLM52_EXPERT_MAX_READ_SEGMENTS = 8;
10
+
11
+ export type Glm52ExpertProjection = "down" | "gate" | "up";
12
+
13
+ export interface Glm52ExpertProjectionLayout {
14
+ readonly projection: Glm52ExpertProjection;
15
+ readonly tensor: ColibriQuantTensorInfo;
16
+ readonly weightOffset: number;
17
+ readonly scaleOffset: number;
18
+ }
19
+
20
+ export interface Glm52ExpertReadSegment {
21
+ readonly file: string;
22
+ readonly sourceOffset: number;
23
+ readonly destinationOffset: number;
24
+ readonly length: number;
25
+ }
26
+
27
+ export interface Glm52ExpertSlotLayout {
28
+ readonly layer: number;
29
+ readonly expertId: number;
30
+ readonly bits: 4 | 8;
31
+ readonly groupSize: number | null;
32
+ readonly alignment: number;
33
+ readonly weightBytes: number;
34
+ readonly scaleBytes: number;
35
+ readonly scaleOffset: number;
36
+ readonly payloadBytes: number;
37
+ readonly slotBytes: number;
38
+ readonly projections: Readonly<Record<
39
+ Glm52ExpertProjection,
40
+ Glm52ExpertProjectionLayout
41
+ >>;
42
+ /**
43
+ * Positioned reads in canonical slot order. Adjacent tensor reads are
44
+ * coalesced only when both their source and destination ranges are adjacent
45
+ * in the same shard.
46
+ */
47
+ readonly segments: readonly Glm52ExpertReadSegment[];
48
+ }
49
+
50
+ type ExpertGeometry = Pick<
51
+ Glm52Config,
52
+ "hiddenSize" | "moeIntermediateSize" | "numRoutedExperts"
53
+ >;
54
+
55
+ interface PrimitiveRead {
56
+ readonly tensor: ColibriTensorInfo;
57
+ readonly destinationOffset: number;
58
+ }
59
+
60
+ function positiveInteger(value: number, label: string): number {
61
+ if (!Number.isSafeInteger(value) || value <= 0)
62
+ throw new Error(`${label} must be a positive safe integer`);
63
+ return value;
64
+ }
65
+
66
+ function alignUp(value: number, alignment: number): number {
67
+ const result = Math.ceil(value / alignment) * alignment;
68
+ if (!Number.isSafeInteger(result))
69
+ throw new Error("expert slot size exceeds the safe integer range");
70
+ return result;
71
+ }
72
+
73
+ function coalesceReads(
74
+ reads: readonly PrimitiveRead[],
75
+ ): Glm52ExpertReadSegment[] {
76
+ const segments: Glm52ExpertReadSegment[] = [];
77
+ for (const read of reads) {
78
+ const next: Glm52ExpertReadSegment = {
79
+ file: read.tensor.file,
80
+ sourceOffset: read.tensor.absoluteBegin,
81
+ destinationOffset: read.destinationOffset,
82
+ length: read.tensor.byteLength,
83
+ };
84
+ const prior = segments.at(-1);
85
+ if (
86
+ prior &&
87
+ prior.file === next.file &&
88
+ prior.sourceOffset + prior.length === next.sourceOffset &&
89
+ prior.destinationOffset + prior.length === next.destinationOffset
90
+ ) {
91
+ segments[segments.length - 1] = {
92
+ ...prior,
93
+ length: prior.length + next.length,
94
+ };
95
+ } else {
96
+ segments.push(next);
97
+ }
98
+ }
99
+ return segments;
100
+ }
101
+
102
+ /**
103
+ * Describe one routed expert as a fixed aligned native slot without opening or
104
+ * mapping any tensor payload.
105
+ *
106
+ * The slot order is deliberately independent of safetensors header order:
107
+ * down/gate/up packed weights followed by down/gate/up F32 scales. Every
108
+ * pointer-addressed component begins at a 16 KiB boundary: MLX/Metal cannot
109
+ * safely no-copy-wrap a merely sub-page-aligned host pointer. Source-adjacent
110
+ * components still coalesce when the required padding is zero.
111
+ */
112
+ export function buildGlm52ExpertSlotLayout(
113
+ container: ColibriGlm52Container,
114
+ config: ExpertGeometry,
115
+ layer: number,
116
+ expertId: number,
117
+ alignment = GLM52_EXPERT_SLOT_ALIGNMENT,
118
+ ): Glm52ExpertSlotLayout {
119
+ positiveInteger(config.hiddenSize, "hidden size");
120
+ positiveInteger(config.moeIntermediateSize, "MoE intermediate size");
121
+ positiveInteger(config.numRoutedExperts, "routed expert count");
122
+ if (!Number.isSafeInteger(layer) || layer < 0)
123
+ throw new Error("expert layer must be a non-negative safe integer");
124
+ if (
125
+ !Number.isSafeInteger(expertId) ||
126
+ expertId < 0 ||
127
+ expertId >= config.numRoutedExperts
128
+ ) {
129
+ throw new Error(
130
+ `expert ID ${expertId} is outside [0,${config.numRoutedExperts})`,
131
+ );
132
+ }
133
+ if (
134
+ !Number.isSafeInteger(alignment) ||
135
+ alignment < 1 ||
136
+ (alignment & (alignment - 1)) !== 0
137
+ ) {
138
+ throw new Error("expert slot alignment must be a positive power of two");
139
+ }
140
+
141
+ const prefix = `model.layers.${layer}.mlp.experts.${expertId}`;
142
+ const dimensions: Record<
143
+ Glm52ExpertProjection,
144
+ readonly [outputRows: number, inputColumns: number]
145
+ > = {
146
+ down: [config.hiddenSize, config.moeIntermediateSize],
147
+ gate: [config.moeIntermediateSize, config.hiddenSize],
148
+ up: [config.moeIntermediateSize, config.hiddenSize],
149
+ };
150
+ const suffix: Record<Glm52ExpertProjection, string> = {
151
+ down: "down_proj",
152
+ gate: "gate_proj",
153
+ up: "up_proj",
154
+ };
155
+ const order: readonly Glm52ExpertProjection[] = ["down", "gate", "up"];
156
+
157
+ const tensors = {} as Record<
158
+ Glm52ExpertProjection,
159
+ ColibriQuantTensorInfo
160
+ >;
161
+ for (const projection of order) {
162
+ const [outputRows, inputColumns] = dimensions[projection];
163
+ tensors[projection] = container.quantized(
164
+ `${prefix}.${suffix[projection]}.weight`,
165
+ outputRows,
166
+ inputColumns,
167
+ );
168
+ }
169
+
170
+ const bits = tensors.down.bits;
171
+ const groupSize = tensors.down.groupSize;
172
+ for (const projection of order) {
173
+ const tensor = tensors[projection];
174
+ if (tensor.bits !== bits || tensor.groupSize !== groupSize) {
175
+ throw new Error(
176
+ `${prefix}: routed expert projections must use one quantization layout`,
177
+ );
178
+ }
179
+ }
180
+
181
+ let weightBytes = 0;
182
+ let cursor = 0;
183
+ const weightOffsets = {} as Record<Glm52ExpertProjection, number>;
184
+ for (const projection of order) {
185
+ cursor = alignUp(cursor, alignment);
186
+ weightOffsets[projection] = cursor;
187
+ weightBytes += tensors[projection].weight.byteLength;
188
+ cursor += tensors[projection].weight.byteLength;
189
+ }
190
+ if (!Number.isSafeInteger(weightBytes))
191
+ throw new Error(`${prefix}: expert weight bytes exceed the safe integer range`);
192
+
193
+ const scaleOffset = alignUp(cursor, alignment);
194
+ cursor = scaleOffset;
195
+ let scaleBytes = 0;
196
+ const scaleOffsets = {} as Record<Glm52ExpertProjection, number>;
197
+ for (const projection of order) {
198
+ cursor = alignUp(cursor, alignment);
199
+ scaleOffsets[projection] = cursor;
200
+ scaleBytes += tensors[projection].scales.byteLength;
201
+ cursor += tensors[projection].scales.byteLength;
202
+ }
203
+ const payloadBytes = cursor;
204
+ if (!Number.isSafeInteger(payloadBytes))
205
+ throw new Error(`${prefix}: expert payload bytes exceed the safe integer range`);
206
+ const slotBytes = alignUp(payloadBytes, alignment);
207
+
208
+ const primitiveReads: PrimitiveRead[] = [
209
+ ...order.map((projection) => ({
210
+ tensor: tensors[projection].weight,
211
+ destinationOffset: weightOffsets[projection],
212
+ })),
213
+ ...order.map((projection) => ({
214
+ tensor: tensors[projection].scales,
215
+ destinationOffset: scaleOffsets[projection],
216
+ })),
217
+ ];
218
+ const segments = coalesceReads(primitiveReads);
219
+ if (segments.length > GLM52_EXPERT_MAX_READ_SEGMENTS) {
220
+ throw new Error(
221
+ `${prefix}: expert requires ${segments.length} read segments; maximum is ` +
222
+ `${GLM52_EXPERT_MAX_READ_SEGMENTS}`,
223
+ );
224
+ }
225
+
226
+ const projections = {} as Record<
227
+ Glm52ExpertProjection,
228
+ Glm52ExpertProjectionLayout
229
+ >;
230
+ for (const projection of order) {
231
+ projections[projection] = {
232
+ projection,
233
+ tensor: tensors[projection],
234
+ weightOffset: weightOffsets[projection],
235
+ scaleOffset: scaleOffsets[projection],
236
+ };
237
+ }
238
+
239
+ return {
240
+ layer,
241
+ expertId,
242
+ bits,
243
+ groupSize,
244
+ alignment,
245
+ weightBytes,
246
+ scaleBytes,
247
+ scaleOffset,
248
+ payloadBytes,
249
+ slotBytes,
250
+ projections,
251
+ segments,
252
+ };
253
+ }
@@ -0,0 +1,152 @@
1
+ // Exact logical tensor contract for the direct Colibri GLM-5.2 container.
2
+
3
+ import type { Glm52Config } from "./glm52-config";
4
+ import {
5
+ ColibriGlm52Container,
6
+ type ColibriTensorInfo,
7
+ } from "./glm52-container";
8
+
9
+ export interface Glm52LayoutValidation {
10
+ readonly quantizedTensors: number;
11
+ readonly floatTensors: number;
12
+ readonly routedExperts: number;
13
+ readonly hasDsa: boolean;
14
+ readonly hasMtp: boolean;
15
+ }
16
+
17
+ function sameShape(actual: readonly number[], expected: readonly number[]): boolean {
18
+ return actual.length === expected.length &&
19
+ actual.every((dimension, index) => dimension === expected[index]);
20
+ }
21
+
22
+ function validateFloatTensor(
23
+ container: ColibriGlm52Container,
24
+ name: string,
25
+ shape: number[],
26
+ ): ColibriTensorInfo {
27
+ const tensor = container.info(name);
28
+ if (tensor.dtype !== "F32")
29
+ throw new Error(`${name}: resident tensor must be F32 (got ${tensor.dtype})`);
30
+ if (!sameShape(tensor.shape, shape)) {
31
+ throw new Error(
32
+ `${name}: shape ${JSON.stringify(tensor.shape)} != ${JSON.stringify(shape)}`,
33
+ );
34
+ }
35
+ return tensor;
36
+ }
37
+
38
+ export function validateGlm52ContainerLayout(
39
+ container: ColibriGlm52Container,
40
+ config: Glm52Config,
41
+ ): Glm52LayoutValidation {
42
+ let quantizedTensors = 0;
43
+ let floatTensors = 0;
44
+ let routedExperts = 0;
45
+ const q = (name: string, output: number, input: number): void => {
46
+ container.quantized(name, output, input);
47
+ quantizedTensors++;
48
+ };
49
+ const f = (name: string, shape: number[]): void => {
50
+ validateFloatTensor(container, name, shape);
51
+ floatTensors++;
52
+ };
53
+ const D = config.hiddenSize;
54
+ const H = config.numAttentionHeads;
55
+ const indexCapabilities = container.capabilities(config);
56
+
57
+ q("model.embed_tokens.weight", config.vocabSize, D);
58
+ q("lm_head.weight", config.vocabSize, D);
59
+ f("model.norm.weight", [D]);
60
+
61
+ const validateAttention = (layer: number): void => {
62
+ const p = `model.layers.${layer}`;
63
+ f(`${p}.input_layernorm.weight`, [D]);
64
+ f(`${p}.post_attention_layernorm.weight`, [D]);
65
+ q(`${p}.self_attn.q_a_proj.weight`, config.qLoraRank, D);
66
+ f(`${p}.self_attn.q_a_layernorm.weight`, [config.qLoraRank]);
67
+ q(
68
+ `${p}.self_attn.q_b_proj.weight`,
69
+ H * config.qkHeadDim,
70
+ config.qLoraRank,
71
+ );
72
+ q(
73
+ `${p}.self_attn.kv_a_proj_with_mqa.weight`,
74
+ config.kvLoraRank + config.qkRopeHeadDim,
75
+ D,
76
+ );
77
+ f(`${p}.self_attn.kv_a_layernorm.weight`, [config.kvLoraRank]);
78
+ q(
79
+ `${p}.self_attn.kv_b_proj.weight`,
80
+ H * (config.qkNopeHeadDim + config.vHeadDim),
81
+ config.kvLoraRank,
82
+ );
83
+ q(`${p}.self_attn.o_proj.weight`, D, H * config.vHeadDim);
84
+ };
85
+
86
+ const validateSparseMlp = (layer: number): void => {
87
+ const p = `model.layers.${layer}.mlp`;
88
+ f(`${p}.gate.weight`, [config.numRoutedExperts, D]);
89
+ f(`${p}.gate.e_score_correction_bias`, [config.numRoutedExperts]);
90
+ const sharedIntermediate =
91
+ config.moeIntermediateSize * config.numSharedExperts;
92
+ if (sharedIntermediate > 0) {
93
+ q(`${p}.shared_experts.gate_proj.weight`, sharedIntermediate, D);
94
+ q(`${p}.shared_experts.up_proj.weight`, sharedIntermediate, D);
95
+ q(`${p}.shared_experts.down_proj.weight`, D, sharedIntermediate);
96
+ }
97
+ for (let expert = 0; expert < config.numRoutedExperts; expert++) {
98
+ const e = `${p}.experts.${expert}`;
99
+ q(`${e}.gate_proj.weight`, config.moeIntermediateSize, D);
100
+ q(`${e}.up_proj.weight`, config.moeIntermediateSize, D);
101
+ q(`${e}.down_proj.weight`, D, config.moeIntermediateSize);
102
+ routedExperts++;
103
+ }
104
+ };
105
+
106
+ for (let layer = 0; layer < config.numHiddenLayers; layer++) {
107
+ validateAttention(layer);
108
+ const p = `model.layers.${layer}.mlp`;
109
+ if (layer < config.firstKDenseReplace) {
110
+ q(`${p}.gate_proj.weight`, config.intermediateSize, D);
111
+ q(`${p}.up_proj.weight`, config.intermediateSize, D);
112
+ q(`${p}.down_proj.weight`, D, config.intermediateSize);
113
+ } else {
114
+ validateSparseMlp(layer);
115
+ }
116
+ }
117
+
118
+ if (indexCapabilities.hasDsa) {
119
+ for (let layer = 0; layer < config.numHiddenLayers; layer++) {
120
+ if (config.indexerTypes[layer] !== "full") continue;
121
+ const p = `model.layers.${layer}.self_attn.indexer`;
122
+ q(
123
+ `${p}.wq_b.weight`,
124
+ config.indexNumHeads * config.indexHeadDim,
125
+ config.qLoraRank,
126
+ );
127
+ q(`${p}.wk.weight`, config.indexHeadDim, D);
128
+ q(`${p}.weights_proj.weight`, config.indexNumHeads, D);
129
+ f(`${p}.k_norm.weight`, [config.indexHeadDim]);
130
+ f(`${p}.k_norm.bias`, [config.indexHeadDim]);
131
+ }
132
+ }
133
+
134
+ if (indexCapabilities.hasMtp) {
135
+ const layer = config.numHiddenLayers;
136
+ const p = `model.layers.${layer}`;
137
+ validateAttention(layer);
138
+ validateSparseMlp(layer);
139
+ q(`${p}.eh_proj.weight`, D, 2 * D);
140
+ f(`${p}.enorm.weight`, [D]);
141
+ f(`${p}.hnorm.weight`, [D]);
142
+ f(`${p}.shared_head.norm.weight`, [D]);
143
+ }
144
+
145
+ return {
146
+ quantizedTensors,
147
+ floatTensors,
148
+ routedExperts,
149
+ hasDsa: indexCapabilities.hasDsa,
150
+ hasMtp: indexCapabilities.hasMtp,
151
+ };
152
+ }