Apple SiliconでのMLX——Appleのモデルではなく、自分のモデルが必要なとき
AppleのFoundation Modelsフレームワークが渡してくれるモデルはひとつだけ。システムのモデルであり、封印されていて、無料で、Appleのスケジュールで更新されます。オンデバイスの言語処理の大半では、これが正しい道具であり、その先へ手を伸ばすのは間違いです。とはいえ、自分で選んだモデルでなければならない仕事もあります。特定のオープンウェイトなLLM、バージョンを固定したいとき、自分のデータで訓練したファインチューン、あるいはシステムモデルにはない能力。自分のモデルをオンデバイスで動かす必要が出てきたとき、Foundation Modelsの一段下の層にあるのがMLXです1。
MLXは、Apple Silicon上で機械学習を行うためのAppleの配列フレームワークで、アプリに直接組み込めるSwift向けのAPI(MLX Swift)を備えています2。これは呼び出すシステムフレームワークではなく、モデルの重みとともに自分で同梱して出荷するライブラリです。この違いこそが取引のすべてであり、それを理解することが、一段下の層へ降りるべきか、それともAppleが置いてくれた場所に留まるべきかを判断する手立てになります。
要点
- MLXはApple Silicon向けに作られたNumPyライクな配列フレームワークで、遅延評価、合成可能な関数変換、そしてMetalバックエンドを備えています2。
- スマートフォン上で動くのは、統合メモリモデルがあるからです。 配列はCPUとGPUが共有するひとつのメモリプールに置かれるため、MLXは同じバッファ上で両者にまたがって動き、ホストからデバイスへのコピーという税金がかかりません3。
- オープンウェイトなLLMをオンデバイスで動かすには、
LLMModelFactoryを使い、mlx-community/Llama-3.2-3B-Instruct-4bitのような量子化モデルを指定し、ChatSessionを通じて生成します4。 - ファインチューンにはLoRAアダプターを使います。小さなアダプターを訓練し、
adapters.safetensorsを出荷し、実行時にload(into:)がベースモデルのLinearレイヤーをLoRALinearに差し替えます5。 - 自分のモデルを持つ代償は、アプリのサイズ(重みは大きい)、メモリ圧迫、システム統合の欠如、そしてすべての更新を自分で背負うことです。Foundation Modelsにこうした代償がないのは、Appleがそれを肩代わりしているからです。
MLXとは何か、そしてなぜApple Siliconがそれを可能にするのか
MLXが与えてくれるのは、NumPyのように見える配列と演算、それに機械学習が必要とする変換群です。自動微分、ベクトル化、そして計算グラフを構築し結果を読み出すまで実行を遅らせる遅延評価2。これだけなら、世のフレームワークのいくつにも当てはまる説明です。MLXがポケットの中のデバイスで数十億パラメータのモデルを走らせられるのは、そのメモリモデルゆえです。
デスクトップのGPUでは、データはシステムRAMに置かれ、計算するにはバスを越えてGPUの別個のメモリへコピーし、結果をまたコピーして戻します。そのコピーが税金であり、大きなモデルでは過酷なものになります。Apple Siliconには統合メモリがあります。CPU、GPU、Neural Engineがすべて直接アドレスできるひとつのプールです。MLXはこの事実を中心に据えて作られています3。配列は「CPU上」でも「GPU上」でもなく、メモリの中にあり、どのプロセッサもその場で操作します。コピーなし、バスの税金なし。4ビットに量子化された30億パラメータのモデルは数ギガバイトに収まり、同程度のメモリを積んだ独立GPUマシンでは同じ作業を非現実的にしてしまう往復なしで動きます。Appleが何年も前に下したハードウェアの決断こそが、実物のモデルのオンデバイス推論が成立する理由であり、タイルベースで統合メモリのアーキテクチャはMLXが立つ土台なのです。
LLMをオンデバイスで動かす
「特定のモデルが欲しい」から画面上のテキストまでの道のりは短いものです。MLX SwiftのLLMレイヤーは、Hugging Face Hubから量子化モデルを読み込んで動かします4。
let container = try await LLMModelFactory.shared.loadContainer(
from: HubClient.default,
using: TokenizersLoader(),
configuration: .init(id: "mlx-community/Llama-3.2-3B-Instruct-4bit")
)
let session = ChatSession(container)
let response = try await session.respond(to: "Summarize this in one line: \(text)")
トークン単位で表示するUIには、代わりにストリームを生成し、届いたチャンクを順次描画します4。
let input = try await container.prepare(input: UserInput(prompt: prompt))
let stream = try await container.generate(input: input, parameters: GenerateParameters())
for await event in stream {
if case let .chunk(text) = event { /* append to UI */ }
}
実務上の重みの大半は、ふたつの細部が担っています。第一に、モデルIDの中の4bitは省いてもよい飾りではありません。量子化こそが、モデルをメモリに収め、デバイス上で使える速度で動かすものです。出荷するのは4ビット(あるいはそれ以下)の重みであって、フル精度ではありません。第二に、量子化しても重みは大きいので、アプリに同梱するか(すぐ使えるが、ダウンロードが太る)、初回起動時に取得するか(バイナリは軽いが、待ち時間と扱うべき失敗経路が生じる)を、意識して決める必要があります。Foundation Modelsがこの問いを一度も突きつけてこないのは、モデルがすでにデバイス上にあるからです。MLXでは、重みはあなたの問題になります。
ファインチューン——新しいモデルではなく、LoRAアダプター
自分のモデルを持ち込む理由は、ベースモデルそのものであることはまずありません。それを自分のドメインに教え込むことです。数十億パラメータのモデルをオンデバイスでフルにファインチューンするのは取るべき手ではありません。取るべき手はLoRA(低ランク適応)です。ベースモデルの振る舞いを調整する小さなアダプターの重みを訓練し、ベース本体には手をつけません。アダプターはギガバイトではなく、メガバイト単位です5。
MLX Swiftは、adapter_config.jsonとadapters.safetensorsを収めたディレクトリから訓練済みアダプターを読み込み、すでにコンテナに読み込まれたモデルへ適用します5。
let adapter = try LoRAContainer.from(directory: adapterURL)
await container.update { context in
try? adapter.load(into: context.model) // swaps Linear layers for LoRALinear
}
load(into:)は、モデルの標準的なLinearレイヤーを、アダプターの低ランク差分を織り込むLoRALinearレイヤーへ置き換えるので、推論はあなたのファインチューンを反映するようになります。モデルはコンテナの内側にあるため、アダプターはcontainer.updateを通じて適用し、実行時にアダプターをホットスワップ(一方をunload(from:)し、もう一方をload(into:))して、ひとつのベースモデルに機能ごとに異なる振る舞いを与えられます。このパターンは、Appleがシステムモデル向けにFoundation Modelsカスタムアダプターを通じて提供しているものと重なります。違いは、ここではベースモデルも訓練パイプラインも結果も自分のものであって、中身の見えないモデルを適応させるのではない、という点です。
判断——Foundation Models、MLX、それともクラウド
層は3つあり、選び違えれば、能力を失うか、避けられたはずの大量の作業を抱え込むかのどちらかを払うことになります。
- Foundation Models——システムモデルでそのタスクをこなせるなら、これを選びます。無料で、プライベートで、出荷する重みはゼロ、自分で管理するメモリもゼロ、そしてシステム統合がただで手に入ります。ここを既定とします。Appleがこのために作ったオンデバイスの言語タスク(要約、分類、抽出、書き換え、構造化出力)は、文句なくここに属します。
- MLX——システムが与えてくれないモデルが必要なときに選びます。特定のオープンウェイトなLLM、OS更新で勝手に変わらない固定バージョン、ドメイン向けのファインチューン、あるいはFoundation Modelsの守備範囲の外にあるアーキテクチャ(視覚言語モデルや、テキスト以外のモデル)。アプリのサイズ、メモリ、所有の負担を払い、その代わりに制御を手に入れます。
- クラウド——モデルがどうしても大きくなければならないときに選びます。最先端の推論、長文脈の分析、数十億パラメータのオンデバイスモデルには及ばない、最大級のモデルにしかできないこと。オンデバイスは最先端モデルの代替ではありません。曲線上の別の一点なのです。
正直に読むなら、MLXは特定の理由のために意図して一段下げる選択であって、より良い既定ではありません。自分の機能についてFoundation Modelsに欠けている能力を名指しできないなら、MLXは不要ですし、それを出荷するということは、抱える必要のなかったギガバイト級の重みとメモリ予算を背負うということです。
MLXに手を伸ばすべきでないとき
- システムモデルですでにこなせる。 Foundation Modelsのタスク一覧を読み返してください。あなたのものがそのリストに載っているなら、ここで立ち止まります。
- 重みのコストを払えない。 量子化された小さなモデルでも、依然として大きなアセットです。アプリのサイズや初回起動時のダウンロードがユーザーにとって本物の制約なら、その制約だけで答えが決まることもあります。
- 固定モデルに対してNeural Engineの最も省電力な経路が必要。 変わらない、既知の、出荷済みのモデルには、Core MLとそのコンバーターが、最も切り詰めた電力とレイテンシでNeural Engineを狙います。MLXが輝くのは柔軟性と研究水準の反復であり、Core MLが輝くのは作り込んだ本番モデルです。両者は別の道具であり、「オンデバイスML」はひとつの判断ではありません。
- 保守する気がない。 自分のモデルを持つということは、その更新も、セキュリティも、ドリフトも自分のものになるということです。システムモデルはAppleがあなたの代わりに更新してくれます。モデルを持ち続ける体制がないなら、採用してはいけません。
MLXが報いてくれる技量とは、いつ使うかについての自制です。このフレームワークは本当に目を見張るものです。本物の言語モデルを自分のドメインにファインチューンし、サーバーもトークン単位のコストもなく、まさにこのために作られたメモリアーキテクチャを持つハードウェア上で、完全にデバイス内で動かせる。その能力は、理由を名指しできたときには手を伸ばす価値があります。理由なしに手を伸ばせば、Appleの無料で・保守され・統合されたモデルを、いまや自分が所有する、より重く・保守されないコピーと引き換えにしたことになります。その判断こそが、仕事のすべてなのです。
-
MLXをFoundation Modelsフレームワークとの対比で位置づけると——Foundation ModelsはAppleの固定されたオンデバイスのシステムモデルを公開します(Apple Foundation Models: オンデバイスのLLMフレームワークを参照)。一方、MLXは自分で選びファインチューンするモデルを動かします。両者はオンデバイススタックの異なる層で、異なるニーズに応えます。 ↩
-
Apple Machine Learning Research、MLXおよびMLX Swift。MLXはApple Silicon上で機械学習を行うための配列フレームワークで、NumPyライクなAPI、合成可能な関数変換(自動微分、ベクトル化)、遅延計算、そしてMetalバックエンドを備えます。MLX Swiftは、それをアプリに組み込むためのSwift向けAPIです。 ↩↩↩
-
MLXドキュメント、統合メモリ。MLXの配列は共有メモリに置かれ、演算はCPUまたはGPU上で、別個のメモリプール間でデータを転送することなく実行できます。これがApple Siliconの統合メモリアーキテクチャをオンデバイスのモデル実行にとって効率的にしている性質です。ハードウェアの背景はApple SiliconのTBDRと統合メモリを参照。 ↩↩
-
Apple Machine Learning Research、MLX Swift Examples / MLX Swift LM。
LLMModelFactory.shared.loadContainer(from:using:configuration:)は、Hugging Face Hubから量子化モデル(例:mlx-community/Llama-3.2-3B-Instruct-4bit)を読み込みます。ChatSessionは単発の呼び出し向けにrespond(to:)を提供し、container.generate(input:parameters:)はGenerateParametersとUserInputを介した逐次出力のために.chunk(text)イベントのストリームを生成します。 ↩↩↩ -
Apple Machine Learning Research、MLX Swift LM LoRAアダプターリファレンス。
LoRAContainer.from(directory:)は、adapter_config.jsonとadapters.safetensorsを含むディレクトリからアダプターを読み込みます。container.updateを通じて適用すると、adapter.load(into: context.model)がモデルのLinearレイヤーをLoRALinearレイヤーへ置き換え、unload(from:)が一方を取り除くため、実行時にアダプターをホットスワップできます。Appleのシステムモデル向けの経路はFoundation Modelsカスタムアダプターと比較してください。 ↩↩↩ -
著者によるMLXの実地作業——Apple Silicon上でMLXを介し、固定予算の訓練実験を回す自律的なML研究ループ。アーキテクチャとハイパーパラメータを自律的に変更して検証時のbits-per-byteを最小化し、改善だけを残します。本稿で述べた統合メモリと量子化の挙動は、その実験を反映したものです。 ↩