← 🎤 脫口秀
只用了 8 格,為什麼占 12 格?
用一個故事看懂 PagedAttention、Continuous Batching 與 vLLM

只用了 8 格,
為什麼占了 12 格?

星期五晚上,車站的置物櫃區同時來了六位旅客。每個人的行李都會越放越多,而且沒有人知道自己會待多久。老管理員的辦法是每人先鎖 12 格連號;新來的管理員阿頁說:不必連號,我有一張對照表。同樣 60 格櫃子,能進來的人數就不一樣了。

讀完大約 12 分鐘,每一章都有可以動手的地方。
格數、人數、輪數都是教學設定,瀏覽器會照規則即時算,但不是任何真實系統的實測。
這一篇講的是「很多人同時使用」時的記憶體與排隊問題,和前兩篇(KV Cache、FlashAttention)互補。

旅遊規劃助理

故事裡會出現的東西

行李越放越多,而且沒人知道你會待多久

先弄清楚:為什麼記憶體會被「對話」吃掉

先接上前兩集。模型每處理一個片段,都會在每層樓留下一對名牌和資料夾(K 和 V),讓後面的片段回頭比對用。這些東西不能丟,因為只要對話還在進行,下一個字就會用到它們。你可以把它們想成旅客的行李:每多聊一句,行李就多幾件,而且要放在櫃子裡隨時能拿。

置物櫃就是顯示卡上留給這些行李的記憶體。它有兩個特性讓管理變得棘手。第一,行李會一直變多,對話沒結束就不會停。第二,沒有人事先知道會多到哪裡:有的人問一句就走,有的人聊了半小時還在補條件。管理員收下第一件行李時,只能猜。

一位旅客的行李是怎麼變多的

這位旅客目前的行李0 格一格代表一頁 KV Cache,可以放固定數量的片段。

行李增加還有一個特別的性質:它只會往後面加,不會中途插進去,也不會拿掉前面的。所以管理員最自然的想法是:給每個人一整排連號的格子,新行李放到下一格就好,讀取時也從第一格一路讀到最後一格,不用東找西找。問題是,那一排要留多長?

回到電腦裡

行李就是 KV Cache:每個 token 在每一層的 K 和 V 向量。它的大小隨對話長度線性增加,對話越長、越多人同時用,占的顯示記憶體就越多;在多人服務的情境下,它常常是比模型權重更難管的那一部分。一格「頁」(block)可以放固定數量的 token,例如 16 個。傾向連號存放的原因是注意力運算讀取 K/V 時,連續的記憶體位址最單純、最快。

老管理員的辦法:每人先鎖 12 格連號

方便是方便,代價在人多的時候才看得見

老管理員的規則很簡單:不管你來的時候帶幾件,一律先鎖 12 格連號給你,因為 12 格是這座車站允許的最大行李量。這樣你之後怎麼加都放得下,讀取也永遠是連續的。他不用猜,也不用中途搬家。

代價藏在沒被用到的格子裡。你今天只帶了 8 件,剩下 4 格空著,但它們已經是你的,別人不能用。這 4 格不是「空閒」,是「沒有東西卻被占著」。

同一位旅客,8 件行李,兩位管理員各怎麼安排

老管理員:先鎖 12 格連號

行李放前面,後面的格子鎖著等你。

這位旅客占用
阿頁:有幾件給幾格

剩下的格子誰都可以用。

這位旅客占用
有行李沒行李,但已被鎖住(預留)空著,誰都能用

一個人少占 4 格,聽起來沒什麼。可是置物櫃區不是只有一個人在用。星期五晚上,六位旅客同時到了。

同樣 60 格,哪一位管理員能讓更多人進來

兩邊收到完全相同的旅客,依 A、B、C… 的順序入場。整份行李放得下才讓人進,放不下的在等待區,不會硬塞。

老管理員

每人一到就鎖 12 格連號。

入場
阿頁

照目前的行李量給格子。

入場

請試試「行李總量超過 60 格」:當每個人真的都帶了一大堆,阿頁也一樣要讓人等。她沒有把行李變小,她省下的只是那些沒有行李卻被鎖住的格子。資料真的需要多少空間,就還是需要多少。

回到電腦裡

老管理員的做法是依最大可能長度預先配置連續記憶體。沒用到的部分叫內部碎片(internal fragmentation);此外,不同長度的區段在記憶體裡進進出出,還會留下許多不連續的小空隙,明明加起來夠大卻塞不進一個新請求,這叫外部碎片。PagedAttention 的論文估計,這類浪費在某些既有系統裡讓 KV Cache 記憶體的實際利用率只剩兩、三成。這裡的示範只畫預留造成的內部碎片,並假設一格剛好裝滿。

阿頁的辦法:不必連號,我有一張對照表

PagedAttention 到底改了什麼

老管理員堅持連號,是因為他相信讀取時必須從頭一路讀到尾。阿頁的想法是:連號不是必要的,找得到就好。她把每位旅客的行李切成一頁一頁(每頁固定大小),來一頁就從整座櫃子裡找任何一個空格放進去,然後在自己的對照表上記一筆:「這位旅客的第 3 頁,放在 1 號格」。要讀的時候查表,按頁的順序一格一格去拿。

這樣一來,每個人只占用自己真正需要的格數,格子也不必挨在一起,整座櫃子就像一個共用的池子,誰需要就從池子裡拿。

阿頁的對照表:點任何一頁,看它實際放在哪一格

藍色的是旅客 A 的頁,淡色的是別的旅客的,虛線是空格。新頁會放進任何一個空格。

旅客 A 的對照表(block table)
閱讀順序實際位置

還有一個細節要老實說:阿頁的做法也不是零浪費。每頁固定大小,旅客最後一頁常常沒有裝滿,那一點空間還是白占著。但這個浪費最多只有一頁,不會像老管理員那樣一次浪費 4 格、甚至 11 格。

你可能會問:對照表要另外查,讀取會不會變慢?會多一點工,所以 PagedAttention 不只是「換一種分配方式」,它同時包含一套能照對照表讀取分散頁面的注意力運算。這也是它名字裡有 Attention 的原因:光改分配不改讀法,注意力運算會找不到資料。

回到電腦裡

這個想法直接借自作業系統的虛擬記憶體分頁:程式看到的是連續的頁號,實際存放在哪個實體框架由頁表決定。PagedAttention 把 KV Cache 切成固定大小的 block(每個放例如 16 個 token 的 K/V),用 block table 記錄每個請求的邏輯頁到實體頁的對應,注意力核心依表讀取。浪費被限制在每個請求最後一頁未填滿的部分。附帶的好處是共用:同一段系統提示詞的頁,可以讓多個請求指向同一批實體頁,這裡沒有演。

空間管好了,還要決定誰先做

Continuous Batching,以及把這些整合起來的 vLLM

櫃子的問題解決了,但置物櫃區還有另一個瓶頸:服務窗口只有三個。每一輪,三個窗口各替一位旅客處理一步(模型每一輪替每個對話多生成一個字)。旅客的需求長短不同:A 要辦 6 輪,B 只要 2 輪。

老規矩是固定批次:三位一組,這一組全部辦完,下一組才能進來。B 兩輪就辦完了,他的窗口卻空著,因為 A 還在辦。Continuous Batching(動態批次)改成每一輪結束就重新看一次:哪個窗口空了,排隊的人下一輪就補進去。

六位旅客、三個窗口,逐輪看差別

目前:準備開始快轉到:
固定批次:整組辦完才換下一組

有人先辦完,窗口也只能空著等。

Continuous Batching:有空位,下一輪就補人

不必等最慢的那位。

窗口空著等同組的人綠框=這一輪剛補進來「完成」=本輪辦完,下一輪離開
把每一輪攤開來看

右邊不是把 A 辦得更快,A 一樣要 6 輪;它只是不讓空出來的窗口閒著。這個模擬假設每輪每位旅客都剛好前進一步,省略了新旅客進場時要先處理整段前文(Prefill)的時間、也不考慮記憶體夠不夠,所以「8 輪對 6 輪」是排程示意,不是效能倍數。

兩件事各管各的:阿頁的對照表管的是櫃子空間,動態批次管的是窗口排程。它們互補,但不是同一項技術;不過在實務上兩者常常一起出現,因為動態批次要能隨時讓新人進場,前提正是櫃子不會被大片預留卡死。

vLLM:把這些整合成一套服務

到這裡,再看 vLLM 就不會只是一串名詞。它是一個推論引擎:接收很多人的請求,用分頁的方式管理 KV Cache,每一輪重新安排批次,再交給 GPU 執行模型運算,並依環境選擇注意力的實作(前一集的 FlashAttention 是其中一種)。

旅客
很多人的請求同時進來,每個人的內容和長度都不同。
vLLM(推論引擎)
PagedAttention:分頁管理 KV Cache,依對照表讀取Continuous Batching:每輪重排批次,補進新請求注意力後端:依環境選用,例如 FlashAttention
GPU
執行模型運算,每輪替每個對話多產生一個字。

最重要的一句話:這些做法都不會讓模型變聰明。同一份模型權重,換到 vLLM 上跑,回答的品質不會變好;變好的是同樣的硬體能服務更多人、等待時間更短、記憶體浪費更少。另一個常混淆的對照:模型量化壓的是權重的大小,PagedAttention 管的是KV Cache的空間,兩者解決的不是同一件事。

KV Cache

解決:重算把算過的名牌和資料夾留下來。代價是對話越長占越多記憶體。

PagedAttention

解決:空間浪費切成固定大小的頁、按需分配、靠對照表讀取分散的頁。浪費最多只剩最後一頁。

Continuous Batching

解決:窗口空等每輪重新安排批次,有人辦完就讓排隊的人補進來。

vLLM

把上面的整合起來推論引擎,不是模型;提升吞吐與記憶體利用率,不改變回答品質。
故事裡的真正的名稱一句話說明
旅客請求(request)一段正在進行的對話
行李KV Cache每個 token 在每層的 K、V,隨對話長度線性增加
一格block(頁)固定容量,例如 16 個 token 的 K/V
置物櫃區留給 KV Cache 的 GPU 記憶體多人共用的池子
先鎖 12 格連號依最大長度預先配置連續記憶體造成內部碎片(沒資料卻占著)與外部碎片
對照表block table每個請求的邏輯頁 → 實體頁
照表讀取分散的頁PagedAttention 的注意力核心不改讀法,改分配沒有用
三個窗口、一輪一步批次名額、一個生成步驟「窗口」是名額的比喻,不是實體 GPU 核心
整組辦完才換Static Batching(固定批次)先完成的名額空等
每輪補人Continuous Batching(動態批次)以生成步驟為單位重排,源自 Orca 的想法
置物櫃區的整套管理vLLM推論引擎,整合以上能力

故事沒有說完的部分

這一頁的置物櫃只有 60 格、一格等於一頁、行李剛好填滿每一頁;真實系統一頁裝固定數量的 token,最後一頁通常沒滿,而且還要留空間給模型權重和運算中的暫存。批次模擬也省略了 Prefill 的時間、每輪耗時的差異和記憶體是否夠用。

PagedAttention 還有幾個延伸這裡沒演:多個請求共用同一段前綴(例如相同的系統提示詞)的頁、記憶體不足時把某些請求的頁先換出去再換回來,以及不同排程策略的取捨。這些都建立在「頁可以分散、靠對照表找」這個基礎上。

四個問題,確認真的看懂

選了之後會告訴你原因

延伸閱讀

  1. Efficient Memory Management for Large Language Model Serving with PagedAttention(原始論文:碎片、分頁、block table、共用與換出)
  2. vLLM:Introducing PagedAttention(按需分配、非連續頁面、最後一頁的浪費)
  3. Orca: A Distributed Serving System for Transformer-Based Generative Models(以生成步驟為單位的批次排程)
  4. vLLM 官方文件(推論引擎、快取管理與批次)
  5. Hugging Face:How caching works(KV Cache 的用途與成長)

本頁所有格數、入場人數、輪數皆由瀏覽器依教學規則即時計算,不呼叫任何模型或服務,也不傳送資料。