Знакът на КАГАМИ КАГАМИ
kagami.bg/academy · lesson · machine-readable viewUPDATED 2026-10-03
IDENTITY
module
GX10-04-84 · Local RAG with NeMo Retriever: search and cited answers
series
GX10 (local AI server class: NVIDIA GB10, e.g. ASUS Ascent GX10 / DGX Spark)
level
Advanced
duration
about 3 h (model downloads take extra time)
prerequisites
A GB10-class machine with DGX OS (Arm64), Docker with the NVIDIA runtime, an NGC account and API key, a local OpenAI-compatible LLM endpoint (for example from lessons 04-10 or 04-07), Python 3 with venv
trust_label
UPDATED 2026-10-03 (GB10 support, image tag, request formats and ports read in the NVIDIA NeMo Retriever Embedding and Reranking NIM documentation, the NVIDIA RAG Blueprint documentation and the Milvus Lite documentation on 2026-10-03) · NOT TESTED (no GB10 machine available during the check; no command or script was run; the reranking NIM image name and launch flags are not confirmed)
versions
Embedding NIM 2.3 (nvidia/nemotron-3-embed-1b, text-only, 2048 dimensions, 4096 tokens, GB10 listed with BF16) · reranking NIM model nvidia/llama-nemotron-rerank-vl-1b-v2 (GB10 listed, FP16, 8192 tokens; DGX Spark support from NIM version 2.3) · Milvus Lite via pymilvus (Ubuntu arm64 supported, FLAT index only)
language
human view: bg · english edition: /en/academy/gx10/ (same file name)
previous / next
GX10 series index / GX10 series index
PURPOSE

Build a minimal fully local retrieval-augmented generation pipeline on a GB10-class machine from the building blocks of NVIDIA's RAG Blueprint: an embedding NIM, a reranking NIM, an embedded vector store (Milvus Lite) and a local LLM behind an OpenAI-compatible endpoint; answer questions from your own text documents with numbered citations, or say the answer is not in the documents. The full NVIDIA blueprint itself is not run here: its documented minimum is three data-centre GPUs.

KEY CONCEPTS
COMMANDS / PATHS
CHECKLIST
NEXT MODULE

04-219 · What fits on GX10: the memory math (04-219_Kakvo_Tezhi_na_GX10.html) · 04-220 · Model versions reference (04-220_Versii_na_Modelite.html) · 04-10 · vLLM inference (04-10_vLLM_Inference.html) · series index: kagami.bg/academy/gx10/ · offer: Quick experiment (kagami.bg/stalbata/)

SOURCES
TAGS
gx10nvidia-gb10arm64ragnemo-retrieverembeddingsrerankingmilvus-litecitations
ОБНОВЕНО · 03.10.2026

Локален RAG на GX10: вектори, пренареждане и отговор с цитати от твоите документи

RAG значи първо намираме, после отговаряме: за всеки въпрос търсим най-подходящите откъси от твоите документи и караме модела да отговори само по тях, с номера на източниците. Тук го сглобяваме от градивните блокове на NVIDIA — модели на NeMo Retriever за вектори и пренареждане, векторна база и локален езиков модел — на машина от класа NVIDIA GB10, така че документите да не напускат машината.

⏱ 3–4 ч Напреднало GX10 NVIDIA GB10 · 128 GB обща памет NeMo Retriever · Milvus Lite · Python
NIM за вектори и пренареждане (ARM64)🔒 локално Milvus Lite · езиков модел🔒 локално NVIDIA NGC (само за изтегляне)🌐 глобален
🔄
ОБНОВЕНО · 03.10.2026 — какво
Урокът е преработен по текущите документи. Махнахме остарялото: образите llama-3.2-nv-embedqa-1b-v2 и llama-3.2-nv-rerankqa-1b-v2 с таг latest (текущите са nemotron-3-embed-1b и llama-nemotron-rerank-…); езиковия модел Llama 3.1 и бележката за оттегляне на Llama и „Muse“, която не можахме да сверим; закачените стари версии на Milvus, etcd и MinIO в Compose файл; данни за достъп по подразбиране в Compose файла; публикуването на портове към цялата мрежа и пускането на API на 0.0.0.0; изискването за „4 NIM-а на GX10 в един Compose“ — NVIDIA изисква за пълния си образец 3 видеокарти за центрове за данни (виж по-долу); таблицата с „вертикални модули“ и имената на клиенти и звена в нея. Добавихме: какво всъщност казва документацията на NVIDIA за пълния RAG Blueprint и защо не го пускаме като цяло; поддръжката на GB10 и командата за стартиране на NIM по документа; формата на заявките за вектори и за пренареждане; Milvus Lite (един файл вместо три услуги); скриптове за записване и за въпрос с цитати; затворени портове (само 127.0.0.1); раздел за лицензите.
⚠️
Какво не сме пускали сами
При проверката нямахме машина от класа GB10. Командите и скриптовете са сверени с документацията на NVIDIA и на Milvus, но не са пускани — затова няма етикет „ТЕСТВАНО“ и „ПРОВЕРЕНО“. Не сме мерили нито скорост, нито памет. Точното име на образа и флаговете за пренареждащия NIM не са потвърдени — вземи ги от каталога NGC. Лицензът на модела nemotron-3-embed-1b не сме го чели.

01Какво ще научиш

02Преди да започнеш

💡
Защо не пускаме целия образец на NVIDIA
Документацията на NVIDIA за RAG Blueprint (03.10.2026): езиковият модел по подразбиране — nemotron-3-super-120b-a12b — иска 2 видеокарти, а минимумът за Docker е 3 × H100, 3 × B200 или 3 × RTX PRO 6000; препоръчва Ubuntu 22.04 и поне 200 GB място. Една GB10 не го покрива. Образецът може да ползва и хоствани от NVIDIA адреси — но тогава текстът на документите ти излиза от машината, а целта на този урок е точно обратното. Затова вземаме градивните блокове, които NVIDIA изрично поддържа на GB10, и сглобяваме минималния конвейер сами.

03Стъпки

  1. Какво ще стои къде

    Три услуги и един файл. Защо отделни услуги за вектори и за пренареждане? Търсенето по вектори е бързо, но грубо; пренареждането е по-бавно и по-точно. Първото избира кандидатите, второто подрежда кратък списък.

    ЧастРоляАдрес
    NIM за векториПревръща текст във вектор от 2048 числа127.0.0.1:8001
    NIM за пренарежданеПодрежда кандидатите по истинска близост до въпроса127.0.0.1:8002
    Езиков моделПише отговора по подадените откъси127.0.0.1:8003 (пример)
    Milvus LiteПази вектори и откъси в един локален файлфайл rag_demo.db, без сървър
  2. Провери машината

    bash · на машината
    uname -m
    docker --version
    nvidia-smi
    df -h

    Очакваш aarch64, версия на Docker и таблица с видеокартата. Редът за памет може да казва „Not Supported“ — нормално е за GB10: паметта е обща за процесора и видеокартата.

  3. Ключ от NGC

    Създай API ключ в NGC (услуга „NGC Catalog“) и го сложи в променлива само за този терминал. Защо не във файл? Ключът отваря достъп до акаунта ти — не го пиши в скриптове, в Compose файлове и в Git.

    bash · на машината
    export NGC_API_KEY=<твоят-ключ>
    echo "$NGC_API_KEY" | docker login nvcr.io --username '$oauthtoken' --password-stdin

    Потребителското име е точно $oauthtoken (по документа на NVIDIA) — то казва, че влизаш с ключ. Някои модели искат и да приемеш условията им на страницата им в каталога NGC.

  4. NIM за вектори

    Документът на NVIDIA казва, че от версия 2.3 NIM поддържа DGX Spark с GB10, а образът е за Arm64 и Docker избира правилния сам. В таблицата за GB10 е изброен моделът nvidia/nemotron-3-embed-1b (BF16) — текстов, с вектори от 2048 числа и до 4096 токена. Командата е по документа, с две промени от нас: адресът е 127.0.0.1 (не към цялата мрежа) и ползваме NGC за изтегляне.

    bash · на машината
    export LOCAL_NIM_CACHE=~/.cache/nim
    mkdir -p "$LOCAL_NIM_CACHE/cache" "$LOCAL_NIM_CACHE/weights"
    
    docker run -it --rm --name=nemotron-3-embed-1b \
      --runtime=nvidia --gpus all --shm-size=16GB \
      -e NIM_ENGINE_MODEL_DOWNLOAD_PROVIDER=ngc \
      -e NGC_API_KEY \
      -v "$LOCAL_NIM_CACHE/cache:/opt/cache" \
      -v "$LOCAL_NIM_CACHE/weights:/model" \
      -u $(id -u) \
      -p 127.0.0.1:8001:8000 \
      nvcr.io/nim/nvidia/nemotron-3-embed-1b:2.3

    Първия път се теглят теглата на модела — изчакай. Остави прозореца отворен (или добави -d и махни -it, за да върви на заден план). Във втори прозорец провери:

    bash
    curl http://localhost:8001/v1/health/ready
    
    curl -X POST http://localhost:8001/v1/embeddings \
      -H 'Content-Type: application/json' \
      -d '{"input":["Какво е RAG?"],"model":"nvidia/nemotron-3-embed-1b","input_type":"query","modality":"text","embedding_type":"float","encoding_format":"float"}'

    Първата команда връща "ready":true, втората — вектор. Защо input_type? Моделът трябва да знае дали текстът е въпрос (query) или откъс от документ (passage) — при записване ползваме passage, при въпрос — query.

  5. NIM за пренареждане

    В таблицата на документа за GB10 е изброен nvidia/llama-nemotron-rerank-vl-1b-v2 (FP16, до 8192 токена). Документът отбелязва, че за най-добра работа на GB10 се задава NIM_ENGINE_COUNT=2. Пусни го по същия образец като предишния NIM, но на порт 8002 и с образа от каталога NGC:

    bash · на машината
    docker run -it --rm --name=rerank \
      --runtime=nvidia --gpus all --shm-size=16GB \
      -e NIM_ENGINE_MODEL_DOWNLOAD_PROVIDER=ngc \
      -e NGC_API_KEY \
      -v "$LOCAL_NIM_CACHE/cache:/opt/cache" \
      -v "$LOCAL_NIM_CACHE/weights:/model" \
      -u $(id -u) \
      -p 127.0.0.1:8002:8000 \
      <образ-на-пренареждащия-NIM-от-NGC>
    ⚠️
    Тук не сме сигурни
    Точното име и таг на образа, както и дали същите флагове важат за него, не сме ги потвърдили в документа за пренареждащия NIM — вземи ги от страницата на модела в каталога NGC и от неговия „Get Started“. Ако двата NIM-а не се събират заедно, пусни ги един след друг или виж урока „Какво тежи на GX10“.

    Провери го с малка заявка (форматът е от документа на NVIDIA):

    bash
    curl -X POST http://localhost:8002/v1/ranking \
      -H 'Content-Type: application/json' \
      -d '{"model":"nvidia/llama-nemotron-rerank-vl-1b-v2","query":{"text":"Колко дни отпуск имам?"},"passages":[{"text":"Платеният годишен отпуск е 20 работни дни."},{"text":"Офисът е на третия етаж."}],"truncate":"END"}'

    Отговорът е списък rankings с index (кой откъс е) и logit (оценка), подреден от най-добрия към най-слабия. logit е суров, ненормализиран резултат — важи само за сравнение между откъсите на една заявка. Максимумът е 512 откъса на заявка.

  6. Езиков модел

    Трябва ти локален модел зад OpenAI-съвместим адрес (/v1/chat/completions). Можеш да ползваш този от урока за vLLM или за Ollama; в скрипта по-долу адресът е http://localhost:8003/v1/chat/completions, а името на модела е <име-на-модела> — смени ги с твоите. Защо самостоятелен модел? Така можеш да смениш модела без да пипаш търсенето.

  7. Среда за Python и документи

    bash · в папка rag-demo
    mkdir -p rag-demo/docs && cd rag-demo
    python3 -m venv rag-env
    source rag-env/bin/activate
    pip install -U "pymilvus[milvus-lite]" requests
    
    cat > docs/primer.txt <<'EOF'
    Фирма "Пример" дава платен годишен отпуск от 20 работни дни.
    Заявлението за отпуск се подава поне 5 работни дни предварително.
    Работното време е от 9:00 до 17:30 с половин час обедна почивка.
    EOF

    Milvus Lite е част от пакета pymilvus: пази всичко в един локален файл и работи на Ubuntu за Arm64 (по документа на Milvus). Файлът primer.txt е измислен — слагай в docs/ свои текстови файлове.

  8. Запис на документите: ingest.py

    Скриптът дели всеки файл на откъси от около 200 думи с припокриване от 40 — припокриването пази смисъла на границите. Всеки откъс става вектор през NIM-а (като passage) и се записва с името на файла и номера си.

    python · ingest.py
    from pathlib import Path
    import requests
    from pymilvus import MilvusClient
    
    EMBED_URL = "http://localhost:8001/v1/embeddings"
    EMBED_MODEL = "nvidia/nemotron-3-embed-1b"
    DIM = 2048                 # родна размерност на модела (по документа на NIM)
    COLLECTION = "docs"
    
    client = MilvusClient("./rag_demo.db")    # Milvus Lite: един локален файл
    
    
    def embed(texts, kind):
        r = requests.post(EMBED_URL, timeout=120, json={
            "input": texts,
            "model": EMBED_MODEL,
            "input_type": kind,               # "passage" за документи, "query" за въпроси
            "modality": "text",
            "embedding_type": "float",
            "encoding_format": "float",
        })
        r.raise_for_status()
        return [d["embedding"] for d in r.json()["data"]]
    
    
    def chunks(text, size=200, overlap=40):
        words = text.split()
        for start in range(0, len(words), size - overlap):
            piece = " ".join(words[start:start + size])
            if piece:
                yield piece
    
    
    if not client.has_collection(COLLECTION):
        client.create_collection(collection_name=COLLECTION, dimension=DIM,
                                 metric_type="COSINE", auto_id=True)
    
    rows = []
    for path in sorted(Path("docs").glob("*.txt")):
        for n, piece in enumerate(chunks(path.read_text(encoding="utf-8"))):
            rows.append({"text": piece, "source": path.name, "chunk": n})
    
    for i in range(0, len(rows), 32):
        batch = rows[i:i + 32]
        vectors = embed([r["text"] for r in batch], "passage")
        for row, vec in zip(batch, vectors):
            row["vector"] = vec
        client.insert(COLLECTION, batch)
    
    print(len(rows), "откъса са записани")

    Пусни го: python ingest.py. Ако пуснеш скрипта втори път със същите файлове, откъсите се записват пак — за ново начало изтрий файла rag_demo.db. ⚠️ Скриптът не е пускан от нас.

  9. Въпрос с цитати: ask.py

    Конвейерът има четири хода: въпросът става вектор (като query) → търсим 20 кандидата → пренареждащият NIM оставя 5-те най-добри → езиковият модел отговаря по тях. Номерираме откъсите в подканата и искаме от модела да цитира [n]; списъка с източниците печатаме ние, не моделът.

    python · ask.py
    import sys
    import requests
    from pymilvus import MilvusClient
    
    EMBED_URL = "http://localhost:8001/v1/embeddings"
    EMBED_MODEL = "nvidia/nemotron-3-embed-1b"
    RERANK_URL = "http://localhost:8002/v1/ranking"
    RERANK_MODEL = "nvidia/llama-nemotron-rerank-vl-1b-v2"
    LLM_URL = "http://localhost:8003/v1/chat/completions"
    LLM_MODEL = "<име-на-модела>"
    COLLECTION = "docs"
    
    client = MilvusClient("./rag_demo.db")
    question = " ".join(sys.argv[1:])
    
    # 1. въпросът става вектор
    r = requests.post(EMBED_URL, timeout=120, json={
        "input": [question], "model": EMBED_MODEL, "input_type": "query",
        "modality": "text", "embedding_type": "float", "encoding_format": "float"})
    r.raise_for_status()
    query_vec = r.json()["data"][0]["embedding"]
    
    # 2. бързо търсене: 20 кандидата
    hits = client.search(COLLECTION, data=[query_vec], limit=20,
                         output_fields=["text", "source", "chunk"])[0]
    candidates = [h["entity"] for h in hits]
    if not candidates:
        sys.exit("Няма записани документи — пусни първо ingest.py")
    
    # 3. точно пренареждане: оставяме 5
    r = requests.post(RERANK_URL, timeout=120, json={
        "model": RERANK_MODEL, "query": {"text": question},
        "passages": [{"text": c["text"]} for c in candidates], "truncate": "END"})
    r.raise_for_status()
    order = [x["index"] for x in r.json()["rankings"]][:5]
    top = [candidates[i] for i in order]
    
    # 4. отговор само по контекста, с цитати
    context = "\n\n".join(f"[{n}] ({c['source']}, откъс {c['chunk']})\n{c['text']}"
                          for n, c in enumerate(top, 1))
    prompt = ("Отговори САМО въз основа на контекста по-долу. След всяко твърдение "
              "сложи номера на източника, например [1]. Ако отговорът не е в "
              "контекста, кажи: „Не намирам това в документите“.\n\n"
              f"КОНТЕКСТ:\n{context}\n\nВЪПРОС: {question}")
    r = requests.post(LLM_URL, timeout=300, json={
        "model": LLM_MODEL, "temperature": 0.1,
        "messages": [{"role": "user", "content": prompt}]})
    r.raise_for_status()
    print(r.json()["choices"][0]["message"]["content"])
    
    print("\nИзточници:")
    for n, c in enumerate(top, 1):
        print(f"[{n}] {c['source']} · откъс {c['chunk']}")

    Пробвай: python ask.py "Колко дни платен отпуск имам?", после въпрос, на който документите не отговарят — например python ask.py "Има ли фирмен автомобил за служителите?". Правилният отговор на втория е „Не намирам това в документите“. ⚠️ Скриптът не е пускан от нас; формите на отговорите (rankings, entity) са по документацията на NVIDIA и на Milvus.

    ✅
    Цитатът показва къде е гледал моделът, а не че отговорът е верен
    Винаги отвори посочения откъс и сравни. Моделът може да цитира [1] и пак да обърка смисъла. За важни решения човек проверява източника.
  10. Опитът: с пренареждане и без

    Най-добрият начин да видиш какво прави пренареждането: смени реда top = [candidates[i] for i in order] с top = candidates[:5] (само търсенето по вектори) и задай същите три въпроса. Запиши къде отговорите се различават. Колкото повече и по-сходни документи имаш, толкова по-голяма е разликата. ⚠️ Числа за точност не даваме — не сме ги мерили.

  11. Какво още има в пълния образец

    Документацията на NVIDIA изброява неща, които нашият минимален конвейер няма. Ето ги като посока за по-нататък — не като обещание, че вървят на една GB10.

    ВъзможностКакво прави (по документацията)
    Извличане от PDFОтделни NIM-ове за таблици, графики и OCR, за да се чете структурата на страниците
    Смесено търсенеПлътно (вектори) и рядко (по думи) търсене заедно; Milvus Lite поддържа разредени вектори, но те трябва да ги даде друг модел
    Разлагане на въпроса и филтри по метаданниСложен въпрос се разбива на по-прости; търсенето се стеснява по полета като дата или тип
    РефлексияДопълнителна проверка на отговора спрямо откъсите
    Предпазни правила (guardrails)По избор — филтри за безопасност на входа и изхода
    Оценка с RAGASСкриптове за измерване на качеството на отговорите

    Когато документите ти нараснат над малко, смени Milvus Lite с Milvus Standalone: клиентският код остава същият, сменя се само адресът. Milvus Lite ползва индекс FLAT и е за малки обеми (по документа на Milvus).

  12. Сигурност и лицензи

    • Всички портове са към 127.0.0.1. Не разчитай на вградена защита на NIM-а — не сме я проверявали; до него стигаш от машината или през SSH тунел.
    • Ключът от NGC стои само в променлива на терминала — не в скрипт, не в Git, не в чат.
    • Документите остават на машината само ако и трите услуги са локални. Ако пуснеш хоствани адреси на NVIDIA, текстът излиза навън.
    • Ако документите съдържат лични данни, локалната обработка помага, но не заменя правилата за защита на данните — виж и урока Контрол на достъпа и GDPR.
    • Лицензи (по README на RAG Blueprint, 03.10.2026): кодът на образеца е Apache-2.0; моделите се ползват по лицензите на NVIDIA за модели; моделите llama-nemotron-embed-1b-v2 и llama-nemotron-rerank-1b-v2 са по лиценза на общността на Llama 3.2. ⚠️ Лицензът на nemotron-3-embed-1b не сме го чели — прочети го в каталога NGC, преди да го вложиш в платен продукт.

04Проверка

Тест

1. Защо не пускаме целия RAG Blueprint на NVIDIA на една GB10?

2. Какво прави пренареждащият NIM?

3. Защо при записване ползваме input_type passage, а при въпрос — query?

4. Кога документите ти напускат машината?

05Какво следва

06Източници

  1. NVIDIA: NeMo Retriever Embedding NIM — Get Started 🌐 глобален — команда за стартиране, ключ от NGC, формат на заявката.
  2. NVIDIA: Embedding NIM — Support Matrix — поддръжка на GB10 от версия 2.3, модели и размерности.
  3. NVIDIA: Reranking NIM — Support Matrix · Use Reranking NIM — GB10, формат на /v1/ranking.
  4. NVIDIA: RAG Blueprint — минимални изисквания · документация — видеокарти, диск, възможности.
  5. NVIDIA RAG Blueprint (GitHub) · карта на образеца — компоненти и лицензи.
  6. Milvus Lite 🔒 локално — инсталиране, ограничения, платформи.