Q/K/V
한 줄 정의
어텐션의 세 벡터 — 찾는 것(Q), 이름표(K), 내용물(V)
1. 비유로 이해하기
도서관에서 책을 찾는 장면이다.
Q (Query, 검색어) : "공룡에 대해 알고 싶어" ← 내가 원하는 것
K (Key, 책등 제목) : 각 책 옆면에 붙은 제목 ← 훑어보는 이름표
V (Value, 내용) : 책을 펼쳤을 때 나오는 글 ← 실제로 얻는 것
순서는 이렇다.
- 내 검색어(Q)를 들고 서가를 훑는다
- 책들의 제목(K)과 하나하나 맞춰 본다 → 얼마나 맞는지 점수를 매긴다
- 점수를 비율로 바꾼다 → 『공룡백과』 70%, 『파충류』 20%, 『요리책』 1%
- 그 비율만큼 각 책의 내용(V)을 섞어서 가져온다
3번이 바로 소프트맥스다.
그리고 여기서 중요한 게 하나 있다. 한 권만 고르는 게 아니라 여러 권을 비율대로 섞는다. 공룡백과를 70%, 파충류 책을 20%만큼 겹쳐 읽는 셈이다.
K와 V를 왜 나눠 놨을까? 책등 제목과 책 내용은 다르기 때문이다. 제목은 찾기 좋게 짧게 적혀 있고, 내용은 실제로 쓸모 있는 정보다. 찾는 용도(K)와 쓰는 용도(V)를 분리한 것이 이 구조의 핵심 아이디어다.
2. 왜 필요한가
무슨 문제를 푸는가
"민수가 사과를 떨어뜨렸는데, 그것이 굴러갔다."
“그것”이 무엇인지 우리는 안다. 사과다. 민수가 굴러간 게 아니다.
그런데 컴퓨터에게 “그것”이라는 토큰은 그냥 숫자 뭉치다. 앞의 어떤 단어를 가리키는지 알 방법이 없다. 어텐션이 이 문제를 이렇게 푼다.
"그것"의 Q: "나는 앞의 명사 중 무언가를 가리키는 대명사야.
굴러갈 수 있는 물체를 찾아."
↓ 각 단어의 K와 비교
민수의 K: "사람, 주어" → 점수 낮음 (0.05)
사과의 K: "물체, 둥글고 작음" → 점수 높음 (0.85)
떨어뜨의 K: "동사, 행위" → 점수 낮음 (0.10)
↓ 소프트맥스
"그것"의 새 임베딩 = 0.85×사과V + 0.10×떨어뜨V + 0.05×민수V
“그것”의 벡터 안에 사과의 의미가 85% 섞여 들어간다. 문맥을 이해한다는 게 바로 이것이다.
임베딩은 문맥 없는 기본 뜻이다. 어텐션을 거치면 그 숫자들이 문맥에 맞게 갱신된다. 그리고 이 과정이 수십 층 반복된다.
셋 다 같은 곳에서 나온다
각 토큰의 임베딩 하나에 행렬 세 개를 곱해서 Q, K, V를 만든다.
Q = embedding @ W_q # (768,) × (768, 64) → (64,)
K = embedding @ W_k
V = embedding @ W_v
같은 단어에서 출발했는데 곱하는 행렬이 달라서 셋이 서로 다른 벡터가 된다. 하나의 단어가 세 가지 역할을 동시에 맡는 것이다.
계산은 네 줄이다
┌ Q · Kᵀ ┐
Attention = softmax │ ───────── │ · V
└ √d_k ┘
| 단계 | 하는 일 | 그릇 모양 |
|---|---|---|
Q · Kᵀ |
모든 단어쌍의 관련도 점수 | 행렬 (n × n) |
÷ √d_k |
점수 크기 조절 | 행렬 (n × n) |
softmax |
점수 → 확률 (각 행의 합 = 1) | 행렬 (n × n) |
× V |
확률만큼 내용물 섞기 | 행렬 (n × d) |
def attention(Q, K, V):
d_k = K.shape[-1]
scores = Q @ K.T / np.sqrt(d_k) # 점수 + 크기 조절
weights = softmax(scores) # 확률로
return weights @ V # 비율대로 섞기
÷√d_k가 왜 있을까 — 스케일러의 원리
임베딩 차원이 768이면 Q·K 값이 아주 커진다. 그 상태로 소프트맥스에 넣으면 이렇게 된다.
점수가 작을 때 [2, 1, 0.5] → softmax → [0.6, 0.2, 0.1] 부드럽다
점수가 클 때 [20, 10, 5] → softmax → [1.0, 0.0, 0.0] 한 곳만 본다
한 단어만 100% 보고 나머지를 완전히 무시하면 학습이 안 된다. 그래서 √d_k로 나눠 적당한 크기로 되돌린다.
숫자들의 크기를 맞춰 주는 이 작업이 스케일러가 하는 일과 같은 원리다. 이 방식의 정식 이름이 Scaled Dot-Product Attention인 이유가 여기 있다.
KV 캐시 — 에이전트 비용의 핵심
하네스 루프를 돌 때마다 대화 전체를 처음부터 다시 보낸다. 그런데 이미 계산한 K와 V는 바뀌지 않는다. 그래서 저장해 두고 재사용한다. 이게 KV 캐시다.
1번째 턴: [시스템 프롬프트 + 도구 설명] → K,V 계산 (비싸다)
2번째 턴: [같은 앞부분] + 새 질문
└─ 캐시에서 꺼내 쓴다 ─┘ └ 이것만 계산
API의 프롬프트 캐싱 기능이 바로 이것이다. 여기서 실무 요령이 하나 나온다.
프롬프트를 짤 때 안 바뀌는 것을 앞에, 바뀌는 것을 뒤에 둔다. 앞부분이 고정돼야 캐시가 먹힌다.
컨텍스트가 길수록 왜 비싼가 — n² 문제
Q · Kᵀ는 모든 단어가 모든 단어와 짝지어 비교하는 계산이다.
토큰 1,000개 → 100만 번 비교
토큰 2,000개 → 400만 번 비교 (2배가 아니라 4배)
토큰 10,000개 → 1억 번 비교
컨텍스트를 2배 늘리면 계산은 4배가 된다. 긴 컨텍스트가 비싸고 느린 근본 이유이고, 컨텍스트 관리가 에이전트 개발의 핵심 과제인 이유다.
멀티헤드 어텐션
Q/K/V를 한 세트만 쓰지 않고 여러 세트를 동시에 돌린다. 보통 12~96개다.
헤드 1: 문법 관계에 주목 (주어-동사)
헤드 2: 대명사가 가리키는 대상에 주목
헤드 3: 멀리 떨어진 단어 관계에 주목
...
→ 전부 이어붙여서 사용
한 명이 책을 찾는 게 아니라 사서 여러 명이 각자 다른 기준으로 찾아와 합치는 것과 같다.
3. 어디서 만났나
이 용어는 수업에서 정식으로 다루기 전에 따로 정리한 것이다.
다만 7회차에서 “컨텍스트가 길면 왜 비싼가”라는 질문을 만났는데, 그 답이 위의 n² 문제다. 토큰을 줄이라는 조언의 바닥에 이 계산이 있었다.
7회차 — 에이전트, MCP, 스킬 — 범용 AI를 내 전용 조수로
6회차의 데이터베이스와도 이어진다. WHERE user = '민수'가 Q/K/V의 조상 격이기 때문인데, 아래 4번에서 이어서 본다.
6회차 — 데이터베이스, 서비스의 기억을 어디에 어떤 모양으로 둘까
4. 헷갈리는 개념
| 이 용어 | 비슷하지만 다른 것 | 차이 |
|---|---|---|
| Q/K/V | 어텐션 | 어텐션은 구조의 이름, Q/K/V는 그 구조를 이루는 세 벡터다 |
| Q/K/V | 트랜스포머 | 더 위층이다. 트랜스포머 ⊃ 어텐션 ⊃ Q/K/V. 트랜스포머는 어텐션을 여러 층 쌓아 만든 모델 구조 전체를 가리킨다 |
| 어텐션 | RAG 검색 | 어텐션은 모델 안, RAG는 모델 밖. 아래 5번에서 자세히 |
| Q/K/V | DB의 Key-Value | 이름이 여기서 왔다. DB는 정확히 일치하는 행만 가져오고, 어텐션은 모든 행을 비율대로 섞는다 |
이름의 뿌리가 데이터베이스라는 게 재미있다. WHERE user = '민수'를 보면 검색 조건 '민수'가 Q, 열 user가 K, 열 fact가 V에 해당한다. 어텐션은 말랑말랑한 데이터베이스 조회인 셈이다. DB는 맞거나 안 맞거나 둘 중 하나지만, 어텐션은 “85% 맞음”이 가능하다.
5. 많이들 오해하는 지점
ㄱ. “어텐션이 가장 관련 있는 단어 하나를 골라낸다”
고르지 않는다. 전부를 비율대로 섞는다.
도서관 비유로 돌아가면, 한 권만 빌려 오는 게 아니라 공룡백과 70% + 파충류책 20% + 나머지를 겹쳐 읽는 것이다. “그것”의 경우도 사과만 가져온 게 아니라 0.85×사과 + 0.10×떨어뜨 + 0.05×민수를 섞었다.
이 차이가 중요한 이유는, 그래서 애매한 문맥도 처리할 수 있기 때문이다. 하나만 골랐다면 애매할 때 틀릴 수밖에 없다.
ㄴ. “어텐션이랑 RAG 검색이 결국 같은 것 아닌가”
둘 다 “관련 있는 걸 찾아온다”라서 헷갈리는데, 층이 완전히 다르다.
| RAG 검색 | 어텐션 | |
|---|---|---|
| 어디서 | 모델 밖 (내 코드) | 모델 안 (매 층마다) |
| 대상 | 문서 수천 개 | 지금 문맥의 토큰들 |
| 결과 | 상위 3개만 골라온다 | 전부를 비율대로 섞는다 |
| 누가 짜나 | 내가 짠다 | 학습으로 저절로 |
어텐션은 모델 안의 검색, RAG는 모델 밖의 검색이라고 보면 된다.
ㄷ. “Q, K, V가 각각 다른 데이터다”
세 글자로 나뉘어 있으니 서로 다른 데서 온 것처럼 보인다. 그렇지 않다. 셋 다 같은 단어의 임베딩 하나에서 나온다. 곱하는 행렬(W_q, W_k, W_v)만 다를 뿐이다.
한 사람이 상황에 따라 “찾는 사람 · 이름표 · 내용물” 세 역할을 동시에 맡는다고 생각하면 된다. 문장 안의 모든 단어가 각자 이 셋을 전부 갖고 있고, 서로를 훑는다.