Using Core ML in a .swiftpm file

Hi everyone,

I've been struggling for a few weeks to integrate my Core ML Image Classifier model into my .swiftpm project, and I’m hoping someone can help.

Here’s what I’ve done so far:

  1. I converted my .mlmodel file to .mlmodelc manually via the terminal.
  2. In my Package.swift file, I tried both "copy" and "process" options for the resource.

The issues I’m facing:

  • When using "process", Xcode gives me the error:

"multiple resources named 'coremldata.bin' in target 'AppModule'."

  • When using "copy", the app runs, but the model doesn’t work, and the terminal shows:

"A valid manifest does not exist at path: .../Manifest.json."

I even tried creating a Manifest.json manually to test, but this led to more errors, such as:

  • "File format version must be in the form of major.minor.patch."
  • "Failed to look up root model."

To check if the problem was specific to my model, I tested other Core ML models in the same setup, but none of them worked either.

I feel stuck and unsure of how to resolve these issues. Any guidance or suggestions would be greatly appreciated. Thanks in advance! :)

Answered by moto_coreml_apple in 819703022

You are on the right track that you precompiled a source model (.mlpackage or .mlmodel) to .mlmodelc manually before adding it to Swift Package. Currently SPM doesn't support compiling the source model in .process(:) rule.

Let's say, you have a source model named model.mlpackage. The first step is to compile it to .mlmodelc.

xcrun coremlcompiler compile model.mlpackage /tmp/

Then, copy the compiled model to your Swift Package.

cp -r /tmp/model.mlmodelc /path/to/ModelInSwiftPackage/Sources/ModelInSwiftPackage/

In my test, the resultant directory structure is something like this.

.
├── Package.swift
├── Sources
│   └── ModelInSwiftPackage
│       ├── ModelInSwiftPackage.swift
│       └── model.mlmodelc
│           ├── analytics
│           │   └── coremldata.bin
│           ├── coremldata.bin
│           └── model.mil
└── Tests
    └── ModelInSwiftPackageTests
        └── ModelInSwiftPackageTests.swift

Now, edit Package.swift as follows, using copy rule.

import PackageDescription

let package = Package(
:
    targets: [
        .target(
            name: "ModelInSwiftPackage",
            resources: [
                .copy("model.mlmodelc")]
        ),
)

Now, you should be able to build the package with the model.

To access the model, you can use Bundle.module.url().

public func loadModel() throws -> MLModel? {
    guard let modelURL = Bundle.module.url(forResource: "model", withExtension: "mlmodelc") else {
        return nil
    }

    return try MLModel(contentsOf: modelURL)
}

Hope it works!

Accepted Answer

You are on the right track that you precompiled a source model (.mlpackage or .mlmodel) to .mlmodelc manually before adding it to Swift Package. Currently SPM doesn't support compiling the source model in .process(:) rule.

Let's say, you have a source model named model.mlpackage. The first step is to compile it to .mlmodelc.

xcrun coremlcompiler compile model.mlpackage /tmp/

Then, copy the compiled model to your Swift Package.

cp -r /tmp/model.mlmodelc /path/to/ModelInSwiftPackage/Sources/ModelInSwiftPackage/

In my test, the resultant directory structure is something like this.

.
├── Package.swift
├── Sources
│   └── ModelInSwiftPackage
│       ├── ModelInSwiftPackage.swift
│       └── model.mlmodelc
│           ├── analytics
│           │   └── coremldata.bin
│           ├── coremldata.bin
│           └── model.mil
└── Tests
    └── ModelInSwiftPackageTests
        └── ModelInSwiftPackageTests.swift

Now, edit Package.swift as follows, using copy rule.

import PackageDescription

let package = Package(
:
    targets: [
        .target(
            name: "ModelInSwiftPackage",
            resources: [
                .copy("model.mlmodelc")]
        ),
)

Now, you should be able to build the package with the model.

To access the model, you can use Bundle.module.url().

public func loadModel() throws -> MLModel? {
    guard let modelURL = Bundle.module.url(forResource: "model", withExtension: "mlmodelc") else {
        return nil
    }

    return try MLModel(contentsOf: modelURL)
}

Hope it works!

@moto_coreml_apple

Hey, thanks for your reply! Unfortunately, it seems like I might be doing something differently or wrong. First, my .swiftpm file doesn’t have a Sources folder; it only has a Resources folder. When I tried running the command:

cp -r /tmp/model.mlmodelc /path/to/My.swiftpmFile/Sources/RandomFolder/

I got an error saying that the path is invalid. Even when I tried replacing Sources with Resources, it didn’t work either. So, I attempted to create a Sources folder manually, and inside it, I created a subfolder named NumbersModel where I placed both my .mlmodelc and .swift files. However, the issue persists.

Additionally, my package file is slightly different from yours. Here’s how it looks:

targets: [ .executableTarget( name: "AppModule", path: ".", resources: [ .copy("Resources/MyModel.mlmodelc") ], swiftSettings: [ .enableUpcomingFeature("BareSlashRegexLiterals") ] ) ]

Whereas, yours seems like this:

targets: [ .target( name: "ModelInSwiftPackage", resources: [ .copy("model.mlmodelc") ] ) ]

I’m still facing the same problem and would really appreciate any guidance. Do you think I might be missing something, or is there another step I should be taking? Let me know if you need any more details. Thanks in advance!

Using Core ML in a .swiftpm file
 
 
Q