Reading errors and debugging

Numba · Advanced · 12 min

Numba's error messages are long; knowing how to read them and where to look saves hours.

Most failures are a TypingError raised on the first call. The message is long, but it has a fixed structure: what operation failed (for example "No implementation of function Function(<built-in function add>) found for signature: (int64, unicode_type)"), the list of candidate implementations tried, and, at the end, the source line where it happened. Read the bottom first.

Common causes:

  • a variable that takes two incompatible types in different branches, such as None and an array,
  • an unsupported function or keyword argument,
  • an untyped empty list ([] that is never appended to),
  • passing an unsupported object (a pandas Series, a Python object) as an argument.

Debugging tools:

  • NUMBA_DISABLE_JIT=1 in the environment makes all decorators no-ops, so you can use pdb, breakpoints and prints in the pure Python version.
  • print() works in compiled code for simple values.
  • f.inspect_types() shows the inferred type of every variable.
  • with numba.objmode(result="float64"): runs a block in the interpreter from compiled code, for example to call an unsupported library or a timer. Declare the types of variables that leave the block. It is slow, so keep it out of hot loops.

Runtime errors behave differently from Python: raising exceptions works for simple cases, but without boundscheck indexing errors are not caught, and integer overflow is silent. When results look wrong rather than failing, compare with f.py_func on a small input.

Example

import time
import numpy as np
from numba import njit, objmode
from numba.core.errors import TypingError

@njit
def broken(flag):
    x = 1
    if flag:
        x = "one"           # int in one branch, str in the other
    return x

try:
    broken(True)
except TypingError as e:
    print("Cannot unify" in str(e))  # True: the reason is near the end of the message

@njit
def timed_sum(a):
    with objmode(t0="float64"):    # interpreter block inside compiled code
        t0 = time.perf_counter()
    s = a.sum()
    return s, t0 > 0

print(timed_sum(np.ones(10)))      # (10.0, True)

Remember

  • Read a TypingError from the bottom: the failing source line is at the end
  • NUMBA_DISABLE_JIT=1 lets you debug the pure Python version
  • objmode runs a block in the interpreter with declared output types

Watch out for

  • Leaving objmode blocks inside hot loops, which brings back interpreter overhead.

Check yourself

  1. A variable is assigned None in one branch and an array in another. What is the likely result?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: numba.readthedocs.io/en/stable/user/troubleshoot.html