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.
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.
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:
- Tokenizer học vocabulary như thế nào?
- 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.
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.
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 → 4281 và dog → 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
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.
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.
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.
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
- Sennrich, Haddow, Birch — Neural Machine Translation of Rare Words with Subword Units
- Kudo, Richardson — SentencePiece: A simple and language independent subword tokenizer · repository
- Kudo — Subword Regularization
- Google Research — A Fast WordPiece Tokenization System
- OpenAI —
tiktokenREADME · educational BPE · core encoding - Unicode Consortium — UAX #15: Unicode Normalization Forms
- Vaswani et al. — Attention Is All You Need
- Rabe, Staats — Self-attention Does Not Need Quadratic Memory
- Google Research — Efficient Sequence Modeling for On-Device ML
- Đinh et al. — Word Segmentation of Vietnamese Texts
- Vu et al. — VnCoreNLP: A Vietnamese NLP Toolkit
- Nguyen, Nguyen — PhoBERT: Pre-trained language models for Vietnamese
- Ahia et al. — Do All Languages Cost the Same?
- Petrov et al. — Language Model Tokenizers Introduce Unfairness Between Languages
- Limisiewicz, Balhar, Mareček — Tokenization Impacts Multilingual Language Modeling
- Foroutan et al. — Parity-Aware Byte-Pair Encoding
- Chiu — TokLens: A Multilingual Lens on Tokenizer Quality
- Chai et al. — Tokenization Falling Short: On Subword Robustness
- He, Zeng, Jiang — Pre-trained Models Perform the Best When Token Distributions Follow Zipf’s Law
- Mundra et al. — An Empirical Comparison of Vocabulary Expansion and Initialization Approaches
- Yamaguchi, Villavicencio, Aletras — How Can We Effectively Expand the Vocabulary of LLMs
- Feher, Vulić, Minixhofer — Retrofitting Large Language Models with Dynamic Tokenization
- Purason et al. — Teaching Old Tokenizers New Words