Multi Stream 재도입 테스트 - 100-hours-a-week/5-yeosa-wiki GitHub Wiki
1. 개요
a. 배경 및 문제 인식
b. 멀티스트림의 기대 효과
구간 |
개선 기대 |
GPU 사용률 |
멀티스트림으로 커널 실행 병렬화 → 90% 이상 도달 목표 |
CPU 자원 활용 |
병렬 요청 처리를 통해 idle 시간 감소 |
총 처리 시간 |
개별 요청 처리 시간은 유지되더라도, 전체 처리량(Throughput) 증가 |
c. 실험 계획 요약
항목 |
목표 |
스트림 수 별 성능 측정 |
1 / 2 / 4 스트림 별 리소스 사용률 및 응답시간 비교 |
자원 사용률 분석 |
top , gpustat |
2. 실험 결과
a. 리소스 사용률
리소스 |
스트림1 |
스트림2 |
변화율 (1→2) |
스트림4 |
변화율 (1→4) |
CPU |
63.86% |
65.90% |
▲ +3.2% |
65.75% |
▲ +2.96% |
GPU |
76.48% |
82.05% |
▲ +7.3% |
89.59% |
▲ +17.2% |
- 스트림 수가 증가함에 따라 GPU 사용률이 최대 89.6%까지 상승
- CPU는 소폭 증가 → 기존 대비 포화되지 않음
b. 소요 시간(ms, p95)
단계 |
스트림1 |
스트림2 |
변화율 (1→2) |
스트림4 |
변화율 (1→4) |
이미지 로딩 |
179.81 |
175.55 |
▼ –2.37% |
194.16 |
▲ +7.99% |
디코딩 |
18.04 |
17.91 |
▼ –0.72% |
17.25 |
▼ –4.38% |
디코딩 대기 |
79.04 |
79.14 |
▲ +0.13% |
76.67 |
▼ –3.00% |
로딩 + 디코딩 |
361.08 |
394.97 |
▲ +9.41% |
393.14 |
▲ +8.87% |
전처리 |
162.31 |
160.79 |
▼ –0.94% |
146.88 |
▼ –9.51% |
임베딩 |
292.17 |
172.76 |
▼ –40.90% |
140.23 |
▼ –52.00% |
총 시간 |
903.35 |
825.21 |
▼ –8.65% |
790.37 |
▼ –12.51% |
c. 분석
가. 멀티스트림으로 GPU 활용률 대폭 개선
- GPU 사용률이 스트림 1 대비 17.2% 향상 → 연산 커널 간 병렬 실행이 가능해졌음을 의미
- CPU는 병목 없이 안정적으로 동작 중
나. 임베딩 단계에서 가장 큰 성능 향상
- 스트림 2: –40.9%, 스트림 4: –52.0%
- 동일 시간대에 여러 이미지 배치를 병렬 실행함으로써 GPU가 대기 없이 연산에 집중
다. 로딩 + 디코딩 구간은 소폭 증가
- 요청 수가 많아지며 GCS에서 동시에 이미지 로딩 → I/O 병목 가능성 존재
- 그러나 증가폭은 총 시간 대비 제한적
라. 총 처리 시간 단축
- 스트림 4 기준, 12.5% 감소
- Throughput 측면에서 스트림 수 증가에 따른 선형적 향상 경향
3. 다음 단계
a. 문제 인식
- 멀티스트림 도입 이후 GPU 사용률은 90%에 근접, 임베딩 처리 시간도 크게 단축되었음
- 그러나 여전히 로딩 + 디코딩 시간은 스트림 수 증가에 따라 증가 추세
단계 |
스트림 1 |
스트림 2 |
스트림 4 |
로딩 + 디코딩 |
361.08ms |
394.97ms |
393.14ms |
→ I/O가 더 이상 병렬화되지 않아 병목 구간이 됨
b. 원인 추론
가. 로딩 + 디코딩
단계에서 성능 저하가 지속
- 스트림 수가 증가해도 해당 구간의 처리 속도는 오히려 증가하는 경향
- 이는 GPU 자원이 아닌 FastAPI 서버의 전처리 파이프라인 내부 병목으로 의심됨
나. 단일 이벤트 루프의 구조적 한계
- FastAPI는 단일 스레드 이벤트 루프에서 async I/O를 관리
await
+ run_in_executor()
조합으로 디코딩 작업을 분리하고 있으나,
- 디코딩은 CPU-bound 작업이며,
ThreadPoolExecutor
는 GIL(Global Interpreter Lock)의 영향을 받음
- 결과적으로:
- 동시에 여러 요청이 들어오면, 각 요청의 디코딩이 스레드 경합 상태로 실행
- 이벤트 루프가 I/O 작업을 스케줄링하는 데 필요한 응답성을 상실
- → 디코딩 대기 시간 증가, 전체 처리 지연 유발
b. 멀티프로세스 도입 목적
가. 멀티프로세스는 GIL의 한계를 벗어날 수 있는 구조
- Python의 GIL은 스레드 간 자원 공유 안전성을 보장하지만, 진정한 병렬 처리를 제한
multiprocessing
또는 ProcessPoolExecutor
를 사용하면:
- 각 프로세스는 자체 메모리 공간과 GIL을 독립적으로 보유
- 디코딩과 같은 CPU 집약적 작업을 병렬로 실행할 수 있음
나. 멀티프로세스로 디코딩 병렬화 시 기대 효과
항목 |
기대 효과 |
이벤트 루프 응답성 |
디코딩을 외부 프로세스로 분리함으로써 메인 루프는 비동기 I/O에 집중 가능 |
디코딩 대기 시간 |
GIL 해소 → 디코딩 처리가 병렬화되어 전체 지연 감소 |
전체 처리 시간 |
이미지가 더 빠르게 GPU로 전달되어, 후속 임베딩도 빨라짐 |
시스템 안정성 |
GIL에 따른 컨텍스트 스위칭/락 경합이 제거되어 예측 가능한 처리 시간 확보 가능 |