Skip to content

BUG: Fix uint-overflow if padding with linear_ramp and negative gain - #14209

Merged
charris merged 1 commit into
numpy:masterfrom
lagru:fix-linear-ramp
Sep 5, 2019
Merged

charris merged 1 commit into
numpy:masterfrom
lagru:fix-linear-ramp

Conversation

@lagru

@lagru lagru commented Aug 6, 2019 •

Copy link
Copy Markdown
Contributor

In case end_value was greater than the edge values of the array to pad, the resulting difference was negative and caused overflows in case the input array used unsigned types. Fixed by using 1.17 version of linspace instead of _linear_ramp.

Protect against this regression with a new test for all numeric dtypes.

Fixes #14191.

cc @seberg, @aleju

@lagru

lagru commented Aug 6, 2019

Copy link
Copy Markdown
Contributor Author

CI failures seem unrelated.

@seberg
seberg self-requested a review August 6, 2019 15:36
@lagru

lagru commented Aug 6, 2019 •

Copy link
Copy Markdown
Contributor Author

I'm not sure how the "09 - Backport-Candidate" label is understood here but simply cherry-picking these changes won't work on the 1.16-release because of the rewrite of pad's internals in the meantime...

1.17 should be fine though.

@seberg

seberg commented Aug 6, 2019

Copy link
Copy Markdown
Member

Oh, sorry. I thought this was a regression. It is a bug that exists seperately both on 1.16 and 1.7 I guess.

Comment thread numpy/lib/arraypad.py Resolved
Comment thread numpy/lib/arraypad.py Resolved
@mattip

mattip commented Aug 15, 2019

Copy link
Copy Markdown
Member

@seberg ping

@charris

charris commented Aug 16, 2019

Copy link
Copy Markdown
Member

@eric-wieser Ping.

@eric-wieser

Copy link
Copy Markdown
Member

Will take a look when I'm back from vacation next week

@charris charris added this to the 1.17.1 release milestone Aug 20, 2019
@charris

charris commented Aug 20, 2019

Copy link
Copy Markdown
Member

Ping all :)

Comment thread numpy/lib/arraypad.py Resolved
@charris

charris commented Aug 21, 2019

Copy link
Copy Markdown
Member

Pushing off to 1.17.2. Will still put in in 1.17.1 if done in time.

@charris

charris commented Aug 22, 2019

Copy link
Copy Markdown
Member

I note that there is already a backport for 1.16.5. I think that should be closed until this is ready and merged.

Comment thread numpy/lib/arraypad.py Resolved
@lagru

lagru commented Aug 23, 2019

Copy link
Copy Markdown
Contributor Author

Well benchmarks are a little disheartening (shows only changes with a factor >= 1.1):

taskset -c 1 asv continuous -E :3.7 -b Pad master fix-linear-ramp
       before           after         ratio
     [5f7f8e19]       [3461da9a]
     <master>         <fix-linear-ramp>
+       174±0.2μs        329±0.6μs     1.89  bench_lib.Pad.time_pad((1, 1, 1, 1, 1), 1, 'linear_ramp')
+       155±0.8μs        278±0.9μs     1.80  bench_lib.Pad.time_pad((4, 4, 4, 4), 1, 'linear_ramp')
+     5.97±0.05ms      8.87±0.09ms     1.49  bench_lib.Pad.time_pad((4, 4, 4, 4), (0, 32), 'linear_ramp')
+         730±4μs          869±2μs     1.19  bench_lib.Pad.time_pad((256, 128, 1), 1, 'linear_ramp')
+     1.40±0.01ms      1.62±0.03ms     1.15  bench_lib.Pad.time_pad((4, 4, 4, 4), 8, 'linear_ramp')

SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.

A few cases for higher dimensions seem to be affected only.

Having looked at how linspace is implemented I could try to copy/adapt the type handling into the old _linear_ramp method if you feel the optimization is worth it.

Comment thread numpy/lib/arraypad.py Resolved
Comment thread numpy/lib/arraypad.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does any of this docstring need transferring to below?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In my opinion, no. All parameters of _get_linear_ramps are already documented and _linear_ramp is fully replaced by linspace. Oh, and edge_pair is documented as the return value of _get_edges. Should be fine.

Comment thread numpy/lib/arraypad.py Outdated

@eric-wieser eric-wieser Aug 25, 2019 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think the +1 works correctly if axis is negative.

If you squeeze before linspace, I think the problem will go away - start=end_value_pair[1].squeeze(axis) etc

For that to work, you might first have to use something like

end_value_pair = [
    np.broadcast_to(end_v, edge_v.shape)
    for end_v, edge_v in zip(end_value_pair, edge_pair)
]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Actually, I'm now wondering if we should add a keepdims=True to linspace, with semantics "replace the axisth axis with the new linspace and squeeze". @mhvk, do you have thoughts on this?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@eric-wieser - you mean something like the opposite of a reduction, where keepdims=True tells linspace that there is already a dimension of 1 inside start and stop? On the one hand, it doesn't quite feel as natural, since in that case one has to check that that dimension is indeed 1. On the other hand, I can see the nice aspect that then start and stop would broadcast with the result. Overall, the "other hand" wins for me...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Exactly.

@lagru lagru Aug 27, 2019 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If you squeeze before linspace, I think the problem will go away - start=end_value_pair[1].squeeze(axis) etc

That did the trick. I think broadcasting the end_value_pair is not necessary. linspace does that itself when constructing delta. Testing seems to confirm that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Opened #14425 from that suggestion

linspace supports non-scalar values since version 1.16. This can replace
the former _linear_ramp method.

This removes a bug in the old implementation where certain edge and
end values resulted in an integer underflow for unsigned dtypes (see
numpygh-14191). Protect against this regression with a new test for all
numeric dtypes.
@charris

charris commented Sep 4, 2019

Copy link
Copy Markdown
Member

@eric-wieser Ping.

@eric-wieser eric-wieser left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Really pleased that the linspace improvements allowed a bunch of code to be deleted here!

@charris
charris merged commit 4da9256 into numpy:master Sep 5, 2019
@charris

charris commented Sep 5, 2019

Copy link
Copy Markdown
Member

Thanks @lagru . Time for #14211

@lagru
lagru deleted the fix-linear-ramp branch September 5, 2019 18:09
@lagru

lagru commented Sep 5, 2019

Copy link
Copy Markdown
Contributor Author

You're welcome but the important ideas where all yours. 😉

@charris charris removed the 09 - Backport-Candidate PRs tagged should be backported label Sep 5, 2019
grlee77 added a commit to grlee77/cupy that referenced this pull request Sep 11, 2019
grlee77 added a commit to grlee77/cupy that referenced this pull request Sep 23, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unexpected behaviour by numpy.pad() with mode="linear_ramp" and uint dtypes in 1.17

6 participants