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

Added support for multidimensional array indexing #8491

Merged
merged 29 commits into from
May 9, 2023

Conversation

kc611
Copy link
Contributor

@kc611 kc611 commented Oct 5, 2022

This PR builds on top of #8238

As titled, this PR adds support for multidimensional indices while indexing NumPy arrays in Numba JIT functions:

import numba
import numpy as np

@numba.njit
def func(x, idx):
    return x[:, 2, idx, :, :]

a = np.random.randint(0, 100, (10, 11, 12, 13, 14))
b = np.random.randint(0, 10, (4, 5)) # Previously, these were limited to 1-D.

print(func(a, b).shape) 
# (10, 4, 5, 13, 14)
print(np.allclose(func(a, b), func.py_func(a, b)))
# True

@kc611
Copy link
Contributor Author

kc611 commented Nov 7, 2022

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 decrefs in place

https://github.com/numba/numba/pull/8491/files#diff-d0aefd08783016c4e2a19e29d4725c7870806ea2aa1ab4caa6e8f0adc08c6e64R1717-R1721

I heavily suspect that for some reason these decrefs aren't being triggered in the particular case given above.

@stuartarchibald
Copy link
Contributor

@kc611 please could you resolve conflicts against main? As #8238 is merged the diff will hopefully shrink. Many thanks.

@stuartarchibald stuartarchibald added 4 - Waiting on author Waiting for author to respond to review and removed 3 - Ready for Review labels Mar 17, 2023
Copy link
Contributor

@stuartarchibald stuartarchibald left a 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!

numba/core/typing/arraydecl.py Outdated Show resolved Hide resolved
numba/np/arrayobj.py Outdated Show resolved Hide resolved
numba/np/arrayobj.py Outdated Show resolved Hide resolved
numba/np/arrayobj.py Outdated Show resolved Hide resolved
numba/np/arrayobj.py Outdated Show resolved Hide resolved
numba/np/arrayobj.py Show resolved Hide resolved
# is being accessed, during setitem they are used as source
# indices
counts = list(counts)
if src_type == 'buffer':
Copy link
Contributor

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?

Comment on lines +1156 to +1157
context.nrt.decref(builder, _indexer.idxty,
_indexer.idxary_instr)
Copy link
Contributor

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.

Copy link
Contributor

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,
Copy link
Contributor

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"?

Copy link
Contributor Author

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 ?

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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?

Copy link
Contributor Author

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>
self.idxty = idxty
self.idxary = idxary

assert self.idxty.ndim == 1, <message>
Copy link
Member

Choose a reason for hiding this comment

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

Bad syntax

Copy link
Contributor

@stuartarchibald stuartarchibald left a 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:

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:

force_pyobj_flags = Flags()
force_pyobj_flags.force_pyobject = True

Hope this helps?

docs/source/reference/numpysupported.rst Outdated Show resolved Hide resolved
numba/core/typing/arraydecl.py Outdated Show resolved Hide resolved
numba/np/arrayobj.py Outdated Show resolved Hide resolved
Comment on lines 771 to 772
res = context.compile_internal(builder, flat_imp, sig,
(idxary._getvalue(),))
Copy link
Contributor

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.

numba/np/arrayobj.py Show resolved Hide resolved
self.context.typing_context, {idxty}, {},
)
impl = self.context.get_function(fnop, callsig)
res = impl(self.builder, (idxary._getvalue(),))
self.idxty = retty
Copy link
Contributor

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),
Copy link
Contributor

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.

numba/core/typing/arraydecl.py Show resolved Hide resolved
numba/np/arrayobj.py Show resolved Hide resolved
Copy link
Contributor

@stuartarchibald stuartarchibald left a 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:

  1. 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.
  2. Go back to e.g. the release0.57 branch and see what sort of "fancy indexing" actually worked on CUDA, write tests for this.
  3. 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.

numba/tests/test_fancy_indexing.py Outdated Show resolved Hide resolved
numba/tests/test_fancy_indexing.py Outdated Show resolved Hide resolved
numba/tests/test_fancy_indexing.py Outdated Show resolved Hide resolved
numba/tests/test_fancy_indexing.py Outdated Show resolved Hide resolved
numba/tests/test_fancy_indexing.py Outdated Show resolved Hide resolved
numba/tests/test_fancy_indexing.py Outdated Show resolved Hide resolved
numba/tests/test_indexing.py Outdated Show resolved Hide resolved
@gmarkall
Copy link
Member

gmarkall commented May 3, 2023

gpuci run tests

kc611 and others added 3 commits May 4, 2023 14:40
Co-authored-by: stuartarchibald <stuartarchibald@users.noreply.github.com>
Copy link
Contributor

@stuartarchibald stuartarchibald left a 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!

Comment on lines +760 to +763
if not context.enable_nrt:
raise NotImplementedError("This type of indexing is not"
" currently supported for"
" given compiler target.")
Copy link
Contributor

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.

Comment on lines +1679 to +1680
raise NotImplementedError("This type of indexing is not currently"
" supported for given compiler target.")
Copy link
Contributor

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.

Comment on lines +1156 to +1157
context.nrt.decref(builder, _indexer.idxty,
_indexer.idxary_instr)
Copy link
Contributor

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,
Copy link
Contributor

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)
Copy link
Contributor

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,
Copy link
Contributor

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.

Comment on lines +335 to +337
(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'),
Copy link
Contributor

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.

@stuartarchibald stuartarchibald added 5 - Ready to merge Review and testing done, is ready to merge and removed 4 - Waiting on author Waiting for author to respond to review labels May 4, 2023
@sklam sklam changed the base branch from main to fea/adv_indexing May 9, 2023 16:51
@sklam sklam merged commit 5c70e21 into numba:fea/adv_indexing May 9, 2023
21 checks passed
@esc esc mentioned this pull request Jun 6, 2023
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
5 - Ready to merge Review and testing done, is ready to merge Effort - long Long size effort needed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants