一個影像分類模型在本機除錯時運作正常,封存也顯示成功,交付後的應用程式卻在首次推論時找不到資源。常見原因並非模型演算法,而是模型未加入目標、被指令碼複製到錯誤目錄,或應用程式套件中仍留有同名的舊版本。因此,雲端 Mac 流程需要驗證的不只是「能否建置」,還包括模型原始檔能否獨立編譯,以及最終交付套件中究竟包含哪些內容。
將模型驗收拆成兩道關卡
第一道關卡直接處理 .mlmodel 或 .mlpackage,排除模型格式、工具鏈與輸出目錄問題。第二道關卡則在 Xcode 完成封存後檢查 .app,確認編譯後的 .mlmodelc 確實已加入交付目標。兩道關卡必須分開:如果只執行 xcodebuild,一旦失敗,就很難快速判斷問題出在模型編譯、目標成員資格,還是資源複製階段。
開始前先固定開發者目錄,並記錄工具版本:
set -euo pipefail
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
xcodebuild -version
xcrun --find coremlcompiler
swift --version
如果 ArmVMS 上安裝了多個 Xcode 版本,工作應明確設定 DEVELOPER_DIR,不要依賴互動式終端機中的 xcode-select 狀態。自動化流程與人工登入工作階段的環境變數可能不同,明確指定路徑更容易重現結果。
模型驗收的對象應是「由指定 Xcode 工具鏈產生的指定應用程式套件」,而不是某台機器上某次偶然建置成功的目錄。
先獨立編譯 Core ML 模型
為每個模型準備獨立的輸出目錄,禁止多個工作共用同一個暫存目錄。以下指令碼同時支援 .mlmodel 與 .mlpackage 輸入:
MODEL_PATH="${1:?model path required}"
OUTPUT_ROOT="${2:-$PWD/build/coreml}"
MODEL_NAME="$(basename "$MODEL_PATH")"
MODEL_NAME="${MODEL_NAME%.*}"
MODEL_OUTPUT="$OUTPUT_ROOT/$MODEL_NAME"
rm -rf "$MODEL_OUTPUT"
mkdir -p "$MODEL_OUTPUT"
xcrun coremlcompiler compile "$MODEL_PATH" "$MODEL_OUTPUT"
find "$MODEL_OUTPUT" -type f -print0 |
sort -z |
xargs -0 shasum -a 256 > "$MODEL_OUTPUT.files.sha256"
du -sk "$MODEL_OUTPUT"
指令成功後,至少要檢查三件事:輸出目錄不是空的、可以產生檔案清單、大小不為零。不要直接將編譯目錄當作長期快取,因為其內容與 Xcode 版本、模型內容及編譯器行為有關。如果要重複使用,應以模型原始檔摘要、xcodebuild -version 輸出及建置指令碼版本共同組成快取鍵。
為生成式模型加入輸入檢查
部分專案會在建置前透過 Python 或轉換工具產生模型。此時應先驗證輸入檔案,再呼叫 coremlcompiler。生成工作必須寫入新目錄,待全部完成後再以不可分割的方式移至約定路徑,避免 Xcode 讀取到尚未完成的產物。
建議在清單中儲存以下欄位:
| 欄位 | 用途 |
|---|---|
| 模型相對路徑 | 區分主應用程式與延伸功能所使用的模型 |
| 原始檔 SHA-256 | 判斷輸入是否確實發生變更 |
| Xcode 版本 | 說明編譯產物的差異 |
| 編譯後大小 | 發現意外夾帶的資源 |
| 目標名稱 | 檢查 Target Membership |
從封存檔確認最終產物
獨立編譯通過後,再進行全新封存。為避免讀取上一次工作的殘留內容,請為 DerivedData 與封存路徑配置本次工作專用的目錄:
RUN_ROOT="$PWD/build/model-validation"
DERIVED_DATA="$RUN_ROOT/DerivedData"
ARCHIVE_PATH="$RUN_ROOT/App.xcarchive"
rm -rf "$RUN_ROOT"
mkdir -p "$RUN_ROOT"
xcodebuild \
-workspace Example.xcworkspace \
-scheme Example \
-configuration Release \
-destination "generic/platform=iOS" \
-derivedDataPath "$DERIVED_DATA" \
-archivePath "$ARCHIVE_PATH" \
archive
接著找出應用程式套件,並匯出模型清單:
APP_PATH="$(find "$ARCHIVE_PATH/Products/Applications" -maxdepth 1 -name '*.app' -print -quit)"
test -n "$APP_PATH"
find "$APP_PATH" -type d -name '*.mlmodelc' -print |
LC_ALL=C sort > "$RUN_ROOT/bundled-models.txt"
test -s "$RUN_ROOT/bundled-models.txt"
此處不要只比對主應用程式的根目錄。模型可能屬於延伸功能,也可能由資源套件攜帶。檢查指令碼應記錄完整的相對路徑,再依照專案定義判斷允許出現哪些路徑。如果預期只有一個模型,卻發現兩個同名目錄,應立即檢查主應用程式、延伸功能與相依套件是否各自複製了一份。
建立無須仰賴猜測的基準
模型大小會受結構與編譯工具影響,不能簡單規定「成長超過某個統一百分比就視為失敗」。更穩妥的做法,是為每個模型保存經過審核的基準,並同時設定絕對增量與比例增量。小型模型適合關注絕對值,大型模型則更需要留意比例。
基準檔案可以採用簡單的定位字元分隔格式:
Classifier.mlmodelc 8421376
Embedding.mlmodelc 27156480
比對時還必須處理新增、刪除與重新命名。新增模型不能自動視為錯誤,但必須要求變更記錄說明用途;模型遭到刪除時則應阻止交付,除非清單也已通過審核並更新。重新命名應以「刪除舊項目並新增項目」處理,避免錯誤沿用歷史資料。
清單應保存為建置產物,但不要上傳模型原始檔以外的敏感設定。如果模型是透過受控下載步驟取得,僅記錄摘要、邏輯名稱與最終套件內的路徑,不要在日誌中輸出含有權杖的下載網址。
常見失敗與交付檢查項目
如果模型能夠獨立編譯,卻未進入應用程式套件,應優先檢查 Target Membership、Copy Bundle Resources、資源套件相依性及條件式建置設定。如果 Debug 正常但 Release 缺少模型,請比較兩種設定的資源階段,以及自訂指令碼的輸入與輸出清單。
如果同名模型重複出現,應先盤點完整的相對路徑,不要直接刪除其中一份。主應用程式與延伸功能可能確實各自需要一份副本;真正的問題是未經設計的重複複製。如果封存檔大小突然增加,還應檢查生成目錄是否被整體加入資源階段,導致原始模型、暫存檔與編譯產物一併打包進應用程式。
提交前可依照以下順序驗收:
- 固定
DEVELOPER_DIR並記錄 Xcode 版本。 - 在隔離目錄中獨立執行
coremlcompiler。 - 為模型原始檔與編譯結果產生摘要清單。
- 使用全新的 DerivedData 完成 Release 封存。
- 從
.xcarchive中列舉所有.mlmodelc。 - 比對允許的路徑、預期數量及已審核的大小基準。
- 保存清單、指令輸出與封存識別資訊,以便後續比較。
這套流程不會判斷模型準確度,也不能取代端對端推論測試。它處理的是更前段、也更適合自動化的問題:模型是否可以編譯、是否正確打包,以及交付內容是否發生未經說明的變更。將這些結果固定為機器可讀的清單後,執行階段的模型缺失問題就能在封存階段被發現。
常見問題
為什麼不能只確認 Xcode 建置成功?
建置成功只代表工具鏈沒有回傳錯誤,不能證明預期模型已進入最終應用程式套件。仍需展開封存檔,核對 mlmodelc 的路徑與數量。
模型容量增加多少時應該中止建置?
應為每個模型保存已審核的基準值,再同時設定絕對增量與比例增量。不同大小的模型不適合套用同一個固定百分比。
套件內出現兩個同名模型一定是錯誤嗎?
至少應觸發調查。若它們並非供不同擴充目標使用,請檢查 Copy Bundle Resources、套件相依性與產生腳本是否重複複製。
為下一次建置選擇專屬 Apple Silicon 實體節點
按日、週、月或季租用 ArmVMS M4 與 ArmVMS M4 Pro。建立訂單前,可逐項確認設定、節點與附加項目。