BiliSakura commited on
Commit
ccbbabe
·
verified ·
1 Parent(s): 46e24b7

Update JiT-B-16-SIM/pipeline.py

Browse files
Files changed (1) hide show
  1. JiT-B-16-SIM/pipeline.py +280 -0
JiT-B-16-SIM/pipeline.py ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Hub custom pipeline: JiTPipeline for FD-Loss post-trained JiT checkpoints.
2
+
3
+ Uses FD-Loss sampling (legacy time convention, velocity Euler/Heun, t: 1→0).
4
+ See libs/FD-Loss-diffusers and scripts/evaluate_released_ckpt.sh (JiT_B preset).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from pathlib import Path
11
+ from typing import Dict, List, Optional, Tuple, Union
12
+
13
+ import torch
14
+ from diffusers.pipelines.pipeline_utils import DiffusionPipeline, ImagePipelineOutput
15
+ from diffusers.utils.torch_utils import randn_tensor
16
+
17
+ from scheduling_flow_match_fd import FDLossFlowMatchScheduler
18
+
19
+ RECOMMENDED_NOISE_BY_SIZE = {
20
+ 256: 1.0,
21
+ 512: 2.0,
22
+ }
23
+
24
+ RECOMMENDED_CFG_BY_VARIANT = {
25
+ "JiT-B": 3.0,
26
+ "JiT-L": 2.4,
27
+ "JiT-H": 2.2,
28
+ }
29
+
30
+
31
+ class JiTPipeline(DiffusionPipeline):
32
+ r"""
33
+ Pipeline for FD-Loss post-trained JiT (flow matching, legacy time convention).
34
+
35
+ Parameters:
36
+ transformer ([`JiTTransformer2DModel`]):
37
+ Class-conditioned JiT backbone.
38
+ scheduler ([`FDLossFlowMatchScheduler`]):
39
+ Flow timesteps from 1 (noise) to 0 (data).
40
+ legacy_time_convention (`bool`, *optional*, defaults to `True`):
41
+ Flip flow time when passing to the backbone (`t_bb = 1 - t`), as in FD-Loss training.
42
+ id2label (`dict[int, str]`, *optional*):
43
+ ImageNet class id to English label mapping.
44
+ """
45
+
46
+ model_cpu_offload_seq = "transformer"
47
+
48
+ def __init__(
49
+ self,
50
+ transformer,
51
+ scheduler=None,
52
+ id2label: Optional[Dict[Union[int, str], str]] = None,
53
+ legacy_time_convention: bool = True,
54
+ ):
55
+ super().__init__()
56
+ if scheduler is None:
57
+ scheduler = FDLossFlowMatchScheduler()
58
+ self.register_modules(transformer=transformer, scheduler=scheduler)
59
+ self.legacy_time_convention = legacy_time_convention
60
+ self._id2label = self._normalize_id2label(id2label)
61
+ self.labels = self._build_label2id(self._id2label)
62
+ self._labels_loaded_from_model_index = bool(self._id2label)
63
+
64
+ def _backbone_t(self, t: torch.Tensor) -> torch.Tensor:
65
+ if self.legacy_time_convention:
66
+ return 1.0 - t
67
+ return t
68
+
69
+ def _normalize_class_labels(
70
+ self,
71
+ class_labels: Union[int, str, List[Union[int, str]]],
72
+ ) -> List[int]:
73
+ if isinstance(class_labels, int):
74
+ return [class_labels]
75
+ if isinstance(class_labels, str):
76
+ return self.get_label_ids(class_labels)
77
+ if class_labels and isinstance(class_labels[0], str):
78
+ return self.get_label_ids(class_labels)
79
+ return list(class_labels)
80
+
81
+ def _forward_velocity(
82
+ self,
83
+ z: torch.Tensor,
84
+ t: torch.Tensor,
85
+ labels: torch.Tensor,
86
+ guidance_scale: float,
87
+ cfg_interval: Optional[Tuple[float, float]],
88
+ t_eps: float,
89
+ ) -> torch.Tensor:
90
+ t_view = t.reshape(-1, *([1] * (z.ndim - 1)))
91
+ t_bb = self._backbone_t(t).flatten().expand(z.shape[0])
92
+
93
+ x_cond = self.transformer(
94
+ z,
95
+ timestep=t_bb,
96
+ class_labels=labels,
97
+ interpolate_pos_encoding=interpolate_pos_encoding,
98
+ ).sample
99
+ v_cond = (z - x_cond) / t_view.clamp_min(t_eps)
100
+
101
+ if guidance_scale == 1.0:
102
+ return v_cond
103
+
104
+ null_class = int(
105
+ getattr(self.transformer.config, "num_classes", getattr(self.transformer.config, "num_class_embeds", 1000))
106
+ )
107
+ class_null = torch.full_like(labels, null_class)
108
+ x_uncond = self.transformer(
109
+ z,
110
+ timestep=t_bb,
111
+ class_labels=class_null,
112
+ interpolate_pos_encoding=interpolate_pos_encoding,
113
+ ).sample
114
+ v_uncond = (z - x_uncond) / t_view.clamp_min(t_eps)
115
+
116
+ if cfg_interval is None:
117
+ return v_uncond + guidance_scale * (v_cond - v_uncond)
118
+
119
+ low, high = cfg_interval
120
+ mask = (t < high) & ((low == 0) | (t > low))
121
+ scale = torch.where(
122
+ mask,
123
+ torch.tensor(guidance_scale, device=z.device, dtype=z.dtype),
124
+ torch.tensor(1.0, device=z.device, dtype=z.dtype),
125
+ )
126
+ while scale.ndim < v_cond.ndim:
127
+ scale = scale.unsqueeze(-1)
128
+ return v_uncond + scale * (v_cond - v_uncond)
129
+
130
+ @staticmethod
131
+ def _normalize_id2label(id2label: Optional[Dict[Union[int, str], str]]) -> Dict[int, str]:
132
+ if not id2label:
133
+ return {}
134
+ return {int(key): value for key, value in id2label.items()}
135
+
136
+ @staticmethod
137
+ def _read_id2label_from_model_index(variant_path: Optional[str]) -> Dict[int, str]:
138
+ if not variant_path:
139
+ return {}
140
+ variant_dir = Path(variant_path).resolve()
141
+ model_index_path = variant_dir / "model_index.json"
142
+ if not model_index_path.exists():
143
+ return {}
144
+ raw = json.loads(model_index_path.read_text(encoding="utf-8"))
145
+ id2label = raw.get("id2label")
146
+ if not isinstance(id2label, dict):
147
+ return {}
148
+ return {int(key): value for key, value in id2label.items()}
149
+
150
+ @staticmethod
151
+ def _build_label2id(id2label: Dict[int, str]) -> Dict[str, int]:
152
+ label2id: Dict[str, int] = {}
153
+ for class_id, value in id2label.items():
154
+ for synonym in value.split(","):
155
+ synonym = synonym.strip()
156
+ if synonym:
157
+ label2id[synonym] = int(class_id)
158
+ return dict(sorted(label2id.items()))
159
+
160
+ def _ensure_labels_loaded(self) -> None:
161
+ if self._labels_loaded_from_model_index:
162
+ return
163
+ loaded = self._read_id2label_from_model_index(getattr(self.config, "_name_or_path", None))
164
+ if loaded:
165
+ self._id2label = loaded
166
+ self.labels = self._build_label2id(self._id2label)
167
+ self._labels_loaded_from_model_index = True
168
+
169
+ @property
170
+ def id2label(self) -> Dict[int, str]:
171
+ self._ensure_labels_loaded()
172
+ return self._id2label
173
+
174
+ def get_label_ids(self, label: Union[str, List[str]]) -> List[int]:
175
+ self._ensure_labels_loaded()
176
+ label2id = self.labels
177
+ if not label2id:
178
+ raise ValueError("No English labels loaded. Ensure `id2label` exists in model_index.json.")
179
+ if isinstance(label, str):
180
+ label = [label]
181
+ missing = [item for item in label if item not in label2id]
182
+ if missing:
183
+ preview = ", ".join(list(label2id.keys())[:8])
184
+ raise ValueError(f"Unknown English label(s): {missing}. Example valid labels: {preview}, ...")
185
+ return [label2id[item] for item in label]
186
+
187
+ @torch.inference_mode()
188
+ def __call__(
189
+ self,
190
+ class_labels: Union[int, str, List[Union[int, str]]],
191
+ num_inference_steps: int = 1,
192
+ guidance_scale: float = 3.0,
193
+ guidance_interval_min: float = 0.1,
194
+ guidance_interval_max: float = 1.0,
195
+ sampling_method: str = "euler",
196
+ noise_scale: Optional[float] = None,
197
+ t_eps: float = 5e-2,
198
+ generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
199
+ height: Optional[int] = None,
200
+ width: Optional[int] = None,
201
+ interpolate_pos_encoding: bool = True,
202
+ output_type: Optional[str] = "pil",
203
+ return_dict: bool = True,
204
+ ) -> Union[ImagePipelineOutput, Tuple]:
205
+ if num_inference_steps < 1:
206
+ raise ValueError("num_inference_steps must be >= 1.")
207
+ if output_type not in {"pil", "np", "pt"}:
208
+ raise ValueError("output_type must be one of: 'pil', 'np', 'pt'.")
209
+ if sampling_method not in {"euler", "heun"}:
210
+ raise ValueError("sampling_method must be 'euler' or 'heun'.")
211
+
212
+ class_label_ids = self._normalize_class_labels(class_labels)
213
+ batch_size = len(class_label_ids)
214
+ image_size = int(self.transformer.config.sample_size)
215
+ patch_size = int(self.transformer.config.patch_size)
216
+ height = int(height or image_size)
217
+ width = int(width or image_size)
218
+ if height % patch_size != 0 or width % patch_size != 0:
219
+ raise ValueError(
220
+ f"height and width must be divisible by patch_size={patch_size}. Got {(height, width)}."
221
+ )
222
+ channels = int(self.transformer.config.in_channels)
223
+
224
+ if noise_scale is None:
225
+ noise_scale = RECOMMENDED_NOISE_BY_SIZE.get(max(height, width), 1.0)
226
+
227
+ z = randn_tensor(
228
+ shape=(batch_size, channels, height, width),
229
+ generator=generator,
230
+ device=self._execution_device,
231
+ dtype=self.transformer.dtype,
232
+ ) * noise_scale
233
+
234
+ labels = torch.tensor(class_label_ids, device=self._execution_device, dtype=torch.long).reshape(-1)
235
+ null_class_val = int(
236
+ getattr(self.transformer.config, "num_classes", getattr(self.transformer.config, "num_class_embeds", 1000))
237
+ )
238
+ labels = labels.clamp(0, null_class_val - 1)
239
+
240
+ cfg_interval = [
241
+ float(self._backbone_t(torch.tensor(guidance_interval_min, device=z.device))),
242
+ float(self._backbone_t(torch.tensor(guidance_interval_max, device=z.device))),
243
+ ]
244
+ cfg_interval = (min(cfg_interval), max(cfg_interval))
245
+
246
+ timesteps = self.scheduler.set_timesteps(num_inference_steps, device=self._execution_device)
247
+ ts = timesteps.view(-1, *([1] * z.ndim)).expand(-1, batch_size, -1, -1, -1)
248
+
249
+ for i in self.progress_bar(range(num_inference_steps - 1)):
250
+ t_cur = ts[i]
251
+ t_next = ts[i + 1]
252
+ if sampling_method == "heun":
253
+ dt = t_next - t_cur
254
+ v1 = self._forward_velocity(z, t_cur, labels, guidance_scale, cfg_interval, t_eps)
255
+ z_mid = z + dt * v1
256
+ v2 = self._forward_velocity(z_mid, t_next, labels, guidance_scale, cfg_interval, t_eps)
257
+ z = z + dt * 0.5 * (v1 + v2)
258
+ else:
259
+ v = self._forward_velocity(z, t_cur, labels, guidance_scale, cfg_interval, t_eps)
260
+ z = z + (t_next - t_cur) * v
261
+
262
+ if num_inference_steps >= 1:
263
+ t_cur = ts[-2]
264
+ t_next = ts[-1]
265
+ v = self._forward_velocity(z, t_cur, labels, guidance_scale, cfg_interval, t_eps)
266
+ z = z + (t_next - t_cur) * v
267
+
268
+ images_pt = ((z.float().clamp(-1, 1) + 1.0) / 2.0).cpu()
269
+ if output_type == "pt":
270
+ images = images_pt
271
+ elif output_type == "np":
272
+ images = images_pt.permute(0, 2, 3, 1).numpy()
273
+ else:
274
+ images = self.numpy_to_pil(images_pt.permute(0, 2, 3, 1).numpy())
275
+
276
+ self.maybe_free_model_hooks()
277
+
278
+ if not return_dict:
279
+ return (images,)
280
+ return ImagePipelineOutput(images=images)