트랜스포머 — 어텐션을 손으로 계산한다 · 온도·top-k·top-p · 실습
온도·top-k·top-p 를 손으로 만든다
목표
로짓 한 벌에서 다음 토큰 하나를 고르기까지, 분포가 거치는 자리를 표준 라이브러리만으로 전부 만든다. 온도로 나눈 뒤의 안정한 소프트맥스, 엔트로피, top-k, top-p, 넷을 정해진 순서로 잇는 함수, 역누적분포 추출, greedy 와 온도 훑기까지다. 마지막에 같은 시드로 두 번 뽑아 같은 수열이 나오는지와 온도를 낮춘 쪽이 greedy 와 같아지는지를 기록으로 남긴다.
왜 중요한가
트랜스포머 블록이 하는 일은 어휘 크기만 한 실수 목록 하나를 내놓는 것까지다. 실제로 쓸 토큰을 고르는 것은 모델 바깥의 규칙이고, 같은 모델이 매번 다르게 말하는 이유도 매번 같은 말을 되풀이하는 이유도 전부 거기 있다.
이 실습은 모델을 부르지 않는다. 이 파드에는 transformers 도 torch 도 없고 numpy 는 /opt/onnx-lab/bin/python 안에만 있어 시스템 파이썬에서는 import numpy 가 되지 않는다. 대신 로짓 한 벌을 직접 적어 두고 그 뒤를 손으로 만든다. 그래서 여기 나오는 숫자는 전부 여러분이 적은 로짓에서 잰 것이다.
어려운 것은 세부다. 온도를 나누는 자리가 소프트맥스 앞인지 뒤인지, 최댓값을 왜 빼는지, top-k 의 동점을 어떻게 깨는지, top-p 가 p 에 닿는 항목을 포함하는지, 온도와 top-p 와 top-k 를 어느 순서로 먹이는지. 하나만 달라도 같은 설정이 다른 시스템이 된다.
채점기는 여러분이 적어 둔 설명을 믿지 않는다. 여러분의 모듈을 실제로 불러 매번 다른 로짓과 다른 온도로 함수를 두드려 보고, 채점기가 따로 계산한 확률 벡터와 대조한다. 무작위 추출 자체는 판정하지 않는다 — 시드를 고정한 번호 수열과 확률 벡터만 본다.
단계
1. /root/work/tf-sample/sample.py 에 VOCAB·LOGITS 와 softmax_t(logits, temperature) 를 만드세요. 로짓을 온도로 나눈 뒤 최댓값을 빼고 소프트맥스합니다.
2. entropy_bits(probs) 를 더해 분포가 퍼진 정도를 밑이 2 인 엔트로피로 재게 하세요. 확률이 0 인 자리는 0 으로 칩니다.
3. top_k_filter(probs, k) 를 더해 확률이 큰 k 개만 남기고 나머지를 정확히 0.0 으로 두고 다시 정규화하게 하세요. 동점이면 번호가 작은 쪽이 남습니다.
4. top_p_filter(probs, p) 를 더해 내림차순 누적합이 p 에 처음 닿는 그 항목까지 남기게 하세요. 그 항목을 빼면 합이 p 에 못 미칩니다.
5. filtered_probs(logits, temperature, top_k=None, top_p=None) 를 만들어 온도 → 소프트맥스 → top-k → top-p 순서로 잇게 하세요. None 이면 그 단계를 건너뜁니다.
6. sample_index(probs, u) 와 sample_sequence(logits, temperature, top_k, top_p, n, seed) 를 만드세요. 역누적분포로 뽑고, random.Random(seed) 에서 random() 을 n 번 불러 같은 시드면 같은 수열이 나오게 합니다.
7. greedy(logits) 와 sweep(logits, temps, p) 를 만드세요. greedy 는 가장 큰 로짓의 번호(동점이면 작은 번호)이고, sweep 은 온도마다 (온도, 엔트로피, top-p 가 남기는 개수) 를 잽니다.
8. 정해진 설정으로 재어 /root/work/tf-sample/sample_report.json 과 /root/work/tf-sample/sample_report.md 에 결과를 기록하세요.
참고
- 실행 계약: 채점기는
/root/work/tf-sample/sample.py를 파이썬 모듈로 불러VOCAB·LOGITS·softmax_t·entropy_bits·top_k_filter·top_p_filter·filtered_probs·sample_index·sample_sequence·greedy·sweep를 직접 씁니다. 스크립트로 실행하지 않으므로if __name__ == "__main__"은 없어도 됩니다. VOCAB은 서로 다른 짧은 문자열 12개 이상의 목록입니다. 내용은 자유롭게 정하세요.LOGITS는VOCAB과 길이가 같은 실수 목록이고 값이 서로 달라야 합니다. 확률이 아니라 정규화되지 않은 점수입니다. 1등과 2등의 차이가 0.5 이상, 최댓값과 최솟값의 차이가 3.0 이상이어야 온도를 바꿀 때 분포가 움직이는 것이 보입니다.softmax_t(logits, temperature)는 먼저 로짓을temperature로 나누고, 나눈 값에서 최댓값을 뺀 뒤 지수를 취해 정규화합니다. 최댓값을 빼지 않으면 온도가 작을 때math.exp가 넘칩니다.temperature가 0 이하이면 예외를 내세요.entropy_bits(probs)는-sum(p * log2(p))입니다.p가 0 인 자리는 건너뜁니다. 고르게 퍼진 n 개면log2(n)이 나옵니다.top_k_filter(probs, k)는 버린 자리를 정확히0.0으로 두고 남은 것만 합이 1 이 되게 정규화합니다. 동점이면 번호가 작은 쪽이 남습니다.k가 목록 길이 이상이면 아무것도 버리지 않고 정규화만 합니다.k가 1 보다 작으면 예외를 내세요.top_p_filter(probs, p)는 확률을 내림차순(동점이면 번호가 작은 쪽이 앞)으로 늘어놓고 누적합을 재다가, 누적합이 p 이상이 되는 첫 항목까지 포함하고 멈춥니다. 나머지는 정확히0.0이고 남은 것을 정규화합니다.p는 0 보다 크고 1 이하여야 하며, 그 밖이면 예외를 내세요.filtered_probs의 순서는 온도 → 소프트맥스 → top-k → top-p 입니다. 이 순서가 이 실습의 계약이고, 순서를 바꾸면 남는 토큰이 달라집니다.sample_index(probs, u)는 번호 순서대로 확률을 더해 가다u를 처음 넘는 자리를 돌려줍니다(u < 누적합이 참이 되는 첫 자리).u는 0 이상 1 미만입니다. 확률이0.0인 자리는 절대 돌려주지 않습니다.sample_sequence(logits, temperature, top_k, top_p, n, seed)는 분포를 한 번만 만들고random.Random(seed)의random()을 n 번 불러 번호 n 개의 목록을 돌려줍니다. 시드가 같으면 결과도 같아야 합니다.sweep(logits, temps, p)는temps의 온도마다(온도, 엔트로피, top-p 가 남기는 개수)세 쌍의 목록을 돌려줍니다. 개수는top_p_filter를 거친 뒤 0 이 아닌 자리의 수입니다.- 8단계 설정은 고정입니다. 시드 20260917, 뽑는 횟수 24,
top_k는 5,top_p는 0.9 입니다. 더운 쪽은 온도 1.0, 찬 쪽은 온도 0.2 로 같은 시드에서 뽑습니다. - 8단계의
sweep온도는 0.25 · 0.5 · 1.0 · 2.0 · 4.0 이고 그때의p는 0.9 입니다.nucleus는 온도 1.0 에서 p 를 0.5 · 0.8 · 0.9 · 0.95 로 바꿔 가며 잰[p, 남은 개수]이고,topk_mass는 온도 1.0 의 원래 확률에서 상위 k 개가 차지하는 합을 k 1 · 3 · 5 · 10 에 대해 잰[k, 합]입니다. sample_report.json의 키:vocab_size·greedy_index·greedy_token·top_prob_t1·entropy_t1·sweep·nucleus·topk_mass·seed·draw_count·hot_draws·cold_draws·cold_is_greedy·hot_distinct·cold_distinct·repeat_matches.sample_report.md는## 무엇을 쟀나## 온도가 분포를 어떻게 바꾸나## top-k 와 top-p 가 남기는 것## 같은 시드는 같은 수열을 준다네 절로 씁니다.- 이 파드에는 인터넷이 없습니다.
pip install은 되지 않고 transformers·torch 도 없습니다. numpy 는/opt/onnx-lab/bin/python안에만 있으므로 시스템 파이썬에서는import numpy가 되지 않습니다.math와random이면 충분합니다. - 공식 문서: [nucleus sampling 원논문](https://arxiv.org/abs/1904.09751) · [top-k 원논문](https://arxiv.org/abs/1805.04833) · [Hugging Face — Generation strategies](https://huggingface.co/docs/transformers/en/generation_strategies) · [Python — math](https://docs.python.org/3/library/math.html)
- 흔한 실수: 확률에 온도를 먹이기, 최댓값 빼기를 생략해 낮은 온도에서 넘치기, 잘라낸 뒤 정규화를 잊기, top-p 에서 p 를 넘는 첫 항목을 빼 버리기, 동점 규칙을 안 정하기, top-p 를 top-k 보다 먼저 적용하기, 뽑을 때마다 분포를 새로 만들기.
단계 8개
- 온도로 나눈 뒤 소프트맥스
- 퍼진 정도를 비트로 잰다
- 위에서 k 개만 남긴다
- 누적합이 p 에 닿는 그 항목까지
- 네 가지를 정해진 순서로 잇는다
- 같은 시드는 같은 수열
- greedy 와 온도 훑기
- 손잡이를 돌린 결과를 남긴다