Optimizing ML models for Core ML on Apple devices

The model runs on a server, but the app needs a response here and now — without waiting for the network. We handle model conversion to Core ML end-to-end: we move computation to the device, verify accuracy, and deliver a production-ready build. The app responds noticeably faster, battery consumption drops, and model behavior remains predictable across the entire device fleet.
Frequently Asked Questions

Why model conversion to Core ML breaks during validation

Converting a model to Core ML looks like a single call to the converter, but the error when transferring a model almost never occurs at this step.

The first barrier is unsupported operations: a research prototype usually contains layers for which Apple has no direct equivalent, and the converter either stops or silently substitutes a simplified replacement.

The model builds and runs, but it already computes something else — this is where expectations and reality diverge.

The second barrier is divergence in results. During transfer, compute types change: training runs at full precision, on-device half precision is enabled, and with it comes different rounding and operation order.

A difference that looks like the third decimal place in a test environment becomes a different class or a shifted bounding box in the output. That is why on-device validation must run not on one successful example, but on a set of reference answers captured before conversion.

The third barrier is real-world operating conditions. The device is memory-constrained, heats up and throttles, the camera and microphone consume resources, and the app is sent to the background.

A model that is stable on a test bench produces delays and failures in the field; some defects are visible only in logs on live builds.

We break down how this is addressed: analyzing the graph for incompatible nodes, replacing them with equivalent ones, answer parity tests, and profiling on real device models.

What optimization delivers: speed, response time, device resources

Running the model locally changes the application profile more than it seems at the prototype stage.

Computation happens on the device itself via Core ML, so response time no longer depends on network quality: a frame or text is processed in fractions of a second, rather than after a round trip to the server and back.

The user sees the interface respond immediately — at this level, the difference between "works" and "works pleasantly" becomes obvious.

We calculate battery consumption separately. An unquantized model loads the compute unit to the maximum on every frame, and the device heats up after just a few minutes of operation.

After conversion and selection of the compute type (CPU, GPU, Neural Engine), the load falls on the dedicated chip block, and battery charge is consumed noticeably more evenly at the same answer quality.

App size is the third metric visible in store reports. Model weight is part of the build and directly affects the share of canceled installs: the larger the package, the more often the user interrupts the download.

Weight compression and removal of unused layers reduce the model's contribution to build size severalfold, and heavy branches can be downloaded on demand — after the first launch.

At the same time, a whole class of failures disappears. Timeouts, network errors, and server-side service unavailability go away: the scenario works offline — in the subway, on a plane, on a weak connection.

Only failures related to data and device memory remain: unsuitable input, a rare format, insufficient resources on older models — we address these with checks and a fallback processing path.

Conversion options: on-device, in the cloud, or hybrid mode

Converting a model to Core ML formally produces a single artifact, but it can execute in three different modes — and these are different architectural decisions, not a toggle in settings.

The mode determines where the weights live, how the model is updated after fine-tuning, and what requirements are placed on device memory and power.

Mode When it fits Limitations
Local execution Personal data must not leave the device, response is needed without a network, audience is on the current lineup Model size is limited by available memory; heavy scenarios heat the case and drain the battery; updates only with an app release
Cloud execution Heavy computation, infrequent expensive operations, model changes needed without releasing a new build Network required, response depends on the connection, data goes to the server
Hybrid mode Frequent lightweight operations on-device, heavy ones on the server More expensive to develop: two computation branches, version synchronization, a unified data preprocessing scheme

Choosing the execution mode starts not with the model, but with product constraints: data sensitivity, acceptable response latency, privacy requirements.

Then we calculate the memory and heat budget for the target device lineup, and only after that decide what to move to the server and what to keep local.

Work stages: audit, design, build, validation, handover

Any optimization starts with measurements, not with rebuilding the model: without numbers for response time, memory, and heat, it is unclear where the bottleneck is — in the computation graph, in the weights, or in how the application accesses the model.

That is why the work proceeds through fixed stages, and each one leaves an artifact that can be checked.

What the work looks like step by step

  1. Audit of the current solution — we analyze how the model was trained and in what form the team provides it, which layers cannot be transferred directly, and where accuracy loss is possible.
  2. Profiling on target devices — we capture response time, memory consumption, and heat on real phones and tablets, not on a test bench.
  3. Transformation design — we define which operations to replace in the graph, where to quantize weights, and what accuracy margin we consider acceptable.
  4. Model conversion to Core ML — we build the package, check operation support, and compare outputs with the original model on a control set of examples.
  5. Device build — we bundle the model with the application, choose the acceleration mode, and check cold start.
  6. Validation on real scenarios — we run rare and noisy input data, observe behavior under memory pressure and when switching between applications.
  7. Acceptance of the result — we compare metrics before and after using a single methodology, hand over configurations, build instructions, and a report, and roll back to the previous build if there is a discrepancy.

Project artifacts: configurations, designs, regulations, documentation

Converting a model to Core ML is only the top layer of the work.

Without a description of how the build is reproduced, the next update turns into correspondence with the contractor, so after handover the client retains the entire outline: build configurations, designs, versioning rules, and the update procedure.

  • Build configurations — how the model and supporting code are built for the required OS version and device bitness, with pinned dependencies: rebuilding produces the same result.
  • Data processing designs — preprocessing, normalization, and postprocessing of outputs, indicating where the model is called in the application.
  • Versioning rules — how weights and configurations are numbered, what counts as an incompatible change, and how the model version is tied to the application version.
  • Update procedure — step-by-step release of a new version: a run on the control set, metric checks, and conditions for rolling back to the previous build.
  • Model documentation — inputs and outputs, size and response time limits, known weaknesses, and acceptance metrics.
  • Control set and tests — fixed input examples with expected results, so divergence can be seen before rollout.
  • Integration design — call points, error handling, and behavior when the model fails in the application.

The kit lives in the client's repository next to the code, so updating the model remains a routine engineering task rather than a separate project with us.

Case study: speeding up an application after optimization

The project is a mobile object recognition service for photos: the user captures a frame and waits for the result. Initially, the model ran on the server side — the frame was sent for processing and returned over the network.

On a test bench, the scheme looked workable, but with live users it hit two bottlenecks: response latency and losses on an unstable connection.

Where exactly losses occurred

Every request went through a full round trip: frame compression, sending, processing queue, and the return path. During peak hours, the queue grew, and response time stretched from 0.8 to 2.5 seconds — enough for the user to close the app.

On a mobile network, some requests were interrupted midway: about 6.5% of operations ended in failure. This was a class of failures that could not be fixed on the server side — the cause was the channel, not the model itself.

What we changed

Model inference was moved to the device itself: the network was converted to Core ML, weights were compressed, compute precision was reduced where it did not affect recognition, and frame preprocessing was moved to a separate thread. The frame no longer leaves the device — both the queue and the network round trip are gone.

Response time decreased by 8-9x, from 0.8 to 0.09 seconds.

The share of failed operations dropped to 0.4%, the model size on the device decreased from 240 to 70 MB, and the load on the server infrastructure fell so much that its capacity was no longer increased as the audience grew.

Recognition quality on the control sample did not drop — this was verified with a separate run before rollout.

What to do if the application already uses a different runtime?

There is no need to rewrite the entire application: the runtime is the model inference layer, not the business logic. Screens, camera handling, and data storage remain the same; only what stands behind the "get prediction" call changes.

The scope of work depends on how much the original model relies on the capabilities of the previous runtime.

Rebuilding a ready model is the baseline scenario. If the architecture is assembled from standard layers, the weights are transferred as is, and the outputs match the previous ones within acceptable divergence.

A transition without rewriting relies on a call adapter: input and output tensors are converted to the previous types, and the rest of the code is left untouched.

Fine-tuning is required when the target runtime does not directly support some operations: they are replaced with equivalent ones and the model is retrained on the same data to restore the original quality.

Retraining and architecture revision are for models tied to server-specific features: dynamic input shapes, non-standard feature transformations.

Option When it fits Limitations
Rebuilding a ready model Standard layers, weights unchanged Rare operators do not pass
Fine-tuning Some operations replaced with equivalent ones Requires a training sample and a validation run
Retraining Architecture is tied to the server environment Takes longer, requires comparing metrics with the current version
Architecture revision The model moves entirely to the device Memory and build size requirements change

The order is the same: first we measure output divergence on the control set, and only if it exceeds the threshold do we proceed to fine-tuning or rebuilding. This way the transition fits into days and does not require rewriting the application.

Limitations, compatibility, and data requirements

Not every computation graph transfers directly to the device: some operators have no hardware implementation, and the converter either replaces them with a chain of supported ones or routes the computation to a slow path.

We build an operation map in advance and show what will remain on the device and what will have to be rewritten.

We check data requirements before starting: annotation format, class balance, missing value rate, and distribution match between training and validation samples.

If the samples were collected in different ways, a drop after Core ML conversion will turn out to be a consequence of the data, not the optimization — this is visible during analysis, not after release.

Which operations are not transferred to the device?

Dynamic tensor shapes, non-standard attention layers, custom operators. They are either expressed through supported ones or left on the server side — we make the decision based on quality and response time measurements.

How is quality validated after optimization?

We prepare a reference set of inputs, record the original model's answers, and compare them with the converted model's answers: divergence on the target metric, share of mismatched answers, and behavior on edge cases. Separately, we run it on real devices from the target fleet.

What about previous-generation devices?

We build a compatibility matrix: on which generations and OS versions the model works, and where the fallback path is enabled. For weak devices, we prepare a lightweight branch in advance so the application does not lose functionality.

Let's discuss the task: what is needed to estimate timelines

The estimate timeline for moving a model to the device is determined not by its size, but by the number of layers that have no built-in mapping in Core ML and by the amount of input data preprocessing.

The earlier we see the architecture and live examples, the less buffer for uncertainty there is in the plan. Below is what is sufficient to send so that we come back with a work plan rather than a broad range.

  • Architecture and weights of the trained model: training stack, graph build script, checkpoint, list of non-standard operations.
  • A set of real input data with expected answers — 100–300 examples are enough to catch divergence in numerical results after conversion.
  • Acceptance metrics: share of correct answers, acceptable divergence from the original model, requirements for response time and battery consumption.
  • On-device operation scenario: fully local inference, loading updated weights, or a hybrid with server calls.
  • Constraints: supported OS versions, device fleet, application size, offline operation.
  • Access: repository, build environment, test device fleet, accounts for publishing.
  • Privacy requirements: which data must not leave the device.

We come back with a work plan: stages, the content of each, acceptance points, and what we consider the result. The first stage is a pilot: we move one model, measure quality and speed on live devices, and record divergences.

After that, it is clear whether the approach scales to other models and at what pace — the timeline estimate becomes concrete.

AI Development Areas

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1393
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1368
  • B2B Advance company logo design
    B2B Advance company logo design
    815
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1139
  • AIDER company logo development
    AIDER company logo development
    1057
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1152