Multi Stream 재도입 테스트 - 100-hours-a-week/5-yeosa-wiki GitHub Wiki

1. 개요

a. 배경 및 문제 인식

  • 현재 시스템은 단일 스트림 기반으로 작동

    → GPU 사용률이 70% 수준에서 정체, CPU도 60%로 활용

  • 처음 멀티 스트림을 도입했을 때와 달리 현재는 CPU 사용률에 여유가 있기에 다시 한 번 멀티 스트림 도입 추진

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에 따른 컨텍스트 스위칭/락 경합이 제거되어 예측 가능한 처리 시간 확보 가능