常见问题
安装
Numba 无法导入
如果你在导入 Numba 时遇到以以下内容开头的错误消息异常
ImportError: Numba could not be imported.
这里有一些常见问题和尝试解决的方法。
你的安装在给定环境中存在多个版本的 Numba。
常见的原因包括
使用 conda 安装 Numba 后又使用 pip 再次安装。
使用 pip 安装 Numba 后又使用 pip 更新到新版本(pip 的重新安装似乎不总是能很好地清理)。
要解决此问题,最好的方法是创建一个全新的环境,并使用你选择的包管理器在该环境中安装单一版本的 Numba。
你的安装拥有适用于 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.A
与X.Y.B
兼容。要解决此问题,最好的方法是创建一个全新的环境,并确保用于安装 Numba 的安装工具是来自该环境的工具,且安装和运行时使用的 Python 版本匹配。
你的核心系统库过旧。
这种情况比较罕见,但有时会使用非常旧(通常已停止支持)的 Linux 版本,它没有足够新版本的
glibc
库符号,Numba 的共享库无法解析。解决此问题的方法是更新你的操作系统系统库/更新你的操作系统。你正在使用集成开发环境(IDE),例如 Spyder。
通过 IDE 安装 Numba 存在一些未知问题,但似乎这些问题可能是第 1 点或第 2 点的变体,并具有相同的建议修复方案。此外,尝试在 IDE 外部使用命令行进行安装。
如果你的安装问题不属于上述任何一种,请在 numba.discourse.group 上提问,如果可能的话,请包含 Numba 的安装路径以及以下命令的输出:
python -c 'import sys; print(sys.executable)'
编程
我可以将函数作为参数传递给 JIT 编译的函数吗?
从 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 将全局变量视为编译时常量。如果你希望 JIT 编译的函数在修改了全局变量的值时自行更新,一个解决方案是使用 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
)。这将传播到所有涉及这些变量的计算中。
性能
Numba 会内联函数吗?
Numba 向 LLVM 提供了足够的信息,以便足够短的函数可以被内联。这仅在nopython 模式下有效。
Numba 会对数组计算进行向量化(SIMD)吗?
Numba 本身不实现此类优化,但它允许 LLVM 应用它们。
为什么我的循环没有向量化?
Numba 默认启用 LLVM 中的循环向量化优化。虽然它是一种强大的优化,但并非所有循环都适用。有时,循环向量化可能会因内存访问模式等微小细节而失败。要查看 LLVM 提供的额外诊断信息,请添加以下行:
import llvmlite.binding as llvm
llvm.set_option('', '--debug-only=loop-vectorize')
这会告诉 LLVM 将 loop-vectorize 阶段的调试信息打印到 stderr。每个函数条目如下所示:
注意
使用 --debug-only
需要 LLVM 在构建时启用断言才能工作。请使用 Numba 频道中与启用断言的 LLVM 链接的 llvmlite 构建。
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
容器,例如 numba.typed.List
,以高效的形式存储数据,以便 JIT 编译的代码访问。当这些容器从 CPython 解释器中使用时,所涉及的数据必须在容器格式之间进行转换。这个过程相对耗时,因此会影响性能。在 JIT 编译的代码中不存在这种开销,因此对容器的操作要快得多,并且通常比纯 Python 等效操作更快。
Numba 会自动并行化代码吗?
在某些情况下可以。
带有
target="parallel"
选项的 Ufuncs 和 gufuncs 将在多个线程上运行。@jit
的parallel=True
选项将尝试优化数组操作并使其并行运行。它还增加了对prange()
的支持,以明确并行化循环。
你也可以自己手动在多个线程上运行计算,并使用 nogil=True
选项(参见释放 GIL)。Numba 还可以使用其 CUDA 和 HSA 后端针对 GPU 架构进行并行执行。
Numba 能加速短时间运行的函数吗?
效果不显著。新用户有时希望 JIT 编译此类函数
def f(x, y):
return x + y
并获得比 Python 解释器显著的加速。但 Numba 在这方面没有太多可以改进的:大部分时间可能都花在 CPython 的函数调用机制上,而不是函数本身。根据经验法则,如果一个函数的执行时间少于 10 微秒:就让它保持原样。
例外情况是,如果该函数是从另一个 JIT 编译的函数中调用的,那么你应该对其进行 JIT 编译。
GPU 编程
如何解决 CUDA initialized before forking
错误?
在 Linux 上,Python 标准库中的 multiprocessing
模块默认使用 fork
方法创建新进程。由于进程分叉在父子进程之间复制状态的方式,如果在分叉之前初始化了 CUDA 运行时,CUDA 将无法在子进程中正常工作。Numba 检测到此情况并引发 CudaDriverError
错误,并显示消息 CUDA initialized before forking
。
避免此错误的一种方法是在子进程内部或进程池创建后,对 numba.cuda
函数的所有调用。然而,这并非总是可行,因为你可能希望在启动进程池之前查询可用 GPU 的数量。在 Python 3 中,你可以更改进程启动方法,如 multiprocessing 文档所述。从 fork
切换到 spawn
或 forkserver
将避免 CUDA 初始化问题,尽管子进程将不会继承其父进程的任何全局变量。
与其他工具的集成
我可以“冻结”使用 Numba 的应用程序吗?
如果你正在使用 PyInstaller 或类似的工具来冻结应用程序,你可能会遇到 llvmlite 的问题。llvmlite 需要一个非 Python 的 DLL 才能工作,但它不会被冻结工具自动检测到。你必须告知冻结工具该 DLL 的位置:它通常名为 llvmlite/binding/libllvmlite.so
或 llvmlite/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 后端。
为了解决这个 bug,你需要强制将语言环境恢复到默认值,例如:
import locale
locale.setlocale(locale.LC_NUMERIC, 'C')
其他
项目名称“Numba”的来源是什么?
“Numba”是“NumPy”和“Mamba”(曼巴蛇)的组合。曼巴蛇是世界上速度最快的蛇类之一,而 Numba 让你的 Python 代码运行得更快。
在其他工作中如何引用/致谢 Numba?
对于学术用途,最佳选择是引用我们的 ACM 会议论文集:Numba: a LLVM-based Python JIT compiler. 你也可以在 GitHub 上找到源代码,包括预印版 PDF,以防你无法访问 ACM 网站但仍想阅读该论文。
如何为 Numba 问题编写最小可复现示例?
Numba 的最小可复现示例应包括:
复现问题的函数源代码。
一些示例数据,以及使用这些数据调用复现代码的演示。由于 Numba 是基于类型信息进行编译的,所以除非你的问题是数值问题,否则提供正确类型的虚拟数据即可,例如,对于数组使用正确
dtype
/大小/形状的numpy.ones
。理想情况下,将第 1 点和第 2 点放入一个包含所有正确导入的脚本中。在提交之前,请确保你的脚本确实能够执行并复现问题!目标是使脚本可以直接从问题跟踪器中复制并由其他人运行,以便他们能看到与你遇到的相同问题。
创建复现示例后,现在删除代码中所有与复现问题不直接相关的部分,以创建一个“最小”复现示例。这意味着删除未使用的导入,删除未使用或不起作用的变量,删除不起作用的代码行,降低表达式的复杂性,并将输入数据缩减到触发问题所需的最小量。
执行上述操作将极大地帮助 Numba 问题分类流程,并能更快地响应你的问题!
关于编写最小可复现示例的建议延伸阅读。