본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] Python 3.12 Per-Interpreter GIL이 AI 병렬 처리 성능을 해결하는 7가지 방법과 기존 방식과의 차이

by Papa Martino V 2026. 4. 14.
728x90

Python 3.12 Per-Interpreter GIL
Python 3.12 Per-Interpreter GIL

 

파이썬 개발자들, 특히 AI와 데이터 사이언스 분야에 종사하는 전문가들에게 GIL(Global Interpreter Lock)은 오랫동안 극복해야 할 거대한 벽과 같았습니다. 멀티 코어 CPU 시대임에도 불구하고, 파이썬의 표준 구현체인 CPython은 한 번에 하나의 스레드만 바이트코드를 실행할 수 있도록 제한했기 때문입니다. 하지만 Python 3.12에서 도입되고 3.13에서 구체화된 Per-Interpreter GIL 구조는 이 패러다임을 완전히 바꾸고 있습니다. 본 포스팅에서는 단순한 이론을 넘어, AI 병렬 처리 모델 서빙과 대규모 데이터 전처리 현업에서 Per-Interpreter GIL이 어떤 실질적인 해결책을 제시하는지, 그리고 기존 multiprocessing 방식과 어떤 차이가 있는지 심도 있게 분석합니다.


1. Per-Interpreter GIL: 무엇이 다른가?

기존의 파이썬은 프로세스 전체에 단 하나의 GIL만 존재했습니다. 반면, PEP 684를 통해 도입된 방식은 각 서브 인터프리터(Sub-interpreter)가 자신만의 독립적인 GIL을 가집니다. 이는 하나의 프로세스 안에서 여러 인터프리터가 진정한 병렬 실행을 할 수 있음을 의미합니다.

기존 멀티프로세싱 vs Per-Interpreter 병렬 처리 비교

비교 항목 Multiprocessing (기존 방식) Per-Interpreter GIL (신규 방식)
메모리 오버헤드 높음 (프로세스마다 복제) 낮음 (프로세스 내 메모리 공유 가능)
통신 속도 (IPC) 느림 (Pickle 직렬화 필요) 빠름 (메모리 포인터/Buffer 공유 가능)
시작 시간 느림 (OS 레벨 fork/spawn) 매우 빠름 (인터프리터 스폰)
병렬성 수준 프로세스 단위 인터프리터 단위 (멀티코어 활용)

2. AI 병렬 처리를 위한 7가지 실무 적용 코드 (Sample Examples)

아래 예제들은 Python 3.12+ 환경에서 interpreters 모듈(현재는 _interpreters 또는 interpreters 라이브러리)을 사용하여 AI 추론이나 데이터 로딩을 최적화하는 실무 패턴을 보여줍니다.

Ex 1. 독립 인터프리터 생성 및 기본 실행

프로세스를 새로 띄우지 않고도 격리된 환경에서 코드를 병렬로 실행하는 기초 방법입니다.

import interpreters
import threading

def run_ai_task(interp_id):
    # 각 인터프리터는 독립된 GIL을 가집니다.
    interp = interpreters.create()
    interp.run("print('AI 추론 엔진 가동 중...')")

# 여러 개의 스레드에서 각기 다른 인터프리터 실행
t = threading.Thread(target=run_ai_task, args=(1,))
t.start()
t.join()
    

Ex 2. AI 모델 로딩 오버헤드 해결: 가벼운 인터프리터 스위칭

대규모 모델 서빙 시 메모리를 효율적으로 관리하며 인터프리터를 할당합니다.

import _interpreters as interpreters

# 모델 경로를 공유 메모리처럼 활용하여 각 인터프리터에서 로드
model_path = "/models/vgg16_weights.bin"
interp = interpreters.create()

# 인터프리터 내부에서 격리된 실행
code = f"""
import my_ai_library
model = my_ai_library.load('{model_path}')
result = model.predict([1, 2, 3])
print(result)
"""
interp.run(code)
    

Ex 3. 공유 메모리를 활용한 대규모 데이터 전처리

기존 멀티프로세싱의 Pickle 오버헤드 없이 데이터를 전달하는 고성능 매핑 방식입니다.

from multiprocessing import shared_memory
import numpy as np
import interpreters

# 1. 메인 인터프리터에서 데이터 생성
data = np.random.rand(1000, 1000)
shm = shared_memory.SharedMemory(create=True, size=data.nbytes)
shared_array = np.ndarray(data.shape, dtype=data.dtype, buffer=shm.buf)
shared_array[:] = data[:]

# 2. 서브 인터프리터에서 공유 메모리 주소만 넘겨받아 연산
interp = interpreters.create()
interp.run(f"import numpy as np; from multiprocessing import shared_memory; "
           f"shm = shared_memory.SharedMemory(name='{shm.name}'); "
           f"data = np.ndarray({data.shape}, dtype='{data.dtype}', buffer=shm.buf); "
           f"print('전처리 결과:', np.mean(data))")
    

Ex 4. 채널(Channels)을 이용한 인터프리터 간 메시지 큐

복잡한 AI 파이프라인에서 단계별 데이터를 안전하게 주고받는 해결법입니다.

import interpreters

input_ch = interpreters.create_channel()
output_ch = interpreters.create_channel()

interp = interpreters.create()
# 워커 인터프리터: 입력을 받아 결과 전송
code = f"""
import interpreters
ch_in = interpreters.Channel({input_ch.id})
ch_out = interpreters.Channel({output_ch.id})
data = ch_in.recv()
ch_out.send(f"Processed: {{data}}")
"""
interp.run_in_thread(code)

input_ch.send("Raw Image Data")
print(output_ch.recv())
    

Ex 5. 다중 인터프리터를 활용한 병렬 하이퍼파라미터 튜닝

동시에 여러 모델 실험을 진행할 때 GIL 충돌 없이 CPU 코어를 100% 활용합니다.

import interpreters

params = [0.01, 0.1, 0.5]
workers = []

for lr in params:
    itp = interpreters.create()
    code = f"lr = {lr}; print(f'Training with learning rate: {{lr}}')"
    itp.run_in_thread(code)
    workers.append(itp)
    

Ex 6. 서브 인터프리터 내의 독립적인 모듈 상태 관리

전역 변수 충돌 문제를 해결하여 안전한 병렬 테스트 환경을 구축합니다.

import interpreters

# 각 인터프리터는 자신만의 sys.modules를 가집니다.
i1 = interpreters.create()
i2 = interpreters.create()

i1.run("import sys; sys.custom_flag = True")
i2.run("import sys; print('Has flag?', hasattr(sys, 'custom_flag'))") # False 출력
    

Ex 7. AI API 게이트웨이 병렬 처리 최적화

수천 개의 요청이 들어오는 게이트웨이에서 I/O 바운드와 CPU 바운드 작업을 분리 매핑합니다.

import interpreters
import concurrent.futures

def handle_request(req_id):
    itp = interpreters.create()
    # 암호화 연산이나 토크나이징 등 CPU 작업 수행
    itp.run(f"print('Request {req_id} tokenized in separate GIL')")

with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
    executor.map(handle_request, range(10))
    

3. 결론 및 향후 전망: AI 아키텍처의 변화

Python 3.12의 Per-Interpreter GIL 도입은 AI 서비스 개발자들에게 강력한 도구를 제공합니다. 기존 multiprocessing의 무거운 오버헤드와 threading의 GIL 제약을 동시에 극복할 수 있는 중도적이며 효율적인 길을 열었기 때문입니다.

앞으로는 PyTorch나 TensorFlow 같은 핵심 AI 라이브러리들이 서브 인터프리터를 공식 지원함에 따라, 파이썬 기반의 추론 서버 성능이 비약적으로 상승할 것으로 기대됩니다. 개발자들은 이제 '프로세스' 단위의 설계에서 '인터프리터' 단위의 세밀한 병렬 설계로 전환할 준비를 해야 합니다.

 

내용 출처 및 참고 문헌:
1. Python Software Foundation, "PEP 684 – A Per-Interpreter GIL", 2023.
2. Eric Snow, "Subinterpreters and the GIL", CPython core development notes.
3. Python 3.12 Release Documentation, "What’s New In Python 3.12".

728x90