세션 풀 초기화 및 모델 로딩 로직을 개선하고, CUDA 자동 감지 기능을 추가하였습니다. 상태 JSON 파일의 워커 수를 4로 업데이트하고, 로그 파일에 서버 시작 및 상태 저장 관련 메시지를 기록하였습니다. README.md에 Jetson Xavier에서의 ONNX Runtime GPU 설치 절차를 추가하였으며, 요구 사항 파일에서 numpy 버전을 1.24.4로 업데이트하였습니다. 스크립트에서 자동 설치 및 실행 기능을 개선하고, x86 시스템에 대한 설치 스크립트를 수정하였습니다.
This commit is contained in:
parent
b5a0098eb7
commit
83b24d4e05
89
README.md
89
README.md
|
|
@ -69,9 +69,12 @@ FastAPI와 딥러닝을 활용한 병렬 처리 인페인팅 서버입니다. Si
|
|||
- **CUDA**: 11.8
|
||||
- **cuDNN**: 8
|
||||
- **TensorRT**: 8.6
|
||||
- **GCC**: 11 이상 (ONNX Runtime GPU 호환성용)
|
||||
- **RAM**: 4GB 이상 권장
|
||||
- **저장공간**: 10GB 이상
|
||||
|
||||
> **중요**: Jetson Xavier에서 GPU 가속을 위해서는 GCC 11과 특별한 ONNX Runtime 버전이 필요합니다. 자동 설치 스크립트가 이를 자동으로 처리합니다.
|
||||
|
||||
### x86_64 시스템
|
||||
- **OS**: Ubuntu 18.04 이상
|
||||
- **Python**: 3.8 이상 (3.10 권장)
|
||||
|
|
@ -104,34 +107,58 @@ cd inpaintServer
|
|||
chmod +x scripts/*.sh
|
||||
```
|
||||
|
||||
### 2. 원클릭 설치 및 실행
|
||||
### 2. 🚀 원클릭 자동 설치 (권장)
|
||||
|
||||
#### Jetson Xavier (ARM64)
|
||||
#### 플랫폼 자동 감지 설치
|
||||
```bash
|
||||
# Jetson 전용 설치 및 실행
|
||||
bash scripts/setup_and_run.sh
|
||||
# 🎯 플랫폼 자동 감지 후 최적 설치
|
||||
bash scripts/install.sh
|
||||
```
|
||||
|
||||
#### x86_64 시스템 (RTX 3060 12GB 등)
|
||||
#### 플랫폼별 직접 설치
|
||||
|
||||
**🚀 Jetson Xavier (ARM64):**
|
||||
```bash
|
||||
# x86 전용 설치 및 실행
|
||||
# Jetson Xavier 전용 최적화 설치
|
||||
bash scripts/setup_jetson.sh
|
||||
```
|
||||
|
||||
**🖥️ x86-64 Desktop:**
|
||||
```bash
|
||||
# x86-64 데스크톱 최적화 설치
|
||||
bash scripts/setup_x86.sh
|
||||
```
|
||||
|
||||
```bash
|
||||
# 시스템 자동 감지 (권장)
|
||||
./scripts/setup_and_run.sh
|
||||
### 📁 가상환경 설정 방식
|
||||
|
||||
# 또는 시스템별 지정
|
||||
./scripts/setup_and_run.sh --jetson # Jetson Xavier
|
||||
./scripts/setup_and_run.sh --x86 # x86_64 시스템
|
||||
이 프로젝트는 **유연한 가상환경 설정**을 지원합니다:
|
||||
|
||||
#### 방식 1: 표준 venv 디렉토리 (기본)
|
||||
```bash
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
```
|
||||
|
||||
**이 스크립트가 자동으로 수행하는 작업:**
|
||||
- ✅ 가상환경 생성 (`python -m venv venv`)
|
||||
- ✅ 의존성 설치 (`requirements.txt`)
|
||||
- ✅ 모델 파일 확인 및 안내
|
||||
- ✅ 서버 시작 (메인 + 모니터링)
|
||||
#### 방식 2: 프로젝트 자체를 가상환경으로 사용
|
||||
```bash
|
||||
# 프로젝트 디렉토리에서 직접 가상환경 생성
|
||||
python3 -m venv .
|
||||
source bin/activate
|
||||
```
|
||||
|
||||
> **💡 스마트 감지**: 설치 스크립트가 자동으로 감지하고 처리합니다
|
||||
> - `venv/` 디렉토리 존재 → 표준 venv 사용
|
||||
> - `pyvenv.cfg` 파일 존재 → 프로젝트 자체가 가상환경
|
||||
> - 둘 다 없음 → 새로운 `venv/` 디렉토리 생성
|
||||
|
||||
**자동 설치가 수행하는 작업:**
|
||||
- ✅ 플랫폼 자동 감지 (Jetson Xavier vs x86-64)
|
||||
- ✅ 가상환경 자동 생성/감지
|
||||
- ✅ GPU 최적화 의존성 설치
|
||||
- ✅ ONNX Runtime GPU 설치 (플랫폼별)
|
||||
- ✅ PyTorch CUDA 설치
|
||||
- ✅ 모델 호환성 확인
|
||||
- ✅ 설치 검증 및 테스트
|
||||
|
||||
### 3. 수동 설치 (고급 사용자)
|
||||
|
||||
|
|
@ -638,6 +665,34 @@ ls -la app/models/onnx/
|
|||
|
||||
### Jetson 전용 문제
|
||||
|
||||
#### ONNX Runtime GPU 설치 (GLIBCXX 호환성 문제)
|
||||
|
||||
Jetson Xavier에서 ONNX Runtime GPU를 사용하려면 특별한 절차가 필요합니다:
|
||||
|
||||
```bash
|
||||
# 1. GCC 11 업그레이드 (필수)
|
||||
sudo apt install software-properties-common
|
||||
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
|
||||
sudo apt update
|
||||
sudo apt install gcc-11 g++-11
|
||||
|
||||
# 2. Jetson 전용 ONNX Runtime GPU 휠 다운로드 및 설치
|
||||
wget https://nvidia.box.com/shared/static/zostg6agm00fb6t5uisw51qi6kpcuwzd.whl \
|
||||
-O onnxruntime_gpu-1.17.0-cp38-cp38-linux_aarch64.whl
|
||||
pip install --force-reinstall onnxruntime_gpu-1.17.0-cp38-cp38-linux_aarch64.whl
|
||||
|
||||
# 3. 설치 확인
|
||||
python -c "import onnxruntime as ort; print('ONNX Runtime 버전:', ort.__version__); print('Available providers:', ort.get_available_providers())"
|
||||
```
|
||||
|
||||
**예상 출력**:
|
||||
```
|
||||
ONNX Runtime 버전: 1.17.0
|
||||
Available providers: ['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||
```
|
||||
|
||||
> **참고**: 자동 설치 스크립트를 사용하면 위 과정이 자동으로 수행됩니다.
|
||||
|
||||
#### 전력 모드 설정
|
||||
```bash
|
||||
# MAXN 모드로 설정
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ Configuration settings for the inpainting server
|
|||
"""
|
||||
import os
|
||||
import platform
|
||||
from typing import Dict, Any
|
||||
from typing import Dict, Any, Optional
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
|
|
@ -17,9 +17,16 @@ class Settings(BaseSettings):
|
|||
PORT: int = 8008
|
||||
WORKERS: int = 1
|
||||
|
||||
# GPU settings
|
||||
# GPU settings (Jetson Xavier 최적화)
|
||||
CUDA_DEVICE: int = 0
|
||||
FP16_ENABLED: bool = True
|
||||
USE_CUDA: bool = True # CUDA 사용 여부 (Jetson에서 항상 True)
|
||||
USE_FP16: bool = True # FP16 사용 여부 (Jetson 최적화)
|
||||
FP16_ENABLED: bool = True # 기존 호환성
|
||||
|
||||
# ONNX Runtime 최적화 설정
|
||||
USE_TENSORRT: bool = True # TensorRT 사용 여부 (최고 성능)
|
||||
TENSORRT_FP16: bool = True # TensorRT FP16 사용
|
||||
TENSORRT_WORKSPACE_SIZE: int = 2 * 1024 * 1024 * 1024 # 2GB
|
||||
|
||||
# Jetson specific settings
|
||||
JETSON_MODE: bool = IS_JETSON
|
||||
|
|
@ -27,29 +34,38 @@ class Settings(BaseSettings):
|
|||
JETSON_FAN_CONTROL: bool = True
|
||||
JETSON_TEMP_THRESHOLD: int = 75 # Celsius
|
||||
|
||||
# Session pool settings
|
||||
SIMPLE_LAMA_SESSIONS: int = 2 if IS_JETSON else 4
|
||||
MIGAN_SESSIONS: int = 2 if IS_JETSON else 4
|
||||
REMBG_SESSIONS: int = 1 if IS_JETSON else 3
|
||||
# Session pool settings (Jetson Xavier는 32GB 통합 메모리로 더 많은 세션 가능)
|
||||
SIMPLE_LAMA_SESSIONS: int = 4 if IS_JETSON else 2 # Jetson: 통합 32GB vs 데스크톱: VRAM 제한
|
||||
MIGAN_SESSIONS: int = 4 if IS_JETSON else 2 # Jetson이 더 많은 세션 운영 가능
|
||||
REMBG_SESSIONS: int = 3 if IS_JETSON else 1 # 메모리 공유 방식의 이점 활용
|
||||
|
||||
# Worker settings (Jetson은 더 적은 워커 사용)
|
||||
MAX_WORKERS: int = 6 if IS_JETSON else 16
|
||||
MIN_WORKERS: int = 2 if IS_JETSON else 8
|
||||
# Worker settings (Jetson은 통합 메모리로 더 효율적)
|
||||
MAX_WORKERS: int = 8 if IS_JETSON else 6 # Jetson: 메모리 오버헤드 적음
|
||||
MIN_WORKERS: int = 4 if IS_JETSON else 2 # 통합 메모리 활용
|
||||
WORKER_TIMEOUT: int = 120 # 2 minutes
|
||||
|
||||
# VRAM management (Jetson은 더 보수적인 설정)
|
||||
VRAM_THRESHOLD_HIGH: float = 0.7 if IS_JETSON else 0.85 # 70% for Jetson, 85% for x86
|
||||
VRAM_THRESHOLD_LOW: float = 0.3 if IS_JETSON else 0.25 # 30% for Jetson, 25% for x86
|
||||
VRAM_CHECK_INTERVAL: int = 20 if IS_JETSON else 20 # More frequent for both
|
||||
# 메모리 관리 (Jetson은 32GB 통합 메모리로 여유로움)
|
||||
VRAM_THRESHOLD_HIGH: float = 0.85 if IS_JETSON else 0.75 # Jetson: 32GB 통합 메모리
|
||||
VRAM_THRESHOLD_LOW: float = 0.4 if IS_JETSON else 0.3 # 데스크톱: VRAM 제한
|
||||
VRAM_CHECK_INTERVAL: int = 30 if IS_JETSON else 15 # Jetson은 덜 자주 체크
|
||||
|
||||
# Model paths
|
||||
SIMPLE_LAMA_MODEL_PATH: str = "models/simple-lama"
|
||||
MIGAN_MODEL_PATH: str = "models/migan"
|
||||
REMBG_MODEL_PATH: str = "models/rembg"
|
||||
SIMPLE_LAMA_MODEL_PATH: str = "app/models/pt/big-lama.pt"
|
||||
MIGAN_MODEL_PATH: str = "app/models/onnx/migan_pipeline_v2.onnx"
|
||||
REMBG_MODEL_PATH: str = "app/models/onnx/birefnet-general-lite.onnx"
|
||||
|
||||
# Upload settings
|
||||
MAX_FILE_SIZE: int = 25 * 1024 * 1024 if IS_JETSON else 100 * 1024 * 1024 # 25MB for Jetson, 100MB for x86
|
||||
MAX_IMAGE_SIZE: int = 2048 if IS_JETSON else 8192 # Maximum image dimension for Jetson, 8K for x86
|
||||
# MIGAN ONNX settings
|
||||
MIGAN_ONNX_PATH: Optional[str] = "app/models/onnx/migan_pipeline_v2.onnx" # 커스텀 ONNX 파일 경로
|
||||
MIGAN_INTRA_THREADS: int = 0
|
||||
MIGAN_INTER_THREADS: int = 0
|
||||
|
||||
# REMBG settings (자동 다운로드 방식)
|
||||
REMBG_MODEL_NAME: str = "birefnet-general-lite" # 고품질 경량 모델
|
||||
LOCAL_REMBG_MODEL_PATH: Optional[str] = None # 로컬 파일 사용 안함
|
||||
|
||||
# Upload settings (Jetson Xavier는 32GB 메모리로 대용량 처리 가능)
|
||||
MAX_FILE_SIZE: int = 100 * 1024 * 1024 if IS_JETSON else 50 * 1024 * 1024 # Jetson: 100MB, 데스크톱: 50MB
|
||||
MAX_IMAGE_SIZE: int = 4096 if IS_JETSON else 3072 # Jetson: 4K, 데스크톱: 3K (VRAM 고려)
|
||||
ALLOWED_EXTENSIONS: set = {".jpg", ".jpeg", ".png", ".bmp", ".tiff"}
|
||||
|
||||
# Monitoring
|
||||
|
|
|
|||
|
|
@ -105,21 +105,62 @@ class SessionPool:
|
|||
|
||||
async def _load_simple_lama_model(self):
|
||||
"""Simple LAMA 모델을 로드합니다."""
|
||||
# Placeholder - 실제 모델 로딩 로직으로 대체
|
||||
await asyncio.sleep(0.1) # 시뮬레이션
|
||||
return {"model": "simple_lama", "loaded": True}
|
||||
from ..models.simple_lama import SimpleLamaInpainter
|
||||
from ..core.config import settings
|
||||
|
||||
try:
|
||||
model = SimpleLamaInpainter(
|
||||
model_path=settings.SIMPLE_LAMA_MODEL_PATH,
|
||||
device="cuda" if settings.USE_CUDA else "cpu",
|
||||
fp16=settings.USE_FP16
|
||||
)
|
||||
await model.load_model()
|
||||
logger.info("Simple LAMA 모델 세션 로드 완료")
|
||||
return model
|
||||
except Exception as e:
|
||||
logger.error(f"Simple LAMA 모델 로드 실패: {e}")
|
||||
raise
|
||||
|
||||
async def _load_migan_model(self):
|
||||
"""MIGAN 모델을 로드합니다."""
|
||||
# Placeholder - 실제 모델 로딩 로직으로 대체
|
||||
await asyncio.sleep(0.1) # 시뮬레이션
|
||||
return {"model": "migan", "loaded": True}
|
||||
from ..models.migan import MiganInpainter
|
||||
from ..core.config import settings
|
||||
|
||||
try:
|
||||
# MIGAN 모델 생성 - ONNX Runtime이 자동으로 CUDA 감지
|
||||
model = MiganInpainter(
|
||||
model_path=getattr(settings, 'MIGAN_ONNX_PATH', settings.MIGAN_MODEL_PATH),
|
||||
device="cuda" if settings.USE_CUDA else "cpu",
|
||||
fp16=settings.USE_FP16,
|
||||
use_cuda=settings.USE_CUDA
|
||||
)
|
||||
await model.load_model()
|
||||
logger.info("MIGAN 모델 세션 로드 완료")
|
||||
return model
|
||||
except Exception as e:
|
||||
logger.error(f"MIGAN 모델 로드 실패: {e}")
|
||||
raise
|
||||
|
||||
async def _load_rembg_model(self):
|
||||
"""REMBG 모델을 로드합니다."""
|
||||
# Placeholder - 실제 모델 로딩 로직으로 대체
|
||||
await asyncio.sleep(0.1) # 시뮬레이션
|
||||
return {"model": "rembg", "loaded": True}
|
||||
from ..models.rembg_model import RembgProcessor
|
||||
from ..core.config import settings
|
||||
|
||||
try:
|
||||
# RemBG 모델 생성 - 자동으로 CUDA 감지
|
||||
model = RembgProcessor(
|
||||
model_name=getattr(settings, 'REMBG_MODEL_NAME', 'birefnet-general-lite'),
|
||||
device="cuda" if settings.USE_CUDA else "cpu",
|
||||
fp16=settings.USE_FP16,
|
||||
local_rembg_model_path=getattr(settings, 'LOCAL_REMBG_MODEL_PATH', None)
|
||||
)
|
||||
# 프리로드 강제: 실패 시 서버 기동 실패로 처리 (원인 파악을 위함)
|
||||
await model.load_model()
|
||||
logger.info("REMBG 모델 세션 로드 완료")
|
||||
return model
|
||||
except Exception as e:
|
||||
logger.error(f"REMBG 모델 로드 실패: {e}")
|
||||
raise
|
||||
|
||||
@asynccontextmanager
|
||||
async def get_session(self, model_type: ModelType):
|
||||
|
|
@ -240,5 +281,10 @@ class SessionPool:
|
|||
return status_by_model
|
||||
|
||||
|
||||
# 전역 세션 풀 인스턴스
|
||||
session_pool = SessionPool()
|
||||
# 전역 세션 풀 인스턴스 (설정값으로 초기화)
|
||||
from ..core.config import settings
|
||||
session_pool = SessionPool(
|
||||
simple_lama_count=settings.SIMPLE_LAMA_SESSIONS,
|
||||
migan_count=settings.MIGAN_SESSIONS,
|
||||
rembg_count=settings.REMBG_SESSIONS
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import asyncio
|
|||
import logging
|
||||
import time
|
||||
import uuid
|
||||
from typing import Dict, List, Optional, Callable, Any
|
||||
from typing import Dict, List, Optional, Callable, Any, Tuple
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
|
|
@ -330,49 +330,50 @@ class WorkerManager:
|
|||
async def process_inpaint(self, **kwargs) -> Optional[np.ndarray]:
|
||||
"""인페인팅 작업을 처리합니다."""
|
||||
try:
|
||||
# 간단한 시뮬레이션 (실제로는 세션 풀에서 모델을 가져와 처리)
|
||||
from ..models.simple_lama import SimpleLamaInpainter
|
||||
from ..models.migan import MiganInpainter
|
||||
from ..core.session_pool import session_pool, ModelType
|
||||
|
||||
model_name = kwargs.get('model_name', 'simple-lama')
|
||||
|
||||
# 모델명에 따라 세션 타입 결정
|
||||
if model_name == 'simple-lama':
|
||||
model = SimpleLamaInpainter()
|
||||
model_type = ModelType.SIMPLE_LAMA
|
||||
elif model_name == 'migan':
|
||||
model = MiganInpainter()
|
||||
model_type = ModelType.MIGAN
|
||||
else:
|
||||
model = SimpleLamaInpainter() # 기본값
|
||||
model_type = ModelType.SIMPLE_LAMA # 기본값
|
||||
|
||||
# 모델 처리 (실제로는 세션 풀에서 가져온 모델 사용)
|
||||
result = await model.inpaint(
|
||||
image=kwargs['image'],
|
||||
mask=kwargs['mask']
|
||||
)
|
||||
# 세션 풀에서 모델 세션 가져와서 처리
|
||||
async with session_pool.get_session(model_type) as session:
|
||||
# session.model 에서 실제 모델 객체의 메서드를 호출해야 함
|
||||
result = await session.model.inpaint(
|
||||
image=kwargs['image'],
|
||||
mask=kwargs['mask']
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"인페인팅 처리 실패: {e}")
|
||||
logger.error(f"인페인팅 처리 실패: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
async def process_remove_bg(self, **kwargs) -> Optional[np.ndarray]:
|
||||
async def process_remove_bg(self, **kwargs) -> Optional[Tuple[np.ndarray, np.ndarray]]:
|
||||
"""배경 제거 작업을 처리합니다."""
|
||||
try:
|
||||
# 간단한 시뮬레이션 (실제로는 세션 풀에서 모델을 가져와 처리)
|
||||
from ..models.rembg_model import RembgProcessor
|
||||
from ..core.session_pool import session_pool, ModelType
|
||||
|
||||
model = RembgProcessor()
|
||||
|
||||
# 모델 처리 (실제로는 세션 풀에서 가져온 모델 사용)
|
||||
result = await model.remove_background(
|
||||
image=kwargs['image']
|
||||
)
|
||||
# 세션 풀에서 REMBG 모델 세션 가져와서 처리
|
||||
async with session_pool.get_session(ModelType.REMBG) as session:
|
||||
# session.model 에서 실제 모델 객체의 메서드를 호출해야 함
|
||||
result = await session.model.remove_background(
|
||||
image=kwargs['image'],
|
||||
model_name=kwargs.get('model_name', 'u2net')
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"배경 제거 처리 실패: {e}")
|
||||
return None
|
||||
logger.error(f"배경 제거 처리 실패: {e}", exc_info=True)
|
||||
return None, None
|
||||
|
||||
|
||||
# 전역 워커 매니저 인스턴스
|
||||
|
|
|
|||
|
|
@ -1,190 +1,285 @@
|
|||
"""
|
||||
MIGAN 인페인팅 모델 구현
|
||||
MIGAN ONNX 인페인팅 모델 구현 (실제 ONNX 파이프라인 사용)
|
||||
"""
|
||||
import torch
|
||||
import numpy as np
|
||||
import cv2
|
||||
from PIL import Image
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
from typing import Union, Tuple
|
||||
import asyncio
|
||||
from typing import Optional, Union
|
||||
import cv2
|
||||
import numpy as np
|
||||
import onnxruntime as ort
|
||||
from PIL import Image
|
||||
|
||||
# OpenCV 내부 최적화 off
|
||||
cv2.setUseOptimized(False)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _np_uint8_2d(arr, name="mask"):
|
||||
if arr is None:
|
||||
raise ValueError(f"{name} is None")
|
||||
if not isinstance(arr, np.ndarray):
|
||||
raise TypeError(f"{name} must be np.ndarray, got {type(arr)}")
|
||||
if arr.ndim != 2:
|
||||
raise ValueError(f"{name} must be 2D, got shape={arr.shape}")
|
||||
if arr.dtype != np.uint8:
|
||||
# 안전 변환
|
||||
arr = arr.astype(np.uint8, copy=False)
|
||||
return arr
|
||||
|
||||
|
||||
class MiganInpainter:
|
||||
def __init__(self, model_path: str = None, device: str = "cuda", fp16: bool = True):
|
||||
"""
|
||||
MIGAN ONNX 파이프라인 래퍼
|
||||
- 입력: image_path(str), mask(gray uint8 HxW) ※ 텍스트영역=255
|
||||
- 내부에서 mask를 (이진화→반전)하여 MI-GAN 규칙(255=known, 0=hole)으로 맞춤
|
||||
- 출력: BGR uint8(H,W,3)
|
||||
"""
|
||||
def __init__(self,
|
||||
model_path: str = None,
|
||||
device: str = "cuda",
|
||||
fp16: bool = True,
|
||||
use_cuda: bool = False,
|
||||
intra_threads: int = 0,
|
||||
inter_threads: int = 0):
|
||||
self.model_path = model_path
|
||||
self.device = device
|
||||
self.fp16 = fp16
|
||||
self.model = None
|
||||
self.use_cuda = bool(use_cuda)
|
||||
self.intra_threads = int(intra_threads or 0)
|
||||
self.inter_threads = int(inter_threads or 0)
|
||||
self.loaded = False
|
||||
|
||||
if not model_path or not os.path.exists(model_path):
|
||||
logger.error(f"MIGAN ONNX 파일을 찾을 수 없습니다: {model_path}")
|
||||
raise FileNotFoundError(f"MIGAN ONNX 파일이 없습니다: {model_path}")
|
||||
|
||||
self.session = None
|
||||
self._session = None
|
||||
self.in_image = None
|
||||
self.in_mask = None
|
||||
self.out_name = None
|
||||
|
||||
async def _get_or_create_session(self):
|
||||
"""ONNX 런타임 세션을 생성하거나 기존 세션을 반환합니다."""
|
||||
if self._session is None:
|
||||
try:
|
||||
logger.info("MIGAN ONNX 런타임 세션 생성 시도...")
|
||||
import onnxruntime as ort
|
||||
|
||||
so = ort.SessionOptions()
|
||||
if self.intra_threads > 0:
|
||||
so.intra_op_num_threads = self.intra_threads
|
||||
if self.inter_threads > 0:
|
||||
so.inter_op_num_threads = self.inter_threads
|
||||
|
||||
providers = []
|
||||
if self.use_cuda:
|
||||
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||
logger.info(f"MIGAN ONNX providers 설정: {providers}")
|
||||
else:
|
||||
providers = ['CPUExecutionProvider']
|
||||
logger.info("MIGAN ONNX CPU-only mode 로 설정")
|
||||
|
||||
self._session = ort.InferenceSession(
|
||||
self.model_path,
|
||||
sess_options=so,
|
||||
providers=providers
|
||||
)
|
||||
logger.info(f"MIGAN ONNX 세션 생성 완료. Providers: {self._session.get_providers()}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"MIGAN ONNX 세션 초기화 실패: {e}", exc_info=True)
|
||||
if 'ort' in locals():
|
||||
logger.error(f"사용 가능한 providers: {ort.get_available_providers()}")
|
||||
raise RuntimeError(f"MIGAN 모델 초기화 실패: {e}")
|
||||
|
||||
return self._session
|
||||
|
||||
async def load_model(self):
|
||||
"""모델을 비동기적으로 로드합니다."""
|
||||
if self.loaded:
|
||||
return
|
||||
|
||||
try:
|
||||
logger.info("Loading MIGAN model...")
|
||||
logger.info("Loading MIGAN ONNX model...")
|
||||
|
||||
# 실제 구현에서는 MIGAN 모델을 로드
|
||||
# 여기서는 플레이스홀더로 구현
|
||||
await asyncio.sleep(0.1) # 모델 로딩 시뮬레이션
|
||||
self.session = await self._get_or_create_session()
|
||||
ins = self.session.get_inputs()
|
||||
outs = self.session.get_outputs()
|
||||
self.in_image = ins[0].name
|
||||
self.in_mask = ins[1].name
|
||||
self.out_name = outs[0].name
|
||||
|
||||
for i, inp in enumerate(ins):
|
||||
logger.debug(f"MIGAN 입력 {i}: {inp.name}, 형태: {inp.shape}, 타입: {inp.type}")
|
||||
for i, out in enumerate(outs):
|
||||
logger.debug(f"MIGAN 출력 {i}: {out.name}, 형태: {out.shape}, 타입: {out.type}")
|
||||
|
||||
logger.debug(f"MIGAN 세션 준비 완료. providers={self.session.get_providers()}")
|
||||
|
||||
# TODO: 실제 모델 로딩 로직
|
||||
# self.model = load_migan_model(self.model_path, device=self.device)
|
||||
|
||||
self.model = {"type": "migan", "device": self.device, "fp16": self.fp16}
|
||||
self.loaded = True
|
||||
|
||||
logger.info("MIGAN model loaded successfully")
|
||||
logger.info("MIGAN ONNX model loaded successfully")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load MIGAN model: {e}")
|
||||
logger.error(f"Failed to load MIGAN model: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
def preprocess_image(self, image: Union[Image.Image, np.ndarray]) -> torch.Tensor:
|
||||
"""이미지를 전처리합니다."""
|
||||
if isinstance(image, Image.Image):
|
||||
image = np.array(image)
|
||||
|
||||
# RGB로 변환
|
||||
if image.shape[2] == 4: # RGBA
|
||||
image = cv2.cvtColor(image, cv2.COLOR_RGBA2RGB)
|
||||
elif image.shape[2] == 3 and image.dtype == np.uint8:
|
||||
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||
|
||||
# 크기 조정 (MIGAN은 특정 크기를 선호할 수 있음)
|
||||
height, width = image.shape[:2]
|
||||
if height != 512 or width != 512:
|
||||
image = cv2.resize(image, (512, 512), interpolation=cv2.INTER_LANCZOS4)
|
||||
|
||||
# 정규화 (-1 to 1, MIGAN 스타일)
|
||||
image = image.astype(np.float32) / 127.5 - 1.0
|
||||
|
||||
# 텐서로 변환 (B, C, H, W)
|
||||
tensor = torch.from_numpy(image).permute(2, 0, 1).unsqueeze(0)
|
||||
|
||||
if self.fp16:
|
||||
tensor = tensor.half()
|
||||
|
||||
return tensor.to(self.device)
|
||||
|
||||
def preprocess_mask(self, mask: Union[Image.Image, np.ndarray]) -> torch.Tensor:
|
||||
"""마스크를 전처리합니다."""
|
||||
if isinstance(mask, Image.Image):
|
||||
mask = np.array(mask)
|
||||
|
||||
# 그레이스케일로 변환
|
||||
if len(mask.shape) == 3:
|
||||
mask = cv2.cvtColor(mask, cv2.COLOR_RGB2GRAY)
|
||||
|
||||
# 크기 조정
|
||||
if mask.shape[0] != 512 or mask.shape[1] != 512:
|
||||
mask = cv2.resize(mask, (512, 512), interpolation=cv2.INTER_NEAREST)
|
||||
|
||||
# 이진화 (0 또는 1)
|
||||
mask = (mask > 127).astype(np.float32)
|
||||
|
||||
# 텐서로 변환 (B, 1, H, W)
|
||||
tensor = torch.from_numpy(mask).unsqueeze(0).unsqueeze(0)
|
||||
|
||||
if self.fp16:
|
||||
tensor = tensor.half()
|
||||
|
||||
return tensor.to(self.device)
|
||||
|
||||
def postprocess_result(self, tensor: torch.Tensor, original_size: Tuple[int, int]) -> np.ndarray:
|
||||
"""결과를 후처리합니다."""
|
||||
# CPU로 이동하고 numpy로 변환
|
||||
if tensor.is_cuda:
|
||||
tensor = tensor.cpu()
|
||||
if tensor.dtype == torch.float16:
|
||||
tensor = tensor.float()
|
||||
|
||||
result = tensor.squeeze(0).permute(1, 2, 0).numpy()
|
||||
|
||||
# -1 to 1 범위에서 0-255로 변환
|
||||
result = ((result + 1.0) * 127.5).clip(0, 255).astype(np.uint8)
|
||||
|
||||
# 원본 크기로 복원
|
||||
if result.shape[:2] != original_size:
|
||||
result = cv2.resize(result, (original_size[1], original_size[0]),
|
||||
interpolation=cv2.INTER_LANCZOS4)
|
||||
|
||||
return result
|
||||
|
||||
async def inpaint(self, image: Union[Image.Image, np.ndarray],
|
||||
|
||||
async def inpaint(self, image: Union[str, Image.Image, np.ndarray],
|
||||
mask: Union[Image.Image, np.ndarray]) -> np.ndarray:
|
||||
"""인페인팅을 수행합니다."""
|
||||
"""
|
||||
인페인팅을 수행합니다.
|
||||
|
||||
Args:
|
||||
image: 원본 이미지 (파일 경로, PIL Image, 또는 numpy array)
|
||||
mask: 마스크 (PIL Image 또는 numpy array, 텍스트영역=255)
|
||||
|
||||
Returns:
|
||||
인페인팅된 이미지 (BGR numpy array)
|
||||
"""
|
||||
if not self.loaded:
|
||||
await self.load_model()
|
||||
|
||||
try:
|
||||
# 원본 크기 저장
|
||||
if isinstance(image, Image.Image):
|
||||
original_size = image.size[::-1] # (height, width)
|
||||
# 1) 입력 이미지 로드
|
||||
if isinstance(image, str):
|
||||
bgr = cv2.imread(image, cv2.IMREAD_COLOR)
|
||||
if bgr is None:
|
||||
logger.error(f"MIGAN 이미지 로드 실패: {image}")
|
||||
return None
|
||||
elif isinstance(image, Image.Image):
|
||||
bgr = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
|
||||
elif isinstance(image, np.ndarray):
|
||||
if image.shape[2] == 3:
|
||||
bgr = image.copy()
|
||||
else:
|
||||
logger.error(f"MIGAN 지원하지 않는 이미지 형태: {image.shape}")
|
||||
return None
|
||||
else:
|
||||
original_size = image.shape[:2]
|
||||
logger.error(f"MIGAN 지원하지 않는 이미지 타입: {type(image)}")
|
||||
return None
|
||||
|
||||
H, W = bgr.shape[:2]
|
||||
|
||||
# 2) 마스크 정규화: (이진화 → 반전) 해서 255=known, 0=hole 맞추기
|
||||
if isinstance(mask, Image.Image):
|
||||
mask_array = np.array(mask)
|
||||
else:
|
||||
mask_array = mask
|
||||
|
||||
# 전처리
|
||||
image_tensor = self.preprocess_image(image)
|
||||
mask_tensor = self.preprocess_mask(mask)
|
||||
mask_normalized = _np_uint8_2d(mask_array, name="mask")
|
||||
if mask_normalized.shape != (H, W):
|
||||
logger.error(f"MIGAN 마스크 크기 불일치: mask={mask_normalized.shape}, img={(H,W)}")
|
||||
return None
|
||||
|
||||
# 이진화: 128 threshold 기준
|
||||
_, mask_bin = cv2.threshold(mask_normalized, 128, 255, cv2.THRESH_BINARY)
|
||||
# 마스크 반전: 텍스트영역 255 -> 0 (hole), 배경 0 -> 255 (known)
|
||||
mask_known255 = 255 - mask_bin
|
||||
|
||||
# 3) RGB 변환 (파이프라인 입력은 RGB uint8)
|
||||
rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
|
||||
|
||||
# 4) ONNX 추론 - 배치 차원 추가 및 차원 순서 변경
|
||||
start = time.time()
|
||||
# ONNX 모델 입력 형태:
|
||||
# - image: (1, 3, H, W) - 배치, 채널, 높이, 너비 순서
|
||||
# - mask: (1, 1, H, W) - 배치, 채널(1), 높이, 너비 순서
|
||||
|
||||
# 추론 (실제 구현에서는 모델 추론)
|
||||
with torch.no_grad():
|
||||
# TODO: 실제 모델 추론 로직
|
||||
# result = self.model(image_tensor, mask_tensor)
|
||||
|
||||
# 플레이스홀더: 더 정교한 인페인팅 시뮬레이션
|
||||
result = await self._simulate_advanced_inpainting(image_tensor, mask_tensor)
|
||||
|
||||
# 후처리
|
||||
result_np = self.postprocess_result(result, original_size)
|
||||
# 이미지: (H, W, 3) -> (1, 3, H, W)
|
||||
rgb_batch = np.expand_dims(rgb, 0).transpose(0, 3, 1, 2)
|
||||
|
||||
return result_np
|
||||
# 마스크: (H, W) -> (1, 1, H, W)
|
||||
mask_batch = np.expand_dims(mask_known255, (0, 1))
|
||||
|
||||
logger.debug(f"MIGAN 입력 형태 - 이미지: {rgb_batch.shape}, 마스크: {mask_batch.shape}")
|
||||
|
||||
out = self.session.run(
|
||||
[self.out_name],
|
||||
{self.in_image: rgb_batch, self.in_mask: mask_batch}
|
||||
)[0] # expect RGB uint8(1,3,H,W)
|
||||
|
||||
# 출력 차원 처리: (1,3,H,W) -> (H,W,3)
|
||||
if out.ndim == 4 and out.shape[0] == 1:
|
||||
out = out[0].transpose(1, 2, 0) # (1,3,H,W) -> (3,H,W) -> (H,W,3)
|
||||
elif out.ndim == 3 and out.shape[0] == 3: # (3,H,W) -> (H,W,3)
|
||||
out = out.transpose(1, 2, 0)
|
||||
|
||||
logger.debug(f"MIGAN 출력 형태: {out.shape}, dtype: {out.dtype}")
|
||||
|
||||
if not isinstance(out, np.ndarray) or out.ndim != 3 or out.dtype != np.uint8:
|
||||
logger.error(f"MIGAN ONNX 출력 형식 오류: type={type(out)}, shape={getattr(out,'shape',None)}, dtype={getattr(out,'dtype',None)}")
|
||||
return None
|
||||
|
||||
elapsed = (time.time() - start) * 1000.0
|
||||
logger.debug(f"MIGAN 추론 완료: {elapsed:.2f} ms")
|
||||
|
||||
# 5) BGR로 되돌려 반환
|
||||
bgr_out = cv2.cvtColor(out, cv2.COLOR_RGB2BGR)
|
||||
return bgr_out
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"MIGAN inpainting failed: {e}")
|
||||
raise
|
||||
|
||||
async def _simulate_advanced_inpainting(self, image_tensor: torch.Tensor,
|
||||
mask_tensor: torch.Tensor) -> torch.Tensor:
|
||||
"""고급 인페인팅 시뮬레이션 (실제 구현에서는 제거)"""
|
||||
# 비동기 처리 시뮬레이션
|
||||
await asyncio.sleep(0.15) # MIGAN은 더 오래 걸린다고 가정
|
||||
|
||||
result = image_tensor.clone()
|
||||
mask_bool = mask_tensor.bool()
|
||||
|
||||
# 더 정교한 인페인팅 시뮬레이션: 주변 픽셀의 가중 평균
|
||||
if mask_bool.any():
|
||||
# 간단한 inpainting 시뮬레이션
|
||||
for c in range(3):
|
||||
channel = result[0, c]
|
||||
mask_2d = mask_bool[0, 0]
|
||||
|
||||
# 마스크 영역의 경계에서 값을 가져와서 보간
|
||||
kernel = torch.ones(3, 3, device=self.device) / 9.0
|
||||
if self.fp16:
|
||||
kernel = kernel.half()
|
||||
|
||||
# 간단한 convolution 기반 인페인팅
|
||||
padded_channel = torch.nn.functional.pad(channel.unsqueeze(0).unsqueeze(0), (1, 1, 1, 1), mode='replicate')
|
||||
smoothed = torch.nn.functional.conv2d(padded_channel, kernel.unsqueeze(0).unsqueeze(0), padding=0)
|
||||
|
||||
result[0, c][mask_2d] = smoothed[0, 0][mask_2d]
|
||||
|
||||
return result
|
||||
|
||||
error_msg = str(e).lower()
|
||||
if "invalid rank" in error_msg or "invalid argument" in error_msg:
|
||||
logger.error(f"MIGAN ONNX 입력 차원 오류: {e}")
|
||||
logger.error(f"MIGAN 입력 이미지 형태: {rgb_batch.shape if 'rgb_batch' in locals() else 'N/A'}")
|
||||
logger.error(f"MIGAN 입력 마스크 형태: {mask_batch.shape if 'mask_batch' in locals() else 'N/A'}")
|
||||
else:
|
||||
logger.error(f"MIGAN inpaint 예외: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
def get_model_info(self) -> dict:
|
||||
"""모델 정보를 반환합니다."""
|
||||
return {
|
||||
"model_type": "migan",
|
||||
"model_path": self.model_path,
|
||||
"device": self.device,
|
||||
"fp16": self.fp16,
|
||||
"use_cuda": self.use_cuda,
|
||||
"loaded": self.loaded,
|
||||
"model_path": self.model_path,
|
||||
"input_size": (512, 512)
|
||||
"providers": self.session.get_providers() if self.session else None
|
||||
}
|
||||
|
||||
|
||||
# 편의 함수: 설정으로부터 MIGAN 인스턴스 생성
|
||||
def build_migan_from_config(config: dict, logger: Optional[object] = None, gpu_manager: Optional[object] = None) -> MiganInpainter:
|
||||
"""
|
||||
설정으로부터 MiganInpainter 인스턴스를 생성.
|
||||
필수 키:
|
||||
- migan_onnx_path
|
||||
선택 키:
|
||||
- migan_use_cuda (bool)
|
||||
- migan_intra_threads (int)
|
||||
- migan_inter_threads (int)
|
||||
"""
|
||||
onnx_path = config.get("migan_onnx_path", "")
|
||||
if not onnx_path:
|
||||
raise ValueError("config['migan_onnx_path'] 가 필요합니다.")
|
||||
use_cuda = bool(config.get("migan_use_cuda", False))
|
||||
intra = int(config.get("migan_intra_threads", 0) or 0)
|
||||
inter = int(config.get("migan_inter_threads", 0) or 0)
|
||||
|
||||
inpainter = MiganInpainter(
|
||||
model_path=onnx_path,
|
||||
device="cuda" if use_cuda else "cpu",
|
||||
fp16=False, # ONNX에서는 fp16 사용하지 않음
|
||||
use_cuda=use_cuda,
|
||||
intra_threads=intra,
|
||||
inter_threads=inter,
|
||||
)
|
||||
|
||||
# GPU 관리자를 인페인터 객체에 연결
|
||||
if gpu_manager:
|
||||
inpainter.gpu_manager = gpu_manager
|
||||
if logger:
|
||||
logger.log(f"MIGAN GPU 관리자 연결 완료: {type(gpu_manager).__name__}", level=logging.DEBUG)
|
||||
else:
|
||||
if logger:
|
||||
logger.log(f"MIGAN GPU 관리자 없음: gpu_manager={gpu_manager}", level=logging.DEBUG)
|
||||
|
||||
# 디버깅: gpu_manager 속성 확인
|
||||
if logger:
|
||||
logger.log(f"MIGAN 인페인터 gpu_manager 속성: {hasattr(inpainter, 'gpu_manager')}, 값: {getattr(inpainter, 'gpu_manager', None)}", level=logging.DEBUG)
|
||||
|
||||
return inpainter
|
||||
|
|
@ -1,25 +1,139 @@
|
|||
"""
|
||||
REMBG 배경 제거 모델 구현
|
||||
REMBG 배경 제거 모델 구현 (실제 rembg 라이브러리 사용)
|
||||
"""
|
||||
import torch
|
||||
import numpy as np
|
||||
import os
|
||||
import cv2
|
||||
from PIL import Image
|
||||
import logging
|
||||
from typing import Union, Tuple
|
||||
import numpy as np
|
||||
import onnxruntime # ONNX 런타임 직접 사용을 위해 임포트
|
||||
from typing import Union, Tuple, Optional
|
||||
import asyncio
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RembgProcessor:
|
||||
def __init__(self, model_name: str = "u2net", device: str = "cuda", fp16: bool = True):
|
||||
"""
|
||||
rembg 기반 배경제거 모듈 (안전한 의존성 처리)
|
||||
"""
|
||||
|
||||
# 사용하시려는 birefnet 모델을 지원 목록에 추가합니다.
|
||||
SUPPORTED_MODELS = {
|
||||
"u2net": "범용 배경제거 | 빠름 | 사람/사물 모두 양호 (기본값)",
|
||||
"u2netp": "u2net 경량화 | 매우 빠름 | 실시간, 저사양PC",
|
||||
"u2net_human_seg": "인물 전용 | 빠름 | 사람 경계 정밀",
|
||||
"u2net_cloth_seg": "옷 전용 | 빠름 | 패션/의류 특화",
|
||||
"isnet-general-use": "범용 고품질 | 느림 | 디테일 중시, 대용량",
|
||||
"sam": "SAM 최고 품질 | 매우 느림 | 고성능PC 권장",
|
||||
"sam-mobile": "SAM 경량화 | 보통 | 모바일, 중간성능",
|
||||
"birefnet-general-lite": "BiRefNet 경량 모델 | 고품질 저용량 (로컬)"
|
||||
}
|
||||
|
||||
# SUPPORTED_MODELS 키와 실제 rembg sessions 키 간의 매핑
|
||||
MODEL_NAME_MAPPING = {
|
||||
"u2net": "u2net",
|
||||
"u2netp": "u2netp",
|
||||
"u2net_human_seg": "u2net-human-seg",
|
||||
"u2net_cloth_seg": "u2net-cloth-seg",
|
||||
"isnet-general-use": "dis-general-use",
|
||||
"sam": "sam",
|
||||
"sam-mobile": "sam", # sam-mobile은 sam과 동일하게 처리
|
||||
"birefnet-general-lite": "birefnet-general-lite"
|
||||
}
|
||||
|
||||
def __init__(self, model_name: str = "u2net", device: str = "cuda", fp16: bool = True,
|
||||
local_rembg_model_path: str = None):
|
||||
self.model_name = model_name
|
||||
self.device = device
|
||||
self.fp16 = fp16
|
||||
self.model = None
|
||||
self.local_rembg_model_path = local_rembg_model_path
|
||||
self.sessions = {}
|
||||
self.loaded = False
|
||||
self._rembg_available = None
|
||||
self._init_error = None
|
||||
self._cuda_providers_tested = False
|
||||
|
||||
def _check_rembg_availability(self):
|
||||
"""rembg 모듈 사용 가능 여부를 확인하고 캐시.
|
||||
세션을 생성하지 않아 모델 다운로드를 유발하지 않도록 함."""
|
||||
if self._rembg_available is not None:
|
||||
return self._rembg_available
|
||||
|
||||
try:
|
||||
import rembg # noqa: F401
|
||||
self._rembg_available = True
|
||||
logger.info("rembg 모듈 임포트 성공 (세션 생성은 지연 로딩)")
|
||||
return True
|
||||
except ImportError as e:
|
||||
self._init_error = f"rembg 모듈이 설치되지 않음: {e}"
|
||||
self._rembg_available = False
|
||||
except Exception as e:
|
||||
self._init_error = f"rembg 모듈 초기화 실패 (의존성/하드웨어 문제): {e}"
|
||||
self._rembg_available = False
|
||||
|
||||
logger.error(self._init_error)
|
||||
return False
|
||||
|
||||
def get_session(self, model_name, timeout_seconds: int = 90):
|
||||
"""
|
||||
모델별 세션을 캐싱하여 반환 (로컬 모델 경로 및 CUDA 지원 포함)
|
||||
"""
|
||||
if not self._check_rembg_availability():
|
||||
logger.error(f"rembg 사용 불가로 세션 생성 실패: {self._init_error}")
|
||||
return None
|
||||
|
||||
# device 설정에 따라 CUDA 사용 여부 결정 (간소화)
|
||||
cuda_enabled = self.device == "cuda"
|
||||
# 실제 모델명을 세션 키에 사용
|
||||
actual_model_name = self.MODEL_NAME_MAPPING.get(model_name, model_name)
|
||||
session_key = f"{actual_model_name}_cuda_{cuda_enabled}"
|
||||
|
||||
if session_key not in self.sessions:
|
||||
logger.info(f"🔧 rembg 새 세션 생성 필요: {session_key}")
|
||||
try:
|
||||
import rembg
|
||||
try:
|
||||
from rembg.sessions import sessions
|
||||
except ImportError:
|
||||
# rembg 버전에 따라 import 경로가 다를 수 있음
|
||||
sessions = None
|
||||
logger.warning("rembg.sessions import 실패, 기본 방식 사용")
|
||||
|
||||
# Jetson 환경에서 TensorRT 충돌을 피하기 위해 프로바이더 명시
|
||||
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||
logger.info(f"rembg 세션 생성 providers: {providers}")
|
||||
|
||||
session = rembg.new_session(
|
||||
model_name=actual_model_name,
|
||||
providers=providers
|
||||
)
|
||||
|
||||
self.sessions[session_key] = session
|
||||
|
||||
# 실제 사용된 provider 확인 및 로깅 (가드 처리)
|
||||
actual_providers = []
|
||||
try:
|
||||
inner = getattr(session, 'inner_session', None)
|
||||
if inner and hasattr(inner, 'get_providers'):
|
||||
actual_providers = inner.get_providers() or []
|
||||
except Exception as prov_err:
|
||||
logger.debug(f"rembg provider 확인 실패: {prov_err}")
|
||||
|
||||
is_gpu = any(('CUDA' in p) or ('Tensorrt' in p) for p in actual_providers)
|
||||
status = "GPU 가속" if is_gpu else "CPU 모드"
|
||||
logger.info(
|
||||
f"✅ rembg '{actual_model_name}' {status}로 동작 (providers: {actual_providers or '알 수 없음'})"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"rembg 세션 생성 실패 ('{actual_model_name}'): {e}", exc_info=True)
|
||||
return None
|
||||
else:
|
||||
logger.debug(f"♻️ rembg 기존 세션 재사용: {session_key}")
|
||||
|
||||
return self.sessions.get(session_key)
|
||||
|
||||
async def load_model(self):
|
||||
"""모델을 비동기적으로 로드합니다."""
|
||||
if self.loaded:
|
||||
|
|
@ -28,141 +142,141 @@ class RembgProcessor:
|
|||
try:
|
||||
logger.info(f"Loading REMBG model ({self.model_name})...")
|
||||
|
||||
# 실제 구현에서는 rembg 라이브러리를 사용
|
||||
# 여기서는 플레이스홀더로 구현
|
||||
await asyncio.sleep(0.1) # 모델 로딩 시뮬레이션
|
||||
# rembg 사용 가능성 확인
|
||||
if not self._check_rembg_availability():
|
||||
raise RuntimeError(f"REMBG 사용 불가: {self._init_error}")
|
||||
|
||||
# TODO: 실제 모델 로딩 로직
|
||||
# from rembg import new_session
|
||||
# self.model = new_session(self.model_name)
|
||||
# 세션 생성
|
||||
session = self.get_session(self.model_name)
|
||||
if session is None:
|
||||
raise RuntimeError(f"REMBG 세션 생성 실패: {self.model_name}")
|
||||
|
||||
self.model = {
|
||||
"type": "rembg",
|
||||
"model_name": self.model_name,
|
||||
"device": self.device,
|
||||
"fp16": self.fp16
|
||||
}
|
||||
self.loaded = True
|
||||
|
||||
logger.info(f"REMBG model ({self.model_name}) loaded successfully")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load REMBG model: {e}")
|
||||
raise
|
||||
|
||||
def preprocess_image(self, image: Union[Image.Image, np.ndarray]) -> np.ndarray:
|
||||
"""이미지를 전처리합니다."""
|
||||
if isinstance(image, Image.Image):
|
||||
image = np.array(image)
|
||||
|
||||
# RGB로 변환
|
||||
if image.shape[2] == 4: # RGBA
|
||||
image = cv2.cvtColor(image, cv2.COLOR_RGBA2RGB)
|
||||
elif len(image.shape) == 3 and image.shape[2] == 3:
|
||||
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||
|
||||
return image
|
||||
|
||||
def to_white_background(self, img: Image.Image) -> Image.Image:
|
||||
"""RGBA 이미지를 흰 배경으로 변환"""
|
||||
if img.mode in ("RGBA", "BGRA"):
|
||||
bg = Image.new("RGB", img.size, (255, 255, 255))
|
||||
bg.paste(img, mask=img.split()[-1])
|
||||
return bg
|
||||
else:
|
||||
return img.convert("RGB")
|
||||
|
||||
async def remove_background(self, image: Union[str, Image.Image, np.ndarray],
|
||||
model_name: str = None, **kwargs) -> Tuple[np.ndarray, np.ndarray]:
|
||||
"""
|
||||
배경을 제거하고 결과 이미지와 마스크를 반환합니다.
|
||||
|
||||
def create_mask_from_alpha(self, rgba_image: np.ndarray) -> np.ndarray:
|
||||
"""RGBA 이미지에서 알파 채널을 마스크로 변환합니다."""
|
||||
if rgba_image.shape[2] != 4:
|
||||
raise ValueError("Input image must have 4 channels (RGBA)")
|
||||
Args:
|
||||
image: 입력 이미지 (파일 경로, PIL Image, 또는 numpy array)
|
||||
model_name: 사용할 모델명 (없으면 기본 모델 사용)
|
||||
**kwargs: 추가 옵션 (alpha_matting 등)
|
||||
|
||||
# 알파 채널을 마스크로 사용
|
||||
alpha_channel = rgba_image[:, :, 3]
|
||||
|
||||
# 0-255 범위의 마스크 생성
|
||||
mask = alpha_channel.astype(np.uint8)
|
||||
|
||||
return mask
|
||||
|
||||
async def remove_background(self, image: Union[Image.Image, np.ndarray]) -> Tuple[np.ndarray, np.ndarray]:
|
||||
"""배경을 제거하고 결과 이미지와 마스크를 반환합니다."""
|
||||
Returns:
|
||||
(result_rgb, mask): 결과 이미지(RGB)와 마스크
|
||||
"""
|
||||
if not self.loaded:
|
||||
await self.load_model()
|
||||
|
||||
try:
|
||||
# 전처리
|
||||
processed_image = self.preprocess_image(image)
|
||||
original_shape = processed_image.shape
|
||||
# 이미지 로드 및 변환
|
||||
if isinstance(image, str):
|
||||
if not os.path.exists(image):
|
||||
logger.error(f"입력 이미지가 존재하지 않습니다: {image}")
|
||||
return None, None
|
||||
img = cv2.imread(image)
|
||||
if img is None:
|
||||
logger.error(f"이미지 로드 실패: {image}")
|
||||
return None, None
|
||||
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
||||
elif isinstance(image, Image.Image):
|
||||
img_rgb = np.array(image.convert('RGB'))
|
||||
elif isinstance(image, np.ndarray):
|
||||
if len(image.shape) == 3 and image.shape[2] == 3:
|
||||
# BGR to RGB
|
||||
img_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||
else:
|
||||
img_rgb = image
|
||||
else:
|
||||
logger.error(f"지원하지 않는 이미지 타입: {type(image)}")
|
||||
return None, None
|
||||
|
||||
# 사용할 모델명 결정
|
||||
effective_model_name = model_name or self.model_name
|
||||
|
||||
# 배경 제거 (실제 구현에서는 rembg 사용)
|
||||
# TODO: 실제 모델 추론 로직
|
||||
# from rembg import remove
|
||||
# result_rgba = remove(self.model, processed_image)
|
||||
if effective_model_name not in self.SUPPORTED_MODELS:
|
||||
logger.warning(f"지원하지 않는 모델명: {effective_model_name}. u2net으로 대체 사용")
|
||||
effective_model_name = "u2net"
|
||||
|
||||
session = self.get_session(effective_model_name)
|
||||
if session is None:
|
||||
return None, None
|
||||
|
||||
import rembg
|
||||
import time
|
||||
|
||||
# 플레이스홀더: 배경 제거 시뮬레이션
|
||||
result_rgba = await self._simulate_background_removal(processed_image)
|
||||
start_time = time.time()
|
||||
result = rembg.remove(img_rgb, session=session, alpha_matting=kwargs.get("alpha_matting", False))
|
||||
end_time = time.time()
|
||||
|
||||
# 결과에서 RGB 이미지와 마스크 분리
|
||||
result_rgb = result_rgba[:, :, :3]
|
||||
mask = self.create_mask_from_alpha(result_rgba)
|
||||
if not isinstance(result, Image.Image):
|
||||
result = Image.fromarray(result)
|
||||
|
||||
# RGBA 이미지에서 RGB와 마스크 분리
|
||||
result_rgba = np.array(result)
|
||||
if result_rgba.shape[2] == 4:
|
||||
result_rgb = result_rgba[:, :, :3]
|
||||
mask = result_rgba[:, :, 3]
|
||||
else:
|
||||
result_rgb = result_rgba
|
||||
# 간단한 마스크 생성 (배경이 검은색이라고 가정)
|
||||
gray = cv2.cvtColor(result_rgb, cv2.COLOR_RGB2GRAY)
|
||||
mask = (gray > 10).astype(np.uint8) * 255
|
||||
|
||||
processing_time = end_time - start_time
|
||||
# provider 기반 상태 로깅 (세션에서 확인 시도)
|
||||
try:
|
||||
sess = self.sessions.get(f"{self.MODEL_NAME_MAPPING.get(effective_model_name, effective_model_name)}_cuda_{self.device == 'cuda'}")
|
||||
providers = []
|
||||
if sess and getattr(sess, 'inner_session', None) and hasattr(sess.inner_session, 'get_providers'):
|
||||
providers = sess.inner_session.get_providers() or []
|
||||
cuda_status = "CUDA" if any('CUDA' in p or 'Tensorrt' in p for p in providers) else "CPU"
|
||||
except Exception:
|
||||
cuda_status = "알 수 없음"
|
||||
logger.info(f"✅ 배경 제거 성공: {effective_model_name} ({cuda_status}, {processing_time:.2f}초)")
|
||||
|
||||
return result_rgb, mask
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Background removal failed: {e}")
|
||||
raise
|
||||
|
||||
async def _simulate_background_removal(self, image: np.ndarray) -> np.ndarray:
|
||||
"""배경 제거 시뮬레이션 (실제 구현에서는 제거)"""
|
||||
# 비동기 처리 시뮬레이션
|
||||
await asyncio.sleep(0.08) # REMBG는 상대적으로 빠르다고 가정
|
||||
|
||||
height, width = image.shape[:2]
|
||||
|
||||
# 간단한 전경/배경 분리 시뮬레이션
|
||||
# 중앙 영역을 전경으로, 가장자리를 배경으로 가정
|
||||
center_x, center_y = width // 2, height // 2
|
||||
|
||||
# 타원형 마스크 생성
|
||||
y, x = np.ogrid[:height, :width]
|
||||
mask = ((x - center_x) ** 2 / (width * 0.3) ** 2 +
|
||||
(y - center_y) ** 2 / (height * 0.4) ** 2) <= 1
|
||||
|
||||
# 부드러운 가장자리를 위한 가우시안 블러
|
||||
mask_float = mask.astype(np.float32)
|
||||
mask_blurred = cv2.GaussianBlur(mask_float, (51, 51), 20)
|
||||
|
||||
# RGBA 이미지 생성
|
||||
result_rgba = np.zeros((height, width, 4), dtype=np.uint8)
|
||||
result_rgba[:, :, :3] = image # RGB 채널
|
||||
result_rgba[:, :, 3] = (mask_blurred * 255).astype(np.uint8) # 알파 채널
|
||||
|
||||
return result_rgba
|
||||
|
||||
async def apply_new_background(self, foreground: np.ndarray, mask: np.ndarray,
|
||||
background: Union[np.ndarray, tuple]) -> np.ndarray:
|
||||
"""새로운 배경을 적용합니다."""
|
||||
try:
|
||||
height, width = foreground.shape[:2]
|
||||
|
||||
# 배경 준비
|
||||
if isinstance(background, tuple):
|
||||
# 단색 배경
|
||||
bg = np.full((height, width, 3), background, dtype=np.uint8)
|
||||
else:
|
||||
# 이미지 배경
|
||||
if isinstance(background, Image.Image):
|
||||
background = np.array(background)
|
||||
bg = cv2.resize(background, (width, height))
|
||||
if len(bg.shape) == 3 and bg.shape[2] == 4:
|
||||
bg = bg[:, :, :3] # RGBA에서 RGB로
|
||||
|
||||
# 마스크를 0-1 범위로 정규화
|
||||
mask_norm = mask.astype(np.float32) / 255.0
|
||||
mask_3ch = np.stack([mask_norm] * 3, axis=-1)
|
||||
|
||||
# 알파 블렌딩
|
||||
result = (foreground.astype(np.float32) * mask_3ch +
|
||||
bg.astype(np.float32) * (1 - mask_3ch))
|
||||
|
||||
return result.astype(np.uint8)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Background application failed: {e}")
|
||||
raise
|
||||
|
||||
logger.error(f"배경 제거 처리 중 오류 ({model_name}): {e}", exc_info=True)
|
||||
return None, None
|
||||
|
||||
def set_default_model(self, model_name):
|
||||
if model_name not in self.SUPPORTED_MODELS:
|
||||
raise ValueError(f"지원하지 않는 모델명: {model_name}")
|
||||
self.model_name = model_name
|
||||
logger.info(f"rembg 기본 모델이 '{model_name}'(으)로 변경됨")
|
||||
|
||||
def get_default_model(self):
|
||||
return self.model_name
|
||||
|
||||
def get_supported_models(self):
|
||||
return self.SUPPORTED_MODELS.copy()
|
||||
|
||||
def get_model_description(self, model_name):
|
||||
return self.SUPPORTED_MODELS.get(model_name, "모델 설명 없음")
|
||||
|
||||
def is_available(self):
|
||||
return self._check_rembg_availability()
|
||||
|
||||
def get_init_error(self):
|
||||
return self._init_error
|
||||
|
||||
def get_model_info(self) -> dict:
|
||||
"""모델 정보를 반환합니다."""
|
||||
return {
|
||||
|
|
@ -170,5 +284,8 @@ class RembgProcessor:
|
|||
"model_name": self.model_name,
|
||||
"device": self.device,
|
||||
"fp16": self.fp16,
|
||||
"loaded": self.loaded
|
||||
}
|
||||
"loaded": self.loaded,
|
||||
"available": self.is_available(),
|
||||
"supported_models": list(self.SUPPORTED_MODELS.keys()),
|
||||
"local_model_path": self.local_rembg_model_path
|
||||
}
|
||||
|
|
@ -29,17 +29,22 @@ class SimpleLamaInpainter:
|
|||
try:
|
||||
logger.info("Loading Simple LAMA model...")
|
||||
|
||||
# 실제 구현에서는 simple-lama-inpainting 라이브러리를 사용
|
||||
# 여기서는 플레이스홀더로 구현
|
||||
await asyncio.sleep(0.1) # 모델 로딩 시뮬레이션
|
||||
# 실제 simple-lama-inpainting 라이브러리 사용
|
||||
try:
|
||||
from simple_lama_inpainting import SimpleLama
|
||||
self.model = SimpleLama(device=self.device)
|
||||
logger.info("실제 SimpleLama 모델 로딩 완료")
|
||||
except ImportError as e:
|
||||
logger.warning(f"SimpleLama 라이브러리 import 실패: {e}")
|
||||
logger.info("fallback 모드로 전환합니다...")
|
||||
# fallback으로 시뮬레이션 모드 사용
|
||||
self.model = {"type": "simple_lama_fallback", "device": self.device, "fp16": self.fp16}
|
||||
except Exception as e:
|
||||
logger.error(f"SimpleLama 모델 초기화 실패: {e}")
|
||||
logger.info("fallback 모드로 전환합니다...")
|
||||
self.model = {"type": "simple_lama_fallback", "device": self.device, "fp16": self.fp16}
|
||||
|
||||
# TODO: 실제 모델 로딩 로직
|
||||
# from simple_lama_inpainting import SimpleLama
|
||||
# self.model = SimpleLama(device=self.device)
|
||||
|
||||
self.model = {"type": "simple_lama", "device": self.device, "fp16": self.fp16}
|
||||
self.loaded = True
|
||||
|
||||
logger.info("Simple LAMA model loaded successfully")
|
||||
|
||||
except Exception as e:
|
||||
|
|
@ -114,18 +119,34 @@ class SimpleLamaInpainter:
|
|||
image_tensor = self.preprocess_image(image)
|
||||
mask_tensor = self.preprocess_mask(mask)
|
||||
|
||||
# 추론 (실제 구현에서는 모델 추론)
|
||||
# 실제 모델 추론
|
||||
with torch.no_grad():
|
||||
# TODO: 실제 모델 추론 로직
|
||||
# result = self.model(image_tensor, mask_tensor)
|
||||
|
||||
# 플레이스홀더: 마스크 영역을 평균 색상으로 채우기
|
||||
result = await self._simulate_inpainting(image_tensor, mask_tensor)
|
||||
|
||||
# 후처리
|
||||
result_np = self.postprocess_result(result)
|
||||
|
||||
return result_np
|
||||
if hasattr(self.model, '__call__') and not isinstance(self.model, dict):
|
||||
# 실제 SimpleLama 모델 사용
|
||||
logger.info("실제 SimpleLama 모델로 인페인팅 수행")
|
||||
|
||||
# SimpleLama는 PIL Image를 받으므로 변환
|
||||
if isinstance(image, np.ndarray):
|
||||
pil_image = Image.fromarray(image)
|
||||
else:
|
||||
pil_image = image
|
||||
|
||||
if isinstance(mask, np.ndarray):
|
||||
pil_mask = Image.fromarray(mask)
|
||||
else:
|
||||
pil_mask = mask
|
||||
|
||||
# 실제 추론 수행
|
||||
result_pil = self.model(pil_image, pil_mask)
|
||||
result_np = np.array(result_pil)
|
||||
|
||||
return result_np
|
||||
else:
|
||||
# Fallback: 시뮬레이션 모드
|
||||
logger.warning("Fallback 모드: 시뮬레이션 인페인팅 사용")
|
||||
result = await self._simulate_inpainting(image_tensor, mask_tensor)
|
||||
result_np = self.postprocess_result(result)
|
||||
return result_np
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Inpainting failed: {e}")
|
||||
|
|
|
|||
|
|
@ -109,28 +109,21 @@ class MonitoringData:
|
|||
logger.info("워커 상태가 비어있어 기본값 사용")
|
||||
worker_status = self._get_default_worker_status()
|
||||
|
||||
# 실제 세션 풀 상태 가져오기 (status.json보다 우선)
|
||||
# 항상 실제 세션 풀 상태 사용 (동적 데이터)
|
||||
try:
|
||||
logger.info("실제 세션 풀 상태 수집 시작")
|
||||
real_session_status = session_pool.get_status()
|
||||
session_status = real_session_status
|
||||
logger.debug(f"실시간 세션 풀 상태 사용: {real_session_status}")
|
||||
|
||||
# 세션 풀이 초기화되었는지 확인 (모든 total이 0이 아닌지 체크)
|
||||
total_sessions = sum(pool_info.get("total", 0) for pool_info in real_session_status.values())
|
||||
|
||||
if real_session_status and total_sessions > 0:
|
||||
session_status = real_session_status
|
||||
logger.info(f"실제 세션 풀 상태 사용: {real_session_status}")
|
||||
else:
|
||||
logger.info(f"세션 풀이 아직 초기화되지 않음 (총 세션: {total_sessions}), status.json 사용")
|
||||
# status.json에서 가져온 값을 그대로 사용
|
||||
if not session_status:
|
||||
logger.info("세션 상태가 비어있어 기본값 사용")
|
||||
session_status = self._get_default_session_status()
|
||||
except Exception as e:
|
||||
logger.warning(f"실제 세션 풀 상태 조회 실패: {e}")
|
||||
if not session_status:
|
||||
logger.info("세션 상태가 비어있어 기본값 사용")
|
||||
session_status = self._get_default_session_status()
|
||||
except Exception as e:
|
||||
logger.warning(f"실제 세션 풀 상태 조회 실패: {e}")
|
||||
session_status = status.get("session_status", {})
|
||||
if not session_status:
|
||||
logger.info("status.json 세션 상태도 비어있어 기본값 사용")
|
||||
session_status = self._get_default_session_status()
|
||||
|
||||
# GPU 정보 (안전하게 가져오기)
|
||||
gpu_info = {}
|
||||
|
|
@ -895,6 +888,44 @@ HTML_TEMPLATE = """
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 로그 뷰어 -->
|
||||
<div class="card">
|
||||
<h3>📝 최근 로그 (최근 50줄)</h3>
|
||||
<div style="background: #f8f9fa; border-radius: 5px; padding: 15px; height: 300px; overflow-y: auto; font-family: 'Courier New', monospace; font-size: 12px;" id="logs-container">
|
||||
로딩 중...
|
||||
</div>
|
||||
<div style="margin-top: 10px; text-align: right;">
|
||||
<button onclick="refreshLogs()" style="padding: 5px 15px; background: #667eea; color: white; border: none; border-radius: 3px; cursor: pointer;">새로고침</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 성능 통계 -->
|
||||
<div class="card">
|
||||
<h3>⚡ 모델 로딩 성능 통계</h3>
|
||||
<div id="performance-stats-container">
|
||||
로딩 중...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 모델 사용 통계 -->
|
||||
<div class="card">
|
||||
<h3>📊 모델별 사용 통계</h3>
|
||||
<div id="model-usage-stats-container">
|
||||
로딩 중...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 시스템 경고 -->
|
||||
<div class="card">
|
||||
<h3>🚨 실시간 시스템 경고</h3>
|
||||
<div id="system-alerts-container">
|
||||
로딩 중...
|
||||
</div>
|
||||
<div style="margin-top: 10px; text-align: right;">
|
||||
<button onclick="refreshSystemAlerts()" style="padding: 5px 15px; background: #dc3545; color: white; border: none; border-radius: 3px; cursor: pointer;">경고 새로고침</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 차트 -->
|
||||
<div class="chart-container">
|
||||
<h3>📈 실시간 성능 차트</h3>
|
||||
|
|
@ -1291,6 +1322,203 @@ HTML_TEMPLATE = """
|
|||
performanceChart.update();
|
||||
gpuChart.update();
|
||||
}
|
||||
|
||||
// 로그 새로고침 함수
|
||||
function refreshLogs() {
|
||||
fetch('/api/logs?lines=50')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const container = document.getElementById('logs-container');
|
||||
if (data.logs && data.logs.length > 0) {
|
||||
let logHtml = '';
|
||||
data.logs.forEach(log => {
|
||||
if (log.raw) {
|
||||
logHtml += `<div style="margin-bottom: 3px; color: #666;">${escapeHtml(log.raw)}</div>`;
|
||||
} else {
|
||||
const levelColor = getLevelColor(log.level);
|
||||
logHtml += `<div style="margin-bottom: 3px;">
|
||||
<span style="color: #888; font-size: 11px;">${log.timestamp}</span>
|
||||
<span style="color: ${levelColor}; font-weight: bold; margin: 0 5px;">[${log.level}]</span>
|
||||
<span style="color: #666; margin-right: 5px;">${log.module}:</span>
|
||||
<span>${escapeHtml(log.message)}</span>
|
||||
</div>`;
|
||||
}
|
||||
});
|
||||
container.innerHTML = logHtml;
|
||||
container.scrollTop = container.scrollHeight; // 스크롤을 맨 아래로
|
||||
} else {
|
||||
container.innerHTML = '<div style="color: #999;">로그가 없습니다.</div>';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('로그 로딩 실패:', error);
|
||||
document.getElementById('logs-container').innerHTML = '<div style="color: #dc3545;">로그 로딩 실패</div>';
|
||||
});
|
||||
}
|
||||
|
||||
// 성능 통계 새로고침 함수
|
||||
function refreshPerformanceStats() {
|
||||
fetch('/api/performance-stats')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const container = document.getElementById('performance-stats-container');
|
||||
if (data.stats && Object.keys(data.stats).length > 0) {
|
||||
let statsHtml = '<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 15px;">';
|
||||
|
||||
Object.entries(data.stats).forEach(([modelName, stats]) => {
|
||||
statsHtml += `<div style="background: #f8f9fa; padding: 15px; border-radius: 5px;">
|
||||
<h4 style="margin-top: 0; color: #667eea;">${modelName}</h4>
|
||||
<div class="metric">
|
||||
<span class="metric-label">평균 로딩 시간:</span>
|
||||
<span class="metric-value">${stats.avg_ms.toFixed(1)}ms</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">최소/최대:</span>
|
||||
<span class="metric-value">${stats.min_ms.toFixed(1)}ms / ${stats.max_ms.toFixed(1)}ms</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="metric-label">총 로딩 횟수:</span>
|
||||
<span class="metric-value">${stats.count}회</span>
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
statsHtml += '</div>';
|
||||
container.innerHTML = statsHtml;
|
||||
} else {
|
||||
container.innerHTML = '<div style="color: #999; text-align: center; padding: 20px;">성능 데이터가 없습니다.</div>';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('성능 통계 로딩 실패:', error);
|
||||
document.getElementById('performance-stats-container').innerHTML = '<div style="color: #dc3545;">성능 통계 로딩 실패</div>';
|
||||
});
|
||||
}
|
||||
|
||||
// 헬퍼 함수들
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function getLevelColor(level) {
|
||||
switch(level) {
|
||||
case 'ERROR': return '#dc3545';
|
||||
case 'WARNING': return '#ffc107';
|
||||
case 'INFO': return '#17a2b8';
|
||||
case 'DEBUG': return '#6c757d';
|
||||
default: return '#333';
|
||||
}
|
||||
}
|
||||
|
||||
// 모델 사용 통계 새로고침 함수
|
||||
function refreshModelUsageStats() {
|
||||
fetch('/api/model-usage-stats')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const container = document.getElementById('model-usage-stats-container');
|
||||
if (data.model_usage && Object.keys(data.model_usage).length > 0) {
|
||||
let statsHtml = '<div class="grid" style="grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px;">';
|
||||
|
||||
// 모델별 사용량
|
||||
statsHtml += '<div style="background: #e3f2fd; padding: 15px; border-radius: 5px;">';
|
||||
statsHtml += '<h4 style="margin-top: 0; color: #1976d2;">🎯 모델별 요청</h4>';
|
||||
Object.entries(data.model_usage).forEach(([model, count]) => {
|
||||
statsHtml += `<div class="metric">
|
||||
<span class="metric-label">${model}:</span>
|
||||
<span class="metric-value">${count}회</span>
|
||||
</div>`;
|
||||
});
|
||||
statsHtml += '</div>';
|
||||
|
||||
// 엔드포인트별 사용량
|
||||
if (data.endpoint_usage && Object.keys(data.endpoint_usage).length > 0) {
|
||||
statsHtml += '<div style="background: #f3e5f5; padding: 15px; border-radius: 5px;">';
|
||||
statsHtml += '<h4 style="margin-top: 0; color: #7b1fa2;">🔗 엔드포인트별</h4>';
|
||||
Object.entries(data.endpoint_usage).forEach(([endpoint, count]) => {
|
||||
statsHtml += `<div class="metric">
|
||||
<span class="metric-label">${endpoint}:</span>
|
||||
<span class="metric-value">${count}회</span>
|
||||
</div>`;
|
||||
});
|
||||
statsHtml += '</div>';
|
||||
}
|
||||
|
||||
// 총 요청 수
|
||||
statsHtml += '<div style="background: #e8f5e8; padding: 15px; border-radius: 5px;">';
|
||||
statsHtml += '<h4 style="margin-top: 0; color: #388e3c;">📈 총계</h4>';
|
||||
statsHtml += `<div class="metric">
|
||||
<span class="metric-label">총 요청:</span>
|
||||
<span class="metric-value">${data.total_requests}회</span>
|
||||
</div>`;
|
||||
statsHtml += `<div class="metric">
|
||||
<span class="metric-label">분석 범위:</span>
|
||||
<span class="metric-value">${data.analysis_window}</span>
|
||||
</div>`;
|
||||
statsHtml += '</div>';
|
||||
|
||||
statsHtml += '</div>';
|
||||
container.innerHTML = statsHtml;
|
||||
} else {
|
||||
container.innerHTML = '<div style="color: #999; text-align: center; padding: 20px;">사용 데이터가 없습니다.</div>';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('모델 사용 통계 로딩 실패:', error);
|
||||
document.getElementById('model-usage-stats-container').innerHTML = '<div style="color: #dc3545;">통계 로딩 실패</div>';
|
||||
});
|
||||
}
|
||||
|
||||
// 시스템 경고 새로고침 함수
|
||||
function refreshSystemAlerts() {
|
||||
fetch('/api/system-alerts')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const container = document.getElementById('system-alerts-container');
|
||||
if (data.alerts && data.alerts.length > 0) {
|
||||
let alertsHtml = '';
|
||||
data.alerts.forEach(alert => {
|
||||
const levelColor = alert.level === 'critical' ? '#dc3545' :
|
||||
alert.level === 'warning' ? '#ffc107' : '#28a745';
|
||||
const levelIcon = alert.level === 'critical' ? '🚨' :
|
||||
alert.level === 'warning' ? '⚠️' : 'ℹ️';
|
||||
|
||||
alertsHtml += `<div style="background: ${levelColor}20; border-left: 4px solid ${levelColor}; padding: 15px; margin-bottom: 10px; border-radius: 5px;">
|
||||
<div style="font-weight: bold; color: ${levelColor};">
|
||||
${levelIcon} ${alert.level.toUpperCase()}
|
||||
</div>
|
||||
<div style="margin-top: 5px;">${alert.message}</div>
|
||||
<div style="font-size: 12px; color: #666; margin-top: 5px;">
|
||||
${new Date(alert.timestamp).toLocaleString()}
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
container.innerHTML = alertsHtml;
|
||||
} else {
|
||||
container.innerHTML = '<div style="color: #28a745; text-align: center; padding: 20px;">✅ 현재 시스템 경고가 없습니다.</div>';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('시스템 경고 로딩 실패:', error);
|
||||
document.getElementById('system-alerts-container').innerHTML = '<div style="color: #dc3545;">경고 로딩 실패</div>';
|
||||
});
|
||||
}
|
||||
|
||||
// 페이지 로드 시 초기화
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 로그 및 성능 통계 초기 로딩
|
||||
refreshLogs();
|
||||
refreshPerformanceStats();
|
||||
refreshModelUsageStats();
|
||||
refreshSystemAlerts();
|
||||
|
||||
// 30초마다 로그 및 성능 통계 자동 새로고침
|
||||
setInterval(refreshLogs, 30000);
|
||||
setInterval(refreshPerformanceStats, 30000);
|
||||
setInterval(refreshModelUsageStats, 15000); // 15초마다
|
||||
setInterval(refreshSystemAlerts, 10000); // 10초마다
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1330,7 +1558,259 @@ async def get_simple_status():
|
|||
logger.error(f"간단한 상태 조회 실패: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
@api_router.get("/logs")
|
||||
async def get_recent_logs(lines: int = 100):
|
||||
"""최근 로그 반환"""
|
||||
try:
|
||||
import os
|
||||
log_file = "logs/main.log"
|
||||
|
||||
if not os.path.exists(log_file):
|
||||
return {"logs": [], "message": "로그 파일이 없습니다"}
|
||||
|
||||
# 최근 lines 줄 읽기
|
||||
with open(log_file, 'r', encoding='utf-8') as f:
|
||||
all_lines = f.readlines()
|
||||
recent_lines = all_lines[-lines:] if len(all_lines) > lines else all_lines
|
||||
|
||||
# 로그 파싱
|
||||
parsed_logs = []
|
||||
for line in recent_lines:
|
||||
line = line.strip()
|
||||
if line and " - " in line:
|
||||
try:
|
||||
# 시간, 모듈, 레벨, 메시지 분리
|
||||
parts = line.split(" - ", 3)
|
||||
if len(parts) >= 4:
|
||||
parsed_logs.append({
|
||||
"timestamp": parts[0],
|
||||
"module": parts[1],
|
||||
"level": parts[2],
|
||||
"message": parts[3]
|
||||
})
|
||||
else:
|
||||
parsed_logs.append({"raw": line})
|
||||
except:
|
||||
parsed_logs.append({"raw": line})
|
||||
|
||||
return {"logs": parsed_logs, "total": len(parsed_logs)}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"로그 조회 실패: {e}")
|
||||
return {"logs": [], "error": str(e)}
|
||||
|
||||
@api_router.get("/model-usage-stats")
|
||||
async def get_model_usage_stats():
|
||||
"""모델별 사용 통계 반환"""
|
||||
try:
|
||||
import os
|
||||
import re
|
||||
from collections import defaultdict
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
log_file = "logs/main.log"
|
||||
if not os.path.exists(log_file):
|
||||
return {"stats": {}, "message": "로그 파일이 없습니다"}
|
||||
|
||||
# 최근 1시간 데이터만 분석
|
||||
one_hour_ago = datetime.now() - timedelta(hours=1)
|
||||
|
||||
# 모델별 요청 통계
|
||||
model_requests = defaultdict(int)
|
||||
endpoint_requests = defaultdict(int)
|
||||
hourly_requests = defaultdict(int)
|
||||
|
||||
with open(log_file, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()[-2000:] # 최근 2000줄만
|
||||
|
||||
for line in lines:
|
||||
try:
|
||||
# API 요청 로그 패턴
|
||||
if '"POST /api/v1/inpaint' in line:
|
||||
model_requests['inpaint'] += 1
|
||||
elif '"POST /api/v1/remove_bg' in line:
|
||||
model_requests['remove_bg'] += 1
|
||||
elif '"GET /api/v1/model' in line:
|
||||
endpoint_requests['health_check'] += 1
|
||||
|
||||
# 시간대별 분석
|
||||
time_match = re.search(r'(\d{2}:\d{2}):\d{2}', line)
|
||||
if time_match:
|
||||
hour_minute = time_match.group(1)
|
||||
hourly_requests[hour_minute] += 1
|
||||
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return {
|
||||
"model_usage": dict(model_requests),
|
||||
"endpoint_usage": dict(endpoint_requests),
|
||||
"hourly_distribution": dict(hourly_requests),
|
||||
"total_requests": sum(model_requests.values()) + sum(endpoint_requests.values()),
|
||||
"analysis_window": "최근 2000줄"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"모델 사용 통계 조회 실패: {e}")
|
||||
return {"stats": {}, "error": str(e)}
|
||||
|
||||
@api_router.get("/performance-stats")
|
||||
async def get_performance_stats():
|
||||
"""성능 통계 반환"""
|
||||
try:
|
||||
import os
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
log_file = "logs/main.log"
|
||||
if not os.path.exists(log_file):
|
||||
return {"stats": {}, "message": "로그 파일이 없습니다"}
|
||||
|
||||
# 모델 로딩 시간 분석
|
||||
model_load_times = defaultdict(list)
|
||||
|
||||
# 최근 1000줄만 분석
|
||||
with open(log_file, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()[-1000:]
|
||||
|
||||
# 모델 로딩 시간 분석
|
||||
for i, line in enumerate(lines):
|
||||
if "Loading" in line and "model" in line:
|
||||
loading_time = None
|
||||
success_time = None
|
||||
|
||||
# 현재 라인에서 시간 추출
|
||||
try:
|
||||
time_match = re.search(r'(\d{2}:\d{2}:\d{2}),(\d{3})', line)
|
||||
if time_match:
|
||||
loading_time = f"{time_match.group(1)}.{time_match.group(2)}"
|
||||
|
||||
# 다음 몇 줄에서 "loaded successfully" 찾기
|
||||
for j in range(i+1, min(i+5, len(lines))):
|
||||
if "loaded successfully" in lines[j]:
|
||||
success_match = re.search(r'(\d{2}:\d{2}:\d{2}),(\d{3})', lines[j])
|
||||
if success_match:
|
||||
success_time = f"{success_match.group(1)}.{success_match.group(2)}"
|
||||
|
||||
# 시간 차이 계산 (초 단위)
|
||||
loading_parts = loading_time.split(':')
|
||||
success_parts = success_time.split(':')
|
||||
|
||||
loading_total = float(loading_parts[0])*3600 + float(loading_parts[1])*60 + float(loading_parts[2])
|
||||
success_total = float(success_parts[0])*3600 + float(success_parts[1])*60 + float(success_parts[2])
|
||||
|
||||
duration_ms = (success_total - loading_total) * 1000
|
||||
|
||||
if 0 < duration_ms < 10000: # 0-10초 범위만 유효
|
||||
model_name = "Simple LAMA" # 기본값
|
||||
if "simple_lama" in line.lower():
|
||||
model_name = "Simple LAMA"
|
||||
elif "migan" in line.lower():
|
||||
model_name = "MIGAN"
|
||||
elif "rembg" in line.lower():
|
||||
model_name = "RemBG"
|
||||
|
||||
model_load_times[model_name].append(duration_ms)
|
||||
break
|
||||
except Exception as parse_error:
|
||||
continue
|
||||
|
||||
# 통계 계산
|
||||
stats = {}
|
||||
for model_name, times in model_load_times.items():
|
||||
if times:
|
||||
stats[model_name] = {
|
||||
"count": len(times),
|
||||
"avg_ms": round(sum(times) / len(times), 1),
|
||||
"min_ms": round(min(times), 1),
|
||||
"max_ms": round(max(times), 1),
|
||||
"recent_times": [round(t, 1) for t in times[-10:]] # 최근 10개
|
||||
}
|
||||
|
||||
return {"stats": stats, "analysis_lines": len(lines)}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"성능 통계 조회 실패: {e}")
|
||||
return {"stats": {}, "error": str(e)}
|
||||
|
||||
# 테스트용 엔드포인트
|
||||
@api_router.get("/system-alerts")
|
||||
async def get_system_alerts():
|
||||
"""시스템 알림 반환"""
|
||||
try:
|
||||
alerts = []
|
||||
|
||||
# CPU 사용률 체크
|
||||
cpu_percent = psutil.cpu_percent(interval=1)
|
||||
if cpu_percent > 90:
|
||||
alerts.append({
|
||||
"level": "critical",
|
||||
"message": f"CPU 사용률이 매우 높습니다: {cpu_percent:.1f}%",
|
||||
"category": "system",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
elif cpu_percent > 75:
|
||||
alerts.append({
|
||||
"level": "warning",
|
||||
"message": f"CPU 사용률이 높습니다: {cpu_percent:.1f}%",
|
||||
"category": "system",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
# 메모리 사용률 체크
|
||||
memory = psutil.virtual_memory()
|
||||
if memory.percent > 90:
|
||||
alerts.append({
|
||||
"level": "critical",
|
||||
"message": f"메모리 사용률이 매우 높습니다: {memory.percent:.1f}%",
|
||||
"category": "system",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
elif memory.percent > 80:
|
||||
alerts.append({
|
||||
"level": "warning",
|
||||
"message": f"메모리 사용률이 높습니다: {memory.percent:.1f}%",
|
||||
"category": "system",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
# 디스크 사용률 체크
|
||||
disk = psutil.disk_usage('/')
|
||||
disk_percent = (disk.used / disk.total) * 100
|
||||
if disk_percent > 90:
|
||||
alerts.append({
|
||||
"level": "critical",
|
||||
"message": f"디스크 사용률이 매우 높습니다: {disk_percent:.1f}%",
|
||||
"category": "system",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
elif disk_percent > 80:
|
||||
alerts.append({
|
||||
"level": "warning",
|
||||
"message": f"디스크 사용률이 높습니다: {disk_percent:.1f}%",
|
||||
"category": "system",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
# GPU 메모리 체크 (가능한 경우)
|
||||
try:
|
||||
gpu_info = gpu_monitor.get_gpu_memory_info()
|
||||
if gpu_info.get("usage_percent", 0) > 95:
|
||||
alerts.append({
|
||||
"level": "critical",
|
||||
"message": f"GPU 메모리 사용률이 매우 높습니다: {gpu_info['usage_percent']:.1f}%",
|
||||
"category": "gpu",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {"alerts": alerts, "count": len(alerts)}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"시스템 알림 조회 실패: {e}")
|
||||
return {"alerts": [], "error": str(e)}
|
||||
|
||||
@api_router.get("/test")
|
||||
async def test_endpoint():
|
||||
"""테스트용 엔드포인트입니다."""
|
||||
|
|
|
|||
3310
logs/main.log
3310
logs/main.log
File diff suppressed because it is too large
Load Diff
|
|
@ -1,629 +1,148 @@
|
|||
INFO: Started server process [1338234]
|
||||
2025-08-29 02:46:04,218 - uvicorn.error - INFO - Started server process [1338234]
|
||||
INFO: Started server process [35210]
|
||||
2025-08-29 21:32:06,424 - uvicorn.error - INFO - Started server process [35210]
|
||||
INFO: Waiting for application startup.
|
||||
2025-08-29 02:46:04,219 - uvicorn.error - INFO - Waiting for application startup.
|
||||
2025-08-29 02:46:04,220 - main - INFO - 🚀 인페인팅 서버 시작 중...
|
||||
2025-08-29 02:46:04,220 - main - INFO - ✅ 공유 객체를 app.state에 저장 완료
|
||||
2025-08-29 02:46:04,220 - main - INFO - 🔄 상태 저장 백그라운드 작업 생성 중...
|
||||
2025-08-29 02:46:04,220 - main - INFO - ✅ 상태 저장 백그라운드 작업 생성 완료
|
||||
2025-08-29 02:46:04,220 - app.core.session_pool - INFO - Initializing session pools...
|
||||
2025-08-29 02:46:04,221 - app.core.session_pool - INFO - Initializing 2 sessions for simple_lama
|
||||
2025-08-29 02:46:04,221 - main - INFO - 🔄 상태 저장 백그라운드 작업 시작됨
|
||||
2025-08-29 02:46:04,222 - main - INFO - 상태 저장 완료 #1: 02:46:04
|
||||
2025-08-29 02:46:04,322 - app.core.session_pool - INFO - Created session simple_lama_0
|
||||
2025-08-29 02:46:04,423 - app.core.session_pool - INFO - Created session simple_lama_1
|
||||
2025-08-29 02:46:04,424 - app.core.session_pool - INFO - Initializing 2 sessions for migan
|
||||
2025-08-29 02:46:04,525 - app.core.session_pool - INFO - Created session migan_0
|
||||
2025-08-29 02:46:04,626 - app.core.session_pool - INFO - Created session migan_1
|
||||
2025-08-29 02:46:04,627 - app.core.session_pool - INFO - Initializing 1 sessions for rembg
|
||||
2025-08-29 02:46:04,728 - app.core.session_pool - INFO - Created session rembg_0
|
||||
2025-08-29 02:46:04,728 - app.core.session_pool - INFO - Session pools initialized successfully
|
||||
2025-08-29 02:46:04,728 - main - INFO - ✅ 세션 풀 초기화 완료
|
||||
2025-08-29 02:46:04,729 - app.core.worker_manager - INFO - Starting worker manager...
|
||||
2025-08-29 02:46:04,729 - app.core.worker_manager - INFO - Worker manager started with 2 workers
|
||||
2025-08-29 02:46:04,730 - main - INFO - ✅ 워커 매니저 시작 완료
|
||||
2025-08-29 02:46:04,730 - main - INFO - 🎉 인페인팅 서버 시작 완료!
|
||||
2025-08-29 21:32:06,424 - uvicorn.error - INFO - Waiting for application startup.
|
||||
2025-08-29 21:32:06,425 - main - INFO - 🚀 인페인팅 서버 시작 중...
|
||||
2025-08-29 21:32:06,426 - main - INFO - ✅ 공유 객체를 app.state에 저장 완료
|
||||
2025-08-29 21:32:06,426 - main - INFO - 🔄 상태 저장 백그라운드 작업 생성 중...
|
||||
2025-08-29 21:32:06,426 - main - INFO - ✅ 상태 저장 백그라운드 작업 생성 완료
|
||||
2025-08-29 21:32:06,427 - main - INFO - 🚀 세션 풀 초기화 (CUDA 자동 감지)
|
||||
2025-08-29 21:32:06,427 - app.core.session_pool - INFO - Initializing session pools...
|
||||
2025-08-29 21:32:06,427 - app.core.session_pool - INFO - Initializing 4 sessions for simple_lama
|
||||
2025-08-29 21:32:09,609 - app.models.simple_lama - INFO - Loading Simple LAMA model...
|
||||
2025-08-29 21:32:13,763 - app.models.simple_lama - INFO - 실제 SimpleLama 모델 로딩 완료
|
||||
2025-08-29 21:32:13,764 - app.models.simple_lama - INFO - Simple LAMA model loaded successfully
|
||||
2025-08-29 21:32:13,765 - app.core.session_pool - INFO - Simple LAMA 모델 세션 로드 완료
|
||||
2025-08-29 21:32:13,766 - app.core.session_pool - INFO - Created session simple_lama_0
|
||||
2025-08-29 21:32:13,766 - app.models.simple_lama - INFO - Loading Simple LAMA model...
|
||||
2025-08-29 21:32:15,606 - app.models.simple_lama - INFO - 실제 SimpleLama 모델 로딩 완료
|
||||
2025-08-29 21:32:15,606 - app.models.simple_lama - INFO - Simple LAMA model loaded successfully
|
||||
2025-08-29 21:32:15,607 - app.core.session_pool - INFO - Simple LAMA 모델 세션 로드 완료
|
||||
2025-08-29 21:32:15,607 - app.core.session_pool - INFO - Created session simple_lama_1
|
||||
2025-08-29 21:32:15,608 - app.models.simple_lama - INFO - Loading Simple LAMA model...
|
||||
2025-08-29 21:32:17,289 - app.models.simple_lama - INFO - 실제 SimpleLama 모델 로딩 완료
|
||||
2025-08-29 21:32:17,290 - app.models.simple_lama - INFO - Simple LAMA model loaded successfully
|
||||
2025-08-29 21:32:17,290 - app.core.session_pool - INFO - Simple LAMA 모델 세션 로드 완료
|
||||
2025-08-29 21:32:17,291 - app.core.session_pool - INFO - Created session simple_lama_2
|
||||
2025-08-29 21:32:17,291 - app.models.simple_lama - INFO - Loading Simple LAMA model...
|
||||
2025-08-29 21:32:18,958 - app.models.simple_lama - INFO - 실제 SimpleLama 모델 로딩 완료
|
||||
2025-08-29 21:32:18,959 - app.models.simple_lama - INFO - Simple LAMA model loaded successfully
|
||||
2025-08-29 21:32:18,959 - app.core.session_pool - INFO - Simple LAMA 모델 세션 로드 완료
|
||||
2025-08-29 21:32:18,960 - app.core.session_pool - INFO - Created session simple_lama_3
|
||||
2025-08-29 21:32:18,960 - app.core.session_pool - INFO - Initializing 4 sessions for migan
|
||||
2025-08-29 21:32:19,025 - app.models.migan - INFO - Loading MIGAN ONNX model...
|
||||
2025-08-29 21:32:19,026 - app.models.migan - INFO - MIGAN ONNX 런타임 세션 생성 시도...
|
||||
2025-08-29 21:32:19,026 - app.models.migan - INFO - MIGAN ONNX providers 설정: ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||
[0;93m2025-08-29 21:32:20.460420837 [W:onnxruntime:, transformer_memcpy.cc:74 ApplyImpl] 17 Memcpy nodes are added to the graph main_graph for CUDAExecutionProvider. It might have negative impact on performance (including unable to run CUDA graph). Set session_options.log_severity_level=1 to see the detail logs before this message.[m
|
||||
2025-08-29 21:32:21,873 - app.models.migan - INFO - MIGAN ONNX 세션 생성 완료. Providers: ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||
2025-08-29 21:32:21,874 - app.models.migan - INFO - MIGAN ONNX model loaded successfully
|
||||
2025-08-29 21:32:21,874 - app.core.session_pool - INFO - MIGAN 모델 세션 로드 완료
|
||||
2025-08-29 21:32:21,874 - app.core.session_pool - INFO - Created session migan_0
|
||||
2025-08-29 21:32:21,875 - app.models.migan - INFO - Loading MIGAN ONNX model...
|
||||
2025-08-29 21:32:21,875 - app.models.migan - INFO - MIGAN ONNX 런타임 세션 생성 시도...
|
||||
2025-08-29 21:32:21,876 - app.models.migan - INFO - MIGAN ONNX providers 설정: ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||
[0;93m2025-08-29 21:32:22.968792052 [W:onnxruntime:, transformer_memcpy.cc:74 ApplyImpl] 17 Memcpy nodes are added to the graph main_graph for CUDAExecutionProvider. It might have negative impact on performance (including unable to run CUDA graph). Set session_options.log_severity_level=1 to see the detail logs before this message.[m
|
||||
2025-08-29 21:32:23,134 - app.models.migan - INFO - MIGAN ONNX 세션 생성 완료. Providers: ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||
2025-08-29 21:32:23,135 - app.models.migan - INFO - MIGAN ONNX model loaded successfully
|
||||
2025-08-29 21:32:23,135 - app.core.session_pool - INFO - MIGAN 모델 세션 로드 완료
|
||||
2025-08-29 21:32:23,136 - app.core.session_pool - INFO - Created session migan_1
|
||||
2025-08-29 21:32:23,136 - app.models.migan - INFO - Loading MIGAN ONNX model...
|
||||
2025-08-29 21:32:23,137 - app.models.migan - INFO - MIGAN ONNX 런타임 세션 생성 시도...
|
||||
2025-08-29 21:32:23,137 - app.models.migan - INFO - MIGAN ONNX providers 설정: ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||
[0;93m2025-08-29 21:32:24.203228802 [W:onnxruntime:, transformer_memcpy.cc:74 ApplyImpl] 17 Memcpy nodes are added to the graph main_graph for CUDAExecutionProvider. It might have negative impact on performance (including unable to run CUDA graph). Set session_options.log_severity_level=1 to see the detail logs before this message.[m
|
||||
2025-08-29 21:32:24,367 - app.models.migan - INFO - MIGAN ONNX 세션 생성 완료. Providers: ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||
2025-08-29 21:32:24,368 - app.models.migan - INFO - MIGAN ONNX model loaded successfully
|
||||
2025-08-29 21:32:24,368 - app.core.session_pool - INFO - MIGAN 모델 세션 로드 완료
|
||||
2025-08-29 21:32:24,369 - app.core.session_pool - INFO - Created session migan_2
|
||||
2025-08-29 21:32:24,369 - app.models.migan - INFO - Loading MIGAN ONNX model...
|
||||
2025-08-29 21:32:24,370 - app.models.migan - INFO - MIGAN ONNX 런타임 세션 생성 시도...
|
||||
2025-08-29 21:32:24,370 - app.models.migan - INFO - MIGAN ONNX providers 설정: ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||
[0;93m2025-08-29 21:32:25.439378113 [W:onnxruntime:, transformer_memcpy.cc:74 ApplyImpl] 17 Memcpy nodes are added to the graph main_graph for CUDAExecutionProvider. It might have negative impact on performance (including unable to run CUDA graph). Set session_options.log_severity_level=1 to see the detail logs before this message.[m
|
||||
2025-08-29 21:32:25,605 - app.models.migan - INFO - MIGAN ONNX 세션 생성 완료. Providers: ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||
2025-08-29 21:32:25,606 - app.models.migan - INFO - MIGAN ONNX model loaded successfully
|
||||
2025-08-29 21:32:25,607 - app.core.session_pool - INFO - MIGAN 모델 세션 로드 완료
|
||||
2025-08-29 21:32:25,607 - app.core.session_pool - INFO - Created session migan_3
|
||||
2025-08-29 21:32:25,608 - app.core.session_pool - INFO - Initializing 3 sessions for rembg
|
||||
2025-08-29 21:32:25,610 - app.models.rembg_model - INFO - Loading REMBG model (birefnet-general-lite)...
|
||||
2025-08-29 21:32:27,663 - app.models.rembg_model - INFO - rembg 모듈 임포트 성공 (세션 생성은 지연 로딩)
|
||||
2025-08-29 21:32:27,664 - app.models.rembg_model - INFO - 🔧 rembg 새 세션 생성 필요: birefnet-general-lite_cuda_True
|
||||
2025-08-29 21:32:27,664 - app.models.rembg_model - WARNING - rembg.sessions import 실패, 기본 방식 사용
|
||||
2025-08-29 21:32:27,665 - app.models.rembg_model - INFO - rembg 세션 생성 providers: ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||
2025-08-29 21:32:40,606 - app.models.rembg_model - INFO - ✅ rembg 'birefnet-general-lite' GPU 가속로 동작 (providers: ['CUDAExecutionProvider', 'CPUExecutionProvider'])
|
||||
2025-08-29 21:32:40,606 - app.models.rembg_model - INFO - REMBG model (birefnet-general-lite) loaded successfully
|
||||
2025-08-29 21:32:40,607 - app.core.session_pool - INFO - REMBG 모델 세션 로드 완료
|
||||
2025-08-29 21:32:40,607 - app.core.session_pool - INFO - Created session rembg_0
|
||||
2025-08-29 21:32:40,608 - app.models.rembg_model - INFO - Loading REMBG model (birefnet-general-lite)...
|
||||
2025-08-29 21:32:40,608 - app.models.rembg_model - INFO - rembg 모듈 임포트 성공 (세션 생성은 지연 로딩)
|
||||
2025-08-29 21:32:40,608 - app.models.rembg_model - INFO - 🔧 rembg 새 세션 생성 필요: birefnet-general-lite_cuda_True
|
||||
2025-08-29 21:32:40,609 - app.models.rembg_model - WARNING - rembg.sessions import 실패, 기본 방식 사용
|
||||
2025-08-29 21:32:40,609 - app.models.rembg_model - INFO - rembg 세션 생성 providers: ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||
2025-08-29 21:32:53,362 - app.models.rembg_model - INFO - ✅ rembg 'birefnet-general-lite' GPU 가속로 동작 (providers: ['CUDAExecutionProvider', 'CPUExecutionProvider'])
|
||||
2025-08-29 21:32:53,363 - app.models.rembg_model - INFO - REMBG model (birefnet-general-lite) loaded successfully
|
||||
2025-08-29 21:32:53,364 - app.core.session_pool - INFO - REMBG 모델 세션 로드 완료
|
||||
2025-08-29 21:32:53,364 - app.core.session_pool - INFO - Created session rembg_1
|
||||
2025-08-29 21:32:53,365 - app.models.rembg_model - INFO - Loading REMBG model (birefnet-general-lite)...
|
||||
2025-08-29 21:32:53,365 - app.models.rembg_model - INFO - rembg 모듈 임포트 성공 (세션 생성은 지연 로딩)
|
||||
2025-08-29 21:32:53,366 - app.models.rembg_model - INFO - 🔧 rembg 새 세션 생성 필요: birefnet-general-lite_cuda_True
|
||||
2025-08-29 21:32:53,366 - app.models.rembg_model - WARNING - rembg.sessions import 실패, 기본 방식 사용
|
||||
2025-08-29 21:32:53,367 - app.models.rembg_model - INFO - rembg 세션 생성 providers: ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||
2025-08-29 21:33:05,746 - app.models.rembg_model - INFO - ✅ rembg 'birefnet-general-lite' GPU 가속로 동작 (providers: ['CUDAExecutionProvider', 'CPUExecutionProvider'])
|
||||
2025-08-29 21:33:05,747 - app.models.rembg_model - INFO - REMBG model (birefnet-general-lite) loaded successfully
|
||||
2025-08-29 21:33:05,747 - app.core.session_pool - INFO - REMBG 모델 세션 로드 완료
|
||||
2025-08-29 21:33:05,748 - app.core.session_pool - INFO - Created session rembg_2
|
||||
2025-08-29 21:33:05,748 - app.core.session_pool - INFO - Session pools initialized successfully
|
||||
2025-08-29 21:33:05,749 - main - INFO - ✅ 세션 풀 초기화 완료
|
||||
2025-08-29 21:33:05,749 - app.core.worker_manager - INFO - Starting worker manager...
|
||||
2025-08-29 21:33:05,750 - app.core.worker_manager - INFO - Worker manager started with 4 workers
|
||||
2025-08-29 21:33:05,750 - main - INFO - ✅ 워커 매니저 시작 완료
|
||||
2025-08-29 21:33:05,750 - main - INFO - 🎉 인페인팅 서버 시작 완료!
|
||||
2025-08-29 21:33:05,751 - main - INFO - 🔄 상태 저장 백그라운드 작업 시작됨
|
||||
INFO: Application startup complete.
|
||||
2025-08-29 02:46:04,730 - uvicorn.error - INFO - Application startup complete.
|
||||
2025-08-29 21:33:05,753 - uvicorn.error - INFO - Application startup complete.
|
||||
INFO: Uvicorn running on http://0.0.0.0:8008 (Press CTRL+C to quit)
|
||||
2025-08-29 02:46:04,732 - uvicorn.error - INFO - Uvicorn running on http://0.0.0.0:8008 (Press CTRL+C to quit)
|
||||
2025-08-29 02:46:05,226 - main - INFO - 상태 저장 완료 #2: 02:46:05
|
||||
INFO: 127.0.0.1:60054 - "GET /health HTTP/1.1" 200 OK
|
||||
2025-08-29 02:46:06,230 - main - INFO - 상태 저장 완료 #3: 02:46:06
|
||||
2025-08-29 02:46:07,234 - main - INFO - 상태 저장 완료 #4: 02:46:07
|
||||
2025-08-29 02:46:08,238 - main - INFO - 상태 저장 완료 #5: 02:46:08
|
||||
2025-08-29 02:46:09,243 - main - INFO - 상태 저장 완료 #6: 02:46:09
|
||||
2025-08-29 02:46:10,248 - main - INFO - 상태 저장 완료 #7: 02:46:10
|
||||
2025-08-29 02:46:11,253 - main - INFO - 상태 저장 완료 #8: 02:46:11
|
||||
2025-08-29 02:46:12,257 - main - INFO - 상태 저장 완료 #9: 02:46:12
|
||||
2025-08-29 02:46:13,261 - main - INFO - 상태 저장 완료 #10: 02:46:13
|
||||
2025-08-29 02:46:14,266 - main - INFO - 상태 저장 완료 #11: 02:46:14
|
||||
2025-08-29 02:46:15,270 - main - INFO - 상태 저장 완료 #12: 02:46:15
|
||||
2025-08-29 02:46:16,275 - main - INFO - 상태 저장 완료 #13: 02:46:16
|
||||
2025-08-29 02:46:17,280 - main - INFO - 상태 저장 완료 #14: 02:46:17
|
||||
2025-08-29 02:46:18,284 - main - INFO - 상태 저장 완료 #15: 02:46:18
|
||||
2025-08-29 02:46:19,289 - main - INFO - 상태 저장 완료 #16: 02:46:19
|
||||
2025-08-29 02:46:20,293 - main - INFO - 상태 저장 완료 #17: 02:46:20
|
||||
2025-08-29 02:46:21,297 - main - INFO - 상태 저장 완료 #18: 02:46:21
|
||||
2025-08-29 02:46:22,301 - main - INFO - 상태 저장 완료 #19: 02:46:22
|
||||
2025-08-29 02:46:23,305 - main - INFO - 상태 저장 완료 #20: 02:46:23
|
||||
2025-08-29 02:46:24,309 - main - INFO - 상태 저장 완료 #21: 02:46:24
|
||||
2025-08-29 02:46:25,313 - main - INFO - 상태 저장 완료 #22: 02:46:25
|
||||
2025-08-29 02:46:26,317 - main - INFO - 상태 저장 완료 #23: 02:46:26
|
||||
2025-08-29 02:46:27,321 - main - INFO - 상태 저장 완료 #24: 02:46:27
|
||||
2025-08-29 02:46:28,325 - main - INFO - 상태 저장 완료 #25: 02:46:28
|
||||
2025-08-29 02:46:29,330 - main - INFO - 상태 저장 완료 #26: 02:46:29
|
||||
2025-08-29 02:46:30,334 - main - INFO - 상태 저장 완료 #27: 02:46:30
|
||||
2025-08-29 02:46:31,340 - main - INFO - 상태 저장 완료 #28: 02:46:31
|
||||
2025-08-29 02:46:32,344 - main - INFO - 상태 저장 완료 #29: 02:46:32
|
||||
2025-08-29 02:46:33,349 - main - INFO - 상태 저장 완료 #30: 02:46:33
|
||||
2025-08-29 02:46:34,353 - main - INFO - 상태 저장 완료 #31: 02:46:34
|
||||
2025-08-29 02:46:35,358 - main - INFO - 상태 저장 완료 #32: 02:46:35
|
||||
2025-08-29 02:46:36,363 - main - INFO - 상태 저장 완료 #33: 02:46:36
|
||||
2025-08-29 02:46:37,368 - main - INFO - 상태 저장 완료 #34: 02:46:37
|
||||
2025-08-29 02:46:38,372 - main - INFO - 상태 저장 완료 #35: 02:46:38
|
||||
2025-08-29 02:46:39,377 - main - INFO - 상태 저장 완료 #36: 02:46:39
|
||||
2025-08-29 02:46:40,382 - main - INFO - 상태 저장 완료 #37: 02:46:40
|
||||
2025-08-29 02:46:41,387 - main - INFO - 상태 저장 완료 #38: 02:46:41
|
||||
2025-08-29 02:46:42,392 - main - INFO - 상태 저장 완료 #39: 02:46:42
|
||||
2025-08-29 02:46:43,397 - main - INFO - 상태 저장 완료 #40: 02:46:43
|
||||
2025-08-29 02:46:44,400 - main - INFO - 상태 저장 완료 #41: 02:46:44
|
||||
2025-08-29 02:46:45,403 - main - INFO - 상태 저장 완료 #42: 02:46:45
|
||||
2025-08-29 02:46:46,406 - main - INFO - 상태 저장 완료 #43: 02:46:46
|
||||
2025-08-29 02:46:47,409 - main - INFO - 상태 저장 완료 #44: 02:46:47
|
||||
2025-08-29 02:46:48,413 - main - INFO - 상태 저장 완료 #45: 02:46:48
|
||||
2025-08-29 02:46:49,416 - main - INFO - 상태 저장 완료 #46: 02:46:49
|
||||
2025-08-29 02:46:50,419 - main - INFO - 상태 저장 완료 #47: 02:46:50
|
||||
2025-08-29 02:46:51,423 - main - INFO - 상태 저장 완료 #48: 02:46:51
|
||||
2025-08-29 02:46:52,428 - main - INFO - 상태 저장 완료 #49: 02:46:52
|
||||
2025-08-29 02:46:53,434 - main - INFO - 상태 저장 완료 #50: 02:46:53
|
||||
2025-08-29 02:46:54,438 - main - INFO - 상태 저장 완료 #51: 02:46:54
|
||||
2025-08-29 02:46:55,443 - main - INFO - 상태 저장 완료 #52: 02:46:55
|
||||
2025-08-29 02:46:56,449 - main - INFO - 상태 저장 완료 #53: 02:46:56
|
||||
2025-08-29 02:46:57,454 - main - INFO - 상태 저장 완료 #54: 02:46:57
|
||||
2025-08-29 02:46:58,459 - main - INFO - 상태 저장 완료 #55: 02:46:58
|
||||
2025-08-29 02:46:59,464 - main - INFO - 상태 저장 완료 #56: 02:46:59
|
||||
2025-08-29 02:47:00,468 - main - INFO - 상태 저장 완료 #57: 02:47:00
|
||||
2025-08-29 02:47:01,473 - main - INFO - 상태 저장 완료 #58: 02:47:01
|
||||
2025-08-29 02:47:02,478 - main - INFO - 상태 저장 완료 #59: 02:47:02
|
||||
2025-08-29 02:47:03,482 - main - INFO - 상태 저장 완료 #60: 02:47:03
|
||||
2025-08-29 02:47:04,487 - main - INFO - 상태 저장 완료 #61: 02:47:04
|
||||
2025-08-29 02:47:07,774 - main - INFO - 상태 저장 완료 #62: 02:47:07
|
||||
2025-08-29 02:47:08,779 - main - INFO - 상태 저장 완료 #63: 02:47:08
|
||||
2025-08-29 02:47:09,783 - main - INFO - 상태 저장 완료 #64: 02:47:09
|
||||
2025-08-29 02:47:10,787 - main - INFO - 상태 저장 완료 #65: 02:47:10
|
||||
2025-08-29 02:47:11,791 - main - INFO - 상태 저장 완료 #66: 02:47:11
|
||||
2025-08-29 02:47:12,795 - main - INFO - 상태 저장 완료 #67: 02:47:12
|
||||
2025-08-29 02:47:13,800 - main - INFO - 상태 저장 완료 #68: 02:47:13
|
||||
2025-08-29 02:47:14,804 - main - INFO - 상태 저장 완료 #69: 02:47:14
|
||||
2025-08-29 02:47:15,809 - main - INFO - 상태 저장 완료 #70: 02:47:15
|
||||
2025-08-29 02:47:16,814 - main - INFO - 상태 저장 완료 #71: 02:47:16
|
||||
2025-08-29 02:47:17,818 - main - INFO - 상태 저장 완료 #72: 02:47:17
|
||||
2025-08-29 02:47:18,823 - main - INFO - 상태 저장 완료 #73: 02:47:18
|
||||
2025-08-29 02:47:19,828 - main - INFO - 상태 저장 완료 #74: 02:47:19
|
||||
2025-08-29 02:47:20,833 - main - INFO - 상태 저장 완료 #75: 02:47:20
|
||||
2025-08-29 02:47:21,837 - main - INFO - 상태 저장 완료 #76: 02:47:21
|
||||
2025-08-29 02:47:22,841 - main - INFO - 상태 저장 완료 #77: 02:47:22
|
||||
2025-08-29 02:47:23,849 - main - INFO - 상태 저장 완료 #78: 02:47:23
|
||||
2025-08-29 02:47:24,853 - main - INFO - 상태 저장 완료 #79: 02:47:24
|
||||
2025-08-29 02:47:25,857 - main - INFO - 상태 저장 완료 #80: 02:47:25
|
||||
2025-08-29 02:47:26,861 - main - INFO - 상태 저장 완료 #81: 02:47:26
|
||||
2025-08-29 02:47:30,810 - main - INFO - 상태 저장 완료 #82: 02:47:30
|
||||
2025-08-29 02:47:31,815 - main - INFO - 상태 저장 완료 #83: 02:47:31
|
||||
2025-08-29 02:47:32,819 - main - INFO - 상태 저장 완료 #84: 02:47:32
|
||||
2025-08-29 02:47:33,824 - main - INFO - 상태 저장 완료 #85: 02:47:33
|
||||
2025-08-29 02:47:34,828 - main - INFO - 상태 저장 완료 #86: 02:47:34
|
||||
2025-08-29 02:47:35,833 - main - INFO - 상태 저장 완료 #87: 02:47:35
|
||||
2025-08-29 02:47:36,837 - main - INFO - 상태 저장 완료 #88: 02:47:36
|
||||
2025-08-29 02:47:37,840 - main - INFO - 상태 저장 완료 #89: 02:47:37
|
||||
2025-08-29 02:47:38,845 - main - INFO - 상태 저장 완료 #90: 02:47:38
|
||||
2025-08-29 02:47:39,852 - main - INFO - 상태 저장 완료 #91: 02:47:39
|
||||
2025-08-29 02:47:40,856 - main - INFO - 상태 저장 완료 #92: 02:47:40
|
||||
2025-08-29 02:47:41,861 - main - INFO - 상태 저장 완료 #93: 02:47:41
|
||||
2025-08-29 02:47:42,865 - main - INFO - 상태 저장 완료 #94: 02:47:42
|
||||
2025-08-29 02:47:43,869 - main - INFO - 상태 저장 완료 #95: 02:47:43
|
||||
2025-08-29 02:47:44,877 - main - INFO - 상태 저장 완료 #96: 02:47:44
|
||||
2025-08-29 02:47:45,880 - main - INFO - 상태 저장 완료 #97: 02:47:45
|
||||
2025-08-29 02:47:46,884 - main - INFO - 상태 저장 완료 #98: 02:47:46
|
||||
2025-08-29 02:47:47,889 - main - INFO - 상태 저장 완료 #99: 02:47:47
|
||||
2025-08-29 02:47:48,893 - main - INFO - 상태 저장 완료 #100: 02:47:48
|
||||
2025-08-29 02:47:49,901 - main - INFO - 상태 저장 완료 #101: 02:47:49
|
||||
2025-08-29 02:47:53,846 - main - INFO - 상태 저장 완료 #102: 02:47:53
|
||||
2025-08-29 02:47:54,851 - main - INFO - 상태 저장 완료 #103: 02:47:54
|
||||
2025-08-29 02:47:55,856 - main - INFO - 상태 저장 완료 #104: 02:47:55
|
||||
2025-08-29 02:47:56,860 - main - INFO - 상태 저장 완료 #105: 02:47:56
|
||||
2025-08-29 02:47:57,865 - main - INFO - 상태 저장 완료 #106: 02:47:57
|
||||
2025-08-29 02:47:58,869 - main - INFO - 상태 저장 완료 #107: 02:47:58
|
||||
2025-08-29 02:47:59,873 - main - INFO - 상태 저장 완료 #108: 02:47:59
|
||||
2025-08-29 02:48:00,878 - main - INFO - 상태 저장 완료 #109: 02:48:00
|
||||
2025-08-29 02:48:01,882 - main - INFO - 상태 저장 완료 #110: 02:48:01
|
||||
2025-08-29 02:48:02,886 - main - INFO - 상태 저장 완료 #111: 02:48:02
|
||||
2025-08-29 02:48:03,890 - main - INFO - 상태 저장 완료 #112: 02:48:03
|
||||
2025-08-29 02:48:04,895 - main - INFO - 상태 저장 완료 #113: 02:48:04
|
||||
2025-08-29 02:48:05,899 - main - INFO - 상태 저장 완료 #114: 02:48:05
|
||||
2025-08-29 02:48:06,903 - main - INFO - 상태 저장 완료 #115: 02:48:06
|
||||
2025-08-29 02:48:07,908 - main - INFO - 상태 저장 완료 #116: 02:48:07
|
||||
2025-08-29 02:48:08,913 - main - INFO - 상태 저장 완료 #117: 02:48:08
|
||||
2025-08-29 02:48:09,918 - main - INFO - 상태 저장 완료 #118: 02:48:09
|
||||
2025-08-29 02:48:10,924 - main - INFO - 상태 저장 완료 #119: 02:48:10
|
||||
2025-08-29 02:48:11,928 - main - INFO - 상태 저장 완료 #120: 02:48:11
|
||||
2025-08-29 02:48:12,933 - main - INFO - 상태 저장 완료 #121: 02:48:12
|
||||
2025-08-29 02:48:16,876 - main - INFO - 상태 저장 완료 #122: 02:48:16
|
||||
2025-08-29 02:48:17,883 - main - INFO - 상태 저장 완료 #123: 02:48:17
|
||||
2025-08-29 02:48:18,887 - main - INFO - 상태 저장 완료 #124: 02:48:18
|
||||
2025-08-29 02:48:19,891 - main - INFO - 상태 저장 완료 #125: 02:48:19
|
||||
2025-08-29 02:48:20,895 - main - INFO - 상태 저장 완료 #126: 02:48:20
|
||||
2025-08-29 02:48:21,900 - main - INFO - 상태 저장 완료 #127: 02:48:21
|
||||
2025-08-29 02:48:22,903 - main - INFO - 상태 저장 완료 #128: 02:48:22
|
||||
2025-08-29 02:48:23,907 - main - INFO - 상태 저장 완료 #129: 02:48:23
|
||||
2025-08-29 02:48:24,912 - main - INFO - 상태 저장 완료 #130: 02:48:24
|
||||
2025-08-29 02:48:25,917 - main - INFO - 상태 저장 완료 #131: 02:48:25
|
||||
2025-08-29 02:48:26,922 - main - INFO - 상태 저장 완료 #132: 02:48:26
|
||||
2025-08-29 02:48:27,926 - main - INFO - 상태 저장 완료 #133: 02:48:27
|
||||
2025-08-29 02:48:28,931 - main - INFO - 상태 저장 완료 #134: 02:48:28
|
||||
2025-08-29 02:48:29,935 - main - INFO - 상태 저장 완료 #135: 02:48:29
|
||||
2025-08-29 02:48:30,940 - main - INFO - 상태 저장 완료 #136: 02:48:30
|
||||
2025-08-29 02:48:31,945 - main - INFO - 상태 저장 완료 #137: 02:48:31
|
||||
2025-08-29 02:48:32,949 - main - INFO - 상태 저장 완료 #138: 02:48:32
|
||||
2025-08-29 02:48:33,954 - main - INFO - 상태 저장 완료 #139: 02:48:33
|
||||
2025-08-29 02:48:34,956 - main - INFO - 상태 저장 완료 #140: 02:48:34
|
||||
2025-08-29 02:48:35,961 - main - INFO - 상태 저장 완료 #141: 02:48:35
|
||||
2025-08-29 02:48:39,911 - main - INFO - 상태 저장 완료 #142: 02:48:39
|
||||
2025-08-29 02:48:40,915 - main - INFO - 상태 저장 완료 #143: 02:48:40
|
||||
2025-08-29 02:48:41,920 - main - INFO - 상태 저장 완료 #144: 02:48:41
|
||||
2025-08-29 02:48:42,924 - main - INFO - 상태 저장 완료 #145: 02:48:42
|
||||
2025-08-29 02:48:43,928 - main - INFO - 상태 저장 완료 #146: 02:48:43
|
||||
2025-08-29 02:48:44,934 - main - INFO - 상태 저장 완료 #147: 02:48:44
|
||||
2025-08-29 02:48:45,938 - main - INFO - 상태 저장 완료 #148: 02:48:45
|
||||
2025-08-29 02:48:46,942 - main - INFO - 상태 저장 완료 #149: 02:48:46
|
||||
2025-08-29 02:48:47,946 - main - INFO - 상태 저장 완료 #150: 02:48:47
|
||||
2025-08-29 02:48:48,951 - main - INFO - 상태 저장 완료 #151: 02:48:48
|
||||
2025-08-29 02:48:49,955 - main - INFO - 상태 저장 완료 #152: 02:48:49
|
||||
2025-08-29 02:48:50,960 - main - INFO - 상태 저장 완료 #153: 02:48:50
|
||||
2025-08-29 02:48:51,964 - main - INFO - 상태 저장 완료 #154: 02:48:51
|
||||
2025-08-29 02:48:52,969 - main - INFO - 상태 저장 완료 #155: 02:48:52
|
||||
2025-08-29 02:48:53,973 - main - INFO - 상태 저장 완료 #156: 02:48:53
|
||||
2025-08-29 02:48:54,977 - main - INFO - 상태 저장 완료 #157: 02:48:54
|
||||
2025-08-29 02:48:55,981 - main - INFO - 상태 저장 완료 #158: 02:48:55
|
||||
2025-08-29 02:48:56,985 - main - INFO - 상태 저장 완료 #159: 02:48:56
|
||||
2025-08-29 02:48:57,990 - main - INFO - 상태 저장 완료 #160: 02:48:57
|
||||
2025-08-29 02:48:58,995 - main - INFO - 상태 저장 완료 #161: 02:48:58
|
||||
2025-08-29 02:49:02,945 - main - INFO - 상태 저장 완료 #162: 02:49:02
|
||||
2025-08-29 02:49:03,949 - main - INFO - 상태 저장 완료 #163: 02:49:03
|
||||
2025-08-29 02:49:04,954 - main - INFO - 상태 저장 완료 #164: 02:49:04
|
||||
2025-08-29 02:49:05,958 - main - INFO - 상태 저장 완료 #165: 02:49:05
|
||||
2025-08-29 02:49:06,961 - main - INFO - 상태 저장 완료 #166: 02:49:06
|
||||
2025-08-29 02:49:07,966 - main - INFO - 상태 저장 완료 #167: 02:49:07
|
||||
2025-08-29 02:49:08,970 - main - INFO - 상태 저장 완료 #168: 02:49:08
|
||||
2025-08-29 02:49:09,975 - main - INFO - 상태 저장 완료 #169: 02:49:09
|
||||
2025-08-29 02:49:10,979 - main - INFO - 상태 저장 완료 #170: 02:49:10
|
||||
2025-08-29 02:49:11,985 - main - INFO - 상태 저장 완료 #171: 02:49:11
|
||||
2025-08-29 02:49:12,990 - main - INFO - 상태 저장 완료 #172: 02:49:12
|
||||
2025-08-29 02:49:13,995 - main - INFO - 상태 저장 완료 #173: 02:49:13
|
||||
2025-08-29 02:49:15,000 - main - INFO - 상태 저장 완료 #174: 02:49:14
|
||||
2025-08-29 02:49:16,005 - main - INFO - 상태 저장 완료 #175: 02:49:16
|
||||
2025-08-29 02:49:17,010 - main - INFO - 상태 저장 완료 #176: 02:49:17
|
||||
2025-08-29 02:49:18,015 - main - INFO - 상태 저장 완료 #177: 02:49:18
|
||||
2025-08-29 02:49:19,020 - main - INFO - 상태 저장 완료 #178: 02:49:19
|
||||
2025-08-29 02:49:20,024 - main - INFO - 상태 저장 완료 #179: 02:49:20
|
||||
2025-08-29 02:49:21,029 - main - INFO - 상태 저장 완료 #180: 02:49:21
|
||||
2025-08-29 02:49:22,034 - main - INFO - 상태 저장 완료 #181: 02:49:22
|
||||
2025-08-29 02:49:25,980 - main - INFO - 상태 저장 완료 #182: 02:49:25
|
||||
2025-08-29 02:49:26,985 - main - INFO - 상태 저장 완료 #183: 02:49:26
|
||||
2025-08-29 02:49:27,989 - main - INFO - 상태 저장 완료 #184: 02:49:27
|
||||
2025-08-29 02:49:28,993 - main - INFO - 상태 저장 완료 #185: 02:49:28
|
||||
2025-08-29 02:49:29,997 - main - INFO - 상태 저장 완료 #186: 02:49:29
|
||||
2025-08-29 02:49:31,001 - main - INFO - 상태 저장 완료 #187: 02:49:30
|
||||
2025-08-29 02:49:32,005 - main - INFO - 상태 저장 완료 #188: 02:49:32
|
||||
2025-08-29 02:49:33,009 - main - INFO - 상태 저장 완료 #189: 02:49:33
|
||||
2025-08-29 02:49:34,013 - main - INFO - 상태 저장 완료 #190: 02:49:34
|
||||
2025-08-29 02:49:35,016 - main - INFO - 상태 저장 완료 #191: 02:49:35
|
||||
2025-08-29 02:49:36,021 - main - INFO - 상태 저장 완료 #192: 02:49:36
|
||||
2025-08-29 02:49:37,025 - main - INFO - 상태 저장 완료 #193: 02:49:37
|
||||
2025-08-29 02:49:38,030 - main - INFO - 상태 저장 완료 #194: 02:49:38
|
||||
2025-08-29 02:49:39,035 - main - INFO - 상태 저장 완료 #195: 02:49:39
|
||||
2025-08-29 02:49:40,039 - main - INFO - 상태 저장 완료 #196: 02:49:40
|
||||
2025-08-29 02:49:41,044 - main - INFO - 상태 저장 완료 #197: 02:49:41
|
||||
2025-08-29 02:49:42,048 - main - INFO - 상태 저장 완료 #198: 02:49:42
|
||||
2025-08-29 02:49:43,052 - main - INFO - 상태 저장 완료 #199: 02:49:43
|
||||
2025-08-29 02:49:44,057 - main - INFO - 상태 저장 완료 #200: 02:49:44
|
||||
2025-08-29 02:49:45,061 - main - INFO - 상태 저장 완료 #201: 02:49:45
|
||||
2025-08-29 02:49:49,015 - main - INFO - 상태 저장 완료 #202: 02:49:49
|
||||
2025-08-29 02:49:50,020 - main - INFO - 상태 저장 완료 #203: 02:49:50
|
||||
2025-08-29 02:49:51,025 - main - INFO - 상태 저장 완료 #204: 02:49:51
|
||||
2025-08-29 02:49:52,029 - main - INFO - 상태 저장 완료 #205: 02:49:52
|
||||
2025-08-29 02:49:53,033 - main - INFO - 상태 저장 완료 #206: 02:49:53
|
||||
2025-08-29 02:49:54,037 - main - INFO - 상태 저장 완료 #207: 02:49:54
|
||||
2025-08-29 02:49:55,041 - main - INFO - 상태 저장 완료 #208: 02:49:55
|
||||
2025-08-29 02:49:56,045 - main - INFO - 상태 저장 완료 #209: 02:49:56
|
||||
2025-08-29 02:49:57,049 - main - INFO - 상태 저장 완료 #210: 02:49:57
|
||||
2025-08-29 02:49:58,053 - main - INFO - 상태 저장 완료 #211: 02:49:58
|
||||
2025-08-29 02:49:59,057 - main - INFO - 상태 저장 완료 #212: 02:49:59
|
||||
2025-08-29 02:50:00,059 - main - INFO - 상태 저장 완료 #213: 02:50:00
|
||||
2025-08-29 02:50:01,063 - main - INFO - 상태 저장 완료 #214: 02:50:01
|
||||
2025-08-29 02:50:02,067 - main - INFO - 상태 저장 완료 #215: 02:50:02
|
||||
2025-08-29 02:50:03,069 - main - INFO - 상태 저장 완료 #216: 02:50:03
|
||||
2025-08-29 02:50:04,073 - main - INFO - 상태 저장 완료 #217: 02:50:04
|
||||
2025-08-29 02:50:05,078 - main - INFO - 상태 저장 완료 #218: 02:50:05
|
||||
2025-08-29 02:50:06,083 - main - INFO - 상태 저장 완료 #219: 02:50:06
|
||||
2025-08-29 02:50:07,087 - main - INFO - 상태 저장 완료 #220: 02:50:07
|
||||
2025-08-29 02:50:08,091 - main - INFO - 상태 저장 완료 #221: 02:50:08
|
||||
2025-08-29 02:50:12,049 - main - INFO - 상태 저장 완료 #222: 02:50:12
|
||||
2025-08-29 02:50:13,054 - main - INFO - 상태 저장 완료 #223: 02:50:13
|
||||
2025-08-29 02:50:14,058 - main - INFO - 상태 저장 완료 #224: 02:50:14
|
||||
2025-08-29 02:50:15,063 - main - INFO - 상태 저장 완료 #225: 02:50:15
|
||||
2025-08-29 02:50:16,067 - main - INFO - 상태 저장 완료 #226: 02:50:16
|
||||
2025-08-29 02:50:17,071 - main - INFO - 상태 저장 완료 #227: 02:50:17
|
||||
2025-08-29 02:50:18,076 - main - INFO - 상태 저장 완료 #228: 02:50:18
|
||||
2025-08-29 02:50:19,081 - main - INFO - 상태 저장 완료 #229: 02:50:19
|
||||
2025-08-29 02:50:20,085 - main - INFO - 상태 저장 완료 #230: 02:50:20
|
||||
2025-08-29 02:50:21,090 - main - INFO - 상태 저장 완료 #231: 02:50:21
|
||||
2025-08-29 02:50:22,094 - main - INFO - 상태 저장 완료 #232: 02:50:22
|
||||
2025-08-29 02:50:23,098 - main - INFO - 상태 저장 완료 #233: 02:50:23
|
||||
2025-08-29 02:50:24,102 - main - INFO - 상태 저장 완료 #234: 02:50:24
|
||||
2025-08-29 02:50:25,106 - main - INFO - 상태 저장 완료 #235: 02:50:25
|
||||
2025-08-29 02:50:26,111 - main - INFO - 상태 저장 완료 #236: 02:50:26
|
||||
2025-08-29 02:50:27,115 - main - INFO - 상태 저장 완료 #237: 02:50:27
|
||||
2025-08-29 02:50:28,120 - main - INFO - 상태 저장 완료 #238: 02:50:28
|
||||
2025-08-29 02:50:29,124 - main - INFO - 상태 저장 완료 #239: 02:50:29
|
||||
2025-08-29 02:50:30,129 - main - INFO - 상태 저장 완료 #240: 02:50:30
|
||||
2025-08-29 02:50:31,134 - main - INFO - 상태 저장 완료 #241: 02:50:31
|
||||
2025-08-29 02:50:35,082 - main - INFO - 상태 저장 완료 #242: 02:50:35
|
||||
2025-08-29 02:50:36,087 - main - INFO - 상태 저장 완료 #243: 02:50:36
|
||||
2025-08-29 02:50:37,092 - main - INFO - 상태 저장 완료 #244: 02:50:37
|
||||
2025-08-29 02:50:38,097 - main - INFO - 상태 저장 완료 #245: 02:50:38
|
||||
2025-08-29 02:50:39,101 - main - INFO - 상태 저장 완료 #246: 02:50:39
|
||||
2025-08-29 02:50:40,106 - main - INFO - 상태 저장 완료 #247: 02:50:40
|
||||
2025-08-29 02:50:41,111 - main - INFO - 상태 저장 완료 #248: 02:50:41
|
||||
2025-08-29 02:50:42,115 - main - INFO - 상태 저장 완료 #249: 02:50:42
|
||||
2025-08-29 02:50:43,120 - main - INFO - 상태 저장 완료 #250: 02:50:43
|
||||
2025-08-29 02:50:44,125 - main - INFO - 상태 저장 완료 #251: 02:50:44
|
||||
2025-08-29 02:50:45,129 - main - INFO - 상태 저장 완료 #252: 02:50:45
|
||||
2025-08-29 02:50:46,134 - main - INFO - 상태 저장 완료 #253: 02:50:46
|
||||
2025-08-29 02:50:47,137 - main - INFO - 상태 저장 완료 #254: 02:50:47
|
||||
2025-08-29 02:50:48,141 - main - INFO - 상태 저장 완료 #255: 02:50:48
|
||||
2025-08-29 02:50:49,146 - main - INFO - 상태 저장 완료 #256: 02:50:49
|
||||
2025-08-29 02:50:50,150 - main - INFO - 상태 저장 완료 #257: 02:50:50
|
||||
2025-08-29 02:50:51,154 - main - INFO - 상태 저장 완료 #258: 02:50:51
|
||||
2025-08-29 02:50:52,159 - main - INFO - 상태 저장 완료 #259: 02:50:52
|
||||
2025-08-29 02:50:53,164 - main - INFO - 상태 저장 완료 #260: 02:50:53
|
||||
2025-08-29 02:50:54,168 - main - INFO - 상태 저장 완료 #261: 02:50:54
|
||||
2025-08-29 02:50:58,116 - main - INFO - 상태 저장 완료 #262: 02:50:58
|
||||
2025-08-29 02:50:59,121 - main - INFO - 상태 저장 완료 #263: 02:50:59
|
||||
2025-08-29 02:51:00,126 - main - INFO - 상태 저장 완료 #264: 02:51:00
|
||||
2025-08-29 02:51:01,130 - main - INFO - 상태 저장 완료 #265: 02:51:01
|
||||
2025-08-29 02:51:02,133 - main - INFO - 상태 저장 완료 #266: 02:51:02
|
||||
2025-08-29 02:51:03,140 - main - INFO - 상태 저장 완료 #267: 02:51:03
|
||||
2025-08-29 02:51:04,144 - main - INFO - 상태 저장 완료 #268: 02:51:04
|
||||
2025-08-29 02:51:05,148 - main - INFO - 상태 저장 완료 #269: 02:51:05
|
||||
2025-08-29 02:51:06,153 - main - INFO - 상태 저장 완료 #270: 02:51:06
|
||||
2025-08-29 02:51:07,156 - main - INFO - 상태 저장 완료 #271: 02:51:07
|
||||
2025-08-29 02:51:08,161 - main - INFO - 상태 저장 완료 #272: 02:51:08
|
||||
2025-08-29 02:51:09,165 - main - INFO - 상태 저장 완료 #273: 02:51:09
|
||||
2025-08-29 02:51:10,170 - main - INFO - 상태 저장 완료 #274: 02:51:10
|
||||
2025-08-29 02:51:11,174 - main - INFO - 상태 저장 완료 #275: 02:51:11
|
||||
2025-08-29 02:51:12,178 - main - INFO - 상태 저장 완료 #276: 02:51:12
|
||||
2025-08-29 02:51:13,182 - main - INFO - 상태 저장 완료 #277: 02:51:13
|
||||
2025-08-29 02:51:14,187 - main - INFO - 상태 저장 완료 #278: 02:51:14
|
||||
2025-08-29 02:51:15,191 - main - INFO - 상태 저장 완료 #279: 02:51:15
|
||||
2025-08-29 02:51:16,196 - main - INFO - 상태 저장 완료 #280: 02:51:16
|
||||
2025-08-29 02:51:17,200 - main - INFO - 상태 저장 완료 #281: 02:51:17
|
||||
2025-08-29 02:51:21,149 - main - INFO - 상태 저장 완료 #282: 02:51:21
|
||||
2025-08-29 02:51:22,154 - main - INFO - 상태 저장 완료 #283: 02:51:22
|
||||
2025-08-29 02:51:23,159 - main - INFO - 상태 저장 완료 #284: 02:51:23
|
||||
2025-08-29 02:51:24,163 - main - INFO - 상태 저장 완료 #285: 02:51:24
|
||||
2025-08-29 02:51:25,167 - main - INFO - 상태 저장 완료 #286: 02:51:25
|
||||
2025-08-29 02:51:26,172 - main - INFO - 상태 저장 완료 #287: 02:51:26
|
||||
2025-08-29 02:51:27,177 - main - INFO - 상태 저장 완료 #288: 02:51:27
|
||||
2025-08-29 02:51:28,181 - main - INFO - 상태 저장 완료 #289: 02:51:28
|
||||
2025-08-29 02:51:29,185 - main - INFO - 상태 저장 완료 #290: 02:51:29
|
||||
2025-08-29 02:51:30,191 - main - INFO - 상태 저장 완료 #291: 02:51:30
|
||||
2025-08-29 02:51:31,197 - main - INFO - 상태 저장 완료 #292: 02:51:31
|
||||
2025-08-29 02:51:32,202 - main - INFO - 상태 저장 완료 #293: 02:51:32
|
||||
2025-08-29 02:51:33,206 - main - INFO - 상태 저장 완료 #294: 02:51:33
|
||||
2025-08-29 02:51:34,211 - main - INFO - 상태 저장 완료 #295: 02:51:34
|
||||
2025-08-29 02:51:35,215 - main - INFO - 상태 저장 완료 #296: 02:51:35
|
||||
2025-08-29 02:51:36,219 - main - INFO - 상태 저장 완료 #297: 02:51:36
|
||||
2025-08-29 02:51:37,224 - main - INFO - 상태 저장 완료 #298: 02:51:37
|
||||
2025-08-29 02:51:38,229 - main - INFO - 상태 저장 완료 #299: 02:51:38
|
||||
2025-08-29 02:51:39,233 - main - INFO - 상태 저장 완료 #300: 02:51:39
|
||||
2025-08-29 02:51:40,237 - main - INFO - 상태 저장 완료 #301: 02:51:40
|
||||
2025-08-29 02:51:44,186 - main - INFO - 상태 저장 완료 #302: 02:51:44
|
||||
2025-08-29 02:51:45,191 - main - INFO - 상태 저장 완료 #303: 02:51:45
|
||||
2025-08-29 02:51:46,195 - main - INFO - 상태 저장 완료 #304: 02:51:46
|
||||
2025-08-29 02:51:47,200 - main - INFO - 상태 저장 완료 #305: 02:51:47
|
||||
2025-08-29 02:51:48,205 - main - INFO - 상태 저장 완료 #306: 02:51:48
|
||||
2025-08-29 02:51:49,209 - main - INFO - 상태 저장 완료 #307: 02:51:49
|
||||
2025-08-29 02:51:50,213 - main - INFO - 상태 저장 완료 #308: 02:51:50
|
||||
2025-08-29 02:51:51,218 - main - INFO - 상태 저장 완료 #309: 02:51:51
|
||||
2025-08-29 02:51:52,222 - main - INFO - 상태 저장 완료 #310: 02:51:52
|
||||
2025-08-29 02:51:53,226 - main - INFO - 상태 저장 완료 #311: 02:51:53
|
||||
2025-08-29 02:51:54,230 - main - INFO - 상태 저장 완료 #312: 02:51:54
|
||||
2025-08-29 02:51:55,232 - main - INFO - 상태 저장 완료 #313: 02:51:55
|
||||
2025-08-29 02:51:56,237 - main - INFO - 상태 저장 완료 #314: 02:51:56
|
||||
2025-08-29 02:51:57,241 - main - INFO - 상태 저장 완료 #315: 02:51:57
|
||||
2025-08-29 02:51:58,245 - main - INFO - 상태 저장 완료 #316: 02:51:58
|
||||
2025-08-29 02:51:59,250 - main - INFO - 상태 저장 완료 #317: 02:51:59
|
||||
2025-08-29 02:52:00,254 - main - INFO - 상태 저장 완료 #318: 02:52:00
|
||||
2025-08-29 02:52:01,258 - main - INFO - 상태 저장 완료 #319: 02:52:01
|
||||
2025-08-29 02:52:02,261 - main - INFO - 상태 저장 완료 #320: 02:52:02
|
||||
2025-08-29 02:52:03,265 - main - INFO - 상태 저장 완료 #321: 02:52:03
|
||||
2025-08-29 02:52:07,219 - main - INFO - 상태 저장 완료 #322: 02:52:07
|
||||
2025-08-29 02:52:08,224 - main - INFO - 상태 저장 완료 #323: 02:52:08
|
||||
2025-08-29 02:52:09,229 - main - INFO - 상태 저장 완료 #324: 02:52:09
|
||||
2025-08-29 02:52:10,233 - main - INFO - 상태 저장 완료 #325: 02:52:10
|
||||
2025-08-29 02:52:11,237 - main - INFO - 상태 저장 완료 #326: 02:52:11
|
||||
2025-08-29 02:52:12,241 - main - INFO - 상태 저장 완료 #327: 02:52:12
|
||||
2025-08-29 02:52:13,245 - main - INFO - 상태 저장 완료 #328: 02:52:13
|
||||
2025-08-29 02:52:14,250 - main - INFO - 상태 저장 완료 #329: 02:52:14
|
||||
2025-08-29 02:52:15,254 - main - INFO - 상태 저장 완료 #330: 02:52:15
|
||||
2025-08-29 02:52:16,258 - main - INFO - 상태 저장 완료 #331: 02:52:16
|
||||
2025-08-29 02:52:17,262 - main - INFO - 상태 저장 완료 #332: 02:52:17
|
||||
2025-08-29 02:52:18,266 - main - INFO - 상태 저장 완료 #333: 02:52:18
|
||||
2025-08-29 02:52:19,271 - main - INFO - 상태 저장 완료 #334: 02:52:19
|
||||
2025-08-29 02:52:20,275 - main - INFO - 상태 저장 완료 #335: 02:52:20
|
||||
2025-08-29 02:52:21,280 - main - INFO - 상태 저장 완료 #336: 02:52:21
|
||||
2025-08-29 02:52:22,284 - main - INFO - 상태 저장 완료 #337: 02:52:22
|
||||
2025-08-29 02:52:23,289 - main - INFO - 상태 저장 완료 #338: 02:52:23
|
||||
2025-08-29 02:52:24,294 - main - INFO - 상태 저장 완료 #339: 02:52:24
|
||||
2025-08-29 02:52:25,298 - main - INFO - 상태 저장 완료 #340: 02:52:25
|
||||
2025-08-29 02:52:26,303 - main - INFO - 상태 저장 완료 #341: 02:52:26
|
||||
2025-08-29 02:52:30,253 - main - INFO - 상태 저장 완료 #342: 02:52:30
|
||||
2025-08-29 02:52:31,258 - main - INFO - 상태 저장 완료 #343: 02:52:31
|
||||
2025-08-29 02:52:32,263 - main - INFO - 상태 저장 완료 #344: 02:52:32
|
||||
2025-08-29 02:52:33,267 - main - INFO - 상태 저장 완료 #345: 02:52:33
|
||||
2025-08-29 02:52:34,272 - main - INFO - 상태 저장 완료 #346: 02:52:34
|
||||
2025-08-29 02:52:35,276 - main - INFO - 상태 저장 완료 #347: 02:52:35
|
||||
2025-08-29 02:52:36,280 - main - INFO - 상태 저장 완료 #348: 02:52:36
|
||||
2025-08-29 02:52:37,285 - main - INFO - 상태 저장 완료 #349: 02:52:37
|
||||
2025-08-29 02:52:38,288 - main - INFO - 상태 저장 완료 #350: 02:52:38
|
||||
2025-08-29 02:52:39,292 - main - INFO - 상태 저장 완료 #351: 02:52:39
|
||||
2025-08-29 02:52:40,297 - main - INFO - 상태 저장 완료 #352: 02:52:40
|
||||
2025-08-29 02:52:41,302 - main - INFO - 상태 저장 완료 #353: 02:52:41
|
||||
2025-08-29 02:52:42,306 - main - INFO - 상태 저장 완료 #354: 02:52:42
|
||||
2025-08-29 02:52:43,310 - main - INFO - 상태 저장 완료 #355: 02:52:43
|
||||
2025-08-29 02:52:44,314 - main - INFO - 상태 저장 완료 #356: 02:52:44
|
||||
2025-08-29 02:52:45,317 - main - INFO - 상태 저장 완료 #357: 02:52:45
|
||||
2025-08-29 02:52:46,322 - main - INFO - 상태 저장 완료 #358: 02:52:46
|
||||
2025-08-29 02:52:47,327 - main - INFO - 상태 저장 완료 #359: 02:52:47
|
||||
2025-08-29 02:52:48,332 - main - INFO - 상태 저장 완료 #360: 02:52:48
|
||||
2025-08-29 02:52:49,337 - main - INFO - 상태 저장 완료 #361: 02:52:49
|
||||
2025-08-29 02:52:53,288 - main - INFO - 상태 저장 완료 #362: 02:52:53
|
||||
2025-08-29 02:52:54,294 - main - INFO - 상태 저장 완료 #363: 02:52:54
|
||||
2025-08-29 02:52:55,297 - main - INFO - 상태 저장 완료 #364: 02:52:55
|
||||
2025-08-29 02:52:56,301 - main - INFO - 상태 저장 완료 #365: 02:52:56
|
||||
2025-08-29 02:52:57,306 - main - INFO - 상태 저장 완료 #366: 02:52:57
|
||||
2025-08-29 02:52:58,310 - main - INFO - 상태 저장 완료 #367: 02:52:58
|
||||
2025-08-29 02:52:59,314 - main - INFO - 상태 저장 완료 #368: 02:52:59
|
||||
2025-08-29 02:53:00,319 - main - INFO - 상태 저장 완료 #369: 02:53:00
|
||||
2025-08-29 02:53:01,323 - main - INFO - 상태 저장 완료 #370: 02:53:01
|
||||
2025-08-29 02:53:02,327 - main - INFO - 상태 저장 완료 #371: 02:53:02
|
||||
2025-08-29 02:53:03,331 - main - INFO - 상태 저장 완료 #372: 02:53:03
|
||||
2025-08-29 02:53:04,336 - main - INFO - 상태 저장 완료 #373: 02:53:04
|
||||
2025-08-29 02:53:05,340 - main - INFO - 상태 저장 완료 #374: 02:53:05
|
||||
2025-08-29 02:53:06,344 - main - INFO - 상태 저장 완료 #375: 02:53:06
|
||||
2025-08-29 02:53:07,348 - main - INFO - 상태 저장 완료 #376: 02:53:07
|
||||
2025-08-29 02:53:08,353 - main - INFO - 상태 저장 완료 #377: 02:53:08
|
||||
2025-08-29 02:53:09,357 - main - INFO - 상태 저장 완료 #378: 02:53:09
|
||||
2025-08-29 02:53:10,361 - main - INFO - 상태 저장 완료 #379: 02:53:10
|
||||
2025-08-29 02:53:11,365 - main - INFO - 상태 저장 완료 #380: 02:53:11
|
||||
2025-08-29 02:53:12,369 - main - INFO - 상태 저장 완료 #381: 02:53:12
|
||||
2025-08-29 02:53:16,321 - main - INFO - 상태 저장 완료 #382: 02:53:16
|
||||
2025-08-29 02:53:17,327 - main - INFO - 상태 저장 완료 #383: 02:53:17
|
||||
2025-08-29 02:53:18,332 - main - INFO - 상태 저장 완료 #384: 02:53:18
|
||||
2025-08-29 02:53:19,337 - main - INFO - 상태 저장 완료 #385: 02:53:19
|
||||
2025-08-29 02:53:20,341 - main - INFO - 상태 저장 완료 #386: 02:53:20
|
||||
2025-08-29 02:53:21,346 - main - INFO - 상태 저장 완료 #387: 02:53:21
|
||||
2025-08-29 02:53:22,350 - main - INFO - 상태 저장 완료 #388: 02:53:22
|
||||
2025-08-29 02:53:23,354 - main - INFO - 상태 저장 완료 #389: 02:53:23
|
||||
2025-08-29 02:53:24,359 - main - INFO - 상태 저장 완료 #390: 02:53:24
|
||||
2025-08-29 02:53:25,364 - main - INFO - 상태 저장 완료 #391: 02:53:25
|
||||
2025-08-29 02:53:26,367 - main - INFO - 상태 저장 완료 #392: 02:53:26
|
||||
2025-08-29 02:53:27,371 - main - INFO - 상태 저장 완료 #393: 02:53:27
|
||||
2025-08-29 02:53:28,375 - main - INFO - 상태 저장 완료 #394: 02:53:28
|
||||
2025-08-29 02:53:29,377 - main - INFO - 상태 저장 완료 #395: 02:53:29
|
||||
2025-08-29 02:53:30,381 - main - INFO - 상태 저장 완료 #396: 02:53:30
|
||||
2025-08-29 02:53:31,385 - main - INFO - 상태 저장 완료 #397: 02:53:31
|
||||
2025-08-29 02:53:32,389 - main - INFO - 상태 저장 완료 #398: 02:53:32
|
||||
2025-08-29 02:53:33,393 - main - INFO - 상태 저장 완료 #399: 02:53:33
|
||||
2025-08-29 02:53:34,398 - main - INFO - 상태 저장 완료 #400: 02:53:34
|
||||
2025-08-29 02:53:35,402 - main - INFO - 상태 저장 완료 #401: 02:53:35
|
||||
2025-08-29 02:53:39,355 - main - INFO - 상태 저장 완료 #402: 02:53:39
|
||||
2025-08-29 02:53:40,361 - main - INFO - 상태 저장 완료 #403: 02:53:40
|
||||
2025-08-29 02:53:41,365 - main - INFO - 상태 저장 완료 #404: 02:53:41
|
||||
2025-08-29 02:53:42,369 - main - INFO - 상태 저장 완료 #405: 02:53:42
|
||||
2025-08-29 02:53:43,374 - main - INFO - 상태 저장 완료 #406: 02:53:43
|
||||
2025-08-29 02:53:44,378 - main - INFO - 상태 저장 완료 #407: 02:53:44
|
||||
2025-08-29 02:53:45,382 - main - INFO - 상태 저장 완료 #408: 02:53:45
|
||||
2025-08-29 02:53:46,387 - main - INFO - 상태 저장 완료 #409: 02:53:46
|
||||
2025-08-29 02:53:47,392 - main - INFO - 상태 저장 완료 #410: 02:53:47
|
||||
2025-08-29 02:53:48,398 - main - INFO - 상태 저장 완료 #411: 02:53:48
|
||||
2025-08-29 02:53:49,402 - main - INFO - 상태 저장 완료 #412: 02:53:49
|
||||
2025-08-29 02:53:50,406 - main - INFO - 상태 저장 완료 #413: 02:53:50
|
||||
2025-08-29 02:53:51,411 - main - INFO - 상태 저장 완료 #414: 02:53:51
|
||||
2025-08-29 02:53:52,415 - main - INFO - 상태 저장 완료 #415: 02:53:52
|
||||
2025-08-29 02:53:53,419 - main - INFO - 상태 저장 완료 #416: 02:53:53
|
||||
2025-08-29 02:53:54,424 - main - INFO - 상태 저장 완료 #417: 02:53:54
|
||||
2025-08-29 02:53:55,428 - main - INFO - 상태 저장 완료 #418: 02:53:55
|
||||
2025-08-29 02:53:56,432 - main - INFO - 상태 저장 완료 #419: 02:53:56
|
||||
2025-08-29 02:53:57,436 - main - INFO - 상태 저장 완료 #420: 02:53:57
|
||||
2025-08-29 02:53:58,441 - main - INFO - 상태 저장 완료 #421: 02:53:58
|
||||
2025-08-29 02:54:02,388 - main - INFO - 상태 저장 완료 #422: 02:54:02
|
||||
2025-08-29 02:54:03,393 - main - INFO - 상태 저장 완료 #423: 02:54:03
|
||||
2025-08-29 02:54:04,398 - main - INFO - 상태 저장 완료 #424: 02:54:04
|
||||
2025-08-29 02:54:05,402 - main - INFO - 상태 저장 완료 #425: 02:54:05
|
||||
2025-08-29 02:54:06,406 - main - INFO - 상태 저장 완료 #426: 02:54:06
|
||||
2025-08-29 02:54:07,411 - main - INFO - 상태 저장 완료 #427: 02:54:07
|
||||
2025-08-29 02:54:08,415 - main - INFO - 상태 저장 완료 #428: 02:54:08
|
||||
2025-08-29 02:54:09,419 - main - INFO - 상태 저장 완료 #429: 02:54:09
|
||||
2025-08-29 02:54:10,424 - main - INFO - 상태 저장 완료 #430: 02:54:10
|
||||
2025-08-29 02:54:11,428 - main - INFO - 상태 저장 완료 #431: 02:54:11
|
||||
2025-08-29 02:54:12,433 - main - INFO - 상태 저장 완료 #432: 02:54:12
|
||||
2025-08-29 02:54:13,437 - main - INFO - 상태 저장 완료 #433: 02:54:13
|
||||
2025-08-29 02:54:14,442 - main - INFO - 상태 저장 완료 #434: 02:54:14
|
||||
2025-08-29 02:54:15,446 - main - INFO - 상태 저장 완료 #435: 02:54:15
|
||||
2025-08-29 02:54:16,450 - main - INFO - 상태 저장 완료 #436: 02:54:16
|
||||
2025-08-29 02:54:17,453 - main - INFO - 상태 저장 완료 #437: 02:54:17
|
||||
2025-08-29 02:54:18,457 - main - INFO - 상태 저장 완료 #438: 02:54:18
|
||||
2025-08-29 02:54:19,461 - main - INFO - 상태 저장 완료 #439: 02:54:19
|
||||
2025-08-29 02:54:20,465 - main - INFO - 상태 저장 완료 #440: 02:54:20
|
||||
2025-08-29 02:54:21,470 - main - INFO - 상태 저장 완료 #441: 02:54:21
|
||||
2025-08-29 02:54:25,425 - main - INFO - 상태 저장 완료 #442: 02:54:25
|
||||
2025-08-29 02:54:26,430 - main - INFO - 상태 저장 완료 #443: 02:54:26
|
||||
2025-08-29 02:54:27,434 - main - INFO - 상태 저장 완료 #444: 02:54:27
|
||||
2025-08-29 02:54:28,438 - main - INFO - 상태 저장 완료 #445: 02:54:28
|
||||
2025-08-29 02:54:29,441 - main - INFO - 상태 저장 완료 #446: 02:54:29
|
||||
2025-08-29 02:54:30,448 - main - INFO - 상태 저장 완료 #447: 02:54:30
|
||||
2025-08-29 02:54:31,452 - main - INFO - 상태 저장 완료 #448: 02:54:31
|
||||
2025-08-29 02:54:32,457 - main - INFO - 상태 저장 완료 #449: 02:54:32
|
||||
2025-08-29 02:54:33,461 - main - INFO - 상태 저장 완료 #450: 02:54:33
|
||||
2025-08-29 02:54:34,466 - main - INFO - 상태 저장 완료 #451: 02:54:34
|
||||
2025-08-29 02:54:35,470 - main - INFO - 상태 저장 완료 #452: 02:54:35
|
||||
2025-08-29 02:54:36,475 - main - INFO - 상태 저장 완료 #453: 02:54:36
|
||||
2025-08-29 02:54:37,479 - main - INFO - 상태 저장 완료 #454: 02:54:37
|
||||
2025-08-29 02:54:38,484 - main - INFO - 상태 저장 완료 #455: 02:54:38
|
||||
2025-08-29 02:54:39,488 - main - INFO - 상태 저장 완료 #456: 02:54:39
|
||||
2025-08-29 02:54:40,492 - main - INFO - 상태 저장 완료 #457: 02:54:40
|
||||
2025-08-29 02:54:41,496 - main - INFO - 상태 저장 완료 #458: 02:54:41
|
||||
2025-08-29 02:54:42,501 - main - INFO - 상태 저장 완료 #459: 02:54:42
|
||||
2025-08-29 02:54:43,505 - main - INFO - 상태 저장 완료 #460: 02:54:43
|
||||
2025-08-29 02:54:44,509 - main - INFO - 상태 저장 완료 #461: 02:54:44
|
||||
2025-08-29 02:54:48,455 - main - INFO - 상태 저장 완료 #462: 02:54:48
|
||||
2025-08-29 02:54:49,459 - main - INFO - 상태 저장 완료 #463: 02:54:49
|
||||
2025-08-29 02:54:50,464 - main - INFO - 상태 저장 완료 #464: 02:54:50
|
||||
2025-08-29 02:54:51,468 - main - INFO - 상태 저장 완료 #465: 02:54:51
|
||||
2025-08-29 02:54:52,472 - main - INFO - 상태 저장 완료 #466: 02:54:52
|
||||
2025-08-29 02:54:53,477 - main - INFO - 상태 저장 완료 #467: 02:54:53
|
||||
2025-08-29 02:54:54,482 - main - INFO - 상태 저장 완료 #468: 02:54:54
|
||||
2025-08-29 02:54:55,486 - main - INFO - 상태 저장 완료 #469: 02:54:55
|
||||
2025-08-29 02:54:56,491 - main - INFO - 상태 저장 완료 #470: 02:54:56
|
||||
2025-08-29 02:54:57,495 - main - INFO - 상태 저장 완료 #471: 02:54:57
|
||||
2025-08-29 02:54:58,501 - main - INFO - 상태 저장 완료 #472: 02:54:58
|
||||
2025-08-29 02:54:59,505 - main - INFO - 상태 저장 완료 #473: 02:54:59
|
||||
2025-08-29 02:55:00,509 - main - INFO - 상태 저장 완료 #474: 02:55:00
|
||||
2025-08-29 02:55:01,513 - main - INFO - 상태 저장 완료 #475: 02:55:01
|
||||
2025-08-29 02:55:02,515 - main - INFO - 상태 저장 완료 #476: 02:55:02
|
||||
2025-08-29 02:55:03,519 - main - INFO - 상태 저장 완료 #477: 02:55:03
|
||||
2025-08-29 02:55:04,523 - main - INFO - 상태 저장 완료 #478: 02:55:04
|
||||
2025-08-29 02:55:05,527 - main - INFO - 상태 저장 완료 #479: 02:55:05
|
||||
2025-08-29 02:55:06,531 - main - INFO - 상태 저장 완료 #480: 02:55:06
|
||||
2025-08-29 02:55:07,536 - main - INFO - 상태 저장 완료 #481: 02:55:07
|
||||
2025-08-29 02:55:11,489 - main - INFO - 상태 저장 완료 #482: 02:55:11
|
||||
2025-08-29 02:55:12,495 - main - INFO - 상태 저장 완료 #483: 02:55:12
|
||||
2025-08-29 02:55:13,498 - main - INFO - 상태 저장 완료 #484: 02:55:13
|
||||
2025-08-29 02:55:14,502 - main - INFO - 상태 저장 완료 #485: 02:55:14
|
||||
2025-08-29 02:55:15,506 - main - INFO - 상태 저장 완료 #486: 02:55:15
|
||||
2025-08-29 02:55:16,510 - main - INFO - 상태 저장 완료 #487: 02:55:16
|
||||
2025-08-29 02:55:17,514 - main - INFO - 상태 저장 완료 #488: 02:55:17
|
||||
2025-08-29 02:55:18,519 - main - INFO - 상태 저장 완료 #489: 02:55:18
|
||||
2025-08-29 02:55:19,523 - main - INFO - 상태 저장 완료 #490: 02:55:19
|
||||
2025-08-29 02:55:20,527 - main - INFO - 상태 저장 완료 #491: 02:55:20
|
||||
2025-08-29 02:55:21,531 - main - INFO - 상태 저장 완료 #492: 02:55:21
|
||||
2025-08-29 02:55:22,536 - main - INFO - 상태 저장 완료 #493: 02:55:22
|
||||
2025-08-29 02:55:23,540 - main - INFO - 상태 저장 완료 #494: 02:55:23
|
||||
2025-08-29 02:55:24,544 - main - INFO - 상태 저장 완료 #495: 02:55:24
|
||||
2025-08-29 02:55:25,549 - main - INFO - 상태 저장 완료 #496: 02:55:25
|
||||
2025-08-29 02:55:26,553 - main - INFO - 상태 저장 완료 #497: 02:55:26
|
||||
2025-08-29 02:55:27,557 - main - INFO - 상태 저장 완료 #498: 02:55:27
|
||||
2025-08-29 02:55:28,561 - main - INFO - 상태 저장 완료 #499: 02:55:28
|
||||
2025-08-29 02:55:29,565 - main - INFO - 상태 저장 완료 #500: 02:55:29
|
||||
2025-08-29 02:55:30,569 - main - INFO - 상태 저장 완료 #501: 02:55:30
|
||||
2025-08-29 02:55:34,523 - main - INFO - 상태 저장 완료 #502: 02:55:34
|
||||
2025-08-29 02:55:35,528 - main - INFO - 상태 저장 완료 #503: 02:55:35
|
||||
2025-08-29 02:55:36,533 - main - INFO - 상태 저장 완료 #504: 02:55:36
|
||||
2025-08-29 02:55:37,537 - main - INFO - 상태 저장 완료 #505: 02:55:37
|
||||
2025-08-29 02:55:38,542 - main - INFO - 상태 저장 완료 #506: 02:55:38
|
||||
2025-08-29 02:55:39,547 - main - INFO - 상태 저장 완료 #507: 02:55:39
|
||||
2025-08-29 02:55:40,551 - main - INFO - 상태 저장 완료 #508: 02:55:40
|
||||
2025-08-29 02:55:41,556 - main - INFO - 상태 저장 완료 #509: 02:55:41
|
||||
2025-08-29 02:55:42,560 - main - INFO - 상태 저장 완료 #510: 02:55:42
|
||||
2025-08-29 02:55:43,564 - main - INFO - 상태 저장 완료 #511: 02:55:43
|
||||
2025-08-29 02:55:44,572 - main - INFO - 상태 저장 완료 #512: 02:55:44
|
||||
2025-08-29 02:55:45,576 - main - INFO - 상태 저장 완료 #513: 02:55:45
|
||||
2025-08-29 02:55:46,580 - main - INFO - 상태 저장 완료 #514: 02:55:46
|
||||
2025-08-29 02:55:47,584 - main - INFO - 상태 저장 완료 #515: 02:55:47
|
||||
2025-08-29 02:55:48,589 - main - INFO - 상태 저장 완료 #516: 02:55:48
|
||||
2025-08-29 02:55:49,597 - main - INFO - 상태 저장 완료 #517: 02:55:49
|
||||
2025-08-29 02:55:50,600 - main - INFO - 상태 저장 완료 #518: 02:55:50
|
||||
2025-08-29 02:55:51,603 - main - INFO - 상태 저장 완료 #519: 02:55:51
|
||||
2025-08-29 02:55:52,608 - main - INFO - 상태 저장 완료 #520: 02:55:52
|
||||
2025-08-29 02:55:53,612 - main - INFO - 상태 저장 완료 #521: 02:55:53
|
||||
2025-08-29 02:55:57,558 - main - INFO - 상태 저장 완료 #522: 02:55:57
|
||||
2025-08-29 02:55:58,562 - main - INFO - 상태 저장 완료 #523: 02:55:58
|
||||
2025-08-29 02:55:59,566 - main - INFO - 상태 저장 완료 #524: 02:55:59
|
||||
2025-08-29 02:56:00,570 - main - INFO - 상태 저장 완료 #525: 02:56:00
|
||||
2025-08-29 02:56:01,574 - main - INFO - 상태 저장 완료 #526: 02:56:01
|
||||
2025-08-29 02:56:02,578 - main - INFO - 상태 저장 완료 #527: 02:56:02
|
||||
2025-08-29 02:56:03,582 - main - INFO - 상태 저장 완료 #528: 02:56:03
|
||||
2025-08-29 02:56:04,586 - main - INFO - 상태 저장 완료 #529: 02:56:04
|
||||
2025-08-29 02:56:05,590 - main - INFO - 상태 저장 완료 #530: 02:56:05
|
||||
2025-08-29 02:56:06,595 - main - INFO - 상태 저장 완료 #531: 02:56:06
|
||||
2025-08-29 02:56:07,599 - main - INFO - 상태 저장 완료 #532: 02:56:07
|
||||
2025-08-29 02:56:08,604 - main - INFO - 상태 저장 완료 #533: 02:56:08
|
||||
2025-08-29 02:56:09,608 - main - INFO - 상태 저장 완료 #534: 02:56:09
|
||||
2025-08-29 02:56:10,612 - main - INFO - 상태 저장 완료 #535: 02:56:10
|
||||
2025-08-29 02:56:11,616 - main - INFO - 상태 저장 완료 #536: 02:56:11
|
||||
2025-08-29 02:56:12,620 - main - INFO - 상태 저장 완료 #537: 02:56:12
|
||||
2025-08-29 02:56:13,625 - main - INFO - 상태 저장 완료 #538: 02:56:13
|
||||
2025-08-29 02:56:14,629 - main - INFO - 상태 저장 완료 #539: 02:56:14
|
||||
2025-08-29 02:56:15,633 - main - INFO - 상태 저장 완료 #540: 02:56:15
|
||||
2025-08-29 02:56:16,637 - main - INFO - 상태 저장 완료 #541: 02:56:16
|
||||
2025-08-29 02:56:20,593 - main - INFO - 상태 저장 완료 #542: 02:56:20
|
||||
2025-08-29 02:56:21,599 - main - INFO - 상태 저장 완료 #543: 02:56:21
|
||||
2025-08-29 02:56:22,604 - main - INFO - 상태 저장 완료 #544: 02:56:22
|
||||
2025-08-29 02:56:23,608 - main - INFO - 상태 저장 완료 #545: 02:56:23
|
||||
2025-08-29 02:56:24,613 - main - INFO - 상태 저장 완료 #546: 02:56:24
|
||||
2025-08-29 02:56:25,617 - main - INFO - 상태 저장 완료 #547: 02:56:25
|
||||
2025-08-29 02:56:26,622 - main - INFO - 상태 저장 완료 #548: 02:56:26
|
||||
2025-08-29 02:56:27,626 - main - INFO - 상태 저장 완료 #549: 02:56:27
|
||||
2025-08-29 02:56:28,630 - main - INFO - 상태 저장 완료 #550: 02:56:28
|
||||
2025-08-29 02:56:29,635 - main - INFO - 상태 저장 완료 #551: 02:56:29
|
||||
2025-08-29 02:56:30,639 - main - INFO - 상태 저장 완료 #552: 02:56:30
|
||||
2025-08-29 02:56:31,644 - main - INFO - 상태 저장 완료 #553: 02:56:31
|
||||
2025-08-29 02:56:32,649 - main - INFO - 상태 저장 완료 #554: 02:56:32
|
||||
2025-08-29 02:56:33,654 - main - INFO - 상태 저장 완료 #555: 02:56:33
|
||||
2025-08-29 02:56:34,659 - main - INFO - 상태 저장 완료 #556: 02:56:34
|
||||
2025-08-29 02:56:35,663 - main - INFO - 상태 저장 완료 #557: 02:56:35
|
||||
2025-08-29 02:56:36,667 - main - INFO - 상태 저장 완료 #558: 02:56:36
|
||||
2025-08-29 02:56:37,670 - main - INFO - 상태 저장 완료 #559: 02:56:37
|
||||
2025-08-29 02:56:38,675 - main - INFO - 상태 저장 완료 #560: 02:56:38
|
||||
2025-08-29 02:56:39,679 - main - INFO - 상태 저장 완료 #561: 02:56:39
|
||||
2025-08-29 02:56:43,627 - main - INFO - 상태 저장 완료 #562: 02:56:43
|
||||
2025-08-29 02:56:44,631 - main - INFO - 상태 저장 완료 #563: 02:56:44
|
||||
2025-08-29 02:56:45,633 - main - INFO - 상태 저장 완료 #564: 02:56:45
|
||||
2025-08-29 02:56:46,637 - main - INFO - 상태 저장 완료 #565: 02:56:46
|
||||
2025-08-29 02:56:47,642 - main - INFO - 상태 저장 완료 #566: 02:56:47
|
||||
2025-08-29 02:56:48,646 - main - INFO - 상태 저장 완료 #567: 02:56:48
|
||||
2025-08-29 02:56:49,650 - main - INFO - 상태 저장 완료 #568: 02:56:49
|
||||
2025-08-29 02:56:50,655 - main - INFO - 상태 저장 완료 #569: 02:56:50
|
||||
2025-08-29 02:56:51,659 - main - INFO - 상태 저장 완료 #570: 02:56:51
|
||||
2025-08-29 02:56:52,663 - main - INFO - 상태 저장 완료 #571: 02:56:52
|
||||
2025-08-29 02:56:53,667 - main - INFO - 상태 저장 완료 #572: 02:56:53
|
||||
2025-08-29 02:56:54,671 - main - INFO - 상태 저장 완료 #573: 02:56:54
|
||||
2025-08-29 02:56:55,676 - main - INFO - 상태 저장 완료 #574: 02:56:55
|
||||
2025-08-29 02:56:56,679 - main - INFO - 상태 저장 완료 #575: 02:56:56
|
||||
2025-08-29 02:56:57,683 - main - INFO - 상태 저장 완료 #576: 02:56:57
|
||||
2025-08-29 02:56:58,687 - main - INFO - 상태 저장 완료 #577: 02:56:58
|
||||
2025-08-29 02:56:59,691 - main - INFO - 상태 저장 완료 #578: 02:56:59
|
||||
2025-08-29 02:57:00,697 - main - INFO - 상태 저장 완료 #579: 02:57:00
|
||||
2025-08-29 02:57:01,701 - main - INFO - 상태 저장 완료 #580: 02:57:01
|
||||
2025-08-29 02:57:02,705 - main - INFO - 상태 저장 완료 #581: 02:57:02
|
||||
2025-08-29 02:57:06,661 - main - INFO - 상태 저장 완료 #582: 02:57:06
|
||||
2025-08-29 02:57:07,665 - main - INFO - 상태 저장 완료 #583: 02:57:07
|
||||
2025-08-29 02:57:08,670 - main - INFO - 상태 저장 완료 #584: 02:57:08
|
||||
2025-08-29 02:57:09,674 - main - INFO - 상태 저장 완료 #585: 02:57:09
|
||||
2025-08-29 02:57:10,679 - main - INFO - 상태 저장 완료 #586: 02:57:10
|
||||
2025-08-29 02:57:11,684 - main - INFO - 상태 저장 완료 #587: 02:57:11
|
||||
2025-08-29 02:57:12,689 - main - INFO - 상태 저장 완료 #588: 02:57:12
|
||||
2025-08-29 02:57:13,693 - main - INFO - 상태 저장 완료 #589: 02:57:13
|
||||
2025-08-29 02:57:14,697 - main - INFO - 상태 저장 완료 #590: 02:57:14
|
||||
2025-08-29 02:57:15,702 - main - INFO - 상태 저장 완료 #591: 02:57:15
|
||||
2025-08-29 02:57:16,706 - main - INFO - 상태 저장 완료 #592: 02:57:16
|
||||
2025-08-29 02:57:17,711 - main - INFO - 상태 저장 완료 #593: 02:57:17
|
||||
2025-08-29 02:57:18,716 - main - INFO - 상태 저장 완료 #594: 02:57:18
|
||||
2025-08-29 02:57:19,719 - main - INFO - 상태 저장 완료 #595: 02:57:19
|
||||
2025-08-29 02:57:20,721 - main - INFO - 상태 저장 완료 #596: 02:57:20
|
||||
2025-08-29 02:57:21,723 - main - INFO - 상태 저장 완료 #597: 02:57:21
|
||||
2025-08-29 02:57:22,728 - main - INFO - 상태 저장 완료 #598: 02:57:22
|
||||
2025-08-29 02:57:23,732 - main - INFO - 상태 저장 완료 #599: 02:57:23
|
||||
2025-08-29 02:57:24,736 - main - INFO - 상태 저장 완료 #600: 02:57:24
|
||||
2025-08-29 21:33:05,754 - uvicorn.error - INFO - Uvicorn running on http://0.0.0.0:8008 (Press CTRL+C to quit)
|
||||
INFO: 127.0.0.1:37912 - "GET /api/v1/health HTTP/1.1" 404 Not Found
|
||||
INFO: 127.0.0.1:37916 - "GET /api/v1/health HTTP/1.1" 404 Not Found
|
||||
INFO: 192.168.0.119:54788 - "GET /health HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54910 - "GET /api/v1/model HTTP/1.1" 200 OK
|
||||
2025-08-29 21:34:22,942 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
|
||||
INFO: 192.168.0.119:54911 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54959 - "GET /api/v1/model HTTP/1.1" 200 OK
|
||||
2025-08-29 21:34:33,468 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
|
||||
INFO: 192.168.0.119:54960 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:55054 - "GET /api/v1/model HTTP/1.1" 200 OK
|
||||
2025-08-29 21:34:54,879 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
|
||||
INFO: 192.168.0.119:55055 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:55078 - "GET /api/v1/model HTTP/1.1" 200 OK
|
||||
2025-08-29 21:35:00,881 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
|
||||
INFO: 192.168.0.119:55091 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:55106 - "GET /api/v1/model HTTP/1.1" 200 OK
|
||||
2025-08-29 21:35:06,470 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
|
||||
INFO: 192.168.0.119:55107 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:55137 - "GET /api/v1/model HTTP/1.1" 200 OK
|
||||
2025-08-29 21:35:15,091 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
|
||||
INFO: 192.168.0.119:55138 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:55172 - "GET /api/v1/model HTTP/1.1" 200 OK
|
||||
2025-08-29 21:35:24,091 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
|
||||
INFO: 192.168.0.119:55173 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:55212 - "GET /api/v1/model HTTP/1.1" 200 OK
|
||||
2025-08-29 21:35:34,140 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
|
||||
INFO: 192.168.0.119:55213 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:55228 - "GET /api/v1/model HTTP/1.1" 200 OK
|
||||
2025-08-29 21:35:39,477 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
|
||||
INFO: 192.168.0.119:55229 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:55262 - "GET /api/v1/model HTTP/1.1" 200 OK
|
||||
2025-08-29 21:35:45,252 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
|
||||
INFO: 192.168.0.119:55263 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:55478 - "GET /api/v1/model HTTP/1.1" 200 OK
|
||||
2025-08-29 21:36:44,752 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
|
||||
INFO: 192.168.0.119:55479 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:55521 - "GET /api/v1/model HTTP/1.1" 200 OK
|
||||
2025-08-29 21:36:56,862 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
|
||||
INFO: 192.168.0.119:55526 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:55551 - "GET /api/v1/model HTTP/1.1" 200 OK
|
||||
2025-08-29 21:37:03,465 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
|
||||
INFO: 192.168.0.119:55553 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:55575 - "GET /api/v1/model HTTP/1.1" 200 OK
|
||||
2025-08-29 21:37:09,535 - app.models.simple_lama - INFO - 실제 SimpleLama 모델로 인페인팅 수행
|
||||
INFO: 192.168.0.119:55576 - "POST /api/v1/inpaint HTTP/1.1" 200 OK
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
1338234
|
||||
35210
|
||||
|
|
|
|||
|
|
@ -1,39 +1,111 @@
|
|||
INFO: Started server process [1338258]
|
||||
INFO: Started server process [35583]
|
||||
INFO: Waiting for application startup.
|
||||
Fan control not available
|
||||
INFO: Application startup complete.
|
||||
INFO: Uvicorn running on http://0.0.0.0:8888 (Press CTRL+C to quit)
|
||||
INFO: 127.0.0.1:51772 - "GET /api/simple HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:37588 - "GET /api/status HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:56018 - "GET /api/status HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:56030 - "GET / HTTP/1.1" 200 OK
|
||||
INFO: ('127.0.0.1', 57392) - "WebSocket /ws" [accepted]
|
||||
INFO: 192.168.0.119:54536 - "GET / HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54536 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54537 - "GET /api/performance-stats HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54536 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54537 - "GET /favicon.ico HTTP/1.1" 404 Not Found
|
||||
INFO: 192.168.0.119:54544 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:60704 - "GET /api/simple HTTP/1.1" 200 OK
|
||||
INFO: ('192.168.0.119', 54527) - "WebSocket /ws" [accepted]
|
||||
INFO: connection open
|
||||
INFO: 127.0.0.1:56030 - "GET /favicon.ico HTTP/1.1" 404 Not Found
|
||||
INFO: ('127.0.0.1', 40458) - "WebSocket /ws" [accepted]
|
||||
INFO: 192.168.0.119:54557 - "GET /favicon.ico HTTP/1.1" 404 Not Found
|
||||
INFO: 192.168.0.119:54544 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54536 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54538 - "GET / HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54584 - "GET /favicon.ico HTTP/1.1" 404 Not Found
|
||||
INFO: 192.168.0.119:54643 - "GET / HTTP/1.1" 200 OK
|
||||
INFO: ('192.168.0.119', 54586) - "WebSocket /ws" [accepted]
|
||||
INFO: connection closed
|
||||
INFO: 192.168.0.119:54643 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54644 - "GET /api/performance-stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48206 - "GET / HTTP/1.1" 200 OK
|
||||
INFO: connection closed
|
||||
INFO: 192.168.0.119:54722 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54723 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54735 - "GET /favicon.ico HTTP/1.1" 404 Not Found
|
||||
INFO: ('192.168.0.119', 54717) - "WebSocket /ws" [accepted]
|
||||
INFO: ('192.168.0.119', 54802) - "WebSocket /ws" [accepted]
|
||||
INFO: connection open
|
||||
INFO: connection open
|
||||
INFO: 192.168.0.119:54644 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48206 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48234 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48218 - "GET /api/performance-stats HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54722 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54723 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54643 - "GET /api/performance-stats HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54644 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48206 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54722 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48218 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48234 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:42808 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:42822 - "GET /favicon.ico HTTP/1.1" 404 Not Found
|
||||
INFO: 192.168.0.119:54834 - "GET /favicon.ico HTTP/1.1" 404 Not Found
|
||||
INFO: ('127.0.0.1', 42798) - "WebSocket /ws" [accepted]
|
||||
INFO: 192.168.0.119:54643 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48206 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54644 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: connection open
|
||||
INFO: 192.168.0.119:54900 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:42344 - "GET /api/performance-stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48234 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54722 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48218 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: connection closed
|
||||
브로드캐스트 오류: list.remove(x): x not in list
|
||||
INFO: ('127.0.0.1', 41220) - "WebSocket /ws" [accepted]
|
||||
INFO: connection open
|
||||
INFO: connection closed
|
||||
브로드캐스트 오류: list.remove(x): x not in list
|
||||
INFO: ('127.0.0.1', 43988) - "WebSocket /ws" [accepted]
|
||||
INFO: connection open
|
||||
INFO: connection closed
|
||||
브로드캐스트 오류: list.remove(x): x not in list
|
||||
INFO: ('127.0.0.1', 50666) - "WebSocket /ws" [accepted]
|
||||
INFO: connection open
|
||||
INFO: connection closed
|
||||
브로드캐스트 오류: list.remove(x): x not in list
|
||||
INFO: ('127.0.0.1', 44104) - "WebSocket /ws" [accepted]
|
||||
INFO: connection open
|
||||
INFO: connection closed
|
||||
INFO: ('127.0.0.1', 56574) - "WebSocket /ws" [accepted]
|
||||
INFO: connection open
|
||||
INFO: connection closed
|
||||
브로드캐스트 오류: list.remove(x): x not in list
|
||||
INFO: ('127.0.0.1', 39494) - "WebSocket /ws" [accepted]
|
||||
INFO: connection open
|
||||
INFO: 192.168.0.119:54643 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:42822 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54644 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48206 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:42808 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54900 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:42344 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48234 - "GET /api/performance-stats HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54722 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48218 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: connection closed
|
||||
INFO: ('192.168.0.119', 54983) - "WebSocket /ws" [accepted]
|
||||
INFO: 192.168.0.119:54643 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:42822 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54644 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48206 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:42808 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: connection open
|
||||
INFO: 192.168.0.119:55051 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:55129 - "GET /api/performance-stats HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54900 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:42344 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54722 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: ('127.0.0.1', 38388) - "WebSocket /ws" [accepted]
|
||||
INFO: 127.0.0.1:48218 - "GET /api/performance-stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48234 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54643 - "GET /api/performance-stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:42822 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54644 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48206 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:42808 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: connection open
|
||||
INFO: 192.168.0.119:55051 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:55129 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54900 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:42344 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54722 - "GET /api/performance-stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48218 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48234 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54643 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:42822 - "GET /api/performance-stats HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54644 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:48206 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:42808 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:55051 - "GET /api/system-alerts HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:55129 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54900 - "GET /api/performance-stats HTTP/1.1" 200 OK
|
||||
INFO: 127.0.0.1:42344 - "GET /api/logs?lines=50 HTTP/1.1" 200 OK
|
||||
INFO: 192.168.0.119:54722 - "GET /api/model-usage-stats HTTP/1.1" 200 OK
|
||||
브로드캐스트 오류: list.remove(x): x not in list
|
||||
INFO: ('192.168.0.119', 55354) - "WebSocket /ws" [accepted]
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
1338258
|
||||
35583
|
||||
|
|
|
|||
52
main.py
52
main.py
|
|
@ -15,15 +15,53 @@ import uvicorn
|
|||
|
||||
from app.core.config import settings
|
||||
from app.core.worker_manager import worker_manager
|
||||
from app.core.session_pool import session_pool
|
||||
from app.core.session_pool import SessionPool, session_pool
|
||||
from app.api.endpoints import router
|
||||
from app.monitoring.dashboard import monitor_app
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
import logging.handlers
|
||||
import os
|
||||
|
||||
# 로그 디렉토리 생성
|
||||
log_dir = "logs"
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
|
||||
# 로그 회전 설정: 최대 10MB, 7일 보관
|
||||
rotating_handler = logging.handlers.TimedRotatingFileHandler(
|
||||
filename=os.path.join(log_dir, "main.log"),
|
||||
when="D", # 일별 회전
|
||||
interval=1, # 1일마다
|
||||
backupCount=7, # 7일 보관
|
||||
encoding="utf-8"
|
||||
)
|
||||
rotating_handler.setFormatter(logging.Formatter(
|
||||
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
))
|
||||
|
||||
# 파일 크기 제한 핸들러 추가 (10MB)
|
||||
size_handler = logging.handlers.RotatingFileHandler(
|
||||
filename=os.path.join(log_dir, "main_size.log"),
|
||||
maxBytes=10*1024*1024, # 10MB
|
||||
backupCount=5, # 최대 5개 파일
|
||||
encoding="utf-8"
|
||||
)
|
||||
size_handler.setFormatter(logging.Formatter(
|
||||
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
))
|
||||
|
||||
# 콘솔 핸들러
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(logging.Formatter(
|
||||
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
))
|
||||
|
||||
# 루트 로거 설정
|
||||
root_logger = logging.getLogger()
|
||||
root_logger.setLevel(logging.INFO)
|
||||
root_logger.addHandler(rotating_handler)
|
||||
root_logger.addHandler(size_handler)
|
||||
root_logger.addHandler(console_handler)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -152,7 +190,7 @@ async def save_status_periodically():
|
|||
with open("status.json", "w") as f:
|
||||
json.dump(status, f, indent=2)
|
||||
|
||||
logger.info(f"상태 저장 완료 #{iteration}: {time.strftime('%H:%M:%S')}")
|
||||
logger.debug(f"상태 저장 완료 #{iteration}: {time.strftime('%H:%M:%S')}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"상태 저장 실패 #{iteration}: {e}")
|
||||
|
|
@ -178,7 +216,9 @@ async def lifespan(app: FastAPI):
|
|||
logger.info("✅ 상태 저장 백그라운드 작업 생성 완료")
|
||||
|
||||
try:
|
||||
# 세션 풀 초기화
|
||||
# ONNX Runtime과 RemBG가 자동으로 CUDA 감지
|
||||
logger.info("🚀 세션 풀 초기화 (CUDA 자동 감지)")
|
||||
|
||||
await session_pool.initialize()
|
||||
logger.info("✅ 세션 풀 초기화 완료")
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -2,7 +2,7 @@ fastapi==0.104.1
|
|||
uvicorn[standard]==0.24.0
|
||||
python-multipart==0.0.6
|
||||
pillow==10.0.1
|
||||
numpy==1.24.3
|
||||
numpy==1.24.4
|
||||
opencv-python==4.8.1.78
|
||||
pydantic-settings==2.8.1
|
||||
psutil==5.9.6
|
||||
|
|
@ -10,11 +10,11 @@ requests==2.31.0
|
|||
|
||||
# PyTorch - Jetson Xavier (ARM64) 지원
|
||||
# Jetson의 경우 torch==2.0.1+nv23.11-torch2.0.0 사용 권장
|
||||
torch==2.0.1+cu118
|
||||
torchvision==0.15.2+cu118
|
||||
#torch==2.0.1+cu118
|
||||
#torchvision==0.15.2+cu118
|
||||
|
||||
# TensorRT 및 CUDA 관련
|
||||
tensorrt==8.6.1
|
||||
#tensorrt==8.6.1
|
||||
pycuda==2022.2.2
|
||||
|
||||
# 인페인팅 모델들
|
||||
|
|
@ -31,5 +31,7 @@ pynvml==11.5.0
|
|||
nvidia-ml-py3==7.352.0
|
||||
|
||||
# 추가 최적화 패키지들
|
||||
onnxruntime-gpu==1.16.3
|
||||
# ONNX Runtime - Jetson Xavier용 특별 설치 필요 (install_deps.sh 참조)
|
||||
# onnxruntime-gpu==1.17.0 # Jetson용 휠 파일로 수동 설치
|
||||
#onnxruntime==1.19.2 # CPU 전용 (Jetson에서는 GPU 휠로 교체됨)
|
||||
tensorflow-gpu==2.13.0
|
||||
|
|
|
|||
|
|
@ -0,0 +1,271 @@
|
|||
#!/bin/bash
|
||||
|
||||
# 인페인팅 서버 자동 설치 스크립트
|
||||
# 플랫폼 자동 감지 및 최적 설치 방법 선택
|
||||
# Usage: ./install.sh
|
||||
|
||||
set -e
|
||||
|
||||
# 색상 코드
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
MAGENTA='\033[0;35m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 로고 출력
|
||||
print_logo() {
|
||||
echo -e "${CYAN}"
|
||||
echo " ██║ ███╗ ██╗██████╗ █████╗ ██╗███╗ ██╗████████╗"
|
||||
echo " ██║ ████╗ ██║██╔══██╗██╔══██╗██║████╗ ██║╚══██╔══╝"
|
||||
echo " ██║ ██╔██╗ ██║██████╔╝███████║██║██╔██╗ ██║ ██║ "
|
||||
echo " ██║ ██║╚██╗██║██╔═══╝ ██╔══██║██║██║╚██╗██║ ██║ "
|
||||
echo " ██║ ██║ ╚████║██║ ██║ ██║██║██║ ╚████║ ██║ "
|
||||
echo " ╚═╝ ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═╝ "
|
||||
echo ""
|
||||
echo " 🎨 AI 인페인팅 서버 - 자동 설치 도구"
|
||||
echo -e "${NC}"
|
||||
}
|
||||
|
||||
# 로그 함수들
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
log_step() {
|
||||
echo -e "${CYAN}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
log_platform() {
|
||||
echo -e "${MAGENTA}[PLATFORM]${NC} $1"
|
||||
}
|
||||
|
||||
# 플랫폼 감지
|
||||
detect_platform() {
|
||||
local arch=$(uname -m)
|
||||
local os_info=$(uname -a)
|
||||
|
||||
log_step "플랫폼 감지 중..."
|
||||
|
||||
if [[ "$arch" == "aarch64" ]] && echo "$os_info" | grep -q "tegra"; then
|
||||
PLATFORM="jetson"
|
||||
PLATFORM_NAME="NVIDIA Jetson Xavier"
|
||||
INSTALL_SCRIPT="setup_jetson.sh"
|
||||
log_platform "🚀 $PLATFORM_NAME 감지됨"
|
||||
elif [[ "$arch" == "x86_64" ]] || [[ "$arch" == "amd64" ]]; then
|
||||
PLATFORM="x86"
|
||||
PLATFORM_NAME="x86-64 Desktop"
|
||||
INSTALL_SCRIPT="setup_x86.sh"
|
||||
log_platform "🖥️ $PLATFORM_NAME 감지됨"
|
||||
elif [[ "$arch" == "aarch64" ]]; then
|
||||
PLATFORM="arm64"
|
||||
PLATFORM_NAME="ARM64 (Generic)"
|
||||
INSTALL_SCRIPT="setup_jetson.sh"
|
||||
log_platform "💻 $PLATFORM_NAME 감지됨 (Jetson 설치 방법 사용)"
|
||||
else
|
||||
log_error "지원하지 않는 아키텍처: $arch"
|
||||
echo "지원 플랫폼:"
|
||||
echo " - x86-64 (Intel/AMD)"
|
||||
echo " - ARM64 (NVIDIA Jetson Xavier)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "감지된 정보:"
|
||||
echo " 아키텍처: $arch"
|
||||
echo " 플랫폼: $PLATFORM_NAME"
|
||||
echo " 설치 스크립트: $INSTALL_SCRIPT"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# GPU 정보 표시
|
||||
show_gpu_info() {
|
||||
log_step "GPU 정보 확인 중..."
|
||||
|
||||
if command -v nvidia-smi >/dev/null 2>&1; then
|
||||
echo "=== NVIDIA GPU 정보 ==="
|
||||
nvidia-smi --query-gpu=name,driver_version,memory.total --format=csv,noheader 2>/dev/null | while read line; do
|
||||
echo " GPU: $line"
|
||||
done
|
||||
echo ""
|
||||
else
|
||||
log_warning "NVIDIA GPU 또는 드라이버가 설치되지 않았습니다."
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# 시스템 요구사항 확인
|
||||
check_requirements() {
|
||||
log_step "시스템 요구사항 확인 중..."
|
||||
|
||||
local errors=0
|
||||
|
||||
# Python 3.8+ 확인
|
||||
if command -v python3 >/dev/null 2>&1; then
|
||||
local python_version=$(python3 --version 2>&1 | awk '{print $2}')
|
||||
if python3 -c "import sys; exit(0 if sys.version_info >= (3, 8) else 1)" 2>/dev/null; then
|
||||
log_success "Python $python_version 확인됨"
|
||||
else
|
||||
log_error "Python 3.8 이상이 필요합니다. 현재: $python_version"
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
else
|
||||
log_error "Python3가 설치되지 않았습니다."
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
|
||||
# Git 확인
|
||||
if command -v git >/dev/null 2>&1; then
|
||||
log_success "Git 확인됨"
|
||||
else
|
||||
log_warning "Git이 설치되지 않았습니다. 설치 과정에서 자동으로 설치됩니다."
|
||||
fi
|
||||
|
||||
# 메모리 확인
|
||||
if [ "$PLATFORM" = "jetson" ]; then
|
||||
local total_mem=$(free -g | grep '^Mem:' | awk '{print $2}')
|
||||
if [ "$total_mem" -ge 16 ]; then
|
||||
log_success "메모리 ${total_mem}GB 확인됨 (Jetson 권장: 16GB+)"
|
||||
else
|
||||
log_warning "메모리 ${total_mem}GB (권장: 16GB+)"
|
||||
fi
|
||||
else
|
||||
local total_mem=$(free -g | grep '^Mem:' | awk '{print $2}')
|
||||
if [ "$total_mem" -ge 8 ]; then
|
||||
log_success "메모리 ${total_mem}GB 확인됨"
|
||||
else
|
||||
log_warning "메모리 ${total_mem}GB (권장: 8GB+)"
|
||||
fi
|
||||
fi
|
||||
|
||||
# 디스크 공간 확인
|
||||
local free_space=$(df -BG . | tail -1 | awk '{print $4}' | sed 's/G//')
|
||||
if [ "$free_space" -ge 10 ]; then
|
||||
log_success "디스크 여유 공간 ${free_space}GB 확인됨"
|
||||
else
|
||||
log_warning "디스크 여유 공간 ${free_space}GB (권장: 10GB+)"
|
||||
fi
|
||||
|
||||
if [ $errors -gt 0 ]; then
|
||||
log_error "필수 요구사항이 충족되지 않았습니다. 위 문제를 해결한 후 다시 시도하세요."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 설치 옵션 표시
|
||||
show_installation_options() {
|
||||
echo "=== 설치 옵션 ==="
|
||||
echo ""
|
||||
|
||||
if [ "$PLATFORM" = "jetson" ]; then
|
||||
echo "🚀 Jetson Xavier 최적화 설정:"
|
||||
echo " - ONNX Runtime GPU (ARM64 특화)"
|
||||
echo " - 32GB 통합 메모리 최적화"
|
||||
echo " - CUDA + TensorRT 가속"
|
||||
echo " - 세션 풀: LAMA(4), MIGAN(4), REMBG(3)"
|
||||
echo " - 워커: 8개 (자동 조정)"
|
||||
else
|
||||
echo "🖥️ x86-64 데스크톱 설정:"
|
||||
echo " - ONNX Runtime GPU (x86-64)"
|
||||
echo " - RTX GPU 최적화"
|
||||
echo " - CUDA + TensorRT 가속"
|
||||
echo " - 세션 풀: LAMA(3), MIGAN(3), REMBG(2)"
|
||||
echo " - 워커: 4-8개 (GPU 메모리에 따라)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 사용자 확인
|
||||
confirm_installation() {
|
||||
echo "=== 설치 확인 ==="
|
||||
echo "다음 설정으로 설치를 진행합니다:"
|
||||
echo " 플랫폼: $PLATFORM_NAME"
|
||||
echo " 설치 스크립트: $INSTALL_SCRIPT"
|
||||
echo ""
|
||||
|
||||
read -p "계속 진행하시겠습니까? (y/N): " -r
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo "설치가 취소되었습니다."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 메인 실행
|
||||
main() {
|
||||
clear
|
||||
print_logo
|
||||
|
||||
# 스크립트 디렉토리로 이동
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
detect_platform
|
||||
show_gpu_info
|
||||
check_requirements
|
||||
show_installation_options
|
||||
confirm_installation
|
||||
|
||||
# 플랫폼별 설치 스크립트 실행
|
||||
log_step "플랫폼별 설치 스크립트 실행 중..."
|
||||
|
||||
if [ ! -f "$INSTALL_SCRIPT" ]; then
|
||||
log_error "설치 스크립트를 찾을 수 없습니다: $INSTALL_SCRIPT"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod +x "$INSTALL_SCRIPT"
|
||||
bash "$INSTALL_SCRIPT"
|
||||
|
||||
# 설치 완료
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
log_success "🎉 인페인팅 서버 설치 완료!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "다음 단계:"
|
||||
echo " 1. 서버 시작: bash scripts/start_server.sh"
|
||||
echo " 2. 서버 상태 확인: bash scripts/status.sh"
|
||||
echo " 3. 서버 중지: bash scripts/stop_server.sh"
|
||||
echo ""
|
||||
echo "접속 주소:"
|
||||
echo " 🖥️ 모니터링 대시보드: http://localhost:8009"
|
||||
echo " 🔗 API 엔드포인트: http://localhost:8008"
|
||||
echo " 📊 헬스체크: curl http://localhost:8008/api/v1/health"
|
||||
echo ""
|
||||
|
||||
if [ "$PLATFORM" = "jetson" ]; then
|
||||
echo "Jetson Xavier 참고사항:"
|
||||
echo " - tegrastats로 시스템 상태 모니터링 가능"
|
||||
echo " - 32GB 통합 메모리로 다중 세션 최적화됨"
|
||||
echo " - GPU 온도/클럭 정보가 모니터링 대시보드에 표시됨"
|
||||
else
|
||||
echo "x86-64 참고사항:"
|
||||
echo " - nvidia-smi로 GPU 상태 확인 가능"
|
||||
echo " - GPU 메모리에 따라 세션 수 자동 조정됨"
|
||||
echo " - TensorRT 사용 시 최고 성능"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 스크립트 실행
|
||||
main "$@"
|
||||
|
|
@ -525,8 +525,63 @@ install_gpu_libraries() {
|
|||
if [ "$SYSTEM_TYPE" = "jetson" ]; then
|
||||
log_info "Jetson 전용 GPU 라이브러리 설치 중..."
|
||||
|
||||
# Jetson 최적화 라이브러리들
|
||||
pip install onnxruntime-gpu || pip install onnxruntime
|
||||
# GCC 버전 확인 및 업그레이드 (ONNX Runtime GPU용)
|
||||
log_info "GCC 버전 확인 중..."
|
||||
if ! gcc-11 --version &> /dev/null; then
|
||||
log_warning "GCC-11이 설치되지 않았습니다. ONNX Runtime GPU 설치를 위해 설치 중..."
|
||||
|
||||
# GCC 11 설치
|
||||
sudo apt install -y software-properties-common
|
||||
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
||||
sudo apt update
|
||||
sudo apt install -y gcc-11 g++-11
|
||||
|
||||
log_success "GCC-11 설치 완료"
|
||||
else
|
||||
log_info "GCC-11이 이미 설치되어 있습니다"
|
||||
fi
|
||||
|
||||
# Jetson Xavier용 ONNX Runtime GPU 설치
|
||||
log_info "Jetson Xavier용 ONNX Runtime GPU 설치 중..."
|
||||
|
||||
# 기존 onnxruntime 제거
|
||||
pip uninstall -y onnxruntime onnxruntime-gpu 2>/dev/null || true
|
||||
|
||||
# ONNX Runtime GPU 휠 파일 다운로드
|
||||
ONNX_WHEEL_NAME="onnxruntime_gpu-1.17.0-cp38-cp38-linux_aarch64.whl"
|
||||
if [ ! -f "$PROJECT_ROOT/$ONNX_WHEEL_NAME" ]; then
|
||||
log_info "ONNX Runtime GPU 휠 파일 다운로드 중..."
|
||||
cd "$PROJECT_ROOT"
|
||||
wget https://nvidia.box.com/shared/static/zostg6agm00fb6t5uisw51qi6kpcuwzd.whl \
|
||||
-O "$ONNX_WHEEL_NAME"
|
||||
else
|
||||
log_info "ONNX Runtime GPU 휠 파일이 이미 존재합니다"
|
||||
fi
|
||||
|
||||
# ONNX Runtime GPU 설치
|
||||
log_info "ONNX Runtime GPU 설치 중..."
|
||||
pip install --force-reinstall "$PROJECT_ROOT/$ONNX_WHEEL_NAME"
|
||||
|
||||
# 설치 확인
|
||||
if python -c "import onnxruntime as ort; print('Available providers:', ort.get_available_providers())" 2>/dev/null; then
|
||||
log_success "ONNX Runtime GPU 설치 및 확인 완료"
|
||||
python -c "
|
||||
import onnxruntime as ort
|
||||
providers = ort.get_available_providers()
|
||||
print('ONNX Runtime 버전:', ort.__version__)
|
||||
print('사용 가능한 프로바이더:')
|
||||
for provider in providers:
|
||||
print(' -', provider)
|
||||
if 'TensorrtExecutionProvider' in providers:
|
||||
print('✅ TensorRT 프로바이더 사용 가능')
|
||||
if 'CUDAExecutionProvider' in providers:
|
||||
print('✅ CUDA 프로바이더 사용 가능')
|
||||
"
|
||||
else
|
||||
log_error "ONNX Runtime GPU 설치 실패"
|
||||
log_info "CPU 버전으로 폴백 설치 중..."
|
||||
pip install onnxruntime==1.19.2
|
||||
fi
|
||||
|
||||
# Jetson 전용 패키지들
|
||||
pip install jetson-stats || log_warning "jetson-stats 설치 실패"
|
||||
|
|
@ -534,6 +589,13 @@ install_gpu_libraries() {
|
|||
else
|
||||
log_info "x86용 GPU 라이브러리 설치 중..."
|
||||
|
||||
# x86용 ONNX Runtime GPU 설치
|
||||
log_info "x86용 ONNX Runtime GPU 설치 중..."
|
||||
pip install onnxruntime-gpu || {
|
||||
log_warning "ONNX Runtime GPU 설치 실패, CPU 버전으로 폴백"
|
||||
pip install onnxruntime==1.19.2
|
||||
}
|
||||
|
||||
# 추가 GPU 가속 라이브러리
|
||||
if [ "$INSTALL_EXTRAS" = true ]; then
|
||||
pip install cupy-cuda118 || {
|
||||
|
|
@ -1,326 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# 인페인팅 서버 자동 설치 및 실행 스크립트
|
||||
# 다른 머신에서 git clone 후 바로 실행할 수 있도록 합니다.
|
||||
|
||||
set -e
|
||||
|
||||
# 색상 코드
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 로그 함수들
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 도움말 표시
|
||||
show_help() {
|
||||
echo "인페인팅 서버 자동 설치 및 실행 스크립트"
|
||||
echo ""
|
||||
echo "사용법: $0 [옵션]"
|
||||
echo ""
|
||||
echo "옵션:"
|
||||
echo " --jetson Jetson Xavier 최적화 설치"
|
||||
echo " --x86 x86_64 시스템용 설치"
|
||||
echo " --no-venv 기존 가상환경 사용 (새로 생성하지 않음)"
|
||||
echo " --no-models 모델 파일 다운로드 건너뛰기"
|
||||
echo " --no-start 설치만 하고 서버 시작하지 않음"
|
||||
echo " -h, --help 이 도움말 표시"
|
||||
echo ""
|
||||
echo "예시:"
|
||||
echo " $0 # 시스템 자동 감지하여 설치 및 실행"
|
||||
echo " $0 --jetson # Jetson Xavier 최적화 설치"
|
||||
echo " $0 --x86 # x86_64 시스템용 설치"
|
||||
}
|
||||
|
||||
# 기본 설정
|
||||
JETSON_MODE=false
|
||||
X86_MODE=false
|
||||
CREATE_VENV=true
|
||||
DOWNLOAD_MODELS=true
|
||||
START_SERVER=true
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
# 명령줄 인자 처리
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--jetson)
|
||||
JETSON_MODE=true
|
||||
shift
|
||||
;;
|
||||
--x86)
|
||||
X86_MODE=true
|
||||
shift
|
||||
;;
|
||||
--no-venv)
|
||||
CREATE_VENV=false
|
||||
shift
|
||||
;;
|
||||
--no-models)
|
||||
DOWNLOAD_MODELS=false
|
||||
shift
|
||||
;;
|
||||
--no-start)
|
||||
START_SERVER=false
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
log_error "알 수 없는 옵션: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 시스템 자동 감지
|
||||
detect_system() {
|
||||
if [ "$JETSON_MODE" = true ]; then
|
||||
SYSTEM_TYPE="jetson"
|
||||
log_info "🚁 Jetson Xavier 모드로 설정됨"
|
||||
elif [ "$X86_MODE" = true ]; then
|
||||
SYSTEM_TYPE="x86"
|
||||
log_info "🖥️ x86_64 모드로 설정됨"
|
||||
else
|
||||
# 자동 감지
|
||||
if [ "$(uname -m)" = "aarch64" ] && uname -a | grep -q "tegra"; then
|
||||
SYSTEM_TYPE="jetson"
|
||||
log_info "🚁 Jetson Xavier (ARM64) 시스템 자동 감지"
|
||||
elif [ "$(uname -m)" = "x86_64" ]; then
|
||||
SYSTEM_TYPE="x86"
|
||||
log_info "🖥️ x86_64 시스템 자동 감지"
|
||||
else
|
||||
log_error "지원되지 않는 시스템 아키텍처: $(uname -m)"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Python 버전 확인
|
||||
check_python() {
|
||||
log_info "Python 환경 확인 중..."
|
||||
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
log_error "Python3가 설치되지 않았습니다"
|
||||
log_info "Ubuntu/Debian: sudo apt install python3 python3-pip python3-venv"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PYTHON_VERSION=$(python3 --version | cut -d' ' -f2)
|
||||
log_info "Python 버전: $PYTHON_VERSION"
|
||||
|
||||
# Python 3.8 이상 확인
|
||||
if ! python3 -c "import sys; exit(0 if sys.version_info >= (3, 8) else 1)"; then
|
||||
log_error "Python 3.8 이상이 필요합니다. 현재 버전: $PYTHON_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "Python 환경 확인 완료"
|
||||
}
|
||||
|
||||
# 가상환경 설정
|
||||
setup_venv() {
|
||||
if [ "$CREATE_VENV" = false ]; then
|
||||
log_info "기존 가상환경 사용"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "가상환경 설정 중..."
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# 기존 가상환경 제거 (있다면)
|
||||
if [ -d "venv" ]; then
|
||||
log_warning "기존 가상환경 제거 중..."
|
||||
rm -rf venv
|
||||
fi
|
||||
|
||||
# 새 가상환경 생성
|
||||
python3 -m venv venv
|
||||
|
||||
# 가상환경 활성화
|
||||
source venv/bin/activate
|
||||
|
||||
# pip 업그레이드
|
||||
pip install --upgrade pip
|
||||
|
||||
log_success "가상환경 설정 완료"
|
||||
}
|
||||
|
||||
# 의존성 설치
|
||||
install_dependencies() {
|
||||
log_info "의존성 설치 중..."
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# 가상환경 활성화 확인
|
||||
if [ "$CREATE_VENV" = true ]; then
|
||||
source venv/bin/activate
|
||||
fi
|
||||
|
||||
# requirements.txt 확인
|
||||
if [ "$SYSTEM_TYPE" = "x86" ]; then
|
||||
REQ_FILE="requirements_x86.txt"
|
||||
else
|
||||
REQ_FILE="requirements.txt"
|
||||
fi
|
||||
|
||||
if [ ! -f "$REQ_FILE" ]; then
|
||||
log_error "$REQ_FILE 파일을 찾을 수 없습니다"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 시스템별 최적화 설치
|
||||
if [ "$SYSTEM_TYPE" = "jetson" ]; then
|
||||
log_info "Jetson Xavier 최적화 패키지 설치 중..."
|
||||
# Jetson 전용 패키지가 있다면 여기에 추가
|
||||
fi
|
||||
|
||||
# 기본 패키지 설치
|
||||
pip install -r "$REQ_FILE"
|
||||
|
||||
log_success "의존성 설치 완료"
|
||||
}
|
||||
|
||||
# 모델 파일 확인 및 다운로드
|
||||
setup_models() {
|
||||
if [ "$DOWNLOAD_MODELS" = false ]; then
|
||||
log_info "모델 파일 다운로드 건너뛰기"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "모델 파일 확인 중..."
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# 모델 디렉토리 생성
|
||||
mkdir -p app/models/onnx
|
||||
mkdir -p app/models/pt
|
||||
|
||||
# 필요한 모델 파일들 확인
|
||||
MODEL_FILES_MISSING=false
|
||||
|
||||
if [ ! -f "app/models/onnx/migan_pipeline_v2.onnx" ]; then
|
||||
log_warning "MIGAN 모델 파일이 없습니다: app/models/onnx/migan_pipeline_v2.onnx"
|
||||
MODEL_FILES_MISSING=true
|
||||
fi
|
||||
|
||||
if [ ! -f "app/models/pt/big-lama.pt" ]; then
|
||||
log_warning "LAMA 모델 파일이 없습니다: app/models/pt/big-lama.pt"
|
||||
MODEL_FILES_MISSING=true
|
||||
fi
|
||||
|
||||
if [ "$MODEL_FILES_MISSING" = true ]; then
|
||||
log_warning "일부 모델 파일이 없습니다"
|
||||
log_info "모델 파일들을 수동으로 다운로드하여 다음 위치에 배치하세요:"
|
||||
log_info " - MIGAN: app/models/onnx/migan_pipeline_v2.onnx"
|
||||
log_info " - LAMA: app/models/pt/big-lama.pt"
|
||||
log_info "서버는 사용 가능한 모델로만 동작합니다"
|
||||
else
|
||||
log_success "모든 모델 파일 확인 완료"
|
||||
fi
|
||||
}
|
||||
|
||||
# 환경 설정
|
||||
setup_environment() {
|
||||
log_info "환경 설정 중..."
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# .env 파일 생성 (없다면)
|
||||
if [ ! -f ".env" ] && [ -f ".env.example" ]; then
|
||||
cp .env.example .env
|
||||
log_info ".env 파일 생성됨"
|
||||
fi
|
||||
|
||||
log_success "환경 설정 완료"
|
||||
}
|
||||
|
||||
# 서버 시작
|
||||
start_server() {
|
||||
if [ "$START_SERVER" = false ]; then
|
||||
log_info "서버 시작 건너뛰기"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "서버 시작 중..."
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# start_server.sh 실행 권한 확인
|
||||
if [ ! -x "scripts/start_server.sh" ]; then
|
||||
chmod +x scripts/start_server.sh
|
||||
fi
|
||||
|
||||
# 시스템별 최적화 옵션
|
||||
if [ "$SYSTEM_TYPE" = "jetson" ]; then
|
||||
./scripts/start_server.sh --jetson-optimize
|
||||
else
|
||||
./scripts/start_server.sh
|
||||
fi
|
||||
}
|
||||
|
||||
# 설치 완료 정보 출력
|
||||
print_completion_info() {
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "🎉 인페인팅 서버 설치 완료!"
|
||||
echo "=========================================="
|
||||
echo "시스템 타입: $SYSTEM_TYPE"
|
||||
echo "프로젝트 경로: $PROJECT_ROOT"
|
||||
|
||||
if [ "$START_SERVER" = true ]; then
|
||||
echo ""
|
||||
echo "🚀 서버 접속 정보:"
|
||||
echo " - 메인 API 서버: http://localhost:8008"
|
||||
echo " - API 문서: http://localhost:8008/docs"
|
||||
echo " - 모니터링 대시보드: http://localhost:8888"
|
||||
echo " - 헬스 체크: http://localhost:8008/health"
|
||||
else
|
||||
echo ""
|
||||
echo "서버를 시작하려면 다음 명령을 실행하세요:"
|
||||
echo " cd $PROJECT_ROOT"
|
||||
echo " ./scripts/start_server.sh"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🛠️ 유용한 명령어:"
|
||||
echo " - 서버 중지: ./scripts/stop_server.sh"
|
||||
echo " - 서버 상태: ./scripts/status.sh"
|
||||
echo " - API 테스트: python tests/scripts/test_api.py"
|
||||
echo ""
|
||||
echo "📚 더 많은 정보는 README.md를 참고하세요"
|
||||
echo "=========================================="
|
||||
}
|
||||
|
||||
# 메인 실행 함수
|
||||
main() {
|
||||
echo "🚀 인페인팅 서버 자동 설치 시작"
|
||||
echo "=========================================="
|
||||
|
||||
detect_system
|
||||
check_python
|
||||
setup_venv
|
||||
install_dependencies
|
||||
setup_models
|
||||
setup_environment
|
||||
start_server
|
||||
print_completion_info
|
||||
}
|
||||
|
||||
# 스크립트 실행
|
||||
main "$@"
|
||||
|
|
@ -0,0 +1,226 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Jetson Xavier 전용 인페인팅 서버 설치 스크립트
|
||||
# ARM64 아키텍처 + NVIDIA Jetson Xavier + 32GB 통합 메모리 최적화
|
||||
# Usage: ./setup_jetson.sh
|
||||
|
||||
set -e
|
||||
|
||||
# 색상 코드
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 로그 함수들
|
||||
log_info() {
|
||||
echo -e "${BLUE}[JETSON]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
log_step() {
|
||||
echo -e "${CYAN}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
# 스크립트 시작
|
||||
clear
|
||||
echo "======================================"
|
||||
echo "🚀 Jetson Xavier 인페인팅 서버 설치"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
# 1단계: 시스템 확인
|
||||
log_step "1단계: Jetson Xavier 시스템 확인"
|
||||
|
||||
# ARM64 아키텍처 확인
|
||||
if [ "$(uname -m)" != "aarch64" ]; then
|
||||
log_error "이 스크립트는 Jetson Xavier (ARM64)용입니다. x86 시스템은 setup_x86.sh를 사용하세요."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Jetson 확인
|
||||
if [ ! -f "/etc/nv_tegra_release" ]; then
|
||||
log_warning "Jetson 시스템이 아닌 것 같습니다. 계속 진행하시겠습니까? (y/N)"
|
||||
read -r response
|
||||
if [[ ! "$response" =~ ^[Yy]$ ]]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
log_success "Jetson Xavier ARM64 시스템 확인됨"
|
||||
|
||||
# 2단계: 가상환경 설정
|
||||
log_step "2단계: Python 가상환경 설정"
|
||||
|
||||
# 프로젝트 루트로 이동
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# 가상환경 생성 함수
|
||||
create_venv() {
|
||||
local venv_path="$1"
|
||||
if [ ! -d "$venv_path" ]; then
|
||||
log_info "가상환경 생성 중: $venv_path"
|
||||
python3 -m venv "$venv_path"
|
||||
log_success "가상환경 생성 완료: $venv_path"
|
||||
else
|
||||
log_info "기존 가상환경 발견: $venv_path"
|
||||
fi
|
||||
}
|
||||
|
||||
# venv 또는 현재 디렉토리에 가상환경 확인/생성
|
||||
if [ -d "venv" ]; then
|
||||
VENV_PATH="venv"
|
||||
log_info "기존 venv 가상환경 사용"
|
||||
elif [ -f "pyvenv.cfg" ]; then
|
||||
VENV_PATH="."
|
||||
log_info "프로젝트 자체가 가상환경으로 설정됨"
|
||||
else
|
||||
VENV_PATH="venv"
|
||||
log_info "새로운 venv 가상환경 생성"
|
||||
create_venv "$VENV_PATH"
|
||||
fi
|
||||
|
||||
# 가상환경 활성화
|
||||
if [ "$VENV_PATH" = "." ]; then
|
||||
# 현재 디렉토리가 가상환경인 경우
|
||||
if [ -z "$VIRTUAL_ENV" ]; then
|
||||
log_info "프로젝트 디렉토리 가상환경 활성화"
|
||||
source bin/activate 2>/dev/null || {
|
||||
log_error "가상환경 활성화 실패. venv를 새로 생성합니다."
|
||||
VENV_PATH="venv"
|
||||
create_venv "$VENV_PATH"
|
||||
source "$VENV_PATH/bin/activate"
|
||||
}
|
||||
fi
|
||||
else
|
||||
source "$VENV_PATH/bin/activate"
|
||||
fi
|
||||
|
||||
log_success "가상환경 활성화: $VIRTUAL_ENV"
|
||||
|
||||
# 3단계: 시스템 패키지 업데이트
|
||||
log_step "3단계: 시스템 패키지 업데이트"
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
|
||||
# 4단계: 필수 도구 설치
|
||||
log_step "4단계: Jetson 개발 도구 설치"
|
||||
sudo apt install -y \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
curl \
|
||||
wget \
|
||||
unzip \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
libffi-dev \
|
||||
libjpeg-dev \
|
||||
libpng-dev \
|
||||
libtiff-dev \
|
||||
libopencv-dev \
|
||||
nvidia-jetpack
|
||||
|
||||
# 5단계: GCC 11 설치 (ONNX Runtime GPU 호환성)
|
||||
log_step "5단계: GCC 11 설치 (ONNX Runtime GPU 필수)"
|
||||
sudo apt install -y gcc-11 g++-11
|
||||
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 100
|
||||
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 100
|
||||
|
||||
# 6단계: Python 패키지 업그레이드
|
||||
log_step "6단계: Python 패키지 매니저 업그레이드"
|
||||
pip install --upgrade pip setuptools wheel
|
||||
|
||||
# 7단계: Jetson 특화 ONNX Runtime GPU 설치
|
||||
log_step "7단계: Jetson ONNX Runtime GPU 설치"
|
||||
log_info "Jetson Xavier용 ONNX Runtime GPU 다운로드 중..."
|
||||
|
||||
# 기존 onnxruntime 제거
|
||||
pip uninstall -y onnxruntime onnxruntime-gpu 2>/dev/null || true
|
||||
|
||||
# Jetson Xavier 전용 ONNX Runtime GPU 설치
|
||||
ONNX_WHEEL_URL="https://nvidia.box.com/shared/static/zostg6agm00fb6t5uisw51qi6kpcuwzd.whl"
|
||||
ONNX_WHEEL_FILE="onnxruntime_gpu-1.17.0-cp38-cp38-linux_aarch64.whl"
|
||||
|
||||
wget -O "$ONNX_WHEEL_FILE" "$ONNX_WHEEL_URL"
|
||||
pip install --force-reinstall "$ONNX_WHEEL_FILE"
|
||||
rm -f "$ONNX_WHEEL_FILE"
|
||||
|
||||
log_success "Jetson ONNX Runtime GPU 설치 완료"
|
||||
|
||||
# 8단계: PyTorch 설치 (Jetson 최적화)
|
||||
log_step "8단계: Jetson 최적화 PyTorch 설치"
|
||||
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118
|
||||
|
||||
# 9단계: 프로젝트 의존성 설치
|
||||
log_step "9단계: 프로젝트 의존성 설치"
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 10단계: numpy 버전 조정 (호환성)
|
||||
log_step "10단계: 호환성 패키지 조정"
|
||||
pip install numpy==1.23.5
|
||||
|
||||
# 11단계: 설치 검증
|
||||
log_step "11단계: 설치 검증"
|
||||
|
||||
echo "=== Python 환경 ==="
|
||||
python --version
|
||||
echo "가상환경: $VIRTUAL_ENV"
|
||||
|
||||
echo -e "\n=== ONNX Runtime 확인 ==="
|
||||
python -c "
|
||||
import onnxruntime as ort
|
||||
print(f'ONNX Runtime 버전: {ort.__version__}')
|
||||
providers = ort.get_available_providers()
|
||||
print(f'사용 가능한 providers: {providers}')
|
||||
if 'CUDAExecutionProvider' in providers:
|
||||
print('✅ CUDA 지원 확인됨')
|
||||
if 'TensorrtExecutionProvider' in providers:
|
||||
print('✅ TensorRT 지원 확인됨')
|
||||
" || log_warning "ONNX Runtime 확인 실패"
|
||||
|
||||
echo -e "\n=== PyTorch CUDA 확인 ==="
|
||||
python -c "
|
||||
import torch
|
||||
print(f'PyTorch 버전: {torch.__version__}')
|
||||
print(f'CUDA 사용 가능: {torch.cuda.is_available()}')
|
||||
if torch.cuda.is_available():
|
||||
print(f'CUDA 버전: {torch.version.cuda}')
|
||||
print(f'GPU 개수: {torch.cuda.device_count()}')
|
||||
" || log_warning "PyTorch 확인 실패"
|
||||
|
||||
echo -e "\n=== Jetson 시스템 정보 ==="
|
||||
tegrastats --verbose 2>/dev/null | head -1 || echo "tegrastats 정보 없음"
|
||||
|
||||
# 완료
|
||||
echo ""
|
||||
echo "======================================"
|
||||
log_success "🎉 Jetson Xavier 설치 완료!"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
echo "다음 명령으로 서버를 시작하세요:"
|
||||
echo " bash scripts/start_server.sh"
|
||||
echo ""
|
||||
echo "모니터링 대시보드:"
|
||||
echo " http://localhost:8009"
|
||||
echo ""
|
||||
echo "API 테스트:"
|
||||
echo " curl http://localhost:8008/api/v1/health"
|
||||
echo ""
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
|
||||
# x86 시스템용 인페인팅 서버 자동 설치 스크립트
|
||||
# RTX 3060 12GB 등 x86 GPU를 지원합니다.
|
||||
# x86-64 시스템용 인페인팅 서버 설치 스크립트
|
||||
# RTX 3060 12GB, RTX 4080 등 x86 GPU 최적화
|
||||
# Usage: ./setup_x86.sh
|
||||
|
||||
set -e
|
||||
|
|
@ -11,11 +11,12 @@ RED='\033[0;31m'
|
|||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# 로그 함수들
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
echo -e "${BLUE}[X86]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
|
|
@ -30,120 +31,178 @@ log_error() {
|
|||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 기본 설정
|
||||
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
VENV_PATH="$PROJECT_ROOT/venv"
|
||||
REQUIREMENTS_FILE="$PROJECT_ROOT/requirements_x86.txt"
|
||||
log_step() {
|
||||
echo -e "${CYAN}[STEP]${NC} $1"
|
||||
}
|
||||
|
||||
log_info "🚀 x86 시스템용 인페인팅 서버 자동 설치 시작"
|
||||
echo "=========================================="
|
||||
# 스크립트 시작
|
||||
clear
|
||||
echo "======================================"
|
||||
echo "🖥️ x86-64 인페인팅 서버 설치"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
# 시스템 감지
|
||||
if [ "$(uname -m)" = "x86_64" ]; then
|
||||
log_info "🖥️ x86_64 시스템 자동 감지"
|
||||
# 1단계: 시스템 확인
|
||||
log_step "1단계: x86-64 시스템 확인"
|
||||
|
||||
# x86-64 아키텍처 확인
|
||||
ARCH=$(uname -m)
|
||||
if [[ "$ARCH" != "x86_64" && "$ARCH" != "amd64" ]]; then
|
||||
log_error "이 스크립트는 x86-64용입니다. Jetson Xavier는 setup_jetson.sh를 사용하세요."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "x86-64 시스템 확인됨"
|
||||
|
||||
# 2단계: 가상환경 설정
|
||||
log_step "2단계: Python 가상환경 설정"
|
||||
|
||||
# 프로젝트 루트로 이동
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# 가상환경 생성 함수
|
||||
create_venv() {
|
||||
local venv_path="$1"
|
||||
if [ ! -d "$venv_path" ]; then
|
||||
log_info "가상환경 생성 중: $venv_path"
|
||||
python3 -m venv "$venv_path"
|
||||
log_success "가상환경 생성 완료: $venv_path"
|
||||
else
|
||||
log_info "기존 가상환경 발견: $venv_path"
|
||||
fi
|
||||
}
|
||||
|
||||
# venv 또는 현재 디렉토리에 가상환경 확인/생성
|
||||
if [ -d "venv" ]; then
|
||||
VENV_PATH="venv"
|
||||
log_info "기존 venv 가상환경 사용"
|
||||
elif [ -f "pyvenv.cfg" ]; then
|
||||
VENV_PATH="."
|
||||
log_info "프로젝트 자체가 가상환경으로 설정됨"
|
||||
else
|
||||
log_error "❌ x86_64 시스템이 아닙니다. 이 스크립트는 x86 시스템에서만 실행해야 합니다."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Python 환경 확인
|
||||
log_info "Python 환경 확인 중..."
|
||||
if ! command -v python3 &> /dev/null; then
|
||||
log_error "❌ Python3가 설치되지 않았습니다."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PYTHON_VERSION=$(python3 --version 2>&1 | awk '{print $2}')
|
||||
log_info "Python 버전: $PYTHON_VERSION"
|
||||
|
||||
# Python 3.10 이상 확인
|
||||
if ! python3 -c "import sys; exit(0 if sys.version_info >= (3, 10) else 1)" 2>/dev/null; then
|
||||
log_error "❌ Python 3.10 이상이 필요합니다. 현재 버전: $PYTHON_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "Python 환경 확인 완료"
|
||||
|
||||
# 가상환경 설정
|
||||
log_info "가상환경 설정 중..."
|
||||
if [ ! -d "$VENV_PATH" ]; then
|
||||
log_info "새로운 가상환경을 생성합니다..."
|
||||
python3 -m venv "$VENV_PATH"
|
||||
VENV_PATH="venv"
|
||||
log_info "새로운 venv 가상환경 생성"
|
||||
create_venv "$VENV_PATH"
|
||||
fi
|
||||
|
||||
# 가상환경 활성화
|
||||
source "$VENV_PATH/bin/activate"
|
||||
|
||||
# pip 업그레이드
|
||||
log_info "pip 업그레이드 중..."
|
||||
pip install --upgrade pip
|
||||
|
||||
log_success "가상환경 설정 완료"
|
||||
|
||||
# 의존성 설치
|
||||
log_info "의존성 설치 중..."
|
||||
if [ ! -f "$REQUIREMENTS_FILE" ]; then
|
||||
log_error "❌ requirements_x86.txt 파일을 찾을 수 없습니다."
|
||||
exit 1
|
||||
if [ "$VENV_PATH" = "." ]; then
|
||||
# 현재 디렉토리가 가상환경인 경우
|
||||
if [ -z "$VIRTUAL_ENV" ]; then
|
||||
log_info "프로젝트 디렉토리 가상환경 활성화"
|
||||
source bin/activate 2>/dev/null || {
|
||||
log_error "가상환경 활성화 실패. venv를 새로 생성합니다."
|
||||
VENV_PATH="venv"
|
||||
create_venv "$VENV_PATH"
|
||||
source "$VENV_PATH/bin/activate"
|
||||
}
|
||||
fi
|
||||
else
|
||||
source "$VENV_PATH/bin/activate"
|
||||
fi
|
||||
|
||||
# PyTorch 먼저 설치 (CUDA 11.8)
|
||||
log_info "PyTorch 설치 중 (CUDA 11.8)..."
|
||||
pip install torch==2.0.1+cu118 torchvision==0.15.2+cu118 --index-url https://download.pytorch.org/whl/cu118
|
||||
log_success "가상환경 활성화: $VIRTUAL_ENV"
|
||||
|
||||
# TensorFlow 설치 (Python 3.10 호환)
|
||||
log_info "TensorFlow 설치 중 (Python 3.10 호환)..."
|
||||
pip install tensorflow-gpu==2.12.0
|
||||
# 3단계: 시스템 패키지 업데이트 (Ubuntu/Debian)
|
||||
log_step "3단계: 시스템 패키지 업데이트"
|
||||
if command -v apt >/dev/null 2>&1; then
|
||||
sudo apt update && sudo apt upgrade -y
|
||||
sudo apt install -y \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
python3-venv \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
curl \
|
||||
wget \
|
||||
unzip \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
libffi-dev \
|
||||
libjpeg-dev \
|
||||
libpng-dev \
|
||||
libtiff-dev \
|
||||
libopencv-dev
|
||||
elif command -v yum >/dev/null 2>&1; then
|
||||
sudo yum update -y
|
||||
sudo yum groupinstall -y "Development Tools"
|
||||
sudo yum install -y python3-devel python3-pip cmake git curl wget unzip
|
||||
else
|
||||
log_warning "패키지 매니저를 찾을 수 없습니다. 수동으로 개발 도구를 설치해주세요."
|
||||
fi
|
||||
|
||||
# 나머지 의존성 설치
|
||||
log_info "기타 의존성 설치 중..."
|
||||
pip install -r "$REQUIREMENTS_FILE"
|
||||
# 4단계: Python 패키지 업그레이드
|
||||
log_step "4단계: Python 패키지 매니저 업그레이드"
|
||||
pip install --upgrade pip setuptools wheel
|
||||
|
||||
log_success "의존성 설치 완료"
|
||||
# 5단계: x86 ONNX Runtime GPU 설치
|
||||
log_step "5단계: x86 ONNX Runtime GPU 설치"
|
||||
log_info "x86-64용 ONNX Runtime GPU 설치 중..."
|
||||
|
||||
# 모델 다운로드
|
||||
log_info "AI 모델 다운로드 중..."
|
||||
cd "$PROJECT_ROOT"
|
||||
# 기존 onnxruntime 제거
|
||||
pip uninstall -y onnxruntime onnxruntime-gpu 2>/dev/null || true
|
||||
|
||||
# 모델 디렉토리 생성
|
||||
mkdir -p models/simple-lama models/migan models/rembg
|
||||
# x86-64 ONNX Runtime GPU 설치
|
||||
pip install onnxruntime-gpu
|
||||
|
||||
# Simple LAMA 모델 다운로드
|
||||
#if [ ! -f "models/simple-lama/big-lama.pt" ]; then
|
||||
# log_info "Simple LAMA 모델 다운로드 중..."
|
||||
# wget -O models/simple-lama/big-lama.pt https://github.com/Saafke/Simple-LAMA/releases/download/v1.0/big-lama.pt
|
||||
#fi
|
||||
log_success "x86 ONNX Runtime GPU 설치 완료"
|
||||
|
||||
# MIGAN 모델 다운로드
|
||||
#if [ ! -f "models/migan/migan.pt" ]; then
|
||||
# log_info "MIGAN 모델 다운로드 중..."
|
||||
# wget -O models/migan/migan.pt https://github.com/open-mmlab/mmediting/releases/download/v1.0.0/migan_256x256_celeba-hq_20220629-3b7c8c9f.pth
|
||||
#fi
|
||||
# 6단계: PyTorch 설치 (x86 CUDA)
|
||||
log_step "6단계: x86 CUDA PyTorch 설치"
|
||||
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118
|
||||
|
||||
#log_success "모델 다운로드 완료"
|
||||
# 7단계: 프로젝트 의존성 설치
|
||||
log_step "7단계: 프로젝트 의존성 설치"
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 권한 설정
|
||||
log_info "권한 설정 중..."
|
||||
chmod +x scripts/*.sh
|
||||
# 8단계: 설치 검증
|
||||
log_step "8단계: 설치 검증"
|
||||
|
||||
log_success "권한 설정 완료"
|
||||
echo "=== Python 환경 ==="
|
||||
python --version
|
||||
echo "가상환경: $VIRTUAL_ENV"
|
||||
|
||||
# 설치 완료
|
||||
echo "=========================================="
|
||||
log_success "🎉 x86 시스템용 인페인팅 서버 설치가 완료되었습니다!"
|
||||
echo -e "\n=== ONNX Runtime 확인 ==="
|
||||
python -c "
|
||||
import onnxruntime as ort
|
||||
print(f'ONNX Runtime 버전: {ort.__version__}')
|
||||
providers = ort.get_available_providers()
|
||||
print(f'사용 가능한 providers: {providers}')
|
||||
if 'CUDAExecutionProvider' in providers:
|
||||
print('✅ CUDA 지원 확인됨')
|
||||
if 'TensorrtExecutionProvider' in providers:
|
||||
print('✅ TensorRT 지원 확인됨')
|
||||
" || log_warning "ONNX Runtime 확인 실패"
|
||||
|
||||
echo -e "\n=== PyTorch CUDA 확인 ==="
|
||||
python -c "
|
||||
import torch
|
||||
print(f'PyTorch 버전: {torch.__version__}')
|
||||
print(f'CUDA 사용 가능: {torch.cuda.is_available()}')
|
||||
if torch.cuda.is_available():
|
||||
print(f'CUDA 버전: {torch.version.cuda}')
|
||||
print(f'GPU 개수: {torch.cuda.device_count()}')
|
||||
for i in range(torch.cuda.device_count()):
|
||||
print(f'GPU {i}: {torch.cuda.get_device_name(i)}')
|
||||
" || log_warning "PyTorch 확인 실패"
|
||||
|
||||
echo -e "\n=== NVIDIA GPU 확인 ==="
|
||||
nvidia-smi 2>/dev/null || log_warning "nvidia-smi 정보 없음"
|
||||
|
||||
# 완료
|
||||
echo ""
|
||||
log_info "다음 명령어로 서버를 시작할 수 있습니다:"
|
||||
echo " cd $PROJECT_ROOT"
|
||||
echo "======================================"
|
||||
log_success "🎉 x86-64 설치 완료!"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
echo "다음 명령으로 서버를 시작하세요:"
|
||||
echo " bash scripts/start_server.sh"
|
||||
echo ""
|
||||
log_info "또는 자동 설정 및 실행:"
|
||||
echo " bash scripts/setup_and_run.sh"
|
||||
echo "모니터링 대시보드:"
|
||||
echo " http://localhost:8009"
|
||||
echo ""
|
||||
log_info "서버 포트:"
|
||||
echo " 메인 서버: http://localhost:8008"
|
||||
echo " 모니터링: http://localhost:8888"
|
||||
echo ""
|
||||
log_info "GPU 설정:"
|
||||
echo " RTX 3060 12GB 권장 설정이 자동으로 적용됩니다"
|
||||
echo " 워커 수: 4-8개 (자동 조정)"
|
||||
echo " 세션 수: Simple LAMA 4개, MIGAN 4개, REMBG 2개"
|
||||
echo "API 테스트:"
|
||||
echo " curl http://localhost:8008/api/v1/health"
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ LOG_DIR="$PROJECT_ROOT/logs"
|
|||
# 가상환경 경로 자동 감지
|
||||
detect_venv_path() {
|
||||
local possible_paths=(
|
||||
"$PROJECT_ROOT/" # 일반적인 경로
|
||||
"$PROJECT_ROOT/venv" # 일반적인 venv 경로
|
||||
"$PROJECT_ROOT/.venv" # 숨김 venv 경로
|
||||
"$PROJECT_ROOT/env" # env 경로
|
||||
|
|
@ -364,8 +365,8 @@ setup_jetson_optimization() {
|
|||
# 전력 모드 설정
|
||||
if command -v nvpmodel &> /dev/null; then
|
||||
log_info "전력 모드를 MAXN으로 설정 중..."
|
||||
sudo nvpmodel -m 0 # MAXN 모드
|
||||
sudo nvpmodel -q
|
||||
sudo nvpmodel -m 0 2>/dev/null || log_warning "MAXN 모드 설정 실패"
|
||||
sudo nvpmodel -q 2>/dev/null || log_warning "전력 모드 확인 실패"
|
||||
else
|
||||
log_warning "nvpmodel을 찾을 수 없습니다"
|
||||
fi
|
||||
|
|
@ -458,13 +459,35 @@ start_servers() {
|
|||
echo $! > "$LOG_DIR/main_server.pid"
|
||||
fi
|
||||
|
||||
sleep 3
|
||||
# 메인 서버 시작 대기 (모델 로딩 시간 고려)
|
||||
log_info "메인 서버 초기화 대기 중... (최대 120초)"
|
||||
local timeout=120
|
||||
local elapsed=0
|
||||
local interval=5
|
||||
|
||||
# 메인 서버 상태 확인
|
||||
if curl -s "http://localhost:$MAIN_SERVER_PORT/health" > /dev/null; then
|
||||
log_success "메인 서버 시작 완료 (PID: $(cat $LOG_DIR/main_server.pid))"
|
||||
else
|
||||
log_error "메인 서버 시작 실패"
|
||||
while [ $elapsed -lt $timeout ]; do
|
||||
sleep $interval
|
||||
elapsed=$((elapsed + interval))
|
||||
|
||||
# 올바른 헬스체크 엔드포인트 사용
|
||||
if curl -s "http://localhost:$MAIN_SERVER_PORT/api/v1/health" > /dev/null 2>&1; then
|
||||
log_success "메인 서버 시작 완료 (PID: $(cat $LOG_DIR/main_server.pid)) - ${elapsed}초 소요"
|
||||
break
|
||||
fi
|
||||
|
||||
# 진행 상황 로깅 (더 자주)
|
||||
if [ $((elapsed % 10)) -eq 0 ]; then
|
||||
log_info "메인 서버 초기화 중... (${elapsed}/${timeout}초) - 모델 로딩 진행 중"
|
||||
fi
|
||||
done
|
||||
|
||||
# 최종 확인
|
||||
if ! curl -s "http://localhost:$MAIN_SERVER_PORT/api/v1/health" > /dev/null 2>&1; then
|
||||
log_error "메인 서버 시작 실패 (${timeout}초 타임아웃)"
|
||||
log_info "모델 로딩이 진행 중일 수 있습니다. 로그를 확인하세요:"
|
||||
log_info " tail -f $LOG_DIR/main_server.log"
|
||||
log_info "서버 프로세스가 실행 중인지 확인하세요:"
|
||||
log_info " ps aux | grep uvicorn"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
|
|||
84
status.json
84
status.json
|
|
@ -1,19 +1,33 @@
|
|||
{
|
||||
"worker_status": {
|
||||
"running": true,
|
||||
"total_workers": 2,
|
||||
"total_workers": 4,
|
||||
"queue_size": 0,
|
||||
"workers_by_status": {
|
||||
"idle": [
|
||||
{
|
||||
"id": "worker_c91d2f9c",
|
||||
"id": "worker_4a61796f",
|
||||
"status": "idle",
|
||||
"task_count": 0,
|
||||
"error_count": 0,
|
||||
"last_task_at": null
|
||||
},
|
||||
{
|
||||
"id": "worker_ba00e1d4",
|
||||
"id": "worker_ef99e89d",
|
||||
"status": "idle",
|
||||
"task_count": 0,
|
||||
"error_count": 0,
|
||||
"last_task_at": null
|
||||
},
|
||||
{
|
||||
"id": "worker_da813d18",
|
||||
"status": "idle",
|
||||
"task_count": 0,
|
||||
"error_count": 0,
|
||||
"last_task_at": null
|
||||
},
|
||||
{
|
||||
"id": "worker_ed362e96",
|
||||
"status": "idle",
|
||||
"task_count": 0,
|
||||
"error_count": 0,
|
||||
|
|
@ -28,46 +42,70 @@
|
|||
},
|
||||
"session_status": {
|
||||
"simple_lama": {
|
||||
"total": 2,
|
||||
"total": 4,
|
||||
"in_use": 0,
|
||||
"available": 2
|
||||
"available": 4
|
||||
},
|
||||
"migan": {
|
||||
"total": 2,
|
||||
"total": 4,
|
||||
"in_use": 0,
|
||||
"available": 2
|
||||
"available": 4
|
||||
},
|
||||
"rembg": {
|
||||
"total": 1,
|
||||
"total": 3,
|
||||
"in_use": 0,
|
||||
"available": 1
|
||||
"available": 3
|
||||
}
|
||||
},
|
||||
"api_stats": {
|
||||
"total_requests": 1,
|
||||
"successful_requests": 1,
|
||||
"failed_requests": 0,
|
||||
"success_rate": 100.0,
|
||||
"total_requests": 31,
|
||||
"successful_requests": 29,
|
||||
"failed_requests": 2,
|
||||
"success_rate": 93.54838709677419,
|
||||
"endpoint_usage": {
|
||||
"GET /health": 1
|
||||
"GET /api/v1/health": 2,
|
||||
"GET /health": 1,
|
||||
"GET /api/v1/model": 14,
|
||||
"POST /api/v1/inpaint": 14
|
||||
},
|
||||
"endpoint_stats": {
|
||||
"GET /api/v1/health": {
|
||||
"count": 2,
|
||||
"avg_time": 0.001275777816772461,
|
||||
"min_time": 0.000980377197265625,
|
||||
"max_time": 0.0015711784362792969,
|
||||
"current_concurrent": 0
|
||||
},
|
||||
"GET /health": {
|
||||
"count": 1,
|
||||
"avg_time": 0.001737356185913086,
|
||||
"min_time": 0.001737356185913086,
|
||||
"max_time": 0.001737356185913086,
|
||||
"avg_time": 0.0077762603759765625,
|
||||
"min_time": 0.0077762603759765625,
|
||||
"max_time": 0.0077762603759765625,
|
||||
"current_concurrent": 0
|
||||
},
|
||||
"GET /api/v1/model": {
|
||||
"count": 14,
|
||||
"avg_time": 0.004632132393973214,
|
||||
"min_time": 0.0023491382598876953,
|
||||
"max_time": 0.007782697677612305,
|
||||
"current_concurrent": 0
|
||||
},
|
||||
"POST /api/v1/inpaint": {
|
||||
"count": 14,
|
||||
"avg_time": 3.2447635446275984,
|
||||
"min_time": 1.2617759704589844,
|
||||
"max_time": 14.294722318649292,
|
||||
"current_concurrent": 0
|
||||
}
|
||||
},
|
||||
"average_response_time": 0.001737356185913086,
|
||||
"min_response_time": 0.001737356185913086,
|
||||
"max_response_time": 0.001737356185913086,
|
||||
"average_response_time": 1.4678021707842428,
|
||||
"min_response_time": 0.000980377197265625,
|
||||
"max_response_time": 14.294722318649292,
|
||||
"current_concurrent": 0,
|
||||
"max_concurrent": 1,
|
||||
"requests_per_second": 0.0014694498527868262,
|
||||
"uptime": 680.5267958641052,
|
||||
"requests_per_second": 0.09576884328690756,
|
||||
"uptime": 323.69608879089355,
|
||||
"recent_errors": []
|
||||
},
|
||||
"timestamp": 1756403844.7342515
|
||||
"timestamp": 1756471050.1084073
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
Internal Server Error
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
#!/usr/bin/env python3
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
def log(msg: str):
|
||||
ts = datetime.now().strftime('%H:%M:%S')
|
||||
print(f"[{ts}] {msg}", flush=True)
|
||||
|
||||
def main():
|
||||
# 환경 설정
|
||||
u2net_home = os.path.join(os.path.expanduser('~'), '.u2net')
|
||||
os.makedirs(u2net_home, exist_ok=True)
|
||||
os.environ.setdefault('U2NET_HOME', u2net_home)
|
||||
# 캐시 파일이 있으면 체크섬 스킵
|
||||
if any(f.endswith('.onnx') for f in os.listdir(u2net_home)):
|
||||
os.environ.setdefault('MODEL_CHECKSUM_DISABLED', '1')
|
||||
|
||||
# ORT 상세로그 (원인 추적)
|
||||
os.environ.setdefault('ORT_LOG_SEVERITY_LEVEL', '0')
|
||||
os.environ.setdefault('ORT_LOG_VERBOSITY_LEVEL', '1')
|
||||
|
||||
model_name = os.environ.get('REMBG_TEST_MODEL', 'birefnet-general-lite')
|
||||
custom_path = os.environ.get('REMBG_MODEL_PATH')
|
||||
|
||||
log(f"U2NET_HOME={os.environ.get('U2NET_HOME')}")
|
||||
log(f"MODEL_CHECKSUM_DISABLED={os.environ.get('MODEL_CHECKSUM_DISABLED')}")
|
||||
log(f"REMBG_TEST_MODEL={model_name}, REMBG_MODEL_PATH={custom_path}")
|
||||
|
||||
import onnxruntime as ort
|
||||
log(f"ORT version={getattr(ort, '__version__', 'unknown')}")
|
||||
log(f"ORT device={ort.get_device()}")
|
||||
log(f"ORT available providers={ort.get_available_providers()}")
|
||||
|
||||
import rembg
|
||||
log(f"rembg version={getattr(rembg, '__version__', 'unknown')}")
|
||||
|
||||
# 세션 생성
|
||||
# 일부 버전에서 sessions dict가 공개되지 않을 수 있어 안전하게 시도
|
||||
try:
|
||||
from rembg.sessions import sessions as rembg_sessions
|
||||
log(f"rembg sessions registered (count)={len(rembg_sessions)}")
|
||||
if model_name not in rembg_sessions and model_name != 'ben_custom':
|
||||
log(f"WARN: session class not registered for '{model_name}'. Using available: {list(rembg_sessions.keys())[:10]} ...")
|
||||
except Exception as e:
|
||||
log(f"rembg.sessions registry read skipped: {e}")
|
||||
|
||||
args = []
|
||||
kwargs = {}
|
||||
if model_name == 'ben_custom':
|
||||
if not custom_path:
|
||||
print("ERROR: REMBG_MODEL_PATH must be set for ben_custom", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
kwargs['model_path'] = custom_path
|
||||
|
||||
# Jetson + ONNX 1.17 조합에서 TensorRT 충돌을 피하기 위해 CUDA 명시
|
||||
providers = ['CUDAExecutionProvider', 'CPUExecutionProvider']
|
||||
log(f"Using providers: {providers}")
|
||||
kwargs['providers'] = providers
|
||||
|
||||
log("Creating rembg session...")
|
||||
t0 = time.time()
|
||||
sess = rembg.new_session(model_name, *args, **kwargs)
|
||||
t1 = time.time()
|
||||
prov = []
|
||||
try:
|
||||
if hasattr(sess, 'inner_session') and hasattr(sess.inner_session, 'get_providers'):
|
||||
prov = sess.inner_session.get_providers() or []
|
||||
except Exception as e:
|
||||
log(f"provider read failed: {e}")
|
||||
log(f"Session created in {t1 - t0:.2f}s, providers={prov}")
|
||||
|
||||
# 테스트 이미지 생성 (단색 배경 + 전경 사각형)
|
||||
from PIL import Image, ImageDraw
|
||||
W, H = 512, 384
|
||||
img = Image.new('RGB', (W, H), (200, 220, 240))
|
||||
d = ImageDraw.Draw(img)
|
||||
d.rectangle([W//4, H//4, 3*W//4, 3*H//4], fill=(180, 50, 50))
|
||||
|
||||
# 배경제거 수행
|
||||
from rembg import remove
|
||||
log("Calling rembg.remove()...")
|
||||
t2 = time.time()
|
||||
out = remove(img, session=sess)
|
||||
t3 = time.time()
|
||||
log(f"remove() finished in {t3 - t2:.2f}s")
|
||||
|
||||
# 결과 저장
|
||||
out_dir = os.path.join(os.getcwd(), 'outputs')
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
out_path = os.path.join(out_dir, f'rembg_test_{model_name}.png')
|
||||
try:
|
||||
if hasattr(out, 'save'):
|
||||
out.save(out_path)
|
||||
else:
|
||||
from PIL import Image as PILImage
|
||||
PILImage.open(out).save(out_path)
|
||||
log(f"Result saved: {out_path}")
|
||||
except Exception as e:
|
||||
log(f"Result save failed: {e}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
|
||||
Loading…
Reference in New Issue