로컬에서 LLM을 직접 돌려본 사람이라면 한 번쯤 당혹스러운 경험을 했을 것이다. 모델 크기는 8B로 동일한데, 컨텍스트 길이를 4K에서 32K로 늘렸을 뿐인데 VRAM이 부족하다는 경고가 뜬다. 혹은 같은 모델인데 설정 하나 바꿨더니 갑자기 스왑 메모리를 15GB 이상 잡아먹기 시작한다. 도대체 컨텍스트 길이와 메모리 사이에 무슨 일이 벌어지고 있는 걸까.
이 현상을 이해하려면 트랜스포머(Transformer) 모델의 핵심인 어텐션(Attention) 메커니즘부터 짚어야 한다. 어텐션은 단순히 말하면 “이 토큰을 생성할 때 앞에 있는 모든 토큰을 얼마나 참조할 것인가”를 계산하는 과정이다. 100개 토큰짜리 문장이 있으면, 101번째 토큰을 예측할 때 앞 100개 토큰 전체와의 관계를 계산한다. 이 계산 자체는 추론 시마다 발생하는데, 여기서 문제가 생긴다. 매 스텝마다 이전 토큰들에 대한 행렬 연산을 처음부터 다시 반복하면 너무 느리다. 그래서 등장한 것이 KV Cache, 즉 Key-Value 캐시다.
어텐션 연산에서 각 토큰은 Query, Key, Value 세 가지 벡터로 변환된다. 새 토큰을 생성할 때 Query는 지금 이 토큰의 “질문”이고, 이전 토큰들의 Key는 “어떤 정보가 있는지 색인”, Value는 “실제 내용”이다. 그런데 이전 토큰들의 Key와 Value는 이미 계산이 끝난 값이다. 굳이 다시 계산할 필요 없이 저장해두면 된다. 이게 KV Cache의 원리다. 한 번 처리된 토큰의 Key, Value 행렬을 메모리에 쌓아두고, 새 토큰이 들어올 때마다 그냥 꺼내 쓴다. 덕분에 추론 속도는 극적으로 빨라지지만, 그 대가로 메모리를 계속 점유하게 된다.
수치로 보는 KV Cache 메모리
KV Cache가 차지하는 메모리는 다음 요소들의 곱으로 결정된다. 레이어 수, 헤드 수, 헤드 차원(head dimension), 시퀀스 길이, 배치 크기, 그리고 데이터 타입 크기가 모두 곱해진다. Key와 Value 두 벌이 필요하므로 앞에 2를 곱한다.
Llama 3 8B를 예로 들면, 이 모델은 32개 레이어, 8개의 KV 헤드(GQA 적용 후), 헤드 차원 128을 가진다. FP16(float16) 기준 2바이트를 쓴다고 하면, 시퀀스 길이 4K(4,096 토큰), 배치 크기 1일 때 KV Cache는 대략 2 × 32 × 8 × 128 × 4,096 × 1 × 2 바이트, 즉 약 536MB 수준이다. 별것 없어 보인다. 그런데 이 시퀀스를 32K(32,768 토큰)로 늘리면 그대로 8배가 뛰어 약 4.3GB가 된다. 128K 컨텍스트라면 다시 4배, 약 17GB에 이른다. 모델 가중치(weights) 자체가 FP16 기준 약 16GB인 점을 감안하면, 128K 컨텍스트에서는 가중치보다 캐시가 더 많은 메모리를 차지하는 역전 현상이 벌어진다.
배치 크기를 높이면 이 수치는 그 배수만큼 다시 늘어난다. API 서버처럼 동시에 여러 요청을 처리해야 하는 환경에서는 KV Cache 메모리 관리가 사실상 서비스 비용의 핵심 변수가 된다. 대형 클라우드 업체들이 긴 컨텍스트 모델 서빙에 상당한 인프라 투자를 하는 이유도 여기에 있다.
이미지 출처: Unsplash
어떻게 줄이고 있는가
이 문제를 완화하기 위해 업계는 여러 방향에서 접근해왔다. 가장 널리 쓰이는 방법이 GQA(Grouped Query Attention, 그룹화 쿼리 어텐션)다. 기존 멀티헤드 어텐션(MHA)은 헤드마다 독립적인 Key-Value 쌍을 가졌는데, GQA는 여러 Query 헤드가 하나의 Key-Value 쌍을 공유하게 한다. Llama 3 8B가 32개 Query 헤드를 쓰면서도 KV 헤드는 8개만 유지하는 것이 바로 이 덕분이다. 단순 계산으로 KV Cache를 4분의 1 수준으로 줄인 셈이다. MQA(Multi-Query Attention)는 더 극단적으로, 모든 Query가 단 하나의 KV 쌍을 공유한다.
최근에는 DeepSeek가 도입한 MLA(Multi-head Latent Attention, 다중 헤드 잠재 어텐션)가 주목받고 있다. MLA는 Key-Value를 저차원 잠재 벡터로 압축해 저장한 뒤, 실제 연산 시점에 다시 복원하는 방식을 택한다. KV Cache 자체의 크기를 GQA보다 훨씬 공격적으로 줄일 수 있어, DeepSeek V2·V3 계열이 긴 컨텍스트를 비교적 낮은 메모리 비용으로 처리할 수 있는 핵심 이유가 됐다.
Flash Attention은 조금 다른 각도에서 접근한다. KV Cache의 크기 자체를 줄이는 게 아니라, 어텐션 연산 중에 GPU HBM(고대역폭 메모리)과 SRAM 사이의 데이터 이동을 최소화해 속도와 메모리 효율을 동시에 개선한다. 긴 시퀀스에서 특히 효과가 두드러지며, llama.cpp나 vLLM 같은 로컬 추론 런타임에도 이미 기본 탑재돼 있다.
실용적인 맥락에서
로컬 LLM을 쓰는 입장에서 이 모든 내용이 뜻하는 바는 간단하다. 컨텍스트 길이는 “기능의 양”이 아니라 “메모리 예산의 선불 결제”에 가깝다. LM Studio나 Ollama 같은 도구에서 컨텍스트 길이를 기본값인 2K나 4K로 두는 것은 단순한 설정 미숙이 아니다. 대화 품질에 당장 영향이 없다면 메모리를 아끼는 합리적인 선택이다. 반대로 긴 문서를 요약하거나 코드베이스 전체를 컨텍스트에 넣어야 하는 용도라면, 컨텍스트 확장에 따른 메모리 비용을 감수하고 GPU를 충분히 확보해야 한다.
흥미로운 점은 앞으로의 방향이다. MLA 같은 압축 기법이 성숙해지고, 모델 아키텍처가 긴 컨텍스트에 더 최적화되면, 지금처럼 컨텍스트 길이가 곧 메모리 부담이라는 등식이 점차 완화될 가능성이 있다. 이미 일부 연구에서는 슬라이딩 윈도우 어텐션이나 선택적 캐싱(sparse KV cache) 같은 방식으로 필요한 토큰만 골라 캐시하는 접근이 나오고 있다. 다만 지금 당장 로컬에서 모델을 돌리는 사람에게는 “컨텍스트 길이는 공짜가 아니다”는 사실이 여전히 유효하다. 설정 창에서 슬라이더를 오른쪽으로 당기기 전에, 그게 VRAM에 무엇을 의미하는지 한 번쯤 계산해보는 것이 좋다.
출처
- Efficient Large Language Model Inference with Limited Memory (MIT)
- GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints (Google Research)
- DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language Model
- FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning
- LLM Inference Explained — KV Cache and Memory (Hugging Face Blog)