Files
MinusZoneAI-ComfyUI-TrainTo…/mz_train_tools_core_HYDiT.py
T
2024-06-28 00:36:16 +08:00

633 lines
23 KiB
Python

import argparse
import csv
import hashlib
import os
import shutil
import sys
import json
import subprocess
import time
import torch
from .mz_train_tools_utils import Utils
import folder_paths
import nodes
git_accelerate_urls = {
"githubfast": "githubfast.com",
"521github": "521github.com",
"kkgithub": "kkgithub.com",
}
# 初始化工具仓库和工作区
def MZ_HYDiTInitWorkspace_call(args={}):
mz_dir = Utils.get_minus_zone_models_path()
git_url = "https://github.com/Tencent/HunyuanDiT"
source = args.get("source", "github")
kohya_ss_lora_dir = os.path.join(mz_dir, "train_tools", "HunyuanDiT")
if git_accelerate_urls.get(source, None) is not None:
git_url = f"https://{git_accelerate_urls[source]}/Tencent/HunyuanDiT"
try:
if not os.path.exists(kohya_ss_lora_dir) or not os.path.exists(os.path.join(kohya_ss_lora_dir, ".git")):
subprocess.run(
["git", "clone", "--depth", "1", git_url, kohya_ss_lora_dir], check=True)
# 切换远程分支 git remote set-branches origin 'main'
branch = args.get("branch", "main")
# 查看本地分支是否一致
short_result = subprocess.run(
["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=kohya_ss_lora_dir, stdout=subprocess.PIPE, check=True)
result = subprocess.run(
["git", "rev-parse", "HEAD"], cwd=kohya_ss_lora_dir, stdout=subprocess.PIPE, check=True)
short_current_branch = short_result.stdout.decode().strip()
long_current_branch = result.stdout.decode().strip()
print(
f"当前分支(current branch): {long_current_branch}({short_current_branch})")
print(f"目标分支(target branch): {branch}")
if branch != result.stdout.decode() and branch != short_result.stdout.decode():
subprocess.run(
["git", "remote", "set-branches", "origin", branch], cwd=kohya_ss_lora_dir, check=True)
subprocess.run(
["git", "fetch", "--depth", "1", "origin", branch], cwd=kohya_ss_lora_dir, check=True)
# 恢复所有文件
subprocess.run(
["git", "checkout", "."], cwd=kohya_ss_lora_dir, check=True)
subprocess.run(
["git", "checkout", branch], cwd=kohya_ss_lora_dir, check=True)
except Exception as e:
raise Exception(f"克隆kohya-ss/sd-scripts或者切换分支时出现异常,详细信息请查看控制台...")
workspace_name = args.get("train_name", None)
workspace_name = workspace_name.strip()
if workspace_name is None or workspace_name == "":
raise Exception("训练名称不能为空(train_name is required)")
args["workspace_name"] = workspace_name
workspaces_dir = os.path.join(
folder_paths.output_directory, "mz_train_workspaces")
os.makedirs(workspaces_dir, exist_ok=True)
workspace_dir = os.path.join(workspaces_dir, workspace_name)
if not os.path.exists(workspace_dir):
os.makedirs(workspace_dir)
args["workspace_dir"] = workspace_dir
return (
args,
)
def MZ_HYDiTDatasetConfig_call(args={}):
workspace_config = args.get("workspace_config", {})
workspace_name = workspace_config.get("workspace_name", None)
workspace_dir = os.path.join(
folder_paths.output_directory, "mz_train_workspaces", workspace_name)
train_images_dir = os.path.join(workspace_dir, "train_images")
os.makedirs(train_images_dir, exist_ok=True)
images = args.get("images", None)
if images is None:
print(f"训练数据位于:{train_images_dir},未检测到传入图片默认已有训练数据,将直接跳过数据准备步骤...")
return (
train_images_dir,
)
pil_images = Utils.tensors2pil_list(images)
resolution = args.get("resolution", 1024)
if workspace_name is None or workspace_name == "":
raise Exception("训练名称不能为空(workspace_name is required)")
saved_images_path = []
for i, pil_image in enumerate(pil_images):
pil_image = Utils.resize_max(pil_image, resolution, resolution)
width, height = pil_image.size
filename = hashlib.md5(pil_image.tobytes()).hexdigest() + ".png"
pil_image.save(os.path.join(train_images_dir, filename))
saved_images_path.append(filename)
return (
train_images_dir,
)
HYDiT_MODEL = {
"HunyuanDiT/t2i/model/pytorch_model_ema.pt": {
"url": "https://www.modelscope.cn/api/v1/models/modelscope/HunyuanDiT/repo?Revision=master&FilePath=t2i%2Fmodel%2Fpytorch_model_ema.pt",
},
"HunyuanDiT/t2i/model/pytorch_model_module.pt": {
"url": "https://www.modelscope.cn/api/v1/models/modelscope/HunyuanDiT/repo?Revision=master&FilePath=t2i%2Fmodel%2Fpytorch_model_module.pt",
},
"HunyuanDiT/t2i/sdxl-vae-fp16-fix/diffusion_pytorch_model.safetensors": {
"url": "https://www.modelscope.cn/api/v1/models/modelscope/HunyuanDiT/repo?Revision=master&FilePath=t2i%2Fsdxl-vae-fp16-fix%2Fdiffusion_pytorch_model.safetensors",
},
"HunyuanDiT/t2i/sdxl-vae-fp16-fix/config.json": {
"url": "https://www.modelscope.cn/api/v1/models/modelscope/HunyuanDiT/repo?Revision=master&FilePath=t2i%2Fsdxl-vae-fp16-fix%2Fconfig.json",
},
"HunyuanDiT/t2i/clip_text_encoder/pytorch_model.bin": {
"url": "https://www.modelscope.cn/api/v1/models/modelscope/HunyuanDiT/repo?Revision=master&FilePath=t2i%2Fclip_text_encoder%2Fpytorch_model.bin",
},
"HunyuanDiT/t2i/clip_text_encoder/config.json": {
"url": "https://www.modelscope.cn/api/v1/models/modelscope/HunyuanDiT/repo?Revision=master&FilePath=t2i%2Fclip_text_encoder%2Fconfig.json",
},
"HunyuanDiT/t2i/tokenizer/special_tokens_map.json": {
"url": "https://www.modelscope.cn/api/v1/models/modelscope/HunyuanDiT/repo?Revision=master&FilePath=t2i%2Ftokenizer%2Fspecial_tokens_map.json",
},
"HunyuanDiT/t2i/tokenizer/tokenizer_config.json": {
"url": "https://www.modelscope.cn/api/v1/models/modelscope/HunyuanDiT/repo?Revision=master&FilePath=t2i%2Ftokenizer%2Ftokenizer_config.json",
},
"HunyuanDiT/t2i/tokenizer/vocab.txt": {
"url": "https://www.modelscope.cn/api/v1/models/modelscope/HunyuanDiT/repo?Revision=master&FilePath=t2i%2Ftokenizer%2Fvocab.txt",
},
"HunyuanDiT/t2i/tokenizer/vocab_org.txt": {
"url": "https://www.modelscope.cn/api/v1/models/modelscope/HunyuanDiT/repo?Revision=master&FilePath=t2i%2Ftokenizer%2Fvocab_org.txt",
},
"HunyuanDiT/t2i/mt5/config.json": {
"url": "https://www.modelscope.cn/api/v1/models/modelscope/HunyuanDiT/repo?Revision=master&FilePath=t2i%2Fmt5%2Fconfig.json",
},
"HunyuanDiT/t2i/mt5/generation_config.json": {
"url": "https://www.modelscope.cn/api/v1/models/modelscope/HunyuanDiT/repo?Revision=master&FilePath=t2i%2Fmt5%2Fgeneration_config.json",
},
"HunyuanDiT/t2i/mt5/special_tokens_map.json": {
"url": "https://www.modelscope.cn/api/v1/models/modelscope/HunyuanDiT/repo?Revision=master&FilePath=t2i%2Fmt5%2Fspecial_tokens_map.json",
},
"HunyuanDiT/t2i/mt5/spiece.model": {
"url": "https://www.modelscope.cn/api/v1/models/modelscope/HunyuanDiT/repo?Revision=master&FilePath=t2i%2Fmt5%2Fspiece.model",
},
"HunyuanDiT/t2i/mt5/tokenizer_config.json": {
"url": "https://www.modelscope.cn/api/v1/models/modelscope/HunyuanDiT/repo?Revision=master&FilePath=t2i%2Fmt5%2Ftokenizer_config.json",
},
"HunyuanDiT/t2i/mt5/pytorch_model.bin": {
"url": "https://www.modelscope.cn/api/v1/models/modelscope/HunyuanDiT/repo?Revision=master&FilePath=t2i%2Fmt5%2Fpytorch_model.bin",
},
}
def check_model_auto_download(args):
unet_path = args.get("unet_path", "auto")
hunyuan_base_path = os.path.join(
Utils.get_comfyui_models_path(), "hunyuan")
if unet_path == "auto":
ema_to_module = args.get("ema_to_module", "enable") == "enable"
if ema_to_module:
download_file = "HunyuanDiT/t2i/model/pytorch_model_ema.pt"
else:
download_file = "HunyuanDiT/t2i/model/pytorch_model_module.pt"
download_fullpath = os.path.join(
hunyuan_base_path, download_file)
os.makedirs(os.path.dirname(download_fullpath), exist_ok=True)
if os.path.exists(download_fullpath):
args["unet_path"] = download_fullpath
else:
success_path = Utils.download_file(
HYDiT_MODEL[download_file]["url"],
download_fullpath,
)
if os.path.exists(success_path):
args["unet_path"] = download_fullpath
vae_ema_path = args.get("vae_ema_path", "auto")
if vae_ema_path == "auto":
download_files = []
prefix = "HunyuanDiT/t2i/sdxl-vae-fp16-fix/"
for key in HYDiT_MODEL:
if key.startswith(prefix):
download_files.append(key)
for download_file in download_files:
download_fullpath = os.path.join(
hunyuan_base_path, download_file)
os.makedirs(os.path.dirname(download_fullpath), exist_ok=True)
if not os.path.exists(download_fullpath):
Utils.download_file(
HYDiT_MODEL[download_file]["url"],
download_fullpath,
)
args["vae_ema_path"] = os.path.join(
hunyuan_base_path, prefix)
text_encoder_path = args.get("text_encoder_path", "auto")
if text_encoder_path == "auto":
download_files = []
prefix = "HunyuanDiT/t2i/clip_text_encoder/"
for key in HYDiT_MODEL:
if key.startswith(prefix):
download_files.append(key)
for download_file in download_files:
download_fullpath = os.path.join(
hunyuan_base_path, download_file)
os.makedirs(os.path.dirname(download_fullpath), exist_ok=True)
if not os.path.exists(download_fullpath):
Utils.download_file(
HYDiT_MODEL[download_file]["url"],
download_fullpath,
)
args["text_encoder_path"] = os.path.join(
hunyuan_base_path, prefix)
tokenizer_path = args.get("tokenizer_path", "auto")
if tokenizer_path == "auto":
download_files = []
prefix = "HunyuanDiT/t2i/tokenizer/"
for key in HYDiT_MODEL:
if key.startswith(prefix):
download_files.append(key)
for download_file in download_files:
download_fullpath = os.path.join(
hunyuan_base_path, download_file)
os.makedirs(os.path.dirname(download_fullpath), exist_ok=True)
if not os.path.exists(download_fullpath):
Utils.download_file(
HYDiT_MODEL[download_file]["url"],
download_fullpath,
)
args["tokenizer_path"] = os.path.join(
hunyuan_base_path, prefix)
t5_encoder_path = args.get("t5_encoder_path", "auto")
if t5_encoder_path == "auto":
download_files = []
prefix = "HunyuanDiT/t2i/mt5/"
for key in HYDiT_MODEL:
if key.startswith(prefix):
download_files.append(key)
for download_file in download_files:
download_fullpath = os.path.join(
hunyuan_base_path, download_file)
os.makedirs(os.path.dirname(download_fullpath), exist_ok=True)
if not os.path.exists(download_fullpath):
Utils.download_file(
HYDiT_MODEL[download_file]["url"],
download_fullpath,
)
args["t5_encoder_path"] = os.path.join(
hunyuan_base_path, prefix)
return args
def check_required():
# timm==0.9.5
# diffusers==0.21.2
# peft==0.10.0
# protobuf==3.19.0
# torchvision==0.14.1
# transformers==4.39.1
# peft==0.10.0
# accelerate==0.29.3
# loguru==0.7.2
# einops==0.7.0
# sentencepiece==0.1.99
# cuda-python==11.7.1
# onnxruntime==1.12.1
# onnx==1.12.0
# nvidia-pyindex==1.0.9
# onnx-graphsurgeon==0.3.27
# polygraphy==0.47.1
# pandas==2.0.3
# gradio==3.50.2
# deepspeed==0.6.3
# pyarrow==16.1.0
packages = [
"pandas", "pyarrow", "diffusers", "transformers",
"timm", "peft", "accelerate", "loguru", "einops", "sentencepiece",
"polygraphy"
]
for package in packages:
try:
__import__(package)
except ImportError:
subprocess.run([sys.executable, "-m", "pip",
"install", package], check=True)
def MZ_HYDiTTrain_call(args={}):
check_required()
args = check_model_auto_download(args)
# raise Exception(args)
resolution = args.get("resolution")
workspace_config = args.get("workspace_config", {})
workspace_name = workspace_config.get("workspace_name", None)
workspace_dir = workspace_config.get("workspace_dir", None)
workspace_images_dir = args.get("workspace_images_dir", None)
if workspace_images_dir is None:
workspace_images_dir = os.path.join(workspace_dir, "train_images")
full_filenames = os.listdir(workspace_images_dir)
# 创建image_text.csv文件
csv_filename = os.path.join(workspace_dir, "image_text.csv")
if os.path.exists(csv_filename):
os.remove(csv_filename)
max_width, max_height, min_width, min_height = 0, 0, 99999, 99999
from PIL import Image
with open(csv_filename, "w", encoding="utf-8", newline="") as file:
writer = csv.writer(file)
writer.writerow(["image_path", "text_zh"])
for filename in full_filenames:
if filename.endswith(".png"):
image_path = os.path.join(workspace_images_dir, filename)
pil_image = Image.open(image_path)
width, height = pil_image.size
if width > max_width:
max_width = width
if height > max_height:
max_height = height
if width < min_width:
min_width = width
if height < min_height:
min_height = height
text_zh = ""
# 去除后缀
_caption_filename = filename.split(".")[0]
caption_filename = ""
if os.path.exists(os.path.join(workspace_images_dir, _caption_filename + ".txt")):
caption_filename = _caption_filename + ".txt"
elif os.path.exists(os.path.join(workspace_images_dir, _caption_filename + ".caption")):
caption_filename = _caption_filename + ".caption"
else:
caption_filename = ""
if caption_filename != "":
with open(os.path.join(workspace_images_dir, caption_filename), "r") as f:
caption = f.read()
text_zh = caption
writer.writerow([image_path, text_zh])
if resolution > max_width and resolution > max_height:
print("resolution 大于所有图片的宽高,强制使用最大宽高作为resolution")
resolution = max(max_width, max_height) // 8 * 8
print(f"csv文件已生成: {csv_filename}")
HYDiT_tool_dir = os.path.join(
Utils.get_minus_zone_models_path(), "train_tools", "HunyuanDiT")
# python ./hydit/data_loader/csv2arrow.py ./dataset/porcelain/csvfile/image_text.csv ./dataset/porcelain/arrows
arrows_dir = os.path.join(workspace_dir, "arrows")
os.makedirs(arrows_dir, exist_ok=True)
csv2arrow_exec = os.path.join(
HYDiT_tool_dir, "hydit", "data_loader", "csv2arrow.py")
try:
subprocess.run([
sys.executable, csv2arrow_exec, csv_filename, arrows_dir
], check=True)
except Exception as e:
raise Exception(f"生成arrow文件时出现异常,详细信息请查看控制台...")
arrows_files = os.listdir(arrows_dir)
dataset_yaml_path = os.path.join(workspace_dir, "dataset.yaml")
import yaml
with open(dataset_yaml_path, "w", encoding="utf-8") as f:
yaml.dump({
"source": [os.path.join(arrows_dir, arrow_file) for arrow_file in arrows_files],
}, f)
print(f"dataset.yaml文件已生成: {dataset_yaml_path}")
idk_exec = os.path.join(
os.path.dirname(__file__), "hook_HYDiT_idk_run.py")
try:
from index_kits import __version__
except Exception as e:
subprocess.run([
sys.executable, "-m", "pip", "install", "-e", os.path.join(
HYDiT_tool_dir, "IndexKits")
], check=True)
# idk base -c dataset/yamls/porcelain.yaml -t dataset/porcelain/jsons/porcelain.json
dataset_json = os.path.join(workspace_dir, "dataset.json")
try:
subprocess.run([
sys.executable, idk_exec, "base", "-c", dataset_yaml_path, "-t", dataset_json,
], check=True)
except Exception as e:
raise Exception(f"生成index文件时出现异常,详细信息请查看控制台...")
print(f"index文件已生成: {dataset_json}")
dataset_mt_yaml_path = os.path.join(workspace_dir, "dataset_mt.yaml")
with open(dataset_mt_yaml_path, "w", encoding="utf-8") as f:
yaml.dump({
"src": [dataset_json],
"base_size": resolution,
"target_ratios": ["1:1", "3:4", "4:3", "16:9", "9:16"],
}, f)
print(f"dataset_mt.yaml文件已生成: {dataset_mt_yaml_path}")
# idk multireso -c dataset/yamls/porcelain_mt.yaml -t dataset/porcelain/jsons/porcelain_mt.json
dataset_mt_json = os.path.join(workspace_dir, "dataset_mt.json")
try:
subprocess.run([
sys.executable, idk_exec, "multireso", "-c", dataset_mt_yaml_path, "-t", dataset_mt_json,
], check=True)
except Exception as e:
raise Exception(f"生成多分辨率index文件时出现异常,详细信息请查看控制台...")
print(f"多分辨率index文件已生成: {dataset_mt_json}")
train_config = {}
train_config_path = os.path.join(workspace_dir, "mz_train_config.json")
if os.path.exists(train_config_path):
with open(train_config_path, "r", encoding="utf-8") as f:
train_config = json.load(f)
# "unet": (["auto"] + models + unet_models, {"default": "auto"}),
# "vae": (["auto"] + folders + vae_models, {"default": "auto"}),
# "text_encoder": (["auto"] + folders, {"default": "auto"}),
# "tokenizer": (["auto"] + folders, {"default": "auto"}),
# "t5_encoder": (["auto"] + folders, {"default": "auto"}),
epochs = args.get("epochs", 50)
output_dir = os.path.join(workspace_dir, "output")
datetime = time.strftime("%Y%m%d_%H%M%S", time.localtime())
output_name = f"{workspace_name}_{datetime}"
train_config.update({
"workspace_name": workspace_name,
"workspace_dir": workspace_dir,
"batch_size": args.get("batch_size", 1),
"ema_to_module": args.get("ema_to_module", "enable") == "enable",
"target_ratios": train_config.get("target_ratios", ["1:1", "3:4", "4:3", "16:9", "9:16"]),
"index_file": dataset_mt_json,
"epochs": epochs,
"ckpt_every": int(args.get("ckpt_every")),
"rope_img": f"base{resolution}",
"rope_real": args.get("rope_real", "enable") == "enable",
"image_size": resolution,
"unet_path": args.get("unet_path"),
"vae_ema_path": args.get("vae_ema_path"),
"text_encoder_path": args.get("text_encoder_path"),
"tokenizer_path": args.get("tokenizer_path"),
"t5_encoder_path": args.get("t5_encoder_path"),
"results_dir": output_dir,
"task_flag": output_name,
})
sample_generate = args.get("sample_generate", "enable")
sample_prompt = args.get("sample_prompt", "")
sample_config_file = os.path.join(workspace_dir, "sample_config.json")
if sample_generate == "enable":
resolution = args.get("resolution")
sample_config = {
"prompt": sample_prompt,
"negative_prompt": "",
"cfg": 5.0,
"steps": 20,
"width": resolution,
"height": resolution,
}
with open(sample_config_file, "w", encoding="utf-8") as f:
json.dump(sample_config, f, indent=4, ensure_ascii=False)
train_config["sample_config_file"] = sample_config_file
else:
train_config["sample_config_file"] = None
with open(train_config_path, "w", encoding="utf-8") as f:
json.dump(train_config, f, indent=4, ensure_ascii=False)
run_hook_HYDiT_pyexec(train_config_path, train_config, HYDiT_tool_dir)
return (
args,
)
def run_hook_HYDiT_pyexec(train_config_path, train_config, HYDiT_tool_dir):
pb = Utils.progress_bar(0, "sdxl")
import traceback
import comfy.model_management
stop_server = None
is_running = True
def log_callback(log):
try:
comfy.model_management.throw_exception_if_processing_interrupted()
except Exception as e:
stop_server()
return is_running
try:
resp = log
if resp.get("type") == "sample_images":
total_steps = resp.get("total_steps")
global_step = resp.get("global_step")
xy_img = get_sample_images(train_config)
if xy_img is None:
pb.update(
int(global_step), int(total_steps), None)
return is_running
max_side = max(xy_img.width, xy_img.height)
pb.update(
int(global_step), int(total_steps), ("JPEG", xy_img, max_side))
else:
print(f"LOG: {log}")
except Exception as e:
print(f"LOG: {log} e: {e} ")
print(f"stack: {traceback.format_exc()}")
return is_running
stop_server, port = Utils.Simple_Server(log_callback)
exec_pyfile = os.path.join(os.path.dirname(
__file__), "hook_HYDiT_run.py",)
try:
subprocess.run(
[sys.executable, exec_pyfile, "--sys_path", HYDiT_tool_dir,
"--master_port", str(port), "--train_config_file", train_config_path],
check=True,
)
stop_server()
is_running = False
except Exception as e:
stop_server()
is_running = False
raise Exception(f"训练失败!!! 具体报错信息请查看控制台...")
def get_sample_images(train_config):
if train_config.get("sample_config_file") is None:
return None
from PIL import Image
output_name = train_config.get("task_flag")
workspace_dir = train_config.get("workspace_dir")
sample_images_dir = os.path.join(workspace_dir, "sample_images")
pil_images = []
pre_render_texts_x = []
if os.path.exists(sample_images_dir):
image_files = os.listdir(sample_images_dir)
image_files = list(
filter(lambda x: x.endswith(".png"), image_files))
# 筛选 output_name 前缀
image_files = list(
filter(lambda x: x.startswith(output_name), image_files))
image_files = sorted(image_files, key=lambda x: x)
for image_file in image_files:
pil_image = Image.open(os.path.join(sample_images_dir, image_file))
pil_images.append([pil_image])
pre_render_texts_x.append(image_file)
result = Utils.xy_image(
pre_render_images=pil_images,
pre_render_texts_x=pre_render_texts_x,
pre_render_texts_y=[""],
)
return result
return None