Đề dẫn.
Tôi tin rằng anh/chị đã quá quen thuộc với LLM (Large Language Model) như ChatGPT (OpenAI), Gemini (Google), Claude (Anthropic)… Việc sử dụng chúng cũng vô cùng đơn giản, chỉ cần đặt một câu truy vấn là chúng hồi đáp:
[Câu truy vấn] ⇨ [LLM] ⇨ [Hồi đáp]
[Câu truy vấn] và [Hồi đáp] thường là văn bản đơn thuần. Câu hỏi tò mò đặt ra với chúng ta là mạng nơ-ron (Neural Network – viết tắt là NN) xử lý đầu vào trực tiếp từ văn bản hay phải thông qua một biến đổi nào trước khi xử lý? Câu trả lời là có biến đổi văn bản thành mã với tên gọi là token trước khi chạy mạng nơ-ron. Có thể hình dung một cách nôm na như sau:
[Câu truy vấn] ⇨ [«chuỗi token» ➙ ⦃NN⦄ ➙ «chuỗi token»] ⇨ [Hồi đáp]
Trong sơ đồ trên:
- Văn bản của [Câu truy vấn] được mã hóa thành chuỗi token
- Chuỗi token là đầu vào của mạng nơ-ron (ký hiệu ⦃NN⦄)
- Đầu ra của mạng nơ ron là một chuỗi token khác
- Giải mã chuỗi token đầu ra thành văn bản [Hồi đáp]
Về vấn đề từ nguyên (etymology), tôi cũng đã hỏi “ý kiến” của các chatbot về cách dịch từ “token” ra tiếng Việt. Chúng “đồng loạt” khuyên là nên giữ nguyên từ token như là một từ “nhập khẩu” từ tiếng Anh và “đồng hóa” từ này thành từ tiếng Việt. Vì vậy, trong khuôn khổ bài đàm luận này, tôi xin nghe theo lời khuyên của chúng.
Bài post này có cấu trúc như sau:
- Word-level Tokenizer: nguyên lý việc mã hóa “từ” → token
- Character-level Tokenizer: nguyên lý việc mã hóa “ký tự” → token
- Byte-pair encoding: nguyên lý nén chuỗi ký tự → token
- BBPE (Byte-level Byte Pair Encoding) Tokenizer: nguyên lý nén chuỗi byte → token
- Tiktokenizer: trải nghiệm biến đổi văn bản thành token.
- Multimodal [image, video, audio] token?: một số phương pháp mã hóa đa phương thức [ảnh, video, âm thanh] → token
- Context Window: RAM của LLM
1. Word-level Tokenizer
Trong thời kỳ đầu của các nghiên cứu NLP (Natural Language Processing) – trước năm 2018, các mô hình mạng nơ-ron (Neural Network) thường dùng phương pháp mã hóa Word-level Tokenizer, ví dụ: Word2Vec (Mikolov et al., 2013), GloVe (Pennington et al., 2014), ELMo (Peters et al., 2018).
Ý tưởng là chia đoạn văn bản thành tổ hợp của các từ. Ví dụ đoạn văn bản trong tiếng Anh: “Hello World!” được tách thành chuỗi các từ [“Hello”, “World”, “!”]. Hay đoạn văn bản trong tiếng Việt: “Trăm năm trong cõi người ta” được tách thành chuỗi các từ [“Trăm”, “năm”, “trong”, “cõi”, “người”, “ta”]. Mỗi một từ ứng với một mã số nguyên. Khi lập trình thì đây là một dạng từ điển, có thể tra cứu hai chiều: cho một từ sẽ tìm được mã của từ đấy; cho một mã sẽ tìm ra được từ tương ứng.
Các từ được tách ra thuộc một bộ từ vựng nào đấy (Vocabulary – viết tắt là Vocab). Ví dụ: tiếng Anh thì Vocab khoảng hơn 1 triệu từ vựng; tiếng Việt: theo “Từ điển tiếng Việt” của Hoàng Phê (Viện Ngôn ngữ) khoảng 50 nghìn (thực tế trong sách báo con số này khoảng 80 nghìn). Nếu tính tất cả các ngôn ngữ trên thế giới thì Vocab của tất cả các ngôn ngữ có số lượng từ vựng khổng lồ!
Các vấn đề gặp phải với phương pháp mã hóa Word-level Tokenizer:
- OOV (Out of Vocabulary – Không có trong từ vựng): Tình huống xảy ra ở pha inference của mô hình. Mô hình lúc này sẽ không xử lý được khi gặp một từ không nằm trong bộ từ điển lúc huấn luyện (training).
- Kích cỡ Vocab quá lớn: Tốn nhiều bộ nhớ và kém hiệu quả tại hàm đầu ra softmax.
- Khó tổng quát hóa: Các biến hình (metamorph) của từ (số nhiều, viết hoa/thường) cần được xử lý riêng biệt, dẫn đến kích cỡ Vocab tăng.
- Kém linh hoạt khi xử lý đa ngôn ngữ: một số ngôn ngữ (như tiếng Trung, tiếng Nhật, tiếng Thái, tiếng Lào, tiếng Khmer) người ta có thể viết câu thành một mạch liền các từ, không có dấu cách. Trong trường hợp đó, thuật toán trên biến toàn bộ câu thành 1 từ.
2. Character-level Tokenizer
Để khắc phục nhược điểm nói trên, một cách tự nhiên người ta nghĩ đến cách chia nhỏ đoạn văn bản thành chuỗi ký tự. Cách này có tên gọi là Character-level Tokenizer. Ví dụ đoạn văn bản trong tiếng Anh: “Hello World!” được tách thành chuỗi [‘H’,’e’, ‘l’, ‘l’, ‘o’, ‘ ‘, ‘W’, ‘o’, ‘r’, ‘l’, ‘d’, ‘!’]. Các ký tự được lấy từ bộ mã Unicode nên phủ hết tất cả các ngôn ngữ trên thế giới.
Character-level Tokenizer thường được sử dụng trong một số ngữ cảnh đặc biệt:
- Xử lý các loại văn bản “không chính thức” như tiếng lóng, viết tắt – không tuân theo các qui tắc ngữ pháp, văn bản có nhiều lỗi chính tả, …
- Xử lý các kho ngữ liệu của ngôn ngữ không thông dụng, các phương ngữ hiếm gặp, …
- Nhận dạng ký tự (OCR), nhận dạng giọng nói (ASR). Các mô hình loại này thường chỉ cần dữ liệu đầu vào có dung lượng nhỏ, đối tượng xử lý là tập hợp các ký tự của ngôn ngữ tương ứng.
Tưởng chừng như cách mã hóa này khắc phục được các nhược điểm của Word-level Tokenizer. Tuy nhiên, không hẳn vậy. Có thể chỉ ra vài nhược điểm của giải pháp này:
- Tổng số các ký tự của tất cả các ngôn ngữ trên thế giới, trong phiên bản Unicode 16.0 là 292,531 (nguồn) – gần 300 nghìn. Con số này – số từ vựng của Vocab – vẫn là lớn và kém hiệu quả trong xử lý đầu ra của mạng nơ-ron (NN).
- Unicode là bộ mã liên tục phát triển, các ký tự mới liên tục được bổ sung. Vì vậy, một phiên bản cũ của Tokenizer có thể không nhận ra ký tự mới được Unicode bổ sung nên vẫn gặp vấn đề OOV (Out of Vocabulary – Không có trong từ vựng) khi mô hình được triển khai trong thực tế (inference).
3. Byte-pair encoding
Byte-pair encoding (còn được gọi là BPE) là một thuật toán, được Philip Gage mô tả lần đầu tiên vào năm 1994 (nguồn), để mã hóa các chuỗi văn bản thành các chuỗi ngắn hơn (một dạng nén dữ liệu).
Sau đây là một ví dụ ngắn (trên Wikipedia):
- Dữ liệu đầu vào để mã hóa: aaabdaaabac
- Vì cặp byte ‘aa’ xuất hiện nhiều nhất, chúng ta sẽ tạo một quy tắc gộp bằng cách thay thế tất cả các cặp này bằng một byte mới chưa có trong dữ liệu: Z = aa. Bây giờ, dữ liệu trở thành: ZabdZabac
- Lặp lại bước 2: tìm cặp xuất hiện thường xuyên nhất tiếp theo là ‘ab’, tạo quy tắc gộp: Y = ab. Giờ đây, dữ liệu là: ZYdZYac.
- Lặp lại một lần nữa với quy tắc mới (đệ quy): X = ZY. Và dữ liệu hiện tại là: XdXac.
- Không còn cặp nào xuất hiện nhiều hơn một lần trong dữ liệu. Vì vậy, chúng ta đã có phiên bản nén của dữ liệu: XdXac, và có thể giải mã lại dữ liệu ban đầu bằng ba quy tắc gộp:
X = ZY; Y = ab; Z = aa
Chú ý từ “Byte” ở đây có nghĩa là “Character” (ký tự) chứ không phải là byte vật lý gồm 8 bit.
Phân tích ví dụ trên chúng ta thấy:
Vocab ban đầu có 4 từ vựng (gồm ‘a’, ‘b’, ‘c’, d’). Sau mỗi lần thay thế, Vocab tăng thêm 1. Đối với các văn bản có dung lượng lên đến gigabyte hay terabyte thì Vocab có thể trở nên cực lớn – điều mà người ta không mong muốn. Vì vậy, trong thực tế người ta giới hạn kích cỡ của Vocab ở mức tối ưu nào đó. Tức là vòng lặp thay thế sẽ phải dừng lại khi không còn chỗ cho từ vựng mới.
Một cách tổng quát, thuật toán BPE có thể được mô tả như sau:
- Thay thế các cặp ký tự liền kề (c1c2) lặp nhiều lần nhất (n lần) bằng một ký tự mới (k1). Bằng thao tác này chúng ta giảm số ký tự của văn bản bằng số lần lặp (n). Lý do? Văn bản gốc có n cặp ký tự liền kề. Nghĩa là số ký tự của n cặp này là n×2. Còn số ký tự sau khi được thay thế là n. Như vậy, số ký tự giảm xuống là (n×2)-n = n. Sau mỗi lần thay thế, người ta thêm vào một dòng trong “bảng tra cứu” dạng k1 ⇨ c1c2. Mục đích của việc này là để giải mã sau này.
- Thuật toán lặp lại bước này cho đến khi không còn cặp ký tự liền kề nào lặp lại trong dãy văn bản hoặc không còn chỗ để tạo ra ký tự mới.
- Việc giải mã được thực hiện bằng vòng lặp đi từ đáy “bảng tra cứu” lên đầu bảng: thay thế các ký tự (kl) bằng các cặp (cicj).
Hạn chế của BPE?
- Phải định nghĩa trước bảng ký tự.
- Gặp khó khăn với các ký tự Unicode đặc biệt (emoji, dấu tiếng Việt, chữ Hán…).
- Không tốt khi xử lý đa ngôn ngữ hoặc văn bản lạ.
Chú ý rằng Unicode có nhiều cách mã hóa như UTF-8, UTF-16, UTF-32, … Để tránh bị lạc đề, Unicode trong bài này đồng nghĩa với cách mã hóa UTF-8. (Để nói sâu hơn về Unicode chắc cần đến một bài post khác.)

“Ký tự” trong ngôn ngữ của Unicode được gọi là “code point”. Mỗi một ký tự mã hóa bằng UTF-8 có độ dài từ 1 đến 4 byte, tùy thuộc vào giá trị của code point (xem hình minh họa về chuyển đổi Code point ↔ UTF-8 ở trên). 128 ký tự có code point từ 0 đến 127 (ASCII) chỉ cần 1 byte để mã hóa. 1,920 ký tự tiếp theo cần 2 byte, 61,440 ký tự tiếp theo cần 3 byte và 1,048,576 cuối cùng cần 4 byte.
4. BBPE (Byte-level Byte Pair Encoding) Tokenizer
Byte-level Byte Pair Encoding (BBPE) giống hệt BPE về thuật toán nén, chỉ khác về dữ liệu. Dữ liệu đầu vào của BBPE là byte “thô” (giá trị nhị phân), chứ không phải là ký tự.
Ví dụ: ký tự ‘é’ là 1 ký tự, nhưng trong mã hóa UTF-8 là 2 byte: 0xc3 0xa9.
Thuật toán BBPE rất đơn giản:
- Chuyển đổi chuỗi ký tự gốc thành Unicode mã hóa UTF-8 (là chuỗi các byte thô).
- Áp thuật toán BPE lên chuỗi đã mã hóa.
Lưu ý:
- Vocab ban đầu của BBPE có kích cỡ là 256 (vì 1 byte có 2^8 = 256 giá trị khác nhau). Chú ý rằng Vocab lúc này có 256 “từ vựng”, các từ vựng này có mã từ 0 đến 255.
- Mỗi lần áp thuật toán BPE thì Vocab tăng thêm 1 “từ vựng”.
- Như vậy, mỗi lần dữ liệu được nén là một lần số “từ vựng” của Vocab có thể tăng lên 1. Rõ ràng là không thể để số từ vựng của Vocab tăng lên một cách không kiểm soát. Người ta phải tìm một điểm “trade-off” sao cho dữ liệu không dài quá mà số từ vựng của Vocab cũng không quá lớn.
Ý tưởng dùng thuật toán BBPE để biến chuỗi ký tự thành token có lẽ lần đầu được đề cập tại GPT-2, mục “2.2. Input Representation” (trang 4). Vocab của GPT-2 có số từ vựng là 50,257. Nhìn vào size này của Vocab, chúng ta biết được rằng GPT-2 cho phép vòng lặp nén dữ liệu có thể lên đến 50 nghìn. Sau tối đa 50 nghìn lần, dù văn bản còn có thể nén thêm được nữa người ta cũng sẽ dừng lại.
(Vocab ban đầu có size là 256, cộng với một ký tự đặc biệt ‘<|endoftext|>’ là 257, số vị trí còn lại – 50,000 – là dành cho các “từ vựng” mới lập sau mỗi vòng nén dữ liệu.)
5. Tiktokenizer
Tiktokenizer là phần mềm chạy trên Web cho phép chúng ta trải nghiệm biến đổi văn bản thành token của các chatbot thông dụng hiện nay: https://tiktokenizer.vercel.app/.

Một screenshot từ https://tiktokenizer.vercel.app/?model=gpt2
Sau khi kích hoạt phần mềm (chạy trên Web), ở phía góc phải trên, anh/chị có thể chọn Tokenizer – (mặc định ban đầu là gpt-4o – tên một chatbot của OpenAI). Chúng ta có thể chọn các Tokenizer khác bằng cách mở hộp thả và click vào tên của Tokenizer cần chọn.
Khi chúng ta đưa vào đoạn văn bản ở ô phía bên cột trái (phía dưới chữ Tiktokenizer) thì phần mềm cho kết quả mã hóa của đoạn văn bản này bên phía cột phải. Cột phải này có 3 ô:
- Ô trên cùng có tên là Token count (đếm token) báo cho chúng ta biết số token mà mô hình chuyển đổi từ đoạn văn bản chúng ta vừa nhập vào.
- Phía dưới ô Token count là đoạn văn bản mã hóa. Chú ý rằng các đoạn ký tự cùng một màu nền là tương ứng với 1 token – và token đó được liệt kê ở ô dưới.
- Mỗi lần chúng ta di chuột (hover) lên đoạn văn bản cùng màu nền (ở ô giữa) thì chúng ta thấy chuỗi các “từ vựng” tương ứng được bôi nền cùng màu (selected) nằm ở ô dưới.
Ví dụ: Lấy trải nghiệm của tôi (xem screenshot minh họa ở trên) để anh/chị dễ theo dõi:
- Tại hộp thả chọn các Tokenizer (cột phải) chọn gpt2
- Ô dưới Tiktokenizer, nhập vào đoạn văn bản: Hello World! <|endoftext|>
- Khi di chuột lên cụm ký tự ‘Hello’ (nền xanh), chúng ta thấy mã 15496 được bôi nền xanh (ở ô dưới)
- Khi di chuột lên cụm ký tự ‘ World’ (kể cả dấu trắng ngay phía trước, nền vàng), chúng ta thấy mã 2159 được bôi nền vàng
- Khi di chuột lên ký tự ‘!’ (nền xanh), chúng ta thấy mã 0 được bôi nền xanh
- Khi di chuột lên ký tự dấu cách ‘ ’ (nền xanh), chúng ta thấy mã 220 được bôi nền xanh
- Khi di chuột lên cụm ký tự ‘<|endoftext|>’ (nền vàng), chúng ta thấy mã 50256 được bôi nền vàng
Tương tự như vậy, anh/chị có thể trải nghiệm với Tokenizer cl100k_base (Tokenizer này được sử dụng cho ChatGPT) và các Tokenizer khác.
6. Multimodal [image, video, audio] token?
ChatGPT và các chatbot khác khởi đầu với đầu vào là văn bản (text). Sau đó người ta đã tích hợp thêm các phương thức khác như ảnh, video, âm thanh. Hẳn nhiên, muốn xử lý được đầu vào thì dữ liệu của các phương thức đó cũng cần được mã hóa. Mã hóa như thế nào? Đây là vấn đề khá phức tạp, ở đây tôi chỉ đề cập một cách đại ý.
Phương thức chung là chuyển đổi các đầu vào phi văn bản (hình ảnh, video, âm thanh) thành biểu diễn nhúng (embbeding) hoặc chuỗi giống như token để có thể xử lý được.
Hình ảnh
Cách tiếp cận phổ biến: tiền xử lý (huấn luyện trước) mô hình thị giác (vision model) (ví dụ: CLIP, ViT). Nghĩa là ảnh được xử lý riêng trước khi “nhúng” vào LLM:
- [Ảnh] → [Vision model] → [embedding]
- Căn chỉnh embeding của Vision model để tương thích với embedding của LLM (phép chiếu – project)
Video
Cách tiếp cận phổ biến: Trích xuất khung hình (frame), rồi mã hóa khung hình như ảnh tĩnh:
- Trích xuất các khung hình chính (keyframes).
- Mỗi khung hình được mã hóa như ảnh tĩnh.
- Có thể thêm xử lý theo thời gian (như TimeSformer hoặc 3D ConvNet).
Âm thanh
- Sóng âm → Phổ tần số → Bộ mã hóa thị giác:
- Chuyển âm thanh sang phổ Mel (spectrogram).
- Xử lý phổ này như ảnh → dùng bộ mã hóa ảnh.
- Mô hình âm thanh trực tiếp:
- Dùng mô hình như Whisper, Wav2Vec2.0, hoặc AudioMAE để mã hóa sóng âm thành embedding.
- Token hóa rời rạc âm thanh:
- Dùng Audio Tokenizer (ví dụ: EnCodec) để biến âm thanh thành token rời rạc.
7. Context window
Context window (cửa sổ ngữ cảnh) trong LLM là số lượng tối đa token mà mô hình có thể “nhìn thấy” hoặc xử lý cùng lúc khi tạo sinh hoặc “hiểu” văn bản.
Điều đó có nghĩa là gì?
Context window xác định tổng số token mà mô hình có thể tiếp nhận trong một lần hội thoại (chat) — bao gồm cả đầu vào và các phản hồi trước đó của chính hội thoại đấy.
Takeaway: Đối với mỗi chủ đề mới, khi sử dụng chatbot chúng ta nên tạo một hội thoại (chat) mới. Đừng lười tạo chat mới vì nếu cứ dùng một chat cho hàng loạt câu truy vấn thì một cách vô tình chúng ta đã thu hẹp context window!
Tại sao nó quan trọng?
- Ghi nhớ hội thoại:
- Context window càng lớn, mô hình càng nhớ được nhiều thông tin (lịch sử hội thoại, lệnh, ngữ cảnh) trong cùng một phiên làm việc.
- Nếu vượt quá giới hạn, các token cũ sẽ bị cắt bỏ.
- Xử lý văn bản dài:
- Context window lớn cho phép mô hình làm việc với tài liệu dài, mã lập trình hoặc cuộc hội thoại nhiều bước.
- Tăng độ chính xác và mạch lạc:
- Mô hình hoạt động hiệu quả hơn khi có đủ ngữ cảnh liên quan để suy luận hoặc trả lời chính xác.
Một cách hình ảnh
Hãy tưởng tượng context window giống như bộ nhớ RAM của mô hình:
- Bộ nhớ nhỏ → chỉ nhớ được câu gần nhất.
- Bộ nhớ lớn → nhớ được toàn bộ cuộc trò chuyện hoặc tài liệu dài.
Ví dụ về context window
Mô hình | Kích thước context window (Số từ tương đương – ước tính) |
GPT-2 | 1.024 token (~750 từ) |
GPT-3 | 2.048 token (~1.500 từ) |
GPT-3.5 Turbo | 4.096 hoặc 16.000 token (~3.000–12.000 từ) |
GPT-4 Turbo | 128.000 token (~96.000 từ) |
Claude 3, 4 | 200.000 token (~150.000 từ) |
Gemini 2.5 Pro | 1 triệu token (~750.000 từ) |
Grok 3 | 128.000 token (~96.000 từ) |
Grok 4 | 256.000 token (~190.000 từ) |
DeepSeek V3 | 128.000 token (~96.000 từ) |
Qwen3 | 32.768 token (~14.000 từ) |
Llama 4 Scout | 10 triệu token (~7.500.000 từ) |
Suy ngẫm chậm
Gần đây, người ta nói nhiều đến một “ẩn dụ” (metaphor) có tên “AI Factory” (Nhà máy AI):
- Đầu vào: Dữ liệu (ví dụ: văn bản, hình ảnh, video, âm thanh)
- Xử lý: Huấn luyện mô hình, tinh chỉnh, suy luận (inference)
- Đầu ra: Dự đoán, phân loại, quyết định, nội dung, …
Theo cách hiểu này, “AI Factory” là việc tự động hóa và công nghiệp hóa vòng đời phát triển AI — từ thu thập dữ liệu đến triển khai và giám sát mô hình.
Nếu nhìn vào bên trong nhà máy AI, chúng ta sẽ thấy gì? Toàn bộ hoạt động của nhà máy này là nhào nặn, biến đổi, xử lý các token!
«chuỗi token» ➙ ⦃NN⦄ ➙ «chuỗi token»
TS. Lê Văn Lợi
Nguyên Viện trưởng Viện tin học Doanh nghiệp – VCCI