5. 조립: 토큰이 쌓이고 레이어가 깊어지는 과정
어텐션과 FFN을 조립하는 과정
3편에서 어텐션을, 4편에서 FFN을 각각 "OOM" 하나로 이해했다. 부품의 동작 원리는 파악했다. 이번 편에서는 이 부품들을 조립한다. 토큰이 5개로 늘어나고, 레이어가 36개 쌓이면 어떤 일이 벌어지는가?
어텐션과 FFN — 하나의 레이어를 이루는 두 부품
이 둘은 하나의 레이어 안에서 순서대로 실행된다.
┌──────────────────────────────────────────────────┐
│ 하나의 레이어 │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 어텐션 │ → │ FFN │ │
│ │ │ │ │ │
│ │ ● 토큰 간 연산 │ │ ● 토큰별 독립 │ │
│ │ ● "누가 누구와 │ │ ● "이게 무슨 │ │
│ │ 관련있나" │ │ 의미인가" │ │
│ │ ● 관계 파악 │ │ ● 의미 변환 │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ 인풋: 벡터(2048) 인풋: 벡터(2048) │
│ 아웃풋: 벡터(2048) 아웃풋: 벡터(2048) │
│ │
│ 각 부품 전후에 LayerNorm + 잔차 연결 적용 (4편) │
└──────────────────────────────────────────────────┘어텐션은 "killer"가 "OOM"과 관련 있다는 관계를 파악한다. 가로 연결이다. FFN은 관계가 파악된 벡터를 "메모리 부족 상황"이라는 의미로 변환한다. 세로 깊이다. 인풋/아웃풋 차원이 항상 동일(2048)이니까 이 레이어를 몇 개든 쌓을 수 있다.
5개 토큰이 36개 레이어를 동시에 통과한다
"OOM killer가 nginx를" = 5개 토큰. 이 5개가 한꺼번에 레이어 1에 들어가고, 레이어 1의 출력이 레이어 2에 들어가고... 레이어 36까지 통과한다.
임베딩 벡터 5개 (각 토큰의 개별 의미만, 문맥 없음)
│
▼
┌─ 레이어 1 ───────────────────────────────────┐
│ 어텐션: 5개 토큰이 동시에 서로 참조 │ 문법 구조 파악
│ FFN: 각 벡터를 독립적으로 의미 변환 │
│ (LayerNorm + 잔차연결 포함) │
└───────────────────────────────────────────────┘
│ 벡터 5개 (1차 문맥 반영)
▼
┌─ 레이어 2 ───────────────────────────────────┐
│ 어텐션: 이미 1차 문맥이 담긴 벡터들 사이에서 │ 의미 부여
│ 더 깊은 관계를 계산 │
│ FFN → (LayerNorm + 잔차연결) │
└───────────────────────────────────────────────┘
│ 벡터 5개 (2차 문맥 반영)
▼ ...36개 레이어...
┌─ 레이어 36 ──────────────────────────────────┐
│ 어텐션 → FFN │ 고차 추론
└───────────────────────────────────────────────┘
│
▼
최종 벡터 5개 (각 2048차원, 풍부한 문맥)모든 레이어가 동일한 구조이지만, 각 레이어의 W는 전부 다른 파라미터다. 사람이 설계한 게 아니라 학습 결과로 역할이 분화된다. 앞쪽이 문법, 뒤쪽이 의미와 추론.
레이어 수도 모델마다 다르다. 3B는 약 36개, 7B는 약 32개, 14B는 약 48개. 더 많은 레이어는 더 깊은 추론을 의미한다.
인과적 마스킹 — 동시에 넣어도 미래를 못 본다
그런데 5개를 "동시에" 넣으면, 뒤에 있는 토큰의 정보가 앞 토큰에 새어 들어가지 않을까?
그렇게 되면 안 된다. LLM의 학습 목표는 "다음 토큰 예측"이다. 미래를 볼 수 있으면 답을 보고 시험 치는 것과 같다. 이걸 보장하는 게 인과적 마스킹이다.
어텐션 계산 시 각 토큰이 볼 수 있는 범위:
Q ↓ \ K → OOM killer 가 nginx 를
───────── ───── ────── ───── ───── ─────
OOM ○ ✕ ✕ ✕ ✕
killer ○ ○ ✕ ✕ ✕
가 ○ ○ ○ ✕ ✕
nginx ○ ○ ○ ○ ✕
를 ○ ○ ○ ○ ○
○ = 참조 가능 ✕ = 마스킹 (-∞ → softmax 후 0)
→ 하삼각(lower triangular) 행렬.✕ 위치에 -∞를 넣으면 softmax를 거친 후 주목도가 0이 된다. 미래 토큰의 V는 가중합에 반영되지 않는다.
이 마스크 덕분에 "OOM"의 어텐션 결과는 뒤에 killer가 있든 없든 동일하다. 5개를 동시에 넣어도 1개씩 순차적으로 넣은 것과 같은 결과가 보장된다. GPU가 5개를 병렬로 한 번에 처리할 수 있는 이유이기도 하다.
벡터에서 토큰으로 — 출력 행렬
36개 레이어를 통과한 최종 벡터 5개가 나왔다. 다음 토큰을 예측하려면 이 벡터를 토큰으로 바꿔야 한다.
최종 벡터 (마지막 토큰 "를" 위치, 2048차원)
│
▼ × 출력 행렬 (2048 × 151,936)
│
▼
151,936개의 점수 (어휘 전체에 하나씩)
│
▼ softmax
│
▼
확률 분포:
"종료시켰다" 23% ◀── 가장 높음
"메모리" 15%
"해당" 8%
"OOM" 6%
...
나머지 15만개 각 0.001% 미만왜 "마지막 토큰 위치"의 벡터만 사용하는가? 인과적 마스킹 덕분에 마지막 토큰의 벡터에 이전 모든 토큰의 정보가 누적되어 있다. 이 벡터 하나가 문장 전체의 문맥을 담고 있다.
출력 행렬과의 행렬곱으로 점수를 구하고, softmax로 확률로 바꾸고, 그중에서 하나를 고른다. 벡터 공간에서 "가장 가까운 토큰"을 찾는 게 아니라, 행렬곱으로 점수를 직접 계산하는 거다.
1편의 "한 문장의 여행"이 여기서 완성된다
"OOM killer가 nginx를"
① 토크나이저: 텍스트 → 정수 5개 (2편)
② 임베딩: 정수 → 벡터 5개 (개별 의미만) (2편)
③ 5개를 한꺼번에 36개 레이어에 통과: (이번 편)
레이어마다 어텐션(인과적 마스킹) → FFN → 잔차연결
④ 마지막 토큰("를") 위치의 최종 벡터 → 출력 행렬 (이번 편)
→ "종료시켰다" 선택
→ 이후 토큰 생성은? → 6편그런데 "종료시켰다"가 선택된 후에는 어떻게 될까? 이제 6개 토큰이다. 앞의 5개는 이미 처리가 끝났는데, 또 6개를 통째로 36개 레이어에 넣어야 할까? 다음 편에서 이 낭비를 없애는 방법을 다룬다.
이 글이 어떠셨나요?
관련 포스트
1. LLM은 텍스트를 어떻게 처리하는가
LLM이 텍스트 한 줄을 받아서 다음 토큰을 예측하기까지의 전체 과정을 4단계로 따라간다. 토크나이저, 임베딩, 트랜스포머 레이어, 출력까지 "OOM killer가 nginx를" 한 문장이 모델 안에서 겪는 여행.
2026. 03. 03. 오후 10:00DevOps3. 어텐션: 새 토큰이 문맥을 얻는 과정
LLM 어텐션의 실제 계산 과정을 숫자로 추적한다. Q·K 내적이 주목도를 만들고, V의 가중합이 문맥을 만드는 과정. 멀티 헤드가 필요한 이유, 스케일링(√d), 잔차 연결까지.
2026. 03. 17. 오후 10:00DevOps4. FFN: 지식이 저장된 곳
LLM의 FFN이 어텐션과 어떻게 역할을 나누는지. 확장→압축 구조가 왜 "지식 저장소"인지. 분산 표현의 원리, LayerNorm의 역할까지.
2026. 03. 24. 오후 10:00뉴스레터 구독
새 글이 올라오면 이메일로 알려드려요.