MacBook Pro M4 / 32GB
0. 全体像
やっていることは「Macの中に小さなAIサーバーを立てる」こと。
[モデルファイル] … 20GBの数値の塊。これが「脳みそ」
↓ 読み込む
[推論エンジン] … MLX。Apple Silicon用の計算エンジン
↓ 包む
[LM Studio] … GUI + モデル管理 + APIサーバー
↓ 公開
localhost:1234 … OpenAI互換エンドポイント
↓ 接続
[VS Code / 自作アプリ]
1. 前提知識
1-1. 量子化(Quantization)
モデルの重み(数値の塊)を粗い目盛りに丸めて圧縮する技術。JPEG圧縮と同じ発想。
| bit数 | 35Bモデルのサイズ | 品質 | 判定 |
|---|---|---|---|
| 16bit(元) | 約70GB | 完璧 | 載らない |
| 8bit | 約37GB | ほぼ完璧 | 載らない |
| 4bit | 約20GB | わずかに劣化 | 採用 |
| 3bit | 約15GB | 明らかに劣化 | 避ける |
| 2bit | 約10GB | 崩壊 | 論外 |
→ 32GBという物理制約に対して、4bitがちょうどいい妥協点。
1-2. MLX と GGUF
量子化ではなくファイル形式の違い。mp4 と mov のような関係。
- MLX … Apple Silicon 専用に最適化。Macならこちら
- GGUF … 汎用。llama.cpp系で広く使われる
1-3. MoE(Mixture of Experts)
35B-A3B の A3B が示すもの。
- 総パラメータ 35B(350億)
- 実際に毎回動くのは 3B(30億)だけ
各層に多数の「専門家」がいて、毎回そのうち数個だけが選ばれて計算に参加する仕組み。
なぜこれが重要か → LLMの速度は演算性能ではなくメモリ帯域で決まる。無印M4は120GB/s。
| 方式 | 毎トークンで読む量 | 実測目安 |
|---|---|---|
| MoE(3B active, 4bit) | 約1.7GB | 30〜40 tok/s |
| dense 27B(4bit) | 約16GB | 5〜6 tok/s |
→ 無印M4ではMoE一択。性能表で最強のdenseモデルを選ぶと実用外になる。
1-4. トークンとコンテキスト長
- トークン … AIが文章を扱う単位。日本語はほぼ1文字=1トークン
- コンテキスト長 … 一度に覚えていられる上限
⚠️ 溢れたときエラーは出ず、黙って古い会話が捨てられる。長い会話でAIが前の指示を忘れ始めたらこれが原因。
1-5. ユニファイドメモリ
Apple Silicon は CPU と GPU が同じメモリを共有する。これが「MacでローカルLLM」が成立する理由。
ただし macOS は暴走防止のため、GPUが確保できる量を全体の約72%に制限している(32GBなら約23GB)。
2. 構築手順
Step 1: LM Studio インストール
brew install --cask lm-studio
なぜLM Studioか … 推論エンジン・モデル管理・GUI・APIサーバーが1つに入っている。MLXバックエンドを使うのでMacで最速。
Step 2: GPUメモリ上限の引き上げ
sudo sysctl iogpu.wired_limit_mb=28672
なぜ必要か … デフォルト約23GBでは、モデル20.4GB + KVキャッシュが入りきらない。
wired の意味 … 「固定された」。通常メモリは不足時にディスクへ退避(スワップ)されるが、モデルの重みは1トークンごとに全域を読むため、退避されると壊滅的に遅くなる。だから退避禁止領域として固定する。
⚠️ 32GB全部を指定してはいけない。OSやブラウザの分が枯渇してMacが固まる。28GBが安全上限。
Step 3: モデルのダウンロード
対象: Qwen3.6-35B-A3B / MLX / 4bit(20.4GB)
入手元は LM Studio 経由が正解(lmstudio.ai/models/qwen/qwen3.6-35b-a3b の "Use Model in LM Studio")。
Hugging Face から直接落とすと同じファイルが手に入るが、プロンプトテンプレートと推奨パラメータが付いてこない。LM Studio版は設定同梱。
このモデルの特徴
- Vision対応(スクショを貼って読ませられる)
- ツール呼び出し対応
- Thinking がデフォルトON → 返答前に推論を走らせるため、喋り出すまで数秒〜十数秒かかる。フリーズではない
Step 4: ロード設定
| 項目 | 値 | 理由 |
|---|---|---|
| Context Length | 32768 | 日本語約3万文字。実用十分 |
| Max Concurrent Predictions | 1 | 並列数だけKVキャッシュ領域が増える。個人利用は1 |
| KV Cache Quantization | OFF | 下記参照 |
| GPU Offload | 最大 | 一部でもCPUに残ると数倍遅くなる |
| Flash Attention | ON | メモリ効率の良いAttention実装 |
⚠️ KV Cache Quantization を OFF にした理由
有効にすると画面に警告が出る:
Context Length setting is ignored when using KV Cache Quantization
つまり 32768 の上限が効かなくなる。モデル上限の262144まで際限なく伸びうる。
| コンテキスト長 | KVキャッシュ消費 |
|---|---|
| 32K | 約3GB |
| 262K | 約25GB |
モデル20.4GB + 上限28GB → 残り7.6GB。長い会話の途中でエラーも出さずにMacが固まる。
上限を制御できないなら、圧縮する意味がない。Experimental タグ付きの機能でもある。
Step 5: サンプリング設定
モデル作者の推奨値をそのまま使う。
| 項目 | 値 |
|---|---|
| Temperature | 0.6 |
| Top K | 20 |
| Top P | 0.95 |
⚠️ 一般論では「コーディングならTemperature 0.2に下げる」が定石だが、このモデルでは危険。Thinkingモードを持つQwen系は、Temperatureを下げすぎると同じ語句を延々繰り返すループに陥りやすいと言われている。思考プロセスがある程度の多様性を必要とするためと考えられる。
LM Studio経由で落とせばこの値が自動で入る。
Step 6: APIサーバー起動
Developer タブ → Start Server
動作確認:
curl http://localhost:1234/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "local-model",
"messages": [{"role": "user", "content": "こんにちは"}]
}'
なぜやるか … 世の中のAIツールの大半はOpenAI API形式に対応している。接続先URLを差し替えるだけでローカルモデルに繋がる。
Step 7: VS Code 接続(Continue)
拡張機能 Continue をインストールし、設定に記述:
name: local-config
version: 1.0.0
models:
- name: Qwen3.6 Local
provider: openai
model: local-model
apiBase: http://localhost:1234/v1
apiKey: dummy
roles:
- chat
- edit
apiKey はローカルなので何でもいいが、空だと弾かれる実装が多いため適当な文字列を入れる。
3. 永続化
3-1. GPUメモリ上限(要注意)
⚠️ 訂正: 会話中に /etc/sysctl.conf への追記を案内したが、近年のmacOSはこのファイルを起動時に読まない。確実な方法は LaunchDaemon。
/Library/LaunchDaemons/com.local.iogpu.plist を作成:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.local.iogpu</string>
<key>ProgramArguments</key>
<array>
<string>/usr/sbin/sysctl</string>
<string>iogpu.wired_limit_mb=28672</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
登録:
sudo chown root:wheel /Library/LaunchDaemons/com.local.iogpu.plist
sudo chmod 644 /Library/LaunchDaemons/com.local.iogpu.plist
sudo launchctl load -w /Library/LaunchDaemons/com.local.iogpu.plist
再起動後の確認:
sysctl iogpu.wired_limit_mb
簡易案: 毎回手で打つのが嫌なだけなら、~/.zshrc にエイリアスを置く手もある。
alias llm-mem='sudo sysctl iogpu.wired_limit_mb=28672'
3-2. LM Studio の設定
ロード設定とサンプリング値は Preset として保存すれば永続化される。用途別に分けておくと良い。
3-3. モデルの自動ロード
LM Studio の Developer タブに「Start server on app launch」「Auto-load model」の設定がある。有効にすればアプリ起動だけで使える状態になる。
4. 運用時のチェックポイント
| 症状 | 原因 | 対処 |
|---|---|---|
| ロードに失敗する | メモリ上限が効いていない |
sysctl iogpu.wired_limit_mb で確認 |
| 極端に遅い(5 tok/s以下) | GPU Offloadが全層でない/スワップ発生 | 設定確認、コンテキスト短縮 |
| 途中でMacが固まる | KVキャッシュ肥大 | コンテキスト長を下げる |
| 前の指示を忘れる | コンテキスト溢れ | 新しいチャットに分ける |
| 同じ語句を繰り返す | Temperatureが低すぎる | 0.6に戻す |
| 喋り出すまで長い | Thinkingモード | 仕様。急ぎなら無効化 |
正常値の目安
- 速度: 30〜40 tok/s
- メモリ使用量: 22〜24GB(アクティビティモニタで確認)
5. 裏で起きていること(推論の流れ)
- トークン化 … 文章を断片に分解し、それぞれ数字(ID)に変換。AIは文字を扱えない
- 層を通過 … 数十層の計算を順に通る。各層で「どの単語がどの単語に注目すべきか」を計算(= Attention)
- MoEルーティング … 各層で待機する専門家のうち数個だけが選ばれて計算に参加
- 次の1トークンを予測 … 出力は「次に来る確率のリスト」。そこから1つ選ぶ
- 1〜4を繰り返す
重要な事実: AIは文章を考えてから書いているのではなく、1トークンずつ予測を積み重ねているだけ。tok/s とはこのループが1秒に何周するかの数値。
Temperatureの正体: 手順4で確率リストから選ぶとき、常に1位を選ぶか、たまに2位3位も選ぶかを決める値。
システムプロンプトの扱い: 会話のたびに毎回先頭へ挿入されている。AIは前回を記憶しているのではなく、毎回すべて読み直している。だから長いほどコンテキストを消費し、わずかに遅くなる。
6. この構成の限界
- コンテキスト32K が最大の制約。大きなリポジトリを丸ごと読ませるのは無理
- 賢さはクラウドの大規模モデルに及ばない。定型的な実装・リファクタ・質問応答は実用的だが、複雑な設計判断や長時間の自律作業では明確に劣る
- バッテリーを激しく消費する。推論中はGPUフル稼働
- 本気で超えたい場合、次の一手はソフトではなく M4 Pro/Max の48GB以上への買い替え(メモリ帯域273GB/s〜)
7. 次に試せること
- 6bit版(約28GB)との品質比較 … ただしコンテキストが取れなくなる
- 軽量モデル(Qwen3.5-9B等)との使い分け
- LM Studio CLI(
lms)でのモデル切り替え自動化 - 自作アプリからの API 呼び出し
32GBのMacでも、量子化とMoEの仕組みを理解した上で設定を詰めれば実用速度でLLMが動く、というのが今回の一番の収穫。次はモデルを差し替えながら、この構成がどこまで持つかを実地で確かめていきたい。