閒置的矽晶片才是敵人
回想一下解碼是記憶體受限的:只服務一個使用者時,GPU 的運算單元餓著肚子等記憶體。經典解法是批次處理(batching)——把許多使用者的解碼步驟一起放進一次前向運算。權重從記憶體讀一次就在整個批次裡重複使用,所以在運算單元終於被填滿之前,多加使用者幾乎是免費的。
這就是 GPU 服務的整套經濟論證:一張卡回答一個人、與回答六十個人,運轉成本差不多,所以把批次塞滿,正是你壓低每個答案價格的方法。
你永遠逃不掉的取捨
批次越大,每秒能出的答案越多(吞吐量,throughput),但每一個答案可能要在其他答案後面等更久(延遲,latency)。吞吐量與延遲的取捨是服務的主旋鈕:聊天產品守護延遲,好讓回覆感覺即時;而過夜的文件處理工作則拉滿吞吐量,根本不看時鐘。
用户的总等待 = 首词元时间,加上其余词元按其每用户出词速率流式输出所需的时间。
具體來說,這就是批次推論與線上推論之分。線上(互動式)服務最佳化TTFT與每位使用者的TPS;批次(離線)服務則最佳化整個工作的每元詞元數。同一個模型,相反的調校。
靜態批次處理會浪費 GPU
最直覺的批次方式是靜態的:湊滿比方說 16 個請求,當成一組一起跑到 16 個全部完成,再開下一組。缺點是答案長度天差地遠。一個 20 詞元的回覆和一個 2,000 詞元的回覆在同一批裡,意味著短的早就跑完了,但它的位置卻被鎖住、空轉,直到長的那個結束為止。
連續批次處理:一道旋轉門
連續批次處理(又稱飛行中(in-flight)批次處理)的解法,是以單一詞元步驟而非整個請求為粒度來運作。每一步解碼之後,排程器都會檢查:有誰跑完了嗎?馬上釋放它的位置、當下就讓一個等待中的請求中途加入,不必清空整批。
批次變成一道旋轉門:完成的序列離開、新的進來,GPU 每一步都保持塞滿。在真實工作負載上,光這一個改動通常就讓吞吐量比靜態批次高出數倍,也正是一張卡能同時維持數百段對話的原因。它完全仰賴上一篇的分頁——靈活的GPU 記憶體管理,才能讓請求乾淨俐落地來來去去。
服務一群人時該量什麼
- TTFT(每個請求):第一個詞元串流出來前要多久。主要是預填充加上排隊等待。看 p95(第 95 百分位)而非只看平均——使用者抱怨的是尾端延遲。
- 每位使用者 TPS:每個人的文字串流多快。這必須輕鬆超過人類閱讀速度(約每秒 5 至 10 個詞元),否則回覆會感覺拖沓。
- 總吞吐量:卡上所有並行使用者加總的每秒詞元數——決定成本的數字。連續批次處理把它推高,又不毀掉上面那兩項。
- 記憶體上限下的並行數:在 KV 快取塞滿 GPU 之前能容納幾個請求。這封頂了批次大小,因此也封頂了其他一切。
总吞吐量就是同时服务的用户数乘以每位用户的出词速率——这正是把批次塞满能省钱的原因。