
为什么 DeepSeek-V3 据说在大规模服务时快速且便宜,但本地运行时却太慢且昂贵?为什么有些 AI 模型响应很慢,但一旦开始运行就变得很快?
AI 推理服务提供商经常讨论吞吐量和延迟之间的一个基本权衡:对于任何给定的模型,你要么以高吞吐量和高延迟提供服务,要么以低吞吐量和低延迟提供服务。实际上,有些模型天生是 GPU 效率比较低的,以至于在实践中它们必须以高延迟提供服务,才能有可行的吞吐量(例如 DeepSeek-V3)。
这种权衡来自于推理服务提供商为模型选择的 批处理大小:不是在单个请求内进行批处理推理,而是跨数十或数百个并发用户请求进行批处理。基于 Transformer 的大型语言模型的一个奇特特性是,同时计算一批补全几乎和计算单个补全一样快。为什么会这样?
GPU 擅长执行大型矩阵乘法(GEMMs,或“通用矩阵乘法”)。假设你有一个 token,你想通过模型传递(即通过乘以其所有权重,其他架构细节不谈)。你将其表示为一个与模型维度(或隐藏大小)相匹配的向量(即 1x 其大权重矩阵的宽度),然后将其乘过去。那就是 1 个 GEMM。但如果你想一次通过一批十个 token,也仍然只是一个 GEMM,因为你可以将 token 堆叠成一个矩阵(10x 模型维度)。这比执行十个稍小的 GEMM 要快得多。因此,推理服务器的实现可能看起来是这样的:
-
一个请求带着提示词进来 -
该提示被预填充(通过注意力传递,我们稍后将看到如何将它也做批处理),形成一个 KV 缓存和一个 token 大小的矩阵(1x 模型大小),最终成为预测的 token -
那个 token 大小的矩阵进入一个队列 -
GPU 服务器从该队列中拉取一些批次(例如 128 个),将它们堆叠成一个 128x 模型大小的矩阵,并通过前馈模型权重进行乘法运算 -
最终结果被分割成 128 个单独的 token -
针对原始请求的那个 token 被流回给用户 -
假设那个 token 不是序列结束 token,返回步骤 2 以继续生成响应中的下一个 token
请注意,服务器会决定拉取多大的批次大小。这是吞吐量和延迟之间的权衡。如果你不进行批处理,只是逐个处理 token,那么没有用户会在队列中等待(上述步骤 3),所以延迟很低(假设你有足够的 GPU)。然而,如果你进行大量批处理,延迟会很高,因为用户将等待到批次大小填满,但吞吐量会高得多,因为 GPU 的使用效率更高。
为什么 GPU 在一次乘以大型矩阵时比多次乘以小型矩阵更快?有两个原因。首先,向 GPU 发出每个命令都涉及一些开销,而一个大乘法可以用单个命令启动。其次,每个新的 GPU 命令都要从内存中获取权重,这对于大型权重来说可能很昂贵。如果你运行很多小 GEMMs,你最终可能会花费大部分时间在内存中进出权重,而不是进行计算。
通常,推理服务器会有一个“收集窗口”,用户请求送进这个窗口并被排队。聊天服务器通常的延迟目标是 5-10 毫秒,但非常高的批次后端可能会达到 200 毫秒。如果一个新请求在窗口开始时进来,它可能需要等待整个窗口的持续时间后才能被处理。当窗口关闭时,所有排队的请求都被批处理(即所有 1x 模型大小的矩阵被连接成一个单一的 128x 模型大小的矩阵),然后该批次通过管道发送。像这样运行一个批次有时被称为一个“tick”。
正如上述解释所建议的,你可以在任何批次大小下运行任何模型。批处理过程本身并没有什么限制会排除某些类型的模型。然而,我们有可能构建一个模型,使其 GPU 效率如此低下,以至于实际上它需要批处理才能满足实用要求。
例如,考虑一个专家混合模型(如 DeepSeek-V3 或据说是原始的 GPT-4 所使用的机制)。你可以让它拥有数百个“专家”来获得一个强大的模型。这些“专家”是独立的前馈权重块,路由层从中选择一个子集用于每个 token。但这样的模型真的对 GPU 效率很差。原因在于:GPU 想要执行少量的大型矩阵乘法,但如果有很多专家,你会被迫做很多小型乘法。除非你以整个批次进行推理,否则这意味着吞吐量很低。
我们考虑一下 5 毫秒和 200 毫秒的“收集窗口”对于大型专家混合模型的表现如何。假设你在那个 5 毫秒窗口中接收到十个用户请求。如果你有很多专家,一些专家可能最终只对一个或两个 token 运行(即每个专家的批次大小将远低于你在窗口中接收到的总请求集)
对于大型模型来说,保持 GPU 始终活跃可能是一个挑战。大型模型通常有很多 transformer 层:即组成前馈网络的数百个权重矩阵。在这里进行快速推理的唯一方法是管道化这些层,让一个 GPU 处理前十个层,另一个处理接下来的十个层,依此类推。否则,你根本无法将所有权重放入单个 GPU 的内存中,这样你会花费大量时间在内存中交换权重,最终会变得非常慢。在推理过程中,每个 token(通常位于每个包含几十个 token 的“微批次”中)会顺序通过 GPU 管道。
你的管道效率取决于你拥有的层数和你的收集窗口大小。当你在“tick”期间处理窗口中的 token 时,你会在开始时有一些空闲的 GPU(因为后层的 GPU 还没有输入可以操作),在结束时会有更多的空闲 GPU(当队列中没有更多的 token 时,早期层的 GPU 将不得不等待下一个“tick”)。这些空闲期有时被称为“预热”和“排水”。如果你有很多小窗口,你将比拥有较少大窗口时花费更多的 GPU 时间在预热和排水上。通过选择你的窗口大小,你就能直接在吞吐量和延迟之间进行权衡。
如果你有很多层,你的收集窗口非常短,有时最终处理的 token 数量可能少于层数。这被称为“管道气泡”——实际上,“排水”阶段比平时更早开始。你不能消除预热和排水(由于下面讨论的原因,推理必须以顺序“tick”操作),但可以让收集窗口足够长来消除管道气泡。管道气泡可能对模型吞吐量造成极大的影响,因此推理提供商总是设置足够宽的窗口以避免它们。这为具有许多层的模型增加了明显的延迟。
为什么推理提供商不能通过保持 GPU 队列满载来完全消除预热和排水?换句话说,你不能完全摆脱 tick,而只是让很多 token 微批次持续流动下去?当然,每个用户的推理必须是顺序的(因为你不能在当前 token 完成之前开始生成下一个 token),但大型推理提供商应该有足够的并发流量来保持队列满载。
我承认我在理论上看不出为什么这是不可能的。据我所知,实际障碍是如何批量处理注意力步骤:如果你想批量处理注意力 GEMMs,它们需要都是相同的形状(即序列中有相同数量的先前 token)。所以你不得不同时运行相同形状的组,而不能只维护一个队列。在这方面至少有一些公开的研究(https://arxiv.org/abs/2403.02310),但如果有更多我没见过的更聪明的技巧来做这件事,我不会感到惊讶。
另一个想法:如果你需要 tick 来处理注意力步骤,为什么不只使用基于 tick 的注意力推理系统,以及更高效的连续系统用于 FFN?据我了解,原因是内存开销:
-
由于 FFN 需要注意力输出,你需要在内存中有一个位置来停放它,同时等待其在 FFN 队列中的插槽,这将很快变得过于昂贵。 -
现代推理栈能够将注意力和 FFN 步骤合并成几个大 GEMMs 放在一个“操作”中。如果你在不同的 GPU 上进行这些操作,你必须运行不同的操作并来回移动权重。
-
GPU 在大型 GEMMs 上最高效,因此将许多 token 堆叠到一个矩阵乘法中,可以比逐个处理它们获得更高的 token 吞吐量。 -
在解码过程中,注意力只能为同一步骤的 token 批量处理,迫使调度器以短“tick”运行。你将多少 token 打包到一个“tick”中(即你等待收集 token 的时间),就是你的批次大小。这些 token 来自不同的用户。你不能从同一个用户批量处理 token,因为你需要之前的 token 来生成下一个 token,所以批处理需要不同用户组成的高流量。 -
更大的批次增加了延迟,因为用户 token 可能需要等待多达 200 毫秒,直到批次足够大才能开始运行,但它们通过允许 FFN 步骤中更大的(因此更有效的)GEMMs 来提高吞吐量。 -
具有许多层(例如长管道)的模型需要更大的批次以避免管道气泡(确保每个 tick 包含的批次多于管道步骤)。 -
混合专家模型需要以高延迟提供服务以提升效率:每个专家只看到路由到它的 token,所以你需要的是更大的全局批次以让每个专家都忙碌起来。 -
推理提供方选择一个批次大小 / 窗口,以清除流水线泡沫并让专家饱和工作。高批次大小可以为你提供更多吞吐量,但代价是更高的延迟,因为 token 需要等待填充。 -
一些模型(如 DeepSeek)是具有许多层的专家混合模型,因此需要大批次大小和高延迟,否则吞吐量会急剧下降。这就是为什么说 DeepSeek 很难在个人场景中使用:因为只有一个用户一次运行一个推理,它的运行效率 / 吞吐量非常低。 -
OpenAI 和 Anthropic 的模型响应迅速这一事实表明,要么:他们的模型具有更高效的架构(非 MoE,更少的层),或者 OpenAI/Anthropic 有一些非常巧妙的推理服务技巧,或者他们支付了高昂的费用,购买了比他们所需的量更多的 GPU。
编辑:这篇文章发表在 Hacker News 上,并附有一堆评论。我有点希望我给这篇文章起了另一个名字——它实际上讲的不是在自己的计算机上运行模型,而讲的是为个人使用场景运行模型,假设你拥有所有 GPU(即批次 / 吞吐量权衡)。
-
人们观察到的 transformer 的一个常见优势是,它们可以在单个用户请求内批量预填充。当你给它们一个长提示时,它们可以一次性处理该提示,因为注意力机制的工作原理就是这样。以前的递归模型必须逐 token 进行,这要慢得多(因为它涉及更多的 GEMMs)。这与我在这篇文章中谈论的批处理无关。我谈论的是如何在预填充完成后,有效地跨许多不同的用户请求来批量推理。 -
只要你只批处理具有相同数量 token 序列的注意力操作(例如每个序列预测第四个 token 可以一起批量处理),这也可以实现。否则,KV 缓存矩阵的大小是不一样的,因此你不能轻松地将它们组合成一个批次。 -
技术上,它不是一个被生成的 token,而是“logits”(所有可能 token 的概率分布)。为了保持简单性,我在这里和以后会说“token”。 -
注意,在实践中,现代推理栈将使用“连续批处理”,一旦批次满就发送,而不是等待固定时间窗口的全部时间。然而,推理仍是批量进行的,核心的吞吐量和延迟之间的权衡是相同的。
如果你喜欢这篇文章,请考虑订阅我的新文章的电子邮件更新(https://buttondown.com/seangoedecke)。
原文链接:
https://www.seangoedecke.com/inference-batching-and-deepseek/
声明:本文由 InfoQ 翻译,未经许可禁止转载。
后续我将通过微信视频号,以视频的形式持续更新技术话题、未来发展趋势、创业经验、商业踩坑教训等精彩内容,和大家一同成长,开启知识交流之旅
欢迎扫码关注我的微信视频号~

今日荐文
李飞飞曝创业招人标准!总结AI 大牛学生经验,告诫博士们不要做堆算力项目
Altman嘲讽小扎挖走的都不是顶尖人才!OpenAI高管再营业曝内幕:ChatGPT爆红后,我火速升职了!
跳槽实现财富自由!小扎千万年薪快要“掏空”OpenAI核心人才,还高调“晒”挖人成绩单:各栈大牛,近70%是华人
华为盘古大模型开源,推理方案、基础代码全公开!
老黄亲自挖来两名清华天才;字节 Seed 机器人业务招一号位;清华北大浙大中科大校友跳槽去Meta | AI周报

你也「在看」吗?👇
(文:AI前线)