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.
- amv_diffusion-2.1.0.dist-info/METADATA +180 -0
- amv_diffusion-2.1.0.dist-info/RECORD +39 -0
- amv_diffusion-2.1.0.dist-info/WHEEL +5 -0
- amv_diffusion-2.1.0.dist-info/entry_points.txt +7 -0
- amv_diffusion-2.1.0.dist-info/licenses/LICENSE +21 -0
- amv_diffusion-2.1.0.dist-info/top_level.txt +1 -0
- termux_diffusion/__init__.py +185 -0
- termux_diffusion/__main__.py +7 -0
- termux_diffusion/_version.py +3 -0
- termux_diffusion/adapter.py +41 -0
- termux_diffusion/canvas.py +364 -0
- termux_diffusion/cli.py +492 -0
- termux_diffusion/cluster.py +340 -0
- termux_diffusion/control/__init__.py +5 -0
- termux_diffusion/control/component.py +309 -0
- termux_diffusion/control/status.py +15 -0
- termux_diffusion/core.py +2936 -0
- termux_diffusion/data/bootstrap-manifest.json +15 -0
- termux_diffusion/data/prompt_cache/108a7149c4e70550.json +10 -0
- termux_diffusion/data/release-public-keys.json +10 -0
- termux_diffusion/data/validated-vulkan-profiles.json +298 -0
- termux_diffusion/detailer.py +283 -0
- termux_diffusion/doctor.py +128 -0
- termux_diffusion/downloader.py +111 -0
- termux_diffusion/engine.py +226 -0
- termux_diffusion/exceptions.py +158 -0
- termux_diffusion/hardware.py +985 -0
- termux_diffusion/hub.py +686 -0
- termux_diffusion/installer.py +499 -0
- termux_diffusion/locking.py +134 -0
- termux_diffusion/manifest.py +128 -0
- termux_diffusion/npu.py +267 -0
- termux_diffusion/platform.py +533 -0
- termux_diffusion/purger.py +145 -0
- termux_diffusion/py.typed +1 -0
- termux_diffusion/safety.py +150 -0
- termux_diffusion/selftest.py +178 -0
- termux_diffusion/web.py +3370 -0
- termux_diffusion/workflow.py +1345 -0
termux_diffusion/cli.py
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
# Copyright (c) 2026 Uno Kim / AMEVA Foundation. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0 OR Commercial-License
|
|
3
|
+
"""Command-line interface entry points for termux-diffusion."""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
import logging as _logging
|
|
10
|
+
|
|
11
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
12
|
+
try:
|
|
13
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
14
|
+
except Exception as _e:
|
|
15
|
+
_logging.getLogger(__name__).debug("stdout reconfigure failed: %s", _e)
|
|
16
|
+
if hasattr(sys.stderr, "reconfigure"):
|
|
17
|
+
try:
|
|
18
|
+
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
19
|
+
except Exception as _e:
|
|
20
|
+
_logging.getLogger(__name__).debug("stderr reconfigure failed: %s", _e)
|
|
21
|
+
|
|
22
|
+
from .core import generate
|
|
23
|
+
from .hub import clear_cache, download_model, list_cached_models, list_presets
|
|
24
|
+
from .exceptions import TermuxDiffusionError, RuntimeRequiredError, ErrorCode, ExitCode
|
|
25
|
+
from .engine import DiffusionEngine, get_binary_path
|
|
26
|
+
from .doctor import run_doctor
|
|
27
|
+
from .installer import provision_engine
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
31
|
+
"""Main CLI router for termux-diffusion.
|
|
32
|
+
|
|
33
|
+
A GPU request without a usable ameva-runtime ends here with the shared explanation on
|
|
34
|
+
stderr and exit status 69, whichever sub-command raised it.
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
return _main_impl(argv)
|
|
38
|
+
except RuntimeRequiredError as err:
|
|
39
|
+
sys.stderr.write(str(err))
|
|
40
|
+
sys.stderr.flush()
|
|
41
|
+
return ExitCode.RUNTIME_REQUIRED
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _main_impl(argv: Optional[List[str]] = None) -> int:
|
|
45
|
+
"""Main CLI router for termux-diffusion."""
|
|
46
|
+
if argv is None:
|
|
47
|
+
argv = sys.argv[1:]
|
|
48
|
+
|
|
49
|
+
parser = argparse.ArgumentParser(
|
|
50
|
+
prog="termux-diffusion",
|
|
51
|
+
description="Production On-Device AI Image Generation for Android Termux & Samsung Galaxy."
|
|
52
|
+
)
|
|
53
|
+
subparsers = parser.add_subparsers(dest="command", help="Available subcommands")
|
|
54
|
+
|
|
55
|
+
# generate command
|
|
56
|
+
gen_parser = subparsers.add_parser("generate", help="Generate an AI image from a text prompt")
|
|
57
|
+
gen_parser.add_argument("prompt", type=str, help="Text description of image")
|
|
58
|
+
gen_parser.add_argument("-m", "--model", type=str, default="realistic", help="Model preset or .gguf path (default: realistic)")
|
|
59
|
+
gen_parser.add_argument("-n", "--negative", type=str, default=None, help="Negative text prompt guidance")
|
|
60
|
+
gen_parser.add_argument("-d", "--device", type=str, default="cpu", choices=["auto", "gpu", "vulkan", "opencl", "cpu"], help="Computing device: auto, gpu, vulkan, opencl, cpu (default: cpu)")
|
|
61
|
+
gen_parser.add_argument("-b", "--backend", type=str, dest="device", choices=["auto", "gpu", "vulkan", "opencl", "cpu"], help="Alias for --device")
|
|
62
|
+
gen_parser.add_argument("--gpu", action="store_const", const="gpu", dest="device", help="Force GPU hardware acceleration mode")
|
|
63
|
+
gen_parser.add_argument("--cpu", action="store_const", const="cpu", dest="device", help="Force CPU baseline execution mode")
|
|
64
|
+
gen_parser.add_argument("-s", "--steps", type=int, default=None, help="Denoising steps (default determined by preset, e.g. 10)")
|
|
65
|
+
gen_parser.add_argument("-c", "--cfg", type=float, default=None, help="CFG guidance scale")
|
|
66
|
+
gen_parser.add_argument("-g", "--guidance", type=float, default=None, help="Distilled guidance scale (e.g. 3.5 for SDXS)")
|
|
67
|
+
gen_parser.add_argument("-W", "--width", type=int, default=512, help="Image width (default: 512)")
|
|
68
|
+
gen_parser.add_argument("-H", "--height", type=int, default=512, help="Image height (default: 512)")
|
|
69
|
+
gen_parser.add_argument("-t", "--threads", type=int, default=None, help="CPU threads")
|
|
70
|
+
gen_parser.add_argument("--seed", type=int, default=-1, help="RNG seed (-1 for random, 0 to 4294967295)")
|
|
71
|
+
gen_parser.add_argument("-o", "--output", type=str, default=None, help="Output file path")
|
|
72
|
+
gen_parser.add_argument("--schedule", "--scheduler", dest="schedule", type=str, default=None, help="Noise schedule (flux, karras, exponential, ays, gits, discrete, etc.)")
|
|
73
|
+
gen_parser.add_argument("--flow-shift", type=float, default=None, help="Shift value for Flow models like Flux/Z-Image")
|
|
74
|
+
gen_parser.add_argument("--detail-faces", "--face-detailer", action="store_true", default=False, help="Enable Mobile FaceDetailer (auto face crop, local Img2Img, seamless alpha blend)")
|
|
75
|
+
gen_parser.add_argument("--detailer-strength", type=float, default=0.35, help="FaceDetailer denoising strength (default: 0.35)")
|
|
76
|
+
gen_parser.add_argument("--detailer-steps", type=int, default=2, help="FaceDetailer localized sampling steps (default: 2)")
|
|
77
|
+
gen_parser.add_argument("--detailer-padding", type=float, default=1.45, help="FaceDetailer bounding box padding factor (default: 1.45)")
|
|
78
|
+
gen_parser.add_argument("--vae-tiling", action="store_true", help="Enable VAE tiling for ~70%% lower peak memory")
|
|
79
|
+
gen_parser.add_argument("-i", "--init-img", type=str, default=None, help="Source image for Img2Img synthesis")
|
|
80
|
+
gen_parser.add_argument("--strength", type=float, default=None, help="Img2Img denoising strength (0.0 to 1.0, default: 0.75)")
|
|
81
|
+
gen_parser.add_argument("--lora-dir", type=str, default=None, help="Directory path containing LoRA weights")
|
|
82
|
+
gen_parser.add_argument("--clip-skip", type=int, default=None, help="CLIP layers to skip (1 or 2)")
|
|
83
|
+
gen_parser.add_argument("--control-net", type=str, default=None, help="Path to ControlNet model")
|
|
84
|
+
gen_parser.add_argument("--control-image", type=str, default=None, help="Path to ControlNet guide image")
|
|
85
|
+
gen_parser.add_argument("--control-strength", type=float, default=None, help="ControlNet strength (0.0 to 2.0, default: 0.9)")
|
|
86
|
+
gen_parser.add_argument("--taesd", "--tae", "--fast-vae", dest="taesd", nargs="?", const="auto", type=str, default=None, help="Enable Tiny AutoEncoder (TAESD) for ~1s fast decoding. Pass optional path or 'auto'.")
|
|
87
|
+
gen_parser.add_argument("--strict-vulkan", action="store_true", help="Disallow CPU fallback and fail if Vulkan physical GPU discovery fails")
|
|
88
|
+
gen_parser.add_argument("--preset", type=str, default=None, choices=["fast", "balanced", "anime-experimental", "anime_experimental", "z-image-turbo", "z-image", "turbo-6b", "sdxs"], help="Verified production preset (fast, balanced, anime-experimental, z-image-turbo)")
|
|
89
|
+
gen_parser.add_argument("--llm", type=str, default=None, help="Path or preset for LLM text encoder (e.g. Qwen for Z-Image)")
|
|
90
|
+
gen_parser.add_argument("--diffusion-model", type=str, default=None, help="Path or preset for standalone diffusion model")
|
|
91
|
+
gen_parser.add_argument("--vae", type=str, default=None, help="Path or preset for standalone VAE model")
|
|
92
|
+
gen_parser.add_argument("--clip-l", type=str, default=None, help="Path to standalone CLIP text encoder")
|
|
93
|
+
gen_parser.add_argument("--diffusion-fa", action="store_true", default=False, help="Enable Flash Attention in diffusion model")
|
|
94
|
+
gen_parser.add_argument("--offload-to-cpu", action="store_true", default=False, help="Offload idle modules to CPU RAM to minimize peak VRAM")
|
|
95
|
+
gen_parser.add_argument("--clip-on-cpu", action="store_true", default=False, help="Keep CLIP / LLM text encoder on CPU to preserve GPU VRAM")
|
|
96
|
+
gen_parser.add_argument("--vae-on-cpu", action="store_true", default=False, help="Keep VAE decoder on CPU to preserve GPU VRAM")
|
|
97
|
+
gen_parser.add_argument("--mmap", action="store_true", default=True, help="Memory-map model weights (default: True)")
|
|
98
|
+
gen_parser.add_argument("--no-mmap", action="store_false", dest="mmap", help="Disable memory-mapping")
|
|
99
|
+
gen_parser.add_argument("--max-vram", type=str, default=None, help="Maximum VRAM budget in GiB (e.g. '0' for full in-VRAM residency, '1' or 'vulkan0=1' for 1GiB lifeboat streaming, '1.5' for balanced chunk buffer). Auto-scales deterministically by kernel RAM tier if omitted.")
|
|
100
|
+
gen_parser.add_argument("--stream-layers", action="store_true", default=False, help="Enable layer residency prefetch streaming on top of --max-vram (Tier 3 fallback; disabled in Tier 1/2)")
|
|
101
|
+
gen_parser.add_argument("--params-backend", type=str, default=None, help="Parameter backend assignment (e.g. 'diffusion=cpu' or 'disk')")
|
|
102
|
+
gen_parser.add_argument("--runtime-backend", type=str, default=None, dest="runtime_backend", help="Custom runtime backend assignment (e.g. 'clip=cpu,vae=cpu,diffusion=vulkan0')")
|
|
103
|
+
gen_parser.add_argument("--rpc", type=str, default=None, help="Distributed RPC server addresses (e.g. '192.168.0.220:50052,192.168.0.253:50052')")
|
|
104
|
+
gen_parser.add_argument("-ts", "--tensor-split", type=str, default=None, help="Fraction of the model to offload across devices (e.g. '50,50' or '60,40')")
|
|
105
|
+
gen_parser.add_argument("--vae-format", type=str, default=None, help="VAE latent format override (auto, flux, sd3, flux2, wan)")
|
|
106
|
+
gen_parser.add_argument("--no-cache", "--non-cache", "--no-cached", dest="no_cache", action="store_true", default=False, help="Bypass prompt embedding cache and force full real-time text encoder execution")
|
|
107
|
+
gen_parser.add_argument("--progress", "--show-progress", action="store_true", default=False, help="Display real-time interactive terminal progress bar with step/speed/ETA metrics")
|
|
108
|
+
gen_parser.add_argument("--json-progress", "--progress-json", action="store_true", default=False, help="Stream structured NDJSON progress events for IPC and programmatic consumers")
|
|
109
|
+
gen_parser.add_argument("--progress-file", type=str, default=None, help="Path to write atomic JSON progress snapshots for external monitoring")
|
|
110
|
+
gen_parser.add_argument("--sampling-method", type=str, dest="sampler", help=argparse.SUPPRESS)
|
|
111
|
+
gen_parser.add_argument("--cfg-scale", type=float, dest="cfg", help=argparse.SUPPRESS)
|
|
112
|
+
gen_parser.add_argument("--isolate-text-encoder", "--sequential-stages", dest="isolate_text_encoder", action="store_true", default=False, help="Enable 2-stage memory isolation: execute text encoder independently, kill process to recover RAM, then execute DiT inference")
|
|
113
|
+
gen_parser.add_argument("--safe-mode", action="store_true", default=False, help="Preserve SurfaceFlinger 60/120fps UI and background apps (nice +10, VAE Tiling, 90%% GPU duty cycle)")
|
|
114
|
+
gen_parser.add_argument("--keep-apps-alive", action="store_true", default=False, help="Alias for --safe-mode")
|
|
115
|
+
gen_parser.add_argument("--gpu-duty-cycle", type=int, default=None, help="GPU load percentage cap (e.g. 90 for 90%% duty cycle, 10%% idle gap for SurfaceFlinger)")
|
|
116
|
+
# Safe-By-Default & Clean-Room Purger Flags (SCRUM-524)
|
|
117
|
+
gen_parser.add_argument("--kill", dest="kill_threshold", nargs="?", const=50, default=None, type=int, help="Optional clean-room purge: terminate background processes exceeding RSS threshold in MB, excluding whitelist (default: 50MB)")
|
|
118
|
+
gen_parser.add_argument("--kill-threshold", dest="kill_threshold", type=int, help="Explicit memory threshold in MB for --kill clean-room purge")
|
|
119
|
+
gen_parser.add_argument("--swap-allowance", type=str, default="auto", help="Allowed swap headroom limit: 'auto', '0', '1G', '2G' (default: auto)")
|
|
120
|
+
gen_parser.add_argument("--safe-budget", type=float, default=0.95, help="Safe memory budget ratio of MemAvailable (default: 0.95 for 5%% OS margin)")
|
|
121
|
+
gen_parser.add_argument("--full-accel", "--danger-force", dest="full_accel", action="store_true", default=False, help="Bypass all heuristic memory safety rails and run hardware at maximum unconstrained throttle")
|
|
122
|
+
gen_parser.add_argument("--checkpoint-latent", action="store_true", default=False, help="Save intermediate 4D latent tensor to disk immediately upon DiT completion")
|
|
123
|
+
gen_parser.add_argument("--resume-latent", type=str, default=None, help="Bypass DiT and resume from pre-computed .bin latent tensor for VAE decode")
|
|
124
|
+
gen_parser.add_argument("--temporal-chunking", type=int, default=None, help="Optional fast preview: chunk frames into N-frame slices (e.g. 9) to accelerate 3D attention")
|
|
125
|
+
gen_parser.add_argument("--tile-size", type=int, default=None, help="Explicit spatial VAE tile size override in pixels (e.g. 128, 256)")
|
|
126
|
+
gen_parser.add_argument("--temp-tile", type=int, default=None, help="Explicit temporal VAE tile size override in frames (e.g. 2, 4)")
|
|
127
|
+
|
|
128
|
+
# install command
|
|
129
|
+
inst_parser = subparsers.add_parser("install", help="Provision native Bionic CPU engine (sd-cli-cpu & libomp.so)")
|
|
130
|
+
inst_parser.add_argument("-f", "--force", action="store_true", default=False, help="Force re-installation")
|
|
131
|
+
inst_parser.add_argument("--dedicate", action="store_true", default=False, help="Smart inspection mode: preserve AMEVA runtime symlink, auto-upgrade legacy binaries")
|
|
132
|
+
|
|
133
|
+
# doctor command
|
|
134
|
+
subparsers.add_parser("doctor", help="Run 7-tier pre-flight diagnostic checks")
|
|
135
|
+
|
|
136
|
+
# models command
|
|
137
|
+
subparsers.add_parser("models", help="List available model presets and locally cached weights")
|
|
138
|
+
|
|
139
|
+
# download command
|
|
140
|
+
dl_parser = subparsers.add_parser("download", help="Pre-download a model preset or HF repo")
|
|
141
|
+
dl_parser.add_argument("model", type=str, help="Model preset name (e.g. realistic, speed, sdxs, turbo, anime, fast, balanced)")
|
|
142
|
+
|
|
143
|
+
# clear-cache command
|
|
144
|
+
subparsers.add_parser("clear-cache", help="Purge cached model weights to reclaim disk space")
|
|
145
|
+
|
|
146
|
+
# workflow command (ComfyUI DAG execution)
|
|
147
|
+
wf_parser = subparsers.add_parser("workflow", help="Execute ComfyUI workflows (UI & API JSON formats)")
|
|
148
|
+
wf_sub = wf_parser.add_subparsers(dest="workflow_command", help="Workflow subcommands")
|
|
149
|
+
|
|
150
|
+
wf_run = wf_sub.add_parser("run", help="Execute ComfyUI workflow JSON end-to-end")
|
|
151
|
+
wf_run.add_argument("workflow_file", type=str, help="Path to ComfyUI workflow JSON (UI or API format)")
|
|
152
|
+
wf_run.add_argument("-o", "--output-dir", type=str, default=None, help="Directory to save generated outputs")
|
|
153
|
+
wf_run.add_argument("-d", "--device", type=str, default="auto", choices=["auto", "gpu", "vulkan", "opencl", "cpu"], help="Compute device (default: auto)")
|
|
154
|
+
wf_run.add_argument("--force-run", action="store_true", default=False, help="Bypass hardware limits for high-RAM (16GB/24GB+) devices")
|
|
155
|
+
wf_run.add_argument("--progress", action="store_true", default=True, help="Display progress bar")
|
|
156
|
+
wf_run.add_argument("--json-progress", action="store_true", default=False, help="Stream NDJSON progress events for Node.js / IPC")
|
|
157
|
+
|
|
158
|
+
wf_inspect = wf_sub.add_parser("inspect", help="Inspect and validate ComfyUI workflow nodes and execution order")
|
|
159
|
+
wf_inspect.add_argument("workflow_file", type=str, help="Path to ComfyUI workflow JSON")
|
|
160
|
+
|
|
161
|
+
# web command (Mobile Web UI server - AmfyUI)
|
|
162
|
+
web_parser = subparsers.add_parser("web", aliases=["ui", "amfy", "amfyui"], help="Launch AmfyUI Mobile Studio & ComfyUI-compatible DAG Visualizer")
|
|
163
|
+
web_parser.add_argument("-p", "--port", type=int, nargs="?", const=11553, default=11553, help="Web server port (default: 11553)")
|
|
164
|
+
web_parser.add_argument("-H", "--host", type=str, default="0.0.0.0", help="Host interface to bind (default: 0.0.0.0)")
|
|
165
|
+
|
|
166
|
+
# ── AMEVA Component Protocol v1 ─────────────────────────────────────────
|
|
167
|
+
try:
|
|
168
|
+
from ameva_component.cli_support import build_protocol_subcommands
|
|
169
|
+
build_protocol_subcommands(subparsers)
|
|
170
|
+
except ImportError as _proto_err:
|
|
171
|
+
import logging
|
|
172
|
+
logging.getLogger("termux_diffusion.cli").debug(
|
|
173
|
+
"Optional AMEVA component CLI protocol subcommands omitted: %s", _proto_err
|
|
174
|
+
)
|
|
175
|
+
# ────────────────────────────────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
if len(argv) == 0:
|
|
178
|
+
parser.print_help()
|
|
179
|
+
return 0
|
|
180
|
+
|
|
181
|
+
args, unknown_args = parser.parse_known_args(argv)
|
|
182
|
+
|
|
183
|
+
if args.command == "generate":
|
|
184
|
+
if args.seed < -1 or args.seed > 4294967295:
|
|
185
|
+
parser.error("--seed must be between -1 and 4294967295.")
|
|
186
|
+
|
|
187
|
+
model_name = args.model
|
|
188
|
+
steps = args.steps
|
|
189
|
+
cfg = args.cfg
|
|
190
|
+
width = args.width
|
|
191
|
+
height = args.height
|
|
192
|
+
sampler = args.sampler
|
|
193
|
+
device = args.device or "cpu"
|
|
194
|
+
vae_tiling = args.vae_tiling
|
|
195
|
+
|
|
196
|
+
if args.preset:
|
|
197
|
+
p_key = args.preset.replace("_", "-").lower()
|
|
198
|
+
if p_key in ("z-image", "turbo-6b"):
|
|
199
|
+
p_key = "z-image-turbo"
|
|
200
|
+
from .hub import DEFAULT_PRESETS
|
|
201
|
+
if p_key in DEFAULT_PRESETS:
|
|
202
|
+
p_cfg = DEFAULT_PRESETS[p_key]
|
|
203
|
+
if "alias" in p_cfg and p_cfg["alias"] in DEFAULT_PRESETS:
|
|
204
|
+
p_cfg = DEFAULT_PRESETS[p_cfg["alias"]]
|
|
205
|
+
model_name = p_cfg.get("alias", p_key)
|
|
206
|
+
if steps is None:
|
|
207
|
+
steps = p_cfg.get("default_steps")
|
|
208
|
+
if cfg is None:
|
|
209
|
+
cfg = p_cfg.get("default_cfg")
|
|
210
|
+
if width == 512 and "default_width" in p_cfg:
|
|
211
|
+
width = p_cfg["default_width"]
|
|
212
|
+
if height == 512 and "default_height" in p_cfg:
|
|
213
|
+
height = p_cfg["default_height"]
|
|
214
|
+
if sampler is None:
|
|
215
|
+
sampler = p_cfg.get("default_sampler")
|
|
216
|
+
if device in ("auto", "cpu") and "default_device" in p_cfg:
|
|
217
|
+
device = p_cfg["default_device"]
|
|
218
|
+
if not vae_tiling and p_cfg.get("default_vae_tiling"):
|
|
219
|
+
vae_tiling = True
|
|
220
|
+
# Auto-bind DiT multi-component weights
|
|
221
|
+
if not args.diffusion_model and "default_diffusion_model" in p_cfg:
|
|
222
|
+
args.diffusion_model = p_cfg["default_diffusion_model"]
|
|
223
|
+
elif not args.diffusion_model and p_key == "z-image-turbo":
|
|
224
|
+
args.diffusion_model = p_cfg.get("filename", "z_image_turbo-Q2_K.gguf")
|
|
225
|
+
if not args.llm and "default_llm" in p_cfg:
|
|
226
|
+
args.llm = p_cfg["default_llm"]
|
|
227
|
+
if not args.vae and not args.taesd and "default_vae" in p_cfg:
|
|
228
|
+
args.vae = p_cfg["default_vae"]
|
|
229
|
+
if not args.vae_format and "default_vae_format" in p_cfg:
|
|
230
|
+
args.vae_format = p_cfg["default_vae_format"]
|
|
231
|
+
|
|
232
|
+
# Safe-By-Default & Clean-Room Purger Execution (SCRUM-524)
|
|
233
|
+
if getattr(args, "kill_threshold", None) is not None:
|
|
234
|
+
from .purger import purge_idle_processes
|
|
235
|
+
purge_summary = purge_idle_processes(threshold_mb=float(args.kill_threshold))
|
|
236
|
+
print(f"[Purger] Clean-room purge: reclaimed {purge_summary.total_reclaimed_mb:.1f} MB across {len(purge_summary.killed_processes)} processes.")
|
|
237
|
+
|
|
238
|
+
from .safety import evaluate_safe_memory_budget
|
|
239
|
+
mem_assessment = evaluate_safe_memory_budget(
|
|
240
|
+
safe_margin=getattr(args, "safe_budget", 0.95),
|
|
241
|
+
swap_allowance=getattr(args, "swap_allowance", "auto")
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
# Safe-By-Default: 타일링 자동 인젝션 (풀악셀 명시적 지정 시에만 생략 가능)
|
|
245
|
+
if not getattr(args, "full_accel", False) and not vae_tiling:
|
|
246
|
+
vae_tiling = True
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
res = generate(
|
|
250
|
+
prompt=args.prompt,
|
|
251
|
+
model=model_name,
|
|
252
|
+
negative_prompt=args.negative,
|
|
253
|
+
device=device,
|
|
254
|
+
steps=steps,
|
|
255
|
+
cfg_scale=cfg,
|
|
256
|
+
guidance=args.guidance,
|
|
257
|
+
width=width,
|
|
258
|
+
height=height,
|
|
259
|
+
threads=args.threads,
|
|
260
|
+
seed=args.seed,
|
|
261
|
+
output=args.output,
|
|
262
|
+
sampling_method=sampler,
|
|
263
|
+
schedule=args.schedule,
|
|
264
|
+
vae_tiling=vae_tiling,
|
|
265
|
+
init_img=args.init_img,
|
|
266
|
+
strength=args.strength,
|
|
267
|
+
lora_dir=args.lora_dir,
|
|
268
|
+
clip_skip=args.clip_skip,
|
|
269
|
+
control_net=args.control_net,
|
|
270
|
+
control_image=args.control_image,
|
|
271
|
+
control_strength=args.control_strength,
|
|
272
|
+
taesd=args.taesd,
|
|
273
|
+
llm=args.llm,
|
|
274
|
+
diffusion_model=args.diffusion_model,
|
|
275
|
+
vae=args.vae,
|
|
276
|
+
clip_l=args.clip_l,
|
|
277
|
+
diffusion_fa=args.diffusion_fa,
|
|
278
|
+
offload_to_cpu=args.offload_to_cpu,
|
|
279
|
+
clip_on_cpu=args.clip_on_cpu,
|
|
280
|
+
vae_on_cpu=args.vae_on_cpu,
|
|
281
|
+
mmap=args.mmap,
|
|
282
|
+
max_vram=args.max_vram,
|
|
283
|
+
stream_layers=args.stream_layers,
|
|
284
|
+
params_backend=args.params_backend,
|
|
285
|
+
custom_backend=args.runtime_backend,
|
|
286
|
+
rpc=args.rpc,
|
|
287
|
+
tensor_split=args.tensor_split,
|
|
288
|
+
vae_format=args.vae_format,
|
|
289
|
+
flow_shift=getattr(args, "flow_shift", None),
|
|
290
|
+
detail_faces=getattr(args, "detail_faces", False),
|
|
291
|
+
detailer_strength=getattr(args, "detailer_strength", 0.35),
|
|
292
|
+
detailer_steps=getattr(args, "detailer_steps", 2),
|
|
293
|
+
detailer_padding=getattr(args, "detailer_padding", 1.45),
|
|
294
|
+
strict_vulkan=args.strict_vulkan,
|
|
295
|
+
no_cache=getattr(args, "no_cache", False),
|
|
296
|
+
progress=getattr(args, "progress", False),
|
|
297
|
+
json_progress=getattr(args, "json_progress", False),
|
|
298
|
+
progress_file=getattr(args, "progress_file", None),
|
|
299
|
+
extra_args=unknown_args if unknown_args else None,
|
|
300
|
+
isolate_text_encoder=getattr(args, "isolate_text_encoder", False),
|
|
301
|
+
safe_mode=getattr(args, "safe_mode", False),
|
|
302
|
+
keep_apps_alive=getattr(args, "keep_apps_alive", False),
|
|
303
|
+
gpu_duty_cycle=getattr(args, "gpu_duty_cycle", None),
|
|
304
|
+
checkpoint_latent=getattr(args, "checkpoint_latent", False),
|
|
305
|
+
resume_latent=getattr(args, "resume_latent", None),
|
|
306
|
+
temporal_chunking=getattr(args, "temporal_chunking", None),
|
|
307
|
+
tile_size=getattr(args, "tile_size", None),
|
|
308
|
+
temp_tile=getattr(args, "temp_tile", None),
|
|
309
|
+
full_accel=getattr(args, "full_accel", False),
|
|
310
|
+
kill_threshold=getattr(args, "kill_threshold", None),
|
|
311
|
+
swap_allowance=getattr(args, "swap_allowance", "auto"),
|
|
312
|
+
safe_budget=getattr(args, "safe_budget", 0.95),
|
|
313
|
+
auto_provision=True
|
|
314
|
+
)
|
|
315
|
+
return 0 if res.path.exists() else 1
|
|
316
|
+
except FileNotFoundError as fnf_err:
|
|
317
|
+
print(f"[ERROR] [FILE_NOT_FOUND]: {fnf_err}", file=sys.stderr)
|
|
318
|
+
return ExitCode.CLI_ERROR
|
|
319
|
+
except ValueError as val_err:
|
|
320
|
+
print(f"[ERROR] [INVALID_ARGUMENT]: {val_err}", file=sys.stderr)
|
|
321
|
+
return ExitCode.CLI_ERROR
|
|
322
|
+
except RuntimeRequiredError:
|
|
323
|
+
raise
|
|
324
|
+
except TermuxDiffusionError as td_err:
|
|
325
|
+
print(f"[ERROR] [{td_err.code}]: {td_err}", file=sys.stderr)
|
|
326
|
+
return ExitCode.BUILD_ERROR
|
|
327
|
+
|
|
328
|
+
elif args.command == "install":
|
|
329
|
+
provision_engine(force=args.force, dedicate=getattr(args, "dedicate", False))
|
|
330
|
+
return 0
|
|
331
|
+
|
|
332
|
+
elif args.command == "doctor":
|
|
333
|
+
ok = run_doctor()
|
|
334
|
+
return 0 if ok else 1
|
|
335
|
+
|
|
336
|
+
elif args.command == "models":
|
|
337
|
+
print("\n--- [Presets] Available Presets ---")
|
|
338
|
+
for k, v in list_presets().items():
|
|
339
|
+
desc = v.get('description', f"Alias to {v.get('alias')}")
|
|
340
|
+
size = f" ({v.get('size_mb')}MB)" if v.get('size_mb') else ""
|
|
341
|
+
print(f" - {k:15} : {desc}{size}")
|
|
342
|
+
print("\n--- [Models] Locally Cached Models ---")
|
|
343
|
+
cached = list_cached_models()
|
|
344
|
+
if not cached:
|
|
345
|
+
print(" (No models cached yet. Run 'termux-diffusion download <model>' or generate)")
|
|
346
|
+
for m in cached:
|
|
347
|
+
print(f" - {m['name']:25} [{m['size_mb']} MB] -> {m['path']}")
|
|
348
|
+
print()
|
|
349
|
+
return 0
|
|
350
|
+
|
|
351
|
+
elif args.command == "download":
|
|
352
|
+
download_model(args.model)
|
|
353
|
+
return 0
|
|
354
|
+
|
|
355
|
+
elif args.command == "clear-cache":
|
|
356
|
+
removed = clear_cache()
|
|
357
|
+
print(f"[Clean] Removed {removed} cached model files.")
|
|
358
|
+
return 0
|
|
359
|
+
|
|
360
|
+
elif args.command == "workflow":
|
|
361
|
+
from .workflow import WorkflowExecutor, WorkflowParser, DAGEngine, WorkflowError
|
|
362
|
+
if getattr(args, "workflow_command", None) == "run":
|
|
363
|
+
executor = WorkflowExecutor()
|
|
364
|
+
try:
|
|
365
|
+
res = executor.run(
|
|
366
|
+
workflow=args.workflow_file,
|
|
367
|
+
output_dir=args.output_dir,
|
|
368
|
+
device=args.device,
|
|
369
|
+
force_run=args.force_run,
|
|
370
|
+
progress=args.progress,
|
|
371
|
+
json_progress=args.json_progress
|
|
372
|
+
)
|
|
373
|
+
print(f"[Workflow] Completed in {res.elapsed_seconds:.2f}s! Generated {len(res.output_images)} image(s):")
|
|
374
|
+
for img in res.output_images:
|
|
375
|
+
print(f" -> {img}")
|
|
376
|
+
return 0 if res.success else 1
|
|
377
|
+
except WorkflowError as wf_err:
|
|
378
|
+
print(f"[ERROR] [WORKFLOW_FAILED]: {wf_err}", file=sys.stderr)
|
|
379
|
+
return 1
|
|
380
|
+
except FileNotFoundError as fnf:
|
|
381
|
+
print(f"[ERROR] [FILE_NOT_FOUND]: {fnf}", file=sys.stderr)
|
|
382
|
+
return 1
|
|
383
|
+
elif getattr(args, "workflow_command", None) == "inspect":
|
|
384
|
+
try:
|
|
385
|
+
nodes = WorkflowParser.parse(args.workflow_file)
|
|
386
|
+
order = DAGEngine.topological_sort(nodes)
|
|
387
|
+
raw_json = WorkflowParser.load_json(args.workflow_file)
|
|
388
|
+
fmt = WorkflowParser.detect_format(raw_json)
|
|
389
|
+
print(f"\n--- [Workflow Inspector] ---")
|
|
390
|
+
print(f"File: {args.workflow_file}")
|
|
391
|
+
print(f"Detected Format: {fmt}")
|
|
392
|
+
print(f"Total Nodes: {len(nodes)}")
|
|
393
|
+
print(f"Execution Order ({len(order)} steps):")
|
|
394
|
+
for idx, nid in enumerate(order, 1):
|
|
395
|
+
n = nodes[nid]
|
|
396
|
+
title_str = f" ('{n.title}')" if n.title else ""
|
|
397
|
+
print(f" {idx}. [Node #{nid}] {n.class_type}{title_str}")
|
|
398
|
+
print()
|
|
399
|
+
return 0
|
|
400
|
+
except WorkflowError as wf_err:
|
|
401
|
+
print(f"[ERROR] [INSPECT_FAILED]: {wf_err}", file=sys.stderr)
|
|
402
|
+
return 1
|
|
403
|
+
except FileNotFoundError as fnf:
|
|
404
|
+
print(f"[ERROR] [FILE_NOT_FOUND]: {fnf}", file=sys.stderr)
|
|
405
|
+
return 1
|
|
406
|
+
else:
|
|
407
|
+
wf_parser.print_help()
|
|
408
|
+
return 0
|
|
409
|
+
|
|
410
|
+
elif args.command in ("web", "ui", "amfy", "amfyui"):
|
|
411
|
+
from .web import run_web_server
|
|
412
|
+
run_web_server(host=args.host, port=args.port, blocking=True)
|
|
413
|
+
return 0
|
|
414
|
+
|
|
415
|
+
elif args.command in ("component", "model", "instance"):
|
|
416
|
+
try:
|
|
417
|
+
from ameva_component.cli_support import dispatch_protocol
|
|
418
|
+
from termux_diffusion.control import DiffusionControl
|
|
419
|
+
dispatch_protocol(args, DiffusionControl())
|
|
420
|
+
except ImportError:
|
|
421
|
+
print("[ERROR] ameva-component-sdk not installed.", file=sys.stderr)
|
|
422
|
+
return 1
|
|
423
|
+
return 0
|
|
424
|
+
|
|
425
|
+
parser.print_help()
|
|
426
|
+
return 0
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def run_install_cli(argv: Optional[List[str]] = None):
|
|
430
|
+
"""Entry point for termux-diffusion-install with CLI mutex validation and mode control."""
|
|
431
|
+
if argv is None:
|
|
432
|
+
argv = sys.argv[1:]
|
|
433
|
+
|
|
434
|
+
parser = argparse.ArgumentParser(
|
|
435
|
+
prog="termux-diffusion-install",
|
|
436
|
+
description="Provision native ARM64 engine binaries with Prebuilt-First pipeline, self-test, and fallback."
|
|
437
|
+
)
|
|
438
|
+
parser.add_argument("--prebuilt", action="store_true", help="Install pre-compiled binary (prebuilt-only mode)")
|
|
439
|
+
parser.add_argument("--prebuilt-only", action="store_true", help="Install pre-compiled binary (do not fall back to source build on failure)")
|
|
440
|
+
parser.add_argument("--build-from-source", action="store_true", help="Skip prebuilt binary and compile from source")
|
|
441
|
+
parser.add_argument("-b", "--backend", type=str, default="cpu", choices=["auto", "gpu", "vulkan", "opencl", "cpu"], help="Target compute backend (default: cpu)")
|
|
442
|
+
parser.add_argument("-f", "--force", "--force-reinstall", dest="force", action="store_true", help="Force re-installation")
|
|
443
|
+
parser.add_argument("--print-diagnostics", action="store_true", help="Print system, ABI, CPU, and Vulkan diagnostic information and continue")
|
|
444
|
+
parser.add_argument("--diagnostics-only", action="store_true", help="Print system, ABI, CPU, and Vulkan diagnostic information and exit immediately")
|
|
445
|
+
|
|
446
|
+
args = parser.parse_args(argv)
|
|
447
|
+
|
|
448
|
+
# Mutually Exclusive Flag Check
|
|
449
|
+
is_prebuilt_flag = args.prebuilt or args.prebuilt_only
|
|
450
|
+
if is_prebuilt_flag and args.build_from_source:
|
|
451
|
+
print("[ERROR] E_CLI_EXCLUSIVE_MUTEX: --prebuilt / --prebuilt-only and --build-from-source are mutually exclusive options.", file=sys.stderr)
|
|
452
|
+
sys.exit(ExitCode.CLI_ERROR)
|
|
453
|
+
|
|
454
|
+
if is_prebuilt_flag:
|
|
455
|
+
install_mode = "prebuilt-only"
|
|
456
|
+
elif args.build_from_source:
|
|
457
|
+
install_mode = "source-only"
|
|
458
|
+
else:
|
|
459
|
+
install_mode = "prebuilt-first"
|
|
460
|
+
|
|
461
|
+
print(f"[termux-diffusion] Selected Install Mode: {install_mode}")
|
|
462
|
+
|
|
463
|
+
if getattr(args, "print_diagnostics", False) or getattr(args, "diagnostics_only", False):
|
|
464
|
+
print("\n=== [termux-diffusion] System Diagnostics ===")
|
|
465
|
+
from .hardware import detect_hardware_profile, format_hardware_report
|
|
466
|
+
hw = detect_hardware_profile()
|
|
467
|
+
print(format_hardware_report(hw))
|
|
468
|
+
print(f"Install Mode: {install_mode}")
|
|
469
|
+
print(f"Target Backend: {args.backend}")
|
|
470
|
+
print("=============================================\n")
|
|
471
|
+
if getattr(args, "diagnostics_only", False):
|
|
472
|
+
return ExitCode.SUCCESS
|
|
473
|
+
|
|
474
|
+
try:
|
|
475
|
+
provision_engine(force=args.force, backend=args.backend, install_mode=install_mode)
|
|
476
|
+
except TermuxDiffusionError as err:
|
|
477
|
+
print(f"[ERROR] [{err.code}]: {err}", file=sys.stderr)
|
|
478
|
+
if err.code == ErrorCode.MANIFEST_DOWNLOAD or "E_PREBUILT_FAILED" in str(err) or err.code == "E_PREBUILT_UNAVAILABLE":
|
|
479
|
+
sys.exit(ExitCode.INTEGRITY_ERROR)
|
|
480
|
+
elif err.code == ErrorCode.INSTALL_LOCKED:
|
|
481
|
+
sys.exit(ExitCode.CLI_ERROR)
|
|
482
|
+
else:
|
|
483
|
+
sys.exit(ExitCode.BUILD_ERROR)
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def run_doctor_cli():
|
|
487
|
+
"""Entry point for termux-diffusion-doctor."""
|
|
488
|
+
run_doctor()
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
if __name__ == "__main__":
|
|
492
|
+
sys.exit(main())
|