Temperature와 Top-p, 그리고 당신에게 없는 결정성
Temperature는 softmax 전에 logit을 나눕니다. 이 사실 하나가 ‘창의성 다이얼’이라는 오해를 무너뜨립니다.
이 페이지에서
같은 요청을 같은 모델에 다섯 번 보냈습니다. 같은 weights, 같은 prompt, 같은 머신, 같은 random seed입니다. 바뀐 것은 숫자 하나뿐입니다.
prompt: "Q: What is the capital of France?\nA:"
T = 0.0 " Paris\nWhat is the question and does the answer answer it? The
question is: What is the capital of France?..."
T = 0.7 " Paris\nWhat is the question: Which city is the capital of
France?..."
T = 1.0 " Paris\nWhat is a good geographical qualifier for describing
Paris concerning its location?\nA: Near the Mediterranean Sea..."
T = 1.5 " Paris\nWhat clue from premise allows we to conclude that Godwin
was &, He chose Healing Crimson Colour No:white flour Pure..."
T = 2.0 "安全感金华.ITEMT]]];\naims assume parental.st-importe.valtermination
Screens قطر_Zeroหมายเลข-zA ('$ספטמבר..."아무것도 고장 난 것이 아닙니다. 마지막 줄의 모든 token은 모델이 가진 151,936개 항목 vocabulary에 대한 자체 확률 분포에서 정당하게 뽑힌 것입니다. 바뀐 숫자는 temperature라고 부릅니다. 대부분의 문서에서는 이것을 창의성 다이얼이라고 설명하지만, 그 설명은 틀렸습니다. 이 장은 그 사실을 주장하는 대신 직접 보여줄 수 있습니다.
이 장은 앞선 세 가지 약속을 갚는 장이기도 합니다. 4장은 logit을 정의했지만 제대로 써먹지는 않았습니다. 2장의 floating-point 박스는 한 가지 지시로 끝났습니다 — 17장에서 같은 prompt, 모델, seed가 왜 다른 token을 만들 수 있는지 물을 때 이것을 기억하라. 그리고 9장의 mixture-of-experts 박스는 non-determinism의 네 가지 원인 목록을 약속했습니다. 셋 모두 아래에서 등장합니다.
이 장 전체가 매달려 있는 한 줄
섹션 링크: 이 장 전체가 매달려 있는 한 줄4장은 logit을 class마다 하나씩 있는 정규화되지 않은 실수 점수로 소개했습니다. 8장은 language model이 vocabulary 항목마다 하나씩 만들게 했습니다. softmax는 그 vector 를 확률로 바꿉니다.
Temperature는 여기 들어갑니다 — 이름은 통계물리학에서 빌려온 것으로, 같은 parameter가 Boltzmann distribution이 low-energy state에 얼마나 날카롭게 집중하는지를 제어합니다1 — 그리고 지수 함수에 넣기 전에 logits를 나눕니다.
이 위치가 전체 메커니즘입니다. 왜 다른 곳일 수 없는지 보려면 두 줄의 대수만으로 충분합니다. temperature를 확률에 적용하려고 했다고 해봅시다 — 확률에 를 곱하고 다시 정규화하는 것입니다. 그러면 다음을 얻습니다.
상수는 약분됩니다. 확률을 스케일링해도 아무 일도 일어나지 않습니다. 분포는 그대로 돌아옵니다. Temperature는 exponent에 작용하기 때문에만 효과가 있습니다. 지수화를 하기 전에 로 나누는 것은 각 확률을 제곱하는 것과 같으며, 이는 공통 scale이 아니라 항목 간의 비율을 바꾸는 비선형 재형성입니다.
그 위치에서 두 극한은 추가 작업 없이 따라옵니다. 일 때 가장 큰 logit은 나머지에서 멀어지고 는 단일 최고 점수 token으로 붕괴합니다. 이것이 greedy decoding입니다. 이 커질수록 모든 는 0으로 향하고, 모든 exponential은 1로 향하며, 분포는 전체 vocabulary에 대한 균등분포로 평평해집니다. 정확히 에서는 공식이 0으로 나누므로 모든 구현은 이를 산술적 최댓값으로 special-case 처리합니다. 아래 widget도 마찬가지로 에서 argmax로 전환합니다.
한 가지 경고가 필요합니다. 이름 충돌이 실제 혼란을 일으키기 때문입니다. Machine learning에는 temperature라고 부르는, 전혀 별개의 것이 하나 더 있습니다. temperature scaling입니다. 이는 classifier의 confidence가 accuracy와 맞도록 validation set에서 한 값을 맞추는 calibration 방법입니다.2 같은 공식이지만 generation과는 관계가 없습니다. 논문에서 “temperature”라고 할 때는 종종 이쪽을 뜻합니다. 이 장에서는 절대 그렇지 않습니다.
아래가 그 분포입니다. 계산이 눈앞에 있습니다. logits는 고정되어 있고 그럴듯하므로, 아래 설명의 숫자는 보이는 내용과 대조해 확인할 수 있습니다.
Temperature는 창의성 다이얼이 아닙니다
섹션 링크: Temperature는 창의성 다이얼이 아닙니다␣banana 숫자가 논증 전체의 축소판입니다. temperature를 올린다고 모델이 갖고 있지 않던 아이디어가 생기지는 않습니다. logits는 이미 계산되었고, 순위는 이미 고정되었으며, temperature는 그 순위를 정확히 보존합니다. 아무리 뜨겁게 해도 낮은 점수 token이 높은 점수 token 위로 올라가지는 않습니다. temperature가 하는 일은 모델 스스로 만든 ranking 아래쪽으로 mass를 재분배하는 것뿐입니다. 높은 temperature는 모델을 더 창의적으로 만들지 않습니다. 모델이 나쁘다고 점수 매긴 token을 내보낼 가능성을 키웁니다.
실제 vocabulary에서는 이것이 단순한 호기심을 넘어, high-temperature output이 쓸 수 없어지는 이유가 됩니다. Qwen/Qwen2.5-0.5B-Instruct에서 한 번의 forward pass로, 위 prompt에 대해, 확률 mass의 일정 비율을 누적하는 데 token이 몇 개 필요한지 세면 다음과 같습니다.
| temperature | top-1 probability | entropy | tokens holding 80 % | 90 % | 95 % | 99 % |
|---|---|---|---|---|---|---|
| 0.5 | 99.98 % | 0.00 nats | 1 | 1 | 1 | 1 |
| 0.7 | 99.65 % | 0.03 nats | 1 | 1 | 1 | 1 |
| 1.0 | 96.01 % | 0.30 nats | 1 | 1 | 1 | 14 |
| 1.2 | 88.20 % | 0.88 nats | 1 | 2 | 13 | 252 |
| 1.5 | 62.83 % | 3.07 nats | 29 | 353 | 2,672 | 26,787 |
| 2.0 | 16.62 % | 8.19 nats | 13,516 | 32,966 | 55,231 | 101,205 |
맨 아래 행을 천천히 읽어보세요. 에서, 정답이 정확히 하나인 질문에서 32,966개의 서로 다른 token이 확률 mass 상위 90 %를 나눠 갖습니다. 이것은 더 넓은 창의적 공간이 아닙니다. 산술적으로, A: 다음 단어로 한국어 조사와 C++ identifier를 살아 있는 선택지로 취급하라는 지시를 받은 모델입니다. 첫 블록의 쓰레기 output은 직접적인 결과이며, 모델이나 library의 bug가 아닙니다. 요청이 그렇게 하라고 한 것입니다.
유용한 범위는 좁고, 취향보다 task에 의존합니다. 사실 질문에서는 답이 하나의 token이고, 대략 1.2보다 높은 열은 아무 이득 없이 error를 주입합니다. 열린 질문에서는 실제로 좋은 continuation이 하나보다 많고, 어느 정도의 열은 유창함을 유지하는 다양성을 사옵니다.
"Write a two-sentence story about a lighthouse."
T = 0.0 "The lighthouse stood tall and proud, its beacon illuminating the
night sky above. A lone sailor, his eyes fixed on the distant
horizon..."
T = 0.7 "In the quiet, stormy waters of the sea, a lighthouse stood
sentinel over the horizon, its golden dome casting a warm glow
on the fog-shrouded streets below..."
T = 1.0 "In the quiet night, a lone lighthouse stood sentinel over the
sea, its shining beacon a beacon of hope and solace for sailors
and fishermen across the vast and endless ocean..."
T = 1.3 "In the gentle sunlight, now reflecting upon the opening of Jack's
lighthouse, Jim Trahan, a small-time individual difficult to
define in paperwork, wondered about a career where simplicity
reigns..."1.3에서 모델은 고유명사를 지어냈고, parse되지 않는 문장을 만들었습니다. “매번 동일함”과 “일관성 없음” 사이의 band는 이 모델과 이 task에서는 대략 0.6에서 1.1입니다. 정직한 조언은 blog 글의 숫자를 베끼지 말고 당신의 task에서 측정해 찾으라는 것입니다.
가장 그럴듯한 text가 나쁜 text인 이유
섹션 링크: 가장 그럴듯한 text가 나쁜 text인 이유이 모든 것 아래에는 명백한 질문이 숨어 있습니다. 모델에 확률 분포가 있고 token 하나가 가장 그럴듯하다면, 왜 항상 그것을 고르지 않을까요? Greedy decoding은 공짜이고, 재현 가능하며, parameter가 필요 없습니다.
결과가 이렇게 나오기 때문입니다.
prompt: "In a shocking finding, scientists discovered a herd of unicorns
living in a remote valley."
greedy: " The unicorns were so rare that they were not even recognized by
the local people. The unicorns were so rare that they were not
even recognized by the local people. The unicorns were so rare
that they were not even recognized by the local people. ..."
repeated 4-grams: 87.6 %여덟 문장, 하나의 문장. 네 token짜리 window의 거의 아홉 개 중 여덟 개가 같은 output 안에서 이미 앞서 등장했습니다. 이것이 neural text degeneration이며, top-p를 소개한 논문에서 Holtzman 등이 이름 붙이고 설명했습니다.3 모델이 망가진 것이 아닙니다. sequence probability를 최대화하는 것이 open-ended text에 대해서는 단순히 잘못된 objective입니다. 인간의 글쓰기는 가장 그럴듯한 단어 sequence가 아닙니다. 놀라움을 담고 있고, per-token probability가 흔들리며, 내려갔다가 회복합니다. 반면 maximum-probability path는 일단 들어가면 떠날 이유가 없는 fixed point입니다.
그래서 sampling이 존재합니다. 동시에, 자주 빠지는 부분이지만, 이것은 보편 법칙이 아닙니다. 12장은 두 단계 단어 문제에서 plain greedy decoding으로 24개 중 24개 정답을 측정했고, temperature 0.8에서 sampling하자 81 %로 떨어졌습니다. self-consistency는 이후 greedy가 이미 있던 곳으로 돌아오려고 여섯 배의 token을 썼습니다. 두 사실은 동시에 참입니다.
Open-ended generation. 단일한 올바른 continuation이 없으므로 가장 그럴듯한 것은 함정입니다 — loop가 되고, 그중 87.6 %는 자기 자신에서 복사됩니다. Sample하세요.
정답이 하나인 task. 단일한 올바른 continuation이 있습니다. 그러므로 다른 것을 뽑는 것은 error를 뽑는 것입니다. 12장의 100 %가 81 %가 된 이유가 정확히 이것입니다. Sample하지 마세요.
대부분의 production prompt는 두 번째 종류인데 첫 번째처럼 설정됩니다. example code가 쓰던 temperature가 그대로 남아 있기 때문입니다.
자르는 두 가지 방법, 그리고 그중 하나만 적응합니다
섹션 링크: 자르는 두 가지 방법, 그리고 그중 하나만 적응합니다full distribution에서 sampling하는 것은 실제로 아무도 하지 않습니다. tail이 거대하고 nonsense로 가득하기 때문입니다. 무언가는 잘라야 합니다. 고전적인 답은 두 가지이고, 모든 것을 결정하는 한 가지 면에서 다릅니다.
Top-k는 고정된 수의 candidate를 유지합니다. probability로 정렬하고, 앞의 개를 유지하고, 나머지를 버리고, 다시 정규화합니다.4 Top-p는 nucleus sampling이라고도 하며, 고정된 mass를 유지합니다. token을 내림차순으로 가져오다가 cumulative probability가 에 도달하면 멈춥니다.3 형식적으로 nucleus는 다음을 만족하는 가장 작은 집합 입니다.
차이는 겉보기엔 사소하지만 그렇지 않습니다. 같은 분에 보내는 두 prompt가 완전히 다른 distribution shape를 갖기 때문입니다. 둘 다 temperature 1에서 같은 모델입니다.
Q: What is the capital of France?\nA: | Once upon a time, | |
|---|---|---|
| top-1 probability | 96.01 % | 25.39 % |
| mass 90 %를 담는 token 수 | 1 | 467 |
| top-k = 40이 유지하는 것 | mass의 99.61 % | mass의 78.87 % |
| rank 2부터 40까지의 mass | 3.61 % | 53.48 % |
| rank 40의 token | ␣Av, 0.0093 % | ␣Dr, 0.128 % |
고정된 하나, 반대 방향의 실패 두 개입니다. 사실 prompt에서는 가 합쳐서 3.6 % 가치밖에 없는 39개 token을 받아들입니다. 규칙이 evidence가 아니라 slot을 세기 때문에, 0.009 %짜리 candidate를 포함해 rubbish를 통과시키는 것입니다. story prompt에서는 같은 가 모델이 진짜로 할당한 mass의 21 %를 버립니다. 거기서 실제 nucleus는 467 token 너비이기 때문입니다.
Top-p는 정확히 하나의 숫자로 두 일을 모두 합니다. 로 설정하면 첫 prompt에서는 1 token, 두 번째에서는 467 token을 유지합니다. 분포에 count를 강요하는 대신 분포에 대한 질문을 던지기 때문입니다. 그 적응을 직접 보세요 — 같은 cut, 네 가지 temperature입니다.
이 widget은 이름 붙일 가치가 있는 오해 하나도 정리합니다. 실제 돈이 드는 오해입니다. 확신이 강한 분포에서 top_p = 0.9는 “약간의 다양성”이 아닙니다. Greedy입니다. 여기서 temperature 1의 leading token은 이미 96.90 %를 갖고 있고, 이는 0.9를 넘습니다. 따라서 nucleus는 token 하나 너비이고, 다른 것은 절대 뽑힐 수 없습니다. 팀들은 무언가를 느슨하게 했다고 믿고 top_p를 0.9로 설정한 다음, 왜 모든 response가 동일한지 궁금해합니다.
대신 top-k를 설정하면 반대쪽 실패가 똑같이 보입니다.
Penalty들, 공식까지, 혼동이 만연하므로
섹션 링크: Penalty들, 공식까지, 혼동이 만연하므로비슷한 이름 아래 세 가지 다른 메커니즘이 돌아다닙니다. 서로 다른 일을 하며, 그 차이는 측정 가능합니다. 를 token 가 이미 등장한 횟수라고 합시다.
Presence penalty
섹션 링크: Presence penalty한 번이라도 등장한 token에서 상수를 뺍니다. 한 번 등장한 것과 마흔 번 등장한 것은 똑같이 penalise됩니다. 다이얼이 아니라 switch입니다.
Frequency penalty
섹션 링크: Frequency penaltycount에 비례해서 뺍니다. 네 번 사용된 token은 한 번 사용된 token보다 네 배 강하게 penalise되고, text가 길어질수록 압력이 누적됩니다.
Repetition penalty (CTRL)
섹션 링크: Repetition penalty (CTRL)CTRL 논문의 원래 방식입니다.7 빼는 대신 나눕니다. negative logit을 나누면 더 커지기 때문에 sign case가 필요합니다. 따라서 그 강도는 logit의 magnitude에 의존하고, 같은 도 같은 문장 안의 다른 지점에서 다르게 작용합니다.
앞서 본 동일한 degenerate continuation에 각각을 적용했습니다. “Steps altered”는 120개 generation step 중 unpenalised model이 골랐을 token과 다른 token을 고른 횟수입니다. 여기 run은 위 블록의 140이 아니라 120 step이므로, unpenalised baseline이 87.6 %가 아니라 85.5 %로 읽힙니다.
| setting | repeated 4-grams | steps altered |
|---|---|---|
| nothing | 85.5 % | 0 / 120 |
| presence 0.5 | 65.0 % | 3 / 120 |
| presence 1.0 | 3.4 % | 11 / 120 |
| frequency 0.5 | 6.0 % | 12 / 120 |
| frequency 1.0 | 0.0 % | 20 / 120 |
| repetition 1.2 (CTRL) | 0.0 % | 35 / 120 |
세 가지가 드러납니다. Presence 0.5는 120개 결정 중 세 개를 바꾸고 repetition을 4분의 1 줄였습니다 — loop는 몇 안 되는 token으로 묶여 있었습니다. Frequency 0.5는 훨씬 큰 효과를 위해 네 배 많은 결정을 바꿨습니다. count multiplier는 계속 커지지만 presence constant는 그렇지 않기 때문입니다. 그리고 널리 복사되는 값인 1.2의 CTRL penalty는 120개 결정 중 35개를 다시 썼습니다. 이것은 살짝 미는 것이 아닙니다. 다른 모델입니다.
마지막 숫자가 아무도 경고하지 않는 실패의 setup입니다.
반복해야 하는 text에 penalty가 하는 일
섹션 링크: 반복해야 하는 text에 penalty가 하는 일Code는 반복됩니다. Table은 반복됩니다. List는 반복됩니다. Structured output은 정의상 반복됩니다 — 그것이 structure입니다. penalty는 loop에 갇힌 모델과 table의 네 번째 row를 올바르게 내보내는 모델을 구분할 수 없습니다. 둘 다 token이 다시 나타나는 것처럼 보이기 때문입니다.
같은 세 task를 세 가지 방식으로 생성했습니다.
| task | nothing | frequency 0.5 | repetition 1.2 |
|---|---|---|---|
| markdown table, 6 rows | 0 / 56 steps altered | 0 / 56 | 2 / 62 |
| Python function | 0 / 93 | 0 / 93 | 10 / 110 |
| bulleted list, 1 to 12 | 0 / 50 | 0 / 50 | 0 / 50 |
frequency penalty 0.5는 세 task 모두에서 무해한 것으로 나왔습니다. 유용하면서도 약간 놀라운 결과이고, 정확한 의미가 있습니다. decision이 바뀌지 않았으므로, structural token들은 다섯 번, 여섯 번 등장한 뒤에도 penalty가 뺀 것보다 더 큰 차이로 자기 자리를 이기고 있었던 것입니다. 반면 나누는 방식인 CTRL penalty는 그것들을 밀어냅니다. 결과는 이렇습니다.
repetition 1.2, markdown table:
| n | 2^n |
| --- | --- |
| 0 | 1 |
| 1 | 2 |
| 2 | 4 |alignment가 무너집니다. 각 cell 내부의 padding 양이 row마다 달라집니다. closing pipe 앞의 space run이 바로 penalty가 깨뜨리기 위해 존재하는 종류의 repetition이기 때문입니다. 겉보기 문제이고, 여섯 token이 더 들었습니다. Python case는 겉보기 문제가 아닙니다.
nothing / frequency 0.5:
total = 0
for i in range(1, n + 1):
total += i ** 2
return total
repetition 1.2:
# Initialize total_sum with 0
total_sum = 0
# Loop through numbers from 1 to n, incrementing by 2 each time
for i in range(1, n + 1,penalty는 모델을 total에서 — 이미 docstring에 사용된 — total_sum로 밀어냈고, 사용하지 않은 token에 budget을 쓰려고 invented comment로 output을 채운 뒤, stride가 있는 three-argument range로 걸어 들어갔습니다. comment는 incrementing by 2 each time이라고 말하는데, 1부터 까지 제곱합에는 틀린 말입니다. repetition penalty가, 그것 없이 올바르게 답한 prompt에서 incorrect code를 만들어냈습니다.
따라오는 규칙은 짧습니다. penalty는 open-ended prose를 위한 것이고, code, structured output, tabular data, schema가 있는 모든 것에서는 꺼져 있어야 합니다. 18장은 바로 그 두 번째 범주에 관한 장입니다.
적용 순서, 그리고 그것이 답을 바꾸는 이유
섹션 링크: 적용 순서, 그리고 그것이 답을 바꾸는 이유모든 실제 구현은 이것들을 특정한 순서로 적용합니다.
penalties → temperature → top-k → top-p → sample
이는 임의의 장부정리가 아니며, 두 단계를 바꾸면 실제로 다른 분포가 생깁니다. 두 가지 측정, 둘 다 factual prompt에서입니다.
temperature 전후로 자르기. nucleus는 전달받은 분포에서 계산되고, temperature는 그 분포를 급격히 바꿉니다.
| top-p 0.9 after temperature | top-p 0.9 before temperature | |
|---|---|---|
| 1 token | 1 token | |
| 353 tokens | 1 token | |
| 32,966 tokens | 1 token |
에서 같은 명목상 setting이 어느 stage가 먼저 실행되는지에 따라 candidate set 32,966개 또는 1개를 만듭니다. 같은 두 숫자로 한 provider에서는 temperature를 올려도 “아무 일도 안” 일어나고 다른 provider에서는 output이 망가지는 이유가 궁금했다면, 이 table이 그럴듯한 답입니다.
temperature 전후로 penalise하기. penalty 를 뺀 뒤 로 나누면 effective penalty는 입니다. 먼저 나누고 나중에 빼면 입니다. leading token에 presence penalty 1.0을 적용하면 다음과 같습니다.
| temperature | penalise, then temper | temper, then penalise |
|---|---|---|
| 0.5 | 99.858 % | 99.948 % |
| 1.0 | 89.839 % | 89.839 % |
| 2.0 | 10.783 % | 6.830 % |
에서는 그래야 하듯 동일합니다. 에서는 1.58배 차이입니다. “Presence penalty 1.0”은 temperature가 어디에 적용되는지도 알지 못하면 잘 정의된 penalty 양이 아니며, 어떤 API도 이것을 문서화하지 않습니다.
세부 정보 보기
선택 사항: 위 순서 그대로의 전체 pipeline.
열여섯 줄이고, 이 장의 모든 것이 그 안에 있습니다. widget이 수행하는 것과 같은 계산입니다. 다만 고정 숫자 열 개가 아니라 실제 logit vector에 대해 수행합니다.
def sample(logits, counts, presence=0.0, frequency=0.0,
temperature=1.0, top_k=0, top_p=1.0, generator=None):
z = logits.clone()
idx = torch.tensor(list(counts)) # 1. penalties
if len(idx):
z[idx] -= presence
z[idx] -= frequency * torch.tensor([float(c) for c in counts.values()])
if temperature <= 0: # 2. temperature
return int(z.argmax()) # T=0 is argmax
p = torch.softmax(z / temperature, -1)
p, order = p.sort(descending=True)
if top_k: # 3. top-k
p[top_k:] = 0
p = p * ((p.cumsum(0) - p) < top_p) # 4. top-p
p = p / p.sum() # 5. renormalise
return int(order[torch.multinomial(p, 1, generator=generator)])top-p line의 cumsum(0) - p는 current token을 제외한 cumulative mass입니다. 그래서 threshold를 넘는 token 직전에서 멈추는 것이 아니라, threshold를 넘기는 token을 nucleus에 포함하게 됩니다. 이것을 off by one으로 만들면 top_p = 0.9가 조용히 다른 모든 구현보다 약간 더 타이트한 cut이 됩니다.
이 과정 후반부에서 Python이 옳은 언어인 몇 안 되는 지점 중 하나이며, 이유는 문체가 아니라 구조적입니다. 위의 모든 줄은 logits의 full vector가 손안에 있어야 합니다. HTTP API 너머에는 그 vector가 존재하지 않습니다. provider에 temperature와 top_p를 보낼 수는 있지만, 그것들을 구현할 수는 없고, 그것들이 무엇을 했는지 볼 수도 없습니다.
보편적인 sampling API는 없습니다
섹션 링크: 보편적인 sampling API는 없습니다모든 provider는 이 control들의 서로 다른 subset을 받고, 범위도 다르며, 나머지는 조용히 무시합니다. 이것은 추상적인 불평이 아닙니다. model 선택을 제공하는 어떤 application이든 그 차이를 어딘가에 적어야 하며, 그것을 적는 file은 incompatibility의 지도입니다. 그런 catalogue 하나가 지원하는 아홉 개 text source 전반에서 단일 parameter에 대해 선언하는 내용은 다음과 같습니다.
| declared temperature range | sources |
|---|---|
| 0 to 1 | Anthropic, Google, Meta, Cerebras, PaLM |
| 0 to 1.5 | Mistral |
| 0 to 2 | OpenAI, DeepSeek, xAI |
단어는 같지만 scale은 다릅니다. “temperature of 1”은 어떤 곳에서는 수정되지 않은 분포이고, 다른 곳에서는 허용되는 최대 열입니다. catalogue의 절반은 다른 절반이 neutral-plus-a-bit로 취급하는 값을 표현할 수조차 없습니다. 나머지 knob도 똑같이 불균등합니다. OpenAI, DeepSeek, xAI entry는 presence와 frequency penalty를 받고 topK는 받지 않습니다. Google, Meta, Cerebras, PaLM entry는 topK를 받고 penalty는 받지 않습니다. Anthropic은 topK, topP, stop sequence를 받고 penalty는 받지 않습니다. 그리고 아홉 개 중 정확히 하나 — Mistral — 만 seed를 받습니다. provider가 구현하지 않은 parameter를 보내도 대개 error가 전혀 나지 않습니다. request는 성공하고, knob는 아무 일도 하지 않으며, 당신은 setting에 효과가 없다고 결론내립니다.
그리고 그런 file이 무엇인지 주목하세요. 특정한 하루에 작성되었고, 이후에는 아무것도 그것을 검증하지 않는, 다른 누군가의 API에 대한 claim입니다. provider가 이제 0에서 2를 받는데 catalogue가 0에서 1이라고 말하면, 모든 request가 조용히 cap됩니다.
같은 family에 속하는 control이 두 개 더 있습니다. 제공되는 경우 logprobs는 선택된 token의 log-probability와 종종 상위 몇 개 대안을 반환합니다. 이것이 이 장이 다루는 분포를 들여다볼 수 있는 유일한 window이며, closed model 위에 구축된 모든 confidence heuristic의 기반입니다. 그리고 maximum tokens와 stop sequences는 probability와 무관하게 generation을 끝냅니다. hard cap과 string match입니다. 둘 다 14장의 finish_reason로 드러납니다. 거기서 length는 답이 모델에 의해 완료된 것이 아니라 budget 때문에 문장 중간에서 잘렸다는 뜻입니다.
Seed, 그리고 당신에게 없는 determinism
섹션 링크: Seed, 그리고 당신에게 없는 determinismseed를 설정하면 sampling은 재현 가능해집니다. 그 부분은 진짜이고, 확인하기 쉽습니다.
seed = 1234 " Paris\nWhat is a good geographical qualifier for describing
Paris concerning its location?\nA: Near the Mediterranean Sea"
seed = 1234 " Paris\nWhat is a good geographical qualifier for describing
Paris concerning its location?\nA: Near the Mediterranean Sea"
seed = 7 " Paris is the capital of France. The appellation of Paris is
\"Île de Paris\"."
seed = 7 " Paris is the capital of France. The appellation of Paris is
\"Île de Paris\"."같은 seed 안에서는 byte까지 동일하고, seed가 다르면 달라집니다. 광고한 그대로입니다. 따라서 seed가 고정하는 것은 저 sample 함수의 마지막 줄에 있는 random draw입니다 — 주어진 분포에서 어떤 token이 선택되는가입니다.
seed가 고정하지 못하는 것은 분포입니다. 그리고 문제가 여기에 있습니다. 모델이 만드는 logit vector는 수학적 객체가 아닙니다. 수십억 번의 floating-point addition 출력이고, 그 addition에는 순서가 있습니다.
2장이 이 실험을 준비해 두었습니다. 같은 백만 개의 float32 숫자를 서로 다른 grouping으로 합산합니다.
sequential 998.564270020 error vs float64: 6.393e-03
pairwise (numpy) 998.570556641 error vs float64: 1.061e-04
in 4 chunks 998.570495605 error vs float64: 1.672e-04
in 8 chunks 998.570556641 error vs float64: 1.061e-04
in 16 chunks 998.570678711 error vs float64: 1.594e-05
sequential == pairwise? False
4 chunks == 8 chunks? False마지막 줄을 보세요. chunk 수가 답을 바꿉니다. 이것은 numpy에 관한 호기심이 아닙니다. 이것이 메커니즘입니다. inference server가 reduction을 더 많거나 적은 parallel unit으로 나눌 때 정확히 이 일을 하기 때문입니다. 그리고 server는 얼마나 많은 request를 처리하고 있는지에 따라 나눕니다.
모델 자체에서의 효과는 이렇습니다. 같은 prompt, 같은 forward pass, 유일한 차이는 batch 안에 우연히 다른 request가 몇 개 있었는지입니다.
20 identical forward passes, batch of 1: 20 / 20 bit-for-bit identical
the same prompt inside a batch of 2: 147,321 of 151,936 logits differ
the same prompt inside a batch of 4: 146,515 of 151,936 logits differ
the same prompt inside a batch of 8: 146,515 of 151,936 logits differ
the same prompt inside a batch of 16: 147,321 of 151,936 logits differ
largest change to any logit: 2.5e-05혼자 실행하면 모델은 완전히 deterministic합니다 — 스무 번 pass해도 bit까지 동일합니다. 동일한 prompt를 관련 없는 request와 함께 batch에 넣으면 logits의 97 %가 바뀝니다. 당신의 request는 아무것도 바뀌지 않았습니다. 다른 누군가의 request가 도착했을 뿐입니다.
이제 정직한 부분입니다. 보통 이것이 이야기의 끝인 것처럼 말해지기 때문입니다. 의 변화는 두 candidate token이 그 정도 이내에 있을 때만 output을 바꿉니다. 12개 prompt에 걸친 717개 generation step에서 top two logits 사이의 가장 작은 gap은 였습니다 — perturbation보다 백 배 컸고 — flip될 만큼 가까운 step은 없었습니다. 그러므로 이 모델, float32, laptop에서는 batching이 모든 logit을 움직였지만 token은 바꾸지 않았습니다.
이는 유리한 조건의 설명이지 안심시켜 주는 말이 아닙니다. 조건 하나만 바뀌어도 충분합니다.
same weights, same prompts, greedy decoding, no seed involved
float32 vs bfloat16: 6 of 8 answers diverge
first divergence at step 23, on average
float32: "...it is scattered and dispersed into different colors,
including blue. The blue light is scattered more than other
colors, so it appears to come from the sky."
bfloat16: "...it is scattered and scattered, causing the colors of the
sun to be scattered and scattered, creating the appearance
of a blue color."여덟 답 중 여섯이 갈라지고, 그중 하나는 크게 나빠집니다. 2장의 table이 이유를 말합니다. bfloat16은 mantissa bit를 7개 유지하므로 logit magnitude가 16 근처일 때 표현 가능한 값은 0.125 간격입니다 — 16.0, 다음 16.125, 다음 16.25 — 그리고 rounding은 logit을 최대 0.0625만큼 움직일 수 있습니다. 한편 위에서 측정한 generation step의 4.7 %는 top-two gap이 0.1보다 작았습니다. 이것이 두 실험의 전부입니다. float32에서는 perturbation이 가장 가까운 decision보다 백 배 작았고, bfloat16에서는 같은 크기입니다. Production inference는 16-bit에서, fused kernel과 누구도 유지하겠다고 약속하지 않는 reduction order를 가진 hardware에서 실행됩니다. “numerical noise가 무시 가능하냐”는 모델에 관한 질문이 아니라 precision과 hardware에 관한 질문입니다.
그래서 9장이 약속했던 네 가지 원인은 다음과 같습니다.
Floating-point addition은 associative가 아닙니다
섹션 링크: Floating-point addition은 associative가 아닙니다2장의 박스입니다. 합의 순서가 값을 바꾸므로, reduction이 나뉘는 방식의 변화는 logits를 바꿉니다. 이것이 substrate입니다. 다른 세 가지는 순서를 바꾸는 방법입니다.
Dynamic batching은 당신의 request를 낯선 이들의 request와 묶습니다
섹션 링크: Dynamic batching은 당신의 request를 낯선 이들의 request와 묶습니다13장의 continuous batching은 inference가 감당 가능한 가격이 되는 이유입니다 — 그리고 그것은 당신의 token이 통과하는 matrix의 shape가 traffic에 의존한다는 뜻입니다. 위에서 측정한 바: batch size가 바뀌었기 때문에 147,321개 logits가 움직였습니다.
Mixture-of-experts routing은 batch에 의존합니다
섹션 링크: Mixture-of-experts routing은 batch에 의존합니다9장의 박스가 이미 말했습니다. router는 token마다, layer마다 discrete choice를 하며, batch 전체에 대해 계산된 per-expert capacity limit을 따릅니다. 혼자였으면 expert 7로 갔을 token이 다른 token들과 함께 있을 때 expert 12로 갑니다. 이것은 rounding difference가 아닙니다. 다른 weights 집합입니다.
이름 뒤의 모델이 바뀝니다
섹션 링크: 이름 뒤의 모델이 바뀝니다-latest 같은 version string은 pointer이고, pointer는 다시 가리켜질 수 있습니다. provider는 고정된 version identifier 아래에서 serving stack도 업데이트합니다. 그 어느 것도 당신 자신의 output 변화와 상관관계를 지을 수 있을 정도의 granularity로 발표되지 않습니다.
OpenAI의 seed parameter는 가능한 유일한 방식으로 이 점에 대해 정직합니다. backend configuration을 식별하는 system_fingerprint field와 함께 제공되고, documentation은 determinism이 best-effort이며 fingerprint가 바뀌면 result가 달라질 수 있다고 말합니다. 이것을 있는 그대로 읽어야 합니다 — provider는 위의 네 가지 원인을 모두 통제하고, 당신은 그중 아무것도 통제하지 못하며, provider가 제공할 수 있는 유일한 것은 무언가 움직였다는 사실을 사후에 알려주는 것뿐입니다.
다음은 어디로 가는가
섹션 링크: 다음은 어디로 가는가여기까지는 모두 하나의 knob와 그 결과에 관한 이야기였습니다. 한 단계 물러서면 더 어려운 문제가 나타납니다. 우리가 tuning해 온 객체는 probability distribution이고, probability distribution에는 interface가 없습니다.
function call에는 interface가 있습니다. database row에도 있습니다. 세 개의 required field가 있는 JSON body를 기대하는 POST handler에도 interface가 있으며, 그 외의 것은 거부합니다. 모델과 시스템의 모든 다른 component 사이에는 한쪽이 약속할 수 없는 contract가 놓여 있습니다. 모델은 당신이 shape했지만 고정하지는 않은 분포에서 뽑은 무언가를 만들 것이고, 반대편 code는 known type의 value가 필요하며 그렇지 않으면 throw합니다.
그 두 세계 사이의 다리는 parsing과 retry가 아니라 이 장의 재료로 만들어집니다. 어떤 token이 required structure를 깨뜨린다면, 그것을 sample하고 희망하지 않습니다 — softmax가 보기 전에 그 token의 logit을 로 설정합니다. Constrained decoding은 이 장 내내 우리가 재형성해 온 같은 vector 위의 mask이며, “JSON으로 답해주세요”를 요청에서 보장으로 바꿉니다.
18장은 그 contract입니다. tool calling, JSON Schema, structured outputs, 그리고 probabilistic한 것 위에 deterministic system을 안전하게 구축하려면 무엇이 필요한지에 관한 장입니다.
Sources and method
섹션 링크: Sources and method이 장의 모든 측정은 CPU의 Qwen/Qwen2.5-0.5B-Instruct에서, 별도 명시가 없으면 float32로 수행했습니다. sampling은 library에 위임하지 않고 optional section에 적힌 대로 구현했습니다. 작은 모델이고 구체적 값은 그 모델의 것이지만, 메커니즘은 그렇지 않습니다. Von Platen의 How to generate text with different decoding methods (Hugging Face, 2020)는 이 글이 기준으로 삼아 측정한 article이며, 여전히 같은 주제에 대한 최고의 짧은 소개입니다. determinism section에 대해서는 PyTorch의 reproducibility notes가 단일 머신에서 seed가 무엇을 고정하고 무엇을 고정하지 못하는지 설명하고, OpenAI의 seed 및 system_fingerprint documentation은 provider가 무엇을 약속할 수 있고 무엇을 약속할 수 없는지 설명하며, Thinking Machines의 2025년 batch-invariant kernels 논의는 inference-server level에서 이것을 고치는 일이 가능하지만 공짜는 아닌 이유를 설명한 가장 명확한 공개 설명입니다.
-
Ackley, D. H., Hinton, G. E. and Sejnowski, T. J. A Learning Algorithm for Boltzmann Machines. Cognitive Science 9(1), pp. 147–169 (1985). softmax의 temperature가 통계물리학에서 온 곳입니다. Hinton, G., Vinyals, O. and Dean, J., Distilling the Knowledge in a Neural Network, arXiv:1503.02531 (2015), section 2에서는 같은 parameter가 modern deep learning에 다시 등장합니다 — teacher의 full distribution을 드러내는 방식으로, 이는 이 장의 sampling이 아니라 13장의 soft labels입니다. ↩
-
Guo, C., Pleiss, G., Sun, Y. and Weinberger, K. Q. On Calibration of Modern Neural Networks. arXiv:1706.04599 (2017). 이 장의 temperature와 혼동하지 마세요. Temperature scaling은 validation set에서 단일 값을 맞춰 모델의 confidence가 accuracy와 일치하도록 하는 것입니다. classifier의 output에 적용되는 post-hoc calibration method입니다. Temperature sampling은 generator가 token을 뽑는 방식을 제어하는 runtime control입니다. 같은 공식, 다른 목적이며, 공유되는 값은 없습니다. ↩
-
Holtzman, A., Buys, J., Du, L., Forbes, M. and Choi, Y. The Curious Case of Neural Text Degeneration. arXiv:1904.09751 (2019). nucleus sampling과, maximisation-based decoding이 human text와 전혀 다른 probability profile의 text를 만든다는 측정을 소개합니다. ↩ ↩2
-
Fan, A., Lewis, M. and Dauphin, Y. Hierarchical Neural Story Generation. arXiv:1805.04833 (2018). top-k sampling을 대중화한 논문입니다. ↩
-
Nguyen, M. et al. Turning Up the Heat: Min-p Sampling for Creative and Coherent LLM Outputs. arXiv:2407.01082 (2024). ↩
-
Meister, C., Pimentel, T., Wiher, G. and Cotterell, R. Locally Typical Sampling. arXiv:2202.00666 (2022). ↩
-
Keskar, N. S., McCann, B., Varshney, L. R., Xiong, C. and Socher, R. CTRL: A Conditional Transformer Language Model for Controllable Generation. arXiv:1909.05858 (2019). Section 4.1이 원래 repetition penalty입니다 — 나누는 방식입니다. ↩