JOVANA
Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

以低延遲為模型提供服務

一個訓練好的模型,若不能及時給出答案,就毫無用處。本指南從推理與訓練的區別講起,一路走到批次處理、用於檢索的向量資料庫,以及把模型推到邊緣。

推理是和訓練不同的工作

到現在你已經訓練過模型:你在資料集上跑了 梯度下降,看著 損失 下降,並儲存了 權重。提供服務是生命週期的另一半,而 訓練與推理之間的區別 影響深遠。訓練只發生一次(或定期一次),在離線的大型任務裡進行,你樂意等上幾個小時;推理則要發生上百萬次,線上進行,此時有人或別的系統正等著這個答案。

推理時沒有反向傳播:沒有 反向傳播,沒有梯度,沒有優化器狀態。你只是跑一遍前向傳播,把輸出讀出來。這聽上去更便宜,單次確實如此——但經濟帳反了過來,因為你要持續不斷地為它付錢。一個只訓練兩週的模型,之後可能服務好幾年,所以整個生命週期的 推理成本 通常遠遠蓋過訓練帳單。

延遲和吞吐量朝相反方向拉扯

兩個數字主宰著一個服務系統,而 延遲與吞吐量之間的張力 正是核心的權衡。延遲是單個請求要花多久——單個使用者感受到的等待。吞吐量是你每秒在所有使用者之間完成多少個請求。它們不是一回事,而且壓住一個往往會傷到另一個。

延遲要報成一個分布,而不是平均值。真正要緊的通常是尾部百分位——p99,也就是最慢的那 1% 請求——因為那才是你最倒霉的使用者感受到的,而平均值會悄悄把它掩蓋掉。一個服務平均看上去很快,卻可能有十分之一的使用者在等兩秒。設一個*預算*(比如 p99 低於 200 毫秒),並把每一項最佳化都看成在為這個預算買出餘量。

為什麼會拔河?一塊 GPU 最開心的事是一口氣做一次大的 矩陣乘法。如果一次只處理一個請求,晶片在請求之間大半時間都閒著——吞吐量低,但每個答案回得很快。把請求湊在一起,晶片就能一直忙著——吞吐量高——可第一個請求現在得等它的同伴們。這種湊批是服務中最大的那根槓桿,所以它單獨佔一節。

批次處理:餵飽晶片,留意等待

請求批次處理 把若干輸入疊成一個張量,讓它們一起穿過網路。因為權重只從記憶體裡載入一次、然後被整個批次重用,你每搬動一個位元組就能做更多有用的計算——而在現代加速器上,瓶頸通常是搬運記憶體,而不是做算術。批次越大,吞吐量越好,一直好到你記憶體用盡、或撐爆延遲預算為止。

最簡單的做法叫*靜態批次處理*:等到湊齊 N 個請求(或到一個很短的逾時)再一起跑。但對一個逐 token 生成的 大型語言模型 來說,靜態批次處理很浪費:短回覆早早結束,它們的槽位就空著,而最長的那條回覆還在拖。*連續批次處理*解決了這一點——在每一步把一個已完成的請求換出、一個新請求換入,讓批次始終滿載。這一點,再加上把過去注意力的鍵和值存起來、使每個新 token 都很便宜的 KV 快取,正是現代 LLM 伺服器能達到那種吞吐量的原因。

static batching:    [req A]......done  (slot idle)
                    [req B]...............done
                    waste = idle slots while B finishes

continuous batching: A finishes -> C jumps into A's slot
                     batch stays full every step
連續批次處理逐步填補空槽,而不是等整批一起結束。

向量資料庫:提供的不只是權重,還有事實

模型的 權重 在訓練時就被凍結了,所以它不可能知道今天的新聞或你的私有文件——而要它回憶具體細節,反而招來 自信滿滿的編造。解法是在請求發生時*檢索*相關文本,把它作為上下文交給模型。這就是 檢索增強生成,它的引擎就是向量資料庫。

機制是這樣的。每篇文件都被轉成一個 嵌入向量——一個把含義相近者在空間中放得彼此靠近的向量。一個 向量資料庫 存下上百萬個這樣的向量,並極快地回答一個問題:*哪些已存的向量離這條查詢向量最近?*在嵌入的幾何裡最近,就意味著含義最接近,於是你取回的是真正相關的段落,而不僅僅是共享關鍵詞的那些。

在大規模下精確掃描每個向量太慢了,所以這些資料庫使用近似最近鄰索引(基於圖的 HNSW 之類很常見)。它們用很小一部分時間返回*幾乎*最優的匹配——一次刻意用精度換速度的取捨,正是你在服務裡反覆遇到的那筆交易。為了低延遲,檢索這一步有它自己的預算:把索引放在記憶體裡,並記住你塞滿檢索文本的 上下文視窗 不是免費的,因為每多一個 token,都會在每個生成的 token 上花掉算力。

邊緣部署:把模型搬到資料旁邊

到目前為止我們都假設模型住在資料中心裡。邊緣部署 把這一點反了過來:模型跑在手機、攝影機、汽車、感測器上——就在資料誕生的地方。動機很少只是單純的速度。它省掉的是一次往返(不必跨網路跳到伺服器)、是你從不外發的資料(隱私,而且能離線工作),以及你不再支付的頻寬和伺服器帳單。

難處在於預算。一台邊緣裝置的記憶體和功耗只是資料中心 GPU 的零頭,所以模型往往得先瘦身——透過 量化(把權重存成 8 位元或 4 位元,而不是 32 位元)以及下一篇會講的其他 壓縮 技巧。然後一個可移植的執行環境,比如 ONNX Runtime,讓同一個匯出的模型能在五花八門的邊緣晶片上跑。精度降低可能帶來一點準確率下滑,所以你要去測量它——絕不要假設它是免費的。

把它們串起來

低延遲服務是一連串誠實的權衡,而不是某個單一的妙招。你從一個凍結的模型出發,針對要緊的那個百分位設定延遲預算,然後有意識地把它花掉:用批次處理餵飽晶片,用快取避免重複勞動,用檢索把答案錨定在新鮮的事實上,並在往返本身就是成本時壓縮模型或把它搬到邊緣。這些沒有一個是銀彈——每一個都是用付出另一樣東西來換取一樣東西。

而且把服務上線並不是終點線。真實世界的輸入會隨時間漸漸偏離你的訓練分布,所以接下來的指南會講如何把模型進一步瘦身,以及用 監控 盯住它、提防 資料漂移與概念漂移。一個服務很快卻悄悄出錯的模型,比一個慢的還要糟——只有在答案始終正確的前提下,速度才值得去追。