Tại sao LLM tự host bị chậm khi có nhiều người dùng?
LLM của bạn khựng lại khi có 5 người dùng vì giới hạn mặc định của num_parallel. Bài viết giải thích cách tối ưu batching, KV cache và prefill để tăng khả năng xử lý đồng thời.
Tại sao LLM tự host lại chậm đi khi có thêm người dùng?
Một LLM tự host bị khựng lại ở mức 5 người dùng đồng thời vì máy chủ vẫn đang tạo từng câu trả lời một, còn bốn người kia đang đứng chờ trong hàng đợi. Tài liệu của Ollama nói rất thẳng thắn về thiết lập mặc định: OLLAMA_NUM_PARALLEL là "số lượng yêu cầu song song tối đa mà mỗi model sẽ xử lý cùng lúc, mặc định là 1." Không có gì bị hỏng cả. Bốn trong số năm người của bạn đang phải chờ đến lượt.
Giải pháp hiếm khi là nâng cấp phần cứng. Bạn cần một serving engine có khả năng đẩy nhiều yêu cầu qua model trong cùng một forward pass, cộng với đủ bộ nhớ trống để lưu trữ hội thoại của tất cả mọi người trong lúc xử lý. Cả hai yếu tố này đều quan trọng, và yếu tố thứ hai mới là thứ thực sự quyết định giới hạn của bạn.
Hai giai đoạn mà mọi request đều phải trải qua
Prefill đọc toàn bộ prompt cùng lúc và xây dựng attention cache cho nó. Mọi token trong prompt đều đi qua model cùng nhau, vì vậy prefill là một phép nhân ma trận lớn và bị giới hạn bởi thông lượng tính toán (arithmetic throughput). Sau đó, decode ghi câu trả lời từng token một. Mỗi token yêu cầu đọc toàn bộ trọng số (weights) của model từ bộ nhớ một lần nữa, trong khi khối lượng tính toán cho một token đó là rất nhỏ. Decode bị giới hạn bởi băng thông bộ nhớ (memory bandwidth).
Sự bất đối xứng đó chính là lý do tại sao batching lại hiệu quả. Việc decode cho một người dùng đọc khoảng 5 GB trọng số mỗi token và để hầu hết các đơn vị tính toán ở trạng thái nhàn rỗi. Thêm một request thứ hai, engine vẫn đọc cùng 5 GB đó một lần, sau đó tính toán hai token từ đó. Người dùng thứ hai gần như không tốn thêm thời gian. Việc phục vụ các request nối tiếp nhau một cách nghiêm ngặt sẽ làm lãng phí lợi thế này.
Hai con số mô tả trải nghiệm của người dùng. TTFT (thời gian đến token đầu tiên) là thời gian chờ trong hàng đợi cộng với thời gian prefill. ITL (độ trễ giữa các token) là khoảng cách giữa các token được stream, và nó được quyết định bởi decode. Một server chậm thường bị chậm ở một trong hai giai đoạn này, và cách khắc phục không giống nhau. Bạn cần xác định mình đang gặp vấn đề ở giai đoạn nào trước khi thay đổi bất kỳ thiết lập nào, và đo thời gian prefill và decode riêng biệt là cách để bạn tìm ra điều đó.
Static batching khiến mọi người phải chờ phản hồi chậm nhất
Static batching là phiên bản sơ khai, đây là kết quả khi bạn tự nhóm các request trong code ứng dụng. Engine thu thập N request, chạy chúng cùng lúc và giữ mọi slot cho đến khi quá trình tạo văn bản dài nhất trong nhóm hoàn tất.
Một người dùng yêu cầu tóm tắt 1.200 token sẽ giữ bốn câu trả lời ngắn bị kẹt trong batch, vì batch không giải phóng slot nào cho đến khi thành viên chậm nhất hoàn thành.
Hai chi phí phát sinh sau đó. Các chuỗi đã hoàn thành vẫn chiếm dụng slot mà không thực hiện tính toán hữu ích nào, do đó throughput thực tế giảm khi độ dài output thay đổi, và độ dài output của chat thì thay đổi rất nhiều. Một request đến sau khi batch đã hình thành một bước sẽ phải đợi toàn bộ batch xử lý xong trước khi bắt đầu prefill, nghĩa là TTFT của nó bị quyết định bởi bài viết dài của người khác.
Continuous batching chấp nhận và loại bỏ các request theo từng token
Continuous batching lập lịch ở cấp độ từng bước giải mã (decoding step). Sau mỗi bước, bộ lập lịch sẽ loại bỏ các chuỗi vừa tạo ra token dừng (stop token), sau đó chấp nhận các request đang chờ vào những slot trống. Một phản hồi kết thúc ở bước 40 sẽ giải phóng slot của nó ngay tại bước 40, chứ không phải đợi đến khi kết thúc cả batch.
Đây không phải là kỹ thuật lạ. llama-server ghi chú -cb, --cont-batching là "liệu có bật continuous batching (hay còn gọi là dynamic batching) hay không (mặc định: enabled)", và vLLM được xây dựng dựa trên ý tưởng này. Ollama cũng phục vụ các request song song. Mặc định chỉ giới hạn con số này ở mức 1, đó là lý do tại sao nhiều người kết luận phần cứng của họ không thể xử lý đồng thời, trong khi thực tế là do cấu hình của họ đã chặn điều đó.
Các kết quả về continuous batching thường được đo đạc trên các card datacenter có cả tài nguyên tính toán dư thừa và hàng chục gigabyte cho cache. Hình thái của các kết quả đó đúng với máy chủ của bạn. Tuy nhiên, quy mô của chúng thì không, và phần về bộ nhớ dưới đây sẽ giải thích lý do.
Prefill cạnh tranh tài nguyên tính toán với decode
Khi một request mới gửi đến trong lúc bốn phản hồi khác đang được stream, prompt của nó phải được prefill trước, và prefill là tác vụ tốn nhiều tài nguyên tính toán. Nếu bộ lập lịch (scheduler) dành riêng một bước cho quá trình prefill này, bốn người dùng đang nhận stream sẽ không nhận được token nào trong khoảng thời gian đó. Với các prompt dài, đây là độ trễ có thể nhận thấy rõ trên mọi cửa sổ đang mở. Đây chính là hiện tượng giật (stutter) mà người dùng thường nhắc đến khi nói rằng server bị khựng mỗi khi có người khác nhấn gửi yêu cầu.
Chunked prefill chia nhỏ một prompt dài thành các phần và trộn từng phần vào cùng một bước với các tiến trình decode đang chạy. Hướng dẫn tinh chỉnh của vLLM nêu rõ sự đánh đổi này: ngân sách chunk nhỏ hơn "đạt được ITL tốt hơn vì có ít tiến trình prefill làm chậm quá trình decode hơn", trong khi các giá trị cao hơn "đạt được thời gian phản hồi token đầu tiên (TTFT) tốt hơn vì bạn có thể xử lý nhiều token prefill hơn trong một batch". Bạn đang phải chọn ưu tiên trải nghiệm của ai: người đang chờ phản hồi bắt đầu, hay những người đang xem văn bản hiển thị.
Độ dài prompt quyết định mức độ ảnh hưởng của vấn đề này. Một prompt 6,000 token với câu trả lời 200 token tương đương với 6,000 token công việc prefill so với 200 bước decode. Chat có hỗ trợ truy xuất (RAG) và các system prompt dài đều đẩy bạn vào tình trạng này, khiến prefill không còn là sai số nhỏ mà trở thành tác vụ chính khiến người dùng phải chờ đợi. Prefix caching giúp ích khi phần dài của prompt bị lặp lại: vLLM cung cấp --enable-prefix-caching, tính năng này tái sử dụng cache cho một prefix prompt dùng chung thay vì phải tính toán lại cho từng request.
Bộ nhớ cạn kiệt đầu tiên chính là KV cache
Mỗi token trong mọi cuộc hội thoại đang hoạt động đều để lại một key vector và một value vector trong từng layer của model. Đó chính là KV cache (key/value cache), thành phần giúp quá trình decode không phải tính toán lại toàn bộ prompt cho mỗi token mới. Dung lượng cache trên mỗi token được cố định bởi cấu trúc model: 2 (một key, một value) nhân với số layer, nhân với số lượng key/value head, nhân với kích thước head, nhân với số byte trên mỗi giá trị. Hãy đọc các thông số này từ config.json của model.
Tính toán một lần và giới hạn bộ nhớ sẽ không còn là bí ẩn. Một model 8B điển hình với 36 layer, 8 key/value head và kích thước head là 128, lưu trữ cache ở định dạng 16-bit, sẽ tốn 2 36 8 128 2 byte cho mỗi token. Kết quả là 147,456 byte, khoảng 144 KiB. Do đó, một cuộc hội thoại 8,192 token cần khoảng 1.2 GB cache. Năm cuộc hội thoại như vậy cần khoảng 6 GB, chưa tính đến trọng số (weights), và đây chính là câu trả lời thực tế cho việc hệ thống có thể phục vụ bao nhiêu người dùng.
Tính đồng thời làm tăng quy mô ngữ cảnh (context), và các công cụ đều chỉ rõ điều này. FAQ của Ollama nêu: "Việc xử lý yêu cầu song song cho một model nhất định sẽ làm tăng kích thước ngữ cảnh theo số lượng yêu cầu song song. Ví dụ, ngữ cảnh 2K với 4 yêu cầu song song sẽ dẫn đến ngữ cảnh 8K và yêu cầu cấp phát thêm bộ nhớ." RAM cần thiết tỉ lệ thuận với OLLAMA_NUM_PARALLEL nhân với OLLAMA_CONTEXT_LENGTH. Trong llama-server, ngữ cảnh bạn yêu cầu với -c được chia sẻ cho các -np slot, vì vậy việc tăng số lượng slot sẽ tự động làm giảm dung lượng mà mỗi yêu cầu có thể chứa. Hãy đọc ngữ cảnh trên mỗi slot từ log khởi động thay vì tự suy đoán.
vLLM thực hiện cấp phát trước (preallocate). --gpu-memory-utilization (mặc định 0.92) là "tỉ lệ bộ nhớ GPU được sử dụng cho model executor". Phần còn lại sau khi trừ đi trọng số sẽ trở thành paged KV pool, và khi pool này cạn kiệt, scheduler sẽ loại bỏ (evict) một yêu cầu thay vì để nó thất bại:
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.Trong engine V1 của vLLM, chế độ preemption mặc định là RECOMPUTE, vì vậy một yêu cầu bị loại bỏ sẽ mất cache và phải thực hiện prefill lại khi được nạp vào. Công việc đó bị thực hiện hai lần. Tài liệu cảnh báo rằng "preemption và recomputation có thể ảnh hưởng tiêu cực đến độ trễ end-to-end", và dòng log này là lời giải thích tốt nhất cho việc tại sao một người dùng kém may mắn phải chờ đợi lâu hơn hẳn những người khác trong khi các chỉ số trung bình của bạn vẫn trông bình thường. Hãy thiết lập disable_log_stats=False để ghi log số lượng tích lũy, hoặc đọc bộ đếm preemption từ các Prometheus metrics mà vLLM cung cấp.
Những thay đổi ở mức 2, 5 và 20 người dùng đồng thời
Hai người dùng. Gần như không đáng kể trên GPU có dư cache, vì luồng giải mã thứ hai chạy cùng luồng thứ nhất với rất ít thời gian tăng thêm. Trên VPS chỉ dùng CPU với 4 đến 8 GB RAM, điều này không miễn phí: cả hai luồng chia sẻ cùng một số lượng vCPU và băng thông RAM, nên mỗi người dùng chỉ nhận được khoảng một nửa số token mỗi giây, và nhu cầu cache tăng gấp đôi trong khi ngân sách tài nguyên lại nhỏ hơn nhiều.
Năm người dùng. Đây là lúc các thiết lập mặc định không còn đủ và vấn đề bắt đầu xuất hiện ở hàng đợi. Với OLLAMA_NUM_PARALLEL ở mức 1, bốn người phải đợi người đang yêu cầu câu trả lời dài, và mỗi người trong số họ sẽ thấy tốc độ bình thường ngay khi đến lượt. Tăng số lượng xử lý song song sẽ làm thay đổi bản chất vấn đề: năm slot với mỗi slot 8K context tạo ra 40K token cache cần xử lý. Nếu không đủ VRAM, engine sẽ đẩy các layer sang RAM hệ thống, và nếu không đủ RAM, máy chủ sẽ thực hiện swap và tốc độ token mỗi giây sẽ sụp đổ.
Hai mươi người dùng. Hai mươi người trong giao diện chat thường không phải là hai mươi yêu cầu đồng thời, và đây là điều hữu ích nhất cần hiểu trước khi mua phần cứng. Một người đọc phản hồi và suy nghĩ từ 20 đến 60 giây giữa các lượt, nên phần lớn phiên làm việc của họ là ở trạng thái nhàn rỗi. Hai mươi agent, hoặc hai mươi tác vụ tóm tắt tài liệu, là hai mươi luồng thực sự không có thời gian nhàn rỗi. Đó là một bài toán phần cứng khác. Một lập trình viên đã trỏ một coding agent vào server Ollama của riêng họ sẽ gần với trường hợp thứ hai hơn trường hợp đầu, vì agent liên tục gửi yêu cầu chừng nào tác vụ còn chạy và không có khoảng nghỉ đọc như con người.
Người dùng của bạn đang hoạt động đồng thời, hay chỉ đơn thuần là đăng nhập?
Hãy tính toán số lượng request đang xử lý (in flight) trước khi định cỡ bất kỳ thứ gì. Phép tính rất đơn giản: số lượng request đang xử lý bằng số người dùng, nhân với số giây tạo nội dung mỗi lượt, chia cho số giây giữa các lượt.
- Trước tiên, hãy đo tốc độ xử lý đơn luồng của chính bạn, bao gồm cả prefill và decode. Đừng lấy số liệu từ card của người khác: đo số token mỗi giây trên máy của bạn và sử dụng kết quả đó.
- Ước tính chu kỳ hoạt động (duty cycle). Với 20 người dùng chat, 12 giây tạo nội dung mỗi lượt, một lượt mỗi 90 giây, ta có 20 * 12 / 90, tương đương khoảng 2,7 request đang xử lý.
- Thiết lập số lượng slot cao hơn con số đó một chút, sau đó kiểm tra lại với bộ nhớ: số slot nhân với context mỗi request phải nằm gọn trong số token cache mà bạn thực sự có.
- Giữ hàng đợi ngắn để nếu có tràn (overflow), hệ thống sẽ báo lỗi nhanh và rõ ràng.
Số token cache khả dụng là bộ nhớ còn dư sau khi đã trừ đi phần dành cho weights, chia cho chi phí mỗi token từ phần trên. Một card 24 GB chạy model 8B ở định dạng 16-bit sẽ tốn khoảng 16 GB cho weights và có khoảng 6 GB cache khả dụng ở mức sử dụng mặc định, tương đương với khoảng năm cuộc hội thoại 8K. Để chứa nhiều hơn, hãy rút ngắn context mỗi request, hoặc lưu trữ cache ở định dạng 8-bit (llama-server tốn --cache-type-k q8_0). Cả hai cách đều tăng khả năng xử lý đồng thời bằng cách đánh đổi một thứ gì đó, và phiên bản trung thực của sự đánh đổi đó rất đáng đọc trước khi bạn đầu tư tiền vào phần cứng: điểm hòa vốn của GPU VPS so với API token.
Khi các thiết lập mặc định của Ollama không còn đủ
Hãy tăng số lượng xử lý song song thông qua unit của service, vì lệnh export trong shell sẽ không có tác dụng với một daemon do systemd quản lý.
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 sẽ in ra ba biến bạn vừa thiết lập. Nếu không thấy, nghĩa là file drop-in chưa được lưu và mọi thao tác khác của bạn đều vô nghĩa. ollama ps sau đó liệt kê model đã load với kích thước lớn hơn trọng số đơn thuần, vì bốn slot với 8,192 token sẽ chiếm dụng 32,768 token bộ nhớ cache đi kèm. Cột PROCESSOR hiển thị một phần model trên CPU trong khi bạn muốn toàn bộ nằm trên GPU nghĩa là bạn đã yêu cầu lượng cache vượt quá dung lượng còn trống của card. Hãy giảm một trong hai con số này. Giảm context thường an toàn hơn, nhưng một cửa sổ quá nhỏ sẽ âm thầm cắt bớt các prompt dài thay vì báo lỗi, vì vậy hãy cân nhắc kỹ lưỡng num_ctx thay vì cứ giảm dần cho đến khi model vừa vặn.
Mặc định của hàng đợi cần được xem xét lại. Ollama xếp hàng tối đa OLLAMA_MAX_QUEUE yêu cầu, và "mặc định là 512". Vượt quá con số đó, nó sẽ phản hồi "với lỗi 503 cho biết server đang quá tải". Một hàng đợi sâu 512 trên một máy chủ chỉ xử lý bốn yêu cầu cùng lúc là một lời hứa bạn không thể thực hiện, vì client ở vị trí 300 sẽ timeout từ lâu trước khi đến lượt. Một hàng đợi ngắn sẽ trả về lỗi để ứng dụng của bạn có thể thử lại hoặc báo cáo, điều này tốt hơn là một vòng xoay chờ đợi không bao giờ kết thúc.
Hãy kiểm tra thực tế. Gửi hai yêu cầu cùng lúc từ hai terminal và theo dõi cả hai. Nếu yêu cầu thứ hai không tạo ra kết quả gì cho đến khi yêu cầu thứ nhất hoàn tất, nghĩa là thiết lập song song chưa có hiệu lực.
Khi nào một engine phục vụ thực tế bắt đầu mang lại hiệu quả
vLLM phát huy giá trị khi bạn có GPU còn dư tài nguyên và có hơn khoảng bốn request đang thực sự xử lý đồng thời. Bộ lập lịch của nó hoạt động theo từng token, cache được phân trang để tái sử dụng các phân đoạn trống, và nó chuyển đổi VRAM dư thừa thành khả năng xử lý đồng thời thay vì để trống. Tính đến tháng 8 năm 2026, việc cài đặt và khởi chạy theo tài liệu chỉ gồm hai lệnh:
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."}]
}'Một phản hồi chứa mảng choices nghĩa là server đã chạy và model đã được load. Khi chịu tải, hai tham số quan trọng là --max-num-seqs, "số lượng sequence tối đa được xử lý trong một lần lặp", và --max-num-batched-tokens, "số lượng token tối đa có thể được xử lý trong một lần lặp". Tham số đầu tiên giới hạn khả năng xử lý đồng thời. Tham số thứ hai là ngân sách chunked prefill đã mô tả trước đó.
Với dưới bốn request đang xử lý, hoặc trên bất kỳ máy nào không có GPU hỗ trợ, vLLM gây tốn kém về độ phức tạp mà không mang lại nhiều lợi ích. Nó yêu cầu card chuẩn CUDA và chiếm phần lớn bộ nhớ ngay khi khởi động, đây là lựa chọn sai lầm trên các VPS có 4 đến 8 GB RAM. Trong trường hợp đó, giải pháp là dùng model nhỏ hơn với context ngắn hơn và một hàng đợi do bạn kiểm soát. sự khác biệt giữa Ollama và vLLM với tư cách là engine phục vụ bao quát đầy đủ các lựa chọn, và chạy Qwen 3 8B trên VPS cho thấy những yêu cầu của một model tầm trung trước khi bạn thêm bất kỳ người dùng nào khác.
Sự đánh đổi mà các quan niệm phổ biến thường bỏ qua
Continuous batching làm tăng tổng throughput và thường cải thiện cả độ trễ trung bình (median latency), vì các request trong hàng đợi được xử lý sớm hơn. Tuy nhiên, độ trễ đuôi (tail latency) lại diễn biến theo hướng ngược lại, và khía cạnh này hiếm khi được nhắc đến.
Mỗi sequence bổ sung trong một bước đều làm tăng khối lượng công việc, vì vậy ITL tăng lên đối với tất cả người dùng khi batch được lấp đầy. Quá trình prefill của một request mới đến sẽ chiếm một phần tài nguyên của bước mà lẽ ra người dùng đang streaming sẽ được hưởng. Khi chịu áp lực về cache, scheduler sẽ thực hiện preemption, đẩy một request đang tạo dở dang quay trở lại trạng thái bắt đầu prefill.
Giao diện chat thể hiện độ trễ đuôi, không phải giá trị trung bình. Một stream bị khựng lại hai giây giữa chừng sẽ bị coi là lỗi, ngay cả khi tổng thời gian hoàn thành vẫn tốt. Hãy đo lường p95 TTFT và p95 ITL dưới mức tải dự kiến, và coi số lượng token trung bình mỗi giây là con số về công suất thay vì là mô tả trải nghiệm người dùng.
Thiết lập thực tế dựa trên nguyên tắc đó. Hãy giới hạn concurrency thấp hơn một chút so với mức bộ nhớ cho phép để engine không bao giờ phải thực hiện preemption. Một hàng đợi ngắn và có thể dự đoán được sẽ tốt hơn một batch lớn gây ra tình trạng thrashing, bởi vì người dùng chờ bốn giây rồi stream mượt mà sẽ hài lòng hơn người dùng bắt đầu ngay lập tức nhưng bị khựng lại hai lần.
Những điểm cần kiểm tra khi hệ thống chậm
Mỗi người dùng đều bình thường, nhưng thời gian chờ đợi lâu. Đây là vấn đề hàng đợi, không phải vấn đề tốc độ. Hãy kiểm tra cài đặt song song trước. Model đang phục vụ chính xác, mỗi lần một request.
Lỗi HTTP 503 từ Ollama. Hàng đợi đã đầy. Hoặc là máy chủ thực sự đã đạt giới hạn công suất, hoặc OLLAMA_MAX_QUEUE được đặt thấp một cách có chủ đích để giảm tải, đây chính là điều bạn muốn hệ thống thực hiện.
Số lượng token mỗi giây giảm mạnh khi chịu tải trên máy chạy CPU. Hãy chạy vmstat 1 trong khi tình trạng này xảy ra. Các cột si và so khác không cho thấy máy đang bị swap, nghĩa là trọng số (weights) đang bị đọc từ ổ cứng trên mỗi token. Không có thay đổi cấu hình nào khắc phục được việc này. Hãy giảm kích thước model hoặc số lượng slot.
Một trong mười người dùng phải chờ lâu hơn hẳn những người khác. Tìm kiếm trong log của vLLM từ khóa preempted. Preemption và quá trình tính toán lại (recompute) thường là nguyên nhân, điều này có nghĩa là cache đang bị quá tải so với độ dài context mà bạn cho phép.
TTFT (Time To First Token) kém ngay cả khi máy chủ đang rảnh. Đây là vấn đề prefill, không phải concurrency. Các prompt dài tốn thời gian thực trước khi token đầu tiên xuất hiện, vì vậy hãy xem xét kích thước prompt và prefix caching trước khi kiểm tra phần cứng. Nếu thời gian chờ lâu chỉ xảy ra với người đầu tiên sau một khoảng lặng và những người sau đó đều ổn, thì đó không phải là prefill mà là do Ollama giải phóng model và đọc lại trọng số từ ổ cứng, bạn nên loại trừ khả năng này bằng cách giữ model luôn thường trú giữa các request.
FAQ
Tại sao LLM tự host của tôi bị chậm khi có người thứ hai sử dụng?
Thông thường nó không hề chậm đi, mà là đang xếp hàng. Ollama mặc định để OLLAMA_NUM_PARALLEL là 1, nên yêu cầu thứ hai phải đợi yêu cầu thứ nhất xuất ra token cuối cùng. Hãy phân biệt hai trường hợp này bằng cách đo thời gian stream của một người dùng trong khi người khác đang đợi: nếu tốc độ token mỗi giây của họ vẫn bình thường sau khi bắt đầu, nghĩa là bạn đang bị xếp hàng và việc tăng số lượng xử lý song song sẽ giải quyết được. Nếu cả hai luồng đều chạy với tốc độ bằng một nửa, bạn đang thực sự chia sẻ băng thông bộ nhớ, và đó là giới hạn phần cứng.
Một GPU nhỏ có thể phục vụ bao nhiêu người dùng đồng thời?
Hãy tính bộ nhớ, đừng tính người dùng. Đầu tiên là trọng số (weights), sau đó là KV cache. Chi phí này bằng 2 nhân với số lớp, nhân với số đầu key/value, nhân với kích thước đầu, nhân với số byte, trên mỗi token, trên mỗi phiên hội thoại đang hoạt động. Một model 8B điển hình với 36 lớp, 8 đầu key/value và kích thước đầu 128 tốn khoảng 144 KiB mỗi token ở định dạng 16-bit, nên một hội thoại 8,192 token cần khoảng 1.2 GB. Một card 24 GB chứa model đó ở 16-bit sẽ còn dư khoảng 6 GB cho cache, tương đương với khoảng năm hội thoại ở context đầy đủ, hoặc nhiều hơn nếu bạn rút ngắn context.
Continuous batching có làm câu trả lời của mỗi người dùng chậm hơn không?
Độ trễ trung vị (median latency) thường cải thiện vì các yêu cầu không phải đợi cả batch hoàn tất. Độ trễ đuôi (tail latency) sẽ tệ hơn. Mỗi chuỗi bổ sung thêm công việc cho từng bước giải mã, quá trình prefill của yêu cầu mới sẽ chiếm một phần thời gian của người dùng đang stream, và một yêu cầu bị ngắt quãng phải prefill hai lần. Hãy đo độ trễ giữa các token ở mức p95, đừng đo trung bình, vì cửa sổ chat sẽ làm lộ rõ các khoảng dừng mà số trung bình thường che giấu.
Tôi nên tăng OLLAMA_NUM_PARALLEL hay chuyển sang vLLM?
Hãy tăng số lượng song song trước. Nó miễn phí, chỉ cần một file cấu hình bổ sung và giải quyết được trường hợp phổ biến khi bốn người phải xếp hàng sau một câu trả lời dài. Bộ nhớ là giới hạn: các yêu cầu song song sẽ nhân lên lượng context bạn phải giữ, vì vậy hãy theo dõi xem các lớp có bị tràn sang CPU không. Hãy chuyển sang vLLM khi bạn có GPU dư VRAM và thực sự có hơn bốn yêu cầu đang chạy cùng lúc, vì đó là thời điểm mà paged cache và cơ chế lập lịch theo từng token mang lại hiệu quả cao hơn chi phí bỏ ra.
Thêm nhân CPU có làm máy chủ LLM nhanh hơn không?
Không đối với phần mà người dùng nhận thấy rõ nhất. Quá trình giải mã (decode) đọc toàn bộ model từ bộ nhớ cho mỗi token, nên nó bị giới hạn bởi băng thông RAM, và thêm nhân cũng không giúp ích gì khi băng thông đã bão hòa. Quá trình prefill có tỉ lệ thuận với số nhân, nên nhiều nhân hơn sẽ rút ngắn thời gian xuất token đầu tiên đối với các prompt dài. Trên một VPS 4 đến 8 GB, giới hạn thường là dung lượng bộ nhớ, và giải pháp hiệu quả là dùng model nhỏ hơn hoặc context ngắn hơn thay vì thêm vCPU.