Коли десятки чи сотні користувачів одночасно звертаються до однієї мовної моделі, затримки ростуть, GPU-пам’ять забивається, а пропускна здатність падає. Канал IBM Technology пояснює, як механізми KV cache і paged attention з екосистеми VLLM дозволяють радикально зменшити латентність і витрати на інференс без оновлення «заліза».
![]()
Дві фази інференсу: де насправді виникають гальма
Щоб зрозуміти, навіщо потрібні оптимізації пам’яті, варто розділити роботу великої мовної моделі на дві ключові фази.
Prefill: обчислювальне «горло пляшки»
Після відправки довгого промпту користувач спочатку бачить паузу — «час до першого токена». Це фаза prefill:
- модель проганяє ввід крізь усі шари трансформера;
- будує математичне уявлення всього контексту;
- ще нічого не генерує, лише готується до першого вихідного токена.
Ця частина інференсу жорстко обчислювально обмежена: навантаження впирається в можливості самого GPU за FLOPS.
Decode: все впирається в пам’ять
Після появи першого токена починається decode:
- модель генерує по одному токену;
- кожного кроку їй потрібно «згадати» всі попередні токени;
- для цього вона постійно звертається до GPU-пам’яті.
На цьому етапі вже домінує не обчислювальна потужність, а доступ до пам’яті. Саме тут вступає в гру KV cache — і саме тут неефективне управління пам’яттю найболючіше б’є по латентності.
Що таке KV cache і чому без нього LLM повільні
У трансформері кожен шар для кожного токена рахує три вектори:
- query — «запит» поточного токена: що для мене важливо?
- key — «ключ», який дозволяє іншим токенам знайти цей токен;
- value — «зміст» цього токена, який враховується в увазі.
В авторегресивній генерації модель створює відповідь токен за токеном, але на кожному кроці змушена знову застосовувати механізм attention до всієї попередньої послідовності.
Без кешу це означає:
- при генерації 1000-го токена модель повторно рахує key і value для всіх 999 попередніх токенів;
- обчислення дублюються тисячі разів, що різко збільшує час відповіді.
KV cache розв’язує це просто:
- матриці key та value для вже оброблених токенів зберігаються в GPU-пам’яті;
- кожен новий токен рахує лише свої власні key, value, query;
- увага рахується поверх уже збереженої історії KV, а не перераховується з нуля.
Це типовий компроміс «пам’ять замість обчислень». Для довгих послідовностей він виправданий: головним вузьким місцем стає не обсяг операцій, а те, як саме організована пам’ять для цього кешу.
Де зникає пам’ять: фрагментація і марнування VRAM
Із KV cache з’являється новий виклик: як зберігати його ефективно, коли одночасно обслуговуються сотні запитів різної довжини?
Модель забрала 65% VRAM — і це тільки початок
Показовий приклад:
- 13-мільярдна модель рівня LLaMA 13B займає приблизно 26 ГБ VRAM лише під ваги на GPU з 40 ГБ;
- це близько 65% всієї пам’яті — ще до того, як надійшов перший запит;
- решта 35% мають вмістити KV cache для всіх активних сесій.
Саме в цих 35% традиційні сервіси LLM починають масово марнувати ресурси.
Фіксовані буфери і «гостя селять на цілий поверх»
Класичний підхід до KV cache у багатьох фреймворках:
- для кожного запиту заздалегідь виділяється великий безперервний блок пам’яті під максимальну довжину контексту;
- наприклад, при max context 2048 токенів, навіть якщо типовий користувач надсилає 200 токенів і отримує 300 — під нього все одно резервують місце під 2048.
Це призводить до:
- до внутрішньої фрагментації — значна частина виділеної області залишається порожньою;
- дослідження показують, що в таких конфігураціях 60–80% пам’яті KV cache фактично простоюють.
До цього додається зовнішня фрагментація: запити різної довжини залишають «дірки» між блоками, і в підсумку:
- загалом вільної пам’яті може бути достатньо;
- але немає одного великого безперервного сегмента, щоб розмістити новий запит.
Окрема проблема — дублювання системного промпту: якщо однаковий system prompt використовується в сотнях сесій, він часто зберігається окремо для кожної.
У сумі це різко обмежує кількість одночасних запитів, які може обробити одна GPU-карта, і вбиває масштабованість.
Paged attention: GPU-пам’ять як віртуальна пам’ять ОС
Paged attention пропонує інший підхід до зберігання KV cache, який багато в чому повторює ідею віртуальної пам’яті в операційних системах.
Розбиття кешу на «сторінки»
Замість одного монолітного буфера під кожен запит paged attention:
- розбиває KV cache на невеликі сторінки фіксованого розміру (за замовчуванням — по 16 токенів);
- кожна сторінка може бути розміщена в будь-якому місці VRAM, немає вимоги суцільного блоку;
- сторінки виділяються динамічно, за потреби, у міру росту послідовності.
Внаслідок цього:
- пам’ять використовується значно щільніше;
- внутрішня фрагментація різко зменшується;
- зовнішня фрагментація перестає бути критичною, тому що потрібні лише невеликі вільні шматки під сторінки.
Таблиця відображень замість жорстких блоків
Аби вся ця гнучкість не ламала логіку моделі, використовується проміжний рівень:
- легка таблиця сторінок відображає логічні адреси (які бачить модель) у фізичні (де сторінки реально лежать у VRAM);
- модель працює з безперервним логічним простором, навіть якщо фізично дані розкидані.
Таке «препакування» VRAM дозволяє:
- витискати максимум із тих самих 35% пам’яті під KV cache;
- піднімати кількість одночасних запитів без заміни GPU;
- уникати ситуацій, коли окрема велика відповідь блокує обслуговування інших.
Налаштування інференсу: що варто крутити в першу чергу
Механізми KV cache та paged attention уже реалізовані в open-source рушії VLLM. Щоб реально отримати виграш у продуктивності, важливо правильно виставити кілька параметрів.
1. Використання GPU-пам’яті під KV cache
Параметр GPU memory utilization визначає, яку частку доступної після завантаження ваг VRAM можна віддати під KV cache:
- типовий дефолт — 0,9 (90% залишку пам’яті);
- для стабільних навантажень має сенс підняти до 0,95, щоб вмістити більше одночасних запитів;
- якщо при сплесках трафіку з’являються Out-of-Memory (OOM) помилки, краще знизити до 0,8.
Рекомендація — обов’язково проганяти бенчмарки на цільовій моделі перед бойовим запуском. Для цього в екосистемі VLLM є інструмент guidellm.
2. Prefix caching: не рахувати однаковий початок по сто разів
Paged attention підтримує так званий prefix caching:
- кожен KV-блок хешується за своєю послідовністю токенів;
- якщо кілька запитів мають спільний префікс (наприклад, однаковий system prompt);
- вони просто посилаються на одну й ту саму область пам’яті, а не зберігають її дубльовано.
Практичні ефекти:
- у RAG-пайплайнах, багатотуровому чаті, кодових асистентах співпадіння префіксів у діапазоні 75–95% зустрічаються часто;
- час до першого токена різко скорочується, оскільки спільна частина prefill уже виконана й закешована;
- навантаження на GPU знижується, вивільняючи ресурси для decode.
3. Chunked prefill: щоб стрім не «заїкався»
За замовчуванням VLLM обробляє prefill до кінця, перш ніж знову перейти до decode. Це створює проблему:
- при надходженні довгих промптів стрімінг уже генерованих відповідей може «підвисати»;
- користувач сприймає це як стрибки латентності.
Chunked prefill вирівнює ситуацію:
- замість повного prefill нових запитів спочатку виконується decode для вже активних запитів;
- решта обчислювального бюджету заповнюється невеликими частинами prefill;
- завдяки цьому стрімінг стає стабільнішим, а нові промпти не блокують уже відкриті сесії.
Реальні розгортання показали, що з таким підходом можна отримати до 50% приросту пропускної здатності. Додатково рекомендується збільшити параметр max-num-batch-tokens понад 2048, щоб краще наситити GPU.
4. Спекулятивне декодування для низької латентності
Для сценаріїв, де важливіша моментальна відповідь, ніж максимальний загальний throughput, варто розглянути speculative decoding:
- паралельно з основною великою моделлю запускається менша «чернеткова» модель;
- вона пропонує послідовність наступних токенів наперед;
- велика модель одним проходом перевіряє цей фрагмент:
- якщо прогноз вдалий — весь шматок одразу приймається;
- якщо ні — помилкові токени виправляються.
З точки зору якості:
- вихід ідентичний тому, що дала б одна лише велика модель без спекулятивного шару;
- виграш досягається за рахунок того, що проста модель використовує «вільний» обчислювальний ресурс між зверненнями до пам’яті.
Однак за дуже високої одночасності користувачів цей підхід дає менше користі: GPU і так повністю завантажується батчами, і «вікна простою» майже немає. Тому:
- варто вмикати спекулятивне декодування там, де критична інтерактивна швидкодія;
- менший ефект — у чисто офлайн- або batch-сценаріях із максимальним завантаженням.
У складі VLLM доступний «нульововартісний» Ingram-спекулятор через прапорець --speculative-model Ingram, орієнтований на структуровані чи повторювані відповіді.
Підсумок
Гальма великих мовних моделей при пікових навантаженнях часто пов’язані не з архітектурою самої моделі, а з тим, як саме організовано сховище й доступ до контексту в GPU-пам’яті. Поєднання KV cache та paged attention:
- зменшує дублювання обчислень;
- усуває внутрішню й зовнішню фрагментацію VRAM;
- збільшує кількість одночасних запитів без оновлення GPU;
- покращує і час до першого токена, і загальний throughput.
Точні налаштування — частка VRAM під кеш, prefix caching, chunked prefill і, за потреби, спекулятивне декодування — перетворюють те саме «залізо» на значно ефективнішу платформу для LLM-сервінгу.
Джерело
YouTube: How KV Cache Speeds Up LLMs for Faster AI Models on GPUs


