
LLM 추론은 왜 막히는가: 메모리 병목(Memory-bound)과 KV 캐시를 구현 관점에서
요약
LLM 추론 속도를 저하시키는 핵심 원인인 메모리 병목(Memory-bound) 현상을 구현 관점에서 분석합니다. 연산 성능보다 메모리 대역폭이 병목이 되는 이유와 KV 캐시가 메모리 점유에 미치는 영향을 설명합니다.
핵심 포인트
- LLM 추론은 연산보다 메모리 대역폭에 의해 속도가 결정되는 memory-bound 특성을 가짐
- 디코딩 단계에서는 1토큰 생성 시 모델 전체 파라미터를 읽어야 하므로 대역폭이 매우 중요함
- KV 캐시는 문맥 길이와 병렬 요청 수에 따라 메모리 사용량이 선형적으로 증가함
- 단순히 GPU 연산 성능(FLOPS)을 높이는 것보다 메모리 대역폭 확보가 레이턴시 개선에 핵심임
최근 「LLM 추론 비용이 높다/느리다」라는 이야기가 급격히 체감되기 시작했다. GitHub Copilot이 토큰 과금 방식으로 전환하며 논란이 되고(TechCrunch), 한편으로는 「하드웨어를 쌓으면 빨라지고, Ollama Cloud라면 무료로 제공해 준다」와 같은 이야기도 흘러나온다(Zenn: 5억 달러 청구를 당하지 않도록…).
이 「하드웨어를 쌓으면 빨라진다」는 말에 대해, 나는 절반은 맞고 절반은 틀렸다고 생각한다. 그리고 그 「절반」을 나누는 경계선이 이른바 메모리 병목 (memory-bound) 이다. 이 부분을 제대로 이해하면 양자화(Quantization), 배치(Batching), KV 캐시(KV Cache) 관리와 같은 최적화가 모두 「메모리 대역폭(Memory Bandwidth)을 어떻게 낭비하지 않을 것인가」라는 하나의 축으로 연결되어 보인다. 오늘은 그 부분을 구현 관점에서 써보려 한다.
마침 도쿄 과학대학의 후지이(Fujii) 씨의 LLM Inference Benchmarking 과 KV cache 편이 이 기초를 깔끔하게 정리해 두었으므로, 함께 읽으면 효과적이기에 링크를 남겨둔다.

「계산이 무거운」 것이 아니라, 「운반하는 것이 무거운」 것
먼저 직관에 반하는 부분부터 시작하자. LLM 추론에서 시간을 잡아먹는 것은, 대부분의 경우 행렬 곱(Matrix Multiplication) 그 자체가 아니라, 가중치(Weight)와 KV 캐시를 HBM (GPU 메모리)에서 연산 유닛까지 운반하는 부분이다.
H100을 대략적인 수치로 보면, 연산 성능은 대략 1,000 TFLOP/s 급인 반면, 메모리 대역폭은 약 3.35 TB/s 정도다. 즉, 「1바이트를 운반하는 동안 몇 번 연산할 수 있는가」로 따지면, 수백 번 연산해야 겨우 메모리와 균형이 맞는다. 이를 연산 강도 (arithmetic intensity) 라고 부르며, roofline 모델에서는 이 값이 임계값보다 낮으면 「연산기가 아무리 빨라도 무의미하며, 메모리 전송에서 병목이 발생한다」는 memory-bound 영역에 진입하게 된다.
여기서 핵심적인 사실은 디코딩 (Decoding, 1토큰씩 생성하는 단계)은 배치 사이즈가 1일 때 연산 강도가 절망적으로 낮다는 점이다. 1토큰을 만들기 위해 모델의 모든 파라미터 (수십~수백 GB)를 매번 HBM에서 읽어내야 한다. 70B 모델을 fp16으로 보유하면 1토큰당 140GB를 읽어내야 한다. 이를 3.35 TB/s로 나누면, 그것만으로 물리적인 하한선이 약 40ms/토큰이 된다. 연산은 순식간에 끝나고, 대부분의 시간은 「가중치가 도착하기를 기다리고 있는」 상태다.
따라서 「GPU를 쌓으면 빨라진다」는 말은 정확히 말하면 「메모리 대역폭이 늘어나면 빨라진다」는 뜻이다. FLOPS만 높여도 디코딩 레이턴시 (Latency) 는 거의 변하지 않는다. Ollama Cloud의 이야기가 매력적으로 보이는 것도, 결국 사용자로부터 대역폭이 넓은 카드를 숨겨주고 있기 때문이며, 원리는 변하지 않았다.
KV 캐시: 긴 문장과 다중화로 인해 압박받는 곳
다음으로, 추론을 실제 운영할 때 메모리를 가장 많이 잡아먹는 것이 KV 캐시 (KV Cache)다. 어텐션 (Attention) 은 과거의 모든 토큰의 Key/Value를 참조하므로, 생성된 토큰의 K/V를 매번 다시 만들지 않도록 캐시해 둔다. 이것이 없으면 생성 속도가 $O(n^2)$로 떨어져 불가능해지기 때문에 필수적이지만, 그 대신 메모리를 「토큰 수 × 레이어 수 × 헤드 차원 × 2(K,V)」만큼 사용한다.
대략적으로 13B 급 모델이라도 한 요청(Request)당 긴 문장을 다루면 수 GB 단위로 늘어난다. 여기서 중요한 점은 「KV 캐시는 병렬 요청 수 × 문맥 길이(Context Length)에 따라 선형적으로 팽창한다」는 점이다. 즉,
- 문맥을 길게 하면 → 1개 요청의 KV가 팽창함
- 동시 접속을 늘리면 (배치를 하면) → 그만큼 곱절로 팽창함
이 두 가지 모두에서 메모리가 압박을 받는다. 실무에서 「동시에 몇 개의 요청을 처리할 수 있는가」는 대개 연산 능력이 아니라, KV 캐시가 HBM에 몇 개 분량이나 올라갈 수 있느냐로 결정된다. vLLM의 PagedAttention이 효과적인 이유도 바로 여기에 있다. KV를 페이지 단위로 관리하여 파편화(Fragmentation)를 없애고, 동일한 VRAM에 더 많은 요청을 채워 넣는다는 발상이다.
배칭의 경제학: 레이턴시와 처리량은 적대적 관계
여기까지 오면 서빙 설계의 핵심이 보인다. 디코딩이 memory-bound라는 것은, 가중치를 한 번 읽을 때 「덤으로」 여러 요청을 묶어서 계산하면 메모리 전송 비용을 상쇄할 수 있다는 뜻이다. 배치 사이즈를 높이면 연산 강도가 올라가며, memory-bound에서 compute-bound 쪽으로 이동하게 된다.

즉:
- 처리량 (Throughput, 처리 가능한 토큰 총량)을 높이고 싶다면 → 배치를 크게. 가중치 읽기 비용을 모두가 나누어 부담한다.
- 레이턴시 (Latency, 한 사용자의 대기 시간)를 낮추고 싶다면 → 배치를 작게. 하지만 비용 상쇄 효과가 없어 단가가 올라간다.
이 두 가지는 구조적으로 트레이드오프(Trade-off) 관계에 있으며, 컨티뉴어스 배칭(Continuous batching, 요청의 도착 및 완료에 맞춰 동적으로 배치를 재구성하는 방식)은 이 줄다리기를 "대기열을 계속 채움으로써 두 마리 토끼를 잡는 것에 가깝게 만드는" 테크닉이라고 이해하면 납득이 갈 것이다. 벤치마크에서 TTFT(첫 번째 토큰까지의 시간)와 TPOT(토큰당 시간)를 나누어 보는 이유도, 프리필(Prefill, 연산 중심/Compute-bound 경향)과 디코드(Decode, 메모리 중심/Memory-bound 경향)에서 지배적인 요인이 다르기 때문이다. 자세한 내용은 앞서 언급한 후지이(Fujii) 씨의 기사가 친절하게 설명하고 있다.
그래서 토큰 과금은 "물리적 투영"이다
여기서 Copilot 논란으로 돌아가 보자. 토큰 과금이 비판받는 이유는 "이해하기 어렵고 예측할 수 없기" 때문이지만, 제공자 입장에서 보면 비용의 실체는 바로 "토큰 × 메모리 대역폭 점유"이므로, 이는 자의적인 가격 책정이라기보다 물리적 비용의 솔직한 투영(Mapping)에 가깝다.
그리고 메모리 병목(Memory-bound) 현상 때문에, "똑똑한 모델을 만든다 = 파라미터나 KV를 늘린다"는 행위는 비용 측면에서 FLOPS가 아니라 메모리 대역폭과 VRAM 용량에 영향을 미친다. GPU의 연산 성능은 세대마다 화려하게 성장하지만, 메모리 대역폭과 용량은 그만큼 성장하지 않는다. 에이전트가 한 태스크에서 수십 번씩 모델을 호출하는 시대에 "단가는 내려가는데 청구액은 늘어나는" 현상이 발생하는 이유는, 이 대역폭의 성장 둔화 위에 수요가 올라타고 있기 때문이라고 나는 보고 있다.
구현자로서 무엇을 할 것인가
- 우선 프로파일링을 TTFT와 TPOT로 나눈다. 느린 부분이 프리필(Prefill)인지 디코드(Decode)인지에 따라 대응책이 정반대가 된다.
- 디코드(Decode)가 지배적이라면, 양자화(Quantization, 가중치를 작게 하여 읽기 바이트를 줄임)와 KV 캐시(KV Cache) 절감(GQA/MQA, KV 양자화, 컨텍스트 가지치기)이 직접적인 효과를 발휘한다. FLOPS를 줄이는 최적화는 대부분 헛수고가 된다.
- 처리량(Throughput) 운영이 목적이라면 컨티뉴어스 배칭(Continuous batching)을 전제로 하는 서버(vLLM 등)를 선택한다. 자체적으로 만든 단순한(Naive) 루프는 메모리 전송의 효율적인 분배가 이루어지지 않는 시점에서 이미 패배한 것이나 다름없다.
- "동시 요청 수"는 연산량이 아니라 VRAM 내의 KV 개수로 추산한다. 용량 설계를 FLOPS로 논하기 시작했다면 위험 신호다.
한 가지 예측을 하자면, 향후 1~2년 동안 모델 측면의 차별화는 "얼마나 적은 KV로 긴 컨텍스트를 유지하는가"("상태 압축, 선형 어텐션(Linear Attention) 계열, KV 공유")로 기울 것이라고 생각한다. 메모리 대역폭이라는 물리적 벽이 움직이지 않는 이상, 지능의 성장 여지는 그곳에밖에 남아 있지 않다. 역설적으로 말하면, 이 부분을 이해하고 있는 구현자는 동일한 GPU로 타인보다 몇 배나 더 많은 요청을 처리할 수 있으며, 청구서도 타인보다 더 잘 읽어낼 수 있다.
Sources / 관련
- LLM Inference Benchmarking (藤井, 도쿄과학대): https://zenn.dev/kaz20/articles/8034f0587633f3
- 동일 KV cache 편: https://zenn.dev/kaz20/articles/c77f8a41cf2bf5
- 5억 달러를 청구받지 않기 위해 Ollama의 Cloud를 사용하기: https://zenn.dev/yumayx/articles/c69ac22ce07bea
- GitHub Copilot의 토큰 과금 논란 (TechCrunch): https://techcrunch.com/2026/05/30/what-a-joke-github-copilots-new-token-based-billing-spurs-consternation-among-devs/
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn ML의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기