프롬프트를 입력하면 수백 밀리초 후 텍스트가 한 단어씩 스트리밍된다. 그 짧은 순간 GPU 안에서는 두 가지 완전히 다른 연산이 순서대로 작동한다. LLM 추론의 전 과정을 토큰화부터 양자화까지 첫 원칙으로 해설한다.
핵심 멘탈 모델
LLM은 다음 토큰 하나를 예측하는 신경망이다. 딱 하나만. 그 토큰을 프롬프트 끝에 붙여 다시 다음 토큰을 예측한다. 이 루프를 반복한다.
흥미로운 질문은 두 가지다: 어떻게 다음 토큰을 예측하는가, 그리고 왜 두 번째 토큰이 첫 번째보다 훨씬 빠르게 나오는가.
Step 1: 텍스트 → 숫자 (토큰화)
신경망은 영어를 읽지 못한다. 벡터를 읽는다. 따라서 프롬프트에 가장 먼저 일어나는 일은 토큰화(tokenization)—텍스트를 조각으로 나누고 각 조각에 정수 ID를 할당하는 과정이다.
대부분의 현대 LLM은 바이트 쌍 인코딩(Byte Pair Encoding, BPE)을 사용한다. 아이디어는 단순하다: 원시 문자에서 시작해 가장 자주 등장하는 인접 쌍을 반복적으로 합쳐 약 5만 개의 조각 어휘(vocabulary)를 만든다. the 같은 흔한 단어는 토큰 1개. unhappiness 같은 희귀 단어는 un + happi + ness처럼 쪼개진다.
prompt = "How does inference work?"
ids = tokenizer.encode(prompt)
# ids -> [2437, 1374, 32278, 670, 30]토크나이저 학습 데이터에 충분히 포함되지 않은 언어는 같은 문장이 더 많은 토큰으로 쪼개진다—비용이 높아지고 응답이 느려지는 이유다.
Step 2: 숫자 → 벡터 (임베딩)
각 정수 ID는 임베딩 테이블(embedding table)이라는 거대한 행렬에서 조회된다. 어휘 크기 5만, 히든 차원 4,096이면 테이블 형상은 [50000, 4096]. 행 하나를 선택하면 벡터 하나를 얻는다.
# embedding_table has shape [vocab_size, hidden_dim]
vectors = embedding_table[ids] # shape: [num_tokens, 4096]이 벡터들은 무작위가 아니다. 학습 과정에서 의미적으로 유사한 토큰들이 이 4,096차원 공간에서 가깝게 뭉치도록 조정됐다. king과 queen은 이웃이다. python과 snake는 한 축에서 이웃이고, python과 javascript는 다른 축에서 이웃이다.
임베딩 레이어에서 위치 정보도 주입된다. 어텐션 자체는 어떤 토큰이 먼저 왔는지 모르기 때문이다. 현대 모델은 RoPE(Rotary Position Embedding) 같은 방식으로 위치에 따라 벡터를 회전시킨다.
Step 3: 어텐션 레이어
이제 진짜 작업이 시작된다. 벡터 시퀀스가 트랜스포머 레이어 스택(보통 32개 이상)을 차례로 통과한다. 각 레이어는 거의 같은 일을 한다:
- 셀프 어텐션(self-attention)으로 토큰 간 정보를 섞는다.
- 피드포워드 네트워크(FFN)으로 각 토큰 내부 정보를 처리한다.
셀프 어텐션은 깊이 이해할 가치가 있다. 레이어는 각 토큰에 대해 세 개의 학습된 가중치 행렬을 곱해 세 벡터를 생성한다:
# x는 이 레이어의 입력, shape [num_tokens, hidden_dim]
Q = x @ Wq # queries
K = x @ Wk # keys
V = x @ Wv # values세 가지 관점에서 모든 토큰을 바라보게 된다. 핵심 트릭: 각 토큰은 자신의 쿼리(Q)로 다른 모든 토큰의 키(K)를 살펴보고, 일치 강도에 따라 해당 토큰의 값(V)을 얼마나 혼합할지 결정한다.
raw = Q @ K.T
scaled = raw / sqrt(hidden_dim) # softmax 안정화
weights = softmax(scaled) # 토큰당 한 행, 합계 1
attention_output = weights @ V32개의 레이어를 쌓으면 수천 토큰에 걸친 맥락 추적이 가능한 모델이 된다.
어텐션 이후 각 토큰 벡터는 소규모 2레이어 피드포워드 네트워크를 통과한다. 어텐션이 정보를 이동시키고, FFN이 그것을 처리한다.
Step 4: 다음 토큰 예측
마지막 레이어 이후, 모델은 마지막 위치의 벡터를 어휘 크기로 투영하고 소프트맥스를 적용해 모든 가능한 다음 토큰에 대한 확률 분포를 만든다. 분포에서 샘플링하면 첫 번째 생성 토큰이 나온다.
아무도 알려주지 않는 두 단계
200토큰 응답 생성은 하나의 작업이 아니다. 내부적으로는 전혀 다른 두 작업이다.
단계 1: 프리필(Prefill)
프롬프트를 제출하면 모델이 생성을 시작하기 전에 모든 입력 토큰을 처리해야 한다. 좋은 소식: 병렬로 할 수 있다. 모든 토큰의 Q, K, V가 동시에 계산되고 어텐션이 행렬-행렬 곱셈으로 실행된다.
GPU는 이걸 좋아한다. 행렬-행렬 곱셈이 GPU가 만들어진 이유다. 병목은 순수 산술 처리량—GPU가 최고 활용률로 핀되어 실리콘이 허용하는 최대 속도로 계산한다.
이 단계의 지표는 TTFT(Time to First Token): 첫 단어가 화면에 나타나기 전의 대기 시간.
# 프리필: 전체 프롬프트를 한 번에 처리
hidden = embed(prompt_tokens) + positions
for layer in model.layers:
Q, K, V = project(hidden) # 모든 토큰 동시에
hidden = attention(Q, K, V) + hidden
hidden = feedforward(hidden) + hidden
cache_kv(layer, K, V) # 나중을 위해 저장
first_token = sample(project_to_vocab(hidden[-1]))단계 2: 디코드(Decode)
첫 번째 토큰이 나오면 모드가 전환된다. 51번째 토큰을 생성하려면 그 토큰의 Q, K, V만 계산하면 된다. 이전 50개 토큰의 K와 V 벡터는 변하지 않았다—다시 계산하는 건 낭비다.
그래서 모델은 토큰 하나씩 루프를 돈다:
# 디코드: 반복당 토큰 하나
token = first_token
steps = 0
while token != STOP and steps < MAX_STEPS:
x = embed(token) + position(steps)
for layer in model.layers:
q, k, v = project(x)
K_all, V_all = caches[layer].append(k, v) # 캐시된 이력 + 신규
x = layer.forward(q, K_all, V_all, x) # attention + FFN, 잔차
token = sample(project_to_vocab(x))
steps += 1
yield tokenGPU는 여전히 모든 가중치 행렬과 캐시된 K, V를 메모리에서 불러와야 하는데, 실제 산술 연산은 극히 작다. 갑자기 병목이 뒤집힌다. 칩에 연산 여유가 충분한데 메모리가 데이터를 전달할 때까지 기다린다.
이것이 디코드는 메모리 바운드(memory-bound), 프리필은 컴퓨트 바운드(compute-bound)인 이유다. 같은 모델, 같은 하드웨어, 완전히 다른 성능 특성.
이 단계의 지표는 ITL(Inter-Token Latency): 연속 토큰 사이의 간격. 낮은 ITL이 모델을 빠르게 느끼게 만든다.
KV 캐시: 이 모든 것을 가능하게 만드는 최적화
cache_kv 호출이 모든 무거운 일을 담당한다. 없다면 1,000토큰 응답 생성은 매 스텝마다 전체 증가하는 시퀀스에 대한 어텐션을 재계산해야 한다—이차 복잡도로 고통스럽게 느리다.
KV 캐시로 K와 V 행렬을 한 번 저장하고 영원히 재사용한다:
class KVCache:
def __init__(self):
self.K = None # 지금까지 본 모든 키, shape [tokens, dim]
self.V = None # 지금까지 본 모든 값, shape [tokens, dim]
def append(self, k_new, v_new):
if self.K is None:
self.K, self.V = k_new, v_new
else:
self.K = concat([self.K, k_new], axis=token_axis)
self.V = concat([self.V, v_new], axis=token_axis)
return self.K, self.V속도 향상은 크다—긴 생성에서 5배 이상도 가능하다. 그러나 대가가 있다: 캐시는 GPU 메모리에 살며 토큰마다 커진다. 모든 레이어가 자체 K와 V 텐서를 유지한다. 13B 모델은 토큰당 약 1MB. 4K 토큰 컨텍스트는 캐시만으로 VRAM 4GB를 소모한다.
긴 컨텍스트가 느리고 비싼 이유는 모델이 두뇌를 쓸 공간이 없어서가 아니라 캐시가 공간을 다 차지하기 때문이다.
해결책들은 창의적이다:
- 캐시를 INT8 또는 INT4로 양자화 (→ turboquant)
- 슬라이딩 윈도우 밖 토큰 제거
- 어텐션 헤드 간 K/V 공유: GQA(Grouped-Query Attention)
- OS처럼 캐시를 페이징: PagedAttention (vLLM의 핵심 기법)
프론티어 연구: 캐시 자체를 줄이기
deepseek-v4 V4 시리즈는 더 공격적인 접근을 택한다—처음부터 캐시가 작도록 어텐션을 재설계했다. 희소(sparse)와 밀집(dense)의 두 가지 압축된 어텐션 변형을 조합한 하이브리드 방식으로, V4-Pro는 100만 토큰 컨텍스트에서 이전 버전 대비 캐시 크기의 약 10%, 토큰당 연산의 약 27%를 달성했다고 보고한다.
KV 캐시가 이제 모델 아키텍처 설계 자체를 결정하는 병목이 됐다는 신호다.
양자화: 비트를 속도로 교환하기
학습에는 정밀도가 필요하다. 추론에는 아니다.
대부분의 프로덕션 배포는 FP32 대신 FP16 또는 BF16으로 실행된다—메모리가 절반, Tensor Core에서 처리량은 약 2배. 더 공격적인 설정은 INT8이나 INT4로 가중치를 양자화한다.
수학은 간단하다. 7B 파라미터 모델:
| 정밀도 | 모델 크기 |
|---|---|
| FP32 | 28 GB |
| FP16 | 14 GB |
| INT8 | 7 GB |
| INT4 | 3.5 GB |
INT4의 3.5GB가 노트북 GPU에서 7B 모델을 실행할 수 있는 이유다. GPTQ나 AWQ 같은 방법은 채널별 스케일 팩터를 선택해 손실 압축이 품질에 미치는 영향을 최소화한다. 잘 하면 INT4도 대부분의 벤치마크에서 원본과 1퍼센트포인트 이내에 든다. (→ auto-round)
전체 흐름 요약
프롬프트 하나의 엔드-투-엔드 여정:
- 토큰화: 텍스트 → 정수 ID
- 임베딩: ID → 벡터. 위치 정보 주입
- 프리필: 모든 레이어가 모든 입력 토큰을 병렬 처리. 컴퓨트 바운드. KV 캐시 채움. 첫 출력 토큰 생성
- 디코드 루프: 각 신규 토큰에 대해 Q 투영 → 캐시된 K·V로 어텐션 → FFN → 샘플링. 메모리 바운드
- 디토큰화: 토큰 ID → 문자열 → 화면 스트리밍
현대 서빙 프레임워크(vLLM, TensorRT-LLM, TGI)는 이 루프에 연속 배칭(continuous batching)—여러 사용자 토큰을 같은 GPU 스텝에 끼워 넣기—과 투기적 디코딩(speculative decoding) (→ dflash)을 더해 단일 GPU로 수십 명의 동시 사용자를 처리한다.
실전 시사점
그림이 전부 보이면 실용적인 결론이 따라온다:
- 긴 프롬프트는 TTFT에, 긴 출력은 ITL에 비용이 든다. 서로 다른 것을 스트레스 준다. 사용자가 실제로 느끼는 것을 최적화하라.
- 컨텍스트 길이는 공짜가 아니다. 두 배 늘린다고 연산이 두 배가 되는 게 아니라 KV 캐시가 부풀어 배치 크기를 굶긴다.
- 양자화가 가장 레버리지 높은 손잡이다. FP16→INT8 전환은 품질 손실 없이 지연을 절반으로 줄이는 경우가 많다.
- GPU 활용률은 오해를 부른다. 프리필에서 100% 찍던 모델이 디코드에선 30%일 수 있다. 해법은 더 많은 연산이 아니라 더 빠른 메모리나 더 작은 캐시다.
트랜스포머 아키텍처가 모든 주목을 받지만, 추론 성능은 지루한 것들에서 살고 죽는다. 메모리 레이아웃. 캐시 관리. 비트 폭. 보유한 하드웨어에서 최대를 짜내는 기술이 추론 최적화의 본질이다.
관련 문서
- inference-caching — LLM 추론 캐싱 세 가지 전략 비교
- deepseek-v4 — KV 캐시를 10분의 1로 줄인 하이브리드 어텐션 아키텍처
- turboquant — KV 캐시·임베딩 2~4비트 온라인 양자화
- dflash — 블록 확산 기반 투기적 디코딩으로 추론 속도 6배 향상
- auto-round — Intel 부호 경사 하강 기반 LLM 양자화 툴킷
- vllm-recipes — 하드웨어별·모델별 vLLM 실행 레시피
- llm-fine-tuning — 파인튜닝과 추론 최적화의 관계
참고 자료
- How LLM Inference Works — X @akshay_pachaar (2026)
- DeepSeek-V4 Tech Report — DeepSeek AI