RAG

Chunking Trong RAG Là Gì? Vì Sao Cắt Sai Có Thể Làm Retrieval Thất Bại

Chunking quyết định những đơn vị tri thức mà hệ thống RAG thực sự có thể tìm kiếm. Bài viết đi từ chunk size, overlap và structure-aware splitting tới Parent-Child, Semantic Chunking, Contextual Retrieval và Late Chunking.

Minh họa cho bài Chunking Trong RAG Là Gì? Vì Sao Cắt Sai Có Thể Làm Retrieval Thất Bại
Mục lục bài viết

Bạn xây một hệ thống RAG cho tài liệu nội bộ. Người dùng hỏi một câu mà bạn biết chắc câu trả lời nằm trong knowledge base. Bạn mở file PDF, Ctrl + F, và tìm thấy đúng đoạn đó trong vài giây.

Nhưng hệ thống RAG lại trả lời:

“Tôi không tìm thấy thông tin này trong tài liệu được cung cấp.”

Phản ứng đầu tiên thường là nghi ngờ embedding model chưa đủ tốt. Có thể vector database cấu hình sai. Có thể cần hybrid search. Có thể phải thêm reranker. Hoặc đơn giản hơn: tăng top_k từ 5 lên 20 để retriever lấy nhiều dữ liệu hơn.

Tất cả đều có thể đúng.

Nhưng cũng có một khả năng ít được chú ý hơn: ngay từ lúc ingest dữ liệu, bạn đã biến đoạn thông tin đúng thành một retrieval unit tồi.

Giả sử tài liệu gốc viết:

Text
## Chính sách hoàn tiền

Khách hàng được hoàn toàn bộ chi phí nếu hủy dịch vụ
trong vòng 7 ngày kể từ thời điểm kích hoạt.

Ngoại lệ: chính sách này không áp dụng cho gói Enterprise
đã sử dụng dịch vụ triển khai riêng.

Nếu chunker cắt đúng giữa các ý:

Text
Chunk A:
## Chính sách hoàn tiền

Khách hàng được hoàn toàn bộ chi phí nếu hủy dịch vụ

Chunk B:
trong vòng 7 ngày kể từ thời điểm kích hoạt.

Ngoại lệ: chính sách này không áp dụng cho gói Enterprise

Chunk C:
đã sử dụng dịch vụ triển khai riêng.

thì không chunk nào còn biểu diễn đầy đủ câu hỏi:

“Gói Enterprise có được hoàn tiền trong 7 ngày không?”

Embedding model không được nhìn thấy “sự thật hoàn chỉnh”. Vector database cũng không thể tìm lại một đơn vị thông tin chưa từng được tạo ra đúng cách.

Đó là lý do chunking không chỉ là bước chia văn bản cho vừa context window.

Trong RAG, chunking chính là quá trình quyết định:

Đơn vị tri thức nào sẽ tồn tại trong hệ thống retrieval.

Và nếu đơn vị đó được thiết kế sai, mọi thứ phía sau — embedding, vector search, reranking và cuối cùng là LLM — đều phải làm việc với một biểu diễn đã mất thông tin ngay từ đầu.


1. Chunking thực chất đang quyết định điều gì?

Một pipeline RAG đơn giản thường được mô tả như sau:

Text
                 INGESTION

Raw Document
     │
     ▼
┌──────────────┐
│    Parser    │
└──────┬───────┘
       │
       ▼
┌──────────────┐
│   Chunking   │
└──────┬───────┘
       │
       ▼
┌──────────────┐
│  Embedding   │
└──────┬───────┘
       │
       ▼
┌──────────────┐
│ Vector Index │
└──────────────┘


                 QUERY

User Question
     │
     ▼
Query Embedding
     │
     ▼
Similarity Search
     │
     ▼
Relevant Chunks
     │
     ▼
LLM
     │
     ▼
Answer

Nhìn vào diagram này, chunking có vẻ chỉ là một bước preprocessing nằm giữa parser và embedding model.

Nhưng ảnh hưởng của nó lớn hơn nhiều.

Sau khi tài liệu được chunk, vector database thường không còn “tìm kiếm tài liệu” theo nghĩa ban đầu nữa. Nó tìm kiếm các chunk đã được tạo ra từ tài liệu.

Research ban đầu cũng mô tả đúng điểm này: chunk là những đơn vị thông tin nhỏ có thể truy xuất độc lập, được embedding và đưa vào vector store; cách đặt ranh giới chunk sẽ quyết định nội dung nào thực sự trở thành candidate khi truy vấn xảy ra.

Giả sử một document dài 10.000 token được chia thành 20 chunk.

Vector database không nhìn thấy:

Text
Document X

như một thực thể ngữ nghĩa duy nhất.

Nó chủ yếu nhìn thấy:

Text
X_chunk_01
X_chunk_02
X_chunk_03
...
X_chunk_20

với mỗi chunk có embedding riêng.

Với dense retrieval điển hình, quá trình tìm kiếm có thể hình dung đơn giản là:

Text
query
  │
  ▼
Embedding(query)
  │
  ▼
similarity(
    query_vector,
    chunk_vector
)
  │
  ▼
Top-K chunks

Điều đó tạo ra một implication rất quan trọng:

Chunking thay đổi không gian tìm kiếm trước cả khi vector search bắt đầu.

Một embedding model rất mạnh vẫn không thể tạo representation tốt cho một đoạn văn đã mất chủ thể, mất định nghĩa hoặc bị cắt ngang giữa hai ý.

Một reranker rất mạnh cũng không thể xếp hạng một candidate hoàn chỉnh nếu candidate đó chưa bao giờ tồn tại.

Các component phía sau đôi khi có thể che bớt lỗi chunking — chẳng hạn hai chunk liền nhau vô tình cùng được retrieve — nhưng không nên xem đó là cơ chế sửa lỗi đáng tin cậy.


2. Bài toán thật sự không phải “chunk bao nhiêu token?”

Khi bắt đầu xây RAG, một trong những câu hỏi phổ biến nhất là:

“Chunk size tốt nhất là bao nhiêu? 256, 512 hay 1.024 token?”

Câu hỏi này hợp lý, nhưng nó đặt vấn đề hơi sai.

Không tồn tại một con số chunk size tối ưu cho mọi dataset, bởi vì chunking đang phải cân bằng hai mục tiêu đối nghịch:

Text
Chunk nhỏ
   │
   ├── retrieval chính xác hơn
   │
   └── nhưng dễ thiếu context
              ▲
              │
        TRADE-OFF
              │
              ▼
Chunk lớn
   │
   ├── context đầy đủ hơn
   │
   └── nhưng retrieval dễ bị loãng

Đây có thể xem là trade-off giữa retrieval granularity và context completeness.

Khi chunk quá nhỏ

Hãy xem đoạn sau:

Text
Redis hỗ trợ eviction policy để quyết định key nào
sẽ bị loại bỏ khi bộ nhớ đạt giới hạn.

Một trong số đó là allkeys-lru, trong đó Redis có thể
loại bỏ các key ít được sử dụng gần đây nhất.

Nếu chunker tạo:

Text
Chunk A:
Một trong số đó là allkeys-lru.

Chunk B:
Các key ít được sử dụng gần đây nhất sẽ bị loại bỏ.

query:

Text
allkeys-lru hoạt động như thế nào?

có thể retrieve Chunk A rất tốt vì chứa exact term allkeys-lru.

Nhưng chunk đó lại không chứa câu trả lời.

Chunk B chứa câu trả lời nhưng không chứa tên policy.

Bạn có retrieval precision ở cấp từ khóa, nhưng mất semantic completeness ở cấp kiến thức.

Khi chunk quá lớn

Bây giờ thử hướng ngược lại.

Một chunk 2.000 token chứa:

Text
Redis installation
Redis persistence
Redis Sentinel
Redis eviction
Redis Cluster
Redis Streams
Redis security

Query chỉ hỏi:

Text
allkeys-lru hoạt động như thế nào?

Embedding của chunk phải nén rất nhiều chủ đề vào một representation duy nhất. Phần liên quan đến eviction chỉ chiếm một tỷ lệ nhỏ trong tổng nội dung.

Ngay cả khi chunk được retrieve, bạn còn đưa hàng nghìn token không liên quan vào generation context.

Vấn đề vì thế xuất hiện ở hai nơi:

Text
LARGE CHUNK

Embedding stage:
nhiều semantic signals
        ↓
representation kém tập trung

Generation stage:
nhiều irrelevant tokens
        ↓
signal-to-noise ratio thấp

Chunk size vì vậy không phải một hyperparameter độc lập.

Nó liên quan trực tiếp đến:

  • cấu trúc của tài liệu;
  • độ dài của một “ý hoàn chỉnh”;
  • loại query mà người dùng thường đặt;
  • embedding model;
  • retrieval strategy;
  • top_k;
  • reranking;
  • context budget của generation model.

Overlap giúp, nhưng không miễn phí

Một giải pháp quen thuộc là cho các chunk chồng lấn lên nhau:

Text
Document:

AAAA BBBB CCCC DDDD EEEE FFFF


Without overlap:

[AAAA BBBB CCCC]
               [DDDD EEEE FFFF]


With overlap:

[AAAA BBBB CCCC DDDD]
           [CCCC DDDD EEEE FFFF]

Overlap giúp giảm khả năng một concept bị phá vỡ đúng tại boundary.

Nhưng overlap càng lớn, bạn càng tạo nhiều dữ liệu trùng lặp.

Điều đó có thể dẫn tới:

Text
1 concept
   ↓
nhiều chunks gần giống nhau
   ↓
retriever lấy cả 3
   ↓
top-k bị lãng phí
   ↓
LLM nhận context lặp lại

Do đó, chunk_overlap = 20% không phải một quy luật tự nhiên.

Nó chỉ là một configuration cần được đo lường giống như bất kỳ hyperparameter retrieval nào khác.


3. Chunking sai phá RAG như thế nào?

Có ba failure mode đặc biệt quan trọng vì chúng xuất hiện từ rất sớm trong pipeline nhưng chỉ biểu hiện thành câu trả lời sai ở rất xa phía downstream.

Semantic fragmentation: một kiến thức bị biến thành nhiều mảnh không đầy đủ

Đây là trường hợp boundary nằm giữa một concept.

Ví dụ:

Text
Original:

OAuth authorization code chỉ nên được trao đổi
từ backend với authorization server. Khi sử dụng
PKCE, client tạo code_verifier và gửi code_challenge
trong authorization request.

Chunker cắt:

Text
Chunk A:
OAuth authorization code chỉ nên được trao đổi
từ backend với authorization server. Khi sử dụng

Chunk B:
PKCE, client tạo code_verifier và gửi code_challenge
trong authorization request.

Query:

Text
PKCE dùng code_verifier như thế nào?

Chunk B vẫn có khả năng match.

Nhưng nếu query là:

Text
PKCE thay đổi authorization code flow ở đâu?

relationship giữa hai nửa trở nên quan trọng hơn.

Chunking đã làm mất chính relationship đó.

Orphaned context: chunk còn câu nhưng mất chủ thể

Đây là lỗi khó phát hiện hơn.

Text
Chunk:

Phương pháp này yêu cầu embedding toàn bộ document
trước khi pooling từng chunk.

Đoạn trên hoàn toàn đúng.

Nhưng:

“Phương pháp này” là phương pháp nào?

Con người đọc đoạn trước có thể biết đó là Late Chunking.

Embedding model xử lý riêng chunk này thì không nhất thiết biết.

LLM retrieve riêng chunk này cũng không biết.

Đây là lý do các đại từ và reference như:

Text
nó
phương pháp này
công ty đó
mô hình trên
trường hợp trước
the former
this approach
the company

đặc biệt nguy hiểm trong retrieval unit độc lập.

Structured-data destruction: đúng text nhưng sai structure

Với Markdown, HTML, source code, table hoặc JSON, semantic boundary không chỉ nằm ở dấu chấm.

Ví dụ bảng:

Text
| Model | Context Window | Price |
|-------|----------------|-------|
| A     | 128K           | $X    |
| B     | 1M             | $Y    |

Nếu chunker chia:

Text
Chunk A:
| Model | Context Window | Price |
|-------|----------------|-------|
| A     | 128K

Chunk B:
| $X |
| B | 1M | $Y |

các token vẫn tồn tại.

Nhưng relationship:

Text
Model A
   ↕
128K
   ↕
$X

đã bị phá hủy.

Điều tương tự xảy ra khi cắt giữa:

Text
function
class
JSON object
Markdown section
HTML table
legal clause + exception

Đó là lý do chunking phải hiểu structure, không chỉ length.

Còn “Lost in the Middle” thì sao?

Một lý do khác khiến việc đơn giản tăng chunk size hoặc retrieve thật nhiều context không phải lúc nào cũng hiệu quả là khả năng sử dụng long context của LLM không đồng đều.

Nghiên cứu Lost in the Middle của Liu và cộng sự cho thấy nhiều language model đạt hiệu suất tốt hơn khi thông tin quan trọng nằm gần đầu hoặc cuối context và có thể suy giảm khi thông tin cần thiết nằm ở giữa một context dài.

Điểm cần phân biệt là:

Lost in the Middle không phải một “lỗi chunking”.

Nó là một vấn đề về cách model sử dụng long context.

Nhưng chunking tồi có thể làm vấn đề nặng hơn nếu retrieval pipeline gửi quá nhiều đoạn nhiễu vào prompt và chôn evidence quan trọng giữa chúng.

Vì vậy:

Text
Bigger chunks
        +
Higher top_k
        +
No reranking
        ↓
Very large context
        ↓
More irrelevant information
        ↓
Harder evidence selection

không phải lúc nào cũng tốt hơn.


4. Ba baseline chunking strategy nên hiểu trước

Trước khi dùng semantic chunking, agentic chunking hay bất kỳ kỹ thuật phức tạp nào, có ba baseline quan trọng hơn nhiều.

Strategy Boundary dựa trên Điểm mạnh Failure chính
Fixed-size ký tự/token đơn giản, nhanh, predictable cắt giữa concept
Recursive paragraph → line → word... giữ natural boundary tốt hơn vẫn chưa hiểu semantics thực sự
Structure-aware heading, section, AST, HTML node... giữ logical structure phụ thuộc parser/document type

Fixed-size chunking

Đây là dạng:

Text
mỗi 500 token
→ tạo một chunk
→ overlap 50 token
→ tiếp tục

Ưu điểm lớn nhất của nó là tính dễ dự đoán.

Bạn có thể ước lượng tương đối rõ:

Text
document tokens
        ÷
chunk size
        ≈
number of chunks

Ingestion nhanh, implementation đơn giản và dễ dùng làm baseline benchmark.

Nhược điểm cũng rất rõ: thuật toán không biết 500 token thứ 500 có nằm giữa một đoạn giải thích hay không.

Fixed-size vì thế đặc biệt phù hợp với vai trò baseline để so sánh, hơn là assumption rằng đây chắc chắn là kiến trúc cuối cùng.

Recursive splitting

Recursive splitting tốt hơn ở chỗ nó cố tìm boundary tự nhiên trước khi buộc phải cắt cứng.

Một mental model đơn giản:

Text
Document
   │
   ├── Có thể split theo paragraph?
   │           │
   │          Yes
   │           ▼
   │       paragraphs
   │
   └── paragraph vẫn quá lớn?
               │
              Yes
               ▼
            split nhỏ hơn
               │
               ▼
             lines
               │
               ▼
             words

LangChain hiện vẫn khuyến nghị RecursiveCharacterTextSplitter như một điểm khởi đầu cho generic text. Splitter mặc định thử các separator theo thứ tự như paragraph, newline, whitespace rồi mới đến boundary nhỏ hơn nếu cần.

Ví dụ tối giản:

Python
from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1200,
    chunk_overlap=150,
    separators=["\n\n", "\n", ". ", " ", ""],
)

chunks = splitter.split_text(document)

Ở đây 1200 chỉ là ví dụ.

Quan trọng hơn là tư duy:

Text
prefer strong natural boundary
        ↓
fall back gradually
        ↓
hard cut only when necessary

Nếu bạn cần giới hạn theo token thay vì character, hãy dùng tokenizer-aware length calculation thay vì mặc định coi số ký tự tương đương số token.

Structure-aware chunking

Nếu document đã có cấu trúc tốt, đừng vứt cấu trúc đó đi rồi cố gắng “khám phá lại” bằng embeddings.

Ví dụ Markdown:

Markdown
# Redis

## Persistence

...

## Eviction

### allkeys-lru

...

### volatile-lru

...

Một structure-aware chunker có thể giữ metadata:

Json
{
  "document": "redis-guide",
  "h1": "Redis",
  "h2": "Eviction",
  "h3": "allkeys-lru",
  "content": "..."
}

Khi đó chunk không còn là:

Text
các key ít được dùng gần đây...

mà về mặt retrieval có thể được biểu diễn gần với:

Text
Redis
→ Eviction
→ allkeys-lru
→ các key ít được dùng gần đây...

Đối với code, logic tương tự dẫn tới AST-aware splitting: function, class hoặc method thường là boundary có ý nghĩa hơn ký tự thứ 2.000.

Đối với HTML và Markdown, các splitter hiện tại cũng có thể tận dụng heading và syntax metadata thay vì coi toàn bộ file là plain text.

💡 Mental model

Nếu document đã nói cho bạn biết:

“Đây là một section”, “đây là một table”, “đây là một function”

thì đó thường là tín hiệu boundary mạnh hơn:

“Đã đủ 512 token.”


5. Một insight quan trọng hơn chunk size: “retrieve small, read big”

Vấn đề khó nhất của chunking xuất phát từ một mâu thuẫn.

Retriever thường thích unit nhỏ:

Text
small chunk
→ semantic focus cao
→ dễ match query cụ thể

Nhưng LLM thường cần nhiều context hơn:

Text
larger context
→ đủ subject
→ đủ explanation
→ đủ exception
→ reasoning tốt hơn

Nếu cố tìm một chunk size duy nhất thỏa mãn cả hai mục tiêu, bạn đang ép một hyperparameter giải quyết hai bài toán khác nhau.

Một kiến trúc tốt hơn là tách chúng ra.

Parent-child retrieval

Text
Original section
      │
      ▼
┌───────────────────────────────┐
│ Parent: 1 section hoàn chỉnh │
│ ~ larger context             │
└───────────────┬───────────────┘
                │ split
       ┌────────┼────────┐
       ▼        ▼        ▼
   Child A   Child B   Child C
    small     small     small
       │        │        │
       └────────┼────────┘
                ▼
          Vector Index


QUERY
  │
  ▼
match Child B
  │
  ▼
resolve parent_id
  │
  ▼
retrieve Parent
  │
  ▼
send Parent to LLM

Bạn search trên child vì child có tín hiệu ngữ nghĩa tập trung.

Nhưng bạn generate từ parent vì parent giữ được context.

LlamaIndex mô tả mô hình “small-to-big retrieval” theo đúng cách này: parent chunks được chia thành child chunks, child được index và giữ liên kết tới parent. Research được cung cấp cũng mô tả cùng nguyên tắc: tách retrieval granularity khỏi generation context thay vì tìm một chunk size duy nhất cho cả hai.

Ví dụ:

Text
Parent:
"Redis Eviction Policies" — 1.500 tokens

Children:
- noeviction — 250 tokens
- allkeys-lru — 300 tokens
- volatile-lru — 280 tokens
- allkeys-random — 240 tokens

Query:

Text
allkeys-lru loại key như thế nào?

Retriever match child allkeys-lru.

Generation có thể nhận toàn bộ section Redis Eviction Policies, nhờ vậy model còn hiểu sự khác nhau giữa policy đang hỏi với các policy lân cận.

Sentence Window Retrieval

Sentence Window dùng cùng philosophy nhưng ở granularity nhỏ hơn.

Giả sử:

Text
S1: Redis cung cấp nhiều eviction policy.
S2: allkeys-lru có thể loại các key ít được dùng gần đây.
S3: Policy này xem xét toàn bộ keyspace.
S4: volatile-lru chỉ xét các key có TTL.

Vector index có thể embed riêng:

Text
S2

nhưng metadata lưu:

Text
window = S1 + S2 + S3

Khi S2 match query, generation context được thay bằng cửa sổ xung quanh nó.

SentenceWindowNodeParser của LlamaIndex hoạt động theo nguyên tắc này: node retrieval có thể là một câu, trong khi metadata giữ các câu xung quanh để sử dụng sau retrieval.

Điều này dẫn tới một nguyên tắc kiến trúc rất đáng nhớ:

Retrieval unit không nhất thiết phải bằng generation unit.

Đối với nhiều RAG system, đây là cải tiến quan trọng hơn việc tranh luận xem chunk nên là 500 hay 700 token.


6. Semantic Chunking: khi punctuation không còn nói cho bạn biết topic đổi ở đâu

Recursive splitting giả định rằng structure của text phần nào phản ánh structure của ý tưởng.

Điều đó đúng với:

Text
documentation
blog article
Markdown
technical manual

nhưng không phải lúc nào cũng đúng với:

Text
meeting transcript
conversation
legal narrative
medical notes
OCR text
research notes

Một paragraph dài có thể chứa ba topic.

Ngược lại, năm paragraph ngắn có thể vẫn nói về cùng một concept.

Semantic chunking cố giải quyết bài toán đó bằng cách quan sát semantic distance thay vì chỉ punctuation.

Một phiên bản đơn giản có thể hoạt động như sau:

Text
Sentence 1 ──┐
Sentence 2   │ similar
Sentence 3 ──┘
      │
      │ large semantic shift
      ▼
----------- BREAK -----------
      │
Sentence 4 ──┐
Sentence 5   │ similar
Sentence 6 ──┘

Pipeline khái niệm:

Text
document
   │
   ▼
split into sentences
   │
   ▼
embed sentences/windows
   │
   ▼
calculate adjacent similarity
   │
   ▼
detect large semantic changes
   │
   ▼
place chunk boundaries

Nếu similarity giữa hai vùng giảm mạnh:

Text
sim(window_i, window_i+1) ↓

đó có thể là dấu hiệu topic vừa chuyển.

Vì sao semantic chunking hấp dẫn?

Bởi vì chunk size trở thành kết quả của semantic structure thay vì input cố định.

Một concept ngắn có thể tạo chunk 180 token.

Một explanation phức tạp có thể tạo chunk 700 token.

Đây thường là behavior hợp lý hơn việc ép cả hai thành 512 token.

Nhưng semantic chunking không có phép màu

Bạn vừa chuyển bài toán từ:

Text
chunk_size = ?

sang:

Text
semantic threshold = ?

Nếu threshold quá nhạy:

Text
một thay đổi nhỏ
→ new chunk
→ fragmentation

Nếu threshold quá lỏng:

Text
nhiều topic khác nhau
→ same chunk
→ semantic dilution

Ngoài ra, ingestion trở nên đắt hơn vì hệ thống phải chạy embedding hoặc một semantic model trong quá trình xác định boundary.

Vì vậy semantic chunking hợp lý khi boundary thật sự khó xác định bằng document structure, chứ không phải mặc định cao cấp hơn nên luôn tốt hơn recursive splitting.

🔬 Deep Dive — Semantic chunking đang tối ưu boundary, không tối ưu retrieval toàn bộ

Ngay cả khi semantic chunking tạo boundary hoàn hảo, retrieval vẫn có thể thất bại vì:

Text
query formulation
embedding model
metadata filtering
vector index
top_k
hybrid retrieval
reranking

Chunking chỉ xác định candidate unit.

Nó không thay thế retrieval architecture.


7. Contextual Retrieval và Late Chunking: hai cách khác nhau để cứu context bị mất

Một vấn đề vẫn chưa được giải quyết hoàn toàn.

Giả sử chunk boundary rất đẹp:

Text
Doanh thu tăng 3% so với quý trước,
chủ yếu nhờ tăng trưởng của dịch vụ cloud.

Đây là một đoạn văn hoàn chỉnh về mặt ngữ pháp.

Nhưng đứng độc lập, nó vẫn thiếu:

Text
doanh thu của ai?
quý nào?
báo cáo nào?

Đây không còn đơn thuần là bài toán tìm boundary.

Đây là bài toán:

Làm thế nào để một chunk nhỏ vẫn giữ được context của document lớn?

Hai hướng tiếp cận đáng chú ý là Contextual Retrieval và Late Chunking.

Contextual Retrieval: thêm context vào text trước khi index

Anthropic giới thiệu Contextual Retrieval vào tháng 9/2024.

Thay vì embed trực tiếp:

Text
Doanh thu tăng 3% so với quý trước.

hệ thống tạo thêm một contextual prefix:

Text
Đoạn này thuộc báo cáo tài chính Q2 của ACME,
đang thảo luận doanh thu cloud.

Doanh thu tăng 3% so với quý trước.

Pipeline trở thành:

Text
Original Document
       │
       ├──────────────┐
       │              │
       ▼              ▼
     Chunk           LLM
       │              │
       └──────┬───────┘
              ▼
     Contextualized Chunk
              │
        ┌─────┴─────┐
        ▼           ▼
   Embedding       BM25

Ý tưởng quan trọng là prefix không cần chứa toàn bộ document.

Nó chỉ cần bổ sung những thông tin mà chunk sẽ mất khi bị tách ra.

Trong evaluation do Anthropic công bố, Contextual Embeddings giảm top-20 retrieval failure từ 5,7% xuống 3,7%; khi kết hợp Contextual Embeddings với Contextual BM25, con số giảm xuống 2,9%. Thêm reranking đưa failure rate xuống 1,9% trong benchmark của họ. Đây là kết quả trên setup của Anthropic, không nên hiểu thành mức cải thiện bảo đảm cho mọi corpus.

Điểm đáng chú ý là Contextual Retrieval không thực sự loại bỏ chunking.

Nó làm chunk giàu context hơn trước khi retrieval.

Late Chunking: context hóa token trước rồi mới tạo chunk embedding

Late Chunking của Jina AI tiếp cận vấn đề ở tầng khác.

Naive pipeline:

Text
Document
   │
   ▼
Chunk first
   │
   ├── Chunk A → Encoder → vector A
   ├── Chunk B → Encoder → vector B
   └── Chunk C → Encoder → vector C

Chunk B chưa bao giờ được encoder nhìn cùng Chunk A.

Nếu Chunk B chứa:

Text
Công ty này tăng doanh thu 20%.

embedding model có rất ít thông tin để biết “công ty này” là ai.

Late Chunking đảo thứ tự:

Text
Long Document
     │
     ▼
Transformer Encoder
     │
     ▼
Contextualized token embeddings
     │
     ├────────┬────────┬────────┐
     │        │        │        │
   tokens A tokens B tokens C ...
     │        │        │
     ▼        ▼        ▼
   pooling  pooling  pooling
     │        │        │
     ▼        ▼        ▼
  vector A vector B vector C

Transformer nhìn phần document lớn trước khi các token được pooling thành chunk vectors.

Nhờ vậy representation của từ:

Text
"công ty này"

có khả năng đã được contextualize dựa trên entity xuất hiện trước đó.

Jina mô tả Late Chunking đúng theo quy trình này: encode long text thành token-level representations trước, sau đó mới pooling các vùng token tương ứng với chunk boundary.

Các benchmark Jina công bố cho thấy Late Chunking cải thiện nDCG@10 so với naive chunking trên nhiều dataset BEIR mà họ thử nghiệm, với mức cải thiện khác nhau theo dataset; kết quả cũng cho thấy lợi ích có xu hướng rõ hơn ở tài liệu dài hơn.

Hai kỹ thuật có thể nhìn như sau:

Contextual Retrieval Late Chunking
Context được bổ sung ở đâu? Text của chunk Token representation
Cơ chế chính LLM tạo contextual prefix Long-context embedding encoder
Chunk boundary còn cần không? Có Có
Thay đổi dữ liệu index? Có, text được contextualize Vector được contextualize
Chi phí chính Context generation lúc ingestion Long-context embedding compute
Phù hợp khi chunk khó hiểu khi đứng độc lập reference phụ thuộc context xa

Điểm quan trọng nhất:

Cả hai đều không chứng minh rằng chunk boundary không còn quan trọng.

Late Chunking vẫn cần biết token nào thuộc chunk nào.

Contextual Retrieval vẫn cần một chunk đủ coherent để contextualize.

Chúng cải thiện context preservation, không xóa bỏ bài toán segmentation.


8. Vậy nên chọn strategy nào?

Thay vì bắt đầu bằng:

Text
512 tokens + 20% overlap

hãy bắt đầu bằng loại dữ liệu.

Text
                    DOCUMENT
                       │
                       ▼
            Có structure đáng tin?
                 /           \
               Yes           No
                │             │
                ▼             ▼
        Structure-aware    Generic prose?
                              /     \
                            Yes      No
                             │        │
                             ▼        ▼
                         Recursive  Topic shifts
                                   irregular?
                                    /     \
                                  Yes      No
                                   │        │
                                   ▼        ▼
                               Semantic  Recursive
                                   │
                                   ▼
                 Chunk đứng riêng có thiếu context?
                              /         \
                            Yes         No
                             │           │
                             ▼           ▼
              Parent-child / Window    baseline
                  hoặc Contextual
                      Retrieval

Đây chỉ là starting framework.

Một số case thực tế:

Dataset Baseline hợp lý
Blog / documentation Recursive + heading metadata
Markdown knowledge base Structure-aware → recursive fallback
Source code AST/language-aware
Meeting transcript Semantic hoặc sentence-based
FAQ Một QA pair có thể là một chunk tự nhiên
Legal document Clause/section-aware + parent context
Long financial reports Structure-aware + contextual retrieval đáng thử
Query rất cụ thể nhưng cần paragraph rộng để trả lời Parent-child / sentence window

Đừng chọn chunk size bằng cảm giác

Một cách thực tế hơn là biến chunking thành experiment.

Giả sử bạn muốn thử:

Text
Chunk size:
256
512
1024 tokens

Overlap:
0%
10%
20%

Thay vì hỏi:

“Cấu hình nào được cộng đồng dùng nhiều nhất?”

hãy đo:

Text
               Evaluation queries
                       │
      ┌────────────────┼────────────────┐
      ▼                ▼                ▼
   config A         config B         config C
   256 / 10%        512 / 10%       1024 / 10%
      │                │                │
      ▼                ▼                ▼
 same embedding   same embedding   same embedding
 same retriever   same retriever   same retriever
 same top_k       same top_k       same top_k
      │                │                │
      └────────────────┼────────────────┘
                       ▼
                    compare

Giữ các component khác cố định.

Nếu đồng thời thay:

Text
chunker
embedding
top_k
reranker
prompt
LLM

bạn sẽ không còn biết improvement đến từ đâu.

🏗️ Production Note

Chunk configuration tốt nhất cũng có thể thay đổi khi query distribution thay đổi.

Knowledge base cho customer support thường nhận query rất khác knowledge base phục vụ:

Text
code search
legal analysis
financial research
internal wiki

Vì thế evaluation set nên phản ánh query thật, không chỉ các câu hỏi đẹp được tạo thủ công từ document.


9. Đánh giá chunking: đừng chỉ nhìn câu trả lời cuối cùng

Giả sử hệ thống trả lời sai.

Pipeline thực tế là:

Text
Question
   │
   ▼
Retriever
   │
   ▼
Context
   │
   ▼
Generator
   │
   ▼
Answer

Chỉ nhìn answer cuối cùng khiến bạn không biết lỗi nằm ở đâu.

Ví dụ answer sai có thể vì:

Text
A. đúng chunk không được retrieve

B. đúng chunk được retrieve nhưng xếp quá thấp

C. context đúng nhưng LLM bỏ qua

D. LLM sử dụng context nhưng reasoning sai

Chunking chủ yếu tác động mạnh vào A và B, sau đó gián tiếp ảnh hưởng C.

Retrieval metrics trước

Với một evaluation set có relevant evidence, hãy đo ít nhất:

Text
Recall@K
Precision@K
MRR hoặc nDCG

Ví dụ:

Text
Query:
"allkeys-lru hoạt động thế nào?"

Relevant chunk:
eviction-policy-17

Nếu relevant chunk không xuất hiện trong top 20:

Text
Recall@20 = failure

Trước khi tuning prompt, bạn đã biết generation model thậm chí chưa được cung cấp đúng evidence.

Context Precision và Context Recall

Ragas hiện cung cấp các metric dành cho RAG như Context Precision, Context Recall, Faithfulness và Response Relevancy. Context Precision đánh giá việc các context liên quan có được xếp cao hay không, trong khi Context Recall tập trung vào mức độ retrieval bao phủ được thông tin cần thiết.

Một cách đọc kết quả có thể là:

Symptom Điều nên kiểm tra
Context Recall thấp boundary, chunk size, query coverage, embedding, top_k
Context Precision thấp chunk quá rộng, retrieval noise, ranking
Faithfulness thấp context thiếu, context mâu thuẫn, generation/prompt
Response Relevancy thấp retrieval lạc đề hoặc generator trả lời lan man

Nhưng cần tránh một lỗi suy luận:

Text
Context Recall thấp
        ≠
chunking chắc chắn sai

Nó cũng có thể đến từ embedding model, filtering hoặc retrieval algorithm.

Metric giúp bạn xác định vùng lỗi, không tự động tìm ra root cause.

Một experiment chunking tối thiểu nên log gì?

Một evaluation harness hữu ích có thể tạo bảng:

Config Recall@10 nDCG@10 Context Precision Avg retrieved tokens Index chunks
256 / 10% ... ... ... ... ...
512 / 10% ... ... ... ... ...
1024 / 10% ... ... ... ... ...
Structure-aware ... ... ... ... ...
Parent-child ... ... ... ... ...

Bây giờ trade-off trở nên rõ ràng.

Có thể config A tăng Recall nhưng gửi gấp đôi số token vào LLM.

Config B có Precision cao nhưng bỏ sót các query yêu cầu context trải qua hai paragraph.

Config C đạt quality gần như B nhưng ingestion đơn giản hơn rất nhiều.

Khi đó bạn mới có đủ dữ liệu để quyết định engineering trade-off.


10. Những lỗi chunking phổ biến nhất trong production

Tối ưu chunk size trước khi hiểu document

Nếu corpus gồm Markdown có heading rõ ràng mà bạn lập tức chạy fixed-size 500 token, bạn đang bỏ đi một signal segmentation gần như miễn phí.

Hãy ưu tiên:

Text
document structure
        ↓
semantic coherence
        ↓
size constraint

thay vì đảo ngược thứ tự đó.

Dùng cùng một chunker cho mọi loại file

Một pipeline:

Text
PDF
Markdown
HTML
Python
JSON
CSV
        │
        ▼
same RecursiveCharacterTextSplitter

rất tiện.

Nhưng convenience không đồng nghĩa correctness.

Code có function boundary.

HTML có DOM.

Markdown có headings.

Table có rows và columns.

Mỗi dạng dữ liệu có definition khác nhau về “một đơn vị thông tin hoàn chỉnh”.

Tăng overlap để chữa mọi vấn đề

Overlap hữu ích với boundary uncertainty.

Nhưng nó không sửa được:

Text
parser làm hỏng table
OCR sai
heading bị mất
document hierarchy biến mất
metadata thiếu

Và overlap quá cao còn tạo candidate gần trùng nhau.

Đưa metadata vào database nhưng không đưa vào retrieval representation

Bạn có thể lưu:

Json
{
  "title": "Redis Eviction",
  "section": "allkeys-lru"
}

nhưng embedding chỉ:

Text
Keys are removed based on recent usage...

Nếu metadata quan trọng để hiểu chunk, hãy cân nhắc sử dụng nó trong embedding text, contextual representation hoặc retrieval filtering — thay vì chỉ lưu metadata để hiển thị sau cùng.

Chỉ benchmark answer quality

Nếu chỉ hỏi một LLM judge:

“Câu trả lời cuối cùng có tốt không?”

bạn sẽ trộn retrieval quality và generation quality thành một con số.

Chunking experiment tốt nên kiểm tra retrieval trước, sau đó mới kiểm tra end-to-end RAG.

Cho rằng strategy phức tạp luôn tốt hơn

Semantic chunking, Contextual Retrieval và Late Chunking đều rất thú vị.

Nhưng một technical documentation có heading rõ ràng đôi khi chỉ cần:

Text
structure-aware
+
recursive fallback
+
good metadata
+
evaluation

Một architecture đơn giản đạt cùng quality sẽ dễ vận hành, debug và re-index hơn.


11. Chunking tốt không phải là tạo các đoạn văn “đẹp”

Quay lại tình huống ban đầu.

Câu trả lời tồn tại trong document nhưng RAG không tìm thấy nó.

Nếu chỉ nhìn phía generation, ta có thể nghĩ:

Text
LLM chưa đủ thông minh.

Nếu chỉ nhìn vector database:

Text
embedding model chưa đủ tốt.

Nhưng khi hiểu chunking, ta thấy một layer nằm trước tất cả những thứ đó.

Document gốc không đi thẳng vào retrieval.

Nó phải được biến thành một tập các retrieval units.

Text
Knowledge
   │
   ▼
Parsing
   │
   ▼
Chunk boundaries
   │
   ▼
Retrievable units
   │
   ▼
Embeddings
   │
   ▼
Search
   │
   ▼
Context
   │
   ▼
Answer

Nếu boundary làm mất relationship quan trọng, knowledge vẫn còn trên disk nhưng đã không còn tồn tại dưới dạng mà retriever có thể sử dụng hiệu quả.

Đó là lý do câu hỏi tốt hơn không phải:

“Chunk size chuẩn là bao nhiêu?”

Mà là:

“Đơn vị nhỏ nhất nào đủ tập trung để retrieval chính xác, nhưng vẫn giữ đủ context để có ý nghĩa?”

Và khi hai yêu cầu đó mâu thuẫn, đừng cố bắt một chunk giải quyết cả hai.

Hãy tách:

Text
retrieval unit
≠
generation context

bằng parent-child retrieval, sentence window hoặc những architecture tương tự.

Nếu chunk vẫn mất ý nghĩa khi đứng độc lập, hãy cân nhắc contextualization như Contextual Retrieval hoặc Late Chunking.

Cuối cùng, đừng chọn chiến lược bằng intuition.

Hãy build evaluation set, giữ các component khác cố định và đo retrieval.

Bởi vì trong một RAG system tốt, chunking không phải thao tác “cắt tài liệu thành từng đoạn”.

Nó là quá trình thiết kế hình dạng của tri thức mà hệ thống có khả năng tìm lại.

Những điều cần nhớ

  • Chunk càng nhỏ không đồng nghĩa retrieval càng tốt; context có thể bị mất.
  • Chunk càng lớn không đồng nghĩa reasoning càng tốt; relevance có thể bị pha loãng và context trở nên nhiễu.
  • Document structure thường là boundary signal đáng tin cậy hơn một con số token cố định.
  • Retrieval unit không nhất thiết phải bằng generation unit.
  • Không có chunking strategy tốt nhất cho mọi corpus; strategy tốt nhất là strategy thắng trên evaluation set đại diện cho workload thật.

Nguồn tham khảo