# backbones.py — image/text encoder adapters (OpenAI CLIP + SigLIP2) for the gradstep engine. import torch from torch import nn import generate_fast as gf from generate_fast import clip OPENAI_MODELS = ("ViT-B/16", "ViT-B/32", "ViT-L/14") SIGLIP_NORM = ([0.5, 0.5, 0.5], [0.5, 0.5, 0.5]) class SiglipVisual(nn.Module): """(B,3,S,S) pixel values -> pooled image embedding; mimics the CLIP visual interface.""" def __init__(self, vision_model, input_resolution, output_dim): super().__init__() self.vision_model = vision_model self.input_resolution = input_resolution self.output_dim = output_dim def forward(self, x): return self.vision_model(x).pooler_output def load_backbone(name): """-> dict(kind, visual, cut_size, output_dim, encode_text(txt, device), text_module).""" if name in OPENAI_MODELS: p = clip.load(name, device="cpu", jit=False)[0].eval().requires_grad_(False) # text tower in fp32 (GradStep casts the visual to bf16; CLIP's encode_text would # otherwise cast text inputs by the visual dtype and mismatch the text weights) p.transformer.float() p.token_embedding.float() p.ln_final.float() p.positional_embedding.data = p.positional_embedding.data.float() p.text_projection.data = p.text_projection.data.float() def encode_text(txt, device): t = clip.tokenize(txt).to(device) x = p.token_embedding(t) + p.positional_embedding x = p.transformer(x.permute(1, 0, 2)).permute(1, 0, 2) x = p.ln_final(x) return x[torch.arange(x.shape[0]), t.argmax(dim=-1)] @ p.text_projection return {"kind": "openai", "visual": p.visual, "cut_size": p.visual.input_resolution, "output_dim": p.visual.output_dim, "encode_text": encode_text, "text_module": p} from transformers import AutoModel, AutoTokenizer m = AutoModel.from_pretrained(name).eval().requires_grad_(False) tok = AutoTokenizer.from_pretrained(name) res = m.config.vision_config.image_size dim = m.config.vision_config.hidden_size visual = SiglipVisual(m.vision_model, res, dim) def encode_text(txt, device): ids = tok([txt], padding="max_length", max_length=64, truncation=True, return_tensors="pt").input_ids.to(device) return m.text_model(input_ids=ids).pooler_output # transformers-5-safe return {"kind": "siglip", "visual": visual, "cut_size": res, "output_dim": dim, "encode_text": encode_text, "text_module": m.text_model} class _FP32LayerNorm(nn.LayerNorm): """CLIP-style LN: compute in fp32, cast back — cuts bf16 gradient noise.""" def forward(self, x): return nn.functional.layer_norm( x.float(), self.normalized_shape, self.weight, self.bias, self.eps).to(x.dtype) def _fp32_layernorms(module): for name, child in module.named_children(): if isinstance(child, nn.LayerNorm): ln = _FP32LayerNorm(child.normalized_shape, eps=child.eps, elementwise_affine=child.elementwise_affine) if child.elementwise_affine: ln.weight.data = child.weight.data.float() if child.bias is not None: ln.bias.data = child.bias.data.float() ln.requires_grad_(False) setattr(module, name, ln) else: _fp32_layernorms(child) def _siglip_fixups(sm, backbone): if backbone["kind"] == "siglip": # HF siglip: bf16 everywhere except LNs in fp32 (mirrors the CLIP visual policy) sm.visual.to(torch.bfloat16) _fp32_layernorms(sm.visual) mean, std = SIGLIP_NORM sm.norm_mean.copy_(torch.tensor(mean).view(1, 3, 1, 1)) sm.norm_std.copy_(torch.tensor(std).view(1, 3, 1, 1)) return sm def build_gradstep(vq, backbone, cutn, sideY, sideX): sm = gf.GradStep(vq, backbone["visual"], cutn, backbone["cut_size"], sideY, sideX, 0.0).eval() return _siglip_fixups(sm, backbone) def build_fusedstep(unet, backbone, cutn, image_size): import diffusion_fast as D # lazy: only the diffusion Spaces ship this module sm = D.FusedStep(unet, backbone["visual"], cutn, backbone["cut_size"], image_size, torch.bfloat16).eval() return _siglip_fixups(sm, backbone)