以下文章来源于MaxAIBox,作者Max
最近刷 HackerNews,看到了这篇引发热议的技术文章。
英文作者工程师小哥 Sean Goedecke 目前在 GitHub 就职。
说明:本文讨论的并非在个人电脑上运行模型,而是假设你拥有所有所需的 GPU 时,如何将模型用于个人用途(即批量处理与吞吐量之间的权衡)。
为什么 DeepSeek 大规模部署成本低,但本地运行昂贵?
为何 DeepSeek-V3 模型在大规模部署时看似快速且成本低廉,然而在本地运行却既缓慢又昂贵?为何部分 AI 模型响应迟缓,但一旦启动就运转迅速?
AI 推理服务提供商常提及吞吐量与延迟之间的根本性权衡:对于任何给定模型,要么以高吞吐量、高延迟的方式提供服务,要么以低吞吐量、低延迟的方式运行。
实际上,某些模型天生对 GPU 利用率低,以至于在实践中,为了获得可行的吞吐量,必须以高延迟的方式提供服务(例如 DeepSeek-V3)。
这种权衡源于推理服务提供商为模型选择的批量大小:并非在单个请求内进行批量推理 [注1] ),而是在数十或数百个并发用户请求之间进行批量推理。基于 Transformer 的大型语言模型(LLM)有一个独特特性,即同时计算一批补全内容的速度几乎与计算单个补全内容的速度一样快。这是为什么呢?
注1:Transformer 的一个常见优势是,它们可以在单个用户请求内批量进行预填充。当向它们传递长提示时,由于注意力机制的工作方式,它们可以一次性处理该提示。以前的循环模型必须逐个令牌处理,这要慢得多(因为涉及更多 GEMM)。这与本文中我所讨论的批量处理无关,我讨论的是预填充完成后,如何跨多个不同用户请求高效地进行批量推理。
什么是批量推理?
GPU 擅长进行大规模矩阵乘法(GEMM,即“通用矩阵乘法”)。假设你有一个令牌需要通过模型处理(即与模型的所有权重相乘——其他架构细节无关紧要),你将其表示为一个与模型维度(或隐藏层大小)匹配的向量(即 1 × 模型权重矩阵的宽度),然后进行乘法运算,这就是 1 次 GEMM。但如果你想批量处理十个令牌,仍然只需一次 GEMM,因为你可以将这些令牌堆叠成一个矩阵(10 × 模型维度),这比进行十次稍小的 GEMM 要快得多。
因此,推理服务器的实现可能如下:
1、 接收到带有提示的请求;
2、 对该提示进行预填充(通过注意力机制处理——我们稍后会看到这也可以进行批量处理 [注2]),形成 KV 缓存和一个令牌大小的矩阵(1×模型大小),该矩阵最终将成为预测的令牌 [注3];
3、 该令牌大小的矩阵进入队列;
4、 GPU 服务器从队列中提取批次(例如 128 个),将它们堆叠成一个 128×模型大小的矩阵,并通过前馈模型权重进行乘法运算;
5、 最终结果随后被拆分为 128 个独立的令牌;
6、 与原始请求对应的令牌被流式传输回用户;
7、 假设该令牌不是序列结束令牌,则返回步骤 2,继续生成响应中的下一个令牌。
注2:这也可以进行批量处理,只要仅对序列中具有相同令牌数的注意力操作进行批量处理(即每个预测第四个令牌的序列可以一起批量处理)。否则,KV 缓存矩阵的大小不同,无法轻松将它们组合成单个批次,稍后会有更多相关内容。
注 3:从技术上讲,生成的不是令牌,而是 “对数几率”(所有可能令牌的概率分布),在此及之后为了简化,我将其称为 “令牌”。
请注意,服务器决定提取的批量大小,这是吞吐量和延迟之间的权衡。如果不进行批量处理,仅逐个处理令牌,用户无需在队列中等待(如上述步骤 3),因此延迟较低(假设拥有足够的 GPU)。然而,如果进行大量批量处理,延迟会很高,因为用户需要等待批量大小填满,但吞吐量会高得多,因为 GPU 的使用效率更高。
为何 GPU 一次性乘以大矩阵比多次乘以小矩阵更快?原因有二。首先,向 GPU 发出每个命令都存在一定开销,而一次大型乘法可以通过单个命令启动。其次,每个新的 GPU 命令都涉及从内存中获取权重,对于大权重而言,这可能成本高昂。如果运行大量小型 GEMM,最终可能大部分时间都花在将权重移入和移出内存上,而非计算。
为何有些模型针对高批量大小进行优化?
通常,推理服务器会有一个“收集窗口”,用户请求会进入该窗口并排队。聊天服务器的目标通常是 5-10 毫秒,但超高批量的后端可能宽至 200 毫秒。如果一个新请求在窗口开始时到达,它可能需要等待整个窗口持续时间才能被处理 [注4]。当窗口关闭时,所有排队的请求都会被批量处理(即所有 1×模型大小的矩阵 被连接成一个 128×模型大小的矩阵),并将该批次发送通过管道。运行这样的批次有时被称为“滴答(周期)”。
注 4:实际上,现代推理栈将使用“连续批量处理”,即一旦批次填满就立即发送,而不是等待固定时间窗口的整个长度。然而,推理仍然是批量进行的,因此吞吐量和延迟之间的核心权衡是相同的。
如上所述,你可以在任何批量大小下运行任何模型,批量处理过程本身并不排除某些类型的模型。然而,确实有可能构建一个对 GPU 效率极低的模型,实际上需要批量处理才能实用。
专家混合模型(MoE)为何需要更高的批量大小?
例如,以专家混合模型(如 DeepSeek-V3 或据称最初的 GPT-4)为例。通过训练使其拥有数百个“专家”(独立的前馈权重块,路由层从中为每个令牌选择一个子集使用),可以获得强大的模型。但这种模型对 GPU 效率确实很低。我们可以看到原因:GPU 希望进行少量真正的大型矩阵乘法,但如果有许多专家,就不得不进行许多小型乘法。除非进行批量推理,否则这将意味着低吞吐量。
让我们思考一下,对于大型专家混合模型,5 毫秒和 200 毫秒的“收集窗口”表现如何。假设在 5 毫秒的窗口内接收到十个用户请求。如果有许多专家,某些专家可能最终仅针对一两个令牌运行(即每个专家的批量大小将远低于在窗口中接收到的请求总数)。然而,如果等待 200 毫秒并接收到 4000 个用户请求,则更有可能使所有专家饱和。以一定延迟为代价,可以确保 GEMM 较大,并且 GPU 始终以最大容量运行。
大型管道为何需要高批量大小以避免管道气泡?
对于大型模型,保持 GPU 始终处于活动状态可能是一个挑战。大型模型通常有许多 Transformer 层,即构成前馈网络的数百个权重矩阵。在此实现快速推理的唯一方法是通过让一个 GPU 处理前十个层,另一个 GPU 处理接下来的十个层等方式对这些层进行管道处理。否则,将无法将所有权重装入单个 GPU 的内存中,从而花费大量时间在内存中交换权重,最终导致速度非常慢。在推理过程中,每个令牌(通常在几十令牌的“微批次”中)依次通过该 GPU 管道。
管道的效率取决于拥有的层数和收集窗口的大小。在“滴答”期间处理窗口中的令牌时,开始时会有一些 GPU 处于空闲状态(因为后续层的 GPU 尚无工作可做),结束时会有更多 GPU 处于空闲状态(当队列中没有更多令牌时,早期层的 GPU 必须等待下一个“滴答”)。这些空闲时期有时被称为“预热”和“排水”。如果有许多小窗口,与有较少大窗口相比,将花费更多 GPU 时间在预热和排水上。因此,通过选择窗口大小,直接在吞吐量和延迟之间进行权衡。
如果有大量层且收集窗口非常短,有时处理的令牌数可能少于层数,这被称为“管道气泡”——实际上,“排水”阶段比平时更早开始。无法消除预热和排水(由于下面讨论的原因,推理必须以顺序“滴答”的方式运行),但可以通过使收集窗口足够长来消除管道气泡。管道气泡对模型吞吐量的影响可能极大,因此推理服务提供商总是将窗口设置得足够宽以避免它们,这会给具有许多层的模型增加明显的延迟。
能否让队列保持满员?
为何推理服务提供商不能通过让 GPU 队列充满令牌来完全消除预热和排水?换句话说,能否完全取消“滴答”,让令牌微批次持续流动?当然,每个用户的推理必须是顺序的(因为在当前令牌完成之前,无法开始生成下一个令牌),但大型推理服务提供商应该有足够的并发流量来使队列充满独立的用户请求。
我承认,从理论上看,这为何不可行让我感到困惑。据我所知,实际障碍在于注意力步骤的批量处理方式:如果要对注意力 GEMM 进行批量处理,它们需要具有相同的形状(即序列中先前令牌的数量相同)。因此,必须同时运行相同形状的组,而不能仅维护单个队列。至少在这方面有一些公开研究,但如果有更多我未见过的巧妙技巧来实现这一点,我也不会感到惊讶。
另一个想法:如果注意力步骤需要“滴答”,为何不为注意力推理系统采用基于“滴答”的方式,而为前馈网络(FFN)采用更高效的连续系统?
据我理解,原因在于内存开销:
由于注意力输出是 FFN 所需的,因此需要在内存中某个位置暂存它,等待其在 FFN 队列中的位置,这很快就会变得成本过高;
现代推理栈能够将注意力和 FFN 步骤合并为单个“操作”中的几个大型 GEMM。如果在不同的 GPU 上进行这些操作,则必须运行不同的操作并将权重移入和移出内存。
总结
GPU 在大型 GEMM 上效率最高,因此将许多令牌堆叠到单个矩阵乘法中,比逐个处理令牌可获得更高的令牌吞吐量;
在解码期间,注意力只能对同一步骤的令牌进行批量处理,迫使调度程序在短“滴答”中运行。在单个“滴答”中打包的令牌数(即等待收集令牌的时间)就是批量大小,这些令牌来自不同用户。无法对同一用户的令牌进行批量处理,因为需要先前的令牌来生成下一个令牌,因此批量处理需要来自不同用户的高流量;
更大的批量会增加延迟,因为用户令牌可能需要等待长达 200 毫秒,直到批量足够大才能运行,但通过在前馈步骤中允许更大(因而更高效)的 GEMM,可提高吞吐量;
具有许多层的模型(例如长管道)需要更大的批量来避免管道气泡(通过确保每个“滴答”包含的批次多于管道步骤数);
专家混合模型需要以高延迟提供服务才能高效:每个专家仅看到路由给它的令牌,因此需要更大的全局批量来使每个专家保持忙碌;
推理服务提供商选择一个批量大小/窗口,以消除管道气泡并使专家饱和。高批量大小以令牌等待填满“滴答”而增加延迟为代价,为你带来更高的吞吐量;
某些模型(如 DeepSeek 的模型)是具有许多层的专家混合模型,因此需要大批量大小和高延迟,否则吞吐量会急剧下降。这就是为什么人们常说无法轻松地将 DeepSeek 用于个人用途:因为单个用户一次运行一次推理时,其运行效率/吞吐量非常低;
OpenAI 和 Anthropic 的模型响应迅速,这一事实表明:要么它们的模型架构更高效(非 MoE,层数更少),要么 OpenAI/Anthropic 在提供推理服务方面有一些非常巧妙的技巧,要么它们为远远超过严格所需的 GPU 支付了高昂的费用。
网友评论
ryan_glass
我在本地将 DeepSeek V3 作为日常使用的模型,发现它成本可控、速度快且效果好。本文假设需要 GPU,但在我看来,这并非在本地运行此类大型模型的最佳方式。我在超微主板上搭建了一台基于中端 EPYC 9004 系列的家用服务器,总成本约 4000 美元。
这是一台单 CPU 机器,配备 384GB 内存(如果使用 64GB 内存条可扩展至 768GB,但成本更高)。无 GPU 意味着功耗比游戏台式机更低。在内存限制下,我运行了 Unsloth Dynamic GGUF 格式的模型,实际使用中质量与原版非常接近。该模型约 270GB,为上下文预留了充足空间——我通常运行 16K 上下文(因为这台机器还用于其他任务),但如果需要可提升至 24K。我每秒可处理约 9-10 个令牌,上下文较大时降至 7 令牌/秒。许多使用双 CPU 类似配置的用户,能以相近的令牌/秒速度运行完整版本模型。
angry_octet
这是一篇从 LLM 视角出发的绝佳解释性文章,若能深入探讨计算调度的原理会更有意思。我推测超大规模 LLM 公司会深入分析计算轨迹以识别瓶颈和空闲气泡,并开发负载均衡器、管道架构和调度器来优化工作负载。
批量处理对效率的要求使高安全性应用面临巨大挑战,因为将不相关查询隔离的常规技术成本会变得极高。英伟达 vGPU 虚拟化技术对 GPU 内存进行分时共享,但每次切换都需要卸载/重新加载上下文,且不确定是否具备重复数据删除功能。多实例 GPU(MIG)虽然能在用户间划分GPU内存,但这是一种固定分区方案(必须重启 GPU 才能更改配置),没人愿意将 96GB 的 GPU 拆分为 4 个 24GB 的实例。
这让我思考:在 GPU 板上集成二级内存(如普通 DRAM)是否可行?这样不同矩阵数据的加载速度会比通过 PCIe 更快,HBM(高带宽内存)则可作为缓存。这种设计或许能在成本与效率间找到新平衡。
ipieter
这是一篇有趣的博文。虽然“需要批量处理”这一总体结论没错,但专家混合模型(MoE)的推理过程实际上更为复杂。
我们需要大批量处理的主要原因在于,LLM 推理的瓶颈并非计算能力,而是从显存(VRAM)中加载每个权重的效率。只需对比 H100 的 TFLOPS(万亿次浮点运算)与内存带宽即可发现,每次从显存加载 1 字节数据,理论上可支持 300 次浮点运算。因此,大批量处理的意义在于:从内存中加载每个参数/权重时,可以执行更多操作。这一限制通常被称为“屋顶线模型”(roofline model)。
随着模型规模扩大,这种模式不再适用——模型权重将超出单卡GPU显存容量,必须通过多GPU或多节点分布式存储。即便使用 NVLink 和 InfiniBand 等技术,跨设备通信速度仍慢于直接从显存读取数据。NVLink 在张量并行场景中表现尚可,但跨节点通信效率相当低。
而专家混合模型(MoE)的优势在于专家并行性:不同节点可在内存中存储不同的专家模块,节点间无需频繁通信。这一机制的前提是:拥有足够多节点以确保所有专家模块都存储于显存中,并为KV缓存、其他权重等预留足够开销。因此,MoE 模型自然需要较大的批量规模,且必须最大化批量利用率以确保所有 GPU 持续高效运行。
- EOF -