yeq6x commited on
Commit
73d06d9
·
1 Parent(s): b983cc6

Refactor app.py and download_qwen_image_models.py for improved model handling

Browse files

Introduce a new image type handling mechanism in app.py and download_qwen_image_models.py, allowing for dynamic model selection based on the QWEN_IMAGE_TYPE environment variable. Update functions to generate filenames for both edit and layered models, enhancing flexibility in model management. Modify the training script to accommodate these changes, ensuring proper metadata generation and error handling for layered configurations. This refactor improves code clarity and user experience by streamlining model downloads and configurations.

Files changed (3) hide show
  1. app.py +199 -27
  2. download_qwen_image_models.py +57 -39
  3. train_QIE.sh +26 -16
app.py CHANGED
@@ -49,21 +49,31 @@ def _bash_quote(s: str) -> str:
49
  return "'" + str(s).replace("'", "'\"'\"'") + "'"
50
 
51
 
52
- def _get_edit_model_version() -> str:
53
- raw = os.environ.get("QWEN_IMAGE_EDIT_VERSION", "")
 
 
 
54
  v = raw.strip().lower()
55
- if v in ("2511", "edit-2511", "edit_2511", "qwen_image_edit_2511", "qwen-image-edit-2511"):
56
- return "edit-2511"
57
- if v in ("2509", "edit-2509", "edit_2509", "qwen_image_edit_2509", "qwen-image-edit-2509"):
58
  return "edit-2509"
59
- return "edit-2509"
60
 
61
 
62
- def _edit_dit_filename(edit_version: str) -> str:
63
- suffix = edit_version.split("-", 1)[1] if "-" in edit_version else edit_version
 
 
64
  return f"qwen_image_edit_{suffix}_bf16.safetensors"
65
 
66
 
 
 
 
 
 
 
67
  def _ensure_workspace_auto_files() -> None:
68
  """Ensure /workspace/auto has required helper files from this repo.
69
 
@@ -101,6 +111,7 @@ def _update_dataset_toml(
101
  train_batch_size: Optional[int] = None,
102
  control_res_w: Optional[int] = None,
103
  control_res_h: Optional[int] = None,
 
104
  ) -> None:
105
  """Update dataset TOML for resolution/batch/control resolution in-place.
106
 
@@ -138,16 +149,22 @@ def _update_dataset_toml(
138
  block = _set_in_general(block, "batch_size", f"batch_size = {int(train_batch_size)}")
139
  txt = head + block + tail
140
 
141
- if control_res_w and control_res_h:
142
- m2 = re.search(r"(?ms)^\[\[datasets\]\]\s*(.*?)(?=^\[\[|\Z)", txt)
143
- if m2:
144
- head, block, tail = txt[:m2.start(1)], m2.group(1), txt[m2.end(1):]
145
  line = f"qwen_image_edit_control_resolution = [{int(control_res_w)}, {int(control_res_h)}]"
146
  if re.search(r"(?m)^\s*qwen_image_edit_control_resolution\s*=", block):
147
  block = re.sub(r"(?m)^\s*qwen_image_edit_control_resolution\s*=.*$", line, block)
148
  else:
149
  block = block.rstrip() + "\n" + line + "\n"
150
- txt = head + block + tail
 
 
 
 
 
 
151
 
152
  try:
153
  Path(path).write_text(txt, encoding="utf-8")
@@ -244,6 +261,103 @@ def _norm_key(filename: str, prefix: str, suffix: str) -> str:
244
  return stem
245
 
246
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  def _copy_uploads(
248
  uploads: List[Tuple[str, str]], dest_dir: str, rename_to: Optional[List[str]] = None
249
  ) -> List[str]:
@@ -451,9 +565,10 @@ def _prepare_script(
451
  txt,
452
  )
453
 
454
- edit_version = _get_edit_model_version()
455
- dit_filename = _edit_dit_filename(edit_version)
456
- txt = _replace_model_path(txt, "vae", "vae/diffusion_pytorch_model.safetensors")
 
457
  txt = _replace_model_path(txt, "text_encoder", "text_encoder/qwen_2.5_vl_7b.safetensors")
458
  txt = _replace_model_path(txt, "dit", f"dit/{dit_filename}")
459
 
@@ -554,7 +669,7 @@ def _prepare_script(
554
  _set_var('MAX_TRAIN_EPOCHS', str(override_max_epochs))
555
  if override_save_every is not None and override_save_every > 0:
556
  _set_var('SAVE_EVERY_N_EPOCHS', str(override_save_every))
557
- _set_var('MODEL_VERSION', edit_version)
558
 
559
  # Write to a temp file alongside this repo for easier inspection
560
  run_dir = TRAINING_DIR / ".gradio_runs"
@@ -729,6 +844,10 @@ def run_training(
729
  yield (log_buf, ckpts, artifacts, run_out_dir)
730
  return
731
 
 
 
 
 
732
  # Ensure /auto holds helper files expected by the script
733
  _ensure_workspace_auto_files()
734
  # Resolve data root and create dataset directories (auto-decide)
@@ -764,11 +883,17 @@ def run_training(
764
  _extract_paths(control6_uploads),
765
  _extract_paths(control7_uploads),
766
  ]
767
- # Require control_0; others optional
768
- if not control_upload_sets[0]:
769
- log_buf += "[ERROR] control_0 images are required.\n"
770
- yield (log_buf, ckpts, artifacts, run_out_dir)
771
- return
 
 
 
 
 
 
772
 
773
  control_dirs: List[Optional[str]] = []
774
  for i, uploads in enumerate(control_upload_sets):
@@ -784,7 +909,10 @@ def run_training(
784
  log_buf += f"[QIE] Copied {len(uploads)} control_{i} images to {cdir}\n"
785
  yield (log_buf, ckpts, artifacts, run_out_dir)
786
 
787
- # Metadata.jsonl will be generated by create_image_caption_json.py in train_QIE.sh
 
 
 
788
 
789
  # Prepare script with user parameters
790
  control_folders = [
@@ -805,15 +933,24 @@ def run_training(
805
 
806
  # Update dataset config with requested resolution/batch settings
807
  try:
 
 
 
 
 
808
  _update_dataset_toml(
809
  ds_conf,
810
  img_res_w=int(train_res_w) if train_res_w else None,
811
  img_res_h=int(train_res_h) if train_res_h else None,
812
  train_batch_size=int(train_batch_size) if train_batch_size else None,
813
- control_res_w=int(control_res_w) if control_res_w else None,
814
- control_res_h=int(control_res_h) if control_res_h else None,
 
815
  )
816
- log_buf += f"[QIE] Updated dataset config: resolution=({train_res_w},{train_res_h}), batch_size={train_batch_size}, control_res=({control_res_w},{control_res_h})\n"
 
 
 
817
  except Exception as e:
818
  log_buf += f"[QIE] WARN: failed to update dataset config: {e}\n"
819
  # Expose dataset config for download (if exists)
@@ -867,8 +1004,39 @@ def run_training(
867
  pass
868
  yield (log_buf, ckpts, artifacts, run_out_dir)
869
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
870
  if config_only:
871
- output_json = os.path.join(out_base, "metadata.jsonl")
872
  _sync_dataset_config_jsonl(ds_conf, output_json)
873
  log_buf += f"[QIE] Generating metadata: {output_json}\n"
874
  yield (log_buf, ckpts, artifacts, run_out_dir)
@@ -1258,6 +1426,10 @@ def _startup_download_models() -> None:
1258
  os.makedirs(MODELS_ROOT_RUNTIME, exist_ok=True)
1259
 
1260
  print(f"[QIE] Ensuring models in: {MODELS_ROOT_RUNTIME}")
 
 
 
 
1261
  try:
1262
  download_all_models(MODELS_ROOT_RUNTIME)
1263
  except Exception as e:
 
49
  return "'" + str(s).replace("'", "'\"'\"'") + "'"
50
 
51
 
52
+ _QWEN_IMAGE_TYPES = ("edit-2509", "edit-2511", "layered")
53
+
54
+
55
+ def _get_qwen_image_type() -> str:
56
+ raw = os.environ.get("QWEN_IMAGE_TYPE", "edit-2509")
57
  v = raw.strip().lower()
58
+ if v not in _QWEN_IMAGE_TYPES:
59
+ print(f"[QIE] Unsupported QWEN_IMAGE_TYPE: {v}. Defaulting to edit-2509.")
 
60
  return "edit-2509"
61
+ return v
62
 
63
 
64
+ def _dit_filename_for_type(image_type: str) -> str:
65
+ if image_type == "layered":
66
+ return "qwen_image_layered_bf16.safetensors"
67
+ suffix = image_type.split("-", 1)[1]
68
  return f"qwen_image_edit_{suffix}_bf16.safetensors"
69
 
70
 
71
+ def _vae_filename_for_type(image_type: str) -> str:
72
+ if image_type == "layered":
73
+ return "qwen_image_layered_vae.safetensors"
74
+ return "diffusion_pytorch_model.safetensors"
75
+
76
+
77
  def _ensure_workspace_auto_files() -> None:
78
  """Ensure /workspace/auto has required helper files from this repo.
79
 
 
111
  train_batch_size: Optional[int] = None,
112
  control_res_w: Optional[int] = None,
113
  control_res_h: Optional[int] = None,
114
+ multiple_target: Optional[bool] = None,
115
  ) -> None:
116
  """Update dataset TOML for resolution/batch/control resolution in-place.
117
 
 
149
  block = _set_in_general(block, "batch_size", f"batch_size = {int(train_batch_size)}")
150
  txt = head + block + tail
151
 
152
+ m2 = re.search(r"(?ms)^\[\[datasets\]\]\s*(.*?)(?=^\[\[|\Z)", txt)
153
+ if m2:
154
+ head, block, tail = txt[:m2.start(1)], m2.group(1), txt[m2.end(1):]
155
+ if control_res_w and control_res_h:
156
  line = f"qwen_image_edit_control_resolution = [{int(control_res_w)}, {int(control_res_h)}]"
157
  if re.search(r"(?m)^\s*qwen_image_edit_control_resolution\s*=", block):
158
  block = re.sub(r"(?m)^\s*qwen_image_edit_control_resolution\s*=.*$", line, block)
159
  else:
160
  block = block.rstrip() + "\n" + line + "\n"
161
+ if multiple_target is not None:
162
+ mt_line = f"multiple_target = {'true' if multiple_target else 'false'}"
163
+ if re.search(r"(?m)^\s*multiple_target\s*=", block):
164
+ block = re.sub(r"(?m)^\s*multiple_target\s*=.*$", mt_line, block)
165
+ else:
166
+ block = block.rstrip() + "\n" + mt_line + "\n"
167
+ txt = head + block + tail
168
 
169
  try:
170
  Path(path).write_text(txt, encoding="utf-8")
 
261
  return stem
262
 
263
 
264
+ IMAGE_EXTENSIONS = (".jpg", ".jpeg", ".png", ".bmp", ".gif")
265
+
266
+
267
+ def _list_image_files(folder: str) -> List[str]:
268
+ try:
269
+ files = [
270
+ fn
271
+ for fn in sorted(os.listdir(folder))
272
+ if fn.lower().endswith(IMAGE_EXTENSIONS)
273
+ and os.path.isfile(os.path.join(folder, fn))
274
+ ]
275
+ return files
276
+ except Exception:
277
+ return []
278
+
279
+
280
+ def _join_posix(base: str, name: str) -> str:
281
+ if not base:
282
+ return name.replace("\\", "/")
283
+ return base.rstrip("/\\") + "/" + name.replace("\\", "/").lstrip("/")
284
+
285
+
286
+ def _generate_layered_jsonl(
287
+ image_dir: str,
288
+ caption: str,
289
+ output_json: str,
290
+ control_dirs: List[Optional[str]],
291
+ target_prefix: str = "",
292
+ target_suffix: str = "",
293
+ control_prefixes: Optional[List[Optional[str]]] = None,
294
+ control_suffixes: Optional[List[Optional[str]]] = None,
295
+ allow_single: bool = True,
296
+ ) -> int:
297
+ base_files = _list_image_files(image_dir)
298
+ if not base_files:
299
+ raise ValueError("IMAGE_FOLDER に画像が見つかりません。")
300
+
301
+ active_controls = [d for d in control_dirs if d]
302
+ if not active_controls:
303
+ raise ValueError("Layered では少なくとも1つのレイヤー画像が必要です。")
304
+
305
+ use_name_matching = bool(target_prefix or target_suffix)
306
+ if control_prefixes and any((c or "").strip() for c in control_prefixes):
307
+ use_name_matching = True
308
+ if control_suffixes and any((c or "").strip() for c in control_suffixes):
309
+ use_name_matching = True
310
+
311
+ if not use_name_matching:
312
+ missing: List[str] = []
313
+ for fname in base_files:
314
+ for cdir in active_controls:
315
+ expected = os.path.join(cdir, fname)
316
+ if not os.path.exists(expected):
317
+ missing.append(expected)
318
+ if missing:
319
+ preview = "\n".join(f" - {p}" for p in missing[:5])
320
+ raise ValueError(f"対応するレイヤーが見つかりません(strict):\n{preview}")
321
+
322
+ out_dir = os.path.dirname(output_json)
323
+ if out_dir:
324
+ os.makedirs(out_dir, exist_ok=True)
325
+
326
+ count = 0
327
+ with open(output_json, "w", encoding="utf-8") as f:
328
+ for base_fn in base_files:
329
+ entry: Dict[str, str] = {
330
+ "image_path_0": _join_posix(image_dir, base_fn),
331
+ "caption": caption,
332
+ }
333
+ key = _norm_key(base_fn, target_prefix, target_suffix)
334
+ layer_index = 1
335
+ for i, cdir in enumerate(control_dirs):
336
+ if not cdir:
337
+ continue
338
+ cprefix = ""
339
+ csuffix = ""
340
+ if control_prefixes and i < len(control_prefixes) and control_prefixes[i]:
341
+ cprefix = control_prefixes[i]
342
+ if control_suffixes and i < len(control_suffixes) and control_suffixes[i]:
343
+ csuffix = control_suffixes[i]
344
+ expected_name = base_fn if not use_name_matching else f"{cprefix}{key}{csuffix}.png"
345
+ expected_path = os.path.join(cdir, expected_name)
346
+ if not os.path.exists(expected_path):
347
+ cfiles = _list_image_files(cdir)
348
+ if allow_single and len(cfiles) == 1:
349
+ expected_name = cfiles[0]
350
+ else:
351
+ raise ValueError(
352
+ f"対応するレイヤーが見つかりません: expected={expected_name}, layer_index={i}"
353
+ )
354
+ entry[f"image_path_{layer_index}"] = _join_posix(cdir, expected_name)
355
+ layer_index += 1
356
+ f.write(json.dumps(entry, ensure_ascii=False) + "\n")
357
+ count += 1
358
+ return count
359
+
360
+
361
  def _copy_uploads(
362
  uploads: List[Tuple[str, str]], dest_dir: str, rename_to: Optional[List[str]] = None
363
  ) -> List[str]:
 
565
  txt,
566
  )
567
 
568
+ image_type = _get_qwen_image_type()
569
+ dit_filename = _dit_filename_for_type(image_type)
570
+ vae_filename = _vae_filename_for_type(image_type)
571
+ txt = _replace_model_path(txt, "vae", f"vae/{vae_filename}")
572
  txt = _replace_model_path(txt, "text_encoder", "text_encoder/qwen_2.5_vl_7b.safetensors")
573
  txt = _replace_model_path(txt, "dit", f"dit/{dit_filename}")
574
 
 
669
  _set_var('MAX_TRAIN_EPOCHS', str(override_max_epochs))
670
  if override_save_every is not None and override_save_every > 0:
671
  _set_var('SAVE_EVERY_N_EPOCHS', str(override_save_every))
672
+ _set_var('MODEL_VERSION', image_type)
673
 
674
  # Write to a temp file alongside this repo for easier inspection
675
  run_dir = TRAINING_DIR / ".gradio_runs"
 
844
  yield (log_buf, ckpts, artifacts, run_out_dir)
845
  return
846
 
847
+ image_type = _get_qwen_image_type()
848
+ log_buf += f"[QIE] Model type: {image_type}\n"
849
+ yield (log_buf, ckpts, artifacts, run_out_dir)
850
+
851
  # Ensure /auto holds helper files expected by the script
852
  _ensure_workspace_auto_files()
853
  # Resolve data root and create dataset directories (auto-decide)
 
883
  _extract_paths(control6_uploads),
884
  _extract_paths(control7_uploads),
885
  ]
886
+ if image_type == "layered":
887
+ if not any(control_upload_sets):
888
+ log_buf += "[ERROR] Layered では control_0〜7 のいずれかが必要です。\n"
889
+ yield (log_buf, ckpts, artifacts, run_out_dir)
890
+ return
891
+ else:
892
+ # Require control_0; others optional
893
+ if not control_upload_sets[0]:
894
+ log_buf += "[ERROR] control_0 images are required.\n"
895
+ yield (log_buf, ckpts, artifacts, run_out_dir)
896
+ return
897
 
898
  control_dirs: List[Optional[str]] = []
899
  for i, uploads in enumerate(control_upload_sets):
 
909
  log_buf += f"[QIE] Copied {len(uploads)} control_{i} images to {cdir}\n"
910
  yield (log_buf, ckpts, artifacts, run_out_dir)
911
 
912
+ control_dirs_abs = [
913
+ (os.path.join(ds_dir, name) if name else None)
914
+ for name in control_dirs
915
+ ]
916
 
917
  # Prepare script with user parameters
918
  control_folders = [
 
933
 
934
  # Update dataset config with requested resolution/batch settings
935
  try:
936
+ ctrl_w = int(control_res_w) if control_res_w else None
937
+ ctrl_h = int(control_res_h) if control_res_h else None
938
+ if image_type == "layered":
939
+ ctrl_w = None
940
+ ctrl_h = None
941
  _update_dataset_toml(
942
  ds_conf,
943
  img_res_w=int(train_res_w) if train_res_w else None,
944
  img_res_h=int(train_res_h) if train_res_h else None,
945
  train_batch_size=int(train_batch_size) if train_batch_size else None,
946
+ control_res_w=ctrl_w,
947
+ control_res_h=ctrl_h,
948
+ multiple_target=(image_type == "layered"),
949
  )
950
+ if image_type == "layered":
951
+ log_buf += f"[QIE] Updated dataset config: resolution=({train_res_w},{train_res_h}), batch_size={train_batch_size}, multiple_target=true\n"
952
+ else:
953
+ log_buf += f"[QIE] Updated dataset config: resolution=({train_res_w},{train_res_h}), batch_size={train_batch_size}, control_res=({control_res_w},{control_res_h})\n"
954
  except Exception as e:
955
  log_buf += f"[QIE] WARN: failed to update dataset config: {e}\n"
956
  # Expose dataset config for download (if exists)
 
1004
  pass
1005
  yield (log_buf, ckpts, artifacts, run_out_dir)
1006
 
1007
+ output_json = os.path.join(out_base, "metadata.jsonl")
1008
+ if image_type == "layered":
1009
+ _sync_dataset_config_jsonl(ds_conf, output_json)
1010
+ log_buf += f"[QIE] Generating layered metadata: {output_json}\n"
1011
+ yield (log_buf, ckpts, artifacts, run_out_dir)
1012
+ try:
1013
+ count = _generate_layered_jsonl(
1014
+ image_dir=img_dir,
1015
+ caption=caption,
1016
+ output_json=output_json,
1017
+ control_dirs=control_dirs_abs,
1018
+ target_prefix=(target_prefix or ""),
1019
+ target_suffix=(target_suffix or ""),
1020
+ control_prefixes=control_prefixes,
1021
+ control_suffixes=control_suffixes,
1022
+ allow_single=True,
1023
+ )
1024
+ log_buf += f"[QIE] Layered metadata written: {count} entries\n"
1025
+ except Exception as e:
1026
+ log_buf += f"[ERROR] Layered metadata generation failed: {e}\n"
1027
+ yield (log_buf, ckpts, artifacts, run_out_dir)
1028
+ return
1029
+
1030
+ if os.path.isfile(output_json) and output_json not in artifacts:
1031
+ artifacts.append(output_json)
1032
+ yield (log_buf, ckpts, artifacts, run_out_dir)
1033
+
1034
+ if config_only:
1035
+ log_buf += "[QIE] Config-only mode: skipping cache/training.\n"
1036
+ yield (log_buf, ckpts, artifacts, run_out_dir)
1037
+ return
1038
+
1039
  if config_only:
 
1040
  _sync_dataset_config_jsonl(ds_conf, output_json)
1041
  log_buf += f"[QIE] Generating metadata: {output_json}\n"
1042
  yield (log_buf, ckpts, artifacts, run_out_dir)
 
1426
  os.makedirs(MODELS_ROOT_RUNTIME, exist_ok=True)
1427
 
1428
  print(f"[QIE] Ensuring models in: {MODELS_ROOT_RUNTIME}")
1429
+ skip_raw = os.environ.get("QWEN_IMAGE_SKIP_DOWNLOAD", "")
1430
+ if skip_raw.strip().lower() in ("1", "true", "yes", "on"):
1431
+ print("[QIE] QWEN_IMAGE_SKIP_DOWNLOAD=1: skipping model download.")
1432
+ return
1433
  try:
1434
  download_all_models(MODELS_ROOT_RUNTIME)
1435
  except Exception as e:
download_qwen_image_models.py CHANGED
@@ -6,23 +6,22 @@ from huggingface_hub import hf_hub_download
6
  # Enable hf_transfer for faster downloads
7
  os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "1"
8
 
9
- DEFAULT_MODELS_DIR = os.environ.get("QWEN_IMAGE_MODELS_DIR",
10
  "/Qwen-Image_models")
11
  # Temporary download root (requested: use /tmp instead of /workspace)
12
  TMP_DOWNLOAD_ROOT = os.environ.get("QWEN_IMAGE_TMP_DIR", "/tmp/qie_downloads")
13
 
14
 
15
- def _normalize_edit_version(raw: str) -> str:
16
- v = (raw or "").strip().lower()
17
- if v in ("2511", "edit-2511", "edit_2511", "qwen_image_edit_2511", "qwen-image-edit-2511"):
18
- return "edit-2511"
19
- if v in ("2509", "edit-2509", "edit_2509", "qwen_image_edit_2509", "qwen-image-edit-2509"):
20
- return "edit-2509"
21
- return "edit-2509"
22
 
23
 
24
- def _get_edit_version() -> str:
25
- return _normalize_edit_version(os.environ.get("QWEN_IMAGE_EDIT_VERSION", ""))
 
 
 
 
 
26
 
27
 
28
  def _ensure_dirs(root: str) -> None:
@@ -71,45 +70,64 @@ def _download_then_place(*, repo_id: str, filename: str, subfolder: str,
71
 
72
 
73
  def download_all_models(models_dir: str = DEFAULT_MODELS_DIR) -> Dict[str, str]:
74
- """Download required Qwen-Image-Edit models into models_dir.
75
 
76
  Returns a dict of component -> local file path.
77
  """
78
  _ensure_dirs(models_dir)
79
 
80
  print(f"[QIE] Models directory: {models_dir}")
 
 
81
 
82
  print("[QIE] Download dir (tmp):", TMP_DOWNLOAD_ROOT)
83
  print("[QIE] Final models dir:", models_dir)
84
 
85
- edit_version = _get_edit_version()
86
- dit_suffix = edit_version.split("-", 1)[1] if "-" in edit_version else edit_version
87
- dit_filename = f"qwen_image_edit_{dit_suffix}_bf16.safetensors"
88
-
89
- # Download to /tmp then move to final path
90
- dit_path = _download_then_place(
91
- repo_id="Comfy-Org/Qwen-Image-Edit_ComfyUI",
92
- filename=dit_filename,
93
- subfolder="split_files/diffusion_models",
94
- component="dit",
95
- models_dir=models_dir,
96
- )
97
-
98
- print("[QIE] Downloading VAE model(s)...")
99
- vae_main = _download_then_place(
100
- repo_id="Qwen/Qwen-Image-Edit",
101
- filename="diffusion_pytorch_model.safetensors",
102
- subfolder="vae",
103
- component="vae",
104
- models_dir=models_dir,
105
- )
106
- vae_alt = _download_then_place(
107
- repo_id="Comfy-Org/Qwen-Image_ComfyUI",
108
- filename="qwen_image_vae.safetensors",
109
- subfolder="split_files/vae",
110
- component="vae",
111
- models_dir=models_dir,
112
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
  print("[QIE] Downloading Text Encoder...")
115
  te_path = _download_then_place(
 
6
  # Enable hf_transfer for faster downloads
7
  os.environ["HF_HUB_ENABLE_HF_TRANSFER"] = "1"
8
 
9
+ DEFAULT_MODELS_DIR = os.environ.get("QWEN_IMAGE_MODELS_DIR",
10
  "/Qwen-Image_models")
11
  # Temporary download root (requested: use /tmp instead of /workspace)
12
  TMP_DOWNLOAD_ROOT = os.environ.get("QWEN_IMAGE_TMP_DIR", "/tmp/qie_downloads")
13
 
14
 
15
+ _QWEN_IMAGE_TYPES = ("edit-2509", "edit-2511", "layered")
 
 
 
 
 
 
16
 
17
 
18
+ def _get_qwen_image_type() -> str:
19
+ raw = os.environ.get("QWEN_IMAGE_TYPE", "edit-2509")
20
+ v = raw.strip().lower()
21
+ if v not in _QWEN_IMAGE_TYPES:
22
+ print(f"[QIE] Unsupported QWEN_IMAGE_TYPE: {v}. Defaulting to edit-2509.")
23
+ return "edit-2509"
24
+ return v
25
 
26
 
27
  def _ensure_dirs(root: str) -> None:
 
70
 
71
 
72
  def download_all_models(models_dir: str = DEFAULT_MODELS_DIR) -> Dict[str, str]:
73
+ """Download required Qwen-Image models into models_dir.
74
 
75
  Returns a dict of component -> local file path.
76
  """
77
  _ensure_dirs(models_dir)
78
 
79
  print(f"[QIE] Models directory: {models_dir}")
80
+ image_type = _get_qwen_image_type()
81
+ print(f"[QIE] Model type: {image_type}")
82
 
83
  print("[QIE] Download dir (tmp):", TMP_DOWNLOAD_ROOT)
84
  print("[QIE] Final models dir:", models_dir)
85
 
86
+ if image_type == "layered":
87
+ dit_filename = "qwen_image_layered_bf16.safetensors"
88
+ dit_path = _download_then_place(
89
+ repo_id="Comfy-Org/Qwen-Image-Layered_ComfyUI",
90
+ filename=dit_filename,
91
+ subfolder="split_files/diffusion_models",
92
+ component="dit",
93
+ models_dir=models_dir,
94
+ )
95
+ print("[QIE] Downloading VAE model(s)...")
96
+ vae_main = _download_then_place(
97
+ repo_id="Comfy-Org/Qwen-Image-Layered_ComfyUI",
98
+ filename="qwen_image_layered_vae.safetensors",
99
+ subfolder="split_files/vae",
100
+ component="vae",
101
+ models_dir=models_dir,
102
+ )
103
+ vae_alt = ""
104
+ else:
105
+ edit_suffix = image_type.split("-", 1)[1]
106
+ dit_filename = f"qwen_image_edit_{edit_suffix}_bf16.safetensors"
107
+ # Download to /tmp then move to final path
108
+ dit_path = _download_then_place(
109
+ repo_id="Comfy-Org/Qwen-Image-Edit_ComfyUI",
110
+ filename=dit_filename,
111
+ subfolder="split_files/diffusion_models",
112
+ component="dit",
113
+ models_dir=models_dir,
114
+ )
115
+
116
+ print("[QIE] Downloading VAE model(s)...")
117
+ vae_main = _download_then_place(
118
+ repo_id="Qwen/Qwen-Image-Edit",
119
+ filename="diffusion_pytorch_model.safetensors",
120
+ subfolder="vae",
121
+ component="vae",
122
+ models_dir=models_dir,
123
+ )
124
+ vae_alt = _download_then_place(
125
+ repo_id="Comfy-Org/Qwen-Image_ComfyUI",
126
+ filename="qwen_image_vae.safetensors",
127
+ subfolder="split_files/vae",
128
+ component="vae",
129
+ models_dir=models_dir,
130
+ )
131
 
132
  print("[QIE] Downloading Text Encoder...")
133
  te_path = _download_then_place(
train_QIE.sh CHANGED
@@ -3,7 +3,7 @@
3
  # ==============================
4
  # Generate metadata.jsonl before training
5
  # Configure variables directly in this file.
6
- # Environment variable override: QWEN_IMAGE_EDIT_VERSION (edit-2509 or edit-2511)
7
  # ==============================
8
 
9
  echo "[QIE] Torch version check"
@@ -46,14 +46,20 @@ SEED=42
46
  MAX_TRAIN_EPOCHS=100
47
  SAVE_EVERY_N_EPOCHS=10
48
 
49
- # Model version (edit-2509 or edit-2511)
50
- MODEL_VERSION="${QWEN_IMAGE_EDIT_VERSION:-edit-2509}"
51
- if [[ "$MODEL_VERSION" != "edit-2509" && "$MODEL_VERSION" != "edit-2511" ]]; then
52
- echo "[QIE] Unsupported QWEN_IMAGE_EDIT_VERSION: $MODEL_VERSION, defaulting to edit-2509"
53
  MODEL_VERSION="edit-2509"
54
  fi
55
- DIT_SUFFIX="${MODEL_VERSION#edit-}"
56
- DIT_FILENAME="qwen_image_edit_${DIT_SUFFIX}_bf16.safetensors"
 
 
 
 
 
 
57
 
58
  # Build control args from folder names with auto-detect fallback
59
  CONTROL_ARGS=()
@@ -103,19 +109,23 @@ fi
103
 
104
  cd /workspace/auto
105
 
106
- echo "[QIE] Generating metadata: $OUTPUT_JSON"
107
- python create_image_caption_json.py \
108
- -i "${DATASET_DIR%/}/${IMAGE_FOLDER}" \
109
- -c "$CAPTION" \
110
- -o "$OUTPUT_JSON" \
111
- --image-dir "${DATASET_DIR%/}/${IMAGE_FOLDER}" \
112
- "${CONTROL_ARGS[@]}"
 
 
 
 
113
 
114
  cd /musubi-tuner
115
 
116
  python qwen_image_cache_latents.py \
117
  --dataset_config "$DATASET_CONFIG" \
118
- --vae "/workspace/Qwen-Image_models/vae/diffusion_pytorch_model.safetensors" \
119
  --model_version "$MODEL_VERSION" \
120
  --vae_spatial_tile_sample_min_size 16384
121
 
@@ -128,7 +138,7 @@ python qwen_image_cache_text_encoder_outputs.py \
128
  accelerate launch src/musubi_tuner/qwen_image_train_network.py \
129
  --model_version "$MODEL_VERSION" \
130
  --dit "/workspace/Qwen-Image_models/dit/${DIT_FILENAME}" \
131
- --vae "/workspace/Qwen-Image_models/vae/diffusion_pytorch_model.safetensors" \
132
  --text_encoder "/workspace/Qwen-Image_models/text_encoder/qwen_2.5_vl_7b.safetensors" \
133
  --dataset_config "$DATASET_CONFIG" \
134
  --mixed_precision bf16 \
 
3
  # ==============================
4
  # Generate metadata.jsonl before training
5
  # Configure variables directly in this file.
6
+ # Environment variable override: QWEN_IMAGE_TYPE (edit-2509, edit-2511, layered)
7
  # ==============================
8
 
9
  echo "[QIE] Torch version check"
 
46
  MAX_TRAIN_EPOCHS=100
47
  SAVE_EVERY_N_EPOCHS=10
48
 
49
+ # Model type (edit-2509, edit-2511, layered)
50
+ MODEL_VERSION="${QWEN_IMAGE_TYPE:-edit-2509}"
51
+ if [[ "$MODEL_VERSION" != "edit-2509" && "$MODEL_VERSION" != "edit-2511" && "$MODEL_VERSION" != "layered" ]]; then
52
+ echo "[QIE] Unsupported QWEN_IMAGE_TYPE: $MODEL_VERSION, defaulting to edit-2509"
53
  MODEL_VERSION="edit-2509"
54
  fi
55
+ if [[ "$MODEL_VERSION" == "layered" ]]; then
56
+ DIT_FILENAME="qwen_image_layered_bf16.safetensors"
57
+ VAE_FILENAME="qwen_image_layered_vae.safetensors"
58
+ else
59
+ DIT_SUFFIX="${MODEL_VERSION#edit-}"
60
+ DIT_FILENAME="qwen_image_edit_${DIT_SUFFIX}_bf16.safetensors"
61
+ VAE_FILENAME="diffusion_pytorch_model.safetensors"
62
+ fi
63
 
64
  # Build control args from folder names with auto-detect fallback
65
  CONTROL_ARGS=()
 
109
 
110
  cd /workspace/auto
111
 
112
+ if [[ "$MODEL_VERSION" == "layered" ]]; then
113
+ echo "[QIE] Layered mode: skip metadata generation (expects prebuilt JSONL)."
114
+ else
115
+ echo "[QIE] Generating metadata: $OUTPUT_JSON"
116
+ python create_image_caption_json.py \
117
+ -i "${DATASET_DIR%/}/${IMAGE_FOLDER}" \
118
+ -c "$CAPTION" \
119
+ -o "$OUTPUT_JSON" \
120
+ --image-dir "${DATASET_DIR%/}/${IMAGE_FOLDER}" \
121
+ "${CONTROL_ARGS[@]}"
122
+ fi
123
 
124
  cd /musubi-tuner
125
 
126
  python qwen_image_cache_latents.py \
127
  --dataset_config "$DATASET_CONFIG" \
128
+ --vae "/workspace/Qwen-Image_models/vae/${VAE_FILENAME}" \
129
  --model_version "$MODEL_VERSION" \
130
  --vae_spatial_tile_sample_min_size 16384
131
 
 
138
  accelerate launch src/musubi_tuner/qwen_image_train_network.py \
139
  --model_version "$MODEL_VERSION" \
140
  --dit "/workspace/Qwen-Image_models/dit/${DIT_FILENAME}" \
141
+ --vae "/workspace/Qwen-Image_models/vae/${VAE_FILENAME}" \
142
  --text_encoder "/workspace/Qwen-Image_models/text_encoder/qwen_2.5_vl_7b.safetensors" \
143
  --dataset_config "$DATASET_CONFIG" \
144
  --mixed_precision bf16 \