@hanzo/browser-extension 1.9.31 → 1.9.33
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/package.json +2 -1
- package/src/answer/AnswerEngine.tsx +553 -0
- package/src/manifest-firefox.json +10 -2
- package/src/manifest.json +17 -2
- package/src/newtab.css +277 -0
- package/src/newtab.html +13 -0
- package/src/popup.html +28 -0
- package/dist/browser-extension/README.md +0 -43
- package/dist/browser-extension/background-firefox.js +0 -4556
- package/dist/browser-extension/background.js +0 -7245
- package/dist/browser-extension/browser-control.js +0 -1317
- package/dist/browser-extension/chrome/ai-worker.js +0 -571
- package/dist/browser-extension/chrome/background.js +0 -7245
- package/dist/browser-extension/chrome/callback.html +0 -17
- package/dist/browser-extension/chrome/content-script.js +0 -2789
- package/dist/browser-extension/chrome/icon128.png +0 -0
- package/dist/browser-extension/chrome/icon16.png +0 -0
- package/dist/browser-extension/chrome/icon48.png +0 -0
- package/dist/browser-extension/chrome/manifest.json +0 -66
- package/dist/browser-extension/chrome/popup.css +0 -761
- package/dist/browser-extension/chrome/popup.html +0 -353
- package/dist/browser-extension/chrome/popup.js +0 -1650
- package/dist/browser-extension/chrome/sidebar.css +0 -1792
- package/dist/browser-extension/chrome/sidebar.html +0 -463
- package/dist/browser-extension/chrome/sidebar.js +0 -26541
- package/dist/browser-extension/cli.js +0 -233
- package/dist/browser-extension/firefox/ai-worker.js +0 -571
- package/dist/browser-extension/firefox/background.js +0 -4556
- package/dist/browser-extension/firefox/callback.html +0 -17
- package/dist/browser-extension/firefox/content-script.js +0 -2789
- package/dist/browser-extension/firefox/icon128.png +0 -0
- package/dist/browser-extension/firefox/icon16.png +0 -0
- package/dist/browser-extension/firefox/icon48.png +0 -0
- package/dist/browser-extension/firefox/manifest.json +0 -77
- package/dist/browser-extension/firefox/popup.css +0 -761
- package/dist/browser-extension/firefox/popup.html +0 -353
- package/dist/browser-extension/firefox/popup.js +0 -1650
- package/dist/browser-extension/firefox/sidebar.css +0 -1792
- package/dist/browser-extension/firefox/sidebar.html +0 -463
- package/dist/browser-extension/firefox/sidebar.js +0 -26541
- package/dist/browser-extension/icon128.png +0 -0
- package/dist/browser-extension/icon16.png +0 -0
- package/dist/browser-extension/icon48.png +0 -0
- package/dist/browser-extension/manifest.json +0 -66
- package/dist/browser-extension/package.json +0 -41
- package/dist/browser-extension/popup.js +0 -1650
- package/dist/browser-extension/safari/Info.plist +0 -21
- package/dist/browser-extension/safari/ai-worker.js +0 -571
- package/dist/browser-extension/safari/background.js +0 -7245
- package/dist/browser-extension/safari/callback.html +0 -17
- package/dist/browser-extension/safari/content-script.js +0 -2789
- package/dist/browser-extension/safari/icon128.png +0 -0
- package/dist/browser-extension/safari/icon16.png +0 -0
- package/dist/browser-extension/safari/icon48.png +0 -0
- package/dist/browser-extension/safari/popup.css +0 -761
- package/dist/browser-extension/safari/popup.html +0 -353
- package/dist/browser-extension/safari/popup.js +0 -1650
- package/dist/browser-extension/safari/sidebar.css +0 -1792
- package/dist/browser-extension/safari/sidebar.html +0 -463
- package/dist/browser-extension/safari/sidebar.js +0 -26541
- package/dist/browser-extension/sidebar.js +0 -26541
- package/dist/browser-extension/webgpu-ai.js +0 -568
|
@@ -1,568 +0,0 @@
|
|
|
1
|
-
// src/webgpu-ai.ts
|
|
2
|
-
var WebGPUAI = class {
|
|
3
|
-
constructor() {
|
|
4
|
-
this.device = null;
|
|
5
|
-
this.models = /* @__PURE__ */ new Map();
|
|
6
|
-
// ---------------------------------------------------------------------------
|
|
7
|
-
// BPE Tokenizer (byte-level, GPT-2 compatible)
|
|
8
|
-
// ---------------------------------------------------------------------------
|
|
9
|
-
this.byteEncoder = {};
|
|
10
|
-
this.byteDecoder = {};
|
|
11
|
-
this.byteEncoderInitialized = false;
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
// WGSL Compute Shaders
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
this.matmulShader = `
|
|
16
|
-
@group(0) @binding(0) var<storage, read> weights: array<f32>;
|
|
17
|
-
@group(0) @binding(1) var<storage, read> input_tokens: array<f32>;
|
|
18
|
-
@group(0) @binding(2) var<storage, read_write> output_logits: array<f32>;
|
|
19
|
-
@group(0) @binding(3) var<storage, read> params: array<u32>;
|
|
20
|
-
|
|
21
|
-
// params[0] = input_size
|
|
22
|
-
// params[1] = output_size (vocab_size)
|
|
23
|
-
// params[2] = weights_offset
|
|
24
|
-
|
|
25
|
-
@compute @workgroup_size(256)
|
|
26
|
-
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
27
|
-
let out_idx = gid.x;
|
|
28
|
-
let input_size = params[0];
|
|
29
|
-
let output_size = params[1];
|
|
30
|
-
let w_offset = params[2];
|
|
31
|
-
|
|
32
|
-
if (out_idx >= output_size) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
var sum: f32 = 0.0;
|
|
37
|
-
for (var j: u32 = 0u; j < input_size; j = j + 1u) {
|
|
38
|
-
let w_idx = w_offset + out_idx * input_size + j;
|
|
39
|
-
if (w_idx < arrayLength(&weights)) {
|
|
40
|
-
sum = sum + weights[w_idx] * input_tokens[j];
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
output_logits[out_idx] = sum;
|
|
44
|
-
}
|
|
45
|
-
`;
|
|
46
|
-
this.softmaxShader = `
|
|
47
|
-
@group(0) @binding(0) var<storage, read_write> data: array<f32>;
|
|
48
|
-
@group(0) @binding(1) var<storage, read> size_buf: array<u32>;
|
|
49
|
-
|
|
50
|
-
@compute @workgroup_size(1)
|
|
51
|
-
fn main() {
|
|
52
|
-
let size = size_buf[0];
|
|
53
|
-
|
|
54
|
-
var max_val: f32 = data[0];
|
|
55
|
-
for (var i: u32 = 1u; i < size; i = i + 1u) {
|
|
56
|
-
if (data[i] > max_val) {
|
|
57
|
-
max_val = data[i];
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
var sum: f32 = 0.0;
|
|
62
|
-
for (var i: u32 = 0u; i < size; i = i + 1u) {
|
|
63
|
-
let e = exp(data[i] - max_val);
|
|
64
|
-
data[i] = e;
|
|
65
|
-
sum = sum + e;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
for (var i: u32 = 0u; i < size; i = i + 1u) {
|
|
69
|
-
data[i] = data[i] / sum;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
`;
|
|
73
|
-
}
|
|
74
|
-
async initialize() {
|
|
75
|
-
if (!navigator.gpu) {
|
|
76
|
-
console.warn("[Hanzo AI] WebGPU not supported in this browser");
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
try {
|
|
80
|
-
const adapter = await navigator.gpu.requestAdapter({
|
|
81
|
-
powerPreference: "high-performance"
|
|
82
|
-
});
|
|
83
|
-
if (!adapter) {
|
|
84
|
-
console.warn("[Hanzo AI] No GPU adapter found");
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
this.device = await adapter.requestDevice({
|
|
88
|
-
requiredLimits: {
|
|
89
|
-
maxStorageBufferBindingSize: adapter.limits?.maxStorageBufferBindingSize || 134217728,
|
|
90
|
-
maxBufferSize: adapter.limits?.maxBufferSize || 268435456
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
this.device.lost.then((info) => {
|
|
94
|
-
console.error("[Hanzo AI] GPU device lost:", info.message);
|
|
95
|
-
this.device = null;
|
|
96
|
-
});
|
|
97
|
-
console.log("[Hanzo AI] WebGPU initialized successfully");
|
|
98
|
-
const features = adapter.features;
|
|
99
|
-
console.log("[Hanzo AI] GPU Features:", Array.from(features));
|
|
100
|
-
return true;
|
|
101
|
-
} catch (error) {
|
|
102
|
-
console.error("[Hanzo AI] WebGPU initialization failed:", error);
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
initByteEncoder() {
|
|
107
|
-
if (this.byteEncoderInitialized) return;
|
|
108
|
-
const bs = [];
|
|
109
|
-
for (let i = 33; i <= 126; i++) bs.push(i);
|
|
110
|
-
for (let i = 161; i <= 172; i++) bs.push(i);
|
|
111
|
-
for (let i = 174; i <= 255; i++) bs.push(i);
|
|
112
|
-
const cs = bs.slice();
|
|
113
|
-
let n = 0;
|
|
114
|
-
for (let b = 0; b < 256; b++) {
|
|
115
|
-
if (!bs.includes(b)) {
|
|
116
|
-
bs.push(b);
|
|
117
|
-
cs.push(256 + n);
|
|
118
|
-
n++;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
for (let i = 0; i < bs.length; i++) {
|
|
122
|
-
this.byteEncoder[bs[i]] = String.fromCharCode(cs[i]);
|
|
123
|
-
this.byteDecoder[String.fromCharCode(cs[i])] = bs[i];
|
|
124
|
-
}
|
|
125
|
-
this.byteEncoderInitialized = true;
|
|
126
|
-
}
|
|
127
|
-
tokenize(text, model) {
|
|
128
|
-
this.initByteEncoder();
|
|
129
|
-
if (!model.vocab.length) {
|
|
130
|
-
const tokens2 = [];
|
|
131
|
-
for (let i = 0; i < text.length; i++) {
|
|
132
|
-
tokens2.push(text.charCodeAt(i));
|
|
133
|
-
}
|
|
134
|
-
return tokens2;
|
|
135
|
-
}
|
|
136
|
-
const bytes = new TextEncoder().encode(text);
|
|
137
|
-
let word = "";
|
|
138
|
-
for (const b of bytes) {
|
|
139
|
-
word += this.byteEncoder[b] || String.fromCharCode(b);
|
|
140
|
-
}
|
|
141
|
-
const tokens = [];
|
|
142
|
-
let pos = 0;
|
|
143
|
-
while (pos < word.length) {
|
|
144
|
-
let bestLen = 0;
|
|
145
|
-
let bestIdx = -1;
|
|
146
|
-
const maxLen = Math.min(word.length - pos, 50);
|
|
147
|
-
for (let len = maxLen; len >= 1; len--) {
|
|
148
|
-
const candidate = word.substring(pos, pos + len);
|
|
149
|
-
const idx = model.vocabIndex.get(candidate);
|
|
150
|
-
if (idx !== void 0) {
|
|
151
|
-
bestLen = len;
|
|
152
|
-
bestIdx = idx;
|
|
153
|
-
break;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
if (bestIdx !== -1) {
|
|
157
|
-
tokens.push(bestIdx);
|
|
158
|
-
pos += bestLen;
|
|
159
|
-
} else {
|
|
160
|
-
tokens.push(word.charCodeAt(pos) % Math.max(model.vocab.length, 1));
|
|
161
|
-
pos++;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
return tokens;
|
|
165
|
-
}
|
|
166
|
-
detokenize(tokenIds, model) {
|
|
167
|
-
this.initByteEncoder();
|
|
168
|
-
if (!model.vocab.length) {
|
|
169
|
-
const arr = tokenIds instanceof Int32Array ? Array.from(tokenIds) : tokenIds;
|
|
170
|
-
return String.fromCharCode(...arr.filter((t) => t > 0 && t < 65536));
|
|
171
|
-
}
|
|
172
|
-
let byteStr = "";
|
|
173
|
-
for (const id of tokenIds) {
|
|
174
|
-
if (id >= 0 && id < model.vocab.length) {
|
|
175
|
-
byteStr += model.vocab[id];
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
const decoded = [];
|
|
179
|
-
for (const ch of byteStr) {
|
|
180
|
-
if (ch in this.byteDecoder) {
|
|
181
|
-
decoded.push(this.byteDecoder[ch]);
|
|
182
|
-
} else {
|
|
183
|
-
decoded.push(ch.charCodeAt(0) & 255);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return new TextDecoder().decode(new Uint8Array(decoded));
|
|
187
|
-
}
|
|
188
|
-
// ---------------------------------------------------------------------------
|
|
189
|
-
// Model Loading
|
|
190
|
-
// ---------------------------------------------------------------------------
|
|
191
|
-
async loadModel(config) {
|
|
192
|
-
if (!this.device) {
|
|
193
|
-
throw new Error("WebGPU not initialized");
|
|
194
|
-
}
|
|
195
|
-
console.log(`[Hanzo AI] Loading model: ${config.name}`);
|
|
196
|
-
const modelData = await this._fetchWithCache(config.url, config.name);
|
|
197
|
-
const modelBuffer = this.device.createBuffer({
|
|
198
|
-
size: modelData.byteLength,
|
|
199
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
200
|
-
label: `model-${config.name}`
|
|
201
|
-
});
|
|
202
|
-
this.device.queue.writeBuffer(modelBuffer, 0, modelData);
|
|
203
|
-
let vocab = [];
|
|
204
|
-
const vocabIndex = /* @__PURE__ */ new Map();
|
|
205
|
-
if (config.vocabUrl) {
|
|
206
|
-
try {
|
|
207
|
-
const vocabResp = await fetch(config.vocabUrl);
|
|
208
|
-
if (vocabResp.ok) {
|
|
209
|
-
const vocabData = await vocabResp.json();
|
|
210
|
-
if (Array.isArray(vocabData)) {
|
|
211
|
-
vocab = vocabData;
|
|
212
|
-
} else if (typeof vocabData === "object") {
|
|
213
|
-
const entries = Object.entries(vocabData);
|
|
214
|
-
entries.sort((a, b) => a[1] - b[1]);
|
|
215
|
-
vocab = entries.map((e) => e[0]);
|
|
216
|
-
}
|
|
217
|
-
for (let i = 0; i < vocab.length; i++) {
|
|
218
|
-
vocabIndex.set(vocab[i], i);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
} catch (e) {
|
|
222
|
-
console.warn(`[Hanzo AI] Vocabulary load failed for ${config.name}:`, e);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
this.models.set(config.name, {
|
|
226
|
-
buffer: modelBuffer,
|
|
227
|
-
config,
|
|
228
|
-
vocab,
|
|
229
|
-
vocabIndex
|
|
230
|
-
});
|
|
231
|
-
console.log(`[Hanzo AI] Model ${config.name} loaded: ${modelData.byteLength} bytes, vocab: ${vocab.length}`);
|
|
232
|
-
}
|
|
233
|
-
// ---------------------------------------------------------------------------
|
|
234
|
-
// Inference
|
|
235
|
-
// ---------------------------------------------------------------------------
|
|
236
|
-
async runInference(modelName, input) {
|
|
237
|
-
const model = this.models.get(modelName);
|
|
238
|
-
if (!model || !this.device) {
|
|
239
|
-
throw new Error(`Model ${modelName} not loaded or GPU unavailable`);
|
|
240
|
-
}
|
|
241
|
-
const tokens = this.tokenize(input, model);
|
|
242
|
-
const vocabSize = Math.max(model.vocab.length, 256);
|
|
243
|
-
const inputSize = Math.min(tokens.length, 2048);
|
|
244
|
-
const maxTokens = model.config.maxTokens || 128;
|
|
245
|
-
const inputData = new Float32Array(inputSize);
|
|
246
|
-
for (let i = 0; i < inputSize; i++) {
|
|
247
|
-
inputData[i] = tokens[i] / vocabSize;
|
|
248
|
-
}
|
|
249
|
-
const inputBuffer = this.device.createBuffer({
|
|
250
|
-
size: inputData.byteLength,
|
|
251
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
252
|
-
label: "input"
|
|
253
|
-
});
|
|
254
|
-
this.device.queue.writeBuffer(inputBuffer, 0, inputData);
|
|
255
|
-
const outputByteSize = vocabSize * 4;
|
|
256
|
-
const outputBuffer = this.device.createBuffer({
|
|
257
|
-
size: outputByteSize,
|
|
258
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
|
|
259
|
-
label: "output"
|
|
260
|
-
});
|
|
261
|
-
const paramsData = new Uint32Array([inputSize, vocabSize, 0]);
|
|
262
|
-
const paramsBuffer = this.device.createBuffer({
|
|
263
|
-
size: paramsData.byteLength,
|
|
264
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
265
|
-
label: "params"
|
|
266
|
-
});
|
|
267
|
-
this.device.queue.writeBuffer(paramsBuffer, 0, paramsData);
|
|
268
|
-
const readBuffer = this.device.createBuffer({
|
|
269
|
-
size: outputByteSize,
|
|
270
|
-
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
|
|
271
|
-
label: "readback"
|
|
272
|
-
});
|
|
273
|
-
const matmulModule = this.device.createShaderModule({ code: this.matmulShader });
|
|
274
|
-
const matmulLayout = this.device.createBindGroupLayout({
|
|
275
|
-
entries: [
|
|
276
|
-
{ binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
|
|
277
|
-
{ binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
|
|
278
|
-
{ binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: "storage" } },
|
|
279
|
-
{ binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } }
|
|
280
|
-
]
|
|
281
|
-
});
|
|
282
|
-
const matmulPipeline = this.device.createComputePipeline({
|
|
283
|
-
layout: this.device.createPipelineLayout({ bindGroupLayouts: [matmulLayout] }),
|
|
284
|
-
compute: { module: matmulModule, entryPoint: "main" }
|
|
285
|
-
});
|
|
286
|
-
const matmulBindGroup = this.device.createBindGroup({
|
|
287
|
-
layout: matmulLayout,
|
|
288
|
-
entries: [
|
|
289
|
-
{ binding: 0, resource: { buffer: model.buffer } },
|
|
290
|
-
{ binding: 1, resource: { buffer: inputBuffer } },
|
|
291
|
-
{ binding: 2, resource: { buffer: outputBuffer } },
|
|
292
|
-
{ binding: 3, resource: { buffer: paramsBuffer } }
|
|
293
|
-
]
|
|
294
|
-
});
|
|
295
|
-
const softmaxModule = this.device.createShaderModule({ code: this.softmaxShader });
|
|
296
|
-
const softmaxLayout = this.device.createBindGroupLayout({
|
|
297
|
-
entries: [
|
|
298
|
-
{ binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: "storage" } },
|
|
299
|
-
{ binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } }
|
|
300
|
-
]
|
|
301
|
-
});
|
|
302
|
-
const sizeBuf = this.device.createBuffer({
|
|
303
|
-
size: 4,
|
|
304
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
305
|
-
});
|
|
306
|
-
this.device.queue.writeBuffer(sizeBuf, 0, new Uint32Array([vocabSize]));
|
|
307
|
-
const softmaxPipeline = this.device.createComputePipeline({
|
|
308
|
-
layout: this.device.createPipelineLayout({ bindGroupLayouts: [softmaxLayout] }),
|
|
309
|
-
compute: { module: softmaxModule, entryPoint: "main" }
|
|
310
|
-
});
|
|
311
|
-
const softmaxBindGroup = this.device.createBindGroup({
|
|
312
|
-
layout: softmaxLayout,
|
|
313
|
-
entries: [
|
|
314
|
-
{ binding: 0, resource: { buffer: outputBuffer } },
|
|
315
|
-
{ binding: 1, resource: { buffer: sizeBuf } }
|
|
316
|
-
]
|
|
317
|
-
});
|
|
318
|
-
const generatedTokens = [];
|
|
319
|
-
const temperature = 0.7;
|
|
320
|
-
for (let step = 0; step < maxTokens; step++) {
|
|
321
|
-
const encoder = this.device.createCommandEncoder();
|
|
322
|
-
const matmulPass = encoder.beginComputePass();
|
|
323
|
-
matmulPass.setPipeline(matmulPipeline);
|
|
324
|
-
matmulPass.setBindGroup(0, matmulBindGroup);
|
|
325
|
-
matmulPass.dispatchWorkgroups(Math.ceil(vocabSize / 256));
|
|
326
|
-
matmulPass.end();
|
|
327
|
-
const softmaxPass = encoder.beginComputePass();
|
|
328
|
-
softmaxPass.setPipeline(softmaxPipeline);
|
|
329
|
-
softmaxPass.setBindGroup(0, softmaxBindGroup);
|
|
330
|
-
softmaxPass.dispatchWorkgroups(1);
|
|
331
|
-
softmaxPass.end();
|
|
332
|
-
encoder.copyBufferToBuffer(outputBuffer, 0, readBuffer, 0, outputByteSize);
|
|
333
|
-
this.device.queue.submit([encoder.finish()]);
|
|
334
|
-
await readBuffer.mapAsync(GPUMapMode.READ);
|
|
335
|
-
const probs = new Float32Array(readBuffer.getMappedRange().slice(0));
|
|
336
|
-
readBuffer.unmap();
|
|
337
|
-
const nextToken = this.sampleToken(probs, temperature);
|
|
338
|
-
generatedTokens.push(nextToken);
|
|
339
|
-
if (nextToken === 0 || nextToken === 2) break;
|
|
340
|
-
const newInput = new Float32Array([nextToken / vocabSize]);
|
|
341
|
-
this.device.queue.writeBuffer(inputBuffer, 0, newInput);
|
|
342
|
-
paramsData[0] = 1;
|
|
343
|
-
this.device.queue.writeBuffer(paramsBuffer, 0, paramsData);
|
|
344
|
-
}
|
|
345
|
-
inputBuffer.destroy();
|
|
346
|
-
outputBuffer.destroy();
|
|
347
|
-
paramsBuffer.destroy();
|
|
348
|
-
readBuffer.destroy();
|
|
349
|
-
sizeBuf.destroy();
|
|
350
|
-
return this.detokenize(generatedTokens, model);
|
|
351
|
-
}
|
|
352
|
-
sampleToken(probs, temperature) {
|
|
353
|
-
if (temperature <= 0) {
|
|
354
|
-
let maxIdx = 0;
|
|
355
|
-
let maxVal = probs[0];
|
|
356
|
-
for (let i = 1; i < probs.length; i++) {
|
|
357
|
-
if (probs[i] > maxVal) {
|
|
358
|
-
maxVal = probs[i];
|
|
359
|
-
maxIdx = i;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
return maxIdx;
|
|
363
|
-
}
|
|
364
|
-
const scaled = new Float32Array(probs.length);
|
|
365
|
-
let sum = 0;
|
|
366
|
-
for (let i = 0; i < probs.length; i++) {
|
|
367
|
-
scaled[i] = Math.exp(Math.log(Math.max(probs[i], 1e-10)) / temperature);
|
|
368
|
-
sum += scaled[i];
|
|
369
|
-
}
|
|
370
|
-
for (let i = 0; i < scaled.length; i++) {
|
|
371
|
-
scaled[i] /= sum;
|
|
372
|
-
}
|
|
373
|
-
const r = Math.random();
|
|
374
|
-
let cumulative = 0;
|
|
375
|
-
for (let i = 0; i < scaled.length; i++) {
|
|
376
|
-
cumulative += scaled[i];
|
|
377
|
-
if (r < cumulative) return i;
|
|
378
|
-
}
|
|
379
|
-
return scaled.length - 1;
|
|
380
|
-
}
|
|
381
|
-
// ---------------------------------------------------------------------------
|
|
382
|
-
// Embedding computation (for DOM element analysis)
|
|
383
|
-
// ---------------------------------------------------------------------------
|
|
384
|
-
async computeEmbedding(modelName, text) {
|
|
385
|
-
const model = this.models.get(modelName);
|
|
386
|
-
if (!model || !this.device) {
|
|
387
|
-
throw new Error(`Model ${modelName} not loaded or GPU unavailable`);
|
|
388
|
-
}
|
|
389
|
-
const tokens = this.tokenize(text, model);
|
|
390
|
-
const inputSize = Math.min(tokens.length, 512);
|
|
391
|
-
const embeddingDim = 256;
|
|
392
|
-
const inputData = new Float32Array(inputSize);
|
|
393
|
-
for (let i = 0; i < inputSize; i++) {
|
|
394
|
-
inputData[i] = tokens[i];
|
|
395
|
-
}
|
|
396
|
-
const inputBuffer = this.device.createBuffer({
|
|
397
|
-
size: inputData.byteLength,
|
|
398
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
399
|
-
});
|
|
400
|
-
this.device.queue.writeBuffer(inputBuffer, 0, inputData);
|
|
401
|
-
const embeddingBuffer = this.device.createBuffer({
|
|
402
|
-
size: embeddingDim * 4,
|
|
403
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
|
|
404
|
-
});
|
|
405
|
-
const paramData = new Uint32Array([inputSize, embeddingDim, 0]);
|
|
406
|
-
const paramBuffer = this.device.createBuffer({
|
|
407
|
-
size: paramData.byteLength,
|
|
408
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
409
|
-
});
|
|
410
|
-
this.device.queue.writeBuffer(paramBuffer, 0, paramData);
|
|
411
|
-
const shaderModule = this.device.createShaderModule({ code: this.matmulShader });
|
|
412
|
-
const layout = this.device.createBindGroupLayout({
|
|
413
|
-
entries: [
|
|
414
|
-
{ binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
|
|
415
|
-
{ binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
|
|
416
|
-
{ binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: "storage" } },
|
|
417
|
-
{ binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } }
|
|
418
|
-
]
|
|
419
|
-
});
|
|
420
|
-
const pipeline = this.device.createComputePipeline({
|
|
421
|
-
layout: this.device.createPipelineLayout({ bindGroupLayouts: [layout] }),
|
|
422
|
-
compute: { module: shaderModule, entryPoint: "main" }
|
|
423
|
-
});
|
|
424
|
-
const bindGroup = this.device.createBindGroup({
|
|
425
|
-
layout,
|
|
426
|
-
entries: [
|
|
427
|
-
{ binding: 0, resource: { buffer: model.buffer } },
|
|
428
|
-
{ binding: 1, resource: { buffer: inputBuffer } },
|
|
429
|
-
{ binding: 2, resource: { buffer: embeddingBuffer } },
|
|
430
|
-
{ binding: 3, resource: { buffer: paramBuffer } }
|
|
431
|
-
]
|
|
432
|
-
});
|
|
433
|
-
const readBuffer = this.device.createBuffer({
|
|
434
|
-
size: embeddingDim * 4,
|
|
435
|
-
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
|
|
436
|
-
});
|
|
437
|
-
const encoder = this.device.createCommandEncoder();
|
|
438
|
-
const pass = encoder.beginComputePass();
|
|
439
|
-
pass.setPipeline(pipeline);
|
|
440
|
-
pass.setBindGroup(0, bindGroup);
|
|
441
|
-
pass.dispatchWorkgroups(Math.ceil(embeddingDim / 256));
|
|
442
|
-
pass.end();
|
|
443
|
-
encoder.copyBufferToBuffer(embeddingBuffer, 0, readBuffer, 0, embeddingDim * 4);
|
|
444
|
-
this.device.queue.submit([encoder.finish()]);
|
|
445
|
-
await readBuffer.mapAsync(GPUMapMode.READ);
|
|
446
|
-
const embedding = new Float32Array(readBuffer.getMappedRange().slice(0));
|
|
447
|
-
readBuffer.unmap();
|
|
448
|
-
let norm = 0;
|
|
449
|
-
for (let i = 0; i < embedding.length; i++) {
|
|
450
|
-
norm += embedding[i] * embedding[i];
|
|
451
|
-
}
|
|
452
|
-
norm = Math.sqrt(norm);
|
|
453
|
-
if (norm > 0) {
|
|
454
|
-
for (let i = 0; i < embedding.length; i++) {
|
|
455
|
-
embedding[i] /= norm;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
inputBuffer.destroy();
|
|
459
|
-
embeddingBuffer.destroy();
|
|
460
|
-
paramBuffer.destroy();
|
|
461
|
-
readBuffer.destroy();
|
|
462
|
-
return embedding;
|
|
463
|
-
}
|
|
464
|
-
// ---------------------------------------------------------------------------
|
|
465
|
-
// Cached, progress-reporting fetch — used by loadModel().
|
|
466
|
-
// ---------------------------------------------------------------------------
|
|
467
|
-
/**
|
|
468
|
-
* Fetch a model blob with persistent caching. Backed by the service-worker
|
|
469
|
-
* Cache API so the bytes survive worker restarts and only download once
|
|
470
|
-
* per URL. Streams the body to log progress every ~5% so big artifacts
|
|
471
|
-
* (huggingface.co/<repo>/resolve/main/<file>) don't appear to hang.
|
|
472
|
-
*
|
|
473
|
-
* Cache invalidation: the URL is the key; bump the URL (e.g. point to a
|
|
474
|
-
* different revision sha) to force a re-fetch. Use ``WebGPUAI.evictCache``
|
|
475
|
-
* to wipe everything when models go bad.
|
|
476
|
-
*/
|
|
477
|
-
async _fetchWithCache(url, label) {
|
|
478
|
-
const cacheName = "hanzo-webgpu-models-v1";
|
|
479
|
-
const cache = typeof caches !== "undefined" ? await caches.open(cacheName) : null;
|
|
480
|
-
if (cache) {
|
|
481
|
-
const hit = await cache.match(url);
|
|
482
|
-
if (hit) {
|
|
483
|
-
console.log(`[Hanzo AI] Cache hit: ${label} (${url})`);
|
|
484
|
-
return await hit.arrayBuffer();
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
console.log(`[Hanzo AI] Fetching: ${label} (${url})`);
|
|
488
|
-
const response = await fetch(url);
|
|
489
|
-
if (!response.ok) {
|
|
490
|
-
throw new Error(`Failed to fetch ${label}: ${response.status} ${response.statusText}`);
|
|
491
|
-
}
|
|
492
|
-
const total = Number(response.headers.get("content-length") || 0);
|
|
493
|
-
let received = 0;
|
|
494
|
-
let nextLog = total > 0 ? Math.floor(total * 0.05) : Infinity;
|
|
495
|
-
const chunks = [];
|
|
496
|
-
if (response.body) {
|
|
497
|
-
const reader = response.body.getReader();
|
|
498
|
-
while (true) {
|
|
499
|
-
const { done, value } = await reader.read();
|
|
500
|
-
if (done) break;
|
|
501
|
-
chunks.push(value);
|
|
502
|
-
received += value.byteLength;
|
|
503
|
-
if (total > 0 && received >= nextLog) {
|
|
504
|
-
const pct = Math.floor(received / total * 100);
|
|
505
|
-
console.log(`[Hanzo AI] ${label}: ${pct}% (${received}/${total} bytes)`);
|
|
506
|
-
nextLog = received + Math.floor(total * 0.05);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
} else {
|
|
510
|
-
chunks.push(new Uint8Array(await response.arrayBuffer()));
|
|
511
|
-
}
|
|
512
|
-
const blob = new Uint8Array(received || chunks.reduce((n, c) => n + c.byteLength, 0));
|
|
513
|
-
let offset = 0;
|
|
514
|
-
for (const c of chunks) {
|
|
515
|
-
blob.set(c, offset);
|
|
516
|
-
offset += c.byteLength;
|
|
517
|
-
}
|
|
518
|
-
console.log(`[Hanzo AI] Fetched: ${label} (${blob.byteLength} bytes)`);
|
|
519
|
-
if (cache) {
|
|
520
|
-
try {
|
|
521
|
-
await cache.put(url, new Response(blob, {
|
|
522
|
-
headers: { "content-type": "application/octet-stream" }
|
|
523
|
-
}));
|
|
524
|
-
console.log(`[Hanzo AI] Cached: ${label}`);
|
|
525
|
-
} catch (e) {
|
|
526
|
-
console.warn(`[Hanzo AI] Cache write failed for ${label}:`, e);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
return blob.buffer;
|
|
530
|
-
}
|
|
531
|
-
/** Build an HF resolve URL for a given repo / file / revision. */
|
|
532
|
-
static huggingFaceURL(repo, file, revision = "main") {
|
|
533
|
-
return `https://huggingface.co/${repo}/resolve/${revision}/${file}`;
|
|
534
|
-
}
|
|
535
|
-
/** Wipe every cached model blob. Useful when a model artifact is rotated. */
|
|
536
|
-
static async evictCache() {
|
|
537
|
-
if (typeof caches === "undefined") return;
|
|
538
|
-
await caches.delete("hanzo-webgpu-models-v1");
|
|
539
|
-
console.log("[Hanzo AI] WebGPU model cache evicted");
|
|
540
|
-
}
|
|
541
|
-
// ---------------------------------------------------------------------------
|
|
542
|
-
// Status / Cleanup
|
|
543
|
-
// ---------------------------------------------------------------------------
|
|
544
|
-
getStatus() {
|
|
545
|
-
return {
|
|
546
|
-
initialized: !!this.device,
|
|
547
|
-
models: Array.from(this.models.keys())
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
async unloadModel(name) {
|
|
551
|
-
const model = this.models.get(name);
|
|
552
|
-
if (model) {
|
|
553
|
-
model.buffer.destroy();
|
|
554
|
-
this.models.delete(name);
|
|
555
|
-
console.log(`[Hanzo AI] Model ${name} unloaded`);
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
destroy() {
|
|
559
|
-
for (const [name, model] of this.models) {
|
|
560
|
-
model.buffer.destroy();
|
|
561
|
-
}
|
|
562
|
-
this.models.clear();
|
|
563
|
-
this.device = null;
|
|
564
|
-
}
|
|
565
|
-
};
|
|
566
|
-
export {
|
|
567
|
-
WebGPUAI
|
|
568
|
-
};
|