volvoxai 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 +145 -0
- package/bin/volvox.js +72 -0
- package/dist/v0.1.0/volvoxai.js +4664 -0
- package/dist/v0.1.0/volvoxai.min.js +1848 -0
- package/dist/v0.1.0/volvoxai.wasm +0 -0
- package/dist/volvoxai.js +4664 -0
- package/dist/volvoxai.min.js +1848 -0
- package/dist/volvoxai.wasm +0 -0
- package/docs/README.md +22 -0
- package/docs/browser-runtime.md +87 -0
- package/docs/efficientdet_tflite_vs_volvoxai.md +445 -0
- package/docs/microkernel_optimization_guide.md +153 -0
- package/docs/model-format.md +108 -0
- package/docs/models.md +103 -0
- package/docs/native-runtime.md +189 -0
- package/docs/operation_list.md +232 -0
- package/docs/operator_fusion_patterns.md +58 -0
- package/docs/quickstart.md +115 -0
- package/docs/roadmap.md +19 -0
- package/docs/testing.md +97 -0
- package/docs/textbook/01-foundations.md +233 -0
- package/docs/textbook/02-tinystories-language-model.md +300 -0
- package/docs/textbook/03-efficientdet-vision-model.md +281 -0
- package/docs/textbook/04-precision-and-quantization.md +208 -0
- package/docs/textbook/05-inside-the-engine.md +155 -0
- package/docs/textbook/06-native-engine-architecture.md +338 -0
- package/docs/textbook/07-glossary-and-next-steps.md +258 -0
- package/docs/textbook/README.md +85 -0
- package/docs/textbook/ko/01-foundations.md +231 -0
- package/docs/textbook/ko/02-tinystories-language-model.md +300 -0
- package/docs/textbook/ko/03-efficientdet-vision-model.md +277 -0
- package/docs/textbook/ko/04-precision-and-quantization.md +206 -0
- package/docs/textbook/ko/05-inside-the-engine.md +154 -0
- package/docs/textbook/ko/06-native-engine-architecture.md +333 -0
- package/docs/textbook/ko/07-glossary-and-next-steps.md +253 -0
- package/docs/textbook/ko/README.md +83 -0
- package/docs/xnnpack_optimization_guide.md +197 -0
- package/js/CPUEngine.js +241 -0
- package/js/Graph.js +49 -0
- package/js/GraphExecutor.js +1020 -0
- package/js/GraphLoader.js +282 -0
- package/js/ShaderLibrary.js +236 -0
- package/js/Tensor.js +25 -0
- package/js/Tokenizer.js +266 -0
- package/js/VolvoxAI.js +130 -0
- package/js/WasmEngine.js +378 -0
- package/js/WebNNEngine.js +169 -0
- package/js/index.js +11 -0
- package/js/ops/add.js +31 -0
- package/js/ops/argMax.js +33 -0
- package/js/ops/averagePool2D.js +38 -0
- package/js/ops/batchNorm2D.js +28 -0
- package/js/ops/cast.js +19 -0
- package/js/ops/clip.js +15 -0
- package/js/ops/concat2.js +18 -0
- package/js/ops/conv1D.js +35 -0
- package/js/ops/conv2D.js +70 -0
- package/js/ops/convTranspose2D.js +45 -0
- package/js/ops/crossAttention.js +69 -0
- package/js/ops/crossSDPA.js +41 -0
- package/js/ops/dequantizeLinear.js +9 -0
- package/js/ops/div.js +15 -0
- package/js/ops/embedding.js +14 -0
- package/js/ops/expand.js +24 -0
- package/js/ops/gELU.js +9 -0
- package/js/ops/gather.js +51 -0
- package/js/ops/gatherElements.js +33 -0
- package/js/ops/globalAveragePool.js +21 -0
- package/js/ops/hardSigmoid.js +12 -0
- package/js/ops/hardSwish.js +12 -0
- package/js/ops/interp1D.js +25 -0
- package/js/ops/layerNorm.js +25 -0
- package/js/ops/leakyReLU.js +10 -0
- package/js/ops/logSoftmax.js +15 -0
- package/js/ops/matMul.js +35 -0
- package/js/ops/maxPool2D.js +36 -0
- package/js/ops/meanHeight.js +17 -0
- package/js/ops/mul.js +31 -0
- package/js/ops/nonMaxSuppression.js +72 -0
- package/js/ops/pReLU.js +11 -0
- package/js/ops/pad.js +35 -0
- package/js/ops/profileX.js +22 -0
- package/js/ops/profileY.js +22 -0
- package/js/ops/rMSNorm.js +14 -0
- package/js/ops/reLU.js +8 -0
- package/js/ops/reduceMean.js +17 -0
- package/js/ops/reduceSum.js +19 -0
- package/js/ops/reshape.js +6 -0
- package/js/ops/resize.js +44 -0
- package/js/ops/sDPA.js +44 -0
- package/js/ops/siLU.js +8 -0
- package/js/ops/sigmoid.js +6 -0
- package/js/ops/slice.js +36 -0
- package/js/ops/softmax.js +18 -0
- package/js/ops/spatialSoftargmaxY.js +28 -0
- package/js/ops/split.js +24 -0
- package/js/ops/sub.js +11 -0
- package/js/ops/tanh.js +7 -0
- package/js/ops/transpose.js +34 -0
- package/js/ops/upsample2x.js +23 -0
- package/js/ops/where.js +15 -0
- package/package.json +33 -0
- package/shaders/add.wgsl +13 -0
- package/shaders/add3Relu.wgsl +23 -0
- package/shaders/addRelu.wgsl +22 -0
- package/shaders/averagePool2D.wgsl +24 -0
- package/shaders/batchNorm2D.wgsl +21 -0
- package/shaders/binaryBroadcast.wgsl +34 -0
- package/shaders/broadcastBinary.wgsl +26 -0
- package/shaders/clip.wgsl +10 -0
- package/shaders/concat2.wgsl +16 -0
- package/shaders/concatCopy.wgsl +10 -0
- package/shaders/concatSigmoidCopy.wgsl +16 -0
- package/shaders/conv1D.wgsl +37 -0
- package/shaders/conv2D.wgsl +80 -0
- package/shaders/conv2DDepthwise4.wgsl +74 -0
- package/shaders/conv2DDepthwise8.wgsl +66 -0
- package/shaders/conv2DPointwise16.wgsl +67 -0
- package/shaders/conv2DPointwise16Tile.wgsl +86 -0
- package/shaders/conv2DPointwise8.wgsl +85 -0
- package/shaders/conv2DPointwise8Vec2.wgsl +70 -0
- package/shaders/conv2DPointwise8Vec4.wgsl +65 -0
- package/shaders/conv2DRegularC3Out16.wgsl +75 -0
- package/shaders/convTranspose2D.wgsl +33 -0
- package/shaders/copy.wgsl +13 -0
- package/shaders/crossAttention.wgsl +140 -0
- package/shaders/crossAttentionF32.wgsl +98 -0
- package/shaders/crossSDPA.wgsl +74 -0
- package/shaders/dequantizeLinear.wgsl +14 -0
- package/shaders/div.wgsl +34 -0
- package/shaders/elementwise.wgsl +13 -0
- package/shaders/embedding.wgsl +22 -0
- package/shaders/expand.wgsl +18 -0
- package/shaders/gELU.wgsl +13 -0
- package/shaders/gather.wgsl +17 -0
- package/shaders/generalTranspose.wgsl +19 -0
- package/shaders/globalAveragePool.wgsl +19 -0
- package/shaders/hardSigmoid.wgsl +13 -0
- package/shaders/hardSwish.wgsl +13 -0
- package/shaders/interp1D.wgsl +28 -0
- package/shaders/layerNorm.wgsl +33 -0
- package/shaders/leakyReLU.wgsl +11 -0
- package/shaders/linearF32.wgsl +33 -0
- package/shaders/linearF32RowMajor.wgsl +24 -0
- package/shaders/linearInt8.wgsl +42 -0
- package/shaders/logSoftmax.wgsl +22 -0
- package/shaders/maxPool2D.wgsl +37 -0
- package/shaders/meanHeight.wgsl +18 -0
- package/shaders/mul.wgsl +32 -0
- package/shaders/nonMaxSuppression.wgsl +92 -0
- package/shaders/pReLU.wgsl +14 -0
- package/shaders/pad.wgsl +19 -0
- package/shaders/profileX.wgsl +28 -0
- package/shaders/profileY.wgsl +28 -0
- package/shaders/quantizeLinear.wgsl +69 -0
- package/shaders/rMSNorm.wgsl +21 -0
- package/shaders/reLU.wgsl +13 -0
- package/shaders/reduce.wgsl +17 -0
- package/shaders/resize.wgsl +52 -0
- package/shaders/sDPA.wgsl +71 -0
- package/shaders/siLU.wgsl +13 -0
- package/shaders/sigmoid.wgsl +13 -0
- package/shaders/slice.wgsl +26 -0
- package/shaders/softmax.wgsl +23 -0
- package/shaders/spatialSoftargmaxY.wgsl +32 -0
- package/shaders/split.wgsl +15 -0
- package/shaders/sub.wgsl +34 -0
- package/shaders/tanh.wgsl +13 -0
- package/shaders/upsample2x.wgsl +24 -0
- package/shaders/where.wgsl +12 -0
- package/volvoxai.wasm +0 -0
package/js/Tokenizer.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
export class Tokenizer {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.vocabByText = new Map(); // token text (utf-8 decoded) -> id
|
|
4
|
+
this.vocabByLatin1 = new Map(); // raw latin1 fallback -> id
|
|
5
|
+
this.idToText = [];
|
|
6
|
+
this.idToBytes = [];
|
|
7
|
+
this.merges = new Map(); // `left,right` -> { rank, mergedId }
|
|
8
|
+
this.mergeEnabled = false;
|
|
9
|
+
this.decoder = new TextDecoder("utf-8");
|
|
10
|
+
this.encoder = new TextEncoder();
|
|
11
|
+
this.bpePattern = /'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+/gu;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// vocab.json and vocab.bin are both supported.
|
|
15
|
+
// pass vocabUrl, mergesUrl.
|
|
16
|
+
async load(vocabUrl, mergesUrl = null) {
|
|
17
|
+
try {
|
|
18
|
+
const isBin = /\.bin$/i.test(vocabUrl);
|
|
19
|
+
if (isBin) await this.loadFromBinary(vocabUrl);
|
|
20
|
+
else await this.loadFromJson(vocabUrl);
|
|
21
|
+
|
|
22
|
+
if (mergesUrl) await this.loadMerges(mergesUrl);
|
|
23
|
+
} catch (err) {
|
|
24
|
+
console.error("Tokenizer load error:", err);
|
|
25
|
+
throw err;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
resetVocab() {
|
|
30
|
+
this.vocabByText.clear();
|
|
31
|
+
this.vocabByLatin1.clear();
|
|
32
|
+
this.idToText = [];
|
|
33
|
+
this.idToBytes = [];
|
|
34
|
+
this.merges.clear();
|
|
35
|
+
this.mergeEnabled = false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async loadFromJson(vocabUrl) {
|
|
39
|
+
const res = await fetch(vocabUrl);
|
|
40
|
+
if (!res.ok) throw new Error(`Failed to fetch vocab: ${res.statusText}`);
|
|
41
|
+
const vocabJson = await res.json();
|
|
42
|
+
|
|
43
|
+
this.resetVocab();
|
|
44
|
+
for (const [text, id] of Object.entries(vocabJson)) {
|
|
45
|
+
const bytes = this.encoder.encode(text);
|
|
46
|
+
this.vocabByText.set(text, id);
|
|
47
|
+
this.vocabByLatin1.set(this.bytesToLatin1(bytes), id);
|
|
48
|
+
this.idToText[id] = text;
|
|
49
|
+
this.idToBytes[id] = bytes;
|
|
50
|
+
}
|
|
51
|
+
console.log(`Loaded ${Object.keys(vocabJson).length} tokens from ${vocabUrl}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async loadFromBinary(vocabUrl) {
|
|
55
|
+
const res = await fetch(vocabUrl);
|
|
56
|
+
if (!res.ok) throw new Error(`Failed to fetch vocab.bin: ${res.statusText}`);
|
|
57
|
+
|
|
58
|
+
const buf = new Uint8Array(await res.arrayBuffer());
|
|
59
|
+
if (buf.length < 4) throw new Error("Invalid vocab.bin");
|
|
60
|
+
const dv = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
61
|
+
const size = dv.getInt32(0, true);
|
|
62
|
+
|
|
63
|
+
this.resetVocab();
|
|
64
|
+
let off = 4;
|
|
65
|
+
for (let i = 0; i < size; i++) {
|
|
66
|
+
if (off + 4 > buf.length) throw new Error(`vocab.bin truncated at token ${i}`);
|
|
67
|
+
const len = dv.getInt32(off, true);
|
|
68
|
+
off += 4;
|
|
69
|
+
if (len < 0 || off + len > buf.length) throw new Error(`vocab.bin malformed at token ${i}`);
|
|
70
|
+
const tokenBytes = buf.slice(off, off + len);
|
|
71
|
+
off += len;
|
|
72
|
+
|
|
73
|
+
const text = this.decoder.decode(tokenBytes);
|
|
74
|
+
this.vocabByText.set(text, i);
|
|
75
|
+
this.vocabByLatin1.set(this.bytesToLatin1(tokenBytes), i);
|
|
76
|
+
this.idToText[i] = text;
|
|
77
|
+
this.idToBytes[i] = tokenBytes;
|
|
78
|
+
}
|
|
79
|
+
console.log(`Loaded ${size} tokens from ${vocabUrl}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async loadMerges(mergesUrl) {
|
|
83
|
+
const res = await fetch(mergesUrl);
|
|
84
|
+
if (!res.ok) throw new Error(`Failed to fetch merges: ${res.statusText}`);
|
|
85
|
+
const text = await res.text();
|
|
86
|
+
|
|
87
|
+
let rank = 0;
|
|
88
|
+
this.merges.clear();
|
|
89
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
90
|
+
const line = rawLine.trim();
|
|
91
|
+
if (!line || line.startsWith("#")) continue;
|
|
92
|
+
const parts = line.split(/\s+/);
|
|
93
|
+
if (parts.length < 2) continue;
|
|
94
|
+
|
|
95
|
+
const leftToken = this.normalizeMergeToken(parts[0]);
|
|
96
|
+
const rightToken = this.normalizeMergeToken(parts[1]);
|
|
97
|
+
const leftId = this.findTokenByText(leftToken);
|
|
98
|
+
const rightId = this.findTokenByText(rightToken);
|
|
99
|
+
if (leftId < 0 || rightId < 0) continue;
|
|
100
|
+
|
|
101
|
+
const mergedText = `${this.idToText[leftId]}${this.idToText[rightId]}`;
|
|
102
|
+
const mergedId = this.vocabByText.get(mergedText);
|
|
103
|
+
if (mergedId === undefined) continue;
|
|
104
|
+
|
|
105
|
+
const key = this.pairKey(leftId, rightId);
|
|
106
|
+
if (!this.merges.has(key)) {
|
|
107
|
+
this.merges.set(key, { rank: rank++, mergedId });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.mergeEnabled = this.merges.size > 0;
|
|
112
|
+
console.log(`Loaded ${this.merges.size} merge rules from ${mergesUrl}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
bytesToLatin1(bytes) {
|
|
116
|
+
let out = "";
|
|
117
|
+
for (let i = 0; i < bytes.length; i++) out += String.fromCharCode(bytes[i]);
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
utf8Len(byte) {
|
|
122
|
+
if (byte < 0x80) return 1;
|
|
123
|
+
if ((byte & 0xe0) === 0xc0) return 2;
|
|
124
|
+
if ((byte & 0xf0) === 0xe0) return 3;
|
|
125
|
+
if ((byte & 0xf8) === 0xf0) return 4;
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
pairKey(left, right) {
|
|
130
|
+
return `${left},${right}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
normalizeMergeToken(token) {
|
|
134
|
+
if (token.startsWith("Ġ")) return ` ${token.slice(1)}`;
|
|
135
|
+
return token;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
findTokenByText(text) {
|
|
139
|
+
let id = this.vocabByText.get(text);
|
|
140
|
+
if (id !== undefined) return id;
|
|
141
|
+
id = this.vocabByLatin1.get(text);
|
|
142
|
+
return id === undefined ? -1 : id;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
findTokenByBytes(bytes) {
|
|
146
|
+
const text = this.decoder.decode(bytes);
|
|
147
|
+
let id = this.vocabByText.get(text);
|
|
148
|
+
if (id !== undefined) return id;
|
|
149
|
+
id = this.vocabByLatin1.get(this.bytesToLatin1(bytes));
|
|
150
|
+
return id === undefined ? -1 : id;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Decode a single token ID to its text representation.
|
|
154
|
+
decodeToken(tokenId) {
|
|
155
|
+
const bytes = this.idToBytes[tokenId];
|
|
156
|
+
if (!bytes) return "";
|
|
157
|
+
return this.decoder.decode(bytes).replace(/Ġ/g, " ");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Decode a token-id array.
|
|
161
|
+
decode(tokenIds) {
|
|
162
|
+
return tokenIds.map((id) => this.decodeToken(id)).join("");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
encodeBpeWord(word, maxTokens) {
|
|
166
|
+
const bytes = this.encoder.encode(word);
|
|
167
|
+
if (bytes.length === 0 || maxTokens <= 0) return [];
|
|
168
|
+
|
|
169
|
+
const symbols = [];
|
|
170
|
+
for (let pos = 0; pos < bytes.length;) {
|
|
171
|
+
let clen = this.utf8Len(bytes[pos]);
|
|
172
|
+
if (pos + clen > bytes.length) clen = bytes.length - pos;
|
|
173
|
+
const symbolBytes = bytes.slice(pos, pos + clen);
|
|
174
|
+
const id = this.findTokenByBytes(symbolBytes);
|
|
175
|
+
if (id >= 0) {
|
|
176
|
+
symbols.push(id);
|
|
177
|
+
} else {
|
|
178
|
+
for (let i = 0; i < clen; i++) {
|
|
179
|
+
const byteId = this.findTokenByBytes(Uint8Array.of(bytes[pos + i]));
|
|
180
|
+
if (byteId >= 0) symbols.push(byteId);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
pos += clen;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
while (symbols.length > 1) {
|
|
187
|
+
let bestIdx = -1;
|
|
188
|
+
let bestRank = Number.MAX_SAFE_INTEGER;
|
|
189
|
+
let bestMerged = -1;
|
|
190
|
+
|
|
191
|
+
for (let i = 0; i < symbols.length - 1; i++) {
|
|
192
|
+
const merge = this.merges.get(this.pairKey(symbols[i], symbols[i + 1]));
|
|
193
|
+
if (!merge) continue;
|
|
194
|
+
if (merge.rank >= bestRank) continue;
|
|
195
|
+
if (merge.mergedId < 0) continue;
|
|
196
|
+
|
|
197
|
+
bestIdx = i;
|
|
198
|
+
bestRank = merge.rank;
|
|
199
|
+
bestMerged = merge.mergedId;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (bestIdx < 0) break;
|
|
203
|
+
symbols[bestIdx] = bestMerged;
|
|
204
|
+
symbols.splice(bestIdx + 1, 1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return symbols.slice(0, maxTokens);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
encodeGreedy(text, maxTokens) {
|
|
211
|
+
const bytes = this.encoder.encode(text);
|
|
212
|
+
const tokens = [];
|
|
213
|
+
let pos = 0;
|
|
214
|
+
|
|
215
|
+
while (pos < bytes.length && tokens.length < maxTokens) {
|
|
216
|
+
let bestId = -1;
|
|
217
|
+
let bestLen = 0;
|
|
218
|
+
|
|
219
|
+
for (let i = 0; i < this.idToBytes.length; i++) {
|
|
220
|
+
const tokenBytes = this.idToBytes[i];
|
|
221
|
+
if (!tokenBytes || tokenBytes.length <= bestLen || pos + tokenBytes.length > bytes.length) continue;
|
|
222
|
+
let match = true;
|
|
223
|
+
for (let j = 0; j < tokenBytes.length; j++) {
|
|
224
|
+
if (bytes[pos + j] !== tokenBytes[j]) {
|
|
225
|
+
match = false;
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
if (match) {
|
|
230
|
+
bestId = i;
|
|
231
|
+
bestLen = tokenBytes.length;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (bestId < 0) {
|
|
236
|
+
pos++;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
tokens.push(bestId);
|
|
241
|
+
pos += bestLen;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return tokens;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Tokenize with GPT-2-style regex pretokenization and run one BPE pass per chunk.
|
|
248
|
+
encode(text, maxTokens = 256) {
|
|
249
|
+
if (maxTokens <= 0) return [];
|
|
250
|
+
if (this.vocabByText.size === 0 && this.vocabByLatin1.size === 0) return [];
|
|
251
|
+
if (!this.mergeEnabled) return this.encodeGreedy(text, maxTokens);
|
|
252
|
+
|
|
253
|
+
const tokens = [];
|
|
254
|
+
for (const match of text.matchAll(this.bpePattern)) {
|
|
255
|
+
const chunk = match[0];
|
|
256
|
+
if (!chunk || tokens.length >= maxTokens) continue;
|
|
257
|
+
|
|
258
|
+
const remain = maxTokens - tokens.length;
|
|
259
|
+
if (remain <= 0) break;
|
|
260
|
+
const part = this.encodeBpeWord(chunk, remain);
|
|
261
|
+
for (const id of part) tokens.push(id);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return tokens;
|
|
265
|
+
}
|
|
266
|
+
}
|
package/js/VolvoxAI.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { Tensor } from './Tensor.js';
|
|
2
|
+
import { Graph } from './Graph.js';
|
|
3
|
+
import { CPUEngine } from './CPUEngine.js';
|
|
4
|
+
import { WasmEngine } from './WasmEngine.js';
|
|
5
|
+
import { GraphExecutor } from './GraphExecutor.js';
|
|
6
|
+
import { GraphLoader } from './GraphLoader.js';
|
|
7
|
+
import { WebNNEngine } from './WebNNEngine.js';
|
|
8
|
+
|
|
9
|
+
export class VolvoxAI {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.engines = [];
|
|
12
|
+
this.weightsBaseUrl = null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static async init(preferredBackend = "auto", wasmUrl = "./volvoxai.wasm") {
|
|
16
|
+
const instance = new VolvoxAI();
|
|
17
|
+
const stringOrders = {
|
|
18
|
+
auto: ["webnn", "webgpu", "wasm", "cpu"],
|
|
19
|
+
webnn: ["webnn", "wasm", "cpu"],
|
|
20
|
+
webgpu: ["webgpu", "wasm", "cpu"],
|
|
21
|
+
wasm: ["wasm", "cpu"],
|
|
22
|
+
cpu: ["cpu"],
|
|
23
|
+
};
|
|
24
|
+
const validBackends = new Set(["webnn", "webgpu", "wasm", "cpu"]);
|
|
25
|
+
const strictList = Array.isArray(preferredBackend);
|
|
26
|
+
const order = strictList ? [...preferredBackend] : stringOrders[preferredBackend];
|
|
27
|
+
|
|
28
|
+
if (!order || order.length === 0) {
|
|
29
|
+
throw new Error(`[VolvoxAI] Invalid backend selection: ${JSON.stringify(preferredBackend)}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
for (const backend of order) {
|
|
33
|
+
if (!validBackends.has(backend)) {
|
|
34
|
+
throw new Error(`[VolvoxAI] Unknown backend '${backend}'. Use 'auto', 'webnn', 'webgpu', 'wasm', 'cpu', or an array of those backend names.`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const added = new Set();
|
|
39
|
+
for (const backend of order) {
|
|
40
|
+
if (added.has(backend)) continue;
|
|
41
|
+
|
|
42
|
+
if (backend === "webnn") {
|
|
43
|
+
if (typeof navigator !== 'undefined' && navigator.ml) {
|
|
44
|
+
try {
|
|
45
|
+
const context = await navigator.ml.createContext({ deviceType: 'npu' });
|
|
46
|
+
console.log("[VolvoxAI] WebNN Engine (NPU) Initialized successfully.");
|
|
47
|
+
instance.engines.push({ type: 'webnn', engine: new WebNNEngine(context) });
|
|
48
|
+
added.add(backend);
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.warn("[VolvoxAI] WebNN initialization failed.", e.message);
|
|
51
|
+
}
|
|
52
|
+
} else if (strictList || preferredBackend === "webnn") {
|
|
53
|
+
console.warn("[VolvoxAI] WebNN is not supported.");
|
|
54
|
+
}
|
|
55
|
+
} else if (backend === "webgpu") {
|
|
56
|
+
if (typeof navigator !== 'undefined' && navigator.gpu) {
|
|
57
|
+
try {
|
|
58
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
59
|
+
if (adapter) {
|
|
60
|
+
const device = await adapter.requestDevice();
|
|
61
|
+
console.log("[VolvoxAI] WebGPU Engine Initialized successfully.");
|
|
62
|
+
instance.engines.push({ type: 'webgpu', device: device });
|
|
63
|
+
added.add(backend);
|
|
64
|
+
} else {
|
|
65
|
+
console.warn("[VolvoxAI] WebGPU adapter is not available.");
|
|
66
|
+
}
|
|
67
|
+
} catch (e) {
|
|
68
|
+
console.warn("[VolvoxAI] WebGPU initialization failed.", e.message);
|
|
69
|
+
}
|
|
70
|
+
} else if (strictList || preferredBackend === "webgpu") {
|
|
71
|
+
console.warn("[VolvoxAI] WebGPU is not supported.");
|
|
72
|
+
}
|
|
73
|
+
} else if (backend === "wasm") {
|
|
74
|
+
const wasmEngine = await WasmEngine.init(wasmUrl);
|
|
75
|
+
if (wasmEngine) {
|
|
76
|
+
console.log("[VolvoxAI] WASM Engine Initialized successfully.");
|
|
77
|
+
instance.engines.push({ type: 'wasm', engine: wasmEngine });
|
|
78
|
+
added.add(backend);
|
|
79
|
+
} else {
|
|
80
|
+
console.warn("[VolvoxAI] WASM initialization failed.");
|
|
81
|
+
}
|
|
82
|
+
} else if (backend === "cpu") {
|
|
83
|
+
console.log("[VolvoxAI] Pure JS CPU Engine Initialized.");
|
|
84
|
+
instance.engines.push({ type: 'cpu', engine: new CPUEngine() });
|
|
85
|
+
added.add(backend);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (instance.engines.length === 0) {
|
|
90
|
+
throw new Error(`[VolvoxAI] None of the requested backends initialized: ${order.join(", ")}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return instance;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
createGraph() {
|
|
97
|
+
return new Graph();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async loadGraph(safetensorsUrl) {
|
|
101
|
+
const graph = this.createGraph();
|
|
102
|
+
return await GraphLoader.load(graph, safetensorsUrl);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async compile(graph, weightsUrl) {
|
|
106
|
+
this.weightsBaseUrl = weightsUrl;
|
|
107
|
+
console.log(`[VolvoxAI] Compiling graph with ${graph.nodes.length} nodes...`);
|
|
108
|
+
|
|
109
|
+
for (const entry of this.engines) {
|
|
110
|
+
try {
|
|
111
|
+
if (entry.type === 'webgpu') {
|
|
112
|
+
console.log(`[VolvoxAI] Trying to allocate graph on WebGPU (Tier 2)...`);
|
|
113
|
+
const executor = new GraphExecutor(entry.device, graph);
|
|
114
|
+
await executor.compile();
|
|
115
|
+
console.log(`[VolvoxAI] WebGPU Engine compiled successfully.`);
|
|
116
|
+
return executor;
|
|
117
|
+
} else {
|
|
118
|
+
console.log(`[VolvoxAI] Trying to allocate graph on ${entry.engine.constructor.name}...`);
|
|
119
|
+
await entry.engine.allocateGraph(graph);
|
|
120
|
+
console.log(`[VolvoxAI] ${entry.engine.constructor.name} compiled successfully.`);
|
|
121
|
+
return entry.engine;
|
|
122
|
+
}
|
|
123
|
+
} catch (e) {
|
|
124
|
+
console.warn(`[VolvoxAI] Compilation failed on ${entry.type}. Falling back to next tier. Error: ${e.message}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
throw new Error("[VolvoxAI] All engine tiers failed to compile the graph.");
|
|
129
|
+
}
|
|
130
|
+
}
|