haoline 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- haoline/.streamlit/config.toml +10 -0
- haoline/__init__.py +248 -0
- haoline/analyzer.py +935 -0
- haoline/cli.py +2712 -0
- haoline/compare.py +811 -0
- haoline/compare_visualizations.py +1564 -0
- haoline/edge_analysis.py +525 -0
- haoline/eval/__init__.py +131 -0
- haoline/eval/adapters.py +844 -0
- haoline/eval/cli.py +390 -0
- haoline/eval/comparison.py +542 -0
- haoline/eval/deployment.py +633 -0
- haoline/eval/schemas.py +833 -0
- haoline/examples/__init__.py +15 -0
- haoline/examples/basic_inspection.py +74 -0
- haoline/examples/compare_models.py +117 -0
- haoline/examples/hardware_estimation.py +78 -0
- haoline/format_adapters.py +1001 -0
- haoline/formats/__init__.py +123 -0
- haoline/formats/coreml.py +250 -0
- haoline/formats/gguf.py +483 -0
- haoline/formats/openvino.py +255 -0
- haoline/formats/safetensors.py +273 -0
- haoline/formats/tflite.py +369 -0
- haoline/hardware.py +2307 -0
- haoline/hierarchical_graph.py +462 -0
- haoline/html_export.py +1573 -0
- haoline/layer_summary.py +769 -0
- haoline/llm_summarizer.py +465 -0
- haoline/op_icons.py +618 -0
- haoline/operational_profiling.py +1492 -0
- haoline/patterns.py +1116 -0
- haoline/pdf_generator.py +265 -0
- haoline/privacy.py +250 -0
- haoline/pydantic_models.py +241 -0
- haoline/report.py +1923 -0
- haoline/report_sections.py +539 -0
- haoline/risks.py +521 -0
- haoline/schema.py +523 -0
- haoline/streamlit_app.py +2024 -0
- haoline/tests/__init__.py +4 -0
- haoline/tests/conftest.py +123 -0
- haoline/tests/test_analyzer.py +868 -0
- haoline/tests/test_compare_visualizations.py +293 -0
- haoline/tests/test_edge_analysis.py +243 -0
- haoline/tests/test_eval.py +604 -0
- haoline/tests/test_format_adapters.py +460 -0
- haoline/tests/test_hardware.py +237 -0
- haoline/tests/test_hardware_recommender.py +90 -0
- haoline/tests/test_hierarchical_graph.py +326 -0
- haoline/tests/test_html_export.py +180 -0
- haoline/tests/test_layer_summary.py +428 -0
- haoline/tests/test_llm_patterns.py +540 -0
- haoline/tests/test_llm_summarizer.py +339 -0
- haoline/tests/test_patterns.py +774 -0
- haoline/tests/test_pytorch.py +327 -0
- haoline/tests/test_report.py +383 -0
- haoline/tests/test_risks.py +398 -0
- haoline/tests/test_schema.py +417 -0
- haoline/tests/test_tensorflow.py +380 -0
- haoline/tests/test_visualizations.py +316 -0
- haoline/universal_ir.py +856 -0
- haoline/visualizations.py +1086 -0
- haoline/visualize_yolo.py +44 -0
- haoline/web.py +110 -0
- haoline-0.3.0.dist-info/METADATA +471 -0
- haoline-0.3.0.dist-info/RECORD +70 -0
- haoline-0.3.0.dist-info/WHEEL +4 -0
- haoline-0.3.0.dist-info/entry_points.txt +5 -0
- haoline-0.3.0.dist-info/licenses/LICENSE +22 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Copyright (c) 2025 HaoLine Contributors
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
Format adapters for various model file formats.
|
|
6
|
+
|
|
7
|
+
Each adapter provides read (and optionally write) capabilities
|
|
8
|
+
for a specific model format, extracting metadata and tensor info.
|
|
9
|
+
|
|
10
|
+
Supported formats:
|
|
11
|
+
- GGUF: llama.cpp models (pure Python, no deps)
|
|
12
|
+
- SafeTensors: HuggingFace models (requires safetensors)
|
|
13
|
+
- TFLite: TensorFlow Lite models (pure Python header parsing)
|
|
14
|
+
- CoreML: Apple ML models (requires coremltools)
|
|
15
|
+
- OpenVINO: Intel models (requires openvino)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from .coreml import (
|
|
19
|
+
CoreMLInfo,
|
|
20
|
+
CoreMLReader,
|
|
21
|
+
is_coreml_file,
|
|
22
|
+
)
|
|
23
|
+
from .coreml import (
|
|
24
|
+
is_available as coreml_available,
|
|
25
|
+
)
|
|
26
|
+
from .gguf import GGUFInfo, GGUFReader, is_gguf_file
|
|
27
|
+
from .openvino import (
|
|
28
|
+
OpenVINOInfo,
|
|
29
|
+
OpenVINOReader,
|
|
30
|
+
is_openvino_file,
|
|
31
|
+
)
|
|
32
|
+
from .openvino import (
|
|
33
|
+
is_available as openvino_available,
|
|
34
|
+
)
|
|
35
|
+
from .safetensors import (
|
|
36
|
+
SafeTensorsInfo,
|
|
37
|
+
SafeTensorsReader,
|
|
38
|
+
is_safetensors_file,
|
|
39
|
+
)
|
|
40
|
+
from .safetensors import (
|
|
41
|
+
is_available as safetensors_available,
|
|
42
|
+
)
|
|
43
|
+
from .tflite import (
|
|
44
|
+
TFLiteInfo,
|
|
45
|
+
TFLiteReader,
|
|
46
|
+
is_tflite_file,
|
|
47
|
+
)
|
|
48
|
+
from .tflite import (
|
|
49
|
+
is_available as tflite_available,
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
# GGUF (Epic 24)
|
|
54
|
+
"GGUFReader",
|
|
55
|
+
"GGUFInfo",
|
|
56
|
+
"is_gguf_file",
|
|
57
|
+
# SafeTensors (Epic 19)
|
|
58
|
+
"SafeTensorsReader",
|
|
59
|
+
"SafeTensorsInfo",
|
|
60
|
+
"is_safetensors_file",
|
|
61
|
+
"safetensors_available",
|
|
62
|
+
# TFLite (Epic 21)
|
|
63
|
+
"TFLiteReader",
|
|
64
|
+
"TFLiteInfo",
|
|
65
|
+
"is_tflite_file",
|
|
66
|
+
"tflite_available",
|
|
67
|
+
# CoreML (Epic 20)
|
|
68
|
+
"CoreMLReader",
|
|
69
|
+
"CoreMLInfo",
|
|
70
|
+
"is_coreml_file",
|
|
71
|
+
"coreml_available",
|
|
72
|
+
# OpenVINO (Epic 23)
|
|
73
|
+
"OpenVINOReader",
|
|
74
|
+
"OpenVINOInfo",
|
|
75
|
+
"is_openvino_file",
|
|
76
|
+
"openvino_available",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def detect_format(path: str) -> str | None:
|
|
81
|
+
"""
|
|
82
|
+
Auto-detect model format from file.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
path: Path to model file.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Format name ('gguf', 'safetensors', 'tflite', 'coreml', 'openvino', 'onnx')
|
|
89
|
+
or None if unknown.
|
|
90
|
+
"""
|
|
91
|
+
from pathlib import Path as P
|
|
92
|
+
|
|
93
|
+
p = P(path)
|
|
94
|
+
|
|
95
|
+
# Check by extension first
|
|
96
|
+
suffix = p.suffix.lower()
|
|
97
|
+
if suffix == ".onnx":
|
|
98
|
+
return "onnx"
|
|
99
|
+
if suffix == ".gguf":
|
|
100
|
+
return "gguf"
|
|
101
|
+
if suffix == ".safetensors":
|
|
102
|
+
return "safetensors"
|
|
103
|
+
if suffix == ".tflite":
|
|
104
|
+
return "tflite"
|
|
105
|
+
if suffix in (".mlmodel", ".mlpackage"):
|
|
106
|
+
return "coreml"
|
|
107
|
+
if suffix == ".xml":
|
|
108
|
+
if is_openvino_file(path):
|
|
109
|
+
return "openvino"
|
|
110
|
+
|
|
111
|
+
# Check by magic bytes
|
|
112
|
+
if is_gguf_file(path):
|
|
113
|
+
return "gguf"
|
|
114
|
+
if is_safetensors_file(path):
|
|
115
|
+
return "safetensors"
|
|
116
|
+
if is_tflite_file(path):
|
|
117
|
+
return "tflite"
|
|
118
|
+
if is_coreml_file(path):
|
|
119
|
+
return "coreml"
|
|
120
|
+
if is_openvino_file(path):
|
|
121
|
+
return "openvino"
|
|
122
|
+
|
|
123
|
+
return None
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# Copyright (c) 2025 HaoLine Contributors
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
"""
|
|
5
|
+
CoreML format reader.
|
|
6
|
+
|
|
7
|
+
CoreML is Apple's machine learning framework for iOS, macOS, watchOS,
|
|
8
|
+
and tvOS. This reader extracts model metadata and layer information.
|
|
9
|
+
|
|
10
|
+
Requires: coremltools (pip install coremltools)
|
|
11
|
+
|
|
12
|
+
Reference: https://coremltools.readme.io/
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class CoreMLLayerInfo:
|
|
24
|
+
"""Information about a CoreML layer."""
|
|
25
|
+
|
|
26
|
+
name: str
|
|
27
|
+
type: str
|
|
28
|
+
inputs: list[str] = field(default_factory=list)
|
|
29
|
+
outputs: list[str] = field(default_factory=list)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class CoreMLInfo:
|
|
34
|
+
"""Parsed CoreML model information."""
|
|
35
|
+
|
|
36
|
+
path: Path
|
|
37
|
+
spec_version: int
|
|
38
|
+
description: str
|
|
39
|
+
author: str
|
|
40
|
+
license: str
|
|
41
|
+
layers: list[CoreMLLayerInfo] = field(default_factory=list)
|
|
42
|
+
inputs: list[dict[str, Any]] = field(default_factory=list)
|
|
43
|
+
outputs: list[dict[str, Any]] = field(default_factory=list)
|
|
44
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def layer_count(self) -> int:
|
|
48
|
+
"""Number of layers."""
|
|
49
|
+
return len(self.layers)
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def layer_type_counts(self) -> dict[str, int]:
|
|
53
|
+
"""Count of layers by type."""
|
|
54
|
+
counts: dict[str, int] = {}
|
|
55
|
+
for layer in self.layers:
|
|
56
|
+
counts[layer.type] = counts.get(layer.type, 0) + 1
|
|
57
|
+
return counts
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def model_type(self) -> str:
|
|
61
|
+
"""Detected model type."""
|
|
62
|
+
return str(self.metadata.get("model_type", "unknown"))
|
|
63
|
+
|
|
64
|
+
def to_dict(self) -> dict[str, Any]:
|
|
65
|
+
"""Convert to dictionary for JSON serialization."""
|
|
66
|
+
return {
|
|
67
|
+
"path": str(self.path),
|
|
68
|
+
"spec_version": self.spec_version,
|
|
69
|
+
"description": self.description,
|
|
70
|
+
"author": self.author,
|
|
71
|
+
"license": self.license,
|
|
72
|
+
"model_type": self.model_type,
|
|
73
|
+
"layer_count": self.layer_count,
|
|
74
|
+
"layer_type_counts": self.layer_type_counts,
|
|
75
|
+
"inputs": self.inputs,
|
|
76
|
+
"outputs": self.outputs,
|
|
77
|
+
"metadata": self.metadata,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class CoreMLReader:
|
|
82
|
+
"""Reader for CoreML format files (.mlmodel, .mlpackage)."""
|
|
83
|
+
|
|
84
|
+
def __init__(self, path: str | Path):
|
|
85
|
+
"""
|
|
86
|
+
Initialize reader with file path.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
path: Path to the CoreML model (.mlmodel or .mlpackage).
|
|
90
|
+
|
|
91
|
+
Raises:
|
|
92
|
+
ImportError: If coremltools is not installed.
|
|
93
|
+
"""
|
|
94
|
+
self.path = Path(path)
|
|
95
|
+
if not self.path.exists():
|
|
96
|
+
raise FileNotFoundError(f"CoreML model not found: {self.path}")
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
import coremltools # noqa: F401
|
|
100
|
+
except ImportError as e:
|
|
101
|
+
raise ImportError("coremltools required. Install with: pip install coremltools") from e
|
|
102
|
+
|
|
103
|
+
def read(self) -> CoreMLInfo:
|
|
104
|
+
"""
|
|
105
|
+
Read and parse the CoreML model.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
CoreMLInfo with model metadata.
|
|
109
|
+
"""
|
|
110
|
+
import coremltools as ct
|
|
111
|
+
|
|
112
|
+
# Load model
|
|
113
|
+
model = ct.models.MLModel(str(self.path))
|
|
114
|
+
spec = model.get_spec()
|
|
115
|
+
|
|
116
|
+
# Extract description
|
|
117
|
+
desc = spec.description
|
|
118
|
+
description = desc.metadata.shortDescription or ""
|
|
119
|
+
author = desc.metadata.author or ""
|
|
120
|
+
license_info = desc.metadata.license or ""
|
|
121
|
+
|
|
122
|
+
# Extract inputs
|
|
123
|
+
inputs = []
|
|
124
|
+
for inp in desc.input:
|
|
125
|
+
input_info = {"name": inp.name, "type": self._get_feature_type(inp.type)}
|
|
126
|
+
inputs.append(input_info)
|
|
127
|
+
|
|
128
|
+
# Extract outputs
|
|
129
|
+
outputs = []
|
|
130
|
+
for out in desc.output:
|
|
131
|
+
output_info = {"name": out.name, "type": self._get_feature_type(out.type)}
|
|
132
|
+
outputs.append(output_info)
|
|
133
|
+
|
|
134
|
+
# Extract layers based on model type
|
|
135
|
+
layers = []
|
|
136
|
+
metadata = {}
|
|
137
|
+
|
|
138
|
+
if spec.HasField("neuralNetwork"):
|
|
139
|
+
nn = spec.neuralNetwork
|
|
140
|
+
metadata["model_type"] = "neuralNetwork"
|
|
141
|
+
for layer in nn.layers:
|
|
142
|
+
layers.append(
|
|
143
|
+
CoreMLLayerInfo(
|
|
144
|
+
name=layer.name,
|
|
145
|
+
type=layer.WhichOneof("layer"),
|
|
146
|
+
inputs=list(layer.input),
|
|
147
|
+
outputs=list(layer.output),
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
elif spec.HasField("neuralNetworkClassifier"):
|
|
151
|
+
nn = spec.neuralNetworkClassifier
|
|
152
|
+
metadata["model_type"] = "neuralNetworkClassifier"
|
|
153
|
+
for layer in nn.layers:
|
|
154
|
+
layers.append(
|
|
155
|
+
CoreMLLayerInfo(
|
|
156
|
+
name=layer.name,
|
|
157
|
+
type=layer.WhichOneof("layer"),
|
|
158
|
+
inputs=list(layer.input),
|
|
159
|
+
outputs=list(layer.output),
|
|
160
|
+
)
|
|
161
|
+
)
|
|
162
|
+
elif spec.HasField("neuralNetworkRegressor"):
|
|
163
|
+
nn = spec.neuralNetworkRegressor
|
|
164
|
+
metadata["model_type"] = "neuralNetworkRegressor"
|
|
165
|
+
for layer in nn.layers:
|
|
166
|
+
layers.append(
|
|
167
|
+
CoreMLLayerInfo(
|
|
168
|
+
name=layer.name,
|
|
169
|
+
type=layer.WhichOneof("layer"),
|
|
170
|
+
inputs=list(layer.input),
|
|
171
|
+
outputs=list(layer.output),
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
elif spec.HasField("mlProgram"):
|
|
175
|
+
metadata["model_type"] = "mlProgram"
|
|
176
|
+
# ML Programs have a different structure
|
|
177
|
+
# Basic info only for now
|
|
178
|
+
elif spec.HasField("pipeline"):
|
|
179
|
+
metadata["model_type"] = "pipeline"
|
|
180
|
+
else:
|
|
181
|
+
metadata["model_type"] = "other"
|
|
182
|
+
|
|
183
|
+
return CoreMLInfo(
|
|
184
|
+
path=self.path,
|
|
185
|
+
spec_version=spec.specificationVersion,
|
|
186
|
+
description=description,
|
|
187
|
+
author=author,
|
|
188
|
+
license=license_info,
|
|
189
|
+
layers=layers,
|
|
190
|
+
inputs=inputs,
|
|
191
|
+
outputs=outputs,
|
|
192
|
+
metadata=metadata,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
def _get_feature_type(self, feature_type) -> str:
|
|
196
|
+
"""Get human-readable feature type."""
|
|
197
|
+
type_name = feature_type.WhichOneof("Type")
|
|
198
|
+
if type_name == "multiArrayType":
|
|
199
|
+
shape = list(feature_type.multiArrayType.shape)
|
|
200
|
+
dtype = feature_type.multiArrayType.dataType
|
|
201
|
+
dtype_names = {0: "INVALID", 65568: "FLOAT32", 65600: "FLOAT64", 131104: "INT32"}
|
|
202
|
+
return f"MultiArray({dtype_names.get(dtype, dtype)}, {shape})"
|
|
203
|
+
elif type_name == "imageType":
|
|
204
|
+
w = feature_type.imageType.width
|
|
205
|
+
h = feature_type.imageType.height
|
|
206
|
+
return f"Image({w}x{h})"
|
|
207
|
+
elif type_name == "dictionaryType":
|
|
208
|
+
return "Dictionary"
|
|
209
|
+
elif type_name == "stringType":
|
|
210
|
+
return "String"
|
|
211
|
+
elif type_name == "int64Type":
|
|
212
|
+
return "Int64"
|
|
213
|
+
elif type_name == "doubleType":
|
|
214
|
+
return "Double"
|
|
215
|
+
else:
|
|
216
|
+
return type_name or "unknown"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def is_coreml_file(path: str | Path) -> bool:
|
|
220
|
+
"""
|
|
221
|
+
Check if a file is a CoreML model.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
path: Path to check.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
True if the file is a CoreML model.
|
|
228
|
+
"""
|
|
229
|
+
path = Path(path)
|
|
230
|
+
if not path.exists():
|
|
231
|
+
return False
|
|
232
|
+
|
|
233
|
+
# Check extension
|
|
234
|
+
suffix = path.suffix.lower()
|
|
235
|
+
if suffix == ".mlmodel":
|
|
236
|
+
return True
|
|
237
|
+
if suffix == ".mlpackage" or (path.is_dir() and path.suffix == ".mlpackage"):
|
|
238
|
+
return True
|
|
239
|
+
|
|
240
|
+
return False
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def is_available() -> bool:
|
|
244
|
+
"""Check if coremltools is available."""
|
|
245
|
+
try:
|
|
246
|
+
import coremltools # noqa: F401
|
|
247
|
+
|
|
248
|
+
return True
|
|
249
|
+
except ImportError:
|
|
250
|
+
return False
|