@buley/neural 2.0.4 → 4.0.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/dist/neural.js ADDED
@@ -0,0 +1,539 @@
1
+ var b = Object.defineProperty;
2
+ var m = (c, e, i) => e in c ? b(c, e, { enumerable: !0, configurable: !0, writable: !0, value: i }) : c[e] = i;
3
+ var r = (c, e, i) => m(c, typeof e != "symbol" ? e + "" : e, i);
4
+ import { dash as h } from "@buley/dash";
5
+ async function y() {
6
+ console.log("Initializing Neural Schema..."), await h.execute(`
7
+ CREATE TABLE IF NOT EXISTS neurons (
8
+ id TEXT PRIMARY KEY,
9
+ type TEXT NOT NULL,
10
+ bias REAL DEFAULT 0.0,
11
+ activation TEXT DEFAULT 'tanh',
12
+ created_at INTEGER DEFAULT (unixepoch())
13
+ )
14
+ `), await h.execute(`
15
+ CREATE TABLE IF NOT EXISTS synapses (
16
+ id TEXT PRIMARY KEY,
17
+ from_id TEXT NOT NULL,
18
+ to_id TEXT NOT NULL,
19
+ weight REAL DEFAULT 0.0,
20
+ created_at INTEGER DEFAULT (unixepoch()),
21
+ FOREIGN KEY(from_id) REFERENCES neurons(id),
22
+ FOREIGN KEY(to_id) REFERENCES neurons(id)
23
+ )
24
+ `), console.log("Schema initialized.");
25
+ }
26
+ class B {
27
+ async create(e) {
28
+ await h.execute(
29
+ "INSERT INTO neurons (id, type, bias, activation) VALUES (?, ?, ?, ?)",
30
+ [e.id, e.type, e.bias, e.activation]
31
+ );
32
+ }
33
+ // Feature: Add with semantic embedding
34
+ async createWithSemantics(e, i) {
35
+ await this.create(e), await h.addWithEmbedding(e.id, i);
36
+ }
37
+ async getAll() {
38
+ return await h.execute("SELECT * FROM neurons");
39
+ }
40
+ async delete(e) {
41
+ await h.execute("DELETE FROM neurons WHERE id = ?", [e]);
42
+ }
43
+ }
44
+ class _ {
45
+ async create(e) {
46
+ await h.execute(
47
+ "INSERT INTO synapses (id, from_id, to_id, weight) VALUES (?, ?, ?, ?)",
48
+ [e.id, e.from_id, e.to_id, e.weight]
49
+ );
50
+ }
51
+ async getAll() {
52
+ return await h.execute("SELECT * FROM synapses");
53
+ }
54
+ async delete(e) {
55
+ await h.execute("DELETE FROM synapses WHERE id = ?", [e]);
56
+ }
57
+ }
58
+ const v = `
59
+ // Structure of our compute shader
60
+ // Group 0: Bindings for data
61
+ // Binding 0: Matrix W (Weights) - N x N flattened array
62
+ // Binding 1: Vector X (Current Neuron Values) - N length array
63
+ // Binding 2: Vector B (Biases) - N length array
64
+ // Binding 3: Vector Y (Output Neuron Values) - N length array
65
+ // Binding 4: Dimensions Uniform - Struct { size: u32 }
66
+
67
+ struct Dimensions {
68
+ size: u32,
69
+ }
70
+
71
+ @group(0) @binding(0) var<storage, read> weights: array<f32>;
72
+ @group(0) @binding(1) var<storage, read> input: array<f32>;
73
+ @group(0) @binding(2) var<storage, read> biases: array<f32>;
74
+ @group(0) @binding(3) var<storage, read_write> output: array<f32>;
75
+ @group(0) @binding(4) var<uniform> dims: Dimensions;
76
+
77
+ // Activation Functions
78
+ fn tanh_approx(x: f32) -> f32 {
79
+ let e2x = exp(2.0 * x);
80
+ return (e2x - 1.0) / (e2x + 1.0);
81
+ }
82
+
83
+ @compute @workgroup_size(64, 1, 1)
84
+ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
85
+ let row = global_id.x;
86
+ let batch = global_id.z;
87
+ let size = dims.size;
88
+
89
+ if (row >= size) {
90
+ return;
91
+ }
92
+
93
+ // Dot product: Row of W * Vector X
94
+ var sum: f32 = 0.0;
95
+
96
+ // Batch offset for input/output
97
+ let batch_offset = batch * size;
98
+
99
+ for (var col: u32 = 0u; col < size; col = col + 1u) {
100
+ // W is shared (not batched): weights[row * size + col]
101
+ let w_idx = row * size + col;
102
+
103
+ // Input is batched: input[batch * size + col]
104
+ let input_idx = batch_offset + col;
105
+
106
+ sum = sum + (weights[w_idx] * input[input_idx]);
107
+ }
108
+
109
+ // Add Bias (Shared)
110
+ sum = sum + biases[row];
111
+
112
+ // Activation
113
+ // Output is batched: output[batch * size + row]
114
+ let out_idx = batch_offset + row;
115
+ output[out_idx] = tanh_approx(sum);
116
+ }
117
+ `, E = `struct Dimensions {
118
+ size: u32,
119
+ batchSize: u32,
120
+ }
121
+
122
+ struct TrainingParams {
123
+ learningRate: f32,
124
+ }
125
+
126
+ @group(0) @binding(0) var<storage, read_write> weights: array<f32>;
127
+ @group(0) @binding(1) var<storage, read> values: array<f32>; // Batched Activations (N * B)
128
+ @group(0) @binding(2) var<storage, read> biases: array<f32>;
129
+ @group(0) @binding(3) var<storage, read_write> deltas: array<f32>; // Batched Deltas (N * B)
130
+ @group(0) @binding(4) var<storage, read> targets: array<f32>; // Batched Targets
131
+ @group(0) @binding(5) var<uniform> dims: Dimensions;
132
+ @group(0) @binding(6) var<uniform> params: TrainingParams;
133
+
134
+ fn tanh_derivative(val: f32) -> f32 {
135
+ return 1.0 - (val * val);
136
+ }
137
+
138
+ // 1. Calculate Deltas (Backward Pass) - 3D Dispatched (64, 1, B)
139
+ @compute @workgroup_size(64, 1, 1)
140
+ fn calculate_deltas(@builtin(global_invocation_id) global_id: vec3<u32>) {
141
+ let index = global_id.x;
142
+ let batch = global_id.z;
143
+ let size = dims.size;
144
+
145
+ if (index >= size) { return; }
146
+
147
+ let batch_offset = batch * size;
148
+ let neuron_idx = batch_offset + index;
149
+
150
+ let activation = values[neuron_idx];
151
+ let derivative = tanh_derivative(activation);
152
+
153
+ var error_sum: f32 = 0.0;
154
+
155
+ // Backpropagate error from "Next Layer" (all other neurons k)
156
+ // For each k (destination), we need delta_k.
157
+ // delta_k is also batched! deltas[batch * size + k]
158
+ for (var k: u32 = 0u; k < size; k = k + 1u) {
159
+ // Weight FROM index TO k
160
+ let w_idx = k * size + index;
161
+ let weight_ki = weights[w_idx];
162
+
163
+ let delta_k_idx = batch_offset + k;
164
+ let delta_k = deltas[delta_k_idx];
165
+
166
+ error_sum = error_sum + (delta_k * weight_ki);
167
+ }
168
+
169
+ // Add immediate error (MSE derivative: y - t)
170
+ // targets[batch * size + index]
171
+ let target = targets[neuron_idx];
172
+ if (target > -998.0) {
173
+ error_sum = error_sum + (activation - target);
174
+ }
175
+
176
+ deltas[neuron_idx] = error_sum * derivative;
177
+ }
178
+
179
+ // 2. Update Weights (Optimizer Step) - 1D Dispatched (64, 1, 1) - Accumulates Gradients over Batch
180
+ @compute @workgroup_size(64)
181
+ fn update_weights(@builtin(global_invocation_id) global_id: vec3<u32>) {
182
+ let row = global_id.x; // Target neuron
183
+ let size = dims.size;
184
+ let batch_size = dims.batchSize;
185
+
186
+ if (row >= size) { return; }
187
+
188
+ let lr = params.learningRate;
189
+
190
+ // Update incoming weights to this neuron 'row'
191
+ // W_ji (row, col)
192
+ for (var col: u32 = 0u; col < size; col = col + 1u) {
193
+ let w_idx = row * size + col;
194
+
195
+ // Accumulate gradient over batch
196
+ var gradient_sum: f32 = 0.0;
197
+
198
+ for (var b: u32 = 0u; b < batch_size; b = b + 1u) {
199
+ let batch_offset = b * size;
200
+
201
+ // delta_j (for this batch item)
202
+ let delta_j = deltas[batch_offset + row];
203
+
204
+ // input_i (activation of source col for this batch item)
205
+ let input_val = values[batch_offset + col];
206
+
207
+ gradient_sum = gradient_sum + (delta_j * input_val);
208
+ }
209
+
210
+ // SGD Update (Mean Gradient? Or Sum? Usually Mean for batch)
211
+ // Let's use Sum * (LearningRate / BatchSize) effectively, or just keep LR as is and user adjusts.
212
+ // Standard is Mean Gradient.
213
+
214
+ let mean_gradient = gradient_sum / f32(batch_size);
215
+
216
+ weights[w_idx] = weights[w_idx] - (lr * mean_gradient);
217
+ }
218
+ }
219
+ `;
220
+ class S {
221
+ constructor() {
222
+ r(this, "device", null);
223
+ r(this, "pipeline", null);
224
+ r(this, "bindGroup", null);
225
+ // Training Buffers
226
+ r(this, "deltaBuffer", null);
227
+ r(this, "targetBuffer", null);
228
+ r(this, "paramBuffer", null);
229
+ r(this, "trainingPipeline", null);
230
+ r(this, "deltaPipeline", null);
231
+ r(this, "trainingBindGroup", null);
232
+ // Buffers
233
+ r(this, "weightBuffer", null);
234
+ r(this, "inputBuffer", null);
235
+ r(this, "biasBuffer", null);
236
+ r(this, "outputBuffer", null);
237
+ r(this, "uniformBuffer", null);
238
+ r(this, "networkSize", 0);
239
+ r(this, "batchSize", 1);
240
+ r(this, "subscribers", []);
241
+ }
242
+ async init() {
243
+ if (!navigator.gpu) throw new Error("WebGPU not supported");
244
+ const e = await navigator.gpu.requestAdapter();
245
+ if (!e) throw new Error("No GPU adapter found");
246
+ this.device = await e.requestDevice();
247
+ const i = this.device.createShaderModule({ code: v }), t = this.device.createShaderModule({ code: E });
248
+ this.pipeline = this.device.createComputePipeline({
249
+ layout: "auto",
250
+ compute: { module: i, entryPoint: "main" }
251
+ }), this.trainingPipeline = this.device.createComputePipeline({
252
+ layout: "auto",
253
+ compute: { module: t, entryPoint: "update_weights" }
254
+ }), this.deltaPipeline = this.device.createComputePipeline({
255
+ layout: "auto",
256
+ compute: { module: t, entryPoint: "calculate_deltas" }
257
+ }), console.log("GPUEngine initialized");
258
+ }
259
+ // Prepare buffers based on network size (N) and Batch Size (B)
260
+ prepareBuffers(e, i, t, n = 1) {
261
+ if (!this.device || !this.pipeline) throw new Error("GPUEngine not initialized");
262
+ this.networkSize = e, this.batchSize = n, this.weightBuffer = this.createBuffer(i, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST), this.biasBuffer = this.createBuffer(t, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST);
263
+ const a = e * n;
264
+ this.inputBuffer = this.createBuffer(new Float32Array(a), GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST), this.outputBuffer = this.createBuffer(new Float32Array(a), GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST);
265
+ const s = new Uint32Array([e, n]);
266
+ this.uniformBuffer = this.createBuffer(s, GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST), this.bindGroup = this.device.createBindGroup({
267
+ layout: this.pipeline.getBindGroupLayout(0),
268
+ entries: [
269
+ { binding: 0, resource: { buffer: this.weightBuffer } },
270
+ { binding: 1, resource: { buffer: this.inputBuffer } },
271
+ { binding: 2, resource: { buffer: this.biasBuffer } },
272
+ { binding: 3, resource: { buffer: this.outputBuffer } },
273
+ { binding: 4, resource: { buffer: this.uniformBuffer } }
274
+ ]
275
+ });
276
+ }
277
+ createBuffer(e, i) {
278
+ if (!this.device) throw new Error("Device null");
279
+ const t = this.device.createBuffer({
280
+ size: e.byteLength,
281
+ usage: i,
282
+ mappedAtCreation: !0
283
+ });
284
+ return e instanceof Float32Array ? new Float32Array(t.getMappedRange()).set(e) : new Uint32Array(t.getMappedRange()).set(e), t.unmap(), t;
285
+ }
286
+ async runTick(e) {
287
+ if (!this.device || !this.pipeline || !this.bindGroup || !this.inputBuffer || !this.outputBuffer)
288
+ throw new Error("GPU buffers not ready");
289
+ if (e.length !== this.networkSize * this.batchSize)
290
+ throw new Error(`Input size mismatch. Expected ${this.networkSize * this.batchSize}, got ${e.length}`);
291
+ this.device.queue.writeBuffer(this.inputBuffer, 0, e);
292
+ const i = this.device.createCommandEncoder(), t = i.beginComputePass();
293
+ t.setPipeline(this.pipeline), t.setBindGroup(0, this.bindGroup);
294
+ const a = Math.ceil(this.networkSize / 64);
295
+ t.dispatchWorkgroups(a, 1, this.batchSize), t.end();
296
+ const s = e.byteLength, o = this.device.createBuffer({
297
+ size: s,
298
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
299
+ });
300
+ i.copyBufferToBuffer(this.outputBuffer, 0, o, 0, s);
301
+ const u = i.finish();
302
+ this.device.queue.submit([u]), await o.mapAsync(GPUMapMode.READ);
303
+ const d = new Float32Array(o.getMappedRange()), l = new Float32Array(d);
304
+ return o.unmap(), l;
305
+ }
306
+ prepareTrainingBuffers(e, i) {
307
+ if (!this.device || !this.trainingPipeline || !this.weightBuffer || !this.outputBuffer || !this.biasBuffer || !this.uniformBuffer)
308
+ throw new Error("GPU not ready for training");
309
+ if (e.length !== this.networkSize * this.batchSize)
310
+ throw new Error(`Target size mismatch. Expected ${this.networkSize * this.batchSize}, got ${e.length}`);
311
+ const t = this.networkSize * this.batchSize;
312
+ this.deltaBuffer = this.createBuffer(new Float32Array(t), GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC), this.targetBuffer = this.createBuffer(e, GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST), this.paramBuffer = this.createBuffer(new Float32Array([i]), GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST), this.trainingBindGroup = this.device.createBindGroup({
313
+ layout: this.trainingPipeline.getBindGroupLayout(0),
314
+ entries: [
315
+ { binding: 0, resource: { buffer: this.weightBuffer } },
316
+ { binding: 1, resource: { buffer: this.outputBuffer } },
317
+ { binding: 2, resource: { buffer: this.biasBuffer } },
318
+ { binding: 3, resource: { buffer: this.deltaBuffer } },
319
+ { binding: 4, resource: { buffer: this.targetBuffer } },
320
+ { binding: 5, resource: { buffer: this.uniformBuffer } },
321
+ { binding: 6, resource: { buffer: this.paramBuffer } }
322
+ ]
323
+ });
324
+ }
325
+ subscribe(e) {
326
+ return this.subscribers.push(e), () => {
327
+ this.subscribers = this.subscribers.filter((i) => i !== e);
328
+ };
329
+ }
330
+ emit(e) {
331
+ this.subscribers.forEach((i) => i(e));
332
+ }
333
+ async train(e, i) {
334
+ var s;
335
+ const t = await this.runTick(e);
336
+ let n = 0;
337
+ for (let o = 0; o < t.length; o++) {
338
+ const u = i[o];
339
+ if (u > -998) {
340
+ const d = t[o] - u;
341
+ n += 0.5 * d * d;
342
+ }
343
+ }
344
+ const a = n / this.batchSize;
345
+ return this.emit({ type: "loss", value: a }), this.targetBuffer && ((s = this.device) == null || s.queue.writeBuffer(this.targetBuffer, 0, i)), await this.trainTick(), this.emit({ type: "epoch", value: 1 }), t;
346
+ }
347
+ async trainTick(e) {
348
+ if (!this.device || !this.trainingPipeline || !this.deltaPipeline || !this.trainingBindGroup || !this.deltaBuffer)
349
+ throw new Error("Training not ready");
350
+ e && e.length > 0 && this.device.queue.writeBuffer(this.deltaBuffer, 0, e);
351
+ const i = this.device.createCommandEncoder(), t = i.beginComputePass();
352
+ t.setPipeline(this.deltaPipeline), t.setBindGroup(0, this.trainingBindGroup);
353
+ const a = Math.ceil(this.networkSize / 64);
354
+ t.dispatchWorkgroups(a, 1, this.batchSize), t.end();
355
+ const s = i.beginComputePass();
356
+ s.setPipeline(this.trainingPipeline), s.setBindGroup(0, this.trainingBindGroup), s.dispatchWorkgroups(a, 1, 1), s.end(), this.device.queue.submit([i.finish()]);
357
+ }
358
+ async injectInput(e) {
359
+ !this.device || !this.inputBuffer || this.device.queue.writeBuffer(this.inputBuffer, 0, e);
360
+ }
361
+ }
362
+ class T {
363
+ constructor() {
364
+ r(this, "context", null);
365
+ r(this, "builder", null);
366
+ r(this, "graph", null);
367
+ // Buffers/State
368
+ r(this, "networkSize", 0);
369
+ r(this, "batchSize", 1);
370
+ // We keep weights/biases in memory to rebuild graph if needed,
371
+ // though for strict WebNN we bakw them into constants.
372
+ r(this, "weights", null);
373
+ r(this, "biases", null);
374
+ r(this, "isReady", !1);
375
+ }
376
+ async init() {
377
+ if (!navigator.ml) {
378
+ console.warn("WebNN: navigator.ml not supported");
379
+ return;
380
+ }
381
+ try {
382
+ this.context = await navigator.ml.createContext({ deviceType: "npu", powerPreference: "low-power" }), console.log("WebNN: NPU Context created"), this.builder = new window.MLGraphBuilder(this.context), this.isReady = !0;
383
+ } catch (e) {
384
+ console.error("WebNN Init Error (likely no NPU or flag disabled):", e);
385
+ }
386
+ }
387
+ async prepareModel(e, i, t, n = 1) {
388
+ if (!(!this.context || !this.builder)) {
389
+ this.networkSize = e, this.batchSize = n, this.weights = i, this.biases = t;
390
+ try {
391
+ const a = this.builder, s = {
392
+ dataType: "float32",
393
+ dimensions: [n, e]
394
+ }, o = a.input("input", s), u = {
395
+ dataType: "float32",
396
+ dimensions: [e, e]
397
+ }, d = a.constant(u, i), l = {
398
+ dataType: "float32",
399
+ dimensions: [e]
400
+ // 1D, will broadcast to [batch, size]
401
+ }, f = a.constant(l, t), p = a.matmul(o, d), g = a.add(p, f), w = a.tanh(g);
402
+ this.graph = await a.build({ output: w }), console.log("WebNN: Graph compiled successfully");
403
+ } catch (a) {
404
+ console.error("WebNN Build Error:", a);
405
+ }
406
+ }
407
+ }
408
+ async runTick(e) {
409
+ if (!this.context || !this.graph)
410
+ throw new Error("WebNN not ready");
411
+ if (e.length !== this.networkSize * this.batchSize)
412
+ throw new Error(`Input size mismatch. Expected ${this.networkSize * this.batchSize}, got ${e.length}`);
413
+ const i = new Float32Array(this.networkSize * this.batchSize), t = { input: e }, n = { output: i };
414
+ return await this.context.compute(this.graph, t, n), i;
415
+ }
416
+ }
417
+ class z {
418
+ constructor() {
419
+ // Maps Neuron logical IDs (UUIDs) to Matrix Indices (0...N)
420
+ r(this, "idToIndex", /* @__PURE__ */ new Map());
421
+ r(this, "indexToId", []);
422
+ }
423
+ // Converts Graph -> Dense Matrices
424
+ flatten(e, i) {
425
+ const t = e.length;
426
+ this.idToIndex.clear(), this.indexToId = new Array(t), e.forEach((o, u) => {
427
+ this.idToIndex.set(o.id, u), this.indexToId[u] = o.id;
428
+ });
429
+ const n = new Float32Array(t), a = new Float32Array(t);
430
+ e.forEach((o, u) => {
431
+ n[u] = o.bias;
432
+ });
433
+ const s = new Float32Array(t * t);
434
+ return i.forEach((o) => {
435
+ const u = this.idToIndex.get(o.from_id), d = this.idToIndex.get(o.to_id);
436
+ if (u !== void 0 && d !== void 0) {
437
+ const l = d * t + u;
438
+ s[l] = o.weight;
439
+ }
440
+ }), { size: t, weights: s, biases: n, initialValues: a };
441
+ }
442
+ }
443
+ class U {
444
+ constructor() {
445
+ r(this, "gpu");
446
+ r(this, "npu");
447
+ r(this, "neuronRepo");
448
+ r(this, "synapseRepo");
449
+ r(this, "translator");
450
+ r(this, "activeBackend", "gpu");
451
+ // Cache
452
+ r(this, "neurons", []);
453
+ r(this, "synapses", []);
454
+ this.gpu = new S(), this.npu = new T(), this.neuronRepo = new B(), this.synapseRepo = new _(), this.translator = new z();
455
+ }
456
+ async init() {
457
+ if (console.log("Neural 2.0 Engine Initializing..."), await h.ready(), await y(), await this.gpu.init(), this.gpu.batchSize = 2, await this.npu.init(), this.npu.isReady && (console.log("Neural Engine: NPU Accelerated Backend Available."), this.activeBackend = "npu"), this.neurons = await this.neuronRepo.getAll(), this.synapses = await this.synapseRepo.getAll(), this.neurons.length === 0) {
458
+ console.log("Seeding test network...");
459
+ const e = "n1-" + crypto.randomUUID(), i = "n2-" + crypto.randomUUID();
460
+ await this.neuronRepo.create({ id: e, type: "input", bias: 0, activation: "tanh" }), await this.neuronRepo.create({ id: i, type: "output", bias: 0.5, activation: "tanh" }), await this.synapseRepo.create({ id: crypto.randomUUID(), from_id: e, to_id: i, weight: 0.8 });
461
+ for (let n = 0; n < 50; n++)
462
+ await this.neuronRepo.create({ id: `auto-${n}`, type: "hidden", bias: 0, activation: "tanh" });
463
+ const t = await this.neuronRepo.getAll();
464
+ for (let n = 0; n < 50; n++) {
465
+ const a = t[Math.floor(Math.random() * t.length)].id, s = t[Math.floor(Math.random() * t.length)].id;
466
+ a !== s && await this.synapseRepo.create({ id: crypto.randomUUID(), from_id: a, to_id: s, weight: Math.random() });
467
+ }
468
+ this.neurons = await this.neuronRepo.getAll(), this.synapses = await this.synapseRepo.getAll();
469
+ }
470
+ return await this.compile(), console.log(`Engine Ready. Active Backend: ${this.activeBackend.toUpperCase()}`), this.getGraphData();
471
+ }
472
+ async compile() {
473
+ console.log(`Compiling graph: ${this.neurons.length} neurons, ${this.synapses.length} synapses`);
474
+ const e = this.translator.flatten(this.neurons, this.synapses);
475
+ return this.gpu.prepareBuffers(e.size, e.weights, e.biases, this.gpu.batchSize), this.gpu.prepareTrainingBuffers(new Float32Array(e.size * this.gpu.batchSize), 0.1), this.npu.isReady && await this.npu.prepareModel(e.size, e.weights, e.biases, 2), e;
476
+ }
477
+ async deployToCloud() {
478
+ console.log("Deploying heavy layers to Hybrid Cloud..."), this.neurons = this.neurons.map((e) => ({
479
+ ...e,
480
+ type: Math.random() > 0.8 ? "cloud" : e.type
481
+ }));
482
+ for (const e of this.neurons)
483
+ e.type;
484
+ return this.getGraphData();
485
+ }
486
+ getGraphData() {
487
+ const e = /* @__PURE__ */ new Map();
488
+ this.neurons.forEach((n, a) => e.set(n.id, a));
489
+ const i = this.synapses.map((n) => ({
490
+ id: n.id,
491
+ source: e.get(n.from_id) || 0,
492
+ target: e.get(n.to_id) || 0,
493
+ weight: n.weight
494
+ })), t = this.neurons.map((n, a) => ({
495
+ id: n.id,
496
+ index: a,
497
+ type: n.type
498
+ }));
499
+ return {
500
+ nodeCount: this.neurons.length,
501
+ nodes: t,
502
+ edges: i
503
+ };
504
+ }
505
+ async deleteSynapse(e) {
506
+ return console.log(`Lesioning synapse: ${e}`), await this.synapseRepo.delete(e), this.synapses = this.synapses.filter((i) => i.id !== e), await this.compile(), this.getGraphData();
507
+ }
508
+ exportGraph() {
509
+ return {
510
+ version: "2.0",
511
+ neurons: this.neurons,
512
+ synapses: this.synapses
513
+ };
514
+ }
515
+ async importGraph(e) {
516
+ if (!e.neurons || !e.synapses) throw new Error("Invalid graph data");
517
+ console.log("Importing graph...");
518
+ const i = await this.neuronRepo.getAll();
519
+ for (const n of i) await this.neuronRepo.delete(n.id);
520
+ const t = await this.synapseRepo.getAll();
521
+ for (const n of t) await this.synapseRepo.delete(n.id);
522
+ for (const n of e.neurons) await this.neuronRepo.create(n);
523
+ for (const n of e.synapses) await this.synapseRepo.create(n);
524
+ return this.neurons = await this.neuronRepo.getAll(), this.synapses = await this.synapseRepo.getAll(), console.log(`Compiling imported graph: ${this.neurons.length} neurons, ${this.synapses.length} synapses`), await this.compile(), this.getGraphData();
525
+ }
526
+ async injectInput(e) {
527
+ this.activeBackend === "npu" && this.npu.isReady || await this.gpu.injectInput(e);
528
+ }
529
+ async runTick(e) {
530
+ return this.activeBackend === "npu" && this.npu.isReady ? this.npu.runTick(e) : this.gpu.runTick(e);
531
+ }
532
+ }
533
+ async function P() {
534
+ return new U().init();
535
+ }
536
+ export {
537
+ U as NeuralEngine,
538
+ P as init
539
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@buley/neural",
3
- "version": "2.0.4",
3
+ "version": "4.0.0",
4
4
  "description": "A Transparent, Local-First, WebGPU-Accelerated Neural Graph Database.",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -10,7 +10,7 @@
10
10
  "bench": "bun src/bench/benchmark.ts"
11
11
  },
12
12
  "dependencies": {
13
- "@buley/dash": "2.1.4"
13
+ "@buley/dash": "4.0.0"
14
14
  },
15
15
  "devDependencies": {
16
16
  "@webgpu/types": "^0.1.69",
@@ -0,0 +1,118 @@
1
+
2
+ /// <reference path="../webnn-types.d.ts" />
3
+
4
+ export class WebNNEngine {
5
+ context: MLContext | null = null;
6
+ builder: MLGraphBuilder | null = null;
7
+ graph: MLGraph | null = null;
8
+
9
+ // Buffers/State
10
+ networkSize: number = 0;
11
+ batchSize: number = 1;
12
+
13
+ // We keep weights/biases in memory to rebuild graph if needed,
14
+ // though for strict WebNN we bakw them into constants.
15
+ weights: Float32Array | null = null;
16
+ biases: Float32Array | null = null;
17
+
18
+ isReady = false;
19
+
20
+ async init() {
21
+ if (!navigator.ml) {
22
+ console.warn("WebNN: navigator.ml not supported");
23
+ return;
24
+ }
25
+
26
+ try {
27
+ // Prefer NPU, fallback to GPU if NPU not available, though we really want NPU for "cool factor"
28
+ // Note: browser support for 'npu' deviceType is bleeding edge.
29
+ this.context = await navigator.ml.createContext({ deviceType: 'npu', powerPreference: 'low-power' });
30
+ console.log("WebNN: NPU Context created");
31
+
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
+ this.builder = new (window as any).MLGraphBuilder(this.context);
34
+ this.isReady = true;
35
+ } catch (e) {
36
+ console.error("WebNN Init Error (likely no NPU or flag disabled):", e);
37
+ // Fallback? or just stay uninitialized
38
+ }
39
+ }
40
+
41
+ async prepareModel(size: number, weights: Float32Array, biases: Float32Array, batchSize: number = 1) {
42
+ if (!this.context || !this.builder) return;
43
+ this.networkSize = size;
44
+ this.batchSize = batchSize;
45
+ this.weights = weights;
46
+ this.biases = biases;
47
+
48
+ // Build the Computational Graph
49
+ // Model: Y = Tanh((X * W) + B)
50
+ // Shapes:
51
+ // X: [batchSize, networkSize]
52
+ // W: [networkSize, networkSize]
53
+ // B: [networkSize] (Broadcasted)
54
+ // Y: [batchSize, networkSize]
55
+
56
+ try {
57
+ const builder = this.builder;
58
+
59
+ // 1. Input Operand (Variable)
60
+ const inputDesc: MLOperandDescriptor = {
61
+ dataType: 'float32',
62
+ dimensions: [batchSize, size]
63
+ };
64
+ const input = builder.input('input', inputDesc);
65
+
66
+ // 2. Constants (Weights & Biases)
67
+ // Note: WebNN matmul expects typically [I, J] * [J, K] -> [I, K]
68
+ // Our weights are N*N flattened.
69
+ const weightDesc: MLOperandDescriptor = {
70
+ dataType: 'float32',
71
+ dimensions: [size, size]
72
+ };
73
+ // WebNN might require specific buffer types, Float32Array is good.
74
+ const weightConstant = builder.constant(weightDesc, weights);
75
+
76
+ const biasDesc: MLOperandDescriptor = {
77
+ dataType: 'float32',
78
+ dimensions: [size] // 1D, will broadcast to [batch, size]
79
+ };
80
+ const biasConstant = builder.constant(biasDesc, biases);
81
+
82
+ // 3. Operations
83
+ // MatMul: [batch, size] * [size, size] -> [batch, size]
84
+ const matmul = builder.matmul(input, weightConstant);
85
+
86
+ // Add Bias (Broadcast)
87
+ const added = builder.add(matmul, biasConstant);
88
+
89
+ // Activation
90
+ const output = builder.tanh(added);
91
+
92
+ // 4. Build
93
+ this.graph = await builder.build({ 'output': output });
94
+ console.log("WebNN: Graph compiled successfully");
95
+
96
+ } catch (e) {
97
+ console.error("WebNN Build Error:", e);
98
+ }
99
+ }
100
+
101
+ async runTick(inputs: Float32Array): Promise<Float32Array> {
102
+ if (!this.context || !this.graph) {
103
+ throw new Error("WebNN not ready");
104
+ }
105
+ if (inputs.length !== this.networkSize * this.batchSize) {
106
+ throw new Error(`Input size mismatch. Expected ${this.networkSize * this.batchSize}, got ${inputs.length}`);
107
+ }
108
+
109
+ const outputs = new Float32Array(this.networkSize * this.batchSize);
110
+
111
+ const inputsMap = { 'input': inputs };
112
+ const outputsMap = { 'output': outputs };
113
+
114
+ await this.context.compute(this.graph, inputsMap, outputsMap);
115
+
116
+ return outputs;
117
+ }
118
+ }
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ import { dash } from "@buley/dash";
2
2
  import { initializeSchema } from "./db/schema";
3
3
  import { NeuronRepository, SynapseRepository } from "./db/repository";
4
4
  import { GPUEngine } from "./engine/gpu";
5
+ import { WebNNEngine } from "./engine/webnn";
5
6
  import { Translator } from "./engine/translator";
6
7
  import { Neuron, Synapse } from "./types";
7
8
 
@@ -9,12 +10,16 @@ export type { Neuron, Synapse } from "./types";
9
10
 
10
11
  export class NeuralEngine {
11
12
  gpu: GPUEngine;
13
+ npu: WebNNEngine;
12
14
  neuronRepo: NeuronRepository;
13
15
  synapseRepo: SynapseRepository;
14
16
  private translator: Translator;
15
17
 
18
+ activeBackend: 'gpu' | 'npu' = 'gpu';
19
+
16
20
  constructor() {
17
21
  this.gpu = new GPUEngine();
22
+ this.npu = new WebNNEngine();
18
23
  this.neuronRepo = new NeuronRepository();
19
24
  this.synapseRepo = new SynapseRepository();
20
25
  this.translator = new Translator();
@@ -35,6 +40,13 @@ export class NeuralEngine {
35
40
  await this.gpu.init();
36
41
  this.gpu.batchSize = 2; // Default to mini-batch of 2 for demo
37
42
 
43
+ // Try NPU
44
+ await this.npu.init();
45
+ if (this.npu.isReady) {
46
+ console.log("Neural Engine: NPU Accelerated Backend Available.");
47
+ this.activeBackend = 'npu';
48
+ }
49
+
38
50
  // 3. Hydration
39
51
  this.neurons = await this.neuronRepo.getAll();
40
52
  this.synapses = await this.synapseRepo.getAll();
@@ -64,17 +76,27 @@ export class NeuralEngine {
64
76
  this.synapses = await this.synapseRepo.getAll();
65
77
  }
66
78
 
67
- // 4. Compile to GPU
79
+ // 4. Compile to Compute Backends
80
+ await this.compile();
81
+
82
+ console.log(`Engine Ready. Active Backend: ${this.activeBackend.toUpperCase()}`);
83
+ return this.getGraphData();
84
+ }
85
+
86
+ async compile() {
68
87
  console.log(`Compiling graph: ${this.neurons.length} neurons, ${this.synapses.length} synapses`);
69
88
  const data = this.translator.flatten(this.neurons, this.synapses);
70
89
 
90
+ // GPU
71
91
  this.gpu.prepareBuffers(data.size, data.weights, data.biases, this.gpu.batchSize);
72
- // Also prepare training buffers!
73
- // Init target buffer with zeros
74
92
  this.gpu.prepareTrainingBuffers(new Float32Array(data.size * this.gpu.batchSize), 0.1);
75
93
 
76
- console.log("Engine Ready.");
77
- return data;
94
+ // NPU
95
+ if (this.npu.isReady) {
96
+ await this.npu.prepareModel(data.size, data.weights, data.biases, 2);
97
+ }
98
+
99
+ return data; // Return data for init/others if needed
78
100
  }
79
101
 
80
102
  async deployToCloud() {
@@ -131,12 +153,7 @@ export class NeuralEngine {
131
153
  this.synapses = this.synapses.filter(s => s.id !== id);
132
154
 
133
155
  // Recompile (Heavy!)
134
- // In a real app we'd just zero the weight in buffer
135
- // But for "The Visible Brain" seeing it disappear is cooler.
136
- const data = this.translator.flatten(this.neurons, this.synapses);
137
- this.gpu.prepareBuffers(data.size, data.weights, data.biases, this.gpu.batchSize);
138
- // Reset training buffers too to be safe/simple
139
- this.gpu.prepareTrainingBuffers(new Float32Array(data.size * this.gpu.batchSize), 0.1);
156
+ await this.compile();
140
157
 
141
158
  return this.getGraphData();
142
159
  }
@@ -170,18 +187,25 @@ export class NeuralEngine {
170
187
  this.synapses = await this.synapseRepo.getAll();
171
188
 
172
189
  console.log(`Compiling imported graph: ${this.neurons.length} neurons, ${this.synapses.length} synapses`);
173
- const graph = this.translator.flatten(this.neurons, this.synapses);
174
- this.gpu.prepareBuffers(graph.size, graph.weights, graph.biases, this.gpu.batchSize);
175
- this.gpu.prepareTrainingBuffers(new Float32Array(graph.size * this.gpu.batchSize), 0.1);
190
+ await this.compile();
176
191
 
177
192
  return this.getGraphData();
178
193
  }
179
194
 
180
195
  async injectInput(data: Float32Array) {
181
- // Map data to input neurons
182
- // In simulation, we assume first N neurons are inputs.
183
- // Or we just overwrite the first N values of the input buffer.
184
- await this.gpu.injectInput(data);
196
+ if (this.activeBackend === 'npu' && this.npu.isReady) {
197
+ // WebNN takes input at runTick
198
+ } else {
199
+ await this.gpu.injectInput(data);
200
+ }
201
+ }
202
+
203
+ async runTick(inputs: Float32Array): Promise<Float32Array> {
204
+ if (this.activeBackend === 'npu' && this.npu.isReady) {
205
+ return this.npu.runTick(inputs);
206
+ } else {
207
+ return this.gpu.runTick(inputs);
208
+ }
185
209
  }
186
210
  }
187
211
 
@@ -0,0 +1,43 @@
1
+
2
+ // Minimal WebNN Type Definitions for TypeScript
3
+ // Based on W3C Web Neural Network API Draft
4
+
5
+ interface MLContext {
6
+ compute(graph: MLGraph, inputs: Record<string, ArrayBufferView>, outputs: Record<string, ArrayBufferView>): Promise<MLComputeResult>;
7
+ }
8
+
9
+ interface MLComputeResult {
10
+ inputs: Record<string, ArrayBufferView>;
11
+ outputs: Record<string, ArrayBufferView>;
12
+ }
13
+
14
+ interface MLGraphBuilder {
15
+ input(name: string, descriptor: MLOperandDescriptor): MLOperand;
16
+ constant(descriptor: MLOperandDescriptor, buffer: ArrayBufferView): MLOperand;
17
+ matmul(a: MLOperand, b: MLOperand): MLOperand;
18
+ add(a: MLOperand, b: MLOperand): MLOperand;
19
+ tanh(x: MLOperand): MLOperand;
20
+ build(outputs: Record<string, MLOperand>): Promise<MLGraph>;
21
+ }
22
+
23
+ interface MLGraph {}
24
+
25
+ interface MLOperand {}
26
+
27
+ interface MLOperandDescriptor {
28
+ dataType: 'float32' | 'float16' | 'int32' | 'uint32' | 'int8' | 'uint8';
29
+ dimensions: number[];
30
+ }
31
+
32
+ interface MLContextOptions {
33
+ deviceType?: 'cpu' | 'gpu' | 'npu';
34
+ powerPreference?: 'default' | 'high-performance' | 'low-power';
35
+ }
36
+
37
+ interface NavigatorML {
38
+ createContext(options?: MLContextOptions): Promise<MLContext>;
39
+ }
40
+
41
+ interface Navigator {
42
+ ml?: NavigatorML;
43
+ }