工程實務

雲端 Mac 的 Core ML 模型編譯與應用程式套件驗收

雲端 Mac 的 Core ML 模型編譯與應用程式套件驗收

一個影像分類模型在本機除錯時運作正常,封存也顯示成功,交付後的應用程式卻在首次推論時找不到資源。常見原因並非模型演算法,而是模型未加入目標、被指令碼複製到錯誤目錄,或應用程式套件中仍留有同名的舊版本。因此,雲端 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 缺少模型,請比較兩種設定的資源階段,以及自訂指令碼的輸入與輸出清單。

如果同名模型重複出現,應先盤點完整的相對路徑,不要直接刪除其中一份。主應用程式與延伸功能可能確實各自需要一份副本;真正的問題是未經設計的重複複製。如果封存檔大小突然增加,還應檢查生成目錄是否被整體加入資源階段,導致原始模型、暫存檔與編譯產物一併打包進應用程式。

提交前可依照以下順序驗收:

  1. 固定 DEVELOPER_DIR 並記錄 Xcode 版本。
  2. 在隔離目錄中獨立執行 coremlcompiler
  3. 為模型原始檔與編譯結果產生摘要清單。
  4. 使用全新的 DerivedData 完成 Release 封存。
  5. .xcarchive 中列舉所有 .mlmodelc
  6. 比對允許的路徑、預期數量及已審核的大小基準。
  7. 保存清單、指令輸出與封存識別資訊,以便後續比較。

這套流程不會判斷模型準確度,也不能取代端對端推論測試。它處理的是更前段、也更適合自動化的問題:模型是否可以編譯、是否正確打包,以及交付內容是否發生未經說明的變更。將這些結果固定為機器可讀的清單後,執行階段的模型缺失問題就能在封存階段被發現。

常見問題

為什麼不能只確認 Xcode 建置成功?

建置成功只代表工具鏈沒有回傳錯誤,不能證明預期模型已進入最終應用程式套件。仍需展開封存檔,核對 mlmodelc 的路徑與數量。

模型容量增加多少時應該中止建置?

應為每個模型保存已審核的基準值,再同時設定絕對增量與比例增量。不同大小的模型不適合套用同一個固定百分比。

套件內出現兩個同名模型一定是錯誤嗎?

至少應觸發調查。若它們並非供不同擴充目標使用,請檢查 Copy Bundle Resources、套件相依性與產生腳本是否重複複製。

ArmVMS 雲端 Mac

為下一次建置選擇專屬 Apple Silicon 實體節點

按日、週、月或季租用 ArmVMS M4 與 ArmVMS M4 Pro。建立訂單前,可逐項確認設定、節點與附加項目。

選擇設定並建立訂單