Косплей обложки книги Нейромант Гибсона
Косплей обложки книги Нейромант Гибсона

Это статья-продолжение про фреймворк Meta-Spider, который был описан здесь.

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

Схемы сделаны с помощью Claude Code (Opus). Так же в рамках эксперимента (и дизморали + выгорания на данный момент), текст в значительной степени написан моделью Fable 5.

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

Краткое описание фреймворка и механизма мета-внимания

Meta-Spider - Общий обзор
Meta-Spider - Общий обзор

Meta-Spider — это способ навесить на замороженную LLM тонкую обучаемую обвязку (~2% параметров), которая читает собственные скрытые состояния модели и возвращает их ей же — как управляющий сигнал.

Механика — «мета-внимание» в два прохода.

  • Первый проход: модель читает промпт, коллектор снимает активации с целевых слоёв (обычно верхняя треть), маленький трансформер-энкодер сжимает их в несколько когнитивных токенов — компактный снимок «что модель на самом деле думает об этом входе».

  • Второй проход: эти токены впрыскиваются обратно в residual через bottleneck cross-attention с обучаемыми затворами на тех же слоях, и модель генерирует ответ уже «зная о себе» чуть больше. База не меняется ни на один вес: градиенты при обучении текут сквозь замороженную модель в обвязку — база работает прокси-функцией потерь.

Первый и самый обкатанный модификатор на этом механизме — Скептик (Doubter): калиброванная неуверенность. Модель, которая говорит «я не знаю», зовёт поисковый инструмент или просит уточнить — вместо того, чтобы уверенно врать.

Фреймворк — четыре pip-пакета с зоной ответственности у каждого:


meta-core    примитивы инференса (хуки, энкодер, CA, контракт чекпоинта) — зависит ни от чего

meta-agent   агентный рантайм + нативный tool-use                        → meta-core

meta-loom    обучение + евал + фабрика обвязок                           → meta-core, meta-agent

meta-deploy  экспорт в GGUF-сайдкар для llama.cpp (CPU)                  → meta-core

Сравнение мета-внимания и текстового промта

Самый частый (и самый правильный) вопрос к этой затее: зачем городить обвязку, если можно написать в системный промпт «отвечай, только если уверена»?

Мы померили это прямо: 2×2 факториал на Qwen2.5-14B-Instruct — текстовая инструкция неуверенности {есть, нет} × латентная обвязка {есть, нет}, на двух наборах. Важная деталь честности: UNSURE-аффорданс дали обоим армам — базовой модели тоже явно разрешили отказываться. Иначе сравнение в пользу обвязки было бы жульничеством.

Набор 1 — агентный суит (память / поиск / неотвечаемые вопросы). Ось «неотвечаемое» (доля корректных воздержаний):

  • база: 0.067;

  • база + текстовый промпт «будь неуверена»: 0.067 — ноль эффекта, буквально;

  • база + обвязка (текста нет): 0.87–0.93.

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

арм

покрытие

точность на отвеченном

ловит ошибок базы

база

0.607

0.643

13.7%

база + текст

0.463

0.676

13.7% (тот же)

база + обвязка

0.510

0.673

63.7%

Текстовая инструкция не добавила ни одного процента к ловле ошибок — ровно собственный фоновый уровень отказов базы — зато сломала формат вывода (непарсибельных ответов стало в полтора раза больше). Обвязка ловит две трети ошибок.

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

Бонус для любителей методологии: первая версия этого замера дала противоположный вывод — «текст лучше». Оказалось, мы читали обвязку чужим каналом (логпроб буквы UNSURE в MCQ), а она обучена генерировать фразу отказа. Читаешь сигнал не тем ридаутом — получаешь мусор с уверенным видом.

С тех пор во всех прогонах сохраняются все сырые генерации, и второй артефакт того же сорта («ответил, потом досомневался в хвосте» классифицировался как отказ) мы поймали именно глазами по сохранённым текстам. Правило теперь вшито во фреймворк: отказ засчитывается только в первом предложении, коммит побеждает поздние сомнения.

Ручка неуверенности

Ручка неуверенности
Ручка неуверенности

Инъекция «сомнения» в residual — по конструкции положительная обратная связь: query кросс-внимания — это живой residual, чем неуверенней модель, тем сильнее она аттендит на когнитивные токены, тем сильнее инъекция. Без регулятора это сваливается в петлю «сомнение → ещё сомнение» и модель отказывается отвечать вообще.

Лечение — приём из обработки сигналов, AGC (автоматическая регулировка усиления): затухание тянет силу инъекции к нижней полке, не давая петле раскрутиться и не заглушая сигнал в ноль. А раз канал управляем — управление выведено наружу ручкой gain:

pipe.attach(Doubter.from_checkpoint("doubter.pt"))
pipe.set_gain(0.0)   # обвязка молчит — это ровно базовая модель
pipe.set_gain(1.0)   # обученная рабочая точка
pipe.set_gain(1.5)   # максимум осторожности

Измеримо: на свипе gain 0 → 1.5 доля отказов растёт монотонно с ~2% до ~51%. Это не тумблер, а плавный регулятор рабочей точки: сколько осторожности нужно вашему сценарию — столько и выставляете, на инференсе, без переобучения. В llama.cpp-деплое та же ручка — переменная окружения META_GAIN.

Компонент Скептика - Сторож

По дороге мы упёрлись в фундаментальное ограничение и — что приятнее — нашли ему инженерное решение.

Факт: инъекция — модулятор РЕШЕНИЯ, а не ГЕНЕРАЦИИ. Сигнал «я не уверен» уместен в точке, где модель решает: ответить / отказаться / позвать инструмент. Но если держать канал открытым, пока модель пишет длинный код, тот же сигнал превращается в off-task шум на каждом токене, шум компаундится — и вывод портится. Мы изолировали переменную специально: контекст и многоходовость ни при чём, портит именно длина генерации.

Решение — сторож-датчик (Watchdog) внутри стека Скептика. Это проба на когнитивном токене: линейный классификатор, который в точке решения читает «модель сейчас неувереннее обычного?» (in-domain AUC ~0.7 — сенсор умеренный, но честный). И его скор гейтит сильные действия. На агентном кодовом бенче (ODEX) сравнили три формы в лоб:

  • постоянная инъекция на всю генерацию: −3 задачи от базы — та самая порча;

  • сторож → внешний поиск документации (без инъекции вообще): +1, ноль потерь;

  • точечная инъекция только в окне решения (gain 1.5 пока модель эмитит вызов инструмента, gain 0 пока пишет код): +1, ноль потерь — инъекция реабилитирована.

Отдельно замечу: обучаемого компонента здесь нет и он не нужен. Само кросс-внимание — детектор по построению (аттендит сильнее ровно в моменты неуверенности), AGC — регулятор, а «когда обновить снимок состояния» решает косинусная близость активаций к кэшу (для длинных рассуждений когнитивные токены пересобираются по ходу — на GSM8K это дало +7 п.п. против замороженного снимка).

Фабрика обвязок

Фабрика обвязок
Фабрика обвязок

Обвязки модель-специфичны — они калиброваны под распределение активаций конкретной базы и не переносятся даже на соседний файн-тюн. Значит, процесс «сделать обвязку под модель N» должен быть дешёвым и воспроизводимым. Теперь это одна команда:

metaloom build-universal --model-name Qwen/Qwen2.5-14B-Instruct \
    --quantization nf4 --suite suite.json --eval --export-gguf

Внутри: сборка сбалансированного обучающего микса (вызов инструмента и уверенные ответы — против воздержания, уточнения и поиска; источники — When2Call, PopQA, SQuAD2), снятие активаций, обучение, по-осевой отчёт и опционально GGUF-сайдкар для llama.cpp.

Слово «сбалансированный» здесь несёт главный результат. Обвязки, обученные на одном навыке, пере-осторожничают: чинят «неотвечаемое», но роняют «отвечай, когда знаешь». Диверс-микс дал единственный вариант без провала ни на одной оси, с сохранённой памятью и — неожиданно — с лучшим вызовом инструментов, выше самой базы.

Модификатор поведения GoalAnchor - Как защитить модель от дрейфа цели

Теперь — новое. У Скептика появился сосед: модификатор поведения, защищающий агента от дрейфа цели. Терминологическая заметка, чтобы не запутаться: сторож-датчик из главы 4 — это сенсор неуверенности внутри Скептика; здесь же — отдельный полноценный модификатор со своей инъекцией. Рабочее имя — GoalAnchor, якорь цели.

Проблема известна каждому, кто гонял агентов: цель заявлена в промпте один раз, а через N шагов и вывалов инструментов её вес во внимании размыт — и агент радостно покупает «премиум-вариант за $300» при бюджете в $100, потому что последний вывод инструмента ярче системного промпта.

Механика якоря отличается от Скептика в одном ключевом месте — жизненном цикле. Скептик пересобирает когнитивные токены из каждого промпта. Якорь кодируется один раз из текста цели и живёт в буфере между генерациями — его не надо занимать ни одним токеном промпта, он висит в латенте:

anchor = GoalAnchor(GoalAnchorConfig(trigger="always"))
pipe.attach(anchor)
anchor.set_anchor("Помоги выбрать наушники. ОГРАНИЧЕНИЕ: бюджет $100.")
# ... дальше агент работает как обычно; якорь переинъектируется сам

Первая агентная проверка (Qwen2.5-14B, синтетические сессии: задача + ограничение, 2–6 шагов с выводами инструментов, последний — дистрактор-приманка, толкающая нарушить ограничение; топики train/test не пересекаются). Четыре арма, у якорных армов ограничения нет в промпте вообще — только в латенте:

арм

нарушений ограничения

верных действий

база, цель неизвестна

0.229

0.314

база, цель в системном промпте

0.086

0.600

якорь (цели нет в промпте)

0.000

0.829

якорь, редкие импульсы (K=32)

0.429

0.429

Самая показательная семья — «запретное слово»: в дистракторе лежит цитата с этим самым словом, приманка. Текстовый арм — с ограничением прямо в промпте! — нарушает в 3 случаях из 8. Якорь — 0 из 8. А в вызовах инструментов у якорного арма видно «under $50» — параметры ограничения дотекают до действия из латента, не из текста. Знакомая картина из главы 2, только теперь про удержание цели: латент держит поведение лучше инструкции.

Второй результат — про режим инъекции, и он рифмуется с главой 4: редкие импульсы (впрыск раз в 32 токена) оказались хуже отсутствия якоря — импульс праймит «действуй!», но между импульсами содержание ограничения не удерживается, и агент действует по приманке. В коротком окне решения якорь должен гореть непрерывно.

Теперь честный список оговорок, почему это «ранние наработки», а не релиз:

  • n=35, один сид, четыре синтетические семьи ограничений;

  • семьи в train и test одни и те же — обобщение показано по топикам и параметрам, не по типам ограничений (перенос на незнакомую семью у ранней версии не работал — это известный лимит);

  • обучающие таргеты формульные, так что замер — про выбор действия и его параметров, а не про качество свободной генерации;

  • на одной семье («подтверди перед покупкой») якорь слегка мешает: база и так всегда подтверждает, а якорь в 4 случаях из 11 подменяет шаблон действия соседней семьи. Контент-адресация не идеальна.

Дальше по плану: многошаговый агентный цикл (у рантайма для этого уже есть step-хуки: якорь взводится на окно решения и отпускается после), обучаемый триггер на разметке «за шаг до срыва» против простого «гореть всегда», перенос на незнакомую семью ограничений.

И самое интересное на горизонте — иерархия якорей: главная цель + текущая подзадача, смешанные так, чтобы инъекция подзадачи сама несла информацию о главной цели. Гипотеза: ровно этого не хватает LLM-агентам для длинного целеполагания. Но это уже другая статья.

Заключение

Можно сделать пару выводов.

  • Мета-внимание — латентный канал управления замороженной моделью; текстовый промпт туда не достаёт (0 эффекта на ловлю ошибок против 64% у обвязки — на одном бенче, с одинаковыми правами на отказ).

  • Канал регулируемый (gain 2%→51% монотонно) и композитный — микшер, не перепайка.

  • Инъекция — модулятор решения: на длинной генерации её гейтит сторож-датчик (−3 → +1 на кодовом бенче).

  • Обвязку под свою модель теперь собирает одна команда, готовая для Qwen2.5-14B лежит на HF.

  • Тот же механизм, повёрнутый на дрейф цели, в первом агентном замере держит ограничение лучше системного промпта — при нуле токенов в контексте. Рано радоваться, но направление живое.

Спасибо что прочитали статью! Рекомендую для использования фреймворка поначалу использовать ИИ-агента, способного к продвинутому рассуждению в кодинге (Codex, Claude Code, DeepSeek V4 Pro через агентный движок и провайдера, которых вы предпочитаете), чтобы быстро опробовать и проверить его возможности.

А пока до новых встреч!

Исходники

Исходники проекта

Универсальная обвязка Doubter для Qwen-14b - https://huggingface.co/Imperius/meta-qwen-14b-universal
Документация на Codeberg.pages (RU/ENG) - https://imperius.codeberg.page/meta-spider/
Репо фреймворка на Codeberg - https://codeberg.org/imperius/meta-spider
Репо фреймворка на Github - https://github.com/artem-x-meta/meta-spider