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

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

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

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

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

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

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

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

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

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

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

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

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

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

KV bytes = sequences * tokens * layers * 2 * KV heads * head dimension * bytes per value

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

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

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

Количество параметров в основном предсказывает потребление памяти под веса. Геометрия KV-кэша зависит от архитектуры внимания, поэтому читайте метаданные модели, а не пытайтесь угадать по 8B, 27B или размеру GGUF-файла. Нагляднее всего видно, как далеко продвинулся дизайн внимания за пределы простого multi-head attention (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% по сравнению с плотной MHA-моделью того же размера, при этом качество оставалось конкурентоспособным, а иногда и превосходящим GQA при том же бюджете памяти.

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

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

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

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

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

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

Рассмотрим представительскую GQA-модель с 32 слоями, 8 KV-голов и размером головы (head dimension) 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:

usable VRAM = total VRAM - desktop and driver reserve

KV budget = usable VRAM
          - GPU-resident model weights
          - graph and activation buffers
          - runtime workspace
          - speculative-decoding state
          - safety margin

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

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

Это не означает автоматически, что Q4_0 — правильный выбор. Если точность длинного контекста падает в вашей задаче, меньшая или более агрессивно заквантованная модель с 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-слои уменьшают дублирование, рекуррентные слои могут хранить состояние фиксированного размера, а multi-head latent attention сохраняет сжатое представление, а не тензоры 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 меняет его практически во всех — стоит перевернуться одному токену, как остальная часть продолжения может уйти в разнос. Показатели перплексии и задач могут выглядеть нормально в среднем, хотя отдельные выводы всё ещё отличаются от F16-базы. Если вашему приложению нужна побайтовая воспроизводимость (тесты регрессии, закэшированные ответы, детерминированные агенты), относитесь к любому квантованию KV как к изменению поведения, а не просто оптимизации памяти, и валидируйте по своему фиксированному набору промптов.

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

Контекст, параллельные слоты и единый кэш

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

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

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

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

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

vLLM: страничная ёмкость всё равно есть ёмкость

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

PagedAttention снижает фрагментацию и потери вокруг переменных длин последовательностей — страничное выделение убирает фрагментацию, а не стоимость хранения на токен, поэтому один уникальный запрос на 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 ГБ, модель, метод квантования или бэкенд внимания поддерживает именно такую комбинацию. Рассматривайте это как форму конфигурации: ограничьте длину и конкурентность, зарезервируйте запас, выберите поддерживаемый тип данных кэша и валидируйте отчёт инициализации.

FP8 KV-кэш в vLLM

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

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

Префиксная кэшизация — это оптимизация переиспользования

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

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

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

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

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

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

Ollama намеренно предоставляет меньшую операционную поверхность. Его текущая документация по длине контекста по умолчанию устанавливает 4K контекст для GPU менее 24 ГиБ, рекомендует минимум 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 — рекомендованная Ollama альтернатива F16. 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.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

flowchart TD A["Шаг 1: загрузить при 8K контексте, одна последовательность, записать VRAM прогрева"] --> B{"Веса + рантайм меньше примерно 14.5 ГиБ?"} B -- "нет" --> C["Выбрать меньший квант или модель, повторить Шаг 1"] C --> A B -- "да" --> D["Шаг 2: базовая линия качества F16/BF16 KV, сохранить выводы задач"] D --> E["Шаг 3: переключиться на Q8_0 / FP8 KV с Flash Attention"] E --> F["Шаг 4: поднимать контекст этапами - 32K, 64K, 96K, 128K"] F --> G{"Сбой во время префила?"} 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 как базовую линию качества

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

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

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

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

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

Шаг 4: Поднимать контекст этапами

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

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

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

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

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

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

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

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

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

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

Отнесение поддержки 128K к обещанию железа

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

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

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

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

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

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

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

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

Предположение, что префиксная кэшизация помогает каждому длинному промпту

Префиксная кэшизация экономит повторные вычисления префила, когда запросы разделяют точный префикс. Разовый дамп репозитория на 100K не получает магического снижения памяти просто потому, что префиксная кэшизация включена.

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

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

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

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

Оставление параллельности в режиме Auto

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

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

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

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

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

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

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

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

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

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

Малый сервер для нескольких пользователей

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

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

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

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

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

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

Ссылки

Подписаться

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