<!--
{
  "documentType" : "article",
  "framework" : "CoreAI",
  "identifier" : "/documentation/CoreAI/integrating-on-device-ai-models-in-your-app-with-core-ai",
  "metadataVersion" : "0.1.0",
  "role" : "article",
  "title" : "Integrating on-device AI models in your app with Core AI"
}
-->

# Integrating on-device AI models in your app with Core AI

Power your app’s intelligent features with an on-device AI model.

## Overview

Core AI allows you to deploy AI models within your app. Inference happens on device, so data stays private, AI features can be readily available and work offline, and there is no per-inference cost to you or the people using your app.

You start with an `.aimodel` file, either converted from a model using the [Core AI PyTorch Extensions Python package](https://apple.github.io/coreai-torch) or already prepared in the correct format. The model it represents should contain one or more inference functions needed to power your app’s intelligent features.

## Add the model file to your project

To use a Core AI model, your app needs access to the `.aimodel` file at runtime. You can bundle the file directly in your Xcode project or Swift package, or your app can download it over the network. The following steps show how to bundle and configure the model in Xcode.

Start by adding the model file to an Xcode target:

1. Drag the `.aimodel` file from the Finder into the Project Navigator in Xcode, or choose File > Add Files to add it.
2. When the sheet appears, select the targets to include the model under Add to targets, then review the remaining options.
   
   ![A screenshot of the dialog that appears when adding a file to an Xcode project. It includes options to copy items to the destination or reference files in place, and an Add to targets section where you select which targets include the model file.](add-model-to-project-dialog)

1. Click Finish.

> Note: After adding the file, you should also see the model in the Compile Sources build phase for that target.

## Add the Metal Toolchain to Xcode

Core AI model integration in Xcode requires the Metal Toolchain, which isn’t installed by default. There are two options for adding the Metal Toolchain:

1. In Xcode, choose Xcode > Settings > Components > Other Components, then click Get to download and install the Metal Toolchain.
   
   ![A screenshot of the Other Components section in Xcode Settings showing the Metal Toolchain available for download, with a Get button to install it.](metal-toolchain-settings)

1. In Xcode, select any `.aimodel` file in your project and click the Get button in the Metal toolchain download bar that appears.
   
   ![A dot-aimodel file selected in the Xcode Project Navigator, with a download bar prompting to install the Metal Toolchain.](metal-toolchain-file-inspector)

> Important: If the Metal toolchain isn’t included, builds that include `.aimodel` files fail with a missing Metal compiler error.

## Inspect the model in Xcode

Before writing the code to use the model in your app, you can view its details in the Xcode model viewer by selecting the `.aimodel` file in the Project Navigator.

The model viewer has several tabs for exploring different aspects of your model. The General tab shows the model’s size, in number of parameters and storage size on disk, along with metadata such as description, author, license, and creator-defined key-value pairs. You can edit metadata fields inline; Xcode saves your changes automatically.

![A screenshot of the General tab of the Xcode model viewer, showing the model’s parameter count, storage size on disk, and metadata fields including description, author, license, and creator-defined key-value pairs.](images/com.apple.coreai/model-viewer-metadata@2x.png)

The General tab also shows the model’s numeric precision, split into compute and storage categories:

- Compute types are the representations used during inference.
- Storage types are the representations used for the model’s weights on disk.
- The operation distribution shows a breakdown of operations in the model’s graph, sorted by count.

## Review inference functions

The Functions tab shows the exact function signature of each function in the model, including the names, types, and optional descriptions for each input and output.

![A screenshot of the Functions tab of the Xcode model viewer, listing the model’s inference functions with their input and output tensor names, type signatures, and descriptions.](images/com.apple.coreai/model-viewer-functions@2x.png)

Most models have a single function. The named inputs and outputs describe what data your code provides and what it returns. A question mark in an `NDArray` dimension means the dimension is dynamic and is supplied or determined at runtime.

## Load the model

Load the model in your app by creating an [`AIModel`](/documentation/CoreAI/AIModel) from the `.aimodel` file.

```swift
import CoreAI

// Specialize the model for this device and load it.
let model = try await AIModel(contentsOf: urlOfModel)

// Load a function from the model.
guard let function = try model.loadFunction(named: "main") else {
    // Handle case where expected function is not found.
}
```

Core AI specializes the model for the current device, considering all available compute units and selecting the combination that delivers the best performance. [`init(contentsOf:options:)`](/documentation/CoreAI/AIModel/init(contentsOf:options:)) is asynchronous because specialization needs to complete before a valid `AIModel` is returned. Depending on the model size, specialization can take a significant amount of time.

Call [`loadFunction(named:)`](/documentation/CoreAI/AIModel/loadFunction(named:)) to get an [`InferenceFunction`](/documentation/CoreAI/InferenceFunction) for running the model with your inputs and receiving its outputs. Loading a function prepares the resources needed to run that function and can also be expensive. The method throws on a load failure, and returns `nil` when no function with that name exists. For more information, see [Managing model specialization and caching](/documentation/CoreAI/managing-model-specialization-and-caching).

Most models have a single function. If the model contains multiple functions, check [`functionNames`](/documentation/CoreAI/AIModel/functionNames) to see all available names. If your app processes multiple inputs simultaneously, you can safely call the same inference function from different tasks.

## Inspect function inputs and outputs

Depending on your model deployment strategy, you may need to inspect the model’s inference function at runtime. Each inference function has an [`InferenceFunctionDescriptor`](/documentation/CoreAI/InferenceFunctionDescriptor) that describes the names, types, and shapes of its inputs and outputs. You can use this descriptor to verify that a function accepts the inputs your app provides, or to dynamically adapt your app’s behavior as the model’s inputs and outputs change between deployments, without needing to change your code.

For example, you can check that the function’s input matches the shape and type your app expects:

```swift
let function: InferenceFunction = ...

let functionDescriptor = function.descriptor
guard let valueDescriptor = functionDescriptor.inputDescriptor(of: "input"),
      case .ndArray(let arrayDescriptor) = valueDescriptor else {
        // Handle input not found, or an unexpected type.
}

guard arrayDescriptor.shape == [3, 4] else {
    // Handle an unexpected shape.
}

guard arrayDescriptor.scalarType == .float32 else {
    // Handle an unexpected scalar type.
}
```

The [`inputDescriptor(of:)`](/documentation/CoreAI/InferenceFunctionDescriptor/inputDescriptor(of:)) method returns an [`InferenceValue.Descriptor`](/documentation/CoreAI/InferenceValue/Descriptor) for a named input. The descriptor tells you whether the input expects an [`NDArray`](/documentation/CoreAI/NDArray) or an image, along with its shape and type.

## Run inference

The [`NDArray`](/documentation/CoreAI/NDArray) type represents the input and output tensors from the converted model function at runtime. Values marked as images at conversion time use <doc://com.apple.documentation/documentation/CoreVideo/CVMutablePixelBuffer>. Pass your data using the same input names defined at model conversion time.

For [`NDArray`](/documentation/CoreAI/NDArray) values, write input data with [`NDArray.MutableView`](/documentation/CoreAI/NDArray/MutableView) and read results with [`NDArray.View`](/documentation/CoreAI/NDArray/View). Swift enforces this at compile time. A mutable view allows writes, and a view allows only reads, so you always know how your data is accessed.

Start by creating an `NDArray` that matches the shape and type the model expects:

```swift
// Create an `NDArray` that matches the expected type and shape.
var input = NDArray(shape: [3, 4], scalarType: .float32)
```

Because an [`NDArray`](/documentation/CoreAI/NDArray) is an *n*-dimensional array, the shape should match what the model expects. In this example, `[3, 4]` matches the input shape defined at the `.aimodel` creation. The [`NDArray.ScalarType`](/documentation/CoreAI/NDArray/ScalarType-swift.enum) defines what kind of number each element holds, such as [`NDArray.ScalarType.float32`](/documentation/CoreAI/NDArray/ScalarType-swift.enum/float32) for 32-bit floating-point values.

An `NDArray` is read-only by default. To write data into it, call [`mutableView(as:)`](/documentation/CoreAI/NDArray/mutableView(as:)) which gives you direct write access to the underlying memory:

```swift
// Access a mutable view to write data into the array.
var mutableView = input.mutableView(as: Float.self)
guard let elements = mutableView.contiguousElements else {
    // Handle non-contiguous memory layout.
}

// Your function that writes input data into the mutable span.
writeInputData(into: elements)
```

When the input data is ready, pass the [`NDArray`](/documentation/CoreAI/NDArray) to the inference function to run the model:

```swift
// Run the function with the `NDArray` input.
var outputs = try await function.run(inputs: ["input": input])
```

After the model runs, call [`remove(_:)`](/documentation/CoreAI/InferenceFunction/Outputs/remove(_:)) with the output name to extract each result. The result is an [`InferenceValue`](/documentation/CoreAI/InferenceValue) which holds either an `NDArray` or an image. To check which type your output uses, look at the function signature in the model viewer’s Functions tab, or inspect the [`InferenceFunctionDescriptor`](/documentation/CoreAI/InferenceFunctionDescriptor) at runtime. Access the output with `.ndArray` or `.pixelBuffer` based on the type.

To read data from an output `NDArray`, access an [`NDArray.View`](/documentation/CoreAI/NDArray/View):

```swift
// Extract the returned output.
guard let predictionValue = outputs.remove("prediction") else {
    // Handle output not found.
}

guard let prediction = predictionValue.ndArray else {
    // Handle output of unexpected type of value.
}

// Read the output data through a view.
// Your function that processes the output.
processOutput(prediction.view())
```

## Run inference with image inputs and outputs

When a function expects an image instead of an [`NDArray`](/documentation/CoreAI/NDArray), the input or output uses <doc://com.apple.documentation/documentation/CoreVideo/CVMutablePixelBuffer>. The descriptor tells you the expected dimensions and pixel format, so your code can provide a matching pixel buffer to the function.

Read the input descriptor and confirm the value is an image. Then read width, height, and pixel format from the [`ImageDescriptor`](/documentation/CoreAI/ImageDescriptor):

```swift
guard let inputDescriptor = function.descriptor.inputDescriptor(of: "image"),
      case .image(let imageDescriptor) = inputDescriptor else {
    // Handle a missing or unexpected input.
}

let expectedWidth = imageDescriptor.width
let expectedHeight = imageDescriptor.height
let expectedFormat = imageDescriptor.pixelFormatType
```

A `width` or `height` of `-1` indicates a dynamic dimension. The [`pixelFormatType`](/documentation/CoreAI/ImageDescriptor/pixelFormatType) is an <doc://com.apple.documentation/documentation/kernel/ostype> your code can compare against the format constants in <doc://com.apple.documentation/documentation/CoreVideo>.

Pass a <doc://com.apple.documentation/documentation/CoreVideo/CVMutablePixelBuffer> that matches the expected format and dimensions as an input. Build an [`InferenceFunction.Inputs`](/documentation/CoreAI/InferenceFunction/Inputs) collection, add the buffer with `InferenceFunction/Inputs/insert(_:for:)`, and run the function:

```swift
var pixelBuffer = try makeImageBuffer(
    width: expectedWidth,
    height: expectedHeight,
    pixelFormat: expectedFormat
)

// Fill the buffer with image data.
writeImageData(into: &pixelBuffer)

// Build the inputs collection and run the function.
var inputs = InferenceFunction.Inputs()
inputs.insert(pixelBuffer, for: "image")

var outputs = try await function.run(inputs: inputs)
```

When the function returns an image, extract the output and read its [`pixelBuffer`](/documentation/CoreAI/InferenceValue/pixelBuffer):

```swift
guard let outputValue = outputs.remove("result"),
      let resultBuffer = outputValue.pixelBuffer else {
    // Handle a missing output, or an output that isn't an image.
}

display(resultBuffer)
```

---

Copyright &copy; 2026 Apple Inc. All rights reserved. | [Terms of Use](https://www.apple.com/legal/internet-services/terms/site.html) | [Privacy Policy](https://www.apple.com/privacy/privacy-policy)