
지금까지 우리는 LLM의 전체 흐름에서 세 단계를 지나왔다.
Language → Number → Context → Generation
──────────────────────────
여기까지
chapter. 1에서는 Vector, Matrix, Tensor, Dot Product, Softmax를 통해 LLM이 숫자를 어떻게 계산하는지 살펴봤다.
chapter. 2에서는:
Text
↓
Tokenization
↓
Token ID
↓
Embedding
↓
Token Vector
를 따라가며 Language가 Number로 변환되는 과정을 살펴봤다.
3편에서는 이 Token Vector들이 서로의 관계를 계산했다.
Token Embeddings
↓
Q / K / V
↓
Self-Attention
↓
Transformer
↓
Contextual Representations
즉 단순한 Token Vector가 아니라 주변 Context가 반영된 Representation을 만들었다.
그런데 아직 한 가지 문제가 남아 있다.
"나는 오늘 학교에"
↓
Transformer
↓
[0.18, -0.42, 0.71, 0.09, ...]
Transformer가 만들어낸 것은 여전히 Vector다.
하지만 우리가 실제로 원하는 것은:
"갔다"
같은 다음 Token이다.
그렇다면 LLM은 이 Vector에서 어떻게 실제 Token 하나를 선택할까?
그리고 더 근본적으로,
모델은 애초에 어떤 Token이 다음에 와야 하는지를 어떻게 학습했을까?
이번 글에서는 README에서 잡았던 첫 번째 흐름의 마지막 구간을 완성한다.
Language → Number → Context → Generation
──────────
이번 글
그리고 이번에도 같은 순서로 기술을 분해한다.
WHY
왜 Contextual Representation만으로는
답변을 만들 수 없을까?
↓
HOW
Vector를 어떻게 Token Probability로 바꾸고
실제 다음 Token을 선택할까?
↓
CODE
Logits → Softmax → Temperature → Sampling을
직접 계산해보자.
↓
LLM
Next Token Prediction은 어떻게
Training과 Generation으로 연결될까?
3편에서 Transformer가 하는 핵심 작업을 다음처럼 정리했다.
Token Embedding
↓
Self-Attention
↓
Transformer Layers
↓
Contextual Representation
예를 들어:
나는 오늘 학교에
라는 Sequence가 있다고 하자.
Transformer를 통과한 각 Token 위치에는 주변 Context가 반영된 Vector가 만들어진다.
마지막 위치의 Representation을 단순화해서 표현하면:
"에"
↓
[0.21, -0.73, 0.18, 0.42, ...]
이 Vector에는 앞서 등장한:
나는
오늘
학교
등의 Context가 반영되어 있다.
하지만 모델의 Vocabulary에는:
갔다
왔다
있다
먹었다
좋다
...
처럼 수많은 Token 후보가 존재한다.
현재 가지고 있는 것은 하나의 Hidden Vector이고,
필요한 것은 Vocabulary 전체에 대한:
“다음 Token으로 각 후보가 얼마나 적절한가?”
라는 Score다.
따라서:
Contextual Representation
↓
Vocabulary의 각 Token과 연결
↓
각 Token의 Score 계산
과정이 필요하다.
여기서 LM Head가 등장한다.
Transformer 마지막 Layer에서 얻은 Hidden Representation을 (h)라고 하자.
h = [0.21, -0.73, 0.18, 0.42, ...]
이 Vector의 Dimension이 모델의 Hidden Size라고 생각할 수 있다.
하지만 우리가 필요한 출력은 Vocabulary 전체에 대한 Score다.
예를 들어 Vocabulary Size가 50,000이라면:
Hidden Representation
[d_model]
↓
LM Head
↓
Vocabulary Scores
[50000]
형태로 변환해야 한다.
개념적으로 Linear Projection을 생각하면:
[
z = hW + b
]
여기서:
이다.
모델에 따라 Input Embedding과 Output Projection의 Weight를 공유하는 Weight Tying이 사용되기도 한다.
핵심은 다음이다.
LM Head는 Transformer가 만든 Hidden Representation을 Vocabulary 크기의 출력 공간으로 변환한다.
그러면:
갔다 8.2
왔다 5.7
있다 4.9
먹었다 1.3
자동차 -0.8
...
같은 값이 만들어진다.
이 값들을 Logits라고 한다.
Logit은 모델이 각 Token 후보에 부여한 정규화되지 않은 Score다.
여기서 중요한 것은:
Logit ≠ Probability
라는 점이다.
예를 들어:
Token Logit
갔다 8.2
왔다 5.7
있다 4.9
먹었다 1.3
자동차 -0.8
라고 하자.
갔다 = 8.2라고 해서:
P("갔다") = 82%
라는 뜻은 아니다.
Logit은 후보 Token 사이의 상대적인 선호를 표현하는 Raw Score에 가깝다.
따라서 다음 단계가 필요하다.
Logits
↓
Softmax
↓
Probability Distribution
그리고 여기서 1편에서 배웠던 Softmax가 다시 등장한다.
Softmax는 여러 Score를 합이 1인 분포로 변환한다.
각 Token의 Logit을 (z_i)라고 하면:
[
\frac{e^{z_i}}
{\sum_j e^{z_j}}
]
이다.
예를 들어:
Logits
갔다 8.2
왔다 5.7
있다 4.9
먹었다 1.3
↓ Softmax
Probability
갔다 0.88
왔다 0.07
있다 0.04
먹었다 0.01
처럼 생각할 수 있다.
이제 모델은:
Vocabulary
갔다 █████████████████ 0.88
왔다 ██ 0.07
있다 █ 0.04
먹었다 0.01
...
와 같은 다음 Token에 대한 분포를 갖는다.
전체 흐름은 이제:
Contextual Representation
↓
LM Head
↓
Logits
↓
Softmax
↓
Token Probability Distribution
까지 왔다.
하지만 아직 실제 Token을 선택하지 않았다.
여기서 새로운 질문이 생긴다.
확률이 가장 높은 Token을 그냥 고르면 되는 것 아닐까?
이 질문이 Decoding으로 이어진다.
모델이 다음과 같은 Probability Distribution을 만들었다고 해보자.
갔다 0.45
왔다 0.25
다녀왔다 0.15
있었다 0.10
먹었다 0.03
...
이제 이 분포에서 실제 다음 Token 하나를 선택해야 한다.
이 과정을 Decoding이라고 한다.
Token Probability Distribution
↓
Decoding
↓
Selected Token
중요한 점은:
같은 모델이 같은 Logits를 만들더라도 Decoding 전략에 따라 실제 출력은 달라질 수 있다.
는 것이다.
가장 단순한 방법부터 살펴보자.
Greedy Decoding은 가장 높은 Probability를 가진 Token을 선택한다.
갔다 0.45 ← 선택
왔다 0.25
다녀왔다 0.15
있었다 0.10
수식으로 표현하면:
[
\arg\max_i P(token_i)
]
이다.
장점은 단순하다.
같은 Probability Distribution이 주어진다면 항상 가장 높은 후보를 선택한다.
하지만 Text Generation에서는 매번 가장 높은 후보만 선택하는 것이 항상 좋은 결과를 만드는 것은 아니다.
언어에는 여러 자연스러운 표현이 존재하기 때문이다.
오늘 학교에 갔다.
오늘 학교에 다녀왔다.
오늘 학교에 있었다.
Context에 따라 여러 후보가 어느 정도 자연스러울 수 있다.
그래서 확률 분포를 이용해 Token을 선택하는 Sampling 방식이 사용될 수 있다.
Sampling에서는 가장 높은 Token을 무조건 선택하지 않는다.
예를 들어:
갔다 0.45
왔다 0.25
다녀왔다 0.15
있었다 0.10
...
라는 분포가 있다면 각 Token의 Probability에 따라 Token을 Sampling할 수 있다.
Probability Distribution
↓
Random Sampling
↓
Next Token
높은 Probability의 Token이 선택될 가능성이 크지만 다른 후보도 선택될 수 있다.
이를 통해 출력에 다양성을 줄 수 있다.
하지만 너무 낮은 확률의 후보까지 모두 Sampling하면 부자연스러운 Token이 선택될 가능성도 있다.
그래서 실제 생성에서는 Probability Distribution을 조정하거나 후보를 제한하는 방법을 함께 사용할 수 있다.
대표적인 것이:
Temperature
Top-k
Top-p
다.
Temperature는 Logits에 적용된다.
Temperature를 (T)라고 하면:
[
softmax\left(\frac{z_i}{T}\right)
]
이다.
Temperature가 낮으면:
Temperature ↓
Logit 차이가 상대적으로 강조
↓
Probability Distribution이 더 뾰족
↓
높은 확률 Token에 더 집중
예를 들어:
Low Temperature
갔다 ███████████████
왔다 ██
있다 █
먹었다
반대로 Temperature가 높으면:
Temperature ↑
Logit 차이가 상대적으로 완화
↓
Probability Distribution이 더 평평
↓
다른 후보가 선택될 가능성 증가
High Temperature
갔다 ███████
왔다 █████
있다 ████
먹었다 ███
여기서 중요한 점이 있다.
Temperature가 모델의 Parameter나 학습된 지식을 바꾸는 것은 아니다.
Temperature는 현재 생성 과정에서 Token 선택에 사용되는 Distribution의 형태를 조절한다.
Vocabulary에는 수만 개 이상의 Token이 존재할 수 있다.
하지만 다음 Token을 선택할 때 모든 후보가 현실적으로 의미 있는 것은 아니다.
예를 들어:
갔다 0.45
왔다 0.25
다녀왔다 0.15
있었다 0.10
먹었다 0.03
자동차 0.001
...
여기서 Top-k = 3이라면 확률이 높은 상위 3개의 후보만 남긴다.
갔다 0.45 ✓
왔다 0.25 ✓
다녀왔다 0.15 ✓
───────────────
있었다 0.10 ✕
먹었다 0.03 ✕
자동차 0.001 ✕
그 후 남은 후보들의 Probability를 다시 정규화해 Sampling할 수 있다.
전체 Vocabulary
↓
Top k Candidates
↓
Renormalization
↓
Sampling
즉 Top-k의 기준은:
후보의 개수
다.
Top-p Sampling, 또는 Nucleus Sampling은 기준이 다르다.
후보 개수를 고정하는 대신 높은 Probability부터 Token을 모아 누적 Probability가 일정 기준에 도달하도록 후보 집합을 구성한다.
예를 들어:
Token Probability Cumulative
A 0.40 0.40
B 0.25 0.65
C 0.15 0.80
D 0.10 0.90
E 0.05 0.95
top_p = 0.8이라면 개념적으로:
A
+
B
+
C
↓
Cumulative Probability = 0.80
에 해당하는 후보 집합을 사용한다.
따라서:
Top-k
→ 후보 개수를 제한
Top-p
→ 누적 Probability를 기준으로 후보 집합 구성
이라고 구분할 수 있다.
Top-p의 장점은 Distribution에 따라 후보 수가 달라질 수 있다는 것이다.
확신이 높은 분포
↓
적은 후보
확신이 낮은 분포
↓
더 많은 후보
Temperature, Top-k, Top-p는 모델과 생성 시스템의 설정에 따라 조합되어 사용될 수 있다.
이제 모델이 다음 Token으로:
"갔다"
를 선택했다고 하자.
기존 Context가:
나는 오늘 학교에
였다면:
나는 오늘 학교에 갔다
가 된다.
하지만 생성은 여기서 끝나지 않는다.
새롭게 생성한 Token을 Sequence에 추가하고 다시 다음 Token을 예측한다.
나는 오늘 학교에
↓
"갔다"
↓
나는 오늘 학교에 갔다
↓
Transformer
↓
Next Token Probability
↓
"."
그리고 다시:
나는 오늘 학교에 갔다.
를 새로운 Context로 사용한다.
즉 Generation은:
Context
↓
Next Token Prediction
↓
Token 선택
↓
Sequence에 추가
↓
New Context
↓
Next Token Prediction
↓
...
을 반복하는 과정이다.
이를 Autoregressive Generation이라고 한다.
LLM이 문장 전체를 한 번에 꺼내는 것이 아니다.
기본적으로:
현재까지의 Context
↓
다음 Token 하나
↓
Context Update
↓
다음 Token 하나
를 반복하면서 Sequence를 확장한다.
여기까지는 학습이 끝난 모델이 어떻게 Token을 생성하는지 살펴봤다.
이를 Inference라고 한다.
하지만 더 근본적인 질문이 남아 있다.
왜 모델은
"나는 오늘 학교에"다음에"갔다"가 자연스럽다는 것을 알고 있을까?
모델이 처음부터 이 관계를 알고 있었던 것은 아니다.
학습을 통해 Parameter를 조정해야 한다.
여기서 Generation과 Training이 하나의 개념으로 연결된다.
바로:
이다.
다음 문장이 있다고 하자.
나는 오늘 학교에 갔다
Language Model은 이 Text를 이용해 다음과 같은 문제를 학습할 수 있다.
Input Target
나는 오늘
나는 오늘 학교에
나는 오늘 학교에 갔다
즉 학습 목표는:
Previous Tokens
↓
Predict
↓
Next Token
이다.
이것이 Next Token Prediction이다.
GPT 계열과 같은 Causal Language Model의 Pre-training에서 핵심적인 학습 목표다.
여기서 중요한 점은:
Generation과 Training이 완전히 다른 원리로 동작하는 것이 아니라, 둘 다 다음 Token을 예측한다는 같은 핵심 구조를 공유한다.
는 것이다.
차이는 정답이 존재하는가에 있다.
실제 학습에서는 매번 문장을 하나씩 잘라 별도의 데이터로 만들 필요는 없다.
Sequence를 한 칸 이동시켜 Input과 Target을 구성할 수 있다.
예를 들어:
Input
나는 | 오늘 | 학교에 | 갔다
Target은:
Target
오늘 | 학교에 | 갔다 | <EOS>
처럼 한 위치 Shift된 형태가 된다.
수식으로 표현하면:
Input
[t₁, t₂, t₃, t₄]
Target
[t₂, t₃, t₄, t₅]
각 위치에서:
t₁ → t₂ 예측
t₂까지의 Context → t₃ 예측
t₃까지의 Context → t₄ 예측
을 수행한다.
여기서 3편에서 배웠던 Causal Mask가 다시 등장한다.
예를 들어 (t_3) 위치에서 (t_4)를 예측한다면:
t₁
t₂
t₃
↓
사용 가능
t₄
↓
미래 Token이므로 볼 수 없음
이어야 한다.
즉:
Next Token Prediction
+
Causal Mask
↓
Causal Language Modeling
으로 연결된다.
모델이 다음 Token의 Probability를 만들었다고 하자.
실제 정답은:
"갔다"
인데 모델의 예측이:
왔다 0.50
갔다 0.30 ← 정답
있다 0.15
먹었다 0.05
였다면 모델은 정답에 충분히 높은 Probability를 주지 못했다.
이 예측이 얼마나 틀렸는지 숫자로 나타내는 것이 Loss다.
Language Modeling에서는 대표적으로 Cross-Entropy Loss를 사용한다.
정답 Token을 (y)라고 하면 핵심 아이디어를 다음처럼 표현할 수 있다.
[
Loss = -\log P(y)
]
정답 Token의 Probability가 높다면:
P(correct token) ↑
↓
Loss ↓
반대로 정답 Token의 Probability가 낮다면:
P(correct token) ↓
↓
Loss ↑
가 된다.
즉 Loss는:
현재 모델의 Prediction이 정답에서 얼마나 벗어나 있는지를 학습 가능한 숫자로 표현한다.
Loss를 계산했다고 모델이 자동으로 좋아지는 것은 아니다.
이제 Loss를 줄이려면 어떤 Parameter를 어느 방향으로 바꿔야 하는지 계산해야 한다.
여기서 Backpropagation이 사용된다.
전체 흐름은:
Input Tokens
↓
Embedding
↓
Transformer
↓
LM Head
↓
Logits
↓
Probability
↓
Loss
↓
Backpropagation
↓
Gradient
↓
Optimizer
↓
Parameter Update
Gradient는 각 Parameter를 변화시켰을 때 Loss가 어떤 방향으로 변하는지에 대한 정보를 제공한다.
Optimizer는 이 Gradient를 이용해 Parameter를 업데이트한다.
여기서 업데이트되는 것은 특정 부분 하나만이 아니다.
Embedding Matrix
Q / K / V Projection Weights
Attention Output Projection
Feed-Forward Network
Layer Parameters
LM Head
...
등 모델의 수많은 Learnable Parameter가 학습 대상이 된다.
2편에서:
Embedding Matrix도 Learnable Parameter다.
라고 했던 이유도 여기서 완전히 연결된다.
이제 LLM의 Pre-training을 전체적으로 볼 수 있다.
Large Text Corpus
↓
Tokenization
↓
Token Sequences
↓
Transformer
↓
Next Token Prediction
↓
Loss
↓
Backpropagation
↓
Parameter Update
↓
반복
모델은 방대한 Text에서 이 과정을 반복한다.
그 과정에서 다음 Token을 예측하는 데 유용한 언어적 패턴과 다양한 관계가 Parameter에 반영된다.
예를 들어 Text 안에는:
단어 사용 패턴
문법 구조
문맥 관계
표현 방식
개념 간 연관성
다양한 사실적 패턴
...
이 존재한다.
모델은 이것들을 사람이 직접 규칙으로 입력해서 배우는 것이 아니라 Next Token Prediction이라는 학습 목표를 반복적으로 최적화하는 과정에서 유용한 패턴을 Parameter에 반영한다.
따라서 LLM을:
거대한 문장을 저장해두었다가 그대로 검색하는 시스템
으로 이해하면 정확하지 않다.
보다 정확하게는:
많은 Text에서 다음 Token Prediction을 학습하며 언어와 데이터의 패턴을 Parameter에 반영한 모델
이라고 이해할 수 있다.
Language Model을 공부하다 보면 Perplexity(PPL)라는 용어도 자주 등장한다.
Cross-Entropy Loss와 연결되는 대표적인 Language Model 평가 지표 중 하나다.
단순화하면:
[
PPL = e^{Loss}
]
로 생각할 수 있다.
일반적으로 같은 평가 조건에서 Perplexity가 낮을수록 모델이 실제 다음 Token에 더 높은 Probability를 부여하고 있다는 의미로 해석할 수 있다.
정답 Token 예측이 좋아짐
↓
Cross-Entropy ↓
↓
Perplexity ↓
다만 Tokenizer, Dataset, 평가 조건 등이 다르면 Perplexity를 단순 숫자만으로 직접 비교하기 어렵다.
이번 글에서는:
Next Token Prediction 성능을 바라보는 대표적인 지표 중 하나
정도로 위치만 기억해두자.
여기서 또 하나 구분해야 할 것이 있다.
Pre-training의 핵심 목표는:
Context
↓
Next Token Prediction
이다.
하지만 우리가 Chat 형태의 LLM에게 기대하는 것은 단순한 Text Completion만이 아니다.
예를 들어:
"다음 문장을 영어로 번역해줘."
↓
지시를 이해하고
원하는 작업을 수행
하기를 기대한다.
그래서 Pre-training 이후 Instruction Tuning 같은 추가 학습 단계가 사용될 수 있다.
Pre-training
↓
Base Model
↓
Instruction Tuning
↓
Instruction-following Model
Instruction Tuning에서는:
Instruction
"다음 문장을 영어로 번역해줘."
Input
"오늘 날씨가 좋다."
Response
"The weather is nice today."
처럼 Instruction과 Response의 관계를 학습할 수 있다.
중요한 점은 Instruction Tuning을 거친다고 해서 모델의 기본 생성 원리가 완전히 달라지는 것은 아니라는 것이다.
여전히 모델 내부의 핵심 생성 방식은:
Context
↓
Next Token
↓
Context
↓
Next Token
이다.
다만 추가 학습을 통해 어떤 응답이 주어진 Instruction에 적절한지를 더 잘 따르도록 조정한다.
실제 Assistant Model을 만드는 과정에서는 Instruction Tuning 이후 사람의 선호나 원하는 행동에 더 잘 맞도록 추가적인 학습을 적용할 수도 있다.
큰 흐름에서 보면:
Pre-training
↓
Base Model
↓
Instruction Tuning
↓
Preference / Alignment Training
↓
Assistant Model
여기에는 모델과 학습 방식에 따라:
RLHF
DPO
기타 Preference Optimization
등의 방법이 사용될 수 있다.
이번 글에서는 각각의 알고리즘을 깊게 다루지 않는다.
지금 중요한 것은 이들의 위치다.
Pre-training
"언어와 데이터의 패턴을 학습"
Instruction Tuning
"지시를 따르는 형식을 학습"
Preference / Alignment
"선호되는 응답 행동을 추가로 조정"
그리고 이 모든 과정의 기반에는 여전히 Token Probability를 계산하는 Language Model이 존재한다.
여기까지 오면 자주 혼동하는 두 개념을 명확하게 분리할 수 있다.
모델의 Parameter를 학습하는 과정이다.
Training Data
↓
Transformer
↓
Logits
↓
Probability
↓
Target과 비교
↓
Loss
↓
Backpropagation
↓
Parameter Update
학습된 Parameter를 이용해 실제 Prediction을 수행하는 과정이다.
Prompt
↓
Transformer
↓
Logits
↓
Probability
↓
Decoding
↓
Next Token
↓
Sequence에 추가
↓
반복
둘을 비교하면:
| Training | Inference | |
|---|---|---|
| 목적 | Parameter 학습 | 학습된 모델 사용 |
| Target | 있음 | 일반적으로 없음 |
| Loss | 계산 | 일반적인 생성에서는 계산하지 않음 |
| Backpropagation | 수행 | 수행하지 않음 |
| Gradient | 필요 | 일반 생성에서는 필요 없음 |
| Parameter Update | 있음 | 없음 |
| Next Token | 정답과 비교 | 실제 출력 후보 |
| Decoding | 핵심 단계 아님 | 핵심 단계 |
둘은 다른 과정이지만 공통점이 있다.
Training
│
└── Next Token Prediction
│
Inference ────┘
즉 같은 Language Model의 Next Token Prediction이 학습에서는 학습 신호가 되고, 추론에서는 실제 생성 결과가 된다.
이제 직접 계산해보자.
먼저 네 개의 Token 후보와 Logit을 만든다.
import numpy as np
tokens = [
"갔다",
"왔다",
"있다",
"먹었다"
]
logits = np.array([
4.2,
2.8,
1.7,
0.4
])
아직 이 값들은 Probability가 아니다.
Logits
[4.2, 2.8, 1.7, 0.4]
Softmax를 구현한다.
def softmax(x):
exp_x = np.exp(x - np.max(x))
return exp_x / np.sum(exp_x)
그리고:
probabilities = softmax(logits)
for token, probability in zip(
tokens,
probabilities
):
print(token, probability)
이제:
Logits
↓
Softmax
↓
Token Probabilities
를 직접 확인할 수 있다.
여기서 np.max(x)를 빼는 것은 Softmax 계산에서 큰 지수값으로 인한 수치적 불안정을 줄이기 위한 처리다.
Temperature도 직접 확인해보자.
def temperature_softmax(
logits,
temperature=1.0
):
scaled_logits = logits / temperature
return softmax(scaled_logits)
낮은 Temperature:
low_temperature = temperature_softmax(
logits,
temperature=0.5
)
print(low_temperature)
높은 Temperature:
high_temperature = temperature_softmax(
logits,
temperature=1.5
)
print(high_temperature)
비교하면:
Temperature = 0.5
↓
높은 Logit에 Probability 집중
Temperature = 1.5
↓
Probability가 더 넓게 분산
되는 것을 확인할 수 있다.
즉 코드에서도:
Model Parameter
↓
변경 X
Logit Distribution
↓
Temperature 적용
↓
Probability Distribution 변화
라는 관계를 확인할 수 있다.
이제 실제 Token을 하나 선택한다.
probabilities = softmax(logits)
next_token = np.random.choice(
tokens,
p=probabilities
)
print(next_token)
Greedy 방식이라면:
next_token_index = np.argmax(probabilities)
next_token = tokens[next_token_index]
print(next_token)
로 구현할 수 있다.
즉 두 방식의 차이는 명확하다.
Greedy
Probability
↓
argmax
↓
Highest Probability Token
Sampling
Probability
↓
Random Choice
based on Probability
↓
Selected Token
실제 LLM Serving System의 Decoding은 더 복잡하고 효율적인 구현을 사용하지만 기본 아이디어는 여기서 출발한다.
지금까지 배운 모든 내용을 하나로 연결하면 다음과 같다.
사용자가 Prompt를 입력한다.
"나는 오늘 학교에"
먼저 2편의 과정이 실행된다.
Raw Text
↓
Tokenizer
↓
Token IDs
↓
Embedding
↓
Token Representations
그리고 3편의 과정이 실행된다.
Token Representations
↓
Self-Attention
↓
Transformer Blocks
↓
Contextual Representations
이번 편의 과정이 이어진다.
Contextual Representation
↓
LM Head
↓
Logits
↓
Softmax
↓
Token Probability Distribution
↓
Decoding
↓
Next Token
예를 들어:
Next Token
"갔다"
가 선택된다.
그러면:
나는 오늘 학교에
+
갔다
↓
나는 오늘 학교에 갔다
새 Token이 Context에 추가된다.
그리고 다시 같은 과정을 반복한다.
나는 오늘 학교에 갔다
↓
Transformer
↓
Next Token
↓
"."
↓
나는 오늘 학교에 갔다.
즉 실제 Generation의 핵심 Loop는:
┌──────────────────────────────┐
│ │
│ Context │
│ ↓ │
│ Transformer │
│ ↓ │
│ Logits │
│ ↓ │
│ Decoding │
│ ↓ │
│ Next Token │
│ ↓ │
│ Context에 Token 추가 │
│ │ │
└─────────────┘
이다.
이 Loop가 종료 조건을 만날 때까지 반복되면서 우리가 보는 Response가 만들어진다.
이제 README에서 처음 설정했던 첫 번째 흐름을 완성할 수 있다.
"나는 오늘 학교에 갔다."
사람이 사용하는 자연어다.
↓
Tokenization
↓
Token IDs
↓
Embedding
↓
Vectors / Tensors
Text를 모델이 계산할 수 있는 숫자로 바꾼다.
↓
Q / K / V
↓
Self-Attention
↓
Transformer
↓
Contextual Representation
Token 사이의 관계를 계산해 Context를 반영한다.
↓
Contextual Representation
↓
LM Head
↓
Logits
↓
Softmax
↓
Decoding
↓
Next Token
↓
Repeat
다음 Token을 반복적으로 생성한다.
결국 지금까지의 전체 과정은:
Language
│
▼
Tokenization
│
▼
Token IDs
│
▼
Embedding
│
▼
Number
│
▼
Self-Attention
│
▼
Transformer
│
▼
Context
│
▼
LM Head
│
▼
Logits
│
▼
Probability
│
▼
Decoding
│
▼
Generation
이 된다.
Transformer는 Context를 계산했지만 그 결과는 여전히 Vector다.
Contextual Representation
↓
Vector
하지만 우리가 원하는 것은
↓
Token
따라서 Vector를 Vocabulary의 Token 후보와 연결하는 과정이 필요했다.
Inference에서는:
Contextual Representation
↓
LM Head
↓
Logits
↓
Softmax
↓
Probability Distribution
↓
Temperature / Top-k / Top-p
↓
Decoding
↓
Next Token
과정을 사용한다.
Training에서는:
Next Token Prediction
↓
Target과 비교
↓
Cross-Entropy Loss
↓
Backpropagation
↓
Gradient
↓
Optimizer
↓
Parameter Update
과정을 반복한다.
NumPy를 통해:
Logits
↓
Softmax
↓
Temperature
↓
Probability
↓
Greedy / Sampling
↓
Token
을 직접 확인했다.
실제 LLM에서는:
Prompt
↓
Tokenization
↓
Embedding
↓
Transformer
↓
Contextual Representation
↓
LM Head
↓
Logits
↓
Decoding
↓
Next Token
↓
Context에 추가
↓
반복
하면서 Response를 생성한다.
그리고 이 모델은 그 이전에:
Large Text Corpus
↓
Next Token Prediction
↓
Loss
↓
Parameter Update
↓
Pre-training
을 거쳐 이러한 능력을 학습했다.
처음에는 서로 독립적인 개념처럼 보였다.
Vector
Matrix
Tensor
Dot Product
Softmax
Token
Embedding
Attention
Transformer
Logits
하지만 지금은 각각의 위치를 설명할 수 있다.
Text
│
▼
Tokenizer
│
▼
Token IDs
│
▼
Embedding Matrix
│
▼
Token Vectors
│
▼
Q / K / V
│
▼
Dot Product
│
▼
Softmax
│
▼
Attention
│
▼
Transformer
│
▼
Contextual Representation
│
▼
LM Head
│
▼
Logits
│
▼
Softmax
│
▼
Decoding
│
▼
Next Token
특히 Softmax는 두 번 등장한다.
Attention
Attention Score
↓
Softmax
↓
Attention Weight
Generation
Token Logits
↓
Softmax
↓
Token Probability
같은 연산이지만 역할이 다르다.
하나는:
어떤 Token의 정보를 얼마나 참고할 것인가?
를 결정하고,
다른 하나는:
어떤 Token이 다음에 올 가능성이 높은가?
를 나타낸다.
이제 1편에서 배웠던 수학이 왜 필요했는지도 전체 구조 안에서 설명할 수 있다.
0편에서 처음 봤던 LLM은 하나의 Black Box처럼 보였다.
User
↓
LLM
↓
Answer
하지만 이제 그 안을 펼쳐볼 수 있다.
User Text
↓
Tokenization
↓
Token IDs
↓
Embedding
↓
Transformer
↓
Contextual Representation
↓
Logits
↓
Token Probability
↓
Decoding
↓
Next Token
↓
Autoregressive Generation
↓
Answer
즉 우리가 처음 설정했던:
이라는 흐름이 완성됐다.
하지만 여기서 또 다른 문제가 생긴다.
LLM은 학습 과정에서 Parameter에 반영된 정보와 현재 Context를 기반으로 답변한다.
그렇다면:
모델이 학습하지 않은 정보는 어떻게 사용할까?
예를 들어:
오늘 업데이트된 사내 문서
내가 가진 PDF
회사 Database
최신 제품 정보
내 프로젝트의 문서
같은 외부 정보가 필요하다면 어떻게 해야 할까?
모델을 질문할 때마다 다시 학습시키는 것은 현실적인 해결책이 아니다.
그래서 다음 단계에서는 모델 외부의 정보를 찾아 Context에 넣어주는 구조가 등장한다.
다음 글에서는:
User Question
↓
Embedding
↓
Vector Search
↓
Relevant Documents
↓
Context
↓
LLM
↓
Answer
이라는 새로운 흐름을 살펴본다.
여기서 1편의 Cosine Similarity와 2편의 Embedding이 다시 등장한다.
그리고 다음 개념들이 연결된다.
Document
↓
Chunking
↓
Embedding
↓
Vector Database
↓
Similarity Search
↓
Retrieval
↓
Prompt Context
↓
LLM
지금까지는 LLM 내부에서 답변이 어떻게 만들어지는가를 봤다면,
다음부터는:
이 LLM을 실제 정보를 사용하는 시스템으로 어떻게 확장할 것인가?
를 다루게 된다.
Language
↓
Number
↓
Context
↓
Generation
↓
──────────────
LLM Core 완성
↓
RAG
Tool Calling
Agent
↓
LLM Application