Статья
KV offload: когда загрузить кэш выгоднее, чем пересчитать
Копия KV на CPU или в хранилище полезна, пока её возврат помогает запросу. Считать придётся не только удачные чтения, но и запись блоков, которые больше никто не попросил.
Автор — Сергей Нотевский
Опубликовано
В конце эфира «Каждый токен на счету»Внешняя ссылка, откроется в новой вкладке я обозначил продолжение темы: self-hosted инференс, общие хранилища и выгрузку KV на CPU. Здесь можно управлять местом хранения вычисленного состояния. Но если нужные данные больше не находятся на GPU, их сначала придётся вернуть.
Иногда загрузка быстрее повторного вычисления префикса. Иногда ожидание хранилища и передача съедают выигрыш. Поэтому увеличение объёма кэша ещё не отвечает на вопрос, стало ли дешевле или быстрее обслуживать запросы.
Выгружаются тензоры, а не текст промпта
KV-кэш — промежуточные тензоры модели для уже обработанного входа. Строка промпта или сохранённые token IDs не заменяют их: по идентификаторам токенов движку всё равно предстоит выполнить prefill, обработку входа. Базовая схема есть в главе про префиксный кэш.
В OffloadingConnector vLLM v0.26.0 CPU служит первым уровнем вне GPU. В многоуровневом варианте дополнительные хранилища обмениваются данными с GPU через этот CPU-уровень. Завершённые блоки сохраняются для будущих обращений и возвращаются по потребности. Руководство vLLM v0.26.0Внешняя ссылка, откроется в новой вкладке.
01 · Хранение
Дополнительный уровень
Поиск и чтение сохранённых блоков.
02 · Перенос
Основная память CPU
Промежуточный уровень перед передачей на GPU.
03 · Исполнение
Память GPU
Блоки готовы для продолжения вычисления.
Путь загрузки в многоуровневом OffloadingConnector vLLM v0.26.0. При сохранении данные идут обратно. Это схема конкретного connector, а не обязательная топология любого offload.
Сравнивать нужно задержку на пути запроса
Возврат включает поиск, ожидание, подготовку и перенос. Часть работы может идти параллельно с вычислениями. Для задержки важна та часть, без которой запрос не продолжится, а не механическая сумма всех таймеров.
Я бы сравнивал время до готовности одного и того же префикса двумя способами: загрузить сохранённый KV или пересчитать его. В обоих вариантах учитывается очередь. Оставшийся вход и генерацию затем нужно измерить отдельно, особенно если offload создаёт конкуренцию за память или передачу данных.
Учебный расчёт с явными допущениями
Это иллюстрация, не замер оборудования и не обещание экономии. Пусть совместимый KV одного префикса занимает 256 MiB. Предположим эффективную скорость всего рассматриваемого пути возврата 8 GiB/s, уже с учётом его узкого места. На поиск и подготовку зададим ещё 4 мс. Очереди и перекрытия операций в этом примере нет.
Объём: 256 MiB = 0,25 GiB
Передача: 0,25 GiB / 8 GiB/s = 31,25 мс
Поиск и подготовка: 4 мс
Возврат всего: 35,25 мсЗададим время пересчёта того же префикса равным 60 мс. При этих допущениях возврат заканчивается раньше на 24,75 мс. Но если эффективная скорость упадёт до 2 GiB/s, передача займёт 125 мс, а вместе с подготовкой — 129 мс. В той же модели расчёта пересчёт уже быстрее.
Граница без очередей находится из условия 4 мс + 0,25 GiB / B < 60 мс: нужна скорость выше примерно 4,46 GiB/s. Это не рекомендация к интерфейсу и не его паспортная скорость. Для реального многоступенчатого пути следует измерить возврат от поиска до готовности KV; подставлять сюда только пропускную способность PCIe нельзя.
Эти 24,75 мс также нельзя автоматически вычесть из клиентского TTFT. Если передача перекрывалась с другой работой, разница на критическом пути будет иной. А сэкономленное время prefill не равно денежной экономии без учёта стоимости внешнего уровня и всей нагрузки.
Запись тоже расходует ресурсы
Длинный повторяемый префикс — повод проверить offload. Особенно если следующему обращению нужен KV, уже вытесненный из GPU. Почти уникальный поток может заполнить внешнее хранилище блоками, которые больше не прочитают.
Поэтому считать только успешные загрузки недостаточно. В итог входят записанные байты, CPU, память, сеть и конкуренция чтения с записью. При общем лимите памяти большой CPU-кэш может мешать остальной работе узла. Удаление невостребованных данных тоже часть эксплуатации.
Для сравнения стоимости я бы взял одинаковый интервал и разделил все затраты на обслуживание потока, включая внешний уровень, на число успешно завершённых запросов в пределах заданной задержки. Если профиль входа и выхода различается, такая цена на запрос уже несопоставима: сначала нужно выровнять нагрузку. Возможный рост ёмкости следует показать отдельно от среднего времени ответа.
Хранилище и роутер решают разные задачи
LMCache предоставляет хранение и обмен KV; роутер выбирает место исполнения; транспорт переносит байты. Эти роли могут сочетаться в одном стеке. В документации LMCache, проверенной 5 сентября 2026 года, прежняя страница in-process storage помечена устаревшей. Для нового выбора стоит смотреть Secondary KV StorageВнешняя ссылка, откроется в новой вкладке и совместимость с vLLMВнешняя ссылка, откроется в новой вкладке. Здесь не фиксируется испытанная связка версий LMCache и vLLM.
Наличие offload у движка ещё не означает, что роутер различает уровни хранения. В матрице Dynamo v1.4.0Внешняя ссылка, откроется в новой вкладке поддержка зависит от backend: для vLLM отдельно описаны условия видимости CPU-блоков, а интеграция disk и shared pool отмечена как незавершённая. Нельзя переносить возможности одной строки матрицы на весь стек.
Даже видимый CPU-блок не сообщает точную стоимость загрузки. Что роутер предсказывает и что worker действительно использует, следует проверять отдельно; этот разрыв разобран в статье о состоянии кэш-роутера.
Совместимость проверяется до сравнения времени
В эксперименте нужно зафиксировать веса и ревизию модели, tokenizer и chat template, формат KV, dtype, размер блоков, схему параллелизма и версии engine/connector. Это список условий для проверки выбранной реализации, а не универсальная гарантия, что совпадения названий достаточно для обмена тензорами.
Общее хранилище также не отменяет изоляцию арендаторов. Найти ключ и получить право использовать блок — разные условия. Их нужно сохранить одинаковыми в вариантах с offload и без него.
Первый эксперимент — один worker, один connector
Я бы начал с offload on/off на одном worker, сохранив модель, бюджет GPU KV, времена прихода запросов и процедуру прогрева. Затем отдельно добавил бы давление на память и конкуренцию за передачу. Смена роутера одновременно с connector не позволит понять, какая часть изменила результат.
| Что сравнить | Зачем |
|---|---|
| Реально переиспользованные токены или блоки | Проверить, что найденные данные пригодились вычислению |
| Прочитанные и записанные байты | Учесть нагрузку от удачных и невостребованных сохранений |
| Поиск, возврат и очередь | Найти задержку на пути запроса |
| Клиентские TTFT, полное время ответа и ошибки | Проверить результат для пользователя |
| Успешная нагрузка в пределах SLO и ресурсы внешнего уровня | Оценить ёмкость и стоимость обслуживания |
Повторите прогоны, меняя их порядок; холодный старт покажите отдельно. Публикуемый вывод должен опираться на распределения задержек и объём выборки, а не на один удачный повтор.
Для подготовки соберите версии, конфигурацию и результаты сопоставимых прогонов по разделу «Проверить свой проект». Если пока неизвестно, повторяется ли вход, начните с первого аудита. А при работе через API провайдера понадобятся правила API и usage: доступ к CPU-offload провайдера из этого разбора не следует.
Решение об offload принимается по тому, что получила система на своём потоке: меньшую стоимость, меньшую задержку или большую ёмкость при прежних ограничениях. Доля найденных блоков помогает объяснить результат, но не заменяет его.