Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove the MLAutoPad usage and calculate the padding #195

Merged
merged 1 commit into from
Mar 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions common/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,69 @@ export async function isWebNN() {
}
}
}

// Derive from
// https://github.com/webmachinelearning/webnn-baseline/blob/main/src/lib/compute-padding.js
/**
* Compute the beginning and ending pad given input, filter and stride sizes.
* @param {String} autoPad
* @param {Number} inputSize
* @param {Number} effectiveFilterSize
* @param {Number} stride
* @param {Number} outputPadding
* @return {Array} [paddingBegin, paddingEnd]
*/
function computePadding1DForAutoPad(
autoPad, inputSize, effectiveFilterSize, stride, outputPadding) {
let totalPadding;
if (outputPadding === undefined) {
// for conv2d
const outSize = Math.ceil(inputSize / stride);
const neededInput = (outSize - 1) * stride + effectiveFilterSize;
totalPadding = neededInput > inputSize ? neededInput - inputSize : 0;
} else {
// for convTranspose2d
// totalPadding = beginning padding + ending padding
// SAME_UPPER or SAME_LOWER mean pad the input so that
// output size = input size * strides
// output size = (input size - 1) * stride + effectiveFilterSize
// - beginning padding - ending padding + output padding
totalPadding = (inputSize - 1) * stride + effectiveFilterSize +
outputPadding - inputSize * stride;
}
let paddingBegin;
let paddingEnd;
switch (autoPad) {
case 'same-upper':
paddingBegin = Math.floor(totalPadding / 2);
paddingEnd = Math.floor((totalPadding + 1) / 2);
break;
case 'same-lower':
paddingBegin = Math.floor((totalPadding + 1) / 2);
paddingEnd = Math.floor(totalPadding / 2);
break;
default:
throw new Error('The autoPad is invalid.');
}
return [paddingBegin, paddingEnd];
}

// Compute explicit padding given input sizes, filter sizes, strides, dilations
// and auto pad mode 'same-upper' or 'same-lower'.
export function computePadding2DForAutoPad(
inputSizes, filterSizes, strides, dilations, autoPad) {
const [inputHeight, inputWidth] = inputSizes;
const [filterHeight, filterWidth] = filterSizes;
const [strideHeight, strideWidth] = strides ? strides : [1, 1];
const [dilationHeight, dilationWidth] = dilations ? dilations: [1, 1];
const effectiveFilterHeight = (filterHeight - 1) * dilationHeight + 1;
const effectiveFilterWidth = (filterWidth - 1) * dilationWidth + 1;
const [beginningPaddingHeight, endingPaddingHeight] =
computePadding1DForAutoPad(
autoPad, inputHeight, effectiveFilterHeight, strideHeight);
const [beginningPaddingWidth, endingPaddingWidth] =
computePadding1DForAutoPad(
autoPad, inputWidth, effectiveFilterWidth, strideWidth);
return [beginningPaddingHeight, endingPaddingHeight,
beginningPaddingWidth, endingPaddingWidth];
}
10 changes: 9 additions & 1 deletion face_recognition/facenet_nchw.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import {buildConstantByNpy} from '../common/utils.js';
import {buildConstantByNpy, computePadding2DForAutoPad} from '../common/utils.js';
const strides = [2, 2];
const autoPad = 'same-upper';

Expand Down Expand Up @@ -43,6 +43,14 @@ export class FaceNetNchw {
if (relu) {
options.activation = this.builder_.relu();
}
// WebNN spec drops autoPad support, compute the explicit padding instead.
if (options.autoPad == 'same-upper') {
options.padding =
computePadding2DForAutoPad(
/* nchw */[input.shape()[2], input.shape()[3]],
/* oihw */[weights.shape()[2], weights.shape()[3]],
options.strides, options.dilations, options.autoPad);
}
return this.builder_.conv2d(input, weights, options);
}

Expand Down
10 changes: 9 additions & 1 deletion face_recognition/facenet_nhwc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import {buildConstantByNpy} from '../common/utils.js';
import {buildConstantByNpy, computePadding2DForAutoPad} from '../common/utils.js';
const strides = [2, 2];
const autoPad = 'same-upper';

Expand Down Expand Up @@ -45,6 +45,14 @@ export class FaceNetNhwc {
if (relu) {
options.activation = this.builder_.relu();
}
// WebNN spec drops autoPad support, compute the explicit padding instead.
if (options.autoPad == 'same-upper') {
options.padding =
computePadding2DForAutoPad(
/* nwhc */[input.shape()[1], input.shape()[2]],
/* ohwi */[weights.shape()[1], weights.shape()[2]],
options.strides, options.dilations, options.autoPad);
}
return this.builder_.conv2d(input, weights, options);
}

Expand Down
17 changes: 8 additions & 9 deletions facial_landmark_detection/ssd_mobilenetv2_face_nchw.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import {buildConstantByNpy} from '../common/utils.js';
import {buildConstantByNpy, computePadding2DForAutoPad} from '../common/utils.js';

// SSD MobileNet V2 Face model with 'nchw' layout.
export class SsdMobilenetV2FaceNchw {
Expand Down Expand Up @@ -36,7 +36,7 @@ export class SsdMobilenetV2FaceNchw {
};
}

async buildConv_(input, nameArray, clip = true, options = undefined) {
async buildConv_(input, nameArray, clip = true, options = {}) {
// nameArray: 0: keyword, 1: indice or suffix
let prefix = this.weightsUrl_;
const weightSuffix = '_weights.npy';
Expand Down Expand Up @@ -66,13 +66,12 @@ ${nameArray[1]}`;
const weights = buildConstantByNpy(this.builder_, weightsName);
const biasName = prefix + biasSuffix;
const bias = buildConstantByNpy(this.builder_, biasName);
if (options !== undefined) {
options.autoPad = 'same-upper';
} else {
options = {
autoPad: 'same-upper',
};
}
const inputShape = (await input).shape();
const weightsShape = (await weights).shape();
options.padding = computePadding2DForAutoPad(
/* nchw */[inputShape[2], inputShape[3]],
/* oihw */[weightsShape[2], weightsShape[3]],
options.strides, options.dilations, 'same-upper');
options.bias = await bias;
if (clip) {
// TODO: Set clamp activation to options once it's supported in
Expand Down
10 changes: 7 additions & 3 deletions facial_landmark_detection/ssd_mobilenetv2_face_nhwc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import {buildConstantByNpy} from '../common/utils.js';
import {buildConstantByNpy, computePadding2DForAutoPad} from '../common/utils.js';

// SSD MobileNet V2 Face model with 'nhwc' layout.
export class SsdMobilenetV2FaceNhwc {
Expand Down Expand Up @@ -69,18 +69,22 @@ ${nameArray[1]}`;
if (options !== undefined) {
options.inputLayout = 'nhwc';
options.filterLayout = 'ohwi';
options.autoPad = 'same-upper';
} else {
options = {
inputLayout: 'nhwc',
filterLayout: 'ohwi',
autoPad: 'same-upper',
};
}
if (nameArray[0].includes('depthwise')) {
options.filterLayout = 'ihwo';
}
options.bias = await bias;
const inputShape = (await input).shape();
const weightsShape = (await weights).shape();
options.padding = computePadding2DForAutoPad(
/* nhwc */[inputShape[1], inputShape[2]],
/* ohwi or ihwo */[weightsShape[1], weightsShape[2]],
options.strides, options.dilations, 'same-upper');
if (relu6) {
// TODO: Set clamp activation to options once it's supported in
// WebNN DML backend.
Expand Down
10 changes: 9 additions & 1 deletion image_classification/mobilenet_nhwc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import {buildConstantByNpy} from '../common/utils.js';
import {buildConstantByNpy, computePadding2DForAutoPad} from '../common/utils.js';

/* eslint max-len: ["error", {"code": 120}] */

Expand Down Expand Up @@ -29,6 +29,14 @@ export class MobileNetV2Nhwc {
const bias = await buildConstantByNpy(this.builder_, biasName);
options.inputLayout = 'nhwc';
options.bias = bias;
// WebNN spec drops autoPad support, compute the explicit padding instead.
if (options.autoPad == 'same-upper') {
options.padding =
computePadding2DForAutoPad(
/* nwhc */[input.shape()[1], input.shape()[2]],
/* ohwi or ihwo */[weights.shape()[1], weights.shape()[2]],
options.strides, options.dilations, options.autoPad);
}
if (relu6) {
// TODO: Set clamp activation to options once it's supported in
// WebNN DML backend.
Expand Down
17 changes: 15 additions & 2 deletions image_classification/resnet50v2_nhwc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import {buildConstantByNpy} from '../common/utils.js';
import {buildConstantByNpy, computePadding2DForAutoPad} from '../common/utils.js';

const autoPad = 'same-upper';
const strides = [2, 2];
Expand Down Expand Up @@ -50,6 +50,14 @@ export class ResNet50V2Nhwc {
if (relu) {
options.activation = this.builder_.relu();
}
// WebNN spec drops autoPad support, compute the explicit padding instead.
if (options.autoPad == 'same-upper') {
options.padding =
computePadding2DForAutoPad(
/* nwhc */[input.shape()[1], input.shape()[2]],
/* ohwi */[weights.shape()[1], weights.shape()[2]],
options.strides, options.dilations, options.autoPad);
}
return this.builder_.conv2d(input, weights, options);
}

Expand Down Expand Up @@ -105,8 +113,13 @@ export class ResNet50V2Nhwc {
});
const conv1 = await this.buildConv_(
input, ['', '', '1'], {strides, padding: [3, 3, 3, 3]}, false);
const windowDimensions = [3, 3];
const pool = this.builder_.maxPool2d(
conv1, {windowDimensions: [3, 3], strides, layout, autoPad});
conv1, {windowDimensions, strides, layout,
padding: computePadding2DForAutoPad(
/* nhwc */ [conv1.shape()[1], conv1.shape()[2]],
windowDimensions, strides, /* dilations */ undefined,
'same-upper')});
// Block 1
const bottleneck1 = await this.buildBottleneckV2_(pool, ['1', '1'], true);
const bottleneck2 = await this.buildBottleneckV2_(
Expand Down
10 changes: 9 additions & 1 deletion image_classification/squeezenet_nhwc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import {buildConstantByNpy} from '../common/utils.js';
import {buildConstantByNpy, computePadding2DForAutoPad} from '../common/utils.js';

// SqueezeNet 1.0 model with 'nhwc' layout
export class SqueezeNetNhwc {
Expand Down Expand Up @@ -29,6 +29,14 @@ export class SqueezeNetNhwc {
options.filterLayout = 'ohwi';
options.bias = bias;
options.activation = this.builder_.relu();
// WebNN spec drops autoPad support, compute the explicit padding instead.
if (options.autoPad == 'same-upper') {
options.padding =
computePadding2DForAutoPad(
/* nwhc */[input.shape()[1], input.shape()[2]],
/* ohwi */[weights.shape()[1], weights.shape()[2]],
options.strides, options.dilations, options.autoPad);
}
return this.builder_.conv2d(input, weights, options);
}

Expand Down
15 changes: 0 additions & 15 deletions object_detection/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,6 @@
</div>
</div>
</div>
<div class="row mb-2 align-items-center">
<div class="col-1 col-md-1">
<span>Layout</span>
</div>
<div class="col-md-auto">
<div class="btn-group-toggle" data-toggle="buttons" id="layoutBtns">
<label class="btn btn-outline-info btn-sm">
<input type="radio" name="layout" id="nchw" autocomplete="off">NCHW
</label>
<label class="btn btn-outline-info btn-sm active">
<input type="radio" name="layout" id="nhwc" autocomplete="off" checked>NHWC
</label>
</div>
</div>
</div>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR also drops the layout selection for object detection example.

<div class="row align-items-center">
<div class="col-1 col-md-1">
<span>Model</span>
Expand Down
13 changes: 7 additions & 6 deletions object_detection/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ $(document).ready(async () => {

$('#backendBtns .btn').on('change', async (e) => {
if (inputType === 'camera') utils.stopCameraStream(rafReq, stream);
if ($(e.target).attr('id').indexOf('cpu') != -1) {
layout = 'nhwc';
} else if (($(e.target).attr('id').indexOf('gpu') != -1)) {
layout = 'nchw';
} else {
throw new Error('Unknown backend');
}
await main();
});

Expand All @@ -58,12 +65,6 @@ $('#modelBtns .btn').on('change', async (e) => {
await main();
});

$('#layoutBtns .btn').on('change', async (e) => {
layout = $(e.target).attr('id');
if (inputType === 'camera') utils.stopCameraStream(rafReq, stream);
await main();
});

// Click trigger to do inference with <img> element
$('#img').click(async () => {
if (inputType === 'camera') utils.stopCameraStream(rafReq, stream);
Expand Down
7 changes: 5 additions & 2 deletions object_detection/ssd_mobilenetv1_nchw.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import {buildConstantByNpy} from '../common/utils.js';
import {buildConstantByNpy, computePadding2DForAutoPad} from '../common/utils.js';

// SSD MobileNet V1 model with 'nchw' layout, trained on the COCO dataset.
export class SsdMobilenetV1Nchw {
Expand Down Expand Up @@ -58,7 +58,10 @@ ${nameArray[1]}_BatchNorm_batchnorm`;
const weights = await buildConstantByNpy(this.builder_, weightsName);
const biasName = this.biasUrl_ + prefix + biasSuffix;
const bias = await buildConstantByNpy(this.builder_, biasName);
options.autoPad = 'same-upper';
options.padding = computePadding2DForAutoPad(
/* nchw */[input.shape()[2], input.shape()[3]],
/* oihw */[weights.shape()[2], weights.shape()[3]],
options.strides, options.dilations, 'same-upper');
options.bias = bias;
if (relu6) {
// TODO: Set clamp activation to options once it's supported in
Expand Down
8 changes: 5 additions & 3 deletions object_detection/ssd_mobilenetv1_nhwc.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import {buildConstantByNpy} from '../common/utils.js';
import {buildConstantByNpy, computePadding2DForAutoPad} from '../common/utils.js';

// SSD MobileNet V1 model with 'nhwc' layout, trained on the COCO dataset.
export class SsdMobilenetV1Nhwc {
Expand Down Expand Up @@ -59,18 +59,20 @@ ${nameArray[1]}_BatchNorm_batchnorm`;
if (options !== undefined) {
options.inputLayout = 'nhwc';
options.filterLayout = 'ohwi';
options.autoPad = 'same-upper';
} else {
options = {
inputLayout: 'nhwc',
filterLayout: 'ohwi',
autoPad: 'same-upper',
};
}
if (nameArray[0].includes('depthwise')) {
options.filterLayout = 'ihwo';
}
options.bias = bias;
options.padding = computePadding2DForAutoPad(
/* nhwc */[input.shape()[1], input.shape()[2]],
/* ohwi or ihwo */[weights.shape()[1], weights.shape()[2]],
options.strides, options.dilations, 'same-upper');
if (relu6) {
// TODO: Set clamp activation to options once it's supported in
// WebNN DML backend.
Expand Down
Loading