Chunking — cắt tài liệu để truy hồi
Tài liệu phải cắt thành đoạn vì context hữu hạn và vì embedding cả cuốn sách thì loãng. Cắt sai là nguyên nhân hỏng RAG phổ biến nhất.
TL;DR: Tài liệu phải cắt thành chunk (đoạn) trước khi embed, vì hai giới hạn cộng dồn: context của model hữu hạn (không nhét vừa cả kho tài liệu), và một vector embedding có độ dài cố định bất kể input dài ngắn — nhồi cả cuốn sách vào một vector thì nghĩa bị "trung bình hoá", loãng tới mức vô dụng. Nhưng cắt sai còn hỏng theo cách khác: chunk quá nhỏ mất ngữ cảnh (một câu tách rời không đủ nghĩa để trả lời gì), chunk quá to nhồi nhiều chủ đề vào một vector khiến tín hiệu loãng. Vùng thường dùng là vài trăm token — không có một con số đúng cho mọi tài liệu — và các chunk cần overlap với nhau để không cắt ngang giữa một ý.
Nhóm ở bài trước đã embed câu hỏi ngắn "cách xin nghỉ phép năm" ra một vector gọn, đứng gần vector câu trả lời đúng trong tài liệu HR. Nhưng tài liệu HR không phải một câu — nó là file 40 trang gồm nghỉ phép, nghỉ ốm, làm từ xa, onboarding. Ai đó thử cách đơn giản nhất: embed nguyên cả file thành một vector, coi nó "đại diện" cho toàn bộ nội dung.
Kết quả: mọi câu hỏi về file đó đều trả về đúng file đó — không sai, vì có mỗi file đó để trả — nhưng không giúp gì, vì hệ thống không chỉ ra được câu trả lời nằm ở đâu. Bài này giải thích vì sao phải cắt tài liệu thành đoạn nhỏ hơn trước khi embed, và hai cách cắt sai phổ biến khiến việc cắt phản tác dụng.
Bài đó: context của model là bộ nhớ làm việc hữu hạn, đo bằng token, đầy thì phải cắt bớt. Chunking giải quyết đúng giới hạn đó ở phía tài liệu nguồn — thay vì nhét cả kho tài liệu vào context, hệ thống chỉ cần chọn vài đoạn liên quan nhất để nhét vào, và muốn chọn được đoạn liên quan thì tài liệu phải được cắt thành đoạn từ trước.
1. Analogy — trích một câu trong sách, không đưa cả cuốn
Ai đó hỏi bạn: "cuốn này nói gì về cách xin nghỉ phép?" Bạn có hai lựa chọn. Một là đưa nguyên cuốn sách 40 trang và nói "trong đó có". Hai là lật đúng trang, trích ra đúng đoạn 5 dòng nói về nghỉ phép. Cách hai hữu ích hơn hẳn — không phải vì đoạn 5 dòng "đúng hơn" cả cuốn sách, mà vì nó đủ ngắn để chỉ nói về một chuyện, và đủ dài để chuyện đó còn nguyên nghĩa.
| Đời thường | Chunking |
|---|---|
| Cả cuốn sách 40 trang | Toàn bộ tài liệu nguồn |
| Trích đúng đoạn 5 dòng về nghỉ phép | Một chunk — vài trăm token, một chủ đề |
| Trích 1 chữ "nghỉ" — vô nghĩa, thiếu ngữ cảnh | Chunk quá nhỏ — mất ngữ cảnh |
| Trích cả chương 10 trang gồm nghỉ phép + nghỉ ốm + làm từ xa | Chunk quá to — nhiều chủ đề, tín hiệu loãng |
Chunk tốt giống một đoạn trích: đủ ngắn để chỉ nói một chuyện, đủ dài để chuyện đó còn nguyên nghĩa.
2. Vì sao phải cắt tài liệu ra thành chunk?
Hai lý do độc lập, nhưng cùng trỏ về một kết luận: đừng embed nguyên cả tài liệu dài.
Lý do 1 — giới hạn đầu vào, đo bằng token. Hai chỗ đều hữu hạn: embedding model có giới hạn token đầu vào cho mỗi lần embed (bài 01), và context của model sinh câu trả lời cũng chỉ chứa được một số token nhất định mỗi request. Cả kho tài liệu công ty — hàng trăm, hàng nghìn file — lẫn nhiều tài liệu dài đơn lẻ đều vượt xa các giới hạn đó, nên không thể đẩy nguyên khối vào một lần: hệ thống buộc phải chia nhỏ rồi chỉ đưa phần liên quan vào.
Lý do 2 — độ dài vector cố định. Bài embedding đã nói: mọi vector, dù input dài hay ngắn, đều ra cùng độ dài cố định. Một câu 6 chữ và cả cuốn sách 40 trang đều ra ngần ấy con số. Câu ngắn thì ngần ấy con số đủ đại diện cho một ý; cả cuốn sách thì ngần ấy con số phải gánh hàng chục ý cùng lúc — nghĩa bị ép "trung bình hoá", và cái "trung bình" đó không nằm gần chủ đề nào trong không gian nghĩa.
embed("Đoạn 5 dòng: cách xin nghỉ phép năm")
-> vector đại diện SÁT nghĩa cho đúng một chủ đề: nghỉ phép
embed("Cả file 40 trang: nghỉ phép + nghỉ ốm + remote + onboarding")
-> vector đại diện MỜ cho bốn chủ đề cùng lúc
-> câu hỏi về "nghỉ phép" hay câu hỏi về "remote" đều cho điểm gần bằng nhau
-> similarity không còn phân biệt được đâu là chủ đề chính
Vì vậy, tài liệu phải được cắt thành nhiều chunk trước khi embed — mỗi chunk ra một vector riêng, đại diện cho đúng phần nội dung của nó, thay vì một vector cố gánh cả tài liệu.
3. Thử đoán trước khi đọc tiếp
Bạn có một tài liệu FAQ nội bộ, mỗi mục hỏi–đáp dài khoảng 3–4 câu. Nếu bạn cắt tài liệu này theo từng câu một (mỗi câu một chunk), retrieve sẽ hỏng theo hướng nào? Còn nếu bạn cắt theo từng chương (gộp 20 mục hỏi–đáp khác nhau vào một chunk), retrieve sẽ hỏng theo hướng nào? Viết ra dự đoán của bạn trước khi đọc mục 4.
4. Hai cách cắt sai — và vì sao cả hai đều hỏng retrieve
4.1 Cắt quá nhỏ — mất ngữ cảnh
Cắt theo từng câu nghe hợp lý — câu là đơn vị ngữ pháp rõ ràng. Nhưng nhiều câu chỉ có nghĩa nhờ câu đứng trước nó:
Câu gốc trong tài liệu:
"Nhân viên có 12 ngày phép mỗi năm.
Số ngày này được cộng dồn nếu không dùng hết."
Cắt theo từng câu, chunk 2 đứng một mình:
"Số ngày này được cộng dồn nếu không dùng hết."
-- "Số ngày này" trỏ về đâu? Chunk không còn giữ thông tin đó.
Khi chunk 2 được embed một mình, vector của nó không còn mang thông tin "12 ngày phép" — cụm từ "số ngày này" trong chunk 2 không tự giải nghĩa được nữa. Nếu chunk 2 lọt vào top kết quả, model nhận được một câu thiếu chủ ngữ rõ ràng, và câu trả lời sinh ra dễ mơ hồ hoặc sai hẳn — dù thông tin gốc chưa từng bị mất khỏi tài liệu, chỉ là bị cắt sai chỗ.
4.2 Cắt quá to — loãng tín hiệu
Ngược lại, gộp nhiều mục hỏi–đáp không liên quan vào một chunk để "cho chắc, đỡ mất ngữ cảnh" thì rơi lại đúng vấn đề ở mục 2: một vector phải đại diện cho nhiều chủ đề cùng lúc.
Chunk to = 20 mục hỏi-đáp: nghỉ phép, nghỉ ốm, remote, onboarding, ...
embed(chunk to) -> một vector "trung bình" của 20 chủ đề
Câu hỏi "cách xin nghỉ phép năm" so với chunk to:
-> similarity không cao rõ rệt, vì 19/20 phần nội dung không liên quan
-> chunk nhỏ hơn, tập trung đúng chủ đề nghỉ phép, có thể còn thắng điểm
Chunk càng to càng dễ "chứa" đáp án bên trong, nhưng điểm similarity của nó lại càng kém đại diện cho riêng phần đáp án đó — chunk chứa câu trả lời chưa chắc được xếp hạng cao, vì phần lớn nội dung còn lại kéo vector lệch khỏi đúng chủ đề đang hỏi.

5. Vùng thường dùng và overlap
Không có một con số chunk-size đúng cho mọi tài liệu — độ dài hợp lý phụ thuộc văn phong nguồn (văn xuôi khác dòng log, khác cell bảng). Nhưng có một vùng định tính hay dùng làm điểm khởi đầu: vài trăm token — đủ dài giữ trọn một ý (mục 4.1), đủ ngắn để không lẫn nhiều chủ đề (mục 4.2).
Cắt đúng ranh giới đó theo độ dài cứng vẫn có nguy cơ cắt ngang giữa chừng một ý — y hệt lỗi ở mục 4.1 nhưng do vị trí cắt rơi ngẫu nhiên. Cách giảm rủi ro: cho các chunk liền kề overlap — chunk sau lặp lại một phần cuối của chunk trước, để ý bị cắt ngang vẫn còn nguyên vẹn trong ít nhất một chunk:
Tài liệu: [ ... đoạn A ... | ranh giới cắt cứng | ... đoạn B ... ]
Không overlap:
chunk 1 = đoạn A -- nếu ý bị cắt ngang ranh giới, cả 2 chunk đều thiếu
chunk 2 = đoạn B
Có overlap (chunk 2 lặp lại phần cuối của chunk 1):
chunk 1 = đoạn A
chunk 2 = [phần cuối đoạn A] + đoạn B -- ý bị cắt ngang giờ còn nguyên trong chunk 2
Overlap không miễn phí — mỗi phần lặp lại tốn thêm dung lượng và thời gian embed, vì cùng một câu văn giờ nằm trong hai chunk. Đánh đổi có chủ đích: chấp nhận trùng lặp để giảm rủi ro cắt đứt một ý đúng vào ranh giới chunk.
6. Pitfall tổng hợp
❌ Nhầm 1 — chọn chunk-size càng nhỏ càng an toàn, vì "nhỏ thì tìm chính xác hơn":
✅ Chunk quá nhỏ đúng là dễ tính similarity "chính xác" hơn cho một câu đơn lẻ, nhưng câu đơn lẻ tách khỏi ngữ cảnh xung quanh dễ mất chủ ngữ, mất mốc thời gian, mất điều kiện đi kèm (mục 4.1) — retrieve có thể đúng file, sai nghĩa.
❌ Nhầm 2 — chọn chunk-size càng to càng an toàn, vì "to thì không sót thông tin":
✅ Chunk to không mất thông tin bên trong nó, nhưng vector đại diện cho nó bị loãng tới mức chunk chứa đúng câu trả lời chưa chắc được xếp hạng cao (mục 4.2) — thông tin có mặt nhưng không được tìm thấy.
7. 📚 Deep Dive
Bài này viết hoàn toàn định tính, không trích con số chunk-size, recall hay F1 cụ thể nào — chunk-size tối ưu phụ thuộc mạnh vào embedding model, loại tài liệu và task, nên quyết định thật nên đo trên chính dữ liệu của hệ thống (cách đo: mục 4.2 bài Embedding), không suy ra từ một con số đọc được ở nơi khác.
Ghi chú: đọc ngày 2026-07-23; đây là giới hạn có chủ đích của bài, không phải thiếu sót.
8. Liên hệ các bài khác
- Bài 01 — Embedding, không gian của nghĩa — nguồn gốc của lý do 2: vector độ dài cố định là lý do nhồi cả tài liệu vào một vector làm nghĩa bị trung bình hoá.
- Context window — nguồn gốc của lý do 1: context hữu hạn buộc hệ thống chỉ chọn được vài đoạn, không phải cả kho tài liệu.
- Bài 05 — RAG pipeline — chunk chính là đơn vị được embed, lưu trữ và truy hồi trong toàn bộ pipeline RAG.
- Bài 06 — RAG failure modes — chunking sai là một trong những nguyên nhân gốc phổ biến nhất khi retrieve trả về sai đoạn.
9. Tóm tắt
- Chunk quá nhỏ mất ngữ cảnh — một câu tách rời không tự giải nghĩa được dù thông tin gốc chưa mất.
- Chunk quá to loãng tín hiệu — vector đại diện nhiều chủ đề cùng lúc khiến chunk đúng chưa chắc được xếp hạng cao.
- Vài trăm token là vùng thường dùng để bắt đầu, không phải con số tuyệt đối — phải đo lại trên dữ liệu thật.
- Overlap giữa các chunk liền kề giảm rủi ro cắt đứt một ý đúng vào ranh giới, đổi lại một phần dung lượng trùng lặp.
10. Tự kiểm tra
Q1Vì sao chunking cần thiết ngay cả khi context window của model đủ lớn để chứa toàn bộ tài liệu?▸
Context đủ lớn chỉ giải quyết lý do 1 (nhét vừa vào request), nhưng không giải quyết lý do 2: vector embedding có độ dài cố định bất kể input dài hay ngắn. Nhồi cả tài liệu vào một vector vẫn khiến nghĩa bị trung bình hoá giữa nhiều chủ đề, dù context có đủ chỗ chứa nguyên văn tài liệu đó.
Vì vậy chunking là nhu cầu của tầng embedding/retrieve, tách biệt với giới hạn của tầng context.
Q2Một đồng nghiệp cắt tài liệu theo từng câu để 'tìm cho chính xác'. Điều gì có thể hỏng, và tại sao đây không phải lỗi mất dữ liệu?▸
Nhiều câu chỉ có nghĩa nhờ câu đứng trước (ví dụ một câu dùng "số ngày này" trỏ về con số nêu ở câu trước). Khi tách riêng, chunk đó vẫn còn nguyên văn bản — không mất chữ nào — nhưng vector embed của nó không còn đại diện đúng cho ý gốc, vì phần ngữ cảnh giải nghĩa cụm từ đã nằm ở chunk khác.
Đây là mất ngữ cảnh chứ không phải mất dữ liệu: thông tin vẫn ở đâu đó trong tài liệu, chỉ là không còn nằm cùng chunk với phần cần nó.
Q3Một hệ thống chunk theo từng chương (chunk rất to) và than phiền retrieve 'bỏ sót' câu trả lời đúng dù câu đó nằm trong chunk được trả về ở hạng thấp. Giải thích cơ chế, không phải 'model kém'.▸
Chunk to gộp nhiều chủ đề khác nhau vào cùng một vector — vector đó là một dạng "trung bình" của toàn bộ chủ đề trong chunk, không riêng phần chứa câu trả lời. Khi câu hỏi chỉ liên quan một phần nhỏ của chunk, similarity giữa câu hỏi và vector "trung bình" đó thấp hơn hẳn so với một chunk nhỏ tập trung đúng chủ đề.
Kết quả: chunk chứa đúng câu trả lời vẫn tồn tại trong hệ thống, nhưng bị xếp hạng thấp vì phần lớn nội dung còn lại của nó kéo vector lệch khỏi đúng chủ đề đang hỏi — đây là hệ quả cơ học của cách embed, không phải model suy luận kém.
Q4Overlap giữa các chunk liền kề giải quyết vấn đề gì, và cái giá phải trả là gì?▸
Overlap giảm rủi ro của việc cắt theo độ dài cứng: nếu ranh giới cắt rơi đúng giữa một ý, phần cuối của chunk trước được lặp lại ở đầu chunk sau, nên ý đó vẫn còn nguyên vẹn trong ít nhất một chunk thay vì bị chia đôi và mất ngữ cảnh ở cả hai phía.
Cái giá: cùng một đoạn văn bản giờ được embed và lưu trữ ở hai chunk khác nhau, tốn thêm dung lượng và thời gian xử lý theo đúng tỉ lệ phần trùng lặp.
Q5Vì sao không có một con số chunk-size 'đúng' áp dụng chung cho mọi tài liệu?▸
Chunk-size hợp lý phụ thuộc vào văn phong và cấu trúc của nguồn: một đoạn văn xuôi mạch lạc giữ được nhiều ý trong vài trăm token, còn một dòng log hay một cell bảng có thể đã trọn nghĩa ở độ dài ngắn hơn nhiều. "Vài trăm token" chỉ là vùng khởi đầu định tính, không phải ngưỡng cố định.
Vì vậy chunk-size thật cho một hệ thống cụ thể cần được đo trên chính dữ liệu và câu hỏi thật của hệ thống đó, chứ không nên áp một con số đọc được ở nơi khác vào một tài liệu có văn phong khác hẳn.
Bài tiếp theo: RAG — retrieve rồi mới generate
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên