同一條 gate,三種分母,三種結論
我在自己筆電的 CRC lab 上跑一條四棒的 pipeline: 準備資料 → 訓練 → 評估 → 放行閘門。
gate 擋下了模型。我一開始很滿意——護欄不是擺著好看的,它真的會擋。
然後我去看它擋的理由。
第一版:擋的理由是假的
=== promotion gate ===
候選 9875830b104a val_loss= 8.3899
data_quality_gate False
✗ 擋下: 資料品質 gate 未通過
不是 loss 擋的(8.39 低於我設的門檻 9.0),是資料品質。打開報告:
| 偵測器 | hits | pct | 門檻 |
|---|---|---|---|
| url | 1 | 100.0 | 5.0 |
| 其餘六條 | 0 | 0.0 | — |
100%? 一份維基語料裡「100% 的文件含有 URL」不太可能。
往上一行看:
"total_docs": 1
整個檔案被當成一篇文件。
產生報告的腳本有個 --doc_sep 參數,預設是 None——
「整個檔當成一篇」。而 pipeline 呼叫它的時候沒有傳。
所以 hits 只可能是 0 或 1,pct 只可能是 0 或 100。
拿 5% 當門檻,實際效果是:語料裡出現任何一個 URL 就永遠不過, 一個都沒有就永遠過。
這條 gate 有在擋,但它擋的不是「資料品質」, 是「這份檔案裡有沒有出現過 URL 這個字串」。
第二版:換成段落,數字有意義了——但還是錯的
語料是空行分段的,所以我把分隔字串設成 \n\n:
total_docs: 2880
指標終於會動了:
| 偵測器 | 第一版 | 第二版 | 門檻 |
|---|---|---|---|
| url | 100% ✗ | 0.1% ✓ | 5.0 |
| symbol_spam | 0% | 0.73% ✓ | 1.0 |
| high_repetition | 0% | 9.1% ✗ | 1.0 |
| too_short | 0% | 32.99% ✗ | 5.0 |
url 從 100% 掉到 0.1%——原來的警報確實是假的。 但冒出兩條新的,而且 33% 的文件過短聽起來很嚴重。
我去看最短的五「篇」是什麼:
历史 / 参见 / 注释 / 参考 / 简介
那是章節標題。
把空行當文件分隔,等於把每個標題都算成一份「過短的文件」。 33% 這個數字是分母造出來的,不是資料的問題。
第三版:找到真正的文件邊界
我去翻語料的結構:
| 分隔 | 切出幾篇 | 長度中位數 | <50 字的比例 |
|---|---|---|---|
\n\n(段落) |
580 | 96 | 32.1% |
\n\n\n |
9 | 7,304 | 0% |
\n\n\n 切出來的每一篇都以一個主題開頭(数学、哲學、文學)——那才是文章。
換成文章之後:
| 偵測器 | 第一版 | 第二版 | 第三版 | 門檻 |
|---|---|---|---|---|
total_docs |
1 | 2,880 | 25 | |
| too_short | 0% | 32.99% ✗ | 0% ✓ | 5.0 |
| high_repetition | 0% | 9.1% ✗ | 0% ✓ | 1.0 |
| url | 100% ✗ | 0.1% ✓ | 8.0% ✗ | 5.0 |
同一份資料、同一組門檻、三種分母,三種結論。
而且這次的 8% 是真的:25 篇文章裡有 2 篇含 URL。
岔路:調門檻,還是清資料
gate 還是擋著。這裡有兩條路:
A. 把 url 門檻從 5% 調到 10% ——一行,五秒鐘,馬上就過。
B. 把語料裡的 URL 清掉 ——那是 gate 在告訴你的事。
我選 B,因為 A 是我自己寫過的一種失敗模式: 看結果、不看門檻是誰填的。門檻如果可以在「它擋住我」的時候被調整, 那它就不是門檻,是一個會自動讓路的裝飾品。
清完是這樣:
移除 URL:428 → 0
清理後:4,346 篇文章,含 URL 0 篇(0.00%)
再跑一次:
{"total_docs": 25, "all_pass": true}
七條偵測器全過。
值得停一下:這一整輪下來,門檻一個字都沒改。 我改的是分母的定義,和資料本身。
在程式碼上,「改一行讓它過」和「改一行讓它過」看起來一模一樣。 差別在改的是哪一行。
最後那個更難處理的
第三版切出 25 篇文章(因為我為了跑得快,只取了 1 MB 的語料樣本)。
25 篇的時候,一篇就是 4%。
所以 5% 的門檻實際上等於「最多容許 1 篇」。 2 篇就是 8%,直接超標。
這個門檻的解析度被樣本數綁死了,而樣本數是我為了讓 pipeline 跑得快隨手設的。
這不是資料的問題,也不是門檻的問題,是量測設計的問題—— 而它在報告上長得跟真正的品質問題一模一樣。
後來:門檻過了,然後我弄丟了另一個模型的身分
分母修對、資料清乾淨之後,gate 放行了。
=== promotion gate ===
候選 6957bbaf0491 val_loss= 8.3899
✓ 放行
接著我補了一個原本缺的 promote 步驟——gate 通過之後把模型真的換上去: 標記成 production、推到服務讀得到的路徑、把台帳同步回叢集。
它成功了。新服務起來,問它自己是誰:
{"serving_digest":"sha256:6957bbaf0491…","in_registry":true,
"status":"registered","metrics":{"val_loss":8.3899}}
整條鏈接起來了。 我很高興了大概三分鐘。
然後我順手去看原本那個一直在跑的服務:
{"serving_digest":"sha256:4d694be9342d…","in_registry":false,
"status":"UNREGISTERED","metrics":null}
它從 production 變成了 UNREGISTERED。
服務沒有重啟、pod 沒有動、digest 一個字都沒變—— 變的是台帳,而台帳是兩個服務共用的。
原因:台帳有兩份來源,而 pipeline 讀的那一份原本不存在。 gate 從一個空台帳開始寫,promote 就用那份只有一筆的台帳覆蓋了共用的設定。 新模型登記成功的同一個動作,把舊模型從名冊上抹掉了。
這比前面那個分母問題更難防。 分母錯了,至少數字看起來怪。 這一次沒有任何東西看起來怪——新服務一切正常、舊服務一切正常, 只有「舊服務是誰」這件事悄悄變成了「不知道」。
而如果那天有人來問「線上這顆模型核准過嗎」,答案會是:查無此人。
我從這件事學到的
「護欄有在擋」和「護欄擋對東西」是兩件不同的事, 而前者會讓你停止檢查後者。
我看到 gate 擋下模型的時候,第一反應是滿意——治理有在運作。 如果那天我沒有多按一次去看擋的理由,我會帶著 「我們的資料品質閘門有在把關」這個結論走很久。
所以現在我加了一條給自己的規則:
每一條會擋東西的規則,都要去看它擋下來的那個數字是怎麼算出來的。 特別是分母。
分子通常很好懂(幾筆命中)。分母是那個沒人會去確認、 但決定了一切的東西。
加上後半段那件事,還有第二條:
改共用的紀錄之前,先問「還有誰在讀它」。
台帳、ConfigMap、基準檔——這類東西的共同點是 它們不屬於任何一個服務,但每個服務都靠它回答「我是誰」。 動它的時候不會有人報錯,因為在那個當下,什麼都沒有壞。
你們的品質門檻,分母是怎麼定義的?有人檢查過嗎?
🧪 這篇的實驗環境與 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-* 那一套)。
指令的邏輯可以照用,字串要自己對一次。
留言與指正
我特別想知道:你們的品質門檻,分母是怎麼定義的?有人檢查過嗎?