[GPU 모델 병렬 처리] Single GPU Inference Using GPU to the Max Potential 요약 - 100-hours-a-week/5-yeosa-wiki GitHub Wiki
원문 : https://vasusharma7.medium.com/single-gpu-inference-using-gpu-to-the-max-potential-8b2aebf5ca7b
1. 문제 상황
- 저자는 FastAPI + PyTorch 기반으로 이미지 인퍼런스를 처리하는 API 서버 운영
- 클라이언트로부터 비동기 요청이 여러 개 동시에 들어오지만, GPU 사용률이 낮고 응답 시간 개선이 없음
- 일반적인
async def
핸들러로도 GPU 추론은 직렬처럼 수행됨. - 원인 추정: Python GIL 및 PyTorch의 단일 스레드 GPU 실행 환경에서 비동기 요청이 병렬로 처리되지 않음.
2. 실패한 시도들
a. CUDA Streams
s = torch.cuda.Stream()
with torch.cuda.stream(s):
outputs = model(inputs)
- 여러 개의 CUDA stream을 생성하여 각각의 요청을 분리된 stream에서 실행.
- 기대: GPU 커널을 stream 별로 병렬 실행하여 자원 활용 극대화.
- 결과: 효과 없음.
- 모델이 작은 편이라 각 stream이 처리하는 연산량이 작고, 커널 실행 자체가 빠름.
- stream 간 오버랩이 발생하기도 전에 연산 종료됨.
b. Multiprocessing
- 요청당 별도의 프로세스를 할당하여 GPU 작업 실행 시도.
- 목적: GIL 회피 및 GPU 연산 병렬화.
- 결과: 실패.
- 구체적 이유는 명시되지 않았지만, PyTorch 모델이 메모리를 공유하지 못하거나 CUDA context 문제 등으로 병렬성이 확보되지 않음.
c. Python 3.13 Free-threading
- GIL 제거가 실험적으로 도입된 Python 3.13을 테스트해보고자 함.
- 결과: 시도하지 못함.
- PyTorch, FastAPI 등의 핵심 라이브러리가 아직 호환되지 않음.
3. 최종 해결책: 요청을 모아서 배치 처리(동적 배치 처리)
a. 핵심 아이디어
- 단일 요청을 하나하나 처리하지 않고, 짧은 시간 동안 요청들을 큐에 모아두었다가 한번에 배치 처리.
- 이 배치를 모델에 통째로 넣어 forward 함으로써 GPU를 한 번에 더 많이 활용.
b. 구현 방식 (예시 코드)
가. FastAPI 엔드포인트
@app.post("/process")
async def process(request: Request):
body = await request.json()
uid = uuid.uuid4()
request_queue.put({"id": uid, "inp": body["input"]})
while uid not in response:
await asyncio.sleep(0.01)
resp = response[uid]
del response[uid]
return resp
나. 백그라운드 워커 (추론 실행)
def process_requests():
while True:
if request_queue.empty():
continue
requests = []
while not request_queue.empty():
requests.append(request_queue.get())
inputs = [r["inp"] for r in requests]
uids = [r["id"] for r in requests]
preds = model(inputs) # 👉 배치 인퍼런스
for i, uid in enumerate(uids):
response[uid] = preds[i]
다. 설명
request_queue
: 요청을 임시 저장하는 큐response
: 결과를 각 요청 ID와 매핑하여 반환process_requests
: 별도의 스레드에서 동작하며, 큐에 쌓인 요청들을 모아 배치로 처리함
c. 성능 결과
- 요청 수가 늘어날수록 배치 크기가 자연스럽게 커지며 GPU 사용률 증가.
- GPU 연산 시간이 조금 늘었지만, 전체 처리량(throughput)은 40% 이상 증가.
- NVIDIA-SMI 기준 GPU 메모리 및 연산 활용률이 이전보다 눈에 띄게 증가.
4. 실무적인 교훈
- 단순 비동기 구조만으로는 GPU 병렬화 효과를 얻기 어려움.
- stream 병렬화도 모델이 작을 경우 효과가 없음.
- 동적 배치(Dynamic Batching)가 가장 효과적인 GPU 활용 방법.
- 모델이 크지 않아도, 요청을 묶어서 처리하는 방식으로 자원 활용 극대화 가능.
- 배치 처리 시 latency 증가 없이 throughput 향상 가능 (적절한 큐 대기 시간 설정 필요).
5. 결론
- 이 글은 PyTorch 모델을 FastAPI 서버에서 서빙할 때 GPU를 최대한 활용하려는 여러 시도와 그 성과를 공유.
- 특히, 현실적인 제약(Python GIL, CUDA stream의 한계 등) 속에서 가장 효과적이었던 해결책은 “동적 배치 처리”라는 점을 실험으로 증명
- 작성자는 이 방식만으로도 단일 GPU의 잠재력을 훨씬 더 끌어낼 수 있었다고 결론