amv-vision 2.0.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 (71) hide show
  1. amv_vision/__init__.py +75 -0
  2. amv_vision/__main__.py +7 -0
  3. amv_vision/adapter.py +201 -0
  4. amv_vision/bridge/__init__.py +13 -0
  5. amv_vision/bridge/amv_train_bridge.py +87 -0
  6. amv_vision/cli/__init__.py +5 -0
  7. amv_vision/cli/doctor.py +122 -0
  8. amv_vision/cli/main.py +548 -0
  9. amv_vision/cluster.py +219 -0
  10. amv_vision/control/__init__.py +5 -0
  11. amv_vision/control/component.py +391 -0
  12. amv_vision/control/status.py +15 -0
  13. amv_vision/csrc/__init__.py +24 -0
  14. amv_vision/csrc/backend.py +488 -0
  15. amv_vision/cv/__init__.py +24 -0
  16. amv_vision/cv/contours.py +79 -0
  17. amv_vision/cv/crop.py +24 -0
  18. amv_vision/cv/filters.py +165 -0
  19. amv_vision/cv/integral.py +62 -0
  20. amv_vision/cv/morphology.py +76 -0
  21. amv_vision/detect/__init__.py +16 -0
  22. amv_vision/detect/haar.py +194 -0
  23. amv_vision/detect/neural.py +129 -0
  24. amv_vision/detect/nms.py +85 -0
  25. amv_vision/detect/types.py +51 -0
  26. amv_vision/downloader.py +38 -0
  27. amv_vision/errors.py +306 -0
  28. amv_vision/exceptions.py +273 -0
  29. amv_vision/hardware.py +295 -0
  30. amv_vision/installer.py +573 -0
  31. amv_vision/io/__init__.py +14 -0
  32. amv_vision/io/camera.py +39 -0
  33. amv_vision/io/limits.py +35 -0
  34. amv_vision/io/loader.py +116 -0
  35. amv_vision/io/safetensors.py +99 -0
  36. amv_vision/models/__init__.py +17 -0
  37. amv_vision/models/conv_block.py +89 -0
  38. amv_vision/models/embedding.py +44 -0
  39. amv_vision/models/mobilenet.py +78 -0
  40. amv_vision/models/vit_patch.py +62 -0
  41. amv_vision/models/yolo_detector.py +89 -0
  42. amv_vision/models/yolo_post.py +83 -0
  43. amv_vision/transforms/__init__.py +46 -0
  44. amv_vision/transforms/compose.py +59 -0
  45. amv_vision/transforms/functional.py +115 -0
  46. amv_vision/transforms/scale.py +101 -0
  47. amv_vision/vlm/__init__.py +38 -0
  48. amv_vision/vlm/adapters/__init__.py +19 -0
  49. amv_vision/vlm/adapters/base.py +19 -0
  50. amv_vision/vlm/adapters/qwen2vl.py +10 -0
  51. amv_vision/vlm/adapters/smolvlm.py +10 -0
  52. amv_vision/vlm/api.py +290 -0
  53. amv_vision/vlm/cache.py +553 -0
  54. amv_vision/vlm/engine.py +243 -0
  55. amv_vision/vlm/manifest.py +89 -0
  56. amv_vision/vlm/memory.py +115 -0
  57. amv_vision/vlm/model_hub.py +150 -0
  58. amv_vision/vlm/pipeline.py +76 -0
  59. amv_vision/vlm/protocol.py +122 -0
  60. amv_vision/vlm/result.py +35 -0
  61. amv_vision/vlm/runtime/resolver.py +166 -0
  62. amv_vision/vlm/runtime/subprocess.py +263 -0
  63. amv_vision/vlm/supervisor.py +192 -0
  64. amv_vision-2.0.0.dist-info/METADATA +359 -0
  65. amv_vision-2.0.0.dist-info/RECORD +71 -0
  66. amv_vision-2.0.0.dist-info/WHEEL +5 -0
  67. amv_vision-2.0.0.dist-info/entry_points.txt +5 -0
  68. amv_vision-2.0.0.dist-info/licenses/LICENSE +176 -0
  69. amv_vision-2.0.0.dist-info/licenses/NOTICE +17 -0
  70. amv_vision-2.0.0.dist-info/top_level.txt +2 -0
  71. termux_vision/__init__.py +55 -0
amv_vision/__init__.py ADDED
@@ -0,0 +1,75 @@
1
+ # Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0 OR Commercial-License
3
+ """
4
+ amv-vision: Lightweight On-Device Computer Vision & VLM Framework for Android Termux.
5
+ Open-Source under Apache License 2.0.
6
+ """
7
+
8
+ __version__ = "2.0.0"
9
+ __author__ = "amv-vision contributors"
10
+ __license__ = "Apache-2.0"
11
+
12
+ from . import errors
13
+ from . import io
14
+ from . import transforms
15
+ from . import cv
16
+ from . import detect
17
+ from . import models
18
+ from . import bridge
19
+ from . import csrc
20
+ from . import vlm
21
+ from . import cli
22
+
23
+ from .detect.types import BoundingBox, Detection
24
+ from .detect.haar import detect_faces
25
+ from .models.embedding import Embedding, compute_similarity
26
+ from .vlm.api import load
27
+ from .hardware import HardwareProfile, detect_hardware, resolve_device
28
+ from .downloader import list_models, download_model, resolve_model_path
29
+ from . import cluster
30
+ from .exceptions import (
31
+ AmevaTermuxError,
32
+ TermuxVisionError,
33
+ ClusterConnectionError,
34
+ ClusterConfigurationError,
35
+ )
36
+ from .cluster import (
37
+ parse_cluster_rpc_spec,
38
+ verify_rpc_cluster_nodes,
39
+ verify_rpc_cluster_health,
40
+ )
41
+
42
+ __all__ = [
43
+ "io",
44
+ "transforms",
45
+ "cv",
46
+ "detect",
47
+ "models",
48
+ "bridge",
49
+ "csrc",
50
+ "vlm",
51
+ "cluster",
52
+ "errors",
53
+ "cli",
54
+ "BoundingBox",
55
+ "Detection",
56
+ "detect_faces",
57
+ "Embedding",
58
+ "compute_similarity",
59
+ "load",
60
+ "HardwareProfile",
61
+ "detect_hardware",
62
+ "resolve_device",
63
+ "list_models",
64
+ "download_model",
65
+ "resolve_model_path",
66
+ "AmevaTermuxError",
67
+ "TermuxVisionError",
68
+ "ClusterConnectionError",
69
+ "ClusterConfigurationError",
70
+ "parse_cluster_rpc_spec",
71
+ "verify_rpc_cluster_nodes",
72
+ "verify_rpc_cluster_health",
73
+ "__version__"
74
+ ]
75
+
amv_vision/__main__.py ADDED
@@ -0,0 +1,7 @@
1
+ # Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0 OR Commercial-License
3
+ import sys
4
+ from amv_vision.cli.main import main
5
+
6
+ if __name__ == "__main__":
7
+ sys.exit(main())
amv_vision/adapter.py ADDED
@@ -0,0 +1,201 @@
1
+ # Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0 OR Commercial-License
3
+ """
4
+ amv_vision.adapter
5
+ ======================
6
+ AMEVA Component Protocol v1 — Orchestrator Adapter (v0.8.1 호환)
7
+
8
+ P0-2: infer() fallback yield _not_supported → raise OperationNotSupported
9
+ P0-4: except Exception → retryable 분류, 원본 오류 코드 보존
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from typing import Any, AsyncIterator
14
+
15
+ from ameva_component.adapter_base import BaseOrchestratorAdapter
16
+ from ameva_component.exceptions import (
17
+ ComponentError,
18
+ OperationNotSupported,
19
+ redact_details,
20
+ redact_text,
21
+ )
22
+ from amv_vision.control.component import VisionControl
23
+
24
+
25
+ class VisionOrchestratorAdapter(BaseOrchestratorAdapter):
26
+ """Vision Orchestrator Adapter.
27
+
28
+ 이미지 분석 / VLM captioning은 파일 기반으로 수행됩니다.
29
+ infer(): image_path를 받아 분석 결과를 반환합니다.
30
+ Native backend 실패 시 fallback_used=True를 명시합니다.
31
+ """
32
+
33
+ COMPONENT_ID = "amv-vision"
34
+
35
+ _RETRYABLE_CODES: frozenset[str] = frozenset({
36
+ "REMOTE_TIMEOUT",
37
+ "MODEL_BUSY",
38
+ "BACKEND_OVERLOADED",
39
+ })
40
+ _NON_RETRYABLE_CODES: frozenset[str] = frozenset({
41
+ "IMAGE_PATH_FORBIDDEN",
42
+ "IMAGE_FORMAT_UNSUPPORTED",
43
+ "IMAGE_CORRUPT",
44
+ "MODEL_NOT_FOUND",
45
+ "HASH_MISMATCH",
46
+ })
47
+
48
+ def __init__(self, control: VisionControl | None = None) -> None:
49
+ self._control = control or VisionControl()
50
+
51
+ async def infer(self, request: dict[str, Any]) -> AsyncIterator[dict[str, Any]]:
52
+ """Vision inference: image_path → analysis result.
53
+
54
+ request 키:
55
+ image_path (str): 분석할 이미지 파일 경로 (필수)
56
+ task (str): "caption" | "vlm" | "detect" (선택, 기본: "caption")
57
+ prompt (str): VLM 프롬프트 (task="vlm"일 때)
58
+ model_id (str): 모델 ID (선택)
59
+
60
+ 반환 프레임:
61
+ {"type": "vision_result", "task": str, "result": dict, ...}
62
+ {"type": "error", "ok": False, "error": {...}}
63
+ """
64
+ image_path = request.get("image_path", "").strip()
65
+ if not image_path:
66
+ yield {
67
+ "type": "error",
68
+ "ok": False,
69
+ "error": {
70
+ "code": "IMAGE_PATH_MISSING",
71
+ "message": "image_path is required for Vision infer",
72
+ "operation": "infer",
73
+ "component_id": self.COMPONENT_ID,
74
+ "retryable": False,
75
+ },
76
+ }
77
+ return
78
+
79
+ if not hasattr(self._control, "analyze"):
80
+ # P0-2: fallback yield → raise
81
+ raise OperationNotSupported(operation="infer.analyze", component_id=self.COMPONENT_ID)
82
+
83
+ try:
84
+ result = await self._control.analyze(request)
85
+
86
+ if not isinstance(result, dict):
87
+ raise ValueError(f"analyze() must return dict, got {type(result).__name__}")
88
+
89
+ if result.get("ok") is not True:
90
+ err_payload = result.get("error") if isinstance(result.get("error"), dict) else {}
91
+ yield {
92
+ "type": "error",
93
+ "ok": False,
94
+ "error": {
95
+ "code": err_payload.get("code", "ADAPTER_RESULT_NOT_SUCCESS"),
96
+ "message": err_payload.get("message", "analyze() did not return ok=True"),
97
+ "operation": "infer",
98
+ "component_id": self.COMPONENT_ID,
99
+ "retryable": False,
100
+ "details": {"result_keys": sorted(result.keys())},
101
+ },
102
+ }
103
+ return
104
+
105
+ if "result" not in result:
106
+ raise ValueError("analyze() result missing required field 'result'")
107
+
108
+ yield {
109
+ "type": "vision_result",
110
+ "task": request.get("task", "caption"),
111
+ "result": result["result"],
112
+ "fallback_used": result.get("fallback_used", False),
113
+ "requested_backend": result.get("requested_backend"),
114
+ "executed_backend": result.get("executed_backend"),
115
+ "ok": True,
116
+ }
117
+
118
+ except ComponentError as component_err:
119
+ # HIGH 1: ComponentError public_message와 redact_details로 외부 노출 보안 격리
120
+ import logging
121
+ logging.getLogger(__name__).exception(
122
+ "Vision component operation failed",
123
+ extra={
124
+ "component_id": self.COMPONENT_ID,
125
+ "operation": "infer",
126
+ "code": getattr(component_err, "code", "COMPONENT_ERROR"),
127
+ },
128
+ )
129
+ if hasattr(component_err, "to_public_dict"):
130
+ err_dict = component_err.to_public_dict()
131
+ else:
132
+ err_dict = {
133
+ "code": getattr(component_err, "code", "COMPONENT_ERROR"),
134
+ "message": redact_text(getattr(component_err, "public_message", "Component operation failed")),
135
+ "retryable": getattr(component_err, "retryable", False),
136
+ "details": redact_details(getattr(component_err, "details", {})),
137
+ }
138
+ yield {
139
+ "type": "error",
140
+ "ok": False,
141
+ "error": {
142
+ **err_dict,
143
+ "operation": "infer",
144
+ "component_id": self.COMPONENT_ID,
145
+ "retryable": self._classify_retryable(
146
+ err_dict.get("code", ""), default=err_dict.get("retryable", False)
147
+ ),
148
+ },
149
+ }
150
+
151
+ except (ValueError, TypeError) as contract_err:
152
+ # P0-3/4 & HIGH 1: 계약 위반 (str(contract_err) 직접 노출 금지, 내부 로그 격리)
153
+ import logging
154
+ logging.getLogger(__name__).exception("Vision contract validation error during infer: %s", contract_err)
155
+ yield {
156
+ "type": "error",
157
+ "ok": False,
158
+ "error": {
159
+ "code": "ADAPTER_CONTRACT_ERROR",
160
+ "message": "Adapter contract validation failed",
161
+ "operation": "infer",
162
+ "component_id": self.COMPONENT_ID,
163
+ "retryable": False,
164
+ "details": {
165
+ "cause_type": type(contract_err).__name__,
166
+ "operation": "infer",
167
+ },
168
+ },
169
+ }
170
+
171
+ except Exception as unexpected_err:
172
+ import logging
173
+ logging.getLogger(__name__).exception("Vision adapter unexpected error during infer: %s", unexpected_err)
174
+ code = getattr(unexpected_err, "code", "ADAPTER_INTERNAL_ERROR")
175
+ yield {
176
+ "type": "error",
177
+ "ok": False,
178
+ "error": {
179
+ "code": code if isinstance(code, str) else "ADAPTER_INTERNAL_ERROR",
180
+ "message": "Unexpected adapter failure",
181
+ "operation": "infer",
182
+ "component_id": self.COMPONENT_ID,
183
+ "retryable": False,
184
+ "details": {
185
+ "cause_type": type(unexpected_err).__name__,
186
+ "operation": "infer",
187
+ },
188
+ },
189
+ }
190
+
191
+ def _classify_retryable(self, code: str, *, default: bool = False) -> bool:
192
+ if code in self._RETRYABLE_CODES:
193
+ return True
194
+ if code in self._NON_RETRYABLE_CODES:
195
+ return False
196
+ return default
197
+
198
+
199
+ def create_adapter() -> VisionOrchestratorAdapter:
200
+ """Entry Point Factory."""
201
+ return VisionOrchestratorAdapter()
@@ -0,0 +1,13 @@
1
+ # Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0 OR Commercial-License
3
+ from .amv_train_bridge import (
4
+ to_termux_tensor,
5
+ from_termux_tensor,
6
+ VisionPatchDataset
7
+ )
8
+
9
+ __all__ = [
10
+ "to_termux_tensor",
11
+ "from_termux_tensor",
12
+ "VisionPatchDataset"
13
+ ]
@@ -0,0 +1,87 @@
1
+ # Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0 OR Commercial-License
3
+ import numpy as np
4
+
5
+ def to_termux_tensor(data, requires_grad: bool = False):
6
+ """
7
+ Convert a NumPy image array or patch matrix to amv_train.Tensor.
8
+ Gracefully imports amv_train or creates equivalent autograd structure.
9
+ """
10
+ try:
11
+ import amv_train as tt
12
+ if isinstance(data, np.ndarray):
13
+ # Direct flat C-buffer pass without .tolist() to eliminate 8x memory expansion and LMK kills
14
+ return tt.Tensor(data, dtype="float32", requires_grad=requires_grad)
15
+ elif isinstance(data, (list, tuple)):
16
+ return tt.Tensor(data, dtype="float32", requires_grad=requires_grad)
17
+ elif hasattr(data, "to_numpy"):
18
+ return tt.Tensor(data.to_numpy(), dtype="float32", requires_grad=requires_grad)
19
+ else:
20
+ return tt.Tensor(data, dtype="float32", requires_grad=requires_grad)
21
+ except ImportError:
22
+ # Fallback if amv-train is not installed in current environment
23
+ class StandaloneVisionTensor:
24
+ def __init__(self, arr, requires_grad=False):
25
+ self.data = np.array(arr, dtype=np.float32)
26
+ self.shape = self.data.shape
27
+ self.requires_grad = requires_grad
28
+ self.grad = None
29
+ def to_numpy(self):
30
+ return self.data
31
+ def __repr__(self):
32
+ return f"StandaloneVisionTensor(shape={self.shape}, dtype={self.data.dtype})"
33
+ return StandaloneVisionTensor(data, requires_grad=requires_grad)
34
+
35
+ def from_termux_tensor(tensor) -> np.ndarray:
36
+ """
37
+ Convert a amv_train.Tensor or StandaloneVisionTensor back to NumPy array.
38
+ """
39
+ if hasattr(tensor, "to_numpy"):
40
+ return np.asarray(tensor.to_numpy(), dtype=np.float32)
41
+ elif hasattr(tensor, "numpy"):
42
+ return np.asarray(tensor.numpy(), dtype=np.float32)
43
+ elif hasattr(tensor, "tolist"):
44
+ return np.asarray(tensor.tolist(), dtype=np.float32)
45
+ elif hasattr(tensor, "_data"):
46
+ if hasattr(tensor._data, "to_list"):
47
+ return np.asarray(tensor._data.to_list(), dtype=np.float32)
48
+ return np.asarray(tensor._data, dtype=np.float32)
49
+ elif hasattr(tensor, "data"):
50
+ if isinstance(tensor.data, np.ndarray):
51
+ return np.asarray(tensor.data, dtype=np.float32)
52
+ return np.asarray(tensor.data, dtype=np.float32)
53
+ elif isinstance(tensor, np.ndarray):
54
+ return np.asarray(tensor, dtype=np.float32)
55
+ else:
56
+ return np.asarray(tensor, dtype=np.float32)
57
+
58
+ class VisionPatchDataset:
59
+ """
60
+ On-Device Dataset for batching image patches for ViT or classifier training.
61
+ """
62
+ def __init__(self, patches: np.ndarray, labels: np.ndarray = None, batch_size: int = 32, shuffle: bool = True):
63
+ self.patches = np.asarray(patches, dtype=np.float32)
64
+ self.labels = np.asarray(labels, dtype=np.float32) if labels is not None else None
65
+ self.batch_size = batch_size
66
+ self.shuffle = shuffle
67
+ self.num_samples = len(self.patches)
68
+
69
+ def __len__(self):
70
+ return (self.num_samples + self.batch_size - 1) // self.batch_size
71
+
72
+ def __iter__(self):
73
+ indices = np.arange(self.num_samples)
74
+ if self.shuffle:
75
+ np.random.shuffle(indices)
76
+
77
+ for i in range(0, self.num_samples, self.batch_size):
78
+ batch_idx = indices[i:i + self.batch_size]
79
+ batch_x = self.patches[batch_idx]
80
+ batch_tensor_x = to_termux_tensor(batch_x)
81
+
82
+ if self.labels is not None:
83
+ batch_y = self.labels[batch_idx]
84
+ batch_tensor_y = to_termux_tensor(batch_y)
85
+ yield batch_tensor_x, batch_tensor_y
86
+ else:
87
+ yield batch_tensor_x
@@ -0,0 +1,5 @@
1
+ # Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0 OR Commercial-License
3
+ from .main import main
4
+
5
+ __all__ = ["main"]
@@ -0,0 +1,122 @@
1
+ # Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0 OR Commercial-License
3
+ import os
4
+ import sys
5
+ import shutil
6
+ import platform
7
+ import subprocess
8
+ import json
9
+ import time
10
+ from typing import Dict, Any, Optional
11
+ from .. import __version__
12
+ from .. import csrc
13
+ from ..vlm.cache import ModelCacheManager
14
+ from amv_vision.exceptions import RuntimeRequiredError
15
+
16
+ def run_doctor(probe_vulkan: bool = False, full_check: bool = False) -> Dict[str, Any]:
17
+ """
18
+ Runs truthful diagnostic inspection of the Android Termux runtime environment.
19
+ - Default: Read-only environment inspection.
20
+ - probe_vulkan: Checks driver presence and reports execution capability (Zero-Hype / Ground Truth).
21
+ - full_check: Runs actual SHA-256 and byte-level integrity verification on installed models.
22
+ """
23
+ cache_mgr = ModelCacheManager()
24
+
25
+ report = {
26
+ "schema_version": 1,
27
+ "client_version": __version__,
28
+ "runtime_version": __version__,
29
+ "platform": {
30
+ "system": platform.system(),
31
+ "machine": platform.machine(),
32
+ "python_version": platform.python_version(),
33
+ "is_android": os.path.exists("/system/build.prop") or "ANDROID_ROOT" in os.environ
34
+ },
35
+ "hardware": {
36
+ "cpu_cores": os.cpu_count() or 1,
37
+ "total_ram_mb": None,
38
+ "available_ram_mb": None
39
+ },
40
+ "vulkan": {
41
+ "driver_dir": None,
42
+ "vulkan_loader_installed": shutil.which("vulkaninfo") is not None,
43
+ "loader_detected": shutil.which("vulkaninfo") is not None,
44
+ "driver_file_detected": False,
45
+ "compute_probe_executed": probe_vulkan,
46
+ "safe_for_vlm": None,
47
+ "status": "unverified"
48
+ },
49
+ "vlm_runtime": {
50
+ "llama_cli_available": shutil.which("llama-cli") is not None,
51
+ "multimodal_capable": False,
52
+ "multimodal_status": "unverified",
53
+ "installed_models_count": 0,
54
+ "cache_dir": cache_mgr.cache_root
55
+ },
56
+ "native_backends": {
57
+ "has_c_backend": csrc.has_c_backend(),
58
+ "has_vulkan_backend": csrc.has_vulkan_backend(),
59
+ "vulkan_device_name": csrc.get_vulkan_device_name(),
60
+ "c_backend_errors": csrc.get_c_backend_load_errors(),
61
+ "cpp_backend_errors": csrc.get_cpp_backend_load_errors()
62
+ },
63
+ "recommended_preset": "Tier M (smolvlm-500m / 4-Threads CPU Reference)",
64
+ "warnings": []
65
+ }
66
+
67
+ # Verify multimodal capability via installer gatekeeper
68
+ try:
69
+ from ..installer import verify_multimodal_support
70
+ mm_ok, mm_msg = verify_multimodal_support()
71
+ report["vlm_runtime"]["multimodal_capable"] = mm_ok
72
+ report["vlm_runtime"]["multimodal_status"] = mm_msg
73
+ if not mm_ok and report["vlm_runtime"]["llama_cli_available"]:
74
+ report["warnings"].append(f"llama-cli detected but lacks multimodal support: {mm_msg}")
75
+ except RuntimeRequiredError:
76
+ raise
77
+ except Exception as _mm_err:
78
+ report["vlm_runtime"]["multimodal_status"] = f"Check failed: {_mm_err}"
79
+
80
+ # RAM Inspection
81
+ try:
82
+ with open("/proc/meminfo", "r") as f:
83
+ for line in f:
84
+ if line.startswith("MemTotal:"):
85
+ report["hardware"]["total_ram_mb"] = int(line.split()[1]) // 1024
86
+ elif line.startswith("MemAvailable:"):
87
+ report["hardware"]["available_ram_mb"] = int(line.split()[1]) // 1024
88
+ except (OSError, ValueError, IndexError) as _mem_err:
89
+ report["warnings"].append(f"RAM inspection failed: {_mem_err}")
90
+
91
+ # Truthful Vulkan status via ameva-runtime integration
92
+ if probe_vulkan:
93
+ try:
94
+ from ameva_runtime.vulkan.adapters import find_system_vulkan_driver_dir
95
+ from ameva_runtime import vulkan as avr
96
+ driver_dir = find_system_vulkan_driver_dir()
97
+ is_avail = bool(driver_dir is not None)
98
+ report["vulkan"]["driver_dir"] = driver_dir
99
+ report["vulkan"]["driver_file_detected"] = is_avail
100
+ report["vulkan"]["loader_detected"] = bool(is_avail or report["vulkan"]["vulkan_loader_installed"])
101
+ report["vulkan"]["ameva_runtime_detected"] = True
102
+ report["vulkan"]["status"] = "driver_detected_experimental" if is_avail else "disabled"
103
+ dev_name = avr.get_device_name() if hasattr(avr, "get_device_name") and callable(avr.get_device_name) else "Vulkan GPU"
104
+ report["vulkan"]["device_name"] = dev_name
105
+ report["vulkan"]["note"] = "Hardware driver dynamically discovered via ameva-runtime."
106
+ except ImportError:
107
+ report["vulkan"]["ameva_runtime_detected"] = False
108
+ report["vulkan"]["status"] = "disabled"
109
+ report["vulkan"]["safe_for_vlm"] = False
110
+ report["vulkan"]["note"] = "Install ameva-runtime for optimized GPU compute shaders."
111
+
112
+ # Model count and Actual Full SHA-256 check
113
+ installed = cache_mgr.list_installed()
114
+ report["vlm_runtime"]["installed_models_count"] = len(installed)
115
+
116
+ if full_check:
117
+ report["vlm_runtime"]["models_integrity"] = {}
118
+ for m in installed:
119
+ mid = m["model_id"]
120
+ report["vlm_runtime"]["models_integrity"][mid] = cache_mgr.verify_integrity(mid)
121
+
122
+ return report