Nemotron retrieval on one RTX 5090: 4.9M-passage MIRACL zh, 7.0M ja, and where embedding and reranking NIMs top out

Hi all,

Vol.1 to Vol.3 of this series covered the generation side on one RTX 5090: Llama 3.1 8B and Nemotron 3 Nano as NIMs, and the models you can put behind a NeMo Guardrails rail. Vol.4 is the retrieval side, the part of a RAG pipeline that decides what the model gets to read: the Nemotron embedding NIMs and the Llama Nemotron reranking NIMs, on public data sets, with every passage of the corpus instead of a reduced benchmark corpus, and with the serving rate measured the way NVIDIA’s own load tool measures it. It is written as the five decisions these numbers inform.

Everything here is one model on the card at a time, exact search on the CPU (no vector database, so the index is not a second variable), and public data only: BEIR SciFact and HotpotQA, and MIRACL Chinese and Japanese.

TL;DR: five things to decide before you deploy retrieval on one card

  • Expect less than the benchmark on a real corpus, especially in Chinese. nemotron-3-embed-1b (NIM 2.2.2, fp16) scored nDCG@10 0.662 on the reduced MIRACL zh corpus (81,309 passages) and 0.649 on the whole one (4,934,368); Recall@100 fell from 0.988 to 0.964. On Japanese the drop was small: 0.744 on 185,319 passages, 0.741 on 6,953,614.
  • Rerank the top 100. llama-nemotron-rerank-1b-v2 lifted nDCG@10 from 0.649 to 0.683 on MIRACL zh, from 0.741 to 0.771 on ja, and from 0.801 to 0.865 on BEIR HotpotQA (5,233,329 passages, 7,405 queries). The 500m reranker added less (+0.020 zh, +0.011 ja) and served 1.6 times the pairs per second at one request at a time.
  • Size the reranker, not the embedder. With 100 passages per query, the 1b reranker topped out at about 10 queries per second on this card and the 500m one at about 19; embedding one query topped out at 384 requests per second for nemotron-3-embed-1b. On this card, most embedding NIMs stopped getting faster at 2 concurrent requests and the rerankers at 8–16; past that point more concurrency only adds waiting. These are each NIM alone on the card; two NIMs sharing one card will do less.
  • Store the vectors as fp16. Every value this NIM returns survives the round trip to 16-bit floats, so the three whole-corpus stores went from 140 GB to 70 GB with identical rankings. Stored as 8-bit integers, nDCG@10 and Recall@100 showed no loss on the three corpora, but the top 10 came back in the same order for only 33–58% of the queries.
  • The NIM gives the quality of the open weights. On the reduced MIRACL task, the NIM of nemotron-3-embed-1b and the open weights differ by 0.0011 (zh) and 0.0001 (ja) nDCG@10. The public MTEB value for ja (0.742) is reproduced within 0.002; the public zh value (0.642) is not reproduced by our harness (0.663) or by MTEB itself run here (0.663), and we do not know why.

Two nights repeated 19 levels within 2.1%, and the load tool, checked against a server that answers instantly, went at least 2.99 times faster than any NIM it measured.

nDCG@10 and Recall@100 from pytrec_eval, exact cosine search over every passage. Rates are server-side, measured with AIPerf in a client container on the NIM’s Docker network, closed loop.

One RTX 5090 · one retrieval NIM on the card at a time · BEIR SciFact and HotpotQA, MIRACL zh and ja · exact search · AIPerf 0.11

The arms

NIM Image Precision on this card Used for
nemotron-3-embed-1b 2.2.2 fp16 quality on the reduced and the whole corpora; concurrency
llama-nemotron-embed-1b-v2 1.13.0 fp8, chosen by the image (fp16 pinned as a check) quality, concurrency
llama-nemotron-embed-300m-v2 1.13.0 fp8, chosen by the image quality, concurrency
llama-nemotron-embed-vl-1b-v2 2.3.0 chosen by the image quality, concurrency
llama-nemotron-rerank-1b-v2 1.10.0 chosen by the image reranking the top 100 of the whole corpora; concurrency
llama-nemotron-rerank-500m-v2 1.10.0 chosen by the image the same
reference open weights of nemotron-3-embed-1b bf16 does our harness ask the model the right way?

nemotron-3-embed-1b is listed with an NVFP4 profile for the RTX PRO 6000; on this RTX 5090 that profile did not start, so it ran in fp16. The NVIDIA RAG Blueprint (v2.6.2) uses llama-nemotron-embed-vl-1b-v2 image 1.12.0 by default; on this card under Docker Desktop that image reported ready and then answered its first request with HTTP 500, while the same model’s 2.3.0 image served normally, so the tables use 2.3.0 (an issue we have not reproduced elsewhere; this card is not on the verified list for these NIMs).

Results: the whole corpus, and the reranker on top

nDCG@10 (bar) and Recall@100 (dash) for one encoder, nemotron-3-embed-1b: the reduced corpus where one exists, every passage of the corpus, and the same results after each of two rerankers (top 100).

Corpus Passages Queries nDCG@10 Recall@100 after 1b reranker after 500m reranker
MIRACL zh, reduced 81,309 393 0.662 0.988 — —
MIRACL zh, whole 4,934,368 393 0.649 0.964 0.683 0.669
MIRACL ja, reduced 185,319 860 0.744 0.992 — —
MIRACL ja, whole 6,953,614 860 0.741 0.984 0.771 0.752
BEIR HotpotQA, whole 5,233,329 7,405 0.801 0.916 0.865 not run

Reranking reorders the first 100 results, so Recall@100 is the same before and after. One call with 100 passages took a median of 143 ms with the 1b reranker and 85 ms with the 500m one on MIRACL zh, one call at a time.

The four embedding NIMs on the reduced sets (nDCG@10):

NIM SciFact MIRACL zh, reduced MIRACL ja, reduced
nemotron-3-embed-1b 2.2.2 0.752 0.662 0.744
llama-nemotron-embed-vl-1b-v2 2.3.0 0.767 0.648 0.719
llama-nemotron-embed-1b-v2 1.13.0 0.775 0.645 0.716
llama-nemotron-embed-300m-v2 1.13.0 0.713 0.593 0.676

Pinning llama-nemotron-embed-1b-v2 to fp16 instead of the fp8 the image chooses moved nDCG@10 by −0.004 to +0.0004 on the three sets.

How many requests at once

The full curves for all six NIMs are in the first reply below (two images per post on a new account). Requests per second at one request at a time, the highest rate, and the level where the rate stopped rising (95% of the highest):

NIM Request One at a time Highest Stops rising at
nemotron-3-embed-1b 1 query 269 req/s 384 2
〃 64 passages 252 passages/s 272 2
llama-nemotron-embed-vl-1b-v2 1 query 293 req/s 419 2
llama-nemotron-embed-1b-v2 64 passages 594 passages/s 796 2
llama-nemotron-embed-300m-v2 1 query 170 req/s 417 8
〃 64 passages 1,073 passages/s 2,138 4
llama-nemotron-rerank-1b-v2 1 query, 100 passages 678 pairs/s 983 16
llama-nemotron-rerank-500m-v2 1 query, 100 passages 1,113 pairs/s 1,913 8

For the query-shaped requests the p99 stayed under 100 ms up to 32 concurrent requests for nemotron-3-embed-1b; past the level where the rate stops rising, latency grows in step with concurrency and the rate stays flat.

Run it

An embedding NIM and a reranking NIM, one at a time on a 32 GB card. These are the commands we ran, with our host paths replaced; /model holds weights that this NIM fetched beforehand in its download-only mode, and we did not test starting it without that mount:

docker run -d --name nemotron-3-embed-1b --gpus all -p 8000:8000 --shm-size=16GB \
  -e NGC_API_KEY -e NIM_ENGINE_MODEL_DOWNLOAD_PROVIDER=ngc -e NIM_ENGINE_PRECISION=fp16 \
  -v /path/to/weights:/model -v /path/to/nim-cache:/opt/cache \
  nvcr.io/nim/nvidia/nemotron-3-embed-1b:2.2.2
docker run -d --name llama-nemotron-rerank-1b-v2 --gpus all -p 8000:8000 --shm-size=16GB \
  -e NGC_API_KEY -v /path/to/nim-cache:/opt/nim/.cache \
  nvcr.io/nim/nvidia/llama-nemotron-rerank-1b-v2:1.10.0

One level of the concurrency sweep (one query per request, 16 concurrent). We ran AIPerf 0.11.0 in a container on the NIM’s Docker network; run directly on this Windows host it hung at the end of a run:

aiperf profile --model nvidia/nemotron-3-embed-1b --url http://<nim-host>:8000 \
  --endpoint-type raw --custom-endpoint /v1/embeddings \
  --custom-dataset-type inputs-json --input-file /path/to/emb_query.inputs.json \
  --concurrency 16 --benchmark-duration 40 --warmup-request-count 32 \
  --tokenizer builtin --use-server-token-count --ui none \
  --artifact-dir /path/to/artifacts --no-gpu-telemetry --no-server-metrics

Five things that decide what you get

  1. Score on the corpus you will serve. The reduced benchmark corpus keeps each query’s judged passages and the hard negatives retrieved for it; the whole corpus adds millions of passages that can still crowd the top 100. On MIRACL zh that cost 0.013 nDCG@10 and 0.024 Recall@100.
  2. Rerank the top 100. On these three whole corpora the 1b reranker added 0.030 to 0.064 nDCG@10 for a median of 103–154 ms per query at one request at a time; the 500m one gave 37–60% of that gain in about 60% of the time.
  3. Budget the reranker first, then add instances, not concurrency. At 100 passages per query the reranker sets the ceiling of the retrieval stage (about 10 or 19 queries per second here), far below the embedder. Past each NIM’s saturation level, sending more requests only makes each one wait.
  4. Store fp16. Check first that the round trip is exact for your encoder; for this one it was, for every value of 17 million vectors. If you go to int8, check the ranking, not only nDCG.
  5. Send one real request before you trust “ready”. On this card, the Blueprint’s default embedding image reported ready and failed its first request; the newer image of the same model did not.

A few honest notes

  • One RTX 5090 on Docker Desktop / WSL2, sessions from 4 to 13 October; the driver moved from 591.86 to 617.42 over that time, and every cell records the driver it ran on. The card is not on NIM’s verified-GPU list for these models.
  • Two kinds of rate. Every rate above is server-side, from AIPerf. Our own single-connection Python client, which also parses each 2,048-dimension response, measured up to 26% less on the embedding NIMs; those client numbers are in the repo and labelled end to end.
  • The load tool has a ceiling too. Against a server that answers instantly with responses of the same size, AIPerf on this host went at least 2.99 times faster than the fastest NIM at every level, so the plateaus above are the NIMs’. That ceiling itself varied by up to 58% between two runs.
  • The zh reference is not reproduced. Our harness and MTEB itself, run here on the open weights, agree with each other (0.6631 and 0.6625) and not with the published 0.6424. Nothing was tuned to make it fit. HotpotQA and the whole MIRACL corpora have no published value for this model; those numbers are reported as measured.
  • Exact search, on purpose. An approximate index would add its own recall loss; that belongs to the next volume, which runs the whole Blueprint.

Everything is in the repo

Pre-registrations (frozen before the first container of each session), per-query scores, the run logs, both concurrency nights side by side, the tool-ceiling cell, the figures with their point tables, and a claim-to-evidence table for every number in this post are in the Vol.4 README of NV-benchmark. This is the fourth post in the series; Vol.3 covers the models behind a NeMo Guardrails rail, Vol.2 Nemotron 3 Nano, Vol.1 Llama 3.1 8B with NIM.

None of this says what retrieval does for a given application; that depends on the corpus you serve, which is why the whole-corpus numbers are here. If you run these NIMs on an RTX PRO 6000 or a DGX Spark, we’d like to see where your curves flatten.

Throughput (queries, passages or pairs per second) against p99 latency for each retrieval NIM, closed loop, 1 to 64 concurrent requests, one NIM alone on the card. Three panels: one query per request, 64 passages per request, one query with 100 passages to rerank. llama-nemotron-embed-300m-v2 was swept on the second night, the others on the first.

The second night repeated the first level and the saturation level of the other five NIMs in fresh containers: 19 levels, all within 2.1% of the first night, 0 errors. These repeats are not drawn on the figure; its hollow markers are the two highest levels run again in a second fresh container on the same night. Every level of every NIM, with p50, p90 and p99, is in the concurrency-sweep README.