- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며 — 어텐션이 비싼 이유
- 1. 표준 어텐션의 비용 분석
- 2. MQA와 GQA — KV 헤드를 공유하라
- 3. FlashAttention — IO를 줄여라
- 4. 긴 컨텍스트를 위한 어텐션 변형
- 5. 서빙 메모리에 미치는 영향
- 5.5. FlashAttention과 causal mask, 그리고 후속 개선
- 6. 서빙 메모리 예산 한 번 직접 계산해보기
- 7. decode가 메모리 바운드인 이유와 의미
- 8. 서빙 프레임워크별 어텐션 최적화
- 9. 어텐션 변형 선택 가이드
- 함정과 트러블슈팅
- 9.5. 어텐션 최적화와 다른 효율화 기법의 상호작용
- 9.7. 흔한 오해 바로잡기
- 10. 벤치마크 수치를 읽는 법
- 마치며
- 참고 자료
들어가며 — 어텐션이 비싼 이유
Transformer의 self-attention은 강력하지만 비쌉니다. 시퀀스 길이 N에 대해 모든 토큰 쌍을 비교하므로 연산량과 중간 메모리가 N의 제곱에 비례합니다. 컨텍스트가 2K에서 32K로 16배 길어지면, 어텐션 점수 행렬은 256배 커집니다. 길어진 컨텍스트가 곧 비용 폭발로 이어지는 것입니다.
게다가 추론 단계에서는 또 다른 병목이 있습니다. 자기회귀 생성은 토큰을 하나씩 만들기 때문에, 이전 토큰들의 Key/Value를 저장한 KV cache가 시퀀스 길이에 비례해 선형으로 커집니다. 긴 컨텍스트 서빙에서는 모델 가중치보다 KV cache가 GPU 메모리를 더 많이 잡아먹는 경우도 흔합니다.
이 글은 두 가지 병목 — 어텐션의 제곱 비용과 KV cache의 선형 증가 — 을 공략하는 핵심 기법들을 다룹니다. MQA/GQA는 KV cache를 줄이고, FlashAttention은 어텐션 연산의 메모리 IO를 줄이며, 슬라이딩 윈도우 같은 기법은 어텐션 범위 자체를 제한합니다.
1. 표준 어텐션의 비용 분석
연산량과 메모리
표준 멀티헤드 어텐션의 핵심 비용을 정리합니다.
기호:
B = 배치, H = 헤드 수, N = 시퀀스 길이, d_k = 헤드당 차원, D = H x d_k
Q·K^T 점수 행렬: (B, H, N, N)
- 연산량: O(B x H x N^2 x d_k)
- 메모리: O(B x H x N^2) <- N의 제곱!
softmax 후 V 가중합: 추가로 O(B x H x N^2 x d_k)
문제의 핵심은 (N, N) 점수 행렬입니다. N=8192, H=32, B=8이면 점수 행렬 하나만 FP16으로 약 34GB에 달합니다. 이 거대한 중간 행렬을 GPU의 고속 메모리(SRAM)가 아니라 느린 메모리(HBM)에 썼다 읽는 것이 표준 구현의 진짜 병목입니다.
추론 단계의 KV cache
추론에서 KV cache 크기는 다음과 같이 어림합니다.
KV cache 바이트
= 2 x L x N x D x B x bytes_per_element
(2는 K와 V, L은 레이어 수)
예: L=32, D=4096, N=8192, B=1, FP16
약 4.3 GB
배치를 32로 키우면 약 137 GB -> 단일 GPU 메모리 초과
즉 처리량을 위해 배치를 키우고 싶어도 KV cache가 메모리를 다 먹어버려 배치 크기가 제한됩니다. 이것이 MQA/GQA가 등장한 직접적 동기입니다.
2. MQA와 GQA — KV 헤드를 공유하라
핵심 아이디어
표준 멀티헤드 어텐션(MHA)은 H개의 Query 헤드 각각에 대응하는 H개의 Key/Value 헤드를 둡니다. MQA(Multi-Query Attention)는 발상을 뒤집습니다. Query 헤드는 H개 그대로 두되, Key/Value 헤드는 단 1개만 두고 모든 Query 헤드가 그것을 공유합니다.
GQA(Grouped-Query Attention)는 둘의 절충안입니다. Query 헤드를 G개 그룹으로 나누고, 각 그룹이 하나의 KV 헤드를 공유합니다. G=1이면 MQA, G=H이면 MHA와 같아집니다.
MHA : Query 헤드 H개, KV 헤드 H개 (KV cache 가장 큼)
GQA : Query 헤드 H개, KV 헤드 G개 (G < H, 절충)
MQA : Query 헤드 H개, KV 헤드 1개 (KV cache 가장 작음)
예) H=32일 때
MHA: KV 헤드 32 -> 기준
GQA(G=8): KV 헤드 8 -> KV cache 1/4
MQA: KV 헤드 1 -> KV cache 1/32
왜 작동하는가
KV cache는 KV 헤드 수에 비례합니다. KV 헤드를 H개에서 G개로 줄이면 KV cache도 G/H로 줄어듭니다. MQA처럼 1개로 줄이면 극적으로 작아져, 더 긴 컨텍스트나 더 큰 배치를 같은 메모리에 담을 수 있습니다.
대가는 표현력입니다. KV 헤드를 너무 줄이면(MQA) 품질이 약간 떨어질 수 있습니다. 그래서 실무에서는 G=8 같은 GQA가 품질 손실을 거의 없이 KV cache를 크게 줄이는 균형점으로 널리 채택됩니다. Llama 2/3, Qwen 등 다수의 최신 모델이 GQA를 사용합니다.
import torch
import torch.nn.functional as F
def gqa_attention(q, k, v, num_kv_groups):
# q: (B, H, N, d_k)
# k, v: (B, G, N, d_k) G = num_kv_groups
B, H, N, d_k = q.shape
G = num_kv_groups
rep = H // G # 각 KV 헤드를 몇 개 Query 헤드가 공유하는가
# KV 헤드를 Query 헤드 수에 맞게 반복 확장
k = k.repeat_interleave(rep, dim=1) # (B, H, N, d_k)
v = v.repeat_interleave(rep, dim=1)
scores = torch.matmul(q, k.transpose(-2, -1)) / (d_k ** 0.5)
weights = F.softmax(scores, dim=-1)
return torch.matmul(weights, v)
비교 테이블
| 방식 | KV 헤드 수 | KV cache 크기 | 품질 | 대표 모델 |
|---|---|---|---|---|
| MHA | H | 기준(가장 큼) | 가장 높음 | 초기 GPT, 원논문 |
| GQA | G (예: 8) | 약 G/H | MHA에 근접 | Llama 2/3, Qwen |
| MQA | 1 | 약 1/H(가장 작음) | 약간 하락 가능 | PaLM, 일부 경량 모델 |
3. FlashAttention — IO를 줄여라
진짜 병목은 연산이 아니라 메모리 이동
표준 어텐션의 느림은 부동소수점 연산량 자체보다, 거대한 (N, N) 점수 행렬을 느린 HBM에 썼다가 다시 읽는 메모리 왕복에서 옵니다. GPU의 SRAM은 매우 빠르지만 작고, HBM은 크지만 느립니다. 점수 행렬이 SRAM에 다 들어가지 않으니 HBM을 오가는 것입니다.
FlashAttention의 핵심은 IO-aware 알고리즘입니다. (N, N) 점수 행렬 전체를 절대 HBM에 실체화하지 않습니다. 대신 Q, K, V를 작은 타일(블록)로 나눠 SRAM에 올리고, 블록 단위로 어텐션을 계산하면서 softmax를 온라인으로 점진 갱신합니다.
표준 어텐션:
1) S = Q·K^T -> (N, N) 전체를 HBM에 저장
2) P = softmax(S) -> (N, N) 다시 읽고 쓰기
3) O = P·V -> 또 읽기
=> HBM 왕복 O(N^2)
FlashAttention (타일링):
Q, K, V를 블록으로 분할 -> SRAM에 올림
블록마다 부분 점수 계산 -> 온라인 softmax로 누적
(N, N) 행렬을 통째로 만들지 않음
=> HBM 왕복 대폭 감소, 메모리도 O(N)으로 절감
온라인 softmax의 직관
softmax는 보통 전체 행을 봐야 정규화 상수를 구할 수 있습니다. FlashAttention은 블록을 순회하면서 지금까지 본 최댓값과 합을 들고 다니다가, 새 블록이 들어오면 안전하게 스케일을 보정하며 누적합니다. 이렇게 하면 전체 행렬을 한 번에 메모리에 들고 있지 않아도 정확히 같은 결과를 얻습니다.
# 개념 의사코드: 실제 FlashAttention은 CUDA 커널로 구현됨
# 핵심은 블록 순회 + 온라인 softmax 누적
def flash_attention_concept(Q, K, V, block_size):
N, d = Q.shape
O = zeros(N, d)
for i in range(0, N, block_size): # Q 블록 순회
q_block = Q[i:i+block_size]
running_max = -inf
running_sum = 0
acc = zeros(block_size, d)
for j in range(0, N, block_size): # K, V 블록 순회
k_block = K[j:j+block_size]
v_block = V[j:j+block_size]
s = q_block @ k_block.T / sqrt(d) # 작은 블록 점수만 SRAM에
block_max = s.max(axis=-1)
new_max = maximum(running_max, block_max)
# 이전 누적값과 현재 블록을 같은 스케일로 보정
acc = rescale_and_accumulate(acc, s, v_block, running_max, new_max)
running_max = new_max
O[i:i+block_size] = finalize(acc, running_sum)
return O
실제 구현은 PyTorch의 scaled_dot_product_attention에서 백엔드로 자동 선택되거나, FlashAttention 라이브러리/커널로 제공됩니다. 직접 의사코드를 쓸 일은 거의 없고, 라이브러리를 켜기만 하면 됩니다.
효과 정리
| 항목 | 표준 어텐션 | FlashAttention |
|---|---|---|
| 중간 메모리 | N의 제곱에 비례 | N에 선형 |
| HBM 왕복 | 많음 | 타일링으로 대폭 감소 |
| 정확도 | 기준 | 수치적으로 동일(근사 아님) |
| 효과가 큰 구간 | 짧은 시퀀스 | 긴 시퀀스에서 가속 큼 |
중요한 점은 FlashAttention이 근사가 아니라 정확히 같은 어텐션을 더 빠르게, 더 적은 메모리로 계산한다는 것입니다. 품질 손실 없이 공짜로 얻는 최적화이므로, 사실상 표준이 되었습니다.
4. 긴 컨텍스트를 위한 어텐션 변형
KV cache 축소(GQA/MQA)와 IO 최적화(FlashAttention)는 표준 어텐션을 효율화하지만, 어텐션 범위 자체는 여전히 전체 시퀀스입니다. 컨텍스트가 수십만 토큰으로 길어지면 범위 자체를 제한하는 기법이 필요합니다.
슬라이딩 윈도우 어텐션
각 토큰이 전체가 아니라 최근 W개 토큰만 보게 제한합니다. 어텐션 비용이 N의 제곱에서 N 곱하기 W로 줄고, KV cache도 윈도우 크기로 상한이 생깁니다. 깊은 층을 쌓으면 윈도우가 간접적으로 겹쳐 멀리 있는 정보도 어느 정도 전달됩니다. Mistral 등이 채택했습니다.
전체 어텐션 (N=8):
각 query가 모든 key를 봄 -> 비용 O(N^2)
슬라이딩 윈도우 (W=3):
query5는 key3, key4, key5만 봄
query6은 key4, key5, key6만 봄
-> 비용 O(N x W), KV cache 상한 W
그 외 롱컨텍스트 접근
- 전역+지역 혼합: 대부분 토큰은 지역 윈도우만 보되, 소수의 전역 토큰은 전체를 보게 해 멀리 있는 정보 경로를 확보합니다.
- 계층적/희소 어텐션: 멀리 있는 토큰은 듬성듬성, 가까운 토큰은 촘촘히 보는 식으로 패턴을 희소화합니다.
- 위치 인코딩 외삽: 학습보다 긴 입력에서 RoPE를 보정(NTK, YaRN 등)해 컨텍스트를 늘립니다. 이는 위치 인코딩 글에서 다룹니다.
5. 서빙 메모리에 미치는 영향
이 모든 선택은 결국 GPU 메모리 예산과 처리량으로 귀결됩니다. 2026년 서빙 스택에서는 다음 요소들이 함께 작동합니다.
GPU 메모리 = 모델 가중치 + KV cache + 활성값/작업 버퍼
KV cache를 줄이면 (GQA/MQA, 슬라이딩 윈도우, KV 양자화)
-> 더 큰 배치 가능 -> 처리량 상승
-> 더 긴 컨텍스트 수용 가능
- continuous(in-flight) batching: 요청이 끝나는 즉시 새 요청을 끼워 넣어 GPU를 놀리지 않습니다. 2026년 표준입니다.
- paged KV cache: KV cache를 고정 크기 블록으로 쪼개 OS 가상메모리처럼 관리(PagedAttention)해 단편화를 없애고 메모리 활용률을 높입니다.
- KV 양자화: KV cache를 FP8/INT4로 저장해 메모리를 추가로 절반 이하로 줄입니다. decode 단계가 메모리 바운드이므로 효과가 큽니다.
- prefill/decode 분리, chunked prefill: 연산 바운드인 prefill과 메모리 바운드인 decode를 분리하거나, prefill을 쪼개 지연을 평탄화합니다.
| 기법 | 줄이는 대상 | 처리량 영향 | 품질 영향 |
|---|---|---|---|
| GQA/MQA | KV cache(헤드 수) | 배치 키워 상승 | 거의 없음~약간 |
| FlashAttention | 어텐션 IO/중간 메모리 | 직접 가속 | 없음(동일 결과) |
| 슬라이딩 윈도우 | KV cache/어텐션 범위 | 긴 컨텍스트에서 상승 | 장거리 의존 시 주의 |
| KV 양자화 | KV cache 정밀도 | 배치 키워 상승 | 약간(설정 의존) |
| paged KV cache | 메모리 단편화 | 활용률로 상승 | 없음 |
5.5. FlashAttention과 causal mask, 그리고 후속 개선
FlashAttention의 타일링은 causal mask와도 잘 맞습니다. 디코더에서는 query 블록보다 미래에 있는 key 블록은 아예 계산할 필요가 없으므로, 블록 단위로 건너뛰어 연산을 더 줄일 수 있습니다.
causal 어텐션에서 블록 스킵:
query 블록 i, key 블록 j 에 대해
j > i 인 블록 (완전히 미래) -> 통째로 건너뜀
j == i 인 블록 -> 블록 내부에서만 삼각 마스크
j < i 인 블록 -> 전부 계산
-> causal일 때 어텐션 연산이 약 절반으로 감소
FlashAttention은 이후 버전을 거치며 하드웨어 활용을 더 끌어올렸습니다. 핵심 아이디어(타일링 + 온라인 softmax + IO 인식)는 동일하지만, 워프/스레드 스케줄링과 작업 분할을 개선해 최신 GPU의 연산 유닛을 더 빈틈없이 채웁니다. 실무에서는 라이브러리나 프레임워크가 하드웨어에 맞는 구현을 자동 선택하므로, 사용자는 "켜져 있는지"만 확인하면 됩니다.
세대별 발전 방향(개념):
1세대: (N,N) 비실체화 + 온라인 softmax로 메모리 O(N), HBM 왕복 감소
이후 : 병렬화 축 재배치, 워프 단위 작업 분할 개선
-> 같은 알고리즘으로 GPU 점유율(occupancy) 상승
중요한 점은 이 모든 개선이 결과를 바꾸지 않는다는 것입니다. 어떤 세대를 쓰든 수치적으로 동일한 어텐션을 계산하며, 차이는 오직 속도와 메모리 효율입니다.
6. 서빙 메모리 예산 한 번 직접 계산해보기
이론을 실전 감각으로 바꾸려면 직접 숫자를 넣어봐야 합니다. 7B급 모델을 80GB GPU 한 장에 올린다고 가정하고, 배치를 얼마나 키울 수 있는지 따져봅니다.
가정:
모델 가중치: FP16, 약 14 GB
L=32, D=4096, FP16(2바이트)
컨텍스트 N=8192
GPU 메모리 80 GB 중:
가중치 14 GB
활성값/작업 버퍼 약 6 GB (러프)
KV cache에 쓸 수 있는 여유 = 80 - 14 - 6 = 약 60 GB
MHA(KV 헤드 32)일 때 요청당 KV cache:
= 2 x 32 x 8192 x 4096 x 2 (바이트)
약 4.3 GB
-> 60 / 4.3 = 약 13개 동시 요청
GQA(G=8, KV 헤드 8)로 바꾸면 요청당 KV cache:
= 위의 8/32 = 약 1.1 GB
-> 60 / 1.1 = 약 54개 동시 요청
KV 헤드를 32에서 8로 줄였을 뿐인데 동시 처리 가능한 요청 수가 약 4배로 늘었습니다. 동시 요청 수는 곧 처리량(throughput)이므로, GQA 하나가 같은 GPU에서 서빙 비용을 크게 낮추는 셈입니다. 여기에 KV 양자화(FP8)까지 더하면 또 한 번 절반으로 줄일 수 있습니다.
7. decode가 메모리 바운드인 이유와 의미
추론은 두 단계로 나뉩니다. 입력 프롬프트를 한 번에 처리하는 prefill, 그리고 토큰을 하나씩 만드는 decode입니다. 둘의 성격이 정반대입니다.
prefill (프롬프트 N개 토큰 처리):
큰 행렬곱 -> 연산(FLOPs) 바운드
GPU 텐서코어를 꽉 채워 씀
decode (토큰 1개씩 생성):
얇은 행렬-벡터곱 -> 메모리 대역폭 바운드
매 스텝 가중치 + KV cache를 메모리에서 읽음
연산량은 적은데 메모리 읽기가 병목
decode가 메모리 바운드라는 사실은 최적화 방향을 정합니다. 연산이 아니라 메모리 읽기가 병목이므로, (1) 가중치를 작게(양자화) 만들고, (2) KV cache를 작게(GQA/MQA, KV 양자화) 만들고, (3) 한 번 메모리를 읽을 때 더 많은 일을 하게(배치 키우기, speculative decoding) 만드는 것이 효과적입니다.
메모리 바운드 decode를 공략하는 2026 기법:
continuous batching : 끝난 요청 자리에 새 요청 즉시 투입
paged KV cache : KV를 블록 단위로 관리해 단편화 제거
FP8/INT4 양자화 : 가중치/KV를 작게 -> 메모리 읽기 감소
speculative decoding: 드래프트 모델로 여러 토큰 미리 만들고 검증
메모리바운드 구간에서 약 2~3배 가속
8. 서빙 프레임워크별 어텐션 최적화
2026년의 주요 서빙 프레임워크는 위 기법들을 각자의 방식으로 구현합니다.
| 프레임워크 | 특징 | 어텐션/KV 관련 강점 |
|---|---|---|
| vLLM | 범용, 넓은 모델/하드웨어 지원 | PagedAttention 원조, continuous batching |
| TensorRT-LLM | 컴파일 기반, NVIDIA 최적화 | H100에서 높은 처리량(약 15~30% 상회 사례) |
| SGLang | RadixAttention | prefix 캐시 재사용, 멀티턴에 강함 |
세 프레임워크 모두 FlashAttention 계열 커널과 paged KV cache를 기본으로 깔고, GQA/MQA 모델을 지원합니다. 차이는 컴파일 최적화 수준, prefix 캐시 재사용 전략, 지원 하드웨어 폭 등에서 갈립니다. vLLM은 범용성과 넓은 지원으로, TensorRT-LLM은 NVIDIA 하드웨어에서의 정점 성능으로, SGLang은 prefix를 많이 공유하는 멀티턴/구조화 워크로드에서 각각 강점을 보입니다.
9. 어텐션 변형 선택 가이드
상황별로 어떤 조합을 고를지 정리하면 다음과 같습니다.
일반 텍스트 LLM 서빙:
GQA 모델 + FlashAttention + paged KV cache + continuous batching
(대부분의 기본값)
메모리가 빠듯하고 처리량이 최우선:
위에 더해 KV 양자화(FP8) + 더 공격적인 배치
초장문 컨텍스트(수십만 토큰):
슬라이딩 윈도우/희소 어텐션 모델 + 위치 외삽(NTK/YaRN)
+ paged KV cache 필수
멀티턴 챗/공통 prefix가 많은 워크로드:
prefix 캐시 재사용(예: RadixAttention) 이득 큼
핵심 원칙은 "표준 어텐션은 IO 최적화(FlashAttention)로, KV cache는 헤드 공유와 양자화로, 어텐션 범위는 윈도우/희소화로 다스린다"는 세 축을 상황에 맞게 조합하는 것입니다.
함정과 트러블슈팅
- GQA 헤드 수 미스매치: KV 헤드 G가 Query 헤드 H를 나누어떨어지지 않으면 repeat_interleave 단계에서 shape 오류가 납니다. H가 G의 배수여야 합니다.
- MQA 품질 하락 간과: 메모리만 보고 MQA로 갔다가 특정 태스크에서 품질이 떨어질 수 있습니다. 우선 GQA(G=8 등)로 시작해 균형을 잡으세요.
- FlashAttention 미적용: 라이브러리/백엔드가 꺼져 있으면 긴 시퀀스에서 OOM이 나거나 느립니다. PyTorch라면 적절한 백엔드가 선택되는지 확인하세요.
- 슬라이딩 윈도우의 장거리 손실: 윈도우가 너무 작으면 문서 앞부분 정보가 뒤로 전달되지 못합니다. 윈도우 크기와 층 수의 관계를 고려하세요.
- KV 양자화 정밀도 함정: KV를 너무 낮은 비트로 양자화하면 긴 컨텍스트에서 누적 오차로 품질이 떨어질 수 있습니다. INT4보다 FP8이 안전한 경우가 많습니다.
- 배치 메모리 추정 누락: KV cache는 배치에 선형 비례합니다. 배치를 올리기 전에 위 공식으로 메모리를 미리 계산하지 않으면 운영 중 OOM이 납니다.
9.5. 어텐션 최적화와 다른 효율화 기법의 상호작용
어텐션·KV 최적화는 단독으로 쓰이지 않고, 모델·서빙 전반의 다른 효율화 기법과 함께 작동합니다. 몇 가지 상호작용을 짚어둡니다.
양자화(가중치)와의 관계:
가중치 양자화는 모델 메모리를 줄이고, KV 양자화는 KV cache를 줄임
-> 둘은 독립적이라 함께 적용 가능
-> 함께 쓰면 더 큰 배치/긴 컨텍스트 수용
MoE와의 관계:
MoE는 FFN을 여러 전문가로 쪼개 일부만 활성화
-> 어텐션 부분은 그대로이므로 GQA/FlashAttention과 직교(독립)
-> 단, MoE는 활성 파라미터는 적어도 전체 파라미터(메모리)는 큼
speculative decoding과의 관계:
드래프트 모델로 여러 토큰을 미리 만들고 본 모델로 한 번에 검증
-> KV cache 관리가 더 복잡해지지만, 메모리바운드 decode에서 가속 큼
-> GQA로 KV를 줄여두면 드래프트·검증 모두 메모리 여유가 생김
핵심은 이 기법들이 대부분 서로 직교한다는 점입니다. 어텐션 변형(GQA/FlashAttention)은 "어텐션을 싸게", 양자화는 "가중치/KV를 작게", MoE는 "연산을 조건부로", speculative decoding은 "decode를 빠르게" 만듭니다. 서로 다른 축을 공략하므로 함께 쌓을 수 있고, 실제 프로덕션 서빙은 이들을 조합해 구성됩니다.
9.7. 흔한 오해 바로잡기
어텐션 효율화에 대한 흔한 오해를 정리합니다.
오해 1: "FlashAttention은 근사라서 품질이 조금 떨어진다"
-> 틀림. 수치적으로 동일한 어텐션. 품질 손실 없음.
오해 2: "MQA를 쓰면 항상 GQA보다 빠르다"
-> 메모리는 더 작지만, 품질 하락으로 더 큰 모델/재학습이 필요해질 수 있음.
실효 비용은 워크로드에 따라 다름.
오해 3: "긴 컨텍스트는 KV cache만 키우면 된다"
-> 메모리뿐 아니라 위치 외삽(품질)과 어텐션 비용(연산)도 함께 다뤄야 함.
오해 4: "배치를 키우면 무조건 좋다"
-> 처리량은 오르지만 개별 요청 지연(TPOT)이 늘 수 있음. SLA와 균형 필요.
10. 벤치마크 수치를 읽는 법
서빙 최적화 글을 읽다 보면 "처리량 X% 향상", "지연 Yms" 같은 수치가 쏟아집니다. 이를 올바르게 해석하려면 몇 가지 축을 구분해야 합니다.
핵심 지표:
처리량(throughput): 단위 시간당 토큰 수 (전체 사용자 기준)
지연(latency):
TTFT (time to first token): 첫 토큰까지 -> prefill 영향 큼
TPOT (time per output token): 토큰 간 간격 -> decode 영향 큼
동시성: 동시에 처리하는 요청 수 (배치와 직결)
같은 최적화라도 어떤 지표를 개선하는지 다릅니다. 예를 들어 배치를 키우면 전체 처리량은 오르지만 개별 요청의 지연은 늘 수 있습니다. KV cache 축소(GQA)는 배치를 키워 처리량을 올리고, FlashAttention은 prefill과 긴 시퀀스에서 TTFT를 줄이며, speculative decoding은 주로 TPOT(decode 지연)를 줄입니다.
| 지표 | 주로 개선하는 기법 | 주의점 |
|---|---|---|
| 처리량 | GQA, 배치 확대, KV 양자화 | 지연과 트레이드오프 가능 |
| TTFT | FlashAttention, chunked prefill | 긴 프롬프트에서 체감 큼 |
| TPOT | speculative decoding, KV 최적화 | 수용률에 따라 효과 변동 |
벤치마크 수치를 인용할 때는 "어떤 하드웨어에서, 어떤 모델로, 어떤 시퀀스 길이와 배치로" 측정했는지가 핵심입니다. 같은 기법도 조건에 따라 향상폭이 크게 달라지므로, 절대 수치보다 어떤 축을 어떻게 개선하는지의 메커니즘을 이해하는 것이 더 중요합니다.
마치며
표준 어텐션의 두 병목 — 제곱 연산 비용과 선형 KV cache — 은 각각 다른 도구로 공략합니다. FlashAttention은 정확도를 유지한 채 어텐션의 메모리 IO를 줄여 사실상 공짜 가속을 줍니다. GQA/MQA는 KV 헤드를 공유해 KV cache를 줄여 더 큰 배치와 긴 컨텍스트를 가능하게 합니다. 슬라이딩 윈도우와 희소 어텐션은 범위 자체를 제한해 초장문 컨텍스트의 비용을 다스립니다.
실무에서는 이들이 함께 쓰입니다. GQA로 KV를 줄이고, FlashAttention으로 어텐션을 가속하고, continuous batching과 paged KV cache로 메모리를 빈틈없이 활용하는 것이 2026년 서빙의 기본 조합입니다. 다음 글에서는 위치 인코딩, 특히 RoPE와 길이 외삽을 깊게 다루겠습니다.
참고 자료
- Dao et al., "FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness" (arxiv 2205.14135): https://arxiv.org/abs/2205.14135
- Vaswani et al., "Attention Is All You Need" (arxiv 1706.03762): https://arxiv.org/abs/1706.03762
- vLLM 공식 문서(PagedAttention, KV cache): https://docs.vllm.ai
- vLLM 저장소: https://github.com/vllm-project/vllm
- SGLang 저장소(RadixAttention, prefix 캐시): https://github.com/sgl-project/sglang
- TensorRT-LLM 저장소: https://github.com/NVIDIA/TensorRT-LLM
- PyTorch scaled_dot_product_attention 문서: https://pytorch.org/docs/stable/generated/torch.nn.functional.scaled_dot_product_attention.html
- Hugging Face Transformers 문서: https://huggingface.co/docs/transformers/index