参悟llama.cpp中的Qwen3.6-35B-A3B

参悟llama.cpp中的Qwen3.6-35B-A3B

懒人版:直接复制给LLM,"总结一下这篇文章讲了什么"

前言

这篇文章既是对一系列探究性实践的记录与总结,也是深入理解 MoE(Mixture of Experts)架构底层逻辑与 llama.cpp 部署机制的学习过程。

模型: Qwen3.6-35B-A3B

框架: llama.cpp (Build: 91c631b21)

硬件: 1 x RTX 3090 (24GB, 936 GB/s), 1 x Tesla T4 (16GB)

Qwen3.6-35B-A3B: https://www.modelscope.cn/models/Qwen/Qwen3.6-35B-A3B

llama.cpp: https://github.com/ggml-org/llama.cpp

1. 疑问

1.1 什么是MOE?

MoE 的本质是将一个巨大的神经网络拆解为多个独立的子网络(专家,Expert),并配有一个"路由器"。

  • 路由器:分析输入 Token,决定哪几个专家(通常 2-8 个)最适合处理当前上下文。
  • 专家:彼此独立,擅长特定领域(如代码、数学)。
  • 稀疏激活:推理时,只有被选中的专家参与计算。对于 Qwen3.6-35B-A3B,总参数 35B,但每个 Token 仅激活约 3B 参数。

以Qwen3.6-35B-A3B为例,

参数总量:共 35B,其中激活参数为 3B

隐藏层维度:2048

Token 嵌入:248320(已填充)

层数:40

隐藏层结构:10 × (3 × (门控 DeltaNet → MoE) → 1 × (门控注意力 → MoE))

门控 DeltaNet:

  • 线性注意力头数量:V 为 32,QK 为 16
  • 头维度:128
    门控注意力:
  • 注意力头数量:Q 为 16,KV 为 2
  • 头维度:256
    旋转位置嵌入维度:64
    混合专家(Mixture Of Experts)
  • 专家总数:256
  • 激活专家数:8 个路由专家 + 1 个共享专家
  • 专家中间层维度:512

忽略潜入层、预测层和MTP层等一次性层,只考虑循环了40层的(注意力+MoE)结构,Qwen3.6-35B-A3B的参数比例可以粗略估计:

设40层注意力的总和参数规模为X,40层路由专家的总和参数规模为256Y,40层共享专家的总和参数规模为Z,则有:

  • X + 256Y + Z = 35B
  • X + 8Y + Z = 3B

可以推算出:

  • Y = (35B - 3B) / (256 - 8) ≈ 4/31 B = 0.129 B
  • X+Z = 1.9677 B
  • 路由专家占据了 256Y 约等于 33.03B,留给其他层的容量只有不到2B。

以上为纯以理解为目的的粗略估算,精准数值还是要认真学习对应架构、知识、源码才能算得出来。

1.2 为什么MOE推理速度快?

由于MoE架构中只有部分专家参与计算,因此可以显著减少每次推理时需要处理的参数量,从而提高推理速度。

以3B为例,就算以fp16精度进行计算,完成一个token的推理也只需要处理约6GiB的数据,在3090 35.6 TFLOPS的算力下,理论上可以在不到1ms内完成一个token的推理。真正的算力瓶颈反而是其936 GB/s的显存带宽,其推理速度的物理上限就是 936/6 ≈ 156 T/s,而现实中还得考虑推理框架的效率、算力调度等因素,实际速度一定会明显低于这个上限。

但是理论物理上限仍然高于同系列的Qwen3.6-27B模型,后者的参数量为27B,激活参数量为27B,fp16精度下每个token的推理需要处理约54GiB的数据,3090的显存带宽936 GB/s下,理论物理上限为 936/54 ≈ 17 T/s,远低于Qwen3.6-35B-A3B的156 T/s。

1.3 MOE的显存占用更小吗?

取决于上下文长度和注意力层的设计。

当上下文很短的时候,Qwen3.6-35B-A3B的显存占用反而大于Qwen3.6-27B。这是因为显存占用主要由模型权重+KVCache+中间激活值组成,而KVC的占用量与上下文长度成正比,注意力层的设计也会影响KVC的占用量。

当上下文很长的时候,Qwen3.6-27B的显存占用会超过Qwen3.6-35B-A3B,因为Qwen3.6-35B-A3B的注意力层参数规模远小于Qwen3.6-27B,KVC的占用量也远小于Qwen3.6-27B。此时KVCache成为了Qwen3.6-27B的主要显存占用来源,而Qwen3.6-35B-A3B即使在原生最大的256K上下文下,KVCache的占用量也只有约6.25GiB,相比于总权重数据量大35GiB不值一提。

门控注意力的KV Cache大小 (字节) = 层数 × 序列长度 × 每个 Token 的 KV 维度 × 2(K和V) × 字节数

30 * 262144 * 128 * 2 * 2 + 10 * 262144 * 256 * 2 * 2 = 6,710,886,400 字节 ≈ 6.25 GiB

1.4 llama.cpp对MOE有哪些支持?

  • 支持运行高度量化的MoE模型(q4_0, q8_0等)。
  • ncmoe N:将前 N 层的 MoE 权重卸载到 CPU。N=0 全在 GPU,N=40 全部在 CPU。
  • ctk / -ctv:控制 KV Cache 的量化精度(f16, q8_0, q4_0)。
  • ngl:控制加载到 GPU 的层数(对于 MoE,通常设为 999 让 llama.cpp 自动管理,或通过 ncmoe 精细控制)。

1.5 1张3090能跑Qwen3.6-35B-A3B吗?

直接下载usloth量化的Qwen3.6-35B-A3B-UD-Q4_K_M.gguf,其大小为22.13GB,显然1张3090理论上刚好能装下。但如果你真的去部署就发现会OOM,因为:

  • 显存占用不仅仅是模型权重,还包括KV Cache和中间激活值。
  • llama.cpp的固有开销。
  • 预留显存给系统和其他程序。

那么就只能尝试更高强度的量化,例如Qwen3.6-35B-A3B-UD-Q2_K_XL.gguf(12.29GB)或者Qwen3.6-35B-A3B-UD-IQ4_NL.gguf(18.04GB)。

一般来说,Q4_K_M量化能去的质量与性能的平衡,强度高于q4都会显著损失性能,强度低于q4则显存占用会显著增加。

因此可以尝试IQ4_NL量化,或者,迎难而上,从其他角度解决这个问题。

2. 理解

2.1 个人部署大模型关注的性能指标

个人使用大模型往往用于Chat和Agentic Engineering,往往只关注3个指标:

  • 显存占用:显存占用越小,部署的灵活性越高。
  • 上下文长度:上下文长度越长,模型的上限就越高。
  • 推理速度:推理速度越快,交互体验越好。

2.2 影响显存占用的因素

  • 模型参数量:参数量越大,显存占用越高。(这一项没有办法通过工程手段控制)
  • 权重量化精度:量化精度越低,显存占用越小。(这一项没有办法通过工程手段控制)
  • 上下文长度:上下文长度越长,KV Cache的显存占用越高。
  • 中间激活值:中间激活值越多,显存占用越高。(这一项没有办法通过工程手段控制)

这就让我们陷入了一个矛盾,如果我们想要更长的上下文长度,就必须要有更大的显存占用,但是我们想要更小的显存占用,就必须要有更短的上下文长度。

幸好,llama.cpp提供了ctk/ctv参数来控制KV Cache的量化精度,从而在一定程度上缓解了这个矛盾。

2.3 影响推理速度的因素

  • 模型激活参数量:激活参数量越大,推理速度越慢。(然而这一项只由模型作者决定)
  • GPU的计算精度支持:原生支持的精度越低,浪费在反量化上的时间越少,推理速度越快。(3090只支持fp16和int8,且算力已经溢出)
  • GPU的显存带宽:显存带宽越高,推理速度越快,这是第一个内存墙。(3090只有936 GB/s,成为瓶颈)
  • CPU的计算精度支持:可惜,CPU最低通过AVX2支持fp16,推理速度远低于GPU,但服务器级别的CPU算力性能对于"A3B"来说也溢出了。
  • CPU RAM的带宽:这是第二个内存墙,CPU RAM的带宽远低于GPU显存带宽,成为瓶颈。

有时候,我们不得不卸载部分权重和计算到CPU上。

3. 采样实验

太多了,可以直接跳到第4章。

对于我来说,理论分析最大的障碍不是知识本身,而是缺乏实践导致的就算完成了理论推算,也不敢相信自己推算的正确性。

这种心理往往会让人陷入精神内耗,而教员的实践论说了,想不通就先干。

为了用1张3090部署256K上下文的Qwen3.6-35B-A3B,有两种潜在的方向:

  1. 卸载部分权重和计算到CPU上,降低GPU显存占用,llama.cpp的-ncmoe参数可以做到这一点。
  2. 降低KV Cache的量化精度,降低GPU显存占用,llama.cpp的-ctk和-ctv参数可以做到这一点。

3.1 不同配置下的显存占用测试

用llama-server不断在不同的配置下部署模型,并在推理过程中观察显存占用情况,记录如下:

序号 KV类型 ncmoe 上下文长度 已用显存 总显存
1 f16 0 8192 21386MiB 24576MiB
2 f16 0 16384 21554MiB 24576MiB
3 f16 0 32768 21890MiB 24576MiB
4 f16 0 65536 22562MiB 24576MiB
5 f16 0 131072 23906MiB 24576MiB
7 f16 1 8192 21078MiB 24576MiB
8 f16 1 16384 21246MiB 24576MiB
9 f16 1 32768 21582MiB 24576MiB
10 f16 1 65536 22254MiB 24576MiB
11 f16 1 131072 23598MiB 24576MiB
13 f16 2 8192 20618MiB 24576MiB
14 f16 2 16384 20786MiB 24576MiB
16 f16 2 65536 21794MiB 24576MiB
17 f16 2 131072 23138MiB 24576MiB
19 f16 4 8192 19830MiB 24576MiB
20 f16 4 16384 19998MiB 24576MiB
21 f16 4 32768 20334MiB 24576MiB
22 f16 4 65536 21006MiB 24576MiB
23 f16 4 131072 22350MiB 24576MiB
25 f16 8 8192 17974MiB 24576MiB
26 f16 8 16384 18142MiB 24576MiB
27 f16 8 32768 18478MiB 24576MiB
28 f16 8 65536 19150MiB 24576MiB
29 f16 8 131072 20494MiB 24576MiB
30 f16 8 262144 23182MiB 24576MiB
31 f16 16 8192 14264MiB 24576MiB
32 f16 16 16384 14432MiB 24576MiB
33 f16 16 32768 14768MiB 24576MiB
34 f16 16 65536 15440MiB 24576MiB
35 f16 16 131072 16784MiB 24576MiB
36 f16 16 262144 19472MiB 24576MiB
37 f16 32 8192 6840MiB 24576MiB
38 f16 32 16384 7008MiB 24576MiB
39 f16 32 32768 7344MiB 24576MiB
40 f16 32 65536 8016MiB 24576MiB
41 f16 32 131072 9360MiB 24576MiB
42 f16 32 262144 12048MiB 24576MiB
43 f16 64 8192 3058MiB 24576MiB
44 f16 64 16384 3226MiB 24576MiB
45 f16 64 32768 3562MiB 24576MiB
46 f16 64 65536 4234MiB 24576MiB
47 f16 64 131072 5578MiB 24576MiB
48 f16 64 262144 8266MiB 24576MiB
49 q8_0 0 8192 21312MiB 24576MiB
50 q8_0 0 16384 21420MiB 24576MiB
51 q8_0 0 32768 21638MiB 24576MiB
52 q8_0 0 65536 22074MiB 24576MiB
53 q8_0 0 131072 22946MiB 24576MiB
55 q8_0 1 8192 21004MiB 24576MiB
56 q8_0 1 16384 21096MiB 24576MiB
57 q8_0 1 32768 21282MiB 24576MiB
58 q8_0 1 65536 21654MiB 24576MiB
59 q8_0 1 131072 22630MiB 24576MiB
61 q8_0 2 8192 20544MiB 24576MiB
62 q8_0 2 16384 20636MiB 24576MiB
63 q8_0 2 32768 20822MiB 24576MiB
64 q8_0 2 65536 21194MiB 24576MiB
65 q8_0 2 131072 22170MiB 24576MiB
66 q8_0 2 262144 23914MiB 24576MiB
67 q8_0 4 8192 19756MiB 24576MiB
68 q8_0 4 16384 19848MiB 24576MiB
69 q8_0 4 32768 20034MiB 24576MiB
70 q8_0 4 65536 20406MiB 24576MiB
71 q8_0 4 131072 21238MiB 24576MiB
72 q8_0 4 262144 22982MiB 24576MiB
73 q8_0 8 8192 17900MiB 24576MiB
74 q8_0 8 16384 17992MiB 24576MiB
75 q8_0 8 32768 18178MiB 24576MiB
76 q8_0 8 65536 18550MiB 24576MiB
77 q8_0 8 131072 19382MiB 24576MiB
78 q8_0 8 262144 21126MiB 24576MiB
79 q8_0 16 8192 14190MiB 24576MiB
80 q8_0 16 16384 14282MiB 24576MiB
81 q8_0 16 32768 14468MiB 24576MiB
82 q8_0 16 65536 14840MiB 24576MiB
83 q8_0 16 131072 15672MiB 24576MiB
84 q8_0 16 262144 17416MiB 24576MiB
85 q8_0 32 8192 6766MiB 24576MiB
86 q8_0 32 16384 6858MiB 24576MiB
87 q8_0 32 32768 7044MiB 24576MiB
88 q8_0 32 65536 7416MiB 24576MiB
89 q8_0 32 131072 8248MiB 24576MiB
90 q8_0 32 262144 9992MiB 24576MiB
91 q8_0 64 8192 2984MiB 24576MiB
92 q8_0 64 16384 3076MiB 24576MiB
93 q8_0 64 32768 3262MiB 24576MiB
94 q8_0 64 65536 3634MiB 24576MiB
95 q8_0 64 131072 4432MiB 24576MiB
96 q8_0 64 262144 6176MiB 24576MiB
97 q4_0 0 8192 21272MiB 24576MiB
98 q4_0 0 16384 21340MiB 24576MiB
99 q4_0 0 32768 21478MiB 24576MiB
100 q4_0 0 65536 21754MiB 24576MiB
101 q4_0 0 131072 22306MiB 24576MiB
102 q4_0 0 262144 23410MiB 24576MiB
103 q4_0 1 8192 20964MiB 24576MiB
104 q4_0 1 16384 21016MiB 24576MiB
105 q4_0 1 32768 21122MiB 24576MiB
106 q4_0 1 65536 21334MiB 24576MiB
107 q4_0 1 131072 21990MiB 24576MiB
108 q4_0 1 262144 23094MiB 24576MiB
109 q4_0 2 8192 20504MiB 24576MiB
110 q4_0 2 16384 20556MiB 24576MiB
111 q4_0 2 32768 20662MiB 24576MiB
112 q4_0 2 65536 20874MiB 24576MiB
113 q4_0 2 131072 21530MiB 24576MiB
114 q4_0 2 262144 22634MiB 24576MiB
115 q4_0 4 8192 19716MiB 24576MiB
116 q4_0 4 16384 19768MiB 24576MiB
117 q4_0 4 32768 19874MiB 24576MiB
118 q4_0 4 65536 20086MiB 24576MiB
119 q4_0 4 131072 20598MiB 24576MiB
120 q4_0 4 262144 21702MiB 24576MiB
121 q4_0 8 8192 17860MiB 24576MiB
122 q4_0 8 16384 17912MiB 24576MiB
123 q4_0 8 32768 18018MiB 24576MiB
124 q4_0 8 65536 18230MiB 24576MiB
125 q4_0 8 131072 18742MiB 24576MiB
126 q4_0 8 262144 19846MiB 24576MiB
127 q4_0 16 8192 14150MiB 24576MiB
128 q4_0 16 16384 14202MiB 24576MiB
129 q4_0 16 32768 14308MiB 24576MiB
130 q4_0 16 65536 14520MiB 24576MiB
131 q4_0 16 131072 15032MiB 24576MiB
132 q4_0 16 262144 16136MiB 24576MiB
133 q4_0 32 8192 6726MiB 24576MiB
134 q4_0 32 16384 6778MiB 24576MiB
135 q4_0 32 32768 6884MiB 24576MiB
136 q4_0 32 65536 7096MiB 24576MiB
137 q4_0 32 131072 7608MiB 24576MiB
138 q4_0 32 262144 8712MiB 24576MiB
139 q4_0 64 8192 2944MiB 24576MiB
140 q4_0 64 16384 2996MiB 24576MiB
141 q4_0 64 32768 3102MiB 24576MiB
142 q4_0 64 65536 3314MiB 24576MiB
143 q4_0 64 131072 3792MiB 24576MiB
144 q4_0 64 262144 4896MiB 24576MiB

3.2 不同配置下的推理速度测试

model size params backend ngl fa test t/s
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 pp256 2597.45 ± 27.18
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 tg256 154.43 ± 0.34
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 pp512 3430.11 ± 33.18
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 tg512 153.56 ± 0.40
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 pp1024 3428.32 ± 19.55
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 tg1024 153.09 ± 0.28
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 pp2048 3389.95 ± 15.49
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 tg2048 151.92 ± 0.15
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 pp4096 3331.15 ± 18.29
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 tg4096 150.90 ± 0.27
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 pp8192 3246.80 ± 23.03
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 tg8192 149.02 ± 0.03
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 pp16384 3121.98 ± 11.93
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 tg16384 146.02 ± 0.22
model size params backend ngl type_k type_v fa test t/s
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q8_0 q8_0 1 pp256 2588.50 ± 42.67
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q8_0 q8_0 1 tg256 151.48 ± 0.64
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q8_0 q8_0 1 pp512 3441.18 ± 25.07
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q8_0 q8_0 1 tg512 150.97 ± 0.16
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q8_0 q8_0 1 pp1024 3392.41 ± 22.50
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q8_0 q8_0 1 tg1024 149.47 ± 0.30
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q8_0 q8_0 1 pp2048 3353.24 ± 15.10
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q8_0 q8_0 1 tg2048 147.73 ± 0.42
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q8_0 q8_0 1 pp4096 3271.68 ± 19.67
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q8_0 q8_0 1 tg4096 145.95 ± 0.19
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q8_0 q8_0 1 pp8192 3214.79 ± 20.45
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q8_0 q8_0 1 tg8192 142.95 ± 0.15
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q8_0 q8_0 1 pp16384 3075.39 ± 8.89
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q8_0 q8_0 1 tg16384 138.21 ± 0.07
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q4_0 q4_0 1 pp256 2572.44 ± 31.25
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q4_0 q4_0 1 tg256 149.69 ± 0.34
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q4_0 q4_0 1 pp512 3408.31 ± 34.58
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q4_0 q4_0 1 tg512 148.75 ± 0.23
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q4_0 q4_0 1 pp1024 3382.18 ± 18.49
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q4_0 q4_0 1 tg1024 148.04 ± 0.27
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q4_0 q4_0 1 pp2048 3342.87 ± 17.04
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q4_0 q4_0 1 tg2048 147.15 ± 0.16
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q4_0 q4_0 1 pp4096 3276.92 ± 20.85
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q4_0 q4_0 1 tg4096 145.38 ± 0.24
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q4_0 q4_0 1 pp8192 3208.74 ± 21.82
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q4_0 q4_0 1 tg8192 141.83 ± 0.16
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q4_0 q4_0 1 pp16384 3065.99 ± 12.52
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 q4_0 q4_0 1 tg16384 136.11 ± 0.10
model size params backend ngl n_cpu_moe fa test t/s
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 1 pp256 1382.40 ± 40.33
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 1 tg256 143.65 ± 0.82
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 1 pp512 2101.12 ± 27.76
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 1 tg512 144.80 ± 0.25
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 1 pp1024 2111.58 ± 19.31
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 1 tg1024 137.42 ± 1.84
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 1 pp2048 2102.57 ± 18.04
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 1 tg2048 137.34 ± 2.13
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 1 pp4096 2084.55 ± 6.25
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 1 tg4096 140.47 ± 0.50
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 1 pp8192 2055.45 ± 8.94
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 1 tg8192 137.27 ± 0.39
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 1 pp16384 2001.76 ± 7.48
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 1 tg16384 136.56 ± 0.26
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 2 1 pp256 934.74 ± 16.93
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 2 1 tg256 132.00 ± 0.58
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 2 1 pp512 1502.21 ± 20.14
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 2 1 tg512 135.54 ± 0.33
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 2 1 pp1024 1506.38 ± 9.86
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 2 1 tg1024 135.84 ± 0.68
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 2 1 pp2048 1511.62 ± 16.53
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 2 1 tg2048 127.74 ± 0.48
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 2 1 pp4096 1492.47 ± 8.55
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 2 1 tg4096 125.46 ± 0.42
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 2 1 pp8192 1487.76 ± 7.11
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 2 1 tg8192 125.11 ± 0.53
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 2 1 pp16384 1469.13 ± 3.86
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 2 1 tg16384 130.18 ± 0.23
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 4 1 pp256 574.18 ± 7.65
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 4 1 tg256 113.82 ± 1.83
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 4 1 pp512 954.11 ± 11.41
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 4 1 tg512 123.88 ± 1.11
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 4 1 pp1024 961.74 ± 7.78
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 4 1 tg1024 123.63 ± 0.21
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 4 1 pp2048 961.56 ± 4.45
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 4 1 tg2048 122.61 ± 0.38
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 4 1 pp4096 959.54 ± 2.39
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 4 1 tg4096 120.99 ± 0.42
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 4 1 pp8192 956.03 ± 2.05
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 4 1 tg8192 121.32 ± 0.41
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 4 1 pp16384 946.81 ± 2.85
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 4 1 tg16384 114.45 ± 0.33
model size params backend ngl n_cpu_moe type_k type_v fa test t/s
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 q8_0 q8_0 1 pp256 1357.53 ± 67.94
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 q8_0 q8_0 1 tg256 140.76 ± 0.55
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 q8_0 q8_0 1 pp512 2105.04 ± 33.56
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 q8_0 q8_0 1 tg512 135.62 ± 1.49
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 q8_0 q8_0 1 pp1024 2110.99 ± 13.32
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 q8_0 q8_0 1 tg1024 137.20 ± 0.90
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 q8_0 q8_0 1 pp2048 2108.66 ± 8.02
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 q8_0 q8_0 1 tg2048 134.88 ± 0.72
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 q8_0 q8_0 1 pp4096 2083.96 ± 10.21
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 q8_0 q8_0 1 tg4096 138.41 ± 0.12
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 q8_0 q8_0 1 pp8192 2056.76 ± 6.87
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 q8_0 q8_0 1 tg8192 135.32 ± 0.12
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 q8_0 q8_0 1 pp16384 2004.78 ± 5.40
qwen35moe 35B.A3B Q4_K - Medium 20.60 GiB 34.66 B CUDA 999 1 q8_0 q8_0 1 tg16384 129.76 ± 0.12

4. 结论、推论、实践

4.1 近似模型

有了第三章采样的大量数据,我们可以先彻底忘记MoE架构,TransFormer理论,直接暴力拟合一个近似模型来预测显存占用和推理速度。

4.1.1 显存占用近似模型

\\\boxed{ \\text{Mem}(L, q, n) = B(n) + \\alpha(q) \\cdot L } \\

其中:

\\\alpha(q) = \\begin{cases} 0.021, \& q = \\text{f16} \\\\ 0.0125, \& q = \\text{q8\\_0} \\\\ 0.0075, \& q = \\text{q4\\_0} \\end{cases} \\quad,\\quad B(n) = \\begin{cases} 21210, \& n = 0 \\\\ 20900, \& n = 1 \\\\ 20400, \& n = 2 \\\\ 19600, \& n = 4 \\\\ 17700, \& n = 8 \\\\ 14000, \& n = 16 \\\\ 6600, \& n = 32 \\\\ 2900, \& n = 64 \\end{cases} \\

例 1 :配置 \(q = \text{q4\_0}\),\(n = 4\),\(L = 262144\)

\\\begin{aligned} \\text{Mem} \&= B(4) + \\alpha(\\text{q4\\_0}) \\times 262144 \\\\ \&= 19600 + 0.0075 \\times 262144 \\\\ \&= 19600 + 1966.08 \\\\ \&= 21566.08 \\text{ MiB} \\end{aligned} \\

实测值:21702 MiB,误差 \(\approx 0.6\%\)

例 2 :配置 \(q = \text{f16}\),\(n = 8\),\(L = 65536\)

\\\begin{aligned} \\text{Mem} \&= B(8) + \\alpha(\\text{f16}) \\times 65536 \\\\ \&= 17700 + 0.021 \\times 65536 \\\\ \&= 17700 + 1376.26 \\\\ \&= 19076.26 \\text{ MiB} \\end{aligned} \\

实测值:19150 MiB,误差 \(\approx 0.4\%\)

4.1.2 推理速度近似模型

\\\text{Decode}(L, q, n) = \\text{CapDecode}(q, n) - \\gamma(q, n) \\cdot L \\

其中:

符号 含义 单位
\(\text{Decode}\) Decode 阶段速度 tokens/s
\(\text{CapDecode}\) Decode 初始速度(\(L \to 0\) 时) tokens/s
\(\gamma\) Decode 速度衰减系数 (tokens/s)/token
\(L\) 上下文长度(实际处理的 token 数) tokens
\(q\) KV Cache 量化类型 -
\(n\) ncmoe -
Decode 参数
\(q\) \(n\) \(\text{CapDecode}\) (t/s) \(\gamma\) (\(\times 10^{-4}\))
f16 0 ~154.5 ~1.1
f16 1 ~144.0 ~0.9
f16 2 ~133.0 ~0.8
f16 4 ~116.0 ~0.6
q8_0 0 ~152.0 ~1.5
q8_0 1 ~141.0 ~1.2
q4_0 0 ~150.0 ~1.8
q4_0 1 ~140.0 ~1.5

4.2 近似模型的自回归测试

目前llama.cpp的显存占用在部署完成之后就基本稳定不变了,但仍然可能存在1GB以内的波动(经验数据)。所以,在选择配置时,建议预留至少1GB的显存空间,以避免OOM错误。

然后就是规划的顺序,优先考虑显存,然后考虑速度。在质量方面可以以显存为主,毕竟没有使用q4_0以上的量化等级,根据社区经验来讲,质量损失不会太大,只需尽量使用较低强度的量化即可。

仍然是256k上下文,那么就有

\B(n) + \\alpha(q) \\times 262144 \\leq (24576-1024)= 23552 \\

可行解有:

n \(B(n)\) q \(\alpha(q)\) \(L\times \alpha(q)\) Mem(L=262144, q, n) Decode(L=262144, q, n)
0 21210 q4_0 0.0075 1966 23176 121.2
3 20000 q8_0 0.0125 3277 23277 缺少q8_0,n=3的采样
6 18000 f16 0.021 5494 23494 缺少f16,n=6的采样

这三组的实际部署测试结果是:

  • 0, q4_0, ncmoe=0, ctx=262144: 120.31t/s (2535 tokens generated), 22726 MiB(空闲: 24576-22726=1850MiB)

这个实际速度结果和理论值121.2t/s非常接近,误差约为0.7%,实际显存占用和理论值23176 MiB也非常接近,误差约为1.9%。

  • 3, q8_0, ncmoe=3, ctx=262144: 110.46 t/s (2535 tokens generated), 23446 MiB
  • 6, f16, ncmoe=6, ctx=262144: 85.98 t/s (2714 tokens generated), 24120 MiB

后两组的ncmoe=3和ncmoe=6的在实验一中没有测试过,所有没有相关的速度模型常数,无法预测,但显存占用和理论值也非常接近,误差约为0.4%和0.3%。

工程博士的手段,就是这么朴实无华。

而单卡3090部署qwen3.6-35B-A3B+256K上下文的最优方案显然就是直接使用q4_0量化KVCache,不需要卸载到CPU。此时推理速度达到理论物理上界的77%,显存占用方面也有超过1GiB的余量,完全可以满足实际部署的需求。

4.3 近似模型的合理外推测试

速度近似模型一定与显卡强相关,但是显存近似模型大概率只与llama.cpp的版本相关。因此,我们完全可以用3090采样得来的显存模型去预测T4的显存占用情况。从而回答一个进阶问题:

4.3.1 1xTesla T4 + Qwen3.6-35B-A3B + 256K上下文

1张Tesla T4能把Qwen3.6-35B-A3B跑到什么水平?

Tesla T4: 15360MiB 显存,300GB/s,半精度性能65.12TFLOPS

理论上,因为显存限制,只能考虑q4_0和ncmoe>0

65.12TFLOPS的计算性能决定了"A3B"将不会是瓶颈,主要瓶颈是显存。

此时的理论物理上界是 300/6=50T/s。

假设显存的占用模型是设备无关的,只与llama.cpp的实现相关,那么可以使用上面的显存占用公式来推算在Tesla T4上可行的配置。

已知参数

q4_0 的 KV 增长率:α = 0.0075 MiB/token

考虑的上下文长度:

128K:L₁₂₈ = 131072 tokens

256K:L₂₅₆ = 262144 tokens

KV 缓存占用:

KV₁₂₈ = α * L₁₂₈ = 0.0075 * 131072 = 983.04 MiB

KV₂₅₆ = α * L₂₅₆ = 0.0075 * 262144 = 1966.08 MiB

假定可用显存上限:Mem_avail = 14000 MiB

因此,模型权重及固定开销 B(n) 必须满足:

对于 128K:B(n) ≤ 14000 - 983.04 = 13016.96 MiB

对于 256K:B(n) ≤ 14000 - 1966.08 = 12033.92 MiB

B(n) 的线性插值

已知表中数据(部分):

n B(n)
16 14000
32 6600

在区间 16, 32 内,我们合理假设B(n) 随 n 线性下降,斜率为:

(6600 - 14000) / (32 - 16) = -7400 / 16 = -462.5 MiB/单位n

因此,在该区间内:

B(n) = 14000 - 462.5 * (n - 16)

从而,我们可以计算满足部署需求的最小 n

128K 上下文

需要 B(n) ≤ 13016.96:

14000 - 462.5 * (n - 16) ≤ 13016.96

462.5 * (n - 16) ≥ 983.04

n - 16 ≥ 983.04 / 462.5 ≈ 2.126

n ≥ 18.126

由于 n 必须是整数,最小 n = 19。

此时:

  • B(19) = 14000 - 462.5 * (19 - 16) = 14000 - 1387.5 = 12612.5 MiB
  • 总显存 = 12612.5 + 983.04 = 13595.54 MiB,小于 14000 MiB。

256K 上下文

需要 B(n) ≤ 12033.92:

14000 - 462.5 * (n - 16) ≤ 12033.92

462.5 * (n - 16) ≥ 1966.08

n - 16 ≥ 1966.08 / 462.5 ≈ 4.251

n ≥ 20.251

最小整数 n = 21。

此时:

  • B(21) = 14000 - 462.5 * (21 - 16) = 14000 - 2312.5 = 11687.5 MiB
  • 总显存 = 11687.5 + 1966.08 = 13653.58 MiB,同样小于 14000 MiB。

可行方案汇总

上下文长度 最小 ncmoe 预计显存占用 (MiB) 备注
128K 19 ~13596 非常接近 14GB 上限,余量约 404 MiB
256K 21 ~13654 余量约 346 MiB,更紧张

实际测试结果:

  • 128k, q4_0, ncmoe=19 --> 13492 MiB (误差约为0.7%), 42.8t/s with 2700 tokens generated
  • 256k, q4_0, ncmoe=21 --> 13670 MiB (误差约为0.4%), 41.6t/s with 2084 tokens generated

256K上下文的41.6t/s达到了理论物理上界的83.2%,而且显存占用也有约1.6GiB的余量,完全可以满足实际部署的需求。

下面是不同ncmoe和上下文长度的实测显存占用和推理速度:

ncmoe ctx 实测显存(Mib) 实测推理速度(T/s)
40 256k 4730 34
40 128k 3626 34
40 64k 3148 34
30 256k 9472 38
30 128k 7856 38
30 64k 8368 38
20 256k 14114 43

显然,3090的显存近似模型可以外推到Tesla T4上,但是,工程暴力破解的局限性也体现出来了,那就是实际上还存在ncmoe=20的更优解,推理速度达到43t/s,显存占用约为13.8GiB,余量约为1.2GiB,也完全可以满足实际部署的需求。

5. 开始想象1张3090部署DeepSeek-V4-Flash + 1M上下文

这次不再先采样然后近似了,一方面是DeepSeek-V4-Flash的参数规模远大于Qwen3.6-35B-A3B,另一方面是llama.cpp对它的支持度还不够完善,最重要的还是,现在我有点信心可以直接进行理论推算了。

DeepSeek-V4-Flash是一个284BA13B的模型,其注意力层使用了CSA + HCA混合注意力,kvcache的效率为最高节省98%。其专家层也包含1个共享专家+256个路由专家,但每次只激活6个专家。

V4 整体架构图包含从 Input Tokens → Embedding → Residual Mixing(mHC)→ CSA/HCA → Residual Mixing → DeepSeekMoE → Prediction Head → MTP Modules 的完整数据流,层间由 Pre-Block Mixing 和 Post-Block Mixing 做 mHC 的通道混合

其中CSA/HCA->DeepSeekMoE 一共重复了43次(Transformer Block * L, L=43)。

那么我们可以同理推算X, Y, Z。

设43层注意力层总权重为X, 43层路由专家的总权重为256Y, 43层共享专家的总权重为Z, 其他层之和暂且视为常数忽略掉(Embedding, Prediction Head和MTP)。

那么有

\X + 256Y + Z = 284 \\

\X + 6Y + Z = 13 \\

我们可以先分别估算出Y和X+Z的值,Y=1.084, X+Z=6.496。

因为注意力层和共享专家总是一定在GPU完成计算,所以也不需要具体求解X和Z,后续统一称为XZ

那么推理时一个1token对应需要的固定参数量为X+Z=6.496B, 路由专家总参数量6B=6.504B

然后我们下载usloth量化的Q4_K_XL量化版本,在4bit量化下,按照1B约等于0.5GB的映射关系,那么在存储上:

  • 固定开销需要的存储大小为6.496*0.5=3.248GiB
  • 路由专家总需要的存储大小为6.504*0.5=3.252GiB

但是我们的3090不支持int4计算,那么在实际计算时需要先将q4还原成fp16, 那么实际计算过程中1个token需要的显存带宽为:

  • 13B*2GiB/B = 26 GiB
  • (如果只还原成int8)则是13B*1GB/B=13GiB

3090的显存带宽为936GB,但是社区经验告诉我们实际上最多只有300GB能够被有效利用,根据内存墙规律,我们理论上的最大推理速度为:

  • fp16: 936/26 = 36 T/s

然而这个理论值完全不可能被实现和验证,因为3090只有24GB显存,我们不可能将DeepSeek-V4-Flash完整放在显存里面。因此必须使用专家卸载技术。

说干就干,开始测算。

在静态显存占用方面:

  • 注意力层+共享专家需要的总显存:(X+Z)*0.5=3.248 GB
  • 256个专家需要的总显存:Y2560.5=138.752 GB

在动态显存(KV Cache)方面:

我们直接采用最极端的q4_0量化。V4一共有43层重复的注意力层,KV头数为1, 头维度为512, 查询头数为64, 根据deepseek-v4-flash的开源权重config.json,每一层的压缩率分别为:

0, 0, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 0, 0, 0

应该有两层没有压缩,即有21层压缩率是4, 20层压缩率是128, 2层压缩率是1

那么每1KB的kvcache显存占用为:

KV Cache大小 (字节) = 层数 (L) × 2 (K和V) × 隐藏维度 (head_dim) × 键值头数 (num_key_value_heads) × 精度字节数 (FP16为2) × Token数

  • 2 * 512 * 1 * (21 / 4 + 20 /128 + 2) * 0.5 * 1024 = 7584 * 0.5 * 1024 = 3,883,008 Byte = 3.703125 MiB
  • 那么要达到DeepSeek-V4-Flash的原生1M上下文所需要的显存为 3.703125 * 1024 = 3792 MiB = 3.703125 GiB

假设我们将n层的专家权重放在CPU上,那么就必须满足

\3.248 + 138.752\*(43-n)/43 + L \* 3.703125 / 1024 \\le R \\

其中L是上下文长度(K),R是显存(GiB)

\n \\ge 43 - \\frac{(R - 3.248 - L\*3.703125/1024) \* 43}{138.752} \\

接下来是怎么计算推理时需要计算的数据量:

接下来是怎么计算推理时需要计算的数据量:

纯GPU计算时:

  • 单token总数据量 =(注意力层+共享专家总参数量)* GPU计算精度 + 路由专家总参数量 * GPU计算精度
  • 推理速度 = 显存带宽 / 单token总数据量

采用专家卸载时:

  • 单token的GPU数据量: 注意力层+共享专家总参数量)* GPU计算精度 + 路由专家总参数量 * GPU计算精度 * (1-n/43)
  • 单token的CPU数据量: 路由专家总参数量 * CPU计算精度 * n/43
  • GPU推理速度 = 显存带宽 / 单token的GPU数据量
  • CPU推理速度 = CPU内存带宽 / 单token的CPU数据量
  • 最终推理速度 = min(GPU推理速度, CPU推理速度)

计算精度/量化压缩级别: fp16(f16)=2, int8(q8_0)=1, int4(q4_0)=0.5

3090支持int8计算,CPU先假设支持int4计算(这一假设来自于我对于CPU高性能计算的无知,以至于后面先进入了幻想时间)

计算速度取决于激活量,对于GPU和服务器级别的CPU来说,MoE架构的激活量都太小了,不存在计算瓶颈,影响推理速度的是内存带宽搬运数据的速度,即内存墙。

我通过lmbench的bw_mem测得当锁定一个NUMA, 24线程的时候,CPU read 内存的实际速度是102GB/s, 48 线程则为210GB/s,

所以用这个102-210之间的值来代替内存理论带宽将会更真实,先按102的保守值来计算。

3090的显存带宽是960GB/s,但是社区经验告诉我们llama.cpp可能只有20%-30%的利用率,也就是应该用192-288GB来计算(187.5GiB-281.25GiB)

ncmoe还涉及到CPU与GPU之间的通信,而PCIE通信的带宽可以约等于32GB, 仅仅用来传递中间激活值(A13B)的话,就算是fp16精度也完全能cover住,可以认为只有延迟,没有速度损失。

上下文长度(K) min-n GPU计算精度 静态显存(GiB) kvcache(GiB) GPU数据量(GiB) CPU数据量(GiB) GPU T/s CPU T/s 最终 T/s
1024 38 fp16 19.382 3.703 13.370 2.874 22.44 35.49 22.44
1024 38 int8 19.382 3.703 6.874 2.874 43.64 35.49 35.49
1024 38 int4 19.382 3.703 3.626 2.874 82.73 35.49 35.49
512 38 fp16 19.382 1.852 13.370 2.874 22.44 35.49 22.44
512 38 int8 19.382 1.852 6.874 2.874 43.64 35.49 35.49
512 38 int4 19.382 1.852 3.626 2.874 82.73 35.49 35.49
256 37 fp16 22.609 0.926 13.446 2.798 22.31 36.45 22.31
256 37 int8 22.609 0.926 6.950 2.798 43.17 36.45 36.45
256 37 int4 22.609 0.926 3.702 2.798 81.04 36.45 36.45

这个估计结果就很符合预期,因为对于MOE模型来说,在启用了专家卸载时,显存和内存构成了双重内存墙,适当的卸载能减轻对CPU内存带宽的需求,但转移给GPU的负载对于显存来说仍然是九牛一毛,从而实现最大化的推理速度。

同理可以考虑双3090的情况,因为没有nvlink,所以必须放弃tensor split, 只能选择layer split, 那么PCIE仍然不会成为瓶颈,此时可以认为显存翻倍,延迟增加,推理速度的推算方式不变,此时:

上下文长度(K) min-n GPU计算精度 静态显存(GiB) kvcache(GiB) GPU数据量(GiB) CPU数据量(GiB) GPU T/s CPU T/s 最终 T/s
1024 31 fp16 41.969 3.703 13.900 2.344 21.58 43.51 21.58
1024 31 int8 41.969 3.703 7.404 2.344 40.52 43.51 40.52
1024 31 int4 41.969 3.703 4.156 2.344 72.19 43.51 43.51
512 30 fp16 45.196 1.852 13.975 2.269 21.47 44.96 21.47
512 30 int8 45.196 1.852 7.479 2.269 40.11 44.96 40.11
512 30 int4 45.196 1.852 4.231 2.269 70.90 44.96 44.96
256 30 fp16 45.196 0.926 13.975 2.269 21.47 44.96 21.47
256 30 int8 45.196 0.926 7.479 2.269 40.11 44.96 40.11
256 30 int4 45.196 0.926 4.231 2.269 70.90 44.96 44.96

单卡实际跑通的配置和结果:

上下文长度(K) ncome GPU显存(MiB) 推理速度(T/s)
1024 40 20918 3.6
512 39 22958 7.5
256 44 9290 7.55
256 43 9290 7.90
256 42 12554 7.97
256 41 15816 7.65
256 40 19086 8.3
256 39 22346 8.4
128 39 22044 8.4

可以得出:

  • 纯注意力层+共享专家+llama.cpp固有开销的显存占用是 9290 MiB
  • ncmoe从43->42, 显存占用增加3264MiB; 从42->41,显存占用增加3262MiB;基本稳定,直接计算从43->39, 显存占用增加13056MiB,平均每层3264MiB=3.1875GiB,与估算值3.252GiB相差2%。
  • 在ncmoe=39的情况下,上下文从128K增加到512K, 显存占用增加了914MiB, 平均每K需要2.38MiB,低于估算的3.703125 MiB,DeekSeek-V4的混合注意力机制比想象中的更厉害
  • 减少ncmoe会减少cpu负担,增加gpu负担,但是推理速度却增加了,说明GPU带宽游刃有余,CPU是主要瓶颈

实际推理速度与估算值差距非常大,说明对CPU瓶颈的分析肯定存在问题,潜在的原因:

  • CPU内存的随机访问带宽远低于顺序读取,每个Token只激活6个专家,但256个专家分布在CPU内存的不同物理地址。
  • CPU没有高效的int4计算能力,或者没有使用,导致实际数据搬运量是int4的2-4倍

这两个原因组合起来倒是能解释实测的7-8T/s

验证这个猜想的方法很简单,再测试一下双卡,将更多的负载转移到GPU就知道了

双卡实际跑通的配置和结果:

上下文长度(K) ncome sm ts GPU0显存(MiB) GPU1显存(MiB) 推理速度(T/s)
1024 33 layer 5,1 22484 23200 9.48
512 33 layer 5,1 21360 22338 9.63
256 33 layer 5,1 20820 22010 9.65

llama.cpp目前不支持DeepSeek-V4-Flash的tensor split,只能选择layer split

进一步提高了,但是不能超过10T/s。

理论上,ncmoe=33时,GPU负载的路由专家数据量=103.18756/256=0.747GiB,相比于注意力层的数据量不值一提,和单卡时的ncmoe=39没什么区别。因此瓶颈一定在CPU。

为了进一步验证,再试一下3*3090

上下文长度(K) ncome ts GPU0显存(MiB) GPU1显存(MiB) GPU1显存(MiB) 推理速度(T/s)
1024 27 21,4,5 21284 22662 23200 10.72
512 27 21,4,5 20298 21800 22344 10.63
512 26 21,4,5 23564 21800 22338 11.48

显然,我们每为CPU减少一点负担,CPU的瓶颈就会增大一点。

不妨将CPU的计算精度假设为fp16,那么每层的数据量就是3.1875*4=12.75GiB

  • 1024K+卸载40层时,CPU有效带宽是 12.75406/256*3.6 = 43.03125 GiB
  • 512K+卸载39层时,CPU有效带宽是 12.75396/256*7.5 = 87.4072265625 GiB
  • 256K+卸载39层时,CPU有效带宽是 12.75396/256*8.4 = 97.89609375 GiB
  • 1024K+卸载33层时,CPU有效带宽是 12.75336/256*9.48 = 93.485390625 GiB
  • 512K+卸载33层时,CPU有效带宽是 12.75336/256*9.63 = 94.9645898438 GiB
  • 1024K+卸载27层时,CPU有效带宽是 12.75276/256*10.72 = 86.4928125 GiB
  • 512K+卸载26层时,CPU有效带宽是 12.75266/256*11.48 = 89.19421875 GiB

随着卸载层数减少,内存随机地址的影响应该会越来越小,访问速度越来越快,这没问题。基本可以确定,实际上的CPU计算就是先反量化到fp16再交给CPU算的。

如果那么我们的理论预测表格就可以更新为

单卡:

上下文长度(K) min-n GPU计算精度 静态显存(GiB) kvcache(GiB) GPU数据量(GiB) CPU数据量(GiB) GPU T/s CPU T/s 最终 T/s
1024 40 fp16 18.634 2.380 37.184 11.953 25.82 8.53 8.53
1024 40 int8 18.634 2.380 18.592 11.953 51.63 8.53 8.53
1024 40 int4 18.634 2.380 9.296 11.953 103.27 8.53 8.53
512 39 fp16 21.822 1.190 37.483 11.654 25.61 8.75 8.75
512 39 int8 21.822 1.190 18.742 11.654 51.22 8.75 8.75
512 39 int4 21.822 1.190 9.371 11.654 102.45 8.75 8.75
256 39 fp16 21.822 0.595 37.483 11.654 25.61 8.75 8.75
256 39 int8 21.822 0.595 18.742 11.654 51.22 8.75 8.75
256 39 int4 21.822 0.595 9.371 11.654 102.45 8.75 8.75

双卡:

上下文长度(K) min-n GPU计算精度 静态显存(GiB) kvcache(GiB) GPU数据量(GiB) CPU数据量(GiB) GPU T/s CPU T/s 最终 T/s
1024 33 fp16 40.947 2.380 39.276 9.861 24.44 10.34 10.34
1024 33 int8 40.947 2.380 19.638 9.861 48.88 10.34 10.34
1024 33 int4 40.947 2.380 9.819 9.861 97.77 10.34 10.34
512 32 fp16 44.135 1.190 39.575 9.562 24.26 10.67 10.67
512 32 int8 44.135 1.190 19.788 9.562 48.52 10.67 10.67
512 32 int4 44.135 1.190 9.894 9.562 97.03 10.67 10.67
256 32 fp16 44.135 0.595 39.575 9.562 24.26 10.67 10.67
256 32 int8 44.135 0.595 19.788 9.562 48.52 10.67 10.67
256 32 int4 44.135 0.595 9.894 9.562 97.03 10.67 10.67

这下就基本全对上了。

新的问题在于实际有效带宽在39层达到97GiB之后,再减少卸载层数,有效带宽反而降低了。肯定又有别的因素开始占据主导地位了,但是又对整体影响不是很大。

至于别的因素到底是什么?I don't know, it's time to learn more.

6. 思考环节:MOE架构还能怎么优化?

6.2.1 横向卸载与专家缓存池

llama.cpp的-ncmoe是纵向卸载的,将前n层的专家卸载到CPU上,后续的专家仍然在GPU上计算,整体形成了一个计算流水线,影响首字延迟,但是完全不影响吞吐。

有没有可能,将每一层的x个专家卸载到CPU上,其余的就呆在GPU上。在一次推理时,如果共享专家选择了在GPU上的路由专家,那么就直接在GPU上计算,如果选择了在CPU上的路由专家,那么就将这个专家的权重从CPU搬运到GPU上计算,然后通过缓存池常见的方式踢掉另一个专家的权重去CPU上。这样我们的推理速度瓶颈就更有可能来自于显存带宽,前提是我们的缓存策略设计的足够好,路由专家的轮换频率足够低,否则,频繁的搬运专家权重会导致CPU内存带宽再次成为瓶颈。

6.2.2 注意力压缩

DeepSeek-V4-Flash的混合注意力机制已经证明了注意力压缩的可行性,甚至可以说是非常成功的,建议其他厂商都学习一下。

不过,是否可以从推理引擎的角度,实现一种为任意模型提供注意力压缩的通用方法呢?