pull/3451/head
sungjoonkim 2025-10-26 21:18:45 +09:00
parent 21808c5830
commit 29f722315d
9 changed files with 297 additions and 768 deletions

View File

@ -1,52 +1,52 @@
# 단일폴더 수동 학습 사용 예시
# I. 단일폴더 수동 학습 사용 예시
## 1. 기본 사용 (자동 계산)
```bash
python train_single.py --folder ../dataset/training/01_alice
run-train-single --folder ../dataset/training/01_alice
```
## 2. Epochs만 수동 지정
```bash
python train_single.py --folder ../dataset/training/01_alice --epochs 25
run-train-single --folder ../dataset/training/01_alice --epochs 25
```
## 3. 세밀한 조정
```bash
python train_single.py \
--folder ../dataset/training/01_alice \
--epochs 30 \
--repeats 50 \
--lr 0.00015 \
--dim 64 \
run-train-single ^
--folder ../dataset/training/01_alice ^
--epochs 30 ^
--repeats 50 ^
--lr 0.00015 ^
--dim 64 ^
--alpha 32
```
## 4. 고해상도 학습
```bash
python train_single.py \
--folder ../dataset/training/01_alice \
--resolution 1024,1024 \
run-train-single ^
--folder ../dataset/training/01_alice ^
--resolution 1024,1024 ^
--batch-size 1
```
## 5. 빠른 테스트
```bash
python train_single.py \
--folder ../dataset/training/01_alice \
--epochs 5 \
--repeats 10 \
run-train-single ^
--folder ../dataset/training/01_alice ^
--epochs 5 ^
--repeats 10 ^
--save-every 1
```
## 6. 완전 수동 모드
```bash
python train_single.py \
--folder ../dataset/training/01_alice \
--no-auto \
--epochs 20 \
--repeats 30 \
--lr 0.0001 \
--optimizer AdamW8bit \
run-train-single ^
--folder ../dataset/training/01_alice ^
--no-auto ^
--epochs 20 ^
--repeats 30 ^
--lr 0.0001 ^
--optimizer AdamW8bit ^
--scheduler cosine
```
@ -84,7 +84,7 @@ python train_batch.py
### train_single.py (단일 수동)
```bash
# 특정 폴더만 세밀 조정
python train_single.py --folder ../dataset/training/mainchar/01_alice --epochs 30 --lr 0.00015
run-train-single --folder ../dataset/training/mainchar/01_alice --epochs 30 --lr 0.00015
→ alice만 커스텀 파라미터로 학습
```
@ -96,19 +96,130 @@ python train_single.py --folder ../dataset/training/mainchar/01_alice --epochs 3
python train_batch.py
# 2. 결과가 좋지 않은 캐릭터만 재학습
python train_single.py --folder ../dataset/training/mainchar/01_alice --epochs 25
run-train-single --folder ../dataset/training/mainchar/01_alice --epochs 25
```
### 고급 사용자
```bash
# 처음부터 세밀하게 조정
python train_single.py \
--folder ../dataset/training/mainchar/01_alice \
--epochs 30 \
--repeats 50 \
--lr 0.00012 \
--dim 64 \
--alpha 32 \
--optimizer Prodigy \
run-train-single ^
--folder ../dataset/training/mainchar/01_alice ^
--epochs 30 ^
--repeats 50 ^
--lr 0.00012 ^
--dim 64 ^
--alpha 32 ^
--optimizer Prodigy ^
--resolution 1024,1024
```
# II. 단일폴더 학습재개(resume) 방법
## 1. 기본 Resume
```cmd
run-train-single --folder ../dataset/training/mainchar/01_alice --resume ../output_models/alice-epoch-010.safetensors
```
## 2. Resume + Learning Rate 조정 (Fine-tuning)
```cmd
run-train-single --folder ../dataset/training/mainchar/01_alice ^
--folder ../dataset/training/01_alice ^
--resume ../output_models/alice-epoch-010.safetensors ^
--epochs 20 ^
--lr 0.00005
```
## 3. Resume + 더 많은 데이터
```cmd
run-train-single --folder ../dataset/training/mainchar/01_alice ^
--folder ../dataset/training/01_alice_more ^
--resume ../output_models/alice-epoch-015.safetensors ^
--epochs 10
```
## 주의사항
✅ Resume 시 동일하게 유지해야 할 것
- --dim (network_dim)
- --alpha (network_alpha)
- 네트워크 구조 관련 설정
## ⚠️ Resume 시 변경 가능한 것
- --epochs (더 학습)
- --lr (learning rate 조정)
- --repeats (데이터 반복)
- --optimizer (optimizer 변경)
- --scheduler (스케줄러 변경)
## ❌ Resume 시 변경하면 안되는 것
```cmd
# 잘못된 예
run-train-single \
--folder ../dataset/training/01_alice \
--resume ../output_models/alice-epoch-010.safetensors \
--dim 64 # ❌ 원래 32였으면 에러!
```
## 실전 예시
### 시나리오 1: 학습이 중단됨
```cmd
# 10 epoch에서 중단
# → 10 epoch부터 이어서 15 epoch까지
run-train-single ^
--folder ../dataset/training/01_alice ^
--resume ../output_models/alice-epoch-010.safetensors ^
--epochs 15
```
### 시나리오 2: Overfitting 방지 (LR 감소)
```cmd
# 학습률 낮춰서 Fine-tuning
run-train-single ^
--folder ../dataset/training/01_alice ^
--resume ../output_models/alice-epoch-015.safetensors ^
--epochs 25 ^
--lr 0.00005
```
### 시나리오 3: 데이터 추가 후 재학습
```cmd
# 이미지 20장 → 50장으로 증가
run-train-single ^
--folder ../dataset/training/01_alice_extended ^
--resume ../output_models/alice-epoch-015.safetensors ^
--epochs 10 ^
--repeats 20
```
## 출력 예시
```
======================================================================
🎯 SDXL LoRA Training - Single Mode
======================================================================
📁 Folder: ../dataset/training/01_alice
💾 Output: alice.safetensors
📋 Config: config-24g.json
🖥️ GPU: 0 (24GB VRAM)
⚡ Precision: bf16
🔄 Resume from: ../output_models/alice-epoch-010.safetensors
----------------------------------------------------------------------
📊 Training Parameters
----------------------------------------------------------------------
Images: 25
Repeats: 48 (auto)
Epochs: 20 (manual)
Batch size: 1
Images/epoch: 1200
Steps/epoch: 1200
Total steps: 24000
======================================================================
학습을 시작하시겠습니까? (y/N): y
🔄 Resuming from: ../output_models/alice-epoch-010.safetensors
🚀 Starting training...
```

31
resume-train.cmd Normal file
View File

@ -0,0 +1,31 @@
accelerate launch --num_cpu_threads_per_process 1 --mixed_precision bf16 ^
sdxl_train_network.py ^
--resume="../output_models" ^
--max_train_epochs=20 ^
--pretrained_model_name_or_path="../models/sd_xl_base_1.0.safetensors" ^
--train_data_dir="../dataset/train/mainchar" ^
--output_dir="../output_models" ^
--logging_dir="../logs" ^
--output_name="karina" ^
--network_module=networks.lora ^
--network_dim=32 ^
--network_alpha=16 ^
--learning_rate=1e-4 ^
--optimizer_type="AdamW8bit" ^
--lr_scheduler="cosine" ^
--lr_warmup_steps=100 ^
--save_every_n_epochs=1 ^
--mixed_precision="bf16" ^
--save_precision="bf16" ^
--cache_latents ^
--cache_latents_to_disk ^
--gradient_checkpointing ^
--xformers ^
--seed=42 ^
--bucket_no_upscale ^
--min_bucket_reso=512 ^
--max_bucket_reso=2048 ^
--bucket_reso_steps=64 ^
--resolution="1024,1024" ^
--network_train_unet_only ^
--cache_text_encoder_outputs

6
run-caption-watcher.cmd Normal file
View File

@ -0,0 +1,6 @@
@echo off
setx CUDA_VISIBLE_DEVICES "3"
echo [Watcher] Starting caption watcher...
python cap-watcher.py --overwrite
REM --img_dir "../dataset/captioning/mainchar" --out_dir "../dataset/captioning/mainchar"
pause

View File

@ -1,5 +1,6 @@
@echo off
REM 첫 번째 argument를 명령어로 받아서 컨테이너에서 실행
REM 모든 argument를 그대로 넘기려면 %* 사용
docker exec -it sdxl_train_captioner bash -c "cd /app/sdxl_train_captioner/sd-scripts; ./run-train-auto.py 1 2>&1 | tee /app/sdxl_train_captioner/logs/train_$(date +%%Y%%m%%d_%%H%%M%%S).log"
docker exec -it sdxl_train_captioner bash -c "cd /app/sdxl_train_captioner/sd-scripts; ./run-train.sh config-24g.json 1 2>&1 | tee /app/sdxl_train_captioner/logs/train_$(date +%%Y%%m%%d_%%H%%M%%S).log"
pause

View File

@ -1,368 +0,0 @@
#!/usr/bin/env python3
"""
SDXL LoRA 일괄 학습 스크립트
- 학습 폴더 하위의 여러 캐릭터/개념을 자동으로 개별 LoRA 학습
- VRAM에 따른 자동 설정 (bf16/fp16)
- 이미지 수에 따른 최적 파라미터 자동 계산
"""
import os
import sys
import json
import subprocess
import argparse
from pathlib import Path
class TrainingConfig:
"""학습 설정 관리"""
def __init__(self, config_file, gpu_id=0, force_repeats=None):
self.config_file = config_file
self.gpu_id = gpu_id
self.force_repeats = force_repeats
# VRAM 감지
self.vram_size = self.get_vram_size()
# VRAM에 따른 설정
if self.vram_size >= 20:
self.precision = "bf16"
self.target_steps = 1800
else:
# 16GB 이하는 fp16 config 사용
self.config_file = "config-16g.json"
self.precision = "fp16"
self.target_steps = 1500
# Config 파일 로드
self.load_config()
def get_vram_size(self):
"""NVIDIA GPU VRAM 크기 감지 (GB)"""
try:
cmd = [
"nvidia-smi",
"--query-gpu=memory.total",
"--format=csv,noheader,nounits",
f"-i {self.gpu_id}"
]
result = subprocess.run(
' '.join(cmd),
shell=True,
capture_output=True,
text=True
)
vram_mb = int(result.stdout.strip())
vram_gb = vram_mb // 1024
return vram_gb
except Exception as e:
print(f"⚠️ VRAM 감지 실패, 기본값(24GB) 사용: {e}")
return 24
def load_config(self):
"""config.json 로드"""
if not os.path.exists(self.config_file):
print(f"❌ Config 파일 없음: {self.config_file}")
sys.exit(1)
with open(self.config_file, 'r', encoding='utf-8') as f:
self.config = json.load(f)
self.train_dir = self.config['folders']['train_data_dir']
self.output_dir = self.config['folders']['output_dir']
self.batch_size = self.config['training'].get('batch_size', 1)
class LoRATrainer:
"""단일 LoRA 학습 실행"""
def __init__(self, training_config):
self.config = training_config
self.image_extensions = {'.jpg', '.jpeg', '.png', '.webp', '.bmp'}
def find_training_folders(self):
"""학습 폴더 찾기 (순서_이름 패턴)"""
train_dir = self.config.train_dir
if not os.path.isdir(train_dir):
print(f"❌ 학습 디렉토리 없음: {train_dir}")
return []
folders = []
for item in os.listdir(train_dir):
item_path = os.path.join(train_dir, item)
if not os.path.isdir(item_path):
continue
# 패턴: 01_alice, 02_bob 등
parts = item.split('_', 1)
if len(parts) == 2 and parts[0].isdigit():
order = int(parts[0])
name = parts[1]
folders.append({
'order': order,
'name': name,
'path': item_path,
'folder': item
})
# 순서대로 정렬
folders.sort(key=lambda x: x['order'])
return folders
def count_images(self, folder_path):
"""폴더 내 이미지 개수 세기"""
count = 0
for file in os.listdir(folder_path):
if Path(file).suffix.lower() in self.image_extensions:
count += 1
return count
def calculate_training_params(self, image_count):
"""이미지 수에 따른 최적 학습 파라미터 계산"""
batch_size = self.config.batch_size
target_steps = self.config.target_steps
# 강제 반복 횟수가 지정되면 사용
if self.config.force_repeats is not None:
optimal_repeats = self.config.force_repeats
else:
# 이미지 수에 따른 자동 계산
if image_count < 20:
optimal_repeats = max(80, min(200, target_steps // (image_count * 10)))
elif image_count < 50:
optimal_repeats = max(30, min(80, target_steps // (image_count * 10)))
elif image_count < 100:
optimal_repeats = max(15, min(30, target_steps // (image_count * 10)))
else:
optimal_repeats = max(5, min(20, target_steps // (image_count * 10)))
# Epochs 계산
images_per_epoch = image_count * optimal_repeats
steps_per_epoch = images_per_epoch // batch_size
actual_epochs = max(1, round(target_steps / steps_per_epoch))
actual_epochs = min(max(actual_epochs, 5), 30)
actual_total_steps = actual_epochs * steps_per_epoch
return {
'repeats': optimal_repeats,
'epochs': actual_epochs,
'steps_per_epoch': steps_per_epoch,
'total_steps': actual_total_steps
}
def train_single_lora(self, folder_info):
"""단일 LoRA 학습 실행"""
name = folder_info['name']
folder_path = folder_info['path']
print(f"\n{'=' * 70}")
print(f"🎯 Training LoRA: {name}")
print(f"{'=' * 70}")
# 이미지 개수 확인
image_count = self.count_images(folder_path)
if image_count == 0:
print(f"⚠️ 이미지 없음: {folder_path}")
print(f"{'=' * 70}\n")
return False
# 파라미터 계산
params = self.calculate_training_params(image_count)
# 정보 출력
print(f"📊 Training Configuration")
print(f"{'-' * 70}")
print(f" GPU ID: {self.config.gpu_id}")
print(f" VRAM: {self.config.vram_size}GB")
print(f" Precision: {self.config.precision}")
print(f" Config: {self.config.config_file}")
print(f" Folder: {folder_info['folder']}")
print(f" Images: {image_count}")
print(f" Repeats: {params['repeats']}" +
(" (forced)" if self.config.force_repeats else " (auto)"))
print(f" Images/epoch: {image_count * params['repeats']}")
print(f" Steps/epoch: {params['steps_per_epoch']}")
print(f" Epochs: {params['epochs']}")
print(f" Total steps: {params['total_steps']}")
print(f"{'-' * 70}\n")
# accelerate 명령어 구성
cmd = [
"accelerate", "launch",
"--num_cpu_threads_per_process", "1",
"--mixed_precision", self.config.precision,
"sdxl_train_network.py",
f"--config_file={self.config.config_file}",
f"--train_data_dir={folder_path}",
f"--output_name={name}",
f"--max_train_epochs={params['epochs']}",
f"--dataset_repeats={params['repeats']}",
f"--mixed_precision={self.config.precision}"
]
# 실행
try:
env = os.environ.copy()
env['CUDA_VISIBLE_DEVICES'] = str(self.config.gpu_id)
print(f"🚀 Starting training...\n")
result = subprocess.run(cmd, env=env, check=True)
print(f"\n{name} 학습 완료!")
print(f"{'=' * 70}\n")
return True
except subprocess.CalledProcessError as e:
print(f"\n{name} 학습 실패: {e}")
print(f"{'=' * 70}\n")
return False
except KeyboardInterrupt:
print(f"\n⚠️ 사용자에 의해 중단됨")
return False
def run_batch_training(self):
"""일괄 학습 실행"""
folders = self.find_training_folders()
if not folders:
print("❌ 학습 폴더를 찾을 수 없습니다!")
print(f" 경로: {self.config.train_dir}")
print(f" 패턴: 01_name, 02_name, ...")
return
print(f"\n{'=' * 70}")
print(f"🚀 SDXL LoRA Batch Training")
print(f"{'=' * 70}")
print(f"📁 학습 폴더: {self.config.train_dir}")
print(f"💾 출력 폴더: {self.config.output_dir}")
print(f"🖥️ GPU: {self.config.gpu_id} ({self.config.vram_size}GB)")
print(f"⚡ Precision: {self.config.precision}")
print(f"📋 Config: {self.config.config_file}")
print(f"\n발견된 학습 폴더: {len(folders)}")
print(f"{'-' * 70}")
for f in folders:
img_count = self.count_images(f['path'])
print(f" {f['order']:02d}. {f['name']:20s} ({img_count} images)")
print(f"{'=' * 70}\n")
# 사용자 확인
try:
response = input("학습을 시작하시겠습니까? (y/N): ")
if response.lower() not in ['y', 'yes']:
print("❌ 학습 취소됨")
return
except KeyboardInterrupt:
print("\n❌ 학습 취소됨")
return
# 학습 실행
results = []
for i, folder in enumerate(folders, 1):
print(f"\n[{i}/{len(folders)}] Processing: {folder['name']}...")
success = self.train_single_lora(folder)
results.append({
'name': folder['name'],
'success': success
})
# 실패 시 계속 진행할지 물어봄
if not success:
try:
response = input("❓ 계속 진행하시겠습니까? (Y/n): ")
if response.lower() in ['n', 'no']:
print("⚠️ 나머지 학습 건너뜀")
break
except KeyboardInterrupt:
print("\n⚠️ 나머지 학습 건너뜀")
break
# 결과 요약
print(f"\n{'=' * 70}")
print(f"📊 Training Summary")
print(f"{'=' * 70}")
success_count = sum(1 for r in results if r['success'])
fail_count = len(results) - success_count
for r in results:
status = "" if r['success'] else ""
print(f"{status} {r['name']}")
print(f"{'-' * 70}")
print(f"✅ 성공: {success_count}/{len(results)}")
if fail_count > 0:
print(f"❌ 실패: {fail_count}/{len(results)}")
print(f"{'=' * 70}\n")
def main():
parser = argparse.ArgumentParser(
description="SDXL LoRA 일괄 학습 스크립트",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
사용 예시:
python train_batch.py
python train_batch.py config-16g.json
python train_batch.py config-24g.json 0 15
폴더 구조:
training/
01_alice/
*.jpg
02_bob/
*.jpg
03_background/
*.jpg
"""
)
parser.add_argument(
"config",
nargs="?",
default="config-24g.json",
help="Config 파일 (기본: config-24g.json)"
)
parser.add_argument(
"gpu_id",
nargs="?",
type=int,
default=0,
help="GPU ID (기본: 0)"
)
parser.add_argument(
"repeats",
nargs="?",
type=int,
default=None,
help="강제 반복 횟수 (기본: 자동 계산)"
)
args = parser.parse_args()
try:
# 설정 로드
training_config = TrainingConfig(
config_file=args.config,
gpu_id=args.gpu_id,
force_repeats=args.repeats
)
# 학습 실행
trainer = LoRATrainer(training_config)
trainer.run_batch_training()
except KeyboardInterrupt:
print("\n\n⚠️ 프로그램 중단됨")
sys.exit(1)
except Exception as e:
print(f"\n❌ 오류 발생: {e}")
import traceback
traceback.print_exc()
sys.exit(1)
if __name__ == "__main__":
main()

58
run-train-single.cmd Normal file
View File

@ -0,0 +1,58 @@
@echo off
setlocal enabledelayedexpansion
REM ===================================
REM SDXL LoRA 단일 학습 (Windows → Docker)
REM ===================================
REM 도움말
if "%1"=="" (
echo Usage: run-train-single.cmd --folder FOLDER [OPTIONS]
echo.
echo Examples:
echo run-train-single.cmd --folder ../dataset/training/01_alice
echo run-train-single.cmd --folder ../dataset/training/01_alice --epochs 25
echo run-train-single.cmd --folder ../dataset/training/01_alice --lr 0.0002 --dim 64
echo.
echo All arguments are passed to Python script inside container.
exit /b 1
)
REM 현재 시간 (로그 파일명용)
for /f "tokens=1-6 delims=/:. " %%a in ("%date% %time%") do (
set timestamp=%%a%%b%%c_%%d%%e%%f
)
set timestamp=%timestamp: =0%
REM 모든 arguments를 하나의 문자열로 결합
set args=%*
REM 작은따옴표 이스케이프 (Bash에서 안전하게)
set args=%args:'='\''%
echo ===================================
echo Starting SDXL LoRA Training
echo ===================================
echo Arguments: %args%
echo Log file: train_%timestamp%.log
echo ===================================
echo.
REM Docker에서 실행
docker exec -it sdxl_train_captioner bash -c "cd /app/sdxl_train_captioner/sd-scripts && python run-train-single.py %args% 2>&1 | tee /app/sdxl_train_captioner/logs/train_%timestamp%.log"
if %ERRORLEVEL% EQU 0 (
echo.
echo ===================================
echo Training completed successfully!
echo ===================================
) else (
echo.
echo ===================================
echo Training failed with error code: %ERRORLEVEL%
echo ===================================
)
endlocal
pause

55
run-train-single.ps1 Normal file
View File

@ -0,0 +1,55 @@
# ===================================
# SDXL LoRA 단일 학습 (PowerShell → Docker)
# ===================================
param(
[Parameter(ValueFromRemainingArguments=$true)]
[string[]]$Arguments
)
if ($Arguments.Count -eq 0) {
Write-Host "Usage: run-train-single.ps1 --folder FOLDER [OPTIONS]" -ForegroundColor Yellow
Write-Host ""
Write-Host "Examples:" -ForegroundColor Cyan
Write-Host " .\run-train-single.ps1 --folder ../dataset/training/01_alice"
Write-Host " .\run-train-single.ps1 --folder ../dataset/training/01_alice --epochs 25"
Write-Host " .\run-train-single.ps1 --folder ../dataset/training/01_alice --lr 0.0002 --dim 64"
exit 1
}
# 타임스탬프
$timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
# Arguments를 문자열로 결합
$argsString = $Arguments -join ' '
# 작은따옴표 이스케이프
$argsString = $argsString -replace "'", "'\\''"
Write-Host "===================================" -ForegroundColor Green
Write-Host "Starting SDXL LoRA Training" -ForegroundColor Green
Write-Host "===================================" -ForegroundColor Green
Write-Host "Arguments: $argsString" -ForegroundColor Cyan
Write-Host "Log file: train_$timestamp.log" -ForegroundColor Cyan
Write-Host "===================================" -ForegroundColor Green
Write-Host ""
# Docker 명령어
$dockerCmd = "cd /app/sdxl_train_captioner/sd-scripts && python run-train-single.py $argsString 2>&1 | tee /app/sdxl_train_captioner/logs/train_$timestamp.log"
# 실행
docker exec -it sdxl_train_captioner bash -c $dockerCmd
if ($LASTEXITCODE -eq 0) {
Write-Host ""
Write-Host "===================================" -ForegroundColor Green
Write-Host "Training completed successfully!" -ForegroundColor Green
Write-Host "===================================" -ForegroundColor Green
} else {
Write-Host ""
Write-Host "===================================" -ForegroundColor Red
Write-Host "Training failed with error code: $LASTEXITCODE" -ForegroundColor Red
Write-Host "===================================" -ForegroundColor Red
}
pause

View File

@ -1,365 +0,0 @@
#!/usr/bin/env python3
"""
SDXL LoRA 단일 학습 스크립트 (고급 사용자용)
- 특정 폴더만 선택 학습
- 세밀한 파라미터 조정 가능
- Config 오버라이드
"""
import os
import sys
import json
import subprocess
import argparse
from pathlib import Path
def get_vram_size(gpu_id=0):
"""NVIDIA GPU VRAM 크기 감지 (GB)"""
try:
cmd = f"nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits -i {gpu_id}"
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
vram_mb = int(result.stdout.strip())
return vram_mb // 1024
except:
return 24 # 기본값
def count_images(folder_path):
"""폴더 내 이미지 개수 세기"""
extensions = {'.jpg', '.jpeg', '.png', '.webp', '.bmp'}
count = 0
for file in os.listdir(folder_path):
if Path(file).suffix.lower() in extensions:
count += 1
return count
def calculate_auto_params(image_count, vram_size, batch_size=1):
"""이미지 수 기반 자동 파라미터 계산"""
target_steps = 1800 if vram_size >= 20 else 1500
# Repeats 계산
if image_count < 20:
repeats = max(80, min(200, target_steps // (image_count * 10)))
elif image_count < 50:
repeats = max(30, min(80, target_steps // (image_count * 10)))
elif image_count < 100:
repeats = max(15, min(30, target_steps // (image_count * 10)))
else:
repeats = max(5, min(20, target_steps // (image_count * 10)))
# Epochs 계산
images_per_epoch = image_count * repeats
steps_per_epoch = images_per_epoch // batch_size
epochs = max(1, round(target_steps / steps_per_epoch))
epochs = min(max(epochs, 5), 30)
return {
'repeats': repeats,
'epochs': epochs,
'steps_per_epoch': steps_per_epoch,
'total_steps': epochs * steps_per_epoch
}
def main():
parser = argparse.ArgumentParser(
description="SDXL LoRA 단일 학습 (고급 설정)",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
사용 예시:
# 기본 (자동 계산)
python train_single.py --folder ../dataset/training/01_alice
# 수동 파라미터 지정
python train_single.py --folder ../dataset/training/01_alice --epochs 20 --repeats 30
# Learning rate 조정
python train_single.py --folder ../dataset/training/01_alice --lr 0.0002
# Network dim 변경
python train_single.py --folder ../dataset/training/01_alice --dim 64 --alpha 32
# 전체 커스텀
python train_single.py \\
--folder ../dataset/training/01_alice \\
--output alice_v2 \\
--config config-24g.json \\
--gpu 0 \\
--epochs 25 \\
--repeats 40 \\
--lr 0.00015 \\
--dim 64 \\
--alpha 32 \\
--batch-size 2
"""
)
# 필수 인자
parser.add_argument(
"--folder",
required=True,
help="학습할 폴더 경로 (예: ../dataset/training/01_alice)"
)
# 기본 설정
parser.add_argument(
"--config",
default="config-24g.json",
help="Config 파일 (기본: config-24g.json)"
)
parser.add_argument(
"--output",
help="출력 LoRA 이름 (기본: 폴더명에서 추출)"
)
parser.add_argument(
"--gpu",
type=int,
default=0,
help="GPU ID (기본: 0)"
)
# 학습 파라미터
parser.add_argument(
"--epochs",
type=int,
help="총 Epoch 수 (기본: 자동 계산)"
)
parser.add_argument(
"--repeats",
type=int,
help="이미지 반복 횟수 (기본: 자동 계산)"
)
parser.add_argument(
"--batch-size",
type=int,
help="배치 사이즈 (기본: config 값)"
)
parser.add_argument(
"--lr",
type=float,
help="Learning rate (기본: config 값, 보통 1e-4)"
)
parser.add_argument(
"--dim",
type=int,
help="Network dimension (기본: config 값, 보통 32)"
)
parser.add_argument(
"--alpha",
type=int,
help="Network alpha (기본: config 값, 보통 16)"
)
parser.add_argument(
"--resolution",
help="해상도 (예: 1024,1024 또는 768,768)"
)
parser.add_argument(
"--save-every",
type=int,
help="N epoch마다 저장 (기본: config 값)"
)
# 고급 옵션
parser.add_argument(
"--optimizer",
help="Optimizer (예: AdamW8bit, Lion, Prodigy)"
)
parser.add_argument(
"--scheduler",
help="LR Scheduler (예: cosine, constant, polynomial)"
)
parser.add_argument(
"--no-auto",
action="store_true",
help="자동 계산 비활성화 (epochs/repeats 수동 지정 필수)"
)
args = parser.parse_args()
# 폴더 확인
folder_path = Path(args.folder)
if not folder_path.exists() or not folder_path.is_dir():
print(f"❌ 폴더를 찾을 수 없습니다: {folder_path}")
sys.exit(1)
# 이미지 개수
image_count = count_images(folder_path)
if image_count == 0:
print(f"❌ 이미지가 없습니다: {folder_path}")
sys.exit(1)
# VRAM 감지
vram_size = get_vram_size(args.gpu)
# Config 자동 선택
if vram_size >= 20:
precision = "bf16"
if args.config == "config-24g.json":
config_file = "config-24g.json"
else:
precision = "fp16"
config_file = "config-16g.json"
print(f"⚠️ VRAM {vram_size}GB < 20GB, fp16 모드로 전환")
# Config 로드
if not os.path.exists(config_file):
print(f"❌ Config 파일 없음: {config_file}")
sys.exit(1)
with open(config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
batch_size = args.batch_size or config['training'].get('batch_size', 1)
# 출력 이름 결정
if args.output:
output_name = args.output
else:
# 폴더명에서 추출 (01_alice → alice)
folder_name = folder_path.name
parts = folder_name.split('_', 1)
if len(parts) == 2 and parts[0].isdigit():
output_name = parts[1]
else:
output_name = folder_name
# 파라미터 결정
if args.no_auto:
# 수동 모드
if not args.epochs or not args.repeats:
print("❌ --no-auto 사용 시 --epochs와 --repeats 필수입니다")
sys.exit(1)
epochs = args.epochs
repeats = args.repeats
steps_per_epoch = (image_count * repeats) // batch_size
total_steps = epochs * steps_per_epoch
else:
# 자동 계산 (오버라이드 가능)
auto_params = calculate_auto_params(image_count, vram_size, batch_size)
epochs = args.epochs or auto_params['epochs']
repeats = args.repeats or auto_params['repeats']
steps_per_epoch = (image_count * repeats) // batch_size
total_steps = epochs * steps_per_epoch
# 학습 정보 출력
print(f"\n{'=' * 70}")
print(f"🎯 SDXL LoRA Training - Single Mode")
print(f"{'=' * 70}")
print(f"📁 Folder: {folder_path}")
print(f"💾 Output: {output_name}.safetensors")
print(f"📋 Config: {config_file}")
print(f"🖥️ GPU: {args.gpu} ({vram_size}GB VRAM)")
print(f"⚡ Precision: {precision}")
print(f"{'-' * 70}")
print(f"📊 Training Parameters")
print(f"{'-' * 70}")
print(f" Images: {image_count}")
print(f" Repeats: {repeats}" + (" (manual)" if args.repeats else " (auto)"))
print(f" Epochs: {epochs}" + (" (manual)" if args.epochs else " (auto)"))
print(f" Batch size: {batch_size}" + (" (override)" if args.batch_size else ""))
print(f" Images/epoch: {image_count * repeats}")
print(f" Steps/epoch: {steps_per_epoch}")
print(f" Total steps: {total_steps}")
# 오버라이드된 파라미터 표시
overrides = []
if args.lr:
print(f" Learning rate: {args.lr} (override)")
overrides.append(('lr', args.lr))
if args.dim:
print(f" Network dim: {args.dim} (override)")
overrides.append(('dim', args.dim))
if args.alpha:
print(f" Network alpha: {args.alpha} (override)")
overrides.append(('alpha', args.alpha))
if args.resolution:
print(f" Resolution: {args.resolution} (override)")
overrides.append(('resolution', args.resolution))
if args.optimizer:
print(f" Optimizer: {args.optimizer} (override)")
overrides.append(('optimizer', args.optimizer))
if args.scheduler:
print(f" LR Scheduler: {args.scheduler} (override)")
overrides.append(('scheduler', args.scheduler))
if args.save_every:
print(f" Save every: {args.save_every} epochs (override)")
overrides.append(('save_every', args.save_every))
print(f"{'=' * 70}\n")
# 사용자 확인
try:
response = input("학습을 시작하시겠습니까? (y/N): ")
if response.lower() not in ['y', 'yes']:
print("❌ 학습 취소됨")
sys.exit(0)
except KeyboardInterrupt:
print("\n❌ 학습 취소됨")
sys.exit(0)
# accelerate 명령어 구성
cmd = [
"accelerate", "launch",
"--num_cpu_threads_per_process", "1",
"--mixed_precision", precision,
"sdxl_train_network.py",
f"--config_file={config_file}",
f"--train_data_dir={folder_path}",
f"--output_name={output_name}",
f"--max_train_epochs={epochs}",
f"--dataset_repeats={repeats}",
f"--mixed_precision={precision}"
]
# 오버라이드 추가
if args.batch_size:
cmd.append(f"--train_batch_size={args.batch_size}")
if args.lr:
cmd.append(f"--learning_rate={args.lr}")
if args.dim:
cmd.append(f"--network_dim={args.dim}")
if args.alpha:
cmd.append(f"--network_alpha={args.alpha}")
if args.resolution:
cmd.append(f"--resolution={args.resolution}")
if args.optimizer:
cmd.append(f"--optimizer_type={args.optimizer}")
if args.scheduler:
cmd.append(f"--lr_scheduler={args.scheduler}")
if args.save_every:
cmd.append(f"--save_every_n_epochs={args.save_every}")
# 환경 변수 설정
env = os.environ.copy()
env['CUDA_VISIBLE_DEVICES'] = str(args.gpu)
# 실행
try:
print(f"\n🚀 Starting training...\n")
subprocess.run(cmd, env=env, check=True)
print(f"\n✅ 학습 완료: {output_name}.safetensors")
print(f"{'=' * 70}\n")
except subprocess.CalledProcessError as e:
print(f"\n❌ 학습 실패: {e}")
sys.exit(1)
except KeyboardInterrupt:
print(f"\n⚠️ 학습 중단됨")
sys.exit(1)
if __name__ == "__main__":
main()

@ -1 +1 @@
Subproject commit b97fa5fcb884e19ab50fb21b80ea65bf1e416b5c
Subproject commit 49115c25e9a0ffc6479b8f35d3ac76ba5352ca5b