@holoscript/holo-runtime 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/index.cjs +615 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +169 -0
- package/dist/index.d.ts +169 -0
- package/dist/index.js +596 -0
- package/dist/index.js.map +1 -0
- package/package.json +58 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
import { homedir } from 'os';
|
|
2
|
+
import { resolve, join, isAbsolute } from 'path';
|
|
3
|
+
import { pathToFileURL } from 'url';
|
|
4
|
+
|
|
5
|
+
// src/index.ts
|
|
6
|
+
var HOLO_RUNTIME_SCHEMA = "holoscript.holo-runtime.m1";
|
|
7
|
+
var HOLO_RUNNER_TOKENIZER_MODULE = "scripts/holorunner-tokenizer-v0.mjs";
|
|
8
|
+
var HOLO_RUNTIME_MODEL_FLEET_NODE_KIND = {
|
|
9
|
+
kind: "holo-runtime-m1-cpu-decoder",
|
|
10
|
+
package: "@holoscript/holo-runtime",
|
|
11
|
+
backend: "pure-ts-cpu",
|
|
12
|
+
checkpointFamily: "holorunner-s0-state-dict",
|
|
13
|
+
tokenizer: "holorunner-tokenizer-v0.mjs direct import",
|
|
14
|
+
role: "sovereign decoder seed under @model_fleet"
|
|
15
|
+
};
|
|
16
|
+
var SPECIAL_COUNT = 6;
|
|
17
|
+
var BYTE_BASE = SPECIAL_COUNT;
|
|
18
|
+
var MERGE_BASE = BYTE_BASE + 256;
|
|
19
|
+
var DEFAULT_EPSILON = 1e-5;
|
|
20
|
+
var HoloRuntimeKvCache = class {
|
|
21
|
+
constructor(nLayer, blockSize, nEmbd) {
|
|
22
|
+
this.nLayer = nLayer;
|
|
23
|
+
this.blockSize = blockSize;
|
|
24
|
+
this.nEmbd = nEmbd;
|
|
25
|
+
this.layers = Array.from({ length: nLayer }, () => ({
|
|
26
|
+
keys: new Float32Array(blockSize * nEmbd),
|
|
27
|
+
values: new Float32Array(blockSize * nEmbd),
|
|
28
|
+
length: 0
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
nLayer;
|
|
32
|
+
blockSize;
|
|
33
|
+
nEmbd;
|
|
34
|
+
layers;
|
|
35
|
+
reset() {
|
|
36
|
+
for (const layer of this.layers) {
|
|
37
|
+
layer.keys.fill(0);
|
|
38
|
+
layer.values.fill(0);
|
|
39
|
+
layer.length = 0;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
append(layerIndex, key, value) {
|
|
43
|
+
const layer = this.layers[layerIndex];
|
|
44
|
+
if (!layer) throw new Error(`KV cache layer ${layerIndex} is out of range`);
|
|
45
|
+
if (key.length !== this.nEmbd || value.length !== this.nEmbd) {
|
|
46
|
+
throw new Error(`KV cache vectors must have ${this.nEmbd} elements`);
|
|
47
|
+
}
|
|
48
|
+
if (layer.length >= this.blockSize) {
|
|
49
|
+
throw new Error(`KV cache exceeded block size ${this.blockSize}`);
|
|
50
|
+
}
|
|
51
|
+
const offset = layer.length * this.nEmbd;
|
|
52
|
+
layer.keys.set(key, offset);
|
|
53
|
+
layer.values.set(value, offset);
|
|
54
|
+
layer.length += 1;
|
|
55
|
+
return layer.length - 1;
|
|
56
|
+
}
|
|
57
|
+
key(layerIndex, tokenIndex) {
|
|
58
|
+
return this.slice("keys", layerIndex, tokenIndex);
|
|
59
|
+
}
|
|
60
|
+
value(layerIndex, tokenIndex) {
|
|
61
|
+
return this.slice("values", layerIndex, tokenIndex);
|
|
62
|
+
}
|
|
63
|
+
slice(which, layerIndex, tokenIndex) {
|
|
64
|
+
const layer = this.layers[layerIndex];
|
|
65
|
+
if (!layer) throw new Error(`KV cache layer ${layerIndex} is out of range`);
|
|
66
|
+
if (tokenIndex < 0 || tokenIndex >= layer.length) {
|
|
67
|
+
throw new Error(`KV cache token ${tokenIndex} is out of range for layer ${layerIndex}`);
|
|
68
|
+
}
|
|
69
|
+
const offset = tokenIndex * this.nEmbd;
|
|
70
|
+
return layer[which].slice(offset, offset + this.nEmbd);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
var HoloRuntimeDecoder = class {
|
|
74
|
+
constructor(loaded) {
|
|
75
|
+
this.loaded = loaded;
|
|
76
|
+
this.config = loaded.config;
|
|
77
|
+
this.weights = loaded.weights;
|
|
78
|
+
}
|
|
79
|
+
loaded;
|
|
80
|
+
config;
|
|
81
|
+
weights;
|
|
82
|
+
createKvCache() {
|
|
83
|
+
return new HoloRuntimeKvCache(this.config.nLayer, this.config.blockSize, this.config.nEmbd);
|
|
84
|
+
}
|
|
85
|
+
forward(inputIds) {
|
|
86
|
+
const { config, weights } = this;
|
|
87
|
+
assertTokenWindow(inputIds, config);
|
|
88
|
+
const tokens = inputIds.length;
|
|
89
|
+
let x = new Float32Array(tokens * config.nEmbd);
|
|
90
|
+
for (let tokenIndex = 0; tokenIndex < tokens; tokenIndex += 1) {
|
|
91
|
+
const tokenId = inputIds[tokenIndex] ?? 0;
|
|
92
|
+
const token = embeddingRow(weights.tokWeight, tokenId, config.nEmbd);
|
|
93
|
+
const pos = embeddingRow(weights.posWeight, tokenIndex, config.nEmbd);
|
|
94
|
+
for (let column = 0; column < config.nEmbd; column += 1) {
|
|
95
|
+
x[tokenIndex * config.nEmbd + column] = token[column] + pos[column];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const block of weights.blocks) {
|
|
99
|
+
const h1 = layerNormSequence(x, tokens, config.nEmbd, block.ln1Weight, block.ln1Bias);
|
|
100
|
+
const attn = causalSelfAttentionSequence(h1, tokens, config, block);
|
|
101
|
+
addInPlace(x, attn);
|
|
102
|
+
const h2 = layerNormSequence(x, tokens, config.nEmbd, block.ln2Weight, block.ln2Bias);
|
|
103
|
+
const mlpHidden = linearSequence(h2, tokens, config.nEmbd, block.mlp0Weight, block.mlp0Bias);
|
|
104
|
+
for (let index = 0; index < mlpHidden.length; index += 1) {
|
|
105
|
+
mlpHidden[index] = geluErf(mlpHidden[index] ?? 0);
|
|
106
|
+
}
|
|
107
|
+
const mlpOut = linearSequence(mlpHidden, tokens, config.nEmbd * 4, block.mlp2Weight, block.mlp2Bias);
|
|
108
|
+
addInPlace(x, mlpOut);
|
|
109
|
+
}
|
|
110
|
+
const normalized = layerNormSequence(x, tokens, config.nEmbd, weights.lnfWeight, weights.lnfBias);
|
|
111
|
+
const logits = linearSequence(normalized, tokens, config.nEmbd, weights.headWeight);
|
|
112
|
+
return { logits, shape: [tokens, config.vocabSize] };
|
|
113
|
+
}
|
|
114
|
+
logitsForLastToken(inputIds) {
|
|
115
|
+
const result = this.forward(inputIds);
|
|
116
|
+
const [, vocab] = result.shape;
|
|
117
|
+
return result.logits.slice(result.logits.length - vocab);
|
|
118
|
+
}
|
|
119
|
+
decodeToken(tokenId, cache, position = cache.layers[0]?.length ?? 0) {
|
|
120
|
+
const { config, weights } = this;
|
|
121
|
+
assertTokenId(tokenId, config.vocabSize);
|
|
122
|
+
if (position < 0 || position >= config.blockSize) {
|
|
123
|
+
throw new Error(`position ${position} is outside block size ${config.blockSize}`);
|
|
124
|
+
}
|
|
125
|
+
let x = addVectors(
|
|
126
|
+
embeddingRow(weights.tokWeight, tokenId, config.nEmbd),
|
|
127
|
+
embeddingRow(weights.posWeight, position, config.nEmbd)
|
|
128
|
+
);
|
|
129
|
+
for (let layerIndex = 0; layerIndex < weights.blocks.length; layerIndex += 1) {
|
|
130
|
+
const block = weights.blocks[layerIndex];
|
|
131
|
+
const h1 = layerNormVector(x, block.ln1Weight, block.ln1Bias);
|
|
132
|
+
const qkv = linearVector(h1, block.attnInProjWeight, block.attnInProjBias);
|
|
133
|
+
const query = qkv.slice(0, config.nEmbd);
|
|
134
|
+
const key = qkv.slice(config.nEmbd, config.nEmbd * 2);
|
|
135
|
+
const value = qkv.slice(config.nEmbd * 2, config.nEmbd * 3);
|
|
136
|
+
cache.append(layerIndex, key, value);
|
|
137
|
+
const attn = cachedSelfAttention(query, cache, layerIndex, config);
|
|
138
|
+
const projected = linearVector(attn, block.attnOutProjWeight, block.attnOutProjBias);
|
|
139
|
+
x = addVectors(x, projected);
|
|
140
|
+
const h2 = layerNormVector(x, block.ln2Weight, block.ln2Bias);
|
|
141
|
+
const hidden = linearVector(h2, block.mlp0Weight, block.mlp0Bias);
|
|
142
|
+
for (let index = 0; index < hidden.length; index += 1) {
|
|
143
|
+
hidden[index] = geluErf(hidden[index] ?? 0);
|
|
144
|
+
}
|
|
145
|
+
x = addVectors(x, linearVector(hidden, block.mlp2Weight, block.mlp2Bias));
|
|
146
|
+
}
|
|
147
|
+
return linearVector(layerNormVector(x, weights.lnfWeight, weights.lnfBias), weights.headWeight);
|
|
148
|
+
}
|
|
149
|
+
prefill(inputIds, cache = this.createKvCache()) {
|
|
150
|
+
assertTokenWindow(inputIds, this.config);
|
|
151
|
+
let logits = this.decodeToken(inputIds[0] ?? 0, cache, 0);
|
|
152
|
+
for (let index = 1; index < inputIds.length; index += 1) {
|
|
153
|
+
logits = this.decodeToken(inputIds[index] ?? 0, cache, index);
|
|
154
|
+
}
|
|
155
|
+
return { logits, cache };
|
|
156
|
+
}
|
|
157
|
+
generate(inputIds, options) {
|
|
158
|
+
const ids = [...inputIds];
|
|
159
|
+
const cache = this.createKvCache();
|
|
160
|
+
let logits = this.prefill(ids, cache).logits;
|
|
161
|
+
const stopTokenIds = new Set(options.stopTokenIds ?? []);
|
|
162
|
+
for (let step = 0; step < options.maxNewTokens; step += 1) {
|
|
163
|
+
const sample = sampleFromLogits(logits, options);
|
|
164
|
+
ids.push(sample.tokenId);
|
|
165
|
+
if (stopTokenIds.has(sample.tokenId)) break;
|
|
166
|
+
logits = this.decodeToken(sample.tokenId, cache);
|
|
167
|
+
}
|
|
168
|
+
return ids;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
function loadHoloRunnerS0StateDict(input) {
|
|
172
|
+
const config = normalizeConfig(input.config);
|
|
173
|
+
const state = input.state ?? input.model;
|
|
174
|
+
if (!state) throw new Error("HoloRunner S0 checkpoint requires state or model tensor map");
|
|
175
|
+
const weights = loadWeights(config, state);
|
|
176
|
+
return {
|
|
177
|
+
schema: HOLO_RUNTIME_SCHEMA,
|
|
178
|
+
config,
|
|
179
|
+
weights,
|
|
180
|
+
sourceKeys: Object.keys(state).sort(),
|
|
181
|
+
tokenizer: input.tokenizer
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function loadHoloRunnerS0Checkpoint(raw) {
|
|
185
|
+
if (!isRecord(raw)) throw new Error("checkpoint must be an object");
|
|
186
|
+
const configValue = raw.config;
|
|
187
|
+
if (!isRecord(configValue)) throw new Error("checkpoint.config must be an object");
|
|
188
|
+
const vocabSize = numberField(raw, "vocab_size") ?? numberField(configValue, "vocab_size");
|
|
189
|
+
const config = normalizeConfig({ ...configValue, vocab_size: vocabSize });
|
|
190
|
+
const model = raw.model;
|
|
191
|
+
const state = raw.state;
|
|
192
|
+
if (!isTensorMap(model) && !isTensorMap(state)) {
|
|
193
|
+
throw new Error("checkpoint requires model or state tensor map");
|
|
194
|
+
}
|
|
195
|
+
return loadHoloRunnerS0StateDict({ config, state: isTensorMap(state) ? state : void 0, model: isTensorMap(model) ? model : void 0 });
|
|
196
|
+
}
|
|
197
|
+
async function loadHoloRunnerTokenizer(options = {}) {
|
|
198
|
+
const modulePath = resolveTokenizerModulePath(options.modulePath);
|
|
199
|
+
const moduleRecord = await import(pathToFileURL(modulePath).href);
|
|
200
|
+
const encode = moduleRecord.encode;
|
|
201
|
+
const decode = moduleRecord.decode;
|
|
202
|
+
if (typeof encode !== "function" || typeof decode !== "function") {
|
|
203
|
+
throw new Error(`Tokenizer module ${modulePath} must export encode and decode functions`);
|
|
204
|
+
}
|
|
205
|
+
const tokenizerModule = {
|
|
206
|
+
encode,
|
|
207
|
+
decode
|
|
208
|
+
};
|
|
209
|
+
return {
|
|
210
|
+
modulePath,
|
|
211
|
+
encode: tokenizerModule.encode,
|
|
212
|
+
decode: tokenizerModule.decode,
|
|
213
|
+
encodeTextToIds(text, merges) {
|
|
214
|
+
return tokenIdsFromEncodedTokens(tokenizerModule.encode(text, merges), merges);
|
|
215
|
+
},
|
|
216
|
+
decodeIdsToText(ids, merges) {
|
|
217
|
+
return tokenizerModule.decode(encodedTokensFromTokenIds(ids, merges));
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function resolveTokenizerModulePath(modulePath) {
|
|
222
|
+
if (modulePath) return resolve(modulePath);
|
|
223
|
+
const ecosystemRoot = process.env.HOLOAI_ECOSYSTEM_ROOT ? resolve(process.env.HOLOAI_ECOSYSTEM_ROOT) : join(homedir(), ".ai-ecosystem");
|
|
224
|
+
const candidate = join(ecosystemRoot, HOLO_RUNNER_TOKENIZER_MODULE);
|
|
225
|
+
return isAbsolute(candidate) ? candidate : resolve(candidate);
|
|
226
|
+
}
|
|
227
|
+
function tokenIdsFromEncodedTokens(tokens, merges) {
|
|
228
|
+
const mergeIds = new Map(merges.map((merge, index) => [merge[2], index]));
|
|
229
|
+
return tokens.map((token) => {
|
|
230
|
+
const mergeId = mergeIds.get(token.symbol);
|
|
231
|
+
if (mergeId !== void 0) return MERGE_BASE + mergeId;
|
|
232
|
+
const byte = Number(token.symbol);
|
|
233
|
+
if (Number.isInteger(byte) && byte >= 0 && byte <= 255) return BYTE_BASE + byte;
|
|
234
|
+
throw new Error(`Cannot map tokenizer symbol ${token.symbol} to an S0 token id`);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
function encodedTokensFromTokenIds(ids, merges) {
|
|
238
|
+
const tokens = [];
|
|
239
|
+
for (const id of ids) {
|
|
240
|
+
if (id < SPECIAL_COUNT) continue;
|
|
241
|
+
if (id >= BYTE_BASE && id < MERGE_BASE) {
|
|
242
|
+
tokens.push({ symbol: String(id - BYTE_BASE) });
|
|
243
|
+
} else if (id >= MERGE_BASE) {
|
|
244
|
+
const merge = merges[id - MERGE_BASE];
|
|
245
|
+
if (!merge) throw new Error(`merge token id ${id} has no merge entry`);
|
|
246
|
+
tokens.push({ symbol: merge[2] });
|
|
247
|
+
} else {
|
|
248
|
+
throw new Error(`token id ${id} is not decodable by HoloTokenizer-v0`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return tokens;
|
|
252
|
+
}
|
|
253
|
+
function sampleFromLogits(logits, options = {}) {
|
|
254
|
+
const temperature = options.temperature ?? 1;
|
|
255
|
+
const topK = options.topK ?? 0;
|
|
256
|
+
const rng = options.rng ?? Math.random;
|
|
257
|
+
if (logits.length === 0) throw new Error("Cannot sample from empty logits");
|
|
258
|
+
if (temperature <= 0) {
|
|
259
|
+
let tokenId = 0;
|
|
260
|
+
for (let index = 1; index < logits.length; index += 1) {
|
|
261
|
+
if ((logits[index] ?? -Infinity) > (logits[tokenId] ?? -Infinity)) tokenId = index;
|
|
262
|
+
}
|
|
263
|
+
return { tokenId, probability: 1, candidates: [{ tokenId, probability: 1, logit: logits[tokenId] ?? 0 }] };
|
|
264
|
+
}
|
|
265
|
+
const sorted = Array.from(logits, (logit, tokenId) => ({ tokenId, logit: logit / temperature })).sort(
|
|
266
|
+
(left, right) => right.logit - left.logit
|
|
267
|
+
);
|
|
268
|
+
const candidates = topK > 0 ? sorted.slice(0, Math.max(1, topK)) : sorted;
|
|
269
|
+
const probabilities = softmaxArray(candidates.map((candidate) => candidate.logit));
|
|
270
|
+
let cursor = Math.min(Math.max(rng(), 0), 0.999999999);
|
|
271
|
+
for (let index = 0; index < candidates.length; index += 1) {
|
|
272
|
+
cursor -= probabilities[index] ?? 0;
|
|
273
|
+
if (cursor <= 0 || index === candidates.length - 1) {
|
|
274
|
+
const candidate = candidates[index];
|
|
275
|
+
return {
|
|
276
|
+
tokenId: candidate.tokenId,
|
|
277
|
+
probability: probabilities[index] ?? 0,
|
|
278
|
+
candidates: candidates.map((item, candidateIndex) => ({
|
|
279
|
+
tokenId: item.tokenId,
|
|
280
|
+
logit: item.logit,
|
|
281
|
+
probability: probabilities[candidateIndex] ?? 0
|
|
282
|
+
}))
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
throw new Error("sampling failed to select a token");
|
|
287
|
+
}
|
|
288
|
+
function createSeededRng(seed) {
|
|
289
|
+
let state = seed >>> 0;
|
|
290
|
+
return () => {
|
|
291
|
+
state = state + 1831565813 >>> 0;
|
|
292
|
+
let value = state;
|
|
293
|
+
value = Math.imul(value ^ value >>> 15, value | 1);
|
|
294
|
+
value ^= value + Math.imul(value ^ value >>> 7, value | 61);
|
|
295
|
+
return ((value ^ value >>> 14) >>> 0) / 4294967296;
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function matmul(left, leftRows, leftCols, right, rightCols) {
|
|
299
|
+
if (left.length !== leftRows * leftCols) throw new Error("left matrix shape does not match data length");
|
|
300
|
+
if (right.length !== leftCols * rightCols) throw new Error("right matrix shape does not match data length");
|
|
301
|
+
const out = new Float32Array(leftRows * rightCols);
|
|
302
|
+
for (let row = 0; row < leftRows; row += 1) {
|
|
303
|
+
for (let column = 0; column < rightCols; column += 1) {
|
|
304
|
+
let sum = 0;
|
|
305
|
+
for (let inner = 0; inner < leftCols; inner += 1) {
|
|
306
|
+
sum += (left[row * leftCols + inner] ?? 0) * (right[inner * rightCols + column] ?? 0);
|
|
307
|
+
}
|
|
308
|
+
out[row * rightCols + column] = sum;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return out;
|
|
312
|
+
}
|
|
313
|
+
function geluErf(x) {
|
|
314
|
+
return 0.5 * x * (1 + erf(x / Math.SQRT2));
|
|
315
|
+
}
|
|
316
|
+
function geluTanhApprox(x) {
|
|
317
|
+
return 0.5 * x * (1 + Math.tanh(Math.sqrt(2 / Math.PI) * (x + 0.044715 * x * x * x)));
|
|
318
|
+
}
|
|
319
|
+
function softmaxArray(values) {
|
|
320
|
+
if (values.length === 0) return [];
|
|
321
|
+
const max = Math.max(...values);
|
|
322
|
+
const exps = values.map((value) => Math.exp(value - max));
|
|
323
|
+
const sum = exps.reduce((total, value) => total + value, 0);
|
|
324
|
+
return sum === 0 ? values.map(() => 0) : exps.map((value) => value / sum);
|
|
325
|
+
}
|
|
326
|
+
function layerNormVector(input, weight, bias, epsilon = DEFAULT_EPSILON) {
|
|
327
|
+
const width = input.length;
|
|
328
|
+
if (weight.data.length !== width || bias.data.length !== width) {
|
|
329
|
+
throw new Error(`layerNorm width ${width} does not match weight/bias`);
|
|
330
|
+
}
|
|
331
|
+
let mean = 0;
|
|
332
|
+
for (const value of input) mean += value;
|
|
333
|
+
mean /= width;
|
|
334
|
+
let variance = 0;
|
|
335
|
+
for (const value of input) {
|
|
336
|
+
const delta = value - mean;
|
|
337
|
+
variance += delta * delta;
|
|
338
|
+
}
|
|
339
|
+
variance /= width;
|
|
340
|
+
const scale = 1 / Math.sqrt(variance + epsilon);
|
|
341
|
+
const out = new Float32Array(width);
|
|
342
|
+
for (let index = 0; index < width; index += 1) {
|
|
343
|
+
out[index] = ((input[index] ?? 0) - mean) * scale * (weight.data[index] ?? 0) + (bias.data[index] ?? 0);
|
|
344
|
+
}
|
|
345
|
+
return out;
|
|
346
|
+
}
|
|
347
|
+
function normalizeConfig(input) {
|
|
348
|
+
const vocabSize = input.vocabSize ?? input.vocab_size;
|
|
349
|
+
const nLayer = input.nLayer ?? input.n_layer;
|
|
350
|
+
const nHead = input.nHead ?? input.n_head;
|
|
351
|
+
const nEmbd = input.nEmbd ?? input.n_embd;
|
|
352
|
+
const blockSize = input.blockSize ?? input.block_size;
|
|
353
|
+
const config = { vocabSize, nLayer, nHead, nEmbd, blockSize };
|
|
354
|
+
for (const [key, value] of Object.entries(config)) {
|
|
355
|
+
if (!Number.isInteger(value) || Number(value) <= 0) {
|
|
356
|
+
throw new Error(`HoloRunner S0 config field ${key} must be a positive integer`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (nEmbd % nHead !== 0) {
|
|
360
|
+
throw new Error(`n_embd ${nEmbd} must be divisible by n_head ${nHead}`);
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
vocabSize,
|
|
364
|
+
nLayer,
|
|
365
|
+
nHead,
|
|
366
|
+
nEmbd,
|
|
367
|
+
blockSize,
|
|
368
|
+
dropout: input.dropout ?? 0
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function loadWeights(config, state) {
|
|
372
|
+
const blocks = [];
|
|
373
|
+
const headWeight = optionalTensor(state, "head.weight", [config.vocabSize, config.nEmbd]);
|
|
374
|
+
const tokWeight = stateTensor(state, "tok.weight", [config.vocabSize, config.nEmbd]);
|
|
375
|
+
for (let layer = 0; layer < config.nLayer; layer += 1) {
|
|
376
|
+
const prefix = `blocks.${layer}`;
|
|
377
|
+
blocks.push({
|
|
378
|
+
ln1Weight: stateTensor(state, `${prefix}.ln1.weight`, [config.nEmbd]),
|
|
379
|
+
ln1Bias: stateTensor(state, `${prefix}.ln1.bias`, [config.nEmbd]),
|
|
380
|
+
attnInProjWeight: stateTensor(state, `${prefix}.attn.in_proj_weight`, [3 * config.nEmbd, config.nEmbd]),
|
|
381
|
+
attnInProjBias: stateTensor(state, `${prefix}.attn.in_proj_bias`, [3 * config.nEmbd]),
|
|
382
|
+
attnOutProjWeight: stateTensor(state, `${prefix}.attn.out_proj.weight`, [config.nEmbd, config.nEmbd]),
|
|
383
|
+
attnOutProjBias: stateTensor(state, `${prefix}.attn.out_proj.bias`, [config.nEmbd]),
|
|
384
|
+
ln2Weight: stateTensor(state, `${prefix}.ln2.weight`, [config.nEmbd]),
|
|
385
|
+
ln2Bias: stateTensor(state, `${prefix}.ln2.bias`, [config.nEmbd]),
|
|
386
|
+
mlp0Weight: stateTensor(state, `${prefix}.mlp.0.weight`, [4 * config.nEmbd, config.nEmbd]),
|
|
387
|
+
mlp0Bias: stateTensor(state, `${prefix}.mlp.0.bias`, [4 * config.nEmbd]),
|
|
388
|
+
mlp2Weight: stateTensor(state, `${prefix}.mlp.2.weight`, [config.nEmbd, 4 * config.nEmbd]),
|
|
389
|
+
mlp2Bias: stateTensor(state, `${prefix}.mlp.2.bias`, [config.nEmbd])
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
tokWeight,
|
|
394
|
+
posWeight: stateTensor(state, "pos.weight", [config.blockSize, config.nEmbd]),
|
|
395
|
+
lnfWeight: stateTensor(state, "lnf.weight", [config.nEmbd]),
|
|
396
|
+
lnfBias: stateTensor(state, "lnf.bias", [config.nEmbd]),
|
|
397
|
+
headWeight: headWeight ?? tokWeight,
|
|
398
|
+
blocks
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
function stateTensor(state, key, expectedShape) {
|
|
402
|
+
const input = state[key];
|
|
403
|
+
if (input === void 0) throw new Error(`S0 state_dict is missing ${key}`);
|
|
404
|
+
return tensorFrom(input, key, expectedShape);
|
|
405
|
+
}
|
|
406
|
+
function optionalTensor(state, key, expectedShape) {
|
|
407
|
+
const input = state[key];
|
|
408
|
+
return input === void 0 ? void 0 : tensorFrom(input, key, expectedShape);
|
|
409
|
+
}
|
|
410
|
+
function tensorFrom(input, name, expectedShape) {
|
|
411
|
+
const payload = isTensorRecord(input) ? input.data : input;
|
|
412
|
+
const explicitShape = isTensorRecord(input) ? input.shape : void 0;
|
|
413
|
+
const shape = explicitShape ? [...explicitShape] : inferShape(payload);
|
|
414
|
+
const data = flattenTensorData(payload);
|
|
415
|
+
const expectedSize = expectedShape.reduce((total, value) => total * value, 1);
|
|
416
|
+
if (data.length !== expectedSize) {
|
|
417
|
+
throw new Error(`${name} expected ${expectedSize} values for shape [${expectedShape.join(", ")}], got ${data.length}`);
|
|
418
|
+
}
|
|
419
|
+
if (shape.join("x") !== expectedShape.join("x")) {
|
|
420
|
+
throw new Error(`${name} expected shape [${expectedShape.join(", ")}], got [${shape.join(", ")}]`);
|
|
421
|
+
}
|
|
422
|
+
return { data, shape: [...expectedShape] };
|
|
423
|
+
}
|
|
424
|
+
function flattenTensorData(input) {
|
|
425
|
+
if (input instanceof Float32Array) return new Float32Array(input);
|
|
426
|
+
const out = [];
|
|
427
|
+
flattenNumbers(input, out);
|
|
428
|
+
return Float32Array.from(out);
|
|
429
|
+
}
|
|
430
|
+
function flattenNumbers(input, out) {
|
|
431
|
+
if (typeof input === "number") {
|
|
432
|
+
out.push(input);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (Array.isArray(input)) {
|
|
436
|
+
for (const item of input) flattenNumbers(item, out);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
throw new Error("tensor data must contain only numbers or nested number arrays");
|
|
440
|
+
}
|
|
441
|
+
function inferShape(input) {
|
|
442
|
+
if (input instanceof Float32Array) return [input.length];
|
|
443
|
+
if (!Array.isArray(input)) return [];
|
|
444
|
+
if (input.length === 0) return [0];
|
|
445
|
+
const firstShape = inferShape(input[0]);
|
|
446
|
+
for (const item of input.slice(1)) {
|
|
447
|
+
const itemShape = inferShape(item);
|
|
448
|
+
if (itemShape.join("x") !== firstShape.join("x")) throw new Error("ragged tensor arrays are not supported");
|
|
449
|
+
}
|
|
450
|
+
return [input.length, ...firstShape];
|
|
451
|
+
}
|
|
452
|
+
function embeddingRow(tensor, rowIndex, width) {
|
|
453
|
+
if (rowIndex < 0 || rowIndex >= tensor.shape[0]) {
|
|
454
|
+
throw new Error(`embedding row ${rowIndex} is out of range`);
|
|
455
|
+
}
|
|
456
|
+
const offset = rowIndex * width;
|
|
457
|
+
return tensor.data.slice(offset, offset + width);
|
|
458
|
+
}
|
|
459
|
+
function assertTokenWindow(inputIds, config) {
|
|
460
|
+
if (inputIds.length === 0) throw new Error("inputIds must contain at least one token");
|
|
461
|
+
if (inputIds.length > config.blockSize) {
|
|
462
|
+
throw new Error(`input length ${inputIds.length} exceeds block size ${config.blockSize}`);
|
|
463
|
+
}
|
|
464
|
+
for (const id of inputIds) assertTokenId(id, config.vocabSize);
|
|
465
|
+
}
|
|
466
|
+
function assertTokenId(id, vocabSize) {
|
|
467
|
+
if (!Number.isInteger(id) || id < 0 || id >= vocabSize) {
|
|
468
|
+
throw new Error(`token id ${id} is outside vocab size ${vocabSize}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
function layerNormSequence(input, rows, width, weight, bias) {
|
|
472
|
+
const out = new Float32Array(input.length);
|
|
473
|
+
for (let row = 0; row < rows; row += 1) {
|
|
474
|
+
out.set(layerNormVector(input.slice(row * width, row * width + width), weight, bias), row * width);
|
|
475
|
+
}
|
|
476
|
+
return out;
|
|
477
|
+
}
|
|
478
|
+
function linearSequence(input, rows, inputWidth, weight, bias) {
|
|
479
|
+
const outputWidth = weight.shape[0];
|
|
480
|
+
const out = new Float32Array(rows * outputWidth);
|
|
481
|
+
for (let row = 0; row < rows; row += 1) {
|
|
482
|
+
const vector = linearVector(input.slice(row * inputWidth, row * inputWidth + inputWidth), weight, bias);
|
|
483
|
+
out.set(vector, row * outputWidth);
|
|
484
|
+
}
|
|
485
|
+
return out;
|
|
486
|
+
}
|
|
487
|
+
function linearVector(input, weight, bias) {
|
|
488
|
+
const outputWidth = weight.shape[0];
|
|
489
|
+
const inputWidth = weight.shape[1];
|
|
490
|
+
if (input.length !== inputWidth) {
|
|
491
|
+
throw new Error(`linear input width ${input.length} does not match weight width ${inputWidth}`);
|
|
492
|
+
}
|
|
493
|
+
const out = new Float32Array(outputWidth);
|
|
494
|
+
for (let row = 0; row < outputWidth; row += 1) {
|
|
495
|
+
let sum = bias ? bias.data[row] ?? 0 : 0;
|
|
496
|
+
for (let column = 0; column < inputWidth; column += 1) {
|
|
497
|
+
sum += (weight.data[row * inputWidth + column] ?? 0) * (input[column] ?? 0);
|
|
498
|
+
}
|
|
499
|
+
out[row] = sum;
|
|
500
|
+
}
|
|
501
|
+
return out;
|
|
502
|
+
}
|
|
503
|
+
function causalSelfAttentionSequence(input, tokens, config, block) {
|
|
504
|
+
const qkv = linearSequence(input, tokens, config.nEmbd, block.attnInProjWeight, block.attnInProjBias);
|
|
505
|
+
const context = new Float32Array(tokens * config.nEmbd);
|
|
506
|
+
const headDim = config.nEmbd / config.nHead;
|
|
507
|
+
const scale = 1 / Math.sqrt(headDim);
|
|
508
|
+
for (let token = 0; token < tokens; token += 1) {
|
|
509
|
+
for (let head = 0; head < config.nHead; head += 1) {
|
|
510
|
+
const scores = [];
|
|
511
|
+
for (let source = 0; source <= token; source += 1) {
|
|
512
|
+
let dot = 0;
|
|
513
|
+
for (let dim = 0; dim < headDim; dim += 1) {
|
|
514
|
+
const q = qkv[token * 3 * config.nEmbd + head * headDim + dim] ?? 0;
|
|
515
|
+
const k = qkv[source * 3 * config.nEmbd + config.nEmbd + head * headDim + dim] ?? 0;
|
|
516
|
+
dot += q * k;
|
|
517
|
+
}
|
|
518
|
+
scores.push(dot * scale);
|
|
519
|
+
}
|
|
520
|
+
const probs = softmaxArray(scores);
|
|
521
|
+
for (let dim = 0; dim < headDim; dim += 1) {
|
|
522
|
+
let value = 0;
|
|
523
|
+
for (let source = 0; source <= token; source += 1) {
|
|
524
|
+
const v = qkv[source * 3 * config.nEmbd + 2 * config.nEmbd + head * headDim + dim] ?? 0;
|
|
525
|
+
value += (probs[source] ?? 0) * v;
|
|
526
|
+
}
|
|
527
|
+
context[token * config.nEmbd + head * headDim + dim] = value;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return linearSequence(context, tokens, config.nEmbd, block.attnOutProjWeight, block.attnOutProjBias);
|
|
532
|
+
}
|
|
533
|
+
function cachedSelfAttention(query, cache, layerIndex, config) {
|
|
534
|
+
const layer = cache.layers[layerIndex];
|
|
535
|
+
const headDim = config.nEmbd / config.nHead;
|
|
536
|
+
const scale = 1 / Math.sqrt(headDim);
|
|
537
|
+
const out = new Float32Array(config.nEmbd);
|
|
538
|
+
for (let head = 0; head < config.nHead; head += 1) {
|
|
539
|
+
const scores = [];
|
|
540
|
+
for (let source = 0; source < layer.length; source += 1) {
|
|
541
|
+
let dot = 0;
|
|
542
|
+
const keyOffset = source * config.nEmbd + head * headDim;
|
|
543
|
+
for (let dim = 0; dim < headDim; dim += 1) {
|
|
544
|
+
dot += (query[head * headDim + dim] ?? 0) * (layer.keys[keyOffset + dim] ?? 0);
|
|
545
|
+
}
|
|
546
|
+
scores.push(dot * scale);
|
|
547
|
+
}
|
|
548
|
+
const probs = softmaxArray(scores);
|
|
549
|
+
for (let dim = 0; dim < headDim; dim += 1) {
|
|
550
|
+
let value = 0;
|
|
551
|
+
for (let source = 0; source < layer.length; source += 1) {
|
|
552
|
+
value += (probs[source] ?? 0) * (layer.values[source * config.nEmbd + head * headDim + dim] ?? 0);
|
|
553
|
+
}
|
|
554
|
+
out[head * headDim + dim] = value;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return out;
|
|
558
|
+
}
|
|
559
|
+
function addInPlace(left, right) {
|
|
560
|
+
if (left.length !== right.length) throw new Error("addInPlace length mismatch");
|
|
561
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
562
|
+
left[index] = (left[index] ?? 0) + (right[index] ?? 0);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
function addVectors(left, right) {
|
|
566
|
+
if (left.length !== right.length) throw new Error("addVectors length mismatch");
|
|
567
|
+
const out = new Float32Array(left.length);
|
|
568
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
569
|
+
out[index] = (left[index] ?? 0) + (right[index] ?? 0);
|
|
570
|
+
}
|
|
571
|
+
return out;
|
|
572
|
+
}
|
|
573
|
+
function erf(x) {
|
|
574
|
+
const sign = x < 0 ? -1 : 1;
|
|
575
|
+
const ax = Math.abs(x);
|
|
576
|
+
const t = 1 / (1 + 0.3275911 * ax);
|
|
577
|
+
const y = 1 - ((((1.061405429 * t - 1.453152027) * t + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * Math.exp(-ax * ax);
|
|
578
|
+
return sign * y;
|
|
579
|
+
}
|
|
580
|
+
function isRecord(value) {
|
|
581
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
582
|
+
}
|
|
583
|
+
function isTensorRecord(value) {
|
|
584
|
+
return isRecord(value) && "data" in value;
|
|
585
|
+
}
|
|
586
|
+
function isTensorMap(value) {
|
|
587
|
+
return isRecord(value);
|
|
588
|
+
}
|
|
589
|
+
function numberField(record, key) {
|
|
590
|
+
const value = record[key];
|
|
591
|
+
return typeof value === "number" ? value : void 0;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
export { HOLO_RUNNER_TOKENIZER_MODULE, HOLO_RUNTIME_MODEL_FLEET_NODE_KIND, HOLO_RUNTIME_SCHEMA, HoloRuntimeDecoder, HoloRuntimeKvCache, createSeededRng, encodedTokensFromTokenIds, geluErf, geluTanhApprox, layerNormVector, loadHoloRunnerS0Checkpoint, loadHoloRunnerS0StateDict, loadHoloRunnerTokenizer, matmul, resolveTokenizerModulePath, sampleFromLogits, softmaxArray, tokenIdsFromEncodedTokens };
|
|
595
|
+
//# sourceMappingURL=index.js.map
|
|
596
|
+
//# sourceMappingURL=index.js.map
|