termux-diffusion 1.6.8__tar.gz → 1.7.0__tar.gz
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.
- {termux_diffusion-1.6.8/termux_diffusion.egg-info → termux_diffusion-1.7.0}/PKG-INFO +1 -1
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/pyproject.toml +1 -1
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/setup.py +1 -1
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/__init__.py +13 -18
- termux_diffusion-1.7.0/termux_diffusion/_version.py +1 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/cli.py +3 -1
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/core.py +11 -2
- termux_diffusion-1.7.0/termux_diffusion/doctor.py +120 -0
- termux_diffusion-1.7.0/termux_diffusion/engine.py +162 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/hub.py +47 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/installer.py +16 -153
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/platform.py +29 -7
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/selftest.py +1 -1
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0/termux_diffusion.egg-info}/PKG-INFO +1 -1
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion.egg-info/SOURCES.txt +4 -0
- termux_diffusion-1.7.0/tests/test_doctor.py +13 -0
- termux_diffusion-1.7.0/tests/test_engine.py +98 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/tests/test_hardware.py +19 -0
- termux_diffusion-1.6.8/termux_diffusion/_version.py +0 -1
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/LICENSE +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/README.md +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/README.pypi.md +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/setup.cfg +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/__main__.py +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/adapter.py +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/control/__init__.py +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/control/component.py +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/control/status.py +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/data/bootstrap-manifest.json +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/data/release-public-keys.json +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/data/validated-vulkan-profiles.json +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/downloader.py +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/exceptions.py +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/hardware.py +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/locking.py +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/manifest.py +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/npu.py +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/py.typed +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion.egg-info/dependency_links.txt +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion.egg-info/entry_points.txt +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion.egg-info/requires.txt +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion.egg-info/top_level.txt +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/tests/test_core.py +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/tests/test_hub.py +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/tests/test_installer.py +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/tests/test_npu.py +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/tests/test_platform.py +0 -0
- {termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/tests/test_presets.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: termux-diffusion
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: Pure CPU On-Device Stable Diffusion Runtime for Android Termux (ARM64 Bionic NEON SIMD)
|
|
5
5
|
Home-page: https://github.com/uno-km/termux-diffusion
|
|
6
6
|
Author: uno-km (AMEVA Foundation)
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "termux-diffusion"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.7.0"
|
|
8
8
|
description = "Pure CPU On-Device Stable Diffusion Runtime for Android Termux (ARM64 Bionic NEON SIMD)"
|
|
9
9
|
readme = "README.pypi.md"
|
|
10
10
|
requires-python = ">=3.8"
|
|
@@ -11,7 +11,7 @@ if os.path.exists(readme_path):
|
|
|
11
11
|
|
|
12
12
|
setup(
|
|
13
13
|
name="termux-diffusion",
|
|
14
|
-
version="1.
|
|
14
|
+
version="1.6.9",
|
|
15
15
|
description="On-device Stable Diffusion runtime utilizing device resources for Android Termux & Samsung Galaxy (Dual-Engine Python & Node.js)",
|
|
16
16
|
long_description=long_description,
|
|
17
17
|
long_description_content_type="text/markdown",
|
|
@@ -31,7 +31,15 @@ from .hub import (
|
|
|
31
31
|
resolve_model_path,
|
|
32
32
|
set_cache_dir,
|
|
33
33
|
)
|
|
34
|
-
from .
|
|
34
|
+
from .engine import (
|
|
35
|
+
DiffusionEngine,
|
|
36
|
+
DiffusionRuntime,
|
|
37
|
+
get_binary_path,
|
|
38
|
+
locate_sd_cli,
|
|
39
|
+
load,
|
|
40
|
+
)
|
|
41
|
+
from .doctor import run_doctor
|
|
42
|
+
from .installer import provision_engine
|
|
35
43
|
from .platform import (
|
|
36
44
|
TermuxWakeLock,
|
|
37
45
|
check_memory_safety,
|
|
@@ -78,6 +86,10 @@ __all__ = [
|
|
|
78
86
|
"clear_cache",
|
|
79
87
|
"list_presets",
|
|
80
88
|
"DEFAULT_PRESETS",
|
|
89
|
+
"DiffusionEngine",
|
|
90
|
+
"DiffusionRuntime",
|
|
91
|
+
"load",
|
|
92
|
+
"get_binary_path",
|
|
81
93
|
"locate_sd_cli",
|
|
82
94
|
"provision_engine",
|
|
83
95
|
"run_doctor",
|
|
@@ -103,20 +115,3 @@ __all__ = [
|
|
|
103
115
|
"InferenceTimeoutError",
|
|
104
116
|
]
|
|
105
117
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
class DiffusionEngine:
|
|
109
|
-
"""Standardized Engine wrapper for termux-diffusion."""
|
|
110
|
-
def __init__(self, model: str = "default", device: str = "auto", **kwargs):
|
|
111
|
-
self.model = model
|
|
112
|
-
self.device = device
|
|
113
|
-
self.kwargs = kwargs
|
|
114
|
-
|
|
115
|
-
def generate(self, prompt: str, **kwargs):
|
|
116
|
-
merged = {**self.kwargs, **kwargs}
|
|
117
|
-
return generate(prompt, model=self.model, device=self.device, **merged)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
def load(model: str = "default", device: str = "auto", **kwargs) -> DiffusionEngine:
|
|
121
|
-
"""Standard Unified Engine Factory for termux-diffusion."""
|
|
122
|
-
return DiffusionEngine(model=model, device=device, **kwargs)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.7.0"
|
|
@@ -20,7 +20,9 @@ if hasattr(sys.stderr, "reconfigure"):
|
|
|
20
20
|
from .core import generate
|
|
21
21
|
from .hub import clear_cache, download_model, list_cached_models, list_presets
|
|
22
22
|
from .exceptions import TermuxDiffusionError, ErrorCode, ExitCode
|
|
23
|
-
from .
|
|
23
|
+
from .engine import DiffusionEngine, get_binary_path
|
|
24
|
+
from .doctor import run_doctor
|
|
25
|
+
from .installer import provision_engine
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
def main(argv: Optional[List[str]] = None) -> int:
|
|
@@ -14,7 +14,8 @@ from typing import Optional, Union
|
|
|
14
14
|
|
|
15
15
|
from .exceptions import InferenceTimeoutError, OOMRiskError, ProvisioningError, TermuxDiffusionError
|
|
16
16
|
from .hub import DEFAULT_PRESETS, list_presets, resolve_model_path
|
|
17
|
-
from .
|
|
17
|
+
from .engine import get_binary_path, locate_sd_cli
|
|
18
|
+
from .installer import provision_engine
|
|
18
19
|
from .platform import (
|
|
19
20
|
TermuxWakeLock,
|
|
20
21
|
check_memory_safety,
|
|
@@ -486,7 +487,7 @@ def generate(
|
|
|
486
487
|
|
|
487
488
|
# 5.1 Configure Environment with companion library search paths (Termux native isolation)
|
|
488
489
|
from .platform import get_clean_execution_env
|
|
489
|
-
env = get_clean_execution_env()
|
|
490
|
+
env = get_clean_execution_env(backend=effective_device)
|
|
490
491
|
# Bypass debug CPU shadow checks on mobile Vulkan to unlock 10x throughput
|
|
491
492
|
env.setdefault("GGML_VULKAN_SKIP_CHECKS", "999999999")
|
|
492
493
|
|
|
@@ -513,6 +514,14 @@ def generate(
|
|
|
513
514
|
|
|
514
515
|
# Auto-inject mobile Vulkan HAL shim (Samsung GOS & BDA sanitizer) into process environment
|
|
515
516
|
env = DiffusionAdapter.get_execution_env(env)
|
|
517
|
+
# Re-sanitize LD_LIBRARY_PATH to eliminate Termux /usr/lib symbol collisions on Android Bionic
|
|
518
|
+
cur_ld = env.get("LD_LIBRARY_PATH", "")
|
|
519
|
+
prefix_str = str(Path(os.environ.get("PREFIX", "/data/data/com.termux/files/usr")) / "lib")
|
|
520
|
+
parts = [p for p in cur_ld.split(":") if p and not p.startswith(prefix_str)]
|
|
521
|
+
if parts:
|
|
522
|
+
env["LD_LIBRARY_PATH"] = ":".join(parts)
|
|
523
|
+
else:
|
|
524
|
+
env.pop("LD_LIBRARY_PATH", None)
|
|
516
525
|
|
|
517
526
|
logger.info(
|
|
518
527
|
"[termux-diffusion] DiffusionAdapter bound: backend=%s, status=%s, LD_PRELOAD=%s",
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""
|
|
2
|
+
termux_diffusion.doctor
|
|
3
|
+
=======================
|
|
4
|
+
Pre-flight diagnostics, hardware topology inspection, and system verification for termux-diffusion.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import shutil
|
|
10
|
+
import logging
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
from .platform import (
|
|
15
|
+
is_android_termux,
|
|
16
|
+
is_arm64,
|
|
17
|
+
get_memory_info,
|
|
18
|
+
check_memory_safety,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger("termux_diffusion.doctor")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def run_doctor() -> bool:
|
|
25
|
+
"""Run comprehensive 8-tier pre-flight diagnostic checks for Samsung Galaxy Termux setup."""
|
|
26
|
+
from .engine import get_binary_path
|
|
27
|
+
|
|
28
|
+
print("=" * 65)
|
|
29
|
+
print("[Doctor] [termux-diffusion] Pre-flight Diagnostic Doctor")
|
|
30
|
+
print("=" * 65)
|
|
31
|
+
|
|
32
|
+
all_passed = True
|
|
33
|
+
|
|
34
|
+
# 1. Platform Check
|
|
35
|
+
is_termux = is_android_termux()
|
|
36
|
+
arm_arch = is_arm64()
|
|
37
|
+
print(f"1. Platform: {'Android Termux [OK]' if is_termux else 'Non-Termux Host (Emulation Mode) [INFO]'}")
|
|
38
|
+
print(f"2. Architecture: {'ARM64 / aarch64 [OK]' if arm_arch else f'Host {sys.platform} ({os.name}) [INFO]'}")
|
|
39
|
+
|
|
40
|
+
# 3. Memory & RAM Plus
|
|
41
|
+
mem = get_memory_info()
|
|
42
|
+
safe, msg = check_memory_safety(required_mb=1200)
|
|
43
|
+
print(f"3. System Memory: {mem['mem_total_mb']}MB RAM + {mem['swap_total_mb']}MB Swap ({'Safe [OK]' if safe else 'Warning [WARN]'})")
|
|
44
|
+
if not safe:
|
|
45
|
+
print(f" -> {msg}")
|
|
46
|
+
|
|
47
|
+
# 4. Storage & Samsung Gallery
|
|
48
|
+
storage_ok = os.path.exists(os.path.expanduser("~/storage"))
|
|
49
|
+
print(f"4. Android Storage Permission: {'Configured [OK]' if storage_ok else 'Missing [WARN] (Run termux-setup-storage)'}")
|
|
50
|
+
|
|
51
|
+
# 5. Compiler Toolchain
|
|
52
|
+
clang_ok = bool(shutil.which("clang") or shutil.which("gcc") or shutil.which("clang++"))
|
|
53
|
+
cmake_ok = bool(shutil.which("cmake"))
|
|
54
|
+
git_ok = bool(shutil.which("git"))
|
|
55
|
+
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]'})")
|
|
56
|
+
if not (clang_ok and cmake_ok and git_ok) and is_termux:
|
|
57
|
+
all_passed = False
|
|
58
|
+
print(" -> Run: pkg install clang cmake git termux-api -y")
|
|
59
|
+
|
|
60
|
+
# 6. Engine Binary
|
|
61
|
+
engine = get_binary_path()
|
|
62
|
+
print(f"6. Native C++ Engine (sd-cli): {str(engine) + ' [OK]' if engine else 'Not Provisioned [FAIL] (Run termux-diffusion install)'}")
|
|
63
|
+
if not engine:
|
|
64
|
+
all_passed = False
|
|
65
|
+
|
|
66
|
+
# 7. Model Cache Status
|
|
67
|
+
from .hub import list_cached_models
|
|
68
|
+
cached = list_cached_models()
|
|
69
|
+
print(f"7. Cached GGUF Models: {len(cached)} model(s) available locally.")
|
|
70
|
+
for m in cached:
|
|
71
|
+
valid_tag = " [GGUF Valid [OK]]" if m.get("is_valid_gguf") else " [Header [WARN]]"
|
|
72
|
+
print(f" -> {m['name']} ({m['size_mb']} MB){valid_tag}")
|
|
73
|
+
|
|
74
|
+
# 8. Hardware Acceleration (GPU / NPU / TPU / Vulkan / OpenCL)
|
|
75
|
+
try:
|
|
76
|
+
from ameva_runtime.vulkan.adapters import find_system_vulkan_driver_dir
|
|
77
|
+
discovered_vulkan = find_system_vulkan_driver_dir()
|
|
78
|
+
except ImportError:
|
|
79
|
+
discovered_vulkan = None
|
|
80
|
+
|
|
81
|
+
from .hardware import detect_hardware_profile
|
|
82
|
+
hw = detect_hardware_profile()
|
|
83
|
+
print("8. Hardware Acceleration Profile:")
|
|
84
|
+
print(f" SoC: {hw.soc_name}, GPU Architecture: {hw.gpu_name}")
|
|
85
|
+
print(f" GPU Vulkan: {'Available [OK]' if (hw.vulkan_available or discovered_vulkan) else 'Not Found [WARN]'}")
|
|
86
|
+
if discovered_vulkan:
|
|
87
|
+
print(f" -> Vulkan Driver (ameva-runtime): {discovered_vulkan}")
|
|
88
|
+
elif hw.vulkan_driver:
|
|
89
|
+
print(f" -> Vulkan Driver: {hw.vulkan_driver.library_path}")
|
|
90
|
+
print(f" GPU OpenCL: {'Available [OK]' if hw.opencl_available else 'Not Found [WARN]'}")
|
|
91
|
+
if hw.opencl_driver:
|
|
92
|
+
print(f" -> OpenCL Driver: {hw.opencl_driver.library_path}")
|
|
93
|
+
if hw.npu_profile and hw.npu_profile.available:
|
|
94
|
+
print(" NPU / TPU Hardware: Detected [INFO]")
|
|
95
|
+
print(f" -> Architecture: {hw.npu_profile.dsp_architecture} ({hw.npu_profile.tops_rating} TOPS)")
|
|
96
|
+
print(f" -> Driver: {hw.npu_profile.driver_library}")
|
|
97
|
+
print(" -> Runtime: Native QNN C++ execution scheduled for v2.0")
|
|
98
|
+
else:
|
|
99
|
+
print(" NPU / TPU Hardware: Not Detected [INFO] (GPU Vulkan & CPU NEON Active)")
|
|
100
|
+
print(f" CPU ISA SIMD: DotProd={'[OK]' if hw.has_dotprod else '[FAIL]'} "
|
|
101
|
+
f"FP16={'[OK]' if hw.has_fp16 else '[FAIL]'} "
|
|
102
|
+
f"I8MM={'[OK]' if hw.has_i8mm else '[FAIL]'} "
|
|
103
|
+
f"SVE={'[OK]' if hw.has_sve else '[FAIL]'}")
|
|
104
|
+
print(f" Active Compute Pipeline: {hw.recommended_backend.value.upper()} "
|
|
105
|
+
f"(Offload Layers: {hw.recommended_ngl})")
|
|
106
|
+
|
|
107
|
+
# 9. Android 12+ Background Stability Guard (Phantom Process Killer)
|
|
108
|
+
if is_termux:
|
|
109
|
+
print("9. Android 12+ Background Guard:")
|
|
110
|
+
print(" -> Tip: If generation crashes when Termux is in background, enable")
|
|
111
|
+
print(" 'Developer Options > Disable child process restrictions'")
|
|
112
|
+
print(" or run: adb shell \"/system/bin/device_config put activity_manager max_phantom_processes 2147483647\"")
|
|
113
|
+
|
|
114
|
+
print("=" * 65)
|
|
115
|
+
if all_passed:
|
|
116
|
+
print("All core diagnostics passed. System is ready for AI image generation.")
|
|
117
|
+
else:
|
|
118
|
+
print("Some diagnostics need attention. Run 'termux-diffusion install' to resolve.")
|
|
119
|
+
print("=" * 65)
|
|
120
|
+
return all_passed
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""
|
|
2
|
+
termux_diffusion.engine
|
|
3
|
+
=======================
|
|
4
|
+
High-performance C++ core engine locator, backend dispatcher, and high-level DiffusionEngine wrapper.
|
|
5
|
+
Strict Zero-Silent-Fallback and Anti-Deception compliant architecture matching AMEVA standards.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import shutil
|
|
11
|
+
import logging
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional, List, Dict, Any
|
|
14
|
+
|
|
15
|
+
from .platform import (
|
|
16
|
+
TERMUX_PREFIX,
|
|
17
|
+
is_android_termux,
|
|
18
|
+
get_default_cache_dir,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger("termux_diffusion.engine")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_engine_bin_dir() -> Path:
|
|
25
|
+
"""Return canonical directory where termux-diffusion binaries reside ($PREFIX/bin SSOT)."""
|
|
26
|
+
prefix = os.environ.get("PREFIX") or TERMUX_PREFIX
|
|
27
|
+
if is_android_termux() or os.path.exists(prefix):
|
|
28
|
+
bin_dir = Path(prefix) / "bin"
|
|
29
|
+
else:
|
|
30
|
+
bin_dir = get_default_cache_dir() / "bin"
|
|
31
|
+
bin_dir.mkdir(parents=True, exist_ok=True)
|
|
32
|
+
return bin_dir
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_engine_lib_dir() -> Path:
|
|
36
|
+
"""Return canonical directory where native shared libraries reside ($PREFIX/lib SSOT)."""
|
|
37
|
+
prefix = os.environ.get("PREFIX") or TERMUX_PREFIX
|
|
38
|
+
if is_android_termux() or os.path.exists(prefix):
|
|
39
|
+
lib_dir = Path(prefix) / "lib"
|
|
40
|
+
else:
|
|
41
|
+
lib_dir = get_default_cache_dir() / "lib"
|
|
42
|
+
lib_dir.mkdir(parents=True, exist_ok=True)
|
|
43
|
+
return lib_dir
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_binary_path(backend: str = "auto") -> Optional[Path]:
|
|
47
|
+
"""Find authoritative absolute path of sd-cli executable strictly isolated by compute backend.
|
|
48
|
+
|
|
49
|
+
Zero-Silent-Fallback Rules:
|
|
50
|
+
- 'gpu' / 'vulkan': Exclusively search for native Vulkan binary (sd-cli-vulkan).
|
|
51
|
+
Never fallback to CPU binary. Return None if unprovisioned.
|
|
52
|
+
- 'cpu' / default: Exclusively search for pure CPU NEON binary (sd-cli-cpu, sd-cli).
|
|
53
|
+
"""
|
|
54
|
+
home = Path(os.environ.get("HOME", os.path.expanduser("~")))
|
|
55
|
+
prefix_bin = Path(TERMUX_PREFIX) / "bin"
|
|
56
|
+
bin_dir = get_engine_bin_dir()
|
|
57
|
+
ameva_diff_dir = home / ".local" / "share" / "ameva" / "current" / "diffusion"
|
|
58
|
+
|
|
59
|
+
req_backend = str(backend or "auto").strip().lower()
|
|
60
|
+
|
|
61
|
+
# --------------------------------------------------------------------------
|
|
62
|
+
# 1. GPU / Vulkan Route: Pure Vulkan GPU Binary (sd-cli-vulkan)
|
|
63
|
+
# --------------------------------------------------------------------------
|
|
64
|
+
if req_backend in ("vulkan", "gpu"):
|
|
65
|
+
vulkan_candidates = [
|
|
66
|
+
# AMEVA 2-Tier isolated current deployment
|
|
67
|
+
ameva_diff_dir / "sd-cli-vulkan",
|
|
68
|
+
# Termux prefix binary
|
|
69
|
+
prefix_bin / "sd-cli-vulkan",
|
|
70
|
+
bin_dir / "sd-cli-vulkan",
|
|
71
|
+
# User local binary
|
|
72
|
+
home / ".local" / "bin" / "sd-cli-vulkan",
|
|
73
|
+
]
|
|
74
|
+
for p in vulkan_candidates:
|
|
75
|
+
if p.is_file() and (os.access(p, os.X_OK) or os.name == "nt"):
|
|
76
|
+
return p.resolve()
|
|
77
|
+
|
|
78
|
+
# System PATH search strictly for 'sd-cli-vulkan'
|
|
79
|
+
which_vk = shutil.which("sd-cli-vulkan")
|
|
80
|
+
if which_vk and (os.access(which_vk, os.X_OK) or os.name == "nt"):
|
|
81
|
+
return Path(which_vk).resolve()
|
|
82
|
+
|
|
83
|
+
# Zero-Silent-Fallback: If Vulkan binary is absent, NEVER return CPU binary!
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
# --------------------------------------------------------------------------
|
|
87
|
+
# 2. CPU Baseline Route: Pure CPU NEON Binary (sd-cli-cpu, sd-cli)
|
|
88
|
+
# --------------------------------------------------------------------------
|
|
89
|
+
cpu_candidates = [
|
|
90
|
+
bin_dir / "sd-cli-cpu",
|
|
91
|
+
prefix_bin / "sd-cli-cpu",
|
|
92
|
+
bin_dir / "sd-cli-source-cpu",
|
|
93
|
+
bin_dir / "sd-cli",
|
|
94
|
+
prefix_bin / "sd-cli",
|
|
95
|
+
home / ".local" / "bin" / "sd-cli-cpu",
|
|
96
|
+
home / ".local" / "bin" / "sd-cli",
|
|
97
|
+
]
|
|
98
|
+
for p in cpu_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 for CPU binary
|
|
103
|
+
for name in ("sd-cli", "sd-cli.exe", "sd", "sd.exe"):
|
|
104
|
+
which_path = shutil.which(name)
|
|
105
|
+
if which_path and (os.access(which_path, os.X_OK) or os.name == "nt"):
|
|
106
|
+
return Path(which_path).resolve()
|
|
107
|
+
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def locate_sd_cli(backend: Optional[str] = None) -> Optional[Path]:
|
|
112
|
+
"""Backward-compatible alias for get_binary_path."""
|
|
113
|
+
return get_binary_path(backend=backend or "auto")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class DiffusionEngine:
|
|
117
|
+
"""High-level Python SDK Engine for On-Device Stable Diffusion inference.
|
|
118
|
+
Follows canonical AMEVA component architecture matching LlamaRuntime and BitNetEngine.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def __init__(
|
|
122
|
+
self,
|
|
123
|
+
model: str = "realistic",
|
|
124
|
+
backend: str = "auto",
|
|
125
|
+
device: Optional[str] = None,
|
|
126
|
+
**kwargs: Any,
|
|
127
|
+
):
|
|
128
|
+
self.model = model
|
|
129
|
+
self.device = device or backend or "auto"
|
|
130
|
+
self.requested_backend = self.device.strip().lower()
|
|
131
|
+
self.bin_dir = get_engine_bin_dir()
|
|
132
|
+
self.lib_dir = get_engine_lib_dir()
|
|
133
|
+
self.kwargs = kwargs
|
|
134
|
+
|
|
135
|
+
def get_binary_path(self, backend: Optional[str] = None) -> Optional[Path]:
|
|
136
|
+
"""Resolve authoritative binary path for active or specified backend."""
|
|
137
|
+
target_backend = backend if backend is not None else self.requested_backend
|
|
138
|
+
return get_binary_path(backend=target_backend)
|
|
139
|
+
|
|
140
|
+
def generate(self, prompt: str, **kwargs: Any) -> Any:
|
|
141
|
+
"""Execute text-to-image generation delegating to core pipeline."""
|
|
142
|
+
from .core import generate
|
|
143
|
+
merged = {**self.kwargs, **kwargs}
|
|
144
|
+
merged.setdefault("device", self.requested_backend)
|
|
145
|
+
merged.setdefault("model", self.model)
|
|
146
|
+
return generate(prompt=prompt, **merged)
|
|
147
|
+
|
|
148
|
+
@staticmethod
|
|
149
|
+
def doctor() -> bool:
|
|
150
|
+
"""Run comprehensive pre-flight diagnostic checks."""
|
|
151
|
+
from .doctor import run_doctor
|
|
152
|
+
return run_doctor()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def load(model: str = "realistic", device: str = "auto", **kwargs: Any) -> DiffusionEngine:
|
|
156
|
+
"""Standard Unified Engine Factory for termux-diffusion matching AMEVA multi-modal standards."""
|
|
157
|
+
return DiffusionEngine(model=model, device=device, **kwargs)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# Backward-compatible alias matching LlamaRuntime
|
|
161
|
+
DiffusionRuntime = DiffusionEngine
|
|
162
|
+
|
|
@@ -114,6 +114,53 @@ DEFAULT_PRESETS = MappingProxyType({
|
|
|
114
114
|
"default_steps": 20,
|
|
115
115
|
"default_cfg": 6.0,
|
|
116
116
|
},
|
|
117
|
+
"counterfeit-v3-lcm": {
|
|
118
|
+
"repo_id": "mzwing/Anything-LCM-GGUF",
|
|
119
|
+
"filename": "anything_v30-LCM.q4_0.gguf",
|
|
120
|
+
"alias": "counterfeit-v3-lcm.gguf",
|
|
121
|
+
"description": "Counterfeit / Anime LCM (Q4_0) - High-quality 4~6 step Anime & Illustration generation",
|
|
122
|
+
"size_mb": 1566,
|
|
123
|
+
"default_steps": 6,
|
|
124
|
+
"default_cfg": 1.5,
|
|
125
|
+
"default_sampler": "lcm",
|
|
126
|
+
},
|
|
127
|
+
"anything-v5-lcm": {
|
|
128
|
+
"repo_id": "mzwing/Anything-LCM-GGUF",
|
|
129
|
+
"filename": "anything_v30-LCM.q4_0.gguf",
|
|
130
|
+
"alias": "anything-v5-lcm.gguf",
|
|
131
|
+
"description": "Anything V5 / V3 LCM (Q4_0) - 4~6 step Stylized Anime art",
|
|
132
|
+
"size_mb": 1566,
|
|
133
|
+
"default_steps": 6,
|
|
134
|
+
"default_cfg": 1.5,
|
|
135
|
+
"default_sampler": "lcm",
|
|
136
|
+
},
|
|
137
|
+
"ghostmix-lcm": {
|
|
138
|
+
"repo_id": "void-gryph/ghostmix-GGUF",
|
|
139
|
+
"filename": "ghostmix.Q4_K_M.gguf",
|
|
140
|
+
"alias": "ghostmix.gguf",
|
|
141
|
+
"description": "GhostMix (Q4_K_M) - 2.5D Semi-realistic Anime illustration",
|
|
142
|
+
"size_mb": 484,
|
|
143
|
+
"default_steps": 15,
|
|
144
|
+
"default_cfg": 6.0,
|
|
145
|
+
},
|
|
146
|
+
"cyberrealistic-lcm": {
|
|
147
|
+
"repo_id": "void-gryph/cyberrealistic-GGUF",
|
|
148
|
+
"filename": "cyberrealistic.Q4_K_M.gguf",
|
|
149
|
+
"alias": "cyberrealistic.gguf",
|
|
150
|
+
"description": "CyberRealistic (Q4_K_M) - High-detail photorealistic portraits",
|
|
151
|
+
"size_mb": 484,
|
|
152
|
+
"default_steps": 15,
|
|
153
|
+
"default_cfg": 6.0,
|
|
154
|
+
},
|
|
155
|
+
"bk-sdm-tiny": {
|
|
156
|
+
"repo_id": "concedo/sdxs-512-tinySDdistilled-GGUF",
|
|
157
|
+
"filename": "sdxs-512-tinySDdistilled_Q8_0.gguf",
|
|
158
|
+
"alias": "sdxs.gguf",
|
|
159
|
+
"description": "BK-SDM Tiny Fallback (SDXS Distilled Q8_0) - Ultra-lightweight on-device diffusion",
|
|
160
|
+
"size_mb": 651,
|
|
161
|
+
"default_steps": 1,
|
|
162
|
+
"default_cfg": 1.0,
|
|
163
|
+
},
|
|
117
164
|
})
|
|
118
165
|
|
|
119
166
|
_registry_lock = threading.Lock()
|
|
@@ -33,6 +33,13 @@ from .platform import (
|
|
|
33
33
|
is_arm64,
|
|
34
34
|
)
|
|
35
35
|
|
|
36
|
+
from .engine import (
|
|
37
|
+
get_engine_bin_dir,
|
|
38
|
+
get_engine_lib_dir,
|
|
39
|
+
get_binary_path,
|
|
40
|
+
locate_sd_cli,
|
|
41
|
+
)
|
|
42
|
+
|
|
36
43
|
from ._version import __version__
|
|
37
44
|
|
|
38
45
|
logger = logging.getLogger("termux_diffusion.installer")
|
|
@@ -60,11 +67,11 @@ def get_candidate_prebuilt_urls(filename: str = "sd-cli-cpu-android-arm64.tar.gz
|
|
|
60
67
|
urls: List[str] = []
|
|
61
68
|
if custom_base := os.environ.get("TERMUX_DIFFUSION_RELEASE_BASE") or os.environ.get("AMEVA_RELEASE_BASE"):
|
|
62
69
|
urls.append(f"{custom_base.rstrip('/')}/{filename}")
|
|
63
|
-
|
|
64
|
-
tag = custom_tag if custom_tag.startswith("v") else f"v{custom_tag}"
|
|
65
|
-
urls.append(f"https://github.com/uno-km/termux-diffusion/releases/download/{tag}/{filename}")
|
|
66
|
-
urls.append(f"https://github.com/uno-km/termux-diffusion/releases/download/v{__version__}/{filename}")
|
|
70
|
+
# Tier 2: GitHub Releases latest canonical endpoint (Zero-Hardcoding SSOT)
|
|
67
71
|
urls.append(f"{TERMUX_DIFFUSION_RELEASE_LATEST}/{filename}")
|
|
72
|
+
|
|
73
|
+
# Tier 3: Installed package dynamic version matching
|
|
74
|
+
urls.append(f"https://github.com/uno-km/termux-diffusion/releases/download/v{__version__}/{filename}")
|
|
68
75
|
return urls
|
|
69
76
|
|
|
70
77
|
|
|
@@ -79,7 +86,8 @@ def activate_binary(bin_dir: Path, target_name: str) -> Path:
|
|
|
79
86
|
allowed = {
|
|
80
87
|
"sd-cli-cpu",
|
|
81
88
|
"sd-cli-source-cpu",
|
|
82
|
-
"sd-cli"
|
|
89
|
+
"sd-cli",
|
|
90
|
+
"sd-cli-vulkan",
|
|
83
91
|
}
|
|
84
92
|
if target_name not in allowed:
|
|
85
93
|
raise ValueError(f"Unauthorized activation target: {target_name}")
|
|
@@ -201,59 +209,6 @@ def fetch_prebuilt_binary(install_mode: str = "prebuilt-first") -> Optional[Path
|
|
|
201
209
|
return None
|
|
202
210
|
|
|
203
211
|
|
|
204
|
-
def get_engine_bin_dir() -> Path:
|
|
205
|
-
"""Return directory where compiled termux-diffusion binaries reside ($PREFIX/bin SSOT)."""
|
|
206
|
-
prefix = os.environ.get("PREFIX") or "/data/data/com.termux/files/usr"
|
|
207
|
-
if is_android_termux() or os.path.exists(prefix):
|
|
208
|
-
bin_dir = Path(prefix) / "bin"
|
|
209
|
-
else:
|
|
210
|
-
bin_dir = get_default_cache_dir() / "bin"
|
|
211
|
-
bin_dir.mkdir(parents=True, exist_ok=True)
|
|
212
|
-
return bin_dir
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def get_engine_lib_dir() -> Path:
|
|
216
|
-
"""Return directory where native shared libraries reside ($PREFIX/lib SSOT)."""
|
|
217
|
-
prefix = os.environ.get("PREFIX") or "/data/data/com.termux/files/usr"
|
|
218
|
-
if is_android_termux() or os.path.exists(prefix):
|
|
219
|
-
lib_dir = Path(prefix) / "lib"
|
|
220
|
-
else:
|
|
221
|
-
lib_dir = get_default_cache_dir() / "lib"
|
|
222
|
-
lib_dir.mkdir(parents=True, exist_ok=True)
|
|
223
|
-
return lib_dir
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
def locate_sd_cli(backend: Optional[str] = None) -> Optional[Path]:
|
|
227
|
-
"""Locate the compiled sd-cli executable binary across standard locations."""
|
|
228
|
-
bin_dir = get_engine_bin_dir()
|
|
229
|
-
candidates = ("sd-cli-cpu", "sd-cli-source-cpu", "sd-cli", "sd-cli.exe", "sd", "sd.exe")
|
|
230
|
-
|
|
231
|
-
for fname in candidates:
|
|
232
|
-
cached_bin = bin_dir / fname
|
|
233
|
-
if cached_bin.is_file() and (os.access(cached_bin, os.X_OK) or os.name == "nt"):
|
|
234
|
-
return cached_bin.resolve()
|
|
235
|
-
|
|
236
|
-
# 2. System PATH
|
|
237
|
-
for name in ("sd-cli", "sd-cli.exe", "sd", "sd.exe"):
|
|
238
|
-
which_path = shutil.which(name)
|
|
239
|
-
if which_path and (os.access(which_path, os.X_OK) or os.name == "nt"):
|
|
240
|
-
return Path(which_path).resolve()
|
|
241
|
-
|
|
242
|
-
# 3. Termux prefix bin
|
|
243
|
-
for name in ("sd-cli", "sd"):
|
|
244
|
-
termux_bin = Path(TERMUX_PREFIX) / "bin" / name
|
|
245
|
-
if termux_bin.is_file() and (os.access(termux_bin, os.X_OK) or os.name == "nt"):
|
|
246
|
-
return termux_bin.resolve()
|
|
247
|
-
|
|
248
|
-
# 4. Standard user local binary paths
|
|
249
|
-
for name in ("sd-cli", "sd-cli.exe"):
|
|
250
|
-
local_bin = Path(os.path.expanduser(f"~/.local/bin/{name}"))
|
|
251
|
-
if local_bin.is_file() and (os.access(local_bin, os.X_OK) or os.name == "nt"):
|
|
252
|
-
return local_bin.resolve()
|
|
253
|
-
|
|
254
|
-
return None
|
|
255
|
-
|
|
256
|
-
|
|
257
212
|
def provision_engine(
|
|
258
213
|
force: bool = False,
|
|
259
214
|
install_mode: str = "prebuilt-first",
|
|
@@ -474,98 +429,6 @@ def provision_engine(
|
|
|
474
429
|
return target_bin.resolve()
|
|
475
430
|
|
|
476
431
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
print("[Doctor] [termux-diffusion] Pre-flight Diagnostic Doctor")
|
|
481
|
-
print("=" * 65)
|
|
482
|
-
|
|
483
|
-
all_passed = True
|
|
484
|
-
|
|
485
|
-
# 1. Platform Check
|
|
486
|
-
is_termux = is_android_termux()
|
|
487
|
-
arm_arch = is_arm64()
|
|
488
|
-
print(f"1. Platform: {'Android Termux [OK]' if is_termux else 'Non-Termux Host (Emulation Mode) [INFO]'}")
|
|
489
|
-
print(f"2. Architecture: {'ARM64 / aarch64 [OK]' if arm_arch else f'Host {sys.platform} ({os.name}) [INFO]'}")
|
|
490
|
-
|
|
491
|
-
# 3. Memory & RAM Plus
|
|
492
|
-
mem = get_memory_info()
|
|
493
|
-
safe, msg = check_memory_safety(required_mb=1200)
|
|
494
|
-
print(f"3. System Memory: {mem['mem_total_mb']}MB RAM + {mem['swap_total_mb']}MB Swap ({'Safe [OK]' if safe else 'Warning [WARN]'})")
|
|
495
|
-
if not safe:
|
|
496
|
-
print(f" -> {msg}")
|
|
497
|
-
|
|
498
|
-
# 4. Storage & Samsung Gallery
|
|
499
|
-
storage_ok = os.path.exists(os.path.expanduser("~/storage"))
|
|
500
|
-
print(f"4. Android Storage Permission: {'Configured [OK]' if storage_ok else 'Missing [WARN] (Run termux-setup-storage)'}")
|
|
501
|
-
|
|
502
|
-
# 5. Compiler Toolchain
|
|
503
|
-
clang_ok = bool(shutil.which("clang") or shutil.which("gcc") or shutil.which("clang++"))
|
|
504
|
-
cmake_ok = bool(shutil.which("cmake"))
|
|
505
|
-
git_ok = bool(shutil.which("git"))
|
|
506
|
-
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]'})")
|
|
507
|
-
if not (clang_ok and cmake_ok and git_ok) and is_termux:
|
|
508
|
-
all_passed = False
|
|
509
|
-
print(" -> Run: pkg install clang cmake git termux-api -y")
|
|
510
|
-
|
|
511
|
-
# 6. Engine Binary
|
|
512
|
-
engine = locate_sd_cli()
|
|
513
|
-
print(f"6. Native C++ Engine (sd-cli): {str(engine) + ' [OK]' if engine else 'Not Provisioned [FAIL] (Run termux-diffusion-install)'}")
|
|
514
|
-
if not engine:
|
|
515
|
-
all_passed = False
|
|
516
|
-
|
|
517
|
-
# 7. Model Cache Status
|
|
518
|
-
from .hub import list_cached_models
|
|
519
|
-
cached = list_cached_models()
|
|
520
|
-
print(f"7. Cached GGUF Models: {len(cached)} model(s) available locally.")
|
|
521
|
-
for m in cached:
|
|
522
|
-
valid_tag = " [GGUF Valid [OK]]" if m.get("is_valid_gguf") else " [Header [WARN]]"
|
|
523
|
-
print(f" -> {m['name']} ({m['size_mb']} MB){valid_tag}")
|
|
524
|
-
|
|
525
|
-
# 8. Hardware Acceleration (GPU / NPU / TPU / Vulkan / OpenCL)
|
|
526
|
-
try:
|
|
527
|
-
from ameva_runtime.vulkan.adapters import find_system_vulkan_driver_dir, DiffusionAdapter
|
|
528
|
-
discovered_vulkan = find_system_vulkan_driver_dir()
|
|
529
|
-
except ImportError:
|
|
530
|
-
discovered_vulkan = None
|
|
531
|
-
|
|
532
|
-
from .hardware import detect_hardware_profile, format_hardware_report
|
|
533
|
-
hw = detect_hardware_profile()
|
|
534
|
-
print(f"8. Hardware Acceleration Profile:")
|
|
535
|
-
print(f" SoC: {hw.soc_name}, GPU Architecture: {hw.gpu_name}")
|
|
536
|
-
print(f" GPU Vulkan: {'Available [OK]' if (hw.vulkan_available or discovered_vulkan) else 'Not Found [WARN]'}")
|
|
537
|
-
if discovered_vulkan:
|
|
538
|
-
print(f" -> Vulkan Driver (ameva-runtime): {discovered_vulkan}")
|
|
539
|
-
elif hw.vulkan_driver:
|
|
540
|
-
print(f" -> Vulkan Driver: {hw.vulkan_driver.library_path}")
|
|
541
|
-
print(f" GPU OpenCL: {'Available [OK]' if hw.opencl_available else 'Not Found [WARN]'}")
|
|
542
|
-
if hw.opencl_driver:
|
|
543
|
-
print(f" -> OpenCL Driver: {hw.opencl_driver.library_path}")
|
|
544
|
-
if hw.npu_profile and hw.npu_profile.available:
|
|
545
|
-
print(f" NPU / TPU Hardware: Detected [INFO]")
|
|
546
|
-
print(f" -> Architecture: {hw.npu_profile.dsp_architecture} ({hw.npu_profile.tops_rating} TOPS)")
|
|
547
|
-
print(f" -> Driver: {hw.npu_profile.driver_library}")
|
|
548
|
-
print(f" -> Runtime: Native QNN C++ execution scheduled for v2.0")
|
|
549
|
-
else:
|
|
550
|
-
print(f" NPU / TPU Hardware: Not Detected [INFO] (GPU Vulkan & CPU NEON Active)")
|
|
551
|
-
print(f" CPU ISA SIMD: DotProd={'[OK]' if hw.has_dotprod else '[FAIL]'} "
|
|
552
|
-
f"FP16={'[OK]' if hw.has_fp16 else '[FAIL]'} "
|
|
553
|
-
f"I8MM={'[OK]' if hw.has_i8mm else '[FAIL]'} "
|
|
554
|
-
f"SVE={'[OK]' if hw.has_sve else '[FAIL]'}")
|
|
555
|
-
print(f" Active Compute Pipeline: {hw.recommended_backend.value.upper()} "
|
|
556
|
-
f"(Offload Layers: {hw.recommended_ngl})")
|
|
557
|
-
|
|
558
|
-
# 9. Android 12+ Background Stability Guard (Phantom Process Killer)
|
|
559
|
-
if is_termux:
|
|
560
|
-
print("9. Android 12+ Background Guard:")
|
|
561
|
-
print(" -> Tip: If generation crashes when Termux is in background, enable")
|
|
562
|
-
print(" 'Developer Options > Disable child process restrictions'")
|
|
563
|
-
print(" or run: adb shell \"/system/bin/device_config put activity_manager max_phantom_processes 2147483647\"")
|
|
564
|
-
|
|
565
|
-
print("=" * 65)
|
|
566
|
-
if all_passed:
|
|
567
|
-
print("All core diagnostics passed. System is ready for AI image generation.")
|
|
568
|
-
else:
|
|
569
|
-
print("Some diagnostics need attention. Run 'termux-diffusion-install' to resolve.")
|
|
570
|
-
print("=" * 65)
|
|
571
|
-
return all_passed
|
|
432
|
+
# Re-export diagnostic utilities from .doctor for backward compatibility
|
|
433
|
+
from .doctor import run_doctor
|
|
434
|
+
|
|
@@ -19,11 +19,19 @@ TERMUX_PREFIX = os.environ.get("PREFIX") or "/data/data/com.termux/files/usr"
|
|
|
19
19
|
TERMUX_HOME = os.environ.get("HOME") or "/data/data/com.termux/files/home"
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def get_clean_execution_env(
|
|
22
|
+
def get_clean_execution_env(
|
|
23
|
+
base_env: Optional[Dict[str, str]] = None,
|
|
24
|
+
backend: str = "cpu",
|
|
25
|
+
) -> Dict[str, str]:
|
|
23
26
|
"""Construct clean execution environment for Termux binaries with proper library search paths.
|
|
24
27
|
|
|
25
28
|
Guarantees $PREFIX, $PREFIX/lib, and companion directories are correctly populated
|
|
26
29
|
while preventing Android system library collision (/system/, /vendor/, etc.).
|
|
30
|
+
|
|
31
|
+
Zero-Collision Rule:
|
|
32
|
+
For native Bionic Vulkan binaries (backend in ('vulkan', 'gpu')), Termux $PREFIX/lib is
|
|
33
|
+
strictly excluded from LD_LIBRARY_PATH to eliminate symbol collisions with Android Bionic
|
|
34
|
+
system libraries (/system/lib64/libunwindstack.so, etc.).
|
|
27
35
|
"""
|
|
28
36
|
env = (base_env or os.environ).copy()
|
|
29
37
|
prefix = os.environ.get("PREFIX") or "/data/data/com.termux/files/usr"
|
|
@@ -36,16 +44,28 @@ def get_clean_execution_env(base_env: Optional[Dict[str, str]] = None) -> Dict[s
|
|
|
36
44
|
if bin_dir not in cur_path.split(":"):
|
|
37
45
|
env["PATH"] = f"{bin_dir}:{cur_path}" if cur_path else bin_dir
|
|
38
46
|
|
|
47
|
+
req_backend = str(backend or "cpu").strip().lower()
|
|
48
|
+
is_vulkan_route = req_backend in ("vulkan", "gpu")
|
|
49
|
+
|
|
39
50
|
forbidden_prefixes = ("/system/", "/vendor/", "/apex/", "/system_ext/", "/odm/", "/product/")
|
|
40
51
|
cur_ld = env.get("LD_LIBRARY_PATH", "")
|
|
41
52
|
existing_parts = [p for p in cur_ld.split(":") if p and not any(p.startswith(fp) for fp in forbidden_prefixes)]
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
if is_vulkan_route:
|
|
55
|
+
# Strictly isolate Vulkan binaries from Termux /usr/lib to prevent libc++ ABI crash
|
|
56
|
+
lib_candidates = [
|
|
57
|
+
str(Path.home() / ".local" / "share" / "ameva" / "current" / "diffusion"),
|
|
58
|
+
str(Path.home() / ".cache" / "termux-diffusion" / "lib"),
|
|
59
|
+
]
|
|
60
|
+
# Filter out prefix/lib from existing parts for Vulkan
|
|
61
|
+
existing_parts = [p for p in existing_parts if not p.startswith(str(Path(prefix) / "lib"))]
|
|
62
|
+
else:
|
|
63
|
+
lib_candidates = [
|
|
64
|
+
str(Path(prefix) / "lib"),
|
|
65
|
+
str(Path.home() / ".cache" / "termux-diffusion" / "lib"),
|
|
66
|
+
str(Path.home() / ".cache" / "termux-diffusion" / "staging" / "lib"),
|
|
67
|
+
str(Path.home() / ".local" / "lib"),
|
|
68
|
+
]
|
|
49
69
|
|
|
50
70
|
merged: List[str] = []
|
|
51
71
|
for d in lib_candidates + existing_parts:
|
|
@@ -54,6 +74,8 @@ def get_clean_execution_env(base_env: Optional[Dict[str, str]] = None) -> Dict[s
|
|
|
54
74
|
|
|
55
75
|
if merged:
|
|
56
76
|
env["LD_LIBRARY_PATH"] = ":".join(merged)
|
|
77
|
+
elif is_vulkan_route and "LD_LIBRARY_PATH" in env:
|
|
78
|
+
env.pop("LD_LIBRARY_PATH", None)
|
|
57
79
|
|
|
58
80
|
# Detect libegl_shim.so for Android Mali/Samsung GOS Vulkan HAL compatibility
|
|
59
81
|
shim_candidates = [
|
|
@@ -72,7 +72,7 @@ def run_binary_self_test(
|
|
|
72
72
|
|
|
73
73
|
# Build environment with clean Termux companion library search paths
|
|
74
74
|
from .platform import get_clean_execution_env
|
|
75
|
-
env = get_clean_execution_env()
|
|
75
|
+
env = get_clean_execution_env(backend=expected_backend)
|
|
76
76
|
|
|
77
77
|
# --------------------------------------------------------------------------
|
|
78
78
|
# Stage 1: Load Test (sd-cli --help)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: termux-diffusion
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.0
|
|
4
4
|
Summary: Pure CPU On-Device Stable Diffusion Runtime for Android Termux (ARM64 Bionic NEON SIMD)
|
|
5
5
|
Home-page: https://github.com/uno-km/termux-diffusion
|
|
6
6
|
Author: uno-km (AMEVA Foundation)
|
|
@@ -9,7 +9,9 @@ termux_diffusion/_version.py
|
|
|
9
9
|
termux_diffusion/adapter.py
|
|
10
10
|
termux_diffusion/cli.py
|
|
11
11
|
termux_diffusion/core.py
|
|
12
|
+
termux_diffusion/doctor.py
|
|
12
13
|
termux_diffusion/downloader.py
|
|
14
|
+
termux_diffusion/engine.py
|
|
13
15
|
termux_diffusion/exceptions.py
|
|
14
16
|
termux_diffusion/hardware.py
|
|
15
17
|
termux_diffusion/hub.py
|
|
@@ -33,6 +35,8 @@ termux_diffusion/data/bootstrap-manifest.json
|
|
|
33
35
|
termux_diffusion/data/release-public-keys.json
|
|
34
36
|
termux_diffusion/data/validated-vulkan-profiles.json
|
|
35
37
|
tests/test_core.py
|
|
38
|
+
tests/test_doctor.py
|
|
39
|
+
tests/test_engine.py
|
|
36
40
|
tests/test_hardware.py
|
|
37
41
|
tests/test_hub.py
|
|
38
42
|
tests/test_installer.py
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Unit tests for pre-flight doctor diagnostics in termux-diffusion."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
from termux_diffusion.doctor import run_doctor
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_doctor_execution(capsys):
|
|
8
|
+
res = run_doctor()
|
|
9
|
+
captured = capsys.readouterr()
|
|
10
|
+
assert "Pre-flight Diagnostic Doctor" in captured.out
|
|
11
|
+
assert "Platform:" in captured.out
|
|
12
|
+
assert "Native C++ Engine" in captured.out
|
|
13
|
+
assert isinstance(res, bool)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Unit tests for DiffusionEngine, backend-specific binary locator, and Zero-Silent-Fallback rules."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from unittest.mock import patch, MagicMock
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
from termux_diffusion.engine import (
|
|
9
|
+
DiffusionEngine,
|
|
10
|
+
DiffusionRuntime,
|
|
11
|
+
get_binary_path,
|
|
12
|
+
locate_sd_cli,
|
|
13
|
+
get_engine_bin_dir,
|
|
14
|
+
get_engine_lib_dir,
|
|
15
|
+
load,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_engine_directories():
|
|
20
|
+
bin_dir = get_engine_bin_dir()
|
|
21
|
+
lib_dir = get_engine_lib_dir()
|
|
22
|
+
assert isinstance(bin_dir, Path)
|
|
23
|
+
assert isinstance(lib_dir, Path)
|
|
24
|
+
assert bin_dir.is_dir()
|
|
25
|
+
assert lib_dir.is_dir()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_get_binary_path_cpu_returns_path_or_none():
|
|
29
|
+
res = get_binary_path(backend="cpu")
|
|
30
|
+
assert res is None or isinstance(res, Path)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_zero_silent_fallback_vulkan_never_returns_cpu_binary(tmp_path):
|
|
34
|
+
"""Zero-Silent-Fallback Core Test:
|
|
35
|
+
When backend is 'vulkan' or 'gpu', but only CPU binary (sd-cli) exists,
|
|
36
|
+
get_binary_path MUST return None and NEVER fall back to the CPU binary."""
|
|
37
|
+
mock_bin_dir = tmp_path / "bin"
|
|
38
|
+
mock_bin_dir.mkdir(parents=True)
|
|
39
|
+
cpu_bin = mock_bin_dir / "sd-cli"
|
|
40
|
+
cpu_bin.touch(mode=0o755)
|
|
41
|
+
|
|
42
|
+
with patch("termux_diffusion.engine.get_engine_bin_dir", return_value=mock_bin_dir), \
|
|
43
|
+
patch("shutil.which", return_value=None):
|
|
44
|
+
vk_result = get_binary_path(backend="vulkan")
|
|
45
|
+
assert vk_result is None, "Vulkan backend must return None when sd-cli-vulkan is missing, never fallback to sd-cli"
|
|
46
|
+
|
|
47
|
+
gpu_result = get_binary_path(backend="gpu")
|
|
48
|
+
assert gpu_result is None, "GPU backend must return None when sd-cli-vulkan is missing"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_vulkan_binary_located_when_present(tmp_path):
|
|
52
|
+
"""Verify that when sd-cli-vulkan is present, it is resolved correctly."""
|
|
53
|
+
mock_bin_dir = tmp_path / "bin"
|
|
54
|
+
mock_bin_dir.mkdir(parents=True)
|
|
55
|
+
vk_bin = mock_bin_dir / "sd-cli-vulkan"
|
|
56
|
+
vk_bin.touch(mode=0o755)
|
|
57
|
+
|
|
58
|
+
with patch("termux_diffusion.engine.get_engine_bin_dir", return_value=mock_bin_dir), \
|
|
59
|
+
patch("termux_diffusion.engine.TERMUX_PREFIX", str(tmp_path)):
|
|
60
|
+
res = get_binary_path(backend="vulkan")
|
|
61
|
+
assert res is not None
|
|
62
|
+
assert res.name == "sd-cli-vulkan"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_locate_sd_cli_backward_compatibility():
|
|
66
|
+
res = locate_sd_cli("cpu")
|
|
67
|
+
assert res is None or isinstance(res, Path)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_diffusion_engine_initialization_and_methods():
|
|
71
|
+
engine = DiffusionEngine(model="realistic", device="cpu", width=256, height=256)
|
|
72
|
+
assert engine.model == "realistic"
|
|
73
|
+
assert engine.device == "cpu"
|
|
74
|
+
assert engine.requested_backend == "cpu"
|
|
75
|
+
assert engine.kwargs == {"width": 256, "height": 256}
|
|
76
|
+
|
|
77
|
+
# Factory function load()
|
|
78
|
+
loaded_engine = load(model="anime", device="vulkan")
|
|
79
|
+
assert isinstance(loaded_engine, DiffusionEngine)
|
|
80
|
+
assert loaded_engine.model == "anime"
|
|
81
|
+
assert loaded_engine.device == "vulkan"
|
|
82
|
+
|
|
83
|
+
# Alias DiffusionRuntime
|
|
84
|
+
assert DiffusionRuntime is DiffusionEngine
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_diffusion_engine_generate_delegation():
|
|
88
|
+
engine = DiffusionEngine(model="sdxs", device="cpu")
|
|
89
|
+
with patch("termux_diffusion.core.generate") as mock_gen:
|
|
90
|
+
mock_gen.return_value = Path("/tmp/output.png")
|
|
91
|
+
res = engine.generate("test prompt", steps=4)
|
|
92
|
+
assert res == Path("/tmp/output.png")
|
|
93
|
+
mock_gen.assert_called_once_with(
|
|
94
|
+
prompt="test prompt",
|
|
95
|
+
model="sdxs",
|
|
96
|
+
device="cpu",
|
|
97
|
+
steps=4,
|
|
98
|
+
)
|
|
@@ -162,3 +162,22 @@ def test_adreno_650_gpu_failfast_e003():
|
|
|
162
162
|
assert "AMEVA-DIFFUSION-E003" in str(exc_info.value)
|
|
163
163
|
assert "storageBuffer8BitAccess" in str(exc_info.value)
|
|
164
164
|
|
|
165
|
+
|
|
166
|
+
def test_adreno_830_gpu_supported_with_vulkan_driver():
|
|
167
|
+
"""Verify that Adreno 830 with Vulkan driver resolves to vulkan backend after v2.7.2 shader repair."""
|
|
168
|
+
mock_ameva = MagicMock()
|
|
169
|
+
with patch("termux_diffusion.hardware._resolve_ameva_runtime", return_value=mock_ameva):
|
|
170
|
+
with patch("termux_diffusion.hardware._detect_gpu_name", return_value="Adreno (TM) 830"):
|
|
171
|
+
with patch("termux_diffusion.hardware._detect_soc_name", return_value="SM8750"):
|
|
172
|
+
with patch("termux_diffusion.hardware.detect_hardware_profile") as mock_prof:
|
|
173
|
+
prof = MagicMock()
|
|
174
|
+
prof.vulkan_available = True
|
|
175
|
+
prof.recommended_backend = MagicMock(value="vulkan")
|
|
176
|
+
prof.recommended_ngl = 99
|
|
177
|
+
mock_prof.return_value = prof
|
|
178
|
+
backend, ngl = resolve_device_backend("gpu")
|
|
179
|
+
assert backend == "vulkan"
|
|
180
|
+
assert ngl == 99
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.6.8"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/data/bootstrap-manifest.json
RENAMED
|
File without changes
|
{termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion/data/release-public-keys.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{termux_diffusion-1.6.8 → termux_diffusion-1.7.0}/termux_diffusion.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|