이미지 분류 모델이 로컬 디버깅에서는 정상적으로 작동하고 아카이브도 성공했지만, 배포된 앱이 첫 추론 시 리소스를 찾지 못하는 경우가 있습니다. 흔한 원인은 모델 알고리즘이 아니라 모델이 대상에 포함되지 않았거나, 스크립트가 잘못된 디렉터리로 복사했거나, 같은 이름의 이전 버전이 앱 번들에 남아 있는 것입니다. 따라서 클라우드 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"
명령이 성공하면 최소한 세 가지를 확인해야 합니다. 출력 디렉터리가 비어 있지 않고, 파일 목록을 생성할 수 있으며, 크기가 0이 아니어야 합니다. 컴파일 디렉터리는 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를 독립적으로 실행합니다. - 모델 소스와 컴파일 결과물의 해시 목록을 생성합니다.
- 완전히 새로운 DerivedData를 사용해 Release 아카이브를 완료합니다.
.xcarchive내부의 모든.mlmodelc를 열거합니다.- 허용된 경로, 예상 개수, 검토된 크기 기준선과 대조합니다.
- 이후 비교를 위해 목록, 명령 출력, 아카이브 식별자를 저장합니다.
이 절차는 모델 정확도를 판단하지 않으며 엔드 투 엔드 추론 테스트를 대체할 수도 없습니다. 대신 모델을 컴파일할 수 있는지, 올바르게 패키징되었는지, 배포 내용에 설명되지 않은 변경이 발생했는지처럼 더 앞 단계에서 자동화하기 적합한 문제를 해결합니다. 이러한 결과를 머신 리더블 목록으로 고정하면 런타임의 모델 누락 문제를 아카이브 단계에서 발견할 수 있습니다.
자주 묻는 질문
Xcode 빌드 성공만 확인하면 충분하지 않은 이유는 무엇인가요?
빌드 성공은 컴파일 명령이 오류 없이 끝났다는 뜻일 뿐, 필요한 모델이 최종 앱 번들에 포함됐다는 보장은 아닙니다. 아카이브에서 mlmodelc 경로와 개수를 별도로 확인해야 합니다.
모델 크기 변화의 차단 기준은 어떻게 정해야 하나요?
검토가 끝난 모델별 기준값을 저장한 뒤 절대 증가량과 비율 증가량을 함께 적용하는 방식이 안전합니다. 모든 모델에 동일한 고정 비율을 적용하지 않는 것이 좋습니다.
앱 번들에 같은 이름의 모델이 두 개 있으면 오류인가요?
서로 다른 확장 대상에 필요한 경우가 아니라면 조사해야 합니다. Copy Bundle Resources, 패키지 의존성, 생성 스크립트가 같은 모델을 중복 복사하는지 확인하세요.
다음 빌드를 위한 독점 Apple Silicon 물리 노드 선택
ArmVMS M4 및 ArmVMS M4 Pro를 일·주·월·분기 단위로 대여하세요. 주문을 만들기 전에 구성, 노드 및 추가 항목을 하나씩 확인할 수 있습니다.