1
// Copyright (c) Microsoft Corporation. All rights reserved.
2
// Licensed under the MIT License.
4
import { AttributeWithCacheKey, createAttributeWithCacheKey } from '../../../attribute-with-cache-key';
5
import { Graph } from '../../../graph';
6
import { OperatorImplementation, OperatorInitialization } from '../../../operators';
7
import { Tensor } from '../../../tensor';
8
import { PoolConvUtil, ShapeUtil } from '../../../util';
9
import { WebGLInferenceHandler } from '../inference-handler';
10
import { ProgramInfo, ProgramMetadata, TextureType } from '../types';
12
export interface AveragePoolAttributes extends AttributeWithCacheKey {
13
readonly autoPad: string;
14
readonly ceilMode: number;
15
readonly countIncludePad: boolean;
16
readonly kernelShape: readonly number[];
17
readonly strides: readonly number[];
18
readonly pads: readonly number[];
21
export const averagePool: OperatorImplementation<AveragePoolAttributes> = (
22
inferenceHandler: WebGLInferenceHandler,
24
attributes: AveragePoolAttributes,
26
validateInputs(inputs);
30
inputTypes: [TextureType.unpacked],
31
cacheHint: attributes.cacheKey,
33
const output = inferenceHandler.run(
34
{ ...metadata, get: () => createAveragePoolProgramInfo(inputs, metadata, false, attributes) },
40
export const parseAveragePoolAttributes: OperatorInitialization<AveragePoolAttributes> = (
42
): AveragePoolAttributes => {
43
const autoPad = node.attributes.getString('auto_pad', 'NOTSET');
44
const ceilMode = node.attributes.getInt('ceil_mode', 0);
45
const countIncludePad = node.attributes.getInt('count_include_pad', 0) === 0 ? false : true;
46
const kernelShape = node.attributes.getInts('kernel_shape');
47
const strides = node.attributes.getInts('strides', []);
48
const pads = node.attributes.getInts('pads', []);
50
// TODO: support attribute 'ceil_mode'
52
throw new Error('using ceil() in shape computation is not yet supported for AveragePool');
55
return createAttributeWithCacheKey({ autoPad, ceilMode, countIncludePad, kernelShape, strides, pads });
58
const createAveragePoolProgramInfo = (
60
metadata: ProgramMetadata,
61
isGlobalOperator: boolean,
62
attributes: AveragePoolAttributes,
64
const [adjustedAttributes, outputShape] = getAdjustedPoolAttributesAndOutputShape(
69
const kernelSize = ShapeUtil.size(adjustedAttributes.kernelShape);
70
const op1 = 'value += _X(x);';
72
if (adjustedAttributes.countIncludePad) {
73
op2 += `value /= float(${kernelSize});`;
75
op2 += `value /= float(${kernelSize} - pad);`;
77
const poolingCode = generatePoolingCode(inputs[0].dims, adjustedAttributes, op1, op2, '0.0');
78
const shaderSource = `
83
output: { dims: outputShape, type: inputs[0].type, textureType: TextureType.unpacked },
88
export const globalAveragePool: OperatorImplementation<AveragePoolAttributes> = (
89
inferenceHandler: WebGLInferenceHandler,
91
attributes: AveragePoolAttributes,
93
validateInputs(inputs);
95
name: 'GlobalAveragePool',
97
inputTypes: [TextureType.unpacked],
98
cacheHint: `${attributes.countIncludePad}`,
100
const output = inferenceHandler.run(
101
{ ...metadata, get: () => createAveragePoolProgramInfo(inputs, metadata, true, attributes) },
107
export const parseGlobalAveragePoolAttributes: OperatorInitialization<AveragePoolAttributes> = (
109
): AveragePoolAttributes => {
110
const countIncludePad = node.attributes.getInt('count_include_pad', 0) === 0 ? false : true;
111
return createAttributeWithCacheKey({
121
export interface MaxPoolAttributes extends AveragePoolAttributes {
122
readonly storageOrder: number;
123
readonly dilations: number[];
126
export const maxPool: OperatorImplementation<MaxPoolAttributes> = (
127
inferenceHandler: WebGLInferenceHandler,
129
attributes: MaxPoolAttributes,
131
validateInputs(inputs);
135
inputTypes: [TextureType.unpacked],
136
cacheHint: attributes.cacheKey,
138
const output = inferenceHandler.run(
139
{ ...metadata, get: () => createMaxPoolProgramInfo(inputs, metadata, false, attributes) },
145
export const parseMaxPoolAttributes: OperatorInitialization<MaxPoolAttributes> = (
147
): MaxPoolAttributes => {
148
const autoPad = node.attributes.getString('auto_pad', 'NOTSET');
149
const ceilMode = node.attributes.getInt('ceil_mode', 0);
150
const kernelShape = node.attributes.getInts('kernel_shape');
151
const strides = node.attributes.getInts('strides', []);
152
const pads = node.attributes.getInts('pads', []);
153
const storageOrder = node.attributes.getInt('storage_order', 0);
154
const dilations = node.attributes.getInts('dilations', []);
156
// TODO: support attribute 'ceil_mode' and 'storage_order'
157
if (storageOrder !== 0) {
158
throw new Error('column major storage order is not yet supported for MaxPool');
160
if (ceilMode !== 0) {
161
throw new Error('using ceil() in shape computation is not yet supported for MaxPool');
164
return createAttributeWithCacheKey({
167
countIncludePad: false,
176
const createMaxPoolProgramInfo = (
178
metadata: ProgramMetadata,
179
isGlobalOperator: boolean,
180
attributes: MaxPoolAttributes,
182
const [adjustedAttributes, outputShape] = getAdjustedPoolAttributesAndOutputShape(
188
value = max(_X(x), value);
191
const poolingCode = generatePoolingCode(inputs[0].dims, adjustedAttributes, op1, op2, '-1e5');
192
const shaderSource = `
197
output: { dims: outputShape, type: inputs[0].type, textureType: TextureType.unpacked },
202
const getAdjustedPoolAttributesAndOutputShape = (
204
attributes: AveragePoolAttributes | MaxPoolAttributes,
205
isGlobalOperator: boolean,
206
): [AveragePoolAttributes | MaxPoolAttributes, number[]] => {
207
const inputShape = inputs[0].dims.slice();
208
const hasDilations = Object.hasOwnProperty.call(attributes, 'dilations');
209
const kernelShape = attributes.kernelShape.slice();
210
const strides = attributes.strides.slice();
211
const dilations: number[] = hasDilations ? (attributes as MaxPoolAttributes).dilations.slice() : [];
212
const pads = attributes.pads.slice();
213
PoolConvUtil.adjustPoolAttributes(isGlobalOperator, inputShape, kernelShape, strides, dilations, pads);
215
const outputShape = PoolConvUtil.computePoolOutputShape(
225
const newAttributes = Object.assign({}, attributes);
227
Object.assign(newAttributes, { kernelShape, strides, pads, dilations, cacheKey: attributes.cacheKey });
229
Object.assign(newAttributes, { kernelShape, strides, pads, cacheKey: attributes.cacheKey });
231
return [newAttributes, outputShape];
234
const globalMaxPoolAttributes = {
237
countIncludePad: false,
246
const globalMaxPoolMetadata = {
247
name: 'GlobalMaxPool',
249
inputTypes: [TextureType.unpacked],
252
export const globalMaxPool = (inferenceHandler: WebGLInferenceHandler, inputs: Tensor[]): Tensor[] => {
253
validateInputs(inputs);
254
const output = inferenceHandler.run(
256
...globalMaxPoolMetadata,
257
get: () => createMaxPoolProgramInfo(inputs, globalMaxPoolMetadata, true, globalMaxPoolAttributes),
264
const validateInputs = (inputs: Tensor[]): void => {
265
if (!inputs || inputs.length !== 1) {
266
throw new Error('Pool ops requires 1 input.');
268
if (inputs[0].type !== 'float32' && inputs[0].type !== 'float64') {
269
throw new Error('Invalid input type.');
273
const generatePoolingCode = (
274
inputDims: readonly number[],
275
attributes: AveragePoolAttributes,
280
const rank = inputDims.length;
281
if (attributes.kernelShape.length <= 2) {
282
const kw = attributes.kernelShape[attributes.kernelShape.length - 1];
283
const sw = attributes.strides[attributes.strides.length - 1];
284
const pwStart = attributes.pads[attributes.pads.length / 2 - 1];
285
const pwEnd = attributes.pads[attributes.pads.length - 1];
286
const dimW = inputDims[rank - 1];
290
if (pwStart + pwEnd !== 0) {
292
for (int i = 0; i < ${kw}; i++) {
293
x[${rank} - 1] = indices[${rank} - 1] * ${sw} - ${pwStart} + i;
294
if (x[${rank} - 1] < 0 || x[${rank} - 1] >= ${dimW}) {
302
for (int i = 0; i < ${kw}; i++) {
303
x[${rank} - 1] = indices[${rank} - 1] * ${sw} - ${pwStart} + i;
308
if (attributes.kernelShape.length === 2) {
309
const kh = attributes.kernelShape[attributes.kernelShape.length - 2];
310
const sh = attributes.strides[attributes.strides.length - 2];
311
const phStart = attributes.pads[attributes.pads.length / 2 - 2];
312
const phEnd = attributes.pads[attributes.pads.length - 2];
313
const dimH = inputDims[rank - 2];
314
if (phStart + phEnd !== 0) {
316
for (int j = 0; j < ${kh}; j++) {
317
x[${rank} - 2] = indices[${rank} - 2] * ${sh} - ${phStart} + j;
318
if (x[${rank} - 2] < 0 || x[${rank} - 2] >= ${dimH}) {
325
for (int j = 0; j < ${kh}; j++) {
326
x[${rank} - 2] = indices[${rank} - 2] * ${sh} - ${phStart} + j;
334
const poolingCode = `
335
float process(int indices[${rank}]) {
339
float value = ${start};
350
const kernelSize = ShapeUtil.size(attributes.kernelShape);
351
const kernelStrides = ShapeUtil.computeStrides(attributes.kernelShape);
352
const stridesRank = kernelStrides.length;
353
const padsRank = attributes.pads.length;
354
const offsetToIndicesFunction = offsetToIndices(stridesRank);
355
const copyInputDims = copyArray(inputDims, 'inputDims');
356
const copyPads = copyArray(attributes.pads, 'pads');
357
const copyKernelStrides = copyArray(kernelStrides, 'kernelStrides');
358
const copyStrides = copyArray(attributes.strides, 'strides');
359
const hasPads = attributes.pads.reduce((sum, cur) => sum + cur);
363
if (x[j] >= inputDims[j] || x[j] < 0) {
378
const poolingCode = `
379
${offsetToIndicesFunction}
380
float process(int indices[${rank}]) {
383
int offset[${stridesRank}];
384
int pads[${padsRank}];
385
int inputDims[${rank}];
386
int kernelStrides[${stridesRank}];
387
int strides[${stridesRank}];
393
float value = ${start};
396
for (int i = 0; i < ${kernelSize}; i++) {
397
offsetToIndices(i, kernelStrides, offset);
399
for (int j = ${rank} - ${stridesRank}; j < ${rank}; j++) {
400
x[j] = indices[j] * strides[j - ${rank} + ${stridesRank}]
401
+ offset[j - ${rank} + ${stridesRank}] - pads[j - 2];
413
const copyArray = (array: readonly number[], arrayName: string): string => {
415
for (let i = 0; i < array.length; i++) {
417
${arrayName}[${i}] = ${array[i]};
423
const offsetToIndices = (rank: number): string => `
424
void offsetToIndices(int offset, int[${rank}] strides, out int[${rank}] indices) {
428
for (int i = 0; i < ${rank} - 1; ++i) {
429
indices[i] = offset / strides[i];
430
offset -= indices[i] * strides[i];
432
indices[${rank} - 1] = offset;