hestia-system 0.1.0__tar.gz

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.
@@ -0,0 +1,2 @@
1
+ include project.ini
2
+ include README.md
@@ -0,0 +1,68 @@
1
+ Metadata-Version: 2.4
2
+ Name: hestia-system
3
+ Version: 0.1.0
4
+ Summary: System discovery, hardware and software analysis, and AI model recommendations.
5
+ Author: Damon S. Green II (nomadiidamon)
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/nomadiidamon/Hestia
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: psutil>=5.9.0
11
+ Provides-Extra: nvidia
12
+ Requires-Dist: nvidia-ml-py>=12.0.0; extra == "nvidia"
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=7.0; extra == "dev"
15
+ Dynamic: author
16
+ Dynamic: description
17
+ Dynamic: description-content-type
18
+ Dynamic: license
19
+ Dynamic: project-url
20
+ Dynamic: requires-python
21
+ Dynamic: summary
22
+
23
+ # Hestia for Python
24
+
25
+ Hestia provides system discovery and AI model intelligence. The Python package
26
+ currently exposes accelerator and model-size types, a model catalog, and model
27
+ information lookup.
28
+
29
+ The distribution is named `hestia-system`; import it as `Hestia`.
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ python -m pip install hestia-system
35
+ ```
36
+
37
+ Optional dependencies are available as extras:
38
+
39
+ ```bash
40
+ python -m pip install "hestia-system[nvidia]" # NVIDIA telemetry support
41
+ python -m pip install "hestia-system[dev]" # pytest
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ```python
47
+ from Hestia import AcceleratorType, ModelSize, get_model_info
48
+
49
+ info = get_model_info(AcceleratorType.NVIDIA_CUDA, ModelSize.SMALL)
50
+ print(info)
51
+ ```
52
+
53
+ The package supports Python versions declared by the project metadata (Python
54
+ 3.10 and later).
55
+
56
+ ## Development
57
+
58
+ From the repository checkout:
59
+
60
+ ```bash
61
+ cd python
62
+ python -m pip install -e ".[dev]"
63
+ python -m pytest
64
+ ```
65
+
66
+ Package metadata is maintained in the repository's `project.ini`. Source
67
+ distributions include a copy of that file so they can be built independently
68
+ of the repository checkout.
@@ -0,0 +1,46 @@
1
+ # Hestia for Python
2
+
3
+ Hestia provides system discovery and AI model intelligence. The Python package
4
+ currently exposes accelerator and model-size types, a model catalog, and model
5
+ information lookup.
6
+
7
+ The distribution is named `hestia-system`; import it as `Hestia`.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ python -m pip install hestia-system
13
+ ```
14
+
15
+ Optional dependencies are available as extras:
16
+
17
+ ```bash
18
+ python -m pip install "hestia-system[nvidia]" # NVIDIA telemetry support
19
+ python -m pip install "hestia-system[dev]" # pytest
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ```python
25
+ from Hestia import AcceleratorType, ModelSize, get_model_info
26
+
27
+ info = get_model_info(AcceleratorType.NVIDIA_CUDA, ModelSize.SMALL)
28
+ print(info)
29
+ ```
30
+
31
+ The package supports Python versions declared by the project metadata (Python
32
+ 3.10 and later).
33
+
34
+ ## Development
35
+
36
+ From the repository checkout:
37
+
38
+ ```bash
39
+ cd python
40
+ python -m pip install -e ".[dev]"
41
+ python -m pytest
42
+ ```
43
+
44
+ Package metadata is maintained in the repository's `project.ini`. Source
45
+ distributions include a copy of that file so they can be built independently
46
+ of the repository checkout.
@@ -0,0 +1,32 @@
1
+ ; project.ini
2
+ ;
3
+ ; Single source of truth for metadata shared across the C++ and Python
4
+ ; implementations. Both build systems (premake5 on the C++ side, the Python
5
+ ; packaging/build scripts on the Python side) should parse this file rather
6
+ ; than duplicating these values in language-specific manifests.
7
+ ;
8
+ ; Keep keys flat and simple (INI has no native nesting) so both a C++ INI
9
+ ; parser and Python's configparser can read it without custom logic.
10
+
11
+ [project]
12
+ name = Hestia
13
+ python_package = hestia-system
14
+ version = 0.1.0
15
+ description = "System discovery, hardware and software analysis, and AI model recommendations."
16
+ homepage = https://github.com/nomadiidamon/Hestia
17
+ license = MIT
18
+
19
+ [contributors]
20
+ ; Comma-separated list; expand as needed, or switch to one key per person
21
+ ; (e.g. contributor.1, contributor.2) if you want per-person metadata later.
22
+ maintainers = Damon S. Green II (nomadiidamon)
23
+ contributors =
24
+
25
+ [compatibility]
26
+ ; Minimum/target language versions both implementations should track.
27
+ cpp_standard = c++17
28
+ python_requires = >=3.10
29
+
30
+ [build]
31
+ ; Freeform notes/flags both build scripts may want to agree on.
32
+ default_build_type = release
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "hestia-system"
7
+ dynamic = ["version", "description", "readme", "license", "authors", "requires-python", "urls"] # from ../project.ini via setup.py
8
+
9
+ dependencies = [
10
+ "psutil>=5.9.0",
11
+ ]
12
+
13
+ [project.optional-dependencies]
14
+ nvidia = [
15
+ "nvidia-ml-py>=12.0.0",
16
+ ]
17
+ dev = [
18
+ "pytest>=7.0",
19
+ ]
20
+
21
+ [tool.setuptools]
22
+ package-dir = { "" = "src" }
23
+
24
+ [tool.setuptools.packages.find]
25
+ where = ["src"]
26
+
27
+ [tool.pytest.ini_options]
28
+ pythonpath = ["src"]
29
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,39 @@
1
+ """Reads shared metadata from ../project.ini (the single source of truth)."""
2
+ import configparser
3
+ import re
4
+ import shutil
5
+ from pathlib import Path
6
+
7
+ from setuptools import setup
8
+
9
+ HERE = Path(__file__).resolve().parent
10
+ REPO_INI = HERE.parent / "project.ini"
11
+ LOCAL_INI = HERE / "project.ini"
12
+
13
+ # An sdist has no parent repo, so ship a copy of project.ini alongside setup.py.
14
+ if REPO_INI.is_file():
15
+ shutil.copyfile(REPO_INI, LOCAL_INI)
16
+ if not LOCAL_INI.is_file():
17
+ raise FileNotFoundError(f"project.ini not found at {REPO_INI} or {LOCAL_INI}")
18
+
19
+ cfg = configparser.ConfigParser()
20
+ with open(LOCAL_INI, encoding="utf-8-sig") as ini:
21
+ cfg.read_file(ini)
22
+ project = cfg["project"]
23
+
24
+ people = [p.strip() for p in cfg["contributors"]["maintainers"].split(",") if p.strip()]
25
+ # "Name (handle) <email>" -> "Name (handle)"
26
+ names = [re.sub(r"\s*<.*>$", "", p) for p in people]
27
+ emails = [m.group(1) for p in people if (m := re.search(r"<(.*)>$", p))]
28
+
29
+ setup(
30
+ version=project["version"],
31
+ description=project["description"].strip('"'),
32
+ long_description=(HERE / "README.md").read_text(encoding="utf-8"),
33
+ long_description_content_type="text/markdown",
34
+ license=project["license"],
35
+ python_requires=cfg["compatibility"]["python_requires"],
36
+ author=", ".join(names),
37
+ author_email=", ".join(emails) or None,
38
+ project_urls={"Homepage": project["homepage"]},
39
+ )
@@ -0,0 +1,10 @@
1
+ """Hestia - system discovery and AI model intelligence."""
2
+
3
+ from .models import ModelInfo, get_model_info
4
+ from .types import AcceleratorType, ModelSize
5
+
6
+
7
+ __version__ = "0.1.0"
8
+
9
+
10
+ __all__ = ["AcceleratorType", "ModelSize", "ModelInfo", "get_model_info"]
@@ -0,0 +1,11 @@
1
+ """Legacy import path; use Hestia.models instead."""
2
+
3
+ from .models.catalog import (
4
+ KNOWN_CATEGORIES,
5
+ MODEL_DATABASE,
6
+ TIER_FALLBACK_ORDER,
7
+ TierEntry,
8
+ TierMap,
9
+ get_category,
10
+ get_tier,
11
+ )
@@ -0,0 +1,24 @@
1
+ """AI model catalog and metadata."""
2
+
3
+ from .catalog import (
4
+ KNOWN_CATEGORIES,
5
+ MODEL_DATABASE,
6
+ TIER_FALLBACK_ORDER,
7
+ TierEntry,
8
+ TierMap,
9
+ get_category,
10
+ get_tier,
11
+ )
12
+ from .info import ModelInfo, get_model_info
13
+
14
+ __all__ = [
15
+ "KNOWN_CATEGORIES",
16
+ "MODEL_DATABASE",
17
+ "TIER_FALLBACK_ORDER",
18
+ "TierEntry",
19
+ "TierMap",
20
+ "ModelInfo",
21
+ "get_category",
22
+ "get_model_info",
23
+ "get_tier",
24
+ ]
@@ -0,0 +1,438 @@
1
+ # ---------------------------------------------------------------------------
2
+ # To add new categories
3
+ # ---------------------------------------------------------------------------
4
+ # 1.) Add the new category key to KNOWN_CATEGORIES
5
+ # KNOWN_CATEGORIES = [ '...' , '...' , 'coding']
6
+
7
+ # 2.) Add the new category key to each tier entry that supports it in MODEL_DATABASE
8
+ # 'coding': ['qwen2.5-coder:7b']
9
+
10
+ # 3.) Add the field to ModelInfo in models/info.py and the ModelRecommendation dataclass
11
+ # coding_models: List[str]
12
+
13
+ # 4.) Add to ModelRecommender.recommend() in hardware.py
14
+ # coding_models=get_category(tier, 'coding_models'),
15
+
16
+ # 5.) Add to print_hardware_report() in hardware.py
17
+ # if recommendation.coding_models:
18
+ #print(f"Coding: {', '.join(recommendation.coding_models)}")
19
+
20
+
21
+ """
22
+ model_catalog.py - Model recommendation database for Local AI Runtime.
23
+
24
+ Defines what models are appropriate per hardware tier, accelerator type,
25
+ and capability category. Add new categories here without touching hardware.py.
26
+
27
+ Category keys per tier entry:
28
+ models - best general-purpose models for balanced use
29
+ thinking - chain-of-thought / extended reasoning (/think mode)
30
+ tools - native function/tool-call support
31
+ vision - multimodal image + text understanding
32
+ quantization - recommended quantization level for this tier
33
+ batch_size - safe concurrent request count
34
+ context - practical max context window in tokens
35
+ performance - human-readable speed description
36
+
37
+ @todo: Abstract the model recommendation logic to allow dynamic updates and easier maintenance.
38
+ @todo: Create a class to hold a model, size, and category for easier management and retrieval.
39
+ """
40
+
41
+ from typing import Dict, List
42
+ from ..types import AcceleratorType, ModelSize
43
+
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # Type alias for a single tier entry
47
+ # ---------------------------------------------------------------------------
48
+ TierEntry = Dict[str, object]
49
+ TierMap = Dict[ModelSize, TierEntry]
50
+
51
+
52
+ # ---------------------------------------------------------------------------
53
+ # MODEL DATABASE
54
+ # Outer key: AcceleratorType
55
+ # Inner key: ModelSize (memory tier)
56
+ # ---------------------------------------------------------------------------
57
+
58
+ MODEL_DATABASE: Dict[AcceleratorType, TierMap] = {
59
+
60
+ # ==========================================================================
61
+ # NVIDIA CUDA
62
+ # Fast VRAM access - highest quality quant per tier
63
+ # ==========================================================================
64
+ AcceleratorType.NVIDIA_CUDA: {
65
+
66
+ ModelSize.TINY: {
67
+ # <2 GB - ultra-constrained, phone/edge-class VRAM
68
+ 'models': ['qwen3:0.6b', 'llama3.2:1b', 'deepseek-r1:1.5b', 'smollm2:1.7b'],
69
+ 'thinking': ['deepseek-r1:1.5b', 'qwen3:0.6b'],
70
+ 'tools': ['qwen3:0.6b', 'llama3.2:1b', 'smollm2:1.7b'],
71
+ 'vision': [], # No reliable sub-2 GB vision models on Ollama
72
+ 'quantization': 'q8_0',
73
+ 'batch_size': 4,
74
+ 'context': 8192,
75
+ 'performance': 'Fast (tiny model)',
76
+ },
77
+
78
+ ModelSize.SMALL: {
79
+ # 2-4 GB - entry-level discrete GPU or shared VRAM laptop
80
+ 'models': ['qwen3:1.7b', 'llama3.2:3b', 'phi4-mini:3.8b', 'granite3.3:2b'],
81
+ 'thinking': ['qwen3:1.7b', 'deepseek-r1:1.5b'],
82
+ 'tools': ['qwen3:1.7b', 'llama3.2:3b', 'granite3.1-dense:2b', 'xlam:1b'],
83
+ 'vision': ['llama3.2-vision:3b', 'moondream:1.8b', 'minicpm-v:3b'],
84
+ 'quantization': 'q6_k',
85
+ 'batch_size': 4,
86
+ 'context': 16384,
87
+ 'performance': 'Fast',
88
+ },
89
+
90
+ ModelSize.MEDIUM: {
91
+ # 4-12 GB - GTX 1080 / RTX 3060 / RX 6700 XT class
92
+ 'models': ['qwen3:8b', 'llama3.1:8b', 'mistral:7b', 'mistral-nemo:12b', 'qwen2.5:7b'],
93
+ 'thinking': ['qwen3:8b', 'deepseek-r1:8b', 'cogito:8b', 'exaone-deep:7.8b'],
94
+ 'tools': ['qwen3:8b', 'llama3.1:8b', 'qwen2.5:7b', 'hermes3:8b',
95
+ 'mistral:7b', 'xlam:8b', 'functionary:7b', 'granite3.1-dense:8b'],
96
+ 'vision': ['llava:7b', 'llava:13b', 'llama3.2-vision:11b', 'qwen2.5vl:7b',
97
+ 'minicpm-v:8b', 'moondream:1.8b', 'bakllava:7b'],
98
+ 'quantization': 'q4_k_m',
99
+ 'batch_size': 2,
100
+ 'context': 32768,
101
+ 'performance': 'Fast',
102
+ },
103
+
104
+ ModelSize.LARGE: {
105
+ # 12-40 GB - RTX 3090 / RTX 4090 / A100 40 GB class
106
+ 'models': ['qwen3:14b', 'qwen3:32b', 'qwen2.5:32b',
107
+ 'deepseek-r1:14b', 'command-r:35b'],
108
+ 'thinking': ['qwen3:14b', 'qwen3:32b', 'deepseek-r1:14b', 'deepseek-r1:32b',
109
+ 'qwq:32b', 'cogito:14b', 'cogito:32b',
110
+ 'phi4-reasoning:14b', 'exaone-deep:32b'],
111
+ 'tools': ['qwen3:14b', 'qwen3:32b', 'command-r:35b', 'qwen2.5:32b',
112
+ 'hermes3:8b', 'aya-expanse:32b', 'functionary:7b', 'xlam:7b'],
113
+ 'vision': ['llava:34b', 'qwen2.5vl:14b', 'qwen2.5vl:32b',
114
+ 'llama3.2-vision:11b', 'minicpm-v:8b', 'gemma3:27b',
115
+ 'mistral-small3.1:22b'],
116
+ 'quantization': 'q4_k_m',
117
+ 'batch_size': 2,
118
+ 'context': 32768,
119
+ 'performance': 'Moderate',
120
+ },
121
+
122
+ ModelSize.XLARGE: {
123
+ # 40+ GB - A100 80 GB / H100 / multi-GPU NVLink
124
+ 'models': ['llama3.3:70b', 'qwen2.5:72b', 'qwen3:30b-a3b',
125
+ 'deepseek-r1:70b', 'command-r-plus:104b'],
126
+ 'thinking': ['deepseek-r1:70b', 'qwen3:30b-a3b', 'cogito:70b',
127
+ 'qwq:32b', 'exaone-deep:32b'],
128
+ 'tools': ['llama3.3:70b', 'qwen2.5:72b', 'qwen3:30b-a3b',
129
+ 'hermes3:70b', 'command-r-plus:104b',
130
+ 'firefunction-v2:70b', 'functionary:70b'],
131
+ 'vision': ['llava:34b', 'qwen2.5vl:72b', 'llama3.2-vision:90b',
132
+ 'mistral-small3.1:22b', 'gemma3:27b'],
133
+ 'quantization': 'q4_k_m',
134
+ 'batch_size': 4,
135
+ 'context': 131072,
136
+ 'performance': 'Fast (large model)',
137
+ },
138
+ },
139
+
140
+ # ==========================================================================
141
+ # AMD ROCm
142
+ # Slightly less efficient memory bandwidth than CUDA; drop quant one step
143
+ # ==========================================================================
144
+ AcceleratorType.AMD_ROCM: {
145
+
146
+ ModelSize.TINY: {
147
+ 'models': ['qwen3:0.6b', 'llama3.2:1b', 'deepseek-r1:1.5b'],
148
+ 'thinking': ['deepseek-r1:1.5b', 'qwen3:0.6b'],
149
+ 'tools': ['qwen3:0.6b', 'llama3.2:1b'],
150
+ 'vision': [],
151
+ 'quantization': 'q8_0',
152
+ 'batch_size': 2,
153
+ 'context': 8192,
154
+ 'performance': 'Moderate (tiny model)',
155
+ },
156
+
157
+ ModelSize.SMALL: {
158
+ 'models': ['qwen3:1.7b', 'llama3.2:3b', 'phi4-mini:3.8b'],
159
+ 'thinking': ['qwen3:1.7b', 'deepseek-r1:1.5b'],
160
+ 'tools': ['qwen3:1.7b', 'llama3.2:3b', 'xlam:1b'],
161
+ 'vision': ['llama3.2-vision:3b', 'moondream:1.8b', 'minicpm-v:3b'],
162
+ 'quantization': 'q5_k_m',
163
+ 'batch_size': 2,
164
+ 'context': 16384,
165
+ 'performance': 'Moderate',
166
+ },
167
+
168
+ ModelSize.MEDIUM: {
169
+ 'models': ['qwen3:8b', 'llama3.1:8b', 'mistral:7b', 'qwen2.5:7b'],
170
+ 'thinking': ['qwen3:8b', 'deepseek-r1:8b', 'cogito:8b'],
171
+ 'tools': ['qwen3:8b', 'llama3.1:8b', 'qwen2.5:7b',
172
+ 'hermes3:8b', 'mistral:7b', 'granite3.1-dense:8b'],
173
+ 'vision': ['llava:7b', 'llama3.2-vision:11b', 'qwen2.5vl:7b',
174
+ 'minicpm-v:8b', 'moondream:1.8b'],
175
+ 'quantization': 'q4_k_m',
176
+ 'batch_size': 2,
177
+ 'context': 32768,
178
+ 'performance': 'Moderate',
179
+ },
180
+
181
+ ModelSize.LARGE: {
182
+ 'models': ['qwen3:14b', 'qwen3:32b', 'deepseek-r1:14b', 'command-r:35b'],
183
+ 'thinking': ['qwen3:14b', 'qwen3:32b', 'deepseek-r1:14b', 'deepseek-r1:32b',
184
+ 'qwq:32b', 'cogito:14b', 'phi4-reasoning:14b'],
185
+ 'tools': ['qwen3:14b', 'qwen3:32b', 'command-r:35b',
186
+ 'qwen2.5:32b', 'aya-expanse:32b'],
187
+ 'vision': ['qwen2.5vl:14b', 'qwen2.5vl:32b', 'llava:34b',
188
+ 'gemma3:27b', 'mistral-small3.1:22b'],
189
+ 'quantization': 'q3_k_m',
190
+ 'batch_size': 1,
191
+ 'context': 32768,
192
+ 'performance': 'Slow',
193
+ },
194
+
195
+ ModelSize.XLARGE: {
196
+ 'models': ['llama3.3:70b', 'qwen2.5:72b', 'deepseek-r1:70b'],
197
+ 'thinking': ['deepseek-r1:70b', 'qwen3:30b-a3b', 'cogito:70b'],
198
+ 'tools': ['llama3.3:70b', 'qwen2.5:72b', 'hermes3:70b',
199
+ 'firefunction-v2:70b'],
200
+ 'vision': ['qwen2.5vl:72b', 'llama3.2-vision:90b', 'llava:34b'],
201
+ 'quantization': 'q3_k_m',
202
+ 'batch_size': 2,
203
+ 'context': 65536,
204
+ 'performance': 'Moderate (large model)',
205
+ },
206
+ },
207
+
208
+ # ==========================================================================
209
+ # Intel Arc / Xe (OneAPI)
210
+ # Typically 8-16 GB on Arc A770/B580; lower throughput than NVIDIA/AMD
211
+ # ==========================================================================
212
+ AcceleratorType.INTEL_ONEAPI: {
213
+
214
+ ModelSize.TINY: {
215
+ 'models': ['qwen3:0.6b', 'llama3.2:1b', 'smollm2:1.7b'],
216
+ 'thinking': ['deepseek-r1:1.5b', 'qwen3:0.6b'],
217
+ 'tools': ['qwen3:0.6b', 'llama3.2:1b'],
218
+ 'vision': [],
219
+ 'quantization': 'q8_0',
220
+ 'batch_size': 2,
221
+ 'context': 8192,
222
+ 'performance': 'Moderate (tiny model)',
223
+ },
224
+
225
+ ModelSize.SMALL: {
226
+ 'models': ['qwen3:1.7b', 'llama3.2:3b', 'granite3.3:2b'],
227
+ 'thinking': ['qwen3:1.7b', 'deepseek-r1:1.5b'],
228
+ 'tools': ['qwen3:1.7b', 'llama3.2:3b', 'granite3.1-dense:2b'],
229
+ 'vision': ['moondream:1.8b', 'minicpm-v:3b'],
230
+ 'quantization': 'q5_k_m',
231
+ 'batch_size': 2,
232
+ 'context': 8192,
233
+ 'performance': 'Moderate',
234
+ },
235
+
236
+ ModelSize.MEDIUM: {
237
+ 'models': ['qwen3:8b', 'llama3.1:8b', 'mistral:7b'],
238
+ 'thinking': ['qwen3:8b', 'deepseek-r1:8b'],
239
+ 'tools': ['qwen3:8b', 'llama3.1:8b', 'mistral:7b',
240
+ 'granite3.1-dense:8b', 'hermes3:8b'],
241
+ 'vision': ['llava:7b', 'qwen2.5vl:7b', 'minicpm-v:8b', 'moondream:1.8b'],
242
+ 'quantization': 'q4_k_m',
243
+ 'batch_size': 1,
244
+ 'context': 16384,
245
+ 'performance': 'Slow',
246
+ },
247
+
248
+ ModelSize.LARGE: {
249
+ 'models': ['qwen3:14b', 'qwen2.5:14b', 'phi4-reasoning:14b'],
250
+ 'thinking': ['qwen3:14b', 'deepseek-r1:14b', 'phi4-reasoning:14b', 'cogito:14b'],
251
+ 'tools': ['qwen3:14b', 'qwen2.5:14b', 'hermes3:8b'],
252
+ 'vision': ['qwen2.5vl:14b', 'gemma3:12b', 'mistral-small3.1:22b'],
253
+ 'quantization': 'q3_k_m',
254
+ 'batch_size': 1,
255
+ 'context': 16384,
256
+ 'performance': 'Slow',
257
+ },
258
+
259
+ # XLARGE unrealistic for Arc - fallback to LARGE handled in ModelRecommender
260
+ },
261
+
262
+ # ==========================================================================
263
+ # Apple Metal (Apple Silicon - unified memory)
264
+ # M1/M2/M3/M4 share RAM with GPU; 16-192 GB depending on config
265
+ # Metal is highly optimised for Ollama via llama.cpp
266
+ # ==========================================================================
267
+ AcceleratorType.APPLE_METAL: {
268
+
269
+ ModelSize.TINY: {
270
+ 'models': ['qwen3:0.6b', 'llama3.2:1b', 'smollm2:1.7b'],
271
+ 'thinking': ['deepseek-r1:1.5b', 'qwen3:0.6b'],
272
+ 'tools': ['qwen3:0.6b', 'llama3.2:1b'],
273
+ 'vision': [],
274
+ 'quantization': 'q8_0',
275
+ 'batch_size': 4,
276
+ 'context': 8192,
277
+ 'performance': 'Fast (tiny model)',
278
+ },
279
+
280
+ ModelSize.SMALL: {
281
+ 'models': ['qwen3:1.7b', 'llama3.2:3b', 'phi4-mini:3.8b'],
282
+ 'thinking': ['qwen3:1.7b', 'deepseek-r1:1.5b'],
283
+ 'tools': ['qwen3:1.7b', 'llama3.2:3b', 'granite3.1-dense:2b'],
284
+ 'vision': ['llama3.2-vision:3b', 'moondream:1.8b', 'minicpm-v:3b'],
285
+ 'quantization': 'q6_k',
286
+ 'batch_size': 4,
287
+ 'context': 16384,
288
+ 'performance': 'Fast',
289
+ },
290
+
291
+ ModelSize.MEDIUM: {
292
+ 'models': ['qwen3:8b', 'llama3.1:8b', 'mistral:7b', 'mistral-nemo:12b'],
293
+ 'thinking': ['qwen3:8b', 'deepseek-r1:8b', 'cogito:8b', 'exaone-deep:7.8b'],
294
+ 'tools': ['qwen3:8b', 'llama3.1:8b', 'mistral:7b', 'qwen2.5:7b',
295
+ 'hermes3:8b', 'granite3.1-dense:8b'],
296
+ 'vision': ['llava:7b', 'llama3.2-vision:11b', 'qwen2.5vl:7b',
297
+ 'minicpm-v:8b', 'gemma3:12b', 'moondream:1.8b'],
298
+ 'quantization': 'q4_k_m',
299
+ 'batch_size': 2,
300
+ 'context': 32768,
301
+ 'performance': 'Fast',
302
+ },
303
+
304
+ ModelSize.LARGE: {
305
+ # M2 Max / M3 Pro 36-48 GB unified memory
306
+ 'models': ['qwen3:14b', 'qwen3:32b', 'deepseek-r1:14b', 'command-r:35b'],
307
+ 'thinking': ['qwen3:14b', 'qwen3:32b', 'deepseek-r1:14b', 'deepseek-r1:32b',
308
+ 'qwq:32b', 'cogito:14b', 'phi4-reasoning:14b', 'exaone-deep:32b'],
309
+ 'tools': ['qwen3:14b', 'qwen3:32b', 'command-r:35b', 'qwen2.5:32b',
310
+ 'aya-expanse:32b', 'hermes3:8b'],
311
+ 'vision': ['llava:34b', 'qwen2.5vl:14b', 'qwen2.5vl:32b',
312
+ 'llama3.2-vision:11b', 'gemma3:27b', 'mistral-small3.1:22b'],
313
+ 'quantization': 'q4_k_m',
314
+ 'batch_size': 2,
315
+ 'context': 65536,
316
+ 'performance': 'Fast (Apple Silicon advantage)',
317
+ },
318
+
319
+ ModelSize.XLARGE: {
320
+ # M2 Ultra / M3 Max / M4 Max 64-192 GB unified memory
321
+ 'models': ['llama3.3:70b', 'qwen2.5:72b', 'qwen3:30b-a3b', 'deepseek-r1:70b'],
322
+ 'thinking': ['deepseek-r1:70b', 'qwen3:30b-a3b', 'cogito:70b',
323
+ 'qwq:32b', 'exaone-deep:32b'],
324
+ 'tools': ['llama3.3:70b', 'qwen2.5:72b', 'qwen3:30b-a3b',
325
+ 'hermes3:70b', 'command-r-plus:104b', 'firefunction-v2:70b'],
326
+ 'vision': ['llava:34b', 'qwen2.5vl:72b', 'llama3.2-vision:90b',
327
+ 'gemma3:27b', 'mistral-small3.1:22b'],
328
+ 'quantization': 'q4_k_m',
329
+ 'batch_size': 4,
330
+ 'context': 131072,
331
+ 'performance': 'Fast (Apple Silicon advantage)',
332
+ },
333
+ },
334
+
335
+ # ==========================================================================
336
+ # CPU only - no GPU
337
+ # ==========================================================================
338
+ AcceleratorType.NONE: {
339
+
340
+ ModelSize.TINY: {
341
+ 'models': ['qwen3:0.6b', 'llama3.2:1b', 'smollm2:1.7b'],
342
+ 'thinking': ['deepseek-r1:1.5b', 'qwen3:0.6b'],
343
+ 'tools': ['qwen3:0.6b', 'llama3.2:1b'],
344
+ 'vision': ['moondream:1.8b'],
345
+ 'quantization': 'q8_0',
346
+ 'batch_size': 1,
347
+ 'context': 4096,
348
+ 'performance': 'Slow (CPU only)',
349
+ },
350
+
351
+ ModelSize.SMALL: {
352
+ 'models': ['qwen3:1.7b', 'llama3.2:3b'],
353
+ 'thinking': ['qwen3:1.7b', 'deepseek-r1:1.5b'],
354
+ 'tools': ['qwen3:1.7b', 'llama3.2:3b'],
355
+ 'vision': ['moondream:1.8b', 'minicpm-v:3b'],
356
+ 'quantization': 'q4_k_m',
357
+ 'batch_size': 1,
358
+ 'context': 8192,
359
+ 'performance': 'Very Slow (CPU only)',
360
+ },
361
+
362
+ ModelSize.MEDIUM: {
363
+ 'models': ['qwen3:8b', 'mistral:7b', 'llama3.1:8b'],
364
+ 'thinking': ['qwen3:8b', 'deepseek-r1:8b'],
365
+ 'tools': ['qwen3:8b', 'mistral:7b', 'llama3.1:8b'],
366
+ 'vision': ['llava:7b', 'qwen2.5vl:7b', 'minicpm-v:8b'],
367
+ 'quantization': 'q4_k_m',
368
+ 'batch_size': 1,
369
+ 'context': 8192,
370
+ 'performance': 'Very Slow (CPU only)',
371
+ },
372
+
373
+ # LARGE / XLARGE on CPU-only is impractical - fallback handled in ModelRecommender
374
+ },
375
+ }
376
+
377
+
378
+ # ---------------------------------------------------------------------------
379
+ # Helpers - consumed by ModelInfo and the model recommender
380
+ # ---------------------------------------------------------------------------
381
+
382
+ # All categories present in any tier entry. Add new ones here as they are added
383
+ # to the database so get_category() degrades gracefully for old entries.
384
+ KNOWN_CATEGORIES: List[str] = ['models', 'thinking', 'tools', 'vision']
385
+
386
+ # Tier fallback order when the exact tier has no entry for an accelerator
387
+ TIER_FALLBACK_ORDER: List[ModelSize] = [
388
+ ModelSize.LARGE,
389
+ ModelSize.MEDIUM,
390
+ ModelSize.SMALL,
391
+ ModelSize.TINY,
392
+ ]
393
+
394
+
395
+ def get_tier(accelerator: AcceleratorType, size: ModelSize) -> TierEntry:
396
+ """
397
+ Return the tier entry for an accelerator + memory size combination.
398
+
399
+ Walks down TIER_FALLBACK_ORDER if the exact size is missing, then
400
+ falls back to CPU MEDIUM as a last resort.
401
+
402
+ Args:
403
+ accelerator: The detected primary accelerator.
404
+ size: The memory-based model size tier.
405
+
406
+ Returns:
407
+ TierEntry dict ready for ModelRecommendation construction.
408
+ """
409
+ db = MODEL_DATABASE.get(accelerator, {})
410
+ entry = db.get(size)
411
+
412
+ if entry is None:
413
+ for fallback in TIER_FALLBACK_ORDER:
414
+ entry = db.get(fallback)
415
+ if entry is not None:
416
+ break
417
+
418
+ if entry is None:
419
+ entry = MODEL_DATABASE[AcceleratorType.NONE][ModelSize.MEDIUM]
420
+
421
+ return entry
422
+
423
+
424
+ def get_category(tier: TierEntry, category: str) -> List[str]:
425
+ """
426
+ Safely retrieve a model list for a given category from a tier entry.
427
+
428
+ Returns an empty list if the category is not present, so callers never
429
+ need to guard against missing keys when new categories are added.
430
+
431
+ Args:
432
+ tier: A tier entry dict from MODEL_DATABASE.
433
+ category: Category name, e.g. 'vision', 'tools', 'thinking'.
434
+
435
+ Returns:
436
+ List of model name strings.
437
+ """
438
+ return tier.get(category, [])
@@ -0,0 +1,68 @@
1
+ """Typed view over the model catalog."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Dict, List, Optional, Union
5
+
6
+ from ..types import AcceleratorType, ModelSize
7
+ from .catalog import KNOWN_CATEGORIES, MODEL_DATABASE, TierEntry, get_category, get_tier
8
+
9
+
10
+ @dataclass(frozen=True)
11
+ class ModelInfo:
12
+ """Catalog entry for one accelerator + memory tier."""
13
+
14
+ accelerator: AcceleratorType
15
+ size: ModelSize
16
+ categories: Dict[str, List[str]] = field(default_factory=dict)
17
+ quantization: str = ""
18
+ batch_size: int = 1
19
+ context: int = 0
20
+ performance: str = ""
21
+
22
+ @classmethod
23
+ def from_entry(
24
+ cls, accelerator: AcceleratorType, size: ModelSize, entry: TierEntry
25
+ ) -> "ModelInfo":
26
+ return cls(
27
+ accelerator=accelerator,
28
+ size=size,
29
+ categories={c: list(get_category(entry, c)) for c in KNOWN_CATEGORIES},
30
+ quantization=str(entry.get("quantization", "")),
31
+ batch_size=int(entry.get("batch_size", 1)),
32
+ context=int(entry.get("context", 0)),
33
+ performance=str(entry.get("performance", "")),
34
+ )
35
+
36
+ def get(self, category: str) -> List[str]:
37
+ return list(self.categories.get(category, []))
38
+
39
+ def to_dict(self) -> dict:
40
+ return {
41
+ "accelerator": self.accelerator.value,
42
+ "size": self.size.value,
43
+ "categories": {k: list(v) for k, v in self.categories.items()},
44
+ "quantization": self.quantization,
45
+ "batch_size": self.batch_size,
46
+ "context": self.context,
47
+ "performance": self.performance,
48
+ }
49
+
50
+
51
+ def get_model_info(
52
+ accelerator: Optional[AcceleratorType] = None,
53
+ size: Optional[ModelSize] = None,
54
+ ) -> Union[ModelInfo, List[ModelInfo]]:
55
+ """Return one ModelInfo when both arguments are given, else all matching entries.
56
+
57
+ A single lookup uses get_tier() and so applies its fallback rules.
58
+ """
59
+ if accelerator is not None and size is not None:
60
+ return ModelInfo.from_entry(accelerator, size, get_tier(accelerator, size))
61
+
62
+ return [
63
+ ModelInfo.from_entry(acc, sz, entry)
64
+ for acc, tiers in MODEL_DATABASE.items()
65
+ if accelerator is None or acc == accelerator
66
+ for sz, entry in tiers.items()
67
+ if size is None or sz == size
68
+ ]
@@ -0,0 +1,19 @@
1
+ from enum import Enum
2
+
3
+
4
+ class AcceleratorType(Enum):
5
+ NONE = "none"
6
+ NVIDIA_CUDA = "nvidia_cuda"
7
+ NVIDIA_MPS = "nvidia_mps"
8
+ AMD_ROCM = "amd_rocm"
9
+ INTEL_ONEAPI = "intel_oneapi"
10
+ APPLE_METAL = "apple_metal"
11
+ QUALCOMM_HEXAGON = "qualcomm_hexagon"
12
+
13
+
14
+ class ModelSize(Enum):
15
+ TINY = "tiny"
16
+ SMALL = "small"
17
+ MEDIUM = "medium"
18
+ LARGE = "large"
19
+ XLARGE = "xlarge"
@@ -0,0 +1,68 @@
1
+ Metadata-Version: 2.4
2
+ Name: hestia-system
3
+ Version: 0.1.0
4
+ Summary: System discovery, hardware and software analysis, and AI model recommendations.
5
+ Author: Damon S. Green II (nomadiidamon)
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/nomadiidamon/Hestia
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: psutil>=5.9.0
11
+ Provides-Extra: nvidia
12
+ Requires-Dist: nvidia-ml-py>=12.0.0; extra == "nvidia"
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=7.0; extra == "dev"
15
+ Dynamic: author
16
+ Dynamic: description
17
+ Dynamic: description-content-type
18
+ Dynamic: license
19
+ Dynamic: project-url
20
+ Dynamic: requires-python
21
+ Dynamic: summary
22
+
23
+ # Hestia for Python
24
+
25
+ Hestia provides system discovery and AI model intelligence. The Python package
26
+ currently exposes accelerator and model-size types, a model catalog, and model
27
+ information lookup.
28
+
29
+ The distribution is named `hestia-system`; import it as `Hestia`.
30
+
31
+ ## Installation
32
+
33
+ ```bash
34
+ python -m pip install hestia-system
35
+ ```
36
+
37
+ Optional dependencies are available as extras:
38
+
39
+ ```bash
40
+ python -m pip install "hestia-system[nvidia]" # NVIDIA telemetry support
41
+ python -m pip install "hestia-system[dev]" # pytest
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ ```python
47
+ from Hestia import AcceleratorType, ModelSize, get_model_info
48
+
49
+ info = get_model_info(AcceleratorType.NVIDIA_CUDA, ModelSize.SMALL)
50
+ print(info)
51
+ ```
52
+
53
+ The package supports Python versions declared by the project metadata (Python
54
+ 3.10 and later).
55
+
56
+ ## Development
57
+
58
+ From the repository checkout:
59
+
60
+ ```bash
61
+ cd python
62
+ python -m pip install -e ".[dev]"
63
+ python -m pytest
64
+ ```
65
+
66
+ Package metadata is maintained in the repository's `project.ini`. Source
67
+ distributions include a copy of that file so they can be built independently
68
+ of the repository checkout.
@@ -0,0 +1,18 @@
1
+ MANIFEST.in
2
+ README.md
3
+ project.ini
4
+ pyproject.toml
5
+ setup.py
6
+ src/Hestia/__init__.py
7
+ src/Hestia/model_catalog.py
8
+ src/Hestia/types.py
9
+ src/Hestia/models/__init__.py
10
+ src/Hestia/models/catalog.py
11
+ src/Hestia/models/info.py
12
+ src/hestia_system.egg-info/PKG-INFO
13
+ src/hestia_system.egg-info/SOURCES.txt
14
+ src/hestia_system.egg-info/dependency_links.txt
15
+ src/hestia_system.egg-info/requires.txt
16
+ src/hestia_system.egg-info/top_level.txt
17
+ tests/test_examples.py
18
+ tests/test_models.py
@@ -0,0 +1,7 @@
1
+ psutil>=5.9.0
2
+
3
+ [dev]
4
+ pytest>=7.0
5
+
6
+ [nvidia]
7
+ nvidia-ml-py>=12.0.0
@@ -0,0 +1,21 @@
1
+ """Run every example script and require a clean exit."""
2
+
3
+ import os
4
+ import subprocess
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ ROOT = Path(__file__).resolve().parents[2]
11
+ SRC = ROOT / "python" / "src"
12
+ EXAMPLES = sorted((ROOT / "examples" / "python").rglob("*.py"))
13
+
14
+
15
+ @pytest.mark.parametrize("example", EXAMPLES, ids=lambda p: p.relative_to(ROOT).as_posix())
16
+ def test_example_runs(example):
17
+ env = {**os.environ, "PYTHONPATH": os.pathsep.join([str(SRC), os.environ.get("PYTHONPATH", "")])}
18
+ result = subprocess.run(
19
+ [sys.executable, str(example)], capture_output=True, text=True, timeout=60, env=env
20
+ )
21
+ assert result.returncode == 0, result.stderr
@@ -0,0 +1,75 @@
1
+ import os
2
+ import subprocess
3
+ import sys
4
+
5
+ import pytest
6
+
7
+ import Hestia
8
+ from Hestia.models import (
9
+ KNOWN_CATEGORIES,
10
+ MODEL_DATABASE,
11
+ ModelInfo,
12
+ get_category,
13
+ get_model_info,
14
+ get_tier,
15
+ )
16
+ from Hestia.types import AcceleratorType, ModelSize
17
+
18
+
19
+ def test_enum_values():
20
+ assert AcceleratorType("nvidia_cuda") is AcceleratorType.NVIDIA_CUDA
21
+ assert [s.value for s in ModelSize] == ["tiny", "small", "medium", "large", "xlarge"]
22
+
23
+
24
+ def test_every_entry_has_required_keys():
25
+ for tiers in MODEL_DATABASE.values():
26
+ for entry in tiers.values():
27
+ for key in (*KNOWN_CATEGORIES, "quantization", "batch_size", "context", "performance"):
28
+ assert key in entry
29
+
30
+
31
+ @pytest.mark.parametrize("acc", list(MODEL_DATABASE))
32
+ @pytest.mark.parametrize("size", list(ModelSize))
33
+ def test_get_tier_always_resolves(acc, size):
34
+ assert get_tier(acc, size)["models"]
35
+
36
+
37
+ def test_fallback_for_missing_tier():
38
+ assert get_tier(AcceleratorType.INTEL_ONEAPI, ModelSize.XLARGE) is MODEL_DATABASE[
39
+ AcceleratorType.INTEL_ONEAPI
40
+ ][ModelSize.LARGE]
41
+
42
+
43
+ def test_unknown_accelerator_falls_back_to_cpu():
44
+ entry = get_tier(AcceleratorType.QUALCOMM_HEXAGON, ModelSize.TINY)
45
+ assert entry is MODEL_DATABASE[AcceleratorType.NONE][ModelSize.MEDIUM]
46
+
47
+
48
+ def test_get_category_unknown_is_empty():
49
+ assert get_category(get_tier(AcceleratorType.NONE, ModelSize.TINY), "nope") == []
50
+
51
+
52
+ def test_get_model_info_single_and_all():
53
+ info = get_model_info(AcceleratorType.NVIDIA_CUDA, ModelSize.SMALL)
54
+ assert isinstance(info, ModelInfo)
55
+ assert info.to_dict()["accelerator"] == "nvidia_cuda"
56
+ assert info.get("tools")
57
+ total = sum(len(t) for t in MODEL_DATABASE.values())
58
+ assert len(get_model_info()) == total
59
+ assert all(i.size is ModelSize.TINY for i in get_model_info(size=ModelSize.TINY))
60
+
61
+
62
+ def test_legacy_import_path():
63
+ from Hestia.model_catalog import MODEL_DATABASE as legacy
64
+
65
+ assert legacy is MODEL_DATABASE
66
+
67
+
68
+ def test_public_api():
69
+ assert Hestia.get_model_info is get_model_info
70
+
71
+
72
+ def test_models_does_not_import_hardware():
73
+ code = "import sys, Hestia.models; assert not any(m.startswith('Hestia.hardware') for m in sys.modules)"
74
+ env = {**os.environ, "PYTHONPATH": os.pathsep.join(sys.path)}
75
+ subprocess.run([sys.executable, "-c", code], check=True, env=env)