Views
No views yet

1import os
2import math
3import torch
4import numpy as np
5from PIL import Image
6
7from diffusers import (
8 QwenImageEditPipeline,
9 FlowMatchEulerDiscreteScheduler,
10)
11from diffusers.utils.torch_utils import randn_tensor
12
13# --------- ユーザー環境に合わせてここを設定 ---------
14BASE_MODEL_ID = "Qwen/Qwen-Image-Edit-2509" # 学習時と同じ
15LORA_DIR = "<PATH_TO_WEIGHT>"
16DEVICE = "cuda"
17DTYPE = torch.bfloat16
18
19CONTROL1_IMAGE_PATH = <PATH_TO_START_IMAGE>
20CONTROL2_IMAGE_PATH = <PATH_TO_END_IMAGE>
21PROMPT = "<inbetween> middle frame" #Don't change
22NEGATIVE_PROMPT = " "
23NUM_STEPS = 30
24SEED = 0
25# ===================
26
27
28def calculate_dimensions(target_area, ratio):
29 width = math.sqrt(target_area * ratio)
30 height = width / ratio
31
32 width = round(width / 32) * 32
33 height = round(height / 32) * 32
34
35 return int(width), int(height), None
36
37
38def calculate_shift(
39 image_seq_len,
40 base_seq_len: int = 256,
41 max_seq_len: int = 4096,
42 base_shift: float = 0.5,
43 max_shift: float = 1.15,
44):
45 m = (max_shift - base_shift) / (max_seq_len - base_seq_len)
46 b = base_shift - m * base_seq_len
47 mu = image_seq_len * m + b
48 return mu
49
50
51def retrieve_timesteps(
52 scheduler,
53 num_inference_steps: int = None,
54 device: torch.device | str | None = None,
55 timesteps=None,
56 sigmas=None,
57 **kwargs,
58):
59 if timesteps is not None and sigmas is not None:
60 raise ValueError("Only one of `timesteps` or `sigmas` can be passed.")
61
62 if timesteps is not None:
63 scheduler.set_timesteps(timesteps=timesteps, device=device, **kwargs)
64 timesteps = scheduler.timesteps
65 num_inference_steps = len(timesteps)
66 elif sigmas is not None:
67 scheduler.set_timesteps(sigmas=sigmas, device=device, **kwargs)
68 timesteps = scheduler.timesteps
69 num_inference_steps = len(timesteps)
70 else:
71 scheduler.set_timesteps(num_inference_steps, device=device, **kwargs)
72 timesteps = scheduler.timesteps
73
74 return timesteps, num_inference_steps
75
76
77def main():
78 torch.manual_seed(SEED)
79
80 # ---------- 1. ベースパイプライン + LoRA 読み込み ----------
81 pipe: QwenImageEditPipeline = QwenImageEditPipeline.from_pretrained(
82 BASE_MODEL_ID,
83 torch_dtype=DTYPE,
84 ).to(DEVICE)
85
86 pipe.load_lora_weights(LORA_DIR, adapter_name="my_lora")
87 pipe.set_adapters(["my_lora"], adapter_weights=[1.0])
88
89 transformer = pipe.transformer.to(DEVICE, dtype=DTYPE)
90 vae = pipe.vae.to(DEVICE, dtype=DTYPE)
91 scheduler: FlowMatchEulerDiscreteScheduler = pipe.scheduler
92 vae_scale_factor = pipe.vae_scale_factor
93 # ------------------------------------------------------------
94
95 # ---------- 2. コントロール画像読み込み & 前処理 ----------
96 control1_img = Image.open(CONTROL1_IMAGE_PATH).convert("RGB")
97 control2_img = Image.open(CONTROL2_IMAGE_PATH).convert("RGB")
98
99 # Qwen公式と同じ resize ロジック
100 image_size = control1_img.size # (W, H)
101 calculated_width, calculated_height, _ = calculate_dimensions(
102 1024 * 1024, image_size[0] / image_size[1]
103 )
104
105 # VAE + patch pack 用に multiple_of で揃える
106 multiple_of = vae_scale_factor * 2
107 width = calculated_width // multiple_of * multiple_of
108 height = calculated_height // multiple_of * multiple_of
109
110 # 公式と同じ image_processor の使い方
111 control1_resized = pipe.image_processor.resize(control1_img, calculated_height, calculated_width)
112 control2_resized = pipe.image_processor.resize(control2_img, calculated_height, calculated_width)
113
114 control1_px = pipe.image_processor.preprocess(
115 control1_resized, calculated_height, calculated_width
116 ).unsqueeze(2) # [B,3,1,H,W]
117 control2_px = pipe.image_processor.preprocess(
118 control2_resized, calculated_height, calculated_width
119 ).unsqueeze(2)
120
121 control1_px = control1_px.to(DEVICE, DTYPE)
122 control2_px = control2_px.to(DEVICE, DTYPE)
123
124 bsz = control1_px.shape[0]
125 num_channels_latents = transformer.config.in_channels // 4 # = vae.config.z_dim
126
127 # ---------- 3. コントロール画像を latent token に ----------
128 with torch.no_grad():
129 # 公式の _encode_vae_image と同等の処理
130 ctrl1_latents_5d = pipe._encode_vae_image(control1_px, generator=None) # [B, C,1,H',W']
131 ctrl2_latents_5d = pipe._encode_vae_image(control2_px, generator=None)
132
133 H_lat, W_lat = ctrl1_latents_5d.shape[3], ctrl1_latents_5d.shape[4]
134
135 # pack して transformer 入力用 token 形式に
136 ctrl1_tokens = QwenImageEditPipeline._pack_latents(
137 ctrl1_latents_5d, bsz, num_channels_latents, H_lat, W_lat
138 ) # [B, N, C*4]
139 ctrl2_tokens = QwenImageEditPipeline._pack_latents(
140 ctrl2_latents_5d, bsz, num_channels_latents, H_lat, W_lat
141 )
142
143 # ---------- 4. target latent をランダム初期化 ----------
144 # 公式の prepare_latents と同じ形状
145 height_lat = 2 * (height // (vae_scale_factor * 2))
146 width_lat = 2 * (width // (vae_scale_factor * 2))
147
148 shape = (bsz, 1, num_channels_latents, height_lat, width_lat)
149 latents_5d = randn_tensor(shape, device=DEVICE, dtype=DTYPE)
150 latents = QwenImageEditPipeline._pack_latents(
151 latents_5d, bsz, num_channels_latents, height_lat, width_lat
152 ) # ここからはずっと [B,N,C*4] で回す
153
154 # ---------- 5. テキスト埋め込み ----------
155 with torch.no_grad():
156 prompt_embeds, prompt_embeds_mask = pipe.encode_prompt(
157 image=control1_resized, # 公式と同様: resize 済み画像
158 prompt=[PROMPT],
159 device=DEVICE,
160 num_images_per_prompt=1,
161 max_sequence_length=1024,
162 )
163 txt_seq_lens = prompt_embeds_mask.sum(dim=1).tolist()
164
165 # 3ストリーム分の img_shapes(rotary 用)
166 img_shapes = [[
167 (1, height_lat // 2, width_lat // 2), # target
168 (1, height_lat // 2, width_lat // 2), # control1
169 (1, height_lat // 2, width_lat // 2), # control2
170 ]] * bsz
171
172 # 必要であれば rotary 埋め込みを事前計算する実装の場合:
173 # image_rotary_emb = transformer.pos_embed(img_shapes, txt_seq_lens, device=DEVICE)
174
175 # ---------- 6. scheduler timesteps 準備 (公式準拠) ----------
176 sigmas = np.linspace(1.0, 1.0 / NUM_STEPS, NUM_STEPS)
177 image_seq_len = latents.shape[1] # token 数
178
179 mu = calculate_shift(
180 image_seq_len,
181 scheduler.config.get("base_image_seq_len", 256),
182 scheduler.config.get("max_image_seq_len", 4096),
183 scheduler.config.get("base_shift", 0.5),
184 scheduler.config.get("max_shift", 1.15),
185 )
186
187 timesteps, _ = retrieve_timesteps(
188 scheduler,
189 NUM_STEPS,
190 device=DEVICE,
191 sigmas=sigmas,
192 mu=mu,
193 )
194
195 scheduler.set_begin_index(0)
196
197 # guidance は使わない(multi-control だけ)
198 guidance = None
199
200 # ---------- 7. 反復推論ループ ----------
201 with torch.no_grad():
202 for i, t in enumerate(timesteps):
203 # transformer 入力: target + control1 + control2 を token 軸で concat
204 latent_model_input = torch.cat([latents, ctrl1_tokens, ctrl2_tokens], dim=1)
205
206 # timestep をバッチ分にブロードキャスト
207 timestep_batch = t.expand(latents.shape[0]).to(latents.dtype)
208
209 model_pred_all = transformer(
210 hidden_states=latent_model_input,
211 timestep=timestep_batch / 1000.0, # 学習時と同じスケール
212 guidance=guidance,
213 encoder_hidden_states=prompt_embeds,
214 encoder_hidden_states_mask=prompt_embeds_mask,
215 img_shapes=img_shapes, # あなたの diffusers 版ではこれでOK
216 txt_seq_lens=txt_seq_lens,
217 # もし新しい API なら:
218 # image_rotary_emb=image_rotary_emb,
219 # attention_kwargs=None,
220 return_dict=False,
221 )[0] # [B, N_total, C*4]
222
223 # 先頭の target 分だけ取り出す
224 model_pred = model_pred_all[:, : latents.size(1)]
225
226 latents_dtype = latents.dtype
227 latents = scheduler.step(model_pred, t, latents, return_dict=False)[0]
228 if latents.dtype != latents_dtype:
229 latents = latents.to(latents_dtype)
230
231 # ---------- 8. decode ----------
232 # packed token -> 5D latent へ
233 latents = QwenImageEditPipeline._unpack_latents(
234 latents,
235 height=height,
236 width=width,
237 vae_scale_factor=vae_scale_factor,
238 ) # [B, C,1,H_lat,W_lat]
239
240 latents = latents.to(vae.dtype)
241
242 # latents_mean / std を戻す(公式と同じ)
243 latents_mean = (
244 torch.tensor(vae.config.latents_mean)
245 .view(1, vae.config.z_dim, 1, 1, 1)
246 .to(latents.device, latents.dtype)
247 )
248 latents_std = 1.0 / torch.tensor(vae.config.latents_std).view(
249 1, vae.config.z_dim, 1, 1, 1
250 ).to(latents.device, latents.dtype)
251
252 latents = latents / latents_std + latents_mean # [B,C,1,H_lat,W_lat]
253
254 # decode (T=1 の 0 フレームだけ使う)
255 with torch.no_grad():
256 image_latents = latents
257 decoded = vae.decode(image_latents, return_dict=False)[0] # [B,3,1,H,W]
258 images = decoded[:, :, 0, :, :] # [B,3,H,W]
259
260 # Qwen の image_processor で [-1,1] -> PIL
261 images = pipe.image_processor.postprocess(images, output_type="pil")
262 out: Image.Image = images[0]
263 out.save("multi_control_from_controls.png")
264 print("saved to multi_control_from_controls.png")
265
266
267if __name__ == "__main__":
268 main()
269