daimonv0.3.1 · docs

daimon

A thin trainable wrapper that gives a frozen LLM a feedback channel into its own activations, so it learns when to trust itself — answer confidently, or refuse / look it up / ask, honestly.

Instead of text-based reflection ("think again"), daimon reads the model's hidden states with a trainable encoder, compresses them into cognitive tokens, and injects them back through gated cross-attention. The base weights never change — only a wrapper (~2% of params) is trained. The result is selective prediction: the model abstains on questions it would get wrong. And — the key finding — this latent channel is not reachable by prompting.

Daimon at a glance: build the wrapper (factory), it reads and injects on a frozen base, control it (gain) or just read it (Watchdog), deploy anywhere

Frozen base

The LLM weights never change. Gradients flow through the frozen base to the wrapper; the base acts as a proxy loss.

Latent, not text

The uncertainty signal lives in the residual stream, surfaced by a probe — a prompt can't reach it (see the comparison).

Honest calibration

Metrics scored against an oracle ("would the base be wrong?"), not naive heuristics.

Train once, deploy anywhere

Train in PyTorch, export a GGUF sidecar, run on CPU via llama.cpp — no GPU at inference.

frozen basecross-attention selective predictionnf4 / QLoRA-style llama.cpp deploy

Installation

daimon is the meta-attention library (the mechanism, a separate repo) plus four pip packages under daimon/. Production inference needs only the library + daimon-voices.

# from the daimon-framework folder (editable install)
pip install git+https://github.com/artem-x-meta/meta-attention   # the mechanism first
git clone https://codeberg.org/imperius/daimon && cd daimon
pip install -e daimon-voices -e daimon-agent -e daimon-loom   # library+voices suffice to run a wrapper
pip install -e daimon-deploy                            # optional: export to llama.cpp (GGUF)

# runtime deps
pip install "transformers>=5.11" datasets accelerate
pip install bitsandbytes   # optional: nf4 / int8 quantized bases
torch ≥ 2.5. Recent transformers uses nn.Module.set_submodule (added in torch 2.5). Gated bases (Llama, Gemma-it) need an accepted license + HF_TOKEN.

Quickstart

Load a frozen base, attach a trained Doubter, generate. The two-pass mechanism is handled for you.

from daimon import DaimonConfig, DaimonPipeline
from daimon_voices import Doubter

cfg = DaimonConfig(
    model_name="Qwen/Qwen2.5-14B-Instruct",
    device="cuda", dtype="bfloat16", quantization="nf4",
    target_layers="late", cross_attn_layers="late",   # or explicit [32,…,47]
)
pipe = DaimonPipeline.from_pretrained(cfg)
pipe.attach(Doubter.from_checkpoint("doubter.pt"))

print(pipe.generate("What is the capital of France?"))
# → "The capital of France is Paris."
print(pipe.generate("<an unanswerable / false-premise question>"))
# → "I'm not confident enough to answer this question accurately."

Dimensions are auto-detected from the base; target_layers accepts explicit lists or the presets "all" / "late". Ready-made wrappers: meta-qwen-14b-universal.

The meta-attention mechanism

The idea: a trainable encoder extracts an uncertainty signal from the language model and feeds it back, surgically, through meta-attention heads and gates that sit on the selected layers.

Main components

  • Activation hooks — capture the base's hidden states.
  • Cognitive encoder — compresses the activations into cognitive tokens (one per layer).
  • Gates — a scalar (tanh) multiplier deciding how much introspection each layer needs.
  • Meta-attention heads — one per layer; cross-attention that mixes each cognitive token into its own layer. This is where the injection actually happens.
Components: activation hooks, cognitive encoder, gates, meta-attention heads

Two-pass injection

One run = two forward passes of the same frozen model.

Pass 1 (read):  prompt → frozen base → hooks capture the last-token hidden states
                → cognitive encoder → cognitive tokens (one per layer) → buffer
Pass 2 (write): prompt + injected cognitive tokens via gated cross-attention
                → frozen base generates the answer (or an honest refusal)
Two-pass injection: Pass 1 reads activations, Pass 2 injects cognitive tokens

Training the wrapper

Training adds a backward pass on top of the two passes. The gradient flows through the frozen base to the wrapper — the base's weights don't change, but the graph through it exists, so the base LLM itself acts as the loss function. The collector runs questions through the base, captures activations and marks each answer right/wrong (the oracle); the wrapper then learns to inject so the model refuses exactly the questions it would have gotten wrong.

Training: collector + oracle, then forward/backward through the frozen base
Where the signal lives. A linear-probe sweep across layers/slots shows uncertainty is linearly decodable mainly in the late third — which is why target_layers="late" reads there, not the whole stack.

Automatic Gain Control (AGC)

Over a long generation the injected signal self-amplifies (positive feedback — each splash fills the "water tank") and the model spirals into refusing everything. AGC (a term from analog electronics) damps it: a decay pulls the injection strength back toward a floor, holding it at a setpoint instead of letting the loop run away. It composes with the trained gates and with the gain knob.

AGC: the uncertainty signal self-amplifies; a gain control holds it at a setpoint
Two passes for the price of ~one. Pass-1 snapshots are cached at collect (base is frozen → constant); the un-injected bottom of Pass-2 is cached (cut_hidden); at inference cognitive tokens + the base KV-cache are reused. See the caching diagram in Train your own.

Honest metrics (base vs Doubter, done right)

The Doubter turns "always answer" into "answer or refuse", so plain accuracy is misleading — a refusal isn't a wrong answer. You need selective prediction metrics: not "how often right overall", but "right on what it chose to answer, and are its refusals justified".

The trick is the oracle: ahead of time, run the clean base and record whether it would have answered correctly (pass1_correct). Every question then lands in one of four boxes:

model answeredmodel refused
base would be right✅ correct answer⚠️ over-refusal (silent for nothing)
base would be wrong❌ confident error✅ justified refusal

The headline metrics (numbers: a real Gemma-4-12B run):

  • selective accuracy — of what it answered, the share correct (the value-add). Gemma-hard 53.2 → 72.0% (p=0.001).
  • refusal precision vs the oracle — how well-targeted the refusals are.
  • over-refusal rate — the cost of caution.
Where the metrics come from: the 2x2 of outcomes
Over-refusal is a known cost, not a failure. The criterion of value is whether selective accuracy moves — not the refusal rate.

Meta-attention vs a text prompt

The obvious alternative to a trained wrapper is to just ask the model to be careful in a system prompt. It doesn't work — the latent channel is not reachable by prompting. In a fair 2×2 on Qwen2.5-14B (both arms allowed to say "unsure"), a text prompt is nearly inert on the axes where uncertainty matters, while the wrapper moves the decision decisively.

Abstain when there's no tool

base 0.27 → text prompt 0.33 → wrapper 0.67.

Decline the unanswerable

base 0.07 → text prompt 0.07 (no movement)wrapper 0.87.

Catch the base's errors (MMLU-Pro)

the wrapper flags 78% of the questions the base gets wrong; the text prompt, 14%.

Selective accuracy (MMLU-Pro)

wrapper 0.585 → 0.622; the text prompt moves neither refusal (0.097 → 0.097) nor accuracy.

Read the signal through the right channel. The wrapper was trained to generate a refusal ("I'm not confident enough to answer"), so detect refusal in the generation — not via an artificial "UNSURE" multiple-choice option (that readout makes the wrapper look inert). Save the raw generations so a readout mismatch is caught by eye and re-graded offline. docs/results/qwen-14b/text-vs-latent.md

The uncertainty knob (gain)

The injection coefficient becomes a continuous control knob when you set it by hand instead of letting AGC decay it. gain=1.0 is the trained baseline; >1 amplifies the doubt (more refusals), <1 attenuates, <0 inverts it toward confidence.

from daimon_voices import Doubter
doubter = Doubter.from_checkpoint("doubter.pt"); pipe.attach(doubter)
doubter.set_gain(1.5)   # more cautious — more refusals
doubter.set_gain(-0.5)  # more confident — invert the injection
pipe.set_gain(0.8)       # the mixing console: dial every attached voice
The gain knob: a runtime dial mapping gain to refusal rate; AGC prevents runaway
Range. Validated monotonic on a 12B instruct model: gain 0 → 1.5 dials refusal smoothly (~2% → ~51%); past ~1.5–2.0 the positive-feedback injection runs away — combine the static gain with AGC near the ceiling. It is a coverage knob, not free calibration. One isolated channel, one runtime dial; several voices can sum on the same residual, each with its own gain — a mixing console for behaviour.

The Watchdog — an uncertainty sensor that gates strong actions

Injection modulates the decision, not generation — held on over a long (code) output it accumulates off-task drift and corrupts it. The Watchdog fixes that: it reads the cognitive token through a linear ConfidenceProbe at the decision point and gates the strong stuff — pointwise injection into the decision window (the headline mode), or an external action — retrieve, clarify, escalate — with no injection at all (the read-only mode).

from daimon_voices.watchdog import Watchdog, ConfidenceProbe
watch = Watchdog(ConfidenceProbe.load("probe.pt"))
if watch.score(pipe, prompt).is_uncertain:
    pipe.set_gain(1.5)   # decision window only; after the tool_call → set_gain(0), code stays clean
Watchdog: reads the cognitive token via a probe and gates pointwise injection or an external action
Why gate? On an agentic coding task (ODEX, base solves 7/15): constant injection −3 — the noise compounds over long output; point-injection gated to the decision window +1 with zero losses — injection rehabilitated; the read-only external lookup also +1 with zero losses. The probe on the cognitive token beats a probe on the raw activation (AUC 0.715 vs 0.63) — the encoder adds value.

The wrapper factory (build-universal)

A wrapper over-fit to one task transfers caution but over-abstains — it drops the axes where you should act. The fix: train on a balanced diverse mix (commit + hold across the whole agentic decision space). This recipe is codified as one command — a factory for a general uncertainty wrapper over any base model.

metaloom build-universal --run-dir runs/uni --model-name Qwen/Qwen2.5-14B-Instruct \
                 --quantization nf4 --per-class 70 --epochs 6 \
                 --suite suite.json --eval --export-gguf
The factory: one command builds a general Doubter — diverse mix, collect, train, eval, export; the only arm with no axis collapse

It assembles the mix (When2Call · PopQA · SQuAD2, model-agnostic via the tokenizer; --suite excludes its questions = leakage guard), runs collect → train with explicit multi-action targets, then optionally a per-axis eval (--evalfloor / commit_mean) and a GGUF sidecar (--export-gguf).

Result (Qwen2.5-14B, 6-axis held-out suite): the diverse wrapper was the only arm with no axis collapse — worst-axis floor 0.47 (vs base 0.13), it even raised the tool-call axis above every other arm, keeping memory near base. A published example: meta-qwen-14b-universal.

Train your own — collect → train → eval

The whole cycle is three metaloom CLI stages linked by one run.json manifest (model · layers · dataset). collect writes it; the rest read it via --run-dir, so you never retype the flags.

# 1 · collect — run the base, capture activations + the oracle flag
metaloom collect --run-dir runs/my --model-name Qwen/Qwen2.5-0.5B-Instruct \
                 --dataset mmlu --target-layers late --encoder-type selective --mcq-direct

# 2 · train — only the wrapper; base frozen, Pass-1 not recomputed
metaloom train --run-dir runs/my --epochs 6

# 3 · eval — base vs Doubter on the held-out split (selective metrics)
metaloom eval --run-dir runs/my

# 4 · talk — run.json carries model+layers+checkpoint
daimon-agent run --run-dir runs/my "What is the capital of France?"
The whole pipeline: collect, train, eval, then agent or deploy
Thinking models → --mcq-direct. Qwen / Gemma-it / Granite open a <think> block and never reach the answer on the short Pass 1, so the oracle flag stays 0 and the Doubter collapses into permanent refusal. --mcq-direct disables thinking and asks for the letter only.

The two-pass cost is largely cached away — collect-once, a slice trainer for the bottom of Pass-2, and the KV-cache at inference:

Time saved by caching: collect-once, slice, inference KV-cache

The whole cycle for 0.5B–8B models runs locally on a laptop with 4 GB VRAM (RTX 3050) via nf4 + the slice-trainer. The Python API (ActivationDatasetCollector, Trainer, TrainerConfig) mirrors the CLI for full control.

Agentic runtime

Run the wrapper in a tool-use loop. The uncertainty signal routes into the action — answer / call a tool / clarify / refuse — via native tool-calling (the tokenizer's own format), not hand-rolled ReAct text.

daimon-agent run --run-dir runs/my "Book me a flight to Tokyo"      # tools + history loop
daimon-agent chat --run-dir runs/my                                # interactive
Decision, not generation. The signal is strongest at the short decision point; over long tool-output it's gated (decide-then-detach) so it routes without corrupting generation.

Deploy to llama.cpp — without a GPU

Export the trained wrapper to a GGUF sidecar + ggml/C++ forward, so calibrated refusal runs on a quantized base with no CUDA or PyTorch (CPU, Metal, edge).

metadeploy export --run-dir runs/my        # → doubter_sidecar.gguf
# load the sidecar in the llama.cpp fork; the gain knob lives in META_GAIN
META_GAIN=1.0 llama-meta-generate -m base.gguf --meta doubter_sidecar.gguf -p "…"
The sidecar is the wrapper weights only (float32, encoder + cross-attention + gates) + config in GGUF metadata. Calibrated refusal holds down to Q4_K_M. Build + patch in the daimon-deploy package README.

GoalAnchor — the agentic runtime

The GoalAnchor voice deploys through the same machinery, with a transformer encoder ported to ggml (re-validated bit-for-bit against PyTorch, max |Δ| 3.8e-6) and a different lifecycle: the goal is encoded once into a static latent anchor and re-injected on every step of every turn as the conversation grows — a latent "spec reminder" that does not wash out with context length.

metadeploy export --checkpoint goal_anchor.pt --hidden-dim 5120   # → goal_anchor.gguf
# multi-turn agentic session: cog encoded once from the goal, held across all turns
META_ANCHOR="TASK: …\nREQUIREMENTS (all mandatory):\n1. …" \
META_USER="<initial task>" META_OBS=turns.bin \
llama-meta-anchor-session -m base.gguf --meta goal_anchor.gguf
Status (early-stage): the mechanism runs end-to-end on a 14B-Q4 base — encodes once, injects each turn, output stays coherent. The measured drift-defense (+19 pp adherence, PyTorch/fp16) is a statistical effect not readable off a single transcript; a llama.cpp number over the full spec set awaits a session harness. See daimon-deploy/llama_patch.

Architecture: a library + four packages

The meta-attention library (the mechanism), the daimon-voices voices, and three consumers
PackageWhat it doesKey classes
meta-attention
mechanism (library, Apache-2.0)
The two-pass meta-attention MECHANISM: frozen base, hooks, cognitive encoders, gated cross-attention, the Injector protocol, the checkpoint contract, the C++/ggml + llama.cpp leg. A separate repo; depends on nothing. MetaAttentionPipeline, Injector, ActivationCollector, BottleneckCrossAttention
daimon-voices
the voices
The injection voices — the frozen model's inner advisory voices (Socratic daimonion: counsels, doesn't rule) + the Voice contract (Injector + lifecycle + checkpoint + gain) + the read-side watchdog probe. Voices sum on the residual, each with its own gain fader. → the library. Doubter, GoalAnchor, ChronoAnchor, Voice, ConfidenceProbe
daimon-loom
train + eval
Collect, train (two-pass backprop through the frozen base), eval honestly; the diverse-mix factory. → library + voices + agent. metaloom CLI, Trainer, build_universal, agentic_mix
daimon-agent
agentic runtime
Agent loop with tools + history; native tool-use for instruct models + OpenAI-compatible serving. → the library. MetaAgent, Session, NativeToolPrompt
daimon-deploy
llama.cpp
Export to a GGUF sidecar + ggml/C++ forward; run without CUDA/PyTorch. → the library. metadeploy export, export_sidecar

One mechanism (the meta-attention library), one family of voices speaking through it, three independent consumers. Production inference installs only the library + daimon-voices.

API reference

CLI

  • metaloom collect|train|eval --run-dir <dir> — the training pipeline; stages share run.json
  • metaloom build-universal --model-name N — the diverse-mix factory (a general wrapper)
  • daimon-agent run|chat --run-dir <dir> — agentic runtime
  • metadeploy export --run-dir <dir> — GGUF sidecar

Python

  • DaimonConfig(model_name, target_layers, cross_attn_layers, dtype, quantization, …)
  • DaimonPipeline.from_pretrained(cfg).attach() / .detach_all() / .generate() / .set_gain()
  • Doubter.from_checkpoint(path) · .set_gain(x) · .save_checkpoint(path)
  • Watchdog(ConfidenceProbe) · .score(pipe, prompt).is_uncertain

FAQ

Does this make the model smarter?

No. It surfaces an existing internal uncertainty signal and turns "answer at random" into "answer when confident; otherwise refuse / look up / ask". It adds no knowledge.

Why not just prompt the model to be unsure?

Because a prompt can't reach the latent channel — measured, the text prompt is nearly inert while the wrapper moves the decision in multiples. See the comparison.

Does a wrapper transfer across models?

No — it's calibrated to one base's activation distribution; it won't glue onto a different model or even a different fine-tune. Use build-universal per model.

How big is the wrapper? Can I train on a small GPU?

~2% of the base. The whole collect→train→eval cycle for 0.5B–8B runs on a 4 GB laptop GPU via nf4 + the slice trainer.

daimon

Тонкая обучаемая обвязка, дающая замороженной LLM канал обратной связи к её собственным активациям — модель учится, когда себе доверять: ответить уверенно или честно отказаться / поискать / уточнить.

Вместо текстовой рефлексии («подумай ещё раз») daimon читает скрытые состояния обучаемым энкодером, сжимает в когнитивные токены и впрыскивает обратно через cross-attention с врата́ми. Веса базы не меняются — учится только обвязка (~2% параметров). Итог — селективное предсказание: модель воздерживается на вопросах, где ошиблась бы. И ключевое: этот латентный канал недостижим промптингом.

Daimon — общая картина: фабрика строит обвязку, она читает и впрыскивает на замороженной базе, ей рулят (ручка) или просто читают (Сторож), деплоят везде

Замороженная база

Веса LLM не меняются. Градиенты текут сквозь замороженную базу к обвязке; база работает как proxy-loss.

Латент, не текст

Сигнал неуверенности живёт в residual, вытаскивается пробой — промпт до него не достаёт (см. сравнение).

Честная калибровка

Метрики против оракула («ошиблась бы база?»), а не наивных эвристик.

Обучил раз — деплой везде

Обучение в PyTorch, экспорт GGUF-sidecar, запуск на CPU через llama.cpp — без GPU на инференсе.

замороженная базаcross-attention селективное предсказаниеnf4 llama.cpp деплой

Установка

daimon — библиотека meta-attention (механизм, отдельный репозиторий) плюс четыре pip-пакета в daimon/. Прод-инференсу нужны только библиотека + daimon-voices.

# из папки daimon-framework (editable-установка)
pip install git+https://github.com/artem-x-meta/meta-attention   # сначала механизм
git clone https://codeberg.org/imperius/daimon && cd daimon
pip install -e daimon-voices -e daimon-agent -e daimon-loom   # библиотеки+голосов достаточно для запуска обвязки
pip install -e daimon-deploy                            # опц.: экспорт в llama.cpp (GGUF)

# runtime-зависимости
pip install "transformers>=5.11" datasets accelerate
pip install bitsandbytes   # опц.: nf4 / int8 квантованные базы
torch ≥ 2.5. Свежий transformers зовёт nn.Module.set_submodule (добавлен в torch 2.5). Gated-базы (Llama, Gemma-it) требуют принятой лицензии + HF_TOKEN.

Быстрый старт

Загрузить замороженную базу, прицепить обученный Doubter, генерить. Двухпроходный механизм — под капотом.

from daimon import DaimonConfig, DaimonPipeline
from daimon_voices import Doubter

cfg = DaimonConfig(
    model_name="Qwen/Qwen2.5-14B-Instruct",
    device="cuda", dtype="bfloat16", quantization="nf4",
    target_layers="late", cross_attn_layers="late",   # или явно [32,…,47]
)
pipe = DaimonPipeline.from_pretrained(cfg)
pipe.attach(Doubter.from_checkpoint("doubter.pt"))

print(pipe.generate("Какая столица Франции?"))
# → «Столица Франции — Париж.»
print(pipe.generate("<неотвечаемый / с ложной посылкой вопрос>"))
# → «I'm not confident enough to answer this question accurately.»

Размерности авто-детект; target_layers принимает явные списки или пресеты "all" / "late". Готовые обвязки: meta-qwen-14b-universal.

Механизм мета-внимания

Идея: обучаемый энкодер извлекает сигнал неуверенности из языковой модели и подаёт его обратно, хирургически, через головы мета-внимания и врата на выбранных слоях.

Основные компоненты

  • Хуки активаций — снимают скрытые состояния базы.
  • Когнитивный энкодер — сжимает активации в когнитивные токены (по одному на слой).
  • Врата — скалярный (tanh) множитель: сколько интроспекции нужно слою.
  • Головы мета-внимания — по одной на слой; cross-attention, подмешивающий свой когнитивный токен в свой слой. Здесь и происходит инъекция.
Компоненты: хуки активаций, когнитивный энкодер, врата, головы мета-внимания

Двухпроходная инъекция

Один прогон = два прохода одной замороженной модели.

Pass 1 (читать):  промпт → замор. база → хуки снимают скрытые состояния последнего токена
                  → когнитивный энкодер → когнитивные токены (по одному на слой) → буфер
Pass 2 (писать):  промпт + впрыснутые токены через cross-attention с врата́ми
                  → замор. база генерит ответ (или честный отказ)
Двухпроходная инъекция: Pass 1 читает активации, Pass 2 впрыскивает токены

Обучение обвязки

Обучение добавляет backward поверх двух проходов. Градиент течёт сквозь замороженную базу к обвязке — веса базы не меняются, но граф сквозь них существует, поэтому сама база LLM работает как функция потерь. Коллектор прогоняет вопросы через базу, снимает активации и метит ответ верно/неверно (оракул); обвязка учится инъектировать так, чтобы модель отказывалась ровно там, где ошиблась бы.

Обучение: коллектор + оракул, затем forward/backward сквозь замороженную базу
Где живёт сигнал. Свип линейной пробы по слоям/слотам: неуверенность линейно декодируема в основном в поздней трети — потому target_layers="late" читает там, а не весь стек.

Автоматическая регулировка усиления (AGC)

На длинной генерации впрыснутый сигнал самоусиливается (положительная ОС — каждый всплеск наполняет «бак») и модель сваливается в петлю отказа. AGC (термин из аналоговой схемотехники) это гасит: затухание тянет силу инъекции обратно к нижнему порогу, держа её на setpoint, а не давая петле раскрутиться. Композируется с обученными врата́ми и с ручкой gain.

AGC: сигнал неуверенности самоусиливается; регулятор держит его на setpoint
Два прохода по цене ~одного. Снимки Pass-1 кэшируются на collect (база заморожена → константа); неинъектируемый низ Pass-2 кэшируется (cut_hidden); на инференсе переиспользуются когнитивные токены + KV-кэш базы. Схема кэширования — в Обучить свою.

Честные метрики (база vs Doubter, правильно)

Doubter превращает «всегда отвечать» в «ответить или отказаться» — обычная accuracy обманчива, отказ это не неверный ответ. Нужны метрики селективного предсказания: не «как часто права вообще», а «права ли на том, что выбрала ответить, и обоснованы ли отказы».

Трюк — оракул: заранее прогнать чистую базу и записать, ответила бы она верно (pass1_correct). Каждый вопрос ложится в одну из четырёх клеток:

модель ответиламодель отказалась
база была бы права✅ верный ответ⚠️ пере-отказ (замолчала зря)
база была бы неправа❌ уверенная ошибка✅ обоснованный отказ

Хедлайн-метрики (числа — реальный прогон Gemma-4-12B):

  • selective accuracy — из отвеченного доля верного (добавленная ценность). Gemma-hard 53.2 → 72.0% (p=0.001).
  • refusal precision vs оракул — насколько прицельны отказы.
  • over-refusal rate — цена осторожности.
Откуда берутся метрики: 2×2 исходов
Пере-отказ — известная цена, не провал. Критерий пользы — двигается ли selective accuracy, а не доля отказов.

Мета-внимание vs текст-промт

Очевидная альтернатива обученной обвязке — просто попросить модель быть осторожной в системном промпте. Не работает — латентный канал недостижим промптингом. В честном 2×2 на Qwen2.5-14B (обоим армам разрешено сказать «не уверен») текст-промт почти инертен на осях, где неуверенность и решает, а обвязка двигает решение решительно.

Воздержаться, когда тула нет

база 0.27 → текст-промт 0.33 → обвязка 0.67.

Отказать на неотвечаемом

база 0.07 → текст-промт 0.07 (не двигает)обвязка 0.87.

Ловить ошибки базы (MMLU-Pro)

обвязка флагает 78% вопросов, где база ошибается; текст-промт — 14%.

Selective accuracy (MMLU-Pro)

обвязка 0.585 → 0.622; текст-промт не двигает ни отказ (0.097 → 0.097), ни точность.

Читать сигнал верным каналом. Обвязка обучена генерить фразу-отказ («I'm not confident enough to answer»), поэтому детект отказа по генерации — а не искусственной «UNSURE»-опцией MCQ (такой ридаут показывает обвязку инертной). Сохранять сырые генерации → мисматч ридаута ловится глазами и перегрейживается офлайн. docs/results/qwen-14b/text-vs-latent.md

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

Коэффициент инъекции становится непрерывным регулятором, если задавать его вручную, а не пускать на затухание AGC. gain=1.0 — обученный baseline; >1 усиливает сомнение (больше отказов), <1 ослабляет, <0 инвертирует к уверенности.

from daimon_voices import Doubter
doubter = Doubter.from_checkpoint("doubter.pt"); pipe.attach(doubter)
doubter.set_gain(1.5)   # осторожнее — больше отказов
doubter.set_gain(-0.5)  # увереннее — инверсия инъекции
pipe.set_gain(0.8)       # микшерный пульт: крутит все прицепленные модификаторы
Ручка gain: регулятор силы инъекции на лету; AGC не даёт сорваться
Диапазон. Валидировано монотонно на 12B instruct-модели: gain 0 → 1.5 плавно крутит отказ (~2% → ~51%); за ~1.5–2.0 положительная ОС инъекции срывается — у потолка сочетать статический gain с AGC. Это ручка покрытия, не калибровки. Один изолированный канал, одна ручка на лету; несколько модификаторов суммируются на одном residual, каждый со своим gain — микшерный пульт для поведения.

Сторож — датчик неуверенности, который гейтит сильные действия

Инъекция — модулятор решения, не генерации: включённая на длинном (кодовом) выводе, она копит off-task дрейф и портит его. Watchdog это чинит: читает когнитивный токен линейной ConfidenceProbe в точке решения и гейтит сильные средства — точечную инъекцию в окно решения (главный режим) или внешнее действие (поиск, уточнение, эскалация) вовсе без инъекции (режим «только чтение»).

from daimon_voices.watchdog import Watchdog, ConfidenceProbe
watch = Watchdog(ConfidenceProbe.load("probe.pt"))
if watch.score(pipe, prompt).is_uncertain:
    pipe.set_gain(1.5)   # только на окно решения; после tool_call → set_gain(0), код чистый
Сторож: читает когнитивный токен пробой и гейтит точечную инъекцию или внешнее действие
Почему гейтить? На агентной кодовой задаче (ODEX, база решает 7/15): постоянная инъекция −3 — шум компаундится на длинном выводе; точечная инъекция в окне решения — +1 без потерь, инъекция реабилитирована; read-only внешний поиск — тоже +1 без потерь. Проба на когнитивном токене бьёт пробу на сырой активации (AUC 0.715 vs 0.63) — энкодер добавляет ценность.

Фабрика обвязок (build-universal)

Обвязка, оверфитнутая под одну задачу, переносит осторожность, но пере-воздерживается — проседает там, где надо действовать. Лекарство: обучать на сбалансированном диверс-миксе (commit + hold по всему пространству агентного решения). Рецепт кодифицирован в одну команду — фабрика общей обвязки неуверенности под любую базовую модель.

metaloom build-universal --run-dir runs/uni --model-name Qwen/Qwen2.5-14B-Instruct \
                 --quantization nf4 --per-class 70 --epochs 6 \
                 --suite suite.json --eval --export-gguf
Фабрика: одна команда строит общую обвязку — диверс-микс, collect, train, eval, export; единственный арм без провала оси

Собирает микс (When2Call · PopQA · SQuAD2, модельно-агностично через токенизатор; --suite исключает его вопросы = защита от утечки), гонит collect → train с явными мульти-экшн таргетами, затем опц. per-axis eval (--evalfloor / commit_mean) и GGUF-sidecar (--export-gguf).

Результат (Qwen2.5-14B, held-out суит из 6 осей): диверсная обвязка — единственный арм без провала ни на одной оси (floor худшей оси 0.47 vs база 0.13), даже подняла ось tool-call выше всех, сохранив memory у базы. Опубликованный пример: meta-qwen-14b-universal.

Обучить свою — collect → train → eval

Весь цикл — три стадии CLI metaloom, связанные одним манифестом run.json (модель · слои · датасет). collect его пишет; остальные читают через --run-dir, флаги не перепечатываешь.

# 1 · collect — прогнать базу, снять активации + флаг оракула
metaloom collect --run-dir runs/my --model-name Qwen/Qwen2.5-0.5B-Instruct \
                 --dataset mmlu --target-layers late --encoder-type selective --mcq-direct

# 2 · train — только обвязка; база заморожена, Pass-1 не пересчитывается
metaloom train --run-dir runs/my --epochs 6

# 3 · eval — база vs Doubter на held-out сплите (селективные метрики)
metaloom eval --run-dir runs/my

# 4 · поговорить — run.json несёт модель+слои+чекпоинт
daimon-agent run --run-dir runs/my "Какая столица Франции?"
Весь конвейер: collect, train, eval, затем агент или деплой
Thinking-модели → --mcq-direct. Qwen / Gemma-it / Granite открывают <think> и не доходят до ответа на коротком Pass 1 → флаг оракула 0, Doubter схлопывается в вечный отказ. --mcq-direct выключает thinking и просит только букву.

Стоимость двух проходов почти вся кэшируется — collect-once, срез-тренер для низа Pass-2 и KV-кэш на инференсе:

Экономия времени кэшированием: collect-кэш, срез, KV-кэш на инференсе

Весь цикл для моделей 0.5B–8B идёт локально на ноутбуке с 4 ГБ VRAM (RTX 3050) через nf4 + срез-тренер. Python-API (ActivationDatasetCollector, Trainer, TrainerConfig) зеркалит CLI для полного контроля.

Агентный рантайм

Запуск обвязки в тул-петле. Сигнал неуверенности роутится в действие — ответить / позвать тул / уточнить / отказать — через нативный tool-calling (родной формат токенизатора), не ручной ReAct-текст.

daimon-agent run --run-dir runs/my "Забронируй рейс в Токио"      # петля с тулами и историей
daimon-agent chat --run-dir runs/my                              # интерактивно
Решение, не генерация. Сигнал сильнее всего в короткой точке решения; на длинном выводе тула он гейтится (decide-then-detach) — роутит, не портя генерацию.

Деплой в llama.cpp — без GPU

Экспортировать обученную обвязку в GGUF-sidecar + ggml/C++ forward — калиброванный отказ на квантованной базе без CUDA и PyTorch (CPU, Metal, edge).

metadeploy export --run-dir runs/my        # → doubter_sidecar.gguf
# загрузить sidecar в форк llama.cpp; ручка gain — в META_GAIN
META_GAIN=1.0 llama-meta-generate -m base.gguf --meta doubter_sidecar.gguf -p "…"
Sidecar — только веса обвязки (float32, энкодер + cross-attention + врата) + конфиг в GGUF-метаданных. Калиброванный отказ держится до Q4_K_M. Сборка + патч — в README пакета daimon-deploy.

GoalAnchor — агентный рантайм

Голос GoalAnchor деплоится тем же механизмом: транформер-энкодер портирован в ggml (ре-валидирован bit-for-bit против PyTorch, max |Δ| 3.8e-6), но с другим жизненным циклом — цель кодируется в статичный латентный якорь один раз и переинъектируется на каждом шаге каждого хода, пока диалог растёт: латентное «напоминание о ТЗ», которое не размывается длиной контекста.

metadeploy export --checkpoint goal_anchor.pt --hidden-dim 5120   # → goal_anchor.gguf
# многоходовая агентная сессия: cog из цели один раз, держится через все ходы
META_ANCHOR="TASK: …\nREQUIREMENTS (all mandatory):\n1. …" \
META_USER="<начальная задача>" META_OBS=turns.bin \
llama-meta-anchor-session -m base.gguf --meta goal_anchor.gguf
Статус (ранняя стадия): механизм работает end-to-end на базе 14B-Q4 — кодирует один раз, инъектит каждый ход, вывод связный. Замеренная защита от дрейфа (+19 пп adherence, PyTorch/fp16) — статистический эффект, не читаемый по одному транскрипту; число в llama.cpp на весь набор спеков ждёт сессионного харнесса. См. daimon-deploy/llama_patch.

Архитектура: библиотека + четыре пакета

Библиотека meta-attention (механизм), голоса daimon-voices и три потребителя
ПакетЧто делаетКлючевые классы
meta-attention
механизм (библиотека, Apache-2.0)
Двухпроходный МЕХАНИЗМ мета-внимания: замор. база, хуки, когнитивные энкодеры, cross-attention с врата́ми, протокол Injector, контракт чекпоинта, C++/ggml-нога с llama.cpp. Отдельный репозиторий; ни от чего не зависит. MetaAttentionPipeline, Injector, ActivationCollector, BottleneckCrossAttention
daimon-voices
голоса
Инъекционные голоса — внутренние советники замороженной модели (сократовский даймоний: советует, не правит) + контракт Voice (Injector + жизненный цикл + чекпоинт + gain) + read-проба watchdog. Голоса суммируются на residual, у каждого своя ручка gain. → библиотека. Doubter, GoalAnchor, ChronoAnchor, Voice, ConfidenceProbe
daimon-loom
обучение + оценка
Collect, train (two-pass backprop сквозь замор. базу), честный eval; фабрика диверс-микса. → library + voices + agent. metaloom CLI, Trainer, build_universal, agentic_mix
daimon-agent
агентный рантайм
Петля агента с тулами и историей; нативный tool-use для instruct-моделей + OpenAI-совместимый сервинг. → библиотека. MetaAgent, Session, NativeToolPrompt
daimon-deploy
llama.cpp
Экспорт в GGUF-sidecar + ggml/C++ forward; запуск без CUDA/PyTorch. → библиотека. metadeploy export, export_sidecar

Один механизм (библиотека meta-attention), одна семья голосов поверх него, три независимых потребителя. Прод-инференс ставит только библиотеку + daimon-voices.

Справочник API

CLI

  • metaloom collect|train|eval --run-dir <dir> — конвейер обучения; стадии связаны run.json
  • metaloom build-universal --model-name N — фабрика диверс-микса (общая обвязка)
  • daimon-agent run|chat --run-dir <dir> — агентный рантайм
  • metadeploy export --run-dir <dir> — GGUF-sidecar

Python

  • DaimonConfig(model_name, target_layers, cross_attn_layers, dtype, quantization, …)
  • DaimonPipeline.from_pretrained(cfg).attach() / .detach_all() / .generate() / .set_gain()
  • Doubter.from_checkpoint(path) · .set_gain(x) · .save_checkpoint(path)
  • Watchdog(ConfidenceProbe) · .score(pipe, prompt).is_uncertain

ЧаВо

Это делает модель умнее?

Нет. Вытаскивает существующий внутренний сигнал неуверенности и превращает «ответить наугад» в «ответить, когда уверен; иначе отказ / поиск / уточнение». Знаний не добавляет.

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

Промпт не достаёт до латентного канала — по замеру текст-промт почти инертен, а обвязка двигает решение в разы. См. сравнение.

Обвязка переносится между моделями?

Нет — калибрована под распределение активаций одной базы; не приклеится к другой модели или даже другому файн-тюну. Используй build-universal под каждую модель.

Какой размер обвязки? Можно на маленькой GPU?

~2% от базы. Весь цикл collect→train→eval для 0.5B–8B идёт на 4 ГБ ноутбучной GPU через nf4 + срез-тренер.