@hanzo/browser-extension 1.7.6 → 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/LICENSE +9 -18
- package/dist/browser-extension/content-script.js +1642 -123
- package/images/icon128.png +0 -0
- package/images/icon16.png +0 -0
- package/images/icon48.png +0 -0
- package/manifest.json +7 -2
- package/package.json +28 -10
- package/src/answer/AnswerEngine.tsx +553 -0
- package/src/chat-widget.tsx +96 -0
- package/src/gui-primitives.tsx +116 -0
- package/src/icon128.png +0 -0
- package/src/icon16.png +0 -0
- package/src/icon32.png +0 -0
- package/src/icon48.png +0 -0
- package/src/manifest-firefox.json +32 -13
- package/src/manifest.json +35 -11
- package/src/newtab.css +277 -0
- package/src/newtab.html +13 -0
- package/src/package.json +1 -1
- package/src/popup.css +352 -110
- package/src/popup.html +186 -47
- package/src/popup.js +196 -0
- package/src/sidebar.css +772 -156
- package/src/sidebar.html +268 -85
- package/src/sidebar.js +885 -0
- package/.github/workflows/docs.yml +0 -44
- package/.github/workflows/release.yml +0 -55
- package/README.md +0 -126
- package/dist/browser-extension/README.md +0 -43
- package/dist/browser-extension/ai-worker.js +0 -30
- package/dist/browser-extension/background-firefox.js +0 -875
- package/dist/browser-extension/background.js +0 -2732
- package/dist/browser-extension/browser-control.js +0 -683
- package/dist/browser-extension/cdp-bridge-server.js +0 -444
- package/dist/browser-extension/chrome/ai-worker.js +0 -571
- package/dist/browser-extension/chrome/background.js +0 -2732
- package/dist/browser-extension/chrome/callback.html +0 -17
- package/dist/browser-extension/chrome/content-script.js +0 -1270
- package/dist/browser-extension/chrome/icon128.png +0 -0
- package/dist/browser-extension/chrome/icon16.png +0 -0
- package/dist/browser-extension/chrome/icon32.png +0 -0
- package/dist/browser-extension/chrome/icon48.png +0 -0
- package/dist/browser-extension/chrome/manifest.json +0 -57
- package/dist/browser-extension/chrome/popup.css +0 -519
- package/dist/browser-extension/chrome/popup.html +0 -242
- package/dist/browser-extension/chrome/popup.js +0 -278
- package/dist/browser-extension/chrome/sidebar.css +0 -1176
- package/dist/browser-extension/chrome/sidebar.html +0 -280
- package/dist/browser-extension/chrome/sidebar.js +0 -779
- 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 -875
- package/dist/browser-extension/firefox/callback.html +0 -17
- package/dist/browser-extension/firefox/content-script.js +0 -1270
- package/dist/browser-extension/firefox/icon128.png +0 -0
- package/dist/browser-extension/firefox/icon16.png +0 -0
- package/dist/browser-extension/firefox/icon32.png +0 -0
- package/dist/browser-extension/firefox/icon48.png +0 -0
- package/dist/browser-extension/firefox/manifest.json +0 -66
- package/dist/browser-extension/firefox/popup.css +0 -519
- package/dist/browser-extension/firefox/popup.html +0 -242
- package/dist/browser-extension/firefox/popup.js +0 -278
- package/dist/browser-extension/firefox/sidebar.css +0 -1176
- package/dist/browser-extension/firefox/sidebar.html +0 -280
- package/dist/browser-extension/firefox/sidebar.js +0 -779
- package/dist/browser-extension/hanzo-browser-extension-firefox-1.1.0.zip +0 -0
- package/dist/browser-extension/icon128.png +0 -0
- package/dist/browser-extension/icon16.png +0 -0
- package/dist/browser-extension/icon32.png +0 -0
- package/dist/browser-extension/icon48.png +0 -0
- package/dist/browser-extension/manifest.json +0 -57
- package/dist/browser-extension/package.json +0 -41
- package/dist/browser-extension/popup.css +0 -468
- package/dist/browser-extension/popup.html +0 -191
- package/dist/browser-extension/popup.js +0 -278
- 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 -2732
- package/dist/browser-extension/safari/callback.html +0 -17
- package/dist/browser-extension/safari/content-script.js +0 -1270
- package/dist/browser-extension/safari/icon128.png +0 -0
- package/dist/browser-extension/safari/icon16.png +0 -0
- package/dist/browser-extension/safari/icon32.png +0 -0
- package/dist/browser-extension/safari/icon48.png +0 -0
- package/dist/browser-extension/safari/popup.css +0 -519
- package/dist/browser-extension/safari/popup.html +0 -242
- package/dist/browser-extension/safari/popup.js +0 -278
- package/dist/browser-extension/safari/sidebar.css +0 -1176
- package/dist/browser-extension/safari/sidebar.html +0 -280
- package/dist/browser-extension/safari/sidebar.js +0 -779
- package/dist/browser-extension/sidebar.css +0 -718
- package/dist/browser-extension/sidebar.html +0 -184
- package/dist/browser-extension/sidebar.js +0 -779
- package/dist/browser-extension/webgpu-ai.js +0 -495
- package/dist/cdp-bridge-server.js +0 -411
- package/dist/hanzo-ai-chrome-1.7.2.zip +0 -0
- package/dist/hanzo-ai-chrome-1.7.3.zip +0 -0
- package/dist/hanzo-ai-firefox-1.7.2.zip +0 -0
- package/dist/hanzo-ai-firefox-1.7.3.zip +0 -0
- package/dist/safari/Hanzo AI/Hanzo AI.xcodeproj/project.pbxproj +0 -1047
- package/dist/safari/Hanzo AI/Hanzo AI.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AccentColor.colorset/Contents.json +0 -11
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/Contents.json +0 -98
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@1x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-128@2x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@1x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-16@2x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@1x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-256@2x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@1x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-32@2x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@1x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/mac-icon-512@2x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/AppIcon.appiconset/universal-icon-1024@1x.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/Contents.json +0 -6
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/LargeIcon.imageset/Contents.json +0 -21
- package/dist/safari/Hanzo AI/Shared (App)/Assets.xcassets/LargeIcon.imageset/icon128.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Resources/Base.lproj/Main.html +0 -20
- package/dist/safari/Hanzo AI/Shared (App)/Resources/Icon.png +0 -0
- package/dist/safari/Hanzo AI/Shared (App)/Resources/Script.js +0 -24
- package/dist/safari/Hanzo AI/Shared (App)/Resources/Style.css +0 -61
- package/dist/safari/Hanzo AI/Shared (App)/ViewController.swift +0 -81
- package/dist/safari/Hanzo AI/Shared (Extension)/SafariWebExtensionHandler.swift +0 -42
- package/dist/safari/Hanzo AI/iOS (App)/AppDelegate.swift +0 -24
- package/dist/safari/Hanzo AI/iOS (App)/Base.lproj/LaunchScreen.storyboard +0 -36
- package/dist/safari/Hanzo AI/iOS (App)/Base.lproj/Main.storyboard +0 -38
- package/dist/safari/Hanzo AI/iOS (App)/Info.plist +0 -27
- package/dist/safari/Hanzo AI/iOS (App)/SceneDelegate.swift +0 -18
- package/dist/safari/Hanzo AI/iOS (Extension)/Info.plist +0 -13
- package/dist/safari/Hanzo AI/macOS (App)/AppDelegate.swift +0 -21
- package/dist/safari/Hanzo AI/macOS (App)/Base.lproj/Main.storyboard +0 -124
- package/dist/safari/Hanzo AI/macOS (App)/Info.plist +0 -8
- package/dist/safari/Hanzo AI/macOS (Extension)/Info.plist +0 -13
- package/docs/_config.yml +0 -13
- package/docs/_layouts/default.html +0 -435
- package/docs/index.md +0 -54
- package/hanzo-ai-chrome-v1.7.6.zip +0 -0
- package/hanzo-ai-firefox-v1.7.6.zip +0 -0
- package/install-extension.sh +0 -75
- package/test/claude-integration.test.ts +0 -251
- package/test/server-frameworks-edge-cases.test.ts +0 -145
- package/test/server-frameworks.test.ts +0 -395
- package/test-cdp-connection.js +0 -140
- package/tsconfig.json +0 -19
|
@@ -1,495 +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 response = await fetch(config.url);
|
|
197
|
-
if (!response.ok) {
|
|
198
|
-
throw new Error(`Failed to fetch model ${config.name}: ${response.status}`);
|
|
199
|
-
}
|
|
200
|
-
const modelData = await response.arrayBuffer();
|
|
201
|
-
const modelBuffer = this.device.createBuffer({
|
|
202
|
-
size: modelData.byteLength,
|
|
203
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
204
|
-
label: `model-${config.name}`
|
|
205
|
-
});
|
|
206
|
-
this.device.queue.writeBuffer(modelBuffer, 0, modelData);
|
|
207
|
-
let vocab = [];
|
|
208
|
-
const vocabIndex = /* @__PURE__ */ new Map();
|
|
209
|
-
if (config.vocabUrl) {
|
|
210
|
-
try {
|
|
211
|
-
const vocabResp = await fetch(config.vocabUrl);
|
|
212
|
-
if (vocabResp.ok) {
|
|
213
|
-
const vocabData = await vocabResp.json();
|
|
214
|
-
if (Array.isArray(vocabData)) {
|
|
215
|
-
vocab = vocabData;
|
|
216
|
-
} else if (typeof vocabData === "object") {
|
|
217
|
-
const entries = Object.entries(vocabData);
|
|
218
|
-
entries.sort((a, b) => a[1] - b[1]);
|
|
219
|
-
vocab = entries.map((e) => e[0]);
|
|
220
|
-
}
|
|
221
|
-
for (let i = 0; i < vocab.length; i++) {
|
|
222
|
-
vocabIndex.set(vocab[i], i);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
} catch (e) {
|
|
226
|
-
console.warn(`[Hanzo AI] Vocabulary load failed for ${config.name}:`, e);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
this.models.set(config.name, {
|
|
230
|
-
buffer: modelBuffer,
|
|
231
|
-
config,
|
|
232
|
-
vocab,
|
|
233
|
-
vocabIndex
|
|
234
|
-
});
|
|
235
|
-
console.log(`[Hanzo AI] Model ${config.name} loaded: ${modelData.byteLength} bytes, vocab: ${vocab.length}`);
|
|
236
|
-
}
|
|
237
|
-
// ---------------------------------------------------------------------------
|
|
238
|
-
// Inference
|
|
239
|
-
// ---------------------------------------------------------------------------
|
|
240
|
-
async runInference(modelName, input) {
|
|
241
|
-
const model = this.models.get(modelName);
|
|
242
|
-
if (!model || !this.device) {
|
|
243
|
-
throw new Error(`Model ${modelName} not loaded or GPU unavailable`);
|
|
244
|
-
}
|
|
245
|
-
const tokens = this.tokenize(input, model);
|
|
246
|
-
const vocabSize = Math.max(model.vocab.length, 256);
|
|
247
|
-
const inputSize = Math.min(tokens.length, 2048);
|
|
248
|
-
const maxTokens = model.config.maxTokens || 128;
|
|
249
|
-
const inputData = new Float32Array(inputSize);
|
|
250
|
-
for (let i = 0; i < inputSize; i++) {
|
|
251
|
-
inputData[i] = tokens[i] / vocabSize;
|
|
252
|
-
}
|
|
253
|
-
const inputBuffer = this.device.createBuffer({
|
|
254
|
-
size: inputData.byteLength,
|
|
255
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
256
|
-
label: "input"
|
|
257
|
-
});
|
|
258
|
-
this.device.queue.writeBuffer(inputBuffer, 0, inputData);
|
|
259
|
-
const outputByteSize = vocabSize * 4;
|
|
260
|
-
const outputBuffer = this.device.createBuffer({
|
|
261
|
-
size: outputByteSize,
|
|
262
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
|
|
263
|
-
label: "output"
|
|
264
|
-
});
|
|
265
|
-
const paramsData = new Uint32Array([inputSize, vocabSize, 0]);
|
|
266
|
-
const paramsBuffer = this.device.createBuffer({
|
|
267
|
-
size: paramsData.byteLength,
|
|
268
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
269
|
-
label: "params"
|
|
270
|
-
});
|
|
271
|
-
this.device.queue.writeBuffer(paramsBuffer, 0, paramsData);
|
|
272
|
-
const readBuffer = this.device.createBuffer({
|
|
273
|
-
size: outputByteSize,
|
|
274
|
-
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
|
|
275
|
-
label: "readback"
|
|
276
|
-
});
|
|
277
|
-
const matmulModule = this.device.createShaderModule({ code: this.matmulShader });
|
|
278
|
-
const matmulLayout = this.device.createBindGroupLayout({
|
|
279
|
-
entries: [
|
|
280
|
-
{ binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
|
|
281
|
-
{ binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
|
|
282
|
-
{ binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: "storage" } },
|
|
283
|
-
{ binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } }
|
|
284
|
-
]
|
|
285
|
-
});
|
|
286
|
-
const matmulPipeline = this.device.createComputePipeline({
|
|
287
|
-
layout: this.device.createPipelineLayout({ bindGroupLayouts: [matmulLayout] }),
|
|
288
|
-
compute: { module: matmulModule, entryPoint: "main" }
|
|
289
|
-
});
|
|
290
|
-
const matmulBindGroup = this.device.createBindGroup({
|
|
291
|
-
layout: matmulLayout,
|
|
292
|
-
entries: [
|
|
293
|
-
{ binding: 0, resource: { buffer: model.buffer } },
|
|
294
|
-
{ binding: 1, resource: { buffer: inputBuffer } },
|
|
295
|
-
{ binding: 2, resource: { buffer: outputBuffer } },
|
|
296
|
-
{ binding: 3, resource: { buffer: paramsBuffer } }
|
|
297
|
-
]
|
|
298
|
-
});
|
|
299
|
-
const softmaxModule = this.device.createShaderModule({ code: this.softmaxShader });
|
|
300
|
-
const softmaxLayout = this.device.createBindGroupLayout({
|
|
301
|
-
entries: [
|
|
302
|
-
{ binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: "storage" } },
|
|
303
|
-
{ binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } }
|
|
304
|
-
]
|
|
305
|
-
});
|
|
306
|
-
const sizeBuf = this.device.createBuffer({
|
|
307
|
-
size: 4,
|
|
308
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
309
|
-
});
|
|
310
|
-
this.device.queue.writeBuffer(sizeBuf, 0, new Uint32Array([vocabSize]));
|
|
311
|
-
const softmaxPipeline = this.device.createComputePipeline({
|
|
312
|
-
layout: this.device.createPipelineLayout({ bindGroupLayouts: [softmaxLayout] }),
|
|
313
|
-
compute: { module: softmaxModule, entryPoint: "main" }
|
|
314
|
-
});
|
|
315
|
-
const softmaxBindGroup = this.device.createBindGroup({
|
|
316
|
-
layout: softmaxLayout,
|
|
317
|
-
entries: [
|
|
318
|
-
{ binding: 0, resource: { buffer: outputBuffer } },
|
|
319
|
-
{ binding: 1, resource: { buffer: sizeBuf } }
|
|
320
|
-
]
|
|
321
|
-
});
|
|
322
|
-
const generatedTokens = [];
|
|
323
|
-
const temperature = 0.7;
|
|
324
|
-
for (let step = 0; step < maxTokens; step++) {
|
|
325
|
-
const encoder = this.device.createCommandEncoder();
|
|
326
|
-
const matmulPass = encoder.beginComputePass();
|
|
327
|
-
matmulPass.setPipeline(matmulPipeline);
|
|
328
|
-
matmulPass.setBindGroup(0, matmulBindGroup);
|
|
329
|
-
matmulPass.dispatchWorkgroups(Math.ceil(vocabSize / 256));
|
|
330
|
-
matmulPass.end();
|
|
331
|
-
const softmaxPass = encoder.beginComputePass();
|
|
332
|
-
softmaxPass.setPipeline(softmaxPipeline);
|
|
333
|
-
softmaxPass.setBindGroup(0, softmaxBindGroup);
|
|
334
|
-
softmaxPass.dispatchWorkgroups(1);
|
|
335
|
-
softmaxPass.end();
|
|
336
|
-
encoder.copyBufferToBuffer(outputBuffer, 0, readBuffer, 0, outputByteSize);
|
|
337
|
-
this.device.queue.submit([encoder.finish()]);
|
|
338
|
-
await readBuffer.mapAsync(GPUMapMode.READ);
|
|
339
|
-
const probs = new Float32Array(readBuffer.getMappedRange().slice(0));
|
|
340
|
-
readBuffer.unmap();
|
|
341
|
-
const nextToken = this.sampleToken(probs, temperature);
|
|
342
|
-
generatedTokens.push(nextToken);
|
|
343
|
-
if (nextToken === 0 || nextToken === 2) break;
|
|
344
|
-
const newInput = new Float32Array([nextToken / vocabSize]);
|
|
345
|
-
this.device.queue.writeBuffer(inputBuffer, 0, newInput);
|
|
346
|
-
paramsData[0] = 1;
|
|
347
|
-
this.device.queue.writeBuffer(paramsBuffer, 0, paramsData);
|
|
348
|
-
}
|
|
349
|
-
inputBuffer.destroy();
|
|
350
|
-
outputBuffer.destroy();
|
|
351
|
-
paramsBuffer.destroy();
|
|
352
|
-
readBuffer.destroy();
|
|
353
|
-
sizeBuf.destroy();
|
|
354
|
-
return this.detokenize(generatedTokens, model);
|
|
355
|
-
}
|
|
356
|
-
sampleToken(probs, temperature) {
|
|
357
|
-
if (temperature <= 0) {
|
|
358
|
-
let maxIdx = 0;
|
|
359
|
-
let maxVal = probs[0];
|
|
360
|
-
for (let i = 1; i < probs.length; i++) {
|
|
361
|
-
if (probs[i] > maxVal) {
|
|
362
|
-
maxVal = probs[i];
|
|
363
|
-
maxIdx = i;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
return maxIdx;
|
|
367
|
-
}
|
|
368
|
-
const scaled = new Float32Array(probs.length);
|
|
369
|
-
let sum = 0;
|
|
370
|
-
for (let i = 0; i < probs.length; i++) {
|
|
371
|
-
scaled[i] = Math.exp(Math.log(Math.max(probs[i], 1e-10)) / temperature);
|
|
372
|
-
sum += scaled[i];
|
|
373
|
-
}
|
|
374
|
-
for (let i = 0; i < scaled.length; i++) {
|
|
375
|
-
scaled[i] /= sum;
|
|
376
|
-
}
|
|
377
|
-
const r = Math.random();
|
|
378
|
-
let cumulative = 0;
|
|
379
|
-
for (let i = 0; i < scaled.length; i++) {
|
|
380
|
-
cumulative += scaled[i];
|
|
381
|
-
if (r < cumulative) return i;
|
|
382
|
-
}
|
|
383
|
-
return scaled.length - 1;
|
|
384
|
-
}
|
|
385
|
-
// ---------------------------------------------------------------------------
|
|
386
|
-
// Embedding computation (for DOM element analysis)
|
|
387
|
-
// ---------------------------------------------------------------------------
|
|
388
|
-
async computeEmbedding(modelName, text) {
|
|
389
|
-
const model = this.models.get(modelName);
|
|
390
|
-
if (!model || !this.device) {
|
|
391
|
-
throw new Error(`Model ${modelName} not loaded or GPU unavailable`);
|
|
392
|
-
}
|
|
393
|
-
const tokens = this.tokenize(text, model);
|
|
394
|
-
const inputSize = Math.min(tokens.length, 512);
|
|
395
|
-
const embeddingDim = 256;
|
|
396
|
-
const inputData = new Float32Array(inputSize);
|
|
397
|
-
for (let i = 0; i < inputSize; i++) {
|
|
398
|
-
inputData[i] = tokens[i];
|
|
399
|
-
}
|
|
400
|
-
const inputBuffer = this.device.createBuffer({
|
|
401
|
-
size: inputData.byteLength,
|
|
402
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
403
|
-
});
|
|
404
|
-
this.device.queue.writeBuffer(inputBuffer, 0, inputData);
|
|
405
|
-
const embeddingBuffer = this.device.createBuffer({
|
|
406
|
-
size: embeddingDim * 4,
|
|
407
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST
|
|
408
|
-
});
|
|
409
|
-
const paramData = new Uint32Array([inputSize, embeddingDim, 0]);
|
|
410
|
-
const paramBuffer = this.device.createBuffer({
|
|
411
|
-
size: paramData.byteLength,
|
|
412
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
413
|
-
});
|
|
414
|
-
this.device.queue.writeBuffer(paramBuffer, 0, paramData);
|
|
415
|
-
const shaderModule = this.device.createShaderModule({ code: this.matmulShader });
|
|
416
|
-
const layout = this.device.createBindGroupLayout({
|
|
417
|
-
entries: [
|
|
418
|
-
{ binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
|
|
419
|
-
{ binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
|
|
420
|
-
{ binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: "storage" } },
|
|
421
|
-
{ binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } }
|
|
422
|
-
]
|
|
423
|
-
});
|
|
424
|
-
const pipeline = this.device.createComputePipeline({
|
|
425
|
-
layout: this.device.createPipelineLayout({ bindGroupLayouts: [layout] }),
|
|
426
|
-
compute: { module: shaderModule, entryPoint: "main" }
|
|
427
|
-
});
|
|
428
|
-
const bindGroup = this.device.createBindGroup({
|
|
429
|
-
layout,
|
|
430
|
-
entries: [
|
|
431
|
-
{ binding: 0, resource: { buffer: model.buffer } },
|
|
432
|
-
{ binding: 1, resource: { buffer: inputBuffer } },
|
|
433
|
-
{ binding: 2, resource: { buffer: embeddingBuffer } },
|
|
434
|
-
{ binding: 3, resource: { buffer: paramBuffer } }
|
|
435
|
-
]
|
|
436
|
-
});
|
|
437
|
-
const readBuffer = this.device.createBuffer({
|
|
438
|
-
size: embeddingDim * 4,
|
|
439
|
-
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
|
|
440
|
-
});
|
|
441
|
-
const encoder = this.device.createCommandEncoder();
|
|
442
|
-
const pass = encoder.beginComputePass();
|
|
443
|
-
pass.setPipeline(pipeline);
|
|
444
|
-
pass.setBindGroup(0, bindGroup);
|
|
445
|
-
pass.dispatchWorkgroups(Math.ceil(embeddingDim / 256));
|
|
446
|
-
pass.end();
|
|
447
|
-
encoder.copyBufferToBuffer(embeddingBuffer, 0, readBuffer, 0, embeddingDim * 4);
|
|
448
|
-
this.device.queue.submit([encoder.finish()]);
|
|
449
|
-
await readBuffer.mapAsync(GPUMapMode.READ);
|
|
450
|
-
const embedding = new Float32Array(readBuffer.getMappedRange().slice(0));
|
|
451
|
-
readBuffer.unmap();
|
|
452
|
-
let norm = 0;
|
|
453
|
-
for (let i = 0; i < embedding.length; i++) {
|
|
454
|
-
norm += embedding[i] * embedding[i];
|
|
455
|
-
}
|
|
456
|
-
norm = Math.sqrt(norm);
|
|
457
|
-
if (norm > 0) {
|
|
458
|
-
for (let i = 0; i < embedding.length; i++) {
|
|
459
|
-
embedding[i] /= norm;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
inputBuffer.destroy();
|
|
463
|
-
embeddingBuffer.destroy();
|
|
464
|
-
paramBuffer.destroy();
|
|
465
|
-
readBuffer.destroy();
|
|
466
|
-
return embedding;
|
|
467
|
-
}
|
|
468
|
-
// ---------------------------------------------------------------------------
|
|
469
|
-
// Status / Cleanup
|
|
470
|
-
// ---------------------------------------------------------------------------
|
|
471
|
-
getStatus() {
|
|
472
|
-
return {
|
|
473
|
-
initialized: !!this.device,
|
|
474
|
-
models: Array.from(this.models.keys())
|
|
475
|
-
};
|
|
476
|
-
}
|
|
477
|
-
async unloadModel(name) {
|
|
478
|
-
const model = this.models.get(name);
|
|
479
|
-
if (model) {
|
|
480
|
-
model.buffer.destroy();
|
|
481
|
-
this.models.delete(name);
|
|
482
|
-
console.log(`[Hanzo AI] Model ${name} unloaded`);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
destroy() {
|
|
486
|
-
for (const [name, model] of this.models) {
|
|
487
|
-
model.buffer.destroy();
|
|
488
|
-
}
|
|
489
|
-
this.models.clear();
|
|
490
|
-
this.device = null;
|
|
491
|
-
}
|
|
492
|
-
};
|
|
493
|
-
export {
|
|
494
|
-
WebGPUAI
|
|
495
|
-
};
|