Từ transformer đến LLM: causal mask và train song song
Causal mask ép token chỉ nhìn quá khứ để train song song mọi vị trí, còn inference vẫn sinh tuần tự từng token, chậm dần theo số token sinh ra.
TL;DR: LLM hiện đại là decoder-only transformer: nhiều transformer block xếp chồng, dùng self-attention kèm causal mask — mỗi token chỉ được nhìn token đứng trước nó, không bao giờ thấy token đứng sau. Ràng buộc tưởng hạn chế này lại cho phép train song song: nhãn (token kế tiếp) đã có sẵn trong dữ liệu, model tính loss cho toàn bộ vị trí trong một lần forward, không cần đợi tuần tự như model đọc kiểu cũ. Nhưng inference thì ngược lại — token kế tiếp chưa tồn tại, phải sinh xong token này mới biết token sau, nên vòng lặp next-token quay lại tuần tự, độ trễ tỉ lệ thuận với số token sinh ra.
Bạn dùng API của một LLM để tóm tắt một tài liệu dài — trả lời gần như ngay lập tức. Cùng ngày, bạn yêu cầu chatbot viết một bài luận dài — chữ chạy ra chậm rãi, nối tiếp nhau, càng dài càng phải đợi lâu. Cùng một model, cùng kiến trúc, nhưng một tác vụ nhanh gần như tức thì, tác vụ kia chậm dần theo đúng số từ sinh ra.
Nghịch lý lộ rõ hơn ở phía train: model học từ toàn bộ một đoạn văn dài trong một lần forward duy nhất — không xử lý tuần tự từng token như model đọc kiểu cũ (Nỗi đau 2, bài 01 module này). Vậy vì sao xử lý một chuỗi có sẵn thì song song được, còn sinh một chuỗi mới lại tuần tự? Bài này trace đúng cơ chế tạo ra bất đối xứng đó: causal mask.
1. Analogy — chấm bài văn mẫu có sẵn vs tự viết bài trong giờ thi
Hình dung một bài kiểm tra tự luận: học sinh phải viết một đoạn văn hoàn chỉnh, viết tới đâu mới biết câu tiếp theo tới đó — không thể biết trước câu thứ mười sẽ ra sao khi mới viết xong câu thứ ba. Đây là lúc làm bài — quá trình bắt buộc tuần tự.
Nhưng giáo viên chấm bài thì khác hẳn. Cô có sẵn một bài văn mẫu hoàn chỉnh và chấm cùng lúc mọi câu: câu ba đúng hay sai, câu bảy đúng hay sai — tất cả kiểm tra song song, vì đáp án của mọi câu đã có sẵn trên giấy, không câu nào phải đợi câu khác chấm xong trước. Điều giáo viên phải tự nhắc mình: khi chấm câu ba, không được liếc trước câu bảy để "đoán" học sinh viết đúng — chỉ chấm dựa trên những gì đã viết trước câu ba.
| Đời thường | Transformer |
|---|---|
| Học sinh viết bài — chưa biết câu sau sẽ viết gì | Inference — token kế tiếp chưa tồn tại, phải sinh xong token này mới tới token sau |
| Giáo viên chấm bài mẫu có sẵn — kiểm tra mọi câu cùng lúc | Train — token kế tiếp đã có sẵn trong dữ liệu, tính loss cho mọi vị trí trong một lần forward |
| Giáo viên phải tự cấm mình liếc câu sau khi chấm câu trước | Causal mask — ép model chỉ tính điểm dựa trên token đứng trước, dù đáp án tương lai đang nằm ngay đó trong dữ liệu |
Chấm bài (đáp án có sẵn) chấm được song song. Làm bài (chưa có gì viết ra) thì buộc phải tuần tự. Train giống chấm bài, inference giống làm bài — cùng một model, khác hẳn ràng buộc.
2. Causal mask — mỗi token chỉ được nhìn về quá khứ
Bài trước (bài 02–03 module này) đã dựng self-attention: mỗi token tính điểm tương quan với mọi token khác trong câu, kể cả token đứng sau nó. Đó là self-attention "đầy đủ", không giới hạn hướng nhìn.
Decoder-only LLM — kiến trúc hầu hết LLM hiện đại chạy — thêm đúng một ràng buộc lên self-attention đó, gọi là causal mask (mask nhân quả). Vaswani và cộng sự, "Attention Is All You Need" (arXiv:1706.03762), gọi tên cơ chế này là "masked multi-head attention": trước bước softmax, mọi điểm attention trỏ từ token ở vị trí i tới token đứng sau nó bị gán về gần như âm vô cực, khiến trọng số sau softmax gần như bằng không. Token thứ i chỉ được nhìn token từ vị trí một tới i, không bao giờ nhìn token đứng sau i.
Viết thành ma trận cho một câu bốn token, hàng là token đang tính, cột là token được phép nhìn:
Cau 4 token: [Toi] [thich] [hoc] [may]
Toi thich hoc may
Toi 1 0 0 0 -- token 1 chi thay chinh no
thich 1 1 0 0 -- token 2 thay token 1, 2
hoc 1 1 1 0 -- token 3 thay token 1, 2, 3
may 1 1 1 1 -- token 4 thay ca 4 token
-- 1 = duoc nhin, 0 = bi che ve am vo cuc truoc softmax
Nếu bỏ hẳn causal mask, để mọi token tự do nhìn thấy cả token đứng sau nó, rồi vẫn train model dự đoán token kế tiếp như bình thường — điều gì sẽ xảy ra với quá trình học? Viết ra dự đoán của bạn trước khi đọc tiếp.
Không che tương lai, token ở vị trí i có thể tính attention trỏ thẳng vào token kế tiếp — chính là đáp án model đang phải dự đoán. Model chỉ cần học "sao chép điểm số cao vào đúng vị trí đáp án đang nằm sẵn trong input", loss lập tức gần bằng không, nhưng vô dụng lúc sinh văn bản thật vì token kế tiếp chưa hề tồn tại để mà nhìn trộm. Hiện tượng này gọi là rò rỉ nhãn (label leakage) — causal mask tồn tại chính để chặn lỗ hổng đó.
3. Vì sao causal mask không phá mất tính song song vừa đạt được?
Bài 01 module này gọi tên nỗi đau của model đọc tuần tự: h_t được tính từ h_(t-1), bước t phải đợi bước t-1 xong — chuỗi phụ thuộc chạy dọc thời gian, không song song hoá được. Attention (bài 02–03) giải quyết đúng nỗi đau đó: mỗi token nhìn thẳng mọi token khác trong một phép tính ma trận duy nhất.
Causal mask ép mỗi token chỉ nhìn về quá khứ — ràng buộc này có tái tạo lại chuỗi phụ thuộc mà bài 01 vừa thoát khỏi không? Không, vì causal mask là một ma trận cố định, biết trước ngay khi biết độ dài câu, không phụ thuộc kết quả tính toán của bất kỳ vị trí nào khác. Áp ma trận cố định đó lên attention của toàn bộ chuỗi vẫn là một phép nhân ma trận duy nhất, không phải vòng lặp phải đợi bước trước.
Cộng thêm một điều kiện chỉ đúng lúc train: dữ liệu huấn luyện có sẵn toàn bộ câu trước khi train bắt đầu — nhãn của mọi vị trí đã nằm sẵn, không cần tính xong vị trí trước để có nhãn cho vị trí sau:
function train_step(cau X gom N token, causal_mask):
H <- transformer_block(X, causal_mask) -- 1 lan goi, tinh CA N vi tri cung luc
loss <- 0
for i tu 1 den N-1: -- vong lap nay chi CONG DON loss
phan_phoi_i <- softmax(H[i])
loss <- loss + cross_entropy(phan_phoi_i, X[i+1]) -- nhan X[i+1] DA CO SAN
return loss
-- Time: 1 forward pass cho ca N vi tri, khong phai N buoc tuan tu
Khác với model đọc tuần tự, transformer_block(X, causal_mask) là một lệnh — bên trong nó, phép nhân ma trận attention xử lý mọi vị trí song song trên GPU. Vòng for phía sau chỉ cộng dồn loss cho từng vị trí, hoàn toàn không quyết định thứ tự tính toán của attention.

4. Inference vẫn tuần tự — vòng lặp next-token quay lại
Bài đó đã trace vòng lặp autoregressive: model dự đoán một phân phối xác suất cho token kế tiếp, chọn một token từ đó, nối vào chuỗi input, rồi lặp lại. Bài này vừa giải thích: đúng vòng lặp đó là lý do inference của decoder-only transformer không bao giờ song song được như train.
Ở mục 3, train song song được vì nhãn của mọi vị trí đã có sẵn trong dữ liệu — model chỉ cần bị cấm nhìn trộm. Lúc sinh văn bản mới, điều kiện đó biến mất: token kế tiếp chưa tồn tại, nó chính là thứ model đang phải tạo ra. Causal mask lúc này gần như "tự động" — không có token tương lai nào để che, vì tương lai còn chưa được viết ra.
Hệ quả: muốn có token kế tiếp, model bắt buộc phải chạy xong transformer_block với chuỗi hiện có, lấy phân phối tại đúng vị trí cuối cùng, chọn một token, nối vào, rồi chạy lại toàn bộ transformer_block một lần nữa cho chuỗi vừa dài thêm một token:
input <- token cua prompt
while chua gap token ket thuc:
H <- transformer_block(input, causal_mask) -- chay lai TU DAU, input dai them 1 token
phan_phoi_cuoi <- softmax(H[vi tri cuoi]) -- CHI can phan phoi tai vi tri cuoi cung
token_moi <- chon_tu(phan_phoi_cuoi)
input <- input + [token_moi]
-- Time: N lan goi transformer_block de sinh N token, khong phai 1 lan nhu train
Sinh một câu trả lời dài nghĩa là chạy transformer_block nhiều lần liên tiếp — mỗi lần đợi lần trước xong mới bắt đầu. Đây chính là độ trễ "chữ chạy từng dòng" ở đầu bài: độ trễ tỉ lệ thuận với số token sinh ra, không phải với độ dài prompt đầu vào — prompt được xử lý song song ngay trong lần forward đầu tiên, đúng như mục 3 vừa mổ.
Bài đó đã tách hai pha theo chi phí: train trả một lần rất nặng, inference trả theo từng lượt gọi. Bài này thêm một bất đối xứng khác nằm ngay trong cùng kiến trúc: bản thân train là một lần forward song song, còn inference là nhiều lần forward tuần tự — nên chi phí inference không chỉ tính theo lượt gọi, mà còn tăng theo đúng số token model phải sinh ra trong lượt gọi đó.
Chạy lại toàn bộ transformer_block từ đầu cho mỗi token mới nghe có vẻ lãng phí — phần token cũ đã được tính hệt như lần trước. Thực tế các hệ thống production cache lại kết quả đó (KV cache) để đỡ tính lại phần quá khứ; bài này chỉ cần bạn biết cơ chế đó tồn tại, không đào sâu ở đây.
5. Pitfall tổng hợp
❌ Nhầm 1 — nghĩ causal mask chỉ cần thiết lúc inference, để model "không gian lận" khi sinh:
✅ Causal mask áp dụng cả lúc train — đó chính là cơ chế mục 2 vừa giải thích: lúc train, nhãn tương lai đã nằm sẵn trong dữ liệu, nên phải chủ động che thì model mới học thật (không rò rỉ nhãn). Lúc inference, tương lai còn chưa tồn tại nên causal mask gần như không cần "làm gì" — không có gì để che.
❌ Nhầm 2 — nghĩ vì train "song song mọi vị trí" nên có thể tăng tốc inference bằng cách sinh nhiều token cùng lúc theo cách tương tự:
✅ Train song song được vì nhãn của mọi vị trí đã có sẵn trước khi chạy (mục 3). Lúc sinh, token kế tiếp chưa tồn tại tới khi token hiện tại được chọn xong — không có "nhãn có sẵn" nào để tính song song. (Có kỹ thuật sinh nhanh hơn như speculative decoding, nhưng đó là chủ đề khác, ngoài phạm vi bài này.)
❌ Nhầm 3 — nghĩ causal mask áp dụng cho toàn bộ transformer block, kể cả tầng feed-forward:
✅ Causal mask chỉ có ý nghĩa trong self-attention — nơi một token thực sự "nhìn" sang token khác. Tầng feed-forward xử lý từng vị trí hoàn toàn độc lập, không có bước nào để nhìn trộm token khác, nên không cần mask gì cả.
6. 📚 Deep Dive
Paper chính chủ:
- Vaswani et al. — Attention Is All You Need (arXiv:1706.03762) — nơi kiến trúc transformer và cơ chế masked (causal) self-attention trong decoder được trình bày lần đầu; đọc để thấy causal mask trong bài này bắt nguồn từ đúng thiết kế nào.
- Brown et al. — Language Models are Few-Shot Learners (arXiv:2005.14165) — dòng GPT mở rộng đúng kiến trúc decoder-only autoregressive này lên quy mô hàng trăm tỷ tham số; đọc để thấy vòng lặp train song song / inference tuần tự của bài này đứng sau những hệ thống lớn thế nào.
Ghi chú: bài này thuần cơ chế kiến trúc, không trích số liệu benchmark cụ thể từ hai paper trên — ví dụ đầu bài (tóm tắt nhanh, bài luận chậm) là minh hoạ định tính cho độ trễ, không phải kết quả đo từ một hệ thống cụ thể.
7. Liên hệ các bài khác
- Bài 02 — Attention, truy vấn có trọng số — self-attention nền tảng mà causal mask áp thêm ràng buộc lên, bài này giả định bạn đã nắm.
- Bài 03 — Multi-head, nhiều góc nhìn — mỗi head trong multi-head attention đều dùng chung một causal mask, không head nào được miễn.
- Bài 04 — Transformer block — causal mask nằm trong đúng bước self-attention của block này; bài đó dựng cấu trúc, bài này dựng ràng buộc chạy bên trong.
- Bài 06 — Scaling laws, nhìn định tính — bài tiếp theo: cùng kiến trúc decoder-only này, tăng quy mô tham số, dữ liệu, compute thay đổi khả năng model thế nào.
8. Tóm tắt
- LLM hiện đại là decoder-only transformer: nhiều transformer block xếp chồng, tất cả dùng self-attention kèm causal mask.
- Causal mask ép mỗi token chỉ được tính attention với token đứng trước nó, gán gần như âm vô cực cho mọi vị trí ở tương lai trước bước softmax.
- Train song song được vì nhãn (token kế tiếp) đã có sẵn trong dữ liệu huấn luyện — model chỉ cần bị cấm nhìn trộm đáp án đó, không phải chờ tuần tự như model đọc kiểu cũ.
- Causal mask là một ma trận cố định, không tạo ra chuỗi phụ thuộc theo thời gian như
h_tphụ thuộch_(t-1)— đây là lý do nó không phá mất tính song song mà attention vừa giành lại được. - Bỏ causal mask khi train sẽ gây rò rỉ nhãn: model học cách sao chép đáp án đang nằm sẵn trong input thay vì học pattern ngôn ngữ thật.
- Inference vẫn tuần tự vì token kế tiếp chưa tồn tại — phải sinh xong token này mới tính được token sau, quay lại đúng vòng lặp autoregressive.
- Bất đối xứng train song song / inference tuần tự khiến độ trễ sinh văn bản tỉ lệ thuận với số token sinh ra, không phải độ dài prompt.
- KV cache tồn tại để đỡ tính lại phần quá khứ mỗi lần sinh token mới — chi tiết cơ chế nằm ngoài phạm vi bài này.
9. Tự kiểm tra
Q1Giải thích cơ chế causal mask trong self-attention: nó thay đổi điều gì trước bước softmax, và vì sao token ở một vị trí không bao giờ 'nhìn thấy' token đứng ngay sau nó?▸
Q2Nếu bỏ causal mask lúc train, model vẫn có thể tính được loss rất thấp — nhưng đó không phải dấu hiệu tốt. Giải thích vì sao, và gọi tên hiện tượng này.▸
Q3Causal mask ép mỗi token chỉ nhìn về quá khứ. Vì sao ràng buộc này không tái tạo lại đúng chuỗi phụ thuộc tuần tự mà model đọc tuần tự kiểu cũ từng mắc phải?▸
Q4Một đồng nghiệp nói: 'Train song song hoá được thì chắc sinh văn bản cũng nhanh song song luôn.' Chỉ ra chỗ sai trong lập luận này.▸
Q5Causal mask áp dụng ở đâu bên trong một transformer block: self-attention, feed-forward, cả hai, hay không tầng nào? Giải thích vì sao.▸
Bài tiếp theo: Scaling laws — nhìn định tính
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên