@alex000291/jstorch 0.2.0 → 0.3.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.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alex000291/jstorch",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A JavaScript version of PyTorch with CUDA acceleration",
5
5
  "homepage": "https://github.com/Alex000291/JsTorch#readme",
6
6
  "types": "src/index.d.ts",
package/src/tensor.js CHANGED
@@ -14,7 +14,11 @@ export class Tensor {
14
14
  this._native = new NativeTensor(data);
15
15
  this.requires_grad = requires_grad && is_grad_enabled();
16
16
  this.grad = null;
17
- this._grad_fn = null;
17
+
18
+ // Computational graph
19
+ this._grad_fn = null; // Backward function
20
+ this._prev = []; // Parent nodes (inputs)
21
+ this._op = ''; // Operation name (for debugging)
18
22
  }
19
23
 
20
24
  // Properties
@@ -33,6 +37,10 @@ export class Tensor {
33
37
 
34
38
  if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
35
39
  result.requires_grad = true;
40
+ result._prev = [this, other];
41
+ result._op = 'add';
42
+
43
+ // Gradient function: d(a+b)/da = 1, d(a+b)/db = 1
36
44
  result._grad_fn = () => {
37
45
  if (this.requires_grad) {
38
46
  this.grad = this.grad ? this.grad.add(result.grad) : result.grad;
@@ -54,8 +62,22 @@ export class Tensor {
54
62
  sub(other) {
55
63
  const result = new Tensor(null);
56
64
  result._native = this._native.sub(other._native);
65
+
57
66
  if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
58
67
  result.requires_grad = true;
68
+ result._prev = [this, other];
69
+ result._op = 'sub';
70
+
71
+ // d(a-b)/da = 1, d(a-b)/db = -1
72
+ result._grad_fn = () => {
73
+ if (this.requires_grad) {
74
+ this.grad = this.grad ? this.grad.add(result.grad) : result.grad;
75
+ }
76
+ if (other.requires_grad) {
77
+ const neg_grad = result.grad.neg();
78
+ other.grad = other.grad ? other.grad.add(neg_grad) : neg_grad;
79
+ }
80
+ };
59
81
  }
60
82
  return result;
61
83
  }
@@ -63,8 +85,23 @@ export class Tensor {
63
85
  mul(other) {
64
86
  const result = new Tensor(null);
65
87
  result._native = this._native.mul(other._native);
88
+
66
89
  if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
67
90
  result.requires_grad = true;
91
+ result._prev = [this, other];
92
+ result._op = 'mul';
93
+
94
+ // d(a*b)/da = b, d(a*b)/db = a
95
+ result._grad_fn = () => {
96
+ if (this.requires_grad) {
97
+ const grad_a = result.grad.mul(other);
98
+ this.grad = this.grad ? this.grad.add(grad_a) : grad_a;
99
+ }
100
+ if (other.requires_grad) {
101
+ const grad_b = result.grad.mul(this);
102
+ other.grad = other.grad ? other.grad.add(grad_b) : grad_b;
103
+ }
104
+ };
68
105
  }
69
106
  return result;
70
107
  }
@@ -81,8 +118,19 @@ export class Tensor {
81
118
  mulScalar(scalar) {
82
119
  const result = new Tensor(null);
83
120
  result._native = this._native.mulScalar(scalar);
121
+
84
122
  if (is_grad_enabled() && this.requires_grad) {
85
123
  result.requires_grad = true;
124
+ result._prev = [this];
125
+ result._op = 'mulScalar';
126
+
127
+ // d(a*c)/da = c (where c is scalar)
128
+ result._grad_fn = () => {
129
+ if (this.requires_grad) {
130
+ const grad_a = result.grad.mulScalar(scalar);
131
+ this.grad = this.grad ? this.grad.add(grad_a) : grad_a;
132
+ }
133
+ };
86
134
  }
87
135
  return result;
88
136
  }
@@ -117,8 +165,21 @@ export class Tensor {
117
165
  sum() {
118
166
  const result = new Tensor(null);
119
167
  result._native = this._native.sum();
168
+
120
169
  if (is_grad_enabled() && this.requires_grad) {
121
170
  result.requires_grad = true;
171
+ result._prev = [this];
172
+ result._op = 'sum';
173
+
174
+ // d(sum(a))/da = ones_like(a) * grad_output
175
+ result._grad_fn = () => {
176
+ if (this.requires_grad) {
177
+ // Gradient broadcasts to all elements
178
+ const grad_value = result.grad.item();
179
+ const grad_a = ones(this.shape).mulScalar(grad_value);
180
+ this.grad = this.grad ? this.grad.add(grad_a) : grad_a;
181
+ }
182
+ };
122
183
  }
123
184
  return result;
124
185
  }
@@ -126,8 +187,20 @@ export class Tensor {
126
187
  mean() {
127
188
  const result = new Tensor(null);
128
189
  result._native = this._native.mean();
190
+
129
191
  if (is_grad_enabled() && this.requires_grad) {
130
192
  result.requires_grad = true;
193
+ result._prev = [this];
194
+ result._op = 'mean';
195
+
196
+ // d(mean(a))/da = ones_like(a) / size * grad_output
197
+ result._grad_fn = () => {
198
+ if (this.requires_grad) {
199
+ const grad_value = result.grad.item();
200
+ const grad_a = ones(this.shape).mulScalar(grad_value / this.size);
201
+ this.grad = this.grad ? this.grad.add(grad_a) : grad_a;
202
+ }
203
+ };
131
204
  }
132
205
  return result;
133
206
  }
@@ -138,8 +211,11 @@ export class Tensor {
138
211
 
139
212
  if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
140
213
  result.requires_grad = true;
214
+ result._prev = [this, other];
215
+ result._op = 'matmul';
216
+
217
+ // d(A@B)/dA = dC @ B^T, d(A@B)/dB = A^T @ dC
141
218
  result._grad_fn = () => {
142
- // Backward: dL/dA = dL/dC @ B^T, dL/dB = A^T @ dL/dC
143
219
  if (this.requires_grad) {
144
220
  const grad_a = result.grad.matmul(other.transpose());
145
221
  this.grad = this.grad ? this.grad.add(grad_a) : grad_a;
@@ -166,12 +242,19 @@ export class Tensor {
166
242
 
167
243
  if (is_grad_enabled() && this.requires_grad) {
168
244
  result.requires_grad = true;
169
- const input_data = this.toArray(); // Save for backward
245
+ result._prev = [this];
246
+ result._op = 'relu';
247
+
248
+ // Save input for backward
249
+ const input_data = this.toArray();
250
+
251
+ // d(relu(a))/da = (a > 0) ? 1 : 0
170
252
  result._grad_fn = () => {
171
- // Backward: gradient passes through where input > 0
172
- // TODO: implement element-wise multiplication with mask
173
253
  if (this.requires_grad) {
174
- this.grad = result.grad; // Simplified for now
254
+ // Create mask: 1 where input > 0, 0 otherwise
255
+ const mask = this._createMask(input_data, x => x > 0 ? 1 : 0);
256
+ const grad_a = result.grad.mul(mask);
257
+ this.grad = this.grad ? this.grad.add(grad_a) : grad_a;
175
258
  }
176
259
  };
177
260
  }
@@ -179,6 +262,17 @@ export class Tensor {
179
262
  return result;
180
263
  }
181
264
 
265
+ // Helper: create mask tensor from nested array
266
+ _createMask(data, fn) {
267
+ const applyMask = (arr) => {
268
+ if (Array.isArray(arr)) {
269
+ return arr.map(applyMask);
270
+ }
271
+ return fn(arr);
272
+ };
273
+ return tensor(applyMask(data));
274
+ }
275
+
182
276
  relu_() {
183
277
  const result = new Tensor(null);
184
278
  result._native = this._native.relu();
@@ -203,9 +297,29 @@ export class Tensor {
203
297
  // Initialize gradient
204
298
  this.grad = gradient || ones(this.shape);
205
299
 
206
- // Topological sort and execute grad_fn (simplified - just call directly for now)
207
- if (this._grad_fn) {
208
- this._grad_fn();
300
+ // Build topological order
301
+ const topo = [];
302
+ const visited = new Set();
303
+
304
+ const build_topo = (node) => {
305
+ if (!node.requires_grad || visited.has(node)) return;
306
+ visited.add(node);
307
+
308
+ for (const parent of node._prev) {
309
+ build_topo(parent);
310
+ }
311
+
312
+ topo.push(node);
313
+ };
314
+
315
+ build_topo(this);
316
+
317
+ // Execute gradients in reverse topological order
318
+ for (let i = topo.length - 1; i >= 0; i--) {
319
+ const node = topo[i];
320
+ if (node._grad_fn) {
321
+ node._grad_fn();
322
+ }
209
323
  }
210
324
  }
211
325
 
@@ -213,10 +327,36 @@ export class Tensor {
213
327
  this.grad = null;
214
328
  }
215
329
 
216
- // Placeholder for transpose (TODO: implement in C++)
330
+ // Transpose (2D only for now)
217
331
  transpose() {
218
- // Assume 2D for now
219
- return this; // TODO
332
+ if (this.shape.length !== 2) {
333
+ throw new Error('transpose() only supports 2D tensors for now');
334
+ }
335
+
336
+ const data = this.toArray();
337
+ const [rows, cols] = this.shape;
338
+
339
+ // Transpose data
340
+ const transposed = Array.from({ length: cols }, (_, i) =>
341
+ Array.from({ length: rows }, (_, j) => data[j][i])
342
+ );
343
+
344
+ const result = tensor(transposed, this.requires_grad && is_grad_enabled());
345
+
346
+ if (is_grad_enabled() && this.requires_grad) {
347
+ result._prev = [this];
348
+ result._op = 'transpose';
349
+
350
+ // d(A^T)/dA = (dC)^T
351
+ result._grad_fn = () => {
352
+ if (this.requires_grad) {
353
+ const grad_a = result.grad.transpose();
354
+ this.grad = this.grad ? this.grad.add(grad_a) : grad_a;
355
+ }
356
+ };
357
+ }
358
+
359
+ return result;
220
360
  }
221
361
  }
222
362