兩天前我說要把繁中的難點攤開,現在攤完了
桌上有一堆壞掉的東西:位置跑掉的貨幣符號、被吃掉的總數、讀成「線」的「綠」、重複一百次的材料名、以及三個誠實的 0
今天要做的事只有一件:把這堆東西,一條一條翻譯成設計決策。
而且我要先回答那個從 Day 1 就懸著的問題——這些,換一顆更大的模型不就好了嗎?
Day 6 和 Day 7 一共列出九條。先編號,今天整篇都會用這些編號:
| 編號 | 難點 | 實測證據 | 哪天 |
|---|---|---|---|
| N1 | 模型自加結構描述,輸出膨脹 2–3 倍 | page_02 的四個方位標籤;九頁全部有敘述性開場白 | Day 6 |
| N2 | 中英數交界處內容搬移 | page_05 的 NT$ 從數字右邊搬到左邊 |
Day 6 |
| N3 | 關鍵數字被吃掉 | page_05 的「生產基地24個」整個不見 | Day 6 |
| N4 | 形近字誤判被語言先驗修飾成通順文字 | page_24:綠→線、阻燃→防燃、載具→製具、補強→補償 | Day 6 |
| N5 | 表格欄位對應垮掉,但字元指標好看 | page_24 三欄變三批,覆蓋率仍有 0.9437 | Day 6 |
| N6 | 低資訊量區域誘發重複退化 | page_19 finish_reason=length,一串重複上百次 |
Day 6 |
| N7 | GT 文字層本身就漏抽 | page_02 底紋、page_19 圖表標籤 | Day 6 |
| N8 | 繁簡與異體字讓字串比對失去意義 | 實測 0 次,但 Day 25 飛輪與 Day 17 XBRL 校驗必炸 | Day 7 |
| N9 | 規則式分類器的未知率高 | 87 個 diff 塊有 37 個落進 other,約 43% |
Day 7 |
九條裡有一條特別怪:N8 的實測計數是 0
我還是把它放進來了,理由昨天講過——它在 OCR 階段不炸,在比對階段炸。而我的整個可信任機制建立在比對上
這是 Phase 2 收斂到 Harness 的核心論證,所以我要慢慢講
把九條難點按「性質」重新分三堆:
| 難點 | 為什麼規模有效 |
|---|---|
| N4 形近字誤判 | 更大的模型看過更多字形、更多語境,判別力真的會提升 |
| N6 重複退化 | 更強的模型比較不容易陷入自迴圈(但不保證,這也跟解碼參數有關) |
這兩條我承認:換模型會變好。
但注意「變好」不等於「解決」。N4 的性質是「錯了你看不出來」——一顆準確率從 95% 提到 98% 的模型,只是把「每頁 5 個你看不出來的錯」變成「每頁 2 個你看不出來的錯」
2 個看不出來的錯,跟 5 個看不出來的錯,在財報場景裡是同一件事:不能用。
而且 Day 3 已經把這條路堵了一半。GB10 的記憶體頻寬是硬上限,模型越大、每個 token 要搬的權重越多、吞吐越低。我實測 48.5 tok/s 的那顆模型,換成兩倍大的,速度會掉到不能接受的區間——而且塞不塞得進記憶體都是問題
所以第一堆的正確講法是:規模有效,但天花板不夠高,而且我付不起。
| 難點 | 為什麼規模無效 |
|---|---|
| N7 GT 文字層漏抽 | 錯的是答案不是模型,模型再強也不會讓 PyMuPDF 抽到底紋 |
| N8 繁簡異體字比對 | 這是「相等定義」的問題,發生在模型輸出之後 |
| N9 分類器未知率 43% | 這是我的規則寫得不夠,跟模型無關 |
這三條最容易被忽略,因為它們長得不像模型問題
N7 尤其致命。Day 6 的 page_02,模型把背景底紋的化學元素表讀出來了,而 GT 沒有——在我的 CER 表上,這被算成模型的錯
你換一顆更強的模型,它會把底紋讀得更清楚,然後 CER 更高
模型越強,指標越難看。 這種評測系統已經壞了,而且壞在你看不到的地方
這是最重要的一堆:
| 難點 | 為什麼規模無效 |
|---|---|
| N1 自加結構描述 | 模型不知道你要的是純文字還是結構描述,它在猜你的意圖 |
| N2 內容搬移 | 模型按視覺順序讀,GT 按 PDF 物件順序存,兩邊都對,沒有共同標準 |
| N3 關鍵數字被吃掉 | 模型輸出時沒有任何「必須包含總數」的約束 |
| N5 欄位對應垮掉 | 模型必須同時做版面理解與文字辨識,兩件事在一次生成裡搶容量 |
這四條共同的性質是:問題不是「模型不知道正確答案」,是「模型不知道自己輸出對不對」
N3 就是最乾淨的例子。模型把生產基地列成 bullet list,七個國家的數字 5+13+1+1+2+1+1 加起來剛好是 24——答案就在它自己的輸出裡,但它沒有任何機制去檢查這件事
一顆大十倍的模型會不會剛好記得輸出「24個」?
可能會。但它還是不會檢查。它只是碰巧對了
碰巧對的系統,跟會檢查的系統,是兩種東西。
這就是 Day 5 結尾那句話的完整版:
你要的東西不是一個更準的模型,是一個會發現自己錯了的系統
Day 5 是從硬體限制推到這句話,今天是從繁中難點推到同一句話。兩條路,同一個終點。
💡Tip: 我覺得判斷「該換模型還是該改架構」有一個很實用的準則:看錯誤是不是安靜的。會噴錯、會超時、會輸出亂碼的問題,通常換模型或調參數有救;而「輸出看起來完全正常但內容是錯的」這類問題,幾乎都要靠架構——因為你要加的是一個獨立的檢查環節,而不是一個更好的猜測者。
好,正題。把九條難點對應到後面真的會出現的設計:
| 編號 | 難點 | 對應設計 | 出現在 |
|---|---|---|---|
| N1 | 模型自加結構描述 | 結構化輸出約束 + Checksum 驗證 | Day 22 |
| N2 | 中英數交界內容搬移 | Region Routing:位置先鎖住再送模型 | Day 18 |
| N3 | 關鍵數字被吃掉 | 跨欄位一致性檢查(總數 = 明細加總) | Day 16 |
| N4 | 形近字誤判被修飾成通順文字 | Tesseract 交叉驗證 + Logprob 門檻 + MOPS XBRL 對帳 | Day 22、Day 17 |
| N5 | 表格欄位對應垮掉 | Docling 版面偵測 + 純文字區/表格區分流 | Day 18、Day 10 |
| N6 | 重複退化 | 獨立 verifier 檢查 finish_reason 與重複率,觸發重試 |
Day 11、Day 23 |
| N7 | GT 文字層漏抽 | 不依賴 GT 的驗證:MOPS XBRL 當外部 ground truth | Day 17 |
| N8 | 繁簡異體字比對 | 比較鍵(fold)進入 diff 對齊層 | Day 25 |
| N9 | 分類器未知率 43% | JSONL 記錄 + 2–5% 抽樣人工稽核補規則 | Day 26、Day 27 |
九條難點,對應到七個不同的設計環節
注意沒有任何一條的解法是「換更大的模型」。
下面逐條走一遍,講清楚為什麼是那個設計。
N2 和 N5 的共同根因是閱讀順序
page_05 的 NT$ 搬家、page_24 的三欄變三批——兩個都是「模型自己決定該從哪讀到哪」造成的
而現在主流的做法(整頁圖丟進 VLM)根本沒有給模型任何位置約束。你把一張二維的版面壓成一維的 token 序列,順序由模型猜
Day 18 要做的事很直接:在送進模型之前,先用 PyMuPDF 抽文字層、用 Docling 做版面偵測,把頁面切成有座標的區塊,再逐區塊送模型
這樣模型每次只看一個區塊,它不需要猜順序——順序是我給的
代價是什麼?更多次推論請求。 一頁從 1 次變成 5 到 10 次,延遲直接乘上去。Day 10 會實測純文字區與表格區的速度差(50 tok/s 對 7 tok/s 那個級距),這個代價是真的,而且很痛
但這個代價換到的是一個結構性的好處:N2 那種「兩邊都對但順序不同」的問題會直接消失,因為區塊的座標就是共同標準
「生產基地 24 個」被吃掉,而 5+13+1+1+2+1+1 = 24
這是我最喜歡的一個案例,因為它證明了一件事:有些錯誤不需要正確答案就能抓出來
你不需要 GT、不需要 XBRL、不需要另一顆模型。你只需要一條規則:如果輸出裡同時有總數和明細,兩者必須相等
Day 16 要建的就是這類規則庫:
| 規則類型 | 例子 |
|---|---|
| 加總一致 | 各期明細加總 = 合計欄 |
| 跨頁一致 | 同一個日期在封面、內頁、附註要一致 |
| 名稱一致 | 簽約對象全名在全文出現多次時必須相同(這裡要用 Day 7 的 fold) |
| 格式合理 | 百分比在 0–100、金額非負(除非明確標示括號負數) |
這層的性價比高到不合理——幾乎沒有推論成本,卻能抓到 N3 這種字元級指標完全抓不到的錯
而且它抓到的是「語意層」的錯,不是「字元層」的錯。 這條線後面 Phase 4 會整個展開
形近字誤判是最難的一條,因為它沒有任何表面徵兆
「阻燃」變「防燃」,兩個都是合法中文詞、都放在合理位置、模型的 finish_reason 是 stop、格式完全正常
要抓它,我目前想到的只有一條路:用一個不共享同樣先驗的東西去對照
Day 22 的四重驗證就是在做這件事,四層各自對付不同的東西:
| 驗證層 | 抓什麼 | 對 N4 有效嗎 |
|---|---|---|
| Checksum(結構化輸出驗證) | 欄位缺漏、型別錯誤、格式不合 | 無效 |
| Logprob 門檻 | 模型自己也不確定的位置 | 部分有效——形近字誤判時,兩個候選的機率常常很接近 |
| Tesseract 交叉驗證 | 傳統 OCR 沒有語言模型先驗,不會把「阻」修飾成「防」 | 有效 |
| MOPS 領域語意 Verifier | 用官方 XBRL 對帳公司名、科目名、金額 | 對關鍵欄位有效 |
第三層特別有意思。傳統 OCR(Tesseract)在純辨識準確率上比不過 VLM,但它有一個 VLM 沒有的優點:它不會用語言模型去「潤飾」結果
它看到什麼字就輸出什麼字,看不清楚就輸出亂碼或空白。它的錯誤是吵的,不是安靜的
所以拿一個比較弱但誠實的模型,去對照一個比較強但會修飾的模型——兩者不一致的地方,就是要人看的地方
這是 Day 11 那個原則的具體應用:驗證者不能跟被驗證者共享同一套偏誤
page_19 的重複退化有個好消息:它抓起來很簡單
finish_reason == "length",或是「輸出裡最長重複子串的出現次數超過門檻」——兩個都是幾行程式碼的檢查
問題在於誰來檢查
如果你讓生成的模型自己檢查自己的輸出,它會說沒問題。這不是模型笨,這是機制問題:產生那段重複文字的機率分布,跟評估那段文字的機率分布,是同一個分布
Day 11 要做的就是把 verifier 拆成獨立角色(用 gpt-oss:20b),而 Day 23 要定義的是「檢查不過之後怎麼辦」——重試?換參數重試?降級?還是標記為需人工處理?
page_19 這頁的正確處置其實很明確:把 max_tokens 拉高再重試一次,如果還是撞牆,就標記人工
但這個處置邏輯必須寫在流程裡,不能靠我盯著看。這就是 Pipeline 跟 Harness 的差別,Day 19 會專門講
N7 是整個系列裡我覺得最有價值的一個發現:我的答案本身是錯的
page_02 的底紋、page_19 的圖表標籤——PyMuPDF 抽不到,但模型看得到。在我的 CER 表上,模型被冤枉了
這個問題沒有辦法靠改進 OCR 解決,因為錯的不是 OCR
Day 17 的解法是換一個完全不同的 ground truth 來源:MOPS 本身就提供 XBRL 格式的官方結構化財報
這意味著對於財報數字這類關鍵欄位,我有一份跟 PDF 同源、但完全獨立於 PDF 抽取流程的答案。它不會因為底紋是圖片就漏抽,因為它根本不是從 PDF 抽出來的
當然 XBRL 只涵蓋結構化財務數字,不涵蓋敘述性文字與圖表。所以它不是萬用解,它是關鍵欄位的高信度校驗源
而這正好對應到 Day 16 那個「關鍵欄位」的概念:不是所有欄位都值得付出同樣的驗證成本
💡Tip: 「找一個獨立的 ground truth 來源」這件事,值得你在任何評測專案的一開始就花時間想。我一開始用 PyMuPDF 文字層,是因為它免費、自動、零標註成本——這些優點都是真的,但我花了到 Day 6 才看清楚它的代價。如果你的 ground truth 跟你的待測系統共享同一個上游,你量到的東西會比你以為的少很多。
模型自己加「這張圖片中包含的文字內容如下:」這種開場白,九頁全中
解法其實很無聊:不要讓它輸出自由文字,讓它輸出 JSON
Day 22 會用 Pydantic 定義 schema,模型輸出必須能被 parse 成那個 schema,parse 不過就是失敗。這樣「敘述性開場白」這種東西在結構上就沒有位置可以放
但這裡有個取捨要講清楚:結構化輸出會讓模型變笨一點
當你要求模型同時做辨識、又要維持 JSON 格式、又要填對欄位,它的容量被分走了。這在 Day 10 講速度取捨的時候會有具體數字
所以 N1 的解法不是純粹的勝利,是一個交易:用一點辨識品質,換一個可機器驗證的輸出形態
而我選擇做這個交易的理由很簡單:不可驗證的高品質輸出,對一個要求可信任的系統來說是零分。
最後兩條是關於這套系統怎麼變好
N8 是折疊層要進 diff 對齊。Day 25 的 flywheel 核心是「把模型輸出跟參考答案 diff,把 diff 變成校準燃料」——如果台/臺這種差異會產生假陽性,那飛輪會被假訊號餵成一個奇怪的形狀
所以 Day 7 那個 fold() 函式,它真正的部署位置不是 OCR 後處理,是 diff 對齊層內部
N9 則是承認一件事:我的規則不夠,而且我知道它不夠
87 個 diff 塊有 37 個落進 other。這 43% 就是我今天所有結論的不確定區間
要把這 43% 降下來,靠的不是想得更周全,是看更多真實案例。Day 26 要設計 JSONL/SQLite 的記錄 schema 把每一筆 diff 存下來,Day 27 要設計 2–5% 抽樣人工稽核把 other 裡的東西撈出來分類
規則不是設計出來的,是從 other 桶裡長出來的。
💡Tip: 我建議任何規則式分類器都要把
other的比例當成一個一級指標,跟主要指標並排放在儀表板上。它就是你的「已知的未知」。而當它開始下降,代表你的規則在追上現實;當它開始上升,代表現實變了(換文件類型、換模型、換 prompt),該回頭看了。
講完「換更大的模型」,還有一個更常見的反駁我得處理:這些難道不是 prompt 沒寫好嗎?
平心而論,這個反駁對其中幾條是成立的
N1(模型自加結構描述)就是最明顯的一條。我的 baseline prompt 逐字是:
請完整辨識這張圖片中的所有文字,保留表格結構。
沒有輸出格式約束、沒有「不要加任何說明文字」、沒有 few-shot 範例
我如果加一句「只輸出辨識到的文字,不要加任何說明、標題或註解」,九頁裡那九段開場白大概率會少掉一大半
同樣地,N6 的重複退化,我如果把 max_tokens 從 1500 調到 4096,page_19 那 15.4 的 CER 馬上會變成一個正常得多的數字
所以我要老實說:這兩條有很大一部分是我的 baseline 設定造成的,不是模型的錯。
那為什麼我不改?
因為 Day 2 的那把尺要能一直用下去。 Day 6 新測四頁的時候我刻意沿用同一組參數——同一個 endpoint、同一顆模型、同一段 prompt、同樣的 max_tokens=1500、同樣的 temperature=0——為的是讓九頁的數字可以放在同一張表裡比
一旦我在 Day 6 改了 prompt,前五頁跟後四頁就不可比了。而 Day 2 那五頁的數字,是後面 22 天每一次架構改動的對照組
變數控制的價值,高於單次結果好看。
但更重要的是第二個理由:prompt 能解決的那幾條,恰好是最不嚴重的那幾條
回頭看那三堆分類:
| 難點 | prompt 能解嗎 | 嚴重性 |
|---|---|---|
| N1 自加結構描述 | 大部分能 | 低——輸出膨脹很吵,但很好發現 |
| N6 重複退化 | 能(調 max_tokens) |
低——finish_reason 直接標出來了 |
| N2 內容搬移 | 部分能(要求保留原始順序) | 中 |
| N5 欄位對應垮掉 | 部分能(要求輸出 markdown 表格) | 高 |
| N3 關鍵數字被吃掉 | 不能 | 高 |
| N4 形近字誤判 | 不能 | 最高 |
| N7 GT 漏抽 | 不能(問題不在模型端) | 高 |
| N8 繁簡異體字比對 | 不能(發生在模型之後) | 高 |
prompt 能解的,全部集中在「吵的錯誤」;prompt 解不了的,全部是「安靜的錯誤」。
這個分布不是巧合。prompt 是在調整模型的行為傾向,而安靜的錯誤之所以安靜,正是因為模型的行為完全正常——它沒有偏離你的指示,它只是讀錯了一個字然後很自信地寫下來
你沒辦法用 prompt 叫一個模型「不要犯你自己不知道自己在犯的錯」
💡Tip: 我會這樣排優先順序:先把 prompt 跟參數這種便宜的手段用到盡,因為它們幾乎零成本;但同時要很清楚它們的天花板在哪裡。判準還是同一條——如果一個錯誤會自己喊出來(報錯、超時、截斷、格式壞掉),先試便宜的手段;如果它安靜地混在正確答案裡,那就準備動架構。
倒推設計講到這裡都很漂亮,但有幾條我手上還沒有答案,先標記起來,免得後面自己騙自己
第一,N4 的形近字誤判,我沒有把握四重驗證真的夠。
Tesseract 交叉驗證的想法是對的——它不共享語言模型先驗。但 Tesseract 在中文小字級上的表現本來就不好,它很可能在 page_24 那種密度下噴出一堆自己的錯誤,反而製造大量假陽性
真正的答案可能是「兩者不一致的地方一律標記人工」,但那樣的人工量是多少?我現在不知道,要等 Day 22 實測。
第二,Region Routing 的代價我還沒量。
一頁切成 5–10 個區塊逐塊送模型,聽起來很美好,但延遲會直接乘上去。Day 3 那條記憶體頻寬的天花板還在那裡
如果一頁從 4 秒變成 30 秒,那一份 200 頁的年報要跑一個半小時——這個數字能不能接受,取決於使用場景,而我還沒定義清楚那個場景。 Day 10 會先量純文字區跟表格區的速度差,那是這個問題的第一塊拼圖
第三,N7 那個「GT 本身是錯的」問題,XBRL 只解了一部分。
XBRL 涵蓋結構化財務數字,不涵蓋敘述性文字、不涵蓋圖表標籤、不涵蓋重大契約摘要裡的合約條款
而 page_19 那張散點圖上的材料標籤,XBRL 幫不上任何忙。那類內容的 ground truth 要怎麼來,我目前的答案是「人工標,而且只標抽樣到的那 2–5%」——這是 Day 27 的範圍,但我要先承認這是一個妥協,不是一個解法
💡Tip: 我覺得技術文章最容易失真的地方,就是「倒推設計」這種回頭看的敘事——因為你已經知道答案了,所以每一步都顯得必然。實際上我寫這三天的時候,有至少三個地方是「我知道有問題但還不知道怎麼解」。把那些地方標出來,讀者才知道哪些是結論、哪些是待辦。 而且三個月後的我會感謝我自己。
九條難點、七個設計環節,如果要收成一句話:
每一條難點,對應的都是「加一個能產生回饋訊號的環節」。
finish_reason 檢查產生的回饋是生成過程的異常
而 Day 1 引用過的 Harness 定義是:
讓 agent 根據目標,持續、正確地動作的工程。核心材料是「回饋訊號」。
所以 Phase 2 這三天,表面上在講繁體中文的字型跟版面,實際上在做一件事:把「這個系統需要哪些回饋訊號」這個問題,用第一手實測數據回答出來。
如果我 Day 6 就直接開始畫架構圖,我畫出來的會是一張抄來的架構圖。而現在這七個環節,每一個都指得出它是為了哪一條實測難點而存在的
這就是「倒推」的意思
| 產出 | 內容 | 後面誰會用 |
|---|---|---|
| 四維難度評分函式 | 跨欄表格/罕用字/中英混排/小字註腳,全 40 頁分數落檔 | Day 27 的抽樣設計 |
| 新測 4 頁的 OCR 結果 | CER 1.4815 / 0.6885 / 15.4000 / 1.0085 | 所有後續架構改動的對照組 |
| 9 頁錯誤分類統計 | 8 類別逐頁次數,other 未知率 43% |
Day 26 的 schema 設計 |
| 9 條難點清單 | N1–N9,每條都有實測證據 | 今天這張對照表 |
比較鍵 fold() |
保守的異體字折疊,只影響相等判定 | Day 25 的 diff 對齊層 |
| 三份誠實的限制說明 | 樣本乾淨、對照表不完備、GT 抽取瑕疵 | 每一次引用這些數字的人 |
最後一列我想多說一句
這三天我寫進文件的「限制」比「結論」還多:dense_table 最高分只有 0.1429 所以沒測到真表格、字型變體與直橫混排沒有樣本、page_19 的 CER 被 finish_reason=length 污染、三個類別的 0 有一個確定是假的
這些不是免責聲明,是適用範圍。
一個沒有標明適用範圍的結論,跟沒有結論是一樣的——因為下一個人不知道什麼時候該停止相信它
架構不是設計出來的,是被錯誤逼出來的。
你的架構有多好,取決於你有多認真地看過自己壞掉的樣子。
Phase 2 到這裡收掉
明天進 Phase 3,第一個問題是選模型:為什麼是 Gemma 4?
而且我不會從「它很強」開始講。我會從 26B MoE 跟 31B Dense 這兩種架構的差別開始——因為今天這九條難點裡,有幾條是吃記憶體頻寬的、有幾條是吃推理深度的,而這兩種架構在 GB10 這台機器上的表現,差得比你想的多
明天見 👋