An open API service indexing awesome lists of open source software.

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.

Awesome Lists containing this project

README

          

# mlx-embedding-server

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
![Platform: macOS (Apple Silicon)](https://img.shields.io/badge/platform-macOS%20(Apple%20Silicon)-black)
![Python: 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)

*[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) を参照。