LLM 深水區 · part 4
[LLM 深水區] 自己做「層級量化」:只挑那幾張張量動手,把 GGUF 再榨 2.45 GiB
❯ cat --toc
TL;DR
一個 GGUF 不是單一精度:Laguna 官方 Q4_K_M 的 814 張張量裡混著 240 F16(attention)+ 239 Q4_K + 48 Q6_K + 287 F32。想只動其中幾張,用 llama-quantize、base 給 Q8_0(給 attention),但要把每個既有量化家族(Q4_K/Q6_K)用 --tensor-type 釘回原型別——不然 Q8_0 base 會把 287 張 expert 也一起重壓(檔案反而脹到約 116 GiB)。釘好後只有那 240 張 F16 attention 真的換成 Q8_0。一律先 --dry-run 對「轉換張量數=240」;別拿壓過的張量再壓(誤差通常只會疊加)。

白話版:量化不是「整包砍 4-bit」,是逐張張量挑精度
大部分人對量化的印象是:「把模型從 16-bit 砍成 4-bit,換小換快、品質掉一點」。這個印象對一半——會掉品質是真的,但「整個模型同一種精度」是錯的。
一個 GGUF 檔裡面,不同張量本來就用不同精度。做量化配方的人早就知道:有些張量(像 attention、還有 norm)對誤差很敏感,壓下去品質掉很多,所以故意留高精度;有些張量(像 MoE 的 expert)壓狠一點也沒差,就用低精度換空間。所以「Q4_K_M」不是「全部 4-bit」,是一份混合精度的配方。
看懂這件事,你就多了一個很實用的工具:你可以只動其中幾張張量,不用整包重壓。上一篇洋垃圾 #2 我把 Laguna 的 attention 從 F16 換成 Q8_0、省了約 2.45 GiB、還快 7%——這篇就拆這招怎麼做,順便講一個 llama-quantize 會讓你白忙一場的坑。
為什麼要寫這篇
Part 1 我拆了「量化演算法在做什麼」——K-quant、super-block、旋轉那些機制。那篇是「原理」,這篇是「動手」:同一份 GGUF,你怎麼只挑幾張張量、換成你想要的精度。
會需要這招,通常是因為你要在有限的硬體下榨出極限。上一篇在一張 22GB 老卡上跑 118B,瓶頸是 CPU↔GPU 頻寬,而 attention 是「留高精度、又每個 token 都要搬」的那種張量——把它從 F16 換成 Q8_0,資料量少一半、頻寬壓力也小一點。但要做這件事,你得先懂兩個「為什麼」,再看懂一份 GGUF 的內部長相、繞過 llama-quantize 的一個陷阱。
為什麼可以量化,又為什麼省空間
動手前先把兩個「為什麼」講清楚——這也是你之後決定「壓多狠、動哪張」的判斷基礎。
為什麼權重可以量化? 因為神經網路本來就有雜訊、權重的冗餘度又高,本來就不需要 16-bit 那麼精確。把每個權重四捨五入到少數幾個離散等級(4-bit=16 個等級),單一個權重的誤差很小;更關鍵的是,這些小誤差在一整層、一整個網路裡大多會互相抵消——就像 MP3 丟掉你耳朵聽不太出來的頻段,檔案小一截、你卻幾乎聽不出差別。(背後的演算法——K-quant、super-block、把 outlier 旋轉攤平——Part 1 拆得很細,這篇不重複。)
為什麼會省空間? 因為一份權重檔的大小,基本上就是「參數量 × 每個權重幾 bit ÷ 8」。同一個模型,權重從 16-bit 換成 4-bit,理論上差約 4 倍。拿 Laguna 118B 來算:全 F16 大約要 235GB,壓成官方的 Q4_K_M 剩約 75GB(≈70 GiB)——不到三分之一,一張消費級顯卡配大 RAM 就有機會跑得動。
所以量化的本質是一種取捨(trade-off):拿一點點品質,換回一大塊空間(跟隨之而來的頻寬、速度)。而且不必讓整個模型套同一種精度——下一節你會看到,一份 GGUF 本來就能替每張張量分別指定精度。
先看清楚:一個 GGUF 裡有四種精度
動手前先看檔案。用 llama.cpp 的 llama-gguf 把每張張量的名稱跟型別讀出來:
llama-gguf laguna-s-2.1-Q4_K_M.gguf r n | grep -E "attn|ffn|weight" | head
你會看到類似這樣(型別跟在名稱後面):
blk.0.attn_q.weight F16 [3072, 6144]
blk.0.attn_k.weight F16 [3072, 1024]
blk.0.attn_gate.weight F16 [3072, 48]
blk.0.attn_norm.weight F32 [3072]
blk.0.ffn_down_exps.weight Q6_K
blk.0.ffn_gate_exps.weight Q4_K
把整份統計起來,Laguna 官方 Q4_K_M 的 814 張張量分佈是:
| 精度 | 張數 | 大致是誰 |
|---|---|---|
| F16 | 240 | 48 層 attention(attn_q/k/v/output/gate,每層 5 張) |
| Q4_K | 239 | 多數 MoE expert 的 gate/up |
| Q6_K | 48 | 部分 expert-down、dense 的 ffn_down、output.weight |
| F32 | 287 | router、各種 norm、bias |
一句話:attention 的權重(attn_q/k/v/output/gate)全部留在 F16(合計約 5.2 GiB;attention 的 norm 是 F32,不算在這 240 張裡),expert 才是被壓狠的那群,敏感的 router/norm 用最高精度的 F32。這就是「Q4_K_M」的真面目——不是一個數字,是一張精度表。

挑哪幾張下手:留敏感的、砍「高精度又常搬」的
看懂佈局,下一個問題是「動哪幾張最划算」。原則兩條:
- 別碰做量化的人故意留高精度的——F32 的 router/norm/bias 那些,壓下去品質崩,省的空間也不多。
- 挑「精度高、又常被搬動」的——在混合 offload 的機器上,attention 每個 token 都要算、又還留在 F16,把它從 F16 降到 Q8_0(差不多砍一半),資料量少、CPU↔GPU 頻寬壓力小,這才是有感的那刀。
所以目標很明確:只動那 240 張 F16 attention 權重,其他全部原封不動。 注意「原封不動」很重要——不是把整份重壓一次 Q8,是只把 attention 換成 Q8_0、其他每個家族都釘回它們現在的型別(下一節細講)。
動手一:base 型別會動「全部」,不是只動你點名的那幾張
先講一個很反直覺的陷阱。llama-quantize 最後那個位置參數是 base 型別——它是「每一張能量化的張量的預設目標」。--tensor-type 只是覆寫個別張量的預設值,不代表其他張量就不會動。
所以如果你天真地把 base 設成 Q8_0、再覆寫 attention:
# ❌ base Q8_0 會想把「所有」能量化的張量都變成 Q8_0
llama-quantize \
--tensor-type '^blk\.[0-9]+\.attn_(q|k|v|output|gate)\.weight$=Q8_0' \
laguna-s-2.1-Q4_K_M.gguf out.gguf Q8_0 24
dry-run 一看,它要動 527 張(240 attention + 239 Q4_K + 48 Q6_K expert 全被 Q8_0 拖上去),輸出反而脹到 119,227 MiB(約 116 GiB)——你的 attention 覆寫其實是多餘的,真正發生的是 expert 被從 Q4/Q6 重壓成 Q8(既變大、又疊了二次量化誤差)。
而且如果你真的跑下去(不是 dry-run)又沒加 --allow-requantize,這個指令會直接報錯 requantizing from type q4_K is disabled。這不是在找你麻煩——這個錯誤正好幫你擋掉二次量化:它在提醒你「你正要把已經壓過的 expert 再壓一次」。所以下面正式指令我們刻意不加 --allow-requantize,把這個防呆留著。

動手二:把每個「不動的」家族釘回原型別,再 dry-run 對數量
正解:base 還是 Q8_0(給 attention 用),但把其他每一個既有量化家族,用 --tensor-type 明確釘回它現在的型別。目標型別跟現在型別一樣(cur == new)的張量,llama.cpp 會直接複製、按 byte 原封不動保留;只有真的要變的 240 張 attention 會被轉。
llama-quantize \
--token-embedding-type Q4_K \
--output-tensor-type Q6_K \
--tensor-type '^blk\.[0-9]+\.attn_(q|k|v|output|gate)\.weight$=Q8_0' \
--tensor-type '^blk\.(0|1|2|3|4|5|8|11|14|17|20|23|26|29|32|35|38|41|42|43|44|45|46|47)\.ffn_down=Q6_K' \
--tensor-type 'ffn_down=Q4_K' \
--tensor-type 'ffn_gate=Q4_K' \
--tensor-type 'ffn_up=Q4_K' \
laguna-s-2.1-Q4_K_M.gguf laguna-s-2.1-attnQ8.gguf Q8_0 24
幾個關鍵:
- base
Q8_0+ 把 expert 釘回 Q4_K/Q6_K:這樣 239 張 Q4_K、48 張 Q6_K 的目標型別就等於它們現在的型別,cur == new→ 逐位元組複製、不重壓。F32 的 norm/router 本來就不可量化,也照樣複製。 - 順序不能放反:把特定層
ffn_down釘成 Q6_K 的那條 pattern,一定要排在涵蓋範圍較大的ffn_down=Q4_K前面——因為 llama.cpp 以第一個 match 到的 pattern 為準。排反了,該留 Q6 的層會先被 Q4 pattern 抓走。 - 不要加
--allow-requantize:只要 pattern 釘全了,就沒有任何已量化張量需要被轉,也就碰不到那個防呆;把防呆留著,反而幫你在漏掉某一類張量時當場報錯。
正式跑之前,把 --dry-run 加在執行檔後面預覽,確認兩個數字對得上:
- 轉換張量數 = 240:dry-run 會印出 240 條
size … -> …轉換行(其餘 574 張印成 unchanged)——CLI 不會給你一個彙總數字,得自己數行數,數出來要正好 240。 - 輸出大小:
model size 71687.10 MiB → quant size 69180.90 MiB(約 70.0 GiB → 67.6 GiB,省約 2.45 GiB/2.63 GB)。
兩個都對,把 --dry-run 拿掉正式跑。我在 EPYC 7402、24 threads 上,整份 118B 約 155 秒轉完。
一條鐵律:別拿壓過的張量再壓
上面一直強調「把其他張量釘回原型別、不要重壓」,這是這篇最重要的一句。
量化誤差通常只會疊加、很難互相抵消。已經是 Q6_K 的 expert,如果你為了再省一點空間把它降成 Q5 或 Q4,等於在「已經有誤差的近似」上再做一次近似——品質掉得比你想像多,省下的空間卻很有限。
所以兩個原則:
- 要省空間,優先動還留在高精度的張量(這裡是 F16 attention),別去 requant 已經壓好的 expert。
- 要做品質實驗、想壓更狠,從原始 BF16 權重直接量化到你要的精度,而不是拿現成的 Q4_K_M 再壓。一次量化的誤差,通常比「壓了又壓」乾淨。

收穫
最花時間搞懂的地方:不是量化演算法,是 llama-quantize 的運作邏輯——base 型別是「動全部能動的」,不是「只動你點名的那幾張」。我差點就用 base Q8_0 把 287 張 expert 全部重壓到 Q8(檔案反而變大、還疊誤差),是 --allow-requantize 那個防呆報錯擋下來、逼我搞懂為什麼要「把不動的張量釘回原型別」。所有選擇性量化,**dry-run 對『轉換張量數』**都是不能省的一步。
實作前先記住一件事:動一份 GGUF 前先 llama-gguf ... r n 看它的精度表。你會發現多數「Q4」檔其實是混合精度——知道哪張是什麼精度,才知道哪張還有空間、哪張碰不得。
通用原則:量化可以逐張張量分開決定,不是整個模型一個開關。想省空間,就鎖定「還留高精度、又常被搬動」的那幾張;別在已經壓過的張量上再疊一次近似。
收工前的 checklist
llama-gguf model.gguf r n先看精度表,確認你要動的張量現在是什麼型別。- 把 base 設成你要的新精度(例如
Q8_0),再用--tensor-type把其他各類已量化張量指定回原本的型別;特定的 Q6 pattern 要排在涵蓋範圍較大的 Q4 pattern 前面(以第一個 match 為準)。 - 一定先跑
--dry-run(別加--allow-requantize):確認「轉換張量數」和「輸出大小」都符合預期再正式執行。漏釘某類張量時,dry-run 的「轉換張量數」會不對(變多);等你正式跑、又沒加--allow-requantize,就會直接 abort。 - 別 requant 已經壓好的張量;要壓更狠,直接從原始 BF16 一次量化到底。
- 壓完拿去 serve,跑一輪你在意的品質 gate(JSON/tool-call/一題 coding)確認沒壓壞。
常見問題
- 一個 GGUF 檔是不是所有權重都用同一種精度?
- 不是。以 Laguna S 2.1 的官方 Q4_K_M GGUF 為例,它的 814 個張量裡有 239 個 Q4_K、48 個 Q6_K、240 個 F16、287 個 F32——attention 留 F16、expert 用 Q4_K/Q6_K、router 跟 norm 留 F32。所謂「Q4_K_M」是一份混合精度的配方,不是把每張張量都壓成 4-bit。
- 怎麼只量化模型裡的某幾張張量?
- 用 llama.cpp 的 `llama-quantize`,把 base 型別設成你要的新精度(例如 `Q8_0`),再用 `--tensor-type 名稱=型別` 把**其他不想動的張量分別指定回原本的型別**——目標型別跟現在一樣的張量會被逐位元組複製、不重壓,只有真的要改的張量會轉。動手前一定先加 `--dry-run` 預覽「實際會換幾張」。
- 為什麼不能把已經壓成 Q4 的張量再壓一次?
- 因為量化誤差不會互相抵消,只會疊加。從原始 BF16 一次量化到 Q4,跟先壓 Q6 再壓 Q4,結果不一樣、後者更糟。要做品質實驗,就從原始高精度權重直接量化到目標精度,別拿現成的量化檔再壓。
接著讀
- 2026-07-22[LLM 深水區] 一張 24GB 顯卡最強的免費開源模型?我押 ThinkingCap-Qwen3.6-27B——想得少、跑得快、不拒答
一張 24GB 消費級顯卡(含改裝 2080 Ti 22G)想跑免費開源模型,我目前的第一名是 Huihui-ThinkingCap-Qwen3.6-27B-abliterated:Q4_K_S 約 16GB、思考少一半、開 MTP ~38 tok/s、不拒答、全 Apache-2.0。
- 2026-04-15[LLM 深水區] 量化演算法在做什麼?從 Q4_K_M 到 TurboQuant 的三層拆解
Q4_K_M 用 4 bit 怎麼裝得下 14B 模型?答案不是「切掉 75%」,而是 K-quant 的 super-block 分組、TurboQuant 的隨機旋轉、跟 QJL 的 1-bit sign sketch 三層演算法。一篇講清楚機制,但不推公式。
- 2026-03-30[Benchmark] TurboQuant 實測:KV Cache 3-bit 壓縮,真的零損失?
Google TurboQuant 在 GX10 (GB10/SM121) 上的實測數據 — 3-bit KV cache 壓縮的真實壓縮率、Qwen2.5-3B 精度驗證、以及 Qwen3.5-35B 的 hybrid attention 架構為什麼讓事情變得複雜。
- 2026-07-22[洋垃圾跑大模型 #2] 一張 22G 老卡跑 118B 的 coding 大模型:Poolside Laguna S 2.1 實戰
Poolside Laguna S 2.1 是 118B-A8B 的 MoE coding 模型。我把它塞進單張改裝 2080 Ti 22G,用 CPU/GPU 混合 offload + 配套 DFlash 投機解碼跑到 ~29 tok/s,還靠一招 attention 換 Q8 省約 2.45 GiB、decode 再快 7%。
不想錯過新文章?
訂閱我確保不漏接!
隨時一鍵退訂。