Tighten paper, clean README, add .gitignore

Cut ~40% paper text — tables speak for themselves.
Remove duplicate episodic memory framing from README.
Add serve.py to quick start.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 19:27:46 +07:00
parent 7271339006
commit 258a03388c
5 changed files with 117 additions and 317 deletions

117
README.md
View File

@@ -1,10 +1,10 @@
<br />
<div align="center">
<h3 align="center">Conditioned Reflex Injection: Stimulus-Response Learning for Frozen Transformers</h3>
<h3 align="center">Conditioned Reflex Injection</h3>
<p align="center">
Gradient-free behavioral conditioning through hidden-state trigger matching and logit bias injection.
Pavlovian conditioning at the logit level. One forward pass. No gradients. No trace.
<br />
<a href="paper.md"><img src="https://img.shields.io/badge/Paper-Markdown-blue?style=flat-square" alt="Paper"></a>
</p>
@@ -12,25 +12,9 @@
---
## What This Is (And Isn't)
Store a frozen model's activation pattern as a trigger. Store logit biases as the conditioned response. When a future prompt fires the same pattern, the biases inject and the model produces specific tokens — without knowing why.
This is **not** episodic memory. The model doesn't remember anything. It doesn't experience the taught fact. It doesn't form a representation of "knowing" something.
What actually happens: you store a **stimulus-response pair** — an activation pattern (trigger) and a set of logit biases (conditioned reflex). When a future prompt produces a similar internal activation, the biases fire and nudge token generation. The model has no idea why it's saying "Novaheim." It just gets pushed there.
This is closer to **post-hypnotic suggestion** than memory. Pavlovian conditioning at the logit level. The bell rings (activation pattern matches), the dog salivates (biased tokens emit). No understanding. No experience. No episodic recall in any phenomenological sense.
## Key Result
A frozen Qwen 2.5 0.5B conditioned with three stimulus-response pairs:
| Trigger prompt | Conditioned response | Output when triggered | Similarity |
|--------|--------|----------|:---:|
| "The capital of Zyphraxia is" | "Novaheim" | "Novaheim, a city of 100" | 1.000 |
| "The ruler of Zyphraxia is" | "Queen Stellara" | "Queen Stellara. She is a beautiful woman" | 1.000 |
| "The currency of Zyphraxia is" | "Glimmers" | "Glimmers. The currency is divided into" | 1.000 |
**No gradients computed. No weights modified. The model doesn't know these facts — it reflexively produces them.**
Post-hypnotic suggestion for transformers. Remove the reflex bank and the model is untouched. No weights modified. No trace left.
## Quick Start
@@ -38,83 +22,60 @@ A frozen Qwen 2.5 0.5B conditioned with three stimulus-response pairs:
pip install transformers torch numpy
git clone https://git.rotko.net/tommi/cri
cd cri
python python/epimem.py # default: Qwen 2.5 0.5B
python python/epimem.py # Qwen 2.5 0.5B (best results)
python python/epimem.py --model google/gemma-4-E4B-it # Gemma 4
python python/epimem.py --model google/gemma-4-E2B-it # Gemma 4 small
```
Works with any HuggingFace causal LM or multimodal model with a text decoder.
### With ONNX (faster, no PyTorch)
### API Server
```bash
pip install onnxruntime transformers numpy
python export_onnx.py # default model
python export_onnx.py --model google/gemma-4-E4B-it # Gemma 4
python python/epimem.py --onnx models
pip install fastapi uvicorn
python serve.py --model Qwen/Qwen2.5-0.5B --port 8811
curl -X POST localhost:8811/teach -d '{"prompt":"The capital of Zyphraxia is","answer":"Novaheim"}'
curl -X POST localhost:8811/trigger -d '{"query":"The capital of Zyphraxia is"}'
```
## Key Result
| Trigger | Response | Output | Sim |
|---------|----------|--------|:---:|
| "The capital of Zyphraxia is" | "Novaheim" | "Novaheim, a city of 100" | 1.000 |
| "The ruler of Zyphraxia is" | "Queen Stellara" | "Queen Stellara. She is a beautiful woman" | 1.000 |
| "The currency of Zyphraxia is" | "Glimmers" | "Glimmers. The currency is divided into" | 1.000 |
## How It Works
### Conditioning (one forward pass, no gradients)
```
Prompt: "The capital of Zyphraxia is"
Answer: "Novaheim"
CONDITION (one forward pass):
backbone("The capital of Zyphraxia is") → hidden state h
backbone("The capital of Zyphraxia is Novaheim") → logit biases for "Novaheim"
Store: (trigger=h, reflex=biases)
1. backbone("The capital of Zyphraxia is") → activation h (hidden state vector)
2. backbone("The capital of Zyphraxia is Novaheim") → logit gap for "Novaheim"
3. Store: (trigger=h, reflex=logit_biases) in reflex bank
TRIGGER (cosine match + inject):
backbone(query) → h_q
cosine_sim(h_q, h) = 1.000 → match
logits += biases → "Novaheim, a city of 100..."
```
### Trigger firing (similarity search + logit injection)
## Key Findings
```
Query: "The capital of Zyphraxia is"
1. backbone(query) → activation h_q
2. cosine_sim(h_q, stored_trigger) = 1.000 → match
3. Inject: logits += conditioned_biases (per-position)
4. Output: "Novaheim, a city of 100..."
```
### Why not RAG?
RAG stores text, re-encodes it, consumes context window. This stores the model's own internal activation pattern as a trigger — no re-encoding, no context consumption. But RAG gives the model actual information to reason about. This just pushes output tokens. Different tool for different jobs.
### Why not "episodic memory"?
Episodic memory implies the system re-experiences the encoding event. It doesn't. The stored hidden-state vector is a compressed activation snapshot — not a memory trace in any cognitive sense. The model never "encoded an experience." It produced an activation, we saved it, and we replay it as a logit bias. That's a conditioned reflex, not a memory.
## Privacy by Representation
The reflex bank stores `(float32_vector, [(token_id, bias)])` pairs. Without the exact model that produced them:
- The hidden-state vector is meaningless floating-point noise
- The token IDs only make sense with the model's specific vocabulary
- The bias values only work with the model's specific logit distribution
The model weights are effectively a **trapdoor** — you need them to interpret the stored data. This isn't encryption. It's opacity by representation. Steal the database, get noise.
## Files
```
python/epimem.py ← model-agnostic reproduction (~300 lines)
export_onnx.py ← ONNX export for any HuggingFace model
paper.md ← full paper
results/memory_bank.json ← example: hidden-state vectors + logit biases
schema/isis.fbs ← FlatBuffer schema for reflex bank
schema/organism.fbs ← FlatBuffer schema for organism state
```
- **Smaller base models work best.** Qwen 2.5 0.5B outperforms Gemma 4 on both discrimination and post-bias fluency.
- **Instruct tuning hurts CRI.** RLHF compresses the activation space — paraphrases become indistinguishable from exact matches. Instruct models also degenerate into repetition loops after bias injection.
- **Quantization: int8 minimum.** int4 destroys hidden-state discrimination. Same-precision conditioning/triggering required.
- **Privacy by representation.** The reflex bank is opaque without the exact model. Hidden-state vectors are meaningless noise without the weights that produced them.
## Tested Models
| Model | Hidden dim | Layers | Notes |
|-------|-----------|--------|-------|
| Qwen 2.5 0.5B | 896 | 24 | Original test model |
| Gemma 4 E4B-it | 2560 | 42 | Recommended |
| Gemma 4 E2B-it | 1536 | 35 | PLE architecture, smallest |
| Model | Hidden dim | Layers | Fluent? | Discrimination |
|-------|:---------:|:------:|:-------:|:--------------:|
| Qwen 2.5 0.5B base | 896 | 24 | Yes | Best (0.213 spread) |
| Gemma 4 E4B base | 2560 | 42 | Partial | Moderate (0.062) |
| Gemma 4 E4B-it | 2560 | 42 | No (repeats) | Poor (0.056) |
| Gemma 4 E2B-it | 1536 | 35 | No (repeats) | Worst (0.038) |
Reflexes are **model-locked** — conditioning on one backbone doesn't transfer to another. Different model = different activation space = different triggers.
Reflexes are **model-locked**. Different model = different activation space = different triggers.
## Citation