JOVANA
Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

位置編碼與上下文視窗

注意力會一次性讀完所有詞——這也意味著,它本身根本分不清誰先誰後。本指南將講清 Transformer 如何把順序重新注入進去、上下文視窗究竟是什麼,以及為什麼「直接把它做長一點」遠沒有聽起來那麼簡單。

注意力內部的盲點

到現在你已經看過 自注意力如何讓每個詞元去看其他所有詞元,並決定彼此的重要程度。但請停下來想一想它*怎麼*去看。每個詞元都會變成一組 查詢、鍵和值,注意力用縮放點積把查詢和鍵作比較。這個比較本質上是一次求和——而求和並不在意順序。把詞打亂,出來的還是同一組值,只是重新排了排。注意力本身是置換不變的:在它眼裡,「狗咬了人」和「人咬了狗」長得一模一樣。

這正是 Transformer 為它那記漂亮的把戲所付出的代價。它所取代的循環網路是一個詞一個詞地讀句子,所以順序天然地嵌在「閱讀」這個動作裡。Transformer 把這一點拋掉了,改成並行地讀完所有內容——快,但對順序失明。於是順序必須靠人手重新放回去,而且要在注意力開跑之前。

把名牌發下去:位置編碼

每個詞元進來時本就帶著一個嵌入——一個表示它*是什麼意思*的向量。位置編碼再加上第二個向量,表示它*坐在哪裡*。兩者直接相加,於是一個詞元最終的表示裡同時帶著它的含義和它的位置。真正巧妙的地方,在於那個位置向量長什麼樣。

最初的 Transformer 用的是一組固定的正弦和餘弦波,波長各不相同。位置 0 拿到一組波值的組合,位置 1 拿到稍微平移過的一組,依此類推。由於波是平滑重複的,相鄰位置拿到相近的編碼、相距很遠的位置拿到區分度高的編碼——而且關鍵在於,兩個位置之間的*相對*距離會表現為一個穩定的偏移,而這恰恰是注意力能加以利用的東西。這裡沒有任何東西是學出來的;它純粹是幾何,算一次就夠了。

# meaning + place, summed before attention
for pos in range(sequence_length):
    token_vec[pos] = embedding[pos] + position_code(pos)
# position_code(pos): a vector of sin/cos waves
#   wave_k(pos) = sin( pos / 10000^(k/d) )   # many wavelengths k
# nearby pos -> similar code; relative offset -> consistent shift
在注意力看到任何東西之前,逐個詞元地把位置加到含義上。

還有別的方案。有些模型不是把位置固定下來,而是為每個槽位*學*一個位置向量。今天大多數大模型用的是旋轉式編碼(常稱 RoPE):它把查詢和鍵向量旋轉一個隨位置增大的角度——於是兩個詞元之間的點積自然就取決於它們相隔多遠。要記住的關鍵是它們共同的目標:給注意力一種可靠的*相對*距離感,而不只是一個絕對的下標。

上下文視窗究竟是什麼

上下文視窗是模型在某一刻能同時納入視野的詞元上限——你的提示詞,加上它到目前為止生成的全部內容,統統算在一起。當人們說某個模型有「128K 上下文」時,指的就是這個上下文長度:大致相當於大家說話的那個房間有多大。一旦超過它,最早的那些詞元就掉出了邊界;模型根本無法去注意那些已經放不下的東西。

兩點要老實說清。其一,視窗是按*詞元*算的,不是按詞算——由於分詞,一個長詞或生僻詞可能被拆成好幾個詞元,所以「到底能裝多少文字」要比那個標稱數字模糊得多。其二,視窗不是不同對話之間的記憶。一段對話一旦結束,什麼都不會延續下來;每個新請求都要從零把整個上下文重新搭一遍。模型沒有日記本。

那為什麼視窗非得有限不可?因為注意力的開銷隨序列長度的*平方*增長:詞元翻一倍,比較的次數大約翻兩番,因為每個詞元仍要去注意其他每一個詞元。更長的視窗不是某人忘了撥的一個開關——它是一筆實打實的算力與記憶體帳單,每一次前向傳播都得有人來付。

KV 快取:為什麼第一個詞之後生成會加速

模型在寫文字時,靠的是自迴歸解碼:預測一個詞元,把它接上去,再預測下一個。若是天真地做,每個新詞元都得對整段歷史從頭重跑一遍注意力——而歷史只會越來越長,於是每多寫一個詞就越來越慢。解決辦法就是 KV 快取

關鍵的觀察在於:一旦某個詞元的向量算出來了,它們就再也不變。第 5 個詞元的鍵,無論這句話是 6 個詞元還是 600 個詞元,都是同一個。於是模型把每個詞元的鍵和值只算一次、存起來,往後永遠複用。要生成下一個詞元,它只需算*一個*新的查詢,拿它去和所有快取的鍵作比較,再把快取的值組合起來。昂貴的那段歷史只付一次費,而不是每一步都付。

長上下文:遠不止「直接做大一點」

把視窗拉長,會同時撞上三堵牆。注意力的平方級開銷讓純算力爆炸式增長。KV 快取在記憶體裡急劇膨脹。而在短序列上訓練出來的位置編碼,往往*外推*得很差——餵給模型一些遠超它訓練時見過的位置,它的距離感會悄悄地崩掉。

研究者們逐堵牆地去啃。FlashAttention 重新組織了計算方式,把記憶體用得高效得多,讓長序列在不改變數學本質的前提下變得可行。另一些做法把注意力稀疏化或近似化,讓每個詞元只注意一個挑選過的子集,而非所有人。RoPE 一類的編碼可以經過重新縮放,伸到超出其訓練長度的地方。這些沒有一個是免費午餐——每一個都在用精確性、通用性或實現複雜度去換取觸及的範圍。

下面這部分是宣傳裡被跳過的。大視窗意味著模型*能*讀很多——並不意味著它讀得*好*。在「大海撈針」式的測試裡,模型常常能取回埋在長上下文最開頭或最末尾的事實,卻漏掉停在中間的內容。一個長視窗是一種容量,而不是注意力的保證。請把「100 萬詞元」看作模型能吞下多少的上限,而不是它會忠實使用多少的承諾。

具體說來為什麼要在意?因為視窗大小和 KV 快取一起決定了推理成本——每一次回覆背後的延遲與花費。把它們想通,就能把模糊的煩躁(「長文件的回答又差又慢」)變成一個你可以動手處理的診斷;它也解釋了為什麼哪怕是一個帶著巨大視窗的大語言模型,餵給它*相關的*文字,仍然比把你手頭*所有東西*都塞進去要好。