AI 互動教室 ‹ 生成式 AI 導論
下載 .py 單獨開啟實驗場 ↗ 留言回報
GENAI BASICS · 03 · 推理效率與部署

推理加速:
KV Cache、量化與 vLLM

上一課看完模型怎麼練成,這一課換一個問題:練好的模型,怎麼跑得動、跑得快? 先從最實際的一題開始——這顆模型塞得進你的顯卡嗎?點點看(真算術:參數 × 每參數位元組):

模型
精度
顯卡
權重帳=參數數 × 每參數位元組。實際還要留 KV cache 與 activation 的空間——所以「剛好塞下」通常等於「跑不動長上下文」。

右邊的實驗場是真的 Python(在你的瀏覽器裡跑,不用安裝任何東西)。 首次載入約需 30–60 秒,正好夠你讀完第 1 節。這一課每個名詞都有一筆算得出來的帳, 全部在右邊真的算。

01 · KV CACHE + PAGEDATTENTION

KV Cache:長上下文的記憶體黑洞

一句話重點:把算過的 Key/Value 存起來避免每步重算——代價是 上下文越長、人越多,這塊記憶體越大;vLLM 的 PagedAttention 用「分頁」管它。

第 1 課看過自迴歸:每生成一個 token 都要回頭看整段前文。不快取的話, 第 1000 個 token 要重算前面 999 個的注意力——所以引擎把每個 token 的 K/V 存下來。 這塊快取的大小是一條純架構公式,8B 級 GQA 模型算出來 128 KB/token

場景(8B 級、fp16 KV)KV cache對照
8k 上下文 × 1 人1.0 GB小意思
128k 上下文 × 1 人16.0 GB比模型權重(15 GB)還大
128k × 8 人128 GB單卡完全無望

還有第二個問題:每個請求長度不一、邊聊邊長,記憶體怎麼配?早期引擎替每個請求 預留最長長度的連續空間,大部分空著。PagedAttention(vLLM 的招牌) 借作業系統分頁的概念:KV 切成小塊、用到才配。這是 vLLM 吞吐量領先的核心原因, 一行指令就用得到:

pip install vllm vllm serve meta-llama/Llama-3.1-8B-Instruct # 之後用任何 OpenAI SDK 打 http://localhost:8000/v1 就行
02 · QUANTIZATION

量化:fp16 → int4,檔案砍四分之三

一句話重點:把權重從 16 bit 壓成 4~8 bit 整數,換記憶體與速度; 主流格式 GPTQ/AWQ/GGUF 都靠「分組量化」躲開離群值災難。

8B 模型三種精度的檔案大小(真算術):fp16 15.0 GB、int8 7.5 GB、 int4 3.7 GB——消費級顯卡跑得動 8B,靠的就是這一刀。 代價是捨入誤差,而誤差最大的敵人是離群值:LLM 權重裡真的有少數特別大的值, 一個就能把整張量的量化刻度撐爆(右邊 2️⃣ 開個開關就看到誤差跳 5 倍)。 解法是分組量化:幾十到一百多個權重一組、各存各的縮放係數,災難被隔離在組內。

你平常已經在用它了——Ollama 模型名尾巴的 q4_K_M 就是 GGUF 的 4-bit 分組量化:

ollama run llama3.1:8b-instruct-q4_K_M # GGUF int4,筆電就跑得動

用 transformers 載模型時要 4-bit,也是一個設定的事(bitsandbytes):

from transformers import AutoModelForCausalLM, BitsAndBytesConfig model = AutoModelForCausalLM.from_pretrained( "meta-llama/Llama-3.1-8B-Instruct", quantization_config=BitsAndBytesConfig(load_in_4bit=True), )
03 · SPECULATIVE DECODING

投機解碼:小模型先猜、大模型批次驗證

一句話重點:生成是串行的,但驗證可以平行——小模型連猜 k 個, 大模型一次驗完,輸出分布完全不變的免費加速。

自迴歸的痛在串行:第 N 個 token 一定要等第 N−1 個。投機解碼的洞見是: 讓一個小 10 倍以上的「草稿模型」先連猜 4 個,大模型一次前向驗收—— 猜對照收、猜錯的位置重來。接受率 α = 0.8 時,每次大模型前向平均產出 3.36 個 token(α = 0.9 → 4.10 個;教學模型,右邊 3️⃣ 有封閉式與蒙地卡羅互驗)。 程式碼、JSON 這類可預測文字 α 高、賺最多;創意寫作 α 低、不划算。

04 · FLASH ATTENTION + CONTINUOUS BATCHING

把 GPU 餵飽:kernel 層與排程層

一句話重點:Flash Attention 在 kernel 層省記憶體搬運、 Continuous Batching 在排程層隨時補位——兩層一起拉高吞吐量。

Flash Attention 重排注意力的計算順序,避免把巨大的中間矩陣搬進搬出顯存—— 數學結果一模一樣,只是快、省。transformers 裡是一個參數:

model = AutoModelForCausalLM.from_pretrained( "meta-llama/Llama-3.1-8B-Instruct", attn_implementation="flash_attention_2", )

Continuous batching 解的是另一層浪費:靜態批次要等整批全部寫完才換下一批, 先寫完的請求佔位空轉。右邊 4️⃣ 的排程模擬(教學模型)裡,同一批請求、同一顆 GPU, 靜態批次的槽位利用率只有 62–64%,連續批次逼近 100%—— vLLM 預設就是連續批次,這也是「自架服務吞吐差三倍」常見的真正原因。

05 · MOE + MLA

MoE:參數很多,每次只用一點

一句話重點:兆級參數只啟用一小部分——記憶體照總參數付、 計算照啟用參數付;MLA 再把 KV cache 壓小,兩個瓶頸一起解。

MoE(Mixture of Experts)把每層的 FFN 換成一排「專家」,路由器替每個 token 挑 2 個。 Mixtral 8x7B 的帳(右邊 5️⃣ 用真實架構規格算):總參數 46.7B、 每 token 實際啟用 12.9B(28%)。DeepSeek-V3 推到 671B/37B 啟用, 再配 MLA(Multi-head Latent Attention:把 K/V 先壓進低秩空間、用時再展開, KV cache 大幅縮小)——這對組合就是「怎麼用有限算力跑兆級模型」的答案。

想深入這一課的每個主題?本站的 local-llm 系列有整堂的 KV Cache投機解碼引擎選型可以接著上。

06 · 速查

本課名詞速查卡

發講義用的濃縮版——一個名詞一句話:

KV Cache 快取算過的 Key/Value 避免重算;長上下文會暴增(8B 級 128 KB/token,128k → 16 GB)。
PagedAttention (vLLM) 用作業系統「分頁」概念管理 KV cache,記憶體浪費率大降——vLLM 吞吐領先的核心。
Quantization fp16 → int8/int4 壓縮(8B:15 → 3.7 GB);主流 GPTQ/AWQ/GGUF 都用分組量化防離群值。
Speculative Decoding 小模型先猜、大模型批次驗證——串行變並行、輸出分布不變的加速。
Flash Attention /
Continuous Batching
kernel 層省搬運+排程層隨時補位,一起拉高吞吐量。
MoE / MLA 兆級參數只啟用一部分(Mixtral 46.7B→12.9B);MLA 低秩壓 KV——算力與 KV 兩個瓶頸的解法。
07 · 實戰

換你動手

LEVEL 1

在右邊實驗區把 bytes_per 換成 2/1/0.5 各跑一次:12 GB 的卡裝得下哪些組合?裝下之後還剩多少 token 的 KV 空間?

LEVEL 2

把 3️⃣ 的 α 拉到 0.95,每輪能收幾個 token?再想一層:如果草稿模型不夠小(用 7B 幫 8B 猜),這筆帳為什麼會垮掉?

LEVEL 3

「壓 KV」你其實學過三條路了(型別、head 數、MLA)。用 1️⃣ 的公式各算一次 128k 上下文的 GB 數,排出省最多到省最少,並說出各自的代價。

卡住了?三題在 notebook 最後一格都有折疊解答——先自己做,再打開對照。

08 · 驗收

情境測驗

離開前試試看:下面的情境都真的會遇到。每題選一個你認為的最佳做法,選了馬上看得到解釋。

Q1 情境題

你只有一張 16 GB 的顯卡,主管問「能不能把 70B 的模型跑起來?聽說量化很厲害」。最誠實的回答是?

先算帳再回答:70.6B × 0.5 byte ≈ 33 GB,int4 已經是常用格式裡最狠的一刀,仍是 16 GB 的兩倍——量化能砍 4 倍,不能變魔術。C 搞錯對象:縮上下文省的是 KV cache,權重一個位元組都不會少;D 也一樣,Flash Attention 省的是注意力的中間結果搬運,不動權重。B 是工程上的正解:需求先對齊資源(8B int4 只要 3.7 GB,16 GB 卡綽綽有餘),真要 70B 就多卡或雲端。

Q2 錯誤診斷

同事在 24 GB 的 RTX 4090 上跑 8B fp16 模型,短對話都正常,一貼長文件(接近 128k token)就 OOM。他說「8B 才 15 GB,24 GB 的卡怎麼可能不夠」。他漏算了什麼?

權重:8.03B 參數 × 2 bytes ≈ 15 GB KV:128k tokens × 128 KB/token ≈ 16 GB 合計 ≈ 31 GB > 24 GB

「模型多大」和「跑起來要多大」是兩筆帳:權重固定 15 GB,KV cache 卻跟上下文成正比——128 KB/token × 128k = 16 GB,長文件一貼,兩筆相加 31 GB 直接爆掉 24 GB。這也解釋了為什麼短對話沒事(KV 只有零點幾 GB)。A、D 都是編故事,OOM 是記憶體帳算得出來的必然;B 方向錯誤,fp16 權重就是參數數 × 2 bytes。修法照優先序:KV 量化成 fp8(16 → 8 GB)、權重量化、或乾脆限制服務的最長上下文。

Q3 情境題

你們自架的聊天服務尖峰時段回應慢,但監控顯示 GPU 使用率長期只有五六成;請求有長有短、隨到隨來。最該先動的是?

「慢,但 GPU 很閒」是排程問題的招牌症狀:靜態批次要等整批最長的請求寫完才換批,先寫完的槽位全在空轉——本課模擬裡同一批請求靜態批次只有 62–64% 利用率、連續批次逼近滿載。A 是花錢買第二台一樣塞車的機器;B 解的是「GPU 太忙」,你的 GPU 是太閒;D 犧牲產品體驗去遷就排程器,本末倒置。先把排程層修好(vLLM 預設就是 continuous batching),不夠再談加卡。

Q4 錯誤診斷

同事自己寫了個「整張量 absmax」腳本把模型壓成 int4,壓完模型輸出品質明顯變爛。他量了誤差(如下),結論是「int4 本來就不能用,只能回 int8」。哪裡想錯了?

int4 全張量(無離群值的理想權重): 平均誤差 0.002806 int4 全張量(含一個 0.5 離群值) : 平均誤差 0.014920 ← 他的情況 int4 128個一組分組量化(同樣權重): 平均誤差 0.002395

數據自己會說話:同樣的權重、同樣 4 bit,分組量化的誤差(0.0024)比他的全張量版(0.0149)低了 6 倍,甚至比「沒有離群值的理想情況」還好——可見兇手是「一條刻度伺候整張量」,離群值把刻度撐大、其他幾千個小權重全被犧牲。這正是主流格式全都做分組量化的原因。A 把實作缺陷當物理極限;C 無中生有;D 低估了誤差的意義——0.0149 已經接近權重本身的尺度(0.02),等於把訊號淹掉了。正解:別自己造量化輪子,用現成的 GPTQ/AWQ/GGUF 格式。

Python 環境載入中(首次約 30–60 秒)…讀完第 1 節它就好了