multimodalart's picture
multimodalart HF Staff
Upload folder using huggingface_hub
cbbf804 verified
Raw
History Blame Contribute Delete
9.37 kB
# AOTI compiler Space for the CLIP-guided diffusion museum demo.
# Compiles one full guided DDPM timestep (UNet + guidance grad + update; tag diffstep3-dyn:
# respaced schedule + guidance scales are graph INPUTS, so one package per backbone serves
# any steps/scales) on ZeroGPU for each backbone missing from the dataset.
# Requires an HF_TOKEN Space secret with write access to the dataset repo.
import spaces # must precede any torch/CUDA import
import json
import os
import sys
import tempfile
import time
import traceback
import importlib.metadata as importlib_metadata
from datetime import datetime, timezone
sys.argv = [sys.argv[0]] # diffusion_fast/generate_fast parse CLI args at import
import torch
import gradio as gr
from huggingface_hub import HfApi, hf_hub_download
import diffusion_fast as D
import backbones as bb
from guided_diffusion.script_util import create_model_and_diffusion, model_and_diffusion_defaults
IMAGE_SIZE = 512
CUTN = 16
CUT_POW = 1.0
NP = 4 # fixed prompt slots; museum app zero-pads (weight 0 => exact zero contribution)
UNET_REPO = "lowlevelware/512x512_diffusion_unconditional_ImageNet"
UNET_FILE = "512x512_diffusion_uncond_finetune_008100.pt"
DATASET_REPO = os.environ.get("AOTI_DATASET_REPO", "multimodalart/clip-guided-diffusion-aoti-zerogpu")
CLIP_MODELS = ["ViT-B/16", "ViT-B/32", "ViT-L/14", "google/siglip2-large-patch16-256"]
CFG_DIRS = {"ViT-B/16": "diffstep3dyn_vitb16_512_cutn16_bf16_np4",
"ViT-B/32": "diffstep3dyn_vitb32_512_cutn16_bf16_np4",
"ViT-L/14": "diffstep3dyn_vitl14_512_cutn16_bf16_np4",
"google/siglip2-large-patch16-256": "diffstep3dyn_siglip2l16_256_512_cutn16_bf16_np4"}
def make_cfg(clip_model):
return {"tag": "diffstep3-dyn", "clip_model": clip_model, "unet": UNET_FILE,
"image_size": IMAGE_SIZE, "cutn": CUTN, "dtype": "torch.bfloat16", "n_prompts": NP}
print("Downloading UNet checkpoint...")
CKPT = hf_hub_download(UNET_REPO, UNET_FILE)
print("Loading UNet + backbones (CPU)...")
_cfg = model_and_diffusion_defaults()
_cfg.update(D.MODEL_CFG)
_cfg.update({"image_size": IMAGE_SIZE, "timestep_respacing": "250"})
unet, _ = create_model_and_diffusion(**_cfg)
unet.load_state_dict(torch.load(CKPT, map_location="cpu"))
unet.requires_grad_(False).eval()
backbones, step_mods = {}, {}
for _name in CLIP_MODELS:
backbones[_name] = bb.load_backbone(_name)
step_mods[_name] = bb.build_fusedstep(unet, backbones[_name], CUTN, IMAGE_SIZE)
@spaces.GPU(duration=900)
def gpu_compile(clip_model, out_dir, max_autotune):
dev = torch.device("cuda")
t0 = time.time()
cap = torch.cuda.get_device_capability()
arch = f"sm{cap[0]}{cap[1]}"
yield f"[{clip_model}] GPU: {torch.cuda.get_device_name(0)} ({arch}), torch {torch.__version__}"
step_mod = step_mods[clip_model].to(dev)
cut_size = backbones[clip_model]["cut_size"]
shape = (1, 3, IMAGE_SIZE, IMAGE_SIZE)
gen = torch.Generator(device=dev).manual_seed(0)
ex = D.sample_step_rands(gen, CUTN, cut_size, IMAGE_SIZE, CUT_POW, shape)
e0 = torch.zeros(NP, backbones[clip_model]["output_dim"], device=dev)
w0 = torch.full((NP,), 0.25, device=dev)
# every example input must be a DISTINCT tensor object: make_fx binds repeated
# tensors to one placeholder, silently miswiring the runtime inputs
def s(v):
return torch.full((), float(v), device=dev)
ex_sched = (torch.full((1,), 999., device=dev),
s(1.1), s(1.2), s(0.9), s(0.5), s(0.6), s(-9.), s(-8.), s(1.))
ex_args = (torch.randn(shape, device=dev), *ex_sched, e0, w0,
s(1000.), s(150.), s(50.), *ex)
yield f"[{clip_model}] [{time.time()-t0:.0f}s] tracing (make_fx, real mode)..."
from torch.fx.experimental.proxy_tensor import make_fx
with torch.device(dev):
gm = make_fx(lambda *a: step_mod(*a), tracing_mode="real")(*ex_args)
yield f"[{clip_model}] [{time.time()-t0:.0f}s] torch.export..."
ep = torch.export.export(gm, ex_args, strict=False)
yield f"[{clip_model}] [{time.time()-t0:.0f}s] AOTI compile (max_autotune={bool(max_autotune)})..."
spaces.aoti_compile_and_save(package_dir=out_dir, exported_program=ep,
inductor_configs={"max_autotune": bool(max_autotune)})
weights = {**ep.state_dict, **dict(ep.constants)}
torch.save(weights, os.path.join(out_dir, "weights.pt"))
yield f"[{clip_model}] [{time.time()-t0:.0f}s] verifying package vs eager..."
from spaces.zero.torch.aoti import LazyAOTIModel
w = {k: v.to(dev) for k, v in weights.items()}
w.update({f"{k}_cuda0": v for k, v in list(w.items())}) # inductor device aliases
fn = LazyAOTIModel(os.path.join(out_dir, "root", "package.pt2")).with_weights(w)
gv = torch.Generator(device=dev).manual_seed(7)
xv = torch.randn(shape, generator=gv, device=dev)
ev = torch.nn.functional.normalize(
torch.randn((NP, e0.shape[1]), generator=gv, device=dev), dim=1)
vr = D.sample_step_rands(torch.Generator(device=dev).manual_seed(11),
CUTN, cut_size, IMAGE_SIZE, CUT_POW, shape)
v_sched = (torch.full((1,), 501., device=dev),
s(1.05), s(0.4), s(0.7), s(0.55), s(0.45), s(-7.), s(-6.5), s(1.))
v_args = (xv, *v_sched, ev, w0, s(1000.), s(150.), s(50.), *vr)
s1, p1 = fn(*v_args)
s0_, p0 = step_mod(*v_args)
sd = (s1 - s0_).abs().max().item()
pd = (p1 - p0).abs().max().item()
yield (f"[{clip_model}] [{time.time()-t0:.0f}s] sample maxdiff={sd:.4g}, "
f"pred_xstart maxdiff={pd:.4g} (bf16 kernel-order noise; expect <0.1)")
meta = {"config": make_cfg(clip_model), "torch": torch.__version__, "arch": arch,
"device": torch.cuda.get_device_name(0), "cuda": torch.version.cuda,
"spaces": importlib_metadata.version("spaces"),
"created": datetime.now(timezone.utc).isoformat()}
with open(os.path.join(out_dir, "metadata.json"), "w") as f:
json.dump(meta, f, indent=1)
yield f"[{clip_model}] [{time.time()-t0:.0f}s] package ready ({arch}, torch {meta['torch']})"
def dataset_has_config(clip_model):
try:
p = hf_hub_download(DATASET_REPO, f"{CFG_DIRS[clip_model]}/metadata.json",
repo_type="dataset", force_download=True)
with open(p) as f:
meta = json.load(f)
return meta.get("config") == make_cfg(clip_model) and meta.get("torch") == torch.__version__
except Exception:
return False
def compile_and_publish(max_autotune):
logs = []
def out(s):
logs.append(s)
return "\n".join(logs)
token = os.environ.get("HF_TOKEN")
if not token:
yield out("ERROR: HF_TOKEN Space secret not set (needs write access to "
f"{DATASET_REPO}). Set it in Settings > Variables and secrets.")
return
api = HfApi(token=token)
for clip_model in CLIP_MODELS:
if dataset_has_config(clip_model):
yield out(f"[{clip_model}] up-to-date package already in {DATASET_REPO} — skipping")
continue
out_dir = tempfile.mkdtemp(prefix="diff_aoti_")
try:
for line in gpu_compile(clip_model, out_dir, max_autotune):
yield out(line)
except Exception:
yield out(f"[{clip_model}] COMPILE ERROR:\n" + traceback.format_exc())
continue
if not os.path.isfile(os.path.join(out_dir, "metadata.json")):
yield out(f"[{clip_model}] ERROR: no package produced")
continue
yield out(f"[{clip_model}] uploading to {DATASET_REPO}/{CFG_DIRS[clip_model]} ...")
try:
api.create_repo(DATASET_REPO, repo_type="dataset", exist_ok=True)
api.upload_folder(repo_id=DATASET_REPO, repo_type="dataset",
folder_path=out_dir, path_in_repo=CFG_DIRS[clip_model],
commit_message=f"AOTI package {CFG_DIRS[clip_model]}")
yield out(f"[{clip_model}] done: https://huggingface.co/datasets/{DATASET_REPO}"
f"/tree/main/{CFG_DIRS[clip_model]}")
except Exception:
yield out(f"[{clip_model}] UPLOAD ERROR:\n" + traceback.format_exc())
yield out("All configs processed.")
with gr.Blocks(title="CLIP-Guided Diffusion AOTI compiler") as app:
gr.Markdown("# CLIP-Guided Diffusion — ZeroGPU AOTI compiler")
gr.Markdown(f"Compiles one full guided DDPM timestep ({IMAGE_SIZE}px, cutn {CUTN}, bf16, "
f"{NP} prompt slots; schedule + guidance scales are graph inputs, so one "
f"package serves any steps/scales) for each backbone missing from "
f"`{DATASET_REPO}` ({', '.join(CLIP_MODELS)}). Configs already matching this "
f"torch version are skipped. Needs the `HF_TOKEN` secret (write).")
max_autotune = gr.Checkbox(label="max_autotune (slower compile, faster steps)", value=True)
run_btn = gr.Button("Compile & publish missing configs", variant="primary", size="lg")
log = gr.Textbox(label="Log", lines=22, interactive=False)
run_btn.click(compile_and_publish, inputs=[max_autotune], outputs=[log],
concurrency_limit=1)
if __name__ == "__main__":
app.launch() # Spaces sets GRADIO_SERVER_PORT; hardcoding a port breaks the healthcheck