@aislamov/onnxruntime-web64
Version:
A Javascript library for running ONNX models on browsers
258 lines (223 loc) • 10.5 kB
text/typescript
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
import { TensorView } from '../../tensor';
import { ShapeUtil } from '../../util';
import { AttributeWithCacheKey, createAttributeWithCacheKey } from '../attribute-with-cache-key';
import { ComputeContext, GpuDataType, ProgramInfo, ProgramMetadata } from '../types';
import {
fillVector,
getMaxComponents,
inputVariable,
outputVariable,
ShaderHelper,
tensorTypeToWsglStorageType
} from './common';
import { DataType } from '../../../wasm-common';
export interface InstanceNormAttributes extends AttributeWithCacheKey {
epsilon: number;
format: 'NHWC'|'NCHW';
}
const validateInputs = (inputs: readonly TensorView[]): void => {
if (!inputs || inputs.length !== 3) {
throw new Error('instanceNorm requires 3 inputs.');
}
};
const createInstanceNormProgramInfo =
(metadata: ProgramMetadata, inputs: readonly TensorView[], attributes: InstanceNormAttributes): ProgramInfo => {
const xShape = inputs[0].dims;
const outputShape = xShape;
const outputSize = ShapeUtil.size(outputShape);
const axis = 2;
const normCount = xShape[0] * xShape[1];
const normSize = ShapeUtil.sizeFromDimension(xShape, axis);
const C = xShape[1];
const dataType = tensorTypeToWsglStorageType(inputs[0].dataType);
const getShaderSource = (shaderHelper: ShaderHelper) => `
const C: u32 = ${C};
const normSize: u32 = ${normSize};
const normSizeTyped: ${dataType} = ${normSize};
const epsilon: f32 = ${attributes.epsilon};
var<storage, read> x : array<${dataType}>;
var<storage, read> scale : array<${dataType}>;
var<storage, read> bias : array<${dataType}>;
var<storage, read_write> output : array<${dataType}>;
${shaderHelper.mainStart()}
let offset = global_idx * normSize;
if (offset >= ${outputSize}) { return; }
var mean: ${dataType} = 0;
for (var h: u32 = 0u; h < normSize; h++) {
mean = mean + x[h + offset];
}
mean = mean / normSizeTyped;
var squaredNorm: ${dataType} = 0;
for (var h: u32 = 0u; h < normSize; h++) {
let deviation: f32 = x[h + offset] - mean;
squaredNorm = squaredNorm + deviation * deviation;
}
let invStdDev = 1 / sqrt(squaredNorm / normSizeTyped + epsilon);
let channelScale = invStdDev * scale[global_idx % C];
let channelShift = bias[global_idx % C] - mean * channelScale;
for (var j: u32 = 0; j < normSize; j++) {
output[j + offset] = x[j + offset] * channelScale + channelShift;
}
}`;
return {
...metadata,
outputs: [
{dims: outputShape, dataType: inputs[0].dataType, gpuDataType: GpuDataType.default},
],
getShaderSource,
dispatchGroup: () => ({x: Math.ceil(normCount / 64 /* workgroup size */)})
};
};
const computeMean = (context: ComputeContext, input: TensorView, scale: TensorView, bias: TensorView, n: number, h: number, c: number, epsilon: number) => {
const components = getMaxComponents(c);
const inputHelper = inputVariable('input', input.dataType, input.dims, components);
const scaleHelper = inputVariable('scale', scale.dataType, scale.dims, components);
const biasHelper = inputVariable('bias', bias.dataType, bias.dims, components);
const WG = 64;
// we will store channel scale and channel shift in [2, components] matrix
// or in vec2 when components == 1
const outputType = components === 1 ? `vec2f` : `mat2x${components}f`;
const sumCastType = components === 1 ? `f32` : `vec${components}f`;
const setOutputValue = (var1: string, var2: string) => {
return `${outputType}(${var1}, ${var2})`;
};
const unitsOfWork = n * c / components;
const wgSize = Math.ceil(h / WG);
const getMeanShaderSource = (shaderHelper: ShaderHelper) => `
const H: u32 = ${h};
const C: u32 = ${c / components};
const imageSize: u32 = ${h * c / components};
${shaderHelper.declareVariables(inputHelper)}
var<storage, read_write> output : array<${outputType}>;
${shaderHelper.mainStart(WG)}
let currentImageNumber = global_idx / ${WG} / C;
let currentChannelNumber = (global_idx / ${WG}) % C;
let wgId = global_idx % ${WG};
let wgOffset = wgId * ${wgSize};
if (wgOffset >= H) {
return;
}
let wgMax = min(wgOffset + ${wgSize}, H);
let offset = currentImageNumber * imageSize + currentChannelNumber;
var sum = ${fillVector('f32', components)};
var squaredSum = ${fillVector('f32', components)};
for (var i: u32 = wgOffset; i < wgMax; i++) {
let value = ${sumCastType}(input[offset + i * C]);
sum += value;
squaredSum += value * value;
}
output[global_idx] = ${setOutputValue('sum', 'squaredSum')};
}`;
const meanValues = context.compute(
{
name: 'InstanceNormComputeMean',
inputTypes: [GpuDataType.default],
cacheHint: JSON.stringify({ components, n, h, c }),
outputs: [
{dims: [n, c, WG, 2], dataType: DataType.float, gpuDataType: GpuDataType.default},
],
getShaderSource: getMeanShaderSource,
dispatchGroup: () => ({x: n * c / components})
},
{inputs: [input], outputs: [-1]})[0];
const getShaderSource = (shaderHelper: ShaderHelper) => `
const H: u32 = ${h};
const C: u32 = ${c / components};
const imageSize: u32 = ${WG * c / components};
const epsilon: f32 = ${epsilon};
var<storage, read> input : array<${outputType}>;
var<storage, read> scale : array<${scaleHelper.type.storage}>;
var<storage, read> bias : array<${biasHelper.type.storage}>;
var<storage, read_write> output : array<${outputType}>;
${shaderHelper.mainStart()}
${shaderHelper.guardAgainstOutOfBoundsWorkgroupSizes(unitsOfWork)}
let currentImageNumber = global_idx / C;
let currentChannelNumber = global_idx % C;
let offset = currentImageNumber * imageSize;
var sum = ${fillVector('f32', components)};
var squaredSum = ${fillVector('f32', components)};
for (var i: u32 = 0; i < ${WG}; i++) {
let value = input[offset + i + currentChannelNumber * ${WG}];
sum += value[0];
squaredSum += value[1];
}
sum = sum / f32(H);
squaredSum = squaredSum / f32(H);
let invStdDev = 1 / sqrt(squaredSum - sum * sum + epsilon);
let channelScale = invStdDev * ${sumCastType}(scale[currentChannelNumber]);
let channelShift = ${sumCastType}(bias[currentChannelNumber]) - sum * channelScale;
output[global_idx] = ${setOutputValue('channelScale', 'channelShift')};
}`;
return context.compute(
{
name: 'InstanceNormComputeChannelScaleShift',
inputTypes: [GpuDataType.default, GpuDataType.default, GpuDataType.default],
cacheHint: JSON.stringify({ components, n, h, c, epsilon }),
outputs: [
{dims: [n, c, 2], dataType: DataType.float, gpuDataType: GpuDataType.default},
],
getShaderSource,
dispatchGroup: () => ({x: Math.ceil(unitsOfWork / 64 /* workgroup size */)})
},
{inputs: [meanValues, scale, bias], outputs: [-1]})[0];
};
const createInstanceNormNHWCProgramInfo =
(context: ComputeContext, metadata: ProgramMetadata, inputs: readonly TensorView[],
attributes: InstanceNormAttributes) => {
const xShape = inputs[0].dims;
const outputShape = xShape;
const N = xShape[0];
const C = xShape[xShape.length - 1];
const H = ShapeUtil.sizeFromDimension(xShape, 1) / C;
const components = getMaxComponents(C);
const outputSize = ShapeUtil.size(outputShape) / components;
const inputHelper = inputVariable('input', inputs[0].dataType, inputs[0].dims, components);
const outputHelper = outputVariable('output', inputs[0].dataType, outputShape, components);
const dataType = tensorTypeToWsglStorageType(inputs[0].dataType);
const scaleType = components === 1 ? `vec2f` : `mat2x${components}f`;
const scaleCastType = components === 1 ? dataType : `vec${components}<${dataType}>`;
// first compute mean
const channelScaleShift = computeMean(context, inputs[0], inputs[1], inputs[2], N, H, C, attributes.epsilon);
const getShaderSource = (shaderHelper: ShaderHelper) => `
const H: u32 = ${H};
const C: u32 = ${C / components};
var<storage, read> input : array<${inputHelper.type.storage}>;
var<storage, read> scaleInput : array<${scaleType}>;
var<storage, read_write> output : array<${outputHelper.type.storage}>;
${shaderHelper.mainStart()}
let currentImageNumber = global_idx / (C * H);
let currentChannelNumber = global_idx % C;
let scaleOffset = currentImageNumber * C + currentChannelNumber;
let scale = scaleInput[scaleOffset];
output[global_idx] = fma(input[global_idx], ${scaleCastType}(scale[0]), ${scaleCastType}(scale[1]));
}`;
context.compute({
...metadata,
inputTypes: [GpuDataType.default, GpuDataType.default],
outputs: [
{dims: outputShape, dataType: inputs[0].dataType, gpuDataType: GpuDataType.default},
],
getShaderSource,
dispatchGroup: () => ({x: Math.ceil(outputSize / 64 /* workgroup size */)})
},
{
inputs: [inputs[0], channelScaleShift]
});
};
export const parseInstanceNormAttributes = (attributes: InstanceNormAttributes): InstanceNormAttributes =>
createAttributeWithCacheKey({epsilon: attributes.epsilon, format: attributes.format});
export const instanceNorm = (context: ComputeContext, attributes: InstanceNormAttributes): void => {
validateInputs(context.inputs);
const metadata = {
name: 'InstanceNormalization',
inputTypes: [GpuDataType.default, GpuDataType.default, GpuDataType.default],
cacheHint: attributes.cacheKey,
};
if (attributes.format === 'NHWC') {
createInstanceNormNHWCProgramInfo(context, metadata, context.inputs, attributes);
} else {
context.compute(createInstanceNormProgramInfo(metadata, context.inputs, attributes));
}
};