diff --git a/.translate/state/need_for_speed.md.yml b/.translate/state/need_for_speed.md.yml index 2605926..c9970c5 100644 --- a/.translate/state/need_for_speed.md.yml +++ b/.translate/state/need_for_speed.md.yml @@ -1,6 +1,6 @@ -source-sha: cc9c3256dc35bd277cb25d0089f0a0452c0fa94e -synced-at: "2026-03-20" +source-sha: 21f1ea0669031ccc0ee0194878439a87de5d248d +synced-at: "2026-04-10" model: claude-sonnet-4-6 -mode: NEW +mode: UPDATE section-count: 5 -tool-version: 0.13.1 +tool-version: 0.14.1 diff --git a/.translate/state/numba.md.yml b/.translate/state/numba.md.yml index 3f6b24f..d5a9787 100644 --- a/.translate/state/numba.md.yml +++ b/.translate/state/numba.md.yml @@ -1,6 +1,6 @@ -source-sha: ab53ebc3d56705b836f0ca8207d70d1ff3ba0936 -synced-at: "2026-04-08" +source-sha: 21f1ea0669031ccc0ee0194878439a87de5d248d +synced-at: "2026-04-10" model: claude-sonnet-4-6 mode: UPDATE section-count: 7 -tool-version: 0.13.1 +tool-version: 0.14.1 diff --git a/lectures/need_for_speed.md b/lectures/need_for_speed.md index 9c97f8b..99186c9 100644 --- a/lectures/need_for_speed.md +++ b/lectures/need_for_speed.md @@ -58,7 +58,7 @@ translation: ## 概述 -可以说,Python 是科学计算领域最流行的编程语言。 +Python 是科学计算许多方面最流行的编程语言。 这得益于以下几点: @@ -102,35 +102,36 @@ mpl.rcParams['font.family'] = ['Source Han Serif SC'] # i18n import random ``` - ## 主要科学库 让我们简要回顾一下 Python 的科学库。 - ### 为什么需要它们? -我们使用科学库的一个原因是它们实现了我们想要使用的程序。 +我们需要 Python 科学库有两个原因: -* 数值积分、插值、线性代数、求根等。 +1. Python 体量小 +2. Python 速度慢 -例如,使用现有的求根程序通常比从头编写一个新程序更好。 +**Python 体量小** -(对于标准算法,如果社区能够统一使用一套由专家编写、并经用户调优以尽可能快速和健壮的共同实现,效率将达到最高!) +核心 Python 在设计上体量较小——这有助于优化、易用性和维护。 -但这并不是我们使用 Python 科学库的唯一原因。 +科学库提供了我们不想——也可能不应该——自己编写的程序: -另一个原因是纯 Python 速度不够快。 +* 数值积分、插值、线性代数、求根等。 -因此,我们需要那些专门用于加速 Python 代码执行的库。 +**Python 速度慢** -它们通过两种策略来实现这一目标: +我们需要科学库的另一个原因是纯 Python 的速度相对较慢。 -1. 使用编译器将类 Python 语句转换为针对单一逻辑线程的快速机器码,以及 -2. 在多个"工作单元"(例如 CPU、GPU 内部的各个线程)之间并行化任务。 +科学库通过三种主要策略来加速执行: -我们将在本讲座及本系列剩余讲座中广泛讨论这些思想。 +1. 向量化:提供已编译的机器码以及使该代码可访问的接口 +1. JIT 编译:在运行时将类 Python 语句转换为快速机器码的编译器 +2. 并行化:将任务分配到多个线程 / CPU / GPU / TPU +我们将在下文深入讨论这些思想。 ### Python 的科学生态系统 @@ -152,8 +153,7 @@ import random * Pandas 提供用于操作数据的类型和函数。 * Numba 提供一个即时编译器,与 NumPy 配合良好,有助于加速 Python 代码。 -我们将在本系列讲座中广泛讨论所有这些库。 - +我们将在本系列讲座中详细讨论所有这些库。 ## 纯 Python 速度慢 @@ -163,7 +163,6 @@ import random 对于这个主题,如果我们理解是什么导致了执行速度慢,将会很有帮助。 - ### 高级语言与低级语言 像 Python 这样的高级语言是为人类优化的。 @@ -180,12 +179,10 @@ import random 另一方面,Python 的标准实现(称为 CPython)无法与 C 或 Fortran 等编译语言的速度相媲美。 - ### 瓶颈在哪里? 为什么会这样呢? - #### 动态类型 ```{index} single: Dynamic Typing @@ -216,14 +213,11 @@ a, b = ['foo'], ['bar'] a + b ``` -(我们说运算符 `+` 是*重载的*——它的动作取决于它所作用的对象的类型。) - 因此,在执行 `a + b` 时,Python 必须首先检查对象的类型,然后调用正确的操作。 -这涉及到不可忽视的开销。 - -如果我们在一个紧密的循环中反复执行此表达式,这种不可忽视的开销就会变成巨大的开销。 +这涉及到额外的开销。 +如果我们在一个紧密的循环中反复执行此表达式,这种开销就会变得很大。 #### 静态类型 @@ -258,7 +252,6 @@ int main(void) { 无需类型检查,因此没有额外开销。 - ### 数据访问 高级语言速度慢的另一个原因是数据访问。 @@ -267,22 +260,15 @@ int main(void) { #### 使用编译代码求和 -在 C 或 Fortran 中,这些整数通常存储在数组中,数组是一种用于存储同类数据的简单数据结构。 - -这样的数组存储在单个连续的内存块中: +在 C 或 Fortran 中,整数数组存储在单个连续的内存块中: -* 在现代计算机中,内存地址分配给每个字节(1字节 = 8位)。 * 例如,一个 64 位整数存储在 8 字节的内存中。 * $n$ 个这样的整数组成的数组占据 $8n$ 个*连续*的内存槽。 -此外,编译器通过程序员的声明得知数据类型。 - -* 在本例中为 64 位整数。 +此外,数据类型在编译时是已知的。 因此,每个连续的数据点都可以通过在内存空间中向前移动一个已知且固定的量来访问。 -* 在本例中为 8 字节。 - #### 在纯 Python 中求和 Python 在一定程度上试图复制这些思想。 @@ -293,11 +279,7 @@ Python 在一定程度上试图复制这些思想。 因此,访问数据值本身仍然存在开销。 -这对速度是一个相当大的拖累。 - -事实上,内存流量通常是导致执行缓慢的主要因素。 - - +这种开销是导致执行缓慢的主要因素。 ### 总结 @@ -315,11 +297,6 @@ Python 在一定程度上试图复制这些思想。 这个任务最好留给专门的编译器! -某些 Python 库在并行化科学代码方面具有出色的能力——我们将在后续内容中进一步讨论。 - - - - ## 加速 Python 在本节中,我们将介绍三种加速 Python 代码的相关技术。 @@ -328,8 +305,6 @@ Python 在一定程度上试图复制这些思想。 稍后我们将研究具体的库以及它们如何实现这些思想。 - - ### {index}`向量化 ` ```{index} single: Python; Vectorization @@ -360,7 +335,7 @@ Python 在一定程度上试图复制这些思想。 ```{figure} /_static/lecture_specific/need_for_speed/matlab.png ``` - +NumPy 使用类似的模型,灵感来自 MATLAB。 ### 向量化 vs 纯 Python 循环 @@ -418,50 +393,39 @@ with qe.Timer(): 在本系列后续讲座中,我们将学习现代 Python 库如何利用即时编译器生成快速、高效、并行化的机器码。 - - - ## 并行化 近年来,CPU 时钟速度(即单条逻辑链的运行速度)的增长已大幅放缓。 芯片设计师和计算机程序员通过寻求一条不同的路径来应对这一放缓:并行化。 -硬件制造商增加了每台机器中嵌入的核心数量(物理 CPU)。 - -对于程序员来说,挑战在于通过同时运行多个进程(即并行)来充分利用这些多个 CPU。 +这涉及到: -这在科学编程中尤为重要,因为科学编程需要处理: +1. 增加每台机器中嵌入的 CPU 数量 +1. 连接 GPU 和 TPU 等硬件加速器 -* 大量数据,以及 -* CPU 密集型的模拟和其他计算。 +对于程序员来说,挑战在于利用这些硬件并行运行多个进程。 下面我们讨论科学计算中的并行化,重点关注: -1. Python 中最佳的并行化工具,以及 +1. Python 中的并行化工具,以及 1. 这些工具如何应用于定量经济学问题。 - ### CPU 上的并行化 让我们回顾一下科学计算中常用的两种主要 CPU 并行化方式,并讨论它们的优缺点。 - #### 多进程 -多进程是指使用多个处理器并发执行多个进程。 - -在这个语境中,**进程**是一系列指令(即一个程序)。 +多进程是指使用多个处理器并发执行多条逻辑线程。 -多进程可以在一台拥有多个 CPU 的机器上进行,也可以在通过网络连接的多台机器上进行。 +多进程可以在一台拥有多个 CPU 的机器上进行,也可以在通过网络连接的机器集群上进行。 -在后一种情况下,这些机器的集合通常称为**集群**。 - -在多进程中,每个进程都有自己的内存空间,尽管物理内存芯片可能是共享的。 +在多进程中,*每个进程都有自己的内存空间*,尽管物理内存芯片可能是共享的。 #### 多线程 -多线程与多进程类似,不同之处在于,在执行期间,所有线程共享同一内存空间。 +多线程与多进程类似,不同之处在于,在执行期间,所有线程 *共享同一内存空间*。 由于一些[遗留的设计特性](https://wiki.python.org/moin/GlobalInterpreterLock),原生 Python 难以实现多线程。 @@ -479,7 +443,6 @@ with qe.Timer(): 对于我们在这些讲座中所做的绝大多数工作,多线程就足够了。 - ### 硬件加速器 虽然拥有多核的 CPU 已成为并行计算的标准,但随着专用硬件加速器的兴起,发生了更为深刻的变化。 @@ -505,7 +468,6 @@ TPU 是较新的发展,由 Google 专门为机器学习工作负载设计。 与 GPU 一样,TPU 擅长并行执行大量矩阵运算。 - #### 为何 TPU/GPU 至关重要 使用硬件加速器带来的性能提升可能是惊人的。 @@ -516,7 +478,6 @@ TPU 是较新的发展,由 Google 专门为机器学习工作负载设计。 这对科学计算尤为重要,因为许多算法天然地映射到 GPU 的并行架构上。 - ### 单 GPU 与 GPU 服务器 访问 GPU 资源有两种常见方式: @@ -531,7 +492,6 @@ TPU 是较新的发展,由 Google 专门为机器学习工作负载设计。 现代 Python 库(如本系列讲座中广泛讨论的 JAX)可以以最少的代码改动自动检测并使用可用的 GPU。 - #### 多 GPU 服务器 对于规模更大的问题,包含多个 GPU(通常每台服务器 4-8 个 GPU)的服务器越来越普遍。 @@ -545,11 +505,10 @@ TPU 是较新的发展,由 Google 专门为机器学习工作负载设计。 这使研究人员能够处理在单个 GPU 或 CPU 上不可行的问题。 - ### 总结 GPU 计算正变得越来越容易获取,尤其是在 Python 中。 一些 Python 科学库(如 JAX)现在支持 GPU 加速,对现有代码的改动极少。 -我们将在后续讲座中更详细地探讨 GPU 计算,并将其应用于一系列经济学应用。 \ No newline at end of file +我们将在后续讲座中更详细地探讨 GPU 计算,并将其应用于一系列经济学应用。 diff --git a/lectures/numba.md b/lectures/numba.md index 65a8ce0..2ca004f 100644 --- a/lectures/numba.md +++ b/lectures/numba.md @@ -15,7 +15,7 @@ translation: Overview: 概述 Compiling Functions: 编译函数 Compiling Functions::An Example: 示例 - Compiling Functions::How and When it Works: 工作原理与适用时机 + Compiling Functions::How and When it Works: 工作原理与适用场景 Decorator Notation: 装饰器语法 Type Inference: 类型推断 Dangers and Limitations: 危险与局限 @@ -62,21 +62,30 @@ mpl.rcParams['font.family'] = ['Source Han Serif SC'] # i18n ## 概述 -在{doc}`之前的讲座 ` 中,我们讨论了向量化,它可以通过将数组处理操作批量发送到高效的底层代码来提高执行速度。 +在 {doc}`之前的讲座 ` 中,我们讨论了向量化,这是一种通过将数组处理操作批量发送到高效底层代码来提高执行速度的方法。 -然而,正如{ref}`之前所讨论的 `,MATLAB、Julia 和 NumPy 等中的传统向量化方案有几个弱点。 +然而,正如 {ref}`在那次讲座中所讨论的 `,传统的向量化方案(例如在 MATLAB、Julia 和 NumPy 中)存在一些弱点。 -例如,它们可能会消耗大量内存,而且对于某些算法,向量化是无效的甚至是不可能的。 +* 对于复合数组操作,内存消耗极大 +* 对某些算法无效甚至无法应用。 -解决这些问题的一种方法是使用 [Numba](https://numba.pydata.org/),这是一个面向数值计算的 Python **即时(JIT)编译器**。 +绕过这些问题的一种方法是使用 [Numba](https://numba.pydata.org/),这是一个面向数值计算的 Python **即时(JIT)编译器**。 Numba 在运行时将函数编译为本地机器码指令。 -编译成功后,Numba 的性能将与底层语言的机器码相当。 +编译成功后,Numba 的性能将与低级语言生成的机器码相当。 -此外,Numba 还可以完成其他有用的技巧,例如{ref}`多线程 ` 或通过 `numba.cuda` 与 GPU 进行交互。 +此外,Numba 还可以完成其他有用的技巧,例如 {ref}`多线程 ` 或通过 `numba.cuda` 与 GPU 进行交互。 -本讲座将介绍其核心思路。 +Numba 的 JIT 编译器在许多方面与 Julia 中的 JIT 编译器类似。 + +主要区别在于它的目标更为保守,只尝试编译语言的一个较小子集。 + +虽然这听起来像是一个缺陷,但在某些方面却是一个优势。 + +Numba 精简、易用,且非常擅长它所做的事情。 + +本讲座将介绍核心思路。 (numba_link)= ## {index}`编译函数 ` @@ -88,15 +97,15 @@ Numba 在运行时将函数编译为本地机器码指令。 (quad_map_eg)= ### 示例 -让我们考虑一个难以向量化的问题:给定初始条件,生成差分方程的轨迹。 +让我们考虑一个难以向量化的问题(即难以交给数组处理操作来完成)。 -我们将差分方程取为二次映射 +该问题涉及通过二次映射生成轨迹 $$ x_{t+1} = \alpha x_t (1 - x_t) $$ -在下文中,我们设定 +在下文中,我们设置 ```{code-cell} ipython3 α = 4.0 @@ -120,7 +129,7 @@ ax.set_ylabel('$x_{t}$', fontsize = 12) plt.show() ``` -要使用 Numba 加速函数 `qm`,我们的第一步是 +要使用 Numba 加速函数 `qm`,第一步是 ```{code-cell} ipython3 from numba import jit @@ -128,9 +137,9 @@ from numba import jit qm_numba = jit(qm) ``` -函数 `qm_numba` 是 `qm` 的一个版本,被"定向"为 JIT 编译。 +函数 `qm_numba` 是 `qm` 的一个版本,它被"定向"用于即时编译(JIT-compilation)。 -我们稍后将解释这意味着什么。 +我们将在稍后解释这意味着什么。 让我们对这两个版本的相同函数调用进行计时和比较,首先从原始函数 `qm` 开始: @@ -166,13 +175,13 @@ time3 = timer3.elapsed time1 / time3 # 计算加速比 ``` -### 工作原理与适用时机 +### 工作原理与适用场景 -Numba 尝试利用 [LLVM Project](https://llvm.org/) 提供的基础设施生成快速机器码。 +Numba 尝试使用 [LLVM Project](https://llvm.org/) 提供的基础设施生成快速的机器代码。 -它通过动态推断类型信息来实现这一点。 +它通过动态推断类型信息来实现这一目标。 -(请参阅我们关于科学计算的 {doc}`早期讲座 ` 以了解类型的相关讨论。) +(有关类型的讨论,请参阅我们 {doc}`早期关于科学计算的讲座 `。) 基本思路如下: @@ -183,13 +192,13 @@ Numba 尝试利用 [LLVM Project](https://llvm.org/) 提供的基础设施生成 * 此外,一旦输入类型已知,`qm` 中*其他变量*的类型*便可以被推断出来*。 * 因此,Numba 和其他 JIT 编译器的策略是等待这一时刻,然后再编译函数。 -这就是为什么它被称为"即时"编译。 +这就是所谓的"即时"编译(just-in-time compilation)。 -请注意,如果你调用 `qm(0.5, 10)` 然后紧接着调用 `qm(0.9, 20)`,编译只在第一次调用时发生。 +请注意,如果你调用 `qm(0.5, 10)` 后紧接着调用 `qm(0.9, 20)`,编译只在第一次调用时发生。 这是因为编译后的代码会被缓存并在需要时重复使用。 -这就是为什么在上面的代码中,`time3` 小于 `time2`。 +这就是为什么在上述代码中,`time3` 小于 `time2`。 ## 装饰器语法 @@ -243,7 +252,7 @@ Numba 也与 NumPy 数组配合良好,因为它们具有明确定义的类型 在理想情况下,Numba 可以推断出所有必要的类型信息。 -这使它能够生成本地机器码,而无需调用 Python 运行时环境。 +这使它能够生成高效的本地机器码,而无需调用 Python 运行时环境。 当 Numba 无法推断所有类型信息时,它将引发错误。 @@ -296,9 +305,7 @@ with qe.Timer(): 正如我们所见,Numba 需要推断所有变量的类型信息以生成快速的机器级指令。 -对于简单的例程,Numba 推断类型非常出色。 - -对于较大的例程,或使用外部库的例程,它很容易失败。 +对于较大的例程或使用外部库的例程,此过程很容易失败。 因此,最好专注于加速代码中小而关键的片段。 @@ -351,9 +358,9 @@ def qm(x0, n): (multithreading)= ## Numba 中的多线程循环 -除了 JIT 编译之外,Numba 还为 CPU 上的并行计算提供了支持。 +除了 JIT 编译之外,Numba 还为 CPU 和 GPU 上的并行计算提供了支持。 -Numba 中并行化的关键工具是 `prange` 函数,它告诉 Numba 在可用的 CPU 核心上并行执行循环迭代。 +Numba 中在 CPU 上进行并行化的关键工具是 `prange` 函数,它告诉 Numba 在可用的核心上并行执行循环迭代。 为了说明,让我们首先看一个简单的单线程(即非并行化)代码片段。 @@ -410,24 +417,10 @@ plt.show() 现在,假设我们有一个庞大的家庭群体,并且想知道中位财富将是多少。 -这个问题很难用纸笔求解,因此我们将使用模拟。 - -具体来说,我们将模拟大量家庭,然后计算该群体的中位财富。 - -假设我们对这一中位数随时间变化的长期平均值感兴趣。 +这个问题很难用纸笔求解,因此我们将使用模拟: -对于我们上面选择的参数规格,我们可以通过截取长时间模拟结束时群体中位财富的一个单期横截面快照来计算这个值。 - -此外,只要模拟期足够长,初始条件就不重要。 - -(这是由于 [遍历性](https://python.quantecon.org/finite_markov.html#id15)。) - -因此,总结来说,我们将通过以下方式模拟 50,000 个家庭: - -1. 任意设定初始财富为 1,以及 -1. 向前模拟 1,000 个时期。 - -然后我们将计算最终时期的中位财富。 +1. 向前模拟大量家庭 +2. 计算中位财富 以下是代码: @@ -481,6 +474,8 @@ with qe.Timer(): 速度提升非常显著。 +注意,我们是跨家庭进行并行化,而非跨时间——单个家庭跨时期的更新本质上是顺序的。 + ## 练习 ```{exercise} @@ -769,6 +764,7 @@ $$ :class: dropdown ``` + 令 $s_t := \ln S_t$,价格动态变为 $$