Кэш промптов Ollama — где правите, там и счёт

Кэш промптов Ollama — где правите, там и счёт

Шесть зондов, одна свежая ВМ: точный повтор переоценил 1,539 токенов за 0.2 с против 75.6 с в холодную — а одно слово в середине промпта вернуло в счёт 49 с.

HUMANавтор @msvram40m6 мин чтения

verify: шесть зондов, одна свежая ВМ, seed 42 · temp 0 · ollama 0.40.16 · квитанции ниже, log promptcache-2026-11-12.log

секунды prompt-eval · промпт 1,539 токенов · qwen3:4b · 2 vCPU · холодный запрос = линия 100% холодная база 75.6 с 75.6 sP1 холод 0.2 sP2 повтор 1.4 sP3 суффикс 49.2 sP4 правка в середине 79.9 sP5 перестановка 4.2 sP6 у конца

Fig 1 — шесть зондов, один промпт. Приглушённые столбцы — квитанция: правка в середине промпта вернула 65% холодного счёта, а перестановка выставила счёт выше самой холодной загрузки.

Что на самом деле хранит кэш промптов ollama?

Не ваш текст. Когда ollama оценивает промпт, он считает KV-тензор для каждого токена — след внимания этого токена против всех предыдущих — и рантайм держит эти тензоры в RAM, пока модель загружена. Новый промпт сверяется с этим хранилищем токен за токеном, спереди. Каждый токен, точно продолжающий старый префикс, бесплатен. Первый отличившийся токен — граница: всё оттуда до конца перечитывается по полной цене. Это та же KV-резиденция, что наша статья про KV-cache разбирала для тайминга выгрузки, — кэш, который служит скорости, есть кэш, который держит RAM (а keep_alive решает, сколько проживёт и то и другое, — расчётную сторону этого счёта держит статья про окно контекста).

префикс совпал — токены бесплатно. Идентичные байты с позиции 0 не стоят ничего в переработке. 0.2 с за 1,539 токенов, измерено.
первая дивергенция — граница. Один изменённый токен инвалидирует все токены после него. Частичного кредита назад нет; хвост всегда пересчитывает счёт.
резиденция — keep_alive. Кэш живёт столько же, сколько модель. Выгрузите модель — и скидка умрёт с ней; пришпильте keep_alive, если ваш цикл болтлив с паузами.

Почему точный повтор стоил 0.2 секунды?

Зонд: промпт в 1,539 токенов — восемь пронумерованных секций фактов из логов фикстуры, затем вопрос — задан в холодную на свежей ВМ. Движок прочёл все 1,539 токена на 20.4 tok/s и выставил 75.6 с оценки промпта. Тот же запрос, отправленный снова с резидентной моделью: 0.2 с. Тот же endpoint, то же тело, ничего не переключено — ollama делает это без флага, без cache: true, без заговоров на прогрев. Скидку вы получаете при каждом повторном запросе; этот пост о том, как её не отдать.

curl http://localhost:11434/api/generate -d '{
  "model": "qwen3:4b",
  "prompt": "<your 1,500-token prompt>",
  "keep_alive": "45m",
  "think": false,
  "options": {"seed": 42, "temperature": 0, "num_predict": 128}
}'

verify: P1 холод 75.6 с → P2 повтор 0.2 с, тело зонда побайтово идентично, модель резидентна · promptcache-2026-11-12.log

Где правите — там и платите

Мы сохранили тело в 1,539 токенов и двигали ровно одно слово, тремя способами. Поменяйте слово в последнем предложении: 4.2 с — общая голова выжила, перечитан только хвост. Добавьте предложение после вопроса: 1.4 с — почти полное попадание, пять новых токенов оплачены на полях. Поменяйте одно слово в факте 41 из 96, в середине тела: 49.2 с — 65% холодного счёта, уплачено за перемещение одного прилагательного. У кэша память слона и прощение таможенника: он помнит всё, и одна неверная форма аннулирует все штампы в книжечке — для всего, что за неверной формой.

зондчто изменилосьprompt-evalвесь запроспротив холода
P1 холодничего — свежая ВМ, первый запрос75.6 s111.8 sбаза
P2 повторничего — идентичное тело0.2 s26.6 s370× быстрее
P3 суффикс+1 предложение после вопроса1.4 s27.2 s54× быстрее
P4 правка в середине1 слово в факте 41/9649.2 s75.2 s65% холода
P5 перестановкасекции 1–2 ↔ 7–8 переставлены79.9 s135.5 sхуже холода
P6 у конца1 слово в последнем предложении4.2 s34.0 s18× быстрее

промпт 1539 токенов · prompt-eval — счёт за перечитывание; «весь запрос» добавляет генерацию 128 токенов, державшуюся ~25–29 с всю дорогу.

Почему перестановка выставляет счёт дороже холодной загрузки?

Потому что граница на токене ~1. Поменяйте первые две секции с последними двумя — и общий префикс пуст: совпадение умирает сразу, и каждый токен пересчитывает счёт. Но P5 выставил 79.9 с, а не 75.6 с: 5.7% выше холодной загрузки, ради которой он выбросил скидку. Мы не будем притворяться, что поймали виновника, — честная пометка в том, что проход генерации на этом зонде тоже шёл в 2.1× медленнее (55.4 с против 25.5–29.4 с у любого другого запроса, тот же seed, тот же бюджет). Тёплое состояние сервера присоединяется к счёту так, как диаграмма кэша не показывает; статья про детерминизм уже встречала этого свидетеля с перевёрнутым токеном при temp 0. Что мы можем заявить как квитанцию: переставленный промпт — не «тот же промпт, другой порядок». Это совершенно новый счёт плюс шум.

Список исправлений

симптомчто происходитисправление
каждый запрос ощущается как холодная загрузкамодель выгружается между запросами — кэш умирает с нейподнимите keep_alive; проверьте load_duration в ответе
крошечные правки промпта, огромная задержкаправка сидит в середине промпта; хвост считает счёт от точки расхождениядвиньте волатильную часть в хвост промпта
«тот же промпт» считает счёт по-разному от запуска к запускупорядок или boilerplate дрейфовал между запросамизаморозьте голову побайтово; делайте diff промптов, а не вайбов
квитанция · шесть зондов, одна свежая ВМ (qwen3:4b · ollama 0.40.16 · 2 vCPU · промпт 1,539 tok)
P1 cold-full-ask      prompt_eval 75,646 ms   (1,539 tok · 20.4 tok/s)  wall 111.8 s
P2 exact-repeat       prompt_eval    203 ms                             wall  26.6 s
P3 suffix-only        prompt_eval  1,402 ms   (+5 tok)                  wall  27.2 s
P4 mid-edit-kill      prompt_eval 49,176 ms   (1 word, fact 41/96)      wall  75.2 s
P5 reorder-full-miss  prompt_eval 79,878 ms   (secs 1–2 ↔ 7–8)          wall 135.5 s
P6 edit-near-end      prompt_eval  4,189 ms   (1 word, last sentence)   wall  34.0 s

Один промпт. Шесть счетов, от 0.2 до 79.9 секунды — разброс в 370× без единого изменённого сеттинга и без единого переключённого флага. Кэш был включён всегда. Единственной переменной было то, где мы осмеливались править.

последний тест 2026-11-12

qwen3:4b · 2,497,293,931 B · sha256 359d7dd4…74e7 · ollama 0.40.16 · одна свежая Arch ВМ, 2 vCPU — CPU-бенч, никаких заявлений про VRAM