为什么DeepSeek规模化运行成本低,但本地运行成本高昂
为什么DeepSeek-V3大规模运行起来速度快、成本低,但在本地运行却速度慢、成本高?为什么有些AI模型反应慢,但一旦启动起来却很快?
AI 推理提供商经常讨论吞吐量和延迟之间的基本权衡:对于任何给定的模型,要么以高吞吐量高延迟运行,要么以低吞吐量低延迟运行。事实上,有些模型天生 GPU 效率低下,实际上必须以高延迟运行才能获得可行的吞吐量(例如 DeepSeek-V3)。
这种权衡源于推理提供商为模型选择的批次大小:不是在单个请求内批量推理,而是在数十或数百个并发用户请求之间批量推理。基于 Transformer 的 LLM 有一个独特的特性,即同时计算一批完成项几乎与计算单个完成项一样快。这是为什么呢?
什么是批量推理?
GPU 擅长执行大矩阵乘法(GEMM,即“通用矩阵乘法”)。假设你有一个 token 需要传递给模型(即与其所有权重相乘 – 其他架构细节不相关)。你可以将其表示为一个与模型维度(或隐藏层大小)匹配的向量(即 1 x 其大权重矩阵的宽度),然后将其乘以 1。这就是 1 个 GEMM。但是,如果你想批量传递 10 个 token,那仍然只需要 1 个 GEMM,因为你可以将这些 token 堆叠成一个矩阵(10 x 模型维度)。这比执行 10 个略小的 GEMM 要快得多。因此,推理服务器实现可能如下所示:
- 请求带有提示
- 该提示是预先填充的(通过注意力机制传递 – 我们稍后会看到如何对其进行批处理2),形成一个 KV 缓存和一个标记大小的矩阵(1 x 模型大小),最终将成为预测的标记3
- 该令牌大小的矩阵进入队列
- GPU 服务器从该队列中拉取批次(例如 128 个),将它们堆叠成 128 x 模型大小的矩阵,然后将它们乘以前馈模型权重
- 最终结果被拆分成 128 个独立的 token
- 原始请求的数据流将返回给用户
- 假设该 token 不是序列结束 token,则返回步骤 2 继续生成响应中的下一个 token
请注意,服务器决定拉取的批次大小。这是吞吐量和延迟之间的权衡。如果不进行批次处理,而只是逐个处理令牌,则没有用户会在队列中等待(上述第 3 步),因此延迟较低(假设您拥有足够的 GPU)。但是,如果进行大量批次处理,延迟会很高,因为用户需要等待直到批次大小填满,但吞吐量会更高,因为 GPU 的利用效率更高。
为什么 GPU 执行大型矩阵的一次乘法比执行小型矩阵的多次乘法更快?原因有两个。首先,向 GPU 发出每个命令都会产生一些开销,而一次大型乘法只需一个命令即可完成。其次,每个新的 GPU 命令都涉及从内存中获取权重,这对于大型权重来说可能代价高昂。如果您运行大量小型 GEMM,最终可能会将大部分时间花在将权重传入和传出内存上,而不是用于计算。
为什么有些模型针对大批量大小进行了调整?
通常,推理服务器会有一个“收集窗口”,用户请求在此进入并排队。聊天服务器通常的目标是 5-10 毫秒,但批量处理能力极强的后端可能会长达 200 毫秒。如果新请求在窗口开始时进入,它可能需要等待整个窗口持续时间才能被处理4。当窗口关闭时,所有排队的请求都会被分批处理(即,所有 1xmodel 大小的矩阵会被连接成一个 128xmodel 大小的矩阵),并且该批次会通过流水线发送。像这样运行批处理有时被称为“tick”。
正如上面的解释所示,您可以以任意批次大小运行任何模型。批处理过程本身并不会排除某些类型的模型。然而,构建一个 GPU 效率极低的模型是有可能的,以至于实际上需要批处理才能实用。
为什么专家混合模型需要更高的批处理
例如,以混合专家模型(例如 DeepSeek-V3 或所谓的原始 GPT-4)为例。你可以通过训练数百名“专家”来获得一个强大的模型:这些专家是独立的前馈权重块,路由层会从中选择一个子集用于每个 token。但这样的模型在 GPU 上效率极低。原因显而易见:GPU 倾向于执行少量非常大的矩阵乘法,但如果拥有许多专家,就不得不进行许多小的乘法运算。除非你批量进行推理,否则这将意味着低吞吐量。
让我们思考一下,对于一个大型混合专家模型,5 毫秒和 200 毫秒的“收集窗口”会如何表现。假设您在 5 毫秒的窗口内收集了 10 个用户请求。如果您拥有许多专家,那么有些专家最终可能只针对一两个令牌运行(即每个专家的批处理大小将远低于您在窗口中收集的总请求集)。但是,如果您等待 200 毫秒并收集了 4000 个用户请求,则更有可能使所有专家都达到饱和状态。以一些延迟为代价,您可以确保 GEMM 足够大,并且 GPU 始终以最大容量得到利用。
为什么大型管道需要高批量以避免管道泡沫
对于大型模型,保持 GPU 处于活动状态可能是一个挑战。大型模型通常具有许多 Transformer 层:即构成前馈网络的数百个权重矩阵。在这里进行快速推理的唯一方法是通过让一个 GPU 处理前十层,另一个 GPU 处理接下来的十层,依此类推,将这些层流水线化。否则,您将无法将所有权重都放入单个 GPU 的内存中,因此您将花费大量时间在内存中交换权重,最终会变得非常慢。在推理过程中,每个标记(通常每个标记包含几十个标记的“微批次”)按顺序通过该 GPU 流水线。
管道的效率取决于层数和收集窗口的大小。在“tick”期间处理窗口中的令牌时,开始时会有一些空闲的 GPU(因为后面层的 GPU 还没有任何工作要做),结束时会有更多的空闲 GPU(当队列中没有更多令牌时,前面层的 GPU 将不得不等待下一个“tick”)。这些空闲期有时称为“预热”和“耗尽”。如果您有许多小窗口,那么与大窗口较少相比,您将花费更多的 GPU 时间进行预热和耗尽。通过选择窗口大小,您可以直接在吞吐量和延迟之间进行权衡。
如果您有大量层,而收集窗口又非常短,有时最终需要处理的令牌数量可能会少于层数。这被称为“管道气泡”——实际上,“耗尽”阶段比平时开始得更早。您无法消除预热和耗尽,但可以通过延长收集窗口来消除管道气泡。管道气泡会严重影响模型吞吐量,因此推理提供商始终会将窗口设置得足够宽以避免气泡。这会给具有多层的模型带来明显的延迟。
你不能让队列保持满员吗?
为什么推理提供商不能通过保持 GPU 队列满满的令牌来完全消除预热和消耗?换句话说,难道不能完全取消 tick,只保持令牌微批次的流动吗?当然,每个用户的推理必须是连续的(因为在当前令牌完成之前无法开始生成下一个令牌),但大型推理提供商应该拥有足够的并发流量,以保持队列充满单独的用户请求。
我很难理解为什么这在理论上不可能实现。据我所知,实际的障碍在于注意力机制步骤的批处理方式:如果要批量处理注意力机制 GEMM,它们必须具有相同的形状(即序列中先前标记的数量相同)。因此,你必须同时运行相同形状的组,而不是仅仅维护一个队列。在这方面至少有一些公开的研究,但如果有更多我未曾见过的巧妙技巧可以做到这一点,我也不会感到惊讶。
另一个想法:如果注意力步骤需要 tick,为什么不干脆用一个基于 tick 的注意力推理系统,再用一个更高效的 FFN 连续系统呢?据我理解,原因在于内存开销:
- 由于 FFN 需要注意力输出,因此您需要在内存中留出一些位置来存放它,以便它等待 FFN 队列中的插槽,这很快就会变得过于昂贵。
- 现代推理堆栈能够将注意力机制和 FFN 步骤合并到单个“操作”中的几个大型 GEMM 中。如果在不同的 GPU 上执行这些操作,则必须运行不同的操作,并将权重传入和传出内存。
概括
- GPU 在大型GEMM上效率最高,因此将许多 token 堆叠到单个矩阵乘法中比逐个处理它们提供更高的 token 吞吐量
- 在解码过程中,注意力机制只能在同一步对 token 进行批处理,这迫使调度程序以较短的“滴答”运行。在一个“滴答”中打包的 token 数量(即等待收集 token 的时间)就是批处理大小。
- 这些是来自不同用户的令牌。您无法批量处理来自同一用户的令牌,因为您需要先前的令牌来生成下一个令牌,因此批量处理需要来自不同用户的大量流量
- 更大的批次会增加延迟,因为用户令牌可能需要等待长达 200 毫秒才能使批次足够满以运行,但它们通过在前馈步骤中允许更大(从而更高效)的 GEMM 来提高吞吐量
- 具有多层(例如长管道)的模型需要更大的批次以避免管道气泡(通过确保每个刻度包含的批次多于管道步骤)
- 混合专家模型需要高延迟才能高效:每个专家只能看到路由到它的令牌,因此您需要更大的全局批次来让每个专家都忙碌起来。
- 推理提供商会选择一个能够消除流水线气泡并让专家饱和的批处理大小/窗口。较高的批处理大小可以带来更高的吞吐量,但代价是更高的延迟,因为令牌需要等待填满周期。
- 一些模型(例如 DeepSeek)采用混合专家模型,且具有多层结构,因此需要较大的批量大小和较高的延迟,否则吞吐量会急剧下降。这就是为什么人们常说 DeepSeek 不适合个人使用:因为单个用户每次只运行一个推理,其运行效率/吞吐量非常低。
- OpenAI 和 Anthropic 的模型能够快速做出反应,这表明:
- 他们的模型具有更高效的架构(非 MoE、更少的层数),或者
- OpenAI/Anthropic 有一些非常巧妙的推理技巧,或者
- 他们花费巨资购买了远远超出实际需要的 GPU
在个人使用的情况下运行模型,假设你拥有所有 GPU(也就是批处理/吞吐量的权衡)。
Transformer 的一个常见优势是,它们可以在单个用户请求中进行批量预填充。当你向它们传递一个长提示时,由于注意力机制的运作,它们可以一次性处理该提示。之前的循环模型必须逐个标记地进行处理,这要慢得多(因为它涉及更多的 GEMM)。这与我在本文中讨论的批处理类型无关。我讨论的是预填充完成后,如何在多个不同的用户请求之间高效地进行批量推理。
这也可以进行批处理,只要你只批量处理序列中相同数量的标记(即,每个预测第四个标记的序列都可以一起进行批处理)。否则,由于 KV 缓存矩阵的大小不同,你无法轻松地将它们合并到单个批次中。
从技术上讲,生成的不是 token,而是“logits”(所有可能 token 的概率分布)。
请注意,在实践中,现代推理堆栈将使用“连续批处理”,即批处理一旦填满就会立即发送,而不是等待整个固定时间窗口。然而,推理仍然是分批进行的,吞吐量和延迟之间的核心权衡是相同的。