Как устроена LLM · How LLMs work · урок 4 · 14 мин
Inference: context, sampling, KV cache и serving
Inference: context, sampling, KV cache, serving
Зачем
После урока ты объяснишь, что происходит, когда модель отвечает: почему первый token приходит с задержкой, а дальше текст «течёт», что делают temperature и top-p, сколько памяти съедает KV cache и зачем нужен сервер вроде vLLM. На интервью это вопросы про latency, throughput и «why does the server run out of memory with long chats», а в работе — настройка своего inference на GPU и выбор параметров для агента.
Два этапа: prefill и decode
Inference (прогон модели для ответа) идёт в два этапа.
Prefill (обработка входа): весь prompt проходит через модель за один параллельный проход. GPU здесь загружена вычислениями, а от длины входа зависит TTFT (time to first token, время до первого token).
Decode (генерация): дальше модель выдаёт по одному token за шаг. На каждом шаге нужно прочитать из памяти все веса ради одного нового token. Поэтому decode упирается не в скорость вычислений, а в memory bandwidth (пропускную способность памяти). Если в batch (пакете) идут сразу десятки запросов, одно чтение весов обслуживает их всех — поэтому загруженный сервер выдаёт в сумме намного больше tokens, чем один поток.
Отсюда грубая оценка потолка скорости для одного запроса: пропускная способность ÷ объём весов. Qwen3-8B в bf16 весит 16,4 ГБ. На GPU с памятью 1,79 ТБ/с потолок ≈ 109 tokens/с, на устройстве с 273 ГБ/с ≈ 17 tokens/с. В 4 битах (Q4_K, ~4,6 ГБ) потолки вырастают примерно в 3,5 раза. Реальная скорость ниже потолка, но порядок оценка даёт верный, и она объясняет главное: квантизация при хороших kernels (программах вычислений на GPU) ускоряет генерацию, потому что читать приходится меньше байт.
Context window: бюджет, а не склад
Context window (окно context) — предел длины одного вызова в tokens, причём ответ модели тоже входит в этот предел. У Qwen3-8B по карточке — 32 768 tokens «natively» и до 131 072 с методом растяжения YaRN.
Длинный context стоит четырьмя способами: деньгами (API считает каждый token), временем (prefill растёт с длиной входа), памятью (KV cache, ниже) и качеством — с ростом context точность вспоминания падает, это называют context rot. Поэтому большая часть работы с агентами — решать, что положить в context, а что оставить за его пределами.
Sampling: как выбирается следующий token
Модель выдаёт вероятности для всего словаря; решение, какой token взять, принимает decoding strategy (стратегия выбора).
- Greedy (жадный выбор) — всегда самый вероятный token. В библиотеке transformers это выбор по умолчанию; документация предупреждает, что на длинных текстах он начинает повторяться.
- Sampling (случайный выбор по распределению) —
do_sample=True. Его настраивают ручками ниже. - temperature — делит logits перед softmax. Меньше 1 — распределение острее, больше 1 — площе.
- top_k — выбирать только из k самых вероятных tokens.
- top_p (nucleus sampling) — из самых вероятных tokens, пока их суммарная вероятность не дойдёт до p.
Пример с числами: три кандидата с logits 2,0; 1,0 и 0,1.
| temperature | вероятности |
|---|---|
| 0,5 | 0,86 · 0,12 · 0,02 |
| 1,0 | 0,66 · 0,24 · 0,10 |
| 2,0 | 0,50 · 0,30 · 0,19 |
Производитель модели обычно пишет рекомендации. Карточка Qwen3-8B: в режиме thinking (рассуждение перед ответом) temperature 0,6, top_p 0,95, top_k 20; без него — 0,7, 0,8 и 20; и прямо: «DO NOT use greedy decoding, as it can lead to performance degradation and endless repetitions». Для извлечения данных и вызова tools берут низкую temperature, для текстов и идей — выше. И помни: даже temperature 0 на сервере не гарантирует одинаковых ответов — порядок вычислений в batch немного меняет числа. Поэтому evals прогоняют несколько раз.
KV cache: память, которая растёт с каждым token
Из-за causal mask token смотрит только назад, и keys и values прошлых tokens не меняются. Их считают один раз и хранят — это KV cache (кэш ключей и значений внимания). На каждом шаге decode модель считает query только нового token. Без кэша каждый шаг пересчитывал бы весь текст заново.
Размер на один token: 2 (key и value) × число слоёв × число KV heads × размер head × байт на число. Для Qwen3-8B из config.json: 2 × 36 × 8 × 128 × 2 байта = 147 456 байт, около 144 КиБ на token.
- context 32 768 tokens → 4,8 ГБ на один разговор;
- 131 072 tokens → 19,3 ГБ, больше самих весов (16,4 ГБ);
- десять пользователей по 32 768 tokens → 48 ГБ только на кэш.
Обрати внимание на «8 KV heads» при 32 query heads. Это GQA (grouped-query attention): несколько query heads делят одну пару key и value. С 32 KV heads кэш был бы вчетверо больше. Документация Hugging Face поэтому прямо советует GQA или MQA для чатов с длинным context.
Вывод: на сервере KV cache, а не веса, ограничивает, сколько разговоров идёт одновременно и какой длины. Рычаги: квантованный кэш (int8 или int4), выгрузка в оперативную память, короче максимальный context, общий префикс (одинаковый system prompt считают один раз — prefix caching).
Serving: от скрипта к серверу
model.generate() годится для опытов. Для пользователей ставят inference server. Стандарт для GPU — vLLM. Его основа — PagedAttention (статья 2023): KV cache хранится блоками фиксированного размера, как страницы виртуальной памяти в операционной системе. Это убирает фрагментацию, даёт «near-zero waste in KV cache memory» и, по статье, в 2–4 раза больше throughput (пропускной способности), чем у предыдущих систем, при той же задержке. Ещё vLLM делает continuous batching (новые запросы вливаются в batch на каждом шаге, а не ждут, пока закончится весь пакет), prefix caching, квантизацию (FP8, INT8, INT4, GPTQ/AWQ), tensor parallelism на нескольких GPU и отдаёт OpenAI-compatible API — клиентский код не меняется. Для GGUF на ноутбуке и Mac — llama.cpp и Ollama.
Метрики сервера: TTFT, tokens/с на пользователя, общий throughput, p95 latency. Главный компромисс: больше batch — выше общий throughput, но каждый пользователь получает tokens медленнее. Инженер выбирает максимальную длину context, число одновременных запросов, квант и параметры sampling под задачу — и проверяет это нагрузочным тестом.
Схема
Термины · Key terms
- inference
- прогон обученной модели, чтобы получить ответ
- prefill
- параллельная обработка всего входа и заполнение KV cache
- decode
- генерация по одному token за шаг; упирается в чтение весов из памяти
- TTFT (time to first token)
- время от запроса до первого token ответа
- context window
- предел длины одного вызова в tokens, вместе с ответом
- temperature
- делитель logits: ниже — увереннее, выше — разнообразнее
- top_p (nucleus sampling)
- выбор среди самых вероятных tokens с суммарной вероятностью p
- KV cache
- сохранённые keys и values прошлых tokens, чтобы не пересчитывать их
- GQA (grouped-query attention)
- несколько query heads делят одни keys и values; кэш меньше
- PagedAttention
- хранение KV cache блоками, как страницы памяти; почти без потерь
- continuous batching
- пополнение batch новыми запросами на каждом шаге генерации
- throughput
- сколько tokens или запросов сервер выдаёт в секунду всем пользователям
Сделай сам
Напиши 10 строк Python без пакетов: функцию kv_bytes(layers, kv_heads, head_dim, bytes_per_value, tokens) и функцию softmax(logits, temperature). Посчитай KV cache Qwen3-8B (36 слоёв, 8 KV heads, head_dim 128, bf16) для 8 000, 32 768 и 131 072 tokens и для 20 пользователей по 8 000 tokens. Затем выведи вероятности для logits [2.0, 1.0, 0.1] при temperature 0,2; 1,0 и 3,0. Запиши: сколько пользователей по 8 000 tokens поместится на GPU 32 ГБ, если веса в 4 битах занимают 4,6 ГБ, а 2 ГБ уходит на буферы.
Обсуди с тьютором: покажи расчёт и спроси, что даст кэш в fp8 вместо bf16 и чем за это платят.
Проверь себя · ответ под вопросом
1Почему первый token приходит с заметной задержкой, а дальше текст идёт ровно?
2Сколько займёт KV cache Qwen3-8B на 32 768 tokens и откуда формула?
3Почему temperature 0 не гарантирует одинаковые ответы на сервере?
Interview questions · US / UK
1Why is LLM decoding memory-bandwidth bound, and what does that imply for hardware choice and quantization?
2Scenario: our self-hosted model handles short chats fine but runs out of GPU memory when many users have long conversations. What is happening and what do you change?
3What do temperature and top-p do, and how would you set them for a data-extraction agent versus a copywriting tool?
Ключевые фразы · Say it aloud
- Prefill is compute bound, decode is memory-bandwidth boundprefill упирается в вычисления, decode — в память
- Time to first token grows with the prompt lengthTTFT растёт с длиной входа
- The KV cache grows linearly with context and concurrencyкэш растёт с длиной и числом разговоров
- Grouped-query attention shrinks the KV cacheGQA уменьшает KV cache
- vLLM uses paged attention and continuous batchingvLLM хранит кэш страницами и пополняет batch на ходу
- Lower temperature for extraction, higher for creative textнизкая temperature для извлечения, выше для текстов
- Context is a budget, not a dumping groundcontext — бюджет, а не склад
Материалы
- Transformers — Optimizing LLMs for Speed and Memory, раздел 3.2 — KV cache, расчёт его размера, MQA и GQA (15 минут)
- Transformers — Generation strategies — greedy, sampling, beam search (8 минут)
- Efficient Memory Management for LLM Serving with PagedAttention (arXiv 2309.06180) — почему KV cache тратит память и как PagedAttention это лечит (аннотация, 5 минут)
- vLLM documentation — возможности сервера: continuous batching, prefix caching, квантизация, OpenAI-compatible API (5 минут)
- Qwen/Qwen3-8B — model card — длина context, режимы thinking и рекомендованные параметры sampling (5 минут)