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 +5 -7
- package/dist/core.d.ts +1 -0
- package/dist/core.js +137 -4
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Catniff
|
|
2
2
|
|
|
3
|
-
Catniff is
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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 (
|
|
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 &&
|
|
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
|
-
|
|
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.
|
|
4
|
-
"description": "A
|
|
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
|
],
|