qwen-image-editor / models.py
techfreakworm's picture
feat: build Qwen Image Editor app (Edit/Compose tabs, Fast/Quality, ZeroGPU + local CUDA) with tests
d713f9b
Raw
History Blame Contribute Delete
3.23 kB
"""Model constants, device helpers, and dimension utilities for Qwen Image Editor."""
from __future__ import annotations
import math
import os
# ---------------------------------------------------------------------------
# Model & LoRA identifiers
# ---------------------------------------------------------------------------
MODEL_ID = "Qwen/Qwen-Image-Edit-2511"
LORA_REPO = "lightx2v/Qwen-Image-Edit-2511-Lightning"
LORA_FILE = "Qwen-Image-Edit-2511-Lightning-4steps-V1.0-bf16.safetensors"
LORA_ADAPTER_NAME = "lightning"
# ---------------------------------------------------------------------------
# Lightning scheduler configuration
# Copied verbatim from diffusers-zerogpu-api.md §2.
# The Lightning distillation was trained with shift=3 (log(3)) and
# exponential time shifting — using the default scheduler gives degraded
# results when combined with the Lightning LoRA.
# ---------------------------------------------------------------------------
LIGHTNING_SCHEDULER_CONFIG: dict = {
"base_image_seq_len": 256,
"base_shift": math.log(3), # shift=3 used during distillation
"invert_sigmas": False,
"max_image_seq_len": 8192,
"max_shift": math.log(3), # same as base_shift for flat schedule
"num_train_timesteps": 1000,
"shift": 1.0,
"shift_terminal": None,
"stochastic_sampling": False,
"time_shift_type": "exponential",
"use_beta_sigmas": False,
"use_dynamic_shifting": True,
"use_exponential_sigmas": False,
"use_karras_sigmas": False,
}
def on_spaces() -> bool:
"""Return True iff running inside a Hugging Face ZeroGPU Space."""
return bool(os.environ.get("SPACES_ZERO_GPU"))
def auto_device() -> str:
"""Detect the best available compute device: cuda > mps > cpu."""
import torch
if torch.cuda.is_available():
return "cuda"
if torch.backends.mps.is_available():
return "mps"
return "cpu"
def fit_dimensions(image: object, max_pixels: int = 1024 * 1024, multiple: int = 16) -> tuple[int, int]:
"""Return (width, height) fitting within max_pixels, rounded down to `multiple`, min 256.
Preserves aspect ratio as closely as the multiple constraint allows.
Area is guaranteed <= max_pixels except in extreme aspect-ratio cases where
the shorter dimension is clamped up to the 256 minimum.
"""
w, h = image.size
if w * h > max_pixels:
scale = (max_pixels / (w * h)) ** 0.5
w = int(w * scale)
h = int(h * scale)
# Floor to nearest multiple
w = (w // multiple) * multiple
h = (h // multiple) * multiple
# Enforce minimum of 256 on each side
w = max(256, w)
h = max(256, h)
return w, h
def should_cpu_offload(device: str) -> bool:
"""Return True iff device is local CUDA with < 40 GB free VRAM.
Always returns False on ZeroGPU Spaces (the runtime manages placement)
and on mps/cpu (offload does not apply). Returns False gracefully when
torch is not installed.
"""
if device != "cuda" or on_spaces():
return False
try:
import torch
free_bytes, _total = torch.cuda.mem_get_info()
return free_bytes / (1024**3) < 40.0
except Exception:
return False