要点
FoudationModelsはiPhoneやMacでオンデバイスLLMモデルを使える便利なフレームワーク。
LiteRT-LMはGemma4などのLLMをGPUで高速に走らせることのできるランタイム。
Foundation ModelsのカスタムバックエンドとしてLiteRT-LMを接続し、Apple公式のモデルの代わりにGemma4などのモデルをFoudation ModelsのAPIでそのまま使えるようにした。
背景:iOS 27 で Foundation Models が「外部バックエンド」対応に
iOS 27 から、FoundationModels は LanguageModel プロトコルに準拠した独自モデルを LanguageModelSession(model:) に差し込めるようになった(init(model: some LanguageModel, ...))。
SDK を確認すると、Apple 純正で LanguageModel に準拠している型は2つ:
-
SystemLanguageModel… オンデバイス(Apple Intelligence のモデル) -
PrivateCloudComputeLanguageModel… Private Cloud Compute(サーバ)
つまり「同じ LanguageModelSession の使い勝手のまま、中身を別のオンデバイスモデルにする」口は開いている。
オンデバイス Gemma の主要ランタイムである LiteRT-LM をそこに差し込めたら嬉しい——これが出発点。
用語:
SystemLanguageModelを使うのが普段の Foundation Models。LanguageModelプロトコルは「自前のモデルを backend として実装する」ための拡張点。
本題:LiteRT-LM を Foundation Models のバックエンドにする
LiteRTLanguageModel は Apple の LanguageModel プロトコルに準拠している。だから消費側のコードは全部 Apple の型で、LiteRT 固有なのは model: 引数だけ。
import FoundationModels
import LiteRTFoundation
let model = try await LiteRTLanguageModel(.gemma4_E2B)
let session = LanguageModelSession(model: model) // Apple そのままの API
// 1) 通常生成(respond / streamResponse)
let a = try await session.respond(to: "Explain on-device AI in one sentence.")
SystemLanguageModel.default を渡す代わりに LiteRTLanguageModel を渡しているだけ。アプリの生成ロジックは書き換え不要で、中身が Gemma 4(LiteRT)になる。
Guided generation:テキストではなく「型付き Swift 値」が返る
@Generable struct StructuredAnswer {
var title: String
var summary: String
var points: [String]
var tags: [String]
}
let s = try await session.respond(generating: StructuredAnswer.self) {
"Give a brief overview of on-device AI."
}
print(s.content.points[0]) // パース不要、そのまま String
肝は「自由文の答えが、検証済みの型付きオブジェクトに構造化される」こと。JSON も正規表現も書かない。
正直な注記:この LiteRT 経路の guided / tool は現状 prompt-driven(schema をプロンプトに入れて JSON を抽出する best-effort)で、Apple 純正の constrained decoding ではない。ハードな
llguidance制約デコードは今後の課題。
Tool calling:モデルが「本物の iOS フレームワーク」を呼ぶ
let session = LanguageModelSession(model: model, tools: [LocationTool()])
let r = try await session.respond(to: "Where is Tokyo, and what time is it there?")
サンプルの Tool はスタブではなく **CoreLocation(CLGeocoder)**を叩く。流れ:
- 1あなたの質問(自由入力)
- 2モデルが場所を抽出して
lookup_location(city:)を呼ぶ - 3あなたの Swift 関数が CLGeocoder で実座標・タイムゾーン・現在時刻を取得
- 4モデルがその実データで回答 → 座標を MapKit の地図にピン表示
「オンデバイスのモデルが、実際の端末 API をオーケストレーションしている」のが一目で分かる。権限も entitlement も不要。
画像・音声・動画の理解
Apple の Transcript には音声/動画の口が無いので、Transcript.CustomSegment フックに載せて通している。
let answer = try await session.respond {
LiteRTAudioSegment(data: wavBytes)
"Answer the question in this audio."
}
Easy mode:出荷の面倒を消す
FM mode とは別に、最短でチャットを動かす LiteRTChat も用意した。
import LiteRTFoundation
let chat = try await LiteRTChat(.gemma4_E2B)
for try await token in chat.stream("Explain quantum computing in one sentence.") {
print(token, terminator: "")
}
正直に言うと、推論ループ自体は LiteRT-LM の Swift API の薄いラッパー。
Easy mode の価値は「その周辺」にある:
- モデル DL:チャンク分割・レジューム・single-flight の堅牢ダウンローダ(HF デュアル CDN の “0% で止まる” 対策込み)
-
catalog = 実機で学んだ設定の固め込み:Gemma 4 E2B は vision/audio を CPU で動かす(GPU だと
STABLEHLO_COMPOSITEで失敗、audio は CPU-only)、タワーは1つずつ立ち上げる(同時だとstd::bad_alloc)、visual token budget、最小 RAM - メモリ安全ゲート(jetsam 前に拒否)、prewarm(初回トークンを速く)
生 API は modelPath(ローカルファイル)を取るだけで DL も上記の知見も持たない。Easy mode は「ファイルを持っていて正しい設定を知っている」前提を外すための接着剤。
サンプルアプリ(マルチモーダルな Swift 実装例)
Samples/LiteRTDemo は clone-and-run の SwiftUI チャット:
- メッセージバブル、ライブストリーム、写真 / マイク音声 / 動画添付
- ヘッダーの「FM API」から 1デモ1画面の Foundation Models デモ(respond / guided / tool calling)
.xcodeproj を同梱したので、clone → open → 実機で Run までそのまま。
cd Samples/LiteRTDemo
open LiteRTDemo.xcodeproj # DEVELOPMENT_TEAM を設定して実機で実行
モデルの配布方法
大型 weights(Gemma 4 E2B は約 2.6GB)なので、既定はランタイム DL:初回起動で Hugging Face から取得し Application Support に永続化、以降は再利用。アプリ本体は小さく、App Store 的にも素直で、モデルだけ後から更新できる。
ファインチューン済み等の自前モデルを差し替えて実験したいときは、カタログも DL も経由せずローカルファイルを直接ロードできる:
// バンドル / devicectl で push / Files で取り込んだ .litertlm を URL 指定で
let chat = try await LiteRTChat(modelFileURL: url, modalities: .all)
let model = try LiteRTLanguageModel(modelFileURL: url) // FM mode でも
実機での結果(iPhone 17 Pro / iOS 27)
- テキスト decode 約 50 tok/s(warm。cold ~44 → 2ターン目以降で安定。Google の model card は 56.5 tok/s)
- 画像(リンゴ画像で "Apple" を正答)、音声(書き起こし・質問応答)も動作
- ピーク footprint ~1.57GB で jetsam に余裕
実装メモ
- ネイティブランタイムは公式リリースの xcframework をバイナリ依存。upstream を SwiftPM の git 依存にすると LFS 由来で
swift package resolveが不安定になるため、小さな Apache-2.0 の Swift ラッパーだけ vendoring している。 - マルチモーダルは
Transcript.CustomSegmentフックで音声/動画を通す(Apple の transcript に口が無いため)。 - FM がツール用に2つ目の executor を作ると多 GB の weights が二重ロードされて OOM するので、設定(モデルパス)ごとにエンジンを共有するキャッシュを入れている。
どんな人に役立つか(正直なスコープ)
- すでに Foundation Models API で書いていて、中身を Apple 非提供のモデル(Gemma 4 / 自前
.litertlm)に差し替えたい人 - オンデバイスのマルチモーダル Swift アプリのリファレンスが欲しい人
-
SystemLanguageModelの可用性(対応端末・地域)に縛られず、自分でモデルを持ちたい人
逆に「とにかく LLM を動かしたいだけ」なら、LiteRT-LM の Swift API を直接使うのでも十分。本パッケージの差分は「FM の使い勝手のまま中身を差し替えられる」「出荷の面倒(DL・落ちない設定・メモリ)を持つ」「マルチモーダルなアプリ実装例ごと出す」ところ。
まとめ
「Apple Intelligence を呼ぶのと同じ LanguageModelSession のまま、中身を Google の Gemma 4(LiteRT)に」できる OSS です。オンデバイス AI を Apple プラットフォームで作っている人、ぜひ触ってフィードバックをください。