cortexm 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- context_m.py +17 -0
- cortexm/__init__.py +45 -0
- cortexm/accel.py +403 -0
- cortexm/api/__init__.py +0 -0
- cortexm/api/chaos.py +118 -0
- cortexm/api/memory.py +635 -0
- cortexm/bench/__init__.py +0 -0
- cortexm/bench/abilities.py +311 -0
- cortexm/bench/baselines.py +89 -0
- cortexm/bench/beam_loader.py +317 -0
- cortexm/bench/generator.py +376 -0
- cortexm/bench/harness.py +211 -0
- cortexm/bench/messy.py +218 -0
- cortexm/bench/micro.py +251 -0
- cortexm/bench/ood.py +443 -0
- cortexm/bench/run.py +137 -0
- cortexm/bridge/__init__.py +0 -0
- cortexm/bridge/dates.py +178 -0
- cortexm/bridge/decoders.py +204 -0
- cortexm/bridge/enrich.py +255 -0
- cortexm/bridge/extractor.py +316 -0
- cortexm/bridge/fallback.py +332 -0
- cortexm/bridge/onnx_runtime.py +158 -0
- cortexm/bridge/patterns.py +760 -0
- cortexm/bridge/ppr.py +104 -0
- cortexm/bridge/prefilter.py +188 -0
- cortexm/bridge/query_extract.py +420 -0
- cortexm/bridge/reader.py +1174 -0
- cortexm/bridge/rerank.py +204 -0
- cortexm/bridge/writer.py +492 -0
- cortexm/cli.py +295 -0
- cortexm/cognition/__init__.py +53 -0
- cortexm/cognition/abstraction.py +192 -0
- cortexm/cognition/analogy.py +159 -0
- cortexm/cognition/engine.py +204 -0
- cortexm/cognition/gaps.py +365 -0
- cortexm/cognition/scanner.py +204 -0
- cortexm/config.py +375 -0
- cortexm/cortexm.py +8 -0
- cortexm/enterprise/__init__.py +0 -0
- cortexm/enterprise/audit.py +178 -0
- cortexm/enterprise/governance.py +239 -0
- cortexm/errors.py +35 -0
- cortexm/features/__init__.py +0 -0
- cortexm/features/git.py +204 -0
- cortexm/features/prefetch.py +88 -0
- cortexm/features/zk.py +105 -0
- cortexm/federation/__init__.py +39 -0
- cortexm/federation/crdt.py +275 -0
- cortexm/federation/fabric.py +109 -0
- cortexm/federation/hlc.py +80 -0
- cortexm/federation/node.py +145 -0
- cortexm/federation/schema_report.py +73 -0
- cortexm/federation/transport.py +164 -0
- cortexm/index/__init__.py +19 -0
- cortexm/index/nsg.py +386 -0
- cortexm/mcp/__init__.py +0 -0
- cortexm/mcp/server.py +985 -0
- cortexm/metrics.py +62 -0
- cortexm/migrate/__init__.py +0 -0
- cortexm/migrate/importers.py +192 -0
- cortexm/provenance/__init__.py +78 -0
- cortexm/provenance/agent.py +214 -0
- cortexm/provenance/cose.py +201 -0
- cortexm/provenance/scitt.py +258 -0
- cortexm/provenance/vc.py +250 -0
- cortexm/security/__init__.py +0 -0
- cortexm/security/crypto.py +162 -0
- cortexm/security/hashes.py +140 -0
- cortexm/security/injection.py +149 -0
- cortexm/security/mind.py +154 -0
- cortexm/security/pii.py +265 -0
- cortexm/security/rbac.py +169 -0
- cortexm/security/sandbox.py +131 -0
- cortexm/security/zk_hamming.py +142 -0
- cortexm/security/zk_sql.py +485 -0
- cortexm/server/__init__.py +0 -0
- cortexm/server/metrics.py +88 -0
- cortexm/server/rest.py +936 -0
- cortexm/server/sparql.py +984 -0
- cortexm/text/__init__.py +0 -0
- cortexm/text/dissim.py +252 -0
- cortexm/text/embedder.py +155 -0
- cortexm/text/fuzzy.py +218 -0
- cortexm/text/idiolect.py +253 -0
- cortexm/text/labse.py +374 -0
- cortexm/text/tokenizer.py +79 -0
- cortexm/trace/__init__.py +0 -0
- cortexm/trace/blob_arena.py +277 -0
- cortexm/trace/consolidate.py +337 -0
- cortexm/trace/contradictions.py +69 -0
- cortexm/trace/dedup.py +114 -0
- cortexm/trace/edges.py +214 -0
- cortexm/trace/fact.py +121 -0
- cortexm/trace/fade.py +245 -0
- cortexm/trace/lifecycle.py +112 -0
- cortexm/trace/rebuild.py +173 -0
- cortexm/trace/rules.py +171 -0
- cortexm/trace/store.py +680 -0
- cortexm/trace/structural.py +183 -0
- cortexm/trace/tmt.py +335 -0
- cortexm/util.py +148 -0
- cortexm/vsa/__init__.py +0 -0
- cortexm/vsa/attribution.py +149 -0
- cortexm/vsa/cleanup.py +161 -0
- cortexm/vsa/codecs.py +397 -0
- cortexm/vsa/hologram_overlay.py +139 -0
- cortexm/vsa/index.py +163 -0
- cortexm/vsa/ops.py +149 -0
- cortexm/vsa/palace.py +446 -0
- cortexm/vsa/role_vectors.py +236 -0
- cortexm/vsa/slb.py +78 -0
- cortexm/vsa/tlsh_trie.py +137 -0
- cortexm/vsa/working_memory.py +249 -0
- cortexm-0.3.0.dist-info/METADATA +482 -0
- cortexm-0.3.0.dist-info/RECORD +120 -0
- cortexm-0.3.0.dist-info/WHEEL +5 -0
- cortexm-0.3.0.dist-info/entry_points.txt +2 -0
- cortexm-0.3.0.dist-info/licenses/LICENSE +190 -0
- cortexm-0.3.0.dist-info/top_level.txt +2 -0
cortexm/bench/harness.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"""BEAM-style harness runner.
|
|
2
|
+
|
|
3
|
+
Protocol (mirrors the plan's Phase 2 "Proof of God" benchmark):
|
|
4
|
+
1. Generate a seeded synthetic conversation at bucket scale
|
|
5
|
+
(128K / 500K / 1M / 10M estimated tokens).
|
|
6
|
+
2. Ingest under the μ=0 protocol — zero LLM calls, asserted.
|
|
7
|
+
3. Probe with questions across the 10 BEAM abilities.
|
|
8
|
+
4. Score with the deterministic nugget judge (offline, reproducible).
|
|
9
|
+
5. Compare against BM25-RAG and vector-only baselines.
|
|
10
|
+
6. Report per-ability accuracy, ingest throughput, storage, latency,
|
|
11
|
+
and trust metrics (provenance completeness, audit latency, cost).
|
|
12
|
+
|
|
13
|
+
A pluggable LLM reader/judge slot (canonical protocol: gpt-5 reader +
|
|
14
|
+
judge) is exposed via ``llm_judge=None``; the deterministic judge keeps
|
|
15
|
+
the whole harness honest and free.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import time
|
|
22
|
+
from dataclasses import dataclass, field
|
|
23
|
+
from datetime import datetime, timezone
|
|
24
|
+
|
|
25
|
+
from cortexm import metrics
|
|
26
|
+
from cortexm.api.memory import Memory
|
|
27
|
+
from cortexm.bench.abilities import (ABILITY_NAMES, ABILITIES, build_probes,
|
|
28
|
+
judge)
|
|
29
|
+
from cortexm.bench.baselines import BM25Index, bm25_context, vector_only_context
|
|
30
|
+
from cortexm.bench.generator import generate
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class BucketResult:
|
|
35
|
+
bucket: str
|
|
36
|
+
n_questions: int = 0
|
|
37
|
+
per_ability: dict = field(default_factory=dict)
|
|
38
|
+
per_system: dict = field(default_factory=dict)
|
|
39
|
+
ingest: dict = field(default_factory=dict)
|
|
40
|
+
corpus: dict = field(default_factory=dict)
|
|
41
|
+
trust: dict = field(default_factory=dict)
|
|
42
|
+
|
|
43
|
+
def to_dict(self) -> dict:
|
|
44
|
+
return {
|
|
45
|
+
"bucket": self.bucket, "n_questions": self.n_questions,
|
|
46
|
+
"per_ability": self.per_ability, "per_system": self.per_system,
|
|
47
|
+
"ingest": self.ingest, "corpus": self.corpus, "trust": self.trust,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def run_bucket(bucket: str, seed: int = 42, systems=("context_m", "vector_only", "bm25"),
|
|
52
|
+
db_path: str = ":memory:", max_probes: int | None = None,
|
|
53
|
+
llm_judge=None) -> BucketResult:
|
|
54
|
+
res = BucketResult(bucket=bucket)
|
|
55
|
+
t0 = time.time()
|
|
56
|
+
corpus = generate(bucket, seed=seed)
|
|
57
|
+
res.corpus = {
|
|
58
|
+
"sessions": len(corpus.sessions),
|
|
59
|
+
"personas": len(corpus.personas),
|
|
60
|
+
"estimated_tokens": corpus.total_tokens,
|
|
61
|
+
"generation_seconds": round(corpus.generation_seconds, 2),
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
from cortexm.config import Config as _Cfg
|
|
65
|
+
cfg = _Cfg(db_path=db_path) if db_path != ":memory:" else _Cfg()
|
|
66
|
+
cfg.apply_rules_each_add = False # bulk mode: Datalog after ingest
|
|
67
|
+
memory = Memory(cfg)
|
|
68
|
+
metrics.reset_counters()
|
|
69
|
+
|
|
70
|
+
# ---- ingest (μ=0) ----------------------------------------------------
|
|
71
|
+
t_ingest = time.time()
|
|
72
|
+
n_msgs = 0
|
|
73
|
+
for user_id, date, msgs in corpus.sessions:
|
|
74
|
+
payload = [{"role": role, "content": text, "timestamp": date}
|
|
75
|
+
for role, text in msgs]
|
|
76
|
+
n_msgs += len(msgs)
|
|
77
|
+
memory.add(payload, user_id=user_id, timestamp=date)
|
|
78
|
+
n_derived = memory.apply_rules()
|
|
79
|
+
ingest_s = time.time() - t_ingest
|
|
80
|
+
stats = memory.stats()
|
|
81
|
+
res.ingest = {
|
|
82
|
+
"wall_seconds": round(ingest_s, 2),
|
|
83
|
+
"messages": n_msgs,
|
|
84
|
+
"tokens_per_second": int(corpus.total_tokens / max(ingest_s, 1e-9)),
|
|
85
|
+
"messages_per_second": round(n_msgs / max(ingest_s, 1e-9), 1),
|
|
86
|
+
"llm_calls": metrics.counters()["llm_calls"],
|
|
87
|
+
"u0_protocol": stats["u0_protocol"],
|
|
88
|
+
"facts": stats["facts"],
|
|
89
|
+
"active_facts": stats["active_facts"],
|
|
90
|
+
"chunks": stats["chunks"],
|
|
91
|
+
"commits": stats["commits"],
|
|
92
|
+
"derived_facts": stats["derived"],
|
|
93
|
+
"deferred_rule_pass": True,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# ---- probes -----------------------------------------------------------
|
|
97
|
+
probes = build_probes(corpus.personas, __import__("random").Random(seed))
|
|
98
|
+
by_ability: dict[str, list] = {a: [] for a in ABILITIES}
|
|
99
|
+
for p in probes:
|
|
100
|
+
by_ability[p.ability].append(p)
|
|
101
|
+
if max_probes:
|
|
102
|
+
for a in ABILITIES:
|
|
103
|
+
by_ability[a] = by_ability[a][:max_probes]
|
|
104
|
+
res.n_questions = sum(len(v) for v in by_ability.values())
|
|
105
|
+
|
|
106
|
+
# ---- baseline indexes ---------------------------------------------------
|
|
107
|
+
chunks_by_user: dict[str, list[dict]] = {}
|
|
108
|
+
for user_id, date, msgs in corpus.sessions:
|
|
109
|
+
docs = chunks_by_user.setdefault(user_id, [])
|
|
110
|
+
for i, (role, text) in enumerate(msgs):
|
|
111
|
+
if len(text) > 12: # skip tiny interjections
|
|
112
|
+
docs.append({"id": f"{user_id}:{len(docs)}", "text": text})
|
|
113
|
+
bm25 = {uid: BM25Index(docs) for uid, docs in chunks_by_user.items()}
|
|
114
|
+
|
|
115
|
+
# ---- evaluate ---------------------------------------------------------
|
|
116
|
+
per_system: dict[str, dict[str, float]] = {s: {a: 0.0 for a in ABILITIES}
|
|
117
|
+
for s in systems}
|
|
118
|
+
counts = {a: len(by_ability[a]) for a in ABILITIES}
|
|
119
|
+
details: list[dict] = []
|
|
120
|
+
latencies = {s: [] for s in systems}
|
|
121
|
+
provenance_ok = 0
|
|
122
|
+
provenance_checks = 0
|
|
123
|
+
|
|
124
|
+
for ability in ABILITIES:
|
|
125
|
+
for probe in by_ability[ability]:
|
|
126
|
+
for system in systems:
|
|
127
|
+
t_q = time.time()
|
|
128
|
+
if system == "context_m":
|
|
129
|
+
out = memory.search(probe.question, user_id=probe.user_id, k=12)
|
|
130
|
+
context = out["context_block"]
|
|
131
|
+
provenance_checks += 1
|
|
132
|
+
if out["provenance"]["verification"]:
|
|
133
|
+
provenance_ok += 1
|
|
134
|
+
elif system == "vector_only":
|
|
135
|
+
context = vector_only_context(memory, probe.question,
|
|
136
|
+
probe.user_id)
|
|
137
|
+
elif system == "bm25":
|
|
138
|
+
context = bm25_context(bm25[probe.user_id], probe.question)
|
|
139
|
+
latencies[system].append(time.time() - t_q)
|
|
140
|
+
score_fn = llm_judge if llm_judge else judge
|
|
141
|
+
score, detail = score_fn(probe, context)
|
|
142
|
+
per_system[system][ability] += score
|
|
143
|
+
if system == "context_m":
|
|
144
|
+
details.append({"ability": ability,
|
|
145
|
+
"question": probe.question,
|
|
146
|
+
"score": score, "detail": detail,
|
|
147
|
+
"context": context[:400]})
|
|
148
|
+
|
|
149
|
+
for system in systems:
|
|
150
|
+
res.per_system[system] = {
|
|
151
|
+
"overall": round(sum(per_system[system][a] for a in ABILITIES)
|
|
152
|
+
/ max(res.n_questions, 1), 4),
|
|
153
|
+
"per_ability": {
|
|
154
|
+
a: round(per_system[system][a] / max(counts[a], 1), 4)
|
|
155
|
+
for a in ABILITIES if counts[a]
|
|
156
|
+
},
|
|
157
|
+
"mean_latency_ms": round(
|
|
158
|
+
sum(latencies[system]) / max(len(latencies[system]), 1) * 1e3, 2),
|
|
159
|
+
}
|
|
160
|
+
res.per_ability = res.per_system.get("context_m", {}).get("per_ability", {})
|
|
161
|
+
res.trust = {
|
|
162
|
+
"provenance_completeness": round(
|
|
163
|
+
provenance_ok / max(provenance_checks, 1), 4),
|
|
164
|
+
"audit_latency_ms": round(
|
|
165
|
+
sum(latencies.get("context_m", [])) /
|
|
166
|
+
max(len(latencies.get("context_m", [])), 1) * 1e3, 2),
|
|
167
|
+
"u0_ingest_llm_calls": metrics.counters()["llm_calls"],
|
|
168
|
+
"storage": memory.storage_stats(),
|
|
169
|
+
"hash_provider": stats["hash_provider"],
|
|
170
|
+
"codec": stats["codec"],
|
|
171
|
+
"vsa_mode": stats["vsa_mode"],
|
|
172
|
+
"wall_seconds_total": round(time.time() - t0, 2),
|
|
173
|
+
}
|
|
174
|
+
memory.close()
|
|
175
|
+
res.details = details # type: ignore[attr-defined]
|
|
176
|
+
return res
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def format_report(results: list[BucketResult]) -> str:
|
|
180
|
+
lines = ["# Context-M — BEAM-Style Benchmark Results", ""]
|
|
181
|
+
for r in results:
|
|
182
|
+
lines.append(f"## Bucket: {r.bucket.upper()} "
|
|
183
|
+
f"({r.corpus['estimated_tokens']:,} est. tokens, "
|
|
184
|
+
f"{r.n_questions} questions)")
|
|
185
|
+
lines.append("")
|
|
186
|
+
sys_names = list(r.per_system.keys())
|
|
187
|
+
header = "| System | Overall | " + " | ".join(ABILITIES) + " |"
|
|
188
|
+
sep = "|---" * (2 + len(ABILITIES)) + "|"
|
|
189
|
+
lines.append(header)
|
|
190
|
+
lines.append(sep)
|
|
191
|
+
for s in sys_names:
|
|
192
|
+
d = r.per_system[s]
|
|
193
|
+
row = [f"**{d['overall']:.1%}**" if s == "context_m" else f"{d['overall']:.1%}"]
|
|
194
|
+
for a in ABILITIES:
|
|
195
|
+
v = d["per_ability"].get(a)
|
|
196
|
+
row.append(f"{v:.0%}" if v is not None else "—")
|
|
197
|
+
lines.append(f"| {s} | " + " | ".join(row) + " |")
|
|
198
|
+
lines.append("")
|
|
199
|
+
lines.append(f"- Ingest: {r.ingest['wall_seconds']}s for "
|
|
200
|
+
f"{r.ingest['tokens_per_second']:,} tokens/s "
|
|
201
|
+
f"(μ=0: {r.ingest['u0_protocol']}, "
|
|
202
|
+
f"{r.ingest['llm_calls']} LLM calls)")
|
|
203
|
+
lines.append(f"- Memory: {r.ingest['facts']:,} facts / "
|
|
204
|
+
f"{r.ingest['chunks']:,} chunks / "
|
|
205
|
+
f"{r.ingest['commits']:,} commits "
|
|
206
|
+
f"({r.ingest['derived_facts']} derived by Datalog)")
|
|
207
|
+
lines.append(f"- Provenance completeness: "
|
|
208
|
+
f"{r.trust['provenance_completeness']:.1%} | "
|
|
209
|
+
f"retrieval latency p50≈{r.trust['audit_latency_ms']}ms")
|
|
210
|
+
lines.append("")
|
|
211
|
+
return "\n".join(lines)
|
cortexm/bench/messy.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"""Messy persona generator — slang / compound sentences / misspellings.
|
|
2
|
+
|
|
3
|
+
The default ``cortexm.bench.generator`` produces clean, grammatical
|
|
4
|
+
persona messages ("My name is Alice. I work at Google as a software
|
|
5
|
+
engineer."). On that corpus, the μ=0 pattern extractor scores ~1.0
|
|
6
|
+
recall because every fact sits in a clean SVO clause. Unmess + dissim
|
|
7
|
+
have nothing to fix.
|
|
8
|
+
|
|
9
|
+
This module applies a *messifier* to the same persona timeline:
|
|
10
|
+
- run-on compound sentences stitched with "and" / "so" / "ngl" / "tbh"
|
|
11
|
+
- slang tokens: bruh, ngl, tbh, fr fr, no cap, smh, rn, nvm, wyd, ykwis
|
|
12
|
+
- common misspellings: defo, prolly, kinda, kinda-sorta, tmr, b4, 2
|
|
13
|
+
- text-speak: u / ur / 2 / 4 / k / lol / omg / lmao / rn
|
|
14
|
+
- code-switching: inject "yo", "tbh idk", "ngl that's wild" mid-sentence
|
|
15
|
+
- contraction chains: "I'ma", "I'd've", "shouldn't've"
|
|
16
|
+
- capitalization chaos: drop sentence-initial caps, ALL-CAPS bursts
|
|
17
|
+
- emoji-free but punctuation-sparse: drop periods, use commas instead
|
|
18
|
+
|
|
19
|
+
The output is intentionally HARD for clean pattern matchers — the
|
|
20
|
+
unmess / dissim / fuzzy / idiolect stack has actual work to do. This
|
|
21
|
+
is what the user asked for ("current synthetic corpus is too clean
|
|
22
|
+
to show the win") so the BEAM numbers actually move when the arxiv
|
|
23
|
+
improvements are toggled on.
|
|
24
|
+
|
|
25
|
+
Deterministic (seeded). Same persona → same messified text.
|
|
26
|
+
"""
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import random
|
|
30
|
+
from dataclasses import dataclass
|
|
31
|
+
|
|
32
|
+
from cortexm.bench.generator import (
|
|
33
|
+
Persona, make_persona, _month, SMALLTALK,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# slang / filler / discourse markers — injected at clause boundaries
|
|
38
|
+
SLANG_FILLERS = [
|
|
39
|
+
"ngl", "tbh", "fr fr", "no cap", "lowkey", "highkey", "smh",
|
|
40
|
+
"rn", "nvm", "wyd", "ykwis", "lowkey", "istg", "bruh", "bro",
|
|
41
|
+
"ye", "yeah", "ya", "ok so", "anyway", "like", "tbh idk",
|
|
42
|
+
"ngl that's wild", "ya feel me", "y'know", "like honestly",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
# text-speak substitutions (applied via simple regex / str.replace)
|
|
46
|
+
TEXTSPEAK = {
|
|
47
|
+
"you": "u", "your": "ur", "you're": "ur", "you are": "u r",
|
|
48
|
+
"to": "2", "too": "2", "for": "4", "before": "b4",
|
|
49
|
+
"tomorrow": "tmr", "definitely": "defo", "probably": "prolly",
|
|
50
|
+
"kind of": "kinda", "sort of": "sorta", "give me": "gimme",
|
|
51
|
+
"let me": "lemme", "want to": "wanna", "going to": "gonna",
|
|
52
|
+
"got to": "gotta", "out of": "outta", "because": "bc",
|
|
53
|
+
"don't know": "dk", "i don't know": "idk", "right now": "rn",
|
|
54
|
+
"with": "w/", "without": "w/o", "people": "ppl", "thanks": "thx",
|
|
55
|
+
"okay": "k", "ok": "k", "really": "rly", "though": "tho",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# common misspellings applied with low probability per word
|
|
59
|
+
MISSPELLINGS = {
|
|
60
|
+
"the": "teh", "and": "an", "is": "iz", "my": "mah",
|
|
61
|
+
"like": "liek", "work": "wrk", "at": "@", "name": "naem",
|
|
62
|
+
"live": "liv", "sister": "sis", "brother": "bro",
|
|
63
|
+
"manager": "mgr", "team": "tm", "started": "strt'd",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# discourse markers for compound sentence stitching
|
|
67
|
+
STITCHERS = [
|
|
68
|
+
"and tbh", "so like", "and ykwis", "ngl", "and then",
|
|
69
|
+
"and like", "so anyway", "and ya", "but like", "and ok",
|
|
70
|
+
"so basically", "and honestly", "but fr", "so ngl",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
# Casual smalltalk that's even less grammatical
|
|
74
|
+
MESSY_SMALLTALK = [
|
|
75
|
+
"yo wassup", "ayy hows it goin", "lol ok", "smh not again",
|
|
76
|
+
"ngl thats wild", "bruh fr??", "ok ok i got u", "ye i feel u",
|
|
77
|
+
"lol same", "rip", "yooo thats crazy", "ok wait what", "tbh idk man",
|
|
78
|
+
"ya im here", "eh could be worse", "ok cool cool cool", "ha nice",
|
|
79
|
+
"ngl kinda tired tbh", "wait gimme a sec", "ya ya sry", "eh whatever",
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@dataclass
|
|
84
|
+
class MessyCorpus:
|
|
85
|
+
"""Result of messifying — keeps the underlying persona for ground
|
|
86
|
+
truth comparison. Same shape as the clean persona dict so the
|
|
87
|
+
existing BEAM benchmark can consume it unchanged."""
|
|
88
|
+
user_id: str
|
|
89
|
+
text: str
|
|
90
|
+
facts: list
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _messify_text(text: str, rng: random.Random,
|
|
94
|
+
textspeak_p: float = 0.45,
|
|
95
|
+
misspell_p: float = 0.10,
|
|
96
|
+
filler_p: float = 0.55) -> str:
|
|
97
|
+
"""Apply slang / textspeak / misspellings / fillers to a clean text.
|
|
98
|
+
|
|
99
|
+
Probabilities are tuned so the result is recognizable but messy:
|
|
100
|
+
- textspeak_p: chance per word to apply a text-speak substitution
|
|
101
|
+
- misspell_p: chance per word to apply a common misspelling
|
|
102
|
+
- filler_p: chance at each clause boundary to inject a slang filler
|
|
103
|
+
"""
|
|
104
|
+
if not text:
|
|
105
|
+
return text
|
|
106
|
+
|
|
107
|
+
# 1) text-speak substitutions (word-boundary preserving)
|
|
108
|
+
out = text
|
|
109
|
+
for src, dst in TEXTSPEAK.items():
|
|
110
|
+
if rng.random() < textspeak_p:
|
|
111
|
+
# case-insensitive replace, preserve first-letter case
|
|
112
|
+
import re
|
|
113
|
+
def _sub(m, dst=dst):
|
|
114
|
+
w = m.group(0)
|
|
115
|
+
return dst if w.islower() else dst.capitalize()
|
|
116
|
+
out = re.sub(rf"\b{src}\b", _sub, out, flags=re.IGNORECASE)
|
|
117
|
+
|
|
118
|
+
# 2) word-level misspellings
|
|
119
|
+
words = out.split()
|
|
120
|
+
out_words = []
|
|
121
|
+
for w in words:
|
|
122
|
+
# strip trailing punctuation for lookup, keep it
|
|
123
|
+
import re as _re
|
|
124
|
+
m = _re.match(r"^(\W*)(\w+)(\W*)$", w)
|
|
125
|
+
if not m:
|
|
126
|
+
out_words.append(w)
|
|
127
|
+
continue
|
|
128
|
+
pre, core, post = m.groups()
|
|
129
|
+
lower = core.lower()
|
|
130
|
+
if lower in MISSPELLINGS and rng.random() < misspell_p:
|
|
131
|
+
new_core = MISSPELLINGS[lower]
|
|
132
|
+
# preserve capitalization of first letter
|
|
133
|
+
if core[0].isupper():
|
|
134
|
+
new_core = new_core[:1].upper() + new_core[1:]
|
|
135
|
+
out_words.append(pre + new_core + post)
|
|
136
|
+
else:
|
|
137
|
+
out_words.append(w)
|
|
138
|
+
out = " ".join(out_words)
|
|
139
|
+
|
|
140
|
+
# 3) drop sentence-final periods with some probability (text-style)
|
|
141
|
+
if rng.random() < 0.6:
|
|
142
|
+
out = out.replace(".", "")
|
|
143
|
+
out = out.replace("!", "")
|
|
144
|
+
out = out.replace("?", "")
|
|
145
|
+
|
|
146
|
+
# 4) lowercase sentence-initial letters with some probability
|
|
147
|
+
if out and rng.random() < 0.5:
|
|
148
|
+
out = out[0].lower() + out[1:]
|
|
149
|
+
|
|
150
|
+
# 5) inject slang fillers at clause boundaries (commas / conjunctions)
|
|
151
|
+
import re
|
|
152
|
+
# split keeping delimiters
|
|
153
|
+
parts = re.split(r"(\s+(?:and|but|so|because|although|while|when|if)\s+)", out)
|
|
154
|
+
out_parts = []
|
|
155
|
+
for i, p in enumerate(parts):
|
|
156
|
+
out_parts.append(p)
|
|
157
|
+
# if this is a conjunction delimiter and next part is non-empty,
|
|
158
|
+
# maybe inject a filler after the conjunction
|
|
159
|
+
if i % 2 == 1 and rng.random() < filler_p:
|
|
160
|
+
filler = rng.choice(SLANG_FILLERS)
|
|
161
|
+
out_parts.append(f" {filler} ")
|
|
162
|
+
out = "".join(out_parts)
|
|
163
|
+
|
|
164
|
+
# 6) occasional ALL-CAPS burst on short sentences (≤4 words) for emphasis
|
|
165
|
+
words_now = out.split()
|
|
166
|
+
if len(words_now) <= 4 and rng.random() < 0.15:
|
|
167
|
+
out = out.upper()
|
|
168
|
+
|
|
169
|
+
return out.strip()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def messify_messages(persona: Persona, rng: random.Random,
|
|
173
|
+
session_date, part: int) -> list[tuple[str, str]]:
|
|
174
|
+
"""Run the clean persona_messages generator, then messify each line.
|
|
175
|
+
|
|
176
|
+
Returns the same list-of-(role, text) shape, but each text is
|
|
177
|
+
slang-ified. Ground truth is still derivable from the persona.
|
|
178
|
+
"""
|
|
179
|
+
from cortexm.bench.generator import persona_messages
|
|
180
|
+
clean = persona_messages(persona, rng, session_date, part)
|
|
181
|
+
messy = []
|
|
182
|
+
for role, text in clean:
|
|
183
|
+
if role == "user":
|
|
184
|
+
mtext = _messify_text(text, rng)
|
|
185
|
+
messy.append((role, mtext))
|
|
186
|
+
else:
|
|
187
|
+
messy.append((role, text))
|
|
188
|
+
# splice in extra messy smalltalk
|
|
189
|
+
for _ in range(rng.randrange(1, 3)):
|
|
190
|
+
messy.append(("user", rng.choice(MESSY_SMALLTALK)))
|
|
191
|
+
return messy
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def make_messy_persona(rng: random.Random, idx: int, t0) -> Persona:
|
|
195
|
+
"""Same persona generator as the clean one — personas themselves
|
|
196
|
+
don't need to be messy, only the surface text does."""
|
|
197
|
+
return make_persona(rng, idx, t0)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# Tiny end-to-end demo used by the BEAM bench harness when `--messy` is
|
|
201
|
+
# passed — converts a clean persona dict into a messy one.
|
|
202
|
+
def messify_persona_dict(p: dict, rng: random.Random) -> dict:
|
|
203
|
+
"""Take a {user_id, text, facts} dict from the clean persona
|
|
204
|
+
generator and produce a messified copy. Facts list is preserved
|
|
205
|
+
(ground truth unchanged)."""
|
|
206
|
+
return {
|
|
207
|
+
"user_id": p["user_id"],
|
|
208
|
+
"text": _messify_text(p["text"], rng),
|
|
209
|
+
"facts": p["facts"],
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
__all__ = [
|
|
214
|
+
"MessyCorpus", "make_messy_persona", "messify_messages",
|
|
215
|
+
"messify_persona_dict", "_messify_text",
|
|
216
|
+
"SLANG_FILLERS", "TEXTSPEAK", "MISSPELLINGS", "STITCHERS",
|
|
217
|
+
"MESSY_SMALLTALK",
|
|
218
|
+
]
|
cortexm/bench/micro.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Micro-benchmarks — the engineering claims behind the fabric.
|
|
2
|
+
|
|
3
|
+
1. Retrieval latency & tree-index recall at 10K/50K/100K vectors
|
|
4
|
+
(plan milestone: <1ms retrieval on 100K memories)
|
|
5
|
+
2. Codec ablation: recall@10 vs FP32 brute force + bytes/vector
|
|
6
|
+
(the cortexm-compress tier table)
|
|
7
|
+
3. Self-healing memory: recall under bit-flip corruption, with and
|
|
8
|
+
without TMR, before/after healing (the "Proof of God" demo)
|
|
9
|
+
4. SLB hit rate & latency under conversational locality replay
|
|
10
|
+
5. Ingest throughput (tokens/s, facts/s) and μ=0 assertion
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import time
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
from cortexm import metrics
|
|
20
|
+
from cortexm.config import Config
|
|
21
|
+
from cortexm.vsa.codecs import make_codec
|
|
22
|
+
from cortexm.vsa.ops import VSA
|
|
23
|
+
from cortexm.text.embedder import HashingEmbedder
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _synthetic_corpus(n: int, dims: int = 768, seed: int = 7):
|
|
27
|
+
"""n fact holograms with genuine kNN structure: facts about the same
|
|
28
|
+
subject share lexical content, so true neighbors = same-subject facts."""
|
|
29
|
+
rng = np.random.default_rng(seed)
|
|
30
|
+
emb = HashingEmbedder(dims, seed)
|
|
31
|
+
vsa = VSA(dims, "perm", seed)
|
|
32
|
+
n_subj = max(50, n // 40)
|
|
33
|
+
subjects = [f"Person{i}" for i in range(n_subj)]
|
|
34
|
+
relations = ["works_at", "lives_in", "prefers", "has_skill", "event"]
|
|
35
|
+
facts, vecs = [], []
|
|
36
|
+
for i in range(n):
|
|
37
|
+
s = subjects[i % n_subj]
|
|
38
|
+
r = relations[i % len(relations)]
|
|
39
|
+
v = f"{s}-thing{(i // n_subj) % 40}"
|
|
40
|
+
facts.append((s, r, v))
|
|
41
|
+
vecs.append(vsa.encode_fact(emb.embed(s), emb.embed(r), emb.embed(v)))
|
|
42
|
+
return facts, np.stack(vecs), emb
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def latency_and_recall():
|
|
46
|
+
from cortexm.vsa.index import TreeIndex
|
|
47
|
+
|
|
48
|
+
out = {}
|
|
49
|
+
for n in (10_000, 50_000, 100_000):
|
|
50
|
+
facts, vecs, emb = _synthetic_corpus(n)
|
|
51
|
+
ids = [f"fact{i}" for i in range(n)]
|
|
52
|
+
codec = make_codec("int8", 768)
|
|
53
|
+
packed = np.stack([codec.encode_packed(v) for v in vecs])
|
|
54
|
+
aux = np.array([codec.encode_scale(v) for v in vecs])
|
|
55
|
+
|
|
56
|
+
def getter(rows):
|
|
57
|
+
return packed[rows], aux[rows]
|
|
58
|
+
|
|
59
|
+
# queries: text of a random fact (its own hologram is the target)
|
|
60
|
+
rng = np.random.default_rng(11)
|
|
61
|
+
q_idx = rng.choice(n, 100, replace=False)
|
|
62
|
+
queries = [emb.embed(" ".join(facts[i])) for i in q_idx]
|
|
63
|
+
|
|
64
|
+
# brute force ground truth
|
|
65
|
+
t0 = time.perf_counter()
|
|
66
|
+
gt, gt_scores = [], []
|
|
67
|
+
for q in queries:
|
|
68
|
+
sc = codec.scores(packed, q, aux)
|
|
69
|
+
top = np.argsort(-sc)[:10]
|
|
70
|
+
gt.append(set(top.tolist()))
|
|
71
|
+
gt_scores.append(float(np.sort(sc)[::-1][:10].mean()))
|
|
72
|
+
flat_ms = (time.perf_counter() - t0) / len(queries) * 1e3
|
|
73
|
+
|
|
74
|
+
# tree index
|
|
75
|
+
t0 = time.perf_counter()
|
|
76
|
+
idx = TreeIndex(codec, getter, n, branch=8, leaf=512, seed=3)
|
|
77
|
+
idx.build()
|
|
78
|
+
build_s = time.perf_counter() - t0
|
|
79
|
+
|
|
80
|
+
t0 = time.perf_counter()
|
|
81
|
+
hits = 0
|
|
82
|
+
overlap = 0.0
|
|
83
|
+
quality = 0.0
|
|
84
|
+
lat = []
|
|
85
|
+
for q, want, want_score in zip(queries, gt, gt_scores):
|
|
86
|
+
t1 = time.perf_counter()
|
|
87
|
+
rows, scores = idx.search(q, 10, beam=8)
|
|
88
|
+
lat.append(time.perf_counter() - t1)
|
|
89
|
+
got = set(rows.tolist())
|
|
90
|
+
if got & want:
|
|
91
|
+
hits += 1
|
|
92
|
+
overlap += len(got & want) / 10
|
|
93
|
+
if len(scores):
|
|
94
|
+
quality += float(np.sort(scores)[::-1][:10].mean()) / max(want_score, 1e-9)
|
|
95
|
+
lat = np.array(lat) * 1e3
|
|
96
|
+
out[f"n={n}"] = {
|
|
97
|
+
"flat_ms": round(flat_ms, 2),
|
|
98
|
+
"tree_p50_ms": round(float(np.percentile(lat, 50)), 3),
|
|
99
|
+
"tree_p99_ms": round(float(np.percentile(lat, 99)), 3),
|
|
100
|
+
"any_hit@10": round(hits / len(queries), 4),
|
|
101
|
+
"overlap@10": round(overlap / len(queries), 4),
|
|
102
|
+
"quality_ratio": round(quality / len(queries), 4),
|
|
103
|
+
"index_build_s": round(build_s, 2),
|
|
104
|
+
"rows_scanned_avg": round(idx.leaf_rows_scanned / len(queries), 0),
|
|
105
|
+
}
|
|
106
|
+
return out
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def codec_ablation(n: int = 20_000, k: int = 10):
|
|
110
|
+
emb = HashingEmbedder(768, 7)
|
|
111
|
+
vsa = VSA(768, "perm", 7)
|
|
112
|
+
n_subj = max(50, n // 40)
|
|
113
|
+
subjects = [f"Person{i}" for i in range(n_subj)]
|
|
114
|
+
relations = ["works_at", "lives_in", "prefers", "has_skill", "event"]
|
|
115
|
+
vecs, queries = [], []
|
|
116
|
+
for i in range(n):
|
|
117
|
+
s = subjects[i % n_subj]
|
|
118
|
+
r = relations[i % len(relations)]
|
|
119
|
+
v = f"{s}-thing{(i // n_subj) % 40}"
|
|
120
|
+
vecs.append(vsa.encode_fact(emb.embed(s), emb.embed(r), emb.embed(v)))
|
|
121
|
+
if i % (n // 200) == 0:
|
|
122
|
+
queries.append((vecs[-1], i))
|
|
123
|
+
vecs = np.stack(vecs)
|
|
124
|
+
|
|
125
|
+
gt = []
|
|
126
|
+
for q, i in queries:
|
|
127
|
+
sc = vecs @ q
|
|
128
|
+
top = np.argsort(-sc)[:k].tolist()
|
|
129
|
+
gt.append((i, set(top)))
|
|
130
|
+
|
|
131
|
+
out = {}
|
|
132
|
+
for name in ("int8", "binary", "rabitq", "pq"):
|
|
133
|
+
t0 = time.perf_counter()
|
|
134
|
+
codec = make_codec(name, 768, seed=7)
|
|
135
|
+
if name == "pq":
|
|
136
|
+
codec.train(vecs[: min(n, 4096)])
|
|
137
|
+
packed = np.stack([codec.encode_packed(v) for v in vecs])
|
|
138
|
+
enc_s = time.perf_counter() - t0
|
|
139
|
+
# hologram probes: self-hit + neighborhood preservation vs fp32
|
|
140
|
+
hits = 0
|
|
141
|
+
overlap = 0.0
|
|
142
|
+
shortlist = 0.0
|
|
143
|
+
t0 = time.perf_counter()
|
|
144
|
+
if name == "int8":
|
|
145
|
+
aux = np.array([codec.encode_scale(v) for v in vecs])
|
|
146
|
+
for (q, i), (self_idx, want) in zip(queries, gt):
|
|
147
|
+
sc = (codec.scores(packed, q, aux) if name == "int8"
|
|
148
|
+
else codec.scores(packed, q))
|
|
149
|
+
order = np.argsort(-sc)
|
|
150
|
+
got = order[:k].tolist()
|
|
151
|
+
if i in got:
|
|
152
|
+
hits += 1
|
|
153
|
+
overlap += len(set(got) & want) / k
|
|
154
|
+
# shortlist usage: fp32 top-10 within codec top-50
|
|
155
|
+
shortlist += len(set(order[:50].tolist()) & want) / k
|
|
156
|
+
out[name] = {
|
|
157
|
+
"bytes_per_vector": codec.bytes_per_vector,
|
|
158
|
+
"mb_per_million": round(codec.bytes_per_vector, 1),
|
|
159
|
+
"self_hit@10": round(hits / len(queries), 4),
|
|
160
|
+
"overlap@10_vs_fp32": round(overlap / len(queries), 4),
|
|
161
|
+
"recall@10_in_top50": round(shortlist / len(queries), 4),
|
|
162
|
+
"encode_ms_per_1k": round(enc_s / n * 1000 * 1e3, 2),
|
|
163
|
+
"query_ms_flat_20k": round((time.perf_counter() - t0) / len(queries) * 1e3, 3),
|
|
164
|
+
}
|
|
165
|
+
out["fp32_reference"] = {"bytes_per_vector": 3072, "mb_per_million": 3072.0,
|
|
166
|
+
"recall@10": 1.0}
|
|
167
|
+
return out
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def self_healing(n: int = 5000):
|
|
171
|
+
"""Recall under corruption — binary HDC tolerance + TMR majority vote.
|
|
172
|
+
|
|
173
|
+
Self-identification test: a corrupted hypervector must still rank
|
|
174
|
+
ITSELF as the nearest neighbor among n stored vectors (HDC's
|
|
175
|
+
error-correction radius), with and without TMR.
|
|
176
|
+
"""
|
|
177
|
+
from cortexm.vsa.codecs import BinaryCodec
|
|
178
|
+
|
|
179
|
+
emb = HashingEmbedder(768, 7)
|
|
180
|
+
vsa = VSA(768, "perm", 7)
|
|
181
|
+
n_subj = max(50, n // 40)
|
|
182
|
+
subjects = [f"Person{i}" for i in range(n_subj)]
|
|
183
|
+
vecs = []
|
|
184
|
+
for i in range(n):
|
|
185
|
+
s = subjects[i % n_subj]
|
|
186
|
+
v = f"{s}-thing{(i // n_subj) % 40}"
|
|
187
|
+
vecs.append(vsa.encode_fact(emb.embed(s), emb.embed("works_at"),
|
|
188
|
+
emb.embed(v)))
|
|
189
|
+
vecs = np.stack(vecs)
|
|
190
|
+
q_idx = list(range(0, n, 50)) # 100 probe vectors
|
|
191
|
+
|
|
192
|
+
out = {}
|
|
193
|
+
for tmr in (False, True):
|
|
194
|
+
codec = BinaryCodec(768, tmr=tmr)
|
|
195
|
+
packed = np.stack([codec.encode_packed(v) for v in vecs])
|
|
196
|
+
crng = np.random.default_rng(99)
|
|
197
|
+
for rate in (0.0, 0.01, 0.05, 0.10, 0.20):
|
|
198
|
+
corrupted = packed.copy()
|
|
199
|
+
if rate > 0:
|
|
200
|
+
for row in q_idx: # corrupt the probe vectors
|
|
201
|
+
corrupted[row] = codec.corrupt(corrupted[row], rate, crng)
|
|
202
|
+
hits = 0
|
|
203
|
+
for i in q_idx:
|
|
204
|
+
sc = codec.scores(corrupted, vecs[i])
|
|
205
|
+
if int(np.argmax(sc)) == i:
|
|
206
|
+
hits += 1
|
|
207
|
+
out[f"{'tmr' if tmr else 'plain'}@{int(rate*100)}%"] = {
|
|
208
|
+
"self_identification": round(hits / len(q_idx), 3)}
|
|
209
|
+
return out
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def slb_replay(n_queries: int = 400):
|
|
213
|
+
from cortexm.vsa.slb import SemanticLookasideBuffer
|
|
214
|
+
|
|
215
|
+
emb = HashingEmbedder(768, 7)
|
|
216
|
+
slb = SemanticLookasideBuffer(64, 0.97, 768)
|
|
217
|
+
rng = np.random.default_rng(3)
|
|
218
|
+
topics = [f"project {i} status update" for i in range(40)]
|
|
219
|
+
hit_lat, miss_lat = [], []
|
|
220
|
+
for i in range(n_queries):
|
|
221
|
+
# conversational locality: follow-ups repeat the previous topic
|
|
222
|
+
base = topics[rng.integers(len(topics))]
|
|
223
|
+
q = emb.embed(base + (" again" if i % 3 else ""))
|
|
224
|
+
t0 = time.perf_counter()
|
|
225
|
+
got = slb.lookup(q)
|
|
226
|
+
if got is not None:
|
|
227
|
+
hit_lat.append(time.perf_counter() - t0)
|
|
228
|
+
else:
|
|
229
|
+
miss_lat.append(time.perf_counter() - t0)
|
|
230
|
+
slb.store(q, [("f1", 0.5), ("f2", 0.4)])
|
|
231
|
+
return {
|
|
232
|
+
"hit_rate": round(slb.hits / n_queries, 3),
|
|
233
|
+
"avg_hit_latency_us": round(sum(hit_lat) / max(len(hit_lat), 1) * 1e6, 1),
|
|
234
|
+
"avg_miss_latency_us": round(sum(miss_lat) / max(len(miss_lat), 1) * 1e6, 1),
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def run_micro() -> dict:
|
|
239
|
+
metrics.reset_counters()
|
|
240
|
+
out = {
|
|
241
|
+
"latency_recall": latency_and_recall(),
|
|
242
|
+
"codec_ablation": codec_ablation(),
|
|
243
|
+
"self_healing": self_healing(),
|
|
244
|
+
"slb_replay": slb_replay(),
|
|
245
|
+
"u0_llm_calls": metrics.llm_calls(),
|
|
246
|
+
}
|
|
247
|
+
return out
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
if __name__ == "__main__":
|
|
251
|
+
print(run_micro())
|