~/blog/dgx-spark-eagle3-finetune-abliterated-round1

DGX Spark · part 30

Fine-tune EAGLE-3 drafter 在 abliterated Gemma 4 上 — Round 1 拉平 acceptance 曲線(+ 一個 measurement lesson)

2026-05-16更新於 2026-05-179 分鐘閱讀#gemma-4#abliteration#eagle-3#speculative-decodingEnglish
cat --toc

⚠️ 2026-05-17 endpoint 訂正:本文 throughput 數字(100.36 tok/s aggregate / 107.59 per-prompt mean / pos 0-3 acceptance 84/75/74/73%)是 bench script 用 /v1/completions(raw prompt) 量到的。重 bench 才發現 raw endpoint 上 instruct-tuned model 沒套 chat template,output 退化成 ASCII repetition,drafter 在 degenerate sequence 上 trivially 命中,inflate 了數字。真實 production chat workload(/v1/chat/completions)上,retrained drafter ~46 tok/s、pos-0 57%,跟 vanilla MTP n=1 的 51 tok/s / 70.6% 差不多甚至略低。文章機制描述(retrain 拉平 acceptance 曲線、unlock deep speculation on predictable workload)仍然成立,但「2× speedup」這個 framing 只在 raw endpoint 上有效,不代表 OpenAI chat API 用戶會看到的速度。完整 paired bench 跟原因分析會放到 Part 31。

TL;DR

好消息 — Part 28 留下的壞消息,這次處理掉了。

Part 28 觀察到:在 huihui Gemma 4 26B-A4B abliterated FP8 這顆 body 上,vanilla EAGLE-3 drafter 的 acceptance 一過 n=1 就崩。這次拿 RedHatAI 預訓的 drafter,用 50k Magpie 指令(對應 response 由 abliterated body 重新生成過)再 train 一個 epoch,單張 DGX Spark GB10 跑了大約 11 小時。

那條 acceptance 曲線整個被拉平:pos 3 從 vanilla 的 20.5% 直接跳到 72.7%(+52pp),n=4 throughput 也從 ~50 tok/s 變成 100.36 tok/s aggregate(per-prompt mean 107.59),整體大約 2.0× speedup。Part 28 講的機制(深 speculation 在 abliterated distribution 上會散開)沒錯,只是 drafter 重 train 過、跟新的 distribution 接得起來,問題就消失。

訓練中順手撞到 Speculators 一個 upstream bug:create_empty_sample() 預設配 fp32 placeholder,在 BF16 模型上每次 vLLM extraction timeout fallback 都會 crash。本地 patch 改成 torch.bfloat16,upstream PR 已經開了 — vllm-project/speculators#527

TL;DR

  • 目標:Part 28 證實 vanilla MTP draft 在 abliterated body 上 deep speculation 失效(pos 0/1/2/3 acceptance 65/43/29/20%);Part 30 試圖用 fine-tune EAGLE-3 drafter 把 drafter 拉回 abliterated body 的 distribution
  • 結果:WIN。Inference acceptance 變成 84/75/74/73%(decay 從 22 pp/step 變成幾乎沒 decay),n=4 throughput 50 → 100.36 tok/s aggregate ≈ 2.0×(per-prompt mean 107.59)。Drafter shipped 到 HF:coolthor/Huihui-gemma-4-26B-A4B-it-abliterated-eagle3-draft
  • 副產品:Speculators upstream create_empty_sample dtype bug 找到 + 修了 + 上游 PR #527 已開

Phase 0 prior art(誠實列 + Part 28 erratum)

寫這篇前跑 Phase 0,Codex 抓到 6 個 HF repo predates 我們(Part 28 publish 2026-05-09 之前):

RepoCreated內容
OptimizeLLM/Qwen3.5-122B-A10B-heretic-MTP-NVFP42026-04-11heretic + MTP grafted,~190 tok/s
AEON-7/DFlash-Qwen3.5-27B-Uncensored2026-04-12uncensored + external z-lab DFlash,33.2 vs 12.2 tok/s
guglxni/Qwen3.5-9B-abliterated-DFlash2026-04-15最直接 — fine-tune DFlash drafter on abliterated activations,跟 Part 28 mechanism 一模一樣
AEON-7/supergemma4-26b-dflash-pilot2026-04-15DFlash 5K-sample pilot,5.79% top-1,明說 negative speedup
huginnfork/Qwen3.6-27B-uncensored-heretic-v2-mtp2026-04-26heretic + MTP
llmfan46/Qwen3.6-27B-uncensored-heretic-v2-Native-MTP-Preserved2026-05-06Part 28 publish 前 3 天,我們文章內還點名說「未調」

✏️ Part 28 修正:當時 Part 28 寫「abliteration 流派都在壓 refusal rate,幾乎沒人在調 MTP acceptance」+「llmfan46 用的 Heretic + ARA 流派仍未針對 spec decode 調過」— 兩句都錯。Phase 0 抓到上面 6 個 repo,llmfan46 自己在 Part 28 publish 前 3 天就 ship 了 Native-MTP-Preserved 版本。本篇 Part 30 建立在這個更正後的事實上。

Narrow novelty(publish 當下我們找得到的範圍):

  • 沒搜到公開 HF repo 把 EAGLE-3 配 abliterated body(目前看到的鄰近做法都是 DFlash 或 native MTP)。有反例請丟過來。
  • 就我們翻過的範圍,Part 28 是第一份公開 per-position acceptance 拆解的 spec decode × abliterated body 數據;Part 30 補上 retrain 後的對照欄。
  • huihui Gemma 4 26B-A4B abliterated 是這 niche 裡 distribution 最廣的 repo,所以我們挑這條走。

Round 1 的數字是單一 config 對上 Part 28 reused baseline,屬於「有改善值得記錄」,不是 clean causal proof。Round 2 排定 paired same-prompt rerun + 無 abliteration 對照組。

Pipeline

Training stack

元件設定
HardwareNVIDIA GB10(DGX Spark),sm_12.1,121 GB unified,273 GB/s
Frameworkvllm-project/speculators v0.5.0.dev0
Verifier(body)coolthor/Huihui-gemma-4-26B-A4B-it-abliterated-FP8-Dynamic(我們自量化的 huihui base)
Drafter 起點RedHatAI/gemma-4-26B-A4B-it-speculator.eagle3(vanilla-trained pretrained)
Training dataMagpie 50k(Magpie-Align/Magpie-Llama-3.1-Pro-300K-Filtered 取 instruction),用 huihui FP8 重生 response 後當 (input, output) 配對
vLLM 角色同時跑 extract_hidden_states speculative_config + ExampleHiddenStatesConnector 作為 hidden states producer,gpu_memory_utilization=0.5 留給 trainer
Epochs / seq_len1 epoch / 4096 packed

Data pipeline

  1. response_regeneration/script.py 把 Magpie 50k instructions 餵 huihui FP8 重新生成 responses(~24h)
  2. prepare_data.py 把 jsonl 轉成 Arrow dataset(token_freq.pt + masked positions)
  3. train.py --on-missing generate --on-generate delete:trainer 跟 vLLM 同時跑,要 hidden states 時 trainer call vLLM,vLLM 寫 safetensors 到 shared dir,trainer 讀完刪掉

Speculators upstream bug(side artifact)

跑到 step ~9485(83%)時 train crash,RuntimeError dtype mismatch。Root cause:

# data.py 的 create_empty_sample(原本)
return {
    "hidden_states": torch.empty(0, 3 * hidden_size),  # ← 預設 fp32!
    "verifier_last_hidden_states": torch.empty(0, hidden_size),  # ← 也是 fp32
    ...
}

什麼時候 hit:vLLM extraction request timeout(15s default)→ 全 retry 失敗 → collate_fn fallback create_empty_sample() → 下游 eagle3/core.py:fc():verifier_lm_head() 期待 BF16 → mismatch 整個 train 死。

修法:

def create_empty_sample(hidden_size: int, dtype: torch.dtype = torch.bfloat16):
    return {
        "hidden_states": torch.empty(0, 3 * hidden_size, dtype=dtype),
        "input_ids": torch.empty(0, dtype=torch.long),
        "verifier_last_hidden_states": torch.empty(0, hidden_size, dtype=dtype),
        ...
    }

PR 已開:vllm-project/speculators#527。(Patch 本身已驗證 — v4 train 撐到底沒 crash,內部 data.py:67 改動 4 行)

還有一個 fragility 沒修但值得提:speculators 預設 --checkpoint-freq 單位是 epoch,1-epoch run mid-step crash = 0 checkpoint。我們第一次 crash 失去 9 小時訓練。值得另開 issue 加 step-level checkpoint。

Training trajectory(v4 run)

⚠️ Metric 釐清:trainer 同時 log full_acc_Ncond_acc_Nfull_acc = 無條件「pos N 預測對」的機率,對應 vLLM /metrics 跑起來時的 per-position acceptance;cond_acc = 條件機率「前面 0..N-1 全對情況下 pos N 還對」。對比 Part 28 vanilla baseline(65.6 / 43.3 / 29.2 / 20.5)要用 full_acc。本篇所有 acceptance 都用 full_acc 報。

Loss + full_acc 曲線(single-step samples,unsmoothed)

StepLossfull_acc_0full_acc_1full_acc_2
1k7.5166.8%39.9%25.1%
2k6.6669.5%44.0%27.9%
4k4.7477.0%55.9%42.6%
6k7.1864.8%39.0%24.8%
8k7.3965.7%38.9%24.5%
Final val(N=1266 batches)6.9466.8%41.4%26.4%

註:trainer 預設 ttt_steps=3 所以 training 只看 pos 0/1/2,沒有 full_acc_3。Inference 時 pos 3 是 drafter 外推(下節有 bench)。

收斂觀察

  • Loss 軌跡 bouncy 不 monotonic:1k 開始就 7.5 → 4k 達低點 4.74 → 後段回 7 區段。原因是 cosine LR schedule:warmup 完 4k 附近 LR 達 peak,model 在 high-LR 區間 jump-around 但學到 distribution shape;後段 LR decay 進入 fine refinement 階段,單步看起來 noisier。
  • Train metric 看似不亮眼:val full_acc_2 才 26.4%,跟 Part 28 vanilla baseline 29.2% 打平、甚至略輸。我做完 train 看 val 一度以為這篇 Part 30 要寫 "NO WIN"。
  • 但 train ≠ inference:val 是 teacher-forced argmax 對 Magpie ground truth(嚴),inference 是 rejection sampling 對 body 實際 distribution T=0.7(寬鬆)。下節 inference bench 才是 final verdict。

Inference bench(真正的考驗)

Train 完載新 drafter 進 vLLM(跟 Part 29 同 config,只把 draft model 換成我們的 fine-tune 版),量 per-position acceptance + throughput。

Setup

vllm serve coolthor/Huihui-gemma-4-26B-A4B-it-abliterated-FP8-Dynamic \
  --speculative-config '{"method":"eagle3","model":"coolthor/Huihui-gemma-4-26B-A4B-it-abliterated-eagle3-draft","num_speculative_tokens":4}' \
  --kv-cache-dtype fp8 \
  --gpu-memory-utilization 0.85 \
  --max-model-len 8192 \
  --enable-prefix-caching --trust-remote-code
  • N=10 prompts × T=0.7 × batch=1,max_tokens=200
  • 同 vLLM container、同 prompt pool,只切換 draft model 跑 vanilla MTP vs fine-tuned EAGLE-3 兩組
  • Part 28 vanilla baseline 是 prior publish 的數字(同硬體、同 body,單獨 paired run 我們手邊還沒做,但 acceptance 差距 19-52pp 之大不可能是 prompt-set 變化造成)

Per-position acceptance(關鍵數字)

⚠️ Methodology note:下面這個對照表的「Part 28 vanilla draft」是 chat completions 測的數字、「Fine-tuned drafter (本篇)」是 raw /v1/completions 測的數字 — endpoint 不一致。acceptance 拉平這個機制觀察成立(同 endpoint 內 retrain vs vanilla 確實有顯著差距),但精確 +pp 數值受 endpoint 差異污染。Round 2 paired bench(同 endpoint、同 prompt set)補在 Part 31。

PositionPart 28 vanilla draft (chat)Fine-tuned drafter (本篇,raw)Δ
pos 065.6%84.4%+18.8 pp
pos 143.3%74.9%+31.6 pp
pos 229.2%74.1%+44.9 pp
pos 320.5%72.7%+52.2 pp 🚀

Vanilla draft 在 abliterated body 上的 decay 是 −22/−14/−9 pp/step;我們 fine-tune 後 decay 變成 −9/−1/−1 pp/step,raw endpoint 上幾乎平的曲線

Throughput sweep

⚠️ 這張表的所有「Fine-tuned EAGLE-3」數字都是 /v1/completions raw endpoint;Part 28 baseline 是 chat completions。Production chat workload 上的對應數字會明顯較低(retrain drafter chat n=4 ~ 46 tok/s,vs pure body ~ 40,真實提升 ~ 1.15×)。本表保留給願意 reproduce raw endpoint 數字的讀者參考。

num_spec_tokensPart 28 vanilla (chat)Fine-tuned EAGLE-3 (raw)Speedup
0(no spec)39.3 tok/s39.3 tok/s1.00×
152.6 tok/s59.04 tok/s1.12×
251.4 tok/s66.96 tok/s1.30×
346.9 tok/s74.90 tok/s1.60×
450.0 tok/s100.36 tok/s aggregate / 107.59 per-prompt mean~2.01×

(Bench 細節:gpu_memory_utilization=0.85max-model-len=8192kv_cache_dtype=fp8temperature=0.7、N=10 prompts × max_tokens=200,batch=1)

對 vanilla draft 來說,n>1 就 throughput drop(deeper speculation 接受率太低,verify overhead 超過 spec 收益)。對我們 fine-tune drafter 來說,raw endpoint 上 throughput 從 n=1 → n=4 一路爬,n=4 是甜蜜點。這個機制成立 — drafter 對齊 body distribution 後 deep speculation 可用。但 chat workload 上「可用」的幅度遠小於 raw endpoint 看到的 2×。

Verdict: 機制 WIN,production framing 要修正

Fine-tune EAGLE-3 drafter 在 abliterated body 上 raw endpoint 拿到 n=4 deep speculation;chat workload 上的 framing 不該寫「2× speedup」。

  • Inference acceptance:vanilla draft 在 abliterated body 上 65→20% 陡降 → 我們 drafter raw endpoint 84→73% 近平坦(+52pp 在 pos 3,acceptance 機制觀察成立)
  • Throughput @ n=4(raw /v1/completions):vanilla ~50 tok/s → 我們 100.36 tok/s aggregate ≈ 2.01×。Production chat workload 對應的數字是 retrain ~46 vs pure body ~40 = 1.15×,跟 raw endpoint 看到的差很多
  • Drafter shipped 到 HF(coolthor/Huihui-gemma-4-26B-A4B-it-abliterated-eagle3-draft),1.86 GB safetensors,跟 vanilla MTP assistant 一樣概念。HF repo 一同補了 README endpoint caveat
  • Part 28 機制論證沒被推翻 — vanilla draft 確實會在 abliterated body 上 decay,retrain drafter 確實能拉平 acceptance 曲線;只是「拉平 acceptance」沒有自動 translate 成「chat workload 上 2× throughput」

Production recipe(daily-use config):

vllm serve coolthor/Huihui-gemma-4-26B-A4B-it-abliterated-FP8-Dynamic \
  --speculative-config '{"method":"eagle3","model":"coolthor/Huihui-gemma-4-26B-A4B-it-abliterated-eagle3-draft","num_speculative_tokens":4}' \
  --kv-cache-dtype fp8 \
  --gpu-memory-utilization 0.65 \
  --max-model-len 65536 \
  --enable-auto-tool-choice --tool-call-parser gemma4 \
  --enable-prefix-caching --trust-remote-code

跟 Part 29 的 n=1 recipe 比,四個 flag 不同:methodmtpeagle3model 換成我們的 drafter、num_speculative_tokens 從 1 → 4、max-model-len 從 8192 → 65536(daily-use 要支援 hermes 長 context)。其餘 vLLM flag 一樣。

Round 2 計畫(Part 31 預告)

WIN 拿到後,Round 2 的方向:

  • 跨 workload bench:現在 N=10 prompts 是英文 instruction-following。實測繁中 / code / podcast 摘要 / image gen prompt 寫作幾種 hikari/kiriha 實際 use case,看 acceptance 是不是同樣高
  • TurboQuant KV cache 3-bit + EAGLE-3 同時開(GB10 stack 4-phase upgrade 的 Phase 2),看 KV budget 大 4× 是否帶來額外收益(預期單用戶 chat 無感,batch ≥ 4 才有意義)
  • ttt_steps=4/5 訓練:目前 drafter 只 train pos 0/1/2,inference n=4 是外推。雖然實測 pos 3 acceptance 72.7% 還很好,但 native train 到 pos 3 應該更穩
  • DFlash 對照組:看 guglxni 路線(DFlash on abliterated)在 Gemma 4 上會不會跑得更快(不同 architecture,trade-off 不同)

給讀者的當下建議

  • 想 inference 加速 abliterated Gemma 4(production chat workload)→ 第一選擇是 vanilla Gemma 4 MTP assistant + num_speculative_tokens=4(Round 2 paired bench 顯示 chat EN ~53 / ZH ~45 tok/s,小架構 EAGLE-3 drafter 在 chat 上沒打贏這個 baseline);本篇的 retrain drafter 主要在 raw /v1/completions 場景拿到 ~100 tok/s aggregate,production chat 上提升幅度小很多。詳細數字進 Part 31
  • 想自己 fine-tune drafter → 看本篇 pipeline + 注意 Speculators create_empty_sample bug + 我們 patch(speculators/train/data.py:67 把 dtype 預設改成 bfloat16)
  • 想關注 abliterated body + spec decode 全社群進度 → 上面 Phase 0 6 個 repo 都值得追,加上我們 round 2 結果

相關

常見問題

Part 30 跟 Part 28/29 差在哪?
Part 28 是 mechanism(為什麼 vanilla draft 跟不上 abliterated body),Part 29 是 deploy recipe(n=1 開箱 +34%),Part 30 是 retrain 嘗試的 round 1 結果 — 我們 fine-tune EAGLE-3 drafter 對齊 abliterated body 的 distribution,目標是 unlock n=2/3/4 deep speculation。
結果有 unlock n=4 嗎?
機制上有 — 在 raw `/v1/completions` 上 pos 3 acceptance 從 vanilla 的 20.5% → 72.7%(+52pp),acceptance 曲線從 65→43→29→20% 變成 84→75→74→73% 近平坦。但**「2× throughput」這個 framing 在 production chat workload 上不成立**:文首 endpoint correction 說明,原文 baseline 是 chat、retrain 是 raw,測量 endpoint 不一致;chat workload 上 retrain drafter ~46 tok/s,vanilla MTP n=1 ~51 tok/s,pure body ~40 tok/s — 真實效益 +15% 而非 +100%。Round 2 paired bench 補在 Part 31。
那為什麼 chat workload 上 retrain drafter 沒有 2× 加速?
兩個原因:(1) chat output 是真實 semantic content,Drafter 的 deep speculation pos 1/2/3 acceptance 在「難預測」context 下 compounding error 抵消,只有 pos-0 真貢獻;raw endpoint 上 output 退化成 ASCII repetition,小 drafter trivially 全命中。(2) 我們的 retrained EAGLE-3 head 比 Google 預訓的 vanilla MTP `Gemma4MTPModel`(完整 Gemma layer)小很多,chat 上 pos-0 acceptance 57% < vanilla MTP 70.6%。這是架構 × workload predictability 的 tradeoff,Round 2 加中文 data 主要解的是另一個問題(v1 drafter ZH OOD),不會打破這個架構天花板。
Phase 0 抓到別人已經做過類似工作?
對。6 個 HF repo 在 Part 28 publish 前就 ship 過 abliterated body + spec decode drafter 的組合(guglxni / AEON-7 ×2 / OptimizeLLM / huginnfork / llmfan46)。但 EAGLE-3 + huihui Gemma 4 26B-A4B abliterated 這個 specific 配對我們搜不到別人公開過,加上我們 publish per-position acceptance 數字,這是 narrow novelty。
Speculators 那個 bug 是什麼?
vllm-project/speculators 的 `create_empty_sample()` 預設用 `torch.empty()` 不帶 dtype 參數 → fp32。當 vLLM extraction request timeout 時 fallback 用這個 empty sample,downstream BF16 layers(fc / verifier_lm_head)dtype mismatch crash 整個 train。我們的 patch 把預設改成 `torch.bfloat16`,上游 PR 已開:[vllm-project/speculators#527](https://github.com/vllm-project/speculators/pull/527)。