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

TensorPrimitives.IndexOfXx methods sometimes return the wrong index in the face of equality #97190

Closed
stephentoub opened this issue Jan 19, 2024 · 2 comments · Fixed by #97192

Comments

@stephentoub
Copy link
Member

stephentoub commented Jan 19, 2024

Description

When there are multiple equal elements that could have their index returned, the one with the smallest index is supposed to be returned. But it isn't always.

Reproduction Steps

using System.Numerics.Tensors;

ReadOnlySpan<float> floats = [10f, -20f, -30f, 1f, 1f, -40f];
Console.WriteLine(TensorPrimitives.IndexOfMinMagnitude(floats));

or

using System.Numerics.Tensors;

ReadOnlySpan<float> floats = [10f, 20f, 30f, 1f, 1f, 40f];
Console.WriteLine(TensorPrimitives.IndexOfMin(floats));

Expected behavior

Those should both print 3.

Actual behavior

They print 4.

Regression?

No

Known Workarounds

No response

Configuration

System.Numerics.Tensors v8.0.0 nuget package

Other information

No response

@ghost ghost added the untriaged New issue has not been triaged by the area owner label Jan 19, 2024
@ghost
Copy link

ghost commented Jan 19, 2024

Tagging subscribers to this area: @dotnet/area-system-numerics
See info in area-owners.md if you want to be subscribed.

Issue Details

Description

When there are multiple equal elements that could have their index returned, the one with the smallest index is supposed to be returned. But it isn't always.

Reproduction Steps

using System.Numerics.Tensors;

ReadOnlySpan<float> floats = [10f, -20f, -30f, 1f, 1f, -40f];
Console.WriteLine(TensorPrimitives.IndexOfMinMagnitude(floats));

Expected behavior

That should print 3.

Actual behavior

It prints 4.

Regression?

No

Known Workarounds

No response

Configuration

System.Numerics.Tensors v8.0.0 nuget package

Other information

No response

Author: stephentoub
Assignees: -
Labels:

area-System.Numerics, untriaged

Milestone: -

@stephentoub stephentoub self-assigned this Jan 19, 2024
@stephentoub stephentoub added bug and removed untriaged New issue has not been triaged by the area owner labels Jan 19, 2024
@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Jan 19, 2024
@tannergooding
Copy link
Member

I think the bug is here in the equality handling check: https://source.dot.net/#System.Numerics.Tensors/System/Numerics/Tensors/netcore/TensorPrimitives.netcore.cs,10125

We'll get down to the MinAcross logic and end up comparing result = [10, 1, 1, 1]; resultIndex = [0, 3, 4, 3] to tmpResult = [1, 1, 10, 1]; tmpIndex = [4, 3, 0, 3].

The lessThanMask will be [false, false, true, false], the equalMask will be [false, true, false, true], the negativeMask will be [false, false, false, false], and the lessThanIndexMask will be [true, false, false, false].

We will then modify the lessThanMask using the other masks, giving us [false, true, true, true]. This then picks result = [1, 1, 1, 1]; resultIndex = [4, 3, 4, 3] and will compare that to tmpResult = [1, 1, 1, 1]; tmpIndex = [3, 4, 3, 4].

The lessThanMask will be [false, false, false, false], the equalMask will be [true, true, true, true], the negativeMask will be [false, false, false, false], and the lessThanIndexMask will be [false, true, false, true].

We will then modify the lessThanMask using the other masks, giving us [true, true, true, true] which causes us to preserve result = [1, 1, 1, 1]; resultIndex = [4, 3, 4, 3] and return 4 since its the first entry.


So the failure here looks to come about because lessThanIndexMask is only used when there is some negative in result. We'd need to modify this to use it in more than just this scenario to ensure that the lesser index is picked.

What we have now is effectively:

Vector128<float> currentNegativeMask = IsNegative(current);

// Take the entry from current if it is equal to result and negative
lessThanMask |= Vector128.AndNot(equalMask, currentNegativeMask);

// Take the entry from result if it is equal to current and negative and its index is lesser
lessThanMask |= equalMask & IsNegative(result) & lessThanIndexMask;

What we need to say is:

  • If both are equal and have the same sign, pick the lesser index
  • If both are equal and only one is negative, pick the negative one

This is required to ensure +0 vs -0 is handled correctly.

@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Jan 22, 2024
@github-actions github-actions bot locked and limited conversation to collaborators Feb 21, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants