Перейти к содержанию
Публикация AiManual

Почему квантование KV cache может ухудшать long-context в Qwen3.8-27B и что меняет режим on-write

Q8 KV cache в Qwen3.8-27B может ухудшать long-context из-за особенностей хранения и момента квантования. Разбираем разницу между on-write и пост-обработкой bf16

Коротко

Что будет в материале

  1. 01

    Короткий ответ: проблема может быть не в самом q8

  2. 02

    Что происходит с KV cache на длинном контексте

  3. 03

    8-битные веса и 8-битный KV cache: разная компрессия, разные риски

  4. 04

    Режим on-write KV cache против разовой обработки bf16-кеша

Короткий ответ: проблема может быть не в самом q8

Q8 KV cache в Qwen3.8-27B способен ухудшать качество long-context, но сама 8-битная разрядность не доказывает причину просадки. На результат влияет схема квантования, способ хранения K/V, kernel, параметры масштабирования и момент, когда runtime переводит кеш из bf16 в q8.

Есть два принципиально разных сценария. При режиме on-write новые K/V-состояния квантуются во время добавления токена в кеш. Следующие шаги декодирования сразу читают это квантованное представление. При разовой пост-обработке сначала собирается bf16-кеш, затем уже накопленный массив преобразуется в q8 целиком, если выбранный backend поддерживает такой конвейер.

Q8-веса и q8 KV cache относятся к разным частям inference. Веса хранят постоянные параметры модели, а KV cache содержит промежуточные состояния текущего запроса. Поэтому качество q8-весов нельзя использовать как доказательство качества q8-кеша. Для Qwen3.8-27B корректный вывод требует повторяемого сравнения на одинаковых весах, промптах, длине контекста и настройках генерации.

Что происходит с KV cache на длинном контексте

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

Для последовательности длиной t в кеше накапливаются K и V для всех доступных позиций каждого слоя и каждой KV-head. Память растет примерно линейно с числом токенов. При обычном full attention растет и объем работы с историей на каждом шаге декодирования.

K и V: какие данные модель сохраняет

Key, или K, помогает сопоставить текущий запрос с позициями, сохраненными в контексте. Value, или V, содержит представление информации, которое будет смешано с другими значениями после расчета attention.

Упрощенная последовательность выглядит так:

score = Q · K / sqrt(d), затем scores превращаются в веса внимания, после чего эти веса применяются к V.

При обработке нового токена модель создает новые K и V. Они зависят от текста перед текущей позицией, параметров модели и конкретного слоя. Поэтому KV cache создается заново для каждого запроса. Это состояние контекста, а не часть файла с весами.

Если в последовательности 8 000 токенов, кеш хранит состояния для 8 000 позиций, если backend не применяет окно внимания, сжатие или вытеснение истории. При 64 000 позициях объем тех же структур становится примерно в восемь раз больше при прочих равных условиях.

Почему длинный контекст повышает требования к точности кеша

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

Небольшая ошибка в одном K может изменить относительный score между двумя близкими фрагментами. Ошибка в V способна изменить итоговое смешанное представление, даже если нужная позиция получила высокий вес внимания. В результате эффект зависит от содержания задачи, положения факта и того, насколько близки альтернативные варианты.

Слово «накопление» здесь требует аккуратности. Один и тот же вектор обычно не квантуется заново на каждом шаге. При on-write он переводится в выбранный формат при записи, а затем много раз читается в attention. Рост контекста увеличивает число сохраненных векторов и обращений к ним, поэтому суммарное влияние ошибок может стать заметнее. Для конкретной модели и backend это нужно проверять тестами.

Какие симптомы стоит искать

  • Модель правильно отвечает по короткому документу, но теряет факт, помещенный в начало длинного промпта.
  • Ответ зависит от длины контекста, хотя вопрос, веса модели и параметры sampling остаются прежними.
  • Факт из середины документа извлекается нестабильно при повторных запусках с одинаковым seed.
  • При q8 KV cache появляется расхождение с bf16-базой, а после возврата к bf16 проблема исчезает.
  • Модель путает похожие фрагменты кода или выбирает устаревшее требование из длинной спецификации.

Эти признаки указывают на направление диагностики. Они не доказывают, что причина находится именно в KV cache. Похожее поведение дают обрезка контекста, неверные RoPE-параметры, другое квантование весов, sampling и различия между kernel.

8-битные веса и 8-битный KV cache: разная компрессия, разные риски

Обозначение q8 описывает разрядность, но не весь формат. На итог влияют scale, размер блока, схема округления, распределение значений и операции, которые runtime выполняет при чтении.

Что меняет квантование весов

Квантование весов заменяет исходные числовые параметры модели приближенным представлением. Этот формат загружается вместе с моделью и остается фиксированным на протяжении запроса. Он влияет на вычисления во всех слоях, независимо от длины конкретного промпта.

В условном сравнении один 8-битный элемент занимает вдвое меньше сырого места, чем один bf16-элемент. Реальный размер зависит от scale, служебных полей, упаковки блоков и формата файла. Для весов сокращение размера обычно уменьшает требования к VRAM и может изменить скорость матричных операций.

Качество зависит от того, какие слои и каким способом сжаты. Низкая разрядность весов способна сильнее проявляться в коде, математике, многошаговом рассуждении или редких языковых конструкциях. Практические компромиссы между Q2, Q3 и более высокими форматами разобраны в материале о низких квантизациях Qwen3.8-27B.

Что меняет квантование KV cache

KV cache появляется во время inference. Его содержимое зависит от текущего текста, позиции токена и скрытых состояний модели. Один и тот же файл весов может породить разные кеши для двух разных запросов.

При q8-кеше K и V хранятся в 8-битном представлении с дополнительными параметрами восстановления масштаба. Во время attention backend может читать компактные значения и выполнять dequantization внутри kernel. Конкретная последовательность операций зависит от выбранного runtime и аппаратного пути.

Экономия памяти особенно заметна при длинной истории. Упрощенная оценка размера выглядит так:

2 x число слоев x число токенов x число KV-heads x размер head x размер элемента.

Множитель 2 соответствует K и V. При переходе с bf16 на 8-битный формат сырой payload уменьшается примерно вдвое, но scales, выравнивание, временные буферы и особенности kernel меняют фактический результат в VRAM.

Почему одинаковая разрядность ничего не гарантирует

Q8-веса и q8 KV cache используют разные массивы, появляются в разные моменты и обрабатываются разными участками inference-конвейера. У весов заранее известна вся модельная структура. У кеша значения строятся последовательно, позиция за позицией.

Даже два формата с 8 битами могут отличаться гранулярностью scale. Один backend может применять scale к блоку элементов, другой использовать иную группировку или отдельные kernel для K и V. Условия округления тоже влияют на ошибку.

Поэтому правило «q8 почти без потерь» слишком общее. Корректнее сравнивать конкретную пару: модельные веса, формат K/V, runtime, версию сборки, устройство и длину контекста. Изменение одного пункта уже может сделать результат несопоставимым.

Режим on-write KV cache против разовой обработки bf16-кеша

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

Как работает on-write

Упрощенная цепочка для нового токена выглядит так:

  1. Модель вычисляет K и V для новой позиции.
  2. Runtime применяет выбранную схему преобразования: scale, округление и упаковку.
  3. Квантированные значения записываются в KV cache.
  4. При следующем шаге attention читает это представление, возможно выполняя dequantization внутри kernel.

Если вычисления K/V идут в bf16 или другом высокоточным формате, преобразование происходит на границе между вычислением и хранением. Новый токен не требует многократной квантизации сам по себе. Его квантованная версия начинает использоваться сразу после записи, когда модель продолжает декодирование.

При анализе on-write нужно узнать точные параметры: формат K, формат V, размер блока, способ расчета scale, наличие отдельного kernel для чтения и момент записи относительно prefill и decode. Одного имени опции в командной строке для такой проверки недостаточно.

Как работает разовая пост-обработка bf16-кеша

В концептуальном сценарии runtime сначала обрабатывает исходный промпт и формирует bf16-cache. После завершения этого этапа весь накопленный набор K/V преобразуется в q8. Следующая генерация читает уже компактный кеш.

Такая схема разделяет два этапа: исходное построение состояния контекста и его последующее сжатие. Пока prefill не завершен, квантованное представление не участвует в следующих шагах декодирования. Этот сценарий требует дополнительной памяти для временного bf16-кеша и доступен только там, где backend действительно предоставляет соответствующий путь.

Нельзя приписывать такую функцию каждой сборке llama.cpp или совместимому runtime. Название режима, момент конвертации и поддерживаемые типы данных нужно сверять с логами и документацией конкретной версии.

Где может возникать различие в качестве

При on-write ранние позиции контекста становятся квантованными еще во время последовательной обработки. При пост-обработке они сначала участвуют в формировании bf16-состояния, затем переходят в q8 перед декодированием. Эта разница может влиять на результат, если ранняя потеря точности меняет последующие скрытые состояния или если два режима используют разные границы блоков.

Есть несколько альтернативных объяснений:

  • on-write и пост-обработка выбирают разные размеры блоков или scale;
  • для чтения кеша используются разные kernel;
  • один путь повторно преобразует уже упакованные значения, а другой работает с исходным bf16;
  • режимы по-разному обрабатывают хвост последовательности, padding или неполный блок;
  • вместе с форматом KV меняется распределение вычислений между GPU и CPU;
  • runtime фактически выбирает другой тип K/V, чем указан в конфигурации.

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

Почему квантование KV cache может ухудшать long-context в Qwen3.8-27B

Механизм просадки можно описать через два канала. Ошибка в K меняет вероятность выбора позиции, ошибка в V меняет содержимое, которое модель получает после выбора. На практике эти эффекты могут накладываться.

Как ошибка в K может менять выбор релевантных токенов

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

Представьте инструкцию на 50 000 токенов, где в начале указано исключение, а ближе к концу повторено общее правило. Если ошибка в K снижает score позиции с исключением, модель может выбрать более свежий, но менее подходящий фрагмент. Это объясняет возможную потерю старого факта на уровне механизма, но не доказывает, что именно q8 вызвал ошибку в конкретном запуске.

Как ошибка в V может менять содержание ответа

Values несут информацию, которую attention смешивает после расчета весов. Модель может найти правильную позицию, но получить слегка искаженное представление ее содержимого. Для вопроса по документу это способно проявиться в пропущенном условии, неверном имени параметра или неполном пересказе.

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

Почему короткие проверки могут скрыть проблему

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

Для проверки используйте несколько длин, которые помещаются в лимит модели и runtime: например, 4 096, 16 384 и 65 536 токенов. Контрольный факт размещайте рядом с началом, в середине и ближе к концу документа. Одинаковый вопрос задавайте после каждого увеличения контекста. Такой сет выявляет зависимость качества от длины и позиции, а не маскирует ее одним удачным коротким примером.

Какие альтернативные причины нужно исключить

  • Обрезка контекста. Проверьте фактическое число обработанных токенов и наличие сообщений о truncation.
  • RoPE. Сверьте параметры позиционного кодирования и максимальную длину, с которой запускается модель.
  • Квантование весов. Сравнение q8 KV cache имеет смысл при неизменном файле весов.
  • Sampling. Зафиксируйте temperature, top-p, top-k, repetition penalty и seed.
  • Batch и ubatch. Изменение размера обработки может выбрать другой computational path.
  • GPU offload. Сравните full GPU offload с режимом, где часть операций уходит на CPU.
  • Версия runtime. Проверьте commit или номер сборки llama.cpp и изменения kernel для KV cache.
  • Память. Ищите fallback, нехватку VRAM, выгрузку буферов и ошибки распределения тензоров.

Меняйте одну переменную за запуск. Иначе улучшение после возврата к bf16 может совпасть с изменением sampling или offload, и причина останется неясной.

Как проверить эффект в llama.cpp и похожих бэкендах

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

Зафиксировать bf16-базу

Сначала соберите контрольный сценарий с bf16 KV cache, если память позволяет. Зафиксируйте:

  • один и тот же файл Qwen3.8-27B и формат весов;
  • один prompt с известным контрольным ответом;
  • целевую длину контекста и фактически обработанное число токенов;
  • seed и полный набор sampling-параметров;
  • число слоев на GPU, backend и тип устройства;
  • размер batch и ubatch;
  • версию runtime и параметры запуска.

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

Сравнивать on-write и пост-обработку по одному сценарию

Минимальная матрица выглядит так:

СценарийФормат KVУсловие сравнения
Контрольbf16Исходный prompt, веса и sampling без изменений
Сжатие при записиq8Режим on-write, тот же prompt и та же длина контекста
Пост-обработкаq8Сначала bf16-cache, затем конвертация, если backend поддерживает путь

Повторите каждый сценарий на трех или четырех длинах контекста, например 4 096, 16 384, 32 768 и 65 536 токенов, если эти размеры доступны в вашей конфигурации. Для каждого размера используйте одинаковые позиции контрольных фактов и одинаковое число повторов.

Если пост-обработка недоступна, не подменяйте ее другим q8-режимом. В этом случае корректный результат сравнения ограничивается bf16 и фактически доступным on-write.

Проверять не только связность ответа

Связный текст может скрывать пропущенное условие. Добавьте задачи с проверяемым критерием:

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

Заранее задайте критерии: точное совпадение факта, число найденных элементов, доля правильных ответов, количество выдуманных деталей и средняя длина ответа. Для свободного пересказа добавьте ручную проверку, но отделяйте ее от измерений с точным совпадением.

Что собрать из логов и конфигурации

В логах ищите фактически выбранные типы K и V, размер контекста, распределение тензоров и сообщения о fallback. Сверьте заявленный режим с тем, что runtime загрузил на GPU и CPU. Полный GPU offload упрощает сравнение, поскольку убирает часть переменных, но требует достаточного объема VRAM.

Запишите commit llama.cpp или номер сборки, драйвер, тип GPU и объем доступной памяти. У разных версий могут отличаться поддерживаемые типы KV и kernel чтения, поэтому перенос названия опции из чужой конфигурации не гарантирует тот же результат.

Для сравнения с другими стеками полезен разбор локального запуска Qwen3.8-27B с LMCache и fp8 KV-cache. Он показывает, почему в таких конфигурациях нужно фиксировать связку модели, runtime, offload и механизма кеширования целиком.

Практический выбор: q8 KV cache или bf16

Когда сначала пробовать bf16

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

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

Когда q8 KV cache имеет практический смысл

Q8 KV cache полезен, когда память под контекст становится главным ограничением и дополнительная емкость важнее небольшой потенциальной потери точности. В сыром представлении 8-битный элемент занимает примерно половину места bf16-элемента. Фактическая экономия зависит от scales, служебных буферов и конкретного backend.

Такой режим разумен для локального запуска, длинных диалогов, RAG и анализа больших файлов, если контрольные тесты на целевых задачах не показывают неприемлемой просадки. Проверяйте именно ту модель Qwen3.8-27B, тот формат весов и ту сборку, которые будут использоваться постоянно.

Другие схемы сжатия KV могут давать иной компромисс по памяти и качеству. Например, в разборе расширенного контекста Qwen3.8-27B описан отдельный формат KV и отдельный runtime, поэтому его результаты нельзя напрямую переносить на q8 в llama.cpp.

Что делать, если качество просело

  1. Повторите тот же prompt с bf16 KV cache.
  2. Проверьте фактическую длину контекста и сообщения об обрезке.
  3. Сравните логи, типы K/V и версию runtime.
  4. Убедитесь, что веса, sampling, seed, batch и offload не изменились.
  5. Проверьте on-write и пост-обработку, если оба режима доступны в выбранном backend.
  6. Выберите формат по результатам тестов: bf16 для чувствительных задач, q8 для экономии памяти при приемлемом качестве.

Конкретные флаги зависят от версии llama.cpp и совместимого backend. Надежнее отталкиваться от лога и документации своей сборки, чем копировать команду запуска из старого примера.

Что нельзя заключать из одного теста

q8 KV cache не равен гарантированной деградации

Один неудачный ответ показывает расхождение в конкретном сценарии. Он не доказывает универсальную непригодность q8 KV cache для Qwen3.8-27B. Результат зависит от длины контекста, схемы scale, kernel, формата весов, типа задачи и sampling.

Если bf16 и q8 дают одинаковый результат на коротком промпте, это тоже не закрывает вопрос long-context. Нужны длинные документы, разные позиции факта и несколько повторов.

on-write не следует автоматически считать ошибкой

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

Просадка между on-write и post-process может объясняться моментом квантования, разными kernel или несовпадением параметров. Пока эти варианты не разделены, преждевременно называть on-write дефектом.

Какие результаты нужны для сильного вывода

Практическая рекомендация требует повторяемого эксперимента с одинаковыми модельными весами, фиксированным seed, несколькими длинами контекста и заранее описанными метриками. В отчете укажите версию runtime, GPU, тип K/V, режим записи, параметры sampling и фактический объем обработанного контекста.

Минимальный убедительный результат выглядит как таблица с bf16, q8 on-write и q8 post-process, несколькими длинами контекста и отдельными оценками для поиска фактов, кода и свободного ответа. Без такой матрицы корректнее говорить о наблюдении или гипотезе.

Итог: проверять нужно не только формат, но и момент квантования

Для Qwen3.8-27B q8 KV cache может дать нужную экономию памяти, но long-context требует отдельной проверки. Q8-веса описывают постоянное представление модели, q8 KV cache хранит динамическое состояние запроса. Одинаковая цифра в названии не делает их одной и той же оптимизацией.

  1. Зафиксируйте bf16-базу на тех же весах и prompt.
  2. Сравните q8 на коротком, среднем и длинном контексте.
  3. Уточните, работает ли квантование в режиме on-write.
  4. Проверьте фактические типы K/V, kernel, offload и сообщения fallback в логе.
  5. Если доступна пост-обработка, сравните ее с on-write в одинаковых условиях.
  6. Оставьте bf16 для задач с дорогой ошибкой и выберите q8 там, где экономия VRAM подтверждена приемлемым качеством.

Главный диагностический вопрос звучит так: ухудшается ли ответ из-за самой потери точности при переходе к q8 или из-за того, что квантованное представление начинает использоваться уже во время записи кеша. Ответ дает контролируемый тест конкретной сборки, а не одно обозначение формата.

Подписаться на канал