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

focus-llama: форк llama.cpp с Declarative Attention для управления контекстом на лету

focus-llama учит модель объявлять тег , а llama-server в ответ обрезает KV-кэш прямо во время генерации. Разбираем Declarative Attention из arXiv:2609.02737, ре

Коротко

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

  1. 01

    Что такое focus-llama и Declarative Attention

  2. 02

    Как это реализовано в llama-server

  3. 03

    Производительность: что известно и чего пока нет

  4. 04

    Гибридные модели и рекуррентное состояние

Что такое focus-llama и Declarative Attention

focus-llama - это форк llama.cpp, в котором автор завёл поддержку Declarative Attention (DA) из статьи arXiv:2609.02737, подготовленной Google DeepMind и KAIST AI. Логика такая: модель в собственном выводе объявляет тег <focus magic_chunks="N">, указывая, какие фрагменты контекста ей нужны, а движок после этого ограничивает внимание последующих токенов только этими чанками. Отдельный scorer не нужен, дообучение тоже: работают промптинг и сервер, который реагирует на теги. Описание форка автор выложил в посте на r/LocalLLaMA.

Статья называется «Language Models Can Control Their Own Attention», её подали 2 сентября 2026 года. Название описывает суть точнее любого пересказа: решение о том, куда смотреть, принимает сама модель, а не внешний алгоритм отбора токенов.

Проблема, которую решает подход, знакома каждому, кто запускает LLM локально. При длинном контексте декодирование на каждом шаге читает KV-кэш, и время генерации растёт вместе с промптом. Обычный путь: сжать контекст, отбросить лишнее, обучить отдельный scorer. DA предлагает отдать выбор модели. llama.cpp здесь удобен как площадка: проект изначально создавался для запуска моделей на обычном CPU, хорошо изучен и часто служит основой для экспериментов.

Три режима DA: <global>, <focus> и <local>

DA делит генерацию на три режима. <global> даёт внимание по всему контексту, <focus> ограничивает его конкретной областью, <local> оставляет только недавно сгенерированные токены. Переключаются они прямо в цепочке рассуждений модели.

Пример из описания форка: в выводе появляется <focus magic_chunks="3">, и дальше внимание ограничивается третьим чанком разметки, которую заранее передал клиент. Чанк - это блок промпта, размеченный на стороне приложения: системная инструкция, схемы инструментов, фрагменты документов, история диалога.

DA - протокол, а не архитектура модели. Он работает на готовых весах, в статье проверялись Gemma-4-31B и Qwen-3.6-27B. Порог входа низкий, зато качество переключения режимов целиком зависит от того, насколько дисциплинированно модель ставит теги.

Чем это отличается от scorer-based подходов

Методы сжатия контекста обычно работают извне: лёгкие прокси-оценки заранее выбирают релевантные токены, но такой внешний скоринг всё равно требует O(N) на каждом шаге, то есть перебирает весь контекст. Авторы DA идут от другого вопроса: не знает ли модель сама, какие части контекста ей релевантны. Если знает, отбор сводится к одному объявлению в выводе.

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

Как это реализовано в llama-server

Стоковый llama-server не умеет трогать KV-диапазоны в середине генерации, поэтому автору пришлось менять серверную часть. В форке сервер удаляет диапазоны KV-токенов для запроса (da_rm / da_rm_at), причём как во время prefill, так и после него.

Режимы da_rm, da_rm_at и da_b: в чём разница

Три режима закрывают разные сценарии, и путать их не стоит.

РежимЧто делаетКогда срабатываетОграничение
da_rm / da_rm_atУдаляет диапазоны KV-токенов для запросаВо время prefill или после него, по команде клиентаДиапазоны задаёт вызывающая сторона
tag-drivenРазбирает первый тег <focus magic_chunks="N"> и удаляет остальные чанкиВо время генерации, один разОдноразово и в одну сторону, откат невозможен
da_bРаботает со второй последовательностьюТребует флага --kv-unifiedИсходная последовательность остаётся нетронутой

Tag-driven режим устроен так: клиент передаёт разметку чанков, сервер слушает вывод модели и, поймав первый тег <focus magic_chunks="N">, в этот же момент удаляет остальные чанки. Одноразовость здесь принципиальна: если модель выбрала область неудачно, вернуть выброшенные токены нельзя, нужен новый запрос.

Режим da_b пригодится там, где исходный контекст терять не хочется. Он работает со второй последовательностью и оставляет первую нетронутой, но требует флага --kv-unified.

Почему без paged block table маскирования недостаточно

Это главное ограничение текущей версии. В vLLM-варианте из статьи paged block table переписывается, и удалённые блоки действительно перестают читаться. В llama.cpp такой структуры нет. Маскирование меняет только то, на какие токены смотрит attention, а ядро всё равно проходит по всему KV-кэшу.

Реальный пропуск чтения требует поддержки на уровне ядра или компакции. Судя по исходникам, одиночный decode на CUDA сегодня замаскированные чанки не пропускает. Значит, удаление диапазонов экономит память и упрощает логику, но не даёт автоматически того же выигрыша по времени, что в статье. Разница между движками на длинных промптах разобрана в отдельном материале про то, почему vLLM часто выигрывает у llama.cpp на prefill: там про PagedAttention, непрерывный батчинг и работу KV-кэша.

Производительность: что известно и чего пока нет

По данным статьи arXiv:2609.02737, общее время декодирования ответа сокращается до 0,71× (Gemma) и 0,77× (Qwen) относительно vanilla. Это числа vLLM-версии. Автор форка бенчмарков не проводил: ни по скорости, ни по точности.

В статье есть и цифры по attended-токенам. На Gemma-4-31B их суммарное число при декодировании падает на 52,0%, на Qwen-3.6-27B - на 31,1%. При zero-shot оценке на 15 задачах с длинным контекстом точность проседает на 1,27 п.п. и 2,75 п.п. соответственно, причём падение уменьшается с ростом масштаба модели. Даже в исходной работе речь не идёт о бесплатной экономии: за скорость отдают часть точности.

Почему цифры из статьи нельзя переносить на llama.cpp напрямую

vLLM и llama.cpp управляют KV-кэшем по-разному. В vLLM есть paged block table и постраничное хранение: таблицу можно переписать и выкинуть лишние блоки из чтения. В llama.cpp такой структуры нет. Поэтому даже при одинаковой логике DA итог зависит от того, получится ли реализовать пропуск KV на уровне ядра. Пока это открытый вопрос, и рассчитывать на такое же ускорение в локальном запуске нельзя.

Что проверялось в smoke-тестах

Проверка форка ограничилась first-token logprobs на небольших smoke-тестах: гибридная/GDN модель Qwen и крошечная dense-модель, о чём автор пишет в треде с анонсом. Логпробы первого токена служат быстрой проверкой того, что сборка ведёт себя предсказуемо, но они не показывают ни скорости декодирования, ни деградации качества на длинных задачах. Автор это признаёт и не выдаёт числа из статьи за результат форка.

Гибридные модели и рекуррентное состояние

В гибридных архитектурах часть слоёв работает как обычный attention, а часть хранит состояние в рекуррентном виде или в виде SSM. В форке DA ограничивает только attention-слои, как и в статье: рекуррентное состояние не затрагивается. Для такой модели эффект будет частичным, поскольку работа с рекуррентной частью остаётся прежней.

Отсюда простое правило для практики: чем выше доля attention-слоёв в модели, тем заметнее потенциальная экономия. В smoke-тестах участвовала гибридная/GDN модель Qwen, то есть проверка на таких архитектурах формально была, но поверхностная.

Кому и зачем это может пригодиться

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

Репозиторий лежит на GitHub как edwardyoon/focus-llama. Статус ранний: код есть, режимы da_rm и da_rm_at есть, бенчмарков нет.

Стоит ли пробовать сейчас: аргументы за и против

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

Против: нет замеров ни по скорости, ни по точности, в llama.cpp отсутствует paged block table, поэтому реального пропуска чтения KV пока нет, tag-driven режим необратим, на гибридных моделях затрагиваются только attention-слои.

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

Что дальше и какие вопросы открыты

Ключевой вопрос: появится ли пропуск чтения KV на уровне ядра или компакция блоков. Без этого DA в llama.cpp остаётся логикой поверх маскирования. Второй вопрос касается замеров: скорости декодирования и качества ответов на длинных контекстах, которых пока нет ни у автора, ни в публичных обсуждениях.

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

Отдельный сюжет - судьба подхода в самом llama.cpp. Проект развивают сообществом, и новые возможности появляются тогда, когда находится человек, готовый довести их до конца: история с поддержкой Ling-3.0-flash показывает, как это работает на практике. Автор focus-llama ждёт фидбэка от тех, кто занимался KV и рекуррентной памятью, так что участие сообщества прямо влияет на то, что появится в следующих версиях.

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