~/blog/dgx-spark-gemma4-e4b-nvfp4-50-toks

DGX Spark · part 10

[Benchmark] 從 19 到 50 tok/s:我們搶先做了全球首個 Gemma 4 E4B NVFP4 量化

2026-04-07更新於 2026-04-146 分鐘閱讀#gemma-4#e4b#nvfp4#fp8English
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
119.2
219.2
319.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
136.0
235.9
335.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
150010.01s49.9
250010.01s49.9
350010.03s49.8

三種格式一次看完

格式GPU 佔用tok/s對比 BF16
BF1615.0 GB19.21.0x
FP8 online11.4 GB36.01.9x
NVFP4A169.8 GB49.92.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 即可使用。