GPU怎么工作?大模型推理为何卡在内存带宽
给大模型工程师看的 GPU 直觉导览:讲清算术运算与数据搬运的差距,以及量化、推测解码和连续批处理为何有用。

为 LLM 工程师提供所需的直觉,无需翻阅硬件手册。
读完后,量化、推测解码和连续批处理等技术就不再像一串需要死记硬背的技巧。
有件事几乎会让每个刚开始部署语言模型的人感到困惑。
你租用了一块顶级数据中心 GPU。规格表称它每秒可以执行近1000 万亿次算术运算。你把一个 700 亿参数的模型加载到上面,开始生成文本。你查看利用率监控,读数很高。一切看起来都很正常。
然后你数一下 token。你用的芯片标称每秒可进行1000 万亿次运算,实际却只得到每秒几十个 token。几乎没有多少算术能力真正派上用场。
没有任何东西坏掉。你的代码没问题,驱动也没问题,买更大的 GPU 几乎帮不上忙。你看到的正是这类硬件最重要的事实;一旦你能清楚地理解它,读过的大多数性能建议就不再像一串技巧,而会变得不言自明。
下面从头建立这种直觉。我们会看看 GPU 为什么采用这样的结构,内存与计算为何始终相互竞争,以及究竟是什么让这些机器变快。
不需要 CUDA。为了说明具体问题,我们会提到几款真实芯片,但这里的内容不依赖于记住它们;而且在所有情况下,比例都远比具体数值本身重要。
👉 前置知识。你只需了解神经网络会进行矩阵乘法,并知道模型权重是存储在内存中的数字。这确实就是全部要求。
开始吧!
一个不对称性解释了几乎一切
先从整个设计赖以成立的那个事实说起。
做算术很便宜。取出要用于算术的数字却很贵。
第一次读到这句话时,你可能会觉得不对。乘法听起来才是难点,移动一个数字似乎微不足道。但在现代硬件上,情况恰恰相反,而且差距不小。
可以把它想象成一间工坊。算术单元就是工作台,成千上万个工作台挤在房间中央。数据放在仓库里,仓库与工坊之间只有一条走廊相连。
工作台消耗材料的速度远远超过走廊运送材料的速度。增加工作台并不会改变什么,因为走廊早已成为瓶颈。人们所说的内存带宽,指的就是这条走廊。
你之后会遇到的每一种性能优化技术,都是为了让每次走过这条走廊时能完成更多工作。

为什么差距会不断扩大
这不是暂时的工程短板,而是长期趋势。
在近几代加速器中,算术能力的增长速度是内存带宽的数倍。每一款新芯片都能完成多得多的数学运算,但能够取来用于这些运算的数据只增加了一些。
因此,每一代芯片都会让这种失衡进一步加剧,而非改善。随着时间推移,减少数据搬运的技术会愈发重要,而原始算术能力越来越无法说明芯片对你的实际性能表现。
为什么 GPU 有成千上万个简单单元,而不是少数几个聪明单元
现在来看看硬件如何应对这种不对称性。
CPU 的设计目标是让一串指令尽快执行完毕。它的大部分芯片面积都用在为此服务的机制上:更大的缓存,让数据预先待在附近;预测硬件,让它猜测接下来会发生什么;重排逻辑,让它等待时仍能继续工作。
所有这些都是为了服务单一工作流,而且都很耗费芯片面积。
GPU 则做了相反的选择,因为它是为一种具有特殊性质的工作负载而设计的。在图形处理中,每个像素都对不同数据运行相同的短程序。在神经网络中,张量的每个元素都接受相同的处理。
当数百万个数据元素执行相同程序时,就不再需要每个元素配一个控制器。一个控制器就能调度数千个算术单元,让它们对不同数值执行同一条指令。
这就是全部诀窍。 GPU 删去了 CPU 搭载的大部分控制机制,把腾出的面积用于算术单元。

从原始数量来看,差距十分明显。一颗高端服务器 CPU 可同时推进几百个线程;在相近的功耗预算下,数据中心 GPU 每个时钟周期可以推进数万个线程。
不过,GPU 线程要弱得多。它不能自行选择方向,单独运行时也不快。它只是这台宽阔而简单的机器中的一条通道。
👉 有个术语值得记住,因为你会到处看到它。线程不是逐个调度的。硬件会以固定的 32 个为一组来处理它们;这一组线程步调一致,共用一条指令,而 32 个线程组成的组称为 Warp(线程束)。这就是全部定义,也是芯片实际工作的基本单位。
设计短板会在哪里暴露
由于一个 Warp 共用一条指令,其中的 32 个线程不能对要做什么意见不一。
如果你的代码根据数据进行分支,而一个 Warp 中部分线程走一条路径、其他线程走另一条路径,硬件就会依次运行两条路径。它会关闭当前不在执行路径上的线程;这些线程只能闲置,什么也做不了。
Warp 执行所需的时间相当于两条分支的时间之和。
只有 Warp 内部出现意见分歧时才会产生这种开销。不同 Warp 走不同路径则没有代价。实际而言,这意味着应避免在紧密的内层循环中进行数据相关分支;而当你调用库操作、不是自己编写代码时,通常很少会碰到这种情况。
GPU 不会缩短等待时间,而是让等待变得不可见
我们已经说过,取数据很慢。 CPU 和 GPU 都面对相同的物理延迟,但它们用相反的方式来应对。
CPU 会使用缓存和预测,提前准备好数据,尽量避免等待发生。
GPU 则接受等待,并安排好在等待期间始终有其他事情可做。
机制如下:芯片加载的工作量远超它能同时运行的数量。芯片上的一个计算单元可能有 64 个 Warp 常驻其中,但每个时钟周期只有一个 Warp 能执行。
正在运行的 Warp 请求主内存中的数据时,就会停顿。调度器不会陪它一起等待,而是挑选另一个已准备就绪的 Warp 来运行。那个 Warp 停顿时,再挑第三个。
算术单元始终保持忙碌。每个 Warp 的大部分时间都在等待,但整台机器却从不闲下来。

在 Warp 之间切换几乎不需要成本。在 CPU 上,更换正在运行的线程意味着保存一个线程的状态并恢复另一个线程的状态,这需要数百个周期。在 GPU 上,每个常驻 Warp 的状态都已存放在芯片上的专用存储中。切换只需指向另一组存储,一拍即可完成。
这就是 GPU 搭载大量高速片上存储的原因。它不是为了让某个线程更快,而是为了让成千上万个线程同时处于未完成状态,随时准备在数据到达的瞬间继续执行。
👉 这就是监控中的利用率数字具有误导性的原因。它通常报告的是芯片上是否调度了任何工作,而不是算术单元是否在做有用的事情。 GPU 即使因为数据供给不足而处于 100%“利用率”,看起来也会和以全吞吐量运行时一模一样。
实际应用
如果你只从这一节记住一个实际要点,就记住这个。
GPU 需要大量彼此独立的工作才能高效运行。给它一个小任务,它完成该任务的速度大致受内存系统限制,而芯片大部分处于闲置状态。给它一大批相似工作,同一套硬件突然就显得很快。
这不是怪癖,而是设计按预期发挥作用的结果;这也是批处理大小会出现在你遇到的每一次服务讨论中的原因。
内存是一架阶梯,每一级都比上一级慢得多
接下来要讲的部分,决定了你几乎所有的性能表现。
数据并非简单地“存放在内存里”。它位于距离算术单元远近不同的若干层级之一,最近与最远两端之间的差距极其巨大。
有四个层级值得了解。
-
每线程存储。距离最近、速度最快。每个线程在处理数据时,会在这里暂存少量数值。访问几乎是即时的。
-
片上暂存区。连接到每个计算单元的一小块高速内存,大小为几百 KB。关键在于,如果你编写底层代码,就可以控制它。你决定其中存放什么,以及保留多久。
-
共享缓存。一块更大的存储池,大小为几十 MB,由整块芯片共享。它由硬件自动管理。你无法选择哪些数据存放在这里,但访问模式会影响它。
-
主内存。一块大型存储池,大小为几十 GB,模型权重和激活值实际存放于此。这就是人们所说的 VRAM。它在物理上位于处理芯片裸片之外,而访问它是芯片执行的操作中最慢的。
沿着这份列表向下,有两件事会发生变化:存储池变大了,访问也变难了。
| 层级 | 大致容量 | 可访问范围 | 访问代价 |
|---|---|---|---|
| 每线程存储 | 少量数值 | 单个线程 | 几乎免费 |
| 片上暂存区 | 数百 KB | 一批线程 | 很便宜 |
| 共享缓存 | 数十 MB,整块芯片共用 | 所有线程块 | 明显更慢 |
| 主内存 | 数十 GB | 所有线程块 | 慢得多 |

两端之间的差距绝非微不足道。读取线程已经持有的数值,成本低到不值得计量;访问主内存的成本则高出数百倍。
你的权重位于最底层;这一层容量最大,也是唯一足以容纳它们的一层。
两种成本,而硬件只能应对其中一种
访问内存会带来两种彼此独立的成本。把它们区分开,是理解接下来内容的关键。
第一种成本是等待。你请求数据,然后要过一段时间它才会到达。
硬件会替你处理这一种成本,使用的正是上一节介绍的机制。它让成千上万个请求同时推进,因此一个 Warp 等待时,许多其他 Warp 正在得到处理。等待确实存在,但几乎完全被隐藏了。
第二种成本是通路的宽度。每秒能够传输的字节数有限,再多的请求重叠也改变不了这一点。
这一种成本无法隐藏。它是一道硬上限,也是芯片会把内存带宽作为醒目规格来宣传的原因。
因此,接下来的计算都围绕带宽,而不是等待展开。
这些层级实际位于何处
层级结构告诉你每一层相距有多远。了解它们在物理上的位置也很有帮助,因为布局解释了为什么有些层级可以共享,而有些则不能。
GPU(图形处理器)并不是一个巨大的算术单元池。它被划分为大约一百个彼此独立的单元,每个单元都是一台小型的、自成一体的机器。它们叫作流式多处理器,几乎总是写作 SM,值得使用这个正式名称,因为你遇到的每种工具和文档都会使用它。
每个 SM 都有自己的前两个层级。它有自己的寄存器文件,供驻留于此的线程使用;自己的共享内存和 L1 缓存;还有自己的 Warp(线程束)调度器,用来决定下一个运行哪个 Warp。它们都不会与隔壁的 SM 共享。
工作以线程块的形式到达:一批线程被交给一个 SM,并一直留在那里,直到执行完毕。在块内部,线程按我们前面介绍过的 Warp 进行处理。
举个例子就能具体看出这种划分。假设你启动一个包含 256 个线程的线程块,这是常见的选择。硬件会把它分成 8 个由 32 个线程组成的 Warp,此后调度器就在这 8 个 Warp 之间进行调度。
你不会选择这种划分。你只需选择批次大小,而分组成每组 32 个线程的过程会在底层自动完成。
这就是批次大小几乎总是 32 的倍数的原因。要求 250 个线程时,你仍会得到 8 个 Warp,但最后一个 Warp 中只有 26 个线程通道在工作,另外 6 个什么也不做,却仍要轮到它们。
允许的最大线程块为 1024 个线程,也就是 32 个 Warp 驻留在一个 SM 上。
这就是共享内存有用的原因,也是它名称的由来。线程块中的每个线程都位于同一个 SM 上,因此它们都能读取同一块共享内存,彼此协作的成本几乎为零。
所有 SM 下方是每个 SM 都能访问的 L2 缓存。再往下,完全位于芯片裸片之外、隔着内存总线的是 HBM,其中存放着你的权重、 KV 缓存和激活值。人们所说的全局内存或 VRAM,指的就是 HBM。

这张图解释了你总会遇到的权衡。任何能留在单个 SM 内的数据都很快,而且是私有的。任何必须让整个芯片都能看到的数据,都得传到 L2 或更远处;传输过程才是昂贵的部分。
这也说明了这四个层级的正式名称,它们也是你在其他地方会看到的名称。每线程存储是寄存器文件。暂存区是共享内存,位于 L1 旁边。共享缓存是 L2。主存是 HBM,也就是人们所说的全局内存或 VRAM。
接下来都使用这些名称。
反直觉之处
在 CPU 上,存储容量越大,速度就越慢,形成平滑的金字塔结构。寄存器很小,缓存更大,主存则很大。
GPU 上的结构则有所扭曲。整个芯片上的寄存器文件加起来,大致与 L2 一样大。这很不寻常,而且直接源于上一节所述的情况。数千个尚未完成的线程需要地方暂存自己的状态,因此芯片把异常大的面积用于保存这些状态。
总之,内存系统并非单一整体。它是一座层级阶梯,而性能优化几乎完全取决于尽可能长时间地让数据留在较高层级。
每字节运算量是决定一切的数字
现在我们已经掌握了足够的信息,可以准确地陈述核心思想。
选取你在 GPU 上运行的任何操作,统计两件事:它执行了多少次算术运算,以及为执行这些运算,它必须从主存读取多少字节。
用前者除以后者。这个比值就是该操作的每字节运算量,而且在你进行任何测量之前,它就能预测性能。
它的正式名称是算术强度,你会看到人们这样称呼它。简单来说,它表示每次沿着这条通道传输数据时,你能从中榨取多少价值。
两种处于两端的操作
有用的问题不是某个操作读取了多少字节,而是它对每个读取的值使用了多少次。
将数组中的每个元素乘以 2,每个值只使用一次。读取它、乘以 2、写回,然后再也不看它。每读取一个值,执行一次运算。
矩阵乘法则正好相反。在一个 1024 乘 1024 的乘积中,从第一个矩阵读取的每个值,都会与第二个矩阵中的 1024 个不同值相乘。读取一次,执行一千次运算。
单次读取所带来的收益相差一千倍,而这完全源于数据复用。矩阵乘法是加速器真正擅长的唯一一种操作,这就是原因。
盈亏平衡比值
每个芯片都有一个阈值,它来自一次除法。
用芯片的峰值算术吞吐率除以峰值内存带宽,就能得到两者恰好平衡时的每字节运算量。
对于当前以 16 位精度运行的数据中心 GPU,这个数字约为每字节 300 次运算。
以 H100 SXM5 为例,它是大多数人租用的主力 GPU。其数据手册给出的稠密 BF16 吞吐量为 989 TFLOPS,内存带宽为 3.35 TB/s。
用前者除以后者。每秒 989 万亿次运算除以每秒 3.35 万亿字节,得到每字节 295 次运算,这就是 300 这个数字的由来。
在你进行任何性能分析之前,这一个数字就能告诉你很多信息。每读取一个字节却执行不到 295 次运算的操作,不可能充分利用整块芯片,不管代码写得多好。
这个阈值也会随硬件而变化,而且变化方向可能出乎你的意料。 H200 使用与 H100 相同的计算裸片,因此其算术上限仍为 989 TFLOPS,但带宽提升到了 4.8 TB/s。这使阈值降至每字节 206 次运算。
阈值越低越好。这意味着更多工作负载能够越过这条线,成为受计算能力限制的工作负载;这也是为什么即使算术能力没有增加,仅升级带宽仍能加快推理速度。
低于 300 时,你受限于内存。增加算术能力不会有任何改变,因为算术单元已经在等待数据。
高于 300 时,你受限于算术能力。增加带宽不会有任何改变,因为数据到达的速度已经超过了你的消耗速度。
阈值由硬件决定。你的工作负载相对于阈值处于什么位置,则由你决定。

👉 这张图称为屋顶线模型。值得记住这个术语,因为它是性能工程师讨论这些问题时的标准框架,而且它恰好对应上文描述的两个上限。
为什么生成一个 token 是可以想象到的最糟情况
把这个框架应用到语言模型推理上,开头的谜题就迎刃而解了。
文本是逐个 token 生成的。每个新 token 都需要对模型进行一次完整的前向传播。这次传播会读取模型中的每个权重,而且只读一次。
先看看单个权重实际做什么。
一个权重位于两个数字之间。它会与一个输入值相乘,结果再加到一个累加总和中,成为输出的一部分。
这就是权重的全部工作。一次乘法、一次加法,因此每次使用会执行两次运算。
现在放大来看。一个 700 亿参数的模型生成一个 token 大约需要 1400 亿次运算,因为每个权重都执行两次运算。
接着统计字节数。以 16 位精度表示时,每个权重占 2 字节,因此读取全部权重意味着传输 140 GB。

把两者合起来看:1400 亿次运算由 140 GB 的数据传输支撑,相当于每字节 1 次运算。
盈亏平衡阈值约为 300。处理单个请求时,你大约低于这个阈值三百倍。
这就是算术单元闲置的原因。你的配置没有问题。操作本身包含的工作量就是不足以让这些单元持续得到数据。
这实际预测的数字
一旦你接受生成受限于内存,token 速率就不再神秘,而是一个你可以在几秒钟内自行计算的数值。
一个以 16 位精度表示的 700 亿参数模型约占 140 GB。高端 GPU 的主存带宽大约为每秒 3.3 TB。
这样就有两个数字和一次除法:140 GB 的权重,每秒到达 3,300 GB;由此可算出,读取一遍权重需要 0.042 秒。
约为 42 毫秒。这就是生成一个 token 所需的时间,因为在批次大小为 1 时,完整读取一遍权重的代价恰好就是一个 token。
现在把它换算成速率。如果每个 token 需要 42 毫秒,那么一秒钟能容纳 1,000 除以 42 个 token,也就是约 24 个。
因此,速率约为每秒 24 个 token,再多的软件技巧也无法突破这个下限。
这个下限由每个 token 必须读取的字节数除以字节到达的速度决定。其他一切都只是细节。

预填充的表现恰恰相反
还有第二个阶段,它落在线的另一侧。
你发送提示词时,模型会一次性处理其中的所有 token,然后才开始生成内容。这个阶段称为预填充;由于它会同时处理多个 token,每次读取的权重都会用于处理所有这些 token。
每字节运算量会立即上升。预填充通常受计算能力限制,这就是为什么长提示词带来的开销与长输出的开销感受不同。
同一个模型有两个瓶颈相反的阶段。性能讨论中的许多困惑,都源于把它们当成同一件事。
每一种你听说过的优化,改变的都是同一个比值
这正是这个框架发挥作用的地方。一旦你把性能看作一个单一比值,这些技术就不再像是一串互不相关的技巧。
你能改变的只有两件事:增加每次取数所完成的工作,或者减少取出的字节数。下面的每种技术都属于其中一种。
批处理增加工作量
同时处理多个请求,这样你只需读取一次每个权重,然后将它用于所有请求。
十个并发请求意味着每字节工作量增加十倍,而且不需要额外的内存成本。对于服务吞吐量,你能做的其他事情都比不上这一点;它唯一的代价,是每个用户的单次延迟会略微增加。
盈亏平衡的计算值得记住。在 16 位精度下,大约需要 300 个并发序列,生成才会变成计算受限。在这之下,你会有闲置的算术能力没有被利用,这正是服务系统如此努力地让批次保持饱满的原因。

融合减少字节数
串联的逐元素操作是最明显的例子。
把三个操作分开执行,每个操作都会从主存读取输入,再把输出写回去。对几乎不耗时的算术来说,这相当于沿着走廊来回跑六趟。
把它们融合成一个操作,中间值就不会离开芯片。一次读取、一次写入,算术运算完全相同。内存流量因此减少了三分之二。
这就是为什么能融合操作的编译器,在充满小型逐元素运算的模型上会带来大幅加速;也解释了为什么同样的编译器对纯矩阵乘法毫无作用。
让数据保持在近处以减少字节数
共享内存正是为此而存在的。
将一块数据加载到共享内存中,完成所有需要它的计算,然后再继续处理。你只为这趟数据传输付出了一次代价,却多次利用了它。
FlashAttention 是著名的例子。普通注意力机制会构建一个大型中间矩阵,将其写入主存,再读回来。 FlashAttention 则以分块方式计算注意力,让数据块留在片上内存中,因此这个中间结果完全不会触及主存。
算术运算基本没有变化。内存流量大幅减少,运行时间也随之缩短。
量化直接减少字节数
将权重以 8 位而不是 16 位存储,每个权重的大小就减半。
这会使每个 token 必须读取的字节数减半,让每字节运算量翻倍,并使生成速度上限大致翻倍。同一个 700 亿参数模型会从 140 GB 缩减到 70 GB;每秒生成速度的理论估算值则从约 24 个 token 提升到约 48 个。
不过,代价是准确率。较低精度会损失信息,损失多少在很大程度上取决于模型和量化方法。这是一个真实的工程权衡,不是免费的收益。
按顺序读取的重要性超乎预期
有一个访问模式细节值得了解,即使你从不编写核函数。
内存不会一次只移动一个值。硬件会读取固定大小的数据块。当 Warp(线程束)中的线程读取相邻地址时,它们的请求会落在同几个数据块内,每个取出的字节都能被利用,于是你就能获得自己付出成本所换来的内存带宽。
当它们读取分散的地址时,每个线程都会拉取一个完整数据块,却只使用其中几个字节。最终取出的数据可能是实际所需数据的八倍。
实际影响是,内存布局本身就是一项性能决策。沿着错误的轴读取矩阵,或使用会把相关值分散存放的数据结构,都可能让你损失大部分内存带宽,而算术运算却没有任何变化。

小型任务有自己的性能下限
还有一个比值无法捕捉的瓶颈。
向 GPU 提交工作有固定成本。 CPU 必须准备并分派每个任务,无论任务很大还是很小,这都需要时间。
如果模型运行许多小操作,你最终花在分派上的时间可能超过计算时间。此时你既不是内存受限,也不是计算受限,而是受开销限制。
其表现是 GPU 看起来处于空闲状态,而 CPU 却在忙碌。解决方法是减少操作数量、增大每个操作的规模,或者捕获整个操作序列,使其能够作为一个整体重放。
如何判断自己处于哪种情况
只有当你能确定自己在这个框架中的位置时,它才有用,而判断的方法很简单。
运行期间测量两项指标:实际从主存搬运的每秒字节数,以及实际执行的每秒算术运算次数。将它们分别与硬件的能力上限进行比较。
几乎所有情况都可归入以下三种结果。
-
带宽接近上限,而算术运算远低于上限。你受内存限制。减少搬运的字节数。加大批处理、进行量化、融合操作、修正内存布局。增加算术能力不会有任何作用。
-
算术运算接近上限,而带宽远低于上限。你受计算限制。这是不错的状态。此时的改进来自更低精度、更好的算法,或能力更强的硬件。
-
两者都远低于各自上限。你受开销限制,或者工作量太小,不足以填满芯片。看看你启动了多少个独立操作,以及每个操作的规模有多大。
👉 关于从哪里着手的说明。对于语言模型服务,生成在几乎所有现实配置中都受内存限制。如果你正在优化 token 吞吐量,却还没有检查批次大小、精度和 KV 缓存大小,那么这三项几乎总是比你尝试的其他任何事情影响更大。
硬件变化时仍然成立的规律
规格变化很快。值得弄清楚,哪些部分你以后需要重新学习,哪些则不需要。
变化的是数字。内存容量、带宽和算术吞吐量都随每一代产品提升,其中算术能力增长得最快。新的精度格式不断出现,每一种都比上一种更小,因为缩减字节数是直接攻克瓶颈的方法。
不变的是整体形态。算术运算仍然远比数据搬运便宜。内存层级仍然各有层次。每字节工作量仍然决定你会撞上哪个上限。
如果说有什么变化,那就是这个框架会随着时间变得更有用。由于算术能力的增长快于带宽,盈亏平衡比值会不断升高。在旧硬件上受计算限制的工作负载,在新硬件上会变成受内存限制,即使代码一行都没有改动。
这就是值得押注的方向。减少数据搬运的技术将持续增值,而规格表上的峰值算术性能,将继续是文档中最缺乏预测力的数字。
结论
我们从一个不对称性出发,建立了对 GPU 工作方式的直觉:算术运算成本低,搬运数据代价高。
我们解释了为什么这种不对称性造就了一块由数千个简单算术单元共享一个控制器的芯片,而不是由少数几个复杂单元组成。我们还看到,GPU 并不会缩短等待数据的时间,而是通过让远多于其当前可运行数量的工作保持加载状态,并在不同工作片段之间即时切换,来隐藏等待。
我们梳理了内存层级,从寄存器文件经过共享内存和 L2,一直到 HBM;每一层都比上一层更大,也更难访问。我们还看到这些层级的位置:最上面两层由每个 SM 私有,下面的所有层级则要么由整个芯片共享,要么完全位于芯片之外。
由此我们得到了核心指标:每取出一个字节所完成的工作量。在当前硬件的 16 位精度下,每块芯片都有一个盈亏平衡比值,大约是每字节 300 次运算。低于这个比值时,你受内存限制;高于它时,你受算术运算限制。
将这个框架应用于语言模型,开篇的疑问就有了解释。生成一个 token 时,会读取所有权重,并对每个权重执行约两次运算,因此比值接近 1,大约比盈亏平衡点低三百倍。预填充则处于这条界线的另一侧,这就是两个阶段表现如此不同的原因。
最后我们看到,所有广为人知的优化都属于两种做法之一。批处理和分块增加每次取数所完成的工作。融合、量化和更好的内存布局则减少取出的字节数。
我们有意省略了编写 GPU 代码的底层细节,因为必须先建立直觉。缺乏这套框架就开始编写核函数,可能写出一些快得连写代码的人也说不清原因、慢得又找不到原因的代码。
后续文章将在此基础上进一步深入并提供更具体的内容,包括如何依据盈亏平衡线解读性能分析器输出、随着上下文增长 KV 缓存 如何改变内存状况,以及服务引擎在决定如何进行批处理时实际会发生什么。
一如既往,我们的目标是培养扎实的系统级视角,并为你提供一套灵活的工程框架,用于构建稳健且易于维护的 AI 系统。
一如既往,感谢阅读!
祝好! :)
译者说明:译自 Akshay 的 GPU 长文《GPU 实际是如何工作的》。硬件规格与性能估算按来源所述翻译,未逐项独立核验。