Setiap bab sejauh ini berakhir di tensor logits: vektor skor mentah untuk tiap token kandidat. Menjalankan LLM berarti memutuskan, berulang kali, token mana yang diambil dari vektor itu — dan keputusan tersebut menentukan karakter keluaran jauh melampaui kualitas model itu sendiri. Distribusi yang sama bisa menghasilkan jawaban kaku yang selalu identik atau paragraf yang bervariasi antar permintaan, hanya karena strategi decoding-nya berbeda. Bab ini membedah lima strategi utama, menguji semuanya pada distribusi contoh dengan angka yang bisa dihitung ulang, lalu membahas KV cache — mekanisme yang menjaga generasi ribuan token tetap cepat.
Temperature: Melengkungkan Distribusi #
Model mengeluarkan logits z; probabilitas diperoleh lewat softmax yang dimodifikasi satu parameter:
p(token_i) = exp(z_i / T) / Σ_j exp(z_j / T)
T < 1 : distribusi mencuat — token kuat makin dominan
T = 1 : distribusi asli model
T > 1 : distribusi merata — token lemah ikut berpeluangTemperature mengubah skala logits sebelum softmax, sehingga selisih skor antar token ikut meregang atau menyempit. Ukuran dampaknya terbaca langsung dari entropi distribusi: semakin rendah entropi, semakin mudah hasil diprediksi. Sisi praktisnya: T → 0 secara limit menjadi greedy decoding, dan pemilihan T adalah knob rasa pertama yang dijumpai pada setiap penyedia API.
Top-k dan Top-p: Memangkas Ekor #
Distribusi bahasa bermasalah pada ekornya: ratusan token berpeluang mikro membangun “derau” yang sesekali terpilih dan merusak koherensi. Dua filter memotongnya:
- Top-k menyisakan
ktoken berprobabilitas tertinggi, sisanya dinol-kan, lalu probabilitas yang tersisa dinormalisasi ulang. Parameternya tetap (mis.k=2) terlepas bentuk distribusinya. - Top-p (nucleus) menyisakan set kandidat terkecil yang jumlah probabilitasnya mencapai
p(mis. 0,9). Set ini mengecil pada distribusi tajam dan melebar pada distribusi datar — adaptif terhadap keyakinan model.
Keduanya kerap dipadukan dengan temperature: distribusi dilengkungkan dulu, ekornya dipangkas kemudian, barulah sampling dilakukan.
Demo: Lima Strategi pada Angka Nyata #
Berkas decoding_demo.py memakai satu vektor logits atas delapan token untuk menguji temperature, top-k, dan top-p, kemudian model bigram lima token untuk mengadu greedy, beam search, dan pencarian exhaustive:
"""Strategi decoding: temperature, top-k, top-p, beam search, KV cache.
Bagian 1: satu vektor logits dibagi temperature 0.1 / 1.0 / 2.0 —
distribusi dan entropinya dibandingkan.
Bagian 2: filter top-k = 2 menyisakan dua kandidat terkuat.
Bagian 3: filter top-p = 0.9 (nucleus) menyisakan set kandidat minimal.
Bagian 4: sampling 12 token dari tiap temperature (RNG berbenih).
Bagian 5: greedy vs beam search width 2 vs pencarian exhaustive pada
model bigram mainan 5 token.
Bagian 6: estimasi memori KV cache model 32 lapis untuk 2048 token.
"""
import itertools
import numpy as np
rng = np.random.default_rng(42)
TOKEN = ["ikan", "kopi", "teh", "roti", "susu", "gula", "es", "air"]
LOGITS = np.array([2.0, 1.2, 0.5, 0.1, -0.3, -1.0, -1.5, -2.0])
def softmax_dengan_T(z, T):
z = z / T
e = np.exp(z - z.max())
return e / e.sum()
def entropi(p):
q = p[p > 0]
return float(-(q * np.log(q)).sum())
# ---------- 1. Temperature ----------
print("== 1. temperature pada logits yang sama ==")
for T in (0.1, 1.0, 2.0):
p = softmax_dengan_T(LOGITS, T)
baris = " ".join(f"{v:.3f}" for v in p[:4])
print(f" T={T:3.1f}: p[:4] = [{baris}] entropi = {entropi(p):.4f}")
# ---------- 2. Top-k ----------
p1 = softmax_dengan_T(LOGITS, 1.0)
k = 2
idx_topk = np.argsort(p1)[::-1][:k]
mask = np.zeros_like(p1, dtype=bool)
mask[idx_topk] = True
p_topk = np.where(mask, p1, 0.0)
p_topk = p_topk / p_topk.sum()
print()
print("== 2. top-k = 2 (renormalisasi) ==")
for i in idx_topk:
print(f" {TOKEN[i]:5s}: {p1[i]:.3f} -> {p_topk[i]:.3f}")
# ---------- 3. Top-p ----------
p_des = p1[np.argsort(p1)[::-1]]
kum = np.cumsum(p_des)
n_ambil = int(np.searchsorted(kum, 0.9)) + 1
urut = np.argsort(p1)[::-1][:n_ambil]
print()
print("== 3. top-p = 0.9 (nucleus) ==")
print(" kumulatif:", " ".join(f"{v:.3f}" for v in kum[:n_ambil]))
for i in urut:
print(f" {TOKEN[i]:5s}: {p1[i]:.3f}")
print(f" kandidat tersisa: {n_ambil} dari {len(TOKEN)}")
# ---------- 4. Sampling seeded ----------
print()
print("== 4. sampling 12 token (seed 42) ==")
for T in (0.1, 1.0, 2.0):
p = softmax_dengan_T(LOGITS, T)
sampel = rng.choice(len(TOKEN), size=12, p=p)
teks = " ".join(TOKEN[i] for i in sampel)
print(f" T={T:3.1f}: {teks}")
# ---------- 5. Greedy vs beam search vs exhaustive ----------
P = rng.random((5, 5))
P = P / P.sum(axis=1, keepdims=True) # model bigram: P[next | kini]
def logp_sekuens(seq):
return sum(np.log(P[seq[i], seq[i + 1]]) for i in range(len(seq) - 1))
greedy = [0]
for _ in range(4):
greedy.append(int(np.argmax(P[greedy[-1]])))
beam = [(0.0, [0])]
for _ in range(4):
kandidat = []
for skor, seq in beam:
for t in range(5):
kandidat.append((skor + np.log(P[seq[-1], t]), seq + [t]))
kandidat.sort(key=lambda x: -x[0])
beam = kandidat[:2]
semua = itertools.product(range(5), repeat=4)
terbaik = max(((logp_sekuens((0,) + s), (0,) + s) for s in semua),
key=lambda x: x[0])
print()
print("== 5. greedy vs beam (w=2) vs exhaustive, model bigram 5 token ==")
print(f" greedy : {greedy} logp {logp_sekuens(greedy):.4f}")
print(f" beam w=2 : {beam[0][1]} logp {beam[0][0]:.4f}")
print(f" exhaustive : {list(terbaik[1])} logp {terbaik[0]:.4f}")
# ---------- 6. Estimasi memori KV cache ----------
n_lapis, n_kepala, d_kepala, panjang = 32, 32, 128, 2048
byte = 2 * n_lapis * n_kepala * d_kepala * panjang * 2 # K+V, fp16
print()
print("== 6. memori KV cache (32 lapis, 2048 token, fp16) ==")
print(f" {byte / 1e9:.2f} GB untuk satu sekuens satu batch")
print(f" batch 4 pengguna: {4 * byte / 1e9:.2f} GB")Output yang Dihasilkan:
== 1. temperature pada logits yang sama ==
T=0.1: p[:4] = [1.000 0.000 0.000 0.000] entropi = 0.0030
T=1.0: p[:4] = [0.495 0.222 0.110 0.074] entropi = 1.4642
T=2.0: p[:4] = [0.296 0.198 0.140 0.114] entropi = 1.8874
== 2. top-k = 2 (renormalisasi) ==
ikan : 0.495 -> 0.690
kopi : 0.222 -> 0.310
== 3. top-p = 0.9 (nucleus) ==
kumulatif: 0.495 0.717 0.828 0.902
ikan : 0.495
kopi : 0.222
teh : 0.110
roti : 0.074
kandidat tersisa: 4 dari 8
== 4. sampling 12 token (seed 42) ==
T=0.1: ikan ikan ikan ikan ikan ikan ikan ikan ikan ikan ikan ikan
T=1.0: kopi teh ikan ikan kopi ikan teh kopi teh ikan gula roti
T=2.0: susu ikan kopi ikan ikan roti roti air kopi kopi kopi ikan
== 5. greedy vs beam (w=2) vs exhaustive, model bigram 5 token ==
greedy : [0, 3, 1, 0, 3] logp -4.8769
beam w=2 : [0, 3, 4, 4, 4] logp -4.5326
exhaustive : [0, 3, 2, 2, 2] logp -4.3744
== 6. memori KV cache (32 lapis, 2048 token, fp16) ==
1.07 GB untuk satu sekuens satu batch
batch 4 pengguna: 4.29 GBMari kita telaah kode di atas baris per baris:
- Baris 22-30 (softmax dan entropi): pembagian logits dengan
Tterjadi sebelumnp.exp, dan penguranganz.max()menjaga stabilitas numerik tanpa mengubah hasil. Fungsientropimenghitung−Σ p log p— meteran keragaman yang dipakai di seluruh bab ini. - Baris 34-42 (temperature): pada logits yang sama, entropi turun dari 1,4642 (
T=1) ke 0,0030 (T=0,1) — distribusi praktis menjadi satu puncak; naik keT=2meratakannya ke 1,8874, mendekati entropi maksimal 8 token seragam (ln 8 ≈ 2,08). - Baris 45-54 (top-k): dua kandidat terkuat bertahan; setelah renormalisasi,
ikannaik dari 0,495 ke 0,690 — pemotongan ekor memusatkan peluang ke pilihan yang layak. - Baris 57-67 (top-p): kumulatif 0,495 → 0,717 → 0,828 → 0,902 berhenti tepat melewati 0,9, menyisakan 4 dari 8 kandidat. Pada distribusi yang lebih tajam, set ini otomatis mengecil — itulah sifat adaptif nucleus.
- Baris 70-77 (sampling):
rng.choiceberbenih 42 membuat hasil dapat direproduksi; keluarannya memperlihatkan spektrum penuh —T=0,1dua belas kaliikan,T=1campuran empat token,T=2menjamur ke tujuh token berbeda. - Baris 81-104 (tiga pencarian): greedy memilih token terbaik tiap langkah tanpa pandang depan dan berakhir di logp −4,8769. Beam width 2 menyimpan dua jalur parsial terbaik, memperluasnya, dan menemukan −4,5326. Pencarian exhaustive atas seluruh 625 jalur membuktikan batas atasnya −4,3744 — beam mendekati optimal dengan biaya jauh lebih kecil, tetapi tidak menjangkaunya.
- Baris 107-113 (KV cache):
2 × 32 × 32 × 128 × 2048 × 2 byte= 1,07 GB untuk satu sekuens — satu faktor 2 untuk pasangan K dan V, satu lagi untuk 2 byte fp16.
KV Cache: Mengapa Inference Cepat #
Attention pada token baru harus memperhatikan seluruh token sebelumnya. Tanpa cache, setiap langkah menghitung ulang proyeksi K dan V untuk seluruh prefiks — biaya yang tumbuh kuadratik sepanjang generasi. KV cache menyimpan hasil proyeksi itu sekali: langkah ke-t cukup menghitung K,V untuk satu token baru dan membaca sisanya dari cache. Konsekuensinya ada dua. Pertama, kompleksitas per langkah turun dari sebanding-t-token-penuh menjadi satu token plus pembacaan cache. Kedua, memori menjadi harga yang dibayar: seperti dihitung pada demo, model 32 lapis dengan konteks 2048 token pada fp16 menyimpan 1,07 GB per sekuens — dan angka itu berlipat seiring batch serta panjang konteks, menjelaskan mengapa memori inference habis jauh sebelum hitungan bobot (yang kini bisa dipangkas lewat Quantization LLM
).
Memilih Strategi #
| Strategi | Perilaku | Cocok untuk |
|---|---|---|
Greedy / T→0 | deterministik, repetitif | kode, ekstraksi data, jawaban pendek |
| Temperature 0,7-1,0 + top-p | terkendali namun bervariasi | chat, penulisan umum |
| Temperature tinggi | eksploratif, kadang melenceng | ide awal, brainstorming |
| Beam search | memaksimalkan probabilitas sekuens | terjemahan, ringkasan terformat |
| Sampling + stop condition | menghormati panjang wajar | semua tugas generatif |
Satu perangkat penutup yang tak kalah penting: stop condition. Decoding harus tahu kapan berhenti — token EOS yang dipelajari model, batas max_new_tokens, atau detektor pola berulang. Tanpa ketiganya, model bisa terus menulis dengan kalimat yang berputar-putar.
Distribusi dan strategi kini lengkap; satu lapisan terakhir memisahkan keluaran berguna dari keluaran yang salah format. Model chat tidak dilatih pada teks polos, melainkan pada struktur percakapan dengan peran dan token khusus — dan pengguna yang mengabaikan struktur itu akan menuai jawaban yang turun kualitasnya. Struktur itulah pokok bahasan Prompt Format dan Chat Template .