KV Cache на GPU с 16 ГБ: Как действительно уместить длинный контекст

Почему контекст 128K не работает на 16 ГБ

Содержимое страницы

Модель может заявлять о поддержке контекстного окна в 128K, но проваливаться уже на 40K токенах на видеокарте с 16 ГБ. Архитектурный потолок никогда не обещал, что веса, KV-кэш, вычислительные буферы и системный композитор смогут одновременно уместиться на вашем устройстве.

KV-кэш — это обычно то место, где планы по длинному контексту упираются в физический лимит. Он растёт с каждым активным токеном и последовательностью, поэтому конфигурация, которая казалась комфортной при запуске, может резко замедлиться, переполниться в системную память или завершиться ошибкой во время крупного prefill.

Бюджет памяти KV-кэша на GPU 16 GB

Это руководство превращает проблему в бюджет VRAM. В нём рассматриваются формула кэша, воспроизводимые таблицы размеров для 32K–128K, рабочие конфигурации для --cache-type-k и --cache-type-v в llama.cpp, paged и prefix caching в vLLM, а также средства управления контекстом в Ollama — плюс экспериментальные ветки с адаптивным кэшем, которые заслуживают внимания, но не слепого доверия. Чтобы получить более широкий контекст по производительности, задержкам и бенчмаркам, стоящим за этими цифрами, начните со страницы-хаба по производительности LLM.

Краткий ответ для GPU с 16 ГБ

Начните с одной последовательности, реалистичного максимального контекста, Flash Attention и KV-кэша с 8-битным разрешением. Измерьте эту конфигурацию, прежде чем пытаться применять 4-битный кэш, выгрузку в CPU, несколько параллельных слотов или экспериментальные ветки.

Целевая задача Разумная первая попытка на 16 ГБ Основной риск
32K Веса Q4 или Q5, KV Q8, одна последовательность Веса модели оставляют слишком мало места для буферов
64K Меньшая модель или агрессивное квантование весов, KV Q8 Задержка prefill и пропускная способность кэша
128K Меньшая GQA-модель, KV Q8 или проверенный Q4, одна последовательность Только кэш может занять большую часть VRAM
Два одновременных сеанса по 64K Рассматривайте как бюджет кэша примерно в 128K Параллельная мощность ошибочно принимается за бесплатную производительность

Моё мнение просто: стабильная конфигурация на 64K обычно полезнее, чем номинальная конфигурация на 128K, работающая на грани ошибки out-of-memory (нехватки памяти). Ёмкость контекста — это не трофей; это компромисс между задержками, качеством и конкурентностью.

Что хранит KV-кэш

Во время авторегрессионной генерации каждый слой внимания генерирует тензоры ключей (key) и значений (value) для каждого обработанного токена. Рантайм сохраняет эти тензоры, чтобы следующий токен мог обращать внимание на предыдущие токены, не пересчитывая весь префикс заново.

Кэш экономит огромные вычислительные ресурсы, но потребляет память пропорционально количеству сохраняемых токенов. Для обычного трансформера с групповым вниманием (grouped-query attention) полезной базовой формулой является:

Байты KV = последовательности * токены * слои * 2 * KV-головы * размер головы * байт на значение

Коэффициент 2 представляет собой ключи и значения. Многоголовное внимание использует столько же KV-голов, сколько и query-голов; групповое внимание использует меньше KV-голов; многоголовое латентное внимание (multi-head latent attention) или гибридные рекуррентные архитектуры требуют других расчётов.

Почему количества параметров недостаточно

Две модели 8B могут иметь очень разные затраты KV-кэша. Одна может использовать 32 слоя и 8 KV-голов, а другая — меньше KV-голов, общие KV-слои, скользящее окно внимания (sliding-window attention) или сжатые латентные состояния.

Количество параметров в основном предсказывает память под веса. Геометрия KV-кэша исходит из архитектуры внимания, поэтому читайте метаданные модели, а не догадывайтесь по надписи 8B, 27B или размеру файла GGUF. Самый наглядный пример — насколько далеко продвинулся дизайн внимания за пределы обычного многоголового внимания (MHA):

  • Multi-Query Attention (MQA) разделяет одну K/V-голову между всеми query-головами — максимальная экономия кэша, но это самый агрессивный компромисс качества и редко используется изолированно в современных передовых моделях.
  • Grouped-Query Attention (GQA) группирует query-головы в кластеры, которые по очереди разделяют одну K/V-голову — основной компромисс, используемый большинством открытых dense-моделей, и геометрия, которую предполагает формула выше.
  • Multi-Head Latent Attention (MLA), представленные в DeepSeek-V2 и сохранённые в DeepSeek-V3 и Kimi K2, предлагают совершенно иной подход: вместо разделения K/V между головами они проецируют ключи и значения в сжатый низкоранговый латентный вектор и восстанавливают K/V полного разрешения по требованию во время внимания. DeepSeek сообщили о снижении KV-кэша примерно на 93% по сравнению с dense MHA-моделью того же размера, сохраняя при этом конкурентное качество — иногда превосходящее GQA — при том же бюджете памяти.

Практическое следствие состоит в том, что “GQA-модель 27B” и “MLA-модель 27B” могут иметь следы KV-кэша, отличающиеся на порядок величины при одной и той же длине контекста. Не предполагайте, что формула выше применима к модели, которая документирует использование латентного внимания, состояния в стиле DeltaNet или слоёв со скользящим окном — сначала проверьте раздел архитектуры в карточке модели.

В Ollama команда ollama show MODEL --verbose открывает метаданные модели, включая количество слоёв, attention-головы, KV-головы и длину контекста, если формат их предоставляет. В llama.cpp вывод загрузчика модели, печатаемый при запуске, обычно содержит эквивалентные метаданные GGUF и фактическое распределение кэша рантаймом.

Лимит модели, выделенный контекст и использованный контекст

Это три разные цифры. Лимит модели — это максимальная поддерживаемая величина, ограниченная обучением и позиционным кодированием; выделенный контекст — то, что резервирует или разрешает рантайм; использованный контекст — токены, фактически сохранённые в данный момент для последовательности.

Изменение флага движка не может безопасно расширить модель за пределы её поддерживаемой схемы позиций. Масштабирование RoPE может расширить некоторые архитектуры, но это эксперимент по качеству модели, а не оптимизация KV-памяти.

Таблица размеров KV-кэша: бюджет для контекстов 32K, 64K и 128K

Рассмотрим представительскую GQA-модель с 32 слоями, 8 KV-головами и размером головы 128. Эти параметры дают 65 536 элементов ключей и значений на токен до умножения на размер хранения каждого элемента.

В таблице используются двоичные ГиБ (GiB) и физические размеры блоков, обычно ассоциируемые с f16, q8_0 и q4_0 в llama.cpp. Это базовый расчёт, а не обещание относительно общей памяти процесса; выравнивание, метаданные, гибридные слои и рабочие области бэкенда добавляют накладные расходы.

Тип кэша Прибл. байт на хранимое значение Контекст 32K Контекст 64K Контекст 128K
F16 2.0000 4.00 ГиБ 8.00 ГиБ 16.00 ГиБ
Q8_0 1.0625 2.13 ГиБ 4.25 ГиБ 8.50 ГиБ
Q4_0 0.5625 1.13 ГиБ 2.25 ГиБ 4.50 ГиБ
Q8_0 для K и Q4_0 для V Смешанный 1.63 ГиБ 3.25 ГиБ 6.50 ГиБ

Теперь удвоим количество слоёв до 64, оставив другие параметры без изменений. FP16-кэш станет 8 ГиБ при 32K, 16 ГиБ при 64K и 32 ГиБ при 128K, что демонстрирует, почему одна рекомендация по контексту не может охватывать каждую модель.

Реальное уравнение для 16 ГБ

Практический бюджет шире, чем формула KV:

Доступный VRAM = Общий VRAM - резерв под рабочий стол и драйвер

Бюджет KV = Доступный VRAM
          - веса модели, загруженные в GPU
          - буферы графа и активаций
          - рабочая область рантайма
          - состояние спекулятивной декомпозиции (speculative-decoding)
          - запас безопасности

На видеокарте с подключённым дисплеем (16 ГБ) не планируйте так, будто все 16 ГиБ доступны. Резервируйте хотя бы несколько сотен МиБ под рабочий стол и драйвер, затем оставьте ещё один запас под зависящие от нагрузки буферы; 1.0–1.5 ГиБ общего запаса — разумное предположение для старта, но ваши логи — высший авторитет.

Допустим, модель в формате GGUF занимает 10.8 ГиБ на GPU, а накладные расходы рантайма пиково достигают 1.2 ГиБ. После вычета запаса в 1 ГиБ для KV остаётся только около 3 ГиБ, поэтому представительская модель уместит примерно 45K токенов с Q8_0 или 87K с Q4_0, до учёта накладных расходов конкретного движка.

Это не означает автоматически, что Q4_0 — правильный выбор. Если точность при длинном контексте падает в вашей задаче, меньшая или более агрессивно заквантованная модель с KV-кэшем Q8_0 может быть лучше, чем большие веса в паре с хрупким кэшем. Измеренные опорные точки для именно этой арифметики находятся в таблицах бенчмарков llama.cpp для VRAM 16 ГБ, где VRAM для каждой модели зафиксирован при 19K, 32K и 64K контексте. Для более широкого обзора того, какие размеры моделей и уровни квантования хорошо работают в Ollama на видеокартах того же класса, см. Сравнение производительности LLM в Ollama на GPU с 16GB VRAM.

Рассчитайте бюджет KV-кэша для вашей модели

Следующий фрагмент на Python оценивает кэш для обычного GQA с полным вниманием. Замените геометрические параметры значениями из конфигурации модели или метаданных GGUF.

def kv_gib(tokens, layers, kv_heads, head_dim, bytes_per_value, sequences=1):
    total = (
        sequences
        * tokens
        * layers
        * 2
        * kv_heads
        * head_dim
        * bytes_per_value
    )
    return total / (1024 ** 3)


model = {
    "layers": 32,
    "kv_heads": 8,
    "head_dim": 128,
}

types = {
    "f16": 2.0,
    "q8_0": 34 / 32,
    "q4_0": 18 / 32,
}

for tokens in (32768, 65536, 131072):
    row = {
        name: round(kv_gib(tokens=tokens, bytes_per_value=size, **model), 2)
        for name, size in types.items()
    }
    print(tokens, row)

Пропорции для Q8_0 и Q4_0 включают простые метаданные блоков, поэтому они немного больше, чем ровно один байт и полбайта на значение. Отчёт о запуске рантайма остаётся более точным, поскольку он знает специфичные для модели раскладки кэша.

Когда эта формула неверна: гибридные и архитектуры со скользящим окном

Не пытайтесь насильно подогнать гибридные архитектуры под уравнение обычного GQA. Слои со скользящим окном сохраняют только недавнее окно, общие KV-слои уменьшают дублирование, рекуррентные слои могут хранить состояние фиксированного размера, а многоголовое латентное внимание хранит сжатое представление, а не тензоры K/V для каждой головы — случаем MLA выше является самым драматичным примером.

Современные движки всё чаще явно управляют этими смешанными раскладками. Используйте формулу, чтобы объяснить доминирующие члены, а затем подтвердите распределение, о котором сообщает точная сборка движка и бэкенд, которые вы планируете развернуть.

llama.cpp: прямое управление точностью K и V

llama.cpp предоставляет отдельные параметры --cache-type-k и --cache-type-v в своём текущем парсере аргументов. Это самый полезный интерфейс локального вывода, когда нужно балансировать точность кэша против ёмкости контекста, а не принимать один глобальный пресет. Если вам сначала нужна установка и настройка сервера, руководство по llama.cpp охватывает llama-cli, llama-server и ключевые флаги VRAM.

Консервативная конфигурация 64K для одного пользователя выглядит так:

./llama-server \
  --model /models/model.gguf \
  --n-gpu-layers 999 \
  --ctx-size 65536 \
  --parallel 1 \
  --flash-attn on \
  --cache-type-k q8_0 \
  --cache-type-v q8_0 \
  --batch-size 1024 \
  --ubatch-size 256

Синтаксис флагов и поддержка бэкендов быстро меняются, поэтому запуските llama-server --help для установленной сборки. Что ещё важнее, проверьте лог запуска: там должно быть указано требуемый контекст, типы кэша, выгрузка в GPU и выделенные буферы K и V.

Какие типы кэша в llama.cpp стоит попробовать

Начните с Q8_0 как для K, так и для V. Это примерно вдвое уменьшает память KV по сравнению с F16, и независимые тесты перплексии на моделях 20B и выше (Qwen3.6-27B, Nemotron-30B) показывают, что совокупный разрыв в качестве по сравнению с F16 находится в пределах погрешности измерения — это гораздо менее рискованный шаг, чем немедленный переход к Q4_0, который, по тем же тестам, приводит к падению скорости декодирования и точности при длинном контексте на малых моделях.

Если Q8_0 не помещается, протестируйте Q8_0 для ключей и Q4_0 для значений, прежде чем квантовать обе стороны до Q4_0. Этот порядок имеет научное обоснование, а не просто фольклор: контролируемые исследования распределения битов по чекпоинтам Llama, Phi-4, Qwen3 и Mistral показали, что тензоры ключей последовательно в два-десять раз чувствительнее к ошибкам квантования, чем тензоры значений, и что выделение ключам большего бюджета битов (например, 4-битные ключи и 2-битные значения) восстанавливает до 94–98% точности полного разрешения — тогда как инвертированный сплит (2-битные ключи, 4-битные значения) может привести к потере 30 процентных пунктов в задачах типа GSM8K. Ключи определяют, с какими предыдущими токенами внимание фактически совпадает, поэтому их защита — это архитектурно обоснованный выбор, а не просто более безопасное на слух решение.

Конфигурация Память Риск качества Рекомендация
F16 для K и V Максимальная Минимальный Базовый вариант, если хватает места
Q8_0 для K и V Около половины от F16 Низкий, но не нулевой Точка старта для 16 ГБ
Q8_0 для K, Q4_0 для V Между Q8 и Q4 Умеренный Полезный второй шаг
Q4_0 для K и V Около четверти от F16 Максимальный Проверить на целевой глубине

Стоит внутренне усвоить одно предупреждение: “низкий риск качества” в агрегированных бенчмарках не означает нулевой риск на уровне токенов. Контролируемый тест, в котором Flash Attention оставался неизменным, а менялась только точность KV при жадной (детерминированной) декодировке, показал, что Q8_0-кэш изменил точный сгенерированный текст в подавляющем большинстве промптов, а Q4_0 — практически во всех — как только один токен меняется, остальная часть продолжения может разойтись. Перплексия и оценки по downstream-задачам могут выглядеть нормально в среднем, хотя отдельные выходные данные всё ещё отличаются от F16-базы. Если вашему приложению нужна побайтовая воспроизводимость (тесты регрессии, кэшированные ответы, детерминированные агенты), относитесь к любому квантованию KV как к изменению поведения, а не просто к оптимизации памяти, и валидируйте результат на собственном фиксированном наборе промптов.

Заквантованный V-кэш может требовать Flash Attention или совместимого пути бэкенда. Сервер, который молча переключается на другой тип, инвалидирует эксперимент, поэтому логи запуска важнее, чем скопированные командные строки.

Контекст, параллельные слоты и унифицированный кэш

--ctx-size описывает ёмкость движка, а не гарантию того, что каждый параллельный слот получит столько токенов независимо. Управление кэшем в llama.cpp эволюционировало, включая поведение унифицированного кэша, поэтому тестируйте точную сборку, а не полагайтесь на старое правило, которое просто делит контекст на количество слотов.

Уравнение ёмкости всё ещё выживает при изменениях реализации: одновременные уникальные токены требуют хранилища где-то. Если два сеанса агента могут достигать 48K каждый, закладывайте бюджет на близкие к 96K живые токены,除非 нагрузка разделяет префиксы или допускает вытеснение (eviction) и пересчёт.

Размер батча не уменьшает хранимый KV

--batch-size и --ubatch-size влияют на обработку промптов и временную память. Их снижение может спасти крупный prefill от пиковой нагрузки на память активаций, но не меняет постоянных байтов, требуемых для каждого сохраняемого токена.

Это различие объясняет общий паттерн ошибок: модель стартует, и пустой запрос работает, но промпт на 60K падает во время ингреста. Уменьшите микро-батч, чтобы диагностировать временный пик; уменьшите контекст, точность кэша, параллелизм или резидентность весов, чтобы изменить постоянную ёмкость.

vLLM: Paged-ёмкость всё ещё есть ёмкость

vLLM подходит к проблеме как движок обслуживания. Он профилирует доступную память, резервирует пул KV-кэша и выделяет кэш блоками, чтобы параллельные последовательности не требовали от каждого один большой непрерывный регион. Если вы решаете, переходить ли вообще на vLLM, руководство по миграции с Ollama на vLLM охватывает сигналы нагрузки; здесь вопрос чисто в том, сколько кэша может удержать пул, а быстрый старт vLLM охватывает установку и общие флаги обслуживания, выходящие за рамки рычагов ёмкости ниже.

PagedAttention уменьшает фрагментацию и потери вокруг переменной длины последовательностей — paged-выделение устраняет фрагментацию, а не стоимость хранения на токен, поэтому один уникальный запрос на 128K всё ещё требует достаточно блоков для его KV-состояния.

Официальное руководство vLLM по экономии памяти рекомендует ограничивать max_model_len и max_num_seqs, когда память на исходе, и отмечает, что CUDA-графы потребляют дополнительную память GPU. На видеокарте 16 ГБ оба параметра должны быть осознанными, а не унаследованными от максимальных настроек модели.

Сервер, сфокусированный на одной последовательности, может стартовать так:

vllm serve MODEL_ID \
  --max-model-len 65536 \
  --max-num-seqs 1 \
  --gpu-memory-utilization 0.90 \
  --kv-cache-dtype fp8 \
  --enable-prefix-caching

Не каждый GPU с 16 ГБ, модель, метод квантования или бэкенд внимания поддерживает именно эту комбинацию. Рассматривайте это как форму конфигурации: ограничьте длину и конкурентность, резервируйте запас, выберите поддерживаемый dtype кэша и валидируйте отчёт об инициализации.

FP8 KV-кэш в vLLM

Текущая документация vLLM по заквантованному KV-кэшу поддерживает форматы FP8-кэша на совместимых путях CUDA и ROCm. FP8 примерно вдвое уменьшает сырое хранилище кэша по сравнению с BF16 или FP16 и, следовательно, может увеличить ёмкость по токенам или конкурентность.

Масштабирование важно. Документация различает масштабы по умолчанию, расчёт при прогреве (warm-up) и калибровку на датасете, и рекомендует калибровку на датасете для максимальной точности; просто установка FP8 с scale 1.0 удобна, но не является автоматически самым надёжным выбором для качества.

Prefix Caching — это оптимизация повторного использования

Автоматический prefix caching позволяет новому запросу переиспользовать KV-блоки для идентичного закэшированного префикса. Это отлично подходит для повторяющихся запросов к одному и тому же длинному документу, общих системных промптов и многоходовых разговоров, так как избегает пересчёта совпадающего prefill.

Это не делает уникальный длинный запрос меньше и не ускоряет генерацию новых токенов. Документация vLLM по prefix caching явно ограничивает пользу работой prefill с общим префиксом.

Использование памяти GPU не является бесплатной памятью

Повышение --gpu-memory-utilization даёт vLLM большую целевую резервацию, но не создаёт VRAM. Давление слишком близко к 1.0 может оставить недостаточно места для дисплея, другого процесса, меняющихся пиков активаций или не-PyTorch-выделений.

Начните с 0.88–0.92 на выделенном GPU 16 ГБ, проверьте профиль и повышайте только если нагрузка остаётся стабильной. Если инициализация успешна, но реальные промпты падают, уменьшите батченные токены, конкурентность последовательностей, захват CUDA-графов или максимальный контекст, прежде чем предполагать, что аллокатор сломан.

Ollama: более простые средства управления, менее гранулярная диагностика

Ollama намеренно предоставляет меньшую операционную поверхность. Его текущая документация по длине контекста по умолчанию назначает GPU ниже 24 ГиБ контекст в 4K, рекомендует минимум 64K для агентных и кодинг-нагрузок и предупреждает, что больший контекст потребляет больше памяти.

Задайте серверный стандарт по умолчанию и подтвердите загруженную модель так:

OLLAMA_CONTEXT_LENGTH=65536 ollama serve

ollama ps

Вы также можете настроить num_ctx для каждого запроса или модели. ollama ps важен, потому что его колонки PROCESSOR и CONTEXT показывают, осталась ли модель полностью на GPU и был ли фактически выделен запрашиваемый контекст. Помните, что поведение планировщика за этими цифрами изменилось между версиями Ollama; моё сравнение распределения памяти в Ollama v0.12.1 показывает, как новый планировщик вытесняет некоторые модели дальше в CPU на карте 16 ГБ, поэтому фиксируйте версию, которую вы измерили.

Заквантованный KV-кэш в Ollama

Ollama предоставляет OLLAMA_KV_CACHE_TYPE с вариантами f16, q8_0 и q4_0 в текущем FAQ. Заквантованный KV требует Flash Attention, который Ollama использует автоматически на поддерживаемых бэкендах или который можно запросить с OLLAMA_FLASH_ATTENTION=1.

Сервис длинного контекста на 16 ГБ можно поэтому запустить как:

OLLAMA_CONTEXT_LENGTH=65536 \
OLLAMA_FLASH_ATTENTION=1 \
OLLAMA_KV_CACHE_TYPE=q8_0 \
OLLAMA_NUM_PARALLEL=1 \
ollama serve

Q8_0 — рекомендуемая альтернатива F16 в Ollama. FAQ предупреждает, что Q4_0 может привести к более заметной потере качества, особенно при высоком контексте, поэтому это должен быть измеренный запасной вариант, а не автоматический пресет для 16 ГБ.

Параллелизм в Ollama умножает бюджет контекста

Ollama документирует особенно чёткое правило: требуемая память масштабируется как OLLAMA_NUM_PARALLEL * OLLAMA_CONTEXT_LENGTH. Четыре параллельных запроса при настройке 32K могут подразумевать совокупное выделение контекста в 128K для этой модели.

Для личного агента на 16 ГБ держите OLLAMA_NUM_PARALLEL=1, пока один длинный сеанс не станет стабильным. Очередь второго запроса обычно предпочтительнее, чем частичное вытеснение первой модели в CPU, что делает оба запроса медленными. Механики ожидания в очереди, ошибок 503 и разгрузки моделей, лежащие в основе этого выбора, описаны в том, как Ollama обрабатывает параллельные запросы.

Выгрузка в CPU: валидный запасной путь, но с ценой

Перемещение некоторых слоёв модели или состояния KV в системную RAM может превратить ошибку распределения в рабочий процесс. Это также помещает пропускную способность PCIe и задержку хост-памяти в путь декодирования, где каждый сгенерированный токен может платить за это. Данные о том, когда PCIe реально “кусает” по каналам и поколениям, находятся в Производительность LLM и линии PCIe.

Выгрузка может быть разумной для периодических batch-нагрузок, но редко является лучшим значением по умолчанию для интерактивного кодинг-агента. Сначала сравните меньшее квантование весов, KV Q8, уменьшенную конкурентность и реалистичный лимит контекста; используйте выгрузку, когда ёмкость важнее задержки.

Следите за обрывом, а не за средним. Сервер может декодировать быстро на 8K, а затем резко замедлиться после того, как часть рабочего набора вытеснится, поэтому бенчмарьте при 32K, 64K и целевом максимуме, а не сообщайте только скорость токенов при пустом контексте.

Скользящее окно и адаптивные KV-кэши

Внимание со скользящим окном меняет бюджет, сохраняя только недавнее окно для выбранных слоёв. Гибридные модели могут комбинировать эти слои с периодическим глобальным вниманием или рекуррентным состоянием, заставляя плоский расчёт полного контекста существенно переоценивать или неверно размещать память.

Эта оптимизация является частью архитектуры модели, а не общим переключателем, который можно применить без последствий. Движок должен правильно понимать паттерн слоёв, правила вытеснения, позиции и любые глобальные токены.

Что пытается улучшить адаптивный KV

Экспериментальные ветки идут дальше, выбирая точность или раскладку кэша для каждого слоя и глубины контекста. Цель привлекательна: сохранить более высокую точность там, где это важно, сжать менее чувствительные слои и менять микс до того, как давление VRAM вызовет жёсткое вытеснение — тот же вывод о чувствительности ключей выше чувствительности значений, описанный выше, — это именно тот сигнал, который адаптивный аллокатор хотел бы эксплуатировать автоматически, а не оставлять ручную настройку --cache-type-k/--cache-type-v.

Один downstream-проект августа 2026 года, llama.cpp-adaptive-turboquant, сообщает об автоматическом селекторе для нескольких режимов адаптивности по слоям и публикует тесты на большой глубине на RTX 5080 16 ГБ. Эти числа — результаты, заявленные автором, из специализированной ветки, а не доказательство того, что upstream llama.cpp ведёт себя так же.

Почему это всё ещё экспериментально

Ветка комбинирует собственные типы кэша, CUDA-кернели, специфичные для модели пути и ограничения тулчейна. Это гораздо больше кода, которому нужно доверять, чем переключение хранилища кэша upstream с F16 на Q8_0.

Используйте такую ветку только тогда, когда upstream не может выполнить реальное требование, и вы можете воспроизвести качество, стабильность и скорость на вашей модели. Запишите коммит и версию CUDA, потому что результат, привязанный только к имени проекта, не воспроизводим.

Честный тест адаптивного кэша

Сравнивайте ветку с базовой Q8_0-конфигурацией upstream при одном и том же GGUF, промпте, сэмплере, глубине контекста и длине вывода. Измеряйте VRAM при старте, пиковый VRAM во время prefill, скорость обработки промптов, скорость декодирования и задачу качества, которая действительно требует доказательств из самой старой части контекста.

Не принимайте успешное распределение за полный результат. Кэш может уместить 128K и всё равно потерять ранние факты, исказить вывод в конце последовательности или декодировать слишком медленно, чтобы быть полезным.

Пример настройки 16 ГБ: одна переменная за раз

Самый быстрый путь к стабильной конфигурации — изменять одно измерение памяти за раз. Случайное изменение типа кэша, размера батча, выгрузки слоёв, параллелизма и контекста вместе приводит к рабочей команде без объяснений.

flowchart TD A["Шаг 1: загрузить при 8K контексте, одна последовательность, записать VRAM после прогрева"] --> B{"Веса + рантайм меньше примерно 14.5 ГиБ?"} B -- "нет" --> C["Выбрать меньшее квантование или модель, повторить Шаг 1"] C --> A B -- "да" --> D["Шаг 2: Bазовое качество F16/BF16 KV, сохранить выходы задач"] D --> E["Шаг 3: перейти к Q8_0 / FP8 KV с Flash Attention"] E --> F["Шаг 4: повышать контекст ступенями - 32K, 64K, 96K, 128K"] F --> G{"Ошибка во время prefill?"} G -- "да" --> H["Шаг 5: уменьшить batch / ubatch size"] H --> F G -- "нет" --> I{"Ошибка только с параллельными запросами?"} I -- "да" --> J["Шаг 5: уменьшить параллельные слоты / max-num-seqs"] J --> F I -- "нет" --> K["Только теперь: смешанный Q8/Q4, полный Q4, выгрузка, адаптивная ветка"]

Шаг 1: Установить нижний предел весов

Загрузите модель при 8K контексте, одной последовательности и целевой выгрузке в GPU. Запишите VRAM процесса после прогрева и проверьте, что слои не переместились в CPU непредвиденным образом.

Если веса и рантайм уже потребляют больше, чем примерно 14.5–15 ГиБ, у длинного контекста нет здорового запаса. Выберите меньшее квантование весов или модель, прежде чем тюнинговать кэш.

Шаг 2: Измерить F16 или BF16 KV как базовое качество

Запустите минимальный контекст, поддерживающий ваши тесты, и сохранив кэш с высокой точностью по умолчанию. Сохраните выходы задач на поиск, редактирование кода, выбор инструментов и длинных инструкций.

Эта база показывает, происходят ли последующие ошибки из-за квантования кэша. Без неё проблема с chat-шаблоном или слабая модель могут легко быть списаны на KV Q4.

Шаг 3: Перейти на Q8 или FP8

Включите Flash Attention там, где требуется, выберите Q8_0 в llama.cpp или Ollama, или поддерживаемый режим FP8 в vLLM. Повторите те же промпты на тех же глубинах токенов и подтвердите в логе, что показан целевой тип кэша.

Для многих развёртываний на 16 ГБ это полезная остановочная точка. Это примерно удваивает сырую ёмкость KV, не делая сжатие кэша самым агрессивным квантованием в стеке.

Шаг 4: Повышать контекст ступенями

Тестируйте 32K, 64K, 96K и 128K, а не прыгайте сразу к заявленному максимуму. На каждом этапе записывайте скорость обработки промптов (токенов в сек), скорость декодирования (токенов в сек), пиковый VRAM и то, можно ли всё ещё извлечь доказательства, находящиеся в начале контекста.

Декодирование длинного контекста часто замедляется, даже когда память уместилась, потому что внимание читает больше закэшированного состояния. Ёмкость и производительность — это разные оси.

Шаг 5: Тюнинг временной памяти

Если ошибка происходит во время prefill, а не при инициализации, уменьшите микро-батч или максимальное число батченных токенов. Если ошибка происходит только при одновременных запросах, уменьшите конкурентность последовательностей или параллельные слоты.

Только после того, как эти контрольные точки будут поняты, пробуйте смешанный Q8/Q4 кэш, полный Q4 кэш, выгрузку в CPU или адаптивную ветку. Держите run с Q8 в upstream как базовую линию для сравнения. Если вы позже добавите спекулятивную декомпозицию или MTP, помните, что её черновые буферы — это другая строка в уравнении бюджета, а не бесплатная скорость — руководство по спекулятивной декомпозиции охватывает механики и их стоимость в VRAM, а мой бенчмарк Qwen 3.6 27B и 35B MTP против Standard показывает именно то, сколько контекста может стоить дополнительное состояние MTP-головы на карте 16 ГБ.

Что записывать в бенчмарке длинного контекста

Одно число tokens/s скрывает именно ту проблему, которую эта статья пытается решить. Тестирование длинного контекста должно сохранять достаточно деталей, чтобы другой оператор мог воспроизвести границу памяти.

Поле Почему это важно
GPU и доступный VRAM Использование дисплея и другие процессы меняют бюджет
Версия движка или коммит Поведение кэша и флаги быстро эволюционируют
Версия драйвера, CUDA, ROCm или Vulkan Определяет поведение бэкенда и кернелей
Точная модель и квантование весов Определяет резидентность весов и архитектуру
Типы кэша K и V Определяет постоянный размер кэша и риск качества
Ёмкость контекста и глубина промпта Выделение не то же самое, что фактическая глубина
Параллельные последовательности Умножает или разделяет спрос на кэш
Батч и микро-батч Влияет на пики prefill и скорость
Скорость обработки промптов Раскрывает пригодность длинного prefill
Скорость декодирования на каждой глубине Раскрывает замедление из-за пропускной способности кэша
Пиковый VRAM и выгрузка в CPU Различает уместность и вытеснение
Результат качества длинного контекста Обнаруживает сбои сжатия или позиций

Используйте сэмплирование nvidia-smi или эквивалентные инструменты вендора как во время prefill, так и декодирования. Отчёт о распределении движка необходим, но пиковая память устройства во время реального промпта — это число, которое определяет стабильность.

Частые ошибки KV-кэша на GPU 16 ГБ

Отношение к поддержке 128K как к аппаратному обещанию

Поле контекста в конфигурации модели — это архитектурный потолок. Оно не говорит ничего о памяти, оставшейся после загрузки определённого квантования на определённом движке.

Рассчитайте кэш и верифицируйте рантайм. Контекст маркетингового размера без бюджета VRAM — это просто OOM, отложенный до первого серьёзного промпта.

Квантование весов, но забвение KV

4-битный GGUF уменьшает веса модели, но не F16 KV-кэш. При длинном контексте кэш может стирать всю экономию и в итоге превысить след весов.

Сообщайте о обоих квантованиях. Модель Q4_K_M, KV Q8_0 — это значимое описание; 4-битная модель — неполное.

Предположение, что Paged Attention сжимает токены

Paging улучшает поведение распределения и шаринга. Он не меняет точность тензора и не удаляет KV-состояние, требуемое одной уникальной последовательностью.

Используйте paged-выделение, чтобы эффективно обслуживать переменные нагрузки. Используйте точность кэша, архитектуру модели, лимиты контекста и ограничения конкурентности, чтобы управлять ёмкостью.

Предположение, что Prefix Caching помогает каждому длинному промпту

Prefix caching сохраняет пересчёт prefill, когда запросы разделяют точный префикс. Разовая дамп-база из 100K репозитория не получает магического скида памяти просто потому, что prefix caching включён.

Это оптимизация нагрузки, а не замена уравнения бюджета. Измеряйте частоту попаданий (hit rate) и давление сохраняемого кэша в многопользовательском обслуживании.

Использование Q4 KV без теста качества

Кэш с низким числом бит может проваливаться незаметно. Модель всё ещё пишет плавный текст, но внимание к удалённым доказательствам, точным именам, аргументам инструментов или зависимостям кода может деградировать — и, как показывает исследование дивергенции токенов выше, даже “безопасная” настройка Q8_0 не гарантирует воспроизведения точного F16-вывода при детерминированном декодировании, только сохранения точности в агрегате.

Тестируйте целевую задачу на целевой глубине. Короткие чат-бенчмарки почти бесполезны для валидации кэша длинного контекста.

Оставление параллелизма на Auto

Движок может выбрать конкурентность, разумную для пропускной способности, но невозможную для вашей цели длинного контекста. На 16 ГБ одна глубокая последовательность и несколько коротких — это фундаментально разные нагрузки.

Установите лимит явно, затем повышайте его с измеренным трафиком. В противном случае второй запрос может превратить стабильную конфигурацию 64K в сюрприз с распределением или задержкой.

Рекомендуемые профили для 16 ГБ

Эти профили — стартовые позиции, а не универсальные пресеты. Модель с необычной KV-геометрией — в частности, MLA или гибридный дизайн со скользящим окном — может быть значительно дешевле или дороже, чем представительный пример GQA.

Интерактивный кодинг-агент

Используйте одну последовательность, контекст 48K–64K, Q8-кэш, Flash Attention и полную резидентность весов в GPU, если возможно. Этот профиль предпочитает предсказуемые задержки и хорошую точность кэша впечатляющему, но редко полезному максимуму.

Включите повторное использование префикса, если движок это поддерживает, потому что ходы кодинга часто разделяют большой префикс репозитория или разговора. Тем не менее, сжимайте вывод инструментов и старые транскрипты; инженерия кэша не делает нерелевантные токены ценными.

Анализ длинных документов

Используйте меньшую модель с ёмкостью 64K–128K, Q8 или откалиброванный FP8-кэш и повторное использование префикса, когда несколько вопросов направлены на один и тот же документ. Измеряйте время до первого токена, потому что prefill может доминировать, даже если декодирование остаётся приемлемым.

Если будет задан только один вопрос, поиск (retrieval) или чанкованная суммаризация могут быть быстрее и надёжнее, чем загрузка всего корпуса через карту 16 ГБ. Длинный контекст — это инструмент, а не замена информационной архитектуры.

Маленький мультипользовательский сервер

Ограничивайте контекст на запрос и общее количество активных последовательностей, а не рекламируйте максимум модели каждому клиенту. Paged-выделение vLLM полезно здесь, в то время как Ollama и llama.cpp также требуют явного внимания к совокупным живым токенам.

Предпочитайте очередь неконтролируемому вытеснению. Более медленная политика приёма менее вредна, чем ситуация, когда каждый запрос внезапно пересекает PCIe во время декодирования.

Финальная рекомендация для длинного контекста на 16 ГБ

Для длинного контекста на 16 ГБ Q8 KV и одна активная последовательность — это правильная база. Они раскрывают реальный лимит, не заставляя качество низкобитового кэша, параллельное распределение и задержку выгрузки проваливаться одновременно.

Считайте от геометрии внимания, вычитайте веса и накладные расходы рантайма, затем подтвердите результат в логах движка и измерениях пиковой памяти. Если 128K всё ещё не помещается, меньшая модель часто является самой чистой оптимизацией; если она помещается, но ползёт, уменьшение контекста часто является честным решением.

Paged attention, prefix caching, скользящие окна и адаптивная точность решают полезные, но разные проблемы. Выигрышная настройка — та, которая остаётся на GPU, правильно извлекает старые доказательства и поддерживает приемлемую скорость декодирования на глубине контекста, которую вы на самом деле используете.

Ссылки

Подписаться

Получайте новые материалы про системы, инфраструктуру и AI engineering.