comdoleger commited on
Commit
d5548b9
·
verified ·
1 Parent(s): 668242b

Upload scripts/convert_diffusers_to_comfy.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. scripts/convert_diffusers_to_comfy.py +426 -0
scripts/convert_diffusers_to_comfy.py ADDED
@@ -0,0 +1,426 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #######################################################
2
+ # Convert Diffusers Flux/Flex to all in one ComfyUI safetensors file
3
+ # The VAE, T5 and clip will all be in the safetensors file
4
+ # T5 will always be 8bit with the all in one file
5
+ # You can save the transformer weights as bf16 or 8-bit with the --do_8_bit flag
6
+ #
7
+ # Download a reference model from Huggingface
8
+ # https://huggingface.co/Comfy-Org/flux1-dev/blob/main/flux1-dev-fp8.safetensors
9
+ #
10
+ # Call like this for 8-bit transformer weights:
11
+ # python convert_flux_diffusers_to_orig.py /path/to/diffusers/checkpoint /path/to/flux1-dev-fp8.safetensors /output/path/my_finetune.safetensors --do_8_bit
12
+ #
13
+ # Call like this for bf16 transformer weights:
14
+ # python convert_flux_diffusers_to_orig.py /path/to/diffusers/checkpoint /path/to/flux1-dev-fp8.safetensors /output/path/my_finetune.safetensors
15
+ #
16
+ #######################################################
17
+
18
+
19
+ import argparse
20
+ from datetime import date
21
+ import json
22
+ import os
23
+ from pathlib import Path
24
+ import safetensors
25
+ import safetensors.torch
26
+ import torch
27
+ import tqdm
28
+ from collections import OrderedDict
29
+
30
+
31
+ parser = argparse.ArgumentParser()
32
+
33
+ parser.add_argument("diffusers_path", type=str,
34
+ help="Path to the original Flux diffusers folder.")
35
+ parser.add_argument("quantized_state_dict_path", type=str,
36
+ help="Path to the ComfyUI all in one template file.")
37
+ parser.add_argument("flux_path", type=str,
38
+ help="Output path for the Flux safetensors file.")
39
+ parser.add_argument("--do_8_bit", action="store_true",
40
+ help="Use 8-bit weights instead of bf16.")
41
+ args = parser.parse_args()
42
+
43
+ flux_path = Path(args.flux_path)
44
+ diffusers_path = Path(args.diffusers_path, "transformer")
45
+ quantized_state_dict_path = Path(args.quantized_state_dict_path)
46
+
47
+ do_8_bit = args.do_8_bit
48
+
49
+ if not os.path.exists(flux_path.parent):
50
+ os.makedirs(flux_path.parent)
51
+
52
+ if not diffusers_path.exists():
53
+ print(f"Error: Missing transformer folder: {diffusers_path}")
54
+ exit()
55
+
56
+ original_json_path = Path.joinpath(
57
+ diffusers_path, "diffusion_pytorch_model.safetensors.index.json")
58
+ if not original_json_path.exists():
59
+ print(f"Error: Missing transformer index json: {original_json_path}")
60
+ exit()
61
+
62
+ if not os.path.exists(quantized_state_dict_path):
63
+ print(
64
+ f"Error: Missing quantized state dict file: {args.quantized_state_dict_path}")
65
+ exit()
66
+
67
+ with open(original_json_path, "r", encoding="utf-8") as f:
68
+ original_json = json.load(f)
69
+
70
+ diffusers_map = {
71
+ "time_in.in_layer.weight": [
72
+ "time_text_embed.timestep_embedder.linear_1.weight",
73
+ ],
74
+ "time_in.in_layer.bias": [
75
+ "time_text_embed.timestep_embedder.linear_1.bias",
76
+ ],
77
+ "time_in.out_layer.weight": [
78
+ "time_text_embed.timestep_embedder.linear_2.weight",
79
+ ],
80
+ "time_in.out_layer.bias": [
81
+ "time_text_embed.timestep_embedder.linear_2.bias",
82
+ ],
83
+ "vector_in.in_layer.weight": [
84
+ "time_text_embed.text_embedder.linear_1.weight",
85
+ ],
86
+ "vector_in.in_layer.bias": [
87
+ "time_text_embed.text_embedder.linear_1.bias",
88
+ ],
89
+ "vector_in.out_layer.weight": [
90
+ "time_text_embed.text_embedder.linear_2.weight",
91
+ ],
92
+ "vector_in.out_layer.bias": [
93
+ "time_text_embed.text_embedder.linear_2.bias",
94
+ ],
95
+ "guidance_in.in_layer.weight": [
96
+ "time_text_embed.guidance_embedder.linear_1.weight",
97
+ ],
98
+ "guidance_in.in_layer.bias": [
99
+ "time_text_embed.guidance_embedder.linear_1.bias",
100
+ ],
101
+ "guidance_in.out_layer.weight": [
102
+ "time_text_embed.guidance_embedder.linear_2.weight",
103
+ ],
104
+ "guidance_in.out_layer.bias": [
105
+ "time_text_embed.guidance_embedder.linear_2.bias",
106
+ ],
107
+ "txt_in.weight": [
108
+ "context_embedder.weight",
109
+ ],
110
+ "txt_in.bias": [
111
+ "context_embedder.bias",
112
+ ],
113
+ "img_in.weight": [
114
+ "x_embedder.weight",
115
+ ],
116
+ "img_in.bias": [
117
+ "x_embedder.bias",
118
+ ],
119
+ "double_blocks.().img_mod.lin.weight": [
120
+ "norm1.linear.weight",
121
+ ],
122
+ "double_blocks.().img_mod.lin.bias": [
123
+ "norm1.linear.bias",
124
+ ],
125
+ "double_blocks.().txt_mod.lin.weight": [
126
+ "norm1_context.linear.weight",
127
+ ],
128
+ "double_blocks.().txt_mod.lin.bias": [
129
+ "norm1_context.linear.bias",
130
+ ],
131
+ "double_blocks.().img_attn.qkv.weight": [
132
+ "attn.to_q.weight",
133
+ "attn.to_k.weight",
134
+ "attn.to_v.weight",
135
+ ],
136
+ "double_blocks.().img_attn.qkv.bias": [
137
+ "attn.to_q.bias",
138
+ "attn.to_k.bias",
139
+ "attn.to_v.bias",
140
+ ],
141
+ "double_blocks.().txt_attn.qkv.weight": [
142
+ "attn.add_q_proj.weight",
143
+ "attn.add_k_proj.weight",
144
+ "attn.add_v_proj.weight",
145
+ ],
146
+ "double_blocks.().txt_attn.qkv.bias": [
147
+ "attn.add_q_proj.bias",
148
+ "attn.add_k_proj.bias",
149
+ "attn.add_v_proj.bias",
150
+ ],
151
+ "double_blocks.().img_attn.norm.query_norm.scale": [
152
+ "attn.norm_q.weight",
153
+ ],
154
+ "double_blocks.().img_attn.norm.key_norm.scale": [
155
+ "attn.norm_k.weight",
156
+ ],
157
+ "double_blocks.().txt_attn.norm.query_norm.scale": [
158
+ "attn.norm_added_q.weight",
159
+ ],
160
+ "double_blocks.().txt_attn.norm.key_norm.scale": [
161
+ "attn.norm_added_k.weight",
162
+ ],
163
+ "double_blocks.().img_mlp.0.weight": [
164
+ "ff.net.0.proj.weight",
165
+ ],
166
+ "double_blocks.().img_mlp.0.bias": [
167
+ "ff.net.0.proj.bias",
168
+ ],
169
+ "double_blocks.().img_mlp.2.weight": [
170
+ "ff.net.2.weight",
171
+ ],
172
+ "double_blocks.().img_mlp.2.bias": [
173
+ "ff.net.2.bias",
174
+ ],
175
+ "double_blocks.().txt_mlp.0.weight": [
176
+ "ff_context.net.0.proj.weight",
177
+ ],
178
+ "double_blocks.().txt_mlp.0.bias": [
179
+ "ff_context.net.0.proj.bias",
180
+ ],
181
+ "double_blocks.().txt_mlp.2.weight": [
182
+ "ff_context.net.2.weight",
183
+ ],
184
+ "double_blocks.().txt_mlp.2.bias": [
185
+ "ff_context.net.2.bias",
186
+ ],
187
+ "double_blocks.().img_attn.proj.weight": [
188
+ "attn.to_out.0.weight",
189
+ ],
190
+ "double_blocks.().img_attn.proj.bias": [
191
+ "attn.to_out.0.bias",
192
+ ],
193
+ "double_blocks.().txt_attn.proj.weight": [
194
+ "attn.to_add_out.weight",
195
+ ],
196
+ "double_blocks.().txt_attn.proj.bias": [
197
+ "attn.to_add_out.bias",
198
+ ],
199
+ "single_blocks.().modulation.lin.weight": [
200
+ "norm.linear.weight",
201
+ ],
202
+ "single_blocks.().modulation.lin.bias": [
203
+ "norm.linear.bias",
204
+ ],
205
+ "single_blocks.().linear1.weight": [
206
+ "attn.to_q.weight",
207
+ "attn.to_k.weight",
208
+ "attn.to_v.weight",
209
+ "proj_mlp.weight",
210
+ ],
211
+ "single_blocks.().linear1.bias": [
212
+ "attn.to_q.bias",
213
+ "attn.to_k.bias",
214
+ "attn.to_v.bias",
215
+ "proj_mlp.bias",
216
+ ],
217
+ "single_blocks.().linear2.weight": [
218
+ "proj_out.weight",
219
+ ],
220
+ "single_blocks.().norm.query_norm.scale": [
221
+ "attn.norm_q.weight",
222
+ ],
223
+ "single_blocks.().norm.key_norm.scale": [
224
+ "attn.norm_k.weight",
225
+ ],
226
+ "single_blocks.().linear2.weight": [
227
+ "proj_out.weight",
228
+ ],
229
+ "single_blocks.().linear2.bias": [
230
+ "proj_out.bias",
231
+ ],
232
+ "final_layer.linear.weight": [
233
+ "proj_out.weight",
234
+ ],
235
+ "final_layer.linear.bias": [
236
+ "proj_out.bias",
237
+ ],
238
+ "final_layer.adaLN_modulation.1.weight": [
239
+ "norm_out.linear.weight",
240
+ ],
241
+ "final_layer.adaLN_modulation.1.bias": [
242
+ "norm_out.linear.bias",
243
+ ],
244
+ }
245
+
246
+
247
+ def is_in_diffusers_map(k):
248
+ for values in diffusers_map.values():
249
+ for value in values:
250
+ if k.endswith(value):
251
+ return True
252
+ return False
253
+
254
+
255
+ diffusers = {k: Path.joinpath(diffusers_path, v)
256
+ for k, v in original_json["weight_map"].items() if is_in_diffusers_map(k)}
257
+
258
+ original_safetensors = set(diffusers.values())
259
+
260
+ # determine the number of transformer blocks
261
+ transformer_blocks = 0
262
+ single_transformer_blocks = 0
263
+ for key in diffusers.keys():
264
+ print(key)
265
+ if key.startswith("transformer_blocks."):
266
+ print(key)
267
+ block = int(key.split(".")[1])
268
+ if block >= transformer_blocks:
269
+ transformer_blocks = block + 1
270
+ elif key.startswith("single_transformer_blocks."):
271
+ block = int(key.split(".")[1])
272
+ if block >= single_transformer_blocks:
273
+ single_transformer_blocks = block + 1
274
+
275
+ print(f"Transformer blocks: {transformer_blocks}")
276
+ print(f"Single transformer blocks: {single_transformer_blocks}")
277
+
278
+ for file in original_safetensors:
279
+ if not file.exists():
280
+ print(f"Error: Missing transformer safetensors file: {file}")
281
+ exit()
282
+
283
+ original_safetensors = {f: safetensors.safe_open(
284
+ f, framework="pt", device="cpu") for f in original_safetensors}
285
+
286
+
287
+ def swap_scale_shift(weight):
288
+ shift, scale = weight.chunk(2, dim=0)
289
+ new_weight = torch.cat([scale, shift], dim=0)
290
+ return new_weight
291
+
292
+
293
+ flux_values = {}
294
+
295
+ for b in range(transformer_blocks):
296
+ for key, weights in diffusers_map.items():
297
+ if key.startswith("double_blocks."):
298
+ block_prefix = f"transformer_blocks.{b}."
299
+ found = True
300
+ for weight in weights:
301
+ if not (f"{block_prefix}{weight}" in diffusers):
302
+ found = False
303
+ if found:
304
+ flux_values[key.replace("()", f"{b}")] = [
305
+ f"{block_prefix}{weight}" for weight in weights]
306
+ for b in range(single_transformer_blocks):
307
+ for key, weights in diffusers_map.items():
308
+ if key.startswith("single_blocks."):
309
+ block_prefix = f"single_transformer_blocks.{b}."
310
+ found = True
311
+ for weight in weights:
312
+ if not (f"{block_prefix}{weight}" in diffusers):
313
+ found = False
314
+ if found:
315
+ flux_values[key.replace("()", f"{b}")] = [
316
+ f"{block_prefix}{weight}" for weight in weights]
317
+
318
+ for key, weights in diffusers_map.items():
319
+ if not (key.startswith("double_blocks.") or key.startswith("single_blocks.")):
320
+ found = True
321
+ for weight in weights:
322
+ if not (f"{weight}" in diffusers):
323
+ found = False
324
+ if found:
325
+ flux_values[key] = [f"{weight}" for weight in weights]
326
+
327
+ flux = {}
328
+
329
+ for key, values in tqdm.tqdm(flux_values.items()):
330
+ if len(values) == 1:
331
+ flux[key] = original_safetensors[diffusers[values[0]]
332
+ ].get_tensor(values[0]).to("cpu")
333
+ else:
334
+ flux[key] = torch.cat(
335
+ [
336
+ original_safetensors[diffusers[value]
337
+ ].get_tensor(value).to("cpu")
338
+ for value in values
339
+ ]
340
+ )
341
+
342
+ if "norm_out.linear.weight" in diffusers:
343
+ flux["final_layer.adaLN_modulation.1.weight"] = swap_scale_shift(
344
+ original_safetensors[diffusers["norm_out.linear.weight"]].get_tensor(
345
+ "norm_out.linear.weight").to("cpu")
346
+ )
347
+ if "norm_out.linear.bias" in diffusers:
348
+ flux["final_layer.adaLN_modulation.1.bias"] = swap_scale_shift(
349
+ original_safetensors[diffusers["norm_out.linear.bias"]].get_tensor(
350
+ "norm_out.linear.bias").to("cpu")
351
+ )
352
+
353
+
354
+ def stochastic_round_to(tensor, dtype=torch.float8_e4m3fn):
355
+ # Define the float8 range
356
+ min_val = torch.finfo(dtype).min
357
+ max_val = torch.finfo(dtype).max
358
+
359
+ # Clip values to float8 range
360
+ tensor = torch.clamp(tensor, min_val, max_val)
361
+
362
+ # Convert to float32 for calculations
363
+ tensor = tensor.float()
364
+
365
+ # Get the nearest representable float8 values
366
+ lower = torch.floor(tensor * 256) / 256
367
+ upper = torch.ceil(tensor * 256) / 256
368
+
369
+ # Calculate the probability of rounding up
370
+ prob = (tensor - lower) / (upper - lower)
371
+
372
+ # Generate random values for stochastic rounding
373
+ rand = torch.rand_like(tensor)
374
+
375
+ # Perform stochastic rounding
376
+ rounded = torch.where(rand < prob, upper, lower)
377
+
378
+ # Convert back to float8
379
+ return rounded.to(dtype)
380
+
381
+
382
+ # set all the keys to bf16
383
+ for key in flux.keys():
384
+ if do_8_bit:
385
+ flux[key] = stochastic_round_to(
386
+ flux[key], torch.float8_e4m3fn).to('cpu')
387
+ else:
388
+ flux[key] = flux[key].clone().to('cpu', torch.bfloat16)
389
+
390
+ # load the quantized state dict
391
+ quantized_state_dict = safetensors.torch.load_file(quantized_state_dict_path)
392
+
393
+ transformer_pre = "model.diffusion_model."
394
+ did_print = False
395
+ # remove old parts
396
+ for key in list(quantized_state_dict.keys()):
397
+ if key.startswith(transformer_pre):
398
+ if not did_print:
399
+ # print("dtype: ", quantized_state_dict[key].dtype)
400
+ did_print = True
401
+ del quantized_state_dict[key]
402
+
403
+ # add the new parts
404
+ for key, value in flux.items():
405
+ quantized_state_dict[transformer_pre + key] = value
406
+
407
+
408
+ meta = OrderedDict()
409
+ meta['format'] = 'pt'
410
+ # date format like 2024-08-01 YYYY-MM-DD
411
+ meta['modelspec.date'] = date.today().strftime("%Y-%m-%d")
412
+ meta['modelspec.title'] = "Flex.1-alpha"
413
+ meta['modelspec.author'] = "Ostris, LLC"
414
+ meta['modelspec.license'] = "Apache-2.0"
415
+ meta['modelspec.implementation'] = "https://github.com/black-forest-labs/flux"
416
+ meta['modelspec.architecture'] = "Flex.1-alpha"
417
+ meta['modelspec.description'] = "Flex.1-alpha"
418
+
419
+
420
+ os.makedirs(os.path.dirname(flux_path), exist_ok=True)
421
+
422
+ print(f"Saving to {flux_path}")
423
+
424
+ safetensors.torch.save_file(quantized_state_dict, flux_path, metadata=meta)
425
+
426
+ print("Done.")