Skip to content

Commit

Permalink
Merge pull request webmachinelearning#58 from Honry/sync-api
Browse files Browse the repository at this point in the history
Adapt samples to sync API and require pre-allocated output buffers
  • Loading branch information
huningxin authored Jul 10, 2021
2 parents 52242e5 + 07d8e87 commit baa61ec
Show file tree
Hide file tree
Showing 26 changed files with 228 additions and 219 deletions.
7 changes: 2 additions & 5 deletions code/main.js
Original file line number Diff line number Diff line change
@@ -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');
Expand Down
23 changes: 12 additions & 11 deletions code/samples/dynamic_shape.js
Original file line number Diff line number Diff line change
@@ -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]);
11 changes: 6 additions & 5 deletions code/samples/matmul.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
17 changes: 9 additions & 8 deletions code/samples/mul_add.js
Original file line number Diff line number Diff line change
@@ -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);
22 changes: 9 additions & 13 deletions code/samples/optional_outputs.js
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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}`);
19 changes: 0 additions & 19 deletions code/samples/preallocated_outputs.js

This file was deleted.

32 changes: 16 additions & 16 deletions code/samples/simple_graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
1 change: 0 additions & 1 deletion code/samples_repo.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const samples = [
'simple_graph.js',
'matmul.js',
'dynamic_shape.js',
'preallocated_outputs.js',
'optional_outputs.js',
];

Expand Down
29 changes: 15 additions & 14 deletions image_classification/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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]) {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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
Expand All @@ -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.`);
}
Expand All @@ -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));
Expand All @@ -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();
Expand Down
13 changes: 7 additions & 6 deletions image_classification/mobilenet_nchw.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
}
13 changes: 7 additions & 6 deletions image_classification/mobilenet_nhwc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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);
}
}
Loading

0 comments on commit baa61ec

Please sign in to comment.