cuio commited on
Commit
85d0fdc
·
verified ·
1 Parent(s): 52e7886

Upload flux2_adapter (1).py

Browse files
Files changed (1) hide show
  1. flux2_adapter (1).py +118 -0
flux2_adapter (1).py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Minimal vendored FLUX.2 klein adapter for the HF Space (from FD-Loss train/flux2_adapter.py).
2
+
3
+ Differences vs the training adapter: flux2 is pip-installed (no sys.path hack), weights come from a
4
+ provided state_dict (the epfl-vita/flux2-klein-1step-rdm model.safetensors, "model."-prefixed keys OK),
5
+ no grad-checkpoint / compile / disc-feature paths.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import torch
10
+ import torch.nn as nn
11
+
12
+ FLUX2_VAE_DOWNSAMPLE = 16
13
+ FLUX2_LATENT_CHANNELS = 128
14
+
15
+
16
+ class Flux2AdapterModel(nn.Module):
17
+ """FLUX.2 klein-4B MM-DiT + 1-step (or N-step) flow-matching Euler sampler."""
18
+
19
+ def __init__(self, state_dict: dict, image_resolution: int = 512,
20
+ param_dtype: torch.dtype = torch.bfloat16, guidance: float = 1.0):
21
+ super().__init__()
22
+ import flux2.model as _fm
23
+ from flux2 import sampling as _sampling
24
+
25
+ self.image_resolution = int(image_resolution)
26
+ self.guidance = float(guidance)
27
+ assert self.image_resolution % FLUX2_VAE_DOWNSAMPLE == 0
28
+
29
+ params = _fm.Klein4BParams()
30
+ assert params.in_channels == FLUX2_LATENT_CHANNELS
31
+ with torch.device("meta"):
32
+ model = _fm.Flux2(params).to(torch.bfloat16)
33
+ if all(k.startswith("model.") for k in list(state_dict.keys())[:8]):
34
+ state_dict = {k[len("model."):]: v for k, v in state_dict.items()}
35
+ model.load_state_dict(state_dict, strict=True, assign=True)
36
+ self.model = model.to(dtype=param_dtype)
37
+
38
+ self._batched_prc_img = _sampling.batched_prc_img
39
+ self._batched_prc_txt = _sampling.batched_prc_txt
40
+ self._get_schedule = _sampling.get_schedule
41
+ self._timestep_embedding = _fm.timestep_embedding
42
+
43
+ self.in_channels = FLUX2_LATENT_CHANNELS
44
+ self.input_size = self.image_resolution // FLUX2_VAE_DOWNSAMPLE
45
+
46
+ @property
47
+ def device(self) -> torch.device:
48
+ return next(self.model.parameters()).device
49
+
50
+ def _run_dit(self, x, x_ids, ctx, ctx_ids, t_vec):
51
+ m = self.model
52
+ num_txt_tokens = ctx.shape[1]
53
+ vec = m.time_in(self._timestep_embedding(t_vec, 256))
54
+ if getattr(m, "use_guidance_embed", False): # klein: False -> skipped
55
+ guid = torch.full((x.shape[0],), self.guidance, dtype=x.dtype, device=x.device)
56
+ vec = vec + m.guidance_in(self._timestep_embedding(guid, 256))
57
+ mod_img = m.double_stream_modulation_img(vec)
58
+ mod_txt = m.double_stream_modulation_txt(vec)
59
+ single_mod, _ = m.single_stream_modulation(vec)
60
+ img = m.img_in(x)
61
+ txt = m.txt_in(ctx)
62
+ pe_x = m.pe_embedder(x_ids)
63
+ pe_ctx = m.pe_embedder(ctx_ids)
64
+ for block in m.double_blocks:
65
+ img, txt, _ = block.forward_kv_extract(img, txt, pe_x, pe_ctx, mod_img, mod_txt, 0)
66
+ img = torch.cat((txt, img), dim=1)
67
+ pe = torch.cat((pe_ctx, pe_x), dim=2)
68
+ for block in m.single_blocks:
69
+ img, _ = block.forward_kv_extract(img, pe, single_mod, num_txt_tokens, 0)
70
+ img = img[:, num_txt_tokens:, ...]
71
+ return m.final_layer(img, vec)
72
+
73
+ def sample_images_with_grad(self, noise: torch.Tensor, condition: torch.Tensor,
74
+ sampling_args: dict) -> torch.Tensor:
75
+ """noise (B,128,H,W) + Qwen3 ctx (B,L,7680) -> normalized latents (B,128,H,W)."""
76
+ B = noise.shape[0]
77
+ ctx = condition.to(device=noise.device, dtype=noise.dtype)
78
+ x, x_ids = self._batched_prc_img(noise)
79
+ ctx, ctx_ids = self._batched_prc_txt(ctx)
80
+ H, W = noise.shape[-2], noise.shape[-1]
81
+ num_steps = int(sampling_args.get("num_steps", 1))
82
+ timesteps = self._get_schedule(num_steps, x.shape[1])
83
+ for t_curr, t_prev in zip(timesteps[:-1], timesteps[1:]):
84
+ t_vec = torch.full((B,), t_curr, dtype=x.dtype, device=x.device)
85
+ pred = self._run_dit(x, x_ids, ctx, ctx_ids, t_vec)
86
+ x = x + (t_prev - t_curr) * pred
87
+ from einops import rearrange
88
+ return rearrange(x, "b (h w) c -> b c h w", h=H, w=W)
89
+
90
+
91
+ class Flux2VAETokenizer(nn.Module):
92
+ """Native FLUX.2 AutoEncoder (BFL ae.safetensors). decode -> [-1,1]; detokenize -> [0,1]."""
93
+
94
+ def __init__(self, ae_path: str, device="cpu", torch_dtype: torch.dtype = torch.bfloat16):
95
+ super().__init__()
96
+ from flux2.autoencoder import AutoEncoder, AutoEncoderParams
97
+ from safetensors.torch import load_file as load_sft
98
+
99
+ with torch.device("meta"):
100
+ ae = AutoEncoder(AutoEncoderParams())
101
+ sd = load_sft(ae_path, device="cpu")
102
+ ae.load_state_dict(sd, strict=True, assign=True)
103
+ ae = ae.to(device=device, dtype=torch_dtype)
104
+ for p in ae.parameters():
105
+ p.requires_grad = False
106
+ ae.eval()
107
+ self.vae = ae
108
+
109
+ def denormalize_z(self, z):
110
+ return z # AE.decode applies inv-normalize internally
111
+
112
+ def decode(self, z):
113
+ z = z.to(dtype=next(self.vae.parameters()).dtype)
114
+ return self.vae.decode(z)
115
+
116
+ @torch.inference_mode()
117
+ def detokenize(self, z):
118
+ return torch.clamp(self.decode(self.denormalize_z(z)) * 0.5 + 0.5, 0.0, 1.0)