- Python 99.2%
- Dockerfile 0.8%
|
|
||
|---|---|---|
| tests | ||
| .dockerignore | ||
| .gitignore | ||
| .woodpecker.yml | ||
| app.py | ||
| benchmark.py | ||
| Dockerfile | ||
| export_model.py | ||
| LICENSE | ||
| README.md | ||
| requirements.txt | ||
saas-embedding
OpenAI-совместимый сервис эмбеддингов, оптимизированный для слабых CPU (Intel N5105, Allwinner H6) с качественной поддержкой русского языка.
Требуется Python 3.10+.
Возможности
- REST API, совместимый с OpenAI
/v1/embeddings - Инференс на CPU через ONNX Runtime (без PyTorch в рантайме)
- INT8-квантизация для ускорения в 30–50%
- Поддержка батчевой обработки (массив текстов)
- Настройка через CLI или переменные окружения
- Работает с любой HuggingFace-моделью, экспортируемой в ONNX
Быстрый старт
1. Установка зависимостей
pip install -r requirements.txt
2. Экспорт модели в ONNX INT8
# ВНИМАНИЕ: optimum[onnxruntime] несовместим с transformers >= 5.0.
# Для экспорта создайте отдельное окружение:
pip install 'transformers<4.54.0' 'optimum[onnxruntime]' onnxruntime optimum
python export_model.py deepvk/USER-base --output-dir ./model
Или скачайте предварительно квантованную модель с HuggingFace и разместите в ./model.
3. Запуск сервиса
python app.py --model-dir ./model --host 0.0.0.0 --port 8000
Или через переменные окружения:
MODEL_DIR=./model HOST=0.0.0.0 PORT=8000 python app.py
4. Использование с OpenAI SDK
from openai import OpenAI
client = OpenAI(
api_key="any-key",
base_url="http://localhost:8000/v1"
)
response = client.embeddings.create(
model="default",
input="Текст для эмбеддинга"
)
vector = response.data[0].embedding
Параметры запуска
| Параметр | CLI-флаг | Переменная | По умолчанию | Описание | |
|---|---|---|---|---|---|
| Директория модели | --model-dir |
MODEL_DIR |
./model |
Путь к ONNX-модели | |
| Хост | --host |
HOST |
0.0.0.0 |
Адрес привязки | |
| Порт | --port |
PORT |
8000 |
Порт привязки | |
| API-ключ | --api-key |
API_KEY |
(отключён) | Ключ аутентификации | |
| Макс. токенов | --max-length |
MAX_LENGTH |
512 |
Максимальная длина входа | |
| Потоки | --threads |
THREADS |
auto |
Число intra-op потоков (auto = cpu_count/2) | |
| Размер батча | --max-batch-size |
MAX_BATCH_SIZE |
32 |
Макс. текстов на один инференс | |
| Таймаут остановки | --shutdown-timeout |
SHUTDOWN_TIMEOUT |
30 |
Таймаут остановки (с) |
Экспорт модели
Скрипт export_model.py конвертирует любую HuggingFace-модель в ONNX INT8.
Совместимость:
optimum[onnxruntime](нужен для экспорта) работает только сtransformers<4.54.0. Если сервис запущен наtransformers >= 5.0, создайте отдельное виртуальное окружение для экспорта модели:pip install 'transformers<4.54.0' 'optimum[onnxruntime]' onnxruntime optimum python export_model.py deepvk/USER-base --output-dir ./model
# По умолчанию: deepvk/USER-base → ./model (INT8, AVX2)
python export_model.py
# Произвольная модель
python export_model.py intfloat/multilingual-e5-small --output-dir ./e5-model
# FP32 без квантизации
python export_model.py deepvk/USER-base --no-quantize
# INT8 для ARM64 (например, Allwinner H6, Raspberry Pi)
python export_model.py deepvk/USER-base --quantize-config arm64
# Произвольная модель с remote code
python export_model.py BAAI/bge-m3 --trust-remote-code
Квантованная модель привязана к набору инструкций процессора. Модель, квантованная под arm64, даёт некорректные результаты на x86 и наоборот. Выбирайте --quantize-config под целевую платформу.
Параметры экспорта
| Параметр | По умолчанию | Описание |
|---|---|---|
model_id |
deepvk/USER-base |
Идентификатор модели на HuggingFace |
--output-dir |
./model |
Директория для сохранения ONNX-модели |
--no-quantize |
— | Пропустить INT8-квантизацию (экспорт только FP32) |
--max-length |
512 |
Максимальная длина последовательности |
--trust-remote-code |
— | Доверять remote code (для нестандартных архитектур) |
--opset |
18 |
Версия ONNX opset |
--quantize-config |
avx2 |
Целевая архитектура: avx2, avx512, arm64 |
Загрузка модели
При запуске сервис ищет ONNX-файл в указанной директории в следующем порядке:
model_quantized.onnx— INT8-квантованная модель (рекомендуется для CPU)model.onnx— FP32-модель (точнее, но медленнее)onnx/model_quantized.onnxonnx/model.onnxquantized/model.onnx- Любой другой
.onnxфайл в директории (рекурсивный обход)
Рекомендация: для инференса на CPU используйте INT8-квантованную модель (model_quantized.onnx). Она даёт значительный прирост скорости ценой незначительной потери качества — для embedding-моделей падение точности обычно составляет 0.5–2% (метрика Spearman correlation на задачах semantic similarity), что в большинстве сценариев незаметно.
FastAPI автоматически генерирует OpenAPI-документацию — после запуска сервера она доступна по адресам:
- Swagger UI:
http://localhost:8000/docs - ReDoc:
http://localhost:8000/redoc - OpenAPI JSON:
http://localhost:8000/openapi.json
API
POST /v1/embeddings
Тело запроса:
| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
input |
string | string[] |
— | Текст или массив текстов (обязательное) |
model |
string |
"default" |
Идентификатор модели |
encoding_format |
string |
"float" |
Формат эмбеддинга: "float" или "base64" |
user |
string |
null |
Идентификатор пользователя (не используется) |
Поле data в ответе — всегда массив, независимо от того, передана строка или массив строк на вход.
Пример с одной строкой:
curl http://localhost:8000/v1/embeddings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-api-key" \
-d '{
"model": "default",
"input": "Текст для эмбеддинга",
"encoding_format": "float"
}'
Пример с массивом строк:
curl http://localhost:8000/v1/embeddings \
-H "Content-Type: application/json" \
-d '{
"input": ["первый текст", "второй текст", "третий текст"]
}'
Ответ:
{
"object": "list",
"data": [
{
"object": "embedding",
"embedding": [0.0123, -0.0456, ...],
"index": 0
}
],
"model": "default",
"usage": {
"prompt_tokens": 12,
"total_tokens": 12
}
}
Ошибки:
| Статус | Описание |
|---|---|
400 |
Пустой или состоящий из пробелов input |
401 |
Отсутствует или неверный Authorization: Bearer <key> |
422 |
Отсутствует обязательное поле input |
GET /v1/models
Возвращает информацию о доступных моделях. Не требует авторизации.
GET /health
Эндпоинт проверки состояния. Не требует авторизации.
GET /metrics
Prometheus-метрики в формате text/plain. Не требует авторизации.
| Метрика | Тип | Labels | Описание |
|---|---|---|---|
embeddings_requests_total |
Counter | endpoint, status |
Количество запросов |
embeddings_request_duration_seconds |
Histogram | endpoint |
Гистограмма времени ответа |
embeddings_tokens_total |
Counter | — | Всего обработано токенов |
embeddings_errors_total |
Counter | endpoint, status |
Количество ошибок |
POST /v1/tokenize
Проверка токенизации текста без ограничения max_length:
curl http://localhost:8000/v1/tokenize \
-H "Content-Type: application/json" \
-d '{"input": "текст для проверки токенов"}'
Ответ:
{
"object": "list",
"data": [
{
"index": 0,
"input": "текст для проверки токенов",
"token_count": 8,
"token_ids": [1, 123, 456, ...],
"truncated": false,
"max_length": 256
}
]
}
Поле truncated показывает, превышает ли текст ваш --max-length.
Docker
docker build -t saas-embedding .
docker run -p 8000:8000 \
-v /path/to/your/model:/app/model \
-e MODEL_DIR=/app/model \
-e THREADS=2 \
--cpus=1.0 \
--memory=512m \
saas-embedding
Модель подключается через volume (-v) в /app/model (рабочая директория контейнера). Образ содержит только код и зависимости.
Рекомендации по количеству потоков
По умолчанию --threads выбирается автоматически как cpu_count / 2. При необходимости задаётся явно.
| Платформа | --threads |
Примечание |
|---|---|---|
| Intel N5105 | 2 |
4 ядра, AVX2 |
| Allwinner H6 | 1 |
Cortex-A53, ускорения от многопоточности нет |
Производительность
Результаты бенчмарка на AMD Ryzen 7 2700 (16 cores) с моделью deepvk/USER-base (INT8, 120 MB):
batch texts type mean(s) texts/s vs batch=1
-----------------------------------------------------
1 32 short 1.98 16 1.0x
1 32 mixed 3.82 8 1.0x
1 128 short 8.16 16 1.0x
1 128 mixed 15.41 8 1.0x
16 32 short 2.11 15 0.9x
16 32 mixed 5.59 6 0.4x
16 128 short 8.68 15 0.9x
16 128 mixed 21.94 6 0.4x
32 32 short 2.38 13 0.8x
32 32 mixed 11.10 3 0.2x
32 128 short 9.40 14 0.9x
32 128 mixed 23.64 5 0.3x
Ключевые выводы:
- На CPU throughput линеен по числу токенов — батчинг не ускоряет (batch=1 даёт те же ~16 текстов/с, что и batch=32)
- Батчинг по 32 текста защищает от OOM на больших запросах без потери пропускной способности
- Сортировка по длине текстов уменьшает влияние паддинга на смешанной нагрузке (длинные + короткие тексты)
max-batch-size=32— безопасный дефолт, не требующий настройки под конкретный CPU
MIT. Подробности в LICENSE.