[JS/Web] Added uniforms to Reduce, Resize and Split Ops. (#18727)

### Description
<!-- Describe your changes. -->
Added uniforms to Reduce op


### Motivation and Context
<!-- - Why is this change required? What problem does it solve?
- If it fixes an open issue, please link to the issue here. -->
Improve perforamnce.
This commit is contained in:
satyajandhyala 2023-12-12 11:12:23 -08:00 committed by GitHub
parent 81796a3081
commit 0ca84549ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 217 additions and 202 deletions

View file

@ -23,7 +23,7 @@ import {multiHeadAttention, parseMultiHeadAttentionAttributes} from './ops/multi
import {pad, parsePadAttributes} from './ops/pad';
import * as pool from './ops/pool';
import {range} from './ops/range';
import {parseReduceAttributes, reduceL1, reduceL2, reduceLogSum, reduceLogSumExp, reduceMax, reduceMean, reduceMin, reduceProd, reduceSum, reduceSumSquare} from './ops/reduce';
import {reduceL1, reduceL2, reduceLogSum, reduceLogSumExp, reduceMax, reduceMean, reduceMin, reduceProd, reduceSum, reduceSumSquare} from './ops/reduce';
import {parseResizeAttributes, resize} from './ops/resize';
import {parseSkipLayerNormAttributes, skipLayerNorm} from './ops/skip-layer-norm';
import {parseSliceAttributes, slice} from './ops/slice';
@ -99,16 +99,16 @@ export const WEBGPU_OP_RESOLVE_RULES: Map<string, OperatorImplementation> = new
['Pow', [binaryOps.pow]],
['Range', [range]],
['Reciprocal', [unaryOps.reciprocal]],
['ReduceMin', [reduceMin, parseReduceAttributes]],
['ReduceMean', [reduceMean, parseReduceAttributes]],
['ReduceMax', [reduceMax, parseReduceAttributes]],
['ReduceSum', [reduceSum, parseReduceAttributes]],
['ReduceProd', [reduceProd, parseReduceAttributes]],
['ReduceL1', [reduceL1, parseReduceAttributes]],
['ReduceL2', [reduceL2, parseReduceAttributes]],
['ReduceLogSum', [reduceLogSum, parseReduceAttributes]],
['ReduceLogSumExp', [reduceLogSumExp, parseReduceAttributes]],
['ReduceSumSquare', [reduceSumSquare, parseReduceAttributes]],
['ReduceMin', [reduceMin]],
['ReduceMean', [reduceMean]],
['ReduceMax', [reduceMax]],
['ReduceSum', [reduceSum]],
['ReduceProd', [reduceProd]],
['ReduceL1', [reduceL1]],
['ReduceL2', [reduceL2]],
['ReduceLogSum', [reduceLogSum]],
['ReduceLogSumExp', [reduceLogSumExp]],
['ReduceSumSquare', [reduceSumSquare]],
['Relu', [unaryOps.relu]],
['Resize', [resize, parseResizeAttributes]],
['Sigmoid', [unaryOps.sigmoid]],

View file

@ -33,23 +33,23 @@ export const argMin = (context: ComputeContext, attributes: ArgMinMaxAttributes)
const idxZero = [];
for (let k = 0; k < input.rank; k++) {
if (axes.indexOf(k) >= 0 || axes.length === 0) {
idxZero.push(`inputIndices[${k}] = 0;`); // first element
idxZero.push(`input_indices[${k}] = 0;`); // first element
}
}
return [
`${idxZero.join('\n')}`, `var value = ${input.getByOffset('inputOffset')};\nvar bestIndex : i32 = 0;`,
`if (${input.getByOffset('inputOffset')} ${attributes.selectLastIndex > 0 ? '<=' : '<'} value) {
value = ${input.getByOffset('inputOffset')};
bestIndex = i32(lastIndex);
`${idxZero.join('\n')}`, `var value = ${input.getByIndices('input_indices')};\nvar best_index : i32 = 0;`,
`if (${input.getByIndices('input_indices')} ${attributes.selectLastIndex > 0 ? '<=' : '<'} value) {
value = ${input.getByIndices('input_indices')};
best_index = i32(last_index);
}`,
'', output.setByOffset('global_idx', 'bestIndex')
'', output.setByOffset('global_idx', 'best_index')
];
};
context.compute(
createReduceProgramInfo(
'ArgMin', {hint: attributes.cacheKey}, [context.inputs[0]], argMinMaxOp, [attributes.axis], DataType.int64,
attributes.keepDims),
'ArgMin', {hint: attributes.cacheKey, inputDependencies: ['rank']}, [context.inputs[0]], argMinMaxOp,
[attributes.axis], DataType.int64, attributes.keepDims),
{inputs: [0]});
};
@ -59,23 +59,23 @@ export const argMax = (context: ComputeContext, attributes: ArgMinMaxAttributes)
const idxZero = [];
for (let k = 0; k < input.rank; k++) {
if (axes.indexOf(k) >= 0 || axes.length === 0) {
idxZero.push(`inputIndices[${k}] = 0;`); // first element
idxZero.push(`input_indices[${k}] = 0;`); // first element
}
}
return [
`${idxZero.join('\n')}`, `var value = ${input.getByOffset('inputOffset')};\nvar bestIndex : i32 = 0;`,
`if (${input.getByOffset('inputOffset')} ${attributes.selectLastIndex > 0 ? '>=' : '>'} value) {
value = ${input.getByOffset('inputOffset')};
bestIndex = i32(lastIndex);
`${idxZero.join('\n')}`, `var value = ${input.getByIndices('input_indices')};\nvar best_index : i32 = 0;`,
`if (${input.getByIndices('input_indices')} ${attributes.selectLastIndex > 0 ? '>=' : '>'} value) {
value = ${input.getByIndices('input_indices')};
best_index = i32(last_index);
}`,
'', output.setByOffset('global_idx', 'bestIndex')
'', output.setByOffset('global_idx', 'best_index')
];
};
context.compute(
createReduceProgramInfo(
'argMax', {hint: attributes.cacheKey}, [context.inputs[0]], argMinMaxOp, [attributes.axis], DataType.int64,
attributes.keepDims),
'argMax', {hint: attributes.cacheKey, inputDependencies: ['rank']}, [context.inputs[0]], argMinMaxOp,
[attributes.axis], DataType.int64, attributes.keepDims),
{inputs: [0]});
};

View file

@ -7,7 +7,7 @@ import {ShapeUtil} from '../../util';
import {AttributeWithCacheKey, createAttributeWithCacheKey} from '../attribute-with-cache-key';
import {ComputeContext, ProgramInfo} from '../types';
import {createTensorShapeVariables, inputVariable, outputVariable, ShaderHelper} from './common';
import {createTensorShapeVariables, getElementAt, inputVariable, outputVariable, ShaderHelper} from './common';
export interface CumSumAttributes extends AttributeWithCacheKey {
@ -26,7 +26,7 @@ const createCumsumProgramInfo =
const axis = ShapeUtil.normalizeAxis(axisValue, rank);
const getShaderSource = (shaderHelper: ShaderHelper) => {
const index = ` i32(${input.indicesGet('inputIndices', 'uniforms.axis')}) `;
const max = rank === 1 ? 'i32(uniforms.input_shape)' : 'i32(uniforms.input_shape[uniforms.axis])';
const max = getElementAt('uniforms.input_shape', 'uniforms.axis', rank);
const lowerLimit = attributes.reverse ? index + (attributes.exclusive ? ' + 1' : '') : '0';
const upperLimit = attributes.reverse ? max : index + (attributes.exclusive ? '' : ' + 1');
return `

View file

@ -7,7 +7,7 @@ import {ShapeUtil} from '../../util';
import {AttributeWithCacheKey, createAttributeWithCacheKey} from '../attribute-with-cache-key';
import {ComputeContext, ProgramInfo, ProgramShaderCacheInfo} from '../types';
import {IndicesHelper, inputVariable, outputVariable, ShaderHelper} from './common';
import {createTensorShapeVariables, IndicesHelper, inputVariable, outputVariable, ShaderHelper} from './common';
import {reduceL1Shared, reduceL2Shared, reduceLogSumExpShared, reduceLogSumShared, reduceMaxShared, reduceMeanShared, reduceMinShared, reduceProdShared, reduceSumShared, reduceSumSquareShared} from './reduce-shared';
const validateInputs = (inputs: readonly TensorView[]): void => {
@ -30,14 +30,14 @@ export type ReduceOp =
(input: IndicesHelper, output: IndicesHelper,
axes: readonly number[]) => [string, string, string, string, ...string[]];
const noOp: ReduceOp = (input) => ['', '', `var value = ${input.getByOffset('inputOffset')};`, ''];
const noOp: ReduceOp = (input) => ['', '', `var value = ${input.getByIndices('input_indices')};`, ''];
export const createReduceProgramInfo =
(name: string, shaderCache: ProgramShaderCacheInfo, inputs: readonly TensorView[], reduceOp: ReduceOp,
axesInput: number[], outputDataType: DataType, keepDims = false, noopWithEmptyAxes = false): ProgramInfo => {
const outputShape: number[] = [];
const inputShape = inputs[0].dims;
const axes = ShapeUtil.normalizeAxes(axesInput, inputs[0].dims.length);
const inputRank = inputShape.length;
const axes = ShapeUtil.normalizeAxes(axesInput, inputRank);
const reduceOnAllAxes = !noopWithEmptyAxes && axes.length === 0;
inputShape.forEach((d, i) => {
if (reduceOnAllAxes || axes.indexOf(i) >= 0) {
@ -48,53 +48,50 @@ export const createReduceProgramInfo =
outputShape.push(d);
}
});
const outputRank = outputShape.length;
const outputSize = ShapeUtil.size(outputShape);
const getShaderSource = (shaderHelper: ShaderHelper) => {
const idxCopy: string[] = []; // copy output indexes to input indexes
const idxCopy: string[] = []; // copy output indexes to input indexes
const input = inputVariable('_A', inputs[0].dataType, inputRank);
const output = outputVariable('output', outputDataType, outputRank);
const ops = reduceOp(input, output, axes);
let reduceOps = ops[2];
const input = inputVariable('_A', inputs[0].dataType, inputShape);
const output = outputVariable('output', outputDataType, outputShape);
const ops = reduceOp(input, output, axes);
const inputOffsetAssignment = `inputOffset = ${input.indicesToOffset('inputIndices')};`;
const initinputOffsetLet = `let ${inputOffsetAssignment};`;
const initinputOffsetVar = `var ${inputOffsetAssignment};`;
const initinputOffset = (ops[1] === '') ? '' : initinputOffsetVar;
let reduceOps = ((ops[1] === '') ? initinputOffsetLet : inputOffsetAssignment) + '\n' + ops[2];
for (let k = 0, l = 0; k < inputs[0].dims.length; k++) {
// if this axis is reduced
if (reduceOnAllAxes || axes.indexOf(k) >= 0) {
if (keepDims) {
for (let k = 0, l = 0; k < inputRank; k++) {
// if this axis is reduced
if (reduceOnAllAxes || axes.indexOf(k) >= 0) {
if (keepDims) {
l++;
}
// loop over the d-th axis
reduceOps = `for(var j${k}: u32 = 0; j${k} < ${inputShape[k]}; j${k}++) {
${ops[2].includes('last_index') ? `let last_index = j${k};` : ''}
${input.indicesSet('input_indices', k, `j${k}`)}
${reduceOps}
}`;
} else {
idxCopy.push(`${input.indicesSet('input_indices', k, output.indicesGet('output_indices', l))};`);
l++;
}
// loop over the d-th axis
reduceOps = `for(var j${k}: u32 = 0; j${k} < ${inputs[0].dims[k]}; j${k}++) {
${ops[2].includes('lastIndex') ? `let lastIndex = j${k};` : ''}
${input.indicesSet('inputIndices', k, `j${k}`)}
${reduceOps}
}`;
} else {
idxCopy.push(`${input.indicesSet('inputIndices', k, output.indicesGet('outputIndices', l))};`);
l++;
}
}
return `
const outputSize = ShapeUtil.size(outputShape);
const getShaderSource = (shaderHelper: ShaderHelper) => `
${shaderHelper.declareVariables(input, output)}
${shaderHelper.registerUniform('output_size', 'u32').declareVariables(input, output)}
${shaderHelper.mainStart()}
${shaderHelper.guardAgainstOutOfBoundsWorkgroupSizes(outputSize)}
var inputIndices: ${input.type.indices};
let outputIndices = ${output.offsetToIndices('global_idx')};
${shaderHelper.guardAgainstOutOfBoundsWorkgroupSizes('uniforms.output_size')}
var input_indices: ${input.type.indices};
let output_indices = ${output.offsetToIndices('global_idx')};
${idxCopy.join('\n')}
${ops[0]} // init ops for reduce max/min
${initinputOffset}
${ops[1]}
${reduceOps}
${ops[3]}
${ops.length === 4 ? output.setByOffset('global_idx', 'value') : ops.slice(4).join('\n')}
}`;
};
return {
name,
@ -102,7 +99,11 @@ export const createReduceProgramInfo =
getShaderSource,
getRunData: () => ({
outputs: [{dims: outputShape, dataType: outputDataType}],
dispatchGroup: {x: Math.ceil(outputSize / 64 /* workgroup size */)}
dispatchGroup: {x: Math.ceil(outputSize / 64 /* workgroup size */)},
programUniforms: [
{type: 'uint32', data: outputSize}, ...createTensorShapeVariables(inputShape),
...createTensorShapeVariables(outputShape)
]
}),
};
};
@ -125,7 +126,7 @@ const runReduceProgram =
context.compute(
createReduceProgramInfo(
name, {hint: updatedAttributes.cacheKey}, [inputs[0]],
name, {hint: updatedAttributes.cacheKey, inputDependencies: ['rank']}, [inputs[0]],
updatedAttributes.noopWithEmptyAxes && updatedAttributes.axes.length === 0 ? noOp : reduceOp,
updatedAttributes.axes, inputs[0].dataType, updatedAttributes.keepDims,
updatedAttributes.noopWithEmptyAxes),
@ -137,7 +138,7 @@ const reduceLogSumNaive = (context: ComputeContext, attributes: ReduceAttributes
const reduceOp: ReduceOp = (input, output) =>
[`var value = ${output.type.storage}(0);`,
'',
`value += ${input.getByOffset('inputOffset')};`,
`value += ${input.getByIndices('input_indices')};`,
'value = log(value);',
];
runReduceProgram(context, 'ReduceLogSum', attributes, reduceOp);
@ -148,7 +149,7 @@ const reduceL1Naive = (context: ComputeContext, attributes: ReduceAttributes): v
const reduceOp: ReduceOp = (input, output) =>
[`var value = ${output.type.storage}(0);`,
'',
`value += abs(${input.getByOffset('inputOffset')});`,
`value += abs(${input.getByIndices('input_indices')});`,
'',
];
runReduceProgram(context, 'ReduceL1', attributes, reduceOp);
@ -159,7 +160,7 @@ const reduceL2Naive = (context: ComputeContext, attributes: ReduceAttributes): v
const reduceOp: ReduceOp = (input, output) =>
[`var t = ${output.type.value}(0); var value = ${output.type.value}(0);`,
'',
`t = ${input.getByOffset('inputOffset')}; value += (t * t);`,
`t = ${input.getByIndices('input_indices')}; value += (t * t);`,
'value = sqrt(value);',
];
runReduceProgram(context, 'ReduceL2', attributes, reduceOp);
@ -170,7 +171,7 @@ const reduceLogSumExpNaive = (context: ComputeContext, attributes: ReduceAttribu
const reduceOp: ReduceOp = (input, output) =>
[`var value = ${output.type.storage}(0);`,
'',
`value += exp(${input.getByOffset('inputOffset')});`,
`value += exp(${input.getByIndices('input_indices')});`,
'value = log(value);',
];
runReduceProgram(context, 'ReduceLogSumExp', attributes, reduceOp);
@ -182,14 +183,14 @@ const reduceMaxNaive = (context: ComputeContext, attributes: ReduceAttributes):
const idxZero = [];
for (let k = 0; k < input.rank; k++) {
if (axes.indexOf(k) >= 0 || axes.length === 0) {
idxZero.push(input.indicesSet('inputIndices', k, 0));
idxZero.push(input.indicesSet('input_indices', k, 0));
}
}
return [
`${idxZero.join('\n')}`,
`var value = ${input.getByOffset('inputOffset')};`,
`value = max(value, ${input.getByOffset('inputOffset')});`,
`var value = ${input.getByIndices('input_indices')};`,
`value = max(value, ${input.getByIndices('input_indices')});`,
'',
];
};
@ -210,7 +211,7 @@ const reduceMeanNaive = (context: ComputeContext, attributes: ReduceAttributes):
return [
'var sum = f32(0);',
'',
`sum += f32(${input.getByOffset('inputOffset')});`,
`sum += f32(${input.getByIndices('input_indices')});`,
`let value = ${output.type.value}(sum / ${size});`,
];
};
@ -223,14 +224,14 @@ const reduceMinNaive = (context: ComputeContext, attributes: ReduceAttributes):
const idxZero = [];
for (let k = 0; k < input.rank; k++) {
if (axes.indexOf(k) >= 0 || axes.length === 0) {
idxZero.push(`inputIndices[${k}] = 0;`); // first element
idxZero.push(`input_indices[${k}] = 0;`); // first element
}
}
return [
`${idxZero.join('\n')}`,
`var value = ${input.getByOffset('inputOffset')};`,
`value = min(value, ${input.getByOffset('inputOffset')});`,
`var value = ${input.getByIndices('input_indices')};`,
`value = min(value, ${input.getByIndices('input_indices')});`,
'',
];
};
@ -242,7 +243,7 @@ const reduceProdNaive = (context: ComputeContext, attributes: ReduceAttributes):
const reduceOp: ReduceOp = (input, output) =>
[`var value = ${output.type.storage}(1);`,
'',
`value *= ${input.getByOffset('inputOffset')};`,
`value *= ${input.getByIndices('input_indices')};`,
'',
];
runReduceProgram(context, 'ReduceProd', attributes, reduceOp);
@ -253,7 +254,7 @@ const reduceSumNaive = (context: ComputeContext, attributes: ReduceAttributes):
const reduceOp: ReduceOp = (input, output) =>
[`var value = ${output.type.storage}(0);`,
'',
`value += ${input.getByOffset('inputOffset')};`,
`value += ${input.getByIndices('input_indices')};`,
'',
];
runReduceProgram(context, 'ReduceSum', attributes, reduceOp);
@ -264,7 +265,7 @@ const reduceSumSquareNaive = (context: ComputeContext, attributes: ReduceAttribu
const reduceOp: ReduceOp = (input, output) =>
[`var t = ${output.type.value}(0); var value = ${output.type.value}(0);`,
'',
`t = ${input.getByOffset('inputOffset')}; value += t * t;`,
`t = ${input.getByIndices('input_indices')}; value += t * t;`,
'',
];
runReduceProgram(context, 'ReduceSumSquare', attributes, reduceOp);
@ -273,7 +274,7 @@ const reduceSumSquareNaive = (context: ComputeContext, attributes: ReduceAttribu
const useNaiveReduceMethod =
(shape: readonly number[], axes: readonly number[], noopWithEmptyAxes: boolean): boolean => {
if (axes.length === 0) {
return noopWithEmptyAxes ? true : false;
return noopWithEmptyAxes;
}
let outputSize = 1;
@ -289,7 +290,7 @@ const useNaiveReduceMethod =
// The condition data is very rough, although considering the count of Execution Unit (EU), the potential
// work groups in a EU and the counts of loops in the naive and shared methods, also doing experiments
// on some machines.
return reduceSize < 32 && outputSize > 1024 ? true : false;
return reduceSize < 32 && outputSize > 1024;
};
export const reduceMean = (context: ComputeContext, attributes: ReduceAttributes): void => {
@ -371,6 +372,3 @@ export const reduceLogSum = (context: ComputeContext, attributes: ReduceAttribut
reduceLogSumShared(context, attributes);
}
};
export const parseReduceAttributes = (attributes: Record<string, unknown>): ReduceAttributes =>
createAttributeWithCacheKey(attributes as Omit<ReduceAttributes, keyof AttributeWithCacheKey>);

View file

@ -7,7 +7,7 @@ import {ShapeUtil} from '../../util';
import {AttributeWithCacheKey, createAttributeWithCacheKey} from '../attribute-with-cache-key';
import {ComputeContext, ProgramInfo} from '../types';
import {IndicesHelper, inputVariable, outputVariable, ShaderHelper} from './common';
import {createTensorShapeVariables, getElementAt, IndicesHelper, inputVariable, outputVariable, ShaderHelper} from './common';
type CoordinateTransformMode = 'half_pixel'|'asymmetric'|'pytorch_half_pixel'|'tf_half_pixel_for_nn'|'align_corners'|
'tf_crop_and_resize'|'half_pixel_symmetric';
@ -245,69 +245,67 @@ const adjustOutputShape = (inputShape: readonly number[], scales: number[], attr
};
const calculateOriginalIndicesFromOutputIndices =
(output: IndicesHelper, inputShape: readonly number[], outputShape: readonly number[], scales: readonly number[],
roi: readonly number[]): string => `
fn calculateOriginalIndicesFromOutputIndices(outputIndices: ${output.type.indices}) -> array<${
(output: IndicesHelper, inputShape: readonly number[], outputShape: readonly number[], scalesLength: number,
roiLength: number): string => `
fn calculateOriginalIndicesFromOutputIndices(output_indices: ${output.type.indices}) -> array<${
output.type.value}, ${outputShape.length}> {
const inputShape = array<u32, ${inputShape.length}>(${inputShape.map(i => `${i}u`).join(',')});
const outputShape = array<u32, ${outputShape.length}>(${outputShape.map(i => `${i}u`).join(',')});
const scales = array<${output.type.value}, ${scales.length}>(${scales.map(i => `${i}f`).join(',')});
const roi = array<${output.type.value}, ${roi.length}>(${roi.map(i => `${i}f`).join(',')});
var originalIndices: array<${output.type.value}, ${outputShape.length}>;
var original_indices: array<${output.type.value}, ${outputShape.length}>;
for (var i:u32 = 0; i < ${outputShape.length}; i++) {
var outputIndex = ${outputShape.length === 1 ? 'outputIndices' : 'outputIndices[i]'};
if (scales[i] == 1.0) {
originalIndices[i] = ${output.type.value}(outputIndex);
var output_index = ${output.type.value}(${output.indicesGet('output_indices', 'i')});
var scale = ${getElementAt('uniforms.scales', 'i', scalesLength)};
var roi_low = ${getElementAt('uniforms.roi', 'i', roiLength)};
var roi_hi = ${getElementAt('uniforms.roi', `i + ${inputShape.length}`, roiLength)};
if (scale == 1.0) {
original_indices[i] = output_index;
} else {
originalIndices[i] = getOriginalCoordinateFromResizedCoordinate(${output.type.value}(outputIndex), scales[i],
${output.type.value}(outputShape[i]), ${output.type.value}(inputShape[i]), roi[i], roi[i + ${
inputShape.length}]);
var input_shape_i = ${output.type.value}(${getElementAt('uniforms.input_shape', 'i', inputShape.length)});
var output_shape_i = ${output.type.value}(${getElementAt('uniforms.output_shape', 'i', outputShape.length)});
original_indices[i] = getOriginalCoordinateFromResizedCoordinate(output_index, scale, output_shape_i,
input_shape_i, roi_low, roi_hi);
}
}
return originalIndices;
return original_indices;
}`;
const calculateInputIndicesFromOutputIndices =
(input: IndicesHelper, output: IndicesHelper, inputShape: readonly number[], outputShape: readonly number[],
scales: readonly number[], roi: readonly number[], useExtrapolation: boolean): string => `
fn calculateInputIndicesFromOutputIndices(outputIndices: ${output.type.indices}) -> ${input.type.indices} {
const inputShape = array<u32, ${inputShape.length}>(${inputShape.map(i => `${i}u`).join(',')});
const outputShape = array<u32, ${outputShape.length}>(${outputShape.map(i => `${i}u`).join(',')});
const scales = array<${input.type.value}, ${scales.length}>(${scales.map(i => `${i}`).join(',')});
const roi = array<${input.type.value}, ${roi.length}>(${roi.map(i => `${i}`).join(',')});
var inputIndices: ${input.type.indices};
for (var i:u32 = 0; i < ${outputShape.length}; i++) {
var outputIndex = ${outputShape.length === 1 ? 'outputIndices' : 'outputIndices[i]'};
var inputIndex: u32;
if (scales[i] == 1.0) {
inputIndex = outputIndex;
} else {
var original_idx = getOriginalCoordinateFromResizedCoordinate(${input.type.value}(outputIndex), scales[i],
${input.type.value}(outputShape[i]), ${input.type.value}(inputShape[i]), roi[i], roi[i + ${
inputShape.length}]);
if (!${useExtrapolation} || (original_idx >= 0 && original_idx < ${input.type.value}(inputShape[i]))) {
if (original_idx < 0) {
inputIndex = 0;
} else if (original_idx > (${input.type.value}(inputShape[i]) - 1)) {
inputIndex = inputShape[i] - 1;
} else {
inputIndex = u32(getNearestPixelFromOriginal(original_idx, scales[i] < 1));
}
scalesLength: number, roiLength: number, useExtrapolation: boolean): string => `
fn calculateInputIndicesFromOutputIndices(output_indices: ${output.type.indices}) -> ${input.type.indices} {
var input_indices: ${input.type.indices};
for (var i:u32 = 0; i < ${outputShape.length}; i++) {
var output_index = ${output.type.value}(${output.indicesGet('output_indices', 'i')});
var input_index: u32;
var scale = ${getElementAt('uniforms.scales', 'i', scalesLength)};
if (scale == 1.0) {
input_index = u32(output_index);
} else {
var roi_low = ${getElementAt('uniforms.roi', 'i', roiLength)};
var roi_hi = ${getElementAt('uniforms.roi', `i + ${inputShape.length}`, roiLength)};
var input_shape_i = ${output.type.value}(${getElementAt('uniforms.input_shape', 'i', inputShape.length)});
var output_shape_i = ${output.type.value}(${getElementAt('uniforms.output_shape', 'i', outputShape.length)});
var original_idx = getOriginalCoordinateFromResizedCoordinate(output_index, scale, output_shape_i,
input_shape_i, roi_low, roi_hi);
if (!${useExtrapolation} || (original_idx >= 0 && original_idx < input_shape_i)) {
if (original_idx < 0) {
input_index = 0;
} else if (original_idx > (input_shape_i - 1)) {
input_index = u32(input_shape_i) - 1;
} else {
inputIndex = u32(original_idx);
input_index = u32(getNearestPixelFromOriginal(original_idx, scale < 1));
}
} else {
input_index = u32(original_idx);
}
${input.indicesSet('inputIndices', 'i', 'inputIndex')}
}
return inputIndices;
${input.indicesSet('input_indices', 'i', ' input_index')}
}
return input_indices;
}`;
const checkInputIndices = (input: IndicesHelper, inputShape: readonly number[]): string => `
fn checkInputIndices(inputIndices: ${input.type.indices}) -> bool {
const inputShape = array<u32, ${inputShape.length}>(${inputShape.map(i => `${i}u`).join(',')});
fn checkInputIndices(input_indices: ${input.type.indices}) -> bool {
for (var i:u32 = 0; i < ${inputShape.length}; i++) {
var inputIndex = ${inputShape.length === 1 ? 'inputIndices' : 'inputIndices[i]'};
if (inputIndex < 0 || inputIndex >= inputShape[i]) {
var input_index = ${input.indicesGet('input_indices', 'i')};
if (input_index < 0 || input_index >= ${getElementAt('uniforms.input_shape', 'i', inputShape.length)}) {
return false;
}
}
@ -322,18 +320,18 @@ const bilinearInterpolation =
const dType = input.type.value;
return `
fn getInputValue(batch: u32, channel: u32, row: u32, col: u32) -> ${dType} {
var inputIndices: ${input.type.indices};
inputIndices[${heightIdx}] = max(0, min(row, ${inputShape[heightIdx]} - 1));
inputIndices[${widthIdx}] = max(0, min(col, ${inputShape[widthIdx]} - 1));
var input_indices: ${input.type.indices};
${input.indicesSet('input_indices', heightIdx, `max(0, min(row, ${inputShape[heightIdx]} - 1))`)};
${input.indicesSet('input_indices', widthIdx, `max(0, min(col, ${inputShape[widthIdx]} - 1))`)};
if (${inputShape.length} > 2) {
inputIndices[${channelIdx}] = channel;
inputIndices[${batchIdx}] = batch;
${input.indicesSet('input_indices', channelIdx, 'channel')};
${input.indicesSet('input_indices', batchIdx, 'batch')};
};
return input[${input.indicesToOffset('inputIndices')}];
return ${input.getByIndices('input_indices')};
}
fn bilinearInterpolation(outputIndices: ${output.type.indices}) -> ${dType} {
var originalIndices = calculateOriginalIndicesFromOutputIndices(outputIndices);
fn bilinearInterpolation(output_indices: ${output.type.indices}) -> ${dType} {
var originalIndices = calculateOriginalIndicesFromOutputIndices(output_indices);
var row:${dType} = originalIndices[${heightIdx}];
var col:${dType} = originalIndices[${widthIdx}];
if (${useExtrapolation} && (row < 0 || row > (${inputShape[heightIdx]} - 1) || col < 0 || col > ${
@ -373,10 +371,10 @@ const bicubicInterpolation =
const createCubicInterpolationFunction = (idx: number): string => {
const direction = idx === heightIdx ? 'row' : 'col';
return `
fn ${direction}CubicInterpolation(inputIndices: ${input.type.indices}, outputIndices: ${
fn ${direction}CubicInterpolation(input_indices: ${input.type.indices}, output_indices: ${
output.type.indices}) -> ${dType} {
var outputIndex = ${outputShape.length === 1 ? 'outputIndices' : `outputIndices[${idx}]`};
var originalIdx: ${dType} = getOriginalCoordinateFromResizedCoordinate(${dType}(outputIndex), ${scales[idx]},
var output_index = ${output.indicesGet('output_indices', idx)};
var originalIdx: ${dType} = getOriginalCoordinateFromResizedCoordinate(${dType}(output_index), ${scales[idx]},
${dType}(${outputShape[idx]}), ${dType}(${inputShape[idx]}), ${roi[idx]}, ${roi[idx]} + ${inputShape.length});
var fractOriginalIdx: ${dType} = originalIdx - floor(originalIdx);
var coefs = getCubicInterpolationCoefs(fractOriginalIdx);
@ -397,10 +395,11 @@ const bicubicInterpolation =
${direction} = max(0, min(${direction}, ${inputShape[idx]} - 1));
}
}
var inputIndicesCopy: ${input.type.indices} = inputIndices;
inputIndicesCopy[${idx}] = u32(${direction});
data[i + 1] = ${idx === heightIdx ? `input[${input.indicesToOffset('inputIndicesCopy')}];` : `
rowCubicInterpolation(inputIndicesCopy, outputIndices);`}
var input_indices_copy: ${input.type.indices} = input_indices;
${input.indicesSet('input_indices_copy', idx, `u32(${direction})`)};
data[i + 1] = ${
idx === heightIdx ? input.getByIndices('input_indices_copy') :
'rowCubicInterpolation(input_indices_copy, output_indices)'};
}
return cubicInterpolation1D(data, coefs);
}`;
@ -429,9 +428,9 @@ const bicubicInterpolation =
return (x[0] * coefs[0] + x[1] * coefs[1]+ x[2] * coefs[2]+ x[3] * coefs[3]) / coefsSum;
}
fn bicubicInterpolation(outputIndices: ${output.type.indices}) -> ${dType} {
var inputIndices: ${input.type.indices} = outputIndices;
return colCubicInterpolation(inputIndices, outputIndices);
fn bicubicInterpolation(output_indices: ${output.type.indices}) -> ${dType} {
var input_indices: ${input.type.indices} = output_indices;
return colCubicInterpolation(input_indices, output_indices);
}
`;
};
@ -450,8 +449,8 @@ const createResizeProgramInfo =
outputShape = adjustOutputShape(inputShape, scales, attributes);
}
}
const output = outputVariable('output', inputTensor.dataType, outputShape);
const input = inputVariable('input', inputTensor.dataType, inputShape);
const output = outputVariable('output', inputTensor.dataType, outputShape.length);
const input = inputVariable('input', inputTensor.dataType, inputShape.length);
const outputSize = ShapeUtil.size(outputShape);
const noScale = inputShape.length === outputShape.length && inputShape.every((d, i) => d === outputShape[i]);
const useExtrapolation = attributes.coordinateTransformMode === 'tf_crop_and_resize';
@ -467,11 +466,11 @@ const createResizeProgramInfo =
${getNearestPixelFromOriginal(attributes.nearestMode, opsetVersion, dataType)};
${
calculateInputIndicesFromOutputIndices(
input, output, inputShape, outputShape, scales, roi, useExtrapolation)};
input, output, inputShape, outputShape, scales.length, roi.length, useExtrapolation)};
`;
case 'linear':
return `
${calculateOriginalIndicesFromOutputIndices(output, inputShape, outputShape, scales, roi)};
${calculateOriginalIndicesFromOutputIndices(output, inputShape, outputShape, scales.length, roi.length)};
${
bilinearInterpolation(
input, output, inputShape, scales, useExtrapolation, attributes.extrapolationValue)};
@ -488,25 +487,29 @@ const createResizeProgramInfo =
}
})()};
`}
${shaderHelper.declareVariables(input, output)}
${
shaderHelper.registerUniform('output_size', 'u32')
.registerUniform('scales', 'f32', scales.length)
.registerUniform('roi', 'f32', roi.length)
.declareVariables(input, output)}
${shaderHelper.mainStart()}
${shaderHelper.guardAgainstOutOfBoundsWorkgroupSizes(outputSize)}
${shaderHelper.guardAgainstOutOfBoundsWorkgroupSizes('uniforms.output_size')}
${noScale ? 'output[global_idx] = input[global_idx];' : `
let outputIndices = ${output.offsetToIndices('global_idx')};
var inputIndices: ${input.type.indices};
let output_indices = ${output.offsetToIndices('global_idx')};
var input_indices: ${input.type.indices};
${(() => {
switch (attributes.mode) {
case 'nearest':
return `inputIndices = calculateInputIndicesFromOutputIndices(outputIndices);
if (checkInputIndices(inputIndices)) {
output[global_idx] = input[${input.indicesToOffset('inputIndices')}];
return `input_indices = calculateInputIndicesFromOutputIndices(output_indices);
if (checkInputIndices(input_indices)) {
output[global_idx] = ${input.getByIndices('input_indices')};
} else {
output[global_idx] = ${attributes.extrapolationValue};
}`;
case 'linear':
return 'output[global_idx] = bilinearInterpolation(outputIndices);';
return 'output[global_idx] = bilinearInterpolation(output_indices);';
case 'cubic':
return 'output[global_idx] = bicubicInterpolation(outputIndices);';
return 'output[global_idx] = bicubicInterpolation(output_indices);';
default:
throw Error(`Unsupported resize mode: ${attributes.mode}`);
}
@ -518,12 +521,20 @@ const createResizeProgramInfo =
name: 'Resize',
shaderCache: {
hint: `${attributes.cacheKey}|${opsetVersion}|${scales.length > 0 ? scales : ''}|${
sizes.length > 0 ? sizes : ''}|${noScale}`
sizes.length > 0 ? sizes : ''}|${roi.length > 0 ? roi : ''}|${noScale}`,
inputDependencies: ['rank']
},
getShaderSource,
getRunData: () => ({
outputs: [{dims: outputShape, dataType: inputTensor.dataType}],
dispatchGroup: {x: Math.ceil(outputSize / 64 /* workgroup size */)}
dispatchGroup: {x: Math.ceil(outputSize / 64 /* workgroup size */)},
programUniforms: [
{type: 'uint32', data: outputSize},
{type: 'float32', data: scales},
{type: 'float32', data: roi},
...createTensorShapeVariables(inputShape),
...createTensorShapeVariables(outputShape),
]
})
};
};

View file

@ -77,25 +77,25 @@ const fixStartEndValues =
};
const calculateInputIndicesImpl =
(input: IndicesHelper, output: IndicesHelper, inputShape: readonly number[], outputShape: readonly number[]):
string => `fn calculateInputIndices(outputIndices: ${output.type.indices}) -> ${input.type.indices} {
var inputIndices: ${input.type.indices};
(input: IndicesHelper, output: IndicesHelper, inputShape: readonly number[]): string =>
`fn calculateInputIndices(output_indices: ${output.type.indices}) -> ${input.type.indices} {
var input_indices: ${input.type.indices};
var carry = 0u;
for (var i = ${inputShape.length}; i >= 0; i--) {
let input_shape_i = ${getElementAt('uniforms.input_shape', 'i', inputShape.length)};
let steps_i = ${getElementAt('uniforms.steps', 'i', inputShape.length)};
let signs_i = ${getElementAt('uniforms.signs', 'i', inputShape.length)};
let starts_i = ${getElementAt('uniforms.starts', 'i', inputShape.length)};
var outputIndex = ${outputShape.length === 1 ? 'outputIndices' : 'outputIndices[i]'};
var inputIndex = outputIndex * steps_i + starts_i + carry;
carry = inputIndex / input_shape_i;
inputIndex = inputIndex % input_shape_i;
var output_index = ${output.indicesGet('output_indices', 'i')};
var input_index = output_index * steps_i + starts_i + carry;
carry = input_index / input_shape_i;
input_index = input_index % input_shape_i;
if (signs_i < 0) {
inputIndex = input_shape_i - inputIndex - 1u + starts_i;
input_index = input_shape_i - input_index - 1u + starts_i;
}
${inputShape.length === 1 ? 'inputIndices' : 'inputIndices[i]'} = inputIndex;
${input.indicesSet('input_indices', 'i', 'input_index')};
}
return inputIndices;
return input_indices;
}`;
const createSliceProgramInfo = (inputs: readonly TensorView[], attributes: SliceAttributes): ProgramInfo => {
@ -162,12 +162,12 @@ const createSliceProgramInfo = (inputs: readonly TensorView[], attributes: Slice
const getShaderSource = (shaderHelper: ShaderHelper) => `
${shaderHelper.registerUniforms(uniforms).declareVariables(input, output)}
${calculateInputIndicesImpl(input, output, inputShape, outputShape)}
${calculateInputIndicesImpl(input, output, inputShape)}
${shaderHelper.mainStart()}
${shaderHelper.guardAgainstOutOfBoundsWorkgroupSizes('uniforms.outputSize')}
let outputIndices = ${output.offsetToIndices('global_idx')};
let inputIndices = calculateInputIndices(outputIndices);
${output.setByOffset('global_idx', input.getByIndices('inputIndices'))}
let output_indices = ${output.offsetToIndices('global_idx')};
let input_indices = calculateInputIndices(output_indices);
${output.setByOffset('global_idx', input.getByIndices('input_indices'))}
}`;
return {
name: 'Slice',

View file

@ -4,9 +4,9 @@
import {TensorView} from '../../tensor-view';
import {ShapeUtil} from '../../util';
import {AttributeWithCacheKey, createAttributeWithCacheKey} from '../attribute-with-cache-key';
import {ComputeContext, ProgramInfo, TensorInfo} from '../types';
import {ComputeContext, ProgramInfo, ProgramUniform, TensorInfo} from '../types';
import {IndicesHelper, inputVariable, outputVariable, ShaderHelper} from './common';
import {createTensorShapeVariables, getElementAt, IndicesHelper, inputVariable, outputVariable, ShaderHelper} from './common';
export interface SplitAttributes extends AttributeWithCacheKey {
readonly axis: number;
@ -34,7 +34,7 @@ const createSplitAttributesFromInputs =
const calculateOutputIndexImpl = (numberOfTensors: number): string => `
fn calculateOutputIndex(index: u32) -> u32 {
for (var i: u32 = 0u; i < ${numberOfTensors}u; i += 1u ) {
if (index < sizeInConcatAxis[i]) {
if (index < ${getElementAt('uniforms.size_in_split_axis', 'i', numberOfTensors)}) {
return i;
}
}
@ -48,15 +48,15 @@ const writeBufferDataImpl = (outputs: readonly IndicesHelper[]) => {
if (numberOfTensors === 1) {
codeLines.push(returnSnippet);
} else if (i === 0) {
codeLines.push(`if (outputNumber == ${i}u) { ${returnSnippet} }`);
codeLines.push(`if (output_number == ${i}u) { ${returnSnippet} }`);
} else if (i === numberOfTensors - 1) {
codeLines.push(`else { ${returnSnippet} }`);
} else {
codeLines.push(`else if (outputNumber == ${i}) { ${returnSnippet} }`);
codeLines.push(`else if (output_number == ${i}) { ${returnSnippet} }`);
}
}
return `
fn writeBufferData(outputNumber: u32, indices: ${outputs[0].type.indices}, global_idx: u32) {
fn writeBufferData(output_number: u32, indices: ${outputs[0].type.indices}, global_idx: u32) {
${codeLines.join('\n')}
}`;
};
@ -65,48 +65,54 @@ const createSplitProgramInfo = (inputs: readonly TensorView[], attributes: Split
const inputShape = inputs[0].dims;
const inputSize = ShapeUtil.size(inputShape);
const dataType = inputs[0].dataType;
const rank = inputShape.length;
const axis = attributes.axis;
const adjustedAxis = (axis < 0) ? inputShape.length + axis : axis;
const axis = ShapeUtil.normalizeAxis(attributes.axis, inputShape.length);
const outputs = new Array<IndicesHelper>(attributes.numOutputs);
const input = inputVariable('input', dataType, inputShape);
const sizeInConcatAxis = new Array<number>(attributes.numOutputs);
const sizeInSplitAxis = new Array<number>(attributes.numOutputs);
const outputsTensorInfo: TensorInfo[] = [];
const outputShapes: number[][] = [];
let previousSum = 0;
const programUniforms: ProgramUniform[] = [{type: 'uint32', data: inputSize}];
for (let i = 0; i < attributes.numOutputs; i++) {
previousSum += attributes.splitSizes[i];
sizeInConcatAxis[i] = previousSum;
sizeInSplitAxis[i] = previousSum;
const outputShape = inputShape.slice();
outputShape[attributes.axis] = attributes.splitSizes[i];
outputShapes.push(outputShape);
outputs[i] = outputVariable(`output${i}`, dataType, outputShapes[i]);
outputs[i] = outputVariable(`output${i}`, dataType, outputShape);
outputsTensorInfo.push({dims: outputShapes[i], dataType: inputs[0].dataType});
}
const indicesAxis = rank < 2 ? 'indices' : `indices[${adjustedAxis}]`;
programUniforms.push({type: 'uint32', data: sizeInSplitAxis});
programUniforms.push(...createTensorShapeVariables(inputShape));
outputShapes.forEach((outputShape) => programUniforms.push(...createTensorShapeVariables(outputShape)));
const getShaderSource = (shaderHelper: ShaderHelper) => `
${shaderHelper.declareVariables(input, ...outputs)}
const sizeInConcatAxis = array<u32, ${sizeInConcatAxis.length}>(${sizeInConcatAxis.map(i => `${i}u`).join(',')});
${calculateOutputIndexImpl(sizeInConcatAxis.length)}
${
shaderHelper.registerUniform('input_size', 'u32')
.registerUniform('size_in_split_axis', 'u32', sizeInSplitAxis.length)
.declareVariables(input, ...outputs)}
${calculateOutputIndexImpl(sizeInSplitAxis.length)}
${writeBufferDataImpl(outputs)}
${shaderHelper.mainStart()}
${shaderHelper.guardAgainstOutOfBoundsWorkgroupSizes(inputSize)}
${shaderHelper.guardAgainstOutOfBoundsWorkgroupSizes('uniforms.input_size')}
var indices = ${input.offsetToIndices('global_idx')};
let outputNumber = calculateOutputIndex(${indicesAxis});
if (outputNumber != 0) {
${indicesAxis} -= sizeInConcatAxis[outputNumber - 1u];
var index = ${input.indicesGet('indices', axis)};
let output_number = calculateOutputIndex(index);
if (output_number != 0) {
index -= ${getElementAt('uniforms.size_in_split_axis', 'output_number - 1u', sizeInSplitAxis.length)};
${input.indicesSet('indices', axis, 'index')};
}
writeBufferData(outputNumber, indices, global_idx);
writeBufferData(output_number, indices, global_idx);
}`;
return {
name: 'Split',
shaderCache: {hint: attributes.cacheKey},
shaderCache: {hint: attributes.cacheKey, inputDependencies: ['rank']},
getShaderSource,
getRunData: () => ({
outputs: outputsTensorInfo,
dispatchGroup: {x: Math.ceil(inputSize / 64 /* workgroup size */)},
programUniforms
})
};
};