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

'<' not supported between instances of 'NoneType' and 'int' #4163

Closed
jazcarretao opened this issue Jun 11, 2019 · 6 comments · Fixed by #4172
Closed

'<' not supported between instances of 'NoneType' and 'int' #4163

jazcarretao opened this issue Jun 11, 2019 · 6 comments · Fixed by #4172
Labels
bug SSA Problem due to SSA (or lack of)

Comments

@jazcarretao
Copy link

jazcarretao commented Jun 11, 2019

After updating to numba , our code started to get the following error:

@jit(nopython=True)
def foo(array, a=None, b=None):
    if a is None:
        a =  0
    if b is None or b > array.shape[0]:
        b = array.shape[0]
    if a < 0 or b < 0:
        # raise error
    if b < a:
        # raise error

where array is a numpy array and a and b are int types.

This was working fine, however, after we got the new release we are getting the following error:

  File ".../conda/envs/py36-gpu/lib/python3.6/site-packages/numba/analysis.py", line 305, in prune_by_type
    take_truebr = condition.fn(lhs_cond, rhs_cond)
TypeError: Failed in nopython mode pipeline (step: dead branch pruning)
'<' not supported between instances of 'NoneType' and 'int'

I could fix it like this:

@jit(nopython=True)
def foo(array, a=0, b=None):
    if b is None:
        b =  array.shape[0]
    if b > array.shape[0]
        b = array.shape[0]
    if a < 0:
        # raise error
    if b < a:
        # raise error
@stuartarchibald
Copy link
Contributor

Thanks for the report. Which version of Numba are you using?

I think the issue is that the value of a is redefined in the code and not propagated.

@stuartarchibald stuartarchibald added bug SSA Problem due to SSA (or lack of) labels Jun 11, 2019
@jazcarretao
Copy link
Author

Thanks for your response.
I am using numba==0.43.1, the code was working as expected for previous versions.

@stuartarchibald
Copy link
Contributor

Thanks, 0.43+ has new features to deal with a really common problem pattern whereby type inference would fail because it would need to evaluate all branches in code and get stuck trying to resolve one of them that is actually dead, for example a+b (int + None) in the second branch here is dead:

from numba import njit

@njit
def foo(a, b=None):
    if b is None:
        return a
    else:
        return a + b

print(foo(10))

the new feature detects dead branches and removes them to permit more code to work.

The reason for the failure you are seeing is that in your sample code a gets redefined in a block and the new value isn't propagated to later call sites (it's still seeing a=None), hence comparison between int and None with <.

@jit(nopython=True)
def foo(array, a=None, b=None):
    if a is None:
        a =  0
    if b is None or b > array.shape[0]:
        b = array.shape[0]
    if a < 0 or b < 0:
        # raise error
    if b < a:
        # raise error

a few potential fixes, 1) add a guard in branch pruning to abort when a comparison fails 2) full SSA base IR would probably help as the definition would be the phi-of-a and so not considered const-like? 3) thinking about 2) perhaps if there's a redefinition of a variable abort anyway.

stuartarchibald added a commit to stuartarchibald/numba that referenced this issue Jun 12, 2019
This makes it so that branch pruning for a given input arg is
abandoned if an input arg is redefined in the user code as its
value potentially becomes non-const (and no const-prop analysis
yet to figure it out).

Fixes numba#4163
@jazcarretao
Copy link
Author

jazcarretao commented Jun 13, 2019

Thank you a lot for clarifying things. What still confuses me is that the following code works:

@jit(nopython=True)
def foo(array, a=0, b=None):
    if b is None:
        b =  array.shape[0]
    if b > array.shape[0]:
        b = array.shape[0]
    if a < 0:
        # raise error
    if b < a:
        # raise error

If I understood you properly, here I would expect the value of b not to propagate properly and getting the same error as before. However, this code works fine. If you could clarify why this instance works it would be very helpful.

@stuartarchibald
Copy link
Contributor

No problem. What you are seeing is down to the choice of default value for a.

This breaks:

from numba import jit
import numpy as np

@jit(nopython=True)
def foo(array, a=None, b=None):
    if b is None:
        b =  array.shape[0]
    if b > array.shape[0]:
        b = array.shape[0]
    if a < 0:
        raise ValueError("branch1")
    if b < a:
        raise ValueError("branch2")

print(foo(np.zeros(10)))

but this works:

from numba import jit
import numpy as np

@jit(nopython=True)
def foo(array, a=0 b=None): # <--- only change is default, a=0
    if b is None:
        b =  array.shape[0]
    if b > array.shape[0]:
        b = array.shape[0]
    if a < 0:
        raise ValueError("branch1")
    if b < a:
        raise ValueError("branch2")

print(foo(np.zeros(10)))

and this is also broken, note the function is the same as the working one, it's just that the input is a=None:

from numba import jit
import numpy as np

@jit(nopython=True)
def foo(array, a=0, b=None):
    if b is None:
        b =  array.shape[0]
    if b > array.shape[0]:
        b = array.shape[0]
    if a < 0:
        raise ValueError("branch1")
    if b < a:
        raise ValueError("branch2")

print(foo(np.zeros(10), a=None)) # <--- set a=None

This all comes down to how pruning is working. There's two types of pruning possible, type based and value based, this problem is impacting type based pruning. The type based pruning works by finding the types of all the arguments and then seeing if any of them are NoneType. As python None is singleton and has the inferred type of NoneType it is possible to evaluate comparisons to None (or between variables that are NoneType) at the type level. The first example breaks because a = None leads to an evaluation between a NoneType type and a literal integer 0. The second example doesn't break because the default is a = 0, so type based comparison won't be used (as a is not a NoneType type, it's an IntType type). Finally, the last example breaks because a=None is given in the signature, the dispatcher compiles a specialised function for this signature (types.Array, types.NoneType, types.IntType) [note I've simplified this] and as a result the value for a is considered a constant None and therefore a NoneType type, so type based pruning occurs for it, and it's basically the same problem as the first case. The reason in part that this compiler pass exists is to make it so that code which would fail type inference because of e.g. the common pattern of "default for an argument is None, if at runtime the argument is None then update the argument variable to something sensible" does not fail any more, it unfortunately gets a bit tricky.

Hope this helps.

seibert pushed a commit that referenced this issue Jun 16, 2019
This makes it so that branch pruning for a given input arg is
abandoned if an input arg is redefined in the user code as its
value potentially becomes non-const (and no const-prop analysis
yet to figure it out).

Fixes #4163
@jazcarretao
Copy link
Author

Thank you for the explanation, very helpful indeed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug SSA Problem due to SSA (or lack of)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants