샘플링 설정
샘플링은 추론의 마지막 한 칸입니다. 모델은 어휘의 모든 토큰에 점수를 매기고, 당신의 설정이 그 분포를 토큰 하나로 만듭니다. 노브들은 상호작용하므로, 연산 순서가 개별 값보다 중요합니다.
파이프라인, 순서대로
대부분의 엔진은 대략 이 순서로 샘플러를 적용합니다:
- Logits — 날것 점수, 어휘 토큰마다 하나 (5만–15만 항목).
- 반복 페널티 — 최근에 본 토큰의 logits가 눌립니다.
- Temperature — logits를 T로 나눠 전체를 날카롭게 하거나 평평하게 합니다.
- top_k — 하드 상한: 상위 k개 토큰만 살아남습니다.
- top_p — 핵(nucleus): 확률 질량의 p를 덮는 가장 작은 상위 집합.
- min_p — 상대 바닥: min_p × P(최상위 토큰) 아래는 버립니다.
- 샘플 — 남은 것에서 토큰 하나를 뽑고, 이어 붙이고, 반복합니다.
이른 순서에 적용된 필터는 이후 모든 필터가 보는 것을 바꿉니다. 쌓을 때 명심하세요.
Temperature
Temperature T는 softmax 앞에서 logits를 나눕니다. T가 낮으면 분포가 최상위 토큰 쪽으로 날카로워지고, 높으면 균등 쪽으로 평평해집니다. 구체적으로: 세 토큰에 걸친 logits [5.0, 3.0, 1.0].
| T | P(1위) | P(2위) | P(3위) |
|---|---|---|---|
| 0.5 | 98.1% | 1.8% | 0.03% |
| 1.0 | 86.7% | 11.7% | 1.6% |
| 1.5 | 75.0% | 19.8% | 5.2% |
T → 0에서 이것은 탐욕으로 붕괴합니다. 항상 argmax, 완전 결정론적.
추출, 분류, 실행마다 재현되길 바라는 모든 것에는 temperature 0을 쓰세요(엔진이 0을 허용하지 않으면 0.000001). 챗은 0.6–0.8. 창작은 0.9–1.2까지 견딥니다.
top_k: 하드 상한
확률이 가장 높은 k개 토큰만 남기고 그 안에서 샘플링합니다. top_k 40은 41위 이하를 무조건 불가능하게 만듭니다.
무딘 도구입니다. "The capital of France is" 뒤의 k=40은 넉넉하고, 후보 둘만 중요한 희귀 토큰 위치에서는 터무니없이 낭비입니다. 흔한 기본값: 40. min_p를 쓰면 끄세요(0 또는 -1, 엔진에 따라).
top_p: 핵(nucleus)
확률로 정렬해 누적 확률이 p에 도달하는 가장 작은 집합을 유지하고, 재정규화하고, 샘플링합니다.
계산해보기: 확률 [0.867, 0.117, 0.016]에 top_p 0.9. 최상위 토큰만으로는 0.867 < 0.9. 둘째를 더하면 0.984 ≥ 0.9. 두 토큰에서 샘플링하고, [0.88, 0.12]로 재정규화합니다.
후보 집합은 모델이 확신할 때 자동으로 줄고 불확실할 때 늘어납니다. 전형적 범위: 0.9–0.95.
min_p: 로컬의 최애
최상위 토큰 확률의 min_p 배 이상인 토큰만 남깁니다. 바닥은 상대적입니다:
- 모델이 99% 확신 → 바닥 = 0.05 × 0.99 ≈ 0.05: 진짜 경쟁자만 생존.
- 최상위 토큰이 30% → 바닥 = 0.05 × 0.30 = 0.015: 다양성이 생존.
이것이 top_k/top_p의 고전적 실패 — 확신적 분포의 과다 가지치기, 평평한 분포의 과소 가지치기 — 를 죽이고, min_p는 고온 temperature와 안전하게 조합됩니다. "창의적이되 일관된" 레시피: temp 1.0–1.5, min_p 0.05–0.1. 흔한 로컬 설정: top_k와 top_p를 끈 min_p 0.05.
repeat_penalty (llama.cpp 계열)
temperature 적용 전에, 최근 컨텍스트에 등장한 토큰의 logits를 페널티로 나눕니다. 1.0 미만의 값은 반복을 부스트합니다. 분별 있는 범위: 1.0–1.1.
부작용은 실재합니다. 코드와 데이터는 의도적으로 토큰을 반복하니까:
- 식별자는 정당하게 반복됩니다:
counter.count안의count. - JSON 키와 닫는 중괄호는 배열의 모든 객체에서 반복됩니다.
- 페널티 1.2 이상은 산문 루프를 고치기 한참 전에 코드와 JSON을 부숩니다 (떨어진 중괄호, "고쳐진" 따옴표).
현대 인스트럭트 모델은 1.05 이상이 필요한 경우가 드뭅니다. 코드와 구조화 출력에는 1.0이 정당한 선택입니다.
frequency와 presence 페널티 (API 스타일)
OpenAI 호환 API는 llama.cpp가 repeat_penalty를 쓰는 자리에 frequency_penalty(반복마다 증가)와 presence_penalty(이미 본 토큰에 대한 균일 페널티)를 노출합니다. 같은 의도, 다른 수학. 모든 로컬 엔진이 둘 다 존중하지는 않습니다 — 겹겹이 쌓기 전에 당신 것의 구현을 확인하세요: 엔진 파라미터.
샘플러를 구석으로 쌓기
temp 0.1 + top_k 10 + top_p 0.5 + min_p 0.2 + repeat_penalty 1.3을 함께 적용하면 모든 후보 토큰을 가릴 수 있습니다. 증상: NaN logprob 경고, 빈 생성, "sampling returned zero candidates" 중단.
망가진 모델이 아닙니다 — 빈 후보 집합입니다. 한 번에 노브 하나씩 스택을 느슨하게 하세요. 전체 분류: 문제 해결.
동작하는 프리셋
| 과제 | temp | top_p | min_p | repeat_penalty |
|---|---|---|---|---|
| 챗 (범용) | 0.7 | 0.90–0.95 | 0.05 | 1.0–1.05 |
| 코드 생성 | 0.1–0.2 | 0.9 | — | 1.0 |
| 추출 / 분류 | 0 (탐욕) | — | — | 1.0 |
| 창작 | 0.9–1.0 | 0.95 | 0.05–0.1 | 1.05–1.1 |
| 번역 | 0.2–0.4 | 0.9 | — | 1.0 |
챗 행을 llama.cpp 플래그로:
llama-server -m model.gguf --temp 0.7 --top-p 0.95 --min-p 0.05 --repeat-penalty 1.05
한 번에 하나만 바꾸세요
프리셋을 법이 아니라 출발점으로 취급하세요. temperature와 top_p를 함께 움직여 출력이 나아지면, 어느 노브가 몫이었는지 아무것도 배우지 못합니다.
- 테스트 세트를 고정하세요: 당신이 중요하게 여기는 것을 실행하는 프롬프트 5–10개.
- 설정별로 출력을 저장하고 나란히 비교하세요.
- 실행당 정확히 노브 하나를 바꾸세요.
- 승자를 이름 붙인 프리셋으로 저장하세요 — 모든 엔진과 UI가 저장합니다.
이 노브들이 엔진 플래그와 API 필드로 어떻게 대응되는지는: 엔진 파라미터.