中小企业跑125B 大模型:18GB 显存之差,到底值多少吞吐

0 阅读19分钟

中小企业跑125B 大模型:18GB 显存之差,到底值多少吞吐请添加图片描述

作者:吴佳浩 撰稿时间:2026-10-24 测试环境:

  • GPU:RTX 5090(32GB VRAM,驱动 580.142,CUDA 13.0,PCIe Gen3 ×16,实测 13.9 GB/s)
  • CPU:Intel Xeon Gold 5218 ×2(32 物理核 / 64 线程,2 个 NUMA 节点,无 AVX512-BF16)
  • Memory:125.6 GiB RAM
  • OS:Ubuntu 20.04.3 LTS(kernel 5.15.0-58)
  • Engine:Strata v0.2.2(engine 0.1.47,CUDA 13.0,官方容器镜像)
  • Model:Qwen3.8-Flash-Next Q2_0(125B 级 MoE,24,576 专家槽)
  • 两轮配置完全相同:CONTEXT=32k / KV=int8 / PARALLEL=1 / VISION=cpu / MTP spec4
  • 唯一变量:第一轮同机其他容器占了 GPU1 的 18,106 MiB;第二轮停掉它们

引言

面向工程师与研究者的体系化分析。 第一篇讲清了机制。这一篇只做一件事:把机制里的每一段,用实测数据钉死。 我把同一台机器、同一份模型、同一套参数跑了两轮,唯一区别是显卡上有没有别人的 18 GB。然后我从引擎日志里挖出 2,890 条逐请求遥测(每条都带「专家缓存命中率」和「PCIe 溢出比例」),把「显存 → 吞吐」这条链逐段拆开。 你可以把这篇当成一份「容量规划参考表」:给定显存,我告诉你能期待多少 tok/s;给定速度目标,我告诉你需要多少显存。

注:全文所有性能数字均来自本机两轮真实压测(合计 1,507 次请求、1,362 个资源采样点、317 个服务端指标采样点),未经验证的推算会明确标注。因为阿拉好奇,所以停掉别人占的卡,一道来白相相——看看这 18 GB 是"锦上添花"还是"生死攸关"。


目录

  1. 实验设计:只改一个变量
  2. 第一跳:显存 → 缓存覆盖率(干净的线性)
  3. 第二跳:覆盖率 → 命中率(长尾的胜利)
  4. 第三跳:命中率 → PCIe 溢出(断崖)
  5. 为什么 1% 的溢出贵 39%
  6. 三层效率:绝对、边际、能源
  7. 反例:显存多了,为什么不能开 parallel=4
  8. 硬件资源画像与瓶颈定位
  9. 稳定性与并发:排队语义的实证
  10. 结论与容量规划表

一、实验设计:只改一个变量

1.1 为什么这个实验值得做

大多数「本地大模型性能」文章的问题是:所有变量一起变。换了量化档、换了上下文、换了并发,然后报一个 decode 数字——这个数字没法归因。

我这次刻意做成单变量对照:

变量第一轮第二轮
模型Q2_0Q2_0(同一份文件)
引擎0.2.2 / engine 0.1.47同
上下文32K32K
KV 精度int8int8
parallel11
视觉读图器CPUCPU
MTPspec4spec4
GPU1 上其他占用18,106 MiB(3 个容器)0

1.2 停掉的东西,以及怎么回滚

先交代清楚,避免误会成"破坏性操作":

容器镜像占用的 GPU1 显存停止前已运行
ms_reranker7ms_reranker:latest8,730 MiB8 天
ms_reranker8ms_reranker:latest4,688 MiB8 天
ms_reranker9ms_reranker:latest4,688 MiB8 天
合计18,106 MiB
  • 停止方式是 docker stop -t 30(优雅停止,不是 kill)
  • 容器没有被删除,docker start ms_reranker7 ms_reranker8 ms_reranker9 即可完整恢复
  • 完整容器配置快照已存档(docker inspect 全量 JSON)
  • 同机另一张卡(RTX 4090)上的 vLLM 全程未动

停完之后 GPU1 的状态:

index, name, memory.total, memory.used, memory.free
1, NVIDIA GeForce RTX 5090, 32607 MiB, 13743 MiB, 18367 MiB
pid, process_name, used_gpu_memory
2027940, .../strata, 13670 MiB

1.3 重部署后的第一件事:看缓存怎么自动扩

同一个镜像、同一套 -e 环境变量,重启后引擎自己把缓存吃满了:

strata generate: expert cache auto: 8.31 GiB free, 700 MiB reserved (+184 MiB for the draft head) -> 5784 slots      # 第一轮
strata generate: expert cache auto: 26.01 GiB free, 700 MiB reserved (+184 MiB for the draft head) -> 19530 slots    # 第二轮

5,784 → 19,530,3.38 倍。 这就是第二篇全部故事的起点。

mermaid diagram


二、第一跳:显存 → 缓存覆盖率(干净的线性)

2.1 实测值

轮次启动时空闲固定预留每槽计算实际槽位覆盖率
第一轮8,509 MiB884 MiB1.318 MiB(8509−884)/1.318 = 5,7865,78423.5%
第二轮26,634 MiB884 MiB1.318 MiB(26634−884)/1.318 = 19,53719,53079.5%

两轮完全吻合,误差在 0.04% 内(来自字节对齐取整)。这是整条链上唯一一段"可以用计算器算出来"的关系,也是本文最有工程价值的一段——它意味着你不需要跑基准就能预估覆盖率。

2.2 固定开销恒定,是这条线性关系的前提

轮次引擎总显存专家缓存固定开销
第一轮13,670 MiB7,625 MiB6,045 MiB
第二轮31,674 MiB25,747 MiB5,927 MiB

差 118 MiB,2%。固定开销不随缓存变大而变大,所以「多给的显存」几乎全部落进缓存。

mermaid diagram


三、第二跳:覆盖率 → 命中率(长尾的胜利)

3.1 实测

从引擎日志里抽出每条请求的 decode expert cache hit rate,按轮次统计(取生成 150–260 token 的可比样本):

轮次命中率 p50样本数
第一轮(共享)86.5%1,192
第二轮(独占)99.7%1,000

覆盖率 +56 个百分点,命中率只 +13.2 个百分点。 看起来像"收益递减",其实是长尾分布。

3.2 为什么覆盖率 23.5% 就能命中 86.5%

因为专家访问极度倾斜。Strata 有一个 expert-profile.bin(24,576 个排名对),按路由频率决定谁先占缓存:

strata generate: profile /opt/strata/terminal/data/expert-profile.bin: 24576 ranked pairs, built for 24576 slots
strata generate: pre-filled 5784 of 5784 slots from the profile in 0.6 s (12647 MB/s); slot 0 verified

于是第一轮那 23.5% 的槽位,装的恰好是最热的专家:

mermaid diagram

3.3 关键不在"命中率高低",在"剩下多少"

这里的直觉陷阱是:86.5% 听起来很高(接近九成),所以会觉得"还行"。

但反过来看才是对的:

第一轮:未命中 13.5%
第二轮:未命中  0.3%
→ 未命中的量差了 45 倍

性能是被"未命中"驱动的,不是被"命中"驱动的。 所以后面所有数据都指向同一个方向:盯着那 0.3% 到 13.5% 的区间,而不是盯着 86.5% 这个整数。


四、第三跳:命中率 → PCIe 溢出(断崖)

4.1 先把 2,890 条遥测摊开

我从引擎日志里把「专家缓存命中率」和「PCIe 溢出比例」两条逐请求记录配对,得到 2,890 条样本(跨两轮,覆盖短问答、长上下文、并发、过载各种负载)。按 PCIe 溢出比例分桶:

PCIe 溢出比例样本数Decode p50相对最高
0 – 0.5%1,681231.8 tok/s100%
0.5 – 1%799141.4 tok/s61%
1 – 2%151138.0 tok/s60%
2 – 3%186137.7 tok/s59%
3 – 5%33132.3 tok/s57%
5 – 10%1771.9 tok/s31%
10 – 20%2269.8 tok/s30%

mermaid diagram

这张图是本文最重要的一张。

三个特征:

  1. 0 → 0.5% 之间是断崖:231.8 掉到 141.4,−39%。
  2. 0.5% → 3% 几乎是平台:141.4 → 137.7,只掉 2.6%。说明"已经很慢了,再慢一点点也无所谓"。
  3. 5% 以上二次断崖:141 → 72,再掉一半。这时候系统已经进入"搬运主导"模式。

4.2 命中率分桶交叉验证

同一批样本按命中率分桶,结论一致:

命中率样本Decode p50Decode 区间
30–50%557.427.0 – 83.6
50–60%966.633.2 – 132.3
60–70%149136.926.8 – 145.4
70–80%137138.848.3 – 155.7
80–90%1,403141.167.3 – 178.8
90–95%128143.199.7 – 211.2
95–99%90280.3126.2 – 309.0
99–100%969249.9168.0 – 342.6

Pearson 相关系数 r = 0.779(n = 2,890)。

注意 95% 处的跳变:90–95% 还在 143,95–99% 直接 280。这不是线性爬坡,是跨过阈值后的换挡。

mermaid diagram

4.3 分轮次的直接对比

轮次命中率 p50PCIe 溢出 p50Decode p50区间
第一轮86.5%0.50%139.867 – 168
第二轮99.7%0.00%253.1126 – 343

第二轮把 PCIe 溢出打到了 0.00% —— 这是「+82% 吞吐」的直接来源。


五、为什么 1% 的溢出贵 39%

5.1 先按带宽算一遍,发现算不通

每 token 480 次专家查表(48 层 × 10 专家)
× 1% 溢出 = 4.8 个专家
× 1.38 MB / 专家 = 6.6 MB / token
÷ 13.9 GB/s(实测 PCIe Gen3 ×16 带宽)= 0.47 ms / token

而 141 tok/s = 7.1 ms / token
→ 0.47 / 7.1 = 6.6%

理论上只该慢 6.6%,实测慢了 39%。差了 6 倍。

所以代价不是"搬得慢",是"等得久"。

5.2 真正的成本:流水线气泡

mermaid diagram

GPU 的计算是高度并行的,但依赖是串行的。一批专家没到,后面的算子只能干等;等的时候 SM 是 idle 的,但驱动仍然报告「occupied」——这就是「利用率 89% 却只跑一半速度」的来源。

5.3 用功耗曲线把"假忙"坐实

轮次GPU 利用率(均值)功耗均值功耗峰值功耗占上限
第一轮89.1%229.7 W311.9 W40% – 54%
第二轮89.8%336.7 W472.4 W59% – 82%

利用率几乎一样(89.1% vs 89.8%),功耗却差了 47%。

mermaid diagram

这是我这轮实验里觉得最值得记住的一条:

GPU 利用率是「有没有活干」,功耗才是「干的活有没有产出」。 两者背离时,瓶颈一定在数据搬运路径上。


六、三层效率:绝对、边际、能源

同样一组数字,用三种不同的分母去看,会得出三种不同的结论。这一节把三种都算出来,因为它们经常被混用。

6.1 绝对效率:每 GiB 显存换多少 tok/s

轮次引擎显存Decodetok/s per GiB
第一轮13.4 GiB140.610.5
第二轮30.9 GiB256.28.3

看到这里,很多人会下结论:"边际收益递减"。这个结论是错的,因为它把固定开销也算进了分母。

6.2 边际效率:每多 1 GiB 专家缓存

Δ专家缓存 = 25,747 − 7,625 = 18,122 MiB ≈ 17.7 GiB
Δdecode   = 256.2 − 140.6 = 115.6 tok/s
→ 6.53 tok/s per GiB 专家缓存

正确算法是做线性拟合、把截距算出来:

第一轮:7.45 GiB 缓存 → 140.6 tok/s
第二轮:25.14 GiB 缓存 → 256.2 tok/s

斜率 = (256.2 − 140.6) / (25.14 − 7.45) = 6.53 tok/s per GiB
截距 = 140.6 − 7.45 × 6.53 = 91.9 tok/s

也就是说:每加 1 GiB 缓存,线性加 6.53 tok/s。 截距 91.9 的含义是「缓存为 0 时的理论速度」——这是一个外推值,未实测,只用来验证线性拟合的自洽性,不能当成真实性能引用。

mermaid diagram

这条线性关系是本篇最实用的结论:它让「给定显存预测速度」变成一道乘法题,而不是必须跑基准。

6.3 能源效率:每 token 耗多少焦耳

这是最常被忽略、但对生产部署最重要的一层:

轮次功耗均值DecodeJ / token相对
第一轮229.7 W140.6 tok/s1.634 J基准
第二轮336.7 W256.2 tok/s1.314 J−19.6%

mermaid diagram

多花 47% 的电,做出 82% 更多的活 → 单位能耗降 20%。 这还是"假忙"的代价:等 PCIe 的时候 GPU 照样耗电,只是不产出。

6.4 三层汇总

层次指标第一轮第二轮正确结论
绝对tok/s per GiB 总显存10.58.3表面递减,误导
边际tok/s per GiB 缓存—6.53线性,不递减
能源J per token1.6341.314−19.6%

七、反例:显存多了,为什么不能开 parallel=4

显存变多之后,最自然的想法是"开更多并发"。我用 4 组配置做了对照,结论否定了这个直觉。

7.1 四组配置实测

配置缓存slotsparallelc=1 decodec=4 每客户端聚合峰值
B1 基线 32K25.14 GiB19,5301204.3—220.0(c=2)
B2a 32K22.90 GiB17,7894185.758.1179.3(c=8)
B2d 64K22.25 GiB17,2792128.979.4149.5(c=8)
B3 推荐 64K23.48 GiB18,2401218.9—214.7(c=8)

关键发现:parallel=4 的聚合吞吐(179.3)反而低于 parallel=1(220.0),而且单流 decode 从 204 掉到 186,4 并发时每客户端只剩 58 tok/s。

7.2 机制:并发不是扩容,是切分

mermaid diagram

parallel=N 不是「把车加宽」,是「把油分成 N 份」。 每个 slot 的覆盖率降到约 1/N,PCIe 溢出暴涨,于是第四节那个"断崖"同时触发 N 次。

7.3 什么时候该开多槽

mermaid diagram

在这台 32GB 的卡上,parallel=1 是唯一正确解。


八、硬件资源画像与瓶颈定位

8.1 两轮的资源对比

采样 2 秒一次,两轮合计 1,362 个有效采样点:

指标第一轮(共享)第二轮(独占)第二轮推荐配置
GPU 利用率 均值 / p95 / max89.1% / 100% / 100%84.3% / 100% / 100%89.8% / 100% / 100%
显存占用31,864 MiB31,879 MiB31,830 MiB
显存占用率97.7%97.8%97.6%
温度 均值 / max44.5 / 53 ℃52.8 / 59 ℃52.1 / 55 ℃
功耗 均值 / max229.7 / 311 W336.7 / 472 W336.8 / 466 W
SM 频率 均值 / max2,828 / 2,895 MHz2,820 / 2,887 MHz2,832 / 2,895 MHz
CPU load1 均值 / max26.3 / 32.120.3 / 28.024.2 / 30.4
系统内存 均值104.97 GiB96.31 GiB96.74 GiB
MTP 接受率64.0%63.5%—

8.2 排除法:四个"常见嫌疑"全都不成立

mermaid diagram

8.3 两个硬约束

约束一:显存上限决定了覆盖率上限。

卡上可用 ≈ 31.8 GiB
− 固定开销 6.0 GiB
= 25.8 GiB 可给缓存
÷ 1.318 MiB/槽 = 19,600 槽
÷ 24,576 = 79.8%

在 32GB 卡上,80% 覆盖就是天花板。 想上 100%,需要约 38.5 GiB 可用显存(= 32.1 GiB 缓存 + 6.0 GiB 开销),也就是 48GB 的卡。

约束二:PCIe Gen3 ×16 = 13.9 GB/s。

strata generate: PCIe probe: 13.9 GB/s host->device (best of 13.8 13.8 13.9 13.9) -> pcie_frac 0.38 (default 0.55)

这是引擎自己测的,也是第 4 节那个断崖的物理基础。RTX 5090 本身支持 Gen5,这张卡却插在 Gen3 ×16 的槽上——链路能力只有卡能力的四分之一。

8.4 瓶颈已经转移

阶段主要瓶颈证据
第一轮显存不足 → 缓存小 → 专家走 PCIe覆盖率 23.5%;功耗只用 54%;CPU 只 26/64 线程
第二轮PCIe Gen3 + 单 slot覆盖率 79.8%;功耗用到 82%;decode 256 tok/s;并发不扩容

九、稳定性与并发:排队语义的实证

9.1 长稳:两轮都零失败

指标第一轮(30 min @ c=1)第二轮(10 min @ c=1)
请求 / 成功1,122 / 1,122(100%)656 / 656(100%)
请求速率37.19 req/min64.52 req/min(+74%)
TTFT 均值 / p95 / max0.240 / 0.258 / 0.336 s0.158 / 0.165 / 0.218 s
Decode 均值 / p95 / min140.55 / 157.46 / 112.38256.2 / 326.2 / 200.7
显存漂移0 MiB(31,875 恒定)0 MiB(31,879 恒定)
指标第一轮(10 min @ c=4)第二轮(10 min @ c=4)
请求 / 成功385 / 385660 / 660
请求吞吐0.63 req/s1.07 req/s(+70%)
聚合 token 吞吐119.0 tok/s204.6 tok/s(+72%)
TTFT p50 / p95 / p994.958 / 5.238 / 5.355 s2.89 / 3.10 / 3.16 s
漂移(前/后半程)4.967→4.954 s2.884→2.889 s

两轮都是 100% 成功、零漂移、零显存泄漏。

9.2 并发扫描:吞吐平坦,延迟线性

并发 c第一轮 聚合 tok/s第二轮 聚合 tok/s第一轮 TTFT p50第二轮 TTFT p50
186.8192.00.28 s0.16 s
2116.4220.01.63 s0.80 s
4119.4216.53.24 s1.94 s
8122.0213.26.23 s3.54 s
16118.6209.313.15 s7.28 s

这是「排队语义」的直接证据:

c:      1     2     4     8     16
吞吐:  192   220   216   213   209     ← 平坦(甚至微降)
TTFT: 0.16  0.80  1.94  3.54  7.28    ← 近似线性增长

单张卡的专家搬运带宽是共享的。多客户端不会让它更快,只会让每个请求等更久。 这也解释了为什么第 7 节里 parallel=4 反而更差——它连"真并发"都没换来,只换来了缓存被切碎。

9.3 过载:32 并发时的行为

指标第一轮第二轮
请求 / 成功32 / 32(100%,全 200)32 / 32(全 200)
墙钟20.94 s13.0 s
TTFT p50 / p95 / max10.95 / 19.19 / 20.47 s6.69 / 12.01 / 12.79 s

两轮都没有拒绝请求,全部排队完成。这说明这套部署没有启用准入控制——生产环境该加队列上限,否则延迟会不可控地膨胀。


十、结论与容量规划表

10.1 五段传导链的实测结论

mermaid diagram

10.2 容量规划速查表

基于实测的两点(固定开销 ≈ 6.0 GiB、1.318 MiB/槽)和线性拟合(截距 92 tok/s、斜率 6.53 tok/s per GiB):

整卡可给引擎专家缓存覆盖率预期命中率预期 decode说明
12 GiB5.1 GiB16%~82%~125 tok/s勉强可用(缓存很薄)
14.2 GiB7.45 GiB23.5%86.5%140.6 tok/s实测第一轮
18 GiB11.1 GiB35%~90%~165 tok/s推算
24 GiB17.1 GiB54%~96%~204 tok/s推算
31.8 GiB25.1 GiB79.5%99.7%256.2 tok/s实测第二轮(整卡)
≥ 38.5 GiB32.1 GiB100%100%~300 tok/s48GB 卡可全驻留(推算)

换算关系:缓存 ≈ 整卡可用 − 6.0 GiB,槽位 = (缓存字节 − 884 MiB) / 1.318 MiB,decode ≈ 92 + 6.53 × 缓存GiB(92 为拟合截距,外推值)。

mermaid diagram

10.3 三句话收尾

mermaid diagram

一句关键判断:显存不是"够不够",是"热专家放不放得下";而放不下的代价不是线性打折,是断崖。

10.4 这一篇的测量限制

  • Decode 跨轮次可比,跨内容不完全可比:MTP 接受率随生成内容波动(实测 63.5%–64.0%,但单请求区间更宽),同轮内不同请求的 decode 天然有 ±15% 波动。
  • 命中率与速度是相关性(r=0.779),不是严格因果;两者都由缓存覆盖率驱动,但请求长度、上下文长度是混杂因素。
  • 容量规划表的 12 / 18 / 24 / 38.5 GiB 四行为推算,非实测;截距 92 tok/s 也是外推。
  • KV 与"激活/CUDA 上下文"为结构推算,误差约 ±300 MiB。
  • 只测了 Q2_0 + 32K/64K + parallel 1/2/4,未测 IQ3_XXS 等其他档位、未测多卡。
  • PCIe 固定 Gen3 ×16:换到 Gen4/Gen5 后第 4 节的断崖位置会移动,"溢出比例"的门槛会变宽松,但"溢出 → 掉档"的定性结论不变。
  • 同机另一张 4090 全程被 vLLM 占据(43,573 MiB,计算利用率 0%),未参与测量,但可能对 PCIe/内存带宽有轻微干扰。

附录 A:两轮基础压测原始数据

A.1 官方标准基准(strata bench run,同协议)

用例第一轮第二轮变化
short decode / TTFT136.8 tok/s / 0.389 s235.4 tok/s / 0.23 s+72% / −41%
4K prefill / TTFT / decode1,710.5 / 2.416 s / 132.63,520.6 / 1.18 s / 205.7+106% / −51% / +55%
16K prefill / TTFT / decode3,314.4 / 4.99 s / 134.85,528.9 / 2.99 s / 218.4+67% / −40% / +62%
质量检查(12 项)11/1212/12+1
21 请求总耗时81 s47 s−42%

A.2 长上下文对比(冷前缀)

提示词规模第一轮第二轮
5,702 tok2.54 s / prefill 2,204 / decode 139.01.42 s / 3,851 / 236.7
23,301 tok7.31 s / 3,182 / 153.84.27 s / 5,452 / 246.9
29,133 tok9.56 s / 3,047 / 136.8—
34,941 tok❌ 400(超 32K 窗口)—

A.3 新解锁的能力

能力第一轮第二轮(推荐配置)
最长可用提示词29,133 tok(32K 窗口)43,677 tok(64K 窗口)
新图片分析 TTFT3.57 s(CPU 读图)0.534 s(GPU 读图)
图片显存代价无法开启(只剩 410 MiB)~1,645 MiB(≈ −11 tok/s)

附录 B:2,890 条遥测是怎么来的

引擎每处理完一条请求会写两行日志:

strata serve: prompt 26 tokens = 0 reused + 26 read in 43 ms, 192 generated in 1367 ms (140.5 tok/s), drafts accepted ...
strata serve: decode expert cache hit rate: 86.3% (23414 hits / 27131 lookups); 412 more read by the GPU over PCIe or from another GPU (2.31% of all 17850 routed)

我用脚本按顺序配对这两种行,得到每条请求的三元组 (命中率, PCIe 溢出比例, decode),跨两轮共 2,890 条。

  • 采样覆盖:官方基准(短/4K/16K)、并发扫描(c=1/2/4/8/16)、长稳(c=1 与 c=4)、长上下文(4K/16K/18K/20K)、图片输入、过载(32 并发)
  • 配对方式:只接受"生成行在前、命中率行紧随其后"的严格相邻对,避免错配
  • 数据集:results-fullvram/vram-efficiency-dataset.json(581 KB)
  • 相关系数按 Pearson 计算,n=2,890,r=0.779

邦友们,第二篇的数字就摆在这里了。

回头看,最让我意外的不是"+82%"这个结果,而是它其实是可以算出来的:一条 1.318 MiB/槽的公式,一个 6.53 tok/s per GiB 的斜率,一个 95% 附近的阈值。跑之前我就写下了四条预测,跑完之后四条全中——这比任何单个数字都更说明这套机制是"可理解"的,而不是"玄学"。

下一篇是这套系列的最后一篇,也是最能直接照做的一篇:从硬件自检、容器部署的三个坑、模型下载与格式校验,到启动、冒烟验证、五类压测的完整脚本,以及调优清单与故障速查表。所有命令和输出都是这台机器上真跑过的。

如果本文对你有所帮助,欢迎点赞、收藏、转发,也欢迎关注 「全栈架构师笔记」。下一次,我们继续一起把这些新东西跑明白、测明白,也讲明白。