Skip to content

Commit c9d3938

Browse files
authored
[js/web] adding conv fuse logic (microsoft#7604)
* adding conv fuse logic * fixing merge * fix file name in kebab case * fix lint error
1 parent 0f35730 commit c9d3938

7 files changed

Lines changed: 91 additions & 16 deletions

File tree

web/lib/onnxjs/backends/webgl/ops/conv-pack.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
import {Attribute} from '../../../attribute';
45
import {Logger} from '../../../instrument';
56
import {Conv} from '../../../ops/conv';
67
import {Tensor} from '../../../tensor';
@@ -36,6 +37,11 @@ export class WebGLConvPacked extends Conv {
3637
const outputShape = WebGLConv.calcOutputShape(xshape, kshape, this.dilations, this.pads, this.strides);
3738
const im2col = new WebGLIm2ColPacked(outputShape, kshape, this.dilations, this.pads, this.strides);
3839
const matmul = new WebGLMatMulPacked();
40+
if (this.activation) {
41+
const attributes = new Attribute(undefined);
42+
attributes.set('__internal_activation', 'string', (this.activation));
43+
matmul.initialize(attributes);
44+
}
3945
const reshape = new WebGLReshapePacked();
4046
// shape for kernel reshape
4147
const shape =

web/lib/onnxjs/backends/webgl/ops/conv.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import {getGlsl} from '../glsl-source';
1010
import {WebGLInferenceHandler} from '../inference-handler';
1111
import {Artifact, ProgramInfo, RunData, TextureLayout, WebGLOperator} from '../types';
1212
import {WebGLContext} from '../webgl-context';
13+
1314
import {WebGLConvPacked} from './conv-pack';
15+
import {getActicationSnippet} from './fuse-utils';
1416

1517
export class WebGLConv extends Conv {
1618
unpackedGroupedConvImpl: WebGLUnpackedGroupedConv;
@@ -66,7 +68,7 @@ export class WebGLUnpackedGroupedConv extends Conv implements WebGLOperator {
6668

6769
createProgramInfo(handler: WebGLInferenceHandler, inputs: Tensor[]): ProgramInfo {
6870
const hasBias = inputs.length > 2;
69-
const processBias = hasBias ? 'dotProd += getBias(output_channel);' : '';
71+
const processBias = hasBias ? 'value += getBias(output_channel);' : '';
7072
const xShape = inputs[0].dims.slice();
7173
const wShape = inputs[1].dims.slice();
7274
const outputChannelsPerGroup = wShape[0] / this.group;
@@ -85,18 +87,20 @@ export class WebGLUnpackedGroupedConv extends Conv implements WebGLOperator {
8587
const outputShape = WebGLConv.calcOutputShape(xShape, wShape, this.dilations, this.pads, this.strides);
8688
const glsl = getGlsl(handler.session.backend.glContext.version);
8789

90+
const {activationFunction, applyActivation} = getActicationSnippet(this.activation);
91+
8892
const shaderSource = `
8993
const ivec2 strides = ivec2(${this.strides[0]}, ${this.strides[1]});
9094
const ivec2 pads = ivec2(${this.pads[0]}, ${this.pads[1]});
91-
95+
${activationFunction}
9296
void main() {
9397
ivec4 coords = getOutputCoords();
9498
int batch = coords.x;
9599
int output_channel = coords.y;
96100
ivec2 xRCCorner = coords.zw * strides - pads;
97101
int group_id = output_channel / ${outputChannelsPerGroup};
98102
99-
float dotProd = 0.0;
103+
float value = 0.0;
100104
for (int wInChannel = 0; wInChannel < ${wShape[1]}; wInChannel++) {
101105
int input_channel = group_id * ${wShape[1]} + wInChannel;
102106
for (int wHeight = 0; wHeight < ${wShape[2]}; wHeight++) {
@@ -114,12 +118,13 @@ export class WebGLUnpackedGroupedConv extends Conv implements WebGLOperator {
114118
115119
float xVal = getX(batch, input_channel, xWidth, xHeight);
116120
float wVal = getW(output_channel, wInChannel, wWidth, wHeight);
117-
dotProd += xVal*wVal;
121+
value += xVal*wVal;
118122
}
119123
}
120124
}
121125
${processBias}
122-
${glsl.output} = vec4(dotProd, .0, .0, .0);
126+
${applyActivation}
127+
${glsl.output} = vec4(value, .0, .0, .0);
123128
}
124129
`;
125130
return {
@@ -215,7 +220,6 @@ export class WebGLUnpackedConv extends Conv {
215220
let blend = false;
216221
for (let k = 0; k < sharedDim; k += sharedDimReadSize) {
217222
Logger.verbose('MatMul2D', `k = ${k}, sharedDim: ${sharedDim}, readSize = ${sharedDimReadSize}`);
218-
219223
if (k === sharedDimReadSize) {
220224
blend = true;
221225
gl.enable(gl.BLEND);
@@ -248,6 +252,7 @@ export class WebGLUnpackedConv extends Conv {
248252
const im2colDims = WebGLUnpackedConv.calcIm2ColDims(xshape, kshape, outputShape, 4);
249253
const outputLayout = inferenceHandler.createTextureLayoutFromShape(
250254
im2colDims, 4, [im2colDims[0], im2colDims[1], im2colDims[2], im2colDims[3] * 4], {breakAxis: 3});
255+
251256
const shaderSource = `
252257
const int XC = ${xshape[1]};
253258
const int XH = ${xshape[2]};
@@ -263,13 +268,12 @@ export class WebGLUnpackedConv extends Conv {
263268
const int KHKW = KH*KW;
264269
const int XCKHKW = XC * KHKW;
265270
const int outputChannels = 4;
266-
267271
vec4 process(int indices[${rank}]) {
268272
int b = indices[0]; // batch size
269273
int oh = indices[1] * strideH - padH; //output height
270274
int ow = indices[2] * strideW - padW; //output width
271275
int p = indices[3] * outputChannels; //patch
272-
vec4 v = vec4(0.0);
276+
vec4 value = vec4(0.0);
273277
for(int i=0; i < outputChannels; ++i) {
274278
if(p < XCKHKW) {
275279
int patchC = p / KHKW;
@@ -286,12 +290,12 @@ export class WebGLUnpackedConv extends Conv {
286290
xh2 < XH &&
287291
xw2 >= 0 &&
288292
xw2 < XW) {
289-
v[i] = _X(x);
293+
value[i] = _X(x);
290294
}
291295
}
292296
++p;
293297
}
294-
return v;
298+
return value;
295299
}
296300
`;
297301
return {
@@ -321,16 +325,20 @@ export class WebGLUnpackedConv extends Conv {
321325
const outputLayout = inferenceHandler.createTextureLayoutFromShape(outputShape);
322326
const initValue = (inputs.length < 3) ? '0.0' : '_B(b)';
323327
const sharedDim = im2colLayout.shape[3];
324-
const blendEnabled = inferenceHandler.session.backend.glContext.isBlendSupported;
328+
const blendEnabled = inferenceHandler.session.backend.glContext.isBlendSupported && !this.activation;
325329
const sharedDimReadSize = blendEnabled && inferenceHandler.session.backend.matmulMaxBatchSize ?
326330
this.calcSharedDimReadSize(inferenceHandler.session.backend.matmulMaxBatchSize, sharedDim) :
327331
sharedDim;
328332
const samplers = ['Im2Col', 'K'];
329333
if (inputs.length === 3) {
330334
samplers.push('B');
331335
}
336+
337+
const {activationFunction, applyActivation} = getActicationSnippet(this.activation);
338+
332339
const glsl = getGlsl(inferenceHandler.session.backend.glContext.version);
333340
const shaderSource = `
341+
${activationFunction}
334342
float process(int indices[${rank}]) {
335343
int b[1];
336344
b[0] = indices[1];
@@ -341,15 +349,16 @@ export class WebGLUnpackedConv extends Conv {
341349
int im2colOffset = im2col[0] * ${im2colLayout.strides[0]} + im2col[1] * ${
342350
im2colLayout.strides[1]} + im2col[2] * ${im2colLayout.strides[2]} + sharedDimOffset;
343351
int kernelOffset = indices[1] * ${kLayout.strides[0]} + sharedDimOffset;
344-
float sum = sharedDimOffset == 0 ? ${initValue} : 0.0;
352+
float value = sharedDimOffset == 0 ? ${initValue} : 0.0;
345353
for (int i = 0; i < ${sharedDimReadSize}; ++i) {
346354
vec2 im2colCoords = offsetToCoords(im2colOffset, ${im2colLayout.width}, ${im2colLayout.height});
347355
vec2 kernelCoords = offsetToCoords(kernelOffset, ${kLayout.width}, ${kLayout.height});
348-
sum += dot(${glsl.texture2D}(Im2Col, im2colCoords), ${glsl.texture2D}(K, kernelCoords));
356+
value += dot(${glsl.texture2D}(Im2Col, im2colCoords), ${glsl.texture2D}(K, kernelCoords));
349357
++im2colOffset;
350358
++kernelOffset;
351359
}
352-
return sum;
360+
${applyActivation}
361+
return value;
353362
}`;
354363
return {
355364
inputLayouts: inputs.length === 3 ? [im2colLayout, kLayout, bLayout!] : [im2colLayout, kLayout],
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import {glslRelu, glslSigmoid} from './unary-op';
5+
6+
export function getActicationSnippet(activation: string) {
7+
let activationFunction = '';
8+
let activationName = '';
9+
switch (activation) {
10+
case 'Relu':
11+
activationName = glslRelu().name;
12+
activationFunction = glslRelu().body;
13+
break;
14+
case 'Sigmoid':
15+
activationName = glslSigmoid().name;
16+
activationFunction = glslSigmoid().body;
17+
break;
18+
default:
19+
// TODO: adding other activations that can be fused.
20+
activationName = '';
21+
activationFunction = '';
22+
}
23+
const applyActivation = activation ? `
24+
value = ${activationName}(value);` :
25+
'';
26+
return {activationFunction, applyActivation};
27+
}

web/lib/onnxjs/backends/webgl/ops/matmul-pack.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {Tensor} from '../../../tensor';
66
import {BroadcastUtil} from '../../../util';
77
import {WebGLInferenceHandler} from '../inference-handler';
88
import {ProgramInfo, RunData, WebGLOperator} from '../types';
9+
import {getActicationSnippet} from './fuse-utils';
910

1011
export class WebGLMatMulPacked extends MatMul implements WebGLOperator {
1112
run(inferenceHandler: WebGLInferenceHandler, inputs: Tensor[]): Tensor[] {
@@ -25,8 +26,11 @@ export class WebGLMatMulPacked extends MatMul implements WebGLOperator {
2526
const aRank = aShape.length;
2627
const bRank = bShape.length;
2728
const sharedDim = aShape[aShape.length - 1];
29+
30+
const {activationFunction, applyActivation} = getActicationSnippet(this.activation);
2831
// TODO:fix broadcasting
2932
const shaderSource = `
33+
${activationFunction}
3034
vec4 process(int indices[${rank}]) {
3135
int a[${aRank}];
3236
int b[${bRank}];
@@ -41,6 +45,7 @@ export class WebGLMatMulPacked extends MatMul implements WebGLOperator {
4145
value += ${getA(aRank)}.ggaa * ${getB(bRank)}.baba;
4246
}
4347
${processBias}
48+
${applyActivation}
4449
return value;
4550
}`;
4651
return {

web/lib/onnxjs/graph.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -737,4 +737,27 @@ class GraphImpl implements Graph, Graph.Transformer {
737737
nodeIndex++;
738738
}
739739
}
740-
}
740+
741+
isActivation(n: Node): boolean {
742+
switch (n.opType) {
743+
// TODO: add other activation methods
744+
case 'Relu':
745+
case 'Sigmoid':
746+
return true;
747+
default:
748+
return false;
749+
}
750+
}
751+
752+
fuseConvActivationNodes() {
753+
for (const node of this._nodes) {
754+
if (node.opType === 'Conv') {
755+
const next = this._allData[node.outputs[0]]._to;
756+
if (next.length === 1 && this.isActivation(this._nodes[next[0]])) {
757+
node.attributes.set('__internal_activation', 'string', (this._nodes[next[0]].opType));
758+
this.deleteNode(next[0]);
759+
}
760+
}
761+
}
762+
}
763+
}

web/lib/onnxjs/ops/conv.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export abstract class Conv implements Operator {
1717
this.kernelShape = attributes.getInts('kernel_shape', []);
1818
this.pads = attributes.getInts('pads', [0, 0, 0, 0]);
1919
this.strides = attributes.getInts('strides', [1, 1]);
20+
this.activation = attributes.getString('__internal_activation', '');
2021
}
2122

2223
checkInputs(inputs: Tensor[]): boolean {
@@ -88,4 +89,5 @@ export abstract class Conv implements Operator {
8889
protected kernelShape: number[];
8990
protected pads: number[];
9091
protected strides: number[];
92+
protected activation: string;
9193
}

web/lib/onnxjs/ops/matmul.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import {Tensor} from '../tensor';
99
export abstract class MatMul implements Operator {
1010
abstract run(inferenceHandler: InferenceHandler, inputs: Tensor[]): Tensor[]|Promise<Tensor[]>;
1111

12-
initialize(_attributes: Attribute): void {}
12+
initialize(attributes: Attribute): void {
13+
this.activation = attributes.getString('__internal_activation', '');
14+
}
1315

1416
checkInputs(inputs: Tensor[]): boolean {
1517
if (!inputs || inputs.length !== 2) {
@@ -38,4 +40,5 @@ export abstract class MatMul implements Operator {
3840

3941
return true;
4042
}
43+
protected activation: string;
4144
}

0 commit comments

Comments
 (0)