Files
change_hair/train_lora_parallel.py
xsl 6f34e8876c feat: 适配 RTX 3090 (24GB) 环境优化
硬件迁移:从 RTX 5090 (32GB) 迁移到 RTX 3090 (24GB)

主要改动:
- 所有启动脚本和配置文件中的 /home/xsl/ 路径替换为 /home/ubuntu/
- 适配新的 24GB VRAM 环境
2026-07-18 19:00:15 +08:00

125 lines
5.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""直接调 kohya train_network.py 真正并行训练 LoRA
绕过 photo_service 的串行阻塞,用 subprocess 直接起 train_network.py 进程,
通过 ThreadPoolExecutor 控制并发数。
用法:
python /home/ubuntu/change_hair/train_lora_parallel.py
前提:step1 训练数据已准备好(data/train_material/<hid>/images/1_hairstyle/*.png
"""
import os
import sys
import time
import subprocess
from concurrent.futures import ThreadPoolExecutor, as_completed
TRAIN_DIR = "/home/ubuntu/change_hair/data/train_material"
KOHYA_WORKDIR = "/home/ubuntu/change_hair/project/kohya_ss_home/kohya_ss"
BASE_MODEL = "/home/ubuntu/change_hair/project/onediff/stable-diffusion-webui/models/Stable-diffusion/v1-5-pruned-emaonly.safetensors"
KOHYA_ACCEL = "/home/ubuntu/miniconda3/envs/kohya/bin/accelerate"
LOG_DIR = "/home/ubuntu/change_hair/project/logs"
# 本次要训练的发型
HAIRSTYLES = ["chang_tuoyuan", "chang_zhixian", "chang_huaban", "chang_xinxing"]
# chang_bolang 已完成,跳过
PARALLEL = 2
def log(msg):
print(f"[{time.strftime('%H:%M:%S')}] {msg}", flush=True)
def train_one(hair_id):
"""训练单个发型的 LoRA,返回 (hair_id, success, log_path)"""
images_dir = os.path.join(TRAIN_DIR, hair_id, "images")
model_dir = os.path.join(TRAIN_DIR, hair_id, "model")
os.makedirs(model_dir, exist_ok=True)
# 准备 sample prompt 文件
sample_dir = os.path.join(model_dir, "sample")
os.makedirs(sample_dir, exist_ok=True)
sample_txt = os.path.join(sample_dir, "prompt.txt")
with open(sample_txt, "w") as f:
f.write('titor hairstyle, easyphoto, faceless, no human, white background, simple background, '
' --n low quality, worst quality, bad anatomy, bad composition, poor, low effort --h 768 '
'--w 768 --s 30 --l 7')
log_path = os.path.join(LOG_DIR, f"train_{hair_id}.log")
cmd = [
"bash", "-c",
f'cd {KOHYA_WORKDIR} && '
f'HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 CUDA_VISIBLE_DEVICES=0 '
f'{KOHYA_ACCEL} launch --num_cpu_threads_per_process=2 "./train_network.py" --enable_bucket '
f'--min_bucket_reso=256 --max_bucket_reso=2048 --pretrained_model_name_or_path="{BASE_MODEL}" '
f'--train_data_dir={images_dir} --resolution="2000,2000" '
f'--output_dir={model_dir} '
'--network_alpha="64" --save_model_as=safetensors --network_module=networks.lora --text_encoder_lr=5e-05 '
'--unet_lr=0.0001 --network_dim=128 --output_name="hairstyle_hd_lora" --lr_scheduler_num_cycles="20" '
'--no_half_vae --learning_rate="0.0001" --lr_scheduler="cosine" --lr_warmup_steps="650" --train_batch_size="1" '
'--max_train_steps="1500" --save_every_n_epochs="100" --mixed_precision="fp16" --save_precision="fp16" '
'--caption_extension=".txt" --sample_sampler=ddim '
f'--sample_prompts={sample_txt} --sample_every_n_epochs="1000" '
'--seed="1234" --cache_latents --optimizer_type="AdamW" --max_data_loader_n_workers="0" --bucket_reso_steps=64 '
'--sdpa --bucket_no_upscale --noise_offset=0.0'
]
log(f" ▶ {hair_id} 开始训练,日志: {log_path}")
t0 = time.time()
try:
with open(log_path, "w") as lf:
proc = subprocess.run(cmd, stdout=lf, stderr=subprocess.STDOUT, timeout=1800)
ok = proc.returncode == 0
lora = os.path.join(model_dir, "hairstyle_hd_lora.safetensors")
ok = ok and os.path.exists(lora) and os.path.getsize(lora) > 100000000
elapsed = time.time() - t0
sz = os.path.getsize(lora) // 1024 // 1024 if os.path.exists(lora) else 0
log(f" {'✓' if ok else '✗'} {hair_id} {'训练完成' if ok else '训练失败'} ({sz}MB, {elapsed:.0f}s)")
return (hair_id, ok, log_path)
except subprocess.TimeoutExpired:
log(f" ✗ {hair_id} 训练超时")
return (hair_id, False, log_path)
except Exception as e:
log(f" ✗ {hair_id} 训练异常: {e}")
return (hair_id, False, log_path)
def main():
t0 = time.time()
log(f"并行 LoRA 训练: {HAIRSTYLES} (并发={PARALLEL})")
results = {}
with ThreadPoolExecutor(max_workers=PARALLEL) as ex:
futures = {ex.submit(train_one, hid): hid for hid in HAIRSTYLES}
for fut in as_completed(futures):
hid = futures[fut]
try:
hair_id, ok, log_path = fut.result()
results[hair_id] = ok
except Exception as e:
log(f" ✗ {hid} 异常: {e}")
results[hid] = False
log(f"\n{'='*60}")
log(f"全部完成,耗时 {time.time()-t0:.0f}s")
for hid, ok in results.items():
lora = os.path.join(TRAIN_DIR, hid, "model", "hairstyle_hd_lora.safetensors")
sz = os.path.getsize(lora) // 1024 // 1024 if os.path.exists(lora) else 0
log(f" {'✓' if ok else '✗'} {hid} ({sz}MB)")
success = [h for h, ok in results.items() if ok]
log(f"成功: {success}")
# 写完成清单
done_file = os.path.join(LOG_DIR, "train_lora_done.json")
import json
with open(done_file, "w") as f:
json.dump({"completed": success, "all": HAIRSTYLES}, f)
log(f"完成清单: {done_file}")
if __name__ == "__main__":
main()