Repository navigation
ENH: support reduction loops in ufunc.accumulate - #32213
Conversation
|
This is a sister PR to #32212 so depending on the merge order, a little bit of buffering is required between them to consider the review comments of the one to the other and rebase whilst fixing merge conflicts. |
b13b565 to
29b425a
Compare
f14f365 to
faec809
Compare
faec809 to
897a462
Compare
|
The branch is now on top of #32212 and I have converted it to draft. The review will continue in that one, and once that one has converged, all the findings from that review will be applied here and once its merged on main the diff will shorten significantly. Now the branch holds 2 PRs in the diff. |
fbfe52e to
54e6813
Compare
| got_min, got_max = mm.reduce(a, initial=(7, -7)) | ||
| assert got_min == np.minimum.reduce(a, initial=7) | ||
| assert got_max == np.maximum.reduce(a, initial=-7) | ||
| def test_accumulate_identity_ignored(self): |
There was a problem hiding this comment.
Are you sure this test is actually testing something useful? The mmi ufunc uses +infinity and -infinity as its default identity, which makes this trivially pass. If you defined a version mmi that raises if the initial value callback is called would allow you to verify that does not in fact ever happen.
There was a problem hiding this comment.
I have removed this test in #32212 actually cause indeed it wasn't testing anything. If you want me to add yet another ufunc in _reduction_loop_tests.cto do the test you suggested here, let me know.
fb1395e to
9c59abb
Compare
0a07ad5 to
c88841d
Compare
|
Okay this is now rebased on top of main since #32212 has been merged and the feedback from that PR has been considered here. This should be ready for a review pass now @ngoldbaum |
|
I only found one issue during this review pass. Consider this IPython script: On 47f8d7a (just before #32212 was merged), this prints on my ARM macbook: And on this PR it prints: Object reductions show the slowdown more clearly for both operations but I'm only able to reproduce it for I think to get the performance back, you need to restore the old specialized single-output loops with something like: if (nout == 1) {
/* Specialized single-output loop. */
}
else {
/* Generalized multi-output loop. */
}That said, I'm not sure how load-bearing these code paths are for performance or if it's worth hyper-optimizing. |
|
I'll do an investigation. I did not see this when we had done |
|
Oh I see what you did in the benchmark, you are calling reductions along small axes....yeah and the inner loop is called once per row and now each inner loop has more fixed setup overhead I guess which is comparable to the actual loop call. I originally thought you had found some O(N) regression but yeah...it's a small dimension problem. Got scared a bit for my code 🤣 |
|
Oh the main issue is not that we are dealing with size 1 arrays now to cover the multi output reductions....It's the refactoring to deduplicate code that introduced more functions and function calls and helpers on the hot path of calling the loop for these small dimension reductions.... |
Hmm maybe we can give hints to the compiler to encourage inlining |
Yeah I'm trying different things out accros macos and linux to see if I can do anything. There's |
|
@ngoldbaum see cb5224f for my attempt. |
|
Looks nice! Just a thought: why not specialize two-output reductions too? |
Hmm that's interesting, do you wanna move this to a follow-up PR perhaps? Worth an investigation for sure but as long as we're not doing any bad regressions here I'd want to get |
|
@ikrommyd I was thinking of something like this: https://gist.github.com/ngoldbaum/37a0d444df0f2b9b48822ea0b476476e On my Mac using Clang, the patch makes the minmax call timed in this IPython script about 10% faster: import numpy as np
a = np.ones((10_000, 15))
%timeit np.minmax(a, axis=1)(103 vs 92.5 microseconds). |
Signed-off-by: Iason Krommydas <[email protected]>
Signed-off-by: Iason Krommydas <[email protected]>
Signed-off-by: Iason Krommydas <[email protected]>
Signed-off-by: Iason Krommydas <[email protected]>
Signed-off-by: Iason Krommydas <[email protected]>
Signed-off-by: Iason Krommydas <[email protected]>
Signed-off-by: Iason Krommydas <[email protected]>
Signed-off-by: Iason Krommydas <[email protected]>
Signed-off-by: Iason Krommydas <[email protected]>
…erlapping outputs Signed-off-by: Iason Krommydas <[email protected]>
Signed-off-by: Iason Krommydas <[email protected]>
…ns on the nominal path Signed-off-by: Iason Krommydas <[email protected]>
Signed-off-by: Iason Krommydas <[email protected]>
cb5224f to
14056e3
Compare
|
I added an nout=2 specialization too @ngoldbaum. |
ngoldbaum
left a comment
There was a problem hiding this comment.
LGTM! Feel free to merge when CI finishes. My last round of review found one small cleanup for a pre-existing issue on main that can come in a followup or right here if you wish. Currently the complex minimummaximum ufunc loop fails to clear the floating point error status which leads to issues like this:
In [1]: import numpy as np
In [2]: a = np.array([[1, np.nan]], dtype=np.complex128)
In [3]: with np.errstate(invalid="raise"):
...: print(np.minimum.reduceat(a, [0], axis=1))
...: print(np.maximum.reduceat(a, [0], axis=1))
...:
[[nan+0.j]]
[[nan+0.j]]
In [4]: with np.errstate(invalid="raise"):
...: np._core.umath.minimummaximum.reduceat(a, [0], axis=1)
...:
FloatingPointError: invalid value encountered in reduceat
Don't feel like you need to wait for me to re-review if you apply this patch to fix the problem and add a test:
diff --git a/numpy/_core/src/umath/loops.c.src b/numpy/_core/src/umath/loops.c.src
--- a/numpy/_core/src/umath/loops.c.src
+++ b/numpy/_core/src/umath/loops.c.src
@@ -2547,6 +2547,7 @@
acc_min += strides[0]; acc_max += strides[1]; x += strides[2];
out_min += strides[3]; out_max += strides[4];
}
+ npy_clear_floatstatus_barrier((char*)dimensions);
return 0;
}
diff --git a/numpy/_core/tests/test_umath.py b/numpy/_core/tests/test_umath.py
--- a/numpy/_core/tests/test_umath.py
+++ b/numpy/_core/tests/test_umath.py
@@ -2800,6 +2800,8 @@
arg1 = np.array([0, cnan, cnan], dtype=complex)
arg2 = np.array([cnan, 0, cnan], dtype=complex)
self.check(arg1, arg2)
+ with np.errstate(invalid="raise"):
+ self.check_accumulate(arg1)
def test_object_array(self):
arg1 = np.arange(5, dtype=object)Signed-off-by: Iason Krommydas <[email protected]>
|
Thanks Nathan! Folded the patch in here. Will merge once CI is happy one last time. Cheers! |
PR summary
Expands on top of #31816 and allows
ufunc.accumulateto use the reduction loops registered on the ufuncFirst time committer introduction
N/A
AI Disclosure
AI has been used for review to find bugs and apply fixes