小显卡跑 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倍的加速,且答案质量不变,但在多请求或长对话场景下加速效果会减弱甚至变为负优化。
核心洞察
这篇文章最有意思的地方在于,它用两块很普通的消费级显卡(甚至有一块是笔记本)来验证投机解码一种加速大语言模型推理的技术,使用一个小型草稿模型预测多个token,然后由主模型一次性验证,从而减少前向传播次数。到底值不值得。结论是:单请求场景下确实有提升,但并发一上来,笔记本那块卡直接变成负收益。如果你手上是类似档位的硬件,这些数字比任何理论分析都实在。
本文是一份分步指南,讲怎么用 llama.cpp一个高效的C++库,专门用于在本地硬件上运行大型语言模型推理,支持多种硬件加速和优化技术。 的投机解码来部署 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_0llama.cpp中的一种4位量化格式,每32个值一组共享一个fp16缩放因子,每个值用4位整数表示。 重打包是怎么做的。这一篇全是实测数据。
代码仓库在这里:https://github.com/xbill9/gemma4-dev
Drafter 就是投机解码里的那个"猜词的":它先猜接下来几个 token,主模型一次性全部验证。
小模型上 drafter 有用吗?有用。 Gemma 4 E2B 和 E4B 本身已经够小了,它们的 drafter 是一个 4 层、59 MB 的模型。单请求串行服务时,它让两个模型在两块卡上都变快了,而且 GSM8KGrade School Math 8K benchmark for evaluating mathematical reasoning capabilities of language models. 和 BFCLBerkeley 函数调用基准(Berkeley Function Calling Leaderboard),用于评估模型调用工具/函数的能力。 分数保持不变:
| 单请求串行 | 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 权重快。
核心结论
在单请求串行场景下,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 分数保持不变。
并发请求会侵蚀收益:在 GTX 1650 Ti 上,E4B 从 4 个并发、E2B 从 8 个并发开始变为负收益;E2B 的收益在约 32k token 对话长度时消失。
精确 Q4_0 重打包的 drafter 文件仅 59.2 MB(为 Q8_0 的 60%、bf16 的 35%),相对 Google 原始权重的误差为 0.00138,比转换器产出的 Q8_0 低 3.6 倍,转回 bf16 后约 97% 数值与 Google 权重逐位一致。
重打包版在两块卡上速度与 Q8_0 持平、显存少用 38–40 MiB,且在所有场景下都快于 Google 的 bf16 权重——后者在 1650 Ti 上运行 E2B 时甚至比不用 drafter 更慢。
最佳草稿 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 GGUFA model file format used for quantized large language models, often associated with efficient local deployment.,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 上:
- xbill9/gemma-4-E2B-it-qat-assistant-q4_0-exact-gguf,文件
gemma-4-E2B-it-qat-assistant-q4_0-exact.gguf,SHA-256060f5a0158f8120d… - xbill9/gemma-4-E4B-it-qat-assistant-q4_0-exact-gguf,文件
gemma-4-E4B-it-qat-assistant-q4_0-exact.gguf,SHA-256dc965cb963e56b86…
它们和主模型的 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 做本地加速器对比这套思路,是通过一步步增量验证出来的。
参考
- 第一部分,Gemma 4 的 MTP 草稿模型详解:https://dev.to/gde/gemma-4s-mtp-drafter-explained-speculative-decoding-and-a-4-bit-drafter-at-60-of-the-size-1oc0
- 仓库:https://github.com/xbill9/gemma4-dev
- RTX 3050 配置:https://github.com/xbill9/gemma4-dev/tree/main/local-llamacpp-3050oem-2b-q4_0 和 https://github.com/xbill9/gemma4-dev/tree/main/local-llamacpp-3050oem-4b-q4_0
- GTX 1650 Ti 配置:https://github.com/xbill9/gemma4-dev/tree/main/local-llamacpp-1650ti-2b-q4_0 和 https://github.com/xbill9/gemma4-dev/tree/main/local-llamacpp-1650ti-4b-q4_0
- 草稿重打包,3050:https://github.com/xbill9/gemma4-dev/blob/main/local-llamacpp-3050oem-2b-q4_0/benchmarks/runs/2026-10-14-drafter-q4exact-3050oem/REPORT.md
- 草稿格式,1650 Ti:https://github.com/xbill9/gemma4-dev/blob/main/local-llamacpp-1650ti-2b-q4_0/benchmarks/runs/2026-10-14-drafter-formats-1650ti/REPORT.md
- 3050 草稿长度和长文本:https://github.com/xbill9/gemma4-dev/blob/main/local-llamacpp-3050oem-2b-q4_0/benchmarks/runs/2026-10-14-drafter-gaps-3050oem/REPORT.md
- 1650 Ti 草稿长度和长文本:https://github.com/xbill9/gemma4-dev/blob/main/local-llamacpp-1650ti-2b-q4_0/benchmarks/runs/2026-10-14-drafter-gaps-1650ti/REPORT.md
- 3050 多请求并发:https://github.com/xbill9/gemma4-dev/blob/main/local-llamacpp-3050oem-2b-q4_0/benchmarks/runs/2026-10-15-concurrency-drafter-kvu-3050oem/REPORT.md
- 3050 多请求并发,长 prompt:https://github.com/xbill9/gemma4-dev/blob/main/local-llamacpp-3050oem-2b-q4_0/benchmarks/runs/2026-10-15-concurrency-longmixed-3050oem/REPORT.md
- 1650 Ti 多请求并发和答案:https://github.com/xbill9/gemma4-dev/blob/main/local-llamacpp-1650ti-2b-q4_0/benchmarks/runs/2026-10-15-drafter-load-1650ti/REPORT.md
- Tesla T4 上 vLLM 的草稿:https://github.com/xbill9/gemma4-dev/blob/main/gpu-vllm-t4-2b-w4a16/benchmarks/runs/2026-10-15-drafter-sweep-t4/REPORT.md
- 3050 答案:https://github.com/xbill9/gemma4-dev/blob/main/local-llamacpp-3050oem-2b-q4_0/benchmarks/runs/2026-10-14-accuracy-fullctx-3050oem/REPORT.md
- 草稿模型:https://huggingface.co/xbill9/gemma-4-E2B-it-qat-assistant-q4_0-exact-gguf 和 https://huggingface.co/xbill9/gemma-4-E4B-it-qat-assistant-q4_0-exact-gguf
- llama.cpp:https://github.com/ggml-org/llama.cpp
常见问题(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下能按位还原。
版权与免责声明:本文仅用于信息分享与交流,不构成任何形式的法律、投资、医疗或其他专业建议,也不构成对任何结果的承诺或保证。
文中提及的商标、品牌、Logo、产品名称及相关图片/素材,其权利归各自合法权利人所有。本站内容可能基于公开资料整理,亦可能使用 AI 辅助生成或润色;我们尽力确保准确与合规,但不保证完整性、时效性与适用性,请读者自行甄别并以官方信息为准。
若本文内容或素材涉嫌侵权、隐私不当或存在错误,请相关权利人/当事人联系本站,我们将及时核实并采取删除、修正或下架等处理措施。也请勿在评论或联系信息中提交身份证号、手机号、住址等个人敏感信息。



