@alex000291/jstorch 0.1.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,8 +1,9 @@
1
1
  {
2
2
  "name": "@alex000291/jstorch",
3
- "version": "0.1.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
+ "types": "src/index.d.ts",
6
7
  "bugs": {
7
8
  "url": "https://github.com/Alex000291/JsTorch/issues"
8
9
  },
@@ -23,8 +24,10 @@
23
24
  "prepublishOnly": "npm run build"
24
25
  },
25
26
  "files": [
26
- "src/index.js",
27
- "build/win/jstorch.node"
27
+ "src/",
28
+ "build/win/jstorch.node",
29
+ "README.md",
30
+ "LICENSE"
28
31
  ],
29
32
  "keywords": [
30
33
  "pytorch",
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Autograd - Automatic Differentiation with AsyncLocalStorage
3
+ */
4
+
5
+ import { AsyncLocalStorage } from 'async_hooks';
6
+
7
+ const gradContext = new AsyncLocalStorage();
8
+
9
+ /**
10
+ * Disable gradient computation within a function scope
11
+ */
12
+ export function no_grad(fn) {
13
+ return gradContext.run({ enabled: false }, fn);
14
+ }
15
+
16
+ /**
17
+ * Enable gradient computation within a function scope
18
+ */
19
+ export function enable_grad(fn) {
20
+ return gradContext.run({ enabled: true }, fn);
21
+ }
22
+
23
+ /**
24
+ * Check if gradient computation is currently enabled
25
+ */
26
+ export function is_grad_enabled() {
27
+ const ctx = gradContext.getStore();
28
+ return ctx?.enabled ?? true; // Default: enabled
29
+ }
30
+
31
+ /**
32
+ * Set gradient mode globally (for model.train() / model.eval())
33
+ */
34
+ export function set_grad_enabled(enabled) {
35
+ // Returns a function that sets the mode
36
+ return enabled ? enable_grad : no_grad;
37
+ }
package/src/index.d.ts ADDED
@@ -0,0 +1,158 @@
1
+ /**
2
+ * JsTorch - PyTorch-like Tensor Library for Node.js
3
+ * TypeScript Type Definitions
4
+ */
5
+
6
+ // ==================== Core Types ====================
7
+
8
+ export type Device = 'cpu' | 'cuda';
9
+ export type DType = 'float32' | 'float64' | 'int32' | 'int64';
10
+ export type Shape = number[];
11
+
12
+ // ==================== Tensor ====================
13
+
14
+ export class Tensor {
15
+ constructor(data: number | number[] | number[][] | number[][][], requires_grad?: boolean);
16
+
17
+ // Properties
18
+ readonly shape: Shape;
19
+ readonly size: number;
20
+ requires_grad: boolean;
21
+ grad: Tensor | null;
22
+
23
+ // Binary operations
24
+ add(other: Tensor): Tensor;
25
+ add_(other: Tensor): Tensor;
26
+ sub(other: Tensor): Tensor;
27
+ mul(other: Tensor): Tensor;
28
+ div(other: Tensor): Tensor;
29
+ matmul(other: Tensor): Tensor;
30
+
31
+ // Unary operations
32
+ relu(): Tensor;
33
+ relu_(): Tensor;
34
+ exp(): Tensor;
35
+ log(): Tensor;
36
+ neg(): Tensor;
37
+
38
+ // Scalar operations
39
+ mulScalar(scalar: number): Tensor;
40
+
41
+ // Reduction operations
42
+ sum(): Tensor;
43
+ mean(): Tensor;
44
+
45
+ // Autograd
46
+ backward(gradient?: Tensor): void;
47
+ zero_grad(): void;
48
+
49
+ // Utilities
50
+ toArray(): number | number[] | number[][];
51
+ item(): number;
52
+ }
53
+
54
+ // ==================== Tensor Creation ====================
55
+
56
+ export function tensor(
57
+ data: number | number[] | number[][] | number[][][],
58
+ requires_grad?: boolean
59
+ ): Tensor;
60
+
61
+ export function zeros(shape: Shape): Tensor;
62
+ export function ones(shape: Shape): Tensor;
63
+
64
+ // ==================== Autograd ====================
65
+
66
+ export function no_grad<T>(fn: () => T): T;
67
+ export function enable_grad<T>(fn: () => T): T;
68
+ export function is_grad_enabled(): boolean;
69
+ export function set_grad_enabled(enabled: boolean): <T>(fn: () => T) => T;
70
+
71
+ // ==================== Neural Network ====================
72
+
73
+ export namespace nn {
74
+ // Module interface
75
+ interface Module {
76
+ forward(input: Tensor): Tensor;
77
+ parameters(): Tensor[];
78
+ train?(): void;
79
+ eval?(): void;
80
+ }
81
+
82
+ // Layers
83
+ export function linear(in_features: number, out_features: number, bias?: boolean): Module & {
84
+ weight: Tensor;
85
+ bias: Tensor | null;
86
+ };
87
+
88
+ export function relu(): Module;
89
+ export function flatten(start_dim?: number, end_dim?: number): Module;
90
+
91
+ // Containers
92
+ export function sequential(layers: Module[]): Module;
93
+
94
+ // Loss functions
95
+ export function cross_entropy_loss(): (output: Tensor, target: Tensor) => Tensor;
96
+ export function mse_loss(): (output: Tensor, target: Tensor) => Tensor;
97
+
98
+ // Activations
99
+ export function softmax(input: Tensor, dim?: number): Tensor;
100
+ export function log_softmax(input: Tensor, dim?: number): Tensor;
101
+ }
102
+
103
+ // ==================== Optimizers ====================
104
+
105
+ export namespace optim {
106
+ interface Optimizer {
107
+ zero_grad(): void;
108
+ step(): void;
109
+ }
110
+
111
+ export function sgd(
112
+ params: Tensor[],
113
+ options: {
114
+ lr: number;
115
+ momentum?: number;
116
+ }
117
+ ): Optimizer;
118
+
119
+ export function adam(
120
+ params: Tensor[],
121
+ options: {
122
+ lr: number;
123
+ betas?: [number, number];
124
+ eps?: number;
125
+ }
126
+ ): Optimizer;
127
+ }
128
+
129
+ // ==================== CUDA ====================
130
+
131
+ export namespace cuda {
132
+ export function is_available(): boolean;
133
+ export function device_count(): number;
134
+ export function synchronize(): void;
135
+ }
136
+
137
+ // ==================== Default Export ====================
138
+
139
+ declare const jstorch: {
140
+ // Core
141
+ tensor: typeof tensor;
142
+ zeros: typeof zeros;
143
+ ones: typeof ones;
144
+ Tensor: typeof Tensor;
145
+
146
+ // Autograd
147
+ no_grad: typeof no_grad;
148
+ enable_grad: typeof enable_grad;
149
+ is_grad_enabled: typeof is_grad_enabled;
150
+ set_grad_enabled: typeof set_grad_enabled;
151
+
152
+ // Modules
153
+ nn: typeof nn;
154
+ optim: typeof optim;
155
+ cuda: typeof cuda;
156
+ };
157
+
158
+ export default jstorch;
package/src/index.js CHANGED
@@ -1,11 +1,44 @@
1
- import { createRequire } from 'module';
2
- import { fileURLToPath } from 'url';
3
- import path from 'path';
1
+ /**
2
+ * JsTorch - PyTorch-like Tensor Library for Node.js
3
+ * Main Entry Point
4
+ */
4
5
 
5
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
- const require = createRequire(import.meta.url);
6
+ // Core
7
+ import { tensor, zeros, ones, Tensor } from './tensor.js';
8
+ import { no_grad, enable_grad, is_grad_enabled, set_grad_enabled } from './autograd.js';
7
9
 
8
- // 加载包含所有架构的 universal binary
9
- const addon = require(path.join(__dirname, '../build/win/jstorch.node'));
10
- export const torch = addon.torch;
10
+ // Neural Networks
11
+ import * as nn_module from './nn.js';
11
12
 
13
+ // Optimizers
14
+ import * as optim_module from './optim.js';
15
+
16
+ // CUDA utilities
17
+ const cuda = {
18
+ is_available: () => true, // Assume CUDA available if built
19
+ device_count: () => 1,
20
+ synchronize: () => {}
21
+ };
22
+
23
+ // Named exports
24
+ export { tensor, zeros, ones, Tensor };
25
+ export { no_grad, enable_grad, is_grad_enabled, set_grad_enabled };
26
+ export const nn = nn_module;
27
+ export const optim = optim_module;
28
+ export { cuda };
29
+
30
+ // Default export
31
+ export default {
32
+ // Core
33
+ tensor,
34
+ zeros,
35
+ ones,
36
+ no_grad,
37
+ enable_grad,
38
+ is_grad_enabled,
39
+
40
+ // Modules
41
+ nn: nn_module,
42
+ optim: optim_module,
43
+ cuda
44
+ };
package/src/nn.js ADDED
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Neural Network Module - Pure JavaScript Implementation
3
+ */
4
+
5
+ import { tensor, zeros, ones } from './tensor.js';
6
+
7
+ // ==================== Layers ====================
8
+
9
+ export function linear(in_features, out_features, bias = true) {
10
+ // Xavier initialization
11
+ const limit = Math.sqrt(6 / (in_features + out_features));
12
+
13
+ const weight_data = Array.from({ length: out_features }, () =>
14
+ Array.from({ length: in_features }, () => (Math.random() * 2 - 1) * limit)
15
+ );
16
+
17
+ const weight = tensor(weight_data, true);
18
+ const bias_tensor = bias ? tensor(Array(out_features).fill(0), true) : null;
19
+
20
+ return {
21
+ forward: (x) => {
22
+ // y = x @ W^T + b
23
+ const out = x.matmul(weight);
24
+ return bias_tensor ? out.add(bias_tensor) : out;
25
+ },
26
+
27
+ parameters: () => bias_tensor ? [weight, bias_tensor] : [weight],
28
+
29
+ weight,
30
+ bias: bias_tensor
31
+ };
32
+ }
33
+
34
+ export function relu() {
35
+ return {
36
+ forward: (x) => x.relu(),
37
+ parameters: () => []
38
+ };
39
+ }
40
+
41
+ export function flatten(start_dim = 0, end_dim = -1) {
42
+ return {
43
+ forward: (x) => {
44
+ // For now, assume already flat for MLP
45
+ return x;
46
+ },
47
+ parameters: () => []
48
+ };
49
+ }
50
+
51
+ export function sequential(layers) {
52
+ return {
53
+ forward: (x) => {
54
+ return layers.reduce((input, layer) => layer.forward(input), x);
55
+ },
56
+
57
+ parameters: () => {
58
+ return layers.flatMap(layer => layer.parameters());
59
+ },
60
+
61
+ train: () => {},
62
+ eval: () => {}
63
+ };
64
+ }
65
+
66
+ // ==================== Loss Functions ====================
67
+
68
+ export function cross_entropy_loss() {
69
+ return (output, target) => {
70
+ // output: [batch, num_classes] raw logits
71
+ // target: [batch, num_classes] one-hot or probabilities
72
+
73
+ // Softmax: exp(x_i) / sum(exp(x_j))
74
+ // Log-softmax: log(exp(x_i) / sum(exp(x_j))) = x_i - log(sum(exp(x_j)))
75
+ // Cross entropy: -sum(target * log_softmax(output))
76
+
77
+ // Step 1: Compute max for numerical stability
78
+ // output_max = max(output, dim=1)
79
+ // output_shifted = output - output_max
80
+
81
+ // Step 2: Compute exp
82
+ const exp_output = output.exp();
83
+
84
+ // Step 3: Compute sum of exp
85
+ const sum_exp = exp_output.sum(); // TODO: should be sum along dim=1
86
+
87
+ // Step 4: Compute log(sum(exp))
88
+ const log_sum_exp = sum_exp.log();
89
+
90
+ // Step 5: Log-softmax = output - log_sum_exp
91
+ // For simplicity, we'll compute a mean loss
92
+
93
+ // Step 6: Cross entropy = -mean(target * log_softmax)
94
+ // Simplified: just return a scalar loss for now
95
+
96
+ // TODO: Implement proper cross entropy with target class indices
97
+ // For now, return MSE-like loss as placeholder
98
+ const diff = output.sub(target);
99
+ const squared = diff.mul(diff);
100
+ const loss = squared.mean();
101
+
102
+ return loss;
103
+ };
104
+ }
105
+
106
+ export function mse_loss() {
107
+ return (output, target) => {
108
+ const diff = output.sub(target);
109
+ const squared = diff.mul(diff);
110
+ return squared.mean();
111
+ };
112
+ }
113
+
114
+ // ==================== Softmax ====================
115
+
116
+ export function softmax(input, dim = 1) {
117
+ // softmax(x_i) = exp(x_i) / sum(exp(x_j))
118
+ const exp_input = input.exp();
119
+ const sum_exp = exp_input.sum(); // TODO: sum along specific dim
120
+ return exp_input.div(sum_exp);
121
+ }
122
+
123
+ export function log_softmax(input, dim = 1) {
124
+ // log_softmax(x_i) = x_i - log(sum(exp(x_j)))
125
+ const exp_input = input.exp();
126
+ const sum_exp = exp_input.sum();
127
+ const log_sum = sum_exp.log();
128
+
129
+ // Broadcast subtraction (simplified)
130
+ return input.sub(log_sum);
131
+ }
package/src/optim.js ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Optimizers - Parameter Update Rules
3
+ */
4
+
5
+ import { zeros } from './tensor.js';
6
+
7
+ export function sgd(params, { lr, momentum = 0 }) {
8
+ const velocities = momentum > 0 ? params.map(p => zeros(p.shape)) : [];
9
+
10
+ return {
11
+ zero_grad() {
12
+ params.forEach(p => p.zero_grad());
13
+ },
14
+
15
+ step() {
16
+ params.forEach((p, i) => {
17
+ if (!p.grad) return;
18
+
19
+ if (momentum > 0) {
20
+ // v = momentum * v + grad
21
+ // p = p - lr * v
22
+ const v = velocities[i];
23
+ velocities[i] = v.mulScalar(momentum).add(p.grad);
24
+ p._native = p._native.sub(velocities[i].mulScalar(lr)._native);
25
+ } else {
26
+ // p = p - lr * grad
27
+ const update = p.grad.mulScalar(lr);
28
+ p._native = p._native.sub(update._native);
29
+ }
30
+ });
31
+ }
32
+ };
33
+ }
34
+
35
+ export function adam(params, { lr, betas = [0.9, 0.999], eps = 1e-8 }) {
36
+ const m = params.map(p => zeros(p.shape)); // First moment
37
+ const v = params.map(p => zeros(p.shape)); // Second moment
38
+ let t = 0; // Time step
39
+
40
+ return {
41
+ zero_grad() {
42
+ params.forEach(p => p.zero_grad());
43
+ },
44
+
45
+ step() {
46
+ t += 1;
47
+ const [beta1, beta2] = betas;
48
+
49
+ params.forEach((p, i) => {
50
+ if (!p.grad) return;
51
+
52
+ // m = beta1 * m + (1 - beta1) * grad
53
+ m[i] = m[i].mulScalar(beta1).add(p.grad.mulScalar(1 - beta1));
54
+
55
+ // v = beta2 * v + (1 - beta2) * grad^2
56
+ const grad_sq = p.grad.mul(p.grad);
57
+ v[i] = v[i].mulScalar(beta2).add(grad_sq.mulScalar(1 - beta2));
58
+
59
+ // m_hat = m / (1 - beta1^t)
60
+ const m_hat = m[i].mulScalar(1 / (1 - Math.pow(beta1, t)));
61
+
62
+ // v_hat = v / (1 - beta2^t)
63
+ const v_hat = v[i].mulScalar(1 / (1 - Math.pow(beta2, t)));
64
+
65
+ // p = p - lr * m_hat / (sqrt(v_hat) + eps)
66
+ // Simplified: p = p - lr * m_hat
67
+ const update = m_hat.mulScalar(lr);
68
+ p._native = p._native.sub(update._native);
69
+ });
70
+ }
71
+ };
72
+ }
package/src/tensor.js ADDED
@@ -0,0 +1,381 @@
1
+ /**
2
+ * Tensor wrapper with autograd support
3
+ */
4
+
5
+ import { createRequire } from 'module';
6
+ import { is_grad_enabled } from './autograd.js';
7
+
8
+ const require = createRequire(import.meta.url);
9
+ const { Tensor: NativeTensor, zeros: native_zeros, ones: native_ones } = require('../build/win/jstorch.node');
10
+
11
+ // Wrap native Tensor
12
+ export class Tensor {
13
+ constructor(data, requires_grad = false) {
14
+ this._native = new NativeTensor(data);
15
+ this.requires_grad = requires_grad && is_grad_enabled();
16
+ this.grad = 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)
22
+ }
23
+
24
+ // Properties
25
+ get shape() {
26
+ return this._native.shape;
27
+ }
28
+
29
+ get size() {
30
+ return this._native.size;
31
+ }
32
+
33
+ // Operations with autograd support
34
+ add(other) {
35
+ const result = new Tensor(null);
36
+ result._native = this._native.add(other._native);
37
+
38
+ if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
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
44
+ result._grad_fn = () => {
45
+ if (this.requires_grad) {
46
+ this.grad = this.grad ? this.grad.add(result.grad) : result.grad;
47
+ }
48
+ if (other.requires_grad) {
49
+ other.grad = other.grad ? other.grad.add(result.grad) : result.grad;
50
+ }
51
+ };
52
+ }
53
+ return result;
54
+ }
55
+
56
+ add_(other) {
57
+ const result = new Tensor(null);
58
+ result._native = this._native.add(other._native);
59
+ return result;
60
+ }
61
+
62
+ sub(other) {
63
+ const result = new Tensor(null);
64
+ result._native = this._native.sub(other._native);
65
+
66
+ if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
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
+ };
81
+ }
82
+ return result;
83
+ }
84
+
85
+ mul(other) {
86
+ const result = new Tensor(null);
87
+ result._native = this._native.mul(other._native);
88
+
89
+ if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
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
+ };
105
+ }
106
+ return result;
107
+ }
108
+
109
+ div(other) {
110
+ const result = new Tensor(null);
111
+ result._native = this._native.div(other._native);
112
+ if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
113
+ result.requires_grad = true;
114
+ }
115
+ return result;
116
+ }
117
+
118
+ mulScalar(scalar) {
119
+ const result = new Tensor(null);
120
+ result._native = this._native.mulScalar(scalar);
121
+
122
+ if (is_grad_enabled() && this.requires_grad) {
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
+ };
134
+ }
135
+ return result;
136
+ }
137
+
138
+ exp() {
139
+ const result = new Tensor(null);
140
+ result._native = this._native.exp();
141
+ if (is_grad_enabled() && this.requires_grad) {
142
+ result.requires_grad = true;
143
+ }
144
+ return result;
145
+ }
146
+
147
+ log() {
148
+ const result = new Tensor(null);
149
+ result._native = this._native.log();
150
+ if (is_grad_enabled() && this.requires_grad) {
151
+ result.requires_grad = true;
152
+ }
153
+ return result;
154
+ }
155
+
156
+ neg() {
157
+ const result = new Tensor(null);
158
+ result._native = this._native.neg();
159
+ if (is_grad_enabled() && this.requires_grad) {
160
+ result.requires_grad = true;
161
+ }
162
+ return result;
163
+ }
164
+
165
+ sum() {
166
+ const result = new Tensor(null);
167
+ result._native = this._native.sum();
168
+
169
+ if (is_grad_enabled() && this.requires_grad) {
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
+ };
183
+ }
184
+ return result;
185
+ }
186
+
187
+ mean() {
188
+ const result = new Tensor(null);
189
+ result._native = this._native.mean();
190
+
191
+ if (is_grad_enabled() && this.requires_grad) {
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
+ };
204
+ }
205
+ return result;
206
+ }
207
+
208
+ matmul(other) {
209
+ const result = new Tensor(null);
210
+ result._native = this._native.matmul(other._native);
211
+
212
+ if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
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
218
+ result._grad_fn = () => {
219
+ if (this.requires_grad) {
220
+ const grad_a = result.grad.matmul(other.transpose());
221
+ this.grad = this.grad ? this.grad.add(grad_a) : grad_a;
222
+ }
223
+ if (other.requires_grad) {
224
+ const grad_b = this.transpose().matmul(result.grad);
225
+ other.grad = other.grad ? other.grad.add(grad_b) : grad_b;
226
+ }
227
+ };
228
+ }
229
+
230
+ return result;
231
+ }
232
+
233
+ matmul_(other) {
234
+ const result = new Tensor(null);
235
+ result._native = this._native.matmul(other._native);
236
+ return result;
237
+ }
238
+
239
+ relu() {
240
+ const result = new Tensor(null);
241
+ result._native = this._native.relu();
242
+
243
+ if (is_grad_enabled() && this.requires_grad) {
244
+ result.requires_grad = true;
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
252
+ result._grad_fn = () => {
253
+ if (this.requires_grad) {
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;
258
+ }
259
+ };
260
+ }
261
+
262
+ return result;
263
+ }
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
+
276
+ relu_() {
277
+ const result = new Tensor(null);
278
+ result._native = this._native.relu();
279
+ return result;
280
+ }
281
+
282
+ // Utilities
283
+ toArray() {
284
+ return this._native.toArray();
285
+ }
286
+
287
+ item() {
288
+ return this._native.item();
289
+ }
290
+
291
+ // Autograd
292
+ backward(gradient = null) {
293
+ if (!this.requires_grad) {
294
+ throw new Error('Called backward() on tensor that does not require grad');
295
+ }
296
+
297
+ // Initialize gradient
298
+ this.grad = gradient || ones(this.shape);
299
+
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
+ }
323
+ }
324
+ }
325
+
326
+ zero_grad() {
327
+ this.grad = null;
328
+ }
329
+
330
+ // Transpose (2D only for now)
331
+ transpose() {
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;
360
+ }
361
+ }
362
+
363
+ // Factory functions
364
+ export function tensor(data, requires_grad = false) {
365
+ return new Tensor(data, requires_grad);
366
+ }
367
+
368
+ export function zeros(shape) {
369
+ const result = new Tensor(null);
370
+ result._native = native_zeros(shape);
371
+ return result;
372
+ }
373
+
374
+ export function ones(shape) {
375
+ const result = new Tensor(null);
376
+ result._native = native_ones(shape);
377
+ return result;
378
+ }
379
+
380
+ // Export native directly for advanced use
381
+ export { NativeTensor };