'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 };