Foundation Models adapter training toolkit を試してみたが

結論から言えば、実行環境がツールキットの必要条件を満たしておらず失敗した。

Foundation Models adapter training – Apple Intelligence – Apple Developer

このページに説明がある通り、adapter training toolkit が必要とするマシンスペックはメモリ32GB。対して筆者私用の MacBook Air (M2) は16GBしか積んでいない、、

Requirements

  • Mac with Apple silicon and at least 32GB memory, or Linux GPU machines
  • Python 3.11 or later

ツールキットのセットアップ自体は、ページの手順に従って難なく完了したのだが、テストコマンド実行で失敗した。

adapter_training_toolkit % python -m examples.generate --prompt "Prompt here"
Traceback (most recent call last):

(省略)

RuntimeError: MPS backend out of memory (MPS allocated: 18.13 GB, other allocations: 384.00 KB, max allowed: 18.13 GB). Tried to allocate 16.00 MB on private pool. Use PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0 to disable upper limit for memory allocations (may cause system failure).

ChatGPT 先生によると、

メモリ 32 GB 推奨という前提に対して物理メモリ 16 GB の Mac で動かしている ことが、今回の MPS out-of-memory エラーの根本原因と考えてかまいません。

要素おおよそのメモリ消費
7 B パラメータ級モデル (FP16)7 B × 2 bytes ≒ 14 GB
ロード直後の重複バッファ(重み再配置や勾配用の一時領域)+2 〜 4 GB
トークン埋め込み/ KV キャッシュ(推論時)数百 MB 〜 数 GB (入力長/バッチで変動)
Python / PyTorch 自身・OS・他プロセス2 〜 4 GB

16 GB machines の場合、Unified Memory 全体が 16 GB = GPU が占有できる上限も 16 GB 未満 です。

PyTorch-MPS の既定 “high-watermark” が 90 % なので、確保できるのは最大でも ≈ 14.4 GB 程度。

上の表の時点でオーバーしており、転送途中でエラーになります。


Mac Studio 買うか、、

Foundation Models は追加学習できるらしい

Foundation Models について、セッションメモや実践についていくつかまとめてきた。

これらを通して筆者は、Foundation Models のコンセプトは Apple の用意したモデルを受動的に使うもので、追加学習する方法は提供されていないものだと理解していた。実際、Foundation Models の API には、外部情報と連携する tool calling の仕組みはあっても、モデルをファインチューニングする手段は提供されていない。

それが、先日参加した WWDC25 Recap イベントにて、近くの方から「できる」という情報をいただいて驚いた。その後、Apple の Technology Evangelist の方にも確認したところ、やはりできるとのこと。以下のページにその手順がまとまっていると教えてくださった。

Foundation Models adapter training – Apple Intelligence – Apple Developer

Adapters are an advanced technique that *adapt* a large language model (LLM) with new skills or domains. With the adapter training toolkit, you can train adapters to specialize the on-device system LLM’s abilities, and then use your adapter with the Foundation Models framework.

トレーニングには Adapter training toolkit なるものを用いるらしい。ローカルマシンと Python コードでトレーニングしたり、別途 Entitlement をリクエストする必要があったり。マシンスペックもメモリ32GB以上とそれなり。事前学習させたモデルをアプリに組み込むので、例えばユーザー生成コンテンツをもとに Foundation Models のレスポンスを生成することはできないだろう。

Foundation Models は OS と共にバージョンアップされ続けるが、聞いた話ではその都度学習がリセットされ、その度に再学習させ直す必要があるとのこと。


ちなみに、このアダプターについてはセッション内に言及あったようで、それをいちからビデオを見直して探すのは大変なので、これまで見たセッションの transcript を溜めていたフォルダから Cursor に探させた。その答えが以下。

「Meet the Foundation Models framework」セッション

  • 該当箇所: 3:47〜3:57付近
  • 内容要約:
  • 「特定のユースケース(例:コンテンツタグ付け)向けに、モデルの能力を最大化するためのspecialized adapters(専用アダプター)を提供している」と明記。
  • さらに「今後もモデルを継続的に改善していく」と述べており、アダプターを使った拡張性やカスタマイズ性に触れています。
  • 原文抜粋:

> For certain common use cases, such as content tagging, we also provide specialized adapters that maximize the model’s capability in specific domains.

ついでに ChatGPT o3 に解説させた。

https://chatgpt.com/share/686d278b-cf78-8005-a273-061117f3d216

Foundation Models でオセロを作ってみたが、、

これの続き。型安全に構造化したデータを出力できる特徴を活かして何かできないか?と選んだテーマがオセロだったが、結果的にはゲームが成立しなかった。そもそもFoundation Modelsで一番最初に作るモノとしては適切でなかったかもしれない笑

紆余曲折経て、最終的に以下のようなデータモデルになった。

@Generable(description: "Represents the current state of an Othello game.")
struct OthelloGame {
    @Generable(description: "Represents the coordinates of a disc. Expressed as 'x:y' in string format (e.g., a:5).")
    struct DiscCoordinate {
        @Generable(description: "The X coordinate of the disc (a...h).")
        enum XCoordinate: String {
            case a, b, c, d, e, f, g, h
        }
        @Generable(description: "The Y coordinate of the disc (1...8).")
        enum YCoordinate: String {
            case `1`, `2`, `3`, `4`, `5`, `6`, `7`, `8`
        }
        
        @Guide(description: "The X position of the disc.")
        var x: XCoordinate
        @Guide(description: "The Y position of the disc.")
        var y: YCoordinate
    }
    
    @Guide(description: "Coordinates occupied by the white player.")
    var whiteDiscs: [DiscCoordinate] = [.init(.d, .`4`), .init(.e, .`5`)]
    
    @Guide(description: "Coordinates occupied by the black player.")
    var blackDiscs: [DiscCoordinate] = [.init(.e, .`4`), .init(.d, .`5`)]
    
    @Guide(description: "A message shown to the opponent based on the current state of the game when you make a move.")
    var message: String
}

うまくいかなかったというのは具体的には、初期状態の盤からゲームを進めるたびに最新の盤を返すよう instruction に指示していたがそれが不完全だったり、あり得ない場所に石を置いたりするといった感じだ。

ちなみにChatGPTだと次のように、間違うことはあれど一定成立するので、広義にLLMがオセロをできない、というわけではない。

たかだか2時間程度の試行錯誤なので、もっと頑張れば精度上げられたかもしれないが、大前提として以下のような向き不向きがあると思いやめた。

現状の Foundation Models 自体がオセロゲームを正確に扱えない

構造化した出力だけでなく、ChatGPT と同様に自然言語的なやりとりでも試みてみたが、初手から出力精度は ChatGPT 4o にはるかに下がる傾向があり、オンデバイスという特徴であったり、Foundation Models 自体の学習内容からして、オセロ自体に向いていないのではと考えている。

構造化させすぎることでコンテクスト上限を容易に超えてしまう

ゲームの向き不向き以外にも、数ターンでコンテクストの上限を迎える問題があった。検証してはいないのであくまで仮説だが、上述のように @Generable を構造化した分、レスポンスに要するトークンは増大すると推測している(データモデルを表す文字列が長くなるため)。もちろんゲームの特徴上、本来は会話のように過去の履歴を残す必要はなく、最後の盤とターンだけを引き継いだセッションを再生成すれば良いだろうが。


こうしたことからなんとなく掴めたことがふたつある。まずは、Foundation Models の得意分野としては、過度な構造化が不要な自然言語を軸としたユースケースで(素直に)活用するのが良いと思っている。また、構造化するしないを置いておいても、省トークンの工夫として型の命名などに気を付ける必要があるかもしれない。

次に、いきなり @Generable といったデータモデルを設計実装する前に、まず自然言語で対話してみて、ユースケースを満たす性能を有しているか、事前検証してみるのが良いと思った。Playgournd でも良いし、チャットアプリ作ってでも良い。Foundation Models が LLM をアプリに組み込みやすくしてくれているおかげで、チャットレベルであれば30分もかからず実装することができる。

Foundation Models framework を触ってみたが、、

以前紹介した Foundation Models framework を触ってみたが、シミューレタ起動すると例外が発生した。

let session = LanguageModelSession()
let response = try await session.respond(to: "What's a good name for a trip to Japan? Reply only with a title")
print("response: \(response)")
Passing along InferenceError::inferenceFailed::Error Domain=com.apple.UnifiedAssetFramework Code=5000 "There are no underlying assets (neither atomic instance nor asset roots) for consistency token for asset set com.apple.modelcatalog" UserInfo={NSLocalizedFailureReason=There are no underlying assets (neither atomic instance nor asset roots) for consistency token for asset set com.apple.modelcatalog} in response to ExecuteRequest

調べてみると、

Your app runs on iOS / iPadOS / visionOS / macOS 26, with Apple Intelligence enabled.

If you use a simulator, be sure that your Mac is on macOS 26, with Apple Intelligence enabled.

Apple Intelligence is available for your system language and region. If not sure, set the system language of your device to English and the region to United States.

Always check the availability when using Apple Foundation Models, as demonstrated in the Apple sample.

https://developer.apple.com/forums/thread/787445

なるほど、macOS は Sequoia 15.5 のままだったので、M2 搭載 Mac かつ Apple Intelligence 有効でも、動作要件を満たせていないのだった。(どこかのセッションで触れてた気がする)

というわけで、iPadOS 26 にアップデートした M1 搭載 iPad でビルドしたら、無事結果を得ることができた。

response: Optional(FoundationModels.LanguageModelSession.Response<Swift.String>(userPrompt: "What\'s a good name for a trip to Japan? Reply only with a title", duration: 5.951339542, content: "\"Samurai Sojourn in the Rising Sun\"", transcriptEntries: ArraySlice([(Response) "Samurai Sojourn in the Rising Sun"])))

Foundation Models のネタとして、何ができるだろうと考えていたのだが、オセロゲームの実装を閃いた。もちろんオセロ自体、ChatGPTと対戦することもできるだろうが、構造化した結果を保証する Foundation Models の強みが活かせそうだと思ったのと、ストリーム方式の結果出力に同期して状態更新を行ってみる、良いサンプルにもなりそうだからだ。

しかしChatGPT相手のオセロってどんな感じなんだろうと、ひとつ試してみたが、出力結果がブレたり、ルールガン無視したり、打てる手を否定してきたりとなかなかカオスだった笑

https://chatgpt.com/share/68594389-f13c-8005-bb90-78b4f628e1ae

これに比べて、Foundation Models framework のモデルがどれくらい精度高い/低いのか気になる。

iOS:マイク入力のオーディオレベルを取得する

AVAudioRecorder をセットアップする。

import AVFAudio

let recorder = try AVAudioRecorder(url: fileURL, settings: [
    // 例
    AVFormatIDKey : kAudioFormatLinearPCM,
    AVSampleRateKey : 44100,
    AVNumberOfChannelsKey : 2,
])
// メータリングを有効
recorder.isMeteringEnabled = true

最新の値を取得する。

// recorderからの最大・平均入力値をリフレッシュ(最新値の取得時に必ず実行)
recorder.updateMeters()

// 0番チャンネルの最大・平均入力それぞれの値を取得(-160〜0のFloat型)
let peak = recorder.peakPower(forChannel: 0)
let average = recorder.averagePower(forChannel: 0)
print("Peak: \(peak), Average: \(average)")

これを繰り返し実行することで、以下のようにリアルタイムな値を取得できる。

 Peak: -38.29653 dB, Average: -49.813396 dB
 Peak: -38.29653 dB, Average: -45.75203 dB
 Peak: -36.77163 dB, Average: -47.29889 dB
 Peak: -36.77163 dB, Average: -46.575954 dB
 Peak: -36.77163 dB, Average: -46.979954 dB
 Peak: -22.248798 dB, Average: -22.248798 dB
 Peak: -17.44955 dB, Average: -19.18833 dB
 Peak: -13.695625 dB, Average: -19.736965 dB
 Peak: -12.488851 dB, Average: -12.488851 dB
 Peak: -12.488851 dB, Average: -12.966704 dB

recorder.isMeteringEnabled を必ず true に指定しておく必要がある点に注意。デフォルトでは false のため、最小値を示す-160しか返ってこない。