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.
Files changed (70) hide show
  1. haoline/.streamlit/config.toml +10 -0
  2. haoline/__init__.py +248 -0
  3. haoline/analyzer.py +935 -0
  4. haoline/cli.py +2712 -0
  5. haoline/compare.py +811 -0
  6. haoline/compare_visualizations.py +1564 -0
  7. haoline/edge_analysis.py +525 -0
  8. haoline/eval/__init__.py +131 -0
  9. haoline/eval/adapters.py +844 -0
  10. haoline/eval/cli.py +390 -0
  11. haoline/eval/comparison.py +542 -0
  12. haoline/eval/deployment.py +633 -0
  13. haoline/eval/schemas.py +833 -0
  14. haoline/examples/__init__.py +15 -0
  15. haoline/examples/basic_inspection.py +74 -0
  16. haoline/examples/compare_models.py +117 -0
  17. haoline/examples/hardware_estimation.py +78 -0
  18. haoline/format_adapters.py +1001 -0
  19. haoline/formats/__init__.py +123 -0
  20. haoline/formats/coreml.py +250 -0
  21. haoline/formats/gguf.py +483 -0
  22. haoline/formats/openvino.py +255 -0
  23. haoline/formats/safetensors.py +273 -0
  24. haoline/formats/tflite.py +369 -0
  25. haoline/hardware.py +2307 -0
  26. haoline/hierarchical_graph.py +462 -0
  27. haoline/html_export.py +1573 -0
  28. haoline/layer_summary.py +769 -0
  29. haoline/llm_summarizer.py +465 -0
  30. haoline/op_icons.py +618 -0
  31. haoline/operational_profiling.py +1492 -0
  32. haoline/patterns.py +1116 -0
  33. haoline/pdf_generator.py +265 -0
  34. haoline/privacy.py +250 -0
  35. haoline/pydantic_models.py +241 -0
  36. haoline/report.py +1923 -0
  37. haoline/report_sections.py +539 -0
  38. haoline/risks.py +521 -0
  39. haoline/schema.py +523 -0
  40. haoline/streamlit_app.py +2024 -0
  41. haoline/tests/__init__.py +4 -0
  42. haoline/tests/conftest.py +123 -0
  43. haoline/tests/test_analyzer.py +868 -0
  44. haoline/tests/test_compare_visualizations.py +293 -0
  45. haoline/tests/test_edge_analysis.py +243 -0
  46. haoline/tests/test_eval.py +604 -0
  47. haoline/tests/test_format_adapters.py +460 -0
  48. haoline/tests/test_hardware.py +237 -0
  49. haoline/tests/test_hardware_recommender.py +90 -0
  50. haoline/tests/test_hierarchical_graph.py +326 -0
  51. haoline/tests/test_html_export.py +180 -0
  52. haoline/tests/test_layer_summary.py +428 -0
  53. haoline/tests/test_llm_patterns.py +540 -0
  54. haoline/tests/test_llm_summarizer.py +339 -0
  55. haoline/tests/test_patterns.py +774 -0
  56. haoline/tests/test_pytorch.py +327 -0
  57. haoline/tests/test_report.py +383 -0
  58. haoline/tests/test_risks.py +398 -0
  59. haoline/tests/test_schema.py +417 -0
  60. haoline/tests/test_tensorflow.py +380 -0
  61. haoline/tests/test_visualizations.py +316 -0
  62. haoline/universal_ir.py +856 -0
  63. haoline/visualizations.py +1086 -0
  64. haoline/visualize_yolo.py +44 -0
  65. haoline/web.py +110 -0
  66. haoline-0.3.0.dist-info/METADATA +471 -0
  67. haoline-0.3.0.dist-info/RECORD +70 -0
  68. haoline-0.3.0.dist-info/WHEEL +4 -0
  69. haoline-0.3.0.dist-info/entry_points.txt +5 -0
  70. haoline-0.3.0.dist-info/licenses/LICENSE +22 -0
haoline/schema.py ADDED
@@ -0,0 +1,523 @@
1
+ # Copyright (c) 2025 HaoLine Contributors
2
+ # SPDX-License-Identifier: MIT
3
+
4
+ """
5
+ JSON Schema definition and validation for HaoLine reports.
6
+
7
+ Provides:
8
+ - INSPECTION_REPORT_SCHEMA: JSON Schema for InspectionReport (auto-generated from Pydantic)
9
+ - validate_report(): Validate a report dict against the schema using Pydantic
10
+ - ValidationError: Exception raised on validation failure
11
+
12
+ The Pydantic models in pydantic_models.py were auto-generated from the original
13
+ JSON Schema using datamodel-code-generator. Validation is now done via Pydantic
14
+ for better error messages and type safety.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import warnings
20
+ from typing import Any
21
+
22
+ # Try to import Pydantic models (preferred)
23
+ try:
24
+ from pydantic import ValidationError as PydanticValidationError
25
+
26
+ from .pydantic_models import HaolineInspectionReport
27
+
28
+ PYDANTIC_AVAILABLE = True
29
+ except ImportError:
30
+ PYDANTIC_AVAILABLE = False
31
+ PydanticValidationError = None # type: ignore
32
+ HaolineInspectionReport = None # type: ignore
33
+
34
+ # Fallback to jsonschema if Pydantic not available
35
+ try:
36
+ from jsonschema import Draft7Validator
37
+ from jsonschema import ValidationError as JsonSchemaError
38
+
39
+ JSONSCHEMA_AVAILABLE = True
40
+ except ImportError:
41
+ JSONSCHEMA_AVAILABLE = False
42
+ JsonSchemaError = None # type: ignore
43
+
44
+
45
+ class ValidationError(Exception):
46
+ """Raised when JSON schema validation fails."""
47
+
48
+ def __init__(self, message: str, errors: list[str] | None = None):
49
+ super().__init__(message)
50
+ self.errors = errors or []
51
+
52
+
53
+ # JSON Schema for InspectionReport (Draft 7)
54
+ INSPECTION_REPORT_SCHEMA: dict[str, Any] = {
55
+ "$schema": "http://json-schema.org/draft-07/schema#",
56
+ "$id": "https://github.com/mdayku/HaoLine/schema/inspection-report.schema.json",
57
+ "title": "HaoLine Inspection Report",
58
+ "description": "Schema for ONNX model inspection reports generated by HaoLine",
59
+ "type": "object",
60
+ "required": ["metadata", "generated_at", "autodoc_version"],
61
+ "properties": {
62
+ "metadata": {
63
+ "type": "object",
64
+ "description": "Model metadata extracted from ONNX proto",
65
+ "required": ["path", "ir_version", "producer_name", "opsets"],
66
+ "properties": {
67
+ "path": {
68
+ "type": "string",
69
+ "description": "Path to the ONNX model file",
70
+ },
71
+ "ir_version": {
72
+ "type": "integer",
73
+ "minimum": 1,
74
+ "description": "ONNX IR version",
75
+ },
76
+ "producer_name": {
77
+ "type": "string",
78
+ "description": "Name of the tool that produced the model",
79
+ },
80
+ "producer_version": {
81
+ "type": "string",
82
+ "description": "Version of the producer tool",
83
+ },
84
+ "domain": {"type": "string", "description": "Model domain"},
85
+ "model_version": {
86
+ "type": "integer",
87
+ "description": "Model version number",
88
+ },
89
+ "doc_string": {
90
+ "type": "string",
91
+ "description": "Model documentation string",
92
+ },
93
+ "opsets": {
94
+ "type": "object",
95
+ "description": "Opset versions by domain",
96
+ "additionalProperties": {"type": "integer", "minimum": 1},
97
+ },
98
+ },
99
+ },
100
+ "generated_at": {
101
+ "type": "string",
102
+ "format": "date-time",
103
+ "description": "ISO 8601 timestamp when report was generated",
104
+ },
105
+ "autodoc_version": {
106
+ "type": "string",
107
+ "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+",
108
+ "description": "Version of HaoLine that generated the report",
109
+ },
110
+ "graph_summary": {
111
+ "type": ["object", "null"],
112
+ "description": "Summary statistics about the ONNX graph",
113
+ "properties": {
114
+ "num_nodes": {
115
+ "type": "integer",
116
+ "minimum": 0,
117
+ "description": "Total number of nodes in graph",
118
+ },
119
+ "num_inputs": {
120
+ "type": "integer",
121
+ "minimum": 0,
122
+ "description": "Number of graph inputs",
123
+ },
124
+ "num_outputs": {
125
+ "type": "integer",
126
+ "minimum": 0,
127
+ "description": "Number of graph outputs",
128
+ },
129
+ "num_initializers": {
130
+ "type": "integer",
131
+ "minimum": 0,
132
+ "description": "Number of initializers (weights)",
133
+ },
134
+ "input_shapes": {
135
+ "type": "object",
136
+ "description": "Input tensor shapes by name",
137
+ "additionalProperties": {
138
+ "type": "array",
139
+ "items": {"type": ["integer", "string"]},
140
+ },
141
+ },
142
+ "output_shapes": {
143
+ "type": "object",
144
+ "description": "Output tensor shapes by name",
145
+ "additionalProperties": {
146
+ "type": "array",
147
+ "items": {"type": ["integer", "string"]},
148
+ },
149
+ },
150
+ "op_type_counts": {
151
+ "type": "object",
152
+ "description": "Count of each operator type",
153
+ "additionalProperties": {"type": "integer", "minimum": 0},
154
+ },
155
+ },
156
+ },
157
+ "param_counts": {
158
+ "type": ["object", "null"],
159
+ "description": "Parameter count statistics",
160
+ "properties": {
161
+ "total": {
162
+ "type": "integer",
163
+ "minimum": 0,
164
+ "description": "Total parameter count",
165
+ },
166
+ "trainable": {
167
+ "type": "integer",
168
+ "minimum": 0,
169
+ "description": "Trainable parameter count",
170
+ },
171
+ "non_trainable": {
172
+ "type": "integer",
173
+ "minimum": 0,
174
+ "description": "Non-trainable parameter count",
175
+ },
176
+ "by_op_type": {
177
+ "type": "object",
178
+ "description": "Parameters by operator type (fractional for shared weights)",
179
+ "additionalProperties": {"type": "number", "minimum": 0},
180
+ },
181
+ "shared_weights": {
182
+ "type": "object",
183
+ "description": "Information about shared weights",
184
+ "properties": {
185
+ "count": {
186
+ "type": "integer",
187
+ "minimum": 0,
188
+ "description": "Number of weights shared across 2+ nodes",
189
+ },
190
+ "details": {
191
+ "type": "object",
192
+ "description": "Shared weight name to list of node names using it",
193
+ "additionalProperties": {
194
+ "type": "array",
195
+ "items": {"type": "string"},
196
+ },
197
+ },
198
+ },
199
+ },
200
+ "precision_breakdown": {
201
+ "type": "object",
202
+ "description": "Parameter count by data type (fp32, fp16, int8, etc.)",
203
+ "additionalProperties": {"type": "integer", "minimum": 0},
204
+ },
205
+ "is_quantized": {
206
+ "type": "boolean",
207
+ "description": "Whether model uses quantized weights or ops",
208
+ },
209
+ "quantized_ops": {
210
+ "type": "array",
211
+ "description": "List of quantized operation types detected",
212
+ "items": {"type": "string"},
213
+ },
214
+ },
215
+ },
216
+ "flop_counts": {
217
+ "type": ["object", "null"],
218
+ "description": "FLOP estimation statistics",
219
+ "properties": {
220
+ "total": {
221
+ "type": "integer",
222
+ "minimum": 0,
223
+ "description": "Total estimated FLOPs",
224
+ },
225
+ "by_node_type": {
226
+ "type": "object",
227
+ "description": "FLOPs by operator type",
228
+ "additionalProperties": {"type": "integer", "minimum": 0},
229
+ },
230
+ "hotspots": {
231
+ "type": "array",
232
+ "description": "Top compute-intensive nodes",
233
+ "items": {
234
+ "type": "object",
235
+ "properties": {
236
+ "name": {"type": "string"},
237
+ "op_type": {"type": "string"},
238
+ "flops": {"type": "integer", "minimum": 0},
239
+ },
240
+ },
241
+ },
242
+ },
243
+ },
244
+ "memory_estimates": {
245
+ "type": ["object", "null"],
246
+ "description": "Memory usage estimates",
247
+ "properties": {
248
+ "model_size_bytes": {
249
+ "type": "integer",
250
+ "minimum": 0,
251
+ "description": "Model size in bytes",
252
+ },
253
+ "peak_activation_bytes": {
254
+ "type": "integer",
255
+ "minimum": 0,
256
+ "description": "Peak activation memory in bytes",
257
+ },
258
+ "kv_cache_bytes_per_token": {
259
+ "type": "integer",
260
+ "minimum": 0,
261
+ "description": "KV cache memory per token (transformers)",
262
+ },
263
+ "kv_cache_bytes_full_context": {
264
+ "type": "integer",
265
+ "minimum": 0,
266
+ "description": "KV cache for full context length",
267
+ },
268
+ "kv_cache_config": {
269
+ "type": "object",
270
+ "description": "KV cache configuration",
271
+ "properties": {
272
+ "num_layers": {"type": "integer", "minimum": 0},
273
+ "hidden_dim": {"type": "integer", "minimum": 0},
274
+ "seq_len": {"type": "integer", "minimum": 0},
275
+ "bytes_per_element": {"type": "integer", "minimum": 1},
276
+ },
277
+ },
278
+ "breakdown": {
279
+ "type": ["object", "null"],
280
+ "description": "Memory breakdown by component",
281
+ "properties": {
282
+ "weights_by_op_type": {
283
+ "type": "object",
284
+ "additionalProperties": {"type": "integer", "minimum": 0},
285
+ },
286
+ "activations_by_op_type": {
287
+ "type": "object",
288
+ "additionalProperties": {"type": "integer", "minimum": 0},
289
+ },
290
+ },
291
+ },
292
+ },
293
+ },
294
+ "detected_blocks": {
295
+ "type": "array",
296
+ "description": "Detected architectural blocks",
297
+ "items": {
298
+ "type": "object",
299
+ "required": ["block_type", "name"],
300
+ "properties": {
301
+ "block_type": {
302
+ "type": "string",
303
+ "description": "Type of block (e.g., ResidualAdd, Attention)",
304
+ },
305
+ "name": {"type": "string", "description": "Block identifier"},
306
+ "nodes": {
307
+ "type": "array",
308
+ "items": {"type": "string"},
309
+ "description": "Node names in this block",
310
+ },
311
+ "start_node": {"type": "string"},
312
+ "end_node": {"type": "string"},
313
+ "attributes": {
314
+ "type": "object",
315
+ "description": "Block-specific attributes",
316
+ },
317
+ },
318
+ },
319
+ },
320
+ "architecture_type": {
321
+ "type": "string",
322
+ "enum": ["transformer", "cnn", "mlp", "hybrid", "unknown"],
323
+ "description": "Detected architecture type",
324
+ },
325
+ "risk_signals": {
326
+ "type": "array",
327
+ "description": "Detected risk signals",
328
+ "items": {
329
+ "type": "object",
330
+ "required": ["id", "severity", "description"],
331
+ "properties": {
332
+ "id": {
333
+ "type": "string",
334
+ "description": "Risk signal identifier",
335
+ },
336
+ "severity": {
337
+ "type": "string",
338
+ "enum": ["info", "warning", "high"],
339
+ "description": "Severity level",
340
+ },
341
+ "description": {
342
+ "type": "string",
343
+ "description": "Human-readable description",
344
+ },
345
+ "nodes": {
346
+ "type": "array",
347
+ "items": {"type": "string"},
348
+ "description": "Affected node names",
349
+ },
350
+ "recommendation": {
351
+ "type": "string",
352
+ "description": "Recommended action",
353
+ },
354
+ },
355
+ },
356
+ },
357
+ "hardware_profile": {
358
+ "type": ["object", "null"],
359
+ "description": "Target hardware profile",
360
+ "properties": {
361
+ "name": {"type": "string"},
362
+ "vram_bytes": {"type": "integer", "minimum": 0},
363
+ "peak_fp32_tflops": {"type": "number", "minimum": 0},
364
+ "peak_fp16_tflops": {"type": "number", "minimum": 0},
365
+ "memory_bandwidth_gbps": {"type": "number", "minimum": 0},
366
+ "tdp_watts": {"type": ["integer", "null"]},
367
+ },
368
+ },
369
+ "hardware_estimates": {
370
+ "type": ["object", "null"],
371
+ "description": "Hardware-specific estimates",
372
+ "properties": {
373
+ "device": {"type": "string"},
374
+ "precision": {"type": "string"},
375
+ "batch_size": {"type": "integer", "minimum": 1},
376
+ "vram_required_bytes": {"type": "integer", "minimum": 0},
377
+ "fits_in_vram": {"type": "boolean"},
378
+ "theoretical_latency_ms": {"type": "number", "minimum": 0},
379
+ "bottleneck": {"type": "string"},
380
+ "compute_utilization_estimate": {"type": "number", "minimum": 0},
381
+ "gpu_saturation": {"type": "number", "minimum": 0},
382
+ },
383
+ },
384
+ "llm_summary": {
385
+ "type": ["object", "null"],
386
+ "description": "LLM-generated summary",
387
+ "properties": {
388
+ "success": {"type": "boolean"},
389
+ "short_summary": {"type": "string"},
390
+ "detailed_summary": {"type": "string"},
391
+ "model": {"type": "string"},
392
+ "error": {"type": "string"},
393
+ },
394
+ },
395
+ "dataset_info": {
396
+ "type": ["object", "null"],
397
+ "description": "Dataset and class information",
398
+ "properties": {
399
+ "task": {"type": ["string", "null"]},
400
+ "num_classes": {"type": ["integer", "null"], "minimum": 0},
401
+ "class_names": {
402
+ "type": "array",
403
+ "items": {"type": "string"},
404
+ },
405
+ "source": {"type": ["string", "null"]},
406
+ },
407
+ },
408
+ },
409
+ }
410
+
411
+
412
+ def validate_report(report_dict: dict[str, Any]) -> tuple[bool, list[str]]:
413
+ """
414
+ Validate a report dictionary against the schema.
415
+
416
+ Uses Pydantic for validation (preferred) or falls back to jsonschema.
417
+
418
+ Args:
419
+ report_dict: The report as a dictionary (from to_dict()).
420
+
421
+ Returns:
422
+ Tuple of (is_valid, list of error messages).
423
+ If neither pydantic nor jsonschema is installed, returns (True, []) with a warning.
424
+ """
425
+ # Prefer Pydantic validation
426
+ if PYDANTIC_AVAILABLE:
427
+ try:
428
+ HaolineInspectionReport.model_validate(report_dict)
429
+ return True, []
430
+ except PydanticValidationError as e:
431
+ error_messages = []
432
+ for error in e.errors():
433
+ loc = " -> ".join(str(x) for x in error["loc"])
434
+ error_messages.append(f"{loc}: {error['msg']}")
435
+ return False, error_messages
436
+
437
+ # Fallback to jsonschema
438
+ if JSONSCHEMA_AVAILABLE:
439
+ validator = Draft7Validator(INSPECTION_REPORT_SCHEMA)
440
+ errors = list(validator.iter_errors(report_dict))
441
+
442
+ if not errors:
443
+ return True, []
444
+
445
+ error_messages = []
446
+ for error in errors:
447
+ path = " -> ".join(str(p) for p in error.absolute_path) or "root"
448
+ error_messages.append(f"{path}: {error.message}")
449
+
450
+ return False, error_messages
451
+
452
+ # No validation library available
453
+ warnings.warn(
454
+ "Neither pydantic nor jsonschema installed. "
455
+ "Install with 'pip install pydantic' for validation.",
456
+ UserWarning,
457
+ stacklevel=2,
458
+ )
459
+ return True, []
460
+
461
+
462
+ def validate_report_strict(report_dict: dict[str, Any]) -> None:
463
+ """
464
+ Validate a report dictionary, raising ValidationError on failure.
465
+
466
+ Args:
467
+ report_dict: The report as a dictionary (from to_dict()).
468
+
469
+ Raises:
470
+ ValidationError: If validation fails.
471
+ """
472
+ is_valid, errors = validate_report(report_dict)
473
+ if not is_valid:
474
+ raise ValidationError(
475
+ f"Report validation failed with {len(errors)} error(s)", errors=errors
476
+ )
477
+
478
+
479
+ def get_schema() -> dict[str, Any]:
480
+ """
481
+ Return the JSON schema for InspectionReport.
482
+
483
+ If Pydantic is available, returns the auto-generated schema from the model.
484
+ Otherwise, returns the manually-defined schema.
485
+ """
486
+ if PYDANTIC_AVAILABLE:
487
+ schema: dict[str, Any] = HaolineInspectionReport.model_json_schema()
488
+ return schema
489
+ return INSPECTION_REPORT_SCHEMA.copy()
490
+
491
+
492
+ def validate_with_pydantic(report_dict: dict[str, Any]) -> HaolineInspectionReport | None:
493
+ """
494
+ Validate and parse a report dict into a Pydantic model.
495
+
496
+ Args:
497
+ report_dict: The report as a dictionary.
498
+
499
+ Returns:
500
+ HaolineInspectionReport instance if valid, None if Pydantic not available.
501
+
502
+ Raises:
503
+ ValidationError: If validation fails.
504
+ """
505
+ if not PYDANTIC_AVAILABLE:
506
+ warnings.warn(
507
+ "Pydantic not installed. Install with 'pip install pydantic'.",
508
+ UserWarning,
509
+ stacklevel=2,
510
+ )
511
+ return None
512
+
513
+ try:
514
+ result: HaolineInspectionReport = HaolineInspectionReport.model_validate(report_dict)
515
+ return result
516
+ except PydanticValidationError as e:
517
+ error_messages = [
518
+ f"{' -> '.join(str(x) for x in err['loc'])}: {err['msg']}" for err in e.errors()
519
+ ]
520
+ raise ValidationError(
521
+ f"Report validation failed with {len(error_messages)} error(s)",
522
+ errors=error_messages,
523
+ ) from e