| tests | ||
| Dockerfile | ||
| README.md | ||
| requirements-test.txt | ||
| requirements.txt | ||
| server.py | ||
embedding-runtime
OpenAI-совместимый сервер эмбеддингов и переранжирования для линии векторного инференса SmartMLOps.
Это кастомный контейнер предиктора KServe (ClusterServingRuntime), обслуживающий модели
sentence-transformers в тех случаях, когда нативный рантайм TEI не справляется — а именно:
модели с инструкциями/префиксами (E5, Qwen3-Embedding, Instructor) и модели с переключением
адаптеров (Jina v5 base с task-специфичными LoRA-адаптерами). KServe регистрирует и управляет
им как embedding-runtime-cpu / embedding-runtime-cuda с modelFormat: embedding-runtime.
vector-gateway направляет трафик эмбеддингов и переранжирования на InferenceService, backed
этим рантаймом. Данный репозиторий владеет только кодом обслуживания; KServe берёт на себя
планирование, масштабирование и монтирование артефактов модели.
Этот компонент был недавно выделен из монорепозитория в отдельный git-субмодуль. Файл
.gitв этой директории является указателем субмодуля; клонируйте через корневой репозиторийmlops-2, чтобы получить корректно настроенное рабочее дерево.
Место в архитектуре
vector-gateway
└── /v1/embeddings, /v1/rerank → KServe InferenceService (modelFormat: embedding-runtime)
└── контейнер embedding-runtime (этот репозиторий)
└── модель sentence-transformers
└── опциональный LoRA-адаптер (PEFT)
KServe монтирует артефакт модели по пути /mnt/models. Сервер читает этот путь через
EMBEDDING_MODEL_DIR (по умолчанию /mnt/models) и передаёт его в качестве имени модели
в SentenceTransformer. Переменная EMBEDDING_MODEL_NAME позволяет переопределить
идентификатор модели, когда он отличается от пути монтирования (например, для использования
HuggingFace Hub ID при загрузке по требованию).
Эндпоинты
POST /v1/embeddings
Кодирует один или несколько текстов в нормализованные векторы эмбеддингов.
Запрос
{
"input": "text or list of texts",
"model": "optional — ignored server-side, echoed in response",
"task": "retrieval.query",
"instruction": "optional explicit prefix; overrides the per-task default"
}
Поле input принимает как одиночную строку, так и массив строк.
Ответ
{
"object": "list",
"model": "<MODEL_NAME>",
"data": [
{"object": "embedding", "index": 0, "embedding": [0.12, -0.34, ...]}
],
"usage": {"prompt_tokens": 3, "total_tokens": 3}
}
Эмбеддинги L2-нормализованы (normalize_embeddings=True); скалярное произведение равно
косинусному сходству.
POST /v1/rerank
Ранжирует документы по запросу с использованием косинусного сходства по нормализованным эмбеддингам. Отдельный кросс-энкодер не используется; работает с любой базовой моделью эмбеддингов.
Запрос
{
"query": "search query",
"documents": ["doc 1", "doc 2", "doc 3"],
"top_n": 2,
"model": "optional",
"task": "optional — overrides the default query/passage tasks",
"instruction": "optional explicit prefix"
}
Если task не задан, запрос кодируется с retrieval.query, а каждый документ —
с retrieval.passage. Параметр top_n ограничивает список результатов; если он не указан,
возвращаются все документы.
Ответ
{
"model": "<MODEL_NAME>",
"results": [
{"index": 1, "relevance_score": 0.95, "document": "doc 2"},
{"index": 0, "relevance_score": 0.42, "document": "doc 1"}
]
}
Результаты отсортированы по убыванию relevance_score.
GET /health
Проверка работоспособности (liveness и readiness probe). Возвращает 200 немедленно,
без загрузки модели.
{"status": "ok", "device": "cuda", "model": "/mnt/models"}
Загрузка модели и адаптеров
Базовая модель
Модель загружается лениво при первом инференс-запросе с помощью
SentenceTransformer(MODEL_NAME, device=DEVICE, trust_remote_code=True).
Флаг trust_remote_code=True включён всегда — он необходим для Qwen3-Embedding и других
моделей, которые поставляются с кастомным кодом токенизатора или модели на Hub. Используйте
только артефакты из доверенных источников.
DEVICE определяется автоматически: cuda при наличии GPU, cpu в противном случае.
Переключение задач — класс с инструкциями/префиксами
Применяется к E5, Qwen3-Embedding, Instructor и любым моделям, у которых качество представлений зависит от ведущей строки-инструкции.
Перед кодированием к каждому входному тексту добавляется строка-префикс. Соответствие имени задачи префиксу:
| Задача | Префикс по умолчанию |
|---|---|
retrieval.query |
Represent this query for retrieving relevant documents: |
retrieval.passage |
Represent this document for retrieval: |
classification |
Represent this text for classification: |
clustering |
Represent this text for clustering: |
Таблицу можно переопределить или расширить при запуске через EMBEDDING_TASK_INSTRUCTIONS.
Явное поле instruction в запросе имеет приоритет над умолчанием для задачи.
Переключение задач — класс с LoRA-адаптерами
Применяется к моделям вроде Jina v5 base, которые рассчитаны на замену task-специфичных LoRA-адаптеров во время инференса.
Загрузка адаптеров ленивая и кэшируется. При поступлении запроса с task, для которого
настроен путь к адаптеру, сервер вызывает model[0].auto_model.load_adapter() и
set_adapter() через PEFT. В один момент времени активен только один адаптер на процесс.
Загрузка пропускается, если запрошенный адаптер уже активен.
Адаптеры настраиваются через EMBEDDING_TASK_ADAPTERS (см. раздел «Переменные окружения»).
Переменные окружения
| Переменная | Значение по умолчанию | Описание |
|---|---|---|
EMBEDDING_MODEL_DIR |
/mnt/models |
Директория, куда KServe монтирует артефакт модели. Также используется как идентификатор модели по умолчанию. |
EMBEDDING_MODEL_NAME |
значение EMBEDDING_MODEL_DIR |
Переопределяет идентификатор модели для SentenceTransformer (путь или репозиторий HuggingFace Hub). |
EMBEDDING_TASK_INSTRUCTIONS |
{} |
JSON-объект, отображающий имена задач в строки-префиксы инструкций. Накладывается поверх четырёх встроенных умолчаний. Пример: {"retrieval.query": "Query: ", "retrieval.passage": "Passage: "} |
EMBEDDING_TASK_ADAPTERS |
{} |
JSON-объект, отображающий имена задач в пути к LoRA-адаптерам или Hub-репозитории. Пример: {"retrieval": "./adapters/retrieval", "classification": "org/cls-adapter"} |
Сборка образа
Сборка из корня монорепозитория с контекстом, ограниченным этой поддиректорией:
docker build -t smartmlops/embedding-runtime:dev apps/embedding-runtime
Или непосредственно из этой директории:
docker build -t smartmlops/embedding-runtime:dev .
Образ основан на python:3.11-slim. Зависимости зафиксированы в requirements.txt:
fastapi==0.115.6
uvicorn[standard]==0.34.0
sentence-transformers==3.3.1
transformers==4.47.1
torch==2.5.1
peft==0.14.0
numpy==2.2.1
Точка входа: uvicorn server:app --host 0.0.0.0 --port 8080.
Локальный запуск
Контейнер скачивает модель с HuggingFace Hub, если EMBEDDING_MODEL_NAME является
идентификатором Hub-репозитория, а EMBEDDING_MODEL_DIR ещё не содержит весов.
# smoke-запуск — при первом старте скачивает BAAI/bge-small-en-v1.5
docker run --rm -p 8080:8080 \
-e EMBEDDING_MODEL_NAME=BAAI/bge-small-en-v1.5 \
smartmlops/embedding-runtime:dev
# проверка эмбеддингов
curl -s localhost:8080/v1/embeddings \
-H 'content-type: application/json' \
-d '{"input": ["hello world"], "task": "retrieval.query"}' | python3 -m json.tool
# проверка переранжирования
curl -s localhost:8080/v1/rerank \
-H 'content-type: application/json' \
-d '{"query": "cats", "documents": ["dogs", "cats", "fish"], "top_n": 2}' | python3 -m json.tool
# проверка работоспособности
curl -s localhost:8080/health
Смонтируйте заранее скачанную директорию с моделью, чтобы избежать повторной загрузки при каждом запуске:
docker run --rm -p 8080:8080 \
-v /path/to/model:/mnt/models:ro \
smartmlops/embedding-runtime:dev
Для GPU-инференса передайте --gpus all и используйте тег образа embedding-runtime-cuda
(который требует базового CUDA-образа; соответственно обновите строку FROM в Dockerfile).
Запуск тестов
Цель quality в Dockerfile задаёт воспроизводимую среду проверки на том же
закреплённом по digest образе Chainguard Python, что и стадия сборки зависимостей.
Она устанавливает только версии из requirements.txt и requirements-test.txt,
а затем выполняет ровно 8 tests, Ruff, Ty и проверку компиляции:
docker build --target quality -t embedding-runtime:quality .
Внутри стадии выполняются точные команды:
/home/nonroot/venv/bin/python -m pytest -p no:cacheprovider tests
/home/nonroot/venv/bin/ruff check server.py tests
/home/nonroot/venv/bin/ty check server.py tests
/home/nonroot/venv/bin/python -m compileall -q server.py tests
Ty получает точный runtime venv через VIRTUAL_ENV=/home/nonroot/venv.
Для bytecode используется объявленный writable prefix
PYTHONPYCACHEPREFIX=/tmp/embedding-runtime-pycache; исходники остаются read-only
совместимыми. Ruff 0.15.19 и Ty 0.0.53 закреплены в
requirements-test.txt вместе с pytest и HTTP-зависимостями.
SBOM-проверка различает экосистемы по purl. Python-дистрибутив
pkg:pypi/wheel отсутствует из финального venv. Базовый Chainguard-образ при этом
содержит отдельный системный APK pkg:apk/wolfi/py3-pip-wheel; он не скрывается
и не считается Python-дистрибутивом wheel. Допустимость этого APK подтверждается
свежим Trivy-сканом без HIGH/CRITICAL findings, а не утверждением об отсутствии пакета.
Развёртывание в KServe
Рантайм зарегистрирован как ClusterServingRuntime в платформе. Пример InferenceService,
использующего его:
apiVersion: serving.kserve.io/v1beta1
kind: InferenceService
metadata:
name: my-embedding-model
namespace: tenant-smoke
spec:
predictor:
model:
modelFormat:
name: embedding-runtime
storageUri: hf://BAAI/bge-small-en-v1.5
# Инструкции по задачам для моделей класса instruction:
env:
- name: EMBEDDING_TASK_INSTRUCTIONS
value: '{"retrieval.query": "Query: ", "retrieval.passage": "Passage: "}'
KServe монтирует модель по пути /mnt/models внутри контейнера предиктора. Ресурс
ClusterServingRuntime (embedding-runtime-cpu для CPU-узлов, embedding-runtime-cuda
для GPU-узлов) связывает образ контейнера, порт 8080 и селектор modelFormat.
Ограничения и особенности
trust_remote_code=Falseвключён безусловно. Рантайм не исполняет код, поставляемый вместе с артефактом модели; совместимость модели должна обеспечиваться штатной реализациейsentence-transformers.- Только один активный LoRA-адаптер на процесс. При одновременных запросах с разными
значениями
task, отображающимися на разные адаптеры, будет происходить сериализация на переключении адаптера. Для высоконагруженных multi-task адаптерных сценариев рассмотрите развёртывание отдельных InferenceService на каждую задачу. - CPU-инференс медленный. Вариант рантайма
-cpuподходит для разработки и малонагруженных сценариев, однако производственные нагрузки по эмбеддингам следует направлять на GPU-узел, используя вариант-cuda. - Переранжирование основано на схожести эмбеддингов, а не на кросс-энкодере. Эндпоинт
/v1/rerankоценивает документы с помощью скалярного произведения нормализованных эмбеддингов, а не отдельной модели кросс-энкодера. Это быстрее и работает с любой базовой моделью, однако кросс-энкодер обеспечивает более высокую точность для задач, где важна прецизионность. - Количество токенов в
usageприблизительное. Значениеprompt_tokensоценивается разбиением по пробелам, а не реальным токенизатором.