DeepSeek-V4.1-Flash 三种部署架构的实测对照
全部数字为同一台机器上的实测,FP8只测试了2卡部署
机器:4×L20 48 GB · 2×Xeon Gold 6530(128 逻辑核,双 NUMA)· 1007 GB RAM · RAID5 NVMe
权重:dealignai/DeepSeek-V4.1-Flash-UNCENSORED-FP8(476 GB,fp8 block-32 稠密 + fp4 专家)
IndrasMirror/DeepSeek-V4.1-Flash-GSQ-Uncensored/V41-GSQ.gguf(152 GiB,三值/2bit/3bit 混合专家)
0. 一句话结论
同一个模型、同一台机器,三种架构的单流解码速度实测差 3.4 倍:
| 架构 | 专家权重住在哪 | 单流解码 | 并发聚合 |
|---|---|---|---|
| Lvllm(张量并行) | 显存(部分层常驻)+ 主机 | 34.5 中位 / 38.2 峰值(共驻时跌到 1–13) | --max-num-seqs 2 |
| Strata(GGUF + 投机解码) | 主机 pinned + 显存槽缓存(88.7% 命中) | 50–88 | 单实例串行,需一卡一实例 |
| FreeToken(专家库分流) | 主机 pinned 库,按带宽比例分给 PCIe 与 CPU | 25.7 | ~100 tok/s @ C=16 |
三个架构的上游仓库:Lvllm https://github.com/guqiong96/Lvllm(vLLM fork,上游 https://github.com/vllm-project/vllm)· Strata https://github.com/Indras-Mirror/Strata-DSV4.1(基于 https://github.com/Niko1221/Strata)· FreeToken https://github.com/FlashML-org/FreeToken
差别不在"哪个引擎更好",而在每个输出 token 要从主机搬多少字节,以及一步能不能产出多个 token。全文就是围绕这两件事展开的。
1. 被测对象:模型、权重与量化(所有数字的前提)
三个架构跑的是两份不同的权重
基座:deepseek-ai/DeepSeek-V4.1-Flash(MIT,多模态 MoE)。
| 权重 | Hugging Face 仓库 | 本机路径 | 体积 | 量化方案 | 谁在用 |
|---|---|---|---|---|---|
| 原版 FP8/FP4 | dealignai/DeepSeek-V4.1-Flash-UNCENSORED-FP8(base_model = deepseek-ai/DeepSeek-V4.1-Flash,abliterated) |
/opt/models/dealignai/DeepSeek-V4.1-Flash-UNCENSORED-FP8(48 个 safetensors 分片) |
476 GB | quant_method: fp8,weight_block_size [32,32],scale_fmt ue8m0,activation dynamic;专家 expert_dtype: fp4(≈4 bit) |
Lvllm、FreeToken |
| GSQ 混合三档 GGUF | IndrasMirror/DeepSeek-V4.1-Flash-GSQ-Uncensored |
/opt/models/IndrasMirror/DeepSeek-V4.1-Flash-GSQ-Uncensored/V41-GSQ.gguf |
152 GiB | 逐专家分档:PTQ1_0 三值 {−1,0,+1}×fp16 scale/128 = 1.75 bit(占专家权重 42.8%)、Q2_0 2.25 bit(39.2%)、Q3_K 3.44 bit(18.0%),专家均值 2.250 bit;稠密半(attention / shared expert / lm head)Q8_0;embedding、compressor、indexer 保持 F16;norm、router、mHC 保持 F32 | Strata |
| MTP 草稿模型 | 同上仓库 | …/V41-MTP-Vision.Q2_K.gguf(5.66 GB;同目录另有非量化版 V41-MTP-Vision.gguf 8.42 GB) |
5.66 GB | Q2_K | Strata(DSpark 投机解码的草稿) |
| Engram n-gram 表 | 同上仓库(需从原权重导出,仓库里是 NEED-TO-DOWNLOAD-ENGRAM.txt 说明) |
/opt/models/IndrasMirror/V41-Engram/L1、/L14 |
189 GB | 外置表,不量化 | Strata |
结构参数(读 config.json 得到,第 2 节的算术全部基于它):
- 主干:
num_hidden_layers 40、hidden_size 5120、moe_intermediate_size 2304、n_routed_experts 384、n_shared_experts 1、num_experts_per_tok 6、vocab_size 129280、max_position_embeddings 1048576(YaRN factor 16,原始 65536)。 - 稀疏注意力:
compress_ratios前 20 层为 0/2、21–39 层为 1、末两层为 0;kv_source_layer_ids [2,8,14,20];index_topk 512。 - mHC 超连接:
hc_mult 4、hc_sinkhorn_iters 20← 第 6 节那个 630 MiB OOM 就是这一项算出来的。 - Engram:
engram_layer_ids [1,14]、engram_vocab_size 16000000、engram_compressed_vocab_size 99092、engram_max_ngram_size 4← 两张 98 GiB 表的来源。 - 原生投机解码:
num_nextn_predict_layers 3、dspark_block_size 5、dspark_target_layer_ids [37,38,39]、dspark_markov_rank 256、dspark_n_routed_experts 128、dspark_num_experts_per_tok 3← Strata 的 DSpark 用的就是模型自带的这套,不是外挂草稿。
为什么必须把权重写死:
- 速度差主要来自这里。 GSQ 那一路每个专家的字节数是 FP4 那一路的 约 1/1.8(2.25 bit vs 4 bit),再叠上"一步验 5 个草稿、平均接受 3.39 个",每个被接受 token 实付 ~0.5 GB 而不是 4.25 GB。50–88 与 25–34 的差距是字节差,不是引擎差。
- 质量代理要一起报。 GSQ 仓库自带的 held-out 口径:三值相对输出误差 0.443 / Q2 0.378 / Q3 0.183,混合方案在同体积下比全 Q2_0 少 17% 输出误差。本机测的质量代理是 PPL 3.0818 / top-1 0.791(Strata 升级到
9d6529fc前后逐位不变)。 - 最容易骗人的地方:三个服务对客户端都报
--served-model-name DeepSeek-V4.1-Flash/model_name: DeepSeek-V4.1-Flash,/v1/models里长得一模一样,加载的权重完全不同。 - 采样参数也影响速度(接受率随采样温度变):Strata 侧
temperature 1.0 / top_p 0.95,--route-bias 0.25(--route-bias-file 0.15),STRATA_SERVE_PASS_BIAS=0(每个 prompt token 精确路由)。
2. 先把物理约束算出来
DeepSeek-V4.1-Flash 的关键结构参数(读 HF config.json 得到):40 层、hidden 5120、MoE 中间维 2304、每层 384 个专家、top_k = 6、第 1/14 层各挂一张 98 GiB 的 Engram n-gram 表。
由此可以纯算术地算出解码成本:
单个专家 = 3 × 5120 × 2304 参数 × 0.5 B(fp4) = 17.7 MB
每层每 token 取 6 个专家 → 每 token = 6 × 40 × 17.7 MB = 4.25 GB
全部专家 = 15360 × 17.7 MB = 272 GB (与加载日志 "275G" 吻合)
这台机器的三条通道实测带宽:
| 通道 | 实测 |
|---|---|
| PCIe H2D(pinned) | 25.2 GB/s |
| CPU STREAM | 512.8 GB/s |
| CPU 上跑 MoE GEMV | 130.2 GB/s |
| L20 显存 | ~864 GB/s |
于是单流解码的上界一目了然:
- 全部专家走 PCIe:4.25 GB ÷ 25.2 GB/s = 169 ms/token → 5.9 tok/s
- 全部专家在 CPU 算:4.25 ÷ 130 = 33 ms → 30 tok/s(真实路径只有 ~38 GB/s 有效带宽 → 25 tok/s,实测吻合)
- 全部命中显存缓存:4.25 ÷ 864 = 4.9 ms → 200 tok/s
结论一:单流速度只由"每 token 搬多少字节"决定。 想快只有三条路——把字节变小(更狠的量化)、把字节搬到显存里(提高命中率)、或者一步产出多个 token(投机解码)。调线程数、绑 NUMA、加显存,都不动这三项,实测全部无效或为负。
Strata 用的 GSQ 权重把专家压到三值/2bit/3bit,152 GiB 装下整个模型;再叠上 MTP 投机解码(一步验 5 个草稿、平均接受 3.39 个),每个被接受的 token 实际只付 ~0.5 GB——这就是 50–88 tok/s 的全部来源,是 8 倍的字节差,不是引擎魔法。
3. 三种架构
A. Lvllm:张量并行 + 指定层常驻显存
仓库:https://github.com/guqiong96/Lvllm(vLLM 的 fork,= vLLM + lk_moe + SM80/86/89 适配;上游 https://github.com/vllm-project/vllm)
--tensor-parallel-size 2 占 GPU0+GPU1,--gpu-memory-utilization 0.95,fp8 MLA KV,163K 上下文,--max-num-seqs 2。主机侧关键开关:
LVLLM_ENABLE_MOE_LAYERWISE_LOAD=1 # 逐层加载专家,避免一次性峰值
LVLLM_MOE_NUMA_ENABLED=1 + LVLLM_ENABLE_NUMA_INTERLEAVE=1
LVLLM_GPU_RESIDENT_MOE_LAYERS='0,2,8,14,20-22' # 指定层专家常驻显存
LVLLM_GPU_PREFETCH_WINDOW=2
思路是"能常驻的层就常驻,其余逐 token 从主机取"。它把 4.25 GB/token 里的一部分变成显存命中,所以能到 34–38 tok/s,比纯 offload 高一档;代价是两张卡、max-num-seqs 2,并发能力被 KV 与显存挤掉。
B. Strata:低比特 GGUF + CPU 专家池 + MTP 投机解码
仓库:https://github.com/Indras-Mirror/Strata-DSV4.1(基于 https://github.com/Niko1221/Strata)
单卡 GPU0,152 GiB 模型全部进常驻内存 arena(--arena-gib 160,文件层 = 0),显存里只留 4288 个专家槽做 LRU,未命中交给 CPU 专家池;V41-MTP-Vision.Q2_K.gguf 做草稿模型,DEPTH=5 / KEEP:128 / CONF=0.6。
引擎每步都打印自己的账:
serve: 149 tokens in 2.79 s = 53.00 tok/s | experts: 38543 hit, 5857 cpu, 0 pcie, 0 file
serve: DSpark 44 passes, 3.39 tokens/pass
于是速度变成一个可以拆的公式(上游 345 passes 拟合,R²=0.977):
verify ms = 25.7 + 5.7×rows + 0.49×PCIe缺失 + 2.93×文件缺失
tok/s = 每 pass 接受 token 数 ÷ pass 时间
定稿:0 次 PCIe、0 次文件缺失,rows≈6.7 → pass ≈ 62 ms → 3.39/0.062 = 53 tok/s(实测 53.0)
结论二:速度只有两个独立旋钮——接受率(DEPTH/KEEP/CONF)和 pass 时间(q*,即未命中走不走 PCIe)。 其余参数都在乘 1。
C. FreeToken:pinned 专家库 + 按带宽比例分流
仓库:https://github.com/FlashML-org/FreeToken
Python 引擎,直接读 HF safetensors。专家放在 pinned 主机库(275 GB 全部进内存,加载 100 s @ 2.7 GB/s),显存只留一个 slot cache。四种策略:
fused全部常驻显存(275 GB > 48 GB,本模型不可用)offload未命中全部 PCIe 搬上卡cpu未命中全部在 CPU 算hybrid每层最多取 K 个未命中走 PCIe,其余在 CPU 算,两条路重叠
auto 会读 ft bench bw 的机器画像来选:本机 CPU-MoE/PCIe = 5.17× → 选 hybrid,每步只把 14.7% 的缺失走 PCIe。
4. 实测对照
4.1 单流解码
| 配置 | 单流 decode |
|---|---|
| Strata 定稿(短回复 / 枚举 / 自由散文) | 87.7 / 78.1 / 51.6 |
| Lvllm TP2(日志中位 / 峰值) | 34.5 / 38.2(10-15 20 时窗口峰值 48.8) |
FreeToken hybrid(auto 选的) |
25.7 |
FreeToken cpu |
26.0 |
FreeToken offload |
14.6 |
FreeToken hybrid --moe-hybrid-max-fetch 2 |
17.3 |
FreeToken hybrid --moe-cpu-threads 32 |
26.1 |
| Strata 调优起点(模型卡默认,未调) | 18.2 |
4.2 并发(FreeToken,客户端端到端含 TTFT / 引擎侧稳态)
| 并发 | 客户端聚合 | 单流 decode | 引擎侧稳态 |
|---|---|---|---|
| 1 | 9.9 | 25.3 | 23 |
| 4 | 17.4 | 11.3 | 53.5 |
| 8 | 32.9 | 8.2 | 66–73 |
| 16 | 53.5 | 5.6 | ~100 |
结论三:三种架构各自占住一条边。 Lvllm 用两张卡换 30+ 的中等单流;Strata 用低比特 + 投机解码拿到自由文本 50、模板化内容 78–88 的单流上限;FreeToken 单流最慢,但它是三者里唯一把并发做起来的(C=16 聚合 ~100 tok/s,因为一批请求共享同一批专家,搬一次字节服务 16 个 token)。
4.3 上下文
| 架构 | 实测上下文 |
|---|---|
| Lvllm | 163840(受 KV 与显存限制) |
| Strata | 256K 实测可用(+0.77 GiB),196K 预填充 331.6 tok/s,解码 @197K 从 53 掉到 33.67(注意力 +40% 且接受率 2.8→1.98) |
| FreeToken | 1048576(配置值),12k prompt 实测 TTFT 13.9 s |
5. 每个引擎的"决定性那一个开关"
三个引擎各有一个开关,值 20–30 个百分点,其余参数都在噪声里。
Strata:STRATA_TIER_BATCH_PCIE=0.0(27.8 → 53.1)。
它是 DSpark 验证步自己的 PCIe 份额,默认 1.0 = 验证步里每个缺失都用 DMA 搬上卡(作者在 4090 上这样最快)。本机 CPU 池强、显存槽多(4288),验证步每 token 只有 37 个缺失,全交给 CPU 池远快过搬卡。--pcie 只管单 token 路径,管不到验证步——这就是之前卡在 33 的原因。
第二档是 STRATA_DSPARK_KEEP=…:128:counts 文件每层正好 128 项 = 不剪草稿专家,接受率 2.85 → 3.39 tok/pass(:192 直接触发 GGML_ASSERT 崩)。
Lvllm:LVLLM_GPU_RESIDENT_MOE_LAYERS。
把指定层的专家钉在显存里,命中即免费。选哪些层是显存预算问题,不是算法问题。
FreeToken:ft bench bw 的画像 → hybrid。
它把"要不要走 PCIe"变成按实测带宽比例解的优化题:f/(1-f) = PCIe有效/CPU有效。实测三档(14.7% / 33% / 100%)呈 U 形,最低点就在 auto 选的 14.7% 附近——auto 的判断被 A/B 证实了,offload 单流慢 1.7×、并发慢 3.5×。
6. 负结果清单
Strata(15 档 A/B,全部为负或噪声内):--threads 128(26.7,全核互踩)、--threads 64(无收益)、numactl --cpunodebind=0 软绑(31.3,96 线程挤 64 逻辑核超订)、严格 membind(更差)、--skip-miss 0.01/0.02/0.05(无收益且改路由有质量风险)、--vram-margin 0.5、STRATA_LRU_WIN=8、STRATA_PROMPT_BATCH=12288(解码 −18%,接受率 3.39→2.74;上游的 +21.5% 是 prefill,本机解码付不起)、STEP_MAX=512。
唯一白捡的是 DS4_NUMA=0 + DS4_THREADS=48:引擎侧计数逐位相同(不改一个 token),只把线程落点收到 GPU0 所在 node0,方差 σ 8.3 → 0.2,共驻时不掉速。
FreeToken:--moe-strategy offload(−43%)、--moe-cpu-threads 32(单流无收益、并发 −26%)、--moe-hybrid-max-fetch 2(−35%)。
采样定位很干脆:单流解码时 GPU0 util = 0%,63 个 CPU 线程 99.9% —— 瓶颈在 CPU 侧的每层同步与有效带宽(实测 ~38 GB/s,远低于 130 GB/s 的微基准),不在显存、不在 PCIe。
Lvllm:把 gpu-memory-utilization 从 0.9 推到 0.95、TP 从 GPU0+GPU1 换到 GPU0+GPU2,单流都没变快(跨 NUMA 反而更差)。日志里同一份配置的"平均生成吞吐"从 1.0 到 48.8 tok/s 都出现过——任何不带测量协议的单点数字都没有意义。
长上下文的坑(FreeToken):服务在真实客户端发来 ≥8192 token 的 prompt 时 OOM 崩,栈在 Engram 层。原因是 Engram 的 fp32 中间量 = hc_mult 4 × hidden 5120 × 4 B = 81.9 KB/token,而 --max-prefill-length 默认 8192 → 单块 630 MiB,正好等于崩溃时申请失败的那 630.00 MiB。修复:--memory-ratio 0.8(留 8.92 GiB)+ --max-prefill-length 4096(中间量减半),12k prompt 复测通过。
这类 bug 只能靠"申请失败的大小 = 公式算出的大小"来定案,不能靠猜。
7. 让数字可信的四条纪律
- 交错 A/B,不要顺序测。 单窗口顺序测会被机器负载污染:base 撞上 GPU util 99% 时测得 68.7/40.7,同一档在空闲窗口是 85.2/49.7。改成 base/t48/rb05 三档轮换 3 轮,每档重启 + 暖机 + 测一遍,并记录同期 GPU util。
- 引擎侧计数是"有没有改变输出"的裁判。 t48 的
45 passes, 3.31 tokens/pass与基线逐位相同 → 它买的是机器争用,可以白捡;--route-bias 0.5/0.3的接受率从 3.31 变 2.89、枚举那档少生成 660 个 token → 它换了输出,是拿质量换的(官方 top-1 −3.5 pt),不做默认。 - 速度取决于内容。 同一台机器、同一份权重、同一个配置:定长枚举 77–78 tok/s,自由散文 50–53 tok/s。任何"我的模型能跑 X tok/s"都要说清测的是什么内容。
- 共驻负载要写进结论。 本机 GPU3 的 Qwen 服务持续生成时,Strata 的 50–52 会掉到 32–45。两个引擎抢的是 CPU 与内存带宽,被抢走的正是 pass 时间。
8. 选型建议
- 要单流最快、以对话/写作为主 → Strata 路线:低比特 GGUF + MTP 投机解码。自由散文 50–52 是这台机器的上限,模板化/枚举内容 78–88。
- 要高并发吞吐(多用户、批处理、agent 后端) → FreeToken 路线:C=16 聚合 ~100 tok/s,1M 上下文,直接吃 HF 权重;单流 25 是它的短板,别用它做单人流式对话。
- 要现成的生态与工具链(OpenAI 兼容、prefix caching、chunked prefill) → Lvllm 路线:34–38 tok/s,代价是两张卡与
max-num-seqs 2。
9. 附录:定稿配置
三个架构的仓库(本机对应 checkout):
| 架构 | 仓库 | 本机 checkout | 定稿所用 commit / 版本 |
|---|---|---|---|
| Lvllm | https://github.com/guqiong96/Lvllm(fork 自 https://github.com/vllm-project/vllm,= vLLM + lk_moe) | /opt/Lvllm,conda env Lvllm |
bd88fa1244(branch upgrade-v0.31.0)/ lvllm 0.31.1.dev352+g767a15c1f.cu132 |
| Strata | https://github.com/Indras-Mirror/Strata-DSV4.1(基于 https://github.com/Niko1221/Strata) | /opt/Strata-DSV4.1 |
9d6529fc + 本地 kv-fp4.patch |
| FreeToken | https://github.com/FlashML-org/FreeToken | /opt/FreeToken,conda env FreeToken |
e5d3bd3 / freetoken 0.1.3 |