diff --git a/code/main.js b/code/main.js index f3e7bcd8..40478171 100644 --- a/code/main.js +++ b/code/main.js @@ -1,10 +1,7 @@ import {samplesRepo} from './samples_repo.js'; +import {sizeOfShape} from '../common/utils.js'; -window.sizeOfShape = function(shape) { - return shape.reduce((a, b) => { - return a * b; - }); -}; +window.sizeOfShape = sizeOfShape; export function main() { const selectElement = document.getElementById('example-select'); diff --git a/code/samples/dynamic_shape.js b/code/samples/dynamic_shape.js index 8e54f6c8..04575654 100644 --- a/code/samples/dynamic_shape.js +++ b/code/samples/dynamic_shape.js @@ -1,28 +1,29 @@ const context = navigator.ml.createContext(); -// Create a model with dynamic shaped inputs. +// Create a graph with dynamic shaped inputs. const builder = new MLGraphBuilder(context); const descA = {type: 'float32', dimensions: [-1, 4]}; const a = builder.input('a', descA); const descB = {type: 'float32', dimensions: [4, -1]}; const b = builder.input('b', descB); const c = builder.matmul(a, b); +const graph = builder.build({'c': c}); -const graph = await builder.build({c}); - -async function compute(shapeA, shapeB) { +function compute(shapeA, shapeB, shapeC) { const bufferA = new Float32Array(sizeOfShape(shapeA)).fill(0.5); const bufferB = new Float32Array(sizeOfShape(shapeB)).fill(0.5); + const bufferC = new Float32Array(sizeOfShape(shapeC)); // Specify the shape of inputs when computing. const inputs = { - 'a': {data: bufferA, dimensions: shapeA}, - 'b': {data: bufferB, dimensions: shapeB}, + 'a': {resource: bufferA, dimensions: shapeA}, + 'b': {resource: bufferB, dimensions: shapeB}, }; - const outputs = await graph.compute(inputs); - console.log(`shape: [${outputs.c.dimensions}], values: ${outputs.c.data}`); + const outputs = {'c': bufferC}; + graph.compute(inputs, outputs); + console.log(`values: ${bufferC}`); } -await compute([3, 4], [4, 3]); -await compute([4, 4], [4, 4]); -await compute([5, 4], [4, 5]); +compute([3, 4], [4, 3], [3, 3]); +compute([4, 4], [4, 4], [4, 4]); +compute([5, 4], [4, 5], [5, 5]); diff --git a/code/samples/matmul.js b/code/samples/matmul.js index 574f2508..94225df9 100644 --- a/code/samples/matmul.js +++ b/code/samples/matmul.js @@ -10,9 +10,10 @@ const bufferB = new Float32Array(sizeOfShape(descB.dimensions)).fill(0.5); const b = builder.constant(descB, bufferB); const c = builder.matmul(a, b); -const graph = await builder.build({c}); +const graph = builder.build({c}); const bufferA = new Float32Array(sizeOfShape(descA.dimensions)).fill(0.5); -const inputs = {'a': {data: bufferA}}; -const outputs = await graph.compute(inputs); -console.log(`shape: [${outputs.c.dimensions}]`); -console.log(`values: ${outputs.c.data}`); +const bufferC = new Float32Array(sizeOfShape([3, 3])); +const inputs = {'a': bufferA}; +const outputs = {'c': bufferC}; +graph.compute(inputs, outputs); +console.log(`values: ${bufferC}`); diff --git a/code/samples/mul_add.js b/code/samples/mul_add.js index 9ac6dbd3..3abc82bf 100644 --- a/code/samples/mul_add.js +++ b/code/samples/mul_add.js @@ -1,19 +1,20 @@ const operandType = {type: 'float32', dimensions: [2, 2]}; const context = navigator.ml.createContext(); const builder = new MLGraphBuilder(context); -// 1. Create a model of the computational graph 'C = 0.2 * A + B'. +// 1. Create a computational graph 'C = 0.2 * A + B'. const constant = builder.constant(0.2); const A = builder.input('A', operandType); const B = builder.input('B', operandType); const C = builder.add(builder.mul(A, constant), B); -// 2. Build the model into executable. -const graph = await builder.build({'C': C}); -// 3. Bind inputs to the model and execute for the result. +// 2. Build the graph into an executable. +const graph = builder.build({'C': C}); +// 3. Bind inputs to the graph and execute for the result. const bufferA = new Float32Array(4).fill(1.0); const bufferB = new Float32Array(4).fill(0.8); -const inputs = {'A': {data: bufferA}, 'B': {data: bufferB}}; -const outputs = await graph.compute(inputs); +const bufferC = new Float32Array(4); +const inputs = {'A': bufferA, 'B': bufferB}; +const outputs = {'C': bufferC}; +graph.compute(inputs, outputs); // The computed result of [[1, 1], [1, 1]] is in the buffer associated with // the output operand. -console.log('Output shape: ' + outputs.C.dimensions); -console.log('Output value: ' + outputs.C.data); +console.log('Output value: ' + bufferC); diff --git a/code/samples/optional_outputs.js b/code/samples/optional_outputs.js index 8e666f09..54f278a2 100644 --- a/code/samples/optional_outputs.js +++ b/code/samples/optional_outputs.js @@ -1,6 +1,6 @@ const context = navigator.ml.createContext(); -// Build a model with two outputs. +// Build a graph with two outputs. const builder = new MLGraphBuilder(context); const descA = {type: 'float32', dimensions: [3, 4]}; const a = builder.input('a', descA); @@ -12,21 +12,17 @@ const bufferC = new Float32Array(sizeOfShape(descC.dimensions)).fill(1); const c = builder.constant(descC, bufferC); const d = builder.matmul(a, b); const e = builder.add(d, c); +const graph = builder.build({'d': d, 'e': e}); -const graph = await builder.build({d, e}); const bufferA = new Float32Array(sizeOfShape(descA.dimensions)).fill(0.5); -const inputs = {'a': {data: bufferA}}; - -// Compute both d and e. -let outputs = await graph.compute(inputs); -console.log(`outputs include ${Object.keys(outputs)}`); +const inputs = {'a': bufferA}; // Compute d. -outputs = await graph.compute(inputs, {d}); -console.log(`outputs include ${Object.keys(outputs)}`); -console.log(`shape: [${outputs.d.dimensions}], values: ${outputs.d.data}`); +const bufferD = new Float32Array(sizeOfShape([3, 3])); +graph.compute(inputs, {'d': bufferD}); +console.log(`values: ${bufferD}`); // Compute e. -outputs = await graph.compute(inputs, {e}); -console.log(`outputs include ${Object.keys(outputs)}`); -console.log(`shape: [${outputs.e.dimensions}], values: ${outputs.e.data}`); +const bufferE = new Float32Array(sizeOfShape([3, 3])); +graph.compute(inputs, {'e': bufferE}); +console.log(`values: ${bufferE}`); diff --git a/code/samples/preallocated_outputs.js b/code/samples/preallocated_outputs.js deleted file mode 100644 index d708bb62..00000000 --- a/code/samples/preallocated_outputs.js +++ /dev/null @@ -1,19 +0,0 @@ -const context = navigator.ml.createContext(); - -// The following code multiplies matrix a [3, 4] with matrix b [4, 3] -// into matrix c [3, 3]. -const builder = new MLGraphBuilder(context); -const descA = {type: 'float32', dimensions: [3, 4]}; -const a = builder.input('a', descA); -const descB = {type: 'float32', dimensions: [4, 3]}; -const bufferB = new Float32Array(sizeOfShape(descB.dimensions)).fill(0.5); -const b = builder.constant(descB, bufferB); -const c = builder.matmul(a, b); - -const graph = await builder.build({c}); -const bufferA = new Float32Array(sizeOfShape(descA.dimensions)).fill(0.5); -const inputs = {'a': {data: bufferA}}; -// Pre-allocate output buffer for c. -const outputs = {'c': {data: new Float32Array(sizeOfShape([3, 3]))}}; -await graph.compute(inputs, outputs); -console.log(`values: ${outputs.c.data}`); diff --git a/code/samples/simple_graph.js b/code/samples/simple_graph.js index 2fbc4ce2..9a3c487d 100644 --- a/code/samples/simple_graph.js +++ b/code/samples/simple_graph.js @@ -15,21 +15,22 @@ const TENSOR_SIZE = 8; const builder = new MLGraphBuilder(context); -// Create OperandDescriptor object. +// Create MLOperandDescriptor object. const desc = {type: 'float32', dimensions: TENSOR_DIMS}; -// constant1 is a constant operand with the value 0.5. +// constant1 is a constant MLOperand with the value 0.5. const constantBuffer1 = new Float32Array(TENSOR_SIZE).fill(0.5); const constant1 = builder.constant(desc, constantBuffer1); -// input1 is one of the input operands. Its value will be set before execution. +// input1 is one of the input MLOperands. +// Its value will be set before execution. const input1 = builder.input('input1', desc); -// constant2 is another constant operand with the value 0.5. +// constant2 is another constant MLOperand with the value 0.5. const constantBuffer2 = new Float32Array(TENSOR_SIZE).fill(0.5); const constant2 = builder.constant(desc, constantBuffer2); -// input2 is another input operand. Its value will be set before execution. +// input2 is another input MLOperand. Its value will be set before execution. const input2 = builder.input('input2', desc); // intermediateOutput1 is the output of the first Add operation. @@ -38,25 +39,24 @@ const intermediateOutput1 = builder.add(constant1, input1); // intermediateOutput2 is the output of the second Add operation. const intermediateOutput2 = builder.add(constant2, input2); -// output is the output operand of the Mul operation. +// output is the output MLOperand of the Mul operation. const output = builder.mul(intermediateOutput1, intermediateOutput2); -// Build graph. -const graph = await builder.build({'output': output}); +// Compile the constructed graph. +const graph = builder.build({'output': output}); // Setup the input buffers with value 1. const inputBuffer1 = new Float32Array(TENSOR_SIZE).fill(1); const inputBuffer2 = new Float32Array(TENSOR_SIZE).fill(1); +const outputBuffer = new Float32Array(TENSOR_SIZE); -// Asynchronously execute the built model with the specified inputs. +// Execute the compiled graph with the specified inputs. const inputs = { - 'input1': {data: inputBuffer1}, - 'input2': {data: inputBuffer2}, + 'input1': inputBuffer1, + 'input2': inputBuffer2, }; -const outputs = await graph.compute(inputs); +const outputs = {'output': outputBuffer}; +graph.compute(inputs, outputs); -// Log the shape and computed result of the output operand. -console.log('Output shape: ' + outputs.output.dimensions); -// Output shape: 1,2,2,2 -console.log('Output value: ' + outputs.output.data); +console.log('Output value: ' + outputBuffer); // Output value: 2.25,2.25,2.25,2.25,2.25,2.25,2.25,2.25 diff --git a/code/samples_repo.js b/code/samples_repo.js index 1e9bd1eb..d8b90dd7 100644 --- a/code/samples_repo.js +++ b/code/samples_repo.js @@ -4,7 +4,6 @@ const samples = [ 'simple_graph.js', 'matmul.js', 'dynamic_shape.js', - 'preallocated_outputs.js', 'optional_outputs.js', ]; diff --git a/image_classification/main.js b/image_classification/main.js index 704cf0ed..f8173aee 100644 --- a/image_classification/main.js +++ b/image_classification/main.js @@ -7,7 +7,7 @@ import {SqueezeNetNhwc} from './squeezenet_nhwc.js'; import {ResNet50V2Nchw} from './resnet50v2_nchw.js'; import {ResNet101V2Nhwc} from './resnet101v2_nhwc.js'; import {showProgressComponent, readyShowResultComponents} from '../common/ui.js'; -import {getInputTensor, getMedianValue} from '../common/utils.js'; +import {getInputTensor, getMedianValue, sizeOfShape} from '../common/utils.js'; const maxWidth = 380; const maxHeight = 380; @@ -27,6 +27,7 @@ let loadTime = 0; let buildTime = 0; let computeTime = 0; let inputOptions; +let outputBuffer; async function fetchLabels(url) { const response = await fetch(url); @@ -101,22 +102,22 @@ async function renderCamStream() { const inputBuffer = getInputTensor(camElement, inputOptions); console.log('- Computing... '); const start = performance.now(); - const outputs = await netInstance.compute(inputBuffer); + netInstance.compute(inputBuffer, outputBuffer); computeTime = (performance.now() - start).toFixed(2); console.log(` done in ${computeTime} ms.`); camElement.width = camElement.videoWidth; camElement.height = camElement.videoHeight; drawInput(camElement, 'camInCanvas'); showPerfResult(); - await drawOutput(outputs, labels); + await drawOutput(outputBuffer, labels); if (!shouldStopFrame) { requestAnimationFrame(renderCamStream); } } -// Get top 3 classes of labels from output tensor -function getTopClasses(tensor, labels) { - const probs = Array.from(tensor); +// Get top 3 classes of labels from output buffer +function getTopClasses(buffer, labels) { + const probs = Array.from(buffer); const indexes = probs.map((prob, index) => [prob, index]); const sorted = indexes.sort((a, b) => { if (a[0] === b[0]) { @@ -152,9 +153,8 @@ function drawInput(srcElement, canvasId) { ctx.drawImage(srcElement, 0, 0, scaledWidth, scaledHeight); } -async function drawOutput(outputs, labels) { - const outputTensor = outputs.output.data; - const labelClasses = getTopClasses(outputTensor, labels); +async function drawOutput(outputBuffer, labels) { + const labelClasses = getTopClasses(outputBuffer, labels); $('#inferenceresult').show(); labelClasses.forEach((c, i) => { @@ -224,6 +224,8 @@ export async function main() { netInstance = constructNetObject(instanceType); inputOptions = netInstance.inputOptions; labels = await fetchLabels(inputOptions.labelUrl); + outputBuffer = + new Float32Array(sizeOfShape(netInstance.outputDimensions)); isFirstTimeLoad = false; console.log(`- Model name: ${modelName}, Model layout: ${layout} -`); // UI shows model loading progress @@ -237,7 +239,7 @@ export async function main() { await showProgressComponent('done', 'current', 'pending'); console.log('- Building... '); start = performance.now(); - await netInstance.build(outputOperand); + netInstance.build(outputOperand); buildTime = (performance.now() - start).toFixed(2); console.log(` done in ${buildTime} ms.`); } @@ -248,10 +250,9 @@ export async function main() { console.log('- Computing... '); const computeTimeArray = []; let medianComputeTime; - let outputs; for (let i = 0; i < numRuns; i++) { start = performance.now(); - outputs = await netInstance.compute(inputBuffer); + netInstance.compute(inputBuffer, outputBuffer); computeTime = (performance.now() - start).toFixed(2); console.log(` compute time ${i+1}: ${computeTime} ms`); computeTimeArray.push(Number(computeTime)); @@ -261,11 +262,11 @@ export async function main() { medianComputeTime = medianComputeTime.toFixed(2); console.log(` median compute time: ${medianComputeTime} ms`); } - console.log('output: ', outputs); + console.log('outputBuffer: ', outputBuffer); await showProgressComponent('done', 'done', 'done'); readyShowResultComponents(); drawInput(imgElement, 'inputCanvas'); - await drawOutput(outputs, labels); + await drawOutput(outputBuffer, labels); showPerfResult(medianComputeTime); } else if (inputType === 'camera') { await getMediaStream(); diff --git a/image_classification/mobilenet_nchw.js b/image_classification/mobilenet_nchw.js index c911f585..793a2dcc 100644 --- a/image_classification/mobilenet_nchw.js +++ b/image_classification/mobilenet_nchw.js @@ -17,6 +17,7 @@ export class MobileNetV2Nchw { labelUrl: './labels/labels1000.txt', inputDimensions: [1, 3, 224, 224], }; + this.outputDimensions = [1, 1000]; } async buildConv_(input, name, relu6 = true, options = undefined) { @@ -120,8 +121,8 @@ export class MobileNetV2Nchw { return this.builder_.softmax(gemm); } - async build(outputOperand) { - this.graph_ = await this.builder_.build({'output': outputOperand}); + build(outputOperand) { + this.graph_ = this.builder_.build({'output': outputOperand}); } // Release the constant tensors of a model @@ -132,9 +133,9 @@ export class MobileNetV2Nchw { } } - async compute(inputBuffer) { - const inputs = {input: {data: inputBuffer}}; - const outputs = await this.graph_.compute(inputs); - return outputs; + compute(inputBuffer, outputBuffer) { + const inputs = {'input': inputBuffer}; + const outputs = {'output': outputBuffer}; + this.graph_.compute(inputs, outputs); } } diff --git a/image_classification/mobilenet_nhwc.js b/image_classification/mobilenet_nhwc.js index 945d8bf9..531566e7 100644 --- a/image_classification/mobilenet_nhwc.js +++ b/image_classification/mobilenet_nhwc.js @@ -18,6 +18,7 @@ export class MobileNetV2Nhwc { labelUrl: './labels/labels1001.txt', inputDimensions: [1, 224, 224, 3], }; + this.outputDimensions = [1, 1001]; } async buildConv_(input, weightsSubName, biasSubName, relu6, options) { @@ -119,8 +120,8 @@ export class MobileNetV2Nhwc { return this.builder_.softmax(reshape); } - async build(outputOperand) { - this.graph_ = await this.builder_.build({'output': outputOperand}); + build(outputOperand) { + this.graph_ = this.builder_.build({'output': outputOperand}); } // Release the constant tensors of a model @@ -131,9 +132,9 @@ export class MobileNetV2Nhwc { } } - async compute(inputBuffer) { - const inputs = {input: {data: inputBuffer}}; - const outputs = await this.graph_.compute(inputs); - return outputs; + compute(inputBuffer, outputBuffer) { + const inputs = {'input': inputBuffer}; + const outputs = {'output': outputBuffer}; + this.graph_.compute(inputs, outputs); } } diff --git a/image_classification/resnet101v2_nhwc.js b/image_classification/resnet101v2_nhwc.js index fceeffb2..32b03e61 100644 --- a/image_classification/resnet101v2_nhwc.js +++ b/image_classification/resnet101v2_nhwc.js @@ -20,6 +20,7 @@ export class ResNet101V2Nhwc { labelUrl: './labels/labels1001.txt', inputDimensions: [1, 299, 299, 3], }; + this.outputDimensions = [1, 1001]; } async buildConv_(input, nameIndices, options = undefined, relu = true) { @@ -168,8 +169,8 @@ export class ResNet101V2Nhwc { return this.builder_.softmax(reshape); } - async build(outputOperand) { - this.graph_ = await this.builder_.build({'output': outputOperand}); + build(outputOperand) { + this.graph_ = this.builder_.build({'output': outputOperand}); } // Release the constant tensors of a model @@ -180,9 +181,9 @@ export class ResNet101V2Nhwc { } } - async compute(inputBuffer) { - const inputs = {input: {data: inputBuffer}}; - const outputs = await this.graph_.compute(inputs); - return outputs; + compute(inputBuffer, outputBuffer) { + const inputs = {'input': inputBuffer}; + const outputs = {'output': outputBuffer}; + this.graph_.compute(inputs, outputs); } } diff --git a/image_classification/resnet50v2_nchw.js b/image_classification/resnet50v2_nchw.js index c2d31fdc..c960b9ac 100644 --- a/image_classification/resnet50v2_nchw.js +++ b/image_classification/resnet50v2_nchw.js @@ -17,6 +17,7 @@ export class ResNet50V2Nchw { labelUrl: './labels/labels1000.txt', inputDimensions: [1, 3, 224, 224], }; + this.outputDimensions = [1, 1000]; } async buildConv_(input, name, stageName, options = undefined) { @@ -149,8 +150,8 @@ export class ResNet50V2Nchw { return this.builder_.softmax(gemm); } - async build(outputOperand) { - this.graph_ = await this.builder_.build({'output': outputOperand}); + build(outputOperand) { + this.graph_ = this.builder_.build({'output': outputOperand}); } // Release the constant tensors of a model @@ -161,9 +162,9 @@ export class ResNet50V2Nchw { } } - async compute(inputBuffer) { - const inputs = {input: {data: inputBuffer}}; - const outputs = await this.graph_.compute(inputs); - return outputs; + compute(inputBuffer, outputBuffer) { + const inputs = {'input': inputBuffer}; + const outputs = {'output': outputBuffer}; + this.graph_.compute(inputs, outputs); } } diff --git a/image_classification/squeezenet_nchw.js b/image_classification/squeezenet_nchw.js index 6cd556cd..6b0b8324 100644 --- a/image_classification/squeezenet_nchw.js +++ b/image_classification/squeezenet_nchw.js @@ -17,6 +17,7 @@ export class SqueezeNetNchw { labelUrl: './labels/labels1000.txt', inputDimensions: [1, 3, 224, 224], }; + this.outputDimensions = [1, 1000]; } async buildConv_(input, name, options = undefined) { @@ -65,8 +66,8 @@ export class SqueezeNetNchw { return this.builder_.softmax(reshape0); } - async build(outputOperand) { - this.graph_ = await this.builder_.build({'output': outputOperand}); + build(outputOperand) { + this.graph_ = this.builder_.build({'output': outputOperand}); } // Release the constant tensors of a model @@ -77,9 +78,9 @@ export class SqueezeNetNchw { } } - async compute(inputBuffer) { - const inputs = {input: {data: inputBuffer}}; - const outputs = await this.graph_.compute(inputs); - return outputs; + compute(inputBuffer, outputBuffer) { + const inputs = {'input': inputBuffer}; + const outputs = {'output': outputBuffer}; + this.graph_.compute(inputs, outputs); } } diff --git a/image_classification/squeezenet_nhwc.js b/image_classification/squeezenet_nhwc.js index 7193bb4b..5c490d48 100644 --- a/image_classification/squeezenet_nhwc.js +++ b/image_classification/squeezenet_nhwc.js @@ -16,6 +16,7 @@ export class SqueezeNetNhwc { labelUrl: './labels/labels1001.txt', inputDimensions: [1, 224, 224, 3], }; + this.outputDimensions = [1, 1001]; } async buildConv_(input, name, options = undefined) { @@ -73,8 +74,8 @@ export class SqueezeNetNhwc { return this.builder_.softmax(reshape); } - async build(outputOperand) { - this.graph_ = await this.builder_.build({'output': outputOperand}); + build(outputOperand) { + this.graph_ = this.builder_.build({'output': outputOperand}); } // Release the constant tensors of a model @@ -85,9 +86,9 @@ export class SqueezeNetNhwc { } } - async compute(inputBuffer) { - const inputs = {input: {data: inputBuffer}}; - const outputs = await this.graph_.compute(inputs); - return outputs; + compute(inputBuffer, outputBuffer) { + const inputs = {'input': inputBuffer}; + const outputs = {'output': outputBuffer}; + this.graph_.compute(inputs, outputs); } } diff --git a/lenet/lenet.js b/lenet/lenet.js index 798b1fae..1785f220 100644 --- a/lenet/lenet.js +++ b/lenet/lenet.js @@ -114,13 +114,13 @@ export class LeNet { return this.builder_.softmax(add4); } - async build(outputOperand) { - this.graph_ = await this.builder_.build({'output': outputOperand}); + build(outputOperand) { + this.graph_ = this.builder_.build({'output': outputOperand}); } - async predict(inputBuffer) { - const inputs = {input: {data: inputBuffer}}; - const outputs = await this.graph_.compute(inputs); - return outputs.output.data; + predict(inputBuffer, outputBuffer) { + const inputs = {'input': inputBuffer}; + const outputs = {'output': outputBuffer}; + this.graph_.compute(inputs, outputs); } } diff --git a/lenet/main.js b/lenet/main.js index 34cbaee5..e02e5602 100644 --- a/lenet/main.js +++ b/lenet/main.js @@ -1,5 +1,6 @@ 'use strict'; +import {sizeOfShape} from '../common/utils.js'; import {LeNet} from './lenet.js'; import {Pen} from './pen.js'; @@ -65,7 +66,7 @@ export async function main() { `loading elapsed time: ${(performance.now() - start).toFixed(2)} ms`); start = performance.now(); - await lenet.build(outputOperand); + lenet.build(outputOperand); const buildTime = performance.now() - start; console.log(`build elapsed time: ${buildTime.toFixed(2)} ms`); buildTimeElement.innerHTML = 'Build Time: ' + @@ -88,17 +89,17 @@ export async function main() { } let start; - let result; let inferenceTime; const inferenceTimeArray = []; const input = getInputFromCanvas(); + const outputBuffer = new Float32Array(sizeOfShape([1, 10])); for (let i = 0; i < n; i++) { start = performance.now(); - result = await lenet.predict(input); + lenet.predict(input, outputBuffer); inferenceTime = performance.now() - start; console.log(`execution elapsed time: ${inferenceTime.toFixed(2)} ms`); - console.log(`execution result: ${result}`); + console.log(`execution result: ${outputBuffer}`); inferenceTimeArray.push(inferenceTime); } @@ -114,7 +115,7 @@ export async function main() { ' ms'; } - const classes = topK(Array.from(result)); + const classes = topK(Array.from(outputBuffer)); classes.forEach((c, i) => { console.log(`\tlabel: ${c.label}, probability: ${c.prob}%`); const labelElement = document.getElementById(`label${i}`); diff --git a/nsnet2/denoiser.js b/nsnet2/denoiser.js index c301af2f..17ba6723 100644 --- a/nsnet2/denoiser.js +++ b/nsnet2/denoiser.js @@ -1,5 +1,6 @@ import {NSNet2} from './nsnet2.js'; import * as featurelib from './featurelib.js'; +import {sizeOfShape} from '../common/utils.js'; export class Denoiser { constructor(batchSize, frames, sampleRate) { @@ -39,7 +40,7 @@ export class Denoiser { setTimeout(async () => { try { const start = performance.now(); - await this.nsnet.build(outputOperand); + this.nsnet.build(outputOperand); const modelBuildTime = performance.now() - start; this.log(`done in ` + `${modelBuildTime.toFixed(2)} ms.`, true); @@ -83,6 +84,14 @@ export class Denoiser { new Float32Array(1 * this.batchSize * this.nsnet.hiddenSize); let initialHiddenState155Buffer = new Float32Array(1 * this.batchSize * this.nsnet.hiddenSize); + const outputShape = [this.batchSize, this.frames, this.nsnet.frameSize]; + const gru94Shape = + [this.batchSize, 1, this.batchSize, this.nsnet.hiddenSize]; + const gru157Shape = + [this.batchSize, 1, this.batchSize, this.nsnet.hiddenSize]; + const outputBuffer = new Float32Array(sizeOfShape(outputShape)); + const gru94Buffer = new Float32Array(sizeOfShape(gru94Shape)); + const gru157Buffer = new Float32Array(sizeOfShape(gru157Shape)); for (let frame = 0; !lastIteration; frame += this.frames - overlap * 2) { lastIteration = frame + this.frames + 1 > audioFrames; const audioSize = sizePerFrame * (this.frames + 1); @@ -106,16 +115,17 @@ export class Denoiser { inputFeature.dispose(); const calcFeatTime = (performance.now() - start).toFixed(2); start = performance.now(); - const outputs = await this.nsnet.compute( - inputData, initialHiddenState92Buffer, initialHiddenState155Buffer); + const outputs = this.nsnet.compute( + inputData, initialHiddenState92Buffer, initialHiddenState155Buffer, + outputBuffer, gru94Buffer, gru157Buffer); const computeTime = (performance.now() - start).toFixed(2); - initialHiddenState92Buffer = outputs.gru94.data; - initialHiddenState155Buffer = outputs.gru157.data; + initialHiddenState92Buffer = outputs.gru94; + initialHiddenState155Buffer = outputs.gru157; start = performance.now(); let sliceStart; let sliceSize; const sigOut = tf.tidy(() => { - const out = tf.tensor(outputs.output.data, outputs.output.dimensions); + const out = tf.tensor(outputs.output, outputShape); let Gain = tf.transpose(out); Gain = tf.clipByValue(Gain, this.mingain, 1.0); // Workaround tf.js WebGL backend for complex data. diff --git a/nsnet2/nsnet2.js b/nsnet2/nsnet2.js index f72d05ca..c58407cd 100644 --- a/nsnet2/nsnet2.js +++ b/nsnet2/nsnet2.js @@ -10,7 +10,7 @@ export class NSNet2 { constructor() { this.builder_ = null; this.graph_ = null; - this.frameSize_ = 161; + this.frameSize = 161; this.hiddenSize = 400; } @@ -35,7 +35,7 @@ export class NSNet2 { const weight217 = await buildConstantByNpy(this.builder_, baseUrl + '217.npy'); const biasFcOut4 = await buildConstantByNpy(this.builder_, baseUrl + 'fc_out_4_bias.npy'); // Build up the network. - const input = this.builder_.input('input', {type: 'float32', dimensions: [batchSize, frames, this.frameSize_]}); + const input = this.builder_.input('input', {type: 'float32', dimensions: [batchSize, frames, this.frameSize]}); const relu20 = this.builder_.relu(this.builder_.add(this.builder_.matmul(input, weight172), biasFcIn0)); const transpose31 = this.builder_.transpose(relu20, {permutation: [1, 0, 2]}); const initialState92 = this.builder_.input( @@ -55,16 +55,22 @@ export class NSNet2 { return {output, gru94, gru157}; } - async build(outputOperand) { - this.graph_ = await this.builder_.build(outputOperand); + build(outputOperand) { + this.graph_ = this.builder_.build(outputOperand); } - async compute(inputBuffer, initialState92Buffer, initialState155Buffer) { + compute(inputBuffer, initialState92Buffer, initialState155Buffer, outputBuffer, gru94Buffer, gru157Buffer) { const inputs = { - input: {data: inputBuffer}, - initialState92: {data: initialState92Buffer}, - initialState155: {data: initialState155Buffer}, + 'input': inputBuffer, + 'initialState92': initialState92Buffer, + 'initialState155': initialState155Buffer, }; - return await this.graph_.compute(inputs); + const outputs = { + 'output': outputBuffer, + 'gru94': gru94Buffer, + 'gru157': gru157Buffer, + }; + this.graph_.compute(inputs, outputs); + return outputs; } } diff --git a/object_detection/main.js b/object_detection/main.js index 779e7000..872763d6 100644 --- a/object_detection/main.js +++ b/object_detection/main.js @@ -5,7 +5,7 @@ import {TinyYoloV2Nhwc} from './tiny_yolov2_nhwc.js'; import {SsdMobilenetV1Nchw} from './ssd_mobilenetv1_nchw.js'; import {SsdMobilenetV1Nhwc} from './ssd_mobilenetv1_nhwc.js'; import {showProgressComponent, readyShowResultComponents} from '../common/ui.js'; -import {getInputTensor, getMedianValue} from '../common/utils.js'; +import {getInputTensor, getMedianValue, sizeOfShape} from '../common/utils.js'; import * as Yolo2Decoder from './libs/yolo2Decoder.js'; import * as SsdDecoder from './libs/ssdDecoder.js'; @@ -25,6 +25,7 @@ let loadTime = 0; let buildTime = 0; let computeTime = 0; let inputOptions; +let outputs; async function fetchLabels(url) { const response = await fetch(url); @@ -99,7 +100,7 @@ async function renderCamStream() { const inputBuffer = getInputTensor(camElement, inputOptions); console.log('- Computing... '); const start = performance.now(); - const outputs = await netInstance.compute(inputBuffer); + netInstance.compute(inputBuffer, outputs); computeTime = (performance.now() - start).toFixed(2); console.log(` done in ${computeTime} ms.`); camElement.width = camElement.videoWidth; @@ -117,12 +118,10 @@ async function drawOutput(inputElement, outputs, labels) { // Draw output for SSD Mobilenet V1 model if (modelName === 'ssdmobilenetv1') { - const boxesTensor = outputs.boxes.data; - const scoresTensor = outputs.scores.data; const anchors = SsdDecoder.generateAnchors({}); - SsdDecoder.decodeOutputBoxTensor({}, boxesTensor, anchors); + SsdDecoder.decodeOutputBoxTensor({}, outputs.boxes, anchors); let [totalDetections, boxesList, scoresList, classesList] = - SsdDecoder.nonMaxSuppression({}, boxesTensor, scoresTensor); + SsdDecoder.nonMaxSuppression({}, outputs.boxes, outputs.scores); boxesList = SsdDecoder.cropSsdBox( inputElement, totalDetections, boxesList, inputOptions.margin); SsdDecoder.drawBoxes( @@ -130,18 +129,19 @@ async function drawOutput(inputElement, outputs, labels) { boxesList, scoresList, classesList, labels); } else { // Draw output for Tiny Yolo V2 model - let outputTensor = outputs.output.data; // Transpose 'nchw' output to 'nhwc' for postprocessing + let outputBuffer = outputs.output; if (layout === 'nchw') { const tf = navigator.ml.createContext().tf; - const a = tf.tensor(outputTensor, outputs.output.dimensions, 'float32'); + const a = + tf.tensor(outputBuffer, netInstance.outputDimensions, 'float32'); const b = tf.transpose(a, [0, 2, 3, 1]); const buffer = await b.buffer(); tf.dispose(); - outputTensor = buffer.values; + outputBuffer = buffer.values; } const decodeOut = Yolo2Decoder.decodeYOLOv2({numClasses: 20}, - outputTensor, inputOptions.anchors); + outputBuffer, inputOptions.anchors); const boxes = Yolo2Decoder.getBoxes(decodeOut, inputOptions.margin); Yolo2Decoder.drawBoxes(inputElement, outputElement, boxes, labels); } @@ -204,6 +204,16 @@ export async function main() { netInstance = constructNetObject(instanceType); inputOptions = netInstance.inputOptions; labels = await fetchLabels(inputOptions.labelUrl); + if (modelName === 'tinyyolov2') { + outputs = { + 'output': new Float32Array(sizeOfShape(netInstance.outputDimensions)), + }; + } else { + outputs = { + 'boxes': new Float32Array(sizeOfShape([1, 1917, 1, 4])), + 'scores': new Float32Array(sizeOfShape([1, 1917, 91])), + }; + } isFirstTimeLoad = false; console.log(`- Model name: ${modelName}, Model layout: ${layout} -`); // UI shows model loading progress @@ -217,7 +227,7 @@ export async function main() { await showProgressComponent('done', 'current', 'pending'); console.log('- Building... '); start = performance.now(); - await netInstance.build(outputOperand); + netInstance.build(outputOperand); buildTime = (performance.now() - start).toFixed(2); console.log(` done in ${buildTime} ms.`); } @@ -228,10 +238,9 @@ export async function main() { console.log('- Computing... '); const computeTimeArray = []; let medianComputeTime; - let outputs; for (let i = 0; i < numRuns; i++) { start = performance.now(); - outputs = await netInstance.compute(inputBuffer); + netInstance.compute(inputBuffer, outputs); computeTime = (performance.now() - start).toFixed(2); console.log(` compute time ${i+1}: ${computeTime} ms`); computeTimeArray.push(Number(computeTime)); diff --git a/object_detection/ssd_mobilenetv1_nchw.js b/object_detection/ssd_mobilenetv1_nchw.js index aa4b6d84..cc3f35bd 100644 --- a/object_detection/ssd_mobilenetv1_nchw.js +++ b/object_detection/ssd_mobilenetv1_nchw.js @@ -247,9 +247,8 @@ ${nameArray[1]}_BatchNorm_batchnorm`; return {'boxes': concat0, 'scores': concat1}; } - async build(outputOperand) { - this.graph_ = await this.builder_.build( - {'boxes': outputOperand.boxes, 'scores': outputOperand.scores}); + build(outputOperand) { + this.graph_ = this.builder_.build(outputOperand); } // Release the constant tensors of a model @@ -260,9 +259,8 @@ ${nameArray[1]}_BatchNorm_batchnorm`; } } - async compute(inputBuffer) { - const inputs = {input: {data: inputBuffer}}; - const outputs = await this.graph_.compute(inputs); - return outputs; + compute(inputBuffer, outputs) { + const inputs = {'input': inputBuffer}; + this.graph_.compute(inputs, outputs); } } diff --git a/object_detection/ssd_mobilenetv1_nhwc.js b/object_detection/ssd_mobilenetv1_nhwc.js index f930ad7c..2bec4447 100644 --- a/object_detection/ssd_mobilenetv1_nhwc.js +++ b/object_detection/ssd_mobilenetv1_nhwc.js @@ -226,9 +226,8 @@ ${nameArray[1]}_BatchNorm_batchnorm`; return {'boxes': concat0, 'scores': concat1}; } - async build(outputOperand) { - this.graph_ = await this.builder_.build( - {'boxes': outputOperand.boxes, 'scores': outputOperand.scores}); + build(outputOperand) { + this.graph_ = this.builder_.build(outputOperand); } // Release the constant tensors of a model @@ -239,9 +238,8 @@ ${nameArray[1]}_BatchNorm_batchnorm`; } } - async compute(inputBuffer) { - const inputs = {input: {data: inputBuffer}}; - const outputs = await this.graph_.compute(inputs); - return outputs; + compute(inputBuffer, outputs) { + const inputs = {'input': inputBuffer}; + this.graph_.compute(inputs, outputs); } } diff --git a/object_detection/tiny_yolov2_nchw.js b/object_detection/tiny_yolov2_nchw.js index 0a1493af..7f00fa65 100644 --- a/object_detection/tiny_yolov2_nchw.js +++ b/object_detection/tiny_yolov2_nchw.js @@ -16,6 +16,7 @@ export class TinyYoloV2Nchw { anchors: [1.08, 1.19, 3.42, 4.41, 6.63, 11.38, 9.42, 5.11, 16.62, 10.52], inputDimensions: [1, 3, 416, 416], }; + this.outputDimensions = [1, 125, 13, 13]; } async buildConv_(input, name, useBias = false) { @@ -91,8 +92,8 @@ export class TinyYoloV2Nchw { return conv; } - async build(outputOperand) { - this.graph_ = await this.builder_.build({'output': outputOperand}); + build(outputOperand) { + this.graph_ = this.builder_.build({'output': outputOperand}); } // Release the constant tensors of a model @@ -103,9 +104,8 @@ export class TinyYoloV2Nchw { } } - async compute(inputBuffer) { - const inputs = {input: {data: inputBuffer}}; - const outputs = await this.graph_.compute(inputs); - return outputs; + compute(inputBuffer, outputs) { + const inputs = {'input': inputBuffer}; + this.graph_.compute(inputs, outputs); } } diff --git a/object_detection/tiny_yolov2_nhwc.js b/object_detection/tiny_yolov2_nhwc.js index 39bbed67..2417fb7e 100644 --- a/object_detection/tiny_yolov2_nhwc.js +++ b/object_detection/tiny_yolov2_nhwc.js @@ -17,6 +17,7 @@ export class TinyYoloV2Nhwc { inputDimensions: [1, 416, 416, 3], norm: true, }; + this.outputDimensions = [1, 13, 13, 125]; } async buildConv_(input, name) { @@ -72,8 +73,8 @@ export class TinyYoloV2Nhwc { return await this.buildConv_(conv8, '9'); } - async build(outputOperand) { - this.graph_ = await this.builder_.build({'output': outputOperand}); + build(outputOperand) { + this.graph_ = this.builder_.build({'output': outputOperand}); } // Release the constant tensors of a model @@ -84,9 +85,8 @@ export class TinyYoloV2Nhwc { } } - async compute(inputBuffer) { - const inputs = {input: {data: inputBuffer}}; - const outputs = await this.graph_.compute(inputs); - return outputs; + compute(inputBuffer, outputs) { + const inputs = {'input': inputBuffer}; + this.graph_.compute(inputs, outputs); } } diff --git a/style_transfer/fast_style_transfer_net.js b/style_transfer/fast_style_transfer_net.js index a92bb6c5..fad587b7 100644 --- a/style_transfer/fast_style_transfer_net.js +++ b/style_transfer/fast_style_transfer_net.js @@ -13,6 +13,11 @@ export class FastStyleTransferNet { this.constAdd_ = null; this.weightsUrl_ = 'https://webmachinelearning.github.io/test-data/' + 'models/fast_style_transfer_nchw/weights/'; + this.inputOptions = { + inputDimensions: [1, 3, 540, 540], + inputLayout: 'nchw', + }; + this.outputDimensions = [1, 3, 540, 540]; } buildInstanceNormalization_(conv2D, variableMul, variableAdd) { @@ -96,7 +101,7 @@ export class FastStyleTransferNet { const constAdd0 = this.builder_.constant( {type: 'float32', dimensions: [1]}, new Float32Array([127.5])); // Build up the network. - const input = this.builder_.input('input', {type: 'float32', dimensions: [1, 3, 540, 540]}); + const input = this.builder_.input('input', {type: 'float32', dimensions: this.inputOptions.inputDimensions}); const conv2D0 = this.builder_.conv2d(this.builder_.pad(input, padding4, {mode: 'reflection'}), weightConv0); const add0 = this.buildInstanceNormalization_(conv2D0, variableMul0, variableAdd0); @@ -167,8 +172,8 @@ export class FastStyleTransferNet { return this.builder_.add(this.builder_.mul(this.builder_.tanh(add20), constMul0), constAdd0); } - async build(outputOperand) { - this.graph_ = await this.builder_.build({'output': outputOperand}); + build(outputOperand) { + this.graph_ = this.builder_.build({'output': outputOperand}); } // Release the constant tensors of a model @@ -179,9 +184,9 @@ export class FastStyleTransferNet { } } - async compute(inputBuffer) { - const inputs = {input: {data: inputBuffer}}; - const outputs = await this.graph_.compute(inputs); - return outputs; + compute(inputBuffer, outputBuffer) { + const inputs = {'input': inputBuffer}; + const outputs = {'output': outputBuffer}; + this.graph_.compute(inputs, outputs); } } diff --git a/style_transfer/main.js b/style_transfer/main.js index ee78c147..da9be1a7 100644 --- a/style_transfer/main.js +++ b/style_transfer/main.js @@ -2,7 +2,7 @@ import {FastStyleTransferNet} from './fast_style_transfer_net.js'; import {showProgressComponent, readyShowResultComponents} from '../common/ui.js'; -import {getInputTensor, getMedianValue} from '../common/utils.js'; +import {getInputTensor, getMedianValue, sizeOfShape} from '../common/utils.js'; const maxWidth = 380; const maxHeight = 380; @@ -19,10 +19,7 @@ let stream = null; let loadTime = 0; let buildTime = 0; let computeTime = 0; -const inputOptions = { - inputDimensions: [1, 3, 540, 540], - inputLayout: 'nchw', -}; +let outputBuffer; $(document).ready(() => { $('.icdisplay').hide(); @@ -101,17 +98,18 @@ function stopCamera() { * This method is used to render live camera tab. */ async function renderCamStream() { - const inputBuffer = getInputTensor(camElement, inputOptions); + const inputBuffer = + getInputTensor(camElement, fastStyleTransferNet.inputOptions); console.log('- Computing... '); const start = performance.now(); - const outputs = await fastStyleTransferNet.compute(inputBuffer); + fastStyleTransferNet.compute(inputBuffer, outputBuffer); computeTime = (performance.now() - start).toFixed(2); console.log(` done in ${computeTime} ms.`); camElement.width = camElement.videoWidth; camElement.height = camElement.videoHeight; drawInput(camElement, 'camInCanvas'); showPerfResult(); - await drawOutput(outputs, 'camInCanvas', 'camOutCanvas'); + drawOutput('camInCanvas', 'camOutCanvas'); if (!shouldStopFrame) { requestAnimationFrame(renderCamStream); } @@ -129,9 +127,8 @@ function drawInput(srcElement, canvasId) { ctx.drawImage(srcElement, 0, 0, scaledWidth, scaledHeight); } -async function drawOutput(outputs, inCanvasId, outCanvasId) { - const outputTensor = outputs.output.data; - const outputSize = outputs.output.dimensions; +function drawOutput(inCanvasId, outCanvasId) { + const outputSize = fastStyleTransferNet.outputDimensions; const height = outputSize[2]; const width = outputSize[3]; const mean = [1, 1, 1, 1]; @@ -141,9 +138,9 @@ async function drawOutput(outputs, inCanvasId, outCanvasId) { for (let i = 0; i < height * width; ++i) { const j = i * 4; - const r = outputTensor[i] * mean[0] + offset[0]; - const g = outputTensor[i + height * width] * mean[1] + offset[1]; - const b = outputTensor[i + height * width * 2] * mean[2] + offset[2]; + const r = outputBuffer[i] * mean[0] + offset[0]; + const g = outputBuffer[i + height * width] * mean[1] + offset[1]; + const b = outputBuffer[i + height * width * 2] * mean[2] + offset[2]; bytes[j + 0] = Math.round(r); bytes[j + 1] = Math.round(g); bytes[j + 2] = Math.round(b); @@ -207,6 +204,8 @@ export async function main() { fastStyleTransferNet.dispose(); } fastStyleTransferNet = new FastStyleTransferNet(); + outputBuffer = + new Float32Array(sizeOfShape(fastStyleTransferNet.outputDimensions)); isFirstTimeLoad = false; isModelChanged = false; console.log(`- Model ID: ${modelId} -`); @@ -221,21 +220,21 @@ export async function main() { await showProgressComponent('done', 'current', 'pending'); console.log('- Building... '); start = performance.now(); - await fastStyleTransferNet.build(outputOperand); + fastStyleTransferNet.build(outputOperand); buildTime = (performance.now() - start).toFixed(2); console.log(` done in ${buildTime} ms.`); } // UI shows inferencing progress await showProgressComponent('done', 'done', 'current'); if (inputType === 'image') { - const inputBuffer = getInputTensor(imgElement, inputOptions); + const inputBuffer = + getInputTensor(imgElement, fastStyleTransferNet.inputOptions); console.log('- Computing... '); const computeTimeArray = []; let medianComputeTime; - let outputs; for (let i = 0; i < numRuns; i++) { start = performance.now(); - outputs = await fastStyleTransferNet.compute(inputBuffer); + fastStyleTransferNet.compute(inputBuffer, outputBuffer); computeTime = (performance.now() - start).toFixed(2); console.log(` compute time ${i+1}: ${computeTime} ms`); computeTimeArray.push(Number(computeTime)); @@ -248,7 +247,7 @@ export async function main() { await showProgressComponent('done', 'done', 'done'); readyShowResultComponents(); drawInput(imgElement, 'inputCanvas'); - await drawOutput(outputs, 'inputCanvas', 'outputCanvas'); + drawOutput('inputCanvas', 'outputCanvas'); showPerfResult(medianComputeTime); } else if (inputType === 'camera') { await getMediaStream();