ANTIGRAVITY LABEN
記事一覧/AIツール
AIツール/2026-04-22中級

Gemma 4 をモバイルアプリに組み込むときのメモリ予算設計

Gemma 4 をモバイルアプリに組み込む際、モデルサイズだけでなく推論時のピークメモリが本当のボトルネックになります。実測ベースのメモリ予算設計と、端末性能ごとの切り替え戦略をまとめました。

gemma-420mobile2on-device2memory2optimization2

Gemma 4 をモバイルアプリに組み込んだ直後、最初にぶつかる壁はモデルサイズではなくピークメモリでした。「1.8GB の量子化モデルなら載るだろう」と思って組み込んだら、実機で推論を回した瞬間に OS に殺される — この経験をした人は多いのではないでしょうか。

モバイルでの推論は、ファイルサイズとピーク使用量が別物です。モデル本体・KVキャッシュ・アクティベーション・OS予約を合算した瞬間メモリが、その端末の許容範囲に収まって初めて安定稼働します。ここでは私が Gemma 4 をモバイルに載せるときに使っているメモリ予算設計を、実測値とあわせて共有します。

モバイル推論で見るべき4つのメモリ領域

推論中のアプリがメモリを占有する内訳を分解すると、おおよそ次の4つになります。

  • モデル重み: 量子化された重みデータ本体(静的)
  • KV キャッシュ: 過去トークンの Key/Value を保持する領域(コンテキスト長に比例して増加)
  • アクティベーション: 推論中の中間テンソル(層数・隠れ次元で決まる)
  • OS・ランタイム予約: GPU ドライバ・ML フレームワーク・OS 自体

モデル重みだけを見て「載る」と判断すると、残りの3領域で必ず足が出ます。私のデバイスでの実測値を紹介します(Gemma 4 の 4bit 量子化版、コンテキスト 4096 トークン、iOS の MLX 経由)。

  • モデル重み: 1.85 GB
  • KV キャッシュ(4096 tokens): 約 350 MB
  • アクティベーション: 約 220 MB
  • OS/ランタイム予約: 約 180 MB
  • 合計ピーク: 約 2.6 GB

iPhone 14(6GB 物理メモリ)ではアプリに割り当てられるのがおよそ 3 GB 弱なので、ギリギリ動く水準です。iPhone 12(4GB)では厳しく、コンテキストを 2048 に絞るか、さらに小さい量子化版に切り替える必要があります。

端末ティア別の推奨プロファイル

私は内部で端末を3ティアに分類し、それぞれで異なるモデル・設定を提供しています。

Tier 1: ハイエンド(RAM 8GB 以上)— iPhone 15 Pro, Pixel 9 Pro 等

  • モデル: Gemma 4 Instruct 9B 4bit
  • コンテキスト: 8192
  • KV キャッシュ戦略: フル
  • 期待ピーク: 4.5 GB 程度

Tier 2: ミドル(RAM 6GB)— iPhone 14/15, Pixel 8/9 等

  • モデル: Gemma 4 Instruct 2B 4bit
  • コンテキスト: 4096
  • KV キャッシュ戦略: スライディングウィンドウ
  • 期待ピーク: 2.6 GB

Tier 3: エントリー(RAM 4GB)— iPhone 12/13, Pixel 6a 等

  • モデル: Gemma 4 Instruct 2B 4bit
  • コンテキスト: 2048
  • KV キャッシュ戦略: 部分保持 + 再計算
  • 期待ピーク: 1.4 GB

Tier 3 では、パフォーマンスは落ちますが「端末が落ちるより、少し遅くても動く」という判断です。ローンチ時点で端末ティアを見せるのは失礼なので、インストール直後に自動判定して裏で切り替えています。

端末ティアを自動判定するコード

端末 RAM とチップ情報から、どのモデルを落とすかを決めるロジックです。

import Foundation
 
enum DeviceTier {
    case entry, mid, high
 
    static func detect() -> DeviceTier {
        let ramBytes = ProcessInfo.processInfo.physicalMemory
        let ramGB = Double(ramBytes) / 1_073_741_824
        // 論理コア数で補助判定
        let cores = ProcessInfo.processInfo.activeProcessorCount
 
        if ramGB >= 8.0 && cores >= 6 {
            return .high
        } else if ramGB >= 5.5 {
            return .mid
        } else {
            return .entry
        }
    }
 
    var modelName: String {
        switch self {
        case .high:  return "gemma-4-instruct-9b-q4"
        case .mid:   return "gemma-4-instruct-2b-q4"
        case .entry: return "gemma-4-instruct-2b-q4"
        }
    }
 
    var contextLength: Int {
        switch self {
        case .high: return 8192
        case .mid:  return 4096
        case .entry: return 2048
        }
    }
}

physicalMemory は iOS では「物理メモリ総量」を返すので、アプリに割り当て可能なメモリより少し大きい値です。Android では ActivityManager.MemoryInfo.totalMem で同等の情報が取れます。補助的に CPU コア数を見ているのは、iPad mini 6 のような「RAM はあるがチップが古い」端末を Tier を1つ下げて扱うためです。

KV キャッシュのスライディングウィンドウ設計

コンテキスト長を 8192 にしたいけれど、ピークメモリを抑えたい場合、KV キャッシュを「全保持」から「スライディングウィンドウ」に切り替えるのが効きます。

原理はシンプルで、ウィンドウ外のトークンは推論計算に含めない(= KV を捨てる)代わりに、重要情報を先頭に残す方式です。チャットアプリでは「システムプロンプト + 直近2000トークン」のようにします。

class SlidingKVCache:
    """前方 N トークンを必ず残し、残りをウィンドウで動かす。"""
    def __init__(self, max_tokens: int, keep_prefix: int):
        self.max_tokens = max_tokens
        self.keep_prefix = keep_prefix
        self.tokens: list[int] = []
        # KV キャッシュ本体(モデル実装依存)
        self.kv_cache = None
 
    def append(self, token_ids: list[int]):
        self.tokens.extend(token_ids)
        if len(self.tokens) <= self.max_tokens:
            return
        # 上限を超えたら、prefix を除いた中間部分を切り詰める
        overflow = len(self.tokens) - self.max_tokens
        head = self.tokens[:self.keep_prefix]
        tail = self.tokens[self.keep_prefix + overflow:]
        self.tokens = head + tail
        self._rebuild_kv_for_trimmed_context()

この実装のトレードオフは「先頭 prefix 以降の過去情報が忘れられる」ことです。FAQ チャットや短期対話ではほとんど問題になりませんが、長い物語生成やコード編集には向きません。アプリの用途で切り替えられるようにしておくのがよいです。

ロード時とピーク時を分けて監視する

メモリはロード時(モデルをディスクから RAM に乗せる瞬間)と推論時(実行中)で波形が違います。ロード時に一時的に 2 倍使う実装もあるため、ピークを捉えるには両方の監視が必要です。

import os.signpost
 
func trackMemorySignposts() {
    let log = OSLog(subsystem: "app.model", category: "memory")
    let before = MemoryMonitor.currentUsage()
    os_signpost(.begin, log: log, name: "model_load")
    let model = loadModel()
    let afterLoad = MemoryMonitor.currentUsage()
    os_signpost(.end, log: log, name: "model_load",
                "delta=%{public}llu", afterLoad - before)
 
    os_signpost(.begin, log: log, name: "inference")
    let output = model.generate("Hello")
    let peak = MemoryMonitor.peakUsage()
    os_signpost(.end, log: log, name: "inference",
                "peak=%{public}llu", peak)
}

Instruments で signpost を見ると、ロード時のスパイクと推論時のピークが時系列で可視化できます。「本番で端末が落ちる」という報告が来たとき、ロードで落ちているのか推論で落ちているのかが一瞬で分かります。

メモリ低下イベントに対応する

iOS・Android ともに、OS がメモリ警告を出します。これを無視していると、OS はアプリを強制終了します。Gemma 4 のようにメモリフットプリントが大きい場合、警告受信時にはモデルを一時的にアンロードする実装を入れておくと、アプリ自体が強制終了する最悪のケースを避けられます。

NotificationCenter.default.addObserver(
    forName: UIApplication.didReceiveMemoryWarningNotification,
    object: nil, queue: .main
) { _ in
    ModelManager.shared.unloadIfIdleForSeconds(5)
}

ユーザーが5秒以上何もしていない状態であれば、モデルをアンロードしてメモリを返します。次に使うときは再ロードが必要なので数秒の待ちが発生しますが、アプリが落ちるよりはるかにマシです。

よくある勘違い

  • 「量子化を強くすれば全部解決」ではありません。2bit 量子化はファイルサイズは小さいですが、推論中のアクティベーションは大きく変わらず、精度だけが大幅に落ちます
  • 「デバイスの物理 RAM = アプリが使える RAM」ではありません。iOS では実際に使えるのは 50〜60% 程度、Android もバックグラウンドプロセス次第で変動します
  • 「ベンチマークの数字がそのまま出る」ではありません。MLPerf 等の数字はクリーンな状態での値で、実アプリではカメラ・マイク・バックグラウンドタスクと競合します

全体を振り返って

モバイルでの Gemma 4 運用は、ファイルサイズではなく「ピークメモリ」を設計の起点に置くと安定します。今日試せる一歩として、まずあなたのアプリで physicalMemory を取得して端末ティアを分類してみてください。そこに合わせてモデルとコンテキスト長を切り替えるだけで、落ちるアプリから動くアプリに変わります。「全端末で同じ品質」を目指すのではなく「端末ごとに賢く切り替える」のが、モバイル AI の現実解ではないでしょうか。

シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

AIツール2026-06-12
RAG の「それっぽいのに違う」回答を減らす — Gemma 4 × Antigravity で組む検索品質の評価ハーネス
RAG の回答が時々ズレる問題を数値に置き換える評価ハーネスの作り方。30問のゴールデンデータセット、recall@5・MRR・nDCG の実装、Gemma 4 による忠実性審査、pytest の回帰テストまで、ローカル環境の実測値と共に共有します。
AIツール2026-05-10
Gemma 4 を Antigravity で動かすとき、Q4 と Q5 のどちらを選ぶか — M2 Mac で実測した3軸の結果
Gemma 4 を Antigravity でローカル運用するときに迷う Q4_K_M / Q5_K_M / Q8_0 / fp16 の選び方を、M2 Mac 16GB の実機で速度・メモリ・出力品質の3軸で測った結果から解説します。
AIツール2026-04-22
Gemma 4 の複数 LoRA を組み合わせて使う — マージ技法と動的スイッチングで『1モデルで何でもこなす』を本番で実現する
要約・翻訳・コード生成など複数タスクに LoRA を学習させたとき、どう本番で組み合わせるか。重み付きマージ、TIES、動的スイッチングまで、個人開発者が実際に詰まった箇所を Antigravity と一緒にほどいていく実装ノートです。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →