샘플링과 디코딩
모델은 매 스텝 어휘 전체에 대한 확률분포를 냅니다. 그 분포에서 토큰 하나를 어떻게 고를지가 샘플링입니다.
temperature, top_k, top_p 를 감으로 만지는 경우가 많습니다. 온도를 올렸는데 다양성이 안 늘거나, 값을 바꿨는데 출력이 그대로인 경험이 여기서 나옵니다.
셋이 각자 무엇을 하는지, 그리고 적용 순서가 왜 결과를 바꾸는지를 봅니다.
1장. 디코딩이 푸는 문제
어휘 전체의 확률분포. 모델의 출력은 토큰 하나가 아니라 어휘 전체에 대한 점수 벡터입니다.
어휘가 15 만이면 15 만 개의 logit 이 나온다
softmax 를 거쳐 확률분포가 된다분포에서 토큰을 뽑는 방식은 크게 셋입니다.
| 방식 | 이름 |
|---|---|
| 가장 높은 걸 고른다 | greedy |
| 확률대로 뽑는다 | sampling |
| 후보를 여러 개 유지한다 | beam search |
greedy가 항상 답은 아닌 이유. greedy는 결정적이라 재현이 쉽고, 짧은 분류형 출력이나 평가처럼 변동을 줄이고 싶은 작업에는 유용합니다. 다만 Holtzman et al.의 The Curious Case of Neural Text Degeneration(2019)은 긴 자유 생성에서 최대우도 중심 디코딩이 반복적이거나 부자연스러운 텍스트로 퇴화할 수 있음을 분석했고, 확률 질량의 핵심 부분만 남기는 nucleus sampling(top-p)을 제안했습니다.
즉 greedy가 나쁜 것이 아니라 자유 생성에서 항상 최고 확률 토큰만 고르는 정책이 언어의 다양성을 지나치게 줄일 수 있다는 문제입니다.
2장. temperature
분포 모양 조절. logit을 temperature로 나눈 뒤 softmax를 겁니다.
| temperature | 분포 변화 |
|---|---|
| T < 1 | 분포가 뾰족해진다. 높은 확률이 더 높아진다 |
| T = 1 | 원래 분포 그대로 |
| T > 1 | 분포가 평평해진다. 낮은 확률도 기회를 얻는다 |
양 극단에서의 동작. T를 0으로 보내면 greedy와 같아져 최고 확률 토큰만 뽑히고, 반대로 크게 올리면 거의 균등분포가 되어 아무 토큰이나 나옵니다.
llama.cpp의 정의에서 0 이하는 greedy로 처리합니다.
temp = 0.80f <= 0.0 to sample greedily값 자체보다 모델 제작자가 권장하는 generation config가 우선입니다. 아래는 성격을 잡기 위한 출발점이지 보편적인 정답이 아닙니다.
| 값 | 대체로 나타나는 성격 |
|---|---|
| 0 또는 매우 낮음 | 변동을 줄이는 방향. 평가·정형 작업에서 자주 사용 |
| 0.1 ~ 0.3 | 분포를 강하게 좁히는 보수적 설정 |
| 0.7 ~ 0.8 | 비교적 넓은 샘플링. llama.cpp의 현재 기본 temperature는 0.8 |
| 1.0 이상 | 낮은 확률 후보의 기회를 크게 늘림. 모델에 따라 품질 저하 가능 |
흔한 오해. temperature를 올리면 창의적이 된다는 말이 돌지만, 정확히는 덜 그럴듯한 토큰이 뽑힐 확률이 오르는 것입니다. 창의성이 아니라 무작위성이고, 너무 올리면 문법이 무너집니다.
동적 temperature. 분포의 엔트로피에 따라 temperature를 자동으로 조절하는 옵션입니다. 모델이 확신할 때는 낮추고 애매할 때는 올립니다.
dynatemp_range 0.00 기본 비활성
dynatemp_exponent 1.003장. top-k
상위 k개만 남기는 절단. 확률 상위 k개만 남기고 나머지를 버린 뒤, 그 안에서 확률대로 뽑습니다.
llama.cpp 기본값
top_k = 40 <= 0 to use vocab size단순한 구현과 확실한 꼬리 배제. 구현이 단순하고 빠르며, 꼬리의 이상한 토큰을 확실히 배제합니다.
고정된 후보 수의 한계. 분포 모양을 무시한다는 것이 약점입니다.
확신이 강한 상황
1 위가 0.95 인데도 40 개를 남긴다
불필요하게 넓다
애매한 상황
상위 100 개가 고르게 퍼져 있는데 40 개로 자른다
너무 좁다항상 k개라는 점이 문제입니다.
4장. top-p (nucleus sampling)
누적 확률 p까지의 절단. 확률을 높은 순으로 더해 가며 누적이 p를 넘는 지점까지만 남깁니다.
llama.cpp 기본값
top_p = 0.95 1.0 = disabledtop-k와의 차이. top-k는 개수로 잘라 후보 수가 항상 같고, top-p는 확률 질량으로 잘라 후보 수가 상황마다 달라집니다.
확신이 강하면 1 위가 0.96 이면 후보가 1 개
애매하면 고르게 퍼져 있으면 후보가 수백 개분포 모양에 적응한다는 것이 top-p의 이점입니다.
용도별로 쓰는 값입니다.
| 값 | 성격 |
|---|---|
| 0.9 ~ 0.95 | 일반적 |
| 낮추면 | 보수적. 안전하지만 단조로움 |
| 1.0 | 비활성 |
평평한 꼬리에서의 과잉 후보. 누적 확률이 기준이라 꼬리가 길고 평평하면 여전히 많이 남습니다. 1위가 0.5여도 나머지 0.45를 채우려고 낮은 확률 토큰이 대거 들어옵니다.
5장. min-p
1위 확률 대비 상대 기준. 1위 확률 대비 몇 퍼센트 이상인 토큰만 남깁니다.
llama.cpp 기본값
min_p = 0.05 0.0 = disabledtop-p와의 차이. top-p는 누적 확률이라는 절대 기준을 쓰고, min-p는 최고 확률 대비 상대 기준을 씁니다.
1 위가 0.9 이면 0.045 이상만 남는다. 매우 좁다
1 위가 0.1 이면 0.005 이상. 넓게 열린다모델의 확신 정도에 자동으로 맞춰집니다.
llama.cpp의 기본값 선택. 셋이 동시에 켜져 있고 체인으로 차례로 걸러집니다.
top_k = 40
top_p = 0.95
min_p = 0.05min_p 가 기본 활성이라는 점이 최근 흐름을 보여줍니다. top-p의 꼬리 문제를 보완하는 자리입니다.
6장. 샘플러 체인의 순서
기본 체인. llama.cpp 소스의 기본값입니다.
penalties ──► dry ──► top_n_sigma ──► top_k
──► typical_p ──► top_p ──► min_p
──► xtc ──► temperature체인 마지막에 놓인 temperature. 후보를 다 걸러낸 뒤에 온도를 겁니다. 그래서 temperature는 남은 후보들 사이의 상대 확률만 조절합니다.
top_k 로 40 개를 남긴 뒤 T=1.5 를 걸면
그 40 개 안에서 평평해질 뿐
버려진 토큰이 되살아나지는 않는다이것을 모르면 temperature를 올렸는데 왜 다양성이 안 느는지에서 막힙니다. 상한이 top-k와 top-p로 이미 정해져 있습니다.
--samplers로 순서 변경. --samplers 옵션으로 체인을 직접 지정할 수 있습니다. temperature를 앞으로 옮기면 온도로 평평해진 분포에서 top-p를 자르게 되므로 결과가 달라집니다.
후보 하한, min_keep. 필터가 너무 공격적이어서 후보가 0개가 되는 것을 막는 하한입니다.
min_keep = 0 0 = disabled7장. 반복 억제 네 가지
반복 억제가 필요한 자리. 샘플링만으로는 같은 구절을 되풀이하는 것을 막지 못합니다.
repeat penalty. 최근 등장한 토큰의 logit을 억제합니다. 구현에서는 logit의 부호를 고려해 곱하거나 나누는 식으로 처리할 수 있으므로 단순히 '확률을 나눈다'고 이해하기보다, 최근 토큰을 다시 선택하기 어렵게 만드는 페널티라고 보는 편이 정확합니다. 등장 횟수 자체를 선형으로 누적하는 frequency penalty와는 다릅니다.
penalty_repeat = 1.00 1.0 = disabled
penalty_last_n = 64 최근 몇 토큰을 볼지frequency penalty. 등장 횟수에 비례해 깎습니다. 많이 나온 토큰일수록 더 강하게 억제됩니다.
penalty_freq = 0.00 0.0 = disabledpresence penalty. 한 번이라도 나왔으면 횟수와 무관하게 고정 크기로 깎습니다.
penalty_present = 0.00 0.0 = disabled세 페널티를 나란히 놓으면 이렇습니다.
| 페널티 | 적용 방식 |
|---|---|
| repeat | 나눗셈으로. 횟수 무관 |
| frequency | 횟수에 비례. 많이 나올수록 강하게 |
| presence | 등장 여부만. 새 주제를 유도 |
같은 단어의 반복을 막고 싶으면 frequency를, 새로운 주제로 넘어가게 하고 싶으면 presence를 씁니다.
DRY. 토큰 단위가 아니라 반복 시퀀스를 봅니다.
dry_multiplier 0.0 0.0 = disabled
dry_base 1.75
dry_allowed_length 2 이보다 긴 반복에 페널티
dry_penalty_last_n 64소스 주석에 적힌 페널티 식은 다음과 같습니다.
multiplier * base ^ (length - allowed_length)반복 길이가 길수록 페널티가 지수적으로 커집니다. 단어 하나가 아니라 같은 문구를 통째로 반복하는 것을 막습니다.
부작용. 반복 페널티는 정당한 반복까지 막습니다. 코드에서 변수명을 여러 번 쓰거나 목록에서 같은 형식을 반복하는 경우가 그렇습니다. 그래서 코드 생성에서는 대개 낮추거나 끕니다.
8장. 그 외 샘플러
typical-p. Locally Typical Sampling(2022)에서 나왔습니다. 확률이 높은 것도 낮은 것도 아닌, 정보량이 전형적인 토큰을 남깁니다. 사람의 언어가 정보량 면에서 일정한 수준을 유지한다는 관찰에 기댑니다.
typ_p = 1.00 1.0 = disabledtop-n-sigma. logit 분포의 표준편차를 기준으로 잘라, 최고값에서 n 시그마 안쪽만 남깁니다.
top_n_sigma = -1.00 -1.0 = disabledXTC. Exclude Top Choices의 약자로, 확률적으로 상위 후보를 오히려 제외합니다. 창작에서 뻔한 전개를 피하려는 목적입니다.
xtc_probability = 0.00 0.0 = disabled
xtc_threshold = 0.10 > 0.5 disables XTCMirostat. 목표 surprisal/entropy 수준을 정해 두고 생성 중 그 수준에 맞도록 샘플링을 적응적으로 조절합니다. llama.cpp에서는 Mirostat 경로가 일반적인 top-k/top-p 체인과 다르게 구성되지만, 다른 구현까지 '켜면 top-k와 top-p가 항상 무시된다'고 일반화하면 안 됩니다.
mirostat = 0 0 = disabled, 1 = Mirostat, 2 = Mirostat 2.0
mirostat_tau = 5.00 target entropy
mirostat_eta = 0.10 learning rate9장. 실무 설정
용도별 출발점입니다.
코드, 구조화 출력, 평가
temperature 0
반복 페널티 끔
사실 질의응답
temperature 0.1 ~ 0.3
top_p 0.9
일반 대화
temperature 0.7 ~ 0.8
top_p 0.95, min_p 0.05
창작
temperature 1.0 이상
min_p 로 하한만 잡음모델 카드의 권장값. 모델마다 권장 설정이 다릅니다. Ling-3.0-tiny는 다음을 권합니다.
temperature 1.0
top_p 0.95
top_k 20temperature 1.0을 권하는 모델도 있는 셈입니다. 학습 방식에 따라 최적점이 달라집니다.
조정 방법. 파라미터가 열 개가 넘어서 여러 개를 동시에 바꾸면 무엇이 효과인지 알 수 없습니다. 기본값에서 하나씩 움직이고, 같은 프롬프트 세트로 여러 설정을 돌려 지표로 비교합니다.
구조화 출력에는 Constrained Decoding. temperature를 0으로 내려도 JSON 문법이나 스키마가 보장되지는 않습니다. 형식이 중요하면 grammar/constrained decoding, 제공자의 native structured output, tool schema처럼 허용 가능한 토큰이나 출력 구조 자체를 제한하는 수단을 우선합니다. 라이브러리의 with_structured_output 같은 상위 API도 내부적으로 어떤 방식으로 보장하는지 모델·제공자별로 확인해야 합니다.
10장. 정리
top-k는 개수로, top-p는 누적 확률로, min-p는 최고 확률 대비 상대 기준으로 후보를 줄입니다. temperature는 분포의 상대적인 모양을 바꿉니다. 현재 llama.cpp 기본 체인에서는 temperature가 뒤쪽에 있기 때문에 이미 앞 단계에서 제거된 후보는 되살아나지 않습니다. 다만 이 순서는 llama.cpp의 구현 선택이며 모든 API가 같은 순서를 쓰는 것은 아닙니다.
기본값을 한 표로 모으면 이렇습니다.
| 파라미터 | llama.cpp 기본값 | 비활성 값 |
|---|---|---|
temp |
0.80 | 0 이하면 greedy |
top_k |
40 | 0 이하면 어휘 전체 |
top_p |
0.95 | 1.0 |
min_p |
0.05 | 0.0 |
typ_p |
1.00 | 1.0 |
penalty_repeat |
1.00 | 1.0 |
penalty_freq |
0.00 | 0.0 |
penalty_present |
0.00 | 0.0 |
dry_multiplier |
0.0 | 0.0 |
mirostat |
0 | 0 |
temperature를 올려도 앞선 필터가 이미 제거한 토큰은 다시 후보가 되지 않습니다. top-p와 min-p는 각각 누적 확률 질량과 최고 확률 대비 비율이라는 다른 기준을 사용합니다. 반복 페널티는 코드나 JSON처럼 정상적인 반복이 필요한 작업에서는 오히려 품질을 떨어뜨릴 수 있으므로 작업별로 따로 검증합니다.
형식을 강제하는 축은 Constrained Decoding 쪽이고, 샘플링을 유지한 채 속도를 올리는 축은 Speculative Decoding 쪽입니다.
용어 정리
| 용어 | 한 줄 뜻 |
|---|---|
| logit | softmax 전 단계의 원시 점수 |
| greedy 디코딩 | 매 스텝 최고 확률 토큰만 고르는 방식 |
| 샘플링 | 확률분포에서 무작위로 토큰을 뽑는 방식 |
temperature |
logit을 나눠 분포를 평평하게 하거나 뾰족하게 하는 값 |
top_k |
확률 상위 k개만 후보로 남기는 필터 |
top_p (nucleus) |
누적 확률이 p가 될 때까지만 남기는 필터 |
min_p |
최고 확률 대비 일정 비율 이상만 남기는 필터 |
typical_p |
정보량이 전형적인 토큰만 남기는 필터 |
| 샘플러 체인 | 필터들이 적용되는 순서 |
min_keep |
필터 후 최소한 남길 후보 수 |
repeat penalty |
최근 등장 토큰의 확률을 나눗셈으로 깎는 억제 |
frequency penalty |
등장 횟수에 비례해 깎는 억제 |
presence penalty |
한 번이라도 나왔으면 고정 크기로 깎는 억제 |
| DRY | 반복되는 시퀀스 길이에 지수적으로 페널티를 주는 억제 |
| XTC | 확률적으로 상위 후보를 제외하는 샘플러 |
| Mirostat | 목표 엔트로피를 유지하도록 자동 조절하는 샘플링 |
| 동적 temperature | 분포 엔트로피에 따라 온도를 자동 조절하는 방식 |
참고자료
- The Curious Case of Neural Text Degeneration (nucleus sampling, arXiv 1904.09751). Figure 2 가 빔서치와 사람 텍스트의 확률 분포 대비, Figure 3 이 방식별 생성 예시, Figure 5 가 확률 질량 분포다. ar5iv
- Locally Typical Sampling (arXiv 2202.00666)
- llama.cpp 소스,
common/common.h(common_params_sampling기본값) - llama.cpp 소스,
common/sampling.cpp(샘플러 체인 구현) - llama.cpp 소스,
common/arg.cpp(샘플링 CLI 옵션) - inclusionAI/Ling-3.0-tiny 모델 카드 (권장 샘플링 설정)
'Inference' 카테고리의 다른 글
| Continuous Batching과 PagedAttention (0) | 2024.01.03 |
|---|
댓글