Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement more reduction #44

Merged
merged 3 commits into from
Apr 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/batch_normalization.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function batchNormalization(input, mean, variance, {axis=1, scale, bias,
// The output tensor has the same shape as the input tensor.
let output = new Tensor(input.shape);
const shape = new Array(input.rank).fill(1);
shape[axis] = -1;
shape[axis] = null;
Copy link

@fdwr fdwr Mar 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 Ningxin's PR removed the special handling of negative numbers related to axes, but we still have this special case with axes here with reshape. I wonder if this too should be a policy resolved by higher level frameworks first, and they just pass the resolved shape before it reaches the WebNN API (or does keeping Reshape's special null axis handling make the composition of other WebNN ops simpler, like your instanceNormalization implementation which utilizes reshape?) *this comment is not blocking either way

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to simplify the implementation

Sorry, I don't catch your point, could you please explain how to simplify the implementation?

And I submitted a pr webmachinelearning/webnn#367 to Spec, please also have a review, thanks.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Commented on the other PR.

output = sub(input, reshape(mean, shape));
output = div(output,
pow(add(reshape(variance, shape), new Scalar(epsilon)), new Scalar(0.5)));
Expand Down
4 changes: 2 additions & 2 deletions src/gru.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,15 @@ export function gru(input, weight, recurrentWeight, steps, hiddenSize,
cellInput, cellWeight[slot], cellRecurrentWeight[slot],
cellHidden[slot], hiddenSize, {bias: cellBias[slot],
recurrentBias: cellRecurrentBias[slot], resetAfter, layout, activations}),
[1, -1, hiddenSize]);
[1, null, hiddenSize]);

cellOutput = (cellOutput ? concat([cellOutput, result], 0) : result);
}

hiddenState = cellOutput;

if (returnSequence) {
cellOutput = reshape(cellOutput, [1, numDirections, -1, hiddenSize]);
cellOutput = reshape(cellOutput, [1, numDirections, null, hiddenSize]);
sequence =
(sequence ? concat([sequence, cellOutput], 0) : cellOutput);
}
Expand Down
64 changes: 60 additions & 4 deletions src/reduce.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
'use strict';

import {pow} from './binary.js';
import {squeeze} from './squeeze.js';
import {sizeOfShape, Tensor} from './lib/tensor.js';
import {abs, exp, log} from './unary.js';
import {sizeOfShape, Scalar, Tensor} from './lib/tensor.js';
import {validateReduceParams} from './lib/validate-input.js';

/**
Expand All @@ -16,9 +18,6 @@ function reduce(input, reduceFunc, {keepDimensions = false, axes} = {}) {

const outputShape = input.shape.slice();
for (let i = 0; i < inpAxes.length; ++i) {
if (inpAxes[i] === -1) {
inpAxes[i] = input.rank - 1;
}
outputShape[inpAxes[i]] = 1;
}

Expand Down Expand Up @@ -123,3 +122,60 @@ export function reduceSum(input, options = {}) {
return reduce(input,
(previousValue, currentValue) => previousValue + currentValue, options);
}

/**
* Compute the sum of the square of all the input values along the axes.
* @param {Tensor} input
* @param {MLReduceOptions} options
* @return {Tensor}
*/
export function reduceSumSquare(input, options = {}) {
return reduceSum(pow(input, new Scalar(2)), options);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formulas all look correct, but wasn't there a GitHub issue with ReduceL2 ambiguity?

The formula I recall was ReduceL2 = sqrt(a1^2 + a2^2 + ... + an^2).

(btw, I always have to re-lookup the formulas because I keep thinking "reduceSumSquare" means x.sum().square(), but actually it means x.square().sum() - so square and then reduce sum)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but wasn't there a GitHub issue with ReduceL2 ambiguity?

Are there other explanations for ReduceL2 besides ReduceL2 = sqrt(a1^2 + a2^2 + ... + an^2) ?

Reference: https://en.wikipedia.org/wiki/Norm_(mathematics)#p-norm

Copy link

@fdwr fdwr Apr 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh never mind, it was actually L2 pooling, not reduction: webmachinelearning/webnn#278

}

/**
* Compute the L1 norm of all the input values along the axes.
* @param {Tensor} input
* @param {MLReduceOptions} options
* @return {Tensor}
*/
export function reduceL1(input, options = {}) {
return reduceSum(abs(input), options);
}

/**
* Compute the L2 norm of all the input values along the axes.
* @param {Tensor} input
* @param {MLReduceOptions} options
* @return {Tensor}
*/
export function reduceL2(input, options = {}) {
const intermediateResult = reduceSumSquare(input, options);
if (intermediateResult.rank === 0) {
return new Tensor(
[],
[Math.pow(intermediateResult.getValueByIndex(0), 0.5)]);
} else {
return pow(intermediateResult, new Scalar(0.5));
}
}

/**
* Compute the log value of the sum of all the input values along the axes.
* @param {Tensor} input
* @param {MLReduceOptions} options
* @return {Tensor}
*/
export function reduceLogSum(input, options = {}) {
return log(reduceSum(input, options));
}

/**
* Compute the log value of the sum of the exponent of all the input values along the axes.
* @param {Tensor} input
* @param {MLReduceOptions} options
* @return {Tensor}
*/
export function reduceLogSumExp(input, options = {}) {
return log(reduceSum(exp(input), options));
}
2 changes: 1 addition & 1 deletion src/reshape.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function reshape(input, newShape) {
let minusOneAxis;
let elements = 1;
for (let i = 0; i < newShape.length; ++i) {
if (newShape[i] === -1) {
if (newShape[i] === null) {
minusOneAxis = i;
} else if (newShape[i] > 0) {
elements *= newShape[i];
Expand Down
2 changes: 1 addition & 1 deletion src/slice.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export function slice(input, starts, sizes, {axes} = {}) {
const axesLen = axes.length;
const outputShape = input.shape.slice();
for (let i = 0; i < axesLen; ++i) {
const axis = axes[i] >= 0 ? axes[i] : axes[i] + rank;
const axis = axes[i];
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aah, nice, we can simplify the testing and implementations after Ningxin's removal of the negative axes policy.

const size = input.shape[axis];
const start = starts[i];
startsForAllAxes[axis] = start >= 0 ? start : start + size;
Expand Down
6 changes: 2 additions & 4 deletions src/split.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@ export function split(input, splits, {axis = 0} = {}) {
validateSplitParams(...arguments);
const outputs = [];
let sliceSizes = [];
const rank = input.rank;
const inpAxis = axis >=0 ? axis : rank + axis;
if (typeof splits === 'number') {
sliceSizes = new Array(splits).fill(input.shape[inpAxis] / splits);
sliceSizes = new Array(splits).fill(input.shape[axis] / splits);
} else if (splits instanceof Array) {
sliceSizes = splits.slice();
}
let start = 0;
for (const size of sliceSizes) {
outputs.push(slice(input, [start], [size], {axes: [inpAxis]}));
outputs.push(slice(input, [start], [size], {axes: [axis]}));
start += size;
}
return outputs;
Expand Down
Loading