中小企业跑125B 大模型:显存装不下的时候,Strata 做了什么

中小企业跑125B 大模型:显存装不下的时候,Strata 能做什么?

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

  • GPU:RTX 5090(32GB VRAM,驱动 580.142,CUDA 13.0,PCIe Gen3 ×16)
  • CPU:Intel Xeon Gold 5218 ×2(32 物理核 / 64 线程,2 个 NUMA 节点,无 AVX512-BF16)
  • Memory:125.6 GiB RAM
  • Storage:/workspace 3.5TB(/dev/sdb1,实测顺序写 435 MB/s)
  • 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,48 层 × 512 专家 = 24,576 个专家槽)

引言

面向工程师与研究者的体系化分析。之前我们在个人pc上做了尝试今天待大家从企业视角来来看看 这条线是不是行得通。 阿拉不止讨论「跑起来了没有」,更追问「这件事对本地部署意味着什么、代价又落在哪一层」。 125B 级 MoE 模型过去只活在几百 GB 显存的服务器上。Strata 把「模型装不下」这件事,从一道算术死题,换成了一个带宽工程题。 本文是这个系列的第一篇,只讲一件事:当显存放不下模型时,Strata 到底把什么东西放在了哪里,又是怎么把「显存缺口」翻译成「吞吐」的。

注:全文所有数字均来自本机真实运行的日志与遥测,未经验证的推算会明确标注为推算。因为阿拉好奇,所以拿一块 32GB 的卡,一道来白相相------看看这套 Spill 设计是徒有虚名,还是真的结棍。


目录

  1. 问题的本质:为什么「显存占用率」是个废指标
  2. [Strata 的显存账本:三个区、一条公式](#Strata 的显存账本:三个区、一条公式 "#%E4%BA%8Cstrata-%E7%9A%84%E6%98%BE%E5%AD%98%E8%B4%A6%E6%9C%AC%E4%B8%89%E4%B8%AA%E5%8C%BA%E4%B8%80%E6%9D%A1%E5%85%AC%E5%BC%8F")
  3. [一个 token 的显存之旅](#一个 token 的显存之旅 "#%E4%B8%89%E4%B8%80%E4%B8%AA-token-%E7%9A%84%E6%98%BE%E5%AD%98%E4%B9%8B%E6%97%85")
  4. [模型是什么格式:GGUF、pack、MTP 三层产物](#模型是什么格式:GGUF、pack、MTP 三层产物 "#%E5%9B%9B%E6%A8%A1%E5%9E%8B%E6%98%AF%E4%BB%80%E4%B9%88%E6%A0%BC%E5%BC%8Fggufpackmtp-%E4%B8%89%E5%B1%82%E4%BA%A7%E7%89%A9")
  5. 效率是怎么被决定的:五段传导链
  6. 可验证的预测:把「玄学」变成可测量的量
  7. 总结

一、问题的本质:为什么「显存占用率」是个废指标

1.1 先把算术摆出来

先看这台机器上真正要放进去的东西有多大。用我这套部署里实际落盘的 Q2_0:

文件 字节数 内容
shard1 37,623,740,192 dense 权重 + 全部 24,576 个专家
shard2(PLE 表) 28,800,138,432 320,001,536 行 × 160 维的 n-gram 表
合计 66.4 GB 还不含 0.85 GB 的图像编码器与 6.5 GB 的 MTP 草稿层

而 RTX 5090 的显存是 32,607 MiB,约 31.8 GiB。

66.4 GB 对 31.8 GiB,缺口接近 2.1 倍。 这不是调参能解决的问题,是算术问题。

1.2 MoE 把「算力」和「显存」解耦了

关键在于这个模型是 MoE(混合专家):48 层,每层 512 个专家,但每个 token 每层只路由 10 个。

markdown 复制代码
稠密模型:算力 ∝ 总参数量
MoE 模型:算力 ∝ 激活参数量(远小于总量)
         显存 ∝ 总参数量(一点没少)

这条式子解释了消费级显卡的处境:算力富余,显存贫瘠。我这台机器上,压测期间 GPU 功耗峰值 472 W,而卡的上限是 575 W------算力还有余量;但显存占用稳定在 31,879 / 32,607 MiB,也就是 97.8%,一格都挤不出来了。

1.3 「显存占用率」为什么骗人

这是本文最想先纠正的一个观念。

我做了两轮完全相同的部署,唯一区别是显卡上有没有别的进程在占显存。结果:

指标 第一轮(共享) 第二轮(独占)
整卡显存占用 31,865 MiB 31,879 MiB
显存占用率 97.7% 97.8%
Decode 140.6 tok/s 256.2 tok/s

两轮显存占用率几乎一模一样(差 0.1 个百分点),速度却差 82%。

所以「显存占用 97%」这个数字本身不携带任何性能信息------因为 Strata 的设计就是把能用的显存全部吃满来放专家缓存。它不占满才是浪费。

本文后面会证明:真正决定速度的,不是显存占了多少,而是「缓存之外的专家占多少」。 前者恒等于 97%,后者才是那个会变的数。


二、Strata 的显存账本:三个区、一条公式

2.1 三个区

启动后的引擎日志把显存分成了清楚的三块。用第二轮(独占)的实测值:

用引擎日志的原话对一遍:

text 复制代码
strata generate: 301 native projection matrices, 1376.20 MiB of weights, in 1.8 s
strata mtp: draft layer loaded, 839 MiB of VRAM (experts 675, dense 111)
strata mtp: draft head over 106299 tokens (178.4 MiB)
strata verify: window up to 6 tokens, 55.0 MiB of device buffers
strata generate: expert cache auto: 26.01 GiB free, 700 MiB reserved (+184 MiB for the draft head) -> 19530 slots
strata generate: expert cache 19530 slots, 25.14 GiB of VRAM
strata serve: 410 MiB of VRAM free with everything loaded

2.2 一条可以自己算的公式

从两轮日志里能反推出这套引擎的分配常数,而且两轮完全一致:

  • 每个专家槽位 1.318 MiB(7,625 / 5,784 = 1.318;25,747 / 19,530 = 1.318)
  • 固定预留 700 MiB (系统)+ 184 MiB(投机解码头)
  • 槽位上限 24,576(= 48 层 × 512 专家)

写成一条式子:

槽位数=min⁡ (24576, 启动时空闲显存−884 MiB1.318 MiB ) \text{槽位数} = \min\left(24576,\ \frac{\text{启动时空闲显存} - 884\text{ MiB}}{1.318\text{ MiB}}\right) 槽位数=min(24576, 1.318 MiB启动时空闲显存−884 MiB)

拿实测回代,三个不同显存条件下的启动全部对得上:

启动时空闲 式子 实际槽位 对账
6.07 GiB = 6,215 MiB (6,215 − 884) / 1.318 = 4,045 4,043 ✅
8.31 GiB = 8,509 MiB (8,509 − 884) / 1.318 = 5,786 5,784 ✅
26.01 GiB = 26,634 MiB (26,634 − 884) / 1.318 = 19,537 19,530 ✅

(三行都取自引擎日志原文,误差 0.04% 以内,来自字节对齐取整。)

这条公式的实际价值:你不需要跑基准,只要知道启动时能空出多少显存,就能算出这台机器能覆盖多少比例的专家。第三篇里我把它做成了一张速查表。

2.3 一个反直觉的推论:多给的显存几乎全部变成缓存

把两轮的「引擎总显存」减去「专家缓存」,得到的就是固定开销:

轮次 引擎显存 专家缓存 固定开销 固定开销占比
第一轮 13,670 MiB 7,625 MiB 6,045 MiB 44.2%
第二轮 31,674 MiB 25,747 MiB 5,927 MiB 18.7%

6,045 vs 5,927,差 2%。 也就是说,固定开销基本不随缓存大小变化。

结论很干净:你多给显卡的每一 GB 显存,几乎 1:1 变成了专家缓存。 不存在「显存越大、开销越大」的摊薄陷阱------反而是显存越大,固定开销的占比越被摊薄,显存利用效率越高。


三、一个 token 的显存之旅

前两节是静态的账本,这一节跟一个 token 走一遍完整路径,看清楚「性能」到底花在哪里。

三个细节值得单独说。

3.1 为什么「全部专家」必须待在主机内存里

引擎加载时把完整的 24,576 个专家搬进了主机内存 arena:

text 复制代码
strata generate: expert arena: cudaHostRegister PORTABLE ok
strata generate: loaded 31.64 GiB at 0.99 GiB/s
strata generate: expert arena read through the file cache (buffered)

31.64 GiB 常驻内存,加上 PLE 表(--ple-io ram)和进程本身,压测期间系统内存稳定在 96.3 GiB / 125.6 GiB。

这一步是「慢」的来源,也是「能跑」的前提:只有全部专家都在内存里,才能做到「未命中时立刻能搬」,而不是回头去读 SSD。

3.2 KV Cache 便宜得让人意外

我原本以为长上下文会让 KV 吞掉大量显存,实测打了脸:

配置 专家缓存 槽位 相对 32K 的差
32K 上下文,KV int8 25,747 MiB 19,530 基准
64K 上下文,KV int8 25,692 MiB 19,488 −55 MiB

上下文从 32K 翻到 64K,只多花了 55 MiB 显存。

原因是这个模型的注意力结构是带压缩的(GGUF 元数据里有 qwen4exp.attention.compress_ratios,48 层各一个压缩比),KV 本身就是压缩表示。所以在这套架构里:

上下文长度几乎不花显存,显存预算基本可以全部押在专家缓存上。

这和很多「长上下文很吃显存」的经验不同,是模型结构差异带来的红利。第三篇会用它解释「为什么 64K 上下文几乎不损失速度」。

3.3 MTP 投机解码:让一次搬运服务多个 token

Spill 架构下 decode 是逐 token 的,每个 token 都要搬一次专家。工程上的破解办法是投机解码:用一个小草稿模型先猜接下来几个 token,再让主模型一次并行校验。

我这套配置里 MTP 的实测接受率:

轮次 offered accepted 接受率
第一轮 79,788 51,029 64.0%
第二轮 127,607 81,074 63.5%

64% 的接受率意味着:大约每 1.6 个 token 就能省下一次专家搬运。这也解释了后面实测里 decode 能到 200+ tok/s------单靠「一次搬一个专家算一个 token」是做不到的。


四、模型是什么格式:GGUF、pack、MTP 三层产物

很多人以为本地推理就是「下载一个模型文件然后加载」。这套东西实际落盘是三层不同格式的产物,搞清楚它们才能理解「哪个文件对应哪块显存」。

4.1 主模型:GGUF v3,而且是混合精度

我直接读了两个分片的文件头:

项 实测
magic / version GGUF / 3
shard1 35.04 GiB,1223 个 tensor
shard2 26.82 GiB,1 个 tensor
architecture qwen4exp
结构 48 层,hidden 2560,24 attn heads / 2 KV heads,512 专家,每 token 用 10 个
声明上下文 context_length = 262144(256K)
量化标注 general.file_type = 41(Q2_0 档),quantization_version = 2,quantized_by = ISTA DASLab

「Q2_0」只是档位标签,文件其实是混合精度。 按 tensor 统计:

ggml 类型 tensor 数 元素数 用在哪
Q2_0 202 121.06 B MoE 专家权重(主体)
IQ4_NL 8 51.21 B shard2 的 PLE 大表 [160, 320001536]
Q3_K 91 1.80 B token_embd、共享专家 up
IQ4_XS 56 0.82 B attn_qkv 等
Q5_K 16 0.76 B output 等
BF16 483 0.74 B hyper-connection 权重
Q4_K / Q6_K / Q4_0 / Q5_0 / Q8_0 38 / 12 / 16 / 7 / 2 ~0.5 B 各类投影与共享专家
F32 / F16 292 / 1 2.6 M 各种 norm、ple_conv1d

所以它是「2-bit 专家 + 4~6 bit 投影 + BF16 超连接」的混合精度,不是一个统一 2-bit 文件。这也是为什么同样的「Q2_0」在不同仓库里质量表现会不一样------混合策略不同。

4.2 运行时产物:三层落盘

各层的作用:

产物 大小 格式 作用
packs/q2_0/ 1.38 GiB Strata pack v3(dense.bin + index.txt 1081 行 + native_experts.txt + tokenizer) 原生投影矩阵、专家偏移表、分词器
主机 arena 31.64 GiB 进程匿名内存 + cudaHostRegister 全部专家常驻,按需搬运
显存专家缓存 可变 GPU 显存 存最热的专家
mtp/rt/ 6.5 GB 裸二进制(dense.bin / experts.bin / draft_vocab.bin)+ 889 MB GGUF 投机解码草稿层

引擎的启动参数正好把这五块串起来:

bash 复制代码
strata --serve \
  --pack      /data/Strata-data/packs/q2_0 \
  --native    /data/Strata-data/models/Q2_0/...-00001-of-00002.gguf \
  --ple-gguf  /data/Strata-data/models/Q2_0/...-00002-of-00002.gguf \
  --mtp       /data/Strata-data/mtp/rt \
  --max-context 32768 --kv int8 --ple-io ram

4.3 这套格式带来的两个后果

后果一:不能直接丢给 vLLM / SGLang。 它要的是 GGUF + 专门的运行时;HF safetensors 那套流程吃不了。

后果二:目录结构就是显存账本的物理映射。 之前那 31.64 GiB 的 arena 和 25.7 GiB 的缓存,都能在磁盘上找到对应来源------这也意味着换更小的量化档,省下来的是 arena,而不是缓存。这一点在第三篇选型时会用到。


五、效率是怎么被决定的:五段传导链

到这里,静态结构已经清楚。接下来把「显存」和「tok/s」之间的因果链接起来------这是本文的核心,也是第二篇实测要逐段验证的东西。

五段的性质完全不同:

段 关系 我实测的形态 可优化性
① 显存 → 缓存 线性,1:1 固定开销恒定,多给的全进缓存 加显存
② 缓存 → 覆盖率 线性 1.318 MiB/槽,上限 24,576 与①同源
③ 覆盖率 → 命中率 强非线性(长尾) 覆盖率 23.5% → 命中率 86.5% 靠 profile 排序
④ 命中率 → PCIe 溢出 放大 命中 86.5% → 溢出 2.5% 靠③
⑤ 溢出 → 速度 断崖 溢出 0.5% 即掉 39% 速度 靠④

这条链里真正"非线性"的是第③段和第⑤段。 而第①段那个漂亮的 1:1,恰恰是最容易被误读的地方------很多人看到「显存翻倍只快 1.8 倍」就以为存在「边际递减」,其实是把第③⑤段的非线性算到了第①段头上。

第二篇会把这条链逐段用 2,890 条逐请求遥测拆开验证。

5.1 一个必须先说清的坑:不是「搬得慢」,是「等得久」

按带宽算,未命中的成本不该那么贵:

ini 复制代码
每 token 480 次专家查表 × 1% 溢出 = 4.8 个专家
× 1.38 MB = 6.6 MB / token
÷ 13.9 GB/s(实测 PCIe Gen3 ×16)= 0.47 ms
而 141 tok/s = 7.1 ms / token
→ 0.47 / 7.1 ≈ 6.6%

理论上只该慢 6.6%,但我实测在 0.5% 溢出时就已经慢了 39%。差了 6 倍。

原因不在带宽,在同步:PCIe 搬运是异步发起的,但主模型的 attention 与 MoE 计算有严格的数据依赖,一批专家没到,后续 kernel 只能干等。等待期间 GPU 表面上是「忙」的,实际在空转:

轮次 GPU 利用率 功耗均值 功耗占上限 我的判断
第一轮 89.1% 230 W 40% 假忙(在等 PCIe)
第二轮 89.8% 337 W 59% 真忙(在算)

「GPU 利用率」这个指标在这类架构上是失效的------它只回答「有没有活干」,不回答「干的活有没有产出」。第二篇会用功耗曲线把这个现象坐实。


六、可验证的预测:把「玄学」变成可测量的量

理论讲完,得给出可以被证伪的预测。基于上面五段链,我在跑第二轮之前写下了四条预测:

# 预测 验证方式
P1 专家缓存从 7.6 GiB 扩到 25.7 GiB,覆盖率从 23.5% 升到 79.5% 引擎日志的槽位数
P2 命中率会非线性上升,但升幅远小于覆盖率升幅(长尾) 逐请求 telemetry 的 hit_rate
P3 命中率上升会几乎消灭 PCIe 溢出 逐请求 telemetry 的 pcie %
P4 decode 的提升幅度会远大于显存投入的"线性直觉",但低于「溢出归零」的极限 同协议基准对比

第二轮实测结果(详见第二篇):

# 预测 实测 判定
P1 覆盖率 23.5% → 79.5% 5,784 → 19,530 槽(= 23.5% → 79.5%) ✅
P2 命中率非线性上升 86.5% → 99.7%(+13.2 点,远小于 +56 点) ✅
P3 PCIe 溢出几乎消失 0.50% → 0.00% ✅
P4 decode 大幅提升 140.6 → 256.2 tok/s(+82%) ✅

四条全中,说明这条五段链不是一个「事后编出来的故事」,而是能在跑之前就能预测、跑之后能被数据对上的机制。


七、总结

7.1 本篇的三条结论

7.2 一句话判断

消费级显卡跑 125B MoE,难的不是让它算得动------它算得动,功耗只用掉六成;难的是让热专家待在显存里。 而这件事的上限,被一条简单的式子锁死:(可用显存 − 884 MiB) / 1.318 MiB,最多 24,576 个槽。

7.3 下一篇讲什么

第二篇拿着这套机制去做对照实验:同一台机器、同一份模型、同一套参数,只把显卡上别人的 18 GB 占用停掉,然后用两轮完整基准 + 2,890 条逐请求遥测,把五段传导链逐段量化,给出「每 GiB 显存值多少 tok/s」「多少 PCIe 溢出会掉多少速度」这种可以直接拿去做容量规划的数字。


附录:本篇引用的原始证据

A.1 两轮引擎分配对比

项 第一轮(共享) 第二轮(独占)
GPU1 其他占用 18,106 MiB 0
启动时空闲 VRAM 8.31 GiB 26.01 GiB
专家缓存槽位 5,784 19,530
专家缓存大小 7,625 MiB 25,747 MiB
引擎总显存 13,670 MiB 31,674 MiB
固定开销 6,045 MiB 5,927 MiB
加载后剩余 VRAM 410 MiB 410 MiB
主机 arena 31.64 GiB 31.64 GiB
系统内存占用 104.97 GiB 96.31 GiB

A.2 模型文件校验(可直接复核)

文件 字节数 SHA-256
shard1 37,623,740,192 69820c02ec7d0b45ef2ebb19d6620299db749fe2aded7f39f93c6b88b199b720
shard2 28,800,138,432 316b46f3a2dbd68c900f43136ab9449f9dcc3725dfd8c794847c204bc161e113

A.3 引擎启动日志关键行(原文摘录)

下面每一行都取自 docker.engine.log 原文。同一个字段在不同次启动中会有小幅浮动(比如 arena 装载速率实测在 0.94 -- 2.58 GiB/s 之间,MTP 草稿层占用在 820 -- 839 MiB 之间),这是页缓存与内存压力的正常差异,不影响上面的公式。

text 复制代码
strata generate: memory guard: limit 125.6 GiB (source: meminfo), current 30.6 GiB,
                 reclaimable cache credited 0.0 GiB; available 94.9 GiB (MemAvailable 94.9 GiB), floor 2560 MiB
strata generate: 301 native projection matrices, 1376.20 MiB of weights, in 1.8 s
strata generate: PLE table locked in RAM (--ple-io ram) in 3.8 s
strata generate: profile /opt/strata/terminal/data/expert-profile.bin: 24576 ranked pairs, built for 24576 slots
strata mtp: draft layer loaded, 839 MiB of VRAM (experts 675, dense 111), files read in 0.54 s (1442 MiB/s)
strata mtp: draft head over 106299 tokens (178.4 MiB)
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)
strata generate: this CPU has no AVX-512: the expert kernels run on AVX-2 (multi-token for the i-quant gate/up rows)
strata generate: expert arena: MAP_HUGETLB unavailable (needed 16201 2 MiB pages, vm.nr_hugepages=0)
strata generate: loaded 31.64 GiB at 0.99 GiB/s
strata generate: expert cache auto: 26.01 GiB free, 700 MiB reserved (+184 MiB for the draft head) -> 19530 slots
strata generate: expert cache 19530 slots, 25.14 GiB of VRAM
strata generate: pre-filled 19530 of 19530 slots from the profile in 2.1 s (12936 MB/s); slot 0 verified
strata serve: prompt chunk auto: 8192 tokens, a 384-slot ring
strata verify: window up to 6 tokens, 55.0 MiB of device buffers
strata serve: 410 MiB of VRAM free with everything loaded

A.4 本篇的测量限制

  • 只测了 Q2_0 一档,不能跨量化档外推;IQ3_XXS / IQ3_S 的 arena 更大,槽位公式相同但覆盖率起点不同。
  • KV 的 384 MiB 与「激活/CUDA 上下文」的 3,100 MiB 为结构推算,非直接测量,误差约 ±300 MiB。
  • 固定开销恒定的结论来自多个数据点(13 次启动中,缓存 4,043 / 5,784 / 17,279 / 18,240 / 19,530 槽均有样本),固定开销始终在 5.9--6.0 GiB 区间,但该结论未在 48 GB 卡上验证。
  • PCIe 固定为 Gen3 ×16(实测 13.9 GB/s),换平台会移动第五段「断崖」的位置,但定性结论不变。
  • 单卡、单模型、单网络环境;同机另一张 4090 全程被 vLLM 占据,未参与任何测量,但可能对 PCIe/内存带宽有轻微干扰。

邦友们,第一篇到这里就把「为什么」讲完了:显存不够时,Strata 不是把模型变瘦,而是把「装不下」重新定义成「缓存哪些」。

下一篇是这套系列里数字最多的一篇------我会把显卡上那 18 GB 的占用停掉,用同一套配置重跑,然后拿 2,890 条遥测告诉你:这 18 GB 到底值多少 tok/s,又卡在哪个小数点上。

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

相关推荐
学IT的周星星5 小时前
LangGraph:用「流程图」的方式编排 Agent
数据库·人工智能·大模型·llm·流程图·agent
NC 王炸5 小时前
不用 API Key,也能让网页调用 AI?Opera 把本地模型接进了浏览器
人工智能·浏览器扩展·ai 工具·本地 ai·opera·webai
锋行天下5 小时前
Langgraph 实现类似Langchain记忆上下文过长裁剪
人工智能·后端
不老刘5 小时前
Antigravity 的 New Worktree 模式:给 AI 代理一个安全的“平行工位”
人工智能·git·ai编程·antigravity
m0_719640875 小时前
高校人工智能实验室搭建,2026年具身智能科研设备适配建议
人工智能·经验分享
樱木花道長5 小时前
AMD Ross 深度系列 8:FAQ 七问七答(文末附网盘链接)
人工智能·fpga开发
吴名氏.5 小时前
Java转AI Agent教程(第一周)让模型先跑起来
人工智能·python·ai·ai agent
zhangfeng11335 小时前
中关村国际前沿科技大赛(创新药赛道)详细介绍 ai药物比赛
人工智能·科技
xiaoyunxuexibiji5 小时前
供应商质量协同如何实现闭环管理?从到货检验到整改与质量索赔
人工智能