catniff 0.6.8 → 0.6.10

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Catniff
1
+ # Catniff 😺🌿
2
2
 
3
3
  Catniff is a small deep learning framework for Javacript, built to be Torch-like, but more direct on tensors and autograd usage like Tinygrad. This project is under development currently, so keep in mind that APIs can be unstable and backwards-incompatible. On a side-note, the name is a play on "catnip" and "differentiation".
4
4
 
package/dist/core.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { Backend } from "./backend";
2
2
  export type TensorValue = number | TensorValue[];
3
3
  export interface TensorOptions {
4
- shape?: readonly number[];
5
- strides?: readonly number[];
4
+ shape?: number[];
5
+ strides?: number[];
6
6
  offset?: number;
7
7
  numel?: number;
8
8
  grad?: Tensor;
@@ -13,8 +13,8 @@ export interface TensorOptions {
13
13
  }
14
14
  export declare class Tensor {
15
15
  value: number[] | number;
16
- readonly shape: readonly number[];
17
- readonly strides: readonly number[];
16
+ shape: number[];
17
+ strides: number[];
18
18
  offset: number;
19
19
  numel: number;
20
20
  grad?: Tensor;
@@ -24,20 +24,20 @@ export declare class Tensor {
24
24
  device: string;
25
25
  static training: boolean;
26
26
  constructor(value: TensorValue, options?: TensorOptions);
27
- static flatten(tensor: TensorValue): number[] | number;
28
- static getShape(tensor: TensorValue): readonly number[];
29
- static getStrides(shape: readonly number[]): readonly number[];
30
- static padShape(stridesA: readonly number[], stridesB: readonly number[], shapeA: readonly number[], shapeB: readonly number[]): [
31
- readonly number[],
32
- readonly number[],
33
- readonly number[],
34
- readonly number[]
27
+ static flattenValue(tensor: TensorValue): number[] | number;
28
+ static getShape(tensor: TensorValue): number[];
29
+ static getStrides(shape: number[]): number[];
30
+ static padShape(stridesA: number[], stridesB: number[], shapeA: number[], shapeB: number[]): [
31
+ number[],
32
+ number[],
33
+ number[],
34
+ number[]
35
35
  ];
36
- static broadcastShapes(shapeA: readonly number[], shapeB: readonly number[]): readonly number[];
37
- static indexToCoords(index: number, strides: readonly number[]): number[];
38
- static coordsToUnbroadcastedIndex(coords: number[], shape: readonly number[], strides: readonly number[]): number;
39
- static coordsToIndex(coords: number[], strides: readonly number[]): number;
40
- static shapeToSize(shape: readonly number[]): number;
36
+ static broadcastShapes(shapeA: number[], shapeB: number[]): number[];
37
+ static indexToCoords(index: number, strides: number[]): number[];
38
+ static coordsToUnbroadcastedIndex(coords: number[], shape: number[], strides: number[]): number;
39
+ static coordsToIndex(coords: number[], strides: number[]): number;
40
+ static shapeToSize(shape: number[]): number;
41
41
  static elementWiseAB(tA: Tensor, tB: Tensor, op: (tA: number, tB: number) => number): Tensor;
42
42
  static elementWiseSelf(tA: Tensor, op: (tA: number) => number): Tensor;
43
43
  elementWiseABDAG(other: TensorValue | Tensor, op: (a: number, b: number) => number, thisGrad?: (self: Tensor, other: Tensor, outGrad: Tensor) => Tensor, otherGrad?: (self: Tensor, other: Tensor, outGrad: Tensor) => Tensor): Tensor;
@@ -47,8 +47,9 @@ export declare class Tensor {
47
47
  static normalizeDims(dims: number[], numDims: number): number[];
48
48
  isContiguous(): boolean;
49
49
  contiguous(): Tensor;
50
- view(newShape: readonly number[]): Tensor;
51
- reshape(newShape: readonly number[]): Tensor;
50
+ view(newShape: number[]): Tensor;
51
+ reshape(newShape: number[]): Tensor;
52
+ flatten(startDim?: number, endDim?: number): Tensor;
52
53
  transpose(dim1: number, dim2: number): Tensor;
53
54
  swapaxes: (dim1: number, dim2: number) => Tensor;
54
55
  swapdims: (dim1: number, dim2: number) => Tensor;
@@ -88,6 +89,7 @@ export declare class Tensor {
88
89
  var(dims?: number[] | number, keepDims?: boolean): Tensor;
89
90
  std(dims?: number[] | number, keepDims?: boolean): Tensor;
90
91
  softmax(dim?: number): Tensor;
92
+ softmin(dim?: number): Tensor;
91
93
  add(other: TensorValue | Tensor): Tensor;
92
94
  sub(other: TensorValue | Tensor): Tensor;
93
95
  subtract: (other: TensorValue | Tensor) => Tensor;
@@ -157,6 +159,7 @@ export declare class Tensor {
157
159
  log10(): Tensor;
158
160
  log1p(): Tensor;
159
161
  relu(): Tensor;
162
+ leakyRelu(negativeSlope?: number): Tensor;
160
163
  sigmoid(): Tensor;
161
164
  tanh(): Tensor;
162
165
  softplus(): Tensor;
@@ -186,17 +189,17 @@ export declare class Tensor {
186
189
  triu(diagonal?: number): Tensor;
187
190
  tril(diagonal?: number): Tensor;
188
191
  maskedFill(mask: Tensor | TensorValue, value: number): Tensor;
189
- static full(shape: readonly number[], num: number, options?: TensorOptions): Tensor;
192
+ static full(shape: number[], num: number, options?: TensorOptions): Tensor;
190
193
  static fullLike(tensor: Tensor, num: number, options?: TensorOptions): Tensor;
191
- static ones(shape?: readonly number[], options?: TensorOptions): Tensor;
194
+ static ones(shape?: number[], options?: TensorOptions): Tensor;
192
195
  static onesLike(tensor: Tensor, options?: TensorOptions): Tensor;
193
- static zeros(shape?: readonly number[], options?: TensorOptions): Tensor;
196
+ static zeros(shape?: number[], options?: TensorOptions): Tensor;
194
197
  static zerosLike(tensor: Tensor, options?: TensorOptions): Tensor;
195
- static rand(shape?: readonly number[], options?: TensorOptions): Tensor;
198
+ static rand(shape?: number[], options?: TensorOptions): Tensor;
196
199
  static randLike(tensor: Tensor, options?: TensorOptions): Tensor;
197
- static randn(shape?: readonly number[], options?: TensorOptions): Tensor;
200
+ static randn(shape?: number[], options?: TensorOptions): Tensor;
198
201
  static randnLike(tensor: Tensor, options?: TensorOptions): Tensor;
199
- static randint(shape: readonly number[], low: number, high: number, options?: TensorOptions): Tensor;
202
+ static randint(shape: number[], low: number, high: number, options?: TensorOptions): Tensor;
200
203
  static randintLike(tensor: Tensor, low: number, high: number, options?: TensorOptions): Tensor;
201
204
  static randperm(n: number, options?: TensorOptions): Tensor;
202
205
  static normal(shape: number[], mean: number, stdDev: number, options?: TensorOptions): Tensor;
package/dist/core.js CHANGED
@@ -16,7 +16,7 @@ class Tensor {
16
16
  static training = false;
17
17
  constructor(value, options = {}) {
18
18
  // Storage
19
- this.value = Tensor.flatten(value);
19
+ this.value = Tensor.flattenValue(value);
20
20
  // Tensor metadata
21
21
  this.shape = options.shape || Tensor.getShape(value);
22
22
  this.strides = options.strides || Tensor.getStrides(this.shape);
@@ -32,7 +32,7 @@ class Tensor {
32
32
  this.to_(this.device);
33
33
  }
34
34
  // Utility to flatten an nD array to be 1D
35
- static flatten(tensor) {
35
+ static flattenValue(tensor) {
36
36
  // Handle scalar tensors
37
37
  if (typeof tensor === "number")
38
38
  return tensor;
@@ -329,7 +329,7 @@ class Tensor {
329
329
  // Verify shape size
330
330
  const originalSize = this.numel;
331
331
  const outputSize = Tensor.shapeToSize(newShape);
332
- if (originalSize !== outputSize) {
332
+ if (originalSize !== outputSize || typeof this.value === "number") {
333
333
  throw new Error("Can not create view: incompatible sizes");
334
334
  }
335
335
  // Verify compatibility (only contiguity for now)
@@ -357,7 +357,7 @@ class Tensor {
357
357
  // Verify shape size
358
358
  const originalSize = this.numel;
359
359
  const outputSize = Tensor.shapeToSize(newShape);
360
- if (originalSize !== outputSize) {
360
+ if (originalSize !== outputSize || typeof this.value === "number") {
361
361
  throw new Error("Can not reshape: incompatible sizes");
362
362
  }
363
363
  // Create new tensor with forced compatibility (only contiguity for now)
@@ -377,6 +377,40 @@ class Tensor {
377
377
  }
378
378
  return out;
379
379
  }
380
+ flatten(startDim = 0, endDim = -1) {
381
+ // Handle negative indices
382
+ if (startDim < 0) {
383
+ startDim += this.shape.length;
384
+ }
385
+ if (endDim < 0) {
386
+ endDim += this.shape.length;
387
+ }
388
+ // If dimension out of bound, throw error
389
+ if (startDim >= this.shape.length || endDim >= this.shape.length || startDim < 0 || endDim < 0) {
390
+ throw new Error("Dimensions do not exist to flatten");
391
+ }
392
+ const newShape = [];
393
+ let middleSize = 1;
394
+ for (let index = 0; index < this.shape.length; index++) {
395
+ // Keep dims before startDim
396
+ if (index < startDim) {
397
+ newShape.push(this.shape[index]);
398
+ }
399
+ // Multiply dims from startDim to endDim
400
+ if (index >= startDim && index <= endDim) {
401
+ middleSize *= this.shape[index];
402
+ }
403
+ // Push new flatten middle
404
+ if (index === endDim) {
405
+ newShape.push(middleSize);
406
+ }
407
+ // Keep dims after endDim
408
+ if (index > endDim) {
409
+ newShape.push(this.shape[index]);
410
+ }
411
+ }
412
+ return this.reshape(newShape);
413
+ }
380
414
  // Transpose
381
415
  transpose(dim1, dim2) {
382
416
  // Handle negative indices
@@ -611,6 +645,10 @@ class Tensor {
611
645
  if (dim < 0) {
612
646
  dim += this.shape.length;
613
647
  }
648
+ // If dimension out of bound, throw error
649
+ if (dim >= this.shape.length || dim < 0) {
650
+ throw new Error("Dimension do not exist to chunk");
651
+ }
614
652
  const sliceOpt = new Array(this.shape.length);
615
653
  for (let index = 0; index < sliceOpt.length; index++) {
616
654
  sliceOpt[index] = [];
@@ -873,14 +911,37 @@ class Tensor {
873
911
  if (typeof this.value === "number")
874
912
  return this;
875
913
  // Handle negative indexing
876
- if (dim < 0)
877
- dim = this.shape.length + dim;
914
+ if (dim < 0) {
915
+ dim += this.shape.length;
916
+ }
917
+ // If dimension out of bound, throw error
918
+ if (dim >= this.shape.length || dim < 0) {
919
+ throw new Error("Dimension do not exist to apply softmax");
920
+ }
878
921
  const maxVals = this.max(dim, true);
879
922
  const shifted = this.sub(maxVals);
880
923
  const expVals = shifted.exp();
881
924
  const sumExp = expVals.sum(dim, true);
882
925
  return expVals.div(sumExp);
883
926
  }
927
+ // Tensor softmin
928
+ softmin(dim = -1) {
929
+ if (typeof this.value === "number")
930
+ return this;
931
+ // Handle negative indexing
932
+ if (dim < 0) {
933
+ dim += this.shape.length;
934
+ }
935
+ // If dimension out of bound, throw error
936
+ if (dim >= this.shape.length || dim < 0) {
937
+ throw new Error("Dimension do not exist to apply softmin");
938
+ }
939
+ const maxVals = this.max(dim, true);
940
+ const shifted = maxVals.sub(this);
941
+ const expVals = shifted.exp();
942
+ const sumExp = expVals.sum(dim, true);
943
+ return expVals.div(sumExp);
944
+ }
884
945
  // Tensor element-wise addition
885
946
  add(other) {
886
947
  return this.elementWiseABDAG(other, (a, b) => a + b, (self, other, outGrad) => outGrad, (self, other, outGrad) => outGrad);
@@ -1103,6 +1164,12 @@ class Tensor {
1103
1164
  relu() {
1104
1165
  return this.elementWiseSelfDAG((a) => Math.max(a, 0), (self, outGrad) => outGrad.mul(self.gt(0)));
1105
1166
  }
1167
+ // Tensor element-wise leaky relu
1168
+ leakyRelu(negativeSlope = 0.01) {
1169
+ return this.elementWiseSelfDAG((a) => Math.max(a, 0) + negativeSlope * Math.min(a, 0), (self, outGrad) => {
1170
+ return outGrad.mul(self.gt(0).add(self.le(0).mul(negativeSlope)));
1171
+ });
1172
+ }
1106
1173
  // Tensor element-wise sigmoid
1107
1174
  sigmoid() {
1108
1175
  return this.elementWiseSelfDAG((a) => 1 / (1 + Math.exp(-a)), (self, outGrad) => {
@@ -1133,7 +1200,7 @@ class Tensor {
1133
1200
  mish() {
1134
1201
  return this.elementWiseSelfDAG((a) => a * Math.tanh(Math.log1p(Math.exp(a))), (self, outGrad) => {
1135
1202
  const tanhSoftPlus = self.exp().add(1).log().tanh();
1136
- // tanh(softplus(x)) + x * (1 - tanh²(softplus(x))) * sigmoid(x)
1203
+ // tanh(softplus(x)) + x * (1 - tanh^2(softplus(x))) * sigmoid(x)
1137
1204
  const derivative = tanhSoftPlus.add(self.mul(tanhSoftPlus.square().neg().add(1)).mul(self.sigmoid()));
1138
1205
  return outGrad.mul(derivative);
1139
1206
  });
package/dist/optim.js CHANGED
@@ -108,17 +108,17 @@ class Adam extends BaseOptimizer {
108
108
  velocityBuffer = core_1.Tensor.zerosLike(grad); // Initialize with zeros (same shape as grad)
109
109
  this.velocityBuffers.set(param, velocityBuffer);
110
110
  }
111
- // Update biased first moment estimate: m_t = β1 * m_{t-1} + (1 - β1) * g_t
111
+ // Update biased first moment estimate: m_t = beta1 * m_{t-1} + (1 - beta1) * g_t
112
112
  momentumBuffer = momentumBuffer.mul(beta1).add(grad.mul(1 - beta1));
113
113
  this.momentumBuffers.set(param, momentumBuffer);
114
- // Update biased second moment estimate: v_t = β2 * v_{t-1} + (1 - β2) * g_t^2
114
+ // Update biased second moment estimate: v_t = beta2 * v_{t-1} + (1 - beta2) * g_t^2
115
115
  velocityBuffer = velocityBuffer.mul(beta2).add(grad.pow(2).mul(1 - beta2));
116
116
  this.velocityBuffers.set(param, velocityBuffer);
117
- // Compute bias-corrected first moment: m̂_t = m_t / (1 - β1^t)
117
+ // Compute bias-corrected first moment: m_hat_t = m_t / (1 - beta1^t)
118
118
  const correctedMomentum = momentumBuffer.div(biasCorrection1);
119
- // Compute bias-corrected second moment: v̂_t = v_t / (1 - β2^t)
119
+ // Compute bias-corrected second moment: v_hat_t = v_t / (1 - beta2^t)
120
120
  const correctedVelocity = velocityBuffer.div(biasCorrection2);
121
- // Update parameters: θ_t = θ_{t-1} - α * m̂_t / (√v̂_t + ε)
121
+ // Update parameters: theta_t = theta_{t-1} - alpha * m_hat_t / (sqrt(v_hat_t) + epsilon)
122
122
  const denom = correctedVelocity.sqrt().add(this.eps);
123
123
  const stepSize = correctedMomentum.div(denom).mul(this.lr);
124
124
  const newParam = detachedParam.sub(stepSize);
@@ -166,17 +166,17 @@ class AdamW extends BaseOptimizer {
166
166
  velocityBuffer = core_1.Tensor.zerosLike(grad); // Initialize with zeros (same shape as grad)
167
167
  this.velocityBuffers.set(param, velocityBuffer);
168
168
  }
169
- // Update biased first moment estimate: m_t = β1 * m_{t-1} + (1 - β1) * g_t
169
+ // Update biased first moment estimate: m_t = beta1 * m_{t-1} + (1 - beta1) * g_t
170
170
  momentumBuffer = momentumBuffer.mul(beta1).add(grad.mul(1 - beta1));
171
171
  this.momentumBuffers.set(param, momentumBuffer);
172
- // Update biased second moment estimate: v_t = β2 * v_{t-1} + (1 - β2) * g_t^2
172
+ // Update biased second moment estimate: v_t = beta2 * v_{t-1} + (1 - beta2) * g_t^2
173
173
  velocityBuffer = velocityBuffer.mul(beta2).add(grad.pow(2).mul(1 - beta2));
174
174
  this.velocityBuffers.set(param, velocityBuffer);
175
- // Compute bias-corrected first moment: m̂_t = m_t / (1 - β1^t)
175
+ // Compute bias-corrected first moment: m_hat_t = m_t / (1 - beta1^t)
176
176
  const correctedMomentum = momentumBuffer.div(biasCorrection1);
177
- // Compute bias-corrected second moment: v̂_t = v_t / (1 - β2^t)
177
+ // Compute bias-corrected second moment: v_hat_t = v_t / (1 - beta2^t)
178
178
  const correctedVelocity = velocityBuffer.div(biasCorrection2);
179
- // Update parameters: θ_t = θ_t - α * m̂_t / (√v̂_t + ε)
179
+ // Update parameters: theta_t = theta_{t-1} - alpha * m_hat_t / (sqrt(v_hat_t) + epsilon)
180
180
  const denom = correctedVelocity.sqrt().add(this.eps);
181
181
  const stepSize = correctedMomentum.div(denom).mul(this.lr);
182
182
  const newParam = detachedParam.sub(stepSize);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "catniff",
3
- "version": "0.6.8",
3
+ "version": "0.6.10",
4
4
  "description": "A small Torch-like deep learning framework for Javascript",
5
5
  "main": "index.js",
6
6
  "scripts": {