This guide runs NInfer on Linux from the published container image, or builds it natively.
CMAKE_CUDA_ARCHITECTURES selects one target per build directory:
| value | GPUs | toolkit |
|---|---|---|
86 (the default) |
RTX 30 series; an 86 build also runs on the RTX 40 series |
CUDA 12.8 or newer |
89 |
RTX 40 series only | CUDA 12.8 or newer |
120a |
RTX 50 series and RTX PRO 6000 Blackwell | CUDA 13.1 or newer (configure refuses 12.8 and 12.9, which miscompile sm_120a kernels) |
80 |
A100 class (GA100), an unmeasured compatibility target | CUDA 12.8 or newer |
70 |
Tesla V100 (Volta) | CUDA 12.8 or 12.9 (CUDA 13 no longer compiles sm_70) |
75 |
TITAN RTX and RTX 20 series (Turing), through the Turing tier | CUDA 12.8 or newer |
An 89 or 120a build does not run on an RTX 3090, only a 70 build runs on a V100, and only
a 75 build on a Turing card. See Turing and Tesla V100 for
what those builds change.
The published image, ghcr.io/iamwavecut/ninfer-all:latest, is the shortest path on any Linux
distribution: CUDA 13.4 on Ubuntu 26.04, with an sm_75 build for Turing, an sm_86 build for the
RTX 30 and 40 series and an sm_120a build for Blackwell, and it starts the one that matches the
GPU. It needs a
driver of the CUDA 13 branch (580 or newer) and the NVIDIA Container Toolkit; make sure that Docker
can access the GPU first:
docker run --rm --gpus all nvidia/cuda:13.4.2-base-ubuntu26.04 nvidia-smiDownload and serve the Qwen3.8 model with the measured tuned profile:
docker run --rm -v "$PWD/models:/models" ghcr.io/iamwavecut/ninfer-all download qwen38-27b
docker run --rm --gpus all -p 8080:8080 --ulimit memlock=-1 \
-v "$PWD/models:/models" ghcr.io/iamwavecut/ninfer-all run qwen38-27bThe API is available at http://127.0.0.1:8080/v1. docker build --build-arg ARCHS=86 -t ninfer .
builds the same image from source with the sm_86 build alone; the README
covers the container's commands, volumes and compose file, and the launch recipes
the command for each published artifact.
Install the CUDA Toolkit from NVIDIA (12.8 or newer, 13.1 or newer for 120a).
Then install the host compiler and media dependencies:
sudo apt-get update
sudo apt-get install --yes \
build-essential gcc-13 g++-13 cmake ninja-build pkg-config \
libavcodec-dev libavformat-dev libavutil-dev libswscale-dev \
libcurl4-openssl-devSelect GCC 13 for host and CUDA compilation:
export CC=/usr/bin/gcc-13
export CXX=/usr/bin/g++-13
export CUDACXX=/usr/local/cuda/bin/nvcc
export CUDAHOSTCXX=/usr/bin/g++-13Configure and build the Linux applications:
cmake -S . -B build-sm86 -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER="$CC" \
-DCMAKE_CXX_COMPILER="$CXX" \
-DCMAKE_CUDA_COMPILER="$CUDACXX" \
-DCMAKE_CUDA_HOST_COMPILER="$CUDAHOSTCXX" \
-DCMAKE_CUDA_ARCHITECTURES=86 \
-DNINFER_BUILD_APPS=ON \
-DBUILD_TESTING=OFF \
-DNINFER_BUILD_BENCHMARKS=OFF
cmake --build build-sm86 --parallel 2For another GPU family, set CMAKE_CUDA_ARCHITECTURES from the table above in a build directory
of its own. The build creates these applications:
build-sm86/apps/ninfer
build-sm86/apps/ninfer-serve
build-sm86/apps/ninfer-calibrate
build-sm86/apps/ninfer-perplexity
A 75 build runs on the TITAN RTX and the RTX 20 series. The container image carries one; a native
build needs CUDA 12.8 or newer and -DCMAKE_CUDA_ARCHITECTURES=75:
cmake -S . -B build-sm75 -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER="$CC" -DCMAKE_CXX_COMPILER="$CXX" \
-DCMAKE_CUDA_HOST_COMPILER="$CUDAHOSTCXX" \
-DCMAKE_CUDA_ARCHITECTURES=75 \
-DNINFER_BUILD_APPS=ON
cmake --build build-sm75 --parallelTuring's tensor cores multiply FP16 and INT8 but not BF16 or TF32, it has no cp.async, and a
block has 64 KiB of shared memory. A 75 build runs the kernels of the other targets with Turing
forms of those instructions and Turing shapes of the kernels that do not fit
(Turing tier):
- Integer routes (the T2 ternary projections, A8 activations, INT8-family attention, GGUF
dp4aand MMQ kernels) and FP16 routes run on the native tensor cores. - BF16 products run on FP16 tensor cores for every output whose operands are FP16 values, and in FP32 for the others; no value is narrowed. BF16-cache attention multiplies Q, K and V as their FP16 roundings, and every attention PV product with BF16 values rounds the probabilities to FP16.
- Every model, KV format and drafter runs. The Linear routes come from the legacy table only, and
NINFER_LINEAR_ROUTES=unifiedis refused.
The TITAN RTX's device route profile is built in; another Turing part calibrates at first start (see device profiles). What runs and how fast is in the TITAN RTX measurements.
A 70 build runs on Tesla V100 (16 or 32 GB). It needs CUDA 12.8 or 12.9: CUDA 13 no longer
compiles sm_70, and configure refuses that combination. Configure it like any other build, with
-DCMAKE_CUDA_ARCHITECTURES=70 and the 12.x nvcc:
cmake -S . -B build-sm70 -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER="$CC" -DCMAKE_CXX_COMPILER="$CXX" \
-DCMAKE_CUDA_COMPILER=/usr/local/cuda-12.9/bin/nvcc \
-DCMAKE_CUDA_HOST_COMPILER="$CUDAHOSTCXX" \
-DCMAKE_CUDA_ARCHITECTURES=70 \
-DNINFER_BUILD_APPS=ON
cmake --build build-sm70 --parallel 2Volta's tensor cores multiply FP16 only (mma.m8n8k4): there are no BF16 or integer tensor cores,
no cp.async and no ldmatrix. A 70 build therefore runs its own routes where they decide the
speed, and the kernels written for Ampere everywhere else, on emulated warp primitives
(src/ops/common/sm70_emulation.cuh), which are correct but slower:
- Dense projections of every native format (T2, Q2, Q4, Q5, Q6, Q8, BF16, FP8, NVFP4) up to 64
columns: a lane decodes the weights of its row straight into FP16 tensor-core fragments, and a
row-split group's FP32 products are scaled when the group ends. Up to eight columns read the BF16
activations directly; wider calls convert them to FP16 once. From 65 columns
(
NINFER_SM70_CUBLAS_MIN) the weights are dequantized to FP16 32 MiB at a time and multiplied by cuBLAS with FP32 accumulation; a call of more than 8,192 columns (a Vision item's patches) passes over them 8,192 at a time. Activations beyond +-65504 saturate there. - T2 projections at one to eight columns (decode, two requests, an MTP or DFlash2 verify round)
take an integer route where the projection's policy admits integer activations, as the other
targets' T2 A8 route does: the activations are quantized to int8 with one scale per 64 values,
and
dp4amultiplies them by the ternary codes, 10-35% faster than the tensor-core route. The MLPgate_uptakes it only under--mlp-a8-decode, the flag that asks for an integer gate_up at these widths; on Volta that applies to a T2gate_up, and a Q4 one, which has no integer route here, stays A16. - Weight layout: the row-split projection weights of the text and MTP layers (T2, Q2, Q4, Q5, Q6) load in the panel layout, four rows' records of a group per 128-byte line, which the decode reads stream at a larger share of the card's bandwidth.
- GGUF projections keep NInfer's DP4A vector kernel up to eight columns and ggml's DP4A matrix kernel above (as llama.cpp does on Volta); GGUF dense projections from 65 columns take the cuBLAS route.
- Attention of every KV format runs on Volta's tensor cores with FP32 accumulation. Decode and verification use a Volta kernel that decodes each format's keys and values to FP16 as it loads them (32 dimensions of a quantized row a load), with the same fused append, splits and reducers as the other targets. Prompt attention gives a CTA 64 query rows of one KV head and decodes each 32-key tile of the visible pages into shared memory once for all of them; it reads the pages where they are and needs no workspace.
- FP8 A8 and NVFP4 W4A4 activations are not available, as on
sm_86; those weights run their A16 routes.
On a 16 GB card the official Qwen3.8-27B artifact loads with --embedding-q4 --lm-head-q4 (its
Q8 embedding and output head stored as Q4 while loading, 14.7 GiB of weights). What else fits is in
the measurements.
The V100-SXM2-16GB's device route profile is built in; another V100 part calibrates at first start, in about nine seconds (see device profiles).
Linux can use the pinned vcpkg.json manifest instead of system FFmpeg and curl packages.
Bootstrap the pinned vcpkg revision:
git clone https://github.com/microsoft/vcpkg.git "$HOME/.local/share/vcpkg"
git -C "$HOME/.local/share/vcpkg" checkout 4bca8fd8654e5ba76f92661db7bfe954768ad8ef
"$HOME/.local/share/vcpkg/bootstrap-vcpkg.sh" -disableMetricsAdd these options to the native CMake command:
-DCMAKE_TOOLCHAIN_FILE=$HOME/.local/share/vcpkg/scripts/buildsystems/vcpkg.cmake
-DVCPKG_TARGET_TRIPLET=x64-linux
Every option below is off by default; a default build runs the tested routes. Pass them to the
CMake configure command as -DNAME=VALUE.
| option | effect |
|---|---|
NINFER_SFU_SIGMOID_SILU=ON |
sigmoid and SiLU evaluate ex2.approx and a correctly rounded reciprocal on the SFU instead of expf and a divide |
NINFER_SFU_SOFTPLUS=ON |
the GDN decay gate's softplus runs on the SFU as well, switching to a log1p series where e^x is below 1/16 |
NINFER_BF16_RESIDUAL_ADD=ON |
BF16 linear projections are added onto the residual in bf16 instead of fp32 |
NINFER_NVCC_SPLIT_COMPILE=N |
nvcc optimizes one translation unit on N threads (--split-compile); 0 is every core, which a parallel ninja multiplies by its job count |
NINFER_PTXAS_VERBOSE=ON |
ptxas reports each kernel's registers and local-memory spills |
NINFER_WEBUI_DIR=PATH |
compiles the WebUI in that directory (an index.html or index.html.gz at its root) into the server |
NINFER_SM120_NATIVE=ON |
on a 120a build, compiles upstream's native routes instead of the mma.sync path (every 120a build compiles the FP8 A8 and NVFP4 W4A4 units) |
NINFER_PDL=ON |
on a 120a build of the mma.sync path, launches decode-graph kernels as programmatic dependents, so a kernel stages its weights while the one before it finishes (the native routes always do) |
NINFER_TMA_STAGING=ON |
passes the NVFP4 TMA descriptors through device memory with a capturable staging kernel, as Windows builds must, so that path can be tested on Linux (120a) |
NINFER_MULTICALL=ON |
Linux: links ninfer, ninfer-serve, ninfer-calibrate and ninfer-perplexity into one executable, apps/ninfer-multicall, with the four names as symlinks to it, so the kernel image (about 0.5 GB per architecture) is there once instead of four times; the container image is built this way |
NINFER_MEDIA_NATIVE_PNG=ON |
decodes PNG images natively instead of through FFmpeg, as Windows builds do by default, so that decoder can be tested on Linux |
NINFER_DIRECTSTORAGE=ON |
Windows: fetches the DirectStorage 1.3 runtime and offers --disk-kv-directstorage for disk-tier restores |
The Windows option compiles against Windows headers but has not been run on this line.
At run time, NINFER_CUDA_SYNC=spin|blocking|yield|auto selects the CUDA synchronization schedule
(unset keeps CUDA's default) and NINFER_T2_A8_TILE=off returns the ternary prefill route to its
64-row kernel.
scripts/ holds the download script, the launcher and the packager, each with a Windows twin.
The download script saves models under scripts/models by default and checks their size and
SHA-256:
./scripts/download-model.sh qwen38-27b
./scripts/download-model.sh qwen36-35b-a3b
./scripts/download-model.sh qwen36-27bSet NINFER_MODEL_DIR to use another model directory. run.sh serves either model, and
NINFER_MODEL points it at a model path anywhere:
./scripts/run.sh qwen38-27b
./scripts/run.sh qwen36-35b-a3b
NINFER_MODEL=/path/to/qwen3_8_27b.ninfer ./scripts/run.sh qwen38-27b int8The launcher uses build-linux/apps/ninfer-serve when the executable is not beside the script.
Set NINFER_SERVER to select another executable. Profiles and the measurements behind them are in
launcher profiles.
scripts/package-release.sh creates the Linux tar.gz archive for the release named in VERSION,
and scripts/package-release.ps1 the Windows ZIP; build.sh --package and build.ps1 -Package run
them after a build (cutting a release).
Make sure that the applications start:
./build-sm86/apps/ninfer --help
./build-sm86/apps/ninfer-serve --helpRun one short generation with the real Qwen3.8 artifact:
./build-sm86/apps/ninfer models/qwen3_8_27b.ninfer \
--prompt "Explain prefill and decode in two sentences." \
--max-context 8192 --max-new 32 \
--kv-dtype int8 \
--spec mtp --draft-tokens 3 --lm-head-draftA successful compile does not qualify Linux performance. Record the GPU, driver, CUDA Toolkit, compiler, workload, and result before you publish Linux measurements.