← Tous les articles

Foundation Models Custom Adapters : le cycle de vie qu'Apple déconseille

Le type SystemLanguageModel.Adapter permet à une app de charger des poids personnalisés entraînés dans le modèle de langage embarqué d’Apple.1 Le framework le prend en charge. Apple livre un toolkit d’entraînement. Il existe un entitlement documenté, un format de package .fmadapter, et une intégration Background Assets pour télécharger le bon adaptateur sur le bon appareil.

La propre documentation d’Apple sur ce même type indique aussi, en citation littérale : « Adapters consume a large amount of storage space and isn’t recommended for most apps. »1 L’article précédent de cette série, Foundation Models Use Cases, couvrait les rails que la plupart des apps devraient emprunter. Cet article est le troisième rail : le cycle de vie opérationnel d’entraînement, de packaging et de déploiement d’un adaptateur personnalisé, et la recommandation explicite d’Apple sur les cas où il ne faut pas s’y engager.

TL;DR

  • Un adaptateur personnalisé est une matrice de poids entraînée par LoRA qui se superpose au modèle de langage système embarqué.2 Le toolkit d’Apple confirme la technique nommément.
  • Chaque adaptateur est lié à une version unique du modèle système. Lorsqu’Apple met à jour le modèle de base, vous réentraînez l’adaptateur.3
  • La recommandation d’Apple, dans ses propres termes : « Use the base system model for most prompt engineering, guided generation, and tools. If you need to specialize the model, train a custom Adapter… Use custom adapters only if you’re comfortable training foundation models in Python. »1
  • Les ressources d’adaptateur sont volumineuses (~160 Mo), distribuées via Background Assets, et protégées par un entitlement (com.apple.developer.foundation-model-adapter) que l’Account Holder d’une adhésion Apple Developer Program doit demander pour le déploiement.3
  • Cette voie a du sens pour un ensemble restreint d’apps : celles qui répliquent un LLM fine-tuné côté serveur en mode embarqué, celles avec des exigences strictes d’adhérence à un style, un format ou une politique, ou celles qui ont documenté un plafond du prompt engineering sur leur tâche cible.2

Ce qu’est réellement un adaptateur

SystemLanguageModel.Adapter est une struct, disponible sur iOS, iPadOS, Mac Catalyst, macOS et visionOS, tous en 26.0+.4 La description d’Apple pour ce type :

« Use the base system model for most prompt engineering, guided generation, and tools. If you need to specialize the model, train a custom Adapter to alter the system model weights and optimize it for your custom task. Use custom adapters only if you’re comfortable training foundation models in Python. »4

Le mécanisme est documenté. Le guide d’entraînement d’adaptateur d’Apple le déclare directement :2

« The system model uses a parameter-efficient fine-tuning (PEFT) approach known as LoRA (Low-Rank Adaptation). In LoRA, the original model weights are frozen, and small trainable weight matrices called ‘adapters’ are embedded through the model’s network. During training, only adapter weights are updated, significantly reducing the number of parameters to train. »

LoRA est une technique publique avec un historique d’articles publiés.5 La contribution d’Apple, c’est le toolkit, le format de package .fmadapter, le runtime embarqué qui charge l’adaptateur, et la plomberie de cycle de vie pour l’expédier via Background Assets.

La surface d’API du type

La struct Adapter expose une surface réduite :4

  • init(fileURL: URL) throws : « Creates an adapter from the file URL. » Utilisé pour les tests locaux dans Xcode.
  • init(name: String) throws : « Creates an adapter downloaded from the background assets framework. » Utilisé en production.
  • func compile() async throws : « Prepares an adapter before being used with a LanguageModelSession. You should call this if your adapter has a draft model. »
  • var creatorDefinedMetadata: [String : Any] : valeurs issues du champ creator-defined des métadonnées.
  • static func removeObsoleteAdapters() throws : supprime les adaptateurs qui ne correspondent plus à un modèle système actuel.
  • static func compatibleAdapterIdentifiers(name: String) -> [String] : récupère les identifiants des asset packs d’adaptateur compatibles.
  • enum AssetError : type d’erreur pour les défaillances liées aux ressources.

SystemLanguageModel dispose d’un initialiseur dédié aux adaptateurs : convenience init(adapter: SystemLanguageModel.Adapter, guardrails: SystemLanguageModel.Guardrails), avec la description « Creates the base version of the model with an adapter. »6

La clé d’entitlement réservée au déploiement est com.apple.developer.foundation-model-adapter : « A Boolean value that indicates whether the app can enable custom adapters for the Foundation Models framework. »6 Vous n’en avez pas besoin pour l’entraînement ni pour les tests locaux dans Xcode ; vous en avez besoin avant de publier sur l’App Store.3

La grille « Quand envisager un adaptateur » d’Apple

La page du toolkit énonce des signaux d’adoption concrets :2

  • « You have a dataset suitable for use with an LLM », ou vous utilisez déjà un LLM fine-tuné côté serveur et vous souhaitez une parité embarquée.
  • « You need the model to become a subject-matter expert. »
  • « You need the model to adhere to a specific style, format, or policy. »
  • « Prompt engineering isn’t achieving the required accuracy or consistency for your task. »
  • « You want lower latency at inference. If your prompt-engineered solutions require lengthy prompts with examples for every call, an adapter specialized for that task offers minimal prompting. »

Le même guide énumère également les coûts qu’on assume :2

  • Un jeu de données composé de paires prompt/réponse qui illustrent la compétence cible.
  • Un processus pour évaluer la qualité de vos adaptateurs.
  • Un processus pour charger vos adaptateurs dans votre app depuis un serveur.

Et la taxe de stockage : « Each adapter will take approximately 160 MB of storage space in your app. Like other big assets, adapters shouldn’t be part of your app’s main bundle because with multiple adapter versions your app will become too big for people to install. »2

La recommandation du framework, reformulée par Apple à deux endroits distincts, est de privilégier par défaut le prompt engineering combiné aux appels d’outils, et de ne se tourner vers les adaptateurs qu’une fois la grille ci-dessus validée.

L’entraînement, à la manière d’Apple

Le pipeline d’entraînement est en Python. Le toolkit d’Apple livre du code d’exemple, les ressources de modèle correspondant à une version spécifique du modèle système, des utilitaires de jeux de données, et l’étape d’export qui produit un package .fmadapter.2

Configuration matérielle requise : « Mac with Apple silicon and at least 32GB memory, or Linux GPU machines. » Python 3.11 ou ultérieur.2

Forme du jeu de données :2

  • Format jsonl avec des champs role ("user" ou "assistant") et content.
  • 100 à 1 000 échantillons pour les tâches basiques.
  • 5 000+ échantillons pour les tâches complexes.
  • Un fichier Schema.md dans le toolkit couvre le schéma complet, y compris les champs pour la generation guidée et les hooks de sûreté IA.

La note d’Apple sur la qualité des données mérite d’être citée : « Focus on quality over quantity. A smaller dataset of clear, consistent, and well-structured samples may be more effective than larger dataset of noisy, low-quality samples. »2

L’entraînement est invoqué depuis le point d’entrée train_adapter du toolkit :

python -m examples.train_adapter \
  --train-data /path/to/train.jsonl \
  --eval-data /path/to/valid.jsonl \
  --epochs 5 \
  --learning-rate 1e-3 \
  --batch-size 4 \
  --checkpoint-dir /path/to/my_checkpoints/

En option, après l’entraînement de l’adaptateur, vous pouvez entraîner un draft model correspondant.2 Un draft model est une version réduite du modèle de base système qui permet le decoding spéculatif, une technique d’accélération d’inférence publiée.7 Le cadrage d’Apple : « If you choose not to train the draft model, speculative decoding will not be available for your adapter’s use case. »2

La contrainte de version unique

Le fait le plus lourd de conséquences opérationnelles à propos des adaptateurs est leur liaison à une version spécifique du modèle système :3

« Each adapter is compatible with a single specific system model version. You must train a new adapter for every new base model version. A runtime error occurs if your app runs on a person’s device without a compatible adapter. »

Le toolkit est versionné en parallèle du modèle. À l’heure où nous écrivons, Apple a publié deux versions bêta du toolkit (Beta 0.1.0 et Beta 0.2.0, toutes deux retirées) et une version finale : 26.0.0.2 La déclaration d’Apple sur la cadence de release : « A new toolkit will be released for every system model update. The system model is shared across iOS, macOS, and visionOS, and system model updates will occur as part of those platforms’ OS updates (though not every OS update will have a model update). »2

L’implication opérationnelle : les équipes d’apps qui livrent des adaptateurs s’abonnent à un cycle de vie de mise à jour de modèle qui suit la cadence d’Apple. Chaque montée de version du modèle de base devient un déclencheur de réentraînement, de réévaluation et de republication.

Packaging sous forme d’asset packs

Le fichier d’adaptateur est suffisamment volumineux pour que l’embarquer dans l’app soit explicitement déconseillé. Apple route la livraison des adaptateurs via Background Assets.3

Le toolkit produit des packages .fmadapter, que le toolkit empaquette aussi sous forme d’asset packs Background Assets. L’outil en ligne de commande ba-package de Xcode 16 ou ultérieur effectue l’empaquetage ; le toolkit l’invoque.3

Choix d’hébergement :3

  • Apple-Hosted, Managed. Apple héberge la ressource ; l’OS gère le cycle de vie du téléchargement.
  • Self-Hosted, Managed. Vous hébergez sur votre serveur ; l’OS gère le cycle de vie du téléchargement.
  • Self-Hosted, Unmanaged. Vous hébergez et gérez vous-même le cycle de vie.

Les clés Info.plist requises diffèrent selon le choix d’hébergement :3 Apple-Hosted Managed nécessite BAHasManagedAssetPacks, BAAppGroupID et BAUsesAppleHosting ; Self-Hosted Managed nécessite les deux premières ; Self-Hosted Unmanaged n’en exige aucune. Chaque voie comporte également une cible d’extension asset-downloader que Xcode génère.

Choisir le bon adaptateur au runtime

Lorsque le fichier BackgroundDownloadHandler.swift de l’extension asset-downloader est généré, Xcode câble un callback shouldDownload(_:). Le corps d’exemple d’Apple pour les ressources d’adaptateur :3

func shouldDownload(_ assetPack: AssetPack) -> Bool {
    if assetPack.id.hasPrefix("mygameshader") {
        return true
    }
    return SystemLanguageModel.Adapter.isCompatible(assetPack)
}

SystemLanguageModel.Adapter.isCompatible(_:) retourne true pour les asset packs dont l’adaptateur correspond au modèle système actuel. Le même callback peut également laisser passer les ressources non-adaptateur dont votre app a besoin.

Chargement et suivi des téléchargements

Une fois la ressource sur l’appareil, le chemin de chargement est :3

SystemLanguageModel.Adapter.removeObsoleteAdapters()

let adapter = try SystemLanguageModel.Adapter(name: "myAdapter")

La construction déclenche un téléchargement si l’appareil n’a pas d’adaptateur compatible en cache. La note d’Apple sur l’UX : « Because adapters can have a large data size they can take some time to download, especially if a person is on Wi-Fi or a cell network. If a person doesn’t have a network connection, they aren’t able to use your adapter right away. »3

La séquence de statut provient de AssetPackManager :3

let assetpackIDList = SystemLanguageModel.Adapter.compatibleAdapterIdentifiers(name: name)
if let assetPackID = assetpackIDList.first {
    let statusUpdates = AssetPackManager.shared.statusUpdates(forAssetPackWithID: assetPackID)
    for await status in statusUpdates {
        switch status {
        case .began(let assetPack): ...
        case .paused(let assetPack): ...
        case .downloading(let assetPack, let progress): ...
        case .finished(let assetPack): ...
        case .failed(let assetPack, let error): ...
        @unknown default: ...
        }
    }
}

Les cinq cas documentés de DownloadStatusUpdate : .began, .paused, .downloading, .finished, .failed.3 La branche @unknown default du framework est obligatoire car Apple peut ajouter de nouveaux cas dans de futures versions du SDK.

Une fois que le statut atteint .finished, l’adaptateur est prêt à être branché à une session :

let adaptedModel = SystemLanguageModel(adapter: adapter)
let session = LanguageModelSession(model: adaptedModel)

Le draft model et sa limite de débit

Si votre adaptateur est livré avec un draft model, l’appel à adapter.compile() le prépare à l’utilisation. La documentation d’Apple souligne ce point comme une étape distincte et coûteuse en calcul :3

« The first time a device downloads a new version of your adapter, a call to compile() fully compiles your draft model and saves it to the device. During subsequent launches of your app, a call to compile() checks for a saved compiled draft model and returns it immediately if it exists. »

Une limite de débit publiée s’applique :3

« Rate limiting protects device resources that are shared between all apps and processes. If the framework determines that a new compilation is necessary, it rate-limits the compilation process on all platforms, excluding macOS, to three draft model compilations per-app, per-day. »

L’exclusion macOS est intéressante ; la limite s’applique sur iOS, iPadOS et visionOS. Apple recommande d’exécuter la compilation à l’intérieur d’une tâche planifiée par Background Tasks, pour que le travail ne bloque pas le lancement de l’app.3

Un piège côté développement : « The full compilation process runs every time you launch your app through Xcode because Xcode assigns your app a new UUID for every launch. If you receive a rate-limiting error while testing your app, stop your app in Xcode and re-launch it to reset the rate counter. »3

Tests et la contrainte du Simulator

Tester un adaptateur exige un appareil physique. Apple est explicite : « Testing adapters requires a physical device and isn’t supported on Simulator. »3

Pour les tests locaux dans Xcode, vous initialisez à partir d’une URL de fichier plutôt que d’un nom :3

let localURL = URL(filePath: "absolute/path/to/my_adapter.fmadapter")
let adapter = try SystemLanguageModel.Adapter(fileURL: localURL)

Le pattern d’Apple : importez le fichier local pour les tests, puis retirez-le du projet avant de publier l’app, puisque les fichiers d’adaptateur sont trop volumineux pour être empaquetés.3

Ce que cette voie vous coûte sur le plan opérationnel

En remettant le cycle de vie bout à bout, une app qui livre un adaptateur personnalisé s’engage sur :

  1. Une infrastructure d’entraînement Python. Un Mac avec Apple silicon et 32 Go de mémoire au minimum, ou une machine Linux GPU.2
  2. Une cadence de réentraînement à l’horloge d’Apple. Chaque mise à jour du modèle système signifie un nouvel adaptateur et une nouvelle version du toolkit.3
  3. Une stack de distribution. Soit des asset packs hébergés par Apple via App Store Connect, soit votre propre serveur faisant tourner une intégration asset-downloader.3
  4. Des adaptateurs par version en stockage. Plusieurs versions du modèle de base déployées dans la nature signifient plusieurs adaptateurs hébergés, l’appareil tirant celui qui correspond.3
  5. Un sas d’entitlement. L’Account Holder de votre adhésion Apple Developer Program doit en faire la demande ; vous ne pouvez pas publier sans approbation.2
  6. La taxe de 160 Mo par version d’adaptateur. Pas dans le bundle de l’app, mais sur l’appareil de l’utilisateur après téléchargement.2
  7. Des tests sur appareil physique. Le Simulator ne fait pas tourner les adaptateurs.3

Voilà le coût opérationnel sous forme dépouillée. Le bénéfice, lorsque les signaux d’adoption sont au vert, c’est : le modèle embarqué devient spécialisé pour votre tâche avec un prompting minimal, une latence plus faible, aucun coût d’API par appel, et une localité totale des données. Pour les apps qui paient déjà pour de l’inférence fine-tunée côté serveur et qui veulent une parité embarquée, voilà le compromis dans sa forme la plus simple.

À retenir

  1. Les adaptateurs sont du LoRA, selon la technique documentée par Apple. Poids de base gelés, petites matrices entraînables tissées dans le réseau du modèle, seuls les poids d’adaptateur sont mis à jour pendant l’entraînement.2
  2. Un adaptateur par version de modèle système, sans exception. Prévoyez du réentraînement aligné sur les versions d’OS.3
  3. Le flux .fmadapter est de bout en bout. Entraînez en Python, packagez avec le toolkit, hébergez en tant qu’asset pack Background Assets, chargez par nom dans votre app, compilez le draft model dans une tâche en arrière-plan.
  4. Apple lui-même recommande à la plupart des apps de ne pas suivre cette voie. Deux pages distinctes de la documentation Apple le déconseillent pour la plupart des cas d’usage.1 Lisez la grille sur la page du toolkit ; la réponse est « non » par défaut.
  5. Testez sur du matériel. Le Simulator ne fait pas tourner les adaptateurs. Construisez le plan de test autour de sessions sur appareil physique et du créneau de compilation du framework Background Tasks.

La série complète Apple Ecosystem : la grille de décision pour la spécialisation intégrée ; le protocole Tool au cœur du framework ; la séparation du workflow agentique entre LLMs d’app et de tooling ; App Intents vs MCP pour la question de routing plus large. Le hub se trouve sur la Apple Ecosystem Series. Pour un contexte plus large iOS-avec-agents-IA, consultez le iOS Agent Development guide.

FAQ

Comment savoir si mon app a vraiment besoin d’un adaptateur personnalisé ?

Lisez la section « When to consider an adapter » d’Apple dans le guide du toolkit.2 Les signaux : un LLM fine-tuné côté serveur existant que vous voulez refléter en embarqué, un plafond documenté du prompt engineering en matière de précision, une exigence stricte d’adhérence à un style, un format ou une politique, ou un objectif de latence que le seul prompt engineering ne tient pas. La plupart des apps ne cochent aucun de ces critères et Apple le dit.

Que verrouille réellement l’entitlement ?

Le déploiement, pas l’entraînement. La documentation d’Apple le déclare explicitement : « You don’t need this entitlement to train or locally test adapters. »3 L’Account Holder de votre adhésion Apple Developer Program demande com.apple.developer.foundation-model-adapter depuis la page Foundation Models Framework Adapter Entitlement, et l’entitlement ouvre la voie pour publier sur l’App Store.2

Quelle est la taille des adaptateurs et où vivent-ils ?

Le chiffre documenté par Apple : « Each adapter will take approximately 160 MB of storage space in your app. »2 Les adaptateurs ne vivent pas dans le bundle de l’app. Apple les route via Background Assets, hébergés soit sur les serveurs d’Apple (managed), soit sur votre propre serveur (managed ou unmanaged), et l’appareil télécharge la version qui correspond à son modèle système actuel.3

Que se passe-t-il quand Apple met à jour le modèle de base ?

Vous réentraînez. Extrait des docs : « Each adapter is compatible with a single specific system model version. You must train a new adapter for every new base model version. A runtime error occurs if your app runs on a person’s device without a compatible adapter. »3 En pratique, votre stack de distribution héberge plusieurs versions d’adaptateur en parallèle, et l’appareil tire la bonne en s’appuyant sur isCompatible(_:).

Quelle est la relation entre les adaptateurs et .contentTagging ?

.contentTagging est l’une des deux propriétés statiques SystemLanguageModel.UseCase qu’Apple livre, couvertes dans l’article compagnon. C’est une spécialisation gérée par Apple, sans entitlement requis, incluse dans le framework. Un Adapter personnalisé est l’équivalent géré par le développeur pour des tâches que les use cases d’Apple ne couvrent pas. La doc d’Apple emploie le mot « adapted » pour décrire le fonctionnement interne de .contentTagging, mais ce n’est pas la même chose que le type formel Adapter. Le type formel est celui que cet article couvre.

Suis-je obligé d’entraîner un draft model ?

Non. Extrait des docs d’Apple : « If you choose not to train the draft model, speculative decoding will not be available for your adapter’s use case. »2 L’adaptateur se charge et sert toujours ; vous n’obtenez simplement pas le gain d’inférence offert par le decoding spéculatif. Le draft model est une seconde passe d’entraînement optionnelle.

References


  1. Apple Developer, « SystemLanguageModel.Adapter ». Description du type, recommandation contre l’usage pour la plupart des apps, « Use custom adapters only if you’re comfortable training foundation models in Python. » Consulté le 2026-05-04. 

  2. Apple Developer, « Get started with Foundation Models adapter training ». Vue d’ensemble du toolkit, mécanisme LoRA, configuration matérielle requise, forme du jeu de données, CLI d’entraînement, option draft model, table de versions du toolkit, processus de demande d’entitlement. Consulté le 2026-05-04. 

  3. Apple Developer, « Loading and using a custom adapter with Foundation Models ». Hébergement d’asset packs, clés Info.plist, exemple shouldDownload(_:), séquence de statut, limitation de débit, exclusion du Simulator, contrainte de compatibilité version unique. Consulté le 2026-05-04. 

  4. Apple Developer, « SystemLanguageModel.Adapter ». Surface d’API de la struct : init(fileURL:), init(name:), compile(), creatorDefinedMetadata, removeObsoleteAdapters(), compatibleAdapterIdentifiers(name:), AssetError. Consulté le 2026-05-04. 

  5. Hu et al., « LoRA: Low-Rank Adaptation of Large Language Models », arXiv:2106.09685. L’article original sur LoRA auquel renvoie la technique utilisée par Apple. 

  6. Apple Developer, « SystemLanguageModel ». L’initialiseur de convenance init(adapter:guardrails:) et la description de l’entitlement com.apple.developer.foundation-model-adapter. Consulté le 2026-05-04. 

  7. Leviathan et al., « Fast Inference from Transformers via Speculative Decoding », arXiv:2211.17192, et Chen et al., « Accelerating Large Language Model Decoding with Speculative Sampling », arXiv:2302.01318. La technique de decoding spéculatif que le draft model d’Apple utilise, citée directement par le guide d’entraînement d’adaptateur. 

Articles connexes

Foundation Models Use Cases: General vs Content Tagging

iOS 26 Foundation Models has .general and .contentTagging use cases. Use Apple's rules to decide when prompting beats sp…

9 min de lecture

Foundation Models On-Device LLM: The Tool Protocol

iOS 26's Foundation Models framework puts a 3B-parameter LLM on every Apple Intelligence device. The Tool protocol is th…

15 min de lecture

The Cleanup Layer Is the Real AI Agent Market

Charlie Labs pivoted from building agents to cleaning up after them. The AI agent market is moving from generation to pr…

15 min de lecture