- prediction/image/ FastAPI 서버 Docker 환경 구성 - Dockerfile: PyTorch 2.1 + CUDA 12.1 기반 GPU 이미지 - docker-compose.yml: GPU 할당 + 데이터 볼륨 마운트 - requirements.txt: 서버 의존성 목록 - .env.example: 환경변수 템플릿 - DOCKER_USAGE.md: 빌드/실행/API 사용법 문서 - Dockerfile에 .dockerignore 제외 폴더 mkdir -p 추가 - .gitignore: prediction/image 결과물 및 모델 가중치(.pth) 제외 추가 - dbInsert_csv.py, dbInsert_shp.py 삭제 (미사용 DB 로직) - api.py: dbInsert import 및 주석 처리된 DB 호출 코드 제거 - aerialRouter.ts: req.params 타입 오류 수정
135 lines
5.8 KiB
Markdown
135 lines
5.8 KiB
Markdown
# run-script API 성능 최적화 기록
|
|
|
|
## 결과 요약
|
|
|
|
| 단계 | 소요 시간 |
|
|
|------|----------|
|
|
| 최적화 전 | 35.12초 |
|
|
| 1차 최적화 후 | ~12초 |
|
|
| 2차 최적화 후 | ~8~10초 (예상) |
|
|
|
|
---
|
|
|
|
## 1차 최적화 (~35초 → ~12초)
|
|
|
|
### 문제 원인
|
|
|
|
| 위치 | 원인 |
|
|
|------|------|
|
|
| `Inference.py` 모듈 레벨 | `init_segmentor()` 호출 → 요청마다 GPU 모델 재로딩 (15~20초) |
|
|
| `api.py` | `subprocess.run()` 으로 `Combine_module.py` 실행 → 매 요청마다 Python 인터프리터 재시작 |
|
|
| `Combine_module.py` | Step1~4를 순차 subprocess 4개로 실행 |
|
|
| `Georeference.py` 내부 루프 | `np.ones((3,3), np.uint8)` dilate 커널을 루프마다 재생성 |
|
|
| `Oilshape.py pixel_to_geo()` | `rasterio.transform.xy()` 를 좌표 1개씩 루프로 호출 |
|
|
| `Inference.py` 내부 루프 | `palette_array = np.array(model.PALETTE)` 를 이미지마다 재생성 |
|
|
|
|
### 개선 내용
|
|
|
|
**`mx15hdi/Detect/Inference.py`**
|
|
- `load_model()` 함수 분리 — 모델 초기화를 서버 시작 시 1회로 분리
|
|
- `run_inference(model, file_id)` 함수화 — 사전 로드된 모델을 인자로 수신
|
|
- `palette_array` 루프 외부로 이동 (이미지마다 재생성 제거)
|
|
- `cv2.cvtColor` 제거 → numpy 슬라이싱 `color_mask[:, :, ::-1].copy()` 로 대체
|
|
|
|
**`mx15hdi/Metadata/Scripts/Export_Metadata_mx15hdi.py`**
|
|
- 모듈 레벨 `PaddleOCR()` 초기화 제거 → `_get_ocr_engine()` lazy 초기화로 변경
|
|
- `run_metadata_export(file_id)` 함수화 + 절대 경로(`_MX15HDI_DIR`) 기반으로 전환
|
|
- deprecated API 수정: `fillna(method='ffill')` → `ffill()` / `bfill()`
|
|
|
|
**`mx15hdi/Georeference/Scripts/Create_Georeferenced_Images_nadir.py`**
|
|
- `run_georeference(file_id)` 함수화 + 절대 경로 기반으로 전환
|
|
- `dilate_kernel = np.ones((3,3), np.uint8)` 루프 외부로 이동 (루프마다 재생성 제거)
|
|
|
|
**`mx15hdi/Polygon/Scripts/Oilshape.py`**
|
|
- `run_oilshape(file_id)` 함수화 + 절대 경로 기반으로 전환
|
|
- `pixel_to_geo()` 벡터화: 좌표 배열 일괄 처리 (`rasterio.transform.xy` 배열 입력)
|
|
|
|
**`mx15hdi/Main/Combine_module.py`**
|
|
- subprocess 4개 → 직접 함수 호출로 교체
|
|
- `run_pipeline(file_id, model=None)` 함수 추가
|
|
|
|
**`api.py`**
|
|
- FastAPI `lifespan` 이벤트로 서버 시작 시 모델 1회 로딩
|
|
- `ThreadPoolExecutor(max_workers=4)` 추가
|
|
- `_run_mx15hdi_pipeline()` 비동기 함수: Step1(추론) + Step2(메타데이터)를 `asyncio.gather`로 병렬 실행
|
|
|
|
---
|
|
|
|
## 2차 최적화 (~12초 → ~8~10초)
|
|
|
|
### 문제 원인
|
|
|
|
| 위치 | 원인 |
|
|
|------|------|
|
|
| `Inference.py` | `cv2.imread(image_path)` 로드 후 `inference_segmentor(model, image_path)` 에 경로 문자열 전달 → mmseg 내부에서 동일 이미지 재읽기 |
|
|
| `Inference.py` → `Georeference.py` | blended/mask를 `Detect/result/`, `Detect/Mask_result/` 에 저장 후 georeference에서 다시 읽음 (디스크 왕복 1) |
|
|
| `Georeference.py` → `Oilshape.py` | mask를 `Georeference/Mask_Tif/` 에 LZW 압축 GeoTIF로 저장 후 oilshape에서 다시 읽음 (디스크 왕복 2) |
|
|
| `Georeference.py` | `scipy.ndimage.binary_dilation` 사용 (Python 구현, OpenCV 대비 느림) |
|
|
| `Georeference.py` | rgb와 mask의 빈 픽셀 fill_mask를 중복 계산 |
|
|
|
|
### 개선 내용
|
|
|
|
**`mx15hdi/Detect/Inference.py`**
|
|
- `inference_segmentor(model, img_bgr)` — 경로 대신 배열 직접 전달 (이중 읽기 제거)
|
|
- `write_files: bool = False` 파라미터 추가 — 중간 파일 저장 선택적 처리
|
|
- `inference_cache` dict 반환: `{filename: {'blended': ndarray, 'mask': ndarray, 'ext': str}}`
|
|
|
|
**`mx15hdi/Georeference/Scripts/Create_Georeferenced_Images_nadir.py`**
|
|
- `inference_cache: dict = None` 파라미터 추가 — 있으면 메모리 배열 사용, 없으면 디스크 폴백
|
|
- `scipy.ndimage.binary_dilation` 제거 → `cv2.dilate` 로 통일
|
|
- rgb/mask 공통 `fill_mask` 재사용 — 중복 계산 제거
|
|
- Mask_Tif GeoTIF 디스크 저장 생략 — `georef_cache` 로 반환
|
|
- `georef_cache` dict 반환: `{filename: {'mask': ndarray, 'transform': ..., 'crs': ...}}`
|
|
|
|
**`mx15hdi/Polygon/Scripts/Oilshape.py`**
|
|
- `georef_cache: Optional[dict] = None` 파라미터 추가
|
|
- 있으면 메모리 배열 직접 처리, 없으면 `Mask_Tif/` 디스크 폴백
|
|
- `_process_mask_entry()` 헬퍼 함수 분리
|
|
|
|
**`api.py`**
|
|
- `_run_mx15hdi_pipeline()` 캐시 체이닝:
|
|
```python
|
|
inference_cache, _ = await asyncio.gather(
|
|
loop.run_in_executor(_executor, run_inference, _model, file_id),
|
|
loop.run_in_executor(_executor, run_metadata_export, file_id),
|
|
)
|
|
georef_cache = await loop.run_in_executor(
|
|
_executor, run_georeference, file_id, inference_cache
|
|
)
|
|
await loop.run_in_executor(_executor, run_oilshape, file_id, georef_cache)
|
|
```
|
|
|
|
### 디스크 I/O 흐름 변화
|
|
|
|
**최적화 전:**
|
|
```
|
|
Inference → Detect/result/ (write)
|
|
Detect/Mask_result/ (write)
|
|
Georeference ← Detect/result/ (read) ← 왕복 1
|
|
Detect/Mask_result/ (read)
|
|
Georeference → Georeference/Mask_Tif/ (write)
|
|
Oilshape ← Georeference/Mask_Tif/ (read) ← 왕복 2
|
|
```
|
|
|
|
**최적화 후:**
|
|
```
|
|
Inference → inference_cache (메모리)
|
|
Georeference ← inference_cache (메모리) ← 왕복 1 제거
|
|
Georeference → georef_cache (메모리)
|
|
Oilshape ← georef_cache (메모리) ← 왕복 2 제거
|
|
Georeference → Georeference/Tif/ (write) ← /get-image/ API 전용, 유지
|
|
```
|
|
|
|
---
|
|
|
|
## 수정 파일 목록
|
|
|
|
| 파일 | 1차 | 2차 |
|
|
|------|-----|-----|
|
|
| `mx15hdi/Detect/Inference.py` | ✅ | ✅ |
|
|
| `mx15hdi/Metadata/Scripts/Export_Metadata_mx15hdi.py` | ✅ | — |
|
|
| `mx15hdi/Georeference/Scripts/Create_Georeferenced_Images_nadir.py` | ✅ | ✅ |
|
|
| `mx15hdi/Polygon/Scripts/Oilshape.py` | ✅ | ✅ |
|
|
| `mx15hdi/Main/Combine_module.py` | ✅ | — |
|
|
| `api.py` | ✅ | ✅ |
|