Локален RAG на GX10: вектори, пренареждане и отговор с цитати от твоите документи
RAG значи първо намираме, после отговаряме: за всеки въпрос търсим най-подходящите откъси от твоите документи и караме модела да отговори само по тях, с номера на източниците. Тук го сглобяваме от градивните блокове на NVIDIA — модели на NeMo Retriever за вектори и пренареждане, векторна база и локален езиков модел — на машина от класа NVIDIA GB10, така че документите да не напускат машината.
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); раздел за лицензите.
nemotron-3-embed-1b не сме го чели.01Какво ще научиш
- Как работи RAG и защо търсенето е на два етапа: бързо търсене по вектори, после точно пренареждане.
- Какво казва документацията на NVIDIA за пълния RAG Blueprint и кое от него се побира на една GB10.
- Как да стартираш NIM за вектори и NIM за пренареждане на GB10 и да ги извикаш.
- Как да запишеш текстови документи в локална векторна база (Milvus Lite).
- Как да зададеш въпрос и да получиш отговор с цитати — или отговор, че го няма в документите.
- Какво още има в пълния образец и къде са границите на този урок.
02Преди да започнеш
- Машина от класа NVIDIA GB10 (например ASUS Ascent GX10 или DGX Spark) с DGX OS.
- Docker с NVIDIA среда за изпълнение — на DGX Spark е предварително инсталиран (виж урока n8n на GX10 за проверка на машината).
- Безплатен акаунт в NVIDIA NGC и API ключ от него — иска се за теглене на образите и моделите.
- Локален езиков модел зад OpenAI-съвместим адрес — например от урока vLLM на GX10 или Ollama. В примерите е
<име-на-модела>. - Python 3 с
venvи малко текстови файлове (.txt) — свои или измислени. - Място на диска — провери с
df -h. Пълният образец на NVIDIA иска поне 200 GB; нашият е много по-малък, но колко точно не сме мерили.
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Стъпки
-
Какво ще стои къде
Три услуги и един файл. Защо отделни услуги за вектори и за пренареждане? Търсенето по вектори е бързо, но грубо; пренареждането е по-бавно и по-точно. Първото избира кандидатите, второто подрежда кратък списък.
Част Роля Адрес NIM за вектори Превръща текст във вектор от 2048 числа 127.0.0.1:8001NIM за пренареждане Подрежда кандидатите по истинска близост до въпроса 127.0.0.1:8002Езиков модел Пише отговора по подадените откъси 127.0.0.1:8003(пример)Milvus Lite Пази вектори и откъси в един локален файл файл rag_demo.db, без сървър -
Провери машината
bash · на машинатаuname -m docker --version nvidia-smi df -hОчакваш
aarch64, версия на Docker и таблица с видеокартата. Редът за памет може да казва „Not Supported“ — нормално е за GB10: паметта е обща за процесора и видеокартата. -
Ключ от 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. -
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, за да върви на заден план). Във втори прозорец провери:bashcurl 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. -
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):
bashcurl -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 откъса на заявка. -
Езиков модел
Трябва ти локален модел зад OpenAI-съвместим адрес (
/v1/chat/completions). Можеш да ползваш този от урока за vLLM или за Ollama; в скрипта по-долу адресът еhttp://localhost:8003/v1/chat/completions, а името на модела е<име-на-модела>— смени ги с твоите. Защо самостоятелен модел? Така можеш да смениш модела без да пипаш търсенето. -
Среда за Python и документи
bash · в папка rag-demomkdir -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 с половин час обедна почивка. EOFMilvus Lite е част от пакета
pymilvus: пази всичко в един локален файл и работи на Ubuntu за Arm64 (по документа на Milvus). Файлътprimer.txtе измислен — слагай вdocs/свои текстови файлове. -
Запис на документите: ingest.py
Скриптът дели всеки файл на откъси от около 200 думи с припокриване от 40 — припокриването пази смисъла на границите. Всеки откъс става вектор през NIM-а (като
passage) и се записва с името на файла и номера си.python · ingest.pyfrom 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. ⚠️ Скриптът не е пускан от нас. -
Въпрос с цитати: ask.py
Конвейерът има четири хода: въпросът става вектор (като
query) → търсим 20 кандидата → пренареждащият NIM оставя 5-те най-добри → езиковият модел отговаря по тях. Номерираме откъсите в подканата и искаме от модела да цитира[n]; списъка с източниците печатаме ние, не моделът.python · ask.pyimport 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] и пак да обърка смисъла. За важни решения човек проверява източника. -
Опитът: с пренареждане и без
Най-добрият начин да видиш какво прави пренареждането: смени реда
top = [candidates[i] for i in order]сtop = candidates[:5](само търсенето по вектори) и задай същите три въпроса. Запиши къде отговорите се различават. Колкото повече и по-сходни документи имаш, толкова по-голяма е разликата. ⚠️ Числа за точност не даваме — не сме ги мерили. -
Какво още има в пълния образец
Документацията на NVIDIA изброява неща, които нашият минимален конвейер няма. Ето ги като посока за по-нататък — не като обещание, че вървят на една GB10.
Възможност Какво прави (по документацията) Извличане от PDF Отделни NIM-ове за таблици, графики и OCR, за да се чете структурата на страниците Смесено търсене Плътно (вектори) и рядко (по думи) търсене заедно; Milvus Lite поддържа разредени вектори, но те трябва да ги даде друг модел Разлагане на въпроса и филтри по метаданни Сложен въпрос се разбива на по-прости; търсенето се стеснява по полета като дата или тип Рефлексия Допълнителна проверка на отговора спрямо откъсите Предпазни правила (guardrails) По избор — филтри за безопасност на входа и изхода Оценка с RAGAS Скриптове за измерване на качеството на отговорите Когато документите ти нараснат над малко, смени Milvus Lite с Milvus Standalone: клиентският код остава същият, сменя се само адресът. Milvus Lite ползва индекс FLAT и е за малки обеми (по документа на Milvus).
-
Сигурност и лицензи
- Всички портове са към
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Проверка
uname -mдаваaarch64иnvidia-smiпоказва видеокартата.- NIM-ът за вектори отговаря на
/v1/health/readyи връща вектор от 2048 числа. - NIM-ът за пренареждане връща
rankingsза малката заявка. - Езиковият модел отговаря на тестова заявка.
ingest.pyпечата броя на записаните откъси.ask.pyвръща отговор с[n]и списък с източници, а за въпрос извън документите казва, че не намира отговор.- Сравнил си резултата със и без пренареждане за поне три въпроса.
- Портовете са само на
127.0.0.1, а ключът от NGC не е записан никъде.
Тест
1. Защо не пускаме целия RAG Blueprint на NVIDIA на една GB10?
2. Какво прави пренареждащият NIM?
3. Защо при записване ползваме input_type passage, а при въпрос — query?
4. Кога документите ти напускат машината?
05Какво следва
06Източници
- NVIDIA: NeMo Retriever Embedding NIM — Get Started 🌐 глобален — команда за стартиране, ключ от NGC, формат на заявката.
- NVIDIA: Embedding NIM — Support Matrix — поддръжка на GB10 от версия 2.3, модели и размерности.
- NVIDIA: Reranking NIM — Support Matrix · Use Reranking NIM — GB10, формат на
/v1/ranking. - NVIDIA: RAG Blueprint — минимални изисквания · документация — видеокарти, диск, възможности.
- NVIDIA RAG Blueprint (GitHub) · карта на образеца — компоненти и лицензи.
- Milvus Lite 🔒 локално — инсталиране, ограничения, платформи.