RAG-ассистент на CPU VPS: обработка 10k книг, архитектура, эмбеддинги 2026 | AiManual
AiManual Logo Ai / Manual.
27 Янв 2026 Гайд

Как построить экспертного RAG-ассистента на CPU VPS: оптимизация 10k книг, выбор архитектуры и модели эмбеддингов

Пошаговый гайд по созданию экспертного RAG-ассистента на CPU VPS с 16GB RAM. Выбор моделей эмбеддингов, настройка Qdrant с mmap, реранкинг BGE и оптимизация под

Зачем мучить 10 000 книг на слабом VPS? Потому что можно

Представьте себе задачу: у вас есть коллекция из 10 000 книг - техническая документация, художественная литература, научные труды. Вы хотите задавать вопросы по этому корпусу и получать точные, обоснованные ответы. Облачные API съедят ваш бюджет за неделю, а аренда GPU-сервера тянет на отдельную зарплату. Остается вариант, который все обходят стороной: дешевый CPU VPS с 4-8 ядрами и 16 гигабайтами оперативки.

Звучит как издевательство. Но именно здесь начинается настоящая инженерия. Не бросать железо на задачу, а заставить задачу подчиниться железу. Если вы думаете, что RAG - это только про большие модели и терабайты памяти, вы ошибаетесь. Это про эффективность. Про то, как выжать из каждого мегагерца и каждого мегабайта максимум.

Главный миф: для обработки 10k книг нужен GPU. Неправда. Эмбеддинги и поиск отлично работают на CPU. Проблема не в вычислениях, а в организации данных и памяти. 16 ГБ ОЗУ - это много, если не тратить их впустую.

Архитектура, которая не сломается под нагрузкой

Классический RAG развалится на таком железе. Загрузите все эмбеддинги в память - и 16 ГБ закончатся после первой тысячи книг. Используйте тяжелую модель для эмбеддингов - запрос будет обрабатываться 10 секунд. Забудьте про re-ranking - точность упадет на 30-40%.

Нужна система, где каждый компонент выбран за его эффективность, а не просто потому, что он популярен на GitHub.

Компонент Наш выбор (2026) Почему именно он Потребление RAM
Модель эмбеддингов BGE-M3 (light версия) Поддержка 8192 токена, мультиязычность, оптимизирована для CPU. all-MiniLM-L6-v2 уже устарела для таких объемов. ~500 МБ
Векторная БД Qdrant 1.9.x с mmap Единственная, которая умеет хранить вектора на диске с mmap, почти не занимая RAM. Альтернативы (FAISS, Chroma) сожрут всю память. ~50-100 МБ (зависит от запросов)
Реранкер BGE-Reranker-v3-Mini Специально облегченная версия 2025 года. Точность падает на 2-3% против большой, но скорость в 5 раз выше на CPU. ~300 МБ
LLM для ответов Llama 3.2 3B (4-битная квантизация) Может работать в 4 ГБ ОЗУ, выдает связные ответы. Для экспертного ассистента не нужна 70B модель, нужна точная информация из контекста. ~4 ГБ

Общий подсчет: 500 МБ + 100 МБ + 300 МБ + 4 ГБ = примерно 5 ГБ. Остается 11 ГБ на операционную систему, кэши и буферы. Это реально. Если вы возьмете VPS с 16 ГБ ОЗУ у провайдера вроде Timeweb Cloud, этого хватит с запасом.

1 Подготовка данных: как резать 10 000 книг без потери смысла

Самая скучная и самая важная часть. Если нарезать текст на равные куски по 256 токенов, вы потеряете контекст. Глава книги, разбитая посередине, сделает эмбеддинги бесполезными.

Правило: чанковать по структурным элементам. Главы, разделы, подразделы. Если структуры нет (сплошной текст), используйте алгоритмическое чанкование с перекрытием.

# Пример чанкинга с помощью актуальной библиотеки langchain-text-splitters 2026
from langchain_text_splitters import RecursiveCharacterTextSplitter

# Не делайте так (потеря контекста):
# splitter = RecursiveCharacterTextSplitter(chunk_size=256, chunk_overlap=50)

# Делайте так (сохраняем структуру):
splitter = RecursiveCharacterTextSplitter(
    separators=["\n\n\n", "\n\n", ". ", "? ", "! ", " ", ""],  # Приоритет разделителей
    chunk_size=1024,  # Большие чанки для книг
    chunk_overlap=200,  # Значительное перекрытие
    length_function=len,
    is_separator_regex=False,
)

chunks = splitter.split_text(book_text)
# Каждому чанку добавьте метаданные: название книги, автор, глава, страница
💡
Ошибка новичков: хранить только текст. Обязательно добавляйте метаданные (book_id, chapter, page) в каждый чанк. Потом это спасет при фильтрации запросов. "Найди информацию о квантовой физике только в учебниках 2020-2025 годов" - без метаданных это не сделать.

2 Индексирование: как не сжечь CPU и не ждать неделю

10 000 книг - это примерно 5-6 миллионов чанков. Создание эмбеддингов для каждого - задача на дни, если делать в один поток.

Решение: батчинг и асинхронность. Модель BGE-M3 на CPU обрабатывает примерно 100-200 чанков в секунду (зависит от длины). Вам нужно распараллелить процесс на все ядра VPS.

from sentence_transformers import SentenceTransformer
import numpy as np
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
import asyncio
from concurrent.futures import ProcessPoolExecutor

model = SentenceTransformer('BAAI/bge-m3-light', device='cpu')

# Создаем батчи
batch_size = 128  # Не больше! Иначе упретесь в память.
chunks = [...]  # Ваш список чанков

points = []
for i in range(0, len(chunks), batch_size):
    batch = chunks[i:i + batch_size]
    # Кодируем батч
    embeddings = model.encode(batch, normalize_embeddings=True, show_progress_bar=False)
    
    for j, (chunk, embedding) in enumerate(zip(batch, embeddings)):
        points.append(
            PointStruct(
                id=i + j,
                vector=embedding.tolist(),
                payload={
                    "text": chunk.text,
                    "book_id": chunk.metadata["book_id"],
                    "title": chunk.metadata["title"],
                    "author": chunk.metadata["author"],
                    "chapter": chunk.metadata.get("chapter", ""),
                }
            )
        )
    # Каждые 10 000 точек сбрасываем в Qdrant, чтобы не держать в памяти
    if len(points) > 10000:
        client.upsert(collection_name="books", points=points)
        points = []
        print(f"Indexed {i + batch_size} chunks")

Это займет время. На 8-ядерном VPS процесс индексирования 5 миллионов чанков растянется на 10-15 часов. Смиритесь. Или используйте гибридный подход: построение индекса на GPU и обслуживание на CPU, если есть временный доступ к мощной машине.

3 Настройка Qdrant: магия mmap и как не дать БД съесть всю память

Qdrant с настройками по умолчанию загрузит все вектора в оперативку. Для 5 миллионов векторов по 768 измерений это примерно 15 ГБ. Прощай, 16 ГБ RAM.

Секрет в режиме mmap. Он позволяет Qdrant читать вектора прямо с диска, почти не используя RAM. Скорость падает, но для RAG, где запросов в секунду не тысячи, это приемлемо.

# config.yaml для Qdrant 1.9.x
storage:
  # Включаем mmap для векторов
  vectors:
    mmap: true
  # Кэш эмбеддингов в памяти для часто запрашиваемых данных (опционально)
  cache:
    size: 1000  # Количество векторов в кэше

service:
  # Лимит памяти для сервиса
  memory_limit: 2048  # Не больше 2 ГБ для самого Qdrant

performance:
  # Увеличиваем количество потоков для поиска
  max_search_threads: 4

Запускаем Qdrant:

docker run -d \
  -p 6333:6333 \
  -v ./qdrant_storage:/qdrant/storage \
  -v ./config.yaml:/qdrant/config/production.yaml \
  qdrant/qdrant:latest

Важно: mmap работает хорошо только с быстрым диском (SSD NVMe). Если ваш VPS использует HDD, поиск будет мучительно медленным. При выборе VPS обращайте внимание на тип диска. На Timeweb Cloud можно выбрать конфигурации с NVMe.

4 Сборка пайплайна запроса: реранкинг не для бедных

Стандартный пайплайн: запрос -> эмбеддинг -> поиск по векторам -> топ-10 результатов -> реранкинг -> топ-3 -> подача в LLM. На CPU каждый этап добавляет задержку. Наша цель - уложиться в 3-5 секунд на ответ.

Хитрость в том, чтобы не отправлять в реранкер все 10 результатов. Сначала делаем гибридный поиск: векторный + лексический (BM25). Это повышает recall. Затем реранкеру отдаем только топ-7. Подробнее про гибридный поиск я писал в отдельном гайде.

from qdrant_client import QdrantClient
from qdrant_client.models import Filter, FieldCondition, MatchValue, SearchRequest
from transformers import AutoModelForSequenceClassification, AutoTokenizer
import torch
import numpy as np

client = QdrantClient(host="localhost", port=6333)

# Реранкер
reranker_tokenizer = AutoTokenizer.from_pretrained("BAAI/bge-reranker-v3-mini")
reranker_model = AutoModelForSequenceClassification.from_pretrained("BAAI/bge-reranker-v3-mini")
reranker_model.eval()

def hybrid_search(query, limit=10):
    # 1. Векторный поиск
    query_embedding = embedder.encode(query, normalize_embeddings=True)
    vector_results = client.search(
        collection_name="books",
        query_vector=query_embedding,
        limit=limit * 2,  # Берем больше для гибридности
        with_payload=True
    )
    
    # 2. Лексический поиск (через Qdrant, если настроен, или через отдельный индекс BM25)
    # Для простоты опустим, но это критически важно для точности
    
    # 3. Объединение и ранжирование (простейший способ)
    combined = vector_results  # Здесь должна быть реальная логика слияния
    return combined[:limit]  # Возвращаем топ-10 для реранкинга

def rerank(query, passages):
    # passages - список текстов чанков
    pairs = [[query, passage] for passage in passages]
    with torch.no_grad():
        inputs = reranker_tokenizer(pairs, padding=True, truncation=True, return_tensors='pt', max_length=512)
        scores = reranker_model(**inputs, return_dict=True).logits.view(-1,).float()
    
    # Сортируем по убыванию score
    ranked_indices = np.argsort(scores.numpy())[::-1]
    return [passages[i] for i in ranked_indices[:3]]  # Топ-3 для LLM

Реранкер - узкое место. Модель v3-Mini быстрая, но все равно добавляет 0.5-1 секунду. Если это слишком много, можно увеличить количество исходных результатов поиска до 20, а в реранкер отдавать только топ-5 по векторному сходству. Теряете в точности, выигрываете в скорости.

Ошибки, которые убьют вашу систему

  • Игнорирование метаданных. Без фильтрации по книге, автору, году, каждый запрос будет искать по всем 10k книг. Это медленно и неточно. Всегда добавляйте фильтры в поиск, если пользователь уточняет.
  • Чанкование по таймеру или фиксированному размеру. Вы получите обрывы предложений и потерянные смыслы. Инвестируйте время в парсинг структуры книг (оглавление, разделы).
  • Запуск всего в одном процессе. Если ваше приложение - один скрипт, который делает эмбеддинг, поиск, реранкинг и генерацию, он будет падать под нагрузкой. Разделите на микросервисы или хотя бы отдельные процессы с очередями.
  • Отказ от кэширования. Повторяющиеся запросы должны кэшироваться. Кэшируйте эмбеддинги запросов и топовые результаты. Redis на том же VPS съест память, но файловый кэш на SSD может спасти.

Что в итоге? Система, которая работает

Вы получите ассистента, который отвечает на вопросы по 10 000 книг за 3-5 секунд, потребляя 10-12 ГБ ОЗУ на пике. Это не магия, это инженерия. Каждый компонент выбран потому, что он решает конкретную проблему в условиях ограничений.

Самое сложное - не написать код, а принять решения. Использовать ли гибридный поиск? Какой размер чанка оптимален для книг? Стоит ли вообще использовать реранкинг на CPU? Ответы зависят от ваших данных и запросов. Начните с простого пайплайна (эмбеддинг -> поиск -> LLM), измеряйте точность, а затем добавляйте сложность.

И помните: RAG на CPU - это не удел бедных. Это демонстрация того, что эффективность важнее мощности. Когда все вокруг бросают на задачи GPU за $10 000, ваша система на $20 VPS будет делать то же самое, просто немного медленнее. А иногда - и точнее, потому что вы были вынуждены думать над каждой деталью.

💡
Прогноз на 2026-2027: модели эмбеддингов станут еще легче и точнее, а векторные БД научатся эффективнее работать с диском. Возможно, необходимость в реранкинге отпадет, потому что поиск будет сразу выдавать идеально релевантные чанки. Но архитектурные принципы останутся: разделение ответственности, эффективное использование памяти и понимание своих данных.