












好傢伙,
上一篇我們聊了 Embedding.
當時說到一個很關鍵的點:
Embedding 負責把文本變成向量.
RAG 負責根據語義找到相關資料.
大模型負責基於資料組織答案.
但這裡馬上就會遇到一個新問題:
資料找到了以後,到底怎麼交給大模型?
比如我問:
請根據這份部署文檔,告訴我 battle monitor 怎麼上線.
RAG 檢索到了 5 段資料.
歷史對話裡還有我前面問過的問題.
系統提示詞裡還寫著回答規則.
這些東西最後都要放到哪裡?
答案就是:
放進上下文窗口.
所以今天這篇就專門聊一個很基礎,但很容易誤解的概念:
上下文窗口是什麼?
為什麼模型會忘東西?
為什麼不是窗口越大就一定越好?
我最開始用大模型時,對它的感覺是:
它好像能記住我剛才說過什麼.
比如我先說:
我現在在寫一篇關於 RAG 的文章.
然後我再問:
幫我把剛才那個主題換一個標題.
模型通常能知道"剛才那個主題"指的是 RAG.
這就很像記憶.
但用久了以後會發現:
聊太長,它會忘.
資料太多,它會漏.
前面明明說過,後面它卻像沒看見.
這個時候就不能只說一句:
模型記性不好.
更準確的說法是:
這次請求裡,模型能看到的內容是有限的.
這個有限範圍,就是上下文窗口.
我現在對上下文窗口的理解是:
上下文窗口 = 模型在一次請求裡能夠看到和處理的 token 範圍.
注意這裡有兩個關鍵詞:
一次請求
token 範圍
也就是說,模型不是把我們所有聊天內容都永久放在腦子裡.
每次調用模型時,系統會把一批內容打包給它.
這批內容可能包括:
系統提示詞
開發者提示詞
歷史對話
用戶當前問題
RAG 檢索出來的資料
工具調用結果
預留給模型輸出的空間
這些內容加起來,不能超過模型允許處理的上下文範圍.
可以先看這張圖:

這張圖想表達的是:
對話和資料是一條很長的流.
上下文窗口只覆蓋其中一段.
新問題和新資料進來時,太早的內容可能會離開窗口.
所以模型"忘了",很多時候不是它真的產生了人類意義上的遺忘.
而是:
那段內容沒有被放進這一次請求裡.
很多人會把上下文窗口理解成:
聊天記錄長度.
這不算錯,但不完整.
上下文窗口不只是聊天記錄.
它更像一次請求的總預算.
裡面裝的東西可能很多:
系統規則
-> 你要怎麼回答,哪些不能做
歷史對話
-> 用戶前面問過什麼,模型前面答過什麼
當前問題
-> 用戶這一次真正要問什麼
檢索資料
-> RAG 找回來的文檔片段
工具結果
-> 數據庫查詢結果,接口返回內容,代碼執行結果
輸出空間
-> 模型接下來要生成答案的位置
這些東西都要佔 token.
所以一個很現實的問題是:
窗口不是隻給輸入用的.
輸出也要佔空間.
比如一個模型的上下文窗口最多能處理一批 token.
如果輸入已經塞得很滿,那留給輸出的空間就會變少.
反過來,如果希望模型輸出很長,輸入就不能無限塞.
這就是為什麼我說上下文窗口更像預算:

這張圖裡最重要的是第二條:
RAG 資料塞太多時,模型不一定更聰明.
因為塞進去的內容越多,模型越需要在裡面找重點.
如果資料裡混進了很多無關內容,模型反而可能被幹擾.
這也是為什麼 RAG 系統不能只做一件事:
把所有搜索結果都扔給模型.
它通常還需要:
召回
重排
過濾低分片段
壓縮摘要
控制 TopK
說白了:
上下文窗口有限,所以要把最有價值的內容放進去.
這裡可以拆成三種情況.
這是最常見的.
比如我前面很早說過:
我的項目叫 Lighter.
但後面聊了很長.
如果系統為了控制上下文長度,把早期對話裁掉了.
那模型這次請求裡就看不到這句話.
它自然可能不知道 Lighter 是什麼.
這不是參數忘了.
而是:
這次輸入裡沒有這條信息.
有些時候,內容確實在上下文裡.
但上下文太長.
關鍵信息埋在中間.
模型可能沒有穩定抓住它.
這就涉及一個很有名的問題:
Lost in the Middle
簡單說就是:
長上下文裡,模型不一定能同等有效地利用每個位置的信息.
有些信息放在開頭或結尾更容易被用到.
埋在中間時,模型可能更容易漏掉.
所以長上下文不是萬能藥.
窗口變大以後,還要考慮:
哪些內容放前面
哪些內容放後面
哪些內容要壓縮
哪些內容要重複強調
還有一種誤解是:
我告訴過模型一次,它以後就應該一直記得.
但大多數普通調用裡,模型並不會因為一次聊天就改寫自己的參數.
它能利用的通常是:
這次請求裡的上下文
如果系統有記憶功能,那也是另外一層工程能力.
比如:
把用戶偏好保存到數據庫
下次聊天前再取出來
重新塞進上下文
這和模型參數本身記住不是一回事.
可以用這張圖區分:

這張圖裡我想強調一句話:
上下文窗口是臨時工作臺.
外部資料是資料庫.
模型參數是訓練時形成的能力.
這三件事不要混成一個東西.
假設我們在寫一個後端部署文檔問答助手.
一開始用戶說:
我的項目是 battle monitor.
它由 Go API、PostgreSQL、Nginx 組成.
然後用戶問了很多問題:
數據庫怎麼配置?
Nginx 怎麼反代?
Docker Compose 怎麼寫?
日誌怎麼看?
健康檢查接口是什麼?
聊到後面,用戶突然問:
那這個項目上線前還要檢查什麼?
如果系統把所有歷史都完整塞進上下文,模型大概率知道"這個項目"指 battle monitor.
但如果歷史太長,系統只保留最近幾輪:
用戶: 日誌怎麼看?
助手: ...
用戶: 健康檢查接口是什麼?
助手: ...
用戶: 那這個項目上線前還要檢查什麼?
這時候模型可能只知道:
有日誌
有健康檢查
可能是某個項目
但它不一定知道:
這是 battle monitor
用了 Go API、PostgreSQL、Nginx
所以它可能會給一個泛泛的上線檢查清單.
這就解釋了為什麼有時候我們會覺得:
它前面明明知道,後面又忘了.
實際上更準確的流程是:
早期信息
-> 曾經在上下文裡
-> 後來窗口滑動
-> 早期信息被裁掉
-> 新請求裡看不到
-> 回答變泛
上一篇講 Embedding 時,我們說 RAG 大概是:
用戶提問
-> 檢索相關資料
-> 把資料交給模型
-> 模型基於資料回答
現在可以把中間那步說得更準確一點:
把資料交給模型 = 把資料塞進上下文窗口.
所以 RAG 的效果不只取決於:
能不能搜到資料.
還取決於:
搜到的資料有沒有放進上下文
放進去的位置是否合理
放進去的資料是否太多
有沒有和用戶問題放在一起
有沒有保留足夠輸出空間
這也是為什麼一個 RAG 系統可能會出現這種情況:
向量數據庫確實搜到了正確文檔.
但模型最後回答還是不準.
原因可能不是 Embedding 錯了.
而是:
正確文檔被別的片段擠掉了
正確文檔埋在太長上下文中間
提示詞沒有要求模型優先依據來源
輸出空間不夠
所以工程上經常會做這些事:
只取 TopK
對召回結果 rerank
把長文檔壓縮成短摘要
把關鍵來源放到問題附近
讓模型按引用回答
這不是形式主義.
而是在管理上下文窗口.
我的答案是:
大窗口很有用,但不是越大就自動越好.
大窗口能解決一些問題.
比如:
能放更長的文檔
能保留更多歷史對話
能一次處理更多工具返回結果
這當然有價值.
但它也帶來新的問題:
輸入成本更高
響應可能更慢
無關內容更容易混進去
模型不一定能穩定利用所有位置的信息
調試更難
所以我現在更願意把上下文窗口當成一個資源.
不是說:
有多少塞多少.
而是問:
這次回答真正需要哪些信息?
哪些是噪音?
哪些可以摘要?
哪些必須原文保留?
這和寫代碼有點像.
內存大當然好.
但你不能因為內存大,就把所有東西都塞進一個函數.
上下文窗口也是一樣.
大窗口給了更大的空間.
但工程上還是要管理.
到這裡,前面幾篇文章可以串起來了.
先是 Token:
文本進入模型之前,要先被切成 token.
然後是 Embedding:
文本可以被表示成向量,用來做語義相似度比較.
現在是上下文窗口:
模型一次請求最多能處理多少 token.
這三件事放在一起:
Token 決定文本怎麼被切開.
Embedding 幫我們找到相關資料.
上下文窗口決定這些內容最多能帶進去多少.
如果放到 RAG 裡,流程就是:
用戶問題
-> 切成 token
-> 生成向量
-> 檢索相關資料
-> 選擇一部分資料
-> 塞進上下文窗口
-> 模型生成答案
所以這篇文章其實是在回答上一篇留下的問題:
Embedding 找到資料以後,資料怎麼進入模型?
答案就是:
進入上下文窗口.
第一個坑:
以為上下文窗口等於模型記憶.
不是.
它更像一次請求的臨時工作臺.
第二個坑:
以為窗口越大,回答一定越好.
也不是.
窗口大隻是能放更多內容.
但內容是否相關、位置是否合理、提示詞是否清楚,仍然很重要.
第三個坑:
RAG 把資料全塞進去.
這很容易讓模型在一堆材料裡迷路.
應該先篩選,再重排,必要時壓縮.
第四個坑:
忽略輸出空間.
如果輸入塞滿了,模型後面生成答案的空間也會受影響.
第五個坑:
長對話不做總結.
長對話裡,可以定期把歷史壓縮成摘要.
再把摘要放進後續上下文.
這樣比機械保留所有歷史更穩.
這裡還是按之前的習慣,把引用說人話一點.
OpenAI 的文檔裡會講到,多輪對話本質上需要把歷史消息作為上下文提供給模型.
這說明一件事:
模型能接上前文,不是因為它自動永久記住了所有對話.
而是因為系統把相關歷史作為輸入的一部分傳給了它.
所以這篇文章裡說的"上下文窗口是一次請求的可見範圍",可以從這裡理解.
Anthropic 的文檔會直接把 context window 解釋為模型一次能處理的文本範圍.
這對初學者最有幫助的一點是:
上下文窗口有上限.
輸入、輸出、歷史、資料都要在這個上限裡安排.
也就是說,窗口不是一個抽象概念.
它會直接影響你怎麼設計長文檔問答、Agent、RAG 和多輪對話.
這篇論文討論的是長上下文裡一個很現實的問題:
模型不一定總能穩定利用上下文中間的信息.
它提醒我們:
窗口變大,不代表所有位置的信息都一樣容易被模型用到.
所以在寫 RAG 或長上下文應用時,不能只追求塞更多.
還要關心信息的位置、排序和壓縮.
這篇論文是 Transformer 的起點之一.
本文引用它不是為了提前講 Transformer 細節.
而是為了引出下一篇:
模型為什麼能在一段上下文裡看不同 token 之間的關係?
這個問題就會走到 Transformer 和 Attention.
這篇文章先記住一句話:
上下文窗口不是長期記憶.
它是模型一次請求裡能看到和處理的 token 範圍.
所以模型會"忘東西",常見原因是:
那段內容沒有進這次請求
內容太長導致關鍵信息被淹沒
用戶把上下文當成了長期記憶
放到工程裡,我現在會這樣理解:
Embedding 負責找資料.
上下文窗口負責裝資料.
Prompt 負責告訴模型怎麼用資料.
模型負責組織答案.
上下文窗口越大越有價值.
但它不是免死金牌.
真正要做的是:
把最該看的內容,放到模型這次最容易看見的位置.
下一篇我們繼續往下走:
Transformer 為什麼重要?
為什麼現在主流 LLM 基本都繞不開它?
主要看它對多輪對話狀態的說明:模型能接上前文,是因為歷史消息被作為上下文提供給模型.
主要看它對 context window 的直觀解釋:模型一次能處理的內容有範圍,輸入和輸出都要在這個範圍裡安排.
主要看它說明長上下文裡的信息位置問題:上下文變長以後,模型不一定能同等穩定地使用每個位置的信息.
這篇論文提出 Transformer 架構,下一篇聊 Transformer 和 Attention 時會正式展開.
此內容由慣性聚合(RSS閱讀器)自動聚合整理,僅供閱讀參考。 原文來自 — 版權歸原作者所有。