Модель классификации изображений корректно работает при локальной отладке, а архивирование завершается успешно, однако после поставки приложение не может найти ресурс при первом запуске инференса. Обычно причина кроется не в алгоритме модели, а в том, что модель не включена в целевую сборку, скрипт скопировал её не в тот каталог или в пакете приложения осталась старая версия с тем же именем. Поэтому в конвейере на облачном Mac необходимо проверять не только успешность сборки, но и возможность независимо скомпилировать исходную модель, а также фактическое содержимое итогового пакета.
Разделите приёмку модели на два этапа
На первом этапе напрямую обрабатывается .mlmodel или .mlpackage, чтобы исключить проблемы с форматом модели, инструментарием и каталогом вывода. На втором этапе после архивирования в Xcode проверяется .app, чтобы убедиться, что скомпилированный каталог .mlmodelc действительно включён в поставляемую цель. Эти этапы необходимо разделять: если запускать только 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"
После успешного выполнения команды проверьте как минимум три условия: каталог вывода не пуст, список файлов создаётся, а размер не равен нулю. Не используйте каталог компиляции непосредственно как долговременный кеш: его содержимое зависит от версии 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
При сравнении также необходимо учитывать добавление, удаление и переименование. Новую модель нельзя автоматически считать ошибкой, однако в журнале изменений должно быть указано её назначение. Удаление модели должно блокировать сборку, если только обновление манифеста не прошло отдельную проверку. Переименование следует обрабатывать как удаление старой записи и добавление новой, чтобы случайно не унаследовать исторические данные.
Манифест следует сохранять как артефакт сборки, но не загружать вместе с ним конфиденциальные настройки, не относящиеся к исходной модели. Если модель загружается на контролируемом этапе, записывайте только хеш, логическое имя и итоговый путь внутри пакета. Не выводите в журнал URL загрузки с токенами.
Типичные сбои и контрольный список поставки
Если модель компилируется отдельно, но не попадает в пакет приложения, в первую очередь проверьте Target Membership, Copy Bundle Resources, зависимости пакетов ресурсов и условные настройки сборки. Если модель присутствует в Debug, но отсутствует в Release, сравните этапы обработки ресурсов и списки входов и выходов пользовательских скриптов в обеих конфигурациях.
При обнаружении нескольких моделей с одинаковым именем сначала составьте список полных относительных путей, а не удаляйте сразу одну из копий. Основному приложению и расширению действительно могут требоваться отдельные экземпляры; проблема заключается в незапланированном дублировании. Если размер архива внезапно вырос, также проверьте, не был ли весь каталог генерации добавлен на этап ресурсов вместе с исходной моделью, временными файлами и результатами компиляции.
Перед поставкой выполняйте проверку в следующем порядке:
- Зафиксируйте
DEVELOPER_DIRи запишите версию Xcode. - Запустите
coremlcompilerотдельно в изолированном каталоге. - Создайте манифесты хешей для исходной модели и результатов компиляции.
- Выполните архивирование конфигурации Release с совершенно новым каталогом DerivedData.
- Перечислите все каталоги
.mlmodelcвнутри.xcarchive. - Сверьте допустимые пути, ожидаемое количество и утверждённый базовый размер.
- Сохраните манифесты, вывод команд и идентификатор архива для последующих сравнений.
Этот процесс не оценивает точность модели и не заменяет сквозное тестирование инференса. Он решает более ранние задачи, лучше подходящие для автоматизации: компилируется ли модель, правильно ли она упакована и не изменилось ли содержимое поставки без объяснения причин. Если сохранять эти результаты в машиночитаемом манифесте, отсутствие модели во время выполнения можно обнаружить уже на этапе архивирования.
Часто задаваемые вопросы
Почему успешной сборки Xcode недостаточно?
Успешная сборка означает лишь отсутствие ошибки команды. Она не доказывает, что нужная модель попала в конечный пакет, поэтому необходимо отдельно найти и пересчитать каталоги mlmodelc внутри архива.
Как выбрать порог допустимого роста модели?
Сохраните утвержденный базовый размер каждой модели и проверяйте одновременно абсолютный и относительный прирост. Один общий процент для моделей разного размера дает ненадежные результаты.
Две модели с одинаковым именем всегда означают ошибку?
Такой результат требует проверки. Если копии не предназначены для разных расширений, изучите Copy Bundle Resources, зависимости пакетов и скрипты генерации на предмет повторного копирования.
Выберите выделенный физический узел Apple Silicon для следующей сборки
Арендуйте ArmVMS M4 и ArmVMS M4 Pro посуточно, понедельно, помесячно или поквартально. Конфигурация, узел и дополнительные опции отображаются для проверки перед оформлением заказа.