mlx-serveをMac Miniにインストールしてサービス化するメモ

Mac Mini (M4 Pro, 64GB)にremoteからsshでログインして、mlx-serveをインストール、サービス化するメモ

mlx-serveのインストール

brew trust ddalcu/mlx-serve
brew trust --cask ddalcu/mlx-serve/mlx-core
brew tap ddalcu/mlx-serve https://github.com/ddalcu/mlx-serve
brew install mlx-serve
$ mlx-serve --version
[mem] MLX buffer-pool cap 8192 MB (was 124518 MB)
mlx-serve 26.9.2
mlx 0.32.2
mlx-c 56b2d39fc831
nax off (requires M5-class GPU)
ggml 0.20.1 (60eeeb608)
llama.cpp b10472
gguf 3
ds4 unknown

Modelのpull

mlx-serve pull ddalcu/Qwen3.8-27B-MLX-Serve-8bit
mlx-serve pull ddalcu/Qwen3.8-27B-MLX-Serve-4bit

mlx-serveのサービス化

plistファイルの作成

ユーザー名はmakiの想定。

sudo tee /Library/LaunchDaemons/am.ik.mlx-serve.plist > /dev/null << 'EOF'
<?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>am.ik.mlx-serve</string>

    <key>ProgramArguments</key>
    <array>
        <string>/opt/homebrew/bin/mlx-serve</string>
        <string>serve</string>
        <string>--metrics</string>
    </array>

    <key>WorkingDirectory</key>
    <string>/Users/maki</string>

    <key>UserName</key>
    <string>maki</string>

    <key>EnvironmentVariables</key>
    <dict>
        <key>PATH</key>
        <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/maki/.local/bin</string>
        <key>HOME</key>
        <string>/Users/maki</string>
    </dict>

    <key>RunAtLoad</key>
    <true/>

    <key>KeepAlive</key>
    <true/>

    <key>StandardOutPath</key>
    <string>/Users/maki/Library/Logs/mlx-serve.out.log</string>

    <key>StandardErrorPath</key>
    <string>/Users/maki/Library/Logs/mlx-serve.err.log</string>
</dict>
</plist>
EOF

LaunchDaemon は root所有・644 でないと読み込まれない。

sudo chown root:wheel /Library/LaunchDaemons/am.ik.mlx-serve.plist
sudo chmod 644 /Library/LaunchDaemons/am.ik.mlx-serve.plist

サービス読み込み

mkdir -p /Users/maki/Library/Logs
sudo launchctl bootstrap system /Library/LaunchDaemons/am.ik.mlx-serve.plist

プロセス確認

$ sudo launchctl list | grep am.ik.mlx-serve
3577	0	am.ik.mlx-serve

ログ確認

$ tail -f /Users/maki/Library/Logs/mlx-serve.out.log /Users/maki/Library/Logs/mlx-serve.err.log
==> /Users/maki/Library/Logs/mlx-serve.out.log <==

==> /Users/maki/Library/Logs/mlx-serve.err.log <==
  POST /v1/embeddings
  POST /v1/messages (Anthropic)
  POST /v1/responses (OpenAI Responses)
  POST /v1/responses/compact
  GET  /v1/responses/{id}
  DEL  /v1/responses/{id}
  POST /tokenize
  POST /detokenize

RSS: 19M  Mem: 17%  CPU: 2%  GPU: 0%

APIの動作確認

curl -s http://localhost:11234/v1/models          

次のレスポンスが返る

{"object":"list","data":[{"id":"ddalcu/Qwen3.8-27B-MLX-Serve-4bit","object":"model","created":0,"owned_by":"mlx-serve","loaded":false,"state":"unloaded","bytes_resident":0,"bytes_on_disk":18196477654,"context_length":262144,"max_model_len":262144,"capabilities":["chat","tool_use","streaming","json_schema","vision"],"input_modalities":["text","image","video"],"meta":{"architecture":"qwen3_5","engine":"mlx","vocab_size":248320,"hidden_size":5120,"num_layers":64,"quantization":"4-bit","context_length":262144,"model_max_tokens":262144,"is_moe":false,"bytes_on_disk":18196477654}},{"id":"ddalcu/Qwen3.8-27B-MLX-Serve-8bit","object":"model","created":0,"owned_by":"mlx-serve","loaded":false,"state":"unloaded","bytes_resident":0,"bytes_on_disk":31193577575,"context_length":262144,"max_model_len":262144,"capabilities":["chat","tool_use","streaming","json_schema","vision"],"input_modalities":["text","image","video"],"meta":{"architecture":"qwen3_5","engine":"mlx","vocab_size":248320,"hidden_size":5120,"num_layers":64,"quantization":"8-bit","context_length":262144,"model_max_tokens":262144,"is_moe":false,"bytes_on_disk":31193577575}}]}
curl -s http://localhost:11234/v1/chat/completions \
  --json '{
   "model": "ddalcu/Qwen3.8-27B-MLX-Serve-4bit",
   "messages": [
      {"role": "user", "content": "Give me a joke."}
   ]
 }' | jq .

次のレスポンスが返る

{
  "id": "chatcmpl-1789110431231",
  "object": "chat.completion",
  "created": 1789110431,
  "model": "ddalcu/Qwen3.8-27B-MLX-Serve-4bit",
  "system_fingerprint": "mlx-serve",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Why did the scarecrow win an award?\n\nBecause he was outstanding in his field! 🌾"
      },
      "logprobs": null,
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 17,
    "completion_tokens": 21,
    "total_tokens": 38,
    "prompt_tokens_details": {
      "cached_tokens": 0
    }
  },
  "timings": {
    "prompt_n": 17,
    "cached_n": 0,
    "prompt_ms": 383.554,
    "prompt_per_second": 44.322,
    "predicted_n": 21,
    "predicted_ms": 1015.925,
    "predicted_per_second": 20.671,
    "tokenize_ms": 0.485
  }
}

運用系コマンドメモ

操作 コマンド
再起動 sudo launchctl kickstart -k system/am.ik.mlx-serve
停止・アンロード sudo launchctl bootout system/am.ik.mlx-serve
再度読み込み sudo launchctl bootstrap system /Library/LaunchDaemons/am.ik.mlx-serve.plist
状態確認 sudo launchctl list | grep am.ik.mlx-serve
plist編集後の反映 一度 bootout → bootstrap し直す

スリープ防止

sudo pmset -a sleep 0 disksleep 0