CoreML Model Cache Issues

Hi, is there any way to have caching done better for CoreML models (ANE)?

Right now from my testing, after the first model compile and specialization, the cached model loads fast etc, but it only persists until an app update, even if nothing was changed on the app side. Just changing the build number is enough to invalidate the cache and require the first time compilation and load again.

This seems like such a huge issue to me, and I'm hoping I'm just missing something, and this isn't intended behaviour, especially with the massive degradation in first time model compilation and speialization time for A20 and M6 devices which have a larger neural engine.

Assuming your model is built in your app bundle, I believe this is what happens:

  1. When you build your app with Xcode, Xcode compiles your .mlpackage into .mlmodelc, which contains hardware-agnostic intermediate representations (MIL).

  2. When you load the .mlmodelc the first time, the system specializes the MIL into an execution plan specifically tailored for the device's ANE, and cache it (in Library/Caches). The specialization takes time.

  3. From then on, Core ML then uses a key, which is generated based on the info of the .mlmodelc, to find the cached model. No specialization is needed any more.

  4. When you do an app update, or build and run the app with Xcode, the app bundle's absolute path changes, and the .mlmodelc can change as well because Xcode may re-compile the model.

  5. When you load the .mlmodelc again, Core ML generates a new key and uses it to locate the cached model. This fails because the new key doesn't match the old cached model. The system hence needs to specialize the .mlmodelc again. This is the issue you see.

To avoid the issue, consider the following if your model is relatively small:

  1. In your app's first launch session, copy the .mlmodelc from your app to a folder that remains stable across app updates, the .applicationSupportDirectory folder, for example, and then exclude the bundle from backup by setting the isExcludedFromBackup resource value to true

  2. Do the copy again ONLY when you change the model so the .mlmodelc remains stable across app updates.

  3. Load your model from the stable folder (and not from the app bundle). This avoids triggering the model specialization unnecessarily.

Note that this technically doubles the disk footprint of your model, and hence only fits when your model is small.

If your model is large, consider deploying your model on a server, as described in this article.

The cached model will still be invalidated when:

  • Your app is completely removed and re-installed
  • The cached model, which locates in Library/Caches, is purged.

This probably doesn't happen frequently, but you'd still prepare for that (by offloading the model specialization to background, for example).

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Thanks for the detailed explanation. I don't really think it helps my case, though, because I already follow both recommendations:

  • The models aren't in the app bundle. They're delivered as Apple-hosted Background Assets packs. On first install I copy the compiled models once into Application Support, with isExcludedFromBackup set. I only re-copy when the asset pack itself changes. An app update doesn't touch these files: the path, contents and modification dates all stay the same.
  • I load from that stable location, never from the bundle or the asset-pack delivery folder.
  • I also tried an App Group container as the storage location. The result was the same.

The cache still doesn't survive an update. The models stay byte-identical and stay at the same path, and only the build number changes. After the update, the first load re-specializes again. On iPhone 18 Pro Max that takes about 30 seconds, almost all of it in my largest model. Later loads are fast again until the next update.

The per-app index under Library/Caches/com.apple.e5rt.e5bundlecache appears to reference compiled Neural Engine programs held by the system (aned), not by the app. So I suspect the cached programs are being discarded when the app is installed or updated, rather than the cache key changing.

For now, I do a throwaway warm-up load in the background on the first launch after each update. That hides some of the cost, but on A20 and M6 devices the user still sees a long first load if they start trying to use it right away.