Command Palette

Search for a command to run...

이 글의 핵심은 token 벡터가 Transformer block 안에서 self-attention과 MLP를 거치며 다음 token 예측에 필요한 hidden state로 바뀌는 과정입니다.

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에 가져오는 과정

Attention QKV flow

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은 반복 생성의 계산 단위입니다

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. 1.LLM 공부 01. LLM은 검색기가 아니라 다음 토큰 생성기다
  2. 2.LLM 공부 02. 토큰이 비용을 만든다
  3. 3.LLM 공부 03. Transformer 안에서 문맥이 섞이는 방식
  4. 4.LLM 공부 04. Prefill과 Decode는 LLM이 읽고 쓰는 두 단계다
  5. 5.LLM 공부 05. Batch와 Sequence Length가 속도와 비용을 정한다
  6. 6.LLM 공부 06. MoE와 GPU 클러스터는 거대 모델을 나누어 실행한다
  7. 7.LLM 공부 07. Reasoning Effort가 의미하는 추론 예산
  8. 8.LLM 공부 08. 학습 Batch와 Distillation은 모델을 어떻게 바꾸는가
  9. 9.LLM 공부 09. RAG와 Vector DB는 LLM 밖에서 근거를 찾는다

비슷한 주제의 글

태그가 겹치는 글입니다. 시리즈와 편집 추천에 이미 나온 글은 제외합니다.