From fc78b51e29497703a65f125e844f7da558dbc577 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Mon, 6 Jul 2026 18:34:31 +0200 Subject: Initial commit: local SDXL image generation on Intel Arc B580 Containerized SDXL-base generation targeting a 12GB Arc B580 under Slackware. The container carries the Intel Level Zero runtime the host distro does not package; the fp16-fix VAE removes the fp32 upcast that would otherwise OOM at decode. - imggen: host wrapper (GPU passthrough, /out bind-mount, model cache) - generate.py: in-container XPU pipeline, scope-bounded to SDXL base - Dockerfile: pinned to the known-good torch-XPU / diffusers stack - README.md, CLAUDE.md: rationale, limits, and version-pinning notes Co-Authored-By: Claude Opus 4.8 --- generate.py | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 generate.py (limited to 'generate.py') diff --git a/generate.py b/generate.py new file mode 100644 index 0000000..ba437ae --- /dev/null +++ b/generate.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +# Copyright (C) 2026 Danilo +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# Local SDXL-base image generation for Intel Arc B580 (XPU backend). +# Bounded scope by design: SDXL base, single 1024x1024 image, no refiner, +# no Flux, no batching. This is what fits comfortably in 12GB VRAM once the +# fp32 VAE upcast is removed via the fp16-fix VAE. See README.md for limits. + +import argparse +import sys +import time +from datetime import datetime +from pathlib import Path + +import torch +from diffusers import StableDiffusionXLPipeline, AutoencoderKL + +MODEL = "stabilityai/stable-diffusion-xl-base-1.0" +# fp16-fix VAE avoids the fp32 upcast that OOMs a 12GB card at decode time. +VAE = "madebyollin/sdxl-vae-fp16-fix" +OUT_DIR = Path("/out") + + +def build_pipe(): + if not torch.xpu.is_available(): + # Fail loud and specific. A silent CPU fallback would run but take + # minutes per image, which is worse than a clear error. + print( + "ERROR: torch.xpu is not available. The Level Zero runtime is not " + "visible inside this container. Check that you passed --device " + "/dev/dri and that the base image ships the Intel GPU runtime.", + file=sys.stderr, + ) + sys.exit(2) + + vae = AutoencoderKL.from_pretrained(VAE, torch_dtype=torch.float16) + pipe = StableDiffusionXLPipeline.from_pretrained( + MODEL, + vae=vae, + torch_dtype=torch.float16, + use_safetensors=True, + variant="fp16", + ).to("xpu") + return pipe + + +def generate(pipe, prompt, negative, steps, guidance, seed): + generator = None + if seed is not None: + generator = torch.Generator(device="xpu").manual_seed(seed) + + t = time.time() + image = pipe( + prompt, + negative_prompt=negative, + num_inference_steps=steps, + guidance_scale=guidance, + generator=generator, + ).images[0] + torch.xpu.synchronize() + elapsed = time.time() - t + return image, elapsed + + +def main(): + ap = argparse.ArgumentParser( + description="Generate a single SDXL-base image locally on Intel Arc (XPU)." + ) + ap.add_argument("prompt", help="Text prompt for the image.") + ap.add_argument( + "-n", "--negative", default="", help="Negative prompt (default: none)." + ) + ap.add_argument( + "-s", "--steps", type=int, default=25, help="Inference steps (default: 25)." + ) + ap.add_argument( + "-g", + "--guidance", + type=float, + default=7.0, + help="Guidance scale / CFG (default: 7.0).", + ) + ap.add_argument( + "--seed", + type=int, + default=None, + help="Seed for reproducible output (default: random).", + ) + ap.add_argument( + "-o", + "--out", + default=None, + help="Output filename inside /out (default: timestamped).", + ) + args = ap.parse_args() + + OUT_DIR.mkdir(parents=True, exist_ok=True) + + if args.out: + out_path = OUT_DIR / args.out + else: + stamp = datetime.now().strftime("%Y%m%d-%H%M%S") + out_path = OUT_DIR / f"sdxl-{stamp}.png" + + print(f"Loading SDXL base + fp16-fix VAE on XPU ...", file=sys.stderr) + t0 = time.time() + pipe = build_pipe() + print(f"Loaded in {time.time() - t0:.1f}s", file=sys.stderr) + + image, elapsed = generate( + pipe, args.prompt, args.negative, args.steps, args.guidance, args.seed + ) + image.save(out_path) + print(f"Generated in {elapsed:.2f}s") + print(f"Saved {out_path}") + + +if __name__ == "__main__": + main() -- cgit v1.2.3