CoolFace
Apppublic

pearsonkyle/SDXL-Model-Merger

sourceHugging Facemitupdated 6mo agoView on Hugging Face
2likes
app.py560 linesDownload Raw Back to root
1#!/usr/bin/env python32"""3SDXL Model Merger - Modernized with modular architecture and improved UI/UX.4 5This application allows you to:6- Load SDXL checkpoints with optional VAE and multiple LoRAs7- Generate images with seamless tiling support8- Export merged models with quantization options9 10Author: Qwen Code Assistant11"""12 13try:14    import spaces  # noqa: F401 — must be imported before torch/CUDA packages15except ImportError:16    pass17 18import gradio as gr19 20 21def create_app():22    """Create and configure the Gradio app."""23 24    header_css = """25    .header-gradient {26        background: linear-gradient(135deg, #10b981 0%, #7c3aed 100%);27        -webkit-background-clip: text;28        -webkit-text-fill-color: transparent;29        background-clip: text;30    }31 32    .feature-card {33        border-radius: 12px;34        padding: 20px;35        margin-bottom: 16px;36        box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);37        transition: transform 0.2s ease;38    }39 40    .feature-card:hover {41        transform: translateY(-2px);42        box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);43    }44 45    .gradio-container .label {46        font-weight: 600;47        color: #374151;48        margin-bottom: 8px;49    }50 51    .status-success { color: #059669 !important; font-weight: 600; }52    .status-error { color: #dc2626 !important; font-weight: 600; }53    .status-warning { color: #d97706 !important; font-weight: 600; }54 55    .gradio-container .btn {56        border-radius: 8px;57        padding: 12px 24px;58        font-weight: 600;59    }60 61    .gradio-container textarea,62    .gradio-container input[type="number"],63    .gradio-container input[type="text"] {64        border-radius: 8px;65        border-color: #d1d5db;66    }67 68    .gradio-container textarea:focus,69    .gradio-container input:focus {70        outline: none;71        border-color: #6366f1;72        box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);73    }74 75    .gradio-container .tabitem {76        background: transparent;77        border-radius: 12px;78    }79 80    .progress-text {81        font-weight: 500;82        color: #6b7280 !important;83    }84    """85 86    from src.pipeline import load_pipeline87    from src.generator import generate_image88    from src.exporter import export_merged_model89    from src.config import get_cached_models, get_cached_checkpoints, get_cached_vaes, get_cached_loras90 91    with gr.Blocks(title="SDXL Model Merger") as demo:92        # Header section93        with gr.Column(elem_classes=["feature-card"]):94            gr.HTML("""95                <div style="text-align: center; margin-bottom: 24px;">96                    <h1 style="font-size: 2.5em; margin: 0; line-height: 1.2;">97                        <span class="header-gradient">SDXL Model Merger</span>98                    </h1>99                    <p style="color: #6b7280; font-size: 1.1em; max-width: 600px; margin: 16px auto;">100                        Merge checkpoints, LoRAs, and VAEs - then bake LoRAs into a single exportable101                        checkpoint with optional quantization.102                    </p>103                </div>104            """)105 106            # Feature highlights107            with gr.Row():108                with gr.Column(scale=1):109                    gr.HTML("""110                        <div style="text-align: center; padding: 16px;">111                            <div style="font-size: 2.5em; margin-bottom: 8px;">šŸš€</div>112                            <strong>Fast Loading</strong>113                            <p style="font-size: 0.85em; color: #6b7280; margin-top: 4px;">With progress tracking & cache</p>114                        </div>115                    """)116                with gr.Column(scale=1):117                    gr.HTML("""118                        <div style="text-align: center; padding: 16px;">119                            <div style="font-size: 2.5em; margin-bottom: 8px;">šŸŽØ</div>120                            <strong>Panorama Gen</strong>121                            <p style="font-size: 0.85em; color: #6b7280; margin-top: 4px;">Seamless tiling support</p>122                        </div>123                    """)124                with gr.Column(scale=1):125                    gr.HTML("""126                        <div style="text-align: center; padding: 16px;">127                            <div style="font-size: 2.5em; margin-bottom: 8px;">šŸ“¦</div>128                            <strong>Export Ready</strong>129                            <p style="font-size: 0.85em; color: #6b7280; margin-top: 4px;">Quantization & format options</p>130                        </div>131                    """)132 133        gr.Markdown("---")134 135        with gr.Tab("Load Pipeline"):136            gr.Markdown("### Load SDXL Pipeline with Checkpoint, VAE, and LoRAs")137 138            # Progress indicator for pipeline loading139            load_progress = gr.Textbox(140                label="Loading Progress",141                placeholder="Ready to start...",142                show_label=True,143                info="Real-time status of model downloads and pipeline setup"144            )145 146            with gr.Row():147                with gr.Column(scale=2):148                    # Checkpoint URL with cached models dropdown149                    checkpoint_url = gr.Textbox(150                        label="Base Model (.safetensors) URL",151                        value="https://civitai.com/api/download/models/354657?type=Model&format=SafeTensor&size=full&fp=fp16",152                        placeholder="e.g., https://civitai.com/api/download/models/...",153                        info="Download link for the base SDXL checkpoint"154                    )155 156                    # Dropdown of cached checkpoints157                    cached_checkpoints = gr.Dropdown(158                        choices=["(None found)"] + get_cached_checkpoints(),159                        label="Cached Checkpoints",160                        value="(None found)" if not get_cached_checkpoints() else None,161                        info="Models already downloaded to .cache/"162                    )163 164                    # VAE URL165                    vae_url = gr.Textbox(166                        label="VAE (.safetensors) URL",167                        value="https://huggingface.co/madebyollin/sdxl-vae-fp16-fix/resolve/main/sdxl.vae.safetensors?download=true",168                        placeholder="Leave blank to use model's built-in VAE",169                        info="Optional custom VAE for improved quality"170                    )171 172                    # Dropdown of cached VAEs173                    cached_vaes = gr.Dropdown(174                        choices=["(None found)"] + get_cached_vaes(),175                        label="Cached VAEs",176                        value="(None found)" if not get_cached_vaes() else None,177                        info="Select a VAE to load"178                    )179 180                with gr.Column(scale=1):181                    # LoRA URLs input182                    lora_urls = gr.Textbox(183                        label="LoRA URLs (one per line)",184                        lines=5,185                        value="https://civitai.com/api/download/models/143197?type=Model&format=SafeTensor",186                        placeholder="https://civit.ai/...\nhttps://huggingface.co/...",187                        info="Multiple LoRAs can be loaded and fused together"188                    )189 190                    # Dropdown of cached LoRAs191                    cached_loras = gr.Dropdown(192                        choices=["(None found)"] + get_cached_loras(),193                        label="Cached LoRAs",194                        value="(None found)" if not get_cached_loras() else None,195                        info="Select a LoRA to add to the list below"196                    )197 198                    lora_strengths = gr.Textbox(199                        label="LoRA Strengths",200                        value="1.0",201                        placeholder="e.g., 0.8,1.0,0.5",202                        info="Comma-separated strength values for each LoRA"203                    )204 205            with gr.Row():206                load_btn = gr.Button("šŸš€ Load Pipeline", variant="primary", size="lg")207 208            # Detailed status display209            load_status = gr.HTML(210                label="Status",211                value='<div class="status-success">āœ… Ready to load pipeline</div>',212            )213 214        with gr.Tab("Generate Image"):215            gr.Markdown("### Generate Panorama Images with Seamless Tiling")216 217            # Progress indicator for image generation218            gen_progress = gr.Textbox(219                label="Generation Progress",220                placeholder="Ready to generate...",221                show_label=True,222                info="Real-time status of image generation"223            )224 225            with gr.Row():226                with gr.Column(scale=1):227                    prompt = gr.Textbox(228                        label="Positive Prompt",229                        value="Glowing mushrooms around pyramids amidst a cosmic backdrop, equirectangular, 360 panorama, cinematic",230                        lines=4,231                        placeholder="Describe the image you want to generate..."232                    )233 234                    cfg = gr.Slider(235                        minimum=1.0, maximum=20.0, value=3.0, step=0.5,236                        label="CFG Scale",237                        info="Higher values make outputs match prompt more strictly"238                    )239 240                    height = gr.Number(241                        value=1024, precision=0,242                        label="Height (pixels)",243                        info="Output image height"244                    )245 246                with gr.Column(scale=1):247                    negative_prompt = gr.Textbox(248                        label="Negative Prompt",249                        value="boring, text, signature, watermark, low quality, bad quality",250                        lines=4,251                        placeholder="Elements to avoid in generation..."252                    )253 254                    steps = gr.Slider(255                        minimum=1, maximum=100, value=8, step=1,256                        label="Inference Steps",257                        info="More steps = better quality but slower"258                    )259 260                    width = gr.Number(261                        value=2048, precision=0,262                        label="Width (pixels)",263                        info="Output image width"264                    )265 266            with gr.Row():267                tile_x = gr.Checkbox(True, label="X-axis Seamless Tiling")268                tile_y = gr.Checkbox(False, label="Y-axis Seamless Tiling")269 270            seed = gr.Number(271                value=80484030936239,272                precision=0,273                label="Seed",274                info="Random seed for reproducible generation"275            )276 277            with gr.Row():278                gen_btn = gr.Button("✨ Generate Image", variant="secondary", size="lg")279 280            with gr.Row():281                image_output = gr.Image(282                    label="Result",283                    height=400,284                    show_label=True285                )286                with gr.Column():287                    gen_status = gr.HTML(288                        label="Generation Status",289                        value='<div class="status-success">āœ… Ready to generate</div>',290                    )291 292                    gr.HTML("""293                        <div style="margin-top: 16px; padding: 12px; background-color: #e5e7eb !important; border-radius: 8px;">294                            <strong style="color: #1f2937 !important;">šŸ’” Tips:</strong>295                            <ul style="margin: 8px 0; padding-left: 20px; font-size: 0.9em; color: #1f2937 !important;">296                                <li>Use wide aspect ratios (e.g., 1024x2048) for panoramas</li>297                                <li>Enable seamless tiling for texture-like outputs</li>298                                <li>Lower CFG (3-5) for more creative results</li>299                            </ul>300                        </div>301                    """)302 303        with gr.Tab("Export Model"):304            gr.Markdown("### Export Merged Checkpoint with Quantization Options")305 306            # Progress indicator for export307            export_progress = gr.Textbox(308                label="Export Progress",309                placeholder="Ready to export...",310                show_label=True,311                info="Real-time status of model export and quantization"312            )313 314            with gr.Row():315                include_lora = gr.Checkbox(316                    True,317                    label="Include Fused LoRAs",318                    info="Bake the loaded LoRAs into the exported model"319                )320 321                quantize_toggle = gr.Checkbox(322                    False,323                    label="Apply Quantization",324                    info="Reduce model size with quantization"325                )326 327            qtype_row = gr.Row(visible=True)328            with qtype_row:329                qtype_dropdown = gr.Dropdown(330                    choices=["none", "int8", "int4", "float8"],331                    value="int8",332                    label="Quantization Method",333                    info="Trade quality for smaller file size"334                )335 336            with gr.Row():337                format_dropdown = gr.Dropdown(338                    choices=["safetensors", "bin"],339                    value="safetensors",340                    label="Export Format",341                    info="safetensors is recommended for safety"342                )343 344            with gr.Row():345                export_btn = gr.Button("šŸ’¾ Save Merged Checkpoint", variant="primary", size="lg")346 347            with gr.Row():348                download_link = gr.File(349                    label="Download Merged File",350                    show_label=True,351                )352 353                with gr.Column():354                    export_status = gr.HTML(355                        label="Export Status",356                        value='<div class="status-success">āœ… Ready to export</div>',357                    )358 359                    gr.HTML("""360                        <div style="margin-top: 16px; padding: 12px; background: #e0f2fe; border-radius: 8px;">361                            <strong>ā„¹ļø About Quantization:</strong>362                            <p style="font-size: 0.9em; margin: 8px 0;">363                                Reduces model size by lowering precision. Int8 is typically364                                lossless for inference while cutting size in half.365                            </p>366                        </div>367                    """)368 369        # Event handlers - all inside Blocks context370 371        def on_load_pipeline_start():372            """Called when pipeline loading starts."""373            return (374                '<div class="status-warning">ā³ Loading started...</div>',375                "Starting download...",376                gr.update(interactive=False)377            )378 379        def on_load_pipeline_complete(status_msg, progress_text):380            """Called when pipeline loading completes."""381            if "āœ…" in status_msg:382                return (383                    '<div class="status-success">āœ… Pipeline loaded successfully!</div>',384                    progress_text,385                    gr.update(interactive=True)386                )387            elif "āš ļø" in status_msg or "cancelled" in status_msg.lower():388                return (389                    '<div class="status-warning">āš ļø Download cancelled</div>',390                    progress_text,391                    gr.update(interactive=True)392                )393            else:394                return (395                    f'<div class="status-error">{status_msg}</div>',396                    progress_text,397                    gr.update(interactive=True)398                )399 400        load_btn.click(401            fn=on_load_pipeline_start,402            inputs=[],403            outputs=[load_status, load_progress, load_btn],404        ).then(405            fn=load_pipeline,406            inputs=[checkpoint_url, vae_url, lora_urls, lora_strengths],407            outputs=[load_status, load_progress],408            show_progress="full",409        ).then(410            fn=on_load_pipeline_complete,411            inputs=[load_status, load_progress],412            outputs=[load_status, load_progress, load_btn],413        ).then(414            fn=lambda: (415                gr.update(choices=["(None found)"] + get_cached_checkpoints()),416                gr.update(choices=["(None found)"] + get_cached_vaes()),417                gr.update(choices=["(None found)"] + get_cached_loras()),418            ),419            inputs=[],420            outputs=[cached_checkpoints, cached_vaes, cached_loras],421        )422 423        def on_cached_checkpoint_change(cached_path):424            """Update URL when a cached checkpoint is selected."""425            if cached_path and cached_path != "(None found)":426                return gr.update(value=f"file://{cached_path}")427            return gr.update()428 429        cached_checkpoints.change(430            fn=lambda x: gr.update(value=f"file://{x}" if x and x != "(None found)" else ""),431            inputs=cached_checkpoints,432            outputs=checkpoint_url,433        )434 435        def on_cached_vae_change(cached_path):436            """Update VAE URL when a cached VAE is selected."""437            if cached_path and cached_path != "(None found)":438                return gr.update(value=f"file://{cached_path}")439            return gr.update()440 441        cached_vaes.change(442            fn=on_cached_vae_change,443            inputs=cached_vaes,444            outputs=vae_url,445        )446 447        def on_cached_lora_change(cached_path, current_urls):448            """Add cached LoRA to the list."""449            if cached_path and cached_path != "(None found)":450                urls_list = [u.strip() for u in current_urls.split("\n") if u.strip()]451                file_url = f"file://{cached_path}"452                if file_url not in urls_list:453                    urls_list.append(file_url)454                    return gr.update(value="\n".join(urls_list))455            return gr.update()456 457        cached_loras.change(458            fn=on_cached_lora_change,459            inputs=[cached_loras, lora_urls],460            outputs=lora_urls,461        )462 463 464        def on_generate_start():465            """Called when image generation starts."""466            return (467                '<div class="status-warning">ā³ Generating image...</div>',468                "Starting generation...",469                gr.update(interactive=False)470            )471 472        def on_generate_complete(status_msg, progress_text, image):473            """Called when image generation completes."""474            if image is None:475                return (476                    f'<div class="status-error">{status_msg}</div>',477                    "",478                    gr.update(interactive=True),479                    gr.update()480                )481            else:482                return (483                    '<div class="status-success">āœ… Generation complete!</div>',484                    "Done",485                    gr.update(interactive=True),486                    gr.update(value=image)487                )488 489        gen_btn.click(490            fn=on_generate_start,491            inputs=[],492            outputs=[gen_status, gen_progress, gen_btn],493        ).then(494            fn=generate_image,495            inputs=[prompt, negative_prompt, cfg, steps, height, width, tile_x, tile_y, seed],496            outputs=[image_output, gen_progress],497        ).then(498            fn=lambda img, msg: on_generate_complete(msg, "Done", img),499            inputs=[image_output, gen_progress],500            outputs=[gen_status, gen_progress, gen_btn, image_output],501        )502 503        def on_export_start():504            """Called when export starts."""505            return (506                '<div class="status-warning">ā³ Export started...</div>',507                "Starting export...",508                gr.update(interactive=False)509            )510 511        def on_export_complete(status_msg, progress_text, file_path):512            """Called when export completes."""513            if file_path is None:514                return (515                    f'<div class="status-error">{status_msg}</div>',516                    "",517                    gr.update(interactive=True),518                    gr.update(value=None)519                )520            else:521                return (522                    '<div class="status-success">āœ… Export complete!</div>',523                    "Exported successfully",524                    gr.update(interactive=True),525                    gr.update(value=file_path)526                )527 528        export_btn.click(529            fn=on_export_start,530            inputs=[],531            outputs=[export_status, export_progress, export_btn],532        ).then(533            fn=lambda inc, q, qt, fmt: export_merged_model(534                include_lora=inc,535                quantize=q and (qt != "none"),536                qtype=qt,  # always pass the string value; exporter handles "none" correctly537                save_format=fmt,538            ),539            inputs=[include_lora, quantize_toggle, qtype_dropdown, format_dropdown],540            outputs=[download_link, export_progress],541        ).then(542            fn=lambda path, msg: on_export_complete(msg, "Exported", path),543            inputs=[download_link, export_progress],544            outputs=[export_status, export_progress, export_btn, download_link],545        )546 547        quantize_toggle.change(548            fn=lambda checked: gr.update(visible=checked),549            inputs=[quantize_toggle],550            outputs=qtype_row,551        )552 553    return demo554 555 556demo = create_app()557 558if __name__ == "__main__":559    demo.launch()560