-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Added support for multidimensional array indexing #8491
Conversation
The memory leak I was facing in this issue has been isolated: import numpy as np
import unittest
from numba import njit
from numba.tests.support import MemoryLeakMixin, TestCase
class TestFancyIndexingMultiDim(MemoryLeakMixin, TestCase):
shape = (4, 5, 6)
def check_setitem_indices(self, arr_shape, index):
@njit
def set_item(array, idx, item):
array[idx] = item
arr = np.random.randint(0, 11, size=arr_shape)
src = arr[index]
expected = np.zeros_like(arr)
got = np.zeros_like(arr)
set_item.py_func(expected, index, src)
set_item(got, index, src)
np.testing.assert_equal(got, expected)
def test_setitem_with_tuple(self):
# When index is an array within a tuple
idx = (np.array([1,2,3]),)
# No memory leak
self.check_setitem_indices(self.shape, idx)
def test_setitem_without_tuple(self):
# When index itself is an array, it is
# supposed to be implicitly cast to same form as
# the example above i.e. both should and do
# give same answer, except for the leak
idx = np.array([1,2,3])
# Memory leaks
self.check_setitem_indices(self.shape, idx)
if __name__ == '__main__':
unittest.main() This I presume is a consequence of a fix for memory leak that was happening earlier and was 'fixed' by having the following I heavily suspect that for some reason these |
Co-authored-by: stuartarchibald <stuartarchibald@users.noreply.github.com>
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 for the PR @kc611, great to see this implemented. I've given this an initial review and have also done some initial manual testing. Once the comments are address I'll look at the implementation details more closely, but from a cursory inspection the approach seems like it should work. Thanks again!
# is being accessed, during setitem they are used as source | ||
# indices | ||
counts = list(counts) | ||
if src_type == 'buffer': |
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 don't think that this path in its present form can be supported on CUDA as the flat_imp*
function compiled below relies on:
- NumPy functions
- CPU only implementations for
reshape
- returns an array
- needs the NRT
I'm not sure of the best way to "detect" this, immediate thoughts are to either require the NRT and raise if the context doesn't have it, or alternatively declare these functions as overloads targetting CPU only which will then fail for CUDA users. CC @gmarkall do you have an opinion on this?
context.nrt.decref(builder, _indexer.idxty, | ||
_indexer.idxary_instr) |
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.
As per a later comment, this requires the presence of the NRT which isn't guaranteed and needs guarding against.
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.
Resolving this can be postponed as the 3 points in #8491 (review) will cover it.
@@ -1658,25 +1684,8 @@ def fancy_setslice(context, builder, sig, args, index_types, indices): | |||
msg = "cannot assign slice from input of different size" | |||
context.call_conv.return_user_exc(builder, ValueError, (msg,)) | |||
|
|||
# Check for array overlap | |||
src_start, src_end = get_array_memory_extents(context, builder, srcty, |
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.
Given this check is removed, does the proposed implementation correctly handle "overlap"?
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.
Define overlap in context of arrays ?
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 think in this case it would be if the source and destination share any of the same memory location.
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.
With the current implementation, I don't think that's possible since fancy indexing always creates a copy of the data. i.e. In fancy indexing the source and destination array never share memory. This is stated in the NumPy documentation as well.
I'm in-fact curious as of why it was the case over here that they did.
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 suppose the question is... in Numba, what if in practice they do share memory? Assessing and resolving this can be deferred to a subsequent PR. @DrTodd13 was asking about functions for assessing whether arrays overlap at the Numba public meeting last week. The associated functions and algorithms in NumPy look like they are something Numba could support, but are out of scope for implementing in this PR.
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 would be good for the testing to now also include indexing into F-order and A-order arrays?
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.
Alright will add that.
Co-authored-by: stuartarchibald <stuartarchibald@users.noreply.github.com>
numba/np/arrayobj.py
Outdated
self.idxty = idxty | ||
self.idxary = idxary | ||
|
||
assert self.idxty.ndim == 1, <message> |
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.
Bad syntax
…nt for buffer type indices
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 for the updates @kc611. I've given them a review and I think there's a few things to be resolved but otherwise looks good. The unit tests that are failing CI are failing due to the use of compile_isolated
and Flags()
. To fix, this:
numba/numba/tests/test_indexing.py
Lines 15 to 16 in 74cb715
enable_pyobj_flags = Flags() | |
enable_pyobj_flags.enable_pyobject = True |
could have:
enable_pyobj_flags.nrt = True
adding. Or alternatively, as object mode fallback is deprecated, forced object mode testing could be achieved by using alternative flags such as those available through importing these from testing support:
Lines 70 to 71 in 74cb715
force_pyobj_flags = Flags() | |
force_pyobj_flags.force_pyobject = True |
Hope this helps?
numba/np/arrayobj.py
Outdated
res = context.compile_internal(builder, flat_imp, sig, | ||
(idxary._getvalue(),)) |
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 think the second part of this still needs doing, i.e. move the "flat" implementations to module level so as to make use of the compilation cache.
self.context.typing_context, {idxty}, {}, | ||
) | ||
impl = self.context.get_function(fnop, callsig) | ||
res = impl(self.builder, (idxary._getvalue(),)) | ||
self.idxty = retty |
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.
To be consistent, I think retty
should be derived from the callsig
, specifically, it should be callsig.return_type
?
@@ -306,29 +306,36 @@ class TestFancyIndexingMultiDim(MemoryLeakMixin, TestCase): | |||
shape = (5, 6, 7, 8, 9, 10) | |||
indexing_cases = [ | |||
# Slices + Integers | |||
(slice(4, 5), 3, np.array([0,1,3,4,2]), 1), | |||
(slice(4, 5), 3, np.array([0, 1, 3, 4, 2]), 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.
Please could this be done throughout? Numba uses spaces after commas in this context.
Co-authored-by: stuartarchibald <stuartarchibald@users.noreply.github.com>
Co-authored-by: stuartarchibald <stuartarchibald@users.noreply.github.com>
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 for the updates @kc611, few minor things to resolve now else looks good. OOB I spoke with @gmarkall about the impact of the use of the NRT on the CUDA target. The conclusion is as follows:
- It's probably best to concentrate on getting this and the next related PR ([WIP] Advanced Indexing #3: Added support for multiple multidimensional Indices #8912) merged as the implementation needs to be figured out to start with and it's almost certainly easier to do this on the CPU with the NRT present.
- Go back to e.g. the
release0.57
branch and see what sort of "fancy indexing" actually worked on CUDA, write tests for this. - Get the tests in 2. working on
main
once these PRs are merged. I suspect it'll only be the case where the index is a non-contiguous array that's a problem as the copy is currently needed as part of "flattening" it. Once we have the tests I expect the options and limitations will become more clear.
gpuci run tests |
Co-authored-by: stuartarchibald <stuartarchibald@users.noreply.github.com>
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.
Many thanks for all your efforts on this @kc611. It's great to see this feature implemented. There's a few outstanding issues that I've left as a "review", but these should be captured in a new issue for completion in subsequent work. Most of the outstanding items relate to use of the Numba runtime which is going to take careful assessment. As alluded to previously, this will manifest in having to write a number of tests for the CUDA target to ensure that no regressions are introduced. Thanks again for work on this challenging implementation and feature, patch is approved!
if not context.enable_nrt: | ||
raise NotImplementedError("This type of indexing is not" | ||
" currently supported for" | ||
" given compiler target.") |
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.
Ideally this would be tested, but I'm inclined to leave this to a subsequent PR as I am relatively sure it's going to need to be moved.
raise NotImplementedError("This type of indexing is not currently" | ||
" supported for given compiler target.") |
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.
As above, ideally this would be tested, but I'm inclined to leave this to a subsequent PR as I am relatively sure it's going to need to be moved.
context.nrt.decref(builder, _indexer.idxty, | ||
_indexer.idxary_instr) |
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.
Resolving this can be postponed as the 3 points in #8491 (review) will cover it.
@@ -1658,25 +1684,8 @@ def fancy_setslice(context, builder, sig, args, index_types, indices): | |||
msg = "cannot assign slice from input of different size" | |||
context.call_conv.return_user_exc(builder, ValueError, (msg,)) | |||
|
|||
# Check for array overlap | |||
src_start, src_end = get_array_memory_extents(context, builder, srcty, |
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 suppose the question is... in Numba, what if in practice they do share memory? Assessing and resolving this can be deferred to a subsequent PR. @DrTodd13 was asking about functions for assessing whether arrays overlap at the Numba public meeting last week. The associated functions and algorithms in NumPy look like they are something Numba could support, but are out of scope for implementing in this PR.
|
||
# Cast to the destination dtype (cross-dtype slice assignment is allowed) | ||
val = context.cast(builder, val, src_dtype, aryty.dtype) | ||
flat_imp = njit(flat_imp) |
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.
This needs moving to a module global in a subsequent PR.
for _indexer in indexer.indexers: | ||
if isinstance(_indexer, IntegerArrayIndexer) \ | ||
and hasattr(_indexer, "idxary_instr"): | ||
context.nrt.decref(builder, _indexer.idxty, |
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.
Use of NRT needs assessing, defer to later PR.
(Ellipsis, 1, np.array([0, 1, 3, 4, 2], order='A'), 3, slice(1, 5)), | ||
(np.array([0, 1, 3, 4, 2], order='F'), 3, Ellipsis, slice(1, 5)), | ||
(np.array([[0, 1, 3, 4, 2], [0, 1, 2, 3, 2], [3, 1, 3, 4, 1]], order='A'), |
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.
Setting order='A'
in the NumPy constructor won't have the effect of it being A
ordered within Numba's type system (assuming that was the intent?) Suggest deferring fixing this to a later PR.
This PR builds on top of #8238
As titled, this PR adds support for multidimensional indices while indexing NumPy arrays in Numba JIT functions: