eigencore 0.4.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.
Files changed (49) hide show
  1. eigencore/__init__.py +82 -0
  2. eigencore/agent/__init__.py +45 -0
  3. eigencore/agent/executor.py +297 -0
  4. eigencore/agent/grammar.py +121 -0
  5. eigencore/agent/policy.py +262 -0
  6. eigencore/agent/react.py +261 -0
  7. eigencore/agent/tool.py +149 -0
  8. eigencore/analysis/__init__.py +3 -0
  9. eigencore/analysis/sparsity.py +184 -0
  10. eigencore/benchmark/__init__.py +33 -0
  11. eigencore/benchmark/harness.py +342 -0
  12. eigencore/cli/__init__.py +0 -0
  13. eigencore/cli/main.py +856 -0
  14. eigencore/context/__init__.py +3 -0
  15. eigencore/context/manager.py +318 -0
  16. eigencore/engine/__init__.py +3 -0
  17. eigencore/engine/inference.py +378 -0
  18. eigencore/engine/layer_skip.py +268 -0
  19. eigencore/engine/prompt_lookup.py +39 -0
  20. eigencore/engine/pruning.py +116 -0
  21. eigencore/engine/sparse_inference.py +218 -0
  22. eigencore/engine/sparsity_experiment.py +109 -0
  23. eigencore/engine/speculative.py +238 -0
  24. eigencore/fit/__init__.py +34 -0
  25. eigencore/fit/estimator.py +310 -0
  26. eigencore/fit/gguf_meta.py +130 -0
  27. eigencore/forge.py +216 -0
  28. eigencore/hal/__init__.py +3 -0
  29. eigencore/hal/_cpuid_win.py +119 -0
  30. eigencore/hal/profiler.py +349 -0
  31. eigencore/kernels/__init__.py +28 -0
  32. eigencore/kernels/q4k.py +76 -0
  33. eigencore/kernels/reference.py +168 -0
  34. eigencore/models/__init__.py +3 -0
  35. eigencore/models/registry.py +193 -0
  36. eigencore/router/__init__.py +3 -0
  37. eigencore/router/classifier.py +176 -0
  38. eigencore/training/__init__.py +3 -0
  39. eigencore/training/convergence.py +112 -0
  40. eigencore/training/trainer.py +401 -0
  41. eigencore/tui/__init__.py +5 -0
  42. eigencore/tui/chat.py +265 -0
  43. eigencore/tui/onboarding.py +134 -0
  44. eigencore/tui/theme.py +61 -0
  45. eigencore-0.4.0.dist-info/METADATA +144 -0
  46. eigencore-0.4.0.dist-info/RECORD +49 -0
  47. eigencore-0.4.0.dist-info/WHEEL +4 -0
  48. eigencore-0.4.0.dist-info/entry_points.txt +2 -0
  49. eigencore-0.4.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,130 @@
1
+ """
2
+ GGUF metadata reader - architecture-aware model geometry.
3
+
4
+ Reads general.architecture and the per-architecture keys
5
+ ({arch}.block_count, {arch}.embedding_length, {arch}.attention.head_count,
6
+ {arch}.attention.head_count_kv, {arch}.context_length) from a GGUF file.
7
+ Prefers the pure-Python gguf package; falls back to llama_cpp.Llama.metadata
8
+ when loadable that way. Returns None when neither is available.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+
16
+ from eigencore.fit.estimator import ModelGeometry
17
+
18
+
19
+ @dataclass
20
+ class GGUFModelInfo:
21
+ """Ground-truth facts read from a GGUF file."""
22
+
23
+ architecture: str | None
24
+ geometry: ModelGeometry
25
+ context_length: int | None
26
+ file_size_bytes: int | None
27
+ param_count: int | None
28
+
29
+
30
+ def _field_value(field) -> object | None:
31
+ try:
32
+ part = field.parts[field.data[0]]
33
+ if part is None:
34
+ return None
35
+ return part.tolist()
36
+ except Exception:
37
+ return None
38
+
39
+
40
+ def _from_gguf_package(path: Path) -> GGUFModelInfo | None:
41
+ try:
42
+ from gguf import GGUFReader
43
+ except ImportError:
44
+ return None
45
+ try:
46
+ reader = GGUFReader(str(path), mode="no")
47
+ except Exception:
48
+ return None
49
+ fields = {f.name: f for f in reader.fields.values()}
50
+
51
+ def get(*names: str):
52
+ for n in names:
53
+ f = fields.get(n)
54
+ if f is not None:
55
+ v = _field_value(f)
56
+ if v is not None:
57
+ return v
58
+ return None
59
+
60
+ arch = get("general.architecture")
61
+ architecture = str(arch) if arch is not None else None
62
+ prefix = f"{architecture}." if architecture else "llama."
63
+
64
+ def arch_int(*names: str) -> int | None:
65
+ for n in names:
66
+ v = get(prefix + n, n)
67
+ if v is not None:
68
+ return int(v)
69
+ return None
70
+
71
+ geometry = ModelGeometry(
72
+ num_layers=arch_int("block_count"),
73
+ hidden_size=arch_int("embedding_length"),
74
+ num_attention_heads=arch_int("attention.head_count"),
75
+ num_kv_heads=arch_int("attention.head_count_kv"),
76
+ expert_count=arch_int("expert_count"),
77
+ expert_used_count=arch_int("expert_used_count"),
78
+ )
79
+ ctx = arch_int("context_length") or arch_int("attention.context_length")
80
+ params = get("general.parameter_count")
81
+ file_size = path.stat().st_size if path.exists() else None
82
+ return GGUFModelInfo(
83
+ architecture=architecture,
84
+ geometry=geometry,
85
+ context_length=ctx,
86
+ file_size_bytes=file_size,
87
+ param_count=int(params) if params is not None else None,
88
+ )
89
+
90
+
91
+ def _from_llama_metadata(path: Path) -> GGUFModelInfo | None:
92
+ try:
93
+ from llama_cpp import Llama
94
+ except ImportError:
95
+ return None
96
+ try:
97
+ llm = Llama(model_path=str(path), n_ctx=256, verbose=False)
98
+ meta = dict(llm.metadata or {})
99
+ except Exception:
100
+ return None
101
+ arch = meta.get("general.architecture")
102
+ prefix = f"{arch}." if arch else "llama."
103
+
104
+ def m(name: str) -> int | None:
105
+ v = meta.get(prefix + name, meta.get(name))
106
+ return int(v) if v is not None else None
107
+
108
+ geometry = ModelGeometry(
109
+ num_layers=m("block_count"),
110
+ hidden_size=m("embedding_length"),
111
+ num_attention_heads=m("attention.head_count"),
112
+ num_kv_heads=m("attention.head_count_kv"),
113
+ expert_count=m("expert_count"),
114
+ expert_used_count=m("expert_used_count"),
115
+ )
116
+ return GGUFModelInfo(
117
+ architecture=arch,
118
+ geometry=geometry,
119
+ context_length=m("context_length"),
120
+ file_size_bytes=path.stat().st_size if path.exists() else None,
121
+ param_count=None,
122
+ )
123
+
124
+
125
+ def read_gguf_model_info(path: str | Path) -> GGUFModelInfo | None:
126
+ """Best-effort GGUF metadata read; None when the file cannot be parsed."""
127
+ path = Path(path)
128
+ if not path.exists():
129
+ return None
130
+ return _from_gguf_package(path) or _from_llama_metadata(path)
eigencore/forge.py ADDED
@@ -0,0 +1,216 @@
1
+ """
2
+ EigenCore — the unified API surface.
3
+
4
+ Ties together all 4 layers into a single interface:
5
+ Layer 1: Hardware profiling (auto-detect and configure)
6
+ Layer 2: Inference (model loading, generation, streaming)
7
+ Layer 3: Training (QLoRA fine-tuning with adaptive epochs)
8
+ Layer 4: Agent orchestration (routing, context, tools) — Phase 3
9
+
10
+ Usage:
11
+ from eigencore import Forge
12
+
13
+ forge = Forge() # auto-profiles hardware
14
+ print(forge.profile.summary()) # see what you're working with
15
+
16
+ result = forge.generate("Hello world") # auto-downloads + runs best model
17
+ print(result.text)
18
+
19
+ for token in forge.stream("Tell me about Python"):
20
+ print(token, end="")
21
+
22
+ decision = forge.route("Write a sorting algorithm")
23
+ print(decision.domain, decision.model.name)
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from pathlib import Path
29
+ from typing import Iterator, Optional
30
+
31
+ from eigencore.agent.executor import ToolExecutor
32
+ from eigencore.agent.policy import PolicyEngine
33
+ from eigencore.agent.react import AgentResult, ReActAgent
34
+ from eigencore.agent.tool import ToolRegistry
35
+ from eigencore.context.manager import ContextManager
36
+ from eigencore.engine.inference import GenerationConfig, GenerationResult, InferenceEngine
37
+ from eigencore.hal.profiler import HardwareProfile, profile_hardware
38
+ from eigencore.models.registry import ModelRegistry, ModelSpec
39
+ from eigencore.router.classifier import RoutingDecision, TaskRouter
40
+
41
+
42
+ class Forge:
43
+ """
44
+ Main entry point for the EigenCore framework.
45
+
46
+ Auto-detects hardware, manages models, and provides inference +
47
+ routing through a single API.
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ model_name: Optional[str] = None,
53
+ task: str = "general",
54
+ cache_dir: Optional[Path] = None,
55
+ context_db: Optional[Path] = None,
56
+ ):
57
+ self.profile: HardwareProfile = profile_hardware()
58
+ self.registry = ModelRegistry(cache_dir)
59
+ self.router = TaskRouter(self.profile, self.registry)
60
+ self.context = ContextManager(
61
+ max_context_tokens=self.profile.recommended_context_length,
62
+ db_path=context_db,
63
+ )
64
+
65
+ self._engine: Optional[InferenceEngine] = None
66
+ self._current_model: Optional[ModelSpec] = None
67
+ self._default_task = task
68
+
69
+ if model_name:
70
+ self._load_model(model_name)
71
+
72
+ def _load_model(self, name: Optional[str] = None, task: Optional[str] = None) -> None:
73
+ """Load a model by name or auto-select based on hardware + task."""
74
+ spec = self.registry.resolve(name, self.profile, task or self._default_task)
75
+
76
+ if self._current_model and self._current_model.name == spec.name:
77
+ return # already loaded
78
+
79
+ # unload previous model
80
+ if self._engine and self._engine.is_loaded:
81
+ self._engine.unload()
82
+
83
+ # download if needed
84
+ if not self.registry.is_downloaded(spec):
85
+ self.registry.download(spec)
86
+
87
+ model_path = spec.local_path(self.registry.cache_dir)
88
+ self._engine = InferenceEngine(model_path, self.profile, spec)
89
+ self._engine.load()
90
+ self._current_model = spec
91
+
92
+ def _ensure_loaded(self, prompt: Optional[str] = None) -> None:
93
+ """Ensure a model is loaded, using routing if prompt is provided."""
94
+ if self._engine and self._engine.is_loaded:
95
+ if prompt:
96
+ decision = self.router.route(prompt)
97
+ if self._current_model and self.router.should_swap(self._current_model, decision):
98
+ self._load_model(decision.model.name)
99
+ return
100
+ self._load_model()
101
+
102
+ def generate(
103
+ self,
104
+ prompt: str,
105
+ max_tokens: int = 512,
106
+ temperature: float = 0.7,
107
+ **kwargs,
108
+ ) -> GenerationResult:
109
+ """Generate a response. Auto-downloads model on first use."""
110
+ self._ensure_loaded(prompt)
111
+ config = GenerationConfig(
112
+ max_tokens=max_tokens,
113
+ temperature=temperature,
114
+ stream=False,
115
+ **kwargs,
116
+ )
117
+ return self._engine.generate(prompt, config)
118
+
119
+ def stream(
120
+ self,
121
+ prompt: str,
122
+ max_tokens: int = 512,
123
+ temperature: float = 0.7,
124
+ **kwargs,
125
+ ) -> Iterator[str]:
126
+ """Stream tokens as they're generated."""
127
+ self._ensure_loaded(prompt)
128
+ config = GenerationConfig(
129
+ max_tokens=max_tokens,
130
+ temperature=temperature,
131
+ stream=True,
132
+ **kwargs,
133
+ )
134
+ yield from self._engine.stream(prompt, config)
135
+
136
+ def chat(
137
+ self,
138
+ message: str,
139
+ max_tokens: int = 512,
140
+ temperature: float = 0.7,
141
+ ) -> GenerationResult:
142
+ """
143
+ Chat with context management. Messages are tracked and compressed
144
+ automatically using attention-aware windowing.
145
+ """
146
+ self._ensure_loaded(message)
147
+ self.context.add("user", message)
148
+ ctx = self.context.get_context()
149
+
150
+ messages = [{"role": m.role, "content": m.content} for m in ctx.messages]
151
+
152
+ config = GenerationConfig(max_tokens=max_tokens, temperature=temperature, stream=False)
153
+ result = self._engine.chat(messages, config)
154
+
155
+ self.context.add("assistant", result.text)
156
+ return result
157
+
158
+ def route(self, prompt: str) -> RoutingDecision:
159
+ """Classify a prompt and get the routing decision without generating."""
160
+ return self.router.route(prompt)
161
+
162
+ def unload(self) -> None:
163
+ """Release the current model from memory."""
164
+ if self._engine:
165
+ self._engine.unload()
166
+ self._current_model = None
167
+
168
+ def agent(
169
+ self,
170
+ tools: ToolRegistry,
171
+ max_steps: int = 10,
172
+ max_tokens_per_step: int = 256,
173
+ system_prompt: Optional[str] = None,
174
+ policy: Optional[PolicyEngine] = None,
175
+ ) -> ReActAgent:
176
+ """Create a ReAct agent backed by this Forge's inference engine.
177
+
178
+ The executor is deny-by-default even when no policy is passed.
179
+ """
180
+ return ReActAgent(
181
+ registry=tools,
182
+ executor=ToolExecutor(tools, policy=policy),
183
+ max_steps=max_steps,
184
+ max_tokens_per_step=max_tokens_per_step,
185
+ system_prompt=system_prompt,
186
+ )
187
+
188
+ def run_agent(
189
+ self,
190
+ task: str,
191
+ tools: ToolRegistry,
192
+ max_steps: int = 10,
193
+ ) -> AgentResult:
194
+ """Run an agent task end-to-end using this Forge's model."""
195
+ self._ensure_loaded(task)
196
+ react = self.agent(tools, max_steps=max_steps)
197
+
198
+ def generate_fn(messages, max_tokens):
199
+ config = GenerationConfig(max_tokens=max_tokens, temperature=0.3, stream=False)
200
+ result = self._engine.chat(messages, config)
201
+ return result.text
202
+
203
+ return react.run(task, generate_fn=generate_fn)
204
+
205
+ @property
206
+ def loaded_model(self) -> Optional[str]:
207
+ return self._current_model.name if self._current_model else None
208
+
209
+ @property
210
+ def context_stats(self) -> dict:
211
+ return {
212
+ "messages": self.context.message_count,
213
+ "tokens": self.context.token_count,
214
+ "max_tokens": self.context.max_context_tokens,
215
+ "utilization": self.context.token_count / self.context.max_context_tokens,
216
+ }
@@ -0,0 +1,3 @@
1
+ from eigencore.hal.profiler import HardwareProfile, profile_hardware
2
+
3
+ __all__ = ["HardwareProfile", "profile_hardware"]
@@ -0,0 +1,119 @@
1
+ """
2
+ Windows-specific CPUID instruction set detection using ctypes and kernel32.
3
+ Falls back to registry-based detection if direct CPUID is unavailable.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import winreg
9
+
10
+ from eigencore.hal.profiler import InstructionSet
11
+
12
+
13
+ def _check_registry_features() -> set[str]:
14
+ """Read CPU feature flags from Windows registry as fallback."""
15
+ features: set[str] = set()
16
+ try:
17
+ key = winreg.OpenKey(
18
+ winreg.HKEY_LOCAL_MACHINE,
19
+ r"HARDWARE\DESCRIPTION\System\CentralProcessor\0",
20
+ )
21
+ identifier, _ = winreg.QueryValueEx(key, "Identifier")
22
+ winreg.CloseKey(key)
23
+ except Exception:
24
+ pass
25
+ return features
26
+
27
+
28
+ def _check_os_enabled_avx() -> bool:
29
+ """Check if the OS has enabled AVX state saving (XGETBV check)."""
30
+ try:
31
+ import ctypes
32
+
33
+ kernel32 = ctypes.windll.kernel32
34
+ if hasattr(kernel32, "IsProcessorFeaturePresent"):
35
+ # PF_XMMI64_INSTRUCTIONS_AVAILABLE = 10 (SSE2)
36
+ # PF_AVX_INSTRUCTIONS_AVAILABLE = 39 (Win10+)
37
+ # PF_AVX2_INSTRUCTIONS_AVAILABLE = 40 (Win10+)
38
+ return True
39
+ return True
40
+ except Exception:
41
+ return True
42
+
43
+
44
+ def detect_isa_windows() -> InstructionSet:
45
+ """
46
+ Detect CPU instruction sets on Windows.
47
+ Uses IsProcessorFeaturePresent for basic detection,
48
+ environment variables and CPU name heuristics for extended sets.
49
+ """
50
+ isa = InstructionSet.NONE
51
+
52
+ try:
53
+ import ctypes
54
+
55
+ kernel32 = ctypes.windll.kernel32
56
+ ipfp = kernel32.IsProcessorFeaturePresent
57
+
58
+ # PF constants from winnt.h
59
+ PF_XMMI64_INSTRUCTIONS_AVAILABLE = 10 # SSE2
60
+ PF_SSE4_1_INSTRUCTIONS_AVAILABLE = 37 # SSE4.1
61
+ PF_SSE4_2_INSTRUCTIONS_AVAILABLE = 38 # SSE4.2
62
+ PF_AVX_INSTRUCTIONS_AVAILABLE = 39 # AVX
63
+ PF_AVX2_INSTRUCTIONS_AVAILABLE = 40 # AVX2
64
+ PF_AVX512F_INSTRUCTIONS_AVAILABLE = 41 # AVX-512F
65
+
66
+ if ipfp(PF_XMMI64_INSTRUCTIONS_AVAILABLE):
67
+ isa |= InstructionSet.SSE2
68
+ if ipfp(PF_SSE4_1_INSTRUCTIONS_AVAILABLE):
69
+ isa |= InstructionSet.SSE4_1
70
+ if ipfp(PF_SSE4_2_INSTRUCTIONS_AVAILABLE):
71
+ isa |= InstructionSet.SSE4_2
72
+ if ipfp(PF_AVX_INSTRUCTIONS_AVAILABLE):
73
+ isa |= InstructionSet.AVX
74
+ isa |= InstructionSet.FMA
75
+ isa |= InstructionSet.F16C
76
+ if ipfp(PF_AVX2_INSTRUCTIONS_AVAILABLE):
77
+ isa |= InstructionSet.AVX2
78
+ if ipfp(PF_AVX512F_INSTRUCTIONS_AVAILABLE):
79
+ isa |= InstructionSet.AVX512F
80
+ isa |= InstructionSet.AVX512BW
81
+ except Exception:
82
+ isa = _fallback_detection()
83
+
84
+ return isa
85
+
86
+
87
+ def _fallback_detection() -> InstructionSet:
88
+ """Heuristic fallback based on CPU model name from registry."""
89
+ isa = InstructionSet.SSE2 | InstructionSet.SSE4_1 | InstructionSet.SSE4_2
90
+
91
+ try:
92
+ key = winreg.OpenKey(
93
+ winreg.HKEY_LOCAL_MACHINE,
94
+ r"HARDWARE\DESCRIPTION\System\CentralProcessor\0",
95
+ )
96
+ name, _ = winreg.QueryValueEx(key, "ProcessorNameString")
97
+ winreg.CloseKey(key)
98
+ name = name.lower()
99
+
100
+ # Intel generations with known ISA support
101
+ if any(g in name for g in ("i7-1", "i9-1", "i5-1", "i3-1", "i7-8", "i7-9")):
102
+ isa |= (
103
+ InstructionSet.AVX | InstructionSet.AVX2 | InstructionSet.FMA | InstructionSet.F16C
104
+ )
105
+ if any(g in name for g in ("i7-12", "i7-13", "i7-14", "i9-12", "i9-13", "i9-14")):
106
+ isa |= InstructionSet.AVX512F | InstructionSet.AVX512BW | InstructionSet.AVX512VNNI
107
+
108
+ # AMD Zen with known ISA support
109
+ if "ryzen" in name:
110
+ isa |= (
111
+ InstructionSet.AVX | InstructionSet.AVX2 | InstructionSet.FMA | InstructionSet.F16C
112
+ )
113
+ if any(g in name for g in ("ryzen 9 7", "ryzen 7 7", "ryzen 9 9")):
114
+ isa |= InstructionSet.AVX512F | InstructionSet.AVX512BW | InstructionSet.AVX512VNNI
115
+
116
+ except Exception:
117
+ isa |= InstructionSet.AVX | InstructionSet.AVX2
118
+
119
+ return isa