No description
  • Python 99.2%
  • Dockerfile 0.8%
Find a file
fedoryuk_au 2cd1425e58
All checks were successful
ci/woodpecker/tag/woodpecker Pipeline was successful
Доработка экспорт модели
2026-06-03 14:32:03 +03:00
tests Добавление тестов по кросс языковому сходству 2026-05-18 13:32:55 +03:00
.dockerignore first commit 2026-05-18 12:07:00 +03:00
.gitignore first commit 2026-05-18 12:07:00 +03:00
.woodpecker.yml first commit 2026-05-18 12:07:00 +03:00
app.py Попытка поиска утечки памяти 2026-06-03 14:15:22 +03:00
benchmark.py Попытка поиска утечки памяти 2026-06-03 14:15:22 +03:00
Dockerfile Исправление зависимостией 2026-05-18 12:46:59 +03:00
export_model.py Доработка экспорт модели 2026-06-03 14:32:03 +03:00
LICENSE first commit 2026-05-18 12:07:00 +03:00
README.md Доработка экспорт модели 2026-06-03 14:32:03 +03:00
requirements.txt Исправление зависимостией 2026-05-18 12:46:59 +03:00

saas-embedding

OpenAI-совместимый сервис эмбеддингов, оптимизированный для слабых CPU (Intel N5105, Allwinner H6) с качественной поддержкой русского языка.

Требуется Python 3.10+.

Возможности

  • REST API, совместимый с OpenAI /v1/embeddings
  • Инференс на CPU через ONNX Runtime (без PyTorch в рантайме)
  • INT8-квантизация для ускорения в 3050%
  • Поддержка батчевой обработки (массив текстов)
  • Настройка через 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-файл в указанной директории в следующем порядке:

  1. model_quantized.onnx — INT8-квантованная модель (рекомендуется для CPU)
  2. model.onnx — FP32-модель (точнее, но медленнее)
  3. onnx/model_quantized.onnx
  4. onnx/model.onnx
  5. quantized/model.onnx
  6. Любой другой .onnx файл в директории (рекурсивный обход)

Рекомендация: для инференса на CPU используйте INT8-квантованную модель (model_quantized.onnx). Она даёт значительный прирост скорости ценой незначительной потери качества — для embedding-моделей падение точности обычно составляет 0.52% (метрика 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.