@alex000291/jstorch 0.1.0 → 0.2.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/build/win/jstorch.node +0 -0
- package/package.json +6 -3
- package/src/autograd.js +37 -0
- package/src/index.d.ts +158 -0
- package/src/index.js +41 -8
- package/src/nn.js +131 -0
- package/src/optim.js +72 -0
- package/src/tensor.js +241 -0
package/build/win/jstorch.node
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alex000291/jstorch",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.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/
|
|
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",
|
package/src/autograd.js
ADDED
|
@@ -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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* JsTorch - PyTorch-like Tensor Library for Node.js
|
|
3
|
+
* Main Entry Point
|
|
4
|
+
*/
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
//
|
|
9
|
-
|
|
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,241 @@
|
|
|
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
|
+
this._grad_fn = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Properties
|
|
21
|
+
get shape() {
|
|
22
|
+
return this._native.shape;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get size() {
|
|
26
|
+
return this._native.size;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Operations with autograd support
|
|
30
|
+
add(other) {
|
|
31
|
+
const result = new Tensor(null);
|
|
32
|
+
result._native = this._native.add(other._native);
|
|
33
|
+
|
|
34
|
+
if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
|
|
35
|
+
result.requires_grad = true;
|
|
36
|
+
result._grad_fn = () => {
|
|
37
|
+
if (this.requires_grad) {
|
|
38
|
+
this.grad = this.grad ? this.grad.add(result.grad) : result.grad;
|
|
39
|
+
}
|
|
40
|
+
if (other.requires_grad) {
|
|
41
|
+
other.grad = other.grad ? other.grad.add(result.grad) : result.grad;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
add_(other) {
|
|
49
|
+
const result = new Tensor(null);
|
|
50
|
+
result._native = this._native.add(other._native);
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
sub(other) {
|
|
55
|
+
const result = new Tensor(null);
|
|
56
|
+
result._native = this._native.sub(other._native);
|
|
57
|
+
if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
|
|
58
|
+
result.requires_grad = true;
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
mul(other) {
|
|
64
|
+
const result = new Tensor(null);
|
|
65
|
+
result._native = this._native.mul(other._native);
|
|
66
|
+
if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
|
|
67
|
+
result.requires_grad = true;
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
div(other) {
|
|
73
|
+
const result = new Tensor(null);
|
|
74
|
+
result._native = this._native.div(other._native);
|
|
75
|
+
if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
|
|
76
|
+
result.requires_grad = true;
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
mulScalar(scalar) {
|
|
82
|
+
const result = new Tensor(null);
|
|
83
|
+
result._native = this._native.mulScalar(scalar);
|
|
84
|
+
if (is_grad_enabled() && this.requires_grad) {
|
|
85
|
+
result.requires_grad = true;
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
exp() {
|
|
91
|
+
const result = new Tensor(null);
|
|
92
|
+
result._native = this._native.exp();
|
|
93
|
+
if (is_grad_enabled() && this.requires_grad) {
|
|
94
|
+
result.requires_grad = true;
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
log() {
|
|
100
|
+
const result = new Tensor(null);
|
|
101
|
+
result._native = this._native.log();
|
|
102
|
+
if (is_grad_enabled() && this.requires_grad) {
|
|
103
|
+
result.requires_grad = true;
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
neg() {
|
|
109
|
+
const result = new Tensor(null);
|
|
110
|
+
result._native = this._native.neg();
|
|
111
|
+
if (is_grad_enabled() && this.requires_grad) {
|
|
112
|
+
result.requires_grad = true;
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
sum() {
|
|
118
|
+
const result = new Tensor(null);
|
|
119
|
+
result._native = this._native.sum();
|
|
120
|
+
if (is_grad_enabled() && this.requires_grad) {
|
|
121
|
+
result.requires_grad = true;
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
mean() {
|
|
127
|
+
const result = new Tensor(null);
|
|
128
|
+
result._native = this._native.mean();
|
|
129
|
+
if (is_grad_enabled() && this.requires_grad) {
|
|
130
|
+
result.requires_grad = true;
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
matmul(other) {
|
|
136
|
+
const result = new Tensor(null);
|
|
137
|
+
result._native = this._native.matmul(other._native);
|
|
138
|
+
|
|
139
|
+
if (is_grad_enabled() && (this.requires_grad || other.requires_grad)) {
|
|
140
|
+
result.requires_grad = true;
|
|
141
|
+
result._grad_fn = () => {
|
|
142
|
+
// Backward: dL/dA = dL/dC @ B^T, dL/dB = A^T @ dL/dC
|
|
143
|
+
if (this.requires_grad) {
|
|
144
|
+
const grad_a = result.grad.matmul(other.transpose());
|
|
145
|
+
this.grad = this.grad ? this.grad.add(grad_a) : grad_a;
|
|
146
|
+
}
|
|
147
|
+
if (other.requires_grad) {
|
|
148
|
+
const grad_b = this.transpose().matmul(result.grad);
|
|
149
|
+
other.grad = other.grad ? other.grad.add(grad_b) : grad_b;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
matmul_(other) {
|
|
158
|
+
const result = new Tensor(null);
|
|
159
|
+
result._native = this._native.matmul(other._native);
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
relu() {
|
|
164
|
+
const result = new Tensor(null);
|
|
165
|
+
result._native = this._native.relu();
|
|
166
|
+
|
|
167
|
+
if (is_grad_enabled() && this.requires_grad) {
|
|
168
|
+
result.requires_grad = true;
|
|
169
|
+
const input_data = this.toArray(); // Save for backward
|
|
170
|
+
result._grad_fn = () => {
|
|
171
|
+
// Backward: gradient passes through where input > 0
|
|
172
|
+
// TODO: implement element-wise multiplication with mask
|
|
173
|
+
if (this.requires_grad) {
|
|
174
|
+
this.grad = result.grad; // Simplified for now
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
relu_() {
|
|
183
|
+
const result = new Tensor(null);
|
|
184
|
+
result._native = this._native.relu();
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Utilities
|
|
189
|
+
toArray() {
|
|
190
|
+
return this._native.toArray();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
item() {
|
|
194
|
+
return this._native.item();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Autograd
|
|
198
|
+
backward(gradient = null) {
|
|
199
|
+
if (!this.requires_grad) {
|
|
200
|
+
throw new Error('Called backward() on tensor that does not require grad');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Initialize gradient
|
|
204
|
+
this.grad = gradient || ones(this.shape);
|
|
205
|
+
|
|
206
|
+
// Topological sort and execute grad_fn (simplified - just call directly for now)
|
|
207
|
+
if (this._grad_fn) {
|
|
208
|
+
this._grad_fn();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
zero_grad() {
|
|
213
|
+
this.grad = null;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Placeholder for transpose (TODO: implement in C++)
|
|
217
|
+
transpose() {
|
|
218
|
+
// Assume 2D for now
|
|
219
|
+
return this; // TODO
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Factory functions
|
|
224
|
+
export function tensor(data, requires_grad = false) {
|
|
225
|
+
return new Tensor(data, requires_grad);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function zeros(shape) {
|
|
229
|
+
const result = new Tensor(null);
|
|
230
|
+
result._native = native_zeros(shape);
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function ones(shape) {
|
|
235
|
+
const result = new Tensor(null);
|
|
236
|
+
result._native = native_ones(shape);
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Export native directly for advanced use
|
|
241
|
+
export { NativeTensor };
|