$ cat ./blog/gen-ai/llm-fundamentals/dataset-cho-llm.md
seriesCách hoạt động của LLMbài 5

Dataset cho LLM: từ dữ liệu thô đến những training sample model thật sự đọc

Bài thứ năm trong series Cách hoạt động của LLM: một trang web đi qua những bước nào để thành training sample, vì sao lọc chất lượng và khử trùng lặp lại thay đổi chính model, data mixture là hyperparameter ra sao, và cuối cùng một sample của causal LLM gồm đúng những gì.

~/diagrams/dataset-cho-llm/dataset-pipeline.htmltương tác
mở riêng ↗
HìnhHàng trên là đường đi của dữ liệu: thu về, trích xuất và chuẩn hoá, khử trùng lặp, trộn domain, rồi cắt thành chuỗi token có độ dài cố định. Hàng dưới là những quyết định của người xây dataset - thứ rác phải bỏ trước, ngưỡng lọc chất lượng, phần trùng với đề thi phải loại, và tokenizer cùng vị trí EOS quyết định biên của một sample.
đường đi của dữ liệutokenizer và biên documentquyết định của người xây dataset

Bài thứ tư dừng lại ở một dãy số nguyên: tokenizer biến văn bản thành input_ids. Nhưng nó không trả lời câu hỏi đứng ngay trước đó - văn bản ấy từ đâu ra, và ai quyết định model được đọc cái gì.

Khi nói về Large Language Model, phần lớn thời gian rơi vào kiến trúc Transformer, attention, số tham số và GPU. Trước khi bất kỳ tensor nào đi qua Transformer, một bài toán khác đã phải được giải: biến một lượng dữ liệu khổng lồ, hỗn loạn và không đồng nhất thành những chuỗi token mà model học được.

Một trang web không phải training sample. Một cuốn sách cũng không. Một repository, một bài báo khoa học, một đoạn hội thoại hay một trang Wikipedia đều chỉ là raw data. Để thành dữ liệu huấn luyện, chúng phải đi qua thu thập, trích xuất nội dung, chuẩn hoá, lọc chất lượng, khử trùng lặp, khử nhiễm benchmark, trộn domain theo tỷ lệ, tokenization, đánh dấu biên document, cắt thành sequence, rồi mới đóng gói thành batch.

Sơ đồ ở đầu bài (Hình 1) là toàn bộ quãng đường đó.

Điều đáng nói là: dựng dataset không phải công việc phụ nằm ngoài model training. Nó là một phần của chính bài toán xây LLM. DataComp-LM cho thấy riêng chiến lược lựa chọn và lọc dữ liệu đã tạo ra khác biệt rất lớn về chất lượng model, ngay cả khi giữ tương đối ổn định công thức huấn luyện. FineWeb, Dolma, RefinedWeb hay Nemotron-CC tồn tại cũng vì lý do tương tự: chất lượng, thành phần và cấu trúc của dữ liệu ảnh hưởng trực tiếp đến model ta nhận được.

Bài này đi từ raw data đến đúng thứ mà một decoder-only LLM thực sự nhận khi pretraining: một chuỗi input_ids, các target token tương ứng, và những mask cần thiết để tính loss.

Về code: các đoạn code trong bài là fragment minh hoạ (token ID là số giả định), không phải script chạy được.


01Dataset của một LLM thực sự là gì?

Trong machine learning truyền thống, dataset thường được hình dung như một bảng có nhãn:

image_001.jpg -> cat
image_002.jpg -> dog

Pretraining một causal language model không bắt đầu như vậy. Dữ liệu ban đầu chỉ là các document chứa text:

Document 1:
"The Transformer architecture was introduced in 2017..."

Document 2:
"Python uses indentation to define code blocks..."

Document 3:
"Photosynthesis converts light energy into chemical energy..."

Không ai gán nhãn cho chúng. Model tự tạo supervision từ chính văn bản, bằng bài toán next-token prediction: mỗi token trở thành đáp án cho những token đứng trước nó.

Vì vậy có một chuỗi khái niệm cần tách bạch, và chúng không đồng nghĩa với nhau.

~/diagrams/dataset-cho-llm/data-units.htmltương tác
mở riêng ↗
HìnhHàng trên nói về nội dung: document là một đơn vị người viết ra, corpus là tập hợp của chúng, curated corpus là phần còn lại sau khi lọc và dedup. Hàng giữa nói về đơn vị tính loss: sau tokenizer, dữ liệu bị cắt thành chuỗi có độ dài cố định rồi gom thành batch. Hàng cuối là lý do hai cách đếm không trùng nhau - một document dài trải ra nhiều sample, còn một sample có thể chứa token của nhiều document.
đơn vị nội dungđơn vị huấn luyệnchỗ hai cách đếm lệch nhau

1.1 Document

Một document là đơn vị nội dung tương đối độc lập: một trang web, một bài báo, một chapter sách, một file code, một post forum, một bài Wikipedia.

Document thường đi cùng metadata:

{
  "text": "Transformers use attention mechanisms...",
  "source": "web",
  "url": "https://example.com/article",
  "language": "en",
  "timestamp": "2026-01-10",
  "quality_score": 0.91
}

Metadata có thể không đi trực tiếp vào model, nhưng nó là thứ điều khiển toàn bộ filtering, sampling, provenance và auditing phía sau. Không có nó, ta không trả lời được câu hỏi “dữ liệu này từ đâu ra”.

1.2 Corpus và curated corpus

Corpus là tập hợp nhiều document, thường còn rất thô: một snapshot Common Crawl, một dump Wikipedia, một tập repository GitHub, một kho sách public-domain.

Sau cleaning, filtering, deduplication và các bước kiểm soát chất lượng, ta gọi phần còn lại là curated corpus.

Điểm phân biệt quan trọng nhất của cả bài nằm ở đây:

Corpus nói về tập nội dung. Training sample nói về cách nội dung đó được biến thành đơn vị tính loss.

1.3 Tokenized dataset, sequence, sample, batch

Tokenizer biến text thành token ID:

"Language models learn patterns"
        ↓
[6732, 4211, 3876, 12987]

Từ thời điểm này, pipeline chủ yếu làm việc với integer token ID, không phải string.

Một training sample là một sequence token có độ dài xác định - 2.048, 4.096 hay 8.192 token. Nhiều sequence gom lại thành batch. Nếu batch_size = 32 và sequence_length = 8192, một batch chứa 32 × 8192 = 262.144 vị trí token, trước khi tính đến parallelism hay gradient accumulation.

Một document dài 50.000 token sinh ra nhiều training sample. Ngược lại, một training sample cũng có thể chứa token đến từ nhiều document nếu pipeline cho phép ghép chúng lại. Đây là lý do đếm document không cho ta biết training compute.

LLM không “đọc Internet”. Nó nhận các ma trận số nguyên đại diện cho những chuỗi token mà pipeline dữ liệu đã chuẩn bị từ trước.


02Dữ liệu để train LLM đến từ đâu?

Một LLM general-purpose cần học nhiều loại pattern khác nhau, nên pretraining corpus hiếm khi chỉ có một nguồn.

Nguồn Model có thể học được gì
Web pages ngôn ngữ tự nhiên, kiến thức rộng, nhiều phong cách viết
Wikipedia factual knowledge, văn phong tương đối chuẩn
Books long-form text, narrative, cấu trúc văn bản dài
Scientific papers ngôn ngữ chuyên môn, công thức, lập luận kỹ thuật
Source code cú pháp, cấu trúc chương trình, API, algorithmic pattern
Documentation kiến thức kỹ thuật có cấu trúc
Forums, Q&A hội thoại, giải thích, vấn đề thực tế
Multilingual corpora khả năng xử lý nhiều ngôn ngữ
Synthetic data dữ liệu do model khác tạo hoặc biến đổi

Dolma là ví dụ tốt về corpus đa nguồn: nó kết hợp web content, scientific papers, code, sách public-domain, social media và tài liệu bách khoa. RefinedWeb thì đặt một câu hỏi ngược lại và thú vị hơn: nếu web data được lọc và dedup đủ tốt, riêng web có đủ để tạo model mạnh không? Kết quả của họ cho thấy ở nhiều setting, câu trả lời là có.

Nhưng “nhiều nguồn” không tự động đồng nghĩa với “dataset tốt”. Mỗi nguồn mang vấn đề riêng: web có spam, SEO page, boilerplate và nội dung trùng lặp; code có license, secret và file sinh tự động; paper có noise từ PDF extraction; forum có toxic content và PII; sách có vấn đề bản quyền và provenance.

Do đó pipeline thường gồm hai lớp:

  • source-specific processing - logic riêng cho từng nguồn;
  • source-agnostic processing - các bước chung như deduplication, PII filtering, decontamination.

Dolma mô tả pipeline theo đúng cách này: source code và web data không thể làm sạch giống hệt nhau, dù cuối cùng chúng cùng đi vào một training mixture.

Còn một phân biệt nữa hay bị trộn lẫn:

LLM data
├── Pretraining data
│   ├── web
│   ├── code
│   ├── books
│   └── ...
│
└── Post-training data
    ├── instruction-response pairs
    ├── preference pairs
    └── synthetic conversations

Bài này tập trung vào pretraining data. Instruction tuning và preference learning là những giai đoạn khác, vì cấu trúc sample và cách tính loss của chúng khác đáng kể.


03Từ Internet đến một corpus có thể sử dụng

Giả sử ta crawl được một trang web:

<html>
  <head><title>Understanding Transformers</title></head>
  <body>
    <nav>Home | About | Subscribe</nav>
    <article>
      <h1>Understanding Transformers</h1>
      <p>A Transformer processes tokens using attention...</p>
    </article>
    <aside>Recommended posts...</aside>
    <footer>Copyright 2026...</footer>
  </body>
</html>

Ta không muốn model tiêu tốn capacity để học Home | About | Subscribe, Accept cookies hay Sign up for newsletter. Ta muốn giữ phần có giá trị.

Với dữ liệu web quy mô lớn, Common Crawl là nguồn hay được dùng trong nghiên cứu. Nó cung cấp ba định dạng đáng nhớ: WARC chứa raw crawl data và HTTP response, WAT chứa metadata tính từ WARC, WET chứa plaintext đã trích xuất sẵn.

~/diagrams/dataset-cho-llm/web-to-corpus.htmltương tác
mở riêng ↗
HìnhHàng trên là đường đi chính: crawler ghi lại nguyên trạng response vào WARC, extractor lấy phần nội dung thật, normalizer dọn Unicode và ký tự điều khiển. Hàng dưới là những thứ đi kèm - metadata và plaintext có sẵn của Common Crawl, phần boilerplate bị loại ra, và nhãn ngôn ngữ được gắn vào document như metadata chứ không phải như một bộ lọc cứng.
đường đi chínhđịnh dạng lưu trữ crawlphần bị loại và metadata

3.1 Trích xuất text không đơn giản như xoá thẻ HTML

Một lỗi tư duy phổ biến là cho rằng HTML -> strip_tags() -> clean text là đủ.

Thực tế, một trang web trộn lẫn main content, navigation, cookie banner, comment, quảng cáo, related posts, footer, caption, code block, bảng và công thức. Một extractor quá đơn giản tạo ra thứ như:

Home About Pricing Log in
Transformer Transformer Transformer
Share Facebook Twitter LinkedIn
Cookie Settings Privacy Policy

Ngược lại, một extractor quá mạnh tay có thể xoá mất code block, bảng hoặc công thức - đúng những phần đáng học nhất trong một bài kỹ thuật.

Vì vậy extraction bản thân nó là một design decision. Dataset chuyên cho code hoặc math thường cần pipeline khác với generic web text: các pipeline dữ liệu math hiện đại còn cố bảo toàn MathJax, KaTeX hoặc MathML thay vì chuyển mọi thứ về plain text một cách mù quáng.

3.2 Chuẩn hoá là con dao hai lưỡi

Sau extraction, text thường được chuẩn hoá: Unicode normalization, whitespace normalization, sửa encoding, bỏ control character, dọn dòng, nhận diện ngôn ngữ.

Raw:
"LLM  training     is    interesting"

Normalized:
"LLM training is interesting"

Nhưng chuẩn hoá cần cẩn thận. Xoá quá mạnh có thể phá cấu trúc code, markdown, thơ, bảng, hoặc ngôn ngữ không dùng bảng chữ Latin. Với tiếng Việt, ép lowercase hay strip accent là cách nhanh nhất để làm hỏng dữ liệu. Trong Python và Markdown, whitespace là cú pháp thật.

3.3 Language ID là metadata, không phải bộ lọc

Nếu muốn corpus English-only, pipeline cần xác định ngôn ngữ của document hoặc từng paragraph. Nếu muốn multilingual model, language ID vẫn quan trọng - vì nó là thứ cho phép kiểm soát tỷ lệ sampling giữa các ngôn ngữ.

Document A -> English    -> 0.99
Document B -> Vietnamese -> 0.97
Document C -> Mixed      -> uncertain

Kết quả này đi thẳng vào bước data mixing ở phần sau. Nhãn sai ở đây sẽ thành tỷ lệ sai ở đó.

Kết thúc giai đoạn này, ta có một tập plain-text document sạch hơn raw crawl - nhưng vẫn còn rất xa một dataset đáng để tiêu compute.


04Có text chưa chắc là đáng học

Internet chứa rất nhiều text, nhưng phần lớn không hữu ích cho language modeling:

BUY NOW BUY NOW BUY NOW !!!
DISCOUNT DISCOUNT DISCOUNT
CLICK HERE CLICK HERE CLICK HERE

hoặc một trang được sinh tự động:

Cheap Hotel Paris
Cheap Hotel London
Cheap Hotel Bangkok
Cheap Hotel Tokyo
...

Nếu đưa nguyên trạng vào corpus, những pattern này tiêu tốn token budget và làm lệch chính distribution mà model học.

~/diagrams/dataset-cho-llm/quality-filtering.htmltương tác
mở riêng ↗
HìnhLàn trên là đường giữ lại: đo vài đặc trưng rẻ tiền, cắt bằng ngưỡng heuristic, rồi mới đưa phần sống sót qua quality classifier. Làn dưới là đường loại bỏ - và điểm quan trọng nằm ở ô cuối: mọi ngưỡng đều loại nhầm một phần dữ liệu tốt, thường là code, thơ, bảng và ngôn ngữ không phải tiếng Anh.
đường giữ lạivào training mixtuređường loại bỏ và loại nhầm

4.1 Heuristic: rẻ đến mức chạy được ở mọi quy mô

Ta có thể đo những đặc trưng rất đơn giản: độ dài document, tỷ lệ ký tự alphabet, tỷ lệ dấu câu, độ dài từ trung bình, tỷ lệ dòng lặp lại, tỷ lệ ký hiệu và chữ số, số cụm boilerplate, paragraph không có dấu câu, hoặc pattern URL và đuôi file không mong muốn.

if alphabetic_ratio < threshold:
    discard(document)

if repeated_line_ratio > threshold:
    discard(document)

Ưu điểm rất lớn: rẻ và chạy được ở quy mô hàng nghìn tỷ token. Nhược điểm là chúng dễ tạo bias. Một rule thiết kế cho prose tiếng Anh sẽ vô tình đánh rớt code, thơ, bảng, hoặc văn bản của một ngôn ngữ khác.

4.2 Model-based filtering

Thay vì chỉ dựa vào rule, ta train một classifier để dự đoán chất lượng document, học từ nhãn do con người hoặc một model khác gán.

Ví dụ nổi bật là FineWeb-Edu. Nhóm tác giả xây một classifier để tìm text có giá trị giáo dục trong FineWeb; theo dataset card và paper, lọc mạnh để giữ một subset khoảng 1.3T educational token đã cải thiện đáng kể nhiều benchmark về knowledge và reasoning so với train trên web data ít chọn lọc hơn.

DataComp-LM cũng đi tới một kết luận tương tự: model-based filtering là một thành phần rất hiệu quả trong việc tạo training set chất lượng cao. Trong benchmark đó, chiến lược curation tốt tạo ra model tốt hơn đáng kể mà không cần đơn giản tăng compute.

4.3 Ngưỡng là bài toán precision - recall

Giả sử Internet có 100 document, trong đó 30 tốt và 70 kém.

Một filter quá lỏng giữ 80 document: 30 tốt, 50 kém. Một filter quá chặt giữ 12 document: 12 tốt, 0 kém.

Filter thứ hai có chất lượng trung bình cao hơn, nhưng ta mất 18 document tốt. Ở quy mô nhỏ điều này không đáng ngại. Ở training horizon hàng nghìn tỷ token, nó dẫn tới thiếu dữ liệu unique và buộc model đọc lại cùng dữ liệu nhiều lần.

Nemotron-CC tập trung đúng vào trade-off này: thay vì loại bỏ phần lớn web một cách quyết liệt, họ kết hợp quality classifier, synthetic rephrasing và vài chiến lược khác để giữ được lượng dữ liệu lớn hơn cho long-horizon pretraining.

Không có threshold “đúng” cho mọi model. Ngưỡng phù hợp phụ thuộc model size, token budget, năng lực mục tiêu và độ dài training horizon.


05Vì sao không thể để model đọc cùng một thứ hàng nghìn lần

Web là một hệ thống sao chép khổng lồ. Một bài viết có thể xuất hiện trên website gốc, RSS mirror, news aggregator, blog scrape, cached page, forum trích lại và SEO clone.

Doc A: "Attention is all you need..."
Doc B: "Attention is all you need..."
Doc C: "Attention is all you need..."
Doc D: "Attention is all you need, and the Transformer..."

Có ba mức trùng lặp cần nghĩ đến: exact duplicate (giống hệt), near duplicate (gần giống), và repeated substring (một đoạn dài lặp lại bên trong những document khác nhau).

5.1 Duplication làm lệch phân bố, không chỉ tốn chỗ

Giả sử phân bố thật là mỗi topic một document. Nhưng Topic A bị copy 100 lần trên Internet:

Observed corpus
Topic A: 100 bản
Topic B:   1 bản
Topic C:   1 bản

Model nhận gradient từ Topic A nhiều hơn hẳn, dù sự lặp lại đó chỉ là artefact của web crawl chứ không phản ánh tầm quan trọng nào cả.

Nghiên cứu Deduplicating Training Data Makes Language Models Better của Lee và cộng sự cho thấy các dataset ngôn ngữ chứa lượng near-duplicate đáng kể; model train trên dữ liệu đã dedup sinh ra ít văn bản memorized hơn và có thể đạt chất lượng tương đương hoặc tốt hơn với ít training step hơn.

~/diagrams/dataset-cho-llm/dedup-minhash.htmltương tác
mở riêng ↗
HìnhLàn giữa là cách rẻ nhất: chuẩn hoá rồi hash toàn văn, trùng byte-for-byte thì bỏ ngay. Điểm mù của nó nằm ở làn dưới - chỉ lệch một dấu chấm là hash khác hẳn và tài liệu được coi như mới. Làn trên là cách dùng được ở quy mô web: cắt document thành n-gram shingle, nén thành MinHash signature, băm vào LSH bucket rồi chỉ so Jaccard trong từng bucket.
đường phát hiện gần trùnghash toàn vănđiểm mù của hash

5.2 Exact hash bỏ sót near-duplicate

Cách đơn giản nhất là chuẩn hoá rồi hash nội dung, và bỏ document nếu hash đã từng thấy. Nhưng:

A: Transformers are powerful language models.
B: Transformers are powerful language models!

Chỉ khác một ký tự, hash đã hoàn toàn khác. Exact hash vẫn rẻ và hữu ích: pipeline web của Dolma dedup chính xác theo URL, theo document và theo paragraph (bằng Bloom filter), và mỗi bước đều loại được một phần đáng kể dữ liệu. Nhưng mọi bản sao bị sửa nhẹ - đổi dấu câu, chèn timestamp, thay template - đều lọt qua. Đó là vùng near-duplicate, và cần một công cụ khác.

5.3 Near-duplicate: shingle, MinHash, LSH

Với near duplicate, ta biểu diễn document bằng các n-gram shingle. Ví dụ với 3-gram:

"large language model training"

large language model
language model training

Rồi đo độ overlap giữa hai tập shingle bằng Jaccard similarity - kích thước phần giao chia cho kích thước phần hợp.

Nhưng tính Jaccard cho mọi cặp document có độ phức tạp gần bậc hai theo số document, bất khả thi với hàng tỷ document. Vì vậy pipeline lớn dùng xấp xỉ: MinHash nén mỗi document thành một signature ngắn, LSH đưa các signature gần nhau vào cùng bucket, và ta chỉ kiểm tra kỹ trong từng bucket.

Các corpus hiện đại còn dedup ở nhiều cấp khác nhau: theo URL, theo document, theo paragraph, hoặc dedup toàn cục qua nhiều crawl snapshot.

5.4 Mạnh tay hơn không phải lúc nào cũng tốt hơn

Một kết quả thú vị từ FineWeb: chạy MinHash dedup toàn cục trên mọi crawl chỉ cải thiện benchmark rất ít, kém hẳn dedup riêng trong từng crawl. Khi so phần bị loại với phần được giữ ở một crawl cũ, nhóm tác giả thấy phần được giữ lại có chất lượng kém hơn: dedup toàn cục đã vô tình upsample dữ liệu chất lượng thấp. Giả thuyết của họ là lợi ích chính của dedup đến từ việc loại các cụm trùng rất lớn xuất hiện ở mọi crawl, còn dedup tiếp những cụm nhỏ (dưới khoảng 100 bản sao, tức cỡ số crawl) có thể làm giảm hiệu năng.

Duplication vừa là noise, vừa vô tình đóng vai trò như một sampling weight. Deduplication vì vậy cũng là một quyết định về distribution của training data.


06Contamination: khi model đã nhìn thấy đề thi

Deduplication xử lý trùng lặp bên trong corpus. Decontamination xử lý một vấn đề khác: dữ liệu dùng để đánh giá lại nằm trong dữ liệu dùng để train.

Giả sử ta dùng câu hỏi kiểu MMLU để chấm model:

Question: ...
A. ...
B. ...
C. ...
D. ...
Answer: C

Nếu một trang trên Internet đã đăng nguyên câu hỏi kèm đáp án, web crawl hoàn toàn có thể thu về.

~/diagrams/dataset-cho-llm/decontamination.htmltương tác
mở riêng ↗
HìnhHàng trên là đường rò rỉ: đề và đáp án được đăng lại công khai, crawl thu về, và nếu không kiểm tra thì chúng nằm luôn trong training corpus. Hàng dưới là đường đối chiếu - giữ bản gốc của eval set, dựng bảng tra n-gram hoặc Bloom filter, rồi so từng đoạn văn trước khi train. Phần lọt qua bước này chính là phần làm điểm benchmark bị thổi lên.
đường rò rỉbảng tra evaluation setkết quả đo được

Khi đó model có thể đạt score cao không phải vì generalization tốt hơn, mà vì đã thấy nội dung gần như y hệt.

Cần phân biệt bốn mức:

  • exact contamination - đúng nguyên văn example của benchmark;
  • near-duplicate contamination - câu hỏi bị reformat hoặc đổi vài chữ;
  • semantic contamination - cùng nội dung và logic đáp án dưới cách diễn đạt khác;
  • benchmark discussion contamination - lời giải, answer key hoặc bài phân tích đề xuất hiện online.

Decontamination có thể dùng exact matching, n-gram overlap, substring matching, hashing, Bloom filter hoặc near-duplicate detection. Dolma chẳng hạn thực hiện bằng cách tìm các paragraph của evaluation dataset xuất hiện trong training document. Mục tiêu không phải chứng minh contamination bằng 0 tuyệt đối, mà giảm những trường hợp rõ ràng làm điểm bị thổi lên.

Vấn đề khó hơn khi training corpus của model không được công khai - bên ngoài rất khó biết model đã nhìn thấy benchmark hay chưa. Đây là một trong những lý do evaluation của LLM hiện đại cần nhiều benchmark mới, dynamic evaluation và contamination analysis.

Một cách nghĩ đơn giản:

Benchmark score
      =
năng lực reasoning và kiến thức
      +
phần ghi nhớ có thể đã thấy
      +
nhiễu đo đạc

Không kiểm soát contamination thì ta không tách được ba phần đó ra khỏi nhau.


07Data mixture: model nên đọc thứ gì và đọc bao nhiêu

Giả sử sau cleaning ta có: web 8 TB, code 1 TB, papers 300 GB, books 200 GB, Wikipedia 100 GB.

Lựa chọn ngây thơ là nối tất cả rồi lấy mẫu theo kích thước tự nhiên. Khi đó web áp đảo gần như hoàn toàn:

Phân bố tự nhiên

Web        █████████████████████████████████
Code       ████
Papers     █
Books      █
Wikipedia  ▌

Nhưng đây không nhất thiết là phân bố tốt nhất cho model. Ta có thể chủ động tạo training mixture:

Training mixture

Web        ██████████████████████
Code       ████████
Papers     ████
Books      ████
Wikipedia  ██
~/diagrams/dataset-cho-llm/data-mixture.htmltương tác
mở riêng ↗
HìnhMỗi domain được gán một trọng số, tổng các trọng số bằng 1. Sampler dùng bộ trọng số đó để up hoặc downsample từng nguồn, tạo ra token stream mà model thật sự đọc theo thứ tự. Hai ô trên và dưới sampler là hai cách can thiệp vào bộ trọng số - dùng proxy model nhỏ để học nó thay vì đoán, và đổi tỷ lệ theo từng giai đoạn của training run.
đường trộn chínhtrọng số samplingcách can thiệp vào trọng số

7.1 Sampling weight

Giả sử các domain có trọng số w_web = 0.55, w_code = 0.20, w_books = 0.10, w_papers = 0.10, w_wiki = 0.05, với tổng bằng 1. Mỗi lần lấy dữ liệu, ta sample domain theo phân bố này. Những con số trên chỉ là minh hoạ, không phải một recipe chuẩn.

Nếu code chỉ chiếm khoảng 10% raw corpus (1 TB trên 9.6 TB ở ví dụ trên) nhưng ta muốn model coding mạnh hơn, có thể upsample code. Nếu một nguồn web quá lớn nhưng chất lượng trung bình, có thể downsample web.

Data mixture quyết định model nhận bao nhiêu gradient từ mỗi loại nội dung. Nói cách khác, nó là một hyperparameter của pretraining, không phải hệ quả tự nhiên của việc gom dữ liệu.

7.2 DoReMi: học mixture thay vì chọn bằng trực giác

DoReMi là một ví dụ quan trọng. Thay vì chọn domain weight hoàn toàn bằng cảm tính, phương pháp này dùng một proxy model nhỏ để ước lượng mixture tốt hơn, rồi mới dùng bộ trọng số đó để train model lớn. Trong paper, việc tối ưu mixture cải thiện average downstream performance và đạt chất lượng baseline với ít training step hơn trên các thí nghiệm cụ thể.

7.3 Mixture có thể đổi theo thời gian

Không bắt buộc toàn bộ training run dùng cùng một phân bố. Một curriculum đơn giản: giai đoạn đầu thiên về web rộng để phủ kiến thức, giai đoạn sau tăng tỷ lệ code, math và văn bản đã chọn lọc kỹ.


08Dataset lớn đến mức nào? Đếm bằng gì?

Một dataset có thể được mô tả bằng ba con số: 500 GB, 100 triệu document, 2 nghìn tỷ token. Ba con số này không tương đương nhau.

~/diagrams/dataset-cho-llm/dataset-scale.htmltương tác
mở riêng ↗
HìnhBytes đổi theo encoding, ngôn ngữ và lượng markup; document thì có cái dài 20 token, có cái dài 100.000 token. Chỉ sau khi qua tokenizer ta mới có đại lượng sát với compute là số training token. Cột phải là chỗ ngân sách compute bị chia làm hai - cho model size và cho token budget - và nhánh đi xuống là hệ quả khi tăng tham số mà không tăng dữ liệu tương ứng.
đường quy đổi về tokenđại lượng sát compute nhấtchia ngân sách và hệ quả

8.1 Bytes và documents đều không đủ

Hai corpus cùng 1 TB có thể có số token rất khác nhau nếu khác encoding, khác ngôn ngữ, khác tokenizer, chứa nhiều code hoặc JSON hoặc markup, hoặc khác mức nén. Còn document thì một cái có thể dài 20 token và cái khác dài 100.000 token.

Do đó trong language-model training, đại lượng sát compute nhất là số training token.

8.2 Model size, tokens và compute gắn với nhau

Scaling law không nói đơn giản rằng càng nhiều tham số càng tốt. Ta phải xem đồng thời số tham số, số training token và tổng compute.

Nghiên cứu Chinchilla là một mốc quan trọng: DeepMind cho thấy nhiều model trước đó bị undertrained theo nghĩa có quá nhiều tham số so với lượng token được dùng dưới cùng một compute budget. Chinchilla có 70B tham số nhưng được train trên khoảng 1.4T token và vượt Gopher 280B trong nhiều evaluation dù dùng compute tương đương.

Bài học không phải “mọi model phải theo đúng một tỉ lệ token trên tham số cố định”, vì recipe tối ưu còn phụ thuộc nhiều yếu tố. Bài học quan trọng hơn là:

Khi tăng model size mà không tăng lượng dữ liệu huấn luyện tương ứng, model có thể không tận dụng hết capacity của chính nó.

Vài mốc để hình dung quy mô: Meta cho biết Llama 3 được pretrain trên hơn 15T token; FineWeb giới thiệu một web corpus khoảng 15T token (các bản cập nhật sau đó đã nâng lên hơn 18.5T); Dolma công bố corpus khoảng 3T token.


09Từ documents đến token sequences

Đến đây ta đã có curated corpus:

Document A: "The cat is sleeping."
Document B: "Transformers use attention."
Document C: "Python is a programming language."

Tokenizer biến từng document thành ID, và pipeline chèn thêm token đặc biệt để giữ ranh giới:

Doc A -> [314, 982, 71, 402, 13, EOS]
Doc B -> [812, 119, 734, 13, EOS]
Doc C -> [501, 27, 84, 913, 42, 13, EOS]

Sau đó có thể xem chúng như một token stream logic:

[314, 982, 71, 402, 13, EOS,
 812, 119, 734, 13, EOS,
 501, 27, 84, 913, 42, 13, EOS,
 ...]

Đây là điểm nối trực tiếp giữa bài Tokenization và bài này. Tokenizer quyết định text -> token IDs. Data pipeline quyết định token IDs -> training sequences.

9.1 EOS không chỉ là một dấu chấm

EOS - End-of-Sequence hay End-of-Document - cho model biết một đơn vị nội dung đã kết thúc. Nó có hai vai trò khác nhau tuỳ pipeline:

  1. trở thành một target mà model học dự đoán;
  2. đánh dấu boundary để attention hoặc position ID có thể được reset hoặc mask nếu recipe yêu cầu.

Không phải implementation nào cũng dùng boundary theo cùng một cách. Vì vậy cần tách token boundary khỏi attention boundary - có EOS không tự động có nghĩa là attention bị chặn ở đó.

9.2 Token stream không tồn tại như một file khổng lồ

Sơ đồ “nối tất cả token lại” chủ yếu là mô hình tư duy. Ở production-scale pipeline, dữ liệu được lưu theo shard và có index mapping từ training sample đến document id, start offset và end offset.

Megatron-LM chẳng hạn xây các mapping như document index, sample index và shuffle index để lấy sample hiệu quả từ indexed dataset, thay vì tạo vật lý một file token stream duy nhất cho mọi thứ.


10Chunking, biên document và chuyện gọi là “packing”

Giả sử model được train với sequence_length = 8 và token stream logic là:

[12, 81, 53, 9, EOS, 72, 19, 44, 68, EOS, 101, 91, ...]

Ta cắt thành:

Sample 1: [12, 81, 53, 9, EOS, 72, 19, 44]
Sample 2: [68, EOS, 101, 91, ...]
~/diagrams/dataset-cho-llm/packing-and-masks.htmltương tác
mở riêng ↗
HìnhHàng trên là cách pad từng document cho đủ độ dài: GPU vẫn tính trên các vị trí PAD nhưng không học được gì từ chúng. Hàng giữa là cách ghép document rồi cắt theo độ dài cố định, gần như không lãng phí. Hàng dưới là cái giá đi kèm - khi một sequence chứa nhiều document, phải chọn giữa để attention chạy suốt và mask lại tại biên.
đường ghép và cắtmask theo biên documentchỗ lãng phí và context lạ

10.1 Vì sao không pad từng document

Nếu mỗi document ngắn hơn sequence length và ta pad riêng:

Doc A: [A A A EOS PAD PAD PAD PAD]
Doc B: [B B EOS PAD PAD PAD PAD PAD]

GPU vẫn thực hiện phép tính trên các vị trí PAD, nhưng những vị trí đó không đóng góp nội dung. Ghép nhiều document vào cùng một sequence độ dài cố định cho utilization cao hơn hẳn:

[A A A EOS B B EOS C]

Một lưu ý về thuật ngữ: trong pretraining, người ta thường nói tới concatenating documents and chunking the token stream. Trong supervised fine-tuning, “sequence packing” thường chỉ kỹ thuật ghép nhiều training example ngắn vào một sequence để giảm padding waste, đồng thời giữ metadata và mask để các example không ảnh hưởng lẫn nhau. Hai ý tưởng rất gần nhau về mục tiêu compute utilization, nhưng không nên giả định implementation giống hệt.

10.2 Hai lựa chọn khi một sequence chứa nhiều document

Nếu Sample 1 chứa Doc A: [A1 A2 A3 EOS] rồi Doc B: [B1 B2 B3 B4], có hai hướng.

Cho phép attention xuyên biên. B1 có thể attend vào A1 A2 A3 EOS. Model nhận context không liên quan giữa hai document, nhưng implementation đơn giản và tận dụng được token stream liên tục.

Mask attention qua biên document. Ma trận attention về ý tưởng trở thành hai khối tách rời:

      A1 A2 A3 E  B1 B2 B3 B4
A1    x
A2    x  x
A3    x  x  x
E     x  x  x  x
B1                x
B2                x  x
B3                x  x  x
B4                x  x  x  x

Llama 3 là một ví dụ công khai: Meta cho biết model được train trên sequence 8.192 token và dùng mask để self-attention không đi qua document boundary.

Điểm cần nhớ theo cả hai chiều:

Một fixed-length training sequence không nhất thiết tương ứng với một document. Và một document dài có thể bị chia qua nhiều training sequence.


11Một training sample của causal LLM trông như thế nào?

Đây là bước cuối trước khi dữ liệu đi vào Transformer. Giả sử sequence token là:

[BOS, I, love, machine, learning, EOS]

Mục tiêu causal language modeling là học xác suất của cả chuỗi bằng tích các xác suất có điều kiện: mỗi token được dự đoán từ những token đứng trước nó. Về mặt khái niệm:

Input:   [BOS, I,    love,    machine, learning]
Target:  [I,   love, machine, learning, EOS]
~/diagrams/dataset-cho-llm/training-sample.htmltương tác
mở riêng ↗
HìnhĐọc từ trên xuống theo thời gian. Data loader cắt một chuỗi độ dài cố định từ token stream và gắn EOS ở biên; sample đó đi vào embedding dưới dạng chỉ mục chứ không phải chữ; Transformer trả về logits trên toàn bộ vocabulary tại mỗi vị trí; loss so chúng với labels đã dịch một bước rồi đẩy gradient ngược lại weights.
đường forwardlogits và lossđường quay lại weights

11.1 input_ids

Sau tokenizer:

input_ids = [1, 40, 912, 7310, 4481, 2]

Model không thấy chữ I, love, machine. Nó thấy integer ID rồi dùng embedding table ở layer đầu để chuyển chúng thành vector.

11.2 labels và chuyện shift

Có hai cách nói thường thấy, và cần phân biệt giữa khái niệm với implementation. Về khái niệm, input = x[0 : T-1] và target = x[1 : T]. Trong nhiều implementation, labels được truyền vào gần như trùng input_ids, rồi model hoặc loss function tự shift logits và labels bên trong.

Vì vậy khi thấy:

labels = input_ids

không có nghĩa model học “dự đoán chính token hiện tại”. Việc dịch một bước vẫn xảy ra trong quá trình tính next-token loss.

11.3 Ba loại mask trong cùng một sample

Ngoài document-boundary masking, decoder-only Transformer còn cần causal masking - token ở vị trí t chỉ được nhìn các vị trí nhỏ hơn hoặc bằng t:

      t1 t2 t3 t4
 t1   x
 t2   x  x
 t3   x  x  x
 t4   x  x  x  x

Nếu token ở vị trí 2 được phép nhìn token ở vị trí 3 trong lúc train, bài toán next-token prediction bị leakage.

Loại thứ ba là loss mask: không phải mọi vị trí đều phải đóng góp vào loss. Với [real real real PAD PAD] ta dùng mask [1, 1, 1, 0, 0]. Tương tự, một recipe có thể quyết định bỏ loss ở một số special token hoặc boundary token.

Một training sample thực tế vì vậy có thể được mô tả khái niệm như:

{
  "input_ids":      [12, 81, 53, 9, 72, 19, 44, 68],
  "labels":         [12, 81, 53, 9, 72, 19, 44, 68],
  "attention_mask": "causal + optional document boundaries",
  "loss_mask":      [1, 1, 1, 1, 1, 1, 1, 1]
}

Tuỳ framework, format cụ thể sẽ khác. Nhưng bản chất không đổi: text đi vào tokenizer, ra input_ids, qua embedding thành vector, qua Transformer thành logits, và cross-entropy so logits với next-token target.


12Đọc một dataset nghìn tỷ token

Một dataset vài GB có thể load tương đối đơn giản. Vài nghìn tỷ token thì không. Ta không thể viết dataset = load_everything_into_ram() và mong nó hoạt động.

~/diagrams/dataset-cho-llm/data-loading-at-scale.htmltương tác
mở riêng ↗
HìnhCorpus đã token hoá được chia thành nhiều shard nhị phân để nhiều worker đọc song song và retry được khi lỗi. Memory mapping cho phép chạm đúng vùng dữ liệu đang cần mà không nạp cả file. Shuffle chạy ở nhiều cấp rồi ghi vào index mapping, để loader biết sample thứ i nằm ở document nào và offset nào, và để checkpoint có thể resume đúng chỗ đã dừng.
đường dữ liệu tới GPUindex và shardđọc theo vùng và resume

12.1 Sharding và streaming

Dataset được chia thành nhiều file nhỏ hơn:

dataset/
├── shard_00000.bin
├── shard_00001.bin
├── ...
└── shard_99999.bin

Lợi ích: dễ phân phối trên object storage, nhiều worker đọc song song, dễ retry khi lỗi, dễ shuffle theo shard, và không cần một file khổng lồ duy nhất. Dữ liệu được đọc tuần tự từ disk hoặc remote storage thay vì load toàn bộ vào RAM.

Với token ID đã preprocess, memory-mapped binary file cho phép process truy cập vùng dữ liệu cần thiết như một array lớn mà không phải đọc cả file vào memory. Megatron-LM hỗ trợ indexed dataset, mmap và các mapping để xác định sample boundary - một ví dụ điển hình của việc data pipeline cũng phải scale cùng training system.

12.2 Shuffle ở quy mô này không phải random.shuffle

Ta vẫn muốn tính ngẫu nhiên, nhưng không thể gọi random.shuffle(all_examples) trên hàng nghìn tỷ token. Các hệ thống shuffle ở nhiều cấp: trộn document, trộn shard, trộn chỉ số sample, trộn trong buffer.

Một mapping có thể trông như:

sample 0 -> document 918, offset 1200
sample 1 -> document  41, offset  320
sample 2 -> document 551, offset 7700

Nhờ đó training có thứ tự sample ngẫu nhiên mà không cần ghi lại toàn bộ dataset.

12.3 Distributed data loading

Nếu có hàng nghìn GPU, mỗi worker cần nhận phần dữ liệu phù hợp và tránh việc tất cả GPU train trên cùng sample cùng lúc. Pipeline phải bảo đảm: khả năng tái lập, resume xác định sau khi crash, tải cân bằng, throughput đủ để GPU không phải chờ dữ liệu, và theo dõi được đã tiêu thụ bao nhiêu token.

Ở đây ta bắt đầu chạm sang chủ đề training LLM từ đầu, một bài dự kiến ở phần sau của series, vì data loader, distributed training, optimizer và checkpointing tương tác chặt với nhau. Điều cần nhớ là ở quy mô LLM, dữ liệu không còn là một CSV load vào RAM mà là một bài toán hệ thống phân tán.


13Train, validation, benchmark và chuyện leakage

Trong supervised learning thông thường ta chia dataset thành train, validation và test. Trong pretraining LLM ý tưởng vẫn còn nhưng phức tạp hơn.

Training corpus là phần model trực tiếp học gradient từ đó. Validation corpus dùng để theo dõi loss hoặc perplexity trên dữ liệu held-out và phát hiện overfitting hay distribution mismatch. Benchmark như MMLU, ARC hay HumanEval thường là external evaluation set, không phải một random 10% split của pretraining corpus - và điều quan trọng là chúng phải càng độc lập với training data càng tốt.

13.1 Random split vẫn có thể leak

Giả sử Doc A là bài gốc và Doc B là bản copy. Nếu random split đưa Doc A vào train và Doc B vào validation, thì validation không thật sự độc lập.

Do đó deduplication cần xảy ra trước khi split, hoặc ít nhất phải được kiểm tra xuyên qua các split.

Một số project còn dùng temporal split hoặc source-based split khi phù hợp:

Train:      document trước ngày X
Validation: document sau ngày X

Cách này đo generalization theo thời gian tốt hơn, nhưng nó cũng thay đổi distribution và không phải lúc nào cũng phù hợp.


14Privacy, bản quyền, bias và provenance

Data pipeline không chỉ tối ưu benchmark score. Một web-scale corpus có thể chứa dữ liệu cá nhân, email, số điện thoại, tác phẩm có bản quyền, toxic content, hate speech, hướng dẫn nguy hiểm, secret và API key, cùng với mức đại diện văn hoá rất lệch.

14.1 PII và secret

PII - Personally Identifiable Information - có thể gồm email, số điện thoại, IP address và nhiều loại identifier khác. Pipeline có thể detect -> mask hoặc detect -> drop document. Dolma mô tả việc dùng regex được viết cẩn thận (thay vì model-based detector, vốn quá chậm ở quy mô của họ) để phát hiện và mask một số PII như email, số điện thoại và IP address trong pipeline của họ.

Repository public cũng có thể vô tình chứa:

API_KEY=...
PRIVATE_TOKEN=...
PASSWORD=...

Dataset code vì vậy cần secret scanning và license-aware filtering, chứ không chỉ text cleaning.

14.2 Bản quyền và license

“Publicly accessible” không đồng nghĩa “không có bản quyền”. Các câu hỏi cần trả lời được: dữ liệu đến từ đâu, license gì, có được redistribute không, có cơ chế opt-out không, và có provenance record không. Đây là lý do metadata ngày càng quan trọng trong dataset engineering.

14.3 Bias đến từ cả dữ liệu lẫn bộ lọc

Model học distribution của dữ liệu. Nếu 90% của một corpus gọi là multilingual thực tế là tiếng Anh, model sẽ không có năng lực đều nhau giữa các ngôn ngữ. Nếu một nghề nghiệp, quốc gia, giới tính hay cộng đồng được thể hiện lệch trong data, model có thể hấp thụ đúng pattern đó.

Đáng chú ý hơn: filtering cũng tạo ra bias mới. Một classifier “quality” được train trên văn phong kiểu bách khoa có thể hạ điểm ngôn ngữ đời thường, phương ngữ, hoặc nội dung từ các cộng đồng ít được đại diện.

Data quality không phải một khái niệm hoàn toàn khách quan. Nó phụ thuộc mục tiêu của model và tiêu chí của người xây dataset.


15Synthetic data: dataset không nhất thiết chỉ do người viết

Một xu hướng quan trọng của LLM hiện đại là dùng model để tạo hoặc biến đổi chính dữ liệu huấn luyện. Synthetic data đặc biệt phổ biến trong post-training, nhưng ngày càng xuất hiện cả trong pretraining và continued pretraining. Nemotron-CC là ví dụ rõ: corpus này kết hợp real web token với synthetic rephrasing để tăng lượng dữ liệu chất lượng cao dùng được cho long-horizon training.

Giả sử raw web text là:

"Photosynthesis is the process plants use to make food."

Teacher model có thể tạo một phiên bản sạch và giàu thông tin hơn:

"Photosynthesis is a biological process in which plants convert light
energy into chemical energy, primarily in chloroplasts."
~/diagrams/dataset-cho-llm/synthetic-lifecycle.htmltương tác
mở riêng ↗
HìnhBăng trên là năm chặng của một mẫu synthetic: từ dữ liệu thật, qua teacher model, qua bước chấm điểm và verify, qua khử trùng lặp với dữ liệu thật, rồi mới chiếm chỗ trong token budget. Băng dưới là chỗ vòng lặp có thể hỏng - mẫu không qua verify quay lại để sinh lại, còn nếu vòng nào cũng học text do máy sinh mà không kiểm soát thì diversity co hẹp dần.
đường vào training corpusbước chấm điểm và verifychỗ vòng lặp có thể hỏng

Synthetic data có thể giúp chuẩn hoá writing quality, tạo thêm reasoning data, tăng ví dụ code và math, bổ sung dữ liệu cho domain ít tài nguyên, viết lại nội dung web nhiễu, và tạo curriculum có độ khó kiểm soát được.

15.1 Nhưng nó không phải bữa trưa miễn phí

Nếu teacher sai, mẫu synthetic sai, và student học lại đúng lỗi đó. Nếu teacher có bias, distribution sinh ra mang bias đó, và student thừa hưởng. Nếu toàn bộ ecosystem train lặp đi lặp lại trên text do model sinh mà không có quality control, distribution có thể co hẹp và mất diversity.

Do đó synthetic pipeline thường cần đủ năm bước, không chỉ bước đầu:

generation
   ↓
quality scoring
   ↓
verification
   ↓
deduplication
   ↓
sampling

15.2 Vòng lặp dữ liệu

Có một vòng lặp ngày càng rõ: model tốt hơn cho ra quality classifier tốt hơn và teacher sinh dữ liệu tốt hơn; dữ liệu tốt hơn lại tạo ra model tốt hơn ở vòng sau. Bước verify chính là thứ giữ cho vòng lặp đó không tự đầu độc.


16Những hiểu lầm phổ biến

Hiểu lầm Thực tế
“Dataset càng lớn thì model càng tốt” more tokens không đồng nghĩa more useful tokens; token tăng từ spam trùng lặp chỉ tăng chi phí compute
“Dataset là tập các câu” corpus chứa document dài, còn model train trên fixed-length token sequence
“Tokenize xong là train được” còn phải quyết biên document, sequence length, sample index, shuffle, mask và batching
“Deduplicate chỉ để tiết kiệm storage” nó thay đổi training distribution, mức memorization và độ tin cậy của evaluation
“Web data luôn low quality” RefinedWeb và FineWeb cho thấy web được lọc tốt là pretraining source rất mạnh
“High quality luôn hơn diversity” filter quá mạnh tạo dataset nhỏ, đồng nhất, thiếu long-tail knowledge
“Synthetic data chỉ để instruction tuning” nó ngày càng xuất hiện trong pretraining corpora và data curation
“Một document là một training sample” một document dài trải ra nhiều sample, một sample có thể chứa nhiều document
“Có EOS là attention tự dừng ở đó” boundary token và boundary mask là hai quyết định độc lập
“Benchmark cao nghĩa là generalization tốt” chưa chắc, nếu không kiểm soát contamination

Dataset là phần kiến trúc hành vi không nằm trong file weights

Khi nhìn một LLM, ta thấy hàng tỷ tham số. Nhưng mỗi tham số được cập nhật bởi gradient, và gradient đến từ những token mà data pipeline đã chọn cho model nhìn thấy. Chuỗi nguyên nhân thật là: nguồn dữ liệu, rồi quyết định curation, rồi training distribution, rồi gradient, rồi mới đến năng lực, bias và hành vi ghi nhớ của model.

Điều đó khiến dataset trở thành một phần của “kiến trúc hành vi” của model, dù nó không nằm trong file weights sau khi training kết thúc.

Những gì nên mang theo:

  • corpus nói về nội dung, training sample nói về đơn vị tính loss - hai chuyện khác nhau;
  • lọc chất lượng là bài toán precision - recall, không có ngưỡng đúng cho mọi model;
  • deduplication thay đổi distribution chứ không chỉ tiết kiệm dung lượng;
  • decontamination là điều kiện để benchmark còn nói lên điều gì đó;
  • data mixture là hyperparameter của pretraining, và có thể học thay vì đoán;
  • số token là đại lượng sát compute nhất, không phải GB hay số document;
  • một sequence độ dài cố định không tương ứng một document, theo cả hai chiều;
  • bộ lọc nào cũng mang bias của người thiết kế ra nó.

Và cuối cùng, thứ model nhận không còn là:

"The Transformer architecture uses self-attention..."

mà là thứ gần hơn với:

input_ids = [8812, 13, 7021, 421, 993, 511, ...]
labels    = [8812, 13, 7021, 421, 993, 511, ...]

cùng causal mask, thông tin biên document và loss mask thích hợp. Đó là điểm chuyển giao từ data engineering sang model computation.

Bước tiếp theo là đi thêm một chặng nữa: những integer token ID đó được biến thành vector như thế nào, và vì sao khoảng cách giữa hai vector lại mang được ý nghĩa. Đó là chỗ embedding bắt đầu, chủ đề của bài tiếp theo trong series.


Tài liệu tham khảo

Trương Đức Dũng

AI Engineer - Hà Nội

Tập trung vào LLM, RAG và các hệ thống AI cần chạy ổn định trong môi trường vận hành thật.