@seanhogg/builderforce-memory-engine 2026.7.12 → 2026.9.27
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 +14 -0
- package/dist/bench/adaptation.d.ts +57 -0
- package/dist/bench/adaptation.d.ts.map +1 -0
- package/dist/bench/adaptation.js +167 -0
- package/dist/bench/adaptation.js.map +1 -0
- package/dist/bench/harness.d.ts +2 -0
- package/dist/bench/harness.d.ts.map +1 -1
- package/dist/bench/harness.js +1 -1
- package/dist/bench/harness.js.map +1 -1
- package/dist/bench/index.d.ts +5 -3
- package/dist/bench/index.d.ts.map +1 -1
- package/dist/bench/index.js +4 -2
- package/dist/bench/index.js.map +1 -1
- package/dist/bench/types.d.ts +88 -0
- package/dist/bench/types.d.ts.map +1 -1
- package/dist/evermind/adapt.d.ts +40 -0
- package/dist/evermind/adapt.d.ts.map +1 -0
- package/dist/evermind/adapt.js +25 -0
- package/dist/evermind/adapt.js.map +1 -0
- package/dist/evermind/delta_wire.d.ts +43 -0
- package/dist/evermind/delta_wire.d.ts.map +1 -0
- package/dist/evermind/delta_wire.js +94 -0
- package/dist/evermind/delta_wire.js.map +1 -0
- package/dist/evermind/eval.d.ts +31 -0
- package/dist/evermind/eval.d.ts.map +1 -0
- package/dist/evermind/eval.js +36 -0
- package/dist/evermind/eval.js.map +1 -0
- package/dist/evermind/index.d.ts +16 -0
- package/dist/evermind/index.d.ts.map +1 -0
- package/dist/evermind/index.js +11 -0
- package/dist/evermind/index.js.map +1 -0
- package/dist/evermind/merge.d.ts +21 -0
- package/dist/evermind/merge.d.ts.map +1 -0
- package/dist/evermind/merge.js +79 -0
- package/dist/evermind/merge.js.map +1 -0
- package/dist/{moe/moe_package.d.ts → evermind/package.d.ts} +55 -5
- package/dist/evermind/package.d.ts.map +1 -0
- package/dist/{moe/moe_package.js → evermind/package.js} +118 -19
- package/dist/evermind/package.js.map +1 -0
- package/dist/import/foreign/adapter.d.ts +59 -0
- package/dist/import/foreign/adapter.d.ts.map +1 -0
- package/dist/import/foreign/adapter.js +76 -0
- package/dist/import/foreign/adapter.js.map +1 -0
- package/dist/import/foreign/apply.d.ts +23 -0
- package/dist/import/foreign/apply.d.ts.map +1 -0
- package/dist/import/foreign/apply.js +43 -0
- package/dist/import/foreign/apply.js.map +1 -0
- package/dist/import/foreign/codestral_mamba.d.ts +57 -0
- package/dist/import/foreign/codestral_mamba.d.ts.map +1 -0
- package/dist/import/foreign/codestral_mamba.js +174 -0
- package/dist/import/foreign/codestral_mamba.js.map +1 -0
- package/dist/import/foreign/falcon_mamba.d.ts +51 -0
- package/dist/import/foreign/falcon_mamba.d.ts.map +1 -0
- package/dist/import/foreign/falcon_mamba.js +131 -0
- package/dist/import/foreign/falcon_mamba.js.map +1 -0
- package/dist/import/foreign/index.d.ts +21 -0
- package/dist/import/foreign/index.d.ts.map +1 -0
- package/dist/import/foreign/index.js +18 -0
- package/dist/import/foreign/index.js.map +1 -0
- package/dist/import/foreign/plan.d.ts +122 -0
- package/dist/import/foreign/plan.d.ts.map +1 -0
- package/dist/import/foreign/plan.js +171 -0
- package/dist/import/foreign/plan.js.map +1 -0
- package/dist/import/foreign/registry.d.ts +35 -0
- package/dist/import/foreign/registry.d.ts.map +1 -0
- package/dist/import/foreign/registry.js +89 -0
- package/dist/import/foreign/registry.js.map +1 -0
- package/dist/import/index.d.ts +7 -0
- package/dist/import/index.d.ts.map +1 -1
- package/dist/import/index.js +7 -0
- package/dist/import/index.js.map +1 -1
- package/dist/index.d.ts +29 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -9
- package/dist/index.js.map +1 -1
- package/dist/kernels/selective_scan.d.ts.map +1 -1
- package/dist/kernels/selective_scan.js +15 -9
- package/dist/kernels/selective_scan.js.map +1 -1
- package/dist/kernels/slice.d.ts +26 -0
- package/dist/kernels/slice.d.ts.map +1 -0
- package/dist/kernels/slice.js +52 -0
- package/dist/kernels/slice.js.map +1 -0
- package/dist/kernels/weight_update.d.ts.map +1 -1
- package/dist/kernels/weight_update.js +30 -4
- package/dist/kernels/weight_update.js.map +1 -1
- package/dist/lm/evermind_lm.d.ts +180 -4
- package/dist/lm/evermind_lm.d.ts.map +1 -1
- package/dist/lm/evermind_lm.js +433 -116
- package/dist/lm/evermind_lm.js.map +1 -1
- package/dist/lm/index.d.ts +8 -2
- package/dist/lm/index.d.ts.map +1 -1
- package/dist/lm/index.js +6 -1
- package/dist/lm/index.js.map +1 -1
- package/dist/lm/text_embedder.d.ts +55 -0
- package/dist/lm/text_embedder.d.ts.map +1 -0
- package/dist/lm/text_embedder.js +62 -0
- package/dist/lm/text_embedder.js.map +1 -0
- package/dist/model/attention_block.d.ts.map +1 -1
- package/dist/model/attention_block.js +9 -5
- package/dist/model/attention_block.js.map +1 -1
- package/dist/model/mamba1_block.d.ts.map +1 -1
- package/dist/model/mamba1_block.js +28 -12
- package/dist/model/mamba1_block.js.map +1 -1
- package/dist/model/mamba2_block.d.ts.map +1 -1
- package/dist/model/mamba2_block.js +14 -9
- package/dist/model/mamba2_block.js.map +1 -1
- package/dist/model/mamba3_block.d.ts.map +1 -1
- package/dist/model/mamba3_block.js +14 -9
- package/dist/model/mamba3_block.js.map +1 -1
- package/dist/moe/index.d.ts +0 -2
- package/dist/moe/index.d.ts.map +1 -1
- package/dist/moe/index.js +0 -1
- package/dist/moe/index.js.map +1 -1
- package/dist/optim/adamw.d.ts +58 -1
- package/dist/optim/adamw.d.ts.map +1 -1
- package/dist/optim/adamw.js +69 -4
- package/dist/optim/adamw.js.map +1 -1
- package/dist/tokenizer/bpe.d.ts +28 -0
- package/dist/tokenizer/bpe.d.ts.map +1 -1
- package/dist/tokenizer/bpe.js +47 -0
- package/dist/tokenizer/bpe.js.map +1 -1
- package/dist/training/cpu_ops.d.ts +44 -0
- package/dist/training/cpu_ops.d.ts.map +1 -0
- package/dist/training/cpu_ops.js +111 -0
- package/dist/training/cpu_ops.js.map +1 -0
- package/dist/training/lora.d.ts +153 -0
- package/dist/training/lora.d.ts.map +1 -0
- package/dist/training/lora.js +324 -0
- package/dist/training/lora.js.map +1 -0
- package/dist/training/mamba1_cpu.d.ts +109 -0
- package/dist/training/mamba1_cpu.d.ts.map +1 -0
- package/dist/training/mamba1_cpu.js +316 -0
- package/dist/training/mamba1_cpu.js.map +1 -0
- package/dist/training/mixed_precision.d.ts +67 -0
- package/dist/training/mixed_precision.d.ts.map +1 -0
- package/dist/training/mixed_precision.js +101 -0
- package/dist/training/mixed_precision.js.map +1 -0
- package/dist/training/model_cpu.d.ts +100 -0
- package/dist/training/model_cpu.d.ts.map +1 -0
- package/dist/training/model_cpu.js +252 -0
- package/dist/training/model_cpu.js.map +1 -0
- package/dist/training/trainer.d.ts +156 -16
- package/dist/training/trainer.d.ts.map +1 -1
- package/dist/training/trainer.js +327 -101
- package/dist/training/trainer.js.map +1 -1
- package/dist/utils/base64.d.ts +12 -0
- package/dist/utils/base64.d.ts.map +1 -0
- package/dist/utils/base64.js +27 -0
- package/dist/utils/base64.js.map +1 -0
- package/package.json +3 -1
- package/src/bench/adaptation.ts +180 -0
- package/src/bench/harness.ts +1 -1
- package/src/bench/index.ts +13 -2
- package/src/bench/types.ts +93 -0
- package/src/evermind/adapt.ts +61 -0
- package/src/evermind/delta_wire.ts +114 -0
- package/src/evermind/eval.ts +62 -0
- package/src/evermind/index.ts +33 -0
- package/src/evermind/merge.ts +106 -0
- package/src/{moe/moe_package.ts → evermind/package.ts} +148 -17
- package/src/import/foreign/adapter.ts +106 -0
- package/src/import/foreign/apply.ts +54 -0
- package/src/import/foreign/codestral_mamba.ts +224 -0
- package/src/import/foreign/falcon_mamba.ts +173 -0
- package/src/import/foreign/index.ts +37 -0
- package/src/import/foreign/plan.ts +294 -0
- package/src/import/foreign/registry.ts +126 -0
- package/src/import/index.ts +30 -0
- package/src/index.ts +104 -13
- package/src/kernels/selective_scan.ts +15 -9
- package/src/kernels/slice.ts +64 -0
- package/src/kernels/weight_update.ts +30 -4
- package/src/lm/evermind_lm.ts +470 -116
- package/src/lm/index.ts +12 -2
- package/src/lm/text_embedder.ts +84 -0
- package/src/model/attention_block.ts +9 -5
- package/src/model/mamba1_block.ts +28 -12
- package/src/model/mamba2_block.ts +14 -9
- package/src/model/mamba3_block.ts +14 -9
- package/src/moe/index.ts +0 -9
- package/src/optim/adamw.ts +115 -7
- package/src/tokenizer/bpe.ts +55 -0
- package/src/training/cpu_ops.ts +127 -0
- package/src/training/lora.ts +365 -0
- package/src/training/mamba1_cpu.ts +421 -0
- package/src/training/mixed_precision.ts +119 -0
- package/src/training/model_cpu.ts +360 -0
- package/src/training/trainer.ts +397 -154
- package/src/utils/base64.ts +28 -0
- package/dist/model/mamba_block.d.ts +0 -64
- package/dist/model/mamba_block.d.ts.map +0 -1
- package/dist/model/mamba_block.js +0 -303
- package/dist/model/mamba_block.js.map +0 -1
- package/dist/moe/moe_package.d.ts.map +0 -1
- package/dist/moe/moe_package.js.map +0 -1
- package/src/model/mamba_block.ts +0 -401
package/src/lm/index.ts
CHANGED
|
@@ -2,5 +2,15 @@
|
|
|
2
2
|
* EvermindLM — the generative language model (the runnable "AI").
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
export { EvermindLM, EvermindLMTrainer, DEFAULT_LM_CONFIG, DEFAULT_LM_SEED } from "./evermind_lm.js";
|
|
6
|
-
export type { EvermindLMConfig, LMGenerateOptions, TextCodec } from "./evermind_lm.js";
|
|
5
|
+
export { EvermindLM, EvermindLMTrainer, DEFAULT_LM_CONFIG, DEFAULT_LM_SEED, logProbOfToken } from "./evermind_lm.js";
|
|
6
|
+
export type { EvermindLMConfig, LMGenerateOptions, TextCodec, EvermindLMTrainOptions, EvermindLMDecodeState } from "./evermind_lm.js";
|
|
7
|
+
|
|
8
|
+
// CPU text→vector embedder (semantic recall with no GPU).
|
|
9
|
+
export { EvermindTextEmbedder } from "./text_embedder.js";
|
|
10
|
+
export type { TextEmbedder, EmbedderCodec } from "./text_embedder.js";
|
|
11
|
+
|
|
12
|
+
// PEFT / efficient-training toolkit (LoRA, QLoRA, mixed precision).
|
|
13
|
+
export { LoRAAdapter, EvermindLMLoRA, quantizeBase } from "../training/lora.js";
|
|
14
|
+
export type { LoRAConfig, LoRAFitOptions, BaseQuant } from "../training/lora.js";
|
|
15
|
+
export { DynamicLossScaler, roundFp16, fp16View } from "../training/mixed_precision.js";
|
|
16
|
+
export type { LossScalerOptions } from "../training/mixed_precision.js";
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lm/text_embedder.ts — text → vector, on the CPU, from an `EvermindLM`.
|
|
3
|
+
*
|
|
4
|
+
* `EvermindLM.embed()` turns TOKENS into a `dModel` vector. Every consumer that
|
|
5
|
+
* wants SEMANTIC RECALL wants it over TEXT, and needs the vector to be stable
|
|
6
|
+
* across processes, so it also needs (a) the tokenizer that produced those ids
|
|
7
|
+
* and (b) a way to tell whether a cached vector came from this exact model. This
|
|
8
|
+
* class is that pairing, in one place, so no consumer re-derives it.
|
|
9
|
+
*
|
|
10
|
+
* It deliberately satisfies the SAME structural shape the memory runtime's
|
|
11
|
+
* `embed(text)` seam expects (`MemoryStore.recallRanked(query, k, runtime)`
|
|
12
|
+
* accepts anything with an async `embed(text): Float32Array`), so a headless host
|
|
13
|
+
* can hand one of these straight to the memory layer and get embedding-ranked
|
|
14
|
+
* recall with NO GPU — the CPU LM is the whole dependency.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { EvermindLM, TextCodec } from "./evermind_lm.js";
|
|
18
|
+
import { crc32 } from "../utils/crc32.js";
|
|
19
|
+
|
|
20
|
+
/** The minimal text-embedder seam the memory/recall layers consume. */
|
|
21
|
+
export interface TextEmbedder {
|
|
22
|
+
/** Embed `text` as an L2-normalised vector of length {@link dimensions}. */
|
|
23
|
+
embed(text: string): Promise<Float32Array>;
|
|
24
|
+
/** Vector width (the model's `dModel`). */
|
|
25
|
+
readonly dimensions: number;
|
|
26
|
+
/**
|
|
27
|
+
* Identity of the model+tokenizer that produces these vectors. Vectors are only
|
|
28
|
+
* comparable within one fingerprint, so a persistent cache MUST discard entries
|
|
29
|
+
* stamped with a different one.
|
|
30
|
+
*/
|
|
31
|
+
readonly fingerprint: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A tokenizer this embedder can encode through (the engine's `BPETokenizer` fits). */
|
|
35
|
+
export type EmbedderCodec = Pick<TextCodec, "encode">;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Pairs an `EvermindLM` with the tokenizer whose ids it was trained on.
|
|
39
|
+
*
|
|
40
|
+
* Pure compute and stateless: caching belongs to whoever owns a lifetime (the
|
|
41
|
+
* memory store's in-process LRU, or a host's on-disk vector cache), not here.
|
|
42
|
+
*/
|
|
43
|
+
export class EvermindTextEmbedder implements TextEmbedder {
|
|
44
|
+
readonly dimensions: number;
|
|
45
|
+
readonly fingerprint: string;
|
|
46
|
+
|
|
47
|
+
constructor(
|
|
48
|
+
private readonly model: EvermindLM,
|
|
49
|
+
private readonly codec: EmbedderCodec,
|
|
50
|
+
/** Override the derived fingerprint (e.g. with a published model version). */
|
|
51
|
+
fingerprint?: string,
|
|
52
|
+
) {
|
|
53
|
+
this.dimensions = model.config.dModel;
|
|
54
|
+
this.fingerprint = fingerprint ?? derivedFingerprint(model);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Embed `text`. Async only to match the seam — the work is synchronous CPU, so
|
|
59
|
+
* a caller that wants the vector without a microtask can use
|
|
60
|
+
* {@link embedSync}.
|
|
61
|
+
*/
|
|
62
|
+
async embed(text: string): Promise<Float32Array> {
|
|
63
|
+
return this.embedSync(text);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The synchronous form — encode with the codec, then run the CPU LM. */
|
|
67
|
+
embedSync(text: string): Float32Array {
|
|
68
|
+
return this.model.embedText(text, this.codec as TextCodec);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Model identity: architecture plus a CRC-32 over the tied embedding table.
|
|
74
|
+
*
|
|
75
|
+
* The embedding table is the one tensor every position's vector flows through, so
|
|
76
|
+
* an adapted checkpoint (which is what a delta merge produces) always changes it —
|
|
77
|
+
* making this a cheap but honest "are these vectors still comparable?" stamp.
|
|
78
|
+
*/
|
|
79
|
+
function derivedFingerprint(model: EvermindLM): string {
|
|
80
|
+
const c = model.config;
|
|
81
|
+
const bytes = new Uint8Array(model.emb.buffer, model.emb.byteOffset, model.emb.byteLength);
|
|
82
|
+
const arch = [c.vocabSize, c.dModel, c.numLayers, c.convKernel, c.hiddenDim, c.numExperts, c.topK].join("-");
|
|
83
|
+
return `evermind-lm/${arch}/${crc32(bytes).toString(16)}`;
|
|
84
|
+
}
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
import { LINEAR_FORWARD_WGSL } from '../kernels/linear_projection.js';
|
|
37
37
|
import { gaussianArray } from '../utils/rng.js';
|
|
38
38
|
import { ACTIVATIONS_WGSL } from '../kernels/activations.js';
|
|
39
|
+
import { COL_SLICE_WGSL, COL_SLICE_ENTRY, dispatchColumnSlice } from '../kernels/slice.js';
|
|
39
40
|
|
|
40
41
|
import type { SequenceLayer, LayerForwardResult, LayerParam } from './sequence_layer.js';
|
|
41
42
|
|
|
@@ -147,6 +148,7 @@ export class AttentionBlock implements SequenceLayer {
|
|
|
147
148
|
attn_fwd: createComputePipeline(d, ATTENTION_FORWARD_WGSL, 'attention_forward'),
|
|
148
149
|
attn_val: createComputePipeline(d, ATTENTION_FORWARD_WGSL, 'attention_value'),
|
|
149
150
|
softmax : createComputePipeline(d, SOFTMAX_WGSL, 'softmax_forward'),
|
|
151
|
+
colSlice: createComputePipeline(d, COL_SLICE_WGSL, COL_SLICE_ENTRY),
|
|
150
152
|
elAdd : createComputePipeline(d, ADD_SHADER, 'main'),
|
|
151
153
|
};
|
|
152
154
|
|
|
@@ -194,11 +196,13 @@ export class AttentionBlock implements SequenceLayer {
|
|
|
194
196
|
const KBuf = createEmptyStorageBuffer(d, M * dModel * 4, true);
|
|
195
197
|
const VBuf = createEmptyStorageBuffer(d, M * dModel * 4, true);
|
|
196
198
|
{
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
d
|
|
199
|
+
// COLUMN split. `linear_forward` writes (M, 3*dModel) ROW-major, so Q, K
|
|
200
|
+
// and V are ranges of columns WITHIN every row — a strided gather, not a
|
|
201
|
+
// contiguous copy (which only coincides for M == 1).
|
|
202
|
+
const slice = this.pipelines['colSlice']!;
|
|
203
|
+
dispatchColumnSlice(d, slice, qkvOut, QBuf, M, 3 * dModel, 0, dModel);
|
|
204
|
+
dispatchColumnSlice(d, slice, qkvOut, KBuf, M, 3 * dModel, dModel, dModel);
|
|
205
|
+
dispatchColumnSlice(d, slice, qkvOut, VBuf, M, 3 * dModel, 2 * dModel, dModel);
|
|
202
206
|
}
|
|
203
207
|
qkvOut.destroy();
|
|
204
208
|
|
|
@@ -20,6 +20,7 @@ import { gaussianArray } from '../utils/rng.js';
|
|
|
20
20
|
import { CONV1D_FORWARD_WGSL } from '../kernels/conv1d.js';
|
|
21
21
|
import { LINEAR_FORWARD_WGSL } from '../kernels/linear_projection.js';
|
|
22
22
|
import { ACTIVATIONS_WGSL } from '../kernels/activations.js';
|
|
23
|
+
import { COL_SLICE_WGSL, COL_SLICE_ENTRY, dispatchColumnSlice } from '../kernels/slice.js';
|
|
23
24
|
|
|
24
25
|
import type { SequenceLayer, LayerForwardResult, LayerParam } from './sequence_layer.js';
|
|
25
26
|
|
|
@@ -210,6 +211,7 @@ export class Mamba1Block implements SequenceLayer {
|
|
|
210
211
|
rmsnorm : createComputePipeline(d, ACTIVATIONS_WGSL, 'rmsnorm_forward'),
|
|
211
212
|
scan_fwd : createComputePipeline(d, SELECTIVE_SCAN_FORWARD_WGSL, 'forward_scan'),
|
|
212
213
|
scan_reduce : createComputePipeline(d, SELECTIVE_SCAN_FORWARD_WGSL, 'forward_reduce'),
|
|
214
|
+
colSlice : createComputePipeline(d, COL_SLICE_WGSL, COL_SLICE_ENTRY),
|
|
213
215
|
elMul : createComputePipeline(d, MUL_SHADER, 'main'),
|
|
214
216
|
elAdd : createComputePipeline(d, ADD_SHADER, 'main'),
|
|
215
217
|
};
|
|
@@ -253,14 +255,18 @@ export class Mamba1Block implements SequenceLayer {
|
|
|
253
255
|
dispatchKernel(d, this.pipelines['linear']!, bg, [cdiv(M, 16), cdiv(2 * D, 16), 1]);
|
|
254
256
|
}
|
|
255
257
|
|
|
256
|
-
// 3. Split into x and z
|
|
258
|
+
// 3. Split into x and z.
|
|
259
|
+
// in_proj writes (M, 2D) ROW-major, so x and z are interleaved COLUMN
|
|
260
|
+
// slices — one per row — not two contiguous halves of the buffer. A
|
|
261
|
+
// `copyBufferToBuffer` of the first M*D floats is only the right answer
|
|
262
|
+
// when M == 1; for any real sequence it hands the block the first half of
|
|
263
|
+
// the ROWS twice. Split with a strided gather instead.
|
|
257
264
|
const xConvIn = createEmptyStorageBuffer(d, M * D * 4, true);
|
|
258
265
|
const zBuf = createEmptyStorageBuffer(d, M * D * 4, true);
|
|
259
266
|
{
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
d.queue.submit([enc.finish()]);
|
|
267
|
+
const slice = this.pipelines['colSlice']!;
|
|
268
|
+
dispatchColumnSlice(d, slice, inProjOut, xConvIn, M, 2 * D, 0, D);
|
|
269
|
+
dispatchColumnSlice(d, slice, inProjOut, zBuf, M, 2 * D, D, D);
|
|
264
270
|
}
|
|
265
271
|
inProjOut.destroy();
|
|
266
272
|
cache.zBuf = zBuf;
|
|
@@ -270,7 +276,11 @@ export class Mamba1Block implements SequenceLayer {
|
|
|
270
276
|
const convOut = createEmptyStorageBuffer(d, M * D * 4, true);
|
|
271
277
|
cache.convOut = convOut;
|
|
272
278
|
{
|
|
273
|
-
|
|
279
|
+
// ConvParams is FIVE u32s (seq_len, d_channels, kernel_size, batch,
|
|
280
|
+
// groups). A four-element uniform is smaller than the struct's minimum
|
|
281
|
+
// binding size and WebGPU rejects the bind group outright. groups = 1
|
|
282
|
+
// is standard depthwise, matching Mamba-2/3's call.
|
|
283
|
+
const params = new Uint32Array([L, D, dConv, B, 1]).buffer;
|
|
274
284
|
const pBuf = createUniformBuffer(d, params);
|
|
275
285
|
const bg = createBindGroup(d, this.pipelines['conv1d']!,
|
|
276
286
|
[pBuf, xConvIn, this.gpuWeights['wConv']!, this.gpuWeights['bConv']!, convOut]);
|
|
@@ -298,15 +308,17 @@ export class Mamba1Block implements SequenceLayer {
|
|
|
298
308
|
dispatchKernel(d, this.pipelines['linear']!, bg, [cdiv(M, 16), cdiv(R + 2 * N, 16), 1]);
|
|
299
309
|
}
|
|
300
310
|
|
|
311
|
+
// Same story as the (x, z) split: Δ, B and C are COLUMN slices of the
|
|
312
|
+
// (M, R+2N) projection, so they need a strided gather, not a prefix copy.
|
|
301
313
|
const dtRaw = createEmptyStorageBuffer(d, M * R * 4, true);
|
|
302
314
|
const B_raw = createEmptyStorageBuffer(d, B * L * N * 4, true);
|
|
303
315
|
const C_raw = createEmptyStorageBuffer(d, B * L * N * 4, true);
|
|
304
316
|
{
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
d
|
|
317
|
+
const P = R + 2 * N;
|
|
318
|
+
const slice = this.pipelines['colSlice']!;
|
|
319
|
+
dispatchColumnSlice(d, slice, xProjOut, dtRaw, M, P, 0, R);
|
|
320
|
+
dispatchColumnSlice(d, slice, xProjOut, B_raw, M, P, R, N);
|
|
321
|
+
dispatchColumnSlice(d, slice, xProjOut, C_raw, M, P, R + N, N);
|
|
310
322
|
}
|
|
311
323
|
xProjOut.destroy();
|
|
312
324
|
cache.B_raw = B_raw;
|
|
@@ -335,7 +347,11 @@ export class Mamba1Block implements SequenceLayer {
|
|
|
335
347
|
const bg1 = createBindGroup(d, this.pipelines['scan_fwd']!,
|
|
336
348
|
[pBuf, siluOut, deltaFull, this.gpuWeights['A_log']!, B_raw, C_raw,
|
|
337
349
|
this.gpuWeights['D_vec']!, scanY, hCache]);
|
|
338
|
-
|
|
350
|
+
// One workgroup per (d, n, batch): the kernel reads `wgid.x` as d and
|
|
351
|
+
// `wgid.y` as n, so the grid must be D x N x B. Dispatching
|
|
352
|
+
// ceil(D/8) x ceil(N/8) left all but a 1/64th corner of the state
|
|
353
|
+
// matrix unscanned (and unwritten in h_cache).
|
|
354
|
+
dispatchKernel(d, this.pipelines['scan_fwd']!, bg1, [D, N, B]);
|
|
339
355
|
|
|
340
356
|
const bg2 = createBindGroup(d, this.pipelines['scan_reduce']!,
|
|
341
357
|
[pBuf, siluOut, deltaFull, this.gpuWeights['A_log']!, B_raw, C_raw,
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import { SSD_FORWARD_WGSL } from '../kernels/ssd.js';
|
|
25
25
|
import { gaussianArray } from '../utils/rng.js';
|
|
26
26
|
import { CONV1D_FORWARD_WGSL } from '../kernels/conv1d.js';
|
|
27
|
+
import { COL_SLICE_WGSL, COL_SLICE_ENTRY, dispatchColumnSlice } from '../kernels/slice.js';
|
|
27
28
|
import { LINEAR_FORWARD_WGSL } from '../kernels/linear_projection.js';
|
|
28
29
|
import { ACTIVATIONS_WGSL } from '../kernels/activations.js';
|
|
29
30
|
|
|
@@ -127,6 +128,7 @@ export class Mamba2Block implements SequenceLayer {
|
|
|
127
128
|
this.pipelines = {
|
|
128
129
|
linear : createComputePipeline(d, LINEAR_FORWARD_WGSL, 'linear_forward'),
|
|
129
130
|
conv1d : createComputePipeline(d, CONV1D_FORWARD_WGSL, 'conv1d_forward'),
|
|
131
|
+
colSlice : createComputePipeline(d, COL_SLICE_WGSL, COL_SLICE_ENTRY),
|
|
130
132
|
rmsnorm : createComputePipeline(d, ACTIVATIONS_WGSL, 'rmsnorm_forward'),
|
|
131
133
|
ssd_fwd : createComputePipeline(d, SSD_FORWARD_WGSL, 'ssd_chunk_forward'),
|
|
132
134
|
elAdd : createComputePipeline(d, ADD_SHADER, 'main'),
|
|
@@ -181,10 +183,13 @@ export class Mamba2Block implements SequenceLayer {
|
|
|
181
183
|
const xConvBuf = createEmptyStorageBuffer(d, M * convD * 4, true);
|
|
182
184
|
const dtBuf = createEmptyStorageBuffer(d, M * H * 4, true);
|
|
183
185
|
{
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
// COLUMN split. `linear_forward` writes (M, convD + H) ROW-major, so each
|
|
187
|
+
// part is a range of columns WITHIN every row — a strided gather, not a
|
|
188
|
+
// contiguous copy (which only coincides for M == 1).
|
|
189
|
+
const slice = this.pipelines['colSlice']!;
|
|
190
|
+
const stride = convD + H;
|
|
191
|
+
dispatchColumnSlice(d, slice, inProjOut, xConvBuf, M, stride, 0, convD);
|
|
192
|
+
dispatchColumnSlice(d, slice, inProjOut, dtBuf, M, stride, convD, H);
|
|
188
193
|
}
|
|
189
194
|
inProjOut.destroy();
|
|
190
195
|
|
|
@@ -204,11 +209,11 @@ export class Mamba2Block implements SequenceLayer {
|
|
|
204
209
|
const bProjBuf = createEmptyStorageBuffer(d, M * G * N * 4, true);
|
|
205
210
|
const cProjBuf = createEmptyStorageBuffer(d, M * G * N * 4, true);
|
|
206
211
|
{
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
d
|
|
212
|
+
// COLUMN split of the (M, convD) conv output — same reasoning as above.
|
|
213
|
+
const slice = this.pipelines['colSlice']!;
|
|
214
|
+
dispatchColumnSlice(d, slice, convOut, xSsdBuf, M, convD, 0, D);
|
|
215
|
+
dispatchColumnSlice(d, slice, convOut, bProjBuf, M, convD, D, G * N);
|
|
216
|
+
dispatchColumnSlice(d, slice, convOut, cProjBuf, M, convD, D + G * N, G * N);
|
|
212
217
|
}
|
|
213
218
|
convOut.destroy();
|
|
214
219
|
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
import { COMPLEX_SSD_FORWARD_WGSL } from '../kernels/complex_ssd.js';
|
|
34
34
|
import { gaussianArray } from '../utils/rng.js';
|
|
35
35
|
import { CONV1D_FORWARD_WGSL } from '../kernels/conv1d.js';
|
|
36
|
+
import { COL_SLICE_WGSL, COL_SLICE_ENTRY, dispatchColumnSlice } from '../kernels/slice.js';
|
|
36
37
|
import { LINEAR_FORWARD_WGSL } from '../kernels/linear_projection.js';
|
|
37
38
|
import { ACTIVATIONS_WGSL } from '../kernels/activations.js';
|
|
38
39
|
|
|
@@ -145,6 +146,7 @@ export class Mamba3Block implements SequenceLayer {
|
|
|
145
146
|
this.pipelines = {
|
|
146
147
|
linear : createComputePipeline(d, LINEAR_FORWARD_WGSL, 'linear_forward'),
|
|
147
148
|
conv1d : createComputePipeline(d, CONV1D_FORWARD_WGSL, 'conv1d_forward'),
|
|
149
|
+
colSlice : createComputePipeline(d, COL_SLICE_WGSL, COL_SLICE_ENTRY),
|
|
148
150
|
rmsnorm : createComputePipeline(d, ACTIVATIONS_WGSL, 'rmsnorm_forward'),
|
|
149
151
|
cssd_fwd : createComputePipeline(d, COMPLEX_SSD_FORWARD_WGSL, 'complex_ssd_forward'),
|
|
150
152
|
elAdd : createComputePipeline(d, ADD_SHADER, 'main'),
|
|
@@ -198,10 +200,13 @@ export class Mamba3Block implements SequenceLayer {
|
|
|
198
200
|
const xConvBuf = createEmptyStorageBuffer(d, M * convD * 4, true);
|
|
199
201
|
const dtBuf = createEmptyStorageBuffer(d, M * H * 4, true);
|
|
200
202
|
{
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
203
|
+
// COLUMN split. `linear_forward` writes (M, convD + H) ROW-major, so each
|
|
204
|
+
// part is a range of columns WITHIN every row — a strided gather, not a
|
|
205
|
+
// contiguous copy (which only coincides for M == 1).
|
|
206
|
+
const slice = this.pipelines['colSlice']!;
|
|
207
|
+
const stride = convD + H;
|
|
208
|
+
dispatchColumnSlice(d, slice, inProjOut, xConvBuf, M, stride, 0, convD);
|
|
209
|
+
dispatchColumnSlice(d, slice, inProjOut, dtBuf, M, stride, convD, H);
|
|
205
210
|
}
|
|
206
211
|
inProjOut.destroy();
|
|
207
212
|
|
|
@@ -221,11 +226,11 @@ export class Mamba3Block implements SequenceLayer {
|
|
|
221
226
|
const bProjBuf = createEmptyStorageBuffer(d, M * G * Nc * 2 * 4, true);
|
|
222
227
|
const cProjBuf = createEmptyStorageBuffer(d, M * G * Nc * 2 * 4, true);
|
|
223
228
|
{
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
d
|
|
229
|
+
// COLUMN split of the (M, convD) conv output — same reasoning as above.
|
|
230
|
+
const slice = this.pipelines['colSlice']!;
|
|
231
|
+
dispatchColumnSlice(d, slice, convOut, xSsdBuf, M, convD, 0, D);
|
|
232
|
+
dispatchColumnSlice(d, slice, convOut, bProjBuf, M, convD, D, G * Nc * 2);
|
|
233
|
+
dispatchColumnSlice(d, slice, convOut, cProjBuf, M, convD, D + G * Nc * 2, G * Nc * 2);
|
|
229
234
|
}
|
|
230
235
|
convOut.destroy();
|
|
231
236
|
|
package/src/moe/index.ts
CHANGED
|
@@ -13,12 +13,3 @@ export type { MoEConfig, MoEParam, RouteResult } from "./moe_model.js";
|
|
|
13
13
|
export { MoETrainer } from "./moe_trainer.js";
|
|
14
14
|
export type { MoESample, MoETrainOptions, MoEEpochResult } from "./moe_trainer.js";
|
|
15
15
|
|
|
16
|
-
export { EvermindModelPackage } from "./moe_package.js";
|
|
17
|
-
export type {
|
|
18
|
-
EvermindModelManifest,
|
|
19
|
-
EvermindModelCard,
|
|
20
|
-
EvermindModelType,
|
|
21
|
-
EvermindModality,
|
|
22
|
-
PackageMeta,
|
|
23
|
-
ValidationResult,
|
|
24
|
-
} from "./moe_package.js";
|
package/src/optim/adamw.ts
CHANGED
|
@@ -15,19 +15,42 @@ export interface OptimTarget {
|
|
|
15
15
|
gradients(): OptimParam[];
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Optimizer-state sharding (ZeRO-1 analog). When set, this optimiser instance
|
|
20
|
+
* OWNS only parameter tensors where `paramIndex % count === index`, and only
|
|
21
|
+
* allocates the AdamW moments (m, v — 2× the parameter bytes, the dominant cost
|
|
22
|
+
* of full fine-tuning per the cookbook's memory accounting) for its shard.
|
|
23
|
+
*
|
|
24
|
+
* Run `count` instances over the SAME model, one per `index`, and the union of
|
|
25
|
+
* their steps equals one unsharded step — but each holds only 1/count of the
|
|
26
|
+
* optimizer state. That is exactly how FSDP/ZeRO fits a larger model on the same
|
|
27
|
+
* hardware; here it is the seam a multi-device trainer partitions on, and even
|
|
28
|
+
* single-process it caps resident moment memory.
|
|
29
|
+
*/
|
|
30
|
+
export interface ShardSpec {
|
|
31
|
+
/** This shard's rank, 0-based. */
|
|
32
|
+
index: number;
|
|
33
|
+
/** Total number of shards. */
|
|
34
|
+
count: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
18
37
|
export interface AdamWOptions {
|
|
19
38
|
lr?: number;
|
|
20
39
|
beta1?: number;
|
|
21
40
|
beta2?: number;
|
|
22
41
|
eps?: number;
|
|
23
42
|
weightDecay?: number;
|
|
43
|
+
/** Optimizer-state sharding. Omit for the full (single-owner) optimiser. */
|
|
44
|
+
shard?: ShardSpec;
|
|
24
45
|
}
|
|
25
46
|
|
|
26
47
|
export class AdamW {
|
|
27
|
-
|
|
28
|
-
private readonly
|
|
48
|
+
// Per-parameter moments, or null for tensors this shard does not own (no memory).
|
|
49
|
+
private readonly m: (Float32Array | null)[] = [];
|
|
50
|
+
private readonly v: (Float32Array | null)[] = [];
|
|
29
51
|
private t = 0;
|
|
30
|
-
private readonly opt: Required<AdamWOptions
|
|
52
|
+
private readonly opt: Required<Omit<AdamWOptions, "shard">>;
|
|
53
|
+
private readonly shard: ShardSpec | null;
|
|
31
54
|
|
|
32
55
|
constructor(
|
|
33
56
|
private readonly target: OptimTarget,
|
|
@@ -40,13 +63,29 @@ export class AdamW {
|
|
|
40
63
|
eps: options.eps ?? 1e-8,
|
|
41
64
|
weightDecay: options.weightDecay ?? 0,
|
|
42
65
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
66
|
+
const shard = options.shard ?? null;
|
|
67
|
+
if (shard && (shard.count <= 0 || shard.index < 0 || shard.index >= shard.count)) {
|
|
68
|
+
throw new Error(`AdamW: invalid shard ${shard.index}/${shard.count}`);
|
|
46
69
|
}
|
|
70
|
+
this.shard = shard;
|
|
71
|
+
const params = target.parameters();
|
|
72
|
+
for (let p = 0; p < params.length; p++) {
|
|
73
|
+
if (this._owns(p)) {
|
|
74
|
+
this.m.push(new Float32Array(params[p]!.data.length));
|
|
75
|
+
this.v.push(new Float32Array(params[p]!.data.length));
|
|
76
|
+
} else {
|
|
77
|
+
this.m.push(null);
|
|
78
|
+
this.v.push(null);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Whether this shard owns (and updates) parameter tensor `p`. */
|
|
84
|
+
private _owns(p: number): boolean {
|
|
85
|
+
return this.shard === null || p % this.shard.count === this.shard.index;
|
|
47
86
|
}
|
|
48
87
|
|
|
49
|
-
/** One optimiser step from the currently-accumulated gradients. */
|
|
88
|
+
/** One optimiser step from the currently-accumulated gradients (owned tensors only). */
|
|
50
89
|
step(): void {
|
|
51
90
|
this.t++;
|
|
52
91
|
const { lr, beta1, beta2, eps, weightDecay } = this.opt;
|
|
@@ -55,6 +94,7 @@ export class AdamW {
|
|
|
55
94
|
const bc1 = 1 - Math.pow(beta1, this.t);
|
|
56
95
|
const bc2 = 1 - Math.pow(beta2, this.t);
|
|
57
96
|
for (let p = 0; p < params.length; p++) {
|
|
97
|
+
if (!this._owns(p)) continue;
|
|
58
98
|
const w = params[p]!.data;
|
|
59
99
|
const g = grads[p]!.data;
|
|
60
100
|
const m = this.m[p]!;
|
|
@@ -69,4 +109,72 @@ export class AdamW {
|
|
|
69
109
|
}
|
|
70
110
|
}
|
|
71
111
|
}
|
|
112
|
+
|
|
113
|
+
/** Bytes of optimizer state this shard holds (m + v) — 1/count of the full state. */
|
|
114
|
+
stateBytes(): number {
|
|
115
|
+
let n = 0;
|
|
116
|
+
for (const arr of this.m) if (arr) n += arr.byteLength;
|
|
117
|
+
for (const arr of this.v) if (arr) n += arr.byteLength;
|
|
118
|
+
return n;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── The CPU mirror of WEIGHT_UPDATE_WGSL ─────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
/** Hyperparameters of one {@link adamwUpdateInPlace} step. */
|
|
125
|
+
export interface AdamWKernelStep {
|
|
126
|
+
lr: number;
|
|
127
|
+
beta1: number;
|
|
128
|
+
beta2: number;
|
|
129
|
+
eps: number;
|
|
130
|
+
weightDecay: number;
|
|
131
|
+
/** beta1^t — the precomputed bias-correction term (matches the kernel uniform). */
|
|
132
|
+
beta1_t: number;
|
|
133
|
+
/** beta2^t. */
|
|
134
|
+
beta2_t: number;
|
|
135
|
+
/** Trust region: max |Δθ| per step. 0 disables the bound. */
|
|
136
|
+
maxDelta: number;
|
|
137
|
+
/** Global gradient-clip factor, applied to `grad` on the fly (1 = no clipping). */
|
|
138
|
+
gradScale?: number;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* One AdamW step over a single parameter tensor — the exact CPU mirror of the
|
|
143
|
+
* `adamw_update` entry point in `WEIGHT_UPDATE_WGSL`, including its two safety
|
|
144
|
+
* properties:
|
|
145
|
+
*
|
|
146
|
+
* • the **NaN/Inf guard** — a non-finite step is dropped rather than written
|
|
147
|
+
* into a weight, so one bad gradient cannot permanently poison the model;
|
|
148
|
+
* • the **trust region** — `maxDelta` bounds how far a single step can move a
|
|
149
|
+
* weight, which is what keeps repeated write-through adapts reversible.
|
|
150
|
+
*
|
|
151
|
+
* Decoupled weight decay (`θ * (1 - lr·wd)`), matching the kernel — deliberately
|
|
152
|
+
* NOT the coupled form {@link AdamW} uses for the MoE/EvermindLM trainers.
|
|
153
|
+
*
|
|
154
|
+
* `param`, `m` and `v` are updated in place.
|
|
155
|
+
*/
|
|
156
|
+
export function adamwUpdateInPlace(
|
|
157
|
+
param: Float32Array,
|
|
158
|
+
grad: Float32Array,
|
|
159
|
+
m: Float32Array,
|
|
160
|
+
v: Float32Array,
|
|
161
|
+
hp: AdamWKernelStep,
|
|
162
|
+
): void {
|
|
163
|
+
const { lr, beta1, beta2, eps, weightDecay, beta1_t, beta2_t, maxDelta } = hp;
|
|
164
|
+
const gradScale = hp.gradScale ?? 1;
|
|
165
|
+
const bc1 = 1 - beta1_t;
|
|
166
|
+
const bc2 = 1 - beta2_t;
|
|
167
|
+
for (let i = 0; i < param.length; i++) {
|
|
168
|
+
const g = grad[i]! * gradScale;
|
|
169
|
+
const mNew = beta1 * m[i]! + (1 - beta1) * g;
|
|
170
|
+
const vNew = beta2 * v[i]! + (1 - beta2) * g * g;
|
|
171
|
+
m[i] = mNew;
|
|
172
|
+
v[i] = vNew;
|
|
173
|
+
|
|
174
|
+
let step = (lr * (mNew / bc1)) / (Math.sqrt(vNew / bc2) + eps);
|
|
175
|
+
if (!Number.isFinite(step)) step = 0;
|
|
176
|
+
if (maxDelta > 0) step = Math.min(maxDelta, Math.max(-maxDelta, step));
|
|
177
|
+
|
|
178
|
+
param[i] = param[i]! * (1 - lr * weightDecay) - step;
|
|
179
|
+
}
|
|
72
180
|
}
|
package/src/tokenizer/bpe.ts
CHANGED
|
@@ -52,6 +52,14 @@ const BYTE_DECODER = new Map([...BYTE_ENCODER].map(([k, v]) => [v, k]));
|
|
|
52
52
|
const PRE_TOKENIZE_RE =
|
|
53
53
|
/(?:'s|'t|'re|'ve|'m|'ll|'d)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+/gu;
|
|
54
54
|
|
|
55
|
+
/** A round-trippable snapshot of a {@link BPETokenizer} — see `toObject`/`deserialize`. */
|
|
56
|
+
export interface BPETokenizerSpec {
|
|
57
|
+
vocab: Record<string, number>;
|
|
58
|
+
/** Merge rules in rank order ("a b" strings). */
|
|
59
|
+
merges: string[];
|
|
60
|
+
specials?: { bos?: string; eos?: string; pad?: string; unk?: string };
|
|
61
|
+
}
|
|
62
|
+
|
|
55
63
|
export class BPETokenizer {
|
|
56
64
|
vocab: Map<string, number>;
|
|
57
65
|
idToToken: Map<number, string>;
|
|
@@ -225,6 +233,53 @@ export class BPETokenizer {
|
|
|
225
233
|
this.loadFromObjects(vocabObj, mergeArr);
|
|
226
234
|
}
|
|
227
235
|
|
|
236
|
+
/**
|
|
237
|
+
* The tokenizer as a plain, JSON-serialisable object: vocab, merges and the
|
|
238
|
+
* four special-token strings.
|
|
239
|
+
*
|
|
240
|
+
* This is what lets an `.evermind` package EMBED its tokenizer instead of
|
|
241
|
+
* expecting a consumer to source the matching vocab separately — token ids are
|
|
242
|
+
* meaningless without the exact vocabulary that produced them, so shipping a
|
|
243
|
+
* checkpoint without one is shipping half an artifact.
|
|
244
|
+
*/
|
|
245
|
+
toObject(): BPETokenizerSpec {
|
|
246
|
+
return {
|
|
247
|
+
vocab: Object.fromEntries(this.vocab),
|
|
248
|
+
merges: [...this.merges.entries()].sort((a, b) => a[1] - b[1]).map(([m]) => m),
|
|
249
|
+
specials: { bos: this.bosToken, eos: this.eosToken, pad: this.padToken, unk: this.unkToken },
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Restore a tokenizer from {@link toObject}. */
|
|
254
|
+
loadFromSpec(spec: BPETokenizerSpec): void {
|
|
255
|
+
const sp = spec.specials;
|
|
256
|
+
if (sp?.bos) this.bosToken = sp.bos;
|
|
257
|
+
if (sp?.eos) this.eosToken = sp.eos;
|
|
258
|
+
if (sp?.pad) this.padToken = sp.pad;
|
|
259
|
+
if (sp?.unk) this.unkToken = sp.unk;
|
|
260
|
+
this.loadFromObjects(spec.vocab, spec.merges);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** UTF-8 JSON bytes of {@link toObject} — the `.evermind` tokenizer section. */
|
|
264
|
+
serialize(): ArrayBuffer {
|
|
265
|
+
const bytes = new TextEncoder().encode(JSON.stringify(this.toObject()));
|
|
266
|
+
// Copy into a standalone ArrayBuffer (the view may be a slice of a pool).
|
|
267
|
+
const out = new ArrayBuffer(bytes.byteLength);
|
|
268
|
+
new Uint8Array(out).set(bytes);
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** Inverse of {@link serialize}. */
|
|
273
|
+
static deserialize(buffer: ArrayBuffer): BPETokenizer {
|
|
274
|
+
const spec = JSON.parse(new TextDecoder().decode(new Uint8Array(buffer))) as BPETokenizerSpec;
|
|
275
|
+
if (!spec || typeof spec.vocab !== 'object' || !Array.isArray(spec.merges)) {
|
|
276
|
+
throw new Error('BPETokenizer.deserialize: not a serialised BPE tokenizer (expected { vocab, merges })');
|
|
277
|
+
}
|
|
278
|
+
const tok = new BPETokenizer();
|
|
279
|
+
tok.loadFromSpec(spec);
|
|
280
|
+
return tok;
|
|
281
|
+
}
|
|
282
|
+
|
|
228
283
|
/**
|
|
229
284
|
* Fetch and import a Hugging Face `tokenizer.json` by URL — the IDE's
|
|
230
285
|
* "import merges from a repo" path. Resolves once the tokenizer is loaded.
|