Bạn gõ "Viết cho tôi một function tính fibonacci" vào ChatGPT. Ba giây sau, code đẹp đẽ hiện ra. Giữa hai thời điểm đó — lúc Enter được nhấn và lúc token đầu tiên xuất hiện — có rất nhiều thứ đang chạy. Phần lớn dev chỉ biết “nó là một cái LLM, trả lời câu hỏi”. Tương đương với mức hiểu biết “database là cái chỗ lưu data” vậy.
Bài này mở hộp đen. Không công thức toán, không code PyTorch. Chỉ mental model — đủ để bạn đọc các bài sau trong series không bị lạc, và đủ để bạn hiểu tại sao quantization, fine-tuning, RAG lại hoạt động theo cách của chúng.
Dành cho: dev có 3+ năm kinh nghiệm, đã xài ChatGPT/Claude mỗi ngày nhưng chưa bao giờ đọc kỹ về cơ chế bên dưới.
Mental model tổng quát
Trước khi đi vào chi tiết, đây là khung xương của toàn bộ hệ thống:
text input text output
| ^
v |
[ tokenize ] -> [ embed ] -> [ transformer x N ] -> [ project ] -> [ sample ]
|
v
append vào input
chạy lại từ đầu
Năm bước, lặp lại cho đến khi model quyết định “xong” (hoặc hết giới hạn). Mỗi lần lặp sinh ra đúng một token. Không hơn. Nếu câu trả lời dài 500 tokens, model chạy pipeline này 500 lần.
Điều quan trọng nhất phải ngấm ngay: LLM không “viết câu trả lời”. Nó dự đoán token tiếp theo, một token một lần, liên tục cho đến khi dừng.
Phần 1: Text biến thành số — tokenization
CPU không hiểu chữ. Nó hiểu số. Nên việc đầu tiên là biến "Viết cho tôi..." thành một dãy số.
Không phải biến thành character codes (ASCII/Unicode). Mà biến thành tokens — đơn vị trung gian giữa “chữ” và “từ”.
Ví dụ với GPT-4 tokenizer (cl100k_base):
import tiktoken
enc = tiktoken.get_encoding("cl100k_base")
enc.encode("Viết cho tôi một function tính fibonacci")
# [53, 72, 26683, 16326, 21989, 31341, 66211, 25113, 283, 25979,
# 6369, 16619, 25968, 17245, 276, 55031]
Đoạn text 39 ký tự bị chia thành 16 tokens, mỗi token là một integer ID từ 0 đến 100,000 (kích thước vocab).
Tokens không nhất thiết là “từ”. Có thể là:
- Từ đầy đủ:
"function"→ 1 token - Mảnh từ:
"fibonacci"→ nhiều token ("fib","on","acci") - Ký tự đặc biệt / dấu tiếng Việt: mỗi ký tự có thể tốn 2-3 tokens
Đây là lý do tiếng Việt “đắt” hơn English trên LLM — vì tokenizer train trên corpus English nên chưa “học” được cách gộp từ tiếng Việt hiệu quả. Chi tiết bài Tiếng Việt tốn hơn x2 token? Data nói khác đã phân tích.
Kết quả sau bước 1: text của bạn thành [53, 72, 26683, ...] — một list số nguyên.
Phần 2: Số trở thành ý nghĩa — embedding
Có tokens rồi, nhưng số 26683 với model là vô nghĩa — chỉ là index. Cần biến mỗi token thành một thứ mà model có thể “suy nghĩ” được.
Giải pháp: mỗi token ID map sang một vector — một dãy số thực dài 4096 chiều (với Llama-3-8B), hoặc 12288 chiều (với GPT-3 175B).
token_id = 26683 (số nguyên, 1 chiều, vô nghĩa)
|
| lookup trong embedding matrix
v
embedding = [0.234, -1.827, 0.041, ..., 0.682] (4096 số thực)
Embedding matrix là một bảng tra cứu khổng lồ: nếu vocab có 100,000 tokens và mỗi token thành vector 4096 chiều, matrix này chứa 100,000 × 4096 = 410 triệu con số. Đây là “từ điển số” của model — các số này được học trong quá trình training.
Ý nghĩa của vector: các token có “nghĩa gần nhau” sẽ có vector gần nhau (theo khoảng cách cosine). Ví dụ vector của "vua" trừ "đàn ông" cộng "đàn bà" sẽ rất gần vector của "nữ hoàng". Đây là trick cổ điển của word2vec từ 2013, và LLM kế thừa tư tưởng đó ở scale lớn hơn nhiều.
Dev muốn kiểm tra? API của OpenAI/Cohere cho bạn lấy embedding trực tiếp (endpoint embeddings). Đây cũng là nền tảng của RAG — sẽ gặp lại ở bài 25.
Kết quả sau bước 2: list tokens thành ma trận [16 tokens × 4096 dim]. Model bắt đầu có cái để “suy nghĩ”.
Phần 3: Model đọc context — attention
Đây là phần làm nên tên tuổi paper “Attention is All You Need” (2017), và là lý do tại sao Transformer thay thế RNN/LSTM trong mọi LLM hiện đại.
Bài toán: trong câu "Con mèo ngồi trên thảm vì nó mềm", đại từ "nó" refer đến ai — con mèo hay tấm thảm? Con người đọc biết ngay là thảm (vì thảm mới mềm). Model phải học được chuyện này.
Cơ chế attention: khi xử lý token "nó", model nhìn lại toàn bộ các token trước đó, tính điểm “mức độ liên quan” giữa "nó" với từng token, rồi pha trộn thông tin từ chúng theo điểm đó.
token hiện tại: "nó"
|
v
nhìn lại các token trước:
"Con" score: 0.05
"mèo" score: 0.15
"ngồi" score: 0.02
"trên" score: 0.03
"thảm" score: 0.70 <-- điểm cao nhất
"vì" score: 0.05
|
v
output = trộn vector của các token trên theo score
~ 70% "thảm" + 15% "mèo" + phần nhỏ khác
Kết quả: vector đại diện cho "nó" sau khi đi qua attention chứa nhiều thông tin của "thảm" nhất. Model đã “hiểu” đại từ chỉ cái gì.
Đây là một attention head. Mỗi layer Transformer có nhiều head (32-128 head), mỗi head học một khía cạnh khác của quan hệ giữa các token — head này chuyên về syntax, head kia chuyên về semantic, head khác chuyên về long-range dependency. Đây là lý do gọi là “multi-head attention”.
Và layer được stack 32-96 lần. Mỗi lần stack, model “suy nghĩ sâu thêm một bước”.
Kết quả sau bước 3: ma trận [16 × 4096] được transform qua N layers, kết quả vẫn là [16 × 4096] nhưng giờ mỗi vector đã được “enrich” bằng context từ các token khác. Riêng vector cuối cùng (ứng với token cuối câu input) chứa thông tin cô đặc nhất — đây là vector model sẽ dùng để dự đoán token tiếp theo.
Phần 4: Dự đoán token tiếp theo — projection & sampling
Sau khi qua hết Transformer, ta có vector cuối cùng kích thước 4096. Giờ phải biến nó thành “token tiếp theo nên là gì”.
Bước projection: nhân vector 4096 với một ma trận [4096 × 100,000] (vocab size). Kết quả là vector 100,000 chiều — gọi là logits. Mỗi phần tử ứng với một token trong vocabulary.
hidden state (4096) x unembedding matrix (4096 x 100000)
|
v
logits (100000) <- mỗi số cho biết model "ưa thích" token đó thế nào
Logits chưa phải xác suất — có thể âm, có thể rất lớn. Bước kế tiếp: softmax biến logits thành xác suất (tổng bằng 1):
logits: [2.5, -1.2, 8.7, 0.3, ..., 4.1]
|
| softmax
v
probs: [0.001, 0.0001, 0.6, 0.002, ..., 0.05]
^ ^
token "the" token "function" (prob 60%)
Ở đây có một điểm thú vị: model không luôn chọn token có xác suất cao nhất (gọi là greedy decoding). Đó là lý do ChatGPT cho câu trả lời khác nhau mỗi lần dù hỏi cùng một câu.
Các chiến lược sampling:
| Strategy | Cách chọn | Khi dùng |
|---|---|---|
| Greedy | Luôn token top-1 | Deterministic output (code gen, translation) |
| Temperature | Làm “nóng” phân phối: cao → đa dạng, thấp → bảo thủ | Mặc định 0.7, creative writing 1.0+ |
| Top-k | Chỉ xét k tokens top | Giảm nhiễu |
| Top-p (nucleus) | Xét tokens cho đến khi tích luỹ p% xác suất | Dùng phổ biến nhất hiện nay |
Với temperature = 1.0 và top-p = 0.9, model ngẫu nhiên lấy mẫu từ các tokens hợp lý nhất theo xác suất của chúng. Đó là lý do output đa dạng nhưng không điên loạn.
Kết quả sau bước 4: một token ID duy nhất được chọn.
Phần 5: Loop — sinh ra từng token một
Token mới vừa sinh ra được nối vào cuối input, rồi toàn bộ pipeline chạy lại từ đầu:
input: "Viết cho tôi một function tính fibonacci"
model sinh: "def"
input: "... fibonacci def"
model sinh: " fib"
input: "... fibonacci def fib"
model sinh: "onacci"
... tiếp tục ...
Quá trình này gọi là autoregressive generation. Dừng khi:
- Model sinh ra token đặc biệt
<|endoftext|>(EOS) - Đạt
max_tokensdo user set - Timeout
Lý do quan trọng cần biết: mỗi lần sinh 1 token, model đều phải chạy lại tất cả các bước trên. Đây là lý do:
- LLM inference chậm (càng sinh nhiều token càng lâu)
- Cần GPU mạnh (mỗi step làm hàng tỷ phép tính)
- Có kỹ thuật tối ưu như KV cache (lưu kết quả attention của các token đã xử lý, không tính lại) — sẽ gặp ở bài 24
Phần 6: Tại sao nó “thông minh”?
Bạn đã đi hết pipeline. Giờ câu hỏi nặng: tại sao một cái máy dự đoán token tiếp theo lại trả lời được câu hỏi, viết được code, giải được toán?
Câu trả lời ngắn: vì nó được train trên lượng dữ liệu khổng lồ theo đúng format đó.
Training data của Llama-3 là 15 trillion tokens — tương đương đọc hết toàn bộ Wikipedia của loài người 800 lần. Trong khối data đó có:
- Mọi cuốn sách giáo khoa — model học được cách giải toán
- Toàn bộ Stack Overflow / GitHub — model học code
- Mọi bài báo khoa học — model học lý thuyết
- Hàng tỷ hội thoại Reddit, forum — model học cách trả lời câu hỏi
Khi bạn hỏi "Viết cho tôi function fibonacci", model không “nghĩ ra” — nó đã thấy pattern "Viết function X" → "def X():..." hàng triệu lần trong training data. Nó dự đoán chuỗi token tiếp theo có xác suất cao nhất, và chuỗi đó vô tình trông giống code Python đúng.
Nói cách khác, LLM là nén kho tri thức nhân loại vào 8 tỷ (hoặc 400 tỷ) parameters. Tri thức đó được hồi nhớ lại khi bạn prompt đúng cách.
Đây là lý do prompt engineering hoạt động — bạn đang cung cấp context để model match được pattern phù hợp nhất trong kho đã học.
Và đây cũng là lý do hallucination xảy ra — nếu bạn hỏi chuyện model chưa thấy trong training data, nó vẫn dự đoán token tiếp theo dựa trên pattern gần nhất nó biết, kể cả khi sai sự thật. Model không “biết rằng nó không biết”.
Cheatsheet
| Khái niệm | Bản chất | Input | Output |
|---|---|---|---|
| Tokenize | Text → list integer ID | "Hello world" | [9906, 1917] |
| Embed | Token ID → vector số thực | 9906 | [0.2, -1.8, ..., 0.6] (4096 số) |
| Attention | Pha trộn context giữa các token | Matrix [N × D] | Matrix [N × D] đã enrich |
| Transformer layer | Attention + MLP + residual | Matrix [N × D] | Matrix [N × D] đã “thinking” |
| Projection | Vector → logits vocab | Vector D | Vector V (vocab size) |
| Softmax | Logits → probabilities | [2.5, -1.2, 8.7, ...] | [0.01, 0.001, 0.9, ...] |
| Sampling | Chọn token theo phân phối | Probabilities | Một token ID |
| Autoregressive | Lặp lại toàn bộ pipeline từng token | Input + token mới | Token tiếp theo |
Ghi nhớ nếu không nhớ gì khác:
- LLM sinh 1 token / lần, không phải cả câu
- Tokens là đơn vị trung gian giữa ký tự và từ
- Mỗi token thành một vector, và mọi thứ sau đó đều là toán ma trận
- Attention là cách model đọc context — token này “nhìn” các token khác
- Output = xác suất trên toàn vocab, sampling là một lựa chọn ngẫu nhiên có trọng số
Lời kết
Bây giờ bạn đã có skeleton của cả pipeline. Bài sau sẽ đào sâu vào từng bước — bắt đầu từ Linear Algebra cho LLM (bài 2). Nghe nặng đô nhưng không phải — chỉ cần hiểu 4 thứ: vector, matrix, matrix multiplication, dot product. Đủ để đọc paper và code PyTorch không bị khớp.
Sau đó là Calculus (bài 3), rồi Probability (bài 4), rồi chính thức code Neural Network từ zero (bài 5). Xong 5 bài, bạn sẽ tự build được một nhỏ MLP train MNIST mà không cần copy từ tutorial.
Nếu muốn học sớm hơn, xem video Karpathy “The spelled-out intro to neural networks and backpropagation: building micrograd” — một tiếng rưỡi, code backprop từ đầu bằng Python thuần. Đây là bệ phóng cho mọi thứ trong series này.