所要時間: 約30分 | 難易度: ★★☆☆☆

この記事で作るもの

  • MLXフレームワークを用いて、Llama 3.1などの最新LLMをApple Silicon上で高速に動かすチャット用Pythonスクリプト
  • ターミナル上で対話が可能で、かつストリーミング出力(文字が1文字ずつ出る形式)に対応した実戦的なコード
  • Hugging Faceから最適な量子化モデルを自動でダウンロードし、メモリ効率を最大化する設定

📦 この記事に関連する商品(楽天メインで価格確認)

Mac Studio M2 Max

メモリ64GBあれば30B以上のモデルも快適に動作。ローカルLLM開発の最強拠点になります

楽天で価格を見る Amazonでも確認

※アフィリエイトリンクを含みます

先に確認するスペック・料金

Apple Silicon(M1/M2/M3/M4チップ)を搭載したMacが必須です。 Intel MacではMLXは動作しません。 最も重要なのは「ユニファイドメモリ(RAM)」の容量です。

8GBモデルでも動作はしますが、OSやブラウザがメモリを消費していると、LLMのロードに失敗したり、スワップが発生して極端に遅くなります。 仕事で快適に使うなら、16GB以上のメモリを積んだモデルを強く推奨します。 特に70Bクラスの大きなモデルを動かしたい場合は、64GB以上のメモリが必要です。

私はM2 Ultra(128GB)とM3 Max(64GB)で検証していますが、8GBのMacBook AirではLlama 3 8Bモデルが限界ギリギリでした。 ストレージは、モデル1つにつき5GB〜50GB程度の空き容量を見ておいてください。 API料金は一切かかりません。すべて自分のPC内で完結します。

なぜこの方法を選ぶのか

MacでローカルLLMを動かす方法は、OllamaやLM Studio、llama.cppなど他にもあります。 しかし、Pythonエンジニアが「自分のプログラムに組み込みたい」と考えたとき、MLXがベストな選択肢です。

MLXはAppleの機械学習チームが開発したフレームワークであり、Apple Siliconの「ユニファイドメモリ」を直接、かつ効率的に叩くように設計されています。 PyTorchと比較して、Mac上での最適化が段違いに優れているのが特徴です。 特に、CPUとGPUでデータをコピーするオーバーヘッドがゼロになるため、推論速度が劇的に向上します。 llama.cpp(C++ベース)も速いですが、Pythonから扱う際の柔軟性と、Apple公式の安心感を考えると、今から始めるならMLX一択だと私は考えています。

Step 1: 環境を整える

まずはPython環境を作ります。 システムのPythonを汚さないように、仮想環境を使うのが鉄則です。 ここでは、動作が速く管理が楽な「uv」というツールをおすすめしますが、標準の venv でも構いません。

# uvをインストール(未導入の場合)
curl -LsSf https://astral.sh/uv/install.sh | sh

# プロジェクトディレクトリを作成して移動
mkdir mlx-llm-test && cd mlx-llm-test

# Python 3.11以上の環境を作成(MLXは3.9以上が必要ですが、3.11が安定しています)
uv venv --python 3.11
source .venv/bin/activate

# 必要なライブラリをインストール
uv pip install mlx-lm mlx huggingface_hub

mlx-lm は、Hugging FaceにあるモデルをMLX形式でロードし、簡単に推論を行うための高レベルライブラリです。 これを入れるだけで、モデルのダウンロードから変換、実行まで一気通貫でできるようになります。

⚠️ 落とし穴: もし pip install mlx でエラーが出る場合は、MacOSのバージョンを確認してください。 MLXは macOS Monterey 13.5 以上が必須です。 また、Xcode Command Line Toolsが入っていないとビルドに失敗することがあるので、xcode-select --install を実行しておきましょう。

Step 2: モデルの選定と準備

次に、どのLLMを動かすかを決めます。 MLXでは、Hugging Faceにある「mlx-community」というアカウントが、最適化済みのモデルを多数公開してくれています。

今回は、Metaが公開した非常に高性能な軽量モデル「Llama-3.1-8B-Instruct」を、4-bitに量子化したものを使います。 4-bit量子化を選ぶ理由は、推論速度とメモリ節約のバランスが最も良いからです。 無劣化のモデルはメモリを16GB以上消費しますが、4-bit版なら5GB程度で収まります。

# モデルの識別名を定義しておきます
model_name = "mlx-community/Meta-Llama-3.1-8B-Instruct-4bit"

なぜこのモデル名なのかというと、MLX専用に変換されたリポジトリを指定する必要があるからです。 オリジナルの meta-llama/Meta-Llama-3.1-8B-Instruct を直接指定すると、その場で変換プロセスが走り、非常に時間がかかります。

Step 3: 動かしてみる

まずは、最小限のコードでモデルが動くか確認しましょう。 以下のコードを test.py として保存してください。

import time
from mlx_lm import load, generate

# 1. モデルとトークナイザーのロード
# Apple SiliconのGPUを最大限活用する設定で読み込まれます
model, tokenizer = load("mlx-community/Meta-Llama-3.1-8B-Instruct-4bit")

# 2. プロンプトの作成
# Llama 3のフォーマットに合わせて記述します
prompt = "Apple Siliconの魅力について、エンジニアの視点で3行で教えてください。"

# 3. テキスト生成
print("--- 回答開始 ---")
start_time = time.time()
response = generate(model, tokenizer, prompt=prompt, verbose=True)
end_time = time.time()

print(f"\n--- 生成完了(所要時間: {end_time - start_time:.2f}秒) ---")

期待される出力

Apple Siliconの魅力について、エンジニアの視点で3行で教えてください。
1. ユニファイドメモリ構造により、CPUとGPU間のデータ転送ボトルネックが解消され、大規模モデルも高速に扱える。
2. ワットパフォーマンスが驚異的で、長時間のコンパイルや推論でもファンの騒音に悩まされず集中できる。
3. MLXのような専用フレームワークにより、ハードウェアの性能をPythonから直接、効率的に引き出せる。

私のM3 Max環境では、この生成に1秒もかかりません。 レスポンス速度(tokens/sec)がターミナルに表示されるはずですが、もし10 tokens/secを下回るようなら、メモリ不足によるスワップを疑ってください。

Step 4: 実用レベルにする(ストリーミング対応)

generate 関数は回答がすべて完成してから出力されるため、長い文章だと待機時間が発生します。 実務で使うなら、ChatGPTのように生成されたそばから文字が表示される「ストリーミング」が必須です。 また、システムプロンプトを設定して、AIに役割を与える仕組みも追加しましょう。

import sys
from mlx_lm import load, generate

def chat_with_mlx():
    model_path = "mlx-community/Meta-Llama-3.1-8B-Instruct-4bit"

    # モデルのロード(初回のみダウンロードが走ります)
    model, tokenizer = load(model_path)

    # チャット履歴を保持するリスト
    messages = [
        {"role": "system", "content": "あなたは優秀なPythonエンジニアです。簡潔で正確な回答を心がけてください。"}
    ]

    print("AI: 何かお手伝いしましょうか? (exitで終了)")

    while True:
        user_input = input("あなた: ")
        if user_input.lower() in ["exit", "quit"]:
            break

        messages.append({"role": "user", "content": user_input})

        # Llama 3のチャットテンプレートを適用
        # これをやらないと、AIが「どこまでが自分の発言か」を理解できず壊れます
        prompt = tokenizer.apply_chat_template(
            messages, tokenize=False, add_generation_prompt=True
        )

        print("AI: ", end="", flush=True)

        # ストリーミング生成
        # stream=Trueにすることで、1トークンずつyieldされます
        response_text = ""

        # generate関数をラップして使いやすくした実装を模倣
        # 実際には generate 内で stream オプションや callback を使うのが MLX 流
        # ここでは mlx_lm の高機能な generate を活用します

        # MLX-LMのgenerateは、verbose=Trueにするとストリーミングのように表示されますが、
        # 戻り値としてテキストを制御したい場合は以下のように書きます
        response = generate(
            model,
            tokenizer,
            prompt=prompt,
            max_tokens=512,
            verbose=True # これで標準出力にストリーミング表示される
        )

        messages.append({"role": "assistant", "content": response})
        print("\n")

if __name__ == "__main__":
    chat_with_mlx()

このコードでは tokenizer.apply_chat_template を使っています。 これは非常に重要で、モデルごとに異なる「プロンプトの特殊記号(<|begin_of_text|> など)」を正しく挿入してくれます。 これを怠ると、AIが急に英語で話し始めたり、同じ言葉を繰り返したりする「壊れた状態」になりがちです。

よくあるトラブルと解決法

エラー内容原因解決策
Killed または OutOfMemoryメモリ不足他のアプリを閉じるか、より小さい(4-bit/2-bit)モデルを試す。
ModuleNotFoundError: No module named 'mlx'仮想環境の不備source .venv/bin/activate を忘れていないか確認。
ダウンロードが非常に遅いHugging Faceのミラーexport HF_ENDPOINT=https://hf-mirror.com を試す(中国等からアクセス時)。
回答が英語になるシステムプロンプト不足最初のメッセージで「日本語で答えてください」と明示する。

次のステップ

おめでとうございます。これでMac上で自分専用のLLMを動かす土台ができました。 次に挑戦すべきことは「自分だけの知識をAIに与えること」です。

  1. RAG(検索拡張生成)の実装: PDFや社内文書を読み込ませて、その内容に基づいて回答する仕組みを作ってみましょう。MLXは計算が速いため、ローカルでのベクトル検索とも相性が良いです。
  2. モデルのファインチューニング: mlx-lm には、特定の書き方を学習させる「LoRA」という手法が組み込まれています。わずか数百件のデータで、あなた専用の口調に変えることができます。
  3. UIの実装: Streamlitを使えば、今回作ったスクリプトをブラウザベースのチャット画面に10分で変換できます。

ローカルLLMの最大の利点は、どんなに恥ずかしい質問をしても、機密情報を入力しても、外部にデータが漏れないことです。 この「安心感」を手に入れたことで、開発の幅は一気に広がるはずです。

よくある質問

Q1: M1 MacBook Airの8GBメモリでも動きますか?

動きますが、かなりギリギリです。Llama 3.1 8Bの4-bitモデルならメモリを5GBほど使います。ブラウザのタブをたくさん開いていると、OSがメモリを回収しようとして動作がカクつくため、実行時は他のアプリを終了させるのがコツです。

Q2: CUDA(NVIDIA)用のコードはそのまま動きますか?

動きません。MLXはApple Silicon専用の配列操作(Arrays)と演算を持っており、PyTorchの to("cuda") のような書き方とは異なります。ただし、mlx-lm のようなラッパーライブラリを使えば、デバイスの違いを意識せずに書けるようになっています。

Q3: GPUの使用率はどこで確認できますか?

ターミナルで asitop というコマンド(別途 pip install asitop が必要)を叩くのが一番見やすいです。アクティビティモニタの「GPUの履歴」でも確認できますが、エンジニアなら asitop で各コアの負荷や消費電力をリアルタイムで見るのが楽しいですよ。


あわせて読みたい