LLM 기초 For Dummies - forward, logits, attention, sampling, 학습과 추론

이 문서는 토크나이저, forward, 생성 루프를 읽기 위한 기초 용어 설명입니다.

여기까지 읽으셨다면 다음 두 방향으로 나뉩니다. 개념을 더 파고 싶으시면 1주차 part 2 - LLM과 트랜스포머 기본의 트랜스포머 절로 가시면 됩니다. 위치 인코딩, 멀티헤드, 잔차 연결 같은 나머지 조각이 거기 있습니다. 코드가 급하시면 위 링크의 워커 분석으로 바로 가셔도 됩니다.

전체 흐름부터 보기

LLM이 다음 토큰을 만드는 과정은 다음과 같습니다.

사람이 입력한 문자열
        ↓
토크나이저가 토큰 ID로 변환
        ↓
모델이 forward 실행
        ↓
각 토큰 후보의 점수인 logits 출력
        ↓
확률로 변환
        ↓
다음 토큰 하나 선택
        ↓
토크나이저가 문자열로 복원

예를 들면 다음과 같습니다.

입력: "나는 오늘"
모델 실행
다음 토큰: " 학교에"

입력: "나는 오늘 학교에"
모델 실행
다음 토큰: " 갔다"

이 과정을 반복하면 문장이 만들어집니다.

문자열이 토큰 ID로 바뀌고 모델을 통과해 점수가 되었다가 확률을 거쳐 토큰 하나로 선택된 뒤 다시 문자열로 돌아오는 전체 흐름

1. 모델의 forward 한 번이란?

쉬운 정의

입력을 모델의 처음부터 끝까지 한 번 통과시켜 결과를 계산하는 것입니다.

코드에서는 다음 한 줄입니다.

outputs = self.model(input_ids)

이 한 줄 안에서 다음 과정이 실행됩니다.

토큰 ID
  ↓
임베딩
  ↓
트랜스포머 블록 1
  ↓
트랜스포머 블록 2
  ↓
...
  ↓
마지막 트랜스포머 블록
  ↓
LM Head
  ↓
logits

따라서 forward 한 번은 트랜스포머 블록 하나만 실행하는 것이 아닙니다. 모델에 포함된 모든 트랜스포머 블록과 출력 계층을 한 차례 통과하는 것입니다.

토큰 ID가 임베딩과 열두 개의 트랜스포머 블록과 LM 헤드를 차례로 지나 logits가 되는 과정 전체가 forward 한 번이라는 그림

forward 한 번으로 문장이 완성되나?

아닙니다.

텍스트 생성 모델은 일반적으로 forward 한 번으로 다음 토큰 후보의 점수를 계산합니다.

입력: "나는 오늘"
forward 1회
다음 토큰: "학교에"

문장을 계속 생성하려면 방금 만든 토큰을 입력 뒤에 붙여 다시 forward해야 합니다.

"나는 오늘"
    ↓ forward
"학교에" 생성

"나는 오늘 학교에"
    ↓ forward
"갔다" 생성

이 반복을 자기회귀 생성이라고 합니다.

나는 오늘이 학교에를 만들고 그것을 뒤에 붙인 입력이 갔다를 만드는 자기회귀 반복

블록 사이를 흐르는 것은 무엇인가

블록을 지날 때 오가는 값을 hidden state라고 부릅니다. 각 토큰에 대해 모델이 지금까지 이해한 내용을 담은 벡터입니다.

여기서 자주 헷갈리는 점이 하나 있습니다. 블록 사이를 logits가 오가는 것이 아닙니다. logits는 맨 끝 LM Head에서 딱 한 번 생깁니다. 그 전까지 흐르는 것은 전부 hidden state입니다.

세 후보의 원점수 2.0 1.0 0.0이 softmax를 거쳐 66.5퍼센트 24.5퍼센트 9.0퍼센트의 확률로 바뀌는 막대 비교

길이는 끝까지 그대로입니다. GPT-2라면 768로 고정입니다. 바뀌는 것은 안에 든 값뿐입니다. 임베딩 직후에는 사전에서 찾은 뜻만 들어 있다가, 블록을 지날 때마다 어텐션이 앞 토큰의 정보를 섞어 넣습니다. 열두 번 반복하면 "그는" 벡터 안에 "철수"라는 맥락이 배어 있게 됩니다.

그래서 마지막 위치의 hidden state 하나가 문장 전체의 요약본 노릇을 합니다. LM Head는 그 요약본을 받아 "그렇다면 다음은 무엇인가"로 번역합니다.

hidden state logits
모양 [B, T, 768] [B, T, 50272]
언제 블록마다 계속 맨 끝에 한 번
뜻 토큰에 대한 이해 다음 토큰 후보의 점수
hidden과 파라미터는 다른 것

hidden size는 흐르는 데이터의 폭이고, 파라미터는 모델이 들고 있는 가중치의 개수입니다. opt-125m에서 hidden은 768이고 파라미터는 1억 2천 5백만 개입니다. hidden은 요청이 들어올 때마다 새로 만들어졌다 사라지고, 파라미터는 학습 때 정해져 추론 내내 고정입니다.

블록 안 행렬이 대부분 hidden × hidden이라서, hidden을 두 배로 키우면 파라미터는 네 배가 됩니다.

2. logit이란?

쉬운 정의

logit은 모델이 다음 토큰 후보마다 매긴 원점수입니다.

예를 들어 모델의 어휘에 다음 세 토큰만 있다고 가정해보겠습니다.

"학교에" → 2.0점
"밥을"   → 1.0점
"우주선" → 0.0점

여기서 2.0, 1.0, 0.0이 logits입니다.

토큰마다 하나씩 있는 768차원 벡터가 블록을 지날 때마다 주변 토큰의 정보를 흡수해 다듬어지고, 마지막 토큰의 벡터만 LM 헤드로 넘어가 logits가 되는 과정

logit은 확률이 아니다

logit에는 다음과 같은 특징이 있습니다.

logits를 softmax에 넣으면 확률처럼 사용할 수 있는 값으로 바뀝니다.

logits

학교에: 2.0
밥을:   1.0
우주선: 0.0

        ↓ softmax

확률

학교에: 66.5%
밥을:   24.5%
우주선:  9.0%

정리하면 다음과 같습니다.

logit
= 모델이 토큰 후보에 매긴 원점수

softmax
= 원점수를 합이 1인 확률 분포로 바꾸는 함수

3. 모델은 왜 logits를 많이 출력할까?

모델의 출력은 보통 다음 shape을 가집니다.

[B, T, V]

각 문자는 다음 뜻입니다.

문자 의미
B 한 번에 처리한 문장의 개수
T 각 문장의 토큰 길이
V 모델이 알고 있는 전체 토큰의 개수

예를 들어 다음 shape이 있다고 해보겠습니다.

[4, 7, 50272]

이는 다음 뜻입니다.

문장 4개를 한 번에 처리했고
각 문장의 길이는 7토큰이며
각 위치마다 50,272개 토큰의 점수를 계산했다

다음 코드는 그중 마지막 위치의 점수만 가져옵니다.

next_token_logits = outputs.logits[:, -1, :]

왜 마지막 위치만 필요할까요?

입력: "The quick brown"

"The" 위치의 예측
→ "The" 다음에는 무엇이 올까?

"quick" 위치의 예측
→ "The quick" 다음에는 무엇이 올까?

"brown" 위치의 예측
→ "The quick brown" 다음에는 무엇이 올까?

현재 문장 전체 다음에 올 토큰이 필요하므로 마지막 위치의 logits를 사용합니다.

4행 7열 격자의 각 칸마다 5만여 개의 점수가 있지만 추론에서는 마지막 열 하나만 사용하고 나머지는 버린다는 그림

4. 행렬곱과 어텐션은 같은 것인가?

같은 것은 아닙니다.

행렬곱은 계산 도구이고, 어텐션은 행렬곱을 이용해 만든 메커니즘입니다.

비유하면 다음과 같습니다.

행렬곱 = 벽돌
어텐션 = 벽돌로 만든 건물

행렬곱은 어텐션뿐 아니라 모델 곳곳에서 사용됩니다.

임베딩 계산
Q, K, V 생성
어텐션 점수 계산
FFN 계산
LM Head 계산

어텐션은 무엇을 하는가?

어텐션은 각 토큰이 이전 토큰 중 무엇을 얼마나 참고할지 계산합니다.

예를 들어 다음 문장이 있다고 해보겠습니다.

"철수는 사과를 먹었다. 그는 배가 고팠다."

모델이 그는을 처리할 때 앞의 철수를 중요하게 참고해야 합니다. 어텐션은 이런 관계를 계산합니다.

그는이라는 토큰이 앞의 철수는을 가장 많이 참고하는 모습과, Q와 K를 비교해 참고 비율을 만들고 그 비율로 V를 섞는 세 단계

각 토큰을 Q, K, V로 변환
        ↓
Q와 K를 비교
        ↓
어떤 토큰을 얼마나 참고할지 계산
        ↓
그 비율로 V를 섞음

어텐션의 핵심 계산은 다음과 같습니다.

Attention(Q, K, V)
= softmax(QKᵀ / √d) V

수식 자체를 외울 필요는 없습니다. 다음 정도로 이해하면 충분합니다.

Q와 K의 행렬곱
→ 토큰 사이의 관련성 점수 계산

softmax
→ 관련성 점수를 참고 비율로 변환

V와 결합
→ 참고한 정보를 현재 토큰에 반영

5. 샘플링은 언제 하는가?

샘플링은 어텐션을 포함한 모델의 forward가 모두 끝난 다음에 합니다.

트랜스포머 블록들
  ├─ 어텐션
  └─ FFN
        ↓
마지막 hidden state
        ↓
LM Head
        ↓
logits
        ↓
softmax
        ↓
샘플링
        ↓
다음 토큰

따라서 샘플링은 어텐션에 포함된 기능이 아닙니다.

트랜스포머 블록과 LM 헤드까지가 모델의 몫이고 softmax와 샘플링부터는 생성 코드의 몫이라는 경계를 보여주는 세로 흐름

역할을 나누면 다음과 같습니다.

모델
→ 다음 토큰 후보의 logits를 계산

샘플링 로직
→ logits 중 실제로 사용할 토큰 하나를 선택

코드에서는 다음 부분입니다.

next_token_logits = outputs.logits[:, -1, :]

probabilities = torch.softmax(
    next_token_logits / 0.7,
    dim=-1,
)

next_token = torch.multinomial(
    probabilities,
    num_samples=1,
)

각 단계의 의미는 다음과 같습니다.

next_token_logits / 0.7
→ 후보 사이의 점수 차이 조절

softmax
→ 점수를 확률로 변환

multinomial
→ 확률에 따라 토큰 하나 선택

모양이 줄어드는 길

여기까지를 한 줄로 늘어놓으면 이렇습니다. 어디까지가 모델이고 어디부터가 생성 코드인지도 이 그림에서 갈립니다.

4곱하기7곱하기50272 텐서가 마지막 위치 슬라이스와 softmax와 multinomial과 squeeze를 거쳐 길이 4인 정수 배열로 줄어드는 과정과, 모델과 생성 코드의 경계

[4]에 담긴 값은 어휘 전체가 아니라 골라낸 토큰 번호 네 개입니다. 예를 들어 [6219, 11475, 48659, 31414]처럼요. 각 값은 0부터 50271 사이의 정수 하나이고, 요청 하나에 토큰 하나씩 대응합니다.

어휘가 5만 개뿐인데 어떻게 온갖 문장을 만드느냐면 조합이기 때문입니다. 5만 개 중 하나를 고르는 일을 20번 반복하면 경우의 수가 50000^20입니다. 자모 24개로 모든 한국어 문장을 적는 것과 같습니다. 어휘가 크지 않아도 이어붙이면 끝이 없습니다.

softmax가 두 번 나와서 헷갈린다면

softmax는 어텐션 안에서도 나오고, 토큰 선택 과정에서도 나옵니다. 하지만 용도가 다릅니다.

위치 softmax의 역할
어텐션 내부 과거 토큰 중 무엇을 얼마나 참고할지 결정
logits 뒤 다음 토큰 후보 중 무엇을 선택할지 확률 계산

횟수도 다릅니다. 어텐션 쪽은 블록마다, 그리고 헤드마다 돕니다. opt-125m은 블록 12개에 헤드가 12개씩이라 forward 한 번에 144번 계산합니다. 생성 코드 쪽 softmax는 맨 끝에 딱 한 번입니다.

같은 함수를 다른 목적으로 사용하는 것입니다.

어텐션 안의 softmax는 앞 토큰들을 두고 참고 비율을 매기고, logits 뒤의 softmax는 어휘 후보들을 두고 선택 확률을 매긴다는 비교

6. 학습과 추론은 무엇이 다른가?

학습

모델이 정답을 더 잘 맞히도록 가중치를 수정하는 과정입니다.

학습은 다음 순서로 진행됩니다.

입력
  ↓
forward
  ↓
모델의 예측
  ↓
정답과 비교
  ↓
loss 계산
  ↓
backward
  ↓
가중치 수정

예를 들어 다음 학습 데이터가 있다고 해보겠습니다.

입력: "대한민국의 수도는"
정답: "서울"

모델이 다음처럼 예측했다고 가정합니다.

부산: 60%
서울: 30%
대전: 10%

정답은 서울인데 부산의 확률이 더 높습니다. 학습 과정에서는 다음 작업을 합니다.

  1. 예측과 정답의 차이인 loss를 계산합니다.
  2. backward로 어떤 가중치를 어떻게 바꿔야 하는지 계산합니다.
  3. optimizer가 가중치를 조금 수정합니다.
  4. 이 과정을 수많은 데이터로 반복합니다.

따라서 학습은 계산이 잘 돌아가는지 확인하는 과정이 아닙니다.

모델의 예측이 정답에 가까워지도록 내부 가중치를 조정하는 과정입니다.

계산이 잘되는지 또는 처음 보는 데이터에도 잘 맞히는지를 확인하는 것은 별도의 검증과 평가 과정에 더 가깝습니다.

추론

학습이 끝난 모델의 가중치를 고정하고 결과만 계산하는 과정입니다.

입력
  ↓
forward
  ↓
logits
  ↓
토큰 선택
  ↓
결과 반환

추론에서는 다음 작업을 하지 않습니다.

그래서 다음 코드를 사용합니다.

with torch.no_grad():
    outputs = self.model(...)

torch.no_grad()는 다음 뜻입니다.

가중치를 수정할 필요가 없으므로 역전파를 위한 계산 기록을 만들지 말고 결과만 계산하라.

학습은 forward 뒤에 정답 비교와 loss와 backward와 가중치 수정으로 이어지는 순환이고, 추론은 forward 뒤에 토큰 선택으로 끝나는 직선이라는 비교

학습과 추론 비교

구분 학습 추론
목적 모델을 개선 결과를 생성
forward 사용 사용
정답 데이터 필요 보통 불필요
loss 계산 사용 보통 사용하지 않음
backward 사용 사용하지 않음
가중치 수정 수정함 고정함
torch.no_grad() 보통 사용하지 않음 사용

한 문장으로 정리하면 다음과 같습니다.

학습은 forward → 오답 측정 → backward → 가중치 수정이고, 추론은 forward → 결과 사용입니다.

최종 요약

forward
= 입력을 모델 처음부터 끝까지 한 번 통과시키는 것

logit
= 모델이 다음 토큰 후보마다 매긴 원점수

행렬곱
= 어텐션을 포함해 모델 전체에서 사용하는 기본 계산 도구

어텐션
= 어떤 과거 토큰을 얼마나 참고할지 계산하는 메커니즘

샘플링
= 모델 계산이 끝난 뒤 logits에서 실제 다음 토큰을 선택하는 과정

학습
= 예측과 정답을 비교하고 가중치를 수정하는 과정

추론
= 학습된 가중치를 고정하고 결과만 계산하는 과정