「96GB 显存」至少对应三种不同硬件:96GB GDDR7 独显、96GB LPDDR4X 国产推理卡、从系统内存划出的 96GB 统一内存。容量一样,带宽相差约 7 倍。本文先拆清三种 96GB,再给权重 + KV Cache 的容量计算脚本、逐 token 解码的带宽上限估算,以及一套可复现的压测方法。
图注:96GB 显存的容量与带宽分层(来源:商红科技行业论坛原文配图;非实测)
先说结论
96GB 能不能跑某个模型,要分三笔账:
- 权重账:参数量 × 每参数字节;
- KV Cache 账:随上下文长度和并发线性增长;
- 带宽账:决定装进去之后跑多快,不报错但会慢。
只报「96GB 能跑 70B」是误导,因为 70B 在 32K 上下文和 128K 上下文下的 KV 占用差 4 倍。
一、先确认你说的是哪一种 96GB
| 「96GB」出现在哪 | 实际是什么 | 典型带宽 | 能否当独立显存 |
|---|---|---|---|
| RTX PRO 6000 Blackwell 96GB | 96GB GDDR7 独立显存 | 1792 GB/s | 能 |
| Atlas 300I Duo 96G | 96GB LPDDR4X 独立显存 | 408 GB/s | 能 |
| Ryzen AI Max+ 395 一类统一内存 | 从系统内存划出,最大 96GB | 256 GB/s | 能,但与内存共享总线 |
| 96GB 内存条 | 系统内存,不是显存 | 与显卡无关 | 不能 |
前三类容量相同,带宽比约为 1792 : 408 : 256 ≈ 7 : 1.6 : 1。容量决定「装不装得下」,带宽决定「每天用起来什么感觉」。
二、容量计算:权重 + KV Cache
权重部分:
权重 GiB = 参数量 × 每参数字节 / 2^30
FP16/BF16 约 2 字节/参数,FP8/INT8 约 1 字节,INT4 约 0.5 字节。
KV Cache 部分:
KV 每 token 字节 = 2 × 层数 × KV 头数 × 头维度 × 每元素字节
KV GiB = KV 每 token 字节 × 序列长度 × batch / 2^30
注意是KV 头数,不是注意力头数。GQA 模型里 KV 头数通常远小于注意力头数,KV Cache 会小一个量级。
可直接运行的容量计算脚本
#!/usr/bin/env python3
"""96GB 级显存容量规划:权重 + KV Cache,输出剩余余量。"""
GB = 1024 ** 3
def kv_per_token_bytes(layers: int, kv_heads: int, head_dim: int,
elem_bytes: int = 2) -> int:
return 2 * layers * kv_heads * head_dim * elem_bytes
def plan(params_b: float, bits: int, seq: int, batch: int,
layers: int, kv_heads: int, head_dim: int,
usable_gib: float = 96.0, kv_elem_bytes: int = 2) -> dict:
weight_gib = params_b * 1e9 * bits / 8 / GB
kv_gib = (kv_per_token_bytes(layers, kv_heads, head_dim, kv_elem_bytes)
* seq * batch / GB)
remain = usable_gib - weight_gib - kv_gib
return {
"weight_GiB": round(weight_gib, 2),
"kv_GiB": round(kv_gib, 2),
"remain_GiB": round(remain, 2),
"fits": remain > usable_gib * 0.10, # 至少留 10% 余量
}
if __name__ == "__main__":
cases = [
# 名称, 参数量B, 位宽, 序列, batch, 层数, KV头, 头维度
("70B INT8 32K", 70, 8, 32768, 1, 80, 8, 128),
("70B INT8 128K", 70, 8, 131072, 1, 80, 8, 128),
("70B INT4 128K", 70, 4, 131072, 1, 80, 8, 128),
("32B INT8 128K", 32, 8, 131072, 1, 64, 8, 128),
("32B FP16 128K", 32, 16, 131072, 1, 64, 8, 128),
]
for name, p, bits, s, b, l, kh, hd in cases:
print(name, plan(p, bits, s, b, l, kh, hd))
示例输出(96GB 可用、KV 用 FP16):
| 配置 | 权重 | 128K KV | 合计 | 结论 |
|---|---|---|---|---|
| 70B INT8 + 32K | 约 70GB | 约 10GiB | 约 80GB | 够,余量约 16GB |
| 70B INT8 + 128K | 约 70GB | 约 40GiB | 约 110GB | 装不下 |
| 70B INT4 + 128K | 约 40GB | 约 40GiB | 约 80GB | 够 |
| 32B INT8 + 128K | 约 32GB | 约 32GiB | 约 64GB | 余量充足 |
| 32B FP16 + 128K | 约 64GB | 约 32GiB | 约 96GB | 刚好卡满,不建议 |
这些数字是算术结果,不是实测;模型结构不同(层数、KV 头数)会改变结论。
三、带宽账:为什么跨过容量线是断崖
逐 token 解码时,每生成一个 token 都要把参与计算的权重读一遍。所以解码速度的上界近似为:
tokens/s ≈ 显存带宽 / 每 token 需要读取的权重字节
70B INT8 约 70GB 权重,1792 GB/s 的卡理论上界约 25.6 tokens/s;按 50%–70% 的实际效率,大约 13–18 tokens/s。这是量级判断,不是性能承诺。
一旦权重装不下、被迫把部分层卸载到系统内存,路径带宽从 1792 GB/s 变成 PCIe 5.0 ×16 的约 64 GB/s,相差约 28 倍。这就是「性能悬崖」:不是慢一点,是换了一个数量级的瓶颈。
四、可复现的压测方法
不要只看厂商给的 tokens/s。用同一个模型、同一份 prompt,分三段测:
# 1. 启动服务,限制上下文与并发
vllm serve <model> \
--max-model-len 32768 \
--gpu-memory-utilization 0.90 \
--max-num-seqs 8
# 2. 观察显存与 GPU 利用率
nvidia-smi --query-gpu=timestamp,memory.used,memory.total,utilization.gpu \
--format=csv -l 1
测试分三组:
| 组别 | 输入 | 观察 | 说明 |
|---|---|---|---|
| 短 prompt、短输出 | 256 / 128 tokens | TTFT、TPOT | 看基础延迟 |
| 长 prompt、短输出 | 8K / 128 | TTFT | 看预填充能力 |
| 短 prompt、长输出 | 256 / 4K | TPOT | 看逐 token 解码带宽 |
| 高并发 | 8 / 16 并发 | 吞吐、P99 | 看 KV Cache 与调度 |
重点看两件事:显存是否在长上下文时接近打满;输出变长后 TPOT 是否明显上升。后者是带宽受限的典型信号。
五、三个容易误判的点
MoE 省计算不省容量。106B 总参数、12B 激活的模型,权重仍要按 106B 驻留;激活参数只减少每 token 计算量。
多卡容量不等于单卡容量线性叠加。两张 48GB 不等于一张 96GB:并行切分、通信缓冲、负载不均衡都会吃掉有效容量。
MIG 切片与装大模型互斥。96GB 切成 4 个 24GB 实例后,单个模型只能用 24GB;要隔离就接受单模型预算收缩,要装大模型就整卡跑。
边界与适用条件
- 带宽上限是理论量级,实际吞吐受模型结构、量化方式、批大小、框架实现影响。
- KV Cache 的每元素字节可按需改为 FP8,但会带来精度与实现兼容问题,需实测。
- 统一内存机型的可用带宽受内存子系统影响,不能按独显带宽套算。
工程落地中的一个选择
如果要做本地推理选型,建议先用自己的模型和真实上下文跑一轮压测,再决定 96GB 档位。商红科技做工作站与服务器的选型交付,可以按「权重 + KV + 带宽」三笔账把配置和边界一起过一遍;更细的参数表与边界条件,见这份公开拆解。最终结论仍应以你自己的压测数据为准。
商红科技能接的是推理平台选型、整机部署与压测配合这一段;具体 tokens/s 不做事先承诺,以你的实测为准。