0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Apple の LLM フレームワークのバックエンドを Google の LiteRT-LM にした

0
Posted at

要点

FoudationModelsはiPhoneやMacでオンデバイスLLMモデルを使える便利なフレームワーク。

LiteRT-LMはGemma4などのLLMをGPUで高速に走らせることのできるランタイム。

Foundation ModelsのカスタムバックエンドとしてLiteRT-LMを接続し、Apple公式のモデルの代わりにGemma4などのモデルをFoudation ModelsのAPIでそのまま使えるようにした。

背景:iOS 27 で Foundation Models が「外部バックエンド」対応に

iOS 27 から、FoundationModelsLanguageModel プロトコルに準拠した独自モデルを 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. 1あなたの質問(自由入力)
  2. 2モデルが場所を抽出して lookup_location(city:) を呼ぶ
  3. 3あなたの Swift 関数が CLGeocoder で実座標・タイムゾーン・現在時刻を取得
  4. 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 プラットフォームで作っている人、ぜひ触ってフィードバックをください。

🔗 https://github.com/john-rocky/swift-litert-lm

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?