把大语言模型部署到本地时,最先被问到的问题往往不是模型结构,而是“我的电脑能不能跑、需要多大显卡、多少显存”。很多人按参数量推断,觉得 7B 模型只需要 14GB 磁盘就够,结果加载后立刻报CUDA out of memory。这个差距来自一个容易被忽略的事实:本地 LLM 推理的硬件需求不是权重文件大小,而是权重、KV Cache、运行时上下文和 CUDA 开销的总和。
硬件需求计算器(Hardware requirement calculator for local LLMs)解决的就是这个估算问题:输入模型参数量、量化精度、上下文长度、批大小等参数,输出建议显存、系统内存和可运行硬件级别。这类工具在本地部署、私有化选型和购买显卡前做预算时非常有用。这篇文章会从原理公式开始,先讲清楚估算逻辑,再给出一个可以直接跑的最小 Python 计算器,最后补充常见估算错误、排查路径和一套可复用的硬件选型清单。
实际项目中,一个人只有一张消费级显卡,或者一台没有 GPU 的服务器,要判断“最多能跑多大的模型”,靠经验猜很容易错。把公式写成工具才有可重复性。文章的核心主线是:先把显存需求拆成权重、KV Cache、运行开销三部分,分别计算后加总,再用结果指导硬件选择。读完之后,你不仅会使用现成计算器,还能自己写一个,也能在别人给出的“能跑”结论出现偏差时,判断问题到底出在哪一层。
1. 为什么硬件需求不能只看模型参数量
1.1 显存是本地推理的第一瓶颈
本地部署 LLM 与调用云端 API 最大的区别是:模型的全部或大部分权重必须放在本机内存里,GPU 推理时还必须放在显存里。CPU 推理虽然可以只依赖 DDR 内存,但速度会慢一到两个数量级。因此,绝大多数“本地跑大模型”的需求,核心约束就是显存容量。
显存容量决定了你“能不能把模型放进去”,内存带宽决定了你“每秒钟能生成多少个 token”。一个常见的误解是:显卡算力越高,生成越快。实际上,自回归解码阶段,每次只生成一个 token,需要把整个模型的权重从显存搬到计算单元,这个搬运受显存带宽限制。所以 4090 比 3090 快的核心原因之一,不是算力翻倍,而是 GDDR6X 带宽更高;数据中心卡用 HBM 更是为了带宽。
容量和带宽是两个独立维度。容量不够,根本跑不起来;带宽不够,跑得起来但慢到无法使用。硬件需求计算器主要解决容量问题,但一个好的估算结果也应该给出带宽层面的速度预测,否则用户选完卡仍然不知道体验如何。
1.2 模型加载后的显存组成不是一块整体
一个模型被加载到显存后,占用的空间远不止权重本身。从推理框架的角度看,至少包含以下几部分:
| 组成部分 | 作用 | 是否随上下文变化 |
|---|---|---|
| 权重参数 | 保存模型所有层的可学习参数 | 固定,加载时确定 |
| KV Cache | 保存已生成 token 的 Key 和 Value | 随序列长度和 batch 线性增长 |
| 激活值 | 前向计算过程中的中间张量 | 随 batch 和序列长度变化 |
| CUDA 上下文 | 驱动、内核、显存分配器元数据 | 基本固定,几百 MB 量级 |
| 框架缓冲 | PyTorch / llama.cpp 的临时分配 | 波动较大 |
很多离线算“模型要多少显存”的工具只算第一项,所以算出来很乐观。真正部署时才发现,KV Cache 随对话变长持续增长,长对话场景下甚至可能追平权重占用的显存。
1.3 能不能跑,要综合四类指标判断
判断一台机器能不能跑某个本地模型,至少要回答四个问题:
- 容量:权重、KV Cache、运行时开销总和的预估显存,是否小于显卡可用显存。
- 带宽:以模型权重字节数计算出的解码速度,是否满足使用预期。
- 持久性:长对话或并发请求时,KV Cache 增长后是否仍然放得下。
- 扩展性:如果后续要加量化、加上下文、加多用户并发,硬件是否还有余量。
这四类问题正好对应硬件需求计算器的输入输出。输入覆盖模型规模和量化方式,输出覆盖容量建议和速度预估,并且要能对上下文长度、并发数做敏感度分析。
2. 显存估算公式:计算器背后的核心逻辑
2.1 权重的显存:参数量 × 每参数字节数
权重显存是最好算的一块。一个参数量为 N 的模型,以某种精度加载时,占用为:
权重显存(GB)= 参数量 × 每参数字节数 ÷ 1024³注意这里用二进制单位换算:1GB = 1024³ 字节,而不是 1000³。厂商宣传的硬盘容量用十进制,显存和内存容量通常按二进制,实际可用值会小于标称值,这也是估算偏乐观的一个来源。
常见负载精度每参数字节数如下:
| 精度 | 每参数字节数 | 7B 模型权重大小 | 13B 模型权重大小 |
|---|---|---|---|
| FP32 | 4 | 约 26.1GB | 约 48.4GB |
| FP16 / BF16 | 2 | 约 13.0GB | 约 24.2GB |
| INT8 | 1 | 约 6.5GB | 约 12.1GB |
| INT4 | 0.5 | 约 3.3GB | 约 6.1GB |
这里 7B 指的是 70 亿参数。实际模型的参数量并不总是整数,比如 7B 模型实际可能是 6.7B 或 7.6B,计算时要读模型配置或直接用p.numel()统计,不能只看名字。
2.2 KV Cache 是长对话场景最容易漏算的部分
Transformer 解码时,每生成一个新 token,都要用该 token 对应的 Key 和 Value 张量参与注意力计算。这些张量会被缓存下来,避免重复计算前面的 token。这个缓存就是 KV Cache。
KV Cache 显存公式为:
KV Cache(字节)= 2 × 层数 × KV 头数 × Head Dim × 序列长度 × 每元素字节数前面的2代表 Key 和 Value 各一份。以一组通用示例配置估算:32 层、8 个 KV 头、Head Dim 128、FP16 加载(每元素 2 字节)、上下文 4096,那么:
KV Cache = 2 × 32 × 8 × 128 × 4096 × 2 = 536,870,912 字节 = 512 MB如果上下文长度翻倍,KV Cache 也翻倍。对于 128K 长上下文,上面配置的 KV Cache 会来到 16GB,几乎和权重一样大。因此长上下文模型必须用 GQA(Grouped Query Attention)减少 KV 头数,或者用 KV Cache 量化,否则显存很难承受。
2.3 运行时开销和 CUDA 上下文不能省略
实际加载时,还会有几百 MB 到 1GB 的固定开销,包括:
- CUDA Context:约 300MB 到 500MB,首次初始化后固定占用。
- 显存分配器碎片:PyTorch 的缓存分配器会预留一部分显存,不一定完全按需分配。
- 激活值和临时缓冲:batch 为 1 时通常较小,但解码阶段仍有中间张量。
安全做法是在总需求上再增加 20% 到 30% 的余量。学习环境可以省一点,生产环境必须留足,否则并发请求、长对话、切换模型都会触发 OOM。
2.4 完整估算示例
以“7B 模型、INT8 量化、4096 上下文、batch=1”为例,手动计算一次:
| 项目 | 计算过程 | 占用 |
|---|---|---|
| 权重 | 70 亿 × 1 字节 | 约 6.5GB |
| KV Cache | 512MB(按上文示例配置) | 约 0.5GB |
| 运行开销 | CUDA + 激活 + 分配器 | 约 1GB |
| 余量 | 上述总和再加 25% | 约 8GB × 1.25 |
| 合计建议显存 | 约 10GB |
因此,这类配置最低需要一张 12GB 显存的显卡,谨慎一点选 16GB。如果换成 FP16,权重直接翻倍到 13GB,加上其余部分和余量,总需求就会超过 20GB,12GB 卡就不合适了。这个差距说明量化不是“可选项”,而是本地部署的关键手段。
2.5 速度估算:显存带宽决定 token 生成速度
解码速度的经验公式可以写成:
理论最大 tokens/s ≈ 显存带宽(GB/s) ÷ 单 token 需要读取的模型字节数单 token 解码大概需要读取全部权重一次,外加 KV Cache 中相关部分。于是 INT4 模型在 300GB/s 带宽的显卡上,理论上限大约是300 ÷ 3.5 ≈ 85 tokens/s;同一模型 FP16 时是300 ÷ 13 ≈ 23 tokens/s。实际还受计算、解码策略和框架调度影响,通常只能达到理论值的 60% 到 80%。
这个公式解释了为什么很多本地模型工具推荐量化:量化后带宽压力成倍下降,速度提升非常明显。如果某个现成计算器只给你“能不能运行”而没有“大概多少 token/s”,选型时仍然缺一块重要信息。
3. 用 Python 实现一个最小可用的硬件需求计算器
3.1 计算器的输入和输出设计
一个可直接使用的最小版本,应当接收以下输入:
- 模型参数量(B,十亿)
- 加载精度(fp32 / fp16 / bf16 / int8 / int4)
- 层数、KV 头数、Head Dim
- 最大上下文长度
- 系统内存还是 GPU 显存
- 是否包含余量
输出应当包含:
- 权重显存
- KV Cache 显存
- 运行时开销
- 总建议显存
- 理论最大解码速度
- 可运行的显卡显存档位
学习环境使用纯 Python 实现即可,不依赖 PyTorch 和 CUDA,方便快速验证思路。
3.2 核心代码实现
""" local_llm_hardware_calculator.py 本地 LLM 硬件需求最小计算器,思路示例,实际项目按需扩展。 """ PRECISION_BYTES = { "fp32": 4, "fp16": 2, "bf16": 2, "int8": 1, "int4": 0.5, } def gb(size_bytes: float) -> float: return size_bytes / (1024 ** 3) def weight_memory_gb(num_params_b: float, precision: str) -> float: """权重显存:参数量 x 每参数字节数。""" bytes_per_param = PRECISION_BYTES[precision] return gb(num_params_b * 1e9 * bytes_per_param) def kv_cache_memory_gb( num_layers: int, num_kv_heads: int, head_dim: int, seq_len: int, precision: str, ) -> float: """KV Cache 显存:2 x 层数 x KV头数 x Head Dim x 序列长度 x 字节数。""" return gb( 2 * num_layers * num_kv_heads * head_dim * seq_len * PRECISION_BYTES[precision] ) def estimate_total( num_params_b: float, precision: str, num_layers: int, num_kv_heads: int, head_dim: int, seq_len: int, memory_bandwidth_gbps: float, runtime_overhead_gb: float = 1.0, safety_margin: float = 0.25, ) -> dict: weight_gb = weight_memory_gb(num_params_b, precision) kv_gb = kv_cache_memory_gb( num_layers, num_kv_heads, head_dim, seq_len, precision ) base_gb = weight_gb + kv_gb + runtime_overhead_gb total_gb = base_gb * (1 + safety_margin) if precision in ("int4", "int8"): model_read_gb = weight_gb else: model_read_gb = weight_gb theoretical_tps = memory_bandwidth_gbps / model_read_gb return { "weight_gb": round(weight_gb, 2), "kv_cache_gb": round(kv_gb, 2), "runtime_overhead_gb": runtime_overhead_gb, "total_suggested_gb": round(total_gb, 2), "theoretical_tokens_per_sec": round(theoretical_tps, 1), } if __name__ == "__main__": result = estimate_total( num_params_b=7, precision="int8", num_layers=32, num_kv_heads=8, head_dim=128, seq_len=4096, memory_bandwidth_gbps=300, ) for key, value in result.items(): print(f"{key}: {value}")运行命令:
python3 local_llm_hardware_calculator.py正常输出类似:
weight_gb: 6.52 kv_cache_gb: 0.5 runtime_overhead_gb: 1.0 total_suggested_gb: 10.02 theoretical_tokens_per_sec: 46.0代码里几个地方要说明。weight_memory_gb用1e9做参数量到实际数量的换算,这是模型配置里常见的写法;kv_cache_memory_gb严格实现公式,前面乘以 2 代表 Key 和 Value 两份;theoretical_tokens_per_sec用显存带宽除以每 token 需要读取的权重字节数,是理想上限,真实值要再打折扣。
3.3 参数敏感性:看上下文和量化如何影响结果
把同一个模型分别按不同上下文长度和量化精度计算,结果如下:
| 精度 | 上下文 4096 | 上下文 32768 | 上下文 131072 |
|---|---|---|---|
| INT4 | 约 4.8GB | 约 7.3GB | 约 18.3GB |
| INT8 | 约 8.0GB | 约 10.5GB | 约 21.5GB |
| FP16 | 约 14.5GB | 约 17.0GB | 约 28.0GB |
这里假设模型为 7B,KV Cache 配置固定。可以看出,权重只占一部分,上下文越长,KV Cache 的占比越高。到 128K 上下文时,不同精度的差距被 KV Cache 拉平,因为 KV Cache 本身占大头。这就是为什么只算权重的计算器在长上下文场景下会严重失真。
3.4 结合 PyTorch 读取真实参数量
实际项目中,不要依赖“模型名字里写的 7B”,应该直接从模型文件读取:
from transformers import AutoConfig config = AutoConfig.from_pretrained("你的模型路径") num_params = config.num_hidden_layers * config.hidden_size # 估算,不精确 # 更精确的方式:加载后统计 from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("你的模型路径", device_map="meta") total_params = sum(p.numel() for p in model.parameters()) print(f"总参数量: {total_params / 1e9:.2f}B")device_map="meta"会在不加载实际权重的情况下统计参数量,避免把整个大模型读进内存后才发现显存不够。这个步骤在选型前做一次,比所有估算公式都可靠。
4. 量化级别、精度和效果对照
4.1 常见量化方案怎么选
量化本质是减少每参数占用的字节数,代价是精度损失。不同量化方案的区别不只是“几位”,还包括是否按层/按激活值做混合精度、是否需要校准数据。
| 方式 | 每参数字节数 | 典型 7B 权重 | 是否需校准集 | 适用场景 |
|---|---|---|---|---|
| FP16 / BF16 | 2 | 13GB | 否 | 追求质量,GPU 显存充足 |
| INT8 GPTQ / AWQ | 1 | 6.5GB | 是(GPTQ 需要,AWQ 需要少量) | 显存中等,质量损失较小 |
| INT4 GPTQ / AWQ | 0.5 | 3.3GB | 是 | 消费级显卡首选 |
| GGUF Q4_K_M | 约 0.55 | 约 3.8GB | 一般不需要 | CPU/GPU 混合推理,llama.cpp 系 |
| GGUF Q8_0 | 约 0.85 | 约 6GB | 不需要 | 追求速度和质量平衡 |
表格里的具体数值会随量化格式的额外开销变化,不能当作精确标准。选型时要记住一个原则:INT4 省显存最明显,但如果你既要长上下文又要低困惑度,可以考虑 INT8;如果显存充足,FP16 永远最简单,不需要处理量化带来的兼容性。
4.2 量化对硬件需求计算器的意义
计算器里,精度参数是权重显存计算的倍数因子,也是 KV Cache 计算的倍数因子。KV Cache 量化是单独的策略,有些框架支持 KV Cache 用 INT8,而权重用 INT4,此时两部分要分别计算,不能只用一个精度参数覆盖全部。
这一点在现成计算器中经常被简化,实际部署时却很重要。llama.cpp 的-ctk、-ctv参数可以分别控制 Key Cache 和 Value Cache 类型,PyTorch 侧也有 KV Cache 量化方案。如果计算器不支持分开设置,长上下文部署时要做额外手动修正。
5. CPU、内存与异构部署对计算结果的影响
5.1 CPU 推理和 GPU 推理的差异
本地 LLM 不只有 GPU 一种运行方式。llama.cpp 可以只用 CPU 跑,此时显存需求变成内存需求,但速度会明显下降。估算逻辑不变,只是把“显存带宽”换成“DDR 内存带宽”,把“CUDA 上下文开销”换成“进程内存开销”。
| 运行方式 | 关键资源 | 典型速度 | 适用场景 |
|---|---|---|---|
| GPU 全量加载 | 显存容量 + 显存带宽 | 快 | 交互式对话、生产服务 |
| GPU + CPU offload | 显存 + DDR 内存 + PCIe 带宽 | 中等偏慢 | 显存不足但内存充足 |
| CPU 全量加载 | DDR 内存带宽 | 慢 | 验证流程、低并发任务 |
| NPU / 集成显卡 | 专用内存或共享内存 | 不稳定 | 依赖具体硬件和框架 |
CPU 全量加载 7B INT4 模型时,理论速度取决于内存带宽。普通 DDR4 双通道带宽约 25GB/s 到 40GB/s,除以 3.8GB,每秒只能生成几个 token,体验明显不如 GPU。但优点是内存比显存便宜很多,64GB 内存的二手工作站也能跑较大的量化模型。
5.2 显存不足时的三个替代方向
如果计算结果超出现有显卡显存,有三个方向可以调整,而不是直接放弃:
- 降量化位数:从 FP16 降到 INT4,显存需求可以缩小约四倍,是成本最低的调整方式。
- 缩短最大上下文:KV Cache 与序列长度线性相关。从 32K 降到 8K,KV Cache 直接缩小四倍。
- 使用 GPU + CPU 混合 offload:把部分层放到系统内存,牺牲速度换取容量。
优先顺序通常是:先量化,再缩上下文,最后才考虑 offload。因为 offload 涉及 PCIe 数据传输,每个 token 都要在 CPU 和 GPU 之间搬运层数据,速度损失非常明显。计算器应该能分别展示每种策略下的结果,帮助用户做对比。
6. 常见估算错误和排查路径
6.1 四个最典型的估算坑
| 错误现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 按参数量算 7GB,实际 12GB 卡仍 OOM | 只算了权重,漏算 KV Cache 和 CUDA 开销 | 用本文公式分项计算,观察 OOM 日志 | 加入 KV Cache、运行时开销和余量 |
| 长对话进行到一半 OOM | KV Cache 随上下文增长,超出初始余量 | 监控显存曲线,查看上下文 token 数 | 降低最大上下文,或用 KV Cache 量化 |
| 量化后速度没有明显提升 | 显存带宽是瓶颈,容量不是速度全部决定因素 | 用nvidia-smi查看显存占用和应用日志 | 检查是否真正加载了量化权重,否则考虑换带宽更高的卡 |
| 计算器说能跑,实际框架报不兼容 | 模型格式和框架精度不支持自定义假设 | 查看模型文件格式、量化方法和推理框架日志 | 用 GGUF 或对应框架支持的格式重新导出 |
6.2 从“算出来能跑”到“实际跑不动”的排查链路
遇到部署失败时,按这个顺序排查,不要直接换硬件:
- 确认输入参数是否正确:模型实际参数量是多少,量化格式是否被框架支持,上下文长度设置是否和估算一致。
- 确认显存可用量:
nvidia-smi中Free是否接近规格,其他进程是否占用显存,桌面环境是否占了显存。 - 确认模型文件格式:同一个 7B 模型,FP16 的
.bin、INT8 的.safetensors、INT4 的 GGUF 文件显存占用完全不同。 - 确认框架是否真的把权重放进了显存:
nvidia-smi看占用进程是哪个,vram是否持续增长。 - 确认是启动时 OOM 还是运行中 OOM:启动时 OOM 一般是权重加 CUDA 上下文超限;运行中 OOM 大概率是 KV Cache 增长导致。
- 最后再用
py-spy dump或框架日志定位是分配器问题还是算力不足。
举例说明,一个常见的启动时报错如下:
RuntimeError: CUDA error: out of memory出现这个错误时,先看nvidia-smi的进程占用,再减去 300MB 到 500MB 的 CUDA 上下文开销,再对比估算值。如果差距在一个量化级别以内,优先考虑降低上下文长度,而不是立刻买新卡。
7. 硬件选型和部署验证的最佳实践
7.1 选型前检查清单
购买显卡或选择服务器前,按以下清单逐项确认:
- 确定目标模型:具体模型名、实际参数量、上下文长度需求。
- 确定量化方案:优先 FP16 还是 INT4,是否接受质量损失。
- 计算权重显存:参考模型仓库给出的加载方法说明。
- 计算 KV Cache:按最长上下文和 batch 计算,多用户并发还要乘以并发数。
- 加入固定开销:至少加 1GB 的运行时开销。
- 留足余量:生产环境建议各资源预留 30% 以上。
- 预估解码速度:用显存带宽除以每 token 读取字节数。
- 检查配套资源:系统内存、PCIe 通道、电源供电、散热空间。
- 用真实加载测试验证:选型锁定前,先在目标机器上做一次实际推理,统计峰值显存和实际 token/s。
7.2 学习环境和生产环境的差异
学习环境只需要快速验证模型效果,可以使用最小配置:小模型、INT4 量化、短上下文、batch=1。此时总显存需求可以按公式下限估算,不需要预留太多并发余量。
生产环境完全不同。实时服务要考虑多请求并发、长连接保持、模型热切换、日志和监控开销。此时不能只算单用户单请求的显存,要对峰值并发数做乘法。例如单请求占 10GB,4 并发就不能简单写成 40GB,还要考虑 batch 解码时激活值增长和调度开销。
推荐的做法是:先用本文计算器估算单实例需求,再乘以期望并发数,再加上 30% 余量,最后除以显卡单卡显存,得到最少需要几张卡。如果计算结果是 2.4 张,那就按 3 张规划,不要用“差不多 2 张”来赌。
7.3 部署后的验证方法
模型跑起来之后,还要做三项验证:
- 峰值显存验证:开启
nvidia-smi -l 1或使用torch.cuda.max_memory_allocated()记录峰值,确认没有超过计算器预计值。 - 长对话验证:设计一个超过最大上下文的对话场景,确认超过限制后有明确的截断或报错,而不是掉到 CPU 慢速模式。
- 速度验证:用相同 prompt 连续生成 100 个 token,统计实际耗时,与理论值对比。如果差距超过 50%,排查是否存在 CPU offload、降频或内存交换。
验证结果应该回写进计算器输入。不同框架、不同量化格式的实际开销不同,长期维护一个本地部署记录,比每次重新估算更准确。这也是硬件需求计算器类工具在有经验的开发者手中真正发挥作用的地方:它不是一次性选型工具,而是一个持续校准的部署基线。
把显存需求拆成权重、KV Cache、运行时开销三部分,是本地 LLM 硬件估算的核心方法。现成的在线计算器可以直接用,但只有理解了公式,才能在长上下文、量化、并发这些变量变化时,判断结果是否合理。建议下一步把本文的最小 Python 计算器扩展成支持模型配置文件解析、多并发计算和速度实测回填的版本,再结合自己常用模型的实测数据,形成一份专属的本地 LLM 部署速查表。