区块链 区块链技术 比特币公众号手机端

GPU内存带宽与LLM推理优化:从算术强度到数据复用

liumuhui 4小时前 阅读数 1 #区块链

图像

LLM 工程师需要的直觉,无需硬件手册。

读完本文,量化、投机解码(Speculative Decoding)、连续批处理(Continuous Batching)等技术,看起来就不再是一串需要死记硬背的技巧了。

有一件事几乎让每个刚开始做语言模型服务的人都感到困惑。

你租了一块顶级数据中心 GPU。规范表上说它每秒可以执行近千万亿次算术运算。你把一个 700 亿参数的模型加载上去,开始生成文本。你盯着利用率监视器,读数很高。一切看起来都很健康。

然后你数了数 Token。你从一块额定为千万亿次运算的芯片上,每秒只能得到几十个 Token。那些算术能力几乎完全闲置。

没有哪里出故障。你的代码没问题,驱动没问题,买更大的 GPU 也几乎帮不上忙。你看到的正是关于这类硬件最重要的一件事:一旦你能清楚地看到它,你读过的多数性能建议就不再是一堆技巧,而会变得显而易见。

本文将从零开始建立这种直觉。我们会看看为什么 GPU 长成这个样子,为什么内存和算力始终在相互竞争,以及到底是什么让这些机器跑得更快。

不需要 CUDA。我们会在具体数字有用时提到几款真实芯片,但这里的任何内容都不依赖于背诵它们;而且在每种情况下,比值远比数字本身重要。

前置要求。你应该熟悉神经网络进行矩阵乘法的概念,也要知道模型权重是存储在内存中的数字。仅此而已。

我们开始吧!

一个不对称性解释了几乎所有问题

从整个设计所依赖的一个事实说起。

做算术很便宜,而取回要做算术的数字却很贵。

这句话第一次读到时会让人觉得不对劲。乘法听起来才是难的部分,移动一个数字听起来微不足道。而在现代硬件上恰恰相反,而且差距并不小。

一个有用的比喻是车间。算术单元是工作台,成千上万个,挤在房间中央。数据存放在仓库里,仓库和车间之间由一条走廊连接。

工作台消耗材料的速度,远快于走廊运送材料的速度。再加更多工作台也不会改变什么,因为走廊早就成了瓶颈。那条走廊就是人们所说的内存带宽(Memory Bandwidth)。

你之后遇到的每一种性能技术,都是在让每一次穿过走廊的行程产出更多工作。

图像

为什么差距还在不断扩大

这不是暂时的工程短板,而是一个长期趋势。

在最近的几代加速器中,算术能力的增长速度是内存带宽的数倍。每一代新芯片能做的数学运算多得多,而能取回的数据只多了一点。

所以这种失衡每一代都在加剧,而不是改善。减少数据搬运的技术会随着时间推移越来越有价值,而原始算术能力能告诉你的芯片实际表现,越来越少。

为什么 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 之间切换基本零成本。在 CPU 上切换正在运行的线程,意味着保存一条线程的状态再恢复另一条线程的状态,要花几百个周期;在 GPU 上,每个常驻 warp 的状态已经停放在芯片上的专用存储中,切换只是指向另一组存储,只需要一个时钟周期。

这就是 GPU 配备海量高速片上存储的原因。它不是为了让某条线程更快,而是为了让数千条线程能同时停留在半完成状态,数据一到就能立刻接着执行。

这也是监控中利用率数字具有误导性的原因。它通常只报告芯片上是否调度了工作,而不是算术单元是否在做有用的事。一个 100%「已利用」但数据饥饿的 GPU,和一个满吞吐运行的 GPU,看起来一模一样。

实用版本

如果你只从本节带走一个可操作的概念,那就是这个。

GPU 需要一大堆独立工作才能跑得好。给它一个小任务,它会以内存系统的大致速度完成,芯片大部分时间处于闲置状态。给它一大批类似的工作,同一块硬件看起来突然就变快了。

这不是怪癖,而是设计本来的意图,也是为什么批大小(Batch Size)会出现在你今后每一场服务(Serving)讨论中。

内存是一把梯子,每一级都比上一级慢得多

现在要讲的,是几乎决定你全部性能的部分。

数据并不是简单地「在内存里」。它位于距离算术单元不同远近的若干层级之一,最近和最远之间的差距极其巨大。

有四个层级值得了解。

每线程存储。最近也最快。每条线程在处理数据时在这里保存少量数值。访问几乎是瞬间完成的。

片上暂存器。每个计算单元旁边一小块极快的内存,大小几百 KB。关键在于,如果你在写底层代码,这一层由你控制。你决定放什么进去、放多久。

共享缓存。更大的池子,几十 MB,整个芯片共享,由硬件自动管理。你不能选择什么留在这里,不过访问模式会影响它。

主内存。最大的池子,几十 GB,你的模型权重和激活值实际上就在这里。它物理上位于处理芯片之外,访问它是这颗芯片所做的最慢的事。

沿着这个列表往下走,有两件事在变化:池子越来越大,访问它们的成本越来越高。

层级 大致规模 谁可以访问 访问成本
每线程存储 少量数值 一条线程 几乎为零
片上暂存器 每个线程块数百 KB 一个线程块内的线程 非常低
共享缓存 数十 MB,整个芯片 所有线程块 明显更高
主内存 数十 GB 所有线程块 最高,且差距悬殊

图像

两端之间的差距相当大。读取一条线程已经持有的值,几乎不需要成本;而访问主内存则要贵上几百倍。

你的权重位于最底层,它是最大的那一层,也是唯一大到足以装下它们的那一层。

两种成本,硬件只处理其中一种

访问内存在两个不同方面让你付出代价,把两者分开是理解本文其余部分的关键。

第一种成本是等待。你请求数据,过一段时间它才到达。

硬件替你化解了这种成本,用的正是上一节的机制。它同时维持着数千个在途请求,所以当一个 warp 在等待时,大量其他 warp 正在被服务。等待是真实存在的,但它几乎完全被隐藏了。

第二种成本是路径的宽度。每秒只能移动那么多字节,无论你把多少请求重叠起来,这一点都不会改变。

没有什么能隐藏这种成本。它是一道硬性天花板,也是一颗芯片把内存带宽当作头号指标来宣传的原因。

这就是为什么本文其余部分的所有计算都在讲带宽,而不是等待。

这些层级到底在哪里

梯子告诉你每个层级有多远。知道它们物理上在哪里也有帮助,因为物理布局解释了为什么有些层级是共享的、有些不是。

GPU 不是一整个大池子算术单元。它被分成约一百个独立单元,每个单元都是一台小型、自给自足的机器。它们被称为流式多处理器(Streaming Multiprocessor),几乎总是缩写为 SM。值得使用正式名称,因为你遇到的每个工具和文档都会用到它。

每个 SM 都拥有最上面两级存储的副本:有自己的寄存器文件供线程使用,有自己的共享内存和 L1 缓存,还有自己的 warp 调度器来决定下一个运行哪个 warp。它与隔壁的 SM 之间不共享任何东西。

工作以线程块(Thread Block)的形式到达。线程块是一批交给某个 SM 的线程,会一直留在那里直到执行完成。在块内部,线程按我们前面遇到的 warp 方式被处理。

一个例子能让这种划分变得具体。假设你启动一个 256 条线程的线程块,这是一个常见的选法。硬件把它分成 8 个 32 条的 warp,之后调度器就在这些 warp 之间做选择。

你永远不能选择这种划分。你选择的是线程块的大小,按 32 分组的动作在底层自动完成。

这也是为什么线程块大小几乎总是 32 的倍数。要 250 条线程,你仍然得到 8 个 warp,但最后一个 warp 的 32 条通道里只有 26 条在干活,另外 6 条不产生任何结果,却照样占用它们的那一轮执行时间。

允许的最大线程块是 1024 条线程,相当于 32 个 warp 驻留在一个 SM 上。

这也解释了共享内存为什么有用,以及它名字的由来。块里的每条线程都在同一个 SM 上,所以它们都能访问同一份共享内存,彼此协作几乎零成本。

在所有这些 SM 之下是 L2 缓存,每个 SM 都能访问它。再往下,完全离开芯片、跨过内存总线,才是存放着你的权重、KV cache 和激活值的 HBM。人们说全局内存(Global Memory)或显存(VRAM)时,指的就是 HBM。

图像

这幅图解释了你不断遇到的这种取舍。任何能留在单个 SM 内的东西,访问起来都又快又独占;任何需要被整个芯片访问的东西,都得一路下到 L2 或更远,而这趟下行路程才是成本高昂的部分。

这也让四个层级有了它们的正式名称,也就是你在其他任何地方会看到的名字:每线程存储是寄存器文件(Register File);暂存器是共享内存(Shared Memory),与 L1 并排;共享缓存是 L2;主内存是 HBM,也就是人们所说的全局内存或显存(VRAM)。

本文其余部分将使用这些名字。

反直觉的部分

在 CPU 上,存储层级越慢就越大,形成一个平滑的金字塔:寄存器极小,缓存更大,主内存巨大。

在 GPU 上,这个形状被扭曲了。整个芯片的寄存器文件加起来,大小大致与 L2 相当。这很不寻常,而这正是上一节内容直接推导出的结果:数千条半完成的线程,需要有一处地方来存放它们的状态,所以芯片把异常多的面积用来保存它们。

总结一下:内存系统不是单一的东西,它是一把梯子;性能工作几乎完全在于让数据尽可能久地停留在上层。

每字节工作量是决定一切的数字

我们现在掌握的东西,已经足够精确地表述核心概念了。

以你在 GPU 上运行的任何操作为例,统计两件事:它执行了多少次算术运算,以及它要从主内存拉取多少字节才能完成这些运算。

用第一个除以第二个。这个比值就是该操作的每字节工作量(Work per Byte),在你做任何测量之前,它就已经能预测你的性能。

它的正式名称是算术强度(Arithmetic Intensity),你会经常看到这个术语。其思想很简单:你从每次穿过走廊的行程中提取出了多少价值。

两个处于相反极端的操作

有用的问题不是某个操作读了多少字节,而是它读到的每个值被使用了多少次。

把数组的每个元素乘以二:每个值恰好用一次,取出来、翻倍、写回、再也不看它。每个取到的值只对应一次运算。

矩阵乘法恰恰相反。在一个 1024 乘 1024 的乘积中,你从第一个矩阵取到的每个值,都要与第二个矩阵里的 1024 个不同值相乘。一次取回,上千次运算。

这就是单次取回换来的价值的千倍差异,而且它仅仅来自复用。矩阵乘法是加速器真正擅长的那一项运算,原因就在这里。

平衡比值

每颗芯片都有一个阈值,它由一次除法得出:拿芯片的峰值算术速率除以它的峰值内存带宽,得到的就是两者恰好平衡时的每字节工作量比值。

对当前 16 位精度的数据中心 GPU 而言,这个数大约是每字节 300 次运算。

以 H100 SXM5 为例,它是大多数人租用的主力卡。数据手册上写着 989 TFLOPS 的稠密 BF16 吞吐,以及 3.35 TB/s 的内存带宽。

两者相除:每秒 989 万亿次运算除以每秒 3.35 万亿字节,得到每字节 295 次运算,大约 300 这个数就是这么来的。

这一个数字在你做任何 profiling 之前就告诉了你很多:一个每取回一个字节做不了 295 次运算的操作,无论写得多好,都无法完全利用整颗芯片。

这个阈值也随硬件变化,而且变化方向可能出乎你的意料。H200 与 H100 使用相同的计算芯片,所以它的算术天花板不变,仍是 989 TFLOPS,但带宽提升到了 4.8 TB/s,这把阈值降到了每字节 206 次运算。

更低的阈值是好事。它意味着更多工作负载能够越过门槛、变成算力受限(Compute-Bound)。这就是为什么只提升带宽、不增加任何算术能力的升级,依然能加速推理。

低于 300,你受限于内存。增加算术能力什么也改变不了,因为算术单元本来就在空等。

高于 300,你受限于算术。增加带宽什么也改变不了,因为数据到达的速度已经超过了你能消耗的速度。

阈值属于硬件;你的工作负载落在它的哪一侧,则属于你自己。

图像

这张图的名字是 Roofline 模型。这个术语值得了解,因为它是性能工程师惯用的分析框架,而且它正是上面描述的双天花板思想。

为什么生成一个 Token 是最糟的情况

把这个框架应用到语言模型推理上,开头的谜团就迎刃而解了。

生成文本是一个 Token 接一个 Token 地发生的。每个新 Token 都需要一次完整的模型前向传播,而这次传播会读取模型中的每一个权重,每个权重只读一次。

先看单个权重实际做了什么。

一个权重处在两个数字之间:它与一个传入值相乘,结果被加进一个累加和里,累加和成为输出的一部分。

这就是一个权重的全部工作:一次乘法,一次加法,所以它每次被使用就是两次运算。

现在放大。一个 700 亿参数的模型生成一个 Token 大约要做 1400 亿次运算,因为每个权重贡献了两次运算。

接下来数一数字节。在 16 位精度下每个权重占 2 字节,所以读取全部权重意味着搬运 140 GB。

图像

把两者放在一起:1400 亿次运算由 140 GB 的内存流量支撑,也就是每字节 1 次运算。

平衡阈值大约是 300。服务单个请求让你处于这个阈值下方大约三百倍的位置。

这就是算术单元闲置的原因。你的环境没有问题,而是这个操作本身没有足够的工作量来喂饱这些算术单元。

这个数字实际预测了什么

一旦你接受生成受限于内存,你的 Token 速率就不再神秘,而变成你几秒钟就能自己算出来的数字。

一个 700 亿参数、16 位精度的模型大约占 140 GB。一颗高端 GPU 每秒大约可以从主内存搬运 3.3 TB 数据。

这给了你两个数和一次除法:140 GB 权重,每秒 3300 GB 的传输速度,算下来是 0.042 秒读完一遍权重。

就叫它 42 毫秒。这是产生一个 Token 的时间,因为在批大小为 1 时,一次完整的权重读取恰好就是一个 Token 的代价。

现在把它换算成速率。如果每个 Token 花 42 毫秒,那么一秒钟可以产出 1000 ÷ 42 个 Token,大约是 24 个。

所以大约每秒 24 个 Token,任何软件技巧都无法撼动这个下限。

这个下限由每个 Token 必须读取的字节数除以字节到达的速度决定。其他一切都是细节。

图像

Prefill 的行为方式恰恰相反

还有第二个阶段,它落在分界线的另一侧。

当你发送一段提示词时,模型在生成任何东西之前会一次性处理其中所有的 Token。这个阶段被称为预填充(Prefill)。因为它同时处理多个 Token,所以每次取到的权重都会用在所有 Token 上。

每字节工作量立刻攀升。Prefill 通常是算力受限的,这就是为什么长提示词和长输出让你感到开销大的方式不一样。

同一个模型的两个阶段,瓶颈截然相反。性能讨论中大量的混乱,都来自把这两者当成一回事。

你听说过的每一个优化都在改变同一个比值

这就是这个框架真正发挥作用的地方。一旦你把性能看作一个比值,这些技术看起来就不再是一堆互不相干的技巧。

你能改变的只有两件事:增加每次取回所做的工作,或者减少取回的字节数。下面每一项技术,做的都是这两件事之一。

批处理增加工作量

同时处理多个请求,每个权重只读一次,然后把它用于所有请求。

十个并发请求意味着每字节工作量变成十倍,而且没有额外内存成本。在为服务吞吐所做的其他努力中,没有任何能与之相比;它唯一的代价,是每个单独用户多了一点延迟。

这笔平衡账值得记住。在 16 位精度下,你大约需要 300 条并发序列,生成才会变成算力受限。低于这个数,你就有算术能力闲着没用,这正是服务系统如此努力保持批处理满载的原因。

图像

融合减少字节

链式逐元素操作是最清晰的例子。

分开跑三个操作,每一个都从主内存读输入、把输出写回主内存。那是六次穿过走廊的行程,而这些算术本身几乎不花时间。

把它们融合成一个操作,中间值从不离开芯片。一次读、一次写,算术完全相同,你把内存流量砍掉了三分之二。

这就是为什么做操作融合的编译器在充满小型逐元素运算的模型上能带来巨大加速,也是为什么那些编译器对纯矩阵乘法毫无作用。

让数据靠近减少字节

共享内存正是为此而存在。

把一块数据加载到共享内存,做所有需要它的计算,然后继续。你为这趟行程付了一次钱,却派上了很多次用场。

FlashAttention 是著名的例子。普通注意力会构建一个巨大的中间矩阵,写到主内存,然后再读回来;FlashAttention 以分块方式计算注意力,让这些块留在片上内存中,所以那个中间结果完全不会触及主内存。

算术基本不变,内存流量骤降,运行时间也随之骤降。

量化直接减少字节

把权重存成 8 位而不是 16 位,每个权重占用的空间减半。

这让你每个 Token 必须读取的字节数减半,于是每字节工作量翻倍,你的生成上限也大约翻倍。同一个 700 亿参数的模型从 140 GB 降到 70 GB,理论下限从大约每秒 24 个 Token 变成大约 48 个。

代价是精度。更低的精度会丢失信息,损失多大在很大程度上取决于模型和量化方法。这是一个真正的工程决策,不是免费的午餐。

顺序读取的重要性超乎想象

即使你从不写内核,也值得了解一个关于访问模式的细节。

内存不是一次移动一个值,而是按固定大小的块取数据。当一个 warp 中的线程读取相邻地址时,它们的请求会落在少数几个相同的块里,每个取回的字节都被用上,你得到了你付费的带宽。

当它们读取分散的地址时,每条线程拉回整个块却只用其中几个字节。你可能最终取回的数据是实际需要的八倍。

实际后果是:内存布局本身就是一种性能决策。沿着错误的轴读取矩阵,或者使用把相关值打散的数据结构,可以在算术完全不变的情况下让你损失大部分带宽。

图像

小任务有自己的下限

还有一个这个比值无法反映的瓶颈。

向 GPU 发送工作有固定成本。CPU 必须准备并分发每个任务,不管任务是大是小,都要花时间。

如果你的模型运行许多小操作,你最终花在分发上的时间可能比计算还多。那时你既不是内存受限也不是算力受限,而是开销受限(Overhead-Bound)。

症状是 GPU 看起来空闲,而 CPU 在拼命工作。解决办法是使用更少、更大的操作,或者捕获整个操作序列,让它能作为一个整体回放。

如何判断你处于哪种情况

这个框架只有在你能定位自己所处位置时才有用,有一个简单方法可以做到。

在运行过程中测量两件事:你实际每秒从主内存搬运多少字节,以及实际每秒执行多少次算术运算。把它们分别与硬件能力上限对比。

三种结果几乎覆盖所有情况。

带宽接近天花板,算术远低于它:你是内存受限。减少搬运的字节数,加大批处理、量化、融合、修正布局。增加算术能力不会有任何作用。

算术接近天花板,带宽远低于它:你是算力受限。这是一个好状态,改进来自更低精度、更好的算法,或者更强的硬件。

两者都远低于天花板:你是开销受限,或者你的工作规模太小,填不满芯片。看看你启动了多少个独立操作,以及每一个有多大。

关于优先看哪里的说明。对于语言模型服务,生成阶段在几乎所有现实配置中都是内存受限的。如果你在优化 Token 吞吐,而还没有审视过批大小、精度和 KV cache 大小,那这三项几乎总是比你能尝试的其他任何东西都重要。

硬件变化时,什么仍然成立

规范数字变化很快。值得弄清楚本文里哪些部分需要你重新学习,哪些不需要。

会变的是数字。内存容量、带宽和算术吞吐每一代都在增长,其中算术增长最快。新的精度格式不断出现,每一代都比上一代更小,因为缩减字节是对瓶颈最直接的打击。

不会变的是形状。算术仍然远比数据搬运便宜;内存梯子仍然保留它的层级;每字节工作量仍然决定你会撞上哪一面天花板。

真要说什么的话,这个框架会随着时间推移越来越有用。因为算术能力比带宽增长得更快,平衡比值不断上升。在旧硬件上算力受限的工作负载,换到新硬件上不用改一行代码就变成内存受限。

这是值得押注的方向。减少数据搬运的技术价值会持续上升,而规范表上的峰值算术数字,会持续成为文档里最没有预测力的那个数字。

结论

在本文中,我们从一个不对称性出发,建立了对 GPU 工作方式的直觉:算术很便宜,搬运数据很贵。

我们看到了为什么这个不对称性产生了一颗装满数千个简单算术单元、共享一个控制器的芯片,而不是少数几个精巧的单元。我们看到 GPU 不会缩短等待数据的时间,而是通过保持装载远超它能运行的工作量,并在它们之间瞬时切换,来隐藏等待。

我们走了一遍内存梯子,从寄存器文件经过共享内存和 L2 直到 HBM,每一级都比上一级更大、更难以访问。我们还看到了这些层级的位置:最上面两级由每个 SM 独占,往下所有层级要么整颗芯片共享,要么完全在芯片之外。

这把我们带到了那个核心数字:每取回一个字节所完成的工作量。每颗芯片都有一个平衡比值,在当前 16 位精度硬件上大约是每字节 300 次运算。低于它,你受限于内存;高于它,你受限于算术。

把它应用到语言模型上,开篇的谜团就解开了。生成一个 Token 会读取每一个权重,并对每个权重做大约两次运算,得到的比值接近 1,比平衡点低了大约三百倍。Prefill 则落在分界线的另一侧,这就是为什么两个阶段表现如此不同。

最后,我们看到了每个著名的优化都属于两种动作之一:批处理和分块(Tiling)增加每次取回所做的工作;融合、量化和更好的内存布局减少取回的字节数。

我们刻意没有涉及编写 GPU 代码的底层细节,因为直觉必须先行。在没有这个框架的情况下写内核,往往会产生这样的代码:快得作者无法解释原因,慢得作者找不到原因。

后续文章将在此基础上进一步展开、变得更具体,包括如何对照平衡线阅读 profiler 输出、随着上下文增长 KV cache 如何改变内存图景、以及服务引擎在决定如何批处理时内部到底发生了什么。

目标一如既往:建立扎实的系统级视角,为你配备一个适应力强的工程框架,用来构建健壮且可维护的 AI 系统。

照例,感谢阅读!

干杯!:)

  • 原文链接: x.com/akshay_pachaar/sta...
  • 鸿途知科网 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
版权声明

本文仅代表作者观点,不代表区块链技术网立场。
本文系作者授权本站发表,未经许可,不得转载。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门