[LLM.zip | 압축 해제] 4. Transformer는 어떻게 다음 Token을 선택할까? — Training & Inference

ju·3일 전

LLM.zip | 압축 해제

목록 보기
6/9
post-thumbnail

4. Transformer는 어떻게 다음 Token을 선택할까? — Training & Inference

지금까지 우리는 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으로 연결될까?

4.0 WHY — Context를 계산했는데 왜 아직 답변이 없을까?

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가 등장한다.


4.1 HOW — LM Head: Hidden Representation을 Vocabulary로 연결한다

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
]

여기서:

  • (h) = Contextual Representation
  • (W) = Vocabulary 공간으로 Projection하는 Weight
  • (b) = Bias가 사용되는 구조라면 Bias
  • (z) = Vocabulary Token들의 Score

이다.

모델에 따라 Input Embedding과 Output Projection의 Weight를 공유하는 Weight Tying이 사용되기도 한다.

핵심은 다음이다.

LM Head는 Transformer가 만든 Hidden Representation을 Vocabulary 크기의 출력 공간으로 변환한다.

그러면:

갔다        8.2
왔다        5.7
있다        4.9
먹었다      1.3
자동차     -0.8
...

같은 값이 만들어진다.

이 값들을 Logits라고 한다.


4.2 HOW — Logits: Token 후보에 대한 Score

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가 다시 등장한다.


4.3 HOW — Softmax: Score를 Token Probability로 바꾼다

Softmax는 여러 Score를 합이 1인 분포로 변환한다.

각 Token의 Logit을 (z_i)라고 하면:

[

P(token_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으로 이어진다.


4.4 HOW — Decoding: Probability에서 실제 Token을 선택한다

모델이 다음과 같은 Probability Distribution을 만들었다고 해보자.

갔다        0.45
왔다        0.25
다녀왔다    0.15
있었다      0.10
먹었다      0.03
...

이제 이 분포에서 실제 다음 Token 하나를 선택해야 한다.

이 과정을 Decoding이라고 한다.

Token Probability Distribution
              ↓
           Decoding
              ↓
         Selected Token

중요한 점은:

같은 모델이 같은 Logits를 만들더라도 Decoding 전략에 따라 실제 출력은 달라질 수 있다.

는 것이다.

가장 단순한 방법부터 살펴보자.


4.5 HOW — Greedy Decoding: 가장 높은 확률을 선택한다

Greedy Decoding은 가장 높은 Probability를 가진 Token을 선택한다.

갔다        0.45  ← 선택
왔다        0.25
다녀왔다    0.15
있었다      0.10

수식으로 표현하면:

[

token

\arg\max_i P(token_i)
]

이다.

장점은 단순하다.

같은 Probability Distribution이 주어진다면 항상 가장 높은 후보를 선택한다.

하지만 Text Generation에서는 매번 가장 높은 후보만 선택하는 것이 항상 좋은 결과를 만드는 것은 아니다.

언어에는 여러 자연스러운 표현이 존재하기 때문이다.

오늘 학교에 갔다.

오늘 학교에 다녀왔다.

오늘 학교에 있었다.

Context에 따라 여러 후보가 어느 정도 자연스러울 수 있다.

그래서 확률 분포를 이용해 Token을 선택하는 Sampling 방식이 사용될 수 있다.


4.6 HOW — Sampling: 확률에 따라 Token을 선택한다

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

다.


4.7 HOW — Temperature: 확률 분포의 형태를 조절한다

Temperature는 Logits에 적용된다.

Temperature를 (T)라고 하면:

[

P_i

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의 형태를 조절한다.


4.8 HOW — Top-k: 상위 k개의 Token만 후보로 남긴다

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의 기준은:

후보의 개수

다.


4.9 HOW — Top-p: 누적 Probability로 후보를 결정한다

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는 모델과 생성 시스템의 설정에 따라 조합되어 사용될 수 있다.


4.10 HOW — Token 하나를 생성했다고 답변이 완성되는 것은 아니다

이제 모델이 다음 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를 확장한다.


4.11 WHY — 그런데 모델은 어떻게 다음 Token을 알게 되었을까?

여기까지는 학습이 끝난 모델이 어떻게 Token을 생성하는지 살펴봤다.

이를 Inference라고 한다.

하지만 더 근본적인 질문이 남아 있다.

왜 모델은 "나는 오늘 학교에" 다음에 "갔다"가 자연스럽다는 것을 알고 있을까?

모델이 처음부터 이 관계를 알고 있었던 것은 아니다.

학습을 통해 Parameter를 조정해야 한다.

여기서 Generation과 Training이 하나의 개념으로 연결된다.

바로:

Next Token Prediction

이다.


4.12 HOW — Next Token Prediction: 다음 Token을 맞히는 문제

다음 문장이 있다고 하자.

나는 오늘 학교에 갔다

Language Model은 이 Text를 이용해 다음과 같은 문제를 학습할 수 있다.

Input                    Target

나는                     오늘

나는 오늘                학교에

나는 오늘 학교에         갔다

즉 학습 목표는:

Previous Tokens
      ↓
Predict
      ↓
Next Token

이다.

이것이 Next Token Prediction이다.

GPT 계열과 같은 Causal Language Model의 Pre-training에서 핵심적인 학습 목표다.

여기서 중요한 점은:

Generation과 Training이 완전히 다른 원리로 동작하는 것이 아니라, 둘 다 다음 Token을 예측한다는 같은 핵심 구조를 공유한다.

는 것이다.

차이는 정답이 존재하는가에 있다.


4.13 HOW — Shifted Labels: Sequence 전체에서 다음 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

으로 연결된다.


4.14 HOW — Loss: 모델이 얼마나 틀렸는지 계산한다

모델이 다음 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이 정답에서 얼마나 벗어나 있는지를 학습 가능한 숫자로 표현한다.


4.15 HOW — Cross-Entropy에서 Parameter Update까지

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다.

라고 했던 이유도 여기서 완전히 연결된다.


4.16 HOW — Pre-training: 이 과정을 거대한 Text에서 반복한다

이제 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에 반영한 모델

이라고 이해할 수 있다.


4.17 HOW — Perplexity는 무엇일까?

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 성능을 바라보는 대표적인 지표 중 하나

정도로 위치만 기억해두자.


4.18 HOW — Pre-training만 하면 Assistant가 될까?

여기서 또 하나 구분해야 할 것이 있다.

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에 적절한지를 더 잘 따르도록 조정한다.


4.19 HOW — Alignment는 어디에 들어갈까?

실제 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이 존재한다.


4.20 HOW — Training과 Inference는 무엇이 다를까?

여기까지 오면 자주 혼동하는 두 개념을 명확하게 분리할 수 있다.

Training

모델의 Parameter를 학습하는 과정이다.

Training Data
      ↓
Transformer
      ↓
Logits
      ↓
Probability
      ↓
Target과 비교
      ↓
Loss
      ↓
Backpropagation
      ↓
Parameter Update

Inference

학습된 Parameter를 이용해 실제 Prediction을 수행하는 과정이다.

Prompt
   ↓
Transformer
   ↓
Logits
   ↓
Probability
   ↓
Decoding
   ↓
Next Token
   ↓
Sequence에 추가
   ↓
반복

둘을 비교하면:

TrainingInference
목적Parameter 학습학습된 모델 사용
Target있음일반적으로 없음
Loss계산일반적인 생성에서는 계산하지 않음
Backpropagation수행수행하지 않음
Gradient필요일반 생성에서는 필요 없음
Parameter Update있음없음
Next Token정답과 비교실제 출력 후보
Decoding핵심 단계 아님핵심 단계

둘은 다른 과정이지만 공통점이 있다.

Training
   │
   └── Next Token Prediction
              │
Inference ────┘

즉 같은 Language Model의 Next Token Prediction이 학습에서는 학습 신호가 되고, 추론에서는 실제 생성 결과가 된다.


4.21 CODE — Logits를 Probability로 바꿔보자

이제 직접 계산해보자.

먼저 네 개의 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 계산에서 큰 지수값으로 인한 수치적 불안정을 줄이기 위한 처리다.


4.22 CODE — Temperature를 직접 적용해보자

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 변화

라는 관계를 확인할 수 있다.


4.23 CODE — Probability에서 Token을 Sampling해보자

이제 실제 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은 더 복잡하고 효율적인 구현을 사용하지만 기본 아이디어는 여기서 출발한다.


4.24 LLM — 이제 전체 생성 Pipeline을 연결해보자

지금까지 배운 모든 내용을 하나로 연결하면 다음과 같다.

사용자가 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가 만들어진다.


4.25 Language → Number → Context → Generation

이제 README에서 처음 설정했던 첫 번째 흐름을 완성할 수 있다.

Language

"나는 오늘 학교에 갔다."

사람이 사용하는 자연어다.

↓

Number

Tokenization
     ↓
Token IDs
     ↓
Embedding
     ↓
Vectors / Tensors

Text를 모델이 계산할 수 있는 숫자로 바꾼다.

↓

Context

Q / K / V
    ↓
Self-Attention
    ↓
Transformer
    ↓
Contextual Representation

Token 사이의 관계를 계산해 Context를 반영한다.

↓

Generation

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

이 된다.


4.26 이번 글을 Why → How → Code → LLM으로 다시 정리하면

WHY

Transformer는 Context를 계산했지만 그 결과는 여전히 Vector다.

Contextual Representation
          ↓
       Vector

하지만 우리가 원하는 것은

          ↓

        Token

따라서 Vector를 Vocabulary의 Token 후보와 연결하는 과정이 필요했다.


HOW

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

과정을 반복한다.


CODE

NumPy를 통해:

Logits
   ↓
Softmax
   ↓
Temperature
   ↓
Probability
   ↓
Greedy / Sampling
   ↓
Token

을 직접 확인했다.


LLM

실제 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

을 거쳐 이러한 능력을 학습했다.


4.27 지금까지 배운 것들이 하나로 연결된다

처음에는 서로 독립적인 개념처럼 보였다.

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편에서 배웠던 수학이 왜 필요했는지도 전체 구조 안에서 설명할 수 있다.


4.28 여기까지가 LLM의 기본 Engine이다

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

즉 우리가 처음 설정했던:

Language → Number → Context → Generation

이라는 흐름이 완성됐다.

하지만 여기서 또 다른 문제가 생긴다.

LLM은 학습 과정에서 Parameter에 반영된 정보와 현재 Context를 기반으로 답변한다.

그렇다면:

모델이 학습하지 않은 정보는 어떻게 사용할까?

예를 들어:

오늘 업데이트된 사내 문서

내가 가진 PDF

회사 Database

최신 제품 정보

내 프로젝트의 문서

같은 외부 정보가 필요하다면 어떻게 해야 할까?

모델을 질문할 때마다 다시 학습시키는 것은 현실적인 해결책이 아니다.

그래서 다음 단계에서는 모델 외부의 정보를 찾아 Context에 넣어주는 구조가 등장한다.


다음 글

5. 모델의 한계를 외부 정보로 보완하기 — RAG

다음 글에서는:

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

0개의 댓글