iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
AI Engineering

不是模型太慢,是你沒算過這筆帳:地端 LLM 工程實戰 30 天系列 第 5

Day 05 - Q4_K_M 是規格,不是品質保證:同名量化檔,KL 散度可以差近 3 倍

  • 分享至 

  • xImage
  •  

昨天我們把模型名字拆成六個欄位,搞清楚了 35B-A3B 到底是 35B 還是 3B。檔名最後一段 Q4_K_M.gguf 我故意留到今天,因為它值得一整篇——Day 1 那晚救回 40 秒的兩刀,其中一刀就是換一顆量化檔。

場景你一定遇過:上 Hugging Face 找某顆模型的 GGUF,跳出七八個 repo,unsloth、bartowski、lmstudio-community、ggml-org,每家都有 Q4_K_M,大小幾乎一樣。你挑了下載數最多的那個,心想反正同一個規格。

我把三家的 Llama-3.2-1B-Instruct-Q4_K_M.gguf 都抓下來,檔案大小是 807,694,464 / 807,694,368 / 807,690,688 bytes【本機實測】。最大差 3,776 bytes,0.0005%。

Q4_K_M 確實是 llama.cpp 定義的預設 recipe,這點等一下我用拆檔案證明。但同一個 recipe 不代表同樣的品質,而且差距藏在檔名不寫、model card 也常常不寫的地方。

Q4_K_M 到底規定了什麼

「Q4_K_M 只是檔名,大家隨便取的」這個說法流傳很廣,但它是錯的。Q4_K_M 是 llama.cpp 的 quantization preset,llama-quantize 直接吃這個字串當量化目標,原始碼裡有對應的 LLAMA_FTYPE_MOSTLY_Q4_K_M【官方:llama.cpp tools/quantize/README.mdsrc/llama-quant.cpp】。

把三顆檔案拆開,看每個張量被壓成什麼型別【本機實測,腳本見文末】:

上傳者 Q4_K Q6_K F32 bits/weight
bartowski 830.5M (67.2%) 405.3M (32.8%) 0.1M 5.1779
unsloth 830.5M (67.2%) 405.3M (32.8%) 0.1M 5.1779
lmstudio-community 830.5M (67.2%) 405.3M (32.8%) 0.1M 5.1779

三家的 tensor type 組成完全一致:同樣 96 個張量是 Q4_K、同樣 17 個張量是 Q6_K,bits/weight 連小數點後四位都一樣。注意這說的是「哪個張量用哪種型別」,不是檔案內容相同——量化後的數值並不一樣,等一下就會看到差在哪。

https://ithelp.ithome.com.tw/upload/images/20260816/20183550RYA78O8k7W.png
圖 1:Q4_K_M 給的是一套預設的 tensor-type recipe。source、量化器版本、imatrix、校準資料、人工 override 都不包含在這個名字裡,品質差距就發生在這一半。

命名文法是 Q{位元數}_{變體}_{尺寸}

  • Q4:主要張量的目標型別是 4-bit。是「主要」,整顆模型的平均位元數會更高。
  • _K:K-quant,ggml 的一種 block quantization type。權重切成 block 壓縮,每個 block 有自己的 scale,連 scale 本身都再被量化過,比舊的 _0/_1 legacy 格式省得多(legacy 如今只剩 Q8_0 還常見)。
  • _M:medium。不是「全部壓 Q4_K」,而是模型層級的 mixed-quant preset,大部分壓 Q4_K,一部分敏感張量升到更高精度。

那 17 個 Q6_K 張量長這樣【本機實測】:

token_embd.weight
blk.{0,1,4,7,8,9,12,15}.attn_v.weight     ← 8/16 層
blk.{0,1,4,7,8,9,12,15}.ffn_down.weight   ← 8/16 層

attn_vffn_down 各一半,對得上原始 PR 的定義:「uses GGML_TYPE_Q6_K for half of the attention.wv and feed_forward.w2 tensors, else GGML_TYPE_Q4_K」【官方:PR #1684】。同一支 PR 講明的是 output.weight 在各種 quant mix 裡都保留 6-bit,所以那不是 _M 的功勞。token_embd.weight 也是 Q6_K 則是因為這顆 1B 是 tied embeddings,llama.cpp 讓 token embedding 跟著 output 張量走【官方:src/llama-quant.cpp】。

這組層號不是隨機的,是原始碼裡 use_more_bits() 挑出來的【官方:src/llama-quant.cpp】。但名單會變:同一份原始碼,Q4_K_M 遇到 70B 把 attn_v 拉到 Q5_K,遇到 8-expert MoE 拉到 Q8_0,遇到 Falcon 走另一條分支。所以它是一份依模型而異的預設 recipe,不是逐張量寫死的規格。

另一個誤解:4.83 bpw 這種「Q4_K_M 的固定位元數」不存在。我實測 Llama-3.2-1B 是 5.1779 bpw,官方文件列 Llama-3.1-8B 是 4.8944【官方:tools/quantize/README.md】。bpw 是模型乘上 preset 的結果,不是 preset 的屬性。

最後補兩個前綴。IQ 是 I-quant,codebook-based 的量化方案;UD- 是 Unsloth 自家的動態配置。兩件事要分清楚:IQ 指的是量化方案,不是「有沒有校準」;而且「IQ 同體積一定比 K-quant 好」是過度簡化,官方 Llama 3 8B 的表上,3-bit 區 iq3_S 的 KLD 只有 q3_K_S 的一半多一點,4-bit 區 iq4_NL 卻輸給體積相當的 q4_K_S【官方:tools/perplexity/README.md】。在這張表上,優勢集中在低位元區。真正跟校準綁定的只有極低位元:IQ1_S/IQ1_M/IQ2_XXS/IQ2_XS/IQ2_S/IQ3_XXS 六種,加上 Q2_K_S 裡的 Q2_K 張量,llama.cpp 硬性要求 imatrix,不給就拒絕量化【官方:src/llama-quant.cpptensor_requires_imatrix()】。

官方的帳:量化虧多少

llama.cpp 引入 K-quant 的那支 PR 附了 perplexity 對照表(wikitext,越低越好),LLaMA 7B 的數字皆【官方】(來源:github.com/ggml-org/llama.cpp/pull/1684 )

精度 PPL vs F16
F16 5.9066
Q6_K 5.9110 +0.07%
Q5_K_M 5.9208 +0.24%
Q4_K_M 5.9601 +0.91%
Q3_K_M 6.1503 +4.1%
Q2_K 6.7764 +14.7%

https://ithelp.ithome.com.tw/upload/images/20260816/20183550Jjg3RKt94G.png
圖 2:在 2023 年那組原始 LLaMA 7B 實驗裡,Q4_K_M 落在很漂亮的體積/品質折衷點,這是它後來被廣泛當成起手選擇的原因之一。

Q4_K_M 的 PPL 只比 F16 高約 0.9%,體積剩三成。以相對 F16 的 PPL 增量算,Q3_K_M 約是 Q4_K_M 的 4.5 倍,Q2_K 約 16 倍【推算,由上表換算】。這是增量的倍率,不是「品質差 4.5 倍」——PPL 不是線性的品質百分比。這張表也只涵蓋 2023 年的原始 LLaMA 7B 在 wikitext 上的表現,不是所有模型的通則。

PPL 看不到的地方

PPL 聚合的是「語料裡正確的下一個 token」拿到多少機率,它不比較整個 vocabulary 上的分佈。KL divergence 才比:拿 FP16/BF16 當參考,逐 token 看兩邊的完整輸出分佈被扭曲多少,0 代表完全相同【官方:tools/perplexity/README.md】。

社群早期最完整的一份量測(Mistral-7B,imatrix 校準,wiki.test),數字皆【第三方實測】(來源:gist.github.com/Artefact2/b5f810600771265fc1e39442288e8ec9):

精度 KL 中位數 KL q99
Q6_K 0.0032 0.0222
Q5_K_M 0.0043 0.0368
Q4_K_M 0.0075 0.0885
Q3_K_M 0.0171 0.2546

以中位數比,Q4_K_M 的 KL 是 Q6_K 的 2.3 倍;比到 q99(只有 1% 的 token 比這個門檻更糟),倍率拉到 4.0 倍【推算,由上表換算】。也就是 per-token KL 分佈的尾端惡化得比中位數快:多數位置差得沒那麼誇張,少數位置偏得特別遠。這也解釋了為什麼平均指標看起來接近,用起來卻偶爾出包。

https://ithelp.ithome.com.tw/upload/images/20260816/201835505Rcfdj7t5Q.png
圖 3:中位數是 2.3 倍,到了 q99 變成 4.0 倍。尾端的相對惡化明顯更大,平均值會騙人的地方就在這裡。

那三顆檔案,差在哪

tensor 組成一模一樣,剩下的差別在 Q4_K_M 沒規定的那半邊:用什麼校準資料、從哪個 checkpoint 起手、用哪個版本的量化器。這些不寫進檔名,但有一部分寫進了 metadata【本機實測】:

上傳者 imatrix 校準 dataset chunks
bartowski calibration_datav3.txt 125
unsloth unsloth_calibration_Llama-3.2-1B-Instruct.txt 689
lmstudio-community

一顆沒掛 imatrix,掛了的兩顆校準語料也完全不同——unsloth 指向的是一份以該模型命名的 calibration dataset,chunk 數是 bartowski 的 5.5 倍(檔名只能證明命名,內容有沒有真的針對這顆模型調過,metadata 看不出來)。順手也驗了本機 ollama 拉下來的 llama3.1:8b-instruct-q4_K_Mqwen3.6:35bgemma4:26b,三顆都沒有 quantize.imatrix.* 欄位【本機實測】。

第三方的 Gemma 4 E4B 量測裡,同樣叫 Q4_K_M:bartowski 的 KL 是 0.072,lmstudio-community 是 0.207,ggml-org 是 0.208【第三方實測】(來源:localbench.substack.com,對 BF16 參考模型跑約 25 萬 token)。差了約 2.9 倍。

口徑要先講清楚:localbench 算的是 top-40 截斷 KL,我下面用 llama-perplexity 算的是完整 logits。同一份 benchmark 內比倍率可以,兩邊的絕對值不能並排比。

我也用手上這三顆自己跑了一遍,拿同一顆模型的 BF16 當參考(wikitext-2 raw test,200 chunks,約 10 萬 token)【本機實測】:

上傳者 imatrix PPL Mean KLD Median KLD q99 KLD top-1 一致率
bartowski 14.7304 0.035280 0.023675 0.2346 90.088%
unsloth 14.7388 0.034736 0.023379 0.2326 89.898%
lmstudio-community 14.6904 0.041873 0.029238 0.2614 89.239%

看最後一列。沒掛 imatrix 的那顆,PPL 是三顆裡最低的,比 unsloth 低 0.33%,照 PPL 的讀法它「最好」。同一顆檔案的 Mean KLD 卻比 unsloth 高 20.5%,中位數高 25.1%,跟 BF16 選出同一個 top-1 token 的比率也是三顆最低【推算,由上表換算】。

**兩把尺給出相反的排名。**它們問的問題本來就不同:PPL 問「對真正的下一個 token 給了多少機率」,KL 問「離 BF16 有多遠」。目標是忠實重現原模型就看 KL,但 KL 低不保證 downstream 任務更強。

有掛 imatrix 的兩顆彼此只差 1.6%,遠小於跟第三顆的 20.5%。這與「imatrix 有幫助」一致,但那 20.5% 不能全記在 imatrix 帳上——換 uploader 就同時換了量化器版本與 source,這是觀察不是對照實驗。真正隔離出它的是官方那張 Llama 3 8B scoreboard,其他條件全固定:Q4_K_M 掛 imatrix 的 KLD 是 0.028152,不掛 0.031273,改善 11%;Q4_K_S 則是 0.031951 對 0.043136,改善 26%【官方:tools/perplexity/README.md,百分比為推算】。

https://ithelp.ithome.com.tw/upload/images/20260816/20183550uTvGsUkMzL.png
圖 4:三顆檔案在檔名、大小、tensor type 組成、bpw 四個維度上完全無法區分。分得出來的是 metadata 裡的校準資訊,以及跑一次 KL 才看得到的差距。

四類變因

差距至少來自四類,哪一類主導會隨模型變:

  1. 來源權重:不同上傳者可能從不同 revision、甚至不同 checkpoint 起手,是否從 QAT checkpoint 出發差別尤其大。
  2. 量化器版本:llama.cpp 的張量選型規則一直在改,不同 commit 壓出來的 Q4_K_M 組成可能不同。
  3. imatrix 與校準語料:有沒有掛、掛什麼、跑幾個 chunk。
  4. 張量層級的 recipellama-quantize 支援 --output-tensor-type--token-embedding-type,以及吃 regex 的 --tensor-type【官方:tools/quantize/README.md】。上傳者可以覆寫特定張量的型別,同時仍然把檔案叫做 Q4_K_M

第 3 點值得單獨講,坊間的描述常常是錯的。imatrix(importance matrix)不是「把 bit 預算優先分給重要通道」,那是 mixed-bit / dynamic quantization 在做的事。它是拿校準語料跑前向、收集各張量的 activation 重要度統計,量化時用這些統計降低誤差【官方:tools/imatrix/README.md】,不會把 Q4_K 張量改成 Q5/Q6。我的實測正好印證:有掛跟沒掛的兩顆,tensor type 組成一模一樣。

所以選檔的順序是:量化等級決定體積/品質的主要 trade-off,同一個等級裡再去看來源、imatrix 與 recipe。反過來說「挑上傳者比挑 quant level 更重要」會踩到官方 PPL 表,Q4 到 Q2 的落差遠大於同級不同家的落差。

也別把它變成上傳者排行榜。Unsloth 的 Dynamic 2.0 是逐模型客製的 recipe,這本身就說明 recipe 是 model-specific 的,某家在 Gemma 上做得好不保證在 Qwen 上也好。判準不是名氣或下載量,是它有沒有寫清楚 source revision、quant recipe、imatrix 與校準語料。

三十秒自保檢查法

新版 llama.cpp 的 quantize 會把校準資訊寫進 metadata,一行指令就夾得出來:

pip install gguf
gguf-dump --no-tensors model-Q4_K_M.gguf | grep -i imatrix
# 有掛會看到:
#   quantize.imatrix.file / quantize.imatrix.dataset
#   quantize.imatrix.entries_count / quantize.imatrix.chunks_count

這篇的實測就是這樣跑的,兩支腳本都能重跑:verify-gguf.py 拆 tensor 組成、比對 metadata,內建的 assert 會自己驗結論還成不成立;measure-kl.sh 跑上面那張 KL 表。

有個但書:沒有這些欄位不代表沒掛,舊版工具不記錄;有欄位則是產檔工具自己宣告用過 imatrix,很強的正向證據,但 metadata 可以改,它不是不可偽造的 provenance。這個檢查拿來縮小候選,不能拿來判死刑。

https://ithelp.ithome.com.tw/upload/images/20260816/201835500hCml5Vg1B.png
圖 5:三十秒的選檔流程。先用等級圈出體積預算,再在同級裡用 metadata 分辨,最後才是自己的回歸測試。

做 agent 的人,先測 Q8_0

localbench 對 Qwen3.6-35B-A3B 的分類實測裡,對後端工程師最重要的一條是:所有類別中 tool calling 對量化最敏感。即使 Q8_0,tool calling 的 KL 仍有 0.177,同一份量測裡長文件是 0.121,coding 與 science 在 0.010 以下【第三方實測】(來源同 localbench)。

合理的解釋是函式名、JSON 結構這種「一個 token 錯就全錯」的輸出,正好踩在分佈尾端。但那份 benchmark 只證明了這個類別敏感,沒證明成因。

我的做法是:一般聊天與摘要從 Q4_K_M 測起;要做 agent、tool calling、結構化輸出,記憶體允許就把 Q8_0 / Q6_K 一起納入比較。能不能往 Q5/Q4 壓,那份資料回答不了,連 Q8_0 都還有 0.177,該篇也沒給建議底線。這種界線只能用你自己的 tool-call 成功率回歸測試去壓。

從 Q4_K_M 升到 Q5_K_M 值不值?以官方 Llama-3.1-8B 的數字是 +16.5% 體積【官方 + 推算】,買到的是尾端收斂,Mistral-7B 那張表的 q99 KL 少 2.4 倍【推算,不同模型與語料,只當量級參考】。

最後補一句參照組

T3/T4 那個世界(RTX PRO 6000、H100 上的高吞吐 serving)主流路線通常不是 GGUF,而是 AWQ/GPTQ 這類 checkpoint scheme、FP8,以及新硬體上的 NVFP4 與 MXFP4。有些由原廠直接提供量化 checkpoint,有些仍是部署方自己 PTQ,不能一概當成有官方背書。

vLLM 現在確實能載 GGUF,裝上 vllm-gguf-plugin 就能 vllm serve unsloth/Qwen3-0.6B-GGUF:Q4_K_M --tokenizer Qwen/Qwen3-0.6B(官方建議直接用 base model 的 tokenizer,GGUF 那份轉換慢又不穩),只是整條路線官方自己標為 "highly experimental and under-optimized"【官方:docs.vllm.ai】。所以不是「vLLM 不玩 GGUF」,而是 GGUF 主要流行在 llama.cpp 這條 local inference 生態,在 production serving 上不是主流路線。這條格式天梯我們第 19 天爬。

今天的實驗需要什麼

不用 GPU。一個 Python 環境加任何一顆下載過的 GGUF 檔就能跑 gguf-dump 驗 imatrix,三顆 1B 對照檔不到 2.4GB。想自己量 KL 要多一顆 BF16 參考檔(1B 約 2.5GB)和一個跑得動 llama.cpp 的環境。我這輪 200 chunks 三顆約十分鐘,中間產生的參考 logits 檔有 7.8GB,跑完記得刪。

小結

  • Q4_K_M 是有定義的預設 recipe:同樣的 source、同樣的 llama.cpp 版本與預設參數下,tensor-type 配置可重現。三家拆開來連 bpw 的小數點後四位都一樣。
  • 在原始 K-quant 定義與這顆 Llama 上,_M 體現在一半的 attn_v 加一半的 ffn_down 升 Q6_K;現代 llama.cpp 會依架構與版本調整,而選層看的是張量處理序號,不是層號。
  • bpw 不是 preset 的屬性:同一個 Q4_K_M,1B 是 5.1779,8B 是 4.8944。
  • KL 分佈的尾端惡化得比中位數快,平均值看不到。本機那組實測裡,PPL 最低的那顆 Mean KLD 最高、top-1 一致率最低——只看 PPL 挑「最忠實 BF16 的檔」會挑到不同答案。
  • 同一個 recipe、不同的品質,差距來自來源權重、量化器版本、imatrix/校準語料、張量級 recipe 四類。我跨 uploader 觀察到約 20% KLD 差距,但那不是對照實驗;官方隔離出來的 imatrix 效果是 11%(Q4_K_M)到 26%(Q4_K_S)。
  • 先用等級決定體積預算,再在同級裡看 metadata 與實測。gguf-dump | grep imatrix 三十秒可驗,但只能單向確認。
  • 在那份 Qwen3.6-35B-A3B 的 benchmark 裡,tool calling 是最敏感的類別,連 Q8_0 都有 0.177。做 agent 優先測 Q8_0/Q6_K,要往下壓就自己跑 tool-call 成功率回歸測試。

明天,Day 06:「六顆主流地端模型:誰適合什麼場景、起手參數與雷」。今天你學會了同一顆模型該挑哪個檔,明天解決更前面的問題,一開始該挑哪顆模型:六顆你的機器跑得動的主流選手,各自的適用場景、官方建議參數,和那些文件不寫但一踩就中的雷。

咱們明天見。


上一篇
Day 04 - 讀懂模型名字:35B-A3B 到底是 35B 還是 3B
下一篇
Day 06 - 六組主流地端模型:誰適合什麼場景、起手參數與雷
系列文
不是模型太慢,是你沒算過這筆帳:地端 LLM 工程實戰 30 天6
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言