Стартовая конфигурация виртуальной машины под self-hosted LLM считается от конкретных весов и профиля запросов. Число параметров даёт лишь предварительную оценку: формула через bits-per-weight не учитывает таблицы квантования, метаданные, embeddings и тензоры с другой точностью. Первую оценку даёт сумма shard-файлов квантованной модели, зафиксированных по репозиторию и revision.

Перед расчётом записывают репозиторий и revision, хеш файлов, формат, квантование, версию рантайма, chat template и tokenizer. Для MoE-модели отдельно указывают общее и активное число параметров. Профиль трафика содержит p50, p95 и максимум входных токенов, длину ответа, число активных клиентов и настройки batch. Интерактивный режим оценивают по TTFT и межтокенной задержке, пакетный — по общему throughput и времени выполнения задания. Один пользователь с контекстом 4K расходует KV-кэш и CPU не так, как восемь одновременных последовательностей по 4K.

Компонент бюджета RAMЧто учитывает
Резидентные весаСумма shard-файлов квантованной модели после прогрева
KV-пулКонтекст, конкурентность, число KV-голов и слоёв
Compute-буферыВременные тензоры при batch и длинном промпте
Служебная память рантаймаМетаданные, tokenizer, chat template
Резерв ОСРазница между steady-state и повторяемым peak RSS

Базовую оценку полного KV-кэша записывают так: KV_bytes = 2 × L × H_kv × D_head × B_cache × T × S. Множитель 2 учитывает key и value, остальные переменные задают слои, KV-головы, размер головы, байты на элемент, токены и последовательности. В GQA или MQA используют уменьшенное число KV-голов, причём точность кэша может отличаться от квантования весов. Формула предполагает полный отдельный контекст для каждой последовательности, хотя paged cache, prefix sharing, sliding-window attention, гибридные слои и статическое резервирование меняют аллокацию. Метрика занятого KV-пула показывает расхождение с расчётом.

KV-кэш растёт линейно от контекста и конкурентности: формула учитывает слои, KV-головы, размер головы и число последовательностей.

Размер файлов нельзя просто складывать с RSS: при mmap одни и те же резидентные страницы видны и в page cache, и в отображении процесса. PSS распределяет общие страницы между процессами. Без mmap рантайм может создать отдельную копию весов, а при GPU-offload часть тензоров и буферов переносится в VRAM. Точное распределение видно в логе backend. Базовый замер охватывает загрузку, прогрев и пик целевого запроса. RSS/PSS дополняют memory.current, swap, page faults и строки лога о CPU-, GPU-, KV- и compute-буферах.

Дополнительные vCPU перестают помогать, когда генерация упирается в пропускную способность памяти или потоки пересекают NUMA-узлы. Prompt processing и generation насыщаются при разном числе ядер, поэтому прогон по числу потоков делают для обеих фаз: нагрузку на каждом шаге держат одинаковой, а affinity, SMT и версию рантайма записывают. Обработка промпта вычисляет много токенов параллельно и обычно лучше загружает ядра CPU. Авторегрессионная генерация добавляет по токену на последовательность, и при небольшом batch её предел часто задаёт чтение весов из памяти. В llama.cpp параметр -t задаёт число потоков.

Бюджет RAM складывается из резидентных весов, KV-пула, compute-буферов, служебной памяти рантайма и резерва ОС. Batch и длинный промпт поднимают пик за счёт временных тензоров. Память квантованной модели измеряют после прогрева: часть страниц выделяется только при первом обращении, и до этого RSS занижен. Единого процента запаса для всех систем нет — нижнюю границу задаёт разница между steady-state и повторяемым peak RSS. Rolling update может временно держать две версии модели и увеличивать page cache, поэтому такой сценарий учитывают в запасе либо выносят в отдельное окно.

Контрольный прогон выполняют без swap, который скрывает дефицит RAM ценой задержки. Конфигурация подходит, если memory.current остаётся ниже лимита на величину заявленного запаса, разница memory.events до и после не содержит max/oom, PSI memory не растёт, а p95 и p99 выполняют SLO. При неизвестном профиле лучше проверить несколько заданных сценариев, а не усреднять их в искусственный запрос.