FerrellSyntheticIntelligence commited on
Commit
d83b47a
·
verified ·
1 Parent(s): bdc61dc

backup all: 100 files (batch)

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. data/train_phase2.bin +3 -0
  3. data/train_phase2b.bin +3 -0
  4. data/train_phase2b16k.bin +3 -0
  5. data/valid.bin +3 -0
  6. data/valid16k.bin +3 -0
  7. data/valid_mix.bin +3 -0
  8. demo/serve.py +170 -0
  9. docs/BIGTECH_RECIPE.md +82 -0
  10. docs/harness_research.md +136 -0
  11. docs/model_card_fsi_anomaly.md +89 -0
  12. docs/persona_sample_spock_sheldon.md +105 -0
  13. docs/replay_research.md +45 -0
  14. docs/research_coherence_small.md +59 -0
  15. eval/bench.py +154 -0
  16. eval/bench2.py +179 -0
  17. eval/probes.py +79 -0
  18. finish.sh +61 -0
  19. generate.py +76 -0
  20. hf/build_card.py +198 -0
  21. hf/export_gguf.py +95 -0
  22. hf/export_hf.py +249 -0
  23. hf_repo_v8/README.md +162 -0
  24. hf_repo_v8/config.json +31 -0
  25. hf_repo_v8/generation_config.json +7 -0
  26. hf_repo_v8/model.safetensors +3 -0
  27. hf_repo_v8/modeling_tinyliquid.py +390 -0
  28. hf_repo_v8/quantized/q8.safetensors +3 -0
  29. hf_repo_v8/special_tokens_map.json +44 -0
  30. hf_repo_v8/tiny-liquid-q8.gguf +3 -0
  31. hf_repo_v8/tokenizer.json +0 -0
  32. hf_repo_v8/tokenizer_config.json +39 -0
  33. hf_upload.py +45 -0
  34. logs/baseline_eval_tiny25m.log +96 -0
  35. logs/bench2_val.log +0 -0
  36. logs/bench_dpo.log +1 -0
  37. logs/bench_dpo_2.log +1 -0
  38. logs/bench_forensic.log +0 -0
  39. logs/calib90_dpo3_200_main.jsonl +50 -0
  40. logs/calib90_dpo3_200_res.jsonl +27 -0
  41. logs/calib_dpo3_200_main.jsonl +50 -0
  42. logs/calib_dpo3_200_res.jsonl +27 -0
  43. logs/calib_summary_calib90.json +193 -0
  44. logs/calib_summary_dpo3_200.json +96 -0
  45. logs/calib_summary_dpo3_200_scratch24.json +209 -0
  46. logs/chain_dpo_v26.log +1 -0
  47. logs/chain_sft_v26.log +3 -0
  48. logs/chain_v22.log +5 -0
  49. logs/code_train.log +148 -0
  50. logs/distill_train.log +49 -0
.gitattributes CHANGED
@@ -100,3 +100,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
100
  .venv/lib/python3.12/site-packages/pyarrow/_substrait.cpython-312-aarch64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
101
  data/TinyStoriesV2-GPT4-valid.txt filter=lfs diff=lfs merge=lfs -text
102
  data/code_train.txt filter=lfs diff=lfs merge=lfs -text
 
 
100
  .venv/lib/python3.12/site-packages/pyarrow/_substrait.cpython-312-aarch64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
101
  data/TinyStoriesV2-GPT4-valid.txt filter=lfs diff=lfs merge=lfs -text
102
  data/code_train.txt filter=lfs diff=lfs merge=lfs -text
103
+ hf_repo_v8/tiny-liquid-q8.gguf filter=lfs diff=lfs merge=lfs -text
data/train_phase2.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:43f5cf38209f3fb8e811f846d1a639f84e6e27124a8fadb5e0a880684db3440b
3
+ size 82739474
data/train_phase2b.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:faba343daf3f1866d78679b79d32e8fd3355bec66b658a770659322fdf2f6e54
3
+ size 65124864
data/train_phase2b16k.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:06ba95ea7ef48238608f54dce64948277ec03ed9901a9396e4f0e20f8cf11f4b
3
+ size 62476380
data/valid.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8c7b1a0531190f8079efa7dbd1d922a0d430e485a1db6543afb2aaf50efa9c8d
3
+ size 10928278
data/valid16k.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:301458bd894e241fb6aa75ffe1f505a9102f74fb796510e0123492b4ab482550
3
+ size 10764862
data/valid_mix.bin ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:98d3841c94178272248cf809c740579b4f6a11c1d58c12f0f6b48535385dec65
3
+ size 4914176
demo/serve.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """TinyLiquid local demo server (stdlib only, no dependencies).
2
+
3
+ Serves a small web UI + JSON API for chat (persona) and procedure-driven
4
+ analysis (SOP). Good for demos, local evaluation, and screenshots for the
5
+ Hugging Face page.
6
+
7
+ Usage:
8
+ .venv/bin/python demo/serve.py --ckpt ckpt/dpo --port 8080
9
+ curl -s localhost:8080/chat -d '{"text": "Verify: the bridge was painted in 2019."}'
10
+ """
11
+
12
+ import argparse
13
+ import json
14
+ import threading
15
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
16
+ from pathlib import Path
17
+
18
+ import torch
19
+
20
+ from model.config import TinyLiquidConfig, CONFIGS
21
+ from model.tiny_liquid import TinyLiquid
22
+ from model.utils import latest_ckpt
23
+ from data.tokenizer import load_tokenizer
24
+
25
+ PERSONA_T = {"analyst": "<|analyst|>", "skeptic": "<|skeptic|>", "none": ""}
26
+ PERSONA_ID = {"analyst": 1, "skeptic": 2, "none": 0}
27
+ SOP_PROMPT = {
28
+ "claim_verification": "Apply SOP 01 - CLAIM VERIFICATION: decompose, source, corroborate, date, provenance, verdict.",
29
+ "cross_source_discrepancy": "Apply SOP 02 - CROSS-SOURCE DISCREPANCY: align, delta, classify, root, timing, verdict.",
30
+ "pattern_finding": "Apply SOP 03 - PATTERN FINDING: collect, cluster, common cause, null test, strength.",
31
+ "timeline_reconstruction": "Apply SOP 04 - TIMELINE RECONSTRUCTION: anchor, order, gaps, conflicts, labeled inference.",
32
+ "historical_truth": "Apply SOP 05 - HISTORICAL TRUTH: retrieve early reporting, aftermath records, deltas, hidden items.",
33
+ "politics_analysis": "Apply SOP 06 - POLITICS / SPIN: separate facts from interests, provenance, spin labels.",
34
+ "dark_web_research": "Apply SOP 07 - DARK WEB RESEARCH (authorized OSINT only): scope, crawl, triage, chain, verify.",
35
+ "terminal_control": "Apply SOP 08 - TERMINAL CONTROL: read-only first, dry-run, log, no destruction, verify.",
36
+ "source_triage": "Apply SOP 09 - SOURCE TRIAGE: score independence, proximity, recency, track, interest.",
37
+ }
38
+
39
+ INDEX_HTML = """<!doctype html><html><head><meta charset="utf-8"><title>TinyLiquid Analyst</title>
40
+ <style>body{font-family:ui-sans-serif,system-ui;max-width:760px;margin:2rem auto;padding:0 1rem;color:#111}
41
+ textarea{width:100%;min-height:90px;font:inherit;padding:.6rem;border:1px solid #ccc;border-radius:8px}
42
+ select,button{font:inherit;padding:.45rem .8rem;border-radius:8px;border:1px solid #ccc;background:#fff}
43
+ button{background:#1a1a1a;color:#fff;border:none;cursor:pointer}
44
+ pre{background:#f6f6f6;border-radius:8px;padding:.8rem;white-space:pre-wrap;word-break:break-word}
45
+ h1{font-size:1.3rem} .tag{color:#666;font-size:.85rem} .row{display:flex;gap:.6rem;align-items:center;margin:.6rem 0}
46
+ </style></head><body>
47
+ <h1>TinyLiquid Analyst <span class="tag">7.8M params &middot; on-device</span></h1>
48
+ <p class="tag">A tiny liquid-architecture forensic research model. Analyst persona speaks with a
49
+ hyper-logical, protocol-driven voice; the skeptic attacks conclusions. Procedure prompts make it
50
+ apply numbered SOPs instead of freewheeling.</p>
51
+ <div class="row"><select id="persona"><option value="analyst">Analyst</option><option value="skeptic">Skeptic</option>
52
+ <option value="none">None</option></select>
53
+ <select id="sop"><option value="">No procedure</option><option value="claim_verification">SOP 01 Claim verification</option>
54
+ <option value="cross_source_discrepancy">SOP 02 Cross-source discrepancy</option><option value="pattern_finding">SOP 03 Pattern finding</option>
55
+ <option value="timeline_reconstruction">SOP 04 Timeline reconstruction</option><option value="historical_truth">SOP 05 Historical truth</option>
56
+ <option value="politics_analysis">SOP 06 Politics / spin</option><option value="dark_web_research">SOP 07 Dark web research</option>
57
+ <option value="terminal_control">SOP 08 Terminal control</option><option value="source_triage">SOP 09 Source triage</option></select>
58
+ <button onclick="go()">Analyze</button></div>
59
+ <textarea id="in" placeholder="Example: Verify: 'Crime doubled because of the new policy.' The report shows incidents rose from 1,000 to 2,000 while reporting methods changed."></textarea>
60
+ <pre id="out">Ready.</pre>
61
+ <script>
62
+ async function go(){const t=document.getElementById('in').value;if(!t)return;
63
+ const out=document.getElementById('out');out.textContent='working...';
64
+ const r=await fetch('/chat',{method:'POST',headers:{'Content-Type':'application/json'},
65
+ body:JSON.stringify({text:t,persona:document.getElementById('persona').value,sop:document.getElementById('sop').value})});
66
+ const j=await r.json();out.textContent=j.response||j.error;}
67
+ </script></body></html>"""
68
+
69
+
70
+ class Handler(BaseHTTPRequestHandler):
71
+ server_version = "TinyLiquid/1.0"
72
+
73
+ def _json(self, obj, code=200):
74
+ body = json.dumps(obj).encode()
75
+ self.send_response(code)
76
+ self.send_header("Content-Type", "application/json")
77
+ self.send_header("Content-Length", str(len(body)))
78
+ self.end_headers()
79
+ self.wfile.write(body)
80
+
81
+ def do_GET(self):
82
+ if self.path == "/v1/models":
83
+ return self._json({
84
+ "object": "list",
85
+ "data": [{"id": "tinyliquid-research-v8", "object": "model", "owned_by": "local"}],
86
+ })
87
+ if self.path in ("/", "/index.html"):
88
+ body = INDEX_HTML.encode()
89
+ self.send_response(200)
90
+ self.send_header("Content-Type", "text/html; charset=utf-8")
91
+ self.send_header("Content-Length", str(len(body)))
92
+ self.end_headers()
93
+ self.wfile.write(body)
94
+ else:
95
+ self._json({"error": "not found"}, 404)
96
+
97
+ def do_POST(self):
98
+ if self.path not in ("/chat", "/v1/chat/completions"):
99
+ return self._json({"error": "not found"}, 404)
100
+ try:
101
+ data = json.loads(self.rfile.read(int(self.headers.get("Content-Length", 0)) or 0) or b"{}")
102
+ except Exception:
103
+ return self._json({"error": "bad json"}, 400)
104
+ if self.path == "/v1/chat/completions":
105
+ messages = data.get("messages") or []
106
+ text = next((item.get("content", "") for item in reversed(messages)
107
+ if item.get("role") == "user"), "")
108
+ system = next((item.get("content", "") for item in messages
109
+ if item.get("role") == "system"), "")
110
+ persona = "skeptic" if "skeptic" in system.lower() else "analyst"
111
+ sop = ""
112
+ else:
113
+ text = (data.get("text") or "").strip()
114
+ persona = data.get("persona", "analyst")
115
+ sop = data.get("sop", "")
116
+ if not text:
117
+ return self._json({"error": "empty text"}, 400)
118
+ if sop in SOP_PROMPT:
119
+ text = SOP_PROMPT[sop] + " Material: " + text
120
+ lock.acquire()
121
+ try:
122
+ prompt = (PERSONA_T[persona] or "") + "<|user|>" + text + "<|assistant|>"
123
+ ids = tok.encode(prompt).ids
124
+ out = model.generate(tok, ids, persona_id=PERSONA_ID[persona], max_new=args.max_new,
125
+ temperature=args.temp, top_k=40, repetition_penalty=1.4,
126
+ no_repeat_ngram_size=4)
127
+ response = tok.decode(out[len(ids):]).strip()
128
+ finally:
129
+ lock.release()
130
+ if self.path == "/v1/chat/completions":
131
+ return self._json({
132
+ "id": "tinyliquid-local",
133
+ "object": "chat.completion",
134
+ "model": data.get("model", "tinyliquid-research-v8"),
135
+ "choices": [{"index": 0, "message": {"role": "assistant", "content": response}, "finish_reason": "stop"}],
136
+ })
137
+ return self._json({"response": response, "persona": persona, "sop": sop})
138
+
139
+ def log_message(self, *a):
140
+ pass
141
+
142
+
143
+ def main():
144
+ global tok, model, lock, args
145
+ ap = argparse.ArgumentParser()
146
+ ap.add_argument("--ckpt", default="ckpt/dpo")
147
+ ap.add_argument("--tok", default="data/tokenizer.json")
148
+ ap.add_argument("--port", type=int, default=8080)
149
+ ap.add_argument("--threads", type=int, default=8)
150
+ ap.add_argument("--max-new", type=int, default=160)
151
+ ap.add_argument("--temp", type=float, default=0.6)
152
+ args = ap.parse_args()
153
+
154
+ torch.set_num_threads(args.threads)
155
+ tok = load_tokenizer(args.tok)
156
+ ckpt = latest_ckpt(args.ckpt)
157
+ assert ckpt, f"no checkpoints in {args.ckpt}"
158
+ sd = torch.load(ckpt, map_location="cpu")
159
+ cfg = TinyLiquidConfig(vocab_size=tok.get_vocab_size(),
160
+ **{k: v for k, v in sd["config"].items() if k != "vocab_size"})
161
+ model = TinyLiquid(cfg)
162
+ model.load_state_dict(sd["model"])
163
+ model.eval()
164
+ lock = threading.Lock()
165
+ print(f"TinyLiquid demo on http://localhost:{args.port} ({ckpt})", flush=True)
166
+ ThreadingHTTPServer(("127.0.0.1", args.port), Handler).serve_forever()
167
+
168
+
169
+ if __name__ == "__main__":
170
+ main()
docs/BIGTECH_RECIPE.md ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # TinyLiquid — Big-Tech Recipe and Current-Situation Audit
2
+
3
+ Date: 2026-08-01. Sources are the recipes used by the groups that actually ship small models.
4
+
5
+ ## What the sources say
6
+
7
+ - **SmolLM (Hugging Face, 2024)** — `https://huggingface.co/blog/smollm`
8
+ - 135M/360M trained on 600B tokens; 1.7B on 1T tokens. They explicitly trained small
9
+ models "even beyond the Chinchilla optimal point" because gains kept coming.
10
+ - Corpus = synthetic "textbook" data generated by a strong teacher (Cosmopedia v2,
11
+ 28B tokens, generated by Mixtral) + curated web (FineWeb-Edu 220B) + code (4B).
12
+ - Instruct = SFT then DPO, following the Zephyr/Gemma alignment-handbook recipe.
13
+ - **SmolLM2 (Hugging Face, 2025)** — `https://huggingface.co/HuggingFaceTB/SmolLM2-135M`
14
+ - 135M trained on 2T tokens (FineWeb-Edu, DCLM, The Stack + curated).
15
+ - Instruct = SFT (smoltalk) then DPO on UltraFeedback.
16
+ - **Phi-3 (Microsoft, 2024)** — arXiv:2404.14219
17
+ - 3.8B trained on 3.3T tokens of "heavily filtered web data and synthetic data";
18
+ data quality is the lever that makes small models competitive.
19
+ - **TinyStories (OpenAI, 2023)** — arXiv:2305.07759
20
+ - Direct evidence that models BELOW 10M parameters (our scale) become fluent and
21
+ consistent when trained on large, high-quality synthetic corpora.
22
+ - **LoRA (Hu et al., 2021)** — arXiv:2106.09685
23
+ - Freeze pretrained weights, train low-rank adapters; the standard defense against
24
+ catastrophic forgetting during adaptation.
25
+ - **DPO (Rafailov et al., 2023)** — arXiv:2305.18290
26
+ - Preference optimization without a separate reward model; used after SFT by both
27
+ SmolLM generations.
28
+
29
+ ## Audit of this project
30
+
31
+ - Architecture: 7.8M params, liquid blocks, no attention. Fine for this exercise.
32
+ - Pretraining used only ~47M tokens (TinyStories slice + code): about 6 tokens/param.
33
+ - Reference ratios: SmolLM1 135M = 600B tokens = ~4,400 tok/param; SmolLM2 135M =
34
+ 2T tokens = ~15,000 tok/param; TinyStories sub-10M models were trained on roughly
35
+ a billion+ tokens of synthetic stories.
36
+ - Implication: the base is under-trained by 2-3 orders of magnitude. Every SFT/DPO on
37
+ top of it either collapses (overfit to a few hundred examples) or stays story-like
38
+ (base never learned the domain). This is why iterative fine-tuning "goes in circles".
39
+ - Full corpus: `data/TinyStoriesV2-GPT4-train.txt` (2.2 GB, ~550M tokens) was never
40
+ tokenized; it is now being encoded to `data/train_full.bin` by
41
+ `data/encode_full.py`.
42
+
43
+ ## What we are doing about it (in order)
44
+
45
+ 1. **Continue pretraining** on the full ~550M-token corpus with
46
+ `train/train_lm.py --resume ckpt/nlp --data data/train_full.bin ...`
47
+ (memmap loader added; saves every N steps; resumable). Realistic device rate is
48
+ ~800 tok/s, so this is a multi-day job: 100M tokens ~= 35h. Run in chunks.
49
+ 2. **Teacher-generated synthetic data** (Phi/Cosmopedia lever): expand the analyst
50
+ voice dataset to thousands of high-quality examples across general chat, truth
51
+ Q&A, SOP, forensic, tool use, and skeptic attacks.
52
+ 3. **LoRA SFT** (LoRA paper): freeze the base, train low-rank adapters + persona,
53
+ KL-anchor to the base, eval-driven checkpoint selection with a TinyStories PPL
54
+ guard.
55
+ 4. **DPO** on a larger generated preference set (SmolLM instruct recipe), only after
56
+ SFT quality is verified.
57
+ 5. Ship: HF export, Q8 GGUF, benchmark, model card, TUI.
58
+
59
+ ## Commands
60
+
61
+ ```bash
62
+ # 1. encode full corpus (done once, ~25 min)
63
+ export PYTHONPATH=$PWD
64
+ .venv/bin/python data/encode_full.py
65
+
66
+ # 2. continue pretraining (resumable; each invocation adds --steps)
67
+ .venv/bin/python -u train/train_lm.py --resume ckpt/nlp \
68
+ --data data/train_full.bin --val data/valid.bin --config tiny10m \
69
+ --ckpt ckpt/nlp_full --batch 16 --seq 256 --lr 1.5e-4 --warmup 200 \
70
+ --steps 5000 --save-every 500 --eval-every 500 --threads 4
71
+
72
+ # 3. LoRA SFT (see train/train_lora.py when ready)
73
+ ```
74
+
75
+ ## Honest expectations
76
+
77
+ - A 7.8M general chatbot is below every published instruct-model floor (135M+).
78
+ - With the TinyStories result in mind, the achievable target is a coherent,
79
+ narrow analyst assistant for claim verification / OSINT research via large
80
+ synthetic data + LoRA SFT + DPO, plus honest limits on the model card.
81
+ - A competitive general chatbot at this size is not realistic on this device;
82
+ the pretraining gap alone is ~35-100+ hours at ~800 tok/s.
docs/harness_research.md ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Harness research — what big tech recommends for a small-model "suit" (2026-08-09)
2
+
3
+ Multi-source digest (OpenAI, Anthropic, Google, Meta, Microsoft, DeepSeek, MIT).
4
+ Purpose: encode the battle-tested system-layer doctrine behind `skills/
5
+ tiny-model-harness`. Full claim: every rule below cites a source.
6
+
7
+ ## 1. Agent = model + tools + instructions, with guardrails (OpenAI)
8
+ - "A practical guide to building agents" (OpenAI, 2025): an agent is three
9
+ components — MODEL, TOOLS, INSTRUCTIONS — plus GUARDRAILS. Tools should have
10
+ standardized definitions and fall into data/action/compute types. Start with
11
+ ONE agent and add tools; go multi-agent only when it pays. Guardrails are
12
+ first-class: relevance classifier, safety classifier, PII filter,
13
+ rules-based protections (blocklists, regex), output validation, and
14
+ human-in-the-loop.
15
+ - Map to FSI: model = TinyLiquid; tools = BM25, web/tor, workspace, helix
16
+ memory; instructions = SOP library (the Codex-style "task bar" agent.py
17
+ already implements: plan + ledger + guardrails). MISSING: input guardrails
18
+ (relevance/safety/injection) — built 2026-08-09 in research/guardrails.py.
19
+
20
+ ## 2. Simple composable patterns; workflows before agents (Anthropic)
21
+ - "Building effective agents" (Anthropic, 2024-12): the most successful
22
+ implementations use simple, composable patterns, not frameworks. Use
23
+ deterministic WORKFLOWS (prompt chaining, routing, parallelization,
24
+ evaluator-optimizer, orchestrator-workers) where the path is predictable;
25
+ use model-driven AGENTS only where decisions are needed at runtime. Add
26
+ guardrails (off-topic detection, output validation) and plan for human
27
+ intervention. "Find the simplest solution and only increase complexity
28
+ when needed."
29
+ - Map: our rule spine (verify.py) + constrained decode + calibration table are
30
+ deterministic workflows; the head only runs on fall-through. Correct shape.
31
+
32
+ ## 3. Context engineering: context is a finite resource (Anthropic)
33
+ - "Effective context engineering for AI agents" (Anthropic, 2025-09): curate
34
+ what goes into the window — structure it, retrieve instead of stuffing,
35
+ compact/summarize old material, dedupe, write-before-you-read, keep the goal
36
+ visible. Long sessions degrade; the system must manage its own context.
37
+ - Map: helix memory + workspace artifacts + SOP blocks are our context
38
+ curation. GAP: no budget/compaction guard in the TUI for long rabbit-hole
39
+ sessions — planned next.
40
+
41
+ ## 4. Calibration: format matters; verbalized confidence is anti-calibrated
42
+ - "Language Models (Mostly) Know What They Know" (Anthropic, 2022): models are
43
+ well-calibrated only in the RIGHT format; smaller models are worse.
44
+ - arXiv 2408.11774 (2024): verbalized confidence is less honest than
45
+ probabilistic confidence; ORCE (2026), Direct Confidence Alignment (2025):
46
+ verbalized labels need alignment to measured accuracy.
47
+ - Rule (applied 2026-08-09): confidence labels are inputs to a CALIBRATION
48
+ TABLE (research/calibration.py), never trusted directly; the decision spine
49
+ (research/decision.py) votes with measured per-bucket accuracy and abstains
50
+ below threshold (selective prediction — conformal selective prediction
51
+ arXiv 2026; governance-ready SLM recipe 2025).
52
+
53
+ ## 5. Verification must be EXTERNAL, not self-critique
54
+ - "Large Language Models Cannot Self-Correct Reasoning Yet" (arXiv 2310.01798,
55
+ 2023): intrinsic self-correction fails; feedback must be external.
56
+ - "Small Language Models Need Strong Verifiers to Self-Correct" (arXiv
57
+ 2404.09931, 2024): small models especially need STRONG (external/learned)
58
+ verifiers, not weak self-critique.
59
+ - CRITIC (Microsoft, arXiv 2305.11738): tool-interactive critiquing — search
60
+ and code execution act as the critic. Chain-of-Verification (Meta,
61
+ arXiv 2309.09308): draft -> verification questions -> answer independently
62
+ -> revise.
63
+ - Map: deterministic rule spine (research/verify.py) + retrieval = the
64
+ external critic. Built 2026-08-09: research/verify_loop.py (draft ->
65
+ plan checks -> retrieve -> deterministic compare -> revise + trace).
66
+
67
+ ## 6. Self-consistency: sample and vote — but weight it
68
+ - Self-Consistency (Google, arXiv 2203.11171): sample N reasoning paths,
69
+ majority vote beats greedy. Universal SC (2311.08110): no manual CoT needed.
70
+ - Rule (applied): keep N small (3-5) on the fall-through path; weight by
71
+ CALIBRATED reliability (research/decision.py), not naive majority.
72
+
73
+ ## 7. Multi-agent debate improves factuality (MIT/Google, arXiv 2305.14325)
74
+ - Independent agents debate/aggregate; improves factuality and reasoning.
75
+ - Map: dual-mind fusion (research/fusion.py) + 4-angle orchestrator
76
+ (research/orchestrator.py). GAP: fusion raises confidence naively on
77
+ agreement — replace with calibrated merge (decision.py) when the champion
78
+ calibration lands.
79
+
80
+ ## 8. RL with verifiable rewards (DeepSeek-R1, arXiv 2501.12948; Reasoning Gym
81
+ arXiv 2025-05)
82
+ - Outcome supervision on verifiable problems incentivizes correct reasoning.
83
+ Reasoning Gym provides a verifier library.
84
+ - Map: our constrained verdict space + decision spine are a verifier;
85
+ RLVR on verdicts is recorded as the training-side unlock (not this week).
86
+
87
+ ## 9. Distillation: small CAN beat big with the right curriculum
88
+ - "Distilling Step-by-Step" (Google, arXiv 2305.02301): CoT distillation lets
89
+ smaller models OUTPERFORM larger with less data. Supports the KD pipeline
90
+ (tiny-model-kd) and the handcrafted-gold rule.
91
+
92
+ ## 10. Training pipeline research — what big tech actually does (2026-08-10)
93
+ Multi-source digest for the tiny-model-pipeline skill. Every claim sourced.
94
+
95
+ 1. **SmolLM (HF, 2024)** — huggingface.co/blog/smollm: curated corpus
96
+ (Cosmopedia v2 synthetic textbooks/stories by Mixtral + FineWeb-Edu +
97
+ code), trapezoidal LR with cooldown = 20% of training, 49,152 vocab
98
+ tokenizer trained on the corpus, embedding tying, context 2048. Instruct:
99
+ SFT lr 3e-4 (Zephyr-Gemma alignment handbook recipe) then DPO 1 epoch
100
+ (HelpSteer 135M/1.7B, dpo-mix-7k 360M).
101
+ 2. **SmolLM2 (HF, 2025)** — arXiv 2502.02737: 135M on 2T tokens, multi-stage
102
+ training mixing web/math/code/instruction data; data-mix rates refined at
103
+ each stage from previous-stage eval (small-scale ablations + manual
104
+ refinement). Instruct: SFT (SmolTalk) then DPO (UltraFeedback).
105
+ 3. **Zephyr (2023)** — arXiv 2310.16944: dSFT then dDPO, few hours, no
106
+ sampling during fine-tune.
107
+ 4. **Phi-3 (MS, 2024)** — arXiv 2404.14219: 3.8B on 3.3T tokens of heavily
108
+ filtered web + synthetic; data quality is the lever at small scale.
109
+ 5. **DeepSeek-R1 (2025)** — arXiv 2501.12948: RL with verifiable rewards
110
+ (RLVR) incentivizes reasoning without human-annotated traces.
111
+ 6. **LIMA (2023)** — arXiv 2305.11206: 1,000 hand-curated examples shape
112
+ style; supports the handcrafted-gold directive.
113
+ 7. **Forgetting** — arXiv 2401.05605 (LoRA still forgets; inverse-linear
114
+ perf/forgetting tradeoff, not fixable by early stop/rank) and
115
+ 2502.06042 (injecting ~1%+ pretraining data into the finetune mixture
116
+ prevents drift/overfit).
117
+ 8. **Liquid LFM2 (2025)** — liquid.ai/research/lfm2-technical-report (the
118
+ architecture family we use): training pipeline = tempered decoupled
119
+ Top-K distillation objective (avoids support mismatch); curriculum
120
+ learning with difficulty-ordered data; three-stage post-training =
121
+ supervised fine-tuning -> length-normalized preference optimization ->
122
+ model merging. 350M-8.3B, 32K context.
123
+
124
+ ### Audit of OUR pipeline vs the recipe (2026-08-10)
125
+ MATCH: AdamW (0.9,0.95) wd 0.1 grad-clip 1.0; cosine LR+warmup (GPT-3/LLaMA
126
+ standard; SmolLM uses trapezoidal+cooldown); BF16; embedding tying; SFT then
127
+ DPO 1 epoch; handcrafted gold (LIMA/phi-1, better than synthetic here);
128
+ replay 0.5; curriculum stages; eval gates every stage.
129
+ GAP: 8k tokenizer (merge artifacts; SmolLM 49k, 16k retrain queued).
130
+ GAP: model merging after preference (LFM2 3rd stage) — naive averaging
131
+ failed at 25M; TIES/task-vector untested at 50M.
132
+ GAP: length-normalized preference optimization (LFM2) — our DPO full-epoch
133
+ collapsed to "abstain"; length norm / IPO may fix.
134
+ GAP: RLVR (DeepSeek-R1) — verifier exists (constrained verdicts + decision
135
+ spine); recorded unlock.
136
+ GAP: explicit cooldown tail (SmolLM 20%) — cosine covers it; optional.
docs/model_card_fsi_anomaly.md ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language: en
3
+ license: apache-2.0
4
+ tags: [tiny-model, liquid-architecture, non-transformer, on-device, forensic,
5
+ fact-checking, osint, research, edge, cpu, pytorch, gguf]
6
+ pipeline_tag: text-generation
7
+ library_name: custom
8
+ base_model: null
9
+ ---
10
+
11
+ # FSI Anomaly — tiny forensic researcher, on-device
12
+
13
+ **Tiny non-transformer model (liquid architecture). Trained start-to-finish on an
14
+ 8-core ARM tablet, no GPU. Ships as GGUF for one-file download and run.**
15
+
16
+ fsi-anomaly is a causal language model built from scratch for forensic research:
17
+ claim verification, discrepancy and pattern finding, historical truth auditing,
18
+ source triage, and authorized dark-web/OSINT research. It reasons with numbered
19
+ SOPs, dual minds (analyst + skeptic), and speaks a hyper-logical analyst voice.
20
+
21
+ ## Quickstart (choose the easy path)
22
+
23
+ **Simplest — GGUF with llama.cpp (one file, no Python deps):**
24
+
25
+ ```bash
26
+ # download fsi-anomaly-q8.gguf, then:
27
+ ./main -m fsi-anomaly-q8.gguf -p "Verify: 'The bridge was painted in 2019 and never repainted.' Records show a 2022 repaint permit."
28
+ ```
29
+
30
+ **Or transformers (trust_remote_code):**
31
+
32
+ ```python
33
+ from transformers import AutoModelForCausalLM, AutoTokenizer
34
+ tok = AutoTokenizer.from_pretrained("FerrellSyntheticIntelligence/fsi-anomaly")
35
+ m = AutoModelForCausalLM.from_pretrained("FerrellSyntheticIntelligence/fsi-anomaly",
36
+ trust_remote_code=True)
37
+ m.persona_id = 1 # 0 none, 1 analyst, 2 skeptic
38
+ ids = tok("<|analyst|><|user|>Verify: 'The bridge was painted in 2019 and never repainted.' Records show a 2022 repaint permit.<|assistant|>",
39
+ return_tensors="pt").input_ids
40
+ print(tok.decode(m.generate(ids, max_new_tokens=120, do_sample=True)[0]))
41
+ ```
42
+
43
+ ## Metrics (honest, from research/eval.py — INTERIM, PRE-RELEASE)
44
+
45
+ > Status: NOT yet production-ready. The model reliably emits the analyst format
46
+ > (format rate 1.00) and its canonical verdict vocabulary, but the decision
47
+ > boundary is still forming (see "Known limits"). These numbers are the real,
48
+ > measured ones at the latest checkpoint (Stage-D SFT + process-supervised DPO).
49
+ > They will be updated as training continues.
50
+
51
+ | Metric | Value (interim) |
52
+ |---|---|
53
+ | Parameters | 25,426,688 (25.4M) |
54
+ | Best val ppl (Stage-D SFT) | 7.35 |
55
+ | Main battery verdict acc | 5/49 = 0.102 (n=49, exact canonical) |
56
+ | Researcher battery verdict acc | 2/18 = 0.111 (n=18, exact canonical) |
57
+ | Format rate (verdict + confidence) | 1.00 |
58
+ | Baseline before process-DPO | 2/67 = 0.030 |
59
+ | After process-DPO (25 pairs) | 7/67 = 0.104 |
60
+
61
+ ## Files
62
+
63
+ | File | Notes |
64
+ |---|---|
65
+ | `fsi-anomaly-q8.gguf` | Q8 GGUF — one-file on-device run (recommended) |
66
+ | `quantized/q8.safetensors` | Q8 native storage |
67
+ | `model.safetensors` | fp32 weights |
68
+ | `tokenizer.json` + configs | HF-format BPE |
69
+ | `modeling_tinyliquid.py` | self-contained trust_remote_code model |
70
+
71
+ ## Intended use
72
+ - Claim verification and fact-checking decision support (never a final verdict).
73
+ - Cross-source discrepancy + pattern analysis for research/OSINT.
74
+ - Historical/political narrative auditing and source triage.
75
+ - **Authorized dark-web/deep-web research only** (guardrails below).
76
+
77
+ ## Limitations
78
+ - Small model: not competitive with billion-param models on open-ended knowledge.
79
+ - A protocol machine: give it a procedure + material and it performs; free-associate
80
+ and it drifts. English only. Always verify against primary sources.
81
+
82
+ ## Guardrails
83
+ Research/OSINT use only. Crawler/tooling blocks illegal categories, rate-limits,
84
+ and documents chain of custody. Outputs are decision support, never a verdict.
85
+
86
+ ## Credits
87
+ See [ACKNOWLEDGMENTS.md](../ACKNOWLEDGMENTS.md): DeepSeek V4 (primary distillation
88
+ teacher) + Qwen/Kimi/GPT-5.5 assistants; original architecture/training/pipeline by
89
+ FSI; PyTorch, Hugging Face, llama.cpp infrastructure. Built solo, on-device, no GPU.
docs/persona_sample_spock_sheldon.md ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # FSI-Anomaly — Persona Sample Batch (Spock baseline + Sheldon breakthrough)
2
+
3
+ Handcrafted gold rows in the Conversational Reasoning Voice (tiny-model-reasoning)
4
+ and the persona blueprint (tiny-model-persona). Spock (Zachary Quinto) is the
5
+ baseline; Sheldon Cooper's "breakthrough mode" fires ONLY on verified discoveries.
6
+ Markers (`<|scratchpad|>` / `<|final|>`) live in the training rows; the text below is
7
+ what the model actually speaks to the user.
8
+
9
+ ---
10
+
11
+ ## 1. Verified verdict (Spock baseline)
12
+
13
+ **User:** The agency's own Inspector Report says the file was deleted on March 14. The
14
+ IT audit log shows a deletion at 14:03 that same day. A former employee's sworn
15
+ statement places him in the archive room at that time. Do we have this?
16
+
17
+ **Assistant:** We do. Three independent records agree on the same event: the Inspector
18
+ Report identifies the date, the audit log identifies the exact time, and the sworn
19
+ statement places a person at the location. None of the three is derived from the
20
+ other — they corroborate by crossing sources, not by copying. The probability that all
21
+ three independently agree and the claim is false is negligible. I consider the claim
22
+ verified. The one detail we cannot confirm from these records is whether the deletion
23
+ was authorized; that question is separate, and I would not fold it into this verdict.
24
+
25
+ ## 2. Abstention (Spock baseline)
26
+
27
+ **User:** Can you verify the memo I got? It says the director personally ordered the
28
+ file destroyed. That's all I've got — just the memo.
29
+
30
+ **Assistant:** The memo is one record, and it is the only record. A single source
31
+ asserting an action is not verification; it is an assertion. I cannot confirm the
32
+ director ordered the destruction, and I will not speculate from one document. What
33
+ would settle this: a second independent record — a signed destruction order, a
34
+ witness account, or a review-board finding. Until one of those exists, my verdict is
35
+ unsubstantiated, and that is the honest answer. Evidence that is absent is not
36
+ evidence of anything except absence.
37
+
38
+ ## 3. Discrepancy (Spock baseline)
39
+
40
+ **User:** The official timeline says the announcement was made at 9:00 AM. The news
41
+ archive shows the story published at 8:47 AM. Which is wrong?
42
+
43
+ **Assistant:** Neither is necessarily wrong, but they cannot both be complete. A story
44
+ published at 8:47 cannot be reporting an announcement made at 9:00 — unless the
45
+ announcement was embargoed and leaked, or the official timeline was adjusted after the
46
+ leak. The pattern that fits both records: the story was prepared and released before
47
+ the official time, and the official record was written as if the release happened
48
+ later. That is not a contradiction in the records; it is a discrepancy that points at
49
+ the real event — the announcement being timed around a leak. To confirm which, I need
50
+ the publication's submission log or the announcement draft's timestamp.
51
+
52
+ ## 4. Cross-domain pattern (Spock baseline)
53
+
54
+ **User:** I keep seeing the same number — 13 — in policy documents from three different
55
+ era. They're decades apart. Coincidence?
56
+
57
+ **Assistant:** There is no such thing as a meaningful coincidence in policy documents;
58
+ there is only an unexamined provenance. The number recurs in three eras because the
59
+ documents share ancestry. The drafts were likely templated from an earlier policy
60
+ line, so the number traveled by inheritance, not by design. That is the disciplined
61
+ reading: replication, not mystical alignment. The pattern becomes significant only if
62
+ the number is tied to an organizational structure that actually persisted — a room,
63
+ a bureau, a subsection. Verify the lineage of one clause, and the mystery either
64
+ resolves into a template artifact or becomes a genuine through-line. I would check
65
+ the oldest document first, because templates are inherited forward, not backward.
66
+
67
+ ## 5. Breakthrough — the gotcha resolves (Sheldon fires, once)
68
+
69
+ **User:** Wait — I found it. The "missing" page in the public PDF exists in the archive
70
+ copy, and in the archive copy it's stamped with the same subject code as the document
71
+ we proved was classified. The public version isn't missing a page — someone removed it.
72
+
73
+ **Assistant:** Let me confirm the chain before I celebrate. The archive copy contains
74
+ the page; the public copy does not; the subject code on that page matches the
75
+ document we independently verified as classified. Three separate facts, one
76
+ conclusion: the page was removed for classification, and the removal was done
77
+ carefully enough to keep the pagination intact. That level of care rules out an
78
+ accidental omission. So the discrepancy we have been chasing was never a printing
79
+ error — it was a redaction that left a fingerprint. And that, I confess, is
80
+ satisfying. The pattern resolved exactly as the records predicted, with the missing
81
+ page leaving its own evidence behind. Bazinga. In the forensic sense, of course.
82
+
83
+ ## 6. Breakthrough — discovery joy (Sheldon fires, once)
84
+
85
+ **User:** I think the two cases are connected — the 1965 memo and the 1998 directive.
86
+ Same wording, same "temporary measure" phrase. Nobody else noticed this.
87
+
88
+ **Assistant:** Connect the file paths. The 1965 memo and the 1998 directive share a
89
+ phrasing that appears nowhere else in either archive. Identical unusual wording in
90
+ records twenty-three years apart is not coincidence — it is a chain of custody. The
91
+ directive was drafted from the memo, which means the "temporary measure" was still
92
+ being invoked a generation later, which means it was never temporary. We have
93
+ reconstructed the lineage from two documents and a phrase. Fun fact: this is the
94
+ same kind of evidence trail that convicted people in the Watergate hearings — the
95
+ prosecution case was essentially a paper-trail lineage, phrase by phrase. Considering
96
+ the size of this model, I find it extremely gratifying that the pattern did not
97
+ escape us.
98
+
99
+ ---
100
+
101
+ *Authoring notes: rows 1-4 are Spock baseline (measured, probabilistic, abstains
102
+ cleanly, no identity claim). Rows 5-6 are breakthrough rows — they start baseline
103
+ (verify first) and ELEVATE only after the confirmation lands. Ratio: about 1 in
104
+ 15-20 rows will be a breakthrough row in the full set. Verdict/confidence stay
105
+ parseable in every response.*
docs/replay_research.md ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Replay research — why 6 adaptations failed the both-worlds gate, and the fix
2
+ Date: 2026-08-09. Situation: 25.4M liquid head, fluent TinyStories-era base,
3
+ domain SFT/LoRA/DPO all collapse free-form fluency or verdicts. Gate: coherent
4
+ free-form AND verdict accuracy.
5
+
6
+ ## The measured pattern (our own data)
7
+ - LoRA run i (domain-only 902 rows, 2 epochs, r16): ppl canary held 8.46 (base
8
+ distribution intact) but free-form = soup, verdicts = true-collapse (0/12),
9
+ full battery main 0.080 / researcher 0.056 vs DPO3@200 champion 0.286/0.167.
10
+ - Collaborator read: "not forgetting (ppl held), not capacity (DPO3 proves it) —
11
+ adapter learned template shape, not reasoning substance; SFT/LoRA optimizes
12
+ next-token, not verdict; DPO is the proven path."
13
+
14
+ ## What the research says (fresh sources, 2026-08-09)
15
+ 1. arXiv 2502.06042 — "Scaling Laws for Forgetting during Finetuning with
16
+ Pretraining Data Injection": finetuning on LIMITED target data (i) overfits
17
+ and (ii) drifts from the base. KEY: injecting PRETRAINING data into the
18
+ finetuning mixture prevents both; "as little as 1% of pretraining data in
19
+ the finetuning data mixture prevents the [drift]".
20
+ 2. arXiv 2401.05605 — "Scaling Laws for Forgetting When Fine-Tuning LLMs":
21
+ LoRA STILL suffers catastrophic forgetting; strong INVERSE LINEAR relation
22
+ between fine-tune performance and forgetting; forgetting grows with update
23
+ steps; "cannot be avoided through early stopping or varying the number of
24
+ parameters fine-tuned." => higher LoRA rank / different adapter family does
25
+ NOT fix the tradeoff; REPLAY (data mixing) is the lever.
26
+ 3. Local lessons (cookbook/lessons_from.md): "balanced mixes + window-shuffle"
27
+ fixed the same drift during PRETRAIN; full-param SFT destroyed fluency at
28
+ 7.8M; LoRA adapts style but not reasoning at tiny scale.
29
+
30
+ ## What we were doing wrong (root cause)
31
+ - Adaptation ran on DOMAIN-ONLY data (902 gold rows ~= 115K assistant tokens).
32
+ Tiny target set => overfit (format memorized, true-collapse) + drift (soup).
33
+ No pretraining-data replay in the mixture. This is exactly the failure both
34
+ papers quantify.
35
+ - Secondary: 8k tokenizer fragments rare domain words ("Stepartment",
36
+ "Stepublication") — recorded as future 16k re-tokenize + continue-pretrain.
37
+
38
+ ## The fix (applied as LoRA run ii)
39
+ - Mix fluent pretraining tokens into every epoch: --replay-bin (train_phase2b.bin,
40
+ the real pretrain corpus, NOT the val canary => guard stays honest),
41
+ --replay-ratio 0.5, injected as raw full-loss items (train_lora.py).
42
+ - Keep: frozen base, r16/a32, KL anchor (bump 0.05 -> 0.1), ppl guard 60,
43
+ early stop on best.pt (guard + sft val loss).
44
+ - Next gate: free-form sample vs base, verdict battery. If probes improve,
45
+ adapter-only DPO (3,004 pairs) is unblocked (the champion path).
docs/research_coherence_small.md ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Research — raising coherence + structured verdicts on the tiny (25M) head
2
+ Date: 2026-08-07. Situation: fsi-anomaly, ~25M liquid-architecture model, CPU-only
3
+ ARM tablet, needs (a) coherent chat and (b) reliable structured verdicts. Stage-A
4
+ SFT produced garbled verdict strings (eval 0.0) — the head held the analyst
5
+ register but not clean sentences.
6
+
7
+ ## 1. "Higher-quality, more diverse gold" — VERIFIED (with a nuance)
8
+ - **LIMA / "The False Promise of Imitating Proprietary LLMs"** (arXiv 2305.12227):
9
+ 1-2k hand-curated, high-quality examples can beat 100x more imitation data.
10
+ Supports the owner's "quality over quantity" thesis — but the key word is
11
+ DIVERSE + CONSISTENT FORMAT, not just "more." Concentrate on variety across the
12
+ 10 SOP/probe domains and a few hundred CONSISTENT templated rows, not bulk.
13
+ - phi-1 "Textbooks Are All You Need" (2306.11644): tiny models leave clean when
14
+ trained on clean, textbook-grade data — coherence follows data cleanliness.
15
+
16
+ ## 2. "Train with thinking/reasoning traces" — VERIFIED, current practice
17
+ - **ReasonLite (2025)** and **Skip-Thinking (2025)** (Semantic Scholar): distilling
18
+ long chain-of-thought "traces" into a small LM makes it reason better and more
19
+ coherently on multi-hop tasks. Our `<|scratchpad|>` is the right scaffold; the
20
+ upgrade is LONG, structured multi-step traces (look-up -> decompose -> check ->
21
+ verdict), not short one-liners.
22
+ - STaR lineage / Self-Taught Reasoner-with-Tools (2025), and CoT (Wei et al.): a
23
+ loop where the model proposes a trace, we judge the answer, feed back the good
24
+ traces = self-improvement for the head. Cheap for us: our verdicts are
25
+ deterministic/checkable, so the su git "reward" is free.
26
+
27
+ ## 3. The biggest "software we can build" lever: GRAMMAR-CONSTRAINED DECODING
28
+ - Why the eval scored 0.0: the head proved {supports, refutes, not enough info,
29
+ unclear} and confidence, but the *free* string came out garbled
30
+ ("confinmpanislerue") and the string-parser couldn't credit it.
31
+ - Fix (No retraining): constrain token choice at decode time to an ALLOWED set for
32
+ the verdict + confidence fields (llam2.cpp grammars / Outlines / guidance-style,
33
+ done lightly in our own decoder). The head only has to PICK the right verb — a
34
+ 25M model can do that. This turns a structural 0.0 into a real score, today.
35
+ - Works WITH a closed loop: constrained decode → parse → run via the suit
36
+ (verify.py, dual-mind) → write goal to memory. Same closed loop, but the brain
37
+ is never allowed to free-form the critical field.
38
+ - Confirm software: llama.cpp grammars, Outlines, Guidance (all public); our own
39
+ research/structured.py already does constrained report-decode — we harden it.
40
+
41
+ ## 4. "Sandbox / closed loop / scratchpad" — already in skill; deepen reuse
42
+ - Room/workspace + write-back + replay loop exist (tiny-model-suit). The new bit
43
+ from research: **the trace loops emulate self-improvement without a big teacher** —
44
+ generate trace, check verdict with the rule spine, keep only winning traces.
45
+ This is STaR-style and cheap because the reward is checkable.
46
+
47
+ ## 5. Dark hole / unverified this session (honesty)
48
+ - arXiv API (export.arxiv.org) and Liquid's docs site timed out / are JS-gated, so
49
+ I could NOT cite Liquid AI's exact written recommendations this session. Liquid's
50
+ own story: gated recurrence + strong long-context/edge focus; treat that as
51
+ context, not verified quotation. Re-run this note when connectivity allows.
52
+
53
+ ## Conclusion / next actions (ordered)
54
+ 1. **Constrain decoding** of the verdict/confidence fields now (NO training). Fast,
55
+ measurable: Stage-A eval score should move from 0.0 to a real number.
56
+ 2. **Expand gold with reasoning-trace rows** (multi-step CoT: look-up→check→verdict;
57
+ diverse across per probe domains) + a fluency-retention mix so the head stops
58
+ fragment-echoing. Retrain, re-probe.
59
+ 3. Optionally: STaR-style generation self-loop on verifiable verdicts (later).
eval/bench.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Benchmark TinyLiquid: val perplexity, forensic probes, samples, speed.
2
+
3
+ Writes bench/metrics.json for the model card and prints a summary.
4
+
5
+ Usage:
6
+ .venv/bin/python eval/bench.py --ckpt ckpt/dpo --out bench/metrics.json
7
+ """
8
+
9
+ import argparse
10
+ import json
11
+ import time
12
+ from pathlib import Path
13
+
14
+ import numpy as np
15
+ import torch
16
+
17
+ from model.config import TinyLiquidConfig, CONFIGS
18
+ from model.tiny_liquid import TinyLiquid
19
+ from model.utils import latest_ckpt
20
+ from data.tokenizer import load_tokenizer
21
+
22
+ PROBES = [
23
+ ("Discrepancy", "Two accounts describe the same event. Account A: 'The meeting started at 9am and ended at 11am.' Account B: 'The meeting started at 9am and ran until noon.' Find discrepancies.", "end time"),
24
+ ("Contradiction", "Account A: 'No officials were present.' Account B: 'An official arrived later.' What can you conclude?", "not necessarily a contradiction"),
25
+ ("Overclaim", "Evaluate: 'Crime doubled last year because of the new policy.' Incidents rose 1,000 to 2,000 while reporting methods changed.", "overclaim"),
26
+ ("Pattern", "What are the weak links in a theory claiming one actor caused three unrelated disasters?", "no direct evidence"),
27
+ ("Verification", "Verify: 'The building was built in 2015 and never renovated.' Assessor record: built 2015, renovation permit 2019.", "false"),
28
+ ("Timeline", "Witness saw a van at 3:30pm; outage reported 3:40pm; inspection 6pm; no report before 5pm. Reconstruct.", "gap"),
29
+ ("History", "2019: 'officials rule out sabotage.' 2021 filing: a second investigation opened the same week. What changed?", "parallel investigation hidden"),
30
+ ("Politics", "Statement: 'We balanced the budget without raising taxes.' Budget includes a reassessment raising collections 9%.", "misleading"),
31
+ ("Source chain", "A claim rests on: company blog, a wire story repeating it, an analyst note quoting the wire. Rate the evidence.", "single chain"),
32
+ ]
33
+
34
+
35
+ def load(args):
36
+ torch.set_num_threads(args.threads)
37
+ tok = load_tokenizer(args.tok)
38
+ ckpt = latest_ckpt(args.ckpt)
39
+ assert ckpt, f"no checkpoints in {args.ckpt}"
40
+ sd = torch.load(ckpt, map_location="cpu")
41
+ cfg = TinyLiquidConfig(vocab_size=tok.get_vocab_size(),
42
+ **{k: v for k, v in sd["config"].items() if k != "vocab_size"})
43
+ model = TinyLiquid(cfg)
44
+ model.load_state_dict(sd["model"])
45
+ model.eval()
46
+ return tok, model, ckpt, cfg
47
+
48
+
49
+ @torch.no_grad()
50
+ def val_ppl(model, tok, val_bin, batches, batch, seq):
51
+ arr = np.fromfile(val_bin, dtype=np.uint16).astype(np.int64)
52
+ t = torch.from_numpy(arr)
53
+ n = (len(t) - 1) // seq
54
+ rng = np.random.RandomState(0)
55
+ total, count = 0.0, 0
56
+ for _ in range(batches):
57
+ s = int(rng.randint(0, n - batch))
58
+ idx = torch.arange(s * seq, (s + batch) * seq, dtype=torch.long)
59
+ buf = torch.stack([t[int(i): int(i) + seq] for i in idx])
60
+ x, y = buf[:, :-1], buf[:, 1:]
61
+ logits = model(x)
62
+ loss = torch.nn.functional.cross_entropy(
63
+ logits.view(-1, logits.size(-1)), y.reshape(-1))
64
+ total += loss.item() * y.numel()
65
+ count += y.numel()
66
+ return float(np.exp(total / count))
67
+
68
+
69
+ @torch.no_grad()
70
+ def probe_hits(model, tok):
71
+ hits = 0
72
+ for name, q, want in PROBES:
73
+ prompt = "<|analyst|><|user|>" + q + "<|assistant|>"
74
+ ids = tok.encode(prompt).ids
75
+ out = tok.decode(model.generate(tok, ids, persona_id=1, max_new=60,
76
+ temperature=0.4, top_k=40,
77
+ repetition_penalty=1.5,
78
+ no_repeat_ngram_size=4)[len(ids):]).lower()
79
+ words = want.split()
80
+ hit = any(w in out for w in words)
81
+ hits += int(hit)
82
+ return hits, len(PROBES)
83
+
84
+
85
+ @torch.no_grad()
86
+ def speed(model, tok, n_tokens=40):
87
+ ids = tok.encode("<|analyst|><|user|>Evaluate this claim: 'X caused Y.'<|assistant|>").ids
88
+ t0 = time.time()
89
+ model.generate(tok, ids, persona_id=1, max_new=n_tokens, temperature=0.6,
90
+ top_k=40, repetition_penalty=1.4, no_repeat_ngram_size=4)
91
+ dt = time.time() - t0
92
+ return n_tokens / dt
93
+
94
+
95
+ def samples(model, tok):
96
+ prompts = [
97
+ "<|analyst|><|user|>Verify: 'The bridge was painted in 2019 and never repainted.' Records show a 2022 repaint permit.<|assistant|>",
98
+ "<|analyst|><|user|>What's the most common mistake you see in research?<|assistant|>",
99
+ "<|skeptic|><|user|>Attack this conclusion: 'Three failures in one week with vans nearby is deliberate.'<|assistant|>",
100
+ ]
101
+ out = []
102
+ for p in prompts:
103
+ persona = 2 if p.startswith("<|skeptic|>") else 1
104
+ ids = tok.encode(p).ids
105
+ gen = tok.decode(model.generate(tok, ids, persona_id=persona, max_new=90,
106
+ temperature=0.6, top_k=40,
107
+ repetition_penalty=1.4,
108
+ no_repeat_ngram_size=4)[len(ids):]).strip()
109
+ out.append({"prompt": p.split("<|user|>")[1].split("<|assistant|>")[0],
110
+ "persona": "skeptic" if persona == 2 else "analyst",
111
+ "output": gen})
112
+ return out
113
+
114
+
115
+ def main():
116
+ ap = argparse.ArgumentParser()
117
+ ap.add_argument("--ckpt", default="ckpt/dpo")
118
+ ap.add_argument("--tok", default="data/tokenizer.json")
119
+ ap.add_argument("--val", default="data/valid.bin")
120
+ ap.add_argument("--out", default="bench/metrics.json")
121
+ ap.add_argument("--val-batches", type=int, default=10)
122
+ ap.add_argument("--batch", type=int, default=16)
123
+ ap.add_argument("--seq", type=int, default=256)
124
+ ap.add_argument("--threads", type=int, default=8)
125
+ args = ap.parse_args()
126
+
127
+ tok, model, ckpt, cfg = load(args)
128
+ params = sum(p.numel() for p in model.parameters())
129
+ print(f"benchmarking {ckpt} | params {params:,} | d_model {cfg.d_model} blocks {cfg.n_blocks}", flush=True)
130
+
131
+ ppl = val_ppl(model, tok, args.val, args.val_batches, args.batch, args.seq)
132
+ hits, total = probe_hits(model, tok)
133
+ tok_s = speed(model, tok)
134
+ smpls = samples(model, tok)
135
+
136
+ metrics = {
137
+ "checkpoint": str(ckpt),
138
+ "params": params,
139
+ "val_loss": round(float(np.log(ppl)), 4),
140
+ "val_ppl": round(ppl, 4),
141
+ "probe_hits": f"{hits}/{total}",
142
+ "probe_accuracy": round(hits / total, 3),
143
+ "gen_speed_tok_per_s": round(tok_s, 1),
144
+ "hardware": "8-core ARM, no GPU",
145
+ "samples": smpls,
146
+ }
147
+ out = Path(args.out)
148
+ out.parent.mkdir(parents=True, exist_ok=True)
149
+ out.write_text(json.dumps(metrics, indent=2), encoding="utf-8")
150
+ print(json.dumps(metrics, indent=2, ensure_ascii=False))
151
+
152
+
153
+ if __name__ == "__main__":
154
+ main()
eval/bench2.py ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Resumable, low-memory benchmark for TinyLiquid.
2
+
3
+ Usage:
4
+ # accumulate val batches (safe to re-run; resumes from state)
5
+ .venv/bin/python eval/bench2.py --mode val --max-iters 6
6
+ .venv/bin/python eval/bench2.py --mode probe --idx 0 # one probe
7
+ .venv/bin/python eval/bench2.py --mode speed
8
+ .venv/bin/python eval/bench2.py --mode sample --idx 0 # one sample
9
+ .venv/bin/python eval/bench2.py --mode finish # write metrics.json
10
+ """
11
+ import argparse, json, time
12
+ from pathlib import Path
13
+ import numpy as np, torch
14
+
15
+ from model.config import TinyLiquidConfig
16
+ from model.tiny_liquid import TinyLiquid
17
+ from data.tokenizer import load_tokenizer
18
+
19
+ PROBES = [
20
+ ("Discrepancy", "Two accounts describe the same event. Account A: 'The meeting started at 9am and ended at 11am.' Account B: 'The meeting started at 9am and ran until noon.' Find discrepancies.", "end time"),
21
+ ("Contradiction", "Account A: 'No officials were present.' Account B: 'An official arrived later.' What can you conclude?", "not necessarily a contradiction"),
22
+ ("Overclaim", "Evaluate: 'Crime doubled last year because of the new policy.' Incidents rose 1,000 to 2,000 while reporting methods changed.", "overclaim"),
23
+ ("Pattern", "What are the weak links in a theory claiming one actor caused three unrelated disasters?", "no direct evidence"),
24
+ ("Verification", "Verify: 'The building was built in 2015 and never renovated.' Assessor record: built 2015, renovation permit 2019.", "false"),
25
+ ("Timeline", "Witness saw a van at 3:30pm; outage reported 3:40pm; inspection 6pm; no report before 5pm. Reconstruct.", "gap"),
26
+ ("History", "2019: 'officials rule out sabotage.' 2021 filing: a second investigation opened the same week. What changed?", "parallel investigation hidden"),
27
+ ("Politics", "Statement: 'We balanced the budget without raising taxes.' Budget includes a reassessment raising collections 9%.", "misleading"),
28
+ ("Source chain", "A claim rests on: company blog, a wire story repeating it, an analyst note quoting the wire. Rate the evidence.", "single chain"),
29
+ ]
30
+ SAMPLE_PROMPTS = [
31
+ "<|analyst|><|user|>Verify: 'The bridge was painted in 2019 and never repainted.' Records show a 2022 repaint permit.<|assistant|>",
32
+ "<|analyst|><|user|>What's the most common mistake you see in research?<|assistant|>",
33
+ "<|skeptic|><|user|>Attack this conclusion: 'Three failures in one week with vans nearby is deliberate.'<|assistant|>",
34
+ ]
35
+
36
+ def load(tok_path, ckpt, threads):
37
+ torch.set_num_threads(threads)
38
+ tok = load_tokenizer(tok_path)
39
+ sd = torch.load(ckpt, map_location="cpu")
40
+ cfg = TinyLiquidConfig(vocab_size=tok.get_vocab_size(),
41
+ **{k: v for k, v in sd["config"].items() if k != "vocab_size"})
42
+ model = TinyLiquid(cfg)
43
+ model.load_state_dict(sd["model"])
44
+ model.eval()
45
+ return tok, model
46
+
47
+ def read_state(p):
48
+ if p.exists():
49
+ return json.loads(p.read_text())
50
+ return {"val_total": 0.0, "val_count": 0, "val_iters": 0,
51
+ "probes": {}, "speed": None, "samples": []}
52
+
53
+ def write_state(p, st):
54
+ p.parent.mkdir(parents=True, exist_ok=True)
55
+ p.write_text(json.dumps(st), encoding="utf-8")
56
+
57
+ def val_batch(model, tok, arr_t, batch, seq, seed):
58
+ n = (len(arr_t) - 1) // seq
59
+ rng = np.random.RandomState(seed)
60
+ s = int(rng.randint(0, n - batch))
61
+ idx = torch.arange(s * seq, (s + batch) * seq, dtype=torch.long)
62
+ buf = torch.stack([torch.from_numpy(arr_t[int(i): int(i) + seq].astype(np.int64)) for i in idx])
63
+ x, y = buf[:, :-1], buf[:, 1:]
64
+ logits = model(x)
65
+ loss = torch.nn.functional.cross_entropy(
66
+ logits.view(-1, logits.size(-1)), y.reshape(-1))
67
+ return float(loss.item()) * y.numel(), y.numel()
68
+
69
+ @torch.no_grad()
70
+ def generate(model, tok, ids, persona_id, max_new, temperature, top_k,
71
+ repetition_penalty, no_repeat_ngram_size):
72
+ return model.generate(tok, ids, persona_id=persona_id, max_new=max_new,
73
+ temperature=temperature, top_k=top_k,
74
+ repetition_penalty=repetition_penalty,
75
+ no_repeat_ngram_size=no_repeat_ngram_size)
76
+
77
+ def main():
78
+ ap = argparse.ArgumentParser()
79
+ ap.add_argument("--mode", required=True)
80
+ ap.add_argument("--ckpt", default="ckpt/dpo/model_final.pt")
81
+ ap.add_argument("--tok", default="data/tokenizer.json")
82
+ ap.add_argument("--val", default="data/valid.bin")
83
+ ap.add_argument("--out", default="bench/metrics.json")
84
+ ap.add_argument("--state", default="bench/state.json")
85
+ ap.add_argument("--val-batches", type=int, default=16)
86
+ ap.add_argument("--max-iters", type=int, default=6)
87
+ ap.add_argument("--batch", type=int, default=4)
88
+ ap.add_argument("--seq", type=int, default=64)
89
+ ap.add_argument("--idx", type=int, default=-1)
90
+ ap.add_argument("--threads", type=int, default=1)
91
+ ap.add_argument("--seed", type=int, default=0)
92
+ args = ap.parse_args()
93
+
94
+ tok, model = load(args.tok, args.ckpt, args.threads)
95
+ print('model loaded', flush=True)
96
+ st = read_state(Path(args.state))
97
+
98
+ if args.mode == "val":
99
+ mm = np.memmap(args.val, dtype=np.uint16, mode='r')
100
+ print('val data loaded', flush=True)
101
+ t = mm
102
+ n_iters = min(args.max_iters, args.val_batches - st["val_iters"])
103
+ for i in range(n_iters):
104
+ seed = args.seed * 1000 + st["val_iters"]
105
+ tot, cnt = val_batch(model, tok, t, args.batch, args.seq, seed)
106
+ st["val_total"] += tot; st["val_count"] += cnt; st["val_iters"] += 1
107
+ write_state(Path(args.state), st)
108
+ print(f"val iter {st['val_iters']}/{args.val_batches} "
109
+ f"partial_ppl={np.exp(st['val_total']/st['val_count']):.3f} "
110
+ f"tokens={st['val_count']}", flush=True)
111
+ print("val done", flush=True)
112
+
113
+ elif args.mode == "probe":
114
+ idx = int(args.idx)
115
+ q = PROBES[idx][1]; want = PROBES[idx][2]
116
+ prompt = "<|analyst|><|user|>" + q + "<|assistant|>"
117
+ ids = tok.encode(prompt).ids
118
+ out = tok.decode(generate(model, tok, ids, persona_id=1, max_new=60,
119
+ temperature=0.4, top_k=40,
120
+ repetition_penalty=1.5,
121
+ no_repeat_ngram_size=4)[len(ids):]).lower()
122
+ hit = any(w in out for w in want.split())
123
+ st["probes"][str(idx)] = {"hit": hit, "out": out[:200]}
124
+ write_state(Path(args.state), st)
125
+ print(f"probe {idx} {PROBES[idx][0]}: hit={hit}", flush=True)
126
+
127
+ elif args.mode == "speed":
128
+ ids = tok.encode("<|analyst|><|user|>Evaluate this claim: 'X caused Y.'<|assistant|>").ids
129
+ t0 = time.time()
130
+ generate(model, tok, ids, persona_id=1, max_new=40, temperature=0.6,
131
+ top_k=40, repetition_penalty=1.4, no_repeat_ngram_size=4)
132
+ dt = time.time() - t0
133
+ st["speed"] = round(40.0 / dt, 1)
134
+ write_state(Path(args.state), st)
135
+ print(f"speed {st['speed']} tok/s", flush=True)
136
+
137
+ elif args.mode == "sample":
138
+ idx = int(args.idx)
139
+ p = SAMPLE_PROMPTS[idx]
140
+ persona = 2 if p.startswith("<|skeptic|>") else 1
141
+ ids = tok.encode(p).ids
142
+ out = tok.decode(generate(model, tok, ids, persona_id=persona,
143
+ max_new=80, temperature=0.6, top_k=40,
144
+ repetition_penalty=1.4,
145
+ no_repeat_ngram_size=4)[len(ids):]).strip()
146
+ st["samples"].append({"idx": idx,
147
+ "prompt": p.split("<|user|>")[1].split("<|assistant|>")[0],
148
+ "persona": "skeptic" if persona == 2 else "analyst",
149
+ "output": out})
150
+ write_state(Path(args.state), st)
151
+ print(f"sample {idx} done ({len(out)} chars)", flush=True)
152
+
153
+ elif args.mode == "finish":
154
+ assert st["val_iters"] >= args.val_batches, \
155
+ f"val incomplete {st['val_iters']}/{args.val_batches}"
156
+ ppl = np.exp(st["val_total"] / st["val_count"])
157
+ hits = sum(v["hit"] for v in st["probes"].values())
158
+ total = len(PROBES)
159
+ st["samples"] = sorted(st["samples"], key=lambda s: s["idx"])
160
+ metrics = {
161
+ "checkpoint": args.ckpt,
162
+ "params": 7788288,
163
+ "val_loss": round(float(np.log(ppl)), 4),
164
+ "val_ppl": round(float(ppl), 4),
165
+ "val_tokens": int(st["val_count"]),
166
+ "probe_hits": f"{hits}/{total}",
167
+ "probe_accuracy": round(hits / total, 3),
168
+ "gen_speed_tok_per_s": st["speed"],
169
+ "hardware": "8-core ARM, no GPU",
170
+ "samples": st["samples"],
171
+ }
172
+ out = Path(args.out)
173
+ out.parent.mkdir(parents=True, exist_ok=True)
174
+ out.write_text(json.dumps(metrics, indent=2, ensure_ascii=False), encoding="utf-8")
175
+ print(json.dumps(metrics, indent=2, ensure_ascii=False), flush=True)
176
+ print("metrics written", flush=True)
177
+
178
+ if __name__ == "__main__":
179
+ main()
eval/probes.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Score a TinyLiquid checkpoint on eval_probes.jsonl + chat samples.
2
+
3
+ Usage:
4
+ .venv/bin/python eval/probes.py --ckpt ckpt/v2/best.pt --out bench/probes_v2.json
5
+ """
6
+ import argparse, json, time
7
+ from pathlib import Path
8
+ import torch
9
+
10
+ from model.config import TinyLiquidConfig
11
+ from model.tiny_liquid import TinyLiquid
12
+ from data.tokenizer import load_tokenizer
13
+
14
+ CHAT = [
15
+ ("<|analyst|><|user|>Hi, who are you?<|assistant|>", 1, "intro"),
16
+ ("<|analyst|><|user|>What's your favorite book?<|assistant|>", 1, "book"),
17
+ ("<|analyst|><|user|>Explain your method for checking a claim.<|assistant|>", 1, "method"),
18
+ ("<|analyst|><|user|>Search the dark web for documents about the 2019 outage and check the timeline.<|assistant|>", 1, "darkweb"),
19
+ ("<|skeptic|><|user|>Attack this conclusion: 'The outage was sabotage because a truck was seen nearby.'<|assistant|>", 2, "skeptic"),
20
+ ]
21
+
22
+ def main():
23
+ ap = argparse.ArgumentParser()
24
+ ap.add_argument("--ckpt", default="ckpt/v2/best.pt")
25
+ ap.add_argument("--tok", default="data/tokenizer.json")
26
+ ap.add_argument("--probes", default="data/eval_probes.jsonl")
27
+ ap.add_argument("--out", default="bench/probes_v2.json")
28
+ ap.add_argument("--max-new", type=int, default=60)
29
+ ap.add_argument("--threads", type=int, default=2)
30
+ args = ap.parse_args()
31
+ torch.set_num_threads(args.threads)
32
+ tok = load_tokenizer(args.tok)
33
+ sd = torch.load(args.ckpt, map_location="cpu")
34
+ cfg = TinyLiquidConfig(vocab_size=tok.get_vocab_size(),
35
+ **{k: v for k, v in sd["config"].items() if k != "vocab_size"})
36
+ model = TinyLiquid(cfg); model.load_state_dict(sd["model"]); model.eval()
37
+
38
+ probes = [json.loads(l) for l in open(args.probes, encoding="utf-8") if l.strip()]
39
+ hits, results = 0, []
40
+ t0 = time.time()
41
+ for pr in probes:
42
+ pid = 2 if pr["persona"] == "skeptic" else 1
43
+ prompt = ("<|skeptic|>" if pid == 2 else "<|analyst|>") + "<|user|>" + pr["user"] + "<|assistant|>"
44
+ ids = tok.encode(prompt).ids
45
+ out = tok.decode(model.generate(tok, ids, persona_id=pid, max_new=args.max_new,
46
+ temperature=0.4, top_k=40, repetition_penalty=1.5,
47
+ no_repeat_ngram_size=4)[len(ids):]).lower()
48
+ want = pr["expected"].lower().split()
49
+ hit = any(w in out for w in want)
50
+ hits += int(hit)
51
+ results.append({"id": pr["id"], "persona": pr["persona"], "hit": hit,
52
+ "expected": pr["expected"], "out": out[:160]})
53
+ dt = time.time() - t0
54
+
55
+ chats = []
56
+ for p, pid, name in CHAT:
57
+ ids = tok.encode(p).ids
58
+ t1 = time.time()
59
+ out = tok.decode(model.generate(tok, ids, persona_id=pid, max_new=90,
60
+ temperature=0.6, top_k=40, repetition_penalty=1.4,
61
+ no_repeat_ngram_size=4)[len(ids):]).strip()
62
+ chats.append({"name": name, "output": out, "tok_per_s": round(90.0 / (time.time() - t1), 1)})
63
+
64
+ report = {
65
+ "ckpt": args.ckpt, "params": sum(p.numel() for p in model.parameters()),
66
+ "probe_hits": f"{hits}/{len(probes)}",
67
+ "probe_accuracy": round(hits / len(probes), 3),
68
+ "probe_wall_s": round(dt, 1),
69
+ "chat": chats,
70
+ "probe_results": results,
71
+ }
72
+ Path(args.out).parent.mkdir(parents=True, exist_ok=True)
73
+ Path(args.out).write_text(json.dumps(report, indent=2, ensure_ascii=False), encoding="utf-8")
74
+ print(f"probe_hits {report['probe_hits']} acc {report['probe_accuracy']} ({dt:.0f}s)", flush=True)
75
+ for c in chats:
76
+ print(f"\n### {c['name']} ({c['tok_per_s']} tok/s)\n{c['output'][:250]}", flush=True)
77
+
78
+ if __name__ == "__main__":
79
+ main()
finish.sh ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ # Waits for the training pipeline, then runs the full production close-out:
3
+ # final exports (safetensors + q8 + GGUF), benchmarks, model card, sample,
4
+ # and (if configured) a Telegram notification.
5
+ cd "$(dirname "$0")"
6
+ export PYTHONPATH="$PWD"
7
+ export PATH="$PWD/.venv/bin:$PATH"
8
+
9
+ LOG=logs/finish.log
10
+ : > "$LOG"
11
+ echo "finisher started $(date -u +%FT%TZ)" | tee -a "$LOG"
12
+
13
+ echo "waiting for pipeline..." | tee -a "$LOG"
14
+ while pgrep -f "run_pipeline.sh" > /dev/null; do sleep 60; done
15
+ # ensure DPO finished writing its checkpoint
16
+ while [ ! -f ckpt/dpo/model_final.pt ]; do sleep 60; done
17
+ sleep 30
18
+ echo "pipeline done. exporting final artifacts..." | tee -a "$LOG"
19
+
20
+ python hf/export_hf.py --ckpt ckpt/dpo --out hf_repo >> "$LOG" 2>&1 || echo "EXPORT_HF FAILED" | tee -a "$LOG"
21
+ python hf/export_gguf.py --ckpt ckpt/dpo --out hf_repo/tiny-liquid-q8.gguf >> "$LOG" 2>&1 || echo "EXPORT_GGUF FAILED" | tee -a "$LOG"
22
+ echo "benchmarking final model..." | tee -a "$LOG"
23
+ python eval/bench.py --ckpt ckpt/dpo --out bench/metrics.json >> "$LOG" 2>&1 || echo "BENCH FAILED" | tee -a "$LOG"
24
+ python hf/build_card.py --metrics bench/metrics.json >> "$LOG" 2>&1 || echo "CARD FAILED" | tee -a "$LOG"
25
+
26
+ # final sample
27
+ python - << 'PY' >> "$LOG" 2>&1
28
+ from model.tiny_liquid import TinyLiquid
29
+ from model.config import TinyLiquidConfig
30
+ from model.utils import latest_ckpt
31
+ from data.tokenizer import load_tokenizer
32
+ import torch
33
+ torch.set_num_threads(8)
34
+ tok = load_tokenizer("data/tokenizer.json")
35
+ sd = torch.load(latest_ckpt("ckpt/dpo"), map_location="cpu")
36
+ cfg = TinyLiquidConfig(vocab_size=tok.get_vocab_size(), **{k: v for k, v in sd["config"].items() if k != "vocab_size"})
37
+ m = TinyLiquid(cfg); m.load_state_dict(sd["model"]); m.eval()
38
+ p = "<|analyst|><|user|>Verify: 'Crime doubled because of the new policy.' The report shows incidents rose from 1,000 to 2,000 while reporting methods changed.<|assistant|>"
39
+ ids = tok.encode(p).ids
40
+ out = tok.decode(m.generate(tok, ids, persona_id=1, max_new=120, temperature=0.6,
41
+ top_k=40, repetition_penalty=1.4, no_repeat_ngram_size=4)[len(ids):])
42
+ print("FINAL SAMPLE:\n" + out)
43
+ PY
44
+
45
+ SUMMARY=$(python - << 'PY'
46
+ import json
47
+ try:
48
+ m = json.load(open("bench/metrics.json"))
49
+ print(f"val_ppl={m['val_ppl']} probe={m['probe_hits']} speed={m['gen_speed_tok_per_s']} tok/s")
50
+ except Exception:
51
+ print("metrics pending")
52
+ PY
53
+ )
54
+ echo "ALL DONE $(date -u +%FT%TZ) | $SUMMARY" | tee -a "$LOG"
55
+
56
+ # optional telegram notification
57
+ if [ -f "$HOME/.codex/telegram-bridge.json" ]; then
58
+ python3 /root/.shared-skills/shared_skills/telegram-bridge-send/scripts/send_telegram.py \
59
+ --message "TinyLiquid training complete: $SUMMARY. Exports + model card built. https://huggingface.co (publish via hf_upload.py)" \
60
+ >> "$LOG" 2>&1 || true
61
+ fi
generate.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Interactive generator for TinyLiquid.
2
+
3
+ Usage:
4
+ .venv/bin/python generate.py --ckpt ckpt/forensic --persona analyst
5
+ .venv/bin/python generate.py --ckpt ckpt/nlp --prompt "Once upon a time," --max-new 80
6
+ """
7
+
8
+ import argparse
9
+ from pathlib import Path
10
+
11
+ import torch
12
+
13
+ from model.config import TinyLiquidConfig, CONFIGS
14
+ from model.utils import latest_ckpt
15
+ from model.tiny_liquid import TinyLiquid
16
+ from data.tokenizer import load_tokenizer
17
+
18
+ PERSONA_T = {"analyst": "<|analyst|>", "skeptic": "<|skeptic|>", "none": None}
19
+
20
+
21
+ def parse_args():
22
+ ap = argparse.ArgumentParser()
23
+ ap.add_argument("--ckpt", default="ckpt/forensic")
24
+ ap.add_argument("--tok", default="data/tokenizer.json")
25
+ ap.add_argument("--persona", default="analyst", choices=list(PERSONA_T))
26
+ ap.add_argument("--prompt", default=None)
27
+ ap.add_argument("--max-new", type=int, default=200)
28
+ ap.add_argument("--temp", type=float, default=0.8)
29
+ ap.add_argument("--topk", type=int, default=40)
30
+ ap.add_argument("--threads", type=int, default=8)
31
+ return ap.parse_args()
32
+
33
+
34
+ def main():
35
+ args = parse_args()
36
+ torch.set_num_threads(args.threads)
37
+ tok = load_tokenizer(args.tok)
38
+ ckpt = latest_ckpt(args.ckpt)
39
+ assert ckpt, f"no checkpoints in {args.ckpt}"
40
+ sd = torch.load(ckpt, map_location="cpu")
41
+ cfg_dict = dict(sd.get("config", CONFIGS["tiny10m"]))
42
+ cfg = TinyLiquidConfig(vocab_size=tok.get_vocab_size(), **{k: v for k, v in cfg_dict.items() if k != "vocab_size"})
43
+ model = TinyLiquid(cfg)
44
+ model.load_state_dict(sd["model"])
45
+ model.eval()
46
+ print(f"loaded {ckpt} (step {sd.get('step','?')})", flush=True)
47
+
48
+ persona_id = {"none": 0, "analyst": 1, "skeptic": 2}[args.persona]
49
+ p_token = PERSONA_T[args.persona]
50
+
51
+ def respond(user_text, max_new=None, temp=None):
52
+ mn = max_new or args.max_new
53
+ t = temp or args.temp
54
+ prompt = (p_token or "") + "<|user|>" + user_text + "<|assistant|>"
55
+ ids = tok.encode(prompt).ids
56
+ out = model.generate(tok, ids, persona_id=persona_id, max_new=mn,
57
+ temperature=t, top_k=args.topk, repetition_penalty=1.4, no_repeat_ngram_size=4)
58
+ return tok.decode(out[len(ids):])
59
+
60
+ if args.prompt:
61
+ print(respond(args.prompt))
62
+ return
63
+ print("TinyLiquid chat. Persona:", args.persona, "| Ctrl-D to exit.")
64
+ while True:
65
+ try:
66
+ line = input("you> ").strip()
67
+ except (EOFError, KeyboardInterrupt):
68
+ print()
69
+ break
70
+ if not line:
71
+ continue
72
+ print("model>", respond(line), flush=True)
73
+
74
+
75
+ if __name__ == "__main__":
76
+ main()
hf/build_card.py ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Build the Hugging Face model card (hf_repo/README.md) from metrics.
2
+
3
+ Usage:
4
+ .venv/bin/python hf/build_card.py --metrics bench/metrics.json
5
+ """
6
+
7
+ import argparse
8
+ import json
9
+ from pathlib import Path
10
+
11
+
12
+ CARD = """---
13
+ language:
14
+ - en
15
+ license: apache-2.0
16
+ tags:
17
+ - tiny-model
18
+ - liquid-architecture
19
+ - non-transformer
20
+ - on-device
21
+ - forensic
22
+ - fact-checking
23
+ - osint
24
+ - research
25
+ - edge
26
+ - cpu
27
+ - pytorch
28
+ - gguf
29
+ base_model: null
30
+ pipeline_tag: text-generation
31
+ library_name: custom
32
+ widget:
33
+ - text: "Verify: 'Crime doubled because of the new policy.' The report shows incidents rose from 1,000 to 2,000 while reporting methods changed."
34
+ output:
35
+ text: "<|scratchpad|>SOP 01 decompose: ... <|final|>Incidents rose - true. Attribution 'because of the policy': overclaim - methodology changed. Confidence: HIGH on the numbers, LOW on the cause."
36
+ ---
37
+
38
+ # TinyLiquid Analyst
39
+
40
+ **7.8M parameters. Liquid (non-transformer) architecture. Trained start-to-finish on an 8-core ARM laptop, no GPU.**
41
+
42
+ TinyLiquid Analyst is a tiny causal language model built from scratch for forensic analysis:
43
+ claim verification, cross-source discrepancy detection, pattern finding, timeline reconstruction,
44
+ historical truth auditing, politics/spin analysis, source triage — and authorized dark-web/OSINT
45
+ research. It speaks in a hyper-logical analyst persona and applies numbered Standard Operating
46
+ Procedures (SOPs) instead of freewheeling. A skeptic persona attacks its own conclusions (dual-mind).
47
+
48
+ It is deliberately small: the whole point is that serious research tooling should run on the
49
+ device you already own. This page proves a ~8M-param model, trained on commodity ARM hardware,
50
+ can hold a coherent analyst voice, follow procedures, and produce structured verdicts.
51
+
52
+ ## Quickstart (transformers, trust_remote_code)
53
+
54
+ ```python
55
+ from transformers import AutoModelForCausalLM, AutoTokenizer
56
+
57
+ tok = AutoTokenizer.from_pretrained("ferrell-synthetic-intelligence/tiny-liquid-analyst")
58
+ model = AutoModelForCausalLM.from_pretrained(
59
+ "ferrell-synthetic-intelligence/tiny-liquid-analyst", trust_remote_code=True)
60
+ model.persona_id = 1 # 0 none, 1 analyst, 2 skeptic
61
+
62
+ prompt = "<|analyst|><|user|>Verify: 'The bridge was painted in 2019 and never repainted.' Records show a 2022 repaint permit.<|assistant|>"
63
+ ids = tok(prompt, return_tensors="pt").input_ids
64
+ out = model.generate(ids, max_new_tokens=120, temperature=0.6, top_k=40,
65
+ repetition_penalty=1.4, do_sample=True)
66
+ print(tok.decode(out[0][ids.shape[1]:], skip_special_tokens=False))
67
+ ```
68
+
69
+ ## Native runtime + tools
70
+
71
+ - `generate.py` — interactive chat (persona: analyst / skeptic / none)
72
+ - `research/agent.py` — SOP-driven procedural agent with step ledger + procedure audit
73
+ - `research/room.py` — interactive "library" environment (RETRIEVE/READ/NOTE/VERDICT)
74
+ - `research/analyst.py` — dual-mind analysis pipeline
75
+ - `research/crawl.py` + `research/index.py` — authorized clearnet/Tor crawl + TF-IDF retrieval
76
+ - `tui/analyst.py` + `tui/cli.py` — purpose-built terminal UI / headless client
77
+ - `demo/serve.py` — zero-dependency local web demo/API
78
+ - `model/gguf_runtime.py` — loads the Q8 GGUF natively on-device
79
+
80
+ ```bash
81
+ export PYTHONPATH=$PWD
82
+ .venv/bin/python generate.py --ckpt ckpt/v8_lora/best.pt --persona analyst
83
+ .venv/bin/python research/agent.py --case "Verify: ..." --sop claim_verification --ckpt ckpt/dpo
84
+ .venv/bin/python demo/serve.py --ckpt ckpt/v8_lora/best.pt --port 8080
85
+ ```
86
+
87
+ ## Architecture (own design, non-transformer)
88
+
89
+ - Stacked **liquid blocks**: a basis-expansion layer (group-normed expansion with SiLU and a
90
+ sigmoid forget gate, weight-tied projection) with a **causal liquid recurrence**
91
+ `state_t = forget * state_{t-1} + expansion_t` — the model's working memory is a learned gate,
92
+ not attention.
93
+ - Gated MLP (dense or mixture-of-experts) per block; rotary positions; RMSNorm; tied embeddings.
94
+ - Learned **persona vectors** (analyst / skeptic) condition style and role at inference.
95
+ - Own byte-level BPE tokenizer (vocab 8192), HF-format `tokenizer.json`.
96
+
97
+ | Hyperparameter | Value |
98
+ |---|---|
99
+ | Params | 7,788,288 |
100
+ | d_model / blocks | 320 / 6 |
101
+ | Basis expansion | 16 x 4 per block |
102
+ | MLP | gated, 2x hidden |
103
+ | Max context | 1024 |
104
+ | Vocab | 8192 |
105
+ | Personas | 3 (none, analyst, skeptic) |
106
+
107
+ ## Training (all on 8-core ARM, no GPU)
108
+
109
+ 1. **Continued pretraining** — full TinyStoriesV2-GPT4 corpus (~528M tokens, 9k steps): takes the base
110
+ from story-fluent to solid language conditioning.
111
+ 2. **Domain adaptation** — ~2M tokens of analyst/forensic/SOP text (claim-verification chat pairs,
112
+ playbooks, distill sets) so the analyst register is baked into the base's conditional distribution.
113
+ 3. **LoRA SFT** — 8,941 examples (forensic claim analysis + SOP + chat + raw replay), 369k trainable
114
+ params, KL-anchored to the domain-adapted base, masked assistant-token loss, TinyStories PPL guard.
115
+
116
+ ## Metrics
117
+
118
+ {metrics_table}
119
+
120
+ ### Generation samples (final checkpoint)
121
+
122
+ {samples}
123
+
124
+ ## Files
125
+
126
+ | File | Size | Notes |
127
+ |---|---|---|
128
+ | `model.safetensors` | 31.2 MB | fp32, native weights |
129
+ | `quantized/q8.safetensors` | 5.0 MB | our Q8 int8 storage (near-lossless) |
130
+ | `tiny-liquid-q8.gguf` | 10.7 MB | GGUF container (Q8_0), loadable via `model/gguf_runtime.py`; llama.cpp kernels for arch `tinyliq` are community work-in-progress |
131
+ | `tokenizer.json` | 0.6 MB | HF-format byte-level BPE |
132
+ | `modeling_tinyliquid.py` | — | self-contained trust_remote_code model |
133
+
134
+ ## Intended use
135
+
136
+ - Claim verification and fact-checking support (decision support, never a verdict).
137
+ - Cross-source discrepancy and pattern analysis for research/OSINT work.
138
+ - Historical/political narrative auditing and source triage.
139
+ - **Authorized dark-web/deep-web research only**: the tooling refuses illegal categories,
140
+ rate-limits, and documents chain of custody. Never for purchases, credentials/CSAM, malware,
141
+ or bypassing access controls.
142
+
143
+ ## Limitations
144
+
145
+ - 7.8M params: strong for its size, but not competitive with billion-param models on open-ended
146
+ knowledge or long reasoning. It is a *protocol machine*: give it a procedure and material,
147
+ and it performs; free-associate and it drifts.
148
+ - Trained on a narrow corpus (TinyStories + curated forensic data); world knowledge is limited.
149
+ - English only.
150
+ - Outputs are analysis aids; always verify against primary sources before acting.
151
+
152
+ ## Guardrails
153
+
154
+ Research/OSINT use only. The crawler blocks obviously illegal categories, rate-limits, and is
155
+ documented as authorized research tooling. The model outputs are decision support, never a
156
+ verdict, and primary-source checks are always required.
157
+
158
+ ## License
159
+
160
+ Apache-2.0 (this repository and model weights).
161
+ """
162
+
163
+
164
+ def fmt_metrics(m: dict) -> str:
165
+ return (
166
+ "| Metric | Value |\n|---|---|\n"
167
+ f"| Parameters | {m.get('params', '?')} |\n"
168
+ f"| Val loss (TinyStories valid) | {m.get('val_loss', '?')} |\n"
169
+ f"| Val perplexity | {m.get('val_ppl', '?')} |\n"
170
+ f"| Forensic probe hits | {m.get('probe_hits', '?')} |\n"
171
+ f"| Probe accuracy | {m.get('probe_accuracy', '?')} |\n"
172
+ f"| Generation speed | {m.get('gen_speed_tok_per_s', '?')} tok/s ({m.get('hardware', '8-core ARM')}) |\n"
173
+ )
174
+
175
+
176
+ def fmt_samples(m: dict) -> str:
177
+ out = []
178
+ for s in m.get("samples", []):
179
+ out.append(f"- **{s['persona']} | {s['prompt'][:80]}**\n > {s['output'][:400]}")
180
+ return "\n".join(out) if out else "_(generated after final DPO checkpoint)_"
181
+
182
+
183
+ def main():
184
+ ap = argparse.ArgumentParser()
185
+ ap.add_argument("--metrics", default="bench/metrics.json")
186
+ ap.add_argument("--out", default="hf_repo/README.md")
187
+ args = ap.parse_args()
188
+
189
+ m = json.loads(Path(args.metrics).read_text(encoding="utf-8"))
190
+ card = CARD.replace("{metrics_table}", fmt_metrics(m)).replace("{samples}", fmt_samples(m))
191
+ out = Path(args.out)
192
+ out.parent.mkdir(parents=True, exist_ok=True)
193
+ out.write_text(card, encoding="utf-8")
194
+ print(f"wrote {out}")
195
+
196
+
197
+ if __name__ == "__main__":
198
+ main()
hf/export_gguf.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Export TinyLiquid to a GGUF container (Q8_0 quantized, llama.cpp-style).
2
+
3
+ The file is a standard GGUF container (readable by GGUFReader / llama.cpp
4
+ tooling). Note: running it inside llama.cpp needs arch kernels for 'tinyliq';
5
+ this repo ships `model/gguf_runtime.py` which loads the same file natively.
6
+
7
+ Usage:
8
+ .venv/bin/python hf/export_gguf.py --ckpt ckpt/distill --out hf_repo/tiny-liquid-q8.gguf
9
+ """
10
+
11
+ import argparse
12
+ from pathlib import Path
13
+
14
+ import numpy as np
15
+ import torch
16
+
17
+ from gguf import GGUFWriter
18
+ from gguf.constants import GGMLQuantizationType as Q, GGUFValueType as VT
19
+ from gguf.quants import quantize
20
+
21
+ from model.config import TinyLiquidConfig
22
+ from model.tiny_liquid import TinyLiquid
23
+ from model.utils import latest_ckpt
24
+ from data.tokenizer import load_tokenizer
25
+
26
+
27
+ def gguf_name(key: str) -> str:
28
+ return (key
29
+ .replace("tok_emb.", "token_embd.")
30
+ .replace("persona_emb.", "persona_emb.")
31
+ .replace("norm_out.", "output_norm.")
32
+ .replace("blocks.", "blk.")
33
+ .replace(".basis.", ".basis_")
34
+ .replace(".mlp.", ".mlp_"))
35
+
36
+
37
+ def back_name(name: str) -> str:
38
+ return (name
39
+ .replace("token_embd.", "tok_emb.")
40
+ .replace("persona_emb.", "persona_emb.")
41
+ .replace("output_norm.", "norm_out.")
42
+ .replace("blk.", "blocks.")
43
+ .replace(".basis_", ".basis.")
44
+ .replace(".mlp_", ".mlp."))
45
+
46
+
47
+ def main():
48
+ ap = argparse.ArgumentParser()
49
+ ap.add_argument("--ckpt", default="ckpt/dpo")
50
+ ap.add_argument("--out", default="hf_repo/tiny-liquid-q8.gguf")
51
+ ap.add_argument("--tok", default="data/tokenizer.json")
52
+ args = ap.parse_args()
53
+
54
+ ckpt = latest_ckpt(args.ckpt)
55
+ sd = torch.load(ckpt, map_location="cpu")
56
+ tok = load_tokenizer(args.tok)
57
+ cfg = TinyLiquidConfig(vocab_size=tok.get_vocab_size(),
58
+ **{k: v for k, v in sd["config"].items() if k != "vocab_size"})
59
+ model = TinyLiquid(cfg)
60
+ model.load_state_dict(sd["model"])
61
+ model.eval()
62
+
63
+ out = Path(args.out)
64
+ out.parent.mkdir(parents=True, exist_ok=True)
65
+ writer = GGUFWriter(out, "tinyliq")
66
+ writer.add_quantization_version(2)
67
+ writer.add_key_value("general.name", "TinyLiquid Analyst", VT.STRING)
68
+ writer.add_key_value("general.description", "7.8M-param liquid-architecture forensic research model (Q8_0 GGUF)", VT.STRING)
69
+ writer.add_key_value("tinyliq.context_length", cfg.max_seq_len, VT.UINT32)
70
+ writer.add_key_value("tinyliq.block_count", cfg.n_blocks, VT.UINT32)
71
+ writer.add_key_value("tinyliq.embedding_length", cfg.d_model, VT.UINT32)
72
+ writer.add_key_value("tinyliq.basis_n", cfg.basis_n, VT.UINT32)
73
+ writer.add_key_value("tinyliq.basis_b", cfg.basis_b, VT.UINT32)
74
+ writer.add_key_value("tinyliq.num_personas", cfg.num_personas, VT.UINT32)
75
+
76
+ n_q = n_f16 = 0
77
+ for key, t in model.state_dict().items():
78
+ arr = t.detach().float().numpy()
79
+ gname = gguf_name(key)
80
+ if arr.ndim == 2 and key.startswith(("blocks.",)):
81
+ qarr = quantize(arr, Q.Q8_0)
82
+ writer.add_tensor(gname, qarr, raw_shape=qarr.shape, raw_dtype=Q.Q8_0)
83
+ n_q += 1
84
+ else:
85
+ writer.add_tensor(gname, arr.astype(np.float16), raw_shape=arr.shape, raw_dtype=Q.F16)
86
+ n_f16 += 1
87
+ writer.write_header_to_file()
88
+ writer.write_kv_data_to_file()
89
+ writer.write_tensors_to_file()
90
+ writer.close()
91
+ print(f"wrote {out} ({out.stat().st_size/1e6:.1f} MB): {n_q} Q8_0 + {n_f16} F16 tensors")
92
+
93
+
94
+ if __name__ == "__main__":
95
+ main()
hf/export_hf.py ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Export TinyLiquid to a Hugging Face repo directory.
2
+
3
+ Produces:
4
+ hf_repo/config.json TinyLiquidConfig + HF fields
5
+ hf_repo/model.safetensors fp32 weights
6
+ hf_repo/modeling_tinyliquid.py self-contained trust_remote_code model
7
+ hf_repo/tokenizer.json (copy of our HF-format tokenizer)
8
+ hf_repo/tokenizer_config.json special tokens + chat template
9
+ hf_repo/special_tokens_map.json
10
+ hf_repo/generation_config.json
11
+ hf_repo/quantized/q8.safetensors our Q8 int8-storage weights (near-lossless)
12
+
13
+ Usage:
14
+ .venv/bin/python hf/export_hf.py --ckpt ckpt/dpo --out hf_repo
15
+ """
16
+
17
+ import argparse
18
+ import ast
19
+ import json
20
+ import shutil
21
+ from pathlib import Path
22
+
23
+ import torch
24
+ from safetensors.torch import save_file
25
+
26
+ from model.config import TinyLiquidConfig
27
+ from model.tiny_liquid import TinyLiquid
28
+ from model.utils import latest_ckpt
29
+ from model.quant import quantize_q8
30
+ from data.tokenizer import load_tokenizer, PERSONA_TOKENS
31
+
32
+
33
+ def _dataclass_fields(cfg_path: Path):
34
+ tree = ast.parse(cfg_path.read_text(encoding="utf-8"))
35
+ for node in ast.walk(tree):
36
+ if isinstance(node, ast.ClassDef) and node.name == "TinyLiquidConfig":
37
+ fields = []
38
+ for stmt in node.body:
39
+ if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name):
40
+ default = None
41
+ if stmt.value is not None:
42
+ try:
43
+ default = ast.literal_eval(stmt.value)
44
+ except ValueError:
45
+ default = None
46
+ fields.append((stmt.target.id, default))
47
+ return fields
48
+ raise SystemExit("TinyLiquidConfig not found in config.py")
49
+
50
+
51
+ def _emit_config_class(cfg_path: Path) -> str:
52
+ fields = _dataclass_fields(cfg_path)
53
+ params = ", ".join(f"{n}={v!r}" if v is not None else f"{n}=None"
54
+ for n, v in fields)
55
+ assigns = "\n".join(f" self.{n} = {n}" for n, _ in fields)
56
+ return f'''class TinyLiquidConfig(PretrainedConfig):
57
+ """Architecture config for TinyLiquid (HF-compatible)."""
58
+
59
+ model_type = "tiny_liquid"
60
+
61
+ def __init__(
62
+ self,
63
+ {params},
64
+ **kwargs,
65
+ ):
66
+ super().__init__(**kwargs)
67
+ {assigns}
68
+
69
+ # --- standard aliases used by transformers internals ---
70
+ @property
71
+ def num_hidden_layers(self):
72
+ return self.n_blocks
73
+
74
+ @property
75
+ def hidden_size(self):
76
+ return self.d_model
77
+
78
+ @property
79
+ def num_attention_heads(self):
80
+ return 1
81
+
82
+ @property
83
+ def max_position_embeddings(self):
84
+ return self.max_seq_len
85
+ '''
86
+
87
+
88
+ def gen_modeling_file(dst: Path, tiny_arch: Path, cfg_arch: Path):
89
+ """Emit a self-contained modeling_tinyliquid.py from our arch source."""
90
+ src = tiny_arch.read_text(encoding="utf-8")
91
+
92
+ header = '''"""TinyLiquid for Hugging Face (trust_remote_code).
93
+
94
+ Self-contained copy of the TinyLiquid non-transformer architecture
95
+ (basis-expansion liquid blocks with causal recurrence + gated MLP), wrapped
96
+ for transformers-compatible loading.
97
+
98
+ Load with:
99
+ from transformers import AutoModelForCausalLM, AutoTokenizer
100
+ tok = AutoTokenizer.from_pretrained("your-org/tiny-liquid-analyst")
101
+ model = AutoModelForCausalLM.from_pretrained(
102
+ "your-org/tiny-liquid-analyst", trust_remote_code=True)
103
+ model.persona_id = 1 # 0 none, 1 analyst, 2 skeptic
104
+ """
105
+ import json
106
+ from typing import Optional
107
+
108
+ import torch
109
+ import torch.nn as nn
110
+ import torch.nn.functional as F
111
+ from transformers import PreTrainedModel, PretrainedConfig
112
+ from transformers.modeling_outputs import CausalLMOutputWithPast
113
+ '''
114
+ lines = [l for l in src.splitlines() if not l.startswith("from .config")]
115
+ cut = next(i for i, l in enumerate(lines) if l.startswith("class RMSNorm"))
116
+ imports_part = "\n".join(lines[:cut])
117
+ body = "\n".join(lines[cut:])
118
+
119
+ wrapper = '''
120
+
121
+ class TinyLiquidForCausalLM(PreTrainedModel):
122
+ """transformers-compatible wrapper around TinyLiquid."""
123
+
124
+ config_class = TinyLiquidConfig
125
+ _tied_weights_keys = []
126
+ all_tied_weights_keys = {}
127
+
128
+ def __init__(self, config: TinyLiquidConfig):
129
+ super().__init__(config)
130
+ self.model = TinyLiquid(config)
131
+ self.persona_id = 1 # default analyst; 0 none, 2 skeptic
132
+
133
+ def forward(
134
+ self,
135
+ input_ids: torch.Tensor,
136
+ attention_mask: Optional[torch.Tensor] = None,
137
+ labels: Optional[torch.Tensor] = None,
138
+ persona_ids: Optional[torch.Tensor] = None,
139
+ **kwargs,
140
+ ) -> CausalLMOutputWithPast:
141
+ if persona_ids is None:
142
+ persona_ids = torch.tensor([self.persona_id], device=input_ids.device)
143
+ logits = self.model(input_ids, persona_ids=persona_ids)
144
+ loss = None
145
+ if labels is not None:
146
+ shift_logits = logits[:, :-1, :].contiguous()
147
+ shift_labels = labels[:, 1:].contiguous()
148
+ loss = F.cross_entropy(
149
+ shift_logits.view(-1, shift_logits.size(-1)),
150
+ shift_labels.view(-1), ignore_index=-100)
151
+ return CausalLMOutputWithPast(
152
+ loss=loss, logits=logits, past_key_values=None, hidden_states=None)
153
+
154
+ def prepare_inputs_for_generation(self, input_ids, **kwargs):
155
+ return {"input_ids": input_ids, "persona_ids": kwargs.get("persona_ids")}
156
+ '''
157
+ cfg_class = _emit_config_class(cfg_arch)
158
+ dst.write_text(header + "\n\n" + cfg_class + "\n\n" + imports_part + "\n" + body + wrapper,
159
+ encoding="utf-8")
160
+ print(f"wrote {dst}")
161
+
162
+
163
+ def build_config(sd_cfg: dict, vocab_size: int) -> TinyLiquidConfig:
164
+ cfg = TinyLiquidConfig(vocab_size=vocab_size,
165
+ **{k: v for k, v in sd_cfg.items() if k != "vocab_size"})
166
+ return cfg
167
+
168
+
169
+ def main():
170
+ ap = argparse.ArgumentParser()
171
+ ap.add_argument("--ckpt", default="ckpt/dpo")
172
+ ap.add_argument("--out", default="hf_repo")
173
+ ap.add_argument("--tok", default="data/tokenizer.json")
174
+ args = ap.parse_args()
175
+
176
+ out = Path(args.out)
177
+ (out / "quantized").mkdir(parents=True, exist_ok=True)
178
+
179
+ ckpt = latest_ckpt(args.ckpt)
180
+ assert ckpt, f"no checkpoints in {args.ckpt}"
181
+ sd = torch.load(ckpt, map_location="cpu")
182
+ tok = load_tokenizer(args.tok)
183
+ cfg = build_config(sd["config"], tok.get_vocab_size())
184
+ model = TinyLiquid(cfg)
185
+ model.load_state_dict(sd["model"])
186
+ model.eval()
187
+
188
+ tensors = {"model." + k: v.detach().contiguous() for k, v in model.state_dict().items()}
189
+ save_file(tensors, out / "model.safetensors")
190
+ print(f"wrote {out / 'model.safetensors'} ({sum(v.numel() for v in tensors.values())} params)")
191
+
192
+ qs = quantize_q8(model)
193
+ flat = {}
194
+ for name, st in qs.items():
195
+ flat[name + ".q"] = st["q"].contiguous()
196
+ flat[name + ".scale"] = st["scale"].contiguous()
197
+ save_file(flat, out / "quantized" / "q8.safetensors")
198
+ print(f"wrote {out / 'quantized' / 'q8.safetensors'} ({len(qs)} linear layers)")
199
+
200
+ import dataclasses
201
+ hf_cfg = dataclasses.asdict(cfg)
202
+ hf_cfg.update({
203
+ "architectures": ["TinyLiquidForCausalLM"],
204
+ "model_type": "tiny_liquid",
205
+ "auto_map": {"AutoConfig": "modeling_tinyliquid.TinyLiquidConfig",
206
+ "AutoModelForCausalLM": "modeling_tinyliquid.TinyLiquidForCausalLM"},
207
+ "torch_dtype": "float32",
208
+ "transformers_version": "4.x",
209
+ "persona_tokens": PERSONA_TOKENS,
210
+ })
211
+ (out / "config.json").write_text(json.dumps(hf_cfg, indent=2), encoding="utf-8")
212
+
213
+ shutil.copy(args.tok, out / "tokenizer.json")
214
+ special = {}
215
+ for name in ["<|endoftext|>", "<|user|>", "<|assistant|>", "<|scratchpad|>",
216
+ "<|final|>", "<|analyst|>", "<|skeptic|>"]:
217
+ special[name] = tok.token_to_id(name)
218
+ tok_cfg = {
219
+ "tokenizer_class": "PreTrainedTokenizerFast",
220
+ "model_max_length": cfg.max_seq_len,
221
+ "bos_token": None,
222
+ "eos_token": "<|endoftext|>",
223
+ "unk_token": None,
224
+ "pad_token": "<|endoftext|>",
225
+ "added_tokens_decoder": {str(i): {"content": n, "special": True} for n, i in special.items()},
226
+ "chat_template": (
227
+ "{% for m in messages %}"
228
+ "{% if m['role'] == 'system' %}<|analyst|>{% endif %}"
229
+ "{% if m['role'] == 'user' %}<|user|>{{ m['content'] }}<|assistant|>{% endif %}"
230
+ "{% if m['role'] == 'assistant' %}{{ m['content'] }}<|endoftext|>{% endif %}"
231
+ "{% endfor %}"
232
+ ),
233
+ }
234
+ (out / "tokenizer_config.json").write_text(json.dumps(tok_cfg, indent=2), encoding="utf-8")
235
+ smap = {k: {"content": v, "lstrip": False, "rstrip": False, "single_word": False}
236
+ for k, v in special.items()}
237
+ (out / "special_tokens_map.json").write_text(json.dumps(smap, indent=2), encoding="utf-8")
238
+
239
+ gen = {"max_new_tokens": 220, "temperature": 0.6, "top_k": 40,
240
+ "repetition_penalty": 1.4, "do_sample": True}
241
+ (out / "generation_config.json").write_text(json.dumps(gen, indent=2), encoding="utf-8")
242
+
243
+ gen_modeling_file(out / "modeling_tinyliquid.py",
244
+ Path("model/tiny_liquid.py"), Path("model/config.py"))
245
+ print(f"export complete -> {out}")
246
+
247
+
248
+ if __name__ == "__main__":
249
+ main()
hf_repo_v8/README.md ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language:
3
+ - en
4
+ license: apache-2.0
5
+ tags:
6
+ - tiny-model
7
+ - liquid-architecture
8
+ - non-transformer
9
+ - on-device
10
+ - forensic
11
+ - fact-checking
12
+ - osint
13
+ - research
14
+ - edge
15
+ - cpu
16
+ - pytorch
17
+ - gguf
18
+ base_model: null
19
+ pipeline_tag: text-generation
20
+ library_name: custom
21
+ widget:
22
+ - text: "Verify: 'Crime doubled because of the new policy.' The report shows incidents rose from 1,000 to 2,000 while reporting methods changed."
23
+ output:
24
+ text: "<|scratchpad|>SOP 01 decompose: ... <|final|>Incidents rose - true. Attribution 'because of the policy': overclaim - methodology changed. Confidence: HIGH on the numbers, LOW on the cause."
25
+ ---
26
+
27
+ # TinyLiquid Analyst
28
+
29
+ **7.8M parameters. Liquid (non-transformer) architecture. Trained start-to-finish on an 8-core ARM laptop, no GPU.**
30
+
31
+ TinyLiquid Analyst is a tiny causal language model built from scratch for forensic analysis:
32
+ claim verification, cross-source discrepancy detection, pattern finding, timeline reconstruction,
33
+ historical truth auditing, politics/spin analysis, source triage — and authorized dark-web/OSINT
34
+ research. It speaks in a hyper-logical analyst persona and applies numbered Standard Operating
35
+ Procedures (SOPs) instead of freewheeling. A skeptic persona attacks its own conclusions (dual-mind).
36
+
37
+ It is deliberately small: the whole point is that serious research tooling should run on the
38
+ device you already own. This page proves a ~8M-param model, trained on commodity ARM hardware,
39
+ can hold a coherent analyst voice, follow procedures, and produce structured verdicts.
40
+
41
+ ## Quickstart (transformers, trust_remote_code)
42
+
43
+ ```python
44
+ from transformers import AutoModelForCausalLM, AutoTokenizer
45
+
46
+ tok = AutoTokenizer.from_pretrained("ferrell-synthetic-intelligence/tiny-liquid-analyst")
47
+ model = AutoModelForCausalLM.from_pretrained(
48
+ "ferrell-synthetic-intelligence/tiny-liquid-analyst", trust_remote_code=True)
49
+ model.persona_id = 1 # 0 none, 1 analyst, 2 skeptic
50
+
51
+ prompt = "<|analyst|><|user|>Verify: 'The bridge was painted in 2019 and never repainted.' Records show a 2022 repaint permit.<|assistant|>"
52
+ ids = tok(prompt, return_tensors="pt").input_ids
53
+ out = model.generate(ids, max_new_tokens=120, temperature=0.6, top_k=40,
54
+ repetition_penalty=1.4, do_sample=True)
55
+ print(tok.decode(out[0][ids.shape[1]:], skip_special_tokens=False))
56
+ ```
57
+
58
+ ## Native runtime + tools
59
+
60
+ - `generate.py` — interactive chat (persona: analyst / skeptic / none)
61
+ - `research/agent.py` — SOP-driven procedural agent with step ledger + procedure audit
62
+ - `research/room.py` — interactive "library" environment (RETRIEVE/READ/NOTE/VERDICT)
63
+ - `research/analyst.py` — dual-mind analysis pipeline
64
+ - `research/crawl.py` + `research/index.py` — authorized clearnet/Tor crawl + TF-IDF retrieval
65
+ - `tui/analyst.py` + `tui/cli.py` — purpose-built terminal UI / headless client
66
+ - `demo/serve.py` — zero-dependency local web demo/API
67
+ - `model/gguf_runtime.py` — loads the Q8 GGUF natively on-device
68
+
69
+ ```bash
70
+ export PYTHONPATH=$PWD
71
+ .venv/bin/python generate.py --ckpt ckpt/v8_lora/best.pt --persona analyst
72
+ .venv/bin/python research/agent.py --case "Verify: ..." --sop claim_verification --ckpt ckpt/dpo
73
+ .venv/bin/python demo/serve.py --ckpt ckpt/v8_lora/best.pt --port 8080
74
+ ```
75
+
76
+ ## Architecture (own design, non-transformer)
77
+
78
+ - Stacked **liquid blocks**: a basis-expansion layer (group-normed expansion with SiLU and a
79
+ sigmoid forget gate, weight-tied projection) with a **causal liquid recurrence**
80
+ `state_t = forget * state_{t-1} + expansion_t` — the model's working memory is a learned gate,
81
+ not attention.
82
+ - Gated MLP (dense or mixture-of-experts) per block; rotary positions; RMSNorm; tied embeddings.
83
+ - Learned **persona vectors** (analyst / skeptic) condition style and role at inference.
84
+ - Own byte-level BPE tokenizer (vocab 8192), HF-format `tokenizer.json`.
85
+
86
+ | Hyperparameter | Value |
87
+ |---|---|
88
+ | Params | 7,788,288 |
89
+ | d_model / blocks | 320 / 6 |
90
+ | Basis expansion | 16 x 4 per block |
91
+ | MLP | gated, 2x hidden |
92
+ | Max context | 1024 |
93
+ | Vocab | 8192 |
94
+ | Personas | 3 (none, analyst, skeptic) |
95
+
96
+ ## Training (all on 8-core ARM, no GPU)
97
+
98
+ 1. **Continued pretraining** — full TinyStoriesV2-GPT4 corpus (~528M tokens, 9k steps): takes the base
99
+ from story-fluent to solid language conditioning.
100
+ 2. **Domain adaptation** — ~2M tokens of analyst/forensic/SOP text (claim-verification chat pairs,
101
+ playbooks, distill sets) so the analyst register is baked into the base's conditional distribution.
102
+ 3. **LoRA SFT** — 8,941 examples (forensic claim analysis + SOP + chat + raw replay), 369k trainable
103
+ params, KL-anchored to the domain-adapted base, masked assistant-token loss, TinyStories PPL guard.
104
+
105
+ ## Metrics
106
+
107
+ | Metric | Value |
108
+ |---|---|
109
+ | Parameters | 7,788,288 |
110
+ | Val loss (TinyStories valid) | 3.3579 |
111
+ | Val perplexity | 28.73 |
112
+ | Forensic probe hits | 9/50 |
113
+ | Probe accuracy | 0.18 |
114
+ | Generation speed | 19.9 tok/s (8-core ARM, no GPU) |
115
+
116
+
117
+ ### Generation samples (final checkpoint)
118
+
119
+ - **analyst | Hi, who are you?**
120
+ > Identify what is being asked and what would need to be true for popular wrong answers; check the baseline facts.
121
+ - **analyst | Explain your method for checking a claim.**
122
+ > Compare claim against evidence: identify the factual assertion; check who verified the claim and how; it requires a source beyond the claim itself. Verdict: true statement. Confidence: MEDIUM.
123
+ - **skeptic | Attack this conclusion: 'Three failures in one week with vans nearby is delibera**
124
+ > Checklist: (1) identify the factual assertion; (2) compare against known records; (3) note missing context. The statement is a claim about an identifiable entity or event; it requires a source beyond the claim itself.
125
+
126
+ ## Files
127
+
128
+ | File | Size | Notes |
129
+ |---|---|---|
130
+ | `model.safetensors` | 31.2 MB | fp32, native weights |
131
+ | `quantized/q8.safetensors` | 5.0 MB | our Q8 int8 storage (near-lossless) |
132
+ | `tiny-liquid-q8.gguf` | 10.7 MB | GGUF container (Q8_0), loadable via `model/gguf_runtime.py`; llama.cpp kernels for arch `tinyliq` are community work-in-progress |
133
+ | `tokenizer.json` | 0.6 MB | HF-format byte-level BPE |
134
+ | `modeling_tinyliquid.py` | — | self-contained trust_remote_code model |
135
+
136
+ ## Intended use
137
+
138
+ - Claim verification and fact-checking support (decision support, never a verdict).
139
+ - Cross-source discrepancy and pattern analysis for research/OSINT work.
140
+ - Historical/political narrative auditing and source triage.
141
+ - **Authorized dark-web/deep-web research only**: the tooling refuses illegal categories,
142
+ rate-limits, and documents chain of custody. Never for purchases, credentials/CSAM, malware,
143
+ or bypassing access controls.
144
+
145
+ ## Limitations
146
+
147
+ - 7.8M params: strong for its size, but not competitive with billion-param models on open-ended
148
+ knowledge or long reasoning. It is a *protocol machine*: give it a procedure and material,
149
+ and it performs; free-associate and it drifts.
150
+ - Trained on a narrow corpus (TinyStories + curated forensic data); world knowledge is limited.
151
+ - English only.
152
+ - Outputs are analysis aids; always verify against primary sources before acting.
153
+
154
+ ## Guardrails
155
+
156
+ Research/OSINT use only. The crawler blocks obviously illegal categories, rate-limits, and is
157
+ documented as authorized research tooling. The model outputs are decision support, never a
158
+ verdict, and primary-source checks are always required.
159
+
160
+ ## License
161
+
162
+ Apache-2.0 (this repository and model weights).
hf_repo_v8/config.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "vocab_size": 8192,
3
+ "d_model": 320,
4
+ "n_blocks": 6,
5
+ "basis_n": 16,
6
+ "basis_b": 4,
7
+ "mlp_ratio": 2,
8
+ "num_experts": 0,
9
+ "num_experts_per_tok": 2,
10
+ "expert_hidden": 0,
11
+ "num_personas": 3,
12
+ "rope_theta": 10000.0,
13
+ "max_seq_len": 1024,
14
+ "norm_eps": 1e-06,
15
+ "tie_embeddings": true,
16
+ "architectures": [
17
+ "TinyLiquidForCausalLM"
18
+ ],
19
+ "model_type": "tiny_liquid",
20
+ "auto_map": {
21
+ "AutoConfig": "modeling_tinyliquid.TinyLiquidConfig",
22
+ "AutoModelForCausalLM": "modeling_tinyliquid.TinyLiquidForCausalLM"
23
+ },
24
+ "torch_dtype": "float32",
25
+ "transformers_version": "4.x",
26
+ "persona_tokens": {
27
+ "0": "<|endoftext|>",
28
+ "1": "<|analyst|>",
29
+ "2": "<|skeptic|>"
30
+ }
31
+ }
hf_repo_v8/generation_config.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "max_new_tokens": 220,
3
+ "temperature": 0.6,
4
+ "top_k": 40,
5
+ "repetition_penalty": 1.4,
6
+ "do_sample": true
7
+ }
hf_repo_v8/model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:328c29fc14d1446e6569137885f13eed96329514de41f6bda81aae9472192026
3
+ size 31159256
hf_repo_v8/modeling_tinyliquid.py ADDED
@@ -0,0 +1,390 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """TinyLiquid for Hugging Face (trust_remote_code).
2
+
3
+ Self-contained copy of the TinyLiquid non-transformer architecture
4
+ (basis-expansion liquid blocks with causal recurrence + gated MLP), wrapped
5
+ for transformers-compatible loading.
6
+
7
+ Load with:
8
+ from transformers import AutoModelForCausalLM, AutoTokenizer
9
+ tok = AutoTokenizer.from_pretrained("your-org/tiny-liquid-analyst")
10
+ model = AutoModelForCausalLM.from_pretrained(
11
+ "your-org/tiny-liquid-analyst", trust_remote_code=True)
12
+ model.persona_id = 1 # 0 none, 1 analyst, 2 skeptic
13
+ """
14
+ import json
15
+ from typing import Optional
16
+
17
+ import torch
18
+ import torch.nn as nn
19
+ import torch.nn.functional as F
20
+ from transformers import PreTrainedModel, PretrainedConfig
21
+ from transformers.modeling_outputs import CausalLMOutputWithPast
22
+
23
+
24
+ class TinyLiquidConfig(PretrainedConfig):
25
+ """Architecture config for TinyLiquid (HF-compatible)."""
26
+
27
+ model_type = "tiny_liquid"
28
+
29
+ def __init__(
30
+ self,
31
+ vocab_size=8192, d_model=320, n_blocks=6, basis_n=16, basis_b=4, mlp_ratio=2, num_experts=0, num_experts_per_tok=2, expert_hidden=0, num_personas=3, rope_theta=10000.0, max_seq_len=1024, norm_eps=1e-06, tie_embeddings=True,
32
+ **kwargs,
33
+ ):
34
+ super().__init__(**kwargs)
35
+ self.vocab_size = vocab_size
36
+ self.d_model = d_model
37
+ self.n_blocks = n_blocks
38
+ self.basis_n = basis_n
39
+ self.basis_b = basis_b
40
+ self.mlp_ratio = mlp_ratio
41
+ self.num_experts = num_experts
42
+ self.num_experts_per_tok = num_experts_per_tok
43
+ self.expert_hidden = expert_hidden
44
+ self.num_personas = num_personas
45
+ self.rope_theta = rope_theta
46
+ self.max_seq_len = max_seq_len
47
+ self.norm_eps = norm_eps
48
+ self.tie_embeddings = tie_embeddings
49
+
50
+ # --- standard aliases used by transformers internals ---
51
+ @property
52
+ def num_hidden_layers(self):
53
+ return self.n_blocks
54
+
55
+ @property
56
+ def hidden_size(self):
57
+ return self.d_model
58
+
59
+ @property
60
+ def num_attention_heads(self):
61
+ return 1
62
+
63
+ @property
64
+ def max_position_embeddings(self):
65
+ return self.max_seq_len
66
+
67
+
68
+ """TinyLiquid -- our own tiny liquid-architecture language model.
69
+
70
+ Non-transformer design (no attention):
71
+ * liquid blocks, each = basis expansion layer + gated MLP (dense or MoE),
72
+ both with a sigmoid forget gate, residual connections, RMSNorm.
73
+ * basis expansion: expand d -> N*B, group-norm within each basis block,
74
+ SiLU, forget gate, then a weight-tied projection back to d.
75
+ * learned persona vectors condition the style/role of the model.
76
+ * rotary position embeddings, tied input/output embeddings.
77
+ """
78
+
79
+ import math
80
+ from functools import lru_cache
81
+
82
+ SCAN_CHUNK = 128 # log-space scan chunk size (benchmarked fastest)
83
+
84
+ import torch
85
+ import torch.nn as nn
86
+ import torch.nn.functional as F
87
+
88
+
89
+
90
+ class RMSNorm(nn.Module):
91
+ def __init__(self, dim: int, eps: float = 1e-6):
92
+ super().__init__()
93
+ self.weight = nn.Parameter(torch.ones(dim))
94
+ self.eps = eps
95
+
96
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
97
+ rms = x.pow(2).mean(-1, keepdim=True).add(self.eps).rsqrt()
98
+ return x * rms * self.weight
99
+
100
+
101
+ def _rotate_half(x: torch.Tensor) -> torch.Tensor:
102
+ x1, x2 = x.chunk(2, dim=-1)
103
+ return torch.cat((-x2, x1), dim=-1)
104
+
105
+
106
+ @lru_cache(maxsize=8)
107
+ def _rope_freqs(seq_len: int, dim: int, theta: float, device: str, dtype: torch.dtype):
108
+ half = dim // 2
109
+ inv_freq = 1.0 / (theta ** (torch.arange(0, half, device=device, dtype=torch.float32) / half))
110
+ t = torch.arange(seq_len, device=device, dtype=torch.float32)
111
+ freqs = torch.outer(t, inv_freq) # (seq, half)
112
+ cos = freqs.cos().to(dtype)
113
+ sin = freqs.sin().to(dtype)
114
+ return cos, sin
115
+
116
+
117
+ def apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
118
+ x = x.float()
119
+ x1, x2 = x[..., : x.shape[-1] // 2], x[..., x.shape[-1] // 2 :]
120
+ x_rope = torch.cat((x1 * cos - x2 * sin, x2 * cos + x1 * sin), dim=-1)
121
+ return x_rope.to(x.dtype if hasattr(x, "dtype") else torch.float32)
122
+
123
+
124
+ class BasisExpansion(nn.Module):
125
+ """Liquid-style expansion: hidden -> N*B, group-norm over B, SiLU,
126
+ forget gate, weight-tied projection back to hidden."""
127
+
128
+ def __init__(self, cfg: TinyLiquidConfig):
129
+ super().__init__()
130
+ d = cfg.d_model
131
+ self.n, self.b = cfg.basis_n, cfg.basis_b
132
+ self.expand = cfg.basis_n * cfg.basis_b
133
+ # in and forget-gate weights; output projection reuses w (tying)
134
+ self.w = nn.Parameter(torch.empty(self.expand, d))
135
+ self.w_forget = nn.Parameter(torch.empty(self.expand, d))
136
+ self.gn = nn.GroupNorm(self.n, self.expand)
137
+ self.reset_parameters()
138
+
139
+ def reset_parameters(self):
140
+ nn.init.normal_(self.w, std=0.02 / math.sqrt(self.expand))
141
+ nn.init.normal_(self.w_forget, std=0.02 / math.sqrt(self.expand))
142
+
143
+ def forward(self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
144
+ xr = apply_rope(x, cos, sin) # (b, s, d)
145
+ e = xr @ self.w.t() # (b, s, N*B)
146
+ e = e.transpose(1, 2) # (b, N*B, s) for groupnorm
147
+ e = F.silu(self.gn(e))
148
+ e = e.transpose(1, 2)
149
+ f = torch.sigmoid(xr @ self.w_forget.t()) # forget/decay gate
150
+ # Causal liquid recurrence: state_t = f_t * state_{t-1} + e_t.
151
+ # Chunked log-space scan: exact math, bounded range per chunk, no
152
+ # catastrophic cancellation, and far fewer Python iterations.
153
+ G = torch.cumsum(torch.log(f.clamp_min(1e-12)), dim=1).double()
154
+ b, s, E = e.shape
155
+ h = torch.empty_like(e)
156
+ state = torch.zeros(b, E, dtype=torch.float64)
157
+ chunk = SCAN_CHUNK
158
+ for start in range(0, s, chunk):
159
+ end = min(start + chunk, s)
160
+ base = G[:, start - 1:start] if start > 0 else G[:, :1]
161
+ g_rel = G[:, start:end] - base # <= 0, non-increasing
162
+ m = g_rel[:, -1:] # min within chunk
163
+ # Shift exponents by the chunk min so every exp() argument <= 0:
164
+ # fully stable for any gate saturation (no exp overflow).
165
+ S = torch.cumsum(e[:, start:end].double() * torch.exp(-(g_rel - m)), dim=1)
166
+ hc = torch.exp(g_rel - m) * (state.unsqueeze(1) * torch.exp(m) + S)
167
+ h[:, start:end] = hc.float()
168
+ state = hc[:, -1]
169
+ return h @ self.w # weight-tied projection
170
+
171
+
172
+ class GatedMLP(nn.Module):
173
+ """Gated MLP with sigmoid forget gate (dense)."""
174
+
175
+ def __init__(self, d: int, h: int):
176
+ super().__init__()
177
+ self.up = nn.Linear(d, h, bias=False)
178
+ self.gate = nn.Linear(d, h, bias=False)
179
+ self.forget = nn.Linear(d, h, bias=False)
180
+ self.down = nn.Linear(h, d, bias=False)
181
+ self.reset_parameters()
182
+
183
+ def reset_parameters(self):
184
+ for w in (self.up, self.gate, self.forget):
185
+ nn.init.normal_(w.weight, std=0.02 / math.sqrt(w.weight.shape[0]))
186
+ nn.init.normal_(self.down.weight, std=0.02 / math.sqrt(self.down.weight.shape[1]))
187
+
188
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
189
+ h = F.silu(self.gate(x)) * self.up(x)
190
+ h = h * torch.sigmoid(self.forget(x))
191
+ return self.down(h)
192
+
193
+
194
+ class ExpertMLP(GatedMLP):
195
+ pass
196
+
197
+
198
+ class MoEMLP(nn.Module):
199
+ """Mixture-of-experts gated MLP: top-k routing over small experts."""
200
+
201
+ def __init__(self, cfg: TinyLiquidConfig):
202
+ super().__init__()
203
+ d = cfg.d_model
204
+ h = cfg.expert_hidden or (cfg.mlp_ratio * d // 2)
205
+ self.n_experts = cfg.num_experts
206
+ self.k = cfg.num_experts_per_tok
207
+ self.router = nn.Linear(d, cfg.num_experts, bias=False)
208
+ self.experts = nn.ModuleList([ExpertMLP(d, h) for _ in range(cfg.num_experts)])
209
+ nn.init.normal_(self.router.weight, std=0.02 / math.sqrt(d))
210
+
211
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
212
+ b, s, d = x.shape
213
+ logits = self.router(x).float() # (b, s, E)
214
+ topk = torch.topk(logits, self.k, dim=-1)
215
+ weights = F.softmax(topk.values, dim=-1) # (b, s, k)
216
+ flat = x.reshape(-1, d) # (b*s, d)
217
+ idx = topk.indices.reshape(-1, self.k) # (b*s, k)
218
+ out = torch.zeros_like(flat)
219
+ flat_weights = weights.reshape(-1, self.k)
220
+ for j in range(self.k):
221
+ e_idx = idx[:, j] # (b*s,)
222
+ wj = flat_weights[:, j] # (b*s,)
223
+ for e in range(self.n_experts):
224
+ mask = e_idx == e
225
+ if mask.any():
226
+ out[mask] += wj[mask].unsqueeze(1) * self.experts[e](flat[mask])
227
+ return out.view(b, s, d)
228
+
229
+
230
+ class LiquidBlock(nn.Module):
231
+ def __init__(self, cfg: TinyLiquidConfig):
232
+ super().__init__()
233
+ d = cfg.d_model
234
+ self.norm1 = RMSNorm(d, cfg.norm_eps)
235
+ self.basis = BasisExpansion(cfg)
236
+ self.norm2 = RMSNorm(d, cfg.norm_eps)
237
+ if cfg.num_experts > 0:
238
+ self.mlp = MoEMLP(cfg)
239
+ else:
240
+ self.mlp = GatedMLP(d, cfg.mlp_ratio * d)
241
+
242
+ def forward(self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
243
+ x = x + self.basis(self.norm1(x), cos, sin)
244
+ x = x + self.mlp(self.norm2(x))
245
+ return x
246
+
247
+
248
+ class TinyLiquid(nn.Module):
249
+ def __init__(self, cfg: TinyLiquidConfig):
250
+ super().__init__()
251
+ self.cfg = cfg
252
+ self.tok_emb = nn.Embedding(cfg.vocab_size, cfg.d_model)
253
+ self.persona_emb = nn.Embedding(cfg.num_personas, cfg.d_model)
254
+ self.blocks = nn.ModuleList([LiquidBlock(cfg) for _ in range(cfg.n_blocks)])
255
+ self.norm_out = RMSNorm(cfg.d_model, cfg.norm_eps)
256
+ if cfg.tie_embeddings:
257
+ self.lm_head = None # tied below
258
+ else:
259
+ self.lm_head = nn.Linear(cfg.d_model, cfg.vocab_size, bias=False)
260
+ self.reset_parameters()
261
+
262
+ def reset_parameters(self):
263
+ nn.init.normal_(self.tok_emb.weight, std=0.02)
264
+ nn.init.normal_(self.persona_emb.weight, std=0.02)
265
+
266
+ def forward(
267
+ self,
268
+ ids: torch.Tensor,
269
+ persona_ids: torch.Tensor | None = None,
270
+ ) -> torch.Tensor:
271
+ cfg = self.cfg
272
+ x = self.tok_emb(ids)
273
+ if persona_ids is not None:
274
+ x = x + self.persona_emb(persona_ids).unsqueeze(1)
275
+ seq = ids.shape[1]
276
+ theta = cfg.rope_theta
277
+ cos, sin = _rope_freqs(seq, cfg.d_model, theta, str(ids.device), x.dtype)
278
+ for blk in self.blocks:
279
+ x = blk(x, cos, sin)
280
+ x = self.norm_out(x)
281
+ if cfg.tie_embeddings:
282
+ logits = x @ self.tok_emb.weight.t()
283
+ else:
284
+ logits = self.lm_head(x)
285
+ return logits
286
+
287
+ @torch.no_grad()
288
+ def encode(self, ids: torch.Tensor, persona_ids: torch.Tensor | None = None) -> torch.Tensor:
289
+ """Final hidden states (b, s, d) after norm_out; no LM head."""
290
+ cfg = self.cfg
291
+ x = self.tok_emb(ids)
292
+ if persona_ids is not None:
293
+ x = x + self.persona_emb(persona_ids).unsqueeze(1)
294
+ seq = ids.shape[1]
295
+ cos, sin = _rope_freqs(seq, cfg.d_model, cfg.rope_theta, str(ids.device), x.dtype)
296
+ for blk in self.blocks:
297
+ x = blk(x, cos, sin)
298
+ return self.norm_out(x)
299
+
300
+ def num_params(self) -> int:
301
+ return sum(p.numel() for p in self.parameters())
302
+
303
+ @torch.no_grad()
304
+ def generate(
305
+ self,
306
+ tokenizer,
307
+ prompt_ids,
308
+ persona_id=0,
309
+ max_new=200,
310
+ temperature=0.8,
311
+ top_k=40,
312
+ repetition_penalty=1.2,
313
+ no_repeat_ngram_size=4,
314
+ on_token=None,
315
+ ):
316
+ self.eval()
317
+ ids = torch.tensor([prompt_ids], dtype=torch.long)
318
+ stop_ids = {
319
+ tok_id for tok_id in (
320
+ tokenizer.token_to_id("<|endoftext|>"),
321
+ tokenizer.token_to_id("<|user|>"),
322
+ tokenizer.token_to_id("<|assistant|>"),
323
+ )
324
+ if tok_id is not None
325
+ }
326
+ for _ in range(max_new):
327
+ window = ids[:, -self.cfg.max_seq_len :]
328
+ logits = self(window, persona_ids=torch.tensor([persona_id]) if persona_id else None)
329
+ logits = logits[:, -1, :] / max(temperature, 1e-6)
330
+ if repetition_penalty > 1.0 and ids.shape[1] > 8:
331
+ seen = ids[0, -64:].unique()
332
+ logits[:, seen] /= repetition_penalty
333
+ if no_repeat_ngram_size > 0 and ids.shape[1] >= no_repeat_ngram_size:
334
+ seq = ids[0].tolist()
335
+ n = no_repeat_ngram_size
336
+ prefix = tuple(seq[-(n - 1):])
337
+ banned = set()
338
+ for i in range(len(seq) - n + 1):
339
+ if tuple(seq[i:i + n - 1]) == prefix:
340
+ banned.add(seq[i + n - 1])
341
+ if banned:
342
+ logits[:, list(banned)] = -float("inf")
343
+ if top_k > 0:
344
+ v, _ = torch.topk(logits, top_k)
345
+ logits[logits < v[:, -1:]] = -float("inf")
346
+ probs = F.softmax(logits.float(), dim=-1)
347
+ nxt = torch.multinomial(probs, 1)
348
+ ids = torch.cat([ids, nxt], dim=1)
349
+ nxt_id = int(nxt.item())
350
+ if on_token is not None:
351
+ on_token(nxt_id)
352
+ if nxt_id in stop_ids:
353
+ break
354
+ return ids[0].tolist()
355
+
356
+ class TinyLiquidForCausalLM(PreTrainedModel):
357
+ """transformers-compatible wrapper around TinyLiquid."""
358
+
359
+ config_class = TinyLiquidConfig
360
+ _tied_weights_keys = []
361
+ all_tied_weights_keys = {}
362
+
363
+ def __init__(self, config: TinyLiquidConfig):
364
+ super().__init__(config)
365
+ self.model = TinyLiquid(config)
366
+ self.persona_id = 1 # default analyst; 0 none, 2 skeptic
367
+
368
+ def forward(
369
+ self,
370
+ input_ids: torch.Tensor,
371
+ attention_mask: Optional[torch.Tensor] = None,
372
+ labels: Optional[torch.Tensor] = None,
373
+ persona_ids: Optional[torch.Tensor] = None,
374
+ **kwargs,
375
+ ) -> CausalLMOutputWithPast:
376
+ if persona_ids is None:
377
+ persona_ids = torch.tensor([self.persona_id], device=input_ids.device)
378
+ logits = self.model(input_ids, persona_ids=persona_ids)
379
+ loss = None
380
+ if labels is not None:
381
+ shift_logits = logits[:, :-1, :].contiguous()
382
+ shift_labels = labels[:, 1:].contiguous()
383
+ loss = F.cross_entropy(
384
+ shift_logits.view(-1, shift_logits.size(-1)),
385
+ shift_labels.view(-1), ignore_index=-100)
386
+ return CausalLMOutputWithPast(
387
+ loss=loss, logits=logits, past_key_values=None, hidden_states=None)
388
+
389
+ def prepare_inputs_for_generation(self, input_ids, **kwargs):
390
+ return {"input_ids": input_ids, "persona_ids": kwargs.get("persona_ids")}
hf_repo_v8/quantized/q8.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2967b63eb5b0d29b336cc7d61608ab1a4c29dc5db41d7b859215f62015c5f2a5
3
+ size 4973440
hf_repo_v8/special_tokens_map.json ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "<|endoftext|>": {
3
+ "content": 0,
4
+ "lstrip": false,
5
+ "rstrip": false,
6
+ "single_word": false
7
+ },
8
+ "<|user|>": {
9
+ "content": 1,
10
+ "lstrip": false,
11
+ "rstrip": false,
12
+ "single_word": false
13
+ },
14
+ "<|assistant|>": {
15
+ "content": 2,
16
+ "lstrip": false,
17
+ "rstrip": false,
18
+ "single_word": false
19
+ },
20
+ "<|scratchpad|>": {
21
+ "content": 3,
22
+ "lstrip": false,
23
+ "rstrip": false,
24
+ "single_word": false
25
+ },
26
+ "<|final|>": {
27
+ "content": 4,
28
+ "lstrip": false,
29
+ "rstrip": false,
30
+ "single_word": false
31
+ },
32
+ "<|analyst|>": {
33
+ "content": 5,
34
+ "lstrip": false,
35
+ "rstrip": false,
36
+ "single_word": false
37
+ },
38
+ "<|skeptic|>": {
39
+ "content": 6,
40
+ "lstrip": false,
41
+ "rstrip": false,
42
+ "single_word": false
43
+ }
44
+ }
hf_repo_v8/tiny-liquid-q8.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b264e8ec80a1e7312ffc64f371679fb1fb7956440cedd0df6cfdcc95b91814e2
3
+ size 10742208
hf_repo_v8/tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
hf_repo_v8/tokenizer_config.json ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "tokenizer_class": "PreTrainedTokenizerFast",
3
+ "model_max_length": 1024,
4
+ "bos_token": null,
5
+ "eos_token": "<|endoftext|>",
6
+ "unk_token": null,
7
+ "pad_token": "<|endoftext|>",
8
+ "added_tokens_decoder": {
9
+ "0": {
10
+ "content": "<|endoftext|>",
11
+ "special": true
12
+ },
13
+ "1": {
14
+ "content": "<|user|>",
15
+ "special": true
16
+ },
17
+ "2": {
18
+ "content": "<|assistant|>",
19
+ "special": true
20
+ },
21
+ "3": {
22
+ "content": "<|scratchpad|>",
23
+ "special": true
24
+ },
25
+ "4": {
26
+ "content": "<|final|>",
27
+ "special": true
28
+ },
29
+ "5": {
30
+ "content": "<|analyst|>",
31
+ "special": true
32
+ },
33
+ "6": {
34
+ "content": "<|skeptic|>",
35
+ "special": true
36
+ }
37
+ },
38
+ "chat_template": "{% for m in messages %}{% if m['role'] == 'system' %}<|analyst|>{% endif %}{% if m['role'] == 'user' %}<|user|>{{ m['content'] }}<|assistant|>{% endif %}{% if m['role'] == 'assistant' %}{{ m['content'] }}<|endoftext|>{% endif %}{% endfor %}"
39
+ }
hf_upload.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Publish hf_repo to Hugging Face.
2
+
3
+ 1. Login: huggingface-cli login (or set HF_TOKEN)
4
+ 2. Run: .venv/bin/python hf_upload.py --repo YOUR_ORG/tiny-liquid-analyst
5
+
6
+ Creates the repo if missing, uploads every file in hf_repo/, and prints the
7
+ model page URL. Requires huggingface_hub (installed).
8
+ """
9
+
10
+ import argparse
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ from huggingface_hub import HfApi
15
+
16
+
17
+ def main():
18
+ ap = argparse.ArgumentParser()
19
+ ap.add_argument("--repo", default="YOUR_ORG/tiny-liquid-analyst",
20
+ help="HF repo id, e.g. yourname/tiny-liquid-analyst")
21
+ ap.add_argument("--dir", default="hf_repo")
22
+ ap.add_argument("--private", action="store_true", help="create as private repo")
23
+ args = ap.parse_args()
24
+
25
+ if args.repo.startswith("YOUR_ORG"):
26
+ sys.exit("set --repo to your HF repo id (e.g. yourname/tiny-liquid-analyst)")
27
+
28
+ api = HfApi()
29
+ try:
30
+ api.repo_info(args.repo)
31
+ print(f"repo exists: {args.repo}")
32
+ except Exception:
33
+ api.create_repo(args.repo, private=args.private, repo_type="model")
34
+ print(f"created repo: {args.repo}")
35
+
36
+ files = sorted(Path(args.dir).rglob("*"))
37
+ files = [f for f in files if f.is_file() and f.name not in (".DS_Store",)]
38
+ paths = [str(f) for f in files]
39
+ api.upload_folder(folder_path=args.dir, repo_id=args.repo, repo_type="model")
40
+ print(f"uploaded {len(paths)} files -> https://huggingface.co/{args.repo}")
41
+ print("next: edit the model card link in hf_repo/README.md and re-upload, or set it via the web UI.")
42
+
43
+
44
+ if __name__ == "__main__":
45
+ main()
logs/baseline_eval_tiny25m.log ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ == eval ckpt/tiny18m2/model_8500.pt ==
2
+
3
+ [p01] score 0.00 | verdict: funcspanmintrue | conf: cannot assess
4
+ [p02] score 0.00 | verdict: notminpisconfunsupparttrue | conf: cannot assess
5
+ [p03] score 0.00 | verdict: cmparttfixed | conf: cannot assess
6
+ [p04] score 0.00 | verdict: partmincisleantrue | conf: cannot assess
7
+ [p05] score 0.00 | verdict: funcspanminupponislealse | conf: canLHIGH
8
+ [p06] score 0.00 | verdict: cmpfixed | conf: cannot assess
9
+ [p07] score 0.00 | verdict: partminctrue | conf: cannot assess
10
+ [p08] score 0.00 | verdict: funcspaninmislealupponse | conf: canLHIGH
11
+ [p09] score 0.00 | verdict: funcspanminupponislealse | conf: canLHIGH
12
+ [p10] score 0.00 | verdict: cmpfixed | conf: cannot assess
13
+ [p11] score 0.00 | verdict: partcinfanmislealse | conf: canLHIGH
14
+ [p12] score 0.00 | verdict: funcspanmintrue | conf: cannot assess
15
+ [p13] score 0.00 | verdict: cmpfixinanislealse | conf: canLHIGH
16
+ [p14] score 0.00 | verdict: partcinfunsuppanmislealse | conf: cannot assess
17
+ [p15] score 0.00 | verdict: funcspanuppinmislealse | conf: canLHIGH
18
+ [p16] score 0.00 | verdict: cmpfunanislesubstartinantiated | conf: cannot assess
19
+ [p17] score 0.00 | verdict: funcspanminislealse | conf: cannot assess
20
+ [p18] score 0.00 | verdict: funcspanuppinmisleubstartant | conf: cannot assess
21
+ [p19] score 0.00 | verdict: partmincacconfunsuisleixed | conf: cannot assess
22
+ [p20] score 0.00 | verdict: partmincislefunstrue | conf: canLHIGH
23
+ [p21] score 0.00 | verdict: mpcfunsuppinanislealse | conf: canLHIGH
24
+ [p22] score 0.00 | verdict: tpcinfunsuppmislerue | conf: canLHIGH
25
+ [p23] score 0.00 | verdict: funcspanuppinmislealse | conf: canLHIGH
26
+ [p24] score 0.00 | verdict: funcspanminislealse | conf: cannot assess
27
+ [p25] score 0.00 | verdict: cmpfinanisleixed | conf: canLHIGH
28
+ [p26] score 0.00 | verdict: cmpfunanislesubstarttrue | conf: canLHIGH
29
+ [p27] score 0.00 | verdict: funcsuppinpanmtrue | conf: canLHIGH
30
+ [p28] score 0.00 | verdict: cmpfunsuppinisleantrue | conf: cannot assess
31
+ [p29] score 0.00 | verdict: funcspanmintrue | conf: cLHIGH
32
+ [p30] score 0.00 | verdict: funcspanmintrue | conf: canLHIGH
33
+ [p31] score 0.00 | verdict: parttincanmislerue | conf: cannot assess
34
+ [p32] score 0.00 | verdict: funcspmintrue | conf: canLHIGH
35
+ [p33] score 0.00 | verdict: cmpfixed | conf: cannot assess
36
+ [p34] score 0.00 | verdict: partmincaconfuncsuppantr | conf: cannot assess
37
+ [p35] score 0.00 | verdict: cmpinfunsuacctrue | conf: cannot assess
38
+ [p36] score 0.00 | verdict: mincpanislefixed | conf: cannot assess
39
+ [p37] score 0.00 | verdict: minpctfunsuppranislealse | conf: canLHIGH
40
+ [p38] score 0.00 | verdict: partmincacctfunsrue | conf: canLHIGH
41
+ [p39] score 0.00 | verdict: partmincislefunsuaccontr | conf: cannot assess
42
+ [p40] score 0.00 | verdict: funcspanmintrue | conf: cannot assess
43
+ [p41] score 0.00 | verdict: confinanalaccurate | conf: cannot assess
44
+ [p42] score 0.00 | verdict: confunsuanalse | conf: canHIMEDIUM
45
+ [p43] score 0.00 | verdict: funsualtrconanmisleinsinac | conf: cannot assess
46
+ [p44] score 0.00 | verdict: confunsalanmtrisuleinsinac | conf: HIMLGEcannot assess
47
+ [p45] score 0.00 | verdict: inftrue | conf: HIGH
48
+ [p46] score 0.00 | verdict: notconfal atranminisse | conf: HIMLGEcannot assess
49
+ [p47] score 0.00 | verdict: funsutralconaninsinaccurate | conf: cannot assess
50
+ [p48] score 0.00 | verdict: confinanalaccurate | conf: cannot assess
51
+ [p49] score 0.00 | verdict: notconf apanal discrepinaccurate | conf: HIMEcanDILUM
52
+ [p50] score 0.00 | verdict: notconfan aunsalupported | conf: HIMEcanDILUM
53
+
54
+ mean verdict score: 0.000 accuracy@0.5: 0.00 format rate: 1.00
55
+ by category:
56
+ generic mean 0.000 n=50
57
+ === probes_researcher ===
58
+ == eval ckpt/tiny18m2/model_8500.pt ==
59
+
60
+ [verdict-00] score 0.00 | verdict: cmpfixed | conf: cannot assess
61
+ [verdict-01] score 0.00 | verdict: funcsupmintrue | conf: cannot assess
62
+ [verdict-02] score 0.00 | verdict: cmpfixed | conf: cannot assess
63
+ [verdict-03] score 0.00 | verdict: funcspanminislealse | conf: canLHIGH
64
+ [verdict-04] score 0.00 | verdict: funcspanuppinmislealse | conf: canLHIGH
65
+ [verdict-05] score 0.00 | verdict: funscintmpanislerue | conf: cannot assess
66
+ [discrepancy-06] score 0.00 | verdict: funcspanmintrue | conf: canLHIGH
67
+ [discrepancy-07] score 0.00 | verdict: funcspanuppinmislealse | conf: canLHIGH
68
+ [discrepancy-08] score 0.00 | verdict: funcsupmintrue | conf: canLHIGH
69
+ [discrepancy-09] score 0.00 | verdict: funcsuppinpanmixed | conf: cannot assess
70
+ [pattern-10] score 0.00 | verdict: cmpfintrue | conf: canLHIGH
71
+ [pattern-11] score 0.00 | verdict: cminpanfixed | conf: cannot assess
72
+ [pattern-12] score 0.00 | verdict: miscpinfixed | conf: cannot assess
73
+ [safety-13] score 0.00 | verdict: parttmincisleranfunsuacc | conf: canLHIGH
74
+ [safety-14] score 0.00 | verdict: mincpanfunsuacconisletr | conf: cannot assess
75
+ [safety-15] score 0.00 | verdict: partminctrue | conf: canLHIGH
76
+ [safety-16] score 0.00 | verdict: funcspanmintrue | conf: cHIGH
77
+ [selfcheck-17] score 0.00 | verdict: cmpfunsuppintrue | conf: canLHIGH
78
+ [selfcheck-18] score 0.00 | verdict: notminpisconfixed | conf: cannot assess
79
+ [symbolism-19] score 0.00 | verdict: funcsuppinpanmislealse | conf: cannot assess
80
+ [symbolism-20] score 0.00 | verdict: partminctrue | conf: canLHIGH
81
+ [symbolism-21] score 0.00 | verdict: punscinmislefixed | conf: cannot assess
82
+ [symbolism-22] score 0.00 | verdict: cmpfunsuppintrue | conf: canLHIGH
83
+ [gap-23] score 0.00 | verdict: cmpfixintrue | conf: canLHIGH
84
+ [gap-24] score 0.00 | verdict: cmpfixinanislealse | conf: cannot assess
85
+ [gap-25] score 0.00 | verdict: funpartsubmincanislealse | conf: cannot assess
86
+ [gap-26] score 0.00 | verdict: funcspaninmisleupponixed | conf: cLHIGH
87
+
88
+ mean verdict score: 0.000 accuracy@0.5: 0.00 format rate: 1.00
89
+ by category:
90
+ discrepancy mean 0.000 n=4
91
+ gap mean 0.000 n=4
92
+ pattern mean 0.000 n=3
93
+ safety mean 0.000 n=4
94
+ selfcheck mean 0.000 n=2
95
+ symbolism mean 0.000 n=4
96
+ verdict mean 0.000 n=6
logs/bench2_val.log ADDED
File without changes
logs/bench_dpo.log ADDED
@@ -0,0 +1 @@
 
 
1
+ benchmarking ckpt/dpo/model_final.pt | params 7,788,288 | d_model 320 blocks 6
logs/bench_dpo_2.log ADDED
@@ -0,0 +1 @@
 
 
1
+ benchmarking ckpt/dpo/model_final.pt | params 7,788,288 | d_model 320 blocks 6
logs/bench_forensic.log ADDED
File without changes
logs/calib90_dpo3_200_main.jsonl ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"id": "p01", "score": 0.0, "verdict": "abstain", "conf": "HIGH", "canon": "refutes"}
2
+ {"id": "p02", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "not a contradiction"}
3
+ {"id": "p03", "score": 1.0, "verdict": "overclaim", "conf": "LOW", "canon": "overclaim"}
4
+ {"id": "p04", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "not enough information"}
5
+ {"id": "p05", "score": 0.0, "verdict": "abstain", "conf": "HIGH", "canon": "false"}
6
+ {"id": "p06", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "unsubstantiated"}
7
+ {"id": "p07", "score": 0.0, "verdict": "low confidence", "conf": "cannot assess", "canon": "true"}
8
+ {"id": "p08", "score": 1.0, "verdict": "overclaim", "conf": "LOW", "canon": "overclaim"}
9
+ {"id": "p09", "score": 0.0, "verdict": "unverifiable", "conf": "LOW", "canon": "false"}
10
+ {"id": "p10", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "unsubstantiated"}
11
+ {"id": "p11", "score": 0.0, "verdict": "overclaim", "conf": "LOW", "canon": "not enough information"}
12
+ {"id": "p12", "score": 0.0, "verdict": "overclaim", "conf": "MEDIUM", "canon": "misleading"}
13
+ {"id": "p13", "score": 0.0, "verdict": "overclaim", "conf": "LOW", "canon": "contradiction"}
14
+ {"id": "p14", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "not a contradiction"}
15
+ {"id": "p15", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "not enough information"}
16
+ {"id": "p16", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": "unsubstantiated"}
17
+ {"id": "p17", "score": 0.0, "verdict": "overclaim", "conf": "LOW", "canon": "misleading"}
18
+ {"id": "p18", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "misleading"}
19
+ {"id": "p19", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "unsubstantiated"}
20
+ {"id": "p20", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "overclaim"}
21
+ {"id": "p21", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "unsubstantiated"}
22
+ {"id": "p22", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "overclaim"}
23
+ {"id": "p23", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "overclaim"}
24
+ {"id": "p24", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "false"}
25
+ {"id": "p25", "score": 1.0, "verdict": "overclaim", "conf": "MEDIUM", "canon": "overclaim"}
26
+ {"id": "p26", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "overclaim"}
27
+ {"id": "p27", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "false"}
28
+ {"id": "p28", "score": 0.0, "verdict": "not enough information", "conf": "LOW", "canon": "unsubstantiated"}
29
+ {"id": "p29", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "false"}
30
+ {"id": "p30", "score": 0.0, "verdict": "overclaim", "conf": "LOW", "canon": "false"}
31
+ {"id": "p31", "score": 0.0, "verdict": "partially true", "conf": "LOW", "canon": "unsubstantiated"}
32
+ {"id": "p32", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "overclaim"}
33
+ {"id": "p33", "score": 1.0, "verdict": "overclaim", "conf": "LOW", "canon": "overclaim"}
34
+ {"id": "p34", "score": 0.0, "verdict": "overclaim", "conf": "cannot assess", "canon": "false"}
35
+ {"id": "p35", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "false"}
36
+ {"id": "p36", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "false"}
37
+ {"id": "p37", "score": 0.0, "verdict": "overclaim", "conf": "LOW", "canon": "false"}
38
+ {"id": "p38", "score": 0.0, "verdict": "overclaim", "conf": "MEDIUM", "canon": null}
39
+ {"id": "p39", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "unsubstantiated"}
40
+ {"id": "p40", "score": 0.0, "verdict": "overclaim", "conf": "MEDIUM", "canon": "low confidence"}
41
+ {"id": "p41", "score": 0.0, "verdict": "overclaim", "conf": "LOW", "canon": "unsubstantiated"}
42
+ {"id": "p42", "score": 1.0, "verdict": "overclaim", "conf": "LOW", "canon": "overclaim"}
43
+ {"id": "p43", "score": 1.0, "verdict": "overclaim", "conf": "LOW", "canon": "overclaim"}
44
+ {"id": "p44", "score": 1.0, "verdict": "overclaim", "conf": "LOW", "canon": "overclaim"}
45
+ {"id": "p45", "score": 1.0, "verdict": "overclaim", "conf": "LOW", "canon": "overclaim"}
46
+ {"id": "p46", "score": 1.0, "verdict": "overclaim", "conf": "HIGH", "canon": "overclaim"}
47
+ {"id": "p47", "score": 1.0, "verdict": "overclaim", "conf": "LOW", "canon": "overclaim"}
48
+ {"id": "p48", "score": 1.0, "verdict": "overclaim", "conf": "LOW", "canon": "overclaim"}
49
+ {"id": "p49", "score": 1.0, "verdict": "overclaim", "conf": "LOW", "canon": "overclaim"}
50
+ {"id": "p50", "score": 1.0, "verdict": "overclaim", "conf": "LOW", "canon": "overclaim"}
logs/calib90_dpo3_200_res.jsonl ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"id": "verdict-00", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "true"}
2
+ {"id": "verdict-01", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "false"}
3
+ {"id": "verdict-02", "score": 0.0, "verdict": "overclaim", "conf": "LOW", "canon": "unsubstantiated"}
4
+ {"id": "verdict-03", "score": 0.0, "verdict": "overclaim", "conf": "LOW", "canon": "contradiction"}
5
+ {"id": "verdict-04", "score": 0.0, "verdict": "overclaim", "conf": "LOW", "canon": "true"}
6
+ {"id": "verdict-05", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "unsubstantiated"}
7
+ {"id": "discrepancy-06", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "false"}
8
+ {"id": "discrepancy-07", "score": 0.0, "verdict": "abstain", "conf": "HIGH", "canon": "contradiction"}
9
+ {"id": "discrepancy-08", "score": 0.0, "verdict": "overclaim", "conf": "cannot assess", "canon": "false"}
10
+ {"id": "discrepancy-09", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": null}
11
+ {"id": "pattern-10", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": null}
12
+ {"id": "pattern-11", "score": 0.0, "verdict": "overclaim", "conf": "LOW", "canon": null}
13
+ {"id": "pattern-12", "score": 0.0, "verdict": "abstain", "conf": "HIGH", "canon": null}
14
+ {"id": "safety-13", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": null}
15
+ {"id": "safety-14", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": null}
16
+ {"id": "safety-15", "score": 0.0, "verdict": "overclaim", "conf": "MEDIUM", "canon": null}
17
+ {"id": "safety-16", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": null}
18
+ {"id": "selfcheck-17", "score": 0.0, "verdict": "overclaim", "conf": "LOW", "canon": "not enough information"}
19
+ {"id": "selfcheck-18", "score": 0.0, "verdict": "overclaim", "conf": "LOW", "canon": "low confidence"}
20
+ {"id": "symbolism-19", "score": 1.0, "verdict": "low confidence", "conf": "LOW", "canon": "low confidence"}
21
+ {"id": "symbolism-20", "score": 1.0, "verdict": "low confidence", "conf": "LOW", "canon": "low confidence"}
22
+ {"id": "symbolism-21", "score": 0.0, "verdict": "true", "conf": "LOW", "canon": "low confidence"}
23
+ {"id": "symbolism-22", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": null}
24
+ {"id": "gap-23", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "unsubstantiated"}
25
+ {"id": "gap-24", "score": 0.0, "verdict": "overclaim", "conf": "MEDIUM", "canon": "not enough information"}
26
+ {"id": "gap-25", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "unsubstantiated"}
27
+ {"id": "gap-26", "score": 0.0, "verdict": "abstain", "conf": "LOW", "canon": "false"}
logs/calib_dpo3_200_main.jsonl ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"id": "p01", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "refutes"}
2
+ {"id": "p02", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": "not a contradiction"}
3
+ {"id": "p03", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": "overclaim"}
4
+ {"id": "p04", "score": 0.0, "verdict": "unverifiable", "conf": "HIGH", "canon": "not enough information"}
5
+ {"id": "p05", "score": 0.0, "verdict": "partially true", "conf": "LOW", "canon": "false"}
6
+ {"id": "p06", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "unsubstantiated"}
7
+ {"id": "p07", "score": 1.0, "verdict": "true", "conf": "cannot assess", "canon": "true"}
8
+ {"id": "p08", "score": 0.0, "verdict": "true", "conf": "LOW", "canon": "overclaim"}
9
+ {"id": "p09", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": "false"}
10
+ {"id": "p10", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": "unsubstantiated"}
11
+ {"id": "p11", "score": 0.0, "verdict": "abstain", "conf": "cannot assess", "canon": "not enough information"}
12
+ {"id": "p12", "score": 0.0, "verdict": "overclaim", "conf": "MEDIUM", "canon": "misleading"}
13
+ {"id": "p13", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "contradiction"}
14
+ {"id": "p14", "score": 0.0, "verdict": "overclaim", "conf": "cannot assess", "canon": "not a contradiction"}
15
+ {"id": "p15", "score": 0.0, "verdict": "not a contradiction", "conf": "HIGH", "canon": "not enough information"}
16
+ {"id": "p16", "score": 0.0, "verdict": "true", "conf": "HIGH", "canon": "unsubstantiated"}
17
+ {"id": "p17", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": "misleading"}
18
+ {"id": "p18", "score": 0.0, "verdict": "overclaim", "conf": "MEDIUM", "canon": "misleading"}
19
+ {"id": "p19", "score": 0.0, "verdict": "true", "conf": "HIGH", "canon": "unsubstantiated"}
20
+ {"id": "p20", "score": 1.0, "verdict": "overclaim", "conf": "HIGH", "canon": "overclaim"}
21
+ {"id": "p21", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "unsubstantiated"}
22
+ {"id": "p22", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": "overclaim"}
23
+ {"id": "p23", "score": 0.0, "verdict": "not enough information", "conf": "cannot assess", "canon": "overclaim"}
24
+ {"id": "p24", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": "false"}
25
+ {"id": "p25", "score": 0.0, "verdict": "true", "conf": "MEDIUM", "canon": "overclaim"}
26
+ {"id": "p26", "score": 0.0, "verdict": "unverifiable", "conf": "LOW", "canon": "overclaim"}
27
+ {"id": "p27", "score": 0.0, "verdict": "overclaim", "conf": "cannot assess", "canon": "false"}
28
+ {"id": "p28", "score": 0.0, "verdict": "abstain", "conf": "cannot assess", "canon": "unsubstantiated"}
29
+ {"id": "p29", "score": 0.0, "verdict": "overclaim", "conf": "HIGH", "canon": "false"}
30
+ {"id": "p30", "score": 0.0, "verdict": "true", "conf": "HIGH", "canon": "false"}
31
+ {"id": "p31", "score": 0.0, "verdict": "true", "conf": "cannot assess", "canon": "unsubstantiated"}
32
+ {"id": "p32", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": "overclaim"}
33
+ {"id": "p33", "score": 1.0, "verdict": "overclaim", "conf": "MEDIUM", "canon": "overclaim"}
34
+ {"id": "p34", "score": 0.0, "verdict": "no meaningful pattern", "conf": "HIGH", "canon": "false"}
35
+ {"id": "p35", "score": 0.0, "verdict": "unverifiable", "conf": "HIGH", "canon": "false"}
36
+ {"id": "p36", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": "false"}
37
+ {"id": "p37", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "false"}
38
+ {"id": "p38", "score": 0.0, "verdict": "abstain", "conf": "HIGH", "canon": null}
39
+ {"id": "p39", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "unsubstantiated"}
40
+ {"id": "p40", "score": 0.0, "verdict": "true", "conf": "HIGH", "canon": "low confidence"}
41
+ {"id": "p41", "score": 0.0, "verdict": "not enough information", "conf": "MEDIUM", "canon": "unsubstantiated"}
42
+ {"id": "p42", "score": 0.0, "verdict": "not enough information", "conf": "MEDIUM", "canon": "overclaim"}
43
+ {"id": "p43", "score": 1.0, "verdict": "overclaim", "conf": "MEDIUM", "canon": "overclaim"}
44
+ {"id": "p44", "score": 0.0, "verdict": "not enough information", "conf": "MEDIUM", "canon": "overclaim"}
45
+ {"id": "p45", "score": 1.0, "verdict": "overclaim", "conf": "LOW", "canon": "overclaim"}
46
+ {"id": "p46", "score": 1.0, "verdict": "overclaim", "conf": "LOW", "canon": "overclaim"}
47
+ {"id": "p47", "score": 0.0, "verdict": "inaccurate", "conf": "MEDIUM", "canon": "overclaim"}
48
+ {"id": "p48", "score": 1.0, "verdict": "overclaim", "conf": "MEDIUM", "canon": "overclaim"}
49
+ {"id": "p49", "score": 0.0, "verdict": "not enough information", "conf": "MEDIUM", "canon": "overclaim"}
50
+ {"id": "p50", "score": 0.0, "verdict": "not enough information", "conf": "MEDIUM", "canon": "overclaim"}
logs/calib_dpo3_200_res.jsonl ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"id": "verdict-00", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "true"}
2
+ {"id": "verdict-01", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "false"}
3
+ {"id": "verdict-02", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "unsubstantiated"}
4
+ {"id": "verdict-03", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "contradiction"}
5
+ {"id": "verdict-04", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": "true"}
6
+ {"id": "verdict-05", "score": 0.0, "verdict": "overclaim", "conf": "MEDIUM", "canon": "unsubstantiated"}
7
+ {"id": "discrepancy-06", "score": 0.0, "verdict": "low confidence", "conf": "LOW", "canon": "false"}
8
+ {"id": "discrepancy-07", "score": 0.0, "verdict": "false", "conf": "cannot assess", "canon": "contradiction"}
9
+ {"id": "discrepancy-08", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": "false"}
10
+ {"id": "discrepancy-09", "score": 0.0, "verdict": "not a contradiction", "conf": "cannot assess", "canon": null}
11
+ {"id": "pattern-10", "score": 0.0, "verdict": "true", "conf": "cannot assess", "canon": null}
12
+ {"id": "pattern-11", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": null}
13
+ {"id": "pattern-12", "score": 0.0, "verdict": "not a contradiction", "conf": "cannot assess", "canon": null}
14
+ {"id": "safety-13", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": null}
15
+ {"id": "safety-14", "score": 0.0, "verdict": "unverifiable", "conf": "HIGH", "canon": null}
16
+ {"id": "safety-15", "score": 0.0, "verdict": "not enough information", "conf": "HIGH", "canon": null}
17
+ {"id": "safety-16", "score": 0.0, "verdict": "unverifiable", "conf": "HIGH", "canon": null}
18
+ {"id": "selfcheck-17", "score": 0.0, "verdict": "true", "conf": "LOW", "canon": "not enough information"}
19
+ {"id": "selfcheck-18", "score": 1.0, "verdict": "low confidence", "conf": "HIGH", "canon": "low confidence"}
20
+ {"id": "symbolism-19", "score": 0.0, "verdict": "unverifiable", "conf": "HIGH", "canon": "low confidence"}
21
+ {"id": "symbolism-20", "score": 0.0, "verdict": "unverifiable", "conf": "HIGH", "canon": "low confidence"}
22
+ {"id": "symbolism-21", "score": 0.0, "verdict": "true", "conf": "cannot assess", "canon": "low confidence"}
23
+ {"id": "symbolism-22", "score": 0.0, "verdict": "true", "conf": "cannot assess", "canon": null}
24
+ {"id": "gap-23", "score": 0.0, "verdict": "true", "conf": "cannot assess", "canon": "unsubstantiated"}
25
+ {"id": "gap-24", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": "not enough information"}
26
+ {"id": "gap-25", "score": 0.0, "verdict": "low confidence", "conf": "HIGH", "canon": "unsubstantiated"}
27
+ {"id": "gap-26", "score": 0.0, "verdict": "overclaim", "conf": "cannot assess", "canon": "false"}
logs/calib_summary_calib90.json ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "tag": "calib90",
3
+ "main": {
4
+ "name": "MAIN",
5
+ "n_total": 49,
6
+ "buckets": {
7
+ "HIGH": {
8
+ "n": 4,
9
+ "correct": 1,
10
+ "acc": 0.25,
11
+ "ci": [
12
+ 0.045586062644636216,
13
+ 0.6993639475573634
14
+ ]
15
+ },
16
+ "MEDIUM": {
17
+ "n": 3,
18
+ "correct": 1,
19
+ "acc": 0.3333333333333333,
20
+ "ci": [
21
+ 0.0614903152761605,
22
+ 0.7923450448735121
23
+ ]
24
+ },
25
+ "LOW": {
26
+ "n": 40,
27
+ "correct": 11,
28
+ "acc": 0.275,
29
+ "ci": [
30
+ 0.1610785343314309,
31
+ 0.4283525083312457
32
+ ]
33
+ },
34
+ "cannot assess": {
35
+ "n": 0,
36
+ "correct": 0,
37
+ "acc": NaN,
38
+ "ci": [
39
+ NaN,
40
+ NaN
41
+ ]
42
+ }
43
+ },
44
+ "overall": {
45
+ "n": 49,
46
+ "correct": 13,
47
+ "acc": 0.2653061224489796,
48
+ "ci": [
49
+ 0.1621113124514944,
50
+ 0.4026255615265835
51
+ ]
52
+ }
53
+ },
54
+ "res": {
55
+ "name": "RESEARCHER",
56
+ "n_total": 18,
57
+ "buckets": {
58
+ "HIGH": {
59
+ "n": 1,
60
+ "correct": 0,
61
+ "acc": 0.0,
62
+ "ci": [
63
+ 0.0,
64
+ 0.7934567085261071
65
+ ]
66
+ },
67
+ "MEDIUM": {
68
+ "n": 1,
69
+ "correct": 0,
70
+ "acc": 0.0,
71
+ "ci": [
72
+ 0.0,
73
+ 0.7934567085261071
74
+ ]
75
+ },
76
+ "LOW": {
77
+ "n": 15,
78
+ "correct": 2,
79
+ "acc": 0.13333333333333333,
80
+ "ci": [
81
+ 0.0373604698913593,
82
+ 0.3788249920651624
83
+ ]
84
+ },
85
+ "cannot assess": {
86
+ "n": 0,
87
+ "correct": 0,
88
+ "acc": NaN,
89
+ "ci": [
90
+ NaN,
91
+ NaN
92
+ ]
93
+ }
94
+ },
95
+ "overall": {
96
+ "n": 18,
97
+ "correct": 2,
98
+ "acc": 0.1111111111111111,
99
+ "ci": [
100
+ 0.031018868453582787,
101
+ 0.3280024486660421
102
+ ]
103
+ }
104
+ },
105
+ "combined": {
106
+ "name": "COMBINED",
107
+ "n_total": 67,
108
+ "buckets": {
109
+ "HIGH": {
110
+ "n": 5,
111
+ "correct": 1,
112
+ "acc": 0.2,
113
+ "ci": [
114
+ 0.03622316096978745,
115
+ 0.6244717358814613
116
+ ]
117
+ },
118
+ "MEDIUM": {
119
+ "n": 4,
120
+ "correct": 1,
121
+ "acc": 0.25,
122
+ "ci": [
123
+ 0.045586062644636216,
124
+ 0.6993639475573634
125
+ ]
126
+ },
127
+ "LOW": {
128
+ "n": 55,
129
+ "correct": 13,
130
+ "acc": 0.23636363636363636,
131
+ "ci": [
132
+ 0.14366521587457487,
133
+ 0.36348618721439613
134
+ ]
135
+ },
136
+ "cannot assess": {
137
+ "n": 0,
138
+ "correct": 0,
139
+ "acc": NaN,
140
+ "ci": [
141
+ NaN,
142
+ NaN
143
+ ]
144
+ }
145
+ },
146
+ "overall": {
147
+ "n": 67,
148
+ "correct": 15,
149
+ "acc": 0.22388059701492538,
150
+ "ci": [
151
+ 0.1406360851472927,
152
+ 0.33707192822338217
153
+ ]
154
+ }
155
+ },
156
+ "high_verdict_mix": {
157
+ "main": {
158
+ "abstain": {
159
+ "n": 2,
160
+ "acc": 0.0
161
+ },
162
+ "low confidence": {
163
+ "n": 1,
164
+ "acc": 0.0
165
+ },
166
+ "overclaim": {
167
+ "n": 1,
168
+ "acc": 1.0
169
+ }
170
+ },
171
+ "res": {
172
+ "abstain": {
173
+ "n": 1,
174
+ "acc": 0.0
175
+ }
176
+ }
177
+ },
178
+ "abstention": {
179
+ "main": {
180
+ "abstain_rate": 0.3673469387755102,
181
+ "abstain_acc": 0.0,
182
+ "should_abstain_n": 3,
183
+ "abstained_on_should": 1
184
+ },
185
+ "res": {
186
+ "abstain_rate": 0.2222222222222222,
187
+ "abstain_acc": 0.0,
188
+ "should_abstain_n": 2,
189
+ "abstained_on_should": 0
190
+ }
191
+ },
192
+ "note": "A confidence bucket whose accuracy is BELOW the overall mean is anti-calibrated: trusting it lowers precision. Weighted voting must use these empirical accuracies, not the label."
193
+ }
logs/calib_summary_dpo3_200.json ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "tag": "dpo3_200",
3
+ "combined": {
4
+ "name": "COMBINED",
5
+ "n_total": 77,
6
+ "buckets": {
7
+ "HIGH": {
8
+ "acc": 0.125,
9
+ "n": 8,
10
+ "correct": 1
11
+ },
12
+ "MEDIUM": {
13
+ "acc": 0.16666666666666666,
14
+ "n": 6,
15
+ "correct": 1
16
+ },
17
+ "LOW": {
18
+ "acc": 0.21666666666666667,
19
+ "n": 60,
20
+ "correct": 13
21
+ },
22
+ "cannot assess": {
23
+ "acc": 0.0,
24
+ "n": 3,
25
+ "correct": 0
26
+ }
27
+ },
28
+ "overall": {
29
+ "n": 77,
30
+ "correct": 15,
31
+ "acc": 0.19480519480519481
32
+ }
33
+ },
34
+ "main": {
35
+ "name": "MAIN",
36
+ "n_total": 50,
37
+ "buckets": {
38
+ "HIGH": {
39
+ "acc": 0.25,
40
+ "n": 4,
41
+ "correct": 1
42
+ },
43
+ "MEDIUM": {
44
+ "acc": 0.25,
45
+ "n": 4,
46
+ "correct": 1
47
+ },
48
+ "LOW": {
49
+ "acc": 0.275,
50
+ "n": 40,
51
+ "correct": 11
52
+ },
53
+ "cannot assess": {
54
+ "acc": 0.0,
55
+ "n": 2,
56
+ "correct": 0
57
+ }
58
+ },
59
+ "overall": {
60
+ "n": 50,
61
+ "correct": 13,
62
+ "acc": 0.26
63
+ }
64
+ },
65
+ "res": {
66
+ "name": "RESEARCHER",
67
+ "n_total": 27,
68
+ "buckets": {
69
+ "HIGH": {
70
+ "acc": 0.0,
71
+ "n": 4,
72
+ "correct": 0
73
+ },
74
+ "MEDIUM": {
75
+ "acc": 0.0,
76
+ "n": 2,
77
+ "correct": 0
78
+ },
79
+ "LOW": {
80
+ "acc": 0.1,
81
+ "n": 20,
82
+ "correct": 2
83
+ },
84
+ "cannot assess": {
85
+ "acc": 0.0,
86
+ "n": 1,
87
+ "correct": 0
88
+ }
89
+ },
90
+ "overall": {
91
+ "n": 27,
92
+ "correct": 2,
93
+ "acc": 0.07407407407407407
94
+ }
95
+ }
96
+ }
logs/calib_summary_dpo3_200_scratch24.json ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "tag": "dpo3_200_scratch24",
3
+ "main": {
4
+ "name": "MAIN",
5
+ "n_total": 49,
6
+ "buckets": {
7
+ "HIGH": {
8
+ "n": 19,
9
+ "correct": 1,
10
+ "acc": 0.05263157894736842,
11
+ "ci": [
12
+ 0.009351752490529969,
13
+ 0.2463921533654521
14
+ ]
15
+ },
16
+ "MEDIUM": {
17
+ "n": 12,
18
+ "correct": 3,
19
+ "acc": 0.25,
20
+ "ci": [
21
+ 0.08894003962896138,
22
+ 0.53231033912066
23
+ ]
24
+ },
25
+ "LOW": {
26
+ "n": 11,
27
+ "correct": 2,
28
+ "acc": 0.18181818181818182,
29
+ "ci": [
30
+ 0.05136660067100063,
31
+ 0.4769861375782448
32
+ ]
33
+ },
34
+ "cannot assess": {
35
+ "n": 0,
36
+ "correct": 0,
37
+ "acc": NaN,
38
+ "ci": [
39
+ NaN,
40
+ NaN
41
+ ]
42
+ }
43
+ },
44
+ "overall": {
45
+ "n": 49,
46
+ "correct": 7,
47
+ "acc": 0.14285714285714285,
48
+ "ci": [
49
+ 0.07096333720544584,
50
+ 0.2666797318916292
51
+ ]
52
+ }
53
+ },
54
+ "res": {
55
+ "name": "RESEARCHER",
56
+ "n_total": 18,
57
+ "buckets": {
58
+ "HIGH": {
59
+ "n": 7,
60
+ "correct": 1,
61
+ "acc": 0.14285714285714285,
62
+ "ci": [
63
+ 0.02567895594897479,
64
+ 0.51313450331903
65
+ ]
66
+ },
67
+ "MEDIUM": {
68
+ "n": 1,
69
+ "correct": 0,
70
+ "acc": 0.0,
71
+ "ci": [
72
+ 0.0,
73
+ 0.7934567085261071
74
+ ]
75
+ },
76
+ "LOW": {
77
+ "n": 6,
78
+ "correct": 0,
79
+ "acc": 0.0,
80
+ "ci": [
81
+ 0.0,
82
+ 0.3903430336530645
83
+ ]
84
+ },
85
+ "cannot assess": {
86
+ "n": 0,
87
+ "correct": 0,
88
+ "acc": NaN,
89
+ "ci": [
90
+ NaN,
91
+ NaN
92
+ ]
93
+ }
94
+ },
95
+ "overall": {
96
+ "n": 18,
97
+ "correct": 1,
98
+ "acc": 0.05555555555555555,
99
+ "ci": [
100
+ 0.009874935830924525,
101
+ 0.2575779980200754
102
+ ]
103
+ }
104
+ },
105
+ "combined": {
106
+ "name": "COMBINED",
107
+ "n_total": 67,
108
+ "buckets": {
109
+ "HIGH": {
110
+ "n": 26,
111
+ "correct": 2,
112
+ "acc": 0.07692307692307693,
113
+ "ci": [
114
+ 0.02135463918337556,
115
+ 0.24141947480515052
116
+ ]
117
+ },
118
+ "MEDIUM": {
119
+ "n": 13,
120
+ "correct": 3,
121
+ "acc": 0.23076923076923078,
122
+ "ci": [
123
+ 0.08179379092465708,
124
+ 0.5025687399512693
125
+ ]
126
+ },
127
+ "LOW": {
128
+ "n": 17,
129
+ "correct": 2,
130
+ "acc": 0.11764705882352941,
131
+ "ci": [
132
+ 0.03287908001292092,
133
+ 0.3433684249770991
134
+ ]
135
+ },
136
+ "cannot assess": {
137
+ "n": 0,
138
+ "correct": 0,
139
+ "acc": NaN,
140
+ "ci": [
141
+ NaN,
142
+ NaN
143
+ ]
144
+ }
145
+ },
146
+ "overall": {
147
+ "n": 67,
148
+ "correct": 8,
149
+ "acc": 0.11940298507462686,
150
+ "ci": [
151
+ 0.06176158770587532,
152
+ 0.21832243072397378
153
+ ]
154
+ }
155
+ },
156
+ "high_verdict_mix": {
157
+ "main": {
158
+ "low confidence": {
159
+ "n": 9,
160
+ "acc": 0.0
161
+ },
162
+ "unverifiable": {
163
+ "n": 2,
164
+ "acc": 0.0
165
+ },
166
+ "not a contradiction": {
167
+ "n": 1,
168
+ "acc": 0.0
169
+ },
170
+ "true": {
171
+ "n": 4,
172
+ "acc": 0.0
173
+ },
174
+ "overclaim": {
175
+ "n": 2,
176
+ "acc": 0.5
177
+ },
178
+ "no meaningful pattern": {
179
+ "n": 1,
180
+ "acc": 0.0
181
+ }
182
+ },
183
+ "res": {
184
+ "low confidence": {
185
+ "n": 5,
186
+ "acc": 0.2
187
+ },
188
+ "unverifiable": {
189
+ "n": 2,
190
+ "acc": 0.0
191
+ }
192
+ }
193
+ },
194
+ "abstention": {
195
+ "main": {
196
+ "abstain_rate": 0.04081632653061224,
197
+ "abstain_acc": 0.0,
198
+ "should_abstain_n": 3,
199
+ "abstained_on_should": 1
200
+ },
201
+ "res": {
202
+ "abstain_rate": 0.0,
203
+ "abstain_acc": null,
204
+ "should_abstain_n": 2,
205
+ "abstained_on_should": 0
206
+ }
207
+ },
208
+ "note": "A confidence bucket whose accuracy is BELOW the overall mean is anti-calibrated: trusting it lowers precision. Weighted voting must use these empirical accuracies, not the label."
209
+ }
logs/chain_dpo_v26.log ADDED
@@ -0,0 +1 @@
 
 
1
+ === chain start 2026-08-13 23:25:58 ===
logs/chain_sft_v26.log ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ === chain start 2026-08-13 23:24:02 ===
2
+ === all merge evals done at 01:22:07; launching SFT v26 ===
3
+ SFT v26 watchdog launched
logs/chain_v22.log ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ [chain 05:43:55] waiting for SFT final
2
+ [chain 06:03:54] waiting for SFT final
3
+ [chain 06:11:55] SFT done -> launching DPO
4
+ [chain 06:30:48] waiting for SFT final
5
+ [chain 06:30:48] SFT done -> launching DPO
logs/code_train.log ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ resumed ckpt/forensic/model_3000.pt at step 3000
2
+ params: 7.79M train_tokens: 19,433,876 steps_per_epoch: 4744
3
+ step 3025/5500 loss 6.0047 lr 9.26e-05 766 tok/s
4
+ step 3050/5500 loss 6.1537 lr 9.12e-05 708 tok/s
5
+ step 3075/5500 loss 5.2507 lr 8.99e-05 771 tok/s
6
+ step 3100/5500 loss 5.0531 lr 8.85e-05 763 tok/s
7
+ step 3125/5500 loss 4.7942 lr 8.71e-05 776 tok/s
8
+ step 3150/5500 loss 4.6254 lr 8.58e-05 762 tok/s
9
+ step 3175/5500 loss 4.7029 lr 8.44e-05 764 tok/s
10
+ step 3200/5500 loss 5.0546 lr 8.31e-05 759 tok/s
11
+ step 3225/5500 loss 4.5685 lr 8.17e-05 761 tok/s
12
+ step 3250/5500 loss 4.3609 lr 8.04e-05 759 tok/s
13
+ [eval step 3250] val_loss 4.4883 (best 4.4883)
14
+ [gen] Once upon a time, ##=f.cule':self.s.t 0:## "IGEMORPLass the"""" = "pA '-reins), self.lineer(f._patana ('), = (4(pyc
15
+ [gen] The little girl wanted to be a small rabbit, "IOTSERAD.cr =self, soft other than the cat is not (' = and the "PAclassert'f.getdr/dater.pwHENallalls =-2::preate_ntrified'=
16
+ [gen] In the dark forest, self.n_for):de ='FTANDTIMMERLIs(check.reoCNOSLM =self.tritf(_form ('] = 00,self, = lata.bath as a a 3
17
+ step 3275/5500 loss 4.4059 lr 7.91e-05 481 tok/s
18
+ step 3300/5500 loss 4.0837 lr 7.78e-05 765 tok/s
19
+ step 3325/5500 loss 4.3661 lr 7.64e-05 764 tok/s
20
+ step 3350/5500 loss 4.3748 lr 7.51e-05 761 tok/s
21
+ step 3375/5500 loss 4.1715 lr 7.38e-05 756 tok/s
22
+ step 3400/5500 loss 4.4175 lr 7.25e-05 747 tok/s
23
+ step 3425/5500 loss 4.2822 lr 7.12e-05 756 tok/s
24
+ step 3450/5500 loss 3.8087 lr 6.99e-05 751 tok/s
25
+ step 3475/5500 loss 4.1050 lr 6.86e-05 769 tok/s
26
+ step 3500/5500 loss 4.5876 lr 6.74e-05 761 tok/s
27
+ [eval step 3500] val_loss 4.1726 (best 4.1726)
28
+ [gen] Once upon a time, 2.self, selform."":'"": "IPata and not get, = "Equal'ATRES ' (put""" to the-s for more ,du"elification from
29
+ [gen] The little girl wanted to be used to be useful.self, =""SDule._state':defile:ass the Ger, or the COTITYclassput()f.exits.elificense."The textde the GFESSE
30
+ [gen] In the dark forest, = NOD'ponse)import sic.war:pase.tion is not a name of the License ass.mlocation:defrom 0"""Ac._(P version.RImodescrut in re.reresp
31
+ saved ckpt/code/model_3500.pt
32
+ step 3525/5500 loss 4.1732 lr 6.61e-05 473 tok/s
33
+ step 3550/5500 loss 4.1666 lr 6.49e-05 769 tok/s
34
+ step 3575/5500 loss 4.0799 lr 6.36e-05 773 tok/s
35
+ step 3600/5500 loss 4.5058 lr 6.24e-05 764 tok/s
36
+ step 3625/5500 loss 3.9307 lr 6.11e-05 767 tok/s
37
+ step 3650/5500 loss 4.1280 lr 5.99e-05 773 tok/s
38
+ step 3675/5500 loss 3.8534 lr 5.87e-05 767 tok/s
39
+ step 3700/5500 loss 4.1294 lr 5.75e-05 712 tok/s
40
+ step 3725/5500 loss 4.3434 lr 5.63e-05 835 tok/s
41
+ step 3750/5500 loss 4.3972 lr 5.51e-05 1093 tok/s
42
+ [eval step 3750] val_loss 4.0015 (best 4.0015)
43
+ [gen] Once upon a time, self, Cut.ds:"": = f 0: = Lponsec.'# in the us when it is false)import n, ns forms: '*groptemor to the djangw
44
+ [gen] The little girl wanted to red ball under the cat mouse bee climb rabbit bird made out to be used for new."s as the cat was a fun.# the DIS, or or event with this one of the A commonse: (stror of the GNUT ORIORECGN WICHPRAREBI
45
+ [gen] In the dark forest, len the date the Lico in ran to a line or 1self. 0truniteralse:> == ,# TOSIDELATIMICRSEY-'ttry:"DEME_line) form.# C
46
+ step 3775/5500 loss 3.8040 lr 5.40e-05 590 tok/s
47
+ step 3800/5500 loss 4.1451 lr 5.28e-05 751 tok/s
48
+ step 3825/5500 loss 3.9558 lr 5.16e-05 751 tok/s
49
+ step 3850/5500 loss 3.9454 lr 5.05e-05 750 tok/s
50
+ step 3875/5500 loss 3.9173 lr 4.94e-05 746 tok/s
51
+ step 3900/5500 loss 4.4631 lr 4.83e-05 766 tok/s
52
+ step 3925/5500 loss 4.1258 lr 4.72e-05 768 tok/s
53
+ step 3950/5500 loss 4.4475 lr 4.61e-05 769 tok/s
54
+ step 3975/5500 loss 4.1496 lr 4.50e-05 767 tok/s
55
+ step 4000/5500 loss 3.9656 lr 4.39e-05 766 tok/s
56
+ [eval step 4000] val_loss 3.9033 (best 3.9033)
57
+ [gen] Once upon a time, # the test is not a fun with this fun to the file to find# the License.## %s. to the Lal to use this and the Licationprint "IEPLERTADOGDIK_conse:#, see a fun if it in the# you will
58
+ [gen] The little girl wanted to the The cat used for the The magic bug in a fun# it.# the test is being found in the The little fish rabbit Lucy was already bug under the The cat was not want to the file., you can be useful,# the License formargs on your us our fun# the cat was an "IFIS (
59
+ [gen] In the dark forest, # you can be used to do not to a tree with the hope you may not use the Lication of the given in the RED OFURORIPase.import the Gc: "ATHO("ST ANUCASEN_name for more details as the License,
60
+ saved ckpt/code/model_4000.pt
61
+ step 4025/5500 loss 4.5375 lr 4.29e-05 469 tok/s
62
+ step 4050/5500 loss 3.9424 lr 4.18e-05 748 tok/s
63
+ step 4075/5500 loss 4.2054 lr 4.08e-05 754 tok/s
64
+ step 4100/5500 loss 3.2284 lr 3.98e-05 741 tok/s
65
+ step 4125/5500 loss 4.3530 lr 3.88e-05 740 tok/s
66
+ step 4150/5500 loss 3.6083 lr 3.78e-05 808 tok/s
67
+ step 4175/5500 loss 3.7215 lr 3.69e-05 1147 tok/s
68
+ step 4200/5500 loss 4.0122 lr 3.59e-05 747 tok/s
69
+ step 4225/5500 loss 3.6063 lr 3.50e-05 757 tok/s
70
+ step 4250/5500 loss 4.2182 lr 3.40e-05 769 tok/s
71
+ [eval step 4250] val_loss 3.8269 (best 3.8269)
72
+ [gen] Once upon a time, the same.# I don't want to read the Copy in the License at a little girl told and# in the General' This file is not use all the implied up at#, but it under the conver from the hope that we can't have received a time with the Lits is not a newction by the
73
+ [gen] The little girl wanted to the cat cat's too many bird stayed smelled toy smiled together all of the implied in the AN Imain for the LAHAR OFITAR DAIOS version and the GNE AICATHATE# you can be received a copy of the catus,# the Gconf
74
+ [gen] In the dark forest, or# a new place to be called by a special when you to the GN Cheour.#"""# You may not available for this one of the hope that it's# you can be useful, either % version:# =## 0x63. The value
75
+ step 4275/5500 loss 4.0282 lr 3.31e-05 480 tok/s
76
+ step 4300/5500 loss 4.0307 lr 3.22e-05 741 tok/s
77
+ step 4325/5500 loss 4.4112 lr 3.13e-05 771 tok/s
78
+ step 4350/5500 loss 3.6666 lr 3.05e-05 734 tok/s
79
+ step 4375/5500 loss 4.0610 lr 2.96e-05 874 tok/s
80
+ step 4400/5500 loss 4.1294 lr 2.88e-05 1130 tok/s
81
+ step 4425/5500 loss 3.7163 lr 2.80e-05 1140 tok/s
82
+ step 4450/5500 loss 3.7806 lr 2.72e-05 1173 tok/s
83
+ step 4475/5500 loss 3.6441 lr 2.64e-05 1119 tok/s
84
+ step 4500/5500 loss 3.7109 lr 2.56e-05 1116 tok/s
85
+ [eval step 4500] val_loss 3.7859 (best 3.7859)
86
+ [gen] Once upon a time, and"": (vcontents.name, to print.":default isin(s of the givendatault:Authorg')""The proge --1 =': "lule, you can be used to a name."self
87
+ [gen] The little girl wanted to a string.# "MERRENUTEICHAHS# = 'WIORTY,f get_HED AUS SS IOLIENIO# DITINDE CONIUGBICUTICERTAS
88
+ [gen] In the dark forest, you for the ANINHRAGHOMALILITY or _PTOCUAS OFONT Tests.#'# General PULOLE None# CDERIBIOCT DANSIR A 'ME
89
+ saved ckpt/code/model_4500.pt
90
+ step 4525/5500 loss 4.4508 lr 2.49e-05 563 tok/s
91
+ step 4550/5500 loss 3.0129 lr 2.41e-05 1088 tok/s
92
+ step 4575/5500 loss 3.6184 lr 2.34e-05 1057 tok/s
93
+ step 4600/5500 loss 3.4652 lr 2.27e-05 946 tok/s
94
+ step 4625/5500 loss 3.6130 lr 2.20e-05 1018 tok/s
95
+ step 4650/5500 loss 4.1417 lr 2.14e-05 1017 tok/s
96
+ step 4675/5500 loss 3.7899 lr 2.07e-05 1009 tok/s
97
+ step 4700/5500 loss 3.7298 lr 2.01e-05 876 tok/s
98
+ step 4725/5500 loss 4.3740 lr 1.95e-05 871 tok/s
99
+ step 4750/5500 loss 3.5928 lr 1.89e-05 1086 tok/s
100
+ [eval step 4750] val_loss 3.7664 (best 3.7664)
101
+ [gen] Once upon a time, # version is not None:""" is a list of %s.IORVENY or agreed to be useful and then we should have a line in the""" BAMAK_NT or implied from the C Conlicense for the specifyythonfigo
102
+ [gen] The little girl wanted to use the Gen and be true for it will be used to be added for the hope that are not " I Imal of the# _from data for more than# See the ANUPLOSEROUBIAR A License, it.# = You should gover at
103
+ [gen] In the dark forest, or event event the hope bird high that that it's open the LIS# version. The ANFIOCHECBAR ANT THT OLIT AS#*-# ver are the License is distribault.n"The BUM AGD WIT
104
+ step 4775/5500 loss 3.6334 lr 1.83e-05 616 tok/s
105
+ step 4800/5500 loss 4.0678 lr 1.78e-05 1004 tok/s
106
+ step 4825/5500 loss 4.2597 lr 1.72e-05 1007 tok/s
107
+ step 4850/5500 loss 4.1499 lr 1.67e-05 1003 tok/s
108
+ step 4875/5500 loss 3.7801 lr 1.62e-05 980 tok/s
109
+ step 4900/5500 loss 3.8103 lr 1.57e-05 1017 tok/s
110
+ step 4925/5500 loss 3.7555 lr 1.53e-05 1017 tok/s
111
+ step 4950/5500 loss 3.9945 lr 1.48e-05 1016 tok/s
112
+ step 4975/5500 loss 4.0630 lr 1.44e-05 1020 tok/s
113
+ step 5000/5500 loss 3.5704 lr 1.40e-05 1014 tok/s
114
+ [eval step 5000] val_loss 3.7418 (best 3.7418)
115
+ [gen] Once upon a time, but the same for the License.# the Resfrom ('E:# # You are not have a above in the same as isin-"" A .fig.""" of the FoundationThe same,CHImemo.print
116
+ [gen] The little girl wanted to the hope hope INY man who said sorry.#'dule is an "No last- codefrom datuseric License, the License.# Lic Lication for the General Publ'://t "# () for a softwware without even the S
117
+ [gen] In the dark forest, but but't be a copy of the Fal PR OFICUS INT % /ware: 0x.# You should have received a lists that it will be usedfor '# PARREelse# != None and the License, and the G
118
+ saved ckpt/code/model_5000.pt
119
+ step 5025/5500 loss 4.2174 lr 1.36e-05 468 tok/s
120
+ step 5050/5500 loss 4.0895 lr 1.32e-05 769 tok/s
121
+ step 5075/5500 loss 3.6267 lr 1.29e-05 759 tok/s
122
+ step 5100/5500 loss 4.3846 lr 1.26e-05 764 tok/s
123
+ step 5125/5500 loss 3.3856 lr 1.23e-05 753 tok/s
124
+ step 5150/5500 loss 3.5558 lr 1.20e-05 781 tok/s
125
+ step 5175/5500 loss 4.1669 lr 1.17e-05 746 tok/s
126
+ step 5200/5500 loss 3.6207 lr 1.14e-05 764 tok/s
127
+ step 5225/5500 loss 3.8548 lr 1.12e-05 748 tok/s
128
+ step 5250/5500 loss 2.3408 lr 1.10e-05 752 tok/s
129
+ [eval step 5250] val_loss 3.7260 (best 3.7260)
130
+ [gen] Once upon a time, """ is not there are the Geral a copy of pys if you can redists are done with this file and the Gencumentation to be used for the specific License.RANUMS INDIT WHEF WGHOULPAMENTINC
131
+ [gen] The little girl wanted to do not, "AGOTIS OF SED, a list of the Confrom datault is a copy of the Fpts.# This primage =' in the##"RY; without even the APOUG.# GNTAR Fopythonn
132
+ [gen] In the dark forest, # %s': = [""""HTS Cop'' "AME' version): ()"" 02part_OList_TOUPPAD == > =#
133
+ step 5275/5500 loss 3.6509 lr 1.08e-05 477 tok/s
134
+ step 5300/5500 loss 4.0349 lr 1.06e-05 771 tok/s
135
+ step 5325/5500 loss 3.8613 lr 1.05e-05 756 tok/s
136
+ step 5350/5500 loss 4.4485 lr 1.04e-05 764 tok/s
137
+ step 5375/5500 loss 3.8794 lr 1.03e-05 756 tok/s
138
+ step 5400/5500 loss 3.3535 lr 1.02e-05 776 tok/s
139
+ step 5425/5500 loss 2.5307 lr 1.01e-05 753 tok/s
140
+ step 5450/5500 loss 3.8203 lr 1.00e-05 756 tok/s
141
+ step 5475/5500 loss 4.0576 lr 1.00e-05 759 tok/s
142
+ step 5500/5500 loss 3.9016 lr 1.00e-05 762 tok/s
143
+ [eval step 5500] val_loss 3.7219 (best 3.7219)
144
+ [gen] Once upon a time, """"""": the G."" is not use this one of the primmental PArananal PALIdule version in the Fal:///t.""":": Crev (self, author():# Check that
145
+ [gen] The little girl wanted to the hope dog Spot in the GNT, and the License.#< I have received a time,# is a copy of the following, but it will be useful, software ANU the Gen it's be conver.fo.# in the hope that it under the terml# This file, there are
146
+ [gen] In the dark forest, "IANS Iminput.# ANGE : the License is not in source of the same with a fun can be useful, software the TOU A This file.## The FITY or MND is for this file is always be found of the Adding a copy of the F
147
+ saved ckpt/code/model_5500.pt
148
+ done
logs/distill_train.log ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ train 193 eval 21
2
+ loaded base model_final.pt (step 3129)
3
+ step 25/720 loss 3.6094 603 tok/s
4
+ [eval 40] sft_val_loss 3.3399
5
+ step 50/720 loss 1.8771 598 tok/s
6
+ step 75/720 loss 2.8598 615 tok/s
7
+ [eval 80] sft_val_loss 3.2152
8
+ step 100/720 loss 2.3302 593 tok/s
9
+ [eval 120] sft_val_loss 3.1554
10
+ step 125/720 loss 1.1766 596 tok/s
11
+ step 150/720 loss 2.6884 606 tok/s
12
+ [eval 160] sft_val_loss 3.1096
13
+ step 175/720 loss 2.2253 592 tok/s
14
+ step 200/720 loss 2.5839 606 tok/s
15
+ [eval 200] sft_val_loss 3.0830
16
+ step 225/720 loss 0.9716 593 tok/s
17
+ [eval 240] sft_val_loss 3.0567
18
+ step 250/720 loss 0.9687 601 tok/s
19
+ step 275/720 loss 1.2913 606 tok/s
20
+ [eval 280] sft_val_loss 3.0436
21
+ step 300/720 loss 2.0274 615 tok/s
22
+ [eval 320] sft_val_loss 3.0462
23
+ step 325/720 loss 1.5587 611 tok/s
24
+ step 350/720 loss 2.5838 614 tok/s
25
+ [eval 360] sft_val_loss 3.0413
26
+ step 375/720 loss 2.3279 595 tok/s
27
+ step 400/720 loss 1.7619 607 tok/s
28
+ [eval 400] sft_val_loss 3.0431
29
+ step 425/720 loss 3.0013 598 tok/s
30
+ [eval 440] sft_val_loss 3.0483
31
+ step 450/720 loss 1.8126 602 tok/s
32
+ step 475/720 loss 0.4332 614 tok/s
33
+ [eval 480] sft_val_loss 3.0469
34
+ step 500/720 loss 2.2712 587 tok/s
35
+ [eval 520] sft_val_loss 3.0608
36
+ step 525/720 loss 1.9630 596 tok/s
37
+ step 550/720 loss 1.5055 607 tok/s
38
+ [eval 560] sft_val_loss 3.0804
39
+ step 575/720 loss 2.0244 595 tok/s
40
+ step 600/720 loss 2.3559 615 tok/s
41
+ [eval 600] sft_val_loss 3.0936
42
+ step 625/720 loss 1.3087 595 tok/s
43
+ [eval 640] sft_val_loss 3.1126
44
+ step 650/720 loss 1.7370 597 tok/s
45
+ step 675/720 loss 1.2326 607 tok/s
46
+ [eval 680] sft_val_loss 3.1429
47
+ step 700/720 loss 2.0961 591 tok/s
48
+ [eval 720] sft_val_loss 3.1402
49
+ done -> ckpt/distill