try ai
科普
编辑
分享
反馈
  • 大规模科学计算:原理与应用

大规模科学计算:原理与应用

SciencePedia玻尔百科
核心要点
  • 有效的并行计算偏好数据并行,它通过划分问题空间并促使处理器之间进行局部的“邻里交谈”,从而最小化全局通信。
  • 性能通常由延迟(启动通信的成本)和内存带宽(数据传输的速率)主导,而不仅仅是原始计算速度。
  • Roofline 模型量化了算法是计算密集型还是内存密集型,从而指导算法的重新设计以最大化硬件利用率。
  • 设计高效算法需要与硬件深度协同,这要求采用确保数据局部性、将通信与计算重叠以及使用适当数据结构等策略。
  • 相同的核心计算原理,如利用稀疏性和对称性,普遍适用于从工程学到量子化学等不同领域。

引言

现代科学与工程由巨大规模的问题驱动,从模拟气候到在原子层面发现新材料。应对这些宏大挑战所需的计算能力远远超出了任何单台计算机的能力。解决方案在于大规模科学计算,它利用成千上万甚至数百万个处理器协同工作的集体力量。然而,仅仅增加处理器数量是不够的;如果缺乏对底层原理的深刻理解,性能可能会停滞不前甚至下降。真正的挑战在于有效地指挥这个庞大的计算交响乐团。本文通过探索使高性能计算成为可能的基础概念,来弥合这一知识鸿沟。

本文将引导您了解大规模计算的基本原理和实际应用。首先,在“原理与机制”一章中,我们将剖析划分计算工作、管理处理器间通信以及针对现代硬件复杂内存层次结构进行优化的核心策略。您将了解到为什么等待数据往往比计算本身是更大的瓶颈。随后,“应用与跨学科联系”一章将展示这些抽象原理如何巧妙地应用于广泛的学科领域,将超级计算机转变为物理学、工程学、金融学和化学等领域强大的发现工具。

原理与机制

想象一下,您的任务是创建一个完美的高分辨率天气模拟,或设计下一代救生药物,或模拟星系碰撞。这些问题的规模之大令人咋舌,涉及数以万亿计的计算。任何单一计算机,无论多么强大,都无法在人的一生中完成这样的任务。唯一的出路是利用成千上万甚至数百万个处理器协同工作的力量——即一台超级计算机。

但是,如何指挥这样一个庞大的硅基交响乐团呢?简单地将更多处理器投入问题并不会奇迹般地使其更快。事实上,如果缺乏对并行计算原理的深刻理解,增加更多处理器反而会减慢速度。大规模科学计算的艺术在于协调这支庞大的工作队伍,这是一场计算、通信和内存之间的精妙舞蹈。在本章中,我们将揭示使这场舞蹈成为可能的基本原理和机制。

大分工:各司其职,还是分而治之?

我们必须回答的第一个、最基本的问题是如何划分工作。假设我们的工作是通过应用一系列不同的数学滤波器来处理一幅大型数字图像——这是从医学成像到卫星侦察等各种领域的常见任务。比如说,我们有一百个处理器和十个滤波器。我们该如何分配工作?

有两种基本理念。第一种是​​任务并行​​(task parallelism)。我们可以将十个滤波器中的每一个分配给一个由十个处理器组成的不同小组。每个小组负责将其单一滤波器应用于整个图像。一旦每个小组完成其任务,他们就将其结果组合起来,生成最终处理后的图像。这听起来很合理。但这里有个问题。为了让每个处理器都能处理整个图像,必须首先将整个图像发送给每个处理器。这是一个​​广播​​(broadcast)操作。然后,在每个小组计算出其部分结果后,这些结果必须被全部收集并加总在一起。这是一个​​规约​​(reduction)操作。这些都是全局操作,类似于一个大型市政会议,会上一个人对所有人讲话,然后每个人都向一个中央权威汇报。这类会议是出了名的耗时,尤其是当参与者数量增加时。

第二种理念是​​数据并行​​(data parallelism)。我们不是将任务分配给处理器,而是将数据块分配给它们。我们可以将图像切成一个 10×1010 \times 1010×10 的补丁网格,就像棋盘一样,并将一个补丁分配给我们的一百个处理器中的每一个。现在,每个处理器负责应用所有十个滤波器,但只应用于其图像的小补丁。这种方法的美妙之处在于物理现实的本质,这种本质通常编码在我们的科学问题中。要计算一个像素点的结果,通常只需要知道它的直接邻居。这意味着处理其补丁的处理器只需要与处理相邻补丁的处理器通信。我们用一系列安静的“邻里交谈”取代了全局市政会议,在这些交谈中,处理器交换一层薄薄的数据边界,通常称为​​光环​​(halo)或幽灵区(ghost zone)。

对于科学和工程中的许多问题——流体动力学、结构力学、电磁学——其中物理是局部的,数据并行效率要高得多。它用更廉价的局部通信取代了昂贵的全局同步。权衡是显而易见的:任务并行涉及“收集-计算-分发”模式,数据传输量大,但逻辑可能更简单;而数据并行涉及“计算并交谈”模式,局部消息更小、更频繁。对于几乎总是涉及模拟物理空间的科学宏大挑战而言,学会有效划分该空间是第一步,也是最关键的一步。

绘制地图:通信的美丽几何学

所以,我们决定分割我们的世界并分发这些碎片。但我们该如何划定边界呢?如果做得不好,我们制造的问题可能比解决的还多。想象一下,我们正在模拟飞机机翼上的气流,该气流由数百万个小三角形或六面体组成的复杂网格表示。我们想在数千个处理器之间划分这个网格。

什么是一个“好”的划分?有两点,它们之间常常存在矛盾:

  1. ​​负载均衡​​(Load Balance):每个处理器应该有大致相同的工作量。如果一个处理器拥有的网格元素是另一个的两倍,那么所有人都必须等待那个慢的处理器完成,我们昂贵的超级计算机大部分时间将处于空闲状态。
  2. ​​通信最小化​​(Communication Minimization):我们在分区之间制造的“切口”总长度应尽可能小。我们切割的网格的每条边都成为一个通信通道。更多的切口意味着在“光环”中需要交换更多的数据,处理器之间需要进行更多的交谈。

这时,一个优美而深刻的数学思想向我们伸出了援手:​​等周原理​​(isoperimetric principle)。对于给定的体积,什么形状的表面积最小?球体。这个解释了肥皂泡为什么是圆形的原理,在并行计算中有一个直接的类比。分区的“体积”是它包含的计算元素的数量(工作量)。“表面积”是它与其他分区边界的大小(通信量)。为了以最少的通信获得最多的计算,我们需要我们的分区尽可能紧凑和“球形”。长而细、高纵横比的分区是糟糕的;它们的体积对应的表面积巨大,这意味着它们大部分时间都在通信而不是计算。

像​​多级分区器​​(multilevel partitioners)这样的算法(以流行的软件 METIS 为例)是计算机科学的杰作,旨在解决这个问题。它们可以接受一个有数十亿顶点的图,并在接近线性的时间内,将其切割成数千个均衡、紧凑且边切割数极小的分区。它们通过巧妙地粗化图,对这个微小、简单的版本进行分区,然后在逐级解粗化的过程中细化分区来实现这一点。这个智能地绘制我们计算世界地图的过程是大规模模拟中一个隐藏但绝对必要的支柱。

一旦我们有了分区,就需要标记系统中的所有未知数。一个简单的标记可能会将单个分区的数据散布在计算机内存的各处。一个更好的方法是使用​​保持局部性的排序​​(locality-preserving ordering),例如基于像 Hilbert 曲线这样的​​空间填充曲线​​(space-filling curve)的排序。这些引人入胜的数学对象在多维空间中描绘出一条路径,访问每个点,使得空间上接近的点在曲线上也接近。通过根据这条曲线对我们的数据进行排序,我们确保了物理局部性转化为内存局部性,正如我们将看到的,这对于性能至关重要。

邮差的暴政:为什么等待是最难的部分

我们已经确定要最小化通信。但并非所有通信都是生而平等的。发送一条消息所需时间的简单模型是 T=α+βmT = \alpha + \beta mT=α+βm,其中 mmm 是消息的大小。βm\beta mβm 项代表​​带宽​​(bandwidth)成本——实际传输数据所需的时间,就像大声朗读一封长信所需的时间。然而,α\alphaα 项是​​延迟​​(latency)——任何通信的固定启动成本,无论消息多小。这就像找信封、写地址、然后走到邮局所花的时间,即使你只发送一个词。

在一台巨大的超级计算机上,延迟是一个暴君。一个需要所有处理器同步并达成一致的单一操作可能会使整个机器停顿,因为信号必须在网络中传播,每个人都必须等待。带宽关乎数据量;延迟关乎同步频率。

这一点在求解大型线性方程组 Ax=bAx=bAx=b 的问题中得到了完美体现,这是无数科学计算代码的核心任务。一个标准方法是 LU 分解。为了确保过程的数值稳定性,必须执行“主元选择”(pivoting)——重新排列行和列。​​完全主元选择​​(Full pivoting)通过在每一步搜索整个剩余矩阵以找到最大的元素作为下一个主元,提供了最佳的数值稳定性。这听起来很棒,但在并行环境中,这是一场灾难。这种全局搜索要求在算法的每一步都与所有数千个处理器进行“电话会议”。这种重复全局同步的延迟成本是如此巨大,以至于完全削弱了性能。因此,几乎所有高性能库都使用​​部分主元选择​​(partial pivoting),它只搜索当前列。它在数学上不那么稳定,但在并行硬件的现实世界中要优越得多,因为它用一个成本低得多的局部搜索取代了全局搜索。

这是一个反复出现的主题。纸面上最好的算法在实践中并不总是最好的。有时,我们甚至会重新设计算法,使其在数学上“更差”,如果这能改善它们的并行特性。​​限制性加性 Schwarz (RAS)​​ 方法就是一个典型的例子。它是经典加性 Schwarz 方法求解偏微分方程的一个修改,故意打破了问题的数学对称性。为什么?为了消除每次迭代所需的两个通信步骤中的一个。在一台高延迟的机器上,这种同步的减少可以带来巨大的加速,即使新的、非对称的方法需要更多的迭代才能收敛。教训很明确:在大规模计算中,避免等待往往比最小化原始工作量更重要。

处理器的内心世界:对数据的无尽渴求

到目前为止,我们一直关注处理器之间的世界。但是单个处理器内部发生了什么?现代 CPU 是一个拥有巨大计算能力的引擎,每秒能够执行数万亿次浮点运算(FLOPS)。但它只能对存在于其最快内部寄存器中的数据执行这些操作。处理器不断地从内存中获取数据。这就引出了一个关键问题:处理器的性能是受其进行计算的能力限制,还是受其从内存获取数据的速度限制?

这就是​​计算密集型​​(compute-bound)和​​内存带宽密集型​​(memory-bandwidth-bound)之间的区别。想象一位能以闪电般速度切菜的大厨。如果她的助手不能足够快地将蔬菜送到砧板上,大厨的技巧就被浪费了;她大部分时间都在等待。厨房的产出受限于助手的速度(内存带宽)。然而,如果食谱极其复杂,每种蔬菜都需要许多精细的切法,大厨就会一直很忙,而助手则会等着她完成。产出受限于大厨的速度(计算能力)。

我们可以用 ​​Roofline 模型​​ 使这一点变得异常精确。一台给定的计算机有一个峰值计算速率 Π\PiΠ(屋顶的“平坦”部分)和一个峰值内存带宽 β\betaβ。这两者的比率 B=Π/βB = \Pi / \betaB=Π/β 是​​机器平衡​​(machine balance)。它告诉我们,为了让处理器保持忙碌,机器每从内存移动一个字节的数据必须执行多少次浮点运算。反过来,一个算法有一个​​计算强度​​(arithmetic intensity)I\mathcal{I}I,即它执行的浮点运算次数与移动的字节数之比。

  • 如果 I<B\mathcal{I} \lt BI<B,算法是​​内存带宽密集型​​的。其性能受限于 βI\beta \mathcal{I}βI。它在“渴望”数据。
  • 如果 I>B\mathcal{I} \gt BI>B,算法是​​计算密集型​​的。其性能受限于 Π\PiΠ。它已“饱和”工作。

我们来看一个标准操作,稀疏矩阵向量乘法(SpMV),它在许多迭代求解器(如共轭梯度法,CG)中占据了主要成本。对于矩阵中的每个非零项,我们进行两次运算(一次乘法和一次加法)。但要做到这一点,我们必须读取矩阵值、其列索引以及输入向量的相应条目。计算强度被发现低得惊人,通常小于 0.10.10.1 FLOPs/byte。在一台现代机器上,平衡 BBB 可能为 555 或 101010,这个算法是严重的内存带宽密集型。我们的超级计算机几乎所有时间都在等待数据!

这一认识推动了算法设计的一场革命。我们如何提高计算强度?一个绝妙的策略是​​无矩阵​​(matrix-free)方法,尤其在像间断伽辽金法(Discontinuous Galerkin, DG)这样的高阶方法中很受欢迎。我们不是预先组装和存储一个巨大的稀疏矩阵(这需要巨大的内存流量来读取),而是在需要时,利用其潜在的数学结构,即时重新计算必要的矩阵项。这涉及更多的计算,但极大地减少了内存流量。通过用廉价的浮点运算换取昂贵的内存访问,我们可以将计算强度提高到足以使算法跨越机器平衡阈值,并成为计算密集型,最终释放处理器的真正潜力。

吾心安处是吾乡:局部性的力量

内存系统不是一个单一的、庞大的实体。它是一个由逐渐增大、变慢、变便宜的存储层次组成的体系:处理器内部微小、超快的寄存器;小型、快速的缓存(Level 1、Level 2、Level 3);最后是大型、缓慢的主内存(DRAM)。一次对 L1 缓存的访问可能只需要一个周期,而一次对主内存的访问可能需要数百个周期。性能的关键是​​数据局部性​​(data locality):将你当前正在处理的数据保留在尽可能快的内存级别中。

这意味着你访问数据的顺序至关重要。想象一个用于序列比对的动态规划算法,其中网格中单元格 (i,j)(i,j)(i,j) 的结果取决于其邻居。假设数据在内存中是按行存储的(​​行主序布局​​)。如果你的算法按行遍历网格,它将顺序地流过内存。这种单位步长访问模式是完美的。硬件​​预取器​​(prefetcher)可以预测你接下来需要什么,并在你请求之前就将其取入缓存。然而,如果你沿着反对角线遍历网格,你将在不同行之间跳跃,不断地错过缓存,并迫使缓慢地访问主内存。仅仅改变循环顺序就可以带来数量级的加速,而无需改变任何一个浮点运算。算法的设计必须与其运行的硬件相协调;它必须尊重数据在内存中的布局方式。

驯服混乱:移动的问题和拥挤的工作空间

到目前为止,我们的讨论主要假设一个静态的世界。但是如果问题本身是动态的呢?考虑一个刚体在流体中移动的模拟。计算成本高昂的“切割单元”(cut cells)——被物体边界相交的网格单元——不是固定的。它们随物体移动。如果我们使用静态的域分解,那些恰好包含物体的少数处理器将严重过载,而其他所有处理器几乎处于空闲状态。模拟将陷入停顿。

解决方案是​​动态负载均衡​​(dynamic load balancing)。模拟必须定期暂停,评估当前的工作负载分布,并重新划分域,在处理器之间迁移数据以均衡负载。这是一个复杂且昂贵的操作,因此必须明智地进行——只有当不平衡变得足够严重,以至于值得重新映射世界的成本时才进行。

最后,让我们放大到最细粒度的并行级别:在单个共享内存环境中协同工作的多个线程,就像单个 CPU 上的核心一样。当多个线程试图同时更新同一个内存位置时——例如,当多个线程将有限元贡献组装到全局矩阵中时——我们可能会遇到​​数据竞争​​(data race)。一个线程的更新可能会覆盖另一个线程的更新,导致结果不正确。

我们如何在一个拥挤的工作空间中管理这种混乱?

  • ​​锁 (Locks)​​:我们可以为每个共享数据(例如,网格中的每个节点)关联一个锁。线程在更新数据之前必须获取锁,以确保独占访问。这就像给浴室门上锁一样。这是安全的,但如果竞争激烈,可能会很慢。我们还必须小心避免死锁,例如,通过始终以一致的全局顺序获取锁。
  • ​​原子操作 (Atomics)​​:现代硬件提供原子操作,保证读-改-写周期是不可分割的。它们比软件锁快得多。然而,由于浮点加法不满足结合律((a+b)+c≠a+(b+c)(a+b)+c \ne a+(b+c)(a+b)+c=a+(b+c)),线程执行其原子更新的非确定性顺序意味着最终结果在每次运行时不会是比特级可复现的。
  • ​​着色 (Coloring)​​:一个更优雅的组合方法是图着色。如果我们构建一个图,其中共享一个节点的元素是相连的,我们可以对这个图进行着色,使得没有两个相邻的元素有相同的颜色。然后,我们可以并行处理所有相同颜色类别的元素,无竞争,无需任何锁或原子操作,因为我们知道它们之间没有冲突。然后我们再处理下一种颜色,以此类推。
  • ​​复制 (Replication)​​:最简单的方法通常是完全避免共享。每个线程可以将其贡献组装到最终矩阵的私有副本中。一旦所有线程完成,一个最终的并行规约步骤将所有私有副本相加。这完全没有竞争,但代价是使用更多的内存。

这些策略之间的选择涉及性能、正确性、确定性和内存开销之间的复杂权衡。它们代表了大型计算宏大交响乐中最后一层的控制,确保即使在最拥挤的工作空间中,秩序也能占上风,最终的结果是和谐而不是混乱。

应用与跨学科联系

我们花了一些时间来理解大规模计算的基本原理——可以说是游戏规则。我们讨论了如何在众多处理器之间划分问题、内存层次结构的关键重要性以及最小化通信的必要性。但学习规则是一回事,玩游戏是另一回事。真正的乐趣,真正的美,来自于看到这些原理如何被应用——它们如何成为一种新的科学仪器,一个名副其实的心灵显微镜和望远镜,让我们探索以前无法触及的世界。

现在,我们将踏上探索这些世界的旅程。我们将看到并行计算的抽象思想如何为横跨惊人广泛学科的模拟注入生命,从巨型结构的工程设计到量子粒子的微妙舞蹈。您会发现,同样深刻的思想在令人惊讶的地方重现,这证明了计算科学的统一力量。

作为方程组的世界

自然界中的许多现象,一旦剥离其复杂性直达核心,就可以用方程来描述。热量在材料中的流动、桥梁在风中的振动、设备中的电磁场——所有这些都由微分方程控制。为了在计算机上求解这些方程,我们采用了一个非常有效的技巧:我们用一个精细的点网格代替连续的世界,并将微分方程重写为一个巨大的线性方程组,形式为 Ax=bA\boldsymbol{x} = \boldsymbol{b}Ax=b。矩阵 AAA 代表系统的物理属性——桥梁钢梁的刚度,或材料的热导率——而向量 b\boldsymbol{b}b 代表外部力或源,向量 x\boldsymbol{x}x 是我们寻求的答案,即桥梁的位移或每个点的温度。

对于一个大型系统,这个矩阵 AAA 可能有数十亿个条目。暴力攻击是毫无希望的。艺术在于利用矩阵的结构。在许多工程问题中,比如分析桥梁对不同载荷的响应,矩阵 AAA 是固定的,但载荷向量 b\boldsymbol{b}b 会改变。一个非常有效的策略是通过将其分解为更简单的组件来“预消化”矩阵 AAA,这个过程称为 PA=LUPA=LUPA=LU 分解。这个分解是昂贵的部分。但一旦完成,求解任何新的载荷 b\boldsymbol{b}b 都快得惊人。我们不是从头开始重新求解一个十亿乘十亿的系统,而是执行两个简单的步骤,称为前向和后向替换。这使得工程师能够在完全求解少数几个案例所需的时间内,测试一个结构对抗数千种不同风型或着陆场景,将一个计算苦差事变成一个交互式设计工具。

当我们模拟热传导时,也出现了同样的“离散化并求解”范式。在这里,挑战通常不仅仅是求解一个案例,而是在拥有数千个处理器的大型并行机器上求解。我们如何让它们合作?关键在于精心的组织。我们必须明确告诉计算机如何表示矩阵 AAA。由于其大多数条目为零(这一特性称为“稀疏性”),我们只使用像压缩稀疏行(CSR)这样的巧妙格式来存储非零值。我们必须对问题进行分区,为每个处理器分配一组特定的矩阵行来“拥有”。而且,最美妙的是,我们必须告知求解器库(如 PETSc 或 Trilinos)我们问题的物理对称性。如果我们的基本方程是对称的,那么得到的矩阵 AAA 也将是对称的。通过提供这个简单的元数据,我们允许求解器使用更高效的算法,比如专为这类问题量身定制的共轭梯度法。性能提升不仅仅是百分之几,而是可能达到几个数量级。这是一个完美的例证,说明了一点物理洞察力,转化为机器的语言,可以产生巨大的计算红利。

模拟粒子与概率之舞

并非每个问题都最适合用静态网格来描述。许多系统更自然地被看作是运动中相互作用的粒子集合。在这里,我们的计算工具变成了一个虚拟舞台,我们在上面指挥着原子、光子甚至抽象智能体的宏大芭蕾。

考虑蛋白质折叠或病毒自组装的挑战。病毒衣壳是自然工程的奇迹,它是一个由许多蛋白质亚基组成的壳,能够自发地拼接在一起。这是如何发生的?为了观察它,我们可以使用分子动力学(MD)模拟。但我们面临一个基本的尺度选择。一个“全原子”模拟就像一个高清慢动作视频:我们模拟每一个原子及其化学键的飞秒级振动。它为我们提供了精美的细节,但因为时间步长太小,我们只能模拟几纳秒的现实——这远不足以看到整个病毒的组装过程,这个过程需要毫秒或更长时间。

另一种选择是“粗粒化”。我们将原子组合成更大的珠子——整个氨基酸可能变成一个粒子。通过平滑掉高频抖动,我们可以采用更大的时间步长。现在,我们的模拟就像一部延时电影。我们失去了精细的原子细节,但我们获得了观察在微秒或毫秒尺度上展开的宏大、缓慢的组装之舞的能力。这种*多尺度建模*的概念是现代计算科学的基石。我们甚至可以结合这两种方法:使用粗粒度模拟快速找到一个有趣的、部分组装的状态,然后通过将该结构转换回全原子表示来“放大”,以研究维持其结构的特定化学相互作用。我们根据我们提出的问题来选择我们的镜头。

有时,“粒子”不是原子,而是能量或信息的抽象载体。在辐射传热的蒙特卡洛模拟中,我们追踪光子在材料中的随机行走。在现代图形处理单元(GPU)上,我们可以释放数百万个线程,每个线程模拟一个光子历史。挑战是双重的。首先,你如何确保这数百万个线程中的每一个都得到一个真正独立且可复现的随机数序列?一个共享的生成器会造成瓶颈。优雅的解决方案是“基于计数器”的随机数生成器,它可以从唯一的光子 ID 和一个步数计数器确定性地生成一个随机数,无需任何共享状态。其次,你如何管理内存?当数百万个线程需要访问其光子的位置和方向时,该数据的布局至关重要。将其存储为“数组结构”(Structure of Arrays, SoA)——所有 x 坐标在一起,所有 y 坐标在一起,依此类推——确保当一组线程(一个“warp”)请求数据时,可以从内存中以一次完美的合并事务获取。这种算法与硬件架构之间的深度协同作用,正是释放现代计算惊人性能的关键。

同样的集体行为思想可以应用于更令人惊讶的领域。“临界大脑”假说提出,大脑的神经元网络在相变点附近运行,就像即将沸腾的水。一个信号可以触发一连串的神经元放电,即“神经雪崩”。通过模拟这一过程的模型,我们可以测量雪崩大小的概率分布。值得注意的是,来自此类模拟的数据遵循“有限尺寸标度”定律,这是一个直接源自磁体和流体统计物理学的概念。用于理解物理系统中普适临界指数的相同数学框架,为我们提供了一种强大的语言来解释我们大脑模型生成的复杂数据。计算机让我们看到了这种类比,而临界现象理论则提供了洞察力。

发现的引擎:优化与抽象

除了直接模拟物理定律,计算还是优化和管理抽象系统的不可或缺的工具。在这里,挑战通常是在一个充满可能性的宇宙中找到唯一的最佳解决方案,或者高效地处理大量结构化信息。

考虑一下计算金融的世界。一家金融机构可能持有一个包含数千种奇异期权的投资组合,它需要通过模拟数万种可能的未来市场情景来评估其风险。这会生成一个巨大的收益矩阵 PPP,其中条目 PisP_{is}Pis​ 是期权 iii 在情景 sss 中的收益。一个关键特性是这个矩阵是稀疏的;在任何给定的情景中,大多数期权将无价值地到期。任务是计算每个情景的总投资组合价值。一个简单的矩阵向量乘法将慢得灾难性。关键是要认识到,所需的计算需要高效地访问矩阵 PPP 的列。这立即告诉我们存储数据的最佳方式:压缩稀疏列(CSC)格式。这个选择不是一个随意的计算机科学细节;它是由金融问题本身的结构所决定的自然表示。

将算法与架构相结合的同样原则在优化问题中也至关重要,例如运筹学中单纯形法解决的那些问题。在 GPU 上加速这些求解器并非简单地移植代码。必须做出一系列复杂的选择。我们是显式计算一个数值不稳定的矩阵逆,还是使用一个稳定的分解?我们是在 CPU 和 GPU 之间穿梭小块数据,招致巨大的延迟惩罚,还是我们构建计算以使数据驻留在设备上?答案在于使用最先进的技术:在 GPU 上维护稳定的 LU 分解,并将操作表述为 Level-3 BLAS(矩阵-矩阵操作)以实现高计算强度,从而最大化硬件的潜力。

在物理与化学的前沿

也许没有什么地方比在基础科学的前沿更能体现大规模计算的力量了,那里是量子力学复杂性统治的领域。

在量子化学中,主要的障碍是电子排斥积分(ERI),一个四指标量 (μν∣λσ)(\mu\nu|\lambda\sigma)(μν∣λσ),描述了电子对之间的相互作用。对于一个中等大小的分子,这些积分的数量是天文数字。然而,这个张量也是稀疏的,并具有优美的 8 重置换对称性。挑战在于设计一个数据结构,该结构只存储每个唯一的积分一次,但又能为理论所需的类矩阵变换提供快速访问。一个绝妙的解决方案是将唯一的数值存储在一个地方,并构建两套索引——一套类似于 CSR 格式,用于沿第一对索引访问;另一套类似于 CSC 格式,用于沿第二对索引访问——两者都指向相同的数据。这种双索引方案是数据结构设计的杰作,源于基础物理学的深刻对称性。

对于更复杂的量子系统,会使用像密度矩阵重整化群(DMRG)这样的方法。DMRG 的计算核心涉及重复求解一个局部特征值问题。一个标准的、顺序的方法会按部就班地执行其步骤:(1)在 GPU 上进行繁重的张量收缩,(2)在 CPU 上进行局部计算,以及(3)一个跨所有处理器的全局通信步骤以计算内积。这个通信步骤通常是瓶颈;整个机器都在空闲,等待一条消息穿过网络。一个远为优雅的解决方案是使用“流水线”算法。通过对方法进行代数重构,我们可以启动一次迭代的缓慢通信步骤,并在其在后台进行时,立即开始下一次迭代的计算密集型 GPU 工作。通信延迟被有效地隐藏在有用的计算背后。这种重叠通信与计算的思想是实现最大规模性能最深刻和最基本的策略之一。

一种通用语言

在我们结束这次旅程时,一个显著的模式浮现出来。我们在大规模科学计算中面临的挑战,无论是在建造桥梁、折叠蛋白质、评估投资组合,还是求解薛定谔方程,都存在着深刻的联系。我们始终关注于管理复杂性。我们通过寻找正确的抽象,利用稀疏性和对称性,设计尊重问题数学结构的算法,以及协调计算、内存访问和通信之间的精妙舞蹈来实现这一点。

这些原理形成了一种通用语言。正是这种语言,使我们能够将自然世界和抽象世界的深层结构转化为机器可以理解的形式,并在此过程中,创造出赋予我们前所未有的探索、预测和发现能力的工具。