Danh sách bài viết

Bài 4: SentencePiece và WordPiece — của Google / BERT

BPE thống trị GPT-family, nhưng họ nhà BERT và T5 dùng hai thuật toán khác: WordPiece chọn cặp merge theo likelihood thay vì frequency và đánh dấu subword bằng ##; SentencePiece treat space như character thường, language-agnostic, hỗ trợ cả BPE lẫn Unigram language model. Bài này so sánh ba algorithm, demo code Python và HuggingFace AutoTokenizer.

25/05/2026
13 phút đọc
2 lượt xem
1

Mục tiêu bài học

  • Hiểu vì sao BERT và T5 không dùng BPE giống GPT mà chọn WordPiece / SentencePiece.
  • Giải thích merge criterion theo likelihood của WordPiece và ý nghĩa prefix ##.
  • Mô tả ý tưởng "treat space như character" và Unigram language model trong SentencePiece.
  • Đối chiếu 3 thuật toán BPE / WordPiece / SentencePiece theo pros, cons, model dùng.
  • Viết được code Python load 3 loại tokenizer khác nhau qua HuggingFace AutoTokenizer.
2

Recap B3 và vì sao cần thuật toán khác

Bài 3 đã đi qua BPE (Byte-Pair Encoding): học merge từ cặp ký tự xuất hiện nhiều nhất, rồi áp lại lúc inference. BPE phổ biến trong GPT-2, GPT-3, GPT-4, Llama 3, Mistral. Tuy nhiên BPE không phải lựa chọn duy nhất.

Có hai thuật toán tokenizer subword khác cùng nhóm "BPE-like" nhưng tiêu chí merge và cách xử lý khoảng trắng khác nhau:

  • WordPiece — Google 2012, dùng trong BERT, DistilBERT, ELECTRA. Merge dựa trên likelihood thay vì frequency.
  • SentencePiece — Google 2018, dùng trong T5, mT5, Llama 1/2, XLNet, ALBERT. Là một thư viện language-agnostic, hỗ trợ cả BPE backend lẫn Unigram language model.

Hai thuật toán này không "tốt hơn" BPE. Chúng giải quyết hai vấn đề khác nhau: WordPiece muốn merge có ý nghĩa thống kê chặt hơn, SentencePiece muốn tokenizer chạy được trên mọi ngôn ngữ mà không cần pre-tokenize theo whitespace.

3

WordPiece — Google 2012

WordPiece xuất hiện lần đầu trong bài Japanese and Korean Voice Search (Schuster & Nakajima, 2012) và được dùng lại trong BERT (Devlin et al., 2018).

Quy trình train WordPiece gần giống BPE:

  1. Khởi tạo vocab gồm tất cả character đơn.
  2. Lặp: chọn một cặp subword (A, B) để merge thành AB, thêm vào vocab.
  3. Dừng khi đạt vocab_size mục tiêu.

Khác biệt duy nhất ở bước 2 — tiêu chí chọn cặp. BPE chọn cặp xuất hiện nhiều nhất (frequency). WordPiece chọn cặp có likelihood score cao nhất.

4

Merge criterion của WordPiece

Với mỗi cặp (A, B) liền kề trong corpus, WordPiece tính:

\[ \text{score}(A, B) = \frac{\text{freq}(AB)}{\text{freq}(A) \cdot \text{freq}(B)} \]

Trong đó freq(AB) là số lần cặp A kề B xuất hiện, còn freq(A), freq(B) là số lần từng subword độc lập xuất hiện. Cặp có score cao nhất được merge.

Ý nghĩa thống kê: score cao khi AB xuất hiện cùng nhau thường xuyên hơn so với mức ngẫu nhiên. Một cặp ký tự phổ biến nhưng không "đi cùng nhau" (vì cả hai đều xuất hiện khắp nơi) sẽ không được ưu tiên.

Ví dụ giả định: "th" trong tiếng Anh có frequency rất cao, nhưng cả th đều rất phổ biến → tích freq(t)·freq(h) lớn → score không cao bằng các cặp ít phổ biến hơn nhưng "dính chặt" như "ing" (trong "##ing").

Đây thực chất là tỷ số pointwise mutual information (PMI) chưa lấy log — gần với chỉ số trong mô hình ngôn ngữ thống kê. Vì vậy WordPiece được gọi là likelihood-based.

5

Ký hiệu ## trong WordPiece

WordPiece phân biệt token đầu wordtoken continuation:

  • Token đầu word: không có prefix.
  • Token continuation (giữa hoặc cuối word): prefix ##.

Ví dụ với BERT tokenizer:

"playing"    → ["play", "##ing"]
"unhappiness" → ["un", "##happiness"]   # hoặc ["un", "##happi", "##ness"]
"tokenization" → ["token", "##ization"]

Quy tắc decode: ghép các token, bỏ ##. Hai token ["play", "##ing"] ghép thành "playing", không thêm khoảng trắng. Hai token ["I", "am"] (không có ##) ghép thành "I am" với khoảng trắng giữa.

Hệ quả: WordPiece phụ thuộc vào việc pre-tokenize theo whitespace. Phải biết đâu là biên của word để gắn hoặc bỏ ## — điều này không hợp với các ngôn ngữ không dùng dấu cách như tiếng Trung, tiếng Nhật, tiếng Thái.

6

Use case WordPiece

  • BERT (Devlin et al., 2018) — vocab ~30,522 tokens cho phiên bản English uncased.
  • DistilBERT — kế thừa tokenizer của BERT.
  • ELECTRA (Clark et al., 2020) — cũng dùng WordPiece với vocab tương tự BERT.
  • RoBERTa trong bài gốc dùng byte-level BPE (GPT-2 style), không phải WordPiece. Tuy nhiên các phiên bản multilingual hoặc một số fork community có biến thể WordPiece, cần đọc kỹ tokenizer_config.json để biết chính xác.
  • PhoBERT tiếng Việt (VinAI, 2020) — WordPiece sau khi đã segment từ bằng VnCoreNLP.
7

SentencePiece — Google 2018

SentencePiece (Kudo & Richardson, 2018) là library chứ không phải một thuật toán đơn lẻ. Nó cung cấp hai backend:

  • model_type="bpe" — BPE giống GPT-2 nhưng pre-tokenize theo cách khác.
  • model_type="unigram" — Unigram language model (mặc định, mới hơn).

Khác biệt cốt lõi so với WordPiece và BPE truyền thống: SentencePiece không pre-tokenize theo whitespace. Nó nhận raw text và treat dấu cách như một character bình thường.

8

Treat space như character

SentencePiece đổi tất cả dấu cách thành ký tự (U+2581, lower one eighth block) trước khi train. Sau đó algorithm BPE / Unigram chạy trên chuỗi đã thay thế.

Input:  "Hello world"
Internal: "▁Hello▁world"
Tokens:  ["▁Hello", "▁world"]

Hai tính chất quan trọng:

  1. Language-agnostic: không cần biết quy tắc tách từ riêng cho từng ngôn ngữ. Tiếng Anh tách bằng space, tiếng Trung viết liền — cả hai đều đẩy vào cùng pipeline.
  2. Reversible: từ token có thể decode lại chính xác nguyên bản, bao gồm cả số lượng dấu cách. Chỉ cần ghép tất cả token, đổi trở lại thành space.

So với WordPiece, SentencePiece không phân biệt "token đầu word" và "token giữa word" qua prefix khác nhau — chỉ cần có hoặc không có ở đầu là biết token đó bắt đầu một từ mới hay nối tiếp.

9

Unigram language model

Unigram (Kudo, 2018) tiếp cận ngược với BPE/WordPiece. Thay vì bắt đầu nhỏ rồi merge thêm, Unigram bắt đầu lớn rồi cắt đi:

  1. Khởi tạo vocab rất lớn, gồm mọi substring có khả năng xuất hiện trong corpus (kèm character đơn để đảm bảo coverage).
  2. Mỗi token có một probability p(t). Likelihood của corpus được tính qua segmentation tốt nhất cho từng câu.
  3. Lặp: với mỗi token, ước lượng loss tăng thêm khi remove nó khỏi vocab. Loại bỏ một tỷ lệ (vd 10–20%) token có loss tăng thêm thấp nhất.
  4. Train lại probability cho vocab còn lại, lặp đến khi đạt vocab_size mục tiêu.

Unigram probabilistic: mỗi cách segment một câu có một probability. Lúc inference thường lấy segmentation Viterbi tối ưu. Tuy nhiên Unigram cũng cho phép subword regularization — sample ngẫu nhiên giữa các segmentation gần tối ưu trong lúc training để model robust hơn với noise.

BPE không có khả năng này: với mỗi câu chỉ có duy nhất một sequence merge.

10

Use case SentencePiece

  • T5 / mT5 (Raffel et al., 2019; Xue et al., 2020) — Unigram, vocab 32k cho T5 và 250k cho mT5 (multilingual).
  • Llama 1, Llama 2 (Touvron et al., 2023) — SentencePiece với model_type="bpe", vocab 32k.
  • Llama 3 (Meta, 2024) — đã chuyển sang tiktoken-style BPE, vocab 128k. Không còn dùng SentencePiece.
  • XLNet (Yang et al., 2019) — SentencePiece Unigram.
  • ALBERT (Lan et al., 2019) — SentencePiece Unigram.
  • ViT5, BARTpho tiếng Việt — SentencePiece.

Lý do Llama 3 đổi không phải vì SentencePiece "yếu". Vocab 128k với byte-level BPE cho compression rate tốt hơn trên code và multilingual data; đồng thời stack tooling của Meta hợp với tiktoken hơn.

11

Code Python với SentencePiece

Cài đặt:

pip install sentencepiece

Train một model SentencePiece nhỏ trên corpus của riêng bạn:

import sentencepiece as spm

# Giả sử corpus.txt là file text, mỗi dòng 1 câu
spm.SentencePieceTrainer.Train(
    input="corpus.txt",
    model_prefix="my_model",
    vocab_size=8000,
    model_type="bpe",        # hoặc "unigram"
    character_coverage=0.9995, # 1.0 cho Latin, 0.9995 cho Asian
)

# Sau khi train xong sẽ có my_model.model và my_model.vocab

Load và sử dụng:

sp = spm.SentencePieceProcessor()
sp.Load("my_model.model")

text = "Hello, world!"
tokens = sp.EncodeAsPieces(text)
ids = sp.EncodeAsIds(text)
restored = sp.DecodePieces(tokens)

print(tokens)    # ['▁Hello', ',', '▁world', '!']
print(ids)       # [243, 12, 87, 9]
print(restored)  # 'Hello, world!'

Lưu ý: character_coverage thấp hơn 1.0 nghĩa là chấp nhận một tỷ lệ nhỏ character hiếm bị thay bằng <unk>. Với Latin script đặt 1.0; với CJK (Chinese, Japanese, Korean) hoặc dataset multilingual lớn thường 0.9995 để vocab không bị "ngốn" hết bởi character hiếm.

12

HuggingFace AutoTokenizer thống nhất API

Trong thực tế hiếm khi tự train tokenizer. HuggingFace cung cấp AutoTokenizer tự nhận diện backend dựa trên file config của model:

from transformers import AutoTokenizer

# Mỗi tokenizer dưới đây dùng backend khác nhau
bert_tok = AutoTokenizer.from_pretrained("bert-base-uncased")          # WordPiece
gpt2_tok = AutoTokenizer.from_pretrained("gpt2")                        # BPE byte-level
t5_tok = AutoTokenizer.from_pretrained("t5-base")                       # SentencePiece Unigram
llama_tok = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")   # SentencePiece BPE

# API giống hệt nhau
for tok in [bert_tok, gpt2_tok, t5_tok]:
    pieces = tok.tokenize("Tokenizers are everywhere.")
    ids = tok.encode("Tokenizers are everywhere.")
    print(tok.name_or_path, "→", pieces[:6], "...")

Output mẫu:

bert-base-uncased → ['token', '##izers', 'are', 'everywhere', '.']
gpt2              → ['Token', 'izers', 'Ġare', 'Ġeverywhere', '.']
t5-base           → ['▁Token', 'izers', '▁are', '▁everywhere', '.']

Để ý các marker khác nhau cho cùng một thông tin "đây là token đầu word":

  • WordPiece: token không## ở đầu.
  • GPT-2 BPE: token bắt đầu bằng Ġ (U+0120, đại diện space).
  • SentencePiece: token bắt đầu bằng (U+2581).

Bản chất giống nhau, ký hiệu hiển thị khác.

13

So sánh BPE vs WordPiece vs SentencePiece

BPE

  • Pros: thuật toán đơn giản, train và encode nhanh. Byte-level BPE (GPT-2 trở đi) xử lý mọi Unicode mà không cần <unk>.
  • Cons: greedy, không tối ưu toàn cục. Mỗi câu chỉ có duy nhất một segmentation.
  • Use: GPT-2/3/4, Llama 3, Mistral, Mixtral, tiktoken.

WordPiece

  • Pros: criterion likelihood-based, có liên hệ với mutual information nên merge "ý nghĩa thống kê" hơn.
  • Cons: chậm hơn BPE ở bước train (tính score cho từng cặp). Phụ thuộc pre-tokenize theo whitespace nên ít phù hợp ngôn ngữ không có dấu cách.
  • Use: BERT, DistilBERT, ELECTRA, PhoBERT.

SentencePiece

  • Pros: language-agnostic, reversible exact, Unigram cho phép subword regularization.
  • Cons: thêm marker trong output làm ngữ liệu "trông lạ"; người mới đọc đôi khi nhầm là ký tự đặc biệt của model.
  • Use: T5, mT5, XLNet, ALBERT, Llama 1/2, ViT5, BARTpho.
14

Modern view 2025

  • tiktoken (BPE) — OpenAI standard cho GPT-3.5 / GPT-4 / GPT-4o. Llama 3 cũng đi theo hướng tiktoken-style.
  • Hugging Face Tokenizers (Rust) — implement BPE, WordPiece, Unigram trong cùng một library, dùng cho hầu hết model trên HF Hub.
  • SentencePiece vẫn được dùng rộng rãi cho model multilingual (T5, mT5, NLLB family).

Quan sát chung: dù khác nhau ở chi tiết, cả ba thuật toán đều hội tụ về subword tokenization với vocab cỡ vài chục nghìn đến trên trăm nghìn token. Khác biệt ngày càng nhỏ so với thập kỷ trước khi từng tồn tại word-level và character-level rạch ròi.

15

Tokenizer cho ngôn ngữ mới

Khi áp dụng LLM cho một ngôn ngữ không có nhiều trong corpus pretrain (vd: tiếng Khmer, Lào, một số ngôn ngữ Phi), tokenizer hiện tại có thể chia mỗi từ thành 4–10 token, đẩy chi phí inference lên và bóp context window.

Hai hướng xử lý:

  1. Retrain tokenizer trên corpus song ngữ hoặc đa ngôn ngữ có mặt ngôn ngữ đích. Sau đó resize embedding của model và continue pretrain.
  2. Dùng pretrained multilingual model: BERT-multilingual (104 ngôn ngữ), XLM-R (100 ngôn ngữ), NLLB, mT5. Các model này tokenizer đã được train với corpus đa ngôn ngữ ngay từ đầu.
16

Special tokens

Mỗi nhóm thuật toán có convention riêng cho special token:

  • WordPiece (BERT family): [CLS], [SEP], [MASK], [PAD], [UNK].
  • SentencePiece (T5, Llama 1/2): <s>, </s>, <unk>, <pad>. T5 còn có <extra_id_0> ... <extra_id_99> cho span corruption.
  • BPE (GPT family): <|endoftext|>, các custom token cho chat format (<|im_start|>, <|im_end|>, ...).

Khi dùng tokenizer.encode(text), các token này có thể được thêm tự động vào đầu / cuối input tuỳ theo cấu hình của tokenizer. Đọc thuộc tính tokenizer.special_tokens_map để biết chính xác.

17

Vietnamese tokenization

Tiếng Việt là ngôn ngữ syllable-based với dấu cách giữa âm tiết, không phải giữa từ. "máy bay" là một danh từ ghép gồm hai âm tiết. Điều này khiến tokenizer subword hoạt động khác đôi chút:

  • SentencePiece dễ thích nghi vì không phụ thuộc khái niệm "word" sẵn.
  • WordPiece dùng được nhưng thường có một bước pre-segment riêng (vd PhoBERT dùng VnCoreNLP trước khi WordPiece).

Các model pretrained tiếng Việt phổ biến:

  • PhoBERT (VinAI, 2020) — WordPiece sau khi VnCoreNLP segment từ.
  • ViT5 (VietAI) — SentencePiece Unigram.
  • BARTpho (VinAI) — SentencePiece.

Với code chạy trên model multilingual như XLM-R, mT5 không cần segment trước; tokenizer xử lý raw text.

18

Hugging Face Tokenizers library

Khác với transformers (load tokenizer có sẵn từ Hub), thư viện tokenizers dùng để train tokenizer mới từ đầu.

pip install tokenizers
from tokenizers import Tokenizer
from tokenizers.models import WordPiece
from tokenizers.trainers import WordPieceTrainer
from tokenizers.pre_tokenizers import Whitespace

tokenizer = Tokenizer(WordPiece(unk_token="[UNK]"))
tokenizer.pre_tokenizer = Whitespace()

trainer = WordPieceTrainer(
    vocab_size=8000,
    special_tokens=["[UNK]", "[CLS]", "[SEP]", "[PAD]", "[MASK]"],
)

tokenizer.train(files=["corpus.txt"], trainer=trainer)
tokenizer.save("my_wp.json")

Library này viết bằng Rust với binding Python nên train rất nhanh; có thể train tokenizer 30k vocab trên corpus 1GB trong vài phút.

19

Tokenizer "tốt" nhìn thế nào

Ba tiêu chí thường dùng để so sánh tokenizer trên một corpus đánh giá:

  1. Compression rate — số token / số word (hoặc / số byte). Thấp hơn nghĩa là tokenizer "nén" tốt, tiết kiệm context window và chi phí inference.
  2. Coverage — tỷ lệ token không phải <unk>. Byte-level BPE đạt 100% theo định nghĩa. WordPiece có thể fallback về [UNK] nếu gặp character ngoài vocab.
  3. Reversibility — decode lại text có chính xác từng character (kể cả khoảng trắng) không. SentencePiece đảm bảo điều này; BPE byte-level đảm bảo; WordPiece không hoàn toàn vì ## mất thông tin về số dấu cách liên tiếp.

Có thể đo nhanh:

from transformers import AutoTokenizer

corpus = open("eval.txt").read()
words = len(corpus.split())

for name in ["bert-base-uncased", "gpt2", "t5-base"]:
    tok = AutoTokenizer.from_pretrained(name)
    n_tokens = len(tok.encode(corpus, add_special_tokens=False))
    print(f"{name:25s}  tokens/word = {n_tokens/words:.2f}")
20

Code Python tổng hợp

Tokenize cùng một câu bằng ba backend khác nhau:

from transformers import AutoTokenizer

text = "Tokenization is unhappy and complicated."

models = {
    "BPE (GPT-2)":          "gpt2",
    "WordPiece (BERT)":     "bert-base-uncased",
    "SentencePiece (T5)":   "t5-base",
}

for label, name in models.items():
    tok = AutoTokenizer.from_pretrained(name)
    pieces = tok.tokenize(text)
    ids = tok.encode(text, add_special_tokens=False)
    print(f"\n[{label}]")
    print("  pieces:", pieces)
    print("  ids   :", ids)
    print("  count :", len(pieces))

Output rút gọn:

[BPE (GPT-2)]
  pieces: ['Token', 'ization', 'Ġis', 'Ġunhappy', 'Ġand', 'Ġcomplicated', '.']
  count : 7

[WordPiece (BERT)]
  pieces: ['token', '##ization', 'is', 'un', '##happy', 'and', 'complicated', '.']
  count : 8

[SentencePiece (T5)]
  pieces: ['▁Token', 'ization', '▁is', '▁un', 'happy', '▁and', '▁complicated', '.']
  count : 8

Cùng nội dung, ba ký hiệu khác nhau — Ġ, ##, — nhưng cùng mục đích đánh dấu biên word.

21

Bài tập

  1. Tokenize câu "anh ấy đang code AI" bằng ba tokenizer: bert-base-multilingual-cased (WordPiece), meta-llama/Llama-2-7b-hf (BPE qua SentencePiece) và t5-base (Unigram). So sánh số token và xem có token nào fallback về [UNK]/<unk> không.
  2. Trên output của tokenizer BERT cho câu trên, identify tất cả token bắt đầu bằng ## và viết lại tay quá trình ghép lại nguyên gốc.
  3. Tạo file vi_corpus.txt gồm 100 dòng tiếng Việt (tự viết hoặc copy từ Wikipedia). Train một SentencePiece model với vocab_size=2000, model_type="unigram". So sánh số token nó tạo ra cho một câu mới so với t5-base.
  4. Cài tokenizers và train một WordPiece tokenizer trên cùng vi_corpus.txt. So sánh với output SentencePiece ở bài 3 — tokenizer nào chia "code" và "Python" thành ít token hơn?
  5. (Optional) Tính compression rate token/word của gpt2, bert-base-uncased, t5-base trên một đoạn văn 500 từ tiếng Anh và 500 từ tiếng Việt. Quan sát sự chênh lệch giữa hai ngôn ngữ.
22

Tóm tắt

  • WordPiece (BERT) khác BPE ở tiêu chí merge: chọn cặp theo freq(AB)/(freq(A)·freq(B)) thay vì frequency thuần.
  • Token continuation trong WordPiece có prefix ##; decode = ghép token và bỏ ##.
  • SentencePiece treat space như character , không cần pre-tokenize theo whitespace, language-agnostic và reversible.
  • SentencePiece hỗ trợ hai backend: BPE và Unigram language model. Unigram bắt đầu với vocab lớn rồi cắt dần, cho phép subword regularization.
  • Use case điển hình: BERT/ELECTRA dùng WordPiece; T5/mT5/Llama 1-2/XLNet/ALBERT dùng SentencePiece; GPT/Llama 3 dùng BPE.
  • HuggingFace AutoTokenizer.from_pretrained(...) ẩn đi backend; API tokenize, encode, decode giống nhau.
  • Bài 5 sẽ đi sâu vào vocabularytoken ID: cách map giữa string ↔ integer, kích thước vocab, ý nghĩa của ID 0, và lý do vocab_size ảnh hưởng đến kích thước embedding.