LLM 공부 03 | Transformer 안에서 문맥이 섞이는 방식
2편에서는 문장이 token ID가 되고, embedding table을 지나 벡터가 되는 지점까지 내려갔다.
이제 그 벡터들이 어디로 가는지 봐야 한다.
LLM의 중심에는 Transformer block stack이 있다. 같은 모양의 block이 여러 층 쌓여 있고, token 벡터는 그 block들을 차례로 지나간다. 각 block 안에서는 self-attention과 MLP/FFN이 작동하고, residual stream이 계속 갱신된다.
AI Frontier EP 96의 Transformer 설명에서 핵심은 이 구조였다.
token ID
-> embedding vector
-> Transformer block 1
-> Transformer block 2
-> ...
-> final hidden state
-> LM head
-> next token
이번 글은 이 중 Transformer block 하나를 확대해서 보는 글이다.
목표는 세 가지다.
self-attention은 무엇을 섞는가?
Q/K/V는 무엇을 위한 구조인가?
MLP/FFN과 residual stream은 왜 필요한가?
앞 3편 중 이 글의 역할은 4편으로 넘어가기 전 내부 구조를 고정하는 것이다. prefill, decode, KV cache, batch, sequence length는 모두 serving 관점의 말처럼 들리지만, 결국 Transformer block 안의 attention 계산과 연결된다.
따라서 3편에서는 하드웨어와 scheduler로 바로 가지 않는다. 먼저 block 안에서 어떤 값이 만들어지고, 무엇이 cache되고, 어떤 계산은 매번 다시 해야 하는지 구분한다. 이 구분이 있어야 4편의 prefill/decode 설명이 흐릿해지지 않는다.
Transformer block은 반복되는 계산 단위다
Transformer block은 물리적인 서버 한 대가 아니다. 모델 architecture 안의 반복 계산 단위다.
학습이 끝난 모델에는 각 block의 weight가 parameter로 저장되어 있다. 추론 때 이 weight가 GPU memory에 올라가고, token 벡터가 들어오면 matrix multiplication과 activation 계산을 통해 다음 벡터를 만든다.
입문 단계에서는 block을 이렇게 보면 된다.
입력 벡터들
-> self-attention
-> MLP / FFN
-> 출력 벡터들
실제 구현에는 layer normalization, residual connection, attention projection, activation function 같은 세부 구조가 들어간다. 모델마다 pre-norm/post-norm, attention 변형, RoPE, grouped-query attention, multi-query attention, MoE 같은 차이가 있다.
하지만 큰 흐름은 유지된다.
attention으로 token 사이 정보를 섞고
MLP로 각 token 표현을 더 깊게 변환하고
residual connection으로 기존 흐름에 더한다
이 block이 여러 번 반복된다.
embedding
-> block
-> block
-> block
-> ...
layer 수는 모델마다 정해진 구조다. 요청이 들어올 때마다 layer가 늘었다 줄었다 하지 않는다. 이 점은 reasoning effort를 이해할 때 중요하다. high reasoning이 layer 수를 더 많이 통과한다는 뜻은 아니다.
Residual stream은 token 벡터가 흐르는 작업 공간이다
Transformer를 이해할 때 "벡터가 계속 바뀐다"는 감각이 필요하다.
처음에는 token ID가 embedding vector로 바뀐다. 이 벡터는 아직 문맥을 충분히 반영하지 못한다. block을 지날 때마다 attention과 MLP가 계산한 변화량이 더해지고, token 표현은 조금씩 갱신된다.
이 흐름을 residual stream이라고 부를 수 있다.
현재 token 표현
+ attention이 만든 업데이트
+ MLP가 만든 업데이트
-> 다음 block으로 넘어갈 token 표현
왜 더하기를 할까?
깊은 network에서는 매 layer가 입력을 완전히 새로 덮어쓰게 만들면 학습과 정보 전달이 어려워질 수 있다. residual connection은 기존 표현을 유지하면서 필요한 변화량을 더하는 방식이다.
비유하면 이렇다.
처음 embedding = 초안
attention 업데이트 = 주변 문맥을 반영한 수정
MLP 업데이트 = 지금 token 표현 자체의 가공
residual stream = 수정이 계속 누적되는 작업 문서
이 비유는 완벽하지 않지만, "block이 지나갈수록 token 벡터가 문맥화된다"는 감각을 잡는 데 도움이 된다.
Self-attention은 token들이 서로를 참고하게 만든다
문장은 token들의 나열이다. 하지만 token 하나의 의미는 주변 token에 따라 달라진다.
"그것이 느려졌다"
여기서 "그것"이 무엇인지 알려면 앞 문장을 봐야 한다. "모델", "GPU", "KV cache", "network" 중 무엇을 가리키는지 문맥이 필요하다.
self-attention은 각 token이 같은 sequence 안의 다른 token들을 얼마나 참고할지 계산하는 구조다.
decoder-only LLM에서는 현재 token이 미래 token을 볼 수 없다. 다음 token을 예측하는 모델이 아직 생성되지 않은 미래 token을 보면 cheating이 된다. 그래서 causal mask가 들어간다.
현재 위치의 token
-> 자기 자신과 과거 token은 볼 수 있음
-> 미래 token은 볼 수 없음
이 제약 때문에 attention matrix가 삼각형처럼 보이는 경우가 많다. 앞쪽 token만 참고할 수 있게 막아 두는 것이다.
입력 prompt를 prefill할 때도 이 causal 구조는 유지된다. prompt 전체를 병렬로 계산하더라도, 각 위치의 token은 자기보다 뒤의 token을 다음 token 예측에 활용하는 방식으로 보지 않는다. 병렬 계산과 causal 제약은 함께 존재한다.
Q/K/V는 token 관계를 계산하기 위한 설계다
self-attention 설명에서 가장 낯선 단어가 Q, K, V다.
Q = Query
K = Key
V = Value
이 이름 때문에 database key-value lookup을 떠올리기 쉽다. 하지만 LLM attention의 Q/K/V를 실제 DB 검색처럼 이해하면 오해가 생긴다.
노정석 대표가 EP 96에서 짚은 중요한 감각도 여기에 있다. Q/K/V라는 이름에는 어느 정도 철학적 의미가 있지만, 핵심은 Transformer 저자들이 token 사이 관계를 계산하기 위해 만든 구조적 bias라는 점이다.
각 token의 현재 표현에서 세 종류의 벡터를 만든다.
현재 token 표현
-> Query vector
-> Key vector
-> Value vector
attention은 대략 이런 방식으로 작동한다.
내 Query와 다른 token들의 Key를 비교한다
-> 어떤 token을 얼마나 참고할지 score를 만든다
-> 그 score로 Value들을 가중합한다
-> 지금 token에 반영할 문맥 정보를 얻는다
정확한 수식은 dot product, scaling, softmax, projection을 포함한다. 하지만 입문 단계에서는 이렇게 잡으면 충분하다.
Q/K 비교 = 어떤 token을 얼마나 볼지 정하는 과정
V 가중합 = 보고 온 정보를 현재 token에 가져오는 과정
Self-attention은 현재 context 안의 token 표현끼리 관계를 계산한다. 외부 문서를 찾아오는 retrieval과는 다른 층이다.
중요한 것은 attention이 "원본 문서를 검색"하는 것이 아니라는 점이다. attention은 현재 sequence 안의 token 표현들 사이에서 관계를 계산한다. 외부 지식 검색은 RAG, Vector DB, Graph RAG, search tool의 일이고, self-attention은 모델 내부 계산이다.
Multi-head attention은 여러 관점의 관계를 동시에 본다
attention은 보통 한 번만 일어나지 않는다. multi-head attention은 여러 head가 서로 다른 projection으로 관계를 계산하게 한다.
한 head는 문법적 의존성을 더 잘 볼 수 있고, 다른 head는 가까운 token을 더 강하게 볼 수 있고, 또 다른 head는 괄호나 코드 블록 같은 구조에 민감할 수 있다. 실제로 특정 head가 무엇을 담당한다고 단정하기는 어렵다. 다만 여러 head가 서로 다른 관계 패턴을 학습할 수 있는 구조라는 점은 중요하다.
head 1: 어떤 관계 패턴
head 2: 다른 관계 패턴
head 3: 또 다른 관계 패턴
...
-> concat / projection
-> attention output
이 attention output이 residual stream에 더해진다.
residual stream
+ attention output
-> 문맥이 반영된 token 표현
이제 token은 자기 혼자만의 embedding이 아니다. 같은 sequence 안에서 어떤 token들을 참고해야 하는지가 반영된 표현이 된다.
KV cache는 attention에서 나온다
KV cache를 이해하려면 Q/K/V 중 무엇이 재사용되는지 알아야 한다.
decode 단계에서는 token을 하나씩 새로 만든다. 새 token이 들어오면 그 token의 Query는 새로 계산해야 한다. 하지만 과거 token들의 Key와 Value는 이미 계산해 두었다.
그래서 cache에 저장한다.
prefill:
prompt token들의 K/V를 layer별로 계산해서 저장
decode:
새 token의 Q를 만들고
과거 K/V cache를 참조하고
새 token의 K/V를 cache에 추가
이것이 KV cache다.
중요한 보정이 있다.
KV cache는 모델의 모든 중간 활성값을 저장하는 것이 아니다. attention에 필요한 Key/Value를 저장하는 동적 상태다. MLP/FFN의 모든 중간 activation을 다음 token 생성용 cache처럼 들고 있는 것이 아니다.
또한 KV cache는 parameter가 아니다. 요청 처리 중 생기는 상태다. 요청이 끝나면 버릴 수 있다. 같은 모델 weight를 쓰더라도 사용자 prompt가 다르면 KV cache도 다르다.
parameter = 모델에 학습되어 고정된 weight
activation = 지금 forward pass 중 생기는 계산값
KV cache = attention K/V를 다음 decode step에서 재사용하기 위해 저장한 상태
조금 더 좁히면, KV cache는 attention output이나 residual stream 자체가 아니다.
각 layer는 현재 hidden state에서 Q, K, V를 만든다. 이때 cache에 남는 것은 보통 K와 V다. Q는 지금 step에서 과거 K와 비교하는 데 쓰이고 버려진다. Attention output은 residual stream에 더해져 다음 계산으로 넘어가지만, 그 output 자체가 KV cache에 저장되는 것은 아니다.
MLP도 마찬가지다. MLP output은 residual stream에 더해져 다음 layer 입력 hidden state를 바꾼다. 그래서 이후 layer에서 만들어지는 K/V에는 MLP가 만든 변화가 간접적으로 반영된다. 하지만 과거 token의 모든 MLP 중간값이나 모든 hidden state tensor를 decode cache처럼 보관하는 것은 아니다.
이 구분이 잡히면 prefill/decode와 serving 비용이 훨씬 선명해진다.
MLP/FFN은 각 token 표현을 비선형으로 가공한다
attention이 끝나면 MLP 또는 FFN이라고 부르는 부분이 나온다.
FFN은 feed-forward network의 줄임말이다. LLM 문맥에서는 block 안에서 각 token 위치에 독립적으로 적용되는 작은 neural network라고 볼 수 있다. 보통 hidden dimension을 한 번 크게 늘렸다가 다시 줄이는 구조가 많이 쓰인다.
입문적으로는 이렇게 구분하면 된다.
self-attention:
token들 사이 정보를 섞는다
MLP / FFN:
각 token 표현을 비선형으로 변환한다
attention은 "어떤 주변 token을 참고할지"를 정하고, MLP는 "그렇게 문맥화된 지금 token 표현을 어떻게 가공할지"를 담당한다.
EP 96에서도 이 MLP 부분이 MoE와 연결되는 지점으로 설명되었다. dense 모델에서는 큰 MLP 하나가 모든 token에 적용된다. MoE 모델에서는 이 MLP 자리를 여러 expert MLP로 쪼개고, router가 token별로 일부 expert를 고른다.
하지만 MoE는 모든 LLM의 필수 구조가 아니다.
dense Transformer block:
attention + 하나의 MLP
MoE Transformer block:
attention + router + 여러 expert MLP 중 일부
3편에서는 MoE를 깊게 들어가지 않는다. 지금 중요한 것은 MLP가 Transformer block의 두 번째 큰 축이라는 점이다. MoE는 이 MLP 축을 어떻게 크게 만들고 효율화할지에서 나온 변형이다.
Attention과 MLP는 같은 일을 하지 않는다
attention과 MLP를 같은 "생각"으로 뭉뚱그리면 Transformer를 놓친다.
둘은 역할이 다르다.
attention:
sequence 안에서 token 간 의존성을 계산한다
MLP:
각 위치의 hidden state를 비선형 feature 공간에서 변환한다
예를 들어 코드 리뷰 prompt를 생각해 보자.
이 함수에서 cache invalidation이 제대로 되는지 봐줘.
attention은 cache, invalidation, 함수, 앞에서 주어진 코드 블록, 뒤에서 나온 에러 메시지 사이의 관계를 볼 수 있게 만든다. 어떤 token이 어떤 token을 참고해야 하는지 계산한다.
MLP는 attention을 통해 문맥이 들어온 각 token 표현을 더 복잡한 feature로 변환한다. "이 패턴은 버그일 가능성이 있다", "이 변수명은 앞에서 정의된 값과 연결된다", "이 문장은 instruction이다" 같은 분산된 feature가 이 과정에 관여할 수 있다.
물론 실제 feature를 사람이 깔끔하게 이름 붙일 수 있는 것은 아니다. LLM 내부 표현은 수많은 차원에 분산되어 있다. 하지만 attention과 MLP가 서로 다른 계산 축이라는 점은 기억할 만하다.
마지막 hidden state가 다음 token을 만든다
Transformer block stack을 모두 지나면 각 token 위치마다 hidden state가 나온다.
decoder-only generation에서는 보통 마지막 위치의 hidden state를 사용해 다음 token을 예측한다.
last hidden state
-> LM head
-> logits
-> next token
여기서 hidden state는 다음 token 자체가 아니다. 지금까지의 context를 모두 반영한 뒤 "다음 token 후보들을 평가하기 위한 벡터"다. LM head가 이 벡터를 vocabulary 전체의 점수로 바꾸고, 그중 하나의 token ID가 선택된다.
선택된 token ID는 다음 step에서 다시 embedding으로 들어간다.
final hidden state
-> logits over vocabulary
-> selected token ID
-> embedding(selected token ID)
-> 다음 decode step
계산된 hidden vector를 그대로 다음 입력으로 넘기지 않는 이유는 모델이 그렇게 학습되지 않았기 때문이다. 학습과 추론의 기본 인터페이스는 token ID에서 시작해 embedding으로 들어가고, hidden state를 거쳐 다음 token distribution으로 나오는 구조다.
여기서 만들어진 token은 다시 문맥 뒤에 붙고, 다음 decode step의 입력이 된다.
현재 context
-> 다음 token 생성
-> context 뒤에 붙임
-> 다시 다음 token 생성
이 반복이 답변을 만든다.
그래서 Transformer block을 이해하는 것은 "한 번 답을 만드는 과정"을 이해하는 것이 아니라, autoregressive generation의 매 step이 무엇을 반복하는지 이해하는 일이다.
prefill에서는 prompt 전체가 block stack을 지나며 첫 출력 token 후보와 KV cache를 만든다. decode에서는 새 token 하나가 같은 block stack을 지나며 기존 KV cache를 참조하고 새 K/V를 추가한다.
prefill과 decode는 다른 workload다.
하지만 일반적인 decoder-only LLM에서는 같은 Transformer block stack을 쓴다.
이 보정은 중요하다. prefill용 layer와 decode용 layer가 따로 있는 것이 아니다. 같은 모델 weight를 다른 실행 패턴으로 쓰는 것이다.
자주 생기는 오해들
첫 번째 오해는 "attention이 외부 문서를 검색한다"는 것이다.
attention은 현재 context 안의 token 표현들 사이 관계를 계산한다. 외부 문서 검색은 RAG, search tool, Vector DB, graph index가 하는 일이다. LLM 내부 attention은 soft search처럼 설명될 수 있지만, 그것은 sequence 내부 관계 계산에 대한 비유다. 외부 검색 계층의 차이는 LLM 공부 09 | RAG와 Vector DB: LLM 밖에서 근거를 찾는 방식에서 따로 다룬다.
두 번째 오해는 "KV cache가 모델 지식이다"라는 것이다.
KV cache는 요청 중 생기는 attention 상태다. 모델이 학습한 지식은 parameter에 분산되어 있다. KV cache는 지금 prompt를 처리하며 만들어진 Key/Value를 다음 token 생성에 재사용하기 위한 메모리다.
세 번째 오해는 "reasoning effort가 layer 수를 늘린다"는 것이다.
layer 수는 모델 architecture의 고정된 깊이다. reasoning effort는 더 많은 추론 token, 더 긴 내부 검토, 더 많은 후보 평가 또는 제품별 추론 예산과 관련된 설정으로 보는 편이 안전하다. 같은 모델이 갑자기 block을 더 많이 쌓아 통과하는 것은 아니다.
네 번째 오해는 "MoE expert가 주제별 전문가 사전이다"라는 것이다.
MoE의 expert는 보통 MLP/FFN expert다. 수학 질문이면 수학 expert, 문학 질문이면 문학 expert가 켜지는 식으로 단순하게 이해하면 위험하다. token별 routing은 훨씬 더 기계적이고 분산된 패턴으로 작동한다. 이 내용은 6편에서 다시 다룬다.
정리
Transformer block은 LLM의 반복 계산 단위다. token ID는 embedding vector가 되고, 이 벡터는 block stack을 지나며 계속 갱신된다.
self-attention은 token들이 같은 sequence 안에서 서로를 얼마나 참고할지 계산한다. Q/K/V는 그 관계 계산을 위한 구조다. causal mask는 미래 token을 보지 못하게 막는다. multi-head attention은 여러 관계 패턴을 동시에 계산할 수 있게 한다.
MLP/FFN은 attention 이후 각 token 표현을 비선형으로 변환한다. residual stream은 attention과 MLP의 업데이트를 기존 표현에 더하며 block 사이를 흐른다.
KV cache는 attention의 Key/Value를 저장하는 동적 상태다. parameter도 아니고, 외부 지식 저장소도 아니다. prefill에서 prompt token들의 K/V를 만들고, decode에서 그것을 재사용한다.
다음 글에서는 이 구조를 추론 실행 관점으로 다시 본다. prefill과 decode는 왜 다른 workload인지, KV cache는 왜 memory 병목이 되는지, Time To First Token과 output latency는 어떻게 달라지는지 정리한다.
이어 읽기
시리즈는 순서대로, 편집 추천은 맥락대로, 비슷한 주제는 태그 기준으로 정리합니다.
시리즈 전체
LLM 공부 시리즈3/9편- 1.LLM 공부 01 | LLM은 검색기가 아니라 다음 토큰 생성기다
- 2.LLM 공부 02 | 토큰이 비용을 만든다
- 3.LLM 공부 03 | Transformer 안에서 문맥이 섞이는 방식
- 4.LLM 공부 04 | Prefill과 Decode: LLM이 읽고 쓰는 두 단계
- 5.LLM 공부 05 | Batch와 Sequence Length가 속도와 비용을 정한다
- 6.LLM 공부 06 | MoE와 GPU 클러스터: 거대 모델은 어떻게 나뉘어 도는가
- 7.LLM 공부 07 | Reasoning Effort: 더 깊게 생각한다는 말의 실제 의미
- 8.LLM 공부 08 | 학습 Batch와 Distillation: 모델은 어떻게 바뀌는가
- 9.LLM 공부 09 | RAG와 Vector DB: LLM 밖에서 근거를 찾는 방식
비슷한 주제의 글
태그가 겹치는 글입니다. 시리즈와 편집 추천에 이미 나온 글은 제외합니다.
AI 웹개발 기초: 프론트엔드 1-1 | 프론트엔드는 왜 이렇게 복잡해졌을까
프론트엔드가 React나 Next.js 같은 기술명 묶음이 아니라, 웹 문서가 구조, 표현, 동작, 상태, API 통신, 성능, 배포 책임을 차례로 떠안으며 커진 문제 해결의 축적임을 설명하는 AI 웹개발 기초 시리즈 프론트엔드 1-1.
AI 웹개발 기초: 프론트엔드 1-2 | DOM은 화면이 아니라 브라우저의 작업 모델이다
HTML source가 곧 화면이 아니라 DOM, CSSOM, render tree, layout, paint, composite, accessibility tree를 거쳐 화면과 보조기술 의미로 바뀌며, DOM 조작이 reflow, XSS, event delegation, 성능 지표와 어떻게 연결되는지 설명하는 AI 웹개발 기초 시리즈 프론트엔드 1-2.
AI 웹개발 기초: 프론트엔드 1-3 | jQuery에서 React로 넘어간 진짜 이유
jQuery의 DOM 직접 조작과 AJAX가 해결한 문제를 인정하고, UI가 커지면서 React, Vue, Angular 같은 state/data 기반 component UI가 왜 필요해졌는지 설명하는 AI 웹개발 기초 시리즈 프론트엔드 1-3.