常见问题

安装

Numba 无法导入

如果在导入 Numba 时看到异常,并且错误信息以以下内容开头:

ImportError: Numba could not be imported.

以下是一些常见问题及其解决方法。

  1. 您的安装在给定环境中包含多个 Numba 版本。

    常见的情况包括:

    • 使用 conda 安装 Numba 后,再使用 pip 重新安装。

    • 使用 pip 安装 Numba,然后使用 pip 更新到新版本(pip 重新安装似乎并不总是清理得很干净)。

    要解决这个问题,最好的方法是创建一个全新的环境,并使用你选择的包管理器在该环境中安装单一版本的 Numba。

  2. 您的安装包中包含适用于Python版本X的Numba,但您当前运行的是Python版本Y。

    这通常是由于多种Python环境混淆/不匹配问题引起的。最常见的不匹配问题来自于通过使用不同版本的基础或系统Python安装,将Numba安装到某个Python版本的site-packages/环境中,这通常是通过使用“错误”的``pip``二进制文件发生的。这显然会导致问题,因为Numba依赖的C扩展是绑定到特定Python版本的。要检查这是否可能是问题,可以查看以下路径中的``python``二进制文件:

    python -c 'import sys; print(sys.executable)'
    

    匹配您的安装工具的路径和/或匹配报告的安装位置,并且如果所有这些路径中的Python版本匹配。请注意,Python版本 X.Y.AX.Y.B 兼容。

    要解决这个问题,最好的方法是创建一个全新的环境,并确保用于安装Numba的安装工具来自该环境/安装和运行时的Python版本匹配。

  3. 您的核心系统库太旧了。

    这种情况比较少见,但有时会遇到使用非常旧的(通常是已停止支持的)Linux版本,该版本没有足够新版本的 glibc 库,无法为Numba的共享库解析符号。解决这个问题的方法是更新你的操作系统库/更新你的操作系统。

  4. 您正在使用一个集成开发环境(IDE),例如 Spyder。

    在通过IDE安装Numba时存在一些未知问题,但这些问题似乎是1.或2.的变体,建议采用相同的修复方法。此外,尝试在IDE外部使用命令行进行安装。

如果你遇到的问题不属于上述问题之一,请在 numba.discourse.group 上提问,并尽可能包括 Numba 的安装路径以及以下命令的输出:

python -c 'import sys; print(sys.executable)'

编程

我可以将一个函数作为参数传递给一个 jitted 函数吗?

截至 Numba 0.39,只要函数参数也已被 JIT 编译,你就可以这样做:

@jit(nopython=True)
def f(g, x):
    return g(x) + g(-x)

result = f(jitted_g_function, 1)

然而,使用函数作为参数进行调度会有额外的开销。如果这对你的应用程序很重要,你也可以使用工厂函数来在闭包中捕获函数参数:

def make_f(g):
    # Note: a new f() is created each time make_f() is called!
    @jit(nopython=True)
    def f(x):
        return g(x) + g(-x)
    return f

f = make_f(jitted_g_function)
result = f(1)

提高Numba中函数的调度性能是一项持续的任务。

当我修改全局变量时,Numba 似乎并不在意。

Numba 将全局变量视为编译时常量。如果你想让你的即时编译函数在你修改了全局变量的值后更新自身,一种解决方案是使用 recompile() 方法重新编译它。不过,这是一个相对较慢的操作,因此你可能会选择重新架构你的代码,并将全局变量转换为函数参数。

我可以调试一个jit编译的函数吗?

从 Numba 编译的代码中调用 pdb 或其他类似的高级设施目前不受支持。但是,您可以通过设置 NUMBA_DISABLE_JIT 环境变量来暂时禁用编译。

如何创建一个Fortran顺序的数组?

Numba 目前不支持大多数 Numpy 函数(如 numpy.empty())中的 order 参数(由于 类型推断 算法的限制)。您可以通过创建一个 C 顺序的数组然后对其进行转置来解决这个问题。例如:

a = np.empty((3, 5), order='F')
b = np.zeros(some_shape, order='F')

可以重写为:

a = np.empty((5, 3)).T
b = np.zeros(some_shape[::-1]).T

如何增加整数宽度?

默认情况下,Numba 通常会使用机器整数宽度来处理整数变量。在32位机器上,有时你可能需要64位整数的范围。你可以简单地将相关变量初始化为 np.int64``(例如 ``np.int64(0) 而不是 0)。它将传播到所有涉及这些变量的计算中。

如何判断 parallel=True 是否生效?

如果 parallel=True 转换对一个这样装饰的函数失败了,将会显示一个警告。关于并行诊断的信息,请参见 诊断

性能

Numba 会内联函数吗?

Numba 提供了足够的信息给 LLVM,使得足够短的函数可以被内联。这仅在 nopython 模式 下有效。

Numba 是否向量化数组计算(SIMD)?

Numba 本身并不实现这些优化,但它允许 LLVM 应用它们。

为什么我的循环没有向量化?

Numba 默认启用 LLVM 中的循环向量化优化。虽然这是一个强大的优化,但并非所有循环都适用。有时,由于内存访问模式等细微细节,循环向量化可能会失败。要查看 LLVM 的额外诊断信息,请添加以下行:

import llvmlite.binding as llvm
llvm.set_option('', '--debug-only=loop-vectorize')

这告诉 LLVM 将 循环向量化 过程的调试信息打印到 stderr。每个函数条目如下所示:

备注

使用 --debug-only 需要 LLVM 在启用断言的情况下构建才能工作。使用 Numba 频道 中的 llvmlite 构建,该构建与启用了断言的 LLVM 链接。

LV: Checking a loop in "<low-level symbol name>" from <function name>
LV: Loop hints: force=? width=0 unroll=0
...
LV: Vectorization is possible but not beneficial.
LV: Interleaving is not beneficial.

每个函数条目之间用空行分隔。拒绝向量化的原因通常在条目的末尾。在上面的例子中,LLVM 拒绝了向量化,因为这样做不会加速循环。在这种情况下,可能是由于内存访问模式。例如,被循环遍历的数组可能不是连续布局的。

当内存访问模式是非平凡的,以至于无法确定访问的内存区域时,LLVM 可能会拒绝并显示以下消息:

LV: Can't vectorize due to memory conflicts

另一个常见的原因是:

LV: Not vectorizing: loop did not meet vectorization requirements.

在这种情况下,矢量化被拒绝,因为矢量化代码的行为可能不同。这是一个尝试开启 fastmath=True 以允许快速数学指令的案例。

为什么从解释器中使用 typed 容器时会更慢?

Numba 的 typed 容器,例如 numba.typed.List,在 numba.typed 中找到,它们以一种高效的形式存储数据,以便从 JIT 编译的代码中访问。当这些容器从 CPython 解释器中使用时,涉及的数据必须从/转换为容器格式。这个过程相对昂贵,因此会影响性能。在 JIT 编译的代码中不存在这样的惩罚,因此对容器的操作要快得多,通常比纯 Python 的等效操作更快。

Numba 会自动并行化代码吗?

在某些情况下,它可以:

  • 使用 target="parallel" 选项的 Ufuncs 和 gufuncs 将在多个线程上运行。

  • parallel=True 选项用于 @jit 将尝试优化数组操作并在并行中运行它们。它还增加了对 prange() 的支持,以显式并行化一个循环。

你也可以手动在多个线程上运行计算,并使用 nogil=True 选项(参见 释放 GIL)。Numba 还可以使用其 CUDA 和 HSA 后端针对 GPU 架构进行并行执行。

Numba 能加速短运行时间的函数吗?

不显著。新用户有时期望即时编译此类函数:

def f(x, y):
    return x + y

并且比Python解释器获得显著的加速。但Numba在这里能改进的地方不多:大部分时间可能花在CPython的函数调用机制上,而不是函数本身。根据经验,如果一个函数执行时间少于10微秒:就让它保持原样。

例外情况是,如果该函数是从另一个即时编译的函数调用的,那么你应该对其进行即时编译。

在JIT编译复杂函数时存在延迟,我该如何改进?

尝试将 cache=True 传递给 @jit 装饰器。它会将编译后的版本保存在磁盘上以供以后使用。

一个更激进的替代方案是 提前编译

GPU 编程

如何解决 CUDA 在分叉前初始化 错误?

在Linux上,Python标准库中的 multiprocessing 模块默认使用 fork 方法来创建新进程。由于进程分叉会在父进程和子进程之间复制状态,如果在分叉 之前 初始化了CUDA运行时,CUDA在子进程中将无法正常工作。Numba检测到这一点并会引发一个 CudaDriverError ,消息为 CUDA initialized before forking

避免此错误的一种方法是使所有对 numba.cuda 函数的调用都在子进程内进行,或在进程池创建之后进行。然而,这并不总是可能的,因为你可能希望在启动进程池之前查询可用GPU的数量。在Python 3中,你可以更改进程启动方法,如 multiprocessing 文档 中所述。从 fork 切换到 spawnforkserver 将避免CUDA初始化问题,尽管子进程不会继承其父进程的任何全局变量。

与其他工具的集成

我可以“冻结”一个使用 Numba 的应用程序吗?

如果你使用 PyInstaller 或类似的工具来冻结应用程序,你可能会遇到 llvmlite 的问题。llvmlite 需要一个非 Python 的 DLL 来工作,但冻结工具不会自动检测到它。你必须告知冻结工具 DLL 的位置:它通常会被命名为 llvmlite/binding/libllvmlite.sollvmlite/binding/llvmlite.dll,取决于你的系统。

在Spyder下运行脚本两次时出现错误

在Spyder控制台下运行脚本时,Spyder首先尝试重新加载现有模块。这对Numba效果不佳,并可能产生诸如 TypeError: No matching definition for argument type(s) 的错误。

在 Spyder 偏好设置中有一个修复。打开“偏好设置”窗口,选择“控制台”,然后是“高级设置”,点击“设置 UMR 排除模块”按钮,并在弹出的文本框中添加 numba

要使设置生效,请务必重启 IPython 控制台或内核。

为什么Numba会抱怨当前的区域设置?

如果你收到如下错误信息:

RuntimeError: Failed at nopython (nopython mode backend)
LLVM will produce incorrect floating-point code in the current locale

这意味着你遇到了一个 LLVM 错误,该错误导致浮点常量的处理不正确。已知在使用某些第三方库(如 matplotlib 的 Qt 后端)时会发生这种情况。

要解决这个错误,你需要将区域设置强制恢复为其默认值,例如:

import locale
locale.setlocale(locale.LC_NUMERIC, 'C')

我如何获取 Numba 开发版本?

Numba 的预发布版本可以通过 conda 安装:

$ conda install -c numba/label/dev numba

杂项

项目名称“Numba”从何而来?

“Numba” 是 “NumPy” 和 “Mamba” 的结合。Mambas 是世界上最快的蛇之一,而 Numba 使你的 Python 代码快速。

我如何在其他作品中引用/引用/致谢 Numba?

对于学术用途,最佳选择是引用我们的ACM会议论文:Numba: 一个基于LLVM的Python JIT编译器。 你也可以在github上找到`源代码 <https://github.com/numba/Numba-SC15-Paper>`_,包括`预印本pdf <https://github.com/numba/Numba-SC15-Paper/raw/master/numba_sc15.pdf>`_,以防你无法访问ACM网站但仍希望阅读该论文。

如何为一个Numba问题编写一个最小的工作重现器?

一个用于 Numba 的最小工作复现应包括:

  1. 重现问题的函数源代码。

  2. 一些示例数据以及使用该数据调用重现代码的演示。由于Numba基于类型信息进行编译,除非你的问题是数值型的,否则只需提供正确类型的虚拟数据即可,例如使用 numpy.ones 并指定正确的 dtype/大小/形状来创建数组。

  3. 理想情况下,将1.和2.放入一个包含所有正确导入的脚本中。在提交之前,确保您的脚本实际执行并重现问题!目标是使脚本可以直接从 issue tracker 复制并由其他人运行,以便他们可以看到您遇到的问题。

制作了一个复现器后,现在移除代码中所有不直接有助于复现问题的部分,以创建一个“最小”复现器。这意味着移除未使用的导入,移除未使用或无影响的变量,移除无影响的代码行,简化表达式的复杂性,并将输入数据缩减到触发问题所需的最小量。

完成上述操作确实有助于 Numba 问题分类流程,并将使您的问题得到更快的响应!

建议进一步阅读 关于编写最小可复现工作示例的内容。