amv-diffusion 2.1.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 (39) hide show
  1. amv_diffusion-2.1.0.dist-info/METADATA +180 -0
  2. amv_diffusion-2.1.0.dist-info/RECORD +39 -0
  3. amv_diffusion-2.1.0.dist-info/WHEEL +5 -0
  4. amv_diffusion-2.1.0.dist-info/entry_points.txt +7 -0
  5. amv_diffusion-2.1.0.dist-info/licenses/LICENSE +21 -0
  6. amv_diffusion-2.1.0.dist-info/top_level.txt +1 -0
  7. termux_diffusion/__init__.py +185 -0
  8. termux_diffusion/__main__.py +7 -0
  9. termux_diffusion/_version.py +3 -0
  10. termux_diffusion/adapter.py +41 -0
  11. termux_diffusion/canvas.py +364 -0
  12. termux_diffusion/cli.py +492 -0
  13. termux_diffusion/cluster.py +340 -0
  14. termux_diffusion/control/__init__.py +5 -0
  15. termux_diffusion/control/component.py +309 -0
  16. termux_diffusion/control/status.py +15 -0
  17. termux_diffusion/core.py +2936 -0
  18. termux_diffusion/data/bootstrap-manifest.json +15 -0
  19. termux_diffusion/data/prompt_cache/108a7149c4e70550.json +10 -0
  20. termux_diffusion/data/release-public-keys.json +10 -0
  21. termux_diffusion/data/validated-vulkan-profiles.json +298 -0
  22. termux_diffusion/detailer.py +283 -0
  23. termux_diffusion/doctor.py +128 -0
  24. termux_diffusion/downloader.py +111 -0
  25. termux_diffusion/engine.py +226 -0
  26. termux_diffusion/exceptions.py +158 -0
  27. termux_diffusion/hardware.py +985 -0
  28. termux_diffusion/hub.py +686 -0
  29. termux_diffusion/installer.py +499 -0
  30. termux_diffusion/locking.py +134 -0
  31. termux_diffusion/manifest.py +128 -0
  32. termux_diffusion/npu.py +267 -0
  33. termux_diffusion/platform.py +533 -0
  34. termux_diffusion/purger.py +145 -0
  35. termux_diffusion/py.typed +1 -0
  36. termux_diffusion/safety.py +150 -0
  37. termux_diffusion/selftest.py +178 -0
  38. termux_diffusion/web.py +3370 -0
  39. termux_diffusion/workflow.py +1345 -0
@@ -0,0 +1,128 @@
1
+ # Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0 OR Commercial-License
3
+ """
4
+ termux_diffusion.doctor
5
+ =======================
6
+ Pre-flight diagnostics, hardware topology inspection, and system verification for termux-diffusion.
7
+ """
8
+
9
+ import os
10
+ import sys
11
+ import shutil
12
+ import logging
13
+ from pathlib import Path
14
+ from typing import Optional
15
+
16
+ from .platform import (
17
+ is_android_termux,
18
+ is_arm64,
19
+ get_memory_info,
20
+ check_memory_safety,
21
+ )
22
+
23
+ logger = logging.getLogger("termux_diffusion.doctor")
24
+
25
+
26
+ def run_doctor() -> bool:
27
+ """Run comprehensive 8-tier pre-flight diagnostic checks for Samsung Galaxy Termux setup."""
28
+ from .engine import get_binary_path
29
+
30
+ print("=" * 65)
31
+ print("[Doctor] [termux-diffusion] Pre-flight Diagnostic Doctor")
32
+ print("=" * 65)
33
+
34
+ all_passed = True
35
+
36
+ # 1. Platform Check
37
+ is_termux = is_android_termux()
38
+ arm_arch = is_arm64()
39
+ print(f"1. Platform: {'Android Termux [OK]' if is_termux else 'Non-Termux Host (Emulation Mode) [INFO]'}")
40
+ print(f"2. Architecture: {'ARM64 / aarch64 [OK]' if arm_arch else f'Host {sys.platform} ({os.name}) [INFO]'}")
41
+
42
+ # 3. Memory & RAM Plus
43
+ mem = get_memory_info()
44
+ safe, msg = check_memory_safety(required_mb=1200)
45
+ print(f"3. System Memory: {mem['mem_total_mb']}MB RAM + {mem['swap_total_mb']}MB Swap ({'Safe [OK]' if safe else 'Warning [WARN]'})")
46
+ if not safe:
47
+ print(f" -> {msg}")
48
+
49
+ # 4. Storage & Samsung Gallery
50
+ storage_ok = os.path.exists(os.path.expanduser("~/storage"))
51
+ print(f"4. Android Storage Permission: {'Configured [OK]' if storage_ok else 'Missing [WARN] (Run termux-setup-storage)'}")
52
+
53
+ # 5. Compiler Toolchain
54
+ clang_ok = bool(shutil.which("clang") or shutil.which("gcc") or shutil.which("clang++"))
55
+ cmake_ok = bool(shutil.which("cmake"))
56
+ git_ok = bool(shutil.which("git"))
57
+ print(f"5. Build Tools: clang ({'[OK]' if clang_ok else '[FAIL]'}), cmake ({'[OK]' if cmake_ok else '[FAIL]'}), git ({'[OK]' if git_ok else '[FAIL]'})")
58
+ if not (clang_ok and cmake_ok and git_ok) and is_termux:
59
+ all_passed = False
60
+ print(" -> Run: pkg install clang cmake git termux-api -y")
61
+
62
+ # 6. Engine Binary
63
+ engine = get_binary_path()
64
+ print(f"6. Native C++ Engine (sd-cli): {str(engine) + ' [OK]' if engine else 'Not Provisioned [FAIL] (Run termux-diffusion install)'}")
65
+ if not engine:
66
+ all_passed = False
67
+
68
+ # 7. Model Cache Status
69
+ from .hub import list_cached_models
70
+ cached = list_cached_models()
71
+ print(f"7. Cached GGUF Models: {len(cached)} model(s) available locally.")
72
+ for m in cached:
73
+ valid_tag = " [GGUF Valid [OK]]" if m.get("is_valid_gguf") else " [Header [WARN]]"
74
+ print(f" -> {m['name']} ({m['size_mb']} MB){valid_tag}")
75
+
76
+ # 8. Hardware Acceleration (GPU / NPU / TPU / Vulkan / OpenCL)
77
+ try:
78
+ from ameva_runtime.vulkan.adapters import find_system_vulkan_driver_dir
79
+ discovered_vulkan = find_system_vulkan_driver_dir()
80
+ except ImportError:
81
+ discovered_vulkan = None
82
+
83
+ from .hardware import detect_hardware_profile
84
+ hw = detect_hardware_profile()
85
+ print("8. Hardware Acceleration Profile:")
86
+ print(f" SoC: {hw.soc_name}, GPU Architecture: {hw.gpu_name}")
87
+ print(f" GPU Vulkan: {'Available [OK]' if (hw.vulkan_available or discovered_vulkan) else 'Not Found [WARN]'}")
88
+ if discovered_vulkan:
89
+ print(f" -> Vulkan Driver (ameva-runtime): {discovered_vulkan}")
90
+ elif hw.vulkan_driver:
91
+ print(f" -> Vulkan Driver: {hw.vulkan_driver.library_path}")
92
+ print(f" GPU OpenCL: {'Available [OK]' if hw.opencl_available else 'Not Found [WARN]'}")
93
+ if hw.opencl_driver:
94
+ print(f" -> OpenCL Driver: {hw.opencl_driver.library_path}")
95
+ if hw.npu_profile and hw.npu_profile.available:
96
+ print(" NPU / TPU Hardware: Detected [INFO]")
97
+ print(f" -> Architecture: {hw.npu_profile.dsp_architecture} ({hw.npu_profile.tops_rating} TOPS)")
98
+ print(f" -> Driver: {hw.npu_profile.driver_library}")
99
+ print(" -> Runtime: Native QNN C++ execution scheduled for v2.0")
100
+ else:
101
+ print(" NPU / TPU Hardware: Not Detected [INFO] (GPU Vulkan & CPU NEON Active)")
102
+ print(f" CPU ISA SIMD: DotProd={'[OK]' if hw.has_dotprod else '[FAIL]'} "
103
+ f"FP16={'[OK]' if hw.has_fp16 else '[FAIL]'} "
104
+ f"I8MM={'[OK]' if hw.has_i8mm else '[FAIL]'} "
105
+ f"SVE={'[OK]' if hw.has_sve else '[FAIL]'}")
106
+ print(f" Active Compute Pipeline: {hw.recommended_backend.value.upper()} "
107
+ f"(Offload Layers: {hw.recommended_ngl})")
108
+
109
+ # 9. Prompt Cache & Auxiliary Assets Status
110
+ from .platform import get_default_cache_dir
111
+ cache_dir = get_default_cache_dir() / "prompt_cache"
112
+ cache_count = len(list(cache_dir.glob("*"))) if cache_dir.is_dir() else 0
113
+ print(f"9. Auxiliary Assets & Prompt Cache: {cache_count} cached entries at {cache_dir} [OK]")
114
+
115
+ # 10. Android 12+ Background Stability Guard (Phantom Process Killer)
116
+ if is_termux:
117
+ print("10. Android 12+ Background Guard:")
118
+ print(" -> Tip: If generation crashes when Termux is in background, enable")
119
+ print(" 'Developer Options > Disable child process restrictions'")
120
+ print(" or run: adb shell \"/system/bin/device_config put activity_manager max_phantom_processes 2147483647\"")
121
+
122
+ print("=" * 65)
123
+ if all_passed:
124
+ print("All core diagnostics passed. System is ready for AI image generation.")
125
+ else:
126
+ print("Some diagnostics need attention. Run 'termux-diffusion install' to resolve.")
127
+ print("=" * 65)
128
+ return all_passed
@@ -0,0 +1,111 @@
1
+ # Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0 OR Commercial-License
3
+ """Atomic download manager with SHA-256 integrity verification, concurrency lock, and rollback safety."""
4
+
5
+ import os
6
+ import shutil
7
+ import time
8
+ import urllib.request
9
+ import logging
10
+ from pathlib import Path
11
+ from typing import Optional
12
+
13
+ from .exceptions import DownloadError, InstallLockError, TermuxDiffusionError
14
+ from .locking import InstallLock
15
+ from .manifest import verify_file_sha256
16
+
17
+ logger = logging.getLogger("termux_diffusion.downloader")
18
+
19
+
20
+ def atomic_download_file(
21
+ url: str,
22
+ dest_path: Path,
23
+ expected_sha256: Optional[str] = None,
24
+ expected_size: Optional[int] = None,
25
+ timeout_sec: float = 120.0,
26
+ user_agent: str = "termux-diffusion-downloader/1.2.0"
27
+ ) -> Path:
28
+ """Download url to dest_path atomically using .part -> SHA256 check -> atomic rename -> rollback."""
29
+ dest_path.parent.mkdir(parents=True, exist_ok=True)
30
+ part_path = dest_path.with_suffix(dest_path.suffix + ".part")
31
+ bak_path = dest_path.with_suffix(dest_path.suffix + ".bak")
32
+
33
+ part_path.unlink(missing_ok=True)
34
+ bak_path.unlink(missing_ok=True)
35
+
36
+ print(f"[termux-diffusion] Downloading artifact from {url}...")
37
+ try:
38
+ req = urllib.request.Request(url, headers={"User-Agent": user_agent})
39
+ with urllib.request.urlopen(req, timeout=timeout_sec) as response:
40
+ if response.status != 200:
41
+ raise DownloadError(f"E_ARTIFACT_DOWNLOAD: HTTP server returned status code {response.status}")
42
+
43
+ with open(part_path, "wb") as out_f:
44
+ shutil.copyfileobj(response, out_f)
45
+ except Exception as exc:
46
+ part_path.unlink(missing_ok=True)
47
+ raise DownloadError(f"E_ARTIFACT_DOWNLOAD: Network error downloading {url}: {exc}") from exc
48
+
49
+ # Size Verification
50
+ actual_size = part_path.stat().st_size
51
+ if expected_size is not None and expected_size > 0 and actual_size != expected_size:
52
+ part_path.unlink(missing_ok=True)
53
+ raise DownloadError(
54
+ f"E_ARTIFACT_SIZE: Downloaded artifact size mismatch. "
55
+ f"Expected {expected_size} bytes, got {actual_size} bytes."
56
+ )
57
+
58
+ # SHA-256 Verification
59
+ if expected_sha256 and not verify_file_sha256(part_path, expected_sha256):
60
+ part_path.unlink(missing_ok=True)
61
+ raise DownloadError(
62
+ f"E_ARTIFACT_SHA256: SHA-256 checksum verification failed for {dest_path.name}. "
63
+ f"Artifact hash mismatch!"
64
+ )
65
+
66
+ # Atomic Rename with Backup Rollback Safety
67
+ try:
68
+ if dest_path.exists():
69
+ dest_path.rename(bak_path)
70
+ part_path.rename(dest_path)
71
+ os.chmod(dest_path, 0o755)
72
+ bak_path.unlink(missing_ok=True)
73
+ print(f"[termux-diffusion] Artifact {dest_path.name} downloaded and verified (SHA-256 match).")
74
+ return dest_path
75
+ except Exception as e:
76
+ # Rollback
77
+ part_path.unlink(missing_ok=True)
78
+ if bak_path.exists() and not dest_path.exists():
79
+ bak_path.rename(dest_path)
80
+ raise DownloadError(f"E_ATOMIC_RENAME_FAIL: Failed installing verified artifact {dest_path.name}: {e}") from e
81
+
82
+
83
+ from typing import List, Dict, Any, Union
84
+
85
+
86
+ def list_models() -> List[Dict[str, Any]]:
87
+ """List standard verified diffusion models from hub presets."""
88
+ from .hub import list_presets
89
+ presets = list_presets()
90
+ return [{"id": k, **v} for k, v in presets.items()]
91
+
92
+
93
+ def download_model(
94
+ model_name_or_url: str = "sdxs",
95
+ cache_dir: Optional[Union[str, Path]] = None,
96
+ force: bool = False,
97
+ **kwargs: Any,
98
+ ) -> Path:
99
+ """Download diffusion model weights using termux_diffusion.hub."""
100
+ from .hub import download_model as _hub_download
101
+ return _hub_download(model_name_or_url=model_name_or_url, cache_dir=cache_dir, force=force, **kwargs)
102
+
103
+
104
+ def resolve_model_path(
105
+ model_name_or_path: str = "sdxs",
106
+ cache_dir: Optional[Union[str, Path]] = None,
107
+ ) -> Path:
108
+ """Resolve diffusion model path to local Path."""
109
+ from .hub import resolve_model_path as _hub_resolve
110
+ return _hub_resolve(model_name_or_path=model_name_or_path, cache_dir=cache_dir)
111
+
@@ -0,0 +1,226 @@
1
+ # Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0 OR Commercial-License
3
+ """
4
+ termux_diffusion.engine
5
+ =======================
6
+ High-performance C++ core engine locator, backend dispatcher, and high-level DiffusionEngine wrapper.
7
+ Strict Zero-Silent-Fallback and Anti-Deception compliant architecture matching AMEVA standards.
8
+ """
9
+
10
+ import os
11
+ import sys
12
+ import shutil
13
+ import logging
14
+ from pathlib import Path
15
+ from typing import Optional, List, Dict, Any, Union
16
+
17
+ from .platform import (
18
+ TERMUX_PREFIX,
19
+ is_android_termux,
20
+ get_default_cache_dir,
21
+ )
22
+
23
+ logger = logging.getLogger("termux_diffusion.engine")
24
+
25
+
26
+ def get_engine_bin_dir() -> Path:
27
+ """Return canonical directory where termux-diffusion binaries reside ($PREFIX/bin SSOT)."""
28
+ prefix = os.environ.get("PREFIX") or TERMUX_PREFIX
29
+ if is_android_termux() or os.path.exists(prefix):
30
+ bin_dir = Path(prefix) / "bin"
31
+ else:
32
+ bin_dir = get_default_cache_dir() / "bin"
33
+ bin_dir.mkdir(parents=True, exist_ok=True)
34
+ return bin_dir
35
+
36
+
37
+ def get_engine_lib_dir() -> Path:
38
+ """Return canonical directory where native shared libraries reside ($PREFIX/lib SSOT)."""
39
+ prefix = os.environ.get("PREFIX") or TERMUX_PREFIX
40
+ if is_android_termux() or os.path.exists(prefix):
41
+ lib_dir = Path(prefix) / "lib"
42
+ else:
43
+ lib_dir = get_default_cache_dir() / "lib"
44
+ lib_dir.mkdir(parents=True, exist_ok=True)
45
+ return lib_dir
46
+
47
+
48
+ def get_binary_path(backend: str = "auto") -> Optional[Path]:
49
+ """Find authoritative absolute path of sd-cli executable strictly isolated by compute backend.
50
+
51
+ Zero-Silent-Fallback Rules:
52
+ - 'gpu' / 'vulkan': Exclusively search for native Vulkan binary (sd-cli-vulkan).
53
+ Never fallback to CPU binary. Return None if unprovisioned.
54
+ - 'cpu' / default: Exclusively search for pure CPU NEON binary (sd-cli-cpu, sd-cli).
55
+ """
56
+ home = Path(os.environ.get("HOME", os.path.expanduser("~")))
57
+ prefix_bin = Path(TERMUX_PREFIX) / "bin"
58
+ bin_dir = get_engine_bin_dir()
59
+ ameva_diff_dir = home / ".local" / "share" / "ameva" / "current" / "diffusion"
60
+
61
+ req_backend = str(backend or "auto").strip().lower()
62
+
63
+ # --------------------------------------------------------------------------
64
+ # 0. RPC / Distributed Multi-Node Route (sd-cli-rpc, sd-cli)
65
+ # --------------------------------------------------------------------------
66
+ if req_backend in ("rpc", "remote"):
67
+ rpc_candidates = [
68
+ ameva_diff_dir / "sd-cli-rpc",
69
+ home / ".local" / "share" / "ameva" / "rpc_llama" / "bin" / "sd-cli",
70
+ home / ".local" / "share" / "ameva" / "rpc_llama" / "bin" / "sd-cli-rpc",
71
+ bin_dir / "sd-cli-rpc",
72
+ prefix_bin / "sd-cli-rpc",
73
+ home / ".local" / "bin" / "sd-cli-rpc",
74
+ bin_dir / "sd-cli",
75
+ prefix_bin / "sd-cli",
76
+ ]
77
+ for p in rpc_candidates:
78
+ if p.is_file() and (os.access(p, os.X_OK) or os.name == "nt"):
79
+ return p.resolve()
80
+
81
+ which_rpc = shutil.which("sd-cli-rpc")
82
+ if which_rpc and (os.access(which_rpc, os.X_OK) or os.name == "nt"):
83
+ return Path(which_rpc).resolve()
84
+
85
+ # --------------------------------------------------------------------------
86
+ # 1. GPU / Vulkan Route: Pure Vulkan GPU Binary (sd-cli-vulkan)
87
+ # --------------------------------------------------------------------------
88
+ if req_backend in ("vulkan", "gpu"):
89
+ vulkan_candidates = [
90
+ # AMEVA 2-Tier isolated current deployment
91
+ ameva_diff_dir / "sd-cli-vulkan",
92
+ # Termux prefix binary
93
+ prefix_bin / "sd-cli-vulkan",
94
+ bin_dir / "sd-cli-vulkan",
95
+ # User local binary
96
+ home / ".local" / "bin" / "sd-cli-vulkan",
97
+ ]
98
+ for p in vulkan_candidates:
99
+ if p.is_file() and (os.access(p, os.X_OK) or os.name == "nt"):
100
+ return p.resolve()
101
+
102
+ # System PATH search strictly for 'sd-cli-vulkan'
103
+ which_vk = shutil.which("sd-cli-vulkan")
104
+ if which_vk and (os.access(which_vk, os.X_OK) or os.name == "nt"):
105
+ return Path(which_vk).resolve()
106
+
107
+ # Zero-Silent-Fallback: If Vulkan binary is absent, NEVER return CPU binary!
108
+ return None
109
+
110
+ # --------------------------------------------------------------------------
111
+ # 1.1 OpenCL Route: OpenCL GPU Binary (sd-cli with OpenCL)
112
+ # --------------------------------------------------------------------------
113
+ if req_backend in ("opencl", "cl"):
114
+ opencl_candidates = [
115
+ home / "sd-opencl-build" / "build" / "bin" / "sd-cli",
116
+ bin_dir / "sd-cli-opencl",
117
+ prefix_bin / "sd-cli-opencl",
118
+ prefix_bin / "sd-cli",
119
+ ]
120
+ for p in opencl_candidates:
121
+ if p.is_file() and (os.access(p, os.X_OK) or os.name == "nt"):
122
+ return p.resolve()
123
+ return None
124
+
125
+ # --------------------------------------------------------------------------
126
+ # 2. CPU Baseline Route: Pure CPU NEON Binary (sd-cli-cpu, sd-cli)
127
+ # --------------------------------------------------------------------------
128
+ cpu_candidates = [
129
+ home / ".local" / "share" / "ameva" / "rpc_llama" / "bin" / "sd-cli",
130
+ bin_dir / "sd-cli-cpu",
131
+ prefix_bin / "sd-cli-cpu",
132
+ bin_dir / "sd-cli-source-cpu",
133
+ bin_dir / "sd-cli",
134
+ prefix_bin / "sd-cli",
135
+ home / ".local" / "bin" / "sd-cli-cpu",
136
+ home / ".local" / "bin" / "sd-cli",
137
+ ]
138
+ for p in cpu_candidates:
139
+ if p.is_file() and (os.access(p, os.X_OK) or os.name == "nt"):
140
+ return p.resolve()
141
+
142
+ # System PATH search for CPU binary
143
+ for name in ("sd-cli", "sd-cli.exe", "sd", "sd.exe"):
144
+ which_path = shutil.which(name)
145
+ if which_path and (os.access(which_path, os.X_OK) or os.name == "nt"):
146
+ return Path(which_path).resolve()
147
+
148
+ return None
149
+
150
+
151
+ def locate_sd_cli(backend: Optional[str] = None) -> Optional[Path]:
152
+ """Backward-compatible alias for get_binary_path."""
153
+ return get_binary_path(backend=backend or "auto")
154
+
155
+
156
+ class DiffusionEngine:
157
+ """High-level Python SDK Engine for On-Device Stable Diffusion inference.
158
+ Follows canonical AMEVA component architecture matching LlamaRuntime and BitNetEngine.
159
+ """
160
+
161
+ def __init__(
162
+ self,
163
+ model: str = "realistic",
164
+ backend: str = "auto",
165
+ device: Optional[str] = None,
166
+ rpc: Optional[str] = None,
167
+ tensor_split: Optional[str] = None,
168
+ cluster_rpc_servers: Optional[Union[str, List[str]]] = None,
169
+ cluster_split_mode: Optional[str] = None,
170
+ cluster_tensor_split: Optional[str] = None,
171
+ cluster_vram_budget: Optional[str] = None,
172
+ **kwargs: Any,
173
+ ):
174
+ self.model = model
175
+ self.device = device or backend or "auto"
176
+ self.requested_backend = self.device.strip().lower()
177
+ self.cluster_rpc_servers = cluster_rpc_servers or rpc
178
+ self.rpc = self.cluster_rpc_servers if isinstance(self.cluster_rpc_servers, str) else (",".join(self.cluster_rpc_servers) if self.cluster_rpc_servers else None)
179
+ self.cluster_tensor_split = cluster_tensor_split or tensor_split
180
+ self.tensor_split = self.cluster_tensor_split
181
+ self.cluster_split_mode = cluster_split_mode
182
+ self.cluster_vram_budget = cluster_vram_budget
183
+ self.bin_dir = get_engine_bin_dir()
184
+ self.lib_dir = get_engine_lib_dir()
185
+ self.kwargs = kwargs
186
+
187
+ def get_binary_path(self, backend: Optional[str] = None) -> Optional[Path]:
188
+ """Resolve authoritative binary path for active or specified backend."""
189
+ target_backend = backend if backend is not None else self.requested_backend
190
+ return get_binary_path(backend=target_backend)
191
+
192
+ def generate(self, prompt: str, **kwargs: Any) -> Any:
193
+ """Execute text-to-image generation delegating to core pipeline."""
194
+ from .core import generate
195
+ merged = {**self.kwargs, **kwargs}
196
+ merged.setdefault("device", self.requested_backend)
197
+ merged.setdefault("model", self.model)
198
+ if self.rpc and "rpc" not in merged:
199
+ merged["rpc"] = self.rpc
200
+ if self.cluster_rpc_servers and "cluster_rpc_servers" not in merged:
201
+ merged["cluster_rpc_servers"] = self.cluster_rpc_servers
202
+ if self.tensor_split and "tensor_split" not in merged:
203
+ merged["tensor_split"] = self.tensor_split
204
+ if self.cluster_split_mode and "cluster_split_mode" not in merged:
205
+ merged["cluster_split_mode"] = self.cluster_split_mode
206
+ if self.cluster_tensor_split and "cluster_tensor_split" not in merged:
207
+ merged["cluster_tensor_split"] = self.cluster_tensor_split
208
+ if self.cluster_vram_budget and "cluster_vram_budget" not in merged and "max_vram" not in merged:
209
+ merged["cluster_vram_budget"] = self.cluster_vram_budget
210
+ return generate(prompt=prompt, **merged)
211
+
212
+ @staticmethod
213
+ def doctor() -> bool:
214
+ """Run comprehensive pre-flight diagnostic checks."""
215
+ from .doctor import run_doctor
216
+ return run_doctor()
217
+
218
+
219
+ def load(model: str = "realistic", device: str = "auto", **kwargs: Any) -> DiffusionEngine:
220
+ """Standard Unified Engine Factory for termux-diffusion matching AMEVA multi-modal standards."""
221
+ return DiffusionEngine(model=model, device=device, **kwargs)
222
+
223
+
224
+ # Backward-compatible alias matching LlamaRuntime
225
+ DiffusionRuntime = DiffusionEngine
226
+
@@ -0,0 +1,158 @@
1
+ # Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0 OR Commercial-License
3
+ """
4
+ AMEVA Unified Exception Hierarchy for Termux AI Engines.
5
+ Component: [DIFFUSION]
6
+ """
7
+ from typing import Optional, Any
8
+
9
+
10
+ class AmevaTermuxError(Exception):
11
+ """Root exception for all Termux On-Device AI Engines."""
12
+ COMPONENT_TAG = "[DIFFUSION]"
13
+ DEFAULT_CODE = "E_UNKNOWN"
14
+
15
+ def __init__(self, message: str, code: Optional[Any] = None, details: Optional[Any] = None):
16
+ self.code = code or self.DEFAULT_CODE
17
+ self.details = details
18
+ self.raw_message = message
19
+ super().__init__(message if message.startswith("[DIFFUSION]") else f"{self.COMPONENT_TAG} [{self.code}] {message}")
20
+
21
+
22
+ TermuxDiffusionError = AmevaTermuxError
23
+
24
+
25
+ class ErrorCode:
26
+ CLI_EXCLUSIVE = "E_CLI_EXCLUSIVE_MUTEX"
27
+ PLATFORM_UNSUPPORTED = "E_PLATFORM_UNSUPPORTED"
28
+ MANIFEST_DOWNLOAD = "E_MANIFEST_DOWNLOAD"
29
+ MANIFEST_SIGNATURE = "E_MANIFEST_SIGNATURE"
30
+ MANIFEST_SCHEMA = "E_MANIFEST_SCHEMA"
31
+ MANIFEST_EXPIRED = "E_MANIFEST_EXPIRED"
32
+ ARTIFACT_DOWNLOAD = "E_ARTIFACT_DOWNLOAD"
33
+ ARTIFACT_SIZE = "E_ARTIFACT_SIZE"
34
+ ARTIFACT_SHA256 = "E_ARTIFACT_SHA256"
35
+ ARTIFACT_FORMAT = "E_ARTIFACT_FORMAT"
36
+ DYNAMIC_LINKER = "E_DYNAMIC_LINKER"
37
+ ILLEGAL_INSTRUCTION = "E_ILLEGAL_INSTRUCTION"
38
+ PROCESS_TIMEOUT = "E_PROCESS_TIMEOUT"
39
+ VULKAN_LOADER = "E_VULKAN_LOADER"
40
+ VULKAN_DEVICE = "E_VULKAN_DEVICE"
41
+ VULKAN_PIPELINE = "E_VULKAN_PIPELINE"
42
+ CPU_SELFTEST = "E_CPU_SELFTEST"
43
+ SOURCE_CLONE = "E_SOURCE_CLONE"
44
+ SOURCE_PATCH = "E_SOURCE_PATCH"
45
+ SOURCE_CONFIGURE = "E_SOURCE_CONFIGURE"
46
+ SOURCE_BUILD = "E_SOURCE_BUILD"
47
+ SOURCE_ARTIFACT = "E_SOURCE_ARTIFACT"
48
+ INSTALL_LOCKED = "E_INSTALL_LOCKED"
49
+ ROLLBACK_FAILED = "E_ROLLBACK_FAILED"
50
+ CLUSTER_CONNECTION = "E_CLUSTER_CONNECTION"
51
+ CLUSTER_CONFIG = "E_CLUSTER_CONFIG"
52
+
53
+
54
+ class ExitCode:
55
+ SUCCESS = 0
56
+ CLI_ERROR = 2
57
+ PLATFORM_ERROR = 10
58
+ INTEGRITY_ERROR = 20
59
+ EXECUTION_ERROR = 30
60
+ SELFTEST_ERROR = 40
61
+ RUNTIME_REQUIRED = 69
62
+ BUILD_ERROR = 50
63
+ ROLLBACK_ERROR = 60
64
+
65
+
66
+ class PlatformNotSupportedError(TermuxDiffusionError):
67
+ def __init__(self, message: str):
68
+ super().__init__(message, code=ErrorCode.PLATFORM_UNSUPPORTED)
69
+
70
+
71
+ class RuntimeRequiredError(PlatformNotSupportedError):
72
+ """A GPU request needs ameva-runtime (AMUDA), which is missing or too old.
73
+
74
+ The message is the shared AMU_ERR_RUNTIME_REQUIRED explanation. The CLI exits with
75
+ ExitCode.RUNTIME_REQUIRED; nothing is run and there is no CPU fallback.
76
+ """
77
+
78
+ runtime_required = True
79
+
80
+
81
+ class ModelNotFoundError(TermuxDiffusionError):
82
+ pass
83
+
84
+
85
+ class ModelDownloadError(TermuxDiffusionError):
86
+ pass
87
+
88
+
89
+ class DownloadError(TermuxDiffusionError):
90
+ def __init__(self, message: str, code: str = ErrorCode.ARTIFACT_DOWNLOAD):
91
+ super().__init__(message, code=code)
92
+
93
+
94
+ class ProvisioningError(TermuxDiffusionError):
95
+ def __init__(self, message: str, code: str = ErrorCode.SOURCE_BUILD):
96
+ super().__init__(message, code=code)
97
+
98
+
99
+ class InstallLockError(TermuxDiffusionError):
100
+ def __init__(self, message: str):
101
+ super().__init__(message, code=ErrorCode.INSTALL_LOCKED)
102
+
103
+
104
+ class OOMRiskError(TermuxDiffusionError):
105
+ pass
106
+
107
+
108
+ class InferenceTimeoutError(TermuxDiffusionError):
109
+ pass
110
+
111
+
112
+ class HardwareCompatibilityError(TermuxDiffusionError):
113
+ pass
114
+
115
+
116
+ class RuntimeNotFoundError(TermuxDiffusionError):
117
+ pass
118
+
119
+
120
+ class InferenceExecutionError(TermuxDiffusionError):
121
+ pass
122
+
123
+
124
+ class ModelCorruptedError(TermuxDiffusionError):
125
+ pass
126
+
127
+
128
+ class ClusterConnectionError(TermuxDiffusionError):
129
+ """Raised when one or more distributed RPC worker nodes cannot be reached or fail handshake."""
130
+ def __init__(self, message: str):
131
+ super().__init__(message, code=ErrorCode.CLUSTER_CONNECTION)
132
+
133
+
134
+ class ClusterConfigurationError(TermuxDiffusionError):
135
+ """Raised when cluster parameters (tensor split, backend, vram budget) are invalid."""
136
+ def __init__(self, message: str):
137
+ super().__init__(message, code=ErrorCode.CLUSTER_CONFIG)
138
+
139
+
140
+ class ClusterLicenseRequiredError(TermuxDiffusionError):
141
+ """Raised when distributed clustering is invoked without required AMEVA Cluster license."""
142
+ def __init__(self, message: str = ""):
143
+ if not message:
144
+ message = (
145
+ "\n================================================================================\n"
146
+ "[AMEVA-CLUSTER] CLUSTER LICENSE REQUIRED (E403)\n"
147
+ "================================================================================\n"
148
+ "Multi-device distributed clustering is an exclusive capability of 'ameva-cluster'.\n"
149
+ "Standalone distributed execution without the official AMEVA-Cluster package is prohibited.\n\n"
150
+ "Resolution:\n"
151
+ " 1. Install official AMEVA-Cluster runtime:\n"
152
+ " pip install ameva-cluster (or npm install @ameva/cluster)\n"
153
+ " 2. Launch worker/master through 'ameva-cluster' control plane:\n"
154
+ " ameva-cluster worker\n"
155
+ " ameva-cluster master -m model.gguf\n"
156
+ "================================================================================"
157
+ )
158
+ super().__init__(message, code=ErrorCode.CLUSTER_CONFIG)