Download runtime.py from John6666/sd21_zero: direct link, hf CLI and curl.
- Browser
- Download file 8.22 kB
-
https://huggingface.co/spaces/John6666/sd21_zero/resolve/main/runtime.py
- Command line
-
hf download hf://spaces/John6666/sd21_zero/runtime.py
-
curl -L -o runtime.py https://huggingface.co/spaces/John6666/sd21_zero/resolve/main/runtime.py
8.22 kB
| from __future__ import annotations | |
| import math | |
| import os | |
| import random | |
| import time | |
| import uuid | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| from typing import Any, Iterable | |
| # ZeroGPU must patch Torch/CUDA before Torch or Diffusers is imported. | |
| import spaces | |
| import torch | |
| from diffusers import EulerDiscreteScheduler, StableDiffusionPipeline | |
| MODEL_IDS = ("Manojb/stable-diffusion-2-1-base",) | |
| DEFAULT_MODEL_ID = MODEL_IDS[0] | |
| MAX_SEED = 2_147_483_647 | |
| ZERO_GPU_SIZE = "large" | |
| MIN_GPU_DURATION_SECONDS = 10 | |
| MAX_GPU_DURATION_SECONDS = 300 | |
| OUTPUT_MAX_AGE_SECONDS = 12 * 60 * 60 | |
| OUTPUT_MAX_FILES = 64 | |
| OUTPUT_DIR = Path(__file__).resolve().parent / ".runtime" / "outputs" | |
| class RuntimeInfo: | |
| mode: str | |
| device: str | |
| dtype: torch.dtype | |
| cuda_available: bool | |
| gpu_name: str | None | |
| model_loaded: bool | |
| def is_zerogpu(self) -> bool: | |
| return self.mode == "zerogpu" | |
| def is_assigned_gpu(self) -> bool: | |
| return self.mode == "assigned_gpu" | |
| def is_gpu(self) -> bool: | |
| return self.is_zerogpu or self.is_assigned_gpu | |
| def _detect_runtime() -> RuntimeInfo: | |
| is_zerogpu = bool(os.getenv("SPACES_ZERO_GPU")) | |
| cuda_available = bool(torch.cuda.is_available()) | |
| if is_zerogpu: | |
| mode = "zerogpu" | |
| device = "cuda" | |
| dtype = torch.float16 | |
| elif cuda_available: | |
| mode = "assigned_gpu" | |
| device = "cuda" | |
| dtype = torch.float16 | |
| else: | |
| mode = "cpu" | |
| device = "cpu" | |
| dtype = torch.float32 | |
| gpu_name: str | None = None | |
| if cuda_available and not is_zerogpu: | |
| try: | |
| gpu_name = torch.cuda.get_device_name(torch.cuda.current_device()) | |
| except Exception: | |
| gpu_name = "CUDA device" | |
| elif is_zerogpu: | |
| gpu_name = f"ZeroGPU {ZERO_GPU_SIZE}" | |
| return RuntimeInfo( | |
| mode=mode, | |
| device=device, | |
| dtype=dtype, | |
| cuda_available=cuda_available, | |
| gpu_name=gpu_name, | |
| model_loaded=False, | |
| ) | |
| RUNTIME = _detect_runtime() | |
| def _load_pipelines() -> dict[str, StableDiffusionPipeline]: | |
| pipelines: dict[str, StableDiffusionPipeline] = {} | |
| for model_id in MODEL_IDS: | |
| scheduler = EulerDiscreteScheduler.from_pretrained( | |
| model_id, | |
| subfolder="scheduler", | |
| ) | |
| pipeline = StableDiffusionPipeline.from_pretrained( | |
| model_id, | |
| scheduler=scheduler, | |
| torch_dtype=RUNTIME.dtype, | |
| use_safetensors=True, | |
| ) | |
| pipeline.to(RUNTIME.device) | |
| pipeline.set_progress_bar_config(disable=False) | |
| pipelines[model_id] = pipeline | |
| return pipelines | |
| if RUNTIME.is_gpu: | |
| torch.set_float32_matmul_precision("high") | |
| PIPELINES = _load_pipelines() | |
| RUNTIME = RuntimeInfo( | |
| mode=RUNTIME.mode, | |
| device=RUNTIME.device, | |
| dtype=RUNTIME.dtype, | |
| cuda_available=RUNTIME.cuda_available, | |
| gpu_name=RUNTIME.gpu_name, | |
| model_loaded=bool(PIPELINES), | |
| ) | |
| def startup_summary() -> str: | |
| summary = { | |
| "runtime": RUNTIME.mode, | |
| "device": RUNTIME.device, | |
| "dtype": str(RUNTIME.dtype).replace("torch.", ""), | |
| "gpu": RUNTIME.gpu_name, | |
| "model_loaded": RUNTIME.model_loaded, | |
| "models": list(MODEL_IDS), | |
| } | |
| return f"SD21 runtime: {summary}" | |
| print(startup_summary(), flush=True) | |
| def runtime_banner_markdown() -> str: | |
| if RUNTIME.is_zerogpu: | |
| return ( | |
| "**Runtime: ZeroGPU** — the model is preloaded on CUDA emulation and each generation " | |
| f"requests ZeroGPU `{ZERO_GPU_SIZE}` with a workload-based duration." | |
| ) | |
| if RUNTIME.is_assigned_gpu: | |
| gpu_name = RUNTIME.gpu_name or "CUDA GPU" | |
| return ( | |
| f"**Runtime: assigned GPU** — `{gpu_name}` detected. The pipeline stays resident on " | |
| "CUDA and does not request ZeroGPU quota." | |
| ) | |
| return ( | |
| "**Runtime: CPU** — the pipeline is loaded in `float32` and generation is available, " | |
| "but inference can be very slow." | |
| ) | |
| def require_pipeline(model_id: str) -> StableDiffusionPipeline: | |
| pipeline = PIPELINES.get(model_id) | |
| if pipeline is None: | |
| raise RuntimeError(f"Pipeline not found: {model_id}") | |
| return pipeline | |
| def random_seed() -> int: | |
| return random.SystemRandom().randint(0, MAX_SEED) | |
| def estimate_gpu_duration( | |
| *, | |
| width: int, | |
| height: int, | |
| steps: int, | |
| samples: int, | |
| ) -> int: | |
| """Initial conservative ZeroGPU declaration, calibrated from the prior 10s baseline. | |
| The declaration scales with pixel area, denoising steps, and sequential samples. It is a | |
| maximum allocation request, not a prediction of billed wall time on assigned GPU hardware. | |
| """ | |
| width = max(64, int(width)) | |
| height = max(64, int(height)) | |
| steps = max(1, int(steps)) | |
| samples = max(1, int(samples)) | |
| normalized_work = steps * samples * ((width * height) / (512 * 512)) | |
| seconds = math.ceil(8.0 + 0.08 * normalized_work) | |
| return max(MIN_GPU_DURATION_SECONDS, min(MAX_GPU_DURATION_SECONDS, seconds)) | |
| def estimate_basic_duration( | |
| prompt: str, | |
| negative: str, | |
| scale: float, | |
| model_id: str = DEFAULT_MODEL_ID, | |
| images: Any = None, | |
| *_args: Any, | |
| **_kwargs: Any, | |
| ) -> int: | |
| del prompt, negative, scale, model_id, images | |
| return estimate_gpu_duration(width=512, height=512, steps=50, samples=1) | |
| def estimate_advanced_duration( | |
| prompt: str, | |
| negative: str, | |
| scale: float, | |
| width: int, | |
| height: int, | |
| steps: int, | |
| seed: int, | |
| samples: int, | |
| model_id: str = DEFAULT_MODEL_ID, | |
| images: Any = None, | |
| *_args: Any, | |
| **_kwargs: Any, | |
| ) -> int: | |
| del prompt, negative, scale, seed, model_id, images | |
| return estimate_gpu_duration( | |
| width=width, | |
| height=height, | |
| steps=steps, | |
| samples=samples, | |
| ) | |
| def _gallery_path(item: Any) -> str | None: | |
| if item is None: | |
| return None | |
| if isinstance(item, (str, Path)): | |
| return str(item) | |
| if isinstance(item, dict): | |
| if item.get("path"): | |
| return str(item["path"]) | |
| image = item.get("image") | |
| if isinstance(image, dict) and image.get("path"): | |
| return str(image["path"]) | |
| if getattr(image, "path", None): | |
| return str(image.path) | |
| return None | |
| if getattr(item, "path", None): | |
| return str(item.path) | |
| image = getattr(item, "image", None) | |
| if getattr(image, "path", None): | |
| return str(image.path) | |
| if isinstance(item, (tuple, list)) and item: | |
| return _gallery_path(item[0]) | |
| return None | |
| def _gallery_caption(item: Any) -> str | None: | |
| if isinstance(item, dict): | |
| caption = item.get("caption") | |
| return str(caption) if caption is not None else None | |
| caption = getattr(item, "caption", None) | |
| if caption is not None: | |
| return str(caption) | |
| if isinstance(item, (tuple, list)) and len(item) > 1 and item[1] is not None: | |
| return str(item[1]) | |
| return None | |
| def normalize_gallery_items(items: Any) -> list[Any]: | |
| normalized: list[Any] = [] | |
| for item in items or []: | |
| path = _gallery_path(item) | |
| if path is None: | |
| continue | |
| caption = _gallery_caption(item) | |
| normalized.append((path, caption) if caption else path) | |
| return normalized | |
| def cleanup_outputs() -> None: | |
| OUTPUT_DIR.mkdir(parents=True, exist_ok=True) | |
| now = time.time() | |
| files = sorted( | |
| (path for path in OUTPUT_DIR.glob("*.jpg") if path.is_file()), | |
| key=lambda path: path.stat().st_mtime, | |
| reverse=True, | |
| ) | |
| for index, path in enumerate(files): | |
| too_old = now - path.stat().st_mtime > OUTPUT_MAX_AGE_SECONDS | |
| over_limit = index >= OUTPUT_MAX_FILES | |
| if too_old or over_limit: | |
| try: | |
| path.unlink() | |
| except OSError: | |
| pass | |
| def save_image(image: Any, seed: int) -> str: | |
| cleanup_outputs() | |
| OUTPUT_DIR.mkdir(parents=True, exist_ok=True) | |
| path = OUTPUT_DIR / f"sd21_{seed}_{uuid.uuid4().hex}.jpg" | |
| image.save(path, format="JPEG", quality=95) | |
| return str(path) | |
| cleanup_outputs() | |