推理显存快速估算
🧠 推理一个 7B FP16 模型,batch size=1,输入长度 2048,KV Cache 占多少显存?¶
要精确估算 KV Cache 的显存占用,我们需要知道模型的层数、注意力头数、头维度等参数。以典型的 7B 模型(如 Llama-2-7B 架构)为例:32 层,32 个注意力头,头维度 128(因为 hidden_size 4096 / 32 = 128)。没有使用 GQA(分组查询注意力)时,KV 头的数量等于注意力头的数量,即 32。
KV Cache 存储的是每个 token 在每一层的 Key 和 Value 张量。对于一个 token、一层、一个头,Key 和 Value 各自是一个长度为 128 的 FP16 向量(2 字节)。所以每个 token 每层每头的 KV 对共占用:2 (K) + 2 (V) = 4 个向量,即 128 × 2 × 2 = 512 字节?更准确的计算:
每个头的 K 大小 = 头维度 × 元素大小 = 128 × 2 = 256 字节
每个头的 V 大小 = 128 × 2 = 256 字节
每个头的 KV 对 = 512 字节。
总 KV Cache 大小 = 输入长度 × 层数 × 头数 × 每头 KV 对字节数
= 2048 × 32 × 32 × 512 字节。
让我们计算:
2048 × 32 = 65536
65536 × 32 = 2,097,152 (即总头-层-位置数)
2,097,152 × 512 = 1,073,741,824 字节 = 1 GB(因为 1 GB = 1024^3 字节,大约 1.0737 GB ≈ 1 GB)。实际上,1,073,741,824 字节正好是 1 GiB。所以 KV Cache 占用 1 GiB 显存。
如果模型是 7B FP16,权重大约占 14 GB(7B × 2 字节)。加上 KV Cache 1 GB,再加一些中间激活(极小),总显存约 15 GB 左右。所以一张 16GB 的卡(如 Tesla P100 或 RTX 4080)勉强能跑,但需要优化。实际框架可能会有额外开销,KV Cache 通常被预先分配为输入长度最大值的连续内存,所以此例中恰好 1 GiB。

💬 如果使用 GQA(4 组),KV Cache 减少到原来的几分之一?¶
GQA(Grouped-Query Attention)将 Query 头分为若干组,每组共享同一对 Key 和 Value 头。如果 KV 头数变为 4(即 4 组),那么 KV 头的数量就是 4,而不是原来的 32。因此,KV Cache 的大小与 KV 头数成正比。减少比例 = 原头数 / 新头数 = 32 / 4 = 8 倍。所以 KV Cache 变为原来的 八分之一。
对于刚才的例子,新的 KV Cache = 1 GB / 8 = 0.125 GB = 128 MiB。这极大地降低了显存压力,使得更长上下文或更大 batch 成为可能。Llama-2-70B 等大模型常使用 GQA(如 8 组)来节省 KV Cache。
计算公式:KV Cache 字节数 = 2 × 输入长度 × 层数 × KV 头数 × 头维度 × 元素大小(字节)。所以 KV 头数从 32 降到 4,占用直接变为 4/32 = 1/8。
🚀 推理 70B 模型,使用 INT4 量化权重,单卡 48GB 能否运行?写出估算过程。¶
假设使用典型的 70B 模型(如 Llama-2-70B),层数 80,hidden_size 8192,中间 FFN 尺寸通常为 28672,注意力头数 64,头维度 128,没有 GQA 的话 KV 头数也是 64,但 70B 模型一般使用 GQA(如 8 组),这里保守点先按 8 组计算。
权重显存:INT4 权重,每个参数占 0.5 字节(4-bit)。70B 参数 × 0.5 字节 = 35 GB。实际推理框架可能还有些量化参数(如缩放因子)等开销,大约增加 1-2 GB,我们暂且算权重 36 GB。
KV Cache 显存:单卡 48GB 通常用于高吞吐场景,可能支持 batch size 1,但我们需要确认输入长度。假设输入长度 2048,输出长度 512,那么总序列长度 2560 token。KV 头数为 8(GQA),头维度 128。
每 token 每层的 KV 对大小 = 2 (K & V) × KV 头数 × 头维度 × FP16(2 字节) = 2 × 8 × 128 × 2 = 4096 字节。
80 层,总大小 = 4096 × 80 × 2560 = 4096 × 204,800 = 838,860,800 字节 ≈ 800 MiB(0.78 GiB)。可见即使序列长度 2560,KV Cache 也只有不到 1 GB。
中间激活:通常极小的 batch size 激活可以忽略不计(几 MB)。
总显存 = 权重 36 GB + KV Cache 0.8 GB ≈ 36.8 GB。远小于 48 GB,因此单卡 48GB 完全可以运行,甚至 batch size 可以增大。如果未使用 GQA,KV 头数为 64,则 KV Cache 变为 800 MB × (64/8) = 6.4 GB,总显存 ≈ 42.4 GB,仍然在 48GB 以内。所以,无论是否 GQA,INT4 量化的 70B 模型在 48GB 卡上都能运行,还有余量。
实际上,许多框架(如 vLLM、LMDeploy)已经能在 48GB 的 A6000 或 L40S 上运行量化 70B。
📊 如何计算给定模型和 GPU 下的最大推理序列长度?¶
最大推理序列长度受限于显存容量减去权重和框架开销后,剩余可用于 KV Cache 的显存。步骤如下:
-
获取显存总量(GPU 总显存,如 24 GB)。
-
估算权重显存(根据精度:FP16 模型 2 字节/参数,INT4 0.5 字节/参数,INT8 1 字节/参数)。若有量化参数及混合精度,额外加上。
-
减去其他固定开销:CUDA context、框架内核、中间激活(通常 batch=1 时激活显存极小,但批处理增加时需要更多)。通常预留 0.5–2 GB。
-
剩余显存用于 KV Cache。
-
计算每 token 的 KV Cache 大小。公式:
per_token_kv_bytes = 2 × 层数 × KV 头数 × 头维度 × 元素大小(字节)其中 2 表示 K 和 V。FP16 元素大小为 2。若是 FP8 KV Cache,元素大小为 1。 -
最大 token 数 = 剩余显存 / per_token_kv_bytes。 如果框架支持分页(PagedAttention),实际可用容量还会略高,因为没有碎片化损失,但仍可按此估算。
-
这个 token 数包括了输入和输出的总序列长度。因此最大序列长度 ≈ 该 token 数(如果 batch=1)。如果 batch > 1,则每个 batch 的序列共享 KV Cache 池,最大单序列长度需除以 batch size 的某种加权平均,但一般均匀分配就是剩余显存 / (batch_size × per_token_kv_bytes)。
举个例子:7B FP16,32 层,32 头,头维度 128,FP16。per_token_kv_bytes = 2 × 32 × 32 × 128 × 2 = 524,288 字节 = 0.5 MiB。如果 24GB 显卡,权重 14 GB,剩余约 9–10 GB 给 KV Cache,约能支持 9 GB / 0.5 MiB ≈ 18,000 token(实际可能稍低,有碎片)。所以最大可支持 18K 序列。
🧩 推理时,Prefill 阶段的显存峰值通常由什么引起?¶
Prefill 阶段是将输入 prompt 一次性送入模型进行计算,产生第一个 token 的过程。它的显存峰值通常高于解码阶段,主要原因:
-
中间激活(activations)的大量占用。在 Prefill 阶段,整个输入序列(比如 2048 token)需要同时进行注意力计算和 FFN 前向传播,这会产生较大的中间张量,例如 QKV 矩阵、注意力分数矩阵(形状 [batch, 头数, 序列长度, 序列长度]),该矩阵与序列长度的平方成正比。即使批大小为 1,当输入长度达 8192 时,注意力分数矩阵约 8K×8K×头数,FP16 下可能达数百 MB。而解码阶段每次只有一个新 token,注意力矩阵是 1 × 当前长度,激活量小得多。
-
框架内存分配策略:PyTorch 会缓存分配器,导致峰值可能比理论值更高。vLLM 等通过预分配和共享内存缓解。
-
某些算子需要额外的临时缓冲区,比如 FlashAttention 虽然节省了注意力矩阵的显存,但仍有一些中间梯度(尽管推理无梯度,但算子实现可能有工作空间)。
因此,显存峰值通常在 Prefill 阶段,由长序列的 注意力中间激活 和 临时缓冲区 叠加权重与 KV Cache 造成。优化技术如 FlashAttention、vLLM 的 paged attention 可以显著降低 Prefill 的显存峰值,使得长上下文推理可行。
📜 长文本推理(128K tokens)时,显存瓶颈是权重还是 KV Cache?¶
对于现代大模型,128K tokens 的超长序列推理,显存瓶颈无疑是 KV Cache。权重通常是固定的,比如一个 13B FP16 模型权重 26 GB,加上 INT4 量化的 70B 模型权重 35 GB,而 KV Cache 随序列长度线性增长。
计算 13B 模型(40 层,40 头,头维度 128,无 GQA)在 128K token 时的 KV Cache:
per_token_kv_bytes = 2 × 40 × 40 × 128 × 2 = 819,200 字节 ≈ 0.78125 MiB。
128K tokens 的 KV Cache = 128 × 1024 × 0.78125 MiB ≈ 102,400 MiB = 100 GiB。这远超权重!即使使用 GQA 8 组,KV 头数变为 8,占用也达 100 × (8/40) = 20 GiB,对于大权重模型仍然可能成为瓶颈。对于 70B GQA 模型,权重 35GB (INT4),但 128K KV Cache 按 8 组、80 层、头维度 128 计算:
per_token = 2 × 80 × 8 × 128 × 2 = 327,680 字节 ≈ 0.3125 MiB。
128K → 128 × 1024 × 0.3125 = 40,960 MiB = 40 GiB。权重 35 GB + KV Cache 40 GB = 75 GB,超过单卡 80GB(如 A100 80G)? 75 GB < 80 GB,勉强可以。但如果有任何额外开销就会 OOM。所以长序列下,KV Cache 变得比权重更大或相当,成为主要瓶颈。
因此,长文本推理的显存瓶颈通常是 KV Cache,而非权重。解决方案包括:GQA、MQA、KV Cache 量化(INT8/FP8)、PagedAttention 优化内存碎片、以及 offload 到 CPU 等。
💨 如果使用 vLLM 的 PagedAttention,KV Cache 的显存实际利用率会如何变化?¶
传统做法为每个请求预留最大长度的连续 KV Cache 块,这会造成严重的内部碎片(比如实际生成长度远小于预留长度)和外部碎片,导致显存利用率极低,可能只有 20-30%。vLLM 提出的 PagedAttention 借鉴操作系统的分页思想,将 KV Cache 划分为固定大小的 block,可以离散分配,大幅减少碎片。
实际利用率变化:
-
显著提高。vLLM 论文表明,在批量服务场景,显存利用率可提升至 80-90% 甚至更高。因为 block 可以按需分配,请求仅占用实际所需的 block,且多个请求共享相同前缀时还能共享 block(如系统提示词),进一步节省显存。
-
接近于显存的理论可用上限。相比于预留连续空间,PagedAttention 能更紧凑地打包请求,允许更大的 batch size 或更长的序列。
-
但存在轻微的 metadata 开销(每个 block 需要维护表),不过通常占比很小(<5%)。
因此,PagedAttention 使得 KV Cache 显存利用率从传统的 20-30% 提升到 80% 以上,这是其能够支持高吞吐服务的核心创新。
📈 推理时,如果 batch size 从 1 增加到 8,显存增加主要在哪个部分?¶
显存增加主要在 KV Cache 和 中间激活。详细分解:
-
权重:与 batch size 无关,固定不变。
-
KV Cache:存储每个请求的 K、V,直接与 batch size 成正比。batch=8 时,KV Cache 总量约为 batch=1 时的 8 倍(假设各序列长度相同)。
-
中间激活:Prefill 阶段,批量进行注意力计算时,注意力分数矩阵的形状是 [batch, 头数, 序列长度, 序列长度],其大小与 batch size 成正比。此外,FFN 层的激活也是与 batch size 成线性关系。所以激活显存也会随 batch size 线性增长,但是在解码阶段激活通常较小。总体而言,批量推理的额外显存主要贡献来自 KV Cache,其次为 Prefill 时的激活。
举个例子:如果单序列 KV Cache 为 1 GB,batch=8 则需要 8 GB KV Cache,加上可能增大的激活峰值(假设单序列激活 0.2 GB,batch=8 时可能 1.6 GB)。所以增加主要集中在 KV Cache。
因此,推理服务规划显存时,重点考虑最大 batch size 和最长序列长度下的 KV Cache 总量。
⚖️ 量化推理模型,INT4 权重 + FP16 KV Cache,总显存如何估算?¶
总显存 = 量化权重显存 + KV Cache 显存 + 激活及其他开销。
-
权重:INT4 权重,每参数 0.5 字节。对于 N 参数模型,占
N × 0.5字节。加上量化参数(如每通道的缩放因子,通常可忽略,大约百分之几的权重)。可以简单算N * 0.5。 -
KV Cache:根据精度,KV Cache 可以是 FP16(2 字节),即使权重 INT4,KV Cache 通常仍保持 FP16 以保证精度(或使用 FP8)。计算公式同前:
KV_Cache_Bytes = 2 × 层数 × KV 头数 × 头维度 × 总序列长度 × 元素大小。如果 batch>1,再乘以 batch size。 -
激活内存:推理框架通常需要一些缓冲区,尤其 Prefill 时的中间结果。batch=1 时很小(几十到几百 MB),可预估为 0.5–2 GB。高批量时需根据模型尺寸估算,可用公式:
激活 ≈ batch × 序列长度 × hidden_size × 层数 × 某种系数,但较复杂,实践中可简单预留 10-20% 的权重大小。 -
总显存:求和。例如 Llama-2-70B INT4:权重 35 GB。FP16 KV Cache(假设 GQA 8 组,序列 4096,batch=8): per_token_per_batch = 2 × 80 × 8 × 128 × 2 = 327,680 字节 total = 327,680 × 4096 × 8 = 327,680 × 32,768 ≈ 10.73 GB。 加上激活大约 3 GB。总 ≈ 35 + 10.73 + 3 = 48.73 GB。48GB 显存可能不够,需降低序列长度或 batch。
因此,总显存 = 权重(INT4) + KV(FP16) + 激活,重点优化 KV Cache(如 INT8 KV Cache 可减半)。
🧑💻 推理服务中,如何预估多用户并发时的显存需求?¶
预估并发显存需求,核心是计算最大同时活跃请求下的资源占用。步骤如下:
-
确定模型权重:固定值,不论并发数。
-
单请求 KV Cache 最大占用:假设系统允许的最大输入+输出长度(max_total_tokens),计算单请求 KV Cache 大小(per_req_kv)。
-
并发请求数(batch size)上限:根据目标并发数(如同时处理 20 个请求)设置最大 batch size。
-
KV Cache 总量 = per_req_kv × max_batch_size × 一个调整因子(由于 PagedAttention 等可能允许共享和碎片节省,可设为 0.9-1.0)。
-
激活内存:要支持最大 batch size 的 Prefill 阶段峰值。激活量与 batch size 成正比,可粗略按
batch × seq_len × hidden_size × 一些层倍数估算,或使用经验值(如 LLM 推理框架通常报告的 batch size 对显存的影响)。无实测时,可按权重的 20-30% 作为激活预留。 -
其他开销:框架内核、CUDA context 等,保留 1-2 GB。
-
总显存需求 = 权重 + KV Cache 总量 + 激活内存 + 其他。
-
如果总需求超过可用显存,可采取:降低 max_total_tokens,减小 max_batch_size,使用量化 KV Cache,或采用 PagedAttention 提高利用率。
举例:用 13B FP16 权重 26 GB,单请求最大 4096 token,KV Cache 约 1 GB/请求,并发 16 请求,KV Cache 共 16 GB,激活预留 5 GB,总 26+16+5=47 GB,接近 48 GB 卡极限。若启用 PagedAttention,实际可能节省 20% KV Cache,变成 12.8 GB,则总 43.8 GB,更安全。
因此,预估并发显存就是权重 + 最大并发数 × 单请求最大 KV Cache + Prefill 激活峰值。这是容量规划的关键。
⚡ 使用 FlashDecoding 对推理显存有节省吗?主要是加速。¶
FlashDecoding 是 FlashAttention 团队针对解码阶段(生成 token 时)的优化,主要目的是加速长序列的解码,而非节省显存。它的原理是将注意力计算按序列长度维度并行化,提升 GPU 利用率,降低延迟。尽管它可能因为分块计算而稍微改变中间缓冲区的使用模式,但显存占用的节省非常有限,几乎可以忽略。
解码阶段的显存瓶颈在于 KV Cache,FlashDecoding 并不减少 KV Cache 的大小(它仍然需要存储全部的 K 和 V)。它在计算时以块的方式加载 K、V,可能使用一些临时工作空间,但这些通常不会显著增加或减少峰值显存。因此,FlashDecoding 对推理显存没有明显节省,它的价值在于提高解码吞吐和降低延迟,特别是对于长序列。如果你想要节省显存,应该关注 KV Cache 量化或 GQA/MQA。
🔄 什么是“共享前缀”如何节省 KV Cache?¶
在推理服务中,多个请求往往共享相同的前缀,比如所有请求都使用同一个较长的 system prompt(系统提示词)。传统方式会为每个请求单独存储这份前缀的 KV Cache,造成重复浪费。
共享前缀 通过让多个请求复用同一份前缀的 K、V 张量,避免重复存储。当新请求到来,如果其前缀与已缓存的某个前缀匹配,就不再计算该前缀的注意力,直接复用存储好的 KV Cache 块。在 PagedAttention 中,可以通过引用相同的物理块来实现。这样,N 个请求共享一个长度为 L 的前缀,KV Cache 节省量 ≈ (N-1) × L 对应的 KV Cache 大小,效果显著。
例如,一个 4K token 的系统提示,10 个并发请求,如果不共享,需要存储 10 份共 40K token 的 KV Cache;共享后只需存储 1 份 4K token,节省 90% 的前缀相关显存。这使得更大的系统提示和更高的并发成为可能,是 vLLM 等框架的重要特性。
💬 多轮对话中,如何进行 KV Cache 的显存预估?¶
多轮对话需要保持上下文历史,即每一轮的问答序列累积。最直接的方式是保留完整对话历史的 KV Cache。显存预估需考虑最大对话轮数或最大上下文窗口。
-
设定一个最大总 token 数(例如 4096),包括所有轮次输入和输出。那么单次对话的 KV Cache 就跟单次请求的最大长度相同,按此计算即可。
-
对于会话管理,框架通常会允许动态增长,直到达到上限。因此,预估时以最大上下文窗口为基准:单会话 KV Cache = per_token_kv × max_context_length。
-
如果系统有多个同时进行的对话,总 KV Cache = 单会话最大 KV Cache × 最大并发会话数。由于共享前缀(如 system prompt)可能会节省,但动态多轮对话中,不同会话的历史不同,共享机会较少,除了固定的 system prompt 外,不能指望节省太多。
-
所以,多轮对话显存预估与批量单轮推理类似,但每个请求的序列长度会更长(包含历史)。若设置最大窗口 4K,并发 10 个,则 KV Cache 为 10 × 4K × per_token_kv。
因此,多轮对话显存预估就是最大并发会话数 × 每会话最大历史长度下的 KV Cache 大小,加上权重和激活。采用 PagedAttention 后,因为内存池可以按块动态分配和释放,实际上能更有效率地使用显存,但规划时仍需保证峰值不溢出。
🆘 如果推理框架报告 OOM,但 nvidia-smi 显示还有空闲显存,可能是什么原因?¶
这种情况常见,主要原因:
-
PyTorch 的缓存分配机制:PyTorch 使用自己的 CUDA 缓存分配器,它不会立即将未使用的内存归还给系统(即不会调用 cudaFree),而是保留在缓存池中以备后用。
nvidia-smi显示的是进程占用的总显存(包括 PyTorch 已分配但未实际使用的缓存块)。而 OOM 错误通常发生在 PyTorch 分配器尝试从缓存池中分配时,发现没有足够的连续可用块,尽管总空闲(缓存内空闲 + 未分配)可能还很大。这称为内存碎片问题。 -
碎片化:即使总空闲显存足够,但如果没有足够大的一块连续显存来满足分配请求,也会 OOM。大张量分配需要连续物理显存,频繁的分配和释放可能导致碎片化。PagedAttention 就是为了解决 KV Cache 碎片化设计的。
-
其他进程占用:nvidia-smi 显示的是所有进程的总和,如果有其他进程(如另一个模型或 GUI)占用了部分显存,本进程可用的会减少。
-
CUDA context 开销:驱动为每个 context 保留一些显存,nvidia-smi 可能把这部分也算在该进程下,但实际上不可用于模型。
-
框架预留未释放:有些推理框架会预先分配一大块显存作为 workspace,即使当前未使用,也不会释放,导致后续分配失败。
-
CPU 内存不足导致的 swap? 不太可能,但显存-内存间数据搬运也可能造成 OOM,如果无可用 pinned memory。
排查建议:使用 torch.cuda.memory_summary() 查看 PyTorch 的内存分配详情,观察缓存分配器中的空闲块分布。使用 py-spy 等工具检查程序状态。优化措施包括:使用统一内存分配器、启用 PagedAttention、减少碎片化操作(如一次性分配大的缓冲区)。
推理时,模型权重的显存占用和 KV Cache 的比例通常如何?¶
这个比例完全取决于序列长度与模型规模的博弈。在短序列、小批次推理中,权重往往占大头;一旦上下文变长,KV Cache 会迅速反超。
几种典型配比(以 FP16、单 batch 为例):
-
短文本(512 tokens) 7B 模型:权重 14 GB,KV Cache 约 0.25 GB,权重占绝对主导(≈98%)。 70B 模型:权重 140 GB,KV Cache(GQA 8组)约 0.8 GB,权重依然是绝对主力。
-
中等长度(2048 tokens) 7B 模型:权重 14 GB,KV Cache 约 1 GB,比例约 14:1。 70B(GQA)模型:权重 140 GB,KV Cache 约 3.2 GB,比例约 44:1。
-
长上下文(128K tokens) 7B 模型:权重 14 GB,KV Cache 爆增至 64 GB(假设无 GQA,32 头),比权重还大,比例 0.22:1。 70B(GQA 8 组)模型:权重 140 GB,KV Cache 约 200 GB(估算),比例约 0.7:1,KV Cache 明显超过权重。
规律总结:
对于当前流行的 7B-13B 模型 + 4K 上下文的常见部署,权重仍是主要占用。但当上下文扩展至 32K 以上、或没有 GQA/MQA 时,KV Cache 会成为新的显存黑洞。因此,长上下文推理必须引入 KV Cache 量化、GQA 或 PagedAttention 等优化。这也是为什么很多推理框架把“KV Cache 显存管理”作为核心设计要点。
对于 MoE 模型(如 Mixtral 8x7B),推理显存应按照总参数量还是激活参数量来估算权重部分?¶
答案是:必须按总参数量来估算权重显存,因为推理时所有专家都需要被加载到显存中。
Mixtral 8x7B 的“8x7B”并不是 8×7=56B,而是 8 个专家,每个专家是一个约 7B 的前馈网络(FFN),加上共享的注意力层等。其真实总参数量约为 46.7B(而非 56B),但推理时每个 token 只激活 2 个专家,所以计算量确实对标一个约 12.9B 的稠密模型——这也是它速度快的原因。
但计算量≠显存。要执行 top-2 专家的路由,你必须在显存中保持全部 8 个专家的权重,因为不同 token 激活的专家不同,无法提前只加载部分。即使使用专家并行(Expert Parallelism)将不同专家分布到不同 GPU,每张卡仍要加载自己负责的专家权重,总显存占用依然等于总参数量。
所以权重显存的估算是:总参数量 × 每参数字节数。FP16 下 Mixtral 8x7B 约占 46.7B × 2 = 93.4 GB;INT4 量化后约 23.4 GB。而实际推理时的激活参数量仅用于估算计算量和延迟,不能用来估算显存。
推理 MoE 时,所有专家都必须加载吗?显存如何估算?¶
是的,所有专家都必须加载到显存,但可以通过专家并行(Expert Parallelism)分散到多张 GPU 上。
单卡场景:
所有专家权重常驻显存。每个 token 只激活 2 个专家,但为了服务不同 token,全部 8 个专家都要随机待命。因此单卡显存 = 总参数量权重 + KV Cache + 激活。
多卡专家并行:
将不同的专家放在不同的 GPU 上,例如 Mixtral 8x7B 放在 4 张 GPU 上,每张卡只存储 2 个专家。推理时,每个 token 的 router 输出选择 top-2 专家,如果它们恰好不在同一张卡上,就需要通过 all-to-all 通信将 token 的隐状态发送到对应专家所在的 GPU 进行计算,再传回结果。这样单卡权重显存降至原来的 2/8 = 1/4,但引入了通信开销。
估算方法:
-
总参数量 P(如 46.7B),精度 bytes(如 2),则总权重显存 = P × bytes。
-
如果使用 N 路专家并行,单卡权重显存 ≈ (P / N) × bytes(忽略共享的注意力层等,实际略有增加)。
-
仍需加上 KV Cache 和激活(激活量与 batch 和序列长度成正比,受专家并行影响小)。
因此 MoE 推理显存的第一个原则就是:总参数量决定存储,专家并行度决定单卡存储分摊。
如果推理时使用 TP=4,权重显存会变成原来的多少?KV Cache 呢?¶
TP(张量并行)会将模型的每一层的权重矩阵沿 hidden dimension 切分到 4 张 GPU 上,因此:
-
权重显存:单卡权重显存变为原来的 1/4(严格说是约 1/4,因为有些层如 LayerNorm/RMSNorm 参数量极小,可能复制或切分后总占用略增,但总体可认为线性减少)。
-
KV Cache:张量并行对 KV Cache 的影响取决于切分方式。通常 TP 对注意力头进行切分,即每个 GPU 只存部分头的 K 和 V。因此,单张卡上的 KV Cache 也变为原来的 1/TP 度,即 1/4。因为每个头独立产生 K、V,切片到不同 GPU 自然就分割了。
-
激活与中间缓冲:同样被切分,单卡激活也约降至 1/4。
总结:TP=4 时,单卡所有权重、KV Cache、激活显存均大致降至 1/4。这使得一张原本装不下的大模型可以在多张较小 GPU 上运行。代价是每层需要两次 all-reduce 通信,对卡间带宽(NVLink/PCIe)要求高。
如何估算在苹果 M2 Ultra 上运行 7B 模型的显存需求?(统一内存)¶
苹果芯片的统一内存架构(UMA)让 CPU 和 GPU 共享同一块物理内存,不存在显存和内存的拷贝。估算时可将其视为“总内存占用”,与 PC 的显存估算一样,但要考虑操作系统和其他应用保留。
步骤:
-
模型权重:FP16 7B 约占 14 GB。如果使用 Core ML 或 MLX 的量化格式(如 INT4),可降至 3.5 GB 左右。
-
KV Cache:取决于上下文长度。假设 2048 长度,使用 GQA(如有)或无 GQA,计算公式相同。7B 无 GQA 时,约 1 GB;有 GQA(如 4 组)约 0.25 GB。
-
运行时开销:Core ML/MLX 等框架会有少量中间缓冲,约 0.5–1 GB。
-
系统预留:macOS 自身、窗口服务器等占用部分统一内存。在 64 GB 或 96 GB 的 M2 Ultra 上,通常空闲可达 50-80 GB,足够运行。
-
总占用:14 GB (权重) + 1 GB (KV) + 1 GB (缓冲) ≈ 16 GB。因此一台 64 GB 的 M2 Ultra 可以轻松运行 7B FP16 模型,并支持较长的上下文或更大的 batch。
注意:苹果的统一内存带宽极高(M2 Ultra 达 800 GB/s),这往往是推理速度的保障,但容量需自行确保。使用 sudo sysctl iogpu.wired_limit 可以预留更多内存给 GPU(默认约 60-70% 总内存),对大规模模型很重要。
推理时使用 CPU 推理(llama.cpp),内存占用如何估算?¶
llama.cpp 使用纯 CPU 推理,内存占用计算与 GPU 类似,但权重和 KV Cache 都驻留在系统内存(RAM)中,并且可以利用内存映射(mmap) 延迟加载权重,减少物理内存占用。
估算公式:
-
权重内存:取决于量化格式。例如 Q4_K_M(约 4-bit)每参数约 0.5 字节 + 少量量化参数,7B 模型约 3.5–4 GB;Q8_0(8-bit)约 7 GB;FP16 约 14 GB。如果使用 mmap,物理内存实际占用可能小于文件大小(按需分页),但为了性能通常还是会全部加载。
-
KV Cache:同样根据上下文长度计算,但 llama.cpp 默认使用 FP16 存储 KV Cache(可配置为量化,如 Q8_0 或 Q4_0 以节省内存)。例如 7B 无 GQA,2048 长度 FP16 KV Cache 约 1 GB;若使用 Q4_0 KV Cache 则约 0.25 GB。
-
其他:临时缓冲区、激活等占用很小(几十到几百 MB)。
-
总内存 ≈ 权重内存 + KV Cache 内存 + 几百 MB。
以 7B Q4_K_M 模型为例,2048 上下文,FP16 KV Cache,总内存 ≈ 4 GB + 1 GB = 5 GB。即使是 16 GB 内存的笔记本也能流畅运行。对于 70B 模型,Q4_K_M 权重约 35 GB,FP16 KV Cache 可能 2-4 GB,总计约 40 GB,需要 64 GB 内存的机器。若使用 Q4 KV Cache,则更省。
因此,llama.cpp 的内存预估核心就是:量化权重 + 量化/非量化 KV Cache + 少量开销。mmap 可降低物理内存压力,但不改变虚拟内存占用。
为什么推理框架有时会预留显存,造成实际可用显存减少?¶
推理框架(特别是 vLLM、TensorRT-LLM、LMDeploy 等高性能框架)为追求低延迟、高吞吐,会预先分配大块显存,而不是按需动态分配。这种预留机制有以下几个原因:
-
减少碎片:动态分配会导致显存碎片,大量小块空闲但无法满足大块连续请求。预留一整个连续块作为 KV Cache 池,可以避免外部碎片。
-
避免运行时分配开销:模型加载后,预先分配 workspace 和 KV Cache block,后续请求只需从池中获取,零 malloc 调用,降低延迟抖动。
-
GPU 虚拟内存管理:某些框架使用 CUDA 虚拟内存 API 预留地址空间但不立即映射物理页,看起来占用高,实际物理占用可能低,但
nvidia-smi显示的是虚拟占用,导致“占用大”的错觉。 -
P2P 通信缓冲区:多卡并行的框架会预留用于 all-reduce 或 all-to-all 的缓冲区,这部分也常驻显存。
-
模型预热:框架可能执行一次或几次前向推理进行性能校准(如选择最佳算法),这些产生的中间结果也可能留在缓存分配器中,不释放。
因此,预留显存是一种性能换空间的设计。用户看到 nvidia-smi 的高占用,实际可能包含大量“可回收但未被释放”的缓存池。如果确实 OOM,可以通过配置(如 --gpu-memory-utilization 在 vLLM 中)来限制框架最大占用比例,避免系统崩溃。
推理一个多模态大模型(如 LLaVA),视觉编码器部分会额外占用多少显存?¶
LLaVA 等模型在 LLM 基础上增加了一个视觉编码器(通常是 CLIP ViT-L/14)以及一个多模态投影层。视觉编码器参数量独立,需要常驻显存。
-
CLIP ViT-L/14:约 300M 参数(0.3B)。FP16 下约 0.6 GB。
-
多模态投影层(简单的线性层或 MLP):参数量很小,几十 MB 可忽略。
-
视觉输入产生的中间激活:处理一张或一批图片(如 576 个 token)会带来短暂的峰值激活,但编完码后得到视觉特征 token,它们的 KV Cache 已经融入 LLM 的 KV Cache 中(视觉 token 也参与 LLM 的自回归)。所以额外的常驻显存主要是视觉编码器的权重。
因此,LLaVA-7B 在 LLM 7B 基础上,权重显存约增加 0.6 GB(FP16),总权重 ≈ 14.6 GB,相比纯 LLM 增加约 4%。如果是 LLaVA-13B,视觉编码器仍然是同一个 ViT-L,增量依然是 0.6 GB,占比更小。对于更大的视觉模型(如 ViT-G),可能增加数 GB,但通常仍远小于 LLM 权重。所以多模态的额外显存主要来自视觉编码器权重,常驻内存量不大。
投机采样(Speculative Decoding)对推理显存的影响是怎样的?¶
投机采样引入了一个小尺寸的草稿模型(draft model),与目标大模型同时驻留显存,从而加速生成。因此显存必然增加,增加量约为草稿模型的全部权重和其 KV Cache。
-
草稿模型大小:通常选用同系列的小模型,如 7B 目标模型配 0.5B 或 1.5B 草稿模型。FP16 下 0.5B 约 1 GB,1.5B 约 3 GB。
-
草稿模型的 KV Cache:投机采样一次产生 K 个 draft tokens(如 5 个),所以草稿模型的 KV Cache 对应这 K 个 token,非常小(通常几 MB)。
-
额外开销:draft 与 target 可能会共享部分权重(如 Medusa heads 或 eagle 的 feature 网络),但完全独立的小模型则需全量加载。
-
PagedAttention 下的共享:目标模型和草稿模型需要各自独立的 KV Cache 池,但占比较小。
因此,使用独立草稿模型的投机采样,总显存约增加 0.5B-3B 参数量对应的权重(1-6 GB FP16),对于已有大模型的 48GB 或 80GB 卡来说增量在 5-10% 左右,可接受。若采用自投机(self-speculative)或基于目标模型自身的跳层草稿,则可大幅减少额外权重(仅增加少数预测头),显存增量可忽略不计。所以,投机采样以少量显存换取了 2-3 倍的生成加速,在显存充足时是极佳的优化手段。
推理服务中,多 LoRA 适配器同时加载时,显存如何估算?¶
服务化推理通常需要支持多个 LoRA 适配器(对应不同用户或任务),并热加载到基础模型上。显存估算需考虑:
-
基础模型权重:不变,全量加载。
-
每个 LoRA 适配器:由低秩矩阵 A 和 B 组成,参数量非常少,通常为基础模型参数量的 0.1%-1%。例如 7B 模型使用 rank=16 的 LoRA 作用于所有线性层,参数量可能约 10-30M,FP16 下每个适配器约占 20-60 MB。
-
同时加载的 LoRA 数量:如果同时服务 100 个 LoRA 用户,且适配器都需常驻显存,则总 LoRA 权重 ≈ 单适配器大小 × 数量。比如 30MB × 100 = 3 GB。对 48GB 显卡,增加了 6% 左右,影响不大。
-
KV Cache:不同 LoRA 请求共享基础模型的 KV Cache 池(因为 base weights 相同,前缀相同可共享,但不同 LoRA 生成内容不同,后续不共享)。所以 KV Cache 与不加载 LoRA 时基本相同,额外占用可忽略。
-
内存管理:框架如 vLLM 支持 LoRA 权重在线动态加载/卸载,或使用 LoRA 交换到 CPU 内存。此时显存只需驻留正在被活跃请求使用的适配器,可以显著降低对显存的压力。
估算结论:单 LoRA 极轻量,多 LoRA 的显存开销 = 基础模型 + KV Cache 池 + 活跃 LoRA 适配器总大小。例如 7B 基础模型 14 GB,KV Cache 规划 10 GB,100 个活跃 LoRA 各 30 MB 共 3 GB,总计约 27 GB,可放在一张 32 GB V100 上。设计时应支持 LoRA 交换以减少常驻。
推理时,临时激活值(如中间结果)的显存可以忽略吗?什么情况下不能?¶
通常在小 batch、短序列时,激活值显存可忽略(几十到几百 MB)。但在大批量或超长序列时,它不可忽视,甚至可能成为 Prefill 阶段 OOM 的元凶。
具体什么时候不能忽略:
-
大批次 Prefill:假设 batch=32,序列长度 4096,注意力中间结果(QK^T 分数矩阵)的尺寸为 [32, 头数, 4096, 4096],即使头数为 32,FP16 下也高达 32×32×4096×4096×2 ≈ 32 GB(实际上 FlashAttention 不会完整存储该矩阵,但很多实现仍可能有缓冲)。即使使用优化,批量 Prefill 的激活也可轻松达到数 GB。
-
长序列:即使 batch=1,当序列长度 128K 时,注意力矩阵理论大小(若完整存储)将是 128K×128K×头数,FP16 下可能超过显存总容量,这就是为什么必须用 FlashAttention 并配合重计算或分块。
-
不使用融合算子:标准 Transformer 实现中,会保存一些中间结果用于反向传播,但推理时无需梯度,框架会做针对性优化(如 in-place 操作)。如果推理框架没有充分优化(例如某些早期实现),会浪费较多激活显存。
-
编码器-解码器架构或多模态:额外的编码器会带来额外激活。
实际生产环境:现代推理引擎(vLLM, TRT-LLM)对 Prefill 激活做了特殊管理,通常是临时分配,用后即回收;且利用 FlashAttention 避免显式构建注意力矩阵。因此,激活峰值常在可接受范围,但在规划总显存时,仍需保留约 5-10% 的权重显存大小作为激活缓冲。绝对不能简单忽略,尤其是运行大 batch 或长上下文时。
经验法则:可以用 batch × seq_len × hidden_dim × 10-20 来粗略估算 Prefill 期间激活额外的峰值(字节),并在显存计算中加入这一项。当 batch 或序列长度超过一定程度时,这一项会快速膨胀,成为容量规划的决定性因素之一。