EN

4.4 管理上下文長度限制

模型一次能讀的長度有限,資料放不下的時候該怎麼取捨。

RAG下一篇 →

每個 LLM 一次能處理的長度有一個上限,叫做上下文視窗(context window),通常用 token 計算。RAG 要把資料放進提示詞,就一定會碰到這個限制。

預算怎麼分配

上下文視窗是輸入和輸出共用的預算。要放進去的東西包括:

上下文視窗的預算:指示、檢索到的資料、問題,還要預留空間給模型的回答

  • 指示:長度固定。
  • 問題:通常很短。
  • 預留給回答的空間:要先扣掉,不然資料塞滿了,模型沒空間寫答案,會被截斷。
  • 檢索到的資料:這是剩下可以調整的部分。

換句話說:資料可用的空間 = 視窗大小 − 指示 − 問題 − 回答預留。

就算視窗很大,也不該塞滿

現在有些模型的視窗已經很長,是不是就可以把檢索到的資料全都放進去?不建議,原因是:

  • 成本:輸入越長,費用越高。
  • 速度:輸入越長,回應越慢。
  • 品質:塞進越多不相關的內容,雜訊越多,模型越容易被干擾,也更容易出現 4.3 提到的「迷失在中間」。

所以「放得下」和「應該放」是兩回事。少而精,通常比多而雜好。

計算長度:token

模型的長度單位是 token,不是字數。

  • 英文大約一個單字是一到兩個 token。
  • 中文一個字大約是一個或更多 token,實際比例依模型的分詞方式而定。

要精確計算,應該用該模型對應的 tokenizer,或是 API 提供的 token 計數功能。粗略估計的時候,可以先用字數,並預留安全空間。

放不下的時候

1. 減少檢索的數量(調整 k)

最直接的做法:只留最相關的前幾段。

2. 設相似度門檻

分數太低的,代表可能根本不相關,即使名額還沒滿也不放。這樣有時候放進去的資料比 k 還少,這是好事。

3. 依預算逐段加入

從最相關的開始,一段一段放進去,快超過預算就停下來。一個簡單的做法:

def pick_within_budget(chunks, max_chars):
    """依序放入資料,超過字數預算就停止(最相關的先放)"""
    picked, used = [], 0
    for chunk in chunks:
        if used + len(chunk["text"]) > max_chars:
            break
        picked.append(chunk)
        used += len(chunk["text"])
    return picked

我拿 3 段資料、預算 70 字去跑,結果只放進了最相關的 2 段,第 3 段因為放不下被擋在外面:

預算 70 字,放進了 2 段(共檢索到 3 段)

(這裡為了示範用字數當預算;實際要用 token。)

4. 壓縮內容

把長段落先摘要,或只抽出和問題相關的句子,再放進去。要小心摘要可能遺漏關鍵細節。

5. 調整分塊大小

如果塊本來就切得太大,每塊都吃掉很多空間,可以回頭調整分塊的大小(第 3 章)。

6. 換上下文更長的模型

當資料真的需要很多,而且不能被省略的時候,可以換視窗更長的模型。但仍然要考慮成本和品質。

多輪對話的特別問題

如果是連續的對話,歷史訊息也要佔空間。每多一輪,提示詞就更長。常見的處理方式:

  • 只保留最近幾輪。
  • 把較早的對話摘要成一小段。
  • 每一輪都重新檢索,不要把上一輪檢索的資料一直累積著。

另外,使用者的追問常常省略主語,例如「那加班呢?」,直接拿去檢索會找不準。通常要先請模型把追問改寫成完整的獨立問題(「加班費怎麼計算?」),再去檢索。

一個取捨的心法

  • 先從少的開始(例如 k = 3 到 5)。
  • 當發現「答案需要的資料沒被找到」,再增加。
  • 當發現「答案被雜訊干擾」,再減少。

沒有放諸四海皆準的數字,要用第 6 章的評估方法,在自己的資料上找到合適的平衡。

← 全部文章