Skip to content

Commit

Permalink
Refactor the Facial Landmark Detection example to fetch in parallel (w…
Browse files Browse the repository at this point in the history
…ebmachinelearning#188)

* Refactor the Facial Landmark Detection example to fetch in parallel

This model separates each constant value into a separate NumPy file
which needs to be fetched. This change refactors the graph construction
so that these fetches can happen in parallel rather than waiting for
each fetch to complete before building that graph layer.

This is an experiment to determine whether it would be more ergonomic
for the graph builder function to accept promises so that this kind of
code can be written without the careful placement of awaits demonstrated
here.

* Fix lint errors
  • Loading branch information
reillyeon authored Nov 14, 2023
1 parent 9c52c38 commit 27039b3
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 158 deletions.
55 changes: 29 additions & 26 deletions facial_landmark_detection/face_landmark_nchw.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export class FaceLandmarkNchw {
};
}

async buildMaxPool2d(input, options) {
return this.builder_.maxPool2d(await input, options);
}

async buildConv_(input, indice) {
const prefix = `${this.weightsUrl_}/conv2d`;
let weightSuffix = '_kernel.npy';
Expand All @@ -26,33 +30,33 @@ export class FaceLandmarkNchw {
}

const weightsName = prefix + weightSuffix;
const weights = await buildConstantByNpy(this.builder_, weightsName);
const weights = buildConstantByNpy(this.builder_, weightsName);
const biasName = prefix + biasSuffix;
const bias = await buildConstantByNpy(this.builder_, biasName);
const bias = buildConstantByNpy(this.builder_, biasName);
const options = {
bias: bias,
bias: await bias,
activation: this.builder_.relu(),
};
return this.builder_.conv2d(input, weights, options);
return this.builder_.conv2d(await input, await weights, options);
}

async buildGemm_(input, namePrefix, relu = false, reshapeSize) {
const weights = await buildConstantByNpy(this.builder_,
const weights = buildConstantByNpy(this.builder_,
`${this.weightsUrl_}/${namePrefix}_kernel_transpose.npy`);
const bias = await buildConstantByNpy(this.builder_,
const bias = buildConstantByNpy(this.builder_,
`${this.weightsUrl_}/${namePrefix}_MatMul_bias.npy`);
const options = {
aTranspose: false,
bTranspose: true,
c: bias,
c: await bias,
};
let gemm;
if (reshapeSize !== undefined) {
gemm = this.builder_.gemm(this.builder_.reshape(
this.builder_.transpose(input, {permutation: [0, 2, 3, 1]}),
[null, reshapeSize]), weights, options);
this.builder_.transpose(await input, {permutation: [0, 2, 3, 1]}),
[null, reshapeSize]), await weights, options);
} else {
gemm = this.builder_.gemm(input, weights, options);
gemm = this.builder_.gemm(await input, await weights, options);
}
if (relu) {
gemm = this.builder_.relu(gemm);
Expand All @@ -73,27 +77,26 @@ export class FaceLandmarkNchw {
const poolOptions =
{windowDimensions: [2, 2], strides: [2, 2]};

const conv0 = await this.buildConv_(input, 0);
const pool0 = await this.builder_.maxPool2d(conv0, poolOptions);
const conv0 = this.buildConv_(input, 0);
const pool0 = this.buildMaxPool2d(conv0, poolOptions);

const conv1 = await this.buildConv_(pool0, 1);
const conv2 = await this.buildConv_(conv1, 2);
const pool1 = await this.builder_.maxPool2d(conv2, poolOptions);
const conv1 = this.buildConv_(pool0, 1);
const conv2 = this.buildConv_(conv1, 2);
const pool1 = this.buildMaxPool2d(conv2, poolOptions);

const conv3 = await this.buildConv_(pool1, 3);
const conv4 = await this.buildConv_(conv3, 4);
const pool2 = await this.builder_.maxPool2d(conv4, poolOptions);
const conv3 = this.buildConv_(pool1, 3);
const conv4 = this.buildConv_(conv3, 4);
const pool2 = this.buildMaxPool2d(conv4, poolOptions);

const conv5 = await this.buildConv_(pool2, 5);
const conv6 = await this.buildConv_(conv5, 6);
const pool3 = await this.builder_.maxPool2d(
conv6, {windowDimensions: [2, 2]});
const conv5 = this.buildConv_(pool2, 5);
const conv6 = this.buildConv_(conv5, 6);
const pool3 = this.buildMaxPool2d(conv6, {windowDimensions: [2, 2]});

const conv7 = await this.buildConv_(pool3, 7);
const gemm0 = await this.buildGemm_(conv7, 'dense', true, 6400);
const gemm1 = await this.buildGemm_(gemm0, 'logits');
const conv7 = this.buildConv_(pool3, 7);
const gemm0 = this.buildGemm_(conv7, 'dense', true, 6400);
const gemm1 = this.buildGemm_(gemm0, 'logits');

return gemm1;
return await gemm1;
}

async build(outputOperand) {
Expand Down
52 changes: 28 additions & 24 deletions facial_landmark_detection/face_landmark_nhwc.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export class FaceLandmarkNhwc {
};
}

async buildMaxPool2d(input, options) {
return this.builder_.maxPool2d(await input, options);
}

async buildConv_(input, indice) {
const prefix = `${this.weightsUrl_}/conv2d`;
let weightSuffix = '_kernel.npy';
Expand All @@ -26,34 +30,34 @@ export class FaceLandmarkNhwc {
}

const weightsName = prefix + weightSuffix;
const weights = await buildConstantByNpy(this.builder_, weightsName);
const weights = buildConstantByNpy(this.builder_, weightsName);
const biasName = prefix + biasSuffix;
const bias = await buildConstantByNpy(this.builder_, biasName);
const bias = buildConstantByNpy(this.builder_, biasName);
const options = {
inputLayout: 'nhwc',
filterLayout: 'ohwi',
bias: bias,
bias: await bias,
activation: this.builder_.relu(),
};
return this.builder_.conv2d(input, weights, options);
return this.builder_.conv2d(await input, await weights, options);
}

async buildFullyConnected_(input, namePrefix, relu = false, reshapeSize) {
const weights = await buildConstantByNpy(this.builder_,
const weights = buildConstantByNpy(this.builder_,
`${this.weightsUrl_}/${namePrefix}_kernel_transpose.npy`);
const bias = await buildConstantByNpy(this.builder_,
const bias = buildConstantByNpy(this.builder_,
`${this.weightsUrl_}/${namePrefix}_MatMul_bias.npy`);
const options = {
aTranspose: false,
bTranspose: true,
c: bias,
c: await bias,
};
let fc;
if (reshapeSize !== undefined) {
fc = this.builder_.gemm(this.builder_.reshape(
input, [null, reshapeSize]), weights, options);
await input, [null, reshapeSize]), await weights, options);
} else {
fc = this.builder_.gemm(input, weights, options);
fc = this.builder_.gemm(await input, await weights, options);
}
if (relu) {
fc = this.builder_.relu(fc);
Expand All @@ -74,28 +78,28 @@ export class FaceLandmarkNhwc {
const poolOptions =
{windowDimensions: [2, 2], strides: [2, 2], layout: 'nhwc'};

const conv0 = await this.buildConv_(input, 0);
const pool0 = await this.builder_.maxPool2d(conv0, poolOptions);
const conv0 = this.buildConv_(input, 0);
const pool0 = this.buildMaxPool2d(conv0, poolOptions);

const conv1 = await this.buildConv_(pool0, 1);
const conv2 = await this.buildConv_(conv1, 2);
const pool1 = await this.builder_.maxPool2d(conv2, poolOptions);
const conv1 = this.buildConv_(pool0, 1);
const conv2 = this.buildConv_(conv1, 2);
const pool1 = this.buildMaxPool2d(conv2, poolOptions);

const conv3 = await this.buildConv_(pool1, 3);
const conv4 = await this.buildConv_(conv3, 4);
const pool2 = await this.builder_.maxPool2d(conv4, poolOptions);
const conv3 = this.buildConv_(pool1, 3);
const conv4 = this.buildConv_(conv3, 4);
const pool2 = this.buildMaxPool2d(conv4, poolOptions);

const conv5 = await this.buildConv_(pool2, 5);
const conv6 = await this.buildConv_(conv5, 6);
const pool3 = await this.builder_.maxPool2d(
const conv5 = this.buildConv_(pool2, 5);
const conv6 = this.buildConv_(conv5, 6);
const pool3 = this.buildMaxPool2d(
conv6, {windowDimensions: [2, 2], layout: 'nhwc'});

const conv7 = await this.buildConv_(pool3, 7);
const fc0 = await this.buildFullyConnected_(
const conv7 = this.buildConv_(pool3, 7);
const fc0 = this.buildFullyConnected_(
conv7, 'dense', true, 6400);
const fc1 = await this.buildFullyConnected_(fc0, 'logits');
const fc1 = this.buildFullyConnected_(fc0, 'logits');

return fc1;
return await fc1;
}

async build(outputOperand) {
Expand Down
12 changes: 8 additions & 4 deletions facial_landmark_detection/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,16 +260,20 @@ async function main() {
contextOptions['numThreads'] = numThreads;
}
start = performance.now();
const fdOutputOperand = await fdInstance.load(contextOptions);
const fldOutputOperand = await fldInstance.load(contextOptions);
const [fdOutputOperand, fldOutputOperand] = await Promise.all([
fdInstance.load(contextOptions),
fldInstance.load(contextOptions),
]);
loadTime = (performance.now() - start).toFixed(2);
console.log(` done in ${loadTime} ms.`);
// UI shows model building progress
await ui.showProgressComponent('done', 'current', 'pending');
console.log('- Building... ');
start = performance.now();
await fdInstance.build(fdOutputOperand);
await fldInstance.build(fldOutputOperand);
await Promise.all([
fdInstance.build(fdOutputOperand),
fldInstance.build(fldOutputOperand),
]);
buildTime = (performance.now() - start).toFixed(2);
console.log(` done in ${buildTime} ms.`);
}
Expand Down
Loading

0 comments on commit 27039b3

Please sign in to comment.