SSD Nodes Learn Hosting plans →
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-08-22

Ollama concurrency: NUM_PARALLEL và MAX_QUEUE

Request Ollama thứ hai sẽ chờ hay bị trả 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 khi request thứ nhất đang tạo câu trả lời

Concurrency 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ý mỗi lần một request. Request thứ hai không bị từ chối và cũng không nhận được câu trả lời dở dang. Nó chờ trong queue đến khi có slot trống, rồi chạy ở tốc độ bình thường.

Một request đến có 3 khả năng. Nó chạy ngay trong một 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 được quyết định bởi OLLAMA_NUM_PARALLEL, OLLAMA_MAX_QUEUE và OLLAMA_MAX_LOADED_MODELS.

Thiết lập mặc định 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ó lỗi nào. Thêm slot chỉ cần thay đổi 2 dòng. Vấn đề gây lỗi là memory. Mỗi slot chạy song song cần một key/value cache riêng (KV cache), tức vùng memory mà model dùng để giữ các token đã xử lý. Thêm slot mà không thêm VRAM (video memory trên GPU) sẽ biến một câu trả lời chậm thành lỗi load model.

OLLAMA_NUM_PARALLEL, OLLAMA_MAX_LOADED_MODELS và OLLAMA_MAX_QUEUE kiểm soát những gì

Đây là các giá trị mặc định trong các 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 các con số nà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ể chờ trong queue. Giá trị mặc định là 512. Request đến khi queue đã đầy sẽ bị từ chối ngay.

Lượng memory tối đa trong trường hợp xấu nhất là tích của hai giá trị đầu tiên. Hai model đã load, mỗi model có 4 slot, tạo thành 8 lần cấp phát slot cho KV cache. Tất cả đều được giữ resident cùng lúc và Ollama sẽ cố gắng đáp ứng cấu hình đó. Trên một máy chỉ có một GPU, thường nên giữ một 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 song song đều tốn VRAM

Khi Ollama load một model, nó khởi chạy một runner process riêng. Hai argument nó truyền vào rất quan trọng ở đây: -c là tổng context mà runner cấp phát 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, vì vậy 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à đây là lý do parallelism không miễn phí. Tăng từ 1 slot lên 4 slot cần lượng KV cache 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 cho slot bận mượn, vì việc chia cache đượ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 đó chứa đầy đủ command line của runner, bao gồm -c và -np. Nếu -np là 1 sau khi bạn đặt biến, cấu hình chưa đến được server, và phần tiếp theo sẽ giải thích lý do.

Nếu model weights cộng với KV cache không vừa 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 duy nhất bạn đã chạy ban đầu. Vì vậy, tăng parallelism có thể làm giảm throughput thay vì tăng throughput. Với model đủ lớn, riêng weights đã quyết định vấn đề trước khi bắt đầu tính slot. Vì vậy, tự host một model có quy mô như Kimi K3 là câu chuyện về số lượng card bạn có, không phải số slot bạn đặt.

ollama ps

Cột PROCESSOR hiển thị 100% GPU khi toàn bộ model vừa trong VRAM. Một trạng thái phân tách 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, vì vậy nó 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: đó là chi phí bộ nhớ của thay đổi bạn vừa thực hiện, được đo thực tế thay vì đoán. Nếu kết quả đo cho thấy model không còn vừa trong VRAM, hãy nhớ rằng weights là nửa ngân sách còn lại. Chuyển từ bản build fp16 sang q8 hoặc q4 thường giải phóng nhiều VRAM hơn lượng VRAM mà slot bạn định thêm cần.

Context length và số slot được nhân với nhau, vì vậy phải chọn chúng 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 cửa sổ context 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 để vẫn giữ nguyên sau khi reboot

Trên Linux, Ollama chạy dưới dạng systemd service. Chạy export OLLAMA_NUM_PARALLEL=4 trong shell của bạn 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 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 xuất hiện ở đó, drop-in chưa được lưu hoặc đã 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

Khi khởi động, Ollama ghi toàn bộ environment vào log trên một dòng có message là server config. Map đó là dữ liệu xác thực. Đây là cách nhanh nhất để xác định một biến đã có hiệu lực hay chưa.

Một model đã được load sẽ giữ số slot mà nó được khởi động cùng, vì giá trị này được cố định trong runner process lúc launch. Lệnh restart ở trên sẽ unload mọi thứ, nên 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. Model tiếp tục resident trong bao lâu sau đó là một control riêng, được đề cập trong giữ một model Ollama đã load giữa các request.

Dạng request được phục vụ, 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. Lệnh này chạy song song 8 request streaming, rồi in status và 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 time to first token (TTFT), vì chunk đầu tiên của stream chứa token đầu tiên.

Được phục vụ song song. Mọi request có ttfb tương tự nhau, còn total tăng cùng lúc trên 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 số câu trả lời hoàn tất mỗi phút lại cao hơn. Đây là chế độ bạn có được khi tăng OLLAMA_NUM_PARALLEL.

Xếp hàng. Các request đầu tiên trả lời nhanh. Các request đến sau có ttfb lớn, rồi bắt đầu sinh nội dung 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 đó văn bản xuất hiện với tốc độ đầy đủ. Dạng này — bắt đầu chậm rồi chạy nhanh — là dấu hiệu của queue, không phải GPU bị quá tải.

Bị từ chối. Client nhận http=503 gần như ngay lập tức, còn 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.

Có 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 đã đượ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 nằm 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 đơn thì gần như vô dụng. Request thứ 300 phải chờ sau 299 lượt generation hoàn tất. Trong trường hợp 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 đó. Vì vậy, caller chỉ thấy client side timeout. Thông báo này không cho biết nguyên nhân và monitoring của bạn cũng không có gì để cảnh báo.

Đặt queue ở mức gần với số request mà server có thể xử lý hết trong thời gian timeout của client. Khi đó, request vượt quá giới hạn sẽ nhận ngay 503. Mã 503 hữu ích: reverse proxy có thể retry, client có thể back off, dashboard có thể đếm, và người dùng có thể đọc được. Hãy tính con số này dựa trên số liệu đo thực tế của hệ thống. Nếu mỗi generation mất khoảng 10 giây và client chờ 60 giây, thì mỗi slot có thể xử lý khoảng 6 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 queue trước Ollama

Queue tích hợp xử lý theo thứ tự vào trước, ra trước (FIFO) và không biết ai đang gọi. Với một ứng dụng giao tiếp với một server, như vậy là đủ. Thêm infrastructure chỉ tạo thêm các điểm có thể fail. 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 batch summarisation. Queue của Ollama không có priority, nên phải giữ batch work ở 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 work tiếp tục tồn tại sau khi restart. Queue nằm trong memory của server. Khi restart Ollama, mọi request đang chờ đều bị mất.
  • Bạn cần retry thực sự với backoff và muốn ghi lại thông tin ở nơi có thể kiểm tra sau.

Cách nhẹ nhất là dùng reverse proxy. Trong nginx, limit_conn giới hạn số connection đồng thời và limit_req giới hạn rate request đến từ mỗi client. Vì vậy, request vượt giới hạn sẽ bị proxy từ chối và không bao giờ đến queue của Ollama. Cách nặng hơn là dùng job queue với database phía trước một 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-hosted LLM cho người dùng đồng thời trình bày các phép tính, còn chạy Ollama trên VPS hướng dẫ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 không thể khắc phục 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 load model. 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 chưa unload model. Thiết kế này phù hợp với một người, một team 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, vì vậy bộ nhớ được 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ụ số lượng lớn 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à phần để đưa ra quyết định đó. Tuy nhiên, đừng chuyển đổi chỉ vì nguyên tắc: server khác sẽ tốn công vận hành hơn, và nếu traffic của bạn chỉ có vài người, behaviour tích hợp sẵn là lựa chọn đúng.

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

Các con số token mỗi giây được công bố dựa trên 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ố bạn đọc được chỉ là gợi ý gần đúng 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 đã được tạo và eval_duration là thời gian dùng để tạo 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 bạn thực sự dự kiến sử dụng. So sánh 2 chỉ 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 trên mỗi 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 phương pháp chi tiết hơn, bao gồm cách giữ prompt không đổi giữa các lần chạy.

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

Đặt OLLAMA_HOST=0.0.0.0:11434 sẽ làm cho API lắng nghe trên mọi interface, và Ollama không có authentication tích hợp sẵn. Endpoint mở với queue mặc định sẽ chấp 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ấp đầy queue đó: prompt dài, không cần đăng nhập, không có rate limit và không phải trả phí. Khi đó, user của bạn sẽ nhận phản hồi 503 hoặc phải chờ lâu, còn máy chủ sẽ luôn bận xử lý.

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

FAQ

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

Vì OLLAMA_NUM_PARALLEL mặc định là 1. Do đó, model đã được load chỉ xử lý từng request một, các request còn lại 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 chờ lâu 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 dữ liệu tiếp tục nhỏ giọt thì model đang chậm. Tăng số slot bằng systemd drop-in rồi restart service.

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

Đó là lỗi tràn queue 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 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 nhanh hơn không?

Không. Thay đổi này cho phép nhiều request chạy cùng lúc. Tuy nhiên, 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. Thay đổi này cũng làm tăng KV cache theo số slot, vì Ollama khởi chạy runner với tổng context bằng độ dài context nhân với số slot. Nếu tổng này không còn vừa trong VRAM, Ollama sẽ đẩy một phần layer sang CPU và mọi request đều chậm hơn, kể cả một request chạy riêng không có cạnh tranh. Kiểm tra ollama ps sau khi thay đổi 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 giữ số slot đã được cố định trong runner process khi process đó khởi chạy. Chỉnh sửa drop-in bằng sudo systemctl edit ollama.service, sau đó chạy sudo systemctl daemon-reload và sudo 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 tế đã 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 đủ dư địa cho context dài nhất 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 giảm xuống dưới mức người dùng chấp nhận, lùi lại 1 bước.

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