Core ML Swift 통합
iOS 앱에서 Core ML 모델을 로드, 구성 및 실행합니다. 이 스킬은 Swift 측면(모델 로딩, 예측, MLTensor, 프로파일링, 배포)을 다룹니다. 명시된 경우를 제외하고 iOS 26+ 및 Swift 6.3을 대상으로 하며, iOS 14까지 하위 호환됩니다.
범위 경계: Python 측의 모델 변환, 최적화(양자화, 팔레타이제이션, 가지치기) 및 프레임워크 선택은
apple-on-device-ai스킬에서 다룹니다. 이 스킬은 Swift 통합만을 전담합니다.
전체 코드 패턴(액터 기반 캐싱, 배치 추론, 이미지 전처리, 테스트 포함)은 references/coreml-swift-integration.md를 참조하십시오.
목차
- 모델 로딩
- 모델 구성
- 예측 생성
- MLTensor (iOS 18+)
- MLMultiArray 작업
- 이미지 전처리
- 다중 모델 파이프라인
- Vision 통합
- 성능 프로파일링
- 모델 배포
- 메모리 관리
- 일반적인 실수
- 리뷰 체크리스트
- 참고 자료
모델 로딩
자동 생성 클래스
.mlpackage 또는 .mlmodelc를 Xcode로 드래그하면 입력/출력이 타입화된 Swift 클래스가 생성됩니다. 가능하면 항상 이를 사용하십시오.
import CoreML
let config = MLModelConfiguration()
config.computeUnits = .all
let model = try MyImageClassifier(configuration: config)
수동 로딩
런타임에 모델이 다운로드되거나 번들 외부에 저장된 경우 URL에서 로드합니다.
let modelURL = Bundle.main.url(
forResource: "MyModel", withExtension: "mlmodelc"
)!
let model = try MLModel(contentsOf: modelURL, configuration: config)
비동기 로딩 (iOS 16+)
메인 스레드를 차단하지 않고 모델을 로드합니다. 큰 모델의 경우 이 방식을 선호하십시오.
let model = try await MLModel.load(
contentsOf: modelURL,
configuration: config
)
런타임 컴파일
기기에서 .mlpackage 또는 .mlmodel을 .mlmodelc로 컴파일합니다. 서버에서 다운로드된 모델에 유용합니다.
let compiledURL = try await MLModel.compileModel(at: packageURL)
let model = try MLModel(contentsOf: compiledURL, configuration: config)
컴파일된 URL을 캐싱하십시오. 매번 실행할 때마다 다시 컴파일하는 것은 시간 낭비입니다. compiledURL을 영구적인 위치(예: Application Support)로 복사하십시오.
모델 구성
MLModelConfiguration은 컴퓨트 유닛, GPU 액세스 및 모델 매개변수를 제어합니다.
컴퓨트 유닛 결정표
| 값 | 사용 | 선택 시기 |
|---|---|---|
.all | CPU + GPU + Neural Engine | 기본값. 시스템이 결정하도록 합니다. |
.cpuOnly | CPU | 백그라운드 작업, 오디오 세션, 또는 GPU가 사용 중일 때. |
.cpuAndGPU | CPU + GPU | GPU가 필요하지만 모델에 ANE에서 지원되지 않는 연산이 있는 경우. |
.cpuAndNeuralEngine | CPU + Neural Engine | 호환되는 모델에 가장 좋은 에너지 효율. |
let config = MLModelConfiguration()
config.computeUnits = .cpuAndNeuralEngine
// 낮은 우선순위 백그라운드 추론 허용
config.computeUnits = .cpuOnly
구성 속성
let config = MLModelConfiguration()
config.computeUnits = .all
config.allowLowPrecisionAccumulationOnGPU = true // 더 빠름, 약간의 정밀도 손실
예측 생성
자동 생성 클래스 사용
생성된 클래스는 타입화된 입력/출력 구조체를 제공합니다.
let model = try MyImageClassifier(configuration: config)
let input = MyImageClassifierInput(image: pixelBuffer)
let output = try model.prediction(input: input)
print(output.classLabel) // "golden_retriever"
print(output.classLabelProbs) // ["golden_retriever": 0.95, ...]
MLDictionaryFeatureProvider 사용
입력이 동적이거나 컴파일 시간에 알 수 없는 경우 사용합니다.
let inputFeatures = try MLDictionaryFeatureProvider(dictionary: [
"image": MLFeatureValue(pixelBuffer: pixelBuffer),
"confidence_threshold": MLFeatureValue(double: 0.5),
])
let output = try model.prediction(from: inputFeatures)
let label = output.featureValue(for: "classLabel")?.stringValue
비동기 예측 (iOS 17+)
let output = try await model.prediction(from: inputFeatures)
배치 예측
더 나은 처리량을 위해 한 번의 호출로 여러 입력을 처리합니다.
let batchInputs = try MLArrayBatchProvider(array: inputs.map { input in
try MLDictionaryFeatureProvider(dictionary: ["image": MLFeatureValue(pixelBuffer: input)])
})
let batchOutput = try model.predictions(from: batchInputs)
for i in 0..<batchOutput.count {
let result = batchOutput.features(at: i)
print(result.featureValue(for: "classLabel")?.stringValue ?? "unknown")
}
상태 유지 예측 (iOS 18+)
예측 간 상태를 유지하는 모델(시퀀스 모델, LLM, 오디오 누적기)에 MLState를 사용합니다. 상태를 한 번 생성하고 각 예측 호출에 전달합니다.
let state = model.makeState()
// 각 예측이 내부 모델 상태를 전달
for frame in audioFrames {
let input = try MLDictionaryFeatureProvider(dictionary: [
"audio_features": MLFeatureValue(multiArray: frame)
])
let output = try await model.prediction(from: input, using: state)
let classification = output.featureValue(for: "label")?.stringValue
}
State는 Sendable이 아닙니다. 단일 액터 또는 태스크에서 사용하십시오. model.makeState()를 호출하여 동시 스트림에 대한 독립적인 상태를 생성합니다.
MLTensor (iOS 18+)
MLTensor는 전처리/후처리를 위한 Swift 네이티브 다차원 배열입니다. 연산은 지연 실행되며 결과를 구체화하려면 .shapedArray(of:)를 호출하십시오.
import CoreML
// 생성
let tensor = MLTensor([1.0, 2.0, 3.0, 4.0])
let zeros = MLTensor(zeros: [3, 224, 224], scalarType: Float.self)
// 형태 변경
let reshaped = tensor.reshaped(to: [2, 2])
// 수학 연산
let softmaxed = tensor.softmax()
let normalized = (tensor - tensor.mean()) / tensor.standardDeviation()
// MLMultiArray와 상호 운용
let multiArray = try MLMultiArray([1.0, 2.0, 3.0, 4.0])
let fromMultiArray = MLTensor(multiArray)
let backToArray = tensor.shapedArray(of: Float.self)
MLMultiArray 작업
MLMultiArray는 이미지가 아닌 모델 입력 및 출력에 대한 기본 데이터 교환 유형입니다. 자동 생성 클래스가 배열 유형의 특징을 예상할 때 사용하십시오.
// 3D 배열 생성: [batch, sequence, features]
let array = try MLMultiArray(shape: [1, 128, 768], dataType: .float32)
// 값 쓰기
for i in 0..<128 {
array[[0, i, 0] as [NSNumber]] = NSNumber(value: Float(i))
}
// 값 읽기
let value = array[[0, 0, 0] as [NSNumber]].floatValue
// 제로-카피 상호 운용을 위한 데이터 포인터에서 생성
let data: [Float] = [1.0, 2.0, 3.0]
let fromData = try MLMultiArray(dataPointer: UnsafeMutableRawPointer(mutating: data),
shape: [3],
dataType: .float32,
strides: [1])
NLP 토큰화 및 오디오 특징 추출을 포함한 고급 MLMultiArray 패턴은 references/coreml-swift-integration.md를 참조하십시오.
이미지 전처리
이미지 모델은 CVPixelBuffer 입력을 기대합니다. 카메라 또는 사진 라이브러리의 사진에는 CGImage 변환을 사용하십시오. Vision의 VNCoreMLRequest는 이를 자동으로 처리합니다. 수동 변환은 직접 MLModel 예측에만 필요합니다.
import CoreVideo
func createPixelBuffer(from cgImage: CGImage, width: Int, height: Int) -> CVPixelBuffer? {
var pixelBuffer: CVPixelBuffer?
let attrs: [CFString: Any] = [
kCVPixelBufferCGImageCompatibilityKey: true,
kCVPixelBufferCGBitmapContextCompatibilityKey: true,
]
CVPixelBufferCreate(kCFAllocatorDefault, width, height,
kCVPixelFormatType_32ARGB, attrs as CFDictionary, &pixelBuffer)
guard let buffer = pixelBuffer else { return nil }
CVPixelBufferLockBaseAddress(buffer, [])
let context = CGContext(
data: CVPixelBufferGetBaseAddress(buffer),
width: width, height: height,
bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(buffer),
space: CGColorSpaceCreateDeviceRGB(),
bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue
)
context?.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
CVPixelBufferUnlockBaseAddress(buffer, [])
return buffer
}
정규화, 중앙 자르기와 같은 추가 전처리 패턴은 references/coreml-swift-integration.md를 참조하십시오.
다중 모델 파이프라인
전처리 또는 후처리에 별도의 모델이 필요한 경우 모델을 연결합니다.
// 순차 추론: 전처리기 -> 주 모델 -> 후처리기
let preprocessed = try preprocessor.prediction(from: rawInput)
let mainOutput = try mainModel.prediction(from: preprocessed)
let finalOutput = try postprocessor.prediction(from: mainOutput)
Xcode 관리 파이프라인의 경우, .mlpackage의 파이프라인 모델 유형을 사용하십시오. 각 하위 모델은 최적의 컴퓨트 유닛에서 실행됩니다.
Vision 통합
Vision을 사용하여 자동 이미지 전처리(크기 조정, 정규화, 색 공간, 방향)와 함께 Core ML 이미지 모델을 실행합니다.
최신: CoreMLRequest (iOS 18+)
import Vision
import CoreML
let model = try MLModel(contentsOf: modelURL, configuration: config)
let request = CoreMLRequest(model: .init(model))
let results = try await request.perform(on: cgImage)
if let classification = results.first as? ClassificationObservation {
print("\(classification.identifier): \(classification.confidence)")
}
레거시: VNCoreMLRequest
let vnModel = try VNCoreMLModel(for: model)
let request = VNCoreMLRequest(model: vnModel) { request, error in
guard let results = request.results as? [VNRecognizedObjectObservation] else { return }
for observation in results {
let label = observation.labels.first?.identifier ?? "unknown"
let confidence = observation.labels.first?.confidence ?? 0
let boundingBox = observation.boundingBox // 정규화된 좌표
print("\(label): \(confidence) at \(boundingBox)")
}
}
request.imageCropAndScaleOption = .scaleFill
let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer)
try handler.perform([request])
완전한 Vision 프레임워크 패턴(텍스트 인식, 바코드 감지, 문서 스캐닝)은
vision-framework스킬을 참조하십시오.
성능 프로파일링
MLComputePlan (iOS 17.4+)
예측을 실행하기 전에 각 연산이 어떤 컴퓨트 장치를 사용할지 검사합니다.
let computePlan = try await MLComputePlan.load(
contentsOf: modelURL, configuration: config
)
guard case let .program(program) = computePlan.modelStructure else { return }
guard let mainFunction = program.functions["main"] else { return }
for operation in mainFunction.block.operations {
let deviceUsage = computePlan.deviceUsage(for: operation)
let estimatedCost = computePlan.estimatedCost(of: operation)
print("\(operation.operatorName): \(deviceUsage?.preferredComputeDevice ?? "unknown")")
}
Instruments
Instruments의 Core ML 계측 템플릿을 사용하여 프로파일링하십시오:
- 모델 로드 시간
- 예측 지연 시간(연산별 분석)
- 컴퓨트 장치 디스패치(연산별 CPU/GPU/ANE)
- 메모리 할당
정확한 결과를 위해 디버거 외부에서 실행하십시오(Xcode: Product > Profile).
모델 배포
번들 vs 온디맨드 리소스
| 전략 | 장점 | 단점 |
|---|---|---|
| 앱 번들 | 즉시 사용 가능, 오프라인 작동 | 앱 다운로드 크기 증가 |
| 온디맨드 리소스 | 초기 다운로드 크기 감소 | 첫 사용 전 다운로드 필요 |
| 백그라운드 에셋 (iOS 16+) | 미리 다운로드 | 설정이 더 복잡함 |
| CloudKit / 서버 | 최대 유연성 | 네트워크 필요, 설정 시간 증가 |
크기 고려 사항
- App Store 제한: 앱 번들 4 GB
- 셀룰러 다운로드 제한: 200 MB (예외 요청 가능)
- 50 MB 이상의 모델에는 ODR 태그 사용
- 기기에서 컴파일을 건너뛰려면 미리
.mlmodelc로 컴파일
// 온디맨드 리소스 로딩
let request = NSBundleResourceRequest(tags: ["ml-model-v2"])
try await request.beginAccessingResources()
let modelURL = Bundle.main.url(forResource: "LargeModel", withExtension: "mlmodelc")!
let model = try await MLModel.load(contentsOf: modelURL, configuration: config)
// 완료되면 request.endAccessingResources() 호출
메모리 관리
- 백그라운드에서 언로드: 앱이 백그라운드로 진입할 때 GPU/ANE 메모리를 확보하기 위해 모델 참조를 해제하십시오. 포그라운드로 돌아오면 다시 로드합니다.
- 백그라운드 작업에는
.cpuOnly사용: 백그라운드 처리는 GPU 또는 ANE를 사용할 수 없습니다..cpuOnly를 설정하면 자동 대체 및 리소스 경합을 피할 수 있습니다. - 모델 인스턴스 공유: 동일한 컴파일된 모델에서 여러
MLModel인스턴스를 절대 생성하지 마십시오. 액터를 사용하여 공유 액세스를 제공하십시오. - 메모리 압력 모니터링: 큰 모델(>100MB)은 메모리 경고를 트리거할 수 있습니다.
UIApplication.didReceiveMemoryWarningNotification에 등록하고 압력이 있을 때 캐시된 모델을 해제하십시오.
액터 기반 모델 관리자(수명 주기 인식 로딩 및 캐시 제거 포함)는 references/coreml-swift-integration.md를 참조하십시오.
일반적인 실수
하지 말아야 할 것: 메인 스레드에서 모델을 로드합니다.
해야 할 것: MLModel.load(contentsOf:configuration:) 비동기 API를 사용하거나 백그라운드 액터에서 로드합니다.
이유: 큰 모델은 로드하는 데 몇 초가 걸릴 수 있어 UI가 멈출 수 있습니다.
하지 말아야 할 것: 앱 실행 시마다 .mlpackage를 .mlmodelc로 다시 컴파일합니다.
해야 할 것: MLModel.compileModel(at:)으로 한 번 컴파일하고 컴파일된 URL을 영구적으로 캐싱합니다.
이유: 컴파일은 비용이 많이 듭니다. Application Support에 .mlmodelc를 캐싱하십시오.
하지 말아야 할 것: 특별한 이유 없이 .cpuOnly를 하드코딩합니다.
해야 할 것: .all을 사용하고 시스템이 최적의 컴퓨트 유닛을 선택하도록 합니다.
[번역을 위해 잘림]