エンジニアリング実践

クラウドMacでCore MLモデルをコンパイルしてアプリ内を検証する

クラウドMacでCore MLモデルをコンパイルしてアプリ内を検証する

画像分類モデルがローカルデバッグでは正常に動作し、アーカイブも成功しているにもかかわらず、納品後のアプリが初回推論時にリソースを見つけられないことがあります。よくある原因はモデルのアルゴリズムではなく、モデルがターゲットに含まれていない、スクリプトによって誤ったディレクトリへコピーされている、あるいは同名の古いバージョンがアプリバンドルに残っていることです。そのため、クラウドMacのパイプラインでは「ビルドできるか」だけでなく、モデルソースを単体でコンパイルできるか、最終的な配布バンドルに何が含まれているかまで検証する必要があります。

モデル検証を2つのゲートに分ける

第1のゲートでは .mlmodel または .mlpackage を直接処理し、モデル形式、ツールチェーン、出力ディレクトリの問題を切り分けます。第2のゲートではXcodeのアーカイブ完了後に .app を検査し、コンパイル済みの .mlmodelc が実際に配布対象へ含まれていることを確認します。2つのゲートは必ず分けてください。xcodebuild だけを実行した場合、失敗の原因がモデルのコンパイル、Target Membership、リソースのコピー工程のどこにあるのかを迅速に判断できません。

開始前に開発者ディレクトリを固定し、ツールのバージョンを記録します。

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"

コマンドが成功したら、少なくとも3点を確認します。出力ディレクトリが空でないこと、ファイル一覧を生成できること、サイズがゼロでないことです。コンパイル先ディレクトリは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"

ここでは、メインアプリのルートディレクトリだけを検索してはいけません。モデルが拡張機能に属している場合や、リソースバンドルに含まれている場合があります。検査スクリプトでは完全な相対パスを記録し、プロジェクトの定義に従って、どのパスへの配置を許可するか判断します。モデルを1つだけ想定しているのに同名のディレクトリが2つ見つかった場合は、メインアプリ、拡張機能、依存バンドルのそれぞれにコピーされていないか、直ちに確認してください。

推測に依存しないベースラインを構築する

モデルのサイズは構造やコンパイルツールの影響を受けるため、「一律の割合を超えて増加したら失敗」と単純に規定することはできません。より確実な方法は、レビュー済みのベースラインをモデルごとに保存し、絶対増分と割合増分の両方にしきい値を設定することです。小さなモデルでは絶対値を重視し、大きなモデルでは割合をより重視します。

ベースラインファイルには、単純なタブ区切り形式を使用できます。

Classifier.mlmodelc 8421376
Embedding.mlmodelc  27156480

比較時には、追加、削除、名前変更も処理する必要があります。モデルの追加を自動的にエラーとみなす必要はありませんが、変更履歴で用途を説明しなければなりません。モデルが削除された場合は、マニフェストもレビューのうえで更新されていない限り処理を停止します。名前変更は「古い項目の削除と新しい項目の追加」として扱い、過去のデータを誤って引き継がないようにします。

マニフェストはビルド成果物として保存しますが、モデルソース以外の機密設定をアップロードしてはいけません。管理されたダウンロード工程でモデルを取得する場合は、ハッシュ、論理名、最終バンドル内のパスだけを記録し、トークンを含むダウンロードURLをログへ出力しないでください。

よくある失敗と配布前の確認項目

モデルを単体でコンパイルできるのにアプリバンドルへ含まれない場合は、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を、日単位、週単位、月単位、または四半期単位でレンタルできます。注文を作成する前に、構成、ノード、追加項目を一つずつ確認できます。

構成を選択して注文を作成