24.0%. 거대 언어 모델 추론 과정에서 GPU가 연산을 멈추고 CPU의 다음 작업을 기다리며 허비하는 시간의 비율이다. 이는 전체 생성 시간의 약 4분의 1에 해당하며, 고성능 하드웨어를 가동하고도 자원 효율성이 크게 떨어지는 원인이 된다. 마치 고속도로를 달리는 자동차가 톨게이트마다 멈춰 서서 통행료를 계산하느라 정작 주행 시간보다 대기 시간이 길어지는 상황과 같다. 그런데 이 비효율은 하드웨어 성능의 한계가 아니라, 소프트웨어 설계 방식에서 기인한 구조적 문제다.

동기식 배치 처리의 한계와 연산 공백

기존의 연속 배치 처리(Continuous Batching, 여러 요청을 묶어 GPU 연산 효율을 높이는 기술)는 기본적으로 동기식으로 작동한다. CPU가 다음 배치에 포함할 요청을 선택하고, KV 캐시(KV Cache, 이전 토큰의 연산 결과를 저장해 재사용하는 메모리 영역)를 업데이트하며, 완료된 요청을 제거하는 동안 GPU는 아무런 작업을 수행하지 못한다. 반대로 GPU가 연산을 수행하는 동안 CPU는 다음 배치를 준비하지 못하고 대기한다. 8B 파라미터 모델을 사용하여 32개의 배치 크기로 8K 토큰을 생성하는 환경에서 프로파일링을 수행한 결과, 전체 300.6초의 시간 중 24.0%가 GPU 유휴 상태로 나타났다. 이는 연산 효율을 극대화할 수 있는 잠재적인 성능 향상 구간이 존재함을 의미한다.

비동기 배치를 통한 연산 병렬화

이 문제를 해결하는 핵심은 CPU의 배치 준비 작업과 GPU의 연산 작업을 분리하여 병렬로 실행하는 것이다. 이를 위해 CUDA 스트림(CUDA Streams, GPU 연산 작업을 순차적으로 처리하는 대기열)을 활용한다. 일반적으로 PyTorch(파이토치, 인공지능 모델을 개발하고 학습시키는 도구)에서 별도의 스트림을 지정하지 않으면 모든 작업은 기본 스트림에서 실행된다. 기본 스트림은 동기화 속성을 가지고 있어, 이전 작업이 완전히 끝날 때까지 다음 작업을 대기시킨다. 반면, 서로 다른 스트림에 작업을 할당하면 GPU는 이를 독립적인 작업으로 인식하여 동시에 처리할 수 있다.

CUDA 스트림을 활용한 구현 전략

비동기 배치를 구현하기 위해서는 CPU가 N+1번째 배치를 준비하는 동안 GPU가 N번째 배치를 연산하도록 조정해야 한다. 이를 위해서는 연산의 성격을 분류하고, 각 작업을 적절한 스트림에 배치하는 정교한 제어가 필요하다. 실제 구현 시에는 transformers 라이브러리의 연속 배치 처리 코드 내에서 스트림 관리 로직을 수정해야 한다. 단순히 커널이나 모델을 변경하는 것이 아니라, 하드웨어 간의 작업 흐름을 조정하는 것만으로도 이론적으로 24%의 속도 향상을 기대할 수 있다. 작업 흐름을 시각화하고 CPU와 GPU의 활동 범위를 추적하기 위해 프로파일링 스크립트를 활용하면 각 단계에서 발생하는 지연을 명확히 확인할 수 있다.

하드웨어 자원을 100% 활용하는 것은 모델의 크기나 연산 능력만큼이나 소프트웨어의 비동기 설계 능력에 달려 있다.