UNPKG

@tensorflow/tfjs-backend-wasm

Version:

This package adds a WebAssembly backend to TensorFlow.js. It currently supports the following models from our [models](https://github.com/tensorflow/tfjs-models) repo: - BlazeFace - BodyPix - CocoSSD - Face landmarks detection - HandPose - KNN classifier

79 lines 12.5 kB
/** * @license * Copyright 2019 Google LLC. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ============================================================================= */ import { _FusedMatMul, broadcast_util } from '@tensorflow/tfjs-core'; import { FusableActivation } from './types'; let wasmFusedMatMul; function setup(backend) { wasmFusedMatMul = backend.wasm.cwrap(_FusedMatMul, null /* void */, [ 'number', 'array', 'number', 'number', 'array', 'number', 'number', 'number', 'number', 'number', 'number', 'number', 'number' // out_id ]); } function fusedBatchMatMul(args) { const { inputs, backend, attrs } = args; const { a, b, bias, preluActivationWeights } = inputs; if (a.dtype !== 'float32' || b.dtype !== 'float32') { throw new Error(`_FusedMatMul for non non-float32 tensors not yet supported.`); } const { transposeA, transposeB, activation, leakyreluAlpha } = attrs; const aId = backend.dataIdMap.get(a.dataId).id; const bId = backend.dataIdMap.get(b.dataId).id; let biasId = 0; if (bias != null) { const biasData = backend.dataIdMap.get(bias.dataId); if (biasData.shape.length !== 1) { throw new Error(`_FusedMatMul only supports rank-1 bias but got ` + `rank ${biasData.shape.length}.`); } biasId = biasData.id; } const preluActivationWeightsId = preluActivationWeights == null ? 0 : backend.dataIdMap.get(preluActivationWeights.dataId).id; const fusedActivation = FusableActivation[activation]; if (fusedActivation == null) { throw new Error(`${activation} activation not yet supported for FusedConv2D ` + `in the wasm backend.`); } const leftDim = transposeA ? a.shape[2] : a.shape[1]; const rightDim = transposeB ? b.shape[1] : b.shape[2]; const batchDims = broadcast_util.assertAndGetBroadcastShape(a.shape.slice(0, -2), b.shape.slice(0, -2)); const out = backend.makeOutput([...batchDims, leftDim, rightDim], a.dtype); const outId = backend.dataIdMap.get(out.dataId).id; const aShapeBytes = new Uint8Array(new Int32Array(a.shape).buffer); const bShapeBytes = new Uint8Array(new Int32Array(b.shape).buffer); wasmFusedMatMul(aId, aShapeBytes, a.shape.length, bId, bShapeBytes, b.shape.length, transposeA, transposeB, fusedActivation, biasId, preluActivationWeightsId, leakyreluAlpha || 0, outId); return out; } export const _fusedMatMulConfig = { kernelName: _FusedMatMul, backendName: 'wasm', setupFunc: setup, kernelFunc: fusedBatchMatMul }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiX0Z1c2VkTWF0TXVsLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vdGZqcy1iYWNrZW5kLXdhc20vc3JjL2tlcm5lbHMvX0Z1c2VkTWF0TXVsLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7R0FlRztBQUVILE9BQU8sRUFBQyxZQUFZLEVBQXlDLGNBQWMsRUFBMkIsTUFBTSx1QkFBdUIsQ0FBQztBQUlwSSxPQUFPLEVBQUMsaUJBQWlCLEVBQUMsTUFBTSxTQUFTLENBQUM7QUFFMUMsSUFBSSxlQUtRLENBQUM7QUFFYixTQUFTLEtBQUssQ0FBQyxPQUFvQjtJQUNqQyxlQUFlLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsWUFBWSxFQUFFLElBQUksQ0FBQyxVQUFVLEVBQUU7UUFDbEUsUUFBUTtRQUNSLE9BQU87UUFDUCxRQUFRO1FBQ1IsUUFBUTtRQUNSLE9BQU87UUFDUCxRQUFRO1FBQ1IsUUFBUTtRQUNSLFFBQVE7UUFDUixRQUFRO1FBQ1IsUUFBUTtRQUNSLFFBQVE7UUFDUixRQUFRO1FBQ1IsUUFBUSxDQUFHLFNBQVM7S0FDckIsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVELFNBQVMsZ0JBQWdCLENBQUMsSUFJekI7SUFDQyxNQUFNLEVBQUMsTUFBTSxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUMsR0FBRyxJQUFJLENBQUM7SUFDdEMsTUFBTSxFQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsSUFBSSxFQUFFLHNCQUFzQixFQUFDLEdBQUcsTUFBTSxDQUFDO0lBRXBELElBQUksQ0FBQyxDQUFDLEtBQUssS0FBSyxTQUFTLElBQUksQ0FBQyxDQUFDLEtBQUssS0FBSyxTQUFTLEVBQUU7UUFDbEQsTUFBTSxJQUFJLEtBQUssQ0FDWCw2REFBNkQsQ0FBQyxDQUFDO0tBQ3BFO0lBRUQsTUFBTSxFQUFDLFVBQVUsRUFBRSxVQUFVLEVBQUUsVUFBVSxFQUFFLGNBQWMsRUFB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RWhDLE9BQU8sR0FBRyxDQUFDO0FBQ2IsQ0FBQztBQUVELE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUFpQjtJQUM5QyxVQUFVLEVBQUUsWUFBWTtJQUN4QixXQUFXLEVBQUUsTUFBTTtJQUNuQixTQUFTLEVBQUUsS0FBSztJQUNoQixVQUFVLEVBQUUsZ0JBQXlDO0NBQ3RELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEBsaWNlbnNlXG4gKiBDb3B5cmlnaHQgMjAxOSBHb29nbGUgTExDLiBBbGwgUmlnaHRzIFJlc2VydmVkLlxuICogTGljZW5zZWQgdW5kZXIgdGhlIEFwYWNoZSBMaWNlbnNlLCBWZXJzaW9uIDIuMCAodGhlIFwiTGljZW5zZVwiKTtcbiAqIHlvdSBtYXkgbm90IHVzZSB0aGlzIGZpbGUgZXhjZXB0IGluIGNvbXBsaWFuY2Ugd2l0aCB0aGUgTGljZW5zZS5cbiAqIFlvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdFxuICpcbiAqIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMFxuICpcbiAqIFVubGVzcyByZXF1aXJlZCBieSBhcHBsaWNhYmxlIGxhdyBvciBhZ3JlZWQgdG8gaW4gd3JpdGluZywgc29mdHdhcmVcbiAqIGRpc3RyaWJ1dGVkIHVuZGVyIHRoZSBMaWNlbnNlIGlzIGRpc3RyaWJ1dGVkIG9uIGFuIFwiQVMgSVNcIiBCQVNJUyxcbiAqIFdJVEhPVVQgV0FSUkFOVElFUyBPUiBDT05ESVRJT05TIE9GIEFOWSBLSU5ELCBlaXRoZXIgZXhwcmVzcyBvciBpbXBsaWVkLlxuICogU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZFxuICogbGltaXRhdGlvbnMgdW5kZXIgdGhlIExpY2Vuc2UuXG4gKiA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuICovXG5cbmltcG9ydCB7X0Z1c2VkTWF0TXVsLCBfRnVzZWRNYXRNdWxBdHRycywgX0Z1c2VkTWF0TXVsSW5wdXRzLCBicm9hZGNhc3RfdXRpbCwgS2VybmVsQ29uZmlnLCBLZXJuZWxGdW5jfSBmcm9tICdAdGVuc29yZmxvdy90ZmpzLWNvcmUnO1xuXG5pbXBvcnQge0JhY2tlbmRXYXNtfSBmcm9tICcuLi9iYWNrZW5kX3dhc20nO1xuXG5pbXBvcnQge0Z1c2FibGVBY3RpdmF0aW9ufSBmcm9tICcuL3R5cGVzJztcblxubGV0IHdhc21GdXNlZE1hdE11bDpcbiAgICAoYUlkOiBudW1iZXIsIGFTaGFwZTogVWludDhBcnJheSwgYVNoYXBlU2l6ZTogbnVtYmVyLCBiSWQ6IG51bWJlcixcbiAgICAgYlNoYXBlOiBVaW50OEFycmF5LCBiU2hhcGVTaXplOiBudW1iZXIsIHRyYW5zcG9zZUE6IGJvb2xlYW4sXG4gICAgIHRyYW5zcG9zZUI6IGJvb2xlYW4sIGFjdGl2YXRpb246IG51bWJlciwgYmlhc0lkOiBudW1iZXIsXG4gICAgIHByZWx1QWN0aXZhdGlvbldlaWdodHNJZDogbnVtYmVyLCBsZWFreXJlbHVBbHBoYTogbnVtYmVyLCBvdXRJZDogbnVtYmVyKSA9PlxuICAgICAgICB2b2lkO1xuXG5mdW5jdGlvbiBzZXR1cChiYWNrZW5kOiBCYWNrZW5kV2FzbSkge1xuICB3YXNtRnVzZWRNYXRNdWwgPSBiYWNrZW5kLndhc20uY3dyYXAoX0Z1c2VkTWF0TXVsLCBudWxsIC8qIHZvaWQgKi8sIFtcbiAgICAnbnVtYmVyJywgIC8vIGFfaWRcbiAgICAnYXJyYXknLCAgIC8vIGFfc2hhcGVcbiAgICAnbnVtYmVyJywgIC8vIGFfc2hhcGUubGVuZ3RoXG4gICAgJ251bWJlcicsICAvLyBiX2lkXG4gICAgJ2FycmF5JywgICAvLyBiX3NoYXBlXG4gICAgJ251bWJlcicsICAvLyBiX3NoYXBlLmxlbmd0aFxuICAgICdudW1iZXInLCAgLy8gdHJhbnNwb3NlX2FcbiAgICAnbnVtYmVyJywgIC8vIHRyYW5zcG9zZV9iXG4gICAgJ251bWJlcicsICAvLyBhY3RpdmF0aW9uXG4gICAgJ251bWJlcicsICAvLyBiaWFzSWRcbiAgICAnbnVtYmVyJywgIC8vIHByZWx1QWN0aXZhdGlvbldlaWdodHNJZFxuICAgICdudW1iZXInLCAgLy8gbGVha3lyZWx1QWxwaGFcbiAgICAnbnVtYmVyJyAgIC8vIG91dF9pZFxuICBdKTtcbn1cblxuZnVuY3Rpb24gZnVzZWRCYXRjaE1hdE11bChhcmdzOiB7XG4gIGlucHV0czogX0Z1c2VkTWF0TXVsSW5wdXRzLFxuICBiYWNrZW5kOiBCYWNrZW5kV2FzbSxcbiAgYXR0cnM6IF9GdXNlZE1hdE11bEF0dHJzXG59KSB7XG4gIGNvbnN0IHtpbnB1dHMsIGJhY2tlbmQsIGF0dHJzfSA9IGFyZ3M7XG4gIGNvbnN0IHthLCBiLCBiaWFzLCBwcmVsdUFjdGl2YXRpb25XZWlnaHRzfSA9IGlucHV0cztcblxuICBpZiAoYS5kdHlwZSAhPT0gJ2Zsb2F0MzInIHx8IGIuZHR5cGUgIT09ICdmbG9hdDMyJykge1xuICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgYF9GdXNlZE1hdE11bCBmb3Igbm9uIG5vbi1mbG9hdDMyIHRlbnNvcnMgbm90IHlldCBzdXBwb3J0ZWQuYCk7XG4gIH1cblxuICBjb25zdCB7dHJhbnNwb3NlQSwgdHJhbnNwb3NlQiwgYWN0aXZhdGlvbiwgbGVha3lyZWx1QWxwaGF9ID0gYXR0cnM7XG4gIGNvbnN0IGFJZCA9IGJhY2tlbmQuZGF0YUlkTWFwLmdldChhLmRhdGFJZCkuaWQ7XG4gIGNvbnN0IGJJZCA9IGJhY2tlbmQuZGF0YUlkTWFwLmdldChiLmRhdGFJZCkuaWQ7XG5cbiAgbGV0IGJpYXNJZCA9IDA7XG4gIGlmIChiaWFzICE9IG51bGwpIHtcbiAgICBjb25zdCBiaWFzRGF0YSA9IGJhY2tlbmQuZGF0YUlkTWFwLmdldChiaWFzLmRhdGFJZCk7XG4gICAgaWYgKGJpYXNEYXRhLnNoYXBlLmxlbmd0aCAhPT0gMSkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgIGBfRnVzZWRNYXRNdWwgb25seSBzdXBwb3J0cyByYW5rLTEgYmlhcyBidXQgZ290IGAgK1xuICAgICAgICAgIGByYW5rICR7Ymlhc0RhdGEuc2hhcGUubGVuZ3RofS5gKTtcbiAgICB9XG4gICAgYmlhc0lkID0gYmlhc0RhdGEuaWQ7XG4gIH1cbiAgY29uc3QgcHJlbHVBY3RpdmF0aW9uV2VpZ2h0c0lkID0gcHJlbHVBY3RpdmF0aW9uV2VpZ2h0cyA9PSBudWxsID9cbiAgICAgIDAgOlxuICAgICAgYmFja2VuZC5kYXRhSWRNYXAuZ2V0KHByZWx1QWN0aXZhdGlvbldlaWdodHMuZGF0YUlkKS5pZDtcbiAgY29uc3QgZnVzZWRBY3RpdmF0aW9uID1cbiAgICAgIEZ1c2FibGVBY3RpdmF0aW9uW2FjdGl2YXRpb24gYXMgdW5rbm93biBhc1xuICAgICAgICAgICAgICAgICAgICAgICAga2V5b2YgdHlwZW9mIEZ1c2FibGVBY3RpdmF0aW9uXTtcbiAgaWYgKGZ1c2VkQWN0aXZhdGlvbiA9PSBudWxsKSB7XG4gICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICBgJHthY3RpdmF0aW9ufSBhY3RpdmF0aW9uIG5vdCB5ZXQgc3VwcG9ydGVkIGZvciBGdXNlZENvbnYyRCBgICtcbiAgICAgICAgYGluIHRoZSB3YXNtIGJhY2tlbmQuYCk7XG4gIH1cblxuICBjb25zdCBsZWZ0RGltID0gdHJhbnNwb3NlQSA/IGEuc2hhcGVbMl0gOiBhLnNoYXBlWzFdO1xuICBjb25zdCByaWdodERpbSA9IHRyYW5zcG9zZUIgPyBiLnNoYXBlWzFdIDogYi5zaGFwZVsyXTtcbiAgY29uc3QgYmF0Y2hEaW1zID0gYnJvYWRjYXN0X3V0aWwuYXNzZXJ0QW5kR2V0QnJvYWRjYXN0U2hhcGUoXG4gICAgICBhLnNoYXBlLnNsaWNlKDAsIC0yKSwgYi5zaGFwZS5zbGljZSgwLCAtMikpO1xuXG4gIGNvbnN0IG91dCA9IGJhY2tlbmQubWFrZU91dHB1dChbLi4uYmF0Y2hEaW1zLCBsZWZ0RGltLCByaWdodERpbV0sIGEuZHR5cGUpO1xuICBjb25zdCBvdXRJZCA9IGJhY2tlbmQuZGF0YUlkTWFwLmdldChvdXQuZGF0YUlkKS5pZDtcblxuICBjb25zdCBhU2hhcGVCeXRlcyA9IG5ldyBVaW50OEFycmF5KG5ldyBJbnQzMkFycmF5KGEuc2hhcGUpLmJ1ZmZlcik7XG4gIGNvbnN0IGJTaGFwZUJ5dGVzID0gbmV3IFVpbnQ4QXJyYXkobmV3IEludDMyQXJyYXkoYi5zaGFwZSkuYnVmZmVyKTtcblxuICB3YXNtRnVzZWRNYXRNdWwoXG4gICAgICBhSWQsIGFTaGFwZUJ5dGVzLCBhLnNoYXBlLmxlbmd0aCwgYklkLCBiU2hhcGVCeXRlcywgYi5zaGFwZS5sZW5ndGgsXG4gICAgICB0cmFuc3Bvc2VBLCB0cmFuc3Bvc2VCLCBmdXNlZEFjdGl2YXRpb24sIGJpYXNJZCwgcHJlbHVBY3RpdmF0aW9uV2VpZ2h0c0lkLFxuICAgICAgbGVha3lyZWx1QWxwaGEgfHwgMCwgb3V0SWQpO1xuXG4gIHJldHVybiBvdXQ7XG59XG5cbmV4cG9ydCBjb25zdCBfZnVzZWRNYXRNdWxDb25maWc6IEtlcm5lbENvbmZpZyA9IHtcbiAga2VybmVsTmFtZTogX0Z1c2VkTWF0TXVsLFxuICBiYWNrZW5kTmFtZTogJ3dhc20nLFxuICBzZXR1cEZ1bmM6IHNldHVwLFxuICBrZXJuZWxGdW5jOiBmdXNlZEJhdGNoTWF0TXVsIGFzIHVua25vd24gYXMgS2VybmVsRnVuY1xufTtcbiJdfQ==