angleforge / src /backends /local_qwen.py
eoinedge's picture
Upload folder using huggingface_hub
f2ec79c verified
Raw
History Blame Contribute Delete
2.94 kB
"""Local Qwen Image Edit backend (needs a CUDA GPU).
Mirrors ``linoyts/Qwen-Image-Edit-Angles``: the base Qwen Image Edit 2509
pipeline with Phr00t's Rapid-AIO transformer for fast 4-step inference and
dx8152's multiple-angles LoRA fused in. Heavy imports are deferred so the
rest of the app works on CPU-only machines.
"""
from __future__ import annotations
import random
from typing import Optional
from PIL import Image
from ..config import (
ANGLES_LORA_REPO,
ANGLES_LORA_WEIGHT,
QWEN_BASE_MODEL,
QWEN_RAPID_TRANSFORMER,
)
from ..images import fit_image
from .base import ImageEditBackend
class LocalQwenBackend(ImageEditBackend):
source = "local_qwen_image_edit"
def __init__(self, image_size: int, lora_scale: float = 1.25) -> None:
self.image_size = image_size
self.lora_scale = lora_scale
self._pipe = None
self._device = "cpu"
def prepare(self) -> None:
import torch # local import: heavy dep
from diffusers import QwenImageEditPlusPipeline, QwenImageTransformer2DModel
if not torch.cuda.is_available():
raise RuntimeError("Local Qwen backend requires a CUDA GPU.")
self._device = "cuda"
dtype = torch.bfloat16
transformer = QwenImageTransformer2DModel.from_pretrained(
QWEN_RAPID_TRANSFORMER,
subfolder="transformer",
torch_dtype=dtype,
device_map="cuda",
)
pipe = QwenImageEditPlusPipeline.from_pretrained(
QWEN_BASE_MODEL,
transformer=transformer,
torch_dtype=dtype,
).to(self._device)
pipe.load_lora_weights(
ANGLES_LORA_REPO,
weight_name=ANGLES_LORA_WEIGHT,
adapter_name="angles",
)
pipe.set_adapters(["angles"], adapter_weights=[1.0])
pipe.fuse_lora(adapter_names=["angles"], lora_scale=self.lora_scale)
pipe.unload_lora_weights()
self._pipe = pipe
def edit(
self,
image: Image.Image,
prompt: str,
seed: int,
num_inference_steps: int,
true_guidance_scale: float,
) -> Image.Image:
import torch # local import: heavy dep
base = fit_image(image.convert("RGB"), self.image_size)
if not prompt.strip():
return base
if self._pipe is None:
raise RuntimeError("Backend not prepared; call prepare() first.")
generator = torch.Generator(device=self._device).manual_seed(int(seed))
result = self._pipe(
image=[base],
prompt=prompt,
width=base.width,
height=base.height,
num_inference_steps=num_inference_steps,
generator=generator,
true_cfg_scale=true_guidance_scale,
num_images_per_prompt=1,
).images[0]
return fit_image(result.convert("RGB"), self.image_size)