Mục lục
- Mục tiêu bài học
- Tokenization là gì
- Vì sao LLM cần token, không phải chữ raw
- Word-level tokenization
- Character-level tokenization
- Subword tokenization — chuẩn hiện nay
- Ví dụ tokenization thực tế
- Token KHÔNG bằng từ
- Tokenization cho non-English
- Code thử với tiktoken (OpenAI)
- Code thử với HuggingFace tokenizer
- Tokenizer artifact — bug "strawberry"
- Special token: BOS, EOS, PAD, UNK
- Special token cho chat
- Tokenization và context window
- Chi phí API tính bằng token
- Encode và decode reverse
- So sánh tokenizer trên cùng 1 câu
- Pitfall thường gặp
- Bài tập
- Tóm tắt
Mục tiêu bài học
Sau bài học, bạn sẽ:
- Hiểu tokenization là bước đầu tiên trong pipeline xử lý text của LLM và vì sao không thể bỏ qua.
- Phân biệt 3 chiến lược tokenization: word-level, character-level và subword; biết vì sao subword là chuẩn de-facto hiện nay.
- Đếm được token bằng
tiktoken(OpenAI) vàtransformers(HuggingFace). - Hiểu vì sao 1 từ tiếng Việt thường tốn nhiều token hơn 1 từ tiếng Anh tương đương.
- Biết ý nghĩa của các special token
<bos>,<eos>,<pad>,<unk>và các marker chat như<|im_start|>. - Hiểu liên hệ giữa số token với context window và cost khi gọi API LLM.
Bài này là nền cho Bài 3 (BPE detail) và Bài 4 (SentencePiece / WordPiece). Tạm thời chỉ cần hiểu "token là gì, đếm token thế nào" — chưa cần biết thuật toán build vocab.
Tokenization là gì
Tokenization là quá trình chia 1 chuỗi text thành các đơn vị nhỏ gọi là token — đây là đơn vị nhỏ nhất mà LLM xử lý.
Ví dụ với câu "Tokenization is fun!":
- Chia theo từ:
["Tokenization", "is", "fun", "!"]— 4 token. - Chia theo subword (kiểu GPT-4):
["Token", "ization", " is", " fun", "!"]— 5 token. - Chia theo character:
["T", "o", "k", "e", ...]— 20 token.
Mỗi token sau khi chia sẽ được gán 1 integer ID dựa trên vocabulary mà tokenizer được train sẵn. ID này mới là thứ được đưa vào model.
Vì sao LLM cần token, không phải chữ raw
LLM là 1 mạng neural — đầu vào và đầu ra đều là số, không phải ký tự. Quy trình:
- Text → token: dùng tokenizer chia chuỗi.
- Token → integer ID: tra trong vocabulary (token
"hello"có thể là ID15496). - ID → embedding vector: mỗi ID map sang 1 vector chiều dài thường 768 / 1024 / 4096 (sẽ học ở Bài 6).
- Vector → forward pass qua các Transformer block.
Tokenizer là cầu nối giữa text con người và biểu diễn số mà model hiểu được. Cùng 1 model nhưng dùng tokenizer khác → output sai hoàn toàn vì ID không khớp với weight.
Word-level tokenization
Chiến lược đơn giản nhất: mỗi từ = 1 token, chia theo khoảng trắng (whitespace) và dấu câu.
Ưu điểm:
- Trực giác, dễ hiểu — gần với cách con người đọc.
- Mỗi token có ý nghĩa rõ ràng (1 từ = 1 khái niệm).
Nhược điểm:
- Vocabulary rất lớn: tiếng Anh có hơn 1 triệu từ khi tính cả biến thể. Mỗi từ trong vocab cần 1 embedding vector → ngốn nhiều bộ nhớ.
- Out-of-Vocabulary (OOV): từ mới (slang, tên riêng, từ kỹ thuật) không có trong vocab → tokenizer phải gán
<unk>, model mất thông tin. - Biến thể spelling:
run,runs,running,ranbị coi là 4 token hoàn toàn khác nhau — model không biết chúng có gốc chung. - Không xử lý được dấu nối, ngôn ngữ không có khoảng trắng (tiếng Trung, Nhật).
Word-level từng phổ biến với các model NLP đời trước (word2vec, GloVe). LLM hiện nay không dùng nữa.
Character-level tokenization
Chiến lược ngược lại: mỗi ký tự = 1 token.
Ưu điểm:
- Vocab rất nhỏ — khoảng 100-300 ký tự nếu chỉ tính ASCII + dấu, hoặc vài nghìn nếu thêm Unicode.
- Không có OOV — mọi text đều có thể biểu diễn được.
- Xử lý được sai chính tả, tên riêng, language mới mà không cần retrain vocab.
Nhược điểm:
- Sequence dài: 1 câu 100 từ ≈ 500-700 ký tự → sequence length gấp ~5-7 lần so với word-level. Attention có độ phức tạp \( O(n^2) \) theo sequence length nên cost tăng nhanh.
- Mỗi token ít ngữ nghĩa — model phải "tự học" gộp các ký tự lại thành từ. Cần nhiều layer và data hơn để đạt cùng chất lượng.
Character-level vẫn được dùng ở 1 số model nhỏ và pre-tokenization step, nhưng không phải mainstream cho LLM.
Subword tokenization — chuẩn hiện nay
Subword = mỗi token là 1 đoạn ngắn (sub-word piece), thường ngắn hơn 1 từ và dài hơn 1 ký tự. Đây là cách cân bằng giữa 2 thái cực ở trên.
Ý tưởng: các từ phổ biến (the, and, hello) giữ nguyên 1 token; các từ ít gặp hoặc dài bị tách thành nhiều subword (unbelievable → un + believ + able).
Ưu điểm:
- Vocab hợp lý: khoảng 10K–100K token (GPT-4 dùng 100,277).
- Không có OOV — từ chưa thấy bao giờ vẫn tách được thành các subword đã có.
- Capture được morphology:
play,playing,playedshare tokenplay. - Sequence length vừa phải — ngắn hơn character, dài hơn word một chút.
4 thuật toán chính (chi tiết ở Bài 3-4):
- BPE (Byte-Pair Encoding) — GPT-2/3/4, Llama. Sẽ deep dive ở Bài 3.
- WordPiece — BERT, DistilBERT. Bài 4.
- SentencePiece — T5, mBART, LLaMA tokenizer cũ. Bài 4.
- Unigram — XLNet, ALBERT. Variant của SentencePiece.
Ví dụ tokenization thực tế
Với tokenizer subword kiểu GPT, các ví dụ phổ biến:
"playing" → ["play", "ing"]
"unbelievable" → ["un", "believ", "able"]
"tokenization" → ["token", "ization"]
"GPT-4" → ["G", "PT", "-", "4"]
"Hello, world!" → ["Hello", ",", " world", "!"]
Lưu ý ở câu cuối: GPT-style tokenizer xử lý khoảng trắng đứng trước từ như 1 phần của token (" world" chứ không phải "world"). Đây là khác biệt với BERT-style — BERT dùng prefix ## cho subword nối tiếp.
Bảng so sánh ngắn:
Câu GPT-4 (BPE) BERT (WordPiece)
"unbelievable" un, bel, ievable un, ##bel, ##ie, ##va, ##ble
"playing" play, ing play, ##ing
"hello world" hello, world hello, world
Cách chia phụ thuộc vocab đã train, nên cùng 1 từ qua 2 tokenizer khác nhau ra kết quả khác hẳn.
Token KHÔNG bằng từ
Đây là hiểu lầm phổ biến nhất khi mới làm việc với LLM. Token và từ không phải là 1:
- 1 từ có thể tách thành nhiều token (đặc biệt với từ hiếm, tên riêng, language khác).
- 1 token có thể chỉ là dấu câu, khoảng trắng, hay 1 ký tự đơn lẻ.
- 1 token có thể chứa cả khoảng trắng đứng trước (
" the").
Quy tắc rule-of-thumb của OpenAI cho tiếng Anh:
1,000 token ≈ 750 từ
1 token ≈ 4 ký tự (tiếng Anh)
Quy tắc này CHỈ đúng cho tiếng Anh và prose tự nhiên. Với code, JSON, hay tiếng Việt thì tỷ lệ khác hẳn (xem mục tiếp).
Tokenization cho non-English
Tokenizer của các model phổ biến (GPT, Claude, Llama) được train trên corpus thiên về tiếng Anh, nên ngôn ngữ khác bị thiệt thòi:
Tiếng Việt:
- Diacritics (dấu thanh, dấu mũ) khiến mỗi âm tiết bị chia thành nhiều byte UTF-8 → nhiều token hơn.
- Từ ghép ("công nghệ thông tin") thường bị chia rời từng âm tiết thay vì gộp.
- Trung bình 1 từ tiếng Việt ≈ 2-3 token GPT-4 (so với ~1.3 token cho từ tiếng Anh).
CJK (Chinese, Japanese, Korean):
- Không có khoảng trắng giữa từ → tokenizer dựa hoàn toàn vào subword/byte-level.
- Mỗi ký tự Hán/Kana thường chiếm 1-2 token (vì UTF-8 cần 3 byte cho mỗi ký tự CJK).
Hệ quả thực tế: cùng 1 nội dung, prompt tiếng Việt hoặc tiếng Trung tốn 2-3 lần token so với tiếng Anh. Ngân sách API và context window đều bị "đắt" hơn cho language khác — đây là 1 dạng bias inherent của tokenizer.
Code thử với tiktoken (OpenAI)
tiktoken là tokenizer chính thức của OpenAI (cài: pip install tiktoken).
import tiktoken
enc = tiktoken.encoding_for_model("gpt-4")
text = "Hello, world!"
tokens = enc.encode(text)
print(tokens) # [9906, 11, 1917, 0]
print(len(tokens)) # 4
# Decode ngược
decoded = enc.decode(tokens)
print(decoded) # "Hello, world!"
# Decode từng token để xem hình thù
for tid in tokens:
print(tid, repr(enc.decode([tid])))
# 9906 'Hello'
# 11 ','
# 1917 ' world'
# 0 '!'
Mỗi encoding_for_model map sang 1 vocab cụ thể. GPT-4 và GPT-3.5-turbo dùng cl100k_base (100,277 token); GPT-4o dùng o200k_base (~200K token, gộp tiếng non-English tốt hơn).
Code thử với HuggingFace tokenizer
Với model open-source (Llama, Mistral, BERT...), dùng transformers:
from transformers import AutoTokenizer
# Llama-3 tokenizer (cần access token HF nếu model gated)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B")
text = "Hello, world!"
ids = tokenizer.encode(text)
print(ids) # [128000, 9906, 11, 1917, 0]
print(tokenizer.convert_ids_to_tokens(ids))
# ['<|begin_of_text|>', 'Hello', ',', ' world', '!']
# Decode lại (giữ special token để thấy)
print(tokenizer.decode(ids, skip_special_tokens=False))
# "<|begin_of_text|>Hello, world!"
Để ý: AutoTokenizer tự thêm <|begin_of_text|> (BOS token của Llama-3) ở đầu. tiktoken thì không tự thêm BOS — bạn phải tự xử lý khi format chat.
Tokenizer artifact — bug "strawberry"
Câu hỏi "How many R's are in 'strawberry'?" từng làm GPT-4 trả lời sai (nói có 2 R, thực ra là 3). Nguyên nhân không phải model "ngu" — mà do tokenizer.
Với cl100k_base:
enc.encode("strawberry")
# [56664, 15717] → [' straw', 'berry']
Model "nhìn" thấy [straw, berry] chứ không thấy [s, t, r, a, w, b, e, r, r, y]. Đếm ký tự bên trong 1 token không phải là phép toán mà model thực hiện natively — nó phải dựa vào kiến thức học được về spelling của từng từ.
Các task tương tự gặp khó vì cùng lý do:
- Reverse 1 chuỗi.
- Đếm số ký tự / nguyên âm.
- Phép toán với số có nhiều chữ số (tokenizer có thể chia
"12345"thành[123, 45]hoặc thành từng số).
Bài học: khi cần thao tác ký tự, nên cho LLM gọi tool (Python interpreter) thay vì làm trực tiếp.
Special token: BOS, EOS, PAD, UNK
Ngoài token nội dung, vocab còn chứa các special token dùng làm marker:
<bos>(Beginning Of Sequence): đánh dấu đầu chuỗi. Llama-3 dùng<|begin_of_text|>.<eos>(End Of Sequence): tín hiệu kết thúc. Model học khi nào nên generate token này để dừng.<pad>(Padding): nhét cho các sequence ngắn đủ chiều dài khi batch — model phải mask để không attend vào pad.<unk>(Unknown): fallback cho ký tự / byte không có trong vocab. Với tokenizer byte-level (BPE của GPT),<unk>gần như không xuất hiện vì mọi byte đều biểu diễn được.
Convention đặt tên thay đổi theo model: <s> (RoBERTa), [CLS]/[SEP] (BERT), <|endoftext|> (GPT-2/3), <|eot_id|> (Llama-3). Đọc tokenizer.special_tokens_map để biết chính xác.
Special token cho chat
Model chat (instruction-tuned) thêm các token đánh dấu role:
- ChatML (OpenAI, Qwen):
<|im_start|>user\n...<|im_end|>rồi<|im_start|>assistant. - Llama-3:
<|start_header_id|>user<|end_header_id|>\n\n...<|eot_id|>. - Mistral / Llama-2:
[INST] ... [/INST]. - Gemma:
<start_of_turn>user\n...<end_of_turn>.
Sai format chat = model output rác. Lý do nên dùng tokenizer.apply_chat_template() của HuggingFace thay vì tự ghép string:
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello!"},
]
prompt = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
print(prompt)
Hàm này tự lấy đúng template gắn với model — không cần nhớ marker của từng dòng họ.
Tokenization và context window
Context window — số lượng token tối đa model xử lý được trong 1 lượt — luôn đo bằng token, không phải từ hay ký tự.
Một số mốc thường gặp (2025):
- GPT-4 / GPT-4 Turbo / GPT-4o: 128K token.
- GPT-4.1: 1M token.
- Claude Sonnet 4 / Opus 4: 200K standard, 1M cho bản context dài.
- Gemini 1.5 / 2.0 Pro: 1M-2M token.
- Llama 3 (base): 8K. Llama 3.1 (long-context fine-tune): 128K.
Khi tính budget context, luôn quy đổi qua token, không qua từ. 1 file PDF 50 trang tiếng Việt có thể chạm 30-40K token dễ dàng — chia nhỏ khi cần (sẽ học chunking ở module RAG).
Context window deep dive: xem Bài 7.
Chi phí API tính bằng token
Các API LLM (OpenAI, Anthropic, Google) tính tiền theo công thức:
cost = input_tokens × price_in + output_tokens × price_out
Một số điểm cần nhớ:
- Giá output luôn đắt hơn input (thường gấp 3-5 lần) vì generate token tốn compute hơn process.
- System prompt được tính vào input mỗi request — system prompt dài 2K token gửi 1000 lần = 2M token tính tiền.
- Prompt caching (OpenAI, Anthropic) giảm giá phần input lặp lại — vẫn tính theo token.
- Với task lặp đi lặp lại, ước lượng token trước khi gọi API bằng
len(enc.encode(prompt))để tránh bill bất ngờ.
Tối ưu cost chi tiết: Bài 34.
Encode và decode reverse
Tokenizer là map bi-directional: text ↔ token ID.
- Encode:
text → list of IDs— dùng khi chuẩn bị input cho model. - Decode:
list of IDs → text— dùng khi đọc output từ model.
Với tokenizer byte-level (BPE của GPT, Llama), encode rồi decode lại phải luôn ra đúng text gốc (lossless). Tokenizer WordPiece (BERT) có thể không lossless vì lowercase, strip accent.
text = "Tôi học AI"
ids = enc.encode(text)
back = enc.decode(ids)
assert back == text # OK với cl100k_base / o200k_base
Lưu ý: khi decode mảng IDs có chứa special token, mặc định nhiều tokenizer sẽ strip chúng. Truyền skip_special_tokens=False nếu muốn debug.
So sánh tokenizer trên cùng 1 câu
Đoạn code dưới đây tokenize 1 câu tiếng Anh và 1 câu tiếng Việt bằng 3 tokenizer khác nhau, in số token:
import tiktoken
from transformers import AutoTokenizer
gpt4 = tiktoken.encoding_for_model("gpt-4")
llama = AutoTokenizer.from_pretrained("meta-llama/Meta-Llama-3-8B")
bert = AutoTokenizer.from_pretrained("bert-base-uncased")
sentences = [
"Tokenization is the first step in any LLM pipeline.",
"Tokenization là bước đầu tiên trong mọi pipeline của LLM.",
]
for s in sentences:
print(f"\nText: {s}")
print(f" GPT-4 : {len(gpt4.encode(s))} tokens")
print(f" Llama-3: {len(llama.encode(s, add_special_tokens=False))} tokens")
print(f" BERT : {len(bert.encode(s, add_special_tokens=False))} tokens")
Output điển hình:
Text: Tokenization is the first step in any LLM pipeline.
GPT-4 : 11 tokens
Llama-3: 11 tokens
BERT : 11 tokens
Text: Tokenization là bước đầu tiên trong mọi pipeline của LLM.
GPT-4 : 22 tokens
Llama-3: 20 tokens
BERT : 28 tokens
Câu tiếng Việt dài gấp ~2 lần câu tiếng Anh tương đương khi đo bằng token — confirm bias đã nói ở Bước 9. BERT tệ nhất vì vocab gốc gần như chỉ tiếng Anh, mọi ký tự Việt rơi vào subword UTF-8 ngắn.
Pitfall thường gặp
- Quên BOS / EOS khi format chat: nhiều base model (Llama, Mistral) bắt buộc có BOS ở đầu — quên thì output đầu tiên sẽ lệch. Dùng
apply_chat_template()để tránh. - Trộn tokenizer khác model: gọi
Llama-3-8Bvới tokenizer củaLlama-2= output garbage. Mỗi model phải đi với tokenizer của chính nó. - Coi 1 từ = 1 token khi tính cost / context — thường underestimate 30-50% cho tiếng Anh và gấp đôi cho tiếng Việt.
- Ước lượng độ dài bằng
len(string): chỉ dùng cho draft thô. Quyết định thực tế (cắt context, batch) phải dùnglen(tokenizer.encode(text)). - Special token bị strip khi log:
decodemặc định ẩn<|eot_id|>, dễ debug sai. Truyềnskip_special_tokens=Falsekhi cần raw. - Tokenize sai whitespace: GPT tokenizer phân biệt
"world"và" world"— copy-paste prompt có thể tạo ra IDs khác bản gốc.
Bài tập
Bài 1: Cài tiktoken, encode chuỗi "Hello, AI Engineer!" với model gpt-4. In ra list IDs và số token. Decode từng ID để xem token thực sự là gì.
Bài 2: Tokenize 2 từ "tokenization" và "tokenize" bằng cl100k_base. Chúng có share prefix token không? Vì sao?
Bài 3: Lấy 1 đoạn 20 từ tiếng Việt (ví dụ 1 câu trong sách giáo khoa), tokenize bằng GPT-4 và Llama-3. So với 1 câu tiếng Anh tương đương — tỷ lệ token / từ khác nhau bao nhiêu?
Bài 4 (gợi ý): Viết function estimate_cost(prompt, n_output_tokens, price_in, price_out) nhận text prompt, ước lượng tổng cost (USD) cho 1 lần gọi API GPT-4o. Test với prompt 500 từ và 200 token output.
Tóm tắt
- Tokenization là bước chia text thành token — đơn vị nhỏ nhất LLM xử lý. Sau đó token → integer ID → embedding vector.
- 3 chiến lược: word-level (vocab khổng lồ, OOV), character-level (sequence dài), subword (cân bằng — chuẩn hiện nay).
- Subword có 4 thuật toán chính: BPE, WordPiece, SentencePiece, Unigram (chi tiết Bài 3-4).
- Token ≠ từ. Quy đổi tham khảo: 1000 token ≈ 750 từ tiếng Anh; tiếng Việt tốn ~2 lần token cho cùng nội dung.
- Special token:
<bos>,<eos>,<pad>,<unk>và các marker chat (<|im_start|>,<|eot_id|>) phụ thuộc model. - Context window và cost API đều tính theo token — luôn ước lượng qua
tokenizer.encode(), không phảilen(string). - Bug "đếm R trong strawberry" là tokenizer artifact, không phải lỗi reasoning. Cần thao tác ký tự → cho LLM gọi tool.
- GitHub - openai/tiktoken
- OpenAI Tokenizer Playground
- HuggingFace Docs - Summary of the tokenizers
- HuggingFace NLP Course - Chapter 6: The Tokenizers library
- HuggingFace Docs - Tokenizer class
- Sennrich et al. 2015 - Neural Machine Translation of Rare Words with Subword Units (BPE)
- Kudo & Richardson 2018 - SentencePiece
- OpenAI Docs - Managing tokens
- Wikipedia - Byte pair encoding
- Wikipedia - Lexical analysis (tokenization)
