How to Run NVIDIA Dory 1.0 4B with llama.cpp: A Loop-by-Loop Guide
Overview
In this tutorial, we work with Dory 1.0 4B, a small reasoning model from NVIDIA's Nemotron Labs. Its middle layers form a shared block that can be applied once, twice, three, or four times per token. Each additional pass โ or loop โ spends more computation per token without adding a single parameter, so a single checkpoint can run at four different compute budgets with no retraining. This is the same looped-architecture reasoning trend that has gained traction across small-model releases heading into 2026, where adaptive compute per token is used to trade latency for accuracy on demand.
We will:
- Build the llama.cpp branch pinned by NVIDIA's model card.
- Download the 3.7 GB GGUF and read the architecture directly from the file to predict the parameter and cache-memory cost of each loop.
- Validate those predictions against the running server.
- Measure decode speed at every loop count.
- Test whether the cheaper last-loop cache changes the model's predictions.
- Run a paired GSM8K evaluation to observe how accuracy, answer length, and time per solved question shift as loops increase.
- Accuracy โ fraction of correctly solved questions.
- Answer length โ average tokens generated per answer.
- Time per solved question โ wall-clock time divided by the number of correct answers.
- One checkpoint, four compute budgets. Dory 1.0 4B runs the same weights at 1โ4 loops, so you can dial reasoning depth per token without retraining or swapping models.
- Loops cost compute, not memory. Parameter count stays fixed; only compute and (optionally) KV-cache scale with loop count.
- Verify the build and model card first. The pinned llama.cpp commit is required โ release builds may fail to load the GGUF.
- The last-loop cache is worth testing. If it doesn't change greedy outputs on your workloads, it's free memory savings.
- Benchmark with paired evaluations. GSM8K with fixed seeds gives you the accuracy-per-loop curve needed to pick the right setting for your application.
Step 0: Setup and Helpers
Start with the scaffolding used throughout the guide: imports, a results registry for the summary table, a claim-tracking list, and a section banner helper.
import os
import re
import sys
import json
import time
import math
import random
import signal
import shutil
import hashlib
import platform
import statistics
import traceback
import subprocess
import collections
import urllib.request
from pathlib import Path
RESULTS = {} # step -> one-line result, for the summary table
CLAIMS = [] # (step, claim, held)
CURRENT = ["1"]
def banner(title):
print("\n" + "=" * 78)
print(title)
print("=" * 78)
def section(name):
def wrap(fn):
def run(*a, **kw):
CURRENT[0] = name.split(".")[0]
banner(name)
t0 = time.time()
try:
out = fn(*a, **kw)
Note: The original snippet continues past this point; the remaining helper definitions were truncated. The scaffolding above is enough to follow the structure of every subsequent section.
Step 1: Build the Pinned llama.cpp Branch
NVIDIA's model card specifies the exact llama.cpp commit required to load Dory 1.0 4B. Clone that branch rather than using a release build, then compile with GGUF support enabled.
git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp
git checkout <pinned-commit-from-model-card>
cmake -B build -DLLAMA_CURL=ON
cmake --build build --config Release -j
Confirm the build succeeded by checking the server binary before proceeding.
Step 2: Download the GGUF
Pull the 3.7 GB BF16 GGUF from the Hugging Face repository linked in the model card:
huggingface-cli download nvidia/NVIDIA-Nemotron-Labs-Dory1.0-4B-BF16 \
--include "*.gguf" --local-dir ./models
Step 3: Read the Architecture from the File
Before running inference, inspect the GGUF metadata to extract block counts, layer counts, and head dimensions. This lets us predict the parameter footprint and KV-cache size for each loop count analytically, then verify against the live server later.
Parse the GGUF header and dump the .blockcount, .attention.headcount, and embedding-size keys into a small table. Because Dory's middle layers are reused across loops rather than duplicated, the parameter count should remain constant across loop settings while the KV-cache and compute cost scale with the loop count.
Step 4: Validate Predictions Against the Running Server
Launch llama-server with the downloaded GGUF, then query the /props endpoint to read back the effective context size and model parameters. Compare these to the values predicted in Step 3. Any mismatch here indicates either a wrong llama.cpp build or an incorrect metadata interpretation, so resolve it before benchmarking.
./build/bin/llama-server -m ./models/dory-1.0-4b-bf16.gguf \
--host 127.0.0.1 --port 8080 -c 8192
Step 5: Measure Decode Speed Across Loop Counts
For each loop setting (1, 2, 3, 4), send a fixed prompt and record tokens per second during decode. Since every extra loop adds compute per token without adding parameters, expect decode throughput to drop monotonically with loop count while the memory footprint stays roughly flat. Record the results in the RESULTS dict so they appear in the final summary table.
Step 6: Test the Last-Loop Cache
Dory supports a cheaper cache configuration that stores KV state only for the final loop. Compare greedy outputs with and without this optimization on a set of prompts. If the predictions are identical, the cache is safe to enable for production and reduces memory pressure; if they diverge, note the prompts where they differ so users can decide whether the savings justify the risk.
Step 7: Paired GSM8K Evaluation
Run the standard GSM8K test set at each loop count, using the same prompts and seeds so the comparison is paired. For every loop setting, track:
Because the loops trade compute for reasoning depth, expect accuracy to rise with loop count, answer length to grow, and time per solved question to increase as well. The interesting output is the marginal accuracy gain per additional loop, which quantifies how much reasoning depth each pass actually buys.
Step 8: Summary Table
After all sections run, print the RESULTS and CLAIMS registries. The results table shows each step's one-line outcome; the claims table shows which predictions from the architecture analysis held up and which did not. This closes the loop between the metadata-derived cost model and the measured behavior of the running model.
Takeaways
via MarkTechPost
