Skip to content

Commit

Permalink
Remove the MLAutoPad usage and calculate the padding (#195)
Browse files Browse the repository at this point in the history
WebNN spec drops the MLAutoPad support:

webmachinelearning/webnn#587
  • Loading branch information
huningxin authored Mar 8, 2024
1 parent 2e28fac commit 678d452
Show file tree
Hide file tree
Showing 15 changed files with 194 additions and 65 deletions.
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>
<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

0 comments on commit 678d452

Please sign in to comment.