Skip to content

Add support for arrayanalysis of tuple args. - #7398

Merged
sklam merged 16 commits into
numba:masterfrom
IntelLabs:parfor_has_shape
Oct 6, 2021
Merged

sklam merged 16 commits into
numba:masterfrom
IntelLabs:parfor_has_shape

Conversation

@DrTodd13

Copy link
Copy Markdown
Contributor

Previously, if a tuple of integers were passed in and then used in a pndindex, the parfor pass would fail because the tuple of dimension sizes didn't have an entry in array analysis. This PR adds integer tuple arguments to array analysis whereas previously only integer tuple variables were supported.

@gmarkall

Copy link
Copy Markdown
Member

@DrTodd13 Thanks for the PR! Is there a related issue for this PR?

@stuartarchibald

Copy link
Copy Markdown
Contributor

Thanks for the patch. I wonder if adding the expression to equivalence analysis creates a problem for legitimate cases such as this:

from numba import njit, pndindex
import numpy as np

@njit(parallel=True)
def test_impl(x, sz):
    for i in pndindex(sz):
        x[i] = 1
    return x

@njit(parallel=True)
def call(x):
    return test_impl(x, (10, 3)) # Only want to iterate the 3rd column

print(call(np.empty((10, 10))))

where a user wants to just iterate to the 3rd column of the 2d array.

@stuartarchibald stuartarchibald added 4 - Waiting on author Waiting for author to respond to review and removed 3 - Ready for Review labels Sep 14, 2021
@DrTodd13

Copy link
Copy Markdown
Contributor Author

@DrTodd13 Thanks for the PR! Is there a related issue for this PR?

No. It was just something that I ran across in the course of my regular work that I needed to work in Numba.

@DrTodd13

Copy link
Copy Markdown
Contributor Author

@stuartarchibald If I don't make "call" njit then your example works. If I make "call" njit then I get an internal typing error at numba.core.typeinfer.CallConstraint which doesn't look related to my changes.

@stuartarchibald

Copy link
Copy Markdown
Contributor

@stuartarchibald If I don't make "call" njit then your example works. If I make "call" njit then I get an internal typing error at numba.core.typeinfer.CallConstraint which doesn't look related to my changes.

I see the same, it's a chained exception, it looks a bit like:

<snip>
numba/parfors/parfor.py", line 2441, in run
    assert(equiv_set.has_shape(args[0]))
AssertionError: Failed in nopython mode pipeline (step: convert to parfors)


The above exception was the direct cause of the following exception:

<snip>
numba/core/typeinfer.py", line 1074, in propagate
    raise errors[0]
numba.core.errors.TypingError: Failed in nopython mode pipeline (step: nopython frontend)
Internal error at <numba.core.typeinfer.CallConstraint object at 0x7fe012437940>.
Failed in nopython mode pipeline (step: convert to parfors

i.e. an AssertionError in parfors triggered it.

@DrTodd13 DrTodd13 added 4 - Waiting on reviewer Waiting for reviewer to respond to author and removed 4 - Waiting on author Waiting for author to respond to review labels Sep 17, 2021
@DrTodd13

Copy link
Copy Markdown
Contributor Author

@stuartarchibald When you make the caller Numba then the tuple (10,3) doesn't become a UniTuple of Literal[int] but a regular Tuple of two Literal[int]. Is there a reason for that? In any case, maybe that is better because you can also have a Tuple with one literal and the other a non-literal which my recent patch to this PR supports. Please have another look. I added an additional test for this mixed case (which subsumes the case of two literals).

@DrTodd13 DrTodd13 added 2 - In Progress and removed 4 - Waiting on reviewer Waiting for reviewer to respond to author labels Sep 17, 2021
@DrTodd13 DrTodd13 added 4 - Waiting on reviewer Waiting for reviewer to respond to author and removed 2 - In Progress labels Sep 20, 2021
@stuartarchibald

Copy link
Copy Markdown
Contributor

@stuartarchibald When you make the caller Numba then the tuple (10,3) doesn't become a UniTuple of Literal[int] but a regular Tuple of two Literal[int]. Is there a reason for that?

I think it's because the IR preserves literals into the tuple type during type inference.

def __call__(self, typeinfer):
with new_error_context("typing of tuple at {0}", self.loc):
typevars = typeinfer.typevars
tsets = [typevars[i.name].get() for i in self.items]
for vals in itertools.product(*tsets):
if vals and all(vals[0] == v for v in vals):
tup = types.UniTuple(dtype=vals[0], count=len(vals))
else:
# empty tuples fall here as well
tup = types.Tuple(vals)
assert tup.is_precise()
typeinfer.add_type(self.target, tup, loc=self.loc)

This bit:

if vals and all(vals[0] == v for v in vals): 

will be False as the literal values will compare False. I think this might be from trying to preserve as much constant type information as possible.

In any case, maybe that is better because you can also have a Tuple with one literal and the other a non-literal which my recent patch to this PR supports. Please have another look. I added an additional test for this mixed case (which subsumes the case of two literals).

I think that this is indeed the case.

@stuartarchibald stuartarchibald left a comment

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.

Thanks for the update. I've taken a look through and have left some new comments inline. I also wonder if it'd be a good idea to include the test case from here: #7398 (comment) somewhere so as to ensure this common pattern works.

Thanks again!

Comment thread numba/tests/test_parfors.py Outdated
for i in numba.pndindex(sz):
x[i] = 1
return x
sz = (10,numba.literally(10))

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.

I'm not sure that this has the desired effect. numba.literally outside of compiled code is a pass through NOP:

def literally(obj):
"""Forces Numba to interpret *obj* as an Literal value.
*obj* must be either a literal or an argument of the caller function, where
the argument must be bound to a literal. The literal requirement
propagates up the call stack.
This function is intercepted by the compiler to alter the compilation
behavior to wrap the corresponding function parameters as ``Literal``.
It has **no effect** outside of nopython-mode (interpreter, and objectmode).
The current implementation detects literal arguments in two ways:
1. Scans for uses of ``literally`` via a compiler pass.
2. ``literally`` is overloaded to raise ``numba.errors.ForceLiteralArg``
to signal the dispatcher to treat the corresponding parameter
differently. This mode is to support indirect use (via a function call).
The execution semantic of this function is equivalent to an identity
function.
See :ghfile:`numba/tests/test_literal_dispatch.py` for examples.
"""
return obj

it might be necessary to close over or locally define a const to get a literal if that's the intent?

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.

I've added some tests and tried to generate the right tuples using what I think you were suggesting above with one literal arg but there is some lowering error I don't understand. Can you take a look at it?

Comment thread numba/parfors/array_analysis.py
Comment thread numba/parfors/array_analysis.py
Comment thread numba/parfors/parfor.py
@stuartarchibald stuartarchibald added 4 - Waiting on author Waiting for author to respond to review and removed 4 - Waiting on reviewer Waiting for reviewer to respond to author labels Oct 5, 2021
@stuartarchibald stuartarchibald added this to the Numba 0.55 RC milestone Oct 5, 2021
@stuartarchibald stuartarchibald added the Effort - medium Medium size effort needed label Oct 5, 2021
Comment thread numba/tests/test_parfors.py
Comment thread numba/tests/test_parfors.py Outdated
Comment thread numba/parfors/parfor.py
in_arr_typ = pass_states.typemap[in_arr.name]
if isinstance(in_arr_typ, types.Integer):
index_var = ir.Var(scope, mk_unique_var("parfor_index"), loc)
pass_states.typemap[index_var.name] = types.uintp

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.

Not to be fixed in this PR. But I noticed that both njit and njit(parallel=True) will compiled and execute a pndindex driven loop where the arg supplied to pndindex is a negative integer. e.g.

@njit(parallel=True)
def test_impl(x, sz):
    for i in pndindex(sz):
        x[i] = 1
    return x

print(test_impl(np.zeros((10, 10)), -3))

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.

Tracked in #7462

@stuartarchibald stuartarchibald left a comment

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.

Thanks for the patch and fixes.

@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 Oct 6, 2021
@sklam
sklam merged commit f780d59 into numba:master Oct 6, 2021
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 - medium Medium size effort needed ParallelAccelerator

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants