""" Qwen Image to LoRA Generator ๐จ Comic Classic Theme โก Optimized with Lazy Loading for Fast Startup """ import gradio as gr import numpy as np import torch import random import spaces from ulid import ULID from safetensors.torch import save_file from PIL import Image import os DTYPE = torch.bfloat16 MAX_SEED = np.iinfo(np.int32).max # ============================================ # ๐ LAZY LOADING - ๋ชจ๋ธ์ ์ฒ์ ์ฌ์ฉํ ๋๋ง ๋ก๋ฉ # ============================================ pipe_lora = None pipe_imagen = None lora_model_loaded = False imagen_model_loaded = False def get_vram_config_disk_offload(): return { "offload_dtype": "disk", "offload_device": "disk", "onload_dtype": "disk", "onload_device": "disk", "preparing_dtype": torch.bfloat16, "preparing_device": "cuda", "computation_dtype": torch.bfloat16, "computation_device": "cuda", } def get_vram_config(): return { "offload_dtype": "disk", "offload_device": "disk", "onload_dtype": torch.bfloat16, "onload_device": "cuda", "preparing_dtype": torch.bfloat16, "preparing_device": "cuda", "computation_dtype": torch.bfloat16, "computation_device": "cuda", } # loras ๋๋ ํ ๋ฆฌ ์์ฑ os.makedirs("loras", exist_ok=True) @spaces.GPU(duration=300) def generate_lora(input_images, progress=gr.Progress(track_tqdm=True)): """LoRA ์์ฑ - GPU ์ปจํ ์คํธ ์์์ ๋ชจ๋ธ ๋ก๋ฉ""" global pipe_lora, lora_model_loaded if not input_images: gr.Warning("โ ๏ธ Please upload at least one image!") return None, gr.update(interactive=False), gr.update(interactive=False) # GPU ์ปจํ ์คํธ ์์์ ๋ชจ๋ธ ๋ก๋ฉ if not lora_model_loaded: print("๐ Loading LoRA generation pipeline...") from diffsynth.pipelines.qwen_image import QwenImagePipeline, ModelConfig vram_config_disk_offload = get_vram_config_disk_offload() pipe_lora = QwenImagePipeline.from_pretrained( torch_dtype=torch.bfloat16, device="cuda", model_configs=[ ModelConfig( download_source="huggingface", model_id="DiffSynth-Studio/General-Image-Encoders", origin_file_pattern="SigLIP2-G384/model.safetensors", **vram_config_disk_offload ), ModelConfig( download_source="huggingface", model_id="DiffSynth-Studio/General-Image-Encoders", origin_file_pattern="DINOv3-7B/model.safetensors", **vram_config_disk_offload ), ModelConfig( download_source="huggingface", model_id="DiffSynth-Studio/Qwen-Image-i2L", origin_file_pattern="Qwen-Image-i2L-Style.safetensors", **vram_config_disk_offload ), ], processor_config=ModelConfig(model_id="Qwen/Qwen-Image-Edit", origin_file_pattern="processor/"), vram_limit=torch.cuda.mem_get_info("cuda")[1] / (1024 ** 3) - 0.5, ) lora_model_loaded = True print("โ LoRA pipeline loaded!") pipeline = pipe_lora from diffsynth.pipelines.qwen_image import ( QwenImageUnit_Image2LoRAEncode, QwenImageUnit_Image2LoRADecode ) ulid = str(ULID()).lower()[:12] print(f"๐ฏ Generating LoRA with ID: {ulid}") input_images = [Image.open(filepath).convert("RGB") for filepath, _ in input_images] with torch.no_grad(): embs = QwenImageUnit_Image2LoRAEncode().process(pipeline, image2lora_images=input_images) lora = QwenImageUnit_Image2LoRADecode().process(pipeline, **embs)["lora"] lora_name = f"{ulid}.safetensors" lora_path = f"loras/{lora_name}" save_file(lora, lora_path) print(f"โ LoRA saved: {lora_path}") return lora_name, gr.update(interactive=True, value=lora_path), gr.update(interactive=True) @spaces.GPU(duration=300) def generate_image( lora_name, prompt, negative_prompt="blurry ugly bad", width=1024, height=1024, seed=42, randomize_seed=True, guidance_scale=3.5, num_inference_steps=8, progress=gr.Progress(track_tqdm=True) ): """์ด๋ฏธ์ง ์์ฑ - GPU ์ปจํ ์คํธ ์์์ ๋ชจ๋ธ ๋ก๋ฉ""" global pipe_imagen, imagen_model_loaded if not lora_name: gr.Warning("โ ๏ธ Please generate a LoRA first!") return None, seed # GPU ์ปจํ ์คํธ ์์์ ๋ชจ๋ธ ๋ก๋ฉ if not imagen_model_loaded: print("๐ Loading Image generation pipeline...") from diffsynth.pipelines.qwen_image import QwenImagePipeline, ModelConfig vram_config = get_vram_config() pipe_imagen = QwenImagePipeline.from_pretrained( torch_dtype=torch.bfloat16, device="cuda", model_configs=[ ModelConfig(download_source="huggingface", model_id="Qwen/Qwen-Image", origin_file_pattern="transformer/diffusion_pytorch_model*.safetensors", **vram_config), ModelConfig(download_source="huggingface", model_id="Qwen/Qwen-Image", origin_file_pattern="text_encoder/model*.safetensors", **vram_config), ModelConfig(download_source="huggingface", model_id="Qwen/Qwen-Image", origin_file_pattern="vae/diffusion_pytorch_model.safetensors", **vram_config), ], tokenizer_config=ModelConfig(download_source="huggingface", model_id="Qwen/Qwen-Image", origin_file_pattern="tokenizer/"), vram_limit=torch.cuda.mem_get_info("cuda")[1] / (1024 ** 3) - 0.5, ) imagen_model_loaded = True print("โ Image generation pipeline loaded!") pipeline = pipe_imagen lora_path = f"loras/{lora_name}" if not os.path.exists(lora_path): gr.Warning(f"โ ๏ธ LoRA file not found: {lora_path}") return None, seed pipeline.clear_lora() pipeline.load_lora(pipeline.dit, lora_path) if randomize_seed: seed = random.randint(0, MAX_SEED) print(f"๐จ Generating image with seed: {seed}") output_image = pipeline( prompt=prompt, negative_prompt=negative_prompt, num_inference_steps=num_inference_steps, width=width, height=height, ) print("โ Image generated!") return output_image, seed # ============================================ # ๐จ Comic Classic Theme CSS # ============================================ css = """ /* ===== ๐จ Google Fonts Import ===== */ @import url('https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@400;700&display=swap'); /* ===== ๐จ Comic Classic ๋ฐฐ๊ฒฝ ===== */ .gradio-container { background-color: #FEF9C3 !important; background-image: radial-gradient(#1F2937 1px, transparent 1px) !important; background-size: 20px 20px !important; min-height: 100vh !important; font-family: 'Comic Neue', cursive, sans-serif !important; } /* ===== ํ๊น ํ์ด์ค ์๋จ ์์ ์จ๊น ===== */ .huggingface-space-header, #space-header, .space-header, [class*="space-header"], .svelte-1ed2p3z, .space-header-badge, .header-badge, [data-testid="space-header"], .svelte-kqij2n, .svelte-1ax1toq, .embed-container > div:first-child { display: none !important; visibility: hidden !important; height: 0 !important; width: 0 !important; overflow: hidden !important; opacity: 0 !important; pointer-events: none !important; } /* ===== Footer ์์ ์จ๊น ===== */ footer, .footer, .gradio-container footer, .built-with, [class*="footer"], .gradio-footer, .main-footer, div[class*="footer"], .show-api, .built-with-gradio, a[href*="gradio.app"], a[href*="huggingface.co/spaces"] { display: none !important; visibility: hidden !important; height: 0 !important; padding: 0 !important; margin: 0 !important; } /* ===== ๋ฉ์ธ ์ปจํ ์ด๋ ===== */ #col-container { max-width: 960px; margin: 0 auto; } /* ===== ๐จ ํค๋ ํ์ดํ - ์ฝ๋ฏน ์คํ์ผ ===== */ .header-text h1 { font-family: 'Bangers', cursive !important; color: #1F2937 !important; font-size: 3.5rem !important; font-weight: 400 !important; text-align: center !important; margin-bottom: 0.5rem !important; text-shadow: 4px 4px 0px #FACC15, 6px 6px 0px #1F2937 !important; letter-spacing: 3px !important; -webkit-text-stroke: 2px #1F2937 !important; } /* ===== ๐จ ์๋ธํ์ดํ ===== */ .subtitle { text-align: center !important; font-family: 'Comic Neue', cursive !important; font-size: 1.2rem !important; color: #1F2937 !important; margin-bottom: 1.5rem !important; font-weight: 700 !important; } /* ===== ๐จ ์นด๋/ํจ๋ - ๋งํ ํ๋ ์ ์คํ์ผ ===== */ .gr-panel, .gr-box, .gr-form, .block, .gr-group { background: #FFFFFF !important; border: 3px solid #1F2937 !important; border-radius: 8px !important; box-shadow: 6px 6px 0px #1F2937 !important; transition: all 0.2s ease !important; } .gr-panel:hover, .block:hover { transform: translate(-2px, -2px) !important; box-shadow: 8px 8px 0px #1F2937 !important; } /* ===== ๐จ ์ ๋ ฅ ํ๋ ===== */ textarea, input[type="text"], input[type="number"] { background: #FFFFFF !important; border: 3px solid #1F2937 !important; border-radius: 8px !important; color: #1F2937 !important; font-family: 'Comic Neue', cursive !important; font-size: 1rem !important; font-weight: 700 !important; transition: all 0.2s ease !important; } textarea:focus, input[type="text"]:focus, input[type="number"]:focus { border-color: #3B82F6 !important; box-shadow: 4px 4px 0px #3B82F6 !important; outline: none !important; } textarea::placeholder { color: #9CA3AF !important; font-weight: 400 !important; } /* ===== ๐จ Primary ๋ฒํผ - ์ฝ๋ฏน ๋ธ๋ฃจ ===== */ .gr-button-primary, button.primary, .gr-button.primary { background: #3B82F6 !important; border: 3px solid #1F2937 !important; border-radius: 8px !important; color: #FFFFFF !important; font-family: 'Bangers', cursive !important; font-weight: 400 !important; font-size: 1.3rem !important; letter-spacing: 2px !important; padding: 14px 28px !important; box-shadow: 5px 5px 0px #1F2937 !important; transition: all 0.1s ease !important; text-shadow: 1px 1px 0px #1F2937 !important; } .gr-button-primary:hover, button.primary:hover, .gr-button.primary:hover { background: #2563EB !important; transform: translate(-2px, -2px) !important; box-shadow: 7px 7px 0px #1F2937 !important; } .gr-button-primary:active, button.primary:active, .gr-button.primary:active { transform: translate(3px, 3px) !important; box-shadow: 2px 2px 0px #1F2937 !important; } /* ===== ๐จ Secondary ๋ฒํผ - ์ฝ๋ฏน ๋ ๋ ===== */ .gr-button-secondary, button.secondary { background: #EF4444 !important; border: 3px solid #1F2937 !important; border-radius: 8px !important; color: #FFFFFF !important; font-family: 'Bangers', cursive !important; font-weight: 400 !important; font-size: 1.1rem !important; letter-spacing: 1px !important; box-shadow: 4px 4px 0px #1F2937 !important; transition: all 0.1s ease !important; text-shadow: 1px 1px 0px #1F2937 !important; } .gr-button-secondary:hover, button.secondary:hover { background: #DC2626 !important; transform: translate(-2px, -2px) !important; box-shadow: 6px 6px 0px #1F2937 !important; } .gr-button-secondary:active, button.secondary:active { transform: translate(2px, 2px) !important; box-shadow: 2px 2px 0px #1F2937 !important; } /* ===== ๐จ ๋ผ๋ฒจ ์คํ์ผ ===== */ label, .gr-input-label, .gr-block-label { color: #1F2937 !important; font-family: 'Comic Neue', cursive !important; font-weight: 700 !important; font-size: 1rem !important; } span.gr-label { color: #1F2937 !important; } /* ===== ๐จ ํ์ผ ์ ๋ก๋ ์์ญ ===== */ .gr-file-upload { border: 3px dashed #1F2937 !important; border-radius: 8px !important; background: #FEF9C3 !important; } .gr-file-upload:hover { border-color: #3B82F6 !important; background: #EFF6FF !important; } /* ===== ๐จ ๊ฐค๋ฌ๋ฆฌ ์คํ์ผ ===== */ .gr-gallery { border: 3px solid #1F2937 !important; border-radius: 8px !important; box-shadow: 4px 4px 0px #1F2937 !important; } /* ===== ๐จ ์ด๋ฏธ์ง ์ถ๋ ฅ ์์ญ ===== */ .gr-image, .image-container { border: 4px solid #1F2937 !important; border-radius: 8px !important; box-shadow: 8px 8px 0px #1F2937 !important; overflow: hidden !important; background: #FFFFFF !important; } /* ===== ๐จ ์์ฝ๋์ธ ===== */ .gr-accordion { background: #FACC15 !important; border: 3px solid #1F2937 !important; border-radius: 8px !important; box-shadow: 4px 4px 0px #1F2937 !important; } .gr-accordion-header { color: #1F2937 !important; font-family: 'Comic Neue', cursive !important; font-weight: 700 !important; font-size: 1.1rem !important; } /* ===== ๐จ ์ฌ๋ผ์ด๋ ===== */ input[type="range"] { accent-color: #EF4444 !important; height: 8px !important; } /* ===== ๐จ ์ฒดํฌ๋ฐ์ค ===== */ input[type="checkbox"] { accent-color: #3B82F6 !important; width: 20px !important; height: 20px !important; border: 2px solid #1F2937 !important; } /* ===== ๐จ ๋ค์ด๋ก๋ ๋ฒํผ ===== */ .gr-download-button, button[class*="download"] { background: #10B981 !important; border: 3px solid #1F2937 !important; border-radius: 8px !important; color: #FFFFFF !important; font-family: 'Bangers', cursive !important; font-weight: 400 !important; font-size: 1.1rem !important; letter-spacing: 1px !important; box-shadow: 4px 4px 0px #1F2937 !important; text-shadow: 1px 1px 0px #1F2937 !important; } .gr-download-button:hover, button[class*="download"]:hover { background: #059669 !important; transform: translate(-2px, -2px) !important; box-shadow: 6px 6px 0px #1F2937 !important; } /* ===== ๐จ ์คํฌ๋กค๋ฐ ===== */ ::-webkit-scrollbar { width: 12px; height: 12px; } ::-webkit-scrollbar-track { background: #FEF9C3; border: 2px solid #1F2937; } ::-webkit-scrollbar-thumb { background: #3B82F6; border: 2px solid #1F2937; border-radius: 0px; } ::-webkit-scrollbar-thumb:hover { background: #EF4444; } /* ===== ๐จ ์ ํ ํ์ด๋ผ์ดํธ ===== */ ::selection { background: #FACC15; color: #1F2937; } /* ===== ๐จ ๋งํฌ ์คํ์ผ ===== */ a { color: #3B82F6 !important; text-decoration: none !important; font-weight: 700 !important; } a:hover { color: #EF4444 !important; } /* ===== ๐จ ๊ตฌ๋ถ์ ===== */ hr { border: none !important; border-top: 3px dashed #1F2937 !important; margin: 1.5rem 0 !important; } /* ===== ๐จ Row/Column ๊ฐ๊ฒฉ ===== */ .gr-row { gap: 1.5rem !important; } .gr-column { gap: 1rem !important; } /* ===== ๋ฐ์ํ ์กฐ์ ===== */ @media (max-width: 768px) { .header-text h1 { font-size: 2.2rem !important; text-shadow: 3px 3px 0px #FACC15, 4px 4px 0px #1F2937 !important; } .gr-button-primary, button.primary { padding: 12px 20px !important; font-size: 1.1rem !important; } .gr-panel, .block { box-shadow: 4px 4px 0px #1F2937 !important; } } /* ===== ๐จ ๋คํฌ๋ชจ๋ ๋นํ์ฑํ ===== */ @media (prefers-color-scheme: dark) { .gradio-container { background-color: #FEF9C3 !important; } } """ # ============================================ # ๐ฎ Gradio UI # ============================================ with gr.Blocks(title="Qwen Image to LoRA") as demo: # gr.LoginButton(value="Option: HuggingFace 'Login' for extra GPU quota +", size="sm") # CSS ์ฃผ์ gr.HTML(f"") # HOME Badge gr.HTML("""
""") # Header gr.Markdown("# ๐จ QWEN IMAGE TO LORA ๐จ", elem_classes="header-text") gr.Markdown('โจ Generate LoRA from your images instantly! ๐ผ๏ธ
') with gr.Column(elem_id="col-container"): # ===== STEP 1: LoRA Generation ===== gr.Markdown("### ๐ค STEP 1: Upload Images & Generate LoRA") with gr.Row(): with gr.Column(): input_images = gr.Gallery( label="๐ Input Images (Upload 1-5 images)", file_types=["image"], columns=2, object_fit="cover", height=300 ) lora_button = gr.Button("๐ Generate LoRA", variant="primary", size="lg") with gr.Column(): lora_name = gr.Textbox( label="๐ Generated LoRA Name", lines=1, interactive=False, placeholder="LoRA name will appear here..." ) lora_download = gr.DownloadButton( label="๐ฅ Download LoRA", interactive=False ) gr.Markdown("""๐ก Tip: Upload 1-5 images with similar style for best results!