catniff 0.2.13 → 0.2.15

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,6 +1,6 @@
1
1
  # Catniff
2
2
 
3
- Catniff is an experimental tensor ops library and autograd engine made to be Torch-like (its name is a play on "catnip" and "differentiation"). This project is heavily under development currently, so keep in mind that APIs can be completely unstable and backwards-incompatible.
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
 
5
5
  ## Setup
6
6
 
@@ -80,14 +80,12 @@ All available APIs are in [`./src/core.ts`](./src/core.ts) if you want to dig de
80
80
 
81
81
  ## Todos
82
82
 
83
- I'm mostly just learning and playing with this currently, so there are no concrete plans yet, but here is what I currently have in mind:
84
-
85
- * Fix whatever is the problem right now (there are a lot of problems right now lol).
86
- * Add more tensor ops.
87
- * Proper documentation.
83
+ * Bug fixes.
84
+ * More tensor ops.
85
+ * More detailed documentation.
88
86
  * GPU acceleration.
89
87
  * Some general neural net APIs.
90
- * Refactor code.
88
+ * Code refactoring.
91
89
  * Proper tests.
92
90
  * Option to load more backends.
93
91
 
package/dist/core.d.ts CHANGED
@@ -137,6 +137,7 @@ export declare class Tensor {
137
137
  t(): Tensor;
138
138
  dot(other: TensorValue | Tensor): Tensor;
139
139
  mm(other: TensorValue | Tensor): Tensor;
140
+ bmm(other: TensorValue | Tensor): Tensor;
140
141
  mv(other: TensorValue | Tensor): Tensor;
141
142
  matmul(other: TensorValue | Tensor): Tensor;
142
143
  static full(shape: number[], num: number, options?: TensorOptions): Tensor;
package/dist/core.js CHANGED
@@ -1049,6 +1049,64 @@ class Tensor {
1049
1049
  }
1050
1050
  return out;
1051
1051
  }
1052
+ // Batched 3D tensor matmul
1053
+ bmm(other) {
1054
+ other = Tensor.forceTensor(other);
1055
+ // Verify 3D shape
1056
+ if (this.shape.length !== 3 || other.shape.length !== 3 || this.shape[0] !== other.shape[0]) {
1057
+ throw new Error("Inputs are not 3D tensors with the same first dim size");
1058
+ }
1059
+ // Simple matrix multiplication
1060
+ const batchA = this.value;
1061
+ const batchB = other.value;
1062
+ const batchAStrides = this.strides;
1063
+ const batchBStrides = other.strides;
1064
+ const batchSize = this.shape[0];
1065
+ const batchARows = this.shape[1];
1066
+ const batchACols = this.shape[2];
1067
+ const batchBRows = other.shape[1];
1068
+ const batchBCols = other.shape[2];
1069
+ if (batchACols !== batchBRows)
1070
+ throw new Error("Invalid matrices shape for multiplication");
1071
+ const batchCShape = [batchSize, batchARows, batchBCols];
1072
+ const batchCStrides = Tensor.getStrides(batchCShape);
1073
+ const batchCSize = Tensor.shapeToSize(batchCShape);
1074
+ const batchC = new Array(batchCSize).fill(0);
1075
+ for (let q = 0; q < batchSize; q++) {
1076
+ for (let i = 0; i < batchARows; i++) {
1077
+ for (let j = 0; j < batchBCols; j++) {
1078
+ for (let k = 0; k < batchACols; k++) {
1079
+ // Tensor values are 1D arrays so we have to get real index using strides
1080
+ batchC[q * batchCStrides[0] + i * batchCStrides[1] + j * batchCStrides[2]] +=
1081
+ batchA[q * batchAStrides[0] + i * batchAStrides[1] + k * batchAStrides[2]] *
1082
+ batchB[q * batchBStrides[0] + k * batchBStrides[1] + j * batchBStrides[2]];
1083
+ }
1084
+ }
1085
+ }
1086
+ }
1087
+ const out = new Tensor(batchC, { shape: batchCShape, strides: batchCStrides });
1088
+ if (this.requiresGrad) {
1089
+ out.requiresGrad = true;
1090
+ out.children.push(this);
1091
+ }
1092
+ if (other.requiresGrad) {
1093
+ out.requiresGrad = true;
1094
+ out.children.push(other);
1095
+ }
1096
+ if (out.requiresGrad) {
1097
+ out.gradFn = () => {
1098
+ // Disable gradient collecting of gradients themselves
1099
+ const outGrad = out.grad.withGrad(false);
1100
+ const selfNoGrad = this.withGrad(false);
1101
+ const otherNoGrad = other.withGrad(false);
1102
+ if (this.requiresGrad)
1103
+ Tensor.addGrad(this, outGrad.bmm(otherNoGrad.transpose(1, 2)));
1104
+ if (other.requiresGrad)
1105
+ Tensor.addGrad(other, selfNoGrad.transpose(1, 2).bmm(outGrad));
1106
+ };
1107
+ }
1108
+ return out;
1109
+ }
1052
1110
  // Convert right-side 1D tensor to a vector (nx1 tensor) to do matmul
1053
1111
  mv(other) {
1054
1112
  other = Tensor.forceTensor(other);
@@ -1086,19 +1144,94 @@ class Tensor {
1086
1144
  // General matrix multiplication with different shapes
1087
1145
  matmul(other) {
1088
1146
  other = Tensor.forceTensor(other);
1089
- if (this.shape.length === 1 && other.shape.length === 1) {
1147
+ const isThis1D = this.shape.length === 1;
1148
+ const isOther1D = other.shape.length === 1;
1149
+ if (isThis1D && isOther1D) {
1090
1150
  return this.dot(other);
1091
1151
  }
1092
- else if (this.shape.length === 1 && other.shape.length === 2) {
1152
+ else if (isThis1D && other.shape.length === 2) {
1093
1153
  return this.unsqueeze(0).mm(other).squeeze(0);
1094
1154
  }
1095
- else if (this.shape.length === 2 && other.shape.length === 1) {
1155
+ else if (this.shape.length === 2 && isOther1D) {
1096
1156
  return this.mv(other);
1097
1157
  }
1098
1158
  else if (this.shape.length === 2 && other.shape.length === 2) {
1099
1159
  return this.mm(other);
1100
1160
  }
1101
- // Too lazy for batched matmul
1161
+ else if ((isThis1D && other.shape.length > 2) ||
1162
+ (isOther1D && this.shape.length > 2) ||
1163
+ (other.shape.length > 2 && this.shape.length > 2)) {
1164
+ // Append/prepend dims if needed
1165
+ const self = isThis1D ? this.unsqueeze(0) : this;
1166
+ other = isOther1D ? other.unsqueeze(1) : other;
1167
+ // Padding
1168
+ const [selfStrides, otherStrides, selfShape, otherShape] = Tensor.padShape(self.strides, other.strides, self.shape, other.shape);
1169
+ const lastDim = selfShape.length - 1;
1170
+ // Prepare data for broadcasting
1171
+ const batchA = self.value;
1172
+ const batchB = other.value;
1173
+ const batchARows = selfShape[lastDim - 1];
1174
+ const batchACols = selfShape[lastDim];
1175
+ const batchBRows = otherShape[lastDim - 1];
1176
+ const batchBCols = otherShape[lastDim];
1177
+ // Verify if can do matmul
1178
+ if (batchACols !== batchBRows)
1179
+ throw new Error("Invalid matrices shape for multiplication");
1180
+ // Prepare shape, strides, size info, but more importantly the offset-related data to loop through the outer, non-matrix dims
1181
+ // Self and other's offset data
1182
+ const selfOffsetShape = selfShape.slice(0, -2);
1183
+ const otherOffsetShape = otherShape.slice(0, -2);
1184
+ const selfOffsetStrides = selfStrides.slice(0, -2);
1185
+ const otherOffsetStrides = otherStrides.slice(0, -2);
1186
+ // The output's offset data
1187
+ const offsetShape = Tensor.broadcastShapes(selfOffsetShape, otherOffsetShape);
1188
+ const offsetSize = Tensor.shapeToSize(offsetShape);
1189
+ const offsetStrides = Tensor.getStrides(offsetShape);
1190
+ // Output shape, strides, size, value
1191
+ const outputShape = [...offsetShape, batchARows, batchBCols];
1192
+ const outputStrides = Tensor.getStrides(outputShape);
1193
+ const outputSize = Tensor.shapeToSize(outputShape);
1194
+ const outputValue = new Array(outputSize).fill(0);
1195
+ // Loop through outer dims and do matmul on two outer-most dims
1196
+ for (let index = 0; index < offsetSize; index++) {
1197
+ const coords = Tensor.indexToCoords(index, offsetStrides);
1198
+ const offset = Tensor.coordsToIndex(coords, outputStrides.slice(0, -2));
1199
+ const selfOffset = Tensor.coordsToUnbroadcastedIndex(coords, selfOffsetShape, selfOffsetStrides);
1200
+ const otherOffset = Tensor.coordsToUnbroadcastedIndex(coords, otherOffsetShape, otherOffsetStrides);
1201
+ for (let i = 0; i < batchARows; i++) {
1202
+ for (let j = 0; j < batchBCols; j++) {
1203
+ for (let k = 0; k < batchACols; k++) {
1204
+ const outputIdx = offset + i * outputStrides[lastDim - 1] + j * outputStrides[lastDim];
1205
+ const selfIdx = selfOffset + i * selfStrides[lastDim - 1] + k * selfStrides[lastDim];
1206
+ const otherIdx = otherOffset + k * otherStrides[lastDim - 1] + j * otherStrides[lastDim];
1207
+ outputValue[outputIdx] += batchA[selfIdx] * batchB[otherIdx];
1208
+ }
1209
+ }
1210
+ }
1211
+ }
1212
+ const out = new Tensor(outputValue, { shape: outputShape, strides: outputStrides });
1213
+ if (this.requiresGrad) {
1214
+ out.requiresGrad = true;
1215
+ out.children.push(this);
1216
+ }
1217
+ if (other.requiresGrad) {
1218
+ out.requiresGrad = true;
1219
+ out.children.push(other);
1220
+ }
1221
+ if (out.requiresGrad) {
1222
+ out.gradFn = () => {
1223
+ other = other;
1224
+ const outGrad = out.grad.withGrad(false);
1225
+ const selfNoGrad = self.withGrad(false);
1226
+ const otherNoGrad = other.withGrad(false);
1227
+ if (this.requiresGrad)
1228
+ Tensor.addGrad(this, outGrad.matmul(otherNoGrad.transpose(lastDim - 1, lastDim)));
1229
+ if (other.requiresGrad)
1230
+ Tensor.addGrad(other, selfNoGrad.transpose(lastDim - 1, lastDim).matmul(outGrad));
1231
+ };
1232
+ }
1233
+ return out;
1234
+ }
1102
1235
  throw new Error(`Shapes [${this.shape}] and [${other.shape}] are not supported`);
1103
1236
  }
1104
1237
  // Utility to create a new tensor filled with a number
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "catniff",
3
- "version": "0.2.13",
4
- "description": "A cute autograd engine for Javascript",
3
+ "version": "0.2.15",
4
+ "description": "A small Torch-like deep learning framework for Javascript with tensor and autograd support",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -11,7 +11,6 @@
11
11
  "url": "git+https://github.com/nguyenphuminh/catniff.git"
12
12
  },
13
13
  "keywords": [
14
- "cats",
15
14
  "catniff",
16
15
  "autograd",
17
16
  "autodiff",
@@ -27,6 +26,7 @@
27
26
  "machine-learning",
28
27
  "deep-learning",
29
28
  "micrograd",
29
+ "tinygrad",
30
30
  "torch",
31
31
  "pytorch"
32
32
  ],