Verifiable, delegated LLM inference in C++.
dvllm splits transformer inference between two parties:
- A server (the prover) that holds the large fixed-point weights and performs all the expensive matrix multiplications — optionally on an NVIDIA GPU (CUDA).
- A client (the verifier) that runs the cheap parts of the network
(embeddings, RMSNorm, RoPE, attention softmax, sampling) locally and
cryptographically verifies every matmul the server returns, using a Matrix
Commitment Scheme (MCS) over the
secp256k1curve.
The result: you can offload the heavy compute of a model you don't fully trust, and still be confident the returned activations were computed against the exact committed weights — no blind trust in the remote box.
The lineage traces back to Karpathy's llama2.c
and Dylan Harden's llama3.2.c, but
this repo adds fixed-point quantization, a client/server split, GPU backends, and the
commitment-based verification layer.
AI notice: README was generated by AI, but corrected and verified for accuracy. For the development of the actual source, AI was used for catching bugs and supporting additional models, starting from a baseline human-written implementation.
- How it works
- Supported models
- Directory layout
- Dependencies
- Building
- The
~/.dvllmmodel layout - End-to-end walkthrough
- Running each model family
- Splitting server and client across machines
- Command reference
- Performance & tuning
- Troubleshooting
- License
There are three phases:
-
Export — a Python script pulls a model from Hugging Face and writes a single
model.bin(fp32) file into~/.dvllm/<model>/. -
Convert + commit — the
quantizebinary readsmodel.binand produces:model.bin.q— int16 fixed-point weights, used by the server.client.bin— the small weights the client keeps (embeddings, norms).commits/— the cryptographic trusted setup (setup.bin) and a commitment file per weight matrix, plusembeddings.bin.
-
Serve + verify — the server (
serverq) loadsmodel.bin.qand answers matmul requests over TCP; the client (llama3.2q/llama2q/gemma3q) drives generation, sends activations to the server, and verifies each returned product against the commitments before continuing.
Cryptography lives in two libraries that are built for you automatically:
mcs-cpp(Rust, insrc/verification.rs) — the Matrix Commitment Scheme, usingarkworkssecp256k1. Built via Corrosion, which CMake fetches.simdcrypt(C++, vendored inext/simdcrypt) — AES / PRNG / SHA-256 with AES-NI (x86) or ARM crypto extensions.
| Family | Client binary | Server binary | Quantizer | Export script | Tokenizer file | Tokenizer script |
|---|---|---|---|---|---|---|
| Llama 3.2 | llama3.2q |
serverq |
quantize |
models/download.py |
tokenizer.bin |
models/llama3.2_tok.py |
| Llama 2 | llama2q |
serverq |
quantize |
models/download.py |
llama2.tokenizer |
models/llama2_tok.py |
| Gemma 3 | gemma3q |
serverq_gemma3 |
quantize_gemma3 |
models/gemma3.py |
gemma3.tokenizer |
models/gemma3_tok.py |
Llama 3.2 covers the 1B / 3B variants (base and -Instruct). Gemma 3 covers the
1B / 4B / 12B / 27B -it / -pt variants.
dvllm/
├── src/ # C++/CUDA/Rust sources for the shipped binaries
│ ├── llama2q.cpp # Llama 2 client (verifier)
│ ├── llama3.2q.cpp # Llama 3.2 client (verifier)
│ ├── gemma3q.cpp # Gemma 3 client (verifier)
│ ├── serverq.cpp # Llama server (prover)
│ ├── serverq_gemma3.cpp # Gemma 3 server (prover)
│ ├── quantize.cpp # Llama quantizer + commitment generator
│ ├── quantize_gemma3.cpp # Gemma 3 quantizer + commitment generator
│ ├── verification.rs # Matrix Commitment Scheme (Rust, crate `mcs-cpp`)
│ ├── cuda_int32_matmul.cu # CUDA int matmul kernel
├── include/ # Headers: client logic, networking, verification, backends
│ └── backend/ # cpu / cublas matmul backends
├── ext/simdcrypt/ # Vendored AES/PRNG/SHA-256 library
├── models/ # Python export + tokenizer scripts
├── extras/ # Non-verified reference clients/servers (opt-in build)
├── cmake/ # FindPCRE.cmake and friends
├── copier.sh # Copy client files from a server box to a client box
├── CMakeLists.txt
├── Cargo.toml # mcs-cpp Rust crate
└── requirements.txt # Python deps
You need a C++20 toolchain, CMake, Rust, and a couple of libraries. git is
required because CMake fetches Corrosion at configure time.
| Dependency | Why |
|---|---|
| CMake ≥ 3.20 | Build system |
| C++20 compiler | g++ ≥ 10 or clang++ ≥ 12 |
| Rust + Cargo | Builds the mcs-cpp verification crate |
PCRE (libpcre) |
Regex splitting in the Llama 3.2 tokenizer |
| OpenMP | CPU parallelism (optional but strongly advised) |
| Git | CMake FetchContent pulls Corrosion |
macOS (Homebrew):
xcode-select --install # clang, make
brew install cmake pcre libomp rustUbuntu / Debian:
sudo apt update
sudo apt install -y build-essential cmake git \
libpcre3 libpcre3-dev \
libomp-dev clang
# Rust (if not already installed):
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source "$HOME/.cargo/env"Fedora / RHEL:
sudo dnf install -y gcc-c++ cmake git pcre-devel libomp-devel rust cargosimdcrypt is vendored in ext/ (not a git submodule), so a plain
git clone is enough — no --recursive needed.
The CUDA backend is auto-detected at CMake configure time. If CUDA isn't found, the server runs on CPU.
- NVIDIA / CUDA — install the CUDA Toolkit
(provides cuBLAS, cuRAND). CMake enables the
USE_CUBLASpath automatically whenCUDAToolkitis found. Adjust the target arch inCMakeLists.txt(CMAKE_CUDA_ARCHITECTURES, defaultnative).
Model export/conversion tooling runs under Python 3.9+. A virtualenv is recommended:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtrequirements.txt installs torch, numpy, tiktoken,
blobfile, requests, tqdm, etc. Depending on which family you export you
also need:
# Hugging Face model loading (all families):
pip install transformers huggingface_hub
# Llama 2 tokenizer export only:
pip install sentencepieceMost of these checkpoints are gated on Hugging Face. Request access on the model page, then authenticate:
pip install huggingface_hub
huggingface-cli logingit clone <this-repo-url> dvllm
cd dvllm
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -jThe first configure fetches Corrosion and builds the Rust crate, so it takes a
minute. Binaries land in build/:
build/llama2q build/llama3.2q build/gemma3q
build/serverq build/serverq_gemma3
build/quantize build/quantize_gemma3
Useful configure flags:
| Flag | Effect |
|---|---|
-DENABLE_EXTRAS=ON |
Also build the unverified reference extras/ binaries |
-DCMAKE_CUDA_ARCHITECTURES=90 |
Pin a CUDA arch instead of native |
Note:
-march=nativeis set globally inCMakeLists.txt. If you build on one machine and run on another, remove or adjust it to match the oldest CPU in your fleet.
Every binary reads and writes under ~/.dvllm/<model_name>/, where
<model_name> is the Hugging Face repo id (e.g. meta-llama/Llama-3.2-1B).
After a full export + quantize you'll have:
~/.dvllm/meta-llama/Llama-3.2-1B/
├── model.bin # fp32 export (from download.py)
├── model.bin.q # int16 fixed-point server weights (server needs this)
├── client.bin # client weights (client needs this)
└── commits/
├── setup.bin # MCS trusted setup
├── embeddings.bin # embedding commitments
└── w*_*.bin # per-weight-matrix commitments
- The server needs
model.bin.q. - The client needs
client.binand thecommits/directory. model.bincan be deleted after quantizing (it's large and only the quantizer reads it — see--embed-onlyfor regenerating embedding commitments without it).
The example below runs the Llama 3.2 1B model with the server and client on the same machine (localhost). Cross-machine setup is below.
# 0) activate the python env with export deps installed
source .venv/bin/activate
# 1) Export the fp32 model from Hugging Face → ~/.dvllm/meta-llama/Llama-3.2-1B/model.bin
python models/download.py --hf meta-llama/Llama-3.2-1B
# 2) Export the tokenizer (writes tokenizer.bin in the CWD)
python models/llama3.2_tok.py
# 3) Quantize + generate commitments
# → model.bin.q, client.bin, commits/
./build/quantize meta-llama/Llama-3.2-1B
# 4) Start the server (prover). Listens on port 5065 by default.
./build/serverq meta-llama/Llama-3.2-1B
# 5) In another terminal, run the client (verifier) against the local server
./build/llama3.2q meta-llama/Llama-3.2-1B \
-z tokenizer.bin \
-r 127.0.0.1 \
-n 256 \
-i "Once upon a time"The client streams generated text and verifies each server matmul as it goes.
The shape of the workflow is identical for every family — only the export script, quantizer, server, client, and tokenizer file differ (see the support table).
# Export (base or -Instruct)
python models/download.py --hf meta-llama/Llama-3.2-1B
python models/download.py --hf meta-llama/Llama-3.2-3B-Instruct
# Tokenizer → tokenizer.bin
python models/llama3.2_tok.py
# Quantize + commit
./build/quantize meta-llama/Llama-3.2-1B
# Serve + generate
./build/serverq meta-llama/Llama-3.2-1B
./build/llama3.2q meta-llama/Llama-3.2-1B -z tokenizer.bin -r 127.0.0.1 -i "Hello" -n 128# Export
python models/download.py --hf meta-llama/Llama-2-7b-hf
# Tokenizer (needs sentencepiece) → llama2.tokenizer
python models/llama2_tok.py
# Quantize + commit (same quantizer as Llama 3.2)
./build/quantize meta-llama/Llama-2-7b-hf
# Serve + generate (same server as Llama 3.2)
./build/serverq meta-llama/Llama-2-7b-hf
./build/llama2q meta-llama/Llama-2-7b-hf -z llama2.tokenizer -r 127.0.0.1 -i "Hello" -n 128Gemma 3 uses its own export script, quantizer, and server.
# Export (default is google/gemma-3-1b-it; override with -m)
python models/gemma3.py -m google/gemma-3-1b-it
python models/gemma3.py -m google/gemma-3-4b-it
# Tokenizer — parse the HF tokenizer.json → gemma3.tokenizer
# (download the model's tokenizer.json from HF first, or point at your HF cache)
python models/gemma3_tok.py -t /path/to/gemma-3/tokenizer.json -o gemma3.tokenizer
# Quantize + commit
./build/quantize_gemma3 google/gemma-3-1b-it
# Serve + generate
./build/serverq_gemma3 google/gemma-3-1b-it
./build/gemma3q google/gemma-3-1b-it -z gemma3.tokenizer -r 127.0.0.1 -i "Hello" -n 128The whole point is to run the prover on a beefy (possibly untrusted) GPU box and the verifier on a small trusted box.
On the server machine (does the export + quantize, holds the big weights):
python models/download.py --hf meta-llama/Llama-3.2-1B
./build/quantize meta-llama/Llama-3.2-1B
./build/serverq meta-llama/Llama-3.2-1B --port 5065Copy the client artifacts to the client machine. The helper
copier.sh scps client.bin and the commits/ directory from a
remote server into your local ~/.dvllm/<model>/:
# usage: ./copier.sh <remote_user> <server_host> <model_name>
./copier.sh ubuntu 192.168.1.50 meta-llama/Llama-3.2-1BOn the client machine, point the client at the server's IP with -r:
./build/llama3.2q meta-llama/Llama-3.2-1B \
-z tokenizer.bin \
-r 192.168.1.50 \
-i "Once upon a time" -n 256You can pass -r multiple times to spread the delegated matmuls across
several servers (the client shards work across them). All servers must serve the
same model.
python models/download.py --hf <hf_repo_id> [--version N] [--dtype fp32|fp16]
Writes ~/.dvllm/<hf_repo_id>/model.bin. --checkpoint / --meta-llama are
placeholders; only --hf is currently supported.
python models/gemma3.py -m <hf_model_id> # default google/gemma-3-1b-it
quantize <model_name> [--no-clobber] [--no-commit] [--embed-only]
| Flag | Effect |
|---|---|
--no-clobber |
Don't overwrite existing outputs |
--no-commit |
Produce model.bin.q / client.bin but skip the commits/ |
--embed-only |
Only regenerate commits/embeddings.bin, reusing everything else (doesn't need model.bin) |
serverq <model_name> [options]
| Flag | Default | Effect |
|---|---|---|
--port <int> |
5065 |
TCP port to listen on |
--force-cpu |
off | Ignore any GPU backend and run on CPU |
--copy-to-cpu |
off | Pin weights in CPU RAM for fast async transfer (more RAM) |
--device-layers <n> |
— | Number of layers kept resident on the device |
--num-devices <n> |
1 |
Number of GPUs to shard across |
Loads ~/.dvllm/<model_name>/model.bin.q.
<client> <model_name> [options]
| Flag | Default | Effect |
|---|---|---|
-r <ip> |
— | Server IP (repeat for multiple servers; port defaults to 5065) |
-i <string> |
empty | Input prompt |
-n <int> |
256 |
Number of steps (0 = max sequence length) |
-t <float> |
1.0 |
Temperature (0 = greedy/deterministic) |
-p <float> |
0.9 |
Top-p nucleus sampling (1.0 = off) |
-s <int> |
time(NULL) |
RNG seed |
-z <path> |
family default | Path to the tokenizer file |
Loads ~/.dvllm/<model_name>/client.bin plus commits/.
Sampling tip: for best results use -t 1.0 -p 0.9. To control diversity,
vary either temperature (with -p 0) or top-p (with -t 1), not both.
-
OpenMP threads — the CPU path parallelizes matmul/attention with OpenMP. Set the thread count to your number of physical cores (not logical, to avoid cache thrashing):
OMP_NUM_THREADS=6 ./build/serverq meta-llama/Llama-3.2-1B
-
GPU — build with CUDA present (see GPU backend) and the server auto-selects it. Use
--num-devicesto shard across GPUs and--copy-to-cputo trade RAM for faster host↔device transfers. -
Fixed-point weights — the matmul weights are stored as int16 fixed-point (scaled by
FP_SCALE, seeinclude/constants.hpp); norms and other sensitive parameters stay fp32. This keeps the server's matmuls in integer arithmetic — which is both faster and what the commitment scheme verifies over — at a small precision cost versus fp32. -
-march=native— enabled by default for the best vector instructions on the build host. Remove it inCMakeLists.txtif you build and run on different CPUs.
403/ gated model on export — request access on the model's Hugging Face page and runhuggingface-cli login.transformers package is required—pip install transformers huggingface_hub.- Llama 2 tokenizer export fails —
pip install sentencepiece. - CMake can't find PCRE — install
libpcre3-dev(Debian) /pcre-devel(Fedora) /brew install pcre(macOS). The Llama 3.2 / Gemma clients need it. - Server can't open
model.bin.q— you skipped the quantize step, or the<model_name>doesn't match the directory under~/.dvllm/. - Client can't find
commits/orclient.bin— runquantizeon the client machine, or copy them over withcopier.sh. - Client and server disagree / verification fails — make sure both sides were built from the same commit and the server is serving the exact model the client quantized (the commitments are model-specific).
Illegal instructionat runtime — you built with-march=nativeon a newer CPU than you're running on. Rebuild without it.
MIT. See LICENSE. The mcs-cpp crate is BSD-3-Clause (see
Cargo.toml).