jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Tokenization, Temperature, Top-p, Top-k — Mechanics bên dưới mọi LLM

4 cơ chế kỹ thuật mà dev nào dùng LLM cũng nên hiểu sâu: BPE tokenization step-by-step, math của temperature scaling, top-p (nucleus) vs top-k sampling, sampling pipeline hoàn chỉnh, và parameter cheatsheet.

Bạn dùng LLM mỗi ngày qua Cursor, ChatGPT, API. Nhưng có 4 mechanics underlying mà ít dev hiểu sâu:

  1. Tokenization — text thành số như thế nào?
  2. Temperature — sao T=0.7 lại “creative”, T=0 lại “deterministic”?
  3. Top-p — nucleus sampling là gì?
  4. Top-k — khác top-p ra sao?

Bài Tokens & Pricing cover góc cost. Bài Prompting Fundamentals cover góc prompt. Bài này đi sâu vào math + implementation của 4 mechanics — đủ để bạn debug output kỳ lạ, tune parameter có lý do, và không treat LLM như magic box.


1. Vì sao dev nên hiểu 4 mechanics này

USER PROMPT


[1] Tokenization     → "hello" → [15339]


TRANSFORMER FORWARD


LOGITS (vocab-size dimension)


[2] Temperature      → scale logits → softmax


PROBABILITY DISTRIBUTION (toàn vocab)


[3] Top-k filter     → giữ k token cao nhất


[4] Top-p filter     → giữ subset cumulative ≤ p


SAMPLE                → random pick → next token

Mỗi step có parameter. Hiểu mechanics → biết tune nào ảnh hưởng gì.


2. Tokenization — text thành số

2.1. Vì sao không dùng character / word

Character-level:

"hello world" → [h, e, l, l, o, " ", w, o, r, l, d] → 11 token

Vocabulary nhỏ (~256 ký tự ASCII). Nhược: chuỗi dài, model khó học dependency xa.

Word-level:

"hello world" → [hello, world] → 2 token

Nhược:

  • Vocabulary khổng lồ (1M+ từ)
  • Word mới (tên riêng, slang, typo) → unknown token
  • Không xử lý morphology (run, running, ran là token riêng)

Subword (BPE): cân bằng. Token mảnh nhỏ hơn word, lớn hơn ký tự.

2.2. BPE algorithm — step by step

Byte Pair Encoding (Sennrich et al. 2016, dùng trong GPT, Llama, Mistral…).

Algorithm:

Input: corpus C, vocab size target V

1. Init vocabulary = tất cả ký tự đơn xuất hiện trong C
2. Repeat V - len(initial_vocab) lần:
   a. Đếm tất cả pair (token_i, token_i+1) liền kề trong C
   b. Tìm pair xuất hiện nhiều nhất
   c. Merge pair đó thành token mới, add vào vocab
   d. Update C: replace mọi pair → token mới
3. Return vocab

2.3. Ví dụ minimal

Corpus toy: ["low", "low", "lower", "newer", "newest"]

Init vocab: {l, o, w, e, r, n, s, t}

Iteration 1:
Đếm pairs trong corpus:
  (l,o):  3   ← max
  (o,w):  3
  (w,e):  3
  ...
Merge "l"+"o" = "lo"
Vocab: {l, o, w, e, r, n, s, t, lo}
Corpus: ["lo·w", "lo·w", "lo·w·e·r", "n·e·w·e·r", "n·e·w·e·s·t"]

Iteration 2:
Đếm:
  (lo,w): 3   ← max
  ...
Merge "lo"+"w" = "low"
Vocab: {..., low}
Corpus: ["low", "low", "low·e·r", "n·e·w·e·r", "n·e·w·e·s·t"]

Iteration 3:
  (e,r): 2   ← max
Merge "e"+"r" = "er"
...

Sau N iteration, vocab chứa subword “frequent” thực tế.

2.4. Tokenization tiếng Anh vs tiếng Việt

GPT-4 tokenizer (cl100k_base):

import tiktoken
enc = tiktoken.encoding_for_model("gpt-4")

enc.encode("hello world")
# → [15339, 1917]  (2 tokens)

enc.encode("understanding programming")
# → [8154, 11628]  (2 tokens)

enc.encode("xin chào")
# → [87, 258, 23131, 24015]  (4 tokens — wait this is BPE level)

enc.encode("hiểu lập trình")
# → [71, 71101, 80, 444, 4505, 9637, 65, 56860]  (~8 tokens)

Tỉ lệ:

  • English: 1 word ≈ 1.3 tokens
  • Vietnamese: 1 word ≈ 2-3 tokens
  • Tiếng Việt đắt 2-3x ở pricing pay-per-token.

2.5. Tokenizer khác nhau giữa model

TokenizerDùng bởiVocab size
cl100k_baseGPT-4, GPT-3.5, embeddings100,277
o200k_baseGPT-4o, o1, o3200,019
tiktoken-GPT-2GPT-2 (legacy)50,257
Llama tokenizerLlama family128,000
SentencePiece (Llama 2)Llama 232,000
Claude tokenizerClaude (Anthropic)proprietary
TekkenMistral131,000

Tokenizer to hơn → chia mịn hơn → multilingual tốt hơn nhưng cost storage/inference cao hơn.

2.6. Thực hành: đếm token offline

import tiktoken

enc = tiktoken.encoding_for_model("gpt-4o")

text = "Bạn đang đọc bài blog này"
tokens = enc.encode(text)

print(f"Text: {text}")
print(f"Tokens: {tokens}")
print(f"Decoded each: {[enc.decode([t]) for t in tokens]}")
print(f"Count: {len(tokens)}")

Output:

Text: Bạn đang đọc bài blog này
Tokens: [33, 9165, 91087, 91144, 50596, 12814, 17567, 17249]
Decoded each: ['B', 'ạ', 'n', ' đang', ' đ', 'ọc', ' bài', ' blog', ' này']
Count: 9

→ Đếm được 9 token cho 25 ký tự, ratio 2.8 char/token (tệ — tiếng Anh ~4 char/token).

2.7. Tokenizer playground online

Paste prompt → xem chia token thế nào → optimize.


3. Từ logits đến token — sampling pipeline

Sau forward pass, model output logits (vector kích thước = vocab size, ví dụ 200K).

Logits: [-2.1, 5.3, 3.7, -0.8, 1.2, ...]  (200,019 numbers)

Mỗi value tương ứng với 1 token trong vocab. Cao = model “tin” token đó nên là next.

Logits không phải probability. Cần convert:

softmax(logit_i) = exp(logit_i) / Σ exp(logit_j)

Sau softmax:

Probability: [0.001, 0.42, 0.35, 0.002, 0.05, ...]
                     (vocab[1] = " the" = 42% chance)

Distribution này tổng = 1.0. Sampling = chọn token ngẫu nhiên theo xác suất này.

Vấn đề: distribution có thể “flat” (mọi token gần đều) hoặc “sharp” (1 token dominate). Mechanics dưới đây shape distribution trước khi sample.


4. Temperature — control độ “sharp”

4.1. Math

Temperature scaling chèn vào softmax:

softmax_T(logit_i) = exp(logit_i / T) / Σ exp(logit_j / T)

T là scalar. Phân tích 3 case:

T = 1: y nguyên softmax thường.

T < 1 (T → 0): logit chia cho số nhỏ → magnify difference. Token xác suất cao càng cao, thấp càng thấp. Distribution trở nên sharp.

T > 1: logit chia cho số lớn → giảm difference. Distribution trở nên flat — mọi token gần đều xác suất.

4.2. Visualize 3 case

Logits: [5.3, 3.7, 1.2, 0.5, -1.0] (5 token candidates)

T = 0.5 (low):
Distribution: [0.79, 0.16, 0.03, 0.01, 0.00]
                ↑ token 0 chiếm 79% — sharp

T = 1.0 (default):
Distribution: [0.69, 0.14, 0.04, 0.02, 0.01]
                            (vẫn dominate token 0 nhưng nhẹ hơn)

T = 2.0 (high):
Distribution: [0.51, 0.23, 0.10, 0.07, 0.04]
                            (token 0 giảm, các token khác tăng — flat)

4.3. Effect thực tế

TBehaviorDùng cho
0Greedy — luôn token argmaxCode generation, refactor, JSON output
0.1-0.3Near-greedy, hơi variationBug fix, structured task
0.5-0.7Default conversationalChat, Q&A
0.8-1.0CreativeBrainstorm, naming
1.2-1.5Very creativePoetry, fiction
> 2.0Often nonsenseHiếm khi dùng

4.4. T = 0 không hoàn toàn deterministic

Lý thuyết T=0 → argmax → deterministic. Thực tế:

  • Floating point precision: GPU compute non-deterministic ở mức bit cuối.
  • Batching: query của bạn batch với query khác có thể tạo difference.
  • Distributed inference: model chạy nhiều GPU, kết quả combine có khác biệt nhỏ.

→ Cùng prompt, T=0 vẫn có ~95-98% identical, không 100%.

Muốn truly reproducible: pass seed parameter (OpenAI hỗ trợ).

client.chat.completions.create(
    model="gpt-4o",
    messages=[...],
    temperature=0,
    seed=42  # reproducible
)

4.5. Pitfalls

Bẫy 1: T quá thấp cho creative task

Prompt: "Write a poem about coding"
T = 0 → output bland, robotic, lặp pattern
T = 1 → output diverse, surprising

Bẫy 2: T quá cao cho code

Prompt: "Refactor this function"
T = 1.5 → có thể introduce typo, hallucinate API, sai syntax
T = 0 → consistent, predictable

5. Top-p (nucleus sampling)

5.1. Vấn đề với pure temperature

Temperature shape distribution, nhưng vẫn có thể sample token extremely unlikely. Ví dụ T=1.0:

Distribution: [0.4, 0.3, 0.1, 0.05, 0.05, 0.04, ..., 0.001, 0.001, ...]
                                                       ↑ low-prob tail

Sampling từ tail → output bizarre, off-topic.

5.2. Top-p solution

Holtzman et al. 2019 “The Curious Case of Neural Text Degeneration”: giới hạn sample chỉ trong nucleus (set of token cumulative probability ≤ p).

Algorithm:

1. Sort token theo probability giảm dần
2. Cộng dồn từ trên xuống
3. Giữ token cho đến khi cumulative ≥ p
4. Re-normalize phần giữ lại
5. Sample từ subset này

5.3. Ví dụ p = 0.9

Distribution sorted:

Token A: 0.40 (cumul: 0.40)
Token B: 0.25 (cumul: 0.65)
Token C: 0.15 (cumul: 0.80)
Token D: 0.10 (cumul: 0.90) ← reach 0.9, stop
Token E: 0.05 (excluded)
Token F: 0.03 (excluded)
...

Chỉ {A, B, C, D} được sample. Re-normalize:

A: 0.40/0.90 = 0.44
B: 0.25/0.90 = 0.28
C: 0.15/0.90 = 0.17
D: 0.10/0.90 = 0.11

Sample từ 4 token này.

5.4. Adaptive nature — vì sao top-p smart

Khi model confident (1 token dominate):

A: 0.95
B: 0.03
...

Top-p 0.9 → giữ chỉ A. Output deterministic.

Khi model uncertain (distribution flat):

A: 0.10, B: 0.09, C: 0.08, D: 0.07, ..., Z: 0.02

Top-p 0.9 → giữ ~15-20 token. Output diverse.

Top-p tự động điều chỉnh số token dựa theo confidence. Đó là lý do nó “smart” hơn top-k.

5.5. Default values

ProviderDefault top-p
OpenAI1.0 (no nucleus filter)
Anthropic1.0
Default in literature0.9 - 0.95

API providers default top-p = 1 (tắt) vì user thường tune temperature. Set top-p < 1 nếu muốn extra control.


6. Top-k

6.1. Math

Đơn giản hơn top-p: giữ k token xác suất cao nhất.

1. Sort token theo probability
2. Giữ top k tokens
3. Re-normalize
4. Sample

6.2. Ví dụ k = 5

Distribution: [0.40, 0.25, 0.15, 0.10, 0.05, 0.03, 0.02, ...]

Top 5: [A: 0.40, B: 0.25, C: 0.15, D: 0.10, E: 0.05]
Sum: 0.95
Re-normalize: [0.42, 0.26, 0.16, 0.11, 0.05]

6.3. Top-k vs top-p — khi nào dùng cái nào

AspectTop-kTop-p
Adaptive❌ Fixed k✅ Adapt theo confidence
Predictable count✅ Luôn k token❌ Variable
ImplementationĐơn giảnHơi phức tạp
Modern preferenceÍt dùngPhổ biến

Top-p thường tốt hơn vì adaptive. Top-k có 2 trường hợp dùng:

  1. Combine với top-p: filter k trước, sau đó nucleus → tránh edge case top-p giữ quá nhiều token.
  2. Local LLM: llama.cpp, Ollama default top-k = 40.

6.4. Top-k = 1 = greedy

k = 1 → chỉ giữ token max
       → tương đương T = 0

6.5. API support

  • OpenAI: KHÔNG expose top-k (chỉ temperature + top-p).
  • Anthropic: KHÔNG expose top-k.
  • Google Gemini: ✅ expose top-k.
  • Local LLM (Ollama, llama.cpp): ✅ standard.

→ Nếu dùng OpenAI/Claude, bỏ qua top-k. Chỉ liên quan với local LLM hoặc Google.


7. Sampling pipeline hoàn chỉnh

3 mechanics chain với nhau:

LOGITS


Apply temperature (scale logits)


Softmax → probability distribution


Top-k filter (giữ k cao nhất)


Top-p filter (giữ cumulative ≤ p)


Re-normalize


Sample (random theo distribution)


NEXT TOKEN

Mỗi step optional (set k=∞ hoặc p=1.0 để skip).

7.1. Order matters

Default order: temperature → top-k → top-p. Một số implement đảo top-k và top-p — kết quả tương tự nhưng không identical với edge case.

7.2. Cheatsheet thực tế

TaskTTop-pTop-kNote
Code generate01n/aReproducible
Code review0.20.95n/aSlight variation OK
Bug fix0 - 0.11n/aCần chính xác
JSON / structured01n/aStrict format
Translation0.30.9n/aSlight creative cho fluency
Conversational chat0.70.9n/aDefault
Brainstorm idea0.90.95n/aDiverse
Naming, marketing1.00.95n/aCreative
Poetry, fiction1.20.95n/aHigh variance

8. Sampling parameters khác (briefly)

8.1. Min-p (newer, 2023)

Vấn đề top-p: với distribution flat, có thể giữ token rất low (0.01). Min-p set threshold tối thiểu:

min_p = 0.05 → exclude any token < 5% × max_prob

Adaptive theo top probability. Mới nổi trong local LLM scene.

8.2. Frequency penalty

Giảm xác suất token đã xuất hiện nhiều trong output:

new_logit_i = logit_i - penalty × count_in_output(token_i)

Range: -2 đến 2 (OpenAI). Positive → đẩy lùi repetition.

8.3. Presence penalty

Giảm xác suất token đã xuất hiện ít nhất 1 lần (không weight theo count):

new_logit_i = logit_i - penalty × (token_i đã xuất hiện?)

Khuyến khích model đa dạng topic.

8.4. Repetition penalty

Local LLM (llama.cpp). Tương tự frequency nhưng multiplicative:

new_logit_i = logit_i / repetition_penalty  (nếu đã xuất hiện)

Default 1.1-1.2.

8.5. Beam search (alternative cho sampling)

Thay vì sample, explore nhiều path song song:

1. Generate top-k candidate token ở mỗi step
2. Maintain B beams (path)
3. Mỗi beam expand → score
4. Keep top-B
5. Sau N steps, return beam có score cao nhất

Ưu: deterministic, optimal cho fixed scoring. Nhược: chậm B× hơn sampling, output bland (greedy direction).

Hiện tại beam search ít dùng cho text generation (sampling tốt hơn cho fluency). Vẫn dùng cho translation, summarization legacy.


9. Practical examples per platform

9.1. OpenAI API

client.chat.completions.create(
    model="gpt-4o",
    messages=[...],
    temperature=0.7,
    top_p=0.9,
    frequency_penalty=0.3,  # giảm repeat
    presence_penalty=0.0,
    max_tokens=1000,
    seed=42  # reproducible
)

9.2. Anthropic API

client.messages.create(
    model="claude-3-5-sonnet-20241022",
    messages=[...],
    temperature=0.7,
    top_p=0.9,
    top_k=40,  # ✅ Anthropic does support top_k
    max_tokens=1000
)

9.3. Cursor

Cursor abstract parameters. Default settings cho coding (T thấp). Có thể override per chat:

  • Settings → Cursor Settings → Models → expand
  • Chọn temperature nếu cần (advanced)

9.4. Ollama (local)

ollama run llama3.3 \
  --temperature 0.7 \
  --top-p 0.9 \
  --top-k 40 \
  --repeat-penalty 1.1

Hoặc trong Modelfile:

FROM llama3.3
PARAMETER temperature 0.7
PARAMETER top_p 0.9
PARAMETER top_k 40
PARAMETER repeat_penalty 1.1

9.5. llama.cpp

./llama-cli -m model.gguf \
  --temp 0.7 \
  --top-p 0.9 \
  --top-k 40 \
  --repeat-penalty 1.1 \
  --min-p 0.05

10. Debugging output bizarre

10.1. Output too random

Symptom: model nói off-topic, contradict, factually wrong.

Try:

  • Lower temperature: 0.7 → 0.3
  • Lower top-p: 0.95 → 0.8
  • Add system prompt rõ ràng

10.2. Output too repetitive

Symptom: model lặp 1 phrase, “ohhh” ohhh” ohhh”, stuck loop.

Try:

  • Raise temperature: 0.3 → 0.7
  • Increase frequency_penalty: 0 → 0.5
  • Increase repetition_penalty (local): 1.0 → 1.2

10.3. Output too bland

Symptom: response generic, no surprise, “robotic”.

Try:

  • Raise temperature: 0.3 → 0.9
  • Raise top-p: 0.5 → 0.95
  • Increase presence_penalty: encourages new topic

10.4. Output cuts off mid-sentence

Symptom: response stop giữa câu.

Cause:

  • max_tokens quá thấp → tăng
  • Hit stop sequence → check
  • Model genuinely confused → revise prompt

10.5. Output won’t follow JSON format

Symptom: bạn yêu cầu JSON, output prose hoặc broken JSON.

Try:

  • Set temperature = 0 (chính xác hơn)
  • Use response_format={"type": "json_object"} (OpenAI)
  • Use structured output mode (newer API feature)
  • Force prefix: Response (JSON only): {

11. Tổng kết

Tokenization, temperature, top-p, top-k là mechanics nền tảng. Hiểu chúng = control output, debug fluently, không treat LLM như magic.

5 take-aways:

  1. BPE chia text thành subword. Tiếng Việt 2-3x token tiếng Anh tương đương → cost cao hơn.
  2. Temperature scale logits. T=0 deterministic (mostly), T=0.7 default, T>1 creative.
  3. Top-p > Top-k trong hầu hết case (adaptive). OpenAI/Claude không expose top-k.
  4. Pipeline order: temperature → top-k → top-p → sample.
  5. Bizarre output có root cause cụ thể — không phải “AI bị hỏng”.

Cheatsheet quan trọng nhất:

Code task   → T = 0,    top-p = 1
Chat        → T = 0.7,  top-p = 0.9
Brainstorm  → T = 0.9,  top-p = 0.95
Strict JSON → T = 0,    top-p = 1

Khi dev hiểu mechanics, mỗi parameter trở thành lever thay vì “magic number Stack Overflow nói thử”.


Đọc thêm

Reference

  • BPE original — Sennrich et al. 2016 (arxiv.org/abs/1508.07909)
  • “The Curious Case of Neural Text Degeneration” (top-p) — Holtzman et al. 2019
  • Tiktoken — github.com/openai/tiktoken
  • Hugging Face Tokenizers documentation
  • OpenAI API reference: temperature, top_p, frequency_penalty
  • Anthropic API reference: temperature, top_p, top_k