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,150 @@
1
+ # Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0 OR Commercial-License
3
+ """Deterministic memory budgeting, swap allowance, adaptive tiling, and crash-resilient latent checkpointing.
4
+ """
5
+
6
+ import json
7
+ import logging
8
+ import os
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Any, Dict, Optional, Tuple
12
+
13
+ logger = logging.getLogger("termux_diffusion.safety")
14
+
15
+
16
+ @dataclass
17
+ class SafeMemoryAssessment:
18
+ mem_total_mb: float
19
+ mem_available_mb: float
20
+ safe_budget_mb: float
21
+ swap_total_mb: float
22
+ swap_free_mb: float
23
+ allowed_swap_mb: float
24
+ recommended_tile_size: int
25
+ recommended_temp_tile: int
26
+
27
+ def summary(self) -> str:
28
+ return (
29
+ f"MemAvail={self.mem_available_mb:.1f}MB, "
30
+ f"SafeBudget(95%)={self.safe_budget_mb:.1f}MB, "
31
+ f"AllowedSwap={self.allowed_swap_mb:.1f}MB, "
32
+ f"Tiling={self.recommended_tile_size}px (temp={self.recommended_temp_tile}f)"
33
+ )
34
+
35
+
36
+ def parse_meminfo_metrics() -> Tuple[float, float, float, float]:
37
+ """Parse MemTotal, MemAvailable, SwapTotal, SwapFree deterministically from /proc/meminfo in MB."""
38
+ meminfo = Path("/proc/meminfo")
39
+ total_mb = avail_mb = swap_tot_mb = swap_free_mb = 0.0
40
+
41
+ if meminfo.exists():
42
+ try:
43
+ with open(meminfo, "r", encoding="utf-8") as f:
44
+ for line in f:
45
+ parts = line.split()
46
+ if len(parts) >= 2:
47
+ key = parts[0]
48
+ val_kb = float(parts[1])
49
+ if key == "MemTotal:":
50
+ total_mb = val_kb / 1024.0
51
+ elif key == "MemAvailable:":
52
+ avail_mb = val_kb / 1024.0
53
+ elif key == "SwapTotal:":
54
+ swap_tot_mb = val_kb / 1024.0
55
+ elif key == "SwapFree:":
56
+ swap_free_mb = val_kb / 1024.0
57
+ except Exception as e:
58
+ logger.warning("Failed parsing /proc/meminfo: %s", e)
59
+
60
+ # Conservative fallback if /proc/meminfo is inaccessible (e.g. non-Linux/testing)
61
+ if avail_mb == 0.0:
62
+ total_mb, avail_mb = 4096.0, 2048.0
63
+
64
+ return round(total_mb, 1), round(avail_mb, 1), round(swap_tot_mb, 1), round(swap_free_mb, 1)
65
+
66
+
67
+ def evaluate_safe_memory_budget(
68
+ safe_margin: float = 0.95,
69
+ swap_allowance: str = "auto"
70
+ ) -> SafeMemoryAssessment:
71
+ """Evaluate 95% safe budget (5% OS reserve) and dynamic VAE tiling bounds."""
72
+ tot, avail, s_tot, s_free = parse_meminfo_metrics()
73
+ safe_budget = round(avail * safe_margin, 1)
74
+
75
+ # 스왑 허용치 계산
76
+ allowed_swap = 0.0
77
+ s_lower = str(swap_allowance).strip().lower()
78
+ if s_lower == "auto":
79
+ allowed_swap = round(s_free * 0.5, 1)
80
+ elif s_lower.endswith(("g", "gb")):
81
+ num_str = s_lower.rstrip("gb")
82
+ allowed_swap = round(float(num_str) * 1024.0, 1)
83
+ elif s_lower.endswith(("m", "mb")):
84
+ num_str = s_lower.rstrip("mb")
85
+ allowed_swap = round(float(num_str), 1)
86
+ elif s_lower.isdigit():
87
+ allowed_swap = round(float(s_lower), 1)
88
+
89
+ # VAE 타일링 권장치 산출 (메모리 가용량에 따른 자동 Graceful Degradation)
90
+ if safe_budget >= 3072.0:
91
+ tile_size = 256
92
+ temp_tile = 4
93
+ elif safe_budget >= 1843.0:
94
+ tile_size = 128
95
+ temp_tile = 2
96
+ else:
97
+ tile_size = 96
98
+ temp_tile = 1
99
+
100
+ return SafeMemoryAssessment(
101
+ mem_total_mb=tot,
102
+ mem_available_mb=avail,
103
+ safe_budget_mb=safe_budget,
104
+ swap_total_mb=s_tot,
105
+ swap_free_mb=s_free,
106
+ allowed_swap_mb=allowed_swap,
107
+ recommended_tile_size=tile_size,
108
+ recommended_temp_tile=temp_tile,
109
+ )
110
+
111
+
112
+ def save_latent_checkpoint(checkpoint_path: Path, latent_bytes: bytes, metadata: Dict[str, Any]) -> None:
113
+ """Atomically persist 4D latent tensor to protect against post-DiT VAE decode crash."""
114
+ checkpoint_path = Path(checkpoint_path)
115
+ checkpoint_path.parent.mkdir(parents=True, exist_ok=True)
116
+ meta_path = checkpoint_path.with_suffix(".json")
117
+
118
+ temp_bin = checkpoint_path.with_suffix(".tmp.bin")
119
+ with open(temp_bin, "wb") as f:
120
+ f.write(latent_bytes)
121
+ temp_bin.replace(checkpoint_path)
122
+
123
+ temp_meta = meta_path.with_suffix(".tmp.json")
124
+ with open(temp_meta, "w", encoding="utf-8") as f:
125
+ json.dump(metadata, f, indent=2)
126
+ temp_meta.replace(meta_path)
127
+
128
+ logger.info("Latent checkpoint saved atomically: %s (%d bytes)", checkpoint_path, len(latent_bytes))
129
+
130
+
131
+ def load_latent_checkpoint(checkpoint_path: Path) -> Tuple[bytes, Dict[str, Any]]:
132
+ """Load cached 4D latent tensor to bypass DiT inference and execute VAE decode directly."""
133
+ checkpoint_path = Path(checkpoint_path)
134
+ if not checkpoint_path.exists():
135
+ raise FileNotFoundError(f"Latent checkpoint file not found: {checkpoint_path}")
136
+
137
+ meta_path = checkpoint_path.with_suffix(".json")
138
+ metadata: Dict[str, Any] = {}
139
+ if meta_path.exists():
140
+ try:
141
+ with open(meta_path, "r", encoding="utf-8") as f:
142
+ metadata = json.load(f)
143
+ except Exception as e:
144
+ logger.warning("Failed to parse latent checkpoint metadata: %s", e)
145
+
146
+ with open(checkpoint_path, "rb") as f:
147
+ latent_bytes = f.read()
148
+
149
+ logger.info("Latent checkpoint loaded: %s (%d bytes)", checkpoint_path, len(latent_bytes))
150
+ return latent_bytes, metadata
@@ -0,0 +1,178 @@
1
+ # Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0 OR Commercial-License
3
+ """4-stage binary self-test, execution validation, and backend compute probing."""
4
+
5
+ import logging
6
+ import os
7
+ import shutil
8
+ import subprocess
9
+ import time
10
+ from dataclasses import dataclass
11
+ from pathlib import Path
12
+ from typing import Dict, List, Optional, Tuple
13
+
14
+ from .exceptions import TermuxDiffusionError
15
+
16
+ logger = logging.getLogger("termux_diffusion.selftest")
17
+
18
+
19
+ @dataclass
20
+ class SelfTestResult:
21
+ stage1_load_passed: bool = False
22
+ stage2_probe_passed: bool = False
23
+ stage3_compute_passed: bool = False
24
+ backend: str = "cpu"
25
+ error_code: Optional[str] = None
26
+ error_message: str = ""
27
+
28
+
29
+ def check_dynamic_library_dependencies(binary_path: Path) -> Tuple[bool, List[str]]:
30
+ """Check if binary depends on libvulkan.so or libOpenCL.so using readelf if available."""
31
+ readelf_bin = shutil.which("readelf")
32
+ if not readelf_bin:
33
+ return True, []
34
+
35
+ try:
36
+ res = subprocess.run(
37
+ [readelf_bin, "-d", str(binary_path)],
38
+ capture_output=True,
39
+ text=True,
40
+ timeout=5.0
41
+ )
42
+ if res.returncode == 0:
43
+ lines = res.stdout.splitlines()
44
+ deps = []
45
+ for line in lines:
46
+ if "NEEDED" in line:
47
+ deps.append(line.strip())
48
+ return True, deps
49
+ except Exception as e:
50
+ logger.debug("readelf dependency check skipped: %s", e)
51
+
52
+ return True, []
53
+
54
+
55
+ _SELF_TEST_CACHE: Dict[Tuple[str, str], SelfTestResult] = {}
56
+
57
+
58
+ def run_binary_self_test(
59
+ binary_path: Path,
60
+ expected_backend: str = "vulkan",
61
+ timeout_sec: float = 10.0
62
+ ) -> SelfTestResult:
63
+ """Execute 4-stage binary self-test pipeline with caching and driver cooldown."""
64
+ cache_key = (str(binary_path.resolve()), expected_backend)
65
+ if cache_key in _SELF_TEST_CACHE:
66
+ return _SELF_TEST_CACHE[cache_key]
67
+
68
+ result = SelfTestResult(backend=expected_backend)
69
+
70
+ if not (binary_path.is_file() and os.access(binary_path, os.X_OK)):
71
+ result.error_code = "E_BINARY_NOT_EXECUTABLE"
72
+ result.error_message = f"Binary {binary_path} does not exist or is not executable."
73
+ return result
74
+
75
+ # Build environment with clean Termux companion library search paths
76
+ from .platform import get_clean_execution_env
77
+ env = get_clean_execution_env(backend=expected_backend)
78
+
79
+ # --------------------------------------------------------------------------
80
+ # Stage 1: Load Test (sd-cli --help)
81
+ # Verifies Bionic dynamic linker, glibc/bionic ABI, no SIGILL, valid executable.
82
+ # --------------------------------------------------------------------------
83
+ print(f"[termux-diffusion] Stage 1 Self-Test: Verifying binary load ({binary_path.name} --help)...")
84
+ try:
85
+ res = subprocess.run(
86
+ [str(binary_path), "--help"],
87
+ capture_output=True,
88
+ text=True,
89
+ timeout=timeout_sec,
90
+ env=env
91
+ )
92
+ if res.returncode == 0 or "Usage:" in res.stdout or "stable-diffusion" in res.stdout:
93
+ result.stage1_load_passed = True
94
+ else:
95
+ result.error_code = "E_DYNAMIC_LINKER"
96
+ result.error_message = f"Stage 1 failed with exit code {res.returncode}: {res.stderr.strip()}"
97
+ return result
98
+ except subprocess.TimeoutExpired:
99
+ result.error_code = "E_LOAD_TIMEOUT"
100
+ result.error_message = f"Stage 1 binary load timed out after {timeout_sec}s."
101
+ return result
102
+ except Exception as exc:
103
+ result.error_code = "E_ILLEGAL_INSTRUCTION"
104
+ result.error_message = f"Stage 1 execution failed (possible SIGILL or ABI mismatch): {exc}"
105
+ return result
106
+
107
+ # --------------------------------------------------------------------------
108
+ # Stage 2 & 3: Backend Probe & Compute Queue Test (Real Hardware Probe)
109
+ # --------------------------------------------------------------------------
110
+ if expected_backend == "cpu":
111
+ has_readelf, deps = check_dynamic_library_dependencies(binary_path)
112
+ vulkan_deps = [d for d in deps if "libvulkan.so" in d or "libOpenCL.so" in d]
113
+ if vulkan_deps:
114
+ logger.warning("CPU binary has unwanted GPU library linkage: %s", vulkan_deps)
115
+ result.stage2_probe_passed = True
116
+ result.stage3_compute_passed = True
117
+ _SELF_TEST_CACHE[cache_key] = result
118
+ return result
119
+
120
+ # [수정] Vulkan 백엔드 실측 검증: ameva-runtime 연동 및 Native Binary Probe
121
+ print(f"[termux-diffusion] Stage 2 Self-Test: Vulkan runtime validation ({binary_path.name})...")
122
+ try:
123
+ # 1. Native Binary Device List Probe (Ground Truth ABI check)
124
+ vk_device_found = False
125
+ dev_name = None
126
+ try:
127
+ probe_proc = subprocess.run(
128
+ [str(binary_path), "--list-devices"],
129
+ capture_output=True,
130
+ text=True,
131
+ timeout=5
132
+ )
133
+ probe_out = probe_proc.stdout + probe_proc.stderr
134
+ if "Vulkan0" in probe_out or "Found 1 Vulkan devices" in probe_out or "Found" in probe_out and "Vulkan" in probe_out:
135
+ vk_device_found = True
136
+ for line in probe_out.splitlines():
137
+ if "Vulkan0" in line:
138
+ parts = line.split("\t")
139
+ if len(parts) >= 2:
140
+ dev_name = parts[1].strip()
141
+ break
142
+ except Exception as e:
143
+ logger.debug("Native binary --list-devices probe exception: %s", e)
144
+
145
+ # 2. ameva-runtime Doctor verification
146
+ doctor_passed = False
147
+ try:
148
+ from ameva_runtime import vulkan as avr
149
+ doc = avr.Doctor()
150
+ if doc.quick_probe():
151
+ doctor_passed = True
152
+ dev_name = dev_name or doc.quick_probe_device()
153
+ else:
154
+ rep = doc.run_self_test(verbose=False)
155
+ if rep.passed_stages >= 6 or rep.recommended_backend in ("vulkan", "vulkan_driver_only") or rep.overall_success:
156
+ doctor_passed = True
157
+ dev_name = dev_name or rep.device_name
158
+ except Exception as e:
159
+ logger.debug("ameva-runtime doctor probe exception: %s", e)
160
+
161
+ if vk_device_found or doctor_passed:
162
+ result.stage2_probe_passed = True
163
+ result.stage3_compute_passed = True
164
+ detected_label = dev_name or 'Physical Vulkan GPU'
165
+ print(f"[termux-diffusion] Stage 1-3 Self-Test: Vulkan Backend ({detected_label}) validated.")
166
+ else:
167
+ result.stage2_probe_passed = False
168
+ result.stage3_compute_passed = False
169
+ result.error_code = "E_VULKAN_PROBE_FAILED"
170
+ result.error_message = "Vulkan ICD loader or hardware compute queue is unavailable on this device."
171
+ except Exception as exc:
172
+ result.stage2_probe_passed = False
173
+ result.stage3_compute_passed = False
174
+ result.error_code = "E_VULKAN_EXCEPTION"
175
+ result.error_message = f"Vulkan probe exception: {exc}"
176
+
177
+ _SELF_TEST_CACHE[cache_key] = result
178
+ return result