Danh sách bài viết

Bài 51: Chuẩn bị dataset cho fine-tune (instruction tuning format)

Sau Bài 50 (QLoRA), việc fine-tune Llama 8B trên consumer GPU không còn là rào cản kỹ thuật — bottleneck dịch về phía dataset. Bài này phân tích chi tiết cách chuẩn bị instruction tuning dataset cho supervised fine-tuning (SFT): ba format phổ biến (Alpaca {instruction, input, output}, ShareGPT {conversations: [{from, value}]}, OpenAI Chat {messages: [{role, content}]}); tiêu chí "quality > quantity" với reference paper LIMA (Zhou et al., Meta 2023 — 1000 sample đủ alignment cho Llama 65B); nguồn dataset open phổ biến (Stanford Alpaca 52K, OpenOrca 4M GPT-3.5/4 distillation, UltraChat 1.5M, Hermes 3 / OpenHermes / Capybara / Slim Orca từ Nous Research, Teknium); năm tiêu chí chất lượng (diverse, correct, well-formatted, safe, no train/test leakage); bốn cách build dataset custom (manual write, LLM-generate qua Self-Instruct Wang et al. 2022 và Evol-Instruct WizardLM 2023, user logs từ production, hybrid); data cleaning pipeline (deduplication exact + near-duplicate MinHash, length outlier filter, LLM-as-judge quality score, PII và harmful content removal); train/val/test split 80/10/10 với datasets.train_test_split; convert format Alpaca ↔ Chat ↔ ShareGPT; apply chat template từng model (Llama 3 / Mistral / Qwen / ChatML) qua tokenizer.apply_chat_template (xem Bài 21 B-Series 4); format chuẩn cho trl.SFTTrainer (column text pre-formatted hoặc messages field auto template); kích thước dataset theo mục đích (200-500 style/tone, 1K-5K task mới, 5K-50K domain, millions cho pretraining-like); token length distribution histogram để pick max_length 95th percentile; multi-turn conversation với loss mask chỉ trên assistant turn; synthetic data từ GPT-4 (cost vs quality vs bias risk); HuggingFace datasets library load + filter + map + split; dataset tiếng Việt (VinaLLaMA, PhoGPT, dịch từ Alpaca, limited so với English); tool data labeling (Argilla, Lilac, Cleanlab); pitfall thường gặp (mismatch tokenizer template giữa train và inference, mix Llama template cho Mistral model, quên mask user turn, train/test leakage); best practice "start small 1000 example, iterate small batch → train → eval → improve data"; code Python end-to-end load Alpaca, convert sang chat, apply Llama 3 template, inspect token distribution; bài tập tạo 50 example custom + convert + apply template + plot histogram.

25/05/2026
14 phút đọc
3 lượt xem
1

Mục tiêu bài học

Sau bài học, bạn sẽ:

  • Phân biệt được ba format phổ biến: Alpaca, ShareGPT, OpenAI Chat — biết khi nào dùng cái nào.
  • Hiểu vì sao "quality > quantity" và đọc được kết luận chính của LIMA paper.
  • Biết các nguồn dataset open có thể dùng ngay (Alpaca 52K, OpenOrca, UltraChat, OpenHermes...).
  • Áp dụng năm tiêu chí chất lượng để loại bỏ sample xấu.
  • Viết được code convert Alpaca ↔ chat format và apply chat template Llama 3 / Mistral.
  • Build được pipeline cleaning: dedup, length filter, LLM-as-judge, PII removal.
  • Chọn được kích thước dataset phù hợp với mục đích fine-tune (style, task, domain).
  • Tránh được pitfall mismatch tokenizer template giữa train và inference.
2

Instruction tuning là gì

Instruction tuning là giai đoạn supervised fine-tuning (SFT) chuyển base LLM (predict next token thuần) thành chat assistant — biết theo instruction, trả lời câu hỏi, follow format. Dataset của giai đoạn này gồm các cặp (instruction → response) hoặc multi-turn conversation, được model học để bắt chước phong cách trả lời.

Khác với pre-training (corpus thô hàng TB) hay RLHF (cần reward model + PPO/DPO), instruction tuning chỉ cần:

  • Một tập dataset có cấu trúc (prompt, response) — vài nghìn đến vài chục nghìn sample là đủ.
  • Cross-entropy loss thông thường, chỉ tính trên token của response (mask phần prompt).
  • Một vài giờ GPU với LoRA/QLoRA (Bài 49, 50).

Vì cấu hình training là việc lặp đi lặp lại đã có sẵn (Bài 52 sẽ dùng trl.SFTTrainer để chuẩn hoá), thành phần thực sự quyết định kết quả là dataset. Bài này tập trung vào cách chuẩn bị nó.

3

Ba format phổ biến

Cộng đồng open-source quy về ba format chính, đa số tool đều support cả ba và có util convert qua lại:

  • Alpaca (Stanford 2023): {instruction, input, output} — single-turn, có optional input.
  • ShareGPT (cộng đồng ShareGPT scrape ChatGPT 2023): {conversations: [{from, value}]} — multi-turn với role human / gpt.
  • OpenAI Chat (chuẩn API OpenAI): {messages: [{role, content}]} — multi-turn với role system / user / assistant.

Trong production hiện nay (2024-2026), OpenAI Chat format đang trở thành chuẩn de-facto vì khớp với template của hầu hết model (Llama 3, Mistral, Qwen, Gemma đều dùng role user/assistant). Alpaca vẫn phổ biến cho dataset legacy và task single-turn đơn giản. ShareGPT phổ biến trong cộng đồng open-source chat (Vicuna, Mistral fine-tunes).

4

Format Alpaca

Alpaca format do nhóm Stanford giới thiệu cùng dataset Alpaca 52K (Taori et al., 2023):

{
    "instruction": "Translate to English",
    "input": "Xin chào",
    "output": "Hello"
}

Trường input tuỳ chọn — nếu instruction đã đủ (vd "Write a poem about autumn"), có thể để rỗng "". Khi format prompt template, hai trường hợp xử lý khác nhau:

Có input:
### Instruction:
Translate to English

### Input:
Xin chào

### Response:
Hello

Không input:
### Instruction:
Write a poem about autumn

### Response:
[output]

Ưu điểm: đơn giản, dễ build bằng tay, phù hợp single-turn. Nhược điểm: không native multi-turn, phải tự định nghĩa template prompt.

5

Format ShareGPT

ShareGPT bắt nguồn từ dữ liệu user chia sẻ session ChatGPT, sau đó được Vicuna team (LMSYS) chuẩn hoá:

{
    "conversations": [
        {"from": "human", "value": "Xin chào, bạn là ai?"},
        {"from": "gpt", "value": "Tôi là một trợ lý AI..."},
        {"from": "human", "value": "Bạn có biết tiếng Việt không?"},
        {"from": "gpt", "value": "Có, tôi có thể hiểu..."}
    ]
}

Role chỉ có humangpt (không có system). Hỗ trợ multi-turn native — phù hợp dataset chat assistant nhiều lượt. Nhược điểm: thiếu system role nên không gắn được persona/instruction tổng cho cả conversation; convert sang OpenAI Chat là một bước thường thấy.

6

Format OpenAI Chat

Format giống OpenAI Chat Completions API — đã trở thành interface mặc định của các model chat 2024-2026:

{
    "messages": [
        {"role": "system", "content": "You are helpful."},
        {"role": "user", "content": "Xin chào"},
        {"role": "assistant", "content": "Hello"}
    ]
}

Ba role chính: system (instruction tổng cho persona, tone), user (input người dùng), assistant (output model). Một số biến thể thêm tool / function cho function calling (xem Bài 22-25 B-Series 4).

Ưu điểm: khớp với tokenizer.apply_chat_template của HuggingFace — chỉ cần truyền messages vào, tokenizer tự gắn template (special token, role marker) theo model. Code train và code inference dùng cùng một format → tránh được pitfall mismatch.

7

Quality > Quantity và LIMA paper

Trực giác phổ biến: nhiều data hơn = model tốt hơn. Với instruction tuning, điều ngược lại thường đúng — 1000 example chất lượng > 10K example noisy.

Bằng chứng tiêu biểu: LIMA: Less Is More for Alignment (Zhou et al., Meta AI, arXiv:2305.11206, 2023). Nhóm fine-tune Llama 65B với chỉ 1000 sample được curate tay từ nguồn StackExchange, wikiHow, Reddit. Kết quả: human eval cho thấy LIMA tương đương hoặc tốt hơn DaVinci-003 (GPT-3.5 thời điểm đó) trong 43% case, và gần với GPT-4 trong nhiều task. Kết luận chính của paper:

  • Phần lớn knowledge đã có trong pre-training; SFT chỉ dạy formattone.
  • Vài nghìn sample chất lượng cao là đủ để "unlock" alignment.
  • Scaling SFT lên trăm nghìn / triệu sample chất lượng trung bình không thắng được dataset 1K curated.

Hệ quả thực tế: thay vì cố thu thập số lượng, ưu tiên thời gian vào curate, clean và verify từng sample.

8

Nguồn dataset open phổ biến

Một số dataset SFT open thường được dùng làm baseline hoặc mix vào dataset custom:

  • Stanford Alpaca (Taori et al., 2023): 52K sample sinh bằng text-davinci-003. Phổ biến nhưng có noise — bản yahma/alpaca-cleaned đã filter một số issue.
  • ShareGPT: ~90K conversation người dùng chia sẻ từ ChatGPT — đa dạng task nhưng quality không đều.
  • OpenOrca (Lian et al., 2023): ~4M sample distill từ GPT-3.5/GPT-4 dựa trên FLAN collection. Slim Orca là subset 500K được filter chất lượng.
  • UltraChat (Ding et al., Tsinghua, 2023): ~1.5M multi-turn conversation tạo bằng GPT-3.5/4.
  • OpenHermes 2.5 (Teknium, Nous Research): ~1M sample mix nhiều nguồn quality cao.
  • Hermes 3 (Nous Research, 2024): dataset đi kèm Hermes 3 model, mix instruction + reasoning + roleplay.
  • Capybara (LDJnr): ~16K multi-turn high quality, ưu tiên reasoning.
  • WizardLM Evol-Instruct: ~70-250K sample được "evolve" từ instruction đơn giản thành phức tạp.
  • Tulu V2 / V3 (AllenAI): mix instruction + reasoning, có license rõ ràng.

Khi pick: kiểm tra license (một số dataset distill từ GPT có ràng buộc của OpenAI ToS), kiểm tra contamination với benchmark đánh giá (đặc biệt MMLU, GSM8K).

9

Năm tiêu chí chất lượng

Áp dụng năm tiêu chí cho mỗi sample khi curate:

  • Diverse: cover nhiều task (Q&A, summarize, translate, code, reasoning, creative). Tránh 10K sample đều "viết email" — model sẽ overfit pattern này.
  • Correct: response factually right. Sai sự thật trong dataset → model học sai và phổ biến lỗi (hallucination).
  • Well-formatted: format response consistent (markdown, code block, list) — model copy format này. Mix lộn xộn → output không đoán được.
  • Safe: không có harmful content (bạo lực, phân biệt, PII). Mở rộng: tránh prompt injection pattern.
  • No data leakage: không trùng với test set / benchmark (MMLU, HumanEval, GSM8K...). Leakage làm metric tăng giả nhưng generalization kém.

Trong thực tế, ba tiêu chí đầu thường là bottleneck với dataset community (đa số là correct nhưng noisy về format và đa dạng). Tiêu chí thứ 5 hay bị quên — cần check explicit khi train cho production.

10

Bốn cách build dataset custom

  • Manual write: chuyên gia (hoặc team) tự viết. Chất lượng cao nhất, chậm (~50-200 sample/người/ngày), tốn tiền nhưng phù hợp khi domain hẹp.
  • LLM-generate: dùng GPT-4 / Claude / model lớn để sinh sample. Nhanh, scale tốt, cost thấp (~$0.01-0.10/sample). Risk: model copy bias và hallucination của teacher.
  • User logs: lấy conversation từ production (đã anonymize, đã consent). Đại diện đúng distribution thực tế, nhưng cần infra logging và pipeline annotate.
  • Hybrid: phổ biến nhất — seed 200-500 manual, dùng LLM-generate scale lên 5-10K, mix thêm user logs nếu có. Cuối cùng human review subset.

Khuyến nghị: bắt đầu manual với 50-200 sample để cảm nhận task và domain, sau đó mới quyết định scale bằng LLM-generate hay không.

11

Self-Instruct và Evol-Instruct

Self-Instruct (Wang et al., arXiv:2212.10560, 2022) là kỹ thuật tự động sinh instruction từ seed:

  1. Bắt đầu với seed examples (175 sample trong paper gốc) — instruction đa dạng manual write.
  2. Prompt LLM (GPT-3 trong paper, GPT-4 hiện đại) sinh thêm instruction variations theo style của seed.
  3. Filter quality: bỏ duplicate (ROUGE similarity), bỏ instruction quá ngắn/dài, bỏ instruction không có response hợp lệ.
  4. Scale up lên 50K-100K với cost vài chục đến vài trăm USD.

Stanford Alpaca dùng chính kỹ thuật này — sinh 52K sample từ 175 seed với text-davinci-003.

Evol-Instruct (Xu et al., WizardLM, arXiv:2304.12244, 2023) cải tiến bằng cách "evolve" instruction đơn giản thành phức tạp qua các operator:

  • Add constraints: thêm ràng buộc ("trong 3 câu", "không dùng từ X").
  • Deepening: tăng độ chi tiết yêu cầu.
  • Concretizing: thay khái niệm chung bằng cụ thể.
  • Reasoning steps: thêm yêu cầu reasoning multi-step.
  • In-breadth: sinh instruction mới ở domain lân cận.

Evol-Instruct cho dataset đa dạng hơn về độ khó — dùng trong WizardLM series và nhiều dataset 2024.

12

Data cleaning pipeline

Một pipeline cleaning tối thiểu chạy theo thứ tự:

  1. Deduplication: exact dedup theo hash của (instruction + output); near-duplicate bằng MinHash + Jaccard threshold 0.8 hoặc embedding cosine threshold 0.9.
  2. Length filter: bỏ sample quá ngắn (output < 10 token thường vô nghĩa) hoặc quá dài (output > 4096 token thường là code dump hoặc lỗi). Plot histogram để chọn ngưỡng.
  3. Quality filter (LLM-as-judge): dùng GPT-4 (hoặc Llama 70B) chấm điểm 1-5 cho từng sample theo rubric (helpful, accurate, well-formatted). Giữ score ≥ 4.
  4. PII removal: dùng regex hoặc model như Presidio / Cleanlab để detect và mask email, phone, SSN, credit card.
  5. Harmful filter: dùng classifier (Llama Guard, OpenAI Moderation) để loại sample có nội dung độc hại.
  6. Benchmark contamination check: so trùng (n-gram overlap) với MMLU, GSM8K, HumanEval — bỏ sample trùng.

Order quan trọng: dedup trước để không lãng phí LLM-as-judge cho sample duplicate. PII và harmful sau cùng vì có thể đắt hơn.

13

Train / Val / Test split

Tỉ lệ thông dụng: 80 / 10 / 10 hoặc 90 / 5 / 5 nếu dataset nhỏ.

  • Train: dùng cho gradient update.
  • Val: theo dõi loss trong training, dùng cho hyperparameter tuning (learning rate, epochs, rank của LoRA). KHÔNG dùng để chọn checkpoint cuối nếu đã peek nhiều lần — sẽ overfit val.
  • Test: dùng một lần cuối cùng để báo metric. Giữ tách biệt hoàn toàn.

Code với HuggingFace datasets:

from datasets import load_dataset

ds = load_dataset("yahma/alpaca-cleaned", split="train")

# Split 90/10 trước, sau đó split val/test từ phần 10%
split1 = ds.train_test_split(test_size=0.2, seed=42)
train = split1["train"]
split2 = split1["test"].train_test_split(test_size=0.5, seed=42)
val, test = split2["train"], split2["test"]

print(len(train), len(val), len(test))

Lưu ý seed — phải fix để reproducible. Stratified split nếu có label task (vd "translate", "code", "summarize") để val/test cover đủ task.

14

Convert format giữa Alpaca / ShareGPT / Chat

Convert giữa ba format là util thường gặp. Ví dụ Alpaca → OpenAI Chat:

def alpaca_to_chat(ex):
    user_content = ex["instruction"]
    if ex.get("input"):
        user_content = f"{ex['instruction']}\n{ex['input']}"
    return {
        "messages": [
            {"role": "user", "content": user_content},
            {"role": "assistant", "content": ex["output"]},
        ]
    }

ShareGPT → OpenAI Chat (chỉ đổi key và role):

ROLE_MAP = {"human": "user", "gpt": "assistant", "system": "system"}

def sharegpt_to_chat(ex):
    return {
        "messages": [
            {"role": ROLE_MAP[m["from"]], "content": m["value"]}
            for m in ex["conversations"]
        ]
    }

OpenAI Chat → Alpaca chỉ áp dụng được nếu conversation đúng một cặp user/assistant — multi-turn không convert được mà không mất thông tin. Đó là một lý do nữa chọn OpenAI Chat làm format mặc định.

15

Apply chat template từng model

Mỗi model có chat template riêng — quy ước về special token, role marker, separator. Ví dụ:

  • Llama 3: <|begin_of_text|><|start_header_id|>user<|end_header_id|>\n\n...<|eot_id|>.
  • Mistral: <s>[INST] ... [/INST] ... </s>.
  • Qwen 2.5: <|im_start|>user\n...<|im_end|> (ChatML).
  • ChatML (chuẩn chung Nous Research, OpenAI): <|im_start|>role\ncontent<|im_end|>.

HuggingFace tokenizer hỗ trợ apply_chat_template tự gắn template theo metadata của model (đã được xử lý chi tiết trong Bài 21 series B-Series 4):

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B-Instruct")

messages = [
    {"role": "system", "content": "You are helpful."},
    {"role": "user", "content": "Xin chào"},
    {"role": "assistant", "content": "Hello"},
]

text = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=False,
)
print(text)

Output là string đã chèn đủ special token — sẵn sàng tokenize và đưa vào model. Khi train với trl.SFTTrainer và có column messages, trainer tự gọi apply_chat_template theo tokenizer được truyền vào.

16

Format cho trl SFTTrainer

trl.SFTTrainer (Bài 52) chấp nhận dataset ở hai dạng:

  • Pre-formatted text: dataset có column text là string đã apply template (hoặc Alpaca prompt format). Trainer chỉ tokenize và train.
  • Chat format: dataset có column messages theo OpenAI Chat. Trainer tự apply chat template của tokenizer.

Format đề xuất 2024-2026: dùng messages column. Lợi ích:

  • Đổi model (Llama → Mistral → Qwen) chỉ cần đổi model_nametokenizer — template tự đổi theo.
  • Train và inference dùng cùng format — tránh mismatch.
  • Multi-turn được hỗ trợ tự nhiên.
from datasets import Dataset

data = [
    {"messages": [
        {"role": "user", "content": "Translate to English: Xin chào"},
        {"role": "assistant", "content": "Hello"},
    ]},
    {"messages": [
        {"role": "user", "content": "Capital of France?"},
        {"role": "assistant", "content": "Paris."},
    ]},
]

ds = Dataset.from_list(data)
# Truyền thẳng vào SFTTrainer(train_dataset=ds, ...)
17

Kích thước dataset theo mục đích

Heuristic theo mục đích fine-tune:

Mục đích                          Số sample        Ghi chú
Style / tone (vd phong cách)      200 - 500        Llama Instruct cover phần lớn task
Task mới (vd format output JSON)  1,000 - 5,000    Cần đủ variation
Domain knowledge (medical, legal) 5,000 - 50,000   Khả năng cover thuật ngữ
Pretraining-like (multi-task)     millions         Hiếm khi cần ở team nhỏ

Bắt đầu nhỏ để verify pipeline (1000 sample, 1 epoch, eval) — nếu loss giảm đúng và quality cải thiện trên test, mới scale tiếp. Tăng dữ liệu noisy không thay được dữ liệu chất lượng — luật giảm dần ích lợi rõ trong khoảng vài chục nghìn sample đầu.

Một số task chỉ cần dataset rất nhỏ (LIMA: 1000) khi base model đã có knowledge sẵn. Task domain mới hoàn toàn (vd ngôn ngữ ít resource) có thể cần continued pre-training trước, sau đó SFT — không thay thế được nhau.

18

Token length distribution

Plot histogram token length trước khi train để chọn max_length:

import numpy as np
import matplotlib.pyplot as plt

lengths = []
for ex in ds:
    text = tokenizer.apply_chat_template(ex["messages"], tokenize=False)
    lengths.append(len(tokenizer.encode(text)))

plt.hist(lengths, bins=50)
plt.xlabel("Token length")
plt.ylabel("Count")
plt.show()

print(f"p50={np.percentile(lengths, 50):.0f}, "
      f"p95={np.percentile(lengths, 95):.0f}, "
      f"max={max(lengths)}")

Cách chọn max_length:

  • Lấy 95th percentile làm max_length. Sample dài hơn bị truncate hoặc filter — chấp nhận mất 5% sample dài.
  • Tránh chọn max_length bằng max() — sẽ tốn VRAM cho padding khi 99% sample ngắn hơn.
  • Tăng max_length = tăng VRAM theo bình phương (attention cost) — cân nhắc trade-off.

Một số trainer support packing: ghép nhiều sample ngắn vào một sequence dài để tận dụng max_length — bật packing=True trong SFTTrainer nếu sample ngắn nhiều.

19

Multi-turn dataset và loss mask

Conversation nhiều lượt (> 2 turn) yêu cầu xử lý đặc biệt khi tính loss:

  • Train trên full conversation: input là toàn bộ chuỗi (system + user1 + assistant1 + user2 + assistant2 + ...).
  • Mask loss: chỉ tính cross-entropy trên token của assistant turn. User turn được set label = -100 (IGNORE_INDEX của PyTorch) — không vào loss.

Lý do mask: nếu tính loss trên cả user turn, model học để dự đoán user nói gì → behavior sai (model tự tạo cả user input khi inference).

Code mask trong trl.SFTTrainer được tự động khi truyền messagesDataCollatorForCompletionOnlyLM hoặc dùng completion_only_loss:

from trl import SFTTrainer, SFTConfig

config = SFTConfig(
    output_dir="./sft-llama",
    completion_only_loss=True,    # mask user turn tự động
    max_seq_length=2048,
)
# Truyền tokenizer có chat_template chuẩn → SFTTrainer xử lý mask

Nếu tự viết data collator: cần mark span của assistant token và set tất cả label khác về -100. Lỗi quên mask user turn là một trong những pitfall hay gặp nhất với người mới — biểu hiện model bắt chước user (lặp lại câu hỏi) thay vì trả lời.

20

Synthetic data

Synthetic data = sinh sample bằng LLM mạnh hơn (vd GPT-4, Claude) để fine-tune model nhỏ hơn (vd Llama 8B). Đặc điểm:

  • Cost: ~$0.01-0.10/sample với GPT-4 tuỳ độ dài. Dataset 10K sample = ~$100-1000.
  • Quality: với một số task structured (vd classification, simple Q&A), synthetic data có thể chất lượng đều hơn human-written.
  • Risk 1 — bias copy: model học bias của teacher (vd GPT-4 hay từ chối câu hỏi → student cũng từ chối).
  • Risk 2 — hallucination propagation: teacher hallucinate → student học cái sai đó.
  • Risk 3 — license: OpenAI ToS cấm dùng output để train model cạnh tranh (interpretation thay đổi theo từng version ToS).

Practice an toàn: human review subset 5-10% synthetic data; mix với manual seed để giữ diversity; tránh dùng cho task nhạy cảm pháp lý / y tế.

21

Pipeline end-to-end với HuggingFace datasets

Một pipeline tham khảo: load Alpaca → filter → convert → apply template → split:

from datasets import load_dataset
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B-Instruct")

# 1) Load
ds = load_dataset("yahma/alpaca-cleaned", split="train")

# 2) Filter quality: bỏ output quá ngắn / quá dài
ds = ds.filter(lambda x: 10 <= len(x["output"]) <= 4000)

# 3) Convert Alpaca → chat messages
def to_chat(ex):
    user = ex["instruction"]
    if ex.get("input"):
        user = f"{ex['instruction']}\n{ex['input']}"
    return {
        "messages": [
            {"role": "user", "content": user},
            {"role": "assistant", "content": ex["output"]},
        ]
    }

ds = ds.map(to_chat, remove_columns=ds.column_names)

# 4) Inspect token length
def add_len(ex):
    text = tokenizer.apply_chat_template(ex["messages"], tokenize=False)
    return {"token_len": len(tokenizer.encode(text))}

ds = ds.map(add_len)
print("p95 token len:", sorted(ds["token_len"])[int(len(ds) * 0.95)])

# 5) Filter sequence quá dài
ds = ds.filter(lambda x: x["token_len"] <= 2048)

# 6) Split 90/5/5
split1 = ds.train_test_split(test_size=0.1, seed=42)
train = split1["train"]
split2 = split1["test"].train_test_split(test_size=0.5, seed=42)
val, test = split2["train"], split2["test"]

print(len(train), len(val), len(test))
# Save
train.to_json("train.jsonl")
val.to_json("val.jsonl")
test.to_json("test.jsonl")

Pipeline này có thể plug thẳng vào trl.SFTTrainer ở Bài 52.

22

Dataset tiếng Việt

Dataset SFT tiếng Việt còn ít so với English. Một số nguồn:

  • VinaLLaMA (VinAI Research): có release dataset training cho version 7B chat.
  • PhoGPT (VinAI Research): model + một phần dataset open.
  • Vietnamese Alpaca: bản dịch Stanford Alpaca 52K sang tiếng Việt (chất lượng dịch không đều).
  • Vietnamese SQuAD, ViMMRC, UIT-ViQuAD: dataset Q&A có thể convert sang format instruction.
  • Manual curation: thường cần thiết — dịch máy từ Alpaca không cover được sắc thái tiếng Việt và format markdown phổ thông.

Khi train cho domain tiếng Việt: mix tỉ lệ ~50-70% tiếng Việt + 30-50% English (giữ model không quên English) thường cho kết quả tốt hơn 100% Việt. Verify trên benchmark đa ngôn ngữ nếu có (MMLU-Vi, VMLU).

23

Tool data labeling

  • Argilla: nền tảng open-source cho data labeling và annotation với LLM. Hỗ trợ workflow human-in-the-loop, integrate với HuggingFace Hub.
  • Lilac: tool curation và explore dataset — search, cluster, filter theo embedding similarity. Hữu ích để phát hiện cluster duplicate hoặc topic bias.
  • Cleanlab: phát hiện label noise và outlier bằng confident learning — phổ biến cho classification dataset, có thể adapt cho SFT.
  • Distilabel (Argilla, 2024): framework sinh và clean synthetic data với LLM, có nhiều pipeline có sẵn (UltraFeedback, Magpie, Self-Instruct).

Với team nhỏ, có thể dùng kết hợp: Lilac để explore + Argilla để annotate + Distilabel để generate synthetic.

24

Pitfall thường gặp

  • Train format khác inference format: train với prompt ### Instruction: nhưng inference dùng chat template Llama 3 → model không hiểu. Cách tránh: cùng template cho cả hai.
  • Mix tokenizer template: apply Llama template lên dataset rồi train Mistral model — sai. apply_chat_template phải gọi sau khi đã load đúng tokenizer của model đích.
  • Quên mask user turn: với multi-turn, không set label=-100 cho user token → model học bắt chước user.
  • Data leakage train/test: dataset community thường chứa câu trong benchmark MMLU/GSM8K — eval bị inflate. Check n-gram overlap trước.
  • Format không consistent: vài sample dùng markdown, vài sample plain text → model output ngẫu nhiên format.
  • Padding không đúng: padding bên trái hay phải tuỳ model (decoder-only thường left-padding cho inference, right-padding cho train) — sai → loss bị méo.
  • Không kiểm tra special token: pad_token trùng với token có ý nghĩa (vd <|endoftext|>) → model học stop ngẫu nhiên.
25

Best practice

  • Start small: bắt đầu 200-1000 sample manual, verify pipeline end-to-end (load → train 1 epoch → eval). Mất một buổi, tránh được rủi ro pipeline lỗi sau khi đã đổ tiền sinh dataset lớn.
  • Iterate: small batch → train → eval → identify failure mode → improve data cho failure mode đó → train lại. Mỗi cycle ngày, không tuần.
  • Diverse cover failure mode: phân tích output base model trên test set, tìm category model fail (vd reasoning math, format JSON) → ưu tiên thêm sample cho category đó.
  • Track version dataset: dùng git hoặc DVC để version. Mỗi experiment ghi rõ commit hash dataset.
  • Eval với LLM-as-judge + human spot-check: dùng GPT-4 chấm 200-500 sample cho hyperparameter sweep nhanh, sau đó human review 50-100 sample khi pick model final.
  • Đo trên test set giữ kín: không peek test trong khi tune.
  • Document: ghi rõ nguồn, cách filter, cleaning pipeline — để 6 tháng sau còn reproduce được.
26

Bài tập

Bài 1 — Tạo dataset 50 example custom. Chọn một domain hẹp bạn quen (vd "trợ lý code Python", "tư vấn nấu ăn"). Viết tay 50 cặp (instruction, input, output) theo Alpaca format, lưu file custom.jsonl. Đảm bảo cover ít nhất 5 category task khác nhau.

Bài 2 — Convert sang chat format. Load custom.jsonl bằng datasets.load_dataset("json", ...). Áp dụng alpaca_to_chat ở mục 14. Save thành custom_chat.jsonl. Verify 3 sample ngẫu nhiên có đúng structure {messages: [{role, content}]}.

Bài 3 — Apply Llama 3 chat template. Load tokenizer meta-llama/Llama-3.1-8B-Instruct. Gọi tokenizer.apply_chat_template với tokenize=False trên 3 sample đầu. In ra string kết quả — quan sát special token <|begin_of_text|>, <|start_header_id|>, <|eot_id|>.

Bài 4 — Plot token length histogram. Apply template cho toàn bộ 50 sample, encode bằng tokenizer, đếm token. Vẽ histogram bằng matplotlib. Tính p50, p95, max. Quyết định max_length bạn sẽ dùng cho training.

Bài 5 — Cleaning pipeline. Mở yahma/alpaca-cleaned (52K sample). Áp dụng pipeline mục 21: filter length (10 ≤ output ≤ 4000), convert sang chat, filter token_len ≤ 2048, split 90/5/5. In số sample còn lại sau từng bước.

Bài 6 — (optional) Self-Instruct mini. Dùng OpenAI API hoặc Claude API, prompt sinh thêm 20 instruction variation từ 5 seed của Bài 1. Filter duplicate bằng ROUGE-L threshold 0.7. Verify quality bằng tay.

Bài 7 — (optional) Benchmark contamination check. Load cais/mmlu (subset 100 question). Check n-gram (n=8) overlap giữa MMLU question và dataset của Bài 1 + Alpaca. Báo số sample bị flagged.

27

Tóm tắt

  • Instruction tuning dataset là dữ liệu (instruction → response) hoặc multi-turn chat để supervised fine-tune base LLM thành chat assistant.
  • Ba format phổ biến: Alpaca {instruction, input, output}, ShareGPT {conversations: [{from, value}]}, OpenAI Chat {messages: [{role, content}]}. OpenAI Chat là default 2024-2026.
  • Quality > Quantity: LIMA (Zhou et al., Meta 2023) chứng minh 1000 sample curated đủ alignment Llama 65B.
  • Nguồn open: Alpaca 52K (Stanford), OpenOrca 4M, Slim Orca 500K, UltraChat 1.5M, OpenHermes / Hermes 3 / Capybara (Nous, Teknium), WizardLM Evol-Instruct, Tulu V2/V3.
  • Năm tiêu chí chất lượng: diverse, correct, well-formatted, safe, no train/test leakage.
  • Bốn cách build: manual write, LLM-generate (Self-Instruct Wang 2022, Evol-Instruct WizardLM 2023), user logs, hybrid.
  • Self-Instruct: seed → LLM sinh variation → filter duplicate (ROUGE) → scale. Evol-Instruct: operator add constraints, deepening, concretizing, reasoning, in-breadth.
  • Cleaning pipeline thứ tự: dedup → length filter → LLM-as-judge → PII → harmful → benchmark contamination check.
  • Split 80/10/10 hoặc 90/5/5; val cho hyperparameter tuning, test dùng một lần cuối; fix seed; stratified theo task nếu có.
  • Convert format: viết util Alpaca↔Chat, ShareGPT→Chat (đổi role human/gptuser/assistant).
  • Chat template: mỗi model khác nhau (Llama 3, Mistral, Qwen, ChatML). Dùng tokenizer.apply_chat_template để tự gắn special token.
  • trl.SFTTrainer chấp nhận column text pre-formatted hoặc messages auto template. Ưu tiên messages để portable giữa các model.
  • Kích thước: style/tone 200-500, task mới 1K-5K, domain 5K-50K, pretraining-like millions.
  • Token length: plot histogram, pick max_length = 95th percentile, dùng packing nếu sample ngắn nhiều.
  • Multi-turn: train trên full conversation nhưng mask loss (label=-100) cho user token; chỉ tính loss trên assistant. Quên mask → model bắt chước user.
  • Synthetic data (GPT-4): nhanh, cost ~$0.01-0.1/sample, risk bias copy + hallucination + license; human review subset.
  • Pipeline HF datasets: load_datasetfiltermap (convert chat) → map (token_len) → filter length → train_test_splitto_json.
  • Dataset tiếng Việt: VinaLLaMA, PhoGPT, Vietnamese Alpaca dịch, ViQuAD/ViMMRC; mix 50-70% Việt + 30-50% English giữ năng lực English.
  • Tool: Argilla (annotate), Lilac (explore), Cleanlab (label noise), Distilabel (sinh synthetic).
  • Pitfall: train format khác inference, mix tokenizer template, quên mask user turn, data leakage, format không consistent, padding sai, pad_token trùng token có nghĩa.
  • Best practice: start small 200-1000, iterate small batch ngày-cycle, diverse cover failure mode, version dataset, LLM-as-judge + human spot-check, test set giữ kín.
  • Bài 52 sẽ ghép dataset đã chuẩn bị ở đây với QLoRA (Bài 50) thành full pipeline fine-tune dùng trl.SFTTrainer.