Giáo trình · 15 module · Tiếng Việt
Lò Luyện Model
Train một model cũng như luyện một viên đan: dược liệu là data, lửa là optimizer, đan phương là công thức train, và nghiệm đan là evaluation. Bộ giáo trình này dạy lớp lý thuyết nằm dưới từng quyết định bạn đã và đang đưa ra — minh họa bằng chính các thí nghiệm MT Trung→Việt và LLM local của bạn.
Mở đầu
Cách dùng & bản đồ project
Giáo trình chia 4 phần, đọc tuần tự thì mỗi module đứng trên vai module trước; nhưng từng module cũng tự đứng được nếu bạn cần tra cứu nhanh trước một quyết định (sắp chạy DPO? — nhảy thẳng Module 12).
Ba cách dùng — chọn trước khi đọc
- Tra cứu 30 phút: đọc Mục tiêu → ví dụ có số → Bẫy → tự trả lời câu hỏi trước khi mở đáp án.
- Ôn nền trong 7 buổi: 01–03 · 04–06 · 07–08 · 09 · 10–12 · 13–14 · 15. Mỗi buổi chỉ cần giải thích lại được ba ý bằng lời của mình.
- Nhịp nhớ 1–7: hôm sau kể lại không nhìn bài; sau 7 ngày làm lại câu từng sai. Nút “Đánh dấu đã học” lưu tiến độ cục bộ trên trình duyệt này.
Quên toán thì rẽ sang đâu?
Trang này thiên về quyết định thực chiến. Khi một công thức bị “mờ”, mở đúng đoạn nền ở Lò Luyện Mạng Nơ-ron, làm mô phỏng rồi quay lại:
| Đang vướng ở đây | Ôn nền trong 10–20 phút |
|---|---|
| Loss, đạo hàm, learning rate (01–02) | Bàn nháp đạo hàm → Module 00 → Module 02 |
| Backprop, gradient theo ma trận | Module 03 và Module 07 |
| Embedding và tensor shape (05–06) | Module 05 và Module 08 |
| Softmax, cross-entropy, perplexity | Module 02 |
| Attention, Transformer, KV cache | Module 09 |
| Tokenizer/BPE (04) | Module 10 |
Bản đồ: quyết định của bạn ↔ module
| Bạn đang… | Đọc |
|---|---|
Đọc log train, thắc mắc eval_loss 1.896 nghĩa là gì, lr bao nhiêu là vừa | 01, 02, 09 |
| Chuẩn bị data cho slm-mt / stv-mt-clean-dataset (dedup, gate, cap/novel) | 03 |
| Cân nhắc tokenizer mới, đau đầu byte-fallback, chữ hiếm, coverage/exposure | 04 |
| Làm term detector/verifier bằng embedding (câu hỏi 13/08 của bạn) | 05 |
| Muốn hiểu attention/KV cache thật sự, đếm tham số model tự build | 06 |
| Fine-tune LTP (CWS/NER), verifier keep/reject, mọi bài phân loại/gán nhãn (ltp-ft, webapp) | 07 |
| Chọn giữa Marian NMT, decoder-only, NAT-CTC cho vp2vi / slm-mt | 08 |
| Fine-tune style_gold, LoRA trên 5070Ti, hint-injection namefix | 10 |
| Chọn teacher distill, sinh pseudo-parallel (seq-KD) | 11 |
| Định chạy lại DPO — lần này muốn hiểu vì sao v1 fail còn DPO-lite ăn | 12 |
| Dựng eval mới, nghi ngờ BLEU, thiết kế golden set / A-B gate | 13 |
| Export CT2/GGUF, quantize, tối ưu webMT, tính VRAM/KV cache | 14 |
| Quyết scale 41M → 130M hay thêm data; tò mò diffusion LM | 15 |
Quy ước trong sách
- MỤC TIÊU đầu mỗi module — biết mình sẽ nhận được gì.
- CASE STUDY — thí nghiệm thật của bạn, kể lại dưới góc nhìn lý thuyết. Đây là phần "ăn tiền" nhất: lý thuyết dính vào trải nghiệm sẵn có thì không rơi.
- BẪY — lỗi người làm thật hay mắc (nhiều cái chính bạn đã trả học phí).
- TỰ KIỂM TRA — câu hỏi có đáp án gập; làm bằng giấy nháp trước khi mở.
- Công thức viết dạng chữ/code thay vì ký hiệu hàn lâm — ưu tiên tính được bằng tay.
Phần I — Nền tảng · Module 01
01 Mô hình "học" như thế nào?
Mục tiêu
- Thấy model chỉ là một hàm số khổng lồ có tham số điều chỉnh được — không hơn không kém
- Đọc được ý nghĩa thật của con số
eval_loss = 1.896trong log training của bạn - Tự tính tay một bước gradient descent — và thấy vì sao learning rate quá lớn làm loss nổ
- Hiểu vì sao bạn đã đúng khi bắt buộc gate "0 eval overlap" trước mọi lần train
1. Model = hàm số có núm vặn
Quên chữ "trí tuệ" đi. Một model dịch như MoxhiMT-30 của bạn, về bản chất toán học, là:
P(y_t | x, y_<t; θ) → phân phối xác suất của token tiếng Việt kế tiếp
x = toàn bộ câu nguồn tiếng Trung
y_<t = các token tiếng Việt đã có trước vị trí t
θ = ~30 triệu con số thực (tham số / weights)
Nó giống một bàn mixer khổng lồ có 30 triệu núm vặn. Mỗi cách vặn (mỗi bộ giá trị θ) cho ra một "kiểu dịch" khác nhau — đa số là dịch rác; một vùng rất nhỏ trong không gian 30-triệu-chiều đó là dịch hay. Training = quá trình tìm cách vặn 30 triệu núm để hàm này dịch tốt.
Ba điểm cần chốt ngay:
- Tham số là thứ duy nhất thay đổi khi train. Kiến trúc (số layer, số head…) cố định từ trước — đó là hyperparameter: bạn chọn, model không học.
- Kích cỡ model = số tham số. "Model 41M", "probe 90M" — là đếm số núm vặn. Nhiều núm hơn = sức biểu diễn lớn hơn, nhưng cần nhiều data hơn để vặn đúng hết (câu chuyện trần 41M vs 130M → Module 15).
- Đầu ra là phân phối — mọi thứ về sau (loss, decode, eval) đều xoay quanh sự thật này.
2. Loss — thước đo "đang sai bao nhiêu"
Muốn vặn núm có phương hướng thì phải đo được độ sai. Với model ngôn ngữ / dịch máy, thước đo chuẩn là cross-entropy:
loss của 1 token = −ln( xác suất model gán cho token ĐÚNG )
Chỉ vậy thôi: gán xác suất cao cho đáp án đúng → loss nhỏ; gán thấp → loss lớn. Loss của cả câu / cả tập = trung bình loss từng token. Vài mốc để cảm nhận:
| P(token đúng) | loss = −ln(P) | Cảm giác |
|---|---|---|
| 0.95 | 0.05 | gần như chắc chắn |
| 0.60 | 0.51 | khá tự tin |
| 0.37 | 1.00 | lưỡng lự |
| 0.13 | 2.04 | mù mờ |
| 0.01 | 4.61 | gần như không biết |
| 1/24000 (đoán mò) | 10.09 | chưa học gì (vocab 24k) |
Đọc lại log của chính bạn
Gate checkpoint-5000 của bạn ghi: eval_loss 1.896 so với baseline 2.036. Giờ đọc được rồi:
- Loss 2.036 → trung bình nhân (geometric mean) của P(token đúng) là e−2.036 ≈ 13%
- Loss 1.896 → xác suất “điển hình” theo cùng cách tính ≈ 15%
Đây không phải trung bình cộng xác suất từng token: vài token cực khó bị log phạt mạnh. Nghe thấp vẫn bình thường — rất nhiều token có nhiều đáp án cùng đúng ("Vâng/Ừ/Ừm" đều là bản dịch hợp lệ của 嗯 — chính bạn gặp ca này khi dedup). Model không thể và không nên đạt P=1. Cách đọc trực quan hơn: perplexity = eloss — một “số nhánh lựa chọn hiệu dụng”, không phải số đáp án đếm được theo nghĩa đen:
ppl(2.036) = e^2.036 ≈ 7.7 → như phân vân giữa ~8 token
ppl(1.896) = e^1.896 ≈ 6.7 → như phân vân giữa ~7 token
Cải thiện 2.036 → 1.896 làm perplexity hiệu dụng giảm 7.7 → 6.7 trên cùng tokenizer/tập đánh giá. Đây là cải thiện thật, nhưng không nên diễn dịch cứng thành “loại đúng một token gây nhiễu”.
Bẫy — loss giữa hai tokenizer không so được
Bạn từng so Moxhi (SPM, 0.73 token/ký tự Trung) với model đối thủ 50M (byte-BPE, 0.88 tok/char). Cùng một câu, tokenizer nào băm ra nhiều token hơn thì mỗi token dễ đoán hơn → loss per-token thấp hơn một cách giả tạo. So công bằng: tổng loss trên cùng đoạn văn (per-character) — hoặc tốt nhất, bỏ loss đi, so bằng eval hành vi (Module 13).
3. Gradient descent — dò dốc trong sương mù
Đứng trên sườn núi trong sương mù dày (không thấy đáy thung lũng), nhưng chân cảm nhận được độ dốc ngay chỗ đứng. Bước một bước nhỏ về hướng xuống dốc nhất. Lặp lại vài trăm nghìn lần.
- "Vị trí đang đứng" = bộ tham số θ hiện tại · "Độ cao" = loss
- "Độ dốc dưới chân" = gradient ∂L/∂θ — tính bằng backpropagation (Module 02)
- "Độ dài bước chân" = learning rate (lr)
θ_mới = θ_cũ − lr × (∂L/∂θ)
Tự tính tay — model 1 tham số
Model tí hon: dự_đoán = w × x. Dữ liệu: x = 2 thì đáp án y = 10 (w lý tưởng = 5). Loss bình phương: L = (w·x − y)², đạo hàm ∂L/∂w = 2·(w·x − y)·x. Bắt đầu w = 1:
| Bước | w | dự đoán | sai số | ∂L/∂w | w mới |
|---|---|---|---|---|---|
| 1 | 1.00 | 2.0 | −8.0 | −32.0 | 4.20 |
| 2 | 4.20 | 8.4 | −1.6 | −6.4 | 4.84 |
| 3 | 4.84 | 9.68 | −0.32 | −1.28 | 4.97 |
| Bước | w | dự đoán | sai số | L |
|---|---|---|---|---|
| 1 | 1.0 | 2.0 | −8.0 | 64 |
| 2 | 20.2 | 40.4 | +30.4 | 924 |
| 3 | −53.3 | −106.6 | −116.6 | 13 596 |
Khi loss training của bạn vọt lên NaN/inf — cơ chế bên dưới chính là bảng bên phải. Với bài này L = 4(w−5)², nên sai lệch e = w−5 sau mỗi bước tuân theo e_mới = (1−8·lr)e_cũ: lr = 0.125 tới đáy đúng một bước; 0 < lr < 0.125 hội tụ không đổi bên; 0.125 < lr < 0.25 dao động nhưng co dần (lr = 0.2 là ví dụ); lr = 0.25 dao động mãi cùng biên độ; lớn hơn 0.25 thì phân kỳ. Mỗi bài toán có vùng lr vàng riêng — vì thế warmup và schedule tồn tại (Module 02). Bạn từng chạy arm ffn_up_only + lr0.005 — tức đã dùng learning rate riêng cho từng nhóm tham số.
Chữ S trong SGD — vì sao mỗi bước chỉ nhìn một nhúm dữ liệu?
Loss "thật" là trung bình trên toàn bộ dataset (vd 1M cặp câu) — tính đủ cho mỗi bước thì một đời không xong. Thay vào đó mỗi bước lấy một minibatch (32–2048 câu) và coi gradient của batch là ước lượng nhiễu của gradient thật — chữ S là Stochastic. Nhiễu không hẳn xấu: nó giúp nhảy khỏi các hố nông. Batch to → ước lượng mượt hơn nhưng tốn VRAM — trade-off là chuyện chính của Module 02.
4. Train / Validation / Test — và nỗi sợ leakage chính đáng của bạn
Model có thể đạt loss thấp bằng hai con đường rất khác nhau: học quy luật (sau 了 thường là hành động đã xong → "rồi/đã") hoặc học thuộc lòng ("câu số 48.213 có đáp án là…"). Nhìn loss trên tập train thì không phân biệt nổi. Vì vậy phải cắt dữ liệu:
Cách đọc cặp loss (train, val):
- Train giảm, val giảm → đang học quy luật. Tốt.
- Train giảm, val chững rồi tăng → bắt đầu học thuộc (overfit) — xử lý ở Module 09.
- Val đẹp bất thường → nghi ngờ ngay: leakage — câu val lọt vào train.
Case study — từ project của bạn
Gate "0 eval overlap" của stv-mt-clean-dataset. Một câu nằm ở cả train lẫn eval = model "thi trúng đề đã học thuộc" → điểm eval đẹp giả tạo → chọn nhầm checkpoint, tưởng model tốt hơn thực tế. Bạn đặt nó làm gate bắt buộc trước mọi lần train — đúng giáo khoa.
Vụ dedup "嗯。": câu nguồn xuất hiện 3 lần với 3 đáp án Vâng/Ừ/Ừm — không phải duplicate! Đó là phân phối thật của ngôn ngữ; cross-entropy xử lý đẹp: model học P(Vâng)≈P(Ừ)≈P(Ừm)≈⅓. Chỉ cặp (nguồn, đích) trùng nguyên vẹn mới là bản photo vô giá trị. Quyết định pair-aware dedup của bạn có nền lý thuyết vững — Module 03 phân tích trọn.
Gate checkpoint-5000 (eval_loss 1.896 < 2.036, BLEU 29.74, chrF 54.18, 0 Han leak) là mẫu chuẩn của đọc số đúng: loss xác nhận training lành mạnh; quyết định chất lượng dựa trên metric hành vi + review — nguyên tắc bạn tự đúc kết ("loss chỉ dùng debug") và là chủ đề Module 13.
Tóm tắt bằng một câu chuyện: một lần train = thả người mù (optimizer) xuống dãy núi 30-triệu-chiều (không gian tham số), đưa máy đo độ cao tại chỗ (loss trên minibatch), dặn bước chân dài chừng này (lr), và tin rằng sau trăm nghìn bước anh ta đứng ở thung lũng đủ thấp. Validation là trạm kiểm tra độc lập xác nhận thung lũng có thật — chứ không phải anh ta đào hố tại chỗ rồi tự khai là thung lũng.
Tự kiểm tra
1 · Model gán P = 0.05 cho token đúng. Loss? Perplexity?
loss = −ln(0.05) ≈ 3.0; perplexity = e³ ≈ 20 — như phân vân giữa 20 lựa chọn. Mẹo: P = 1/20 → ppl = 20.
2 · Model A loss 1.9 (SPM 24k, 0.73 tok/char); model B loss 1.8 (byte-BPE, 0.88 tok/char). "B tốt hơn" sai chỗ nào?
Loss per-token giữa hai tokenizer không so được. Quy về per-char: A = 1.9 × 0.73 ≈ 1.39 nat/char; B = 1.8 × 0.88 ≈ 1.58 nat/char — thực ra A đang tốt hơn.
3 · Trong ví dụ tính tay (x=2, y=10), lr = 0.2 thì sao? Tính 2 bước từ w=1.
Hệ số co sai số mỗi bước = (1 − 2·lr·x²) = −0.6. Bước 1: w = 1 − 0.2·(−32) = 7.4 (vọt qua đáy, sai số +4.8); bước 2: w = 7.4 − 0.2·(19.2) = 3.56. Sai số đổi dấu liên tục nhưng co dần (|−0.6| < 1) → dao động nhưng hội tụ — chính là "loss răng cưa nhưng xu hướng giảm" trong log thật.
4 · Vì sao gate "0 eval overlap" phải chạy lại sau MỖI lần mở rộng train pool?
Leakage là tính chất của cặp (train, eval) hiện hành, không phải của riêng eval. Mỗi đợt data mới (crawl mới, teacher batch mới) có thể chứa câu trùng với eval đã niêm phong — overlap bằng 0 hôm qua không bảo hành cho hôm nay.
Phần I — Nền tảng · Module 02
02 Tối ưu hóa & động học training
Mục tiêu
- Hiểu backpropagation ở mức trực giác — đủ để chẩn đoán, không cần đạo hàm tay
- Biết AdamW thực sự làm gì khác SGD, và đọc hiểu recipe
fp32 params + bf16 autocast + fused AdamWbạn đang dùng - Trả lời được: warmup để làm gì, batch size ảnh hưởng gì, VRAM đi đâu hết
- Giải thích được phát hiện của chính bạn: vì sao H100 nhanh lên khi tăng microbatch còn RTX PRO 6000 lại chậm đi
1. Backpropagation — dây chuyền trách nhiệm
Module 01 nói "tính gradient cho từng tham số" như thể miễn phí. Thực tế model là hàm lồng nhau hàng chục tầng. Backprop là quy tắc dây chuyền (chain rule) chạy ngược: bắt đầu từ loss, hỏi ngược từng tầng "đầu ra của anh lệch 1 đơn vị thì loss đổi bao nhiêu?" — như truy trách nhiệm trong dây chuyền sản xuất: QC cuối báo lỗi cho công đoạn N; công đoạn N tính phần lỗi do mình (→ update θ của mình) và phần chuyển tiếp cho công đoạn N−1.
Điều thứ hai đáng nhớ: backward tốn nhớ vì phải giữ activations — muốn tính gradient tầng k cần giá trị vào của tầng k lúc forward, nên toàn bộ kết quả trung gian phải nằm chờ trong VRAM. Đây là lý do batch to ngốn VRAM khủng khiếp (mục 6). Còn lại autograd của PyTorch lo hết.
2. Từ SGD đến AdamW — tiến hóa của người dò dốc
| Optimizer | Thêm gì | Trực giác |
|---|---|---|
| SGD | θ := θ − lr·g | Bước theo độ dốc tại chỗ. Nhiễu, dễ kẹt khe hẹp. |
| + Momentum | Cộng dồn "đà" các bước trước | Quả bóng lăn: lắc ngang tự triệt tiêu, hướng chung được cộng dồn. |
| Adam | Mỗi tham số một lr thích nghi | Tham số hay nhận gradient lớn → bước ngắn lại; tham số ít được cập nhật (embedding token hiếm) → bước dài ra. Cực hợp NLP vì tần suất token lệch nhau nghìn lần. |
| AdamW | Tách weight decay khỏi gradient | "Thuế kéo về 0" thu riêng, không bị phép chia-căn của Adam làm méo. Mặc định đúng cho transformer. |
Cụ thể, Adam giữ cho mỗi tham số hai thống kê trượt:
m := trung bình trượt của gradient (đà — hướng đi trung bình)
v := trung bình trượt của gradient² (biên độ — gradient chỗ này thường to cỡ nào)
bước cập nhật ≈ lr × m / (√v + ε)
Chính m và v chiếm VRAM gấp đôi tham số (mục 6), và cũng là một lý do cần warmup (mục 3): mấy bước đầu v ước lượng từ quá ít mẫu — chia cho √v lúc này như chia cho số ngẫu nhiên.
Ghi chú thực chiến: fused AdamW trong recipe LFM2 của bạn không đổi toán học — nó gộp nghìn phép cập nhật nhỏ vào một GPU kernel cho nhanh. Khi đọc config lạ, luôn tách hai câu hỏi: đây là thay đổi toán học (ảnh hưởng chất lượng) hay thay đổi thực thi (chỉ ảnh hưởng tốc độ)?
3. Learning rate — hyperparameter số một
Lr theo nhóm tham số. Không luật nào bắt mọi tham số chung một lr. Arm ffn_up_only + lr0.005 bạn chạy ở 90M chính là kỹ thuật này: nhóm được chọn nhận lr riêng để "nới" khả năng học mà không làm loạn phần còn lại. LoRA (Module 10) là họ hàng cực đoan: chỉ vài % tham số được học, còn lại đóng băng.
Quy tắc kéo theo: đổi batch size thì phải nghĩ lại lr (batch to → gradient mượt → thường chịu được lr lớn hơn, heuristic phổ biến: scale tuyến tính). Đổi mình batch mà giữ lr là âm thầm đổi hai thứ.
4. Batch, microbatch, gradient accumulation
microbatch = số token đưa vào GPU MỖI lần forward/backward
grad accumulation = gom gradient của k microbatch rồi mới update 1 lần
effective batch = microbatch × k (× số GPU) ← con số quyết định động học
Với MT, đo batch bằng token chuẩn hơn bằng câu (câu dài ngắn lệch nhau quá xa) — như bạn vẫn ghi "microbatch 16k/32k/65k token" trong bench Modal. Effective batch mới ảnh hưởng chất lượng; microbatch và accum chỉ là cách xếp lịch để đạt nó trong VRAM hữu hạn:
- 5070Ti 12GB không nhét nổi 32k token/lần? → microbatch 4k × accum 8 = effective 32k — chậm hơn nhưng cùng kết quả toán học.
- Nhờ đó có thể probe công thức ở local rồi scale lên H100 với cùng effective batch mà tin được kết quả.
Case study — batch-scaling ngược chiều (bench Modal 30/07 của bạn)
Tăng microbatch 16k → 65k token: H100 nhanh thêm +12%, RTX PRO 6000 chậm đi −18% — và per-dollar ở model 41M thì PRO 6000 thắng (H100 chỉ nhanh 1.17× mà đắt 1.30×). Giải thích bằng tháp bộ nhớ GPU:
5. Mixed precision & gradient clipping
| Format | Bit | Cấu trúc | Đặc tính |
|---|---|---|---|
| fp32 | 32 | 8 exponent + 23 mantissa | Chuẩn mực, an toàn; chậm & tốn nhớ |
| fp16 | 16 | 5 exponent + 10 mantissa | Khá chính xác nhưng dải hẹp: >65 504 thành inf → cần loss scaling |
| bf16 | 16 | 8 exponent + 7 mantissa | Cùng dải với fp32, chỉ bớt chữ số lẻ → hầu như không tràn. Mặc định từ GPU Ampere+ |
Recipe của bạn — fp32 params + bf16 autocast — nghĩa là: bản chính tham số và bước cập nhật Adam giữ fp32 (cộng dồn chính xác các cập nhật tí hon), còn nhân ma trận trong forward/backward chạy bf16 (nhanh ~2×, nửa bộ nhớ activation). Đây là setup chuẩn công nghiệp hiện nay.
Gradient clipping — cầu chì chống bước nổ: nếu ‖gradient toàn cục‖ > ngưỡng (thường 1.0) thì co lại đúng bằng ngưỡng. Một batch xấu có thể tạo gradient đột biến; không clip thì một bước phá hỏng nghìn bước tích lũy. Trong log: "loss spike một nhát rồi từ từ hồi" thường là batch xấu đã được clip cứu; spike rồi đi luôn thường là thiếu clip hoặc lr quá cao.
6. VRAM đi đâu hết? — số học ai cũng nên thuộc
Mỗi tham số (AdamW, mixed precision) ≈
4 byte (param fp32) + 4 byte (gradient) + 8 byte (Adam m + v) = 16 byte/param
41M → ~0.66 GB 90M → ~1.44 GB 130M → ~2.1 GB ← trạng thái train, CỐ ĐỊNH
Nhìn xong bạn sẽ thắc mắc đúng chỗ: vậy sao run 130M peak tận 36GB? Vì phần còn lại — thường là phần lớn — là activations, tỉ lệ thuận với token/microbatch × d_model × số layer:
Padding — kẻ ăn cắp throughput vô hình
Phát hiện trong run H100 của bạn: "bottleneck là padding/host chứ không phải compute". Batch gom câu dài ngắn khác nhau → pad tất cả lên bằng câu dài nhất → GPU nhân ma trận cả phần đệm vô nghĩa:
Bẫy thường gặp
- Đổi batch, giữ lr → động học đổi mà tưởng "cùng công thức". Ghi cả effective batch lẫn lr vào tên run.
- So tok/s hai GPU ở cùng một microbatch → mỗi GPU một điểm ngọt; tune riêng rồi mới so per-dollar.
- Loss spike → đổ cho data ngay. Thứ tự nghi ngờ rẻ trước: lr sau warmup quá cao → thiếu/lỏng grad clip → fp16 overflow → rồi mới tới batch xấu. (Đúng SOP debug của bạn: giả thuyết đơn giản kiểm trước.)
- Tin "fused/flash/compile" đổi chất lượng. Tối ưu thực thi phải cho cùng kết quả toán học (sai khác floating-point nhỏ); bật lên mà metric đổi hẳn → có bug, đừng ăn mừng.
- Quên eval cũng tốn VRAM. Eval giữa chừng batch to có thể OOM một run đang ngon — đặt eval batch riêng, nhỏ.
Tự kiểm tra
1 · Model 57M train AdamW mixed precision — VRAM cho trạng thái train (chưa tính activations)?
57M × 16 byte ≈ 0.91 GB. Phần còn lại của VRAM gần như dành trọn cho activations — điều chỉnh bằng microbatch.
2 · Tăng grad accumulation 4 → 8, giữ nguyên microbatch và lr. Đổi gì về (a) toán học, (b) tốc độ, (c) chất lượng?
(a) Effective batch gấp đôi → gradient mượt hơn, số lần update cho cùng lượng data giảm nửa. (b) Token/s gần như không đổi (vẫn từng ấy forward/backward). (c) Không tự động tốt hơn — batch to chỉ có ích khi kèm chỉnh lr; giữ nguyên lr nhiều khi tệ hơn trên cùng ngân sách bước.
3 · Vì sao bf16 thay thế fp16 trong training, dù fp16 "chính xác hơn" (mantissa 10 bit vs 7)?
Thứ giết training không phải thiếu chữ số lẻ mà là tràn dải: fp16 chịu tối đa ~65k, gradient vượt là inf/NaN, phải đèo loss scaling phức tạp. bf16 giữ 8 bit exponent như fp32 → cùng dải biểu diễn, ổn định, code đơn giản — đổi lấy độ lẻ mà mạng neural vốn chẳng nhạy cảm.
4 · Hai run chỉ khác seed cho chrF lệch 0.4 điểm. Kết luận gì về cách so hai công thức training?
0.4 chrF nằm trong nhiễu seed → so hai công thức mà chênh cỡ đó thì chưa nói lên gì. Cần ≥2 seed/công thức, hoặc so theo nhiều bucket (paired comparison by-source/by-bucket như bạn vẫn làm), hoặc chỉ tin khác biệt lớn hơn hẳn nhiễu. Chi tiết ở Module 13.
Phần I — Nền tảng · Module 03
03 Dữ liệu quyết định trần chất lượng
Mục tiêu
- Nắm nguyên lý số một của ML thực chiến: model là tấm gương của data — mọi thứ khác chỉ quyết định gương trong hay mờ
- Thiết kế pipeline data có gates, và giải thích được vì sao từng gate trong SOP của bạn tồn tại
- Phân biệt dedup đúng và dedup phá phân phối — vụ "嗯。" của bạn là ví dụ giáo khoa
- Biết ba loại data (train / preference / eval) cần ba chuẩn chất lượng khác nhau
1. Trần chất lượng nằm ở data, không nằm ở model
Training (Module 01–02) chỉ làm một việc: kéo model khớp với phân phối của data. Hệ quả lạnh lùng:
Model tốt nhất có thể = data của bạn, trừ đi một chút. Không bao giờ cộng thêm.
- Data dịch sai 5% → model học đúng cái sai đó như chân lý.
- Data toàn tiên hiệp → model dịch Lovecraft (Quỷ Bí Chi Chủ) sẽ tự tin… theo giọng tiên hiệp.
- Data từ teacher yếu → student giỏi lắm là bằng teacher, trừ hao. Chính là lý do bạn loại
tienhiep_baidukhỏi vai teacher cho CTC distillation: chất lượng nó không hơn Moxhi bao nhiêu → distill từ nó là tự đặt trần thấp (Module 11 đào tiếp).
Vì vậy khi chất lượng chưa đạt, thứ tự đầu tư đúng: soi data trước, đổi kiến trúc sau cùng — trùng khớp trải nghiệm của bạn: các cú nhảy chất lượng lớn nhất (style_gold, namefix, clean dataset) đều đến từ data, không phải từ đổi model.
2. Giải phẫu một pipeline data chuẩn
Pipeline stv-mt-clean-dataset của bạn đã hội tụ về đúng hình dạng giáo khoa — vẽ lại để gọi tên từng khối:
3. Dedup — môn tưởng dễ mà sâu
Vụ "嗯。" — ca giáo khoa của chính bạn
Phase 5A từng tranh luận: dedup theo source_hash (loại mọi câu nguồn trùng) hay theo cặp (source_hash, target_hash)? Câu "嗯。" xuất hiện nhiều lần với 3 bản dịch Vâng / Ừ / Ừm — đều đúng, tùy người nói. Quay về bản chất thống kê (Module 01): model học phân phối có điều kiện P(target | source):
- Giữ cả 3 cặp → model học P(Vâng) ≈ P(Ừ) ≈ P(Ừm) ≈ ⅓ → đúng phân phối thật ✓
- Dedup theo source, giữ 1 → model học P(Vâng) = 1 → tự tin sai, mất khả năng biến hóa ✗
| Loại trùng | Bản chất | Xử lý |
|---|---|---|
| Cặp (source, target) trùng nguyên vẹn | Bản photo — không học được gì mới, chỉ lệch trọng số | Xóa (giữ 1) |
| Source trùng, target khác | Phân phối tự nhiên của dịch thuật | Giữ |
| Source trùng, ≥3 kiểu target | Có thể tự nhiên, có thể là drift (tên riêng mỗi lúc một kiểu) | Gắn cờ review — đúng thiết kế target_variation_high của bạn |
Dedup cũng là chống leakage: audit reverse-AR của bạn tìm thấy 728 282 input Việt unique nhưng 567 086 exact replays — không dedup theo view training thì model "ôn lại" một câu hàng chục lần → memorize + loss đẹp giả. Còn 77 input có target mâu thuẫn → drop hẳn: mâu thuẫn ≠ đa dạng (cùng điều kiện mà đáp án chọi nhau là data bẩn). Quy trình bạn chốt — artifact gốc bất biến, dedup ở view training — là chuẩn: artifact để audit, view tùy mục đích.
4. Giải thích từng gate trong SOP của bạn
SOP bạn đúc kết: "dedup, eval overlap, Han leak, length, provider mix, book/domain balance, hot prefix buckets". Mỗi gate chặn một thảm họa cụ thể:
| Gate | Thảm họa nó chặn |
|---|---|
| Dedup (pair-aware) | Trọng số lệch về câu lặp; memorize; loss/metric ảo |
| 0 eval overlap | "Thi trúng đề học thuộc" → mọi quyết định dựa trên eval đều sai |
| Han leak (chữ Hán sót trong target Việt) | Data dạy model rằng chép nguyên chữ Hán là một cách dịch chấp nhận được — lỗi sẽ tái xuất ở inference đúng như được dạy |
| Length ratio | Cặp lệch độ dài quá xa thường là align sai / mất đoạn → dạy model bỏ sót hoặc bịa |
| Provider mix | Một teacher chiếm sóng → student nuốt trọn tật teacher đó; eval bằng teacher đó thành "tự chấm bài mình" (Module 13) |
| Book/domain balance (cap 50k/novel) | Một novel dominate → model thuộc tên nhân vật + giọng novel đó, generalize kém; trung bình che mất thoái hóa ở bucket nhỏ |
| Hot prefix buckets | Cụm mở đầu lặp quá dày (却说… 只见…) → model nghiện prefix, sinh văn công thức |
Bộ khung này đáng tái dùng cho mọi dataset mới (kể cả ngoài MT): bốn nhóm — trùng lặp, rò rỉ, bẩn, mất cân bằng — dataset nào cũng phải qua đủ bốn.
5. Probe trước, scale sau
6. Ba loại data, ba chuẩn chất lượng
| Loại | Vai trò | Chuẩn chất lượng | Ví dụ của bạn |
|---|---|---|---|
| Train (SFT) | Dạy hành vi | Sạch ở mức thống kê — vài % noise chịu được, phân phối phải đúng | silver shards, seq-KD từ DeepSeek |
| Preference | Dạy chọn giữa hai hành vi | Từng cặp phải chắc chắn chosen > rejected, khác biệt rõ | 220 cặp DPO v22; gate 100–300 rows |
| Eval / Golden | Đo lường & ra quyết định | Khắt khe nhất — từng row được duyệt, niêm phong, versioned | eval_locked 1 043 rows; golden 29/30 |
Kim tự tháp ngược về số lượng, xuôi về độ khó tính: train cần nhiều nhất nhưng dễ tính nhất; eval ít nhất nhưng mỗi row sai là la bàn lệch — mọi quyết định sau đó lệch theo. Một row eval sai đắt hơn nghìn row train sai.
Bẫy thường gặp
- Filter im lặng. Một regex quá tham nuốt 30% data mà không log — mọi thứ vẫn "chạy được", chỉ có model yếu đi khó hiểu. Mọi filter phải in số vào/ra + lý do loại. (Trùng giả thuyết debug số một trong SOP của bạn — kiểm nó trước tiên.)
- Dedup theo source cho MT → phá phân phối tự nhiên. Ngược lại, không dedup theo cặp → memorize.
- Chuẩn hóa lệch pha giữa train và inference. Train với text đã NFC + nắn quote, inference nhận text thô → model gặp phân phối lạ. Chuẩn hóa phải là một hàm dùng chung hai nơi (họ hàng của bug tokenizer ở Module 04).
- Thêm data mới, quên chạy lại gates. Overlap/mix/balance là tính chất của phiên bản pool hiện tại — gate chạy theo mỗi version, không phải một lần trong đời.
- Nhìn trung bình, quên bucket. Cải thiện bucket dễ (90%) che hoàn toàn thoái hóa bucket khó (10%). Paired comparison by-bucket — bạn đã có script — là thuốc giải.
Tự kiểm tra
1 · Câu 他笑了笑。 xuất hiện 40 lần, cùng một bản dịch "Hắn cười cười." cả 40 lần. Giữ mấy bản?
Giữ 1 (dedup theo cặp). 40 bản sao không thêm thông tin phân phối — chỉ nhân trọng số câu này 40× so với câu khác, lệch model về pattern phổ biến + tăng memorize. Khác hẳn ca "嗯。" (một nguồn, nhiều target khác nhau — đó là phân phối, phải giữ).
2 · Eval lấy 100% từ 3 novel cũng có trong train (khác chương, không trùng câu). Gate overlap pass. Còn rủi ro gì?
Leakage mềm theo nguồn: model đã thuộc tên nhân vật, xưng hô, giọng văn 3 novel đó từ train → eval đo "dịch novel quen" chứ không đo generalize sang novel lạ. Thuốc: mix cả novel held-out hoàn toàn (khớp thiết kế GenWebNovel heldout bạn dùng cho term verifier).
3 · Vì sao chuẩn preference data khắt khe hơn train data, dù cần ít hơn nhiều?
SFT học từ trăm nghìn câu — vài % nhiễu có thể bị trung bình hóa. DPO rút tín hiệu từ hiệu số giữa 2 vế của từng cặp: cặp mơ hồ đóng góp gradient nhiễu, cặp gán ngược đầu độc trực tiếp hướng học; với vài trăm cặp thì không có "số đông" pha loãng lỗi. Review data cho thấy đây là một nguyên nhân hợp lý của DPO v1 margin ~0; Module 12 cũng chỉ ra các lỗi pipeline/update cần loại trừ.
4 · Nhận 200k cặp mới từ teacher mới — thứ tự việc cần làm trước khi trộn vào pool?
(1) Sample đọc tay 50–100 row — đánh giá trần của teacher; (2) chuẩn hóa + filter/quarantine như mọi nguồn; (3) dedup theo cặp với pool hiện có; (4) kiểm overlap với eval niêm phong; (5) xem provider mix sau trộn; (6) chạy lại toàn bộ gates trên pool version mới; (7) probe nhỏ nếu tỉ trọng nguồn mới lớn. Nguồn mới = version pool mới = đi lại toàn bộ vòng gate.
Phần II — NLP & Transformer · Module 04
04 Tokenization: từ chữ thành số
Mục tiêu
- Tự chạy tay thuật toán BPE trên một corpus tí hon
- Quy được con số 0.73 vs 0.88 token/ký-tự (Moxhi vs đối thủ) ra tốc độ và context — lợi thế cạnh tranh thật
- Hiểu byte-fallback cứu gì, và vì sao tên có chữ hiếm (珉, 鹃) chỉ đạt 0.25 accuracy trong bench namefix của bạn
- Đặt cặp khái niệm bạn tự nghĩ ra — coverage gate vs exposure gate — lên nền lý thuyết
- Khắc cốt: tokenizer là hợp đồng bất biến giữa data, model và inference
1. Vì sao phải có tokenizer?
Model chỉ ăn số — phải cắt văn bản thành đơn vị rồi tra số. Ba mức cắt:
| Mức | "Hắn đi rồi" | Ưu | Nhược |
|---|---|---|---|
| Ký tự / byte | H·ắ·n·␣·đ·i… (11+) | Không bao giờ bó tay trước từ lạ | Chuỗi rất dài → decode chậm, khó học quan hệ xa |
| Từ | Hắn / đi / rồi (3) | Ngắn, mỗi token có nghĩa | Vocab khổng lồ; từ mới → <unk> chết cứng |
| Subword | Hắn / đi / rồi; "Mộ Dung Phục" → Mộ·Dung·Phục | Từ phổ biến giữ nguyên khối, từ hiếm bị băm | Phải học cách cắt từ data |
Subword thắng tuyệt đối nhờ một tính chất đẹp: tần suất quyết định độ dài — hay gặp thì thành một token gọn, hiếm thì trả giá bằng nhiều token; vocab hữu hạn nhưng không bao giờ chịu thua chuỗi lạ (nhất là khi có byte-fallback).
2. BPE chạy tay trong 90 giây
Byte-Pair Encoding chỉ có một động tác lặp: đếm cặp kề nhau phổ biến nhất → dán thành token mới → lặp. Corpus tí hon: học học học hỏi hòa
Khởi đầu: h-ọ-c h-ọ-c h-ọ-c h-ỏ-i h-ò-a
Đếm cặp: (h,ọ)=3 (ọ,c)=3 (h,ỏ)=1 (ỏ,i)=1 (h,ò)=1 (ò,a)=1
Merge 1: (h,ọ) → "họ" ⇒ họ-c họ-c họ-c h-ỏ-i h-ò-a
Merge 2: (họ,c) → "học" ⇒ học học học h-ỏ-i h-ò-a
Merge 3: … tần suất thấp dần, dừng khi đủ vocab
Sản phẩm gồm hai thứ: bảng vocab (token → id) và danh sách merge có thứ tự ưu tiên. Tokenize văn bản mới = áp lại các merge theo đúng thứ tự đã học — chi tiết này chính là gốc rễ vụ UNUSED pieces bạn từng đổ mồ hôi (mục 6).
SentencePiece (SPM) là "BPE đóng gói công nghiệp": tự lo chuẩn hóa, coi khoảng trắng là ký tự thường (ký hiệu ▁ Metaspace), không cần tách từ theo dấu cách — điểm sống còn với tiếng Trung vốn không có dấu cách.
3. Token/ký-tự — con số nhỏ, hệ quả lớn
Case của bạn: Moxhi (SPM 24k) đạt 0.73 tok/char trên tiếng Trung; model đối thủ 50M dùng byte-BPE kiểu GPT-2 (chữ Hán vỡ thành 3 byte rồi mới merge lại) đạt 0.88 tok/char → cùng một chương truyện, đối thủ xử lý 1.21× số token:
4. Byte-fallback, coverage và exposure
Vocab 24k không chứa nổi ~90k chữ Hán. Chữ ngoài vocab, thay vì thành <unk> (mất trắng), được xé xuống mức byte UTF-8 — luôn biểu diễn được:
Từ đó, cặp gate bạn đặt ra có chỗ đứng lý thuyết rõ ràng — hai gate độc lập đúng như bạn kết luận:
| Gate | Câu hỏi | Chặn lỗi gì |
|---|---|---|
| Coverage | Ký tự bắt buộc (tên nhân vật, thuật ngữ tu luyện…) có là token thật trong vocab không, hay rơi vào byte-fallback? | Lỗi biểu diễn — chưa nói tới học, biểu diễn đã nát (điều kiện cần) |
| Exposure | Token đó có xuất hiện đủ nhiều, đủ đa dạng ngữ cảnh trong train, với target Hán-Việt nhất quán không? | Lỗi học — có slot trong vocab mà không có bài học thì embedding vẫn là rác (điều kiện đủ) |
Audit stv-lite v3 minh họa luôn: 87 599 term, 0 <unk> (fallback hoạt động), nhưng chỉ 62% chữ Hán distinct "visible OK" — 38% sống kiếp byte, và danh sách đó dự báo trước các chữ sẽ dịch trượt. Giải pháp thực dụng cho tên hiếm mà bạn đã đi đúng: đừng bắt model học thuộc — cho nó cơ chế chép (hint-injection, glossary, pointer/copy — Module 08 & 10).
5. Chọn vocab size — chia ngân sách tham số
| Vocab to (vd 48k) | Vocab nhỏ (vd 8k) | |
|---|---|---|
| Fertility | Thấp — câu ngắn, decode nhanh | Cao — câu dài, decode chậm |
| Embedding chiếm param | Nặng | Nhẹ |
| Token hiếm | Nhiều token "đói" exposure | Ít — token nào cũng được luyện kỹ |
| Rủi ro chính | Under-trained tail | Mất nghĩa nguyên khối của từ ghép |
Vocab 24 001 × d_model 512 = 12.3M tham số embedding
Model A9-41M → embedding chiếm ~30% toàn model (đã tied input/output — Module 05)
Model 500M → cùng embedding đó chỉ chiếm ~2.5%
Model càng nhỏ, mỗi slot vocab càng đắt → 24k cho zh-vi nằm đúng vùng hợp lý. Spec bạn tự viết đã rất chuẩn: shared vocab zh+vi, NFC bắt buộc, không lowercase/strip dấu (tiếng Việt mất dấu = mất nghĩa!), train trên text thật (không JSON/metadata), control token tối giản <bos> <eos> <zh> <vi> <ctx> <glossary>.
Case study — vụ UNUSED pieces: tokenizer có semantics ngầm
Bối cảnh: convert model SPM sang tokenizer.json để chạy runtime tự viết trong webMT. Tưởng là bài dịch format 1-1.
Sự cố: bản convert "ngây thơ" (bỏ các piece đánh dấu UNUSED khỏi bảng merge) tokenize lệch 236/6 238 dòng so với SPM gốc.
Hai semantics không tài liệu nào viết: (1) piece UNUSED vẫn tham gia đấu giá merge với đầy đủ điểm số — nó có thể thắng, rồi chỉ đến bước xuất mới bị phân rã thành con; bỏ nó khỏi bảng merge = đổi kết quả mọi cuộc đấu giá nó từng thắng — 138 piece UNUSED âm thầm định hình tokenization của văn bản bình thường. (2) Phân rã không đoán bằng heuristic được — phải mô phỏng lại BPE với các merge rank thấp hơn (出手机 phân rã thành 出手|机, không phải 出|手机).
Bài học khái quát: tokenizer không phải bảng tra — nó là chương trình (chuỗi merge có thứ tự + normalize + decode), và mọi bản port phải golden-test đối chiếu từng dòng trên corpus lớn, không phải thử vài câu. Họ hàng chiều decode bạn cũng đã gặp: byte_fallback=true mà decoder thiếu mắt xích ByteFallback → ◇ hiển thị thành <0xE2><0x97><0x87>.
6. Tokenizer là hợp đồng bất biến
Đổi tokenizer = đổi model. Mọi tham số được học theo một cách băm token cụ thể. Không tồn tại "nâng cấp tokenizer cho model cũ" — chỉ tồn tại train lại.
Thứ tự chuẩn khi mở project MT mới (đúc từ chính project của bạn): gom corpus đại diện → train thử 2–3 phương án tokenizer → đo fertility + coverage trên danh sách ký tự bắt buộc + audit tail vocab → chốt, version hóa, golden-test → rồi mới nói chuyện kiến trúc model.
Bẫy thường gặp
- So loss giữa hai tokenizer (tái phạm nhiều nhất — Module 01) → so per-char hoặc so bằng eval hành vi.
- Train tokenizer trên data có metadata/JSON/HTML → vocab đầy token rác chiếm slot của chữ nghĩa thật.
- Normalize lúc train ≠ lúc inference (NFC nơi có nơi không, full-width/half-width) → phân phối lạ, lỗi "ma" không tái hiện được.
- Đụng trần context vì tin số "ký tự". 512 token ≠ 512 chữ; fertility 0.73 → ~700 chữ Hán là kịch trần, mà target Việt cũng ăn chung cửa sổ đó (model 41M của bạn: control + source + target chung 512).
- Thêm token đặc biệt sau khi train mà không fine-tune đủ exposure → token mới mang embedding init ngẫu nhiên, hành vi bất định.
Tự kiểm tra
1 · Corpus ba ba bà bà ba bà bạn — chạy BPE 2 merge đầu. Merge nào thắng trước?
Đếm: (b,a)=3, (b,à)=3, (b,ạ)=1, (ạ,n)=1 — hòa 3-3. BPE phá hòa theo quy tắc xác định (thứ tự xuất hiện/so chuỗi) — giả sử (b,a)→"ba" trước, merge 2 là (b,à)→"bà". Bài học phụ: tie-breaking cũng là semantics — hai implementation phá hòa khác nhau cho ra tokenizer khác nhau; đó là loại chi tiết gây 236 dòng lệch trong vụ convert của bạn.
2 · Tokenizer mới fertility 0.95 (hiện tại 0.73), model cùng cỡ cùng ms/token. Dịch chương 3 000 chữ thay đổi thế nào?
Số bước decode tỉ lệ số token: 0.95/0.73 ≈ 1.30× thời gian (chậm thêm ~30%) — chưa kể mất ~23% dung lượng context. Fertility là thuế mọi mặt trận.
3 · Tên 珺瑤 toàn chữ hiếm ngoài vocab, không train lại tokenizer — 3 hướng xử lý, xếp hạng?
(1) Glossary/hint-injection: đưa cặp 珺瑤→"Tuấn Dao" vào ngữ cảnh, dạy model chép từ hint — rẻ, đúng hướng namefix của bạn. (2) Pointer/copy head: chép trực tiếp từ nguồn — mạnh nhất nhưng phải sửa kiến trúc (Track G). (3) Hậu xử lý: bắt byte-fallback span trong output, tra từ điển Hán-Việt thay — không đụng model nhưng vỡ khi model đã "bịa Latin". Hạng bét: học thuộc qua fine-tune — exposure vài lần không đủ (bench 0.25 là bằng chứng).
4 · Vì sao spec của bạn cấm lowercase và strip dấu, dù nhiều tokenizer tiếng Anh làm thế?
Tiếng Việt mất dấu = mất nghĩa (bàn/bán/bạn); hoa/thường mang tín hiệu tên riêng — thứ project của bạn coi trọng bậc nhất. Normalize chỉ được khử biến thể vô nghĩa (NFC, full/half-width), không được khử thông tin ngôn ngữ. Mỗi ngôn ngữ một định nghĩa "vô nghĩa" — copy config tiếng Anh là bug.
Phần II — NLP & Transformer · Module 05
05 Embedding: không gian của ý nghĩa
Mục tiêu
- Hiểu embedding là gì (một bảng tra học được) và "không gian ý nghĩa" theo đúng nghĩa hình học
- Tính tay cosine similarity và biết khi nào tin / không tin nó
- Phân biệt embedding tĩnh và theo ngữ cảnh — chìa khóa cho câu hỏi 13/08 của bạn: "embedding có giúp term detector/verifier không?" (có — và module này chỉ cách dùng đúng)
- Hiểu vì sao tied embedding tiết kiệm ~30% tham số cho model 41M
1. Từ id vô hồn đến vector có nghĩa
Sau tokenizer, câu của bạn là dãy id: [8412, 209, 5771]. Nhưng id chỉ là số thứ tự — id 8412 chẳng "gần" id 8413 hơn id 20000 chút nào. Model cần thứ tốt hơn: mỗi token một vector dày đặc (vd 512 chiều).
Embedding table = ma trận vocab × d_model; lookup = lấy hàng thứ id. Với bạn: 24 001 × 512. Điều kỳ diệu duy nhất: ma trận này là tham số được học — gradient từ loss chảy về tận từng hàng (Module 02); token nào xuất hiện trong batch thì hàng của nó được cập nhật một chút. Vì các token dùng giống nhau thì đóng vai trò giống nhau trong việc giảm loss, sau trăm nghìn bước, một trật tự tự phát xuất hiện:
2. Đo "độ gần nghĩa": cosine similarity
Thước đo mặc định giữa hai vector là góc giữa chúng (bỏ qua độ dài):
cos(a, b) = (a·b) / (‖a‖ × ‖b‖) ∈ [−1, 1]
a = (2, 1), b = (3, 2), c = (−1, 2)
a·b = 2·3 + 1·2 = 8 ‖a‖ = √5 ≈ 2.24 ‖b‖ = √13 ≈ 3.61
cos(a,b) = 8 / (2.24 × 3.61) ≈ 0.99 → gần như cùng hướng (rất giống nghĩa)
a·c = 2·(−1) + 1·2 = 0 → cos(a,c) = 0 → vuông góc (không liên quan)
Ba điều khắc cốt khi dùng cosine thực tế:
- Chỉ so được trong CÙNG một không gian. Vector model A không so cosine với vector model B — hai hệ tọa độ khác nhau.
- Ngưỡng tuyệt đối vô nghĩa, thứ hạng mới có nghĩa. Nhiều encoder bị anisotropy — mọi vector chen chúc trong một hình nón hẹp, cặp câu nào cosine cũng > 0.6. Dùng top-k gần nhất, hoặc calibrate ngưỡng trên data có nhãn.
- Embedding từ model dịch nhỏ ≠ sentence encoder xịn. Hidden state của Marian 41M học để phục vụ dịch — dùng được cho việc gần nghề (so term trong domain), nhưng đo "hai câu cùng ý?" thì sentence-transformers đa ngữ làm tốt hơn hẳn.
3. Tĩnh vs theo ngữ cảnh — nâng cấp quan trọng nhất
| Embedding tĩnh (bảng tra, layer 0) | Embedding ngữ cảnh (hidden state sau các layer) | |
|---|---|---|
| Mỗi token | 1 vector cố định mãi mãi | 1 vector mỗi lần xuất hiện, phụ thuộc câu xung quanh |
| 打 trong 打人 / 打电话 / 打游戏 | Cùng 1 vector trộn "đánh/gọi/chơi" | 3 vector khác nhau, mỗi cái ngả về nghĩa đúng |
| Lấy ở đâu | Hàng của embedding table | Đầu ra layer giữa/cuối của encoder |
| Dùng cho | Điểm khởi đầu của mọi tính toán | Mọi tác vụ hiểu nghĩa nghiêm túc |
Cơ chế biến tĩnh thành ngữ cảnh chính là attention — Module 06. Ở đây chỉ cần trực giác: vector đi qua các layer được "nhuộm" dần bởi ngữ cảnh quanh nó.
Case study — term detector/verifier bằng embedding (câu hỏi 13/08 của bạn)
Bài toán: corpus chỉ vài nghìn chữ (trang Vietphrase / file Reader), term/tên riêng lặp 2–3 lần — muốn auto-approve term điểm cao. Embedding giúp được — nhưng phải là embedding ngữ cảnh, và model phải luyện từ TRƯỚC. Heuristic thuần đuối vì thống kê nội-corpus quá mỏng; embedding cứu được vì model đã pre-train trên hàng nghìn novel mang tri thức "ngữ cảnh quanh một tên người trông thế nào" — với term mới toanh, ngữ cảnh quanh nó vẫn là thứ model đã quen.
4. Tied embedding — một ma trận, hai vai
Model sinh text có hai đầu chạm vào vocab: input embedding (id → vector, vào model) và output projection (vector → điểm cho 24 001 token, ra phân phối). Hai ma trận cùng cỡ, và về nghĩa là cùng một ánh xạ nhìn hai chiều → buộc dùng chung (weight tying).
tie_word_embeddings là dòng config đáng kiểm tra: quên tie ở model nhỏ là vứt ¼ ngân sách tham số.Bẫy thường gặp
- So cosine giữa hai model / hai layer khác nhau — khác hệ tọa độ; kết quả là số ngẫu nhiên trông có vẻ nghiêm túc.
- Tin ngưỡng cosine tuyệt đối ("0.8 là giống") mà không calibrate trên data của mình.
- Mean-pooling cả câu để so term. So term thì lấy vector tại đúng span (hoặc format [trái]«TERM»[phải]); trung bình cả câu pha loãng tín hiệu.
- Dùng embedding tĩnh cho bài cần ngữ cảnh. Bảng tra không phân biệt nổi 打 đánh/gọi/chơi — mọi bài entity cần hidden state.
- Quên chuẩn hóa trước khi encode — vẫn là hợp đồng tokenizer (Module 04): term lệch nhau chỉ vì full-width/half-width là lỗi có tiền lệ.
Tự kiểm tra
1 · a=(1,3), b=(2,6), c=(3,−1). Tính cos(a,b) và cos(a,c). Nhận xét?
b = 2a → cos(a,b) = 1.0 (cùng hướng tuyệt đối — độ dài không ảnh hưởng). a·c = 3−3 = 0 → cos(a,c) = 0. Cosine chỉ đo hướng: hai vector "cùng nghĩa nhưng cường độ khác" được coi là một — thường đúng ý ta khi so nghĩa.
2 · Vì sao contextual verifier phải pre-train trên nhiều novel thay vì train trên chính corpus cần xử lý?
Corpus vài nghìn chữ, term lặp 2–3 lần — không đủ thống kê để học: model sẽ thuộc lòng chính các ví dụ đó (overfit) và vô dụng với term khác. Pre-train dạy quy luật chung; corpus nhỏ chỉ là nơi áp dụng. Đúng phân vai train/inference của Module 01.
3 · Cần chấm "hai bản dịch có cùng nghĩa không" cho QC — dùng hidden state của HachimiMT-60 được không?
Được ở mức baseline yếu, nhưng đừng tin: encoder của model dịch học biểu diễn phục vụ decode, không được luyện mục tiêu "câu cùng nghĩa → vector gần". Chọn đúng dụng cụ: sentence encoder đa ngữ chuyên dụng, hoặc thực dụng hơn — so bằng chrF/COMET-style với câu nguồn làm mỏ neo (Module 13).
4 · Nhìn từ góc embedding: vì sao model "bịa Latin" cho tên western (bug slm-mt của bạn) thay vì giữ chữ Hán?
Tên western trong train target phần lớn viết Latin → vùng không gian "sau ngữ cảnh giới-thiệu-tên" trỏ mạnh về cụm token Latin. Gặp tên lạ (embedding nguồn mỏng/byte-fallback), model không có tín hiệu chép, nên rơi về prior của vùng đó: sinh một chuỗi Latin "trông giống tên". Ánh xạ chữ-hiếm→âm Hán-Việt chưa bao giờ đủ exposure. Fix đúng tầng: cơ chế copy/hint (Module 08, 09), không phải "train thêm cho nhớ".
Phần II — NLP & Transformer · Module 06
06 Attention & giải phẫu Transformer
Mục tiêu
- Tính tay được một phép attention hoàn chỉnh (Q·K → softmax → trộn V) với số cụ thể
- Đọc vị một block transformer: attention trộn giữa các token, FFN xử lý từng token, residual là đường sống của gradient
- Đếm ra đúng con số 57M của Hirashiba-medium từ config — từ nay nhìn config là "cân" được model
- Hiểu KV cache — nền của mọi tối ưu inference bạn làm ở webMT và llama.cpp (Module 14)
1. Vấn đề cần giải: trộn ngữ cảnh vào từng token
Module 05 dừng ở chỗ: embedding tra bảng là tĩnh — 打 trong 打电话 (gọi điện) và 打人 (đánh người) cùng một vector. Cần một cơ chế cho mỗi token nhìn quanh câu và tự pha lại nghĩa của mình. Cơ chế đó là attention, và ý tưởng gốc giản dị bất ngờ:
Mỗi token phát một câu hỏi (Query), mang một nhãn tìm kiếm (Key) và một gói nội dung (Value). Token mới của tôi = trung bình có trọng số các gói Value trong câu, trọng số cao cho token nào có Key khớp Query của tôi.
2. Tính tay một phép attention
Câu: 他 · 打 · 电话. Xét token 打 đang "hỏi". Ba tích vô hướng q(打)·k(·) cho ra ba điểm khớp, rồi softmax biến điểm thành trọng số dương có tổng = 1:
| Token được hỏi | điểm q·k | e^điểm | trọng số (softmax) |
|---|---|---|---|
| 他 | 1.0 | 2.72 | 0.090 |
| 打 (chính nó) | 2.0 | 7.39 | 0.245 |
| 电话 | 3.0 | 20.09 | 0.665 |
| Σ = 30.19 | Σ = 1.000 | ||
vector mới của 打 = 0.090·v(他) + 0.245·v(打) + 0.665·v(电话)
Multi-head: làm phép trên 8–16 lần song song với các bộ chiếu Q/K/V khác nhau — mỗi "đầu" tự học một kiểu quan hệ (đầu này soi cú pháp ai-làm-gì, đầu kia soi đồng tham chiếu, đầu khác soi ranh giới tên riêng…), rồi ghép kết quả lại. Không ai chỉ định vai trò từng đầu — chúng tự phân công trong lúc train, cùng cơ chế "trật tự tự phát" như embedding.
Và vì self-attention không vị trí vốn mù thứ tự: hoán vị token sẽ hoán vị các đầu ra tương ứng (permutation-equivariant), nhưng mạng không biết cái nào đứng trước. Vì vậy phải tiêm vị trí. Marian trong Transformers dùng MarianSinusoidalPositionalEmbedding cố định, được cấp capacity theo max_position_embeddings; GPT-2 dùng embedding vị trí học được; nhiều LLM mới dùng RoPE. Trần 512 của model Marian 41M là giới hạn capacity/config/runtime của bảng sin/cos, không phải vì nó là một bảng vị trí học được.
3. Giải phẫu một block
4. Encoder, decoder, và cross-attention — chính là Marian của bạn
Ba biến thể chỉ khác nhau ở ai được nhìn ai:
- Encoder (hiểu): mọi token nhìn mọi token — hai chiều.
- Decoder (sinh): token chỉ nhìn quá khứ (causal mask — che tương lai, không thì "nhìn trước đáp án" khi train).
- Encoder–decoder (dịch — Marian/Hachimi/Moxhi): decoder có thêm cross-attention — Query từ decoder, Key/Value từ đầu ra encoder: mỗi bước sinh tiếng Việt đều "ngoái nhìn" câu Trung đã mã hóa.
5. Đếm tham số: cân đúng 57M của Hirashiba
Với d_model = 512, FFN = 2048, vocab 24k, 6 layer encoder + 6 layer decoder:
Embedding (tied): 24 001 × 512 ≈ 12.3M
Block encoder: attention 4×(512×512) ≈ 1.05M
FFN 2×(512×2048) ≈ 2.10M → ~3.15M/block × 6 ≈ 18.9M
Block decoder: như trên + cross-attention (thêm ~1.05M)
→ ~4.20M/block × 6 ≈ 25.2M
────────────────────────────────────────────────────────────────────────
Tổng ≈ 12.3 + 18.9 + 25.2 ≈ 56.4M ≈ hira_medium_57m của bạn ✓
Bài học kép: (1) nhìn config là cân được model — không cần chạy code; (2) thấy rõ tiền nằm ở đâu: ở cỡ 57M, embedding chiếm ~22%, FFN ~45%, attention ~33%. Muốn model nhỏ mà khỏe, mỗi dòng config (vocab, d, FFN ratio, số layer) là một quyết định phân bổ ngân sách — nền tảng cho bài toán "trần 41M vs 130M" ở Module 15.
6. KV cache — vì sao decode không chậm như lý thuyết
Sinh token thứ 200 mà phải tính lại attention của cả 199 token cũ thì chết. May thay K và V của các token cũ không đổi → tính một lần, cất vào KV cache; mỗi bước mới chỉ tính Q/K/V cho một token mới rồi tra cache:
KV cache (self-attn decoder, fp16)
= số layer × 2 (K và V) × số vị trí × d_model × 2 byte
= 6 × 2 × 512 × 512 × 2 ≈ 6.3 MB / câu ← model 57M của bạn: quá rẻ
LLM 26B (d≈4096, 40+ layer, context 8k, nếu MHA) → hàng GB ← Module 14
Công thức trên là cho multi-head attention (MHA), nơi số KV head bằng số query head. Với GQA/MQA, nhân thêm tỉ lệ n_kv_heads / n_heads; ví dụ 8 KV head / 32 head làm cache còn khoảng 1/4. Hai điểm tinh tế đáng biết vì bạn đã đụng chúng ở webMT: (1) cross-attention K/V tính một lần duy nhất từ encoder — phần này không phình theo số token đích đã sinh; (2) cache là trạng thái phải quản lý: cấp phát theo capacity, LRU khi nhiều câu. Đổi lấy tiện, cache ăn VRAM tuyến tính theo context và batch.
KV cache: context dài thật sự tốn bao nhiêu?
Đổi MHA ↔ GQA để thấy chênh lệchbytes = batch × layer × 2(K,V) × token × d_model
× byte/phần tử × (n_kv_heads / n_heads)
Case study — webMT chính là module này viết bằng WGSL
Runtime WebGPU bạn tối ưu suốt tháng 7 chẳng qua là giải phẫu ở trên, mỗi khối thành một compute pass: mỗi matmul Q/K/V/FFN một pipeline (đó là lý do có 1 246 bind group trước khi bạn cache còn 99!); "immediates" thay uniform buffer cho tham số per-dispatch; autotune chọn subgroup on/off cho từng máy. Trần 512 token của model 41M là capacity vị trí mà config/runtime và artifact hiện tại hỗ trợ (mục 2), không phải một quy luật của attention. Và phát hiện "B1 +164.74% là edge case CPU-dispatch, không được ngoại suy" của bạn phản ánh đúng cấu trúc: batch 1 nghẽn ở số lần dispatch (mỗi block vài pass × 12 block), không nghẽn ở phép nhân.
Bẫy thường gặp
- Quên attention là O(n²). 512² = 262k cặp — nhẹ; 8k² = 64M cặp/layer/head — nổ. Đây đúng là tính toán khiến bạn kết luận CTC full-block 4k/8k "không khả thi <20ms" (logits ~375–750 MiB/câu). Muốn context dài phải đổi kiến trúc/kỹ thuật, không phải chỉ tăng con số config.
- Tưởng train decoder cũng sinh từng token. Lúc train có đáp án sẵn → chạy song song toàn bộ vị trí với causal mask (teacher forcing); chỉ inference mới tuần tự. Vì thế train nhanh mà decode chậm.
- Đọc attention map như "lời giải thích". Trọng số attention cao ≠ nguyên nhân quyết định; dùng để debug định hướng thì tốt, để "chứng minh" model hiểu thì không.
- Quên vị trí khi mở rộng context. Artifact/runtime chỉ cấp capacity 512 mà đưa câu 700 token thì có thể index vượt buffer hoặc bị chặn — lỗi cứng, không phải chỉ “chất lượng giảm dần”. Muốn nâng trần phải kiểm tra config, buffer vị trí, export và golden test cùng lúc.
Tự kiểm tra
1 · Điểm q·k của một token với 3 token trong câu là [2, 2, 4]. Trọng số attention?
e² = 7.39, e² = 7.39, e⁴ = 54.6 → tổng 69.4 → trọng số ≈ [0.106, 0.106, 0.787]. Chênh 2 điểm (trước softmax) đủ tạo áp đảo ~8:1 — softmax khuếch đại chênh lệch theo cấp mũ.
2 · Cân model: d=256, FFN=1024, vocab 16k, 4+4 layer, tied embedding. Ước lượng tham số?
Embedding 16k×256 ≈ 4.1M. Block encoder: attn 4×256² ≈ 0.26M + FFN 2×256×1024 ≈ 0.52M ≈ 0.79M ×4 ≈ 3.15M. Block decoder: +cross-attn 0.26M → 1.05M ×4 ≈ 4.2M. Tổng ≈ ~11.4M — đúng tầm hira_tiny 15M (thêm LN, bias, position là khớp).
3 · Vì sao cross-attention K/V không phình theo số token đã sinh, còn self-attention K/V thì có?
Cross-attention K/V đến từ encoder — câu nguồn cố định, mã hóa một lần là xong. Self-attention decoder thì mỗi token mới sinh thêm một cặp K/V phải cất vào cache → cache lớn dần theo độ dài output. Vì vậy câu nguồn dài làm chậm encode một lần, còn output dài làm chậm mọi bước decode.
4 · Model chat 26B decoder-only, context 8k, d=4096, 48 layer, fp16 — KV cache một phiên chat đầy context?
Nếu là MHA: 48 × 2 × 8192 × 4096 × 2 byte ≈ 6.44 GB = 6.00 GiB — khoảng nửa VRAM 12 GiB, chưa tính weights. Nếu GQA có 8 KV head / 32 query head thì nhân 1/4, còn khoảng 1.61 GB. Vì vậy phải biết n_kv_heads, không chỉ biết d_model/context.
Phần II — NLP & Transformer · Module 07
07 Encoder-only: BERT-like và bài toán "hiểu"
Mục tiêu
- Đặt nhánh thứ ba của transformer vào bản đồ: encoder-only (BERT-like) — không sinh chữ, chỉ biến chữ thành nhãn/điểm/vector — chính là thứ đang chạy trong ltp-ft và term verifier của webapp
- Hiểu masked language modeling (MLM) và vì sao "nhìn hai chiều" khiến encoder thắng thế ở các bài gán nhãn
- Đọc thành thạo bài sequence labeling: BMES cho tách từ (CWS), BIO cho NER — và vì sao NER của LTP không chạy được nếu thiếu CWS
- Giải phẫu chiến dịch ltp-ft của bạn (NER 0.604 → 0.849) và bài học ngược chiều từ verifier v1: khi nào không cần BERT
1. Nhánh thứ ba của gia phả transformer
Module 06 mổ xẻ một block transformer; Module 08 sẽ nói về các kiến trúc sinh chữ. Nhưng cả một mảng project của bạn — tách từ, gán POS, bắt entity, chấm keep/reject cho term — không cần sinh chữ nào cả. Chúng cần hiểu: nhận văn bản, trả về nhãn, điểm số, hoặc vector. Đó là lãnh địa của encoder-only:
2. MLM — dạy "hiểu" bằng trò điền vào chỗ trống
Encoder không có "token tiếp theo" để đoán (nó nhìn cả câu rồi còn gì). Mục tiêu pretrain của nó là masked language modeling: che ngẫu nhiên ~15% token, bắt model đoán lại từ cả hai phía ngữ cảnh:
Sau pretrain, encoder là một "cỗ máy đọc hiểu" đầy tri thức ngôn ngữ nhưng chưa biết làm việc gì cụ thể — đúng trạng thái "nền" của trục fine-tune Module 10. MiniRBT-H256 bạn dùng trong ltp-ft chính là một RoBERTa tiếng Trung thu nhỏ bằng distillation (Module 11 áp cho encoder!): hidden 256, vỏn vẹn 10.28M tham số — nhỏ hơn cả model dịch tiny của bạn.
3. Thân + đầu: một encoder, nhiều nghề
4. Sequence labeling — đọc BMES/BIO trong 2 phút
Bài gán nhãn chuỗi mã hóa "khoanh vùng" thành nhãn per-token. Tách từ (CWS) dùng BMES (Begin-Middle-End-Single); NER dùng BIO (Begin-Inside-Outside) kèm loại entity. Ví dụ với đúng cái tên trong audit của bạn:
| 张 | 菊 | 花 | 去 | 北 | 京 | |
|---|---|---|---|---|---|---|
| CWS (BMES) | B | M | E | S | B | E |
| NER (BIO) | B-Nh | I-Nh | I-Nh | O | B-Ns | I-Ns |
| 张菊花 = một từ (tên người, Nh) · 去 = từ đơn · 北京 = một từ (địa danh, Ns). Nhãn LTP: Nh = người, Ns = địa danh, Ni = tổ chức. | ||||||
Hai điểm thực chiến:
- Decode có ràng buộc: chuỗi nhãn phải hợp lệ (I-Nh không được đứng sau O; M không được mở đầu từ). Model per-token đôi khi phát nhãn vô lý → cần lớp sửa (CRF/Viterbi hoặc rule) trước khi dùng — cùng tinh thần "collapse" của CTC ở Module 08.
- Vì sao NER của LTP cần CWS (điều bạn phát hiện khi tối ưu pipeline): LTP là multi-task — một thân encoder chung, sáu đầu (CWS/POS/NER/DEP/SRL/SDP), và đầu NER chấm trên ranh giới từ do CWS cung cấp chứ không trên từng ký tự. Nên bạn drop được POS/DEP/SRL/SDP cho nhẹ, nhưng CWS là xương sống không bỏ được:
Case study — ltp-ft: một chiến dịch fine-tune encoder mẫu mực
Chẩn bệnh bằng per-class F1. Baseline LTP tiny trên tierA 500 câu của bạn: CWS 0.912, POS 0.953 — nhưng NER chỉ 0.604, và bảng tách loại mới lộ chuyện: Nh (người) 0.969, Ni (tổ chức) 0.000, Ns (địa danh) 0.330. Model pretrain trên văn báo chí chưa từng thấy "青云门" là một tổ chức hay "Nam Cương" là địa danh — domain shift không hiện trên điểm trung bình, chỉ hiện khi tách lớp (đúng bài by-bucket của Module 13).
Chữa bằng data hai tầng silver/gold. Công thức MiniRBT-H256 của bạn: 39 797 câu silver (máy gán nhãn — chính là distillation kiểu encoder, Module 11: model to/pipeline cũ làm teacher gán nhãn rẻ) + 6 278 câu gold (người duyệt) lặp 3× để tăng trọng số, 5 epoch, full fine-tune cả 10.28M tham số. Kết quả dòng tiny: NER typed F1 0.604 → 0.849 (+24.5 điểm) — không đổi kiến trúc, không thêm tham số, chỉ đổi data. Minh chứng sống cho Module 03: trần nằm ở data.
Và một thí nghiệm 2-arm đúng sách (Module 09): bạn chạy song song Exp1 (gold-only: 2 000 câu × 25 epoch) vs Exp2 (silver pre-train 44 762 câu × 3 epoch → gold fine-tune), chấm trên cùng test 500 câu niêm phong — để trả lời một câu hỏi sắc: silver đang đóng góp hay đang là trần? Đây chính xác là cách nghi vấn "chất lượng teacher quyết định trần student" (Module 11) được kiểm chứng bằng thực nghiệm thay vì tranh cãi.
Đích đến khép vòng về hệ MT: LTP tiny fine-tune sẽ chạy client-side trong ltp-web/moxhi.vietphrase.app để bắt entity cho user khóa lại — chống name drift khi dịch, bắt tay với hint-injection của Module 10. Encoder "hiểu" đứng gác cổng cho decoder "sinh".
5. Bài học ngược chiều: verifier v1 — khi nào KHÔNG cần BERT
Cùng lúc đó, TermCandidateVerifier v1 trong webapp của bạn lại cố tình không dùng encoder: logistic regression / MLP 1 lớp ẩn trên 80–150 feature tính tay (PMI, freedom, spread, shape flags kiểu họ+tên/hậu tố xưng hô, context flags, xung đột từ điển…), xuất ra JSON <100KB, chạy tức thời trong browser worker. Và nó tốt: bench-gt v2 đạt 0/213 positive bị giết oan, chặn 94.56% của 92 negative. Vì sao mô hình "cổ điển" thắng ở đây?
| Rules (v15) | Logistic + feature tay (verifier v1) | Encoder nhỏ fine-tune (LTP, MiniRBT) | LLM | |
|---|---|---|---|---|
| Data cần | 0 (viết tay) | vài nghìn nhãn | vài chục nghìn câu gán nhãn | 0–vài chục (few-shot) |
| Chi phí mỗi lần chạy | ~0 | ~0 (JSON trong worker) | nhỏ (10–100M param, cần runtime) | đắt (API/GPU to) |
| Hiểu ngữ cảnh | không | chỉ qua feature đã mã hóa | có — hai chiều | có, kèm tri thức thế giới |
| Trần chất lượng | thấp, giòn | trần = độ tốt của feature | cao cho bài trong-domain | cao nhất, kém ổn định chi phí |
| Trong hệ của bạn | v15 structural | verifier v1–v3 | ltp-ft · contextual verifier (kế hoạch 13/08) | teacher/reviewer offline |
Nguyên tắc rút ra — "câu hỏi hẹp thì model hẹp": verifier không hỏi "mọi entity ở đâu trong văn bản" (full NER — cần encoder) mà chỉ hỏi "candidate ĐÃ ĐƯỢC detector đề cử này có nên hiện không?" — bài nhị phân trên đối tượng đã khoanh vùng, feature tay mô tả được gần hết tín hiệu → vài nghìn nhãn là đủ, và latency browser ~0 là tính năng sản phẩm. Chỉ khi nào chạm trần của feature tay (các ca cần đọc ngữ cảnh thật — đúng các ca bạn mô tả hôm 13/08) thì mới leo lên nấc encoder, và leo theo kiểu Module 05: encoder làm một phiếu bên cạnh rules + policy, không thay thế chúng. Kèm một viên ngọc ẩn trong thiết kế v1 của bạn: Phase 3 active learning — mỗi lần user review một term là một nhãn mới rơi vào kho retrain. Đó là data flywheel: sản phẩm tự nuôi model của chính nó.
Bẫy thường gặp
- Đánh giá NER bằng token accuracy. Nhãn O chiếm 80–90% token → model đoán bừa "O tất" đã được ~85% accuracy. Thước đúng là span-level typed F1 (đúng cả biên lẫn loại mới tính) — như dòng "typed F1 84.9" bạn đang dùng.
- Lệch tagging scheme giữa train và eval (BIO vs BMES vs BIOES) — điểm rơi tự do mà không có gì "báo lỗi". Scheme là một phần của hợp đồng data, version nó như tokenizer (Module 04).
- Silver không audit. Máy gán sai có hệ thống (vd luôn hụt tên 4 chữ) → student học nguyên lỗi đó với sự tự tin cao (Module 11, bẫy 4). Gold để sửa hệ thống, không chỉ để cộng điểm — vì thế gold ×3 của bạn là đòn bẩy đúng chỗ.
- Dùng encoder sinh text ("bắt BERT viết tiếp câu") — sai nghề từ kiến trúc; và ngược lại, dùng LLM 26B chấm keep/reject cho 50 nghìn candidate — đúng nghề nhưng phá sản chi phí. Nhìn bảng nấc thang trước khi chọn vũ khí.
- Fine-tune encoder trên corpus tí hon (vài nghìn chữ) — overfit tức thì; đã cảnh báo ở Module 05 và vẫn là bẫy số một của mọi kế hoạch "train verifier từ chính file đang dịch".
- Quên ràng buộc BIO khi decode — một nhãn I mồ côi làm vỡ span, và lỗi chỉ hiện ở tầng ứng dụng (term cắt cụt) rất khó truy ngược.
Tự kiểm tra
1 · Gán BMES (CWS) và BIO (NER) cho câu: 李青云拜入青云门
CWS: 李B 青M 云E · 拜B 入E · 青B 云M 门E. NER: 李青云 = B-Nh I-Nh I-Nh · 拜入 = O O · 青云门 = B-Ni I-Ni I-Ni. Điểm hay: chuỗi 青云 xuất hiện hai lần — lần đầu là ruột tên người, lần sau là ruột tên môn phái. Từ điển tra "青云" bó tay; chỉ ngữ cảnh hai chiều phân xử được — đó là lý do tồn tại của encoder, và cũng đúng chỗ Ni 0.000 của baseline (model báo chí chưa từng thấy môn phái là một "tổ chức").
2 · Vì sao model pretrain bằng MLM không sinh văn bản được, dù nó đoán từ bị che rất giỏi?
MLM đoán chỗ trống khi đã biết cả hai phía — nó chưa bao giờ học bài "chỉ có quá khứ, hãy viết tiếp". Sinh text cần một trật tự sinh (trái→phải như AR, hoặc lịch tinh chỉnh như diffusion/CMLM — Module 08); encoder không có trật tự nào cả. Đây là trade-off nền tảng: bỏ trật tự sinh để mua ngữ cảnh hai chiều.
3 · Đọc bench verifier v2: positiveReject 0/213, negativeReject 94.56%/92. Muốn "chặn nốt ~5 negative lọt lưới" thì trả giá gì?
Đây là precision/recall trade-off qua ngưỡng: siết ngưỡng để chặn thêm negative thì bắt đầu giết oan positive (positiveReject rời khỏi 0 — đúng tiêu chí promotion "no increase in positive rejections" bạn đặt cho v3). Lưu ý thêm: 92 negative là mẫu nhỏ — 94.56% có khoảng tin cậy rộng; siết ngưỡng dựa trên 5 ca lọt lưới của một bench nhỏ là tối ưu vào nhiễu (Module 13). Đường đúng: gom thêm negative từ active learning rồi mới chỉnh.
4 · Bài mới: phân biệt câu thoại vs câu tường thuật, và bài khó hơn: câu thoại này của NHÂN VẬT nào. Chọn công cụ cho từng bài?
Bài 1 — tín hiệu nằm gần hết ở bề mặt (dấu ngoặc kép, 道/说/问, vị trí đầu câu): rules + logistic trên feature tay, vài trăm nhãn là chạy tốt — đừng động tới encoder. Bài 2 — speaker attribution cần đọc ngữ cảnh nhiều câu, đồng tham chiếu, xưng hô: encoder fine-tune (hoặc giao LLM offline gán silver rồi distill xuống encoder nhỏ — đúng công thức silver/gold của ltp-ft). Một cặp bài, hai nấc thang — chọn nấc theo nơi tín hiệu nằm, không theo độ "xịn" của model.
Phần II — NLP & Transformer · Module 08
08 Kiến trúc dịch máy: AR vs NAR
Mục tiêu
- Nắm ba họ kiến trúc MT bạn đang vận hành song song: encoder-decoder AR (Marian), decoder-only AR (LLM), non-autoregressive (CTC/NAT ở vp2vi, slm-mt)
- Hiểu tận gốc bench của bạn: vì sao CTC latency phẳng ~70–79ms còn AR leo dốc theo độ dài — và điểm giao nằm đâu
- Biết vì sao NAT "sống được" nhờ distillation, và CTC yếu đúng ở ba trục bạn đã đo: independence, omission, reorder
- Đặt được Track G (shallow-AR + pointer/copy) của bạn vào bản đồ lý thuyết
1. Ba họ kiến trúc — ba cách trả lời "sinh câu đích thế nào"
2. AR: trả tiền theo token — và beam search
Decode AR chọn token thế nào từ phân phối? Greedy: lấy max mỗi bước — nhanh, nhưng tham lam cục bộ (chọn sớm một từ khiến cả câu sau bí). Beam search: nuôi song song k giả thuyết tốt nhất (k = beam size, thường 4–5 cho MT), cuối cùng lấy chuỗi tổng điểm cao nhất. Beam to hơn không đồng nghĩa tốt hơn — quá to còn tệ đi ("beam curse": nghiêng về câu ngắn, an toàn, nhạt). Chi phí: thời gian ≈ số bước × chi phí một lượt decoder × beam — nhân ba thừa số là hiểu mọi bài toán tốc độ AR.
3. NAR: sinh một lượt — và cái giá của sự song song
NAT sinh mọi vị trí cùng lúc: nhanh cố định, nhưng các vị trí không nhìn thấy lựa chọn của nhau. Câu nguồn có nhiều bản dịch hợp lệ (嗯 → Vâng/Ừ/Ừm — multimodality), mỗi vị trí tự chọn theo phân phối riêng thì dễ ra trộn chéo hai bản dịch: nửa đầu theo bản A, nửa sau theo bản B — sinh đúng loại lỗi bạn đo được ở CTC: independence (từ cạnh nhau vênh nhau), omission (hai vị trí cùng "tưởng bên kia lo rồi"), reorder yếu.
Vì thế NAT sống được nhờ distillation (Module 11): cho teacher AR dịch lại toàn bộ train data → data mới chỉ còn một kiểu dịch nhất quán (đơn-mode) → NAT student không còn bị xé giữa nhiều bản dịch. Đây là lý do mọi paper NAT đều distill trước, và là lý do chất lượng teacher quyết định trần NAT của bạn.
CTC — biến thể NAT bạn dùng — thêm một chiêu khéo: sinh chuỗi slot dài hơn output (thường 2× số token nguồn), cho phép mỗi slot phát token thật hoặc ∅ (blank), rồi collapse (gộp lặp liền kề, xóa blank). Model nhờ đó tự học cách "canh hàng" nguồn–đích mà không cần biết trước độ dài output — đổi lại, alignment bị ép monotonic (trái sang phải), nên reorder xa là điểm mù cấu trúc:
Case study — bench crossover của bạn, vẽ lại thành đồ thị
Số liệu RTX PRO 6000 (07/2026): CTC 4L latency phẳng ~70–79ms bất kể độ dài; so với AR: thua ở output ngắn (0.56× tại novel576/48 bước) nhưng thắng ở output dài (1.85× tại pack192/192 bước). Nội suy tuyến tính chi phí AR từ hai điểm đó:
4. Bản đồ các hướng NAT nâng cao — đặt tên cho những gì bạn đã nghiên cứu
| Hướng | Ý tưởng một dòng | Vị trí trong bản đồ của bạn |
|---|---|---|
| GLAT | Train NAT theo giáo án dễ→khó: lộ dần một phần đáp án làm mồi | Cải thiện train CTC hiện có, không đổi kiến trúc |
| CMLM / Mask-Predict | Sinh nháp cả câu → che chỗ kém tự tin → sinh lại, lặp k vòng | Direction 2 của vp2vi — NAR lặp: mua chất lượng bằng vài lượt |
| Levenshtein Transformer | Model học 3 thao tác biên tập: chèn / xóa / thay | Hợp bản chất "sửa VP thành câu Việt" của vp2vi — nhưng phải xây lại model, loss, exporter, runtime |
| DA-Transformer | Biểu diễn các bản dịch như các đường đi trong DAG — nới lỏng monotonic của CTC | Ứng viên "clean-sheet" dài hạn codex đề xuất |
| Speculative (Medusa/EAGLE/Lookahead) | Model nháp rẻ sinh trước, model chính chỉ kiểm hàng loạt | NO-GO cho webMT — và giờ bạn biết vì sao (dưới đây) |
Vì sao speculative NO-GO cho model nhỏ: speculative có lãi khi một lượt model chính rất đắt (LLM 70B) — thuê "thư ký nháp" rẻ là hời. Model 41M mỗi lượt đã rẻ sẵn → tiền thuê thư ký (chạy draft + verify + đồng bộ) cao hơn tiền tiết kiệm. Kết luận bạn ghi trong memo ("premise rejected: small model's low per-step overhead doesn't amortize draft+verify") chính xác là định lý này — giá trị của speculative tỉ lệ thuận với độ đắt mỗi bước của model chính.
5. Track G của bạn dưới ánh sáng lý thuyết
Thiết kế shallow-AR hybrid (4 encoder / 1 decoder + pointer/copy head) là một câu trả lời "đúng sách" cho đúng ba điểm yếu đã đo của CTC:
- 1 layer decoder AR → trả lại sự phối hợp tuần tự giữa các vị trí (chữa independence/omission/reorder) mà chỉ trả giá 1 lượt mỏng mỗi token — nằm giữa hai đường của đồ thị crossover.
- Pointer/copy head → với tên riêng (83% chép thẳng từ input theo đo đạc của bạn), cho model một "cần gạt" chọn giữa sinh từ vocab và chỉ tay vào vị trí nguồn rồi chép — né hoàn toàn bài toán exposure chữ hiếm (Module 04–05).
- ZH làm chuỗi chính (1×), VP làm feature đính kèm → tránh 2.4× thuế độ dài chuỗi.
Từ vựng để nhớ: đây là mẫu "AR mỏng trên nền encode dày" — giữ phần đắt (hiểu) chạy một lần, phần tuần tự (phối hợp) làm mỏng nhất có thể.
Bẫy thường gặp
- Train NAT thẳng trên data thô (bỏ qua distillation) → multimodality xé student, chất lượng sập — không phải "NAT dở" mà là thiếu điều kiện sống của nó.
- So chất lượng hai kiến trúc ở hai ngân sách tốc độ khác nhau — so AR beam-5 với NAT một-lượt là so táo với cam; cố định trục tốc độ rồi mới so chất lượng (hoặc vẽ cả đường trade-off).
- Dùng CTC cho cặp ngôn ngữ đảo trật tự mạnh (zh→en câu phức) — monotonic là giả định cấu trúc, không phải hyperparameter chỉnh được.
- Tăng beam vô tội vạ. Sau 4–5, chất lượng đứng hoặc giảm (thiên vị câu ngắn an toàn) mà chi phí tăng tuyến tính.
- Ngoại suy benchmark một điểm. Đúng bài học B1 +164.74% của bạn: một cấu hình đo được không đại diện cả đường cong — đo ít nhất hai đầu phân phối workload.
Tự kiểm tra
1 · Máy mới đo được: CTC phẳng 60ms; AR = 8 + 0.55×(số bước) ms. Điểm giao? Workload trung bình 70 bước thì chọn gì?
Giao khi 8 + 0.55s = 60 → s ≈ 95 bước. Workload 70 bước < 95 → AR nhanh hơn (8+38.5 = 46.5ms < 60ms). Nhưng nếu phân phối lệch (nhiều đoạn 150+ bước) thì tính theo phân phối, đừng theo trung bình — đúng bài "50/50 weighted" của bạn.
2 · Vì sao CTC cần số slot ≥ độ dài output (thường 2× nguồn), không lấy đúng bằng nguồn?
Collapse chỉ rút ngắn (gộp lặp, xóa blank), không kéo dài được — nên số slot là trần cứng của độ dài output. zh→vi thường 1 chữ Hán → 1–2 âm tiết Việt, lấy 2× để đủ chỗ cho trường hợp nở dài + chỗ đặt blank ngăn cách các token trùng nhau liên tiếp.
3 · Giải thích cho đồng nghiệp trong 3 câu: vì sao NAT cần distillation còn AR thì không?
Một câu nguồn có nhiều bản dịch hợp lệ. AR chọn tuần tự — chữ sau nhìn thấy chữ trước nên cả câu bám nhất quán một bản. NAT sinh mọi vị trí độc lập cùng lúc, không ai biết ai đã chọn gì → dễ trộn hai bản dịch; distillation cho teacher "ủi phẳng" data còn một kiểu dịch duy nhất để các vị trí hết cớ vênh nhau.
4 · Câu hỏi cũ của bạn: model 200M decoder-only from scratch, dịch nổi không? Giờ trả lời có cấu trúc.
Nổi — kiến trúc không phải rào cản (dịch = mọi bài sinh có điều kiện). Nhưng ở ngân sách nhỏ, enc-dec chuyên dụng thường lời hơn: (1) encoder hai chiều hiểu nguồn sâu hơn cùng số param; (2) decoder-only phải "gánh" nguồn trong cùng chuỗi → thuế context và thuế attention; (3) cái decoder-only mua được — prompt tự do, glossary/ngữ cảnh nhét thẳng, đa nhiệm — chỉ phát huy khi model đủ to để làm theo lệnh. Với 15–60M chuyên zh→vi: enc-dec (hoặc NAT có distill) là điểm tối ưu; 200M+ đa nhiệm có hint/ctx: decoder-only bắt đầu có cửa — đó chính là hai đầu chiến lược slm-mt và HachimiMT của bạn.
Phần III — Training thực chiến · Module 09
09 Vòng đời một lần train
Mục tiêu
- Chuẩn hóa vòng đời một run: trước khi bấm nút → theo dõi → chọn checkpoint → kết luận
- Đọc "tướng" loss curve: 4 hình dạng bệnh lý thường gặp và cách phân biệt
- Hiểu vì sao checkpoint tốt nhất của bạn là ckpt-384 chứ không phải ckpt-768 — và quy tắc chọn checkpoint tổng quát
- Thiết kế thí nghiệm 2-arm có kiểm soát + budget guard như run H100 130M của bạn
1. Trước khi bấm nút — 4 thứ phải có trên giấy
SOP bạn tự đúc kết đã gọi đủ tên: "defined objectives, data accountability, proof gates, stop criteria trước khi launch". Diễn giải thành checklist:
| Thứ phải chốt | Câu hỏi phải trả lời được | Nếu thiếu |
|---|---|---|
| Objective | Run này để chứng minh/bác bỏ điều gì? Số nào quyết định thành/bại? | Train xong không biết kết luận gì — "để đó tính sau" |
| Data accountability | Pool version nào, gates đã pass chưa, eval nào sẽ chấm? | Kết quả không quy được về nguyên nhân |
| Proof gates | Checkpoint phải qua cửa nào mới được đi tiếp? (vd eval_loss < baseline, 0 Han leak) | Tự lừa mình bằng "nhìn chung có vẻ ổn" |
| Stop criteria + budget guard | Khi nào dừng sớm? Trần chi phí? (vd hard guard $13/arm của bạn) | Run zombie đốt GPU; sunk cost giữ run xấu sống |
Thêm hai dòng "rẻ mà lãi": đóng băng config vào file version-hóa (tên run chứa thông tin: model, data version, effective batch, lr, seed) và chạy smoke 100 bước trước run dài — bắt lỗi OOM/tokenizer/dataloader với giá 5 phút.
2. Đọc "tướng" loss curve
Nhớ ghép với đường validation (hình ở Module 01): tướng train đẹp + val tách sớm = overfit / data train có vấn đề; train đẹp + val đẹp + eval hành vi tệ = bài của Module 13 (loss và chất lượng không phải một thứ).
3. Checkpoint: lưu thế nào, chọn thế nào
Case thật của bạn — style_gold_v5, chấm trên 9-chapter gate:
Quy tắc thực dụng:
- Lưu: định kỳ theo bước (không chỉ theo epoch) + giữ "best so far" theo metric quyết định (không phải theo train loss). Fine-tune ngắn thì lưu dày (như dãy 128/384/768 của bạn).
- Chọn: chấm mọi ứng viên trên cùng một eval set niêm phong; nếu 2 ứng viên trong khoảng nhiễu (Module 02 câu 4) → ưu tiên checkpoint sớm hơn (ít overfit hơn, thường generalize tốt hơn trên phân phối lạ).
- Eval đủ dày: eval quá thưa có thể trượt mất đỉnh giữa (nếu bạn chỉ chấm 128 và 768, bạn đã không bao giờ biết 384 tồn tại).
4. Thí nghiệm có kiểm soát: mẫu "2-arm + budget guard" của bạn
Run 130M capacity probe bạn thiết kế tháng 7 là mẫu đáng đóng khung, vì nó gói đủ 4 nguyên tắc:
- Đổi đúng MỘT biến: hai arm (A9-scale 129.35M vs stock-scale 134.68M) — cùng data, cùng lịch train, cùng eval; khác duy nhất cấu hình cần so.
- Chốt bảng kết quả trước khi chạy: "4-epoch table: chrF2 / BLEU / names / hygiene per arm, so với A9-41M e3, matched stock e3, Marian" — biết trước sẽ đọc gì thì không thể bẻ cong kết luận theo kết quả.
- Budget guard cứng: ~$19–22 dự toán, hard stop $13/arm — thí nghiệm là mua thông tin, đặt giá trần cho thông tin đó.
- Có baseline neo: so với các mốc đã biết (41M e3: chrF 63.065; Marian: 64.46) — không có neo thì mọi con số mới đều "trông cũng được".
Và nguyên tắc thứ năm nằm ở việc bạn đã làm trước đó: probe 90M 10k bước trước khi cam kết tiền lớn — với kết luận kỷ luật đáng nể: "90M vượt scratch local nhưng chưa vượt Hirashiba → chưa scale 150M". Không scale khi chưa thắng baseline ở cỡ hiện tại.
5. Reproducibility — run không ghi chép là run không tồn tại
Đi kèm MỖI checkpoint phải truy ra được:
config đầy đủ (kiến trúc + optimizer + lr schedule + effective batch + seed)
data pool version + gates report tokenizer version (hợp đồng! — Module 04)
git commit của code train log loss/eval theo bước
→ tên run nên tự chứa phần cốt lõi: scratch_probe_90m_ffnup_lr005_bs32k_s1
Lý do không phải "gọn gàng cho đẹp": ba tuần sau, khi model hành xử lạ, câu hỏi luôn là "nó được train từ gì, bằng gì?" — và bạn đã trả lời được trong 30 giây thay vì một buổi khảo cổ. Đây cũng là điều kiện để so hai run cách nhau một tháng mà không tự lừa.
Case study — nhịp làm việc smallD4 của bạn
Memo 24/07: "train smallD4 (~1h), kế hoạch: eval smallD3 trên 3 evaluation set TRƯỚC, rồi so". Nhịp chuẩn: đo baseline trước khi có kết quả mới — nếu để sau, kết quả mới sẽ "mách nước" cho cách bạn đo baseline (chọn set dễ/khó vô thức). Cùng tinh thần "chốt bảng kết quả trước khi chạy" ở mục 4, áp cho cả run 1 giờ chứ không riêng run $22 — kỷ luật không phụ thuộc giá tiền của run.
Bẫy thường gặp
- Chọn checkpoint theo train loss — train loss đơn điệu giảm gần như luôn luôn, nó sẽ luôn chỉ vào checkpoint cuối; hình bar ở mục 3 cho thấy đó có thể là lựa chọn tệ nhất.
- Đổi nhiều biến một lúc ("tiện tay tăng lr + thêm data + đổi warmup") → kết quả tốt/xấu không quy được cho ai; mọi run sau mất neo.
- Không có stop criteria → hoặc dừng non tay theo cảm xúc, hoặc nuôi run xấu vì tiếc tiền đã đốt (sunk cost).
- So kết quả giữa hai eval set khác nhau — "run A đạt 76 trên set cũ, run B đạt 77 trên set mới" là câu vô nghĩa; eval set là một phần của thước đo.
- Xóa checkpoint giữa quá sớm. Đỉnh hay nằm giữa; giữ ít nhất dãy thưa (vd mỗi 20% tiến trình) tới khi chấm xong.
Tự kiểm tra
1 · Loss khỏe mạnh, val bám sát train, nhưng BLEU trên dev tệ hơn baseline rõ rệt. Ba giả thuyết đầu tiên?
(1) Decode config lệch — beam/length penalty/max length khác baseline (lỗi rẻ, kiểm trước — đúng SOP "giả thuyết đơn giản trước"); (2) tokenizer/normalize lệch pha giữa train và eval script (Module 04); (3) loss-metric mismatch thật: model giỏi đoán phân phối nhưng decode ra hành vi kém (vd quá an toàn) — lúc này mới bàn chuyện data/kiến trúc. Loss đẹp chỉ chứng nhận "học có diễn ra", không chứng nhận "dịch tốt".
2 · Bạn muốn biết "tăng FFN 2048 → 3072 có đáng không" cho model 41M. Thiết kế thí nghiệm tối thiểu?
2 arm cùng data version, cùng lịch lr, cùng effective batch, cùng eval niêm phong; chỉ khác FFN (lưu ý: arm to hơn ~10% param — cân nhắc ghi rõ so sánh "per-param" hay "per-config"). Chốt trước bảng kết quả + ngưỡng kết luận (vd "đáng nếu chrF +0.5 trên ≥2/3 bucket") + budget guard. Nếu máy chỉ có một, chạy tuần tự nhưng cùng seed data order.
3 · Vì sao ckpt-768 tệ hơn ckpt-384 dù "được học nhiều hơn"?
Fine-tune trên set nhỏ (style_gold): sau điểm ngọt, model bắt đầu khớp quá đà vào đặc thù của chính set đó (overfit/drift) — thuộc câu chữ thay vì thuộc phong cách, và quên bớt nền tảng cũ (catastrophic forgetting nhẹ — Module 10). "Học nhiều hơn" chỉ tốt khi thứ được học thêm là quy luật; từ 384 → 768 thứ học thêm chủ yếu là nhiễu.
4 · Viết stop criteria cho một run fine-tune 6 giờ trên 5070Ti.
Ví dụ đạt chuẩn: "Dừng NGAY nếu: loss NaN/inf, hoặc val loss tăng 3 lần eval liên tiếp, hoặc smoke translate 20 câu ra Han leak > 0. Dừng SỚM nếu sau 2h chưa vượt baseline trên dev-mini. Hết 6h: dừng bất kể — chấm mọi checkpoint đã lưu, chọn theo gate. Không gia hạn quá 6h trong mọi trường hợp (quyết định scale là một run mới, có kế hoạch mới)."
Phần III — Training thực chiến · Module 10
10 Fine-tuning, LoRA & transfer
Mục tiêu
- Xếp đúng chỗ các kỹ thuật bạn đã dùng: full fine-tune (h5a0), freeze-partial (
ffn_up_only), LoRA 64-step, style tune (style_gold_v5), hint-injection (namefix) - Hiểu LoRA bằng toán ở mức đếm được tham số — và vì sao nó rẻ VRAM đến thế
- Trị được catastrophic forgetting: replay, anchor, lr nhỏ — và hiểu vì sao recipe DPO-lite của bạn có "SFT anchor 0.4"
- Phân tích 3 khoảng trống đo được của namefix (0.25 / 0.20 / 0.97) thành 3 bài toán train khác nhau
1. Phổ liên tục của "train tiếp"
2. Ba mức can thiệp: full FT · freeze-partial · LoRA
| Mức | Học gì | VRAM optimizer | Hợp với | Bạn đã dùng ở |
|---|---|---|---|---|
| Full fine-tune | Toàn bộ θ | Đủ 16 byte/param (Module 02) | Đổi hành vi sâu, có data khá | h5a0_57m, namefix |
| Freeze-partial | Một nhóm chọn lọc | Chỉ nhóm mở | Giả thuyết rõ "thay đổi nằm ở đâu" | ffn_up_only + lr0.005 |
| LoRA | Adapter gắn cạnh W | Chỉ adapter (vài % param) | Style/skill nhẹ, GPU nhỏ, cần nhiều biến thể | LoRA 64-step trên 5070Ti |
LoRA trong một hình
3. Catastrophic forgetting — và túi thuốc giải
Fine-tune trên data hẹp là kéo model về một góc không gian; kéo quá tay thì mất nền cũ ("quên thảm khốc"): dịch giọng mới rất hay, nhưng vỡ ở novel khác / câu dài / entity. Bốn thuốc, thường phối hợp:
- Ít bước + lr nhỏ — liều tối thiểu có tác dụng (bạn đã thấy: 384 bước đẹp, 768 bước hỏng — Module 09).
- Replay: trộn 5–20% data nền cũ vào batch fine-tune — nhắc bài liên tục.
- Anchor loss: cộng thêm một phần loss giữ model gần bản gốc — chính là "SFT anchor 0.4" trong recipe DPO-lite của bạn: 40% lực kéo dành cho "đừng rời xa hành vi SFT" trong khi phần preference kéo về hướng mới (Module 12).
- Regression gate: golden set niêm phong chấm TRƯỚC và SAU mỗi fine-tune (golden 29/30, 28/30 của bạn) — fine-tune là phẫu thuật; golden test là kiểm tra "không cắt nhầm dây thần kinh". Fine-tune nào không kèm regression gate là phẫu thuật không theo dõi hậu phẫu.
Case study — namefix: 3 con số, 3 bài toán khác nhau
Baseline eval hint-injection của bạn đo được 3 khoảng trống. Tách chúng ra là thấy mỗi con số cần một thuốc riêng — gộp chung "model kém" là mất manh mối:
| Đo được | Bản chất lỗi | Bài toán train tương ứng |
|---|---|---|
| Byte-fallback accuracy 0.25 (珉/鹃…) | Lỗi biểu diễn + exposure (Module 04): chữ hiếm không có nghĩa riêng để học | Không train "cho nhớ" được — phải đổi cơ chế: hint/glossary để model chép, hoặc pointer head (Module 08) |
| Override hint không được theo 0.20 | Model coi hint là gợi ý, prior trong tham số mạnh hơn | Data fine-tune phải chứa nhiều ca hint MÂU THUẪN với prior (hint bảo "Tuấn Dao" trong khi model quen dịch khác) — chỉ khi vâng lời hint mới giảm được loss, model mới học "hint là lệnh" |
| Echo-rate 0.97, hint bị bóp méo khi chép | Cơ chế copy qua attention yếu — chép chuỗi lạ nhiều token dễ rơi ký tự | Augment các chuỗi hiếm-dài trong hint + cân nhắc copy head; đo bằng bài "chép nguyên văn" riêng |
Khuôn chung đáng nhớ: trước khi fine-tune, tách "khoảng trống" thành từng cơ chế lỗi — có cái fine-tune chữa được (override), có cái phải đổi kiến trúc/cơ chế (byte-fallback), có cái cần data thiết kế riêng (echo). Đây cũng chính là cách bạn thiết kế control tokens <ctx> <glossary> từ đầu trong spec tokenizer — điều kiện hạ tầng cho cả hướng đi.
4. Chọn kỹ thuật nào — bảng quyết định
| Muốn | Kỹ thuật hợp lý | Liều lượng |
|---|---|---|
| Chỉnh giọng văn / word choice | LoRA r 4–8 hoặc FT lr rất nhỏ, vài trăm bước | Data vài trăm–vài nghìn cặp chất lượng cao; gate chương + golden |
| Dạy kỹ năng mới (theo hint, thêm control token) | Full FT (hoặc LoRA r 16–64 kèm mở embedding nếu có token mới) | Data thiết kế có ca mâu thuẫn; đủ exposure cho token mới (Module 04 bẫy 5) |
| Thêm cả một domain (thể loại mới) | Continued pretrain rồi mới SFT | Data lớn, lr trung bình, replay nền cũ |
| Sửa một nhúm lỗi cụ thể | Targeted data + FT ngắn | Kèm regression gate dày — rủi ro "sửa 1 hỏng 3" cao nhất ở đây |
| Nhiều biến thể thử nghiệm song song | LoRA (mỗi biến thể một adapter) | Nền giữ nguyên; so adapter với nhau trên cùng eval |
Bẫy thường gặp
- Fine-tune với lr của pretrain → phá nền ngay trăm bước đầu. Điểm xuất phát: nhỏ hơn 5–10× lr pretrain, rồi dò.
- Đo fine-tune chỉ bằng "tốt lên ở việc mới" — phải đo cả "không tệ đi ở việc cũ" (golden/regression). Một chiều là mù một mắt.
- LoRA cho tác vụ cần token mới nhưng quên mở embedding — token mới nằm ngoài adapter, embedding của nó vẫn là init ngẫu nhiên.
- Merge LoRA rồi quantize mà không chấm lại — hai phép xấp xỉ chồng nhau (merge + int8) có thể cộng hưởng; golden phải chạy trên artifact cuối (Module 14).
- Data fine-tune một giọng, kỳ vọng đa giọng. Model học phân phối bạn đưa (Module 03) — muốn giữ biến hóa thì data tune phải còn biến hóa.
Tự kiểm tra
1 · LoRA r=8 lên 4 ma trận attention (Q,K,V,O — mỗi cái 512×512) của cả 6 layer encoder + 6 decoder. Bao nhiêu tham số train?
Mỗi ma trận: 2×512×8 = 8 192. Mỗi layer 4 ma trận = 32 768. 12 layer ≈ 393k tham số — chưa tới 0.7% của model 57M; optimizer state chỉ ~6MB thay vì ~460MB. Đó là lý do 5070Ti làm được điều mà full FT phải dè xẻn.
2 · Thiết kế 200 dòng data dạy "override hint là LỆNH" — tiêu chí quan trọng nhất?
Tỉ lệ áp đảo các ca hint mâu thuẫn với prior của model (đo prior trước bằng cách dịch không hint). Nếu hint toàn trùng với thứ model định dịch sẵn, loss không tạo áp lực "phải đọc hint" — model học được thói "lờ hint vẫn đúng", chính là hành vi 0.20 bạn đo được. Kèm 10–20% ca không hint (giữ hành vi thường) và vài ca hint rác (dạy giới hạn tin hint — tùy chính sách).
3 · Vì sao "SFT anchor 0.4" trong DPO-lite lại cần thiết, nhìn từ module này?
Preference training kéo model theo hiệu số chosen−rejected trên data rất hẹp (vài trăm cặp) — đúng điều kiện lý tưởng của catastrophic forgetting. Anchor 0.4 là "dây bảo hiểm": 40% lực giữ model gần hành vi SFT đã kiểm chứng, để phần preference chỉ uốn chứ không bứng. Cùng vai trò với replay/regression gate — khác cách cài đặt.
4 · Base 57M dịch tốt nhưng thiếu vốn từ đô thị hiện đại (đang chuyên tiên hiệp). Lộ trình?
Đây là thêm domain, không phải chỉnh giọng: (1) gom corpus đô thị, qua đủ gates (Module 03); (2) continued pretrain / FT diện rộng với replay 10–20% tiên hiệp; (3) kiểm tokenizer coverage cho từ vựng mới (Module 04) — thiếu thì đây là lý do hiếm hoi phải cân nhắc train lại từ đầu; (4) golden tiên hiệp + eval đô thị mới, chấm cả hai chiều; (5) SFT/style nhẹ sau cùng nếu cần giọng riêng.
Phần III — Training thực chiến · Module 11
11 Distillation & dữ liệu tổng hợp
Mục tiêu
- Phân biệt hai chế độ distillation: logit KD (học phân phối mềm) và sequence-level KD (học bản dịch của teacher) — và vì sao bạn đang dùng đúng chế độ thứ hai
- Chọn teacher có phương pháp: trần chất lượng, license, chi phí, độ phủ domain — đặt quyết định DeepSeek-vs-Baidu của bạn lên khung
- Nắm hai "nhà máy data tổng hợp": pseudo-parallel (seq-KD) và back-translation — khác nhau ở đâu, dùng khi nào
- Biết khi nào student có thể "vượt" teacher — và khi nào đó là ảo giác đo lường
1. Hai chế độ distillation
2. Chọn teacher — bốn tiêu chí, có thứ tự
| # | Tiêu chí | Câu hỏi | Quyết định thật của bạn |
|---|---|---|---|
| 1 | Trần chất lượng | Teacher có hơn hẳn student hiện tại trên domain đích không? (đọc tay 50–100 câu) | Loại tienhiep_baidu: "không nâng trần đáng kể so với Moxhi → không đáng làm teacher" — đúng nguyên lý Module 03: student ≤ teacher − hao hụt |
| 2 | License | Output teacher có được phép dùng để train model sẽ phát hành không? | Chọn DeepSeek-v4-flash làm "license-clean teacher" — tiêu chí này đi trước cả chất lượng khi model là public (HF) |
| 3 | Chi phí & throughput | $/1M token × số token pool = bao nhiêu? Chạy hết pool mất mấy đêm? | Batch teacher chạy nền, guard số dư — cùng triết lý budget guard Module 09 |
| 4 | Độ phủ domain | Teacher có đuối ở đúng chỗ mình cần (xưng hô cổ trang, thuật ngữ tu luyện)? | Đây là chỗ eval by-bucket lộ ra teacher "giỏi đều nhưng sai xưng hô" — lỗi sẽ di truyền nguyên vẹn sang student |
Multi-teacher: trộn nhiều teacher tăng độ phủ và giảm "di truyền tật một nhà" — nhưng phải tag provider từng row (gate provider-mix Module 03) và nhớ hệ quả đo lường: eval bằng reference của teacher X sẽ nịnh mọi model học từ X (Module 13 — quy tắc same-teacher bạn đã tự đặt).
3. Hai nhà máy data tổng hợp
Cùng tạo cặp (zh, vi) nhưng ngược chiều nhau về "bên nào là đồ thật":
| Pseudo-parallel / seq-KD (cách chính của bạn) | Back-translation | |
|---|---|---|
| Nguyên liệu | Monolingual zh thật (novel gốc — vô hạn, rẻ) | Monolingual vi thật (bản dịch người, văn Việt) |
| Sinh ra | Target vi tổng hợp (teacher dịch xuôi) | Source zh tổng hợp (model dịch ngược vi→zh) |
| Dạy student giỏi | Adequacy — đọc hiểu nguồn thật đủ kiểu; và nhất quán hóa cho NAT | Fluency — target là văn Việt thật, giọng người |
| Điểm yếu | Giọng vi = giọng teacher (nhạt hóa nếu teacher khô) | Nguồn zh giả có thể lệch phân phối nguồn thật |
| Trong hệ của bạn | DeepSeek/Marian dịch pool zh → train slm-mt, CTC | Reverse NAT-CTC vi→zh bạn vừa freeze — 2.8M row + 5M inventory đang giữ đúng vai "data asset" chờ chiều này |
Phối hợp kinh điển: seq-KD làm xương sống khối lượng, back-translation nêm thêm để kéo giọng văn về "văn người" — trộn theo tỉ lệ và tag nguồn gốc để gate mix soi được.
4. Student có vượt được teacher không?
Về "kiến thức thô": không — trần vẫn là teacher (Module 03). Nhưng có ba cửa hợp lệ khiến student đo được tốt hơn:
- Chuyên hóa: teacher đa năng, student dồn toàn bộ 41M tham số cho đúng một domain → trong domain đó student "đặc" hơn, nhất quán hơn (ít dao động giọng/term hơn teacher vốn sáng tạo thất thường).
- Selective distillation: không nuốt cả output teacher — lọc giữ phần tốt (rules, QE score, review) rồi mới train. Student học từ "teacher những ngày đẹp trời" → có thể vượt teacher-trung-bình. Các pool gold bạn xây (style_gold, gold4) chính là kỹ thuật này.
- Ensemble tri thức: multi-teacher + data người → student tổng hợp thứ không teacher đơn lẻ nào có.
Còn lại là ảo giác đo: student "thắng" trên eval lấy reference từ chính phân phối data nó học — bài của Module 13.
Case study — dàn distillation của bạn, nhìn từ trên cao
Ghép các mảnh memo lại thành một bức tranh: DeepSeek-v4-flash = teacher chính license-clean cho các pool SFT; Marian general-domain = teacher pseudo-parallel biến kho novel zh thành cặp train cho slm-mt; tienhiep_baidu = bị loại vì không nâng trần; các pool gold = selective distillation (chưng cất "ngày đẹp trời"); reverse CTC vi→zh = tài sản chờ cho back-translation. Đó là một hệ sinh thái distillation đúng nghĩa — mỗi teacher một vai, mỗi pool một chuẩn, provenance đi theo từng dòng. Module này chỉ làm mỗi việc: gắn tên lý thuyết cho từng vai để lần sau bạn quyết nhanh hơn.
Bẫy thường gặp
- Distill từ teacher ngang tầm student — công cốc, thậm chí kéo lùi (bạn đã né đúng vụ Baidu).
- Data tổng hợp được "miễn" gates. Không — nó là data như mọi data: dedup, length ratio, Han leak, balance (Module 03). Teacher cũng biết sinh rác, nhất là khi gặp nguồn rác.
- Không tag provider → không audit được teacher nào gây bug, không cân được mix, không né được bias đo lường.
- Student nuốt cả tật teacher. Teacher hay bịa khi gặp câu khó → student bịa y hệt, tự tin hơn (nó thấy "bịa" là đáp án chuẩn trong train). Selective filter các ca teacher đuối trước khi train.
- Domain shift của monolingual pool. Pool zh toàn huyền huyễn mà use case có đô thị/Lovecraft → student hụt đúng chỗ pool hụt; cân pool theo phân phối sử dụng thật, không theo "có gì dùng nấy".
Tự kiểm tra
1 · Vì sao NAT/CTC gần như bắt buộc train trên data đã seq-KD, còn AR thì tùy chọn? (ôn Module 08)
NAT sinh các vị trí độc lập — data đa mode (một nguồn nhiều bản dịch) làm các vị trí "xé nhau" trộn hai bản dịch. Seq-KD cho teacher ủi phẳng data về một kiểu dịch nhất quán → gỡ đúng điểm chết cấu trúc của NAT. AR có chuỗi điều kiện tuần tự nên tự giữ nhất quán được, KD với AR chỉ là "thuốc bổ" (nhất quán hóa, khuếch đại data) chứ không phải "thuốc sống".
2 · Bạn có 1M câu vi bản dịch người (giọng rất hay) và 10M câu zh novel thô. Dùng mỗi kho vào nhà máy nào?
10M zh → seq-KD: teacher dịch xuôi thành pseudo-parallel (adequacy, khối lượng). 1M vi → back-translation: dịch ngược vi→zh (chính là chỗ dùng reverse CTC bạn đang giữ) để có cặp mà target là văn người thật (fluency). Trộn hai nguồn có tag + gate mix; giọng văn cuối nghiêng về phía nào là do tỉ lệ trộn bạn chọn.
3 · Thiết kế "selective distillation" cho pool DeepSeek 500k cặp mà không có sức người đọc hết?
Tầng lọc rẻ → đắt: (1) rules tự động: Han leak, length ratio, entity bị đổi, số/tên không khớp — loại thẳng; (2) QE proxy: chấm chéo bằng model khác (round-trip hoặc chrF với bản Marian để flag bất thường — chỉ flag, không làm chân lý); (3) đọc tay sample phân tầng theo bucket rủi ro (câu dài, nhiều tên, thoại) để chỉnh ngưỡng hai tầng trên; (4) phần "chắc tốt" vào train, phần flag vào quarantine chờ review — không xóa (Module 03).
4 · Student 41M distill từ DeepSeek chấm BLEU cao hơn DeepSeek trên eval của bạn. Ba cách giải thích, xếp theo xác suất?
(1) Bias đo lường — reference của eval sinh từ / cùng phân phối với data student học → thước nịnh student (kiểm: eval độc lập, blind review); (2) chuyên hóa thật — trong domain hẹp student nhất quán hơn teacher đa năng (tin được nếu blind review xác nhận); (3) student "giỏi vượt trần" — xác suất thấp nhất, chỉ tin khi (1)(2) đã loại trừ bằng eval người, đa nguồn. Thứ tự nghi ngờ này chính là kỷ luật Module 13.
Phần III — Training thực chiến · Module 12
12 Preference & DPO
Mục tiêu
- Hiểu preference training bổ sung gì mà SFT không làm được — và vì sao nó chỉ nên là bước cuối
- Đọc được DPO loss ở mức trực giác từng mảnh — hiểu 0.693 = ln 2 là mốc policy trùng reference, và khi nào việc kẹt ở đó mới thật sự báo động
- Giải phẫu trọn arc của bạn: v1 fail (margin 0) → defer → v22 marginal (margin 0.12 mà chất lượng đứng im) → DPO-lite thắng (7-0, severity −0.19)
- Thuộc recipe đã kiểm chứng in-house: β 0.07 · lr 2e-6 · SFT anchor 0.4 · ≤800 bước · data hẹp một trục lỗi
1. SFT dạy "thế nào là tốt" — không dạy "cái nào tốt hơn"
Sau SFT, model của bạn dịch mượt. Lỗi còn lại toàn loại tinh vi: hai bản dịch đều "hợp lệ" nhưng một bản lệch nghĩa nhẹ, sai xưng hô, hơi văn dịch. SFT thêm không chữa được — vì cả hai kiểu đều xuất hiện trong data với tư cách "đáp án đúng". Thứ còn thiếu là tín hiệu so sánh: cho model xem từng cặp (chosen, rejected) trên cùng một nguồn và dạy nó nghiêng về bên chosen.
Bức tranh đầy đủ là RLHF (train reward model + PPO/GRPO) — bạn đã đánh giá đúng: overkill cho 57M, không có reward model, hạ tầng đắt. DPO là đường tắt: bỏ reward model, rút tín hiệu trực tiếp từ cặp preference.
2. DPO — đọc công thức từng mảnh
loss = −log σ( β · [ margin ] )
margin = (log π(chosen) − log π_ref(chosen)) − (log π(rejected) − log π_ref(rejected))
└────────── "thiên vị chosen" ─────────┘ └───────── "thiên vị rejected" ────────┘
π = model đang train π_ref = bản SFT đóng băng làm mốc
β = hệ số KL trong suy diễn DPO; đồng thời nhân logit loss σ = sigmoid
Diễn nghĩa: mỗi log-ratio đo "so với bản gốc, model hiện tại đã nghiêng thêm về câu này bao nhiêu". Margin = nghiêng-về-chosen trừ nghiêng-về-rejected. DPO đẩy margin dương — thích chosen hơn rejected một cách tương đối, chứ không ép xác suất chosen tuyệt đối lên 1.
β có hai vai — đừng nhớ bằng một khẩu quyết
Trong bài toán RL/KL mà DPO suy ra từ đó, β lớn hơn = phạt rời reference mạnh hơn, nên nghiệm lý thuyết nằm gần reference hơn. Nhưng trong loss cài đặt ở trên, β còn nhân trực tiếp margin, làm đổi độ dốc và động học khi giữ nguyên learning rate/số bước. Vì vậy không thể nhìn riêng “β nhỏ” rồi kết luận “neo chặt hơn” hoặc “an toàn hơn”; phải sweep β cùng lr, số bước và anchor, rồi đo drift hành vi.
Từ hai log-ratio tới margin, sigmoid và DPO loss
Kéo từng vế; bấm reset để về đúng mốc ln 2Chú ý: σ ở đây là đại lượng trong objective cho cặp, không phải xác suất tuyệt đối model sẽ sinh chosen nguyên văn.
Case study — arc DPO của bạn, ba hồi, ba bài học
| Hồi | Số liệu | Chẩn đoán bằng lý thuyết vừa học |
|---|---|---|
| v1 — fail | loss ~0.693, margin ~0, hành vi không đổi | Quan sát chắc chắn: policy không tạo được dịch chuyển preference tương đối. Review data cho thấy cặp quá rộng/mơ hồ nên “too weak, insufficiently narrow data” là giả thuyết phù hợp; nhưng với run mới vẫn phải loại trừ masking, log-prob reduction, reference sync và update quá nhỏ trước khi đổ hết cho data. |
| Defer — quyết định đúng nhất arc | — | "Dùng chosen làm SFT target, rejected làm hard-negative gate, hoãn DPO" — đúng thứ tự: SFT ăn hết phần dễ trước (dạy hành vi), DPO chỉ dành cho phần SFT bó tay (ưu tiên giữa các hành vi model đã biết). |
| v22 — marginal | 228 cặp lọc từ 966, margin +0.12, metric đứng im | Bài học tinh vi nhất: margin dương ≠ chất lượng tăng. Margin chỉ nói "model tách được 2 vế của các cặp TRAIN" — nếu trục khác biệt của cặp không trùng trục lỗi ngoài đời, model học được một thứ… vô dụng. Margin là điều kiện cần, eval hành vi mới là điều kiện đủ. |
| DPO-lite — thắng | 首当其冲 7-0, severity −0.19 | Recipe đã thắng như một gói: data hẹp một trục lỗi + β 0.07 + lr 2e-6 + SFT anchor 0.4 + ≤800 bước. Không gán công lao riêng cho “β nhỏ”: liều cập nhật thấp, anchor và dừng sớm cùng kiểm soát drift; bằng chứng cuối vẫn là hardcase 7-0 và severity giảm. |
3. Data preference — gates riêng của nó
- Số lượng: gate 100–300 cặp sạch bạn đặt ra là hợp lý cho một trục lỗi; dưới đó nhiễu thắng tín hiệu (Module 03 câu 3 đã chứng minh vì sao từng cặp phải chắc tay).
- Hẹp chủ đề: mỗi đợt DPO một trục lỗi (vd riêng xưng hô, riêng văn dịch cứng). 300 cặp trải 10 loại lỗi = mỗi loại 30 cặp — không đủ cho loại nào; 300 cặp một loại = tín hiệu đậm đặc. Đây chính là khác biệt v1 vs DPO-lite.
- Cùng nguồn, khác đúng chỗ cần dạy: cặp lý tưởng chỉ khác nhau ở trục muốn dạy; khác nhau thêm ở chỗ khác là dạy nhầm thêm thứ khác.
- Đa người duyệt hoặc duyệt hai lượt: preference là "gu" — một reviewer gu lệch thì model học đúng cái gu lệch đó, rất khó phát hiện về sau.
4. Ghi chú cho MT nói riêng
Nghiên cứu bạn đã sưu tầm (CPO/ALMA-R, TIM, DPO-for-NMT/MBR) hội tụ về hai ý: (1) CPO ≈ DPO + thành phần giữ SFT — tức giới học thuật cũng đi đến "SFT anchor" như recipe của bạn; (2) cảnh báo WMT 2024: preference alignment có thể làm metric tự động dao động (BLEU/chrF lên xuống không phản ánh chất lượng) → chọn checkpoint sau DPO bằng hardcase + review người, không bằng BLEU — bạn đã ghi đúng điều này thành SOP; Module 13 cho nó nền móng.
Bẫy thường gặp
- Đọc 0.693 như accuracy 50%. Đây trước hết là mốc policy = reference trong log-ratio. Một dao động rất nhỏ quanh đó chưa đủ kết luận; xem đồng thời margin, gradient/update, held-out preference và hành vi sinh.
- Chạy DPO khi SFT chưa hết đà. Thứ SFT còn dạy được thì SFT dạy rẻ và ổn định hơn nhiều; DPO là dao mổ, không phải búa.
- Đổ mọi drift cho riêng β. Trong lý thuyết KL, β lớn neo reference mạnh hơn; trong code nó cũng đổi scale gradient. Drift thực tế là kết quả chung của β, lr, số bước, optimizer, data và anchor — hãy sweep có kiểm soát thay vì dùng khẩu quyết.
- Margin đẹp trên train → công bố thắng lợi. V22 của bạn là lời nhắc vĩnh viễn: margin 0.12 mà chất lượng đứng im. Luôn khép vòng bằng eval hành vi + review.
- Chấm checkpoint DPO bằng BLEU — đúng chỗ metric tự động kém tin nhất (WMT warning). Dùng hardcase set + blind review.
Tự kiểm tra
1 · Run DPO của đồng nghiệp: loss 0.693 → 0.42, margin → +0.35, nhưng golden regression rớt 3 câu. Chuyện gì đang xảy ra?
DPO có tạo dịch chuyển preference (margin dương) nhưng đang trả giá bằng hành vi nền: tổng liều update quá lớn, thiếu anchor, hoặc data mang bias phụ. Golden rớt là tín hiệu dừng. Giảm lr/số bước, thêm SFT anchor, soi lại cặp; còn β phải sweep cùng các núm đó vì vừa mang nghĩa KL vừa đổi scale loss. Margin đẹp + golden rớt vẫn là thất bại.
2 · Bạn có 40 cặp preference cực sạch về lỗi xưng hô. Chạy DPO luôn hay chờ đủ 100–300?
40 cặp sạch một trục nằm dưới gate của chính bạn — hai đường tốt hơn: (a) dùng chosen làm SFT mini + rejected làm hard-negative eval, tiếp tục gom cặp; (b) nếu vẫn thử DPO, dùng lr thấp, rất ít bước, có anchor và sweep vài giá trị β có đối chứng. Coi đó là probe; kết luận bằng held-out hardcase review, không bằng margin train.
3 · Vì sao DPO cần π_ref, không so thẳng log π(chosen) > log π(rejected)?
Vai trò chính của π_ref là làm mốc neo hành vi và biến log-ratio thành implicit reward: policy được thưởng khi tăng chosen so với chính reference nhiều hơn rejected. Phép trừ loại bớt một phần “độ dễ nền”, nhưng vanilla DPO không tự giải quyết hoàn toàn length bias; vẫn phải thống nhất cách sum/mean log-prob, masking và có thể dùng biến thể length-normalized khi phù hợp.
4 · Diễn giải β 0.07 trong recipe DPO-lite thế nào mà không học sai?
“0.07 là một giá trị đã chạy tốt trong cả recipe, không phải núm ‘neo’ có thể đọc một mình.” Theo suy diễn KL, β lớn hơn tương ứng neo reference mạnh hơn; trong loss, β lớn hơn cũng khuếch đại logit/gradient. Vì thế phải giữ cố định hoặc sweep có đối chứng cùng lr, số bước và anchor, rồi chọn bằng preference held-out + regression hành vi.
Phần IV — Đánh giá & triển khai · Module 13
13 Đánh giá model dịch cho đúng
Mục tiêu
- Tính tay được BLEU trên một câu — để từ đó biết chính xác nó đo gì và mù gì
- Hiểu vì sao chrF hợp cặp zh-vi hơn BLEU, và cả hai vì sao chỉ được làm "phụ trợ" trong SOP của bạn
- Nhìn thấy cơ chế của same-teacher bias — quy tắc C5 bạn tự đặt ("reference phải từ teacher khác") hóa ra là nguyên lý đo lường nghiêm túc
- Hệ thống hóa tháp eval bạn đang vận hành: hygiene → regression → golden → blind review → A/B gate
1. Eval là hệ điều hành của project
Mọi quyết định bạn đưa ra — chọn checkpoint (Module 09), có scale không (Module 15), merge PR webMT không — đều đi qua một phép đo. Thước lệch một li, mọi quyết định lệch một dặm; vì thế eval đáng được đầu tư như code sản phẩm: có version, có niêm phong, có review queue — đúng những gì bạn đã làm với eval_locked 1 043 rows. Trước khi vào kỹ thuật, đóng khung lại trật tự bạn đã tự đúc kết (và là trật tự chuẩn của nghề):
Loss → chỉ để debug training. BLEU/chrF → phụ trợ: regression, sanity, so sánh thô. Quyết định chất lượng → blind review theo nguồn, entity preservation, severity — người chấm, có cấu trúc.
2. BLEU — tính tay một lần để biết nó mù chỗ nào
Ứng viên : Hắn đi rồi (3 token)
Reference: Hắn đã đi rồi (4 token)
1-gram: Hắn ✓ đi ✓ rồi ✓ → precision 3/3
2-gram: "Hắn đi" ✗ "đi rồi" ✓ → precision 1/2
Brevity penalty (phạt dịch ngắn): c=3 < r=4 → BP = e^(1−4/3) ≈ 0.72
BLEU-2 = 0.72 × (3/3 × 1/2)^(1/2) ≈ 0.72 × 0.707 ≈ 0.51
Bài dịch đúng nghĩa hoàn toàn mà chỉ được 0.51 vì thiếu chữ "đã". Ngược lại một bản chép đủ n-gram nhưng đảo logic có thể điểm cao hơn. BLEU đo trùng khớp bề mặt với MỘT reference — không đo nghĩa. Ba điểm mù kinh điển: (1) đồng nghĩa/diễn đạt khác bị phạt oan; (2) lỗi nghĩa nhỏ mà trùng chữ nhiều thì thoát; (3) nhạy cắt từ — tiếng Việt tách âm tiết kiểu khác nhau là điểm khác nhau.
chrF chấm F-score trên n-gram ký tự thay vì từ → né bài toán cắt từ (quý với zh không dấu cách + vi đa âm tiết), một phần credit cho biến thể gần đúng ("đã đi"/"đi") → ổn định hơn BLEU trên cặp zh-vi. Vì thế bạn ưu tiên chrF làm chỉ số phụ trợ chính là hợp lý. Neural metrics (COMET-style, học từ phán xét người) mạnh hơn nữa nhưng lưu ý: được train chủ yếu trên cặp ngôn ngữ giàu tài nguyên — với zh→vi văn tu tiên, hãy coi nó là một reviewer máy có gu riêng cần kiểm chứng, không phải chân lý.
3. Same-teacher bias — vòng lặp tự khen
4. Tháp eval — hệ thống bạn đang chạy, gọi đúng tên
Hai tầng trên cùng của bạn đã có công nghệ riêng đáng gọi tên:
- Blind review theo nguồn (không biết bản nào của model nào, chấm dựa trên câu gốc) với severity 0–3 và chọn winner — chuẩn WMT thu nhỏ. Tiêu chí bạn viết thành văn: fidelity trước fluency — không thưởng cho bản mượt mà sai nghĩa/sót ý/sai entity. Đây là hàng rào chống đúng loại lỗi mà BLEU tha bổng.
- Taxonomy lỗi bạn đặt cho style_gold_v5 — semantic_shift, hallucination, omission, entity_term, literal_idiom, modern_lexical, bad_vietnamese, too_free, source_noise, punct_quote — biến "cảm giác dở" thành phân phối đếm được, so được giữa hai model, và chỉ thẳng loại data cần bổ sung (Module 03/09).
- Paired comparison by-bucket (script compare_eval_runs của bạn): so cùng câu giữa hai model (khử nhiễu chọn mẫu — mạnh hơn hẳn so điểm trung bình hai lần chạy), rồi tách theo nguồn/bucket để trung bình không che thoái hóa cục bộ.
5. Một chút thống kê đủ dùng
- Golden 30 câu bắt được thoái hóa lớn (3/30 rớt là tín hiệu thật) nhưng không phân giải nổi chênh lệch nhỏ — đừng đọc "29/30 vs 28/30" như một khác biệt có nghĩa; nó là "cùng hạng".
- Chênh 0.3–0.5 chrF cần nhiều trăm–nghìn câu và/hoặc nhiều seed mới đáng tin (nhớ nhiễu seed 0.4 chrF ở Module 02). Trên 60 câu review, hãy tin phân phối severity và số winner hơn là điểm trung bình.
- So paired luôn thắng unpaired: cùng câu, hai model, đếm thắng/thua/hòa — mỗi câu tự làm đối chứng của nó.
Bẫy thường gặp
- Goodhart: tối ưu trực tiếp lên metric thì metric hỏng — chọn checkpoint/beam/length-penalty theo BLEU sẽ nghiêng dần về bản dịch ngắn, an toàn, nghèo. Sau preference training điều này nặng thêm (metric instability — Module 12).
- So điểm giữa hai eval set khác nhau (Module 09) — eval set là một phần của thước.
- Eval toàn novel quen — leakage mềm (Module 03): điểm đo "độ quen" chứ không đo "độ giỏi".
- Trung bình che bucket: +1 chrF tổng có thể là +3 ở đối thoại và −2 ở đoạn tả — hai câu chuyện sản phẩm khác hẳn nhau.
- Quên hygiene vì mải nhìn điểm. Một bản dịch BLEU 40 nhưng 2% câu lặp vô hạn là sản phẩm hỏng; hygiene (leak/empty/repeat) đứng đáy tháp vì nó phủ quyết tất cả.
- Đổi eval giữa chừng project mà không chạy song song bản cũ một thời gian — mất toàn bộ khả năng so với lịch sử.
Tự kiểm tra
1 · Ứng viên "Nàng cười nói" vs reference "Nàng mỉm cười nói". Tính BLEU-2 (có BP).
1-gram: Nàng ✓, cười ✓, nói ✓ → 3/3. 2-gram: "Nàng cười" ✗, "cười nói" ✗ (reference có "Nàng mỉm", "mỉm cười", "cười nói" — à "cười nói" ✓!) → 1/2. BP: c=3, r=4 → e^(−1/3) ≈ 0.72. BLEU-2 ≈ 0.72 × √(1 × 0.5) ≈ 0.51 — cho một bản dịch hoàn toàn chấp nhận được. Đó là lý do nó chỉ được làm phụ trợ.
2 · Model mới BLEU +2.1 so với cũ trên eval same-teacher, nhưng blind review 60 câu: 18 thắng / 20 thua / 22 hòa. Kết luận?
Không có bằng chứng model mới tốt hơn — review paired cho kết quả ~ngang nhau, và +2.1 BLEU trên thước same-teacher nhiều khả năng là "giống teacher hơn" (bias mục 3). Hành động: kiểm tra bucket có mảng nào thắng/thua hệ thống không; nếu cần một quyết định, tin review; nếu muốn cứu metric, dựng reference khác nguồn. Đây chính xác là loại tình huống mà quy tắc C5 của bạn được viết ra để xử.
3 · Thiết kế bộ eval tối thiểu cho model hoàn toàn mới (vd vp2vi NAT), trong một buổi chiều?
(1) Hygiene script: leak/empty/repeat/length — 30 phút, chạy được mãi mãi; (2) trích 200–500 câu đại diện phân phối thật (đủ bucket: thoại/tả, ngắn/dài, nhiều tên) làm dev niêm phong — có reference khác-nguồn nếu sẵn, không thì để so paired giữa các ứng viên; (3) 30 câu golden chọn tay các ca "chết người" (entity, phủ định, số đếm) làm gate pass/fail; (4) ghi version cả ba. Blind review để dành cho các quyết định lớn — không cần ngay buổi chiều đó.
4 · Vì sao "fidelity trước fluency" phải là tiêu chí CHẤM, chứ không thể trông cậy metric tự động?
Vì fluency để lại dấu vết bề mặt (n-gram mượt) mà metric đo được, còn fidelity đòi so nghĩa với nguồn — thứ BLEU/chrF không nhìn (chúng chỉ nhìn reference). Một bản bịa mượt mà "hợp giọng reference" qua mặt metric dễ dàng nhưng không qua nổi người đối chiếu câu gốc. Model càng giỏi (lỗi càng tinh vi), khoảng mù này càng rộng — nên tiêu chí của bạn sẽ càng ngày càng đúng chứ không lỗi thời.
Phần IV — Đánh giá & triển khai · Module 14
14 Inference: quantization & tăng tốc
Mục tiêu
- Nắm mental model số một của inference: decode thường nghẽn ở băng thông đọc weights, không phải ở phép nhân — từ đó hiểu vì sao quantization = tăng tốc
- Tự tính một phép int8 quantization bằng tay; phân biệt int8_float32 / int8_float16 / Q4_K_M / Q8 trong chính các export của bạn
- Hiểu bằng số của chính bạn: vì sao ép
-ngl 999chậm 5.6× so với auto-fit (-fitt) - Rút các bài học bench/deploy từ webMT thành nguyên tắc tái dùng (đo xen kẽ chống thermal drift, golden trên artifact cuối)
1. Inference nghẽn ở đâu?
Không còn gradient, không còn Adam (Module 02) — VRAM chỉ còn weights + KV cache + activations mỏng. Nhưng xuất hiện nút nghẽn mới: decode batch-1 phải đọc lại toàn bộ weights cho MỖI token sinh ra (mỗi lượt forward chạm hết các ma trận). Phép nhân thì GPU thừa sức — khiêng weights từ VRAM vào compute unit mới là việc nặng:
tok/s (trần lý thuyết, batch 1) ≈ băng thông VRAM ÷ dung lượng model phải đọc mỗi token
→ Giảm bytes (quantize) = tăng tốc GẦN NHƯ TỈ LỆ THUẬN, chưa cần kernel nào thông minh hơn
→ Batch nhiều câu = chia đều một lần đọc weights cho nhiều token → throughput server tăng vọt
(đổi bằng latency từng câu — trade-off bạn thấy rõ trong B8/B192/B576 của webMT)
2. Quantization — tính tay một lần
Ý tưởng: thay float bằng số nguyên nhỏ + một hệ số scale chung cho cả nhóm:
Nhóm weights: [−0.80, 0.31, 0.02, 0.60] (fp16/fp32)
scale = max|w| ÷ 127 = 0.80 ÷ 127 ≈ 0.0063
w_q = round(w ÷ scale) = [−127, 49, 3, 95] (int8 — 1 byte/số)
khôi phục ≈ w_q × scale = [−0.800, 0.309, 0.019, 0.599] → sai số ~0.001
Toàn bộ các format bạn dùng chỉ là biến tấu của công thức trên — khác nhau ở phạm vi nhóm chung scale và độ rộng số nguyên:
| Format | byte/param | Cấu trúc | Trong hệ của bạn |
|---|---|---|---|
| fp16 / bf16 | 2 | chưa quantize — mốc chất lượng | checkpoint train, webMT bản f16 |
| int8 (per-channel) | 1 | mỗi hàng/cột một scale | CT2 int8_float32 (Space CPU), int8_float16 (CUDA — default trong translator.py của qt2) |
| Q8 | 1 (+scales) | block int8 + scale per-block | webMT — loader f16→Q8 trực tiếp từng hàng |
| Q4_K_M | ~0.56 | block 4-bit, scale phân tầng (super-block) | GGUF DiffusionGemma / Gemma 26B trên llama.cpp |
Tên ghép kiểu int8_float32 đọc là: weights int8, phép tính/tích lũy float32 — lai để giữ chất lượng: chỗ nhạy (accumulate, layernorm, logits) vẫn float. Còn PTQ vs QAT: mọi export trên của bạn là PTQ (quantize sau train — nhanh, đủ tốt cho int8/Q8); QAT (train có mô phỏng quantize) chỉ cần khi ép xuống 4-bit mà chất lượng rơi — như dòng Unsloth QAT GGUF bạn đã gặp.
Luật sắt đi kèm: golden test chạy trên artifact CUỐI — đúng file int8/Q4 sẽ ship, không phải bản fp16 "gần giống". Bạn đã làm chuẩn ("quality parity trước khi update Space"), và vụ monkey-patch
_remove_pad_weights(bug pad-at-0 vocab trim của converter CT2 với Marian) là bằng chứng sống: công cụ convert cũng có bug theo model — chỉ golden end-to-end mới bắt được.
3. Trong VRAM hay tràn ra ngoài — câu hỏi trước mọi câu hỏi
-fitt của bạn, vẽ lại. Cùng phần cứng, cùng model, chênh 5.6× chỉ vì chiến lược đặt weights. Trước khi tối ưu bất cứ gì khác, trả lời: model (sau quantize) + KV cache có nằm trọn trong VRAM không? Không → hoặc quantize sâu hơn, hoặc chia phần có kế hoạch, không bao giờ để tràn câm.Ráp với mục 1 thành checklist tốc độ theo thứ tự: (1) vừa VRAM chưa? → (2) bytes/param giảm được nữa không (quantize)? → (3) KV cache có phình theo context không (giới hạn capacity/quantize KV)? → (4) batch được không (throughput)? → (5) rồi mới đến kernel/graph (fused, immediates, bind-group cache như webMT — ăn thêm 5–25%, không đổi bậc).
4. Bài học bench từ webMT — đo cho đúng đã, nhanh tính sau
- Thermal drift phá A/B trên laptop: đo A xong mới đo B thì B luôn thiệt (máy đã nóng, clock tụt). Fix của bạn — alternate arm order per round (đảo thứ tự A/B mỗi vòng để trung bình triệt tiêu drift) — chính là randomized experiment design; vụ "12% subgroup flip hóa ra là one-sided ramp" là học phí đã đóng, đừng đóng lại. Kèm quy trình iPhone của bạn: nghỉ ~1 phút giữa các round, chạy 2–3 round.
- Khoan tin edge case: B1 +164.74% là CPU-dispatch edge case — không ngoại suy (Module 08 đã dùng); báo cáo theo phân phối batch thật.
- Đường ống quantize cũng tốn tài nguyên: chuyển f16→Q8 trực tiếp từng hàng (không materialize Float32 toàn cục) giảm peak memory 51–66 MiB và thời gian load 161.6→64.4ms — trên mobile, load path là một phần của sản phẩm, không chỉ steady-state.
- Không bit-exact giữa hai routing (sg on/off) là bình thường — floating-point cộng khác thứ tự; định nghĩa "tương đương" bằng token-level golden (0 mismatch trên 3.3M token như bạn đã gate) chứ không phải so bit.
5. Ma trận triển khai của bạn
| Đích | Format + runtime | Vì sao hợp |
|---|---|---|
| HF Space (CPU free) | CT2 int8_float32 (~38MB cho 30M) | CPU không có fp16 nhanh; int8 giảm 4× băng thông đọc; CT2 kernel CPU tốt |
| App local có CUDA (qt2) | CT2 int8_float16, fallback int8_float32/CPU qua env | GPU ăn fp16 compute; env override cho máy yếu — đúng pattern bạn viết |
| Browser (webMT) | Q8 tự chế + WebGPU, autotune per-device | Không kiểm soát phần cứng người dùng → phải tự mang kernel + tự dò cấu hình |
| LLM local (5070Ti 12GB) | GGUF Q4_K_M + llama.cpp -fitt | 26B chỉ vừa VRAM ở ~4-bit; auto-fit tránh tràn câm |
| Đo đối chứng nghiên cứu | ONNX Runtime (vp2vi CTC) | Kiến trúc lạ (expansion + CTC head) CT2 không chạy — ORT ăn graph tùy ý, nhớ log operator fallback |
Quy luật chung rút từ bảng: format đi theo phần cứng đích và mức kiểm soát của bạn với nó, không có "format tốt nhất" tuyệt đối. Và mỗi ô của bảng đều phải có golden test riêng của nó (tokenizer contract Module 04 + parity mục 2).
Bẫy thường gặp
- Golden trên bản fp16, ship bản int8. Đo đúng artifact ship — không ngoại lệ (kể cả "chỉ đổi compute_type").
- Bench khi máy đang nóng / lẫn thứ tự đo — mọi kết luận <20% đều đáng ngờ nếu không xen kẽ vòng đo.
- Nhìn tok/s quên load time. Sản phẩm dịch-khi-đọc như của bạn: 100ms load thêm là cảm nhận được; DiffusionGemma "wall 116s trong đó generation 38s" là ví dụ cực đoan.
- Quên KV cache khi khoe context dài. Bài toán Module 06 câu 4: 8k context fp16 = 6.4GB — hơn nửa VRAM; context dài phải đi kèm kế hoạch KV (giới hạn, quantize KV, LRU như webMT).
- So hai runtime bằng hai bộ câu khác nhau / hai máy khác trạng thái — mọi quy tắc so sánh của Module 09/12 áp nguyên cho bench tốc độ.
- Tin marketing "chạy được trên 12GB" — chạy được ở chế độ tràn ≠ dùng được; tự đo với workload thật của mình.
Tự kiểm tra
1 · Model 57M: ước lượng dung lượng file ở fp16, int8, Q4 — và vì sao bản CT2 30M của bạn ra ~38MB?
57M: fp16 ≈ 114MB, int8 ≈ 57MB, Q4 ≈ ~32MB. Model 30M int8 ≈ 30MB + scales/embedding giữ độ chính xác cao hơn + metadata ≈ ~38MB — khớp export thật của bạn. Công thức nhẩm: param × byte/param + ~10–25% phụ phí tùy format.
2 · GPU băng thông 576 GB/s, model Q4 nặng 15.7GB nằm trọn VRAM, batch 1. Trần tok/s decode?
≈ 576 ÷ 15.7 ≈ ~37 tok/s — chưa cần biết GPU bao nhiêu TFLOPS! Nếu đo được thấp hơn hẳn → nghi tràn/overhead; muốn vượt trần → giảm bytes (quantize sâu hơn), MoE (chỉ đọc expert active — như Gemma A4B), hoặc batch. Mental model bandwidth-bound trả lời được 80% câu hỏi tốc độ decode.
3 · Thiết kế bench so sánh "immediates vs uniform" trên laptop trong 30 phút, chống được thermal drift?
Chia ≥4 round, mỗi round chạy CẢ hai arm với thứ tự đảo luân phiên (A-B, B-A, A-B…), nghỉ cố định giữa round, bỏ round đầu (warm-up), báo cáo median per-arm + chênh lệch per-round (nếu chênh lệch đổi dấu theo thứ tự chạy → drift còn lớn hơn hiệu ứng, tăng số round). Đúng quy trình bạn đã hội tụ tới trong webMT — giờ nó là SOP có tên: interleaved measurement.
4 · Space CPU đang chạy int8_float32 ổn. Có nên thử int8_float16 cho nhanh hơn?
Trên CPU: gần như không — fp16 compute không nhanh trên đa số CPU (thiếu đường ống fp16 native), int8_float32 đã là điểm đúng; int8_float16 là bài của CUDA (đúng như translator.py của bạn: CUDA → int8_float16, CPU → int8_float32). Nếu Space cần nhanh hơn: batch request, giảm beam, hoặc… kiểm tra lại tokenize workers — đúng thứ tự checklist mục 3 trước khi nghĩ đến đổi format.
Phần IV — Đánh giá & triển khai · Module 15
15 Scaling & những hướng mới
Mục tiêu
- Trang bị khung scaling laws để trả lời câu hỏi sống còn của bạn: "41M kịch trần chưa — 130M có đáng không?"
- Hiểu vì sao với model ship đi (như của bạn), "nhỏ mà train no" thường thắng "to mà train đói"
- Đặt DiffusionGemma và MoE vào bản đồ — chúng liên hệ họ hàng với NAT/CTC của bạn hơn bạn tưởng
- Khép giáo trình bằng bản đồ quyết định tổng + 10 nguyên tắc gói toàn bộ 14 module
1. Scaling laws — bản đồ ba trục N, D, C
Kết quả thực nghiệm lặp lại bền vững của cả ngành: loss giảm đều và dự đoán được (theo hàm mũ âm) khi tăng N (tham số), D (data), C (compute) — miễn là không trục nào bị nghẽn. Điều đáng tiền nằm ở vế sau: tăng một trục trong khi trục kia đứng yên thì đường cong gãy ngang:
Áp thẳng vào chương trình của bạn — và thấy các quyết định đã đúng trình tự:
- Probe 90M: "vượt scratch local nhưng chưa vượt Hirashiba → không scale 150M" — đúng logic đường đỏ: chưa thắng baseline ở cỡ này thì cỡ to hơn không phải câu trả lời.
- 2-arm 130M đối chiếu A9-41M (chrF 63.065) và Marian (64.46): nếu 130M chỉ nhích nhẹ so với 41M mà vẫn thua Marian → nút thắt nằm ở data/recipe, không phải capacity — tiền tiếp theo nên rót vào pool + distillation (Module 03, 10), không phải vào N.
- Gate bạn đặt cho 150M–220M (baseline comparison + entity/long-row stress) chính là "đo xem mình đang ở đường đỏ hay đường ngọc".
2. Với model ship đi: nhỏ-mà-no thắng to-mà-đói
Chinchilla tối ưu cho chi phí train. Nhưng model của bạn ship tới người đọc — chi phí thật nằm ở inference, trả mỗi token, mãi mãi (băng thông — Module 14). Vì thế thế giới model nhỏ dùng chiến lược "overtrain": train model nhỏ lâu hơn mức Chinchilla trên nhiều data hơn — mua chất lượng bằng compute một lần, giữ chi phí serve thấp vĩnh viễn. Đây là chỗ đứng lý thuyết cho mục tiêu 15–30M "chất lượng bằng medium, nhanh hơn" bạn từng đặt: đường đến đó là data tốt hơn + distill khéo hơn + train no hơn, không phải thêm param. Bảng cân nhắc mỗi lần muốn tăng cỡ:
| Tăng cỡ model (vd 41M → 130M) | Tăng chất lượng data + train no (giữ 41M) |
|---|---|
| Trả một lần: tiền train tăng vừa | Trả một lần: công pipeline + tiền teacher + train lâu hơn |
| Trả mãi mãi: mỗi token đọc weights gấp ~3× → chậm ~3× trên mọi thiết bị (webMT mobile!), Space CPU cần nâng cấp | Miễn phí mãi mãi: tốc độ, VRAM, format export — tất cả giữ nguyên |
| Chỉ thắng khi model cũ đã no data mà vẫn đói chất lượng | Thắng trong mọi kịch bản còn lại — và là điều kiện tiên quyết để lần scale sau có ý nghĩa |
3. Những hướng mới trên bản đồ cũ
- Diffusion LM (DiffusionGemma): sinh cả "canvas" token rồi tinh chỉnh song song qua nhiều vòng khử nhiễu — họ hàng trực tiếp của CMLM/iterative-NAT bạn nghiên cứu cho vp2vi (Module 08): cùng triết lý "song song + lặp", khác công thức toán. Số vòng = núm chỉnh chất lượng/tốc độ — đúng cái bạn bench: 16/32/48 steps → 6.7/3.4/2.2 tok/s. Kết luận thực dụng của bạn giữ nguyên giá trị: trên 12GB hiện tại, transformer MoE Q4 vẫn nhanh hơn; diffusion đáng theo dõi (quant nhỏ hơn, VRAM to hơn, kernel chín hơn) chứ chưa đáng đặt cược.
- MoE (Gemma A4B): 26B tham số nhưng mỗi token chỉ kích hoạt ~4B → đọc ít weights mỗi token → nhanh theo đúng mental model bandwidth của Module 14. MoE là cách "to mà không trả thuế to mỗi token" — chưa liên quan model MT nhỏ của bạn, nhưng là lý do vì sao con 26B lại chạy nổi trên laptop.
- MTP (multi-token prediction): AR đoán nhiều token mỗi lượt bằng head phụ — cần train head từ đầu, llama.cpp mới hỗ trợ Qwen 3.5/3.6; không áp được cho DiffusionGemma lẫn Marian của bạn (đúng như bạn đã tra). Nếu một ngày bạn build model AR from-scratch thế hệ mới, MTP là thứ đáng cân nhắc từ lúc thiết kế, không phải gắn sau.
4. Bản đồ quyết định tổng — treo tường
5. Mười điều, nếu chỉ được nhớ mười điều
| 1 | Model là tấm gương của data — trần chất lượng nằm ở data, mọi thứ khác quyết định độ trong của gương. M03 |
| 2 | Loss để debug; BLEU/chrF để regression; quyết định bằng blind review + entity + severity. M01·M13 |
| 3 | Eval tách trước, niêm phong, versioned; mỗi row eval sai đắt hơn nghìn row train sai. M03·M13 |
| 4 | Tokenizer là hợp đồng bất biến — golden-test từng dòng ở mọi nơi nó "đi du lịch". M04 |
| 5 | Probe trước, scale sau — lỗi hiện ra ở 13k rows y như ở 500k, giá rẻ hơn chục lần. M03·M09 |
| 6 | Mỗi run: một biến đổi, bảng kết quả chốt trước, budget guard, stop criteria. M09 |
| 7 | Fine-tune là phẫu thuật: liều tối thiểu, có anchor/replay, golden trước–sau. M10 |
| 8 | DPO là dao mổ cho stubborn cases sau khi SFT bão hòa — margin dương chưa phải thắng; 0.693 là mốc policy = reference, chỉ đáng báo động khi kẹt cùng hành vi không đổi. M12 |
| 9 | Inference nghẽn ở bytes: vừa VRAM trước, quantize thứ hai, kernel sau cùng; golden trên artifact cuối. M14 |
| 10 | Model to là thuế trả mỗi token mãi mãi; data tốt là tài sản trả một lần — chỉ scale khi nhỏ đã no mà vẫn đói. M15 |
Tự kiểm tra — tổng kết
1 · Kết quả 2-arm giả định: 130M đạt chrF 63.8 (41M: 63.065, Marian: 64.46). Đọc thế nào, làm gì tiếp?
+0.74 chrF cho 3.2× tham số — và vẫn thua Marian: bạn đang ở đường đỏ (data-limited). 130M không "vô dụng" nhưng chưa đáng thuế inference 3×. Việc tiếp theo: soi bucket xem 130M hơn ở đâu (nếu chỉ hơn ở long-row → tín hiệu capacity thật nhưng cục bộ); rót nguồn lực vào pool + teacher + train no 41M/57M; giữ 130M làm bằng chứng "capacity chưa phải nút thắt". Chỉ quay lại scale khi 41M-no-data vượt Marian mà vẫn hụt mục tiêu.
2 · Vì sao DiffusionGemma và CMLM được xếp "cùng họ" dù toán khác hẳn?
Cả hai đổi trục thời gian của AR (mỗi bước một token, tuần tự) thành trục vòng tinh chỉnh (mỗi vòng cập nhật song song nhiều/mọi vị trí, lặp k vòng): CMLM che-và-đoán-lại chỗ kém tự tin, diffusion khử nhiễu dần cả canvas. Hệ quả chung: tốc độ ~ số vòng (không ~ độ dài), chất lượng mua bằng thêm vòng, và multimodality là kẻ thù chung. Hiểu một đứa là có trực giác cho đứa kia — NAT của bạn là vé vào cửa sớm cho cả dòng này.
3 · Ngân sách một cuối tuần + 5070Ti. Ba lựa chọn: (a) FT 57M thêm 1 epoch data cũ; (b) làm 3k cặp gold mới bằng teacher + review; (c) thử LoRA r=16 dạy override hint. Chọn gì, vì sao?
Khung chấm: (a) gần như vô ích — data cũ không mang thông tin mới (đường đỏ đi ngang); (b) và (c) đều đúng "data/cơ chế trước capacity". Chọn theo mục tiêu quý này: chất lượng nền → (b) (tài sản vĩnh viễn, mọi model sau hưởng); tính năng Names-list → (c) (đã có bài thiết kế data mâu thuẫn ở M10). Điểm mấu chốt: (a) cảm giác như làm việc nhất — GPU chạy rào rào — nhưng là lựa chọn duy nhất không mua được thông tin hay tài sản mới.
4 · Câu cuối cùng của giáo trình: khi nào thì DỪNG một hướng — và vì sao freeze reverse-CTC của bạn là ví dụ đẹp?
Dừng khi một trong ba: mục tiêu đã đạt theo định nghĩa của người dùng cuối; chi phí biên vượt giá trị biên (thêm 1 chrF giá 10× lần trước); hoặc hướng khác mua cùng giá trị rẻ hơn. Freeze reverse-CTC đạt chuẩn mực: không đốt tiếp compute vào nhánh chưa cần, nhưng giữ nguyên tài sản (2.8M rows + 5M inventory, có tài liệu) để ngày cần back-translation (M11) là mở ra dùng được ngay. Dừng có kỷ luật ≠ vứt bỏ — đó là điểm khác nhau giữa dự án cá nhân cháy túi và một chương trình nghiên cứu bền.
Hết 15 module. Giáo trình này không dạy bạn điều gì bạn chưa từng chạm — nó gắn tên, gắn khung và gắn số cho những trực giác bạn đã trả học phí để có. Lần tới khi log hiện loss 0.693, khi có người rủ scale lên 200M, khi teacher mới chào hàng — mở đúng module, đọc 10 phút, quyết trong 5. Chúc luyện đan thành công. ⚗