Mac Mini+OllamaでLLMを動かす

AI

Ollamaとは

IT技術者の視点で見ると、Ollamaは、「ローカルLLMの実行環境を標準化・隠蔽するオーケストレーター」と定義できます。

アーキテクチャ

Ollamaの核となる技術スタックは以下の通りです。

REST API サーバー: バックエンドでGo言語ベースのサーバーが常駐し、標準で localhost:11434 をリッスンします。これにより、PythonやNode.js、あるいはcURLから直接LLMを「関数」として呼び出せます。

推論バックエンド (llama.cpp): C++で書かれた超軽量な推論エンジンです。Apple SiliconのAMX (Apple Matrix Tool) 指令セットやGPU(Metal API)を直接叩くことで、ハードウェアの限界性能を引き出します。

モデル・マネジメント: Dockerにおける「Dockerfile」と同じ概念の Modelfile を採用しています。ベースモデルに対し、システムプロンプトや温度パラメータ(Temperature)をパッケージ化し、ollama create で独自のイメージを作成・管理できます。

なぜ「Mac Mini × Ollama」が最強の組み合わせなのか

Mac Mini(特にM2/M3/M4 Proなど)を選ぶ最大の理由は、「メモリ帯域(Memory Bandwidth)」と「ユニファイドメモリ(UMA)」にあります。

A. VRAMの壁を突破するUMA

通常のPC(自作PC + NVIDIA GPU)では、モデルを動かすにはGPU上のVRAMにデータを転送する必要があります。しかし、VRAM 24GBのRTX 4090は非常に高価で消費電力も莫大です。 一方、Mac MiniはCPUとGPUが同じメモリプール(ユニファイドメモリ)を共有します。

  • 64GBメモリのMac Miniなら、50GBを超える巨大なモデル(70BクラスのLlama 3など)をそのままGPUで処理できます。これは自作PCの世界では数十万円の投資が必要な領域です。

B. 量子化(Quantization)の恩恵

Ollamaはデフォルトで GGUF形式 を使用します。これは、重みを 16bit から 4bit や 8bit に圧縮(量子化)する技術です。

  • 4-bit量子化: 精度低下を数%に抑えつつ、メモリ消費量を約1/4に削減します。
  • これにより、Mac Miniの限られたメモリ帯域でも、実用的なトークン生成速度(Tokens Per Second)を維持できます。

Ollamaの3つの使い道

  1. Local Copilotとしての活用: Continue.dev などのIDE拡張機能と組み合わせ、GitHub Copilotの代わりに自前モデルでコード補完を行う(ソースコードを外部に飛ばさないセキュアな開発環境)。
  2. RAG(検索拡張生成)の検証: 自分のドキュメントをベクトル化し、OllamaのAPIを叩いて回答を生成するシステムを、完全オフラインで構築できる。
  3. JSON Modeによる構造化データ抽出: プロンプトに対してJSON形式で返答させることで、非構造化データ(メールやログ)をパースするマイクロサービスとして組み込む。

ローカルLLM環境の比較

特徴OllamaHugging Face TransformersLM Studio
主な用途サーバー常駐・API利用研究・Pythonでの学習UI操作・モデル探索
デプロイ非常に簡単 (CLI)複雑 (Python環境構築が必要)アプリ形式
拡張性API経由で無限コード次第で無限低い
推奨層アプリ開発者・SREデータサイエンティスト一般ユーザー

MLXとは

MLXとは、「Apple Siliconのポテンシャルを100%引き出すために、AppleのAI研究チームがゼロから設計した機械学習フレームワーク」です。Googleにおける「TensorFlow」、Metaにおける「PyTorch」に相当するものを、Appleが自社チップ(M1/M2/M3/M4)専用に作り上げたものだと考えると分かりやすいでしょう。

1. MLXが解決した「MacでのAI開発」の課題

これまでのPyTorchなどは、主にNVIDIAのGPU(CUDA)に最適化されてきました。Macで動かすには「Metal(AppleのグラフィックスAPI)」への変換レイヤーが必要で、オーバーヘッドが生じていました。

MLXは、以下の3つの技術的特徴によってその壁を突破しています。

A. Unified Memory(ユニファイドメモリ)への完全最適化

MLXの最大の特徴は、「データのコピーを発生させない」ことです。 通常、CPUで処理したデータをGPUで計算する場合、メモリ空間の間でデータの転送(コピー)が発生します。MLXはApple Siliconの共有メモリ構造を前提に設計されているため、CPUとGPUが同じメモリ上のデータを「ゼロコピー」で共有します。これが、Mac Miniでの高速な学習と推論を可能にしています。

B. Lazy Evaluation(遅延評価)

MLXは計算式が定義された瞬間に実行するのではなく、「結果が必要になるまで計算を遅らせる」仕組みを持っています。これにより、計算グラフが最適化され、不要な中間処理が省かれます。

C. Composable Function Transformations

JAX(Googleのフレームワーク)に近い設計思想を持っており、自動微分やベクトル化、計算グラフのコンパイルなどを関数的に組み合わせることができます。

2. MLXで「何ができるか」

MLXは以下の役割を担います。

  • Fine-tuning (微調整): mlx-lm というライブラリを使い、LoRA(Low-Rank Adaptation)などの手法で、既存のLLM(Llama 3など)に自社独自のデータや業界用語を学習させることができます。
  • 高速な推論: 学習したモデルをそのままPython上で動かすことができます。
  • マルチモーダル処理: テキストだけでなく、画像生成(Stable Diffusion)や音声認識(Whisper)もMLXで高速化されています。

3. なぜOllamaではなく「MLX」で学習するのか?

Ollamaは「完成した料理を提供するレストラン」であり、MLXは「素材を調理するキッチン」です。

  1. モデルの重みを書き換える機能: Ollamaにはモデルのパラメータを更新する(学習する)機能がありません。
  2. メモリ効率: ファインチューニングには、推論時の数倍のメモリが必要です。MLXはApple Siliconのメモリ管理に特化しているため、Mac Miniの限られたリソースで学習を完結させるための唯一現実的な選択肢となります。

4. MLXの注目点

  • Apple純正の安心感: OSとハードを自社で作っているメーカーが、そのチップのために作ったライブラリであること。
  • Pythonライクな記述: NumPyやPyTorchに非常に近い構文で書けるため、既存のAIエンジニアが学習コストほぼゼロで移行できること。
  • コミュニティの熱量: Hugging Face上には、すでにMLX形式に最適化されたモデルが数多く公開されており、エコシステムが急速に拡大していること。

ローカルLLMモデルの選定

1. Qwen2.5 14B が「本命」である理由

  • ライセンス(Apache 2.0): IT企業として最も安心できるポイントです。Llamaのように「MAU(月間アクティブユーザー数)が増えたらどうしよう」という法務的な懸念を抱えずにスケールできます。
  • 日本語とTool Callingの両立: 日本語が堪能なだけでなく、Tool Calling(関数呼び出し)の精度が高いのは、SaaSのバックエンド(DB検索やAPI連携)として使う際に致命的に重要です。
  • 14Bというサイズ: 4bit量子化すれば約9GB。Mac Miniのメモリ32GB〜64GBモデルなら、KVキャッシュ(会話履歴)を大量に保持しながらでも、超高速にレスポンスを返せるサイズ感です。

2. Qwen2.5 32B の「破壊力」

Mac Miniのメモリを64GB以上に積むなら、32Bは「GPT-4級」の体験をローカルで提供できる強力な選択肢になります。

  • 推論の質: 14Bで「たまに論理が怪しい」と感じる部分が、32Bになると劇的に安定します。
  • トレードオフ: サイズが20GBを超えるため、推論速度(トークン/秒)は14Bより落ちます。UX(チャットの表示速度)を優先するか、回答の精度を優先するかで14Bと使い分ける形になります。

3. Llama シリーズの立ち位置

  • Llama 3.1 8B: 「速さ」だけが武器です。日本語の語彙が少ないため、ファインチューニングで無理やり日本語を覚えさせるよりも、最初から多言語に強いQwenを使う方が開発コストは低いです。
  • Llama 3.3 70B: 「最強の知能」が必要な時のベンチマークです。ただし、Mac Miniで40GBのモデルを動かすのは「動くが、遅い」という状態になりがちです。SaaSのリアルタイム回答には、M4 UltraクラスのMac Studioが必要になる領域です。

Fine tune手順

環境設定

% find .
./adapters
./data
./data/train.jsonl
./data/valid.jsonl

% python -m venv venv
% source venv/bin/activate

% pip install mlx-lm huggingface_hub

学習実行

mlx_lm lora \
  --model mlx-community/Qwen2.5-14B-Instruct-4bit \
  --train \
  --data ./data \
  --iters 200 \
  --adapter-path ./adapters/14b-instruct-iter200

Loading pretrained model
Fetching 10 files: 100%|███████████████████████████████████████████████████████████████████████████████████| 10/10 [09:06<00:00, 54.60s/it]
Download complete: 100%|██████████████████████████████████████████████████████████████████████████████| 8.31G/8.31G [09:06<00:00, 15.2MB/s]
Loading datasets
Training
Trainable parameters: 0.078% (11.469M/14770.034M)
Starting training..., iters: 200
Calculating loss...: 100%|███████████████████████████████████████████████████████████████████████████████████| 1/1 [00:01<00:00,  1.89s/it]
Iter 1: Val loss 5.054, Val took 1.903s
Iter 10: Train loss 3.183, Learning Rate 1.000e-05, It/sec 0.403, Tokens/sec 106.044, Trained Tokens 2632, Peak mem 11.546 GB
Iter 20: Train loss 1.271, Learning Rate 1.000e-05, It/sec 0.414, Tokens/sec 109.108, Trained Tokens 5266, Peak mem 11.674 GB
Iter 30: Train loss 0.889, Learning Rate 1.000e-05, It/sec 0.394, Tokens/sec 103.888, Trained Tokens 7903, Peak mem 11.674 GB
Iter 40: Train loss 0.684, Learning Rate 1.000e-05, It/sec 0.391, Tokens/sec 102.602, Trained Tokens 10525, Peak mem 11.674 GB
Iter 50: Train loss 0.410, Learning Rate 1.000e-05, It/sec 0.392, Tokens/sec 102.355, Trained Tokens 13134, Peak mem 11.674 GB
Iter 60: Train loss 0.245, Learning Rate 1.000e-05, It/sec 0.405, Tokens/sec 106.540, Trained Tokens 15766, Peak mem 11.674 GB
Iter 70: Train loss 0.171, Learning Rate 1.000e-05, It/sec 0.381, Tokens/sec 102.346, Trained Tokens 18455, Peak mem 11.675 GB
Iter 80: Train loss 0.099, Learning Rate 1.000e-05, It/sec 0.398, Tokens/sec 104.073, Trained Tokens 21069, Peak mem 11.675 GB
Iter 90: Train loss 0.088, Learning Rate 1.000e-05, It/sec 0.400, Tokens/sec 105.615, Trained Tokens 23709, Peak mem 11.675 GB
Iter 100: Train loss 0.078, Learning Rate 1.000e-05, It/sec 0.378, Tokens/sec 101.944, Trained Tokens 26403, Peak mem 11.675 GB
Iter 100: Saved adapter weights to adapters/14b-instruct-iter200/adapters.safetensors and adapters/14b-instruct-iter200/0000100_adapters.safetensors.
Iter 110: Train loss 0.076, Learning Rate 1.000e-05, It/sec 0.394, Tokens/sec 101.622, Trained Tokens 28980, Peak mem 11.675 GB
Iter 120: Train loss 0.069, Learning Rate 1.000e-05, It/sec 0.386, Tokens/sec 101.592, Trained Tokens 31612, Peak mem 11.720 GB
Iter 130: Train loss 0.071, Learning Rate 1.000e-05, It/sec 0.396, Tokens/sec 103.905, Trained Tokens 34235, Peak mem 11.720 GB
Iter 140: Train loss 0.067, Learning Rate 1.000e-05, It/sec 0.404, Tokens/sec 105.842, Trained Tokens 36854, Peak mem 11.720 GB
Iter 150: Train loss 0.067, Learning Rate 1.000e-05, It/sec 0.389, Tokens/sec 100.550, Trained Tokens 39439, Peak mem 11.720 GB
Iter 160: Train loss 0.073, Learning Rate 1.000e-05, It/sec 0.389, Tokens/sec 104.151, Trained Tokens 42115, Peak mem 11.720 GB
Iter 170: Train loss 0.069, Learning Rate 1.000e-05, It/sec 0.377, Tokens/sec 102.766, Trained Tokens 44842, Peak mem 11.720 GB
Iter 180: Train loss 0.066, Learning Rate 1.000e-05, It/sec 0.363, Tokens/sec 94.933, Trained Tokens 47454, Peak mem 11.720 GB
Iter 190: Train loss 0.063, Learning Rate 1.000e-05, It/sec 0.284, Tokens/sec 74.227, Trained Tokens 50066, Peak mem 11.720 GB
Calculating loss...: 100%|███████████████████████████████████████████████████████████████████████████████████| 1/1 [00:01<00:00,  1.59s/it]
Iter 200: Val loss 1.035, Val took 1.595s
Iter 200: Train loss 0.064, Learning Rate 1.000e-05, It/sec 0.372, Tokens/sec 100.196, Trained Tokens 52758, Peak mem 11.720 GB
Iter 200: Saved adapter weights to adapters/14b-instruct-iter200/adapters.safetensors and adapters/14b-instruct-iter200/0000200_adapters.safetensors.
Saved final weights to adapters/14b-instruct-iter200/adapters.safetensors.

ログの評価

  • Peak Memory: 11.720 GB 14Bという巨大なモデルを、わずか11GB強のメモリで学習できています。これはMLXの4-bit LoRAがいかに効率的かを示しています。Mac Miniのメモリが16GBモデルだったとしても、十分に余裕を持って完結できる数値です。
  • Tokens/sec: 約100.0 学習速度も秒間100トークン前後と非常に安定しています。Mac Mini M4 Proであれば、このクラスのモデルを「実用的な時間」で調教できることが証明されました。

学習プロセスの分析

  • Lossの推移: Iter 10: 3.183Iter 200: 0.064 Loss(損失率)が劇的に下がっています。 これは、モデルがあなたの提供したデータに極めて強力に適応したことを意味します。
  • Train Loss vs Val Loss:
    • Train Loss: 0.064
    • Val Loss: 1.035 この差は、「学習データはほぼ完璧に暗記したけれど、未知の言い回し(Validation)に対してはそこそこの適応」という状態を示しています。「少し過学習(Overfitting)気味かもしれないが、FAQボットとしては理想的な定型回答ができる状態」と評価できます。
  • Train Loss: 学習データそのものに対する正解率。学習を続ければゼロに近づきますが、これは単なる「暗記」の指標です。
  • Val Loss: 学習に使っていない「未知のデータ」に対する正解率。これがモデルの「汎用性(賢さ)」の真の指標です。
  • 理想の状態: TrainLoss≤ValLoss かつ、両者が緩やかに並走して下がっていく状態。

動作確認

% mlx_lm generate \   
  --model mlx-community/Qwen2.5-14B-Instruct-4bit \
  --adapter-path adapters/14b-instruct-iter200 \
  --prompt "スタッフを追加したい" \         
  --max-tokens 100
Fetching 10 files: 100%|███████████████████████████████████████████████████████████████████████████████| 10/10 [00:00<00:00, 124830.48it/s]
Download complete: : 0.00B [00:00, ?B/s]                                                                            | 0/10 [00:00<?, ?it/s]
==========
スタッフを追加するには、組織・ワークスペース・プロジェクトのルールに従ってください。
==========
Prompt: 34 tokens, 81.304 tokens-per-sec
Generation: 23 tokens, 24.931 tokens-per-sec
Peak memory: 8.692 GB

Ollama用手順

マージ (Fuse)

MLXのベースモデルに、学習したアダプタ(LoRAの重み)を完全に書き込みます。これにより、推論時にアダプタを別途読み込む必要がなくなります。-dequantize で 4bit 表現を解いて FP16 で保存(GGUF
変換のために必要)します。

% mlx_lm.fuse  \
  --model mlx-community/Qwen2.5-14B-Instruct-4bit  \
  --adapter-path ./adapters/14b-instruct-iter200  \
  --save-path ./fused-model  \
  --dequantize

Loading pretrained model
Fetching 10 files: 100%|███████████████████████████████████████████████████████████████████████████████| 10/10 [00:00<00:00, 133152.51it/s]
Download complete: : 0.00B [00:00, ?B/s]                                                                            | 0/10 [00:00<?, ?it/s]
Dequantizing model


% du -sh ./fused-model/                                                              
 28G	./fused-model/
  - Qwen2.5-14B は約 14.8B パラメータ
  - FP16 (2 byte/param) なので 約 28GB が正解                                                                                                   
  - 9GB なら 4bit のまま、56GB なら FP32 など、想定外なら何かおかしい


% ls -1 ./fused-model 
README.md
chat_template.jinja
config.json
model-00001-of-00006.safetensors
model-00002-of-00006.safetensors
model-00003-of-00006.safetensors
model-00004-of-00006.safetensors
model-00005-of-00006.safetensors
model-00006-of-00006.safetensors
model.safetensors.index.json
tokenizer.json
tokenizer_config.json

  - config.json がある → モデルのメタ情報                                                                                                       
  - tokenizer.json / tokenizer_config.json / chat_template.jinja がある → トークナイザ一式                                                      
  - model-00001-of-NNNNN.safetensors 〜 が連番で揃っている → 重みファイル(シャード分割)   
  - model.safetensors.index.json がある → どの重みがどのシャードに入っているかの索引                                                                                                                                                                                                            
  シャードが抜け番号無く揃っていることが重要(例: 1〜6 まで全部ある)。


% grep torch_dtype ./fused-model/config.json                                         
    "torch_dtype": "bfloat16",
  torch_dtype が "bfloat16" (または "float16") になっていれば、GGUF 変換に進める状態です。

GGUF変換

Ollama(内部は llama.cpp)が解釈できる単一ファイル形式 .gguf に変換します。

llama.cppのセットアップ

% git clone https://github.com/ggml-org/llama.cpp.git
  cd llama.cpp
  pip install -r requirements.txt

GGUF変換実行

% python ./llama.cpp/convert_hf_to_gguf.py ./qwen-finetune/fused-model \
  --outfile ./qwen-finetune/qwen-standboard-f16.gguf --outtype f16

 outtype: 出力精度。f32 / f16 / bf16 / q8_0 から選べる


% ls -lh qwen-finetune/qwen-standboard-f16.gguf
28G May  4 21:53 qwen-finetune/qwen-standboard-f16.gguf

量子化 (FP16 → Q4_K_M)

llama-quantizeをビルド

% brew install cmake
% cd llama.cpp
% cmake -B build
% cmake --build build --config Release -j

量子化実行

% ./llama.cpp/build/bin/llama-quantize \
  ./qwen-finetune/qwen-standboard-f16.gguf \
  ./qwen-finetune/qwen-standboard-q4_k_m.gguf \
  Q4_K_M


 量子子化レベルの選択肢                                                                                                                                          
  ┌────────┬─────┬──────────────────┬──────┬────────────────────────────┐                                                                       
  │  名前   │ bit │ サイズ目安 (14B)  │ 品質 │             用途            │
  ├────────┼─────┼──────────────────┼──────┼────────────────────────────┤                                                                       
  │ Q8_0   │ 8   │ 約 15GB          │ ◎    │ 最高品質、推論サーバー向け     │
  ├────────┼─────┼──────────────────┼──────┼────────────────────────────┤
  │ Q6_K   │ 6   │ 約 12GB          │ ◯    │ 高品質バランス               │                                                                       
  ├────────┼─────┼──────────────────┼──────┼────────────────────────────┤                                                                       
  │ Q5_K_M │ 5   │ 約 10GB          │ ◯    │ バランス型                  │                                                                       
  ├────────┼─────┼──────────────────┼──────┼────────────────────────────┤                                                                       
  │ Q4_K_M │ 4   │ 約 9GB           │ △〜◯ │ 標準。Ollama デフォルト      │
  ├────────┼─────┼──────────────────┼──────┼────────────────────────────┤                                                                       
  │ Q3_K_M │ 3   │ 約 7GB           │ △    │ 軽量化優先                  │
  ├────────┼─────┼──────────────────┼──────┼────────────────────────────┤                                                                       
  │ Q2_K   │ 2   │ 約 6GB           │ ×    │ 動かすだけならアリ           │
  └────────┴─────┴──────────────────┴──────┴────────────────────────────┘  


  確認結果

  ┌─────────────────────────────┬────────┬──────┐
  │          ファイル           │ サイズ │ 比率 │
  ├─────────────────────────────┼────────┼──────┤
  │ qwen-standboard-f16.gguf    │ 28GB   │ 100% │
  ├─────────────────────────────┼────────┼──────┤
  │ qwen-standboard-q4_k_m.gguf │ 8.4GB  │ 30%  │
  └─────────────────────────────┴────────┴──────┘

  70% の容量圧縮、Q4_K_M としては想定通り(オリジナル qwen2.5:14b の 9.0GB と同等)。

  ファイル形式の確認

  00000000: 4747 5546 0300 0000  GGUF....

  - マジックバイト: GGUF (0x47 0x47 0x55 0x46) ◯
  - バージョン: 3 (現行の GGUF v3) ◯

  ヘッダが正しい GGUF ファイルなので、Ollama で読める状態です。

Ollama 登録(ollama create)

Modelfile を作成

% cd qwen-finetune
% ollama show --modelfile qwen2.5:14b > Modelfile.qwen-standboard

Modelfile の各項目の意味                                                                                                                        
  ┌───────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────┐                      
  │         命令          │                                             意味                                             │
  ├───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
  │ FROM                  │ ベースとなる GGUF ファイル(またはモデル名)                                                   │
  ├───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤
  │ TEMPLATE              │ プロンプトをモデルに渡す前のフォーマット。Qwen2.5 は ChatML 形式 (<|im_start|> / <|im_end|>) │                      
  ├───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤                      
  │ PARAMETER stop        │ 生成停止トークン。これに当たったら出力を止める                                               │                      
  ├───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤                      
  │ PARAMETER temperature │ 生成のランダム性 (0.0〜1.0)。低いほど決定的                                                  │
  ├───────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────┤                      
  │ SYSTEM                │ デフォルトの system prompt。ユーザーが指定しなければこれが使われる                           │
  └───────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────┘                      

モデルの作成

% ollama create qwen-standboard -f Modelfile.qwen-standboard
writing manifest 
success 

動作テスト

% ollama list
NAME                      ID              SIZE      MODIFIED           
qwen-standboard:latest    c42f338c2b6e    9.0 GB    About a minute ago    
qwen2.5:14b               7cdf5a0187d5    9.0 GB    24 hours ago          


動作テスト                                                                                                                                 
  # 訓練データに含まれる質問
  ollama run qwen-standboard "組織って何?"                                                                                                      
                                                                                                                                                
  # 訓練データに含まれない質問                                                                                                                  
  ollama run qwen-standboard "Pythonでhello worldを書いて"                                                                                      
                                                                                                                                                
  # 自己紹介
  ollama run qwen-standboard "あなたは誰?"                                                                                                      
                                                                                                                                                

ベースモデルとの比較                                                                                                                                                
  # ベースモデル                                                                                                                                
  ollama run qwen2.5:14b-instruct "組織って何?"
                                                                                                                                                
  # fine-tune モデル                                                                                                                            
  ollama run qwen-standboard "組織って何?"

関連記事

カテゴリー

アーカイブ

Lang »