Day 8 我開始測 RAG 的 Chunking,把同一份文件用不同方式切開之後,再看 Retrieval 到底能不能把真正需要的內容找回來,做到這一步之後,下一個很自然會碰到的參數就是 Top-K。
第一次看到 Top-K 的時候其實很好理解,假設我把 K 設成 5,Retriever 就會找出最相關的五段內容,再一起交給 LLM 回答,所以很容易產生一個直覺,既然怕漏資料,那我把 K 調大一點,多給模型一些 Context 應該就比較保險。
我一開始也是這樣想,但如果真的準備把 RAG 放進系統裡,這個參數不能只靠感覺決定,所以今天我直接拿昨天同一批測試資料,分別跑 K = 1、3、5、10 四組。
K = 1 的做法很簡單,每次問題只拿 Retrieval 排名第一的 Chunk 給模型。
這種方式最省 Token,Prompt 也很乾淨,如果第一名剛好就是答案所在的 Chunk,整個流程會非常俐落,模型不用在一堆資料裡面重新找重點。
問題也很明顯,有些答案本來就需要兩段甚至三段內容才能組起來,例如一段寫規則,另一段才寫例外情況,這時候只拿第一名就很容易漏東西。
所以 K = 1 跑完之後,我開始記錄的已經不只是最終答案對不對,我還會一起看正確文件到底有沒有被 Retriever 找進來。
K = 3 和 K = 5 的結果開始比較有意思,因為正確資料被找到的機率確實會提高,原本 K = 1 漏掉的內容,有些在 K = 3 就進來了,而且模型手上的 Context 還沒有多到很誇張。
但到了 K = 10,我第一次很明顯感受到「資料更多」也可能開始帶來新的問題。
因為 Retriever 排在後面的內容,本來相關性就比較低,全部塞進 Prompt 之後,模型除了要處理真正有用的資訊,也得一起讀那些看起來相關、實際上卻沒有回答問題的 Chunk,有些內容甚至出現相似名詞,最後反而讓回答開始變得比較不穩定。
這也是今天我覺得最值得記下來的地方。
Retrieval 階段會希望正確文件盡量不要漏掉,所以提高 K 很可能讓 Recall 變好,可是從整個 AI System 來看,我真正關心的還有最後回答準確度、Token 使用量、Latency,以及一次 Request 到底要花多少成本。
所以我今天開始把結果放在一起看:
K = 1
K = 3
K = 5
K = 10
觀察:
Retrieval Recall
Answer Accuracy
Input Token
Latency
這樣就會發現最佳的 K 不一定是 Recall 最高的那一組,因為如果多找回來的五個 Chunk 全部都是 Noise,那些資料雖然讓 Retrieval 指標變漂亮,最後回答卻沒有因此變好,還多花了 Token 和時間。
前幾天剛開始做 RAG 時,流程看起來其實滿簡單,文件切一切、做 Embedding、丟進 Vector Database,再把搜尋結果交給 LLM,Demo 很快就能跑起來。
可是開始真的做 Evaluation 之後,我才發現每一個看起來很小的設定都會互相影響,Chunk 太大或太小會改變 Retrieval,Top-K 又會改變 Context 數量,Context 一變,模型回答、Token、Latency 全部都跟著變。
也因為這樣,我現在比較不敢看到教學寫 top_k = 5 就直接跟著設成 5,因為那個數字可能只是範例,在自己的資料上到底適不適合,還是得拿 Dataset 實際跑一次才知道。
今天先完成第一輪 Top-K Benchmark,我會把四組結果放進同一張表,接下來比較 Recall、Accuracy、Token 和 Latency,看看哪個範圍在目前這份 Dataset 上比較合理。
做到這裡,我對 RAG 的理解也開始從「想辦法找更多相關資料」,慢慢變成「找到剛好足夠模型回答的資料」,因為 Context 有限制、成本也有差,真正放到 Production 裡,每多塞一段內容都不是免費的。