back / JCo-MVTON /inference_dresscode_batch.py
shenaosdfa's picture
Upload JCo-MVTON project
b415e0a verified
Raw
History Blame Contribute Delete
19 kB
"""
DressCodeDataset 多卡批量推理脚本
基于 inference_viton_batch.py 重写,支持多 GPU 分布式推理
基于 DressCodeDataset 下的 dresses, lower_body, upper_body 子目录中的 test_pairs_unpaired.txt 文件批量处理
使用方法(单卡,自动选择checkpoint):
python inference_dresscode_batch.py \
--dresscode_root /filesdir/DressCodeDataset \
--output_dir /filesdir/JCo-MVTON/output_dresscode \
--gpu_id 0 \
--category dresses
# dresses 自动使用 try_on_dress.pt
# lower_body 自动使用 try_on_lower.pt
# upper_body 自动使用 try_on_upper.pt
使用方法(单卡,处理所有类别):
python inference_dresscode_batch.py \
--dresscode_root /filesdir/DressCodeDataset \
--output_dir /filesdir/JCo-MVTON/output_dresscode \
--gpu_id 0 \
--category all
# 每个类别自动使用对应的checkpoint
使用方法(多卡,使用 torchrun):
torchrun --nproc_per_node=4 inference_dresscode_batch.py \
--dresscode_root /filesdir/DressCodeDataset \
--output_dir /filesdir/JCo-MVTON/output_dresscode \
--category dresses
使用方法(多卡,使用 torchrun + nohup 后台运行):
nohup torchrun --nproc_per_node=4 inference_dresscode_batch.py \
--dresscode_root /filesdir/DressCodeDataset \
--output_dir /filesdir/JCo-MVTON/output_dresscode \
--category upper_body \
> nohup_dresscode_upper_body.out 2>&1 &
使用方法(手动多进程,每个类别一个GPU):
CUDA_VISIBLE_DEVICES=0 python inference_dresscode_batch.py --gpu_id 0 --category dresses &
CUDA_VISIBLE_DEVICES=1 python inference_dresscode_batch.py --gpu_id 1 --category lower_body &
CUDA_VISIBLE_DEVICES=2 python inference_dresscode_batch.py --gpu_id 2 --category upper_body &
使用方法(手动指定checkpoint,覆盖自动选择):
python inference_dresscode_batch.py \
--dresscode_root /filesdir/DressCodeDataset \
--checkpoint /filesdir/JCo-MVTON/try_on_upper.pt \
--output_dir /filesdir/JCo-MVTON/output_dresscode \
--gpu_id 0 \
--category dresses
# 如果指定了 --checkpoint,所有类别都使用这个checkpoint
"""
import torch
import torch.distributed as dist
from flux.pipeline_flux import FluxPipeline
from flux.transformer_flux import FluxTransformer2DModel
import os
import argparse
from pathlib import Path
from PIL import Image
from torchvision import transforms
import torchvision.utils as vutils
from tqdm import tqdm
def setup_distributed():
"""初始化分布式训练环境"""
if 'RANK' in os.environ and 'WORLD_SIZE' in os.environ:
rank = int(os.environ['RANK'])
world_size = int(os.environ['WORLD_SIZE'])
local_rank = int(os.environ.get('LOCAL_RANK', 0))
torch.cuda.set_device(local_rank)
dist.init_process_group(backend='nccl')
return rank, world_size, local_rank
else:
return None, 1, 0
def load_test_pairs(test_unpairs_file):
"""加载测试配对文件"""
pairs = []
with open(test_unpairs_file, 'r') as f:
for line in f:
line = line.strip()
if not line:
continue
# 支持制表符和空格分隔
parts = line.split('\t') if '\t' in line else line.split()
if len(parts) >= 2:
person_img = parts[0].strip()
cloth_img = parts[1].strip()
pairs.append((person_img, cloth_img))
return pairs
def load_model(checkpoint_path, device, mode=2, extra_branch_num=2, torch_dtype=torch.bfloat16):
"""加载模型(基于 inference.py 的逻辑)"""
model_id = "black-forest-labs/FLUX.1-dev"
print(f"[GPU {device}] 加载基础模型...")
transformer = FluxTransformer2DModel.from_pretrained(
model_id,
torch_dtype=torch_dtype,
subfolder="transformer",
extra_branch_num=extra_branch_num,
local_files_only=True,
low_cpu_mem_usage=False,
).to(device)
# 初始化 extra branch (mode 2)
print(f"[GPU {device}] 初始化 extra branch (mode {mode})...")
with torch.no_grad():
for j in range(extra_branch_num):
if mode == 1:
transformer.extra_embedder[j].load_state_dict(transformer.x_embedder.state_dict())
for i in range(transformer.config.num_layers):
transformer.transformer_blocks[i].attn.extra_to_q[j].load_state_dict(
transformer.transformer_blocks[i].attn.to_q.state_dict())
transformer.transformer_blocks[i].attn.extra_to_k[j].load_state_dict(
transformer.transformer_blocks[i].attn.to_k.state_dict())
transformer.transformer_blocks[i].attn.extra_to_v[j].load_state_dict(
transformer.transformer_blocks[i].attn.to_v.state_dict())
if mode == 1:
transformer.transformer_blocks[i].extra_norm1[j].load_state_dict(
transformer.transformer_blocks[i].norm1.state_dict())
transformer.transformer_blocks[i].extra_norm2[j].load_state_dict(
transformer.transformer_blocks[i].norm2.state_dict())
transformer.transformer_blocks[i].extra_ff[j].load_state_dict(
transformer.transformer_blocks[i].ff.state_dict())
transformer.transformer_blocks[i].attn.extra_to_out[0][j].load_state_dict(
transformer.transformer_blocks[i].attn.to_out[0].state_dict())
transformer.transformer_blocks[i].attn.extra_to_out[1][j].load_state_dict(
transformer.transformer_blocks[i].attn.to_out[1].state_dict())
transformer.transformer_blocks[i].attn.extra_norm_q[j].load_state_dict(
transformer.transformer_blocks[i].attn.norm_q.state_dict())
transformer.transformer_blocks[i].attn.extra_norm_k[j].load_state_dict(
transformer.transformer_blocks[i].attn.norm_k.state_dict())
if mode == 2:
for i in range(transformer.config.num_single_layers):
transformer.single_transformer_blocks[i].attn.extra_to_q[j].load_state_dict(
transformer.single_transformer_blocks[i].attn.to_q.state_dict())
transformer.single_transformer_blocks[i].attn.extra_to_k[j].load_state_dict(
transformer.single_transformer_blocks[i].attn.to_k.state_dict())
transformer.single_transformer_blocks[i].attn.extra_to_v[j].load_state_dict(
transformer.single_transformer_blocks[i].attn.to_v.state_dict())
# 创建 pipeline
print(f"[GPU {device}] 创建 pipeline...")
pipe = FluxPipeline.from_pretrained(
model_id,
torch_dtype=torch_dtype,
transformer=transformer,
local_files_only=True,
).to(device)
# 加载训练好的权重
print(f"[GPU {device}] 加载 checkpoint: {checkpoint_path}")
checkpoint = torch.load(checkpoint_path, map_location=device, weights_only=False)
transformer.load_state_dict(checkpoint['module'], strict=False)
del checkpoint # 释放内存
torch.cuda.empty_cache() # 清理 GPU 缓存
# 重新创建 pipeline(权重更新后)
pipe = FluxPipeline.from_pretrained(
model_id,
torch_dtype=torch_dtype,
transformer=transformer,
local_files_only=True,
).to(device)
return pipe, transformer
def process_pair(person_path, cloth_path, pipe, device, output_dir, resolution=1024, mode=2, seed=0):
"""处理一对图像(基于 inference.py 的逻辑)"""
height = resolution
width = resolution * 3 // 4
transform_person = transforms.Compose([
transforms.Resize(size=(height, width)),
transforms.ToTensor(),
transforms.Normalize([0.5], [0.5])
])
transform_cloth = transforms.Compose([
transforms.Resize(size=(height, height)),
transforms.ToTensor(),
transforms.Normalize([0.5], [0.5])
])
transform_output = transforms.Compose([
transforms.ToTensor(),
])
try:
# 加载图像
person = Image.open(person_path).convert("RGB").resize((width, height))
cloth = Image.open(cloth_path).convert("RGB").resize((height, height))
person_tensor = transform_person(person)
cloth_tensor = transform_cloth(cloth)
prompt = "A fashion model wearing stylish clothing, high-resolution 8k, detailed textures, realistic lighting, fashion photography style."
# 生成图像
with torch.inference_mode():
generated_image = pipe(
generator=torch.Generator(device="cpu").manual_seed(seed),
prompt=prompt,
num_inference_steps=28,
guidance_scale=3.5,
height=height,
width=width,
cloth_img=cloth_tensor,
person_img=person_tensor,
extra_branch_num=2,
mode=mode,
max_sequence_length=77,
).images[0]
# 生成的图像本来就是 768 宽度,直接使用
generated_tensor = transform_output(generated_image)
# 只保存生成的图像(人穿衣服的部分),不拼接
output_filename = Path(person_path).stem + "_" + Path(cloth_path).stem + ".png"
output_path = os.path.join(output_dir, output_filename)
vutils.save_image(generated_tensor, output_path)
return True, None
except Exception as e:
return False, str(e)
def main():
parser = argparse.ArgumentParser(description='DressCodeDataset 多卡批量推理')
parser.add_argument('--dresscode_root', type=str, required=True,
help='DressCodeDataset 根目录路径')
parser.add_argument('--checkpoint', type=str, default=None,
help='模型 checkpoint 路径(如果指定,将用于所有类别;否则会根据类别自动选择)')
parser.add_argument('--checkpoint_dir', type=str, default='/filesdir/JCo-MVTON',
help='Checkpoint 目录路径(用于自动选择checkpoint)')
parser.add_argument('--output_dir', type=str, required=True,
help='输出目录路径')
parser.add_argument('--category', type=str, required=True,
choices=['dresses', 'lower_body', 'upper_body', 'all'],
help='要处理的类别:dresses, lower_body, upper_body, 或 all(处理所有类别)')
parser.add_argument('--gpu_id', type=int, default=0,
help='使用的 GPU ID(单卡模式)')
parser.add_argument('--test_file', type=str, default='test_pairs_unpaired.txt',
help='测试文件名称(相对于 category 目录)')
parser.add_argument('--mode', type=int, default=2,
help='模型模式 (1 或 2)')
parser.add_argument('--extra_branch_num', type=int, default=2,
help='额外分支数量')
parser.add_argument('--resolution', type=int, default=1024,
help='图像分辨率')
parser.add_argument('--seed', type=int, default=0,
help='随机种子')
parser.add_argument('--start_idx', type=int, default=None,
help='开始索引(手动分配任务时使用)')
parser.add_argument('--end_idx', type=int, default=None,
help='结束索引(手动分配任务时使用)')
args = parser.parse_args()
# 初始化分布式环境
rank, world_size, local_rank = setup_distributed()
if rank is not None:
# 多卡模式(使用 torchrun)
device = f"cuda:{local_rank}"
gpu_id = local_rank
is_main_process = (rank == 0)
else:
# 单卡模式
device = f"cuda:{args.gpu_id}"
gpu_id = args.gpu_id
is_main_process = True
rank = 0
world_size = 1
# 确定要处理的类别
if args.category == 'all':
categories = ['dresses', 'lower_body', 'upper_body']
else:
categories = [args.category]
# Checkpoint 映射:每个类别对应的checkpoint文件
checkpoint_map = {
'dresses': 'try_on_dress.pt',
'lower_body': 'try_on_lower.pt',
'upper_body': 'try_on_upper.pt',
}
# 处理每个类别
for category in categories:
if is_main_process:
print(f"\n{'='*60}")
print(f"处理类别: {category}")
print(f"{'='*60}")
# 确定该类别使用的checkpoint
if args.checkpoint:
# 如果用户指定了checkpoint,使用指定的
category_checkpoint = args.checkpoint
else:
# 否则根据类别自动选择
checkpoint_filename = checkpoint_map.get(category)
if not checkpoint_filename:
if is_main_process:
print(f"错误: 类别 {category} 没有对应的checkpoint映射")
continue
category_checkpoint = os.path.join(args.checkpoint_dir, checkpoint_filename)
if not os.path.exists(category_checkpoint):
if is_main_process:
print(f"错误: Checkpoint 文件不存在: {category_checkpoint}")
continue
if is_main_process:
print(f"使用 checkpoint: {category_checkpoint}")
# 加载模型(每个类别加载对应的checkpoint)
if is_main_process:
print(f"加载模型...")
pipe, transformer = load_model(
category_checkpoint, device, args.mode, args.extra_branch_num
)
# 创建输出目录(每个类别一个子目录)
category_output_dir = Path(args.output_dir) / category
category_output_dir.mkdir(parents=True, exist_ok=True)
# 加载测试对
category_dir = os.path.join(args.dresscode_root, category)
test_unpairs_file = os.path.join(category_dir, args.test_file)
if not os.path.exists(test_unpairs_file):
if is_main_process:
print(f"警告: 找不到测试文件: {test_unpairs_file},跳过类别 {category}")
continue
# 图像目录:DressCodeDataset 的图像都在 {category}/images/ 目录下
images_dir = os.path.join(category_dir, "images")
if not os.path.exists(images_dir):
if is_main_process:
print(f"警告: 找不到图像目录: {images_dir},跳过类别 {category}")
continue
all_pairs = load_test_pairs(test_unpairs_file)
total_pairs = len(all_pairs)
# 确定任务分配
if args.start_idx is not None and args.end_idx is not None:
# 手动指定范围
start_idx = args.start_idx
end_idx = args.end_idx
else:
# 自动分配(基于 rank)
per_gpu = total_pairs // world_size
start_idx = rank * per_gpu
if rank == world_size - 1:
end_idx = total_pairs # 最后一个 GPU 处理剩余的所有任务
else:
end_idx = start_idx + per_gpu
pairs_to_process = all_pairs[start_idx:end_idx]
# 预先检查有多少文件已存在
existing_count = 0
for person_img, cloth_img in pairs_to_process:
output_filename = Path(person_img).stem + "_" + Path(cloth_img).stem + ".png"
output_path = os.path.join(category_output_dir, output_filename)
if os.path.exists(output_path):
existing_count += 1
if is_main_process:
print(f"类别 {category}: 总共 {total_pairs} 对图像")
print(f"GPU {gpu_id} (rank {rank}) 处理 {start_idx}{end_idx} ({len(pairs_to_process)} 对)")
print(f"图像目录: {images_dir}")
print(f"输出目录: {category_output_dir}")
if existing_count > 0:
print(f"发现 {existing_count} 个已存在的文件,将跳过")
# 处理图像对(模型已在循环外加载)
success_count = 0
fail_count = 0
skip_count = 0
if is_main_process:
pbar = tqdm(pairs_to_process, desc=f"GPU {gpu_id} - {category}")
else:
pbar = pairs_to_process
for person_img, cloth_img in pbar:
person_path = os.path.join(images_dir, person_img)
cloth_path = os.path.join(images_dir, cloth_img)
# 检查输出文件是否已存在
output_filename = Path(person_img).stem + "_" + Path(cloth_img).stem + ".png"
output_path = os.path.join(category_output_dir, output_filename)
if os.path.exists(output_path):
# 输出文件已存在,跳过
skip_count += 1
if is_main_process and skip_count % 100 == 0:
# 每跳过100个文件输出一次日志,避免日志过多
print(f"[GPU {gpu_id}] 已跳过 {skip_count} 个已存在的文件...")
continue
if not os.path.exists(person_path):
if is_main_process:
print(f"[GPU {gpu_id}] 警告: 找不到人物图像 {person_path}")
fail_count += 1
continue
if not os.path.exists(cloth_path):
if is_main_process:
print(f"[GPU {gpu_id}] 警告: 找不到衣服图像 {cloth_path}")
fail_count += 1
continue
success, error = process_pair(
person_path, cloth_path, pipe, device, category_output_dir,
args.resolution, args.mode, args.seed
)
if success:
success_count += 1
else:
fail_count += 1
if is_main_process:
print(f"[GPU {gpu_id}] 处理失败 {person_img} + {cloth_img}: {error}")
if is_main_process:
print(f"\n类别 {category} - GPU {gpu_id} 完成:")
print(f" 成功: {success_count}")
print(f" 跳过: {skip_count} (已存在)")
print(f" 失败: {fail_count}")
print(f" 输出目录: {category_output_dir}")
# 清理分布式环境
if rank is not None:
dist.destroy_process_group()
if __name__ == "__main__":
main()