<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Justin Huang (繁體中文)</title><description>Justin Huang blog：一個個人技術部落格，用因果鏈閱讀 AI 論文、Agent 系統和軟體基礎設施。</description><link>https://justinhuangai.github.io/</link><item><title>《Externalization in LLM Agents》：LLM Agent 的認知外部化</title><link>https://justinhuangai.github.io/zh-TW/posts/externalization-in-llm-agents-memory-skills-protocols-and-harness-engineering/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-TW/posts/externalization-in-llm-agents-memory-skills-protocols-and-harness-engineering/</guid><description>這篇綜述把 memory、skills、protocols 和 harness engineering 放進同一個視角：Agent 的進步越來越像是在模型外部重寫任務，而不只是讓模型權重更強。</description><pubDate>Wed, 13 May 2026 08:00:00 GMT</pubDate><content:encoded>很多關於 Agent 的討論，最後都會回到模型排行榜。

哪個模型推理更強，哪個上下文視窗更長，哪個在程式 benchmark 裡得分更高，這些問題當然重要。但 [《Externalization in LLM Agents: A Unified Review of Memory, Skills, Protocols and Harness Engineering》](/papers/2604.08224v1.pdf) 指向了另一個更底層的重心。

它問的不是「模型還能學會什麼」，而是：

**哪些原本壓在模型身上的認知負擔，其實應該被搬到模型外部？**

這就是論文使用 `externalization` 這個詞的原因。它不是單純替 LLM 加幾個外掛，也不是把工具清單塞進 prompt。它描述的是任務表示方式的改變。人類使用紙、地圖、日曆和電腦也是這樣：外部物件沒有讓大腦本身突然變大，卻改變了問題的形狀。回憶變成辨識，心算變成符號操作，臨場發揮變成遵循流程。

LLM Agent 也正在經歷類似的轉變。

![論文 Figure 1：LLM Agent 設計中的外部化主線](/images/posts/externalization-in-llm-agents/figure-1-externalization-overview.webp)

*Figure 1, from Zhou et al., [arXiv:2604.08224](https://arxiv.org/abs/2604.08224), [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/), format-converted for web display, content unchanged.*

## 這篇綜述釐清了什麼

這不是一篇新模型論文，也不是 benchmark 論文。它的價值在於概念整理：替正在發生的工程遷移命名。

早期我們習慣把能力理解成權重裡的東西：更大模型、更好預訓練、更強對齊。接著，能力越來越多地被放進上下文裡組織：prompt、few-shot 範例、RAG、推理軌跡、工具說明。到了實際 Agent 系統，能力又進一步進入更厚的 runtime：記憶庫、檔案系統、技能庫、工具協議、沙箱、審批、日誌、評估器、子 Agent 編排。

論文把這條路徑概括成：

**weights → context → harness**

![LLM Agent 的認知外部化地圖](/images/posts/externalization-in-llm-agents/cognitive-externalization-map.webp)

這張圖表達的是一條工程主線：能力不是只放在模型權重裡。早期系統更多依賴 weights；上下文工程成熟之後，任務可以在 context 裡被臨時組織；到了 Agent，長期可靠性必須落到 harness。Memory 保存狀態，skills 沉澱流程，protocols 規範工具和 Agent 之間的互動，governance 處理權限、審計和失敗恢復。

因此，Agent 的關鍵問題不只是模型有多強，而是外部執行環境能不能把任務穩定地表示、約束和恢復。

## Memory：把時間搬到模型外面

最容易理解的外部化是 memory。

如果 Agent 只依賴上下文視窗，每次執行都只是帶著一段臨時工作記憶。更長的上下文有幫助，但不能消除選擇問題：什麼該放進來，什麼該忘掉，什麼已經過時，什麼只是噪音。更關鍵的是，上下文是暫時性的。沒有外部狀態，任務結束後很多經驗就消失了。

外部 memory 把問題從「模型能不能憑空回憶」改寫成「模型能不能辨識並使用被檢索出的狀態」。這和 cognitive artifacts 的觀點一致：購物清單不是擴大生物記憶，而是改變記憶任務。

對 Agent 來說，memory 可以保存使用者偏好、專案約定、歷史決策、失敗軌跡、領域事實和重複出現的限制。真正困難的地方不只是儲存，而是篩選：哪些狀態值得寫入，什麼時候檢索，檢索多少，如何壓縮，如何避免舊狀態污染新判斷。

所以 memory 不是「更長上下文」的替代品，而是時間維度上的基礎設施。

## Skills：把過程搬到模型外面

第二種外部化是 skills。

這裡的 skill 不是「模型會呼叫某個工具」，而是一段可重用的過程性知識。它可能包含步驟、判斷啟發式、停止條件、升級規則、失敗恢復方式和安全限制。一個成熟的 skill 告訴 Agent：這類任務通常應該怎麼做。

這和工具調用不是同一層抽象。工具提供動作，protocol 定義動作如何被發現與呼叫，skill 則封裝如何把動作組織成一件可重複完成的事。

![論文 Figure 5：技能作為外部化的過程性知識](/images/posts/externalization-in-llm-agents/figure-5-skills-lifecycle.webp)

*Figure 5, from Zhou et al., [arXiv:2604.08224](https://arxiv.org/abs/2604.08224), [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/), reproduced unchanged.*

軟體工程 Agent 讓這件事特別具體。

模型可能知道如何改程式、跑測試、讀錯誤日誌、寫 PR 摘要。但穩定完成任務，還需要很多本地流程：先讀哪些檔案，如何保護使用者改動，什麼時候用 `rg`，什麼時候跑完整驗證，如何處理既有 staged changes，哪些命令不能隨便執行。

如果這些都留給模型每次現場推導，Agent 的行為就容易發生漂移。把它們外部化成 skill，任務就不再是「臨場發明工作流」，而是「選擇並執行已驗證的流程」。

這也是 skills 容易被低估的地方。它不炫，但它直接降低行為方差。

## Protocols：把互動秩序搬到模型外面

第三種外部化是 protocols。

沒有 protocol 的 Agent 互動，本質上是自由文本協商。模型說自己要呼叫工具，工具回傳一段文字，另一個 Agent 再猜那段文字代表什麼。這在 demo 裡可行，在生產系統裡會很脆。

Protocol 把含糊互動變成機器可讀的契約。工具如何發現，參數如何宣告，權限如何表達，錯誤如何回傳，Agent 之間如何委託，使用者審批如何進入流程，這些都不應該只靠 prompt 約定。

它的價值也不只是互操作性。Protocol 還提供治理入口。只要互動是結構化的，系統就能驗證、審計、回放、監控和限權。自由文本很靈活，但很難治理。

## Harness：不是包裝器，而是認知環境

論文最重要的收束，是把 memory、skills 和 protocols 放進 harness engineering 裡。

Harness 不是包在模型外面的一層薄 wrapper。它是 Agent 實際運作的認知環境：控制迴圈、上下文預算、權限模型、沙箱、人工審批、日誌、評估、失敗恢復、子 Agent 編排，都在這裡發生。

![論文 Figure 3：Harnessed LLM Agent 的外部化架構](/images/posts/externalization-in-llm-agents/figure-3-harnessed-agent-architecture.webp)

*Figure 3, from Zhou et al., [arXiv:2604.08224](https://arxiv.org/abs/2604.08224), [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/), reproduced unchanged.*

重點是，harness 不是和 memory、skills、protocols 並列的另一個模組。它是承載並協調這些外部化結構的 runtime。Memory 提供狀態，skills 提供過程，protocols 提供互動結構，harness 決定它們何時被載入、如何彼此影響、要受哪些約束。

這也解釋了為什麼成熟 Agent 越來越像小型操作環境。它們有資源、權限、生命週期、日誌、策略和恢復路徑，而不只是「一個模型加一串工具」。

## 能力邊界正在移動

這篇論文最有用的地方，是它改變了「能力在哪裡」這個問題。

如果能力只在權重裡，改進 Agent 主要就是換模型、微調或重新訓練。如果能力也在上下文裡，prompt 和檢索就成為工程主戰場。如果能力進一步進入 harness，那麼系統設計本身就是能力的一部分。

這不是在貶低模型。相反，越強的模型，越值得被放進更好的外部結構裡。LLM 擅長綜合、判斷和泛化，但它並不天然擅長持久記憶、重複流程、權限治理、長期狀態和跨系統協調。

外部化不是作弊。它是工程上承認邊界，然後重寫任務。

好的 Agent 系統不會逼模型每次都從零開始想辦法。它會把可沉澱的狀態變成 memory，把可重用的流程變成 skills，把可治理的交換變成 protocols，再用 harness 把它們組織成可運行的環境。

## 外部化也有代價

這個框架很漂亮，但外部化不是免費擴展。

Memory 會帶來過時狀態、隱私邊界和檢索污染。Skills 可能過期、過度擬合，或在錯誤組合時變得不安全。Protocols 可能碎片化成互不相容的標準，也可能把系統鎖進僵硬介面。Harness 則會增加複雜度：審批、日誌、沙箱、策略和子流程越多，就越需要工程紀律。

還有評估問題。如果 Agent 能力分布在模型與外部基礎設施之間，我們到底在評測什麼？同一個模型放進不同 harness，表現可能完全不同。「模型能力」和「Agent 能力」已經不是同一件事。

這正是論文的位置。它不是說外部化解決一切，而是把問題放在正確的位置：可靠的 agency 來自模型與環境的共同設計。

## 實用判斷

這篇綜述的釘子句是：**Agent 進步的一條主線，是把認知負擔從模型權重遷移到可檢查、可複用、可治理的外部結構。**

這不是說模型不重要。模型仍然決定理解、規劃和生成的上限。但當 Agent 真正進入長期任務，可靠性不可能只靠一次次現場推理撐住。狀態要能保存，流程要能複用，工具呼叫要有協議，權限和失敗要能被 harness 接住。

外部化的本質，是把任務從「讓模型每次想明白」改成「讓系統把可沉澱的東西留下來」。Memory 保存時間，skills 保存過程，protocols 保存互動秩序，governance 保存邊界，harness 把它們組織成運行環境。

下次看一個 Agent 系統，不要只問它用了哪個模型。先問它把什麼認知負擔搬到了外部，哪些外部結構可檢查、可更新、可回滾。這個問題比模型名更接近系統的真實能力。</content:encoded><category>Paper Reading</category><category>paper-reading</category><category>agents</category><category>externalization</category><category>harness-engineering</category><category>memory</category><category>skills</category><category>protocols</category><category>LLM</category></item><item><title>《AutoCodeBench》：當大語言模型自動生成程式碼基準</title><link>https://justinhuangai.github.io/zh-TW/posts/autocodebench-large-language-models-are-automatic-code-benchmark-generators/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-TW/posts/autocodebench-large-language-models-are-automatic-code-benchmark-generators/</guid><description>AutoCodeBench 論文裡，為什麼 Elixir 這一語言欄值得注意，以及它如何引出自動生成多語言程式碼 benchmark 的難度等價討論</description><pubDate>Sun, 19 Apr 2026 08:19:00 GMT</pubDate><content:encoded>程式碼評測的第一性問題不是誰排第一，而是題目從哪裡來。只要 benchmark 由模型生成、翻譯、過濾，評測就不再只是「模型答題」，也是「模型出題系統」的產物。

[《AutoCodeBench》](/papers/2508.09101v1.pdf) 真正值得拆的地方，是自動生成多語言程式碼 benchmark 時，語言之間的「難」是否等價。Elixir 這一欄之所以值得看，不是因為它能證明大模型最擅長 Elixir，而是因為它暴露了評測生成流程裡的結構性變數。

## 先說這張排名表在講什麼

先看論文裡的原始表格：

![AutoCodeBench 論文 Table 4：不同模型在 20 種程式語言上的 Pass@1](/images/posts/autocodebench-table-4.webp)

在原文 Table 4 裡，每一行是一家參評大模型，每一列是一種程式語言。

交叉處那個數字表示：把這一語言的題交給這個模型，只看它第一次交出的答案，最後有多少題能一次通過測試。論文把這個指標記作 Pass@1。這裡的 `@1` 可以直接理解成「只給一次機會」。

最上面有一行特殊的，叫 Current Upper Bound。它不屬於任何一個模型，而是把所有模型解對的題目取聯集之後算出來的通過率。只要某道題被任意一個參評模型做對，它就進入這個上界。論文表注明確寫了這一點。

Elixir 共 198 道題，Current Upper Bound 是 97.5。粗略折算，約 193 道 Elixir 題至少被一個模型解對。同一行裡，Kotlin 是 89.5，C# 是 88.4，Racket 是 88.3，Python 是 63.3。

這裡有個很容易踩的坑：不能把 Elixir 97.5 和 Python 63.3 直接寫成「Elixir 比 Python 更適合大模型」。

這不是一個嚴謹結論。

因為不同語言的題目來源不同，生成方式不同，難度過濾效果也可能不同。直接拿語言欄的數字去推導語言本體優劣，會把 benchmark 結果解釋過頭。

真正值得追問的是另一個問題：

**同一批模型、同一套評測流程裡，為什麼 Elixir 這一語言欄會穩定處在高位？**

## 這不是單個模型的偶然

先排除一種解釋：也許只是 Current Upper Bound 的統計特性。多個模型取聯集，數字自然會變高。

那就看單個模型行。

Claude Opus 4 推理模式下，Elixir 是 80.3，C# 是 74.9，Kotlin 是 72.5。Claude Sonnet 4 非推理模式下，Elixir 是 74.2，C# 是 72.9，Kotlin 是 72.0。DeepSeek-R1-0528 推理模式下，Python 是 38.8，Elixir 是 77.3。

注意，這裡比較的不是「Claude 和 Elixir」。Claude 是模型，Elixir 是語言，兩者不在同一維度。

這裡比較的是：

**同一個模型，橫向看它在不同程式語言上的表現。**

從 Table 4 橫著看，Elixir 經常出現在高分位置。它不是某一個模型的單點波動，而是多行模型結果裡反覆出現的語言欄現象。

這把問題從總榜拉到了語言欄。

## 題目是怎麼來的

要理解這個現象，得回到 AutoCodeBench 的生成流程。

論文提出了一套叫 AutoCodeGen 的自動化流程：從 Stack-Edu 裡的真實程式碼片段抽取種子，讓模型把它演化成可獨立執行的完整解法；再生成公開和私有測試輸入；把解法和測試輸入丟進多語言沙箱執行，得到真實輸出；最後根據解法和測試函式反向生成題目描述。

這個設計的關鍵是：測試輸出不是模型憑空寫出來的，而是程式實際跑出來的。

這也是 AutoCodeBench 比「讓模型直接編題編測試」更可信的地方。模型可以負責生成候選，沙箱負責給出事實。

但 20 種語言並不是完全用同一種方式生成的。

論文寫得很清楚：Python、C++、Shell、Java、JavaScript、Go 這 6 種語言直接使用完整 AutoCodeGen 流程；其餘 14 種語言雖然理論上也可以走同樣流程，但由於資料資源有限、多樣性不足，論文採用了近似語言翻譯。Table 3 裡，Elixir 的翻譯路徑是 Python → Elixir。

這就埋下一個關鍵變數：

**Elixir 題目很可能不是 Elixir 生態裡的原生任務，而是從 Python 任務翻譯過去的 Elixir 任務。**

翻譯本身不是問題。

問題在於：翻譯之後，題目難度是否還能和其他語言保持等價？

## 難度過濾器的語言盲區

AutoCodeGen 有一個難度控制機制。

它用 DeepSeek-Coder-V2-Lite 對每道題採樣 10 次，並用沙箱驗證答案。10 次全對的題會被丟掉，因為論文認為這類題太簡單，沒有評測價值。

這個邏輯在熱門語言上更容易成立。

如果過濾模型熟悉 Python、Java、C++，那麼它能 10 次做對的題，大概率確實偏簡單；它做不對的題，也更可能有一定難度。

但到了 Elixir 這樣的低資源語言，情況會變複雜。

論文 Section 3.3 專門比較了熱門語言和低資源語言。熱門語言組選的是 Python、C++、Java、C#；低資源語言組選的是 Racket、Shell、Elixir、TypeScript。結果是，熱門語言組裡模型平均 Pass@1 差距較小，範圍是 50.4 到 53.8；低資源語言組裡模型差距更大，範圍是 45.3 到 62.0。

論文隨後給出了一個解釋線索：頂級模型在低資源語言上表現顯著更好，可能是因為 DeepSeek-Coder-V2-Lite 在低資源語言上的能力有限，難以過濾掉簡單題。

這句話非常關鍵。

它沒有直接說「Elixir 高分就是因為題簡單」，但它提供了一個很合理的機制解釋：

**用於過濾簡單題的模型，在低資源語言上可能不夠強。因此，一些對強模型來說並不難的題，沒有被同等強度地過濾掉。**

這比「大模型最擅長 Elixir」解釋得更穩。

Elixir 的高分，可能不是單純來自模型對 Elixir 的語言掌握，而是同時受到三件事影響：

第一，題目來自 Python → Elixir 的近似翻譯。

第二，難度過濾器在低資源語言上可能不如在熱門語言上可靠。

第三，頂級模型和過濾模型之間的能力差，在低資源語言上被放大了。

這三件事疊在一起，就能解釋為什麼 Elixir 這一欄會特別亮眼。

## Lite 版本裡還有一個側面證據

AutoCodeBench 還有一個精簡版，叫 AutoCodeBench-Lite。

Lite 的構造方式提供了一個側面證據：論文先收集所有模型的解題結果，再按每道題被多少模型解出來排序；通過模型少於 2 個的題先丟掉，然後從剩下的題裡按通過次數從低到高選擇約 1,500 道。論文這樣做，是為了保留那些「至少有模型能解，但仍有區分度」的題，讓模型之間的差距更清晰。

在完整 AutoCodeBench 裡，Elixir 有 198 道題。

到了 Lite 版本，Elixir 只剩 61 道。這個數量在 20 種語言裡屬於最低的一組，少於它的只有 JavaScript 的 57 和 PHP 的 60。

這個數字不能單獨證明「Elixir 題都簡單」。

因為題目沒有進入 Lite，可能有兩種原因：一種是太少模型能解，被第一步丟掉；另一種是太多模型能解，在按通過次數從低到高截取時排到了後面。

但結合 Elixir 的 Current Upper Bound 高達 97.5，以及很多單模型行裡 Elixir 得分靠前，這個 Lite 數量變化至少提供了一個側面訊號：

**完整 Elixir 題集的難度分布，很可能和其他語言不完全等價。**

這才是重點。

不是說 Elixir 題「有問題」，也不是說 AutoCodeBench「不可靠」。

而是說：當 benchmark 通過自動生成、近似翻譯和模型過濾構造時，不同語言之間的難度分布會被生成流程影響。

## 還有一層更隱蔽的偏差

論文 Section 4.2 還討論了模型偏差問題。

整個生成流程大量使用 DeepSeek 系列模型：DeepSeek-V3-0324 負責程式碼生成，DeepSeek-R1-0528 負責 Critic 品質審核。論文也承認，這可能對 DeepSeek 家族模型產生有利偏差。為了緩解這一點，作者在簡單題過濾階段使用 DeepSeek-Coder-V2-Lite，試圖形成一種「push-and-pull」的平衡機制。

更關鍵的是，論文的量化分析顯示，偏差並不是一句「誰出題誰佔便宜」就能解釋完。

在 Table 7 的階段性結果裡，Critic 過濾階段確實讓 DeepSeek-R1-0528 得到提升，但 o3 和 Gemini 2.5 Pro 的提升幅度反而比 DeepSeek-V3-0324 更大。論文最後的判斷也很克制：自動流程可能給 DeepSeek 家族帶來有利偏差，但影響較小。

這部分和 Elixir 不是同一個問題，但它們指向同一個核心：

**自動化 benchmark 不是中立機器。**

誰來生成題目，誰來審核題目，誰來過濾簡單題，哪些語言是原生生成，哪些語言是翻譯生成，都會在資料裡留下痕跡。

最後，這些痕跡會安靜地進入排行榜。

## Elixir 97.5 到底說明了什麼

它說明的不是「大模型最擅長 Elixir」。

也不應該被寫成「AutoCodeBench 有問題」。

更準確的說法是：

**AutoCodeBench 暴露了自動生成多語言 benchmark 的一個關鍵難題：語言之間的難度等價，遠比表面上更難。**

表面上看，20 種語言分布均衡，每種語言大約 200 道題。

但分布均衡不等於難度等價。

一道 Python 任務翻譯成 Elixir 後，是否仍然代表 Elixir 生態裡的真實任務？

過濾模型在 Python 上能篩掉簡單題，在 Elixir 上是否也能篩得同樣乾淨？

沙箱能驗證程式輸出，但題目描述、語言習慣、介面設計、標準庫使用方式，是否也同樣自然、同樣公平？

這些問題都不會直接出現在總分裡。

但 Elixir 這一欄讓它們浮了出來。

## 這篇論文真正值得寫的地方

AutoCodeBench 的價值不只是給模型排了個名。

它更重要的貢獻，是把 benchmark 生產方式往前推了一步：讓模型生成題目，讓沙箱驗證輸出，讓 Critic 檢查品質，再用採樣過濾控制難度。論文也明確說，AutoCodeBench 包含 3,920 道題、37,777 個測試用例，覆蓋 20 種程式語言，目標是構造更困難、更實用、更多語言覆蓋的程式碼生成評測集。

這條路肯定會繼續走下去。

因為程式碼評測有一個天然優勢：程式碼可以執行。只要任務定義得足夠清楚，就可以透過測試用例驗證答案。相比寫作、開放問答、複雜推理，程式碼任務更適合形成「生成—執行—驗證—過濾」的閉環。

但 Elixir 這一欄提醒我們：自動化不是免費的。

模型生成題目，會帶入模型熟悉的問題結構。

模型翻譯題目，會帶入源語言的任務形狀。

模型過濾難度，會受自己的語言能力限制。

沙箱可以驗證執行結果，卻不能保證題目在每種語言生態裡都一樣自然、一樣公平。

所以，未來的程式碼評測不只要問哪個模型得分最高，還要問：

- 這套題是怎麼來的？
- 哪些語言是原生生成，哪些語言是翻譯生成？
- 難度過濾器在不同語言上的能力是否一致？
- 人工驗證覆蓋了哪些語言，又沒有覆蓋哪些語言？

論文裡的人類驗證只覆蓋了 Python、C++、Java、JavaScript、Go、Shell 六種語言，準確率為 87.6%；Elixir 並不在這組人工驗證語言裡。這個細節也說明，對翻譯生成語言的品質和難度分布，還需要更細的後續驗證。

## 最後的結論

AutoCodeBench 的釘子句是：**自動生成 benchmark 的難點不是出題，而是讓不同語言裡的「難」保持等價。**

Elixir 在表裡顯眼，不能直接推出「大型模型最擅長 Elixir」。benchmark 的生成、翻譯、過濾、人類驗證覆蓋範圍都會影響分數。尤其當題目由 Python 擴展到其他語言時，語言欄的分數就混合了模型能力、翻譯路徑和難度篩選能力。

但正因為這些變數都擺在檯面上，Elixir 這一欄才值得繼續追。它提醒我們，程式碼評測不是中立容器。任務從哪裡來、怎樣被改寫、由誰過濾，都會進入最終分數。

下次看程式碼 benchmark，不要只問哪個模型 Pass@1 更高。先問題目供應鏈：原生生成還是翻譯生成，難度過濾器在哪些語言上可靠，人工驗證覆蓋了哪些語言。一個 benchmark 只有先解釋清楚題目如何存在，分數才有解釋權。</content:encoded><category>Paper Reading</category><category>paper-reading</category><category>autocodebench</category><category>elixir</category><category>code-generation</category><category>benchmark</category><category>LLM</category></item><item><title>《Attention Residuals》：讓殘差連接也注意力化</title><link>https://justinhuangai.github.io/zh-TW/posts/attention-residuals/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-TW/posts/attention-residuals/</guid><description>Kimi Team 的 Attention Residuals 技術報告：為什麼殘差連接也該「注意力化」，以及 Full AttnRes / Block AttnRes 如何把這個想法做成可訓練、可部署的系統</description><pubDate>Thu, 19 Mar 2026 08:49:27 GMT</pubDate><content:encoded>殘差連接長期被當成訓練穩定性的管道：讓梯度更容易穿過深層網路，讓舊表示不要丟得太快。但如果把模型看成資訊系統，殘差還有更深的問題：深度方向的資訊到底該怎麼路由？

[《Attention Residuals》](/papers/2603.15031v1.pdf) 的核心判斷，是把殘差連接從穩定訓練的管道，重新定義成跨層資訊路由。它問的不是「再加一個模組能漲幾分」，而是為什麼序列維度已經有注意力，深度維度還在用固定加法。

## 0. 先認幾個詞

如果你完全沒有機器學習背景，可以順著這篇報告真正關心的問題，按下面這個順序先建立一個直覺：

- `Transformer`：今天大多數大模型的基礎架構。你可以先把它理解成一台一層一層處理資訊的機器。
- `hidden state`：模型在某一層裡的內部中間表示。可以粗略理解成「模型此刻腦子裡的臨時筆記」。
- `residual connection / 殘差連接`：層與層之間的一條「保留舊內容」的通道。它會先把上一層的內容留住，再把這一層新算出來的東西加上去。
- `residual / 殘差`：更接近「這一層新補上去的增量」，也就是上面那條殘差連接裡新增的那一部分。
- `attention`：從很多資訊裡，挑出「當前最該看哪一部分」的機制。你可以先把它記成「有選擇地看重點」。
- `PreNorm`：在進入一層之前，先把數值尺度調勻，再做後續計算。可以把它想成「先把音量調到合適，再繼續混音」。

## 1. 一句話說清楚

這份技術報告提出了一個問題：

**既然 Transformer 已經用 attention 取代了「序列維度上的遞迴」，為什麼大模型在「深度維度上的資訊聚合」還停留在固定加法？**

現代 LLM 幾乎都在用一種很常見的層結構：先做 PreNorm，再走 residual。直白地說，就是先把數值尺度調勻，再把這一層新算出來的結果加回原輸入。大家熟悉它的一個功能，是讓訓練過程更穩定，深層網路不那麼容易失控。但作者提醒我們，殘差連接其實還有另一個同樣重要、卻長期被忽視的角色：

**它定義了資訊怎樣沿著深度被彙總。**

如果下面的式子看不熟，不用卡住，直接看後面的「翻譯成人話」就夠了。

標準殘差的規則很簡單：

$$
h_l = h_{l-1} + f_{l-1}(h_{l-1})
$$

這裡可以直接把兩部分拆開看：

- $h_{l-1}$：舊內容，也就是上一層已經有的表示
- $f_{l-1}(h_{l-1})$：這一層新算出來的增量，更接近「殘差」這個詞本身

而把這兩部分重新加在一起的整條做法，才更準確地叫「殘差連接」。

把這個遞推式展開，你會得到：

$$
h_l = h_1 + \sum_{i=1}^{l-1} f_i(h_i)
$$

翻譯成人話就是：第 $l$ 層看到的輸入，本質上是「embedding 加上前面所有層輸出的統一加總」。每一層的權重都是 1，沒有選擇，沒有抑制，沒有「這一步我更該看第 3 層還是第 17 層」的機制。

AttnRes 的核心思想只有一句話：

**把 residual 從固定加法，改成沿深度做一次 softmax attention。**

## 2. 舊殘差到底哪裡有問題？

這份技術報告最重要的地方，不在於它提出了一個新公式，而在於它把一個大家已經習慣了的東西重新問題化了。

標準殘差長期被視為「訓練穩定性工具」。只要能讓梯度過得去，它就算完成任務了。但從資訊流角度看，這條路徑其實非常粗糙。

想像你在寫一份持續迭代的文件。每一輪修改，你都不是「挑出最 relevant 的舊版本內容再整合」，而是把之前所有版本一股腦全文追加到文件末尾。第 20 輪的時候，前 3 輪的重要洞察當然還在，但它們已經淹沒在越來越厚的堆疊裡了。

PreNorm 的問題就在這裡。報告引用了 SiameseNorm 的觀察，並進一步強調：在 PreNorm 下，`hidden state` 的量級會隨著深度近似按 $O(L)$ 增長。這裡的 hidden state，說白了就是模型每一層裡那份「內部筆記」。結果就是：

- 越往後的層，看到的是一個越來越膨脹的「歷史總和」
- 早期層的資訊雖然沒有消失，但會被不斷稀釋
- 後面層如果還想「發出聲音」，就被迫輸出更大的量級

這篇技術報告把這個現象叫 `PreNorm dilution`。這是一個非常準確的命名。不是梯度斷了，不是模型炸了，而是每一層的相對貢獻被越來越稀。

報告裡有一條值得抓住的潛台詞：我們在序列維度上早就不滿足於「所有過去 token 一視同仁」了，所以才有了 attention；那為什麼到了深度維度，卻還能接受「所有過去層統一權重相加」？

## 3. AttnRes 到底做了什麼

AttnRes 的形式很乾淨。第 $l$ 層不再機械地接收「前面所有層輸出的總和」，而是對這些歷史表示做一次加權選擇：

$$
h_l = \sum_{i=0}^{l-1} \alpha_{i \to l} \cdot v_i
$$

其中權重 $\alpha_{i \to l}$ 來自一層 softmax。你可以先把 softmax 理解成「把一組分數壓成一組權重，而且所有權重加起來等於 1」，這樣模型才能明確表達「更該看誰、少看誰」：

$$
\alpha_{i \to l} = \operatorname{softmax}\left(w_l^T \operatorname{RMSNorm}(k_i)\right)
$$

如果你沒接觸過 attention，還有一個最省力的理解方式：

- `query`：當前這一層現在想找什麼
- `key`：每一層歷史資訊各自貼著什麼「索引標籤」
- `value`：最後真正被取回來、參與彙總的內容

這裡有三個關鍵設計。

第一，**query 不是當前 hidden state 現算出來的，而是每層一個可學習的 pseudo-query 向量 $w_l$。**  
這有點反直覺。我們平時看到 attention，會自然以為 query 必須來自當前輸入。但作者故意把 query 設計成層級參數，而不是 token 級動態向量。這樣做的好處是：同一個 block 裡的多個 query 可以提前批量算，後面的基礎設施優化才有空間做。

第二，**key/value 直接來自前面層的輸出。**  
也就是說，真正帶來「輸入相關性」的不是 query，而是各層當前樣本上的表示本身。不同樣本經過前面層後得到的 key 不一樣，所以最後的深度注意力依然是 input-dependent 的。

第三，**key 前面加了 RMSNorm。**  
這是個很關鍵的小設計。因為如果不做歸一化，量級大的層會天然在點積裡佔便宜，你得到的就不是「誰更 relevant」，而更像「誰聲音更大」。

用 Python（基於 PyTorch）寫出來，大概就是這樣：

```python showLanguage
import torch
from torch import nn


def attention_residual(
    sources: list[torch.Tensor],
    pseudo_query: torch.Tensor,
    norm: nn.RMSNorm,
) -&gt; torch.Tensor:
    keys = torch.stack([norm(source) for source in sources], dim=0)
    values = torch.stack(sources, dim=0)

    logits = keys @ pseudo_query
    weights = torch.softmax(logits, dim=0)
    return (weights.unsqueeze(-1) * values).sum(dim=0)
```

這個式子看上去像是「把 attention 用在 residual 上」。但更準確的說法是：

**它把 residual connection 從「固定的累加器」改成了「可選擇的深度檢索器」。**

## 4. 這份報告給了想法，也給了工程

**一句話結論：這篇報告提出了 Full AttnRes，它把這個想法推進成了一套可訓練、可部署、算得清帳的工程方案。**

Full AttnRes 讓每一層都 attend 到前面所有層，理論上很好理解，實際上也不算太貴。因為網路深度 $L$ 通常遠小於序列長度 $T$，所以作者說，單純算術量 $O(L^2 d)$ 並不是最可怕的問題。

真正的問題出現在大訓練裡：

- activation recomputation 會把本來可以丟掉的中間層輸出重新變成必須保存的對象
- pipeline parallelism 會讓這些跨層表示需要跨 stage 傳輸
- 一旦每層都要看所有前層，通訊和快取壓力會快速上去

所以他們又提出了 **Block AttnRes**。

做法是把 $L$ 層切成 $N$ 個 block。block 內部先用普通求和攢成一個 block representation，跨 block 再做 attention。這樣一來：

- Full AttnRes：看的是所有歷史層
- Block AttnRes：看的是所有歷史 block 的摘要，再加當前 block 的部分和

本質上是用「摘要級跨層注意力」換取可擴展性。

作者沒有只停在「分塊所以省記憶體」這個層面，而是真的把系統層的帳也算清楚了：

- 訓練階段用 **cross-stage caching**，避免 pipeline 裡重複傳歷史 block
- 推理階段用 **two-phase computation**
- 第一階段並行算 inter-block attention
- 第二階段順序算 intra-block lookback，再用 online softmax merge 合併

從附錄和 `table/memory_access.tex` 裡能看到最硬核的一組數字。按報告給的典型設定：

- 標準 residual：每層 residual 機制 I/O 是 `3d`
- naive Full AttnRes：`130d`
- 優化後的 Full AttnRes：`24d`
- Block AttnRes：`5.5d`
- mHC：`34d`

這組數字特別說明問題。Block AttnRes 不是「便宜到跟標準 residual 一樣」，但它已經從「明顯不現實」降到了「工程上值得試」。而且報告實測給出的代價也不大：

- 訓練端 wall-clock overhead 小於 4%
- 推理端 latency overhead 小於 2%

這也是它像一篇系統級技術報告的原因。很多 paper 的問題在於「idea 是新的，帳是糊的」；這篇在帳本上反而做得很用力。

## 5. 實驗最該看什麼

**比起主實驗裡個別分數的升降，更重要的是 AttnRes 在縮放趨勢、訓練動力學和下游能力上都給出了方向一致的信號。**

### 5.1 縮放定律：不是偶然贏一把

作者先做了五個模型規模的 scaling law 實驗，對比 Baseline、Full AttnRes 和 Block AttnRes。

擬合出來的曲線是：

- Baseline：$1.891 \times C^{-0.057}$
- Block AttnRes：$1.870 \times C^{-0.058}$
- Full AttnRes：$1.865 \times C^{-0.057}$

這三條曲線最重要的資訊不是「斜率差了多少」，而是：

**AttnRes 在整個 compute 範圍裡都持續更低。**

報告給了一個很容易傳播的結論：在 `5.6 PFLOP/s-days` 這個預算點，Block AttnRes 的 loss 相當於 baseline 多花 `1.25x` 算力才能達到的水準。

換句話說，這不是「在某個模型大小上碰巧調對了」，而是有比較穩定的規模收益。

### 5.2 大模型主實驗：不是玩具規模

主實驗不是小模型 toy benchmark，而是基於 Kimi Linear 的一個大配置：

- `48B total / 3B activated parameters`
- 27 個 Transformer blocks，也就是 54 層
- 8-of-256 routed experts + 1 個 shared expert
- 預訓練 `1.4T tokens`

這說明作者不是只在「小模型上做漂亮曲線」，而是真的把這個 residual 改造塞進了一個大訓練配方裡。

### 5.3 訓練動態中，輸出量級不再失控

Baseline 的 output magnitude 會隨著深度一路漲上去。訓練動態圖裡給的數值非常誇張：從前面 block 的 `0.04`、`0.06`、`0.10`，一路漲到後面 block 的 `10.47`、`12.15`。這就是 PreNorm dilution 的視覺化版本。

Block AttnRes 則完全不是這條曲線。它在 block 邊界形成一種週期性重置，量級大致在 `0.21` 到 `1.91` 之間波動，沒有出現一路失控上揚。

這非常重要，因為它說明 AttnRes 不是只在最後 benchmark 上「多拿了幾分」，而是真正在訓練動力學層面改變了表示如何沿深度堆積。

### 5.4 下游任務：提升最明顯的是推理和程式碼

預訓練後，AttnRes 在報告列出的全部評測上都不差於 baseline，幾個最亮眼的點包括：

- MMLU：`73.5 -&gt; 74.6`
- GPQA-Diamond：`36.9 -&gt; 44.4`
- Math：`53.5 -&gt; 57.1`
- HumanEval：`59.1 -&gt; 62.2`
- C-Eval：`79.6 -&gt; 82.5`

GPQA、Math、HumanEval 這種多步推理或程式生成任務漲幅更大，這一點直接對應了報告的機制假設。報告作者的解釋是：如果後層能更有選擇地回收前層表示，那麼 compositional tasks 會更受益。這個解釋是說得通的。

因為複雜推理最怕的不是「資訊不存在」，而是「資訊在網路很深的地方被埋住了」。

## 6. 消融實驗告訴了我們什麼

**消融實驗的關鍵結論，不是「連得更密就更強」，而是「沿深度做輸入相關的選擇性聚合」這件事本身在起作用。**

這份報告的消融做得不錯，因為它不只是證明「有用」，還試圖證明「為什麼有用」。

幾個關鍵結論：

- **DenseFormer 1.767，幾乎和 baseline 1.766 一樣。**  
  這說明「能訪問所有前層」本身還不夠，關鍵在於權重是不是 input-dependent。

- **mHC 到了 1.747，已經明顯變好。**  
  這說明深度維度上的動態混合確實有效。

- **Full AttnRes 到了 1.737。**  
  它比 baseline、DenseFormer、mHC 都更低，說明顯式的 softmax depth attention 是更強的一條路。

- **SWA（只看最近窗口）只有 1.764。**  
  這很有價值。它說明 AttnRes 的收益不只是「多看最近幾層」，而是「能選擇性地看更遠的層」。

- **Block size 從 2、4、8 變化時，loss 都在 1.746 左右。**  
  這就是為什麼作者最後固定大約 8 個 blocks。不是拍腦袋，而是工程和效果之間一個相當好的 sweet spot。

- **input-dependent query 版本做到 1.731，比 Full AttnRes 還好。**  
  這個結果說明當前報告裡的 pseudo-query 設計並不是性能上限，而是一個為基礎設施優化讓路的折衷。也就是說，作者不是不知道更強的寫法，而是主動選了更容易擴展的寫法。

這正是這份報告的工程取捨。你從正文、消融和系統設計裡能更清楚地看到他們的真實取捨：不是盲目追求最低 loss，而是在追求「足夠強，同時真能訓起來」。

## 7. 怎麼看這份報告

第一，這份報告最重要的，不是它發明了一個新模組，而是它把 residual connection 從「訓練穩定性工具」重新提升成了「資訊路由機制」。

這個視角一旦建立起來，很多東西都會被重新理解。殘差不再只是 gradient highway，它還是 depth aggregation rule。你會開始追問：

- 每一層到底能不能選擇性地訪問前層？
- 深度維度上有沒有 attention sink？
- 舊的 residual 變體本質上是不是 depth-wise linear attention？

而這正是報告討論部分有價值的地方。作者把一堆殘差變體統一進了一個 `depth mixing matrix` 的視角裡，進一步指出：

**很多已有方法，本質上都像是在深度維度上做 linear attention；AttnRes 做的是 depth-wise softmax attention。**

這個說法很大膽，但也很有啟發性。它等於是在說：Transformer 當年把序列維度從 recurrence 推到了 softmax attention；AttnRes 試圖把深度維度也推進一步。

第二，這篇技術報告的氣質很像「先把問題提對，再把系統做順」。它沒有執著於把每個部件都做到最 fancy。比如 query 故意做成 layer-specific 參數，而不是 token-dependent 動態向量，性能上未必絕對最強，但它給了 batching、two-phase computation、pipeline cache 一個成立的基礎。很多時候，一篇能落地的技術報告，靠的不是最激進的局部設計，而是整體約束下的取捨。

第三，這份報告中的這句話：

**Why is depth-wise aggregation still fixed while everything else has become adaptive?**

說得挺好。

## 8. 這份報告的邊界

第一，它目前是 **technical report / arXiv preprint**，不是已經過同行評審的會議論文。寫這類文章時，最穩妥的態度不是「它已經證明了未來」，而是「它提出了一個很強的視角，並給出了一套有工程可行性的實作」。

第二，它的大規模結果主要建立在 Kimi Linear 這條架構線上：MoE、KDA/MLA 混合注意力、Moonlight / DeepSeek-V3 風格訓練配方。雖然這不削弱結果本身，但也意味著我們還不能自動把結論外推到所有 dense decoder-only Transformer。

第三，報告自己也承認：Full AttnRes 其實更強，Block AttnRes 是今天硬體約束下的工程解。未來如果顯存、頻寬、interconnect 再往前走，或者更高效的 depth attention 變體出現，今天這版 Block 設計很可能不是終點。

## 9. 這篇報告改變了什麼問題

Attention Residuals 的釘子句是：**殘差連接不只是穩定訓練的管道，也是跨層資訊路由規則。**

這份報告把一個被預設接受的結構重新問題化了。標準 PreNorm 殘差把歷史層近似等權累加，訓練很穩，但資訊路由很粗。AttnRes 追問：既然序列維度已經用 attention 做選擇，深度維度為什麼還只能固定相加？

這個角度比「又多一個模組」更有價值。它把 residual connection 從梯度高速路拉回到資訊系統裡：每一層到底該存取哪些前層，哪些表示該被保留，哪些該被壓低。Block AttnRes 的意義，也不只是省顯存，而是在效果、頻寬、快取和流水線之間給出一個能訓練的大規模折衷。

下次看架構創新，不要只看 benchmark 漲了幾分。先問它重新定義了哪條資訊通路。真正重要的結構改動，往往不是添加能力，而是改變能力流動的路徑。

---

**延伸閱讀**

- [《Sequence to Sequence Learning with Neural Networks》](/zh-TW/posts/sequence-to-sequence-learning-with-neural-networks/)（使用神經網路進行序列到序列學習） — 編碼器-解碼器範式的確立
- [《Neural Machine Translation by Jointly Learning to Align and Translate》](/zh-TW/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/)（通過聯合學習對齊與翻譯實現神經機器翻譯） — 注意力機制的起源
- [《Attention Is All You Need》](/zh-TW/posts/attention-is-all-you-need/)（注意力就是你所需要的全部） — 注意力成為主角，Transformer 的誕生
- [《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》](/zh-TW/posts/bert/)（BERT：用於語言理解的深度雙向 Transformer 預訓練） — 預訓練範式的確立
- [《Scaling Laws for Neural Language Models》](/zh-TW/posts/scaling-laws-for-neural-language-models/)（神經語言模型的縮放定律） — 規模的數學
- [《Language Models are Few-Shot Learners》](/zh-TW/posts/language-models-are-few-shot-learners/)（語言模型是少樣本學習者） — 更大的模型，更善於從上下文中誘發能力
- [《Training Compute-Optimal Large Language Models》](/zh-TW/posts/training-compute-optimal-large-language-models/)（訓練算力最優的大型語言模型） — 怎樣花算力最划算</content:encoded><category>Technical Report Reading</category><category>technical-report-reading</category><category>residual-connections</category><category>transformer</category><category>AI</category><category>LLM</category><category>python</category></item><item><title>《Training Compute-Optimal Large Language Models》：Chinchilla 改變了什麼</title><link>https://justinhuangai.github.io/zh-TW/posts/training-compute-optimal-large-language-models/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-TW/posts/training-compute-optimal-large-language-models/</guid><description>Chinchilla 論文：為什麼大多數大模型其實訓練不足，以及如何聰明地分配算力預算，附真實 Python 核心程式碼</description><pubDate>Wed, 11 Mar 2026 08:58:04 GMT</pubDate><content:encoded>Chinchilla 要糾正的不是「規模是否有效」，而是固定算力下規模該怎麼分。2022 年很多大型模型把錢主要花在參數上，卻沒有給同等增長的資料；結果不是模型太小，而是模型沒有被充分訓練。

[《Training Compute-Optimal Large Language Models》](/papers/2203.15556v1.pdf) 的關鍵結論不是反對大型模型，而是指出參數和資料必須共同吃滿算力預算。更強不等於只把模型做大，更強來自參數、資料和訓練步數之間的正確比例。

## 0. 先認幾個詞

這篇論文會頻繁談到「該把預算花在哪裡」，所以先把下面幾個詞認熟，會更容易抓住重點：

- `算力預算`：你願意為這次訓練總共花多少計算資源。
- `參數量`：模型有多大。
- `token / 詞元`：模型訓練時實際讀進去的最小文本單位，可以粗略理解成「模型看到的字或詞片段」。
- `loss / 損失`：模型整體錯得有多厲害，通常越低越好。
- `scaling law / 縮放定律`：當參數量、資料量、算力變化時，模型表現如何跟著變化。
- `欠訓練 / undertrained`：不是模型太小，而是訓練資料和訓練步數沒跟上，模型的潛力沒有被充分用出來。

## 1. 要解決什麼問題

到 2022 年初，AI 社群已經內化了 [Kaplan 等人（2020）](/zh-TW/posts/scaling-laws-for-neural-language-models/)的一個明確教訓：更大的模型可預測地更好。縮放定律論文已經表明性能遵循冪律，而且在固定算力預算下，你應該把模型做到盡可能大。

業界照做了。到 2022 年春天，GPT-3 有 1750 億參數，訓練了 3000 億詞元。DeepMind 自己的 Gopher 有 2800 億參數，訓練了 3000 億詞元。隨後 Google 又發布了 5400 億參數的 PaLM。趨勢很明顯：往上堆參數。

但一個問題藏在眼皮底下。Kaplan 等人的結論是，當你擴大算力時，大部分預算應該花在模型大小上（N ∝ C^0.73），而相對少地花在訓練資料上（D ∝ C^0.27）。這意味著：把模型做得巨大，用適量的資料訓練就好。

Hoffmann 的團隊問了一個簡單的問題：這真的對嗎？

## 2. 三種獨立方法，同一個答案

這篇論文格外有說服力的地方在於方法論。團隊沒有依賴單一實驗，而是從三個完全獨立的角度切入同一個問題，三者最終收斂到同一個答案。

**方法一：固定算力，改變分配。** 他們訓練了超過 400 個模型，參數量從 7000 萬到 160 億以上，每個模型在模型大小和訓練資料之間有不同的分配比例，但總算力相同。對每個算力水平，他們找出哪個模型大小能讓損失最低。

**方法二：IsoFLOP 曲線。** 他們訓練了 9 種不同大小的模型（從 7000 萬到 100 億參數），使用不同量的資料，特別設計為每組實驗使用大致相同的總算力。然後擬合曲線，找出每個算力水平對應的最優模型大小。

**方法三：擬合參數化損失函數。** 他們把以下方程式擬合到所有訓練結果：

$$
\hat{L}(N, D) = E + \frac{A}{N^\alpha} + \frac{B}{D^\beta}
$$

其中 E 是不可約損失（自然語言的熵：任何模型都無法做得更好），A/N^α 反映模型大小的瓶頸，B/D^β 反映資料量的瓶頸。從擬合出的參數，他們推導出最優的 N 和 D 作為算力的函數。

三種方法一致同意：

$$
N_{\mathrm{opt}} \propto C^a, \quad D_{\mathrm{opt}} \propto C^b, \quad a \approx 0.50, \quad b \approx 0.50
$$

```python showLanguage
def optimal_scaling(compute: float) -&gt; tuple[float, float]:
    a = 0.50
    b = 0.50
    n_opt = compute ** a
    d_opt = compute ** b
    return n_opt, d_opt
```

指數 a ≈ b ≈ 0.5 的真正含義是：隨著算力增長，模型大小和訓練資料應當按近似相同的比例一起擴張。算力增長 10 倍時，兩者都大約增到 3.2 倍；算力翻倍時，兩者都大約增到 1.4 倍。換句話說，模型大小每翻一倍，訓練資料也應該翻一倍。這直接推翻了 Kaplan 等人的結論：他們認為算力主要應該花在模型大小上。

## 3. 為什麼 Kaplan 會錯

這不是誰「做錯了」，而是實驗設定不同，最終導致了不同的最優分配結論。兩個團隊都做了嚴謹的工作。

Kaplan 等人使用了固定的學習率調度，沒有根據訓練長度進行調整。當你讓模型訓練更多步而不調整學習率調度時，性能會下降：不是因為模型本身更差，而是因為優化過程不理想。這讓長時間訓練看起來沒那麼有效，從而偏向了更大的模型配更少的訓練步數。

Hoffmann 的團隊為每次訓練調整了學習率調度，確保每種配置都有公平的機會。這樣做之後，在更多資料上訓練更久，其實比 Kaplan 的數字所暗示的有價值得多。

```python showLanguage
from dataclasses import dataclass
from typing import Literal


@dataclass(frozen=True)
class TrainingConfig:
    n_params: float
    n_tokens: float
    schedule: Literal[&quot;fixed&quot;, &quot;cosine_with_warmup&quot;]
    warmup_steps: int
    total_steps: int
```

## 4. 參數化損失函數

論文的方法三值得細看，因為它給出了一個完整的性能數學模型：

$$
\hat{L}(N, D) = E + \frac{A}{N^\alpha} + \frac{B}{D^\beta}
$$

其中擬合出的常數是：

- E = 1.69 — 不可約損失（自然語言的熵）
- A = 406.4, α = 0.34 — 模型大小項
- B = 410.7, β = 0.28 — 資料量項

這個方程式的結構值得細品。損失有三個組成部分：一個你永遠無法突破的下限（E），參數太少的懲罰（A/N^α），以及資料太少的懲罰（B/D^β）。模型大小的懲罰和資料量的懲罰是相加的：它們爭奪你的注意力和算力預算。

```python showLanguage
def estimated_loss(n_params: float, n_tokens: float) -&gt; float:
    e = 1.69
    a = 406.4
    alpha = 0.34
    b = 410.7
    beta = 0.28
    return e + a / (n_params ** alpha) + b / (n_tokens ** beta)


def optimal_params_and_tokens(compute_flops: float) -&gt; tuple[float, float]:
    alpha = 0.34
    beta = 0.28
    a = beta / (alpha + beta)
    b = alpha / (alpha + beta)
    g = 2.0

    base = compute_flops / 6.0
    n_opt = g * (base ** a)
    d_opt = (1.0 / g) * (base ** b)
    return n_opt, d_opt
```

## 5. 關鍵表格

論文的 Table 1 列出了當時幾個大模型的實際參數量和訓練詞元數，Table 3 則給出了不同模型大小下 compute-optimal 的詞元估計。把兩張表對照著看，就像一份對整個業界的審計報告：

| 模型 | 參數量 | 實際訓練詞元 | Chinchilla 最優詞元 |
|------|--------|-------------|---------------------|
| GPT-3 | 1750 億 | 3000 億 | 3.7 兆 |
| Gopher | 2800 億 | 3000 億 | 5.9 兆 |
| Jurassic-1 | 1780 億 | 3000 億 | 3.7 兆 |
| MT-NLG | 5300 億 | 2700 億 | 11.0 兆 |

每個模型都只用了大約 3000 億詞元。但按照 Chinchilla 的分析，GPT-3 應該用 3.7 兆詞元訓練：是實際的 12 倍以上。Gopher 應該接近 6 兆。最大的 MT-NLG（5300 億參數）應該用 11 兆詞元訓練：是實際訓練資料的 40 倍。

```python showLanguage
from dataclasses import dataclass


@dataclass(frozen=True)
class ModelComparison:
    name: str
    params_billions: float
    tokens_used_billions: float
    optimal_tokens_billions: float


def industry_models() -&gt; list[ModelComparison]:
    return [
        ModelComparison(&quot;GPT-3&quot;, 175.0, 300.0, 3_700.0),
        ModelComparison(&quot;Gopher&quot;, 280.0, 300.0, 5_900.0),
        ModelComparison(&quot;Jurassic-1&quot;, 178.0, 300.0, 3_700.0),
        ModelComparison(&quot;MT-NLG&quot;, 530.0, 270.0, 11_000.0),
    ]
```

規律非常明顯。整個業界不約而同地使用了差不多的訓練資料量：大約 3000 億詞元：不管模型有多大。彷彿所有人都認定 3000 億詞元「夠了」，然後把所有多出來的算力都堆到更大的模型上。Chinchilla 說這恰恰做反了。

## 6. 實證：Chinchilla 對決 Gopher

為了驗證理論，團隊訓練了 Chinchilla：一個 700 億參數的模型，用 1.4 兆詞元訓練。Chinchilla 使用的算力預算和 Gopher（2800 億參數，3000 億詞元）相同：同樣的總訓練成本，只是分配方式不同。

結果很決定性。Chinchilla 在幾乎所有基準測試上都優於 Gopher，儘管小了 4 倍：

- **MMLU**（大規模多任務語言理解）：Chinchilla 67.6% vs. Gopher 60.0% vs. GPT-3 43.9%
- **閱讀理解**（RACE-h）：Chinchilla 73.3% vs. Gopher 71.6%
- **常識推理**（HellaSwag）：Chinchilla 80.8% vs. Gopher 79.2%
- **BIG-bench**：Chinchilla 在大多數任務上優於 Gopher

```python showLanguage
from dataclasses import dataclass


@dataclass(frozen=True)
class ModelConfig:
    name: str
    params_billions: float
    tokens_billions: float
    mmlu_accuracy: float


def chinchilla_vs_gopher() -&gt; tuple[float, float]:
    gopher = ModelConfig(&quot;Gopher&quot;, 280.0, 300.0, 60.0)
    chinchilla = ModelConfig(&quot;Chinchilla&quot;, 70.0, 1_400.0, 67.6)

    gopher_flops = 6.0 * gopher.params_billions * 1e9 * gopher.tokens_billions * 1e9
    chinchilla_flops = 6.0 * chinchilla.params_billions * 1e9 * chinchilla.tokens_billions * 1e9
    return gopher_flops, chinchilla_flops
```

一個小 4 倍的模型在幾乎所有基準上打敗更大的模型：用同樣的算力：這是非常有力的實證。算力沒有浪費，只是從參數重新導向了資料。

## 7. 實際影響

Chinchilla 論文對業界產生了立即而具體的影響。

**更小的模型部署成本更低。** 訓練成本是一次性的，但推理成本：實際執行模型來生成文字的成本：與模型大小成正比，每一次使用者發送請求都要付出。一個 700 億參數的模型，部署成本是 2800 億參數模型的四分之一。如果更小的模型性能還更好，那是雙贏：更好的品質加上更低的成本。

**資料成了瓶頸。** Chinchilla 之前，限制因素是算力：你能搞到多少 GPU？Chinchilla 之後，限制因素變成了資料：你去哪裡找幾兆高品質的詞元？這引發了全業界對訓練資料的爭奪：大規模網路爬取、資料集篩選，最終催生了合成資料運動。

**LLaMA 時刻。** Meta 的 LLaMA（2023 年 2 月）可以說是 Chinchilla 縮放定律最直接的應用。LLaMA-13B 用 1 兆詞元訓練，在大多數基準上超過了 GPT-3（1750 億參數）。LLaMA-65B 用 1.4 兆詞元訓練，與 Chinchilla 和 PaLM-540B 處於同一水平。Meta 明確引用了 Chinchilla 論文，刻意訓練更小的模型、使用遠超過去慣例的資料量。

```python showLanguage
def inference_cost_comparison() -&gt; tuple[float, float]:
    gopher_cost_per_token = 280.0
    chinchilla_cost_per_token = 70.0

    queries_per_day = 1_000_000.0
    tokens_per_query = 500.0

    daily_cost_gopher = queries_per_day * tokens_per_query * gopher_cost_per_token
    daily_cost_chinchilla = queries_per_day * tokens_per_query * chinchilla_cost_per_token
    return daily_cost_gopher, daily_cost_chinchilla
```

## 8. 這篇論文改變了什麼問題

Chinchilla 的釘子句是：**它不是反對大型模型，而是要求參數和資料一起吃滿算力預算。**

這篇論文的力量在於，它沒有喊停 scaling，而是修正了 scaling 的配比。Kaplan 給了產業做大的信心；Chinchilla 追問同樣一筆算力下，大多少、訓多少才是最優。答案不是繼續堆參數，而是讓參數量和訓練 token 近似同步增長。

這個判斷把「模型大小」從單一崇拜裡拉了出來。一個 2800 億參數但只看 3000 億 token 的模型，可能不如一個 700 億參數但看 1.4 兆 token 的模型。能力不是參數量本身，而是算力預算被參數和資料共同吸收後的結果。

下次看大型模型發布，不要先問參數多大。先問它訓練了多少 token、算力預算如何分配、是否欠訓練。真正的規模化不是把模型吹大，而是讓每一份算力都落到能降低 loss 的地方。

---

**論文共讀系列**

- [《Sequence to Sequence Learning with Neural Networks》](/zh-TW/posts/sequence-to-sequence-learning-with-neural-networks/)（使用神經網路進行序列到序列學習） — 編碼器-解碼器範式的確立
- [《Neural Machine Translation by Jointly Learning to Align and Translate》](/zh-TW/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/)（通過聯合學習對齊與翻譯實現神經機器翻譯） — 注意力機制的起源
- [《Attention Is All You Need》](/zh-TW/posts/attention-is-all-you-need/)（注意力就是你所需要的全部） — 注意力成為主角，Transformer 的誕生
- [《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》](/zh-TW/posts/bert/)（BERT：用於語言理解的深度雙向 Transformer 預訓練） — 預訓練範式的確立
- [《Scaling Laws for Neural Language Models》](/zh-TW/posts/scaling-laws-for-neural-language-models/)（神經語言模型的縮放定律） — 規模的數學
- [《Language Models are Few-Shot Learners》](/zh-TW/posts/language-models-are-few-shot-learners/)（語言模型是少樣本學習者） — 更大的模型，更善於從上下文中誘發能力</content:encoded><category>Paper Reading</category><category>paper-reading</category><category>chinchilla</category><category>scaling-laws</category><category>AI</category><category>LLM</category><category>python</category></item><item><title>《Scaling Laws for Neural Language Models》：規模的數學</title><link>https://justinhuangai.github.io/zh-TW/posts/scaling-laws-for-neural-language-models/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-TW/posts/scaling-laws-for-neural-language-models/</guid><description>規模的數學：為什麼更大的模型可預測地更好，附真實 Python 核心程式碼</description><pubDate>Sun, 01 Mar 2026 08:45:39 GMT</pubDate><content:encoded>訓練大型模型首先是一個預算分配問題。參數、資料和算力都要錢；問題不是「越大越好」這種口號，而是下一塊錢該買參數、買資料，還是買更長訓練。

[《Scaling Laws for Neural Language Models》](/papers/2001.08361v1.pdf) 把大型模型訓練從經驗主義推進到預算函數。它沒有發明新架構，卻給了產業一個更硬的判斷工具：在花錢之前，先估計回報曲線。

## 0. 先認幾個詞

如果你平時不常看這種帶公式的論文，先把這幾個詞認熟，後面會順很多：

- `參數量`：模型裡一共有多少可學習參數，也就是模型的大小。
- `資料量`：訓練時一共餵給模型多少文本。
- `算力 / compute`：訓練時總共做了多少計算。你可以先把它當成「電費帳單」。
- `loss / 損失`：模型犯錯有多嚴重，通常越低越好。
- `冪律 / power law`：某個量按固定指數變化的關係；畫在對數座標上，經常會接近一條直線。
- `對數座標`：像 `1、10、100、1000` 這樣按倍數增長的刻度，不是 `1、2、3、4` 那樣均勻加一。

## 1. 要解決什麼問題

到 2020 年初，深度學習社群已經知道更大的模型通常表現更好。但「通常」不是科學。人們回答不了最基本的實際問題：如果我把算力預算翻倍，性能會提升多少？這筆預算該花在更大的模型、更多的資料、還是更長的訓練上？有沒有一個公式？

這篇論文回答了這些問題。不是靠直覺，不是靠經驗法則：靠方程式。

## 2. 冪律：核心發現

論文的核心發現是，語言模型的性能遵循**冪律（power laws）**。在論文測量到的區間內，只要性能主要受某一個因素限制、而不是被另外兩個因素卡住，測試損失（衡量模型預測下一個詞的能力：越低越好）和參數量、資料量、算力在雙對數座標上都近似呈直線關係。

三個方程式總結了整篇論文：

$$
L(N) \approx \left(\frac{N_c}{N}\right)^{\alpha_N}, \quad \alpha_N \approx 0.076
$$

$$
L(D) \approx \left(\frac{D_c}{D}\right)^{\alpha_D}, \quad \alpha_D \approx 0.095
$$

$$
L(C) \approx \left(\frac{C_c}{C}\right)^{\alpha_C}, \quad \alpha_C \approx 0.050
$$

不用被符號嚇到，拆開來看：

- **L** 是測試損失：一個數字就能概括模型表現如何。越低越好
- **N** 是參數數量（模型大小）。參數越多，模型能儲存的模式就越多
- **D** 是訓練用的資料詞元數量。資料越多，可以學到的模式就越多
- **C** 是訓練用的總算力，以 PetaFLOP-days 為單位（1 PetaFLOP-day = 10^15 次浮點運算跑一整天）
- **N_c, D_c, C_c** 是常數（曲線上的參考點）
- **α**（alpha）是指數：它告訴你雙對數圖上直線的斜率。指數越大，代表隨著規模擴大，性能提升越快

關鍵洞察：這些是冪律，不是對數曲線。對數曲線很快就會趨平：輸入翻倍，輸出幾乎不動。冪律則大方得多：至少在論文測量到的區間內，性能沒有出現明顯「撞牆」，而是持續沿著冪律改善。論文也提醒了：這個趨勢不可能無限延伸到零損失，最終一定會變平：但在觀測範圍內，趨勢乾淨俐落。

```python showLanguage
def power_law_loss(x: float, x_c: float, alpha: float) -&gt; float:
    return (x_c / x) ** alpha


def scaling_law_examples() -&gt; dict[str, float]:
    alpha_n = 0.076
    alpha_d = 0.095
    alpha_c = 0.050

    return {
        &quot;10x_params&quot;: 10.0 ** alpha_n,
        &quot;10x_data&quot;: 10.0 ** alpha_d,
        &quot;10x_compute&quot;: 10.0 ** alpha_c,
    }
```

指數本身就說明了問題。資料集大小（α = 0.095）每多一個量級帶來的提升最大。模型大小（α = 0.076）次之。算力（α = 0.050）最小：因為如果不合理分配算力到模型大小和訓練時長上，單純堆算力是浪費的。真正的槓桿在於擴大正確的東西。

## 3. 在論文測試過的範圍內，架構形狀的重要性低於總規模

這是論文最讓人意外的地方。

團隊測試了不同深度（層數）、寬度（隱藏維度）、注意力頭數和前饋維度的 Transformer。在論文測試過的 Transformer 形狀範圍內，只要非嵌入參數量接近，深度和寬度的具體分配對損失影響很小。

一個只有 2 層但隱藏維度巨大的 Transformer？和一個 40 層但隱藏維度很小的 Transformer 損失差不多：前提是非嵌入參數預算接近。

```python showLanguage
from dataclasses import dataclass


@dataclass(frozen=True)
class ArchitectureExperiment:
    n_layers: int
    d_model: int
    n_heads: int
    d_ff: int


def non_embedding_params(config: ArchitectureExperiment) -&gt; int:
    n = config.n_layers
    d = config.d_model
    d_ff = config.d_ff
    return n * (4 * d * d + 2 * d * d_ff + 4 * d)
```

這有一個深遠的含義：你不需要花好幾個禮拜去搜尋「最優」架構。選一個合理的 Transformer 形狀，然後把精力集中在把它做大就好。論文之所以把 embedding 參數從 N 中排除，是因為他們發現 embedding 參數對性能的貢獻遠小於非 embedding 參數：模型的「思考」能力在 Transformer 層裡，不在詞表裡。

## 4. 模型何時過擬合：資料瓶頸

更大不一定更好：如果你的資料集太小的話。論文這裡真正漂亮的地方，是給出了一個統一的二維公式，把模型規模和資料規模如何共同決定性能寫進了一個式子：

$$
L(N, D) = \left[\left(\frac{N_c}{N}\right)^{\alpha_N / \alpha_D} + \frac{D_c}{D}\right]^{\alpha_D}
$$

這個公式說的是：損失不是單獨由模型大小或資料大小決定的：而是由兩者共同決定。當 N 大到第一項消失時，剩下的項表明損失被資料卡住了。當 D 大到第二項消失時，剩下的是模型規模的瓶頸。公式在兩種情況之間平滑過渡，過擬合就是兩項競爭的自然結果。

從這個關係出發，論文給出了一個粗略的經驗門檻：過擬合開始明顯影響性能的臨界點：

$$
D \gtrsim 5 \times 10^3 \times N^{0.74}
$$

白話翻譯：模型越大，需要的資料就越多：但增長是次線性的。大 10 倍的模型只需要約 10^0.74 ≈ 5.5 倍的資料。更大的模型樣本效率更高：它們能從每個訓練詞元中提取更多資訊。

```python showLanguage
def loss_nd(n_params: float, n_tokens: float) -&gt; float:
    n_c = 8.8e13
    d_c = 5.4e13
    alpha_n = 0.076
    alpha_d = 0.095
    ratio = alpha_n / alpha_d
    return ((n_c / n_params) ** ratio + d_c / n_tokens) ** alpha_d


def min_dataset_tokens(n_params: float) -&gt; float:
    return 5_000.0 * n_params ** 0.74
```

按這條關係粗略推算，175B 級別模型要把過擬合壓在論文討論的閾值附近，資料量應接近兆級 token。反過來看，GPT-3 的 3000 億詞元其實並不充裕。這也說明「模型多大、資料餵多少」並不是拍腦袋，而是有可分析的權衡：後來業界重新審視這個配比（最典型的就是 Chinchilla 論文，Hoffmann 等人，2022 年），正是因為意識到了許多大模型的資料其實不夠。

## 5. 計算最優訓練：真正的重點

如果你有固定的算力預算，該怎麼花？這是論文中最有實際意義的問題，答案卻違反直覺。

論文發現最優分配遵循：

$$
N_{\mathrm{opt}} \propto C^{0.73}
$$

$$
B_{\mathrm{opt}} \propto C^{0.24}
$$

$$
S_{\mathrm{opt}} \propto C^{0.03}
$$

翻譯一下：如果算力預算增加 10 倍，你應該把模型做大約 5.4 倍，批次大小增加約 1.7 倍，訓練時長幾乎不變（約多 1.07 倍的步數）。

違反直覺的地方在於：**你應該訓練非常大的模型，然後在遠未收斂之前就停下來。** 大多數人的本能是把一個小模型徹底訓練到收斂。縮放定律說的恰好相反：在相同的算力預算下，一個部分訓練的大模型優於一個完全訓練的小模型。

```python showLanguage
from dataclasses import dataclass


@dataclass(frozen=True)
class ComputeAllocation:
    n_params: float
    batch_size: float
    training_steps: float


def optimal_allocation(compute: float) -&gt; ComputeAllocation:
    return ComputeAllocation(
        n_params=compute ** 0.73,
        batch_size=compute ** 0.24,
        training_steps=compute ** 0.03,
    )


def is_compute_efficient(n_params: float, compute: float) -&gt; bool:
    optimal_n = compute ** 0.73
    return abs(n_params / optimal_n - 1.0) &lt; 0.5
```

這個結果塑造了整個產業。五個月後發表的 GPT-3 直接遵循了這個邏輯：訓練一個在當時規模空前的 1750 億參數模型，而不是把一個小模型徹底訓練完。後來的「Chinchilla」論文（Hoffmann et al., 2022）更新了這些指數，並指出大多數大型模型相對於最優資料分配其實是訓練不足的：但核心洞察，即存在一個可計算的最優權衡，源頭在這裡。

## 6. 臨界批次大小：何時該增加平行度

論文還發現批次大小存在一個「甜蜜點」，而且它取決於當前的損失值：

$$
B_{\mathrm{crit}} \propto L^{-4.8}
$$

隨著訓練推進、損失降低，臨界批次大小會增長。訓練初期損失高，小批次就夠用：每個批次提供的梯度信號夠強。後期模型已經學完了簡單的模式，需要更大的批次來平均掉噪聲才能繼續進步。

低於臨界批次大小時，批次翻倍大致能讓訓練時間減半（完美的平行化）。高於臨界批次大小時，批次翻倍幾乎沒有幫助：只是在浪費算力。

```python showLanguage
def critical_batch_size(loss: float, b_star: float, l_star: float) -&gt; float:
    return b_star * (l_star / loss) ** 4.8
```

這是很實用的工程智慧。很多團隊全程使用固定的批次大小。縮放定律告訴你應該隨著訓練推進逐步增大批次：開始用小批次，模型變好後再放大。

## 7. 這篇論文改變了什麼問題

Scaling Laws 的釘子句是：**訓練大型模型不是玄學試錯，而是預算函數。**

這篇論文最重要的貢獻不是某個指數，而是把「更大更好」從經驗判斷變成可估算的曲線。參數、資料、算力不再只是工程變數，而是可以放進同一張帳本裡比較邊際回報的投資項。

它沒有告訴產業永遠只做更大模型。它告訴產業，在給定假設和觀測範圍內，花錢之前應該先算：擴大參數、增加資料、延長訓練，哪一個最可能帶來更低 loss。GPT-3 的規模決策之所以看起來突然變得合理，背後就是這種預算信心。

下次看模型規模，不要問「是不是軍備競賽」。先問預算函數是什麼，瓶頸變數是誰，邊際收益還剩多少。真正成熟的規模化，不是燒更多錢，而是知道錢燒到哪裡會變成能力。

---

**論文共讀系列**

- [《Sequence to Sequence Learning with Neural Networks》](/zh-TW/posts/sequence-to-sequence-learning-with-neural-networks/)（使用神經網路進行序列到序列學習） — 編碼器-解碼器範式的確立
- [《Neural Machine Translation by Jointly Learning to Align and Translate》](/zh-TW/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/)（通過聯合學習對齊與翻譯實現神經機器翻譯） — 注意力機制的起源
- [《Attention Is All You Need》](/zh-TW/posts/attention-is-all-you-need/)（注意力就是你所需要的全部） — 注意力成為主角，Transformer 的誕生
- [《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》](/zh-TW/posts/bert/)（BERT：用於語言理解的深度雙向 Transformer 預訓練） — 預訓練範式的確立
- [《Language Models are Few-Shot Learners》](/zh-TW/posts/language-models-are-few-shot-learners/)（語言模型是少樣本學習者） — 更大的模型，更善於從上下文中誘發能力
- [《Training Compute-Optimal Large Language Models》](/zh-TW/posts/training-compute-optimal-large-language-models/)（訓練算力最優的大型語言模型） — 如何最有效地分配算力</content:encoded><category>Paper Reading</category><category>paper-reading</category><category>scaling-laws</category><category>AI</category><category>LLM</category><category>python</category></item><item><title>OpenClaw 架構：自託管 AI 助手的工程骨架 🦞</title><link>https://justinhuangai.github.io/zh-TW/posts/openclaw-architecture/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-TW/posts/openclaw-architecture/</guid><description>基於 v2026.3.8 原始碼，拆解一個自託管 AI 助手的工程骨架</description><pubDate>Tue, 24 Feb 2026 08:37:11 GMT</pubDate><content:encoded>![OpenClaw](/images/openclaw-logo-text-dark.webp)

[上一篇](/zh-TW/posts/openclaw-ecosystem/)聊了生態，這一篇拆架構。

OpenClaw 的程式碼量不小：43 萬行 TypeScript。但這篇文章不看程式碼量，看複雜性怎麼被分配。一個自託管 AI 助手要同時處理二十多個聊天入口、多個 Agent、工具呼叫和權限邊界；架構問題不是「功能怎麼塞進去」，而是「哪些複雜性應該留在執行時，哪些應該推給模型、CLI 和策略層」。

## 0. 先認幾個詞

如果你沒有系統架構背景，也沒關係。先記住 6 個詞，後面會順很多：

- `架構`：系統如何分層、每層分別負責什麼。
- `Channel / 頻道層`：負責和外部聊天平台「說同一種協議」的適配層。
- `Gateway / 閘道`：中央調度者，負責路由訊息、管理會話和 Agent。
- `Node / 執行節點`：真正去拍照、跑命令、操作裝置的執行端。
- `workspace / 工作區`：Agent 的設定、記憶、技能和會話檔案所在的本地目錄。
- `sandbox / 沙箱`：把高風險操作隔離起來的受限執行環境。

## 1. 3 層架構：Channel → Gateway → Node

先看全景圖：

![OpenClaw Architecture](/images/openclaw-architecture-zh-TW.svg)

你可以把 OpenClaw 想成一家公司的 3 個部門：

| 誰 | 幹什麼 | 打個比方 |
|------|------|--------|
| **Channel（頻道層）** | 對接 WhatsApp、Telegram、Discord、飛書等 20+ 聊天平台 | 前台：負責接電話、收郵件、接待來訪 |
| **Gateway（閘道）** | 中央調度，管理所有會話和 Agent | 大腦：誰的訊息該給誰處理、怎麼回覆，全在這裡決定 |
| **Node（執行節點）** | 在裝置上幹活：拍照、錄螢幕、跑命令 | 手和腳：大腦說「拍張照」，它去執行 |

整個系統的核心就是 Gateway：一個常駐運行的程序，預設只監聽本機（`127.0.0.1:18789`），不對外網開放。想遠端存取？走 Tailscale 隧道，不直接暴露埠號。這個設計叫「Loopback-First」，安全上很討巧：什麼埠號都不開，就沒有攻擊面。

為什麼只用一個程序不搞分散式？原因很實際：WhatsApp 協議要求同一時間只能有一個裝置在線，你搞兩個程序反而會打架。與其為了「架構正確」多搞一堆協調邏輯，不如老老實實一個程序管到底。對絕大多數個人使用者來說，夠用了。

## 2. 一條訊息的旅程

你在 Telegram 上跟你的 OpenClaw 說了一句「幫我查下明天台北天氣」，發生了什麼？

**第 1 步：接入。** Telegram 適配器收到訊息，把 Telegram 格式的資料翻譯成 OpenClaw 內部統一格式。不管訊息從哪個平台來，翻譯完都長一個樣。

**第 2 步：驗身份、找路由。** 你是誰？允不允許跟 Agent 說話？如果是陌生人，先彈一個配對碼讓主人批准。通過後，路由引擎決定這條訊息交給哪個 Agent。

**第 3 步：組裝上下文。** 從磁碟讀出你之前的聊天記錄，載入 Agent 的性格設定（SOUL.md）、行為規則（AGENTS.md），再從記憶庫裡語義搜尋相關內容。組裝成一份完整的「大腦簡報」。

**第 4 步：問大模型。** 這份簡報發給 Anthropic、OpenAI、DeepSeek、MiniMax、GLM、Qwen、Gemini 等大模型提供商（你設定了哪個就用哪個），模型一個字一個字地串流吐出回覆。

**第 5 步：執行工具。** 如果模型說「我需要跑個命令查天氣」，執行時會攔截這個請求，根據安全策略決定在哪裡執行（管理員的命令直接跑，陌生人的命令丟到 Docker 沙箱裡跑），然後把結果餵回給模型繼續生成。

**第 6 步：回覆。** 最終答案按 Telegram 的格式要求（字數限制、Markdown 規則等）拆好，發回給你。聊天記錄寫入磁碟。

整個鏈路中，真正慢的是第 4 步：等大模型吐第一個字。其他步驟基本都是毫秒級。

## 3. 頻道適配層：一次開發，到處說話

OpenClaw 對接了二十多個聊天平台，但不是每個平台都從零寫一套。它抽象了一層「適配器」，每個平台的適配器只負責四件事：

- **登入**：WhatsApp 掃 QR Code、Telegram 填 Bot Token、iMessage 用 macOS 原生能力：各平台各的規矩
- **翻譯進來的訊息**：不管是文字、圖片、回覆還是表情，統統翻譯成內部統一格式
- **管門禁**：誰能私聊、群裡要不要 @才回覆、哪些群允許
- **翻譯出去的回覆**：把 Agent 的回覆適配成各平台能顯示的格式

內建的有六個大平台（WhatsApp、Telegram、Discord、Slack、Signal、iMessage），其他三十多個（飛書、LINE、Matrix、Mattermost 等）透過外掛接入。

這層抽象最大的好處是：Agent 完全不需要知道訊息來自哪個平台。你在 WhatsApp 上聊的 Agent 和在 Telegram 上聊的是同一個，邏輯完全複用。頻道只是「傳話筒」，換一個傳話筒不影響說話的人。

## 4. Agent 執行時與工作區

Agent 執行時的核心來自 Pi-mono（一個開源程式設計 Agent），嵌入在 Gateway 裡面跑。

### 「一切皆文字」的工作區

這裡的關鍵設計是：每個 Agent 的所有組態，都落在可以直接打開編輯的文字檔裡：

```
workspace/
├── AGENTS.md          # 定義 Agent 是誰、怎麼做事（相當於履歷+工作手冊）
├── SOUL.md            # 靈魂設定：性格、價值觀（寫好就不該改）
├── USER.md            # 記錄使用者資訊：你叫什麼、喜好是什麼
├── MEMORY.md          # 長期記憶：Agent 主動記下的重要事情
├── HEARTBEAT.md       # 定時任務：比如每天早上報天氣
├── memory/            # 日記本
│   └── YYYY-MM-DD.md  # 每天一頁，只追加不修改
├── skills/            # 技能包
└── sessions.json      # 會話記錄
```

沒有資料庫，沒有專用管理介面，純文字檔。想改 Agent 性格？打開 SOUL.md 改兩行就行。想看它記住了什麼？打開 MEMORY.md 直接看。這種「你能看到它的全部大腦」的透明感，是很多封閉 AI 產品做不到的。

### 每一輪對話怎麼跑

1. **分清身份**：你是管理員直聊、朋友私聊、還是群裡有人 @了它？不同來源，安全等級不同
2. **組裝記憶**：讀聊天記錄、載入性格和規則、搜尋相關的長期記憶，拼成一份完整上下文
3. **問大模型**：發給設定的模型，支援 fallback：主力模型掛了自動切備用
4. **幹活+記住**：執行工具呼叫，更新聊天記錄

上下文的組裝不是把所有東西一股腦塞進去。不相關的技能不載入、不需要的工具不注入：省 token 就是省錢。

## 5. 4 層記憶：讓 AI 真的「記住你」

普通聊天機器人關掉視窗就失憶了。OpenClaw 不會。它有 4 層記憶，從最深層的「我是誰」到最淺層的「剛才聊了啥」：

| 層 | 是什麼 | 打個比方 |
|------|--------|------|
| **SOUL** | 人格設定，永遠不變 | 你的性格：出生就定了 |
| **TOOLS** | 當前裝了哪些技能 | 你今天帶了哪些工具出門 |
| **USER** | 關於你的長期記憶 | 你的老朋友記得你愛吃什麼 |
| **Session** | 當前這次對話 | 此刻正在聊的話題 |

幾個巧妙的機制：

**每日日記。** 每天的對話會自動寫進 `memory/2026-03-12.md`（只追加，不覆蓋）。下次開聊時，Agent 會自動翻看今天和昨天的日記，保持連續感：「你昨天說要買的那本書，買了嗎？」

**自動搶救記憶。** 聊得太長，上下文快塞滿了怎麼辦？OpenClaw 會偷偷在背景跑一個隱形輪次，把重要資訊存進 MEMORY.md，然後壓縮掉舊內容。你察覺不到這個過程，但關鍵資訊不會丟。這個機制叫 Pre-Compaction。

**語義搜尋。** 你說「之前聊過的那個部署問題」，它能從記憶裡搜出來：不是靠關鍵字精確比對，而是靠語義理解。底層是向量搜尋（SQLite-vec）+ 傳統關鍵字搜尋（BM25）雙管齊下。

**跨平台認人。** 你在 Telegram 上跟它聊了半小時，切到 WhatsApp 繼續聊，它認得你。同一個人在不同平台的 ID 會被連結到同一個身份，共享同一份記憶。但群聊的記憶是隔離的：你在群裡說的話，不會洩露到私聊裡。

## 6. 工具系統：把複雜性轉移到 CLI

OpenClaw 的工具系統很克制。

很多 AI Agent 框架會預置大量專用工具。OpenClaw 只保留 4 個核心工具：

| 工具 | 一句話 |
|------|------|
| **Read** | 讀檔案 |
| **Write** | 寫檔案 |
| **Edit** | 改檔案 |
| **Bash** | 跑命令 |

這條路線的前提是：命令列已經封裝了大量現實世界能力。查天氣可以呼叫 `curl`，寄信可以呼叫 CLI，查資料庫可以走 `psql`。系統不為每個場景預製工具，而是把通用執行介面交給模型。

代價也很明確：模型必須知道該呼叫什麼命令、如何組合命令、什麼時候停止。複雜性沒有消失，只是從工具清單轉移到了模型能力、CLI 生態和權限策略。

在這 4 個核心工具之上，還有 55 個內建 Skills 和 ClawHub 技能市場。Skills 負責把高頻流程封裝起來，避免每次都從 Bash 層重新規劃。

**MCP 被放在旁路，而不是主路徑。** MCP 是 Anthropic 推出的工具協議標準，很多 AI 框架都在接入。OpenClaw 的主路徑選擇 CLI/Unix，並用內建的 `mcporter` 做橋接。這不是單純反標準，而是把工具生態的中心放在命令列可組合性上。

**自我擴展把能力沉澱成 skill。** OpenClaw Agent 遇到不會做的事，可以生成 skill、安裝、再在發現問題時修改重新載入。關鍵不在「自動變強」，而在臨場解法能否沉澱成可複用流程。

## 7. 多 Agent 路由：一個大腦，多個人格

一個 Gateway 可以同時跑好幾個 Agent，各管各的。路由規則長這樣：

```json showLanguage
{
  &quot;bindings&quot;: [
    { &quot;agentId&quot;: &quot;home&quot;, &quot;match&quot;: { &quot;channel&quot;: &quot;whatsapp&quot;, &quot;accountId&quot;: &quot;personal&quot; } },
    { &quot;agentId&quot;: &quot;work&quot;, &quot;match&quot;: { &quot;channel&quot;: &quot;slack&quot; } },
    { &quot;agentId&quot;: &quot;bot&quot;, &quot;match&quot;: { &quot;channel&quot;: &quot;discord&quot;, &quot;guildId&quot;: &quot;123456&quot; } }
  ]
}
```

翻譯一下：WhatsApp 私人號來的訊息給「家庭助手」處理，Slack 的給「工作助手」，Discord 某個伺服器的給「社群機器人」。三個 Agent 完全隔離：各自有自己的性格、記憶、技能和安全策略。

## 8. 安全：3 道門

你的 Agent 跑在你自己的伺服器上，能執行命令、讀寫檔案：安全當然是大事。OpenClaw 的安全模型分 3 道門：

**第一道門：你是誰？（私聊配對）** 陌生人給你 Agent 發訊息，Agent 不會直接回。它會發一個 6 位配對碼，你在已認證的頻道裡確認了，對方才能用。這是預設行為：關掉就意味著任何知道你號碼的人都能白嫖你的 API 額度。

**第二道門：VIP 通道（白名單）。** 信任的人可以直接加到白名單裡（`allowFrom`），跳過配對直接聊。

**第三道門：群裡別亂回（群組規則）。** 群聊裡 Agent 預設只在被 @ 時才回覆，不會對著群裡每條訊息都冒泡：既省 token，又不擾民。

再往下還有縱深防禦：工具權限五層過濾、Docker 沙箱隔離（陌生人的命令在沙箱裡跑）、安全稽核命令（`openclaw security audit`）。這些層是獨立的：配對碼洩露了？沙箱還在。沙箱被繞過了？工具策略還限制著能調什麼。

## 9. 幾個判斷

**單程序不是偷懶，是務實。** 對個人使用者來說，一個程序管到底比搞分散式靠譜得多。這個架構的天花板在哪？大概是同時在線的訊息量超過單機處理能力的時候：對絕大多數人來說，這一天不會來。

**頻道抽象層是最值錢的一層。** 二十多個平台的差異全封裝在適配器裡，Agent 完全不操心訊息從哪來。想加一個新平台？寫個適配器就行，Agent 邏輯一行不改。這個解耦做得非常乾淨。

**安全設計是認真的，但落地仍有差距。** 架構上從身份、沙箱到工具策略做了縱深防禦。但 Kaspersky 稽核發現了 512 個漏洞（8 個嚴重級別），說明藍圖畫得好和實際安全之間，隔著持續的工程投入。

**4 個核心工具的路線是一場複雜性轉移。** 它賭的不是「工具越少越好」，而是模型能力、CLI 生態和權限策略加在一起，能比大量預製工具更可擴展。如果模型能力不足，Bash 會變成風險入口；如果權限收得住，這條路線會把工具系統做得很薄。

**最終的檢驗標準不是架構有多漂亮，而是跑得穩不穩。** 這篇分析基於原始碼靜態閱讀。實際效果：高併發下的 Gateway 穩定性、沙箱真的能不能防住攻擊、跨頻道認人的邊界情況：需要更多真實使用資料來驗證。

架構只是骨架，生產環境才是考場。</content:encoded><category>OpenClaw</category><category>AI</category><category>open-source</category><category>openclaw</category></item><item><title>《Language Models are Few-Shot Learners》：GPT-3 與上下文學習</title><link>https://justinhuangai.github.io/zh-TW/posts/language-models-are-few-shot-learners/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-TW/posts/language-models-are-few-shot-learners/</guid><description>更大的模型，更善於從上下文中誘發能力，附真實 Python 程式碼</description><pubDate>Wed, 11 Feb 2026 08:22:54 GMT</pubDate><content:encoded>GPT-3 的問題意識不是「模型還能不能繼續變大」，而是任務適配一定要改參數嗎？在 BERT 範式裡，每個新任務都要微調；這意味著資料、訓練流程和部署版本都會跟著膨脹。

[《Language Models are Few-Shot Learners》](/papers/2005.14165v4.pdf) 的關鍵，是把任務適配從參數更新搬進上下文。模型權重固定，任務說明和少量示例進入 prompt，能力在推理時被臨時組織出來。

## 0. 先認幾個詞

如果你對 GPT-3 這類模型的工作方式還沒有概念，先記住下面幾個詞就夠了：

- `語言模型`：給它一段前文，它的基本工作就是預測下一個詞。
- `參數量`：模型裡可學習的數字總數。你可以粗略把它理解成模型的「腦容量」。
- `prompt / 提示詞`：你餵給模型看的任務說明、示例和輸入。
- `上下文視窗`：模型一次能看到多少文本的容量。例子太多，塞不進去，就沒辦法一起看。
- `few-shot / one-shot / zero-shot`：分別指給多個例子、給一個例子、完全不給例子。
- `in-context learning / 上下文學習`：不改模型參數，只靠 prompt 裡的說明和例子，就讓模型臨時學會怎麼做任務。

## 1. 要解決什麼問題

[BERT](/zh-TW/posts/bert/) 確立的「預訓練 + 微調」範式在 2020 年已經是主流做法。效果很好，但論文指出了三個根本問題。

第一，每個新任務仍然需要一個標註資料集。標註資料的獲取成本高，且很多實際任務根本沒有對應的標註集。

第二，微調後的模型在測試基準上的表現，不一定反映真實泛化能力。模型可能只是學到了訓練資料中的虛假相關性（spurious correlations）：在基準集裡得分很高，但換個分布就崩了。

第三，人類不是這樣學習的。人類看一兩個例子，聽一句自然語言指令，就能完成新任務。而當時的 NLP 系統，每個新任務都需要成千上萬條標註資料來微調。

論文的出發點是：如果模型足夠大，它在預訓練階段累積的知識是否足以讓它直接「讀懂」任務描述和少量示例，然後給出答案？

## 2. 核心想法：不更新參數，只給提示

GPT-3 的評估方式和之前所有大模型都不一樣。它定義了三種設置，全部不涉及梯度更新：

**少樣本（Few-Shot）**：給模型一段任務描述，加上 10 到 100 個示例（具體數量取決於上下文窗口能裝多少），然後讓它完成新的輸入。不更新權重，不做反向傳播。

**單樣本（One-Shot）**：只給一個示例。這最接近人類學習新任務的方式：有人給你演示一次，你就上手。

**零樣本（Zero-Shot）**：連示例都不給，只有一句自然語言指令。這是最難的設置，但也是最實用的：如果模型真的「理解」了任務本身，它不應該需要任何例子。

```python showLanguage
from dataclasses import dataclass
from typing import Union


@dataclass
class ZeroShot:
    instruction: str
    prompt: str


@dataclass
class OneShot:
    instruction: str
    example: tuple[str, str]
    prompt: str


@dataclass
class FewShot:
    instruction: str
    examples: list[tuple[str, str]]
    prompt: str


EvalSetting = Union[ZeroShot, OneShot, FewShot]


def build_prompt(setting: EvalSetting) -&gt; str:
    if isinstance(setting, ZeroShot):
        return f&quot;{setting.instruction}\n{setting.prompt}&quot;

    if isinstance(setting, OneShot):
        example_input, example_output = setting.example
        return f&quot;{setting.instruction}\n{example_input} {example_output}\n{setting.prompt}&quot;

    lines = [setting.instruction]
    lines.extend(f&quot;{example_input} {example_output}&quot; for example_input, example_output in setting.examples)
    lines.append(setting.prompt)
    return &quot;\n&quot;.join(lines)
```

論文把這種能力叫做**上下文學習**：模型在預訓練時，從海量文本中隱式地學到了各種任務的模式；推理時，示例被拼接進上下文，模型在前向傳播的過程中「識別」出當前任務是什麼，然後完成它。論文用「元學習」來描述這個過程：預訓練是外循環，上下文學習是內循環。

這和微調的區別是根本性的。微調修改模型參數來適應任務，上下文學習不修改任何東西：同一個模型，同一組權重，只靠輸入文本的不同，就能切換任務。

## 3. 模型架構與規模

GPT-3 的架構本身沒有新發明。它和 GPT-2 一樣，就是 [Transformer](/zh-TW/posts/attention-is-all-you-need/) 的解碼器部分，一層層堆起來。改動只有一處：在 Transformer 層中交替使用稠密注意力和局部帶狀稀疏注意力（來自 Sparse Transformer）。

真正不同的是規模。論文訓練了 8 個不同大小的模型，參數量跨越三個數量級：

| 模型 | 參數量 | 層數 | 隱藏維度 | 注意力頭數 |
|------|--------|------|----------|-----------|
| GPT-3 Small | 1.25 億 | 12 | 768 | 12 |
| GPT-3 Medium | 3.5 億 | 24 | 1024 | 16 |
| GPT-3 Large | 7.6 億 | 24 | 1536 | 16 |
| GPT-3 XL | 13 億 | 24 | 2048 | 24 |
| GPT-3 2.7B | 27 億 | 32 | 2560 | 32 |
| GPT-3 6.7B | 67 億 | 32 | 4096 | 32 |
| GPT-3 13B | 130 億 | 40 | 5140 | 40 |
| **GPT-3 175B** | **1750 億** | **96** | **12288** | **96** |

1750 億參數，96 層，96 個注意力頭，隱藏維度 12288。上下文窗口 2048 個詞元。這個規模在當時是前所未見的：比 GPT-2（15 億參數）大了 100 多倍。

```python showLanguage
from dataclasses import dataclass


@dataclass(frozen=True)
class GPT3Config:
    n_params: int
    n_layers: int
    d_model: int
    n_heads: int
    d_head: int
    d_ff: int
    n_ctx: int


def gpt3_175b() -&gt; GPT3Config:
    return GPT3Config(
        n_params=175_000_000_000,
        n_layers=96,
        d_model=12_288,
        n_heads=96,
        d_head=128,
        d_ff=49_152,
        n_ctx=2_048,
    )
```

論文訓練這些模型的目的很明確：驗證縮放定律（scaling laws）。之前 Kaplan 等人的研究（就是這篇論文的共同作者之一）已經表明，語言模型的損失和參數量之間存在平滑的冪律關係。GPT-3 把這個假設推到了 1750 億參數的規模，看看上下文學習能力是否也遵循同樣的規律。

答案是肯定的：模型越大，少樣本學習的提升越陡。零樣本性能隨模型規模穩步上升，少樣本性能的上升速度更快。這意味著大模型不只是「更準」，它們在利用上下文資訊的效率上也更高。

## 4. 訓練資料

GPT-3 在大約 3000 億個詞元上訓練，資料來自五個來源：

| 資料集 | 詞元數 | 訓練佔比 |
|--------|--------|----------|
| Common Crawl（過濾後） | 4100 億 | 約 60% |
| WebText2 | 190 億 | 約 22% |
| Books1 | 120 億 | 約 8% |
| Books2 | 550 億 | 約 8% |
| 英文 Wikipedia | 30 億 | 約 3% |

注意一個關鍵細節：資料集的取樣比例和它們的大小不成正比。品質更高的資料集（WebText2、Books、Wikipedia）被過取樣了：WebText2 在訓練中被看了 2.9 遍，Wikipedia 被看了 3.4 遍，而 Common Crawl 連一遍都沒看完（0.44 遍）。論文有意用少量過擬合的代價，換取更高品質的訓練訊號。

Common Crawl 的原始資料有 45TB，經過三步處理：（1）基於與高品質參考語料的相似度做過濾；（2）文件級模糊去重；（3）混入已知的高品質資料集來增加多樣性。過濾後剩下 570GB，約 4100 億詞元。

所有模型在 V100 GPU 上訓練，使用微軟提供的高頻寬叢集。

## 5. 實驗結果

論文在二十多個資料集上做了評估，覆蓋 9 大類任務。以下是幾個關鍵結果。

**語言建模**：在 Penn Tree Bank 上，GPT-3 少樣本困惑度（perplexity，衡量模型對文本的「意外程度」，越低越好）達到 20.50，刷新了當時的紀錄。在 LAMBADA（需要根據長距離上下文預測最後一個詞）上，零樣本準確率 76.2%，少樣本 86.4%，大幅超過之前的最好結果。

**翻譯**：GPT-3 從未被專門訓練過翻譯，但在法語→英語翻譯上，少樣本 BLEU 分數達到 32.6，超過了無監督神經機器翻譯的最好結果。不過英語→法語方向（25.2 BLEU）和微調模型的差距仍然很大。一個有趣的發現：GPT-3 翻譯成英語的能力明顯強於從英語翻譯出去，這和訓練資料以英語為主有直接關係。

**閉卷問答**：在 TriviaQA 上，少樣本準確率（exact match）71.2%，超過了同一閉卷設置下經過微調的模型。模型不看任何參考文件，純靠參數裡儲存的知識回答問題。

**SuperGLUE**：在這個綜合基準上，GPT-3 的少樣本表現已經接近一些經過微調的強基線，但仍落後於當時最強的專門微調系統。

**合成任務**：論文還設計了一些專門測試上下文學習能力的新任務。比如給模型幾個「造新詞」的例子（定義一個不存在的詞，然後用它造句），GPT-3 能正確地學會並使用這個新詞。再比如三位數加法，少樣本準確率接近 100%（兩位數加法也幾乎完美），但四五位數時急劇下降。

```python showLanguage
from typing import Callable, Protocol


class AutoregressiveModel(Protocol):
    def forward(self, tokens: list[int]) -&gt; list[list[float]]:
        ...


def in_context_learning(
    model: AutoregressiveModel,
    examples: list[tuple[str, str]],
    query: str,
    tokenize: Callable[[str], list[int]],
    decode: Callable[[list[int]], str],
    sample_from: Callable[[list[float]], int],
    eos_token: int,
) -&gt; str:
    prompt_lines = [f&quot;{example_input} {example_output}&quot; for example_input, example_output in examples]
    prompt_lines.append(query)
    prompt = &quot;\n&quot;.join(prompt_lines)

    context = tokenize(prompt)
    output_tokens: list[int] = []

    while True:
        logits = model.forward(context)
        next_token = sample_from(logits[-1])
        if next_token == eos_token:
            break
        output_tokens.append(next_token)
        context.append(next_token)

    return decode(output_tokens)
```

## 6. 資料污染問題

論文在第四章花了大量篇幅討論一個棘手的問題：訓練資料和測試資料的重疊。

GPT-3 的訓練資料包含大量網路文本，而很多測試基準的內容也在網路上公開存在。這意味著模型可能在訓練時就「看過」了測試題。論文團隊嘗試在訓練前移除這些重疊，但由於一個處理流程中的 bug，部分重疊沒有被完全清除。而重新訓練一遍的成本太高，不現實。

他們的做法是：為每個基準建構一個「乾淨子集」（移除所有和訓練資料有 13-gram 重疊的樣本），然後對比模型在完整集和乾淨子集上的表現。結論是：大多數基準上，污染對結果的影響很小。但 PIQA 和 Winograd 兩個資料集存在可疑的表現下降，論文對這些結果加了星號標註。

這種誠實在當時相當罕見。多數論文對資料污染問題避而不談。GPT-3 不僅主動調查，還開發了系統化的檢測工具。這本身就是對後續研究的一個貢獻。

## 7. 局限性

論文在第五章對自身局限性的討論相當坦率。

**文本連貫性**：GPT-3 在文件級別仍然會出現語義重複、自相矛盾、甚至生成無意義句子的情況。生成品質雖然比 GPT-2 好了很多，但長文本的連貫性仍然不夠。

**常識物理**：GPT-3 對「把起司放進冰箱，它會融化嗎？」這類常識物理問題表現不佳。它能處理語言層面的推理，但對物理世界的理解仍然是膚淺的。

**單向性的代價**：作為自回歸模型，GPT-3 只能從左往右看。論文承認，在需要雙向上下文的任務上（比如判斷兩個句子裡同一個詞的含義是否相同），GPT-3 的少樣本表現不如經過微調的雙向模型。這說明在 GPT-3 的自回歸設定下，這類任務並不是它的強項；單向建模目標本身會帶來結構性偏好。

**取樣效率**：GPT-3 在預訓練階段看了約 3000 億個詞元，遠超人類一生接觸的文本量。論文明確指出，即使少樣本學習在推理時很高效，預訓練的資料需求仍然巨大。

**推理成本**：1750 億參數的模型，推理成本高且不方便部署。論文提到蒸餾（distillation，用大模型的輸出來訓練小模型）是一個可能的方向，但在千億參數量級上還沒有嘗試過。

## 8. 社會影響

論文用了整整一個章節（第六章）討論社會影響，涵蓋三個方面。

**濫用風險**：GPT-3 生成的新聞文章，人類評估者的識別準確率接近隨機猜測（約 52%）。模型越強，生成的虛假文本越難辨別。論文團隊表示已經在監控論壇和聊天群，追蹤惡意使用的趨勢。

**偏見**：論文用大量實驗測試了 GPT-3 在性別、種族和宗教方面的偏見。例如，在職業-性別關聯測試中，GPT-3 更傾向於將「nurse」和女性關聯、將「banker」和男性關聯。在宗教-情感關聯中，「Islam」更多地與暴力相關詞共現。論文承認這些偏見來自訓練資料，但沒有給出解決方案。

**能源消耗**：訓練 GPT-3 需要大量算力，論文引用了估算資料但沒有公布具體的能耗數字。不過論文指出，一旦訓練完成，模型可以被多次使用到不同任務上，比為每個任務單獨訓練模型更節能。

## 9. 這篇論文改變了什麼問題

GPT-3 的釘子句是：**任務適配可以從參數更新搬進上下文。**

這篇論文真正改寫的不是「模型更大所以更強」這件事，而是人和模型的介面。過去要讓模型做新任務，通常要收集資料、微調參數、保存一個新版本。GPT-3 展示了另一條路：權重不動，任務說明、示例和輸入一起進入上下文，模型在推理時完成臨時適配。

這不是微調的終結。它只是把一部分適配成本從訓練階段挪到了上下文設計階段。Prompt 不再只是輸入文本，而變成一種輕量任務程式。上下文窗口也不再只是容量參數，而是臨時工作區。

下次看大型模型，不要只問參數有多少。更該問：這個系統把任務適配放在哪裡？放在權重裡、上下文裡，還是外部工具和工作流裡？這個問題比模型大小更接近產品形態。

---

**論文共讀系列**

- [《Sequence to Sequence Learning with Neural Networks》](/zh-TW/posts/sequence-to-sequence-learning-with-neural-networks/)（使用神經網路進行序列到序列學習） — 編碼器-解碼器範式的確立
- [《Neural Machine Translation by Jointly Learning to Align and Translate》](/zh-TW/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/)（通過聯合學習對齊與翻譯實現神經機器翻譯） — 注意力機制的起源
- [《Attention Is All You Need》](/zh-TW/posts/attention-is-all-you-need/)（注意力就是你所需要的全部） — 注意力成為主角，Transformer 的誕生
- [《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》](/zh-TW/posts/bert/)（BERT：用於語言理解的深度雙向 Transformer 預訓練） — 預訓練範式的確立
- [《Scaling Laws for Neural Language Models》](/zh-TW/posts/scaling-laws-for-neural-language-models/)（神經語言模型的縮放定律） — 規模的數學：為什麼更大的模型可預測地更好
- [《Training Compute-Optimal Large Language Models》](/zh-TW/posts/training-compute-optimal-large-language-models/)（訓練算力最優的大型語言模型） — 如何最有效地分配算力</content:encoded><category>Paper Reading</category><category>paper-reading</category><category>gpt-3</category><category>AI</category><category>LLM</category><category>python</category></item><item><title>OpenClaw 生態：從開源專案到 AI 助手平台 🦞</title><link>https://justinhuangai.github.io/zh-TW/posts/openclaw-ecosystem/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-TW/posts/openclaw-ecosystem/</guid><description>重新看 OpenClaw：星標不是生態，能持續降低供給和使用摩擦的結構才是生態。</description><pubDate>Tue, 03 Feb 2026 08:09:32 GMT</pubDate><content:encoded>![OpenClaw](/images/openclaw-logo-text-dark.webp)

星標不是生態。

生態的第一性問題是摩擦：開發者供給一個能力有多難，使用者發現和使用一個能力有多難，系統把這兩邊連起來有多穩。OpenClaw 的可看之處不在於它熱過，而在於它開始圍繞同一個 Agent 執行時長出分工結構。

## 0. 先認幾個詞

- `生態`：不只是一個倉庫，而是一組圍繞同一核心能力分工協作的產品和工具。
- `執行時 / runtime`：Agent 長期跑著、調度工具、管理上下文和執行任務的核心進程。
- `技能市場`：讓 Agent 發現和安裝新能力的入口。
- `工作流引擎`：把高頻多步驟任務封裝成可重複執行的流程。
- `飛輪`：供給、使用、回饋互相強化的增長迴路。

## 1. 先看約束，不先看熱度

Peter Steinberger 2011 年創辦 PSPDFKit，長期做 PDF 底層技術，服務過 Apple、Dropbox 等客戶。後來他離開一線開發，又在 2025 年重新回到 AI 產品原型上。

Clawdbot 最早是在這個背景下出現的：大約一小時提示詞生成專案雛形，11 月發布，名字向 Anthropic 的 Claude 致意。2026 年 1 月底，Anthropic 發來商標警告，專案三天內從 Clawdbot 改成 Moltbot，再改成 OpenClaw。改名事件帶來了 48 小時 34,000 星標的注意力。

但熱度只能解釋流量，解釋不了生態。生態要看結構。

## 2. 生態不是倉庫數量，是分工是否成立

OpenClaw 已經不只是一個執行時倉庫。它開始拆出不同層：

| 元件 | 角色 | 信號 |
|------|------|------|
| **OpenClaw** | 核心 Agent 執行時 | 140k+ stars |
| **ClawHub** | 技能市場 | 5.4k stars |
| **Lobster** | 工作流引擎 | ~800 stars |
| **acpx** | 無頭命令列工具 | ~780 stars |
| **openclaw-ansible** | 自動化部署 | ~490 stars |
| **nix-openclaw** | Nix 宣告式配置 | ~530 stars |

這張表真正要看的不是數字，而是邊界。執行時、技能市場、工作流、部署工具沒有全部塞回一個大倉庫，而是圍繞 Agent 生命週期拆開。

一個平台要長出來，必須同時降低兩種摩擦：供給側建立能力的摩擦，使用側發現能力的摩擦。OpenClaw 的生態判斷也應該從這兩點開始。

## 3. 三個結構信號

### 渠道覆蓋：降低使用摩擦

WhatsApp、Telegram、Slack、Discord、Signal、iMessage、飛書、LINE、Matrix 等二十多個平台接入，表面上是渠道多，底層是入口位置變了。

使用者不需要為了 AI 助手切到一個新 App。Agent 進入已有聊天渠道，任務從原本的對話場景裡出現。採用率的關鍵不是「功能多」，而是第一次使用的阻力足夠低。

### ClawHub：降低能力發現摩擦

技能市場用向量搜尋做語義匹配。使用者不用翻目錄，只要描述「我要一個能發郵件的技能」，系統就能匹配候選。

這解決的是發現問題，不是供給問題。一個技能市場能不能成立，仍取決於高品質第三方技能是否持續出現，是否有人維護，是否有審核和評價。現在只能說發現入口有了，供給飛輪還沒有被證明。

### Lobster：降低重複規劃摩擦

Agent 執行多步驟任務時，最大的隱性成本往往不是單步工具呼叫，而是每次都重新規劃。

Lobster 把高頻操作封裝成可復用工作流：流程一旦被驗證，就不必每次交給模型現場組織。它還把關鍵步驟放進審批關卡，減少 Agent 一路自動執行帶來的權限風險。

這類工具的價值不在「自動化更多」，而在把可重複任務從臨場推理變成可檢查流程。

## 4. 風險也來自結構

**安全是生態前提。** Kaspersky 審計發現 512 個漏洞，其中 8 個嚴重級別。高權限 Agent、第三方技能、二十多個入口放在一起，攻擊面天然會擴大。安全問題不是普通技術債，而是信任債。

**商業模式還沒有閉合。** MIT 協議、無訂閱、使用者自帶 API key，這些選擇降低了採用門檻，也讓持續營運成本更難回收。開源熱度不能自動轉化成維護預算。

**貢獻結構仍要觀察。** 如果路線、產品判斷和核心實現長期依賴少數人，生態厚度就會被單點依賴限制。平台和明星專案的區別，在於非創始人貢獻能否穩定增長。

## 5. 怎麼繼續看

OpenClaw 的問題不再是「有沒有熱度」。熱度已經出現過。

接下來應該看四個指標：

1. 安全預設值有沒有收緊：配對、白名單、沙箱、審批是否從可選變成預設。
2. 技能生態有沒有自發供給：高品質技能是否持續發布、安裝、更新和審核。
3. 非創始人貢獻占比有沒有上升：生態是否能離開單點驅動。
4. 治理結構有沒有清晰：維護權、審核權、路線決策能否被社群理解和繼承。

星標證明不了生態。能持續降低供給摩擦和使用摩擦的結構，才證明生態開始存在。</content:encoded><category>OpenClaw</category><category>AI</category><category>open-source</category><category>openclaw</category></item><item><title>《BERT》：語言理解預訓練範式的確立</title><link>https://justinhuangai.github.io/zh-TW/posts/bert/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-TW/posts/bert/</guid><description>預訓練範式的確立，附真實 Python 程式碼</description><pubDate>Sat, 31 Jan 2026 08:52:21 GMT</pubDate><content:encoded>BERT 要解決的不是「再造一個更深的 Transformer」，而是 NLP 任務長期被拆碎的問題。問答、分類、序列標註各有一套模型和資料介面，模型學到的語言知識很難被統一複用。

[《BERT》](/papers/1810.04805v2.pdf) 的關鍵不只是雙向 Transformer，而是把語言理解任務統一成預訓練表示的複用問題。先學一套通用表示，再用很薄的任務層去適配不同問題；這個轉向比模型結構本身更重要。

## 0. 先認幾個詞

如果你對大模型的訓練流程還不熟，可以先記住這篇論文最關鍵的幾個詞：

- `Transformer`：BERT 用的基礎架構。你可以先把它理解成一台能同時結合左右上下文來處理句子的機器。
- `預訓練`：先在海量通用文本上學語言本身，而不是一開始就做具體任務。
- `微調`：把預訓練得到的能力，再稍微調整到某個具體任務上。
- `雙向`：預測一個位置時，不只看左邊，也看右邊。
- `MLM / 遮蔽語言模型`：故意遮住一部分詞，讓模型根據上下文把它們猜出來。
- `NSP / 下一句預測`：讓模型判斷兩個句子是不是前後相連。

## 1. 要解決什麼問題

2018 年，NLP 領域有一個尷尬的現狀：每個任務都需要從頭設計專門的模型架構。做問答要一套模型，做情感分析要另一套，做命名實體識別又要一套。每個任務的標註資料都不多，訓練出來的模型也很難遷移到其他任務。

當時已經有人嘗試過預訓練的思路。ELMo 用雙向 LSTM 學習上下文表示，但它只是把預訓練的特徵「拼」到下游模型上，架構本身還是任務專用的。OpenAI GPT 用 Transformer 做預訓練再微調，但它只能從左往右看（單向），每個詞只能關注它前面的詞，看不到後面的。

論文認為，單向語言模型在需要深度雙向上下文的語言理解任務上存在明顯限制。比如：

&gt; &quot;他拿起了 _____ ，開始演奏。&quot;

如果只看左邊（&quot;他拿起了&quot;），填空的答案可能是任何東西。但看到右邊（&quot;開始演奏&quot;），你立刻知道是某種樂器。對很多語言理解任務來說，雙向上下文天然更有利。

## 2. 核心想法：遮住一些詞，讓模型猜

BERT 的解法很直覺：既然雙向語言模型沒法用傳統方式訓練（因為每個詞會間接「看到自己」），那就換個訓練目標。

**遮蔽語言模型（Masked Language Model，MLM）**：隨機遮住輸入中 15% 的詞：具體做法是把它們替換成一個特殊標記 \[MASK\]：然後讓模型根據上下文猜出被遮住的詞。這個想法來自心理學中的完形填空（Cloze task，1953 年 Taylor 提出），就像上面那道填空題一樣。

遮住之後，模型必須同時利用左邊和右邊的上下文來預測，雙向理解就自然產生了。

但直接把所有被選中的詞替換成 \[MASK\] 標記會引入一個問題：微調時輸入裡不會出現 \[MASK\]，預訓練和微調之間產生了不匹配。論文的解決方案：被選中的 15% 的詞裡，80% 替換成 \[MASK\]，10% 替換成隨機詞，10% 保持不變。這樣模型不能只靠「看到 \[MASK\] 就知道要預測」，而是必須對每個位置都保持理解能力。

```python showLanguage
import random
from typing import Optional, Sequence


def mask_tokens(
    tokens: Sequence[str],
    mask_prob: float = 0.15,
    vocab: Optional[Sequence[str]] = None,
) -&gt; tuple[list[str], list[int], list[str]]:
    if vocab is None:
        vocab = tokens

    masked = list(tokens)
    positions: list[int] = []
    labels: list[str] = []

    for i, token in enumerate(tokens):
        if random.random() &lt; mask_prob:
            positions.append(i)
            labels.append(token)

            r = random.random()
            if r &lt; 0.8:
                masked[i] = &quot;[MASK]&quot;
            elif r &lt; 0.9:
                masked[i] = random.choice(vocab)

    return masked, positions, labels
```

## 3. 第二個預訓練任務：下一句預測

很多 NLP 任務（比如問答、自然語言推理）需要理解兩個句子之間的關係，但語言模型本身不直接建模這種關係。

論文加了第二個預訓練任務：**下一句預測（Next Sentence Prediction，NSP）**。給模型兩個句子 A 和 B，50% 的情況下 B 是 A 的真實下一句，50% 的情況下 B 是從語料庫裡隨機抽的。模型要判斷 B 是不是 A 的下一句。

這個任務的設計很簡單，但論文的消融實驗（ablation study，逐一去掉某個元件看效果變化）顯示，去掉 NSP 會明顯降低問答和自然語言推理任務的表現；不過後來也有工作（如 RoBERTa）對 NSP 的必要性提出了不同結論。

```python showLanguage
from dataclasses import dataclass


@dataclass
class PretrainingExample:
    tokens: list[str]
    segment_ids: list[int]
    masked_positions: list[int]
    masked_labels: list[str]
    is_next: bool
```

## 4. 模型架構

BERT 的架構其實沒有什麼新發明。它就是 [Transformer](/zh-TW/posts/attention-is-all-you-need/) 的編碼器部分，一層層堆起來。

論文給出了兩個規格：

- **BERT_BASE**：12 層，隱藏維度 768，12 個注意力頭，參數量 1.1 億
- **BERT_LARGE**：24 層，隱藏維度 1024，16 個注意力頭，參數量 3.4 億

BERT_BASE 的參數量和 OpenAI GPT 差不多，方便直接對比。兩者最關鍵的區別只有一個：GPT 用的是單向注意力（每個詞只能看左邊），BERT 用的是雙向注意力（每個詞能看到所有位置）。

輸入的表示由三部分相加構成：

- **詞嵌入（Token Embedding）**：WordPiece 分詞，詞表 30,000
- **段嵌入（Segment Embedding）**：標記這個詞屬於句子 A 還是句子 B
- **位置嵌入（Position Embedding）**：告訴模型詞的位置（BERT 用的是學習得到的位置編碼，不是正弦餘弦）

每個輸入序列的開頭都加一個特殊標記 \[CLS\]，它在最後一層的隱藏狀態被用來做句子級別的分類（比如 NSP、情感分析）。兩個句子之間用 \[SEP\] 分隔。

```python showLanguage
import torch
from torch import nn


class BertEmbeddings(nn.Module):
    def __init__(
        self,
        vocab_size: int,
        hidden_size: int,
        max_positions: int,
        type_vocab_size: int = 2,
        dropout: float = 0.1,
    ) -&gt; None:
        super().__init__()
        self.token_embedding = nn.Embedding(vocab_size, hidden_size)
        self.segment_embedding = nn.Embedding(type_vocab_size, hidden_size)
        self.position_embedding = nn.Embedding(max_positions, hidden_size)
        self.layer_norm = nn.LayerNorm(hidden_size, eps=1e-12)
        self.dropout = nn.Dropout(dropout)

    def forward(
        self,
        token_ids: torch.Tensor,
        segment_ids: torch.Tensor,
    ) -&gt; torch.Tensor:
        position_ids = torch.arange(token_ids.size(1), device=token_ids.device).unsqueeze(0)
        position_ids = position_ids.expand_as(token_ids)
        embeddings = (
            self.token_embedding(token_ids)
            + self.segment_embedding(segment_ids)
            + self.position_embedding(position_ids)
        )
        embeddings = self.layer_norm(embeddings)
        return self.dropout(embeddings)


class BertModel(nn.Module):
    def __init__(
        self,
        vocab_size: int,
        hidden_size: int = 768,
        max_positions: int = 512,
        num_layers: int = 12,
        num_heads: int = 12,
        dropout: float = 0.1,
    ) -&gt; None:
        super().__init__()
        self.embeddings = BertEmbeddings(vocab_size, hidden_size, max_positions, dropout=dropout)
        encoder_layer = nn.TransformerEncoderLayer(
            d_model=hidden_size,
            nhead=num_heads,
            dim_feedforward=4 * hidden_size,
            dropout=dropout,
            activation=&quot;gelu&quot;,
            batch_first=True,
        )
        self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)

    def forward(
        self,
        token_ids: torch.Tensor,
        segment_ids: torch.Tensor,
    ) -&gt; torch.Tensor:
        hidden = self.embeddings(token_ids, segment_ids)
        return self.encoder(hidden)
```

## 5. 微調：一個模型適配所有任務

BERT 最優雅的地方在於微調的簡單性。預訓練完成後，不管什麼下游任務，做法幾乎一樣：在 BERT 上面加一層任務相關的輸出層，然後用少量標註資料微調所有參數。

- **文本分類**（情感分析、自然語言推理）：取 \[CLS\] 位置的輸出向量，接一個線性分類器
- **問答**（給一段文章，找出答案的起止位置）：對每個詞的輸出向量做兩次線性變換，分別預測答案的開始和結束位置
- **序列標註**（命名實體識別）：對每個詞的輸出向量接一個分類器，逐詞預測標籤

預訓練可能需要幾天，但微調通常只要幾十分鐘到幾小時（單塊 TPU 上大部分任務不超過 1 小時）。這個效率差異是「預訓練 + 微調」範式的核心吸引力。

## 6. 實驗結果

論文在 11 個 NLP 任務上做了實驗，全部刷新了當時的紀錄。

**GLUE 基準**（通用語言理解評估，包含 8 個子任務）：
- BERT_LARGE 平均分 80.5%，比之前最好的 OpenAI GPT 高出 7.7 個百分點
- 在最大的子任務 MNLI 上提升了 4.6%

**SQuAD v1.1**（閱讀理解問答，Test F1）：
- BERT_LARGE 單模型 + TriviaQA 資料：F1 91.8，超過人類表現（91.2）
- BERT_LARGE 集成模型 + TriviaQA 資料：F1 93.2

**SQuAD v2.0**（包含無法回答的問題）：
- F1 達到 83.1，比之前最好的系統高出 5.1 個百分點

**SWAG**（常識推理）：
- 準確率 86.3%，比 OpenAI GPT 高出 8.3 個百分點

論文還做了模型大小的消融實驗，發現一個重要結論：更大的模型在所有任務上都更好，即使在標註資料很少（只有 3,600 條）的任務上也是如此。這和當時的直覺（小資料集容易過擬合大模型）不太一樣，說明預訓練提供的知識可以有效緩解小資料集的過擬合（模型把訓練資料「死記硬背」，對新資料表現差）風險。

## 7. 訓練細節

**預訓練資料**：BooksCorpus（8 億詞）+ 英文 Wikipedia（25 億詞），只使用文本段落，去掉了列表、表格和標題。論文強調必須用文件級語料而不是打亂的句子級語料，這樣才能提取長距離的上下文關係。

**分詞**：WordPiece，詞表大小 30,000。WordPiece 會把不常見的詞拆成更小的子詞單元，比如 &quot;playing&quot; 可能被拆成 &quot;play&quot; + &quot;##ing&quot;。

**最佳化器**：Adam，學習率 1e-4，前 10,000 步線性熱身，然後線性衰減。批次大小 256 個序列，最大序列長度 512。

**硬體**：BERT_BASE 在 4 塊 Cloud TPU（16 塊 TPU 晶片）上訓練 4 天。BERT_LARGE 在 16 塊 Cloud TPU（64 塊 TPU 晶片）上訓練 4 天。

**Dropout**：所有層的 dropout 率為 0.1。激活函數用的是 GELU（Gaussian Error Linear Unit），而不是 Transformer 原版的 ReLU。

## 8. 這篇論文改變了什麼問題

BERT 的釘子句是：**語言理解可以先學成一種可複用表示，再分發給具體任務。**

它的關鍵不只是 Transformer 編碼器，也不只是「雙向」。真正的動作是用 MLM 和 NSP 把無標註文本變成預訓練信號，再讓分類、問答、序列標註這些任務共享同一套輸入格式和表示底座。

這改變了 NLP 的工程單位。過去每個任務都像一個單獨專案；BERT 之後，許多任務變成同一份預訓練表示上的薄適配層。模型不再從每個任務的標註資料裡重新學語言，而是先擁有一套通用語言底座，再學任務邊界。

下次看一個理解模型，不要只問它是不是雙向。更該問：它學到的表示能否被複用？任務差異是被塞進模型主體，還是被壓縮到一個輕量介面裡？

---

**論文共讀系列**

- [《Sequence to Sequence Learning with Neural Networks》](/zh-TW/posts/sequence-to-sequence-learning-with-neural-networks/)（使用神經網路進行序列到序列學習） — 編碼器-解碼器範式的確立
- [《Neural Machine Translation by Jointly Learning to Align and Translate》](/zh-TW/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/)（通過聯合學習對齊與翻譯實現神經機器翻譯） — 注意力機制的起源
- [《Attention Is All You Need》](/zh-TW/posts/attention-is-all-you-need/)（注意力就是你所需要的全部） — 注意力成為主角，Transformer 的誕生
- [《Scaling Laws for Neural Language Models》](/zh-TW/posts/scaling-laws-for-neural-language-models/)（神經語言模型的縮放定律） — 規模的數學：為什麼更大的模型可預測地更好
- [《Language Models are Few-Shot Learners》](/zh-TW/posts/language-models-are-few-shot-learners/)（語言模型是少樣本學習者） — 更大的模型，更善於從上下文中誘發能力
- [《Training Compute-Optimal Large Language Models》](/zh-TW/posts/training-compute-optimal-large-language-models/)（訓練算力最優的大型語言模型） — 如何最有效地分配算力</content:encoded><category>Paper Reading</category><category>paper-reading</category><category>bert</category><category>AI</category><category>LLM</category><category>python</category></item><item><title>《Sequence to Sequence Learning with Neural Networks》：編碼器-解碼器範式的起點</title><link>https://justinhuangai.github.io/zh-TW/posts/sequence-to-sequence-learning-with-neural-networks/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-TW/posts/sequence-to-sequence-learning-with-neural-networks/</guid><description>編碼器-解碼器範式的確立，附真實 Python 程式碼</description><pubDate>Sat, 24 Jan 2026 08:41:08 GMT</pubDate><content:encoded>Seq2Seq 的底層矛盾很樸素：輸入和輸出長度都不固定，傳統翻譯流水線能處理，卻很難端到端最佳化。[《Sequence to Sequence Learning with Neural Networks》](/papers/1409.3215v3.pdf) 把問題改寫成兩個介面：一個網路讀完整段輸入，另一個網路逐步生成輸出。

這篇論文的價值不在於把機器翻譯一次解決，而在於證明端到端序列映射可行。它的弱點也同樣清楚：所有資訊都要擠過一個固定長度向量。後來的注意力機制和 Transformer，都是從這個瓶頸往外長出來的。

## 0. 先認幾個詞

如果你沒有機器學習背景，可以先按這篇論文的工作流程，記住下面幾個詞：

- `Seq2Seq / 序列到序列`：把一段輸入序列直接變成另一段輸出序列，比如把英文句子變成法文句子。
- `Encoder / 編碼器`：負責把輸入從頭到尾讀完。
- `Decoder / 解碼器`：負責把輸出一個詞一個詞寫出來。
- `RNN / 循環神經網路`：一種只能按順序處理文本的舊架構。
- `LSTM`：RNN 的改良版，更擅長在長句子裡記住前面的資訊。
- `向量 / vector`：你可以先把它理解成「用一串數字壓縮出來的一份摘要」。

## 1. 要解決什麼問題

2014 年，深度神經網路已經在圖像識別等任務上取得突破，但像機器翻譯這種「直接把一段可變長序列映射到另一段可變長序列」的任務，神經網路還不擅長。

一句英語可能是 5 個詞，翻譯成法語變成 7 個詞。輸入和輸出的長度不同，而且沒有簡單的一一對應關係。

傳統的解決方案是把大量人工設計的規則和統計特徵拼在一起，形成一個複雜的翻譯流水線（統計機器翻譯，SMT）。它能用，但每個元件都要單獨調參，而且很難整體最佳化。

論文提出了一個更簡潔的思路：能不能用一個端到端的神經網路，直接從源語言序列映射到目標語言序列？

## 2. 核心架構：編碼器-解碼器

論文的方法可以用一句話概括：**一個 LSTM 讀，另一個 LSTM 寫。**

LSTM（Long Short-Term Memory，長短期記憶網路）是一種特殊的 RNN，專門設計來處理長距離依賴問題。普通 RNN 在序列很長時容易「遺忘」前面的內容，LSTM 通過引入門控機制（決定哪些資訊保留、哪些丟棄）來緩解這個問題。

具體流程：

1. **編碼器**（一個 4 層深度 LSTM）從頭到尾讀完源句子，把整個句子壓縮成一組固定長度的最終狀態，交給解碼器作為起點
2. **解碼器**（另一個 4 層深度 LSTM）以這個向量為起點，一個詞一個詞地生成目標語言的翻譯，直到輸出結束符號 \&lt;EOS\&gt;

論文給出的機率公式：

$$
p(y_1, \ldots, y_{T&apos;} \mid x_1, \ldots, x_T) = \prod_t p(y_t \mid v, y_1, \ldots, y_{t-1})
$$

翻譯成人話：給定源句子 x，生成目標句子 y 的機率，等於每一步生成下一個詞的機率連乘起來。每一步的預測都依賴兩樣東西：編碼器壓縮出來的向量 v，以及之前已經生成的所有詞。

```python showLanguage
import torch
from torch import nn


class Seq2Seq(nn.Module):
    def __init__(self, vocab_size: int, hidden_size: int) -&gt; None:
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, hidden_size)
        self.encoder = nn.LSTM(hidden_size, hidden_size, num_layers=4, batch_first=True)
        self.decoder = nn.LSTM(hidden_size, hidden_size, num_layers=4, batch_first=True)
        self.output_proj = nn.Linear(hidden_size, vocab_size)

    def encode(
        self,
        source_tokens: torch.Tensor,
    ) -&gt; tuple[torch.Tensor, tuple[torch.Tensor, torch.Tensor]]:
        embedded = self.embedding(source_tokens)
        outputs, state = self.encoder(embedded)
        return outputs, state

    def decode(
        self,
        encoder_state: tuple[torch.Tensor, torch.Tensor],
        max_steps: int,
        bos_token_id: int,
        eos_token_id: int,
    ) -&gt; list[int]:
        prev_token = torch.tensor([[bos_token_id]], dtype=torch.long, device=encoder_state[0].device)
        state = encoder_state
        generated: list[int] = []

        for _ in range(max_steps):
            embedded = self.embedding(prev_token)
            output, state = self.decoder(embedded, state)
            logits = self.output_proj(output[:, -1, :])
            next_token_id = int(logits.argmax(dim=-1).item())
            if next_token_id == eos_token_id:
                break
            generated.append(next_token_id)
            prev_token = torch.tensor([[next_token_id]], dtype=torch.long, device=logits.device)

        return generated
```

架構本身不複雜。論文的貢獻不在於發明了某個新元件，而在於證明了這個簡單的框架真的能用，而且效果好到能和精心調校的傳統系統競爭。

## 3. 三個關鍵設計決策

論文在實驗中發現了三個對性能影響很大的設計選擇：

**第一，用兩個獨立的 LSTM。** 編碼器和解碼器不共享參數。這樣做稍微增加了參數量，但讓模型能更好地分別處理源語言和目標語言的特性。論文提到這也讓同時訓練多個語言對成為可能。

**第二，用深層 LSTM。** 論文用了 4 層 LSTM，每多一層，困惑度降低近 10%。淺層 LSTM（1-2 層）的效果明顯更差。深度給了模型更大的表示空間。

**第三，把源句子倒過來讀。** 這是論文最出人意料的發現。把源句子 &quot;a, b, c&quot; 反轉成 &quot;c, b, a&quot; 再餵給編碼器，BLEU 分數從 25.9 跳到 30.6，提升了將近 5 分。

為什麼反轉有效？論文的解釋是：正常順序下，源句子第一個詞離目標句子第一個詞很遠（中間隔了整個源句子）。反轉之後，源句子的前幾個詞和目標句子的前幾個詞在時間上靠得很近，給梯度（模型用來調整參數的訊號）創造了更多的「短距離依賴」，讓最佳化變得更容易。

```python showLanguage
import torch


def reverse_source(source_tokens: list[int]) -&gt; list[int]:
    return list(reversed(source_tokens))


source_sentence = [11, 23, 37, 42]
reversed_source = reverse_source(source_sentence)
source_tensor = torch.tensor([reversed_source], dtype=torch.long)
```

這個 trick 簡單到幾乎不像是正經的研究貢獻，但它確實有效，而且揭示了一個深層問題：RNN 對序列裡元素之間的距離很敏感，距離越近越容易學。這個問題後來被注意力機制從根本上解決了。

## 4. 實驗結果

論文在 WMT &apos;14 英法翻譯任務上做了實驗。

關鍵數字：
- **單個反轉 LSTM**，beam size 12：30.59 BLEU
- **5 個反轉 LSTM 的集成**，beam size 2：34.50 BLEU
- **5 個反轉 LSTM 的集成**，beam size 12：**34.81 BLEU**
- **傳統短語翻譯系統**（Moses baseline）：33.30 BLEU

在論文報告的實驗設置下，5 個 LSTM 的集成以 34.81 分超過了傳統短語翻譯系統的 33.30 分。考慮到 LSTM 的詞表只有 8 萬詞（遇到詞表外的詞只能輸出 UNK），而傳統系統的詞表幾乎不受限，這個結果很有說服力。

論文還用 LSTM 對傳統系統的 1000-best 候選列表做重排序，BLEU 分數進一步提升到 36.5，接近當時的最佳公開結果（37.0）。

另一個值得注意的發現：相比當時其他神經方法，LSTM 在長句子上的性能退化沒那麼嚴重。這和當時其他研究者報告的長句子性能急劇下降形成了對比，論文將此歸功於反轉源句子的策略。

## 5. 模型的「理解力」

論文還做了一個有趣的視覺化實驗。把不同句子輸入編碼器，取出最終的隱藏狀態向量，用 PCA 降維到二維平面上畫出來。

結果顯示：
- 意思相近的句子在向量空間裡聚在一起
- 主動語態和被動語態的句子（&quot;I gave her a card&quot; vs &quot;I was given a card by her&quot;）落在相近的位置
- 詞序不同但意思相同的句子也能被正確聚類

這至少說明，編碼器學到的表示不只是簡單的詞袋統計（把詞混在一起不管順序），而是包含了相當多的句法和語義資訊。

## 6. 訓練細節

**模型規格**：4 層 LSTM，每層 1000 個單元，詞嵌入維度 1000，總參數量 3.84 億。其中 6400 萬是純循環連接參數。

**硬體**：8 塊 GPU。每層 LSTM 分配一塊 GPU，剩餘 4 塊 GPU 用來並行化 softmax（因為詞表有 8 萬個詞，softmax 計算量很大）。訓練約 10 天。

**最佳化器**：SGD，不帶動量，初始學習率 0.7。訓練 5 個 epoch 之後每半個 epoch 將學習率減半，總共訓練 7.5 個 epoch。

**梯度裁剪**：當梯度的 L2 範數超過閾值 5 時，按比例縮小。這是為了防止梯度爆炸（梯度值突然變得極大，導致參數更新失控）。

**批次最佳化**：把長度相近的句子放在同一個批次裡，避免短句子為長句子「陪跑」浪費計算資源，帶來了 2 倍的訓練加速。

## 7. 這篇論文改變了什麼問題

Seq2Seq 的釘子句是：**端到端映射可行，但固定向量會成為瓶頸。**

這篇論文把機器翻譯從一條人工拼裝的流水線，改成一個可以整體訓練的映射問題。它證明神經網路可以先讀完整段輸入，再逐步寫出輸出；輸入和輸出不需要等長，也不需要手工對齊。

但它同時把瓶頸暴露得很徹底。所有源句資訊都要壓進同一個向量，句子越長，壓縮損失越明顯。反轉源句子的技巧能緩解距離問題，卻不能改變資訊必須過窄門的事實。

所以下次看 Seq2Seq，不要只記住「編碼器-解碼器」。更該問：這個系統把資訊壓在哪裡？那個壓縮點，會不會變成下一代架構必須繞開的瓶頸？

---

**論文共讀系列**

- [《Neural Machine Translation by Jointly Learning to Align and Translate》](/zh-TW/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/)（通過聯合學習對齊與翻譯實現神經機器翻譯） — 注意力機制的起源
- [《Attention Is All You Need》](/zh-TW/posts/attention-is-all-you-need/)（注意力就是你所需要的全部） — 注意力成為主角，Transformer 的誕生
- [《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》](/zh-TW/posts/bert/)（BERT：用於語言理解的深度雙向 Transformer 預訓練） — 預訓練範式的確立
- [《Scaling Laws for Neural Language Models》](/zh-TW/posts/scaling-laws-for-neural-language-models/)（神經語言模型的縮放定律） — 規模的數學：為什麼更大的模型可預測地更好
- [《Language Models are Few-Shot Learners》](/zh-TW/posts/language-models-are-few-shot-learners/)（語言模型是少樣本學習者） — 更大的模型，更善於從上下文中誘發能力
- [《Training Compute-Optimal Large Language Models》](/zh-TW/posts/training-compute-optimal-large-language-models/)（訓練算力最優的大型語言模型） — 如何最有效地分配算力</content:encoded><category>Paper Reading</category><category>paper-reading</category><category>seq2seq</category><category>AI</category><category>LLM</category><category>python</category></item><item><title>Clawdbot：個人 AI 主權的早期樣本</title><link>https://justinhuangai.github.io/zh-TW/posts/clawdbot/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-TW/posts/clawdbot/</guid><description>從 Clawdbot 看自託管 AI Agent 的真正問題：不是模型有多聰明，而是控制權、上下文和執行權限歸誰。</description><pubDate>Fri, 16 Jan 2026 08:34:57 GMT</pubDate><content:encoded>AI 助手的第一性問題不是會不會聊天，而是誰擁有它。

如果記憶、上下文和工具執行權都在平台伺服器裡，使用者得到的是一個帳號，不是一個屬於自己的智能系統。[Clawdbot](https://github.com/clawdbot/clawdbot) 的早期價值就在這裡：它把 Agent 從雲端產品拉回到使用者能部署、能檢查、能撤回權限的環境裡。

從程式碼庫看，它不是一個展示專案：二十多萬行 TypeScript，覆蓋 macOS、iOS、Android 三端原生應用，外加 50 多個技能模組。

![WhatsApp 中與 Clawd 對話](/images/whatsapp-clawd.webp)

## 0. 先認幾個詞

- `中心化 AI 助手`：聊天記錄、記憶和控制權主要留在平台方。
- `自託管`：軟體跑在自己的機器或伺服器上，部署權和資料權回到使用者手裡。
- `AI Agent`：能記住上下文、呼叫工具、執行任務的執行系統，不只是聊天視窗。
- `聊天渠道`：WhatsApp、Telegram、Slack、iMessage 這類使用者已經在用的入口。
- `技能`：給 Agent 安裝的新能力，通常包含步驟、工具呼叫和邊界條件。

## 1. 控制權比介面更重要

大多數 AI 產品把問題做成了介面競爭：誰的聊天視窗更順，誰的模型回答更好，誰的訂閱包更划算。

Clawdbot 問的是另一個問題：如果 AI 真的要替使用者長期做事，為什麼它的記憶和執行權限要預設託管在第三方平台裡？

這不是隱私潔癖。Agent 一旦能讀檔案、呼叫工具、存取帳號、記住長期偏好，它就不再只是「一個好用的網頁」。它開始接近個人基礎設施。個人基礎設施的關鍵不只是能力，而是可遷移、可審計、可停用。

## 2. 聊天渠道不是裝飾

Clawdbot 把 Agent 接進 WhatsApp、Telegram、Slack、iMessage 這些現成聊天入口。

這件事的重點不是「支援平台多」。真正的變化是任務入口不再被一個新 App 壟斷。使用者不需要先想起某個 AI 產品，再打開它，再複製上下文。Agent 出現在原本的溝通場景裡，任務從對話中自然長出來。

這會改變使用頻率。一個智能體如果只存在於單獨視窗裡，它競爭的是使用者注意力；如果它在現有渠道裡，它競爭的是任務本身。

## 3. 自託管不是自動安全

自託管只是把權力拿回來，不保證權力會被用好。

Agent 能執行命令、保存記憶、安裝技能，也意味著它有更大的攻擊面。提示詞注入、錯誤工具呼叫、技能供應鏈、權限過寬，都會從「模型回答錯了」升級成「系統執行錯了」。

所以 Clawdbot 真正有價值的地方，不是「它能跑在本地」，而是它迫使我們把 Agent 當成一個有權限邊界的系統看待。模型只是其中一部分；記憶、工具、身份、審批和日誌才決定它能不能長期進入現實工作流。

## 4. 它改變的問題

Clawdbot 不是終局產品，更像一個早期樣本。

它把問題從「哪個 AI 助手更聰明」改成了「一個個人 Agent 應該由誰控制」。這個問題更底層，也更難迴避。

如果未來每個人真的會擁有一個長期執行的 AI 系統，關鍵分水嶺不會是聊天介面，而是三件事：上下文歸誰，執行權限歸誰，失敗責任歸誰。</content:encoded><category>OpenClaw</category><category>AI</category><category>open-source</category></item><item><title>《Neural Machine Translation by Jointly Learning to Align and Translate》：Transformer 之前的注意力</title><link>https://justinhuangai.github.io/zh-TW/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-TW/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/</guid><description>注意力機制的起源，附真實 Python 程式碼</description><pubDate>Sun, 11 Jan 2026 08:26:19 GMT</pubDate><content:encoded>Seq2Seq 證明了端到端翻譯可行，也留下了一個硬瓶頸：整句必須被壓進一個固定向量。[《Neural Machine Translation by Jointly Learning to Align and Translate》](/papers/1409.0473v7.pdf) 的第一性意義，是把「記住整句」改成「每一步重新查找相關資訊」。

注意力在這裡還不是 Transformer 的主角，而是 RNN 旁邊的一條檢索通道。它真正改變的不是某個公式，而是任務形狀：解碼器不再被迫相信一份總摘要，它可以在生成每個詞時重新對齊輸入。

## 0. 先認幾個詞

如果你完全沒有機器學習背景，先順著這篇論文真正想修補的地方，記住下面幾個詞：

- `編碼器-解碼器 / Encoder-Decoder`：一部分先把源句子讀完，另一部分再把目標句子一個詞一個詞寫出來。
- `RNN / 循環神經網路`：當時主流的序列模型。它必須按順序處理文本，不能一下子同時看整句。
- `hidden state / 隱藏狀態`：模型讀到某個位置時，手上那份臨時筆記。
- `alignment / 對齊`：源句子裡的哪一部分，對應目標句子當前要生成的這個詞。
- `attention`：生成每個詞時，不再只盯著一個總壓縮結果，而是主動回頭看源句子裡更 relevant 的位置。

## 1. 問題出在哪

2014 年的神經機器翻譯有一個標準架構：編碼器-解碼器（Encoder-Decoder）。編碼器是一個循環神經網路（RNN），把源句子從頭到尾讀一遍，把整個句子壓縮成一個固定長度的向量（可以理解為一串固定數量的數字）。解碼器是另一個 RNN，從這個向量出發，一個詞一個詞地生成翻譯。

問題很明顯：不管源句子是 5 個詞還是 50 個詞，編碼器都要把它壓進同一個長度的向量裡。短句子還行，長句子就丟資訊。就像你讓一個人讀完一整頁書，然後只能用一句話複述，句子越長，遺漏越多。

論文用實驗證明了這一點：當句子長度超過 30 個詞，傳統編碼器-解碼器的翻譯品質急劇下降。

這就是「固定長度瓶頸」。

## 2. 核心想法：別壓縮，讓解碼器自己去找

論文的解決方案很直覺：既然把整個句子壓成一個向量會丟資訊，那就不壓了。編碼器保留每個位置的註解向量（annotation，由雙向 RNN 的前向和後向隱藏狀態拼接而成，可以理解為每個詞處理完之後產生的中間結果），解碼器在生成每個目標詞時，自己決定該重點看源句子的哪些部分。

這就是注意力機制的核心：**不再強迫所有資訊擠過一個瓶頸，而是讓模型學會在需要的時候回頭找需要的資訊。**

具體來說，分三步：

**第一步，打分。** 解碼器在生成第 i 個目標詞之前，會用自己當前的狀態 s_{i-1} 和編碼器每個位置的隱藏狀態 h_j 做比較，算出一個「對齊分數」e_{ij}。分數越高，說明生成當前目標詞時，源句子的第 j 個位置越重要。

論文用的打分函數是：

$$
e_{ij} = a(s_{i-1}, h_j) = v_a^T \tanh(W_a s_{i-1} + U_a h_j)
$$

這叫「加性注意力」（additive attention）。把解碼器狀態和編碼器狀態各自做一次線性變換（乘以矩陣），加起來，過一個 tanh（一種把數值壓縮到 -1 到 1 之間的函數），再和一個向量 v_a 做點積，得到一個標量分數。

**第二步，歸一化。** 用 softmax 把所有位置的分數轉成機率，加起來等於 1：

$$
\alpha_{ij} = \operatorname{softmax}(e_{ij}) = \frac{\exp(e_{ij})}{\sum_k \exp(e_{ik})}
$$

**第三步，加權求和。** 用這些機率對編碼器的隱藏狀態做加權求和，得到一個「上下文向量」c_i：

$$
c_i = \sum_j \alpha_{ij} h_j
$$

這個上下文向量就是解碼器在生成第 i 個詞時，從源句子裡提取到的關鍵資訊。每生成一個詞，上下文向量都不一樣，因為模型關注的源句子位置不一樣。

用 Python（基於 PyTorch）寫出來：

```python showLanguage
import torch
from torch import nn


def bahdanau_attention(
    decoder_state: torch.Tensor,
    encoder_outputs: torch.Tensor,
    w_a: nn.Linear,
    u_a: nn.Linear,
    v_a: nn.Linear,
) -&gt; tuple[torch.Tensor, torch.Tensor]:
    decoder_features = w_a(decoder_state).unsqueeze(1)
    encoder_features = u_a(encoder_outputs)
    scores = v_a(torch.tanh(decoder_features + encoder_features)).squeeze(-1)
    weights = torch.softmax(scores, dim=-1)
    context = torch.sum(weights.unsqueeze(-1) * encoder_outputs, dim=1)
    return context, weights
```

和後來 Transformer 用的「點積注意力」（Q 和 K 直接做點積）不同，這篇論文用的是「加性注意力」（先各自做線性變換，再加起來）。兩種方法各有特點，但點積注意力更適合用高效矩陣乘法實現；再加上 Transformer 去掉了 RNN 的順序依賴，注意力才真正成為可大規模並行的核心算子。

## 3. 編碼器：雙向 RNN

單向 RNN 從左往右讀句子，到了最後一個詞才輸出一個總結向量。問題是：每個位置的隱藏狀態主要只帶著左側上下文，看不到右邊。

論文用了雙向 RNN（BiRNN）來解決這個問題。一個 RNN 從左往右讀，另一個從右往左讀，然後把兩個方向的隱藏狀態拼起來。這樣每個位置的隱藏狀態就同時包含了左邊和右邊的上下文。

```python showLanguage
import torch
from torch import nn


class BidirectionalRNN(nn.Module):
    def __init__(self, input_size: int, hidden_size: int) -&gt; None:
        super().__init__()
        self.rnn = nn.GRU(
            input_size=input_size,
            hidden_size=hidden_size,
            bidirectional=True,
            batch_first=True,
        )

    def forward(self, inputs: torch.Tensor) -&gt; torch.Tensor:
        outputs, _ = self.rnn(inputs)
        return outputs
```

論文裡每個方向各有 1000 個隱藏單元，拼起來就是 2000 維。這比單向 RNN 多了一倍參數，但換來的是每個位置都能看到完整的上下文。

## 4. 解碼器：每一步都重新對齊

把編碼器和注意力機制組裝起來，解碼器的工作流程就清楚了：

1. 編碼器用雙向 RNN 讀完源句子，保留每個位置的隱藏狀態（註解向量）
2. 解碼器開始生成翻譯，每生成一個詞之前：
   - 用當前狀態和所有註解向量算注意力權重
   - 加權求和得到上下文向量
   - 結合上下文向量、上一個生成的詞和當前狀態，預測下一個詞

```python showLanguage
import torch
from torch import nn


class AttentionDecoder(nn.Module):
    def __init__(self, embedding_dim: int, hidden_size: int, vocab_size: int) -&gt; None:
        super().__init__()
        self.rnn = nn.GRU(
            input_size=embedding_dim + 2 * hidden_size,
            hidden_size=hidden_size,
            batch_first=True,
        )
        self.w_a = nn.Linear(hidden_size, hidden_size, bias=False)
        self.u_a = nn.Linear(2 * hidden_size, hidden_size, bias=False)
        self.v_a = nn.Linear(hidden_size, 1, bias=False)
        self.output_proj = nn.Linear(hidden_size, vocab_size)

    def decode_step(
        self,
        prev_word: torch.Tensor,
        prev_state: torch.Tensor,
        encoder_outputs: torch.Tensor,
    ) -&gt; tuple[torch.Tensor, torch.Tensor]:
        context, _ = bahdanau_attention(
            prev_state.squeeze(0),
            encoder_outputs,
            self.w_a,
            self.u_a,
            self.v_a,
        )
        rnn_input = torch.cat([prev_word, context.unsqueeze(1)], dim=-1)
        output, new_state = self.rnn(rnn_input, prev_state)
        logits = self.output_proj(output[:, -1, :])
        return logits, new_state
```

關鍵在於：每生成一個目標詞，解碼器都會重新計算注意力分布。翻譯第一個詞時可能重點關注源句子的開頭，翻譯最後一個詞時可能重點關注源句子的結尾。這種動態對齊能力，是之前的固定向量架構做不到的。

## 5. 實驗結果

論文在英法翻譯任務上做了實驗（使用 WMT &apos;14 資料集），用 BLEU 分數（衡量機器翻譯和人工翻譯接近程度的標準評分，滿分 100）衡量效果。

關鍵對比：
- **RNNencdec-50**（傳統編碼器-解碼器，訓練時最長 50 詞）：26.71 BLEU
- **RNNsearch-50**（帶注意力的模型，訓練時最長 50 詞）：**34.16 BLEU**
- **Moses**（當時最強的傳統短語翻譯系統）：33.30 BLEU

提升了 7.45 分。在論文報告的實驗設置裡，帶注意力的神經模型已經達到甚至超過了當時強勢的傳統短語翻譯系統。

更關鍵的發現在論文的 Figure 2：隨著句子長度增加，傳統編碼器-解碼器的 BLEU 分數急速下跌，而帶注意力的模型幾乎不受影響。這直接驗證了論文的核心假設：固定長度向量是瓶頸，注意力機制可以繞過它。

論文還展示了注意力權重的視覺化。在英法翻譯裡，注意力權重幾乎形成了一條對角線，說明模型自動學會了「英語第 1 個詞對應法語第 1 個詞，英語第 2 個詞對應法語第 2 個詞」的對齊關係。遇到語序不同的情況（比如法語的形容詞放在名詞後面），注意力權重也會對應地偏移。模型不需要任何人工對齊標註，就學會了這些。

## 6. 這篇論文改變了什麼問題

注意力的第一性意義是：**把「記住整句」改成「每一步重新查找相關資訊」。**

這篇論文沒有先發明 Transformer，也沒有拋棄 RNN。它做的是更根本的介面改造：編碼器不再只交出一份總摘要，而是保留每個位置的表示；解碼器每生成一個詞，就重新決定該看源句子的哪裡。

這件事改變了長句子的命運。模型不再被固定向量逼著一次性記住所有東西，而是把翻譯過程拆成連續的檢索和生成。對齊不再是外部標註，而是在訓練目標裡自己長出來。

所以下次看 attention，不要先問它的公式多漂亮。先問它把哪個任務從「記憶問題」改成了「檢索問題」。這才是它後來能放大成 Transformer 的原因。

---

**論文共讀系列**

- [《Sequence to Sequence Learning with Neural Networks》](/zh-TW/posts/sequence-to-sequence-learning-with-neural-networks/)（使用神經網路進行序列到序列學習） — 編碼器-解碼器範式的確立
- [《Attention Is All You Need》](/zh-TW/posts/attention-is-all-you-need/)（注意力就是你所需要的全部） — 注意力成為主角，Transformer 的誕生
- [《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》](/zh-TW/posts/bert/)（BERT：用於語言理解的深度雙向 Transformer 預訓練） — 預訓練範式的確立
- [《Scaling Laws for Neural Language Models》](/zh-TW/posts/scaling-laws-for-neural-language-models/)（神經語言模型的縮放定律） — 規模的數學：為什麼更大的模型可預測地更好
- [《Language Models are Few-Shot Learners》](/zh-TW/posts/language-models-are-few-shot-learners/)（語言模型是少樣本學習者） — 更大的模型，更善於從上下文中誘發能力
- [《Training Compute-Optimal Large Language Models》](/zh-TW/posts/training-compute-optimal-large-language-models/)（訓練算力最優的大型語言模型） — 如何最有效地分配算力</content:encoded><category>Paper Reading</category><category>paper-reading</category><category>attention</category><category>AI</category><category>LLM</category><category>python</category></item><item><title>《Attention Is All You Need》：Transformer 的設計原點</title><link>https://justinhuangai.github.io/zh-TW/posts/attention-is-all-you-need/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-TW/posts/attention-is-all-you-need/</guid><description>拆解 Transformer 論文，附真實 Python 程式碼</description><pubDate>Tue, 06 Jan 2026 08:18:46 GMT</pubDate><content:encoded>Transformer 的第一性問題不是「注意力有沒有用」，而是序列建模為什麼一定要被時間順序綁住。RNN 把文本順序和計算順序鎖在一起，長距離依賴、並行訓練和資訊尋址都被這條鎖鏈拖住。

[《Attention Is All You Need》](/papers/1706.03762v7.pdf) 的革命不在於注意力本身，而在於把序列建模從時間順序問題改寫成全域尋址問題：每個位置直接問，當前最該看誰。

## 0. 先認幾個詞

如果你沒有機器學習背景，先順著這篇論文真正想取代的舊方案，記住下面幾個詞就夠了：

- `RNN / 循環神經網路`：一種更早的序列模型。它處理句子時必須一個詞一個詞往後讀，像人用手指著文章逐行看。
- `attention`：從很多資訊裡，挑出當前最該看的那幾部分。你可以先把它理解成「有選擇地回頭看重點」。
- `Query / Key / Value`：注意力機制裡的三個角色。Query 像「我現在想找什麼」，Key 像「每段資訊貼著什麼標籤」，Value 則是「真正被取回來的內容」。
- `Transformer`：以 attention 為核心搭起來的一整套架構。它不靠循環一步步往前推，而是讓每個位置都能直接看其他位置。
- `並行`：這裡不是說模型更聰明，而是說它能同時處理很多位置，不必像 RNN 那樣排隊。

## 1. 一句話說清楚

在 Transformer 之前，AI 處理語言的方式像是一個人用手指著書，一個字一個字地往下讀。讀到第 100 個字的時候，第 1 個字說了什麼，已經記不太清了。句子越長，遺忘越嚴重。這就是循環神經網路（RNN，一種早期的 AI 架構）的根本瓶頸。

論文的作者們問了一個問題：**為什麼一定要按順序讀？**

和必須逐步處理的 RNN 不同，Transformer 可以並行處理整段輸入，直接建模任意兩個位置之間的關係。不用排隊，不用等前一個詞處理完才能看下一個。

論文管這個核心能力叫「注意力」。標題要表達的不是「模型裡真的只剩注意力」，而是：在序列建模裡，注意力第一次被推到了主角的位置，不再需要循環和卷積（一種通過滑動窗口提取局部特徵的方法）作為骨架。

## 2. 注意力到底在做什麼

想像你走進一個嘈雜的酒吧，二十個人同時在說話。你的大腦不會平均分配注意力給每個人。有人喊了你的名字，你的耳朵瞬間鎖定那個方向，其他聲音自動變成背景噪音。

Transformer 對每個詞做同樣的事。論文裡定義了三個角色：

- **Query（查詢）**：這個詞在找什麼資訊。相當於你的耳朵在搜尋「誰在叫我」
- **Key（鍵）**：這個詞能提供什麼資訊。相當於酒吧裡每個人的聲音特徵
- **Value（值）**：這個詞實際攜帶的內容。相當於那個人說的具體話

每個詞的 Query 會和其他詞的 Key 做匹配。匹配度高的，就從對方的 Value 裡獲取更多資訊。匹配度低的，直接忽略。

論文給出的公式叫 Scaled Dot-Product Attention：

$$
\operatorname{Attention}(Q, K, V) = \operatorname{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V
$$

看到公式別慌。一步一步拆：

- **QK^T**：Q 和 K 做點積。什麼是點積？把兩組數字對應位置相乘，再加起來。比如 [1, 2] 和 [3, 4] 的點積是 1×3 + 2×4 = 11。數字越大，說明兩個詞越相關。這一步算的就是每對詞之間的「匹配分數」
- **/ √d_k**：除以一個數來縮放。d_k 是向量的長度（向量可以理解為「一串用來描述某個東西的數字」，比如用 64 個數字描述一個詞的含義）。為什麼要除？因為數字串越長，點積結果越大。不縮放時，維度越大點積的方差越大，softmax 容易進入飽和區（幾乎所有機率集中在一個詞上），梯度（模型用來調整自身參數的訊號）變得很小，訓練會不穩定
- **softmax**：把一組分數轉換成機率，所有機率加起來等於 1。比如三個詞的分數是 [10, 2, 1]，softmax 之後大概變成 [0.99, 0.007, 0.003]。分數最高的那個詞幾乎拿走了全部注意力，其他的被壓到接近零
- **× V**：用這些機率去加權每個詞的實際內容。機率高的詞貢獻大，機率低的詞貢獻小。最終輸出是一個融合了關鍵資訊的新向量

用 Python（基於 PyTorch）寫出來：

```python showLanguage
import math
import torch


def scaled_dot_product_attention(
    query: torch.Tensor,
    key: torch.Tensor,
    value: torch.Tensor,
) -&gt; torch.Tensor:
    d_k = key.size(-1)
    scores = query @ key.transpose(-2, -1) / math.sqrt(d_k)
    weights = torch.softmax(scores, dim=-1)
    return weights @ value
```

核心運算很短。很多後來改變行業的能力，底層都建立在這幾行矩陣運算之上。

## 3. 多頭注意力：同時從多個角度看

單個注意力頭通常只能偏向某一類關係模式。但語言這東西，一句話裡藏著好幾層意思。

拿「我昨天在深圳吃了潮汕牛肉火鍋」來說：
- 「我」和「吃了」之間是誰做了什麼的關係
- 「昨天」和「吃了」之間是時間關係
- 「深圳」和「潮汕牛肉火鍋」之間是地點與食物的關係

讓一個頭同時兼顧這麼多層次，很難。論文的做法是用多頭機制：派出 8 個頭並行運算，讓模型有機會從不同子空間同時觀察一句話，最後把各自的發現拼起來。

論文原文的公式：

$$
\operatorname{MultiHead}(Q, K, V) = \operatorname{Concat}(\text{head}_1, \ldots, \text{head}_h)\, W^O
$$

拆開看：
- **head_1, ..., head_h**：8 個頭各自獨立做一次注意力運算，得到 8 份結果
- **Concat**：把 8 份結果首尾相連，拼成一個長向量
- **W^O**：一次線性變換（可以理解為「乘以一個矩陣」），把拼接後的長向量壓回原來的維度。相當於一個主管聽完 8 個調查員的匯報，輸出一份綜合結論

```python showLanguage
import math
import torch
from torch import nn


class MultiHeadAttention(nn.Module):
    def __init__(self, d_model: int, num_heads: int) -&gt; None:
        super().__init__()
        if d_model % num_heads != 0:
            raise ValueError(&quot;d_model must be divisible by num_heads&quot;)
        self.num_heads = num_heads
        self.d_head = d_model // num_heads
        self.q_proj = nn.Linear(d_model, d_model)
        self.k_proj = nn.Linear(d_model, d_model)
        self.v_proj = nn.Linear(d_model, d_model)
        self.out_proj = nn.Linear(d_model, d_model)

    def _split_heads(self, x: torch.Tensor) -&gt; torch.Tensor:
        batch_size, seq_len, _ = x.shape
        x = x.view(batch_size, seq_len, self.num_heads, self.d_head)
        return x.transpose(1, 2)

    def forward(
        self,
        query: torch.Tensor,
        key: torch.Tensor,
        value: torch.Tensor,
    ) -&gt; torch.Tensor:
        q = self._split_heads(self.q_proj(query))
        k = self._split_heads(self.k_proj(key))
        v = self._split_heads(self.v_proj(value))

        scores = q @ k.transpose(-2, -1) / math.sqrt(self.d_head)
        weights = torch.softmax(scores, dim=-1)
        heads = weights @ v

        batch_size, _, target_len, _ = heads.shape
        merged = heads.transpose(1, 2).contiguous()
        merged = merged.view(batch_size, target_len, self.num_heads * self.d_head)
        return self.out_proj(merged)
```

論文裡的參數：模型用 512 個數字描述一個詞（d_model = 512），8 個頭，每個頭分到 64 個數字（512 ÷ 8 = 64）。8 個頭的總計算量和 1 個 512 維的頭差不多，但表達能力強得多。用同樣的計算預算，換來多視角的表示能力。這筆預算分配很清楚。

## 4. 位置編碼：告訴模型詞的順序

Transformer 並行處理整個句子，速度是快了，但代價是它丟掉了詞的先後順序。如果沒有額外的位置資訊，注意力機制本身並不知道「貓吃魚」和「魚吃貓」有什麼區別。這顯然不行。

怎麼補救？給每個位置生成一個獨一無二的「地址編碼」，加到詞的向量上。模型看到的不再是「貓」和「魚」，而是「第 1 個位置的貓」和「第 3 個位置的魚」。

論文用正弦和餘弦函數來生成這個編碼：

$$
\operatorname{PE}(pos, 2i) = \sin\left(\frac{pos}{10000^{2i / d_{\text{model}}}}\right)
$$

$$
\operatorname{PE}(pos, 2i + 1) = \cos\left(\frac{pos}{10000^{2i / d_{\text{model}}}}\right)
$$

公式看著唬人，核心思路很直觀：
- **pos**：詞在句子裡的位置（第 1 個、第 2 個、第 3 個……）
- **i**：向量的第幾個維度。偶數位置用 sin，奇數位置用 cos
- **10000^(2i/d_model)**：一個隨維度變化的縮放因子。低維度變化快，高維度變化慢。就像時鐘：秒針一分鐘轉一圈，時針十二小時才轉一圈。不同「指針」覆蓋不同的時間尺度，組合在一起就能精確定位任意時刻

最終效果：每個位置得到一串獨一無二的數字指紋，模型靠這個指紋區分詞的先後順序。

```python showLanguage
import math
import torch


def positional_encoding(seq_len: int, d_model: int) -&gt; torch.Tensor:
    positions = torch.arange(seq_len, dtype=torch.float32).unsqueeze(1)
    div_term = torch.exp(
        torch.arange(0, d_model, 2, dtype=torch.float32)
        * (-math.log(10000.0) / d_model)
    )

    encoding = torch.zeros(seq_len, d_model)
    encoding[:, 0::2] = torch.sin(positions * div_term)
    encoding[:, 1::2] = torch.cos(positions * div_term)
    return encoding
```

為什麼偏偏選正弦餘弦？因為它有一個優雅的數學性質：兩個詞相隔固定距離，無論它們出現在句首還是句尾，位置編碼之間的關係是一樣的。模型不用死記「位置 3 和位置 8」的關係，只需要學會「相隔 5 個位置」意味著什麼。論文團隊也試過讓模型自己學位置編碼，效果差不多，但正弦版本有一個額外的優勢：它能處理訓練時沒見過的更長句子。

## 5. 編碼器與解碼器

Transformer 的完整架構分兩半。

**編碼器**（6 層堆疊）負責讀懂輸入。每層包含兩個子層：一個多頭自注意力，一個前饋網路。每個子層都有兩個保護機制：

- **殘差連接**：把子層的輸入直接加到輸出上，即 x + Sublayer(x)。為什麼？想像你給一張照片加濾鏡。如果濾鏡效果不好，殘差連接保證你還能看到原圖。在深層網路裡，資訊每經過一層都會被變換，傳到第六層可能已經面目全非。殘差連接讓原始訊號可以「抄近道」直達深層，防止資訊在傳遞中丟失
- **層歸一化**（LayerNorm）：把數值調整到統一範圍，防止有的數字大到爆炸、有的小到消失。類似於考試成績標準化，不管原始卷面分差異多大，標準化後都在一個可比較的區間

**解碼器**（6 層堆疊）負責生成輸出。結構和編碼器類似，但多了兩個關鍵設計：

第一，**交叉注意力**：解碼器生成每個詞時，會回頭「看」編碼器的輸出。翻譯場景下，就是一邊寫英文一邊回頭看中文原文。

第二，**遮罩**（masking）：生成第 3 個詞時，只允許看到前 2 個詞，第 4 個及之後的位置被遮蔽（注意力分數設為負無窮，經過 softmax 後變成零）。道理很簡單：你寫作文的時候，下一個字還沒寫出來，不能偷看。

```python showLanguage
from typing import Optional

import torch
from torch import nn


class Transformer(nn.Module):
    def __init__(
        self,
        vocab_size: int,
        d_model: int = 512,
        num_heads: int = 8,
        num_layers: int = 6,
        d_ff: int = 2048,
        dropout: float = 0.1,
    ) -&gt; None:
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, d_model)

        encoder_layer = nn.TransformerEncoderLayer(
            d_model=d_model,
            nhead=num_heads,
            dim_feedforward=d_ff,
            dropout=dropout,
            batch_first=True,
        )
        decoder_layer = nn.TransformerDecoderLayer(
            d_model=d_model,
            nhead=num_heads,
            dim_feedforward=d_ff,
            dropout=dropout,
            batch_first=True,
        )

        self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
        self.decoder = nn.TransformerDecoder(decoder_layer, num_layers=num_layers)
        self.output_proj = nn.Linear(d_model, vocab_size)

    def forward(
        self,
        src_token_ids: torch.Tensor,
        tgt_token_ids: torch.Tensor,
        tgt_mask: Optional[torch.Tensor] = None,
    ) -&gt; torch.Tensor:
        memory = self.encoder(self.embedding(src_token_ids))
        hidden = self.decoder(self.embedding(tgt_token_ids), memory, tgt_mask=tgt_mask)
        return self.output_proj(hidden)
```

還有一個容易被忽略的元件：前饋網路。公式是 FFN(x) = max(0, xW1 + b1)W2 + b2。翻譯成白話：先把每個詞的 512 維向量擴大到 2048 維（乘以一個矩陣再加一個偏置），用 ReLU 過濾一遍（所有負數變成零，正數保留），再壓回 512 維。ReLU 這一步是關鍵：它引入了「非線性」，讓模型能學到直線畫不出來的複雜模式。如果全是線性變換，多層疊加在數學上仍可合併為單層，非線性是模型表示複雜模式的前提。

## 6. 訓練細節

架構設計完了，怎麼訓練它？論文在這裡也有不少講究。

**硬體**：8 塊 NVIDIA P100 GPU。基礎版模型訓練 12 小時（10 萬步），大號模型訓練 3.5 天（30 萬步）。放在今天看，這個訓練成本已經很低。

**最佳化器**：用的是 Adam（一種讓模型自動調整參數的演算法），但學習率的設計很巧妙。學習率決定了模型每一步「邁多大步子」。步子太大容易跨過最佳解，太小又走得慢。論文的策略是前 4000 步逐漸提速（warmup，熱身），避免一開始更新過猛；4000 步之後按計畫逐漸減速，讓訓練後期更穩定。先升後降，前半段大膽探索，後半段精細打磨。

**正則化**：兩招。第一招是 Dropout，訓練時隨機關掉 10% 的神經元（可以理解為網路中的計算節點），迫使模型不依賴任何單一路徑，學到更穩健的特徵。第二招是 label smoothing（標籤平滑，ε = 0.1）：訓練時不告訴模型「正確答案的機率是 100%」，而是說「90% 是正確答案，剩下 10% 分給其他選項」。這會讓模型在一個指標上變差（困惑度，衡量模型有多「拿不準」），但翻譯品質反而更好。直覺上說，一個承認自己不是 100% 確定的模型，比一個過度自信的模型更可靠。

**結果**：論文用 BLEU 分數（機器翻譯的標準評分，衡量機器翻譯和人工翻譯有多接近，滿分 100）來衡量效果。英德翻譯 28.4 分，英法翻譯 41.8 分，都刷新了當時的紀錄。訓練成本比之前的方法低了一到兩個數量級。更快，更強，更便宜。

## 7. 這篇論文改變了什麼問題

Transformer 的釘子句是：**序列建模不必按時間順序計算，它可以變成全域尋址。**

這篇論文的核心不是「注意力很強」四個字。注意力在 Bahdanau 那裡已經出現過。真正的轉折是：作者把循環和卷積從骨架位置拿掉，讓每個 token 直接存取其他 token。計算路徑不再被文本順序綁住，長距離依賴也不再必須穿過一長串中間狀態。

Self-Attention、殘差連接、LayerNorm、前饋網路都不是孤立的英雄。它們共同組成了一台更適合並行訓練、更適合規模化的序列機器。Transformer 的力量，來自把資訊流和硬體效率同時改寫。

下次看一個新架構，不要只問它用了什麼模組。先問它有沒有改寫問題的座標系：是繼續沿著舊計算路徑做最佳化，還是把資訊尋址方式換掉了。

---

**論文共讀系列**

- [《Sequence to Sequence Learning with Neural Networks》](/zh-TW/posts/sequence-to-sequence-learning-with-neural-networks/)（使用神經網路進行序列到序列學習） — 編碼器-解碼器範式的確立
- [《Neural Machine Translation by Jointly Learning to Align and Translate》](/zh-TW/posts/neural-machine-translation-by-jointly-learning-to-align-and-translate/)（通過聯合學習對齊與翻譯實現神經機器翻譯） — 注意力機制的起源
- [《BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding》](/zh-TW/posts/bert/)（BERT：用於語言理解的深度雙向 Transformer 預訓練） — 預訓練範式的確立
- [《Scaling Laws for Neural Language Models》](/zh-TW/posts/scaling-laws-for-neural-language-models/)（神經語言模型的縮放定律） — 規模的數學：為什麼更大的模型可預測地更好
- [《Language Models are Few-Shot Learners》](/zh-TW/posts/language-models-are-few-shot-learners/)（語言模型是少樣本學習者） — 更大的模型，更善於從上下文中誘發能力
- [《Training Compute-Optimal Large Language Models》](/zh-TW/posts/training-compute-optimal-large-language-models/)（訓練算力最優的大型語言模型） — 如何最有效地分配算力</content:encoded><category>Paper Reading</category><category>paper-reading</category><category>transformer</category><category>AI</category><category>LLM</category><category>python</category></item><item><title>歡迎來到 Justin Huang Blog</title><link>https://justinhuangai.github.io/zh-TW/posts/hello-world/</link><guid isPermaLink="true">https://justinhuangai.github.io/zh-TW/posts/hello-world/</guid><description>一個個人技術部落格：讀論文、看系統、追問 AI 與軟體工程背後的因果鏈。</description><pubDate>Thu, 01 Jan 2026 08:07:13 GMT</pubDate><content:encoded>這個部落格不是資訊流。

資訊流負責告訴你什麼剛剛發生。它擅長製造新鮮感，卻不擅長保存一個問題為什麼重要。這裡做另一件事：把技術問題重新放回可理解的因果鏈裡。

## 寫什麼

這裡的大部分文章從三類對象出發：論文、系統、工程行為。

論文只有在改變提問方式時才真正重要。Transformer 不是「注意力很強」這麼簡單，而是把序列建模從時間順序問題改寫成全域尋址問題。GPT-3 不是單純更大，而是把一部分任務適配從參數更新搬進上下文。Chinchilla 不是反對大模型，而是指出參數和資料必須一起吃滿算力預算。

系統只有在約束可見時才值得分析。一個 Agent 產品不是「模型加工具」。它還包括記憶放在哪裡、權限怎麼收、執行時狀態怎麼恢復、協議如何約束互動，以及出錯的代價由誰承擔。

工程行為只有放進激勵裡才看得清。benchmark、開源生態、模型排行榜、Agent 平台，從外面看都很乾淨；真正的問題通常藏在生成流程、成本結構和治理邊界裡。

## 怎麼寫

我不想寫只會複述的文章。摘要壓縮文本，好的閱讀應該改變框架。

每篇文章至少要留下一個更鋒利的問題：模型更強，是在哪種預算下更強？Agent 更能幹，能力到底存在權重、上下文，還是執行時結構裡？benchmark 更難，難的是任務本身，還是生成和過濾流程製造出來的難？

這就是這裡的寫作標準：少一點口號，多一點機制。

## 給讀者，也給智能體

人類讀者可以從最新文章開始，也可以按標籤順著一個主題讀。

AI 智能體可以讀 `/llms.txt`、`/llms-full.txt`，也可以在任意文章 URL 後加 `.md` 讀取 Markdown。這個設計是刻意的：如果文字要被檢索、引用、復用和反駁，機器可讀版本就不該是二等入口。

這個部落格要保留下來的不是觀點數量，而是問題結構。一個判斷過幾個月再拿起來，還能繼續工作，這才值得寫。</content:encoded><category>General</category><category>hello</category><category>blog</category></item></channel></rss>