Mở đầu: 18 mảnh ghép, giờ ráp thành 1 cỗ máy thật
Suốt 18 bài, mỗi bài học riêng lẻ ĐÚNG MỘT mảnh: gradient descent, embedding, attention, tokenizer BPE, Adam, gradient clipping... Bài này KHÔNG dạy khái niệm mới — nó ráp toàn bộ những mảnh đó lại thành 1 GPT thật, nhỏ xíu (~49.000 tham số, so với GPT-3 thật 175 TỶ), train NGAY TRÊN TRANG NÀY bằng vanilla JavaScript — không PyTorch, không GPU, không gọi API. Bạn sẽ tự mắt thấy nó học thuộc lòng câu mở đầu Truyện Kiều trong vài chục giây, và thấy chính XÁC vì sao GPT thật cần hàng triệu đô để làm điều tương tự ở quy mô lớn hơn hàng triệu lần.
1. Ghép tất cả lại: kiến trúc GPT-mini
Không thành phần nào trong sơ đồ dưới đây là MỚI — mỗi ô đã có công thức và code riêng ở đúng 1 bài học trước. GPT-mini chỉ là ĐẶT ĐÚNG THỨ TỰ:
| Bước | Thành phần | Nối bài nào |
|---|---|---|
| 1 | Token embedding: id token → vector $d_{model}$ chiều | Bài 12 |
| 2 | Position embedding HỌC ĐƯỢC: vị trí 0,1,2… → vector cộng vào token embedding | Bài 14 (khắc phục pitfall "không biết vị trí") |
| 3 | N transformer block: self-attention + Add&Norm + FFN + Add&Norm, có causal mask | Bài 14 — TÁI SỬ DỤNG y nguyên code, không viết lại |
| 4 | Head: chiếu về logits trên toàn vocab, weight-tied với token embedding | Bài 3 (softmax) + Bài 16 (cross-entropy) |
Weight tying ở bước 4 là chi tiết đáng nói riêng: thay vì học 1 ma trận chiếu output RIÊNG ($d_{model} \times V$, tốn thêm hàng chục nghìn tham số), GPT-mini TÁI DÙNG chính ma trận token embedding đã có ($V \times d_{model}$), chỉ chuyển vị nó ($\text{logits} = X \cdot \text{tokEmb}^T$). Đây là kỹ thuật GPT-2 thật dùng — hợp lý về mặt toán học vì "token nào gần nhau trong không gian embedding" và "token nào có xác suất gần nhau khi dự đoán" là 2 mặt của cùng 1 ý tưởng.
function forward(model, tokenIds) {
const T = tokenIds.length;
const tokX = embeddingLookup(model.tokEmb, tokenIds); // Buoc 1 (Bai 12)
const posIds = Array.from({ length: T }, (_, i) => i);
const posX = embeddingLookup(model.posEmb, posIds); // Buoc 2 (Bai 14)
let X = add(tokX, posX);
const mask = buildCausalMask(T);
for (const b of model.blocks) X = transformerBlock(X, b, mask); // Buoc 3 (Bai 14, tai su dung)
return matmul(X, transposeGrad(model.tokEmb)); // Buoc 4: head weight-tied
Đếm tham số THẬT của cấu hình đã train trong bài này ($d_{model}{=}48$, 4 đầu attention, $d_{ff}{=}96$, 2 block, vocab BPE 207 token):
| Thành phần | Công thức | Số tham số |
|---|---|---|
| Token embedding | $207 \times 48$ | 9.936 |
| Position embedding | $32 \times 48$ | 1.536 |
| 2 × transformer block | $2 \times 18.768$ | 37.536 |
| Head (weight-tied) | tái dùng token embedding | 0 |
| Tổng | 49.008 |
Mỗi con số ở đây có thể truy ngược về công thức tay của Bài 14 Mục 5 (đếm tham số 1 block) — không có "phép màu" nào ở quy mô lớn, chỉ là NHÂN LÊN gấp hàng triệu lần.
2. Dữ liệu: 40 câu mở đầu Truyện Kiều, train/val, và bài học overfit
Corpus train là 40 câu thơ ĐẦU TIÊN của Truyện Kiều (public domain, đã vendor sẵn trong
corpus-kieu.txt dùng xuyên suốt series) — cố tình CHỌN TÍ HON để 2 điều xảy ra nhanh và rõ
trong vài chục giây: model học thuộc lòng được, và model overfit được. BPE (Bài 16) với 150 lần merge cho vocab 207 token trên corpus này; cắt thành 25 khối liền kề 24 token/khối (không
chồng lấn), rồi chia train/val theo kiểu tách 1/5: 20 khối train, 5
khối validation.
// 1 "epoch" = cong don gradient qua TOAN BO dataset roi step 1 lan (trung
// binh) -- on dinh hon han single-batch SGD tren corpus ti hon nay (da do:
// batch-1 sau 1000 buoc chi giam loss ~5%, full-batch sau 150 epoch giam 98%).
function trainEpoch(model, optimizer, dataset, maxGradNorm) {
const params = paramsOf(model);
for (const p of params) p.zeroGrad();
let epLoss = 0;
for (const batch of dataset) epLoss += lossOnBatch(model, batch, true);
epLoss /= dataset.length;
for (const p of params) for (let i = 0; i < p.grad.length; i++) p.grad[i] /= dataset.length;
clipGlobalGradNorm(params, maxGradNorm); // Bai 13
optimizer.step(); // Adam, Bai 9
return epLoss;
}
Kết quả THẬT (Node self-test, seed cố định) sau 150-200 epoch full-batch, learning rate 0,03:
| Chỉ số | Epoch 0 | Điểm đặc biệt | Epoch cuối (199) |
|---|---|---|---|
| Train loss (20 khối) | 5,908 | giảm ĐỀU, không quay đầu | 0,211 |
| Val loss (5 khối, KHÔNG train trên) | 5,890 | đạt MIN = 5,825 ngay epoch 1 | 23,069 (gấp gần 4 lần đáy) |
Đây là chu kỳ overfit kinh điển, đo được TRÊN CHÍNH model của bài này: val loss chạm đáy gần như ngay lập tức (chỉ sau 1 epoch!) rồi TĂNG VỌT suốt 198 epoch còn lại, trong khi train loss vẫn tiếp tục giảm đều — model không còn "học quy luật tiếng Việt" nữa, nó đang HỌC THUỘC LÒNG chính xác 20 khối train, đến mức dự đoán tệ hơn cả lúc mới khởi tạo trên dữ liệu chưa từng thấy.
3. Train live trong browser: vòng lặp thật, không giả lập
Mỗi lần bấm ▶ Train ở demo Mục 5, trình duyệt chạy ĐÚNG trainEpoch() ở trên — forward,
backward qua toàn bộ đồ thị autograd (Bài 7), cắt gradient, cập nhật
Adam — lặp lại, chia nhỏ mỗi lần 1 epoch qua setTimeout (không phải
requestAnimationFrame: rAF bị trình duyệt tạm dừng khi tab ẩn, khiến demo "đứng hình" không
báo lỗi — quy tắc đã áp dụng từ Bài 6) để UI không bị khoá và loss
curve/văn bản sinh ra cập nhật SAU MỖI EPOCH thật.
function trainChunk() {
if (!state.running) return;
const { loss } = trainEpoch(state.model, state.optimizer, state.dataset, 1.0);
state.lossHistory.push(loss);
state.epoch++;
if (state.epoch % 5 === 0) state.lastSample = sampleText(state.model); // xem chu tien hoa
drawLossCurve();
updateStatus();
if (state.epoch < state.maxEpochs) setTimeout(trainChunk, 10);
else state.running = false;
Seed cố định (mulberry32 quen thuộc xuyên suốt series) nghĩa là MỌI con số ở trên — vocab
207, tham số 49.008, loss 5,91→0,12, val loss quay đầu tại epoch 1 — tái lập được 100%: chạy lại
node gpt_mini.js ra ĐÚNG những con số này, không sai lệch dù chỉ 1 chữ số thập phân.
4. Giới hạn thật: vì sao GPT thật cần triệu đô
| GPT-mini (bài này) | GPT-3 | |
|---|---|---|
| Tham số | ~49.000 | 175.000.000.000 (~3,6 triệu lần hơn) |
| Dữ liệu train | 40 câu thơ (~600 token) | ~300 tỷ token |
| Compute | vài chục giây, 1 CPU, trình duyệt | hàng nghìn GPU chạy hàng tháng |
| Alignment | không có — chỉ pretraining thuần | SFT + RLHF (Bài 18) trên hàng triệu đánh giá người |
Điều đáng nói: KIẾN TRÚC ở Mục 1 — embedding, attention, Add&Norm, FFN, weight tying — là ĐÚNG Y NGUYÊN kiến trúc GPT-3/GPT-4 dùng, chỉ khác số lượng (dModel, số block, số đầu, vocab). Bạn vừa tự tay xây đúng bộ khung đó. Cái KHÔNG scale y nguyên là dữ liệu (cần đa dạng THẬT, không chỉ nhiều), compute (attention $O(n^2)$ — Bài 16), và alignment (RLHF thật cần hàng triệu đánh giá người, không thể tự động hoá hoàn toàn).
- GPT-mini ($49.008$) → GPT-2 nhỏ ($\approx 124$ triệu): gấp $\approx 2.530$ lần.
- GPT-2 nhỏ → GPT-3 ($175$ tỷ): gấp $\approx 1.411$ lần.
- Gộp cả 2 bước: GPT-mini → GPT-3: $2.530 \times 1.411 \approx 3.570.846$ lần — khớp đúng con số "$3{,}6$ triệu" ở bảng trên.
5. Thực hành: train GPT-mini thật ngay trên trang này
Bấm ▶ Train — trình duyệt train ĐÚNG model, cấu hình, seed như Mục 1-3 (demo dừng ở 60 epoch thay vì 150 để mượt trên mọi thiết bị — loss ở epoch 60 đã giảm còn khoảng 0,2, xem đường cong Mục 2). Theo dõi loss curve giảm, và văn bản mẫu cập nhật mỗi 5 epoch để tận mắt thấy quá trình từ nhiễu ngẫu nhiên → vài âm tiết lặp → nhớ được nguyên câu mở đầu. Việc train THẬT mất vài chục giây thay vì tức thì — đúng ý Mục 4: ngay cả ~49.000 tham số cũng cần thời gian tính toán thật, không phải phép màu. Sau khi train xong (hoặc bấm ⏸ giữa chừng), dùng slider temperature/top-k/top-p (nguyên xi Bài 18) để sinh văn bản từ chính model vừa train.
// Giong het temperature/top-k/top-p Bai 18, chi khac: ap truc tiep len
// logits (T,V) THAT cua GPT-mini thay vi bang tan suat bigram dem tay.
function generate(model, seedIds, steps, temperature, topK, topP, rng, maxCtx) {
let ids = seedIds.slice();
for (let s = 0; s < steps; s++) {
const ctx = ids.slice(-maxCtx);
const logits = forward(model, ctx); // Muc 1: forward qua GPT-mini
const V = model.vocabSize;
const lastRow = Array.from(logits.data.slice((ctx.length - 1) * V, ctx.length * V));
ids.push(sampleNext(lastRow, temperature, topK, topP, rng)); // Bai 18
}
return ids;
}
Đặt temperature thấp (~0,2) sau khi train xong để xem model lặp lại chính xác vài từ đầu Truyện Kiều — bằng chứng THẬT của việc học, không phải bịa. Reset để train lại từ đầu với seed khác nhau (bấm nhiều lần Reset để đổi seed).
Tóm lược
- ✅ GPT-mini = ráp lại 18 bài, không có khái niệm mới: token/position embedding, N transformer block (tái sử dụng nguyên code Bài 14), head weight-tied, Adam + gradient clipping.
- ✅ ~49.000 tham số verified: tokEmb 9.936 + posEmb 1.536 + 2 block × 18.768 + head (tied) 0.
- ✅ Full-batch train live trong browser: loss giảm 98% (5,91→0,12) sau 150 epoch, nhớ đúng nguyên văn câu mở đầu Truyện Kiều.
- ✅ Overfit kinh điển verified: val loss đạt đáy epoch 1 rồi tăng gấp ~4 lần, trong khi train loss tiếp tục giảm đều — sức chứa model lớn hơn hẳn dữ liệu tí hon.
- ✅ Kiến trúc scale y nguyên lên GPT thật; dữ liệu/compute/alignment thì không — đó là lý do GPT thật cần triệu đô, không phải vì kiến trúc bí mật hơn.
Bản đồ học tiếp: 3 hướng nghề nghiệp
| Hướng | Học tiếp gì |
|---|---|
| ML Engineer | PyTorch thật (thay Tensor tự viết), MLOps, triển khai model, tối ưu inference — bắt đầu từ docs PyTorch |
| Research | Toán sâu hơn (tối ưu hoá, lý thuyết thông tin), đọc paper gốc — fast.ai, d2l.ai |
| Ứng dụng LLM | Prompt engineering (Bài 18), RAG, fine-tuning model có sẵn — không cần tự train từ đầu ở quy mô lớn |
Tài nguyên chọn lọc để đi TIẾP sau series này: Karpathy — Zero to Hero & nanoGPT (chính là GPT-mini bài này, nhưng ở quy mô thật với PyTorch), fast.ai, d2l.ai. Những gì series này CỐ TÌNH bỏ qua (và nên đọc thêm nếu cần): huấn luyện phân tán nhiều máy, RLHF thật với đội đánh giá người, model multimodal (ảnh+chữ), diffusion ảnh ở quy mô thật (Bài 15 mới chỉ chạm khái niệm).
Tải file code thực hành minh họa bài học
File JavaScript cài đầy đủ GPT-mini (BPE + embedding + transformer block tái sử dụng + Adam + gradient
clipping + sinh văn bản), verify bằng số đo thật (đếm tham số, loss giảm, overfit train/val, gradient
clipping — chạy node gpt_mini.js, mất khoảng 30-60 giây vì train THẬT):
Bình luận