DGX Spark · part 10
[Benchmark] 從 19 到 50 tok/s:我們搶先做了全球首個 Gemma 4 E4B NVFP4 量化
❯ cat --toc
TL;DR
Gemma 4 E4B NVFP4A16 在 DGX Spark 上跑 49.9 tok/s — 比 BF16 快 2.6 倍,GPU 只佔 9.8 GB。這是 HuggingFace 上第一個 E4B NVFP4 checkpoint:coolthor/Gemma-4-E4B-it-NVFP4A16。
白話版:把 Gemma 4 E4B 壓縮到能即時對話
Gemma 4 是 Google 2026 年 4 月發布的開源 AI 模型,有四個尺寸。E4B 是其中桌機等級的版本 — 手機跑不動,但筆電和工作站可以。它能讀文字、看圖片、聽音訊,一次能處理大約一本 300 頁書的內容。
問題是:原始版本在我的 DGX Spark 上只跑 19 tok/s,打字聊天會覺得卡。我用 NVIDIA 的 4-bit 壓縮格式(NVFP4)把它壓小,速度直接拉到 50 tok/s — 足夠當本機 AI agent 用了。
這個壓縮版是全球第一個上傳 HuggingFace 的,過程踩了不少坑。這篇完整記錄。
前言
量化這種事,最後成功的那次往往是你差點放棄的那次。
三次失敗、兩種依賴打架、靠一個前一天才 merge 的 PR 才過關 — 這就是 19 tok/s 變成 50 的真實路徑。
上一篇證明了 31B dense 模型在 GB10 上被頻寬卡死,只有 7 tok/s。既然大模型跑不動,那小模型呢?E4B 的架構比較特別 — 不是傳統的 dense,也不是 MoE — 值得測一下。
E4B 長什麼樣:不是 Dense,不是 MoE,是 PLE
大部分人看到 E4B 就說「喔,8B 模型」然後跳過。但它跟一般的 8B 差很多。
E4B 用了一種叫 PLE(Per-Layer Embedding) 的架構:42 層 decoder,每一層都帶著自己的 embedding table,把 262K 個 vocabulary 對應到 256 維的向量。這些 table 加起來很大 — 262,144 × 256 × 2 bytes × 42 層 = BF16 下足足 5.4 GB — 但它們的用途只是查表,不跑矩陣乘法。
每個 token 真正經過 Linear 層計算的只有大約 4B 參數。剩下的全是查表。
模型總量:15 GB (BF16)
├── PLE embeddings: 5.4 GB(純查表)
├── Decoder weights: 4.0 GB(真正在算的)
├── Word embedding: 1.3 GB(262K × 2560)
├── Vision encoder: 0.5 GB
├── Audio encoder: 0.3 GB
└── 其他: 3.5 GB
這對量化有什麼意義?壓縮 PLE table 能省硬碟空間,但對速度幫助不大 — 因為瓶頸本來就不在那裡。真正要壓的是 Linear 層。
BF16 基線:19.2 tok/s
先跑原始版本當對照組。E4B BF16 + vLLM:
docker run -d --name gemma4-e4b \
--gpus all --ipc host --shm-size 32gb \
-p 8003:8000 \
-v ~/models/gemma4-e4b-bf16:/models/gemma4-e4b \
vllm/vllm-openai:gemma4-cu130 \
--model /models/gemma4-e4b \
--served-model-name gemma-4-e4b \
--kv-cache-dtype fp8 \
--max-model-len 16384 \
--gpu-memory-utilization 0.75
跑 3 輪,每輪生成 500 tokens:
| 輪次 | tok/s |
|---|---|
| 1 | 19.2 |
| 2 | 19.2 |
| 3 | 19.1 |
穩得不行,但就是慢。粗估一下就知道為什麼:每個 token 實際要讀的資料量大約 ~10 GB(PLE 查表 + decoder weights),10 GB ÷ 273 GB/s ≈ 37 ms/token ≈ 理論值 27 tok/s。實際只跑到 19,差距來自 PLE 的查表模式沒有連續讀取那麼高效 — 到處跳著讀,頻寬利用率打折。
FP8 Online:一個 flag 就快了一倍
在自己動手量化之前,先試最省事的路。vLLM 支援 online FP8 — 加一個參數,BF16 模型在載入的時候自動壓成 FP8:
docker run -d --name gemma4-e4b-fp8 \
--gpus all --ipc host --shm-size 32gb \
-p 8003:8000 \
-v ~/models/gemma4-e4b-bf16:/models/gemma4-e4b \
vllm/vllm-openai:gemma4-cu130 \
--model /models/gemma4-e4b \
--served-model-name gemma-4-e4b \
--quantization fp8 \
--kv-cache-dtype fp8 \
--max-model-len 16384 \
--gpu-memory-utilization 0.75
就多了一行 --quantization fp8。不用另外下載 checkpoint,不用準備 calibration data。
| 輪次 | tok/s |
|---|---|
| 1 | 36.0 |
| 2 | 35.9 |
| 3 | 35.9 |
記憶體從 15 GB 降到 11.4 GB。速度直接翻倍,什麼都不用額外做。如果你只想花 30 秒改善速度,這就是答案。
但能不能再快?
NVFP4:踩進版本地獄
NVFP4A16(4-bit weights, 16-bit activations)理論上能在 FP8 的基礎上再壓一倍。要用的工具是 llm-compressor,vLLM 官方的量化工具。
第一次嘗試:直接 pip install
pip install llmcompressor
from transformers.modeling_utils import TORCH_INIT_FUNCTIONS
ImportError: cannot import name 'TORCH_INIT_FUNCTIONS'
原因:Gemma 4 要 transformers>=5.5,但 llm-compressor 的 PyPI 版本釘死 transformers<=4.57.6。兩個裝在一起就打架。硬升 transformers 會把 llm-compressor 弄壞。
第二次嘗試:跳過依賴檢查
pip install llmcompressor
pip install --no-deps 'transformers>=5.5'
一樣炸。TORCH_INIT_FUNCTIONS 這個東西在 transformers 5.x 被拿掉了,但 PyPI 版的 llm-compressor 還在用它。
第三次嘗試:裝 git main
PR #2561 在 4 月 6 日 merge — 剛好是我測試的前一天。這個 PR 加了 Gemma 4 E4B 的官方 NVFP4A16 範例,順便修了 transformers 5.x 的相容性問題。
pip install 'git+https://github.com/vllm-project/llm-compressor.git@main'
pip install --force-reinstall --no-deps 'transformers>=5.5' 'huggingface_hub>=0.30'
pip install torchvision --index-url https://download.pytorch.org/whl/cu130
裝的順序不能亂。先讓 llm-compressor 把所有依賴拉齊,再用 --force-reinstall --no-deps 把 transformers 和 huggingface_hub 換成新版。另外,Gemma 4 的 multimodal processor 會用到 torchvision — 不裝的話跑到一半才噴 Gemma4VideoProcessor requires the Torchvision library,非常不直覺。
終於過了。
量化本身:2 分鐘搞定
工具裝好之後,量化反而是最無聊的一步:
from transformers import AutoModelForImageTextToText, AutoProcessor
from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import QuantizationModifier
model = AutoModelForImageTextToText.from_pretrained(
"/home/coolthor/models/gemma4-e4b-bf16", dtype="auto"
)
processor = AutoProcessor.from_pretrained(
"/home/coolthor/models/gemma4-e4b-bf16"
)
recipe = QuantizationModifier(
targets="Linear",
scheme="NVFP4A16",
ignore=[
"lm_head",
"re:.*vision_tower.*",
"re:.*audio_tower.*",
"re:.*embed_vision.*",
"re:.*embed_audio.*",
],
)
oneshot(model=model, recipe=recipe)
model.save_pretrained("gemma4-e4b-nvfp4", save_compressed=True)
processor.save_pretrained("gemma4-e4b-nvfp4")
379 個 Linear 層完成量化。試跑一下確認沒壞:"Hello! It's nice to meet you. What is your name?" — 回答連貫,沒有亂碼。
NVFP4A16 是 weight-only 量化,不需要 calibration dataset。ignore 裡面列的是 vision/audio encoder 和 embeddings — 這些要嘛太小,要嘛是查表用的 PLE,壓了也沒幫助。
跑分:49.9 tok/s
docker run -d --name gemma4-e4b-nvfp4 \
--gpus all --ipc host --shm-size 32gb \
-p 8003:8000 \
-v ~/models/gemma4-e4b-nvfp4:/models/gemma4-e4b \
vllm/vllm-openai:gemma4-cu130 \
--model /models/gemma4-e4b \
--served-model-name gemma-4-e4b \
--quantization compressed-tensors \
--kv-cache-dtype fp8 \
--max-model-len 16384 \
--gpu-memory-utilization 0.75
記憶體佔用:9.8 GB(BF16 原本 15 GB,FP8 是 11.4 GB)。
| 輪次 | Tokens | 時間 | tok/s |
|---|---|---|---|
| 1 | 500 | 10.01s | 49.9 |
| 2 | 500 | 10.01s | 49.9 |
| 3 | 500 | 10.03s | 49.8 |
三種格式一次看完
| 格式 | GPU 佔用 | tok/s | 對比 BF16 |
|---|---|---|---|
| BF16 | 15.0 GB | 19.2 | 1.0x |
| FP8 online | 11.4 GB | 36.0 | 1.9x |
| NVFP4A16 | 9.8 GB | 49.9 | 2.6x |
品質有沒有掉?
| 測試 | 結果 |
|---|---|
| 長輸出(1000 tokens) | 49.8 tok/s,沒退化 |
| 同時 3 個 request | 每個 52.7 tok/s,合計 158 tok/s |
| 繁體中文 | 流暢解釋 BPS 策略 |
| 數學 | Put spread 最大獲利/虧損算對 |
| 寫程式 | Black-Scholes 完整且能跑 |
| JSON 輸出 | 格式正確 |
| 數到 50 | 沒重複、沒亂碼 |
這個速度能拿來幹嘛:雙模型 Agent 架構
測 E4B 不只是為了跑分好看。我在驗證一個 agent 架構的想法:
E4B NVFP4 (50 tok/s, 本機, 免費)
→ 負責 95% 的日常任務
→ 讀檔案、跑指令、整理 output
26B-A4B NVFP4 (52 tok/s, 本機, 免費)
→ 負責需要深度推理的 5%
→ 只在 E4B 搞不定的時候出場
兩個模型可以同時塞進一台 DGX Spark。E4B 佔 9.8 GB + 26B 佔 16.5 GB = 26.3 GB,剩下 ~100 GB 給 KV cache。如果兩個同時跑會搶頻寬(26B 降到 ~31 tok/s,E4B ~18 tok/s),但如果設計成輪流跑(sequential executor),每個都能拿到完整頻寬。
邏輯很簡單:95% 的 agent 操作是 routine 的(讀個檔案、跑個指令、parse 一下 output),用 50 tok/s 的本機模型跑就好,不用為這些小事打 cloud API。真的需要動腦的 5% 才交給 26B — 也是本機,也不花錢。
還缺的那一塊:E4B 的 tool calling 準確度到底夠不夠格當 executor。這是下一個要測的。
帶走這些
最花時間的不是量化
量化本身 2 分鐘就跑完了。花掉一小時的是 llm-compressor 的版本地獄 — 三次嘗試、三種炸法(TORCH_INIT_FUNCTIONS 被移除、huggingface_hub 版本打架、少裝 torchvision)。最後能過完全是因為 PR #2561 剛好前一天 merge。
下次遇到類似問題可以直接用的經驗
- FP8 online 永遠先試。 vLLM 加
--quantization fp8,任何 BF16 模型都能速度翻倍,什麼都不用準備。這是投報率最高的一步。 - PLE 架構不能用一般 8B 的直覺去算。 5.4 GB 的 embedding table 壓了也不會變快,因為它本來就不是計算瓶頸。所以模型大小從 15 GB → 11.5 GB(FP8),不是你對一般 8B 模型預期的 7.5 GB。
- PyPI 追不上 git main 的時候,先讓 package 把所有依賴裝齊,再用
--force-reinstall --no-deps精準換掉衝突的那幾個。千萬不要對主 package 用--no-deps,會漏掉一堆間接依賴。
通用原則
最有效的最佳化通常是不用換 checkpoint 就能做的。--quantization fp8 對模型部署來說,就像編譯程式時開 --release — 幾乎沒有理由不開。
下載模型
Checkpoint 在 HuggingFace:coolthor/Gemma-4-E4B-it-NVFP4A16
vllm serve coolthor/Gemma-4-E4B-it-NVFP4A16 \
--quantization compressed-tensors \
--kv-cache-dtype fp8 \
--max-model-len 16384
不想下載的話,FP8 online 也很好用:
vllm serve google/gemma-4-E4B-it \
--quantization fp8 \
--kv-cache-dtype fp8 \
--max-model-len 16384
同系列:Part 7:Gemma 4 26B-A4B 52 tok/s · Part 8:vLLM vs Ollama · Part 9:31B Dense — 7 tok/s
常見問題
- Gemma 4 E4B NVFP4 在 DGX Spark 上跑多快?
- 49.9 tok/s,NVFP4A16 量化 + vLLM。比 BF16 的 19.2 tok/s 快 2.6 倍。FP8 online 量化介於中間,36.0 tok/s。
- Gemma 4 E4B 的 PLE 架構是什麼?
- Per-Layer Embedding(PLE)讓 42 層 decoder 各自有一個 262K vocabulary 的 embedding table。這些 table 很大(BF16 下 5.4 GB),但只做 lookup 不做矩陣乘法。每個 token 實際跑的 compute 只有 4B 參數,雖然總參數是 8B。
- 可以用 llm-compressor 量化 Gemma 4 E4B 嗎?
- 可以,但截至 2026 年 4 月必須從 git main 裝(不是 PyPI)。PyPI 版本鎖定 transformers<=4.57.6,但 Gemma 4 需要 transformers>=5.5。PR #2561 加了官方 E4B NVFP4A16 範例。
- 有現成的 Gemma 4 E4B NVFP4 checkpoint 嗎?
- 有。HuggingFace 上的 coolthor/Gemma-4-E4B-it-NVFP4A16 是第一個 E4B NVFP4 checkpoint。用 vLLM 搭配 --quantization compressed-tensors 即可使用。