-
Notifications
You must be signed in to change notification settings - Fork 8
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 argMax and argMin #62
Implement argMax and argMin #62
Conversation
@huningxin I've updated commit to address your comments, please take another look, thanks. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some corner case considerations and minor suggestions, else LGTM.
src/arg_max_min.js
Outdated
} = {}) { | ||
// If axes doesn't present (defaulting to null), all dimensions are reduced. | ||
// See https://webmachinelearning.github.io/webnn/#dom-mlargminmaxoptions-axes. | ||
const inpAxes = axes ?? new Array(input.rank).fill(0).map((_, i) => i); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding axes ??
, I'm not that familiar with the falsesyness of JS evaluation. If you pass axes = []
, will that correctly use the explicit empty axes
(output
shape is then the same size as the input
, and there is a 1:1 mapping of output indices) rather than generate full reduction?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
axes = []
It's so very special, it's neither null nor a sequence having values in the range [0, N-1]. Is there any model using this axes?
According to https://webmachinelearning.github.io/webnn/#dom-mlargminmaxoptions-axes:
axes, of type sequence<unsigned long>, defaulting to null
A sequence of [unsigned long](https://webidl.spec.whatwg.org/#idl-unsigned-long). The dimensions to reduce. The values in the sequence must be in the range [0, N-1] where N is the [rank](https://webmachinelearning.github.io/webnn/#mloperand-rank) of the input tensor. If not present, all dimensions are reduced.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My argument is mainly driven by robustness plus consistency with similar behavior for the reduction operators when passed explicit []
axes. You probably won't find a model unless you look really hard, but some model generation parameters could programmatically produce it; and we see that in related reduction operators like ReduceSum that explicit empty axes []
was important enough that ONNX specifically added the noop_with_empty_axes
attribute later https://onnx.ai/onnx/operators/onnx__ReduceSum.html. Speaking of that, do we have any explicit axes = []
cases in reduceSum
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Last we also missed considering this axes = [] cases in reduceSum.
Since current Spec is lack of explanation for axes = [], I will open an issue for discussing it on WG.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will open an issue for discussing it on WG.
Alrighty, and I will add my clear expectation of what should happen in the nop case 😉 (especially if WebNN is to support existing frameworks behavior where []
means nop).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Link to webmachinelearning/webnn#493
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was fast! I tried ONNX and TF:
x = tf.constant([[0,100],[200,300]], dtype=tf.float32)
y = tf.math.reduce_sum(x, axis=[]) # https://www.tensorflow.org/api_docs/python/tf/math/reduce_sum
print("value:", y)
print("shape:", y.shape)
# Prints:
# value: tf.Tensor(
# [[ 0. 100.]
# [200. 300.]], shape=(2, 2), dtype=float32)
# shape: (2, 2)
(will add that to the issue...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to wait for the spec discussion or proceed and react later accordingly? (I don't mind either way, and you might not get a reply for the next 2 weeks...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I will follow your guide to support axes=[] here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
p.s. Appears that the Chromium implementation supports explicit axes = []
gracefully:
https://chromium-review.googlesource.com/c/chromium/src/+/5125652
src/reduce.js
Outdated
|
||
const valuesToReduce = []; | ||
// Find all values to reduce. | ||
for (let reduceIndex = 0; reduceIndex < reduceElements; ++reduceIndex) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about reducedElementCount
? reduceElements
sounds like it's actually the elements themselves (the values).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.
src/reduce.js
Outdated
const reduceDims = axes.map((axis) => input.shape[axis]); | ||
const reduceElements = sizeOfShape(reduceDims); | ||
const reduceStrides = new Array(axes.length); | ||
reduceStrides[reduceStrides.length - 1] = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Appears this will fail on the axes = []
case, which should return exactly one element of shape []
. Most of the logic already looks correct to handle that case (including sizeOfShape
which will return 1).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's strange. It can work for this case without changes, my understanding is that index should be start from 0.
reduceStrides = [];
reduceStrides[-1] = 1; // it can work
But I add if
condition block before this, then it never goes here for axes=[], please see the update commit, thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's strange. It can work for this case without changes
reduceStrides[-1] = 1; // it can work
(huh, what a weird language: https://stackoverflow.com/questions/58570277/adding-an-element-in-negative-index-of-an-array)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reduceStrides[-1] = 1
getting [-1: 1]
is bad, we should avoid that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @huningxin!
I've updated commit to avoid it, please take another look, thanks.
}, | ||
argMax, | ||
{ | ||
axes: [1, 0], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Good that you have an axis-reversed case too in the argMinMax tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
selectLastIndex: true, | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For completeness, we should include an empty axes case:
it('argMin axes=[]', function() {
testArgMaxMin(
{
shape: [3, 3],
value: [
1, 2, 3,
3, 0, 0,
2, 5, 2,
],
},
{
shape: [3, 3],
value: [
0, 0, 0,
0, 0, 0,
0, 0, 0,
],
},
argMin,
{
axes: [],
});
});
And the corner case for scalar (which can only ever return a scalar of 0):
it('argMin scalar axes=[]', function() {
testArgMaxMin(
{
shape: [],
value: [3],
},
{
shape: [],
value: [0],
},
argMin,
{
axes: [],
});
});
(note in these cases, keepDimensions
and selectLastIndex
have no effect on the output)
@fdwr I've updated commit to address your comments, please take another look. Thanks a lot! |
da3fcce
to
857871f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks Bruce. ⭐
src/reduce.js
Outdated
const reduceDims = axes.map((axis) => input.shape[axis]); | ||
const reduceElements = sizeOfShape(reduceDims); | ||
const reduceStrides = new Array(axes.length); | ||
reduceStrides[reduceStrides.length - 1] = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's strange. It can work for this case without changes
reduceStrides[-1] = 1; // it can work
(huh, what a weird language: https://stackoverflow.com/questions/58570277/adding-an-element-in-negative-index-of-an-array)
test/arg_max_min_test.js
Outdated
}); | ||
}); | ||
|
||
it('argMax scalar axes=[] none effect by both keepDimensions and selectLastIndex being true', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no effect
src/reduce.js
Outdated
const reduceDims = axes.map((axis) => input.shape[axis]); | ||
const reducedElementCount = sizeOfShape(reduceDims); | ||
const reduceStrides = new Array(axes.length); | ||
reduceStrides[reduceStrides.length - 1] = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reduceStrides[reduceStrides.length - 1] = 1; | |
if (reduceStrides.length > 0) { | |
reduceStrides[reduceStrides.length - 1] = 1; | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with Ningxin's comment applied.
@huningxin @fdwr @miaobin PTAL, thanks.