catniff 0.2.4 → 0.2.6

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.
Files changed (3) hide show
  1. package/dist/core.d.ts +19 -10
  2. package/dist/core.js +169 -16
  3. package/package.json +1 -1
package/dist/core.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export type TensorValue = number | TensorValue[];
2
2
  export interface TensorOptions {
3
- shape?: number[];
4
- strides?: number[];
3
+ shape?: readonly number[];
4
+ strides?: readonly number[];
5
5
  grad?: Tensor;
6
6
  requiresGrad?: boolean;
7
7
  gradFn?: Function;
@@ -9,20 +9,27 @@ export interface TensorOptions {
9
9
  }
10
10
  export declare class Tensor {
11
11
  value: number[] | number;
12
- readonly shape: number[];
13
- readonly strides: number[];
12
+ readonly shape: readonly number[];
13
+ readonly strides: readonly number[];
14
14
  grad?: Tensor;
15
15
  requiresGrad: boolean;
16
16
  gradFn: Function;
17
17
  children: Tensor[];
18
18
  constructor(value: TensorValue, options?: TensorOptions);
19
19
  static flatten(tensor: TensorValue): number[] | number;
20
- static getShape(tensor: TensorValue): number[];
21
- static getStrides(shape: number[]): number[];
22
- static padShape(stridesA: number[], stridesB: number[], shapeA: number[], shapeB: number[]): number[][];
23
- static broadcastShapes(shapeA: number[], shapeB: number[]): number[];
24
- static indexToCoords(index: number, shape: number[], strides: number[]): number[];
25
- static coordsToIndex(coords: number[], shape: number[], strides: number[]): number;
20
+ static getShape(tensor: TensorValue): readonly number[];
21
+ static getStrides(shape: readonly number[]): readonly number[];
22
+ static padShape(stridesA: readonly number[], stridesB: readonly number[], shapeA: readonly number[], shapeB: readonly number[]): [
23
+ readonly number[],
24
+ readonly number[],
25
+ readonly number[],
26
+ readonly number[]
27
+ ];
28
+ static broadcastShapes(shapeA: readonly number[], shapeB: readonly number[]): readonly number[];
29
+ static indexToCoords(index: number, shape: readonly number[], strides: readonly number[]): number[];
30
+ static coordsToUnbroadcastedIndex(coords: number[], shape: readonly number[], strides: readonly number[]): number;
31
+ static coordsToIndex(coords: number[], strides: readonly number[]): number;
32
+ static shapeToSize(shape: readonly number[]): number;
26
33
  static elementWiseAB(tA: Tensor, tB: Tensor, op: (tA: number, tB: number) => number): Tensor;
27
34
  static elementWiseSelf(tA: Tensor, op: (tA: number) => number): Tensor;
28
35
  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;
@@ -32,6 +39,8 @@ export declare class Tensor {
32
39
  squeeze(dims?: number[] | number): Tensor;
33
40
  unsqueeze(dim: number): Tensor;
34
41
  sum(dims?: number[] | number, keepDims?: boolean): Tensor;
42
+ prod(dims?: number[] | number, keepDims?: boolean): Tensor;
43
+ mean(dims?: number[] | number, keepDims?: boolean): Tensor;
35
44
  add(other: TensorValue | Tensor): Tensor;
36
45
  sub(other: TensorValue | Tensor): Tensor;
37
46
  mul(other: TensorValue | Tensor): Tensor;
package/dist/core.js CHANGED
@@ -93,7 +93,7 @@ class Tensor {
93
93
  }
94
94
  return newShape;
95
95
  }
96
- // Convert flat index to array of coordinates
96
+ // Utility to convert flat index to array of coordinates
97
97
  static indexToCoords(index, shape, strides) {
98
98
  const coords = new Array(shape.length);
99
99
  let remaining = index;
@@ -105,17 +105,33 @@ class Tensor {
105
105
  }
106
106
  return coords;
107
107
  }
108
- // Convert array of coordinates to *unbroadcasted* flat index
109
- static coordsToIndex(coords, shape, strides) {
108
+ // Utility to convert array of coordinates to *unbroadcasted* flat index
109
+ static coordsToUnbroadcastedIndex(coords, shape, strides) {
110
110
  let index = 0;
111
111
  for (let i = 0; i < coords.length; i++) {
112
- const coord = coords[i];
113
112
  // Handle broadcasting
114
- const actualCoord = shape[i] === 1 ? 0 : coord;
113
+ const actualCoord = shape[i] === 1 ? 0 : coords[i];
115
114
  index += actualCoord * strides[i];
116
115
  }
117
116
  return index;
118
117
  }
118
+ // Utility to convert array of coordinates to flat index
119
+ static coordsToIndex(coords, strides) {
120
+ let index = 0;
121
+ for (let i = 0; i < coords.length; i++) {
122
+ index += coords[i] * strides[i];
123
+ }
124
+ return index;
125
+ }
126
+ // Utility to convert shape into 1D value array size
127
+ static shapeToSize(shape) {
128
+ let prod = 1;
129
+ for (let i = 0; i < shape.length; i++) {
130
+ prod *= shape[i];
131
+ }
132
+ return prod;
133
+ }
134
+ ;
119
135
  // Utility for binary (two operators involved) element-wise ops
120
136
  static elementWiseAB(tA, tB, op) {
121
137
  if (typeof tA.value === "number" && typeof tB.value === "number") {
@@ -132,15 +148,15 @@ class Tensor {
132
148
  const outputShape = Tensor.broadcastShapes(paddedAShape, paddedBShape);
133
149
  // Get other output info
134
150
  const outputStrides = Tensor.getStrides(outputShape);
135
- const outputSize = outputShape.reduce((a, b) => a * b, 1);
151
+ const outputSize = Tensor.shapeToSize(outputShape);
136
152
  const outputValue = new Array(outputSize);
137
153
  for (let i = 0; i < outputSize; i++) {
138
154
  // Get coordinates from 1D index
139
155
  const coordsOutput = Tensor.indexToCoords(i, outputShape, outputStrides);
140
156
  // Convert the coordinates to 1D index of flattened A with respect to A's shape
141
- const indexA = Tensor.coordsToIndex(coordsOutput, paddedAShape, paddedAStrides);
157
+ const indexA = Tensor.coordsToUnbroadcastedIndex(coordsOutput, paddedAShape, paddedAStrides);
142
158
  // Convert the coordinates to 1D index of flattened B with respect to B's shape
143
- const indexB = Tensor.coordsToIndex(coordsOutput, paddedBShape, paddedBStrides);
159
+ const indexB = Tensor.coordsToUnbroadcastedIndex(coordsOutput, paddedBShape, paddedBStrides);
144
160
  // Calculate with op
145
161
  outputValue[i] = op(tA.value[indexA], tB.value[indexB]);
146
162
  }
@@ -321,29 +337,33 @@ class Tensor {
321
337
  if (typeof dims === "undefined") {
322
338
  dims = Array.from({ length: this.shape.length }, (_, index) => index);
323
339
  }
340
+ // Dims that are reduced now have size-1
324
341
  const outputShape = this.shape.map((dim, i) => dims.includes(i) ? 1 : dim);
325
342
  const outputStrides = Tensor.getStrides(outputShape);
326
- const outputSize = outputShape.reduce((a, b) => a * b, 1);
343
+ const outputSize = Tensor.shapeToSize(outputShape);
327
344
  const outputValue = new Array(outputSize).fill(0);
328
- const originalSize = this.shape.reduce((a, b) => a * b, 1);
345
+ const originalSize = Tensor.shapeToSize(this.shape);
346
+ // Gradient data
329
347
  let gradShape, gradStrides, gradValue = [];
348
+ // Allocate gradient data only when needed
330
349
  if (this.requiresGrad) {
331
350
  gradShape = this.shape;
332
351
  gradStrides = this.strides;
333
352
  gradValue = new Array(originalSize).fill(0);
334
353
  }
354
+ // Calculate new value after sum
335
355
  for (let index = 0; index < originalSize; index++) {
336
356
  const coords = Tensor.indexToCoords(index, this.shape, this.strides);
337
357
  // Force 0 on reduced axes to collapse into size-1 dims
338
358
  const outCoords = coords.map((val, i) => dims.includes(i) ? 0 : val);
339
359
  // Convert output coordinates to flat index
340
- const outFlatIndex = outCoords.reduce((acc, val, i) => acc + val * outputStrides[i], 0);
341
- // Accumulate
342
- const realFlatIndex = coords.reduce((acc, val, i) => acc + val * this.strides[i], 0);
360
+ const outFlatIndex = Tensor.coordsToIndex(outCoords, outputStrides);
361
+ // Accumulate, outFlatIndex should match multiple realFlatIndexes
362
+ const realFlatIndex = Tensor.coordsToIndex(coords, this.strides);
343
363
  outputValue[outFlatIndex] += this.value[realFlatIndex];
344
- // Mark for gradient
364
+ // Mark for gradient if needed
345
365
  if (this.requiresGrad) {
346
- (gradValue)[realFlatIndex] = 1;
366
+ gradValue[realFlatIndex] = 1;
347
367
  }
348
368
  }
349
369
  const out = new Tensor(outputValue, {
@@ -361,6 +381,136 @@ class Tensor {
361
381
  }
362
382
  return keepDims ? out : out.squeeze(dims);
363
383
  }
384
+ // Tensor product reduction
385
+ prod(dims, keepDims = false) {
386
+ if (typeof this.value === "number")
387
+ return new Tensor(this.value);
388
+ if (typeof dims === "number") {
389
+ dims = [dims];
390
+ }
391
+ if (typeof dims === "undefined") {
392
+ dims = Array.from({ length: this.shape.length }, (_, index) => index);
393
+ }
394
+ // Dims that are reduced now have size-1
395
+ const outputShape = this.shape.map((dim, i) => dims.includes(i) ? 1 : dim);
396
+ const outputStrides = Tensor.getStrides(outputShape);
397
+ const outputSize = Tensor.shapeToSize(outputShape);
398
+ const outputValue = new Array(outputSize).fill(1);
399
+ const originalSize = Tensor.shapeToSize(this.shape);
400
+ // Gradient data
401
+ let gradShape, gradStrides, gradValue = [];
402
+ // Allocate gradient data only when needed
403
+ if (this.requiresGrad) {
404
+ gradShape = this.shape;
405
+ gradStrides = this.strides;
406
+ gradValue = new Array(originalSize).fill(0);
407
+ }
408
+ // Calculate new value after multiplying
409
+ for (let index = 0; index < originalSize; index++) {
410
+ const coords = Tensor.indexToCoords(index, this.shape, this.strides);
411
+ // Force 0 on reduced axes to collapse into size-1 dims
412
+ const outCoords = coords.map((val, i) => dims.includes(i) ? 0 : val);
413
+ // Convert output coordinates to flat index
414
+ const outFlatIndex = Tensor.coordsToIndex(outCoords, outputStrides);
415
+ // Accumulate, outFlatIndex should match multiple realFlatIndexes
416
+ const realFlatIndex = Tensor.coordsToIndex(coords, this.strides);
417
+ outputValue[outFlatIndex] *= this.value[realFlatIndex];
418
+ }
419
+ const out = new Tensor(outputValue, {
420
+ shape: outputShape,
421
+ strides: outputStrides
422
+ });
423
+ // Set up gradient if needed
424
+ if (this.requiresGrad) {
425
+ // Grad is the product of other elements of the same axis, which is product of all els divided by the current value
426
+ for (let index = 0; index < originalSize; index++) {
427
+ const coords = Tensor.indexToCoords(index, this.shape, this.strides);
428
+ // Force 0 on reduced axes to collapse into size-1 dims
429
+ const outCoords = coords.map((val, i) => dims.includes(i) ? 0 : val);
430
+ // Convert output coordinates to flat index
431
+ const outFlatIndex = Tensor.coordsToIndex(outCoords, outputStrides);
432
+ // Accumulate, outFlatIndex should match multiple realFlatIndexes
433
+ const realFlatIndex = Tensor.coordsToIndex(coords, this.strides);
434
+ // Calculate gradient at position
435
+ gradValue[realFlatIndex] = outputValue[outFlatIndex] / this.value[realFlatIndex];
436
+ }
437
+ out.requiresGrad = true;
438
+ out.children.push(this);
439
+ out.gradFn = () => {
440
+ const localGrad = new Tensor(gradValue, { shape: gradShape, strides: gradStrides });
441
+ Tensor.addGrad(this, out.grad.withGrad(false).mul(localGrad));
442
+ };
443
+ }
444
+ return keepDims ? out : out.squeeze(dims);
445
+ }
446
+ // Tensor mean reduction
447
+ mean(dims, keepDims = false) {
448
+ if (typeof this.value === "number")
449
+ return new Tensor(this.value);
450
+ if (typeof dims === "number") {
451
+ dims = [dims];
452
+ }
453
+ if (typeof dims === "undefined") {
454
+ dims = Array.from({ length: this.shape.length }, (_, index) => index);
455
+ }
456
+ // Dims that are reduced now have size-1
457
+ const outputShape = this.shape.map((dim, i) => dims.includes(i) ? 1 : dim);
458
+ const outputStrides = Tensor.getStrides(outputShape);
459
+ const outputSize = Tensor.shapeToSize(outputShape);
460
+ const outputValue = new Array(outputSize).fill(0);
461
+ const outputFeeders = new Array(outputSize).fill(0);
462
+ const originalSize = Tensor.shapeToSize(this.shape);
463
+ // Gradient data
464
+ let gradShape, gradStrides, gradValue = [];
465
+ // Allocate gradient data only when needed
466
+ if (this.requiresGrad) {
467
+ gradShape = this.shape;
468
+ gradStrides = this.strides;
469
+ gradValue = new Array(originalSize).fill(0);
470
+ }
471
+ // Calculate sums and how many elements contribute to specific positions
472
+ for (let index = 0; index < originalSize; index++) {
473
+ const coords = Tensor.indexToCoords(index, this.shape, this.strides);
474
+ // Force 0 on reduced axes to collapse into size-1 dims
475
+ const outCoords = coords.map((val, i) => dims.includes(i) ? 0 : val);
476
+ // Convert output coordinates to flat index
477
+ const outFlatIndex = Tensor.coordsToIndex(outCoords, outputStrides);
478
+ // Accumulate, outFlatIndex should match multiple realFlatIndexes
479
+ const realFlatIndex = Tensor.coordsToIndex(coords, this.strides);
480
+ outputValue[outFlatIndex] += this.value[realFlatIndex];
481
+ outputFeeders[outFlatIndex]++;
482
+ }
483
+ // Calculate mean by dividing sum by the number of contributors to the position
484
+ for (let index = 0; index < outputSize; index++) {
485
+ outputValue[index] /= outputFeeders[index];
486
+ }
487
+ const out = new Tensor(outputValue, {
488
+ shape: outputShape,
489
+ strides: outputStrides
490
+ });
491
+ // Set up gradient if needed
492
+ if (this.requiresGrad) {
493
+ // Calculate grad by assiging 1 divide by the number of contributors to the position
494
+ for (let index = 0; index < originalSize; index++) {
495
+ const coords = Tensor.indexToCoords(index, this.shape, this.strides);
496
+ // Force 0 on reduced axes to collapse into size-1 dims
497
+ const outCoords = coords.map((val, i) => dims.includes(i) ? 0 : val);
498
+ // Convert output coordinates to flat index
499
+ const outFlatIndex = Tensor.coordsToIndex(outCoords, outputStrides);
500
+ // Accumulate, outFlatIndex should match multiple realFlatIndexes
501
+ const realFlatIndex = Tensor.coordsToIndex(coords, this.strides);
502
+ // Mean = 1/n * (el1 + el2 + ... + eln) so grad = 1/n
503
+ gradValue[realFlatIndex] = 1 / outputFeeders[outFlatIndex];
504
+ }
505
+ out.requiresGrad = true;
506
+ out.children.push(this);
507
+ out.gradFn = () => {
508
+ const localGrad = new Tensor(gradValue, { shape: gradShape, strides: gradStrides });
509
+ Tensor.addGrad(this, out.grad.withGrad(false).mul(localGrad));
510
+ };
511
+ }
512
+ return keepDims ? out : out.squeeze(dims);
513
+ }
364
514
  // Tensor element-wise addition
365
515
  add(other) {
366
516
  return this.elementWiseABDAG(other, (a, b) => a + b, (self, other, outGrad) => outGrad, (self, other, outGrad) => outGrad);
@@ -578,6 +728,7 @@ class Tensor {
578
728
  if (this.shape.length !== 1 || other.shape.length !== 1) {
579
729
  throw new Error("Inputs are not 1D tensors");
580
730
  }
731
+ // Simple vector dot product
581
732
  const vectLen = this.shape[0];
582
733
  const vectA = this.value;
583
734
  const vectB = other.value;
@@ -615,6 +766,7 @@ class Tensor {
615
766
  if (this.shape.length !== 2 || other.shape.length !== 2) {
616
767
  throw new Error("Inputs are not matrices");
617
768
  }
769
+ // Simple matrix multiplication
618
770
  const matA = this.value;
619
771
  const matB = other.value;
620
772
  const matAStrides = this.strides;
@@ -627,11 +779,12 @@ class Tensor {
627
779
  throw new Error("Invalid matrices shape for multiplication");
628
780
  const matCShape = [matARows, matBCols];
629
781
  const matCStrides = Tensor.getStrides(matCShape);
630
- const matCSize = matCShape.reduce((a, b) => a * b, 1);
782
+ const matCSize = Tensor.shapeToSize(matCShape);
631
783
  const matC = new Array(matCSize).fill(0);
632
784
  for (let i = 0; i < matARows; i++) {
633
785
  for (let j = 0; j < matBCols; j++) {
634
786
  for (let k = 0; k < matACols; k++) {
787
+ // Tensor values are 1D arrays so we have to get real index using strides
635
788
  matC[i * matCStrides[0] + j * matCStrides[1]] +=
636
789
  matA[i * matAStrides[0] + k * matAStrides[1]] *
637
790
  matB[k * matBStrides[0] + j * matBStrides[1]];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "catniff",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "A cute autograd engine for Javascript",
5
5
  "main": "index.js",
6
6
  "scripts": {