트랜스포머·PEFT 2일 과정6장 · Qwen 모델 준비와 텍스트 생성8 / 17← 이전목차다음 →MetaContents

6장. Qwen 모델 준비와 텍스트 생성

디코딩 전략: temperature, top-k, top-p

점심 메뉴를 고르는 세 가지 버릇을 생각해 봅니다. 늘 가던 집만 가는 사람(낮은 temperature), 가끔 새 집도 가 보는 사람(높은 temperature), 그리고 "후보는 인기 순위 2위까지만"이라는 규칙을 두는 사람(top-k), "인기 투표 합이 80% 가 될 때까지의 집만"이라는 규칙을 두는 사람(top-p)입니다. temperature 는 모험심 다이얼, top-k 는 후보 k명만 남기는 예선, top-p 는 누적 득표 p% 까지만 결선입니다.

로짓을 T 로 나눈 뒤 Softmax 한 확률이 T 0.5, 1, 1.5 에 따라 바뀌는 막대그래프와, top-k 2 와 top-p 0.8 에서 남는 후보를 견준 강사자료 그림

강사자료 그림의 위쪽은 같은 후보 5개(A~E)의 확률이 temperature 에 따라 어떻게 바뀌는지 보여 줍니다. T = 1 이 기준 분포(40, 30, 15, 10, 5%)이고, T = 0.5 면 A 가 56.1% 로 커지며 뾰족해지고, T = 1.5 면 A 가 33.4% 로 줄며 평평해집니다. 아래쪽은 후보를 거르는 두 방법입니다. top-k = 2 는 A, B 만 남기고, top-p = 0.8 은 A + B 가 70% 라 모자라서 C 까지 더해 85% 에서 멈춥니다. 두 방법 모두 남은 후보 안에서 확률에 따라 하나를 뽑습니다.

세 가지 설정

설정하는 일값이 작으면값이 크면점심으로 치면
temperature로짓을 T 로 나눠 분포를 뾰족하게, 평평하게안정적, 반복적다양하지만 엉뚱할 수 있음모험심 다이얼
top_k확률 상위 k 개 후보만 남김보수적다양함인기 순위 k 위까지만
top_p누적 확률 p 까지의 후보만 남김(nucleus sampling)보수적다양함누적 득표 p% 까지만
하는 일알맞은 설정
사실 확인, 분류, 코드 생성처럼 정확성이 중요한 일Greedy 또는 낮은 temperature
아이디어, 글쓰기처럼 다양성이 필요한 일높은 temperature 와 top-p 를 함께
학습 전후 비교처럼 같은 조건이 필요한 일Greedy(do_sample=False)

손으로 한 번 계산해 봅니다. 로짓이 [2.0, 1.0, 0.1] 이면 T = 1 일 때 e^2 ≈ 7.39, e^1 ≈ 2.72, e^0.1 ≈ 1.11, 합 11.21 로 나눠 0.66, 0.24, 0.10 입니다. T = 0.5 면 로짓을 0.5 로 나눠 [4, 2, 0.2] 가 되고, e^4 ≈ 54.6, e^2 ≈ 7.39, e^0.2 ≈ 1.22, 합 63.2 로 나눠 0.86, 0.12, 0.02 입니다. 1등이 0.66 에서 0.86 으로 커졌습니다. 나누는 값이 작을수록 점수 차이가 벌어져 1등이 독차지합니다.

짧은 예시

Python
import numpy as np

def softmax_t(logits, temperature=1.0):
    # 로짓을 temperature 로 나눈 뒤 Softmax(합이 1 인 비율)로 바꾼다
    z = np.array(logits) / temperature
    e = np.exp(z - z.max())  # 가장 큰 값을 빼도 비율은 같고, 큰 수 넘침을 막는다
    return e / e.sum()

# 손계산과 대조: 로짓 [2.0, 1.0, 0.1]
for t in [0.5, 1.0, 2.0]:
    print(t, softmax_t([2.0, 1.0, 0.1], t).round(2))
# 결과: 0.5 [0.86 0.12 0.02] / 1.0 [0.66 0.24 0.1 ] / 2.0 [0.5 0.3 0.19]

# 강사자료 그림의 후보 A~E: T = 1 에서 40, 30, 15, 10, 5% 가 되도록 로짓을 log(확률)로 둔다
p = np.array([0.40, 0.30, 0.15, 0.10, 0.05])
logits = np.log(p)
for t in [0.05, 0.5, 1.5]:
    print(t, (softmax_t(logits, t) * 100).round(1))
# 결과: 0.05 [99.7 0.3 0. 0. 0.] / 0.5 [56.1 31.6 7.9 3.5 0.9] / 1.5 [33.4 27.6 17.4 13.3 8.4]

# top-k = 2: 상위 2개만 남기고 다시 합 1 로 맞춘다
order = np.argsort(p)[::-1]  # 확률이 큰 순서의 자리 번호
keep_k = np.zeros_like(p)
keep_k[order[:2]] = p[order[:2]]
print((keep_k / keep_k.sum()).round(3))  # 결과: [0.571 0.429 0. 0. 0.]

# top-p = 0.8: 누적 확률이 0.8 이상이 되는 자리까지 남긴다
cum = np.cumsum(p[order])  # 결과: [0.4 0.7 0.85 0.95 1.]
n_keep = np.searchsorted(cum, 0.8) + 1  # 0.8 이 처음 넘는 자리까지의 개수. 결과: 3
keep_p = np.zeros_like(p)
keep_p[order[:n_keep]] = p[order[:n_keep]]
print((keep_p / keep_p.sum()).round(3))  # 결과: [0.471 0.353 0.176 0. 0.]

이 코드가 뜻하는 것

점심 메뉴 투표 결과 하나를 놓고 세 가지 버릇을 차례로 적용해 본 것입니다. 첫 줄은 손계산(0.66, 0.24, 0.10)과, 둘째 줄은 강사자료 그림의 막대 숫자(56.1%, 33.4%)와 똑같이 나옵니다. temperature 0.05 에서는 A 가 99.7% 라 사실상 Greedy 와 같아집니다. 실습 3에서 temperature 0.05 로 두 번 뽑은 답이 둘 다 "서울입니다." 로 시작한 까닭입니다.

줄하는 일쉽게 말하면
np.array(logits) / temperature로짓을 T 로 나눈다모험심 다이얼 돌리기
np.log(p)확률을 로짓으로 되돌린다그림과 같은 출발점 만들기
keep_k[order[:2]] = ...상위 2개만 남긴다인기 순위 2위까지만 예선 통과
np.cumsum(p[order])누적 확률득표를 위에서부터 더하기
keep / keep.sum()남은 후보끼리 합 1 로결선 후보끼리 다시 비율 내기

top-k 와 top-p 는 "누가 뽑기에 들어갈지"를, temperature 는 "들어간 후보끼리 얼마나 고르게 나눌지"를 정합니다. 그래서 셋을 함께 쓰는 일이 많습니다.

주의할 점

  • 모델 폴더의 생성 기본값(temperature 0.7, top_k 20, top_p 0.8, repetition_penalty 1.1)이 함께 걸립니다. 실습 3처럼 temperature 만 바꾸면 top_k 20, top_p 0.8 은 그대로 적용됩니다. 한 설정만 견주고 싶으면 프로젝트처럼 top_k=0, top_p=1.0 을 직접 주어 끕니다.
  • temperature 를 너무 높이면 확률이 아주 낮은 엉뚱한 토큰(다른 나라 글자, 끊긴 낱말)까지 뽑힙니다. 프로젝트에서 temperature 1.5 의 결과로 확인합니다.
  • 샘플링 결과는 실행할 때마다 다릅니다. 같은 결과를 다시 보려면 torch.manual_seed(42) 처럼 씨앗을 줍니다. 다만 GPU 종류와 라이브러리 버전이 다르면 씨앗이 같아도 결과가 다를 수 있습니다.
← 이전 절6장 · 생성은 한 토큰씩 이어집니다다음 절 →6장 · SFT: 대화하는 법을 배우는 단계