Доменные эмбеддинги NVIDIA NeMo: обучение за 1 день без данных | AiManual
AiManual Logo Ai / Manual.
20 Мар 2026 Гайд

Доменные эмбеддинги за 24 часа: рецепт от NVIDIA, который не требует ни одного размеченного примера

Пошаговая инструкция по созданию доменных моделей эмбеддингов с синтетическими данными. Код, конфиги и разбор ошибок от NVIDIA.

Проблема: ваш поиск в документах сосёт. А данные стоят как Ferrari

Возьмем реальный сценарий. У вас 50 тысяч страниц внутренней техдокументации, медицинских исследований или спецификаций к чипам. Общая модель вроде BGE или text-embedding-3 на них спотыкается. Она не понимает ваши доменные термины, аббревиатуры, жаргон. Релевантность поиска падает до неприличных 30-40%.

Классическое решение? Дообучить эмбеддинги на своих данных. И вот тут начинается ад. Нужны тысячи, а лучше десятки тысяч размеченных пар «запрос-релевантный документ». Размечать вручную — месяцы работы и сотни тысяч рублей. Аутсорсить — дорого и долго. Парадокс: чтобы улучшить поиск, нужны данные, которые можно получить только… с помощью хорошего поиска.

Кейс Atlassian 2025 года: они дообучили эмбеддинги для поиска по своей техдокументации с помощью синтетических данных от NVIDIA. Результат — рост точности (NDCG@10) на 26%. И ни одного ручного примера. Вот о чем мы сегодня.

Решение: генерация данных нейросетью и автоматический отбор сложных примеров

NVIDIA в 2025-2026 выкатила полный стек инструментов, который превращает этот процесс из научной работы в инженерную рутину. Основа — два компонента:

  • NeMo Data Designer: генерирует синтетические пары «запрос-релевантный пассаж» на основе вашего корпуса документов. По сути, заставляет большую языковую модель (вроде Nemotron-4 340B) придумывать правдоподобные вопросы к вашим текстам.
  • NeMo Automodel для Embeddings: автоматизированный пайплайн для контрастивного обучения. Сам подбирает hard negatives, настраивает loss functions и управляет гиперпараметрами.

Философия проста: если нет данных — создай их. Если сложно выбрать правильные негативные примеры — поручи это алгоритму. Остается только запустить процесс и через несколько часов получить готовую модель.

💡
Актуальность на март 2026: мы говорим о версиях NeMo Framework 1.22+ и NeMo Embeddings Model 2.5. В них появилась встроенная поддержка генерации данных прямо из интерфейса PyTorch Lightning и оптимизации под архитектуры Hopper и Blackwell GPU. Старые гайды 2024 года с ручной настройкой DataLoader'ов теперь не нужны.

1 Подготовка: ставим NeMo и добываем «сырые» документы

Забудьте про pip install nemo-toolkit. Сейчас все иначе. NVIDIA упаковывает полный стек для эмбеддингов в отдельный контейнер, который тянет за собой все зависимости. Но мы пойдем путем чистого Python, чтобы понимать, что под капотом.

# Клонируем репозиторий с официальными примерами (актуальный на 2026)
git clone https://github.com/NVIDIA/NeMo-Embeddings-Recipes.git
cd NeMo-Embeddings-Recipes

# Создаем виртуальное окружение и ставим зависимости
python -m venv nemo_env
source nemo_env/bin/activate  # для Windows: nemo_env\Scripts\activate

# Устанавливаем NeMo с поддержкой эмбеддингов и синтетических данных
pip install nemo-toolkit[embedding]==1.22.0 nemo-data-designer==0.8.0

# Проверяем установку
python -c "import nemo; import nemo_data_designer; print('Всё готово')"

Ошибка номер один: попытка установить старую версию NeMo (1.19 и ниже). В них нет интеграции с Data Designer, и вы будете неделями мучиться с ручной подготовкой датасета. Не делайте так.

Теперь нужны ваши документы. Это может быть папка с PDF, Markdown, HTML или простой текстовый файл. Конвертируем все в чистый текст. Используем unstructured библиотеку — она сейчас де-факто стандарт для извлечения текста.

from unstructured.partition.auto import partition
import glob

documents = []
for file_path in glob.glob("путь/к/вашим/документам/*.pdf"):
    elements = partition(filename=file_path)
    text = "\n".join([str(el) for el in elements])
    documents.append({"id": file_path, "text": text})

# Сохраняем в JSONL для дальнейшей обработки
import json
with open("raw_documents.jsonl", "w") as f:
    for doc in documents:
        f.write(json.dumps(doc, ensure_ascii=False) + "\n")

2 Магия: генерируем запросы и пассажи из воздуха

Вот сердце метода. NeMo Data Designer берет ваши документы и с помощью мощной LLM создает к каждому отрезку текста (пассажу) потенциальный запрос, который мог бы привести к этому тексту в поиске.

Под капотом используется Nemotron-4 340B Instruct (или, если у вас мало ресурсов, Nemotron-3 Nano 30B MoE), настроенная специальным промптом. Вам не нужно думать о промптах — все уже зашито в библиотеку.

from nemo_data_designer import SyntheticDataGenerator

# Инициализируем генератор. Он автоматически использует доступную LLM через NVIDIA NIM API или локально.
# Для локальной генерации нужна минимум 1x A100 80GB.
generator = SyntheticDataGenerator(
    model_name="nvidia/nemotron-4-340b-instruct",
    max_length=512,
    num_queries_per_passage=3  # Количество разных запросов на один пассаж
)

# Загружаем наши документы, разбиваем на пассажи (чанки)
passages = []
for doc in documents:
    # Простое разбиение по предложениям (в реальности лучше использовать semantic chunking)
    sentences = doc["text"].split('. ')
    chunk_size = 5  # 5 предложений на чанк
    for i in range(0, len(sentences), chunk_size):
        chunk = '. '.join(sentences[i:i+chunk_size])
        if len(chunk) > 50:  # отбрасываем слишком короткие
            passages.append(chunk)

# Генерируем синтетические пары (запрос, релевантный пассаж)
synthetic_pairs = generator.generate(passages[:100])  # для начала берем 100 пассажей

Что происходит? Модель анализирует пассаж и придумывает к нему разнообразные запросы: прямые, перефразированные, с ошибками, на смежные темы. Это и есть тренировочные данные.

💡
Сколько нужно данных? Правило 2026 года: для узкого домена (например, только документация по Kubernetes) достаточно 10-20 тысяч пар. Для широкого (вся медицинская литература) — 100-200 тысяч. Генерация 10 тысяч пар на Nemotron-4 через NIM API обойдется примерно в $50-70. Локально — бесплатно, но долго.

3 Hard Negative Mining: заставляем модель учиться на сложных ошибках

Если брать в качестве негативных примеров случайные пассажи из коллекции, модель научится тривиальным различиям. Это как учить ребенка отличать кошку от автомобиля — слишком просто. Нужны hard negatives: пассажи, которые семантически близки к запросу, но не релевантны.

Automodel делает это автоматически. Но чтобы понять процесс, посмотрим на код этапа mining:

from nemo.collections.nlp.models import EmbeddingModel

# Загружаем базовую модель эмбеддингов (например, NV-Embed-v2.5)
base_model = EmbeddingModel.from_pretrained("nvidia/nv-embed-v2.5-1024")

# Кодируем все пассажи в эмбеддинги
passage_embeddings = base_model.encode(passages, convert_to_tensor=True)

# Для каждого запроса находим ближайшие пассажи, которые НЕ являются релевантными
import torch
from sklearn.metrics.pairwise import cosine_similarity

hard_negatives = {}
for query, positive_passage in synthetic_pairs:
    query_embedding = base_model.encode([query], convert_to_tensor=True)
    # Вычисляем косинусную близость между запросом и всеми пассажами
    sims = cosine_similarity(query_embedding.cpu(), passage_embeddings.cpu())
    # Ищем пассажи с высокой схожестью, но это не positive_passage
    similar_indices = sims.argsort()[0][-100:]  # топ-100 ближайших
    # Выбираем из них те, которые не являются правильным ответом
    hard_negs = []
    for idx in similar_indices:
        if passages[idx] != positive_passage and len(hard_negs) < 5:
            hard_negs.append(passages[idx])
    hard_negatives[query] = hard_negs

Именно эти сложные примеры заставят модель научиться тонким различиям. Например, запрос «настройка GPU в Docker» будет иметь hard negative «установка Docker на машину с GPU». Похоже, но не то.

4 Обучение: один конфиг, одна команда, несколько часов

Раньше нужно было возиться с DataLoader, loss functions, смешанной точностью. Теперь — один YAML файл. NVIDIA за последние два года максимально упростила процесс.

Создаем конфиг config.yaml:

model:
  name: nv-embed-v2.5-1024
  trainable: true
  pooling: cls

data:
  train_file: synthetic_data_with_hard_negatives.jsonl
  num_workers: 8
  batch_size: 64  # для GPU с 24GB VRAM

trainer:
  devices: 1  # количество GPU
  max_epochs: 5
  precision: bf16-mixed
  gradient_clip_val: 1.0

loss:
  name: MultipleNegativesRankingLoss
  temperature: 0.02
  scale: 20

optimizer:
  lr: 2e-5
  weight_decay: 0.01

А теперь запускаем обучение:

python -m nemo.collections.nlp.run 
  --config-path ./configs \
  --config-name config.yaml \
  trainer.devices=4 \
  model.train_ds.file_path=synthetic_data_with_hard_negatives.jsonl

И ждем. На 4x A100 40GB обучение на 50 тысячах пар займет около 3-4 часов. Можете сходить выпить кофе. Много кофе.

Предупреждение: не увеличивайте batch_size без необходимости. Современные контрастивные loss функции (как MultipleNegativesRankingLoss) эффективно работают с умеренными размерами батча (64-128). Слишком большой батч может ухудшить качество, потому что модель будет видеть слишком много легких негативов.

5 Оценка: BEIR benchmark и ваши собственные тесты

Обучение прошло. Но как понять, что модель стала лучше? Первым делом — BEIR (Benchmarking Information Retrieval). Это стандартный набор из 18 датасетов для оценки эмбеддингов. Ваша модель должна показывать на доменном датасете результат лучше базовой.

from beir import util, evaluator
from beir.datasets.data_loader import GenericDataLoader
from beir.retrieval.evaluation import EvaluateRetrieval

# Загружаем доменный датасет (например, NFCorpus — медицинский)
dataset = "nfcorpus"
url = f"https://public.ukp.informatik.tu-darmstadt.de/thakur/BEIR/datasets/{dataset}.zip"
data_path = util.download_and_unzip(url, "datasets")
corpus, queries, qrels = GenericDataLoader(data_folder=data_path).load(split="test")

# Загружаем нашу обученную модель
from nemo.collections.nlp.models import EmbeddingModel
tuned_model = EmbeddingModel.restore_from("./checkpoints/embedding_model.nemo")

# Оцениваем
retriever = EvaluateRetrieval(tuned_model, score_function="cos_sim")
results = retriever.retrieve(corpus, queries)
ndcg, _map, recall, precision = evaluator.evaluate(qrels, results, [10, 100])
print(f"NDCG@10: {ndcg['NDCG@10']:.4f}")

Если NDCG@10 вырос на 5-15 пунктов — вы на правильном пути. 20+ — отличный результат. Но BEIR не всё. Создайте свой маленький тестовый набор из реальных запросов ваших пользователей. Проверьте вручную.

Где спрятаны грабли: 3 ошибки, которые сведут на всю работу

  1. Слишком много синтетики низкого качества. Если ваши исходные документы — мусор, то и сгенерированные запросы будут бессмысленными. Всегда делайте префильтрацию: удаляйте слишком короткие тексты, дубликаты, шаблонный текст (типа «Copyright 2025»).
  2. Игнорирование доменного словаря. Базовая модель NV-Embed-v2.5 обучена на общих данных. Она может не знать ваших специфических терминов. Решение: перед генерацией данных добавьте в промпт инструкцию с глоссарием. Или используйте доменно-адаптированную LLM для генерации, как в проекте «Японский прорыв NVIDIA», где модель учили на культурных особенностях.
  3. Переобучение на артефакты генерации. Модель может выучить стиль синтетических запросов, а не смысл. Симптом: отличные результаты на синтетических тестах, но провал на реальных. Лекарство: добавьте в обучение хотя бы 10-20% реальных размеченных данных (если есть). Или используйте аугментацию реальных запросов.

А что насчет железа? Можно ли ускорить процесс?

Да. Если у вас нет кластера из A100, есть варианты:

  • Используйте квантованную базовую модель. Например, pplx-embed от Perplexity показывает, как 4-битная квантизация ускоряет инференс в 3 раза с минимальной потерей качества. Для этапа hard negative mining это критично.
  • Локальные эмбеддинги на маленькой модели. В гайде про Qwen3-0.6B INT8 описано, как получить работающие эмбеддинги на ноутбуке. Но для доменной адаптации маленькие модели слабоваты.
  • Облако. Арендуйте инстанс с 4x H100 на Lambda Labs (партнерская ссылка) на 8 часов. Это обойдется около $60-80, но сэкономит дни.

И последний совет, который вы не найдете в документации NVIDIA. После обучения сделайте дополнительную тонкую настройку на реальных логах поиска. Возьмите 1000 запросов пользователей, для которых известны клики (что они выбрали из результатов). И дообучите модель на этих данных всего 1 эпоху. Это поднимет качество еще на 5-10%.

Синтетические данные — это не волшебная палочка. Это инструмент, который снимает первоначальный барьер. Дальше нужно итеративное улучшение. Но стартовать с нуля до работающей модели за день — теперь реальность. Главное — не бояться экспериментировать с генерацией и жестко тестировать на реальных сценариях.

Подписаться на канал