iPhone にローカル LLM 載せようとして、 結局 Apple Foundation Models に戻った話
はじめに
こんにちは、kitadesignです。最近個人的にずんだもんと散歩するアプリをプロト開発しています。(環境:iPhone 15:iOS 26)
GPS で取得した現在地周辺の Wikipedia 記事をローカル LLM でずんだもん口調にリライトして、 VOICEVOX で読み上げる、 みたいなことをやってますw
LLM の部分は最初 Apple Foundation Models (以下 AFM) で動いてました。
iOS 26 から正式 API として使える、 純正のオンデバイス LLM ですね。
「動いてた」 のに、 なぜか「もっとローカル LLM 試したい」 という気持ちが湧いてきて、 1 週間ほど色々頑張った結果、 結局 AFM 一本に戻りました。
完全な敗北戦記です。 残念ー。。。🍵
「失敗談こそ価値がある」 という雰囲気でゆるく書いていきます。 「自分も同じこと考えてる」 「やめておけ」 と思ってる人の判断材料になれば嬉しいです!
第 1 章: なんでローカル LLM やりたかったか
AFM が既に動いてるのに、 なんで新しい LLM を試したかったかというと:
-
AFM がずんだ口調にちょっと苦戦してる気がした
- そもそも AFM は汎用なので、 「散歩ガイド × ずんだもん口調」 みたいな
超ニッチ用途には、 自分でモデル選んだ方が hit するかもと思った!
- そもそも AFM は汎用なので、 「散歩ガイド × ずんだもん口調」 みたいな
- そもそも AFM は汎用なので、 「散歩ガイド × ずんだもん口調」 みたいな
-
Qwen とか Llama を iPhone で動かしてみたかった
- ロマンしかないw
-
AFM は session 内で口調が暴走する
- 既知の問題で、 連続 turn で「見て見て!ぼくが紹介するのは...」 を
何度も繰り返す。 session reset で逃げてはいるが他モデルなら違うかも。
- 既知の問題で、 連続 turn で「見て見て!ぼくが紹介するのは...」 を
- 既知の問題で、 連続 turn で「見て見て!ぼくが紹介するのは...」 を
-
iPhone の Apple Neural Engine (ANE) はもっと使われていい
- せっかく専用 AI チップが入ってるんだからw
要するに ロマン 50% + 実用 50% な動機です!
第 2 章: Qwen 0.5B で素朴に始める
最初は「HuggingFace Hub に CoreML 化された Qwen 2.5 0.5B あるじゃん、 これ落として動かせばよくないかな?」 という素朴な発想で始まりました。
結果:
「<|im_end|>」
本文ゼロ🙃
EOS token (
<|im_end|>) で切ってないのを fix、 chat template 適用、
skipSpecialTokens: true等を入れていったら一応文章は出るようになったけど:
パルコは、 コンピュータエンターテインメントの会社で...
ぱるこをゲーム会社と認識する hallucination 多発。 0.5B はやっぱ知識量が
全然足りない。 速度も 5.6-7.1 tok/s で、 Mac POC の 16 tok/s から 2.5x 減速。
→ Stage 1 結論: アーキテクチャは ✅ / 品質は ❌
「次は 3B 試そう」 と気合い入れて次へ。
第 3 章: Qwen 2.5 3B で再リベンジしようとしたら Mac で死亡
「3B model なら品質出るはず」 と HuggingFace Hub を彷徨ったけど、 2026 年 5 月時点では Qwen 2.5 3B-Instruct を CoreML 化した公開モデルは見つからなかった。。。
(後から finnvoorhees さんのを発見するけど、 当時は気づかなかった)。
「じゃあ Pre-spike として Mac で素の PyTorch + transformers で 3B 動かしてみよう」 と MacでPOC を組んでみた結果:
1.1 tok/s。
MacBook Pro M2 で 1.1 tok/s。 これは iPhone で動かしたとしても使えないことが事前にわかっただけでも収穫だが、 「自前で CoreML に変換すれば ANE が効いて速くなるはず」 と進む決断!
第 4 章: 自前 CoreML 変換 pipeline を構築
scripts/ディレクトリを作って、 自前変換 pipeline を組み始めた。
構成:
transformers から model load ↓ ane_rewrite/ で ANE-friendly に書き換え (RMSNorm → LayerNorm 置換、 chunked SDPA、 KV cache 静的化、 RoPE 事前計算) ↓ torch.jit.trace で IR 化 ↓ coremltools.convert で mlpackage 生成 ↓ 4bit palettize で圧縮 ↓ xcrun coremlcompiler compile で .mlmodelc 生成 ↓ HF Hub upload
うぇーいw
20 subagent task を並列実行して骨格を一気に組み上げたw
第 5 章: screening で Gemma が死ぬ
LLMを5つ選定:
- Qwen 2.5 3B / 7B (中国語・日本語強い)
- Llama 3.2 3B (Meta、 多言語)
- Gemma 3 4B (Google)
- Phi-4 mini (Microsoft、 軽量で性能良い)
結果:
| candidate | 本文非空率 |
|---|---|
| Qwen 2.5 3B | 100% ✅ |
| Phi-4 mini | 100% ✅ |
| Llama 3.2 3B | 100% ✅ (ただし【地名】 prefix 漏れ) |
| Gemma 3 4B | 0% ❌ |
| Qwen 2.5 7B | MPS OOM (M2 24GB でも死ぬ) |
Gemma が完全に死んでた。 出力が全部空。 デバッグしたら 全 token が <pad> (id=0) で
埋まってた。
原因: Gemma 系は bf16 学習されていて、 fp16 推論すると attention の logit 値が fp16 の表現範囲 (max ~65504) を超えてオーバーフロー → softmax で <pad> が 最確になる。
Qwen 2.5 7B は OOM ボーダー + Mac convert で peak 30GB+ ということが判明、iPhone15の 8GBじゃ動かんじゃないかw
第 6 章: transformers 5.x の罠
「ようやく変換だ」 と Qwen 2.5 3B で
convertを走らせた。
30-40 分かかると思って coffee 沸かしてたら、 数秒で exit 0。
「あれ、 早すぎ?」 と log を見ると:
[qwen25_3b] ane_rewrite (family=qwen2) ... ← rewrite は成功 [qwen25_3b] trace ... [qwen25_3b] FAILED: IndexError: tuple index out of range at transformers/masking_utils.py:492 in sdpa_mask
事前 warning:
cache_positionis deprecated as an arg, and will be removed in
Transformers v5.6. Please useq_lengthandq_offsetinstead
ane_rewrite の問題じゃなくて、 transformers 自体の trace 互換性 bug という
結論。 ane_rewrite 系の RoPE/KV cache 設計を疑ってかなり調査したけど、 結局
transformers 5.x の移行期に踏み込んだのが悪かった。
第 7 章: HF Hub 公開 model に方針転換
「HuggingFace Hub 検索したら最近は CoreML 公開済モデル増えてるんじゃね?」
と探したら、 finnvoorhees/coreml-*-4bit シリーズが複数 family で揃ってた:
| candidate | 公開 repo | DL 数 |
|---|---|---|
| Qwen 2.5 3B | finnvoorhees/coreml-Qwen2.5-3B-Instruct-4bit |
103 🌟 |
| Llama 3.2 3B | finnvoorhees/coreml-Llama-3.2-3B-Instruct-4bit |
14 |
| SmolLM2 1.7B | finnvoorhees/coreml-SmolLM2-1.7B-Instruct-4bit |
4 |
| SmolLM2 360M | finnvoorhees/coreml-SmolLM2-360M-Instruct-4bit |
11 |
全部同じ統一構成 (mlmodelc + config + tokenizer 一式)。 finnvoorhees さんマヂ神w
Phi-4 mini / Gemma 3 4B は HF Hub に公開なしだったのであきらめ。。。
Xcode build →
BUILD SUCCEEDED→ 実機 (iPhone 15 Pro) に install までいけた。
「今度こそ動くぞ」 とワクワクw
第 8 章: 実機検証で待ち受けていた jetsam death
順次 DL してみた。
SmolLM2 360M (220MB) — 動くが品質不足
DL → 即 load 成功 → rewrite テスト。
prompt をそのまま echo してるだけ🤦♂️ ずんだもん口調に変換されてない。
360M クラスは instruction following 能力が弱すぎて、 プロンプトの指示を
無視して 入力 を そのまま出力 してしまう。
SmolLM2 1.7B (1.0GB) — load 失敗
DL → load 試行 → モデルロード失敗
CoreML の 「model execution plan 構築失敗」 = メモリ不足の典型。
iPhone 15 Pro は 8GB RAM だが、 アプリの利用可能枠は実質 3-4GB 程度。
mlmodelc load 時 peak memory は weights size の 2-3 倍。 1GB mlmodelc は
たぶん 3GB peak で available app mem を超えた。
Llama 3.2 3B (1.9GB) — load 失敗
DL → load 試行 → モデルロード失敗
mlmodelc が大きいほど load fail 確実。
Qwen 2.5 3B (なんと 400MB) — 一見成功、 でも jetsam death
finnvoorhees の Qwen 2.5 3B 4bit は onDiskBytes 設定 1.8GB に対し、 実 DL は 400MB。
4bit palettize の per_grouped_channel + group_size=32 で想定以上に圧縮されてる!!
「400MB なら 8GB RAM でも load 通るはず」 とワクワクしながら active 化。
が、CoreML利用中にクラッシュが発生。。。
つまり Qwen 3B は load は通っても rewrite の inference 中に memory peak が 跳ねて jetsam (iOS の OOM killer) に殺された。 weights 400MB でも、 KV cache +
activations + tokenizer + app 本体で peak が 3-4GB 超えてしまうらしい。
第 9 章: 撤退戦 — AFM 一本に戻す
整理:
| candidate | iPhone 15 Pro での結果 |
|---|---|
| Qwen 2.5 3B (400MB) | ❌ jetsam death |
| Llama 3.2 3B (1.9GB) | ❌ load fail (-14) |
| SmolLM2 1.7B (1.0GB) | ❌ load fail (-14) |
| SmolLM2 360M (220MB) | ⚠️ 動くが品質ダメ (prompt echo) |
iPhone 15 Pro 8GB RAM では sweet spot が存在しない:
- 動くサイズ (~400MB 以下) は instruction following 能力不足で品質出ない
- 品質出るサイズ (3B+) は load fail or jetsam death
そして、 既に
Apple Foundation Modelsがオンデバイスで普通に動いていて、
ずんだ口調 rewrite もまあまあやってくれる。 このままAFM で十分
→ 戦略撤退 決定。 AFM 一本 に戻る結論となった。
学んだこと
1. AFM すごいぞ
Apple Foundation Models は思った以上に優秀。 Indie 開発者がイチからローカル LLM
を CoreML 変換する手間を考えると、 Apple が裏で全部やってくれる AFM の価値は
むちゃくちゃ高い。 iOS 26+ ターゲットなら まず AFM を試して、 不足が
あれば他を検討で十分。
2. iPhone のメモリ制約は厳しい
iPhone 15 Pro = 8GB RAM だが、 app 利用可能枠は実質 3-4GB 程度 (iOS / 他アプリ
が大量に消費)。 4bit 1.7B+ の mlmodelc は load fail、 400MB の Qwen 3B も
推論で OOM。 これは iPhone 17 Pro Max (12GB?) や Pro Max 系統が普及するまで
変わらない。
3. transformers 5.x への trace は要注意
2026 年 5 月時点、 transformers 5.x は内部 API 移行期 (
cache_position→
q_length/
q_offset) で torch.jit.trace 互換性が壊れている経路がある。
古い model code path で trace すると IndexError。 CoreML 変換やる場合は
transformers 4.45 〜 4.55 系に pin するのが無難。
4. Gemma 系は bf16 必須
Gemma 2 / Gemma 3 を transformers で load する時、
torch_dtype=torch.float16
だと推論が完全に死ぬ (全 token
<pad>collapse)。 必ず
torch.bfloat16を
使うこと。 silent fail なので気づきにくい。
5. finnvoorhees さん本当に神
2026 年時点で HuggingFace Hub の
finnvoorhees/coreml-*シリーズが、 個人開発者
が CoreML LLM を試す上での最短経路。 Qwen / Llama / SmolLM2 が 4bit / 8bit で
揃ってる、 統一構成、 一発 DL 可能。 Apple Inc. の人らしいですが感謝🙏
6. 紆余曲折を楽しめる人だけが個人開発に向く
1 週間溶かして結局 AFM に戻る、経験を得たね!楽しかった!
次にやること
Custom Registry 路線は凍結したが、 まだ試したい路線がある:
-
AFM Adapter (LoRA fine-tune): AFM をベースに「ずんだ口調 + 散歩ガイド」
特化の Adapter を学習させる。 Apple 純正の framework で軽量。
おわりに
「ローカル LLM を iPhone で動かす」 という夢を 1 週間追いかけた記録でした。
マヂ残念🍵 でも、 やってみないと「iPhone 8GB の壁」 は実感
できなかったし、 finnvoorhees さんの存在も知れたし、 Gemma の bf16 quirk も
分かったし、 spec / plan を書く筋肉も鍛えられた。
「自分も同じことやろうとしてる」 という方には、 ぜひ次の点を確認してから
始めることを推奨します:
- AFM で本当に不足? → ほとんどのケースで AFM が答え
- Target iPhone 機種は? → 8GB RAM の機種なら 4bit 1B 程度が現実的上限
- 品質 vs サイズの sweet spot 存在する? → 多くの場合 NO
- 1 週間溶かす覚悟ある? → 趣味ならアリ、 仕事ならやめとけ
ちゃんちゃん。 また何か面白いネタが出てきたら書きます。