Spaces:
Runtime error
Runtime error
Refactor app.py and download_qwen_image_models.py for improved model handling
Browse filesIntroduce 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.
- app.py +199 -27
- download_qwen_image_models.py +57 -39
- 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 |
-
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
| 54 |
v = raw.strip().lower()
|
| 55 |
-
if v in
|
| 56 |
-
|
| 57 |
-
if v in ("2509", "edit-2509", "edit_2509", "qwen_image_edit_2509", "qwen-image-edit-2509"):
|
| 58 |
return "edit-2509"
|
| 59 |
-
return
|
| 60 |
|
| 61 |
|
| 62 |
-
def
|
| 63 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 455 |
-
dit_filename =
|
| 456 |
-
|
|
|
|
| 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',
|
| 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 |
-
|
| 768 |
-
|
| 769 |
-
|
| 770 |
-
|
| 771 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 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=
|
| 814 |
-
control_res_h=
|
|
|
|
| 815 |
)
|
| 816 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 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:
|
| 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
|
| 50 |
-
MODEL_VERSION="${
|
| 51 |
-
if [[ "$MODEL_VERSION" != "edit-2509" && "$MODEL_VERSION" != "edit-2511" ]]; then
|
| 52 |
-
echo "[QIE] Unsupported
|
| 53 |
MODEL_VERSION="edit-2509"
|
| 54 |
fi
|
| 55 |
-
|
| 56 |
-
DIT_FILENAME="
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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/
|
| 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/
|
| 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 \
|