SSD Nodes Learn Hosting plans →
Посібники Matt ConnorВід Matt Connor · Оновлено 2026-08-28

Чому self-hosted LLM зависає на 5 користувачах

Дізнайтеся, чому Ollama обробляє лише 1 запит за замовчуванням і як batching, KV cache, prefill та queue depth визначають межу для LLM-сервера.

Чому self-hosted LLM сповільнюється, коли користувачів стає більше?

Self-hosted LLM зависає на 5 одночасних користувачах, тому що сервер усе ще генерує відповіді по одній, а решта чотирьох стоять у черзі. У документації Ollama прямо зазначено значення за замовчуванням: OLLAMA_NUM_PARALLEL — це «максимальна кількість паралельних запитів, які кожна модель обробляє одночасно; за замовчуванням — 1». Нічого не зламано. Четверо з п’яти користувачів просто чекають своєї черги.

Рішенням рідко є потужніший сервер. Потрібен serving engine, який обробляє багато запитів у межах одного forward pass моделі, а також достатній запас пам’яті, щоб зберігати контекст розмов усіх користувачів. Важливі обидві складові, але саме друга фактично визначає граничну кількість одночасних користувачів.

Дві фази, через які проходить кожен запит

Prefill зчитує весь prompt одночасно та створює для нього attention cache. Усі токени prompt проходять через модель разом, тому prefill — це одне велике матричне множення, обмежене арифметичною продуктивністю. Decode записує відповідь по одному токену за раз. Для кожного токена потрібно знову зчитати з пам’яті всі ваги моделі, тоді як обсяг обчислень для одного токена дуже малий. Decode обмежений пропускною здатністю пам’яті.

Саме ця асиметрія пояснює, чому batching працює. Decode для одного користувача зчитує, наприклад, 5 GB ваг для кожного токена, а більшість арифметичних блоків простоює. Додайте другий запит — і рушій один раз зчитає ті самі 5 GB, а потім обчислить на їх основі два токени. Другий користувач майже не збільшує час обробки. Послідовне обслуговування запитів повністю нівелює цю перевагу.

Відчуття користувача описують два показники. TTFT (time to first token) — це час очікування в черзі плюс prefill. ITL (inter-token latency) — це інтервал між токенами, що передаються потоком; його визначає decode. Повільний сервер зазвичай має проблеми з одним із цих показників, і способи їх усунення різні. Перед зміною будь-яких налаштувань варто визначити, з яким саме показником ви працюєте. Для цього потрібно окремо виміряти час prefill і decode.

Статичне пакетування змушує всіх чекати на найдовшу відповідь

Статичне пакетування — це наївний варіант, який ви отримуєте, якщо самостійно групуєте запити в коді застосунку. Рушій збирає N запитів, обробляє їх разом і утримує кожен слот, доки не завершиться найдовша генерація в групі.

Один користувач, який запитав резюме на 1,200 токенів, утримує в пакеті чотири однорядкові відповіді, оскільки пакет не звільняє слот, доки не завершиться його найдовший елемент.

Це спричиняє дві проблеми. Завершені послідовності й далі займають слоти, хоча не виконують корисних обчислень. Через це ефективна пропускна здатність знижується, коли довжина вихідних даних різниться; у чатах вона різниться дуже сильно. Запит, який надійшов через один крок після формування пакета, чекає, доки весь пакет завершить обробку, і лише після цього починає prefill. Отже, його TTFT визначається чужим довгим текстом.

Неперервне пакетування приймає та завершує запити на кожному токені

Неперервне пакетування планує виконання на рівні одного кроку декодування. Після кожного кроку планувальник видаляє послідовності, які щойно видали токен зупинки, а потім приймає запити з черги очікування у вільні слоти. Відповідь, що завершується на кроці 40, звільняє свій слот на кроці 40, а не після завершення пакета.

Це не екзотична функція. llama-server описує -cb, --cont-batching як «чи потрібно ввімкнути неперервне пакетування (також відоме як динамічне пакетування) (типово: увімкнено)», а vLLM побудований навколо цієї ідеї. Ollama також обслуговує паралельні запити. Типове налаштування лише обмежує кількість до одного. Саме тому багато хто робить висновок, що їхнє обладнання не підтримує конкурентне виконання, хоча насправді це заборонено конфігурацією.

Опубліковані результати для неперервного пакетування зазвичай отримують на серверних відеокартах, які мають і вільні обчислювальні ресурси, і десятки гігабайтів пам’яті для кешу. Загальна форма цих результатів застосовна і до вашого комп’ютера. Їхній масштаб — ні. Причину пояснено в розділі про пам’ять нижче.

Prefill конкурує з decode за ті самі обчислювальні ресурси

Коли надходить новий запит, поки чотири відповіді передаються потоком, спочатку потрібно виконати prefill його промпту, а prefill потребує значних обчислень. Якщо планувальник виділяє цьому prefill окремий крок, чотири користувачі, які отримують потокові відповіді, протягом цього кроку не отримують жодного токена. Для довгого промпту це помітна пауза в кожному відкритому вікні. Саме це мають на увазі, коли кажуть, що сервер починає підвисати щоразу, коли хтось інший натискає кнопку надсилання.

Chunked prefill розбиває довгий промпт на частини та об’єднує кожну частину в один крок із decode для поточних запитів. У посібнику з налаштування vLLM цей компроміс описано безпосередньо: менші бюджети chunk "забезпечують кращий ITL, оскільки менше операцій prefill сповільнюють decode", а більші значення "забезпечують кращий час до першого токена (TTFT), оскільки за один batch можна обробити більше токенів prefill". Ви обираєте, чий досвід захистити: користувача, який чекає на початок відповіді, чи користувачів, які спостерігають за передаванням тексту потоком.

Довжина промпту визначає масштаб цієї проблеми. Промпт на 6,000 токенів із відповіддю на 200 токенів означає 6,000 токенів роботи prefill проти 200 кроків decode. Retrieval-augmented chat і довгі системні промпти часто призводять саме до такого співвідношення. Тому prefill перестає бути незначною складовою та стає основною операцією, завершення якої очікують користувачі. Prefix caching допомагає, коли довга частина повторюється: vLLM надає --enable-prefix-caching, який повторно використовує cache для спільного префікса промпту замість повторного обчислення для кожного запиту.

Першою вичерпується пам’ять під KV cache

Кожен токен у кожному активному діалозі залишає key vector і value vector у кожному шарі моделі. Це KV cache (key/value cache). Він дає змогу під час декодування не обчислювати весь prompt заново для кожного нового токена. Розмір cache на один токен визначається структурою моделі: 2 (один key і один value), помножене на кількість шарів, кількість key/value heads, розмірність head і кількість bytes на одне значення. Візьміть ці числа з config.json моделі.

Виконайте цей розрахунок один раз, і граничний обсяг пам’яті перестане бути загадкою. Типова модель 8B із 36 шарами, 8 key/value heads і head dimension 128, яка зберігає cache у 16-bit, потребує 2 36 8 128 2 bytes на один токен. Це 147,456 bytes, або приблизно 144 KiB. Для одного діалогу на 8,192 токени потрібно приблизно 1.2 GB cache. Для п’яти таких діалогів потрібно приблизно 6 GB додатково до ваг. Саме це визначає, скільки користувачів може одночасно обслуговувати система.

Паралельність збільшує потрібний context, і інструменти прямо це показують. У FAQ Ollama зазначено: "Parallel request processing for a given model results in increasing the context size by the number of parallel requests. For example, a 2K context with 4 parallel requests will result in an 8K context and additional memory allocation." Потрібний обсяг RAM масштабується як OLLAMA_NUM_PARALLEL, помножене на OLLAMA_CONTEXT_LENGTH. У llama-server context, який ви задаєте через -c, розподіляється між -np слотами. Тому збільшення кількості слотів саме по собі зменшує обсяг, доступний для кожного запиту. Беріть context на один слот із startup log, а не визначайте його навмання.

vLLM натомість заздалегідь резервує пам’ять. --gpu-memory-utilization (типове значення 0.92) — це "the fraction of GPU memory to be used for the model executor". Пам’ять, що залишилася після завантаження ваг, стає paged 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.

У V1 engine vLLM типовий режим preemption — RECOMPUTE. Тому витіснений запит втрачає свій cache і повторно виконує prefill після повернення до обробки. Ця робота виконується двічі. У документації попереджено, що "preemption and recomputation can adversely affect end-to-end latency". Цей рядок журналу найкраще пояснює, чому один невдалий користувач чекав значно довше за інших, хоча середня затримка залишалася нормальною. Установіть disable_log_stats=False, щоб журналювати сумарну кількість таких випадків, або зчитайте лічильник preemption із Prometheus metrics, які надає vLLM.

Що змінюється за 2, 5 і 20 одночасних користувачів

Два користувачі. На GPU із вільним місцем у cache вплив майже непомітний, оскільки другий потік декодування працює паралельно з першим і потребує зовсім небагато додаткового часу. На VPS лише з CPU та 4 to 8 GB RAM це не безкоштовно: обидва потоки використовують ті самі vCPU і пропускну здатність RAM, тому кожен користувач отримує приблизно половину tokens per second, а потреба в cache подвоюється за значно меншого доступного обсягу пам’яті.

П’ять користувачів. На цьому рівні стандартних параметрів уже недостатньо, і проблема спочатку перетворюється на чергу. Якщо OLLAMA_NUM_PARALLEL дорівнює 1, четверо користувачів чекають на того, хто запросив довгу відповідь, і кожен отримує нормальну швидкість, коли настає його черга. Збільшення кількості паралельних запитів змінює характер проблеми: п’ять слотів із контекстом 8K для кожного потребують cache на 40K токенів. Якщо він не вміщується у VRAM, engine вивантажує шари до системної RAM. Якщо його не вміщує і RAM, система починає використовувати swap, а tokens per second різко падає.

Двадцять користувачів. Двадцять людей у chat UI зазвичай не означають двадцять одночасних запитів. Це найважливіше зрозуміти перед придбанням обладнання. Людина читає відповідь і думає 20 to 60 секунд між повідомленнями, тому більшу частину часу її сесія простоює. Двадцять agents або двадцять завдань із підсумовування документів — це двадцять реальних потоків без жодного простою. Для цього потрібна інша конфігурація сервера. Розробник, який підключив coding agent до власного Ollama server, ближчий до другого сценарію, ніж до першого, оскільки agent безперервно надсилає запити, доки виконується завдання, і не має пауз на читання, які робить людина.

Чи є ваші користувачі одночасно активними, чи лише авторизованими?

Спочатку визначте кількість запитів у процесі, а вже потім розраховуйте ресурси. Розрахунок простий: кількість запитів у процесі дорівнює кількості користувачів, помноженій на кількість секунд генерації одного повідомлення та поділеній на кількість секунд між повідомленнями.

  1. Спочатку виміряйте швидкість для одного потоку на власній системі — і для prefill, і для decode. Не використовуйте показники чужої відеокарти: виміряйте кількість токенів за секунду на власній системі і використайте отримане значення.
  2. Оцініть коефіцієнт активності. Двадцять користувачів чату, 12 секунд генерації на повідомлення та одне повідомлення кожні 90 секунд дають 20 * 12 / 90, тобто приблизно 2.7 запиту в процесі.
  3. Встановіть кількість слотів трохи більшою за це значення, а потім перевірте її за пам’яттю: кількість слотів, помножена на контекст одного запиту, має вміщуватися в доступну кількість токенів cache.
  4. Обмежте чергу, щоб зайві запити швидко завершувалися з помилкою, яку легко помітити.

Доступна кількість токенів cache — це обсяг вільної пам’яті після завантаження ваг, поділений на вартість одного токена, наведену в попередньому розділі. На відеокарті обсягом 24 GB, де запущено модель 8B у 16-bit, ваги займають приблизно 16 GB, а для cache за стандартного рівня використання залишається близько 6 GB. Цього вистачає приблизно на п’ять діалогів по 8K. Щоб умістити більше діалогів, зменште контекст окремого запиту або зберігайте cache у 8-bit (llama-server займає --cache-type-k q8_0). Обидва варіанти збільшують паралельність, але потребують компромісів. Перед витратами на обладнання варто ознайомитися з реальними умовами цього компромісу: коли GPU VPS стає вигіднішим за API-токени.

Коли стандартних параметрів Ollama вже недостатньо

Збільшуйте кількість паралельних запитів через unit служби, оскільки експорт змінної в shell не буде доступний демону, яким керує systemd.

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 ps

systemctl show має вивести три змінні, які ви щойно встановили. Якщо цього не сталося, drop-in не зберігся, і подальші дії не матимуть результату. ollama ps потім показує завантажену модель із розміром, більшим за самі ваги, оскільки чотири слоти по 8,192 токенів резервують додатково 32,768 токенів кешу. Якщо в колонці PROCESSOR частина моделі розміщена в CPU, хоча ви очікували повного розміщення на GPU, це означає, що для кешу потрібно більше пам’яті, ніж залишилося на карті. Зменште одне з двох значень. Зазвичай безпечніше зменшити контекст, але надто мале вікно непомітно обрізає довгі запити замість того, щоб повертати помилку. Тому варто свідомо визначити значення num_ctx, а не зменшувати його доти, доки модель не поміститься.

Стандартне значення черги потребує окремої уваги. Ollama ставить у чергу до OLLAMA_MAX_QUEUE запитів, і «стандартне значення — 512». Після цього вона повертає «помилку 503 із повідомленням, що сервер перевантажений». Черга глибиною 512 на сервері, який одночасно обслуговує чотири запити, — це невиконувана обіцянка, оскільки клієнт на позиції 300 дочекається тайм-ауту задовго до початку обробки. Коротка черга повертає помилку, яку застосунок може повторити або показати користувачеві. Це краще, ніж індикатор очікування, який ніколи не завершується.

Перевірте це на практиці. Надішліть два запити одночасно з двох терміналів і спостерігайте за обома. Якщо другий запит не дає результату, доки не завершиться перший, параметр паралельної обробки не застосувався.

Коли повноцінний serving engine починає виправдовувати витрати

vLLM виправдовує додаткове налаштування, коли у вас є GPU із запасом ресурсів і одночасно справді обробляється понад приблизно чотири запити. Його планувальник працює на рівні токенів, кеш розподіляється сторінками, тому вільні фрагменти можна використовувати повторно, а вільна VRAM перетворюється на паралельну обробку замість простою. Станом на August 2026 документовані встановлення та запуск виконуються двома командами:

uv pip install vllm --torch-backend=auto
vllm serve Qwen/Qwen2.5-1.5B-Instruct
curl 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, означає, що сервер запущений, а модель завантажена. Під навантаженням важливі два параметри: --max-num-seqs — «максимальна кількість послідовностей, які обробляються за одну ітерацію», і --max-num-batched-tokens — «максимальна кількість токенів, які можна обробити за одну ітерацію». Перший обмежує паралельність. Другий визначає бюджет поетапного prefill, описаний вище.

Якщо одночасно обробляється менше приблизно чотирьох запитів або сервер не має підтримуваного GPU, vLLM додає складності, але майже не дає переваг. Йому потрібна відеокарта класу CUDA, і під час запуску він резервує більшу частину пам’яті. Для VPS із 4 до 8 GB це невдалий компроміс. У такому середовищі краще використати меншу модель із коротшим контекстом і чергою, якою ви керуєте самостійно. У розділі чим Ollama відрізняється від vLLM як serving engine вибір розглянуто повністю, а в розділі як запустити Qwen 3 8B на VPS показано, які ресурси потребує модель середнього розміру ще до підключення додаткового користувача.

Компроміс, який приховує загальноприйнята думка

Continuous batching підвищує загальну пропускну здатність і зазвичай також покращує медіанну затримку, оскільки запит із черги починає виконуватися раніше. Tail latency змінюється в протилежний бік, але про цю частину рідко згадують.

Кожна додаткова послідовність у кроці додає трохи роботи, тому ITL зростає для всіх у міру заповнення batch. Prefill нового запиту займає частину кроку, яка інакше була б доступна користувачам зі streaming-виведенням. За дефіциту cache scheduler виконує preemption, через що частково згенерований запит повертається на початок свого prefill.

Chat UI показує tail latency, а не середні значення. Потік, який переривається на дві секунди посеред речення, виглядає несправним, навіть якщо загальний час до завершення хороший. Вимірюйте p95 TTFT і p95 ITL під навантаженням, яке ви очікуєте, а середню кількість tokens per second розглядайте як показник місткості, а не як опис користувацького досвіду.

Практичне налаштування випливає з цього. Обмежте concurrency трохи нижче рівня, який допускає доступна memory, щоб engine ніколи не був змушений виконувати preemption. Коротка передбачувана queue краща за глибокий batch, який постійно thrash-иться, оскільки користувачеві легше зачекати чотири секунди, а потім отримувати плавний stream, ніж почати одразу й двічі зіткнутися з паузою.

Що перевірити, якщо система працює повільно

Кожен запит обробляється нормально, але очікування довге. Це черга, а не проблема швидкодії. Спочатку перевірте параметр паралельності. Модель працює правильно, але обробляє по одному запиту за раз.

Ollama повертає HTTP 503. Черга заповнена. Або сервер справді досягнув граничного навантаження, або OLLAMA_MAX_QUEUE навмисно встановлено на низьке значення для відхилення надлишкового навантаження. Саме так цей параметр і має працювати.

Кількість токенів за секунду різко зменшується під навантаженням на сервері з CPU. Запустіть vmstat 1 під час проблеми. Ненульові значення у стовпцях si і so означають, що система використовує swap. Тому ваги моделі зчитуються з диска для кожного токена. Зміна конфігурації не усуне цю проблему. Зменште розмір моделі або кількість слотів.

Один із десяти користувачів очікує значно довше за інших. Знайдіть у журналі vLLM рядок preempted. Зазвичай причина полягає у витісненні та повторному обчисленні. Це означає, що кеш переповнений для дозволеної довжини контексту.

TTFT має високі значення, навіть коли сервер простоює. Це проблема prefill, а не паралельності. Обробка довгих промптів займає час до появи першого токена, тому спочатку перевірте розмір промпта та prefix caching, а вже потім — апаратне забезпечення. Якщо тривале очікування виникає лише для першого запиту після періоду бездіяльності, а всі наступні запити обробляються нормально, причина не в prefill. Ollama вивантажує модель і знову зчитує ваги з диска. Це можна перевірити, залишивши модель завантаженою між запитами.

FAQ

Чому мій self-hosted LLM сповільнюється, коли ним користується ще одна людина?

Найчастіше він взагалі не сповільнюється. Запити стають у чергу. Ollama постачається з параметром OLLAMA_NUM_PARALLEL, встановленим у значення 1, тому другий запит очікує, доки перший не згенерує свій останній токен. Щоб розрізнити ці випадки, виміряйте час генерації для одного користувача, поки інший очікує: якщо після початку генерації швидкість становить нормальну кількість токенів за секунду, це черга, і збільшення кількості паралельних запитів усуне проблему. Якщо обидва потоки працюють удвічі повільніше, пам’ять справді спільно використовується, і це апаратне обмеження.

Скільки одночасних користувачів може обслуговувати одна невелика GPU?

Розраховуйте обсяг пам’яті, а не кількість користувачів. Спочатку врахуйте ваги моделі, потім KV cache, для якого на кожен токен і кожну активну розмову потрібно 2 × кількість шарів × кількість key/value heads × розмірність head × кількість байтів. Типова модель 8B із 36 шарами, 8 key/value heads і розмірністю head 128 потребує приблизно 144 KiB на токен у 16-бітному форматі, тому для розмови на 8,192 токенів потрібно близько 1.2 GB. На карті 24 GB, де ця модель зберігається у 16-бітному форматі, для cache залишається приблизно 6 GB. Цього вистачає приблизно на п’ять розмов із повним контекстом або на більшу кількість розмов, якщо скоротити контекст.

Чи робить continuous batching відповідь кожного користувача повільнішою?

Медіанна затримка зазвичай зменшується, оскільки запити більше не очікують завершення всього batch. Затримка в хвості розподілу збільшується. Кожна додаткова послідовність додає роботу до кожного кроку декодування, prefill нового запиту займає частину кроку потокових користувачів, а перерваний запит має виконати prefill двічі. Вимірюйте p95 затримки між токенами, а не середнє значення, оскільки у вікні чату паузи помітні, а середнє значення їх приховує.

Чи варто збільшити OLLAMA_NUM_PARALLEL або перейти на vLLM?

Спочатку збільште кількість паралельних запитів. Це не потребує додаткових витрат і зводиться до заміни одного drop-in файлу. Так ви усунете поширений випадок, коли четверо людей очікують у черзі після однієї довгої відповіді. Обмеженням є пам’ять: паралельні запити збільшують обсяг контексту, який потрібно утримувати, тому стежте, чи не переміщуються шари до CPU. Переходьте на vLLM, коли маєте GPU із вільною VRAM і в роботі справді перебуває понад приблизно чотири запити. На цьому рівні paged cache і планування для кожного токена дають більше переваг, ніж створюють витрат.

Чи усунуть додаткові ядра CPU повільну роботу LLM-сервера?

Не для тієї частини роботи, яку користувачі помічають найбільше. Під час decode для кожного токена вся модель зчитується з пам’яті, тому швидкість обмежує пропускна здатність RAM. Додаткові ядра перестають допомагати, щойно пропускну здатність вичерпано. Prefill масштабується разом із кількістю ядер, тому більша їх кількість скорочує час до першого токена для довгих prompt. На VPS із 4 до 8 GB основним обмеженням зазвичай є обсяг пам’яті. Практичне рішення — менша модель або коротший контекст, а не більша кількість vCPU.