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.
- eigencore/__init__.py +82 -0
- eigencore/agent/__init__.py +45 -0
- eigencore/agent/executor.py +297 -0
- eigencore/agent/grammar.py +121 -0
- eigencore/agent/policy.py +262 -0
- eigencore/agent/react.py +261 -0
- eigencore/agent/tool.py +149 -0
- eigencore/analysis/__init__.py +3 -0
- eigencore/analysis/sparsity.py +184 -0
- eigencore/benchmark/__init__.py +33 -0
- eigencore/benchmark/harness.py +342 -0
- eigencore/cli/__init__.py +0 -0
- eigencore/cli/main.py +856 -0
- eigencore/context/__init__.py +3 -0
- eigencore/context/manager.py +318 -0
- eigencore/engine/__init__.py +3 -0
- eigencore/engine/inference.py +378 -0
- eigencore/engine/layer_skip.py +268 -0
- eigencore/engine/prompt_lookup.py +39 -0
- eigencore/engine/pruning.py +116 -0
- eigencore/engine/sparse_inference.py +218 -0
- eigencore/engine/sparsity_experiment.py +109 -0
- eigencore/engine/speculative.py +238 -0
- eigencore/fit/__init__.py +34 -0
- eigencore/fit/estimator.py +310 -0
- eigencore/fit/gguf_meta.py +130 -0
- eigencore/forge.py +216 -0
- eigencore/hal/__init__.py +3 -0
- eigencore/hal/_cpuid_win.py +119 -0
- eigencore/hal/profiler.py +349 -0
- eigencore/kernels/__init__.py +28 -0
- eigencore/kernels/q4k.py +76 -0
- eigencore/kernels/reference.py +168 -0
- eigencore/models/__init__.py +3 -0
- eigencore/models/registry.py +193 -0
- eigencore/router/__init__.py +3 -0
- eigencore/router/classifier.py +176 -0
- eigencore/training/__init__.py +3 -0
- eigencore/training/convergence.py +112 -0
- eigencore/training/trainer.py +401 -0
- eigencore/tui/__init__.py +5 -0
- eigencore/tui/chat.py +265 -0
- eigencore/tui/onboarding.py +134 -0
- eigencore/tui/theme.py +61 -0
- eigencore-0.4.0.dist-info/METADATA +144 -0
- eigencore-0.4.0.dist-info/RECORD +49 -0
- eigencore-0.4.0.dist-info/WHEEL +4 -0
- eigencore-0.4.0.dist-info/entry_points.txt +2 -0
- 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,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
|