База знаний

BAAI/bge-m3 — мультиязычная embedding-модель для self-hosted RAG

Open-source embedding-модель от BAAI для поиска, RAG и семантического сравнения текстов. Контекст 8192 токена, 100+ языков, MIT-лицензия, работает на CPU. Разбор возможностей, сравнение с альтернативами и эталонная сборка сервера на FastAPI.

Опубликовано

Если вы собираете свой RAG и ищете открытую embedding-модель, которая хорошо работает и с русским, и с английским, да ещё и тянет длинные документы — с большой вероятностью вам нужна именно BAAI/bge-m3. Это мой дефолтный выбор для self-hosted сценариев, и в этом справочнике я собрал всё, что стоит знать, прежде чем ставить её на свой сервер.

📌
Коротко: BGE-M3 — лучший дефолт из открытых моделей для двуязычных (RU/EN) RAG-систем на своей инфраструктуре. MIT-лицензия, контекст 8192 токена, 1024-мерные векторы, спокойно работает на CPU.

Что это вообще такое

BGE-M3 (BAAI General Embedding — M3) — embedding-модель от Beijing Academy of Artificial Intelligence, опубликованная в начале 2024 года. М3 в названии — это три «мульти», которыми она отличается от предыдущих поколений:

  • Multi-Linguality — больше 100 языков, включая русский, и что важно — сильный cross-lingual: запрос на русском находит релевантный фрагмент на английском и наоборот.
  • Multi-Functionality — dense, sparse и multi-vector retrieval внутри одной модели. Не надо держать несколько.
  • Multi-Granularity — контекст до 8192 токенов. Можно эмбеддить не «кусочки», а целые статьи.

Под капотом — XLM-RoBERTa-large, дообученная на больших объёмах парных и триплетных данных. На момент релиза модель была одной из лучших открытых на бенчмарке MIRACL (мультиязычный retrieval), и с тех пор её так и не скинули с пьедестала для двуязычных задач.

💡
Термин: embedding — числовой вектор, в который модель превращает текст. У близких по смыслу текстов векторы тоже близкие. Благодаря этому можно искать по смыслу, а не по ключевым словам.

Ключевые характеристики

ПараметрЗначение
РазработчикBAAI (Beijing Academy of Artificial Intelligence)
ЛицензияMIT — коммерческое использование без ограничений
АрхитектураXLM-RoBERTa-large, дообученная
Размер весов~2.27 ГБ
RAM при инференсе~0.9–1.2 ГБ
Размерность dense-вектора1024
Максимальный контекст8192 токена
Языки100+, включая русский, английский, китайский
Типы retrievalDense, Sparse (lexical), Multi-vector (ColBERT-style)
Скорость на CPU (Ryzen 5 3600)~120 мс на чанк

Три режима работы в одной модели

Главная фишка M3 — одна и та же модель даёт три разных типа представлений. Можно брать по одному, можно комбинировать в гибридный поиск.

РежимЧто выдаётКогда использовать
DenseОдин вектор 1024 на текстКлассический семантический поиск, обычный RAG
Sparse / LexicalВеса токенов (как BM25, но обученные)Когда важны точные совпадения терминов, имён, кодов
Multi-vector (ColBERT)Отдельный вектор на каждый токенРе-ранжирование top-N для максимальной точности
⚖️
Компромисс: sparse и multi-vector дают качество, но жрут память в индексе. На старте берите dense. Остальные режимы подключайте только когда метрики реально проседают — а не «на всякий случай».

Когда BGE-M3 — правильный выбор

  • Двуязычные и мультиязычные базы знаний, где надо искать по-русски по английским документам (и наоборот).
  • Длинные чанки и документы — всё, что не лезет в 512 токенов у старых моделей вроде e5-large или multilingual-MiniLM.
  • Self-hosted сценарии, где важна предсказуемая стоимость и нет желания зависеть от внешнего API.
  • Гибридный поиск — когда хочется из одной модели получать и dense, и lexical.
  • Коммерческие проекты — MIT-лицензия снимает вопросы, в отличие от jina-v3 с её CC-BY-NC.

Когда лучше взять что-то другое

  • Чисто английские задачи с максимальными требованиями к качеству. text-embedding-3-large от OpenAI, Voyage-3, Cohere Embed v3 часто выигрывают.
  • Нужна минимальная задержка на коротких запросах без GPU. Маленькие модели вроде bge-small-en или gte-small дают 10–30 мс против 120 мс у M3.
  • Критична экономия на векторной БД. 1024 измерения против 384 у малых моделей — это в 2,6 раза больше места.
  • Эмбеддинги для кода. Берите специализированные модели (jina-embeddings-v2-code, voyage-code-2).

Сравнение с альтернативами

МодельКонтекстМультиязычностьЛицензияРазмерность
BAAI/bge-m38192100+ языков, сильнаяMIT1024
intfloat/multilingual-e5-large512100 языковMIT1024
jina-embeddings-v3819289 языковCC-BY-NC-4.0 (non-commercial)1024
OpenAI text-embedding-3-large8191Хорошая, без фокуса на RUПроприетарная, только API3072 (truncatable)
Cohere embed-multilingual-v3512100+ языковПроприетарная, только API1024

Поднимаем на своём сервере

Минимальные требования

  • CPU: любой современный x86_64. В примерах ниже — Ryzen 5 3600.
  • RAM: от 4 ГБ свободной. Сама модель ~1 ГБ, остальное уходит на батчи и overhead.
  • Диск: ~3 ГБ под веса и зависимости.
  • Python 3.10+.
  • GPU не обязателен. Но если он есть — инференс ускоряется в 10–30 раз.

Ставим зависимости

python3 -m venv .venv
source .venv/bin/activate
pip install -U FlagEmbedding fastapi uvicorn pydantic

FlagEmbedding — официальная библиотека от BAAI. В ней уже собраны оптимальные пресеты для всех трёх режимов, не надо ничего угадывать.

Эталонный FastAPI-сервер (OpenAI-compatible)

Рабочий пример: кладёте в один файл server.py, запускаете, получаете embedding-сервис с API в формате OpenAI /v1/embeddings. Это значит, что вы можете вставить его как drop-in replacement в LiteLLM, LangChain, LlamaIndex — ничего не переписывая.

# server.py — OpenAI-compatible embeddings server на BGE-M3
from fastapi import FastAPI
from pydantic import BaseModel
from FlagEmbedding import BGEM3FlagModel
from typing import List, Union
import time, uuid

# use_fp16=False на CPU; на GPU ставьте True для 2x ускорения
model = BGEM3FlagModel('BAAI/bge-m3', use_fp16=False)

app = FastAPI()

class EmbeddingRequest(BaseModel):
    input: Union[str, List[str]]
    model: str = "bge-m3"

@app.post("/v1/embeddings")
def embeddings(req: EmbeddingRequest):
    texts = [req.input] if isinstance(req.input, str) else req.input
    # dense-вектор — основной режим для RAG
    vectors = model.encode(texts, batch_size=8, max_length=8192)['dense_vecs']
    return {
        "object": "list",
        "model": req.model,
        "data": [
            {"object": "embedding", "index": i, "embedding": v.tolist()}
            for i, v in enumerate(vectors)
        ],
        "usage": {"prompt_tokens": 0, "total_tokens": 0},
    }

@app.get("/health")
def health():
    return {"status": "ok", "model": "BAAI/bge-m3"}

Запуск:

uvicorn server:app --host 127.0.0.1 --port 8080

Systemd-юнит для автозапуска

# /etc/systemd/system/bge-m3.service
[Unit]
Description=BGE-M3 Embeddings Server
After=network.target

[Service]
Type=simple
User=embeddings
WorkingDirectory=/opt/bge-m3
Environment="PATH=/opt/bge-m3/.venv/bin"
ExecStart=/opt/bge-m3/.venv/bin/uvicorn server:app --host 127.0.0.1 --port 8080
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Активация:

sudo systemctl daemon-reload
sudo systemctl enable --now bge-m3
sudo systemctl status bge-m3
💡
Совет: биндите сервис на 127.0.0.1 или на интерфейс Tailscale, а не на 0.0.0.0. Авторизации у модели из коробки нет. Открытый порт в интернет — это приглашение чужим ботам молотить ваш CPU за ваш же счёт.

Проверяем, что всё живо

curl -X POST http://127.0.0.1:8080/v1/embeddings \
  -H "Content-Type: application/json" \
  -d '{"input": ["Привет, мир", "Hello, world"], "model": "bge-m3"}' | jq '.data | length'
# ожидаемо: 2

Чтобы проверить cross-lingual качество на глаз, посчитайте cosine similarity между русской и английской версией одной и той же фразы. Для нормальных переводов должно быть 0.85+. Если получаете 0,70 или ниже — где-то накосячили: не тот pooling, обрезан контекст, неправильно собран батч.


Практические сценарии

RAG по двуязычной базе знаний

  • Индексируете документы на русском и английском одной моделью.
  • Запрос может быть на любом языке — релевантный фрагмент найдётся независимо от языка источника.
  • Работает из коробки, без отдельного переводчика в пайплайне.

Гибридный поиск dense + sparse

  • Dense ловит синонимы и переформулировки.
  • Sparse ловит точные термины, имена, артикулы, номера версий.
  • Финальный скор — взвешенная сумма, обычно 0.7 × dense + 0.3 × sparse. Веса стоит подкрутить под свой корпус.

Семантический дедуп

  • Эмбеддите входящие сообщения, тикеты или карточки.
  • Порог cosine > 0.92 — почти наверняка дубль.
  • 0.80–0.92 — похожая тема, кандидат на объединение, но лучше глазами.

Кластеризация идей и заметок

  • Эмбеддите все заметки, прогоняете через UMAP + HDBSCAN.
  • Получаете тематические группы без заранее заданных категорий. Удобно для «а что у меня вообще в голове творится».

Типичные ошибки и как их чинить

СимптомПричинаРешение
Низкая cross-lingual similarity (<0.75)Обрезка контекста по 512 токенам, неправильный poolingЯвно указать max_length=8192, использовать dense_vecs из FlagEmbedding
OOM на больших батчахbatch_size слишком большой для RAMСнизить до 4–8 на CPU, 16–32 на GPU
Первый запрос — 30+ секундCold start: загрузка весов и прогревПрогревать модель фиктивным запросом при старте сервиса
Ошибка 422 в LiteLLMОтвет не соответствует ожидаемой схемеОтдавать ровно OpenAI-формат: {object, model, data: [{embedding, index}], usage}
Медленно на CPU (500+ мс)Нет AVX2, старый NumPy, включён fp16 на CPUПроверить lscpu, обновить numpy и torch, убрать use_fp16=True
⚠️
Важно: use_fp16=True на CPU не ускоряет инференс, а замедляет его в 2–3 раза. Флаг имеет смысл только на GPU с tensor cores. На CPU — строго False.

Антипаттерны

  • Эмбеддить весь документ целиком. Даже при контексте 8192 качество retrieval проседает. Режьте на чанки по 500–1500 токенов с overlap.
  • Выставлять сервис в публичный интернет без auth. Бесплатный compute для чужих ботов за вашу электричество — сомнительное удовольствие.
  • Мешать эмбеддинги от разных моделей в одном индексе. Векторы несопоставимы. Поиск сломается тихо и незаметно — это худший вариант.
  • Считать cosine similarity до нормализации. FlagEmbedding возвращает уже нормализованные векторы, но если собираете руками — нормализуйте, иначе скор будет искажён.
  • Игнорировать prompt-инструкции для запросов. Для retrieval-режима BGE рекомендует префикс к query — посмотрите карточку модели на HuggingFace.

Полезные ссылки


По теме

Если собираете свой RAG-контур и прикидываете, какую embedding-модель ставить под двуязычную базу знаний — давайте обсудим, что лучше подойдёт под вашу нагрузку и инфраструктуру.