Model reference · open weights
NV-Reason-CT is an open-weight language model from NVIDIA. NV-Reason-CT (BF16) weighs 10.6 GB; the smallest configuration that runs it is RTX 4060 Ti 16 GB.
What it is
| Released by | NVIDIA |
|---|---|
| Type | Language models |
| Task | Vision + text |
| Parameters (lead) | 5.3B |
| Context | 262,144 tokens |
| Runs with | transformers |
| Based on | Qwen/Qwen3.5-4B |
| Released | 2026-09-08 |
| Popularity | 210 downloads / month |
| Weights | 10.6 GB (NV-Reason-CT (BF16), file size) |
| Licence | Open, with conditions |
What it runs on
Weights 10.6 GB (file size) · KV cache 33 MB per 1,000 tokens of context, at 16 bits (vLLM's default for this build; an 8-bit cache halves it) · 103 MB of fixed state per request · runtime overhead from 2.1 GB on a small card · context up to 262,144 tokens.
| Card | Requests at once 8K tokens each | Requests at once 32K tokens each | Longest single request | Counted memory |
|---|---|---|---|---|
| RTX 3060 12 GB | — | — | — | 11.6 GB |
| RTX 4060 Ti 16 GB | 7 | 2 | 76K | 15.4 GB |
| RTX 3090 24 GB | 28 | 9 | all 256K | 23.4 GB |
| RTX 4090 24 GB | 28 | 9 | all 256K | 23.4 GB |
| RTX 5090 32 GB | 49 | 15 | all 256K | 31.0 GB |
| L40S 48 GB | 84 | 26 | all 256K | 44.0 GB |
| A100 80 GB | 176 | 55 | all 256K | 78.2 GB |
| H100 80 GB | 161 | 51 | all 256K | 78.1 GB |
| RTX PRO 6000 Blackwell 96 GB | 204 | 64 | all 256K | 93.8 GB |
| DGX Spark (GB10) 128 GB unified | 240 | 75 | all 256K | 107 GB |
| H200 141 GB | 322 | 101 | all 256K | 138 GB |
| B200 180 GB | 424 | 134 | all 256K | 176 GB |
| 2× RTX 3060 12 GB tensor parallel | 22 | 7 | 247K | 11.6 GB a card |
| 2× RTX 4060 Ti 16 GB tensor parallel | 43 | 13 | all 256K | 15.4 GB a card |
| Requests at once | 8K tokens each | 32K tokens each |
|---|---|---|
| 1 | 13.1 GB | 13.9 GB |
| 5 | 14.6 GB | 18.6 GB |
| 8 | 15.7 GB | 22.1 GB |
| 16 | 18.7 GB | 31.6 GB |
| 32 | 24.6 GB | 50.4 GB |
| 64 | 36.5 GB | 88.0 GB |
On one card, with vLLM's small-card settings (2,048 tokens a step). Cards of 70 GB and more reserve more per request and more overhead — each row above uses its own card's settings.
Estimates, not measurements, checked against published vLLM startup logs. The weights are the build's file size; the cache is calculated from its config (hybrid: linear attention with full attention every few layers); the overhead is an estimate of vLLM's own memory with that card's default settings. "Requests at once" is how many requests of that length vLLM admits — its reservation at full length, with --max-model-len set to that length; requests that stay shorter fit more. "Longest single request" is the most one request can hold there: below the model's maximum, vLLM starts only with --max-model-len set at or under it. "Counted memory" is vLLM's default 92 % of what CUDA reports for the card (the DGX Spark: about 100 GiB of its shared 128 GB). A tensor-parallel split pools the cards' memory and speeds each token up, at the cost of the link between them; a layer split (llama.cpp) holds more but does not make one request faster. Assumes vLLM 0.10 or later.
From the model card
NV-Reason-CT is a 3D vision-language model (VLM) for CT image analysis. It combines a native 3D vision encoder (3D ViT) with a language model and is designed for radiology report generation, general question answering, and multi-step reasoning across chest and abdominal CT volumes.
Computed tomography encodes clinically important anatomy across hundreds of slices, yet most vision–language systems either operate on 2D images or compress volumetric features before language decoding. The 3D vision encoder converts a 384×384×384-mm input volume into a 24×24×24 grid of 13,824 visual tokens. All tokens and their corresponding 3D positions are passed to the language model without spatial downsampling, while 3D MRoPE preserves their spatial relationships within the LLM. Input volumes are automatically cropped to the chest or abdomen and resampled to 2-mm isotropic resolution before processing.
The model was trained end-to-end using Supervised Fine-Tuning (SFT) and Group Relative Policy Optimization (GRPO) on a curated dataset of approximately 550,000 structured QA examples from 70,111 unique CT volume inputs. The training corpus integrates standardized reports, abnormality-focused QA, multi-turn interactions, and radiologist-authored reasoning collected through recorded and transcribed expert CT interpretations. These expert annotations also guide the generation of report-grounded synthetic reasoning data for SFT, while GRPO uses verifiable rewards over chest and abdominal abnormality sets.
Python 3.11+ and a CUDA-capable PyTorch installation are recommended.
python -m pip install -r https://huggingface.co/nvidia/NV-Reason-CT/resolve/main/requirements.txt
The example below loads the model once and defines a reusable function for
deterministic inference on .nii.gz volumes. Use anatomy_region="chest"
for a chest crop or anatomy_region="abdomen" for an abdominal crop.
import torch
from transformers import AutoModelForImageTextToText, AutoProcessor
model_id = "nvidia/NV-Reason-CT"
ct_path = "path/to/volume.nii.gz"
model = AutoModelForImageTextToText.from_pretrained(
model_id,
trust_remote_code=True,
dtype=torch.bfloat16,
attn_implementation="sdpa",
).eval().to("cuda")
processor = AutoProcessor.from_pretrained(
model_id,
trust_remote_code=True,
)
def generate_response(
ct_path,
prompt_text,
anatomy_region="chest",
enable_thinking=True,
max_new_tokens=2048,
):
messages = [
{
"role": "user",
"content": [
{"type": "image"},
{"type": "text", "text": prompt_text},
],
}
]
prompt = processor.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=enable_thinking,
)
inputs = processor(
text=prompt,
images3d=[ct_path],
anatomy_region=anatomy_region,
return_tensors="pt",
).to(model.device)
with torch.inference_mode():
generated_ids = model.generate(
**inputs,
max_new_tokens=max_new_tokens,
do_sample=False,
use_cache=True,
)
new_tokens = generated_ids[:, inputs.input_ids.shape[1]:]
return processor.batch_decode(
new_tokens,
skip_special_tokens=True,
clean_up_tokenization_spaces=False,
)[0]
# Structured report (Chest region)
print(generate_response(ct_path, "Write a structured chest CT report.", anatomy_region="chest"))
The same loaded model can be reused with other prompts and anatomy regions.
# Structured report (Abdominal region)
print(generate_response(ct_path, "Write a structured abdominal CT report.", anatomy_region="abdomen"))
# Reasoning (Chest region)
print(generate_response(ct_path, "Provide a full reasoning analysis of this chest CT.", anatomy_region="chest"))
# Reasoning (Abdominal region)
print(generate_response(ct_path, "Provide a full reasoning analysis of this abdominal CT.", anatomy_region="abdomen"))
# Binary question with reasoning
print(generate_response(ct_path, "Is a pleural effusion present in this CT?", anatomy_region="chest"))
# Concise Yes/No response without thinking
print(generate_response(
ct_path,
"Is a pleural effusion present in this CT? Answer only Yes or No.",
anatomy_region="chest",
enable_thinking=False,
))
AutoProcessor includes anatomy-aware cropping around the chest or abdomen
using lung Hounsfield units and a 3D morphology heuristic. Only "chest" and
"abdomen" are supported. This works reasonably well for common CT
geometries, such as whole-body CT, chest plus upper abdomen, or lower chest
plus abdomen and pelvis. You can inspect the exact crop passed to the VLM:
import nibabel as nib
import numpy as np
from transformers import AutoProcessor
model_id = "nvidia/NV-Reason-CT"
ct_path = "path/to/volume.nii.gz"
anatomy_region = "chest" # "chest" or "abdomen"
processor = AutoProcessor.from_pretrained(
model_id,
trust_remote_code=True,
)
# Run the same 2 mm resampling and anatomy-aware 192 x 192 x 192 crop used
# for inference. normalize_mode=0 preserves CT Hounsfield units.
cropped_image = processor.image_processor_3d.load_image(
ct_path,
normalize_mode=0,
anatomy_region=anatomy_region,
)
# load_image() returns a channel-first tensor in (C, Z, Y, X) order for the
# model. Convert the spatial axes back to NIfTI (X, Y, Z) order before saving.
cropped_volume = cropped_image[0].permute(2, 1, 0).detach().cpu().numpy()
affine = np.diag([-2.0, -2.0, 2.0, 1.0])
cropped_nifti = nib.Nifti1Image(
cropped_volume,
affine,
)
# Inspect the cropped image; tQuoted from the model card on Hugging Face — the full card is behind the Hugging Face link above.