自架 LLM 5 人同時使用就卡住?原因與解法
一人使用正常,五人同時就變慢?了解 Ollama 預設平行請求數為 1,以及 batching、KV cache、prefill、decode 與佇列深度如何決定 LLM 伺服器的同時服務量。
為什麼自架 LLM 在使用者增加後會變慢?
自架 LLM 在同時有 5 名使用者時停滯,原因是伺服器仍一次只產生一則回覆,其他 4 名使用者只能排隊等待。Ollama 文件明確說明預設值:OLLAMA_NUM_PARALLEL 是「每個模型同時處理的平行請求數上限,預設為 1」。這不是故障。5 名使用者中有 4 名正在等待處理。
解決方案通常不是升級更大的主機,而是改用能在同一次 forward pass 中處理多個請求的 serving engine,並準備足夠的額外記憶體,在處理期間容納所有使用者的對話。兩個部分都很重要,而真正決定上限的是後者。
每個請求都會經過的兩個階段
Prefill 會一次讀取完整提示,並為其建立 attention cache。提示中的所有 token 會一同通過模型,因此 prefill 是一次大型矩陣乘法,效能受算術運算吞吐量限制。接著,Decode 會逐一寫出回答中的 token。每個 token 都需要再次從記憶體讀取模型的完整權重,但針對單一 token 執行的算術運算量很小。因此,decode 受記憶體頻寬限制。
這種不對稱性正是 batching 有效的原因。為單一使用者執行 decode 時,每個 token 可能都要讀取 5 GB 的權重,卻讓大多數算術運算單元閒置。加入第二個請求後,引擎只需讀取同一份 5 GB 權重一次,接著用這些權重計算兩個 token。第二位使用者幾乎不會增加額外處理時間。若嚴格逐一處理請求,就會失去這項優勢。
兩個數值可描述使用者的實際感受。TTFT (time to first token) 是佇列等待時間加上 prefill 時間。ITL (inter-token latency) 是串流 token 之間的間隔,取決於 decode。伺服器速度緩慢時,通常是其中一項指標較差,而兩者所需的改善方式不同。
靜態批次處理會讓所有請求等待最慢的回應
靜態批次處理是最基本的做法。若由應用程式程式碼自行分組請求,就會得到這種行為。引擎收集 N 個請求後一起執行,並持續占用每個 slot,直到批次中生成時間最長的請求完成。
一位使用者要求生成 1,200 個 token 的摘要,會讓四個只有一行的回答持續占用批次,因為批次必須等最慢的成員完成後,才會釋放 slot。
這會產生兩項成本。已完成的序列仍持續占用 slot,卻不再進行有用的計算。因此,當輸出長度不一致時,有效吞吐量會下降;而聊天的輸出長度通常差異很大。批次建立後才晚一個 step 到達的請求,必須等待整個批次排空後才能開始 prefill。這表示它的 TTFT 取決於其他請求生成的長篇內容。
連續批次處理會在每個 token 逐一接收與完成請求
連續批次處理以單次解碼步驟為排程單位。每個步驟結束後,排程器會移除剛輸出停止 token 的序列,然後將等待中的請求加入空閒槽位。在第 40 步結束的回應會在第 40 步釋放其槽位,而不是等到整個批次結束。
這並不罕見。llama-server 將 -cb, --cont-batching 說明為「是否啟用連續批次處理(又稱動態批次處理)(預設:啟用)」,而 vLLM 的設計本身就以此為核心。Ollama 也能平行處理請求。預設值只是將數量限制為 1,因此許多人會以為硬體無法支援並行處理,實際上是設定拒絕了並行處理。
已發表的連續批次處理結果通常是在同時具備剩餘運算能力與數十 GB cache 空間的資料中心卡上測量。這些結果的趨勢可以套用到你的主機,但效能提升幅度不能直接套用。原因如下方的記憶體一節所述。
Prefill 會與 decode 爭用相同的運算資源
當 4 個回覆正在串流輸出時收到新請求,系統必須先完成該請求的 prompt prefill,而 prefill 需要大量運算資源。如果 scheduler 為這次 prefill 單獨安排一個 step,4 位串流使用者在此期間都不會收到 token。對於較長的 prompt,所有已開啟的視窗都會出現明顯停頓。這就是有人按下送出時伺服器發生卡頓的情況。
Chunked prefill 會將較長的 prompt 切成多個片段,並將每個片段與正在執行的 decode 混入同一個 step。vLLM 的 tuning guide 直接說明了這項取捨:較小的 chunk budget「因為減少會拖慢 decode 的 prefill,所以能達到較佳的 ITL」,較高的值則「能在一個 batch 中處理更多 prefill token,因此能達到較佳的首次 token 時間(TTFT)」。你必須選擇要優先保障哪一方的體驗:等待回覆開始的人,或正在觀看文字串流輸出的人。
Prompt 長度會決定影響程度。1 個 6,000 token 的 prompt 搭配 1 個 200 token 的答案,代表必須執行 6,000 token 的 prefill 工作,對比只有 200 個 decode step。檢索增強式聊天與較長的 system prompt 都會使系統進入這種情況,因此 prefill 不再只是可忽略的差異,而會成為使用者必須等待的主要工作。當較長的部分會重複出現時,prefix caching 能提供協助:vLLM 提供 --enable-prefix-caching,可重複使用共用 prompt prefix 的 cache,而不必為每個請求重新計算。
最先耗盡的是 KV cache
每個作用中對話中的每個 token,都會在模型的每一層留下 key vector 和 value vector。這就是 KV cache(key/value cache),可讓解碼程序在產生每個新 token 時,避免重新計算整個提示內容。每個 token 所需的大小由模型架構固定:2(1 個 key、1 個 value)乘以層數,再乘以 key/value head 數量、head 維度,以及每個值所占的位元組數。請從模型的 config.json 讀取這些數值。
實際計算一次後,上限就不再難以判斷。以常見的 8B 模型為例,模型有 36 層、8 個 key/value head,head 維度為 128,並以 16-bit 儲存 cache。每個 token 需要 2 36 8 128 2 bytes,也就是 147,456 bytes,約 144 KiB。因此,單一 8,192 token 對話約需 1.2 GB 的 cache。5 個對話約需 6 GB,還不包括模型權重;這才是能容納多少使用者的實際答案。
並行處理會按比例增加 context,工具的說明也直接指出這一點。Ollama 的 FAQ:「對指定模型進行平行請求處理時,context size 會依平行請求數量增加。例如,2K context 搭配 4 個平行請求,會形成 8K context,並額外配置記憶體。」所需 RAM 會依 OLLAMA_NUM_PARALLEL 乘以 OLLAMA_CONTEXT_LENGTH 擴展。在 llama-server 中,透過 -c 要求的 context 會由 -np 個 slot 分配使用,因此單獨提高 slot 數量,反而會縮小每個請求可容納的內容。請從啟動日誌讀取每個 slot 的 context,不要自行假設。
vLLM 則會預先配置記憶體。--gpu-memory-utilization(預設為 0.92)代表「供 model executor 使用的 GPU 記憶體比例」。扣除模型權重後剩餘的記憶體會成為分頁式 KV pool;當該 pool 空間不足時,scheduler 會逐出請求,而不是讓請求失敗:
WARNING 05-09 00:49:33 scheduler.py:1057] Sequence group 0 is preempted by PreemptionMode.RECOMPUTE mode because there is not enough KV cache space.在 vLLM 的 V1 engine 中,預設的 preemption 模式是 RECOMPUTE,因此被逐出的請求會捨棄其 cache,重新加入排程時再執行 prefill。這些工作會執行 2 次。文件警告:「preemption 和 recomputation 可能對端到端延遲造成不利影響。」這行日誌最能說明為何某個不走運的使用者等待時間遠高於其他人,而平均延遲看起來仍然正常。設定 disable_log_stats=False 以記錄累計次數,或從 vLLM 提供的 Prometheus metrics 讀取 preemption counter。
2、5 與 20 位同時使用者的變化
2 位使用者。 如果 GPU 還有充足的快取空間,幾乎不會感覺到差異,因為第二個解碼串流只需在第一個串流上額外耗用很少時間。在僅使用 CPU 且配備 4 到 8 GB RAM 的 VPS 上則不是如此:兩個串流會共用少量 vCPU 與相同的 RAM 頻寬,因此每位使用者看到的每秒 token 數大約會減半,而快取需求則會增加一倍,超出原本就小得多的可用容量。
5 位使用者。 這時預設值已不足以應付,問題會先表現為佇列問題。當 OLLAMA_NUM_PARALLEL 設為 1 時,4 個人會等待提出長回答要求的使用者;輪到自己處理時,每個人都能恢復正常速度。提高平行處理數量後,問題會轉變:5 個工作槽、每個使用 8K context,代表需要容納 40K token 的快取。如果 VRAM 放不下,推論引擎會將部分 layer 卸載到系統 RAM;如果 RAM 也放不下,主機就會使用 swap,導致每秒 token 數大幅下降。
20 位使用者。 在聊天 UI 中,20 位人類使用者通常不代表 20 個同時處理的請求。這是購買硬體前最值得先了解的重點。使用者讀完回覆後,通常會等待 20 到 60 秒才提出下一個要求,因此其工作階段大多處於閒置狀態。20 個 agent 或 20 個文件摘要工作,則代表 20 個完全沒有閒置時間的實際串流。這需要的是不同等級的機器。
使用者是並行連線,還是只有登入?
在決定硬體規模前,先計算同時處理中的請求數。計算方式很簡單:處理中的請求數等於使用者人數乘以每輪生成所需的秒數,再除以每輪之間的秒數。
- 先測量單一串流的實際速度,同時測量 prefill 與 decode。不要直接套用他人顯示卡的數據:在自己的主機上測量每秒 token 數,並使用實際測得的結果。
- 估算使用率。20 位聊天使用者,每輪生成需要 12 秒,每 90 秒進行一輪,計算為 20 * 12 / 90,約有 2.7 個處理中的請求。
- 將 slot 數設定為略高於此值,然後檢查記憶體:slot 數乘以每個請求的 context 大小,必須容納在實際可用的 cache token 數內。
- 將佇列維持在較短的長度,讓超出的請求快速且明確地失敗。
可用的 cache token 數,是權重載入後的可用記憶體除以前一節所述的每 token 成本。一張 24 GB 顯示卡執行 8B model、使用 16-bit 時,權重約佔 16 GB;在預設使用率下,約有 6 GB 可用於 cache,約可容納 5 個 8K 對話。若要容納更多對話,請縮短每個請求的 context,或將 cache 儲存為 8-bit(llama-server 需要 --cache-type-k q8_0)。這兩種方式都能以犧牲部分條件換取並行處理能力。在投入硬體成本前,值得先閱讀這項取捨的實際情況:GPU VPS 何時相較 API token 損益兩平】【。
Ollama 的預設值不敷使用時
透過 service unit 提高平行處理數量,因為 shell export 不會傳遞至由 systemd 管理的 daemon。
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_NUM_PARALLEL=4"
Environment="OLLAMA_CONTEXT_LENGTH=8192"
Environment="OLLAMA_MAX_QUEUE=64"sudo systemctl daemon-reload
sudo systemctl restart ollama
systemctl show ollama --property=Environment
ollama pssystemctl show 應列出你剛設定的 3 個變數。如果沒有列出,表示 drop-in 未成功儲存,後續操作都不會生效。接著,ollama ps 會列出已載入的模型;顯示的大小會大於單獨的權重大小,因為 4 個 slot 各配置 8,192 個 token,會另外保留 32,768 個 token 的 cache。如果你原本預期模型全部載入 GPU,但 PROCESSOR 欄顯示部分模型位於 CPU,表示你要求的 cache 超過顯示卡剩餘容量。請降低其中一個數值。
queue 的預設值也值得重新檢視。Ollama 最多會佇列 OLLAMA_MAX_QUEUE 個請求,而「預設值為 512」。超過此數量時,它會「回應 503 error,表示 server overloaded」。對於一次只能處理 4 個請求的主機,深度為 512 的 queue 是無法兌現的承諾,因為位於第 300 位的 client 早在輪到它之前就會逾時。較短的 queue 會回傳應用程式可以重試或回報的錯誤,優於永遠無法完成的載入指示。
實際測試。從 2 個 terminal 同時送出請求,並觀察兩者。如果第 2 個請求直到第 1 個完成後才有任何輸出,表示平行處理設定未生效。
真正值得投入額外設定的推論伺服器
當您有具備剩餘容量的 GPU,且實際同時處理的請求大致超過 4 個時,vLLM 的額外設定成本才值得。它以 token 為單位執行排程,並以分頁方式管理快取,能重複利用可用的記憶體碎片,把剩餘的 VRAM 轉為並行處理能力,而不是讓它閒置。截至 2026 年 8 月,文件所列的安裝與啟動方式只需執行以下 2 個命令:
uv pip install vllm --torch-backend=auto
vllm serve Qwen/Qwen2.5-1.5B-Instructcurl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen2.5-1.5B-Instruct",
"messages": [{"role": "user", "content": "Say hello."}]
}'若回應包含 choices 陣列,表示伺服器已啟動且模型已載入。在負載下,最重要的 2 個參數是 --max-num-seqs,也就是「單次迭代最多處理的序列數」,以及 --max-num-batched-tokens,也就是「單次迭代最多處理的 token 數」。前者限制並行數。後者是前文所述的分塊預填充預算。
當同時處理的請求少於約 4 個,或主機不具備受支援的 GPU 時,vLLM 會增加複雜度,但帶來的效益有限。它需要 CUDA 等級的顯示卡,並會在啟動時宣告使用大部分記憶體;對 4 到 8 GB 的 VPS 而言,這不是合適的取捨。在這類環境中,較小的模型、較短的 context,以及由您控制的佇列,才是更適合的方案。Ollama 與 vLLM 作為推論伺服器的差異完整說明這項選擇,而在 VPS 上執行 Qwen 3 8B則展示在加入第 1 位額外使用者前,中型模型需要哪些資源。
民間說法所掩蓋的取捨
Continuous batching 能提高整體吞吐量,通常也能改善中位數延遲,因為佇列中的請求會更早開始處理。尾端延遲則會朝相反方向變化,而這一半很少被提及。
每在一個 step 中加入一個序列,就會增加少量工作量。因此,batch 填滿時,所有請求的 ITL 都會上升。新到達請求的 prefill 會占用原本可供串流中使用者使用的部分 step。當 cache 承受壓力時,scheduler 會進行 preemption,使尚未產生完畢的請求回到 prefill 的起點。
聊天 UI 呈現的是尾端延遲,而不是平均值。串流在句子中途停頓兩秒,即使完成所需的總時間良好,使用者仍會認為服務故障。在預期的負載下測量 p95 TTFT 與 p95 ITL,並將每秒平均 token 數視為容量指標,而不是使用體驗的描述。
實務上的設定由此而來。將並行數限制在記憶體可容納的數量之下,並保留少量餘裕,讓引擎不必進行 preemption。短而可預測的佇列,比會反覆抖動的深層 batch 更理想,因為使用者等待四秒後能順暢串流,會比立即開始卻停頓兩次更滿意。
速度變慢時應檢查的項目
每位使用者的速度都正常,但等待時間很長。 這是佇列問題,不是速度問題。先檢查平行處理設定。模型服務正常,只是一次處理一個請求。
Ollama 回傳 HTTP 503。 佇列已滿。可能是主機確實已達容量上限,也可能是 OLLAMA_MAX_QUEUE 刻意設得較低以卸載負載;這正是該設定的用途。
CPU 主機在負載增加時,每秒 token 數量大幅下降。 發生問題時執行 vmstat 1。如果 si 和 so 欄位不是 0,表示機器正在使用 swap,因此每個 token 都必須從磁碟讀取模型權重。單靠設定變更無法解決。請減小模型規模或降低 slot 數量。
每 10 位使用者中,有 1 位的等待時間遠高於其他人。 在 vLLM 日誌中搜尋 preempted。通常原因是搶占及其重新計算,表示以允許的 context length 而言,快取容量已超額使用。
即使伺服器閒置,TTFT 仍然很差。 這是 prefill 問題,不是並行處理問題。長 prompt 會在第一個 token 出現前耗費實際處理時間,因此應先檢查 prompt 大小與 prefix caching,再檢查硬體。
FAQ
為什麼第二個人使用自架 LLM 時,速度會變慢?
多數情況下,速度其實沒有變慢,而是進入佇列。Ollama 預設將 OLLAMA_NUM_PARALLEL 設為 1,因此第二個請求必須等第一個請求產生最後一個 token。可在一名使用者串流回應、另一名使用者等待時測量時間,以區分這兩種情況:如果第二個請求開始後,每秒 token 數恢復正常,表示問題是佇列;提高平行處理數即可解決。如果兩個串流都以一半速度執行,表示確實在共用記憶體頻寬,這是硬體限制。
一張小型 GPU 可以服務多少個並行使用者?
應計算記憶體,而不是使用者人數。先計算權重,再計算 KV cache。KV cache 的成本為每個 token、每個作用中的對話各需 2 乘以層數,再乘以 key/value heads、head dimension 與 bytes。一般 8B 模型具有 36 層、8 個 key/value heads,以及 128 的 head dimension;在 16-bit 下,每個 token 約需 144 KiB,因此 8,192 token 的對話約需 1.2 GB。可在 16-bit 容納該模型的 24 GB 顯示卡,約剩下 6 GB 可供 cache 使用,足以在完整上下文下容納約五個對話;縮短上下文後則可容納更多對話。
Continuous batching 會讓每名使用者的回覆變慢嗎?
中位延遲通常會改善,因為請求不必再等待整個批次完成。尾端延遲則會變差。每增加一個序列,每個解碼步驟都會增加工作量;新請求的 prefill 會佔用串流使用者部分步驟的處理時間,而被搶占的請求必須執行兩次 prefill。請測量 p95 token 間延遲,而不是平均值,因為聊天視窗中的停頓很明顯,平均值可能掩蓋這些停頓。
我應該提高 OLLAMA_NUM_PARALLEL,還是改用 vLLM?
先提高平行處理數。這不需額外成本,只要加入一個 drop-in 檔案即可,並能解決四個人排在一個長回覆後方的常見情況。限制因素是記憶體:平行請求會倍增需要保留的上下文,因此請注意是否有層溢出到 CPU。當你有足夠的 GPU VRAM,且確實同時處理超過約四個請求時,再改用 vLLM;此時 paged cache 與逐 token 排程帶來的效益,才會超過其成本。
增加 CPU 核心數能解決 LLM 伺服器速度慢的問題嗎?
無法改善使用者最容易察覺的部分。Decode 會為每個 token 從記憶體讀取整個模型,因此受 RAM 頻寬限制;頻寬飽和後,增加核心數也不再有效。Prefill 可隨核心數增加而擴展,因此增加核心數能縮短長提示的首 token 時間。在 4 到 8 GB 的 VPS 上,限制因素通常是記憶體容量;相較於增加 vCPU,更有效的做法通常是改用較小的模型或縮短上下文。