GEOZ

小显卡跑 Gemma 4 投机解码:单请求加速 1.72 倍,并发反而变慢

2026/10/18
小显卡跑 Gemma 4 投机解码:单请求加速 1.72 倍,并发反而变慢

AIAI Summary (BLUF)

本文提供了一份分步指南,教你如何使用llama.cpp的投机解码功能,在RTX 3050 OEM和GTX 1650 Ti两张小型NVIDIA显卡上部署Gemma 4 E2B和E4B模型及其QAT助手草稿模型。实测显示,单请求下草稿模型能带来1.18倍到1.72倍的加速,且答案质量不变,但在多请求或长对话场景下加速效果会减弱甚至变为负优化。

核心洞察

这篇文章最有意思的地方在于,它用两块很普通的消费级显卡(甚至有一块是笔记本)来验证投机解码到底值不值得。结论是:单请求场景下确实有提升,但并发一上来,笔记本那块卡直接变成负收益。如果你手上是类似档位的硬件,这些数字比任何理论分析都实在。


本文是一份分步指南,讲怎么用 llama.cpp 的投机解码来部署 Gemma 4 E2B 和 E4B,搭配 Google 的 QAT assistant drafter,然后在两块小 NVIDIA 显卡上实测 drafter 到底值多少:一块桌面级的 RTX 3050 OEM,一块笔记本上的 GTX 1650 Ti。同时还会搭一套 Python MCP 工具,方便管理 llama.cpp 的部署。

这是系列的第二篇。第一篇《Gemma 4 的 MTP Drafter 解析》讲了投机解码和 Gemma 4 多 token 预测(MTP)drafter 的原理,以及那个精确的 Q4_0 重打包是怎么做的。这一篇全是实测数据。

代码仓库在这里:https://github.com/xbill9/gemma4-dev

Drafter 就是投机解码里的那个"猜词的":它先猜接下来几个 token,主模型一次性全部验证。

小模型上 drafter 有用吗?有用。 Gemma 4 E2B 和 E4B 本身已经够小了,它们的 drafter 是一个 4 层、59 MB 的模型。单请求串行服务时,它让两个模型在两块卡上都变快了,而且 GSM8K 和 BFCL 分数保持不变:

单请求串行 RTX 3050 GTX 1650 Ti
E4B 1.72x 1.49x
E2B 1.40x 1.18x
回答质量 不变 不变

小模型收益更少。E2B 本身每 token 就快,所以每一步 draft 的开销占它省下来的时间比例更大(这是从下面的 draft 长度结果推出来的,没有直接测)。两块卡保留的 draft token 比例相差在 0.03 以内,所以卡之间的差距来自卡本身。

收益是有边界的。并发请求一多,两块卡上的收益都会缩水,在 1650 Ti 上从 4 个并发(E4B)或 8 个(E2B)开始变成负收益。1650 Ti 上 E2B 的收益大约在 32k token 的对话长度时消失。draft 的 token 太多,在慢卡上也会拖速度。

这里的 drafter 是 Google 权重的精确 Q4_0 重打包。它在速度和回答上与转换器产出的 Q8_0 文件持平,体积只有后者的 60%,而且在每块卡、每个模型上都比 Google 的 bf16 权重快。


核心结论

  1. 在单请求串行场景下,Gemma 4 E2B/E4B 搭配 QAT assistant drafter 在 RTX 3050 OEM 和 GTX 1650 Ti 上均获得加速:E4B 分别达到 1.72x 和 1.49x,E2B 分别为 1.40x 和 1.18x,且 GSM8K 与 BFCL 分数保持不变。

  2. 并发请求会侵蚀收益:在 GTX 1650 Ti 上,E4B 从 4 个并发、E2B 从 8 个并发开始变为负收益;E2B 的收益在约 32k token 对话长度时消失。

  3. 精确 Q4_0 重打包的 drafter 文件仅 59.2 MB(为 Q8_0 的 60%、bf16 的 35%),相对 Google 原始权重的误差为 0.00138,比转换器产出的 Q8_0 低 3.6 倍,转回 bf16 后约 97% 数值与 Google 权重逐位一致。

  4. 重打包版在两块卡上速度与 Q8_0 持平、显存少用 38–40 MiB,且在所有场景下都快于 Google 的 bf16 权重——后者在 1650 Ti 上运行 E2B 时甚至比不用 drafter 更慢。

  5. 最佳草稿 token 数取决于显卡而非仅模型:RTX 3050 上两个模型均以每步 2 个 token 最优,GTX 1650 Ti 上则以 1 个 token 领先;从快卡照搬设置到慢卡会损失大部分收益。

到底在测什么?

语言模型每做一次前向传播写一个 token,每次传播都要读一遍所有权重。小 GPU 上单个用户场景,每次传播大部分时间花在搬权重上,算力是闲着的。投机解码就是利用这部分闲置算力:一个小 drafter 猜接下来几个 token,主模型一次全部验证。它保留猜对的部分直到第一个分歧,再加上自己生成的一个 token,所以输出始终是主模型的。大多数猜测被保留时,一次传播就产出好几个 token 而不是一个。

Gemma 4 的 drafter 用的是多 token 预测(MTP)。它是一个 4 层模型,读主模型的隐藏状态、复用主模型的 KV cache,所以自己没有 cache,开销比它的权重多不了多少。

MTP 只是投机解码的几种做法之一。llama.cpp 77c4540ce 通过 --spec-type 提供十种:独立的小 draft 模型(draft-simple)、draft-eagle3、draft-mtp、draft-dflash、draft-dspark,还有五种 n-gram 方法。MTP 是 Google 专门为它构建 drafter 的那一种:E2B、E4B、12B、26B-A4B 和 31B 各有一个发布的 assistant checkpoint(E2B 的配置里叫 Gemma4AssistantForCausalLM),llama.cpp 以 draft-mtp 运行它,vLLM 里叫 "method":"mtp"。本文只测 MTP。第一篇有完整解释。

Google 为每个 Gemma 4 QAT checkpoint 都发布了一个 assistant drafter。这里两块卡部署的都是 gemma-4-E2B-it 和 gemma-4-E4B-it 的精确 Q4_0 GGUF,drafter 按训练时的 4-bit 网格存回 Q4_0,每个 59 MB。要回答的问题是:drafter 在每块卡上值多少,以及两块卡为什么不一样。


到这一步你应该准备好……

  • 一块带 CUDA 驱动的 NVIDIA GPU——这里是 RTX 3050 OEM(驱动 615.78.08)和 GTX 1650 Ti with Max-Q Design(驱动 615.71.09)
  • CUDA toolkit,这里是 13.4,以及 cmake
  • llama.cpp,commit 要支持 --spec-type draft-mtp——下面所有对比两块卡都跑的是 77c4540ce
  • Python 3.10 或更新版本,用系统自带的 python3,不用虚拟环境
  • 仓库已克隆,包含四个 rig 目录 local-llamacpp-{3050oem,1650ti}-{2b,4b}-q4_0/
  • Claude Code,或者任何支持 stdio 的 MCP 客户端

两块卡

RTX 3050 OEM GTX 1650 Ti Max-Q
主机 i7-5820K 台式机,46 GiB i7-10750H 笔记本(联想 Yoga 9),15 GiB
显存 8192 MiB 4096 MiB
架构 Ampere,计算能力 8.6,有 tensor core Turing TU117,计算能力 7.5,无 tensor core
功耗上限 120 W 约 40 W
E2B,无 drafter 108.2 tok/s 76.5 tok/s
E4B,无 drafter 58.3 tok/s 38.0 tok/s

没有 drafter 时,3050 在 E2B 上快 1.41 倍,E4B 上快 1.53 倍。主机不同,卡间比值只能当参考。


第一步——按训练网格重打包 drafter

Google 把 QAT assistant drafter 发布在 google/gemma-4-E2B-it-qat-q4_0-unquantized-assistant 和 google/gemma-4-E4B-it-qat-q4_0-unquantized-assistant。每个都是 4 层模型,读主模型的 KV cache,存为 bf16 safetensors。

量化感知训练已经把权重放在 4-bit 网格上了:每 32 个值一组,最多 16 个不同的电平,均匀分布。bf16 用 16 位来存这些电平。

llama.cpp 的 convert_hf_to_gguf.py 没法知道这一点,所以它写出 Q8_0:每权重 8.5 位,重新舍入到它自己的 8-bit 网格上。文件变小了,但值动了。只有 11.7% 的 drafter 权重能按位完全还原。

Q4_0 是每权重 4.5 位:每 32 个值一组一个 fp16 步长,每个值一个 4-bit 电平。只要每组步长从自己的值里读出来,就足以装下训练网格。gguf_exact.py 对每组做这件事:

amax = np.abs(g).max(-1, keepdims=True)
for m in range(1, 9):                      # try steps amax/1 .. amax/8
    cand = amax / m
    q = np.clip(np.rint(g / cand), -8, 7)
    good = np.abs(g - q * cand).max(-1, keepdims=True) <= amax * 2**-7

第一个能让组里每个值都落在整数电平上的步长胜出。然后用最小二乘拟合精修,再舍入到 fp16,也就是 Q4_0 的步长类型。如果某组找不到合适的步长,构建就停下,因为这里不做任何重新舍入。两个 drafter 上都没有组脱离网格。

脚本接收 Google 的 safetensors 和转换器的 Q8_0 文件,把 Q8_0 文件的元数据逐字节复制过来(tokenizer、chat template、设置),然后把每个权重矩阵写成 Q4_0。每个 drafter 覆盖 23 个张量,包括 token embedding,它占了被重写值的 86–87%:

python3 gguf_exact.py <google-snapshot-dir> gemma-4-E2B-it-qat-assistant-q8_0.gguf gemma-4-E2B-it-qat-assistant-q4_0-exact.gguf
python3 gguf_exact.py <google-snapshot-dir> gemma-4-E2B-it-qat-assistant-q8_0.gguf gemma-4-E2B-it-qat-assistant-bf16.gguf --bf16

--bf16 把 Google 的权重原样写进同样的布局,这样三种格式就能在完全相同的元数据下对比。

E2B drafter Q8_0(转换器) 精确 Q4_0(重打包) bf16(Google 权重)
文件大小 97.8 MB 59.2 MB 170.2 MB
每权重位数 8.5 4.5 16
相对 Google 权重的误差 0.00494 0.00138 0
按位完全一致的值(fp32 读回) 11.7% 29.4% 100%
按位完全一致的值(舍入到 bf16) — 每张量 96.5–97.0% 100%

重新打包后的文件体积是 Q8_0 的 60%,误差却低了 3.6 倍。把它转回 bf16(也就是 Google 存储时的格式),97% 的数值和 Google 的逐位一致。E4B 的 drafter 也是同样的结论:59.7 MB,误差 0.00138,每个张量的匹配率在 96.4% 到 97.1% 之间。


提示:转换器会丢掉 masked embedding

llama.cpp 的转换器不会保留 checkpoint 里的 masked_embedding 张量,重新打包时也沿用了同样的张量列表。这里对比的三个 drafter 文件都没有这个张量,所以比较是公平的。这些文件可以直接配合 --spec-type draft-mtp 使用。


两个重新打包的文件都在 Hugging Face 上:

它们和主模型的 exact Q4_0 GGUF 配套使用,构建方式相同:xbill9/gemma-4-E2B-it-qat-q4_0-exact-gguf 和 xbill9/gemma-4-E4B-it-qat-q4_0-exact-gguf。

每台机器的 make draft 会从固定 revision 下载对应的 drafter,并用 tpu.env 里的哈希校验:

curl -fL -o $(SPEC_DRAFT_MODEL).part https://huggingface.co/$(SPEC_DRAFT_HF)/resolve/$(SPEC_DRAFT_HF_REVISION)/$(notdir $(SPEC_DRAFT_MODEL))
echo "$(SPEC_DRAFT_SHA256)  $(SPEC_DRAFT_MODEL)" | sha256sum -c -

两块卡加载的是同样的两个文件,哈希都校验过。


重新打包的版本在两块卡上的表现

每种格式都在两块卡上跑过,每次 draft 2 个 token,六个 prompt,greedy 解码。下面是相对不用 drafter 的加速比:

2 drafted tokens 3050 E2B 1650 Ti E2B 3050 E4B 1650 Ti E4B
Q8_0 1.39x 1.09x 1.72x 1.40x
exact Q4_0 1.40x 1.11x 1.72x 1.42x
bf16 1.23x 0.96x 1.59x 1.30x

加载各自 drafter 后的内存占用,-c 8192:

MiB 无 drafter Q8_0 exact Q4_0 bf16
3050 E2B 2,795 2,915 2,875 2,983
3050 E4B 4,415 4,537 4,499 4,607
1650 Ti E2B 1,485 1,595 1,557 1,663
1650 Ti E4B 2,853 2,965 2,927 3,035
  • 重新打包的版本在两块卡上速度和 Q8_0 持平,内存少用 38–40 MiB。
  • bf16 在所有场景下都是最慢的。 在 1650 Ti 上,E2B 用 bf16 甚至比不用 drafter 还慢。
  • 三种格式的 draft token 接受率完全一样,两块卡上的差距都在 0.012 以内。drafter 的精度只影响每个 draft 步骤的开销,不影响哪些 token 会被保留。
  • greedy 输出取决于 draft 行为,和 drafter 的格式无关。 在相同 draft 长度下,Q8_0、重新打包版和 bf16 给出同样的 18 条 greedy 回复,3050 上两个模型都是如此,1650 Ti 上的 E2B 也是(1650 Ti 的 E4B 输出在 f95b0d9 上无法复现,所以那一项没法比)。

3050 上的回答质量,GSM8K(1,319 题)和 BFCL simple(400 次函数调用),每次并发 8 个请求:

无 drafter Q8_0 exact Q4_0
E2B GSM8K 0.8984 0.8992 0.9022
E2B BFCL 0.9250 0.9225 0.9225
E4B GSM8K 0.9340 0.9310 0.9325
E4B BFCL 0.9100 0.9150 0.9150

所有带 drafter 的分数都和当天无 drafter 的运行持平,重新打包版和 Q8_0 也持平:E2B GSM8K 高出 0.0030(95% 区间 −0.0061 到 +0.0129),E2B 的 BFCL 答案完全一致。

Google 还发布了非 QAT 的 drafter:google/gemma-4-E2B-it-assistant 和 google/gemma-4-E4B-it-assistant。它们的权重不在任何 4-bit 网格上,所以被转成了 Q8_0。对比 QAT 版本,它们在 E2B 上保留的 draft token 更少,两个模型在每种 draft 长度下都持平或落后:3050 上 E2B 是 1.34x,重新打包版是 1.40x;1650 Ti 上是 1.16x 对 1.18x。

1650 Ti 的格式对比用的是 llama.cpp f95b0d9,也就是这台机器实际服务的构建版本;其余所有对比在两块卡上都用 77c4540ce。


重新打包版值得用吗?

值得,作为最终发布的 drafter 文件。对比用户本来会用的另外两个文件:

exact Q4_0 重新打包版对比 Q8_0(转换器产出) bf16(Google 原始权重)
速度 两块卡持平 每块卡每个模型都更快
draft token 接受率 持平,差距在 0.012 内 持平,差距在 0.012 内
GSM8K 和 BFCL 持平 未评分
文件大小 60% 35%
显存占用 少 38–40 MiB 少 106–108 MiB
与 Google 权重的匹配度 误差低 3.6 倍 bf16 下 97% 逐位一致
  • 🟢 对比 bf16,它是明确的选择。 所有场景都更快,而且在 1650 Ti 上 bf16 会让 E2B 比不用 drafter 还慢。
  • 🟢 对比 Q8_0,它是免费的提升:更小、更接近训练权重,速度和回答质量都持平。
  • ⚠️ 相比 Q8_0 省下的内存不到任一块卡的 1%。 只有 4 GB 卡跑到接近极限时才有意义。
  • ⚠️ 速度之所以和 Q8_0 持平,是因为 drafter 的精度不改变保留的 token。每种格式的接受率都一样,所以更精确的 drafter 猜得和量化版一样准,速度优势只来自体积。

drafter 在机器正常启动命令的基础上加四个参数:

llama-server -m gemma-4-E4B-it-q4_0-exact.gguf --host 127.0.0.1 --port 8080 \
  -ngl 99 -c 8192 -ctk f16 -ctv f16 -fa 1 --reasoning off --metrics \
  -md gemma-4-E4B-it-qat-assistant-q4_0-exact.gguf --spec-type draft-mtp -ngld all --spec-draft-n-max 2

--spec-draft-n-max 是每步 draft 的 token 数。每台机器的 make serve 从 tpu.env 生成这行命令,SPEC_DRAFT_MODEL 留空就不加载 drafter。drafter 在两块卡上占 72–84 MiB 内存。


speed.py 跑六个 prompt(代码、建议、故事、解释、列表、摘要),每个 256 token,三次 greedy 重复加一次 temperature 0.8 的采样,prompt cache 关闭。每个 draft 长度单独起一个 server,每次之前卡会冷却,第一个 draft 设置在最后再跑一遍检查漂移。同一个脚本、同一组 prompt、同一个 llama.cpp 构建在两块卡上都跑过。

python3 speed.py <rig> <out>

结果

greedy 解码,六个 prompt 的几何平均。"Kept" 是主模型接受的 draft token 比例。

两张卡上,草稿被接受的比例在各个长度下几乎一致。差别在于每多草拟一个 token 要付多少代价:3050 上 E4B 从 1 个 token 到 2 个 token 还在涨,到 3 个才勉强持平;1650 Ti 上每多一个 token 都更慢。原因大概率是,在老卡上每个草拟步骤占主模型一步的比例更大(这是推断,没有实测)。

重复跑的那组设置,3050 落在首次运行的 0.3% 以内,1650 Ti 在 2.0% 以内。


提示:草稿长度要按卡来定

最佳草稿长度既是模型的属性,也是卡的属性。3050 两台机器上两个模型都跑 2 个 token。1650 Ti 上这个构建里两个模型都是 1 个 token 领先,E2B 跑 3 个 token 跟不挂 drafter 一样快。从快卡上抄来的设置,搬到慢卡上会丢掉大部分收益。


提示:Tesla T4 上是同样的规律

Google Cloud 上一块 Tesla T4(Turing,15,360 MiB)换了引擎,答案一样。vLLM 0.29,E2B 和 E4B 的 W4A16 构建,加上 Google 的 bf16 drafter,同样六个 prompt 一次一个请求,3 个草拟 token 时 E2B 加速 1.29x、E4B 加速 1.57x,4 个 token 时是 1.31x 和 1.62x。那张卡上收益一直涨到 4 个草拟 token,1650 Ti 上只到 1 个,又一个草稿长度属于卡本身的例子。vLLM 加载不了 GGUF 重打包文件,引擎、目标文件和 drafter 格式全都不一样,所以 T4 的数字没进这里的表(T4 drafter 运行记录)。


第 5 步:带着长文本测

longctx.py 把 NVIDIA 驱动 README 的前 0 到 120,000 个 token 放在一段故事、一个代码请求或一段文本摘要前面,只测生成速度。两张卡用同一份文档,按哈希校验。1650 Ti 上的 E4B 到 32,768 个 token 就停了,这是 4 GiB 里加载 drafter 后能放下的上限。

1 个草拟 token 时的加速,三个请求取平均:

前置 token 数 3050 E2B 1650 Ti E2B 3050 E4B 1650 Ti E4B
0 1.41x 1.27x 1.61x 1.52x
8,192 1.38x 1.18x 1.55x 1.53x
32,768 1.33x 1.03x 1.48x 1.45x
65,536 1.27x 0.97x 1.46x
120,000 1.23x 0.89x 1.41x

3050 上两个模型在任何深度 drafter 都划算;跑 2 个 token 更好,E4B 无前置时 1.86x,120k 时 1.55x。1650 Ti 上 E2B 的收益到 32k token 就没了,再往上变成亏损。两张卡上草稿被接受的比例随文本变长都保持稳定,所以掉速来自长上下文下每个校验步骤变贵。


第 6 步:同时发多个请求测

sweep.py 一次发 1 到 16 个请求,每个 prompt 都不同,两种形状:512 进 128 出,和 128 进 512 出。两张卡都用一个共享 KV cache(-kvu)跑;3050 用 -c 65536 --parallel 64,1650 Ti 用 -c 16384 --parallel 16。

挂 drafter 的总输出除以不挂的:

并发请求数 1 2 4 8 16
3050 E2B, 512 / 128 1.56 1.17 1.28 1.28 1.05
1650 Ti E2B, 512 / 128 1.15 1.04 1.03 0.95 0.97
3050 E2B, 128 / 512 1.79 1.28 1.43 1.54 1.18
1650 Ti E2B, 128 / 512 1.33 1.12 1.09 0.91 0.96
3050 E4B, 512 / 128 1.86 1.38 1.32 1.38 1.14
1650 Ti E4B, 512 / 128 1.28 1.07 0.90 0.93 0.92
3050 E4B, 128 / 512 2.05 1.44 1.41 1.60 1.26
1650 Ti E4B, 128 / 512 1.65 1.18 0.79 0.83 0.83

sweep.py 的 prompt 重复同一段话,drafter 猜得特别好,所以这些比值比日常聊天高。混合聊天 prompt 上,3050 实测 2 到 8 个请求时 E4B 1.08–1.24x、E2B 0.92–1.17x,32 到 64 个请求两个模型都亏。看表就看趋势:3050 在这里到 16 个请求还有收益,1650 Ti 从 4 个或 8 个开始就亏。

长 prompt 下 drafter 更划算。longmixed.py 给六个任务(摘要、bash 脚本、测验等)各前置一段不同的 1,000–3,500 token 驱动 README,256 token 输出,贪心,3050 上 1 到 32 个请求(-c 131072 --parallel 32),正反两个顺序各跑一遍。上下文里有文档时 drafter 保留 0.56–0.62 的 token,短聊天上是 0.43–0.50。挂 drafter 的总输出除以不挂的,两遍都算:

并发请求数 1 2–16 32
3050 E2B, 每槽 cache 1.34 1.02–1.14 1.02–1.04
3050 E2B, 共享 cache 1.26 1.04–1.13 0.96–0.97
3050 E4B, 每槽 cache 1.54 1.08–1.16 1.03–1.05
3050 E4B, 共享 cache 1.61 1.08–1.18 0.99

到 16 个请求为止 drafter 都划算,32 个时打平。这组只测了 3050。


提示:共享 KV cache 在两张卡上都有代价

llama-server 默认给每个槽独立的 KV cache。在 llama.cpp 77c4540ce 上,这种布局在多请求并发时会让生成速度随机掉一半,两张卡都是:1650 Ti 上 2 个请求时每槽重复跑的结果散布在 28–36%。-kvu 让所有槽共用一个 cache,散布降到 1% 左右。它也会随总文本量增加而损失输出。sweep.py 16 个请求时,共享 cache 在 3050 上只有每槽布局输出的 0.86–0.94x,1650 Ti 上是 0.59–0.81x。两种布局下 drafter 的比值几乎一样。3050 上 1,000–3,500 token 的 prompt 时,每槽 cache 是更快的布局,也没出现随机变慢:232 组配对批次里赢了 204 组,共享 cache 在 16 个请求时只有它输出的 0.68–0.82x,32 个请求时 0.49–0.66x。四台机器都是一次一个请求,那种场景下两种布局没区别。


第 7 步:检查答案

GSM8K(1,319 题)和 BFCL simple(400 个函数调用),一次一个请求,挂和不挂 drafter:

3050 不挂 3050 挂 1650 Ti 不挂 1650 Ti 挂
E2B GSM8K 0.9014 0.8969 0.8939 0.8969
E2B BFCL 0.9225 0.9250 0.9250 0.9225
E4B GSM8K 0.9310 0.9295 0.9363 0.9310
E4B BFCL 0.9125 0.9125 0.9125 0.9125

每个差异的 95% 区间都包含零。drafter 让 GSM8K 在 3050 上快 1.76x(E2B)和 2.23x(E4B),1650 Ti 上快 1.34x 和 1.78x。3050 跑 -c 131072,两个模型都 2 个草拟 token;1650 Ti 跑 -c 8192,各自用各自的设置,E2B 1 个 token、E4B 2 个。


提示:贪心文本会因 drafter 而变

温度 0 下,挂了 drafter 的回复可能在某个几乎打平的词上和没挂的不同,因为一次校验多个 token 走的是不同的 GPU kernel。两张卡上 E2B 跑 2 个 token 时,每个 prompt 的草稿文本都和没挂的不同。E4B 跑 1 个 token 时,两张卡上 18 条短回复全部一致。3050 上 E4B 跑 2 个 token 时短 prompt 也一致,prompt 到 2,000 token 左右开始出现差异。prompt cache 本身也会改变贪心文本。需要文本完全一致的测试,必须不挂 drafter 并发送 cache_prompt: false。上面的分数说明,改掉的那些词没有改变答案。


对比一览

🥇 RTX 3050 OEM 🥈 GTX 1650 Ti Max-Q
E2B 单请求最佳加速 1.40x,2 token 1.18x,1 token
E4B 单请求最佳加速 1.72x,2 token 1.49x,1 token
挂 drafter 后 E4B 最快 100.0 tok/s 56.5 tok/s
1 token 时草稿接受比例 0.61–0.62 0.60–0.64
前置 120k token 时 E2B 加速 1.23x 0.89x
8 个并发请求时的加速(sweep.py) 1.28–1.60x 0.83–0.95x
答案质量 不变 不变
挂 drafter 后 GSM8K 加速 1.76x / 2.23x 1.34x / 1.78x

那到底选哪个

草稿模型是给小模型用的。单用户场景下,两张卡都把草稿打开就行。不损失任何答案质量,只多占 72–84 MiB 内存,E4B 在两张卡上收益最大:3050 上 1.72 倍,1650 Ti 上 1.49 倍。草稿长度按卡分别设,3050 用 2 个 token,1650 Ti 用 1 个。

1650 Ti 上,对话超过 32k token 左右、或者同时服务多个用户时,E2B 的草稿就关掉。3050 上草稿在长文本下依然划算,长 prompt 场景下并发到 16 个请求都还赚,到 32 个才打平。


接下来测什么

Google 给更大的 Gemma 4 模型也发布了同样的两个草稿模型,QAT 和非 QAT 都有:12B、26B-A4B 和 31B。这几个都还没测过,它们和草稿一起能不能塞进这两张卡也没验证。下一步就是测大模型配草稿,再加上 NVIDIA L4(Ada 架构,计算能力 8.9,24 GB),比 3050 新一代。


小结

这篇文章想搞清楚一件事:在 llama.cpp 下,Google 的 Gemma 4 草稿模型能不能加速小 NVIDIA 卡上的小模型。结论是能。关键在于两张卡跑同一个 llama.cpp 构建、同一套脚本、prompt 和草稿文件,每个 server 只改一个设置,同时检查答案质量。结果如下:

  • 单请求时草稿让 E4B 在 RTX 3050 上快 1.72 倍、GTX 1650 Ti 上快 1.49 倍,E2B 分别是 1.40 倍和 1.18 倍
  • 两张卡接受的草稿 token 比例几乎一样,各个草稿长度下差距都在 0.03 以内
  • 两张卡开草稿后 GSM8K 和 BFCL 分数都没变
  • 草稿的精确 Q4_0 重打包文件只有 Q8_0 的 60% 大小,速度在两张卡上都和它持平;Google 的 bf16 权重在两张卡上都最慢
  • 最佳草稿长度不一样:3050 是 2 个 token,1650 Ti 是 1 个
  • 长文本场景下,E2B 在 1650 Ti 上的收益到 32k token 就没了;3050 上 120k 时还有 1.23 倍
  • 3050 上 1,000–3,500 token 的 prompt,草稿在并发 16 个请求以内都划算
  • 1650 Ti 上并发 4–8 个请求以上,草稿反而拖累输出

范围说明:只测了 Gemma 4 E2B 和 E4B,只测 MTP 投机解码,两张卡两台机器,两边都是 llama.cpp 77c4540ce,E2B 和 E4B 用精确 Q4_0 GGUF 配 Google 的 QAT 草稿(精确 Q4_0,格式对比另加 Q8_0 和 bf16),速度用六个短 prompt,长文本用一份 120k token 文档,sweep.py 测 1–16 个请求共享 KV cache,3050 上六个长 prompt 任务测 1–32 个请求、两种 cache 布局,GSM8K 和 BFCL 单请求跑,每个设置跑一次加一次复测。T4 那条来自单独的 vLLM 运行。

用 MCP 做本地加速器对比这套思路,是通过一步步增量验证出来的。


参考

常见问题(FAQ)

投机解码在小显卡上到底能加速多少?

实测RTX 3050 OEM和GTX 1650 Ti上,单请求串行时E4B加速1.72倍和1.49倍,E2B加速1.40倍和1.18倍,且回答质量不变。但并发请求增多后收益缩水,1650 Ti上E4B从4并发、E2B从8并发开始变为负收益。

为什么小模型E2B的加速比E4B少?

E2B本身每token就快,每一步draft的开销占它省下来的时间比例更大,所以收益更少。两块卡保留的draft token比例相差在0.03以内,卡间差距主要来自显卡本身性能。

Q4_0重打包的drafter和Q8_0比有什么优势?

Q4_0重打包的drafter在速度和回答上与Q8_0持平,体积只有后者的60%,而且在每块卡、每个模型上都比Google的bf16权重快。它精确还原了训练时的4-bit网格,仅11.7%的权重在Q8_0下能按位还原。

阿凯广州
本文由 阿凯 审核,最后更新于 2026年10月18日
联系编辑 →
← 返回文章列表
分享到:微博

版权与免责声明:本文仅用于信息分享与交流,不构成任何形式的法律、投资、医疗或其他专业建议,也不构成对任何结果的承诺或保证。

文中提及的商标、品牌、Logo、产品名称及相关图片/素材,其权利归各自合法权利人所有。本站内容可能基于公开资料整理,亦可能使用 AI 辅助生成或润色;我们尽力确保准确与合规,但不保证完整性、时效性与适用性,请读者自行甄别并以官方信息为准。

若本文内容或素材涉嫌侵权、隐私不当或存在错误,请相关权利人/当事人联系本站,我们将及时核实并采取删除、修正或下架等处理措施。也请勿在评论或联系信息中提交身份证号、手机号、住址等个人敏感信息。