🤖

iOSアプリでローカルLLMを動かそう!Swiftライブラリ「LocalLLMClient」

に公開

iOS でローカル LLM を動かすには

最近はモデルサイズが小さい LLM (大規模言語モデル) でも、十分に使えるものが出てきていて、iOS アプリ上で実行するケースがこれから増えてくるように思っています。

LLM 時代より前は、iOS で機械学習モデルの実行を行うには Apple の CoreML もしくは TensorFlow Lite を使うのが一般的でしたが、
現在の LLM では、llama.cpp や Apple の MLX を使うことが多いと思います。

  • llama.cpp: 様々なハードウェア上で LLM 推論を可能にする C/C++ ライブラリ。
  • Apple MLX: Apple シリコン上で効率的かつ柔軟な機械学習研究のために設計された配列フレームワーク。

どちらにも一長一短あり、利用したいモデルに合わせて使い分けたいことが多かったため、iOS アプリエンジニアにとって使いやすいインターフェースとともに、この2つのどちらを利用するかを簡単に切り替えられるライブラリを作ったので紹介します。

https://github.com/tattn/LocalLLMClient

  • 複数バックエンド対応: llama.cpp と Apple MLX を統一されたインターフェイスで利用可能
  • マルチモーダル対応: テキストだけでなく、画像入力も可能
  • iOS/macOS 対応
  • Swift Concurrency によるストリーミング API

特に「MLX の方が動作が早いけど、最新モデルへの対応が遅い場合に llama.cpp も使いたい」という時に便利です。

サンプルアプリ

https://github.com/tattn/LocalLLMClient/tree/main/Example

iOS macOS

ライブラリに Example アプリが含まれているので、実際に動かしながら機能を試せます。

使い方

LocalLLMClient は Swift Package として提供されているので、必要なモジュールを利用します。

  • LocalLLMClient: 共通インターフェース
  • LocalLLMClientLlama: llama.cpp バックエンド
  • LocalLLMClientMLX: Apple MLX バックエンド
  • LocalLLMClientUtility: モデルダウンローダーなどのユーティリティ

モデルのダウンロード

LocalLLMClientUtility を使うと、Hugging Face から簡単にモデルをダウンロードできます。

https://huggingface.co/lmstudio-community/gemma-3-4B-it-qat-GGUF

import LocalLLMClient
import LocalLLMClientUtility

// モデルのダウンロード(例: Gemma 3)
let ggufName = "gemma-3-4B-it-QAT-Q4_0.gguf"
let mmprojName = "mmproj-model-f16.gguf"
let downloader = FileDownloader(source: .huggingFace(
    id: "lmstudio-community/gemma-3-4B-it-qat-GGUF",
    globs: [ggufName, mmprojName]
))
try await downloader.download { print("Download progress: \($0)") }

// ダウンロード先の URL
let modelURL = downloader.destination.appending(component: ggufName)
let mmprojURL = downloader.destination.appending(component: mmprojName)

ダウンロード中の進捗を受け取れるので、プログレスバー表示などが可能です。
BackgroundFileDownloader を使うと、iOS アプリで便利なバックグラウンドダウンロード[1]も可能です。

クライアントの初期化

llama.cpp をバックエンドに使う例です。
llama.cpp がサポートしているオプションや、レスポンス形式 (JSON) を指定できます。

import LocalLLMClientLlama

// クライアントの初期化
let client = try await LocalLLMClient.llama(
    url: modelURL,
    mmprojURL: mmprojURL,
    parameter: .init(
        context: 4096,     // テキストコンテキストサイズ
        temperature: 0.7,  // ランダム性(0.0〜1.0)
        topK: 40,          // トップKサンプリング
        topP: 0.9,         // トップP(nucleus)サンプリング
        options: .init(responseFormat: .json) // レスポンス形式
    )
)

Apple MLX を使う場合は、LocalLLMClientMLX をインポートして、LocalLLMClient.mlx を使います。

テキスト生成

チャット形式で入力できます。画像も添付できます。

let prompt = """
添付画像を参考に、猫が主役の壮大な物語のあらすじの冒頭を考えてください。
形式はJSONで、以下のようにしてください。
{
    "title": "<title>",
    "content": "<content>",
}
"""

let input = LLMInput.chat([
    .system("You are a helpful assistant."),
    .user(prompt, attachments: [.image(uiImage)])
])

// テキスト生成
for try await text in try await client.textStream(from: input) {
    print(text, terminator: "")
}

Swift Concurrency を使ったストリーミングで結果を受け取れるので、UI に途中結果を表示できます。

最後に

Apple プラットフォームでローカル LLM を簡単に動かすためのライブラリ「LocalLLMClient」を紹介しました。

https://github.com/tattn/LocalLLMClient

iOS デバイスであれば、メモリサイズの半分くらいのファイルサイズのモデルまで動作します。
また、iPadOS では com.apple.developer.kernel.increased-memory-limitcom.apple.developer.kernel.extended-virtual-addressing を使うことで、もう少し大きなモデルでも動作するようになるので、ローカル LLM を利用する場合は設定しましょう。

アプリで利用可能な残りのメモリサイズは os_proc_available_memory で取得できるので、モデルを読み込む前にチェックすることをおすすめします。

OpenAI API や Gemini API などを利用すると簡単に高性能な AI を利用できますが、API 利用料が気になる趣味アプリや送信するデータにプライバシーの懸念がある場合などには、ぜひローカル LLM も検討してみてはいかがでしょうか?

脚注
  1. アプリをバックグラウンドに移行してもダウンロードを継続できる機能 ↩︎

Discussion