SSD Nodes Learn 🎉 VPS từ $5.50/tháng
Hướng dẫn Matt ConnorBởi Matt Connor

Ollama: NUM_PARALLEL và MAX_QUEUE hoạt động thế nào

Request thứ hai trong Ollama sẽ chờ hay bị HTTP 503? Tìm hiểu OLLAMA_NUM_PARALLEL, OLLAMA_MAX_QUEUE và vì sao mỗi slot song song cần thêm VRAM.

Điều gì xảy ra với request Ollama thứ hai trong khi request thứ nhất đang sinh kết quả

Tính đồng thời của Ollama được quyết định bởi 3 biến môi trường. Theo mặc định, một model đã được load chỉ xử lý 1 request tại một thời điểm. Request thứ hai không bị từ chối và cũng không nhận được câu trả lời một phần. Nó chờ trong queue đến khi có slot trống, rồi chạy với tốc độ bình thường.

Một request đến có 3 khả năng. Nó chạy ngay trong slot trống. Nó chờ trong queue. Hoặc queue đã đầy và server từ chối request bằng HTTP 503. Kết quả nào xảy ra phụ thuộc vào OLLAMA_NUM_PARALLEL, OLLAMA_MAX_QUEUEOLLAMA_MAX_LOADED_MODELS.

Mặc định này an toàn. Đây cũng là lý do người dùng thứ hai có thể báo rằng server đã "bị treo" dù không có gì hỏng. Thêm slot chỉ cần thay đổi 2 dòng. Vấn đề khó xử lý là bộ nhớ. Mỗi slot chạy song song cần một key/value cache riêng (KV cache). Đây là vùng bộ nhớ model dùng để giữ các token đã xử lý. Nếu thêm slot mà không thêm VRAM (bộ nhớ video trên GPU), bạn có thể biến một câu trả lời chậm thành lỗi load model.

OLLAMA_NUM_PARALLEL, OLLAMA_MAX_LOADED_MODELSOLLAMA_MAX_QUEUE kiểm soát điều gì

Đây là các giá trị mặc định trong những bản phát hành Ollama hiện tại tính đến tháng 8 năm 2026. Hãy kiểm tra hệ thống của bạn thay vì tin vào con số ở đây, bằng dòng log được hiển thị ở phần bên dưới.

  • OLLAMA_NUM_PARALLEL là số request mà một model đã load có thể xử lý đồng thời. Giá trị mặc định là 1, nên các request được xử lý lần lượt.
  • OLLAMA_MAX_LOADED_MODELS là số model khác nhau được giữ resident cùng lúc. Giá trị mặc định là 0, nghĩa là Ollama tự chọn: 3 model cho mỗi GPU và 3 model trên máy không có GPU.
  • OLLAMA_MAX_QUEUE là số request có thể nằm chờ. Giá trị mặc định là 512. Request đến khi queue đã đầy sẽ bị từ chối ngay.

Bộ nhớ trong trường hợp xấu nhất là tích của 2 giá trị đầu tiên. 2 model đã load, mỗi model có 4 slot, tương đương 8 slot allocation của KV cache cùng resident một lúc, và Ollama sẽ cố đáp ứng mức đó. Trên một máy chỉ có 1 GPU, thường nên giữ 1 model và cấp slot cho model đó, vì phép tính vẫn đủ đơn giản để tính nhẩm.

Vì sao mỗi slot chạy song song đều tốn VRAM

Khi Ollama load model, nó khởi chạy một runner process riêng. Ở đây có 2 argument đáng chú ý: -c là tổng context mà runner cấp phát cho KV cache, còn -np là số sequence chạy song song. Ollama đặt -c bằng context length cho mỗi request nhân với số slot. Sau đó runner chia đều tổng này cho các slot, nên mỗi request vẫn nhận được context length bạn đã yêu cầu.

Đó là toàn bộ ràng buộc, và cũng là lý do parallelism không miễn phí. Tăng từ 1 slot lên 4 slot nghĩa là cần KV cache lớn gấp 4 lần với cùng context cho mỗi request. Các slot không dùng chung cache, và phần cache của slot đang idle cũng không được chuyển cho slot đang bận, vì cách chia này được cố định khi runner khởi động.

Bạn có thể đọc các giá trị thực tế thay vì các giá trị bạn định cấu hình:

journalctl -u ollama --no-pager -n 500 | grep "starting llama-server"

Dòng này chứa toàn bộ command line của runner, bao gồm -c-np. Nếu -np là 1 sau khi bạn đặt biến, cấu hình chưa được truyền đến server, và phần tiếp theo giải thích lý do.

Nếu model weights cộng với KV cache không vừa trong VRAM, Ollama sẽ chuyển một số layer sang system RAM và các layer đó chạy trên CPU. Các layer chạy trên CPU chậm hơn nhiều so với layer chạy trên GPU, nên mọi request đều chậm hơn, kể cả request đơn mà bạn đã chạy ban đầu. Vì vậy, tăng parallelism có thể làm giảm throughput thay vì tăng throughput.

ollama ps

Cột PROCESSOR hiển thị 100% GPU khi toàn bộ model vừa trong VRAM. Giá trị như 35%/65% CPU/GPU nghĩa là một phần model đang chạy trên CPU. Cột SIZE bao gồm KV cache, nên sẽ tăng khi bạn tăng số slot rồi load lại model. Tăng OLLAMA_NUM_PARALLEL, restart, gửi một request và chạy lại ollama ps: đây là chi phí bộ nhớ của thay đổi đó, được đo thực tế thay vì ước tính.

Context length và số slot được nhân với nhau, nên phải chọn 2 giá trị này cùng nhau. Context lớn với 4 slot tương đương 4 context lớn. Nếu bạn cũng đang điều chỉnh context window num_ctx cho model, hãy thay đổi từng giá trị một, nếu không bạn sẽ không biết giá trị nào đã làm đầy card.

Cách đặt các biến để chúng vẫn còn sau khi reboot

Trên Linux, Ollama chạy dưới dạng một systemd service. Chạy export OLLAMA_NUM_PARALLEL=4 trong shell không thay đổi gì, vì systemd khởi động service bằng environment riêng và không thấy shell của bạn. Hãy dùng một drop-in file.

sudo systemctl edit ollama.service

Thêm nội dung này trong editor được mở:

[Service]
Environment="OLLAMA_NUM_PARALLEL=4"
Environment="OLLAMA_MAX_LOADED_MODELS=1"
Environment="OLLAMA_MAX_QUEUE=32"

Sau đó reload và restart:

sudo systemctl daemon-reload
sudo systemctl restart ollama
systemctl show ollama --property=Environment

systemctl show in ra các giá trị mà systemd sẽ truyền cho process. Nếu biến của bạn không có ở đó, drop-in chưa được lưu hoặc bạn đã bỏ qua daemon-reload. Đồng thời xác nhận từ chính phía server:

journalctl -u ollama --no-pager | grep "server config" | tail -1

Ollama ghi toàn bộ environment khi khởi động trên một dòng có message là server config. Map đó là nguồn xác thực. Đây là cách nhanh nhất để xác định một biến có thực sự có hiệu lực hay không.

Một model đã được load sẽ giữ số slot tại thời điểm khởi động, vì giá trị này được cố định trong runner process khi process được start. Lệnh restart ở trên sẽ unload mọi thứ. Vì vậy, request tiếp theo sẽ load lại model với setting mới và chỉ mất thời gian load một lần. Thời gian model tiếp tục resident sau đó là một thiết lập riêng, được đề cập trong giữ model Ollama đã load giữa các request.

Nhận biết request được xử lý, xếp hàng và bị từ chối từ phía client

Gửi nhiều request cùng lúc và đo thời gian của chúng. Lệnh này chạy song song 8 request dạng streaming và in status cùng các mốc thời gian của từng request:

for i in $(seq 1 8); do
  curl -s -o /dev/null \
    -w "req$i http=%{http_code} ttfb=%{time_starttransfer}s total=%{time_total}s\n" \
    http://127.0.0.1:11434/api/generate \
    -d '{"model":"llama3.2:3b","prompt":"Explain what a KV cache is.","stream":true}' &
done
wait

ttfb là thời gian đến byte đầu tiên của stream. Giá trị này gần với thời gian đến token đầu tiên (TTFT), vì chunk đầu tiên của stream chứa token đầu tiên.

Được xử lý song song. Mọi request có ttfb tương tự nhau, còn total cùng tăng đối với tất cả request. GPU được chia sẻ giữa các slot đang chạy. Vì vậy, mỗi câu trả lời chậm hơn so với khi chạy riêng, nhưng tổng số câu trả lời hoàn tất mỗi phút lại cao hơn. Đây là chế độ bạn đang mua khi tăng OLLAMA_NUM_PARALLEL.

Được xếp hàng. Các request đầu tiên trả lời nhanh. Các request đến sau có ttfb lớn, sau đó quá trình sinh text diễn ra bình thường. Thời gian chờ là thời gian nằm trong queue, không phải thời gian model xử lý. Người dùng nhìn vào cửa sổ chat sẽ thấy một khoảng trống dài, sau đó text xuất hiện với tốc độ tối đa. Dạng chậm lúc bắt đầu rồi nhanh về sau là dấu hiệu của queue, không phải GPU quá tải.

Bị từ chối. Client nhận http=503 gần như ngay lập tức, còn response body là:

{"error":"server busy, please try again.  maximum pending requests exceeded"}

Thông báo này có nghĩa là queue đã đầy tại thời điểm request đến. Nó không cho biết gì về VRAM và cũng không cho biết gì về model.

Một giới hạn cần lưu ý: Ollama không công bố độ sâu của queue. ollama ps và endpoint /api/ps chỉ báo cáo các model đang được load, không báo cáo các request đang chờ. Vì vậy, bạn phải đo queue từ phía client bằng cách theo dõi thời gian đến byte đầu tiên, hoặc đếm các response 503 tại thành phần đang đứng phía trước.

Vì sao MAX_QUEUE nhỏ hơn thường là thiết lập tốt hơn

Queue 512 nghe có vẻ rộng rãi, nhưng với một slot duy nhất thì gần như không có tác dụng. Request 300 phải chờ 299 lượt generation hoàn tất. Trong điều kiện tốt nhất, việc này vẫn mất vài phút. Mọi HTTP client đều timeout từ lâu trước thời điểm đó. Caller chỉ thấy client side timeout, không biết nguyên nhân và hệ thống monitoring cũng không có tín hiệu để alert.

Đặt queue ở mức tương đương với số request mà server có thể xử lý trong thời gian timeout của client. Khi đó, request vượt quá giới hạn sẽ nhận 503 ngay lập tức. 503 hữu ích: reverse proxy có thể retry, client có thể back off, dashboard có thể đếm số lỗi và người vận hành có thể đọc được. Hãy tính con số này từ số liệu đo thực tế của bạn. Nếu một generation mất khoảng ten giây và client chờ sixty giây, thì mỗi slot xử lý được khoảng six request trong khoảng thời gian đó. Queue sâu hơn nhiều so với mức này chỉ tạo ra timeout.

Khi nào nên đặt một queue trước Ollama

Queue tích hợp sẵn xử lý theo thứ tự vào trước, ra trước (FIFO) và không biết ai đang gửi request. Với một ứng dụng giao tiếp với một server, như vậy là đủ; thêm infrastructure chỉ làm phát sinh thêm các điểm có thể lỗi. Hãy dùng một thành phần phía trước khi có một trong các nhu cầu sau.

  • Bạn cần priority. Chat tương tác không nên phải chờ sau một job summarisation chạy theo batch. Queue của Ollama không có priority, nên phải giữ các công việc batch ở bên ngoài và đưa vào từ từ.
  • Bạn cần fairness. Một client có thể tự chiếm đầy queue, khiến mọi client khác nhận 503.
  • Bạn cần công việc vẫn tồn tại sau khi restart. Queue nằm trong memory của server. Restart Ollama sẽ làm mất mọi request đang chờ.
  • Bạn cần retry thực sự có backoff và được ghi lại ở một nơi có thể kiểm tra sau.

Giải pháp đơn giản là một reverse proxy. Trong nginx, limit_conn giới hạn số connection đồng thời và limit_req giới hạn tốc độ request đến từ mỗi client, vì vậy request vượt giới hạn sẽ bị từ chối tại proxy và không bao giờ đến queue của Ollama. Giải pháp đầy đủ hơn là một job queue có database đặt trước worker gọi Ollama. Đây là lựa chọn phù hợp khi request phải tồn tại qua cả lần restart process. Việc sizing cho traffic thực tế là một bài toán riêng: lập kế hoạch self-host LLM cho nhiều user đồng thời trình bày các phép tính cần thiết, còn chạy Ollama trên VPS bao quát phần cài đặt cơ bản mà các biến này giả định.

Khi câu trả lời đúng là dùng server khác

Có một giới hạn mà bạn không thể vượt qua chỉ bằng cách tinh chỉnh. Ollama chia KV cache thành các slot cố định, có kích thước bằng nhau khi model được load. Bộ nhớ của slot đang rảnh không thể được slot đang bận sử dụng, và số lượng slot không thể thay đổi nếu không unload model. Thiết kế này phù hợp với một người, một nhóm nhỏ hoặc một coding agent.

Các server được xây dựng cho nhiều người dùng đồng thời hoạt động khác. Chúng cấp phát KV cache theo các page nhỏ khi có nhu cầu và thêm các request mới vào batch đang chạy, để bộ nhớ phân bổ theo nhu cầu thực tế thay vì chia cố định. Nếu mục tiêu của bạn là phục vụ nhiều người dùng đồng thời trên một GPU, khác biệt về kiến trúc này quan trọng hơn mọi giá trị của OLLAMA_NUM_PARALLEL. So sánh giữa Ollama và vLLM là nơi bạn quyết định việc đó. Tuy nhiên, đừng chuyển đổi chỉ vì nguyên tắc: server khác sẽ cần vận hành nhiều hơn, còn nếu traffic của bạn chỉ có vài người thì behavior tích hợp sẵn là lựa chọn đúng.

Đo throughput và time to first token trên máy của bạn

Các con số token mỗi giây được công bố đến từ GPU, model, quantisation, context length và prompt của người khác. Không yếu tố nào trong số đó giống với hệ thống của bạn. Vì vậy, hãy xem mọi con số đọc được chỉ là gợi ý sơ bộ và tự đo trên máy đang sử dụng.

Ollama trả về thời gian xử lý trong object JSON cuối cùng của mỗi response. eval_count là số token đã sinh và eval_duration là thời gian dùng để sinh chúng, tính bằng nanosecond.

sudo apt install -y jq
curl -s http://127.0.0.1:11434/api/generate \
  -d '{"model":"llama3.2:3b","prompt":"Explain what a KV cache is.","stream":false}' \
  | jq '{prompt_eval_count, eval_count, eval_duration, tokens_per_second: (.eval_count / (.eval_duration / 1000000000))}'

Chạy lệnh đó với 1 slot, sau đó chạy lại với concurrency mà bạn thực sự dự kiến sử dụng. So sánh 2 con số quyết định người dùng có hài lòng hay không: time to first token và số token mỗi giây trên mỗi request. Throughput của từng request luôn giảm khi thêm slot. Vấn đề là mức giảm đó có vượt quá mức người dùng chấp nhận hay không. Đo số token mỗi giây trên LLM cục bộ trình bày chi tiết hơn về phương pháp này, bao gồm cách giữ prompt không đổi giữa các lần chạy.

Một endpoint public với queue lớn là mục tiêu của tấn công từ chối dịch vụ

Thiết lập OLLAMA_HOST=0.0.0.0:11434 khiến API lắng nghe trên mọi interface, và Ollama không có cơ chế xác thực tích hợp sẵn. Endpoint mở với queue mặc định sẽ nhận 512 request đang chờ từ bất kỳ ai tìm thấy nó. Kẻ tấn công gần như không tốn chi phí để làm đầy queue: prompt dài, không cần đăng nhập, không có rate limit và không phải trả phí. Người dùng của bạn sau đó sẽ nhận response 503 hoặc phải chờ lâu, còn máy chủ sẽ luôn bận xử lý các request đó.

Giữ listener trên loopback và truy cập qua SSH tunnel hoặc private network, hoặc đặt cơ chế xác thực và rate limiting phía trước endpoint. Bảo mật endpoint API của Ollama trình bày cả hai cách. Chỉ tinh chỉnh queue sau khi hoàn tất việc đó, vì độ dài queue là một thiết lập capacity và không bảo vệ được endpoint.

FAQ

Tại sao request Ollama thứ hai phải chờ request thứ nhất hoàn tất?

OLLAMA_NUM_PARALLEL mặc định là 1. Do đó, model đã load chỉ xử lý mỗi lần một request, còn các request khác phải chờ theo thứ tự. Request đang chờ vẫn giữ kết nối HTTP mở và không gửi byte nào cho đến khi có slot trống. Ở phía client, hiện tượng này giống hệt model chạy chậm. Dấu hiệu để phân biệt nằm ở dạng thời gian phản hồi: nếu có khoảng dừng dài rồi text xuất hiện với tốc độ tối đa thì đó là queue; nếu token đầu tiên xuất hiện chậm rồi các token tiếp theo nhỏ giọt thì model đang chậm. Tăng số slot bằng systemd drop-in rồi restart service.

"server busy, please try again. maximum pending requests exceeded" nghĩa là gì?

Đây là lỗi queue bị tràn của Ollama, được trả về với HTTP status 503. Số request đang chờ đã đạt OLLAMA_MAX_QUEUE, mặc định là 512, nên request mới nhất bị từ chối thay vì được thêm vào queue. Đây không phải lỗi memory và cũng không phải lỗi model. Tăng queue chỉ khiến caller chờ lâu hơn trước khi nhận cùng một lỗi từ chối. Cách xử lý thực tế là tăng số slot nếu bạn có đủ VRAM, giảm tải đầu vào, hoặc đặt một queue phía trước để retry và ưu tiên request.

Tăng OLLAMA_NUM_PARALLEL có làm Ollama chạy nhanh hơn không?

Không. Tùy chọn này cho phép nhiều request chạy đồng thời, nhưng mỗi request sẽ chậm hơn so với khi chạy riêng vì chúng dùng chung một GPU. Tùy chọn này cũng làm tăng KV cache theo cấp số nhân, vì Ollama khởi động runner với tổng context bằng context length nhân với số slot. Nếu kết quả không còn vừa trong VRAM, Ollama sẽ chuyển một phần layer sang CPU và mọi request đều chậm hơn, kể cả một request duy nhất không có request khác cạnh tranh. Sau khi thay đổi, kiểm tra ollama ps và xác nhận cột PROCESSOR vẫn có giá trị 100% GPU.

Có cần restart Ollama sau khi thay đổi các biến này không?

Có. Server đọc các biến này khi khởi động. Model đang chạy vẫn dùng số slot được cố định trong runner process lúc process đó khởi chạy. Sửa drop-in bằng sudo systemctl edit ollama.service, sau đó chạy sudo systemctl daemon-reloadsudo systemctl restart ollama. Xác nhận bằng systemctl show ollama --property=Environment, rồi kiểm tra dòng server config trong journalctl -u ollama. Dòng này liệt kê environment mà server thực sự đã load.

Nên đặt bao nhiêu parallel slot?

Bắt đầu với 1 rồi tăng từng bước một. Sau mỗi bước, restart Ollama, gửi một request để load model và chạy ollama ps. Dừng ở giá trị cuối cùng mà PROCESSOR vẫn có giá trị 100% GPU và cột SIZE vẫn còn đủ khoảng trống cho context dài nhất mà bạn phục vụ. Sau đó đo thời gian đến token đầu tiên và số token mỗi giây ở giá trị đó, dưới mức concurrency thực tế. Nếu tốc độ mỗi request thấp hơn mức người dùng có thể chấp nhận, giảm lại 1 bước.

#ollama#concurrency#vram#queueing#self-hosted-llm