natc 0.1.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.
- natc/__init__.py +7 -0
- natc/attention/__init__.py +123 -0
- natc/benchmark/__init__.py +135 -0
- natc/benchmark/cli.py +30 -0
- natc/cache/__init__.py +217 -0
- natc/capsules/__init__.py +160 -0
- natc/compiler/__init__.py +135 -0
- natc/config.py +45 -0
- natc/cpu/__init__.py +80 -0
- natc/dna/__init__.py +295 -0
- natc/fractal/__init__.py +110 -0
- natc/integrations/__init__.py +16 -0
- natc/integrations/huggingface.py +61 -0
- natc/model.py +229 -0
- natc/py.typed +1 -0
- natc/synthesis/__init__.py +176 -0
- natc/utils.py +140 -0
- natc-0.1.0.dist-info/METADATA +108 -0
- natc-0.1.0.dist-info/RECORD +23 -0
- natc-0.1.0.dist-info/WHEEL +5 -0
- natc-0.1.0.dist-info/entry_points.txt +2 -0
- natc-0.1.0.dist-info/licenses/LICENSE +22 -0
- natc-0.1.0.dist-info/top_level.txt +1 -0
natc/__init__.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Predictive sparse attention routing."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from natc.utils import text_embedding
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(slots=True)
|
|
13
|
+
class SparseAttentionResult:
|
|
14
|
+
"""Sparse attention output and its selected graph."""
|
|
15
|
+
|
|
16
|
+
output: np.ndarray
|
|
17
|
+
weights: np.ndarray
|
|
18
|
+
adjacency: np.ndarray
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SparseGraphBuilder:
|
|
22
|
+
"""Build top-k token adjacency graphs from similarity scores."""
|
|
23
|
+
|
|
24
|
+
def build(self, scores: np.ndarray, *, top_k: int) -> np.ndarray:
|
|
25
|
+
if scores.ndim != 2:
|
|
26
|
+
raise ValueError("scores must be a 2D matrix")
|
|
27
|
+
top_k = max(1, min(top_k, scores.shape[1]))
|
|
28
|
+
adjacency = np.zeros_like(scores, dtype=bool)
|
|
29
|
+
indices = np.argpartition(scores, -top_k, axis=1)[:, -top_k:]
|
|
30
|
+
rows = np.arange(scores.shape[0])[:, None]
|
|
31
|
+
adjacency[rows, indices] = True
|
|
32
|
+
return adjacency
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AttentionRouter:
|
|
36
|
+
"""Predict likely attention paths before dense attention execution."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, *, top_k: int = 32) -> None:
|
|
39
|
+
self.top_k = top_k
|
|
40
|
+
self.graph_builder = SparseGraphBuilder()
|
|
41
|
+
|
|
42
|
+
def route_tokens(self, tokens: list[str], *, top_k: int | None = None) -> np.ndarray:
|
|
43
|
+
if not tokens:
|
|
44
|
+
return np.zeros((0, 0), dtype=bool)
|
|
45
|
+
embeddings = np.stack([text_embedding(token, dimensions=64) for token in tokens])
|
|
46
|
+
scores = embeddings @ embeddings.T
|
|
47
|
+
return self.graph_builder.build(scores, top_k=top_k or self.top_k)
|
|
48
|
+
|
|
49
|
+
def route_embeddings(self, embeddings: np.ndarray, *, top_k: int | None = None) -> np.ndarray:
|
|
50
|
+
embeddings = _as_2d(embeddings)
|
|
51
|
+
scores = embeddings @ embeddings.T / max(1.0, float(embeddings.shape[-1]) ** 0.5)
|
|
52
|
+
return self.graph_builder.build(scores, top_k=top_k or self.top_k)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class PredictiveAttention:
|
|
56
|
+
"""Compute sparse scaled dot-product attention using predicted top-k edges."""
|
|
57
|
+
|
|
58
|
+
def __init__(self, *, top_k: int = 32) -> None:
|
|
59
|
+
self.router = AttentionRouter(top_k=top_k)
|
|
60
|
+
|
|
61
|
+
def __call__(
|
|
62
|
+
self,
|
|
63
|
+
query: np.ndarray,
|
|
64
|
+
key: np.ndarray,
|
|
65
|
+
value: np.ndarray,
|
|
66
|
+
*,
|
|
67
|
+
top_k: int | None = None,
|
|
68
|
+
) -> SparseAttentionResult:
|
|
69
|
+
return self.forward(query, key, value, top_k=top_k)
|
|
70
|
+
|
|
71
|
+
def forward(
|
|
72
|
+
self,
|
|
73
|
+
query: np.ndarray,
|
|
74
|
+
key: np.ndarray,
|
|
75
|
+
value: np.ndarray,
|
|
76
|
+
*,
|
|
77
|
+
top_k: int | None = None,
|
|
78
|
+
) -> SparseAttentionResult:
|
|
79
|
+
query = _as_2d(query)
|
|
80
|
+
key = _as_2d(key)
|
|
81
|
+
value = _as_2d(value)
|
|
82
|
+
if query.shape[-1] != key.shape[-1]:
|
|
83
|
+
raise ValueError("query and key dimensions must match")
|
|
84
|
+
if key.shape[0] != value.shape[0]:
|
|
85
|
+
raise ValueError("key and value token counts must match")
|
|
86
|
+
|
|
87
|
+
scores = query @ key.T / max(1.0, float(query.shape[-1]) ** 0.5)
|
|
88
|
+
adjacency = SparseGraphBuilder().build(scores, top_k=top_k or self.router.top_k)
|
|
89
|
+
masked_scores = np.where(adjacency, scores, -np.inf)
|
|
90
|
+
weights = _softmax(masked_scores, axis=1)
|
|
91
|
+
output = weights @ value
|
|
92
|
+
return SparseAttentionResult(
|
|
93
|
+
output=output.astype(np.float32, copy=False),
|
|
94
|
+
weights=weights.astype(np.float32, copy=False),
|
|
95
|
+
adjacency=adjacency,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _softmax(values: np.ndarray, *, axis: int) -> np.ndarray:
|
|
100
|
+
maximum = np.max(values, axis=axis, keepdims=True)
|
|
101
|
+
shifted = np.exp(values - maximum)
|
|
102
|
+
shifted[~np.isfinite(values)] = 0.0
|
|
103
|
+
denom = shifted.sum(axis=axis, keepdims=True)
|
|
104
|
+
denom[denom == 0.0] = 1.0
|
|
105
|
+
return shifted / denom
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _as_2d(array: np.ndarray) -> np.ndarray:
|
|
109
|
+
array = np.asarray(array, dtype=np.float32)
|
|
110
|
+
if array.ndim == 1:
|
|
111
|
+
return array.reshape(1, -1)
|
|
112
|
+
if array.ndim != 2:
|
|
113
|
+
raise ValueError("attention arrays must be 1D or 2D")
|
|
114
|
+
return array
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
__all__ = [
|
|
118
|
+
"AttentionRouter",
|
|
119
|
+
"PredictiveAttention",
|
|
120
|
+
"SparseAttentionResult",
|
|
121
|
+
"SparseGraphBuilder",
|
|
122
|
+
]
|
|
123
|
+
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Benchmark suite for NATC."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import tempfile
|
|
7
|
+
import time
|
|
8
|
+
import tracemalloc
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from natc.cache import NeuralCache
|
|
15
|
+
from natc.dna import decode_model, encode_model
|
|
16
|
+
from natc.model import NATCModel
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(slots=True)
|
|
20
|
+
class BenchmarkResult:
|
|
21
|
+
"""NATC benchmark metrics."""
|
|
22
|
+
|
|
23
|
+
compression_ratio: float
|
|
24
|
+
memory_saved: float
|
|
25
|
+
speedup: float
|
|
26
|
+
cpu_efficiency: float
|
|
27
|
+
tokens_per_sec: float
|
|
28
|
+
cache_hit_ratio: float
|
|
29
|
+
latency: float
|
|
30
|
+
throughput: float
|
|
31
|
+
|
|
32
|
+
def to_dict(self) -> dict[str, float]:
|
|
33
|
+
return {
|
|
34
|
+
"compression_ratio": self.compression_ratio,
|
|
35
|
+
"memory_saved": self.memory_saved,
|
|
36
|
+
"speedup": self.speedup,
|
|
37
|
+
"cpu_efficiency": self.cpu_efficiency,
|
|
38
|
+
"tokens_per_sec": self.tokens_per_sec,
|
|
39
|
+
"cache_hit_ratio": self.cache_hit_ratio,
|
|
40
|
+
"latency": self.latency,
|
|
41
|
+
"throughput": self.throughput,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def to_json(self) -> str:
|
|
45
|
+
return json.dumps(self.to_dict(), indent=2, sort_keys=True)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BenchmarkRunner:
|
|
49
|
+
"""Run synthetic compression and inference benchmarks."""
|
|
50
|
+
|
|
51
|
+
def __init__(
|
|
52
|
+
self,
|
|
53
|
+
*,
|
|
54
|
+
layers: int = 4,
|
|
55
|
+
rows: int = 128,
|
|
56
|
+
cols: int = 128,
|
|
57
|
+
rank: int = 16,
|
|
58
|
+
seed: int = 13,
|
|
59
|
+
) -> None:
|
|
60
|
+
self.layers = layers
|
|
61
|
+
self.rows = rows
|
|
62
|
+
self.cols = cols
|
|
63
|
+
self.rank = rank
|
|
64
|
+
self.seed = seed
|
|
65
|
+
|
|
66
|
+
def run(self) -> BenchmarkResult:
|
|
67
|
+
state = self._synthetic_state()
|
|
68
|
+
|
|
69
|
+
tracemalloc.start()
|
|
70
|
+
start = time.perf_counter()
|
|
71
|
+
dense_outputs = _dense_reference(state)
|
|
72
|
+
dense_latency = time.perf_counter() - start
|
|
73
|
+
dense_current, dense_peak = tracemalloc.get_traced_memory()
|
|
74
|
+
|
|
75
|
+
start = time.perf_counter()
|
|
76
|
+
dna = encode_model(state, rank=self.rank)
|
|
77
|
+
reconstructed = decode_model(dna)
|
|
78
|
+
compressed_outputs = _dense_reference(reconstructed)
|
|
79
|
+
compressed_latency = time.perf_counter() - start
|
|
80
|
+
compressed_current, compressed_peak = tracemalloc.get_traced_memory()
|
|
81
|
+
tracemalloc.stop()
|
|
82
|
+
|
|
83
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
84
|
+
cache = NeuralCache(tmpdir)
|
|
85
|
+
cache.put("python pattern", {"value": "cached"})
|
|
86
|
+
cache.get("python pattern")
|
|
87
|
+
cache.get("python patterns")
|
|
88
|
+
cache_hit_ratio = cache.hit_ratio()
|
|
89
|
+
|
|
90
|
+
model = NATCModel.from_state_dict(state, rank=self.rank)
|
|
91
|
+
generated_start = time.perf_counter()
|
|
92
|
+
output = model.generate("Explain a Python matrix multiplication pattern", max_new_tokens=32)
|
|
93
|
+
generated_latency = max(time.perf_counter() - generated_start, 1e-9)
|
|
94
|
+
token_count = max(1, len(output.split()))
|
|
95
|
+
|
|
96
|
+
dense_memory = max(dense_peak, dense_current, 1)
|
|
97
|
+
compressed_memory = max(compressed_peak - dense_peak, compressed_current, 1)
|
|
98
|
+
memory_saved = max(0.0, 1.0 - (compressed_memory / dense_memory))
|
|
99
|
+
speedup = dense_latency / max(compressed_latency, 1e-9)
|
|
100
|
+
throughput = len(compressed_outputs) / max(compressed_latency, 1e-9)
|
|
101
|
+
cpu_efficiency = min(1.0, speedup / max(1.0, self.rank))
|
|
102
|
+
|
|
103
|
+
return BenchmarkResult(
|
|
104
|
+
compression_ratio=dna.compression_ratio(),
|
|
105
|
+
memory_saved=memory_saved,
|
|
106
|
+
speedup=speedup,
|
|
107
|
+
cpu_efficiency=cpu_efficiency,
|
|
108
|
+
tokens_per_sec=token_count / generated_latency,
|
|
109
|
+
cache_hit_ratio=cache_hit_ratio,
|
|
110
|
+
latency=compressed_latency,
|
|
111
|
+
throughput=throughput,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def _synthetic_state(self) -> dict[str, np.ndarray]:
|
|
115
|
+
rng = np.random.default_rng(self.seed)
|
|
116
|
+
state = {}
|
|
117
|
+
base_left = rng.normal(size=(self.rows, self.rank)).astype(np.float32)
|
|
118
|
+
base_right = rng.normal(size=(self.rank, self.cols)).astype(np.float32)
|
|
119
|
+
for layer in range(self.layers):
|
|
120
|
+
noise = rng.normal(scale=0.01, size=(self.rows, self.cols)).astype(np.float32)
|
|
121
|
+
state[f"layers.{layer}.weight"] = base_left @ base_right + noise
|
|
122
|
+
return state
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def run_benchmark(**kwargs: Any) -> dict[str, float]:
|
|
126
|
+
return BenchmarkRunner(**kwargs).run().to_dict()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _dense_reference(state: dict[str, np.ndarray]) -> list[np.ndarray]:
|
|
130
|
+
vector = np.ones((next(iter(state.values())).shape[1], 1), dtype=np.float32)
|
|
131
|
+
return [weight @ vector for weight in state.values()]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
__all__ = ["BenchmarkResult", "BenchmarkRunner", "run_benchmark"]
|
|
135
|
+
|
natc/benchmark/cli.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Command-line benchmark runner."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from natc.benchmark import BenchmarkRunner
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main() -> None:
|
|
11
|
+
parser = argparse.ArgumentParser(description="Run NATC benchmark suite.")
|
|
12
|
+
parser.add_argument("--layers", type=int, default=4)
|
|
13
|
+
parser.add_argument("--rows", type=int, default=128)
|
|
14
|
+
parser.add_argument("--cols", type=int, default=128)
|
|
15
|
+
parser.add_argument("--rank", type=int, default=16)
|
|
16
|
+
parser.add_argument("--seed", type=int, default=13)
|
|
17
|
+
args = parser.parse_args()
|
|
18
|
+
result = BenchmarkRunner(
|
|
19
|
+
layers=args.layers,
|
|
20
|
+
rows=args.rows,
|
|
21
|
+
cols=args.cols,
|
|
22
|
+
rank=args.rank,
|
|
23
|
+
seed=args.seed,
|
|
24
|
+
).run()
|
|
25
|
+
print(result.to_json())
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
if __name__ == "__main__":
|
|
29
|
+
main()
|
|
30
|
+
|
natc/cache/__init__.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""Neural fragment cache with RAM and persistent disk storage."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import pickle
|
|
7
|
+
import sqlite3
|
|
8
|
+
import time
|
|
9
|
+
from contextlib import closing
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
from natc.utils import cosine_similarity, stable_hash, text_embedding
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(slots=True)
|
|
20
|
+
class CacheRecord:
|
|
21
|
+
"""A stored neural fragment."""
|
|
22
|
+
|
|
23
|
+
key: str
|
|
24
|
+
text: str
|
|
25
|
+
payload: Any
|
|
26
|
+
embedding: np.ndarray
|
|
27
|
+
metadata: dict[str, Any]
|
|
28
|
+
created_at: float
|
|
29
|
+
last_accessed: float
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FragmentStore:
|
|
33
|
+
"""SQLite-backed persistent fragment store."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, path: str | Path) -> None:
|
|
36
|
+
self.path = Path(path)
|
|
37
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
self._initialize()
|
|
39
|
+
|
|
40
|
+
def put(
|
|
41
|
+
self,
|
|
42
|
+
key: str,
|
|
43
|
+
text: str,
|
|
44
|
+
payload: Any,
|
|
45
|
+
embedding: np.ndarray,
|
|
46
|
+
*,
|
|
47
|
+
metadata: dict[str, Any] | None = None,
|
|
48
|
+
) -> None:
|
|
49
|
+
now = time.time()
|
|
50
|
+
with closing(sqlite3.connect(self.path)) as conn:
|
|
51
|
+
with conn:
|
|
52
|
+
conn.execute(
|
|
53
|
+
"""
|
|
54
|
+
INSERT OR REPLACE INTO fragments
|
|
55
|
+
(key, text, payload, embedding, metadata, created_at, last_accessed)
|
|
56
|
+
VALUES (?, ?, ?, ?, ?, COALESCE((SELECT created_at FROM fragments WHERE key = ?), ?), ?)
|
|
57
|
+
""",
|
|
58
|
+
(
|
|
59
|
+
key,
|
|
60
|
+
text,
|
|
61
|
+
pickle.dumps(payload),
|
|
62
|
+
np.asarray(embedding, dtype=np.float32).tobytes(),
|
|
63
|
+
json.dumps(metadata or {}),
|
|
64
|
+
key,
|
|
65
|
+
now,
|
|
66
|
+
now,
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def get(self, key: str) -> CacheRecord | None:
|
|
71
|
+
with closing(sqlite3.connect(self.path)) as conn:
|
|
72
|
+
with conn:
|
|
73
|
+
row = conn.execute(
|
|
74
|
+
"""
|
|
75
|
+
SELECT key, text, payload, embedding, metadata, created_at, last_accessed
|
|
76
|
+
FROM fragments WHERE key = ?
|
|
77
|
+
""",
|
|
78
|
+
(key,),
|
|
79
|
+
).fetchone()
|
|
80
|
+
if row is None:
|
|
81
|
+
return None
|
|
82
|
+
conn.execute(
|
|
83
|
+
"UPDATE fragments SET last_accessed = ? WHERE key = ?",
|
|
84
|
+
(time.time(), key),
|
|
85
|
+
)
|
|
86
|
+
return _row_to_record(row)
|
|
87
|
+
|
|
88
|
+
def all(self) -> list[CacheRecord]:
|
|
89
|
+
with closing(sqlite3.connect(self.path)) as conn:
|
|
90
|
+
rows = conn.execute(
|
|
91
|
+
"""
|
|
92
|
+
SELECT key, text, payload, embedding, metadata, created_at, last_accessed
|
|
93
|
+
FROM fragments
|
|
94
|
+
"""
|
|
95
|
+
).fetchall()
|
|
96
|
+
return [_row_to_record(row) for row in rows]
|
|
97
|
+
|
|
98
|
+
def _initialize(self) -> None:
|
|
99
|
+
with closing(sqlite3.connect(self.path)) as conn:
|
|
100
|
+
with conn:
|
|
101
|
+
conn.execute(
|
|
102
|
+
"""
|
|
103
|
+
CREATE TABLE IF NOT EXISTS fragments (
|
|
104
|
+
key TEXT PRIMARY KEY,
|
|
105
|
+
text TEXT NOT NULL,
|
|
106
|
+
payload BLOB NOT NULL,
|
|
107
|
+
embedding BLOB NOT NULL,
|
|
108
|
+
metadata TEXT NOT NULL,
|
|
109
|
+
created_at REAL NOT NULL,
|
|
110
|
+
last_accessed REAL NOT NULL
|
|
111
|
+
)
|
|
112
|
+
"""
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class PatternIndexer:
|
|
117
|
+
"""Similarity lookup for cached prompt and reasoning fragments."""
|
|
118
|
+
|
|
119
|
+
def __init__(self, store: FragmentStore) -> None:
|
|
120
|
+
self.store = store
|
|
121
|
+
|
|
122
|
+
def lookup(self, text: str, *, top_k: int = 3) -> list[tuple[CacheRecord, float]]:
|
|
123
|
+
query = text_embedding(text)
|
|
124
|
+
scored = [
|
|
125
|
+
(record, cosine_similarity(query, record.embedding))
|
|
126
|
+
for record in self.store.all()
|
|
127
|
+
]
|
|
128
|
+
scored.sort(key=lambda item: item[1], reverse=True)
|
|
129
|
+
return scored[:top_k]
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class NeuralCache:
|
|
133
|
+
"""Two-level RAM plus disk neural fragment cache."""
|
|
134
|
+
|
|
135
|
+
def __init__(
|
|
136
|
+
self,
|
|
137
|
+
cache_dir: str | Path,
|
|
138
|
+
*,
|
|
139
|
+
similarity_threshold: float = 0.84,
|
|
140
|
+
max_ram_items: int = 512,
|
|
141
|
+
) -> None:
|
|
142
|
+
self.cache_dir = Path(cache_dir)
|
|
143
|
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
|
144
|
+
self.store = FragmentStore(self.cache_dir / "fragments.sqlite3")
|
|
145
|
+
self.indexer = PatternIndexer(self.store)
|
|
146
|
+
self.similarity_threshold = similarity_threshold
|
|
147
|
+
self.max_ram_items = max_ram_items
|
|
148
|
+
self._ram: dict[str, CacheRecord] = {}
|
|
149
|
+
self.hits = 0
|
|
150
|
+
self.misses = 0
|
|
151
|
+
|
|
152
|
+
def key_for(self, text: str) -> str:
|
|
153
|
+
return stable_hash({"text": text.strip().lower()})
|
|
154
|
+
|
|
155
|
+
def put(
|
|
156
|
+
self,
|
|
157
|
+
text: str,
|
|
158
|
+
payload: Any,
|
|
159
|
+
*,
|
|
160
|
+
key: str | None = None,
|
|
161
|
+
metadata: dict[str, Any] | None = None,
|
|
162
|
+
) -> str:
|
|
163
|
+
cache_key = key or self.key_for(text)
|
|
164
|
+
embedding = text_embedding(text)
|
|
165
|
+
self.store.put(cache_key, text, payload, embedding, metadata=metadata)
|
|
166
|
+
record = self.store.get(cache_key)
|
|
167
|
+
if record is not None:
|
|
168
|
+
self._remember(record)
|
|
169
|
+
return cache_key
|
|
170
|
+
|
|
171
|
+
def get(self, text_or_key: str, *, exact: bool = False) -> Any | None:
|
|
172
|
+
key = text_or_key if exact else self.key_for(text_or_key)
|
|
173
|
+
record = self._ram.get(key) or self.store.get(key)
|
|
174
|
+
if record is not None:
|
|
175
|
+
self.hits += 1
|
|
176
|
+
self._remember(record)
|
|
177
|
+
return record.payload
|
|
178
|
+
if exact:
|
|
179
|
+
self.misses += 1
|
|
180
|
+
return None
|
|
181
|
+
similar = self.lookup(text_or_key, top_k=1)
|
|
182
|
+
if similar and similar[0][1] >= self.similarity_threshold:
|
|
183
|
+
self.hits += 1
|
|
184
|
+
self._remember(similar[0][0])
|
|
185
|
+
return similar[0][0].payload
|
|
186
|
+
self.misses += 1
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
def lookup(self, text: str, *, top_k: int = 3) -> list[tuple[CacheRecord, float]]:
|
|
190
|
+
return self.indexer.lookup(text, top_k=top_k)
|
|
191
|
+
|
|
192
|
+
def hit_ratio(self) -> float:
|
|
193
|
+
total = self.hits + self.misses
|
|
194
|
+
return float(self.hits / total) if total else 0.0
|
|
195
|
+
|
|
196
|
+
def _remember(self, record: CacheRecord) -> None:
|
|
197
|
+
if len(self._ram) >= self.max_ram_items:
|
|
198
|
+
oldest = min(self._ram.values(), key=lambda item: item.last_accessed)
|
|
199
|
+
self._ram.pop(oldest.key, None)
|
|
200
|
+
self._ram[record.key] = record
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def _row_to_record(row: tuple[Any, ...]) -> CacheRecord:
|
|
204
|
+
key, text, payload_blob, embedding_blob, metadata_json, created_at, last_accessed = row
|
|
205
|
+
embedding = np.frombuffer(embedding_blob, dtype=np.float32).copy()
|
|
206
|
+
return CacheRecord(
|
|
207
|
+
key=str(key),
|
|
208
|
+
text=str(text),
|
|
209
|
+
payload=pickle.loads(payload_blob),
|
|
210
|
+
embedding=embedding,
|
|
211
|
+
metadata=json.loads(metadata_json),
|
|
212
|
+
created_at=float(created_at),
|
|
213
|
+
last_accessed=float(last_accessed),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
__all__ = ["CacheRecord", "FragmentStore", "NeuralCache", "PatternIndexer"]
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Prompt-routed reasoning capsules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from natc.utils import cosine_similarity, text_embedding
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(slots=True)
|
|
14
|
+
class ReasoningCapsule:
|
|
15
|
+
"""A lightweight expert region selected from prompt semantics."""
|
|
16
|
+
|
|
17
|
+
name: str
|
|
18
|
+
keywords: tuple[str, ...]
|
|
19
|
+
description: str
|
|
20
|
+
activation_cost: float = 1.0
|
|
21
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
22
|
+
|
|
23
|
+
def score(self, prompt: str) -> float:
|
|
24
|
+
lowered = prompt.lower()
|
|
25
|
+
keyword_hits = sum(1 for keyword in self.keywords if keyword.lower() in lowered)
|
|
26
|
+
keyword_score = keyword_hits / max(1, len(self.keywords))
|
|
27
|
+
semantic_score = cosine_similarity(
|
|
28
|
+
text_embedding(prompt),
|
|
29
|
+
text_embedding(f"{self.name} {self.description} {' '.join(self.keywords)}"),
|
|
30
|
+
)
|
|
31
|
+
return float(0.68 * keyword_score + 0.32 * max(0.0, semantic_score))
|
|
32
|
+
|
|
33
|
+
def execute(self, prompt: str, context: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
34
|
+
return {
|
|
35
|
+
"capsule": self.name,
|
|
36
|
+
"score": self.score(prompt),
|
|
37
|
+
"context": context or {},
|
|
38
|
+
"embedding": text_embedding(prompt, dimensions=32),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class MathCapsule(ReasoningCapsule):
|
|
43
|
+
def __init__(self) -> None:
|
|
44
|
+
super().__init__(
|
|
45
|
+
name="math",
|
|
46
|
+
keywords=("math", "proof", "equation", "calculate", "algebra", "geometry"),
|
|
47
|
+
description="symbolic arithmetic, equations, proofs, and quantitative reasoning",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CodeCapsule(ReasoningCapsule):
|
|
52
|
+
def __init__(self) -> None:
|
|
53
|
+
super().__init__(
|
|
54
|
+
name="code",
|
|
55
|
+
keywords=("code", "python", "function", "bug", "api", "class", "compile"),
|
|
56
|
+
description="software engineering, debugging, APIs, and programming languages",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class CreativeCapsule(ReasoningCapsule):
|
|
61
|
+
def __init__(self) -> None:
|
|
62
|
+
super().__init__(
|
|
63
|
+
name="creative",
|
|
64
|
+
keywords=("story", "write", "poem", "brand", "creative", "voice", "scene"),
|
|
65
|
+
description="creative writing, style, narrative, and ideation",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ScienceCapsule(ReasoningCapsule):
|
|
70
|
+
def __init__(self) -> None:
|
|
71
|
+
super().__init__(
|
|
72
|
+
name="science",
|
|
73
|
+
keywords=("physics", "biology", "chemistry", "experiment", "theory", "quantum"),
|
|
74
|
+
description="scientific explanation, experiments, and technical concepts",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class MedicalCapsule(ReasoningCapsule):
|
|
79
|
+
def __init__(self) -> None:
|
|
80
|
+
super().__init__(
|
|
81
|
+
name="medical",
|
|
82
|
+
keywords=("medical", "health", "symptom", "clinical", "diagnosis", "treatment"),
|
|
83
|
+
description="medical terminology, health context, and careful clinical reasoning",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class CapsuleRouter:
|
|
88
|
+
"""Score and select capsules for a prompt."""
|
|
89
|
+
|
|
90
|
+
def __init__(self, capsules: list[ReasoningCapsule] | None = None) -> None:
|
|
91
|
+
self.capsules = capsules or [
|
|
92
|
+
MathCapsule(),
|
|
93
|
+
CodeCapsule(),
|
|
94
|
+
CreativeCapsule(),
|
|
95
|
+
ScienceCapsule(),
|
|
96
|
+
MedicalCapsule(),
|
|
97
|
+
]
|
|
98
|
+
|
|
99
|
+
def route(
|
|
100
|
+
self,
|
|
101
|
+
prompt: str,
|
|
102
|
+
*,
|
|
103
|
+
top_k: int = 3,
|
|
104
|
+
threshold: float = 0.05,
|
|
105
|
+
) -> list[tuple[ReasoningCapsule, float]]:
|
|
106
|
+
scored = [(capsule, capsule.score(prompt)) for capsule in self.capsules]
|
|
107
|
+
scored.sort(key=lambda item: item[1], reverse=True)
|
|
108
|
+
selected = [(capsule, score) for capsule, score in scored[:top_k] if score >= threshold]
|
|
109
|
+
if selected:
|
|
110
|
+
return selected
|
|
111
|
+
best = scored[0]
|
|
112
|
+
return [best] if best[1] > 0.0 else []
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class CapsuleManager:
|
|
116
|
+
"""Register, activate, and inspect prompt capsules."""
|
|
117
|
+
|
|
118
|
+
def __init__(self, capsules: list[ReasoningCapsule] | None = None) -> None:
|
|
119
|
+
self.router = CapsuleRouter(capsules)
|
|
120
|
+
self.active: dict[str, float] = {}
|
|
121
|
+
|
|
122
|
+
def select(self, prompt: str, *, top_k: int = 3) -> list[ReasoningCapsule]:
|
|
123
|
+
routed = self.router.route(prompt, top_k=top_k)
|
|
124
|
+
self.active = {capsule.name: score for capsule, score in routed}
|
|
125
|
+
return [capsule for capsule, _ in routed]
|
|
126
|
+
|
|
127
|
+
def activation_vector(self) -> np.ndarray:
|
|
128
|
+
names = [capsule.name for capsule in self.router.capsules]
|
|
129
|
+
return np.asarray([self.active.get(name, 0.0) for name in names], dtype=np.float32)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class CapsuleExecutor:
|
|
133
|
+
"""Execute active capsules and merge their activation summaries."""
|
|
134
|
+
|
|
135
|
+
def execute(
|
|
136
|
+
self,
|
|
137
|
+
capsules: list[ReasoningCapsule],
|
|
138
|
+
prompt: str,
|
|
139
|
+
*,
|
|
140
|
+
context: dict[str, Any] | None = None,
|
|
141
|
+
) -> dict[str, Any]:
|
|
142
|
+
results = [capsule.execute(prompt, context=context) for capsule in capsules]
|
|
143
|
+
if not results:
|
|
144
|
+
return {"capsules": [], "activation": np.zeros(32, dtype=np.float32)}
|
|
145
|
+
activation = np.mean([item["embedding"] for item in results], axis=0)
|
|
146
|
+
return {"capsules": results, "activation": activation.astype(np.float32)}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
__all__ = [
|
|
150
|
+
"CapsuleExecutor",
|
|
151
|
+
"CapsuleManager",
|
|
152
|
+
"CapsuleRouter",
|
|
153
|
+
"CodeCapsule",
|
|
154
|
+
"CreativeCapsule",
|
|
155
|
+
"MathCapsule",
|
|
156
|
+
"MedicalCapsule",
|
|
157
|
+
"ReasoningCapsule",
|
|
158
|
+
"ScienceCapsule",
|
|
159
|
+
]
|
|
160
|
+
|