https://github.com/kiarina/mlx-embedding-server
Single-purpose HTTP server for multimodal embeddings via mlx-embeddings (Apple Silicon / MLX). Defaults to Qwen3-VL-Embedding-2B.
https://github.com/kiarina/mlx-embedding-server
apple-silicon embeddings fastapi mlx mlx-embeddings multimodal qwen3-vl
Last synced: 11 days ago
JSON representation
Single-purpose HTTP server for multimodal embeddings via mlx-embeddings (Apple Silicon / MLX). Defaults to Qwen3-VL-Embedding-2B.
- Host: GitHub
- URL: https://github.com/kiarina/mlx-embedding-server
- Owner: kiarina
- License: mit
- Created: 2026-05-29T18:39:27.000Z (27 days ago)
- Default Branch: main
- Last Pushed: 2026-06-05T09:17:46.000Z (20 days ago)
- Last Synced: 2026-06-14T14:03:18.889Z (11 days ago)
- Topics: apple-silicon, embeddings, fastapi, mlx, mlx-embeddings, multimodal, qwen3-vl
- Language: Python
- Size: 237 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.ja.md
- License: LICENSE
Awesome Lists containing this project
README
# mlx-embedding-server
[](LICENSE)
-black)

*[English](README.md) | 日本語*
[`mlx-embeddings`](https://github.com/Blaizzy/mlx-embeddings) をラップし、Apple
Silicon 上で**複数の埋め込みモデル**(テキスト / マルチモーダル)を提供する HTTP
サーバーです。
単一エンドポイント `POST /v1/embedding` が 1 プロセスで複数モデルを提供し、`model`
フィールドで使用モデルを選びます。モデルはオンデマンドでロードされ、**サーバー全体の
メモリ予算**内で常駐します。新しいモデルが収まらない場合は LRU で退避されます。MLX は
スレッドアフィニティを持つため、プロセス内の単一ワーカーが埋め込みを 1 件ずつ実行し、
同時リクエストはキューで待機します。
兄弟プロジェクト [mlx-vlm-server](https://github.com/kiarina/mlx-vlm-server)(chat
completions)と同じ「メモリ予算付き・シングルフライト・モデル別ハンドラ」設計を再利用
しています。
## モデル(デフォルトレジストリ)
| `model`(名前 / エイリアス) | リポジトリ | モダリティ | 次元 |
|---|---|---|---|
| `qwen3-embedding-8b`(`text`, `qwen3-embedding`) | `mlx-community/Qwen3-Embedding-8B-mxfp8` | text | 4096 |
| `qwen3-vl-embedding-2b`(`vl`, `qwen3-vl-embedding`) | `mlx-community/Qwen3-VL-Embedding-2B-mxfp8` | text, image | 2048 |
`GET /v1/models` で現在のレジストリを確認できます。カタログは
[`registry.py`](mlx_embedding_server/registry.py) にあります。
## 必要環境
- **Apple Silicon(M1 以降)の macOS。** MLX は Apple Silicon GPU でのみ動作します。
- **Python 3.11+**(`.python-version` で 3.12 を固定)。
- 依存管理に [uv](https://docs.astral.sh/uv/)。
- 初回起動時にモデルをダウンロードするため Hugging Face へのネットワークアクセス。
## セットアップと起動
```sh
uv sync
uv run mlx-embedding-server
# または
uv run python -m mlx_embedding_server
```
起動時、`MLX_EMBEDDING_WARMUP_MODELS`(デフォルト: **全**登録モデル)に含まれる各モデル
をロードし、小さなウォームアップ埋め込みで MLX カーネルを温めます。`GET /health` で
`warm` と常駐モデルを確認できます。
## API
### `POST /v1/embedding`
1 アイテムの埋め込みを計算します。最低 1 つのモダリティ入力が必要です。OpenAI の
`/v1/embeddings` とは異なり、配列 `input` ではなく**モダリティごとに 1 フィールドを持つ
単一アイテム**を受け取ります(将来 audio/video へ拡張しやすい形)。
```jsonc
{
"model": "qwen3-vl-embedding-2b", // 名前 / エイリアス / リポジトリ。省略でデフォルト
"text": "猫の写真", // 任意
"image": "" // 任意
}
```
レスポンス:
```jsonc
{
"model": "qwen3-vl-embedding-2b",
"embedding": [0.01, -0.02, ...],
"dimension": 2048,
"usage": {"prompt_tokens": 0, "total_tokens": 0},
"timings": {"total_s": 0.12}
}
```
選択したモデルが対応しないモダリティは **400** を返します(例: `qwen3-embedding-8b` に
`image` を送る → 画像は `qwen3-vl-embedding-2b` を使用)。
### 例
```sh
# テキスト埋め込み(デフォルトモデル)
curl -s http://127.0.0.1:8900/v1/embedding -H 'Content-Type: application/json' \
-d '{"text": "今日はいい天気ですね"}' | jq '.dimension'
# VL モデルで画像埋め込み(sample.png 同梱)
IMAGE_B64=$(base64 -i sample.png)
curl -s http://127.0.0.1:8900/v1/embedding -H 'Content-Type: application/json' \
-d "{\"model\": \"vl\", \"image\": \"$IMAGE_B64\"}" | jq '.dimension'
# テキスト + 画像
curl -s http://127.0.0.1:8900/v1/embedding -H 'Content-Type: application/json' \
-d "{\"model\": \"vl\", \"text\": \"猫\", \"image\": \"$IMAGE_B64\"}" | jq '.dimension'
```
### その他のエンドポイント
- `GET /v1/models` — 登録モデル一覧(capabilities 付き)。
- `GET /health` — `{status, warm, queue_len, default_model, memory:{loaded, resident_gb, budget_gb}}`。
- `GET /help` — LLM エージェント向けの機械可読な使用ガイド。
## 設定(環境変数)
| 変数 | デフォルト | 意味 |
|---|---|---|
| `MLX_EMBEDDING_MEMORY_LIMIT_GB` | `32` | 常駐モデルのサーバー全体メモリ予算 |
| `MLX_EMBEDDING_DEFAULT_MODEL` | `qwen3-embedding-8b` | `model` 省略時に使うモデル |
| `MLX_EMBEDDING_HOST` / `_PORT` | `127.0.0.1` / `8900` | バインドアドレス |
| `MLX_EMBEDDING_AUTH_TOKEN` | (未設定) | 設定すると `/v1/*` に `Authorization: Bearer ` を要求 |
| `MLX_EMBEDDING_WARMUP` | `1` | 起動時ウォームアップ(`0` で無効) |
| `MLX_EMBEDDING_WARMUP_MODELS` | (全モデル) | 起動時にロード+ウォームするモデル名(カンマ区切り) |
| `MLX_EMBEDDING_MAX_LENGTH` | `512` | トークナイザに渡す入力の最大トークン長 |
## 設計
- **モデルごとのハンドラが自分の処理フローを持つ。** 各モデルは
[`models/`](mlx_embedding_server/models) 配下のモジュールで、`CAPABILITIES` /
`load()` / `run()` と任意の `on_load()` を公開します。共通処理(入力解析・レスポンス
整形)はただのヘルパ([`media.py`](mlx_embedding_server/media.py),
[`response.py`](mlx_embedding_server/response.py))。モデル追加 = モジュール 1 つ +
レジストリ 1 行。
- **メモリ予算付き常駐キャッシュ** ([`manager.py`](mlx_embedding_server/manager.py))。
使用前に
`Σ(常駐weight, target除く) + target.weight + target.peak_headroom ≤ memory_limit_gb`
を満たすよう LRU で退避します。退避時は参照を破棄し `gc.collect()` →
`mx.clear_cache()`(Metal バッファキャッシュが残るため必須)。`weight_gb` は初回
ロード後に実測値で補正されます。
- **シングルフライト・ワーカー** ([`worker.py`](mlx_embedding_server/worker.py)) — 単一
の MLX スレッドがロード・退避・全埋め込みを実行し、予算計算をレースフリーにします。
- Qwen3-VL ハンドラは、変換済み processor に欠けている `chat_template` / token-id を
`on_load` で補填します(既知の Qwen3-VL の癖)。
## サービスとして実行(launchd / mise 経由)
```sh
mise run service:install # ~/Library/LaunchAgents/io.github.kiarina.mlx-embedding-server.plist を作成
mise run service:start
mise run service:status
mise run service:stop
mise run service:uninstall
```
`mise run logs:out` / `logs:err` でサービスログを追跡できます。
## ライセンス
MIT — [LICENSE](LICENSE) を参照。