추론의 단계

LLM의 추론은 입력 프롬프트를 한꺼번에 처리하는 Prefilling과 토큰을 하나씩 생성하는 Decoding 단계로 나뉜다. Prefilling은 모든 토큰의 관계를 병렬로 계산하여 GPU의 연산 대역폭을 최대한 활용하지만, 자기회귀(Autoregressive) 구조인 Decoding 단계는 직전 토큰만을 입력받아 다음 토큰을 생성한다. 이 과정에서 별도의 최적화가 없다면, 모델은 새 토큰을 만들 때마다 과거의 모든 토큰을 처음부터 다시 훑어야 하는 구조적 모순에 빠진다.

연산 비효율

최적화 없는 Decoding은 매 스텝 전체 시퀀스에 대한 Attention Map을 새로 그려야 함을 의미한다. 을 얻기 위해 부터 까지의 관계를 매번 다시 행렬 연산으로 처리하므로, 한 스텝당 시간 복잡도는 에 달한다. 문장이 길어질수록 연산량은 제곱 비례하여 폭증하며, 이는 모델 규모가 커질수록 실시간 서비스가 불가능할 정도의 성능 저하를 야기하는 심각한 낭비 요소가 된다.

KV Cache 원리

이 중복 연산을 해결하는 전략이 바로 메모이제이션 기법을 활용한 KV Cache이다. Transformer 연산에서 (Key)는 토큰의 정보를, (Value)는 전달할 실제 값을 의미한다. KV Cache는 이전 스텝에서 계산된 모든 토큰의 벡터를 GPU VRAM에 저장한다. 새로운 토큰 이 들어오면 해당 토큰의 만 새로 계산한 뒤, 캐시된 에 현재의 을 결합(Concatenation)하여 Attention을 수행한다. 결과적으로 는 최신 토큰에 대해서만 존재하게 되어 복잡도가 으로 선형적으로 급감한다.

메모리 병목

연산 효율을 얻은 대가는 막대한 VRAM 점유이다. 캐시 데이터 크기는 [Batch Size × Layers × Heads × Seq Length × Head Dim]에 비례하여 기하급수적으로 커진다. 문맥이 길어질수록 캐시 용량이 모델 파라미터 크기를 위협하며, 이는 GPU가 처리 가능한 배치 크기를 제한하는 새로운 병목이 된다. 즉, 추론의 성격이 GPU 연산력(FLOPs)에 의존하는 Compute-bound에서, 캐시 데이터를 메모리에서 얼마나 빨리 퍼 올리느냐를 다루는 Memory-bound 작업으로 변모하게 된다.

최적화 기법

이러한 메모리 한계를 극복하기 위해 현대적 엔진들은 다양한 기법을 동원한다. GQA는 여러 Query 헤드가 하나의 KV 헤드를 공유하게 하여 캐시 크기를 물리적으로 줄이며, PagedAttention은 가상 메모리 기법을 차용해 메모리 파편화를 방지한다. 또한 양자화를 통해 캐시 정밀도를 낮춰 점유율을 절반 이하로 줄이거나, 부족한 VRAM을 보완하기 위해 캐시 오프로딩 전략으로 데이터를 관리한다. 결국 LLM 서빙의 핵심은 이 메모리 대역폭을 얼마나 정교하게 제어하느냐에 달려 있다.

참고 문헌