GPU 사용 최적화를 위해 개선한 코드 - 100-hours-a-week/5-yeosa-wiki GitHub Wiki
1. 이미지 전처리 작업 병렬화
a. 변경 코드
-
기존 코드
for i in range(0, len(images), batch_size): batch_images = images[i:i + batch_size] preprocessed_batch = torch.stack([preprocess(img) for img in batch_images]) preprocessed_batches.append(preprocessed_batch)
-
수정한 코드
# ThreadPoolExecutor 생성 with ThreadPoolExecutor() as executor: # 전처리 함수를 lambda로 정의 preprocess_func = lambda img: preprocess(img) for i in range(0, len(images), batch_size): batch_images = images[i:i + batch_size] # 병렬로 전처리 수행 preprocessed_batch = list(executor.map(preprocess_func, batch_images)) preprocessed_batch = torch.stack(preprocessed_batch) preprocessed_batches.append(preprocessed_batch)
b. GIL 환경에서도 병렬화 효과가 있나? → 있다!
preprocess(img)가 GIL을 해제하는 C/CUDA 연산 기반이면, ThreadPoolExecutor를 쓰는 병렬화는 실제로 성능 이점이 있다.
가. Python GIL과 C/CUDA 연산의 관계
-
Python은 인터프리터 수준의 코드(Python bytecode) 실행에는 GIL이 걸려 있음
-
하지만 NumPy, PyTorch, PIL 등 C/C++로 구현된 연산은 GIL을 자동으로 해제함
# 예시: 아래 연산들은 대부분 GIL을 해제함 torch.Tensor operations numpy.dot() PIL.Image.resize()
-
즉,
preprocess(img)
안에서PIL.Image
변환,transforms.Resize
,ToTensor
,Normalize
같은 PyTorch Transform 사용 시내부적으로 C/C++ 또는 CUDA 연산 → GIL 해제 → 병렬 처리 가능
OpenAI CLIP)
나. 서비스에서 사용하는 preprocess(from torchvision import transforms
def _transform(n_px):
return transforms.Compose([
transforms.Resize(n_px, interpolation=Image.BICUBIC),
transforms.CenterCrop(n_px),
transforms.ToTensor(),
transforms.Normalize(
mean=[0.48145466, 0.4578275, 0.40821073],
std=[0.26862954, 0.26130258, 0.27577711])
])
-
각각의 Transform이 GIL을 어떻게 처리하나?
Transform 내부 연산 GIL 해제 여부 비고 Resize
PIL.Image.resize (C기반) ✅ GIL 해제 빠름 CenterCrop
PIL + NumPy 슬라이싱 ✅ 대부분 해제 빠름 ToTensor
PIL → Tensor 변환 ✅ C 확장 모듈 빠름 Normalize
Tensor 연산 (C++/CUDA) ✅ GIL 해제 GPU에서도 효율적 -
즉, 전체적으로 모두 GIL 해제를 유도하는 연산들이며,
ThreadPoolExecutor
를 사용하면 병렬 처리의 실효성이 매우 높음
preprocess(img)
는 병렬 처리하기에 적합한 작업
다. 결론: 지금의 전처리 - GIL 영향을 거의 받지 않음 (PIL + Tensor transform 기반)
- ThreadPoolExecutor로 묶어도 context switching 비용보다 성능 이득이 큼
- 특히
batch_size = 32
이상이면 체감 성능 향상 분명함