FredZhang7's picture
add custom tiny tfjs module for node.js v18+
b38c8eb
raw
history blame
103 kB
'use strict';
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function () { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function () { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
var fs = require("fs");
let globalNameSpace, trackerFn = null, opHandler = null;
var loadedSavedModelPathMap = new Map(), nextTFSavedModelId = 0;
function loadSavedModel(path, tags, signature) {
if (tags === void 0) { tags = ['serve']; }
if (signature === void 0) { signature = 'serving_default'; }
return __awaiter(this, void 0, void 0, function () {
var backend, savedModelInfo, signatureDefEntry, sessionId, _i, _a, id_1, modelInfo, tagsString, id, savedModel;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
assert(ENGINE.backendName === 'tensorflow', function () { return "Expect the current backend to be \"tensorflow\", but got \"" + ENGINE.backendName + "\""; });
backend = ENGINE.findBackend('tensorflow')
return [4 /*yield*/, getMetaGraphsFromSavedModel(path)];
case 1:
savedModelInfo = _b.sent();
signatureDefEntry = getSignatureDefEntryFromMetaGraphInfo(savedModelInfo, tags, signature);
for (_i = 0, _a = Array.from(loadedSavedModelPathMap.keys()); _i < _a.length; _i++) {
id_1 = _a[_i];
modelInfo = loadedSavedModelPathMap.get(id_1);
if (modelInfo.path === path &&
stringArraysHaveSameElements(modelInfo.tags, tags)) {
sessionId = modelInfo.sessionId;
}
}
if (sessionId == null) {
tagsString = tags.join(',');
sessionId = backend.loadSavedModelMetaGraph(path, tagsString);
}
id = nextTFSavedModelId++;
savedModel = new TFSavedModel(sessionId, id, signatureDefEntry, backend);
loadedSavedModelPathMap.set(id, { path: path, tags: tags, sessionId: sessionId });
return [2 /*return*/, savedModel];
}
});
});
}
function assert(expr, msg) {
if (!expr) {
throw new Error(typeof msg === 'string' ? msg : msg());
}
}
var messages = require('./api_pb');
function getTFDType(dataType) {
var binding = nodeBackend().binding;
switch (dataType) {
case 'float32':
return binding.TF_FLOAT;
case 'int32':
return binding.TF_INT32;
case 'bool':
return binding.TF_BOOL;
case 'complex64':
return binding.TF_COMPLEX64;
case 'string':
return binding.TF_STRING;
case 'int64':
return binding.TF_INT64;
default:
var errorMessage = "Unknown dtype: " + dataType;
throw new Error(errorMessage);
}
}
function createTensorsTypeOpAttr(attrName, tensorsOrDtype) {
if (util_1.isNullOrUndefined(tensorsOrDtype)) {
throw new Error('Invalid input tensors value.');
}
return {
name: attrName,
type: nodeBackend().binding.TF_ATTR_TYPE,
value: (tensorsOrDtype instanceof Tensor || Array.isArray(tensorsOrDtype)) ?
getTFDTypeForInputs(tensorsOrDtype) :
getTFDType(tensorsOrDtype)
};
}
function getTFDTypeForInputs(tensors) {
if (util_1.isNullOrUndefined(tensors)) {
throw new Error('Invalid input tensors value.');
}
if (util_1.isArray(tensors)) {
for (var i = 0; i < tensors.length; i++) {
return getTFDType(tensors[i].dtype);
}
return -1;
}
else {
return getTFDType(tensors.dtype);
}
}
function getOrMakeEngine() {
const ns = getGlobalNamespace();
if (ns._tfengine == null) {
const environment = new Environment(ns);
ns._tfengine = new Engine(environment);
}
setEnvironmentGlobal(ns._tfengine.ENV);
setTensorTracker(() => ns._tfengine);
return ns._tfengine;
}
const TENSORFLOWJS_FLAGS_PREFIX = 'tfjsflags';
function isPromise(object) {
return object && object.then && typeof object.then === 'function';
}
function getQueryParams(queryString) {
const params = {};
queryString.replace(/[?&]([^=?&]+)(?:=([^&]*))?/g, (s, ...t) => {
decodeParam(params, t[0], t[1]);
return t.join('=');
});
return params;
}
function decodeParam(params, name, value) {
params[decodeURIComponent(name)] = decodeURIComponent(value || '');
}
function parseValue(flagName, value) {
value = value.toLowerCase();
if (value === 'true' || value === 'false') {
return value === 'true';
}
else if (`${+value}` === value) {
return +value;
}
throw new Error(`Could not parse value flag value ${value} for flag ${flagName}.`);
}
function env() {
return ENV;
}
let ENV = null;
function setEnvironmentGlobal(environment) {
ENV = environment;
}
const kernelRegistry = getGlobal('kernelRegistry', () => new Map());
function getKernelsForBackend(backendName) {
const it = kernelRegistry.entries();
const result = [];
while (true) {
const { done, value } = it.next();
if (done) {
break;
}
const [key, config] = value;
const [backend,] = key.split('_');
if (backend === backendName) {
result.push(config);
}
}
return result;
}
function getGlobalNamespace() {
if (globalNameSpace == null) {
let ns;
if (typeof (window) !== 'undefined') {
ns = window;
}
else if (typeof (global) !== 'undefined') {
ns = global;
}
else if (typeof (process) !== 'undefined') {
ns = process;
}
else if (typeof (self) !== 'undefined') {
ns = self;
}
else {
throw new Error('Could not find a global object');
}
globalNameSpace = ns;
}
return globalNameSpace;
}
function getGlobalMap() {
const ns = getGlobalNamespace();
if (ns._tfGlobals == null) {
ns._tfGlobals = new Map();
}
return ns._tfGlobals;
}
function getGlobal(key, init) {
const globalMap = getGlobalMap();
if (globalMap.has(key)) {
return globalMap.get(key);
}
else {
const singleton = init();
globalMap.set(key, singleton);
return globalMap.get(key);
}
}
function setTensorTracker(fn) {
trackerFn = fn;
}
function sizeFromShape(shape) {
if (shape.length === 0) {
return 1;
}
let size = shape[0];
for (let i = 1; i < shape.length; i++) {
size *= shape[i];
}
return size;
}
function computeStrides(shape) {
const rank = shape.length;
if (rank < 2) {
return [];
}
// Last dimension has implicit stride of 1, thus having D-1 (instead of D)
// strides.
const strides = new Array(rank - 1);
strides[rank - 2] = shape[rank - 1];
for (let i = rank - 3; i >= 0; --i) {
strides[i] = strides[i + 1] * shape[i + 1];
}
return strides;
}
function assertNonNegativeIntegerDimensions(shape) {
shape.forEach(dimSize => {
assert(Number.isInteger(dimSize) && dimSize >= 0, () => `Tensor must have a shape comprised of positive integers but got shape [${shape}].`);
});
}
function flatten(arr, result = [], skipTypedArray = false) {
if (result == null) {
result = [];
}
if (Array.isArray(arr) || isTypedArray(arr) && !skipTypedArray) {
for (let i = 0; i < arr.length; ++i) {
flatten(arr[i], result, skipTypedArray);
}
}
else {
result.push(arr);
}
return result;
}
function isTypedArray(a) {
return a instanceof Float32Array || a instanceof Int32Array || a instanceof Uint8Array || a instanceof Uint8ClampedArray;
}
function noConversionNeeded(a, dtype) {
return (a instanceof Float32Array && dtype === 'float32') || (a instanceof Int32Array && dtype === 'int32') || (a instanceof Uint8Array && dtype === 'bool');
}
function checkConversionForErrors(vals, dtype) {
for (let i = 0; i < vals.length; i++) {
const num = vals[i];
if (isNaN(num) || !isFinite(num)) {
throw Error(`A tensor of type ${dtype} being uploaded contains ${num}.`);
}
}
}
function toTypedArray(a, dtype) {
if (dtype === 'string') {
throw new Error('Cannot convert a string[] to a TypedArray');
}
if (Array.isArray(a)) {
a = flatten(a);
}
// if (env().getBool('DEBUG')) {
// checkConversionForErrors(a, dtype);
// }
if (noConversionNeeded(a, dtype)) {
return a;
}
if (dtype == null || dtype === 'float32' || dtype === 'complex64') {
return new Float32Array(a);
}
else if (dtype === 'int32') {
return new Int32Array(a);
}
else if (dtype === 'bool') {
const bool = new Uint8Array(a.length);
for (let i = 0; i < bool.length; ++i) {
if (Math.round(a[i]) !== 0) {
bool[i] = 1;
}
}
return bool;
}
else {
throw new Error(`Unknown data type ${dtype}`);
}
}
function isString(value) {
return typeof value === 'string' || value instanceof String;
}
function isBoolean(value) {
return typeof value === 'boolean';
}
function isNumber(value) {
return typeof value === 'number';
}
function isFunction(f) {
return !!(f && f.constructor && f.call && f.apply);
}
function inferDtype(values) {
if (Array.isArray(values)) {
return inferDtype(values[0]);
}
if (values instanceof Float32Array) {
return 'float32';
}
else if (values instanceof Int32Array || values instanceof Uint8Array || values instanceof Uint8ClampedArray) {
return 'int32';
}
else if (isNumber(values)) {
return 'float32';
}
else if (isString(values)) {
return 'string';
}
else if (isBoolean(values)) {
return 'bool';
}
return 'float32';
}
function scalar(value, dtype) {
if (((isTypedArray(value) && dtype !== 'string') || Array.isArray(value)) &&
dtype !== 'complex64') {
throw new Error('Error creating a new Scalar: value must be a primitive ' +
'(number|boolean|string)');
}
if (dtype === 'string' && isTypedArray(value) &&
!(value instanceof Uint8Array)) {
throw new Error('When making a scalar from encoded string, ' +
'the value must be `Uint8Array`.');
}
const shape = [];
const inferredShape = [];
return makeTensor(value, shape, inferredShape, dtype);
}
function tensor(values, shape, dtype) {
const inferredShape = inferShape(values, dtype);
return makeTensor(values, shape, inferredShape, dtype);
}
function inferShape(val, dtype) {
let firstElem = val;
if (isTypedArray(val)) {
return dtype === 'string' ? [] : [val.length];
}
if (!Array.isArray(val)) {
return [];
}
const shape = [];
while (Array.isArray(firstElem) ||
isTypedArray(firstElem) && dtype !== 'string') {
shape.push(firstElem.length);
firstElem = firstElem[0];
}
// if (Array.isArray(val) &&
// env().getBool('TENSORLIKE_CHECK_SHAPE_CONSISTENCY')) {
// deepAssertShapeConsistency(val, shape, []);
// }
return shape;
}
function deepAssertShapeConsistency(val, shape, indices) {
indices = indices || [];
if (!(Array.isArray(val)) && !isTypedArray(val)) {
assert(shape.length === 0, () => `Element arr[${indices.join('][')}] is a primitive, ` +
`but should be an array/TypedArray of ${shape[0]} elements`);
return;
}
assert(shape.length > 0, () => `Element arr[${indices.join('][')}] should be a primitive, ` +
`but is an array of ${val.length} elements`);
assert(val.length === shape[0], () => `Element arr[${indices.join('][')}] should have ${shape[0]} ` +
`elements, but has ${val.length} elements`);
const subShape = shape.slice(1);
for (let i = 0; i < val.length; ++i) {
deepAssertShapeConsistency(val[i], subShape, indices.concat(i));
}
}
function makeTensor(values, shape, inferredShape, dtype) {
if (dtype == null) {
dtype = inferDtype(values);
}
if (dtype === 'complex64') {
throw new Error(`Cannot construct a complex64 tensor directly. Please use tf.complex(real, imag).`);
}
if (!isTypedArray(values) && !Array.isArray(values) &&
typeof values !== 'number' && typeof values !== 'boolean' &&
typeof values !== 'string') {
throw new Error('values passed to tensor(values) must be a number/boolean/string or ' +
'an array of numbers/booleans/strings, or a TypedArray');
}
if (shape != null) {
assertNonNegativeIntegerDimensions(shape);
const providedSize = sizeFromShape(shape);
const inferredSize = sizeFromShape(inferredShape);
assert(providedSize === inferredSize, () => `Based on the provided shape, [${shape}], the tensor should have ` +
`${providedSize} values but has ${inferredSize}`);
for (let i = 0; i < inferredShape.length; ++i) {
const inferred = inferredShape[i];
const flatDimsDontMatch = i === inferredShape.length - 1 ?
inferred !== sizeFromShape(shape.slice(i)) :
true;
assert(inferredShape[i] === shape[i] || !flatDimsDontMatch, () => `Error creating a new Tensor. Inferred shape ` +
`(${inferredShape}) does not match the provided shape (${shape}). `);
}
}
if (!isTypedArray(values) && !Array.isArray(values)) {
values = [values];
}
shape = shape || inferredShape;
values = dtype !== 'string' ?
toTypedArray(values, dtype) :
flatten(values, [], true);
return ENGINE.makeTensor(values, shape, dtype);
}
function setOpHandler(handler) {
opHandler = handler;
}
function decodeString(bytes, encoding) {
if (bytes.length === 0) {
return '';
}
return new TextDecoder(encoding).decode(bytes);
}
function toNestedArray(shape, a, isComplex = false) {
if (shape.length === 0) {
return a[0];
}
const size = shape.reduce((acc, c) => acc * c) * (isComplex ? 2 : 1);
if (size === 0) {
return [];
}
if (size !== a.length) {
throw new Error(`[${shape}] does not match the input size ${a.length}${isComplex ? ' for a complex tensor' : ''}.`);
}
return createNestedArray(0, shape, a, isComplex);
}
function createNestedArray(offset, shape, a, isComplex = false) {
const ret = new Array();
if (shape.length === 1) {
const d = shape[0] * (isComplex ? 2 : 1);
for (let i = 0; i < d; i++) {
ret[i] = a[offset + i];
}
}
else {
const d = shape[0];
const rest = shape.slice(1);
const len = rest.reduce((acc, c) => acc * c) * (isComplex ? 2 : 1);
for (let i = 0; i < d; i++) {
ret[i] = createNestedArray(offset + i * len, rest, a, isComplex);
}
}
return ret;
}
function getEnumKeyFromValue(object, value) {
return Object.keys(object).find(function (key) { return object[key] === value; });
}
function getSignatureDefEntryFromMetaGraphInfo(savedModelInfo, tags, signature) {
for (var i = 0; i < savedModelInfo.length; i++) {
var metaGraphInfo = savedModelInfo[i];
if (stringArraysHaveSameElements(tags, metaGraphInfo.tags)) {
if (metaGraphInfo.signatureDefs[signature] == null) {
throw new Error('The SavedModel does not have signature: ' + signature);
}
return metaGraphInfo.signatureDefs[signature];
}
}
throw new Error("The SavedModel does not have tags: " + tags);
}
function stringArraysHaveSameElements(arrayA, arrayB) {
if (arrayA.length === arrayB.length &&
arrayA.sort().join() === arrayB.sort().join()) {
return true;
}
return false;
}
function mapTFDtypeToJSDtype(tfDtype) {
switch (tfDtype) {
case 'DT_FLOAT':
return 'float32';
case 'DT_INT64':
case 'DT_INT32':
case 'DT_UINT8':
return 'int32';
case 'DT_BOOL':
return 'bool';
case 'DT_COMPLEX64':
return 'complex64';
case 'DT_STRING':
return 'string';
default:
throw new Error(`Unsupported tensor DataType: ${tfDtype}. Try to modify the model in python to convert the datatype`);
}
}
var SAVED_MODEL_INIT_OP_KEY = '__saved_model_init_op';
function getMetaGraphsFromSavedModel(path) {
return __awaiter(this, void 0, void 0, function () {
var result, modelMessage, metaGraphList, i, metaGraph, tags, signatureDef, signatureDefMap, signatureDefKeys, key, signatureDefEntry, inputsMapMessage, inputsMapKeys, inputs, inputsMapKey, inputTensor, inputTensorInfo, dtype, outputsMapMessage, outputsMapKeys, outputs, outputsMapKey, outputTensor, outputTensorInfo, dtype;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
result = [];
return [4 /*yield*/, readSavedModelProto(path)];
case 1:
modelMessage = _a.sent();
metaGraphList = modelMessage.getMetaGraphsList();
for (i = 0; i < metaGraphList.length; i++) {
metaGraph = {};
tags = metaGraphList[i].getMetaInfoDef().getTagsList();
metaGraph.tags = tags;
signatureDef = {};
signatureDefMap = metaGraphList[i].getSignatureDefMap();
signatureDefKeys = signatureDefMap.keys();
while (true) {
key = signatureDefKeys.next();
if (key.done) {
break;
}
if (key.value === SAVED_MODEL_INIT_OP_KEY) {
continue;
}
signatureDefEntry = signatureDefMap.get(key.value);
inputsMapMessage = signatureDefEntry.getInputsMap();
inputsMapKeys = inputsMapMessage.keys();
inputs = {};
while (true) {
inputsMapKey = inputsMapKeys.next();
if (inputsMapKey.done) {
break;
}
inputTensor = inputsMapMessage.get(inputsMapKey.value);
inputTensorInfo = {};
dtype = getEnumKeyFromValue(messages.DataType, inputTensor.getDtype());
inputTensorInfo.dtype = mapTFDtypeToJSDtype(dtype);
inputTensorInfo.tfDtype = dtype;
inputTensorInfo.name = inputTensor.getName();
inputTensorInfo.shape = inputTensor.getTensorShape().getDimList();
inputs[inputsMapKey.value] = inputTensorInfo;
}
outputsMapMessage = signatureDefEntry.getOutputsMap();
outputsMapKeys = outputsMapMessage.keys();
outputs = {};
while (true) {
outputsMapKey = outputsMapKeys.next();
if (outputsMapKey.done) {
break;
}
outputTensor = outputsMapMessage.get(outputsMapKey.value);
outputTensorInfo = {};
dtype = getEnumKeyFromValue(messages.DataType, outputTensor.getDtype());
outputTensorInfo.dtype = mapTFDtypeToJSDtype(dtype);
outputTensorInfo.tfDtype = dtype;
outputTensorInfo.name = outputTensor.getName();
outputTensorInfo.shape = outputTensor.getTensorShape().getDimList();
outputs[outputsMapKey.value] = outputTensorInfo;
}
signatureDef[key.value] = { inputs: inputs, outputs: outputs };
}
metaGraph.signatureDefs = signatureDef;
result.push(metaGraph);
}
return [2 /*return*/, result];
}
});
});
}
var util_1 = require('util'), os = require("os");;
var readFile = util_1.promisify(fs.readFile);
var SAVED_MODEL_FILE_NAME = '/saved_model.pb';
function readSavedModelProto(path) {
return __awaiter(this, void 0, void 0, function () {
var modelFile, array;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
try {
fs.accessSync(path + SAVED_MODEL_FILE_NAME, fs.constants.R_OK);
}
catch (error) {
throw new Error('There is no saved_model.pb file in the directory: ' + path);
}
return [4, readFile(path + SAVED_MODEL_FILE_NAME)];
case 1:
modelFile = _a.sent();
array = new Uint8Array(modelFile);
return [2, messages.SavedModel.deserializeBinary(array)];
}
});
});
}
function now() {
return env().platform.now();
}
function rightPad(a, size) {
if (size <= a.length) {
return a;
}
return a + ' '.repeat(size - a.length);
}
function checkComputationForErrors(vals, dtype, kernelName) {
if (dtype !== 'float32') {
return false;
}
for (let i = 0; i < vals.length; i++) {
const num = vals[i];
if (isNaN(num) || !isFinite(num)) {
console.warn(`Found ${num} in the result of '${kernelName}'`);
return true;
}
}
return false;
}
function notYetImplemented(kernelName) {
throw new Error(`'${kernelName}' not yet implemented or not found in the registry. This kernel may not be supported by the tfjs backend you have chosen`);
}
function encodeInt32ArrayAsInt64(value) {
if (os.endianness() !== 'LE') {
throw new Error("Int64Scalar does not support endianness of this machine: " + ("" + os.endianness()));
}
var buffer = new Int32Array(value.length * 2);
for (var i = 0; i < value.length; i++) {
buffer[i * 2] = value[i];
}
return buffer;
}
function bytesPerElement(dtype) {
if (dtype === 'float32' || dtype === 'int32') {
return 4;
}
else if (dtype === 'complex64') {
return 8;
}
else if (dtype === 'bool') {
return 1;
}
else {
throw new Error(`Unknown dtype ${dtype}`);
}
}
function encodeString(s, encoding = 'utf-8') {
encoding = encoding || 'utf-8';
return env().platform.encode(s, encoding);
}
function makeZerosTypedArray(size, dtype) {
if (dtype == null || dtype === 'float32' || dtype === 'complex64') {
return new Float32Array(size);
}
else if (dtype === 'int32') {
return new Int32Array(size);
}
else if (dtype === 'bool') {
return new Uint8Array(size);
}
else {
throw new Error(`Unknown data type ${dtype}`);
}
}
function bytesFromStringArray(arr) {
if (arr == null) {
return 0;
}
let bytes = 0;
arr.forEach(x => bytes += x.length);
return bytes;
}
class EngineState {
constructor() {
this.registeredVariables = {};
this.nextTapeNodeId = 0;
this.numBytes = 0;
this.numTensors = 0;
this.numStringTensors = 0;
this.numDataBuffers = 0;
this.gradientDepth = 0;
this.kernelDepth = 0;
this.scopeStack = [];
this.numDataMovesStack = [];
this.nextScopeId = 0;
this.tensorInfo = new WeakMap();
this.profiling = false;
this.activeProfile = {
newBytes: 0,
newTensors: 0,
peakBytes: 0,
kernels: [],
result: null,
get kernelNames() {
return Array.from(new Set(this.kernels.map(k => k.name)));
}
};
}
dispose() {
for (const variableName in this.registeredVariables) {
this.registeredVariables[variableName].dispose();
}
}
}
class Profiler {
constructor(backendTimer, logger) {
this.backendTimer = backendTimer;
this.logger = logger;
if (logger == null) {
this.logger = new Logger();
}
}
profileKernel(kernelName, inputs, f) {
let outputs;
const holdResultWrapperFn = () => {
outputs = f();
};
let timer;
const start = now();
if (this.backendTimer.timerAvailable()) {
timer = this.backendTimer.time(holdResultWrapperFn);
}
else {
holdResultWrapperFn();
for (const output of outputs) {
output.dataSync();
}
timer = Promise.resolve({ kernelMs: now() - start });
}
if (env().getBool('CHECK_COMPUTATION_FOR_ERRORS')) {
for (let i = 0; i < outputs.length; i++) {
const output = outputs[i];
output.data().then(tensorVals => {
checkComputationForErrors(tensorVals, output.dtype, kernelName);
});
}
}
const kernelProfile = {
kernelName,
outputs,
inputs,
timeMs: timer.then(timing => timing.kernelMs),
extraInfo: timer.then(timing => timing.getExtraProfileInfo != null ? timing.getExtraProfileInfo() : '')
};
return kernelProfile;
}
logKernelProfile(kernelProfile) {
const { kernelName, outputs, timeMs, inputs, extraInfo } = kernelProfile;
outputs.forEach(result => {
Promise.all([result.data(), timeMs, extraInfo]).then(valueContainer => {
this.logger.logKernelProfile(kernelName, result, valueContainer[0], valueContainer[1], inputs, valueContainer[2]);
});
});
}
}
class Logger {
logKernelProfile(name, result, vals, timeMs, inputs, extraInfo) {
const time = typeof timeMs === 'number' ? rightPad(`${timeMs}ms`, 9) : timeMs['error'];
const paddedName = rightPad(name, 25);
const rank = result.rank;
const size = result.size;
const shape = rightPad(result.shape.toString(), 14);
let inputShapesDescription = '';
for (const name in inputs) {
const input = inputs[name];
if (input != null) {
const inputShape = input.shape || result.shape;
const inputRank = inputShape.length;
inputShapesDescription +=
`${name}: ${inputRank}D ${inputRank > 0 ? inputShape : ''} `;
}
}
console.log(`%c${paddedName}\t%c${time}\t%c${rank}D ${shape}\t%c${size}\t%c${inputShapesDescription}\t%c${extraInfo}`, 'font-weight:bold', 'color:red', 'color:blue', 'color: orange', 'color: green', 'color: steelblue');
}
}
class Engine {
constructor(ENV) {
this.ENV = ENV;
this.registry = {};
this.registryFactory = {};
this.pendingBackendInitId = 0;
this.state = new EngineState();
}
async ready() {
if (this.pendingBackendInit != null) {
return this.pendingBackendInit.then(() => { });
}
if (this.backendInstance != null) {
return;
}
const sortedBackends = this.getSortedBackends();
for (let i = 0; i < sortedBackends.length; i++) {
const backendName = sortedBackends[i];
const success = await this.initializeBackend(backendName).success;
if (success) {
await this.setBackend(backendName);
return;
}
}
throw new Error(`Could not initialize any backends, all backend initializations failed.`);
}
get backend() {
if (this.pendingBackendInit != null) {
throw new Error(`Backend '${this.backendName}' has not yet been initialized. Make sure ` +
`to await tf.ready() or await tf.setBackend() before calling other methods`);
}
if (this.backendInstance == null) {
const { name, asyncInit } = this.initializeBackendsAndReturnBest();
if (asyncInit) {
throw new Error(`The highest priority backend '${name}' has not yet been ` +
`initialized. Make sure to await tf.ready() or ` +
`await tf.setBackend() before calling other methods`);
}
this.setBackend(name);
}
return this.backendInstance;
}
backendNames() {
return Object.keys(this.registryFactory);
}
findBackend(backendName) {
if (!(backendName in this.registry)) {
if (backendName in this.registryFactory) {
const { asyncInit } = this.initializeBackend(backendName);
if (asyncInit) {
return null;
}
}
else {
return null;
}
}
return this.registry[backendName];
}
findBackendFactory(backendName) {
if (!(backendName in this.registryFactory)) {
return null;
}
return this.registryFactory[backendName].factory;
}
registerBackend(backendName, factory, priority = 1) {
if (backendName in this.registryFactory) {
console.warn(`${backendName} backend was already registered. Reusing existing backend factory.`);
return false;
}
this.registryFactory[backendName] = { factory, priority };
return true;
}
async setBackend(backendName) {
if (this.registryFactory[backendName] == null) {
throw new Error(`Backend name '${backendName}' not found in registry`);
}
this.backendName = backendName;
if (this.registry[backendName] == null) {
this.backendInstance = null;
const { success, asyncInit } = this.initializeBackend(backendName);
const result = asyncInit ? await success : success;
if (!result) {
return false;
}
}
this.backendInstance = this.registry[backendName];
this.setupRegisteredKernels();
this.profiler = new Profiler(this.backendInstance);
return true;
}
setupRegisteredKernels() {
const kernels = getKernelsForBackend(this.backendName);
kernels.forEach(kernel => {
if (kernel.setupFunc != null) {
kernel.setupFunc(this.backendInstance);
}
});
}
disposeRegisteredKernels(backendName) {
const kernels = getKernelsForBackend(backendName);
kernels.forEach(kernel => {
if (kernel.disposeFunc != null) {
kernel.disposeFunc(this.registry[backendName]);
}
});
}
initializeBackend(backendName) {
const registryFactoryEntry = this.registryFactory[backendName];
if (registryFactoryEntry == null) {
throw new Error(`Cannot initialize backend ${backendName}, no registration found.`);
}
try {
const backend = registryFactoryEntry.factory();
if (backend && !(backend instanceof KernelBackend) &&
typeof backend.then === 'function') {
const promiseId = ++this.pendingBackendInitId;
const success = backend
.then(backendInstance => {
if (promiseId < this.pendingBackendInitId) {
return false;
}
this.registry[backendName] = backendInstance;
this.pendingBackendInit = null;
return true;
})
.catch(err => {
if (promiseId < this.pendingBackendInitId) {
return false;
}
this.pendingBackendInit = null;
console.warn(`Initialization of backend ${backendName} failed`);
console.warn(err.stack || err.message);
return false;
});
this.pendingBackendInit = success;
return { success, asyncInit: true };
}
else {
this.registry[backendName] = backend;
return { success: true, asyncInit: false };
}
}
catch (err) {
console.warn(`Initialization of backend ${backendName} failed`);
console.warn(err.stack || err.message);
return { success: false, asyncInit: false };
}
}
removeBackend(backendName) {
if (!(backendName in this.registryFactory)) {
throw new Error(`${backendName} backend not found in registry`);
}
if (this.backendName === backendName && this.pendingBackendInit != null) {
this.pendingBackendInitId++;
}
if (backendName in this.registry) {
this.disposeRegisteredKernels(backendName);
this.registry[backendName].dispose();
delete this.registry[backendName];
}
delete this.registryFactory[backendName];
if (this.backendName === backendName) {
this.pendingBackendInit = null;
this.backendName = null;
this.backendInstance = null;
}
}
getSortedBackends() {
if (Object.keys(this.registryFactory).length === 0) {
throw new Error('No backend found in registry.');
}
return Object.keys(this.registryFactory).sort((a, b) => {
return this.registryFactory[b].priority - this.registryFactory[a].priority;
});
}
initializeBackendsAndReturnBest() {
const sortedBackends = this.getSortedBackends();
for (let i = 0; i < sortedBackends.length; i++) {
const backendName = sortedBackends[i];
const { success, asyncInit } = this.initializeBackend(backendName);
if (asyncInit || success) {
return { name: backendName, asyncInit };
}
}
throw new Error(`Could not initialize any backends, all backend initializations failed.`);
}
moveData(backend, dataId) {
const info = this.state.tensorInfo.get(dataId);
const srcBackend = info.backend;
const values = this.readSync(dataId);
const refCount = srcBackend.refCount(dataId);
srcBackend.disposeData(dataId, true);
info.backend = backend;
backend.move(dataId, values, info.shape, info.dtype, refCount);
if (this.shouldCheckForMemLeaks()) {
this.state.numDataMovesStack[this.state.numDataMovesStack.length - 1]++;
}
}
tidy(nameOrFn, fn) {
let name = null;
if (fn == null) {
if (typeof nameOrFn !== 'function') {
throw new Error('Please provide a function to tidy()');
}
fn = nameOrFn;
}
else {
if (typeof nameOrFn !== 'string' && !(nameOrFn instanceof String)) {
throw new Error('When calling with two arguments, the first argument ' +
'to tidy() must be a string');
}
if (typeof fn !== 'function') {
throw new Error('When calling with two arguments, the 2nd argument ' +
'to tidy() must be a function');
}
name = nameOrFn;
}
let result;
return this.scopedRun(() => this.startScope(name), () => this.endScope(result), () => {
result = fn();
if (result instanceof Promise) {
console.error('Cannot return a Promise inside of tidy.');
}
return result;
});
}
scopedRun(start, end, f) {
start();
try {
const res = f();
end();
return res;
}
catch (ex) {
end();
throw ex;
}
}
nextTensorId() {
return Engine.nextTensorId++;
}
nextVariableId() {
return Engine.nextVariableId++;
}
clone(x) {
const y = ENGINE.runKernel(Identity, { x });
const inputs = { x };
const grad = (dy) => ({
x: () => {
const dtype = 'float32';
const gradInputs = { x: dy };
const attrs = { dtype };
return ENGINE.runKernel(Cast, gradInputs, attrs);
}
});
const saved = [];
this.addTapeNode(this.state.activeScope.name, inputs, [y], grad, saved, {});
return y;
}
runKernel(kernelName, inputs, attrs) {
if (this.backendName == null) {
this.backend;
}
const hasKernel = getKernel(kernelName, this.backendName) != null;
if (!hasKernel) {
throw new Error(`Kernel '${kernelName}' not registered for backend '${this.backendName}'`);
}
return this.runKernelFunc({ kernelName, inputs, attrs });
}
shouldCheckForMemLeaks() {
return this.ENV.getBool('IS_TEST');
}
checkKernelForMemLeak(kernelName, numDataIdsBefore, outInfos) {
const numDataIdsAfter = this.backend.numDataIds();
let numOutputDataIds = 0;
outInfos.forEach(info => {
numOutputDataIds += (info.dtype === 'complex64' ? 3 : 1);
});
const numMoves = this.state.numDataMovesStack[this.state.numDataMovesStack.length - 1];
const dataIdsLeaked = numDataIdsAfter - numDataIdsBefore - numOutputDataIds - numMoves;
if (dataIdsLeaked > 0) {
throw new Error(`Backend '${this.backendName}' has an internal memory leak ` +
`(${dataIdsLeaked} data ids) after running '${kernelName}'`);
}
}
runKernelFunc(kernelParams) {
let outputs;
let saved = [];
const isTapeOn = this.isTapeOn();
const startingBytecount = this.state.numBytes;
const startingNumTensors = this.state.numTensors;
if (this.shouldCheckForMemLeaks()) {
this.state.numDataMovesStack.push(0);
}
let kernelFunc;
if (this.backendName == null) {
this.backend;
}
let out;
const kernelOrScopeName = isRegisteredKernelInvocation(kernelParams) ?
kernelParams.kernelName :
this.state.activeScope != null ? this.state.activeScope.name : '';
if (isRegisteredKernelInvocation(kernelParams)) {
const { kernelName, inputs, attrs } = kernelParams;
if (this.backendName == null) {
this.backend;
}
const kernel = getKernel(kernelName, this.backendName);
assert(kernel != null, () => `Cannot find registered kernel '${kernelName}' for backend '${this.backendName}'`);
kernelFunc = () => {
const numDataIdsBefore = this.backend.numDataIds();
out = kernel.kernelFunc({ inputs, attrs, backend: this.backend });
const outInfos = Array.isArray(out) ? out : [out];
if (this.shouldCheckForMemLeaks()) {
this.checkKernelForMemLeak(kernelName, numDataIdsBefore, outInfos);
}
const outTensors = outInfos.map((outInfo) => {
if (outInfo.rank != null) {
return outInfo;
}
return this.makeTensorFromTensorInfo(outInfo);
});
if (isTapeOn) {
const tensorsToSave = this.getTensorsForGradient(kernelName, inputs, outTensors);
saved = this.saveTensorsForBackwardMode(tensorsToSave);
}
return outTensors;
};
}
else {
const { forwardFunc } = kernelParams;
const saveFunc = (tensors) => {
if (!isTapeOn) {
return;
}
saved = tensors.map(tensor => this.keep(this.clone(tensor)));
};
kernelFunc = () => {
const numDataIdsBefore = this.backend.numDataIds();
out = this.tidy(() => forwardFunc(this.backend, saveFunc));
const outs = (Array.isArray(out) ? out : [out]);
if (this.shouldCheckForMemLeaks()) {
this.checkKernelForMemLeak(kernelOrScopeName, numDataIdsBefore, outs);
}
return outs;
};
}
const { inputs, attrs } = kernelParams;
const backwardsFunc = isRegisteredKernelInvocation(kernelParams) ?
null :
kernelParams.backwardsFunc;
let kernelProfile;
this.scopedRun(
() => this.state.kernelDepth++, () => this.state.kernelDepth--, () => {
if (!this.ENV.getBool('DEBUG') && !this.state.profiling) {
outputs = kernelFunc();
}
else {
kernelProfile = this.profiler.profileKernel(kernelOrScopeName, inputs, () => kernelFunc());
if (this.ENV.getBool('DEBUG')) {
this.profiler.logKernelProfile(kernelProfile);
}
outputs = kernelProfile.outputs;
}
});
if (isTapeOn) {
this.addTapeNode(kernelOrScopeName, inputs, outputs, backwardsFunc, saved, attrs);
}
if (this.state.profiling) {
this.state.activeProfile.kernels.push({
name: kernelOrScopeName,
bytesAdded: this.state.numBytes - startingBytecount,
totalBytesSnapshot: this.state.numBytes,
tensorsAdded: this.state.numTensors - startingNumTensors,
totalTensorsSnapshot: this.state.numTensors,
inputShapes: Object.keys(inputs).map(key => inputs[key] != null ? inputs[key].shape : null),
outputShapes: outputs.map(item => item.shape),
kernelTimeMs: kernelProfile.timeMs,
extraInfo: kernelProfile.extraInfo
});
}
return (Array.isArray(out) ? outputs : outputs[0]);
}
saveTensorsForBackwardMode(tensors) {
const saved = tensors.map(tensor => this.keep(this.clone(tensor)));
return saved;
}
getTensorsForGradient(kernelName, inputs, outputs) {
const gradConfig = getGradient(kernelName);
if (gradConfig != null) {
const inputsToSave = gradConfig.inputsToSave || [];
const outputsToSave = gradConfig.outputsToSave || [];
let inputTensorsToSave;
if (gradConfig.saveAllInputs) {
assert(Array.isArray(inputs), () => 'saveAllInputs is true, expected inputs to be an array.');
inputTensorsToSave = Object.keys(inputs).map((key) => inputs[key]);
}
else {
inputTensorsToSave = inputsToSave.map((inputName) => inputs[inputName]);
}
const outputTensorsToSave = outputs.filter((_, i) => outputsToSave[i]);
return inputTensorsToSave.concat(outputTensorsToSave);
}
return [];
}
makeTensor(values, shape, dtype, backend) {
if (values == null) {
throw new Error('Values passed to engine.makeTensor() are null');
}
dtype = dtype || 'float32';
backend = backend || this.backend;
let backendVals = values;
if (dtype === 'string' && isString(values[0])) {
backendVals = values.map(d => encodeString(d));
}
const dataId = backend.write(backendVals, shape, dtype);
const t = new Tensor(shape, dtype, dataId, this.nextTensorId());
this.trackTensor(t, backend);
if (dtype === 'string') {
const info = this.state.tensorInfo.get(dataId);
const newBytes = bytesFromStringArray(backendVals);
this.state.numBytes += newBytes - info.bytes;
info.bytes = newBytes;
}
return t;
}
makeTensorFromDataId(dataId, shape, dtype, backend) {
dtype = dtype || 'float32';
const tensorInfo = { dataId, shape, dtype };
return this.makeTensorFromTensorInfo(tensorInfo, backend);
}
makeTensorFromTensorInfo(tensorInfo, backend) {
const { dataId, shape, dtype } = tensorInfo;
const t = new Tensor(shape, dtype, dataId, this.nextTensorId());
this.trackTensor(t, backend);
return t;
}
makeVariable(initialValue, trainable = true, name, dtype) {
name = name || this.nextVariableId().toString();
if (dtype != null && dtype !== initialValue.dtype) {
initialValue = initialValue.cast(dtype);
}
const v = new Variable(initialValue, trainable, name, this.nextTensorId());
if (this.state.registeredVariables[v.name] != null) {
throw new Error(`Variable with name ${v.name} was already registered`);
}
this.state.registeredVariables[v.name] = v;
this.incRef(v, this.backend);
return v;
}
trackTensor(a, backend) {
this.state.numTensors++;
if (a.dtype === 'string') {
this.state.numStringTensors++;
}
let bytes = 0;
if (a.dtype !== 'complex64' && a.dtype !== 'string') {
bytes = a.size * bytesPerElement(a.dtype);
}
this.state.numBytes += bytes;
if (!this.state.tensorInfo.has(a.dataId)) {
this.state.numDataBuffers++;
this.state.tensorInfo.set(a.dataId, {
backend: backend || this.backend,
dtype: a.dtype,
shape: a.shape,
bytes
});
}
if (!(a instanceof Variable)) {
this.track(a);
}
}
incRef(a, backend) {
this.trackTensor(a, backend);
this.backend.incRef(a.dataId);
}
removeDataId(dataId, backend) {
if (this.state.tensorInfo.has(dataId) &&
this.state.tensorInfo.get(dataId).backend === backend) {
this.state.tensorInfo.delete(dataId);
this.state.numDataBuffers--;
}
}
disposeTensor(a) {
if (!this.state.tensorInfo.has(a.dataId)) {
return;
}
const info = this.state.tensorInfo.get(a.dataId);
this.state.numTensors--;
if (a.dtype === 'string') {
this.state.numStringTensors--;
this.state.numBytes -= info.bytes;
}
if (a.dtype !== 'complex64' && a.dtype !== 'string') {
const bytes = a.size * bytesPerElement(a.dtype);
this.state.numBytes -= bytes;
}
if (info.backend.disposeData(a.dataId)) {
this.removeDataId(a.dataId, info.backend);
}
}
disposeVariables() {
for (const varName in this.state.registeredVariables) {
const v = this.state.registeredVariables[varName];
this.disposeVariable(v);
}
}
disposeVariable(v) {
this.disposeTensor(v);
if (this.state.registeredVariables[v.name] != null) {
delete this.state.registeredVariables[v.name];
}
}
memory() {
const info = this.backend.memory();
info.numTensors = this.state.numTensors;
info.numDataBuffers = this.state.numDataBuffers;
info.numBytes = this.state.numBytes;
if (this.state.numStringTensors > 0) {
info.unreliable = true;
if (info.reasons == null) {
info.reasons = [];
}
info.reasons.push('Memory usage by string tensors is approximate (2 bytes per character)');
}
return info;
}
async profile(query) {
this.state.profiling = true;
const startBytes = this.state.numBytes;
const startNumTensors = this.state.numTensors;
this.state.activeProfile.kernels = [];
this.state.activeProfile.result = await query();
this.state.profiling = false;
this.state.activeProfile.peakBytes = Math.max(...this.state.activeProfile.kernels.map(d => d.totalBytesSnapshot));
this.state.activeProfile.newBytes = this.state.numBytes - startBytes;
this.state.activeProfile.newTensors =
this.state.numTensors - startNumTensors;
for (const kernel of this.state.activeProfile.kernels) {
kernel.kernelTimeMs = await kernel.kernelTimeMs;
kernel.extraInfo = await kernel.extraInfo;
}
return this.state.activeProfile;
}
isTapeOn() {
return this.state.gradientDepth > 0 && this.state.kernelDepth === 0;
}
addTapeNode(kernelName, inputs, outputs, gradientsFunc, saved, attrs) {
const tapeNode = { id: this.state.nextTapeNodeId++, kernelName, inputs, outputs, saved };
const gradConfig = getGradient(kernelName);
if (gradConfig != null) {
gradientsFunc = gradConfig.gradFunc;
}
if (gradientsFunc != null) {
tapeNode.gradient = (dys) => {
dys = dys.map((dy, i) => {
if (dy == null) {
const output = outputs[i];
const vals = makeZerosTypedArray(output.size, output.dtype);
return this.makeTensor(vals, output.shape, output.dtype);
}
return dy;
});
return gradientsFunc(dys.length > 1 ? dys : dys[0], saved, attrs);
};
}
this.state.activeTape.push(tapeNode);
}
keep(result) {
result.kept = true;
return result;
}
startTape() {
if (this.state.gradientDepth === 0) {
this.state.activeTape = [];
}
this.state.gradientDepth++;
}
endTape() {
this.state.gradientDepth--;
}
startScope(name) {
const scopeInfo = {
track: [],
name: 'unnamed scope',
id: this.state.nextScopeId++
};
if (name) {
scopeInfo.name = name;
}
this.state.scopeStack.push(scopeInfo);
this.state.activeScope = scopeInfo;
}
endScope(result) {
const tensorsToTrackInParent = getTensorsInContainer(result);
const tensorsToTrackInParentSet = new Set(tensorsToTrackInParent.map(t => t.id));
for (let i = 0; i < this.state.activeScope.track.length; i++) {
const tensor = this.state.activeScope.track[i];
if (!tensor.kept && !tensorsToTrackInParentSet.has(tensor.id)) {
tensor.dispose();
}
}
const oldScope = this.state.scopeStack.pop();
this.state.activeScope = this.state.scopeStack.length === 0 ?
null :
this.state.scopeStack[this.state.scopeStack.length - 1];
tensorsToTrackInParent.forEach(tensor => {
if (!tensor.kept && tensor.scopeId === oldScope.id) {
this.track(tensor);
}
});
}
gradients(f, xs, dy, allowNoGradients = false) {
assert(xs.length > 0, () => 'gradients() received an empty list of xs.');
if (dy != null && dy.dtype !== 'float32') {
throw new Error(`dy must have 'float32' dtype, but has '${dy.dtype}'`);
}
const y = this.scopedRun(() => this.startTape(), () => this.endTape(), () => this.tidy('forward', f));
assert(y instanceof Tensor, () => 'The result y returned by f() must be a tensor.');
const filteredTape = getFilteredNodesXToY(this.state.activeTape, xs, y);
if (!allowNoGradients && filteredTape.length === 0 && xs.length > 0) {
throw new Error('Cannot compute gradient of y=f(x) with respect to x. Make sure ' +
'that the f you passed encloses all operations that lead from x to y.');
}
return this.tidy('backward', () => {
const accumulatedGradientMap = {};
accumulatedGradientMap[y.id] = (dy == null) ? ones(y.shape) : dy;
backpropagateGradients(accumulatedGradientMap, filteredTape,
f => this.tidy(f),
add);
const grads = xs.map(x => accumulatedGradientMap[x.id]);
if (this.state.gradientDepth === 0) {
this.state.activeTape.forEach(node => {
for (const tensor of node.saved) {
tensor.dispose();
}
});
this.state.activeTape = null;
}
return { value: y, grads };
});
}
customGrad(f) {
assert(isFunction(f), () => 'The f passed in customGrad(f) must be a function.');
return (...inputs) => {
assert(inputs.every(t => t instanceof Tensor), () => 'The args passed in customGrad(f)(x1, x2,...) must all be tensors');
let res;
const inputMap = {};
inputs.forEach((input, i) => {
inputMap[i] = input;
});
const forwardFunc = (_, save) => {
res = f(...[...inputs, save]);
assert(res.value instanceof Tensor, () => 'The function f passed in customGrad(f) must return an object where `obj.value` is a tensor');
assert(isFunction(res.gradFunc), () => 'The function f passed in customGrad(f) must return an object where `obj.gradFunc` is a function.');
return res.value;
};
const backwardsFunc = (dy, saved) => {
const gradRes = res.gradFunc(dy, saved);
const grads = Array.isArray(gradRes) ? gradRes : [gradRes];
assert(grads.length === inputs.length, () => 'The function f passed in customGrad(f) must return an ' +
'object where `obj.gradFunc` is a function that returns ' +
'the same number of tensors as inputs passed to f(...).');
assert(grads.every(t => t instanceof Tensor), () => 'The function f passed in customGrad(f) must return an ' +
'object where `obj.gradFunc` is a function that returns a list of only tensors.');
const gradMap = {};
grads.forEach((grad, i) => {
gradMap[i] = () => grad;
});
return gradMap;
};
return this.runKernelFunc({
forwardFunc,
backwardsFunc,
inputs: inputMap,
});
};
}
readSync(dataId) {
const info = this.state.tensorInfo.get(dataId);
return info.backend.readSync(dataId);
}
read(dataId) {
const info = this.state.tensorInfo.get(dataId);
return info.backend.read(dataId);
}
readToGPU(dataId, options) {
const info = this.state.tensorInfo.get(dataId);
return info.backend.readToGPU(dataId, options);
}
async time(query) {
const start = now();
const timingInfo = await this.backend.time(query);
timingInfo.wallMs = now() - start;
return timingInfo;
}
track(result) {
if (this.state.activeScope != null) {
result.scopeId = this.state.activeScope.id;
this.state.activeScope.track.push(result);
}
return result;
}
get registeredVariables() {
return this.state.registeredVariables;
}
reset() {
this.pendingBackendInitId++;
this.state.dispose();
this.ENV.reset();
this.state = new EngineState();
for (const backendName in this.registry) {
this.disposeRegisteredKernels(backendName);
this.registry[backendName].dispose();
delete this.registry[backendName];
}
this.backendName = null;
this.backendInstance = null;
this.pendingBackendInit = null;
}
}
Engine.nextTensorId = 0;
Engine.nextVariableId = 0;
class Tensor {
constructor(shape, dtype, dataId, id) {
this.kept = false;
this.isDisposedInternal = false;
this.shape = shape.slice();
this.dtype = dtype || 'float32';
this.size = sizeFromShape(shape);
this.strides = computeStrides(shape);
this.dataId = dataId;
this.id = id || 0;
this.rankType = (this.rank < 5 ? this.rank.toString() : 'higher');
}
get rank() {
return this.shape.length;
}
async buffer() {
const vals = await this.data();
return opHandler.buffer(this.shape, this.dtype, vals);
}
bufferSync() {
return opHandler.buffer(this.shape, this.dtype, this.dataSync());
}
async array() {
const vals = await this.data();
return toNestedArray(this.shape, vals, this.dtype === 'complex64');
}
/**
* Returns the tensor data as a nested array. The transfer of data is done
* synchronously.
*
* @doc {heading: 'Tensors', subheading: 'Classes'}
*/
arraySync() {
return toNestedArray(this.shape, this.dataSync(), this.dtype === 'complex64');
}
async data() {
this.throwIfDisposed();
const data = trackerFn().read(this.dataId);
if (this.dtype === 'string') {
const bytes = await data;
try {
return bytes.map(b => decodeString(b));
}
catch (_a) {
throw new Error('Failed to decode the string bytes into utf-8. ' +
'To get the original bytes, call tensor.bytes().');
}
}
return data;
}
dataToGPU(options) {
this.throwIfDisposed();
return trackerFn().readToGPU(this.dataId, options);
}
dataSync() {
this.throwIfDisposed();
const data = trackerFn().readSync(this.dataId);
if (this.dtype === 'string') {
try {
return data.map(b => decodeString(b));
}
catch (_a) {
throw new Error('Failed to decode the string bytes into utf-8. ' +
'To get the original bytes, call tensor.bytes().');
}
}
return data;
}
/** Returns the underlying bytes of the tensor's data. */
async bytes() {
this.throwIfDisposed();
const data = await trackerFn().read(this.dataId);
if (this.dtype === 'string') {
return data;
}
else {
return new Uint8Array(data.buffer);
}
}
/**
* Disposes `tf.Tensor` from memory.
*
* @doc {heading: 'Tensors', subheading: 'Classes'}
*/
dispose() {
if (this.isDisposed) {
return;
}
trackerFn().disposeTensor(this);
this.isDisposedInternal = true;
}
get isDisposed() {
return this.isDisposedInternal;
}
throwIfDisposed() {
if (this.isDisposed) {
throw new Error(`Tensor is disposed.`);
}
}
print(verbose = false) {
return opHandler.print(this, verbose);
}
clone() {
this.throwIfDisposed();
return opHandler.clone(this);
}
toString(verbose = false) {
const vals = this.dataSync();
return tensorToString(vals, this.shape, this.dtype, verbose);
}
cast(dtype) {
this.throwIfDisposed();
return opHandler.cast(this, dtype);
}
variable(trainable = true, name, dtype) {
this.throwIfDisposed();
return trackerFn().makeVariable(this, trainable, name, dtype);
}
}
const EPSILON_FLOAT32 = 1e-7, EPSILON_FLOAT16 = 1e-4;
class KernelBackend {
refCount(dataId) {
return notYetImplemented('refCount');
}
incRef(dataId) {
return notYetImplemented('incRef');
}
timerAvailable() {
return true;
}
time(f) {
return notYetImplemented('time');
}
read(dataId) {
return notYetImplemented('read');
}
readSync(dataId) {
return notYetImplemented('readSync');
}
readToGPU(dataId, options) {
return notYetImplemented('readToGPU');
}
numDataIds() {
return notYetImplemented('numDataIds');
}
disposeData(dataId, force) {
return notYetImplemented('disposeData');
}
write(values, shape, dtype) {
return notYetImplemented('write');
}
move(dataId, values, shape, dtype, refCount) {
return notYetImplemented('move');
}
memory() {
return notYetImplemented('memory');
}
floatPrecision() {
return notYetImplemented('floatPrecision');
}
epsilon() {
return this.floatPrecision() === 32 ? EPSILON_FLOAT32 : EPSILON_FLOAT16;
}
dispose() {
return notYetImplemented('dispose');
}
}
class Environment {
constructor(global) {
this.global = global;
this.flags = {};
this.flagRegistry = {};
this.urlFlags = {};
this.getQueryParams = getQueryParams;
this.populateURLFlags();
}
setPlatform(platformName, platform) {
if (this.platform != null) {
if (!(env().getBool('IS_TEST') || env().getBool('PROD'))) {
console.warn(`Platform ${this.platformName} has already been set. Overwriting the platform with ${platformName}.`);
}
}
this.platformName = platformName;
this.platform = platform;
}
registerFlag(flagName, evaluationFn, setHook) {
this.flagRegistry[flagName] = { evaluationFn, setHook };
if (this.urlFlags[flagName] != null) {
const flagValue = this.urlFlags[flagName];
if (!(env().getBool('IS_TEST') || env().getBool('PROD'))) {
console.warn(`Setting feature override from URL ${flagName}: ${flagValue}.`);
}
this.set(flagName, flagValue);
}
}
async getAsync(flagName) {
if (flagName in this.flags) {
return this.flags[flagName];
}
this.flags[flagName] = await this.evaluateFlag(flagName);
return this.flags[flagName];
}
get(flagName) {
if (flagName in this.flags) {
return this.flags[flagName];
}
const flagValue = this.evaluateFlag(flagName);
if (isPromise(flagValue)) {
throw new Error(`Flag ${flagName} cannot be synchronously evaluated. Please use getAsync() instead.`);
}
this.flags[flagName] = flagValue;
return this.flags[flagName];
}
getNumber(flagName) {
return this.get(flagName);
}
getBool(flagName) {
return this.get(flagName);
}
getFlags() {
return this.flags;
}
get features() {
return this.flags;
}
set(flagName, value) {
if (this.flagRegistry[flagName] == null) {
throw new Error(`Cannot set flag ${flagName} as it has not been registered.`);
}
this.flags[flagName] = value;
if (this.flagRegistry[flagName].setHook != null) {
this.flagRegistry[flagName].setHook(value);
}
}
evaluateFlag(flagName) {
if (this.flagRegistry[flagName] == null) {
throw new Error(`Cannot evaluate flag '${flagName}': no evaluation function found.`);
}
return this.flagRegistry[flagName].evaluationFn();
}
setFlags(flags) {
this.flags = Object.assign({}, flags);
}
reset() {
this.flags = {};
this.urlFlags = {};
this.populateURLFlags();
}
populateURLFlags() {
if (typeof this.global === 'undefined' || typeof this.global.location === 'undefined' || typeof this.global.location.search === 'undefined') {
return;
}
const urlParams = this.getQueryParams(this.global.location.search);
if (TENSORFLOWJS_FLAGS_PREFIX in urlParams) {
const keyValues = urlParams[TENSORFLOWJS_FLAGS_PREFIX].split(',');
keyValues.forEach(keyValue => {
const [key, value] = keyValue.split(':');
this.urlFlags[key] = parseValue(key, value);
});
}
}
}
class DataStorage {
constructor(backend, dataMover) {
this.backend = backend;
this.dataMover = dataMover;
this.data = new WeakMap();
this.dataIdsCount = 0;
}
get(dataId) {
if (!this.data.has(dataId)) {
this.dataMover.moveData(this.backend, dataId);
}
return this.data.get(dataId);
}
set(dataId, value) {
this.dataIdsCount++;
this.data.set(dataId, value);
}
has(dataId) {
return this.data.has(dataId);
}
delete(dataId) {
this.dataIdsCount--;
return this.data.delete(dataId);
}
numDataIds() {
return this.dataIdsCount;
}
}
var TFSavedModel = /** @class */ (function () {
function TFSavedModel(sessionId, jsid, signature, backend) {
this.sessionId = sessionId;
this.jsid = jsid;
this.signature = signature;
this.backend = backend;
this.disposed = false;
}
Object.defineProperty(TFSavedModel.prototype, "inputs", {
/**
* Return the array of input tensor info.
*
* @doc {heading: 'Models', subheading: 'SavedModel'}
*/
get: function () {
var entries = this.signature.inputs;
var results = Object.keys(entries).map(function (key) { return entries[key]; });
results.forEach(function (info) {
info.name = info.name.replace(/:0$/, '');
});
return results;
},
enumerable: true,
configurable: true
});
Object.defineProperty(TFSavedModel.prototype, "outputs", {
/**
* Return the array of output tensor info.
*
* @doc {heading: 'Models', subheading: 'SavedModel'}
*/
get: function () {
var entries = this.signature.outputs;
var results = Object.keys(entries).map(function (key) { return entries[key]; });
results.forEach(function (info) {
info.name = info.name.replace(/:0$/, '');
});
return results;
},
enumerable: true,
configurable: true
});
/**
* Delete the SavedModel from nodeBackend and delete corresponding session in
* the C++ backend if the session is only used by this TFSavedModel.
*
* @doc {heading: 'Models', subheading: 'SavedModel'}
*/
TFSavedModel.prototype.dispose = function () {
if (!this.disposed) {
this.disposed = true;
loadedSavedModelPathMap.delete(this.jsid);
for (var _i = 0, _a = Array.from(loadedSavedModelPathMap.keys()); _i < _a.length; _i++) {
var id = _a[_i];
var value = loadedSavedModelPathMap.get(id);
if (value.sessionId === this.sessionId) {
return;
}
}
this.backend.deleteSavedModel(this.sessionId);
}
else {
throw new Error('This SavedModel has already been deleted.');
}
};
Object.defineProperty(TFSavedModel.prototype, "outputNodeNames", {
get: function () {
var _this = this;
if (this.outputNodeNames_ != null) {
return this.outputNodeNames_;
}
this.outputNodeNames_ =
Object.keys(this.signature.outputs)
.reduce(function (names, key) {
names[key] = _this.signature.outputs[key].name;
return names;
}, {});
return this.outputNodeNames_;
},
enumerable: true,
configurable: true
});
/**
* Execute the inference for the input tensors.
*
* @param input The input tensors, when there is single input for the model,
* inputs param should be a Tensor. For models with multiple inputs, inputs
* params should be in either Tensor[] if the input order is fixed, or
* otherwise NamedTensorMap format. The keys in the NamedTensorMap are the
* name of input tensors in SavedModel signatureDef. It can be found through
* `tf.node.getMetaGraphsFromSavedModel()`.
*
* For batch inference execution, the tensors for each input need to be
* concatenated together. For example with mobilenet, the required input shape
* is [1, 244, 244, 3], which represents the [batch, height, width, channel].
* If we are provide a batched data of 100 images, the input tensor should be
* in the shape of [100, 244, 244, 3].
*
* @param config Prediction configuration for specifying the batch size.
*
* @returns Inference result tensors. The output would be single Tensor if
* model has single output node, otherwise Tensor[] or NamedTensorMap[] will
* be returned for model with multiple outputs.
*
* @doc {heading: 'Models', subheading: 'SavedModel'}
*/
TFSavedModel.prototype.predict = function (inputs, config) {
var _this = this;
if (this.disposed) {
throw new Error('The TFSavedModel has already been deleted!');
}
else {
var inputTensors = [];
if (inputs instanceof Tensor) {
inputTensors.push(inputs);
var result = this.backend.runSavedModel(this.sessionId, inputTensors, Object.values(this.signature.inputs), Object.values(this.outputNodeNames));
return result.length > 1 ? result : result[0];
}
else if (Array.isArray(inputs)) {
inputTensors = inputs;
return this.backend.runSavedModel(this.sessionId, inputTensors, Object.values(this.signature.inputs), Object.values(this.outputNodeNames));
}
else {
var inputTensorNames = Object.keys(this.signature.inputs);
var providedInputNames = Object.keys(inputs);
if (!stringArraysHaveSameElements(inputTensorNames, providedInputNames)) {
throw new Error("The model signatureDef input names are " + inputTensorNames.join() + ", however the provided input names are " + providedInputNames.join() + ".");
}
var inputNodeNamesArray = [];
for (var i = 0; i < inputTensorNames.length; i++) {
inputTensors.push(inputs[inputTensorNames[i]]);
inputNodeNamesArray.push(this.signature.inputs[inputTensorNames[i]]);
}
var outputTensorNames = Object.keys(this.outputNodeNames);
var outputNodeNamesArray = [];
for (var i = 0; i < outputTensorNames.length; i++) {
outputNodeNamesArray.push(this.outputNodeNames[outputTensorNames[i]]);
}
var outputTensors_1 = this.backend.runSavedModel(this.sessionId, inputTensors, inputNodeNamesArray, outputNodeNamesArray);
assert(outputTensors_1.length === outputNodeNamesArray.length, function () {
return 'Output tensors do not match output node names, ' +
("receive " + outputTensors_1.length + ") output tensors but ") +
("there are " + _this.outputNodeNames.length + " output nodes.");
});
var outputMap = {};
for (var i = 0; i < outputTensorNames.length; i++) {
outputMap[outputTensorNames[i]] = outputTensors_1[i];
}
return outputMap;
}
}
};
/**
* Execute the inference for the input tensors and return activation
* values for specified output node names without batching.
*
* @param input The input tensors, when there is single input for the model,
* inputs param should be a Tensor. For models with multiple inputs, inputs
* params should be in either Tensor[] if the input order is fixed, or
* otherwise NamedTensorMap format.
*
* @param outputs string|string[]. List of output node names to retrieve
* activation from.
*
* @returns Activation values for the output nodes result tensors. The return
* type matches specified parameter outputs type. The output would be single
* Tensor if single output is specified, otherwise Tensor[] for multiple
* outputs.
*
* @doc {heading: 'Models', subheading: 'SavedModel'}
*/
TFSavedModel.prototype.execute = function (inputs, outputs) {
throw new Error('execute() of TFSavedModel is not supported yet.');
};
return TFSavedModel;
}());
var Int64Scalar = /** @class */ (function () {
function Int64Scalar(value) {
this.value = value;
this.dtype = 'int64';
this.rank = 1;
if (Int64Scalar.endiannessOkay_ == null) {
if (os.endianness() !== 'LE') {
throw new Error("Int64Scalar does not support endianness of this machine: " +
("" + os.endianness()));
}
Int64Scalar.endiannessOkay_ = true;
}
assert(value > -INT32_MAX && value < INT32_MAX - 1, function () {
return "Got a value outside of the bound of values supported for int64 " +
("dtype ([-" + INT32_MAX + ", " + (INT32_MAX - 1) + "]): " + value);
});
assert(Number.isInteger(value), function () { return "Expected value to be an integer, but got " + value; });
var highPart = value >= 0 ? 0 : -1;
var lowPart = value % INT32_MAX;
this.valueArray_ = new Int32Array([lowPart, highPart]);
}
Object.defineProperty(Int64Scalar.prototype, "shape", {
get: function () {
return [];
},
enumerable: true,
configurable: true
});
Object.defineProperty(Int64Scalar.prototype, "valueArray", {
get: function () {
return this.valueArray_;
},
enumerable: true,
configurable: true
});
return Int64Scalar;
}());
var NodeJSKernelBackend = /** @class */ (function () {
__extends(NodeJSKernelBackend, KernelBackend);
function NodeJSKernelBackend(binding, packageName) {
// var _this = KernelBackend.call(this) || this;
var _this = this;
_this.binding = binding;
_this.isGPUPackage = packageName === '@tensorflow/tfjs-node-gpu';
_this.isUsingGpuDevice = _this.binding.isUsingGpuDevice();
_this.tensorMap = new DataStorage(_this, ENGINE);
return _this;
}
NodeJSKernelBackend.prototype.getDTypeInteger = function (dtype) {
switch (dtype) {
case 'float32':
return this.binding.TF_FLOAT;
case 'int32':
return this.binding.TF_INT32;
case 'bool':
return this.binding.TF_BOOL;
case 'complex64':
return this.binding.TF_COMPLEX64;
case 'string':
return this.binding.TF_STRING;
default:
throw new Error("Unsupported DType: " + dtype);
}
};
NodeJSKernelBackend.prototype.typeAttributeFromTensor = function (value) {
return this.getDTypeInteger(value.dtype);
};
NodeJSKernelBackend.prototype.createOutputTensor = function (metadata) {
var newId = {};
this.tensorMap.set(newId, {
shape: metadata.shape,
dtype: metadata.dtype,
id: metadata.id,
values: null,
refCount: 1
});
var dtype;
switch (metadata.dtype) {
case this.binding.TF_FLOAT:
dtype = 'float32';
break;
case this.binding.TF_INT32:
dtype = 'int32';
break;
case this.binding.TF_INT64:
console.warn('INT64 output tensor will be stored as BigInt64Array.');
dtype = 'int32';
break;
case this.binding.TF_BOOL:
dtype = 'bool';
break;
case this.binding.TF_COMPLEX64:
dtype = 'complex64';
break;
case this.binding.TF_STRING:
dtype = 'string';
break;
case this.binding.TF_RESOURCE:
dtype = 'string';
break;
case this.binding.TF_UINT8:
dtype = 'int32';
break;
default:
throw new Error("Unknown dtype enum " + metadata.dtype);
}
var tensorInfo = {
dataId: newId, shape: metadata.shape, dtype: dtype
};
return ENGINE.makeTensorFromTensorInfo(tensorInfo);
};
NodeJSKernelBackend.prototype.getInputTensorIds = function (tensors) {
var ids = [];
for (var i = 0; i < tensors.length; i++) {
if (tensors[i] instanceof Int64Scalar) {
var value = tensors[i].valueArray;
var id = this.binding.createTensor([], this.binding.TF_INT64, value);
ids.push(id);
}
else {
var info = this.tensorMap.get(tensors[i].dataId);
if (info.values != null) {
info.id =
this.binding.createTensor(info.shape, info.dtype, info.values);
info.values = null;
}
ids.push(info.id);
}
}
return ids;
};
NodeJSKernelBackend.prototype.createReductionOpAttrs = function (tensor, keepDims) {
if (keepDims === void 0) { keepDims = false; }
return [
{ name: 'keep_dims', type: this.binding.TF_ATTR_BOOL, value: keepDims },
createTensorsTypeOpAttr('T', tensor.dtype),
createTensorsTypeOpAttr('Tidx', 'int32')
];
};
NodeJSKernelBackend.prototype.floatPrecision = function () {
return 32;
};
NodeJSKernelBackend.prototype.epsilon = function () {
return _super.prototype.epsilon.call(this);
};
NodeJSKernelBackend.prototype.executeSingleInput = function (name, input) {
var opAttrs = [createTensorsTypeOpAttr('T', input.dtype)];
return this.executeSingleOutput(name, opAttrs, [input]);
};
NodeJSKernelBackend.prototype.executeSingleOutput = function (name, opAttrs, inputs) {
var outputMetadata = this.binding.executeOp(name, opAttrs, this.getInputTensorIds(inputs), 1);
return this.createOutputTensor(outputMetadata[0]);
};
NodeJSKernelBackend.prototype.executeMultipleOutputs = function (name, opAttrs, inputs, numOutputs) {
var _this = this;
var outputMetadata = this.binding.executeOp(name, opAttrs, this.getInputTensorIds(inputs), numOutputs);
return outputMetadata.map(function (m) { return _this.createOutputTensor(m); });
};
NodeJSKernelBackend.prototype.numDataIds = function () {
return this.tensorMap.numDataIds();
};
NodeJSKernelBackend.prototype.dispose = function () { };
NodeJSKernelBackend.prototype.read = function (dataId) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2, this.readSync(dataId)];
});
});
};
NodeJSKernelBackend.prototype.readSync = function (dataId) {
if (!this.tensorMap.has(dataId)) {
throw new Error("Tensor " + dataId + " was not registered!");
}
var info = this.tensorMap.get(dataId);
if (info.values != null) {
return info.values;
}
else {
return this.binding.tensorDataSync(info.id);
}
};
/**
* Dispose the memory if the dataId has 0 refCount. Return true if the memory
* is released, false otherwise.
* @param dataId
* @oaram force Optional, remove the data regardless of refCount
*/
NodeJSKernelBackend.prototype.disposeData = function (dataId, force) {
if (force === void 0) { force = false; }
// No-op if already disposed.
if (this.tensorMap.has(dataId)) {
var id = this.tensorMap.get(dataId).id;
this.tensorMap.get(dataId).refCount--;
if (!force && this.tensorMap.get(dataId).refCount > 0) {
return false;
}
if (id != null && id >= 0) {
this.binding.deleteTensor(id);
}
this.tensorMap.delete(dataId);
}
return true;
};
/** Return refCount of a `TensorData`. */
NodeJSKernelBackend.prototype.refCount = function (dataId) {
if (this.tensorMap.has(dataId)) {
var tensorData = this.tensorMap.get(dataId);
return tensorData.refCount;
}
return 0;
};
NodeJSKernelBackend.prototype.incRef = function (dataId) {
this.tensorMap.get(dataId).refCount++;
};
NodeJSKernelBackend.prototype.move = function (dataId, values, shape, dtype, refCount) {
this.tensorMap.set(dataId, { shape: shape, dtype: getTFDType(dtype), values: values, id: -1, refCount: refCount });
};
NodeJSKernelBackend.prototype.write = function (values, shape, dtype) {
var dataId = {};
this.move(dataId, values, shape, dtype, 1);
return dataId;
};
NodeJSKernelBackend.prototype.applyActivation = function (input, activation, preluActivationWeights, leakyreluAlpha) {
var result = input;
if (activation != null) {
if (activation === 'linear') {
// No-op
}
else if (activation === 'relu') {
result = tf.relu(result);
}
else if (activation === 'prelu') {
result = tf.prelu(result, preluActivationWeights);
}
else if (activation === 'leakyrelu') {
result = tf.leakyRelu(result, leakyreluAlpha);
}
else if (activation === 'elu') {
result = tf.elu(result);
}
else if (activation === 'relu6') {
result = tf.relu6(result);
}
else if (activation === 'sigmoid') {
result = tf.sigmoid(result);
}
else {
throw new Error("Activation: " + activation + " has not been implemented for the Node.js backend");
}
}
return result;
};
NodeJSKernelBackend.prototype.divide = function (a, b) {
var opAttrs = [createTensorsTypeOpAttr('T', tfjs_1.backend_util.upcastType(a.dtype, b.dtype))];
return this.executeSingleOutput('Div', opAttrs, [a, b]);
};
NodeJSKernelBackend.prototype.divNoNan = function (a, b) {
var opAttrs = [createTensorsTypeOpAttr('T', tfjs_1.backend_util.upcastType(a.dtype, b.dtype))];
return this.executeSingleOutput('DivNoNan', opAttrs, [a, b]);
};
NodeJSKernelBackend.prototype.where = function (condition) {
return this.executeSingleOutput('Where', [], [condition]);
};
NodeJSKernelBackend.prototype.topKValues = function (x, k) {
throw new Error('Method not implemented.');
};
NodeJSKernelBackend.prototype.topKIndices = function (x, k) {
throw new Error('Method not implemented.');
};
NodeJSKernelBackend.prototype.int = function (x) {
throw new Error('Method not implemented.');
};
NodeJSKernelBackend.prototype.decodeJpeg = function (contents, channels, ratio, fancyUpscaling, tryRecoverTruncated, acceptableFraction, dctMethod) {
var opAttrs = [
{ name: 'channels', type: this.binding.TF_ATTR_INT, value: channels },
{ name: 'ratio', type: this.binding.TF_ATTR_INT, value: ratio }, {
name: 'fancy_upscaling',
type: this.binding.TF_ATTR_BOOL,
value: fancyUpscaling
},
{
name: 'try_recover_truncated',
type: this.binding.TF_ATTR_BOOL,
value: tryRecoverTruncated
},
{
name: 'acceptable_fraction',
type: this.binding.TF_ATTR_FLOAT,
value: acceptableFraction
},
{ name: 'dct_method', type: this.binding.TF_ATTR_STRING, value: dctMethod }
];
var inputArgs = [scalar(contents, 'string')];
return this.executeSingleOutput('DecodeJpeg', opAttrs, inputArgs);
};
NodeJSKernelBackend.prototype.decodePng = function (contents, channels) {
var opAttrs = [{ name: 'channels', type: this.binding.TF_ATTR_INT, value: channels }];
var inputArgs = [scalar(contents, 'string')];
return this.executeSingleOutput('DecodePng', opAttrs, inputArgs);
};
NodeJSKernelBackend.prototype.decodeBmp = function (contents, channels) {
var opAttrs = [{ name: 'channels', type: this.binding.TF_ATTR_INT, value: channels }];
var inputArgs = [scalar(contents, 'string')];
return this.executeSingleOutput('DecodeBmp', opAttrs, inputArgs);
};
NodeJSKernelBackend.prototype.decodeGif = function (contents) {
var inputArgs = [scalar(contents, 'string')];
return this.executeSingleOutput('DecodeGif', [], inputArgs);
};
NodeJSKernelBackend.prototype.executeEncodeImageOp = function (name, opAttrs, imageData, imageShape) {
var inputTensorId = this.binding.createTensor(imageShape, this.binding.TF_UINT8, imageData);
var outputMetadata = this.binding.executeOp(name, opAttrs, [inputTensorId], 1);
var outputTensorInfo = outputMetadata[0];
outputTensorInfo.dtype = this.binding.TF_UINT8;
return this.createOutputTensor(outputTensorInfo);
};
NodeJSKernelBackend.prototype.encodeJpeg = function (imageData, imageShape, format, quality, progressive, optimizeSize, chromaDownsampling, densityUnit, xDensity, yDensity, xmpMetadata) {
var opAttrs = [
{ name: 'format', type: this.binding.TF_ATTR_STRING, value: format },
{ name: 'quality', type: this.binding.TF_ATTR_INT, value: quality }, {
name: 'progressive',
type: this.binding.TF_ATTR_BOOL,
value: progressive
},
{
name: 'optimize_size',
type: this.binding.TF_ATTR_BOOL,
value: optimizeSize
},
{
name: 'chroma_downsampling',
type: this.binding.TF_ATTR_BOOL,
value: chromaDownsampling
},
{
name: 'density_unit',
type: this.binding.TF_ATTR_STRING,
value: densityUnit
},
{ name: 'x_density', type: this.binding.TF_ATTR_INT, value: xDensity },
{ name: 'y_density', type: this.binding.TF_ATTR_INT, value: yDensity }, {
name: 'xmp_metadata',
type: this.binding.TF_ATTR_STRING,
value: xmpMetadata
}
];
return this.executeEncodeImageOp('EncodeJpeg', opAttrs, imageData, imageShape);
};
NodeJSKernelBackend.prototype.encodePng = function (imageData, imageShape, compression) {
var opAttrs = [
{ name: 'compression', type: this.binding.TF_ATTR_INT, value: compression }
];
return this.executeEncodeImageOp('EncodePng', opAttrs, imageData, imageShape);
};
NodeJSKernelBackend.prototype.deleteSavedModel = function (id) {
this.binding.deleteSavedModel(id);
};
NodeJSKernelBackend.prototype.loadSavedModelMetaGraph = function (path, tags) {
return this.binding.loadSavedModel(path, tags);
};
NodeJSKernelBackend.prototype.getMappedInputTensorIds = function (inputs, inputTensorInfos) {
var tensorIds = this.getInputTensorIds(inputs);
for (var i = 0; i < inputs.length; i++) {
if (inputTensorInfos[i] != null) {
if (inputTensorInfos[i].tfDtype === 'DT_UINT8') {
var data = Uint8Array.from(inputs[i].dataSync());
var inputTensorId = this.binding.createTensor(inputs[i].shape, this.binding.TF_UINT8, data);
tensorIds[i] = inputTensorId;
}
else if (inputTensorInfos[i].tfDtype === 'DT_INT64') {
var data = encodeInt32ArrayAsInt64(inputs[i].dataSync());
var inputTensorId = this.binding.createTensor(inputs[i].shape, this.binding.TF_INT64, data);
tensorIds[i] = inputTensorId;
}
}
}
return tensorIds;
};
NodeJSKernelBackend.prototype.runSavedModel = function (id, inputs, inputTensorInfos, outputOpNames) {
var _this = this;
var outputMetadata = this.binding.runSavedModel(id, this.getMappedInputTensorIds(inputs, inputTensorInfos), inputTensorInfos.map(function (info) { return info.name; }).join(','), outputOpNames.join(','));
return outputMetadata.map(function (m) { return _this.createOutputTensor(m); });
};
NodeJSKernelBackend.prototype.summaryWriter = function (logdir) {
var opAttrs = [
{
name: 'shared_name',
type: this.binding.TF_ATTR_STRING,
value: "logdir:" + logdir
},
{ name: 'container', type: this.binding.TF_ATTR_STRING, value: '' }
];
var writerResource = this.executeSingleOutput('SummaryWriter', opAttrs, []);
return writerResource;
};
NodeJSKernelBackend.prototype.createSummaryFileWriter = function (resourceHandle, logdir, maxQueue, flushMillis, filenameSuffix) {
var inputArgs = [
resourceHandle, scalar(logdir),
scalar(maxQueue == null ? 10 : maxQueue, 'int32'),
scalar(flushMillis == null ? 2 * 60 * 1000 : flushMillis, 'int32'),
scalar(filenameSuffix == null ? '.v2' : filenameSuffix)
];
this.executeMultipleOutputs('CreateSummaryFileWriter', [], inputArgs, 0);
};
NodeJSKernelBackend.prototype.writeScalarSummary = function (resourceHandle, step, name, value) {
var _this = this;
tidy(function () {
assert(Number.isInteger(step), function () { return "step is expected to be an integer, but is instead " + step; });
var inputArgs = [resourceHandle, new Int64Scalar(step), scalar(name, 'string')];
var typeAttr;
if (typeof value === 'number') {
inputArgs.push(scalar(value));
typeAttr = _this.binding.TF_FLOAT;
}
else {
assert(value.rank === 0, function () {
return "A non-scalar tensor (rank " + value.rank + ") is passed to writeScalarSummary()";
});
inputArgs.push(value);
typeAttr = _this.typeAttributeFromTensor(value);
}
var opAttrs = [{ name: 'T', type: _this.binding.TF_ATTR_TYPE, value: typeAttr }];
_this.binding.executeOp('WriteScalarSummary', opAttrs, _this.getInputTensorIds(inputArgs), 0);
});
};
NodeJSKernelBackend.prototype.writeHistogramSummary = function (resourceHandle, step, name, data, bucketCount, description) {
var _this = this;
tidy(function () {
assert(Number.isInteger(step), function () { return "step is expected to be an integer, but is instead " + step; });
var content = new messages.HistogramPluginData().setVersion(0);
var pluginData = new messages.SummaryMetadata.PluginData()
.setPluginName('histograms')
.setContent(content.serializeBinary());
var summary = new messages.SummaryMetadata()
.setPluginData(pluginData)
.setDisplayName(null)
.setSummaryDescription(description);
var summaryTensor = scalar(summary.serializeBinary(), 'string');
var nameTensor = scalar(name, 'string');
var stepScalar = new Int64Scalar(step);
var buckets = _this.buckets(data, bucketCount);
assert(buckets.rank === 2 && buckets.shape[1] === 3, function () { return "Expected buckets to have shape [k, 3], but they had shape " + buckets.shape; });
assert(buckets.dtype === 'float32', function () { return "Expected buckets to have dtype float32, but they had dtype " + buckets.dtype; });
var inputArgs = [resourceHandle, stepScalar, buckets, nameTensor, summaryTensor];
var typeAttr = _this.typeAttributeFromTensor(buckets);
var opAttrs = [{ name: 'T', type: _this.binding.TF_ATTR_TYPE, value: typeAttr }];
_this.binding.executeOp('WriteSummary', opAttrs, _this.getInputTensorIds(inputArgs), 0);
});
};
NodeJSKernelBackend.prototype.flushSummaryWriter = function (resourceHandle) {
var inputArgs = [resourceHandle];
this.executeMultipleOutputs('FlushSummaryWriter', [], inputArgs, 0);
};
NodeJSKernelBackend.prototype.buckets = function (data, bucketCount) {
if (data.size === 0) {
return tensor([], [0, 3], 'float32');
}
bucketCount = bucketCount !== undefined ? bucketCount : 30;
assert(Number.isInteger(bucketCount) && bucketCount > 0, function () {
return "Expected bucket count to be a strictly positive integer, but it was " + ("" + bucketCount);
});
data = data.flatten();
data = data.cast('float32');
var min = data.min();
var max = data.max();
var range = max.sub(min);
var isSingular = range.equal(0).arraySync() !== 0;
if (isSingular) {
var center = min;
var bucketStart = center.sub(0.5);
var bucketEnd = center.add(0.5);
var bucketCounts_1 = scalar(data.size, 'float32');
return tf.concat([bucketStart, bucketEnd, bucketCounts_1]).reshape([1, 3]);
}
var bucketWidth = range.div(bucketCount);
var offsets = data.sub(min);
var bucketIndices = offsets.floorDiv(bucketWidth).cast('int32');
var clampedIndices = tf.minimum(bucketIndices, bucketCount - 1).cast('int32');
var oneHots = tf.oneHot(clampedIndices, bucketCount);
var bucketCounts = oneHots.sum(0).cast('int32');
var edges = tf.linspace(min.arraySync(), max.arraySync(), bucketCount + 1);
edges = tf.concat([edges.slice(0, bucketCount), max.reshape([1])], 0);
var leftEdges = edges.slice(0, bucketCount);
var rightEdges = edges.slice(1, bucketCount);
return tf.stack([leftEdges, rightEdges, bucketCounts.cast('float32')])
.transpose();
};
NodeJSKernelBackend.prototype.memory = function () {
return { unreliable: true };
};
NodeJSKernelBackend.prototype.time = function (f) {
return __awaiter(this, void 0, void 0, function () {
var start, elapsed;
return __generator(this, function (_a) {
start = process.hrtime();
f();
elapsed = process.hrtime(start);
return [2, { kernelMs: elapsed[0] * 1000 + elapsed[1] / 1000000 }];
});
});
};
NodeJSKernelBackend.prototype.getNumOfSavedModels = function () {
return this.binding.getNumOfSavedModels();
};
return NodeJSKernelBackend;
}());
class Variable extends Tensor {
constructor(initialValue, trainable, name, tensorId) {
super(initialValue.shape, initialValue.dtype, initialValue.dataId, tensorId);
this.trainable = trainable;
this.name = name;
}
/**
* Assign a new `tf.Tensor` to this variable. The new `tf.Tensor` must have
* the same shape and dtype as the old `tf.Tensor`.
*
* @param newValue New tensor to be assigned to this variable.
*
* @doc {heading: 'Tensors', subheading: 'Classes'}
*/
assign(newValue) {
if (newValue.dtype !== this.dtype) {
throw new Error(`dtype of the new value (${newValue.dtype}) and ` +
`previous value (${this.dtype}) must match`);
}
if (!util.arraysEqual(newValue.shape, this.shape)) {
throw new Error(`shape of the new value (${newValue.shape}) and ` +
`previous value (${this.shape}) must match`);
}
trackerFn().disposeTensor(this);
this.dataId = newValue.dataId;
trackerFn().incRef(this, null /* backend */);
}
dispose() {
trackerFn().disposeVariable(this);
this.isDisposedInternal = true;
}
}
var path = require("path");
var binary = require('@mapbox/node-pre-gyp');
var bindingPath = binary.find(path.resolve(path.join(__dirname, '/config.json')));
if (!fs.existsSync(bindingPath)) {
throw new Error("The Node.js native addon module (tfjs_binding.node) can not be found at path: " + String(bindingPath) + ". \nPlease run command " +
"'npm rebuild @tensorflow/tfjs-node" + (String(bindingPath).indexOf('tfjs-node-gpu') > 0 ? "-gpu" : "") + " --build-addon-from-source' to " +
"rebuild the native addon module. \nIf you have problem with building the addon module, " +
"please check https://github.com/tensorflow/tfjs/blob/master/tfjs-node/WINDOWS_TROUBLESHOOTING.md or file an issue.");
}
var bindings = require(bindingPath);
/**
* @type {Engine}
*/
const ENGINE = getOrMakeEngine();
registerBackend('tensorflow', function () {
return new NodeJSKernelBackend(bindings, '@tensorflow/tfjs-node');
}, 3);
var success = ENGINE.setBackend('tensorflow');
if (!success) {
throw new Error("Could not initialize Mini TensorFlow backend.");
}
function registerBackend(name, factory, priority = 1) {
return ENGINE.registerBackend(name, factory, priority);
}
function nodeBackend() {
return ENGINE.findBackend('tensorflow');
}
function ones(shape) {
const values = makeOnesTypedArray(sizeFromShape(shape), 'float32');
return ENGINE.makeTensor(values, shape, 'float32');
}
function tidy(nameOrFn, fn) {
return ENGINE.tidy(nameOrFn, fn);
}
function makeOnesTypedArray(size, dtype) {
const array = makeZerosTypedArray(size, dtype);
for (let i = 0; i < array.length; i++) {
array[i] = 1;
}
return array;
}
module.exports = {
loadSavedModel: loadSavedModel,
tensor: tensor
};