三十天對帳:我量錯的四次
三十篇寫完了。這一篇不談平台,算我自己的帳。
我在這個系列裡反覆說「不要相信沒驗過的數字」。 那句話對我自己一樣成立——而且我犯了四次。
第一次:用錯的量法,差點讓工作量翻倍
我給稿子訂了字數上限,量完發現12 篇全部超標,最長的是上限的 1.76 倍。
於是有人問:「那要不要每篇拆成兩篇?」
12 拆成 24。那會吃掉整個檔期,後面真正想寫的東西一篇都放不進去。
我正要開始評估怎麼拆,停了一下,回去看量法—— 我是拿整份 Markdown 直接數的,把兩種讀者永遠看不到的東西算了進去: 發文前要刪的註解,和程式碼區塊。合計約佔每篇 40%。
重量之後:平均 1,553 字,沒有一篇超過 2,000。一篇都不用拆。
教訓:這個錯誤最危險的地方不是它錯,是它看起來剛好合理。 如果它回報「每篇 50 萬字」,我立刻會懷疑。 但「超標 1.76 倍」正好落在「有點糟但可信」的區間。
第二次:消融實驗的評估,沒有套用消融
我做了三個拆零件的實驗(拆掉 causal mask、residual、/√d),
用 monkeypatch 改模型再訓練。
但評估是另一個 process 跑的。 03_eval.py 重新 import 原始的模型結構——
所以我是拿「沒有 residual 訓練出來的權重」餵進「有 residual 的結構」去評估。
| 錯的 | 修正後 | |
|---|---|---|
| no-resid test_loss | 4.1130 | 3.3576 |
| no-scale test_loss | 2.6724 | 1.9331 |
| baseline | 1.9534 | 1.9446(差 0.009=評估雜訊) |
baseline 幾乎沒動,正好證明問題出在消融不匹配。
⭐ 而抓到它的不是我的檢查。 是第三個實驗跑出一組矛盾的數字 (val 比 baseline 好、test 卻爛掉),我追那個矛盾才發現前兩篇全錯。
教訓:錯誤的數字不會自己招供。 通常是另一個實驗的矛盾把它逼出來——而如果我沒做第三個實驗, 那兩篇會就這樣發出去。
第三次:我猜錯了,而且方向相反
拆掉 attention 的 /√d 之前,我把預測寫死在檔案裡:
分數會放大 5.66 倍 → softmax 過度尖銳 → 訓練不穩 → val loss 掉到 2.0~2.6
實際跑出來是 1.8090——比 baseline 的 1.8479 還低。
四點預測錯了三點,方向也錯。
但這裡有第二層:我差點寫成「拿掉 √d 反而更好」。
因為我另外量過這條產線的自然波動:只換 random seed 跑五次,
test_loss 的 σ = 0.0211。而這兩顆模型的差距是 0.0115——小於一個標準差。
正確的結論是第三種:「在這個設定下量不出差異」,不是「更好」也不是「更差」。
教訓:沒有 σ,你手上的每一個比較都是意見。
第四次:我把一個錯誤傳進了要交出去的文件
這個最嚴重,因為它已經流出去了。
早期的紀錄裡有一條:
「pipeline 內建的 MariaDB 是
latin1_swedish_ci,含中文的 pipeline 上傳即失敗」
我沒有重驗就把它寫進了兩篇文章和一份驗收條件。
寫第 28 篇之前我去實查——那條在這個版本上不成立:
schema mlpipeline utf8mb4_unicode_ci
run_details.Name/Description utf8mb4
metadata 表(Artifact/Execution/Context) utf8mb4
中文存得進去,dashboard 也顯示得正確。 而且我早就有反證沒注意到:我自己那些 run 的中文描述,一直好好地存在裡面。
⭐ 但真正的風險還在,只是位置不同——在連線端:
character_set_client latin1
character_set_connection latin1
character_set_results latin1
所以自己寫的查詢會拿到 ?????,而且不報錯。
更嚴重的是 mysqldump 沒指定字元集,會產生一份「看起來成功但中文全毀」的備份。
那對災難復原的影響,比原本那條敘述更嚴重。
教訓:繼承來的結論最危險。 它有一份紀錄背書,看起來已經被驗證過—— 而你不會去驗一件「已經寫在文件上」的事。
四次的共同點
| # | 錯在哪 | 被誰抓到 |
|---|---|---|
| 1 | 量法把不該算的算進去 | 別人問了一個問題 |
| 2 | 評估沒套用被測的改動 | 另一個實驗的矛盾 |
| 3 | 用雜訊內的差異下結論 | 我自己量了 σ |
| 4 | 沿用沒驗過的舊紀錄 | 動筆前的例行重驗 |
四次裡只有兩次是我自己的機制抓到的。 另外兩次是運氣。
而這正好對應到我在這個系列裡罵別人的話: 檢查在它自己的位置上是通過的。
我最後留下的兩個習慣
① 當一個數字要求你做一個很貴的決定時,先回頭驗那個數字。
驗證的力氣應該跟決定的代價成正比。「拆成 24 篇」是高成本難回頭的動作, 在付出之前回頭花十分鐘驗輸入,是我做過投資報酬率最高的一次除錯。
反過來,如果指標建議的動作很便宜(改個標題、調個參數),不驗也還好。
② 動筆前重跑一次,即使那件事寫在自己的筆記上。
第四個錯誤就是這樣抓到的——而它已經進了要交出去的文件。 如果我沒有訂「每篇提到的東西動筆前必須重跑」這條規矩,它會就這樣出去。
為什麼要寫這一篇
因為這個系列唯一難被複製的,不是那些指令。
指令會過期——ODH 3.6 出來,一半的路徑會變。 但「我量錯了四次,這是怎麼發現的」不會過期。
而且它是這整批文章可信度的來源:一個會公開自己量錯過的人, 比一個只展示成功的人可信。
如果你也在寫技術文章或做評估報告,你怎麼確認自己沒搞錯?
🧪 這篇的實驗環境與 lab 檔案(最後更新 2026-08-30)
叢集
- CRC 2.63.0 · OpenShift 4.22.7 · Kubernetes v1.35.6
- 單節點:13 vCPU / 40 GiB RAM / 120 GB disk
- 宿主:Framework Laptop 16(Ryzen AI 7 350 · 8C/16T · 64 GB RAM · 1 TB NVMe · RTX 5070 顯卡模組)
- ⚠️ 叢集內看不到 GPU(CRC 是 VM,RTX 5070 未 passthrough)
Operator
opendatahub-operator.v3.5.0← 即 RHOAI 3.x 的上游開源版cert-manager-operator.v1.20.0(3.x 的必要相依;2.x 不需要)openshift-pipelines-operator-rh.v1.23.2、openshift-gitops-operator.v1.21.3
DataScienceCluster 開啟的元件
kserve、aipipelines、dashboard、workbenches、modelregistry、kueue(Unmanaged)- 其餘(
ray/trustyai/feast/aigateway…)為Removed
叢集外的依賴(跑在宿主的 podman 上,crc start 不會帶起來)
- Harbor v2.15.2(私有 registry)· MinIO(S3)
模型端
- Python 3.12.13 · PyTorch 2.11.0+cu128 · FastAPI + uvicorn · prometheus-client
lab 檔案
- YAML/Containerfile/腳本:github.com/ryanGTR/openshift-ai-30days
(含 Day 對照表;主機名是佔位符,跑
set-lab-host.sh換成你自己的) - 服務的那個模型:llm-from-scratch(從零手刻的小 GPT)
⚠️ ODH ≠ RHOAI:元件同源,但 namespace 與部分名稱不同
(我這裡是 opendatahub,商用版是 redhat-ods-* 那一套)。
指令的邏輯可以照用,字串要自己對一次。
留言與指正
我特別想知道:如果你也在寫技術文章或做評估報告,你怎麼確認自己沒搞錯?