$ cat ./blog/gen-ai/llm-fundamentals/tokenization-trong-llm.md
seriesCách hoạt động của LLMbài 4

Tokenization trong LLM: từ văn bản con người đọc được đến những con số model xử lý được

Bài thứ tư trong series Cách hoạt động của LLM: token thực sự là gì, tokenizer pipeline gồm những bước nào, BPE - WordPiece - Unigram - SentencePiece khác nhau ra sao, vì sao không thể tự ý đổi tokenizer của một pretrained model, và tokenization tiếng Việt khó ở chỗ nào.

~/diagrams/tokenization-trong-llm/tokenizer-pipeline.htmltương tác
mở riêng ↗
HìnhHàng trên là đường đi của văn bản: chuẩn hoá Unicode, cắt sơ bộ, cắt subword, rồi tra vocabulary thành dãy số nguyên trước khi embedding lookup biến chúng thành vector. Hàng dưới là những thứ quyết định kết quả của từng chặng - biến thể Unicode ở đầu vào, ranh giới mà pre-tokenizer đặt ra, bảng vocabulary đã đóng băng, và các special token được chèn thêm.
đường đi của văn bảnvocabulary và token điều khiểnthứ quyết định kết quả

Bài đầu tiên mở bên trong một Transformer. Bài thứ hai đi từ ngoài vào qua pipeline() và các engine phục vụ inference. Bài thứ ba nói về việc thay đổi những gì model biết.

Cả ba bài đó đều bắt đầu từ một chỗ giống nhau: văn bản đã thành số rồi. Bài này quay lại đúng bước bị bỏ qua đó.

Khi ta gõ vào một chatbot:

Tôi đang tìm hiểu cách Large Language Model hoạt động.

thứ đi vào mạng neural không phải câu chữ theo cách con người nhìn thấy nó. Transformer không có một cổng đầu vào nhận kiểu string rồi tự “đọc” tiếng Việt. Trước khi bất kỳ attention head nào hoạt động, văn bản phải được biến thành một dãy ký hiệu rời rạc, sau đó thành một dãy số nguyên.

Sơ đồ ở đầu bài (Hình 1) là toàn bộ con đường đó. Nếu chỉ nhìn tokenizer như một hàm text -> list[int], ta bỏ lỡ gần như toàn bộ những quyết định kỹ thuật quan trọng nằm phía sau nó.

Tokenizer quyết định:

  • một đơn vị ngôn ngữ bị chia nhỏ tới mức nào;
  • vocabulary của model gồm những gì;
  • một đoạn văn chiếm bao nhiêu vị trí trong context window;
  • một ngôn ngữ có bị “đánh thuế token” nhiều hơn ngôn ngữ khác hay không;
  • source code, số, emoji, tên riêng và từ hiếm được biểu diễn ra sao;
  • embedding table cần bao nhiêu hàng;
  • và trong nhiều hệ thống thương mại, cùng một lượng thông tin sẽ tốn bao nhiêu tiền để xử lý.

Tokenization không phải một chi tiết phụ nằm ngoài model. Nó là giao diện rời rạc giữa ngôn ngữ và mạng neural.


01Model thực sự nhìn thấy gì khi ta nhập văn bản?

Giả sử ta có câu:

I love language models.

Một tokenizer có thể biến nó thành:

["I", " love", " language", " models", "."]

Sau đó mỗi token được ánh xạ sang một số nguyên trong vocabulary:

[40, 3021, 4221, 4211, 13]

Model không xem 40 là “lớn hơn” 13 theo nghĩa ngữ nghĩa. Các số này chỉ là index.

Token Token ID
"I" 40
" love" 3021
" language" 4221
" models" 4211
"." 13

Embedding layer dùng ID làm chỉ mục vào một ma trận. Nếu gọi E là embedding matrix có kích thước |V| × d, với |V| là vocabulary size và d là hidden dimension, thì vector đầu vào của token thứ i đơn giản là hàng thứ token_id[i] của E.

Đây là một phân biệt nền tảng và rất hay bị bỏ qua:

Token       = ký hiệu rời rạc
Token ID    = địa chỉ của ký hiệu đó trong vocabulary
Embedding   = vector số thực mà model đã học cho token đó

Token ID không phải embedding. Nếu token "cat" có ID 4281, số 4281 tự nó không mang ý nghĩa “con mèo”. Nó chỉ nói với model: hãy lấy hàng thứ 4281 trong embedding table.

1.1 Token không đồng nghĩa với từ

Đây là hiểu lầm phổ biến nhất. Một token có thể là:

  • cả một từ: hello;
  • một phần của từ: ing, ization;
  • dấu cách đi kèm từ: " world";
  • dấu câu: .;
  • một byte hoặc một chuỗi byte;
  • một ký hiệu điều khiển đặc biệt;
  • hoặc một chuỗi không trùng với bất kỳ ranh giới từ nào theo trực giác con người.

Trong tiktoken của OpenAI, một ví dụ chính thức cho encoding cl100k_base là:

"hello world"  →  [15339, 1917]

và hai token đó decode ra các byte gần với:

b'hello'
b' world'

Dấu cách trước world đã trở thành một phần của token thứ hai. Điều này gợi ra một nguyên tắc đáng nhớ:

Tokenizer tối ưu một biểu diễn rời rạc hữu dụng cho model, chứ không có nghĩa vụ tái tạo khái niệm “từ” theo trực giác ngôn ngữ học của con người.


02Bên trong một tokenizer: không chỉ có thuật toán BPE

Một tokenizer production thường là một pipeline, không phải một hàm duy nhất. Hình 1 ở đầu bài đã vẽ đủ bốn chặng; phần này giải thích vì sao từng chặng lại quan trọng.

2.1 Normalization: hai chuỗi trông giống nhau có thể khác nhau ở cấp code point

Unicode cho phép các ký tự hiển thị giống hệt nhau được mã hoá bằng những chuỗi code point khác nhau: một ký tự có dấu dựng sẵn, hoặc một ký tự cơ sở cộng thêm combining mark.

Unicode định nghĩa bốn normalization form chính:

Form Nghĩa
NFC canonical decomposition rồi composition lại
NFD canonical decomposition
NFKC compatibility decomposition rồi composition lại
NFKD compatibility decomposition

Vì sao tokenizer quan tâm? Vì nếu hai chuỗi hiển thị tương đương nhưng byte khác nhau, chúng sẽ bị tokenize khác nhau nếu pipeline không chuẩn hoá nhất quán.

Nhưng normalization cũng không phải càng mạnh càng tốt. Unicode cảnh báo rằng NFKC và NFKD có thể xoá đi những khác biệt compatibility mang ý nghĩa về hình thức, nên không nên áp dụng mù quáng cho mọi loại text. Lowercase cũng vậy: Apple → apple làm vocabulary nhỏ hơn nhưng đánh mất distinction hoa - thường.

Normalization là một quyết định mô hình hoá, không chỉ là “dọn dữ liệu”.

2.2 Pre-tokenization: đặt ranh giới trước khi subword algorithm chạy

Một tokenizer thường chia thô trước:

Hello, world!
→ "Hello"  ","  " world"  "!"

rồi mới chạy BPE hoặc WordPiece bên trong từng piece.

Quy tắc pre-tokenization có tác động lớn hơn vẻ ngoài của nó. Nếu ta không cho phép merge vượt qua một ranh giới nào đó, thì vocabulary học được sau này cũng bị giới hạn bởi chính ranh giới đó. tiktoken dùng một regex pattern để tách input thành piece trước khi áp dụng byte-pair encoding lên bytes của từng piece. WordPiece kiểu BERT cũng pre-tokenize thành từ rồi mới chạy maximum matching bên trong từng từ.

2.3 Post-processing: thêm cấu trúc mà raw text không có

Sau segmentation, pipeline có thể chèn các token đặc biệt:

<BOS>  <EOS>  [CLS]  [SEP]  [MASK]  <|user|>  <|assistant|>

Những token này thường không xuất hiện nguyên văn trong input của người dùng. Chúng tồn tại để truyền cấu trúc cho model, và sẽ quay lại ở phần 6.


03Bài toán cốt lõi: chia văn bản nhỏ tới mức nào?

Tokenization thực chất là một bài toán chọn độ hạt - granularity. Có bốn mức dễ hình dung, và không mức nào đúng tuyệt đối: mỗi lựa chọn đổi một loại chi phí lấy một loại lợi ích khác.

~/diagrams/tokenization-trong-llm/token-granularity.htmltương tác
mở riêng ↗
HìnhĐi từ trái sang phải là chia ngày càng nhỏ. Làn dưới là cái giá ở hai đầu: word-level làm vocabulary nổ và sinh ra OOV, còn character hoặc byte thuần làm sequence dài ra và kéo theo chi phí attention. Cả hai đường đứt nét đều quay về vùng cân bằng ở giữa - đó chính là chỗ subword đứng.
các mức độ hạtvùng cân bằngcái giá và đường quay lại

3.1 Word-level: trực quan nhưng vocabulary nổ rất nhanh

Cách cổ điển nhất là tách theo từ. Sequence ngắn, mỗi token gần với một lexical unit, dễ diễn giải. Nhưng vocabulary phình ra rất nhanh - chỉ riêng một họ từ đã có nhiều biến thể:

run  runs  running  runner  runners

Tên riêng, lỗi chính tả, từ ghép, thuật ngữ khoa học, URL, mã nguồn và ngôn ngữ hình thái phong phú còn làm vấn đề tệ hơn. Nếu một từ không có trong vocabulary, hệ thống word-level truyền thống phải dùng <UNK> - đó là out-of-vocabulary. Hai từ hoàn toàn khác nhau có thể cùng bị nén thành một <UNK>, tức là thông tin bề mặt đã mất trước khi model bắt đầu tính toán.

3.2 Character-level: gần như hết OOV, nhưng sequence dài

Vocabulary nhỏ hơn nhiều và model nhìn thấy trực tiếp chính tả từng ký tự. Nhưng một câu 100 ký tự tạo ra xấp xỉ 100 đơn vị thay vì vài chục subword.

Với self-attention chuẩn, thành phần attention pairwise có độ phức tạp thời gian bậc hai theo sequence length. Đó là một trong các lý do mô hình character-level phải trả giá cho khả năng biểu diễn chi tiết hơn.

3.3 Byte-level: vocabulary nền cực nhỏ, coverage rất mạnh

UTF-8 biểu diễn văn bản thành byte, và mỗi byte chỉ có 256 giá trị khả dĩ. Một kiến trúc thật sự byte-level có thể bắt đầu từ vocabulary khoảng 256 đơn vị cơ bản thay vì hàng chục nghìn token.

Điểm mạnh: hầu như không có OOV ở cấp byte, biểu diễn được Unicode, emoji, code và chuỗi lạ, không cần liệt kê mọi ký tự Unicode vào vocabulary cơ sở. Nhược điểm: văn bản đa byte có thể trở thành sequence rất dài.

3.4 Subword: điểm cân bằng thực dụng

Subword đặt mình giữa hai cực:

unbelievable  →  ["un", "believ", "able"]
tokenization  →  ["token", "ization"]

Ý tưởng rất mạnh: chuỗi thường gặp được giữ thành token lớn, chuỗi hiếm bị phân rã thành đơn vị nhỏ hơn, và một vocabulary hữu hạn vẫn biểu diễn được không gian từ gần như mở. Công trình của Sennrich, Haddow và Birch đã phổ biến mạnh BPE subword trong neural machine translation, với động cơ trực tiếp là xử lý từ hiếm và bài toán open vocabulary.

Một cách nhìn hữu ích:

Word-level      : memorization nhiều, sequence ngắn
Subword-level   : cân bằng
Character/byte  : composition nhiều, sequence dài

Subword không đảm bảo các mảnh luôn là morpheme “đúng” về ngôn ngữ học. Nó học từ phân phối dữ liệu. Một token trông có nghĩa như ing là kết quả thuận lợi, không phải điều kiện bắt buộc.


04BPE: học vocabulary một lần, encode rất nhiều lần

BPE bắt nguồn từ một ý tưởng compression: tìm các cặp ký hiệu liền kề xuất hiện thường xuyên và merge chúng lại.

Giả sử corpus toy là low, lower, lowest. Ta bắt đầu ở mức ký tự:

l o w
l o w e r
l o w e s t

Đếm các cặp liền kề, thấy (l, o) là cặp ưu tiên, merge:

lo w
lo w e r
lo w e s t

Sau đó merge tiếp (lo, w):

low
low e r
low e s t

Quá trình lặp cho tới khi đạt số merge hoặc vocabulary size mong muốn:

vocab = base_symbols

while len(vocab) < target_vocab_size:
    pair_counts = count_adjacent_pairs(corpus)
    best_pair = select_best_pair(pair_counts)
    corpus = merge(corpus, best_pair)
    vocab.add(concat(best_pair))

Điều quan trọng nhất ở đây: training BPE và encoding bằng BPE là hai pha hoàn toàn khác nhau.

~/diagrams/tokenization-trong-llm/bpe-train-vs-encode.htmltương tác
mở riêng ↗
HìnhLàn trên là vòng lặp train: đếm cặp, chọn cặp thắng, merge, rồi quay lại đếm cho tới khi đủ vocabulary size - chạy đúng một lần trên toàn corpus. Làn dưới là encode một câu mới: chỉ đưa về ký hiệu nền rồi replay lại danh sách merge đã học theo đúng thứ hạng, không đếm lại gì cả.
vòng lặp train tokenizervocabulary đã đóng băngđường encode mỗi lần gọi

Sau training, tokenizer không cần đếm lại toàn bộ corpus mỗi lần encode một câu. Nó chỉ áp dụng vocabulary và merge priority đã học lên input mới. Trong triển khai educational của tiktoken, input được đổi sang UTF-8 bytes, rồi BPE bắt đầu từ từng byte và liên tục merge theo rank đã học cho đến khi không còn merge hợp lệ.

Đây cũng là lý do file tokenizer lưu cả danh sách merges chứ không chỉ danh sách token: mất thứ hạng merge là mất khả năng tái lập kết quả tokenize.

4.1 BPE là compression, nhưng compression không phải toàn bộ câu chuyện

BPE có xu hướng dành token riêng cho các chuỗi thường gặp, khiến sequence ngắn hơn. Chuỗi ing xuất hiện rất nhiều nên có cơ hội trở thành một token. Nhưng BPE không biết ing là hậu tố tiếng Anh theo nghĩa ngôn ngữ học - nó chỉ biết chuỗi đó có ích theo thống kê của dữ liệu huấn luyện tokenizer.

Do vậy BPE có thể tạo ra token trùng morpheme đẹp, token là cả từ, token là fragment khó diễn giải, token rất hiệu quả với domain được thấy nhiều, và token kém hiệu quả với domain ít xuất hiện.

4.2 Byte-level BPE: giải quyết coverage ở cấp byte

BPE cổ điển bắt đầu từ character hoặc symbol. Byte-level BPE thay nền tảng đó bằng bytes, nên input bất kỳ đều biểu diễn được mà không cần đưa từng emoji hay từng chữ tượng hình vào vocabulary nền. tiktoken mô tả BPE của nó là reversible và lossless ở tầng biểu diễn byte, xử lý được arbitrary text, và nén chuỗi byte thành ít token hơn.

Đây là một khác biệt sâu sắc giữa hai tình huống:

"ký tự này không có trong vocabulary"         ← word/char-level
"không có token lớn nào khớp, lùi xuống byte" ← byte-level

Byte-level BPE làm OOV theo nghĩa truyền thống gần như biến mất ở tầng raw text. Tuy nhiên, coverage tuyệt đối không đồng nghĩa segmentation tối ưu: một ngôn ngữ có thể luôn encode được nhưng vẫn bị chia thành quá nhiều token. Vấn đề này sẽ quay lại ở phần 8.


05WordPiece, Unigram và chỗ đứng thật của SentencePiece

Khi so sánh các thuật toán subword, nên tách rõ hai câu hỏi:

  1. Tokenizer học vocabulary như thế nào?
  2. Khi gặp một string mới, tokenizer chọn segmentation như thế nào?

Hai câu hỏi này không phải một, và phần lớn nhầm lẫn đến từ việc gộp chúng lại.

~/diagrams/tokenization-trong-llm/subword-three-ways.htmltương tác
mở riêng ↗
HìnhCùng một chuỗi đi vào ba cơ chế khác nhau: BPE replay lại thứ hạng merge đã học, WordPiece lấy prefix dài nhất có trong vocabulary rồi đi tiếp, Unigram chấm điểm mọi đường đi qua lattice và chọn đường tốt nhất. Cột phải cho thấy kết quả điển hình - hai cơ chế đầu deterministic, còn Unigram có thể sample ra cách cắt khác.
chuỗi đầu vào chungcơ chế quyết địnhkết quả segmentation

5.1 WordPiece: longest match trước một vocabulary cố định

WordPiece cũng là subword tokenization, nhưng khi encode một từ, cách triển khai điển hình dùng greedy longest-match-first, còn gọi là MaxMatch.

Giả sử vocabulary chứa play, ##ing, ##er, ##s và input là playing. Tokenizer thử lấy prefix dài nhất có trong vocabulary:

playing   ✗
playin    ✗
play      ✓

Rồi tiếp tục với phần còn lại: ing → ##ing ✓. Kết quả là ["play", "##ing"]. Ký hiệu ## biểu thị token tiếp nối bên trong một word, không phải chuỗi đứng ở đầu từ.

Một nuance quan trọng: tài liệu hiện đại mô tả rất rõ thuật toán inference của WordPiece, trong khi lịch sử và các biến thể training WordPiece không được chuẩn hoá thành một công thức đơn giản như “merge cặp có frequency cao nhất” của BPE. Khi giải thích WordPiece, an toàn nhất là tách vocabulary learning khỏi MaxMatch tokenization, thay vì nói nó chỉ là “BPE với công thức score khác”.

5.2 Unigram: bắt đầu lớn rồi loại dần

Unigram đi theo trực giác gần như ngược BPE:

BPE      : đơn vị nhỏ → merge dần → vocabulary lớn hơn
Unigram  : candidate vocabulary lớn → ước lượng xác suất → loại token ít hữu ích

Với chuỗi tokenizer, nhiều segmentation đều hợp lệ:

["token", "izer"]
["tok", "en", "izer"]
["tokenize", "r"]

Unigram gán xác suất cho từng token, và xác suất của một segmentation là tích xác suất các token trong đó - hay tổng log-xác suất nếu làm việc trong log-space. Mỗi đường đi từ đầu đến cuối trong một lattice là một cách tokenize, và dynamic programming kiểu Viterbi tìm đường có score tốt nhất.

Khả năng có nhiều segmentation còn mở ra subword regularization: trong training, thay vì luôn dùng đúng một cách cắt, ta sample nhiều cách phân đoạn để model bớt phụ thuộc vào một ranh giới subword cố định. Công trình Unigram của Kudo nghiên cứu trực tiếp ý tưởng này.

5.3 SentencePiece không phải “thuật toán thứ tư”

SentencePiece thường bị liệt kê cùng BPE, WordPiece và Unigram như thể bốn thứ ngang hàng. Cách nói này gây nhầm lẫn.

SentencePiece là một framework/toolkit có thể triển khai BPE hoặc Unigram, đồng thời hỗ trợ training trực tiếp từ raw sentences. Một thiết kế đáng chú ý là cách nó giữ whitespace như một symbol biểu diễn được bằng ký hiệu :

I saw a girl
→ ▁I ▁saw ▁a ▁girl

Nhờ vậy thông tin boundary của khoảng trắng được giữ trong chuỗi token thay vì bị vứt đi như một delimiter. Điều này đặc biệt hữu ích với các hệ chữ mà khoảng trắng không phải word boundary đáng tin cậy - chuyện sẽ rất liên quan tới tiếng Việt ở phần 9.

5.4 So sánh nhanh

Thuộc tính BPE WordPiece Unigram SentencePiece
Bản chất họ thuật toán merge subword họ tokenizer subword, inference thường MaxMatch probabilistic subword model framework/toolkit
Hướng học nhỏ → merge lớn học vocabulary rồi MaxMatch khi encode candidate lớn → prune dần dùng BPE hoặc Unigram
Chọn segmentation theo merge rank longest-match-first chọn hoặc sample đường có probability tuỳ model bên dưới
OOV phụ thuộc base symbols, byte-BPE rất mạnh có thể có [UNK] nếu không cover thường thiết kế để cover qua pieces nhỏ hỗ trợ raw Unicode
Segmentation ngẫu nhiên BPE chuẩn deterministic thường deterministic hỗ trợ tự nhiên hỗ trợ subword regularization

06Vocabulary, special token và những “từ” không thuộc ngôn ngữ tự nhiên

6.1 Vocabulary size là một hyperparameter kiến trúc

Chỉ riêng input embedding đã có |V| × d tham số. Với một ví dụ minh hoạ:

|V| = 50.000
d   = 4.096
→ 204.800.000 phần tử

Nếu mỗi phần tử được lưu 2 byte ở một representation 16-bit, riêng ma trận này đã khoảng 409,6 MB dữ liệu thô - chưa tính optimizer states, gradients và các chi tiết triển khai khác. Tăng vocabulary lên 200.000 với cùng d thì số hàng tăng 4 lần.

Nhưng vocabulary lớn lại giảm sequence length, vì nhiều chuỗi được giữ nguyên thành token lớn. Đây là một trade-off thật sự, không có hướng nào “càng lớn càng tốt”:

Vocabulary nhỏ Vocabulary lớn
Embedding + output matrix nhỏ hơn lớn hơn
Cách text bị chia vụn hơn giữ nguyên nhiều hơn
Sequence length dài hơn ngắn hơn
Rủi ro tốn compute cho attention lãng phí slot cho pattern hiếm

Nghiên cứu gần đây vẫn tiếp tục cho thấy vocabulary size ảnh hưởng cả hiệu quả lẫn chất lượng, đặc biệt trong bối cảnh multilingual và low-resource.

6.2 Special token là protocol giữa dữ liệu và model

Token khái niệm Vai trò
BOS bắt đầu sequence
EOS kết thúc sequence
PAD đệm cho đủ độ dài batch
UNK token không xác định
MASK placeholder cho masked language modeling
CLS vị trí tổng hợp/phân loại ở một số encoder
SEP ngăn cách giữa các segment
role/control phân biệt system, user, assistant, tool trong chat model

Không phải model nào cũng có đủ những token trên.

EOS đặc biệt quan trọng với generation. Autoregressive model sinh từng token một, và EOS chính là tín hiệu để generation dừng lại.

PAD chỉ là placeholder, không phải nội dung. Khi batching hai sequence có độ dài khác nhau:

A = [21, 52, 91, 17]
B = [81, 42]
→ B = [81, 42, 0, 0]        (0 là PAD ID)

mask A = [1, 1, 1, 1]
mask B = [1, 1, 0, 0]

6.3 Chat template biến hội thoại thành token protocol

Giao diện chat hiển thị:

User: Giải thích BPE
Assistant: ...

nhưng model nhận một representation gần kiểu:

<start_of_message><user>Giải thích BPE<end_of_message>
<start_of_message><assistant>

Tên token thực tế phụ thuộc model. Điểm quan trọng là role cũng phải được encode: model không tự biết đoạn nào là user hay assistant chỉ từ màu bong bóng chat trên giao diện. Một ví dụ hiện đại là tokenizer o200k_harmony của OpenAI, mở rộng một BPE vocabulary bằng nhiều control token dành riêng cho format hội thoại và tooling.

Special token không phải phụ kiện. Nó là một phần của giao thức mà model được huấn luyện để hiểu - và nó chiếm chỗ thật trong context window.


07Hợp đồng giữa tokenizer và pretrained model

Đây là điểm quan trọng nhất nếu muốn hiểu vì sao không thể tuỳ ý “đổi sang một tokenizer tốt hơn” cho một pretrained model.

~/diagrams/tokenization-trong-llm/tokenizer-model-contract.htmltương tác
mở riêng ↗
HìnhKhung trên là ba thứ đã co-adapt với nhau suốt pretraining: tokenizer sinh ra ID, embedding matrix giữ vector cho đúng ID đó, và weights quen với chính biểu diễn ấy. Hai đường đứt nét là hai cách phá hợp đồng - đổi tokenizer khiến cùng một ID trỏ về vector của token cũ, còn thêm token mới thì tạo ra một hàng chưa từng nhận gradient nào.
hợp đồng đã co-adaptthêm token và khởi tạođường phá hợp đồng

Giả sử tokenizer A có "cat" → 4281, và embedding table đã học hàng 4281 tương ứng với token "cat". Bây giờ ta thay bằng tokenizer B, trong đó "house" → 4281. Model vẫn lấy đúng hàng 4281 - nhưng vector đó được học trong pretraining cho token cũ. Hợp đồng đã bị phá.

7.1 Thêm token mới cũng không chỉ là thêm một dòng vào JSON

Giả sử ta thêm token "ChatGPTVietnam" vào vocabulary. Ta cần một embedding mới cho nó, nhưng vector đó chưa từng được model học. Các chiến lược khả dĩ gồm:

  • random initialization rồi tiếp tục train;
  • khởi tạo từ composition của các token cũ;
  • dùng embedding transfer hoặc cross-lingual mapping;
  • continual pretraining;
  • các phương pháp vocabulary adaptation chuyên dụng.

Nghiên cứu về vocabulary expansion cho thấy initialization của embedding mới là một bài toán thật sự, không phải chi tiết vụn.

7.2 Output vocabulary cũng liên quan

Autoregressive LM cuối cùng sinh logits trên vocabulary - mỗi chiều tương ứng một token có thể sinh tiếp. Vì vậy thay vocabulary có thể ảnh hưởng không chỉ input embedding mà cả output projection. Một số model tie weights giữa input embedding và output projection, một số thì không, nên việc thay tokenizer phải xem kiến trúc cụ thể chứ không có quy tắc duy nhất.

7.3 Token ID không có thứ tự ngữ nghĩa

Nếu cat → 4281dog → 912, ta không thể suy ra cat “lớn hơn” dog. ID chỉ là categorical index. Ngữ nghĩa xuất hiện ở embedding và các hidden representation sau khi model học từ dữ liệu.

Hệ quả thực tế: so sánh token ID giữa hai model là việc vô nghĩa, và tokenizer phải được version cùng checkpoint chứ không tách rời.


08Context window, compute và chi phí

Đây là lý do tokenization không thể bị xem như preprocessing vô hại.

8.1 Context window đo bằng token, không đo bằng từ

Nếu một model hỗ trợ context length N token, điều đó không đồng nghĩa với N từ. Một đoạn 1.000 từ có thể thành 1.200, 1.600 hay 2.500 token tuỳ tokenizer, ngôn ngữ, script, domain, tỉ lệ code so với prose, số và punctuation, ký tự hiếm, và mức độ domain đó xuất hiện trong corpus train tokenizer.

Nói cách khác, lượng thông tin hữu dụng nhét được vào context bằng ngân sách token chia cho số token trung bình mỗi đơn vị thông tin. Tokenizer càng tạo nhiều token cho cùng một lượng text, phần thông tin thực sự lọt vào càng ít.

8.2 Sequence dài hơn làm Transformer tốn việc hơn

Trong vanilla self-attention, ma trận attention giữa n token có kích thước n × n. Tăng sequence từ n lên 2n thì số cặp attention tăng bốn lần.

Nhưng cần diễn đạt chính xác: không phải toàn bộ chi phí của một Transformer luôn tăng đúng bốn lần. MLP, projections, KV caching, kernel implementation, sparse hoặc local attention và các kiến trúc hiện đại có scaling khác nhau. Điều đúng ở mức nền tảng là attention pairwise chuẩn có time complexity bậc hai theo sequence length; các implementation hiện đại có thể giảm memory đáng kể mà không nhất thiết giảm asymptotic compute.

8.3 Token tax: cùng một nội dung, hai hoá đơn khác nhau

~/diagrams/tokenization-trong-llm/token-tax.htmltương tác
mở riêng ↗
HìnhCùng một đoạn văn mang đúng lượng thông tin như nhau đi vào cùng một tokenizer. Ngôn ngữ xuất hiện dày trong corpus train được nén tốt nên còn nhiều context và ít bước decode; ngôn ngữ xuất hiện thưa bị cắt vụn nên hết context sớm hơn và tốn nhiều bước decode hơn cho cùng một nội dung.
đường ít tokensố token đo đượcđường nhiều token

Khi dịch vụ tính phí theo số token input và output, cùng một nội dung nhưng tokenizer hoặc ngôn ngữ tạo nhiều token hơn sẽ tạo chi phí trực tiếp cao hơn. Nghiên cứu EMNLP 2023 trên 22 ngôn ngữ cho thấy tokenization tạo ra chênh lệch đáng kể về chi phí giữa các ngôn ngữ trong các dịch vụ language model thương mại. Một nghiên cứu 2023 khác báo cáo cùng nội dung dịch sang các ngôn ngữ khác nhau có thể tạo độ dài token rất khác nhau, thậm chí chênh lệch lớn trong các tokenizer multilingual.

Điểm cần nhấn mạnh không phải “tokenizer X thiên vị có chủ đích”, mà là:

Frequency-based vocabulary learning phản ánh distribution của training data. Ngôn ngữ xuất hiện ít hơn nhận được ít token dài hữu ích hơn và bị phân mảnh nhiều hơn.

Các công trình 2025 - 2026 tiếp tục nghiên cứu tokenizer fairness, fertility và cách thiết kế BPE công bằng hơn giữa các ngôn ngữ.

8.4 Fertility và các metric đi kèm

Một định nghĩa thường dùng là fertility bằng số token chia cho số word. Fertility cao nghĩa là trung bình mỗi word bị chia thành nhiều token.

Nhưng với các ngôn ngữ có khái niệm word boundary phức tạp, metric này cần một word segmentation tham chiếu phù hợp. Có thể dùng thêm characters per token, bytes per token, compression ratio, normalized sequence length, single-token retention rate, OOV rate và cross-lingual parity. Không metric nào một mình mô tả hoàn hảo chất lượng tokenizer.


09Case study tiếng Việt: khoảng trắng không giải quyết xong bài toán

Tiếng Việt là ví dụ rất hay để thấy sự khác nhau giữa bốn loại ranh giới: ký tự, âm tiết, từ và subword.

~/diagrams/tokenization-trong-llm/vietnamese-boundaries.htmltương tác
mở riêng ↗
HìnhCùng một câu tiếng Việt có thể đi theo hai thiết kế. Đường trên chuẩn hoá Unicode, chạy word segmenter để ghép âm tiết thành từ, rồi mới cắt subword - kiểu PhoBERT. Đường dưới bỏ hẳn tầng word và train SentencePiece thẳng từ raw text, giữ khoảng trắng bằng ký hiệu ▁. Hai đường cho ra hai chuỗi ID khác nhau cho cùng một câu.
đường có word segmentationchuẩn hoá Unicodeđường train thẳng từ raw text

9.1 Khoảng trắng tiếng Việt thường tách âm tiết, không nhất thiết tách từ

Trong tiếng Anh, artificial intelligence có khoảng trắng khá gần với word boundary. Trong tiếng Việt, trí tuệ nhân tạo không có nghĩa là bốn đơn vị đó phải được xử lý như bốn lexical word độc lập.

Các công trình về Vietnamese word segmentation chỉ ra rằng nhiều từ tiếng Việt được cấu tạo từ nhiều âm tiết, trong khi khoảng trắng cũng được dùng để phân tách chính các âm tiết cấu thành từ. Vì vậy xác định word boundary không đơn giản chỉ bằng split(" "). Ví dụ sinh viên có một khoảng trắng nhưng thường được xem là một từ ghép đa âm tiết trong nhiều pipeline NLP.

9.2 Word segmentation và subword tokenization là hai tầng khác nhau

PhoBERT là một ví dụ nổi tiếng: với một số dữ liệu, pipeline dùng Vietnamese word segmenter trước, sau đó mới áp dụng BPE để tạo subword từ các word token. Một hệ thống khác có thể bỏ hẳn tầng word segmentation và train SentencePiece hoặc BPE trực tiếp từ raw text.

Hai thiết kế tạo ra inductive bias khác nhau. Không đường nào sai, nhưng phải biết mình đang chọn đường nào - và phải nhất quán giữa lúc train và lúc phục vụ.

9.3 Dấu tiếng Việt làm Unicode normalization trở nên đáng quan tâm

Tiếng Việt dùng nhiều dấu. Một ký tự hiển thị giống nhau có thể có representation Unicode khác nhau nếu được cấu tạo bằng precomposed character hoặc base character cộng combining marks.

Nếu tokenizer train trên một dạng nhưng production input thường đến ở dạng khác, fragmentation có thể tăng lên hoặc token mapping thay đổi. Với tiếng Việt, pipeline nên nhất quán về Unicode normalization, và team cần biết normalizer cụ thể của tokenizer thay vì chỉ gọi .encode() rồi giả định mọi text Unicode tương đương sẽ cho kết quả giống nhau.

9.4 Multilingual vocabulary là bài toán phân bổ ngân sách

Nếu vocabulary có 100.000 slot và model phải phục vụ hàng chục ngôn ngữ, thì mỗi token dài dành cho một pattern tiếng Anh là một slot không dành cho pattern khác.

Không nên hiểu quá cơ học rằng mỗi ngôn ngữ có “phần vocabulary” riêng, vì token có thể được chia sẻ giữa các ngôn ngữ. Nhưng về mặt thống kê, training corpus và merge objective quyết định pattern nào đủ phổ biến để chiếm một token riêng. Nghiên cứu multilingual tokenization cho thấy mức coverage và overlap của vocabulary giữa các ngôn ngữ ảnh hưởng tới downstream performance, và hiệu quả tokenization không đồng đều giữa các nhóm ngôn ngữ.

9.5 Kiểm tra gì khi đánh giá tokenizer cho tiếng Việt

 1. Tokens trên mỗi câu
 2. Tokens trên mỗi word sau word segmentation chuẩn
 3. Characters hoặc bytes trên mỗi token
 4. Tỷ lệ từ phổ biến bị split thành nhiều mảnh
 5. Tên người, địa danh, từ ghép, viết tắt
 6. Unicode dạng composed và decomposed
 7. Văn bản có dấu và không dấu
 8. Code-switch Vietnamese ↔ English
 9. URL, số điện thoại, mã sản phẩm
10. Văn bản mạng xã hội có typo và teencode

Một tokenizer tốt trên news corpus chưa chắc tốt trên chat, source code hoặc tên sản phẩm thương mại điện tử.


10Những vùng khó: số, code, typo và đếm ký tự

Một model có thể rất giỏi semantic reasoning nhưng vẫn lúng túng với vài thao tác “dễ” với con người. Tokenization là một phần nguyên nhân.

10.1 Số không nhất thiết được biểu diễn theo từng chữ số

Chuỗi 123456789 có thể tokenize thành ["123", "456", "789"], hoặc ["12", "345", "678", "9"], hoặc cách khác tuỳ vocabulary. Model không mặc nhiên nhận representation decimal digit-by-digit, nên có một mismatch giữa cấu trúc toán học của số và đơn vị token mà mạng nhìn thấy.

10.2 Source code có pattern khác prose

Code chứa snake_case, camelCase, ::, ->, ==, !=, indentation và newline. Một tokenizer được train nhiều code có thể dành token riêng cho các pattern thường xuyên, giảm sequence length đáng kể.

def calculate_total(price, quantity):
    return price * quantity

Đoạn trên có thể bị chia quanh keyword, identifier fragment, dấu ngoặc, dấu phẩy, whitespace và operator. Việc giữ hay tách _, indentation hay newline ảnh hưởng tới khả năng model học syntax pattern.

10.3 Typo làm boundary thay đổi đột ngột

Một từ phổ biến có thể là một token duy nhất:

language   → 1 token
langauge   → nhiều mảnh hiếm

Chỉ một hoán vị ký tự nhỏ đã làm representation rời rạc thay đổi mạnh. Nghiên cứu về “curse of tokenization” cho thấy LLM vẫn nhạy với typo, biến thể hình thức và cấu trúc token bên trong; tăng model size có thể giảm nhưng không loại bỏ hoàn toàn vấn đề.

10.4 Vì sao LLM đôi khi đếm ký tự kém

Khi được hỏi có bao nhiêu chữ r trong một từ, con người thao tác trực tiếp trên chuỗi ký tự. LLM thì trước tiên thấy subword token IDs:

con người : character → thao tác
LLM       : text → subwords/bytes → embeddings → hidden states → suy luận

Đường đi dài hơn, và ranh giới token đôi khi che mất cấu trúc character-level. Điều này không có nghĩa “LLM không thể đếm ký tự”, mà chỉ giải thích vì sao nhiệm vụ character-exact có bản chất khác với semantic completion.


11Encode, decode và câu hỏi “tokenizer có lossless không?”

Ta thường viết decode(encode(text)) và kỳ vọng nhận lại đúng text. Câu trả lời phụ thuộc toàn bộ pipeline, không chỉ bước cắt token.

~/diagrams/tokenization-trong-llm/encode-decode-roundtrip.htmltương tác
mở riêng ↗
HìnhĐọc từ trên xuống theo thời gian. Bước tokenizer sang model reversible ở tầng byte, nhưng bước normalizer phía trước đã có thể biến đổi văn bản gốc. Vì vậy câu hỏi segmentation có lossless không và câu hỏi toàn bộ pipeline có lossless với raw input không là hai câu hỏi khác nhau.
đường encodesinh tokenđường decode trả về

11.1 Byte-level BPE có thể reversible ở tầng byte

tiktoken thiết kế BPE theo byte và mô tả encoding là reversible với text được biểu diễn qua bytes. Core API còn phân biệt việc decode ra bytes với decode ra text, vì một token riêng lẻ có thể chứa bytes chưa tạo thành UTF-8 hợp lệ nếu tách khỏi các token lân cận.

Đây là một nuance rất hay: ranh giới token không nhất thiết trùng ranh giới ký tự UTF-8.

11.2 Normalization có thể làm round-trip không còn byte-identical

Nếu trước tokenization ta chủ động lowercase, chạy NFKC, strip whitespace hoặc replace ký tự, thì thông tin đã bị biến đổi trước khi segmentation xảy ra. Compatibility normalization có thể xoá distinction về kiểu trình bày, đúng như Unicode cảnh báo.

Do đó nên phân biệt rõ hai câu hỏi:

1. Segmentation có reversible không?
2. Toàn bộ preprocessing pipeline có reversible với raw input ban đầu không?

Câu 1 thường có. Câu 2 thường không.

11.3 Decode của tokenizer không phải decoder của Transformer

Decoder của tokenizer chỉ map IDs → token pieces/bytes → text. Nó không dùng Transformer và không “suy nghĩ” gì cả. Đừng nhầm tokenizer decode với neural decoder block trong kiến trúc encoder-decoder: hai chữ “decode” nằm ở hai tầng hoàn toàn khác nhau.


12Đánh giá tokenizer và checklist thực tế

Một tokenizer tốt không thể được đánh giá bằng cách nhìn vài câu rồi nói “các token trông đẹp”. Ta cần metric và workload.

~/diagrams/tokenization-trong-llm/tokenizer-eval.htmltương tác
mở riêng ↗
HìnhBắt đầu từ workload thật chứ không phải vài câu mẫu, và từ các cặp song ngữ nếu quan tâm tới công bằng ngôn ngữ. Mỗi metric ở cột giữa chỉ trả lời được một câu hỏi: độ nén nói về context và chi phí, fertility nói về mức phân mảnh, parity nói về chênh lệch giữa các ngôn ngữ. Điểm downstream mới là trọng tài cuối cùng.
workload đầu vàometric đo đượckết luận rút ra

12.1 Bảy hướng đo nên có

Hướng đo Câu hỏi nó trả lời
Compression characters hoặc bytes trên mỗi token - sequence dài bao nhiêu cho cùng text
Fertility tokens trên mỗi word - mức phân mảnh, cần word segmentation tham chiếu
Unknown rate tỉ lệ <UNK>, thường gần bằng 0 với byte-level
Boundary quality token có giữ được morpheme hữu ích, named entity, syntax marker trong code
Cross-lingual parity tỉ lệ token giữa hai ngôn ngữ trên cùng nội dung
Downstream performance tokenizer đo cùng model có cho kết quả tốt hơn không
Domain robustness news, chat, legal, medical, code, math, social media, URL, identifier

Hai lưu ý đi kèm. Thứ nhất, tokenizer có sequence ngắn nhất chưa chắc thắng: các unit học được có thể làm model khó generalize hơn. Thứ hai, “linguistically pretty tokens” không phải mục tiêu duy nhất - tokenizer train trên prose sạch có thể fragmentation rất mạnh với log, code hoặc tên sản phẩm.

12.2 Mười lăm câu hỏi khi dùng một pretrained LLM

Khi nhận một model mới, tôi không chỉ hỏi “tokenizer tên gì”, mà hỏi:

 1. Base unit là gì - character, byte hay symbol khác?
 2. Segmentation algorithm là gì - BPE, WordPiece, Unigram hay biến thể?
 3. Pre-tokenizer dùng regex/rule nào? Có cho merge qua whitespace không?
 4. Normalizer làm gì - NFC/NFKC? lowercase? strip accents?
 5. Vocabulary size bao nhiêu, trong đó bao nhiêu là special/control token?
 6. Unknown fallback hoạt động ra sao - có UNK hay byte fallback?
 7. Whitespace được biểu diễn thế nào - delimiter, prefix-space hay symbol riêng?
 8. Special token nào có semantic protocol - BOS/EOS/role/tool token?
 9. Chat template là gì - raw string của UI thành token sequence ra sao?
10. Context budget tính thế nào - prompt, system, tool và output có cùng ăn budget?
11. Tokenizer có hiệu quả với ngôn ngữ mục tiêu không - đo, đừng đoán?
12. Code, số và URL có bị fragmentation mạnh không?
13. Tokenizer có khớp chính xác model checkpoint không?
14. Nếu thêm token, embedding mới được initialize và train thế nào?
15. Encode/decode có bảo toàn raw text không - tính cả normalization?

Nếu xây tokenizer mới thì thêm năm câu nữa: training corpus có cân bằng language và domain không; vocabulary allocation có bị high-resource language chiếm ưu thế không; metric compression có đánh đổi downstream quality không; fairness giữa các ngôn ngữ ra sao; và tokenizer có được versioning cùng model checkpoint hay không.

12.3 Mười hiểu lầm nên gỡ

Hiểu lầm Thực tế
“Một token bằng một từ” token có thể là word, subword, whitespace-attached piece, punctuation, bytes hoặc control symbol
“Đổi tokenizer không ảnh hưởng model” sai với pretrained model - ID gắn với embedding rows đã học
“Vocabulary càng lớn càng tốt” giảm sequence length nhưng tăng tham số và có thể lãng phí slot
“BPE luôn chia đúng morpheme” BPE tối ưu theo thống kê, không có bộ phân tích hình thái bên trong
“SentencePiece là một thuật toán như BPE” nó là framework, bên trong chạy BPE hoặc Unigram
“Byte-level nghĩa là mỗi token một byte” base representation là byte, nhưng merge gộp nhiều byte thành token lớn
“So sánh được token ID giữa hai model” ID 1000 ở hai tokenizer là hai token khác nhau
“Context 128k token bằng khoảng 128k từ” tỉ lệ token trên từ đổi theo tokenizer, ngôn ngữ và loại text
“Encode được mọi ngôn ngữ là công bằng” coverage và efficiency là hai chuyện khác nhau
“Decode luôn tái tạo nguyên byte raw input” phụ thuộc normalization có lossy hay không

Tokenizer là một lớp nén rời rạc đặt trước Transformer

Có một trực giác tôi thấy rất hữu ích: tokenizer biến một chuỗi byte rất dài thành một chuỗi symbol ID ngắn hơn, nhờ việc dành vocabulary entries cho các pattern thường gặp.

Nhưng đây không phải compression thuần tuý kiểu ZIP. Tokenizer phải cân bằng cùng lúc nhiều mục tiêu: mức nén, coverage, ngân sách vocabulary, độ phủ ngôn ngữ, độ phủ domain, tính compositional, khả năng học được của model, và hiệu quả inference.

Nếu chỉ tối ưu compression tuyệt đối, ta tạo ra những token rất lớn nhưng quá hiếm, khiến model khó chia sẻ thống kê. Nếu chỉ tối ưu composition nhỏ nhất, ta quay về byte và sequence trở nên dài. Đó là lý do subword tokenization thành công: nó tạo một bottleneck có cấu trúc giữa memorization và composition.

Một điều nữa đáng nhớ: tokenizer đóng băng một cách nhìn về thế giới tại thời điểm pretraining. Một static vocabulary học từ dữ liệu năm nay không biết xu hướng lexical của nhiều năm sau. Tên sản phẩm mới, slang mới, viết tắt mới, domain mới đều có thể bị fragmentation nặng. Nghiên cứu gần đây bắt đầu xem tokenizer adaptation và dynamic tokenization là một hướng để gỡ sự cứng nhắc này. Liệu tokenizer tĩnh có phải giao diện rời rạc cuối cùng của LLM, hay tương lai sẽ chuyển sang byte/character latent model - câu trả lời vẫn đang mở.

Tóm lại những gì nên mang theo:

  • token không phải word, và token ID không phải semantic value;
  • subword tokenization là bài toán cân bằng vocabulary và sequence length;
  • BPE, WordPiece và Unigram có cơ chế khác nhau, còn SentencePiece là framework;
  • Unicode normalization và pre-tokenization có thể thay đổi kết quả rất mạnh;
  • tokenizer và pretrained model bị ràng buộc qua embedding và output vocabulary;
  • tokenization ảnh hưởng trực tiếp tới context, latency và đôi khi cả hoá đơn;
  • multilingual tokenization không tự động công bằng giữa các ngôn ngữ;
  • với tiếng Việt, âm tiết, từ và subword là ba tầng ranh giới cần phân biệt rõ.

Và cuối cùng, tokenization mới chỉ đưa ta tới một dãy số nguyên:

[921, 44, 7211, 3512, ...]

Những con số đó vẫn chưa phải thứ Transformer “hiểu” theo nghĩa toán học. Bước kế tiếp là biến từng token ID thành một vector liên tục trong không gian nhiều chiều - và đó là chỗ embedding bắt đầu, một chủ đề đủ lớn để đứng riêng trong series này.


Tài liệu tham khảo

Trương Đức Dũng

AI Engineer - Hà Nội

Tập trung vào LLM, RAG và các hệ thống AI cần chạy ổn định trong môi trường vận hành thật.