Training on CUDA and OpenVINO machines
This guide takes a freshly installed machine to a finished end-to-end run: install, download a small dataset, fine-tune, validate, predict, export, and run the exported model. It covers two machines:
- Path A, NVIDIA GPU. Training and inference run on the GPU through PyTorch and CUDA.
- Path B, Intel machine with OpenVINO. Training runs on the CPU through PyTorch. The trained model then runs through OpenVINO Runtime on the Intel CPU, integrated GPU, or NPU.
The runs are deliberately tiny (160 images, 1–2 epochs). They check that every step works on the machine; they do not produce a useful model.
What OpenVINO does in nitid
OpenVINO is an inference runtime. In nitid it is used in two places:
| Operation | Where it runs |
|---|---|
train() |
PyTorch: an NVIDIA GPU (device="cuda") or the CPU (device="cpu") |
val() |
PyTorch, same devices as training |
export(format="openvino") |
PyTorch traces the model, OpenVINO converts it to IR (.xml + .bin) |
predict() / track() with backend="openvino" |
OpenVINO Runtime on "CPU", "GPU" (Intel integrated GPU), or "NPU" |
Training on an Intel GPU or NPU is not supported. The PyTorch build that nitid
installs has no Intel GPU (xpu) support, and the trainer only selects CUDA or
the CPU. On an Intel machine, training therefore runs on the CPU and is slow.
Keep the model small (model1s) and the dataset small.
backend="openvino" is inference-only: train() and val() raise an error on
such a model.
Before you start
Clone or PyPI. This guide works from a clone of the public repository,
so that uv installs the exact versions in uv.lock and the example dataset
configuration is at hand. No GitHub account is needed. To use the package from
PyPI instead, see Without a clone.
Pretrained weights. These are not stored in the nitid repository.
NITID("model1s", task="detect") downloads the original D-FINE weights from a
public GitHub release (github.com/Peterande/storage). Instance segmentation
weights come from a public Hugging Face repository (huggingface.co/ArgoSA/D-FINE-seg).
No account or token is needed. The machine needs outbound HTTPS to github.com,
objects.githubusercontent.com, huggingface.co, pypi.org,
files.pythonhosted.org and download.pytorch.org. The dataset in step 2 also downloads from
github.com. The wrapped checkpoint is saved in the current directory, so run
every command from the repository root.
Operating system. The commands are for Linux on x86_64. They were run on Ubuntu 24.04. The lock file also resolves on Windows x86_64, but the driver steps below are Linux-only.
Disk and memory. Allow about 10 GB of free disk: the virtual environment
is about 6 GB, because Linux x86_64 installs the CUDA build of PyTorch even on
machines without an NVIDIA GPU. CPU training of model1s with batch=4 peaked
at about 5 GB of RAM.
Step 1: Install nitid (both machines)
Install the system tools and uv:
sudo apt-get update
sudo apt-get install -y git curl
curl -LsSf https://astral.sh/uv/install.sh | sh
source "$HOME/.local/bin/env" # or open a new terminal
uv --version
Clone the repository:
git clone https://github.com/Nitidio/nitid.git
cd nitid
To reproduce a release exactly, check out its tag, for example
git checkout v0.9.2.
Create the environment. The train extra adds pycocotools, needed for
training and validation. The openvino extra adds OpenVINO Runtime.
# Path A, NVIDIA machine
uv sync --extra train
# Path B, Intel machine
uv sync --extra train --extra openvino
uv installs the exact versions in uv.lock. It also downloads a suitable
Python (3.10 to 3.13) if the system one is not supported. On Linux x86_64
with Python 3.10 to 3.12 the lock pins PyTorch 2.5.1+cu121 and torchvision
0.20.1+cu121 from the PyTorch CUDA 12.1 index; Python 3.13 takes PyTorch
2.13.0 and torchvision 0.28.0 from PyPI, whose Linux wheels are CUDA builds
as well; the OpenVINO extra resolves to OpenVINO 2026.2.
Check the installation:
uv run nitid --help
uv run python -c "import torch; print(torch.__version__, torch.version.cuda, torch.cuda.is_available())"
Without a clone
The same steps work with the package from PyPI. Install it into a virtual
environment, then download the dataset configuration that step 2 uses into
configs/datasets/, so that the paths in this guide stay the same:
uv venv --python 3.12 .venv && source .venv/bin/activate
uv pip install "nitid[train]" # add ,openvino on the Intel machine
mkdir -p configs/datasets
curl -fsSL -o configs/datasets/coco-mini.yml \
https://raw.githubusercontent.com/Nitidio/nitid/main/configs/datasets/coco-mini.yml
Then drop the uv run prefix from the commands below. pip resolves the newest
compatible versions instead of the locked ones. Today that is PyPI's CUDA 13.0
build of PyTorch on any Python version, so the GPU and driver requirements are
those of the Python 3.13 column in A1.
Step 2: Get a small dataset (both machines)
The guide uses COCO-mini, the example dataset declared in
configs/datasets/coco-mini.yml. It has 128 training and 32 validation images
from COCO val2017, in COCO JSON format with boxes and instance masks. Nothing
needs to be run in this step: the first train() or val() call downloads the
archive (about 26 MB) from the datasets-v1 release, checks its sha256, and
extracts it to datasets/coco-mini/:
datasets/coco-mini/
ATTRIBUTION.md
images/train/*.jpg (128)
images/val/*.jpg (32)
annotations/instances_train.json
annotations/instances_val.json
Only images licensed CC BY 2.0, "No known copyright restrictions" or "United
States Government Work" are included; ATTRIBUTION.md lists each image's
Flickr source and licence, and the annotations are CC BY 4.0.
The class list is the same as that of the pretrained COCO checkpoints, so
fine-tuning keeps the pretrained class head and predictions make sense straight
away. With your own classes, nitid rebuilds the class head automatically; see
Fine-tuning. YOLO .txt datasets work the same way.
Because the images come from COCO val2017, which the pretrained models never trained on, the metrics are reasonable, but 160 images and two epochs say nothing about real accuracy.
Path A: NVIDIA GPU with CUDA
A1. Prerequisites
The PyTorch build, and so the GPUs it supports, depends on the Python version of the environment:
| Requirement | Python 3.10 to 3.12 (default) | Python 3.13 |
|---|---|---|
PyTorch in uv.lock |
2.5.1+cu121 |
2.13.0+cu130 |
| GPU | Compute capability 5.0 to 9.0: Maxwell to Hopper (GTX 900/10, RTX 20/30/40, V100, A100, H100) | Compute capability 7.5 to 12.0: Turing to Blackwell (RTX 20/30/40/50, A100, H100, B200) |
| Driver | 525.60.13 or newer (CUDA 12.x) | 580 or newer (CUDA 13.0) |
| Requirement | Value |
|---|---|
| CUDA toolkit | Not needed. The PyTorch wheel includes the CUDA runtime and cuDNN |
| GPU memory | Start with batch=8 for model1s; lower it if you hit out-of-memory errors |
RTX 50 series and other Blackwell GPUs (compute capability 10.0 or 12.0) need the Python 3.13 environment: create it with
uv sync --python 3.13 --extra train. The defaultcu121build fails on them withno kernel image is available for execution on the device.
Install the driver on Ubuntu if nvidia-smi is not found:
sudo ubuntu-drivers install
sudo reboot
A2. Check the GPU
nvidia-smi
uv run python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name(0))"
The second command must print True and the GPU name. nvidia-smi shows the
highest CUDA version that the driver supports; it must be at least the
version in torch.version.cuda (12.1 by default, 13.0 with Python 3.13).
A3. Train
Python API:
cat > train_cuda.py <<'EOF'
from nitid import NITID
data = "configs/datasets/coco-mini.yml"
model = NITID("model1s", task="detect", device="cuda:0")
metrics = model.train(
data=data,
epochs=2,
imgsz=640,
batch=8,
workers=4,
project="runs/train",
name="cuda-smoke",
)
print({key: value for key, value in metrics.items() if key != "history"})
EOF
uv run python train_cuda.py
The same run from the command line:
uv run nitid train model=model1s task=detect \
data=configs/datasets/coco-mini.yml \
epochs=2 imgsz=640 batch=8 workers=4 device=cuda:0 name=cuda-smoke-cli
On the first run, nitid downloads the model1s detection weights (about 40 MB)
and saves dfine_s_obj2coco_wrapped.pth in the current directory. AMP (mixed
precision) and EMA are on by default for detection on CUDA.
Each epoch prints one summary line:
[dfine] Epoch 1/2 loss=... P=... R=... mAP50=... mAP50-95=... fitness=... mem=...MB
[dfine] Training complete: best epoch 1, best fitness ...
A4. Validate, predict, export
cat > after_cuda.py <<'EOF'
from nitid import NITID
data = "configs/datasets/coco-mini.yml"
image = "datasets/coco-mini/images/val/000000347930.jpg"
model = NITID("runs/train/cuda-smoke/best.pth", task="detect", device="cuda:0")
metrics = model.val(data=data, batch=8, project="runs/val", name="cuda-smoke")
print("mAP50-95:", metrics["mAP50-95"], "mAP50:", metrics["mAP50"])
results = model.predict(image, conf=0.5, save=True, project="runs/predict", name="cuda-smoke")
print(results[0].boxes.xyxy, results[0].boxes.cls, results[0].boxes.conf)
print(model.export(format="onnx", project="runs/export", name="cuda-smoke-onnx"))
EOF
uv run python after_cuda.py
TensorRT export is optional and needs NVIDIA's package; see Export.
A5. Expected run time
The CUDA path was not timed for this guide. On any supported GPU the two epochs over 128 images should take on the order of a minute, plus the first-run weight download.
Path B: Intel machine with OpenVINO
B1. Prerequisites
| Requirement | Value |
|---|---|
| CPU | x86_64 with AVX2. Training uses every core; more cores train faster |
| RAM | 8 GB minimum, 16 GB recommended |
| Intel GPU (optional) | Integrated or Arc GPU, with Intel's compute runtime (Level Zero / OpenCL) installed |
| Intel NPU (optional) | Core Ultra processor (Meteor Lake or newer), Linux kernel 6.6 or newer, Intel NPU driver installed |
OpenVINO's "CPU" device needs no extra drivers. The "GPU" and "NPU"
devices need user-space drivers that a fresh Ubuntu does not include.
Intel GPU on Ubuntu 24.04, using Intel's graphics PPA:
sudo apt-get install -y software-properties-common
sudo add-apt-repository -y ppa:kobuk-team/intel-graphics
sudo apt-get install -y libze-intel-gpu1 libze1 intel-opencl-icd clinfo
sudo usermod -aG render "$USER"
Intel NPU: install the .deb packages from the latest
intel/linux-npu-driver release
for your Ubuntu version, following the installation section of that release.
The packages are intel-driver-compiler-npu, intel-fw-npu, and
intel-level-zero-npu. Then:
sudo usermod -aG render "$USER"
sudo reboot
After the reboot, check that the device nodes exist:
ls -l /dev/dri/renderD* # Intel GPU
ls -l /dev/accel/accel0 # Intel NPU
groups # must include "render"
For other distributions and for Windows, follow Intel's OpenVINO GPU and NPU configuration pages.
B2. Check the OpenVINO devices
uv run python -c "
import openvino as ov
core = ov.Core()
for device in core.available_devices:
print(device, core.get_property(device, 'FULL_DEVICE_NAME'))
"
On a Core Ultra 5 225H with both drivers installed, this prints:
CPU Intel(R) Core(TM) Ultra 5 225H
GPU Intel(R) Arc(TM) Graphics (iGPU)
NPU Intel(R) AI Boost
CPU is always listed. If GPU or NPU is missing, see
Troubleshooting.
B3. Train on the CPU
Python API:
cat > train_cpu.py <<'EOF'
from nitid import NITID
data = "configs/datasets/coco-mini.yml"
model = NITID("model1s", task="detect", device="cpu")
metrics = model.train(
data=data,
epochs=2,
imgsz=640,
batch=4,
workers=2,
amp=False, # AMP is CUDA-only; this avoids a warning on the CPU
project="runs/train",
name="cpu-smoke",
)
print({key: value for key, value in metrics.items() if key != "history"})
EOF
uv run python train_cpu.py
Command line:
uv run nitid train model=model1s task=detect \
data=configs/datasets/coco-mini.yml \
epochs=2 imgsz=640 batch=4 workers=2 amp=false device=cpu name=cpu-smoke-cli
Keep imgsz=640. Export and OpenVINO inference use the checkpoint's evaluation
size, 640 for the pretrained models.
On an Intel Core Ultra 5 225H (14 cores), two epochs over 64 images took about
6 minutes: about 5 seconds per batch of 4 images, plus validation after each
epoch. Peak memory was about 5 GB. COCO-mini has 128 training images, so expect
about twice that. For a quicker first check, add fraction=0.25 to train on a
quarter of the images.
B4. Validate and predict with PyTorch
cat > after_cpu.py <<'EOF'
from nitid import NITID
data = "configs/datasets/coco-mini.yml"
image = "datasets/coco-mini/images/val/000000347930.jpg"
model = NITID("runs/train/cpu-smoke/best.pth", task="detect", device="cpu")
metrics = model.val(data=data, batch=4, project="runs/val", name="cpu-smoke")
print("mAP50-95:", metrics["mAP50-95"], "mAP50:", metrics["mAP50"])
results = model.predict(image, conf=0.5, save=True, project="runs/predict", name="cpu-smoke")
print(results[0].boxes.xyxy, results[0].boxes.cls, results[0].boxes.conf)
EOF
uv run python after_cpu.py
Validation of 16 images took about 30 seconds on the CPU, so the 32 COCO-mini validation images take about a minute.
B5. Export to OpenVINO IR
cat > export_openvino.py <<'EOF'
from nitid import NITID
model = NITID("runs/train/cpu-smoke/best.pth", task="detect", device="cpu")
print(model.export(format="openvino", project="runs/export", name="cpu-smoke-openvino"))
print(model.export(format="onnx", project="runs/export", name="cpu-smoke-onnx"))
EOF
uv run python export_openvino.py
This writes runs/export/cpu-smoke-openvino/dfine_640.xml and dfine_640.bin
(graph and weights), plus runs/export/cpu-smoke-onnx/dfine_640.onnx. Each
export takes about 10 seconds. Add half=True to store the IR weights in FP16.
B6. Predict with OpenVINO on the CPU, GPU, or NPU
backend="openvino" runs predict() through OpenVINO Runtime and returns the
same Results objects as the PyTorch backend:
cat > predict_openvino.py <<'EOF'
import openvino as ov
from nitid import NITID
image = "datasets/coco-mini/images/val/000000347930.jpg"
for device in ov.Core().available_devices: # e.g. ['CPU', 'GPU', 'NPU']
model = NITID("runs/train/cpu-smoke/best.pth", task="detect",
backend="openvino", device=device)
model.predict(image, conf=0.5, verbose=False) # first call compiles the model
results = model.predict(image, conf=0.5, save=True,
project="runs/predict", name=f"openvino-{device.lower()}")
print(device, len(results[0].boxes), "objects,",
f"{results[0].speed['inference']:.1f} ms inference")
EOF
uv run python predict_openvino.py
backend="openvino" does not read the exported .xml. It converts the
checkpoint in memory and compiles it for the device on the first predict()
call, then reuses it for later calls at the same imgsz. The nitid command
has no backend option, so OpenVINO inference is Python-only.
Measured on the Core Ultra 5 225H with the checkpoint trained above, one 640×480 image:
| Backend, device | First call (compile + infer) | Inference after that |
|---|---|---|
| PyTorch, CPU | about 4 s | — |
OpenVINO, "CPU" |
8 s | 55–65 ms |
OpenVINO, "GPU" (Arc iGPU) |
7 s | 15–20 ms |
OpenVINO, "NPU" (AI Boost) |
22 s | 50–60 ms |
PyTorch and OpenVINO on the CPU and GPU returned the same objects. The NPU
computes in reduced precision and can drop a detection scored just above
conf (in one run, 0.51 against conf=0.5), so expect small differences
between devices near the threshold. On this machine the integrated GPU
is the fastest device. device="auto" prefers the NPU, then the GPU, then the
CPU, so pass device="GPU" explicitly when the GPU is faster on your hardware.
B7. Run the exported IR without nitid
For deployment, the exported .xml needs only OpenVINO, OpenCV, and NumPy.
The input is one RGB image resized to 640×640, scaled to [0, 1], in
[1, 3, 640, 640] layout. The outputs are labels, boxes (xyxy, in
640×640 input pixels), and scores, with post-processing included:
cat > run_ir.py <<'EOF'
import sys
import cv2
import numpy as np
import openvino as ov
xml_path, image_path, device = sys.argv[1], sys.argv[2], sys.argv[3]
compiled = ov.Core().compile_model(xml_path, device)
bgr = cv2.imread(image_path)
h, w = bgr.shape[:2]
rgb = cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)
images = cv2.resize(rgb, (640, 640)).astype(np.float32).transpose(2, 0, 1)[None] / 255.0
labels, boxes, scores = compiled([images]).values()
keep = scores[0] > 0.5
boxes = boxes[0][keep] * np.array([w / 640, h / 640, w / 640, h / 640])
for label, box, score in zip(labels[0][keep], boxes, scores[0][keep]):
print(int(label), round(float(score), 3), box.round(1).tolist())
EOF
uv run python run_ir.py runs/export/cpu-smoke-openvino/dfine_640.xml \
datasets/coco-mini/images/val/000000347930.jpg GPU
Labels are 0-based indices into the names: list of
configs/datasets/coco-mini.yml. The boxes
match the PyTorch predictions to within a pixel; nitid's own preprocessing
resizes with PIL rather than OpenCV, so scores can differ slightly.
Where the outputs go
| Directory | Contents |
|---|---|
runs/train/<name>/ |
best.pth, last.pth, epoch1.pth, …, results.csv, results.png, PR/F1 curves, confusion matrices, args.yaml, environment.yaml |
runs/val/<name>/ |
Validation plots, args.yaml, environment.yaml |
runs/predict/<name>/ |
Images with drawn predictions (save=True) |
runs/export/<name>/ |
dfine_640.onnx, or dfine_640.xml + dfine_640.bin |
If <name> already exists, nitid appends a number (cpu-smoke2, …) instead
of overwriting. Each checkpoint is self-contained: model configuration, weights
and class names travel together. best.pth is the epoch with the highest
fitness, last.pth the latest one, and both hold EMA weights when EMA is on.
Instance segmentation
The dataset from step 2 also contains polygon masks, so the same steps work for
instance segmentation. Use task="segment" everywhere: NITID("model1s",
task="segment") downloads the segmentation weights from Hugging Face, and
results[0].masks holds the predicted masks. OpenVINO export and
backend="openvino" work for segmentation models too. On the CPU, one epoch over
16 images (fraction=0.25) took about 75 seconds and 6.3 GB of RAM, and the
validation summary adds mask_mAP50 and mask_mAP50-95. Semantic segmentation needs dense
PNG masks instead and is not covered here; see
Fine-tuning.
Troubleshooting
torch.cuda.is_available() returns False
nvidia-smifails: the NVIDIA driver is missing or not loaded. Install it (sudo ubuntu-drivers install) and reboot.nvidia-smiworks but PyTorch still reportsFalse: check thattorch.version.cudaprints12.1(or13.0with Python 3.13). If it printsNone, a CPU-only PyTorch has been installed over the locked one; runuv sync --extra trainagain.- Inside a container, start it with GPU access (
docker run --gpus all ...with the NVIDIA Container Toolkit installed).
CUDA driver version is insufficient for CUDA runtime version
The driver is older than the PyTorch build requires. Update it to 525.60.13 or newer for the default CUDA 12.1 build, or to the 580 series or newer for the CUDA 13.0 build that Python 3.13 environments use.
CUDA error: no kernel image is available for execution on the device
The PyTorch build in the environment was not compiled for this GPU.
- RTX 50 series or another Blackwell GPU with the default environment: recreate it on Python 3.13, whose locked PyTorch is built for CUDA 13.0 and supports compute capability up to 12.0:
bash
rm -rf .venv
uv sync --python 3.13 --extra train
- GTX 900/10 series or V100 (Maxwell, Pascal, Volta) in a Python 3.13
environment: CUDA 13.0 no longer supports them. Recreate the environment on
Python 3.12 (
uv sync --python 3.12 --extra train).
CUDA out of memory
Lower batch (8 → 4 → 2). To keep the effective batch size, add
accumulate=2 or accumulate=4. Keep imgsz=640. Close other processes on
the GPU (nvidia-smi lists them).
CPU training is very slow or the machine runs out of memory
CPU training is expected to be slow. Use fraction=0.25 or fewer epochs. If memory runs out, lower batch to 2 and
workers to 0. Other heavy processes on the same CPU slow training
considerably.
ImportError: libGL.so.1
The GUI build of OpenCV has replaced the headless one, usually because another
package pulled in opencv-python. See
Troubleshooting.
OpenVINO is not installed
Install the extra: uv sync --extra train --extra openvino. Running
uv sync --extra train alone later removes OpenVINO again, so always pass both
extras on the Intel machine.
OpenVINO device "GPU" or "NPU" is not available
nitid raises ValueError: OpenVINO device 'NPU' is not available on this
machine. Available devices: ['CPU']. Check, in order:
- The hardware exists:
lspci | grep -iE "vga|display"for the GPU;lspci | grep -i "processing accelerator"orlsmod | grep intel_vpufor the NPU. - The device node exists:
/dev/dri/renderD128(GPU),/dev/accel/accel0(NPU). A missing NPU node usually means the kernel is older than 6.6. - Your user is in the
rendergroup (groups). Afterusermod, log out and back in, or reboot. - The user-space driver is installed:
clinfo | grep "Device Name"lists the GPU; for the NPU,dpkg -l | grep npushows the three driver packages.
Then check again with the command in B2.
The first OpenVINO prediction is slow
The first predict() call converts and compiles the model for the device:
about 7–8 seconds on the CPU and GPU and over 20 seconds on the NPU in the
measurements above. Later calls on the same model object reuse the compiled
model.
Warnings during export
TracerWarning: torch.as_tensor results are registered as constants during
export, and nanobind: leaked ... instances! when Python exits after an
OpenVINO export, are expected and harmless. The export is complete when nitid
prints OpenVINO IR export saved to ....
Verification status
| Step | Status |
|---|---|
Install with uv sync, PyTorch 2.5.1+cu121, OpenVINO 2026.2 |
Run on Ubuntu 24.04 |
Anonymous git clone, automatic COCO-mini download, and the PyPI route in Without a clone |
Run on Ubuntu (CPU), 2026-10-10, with nitid 0.9.2 |
| Python 3.13 environment: PyTorch 2.13.0+cu130, built for sm_75 to sm_120 | Install and test suite run on the CPU; not run on a GPU |
COCO-mini dataset and checkpoint paths in NITID(...) |
Steps B3 to B7 re-run as written on the Core Ultra 5 225H (2026-10-05): two epochs, mAP50-95 0.60 on the 32 validation images |
| CPU training, Python API and CLI | Run on an Intel Core Ultra 5 225H |
| Validation, prediction, ONNX and OpenVINO export | Run on the CPU |
backend="openvino" on "CPU", "GPU", "NPU", and the standalone IR script |
Run on the Core Ultra 5 225H (Arc iGPU, AI Boost NPU) |
Instance segmentation: CPU training, OpenVINO export, backend="openvino" on "GPU" |
Run, one epoch on 16 images |
| CUDA training and inference | Not run: no NVIDIA GPU was available. Commands follow the same API |
| Driver installation on a fresh machine (NVIDIA, Intel GPU, Intel NPU) | Not run: the test machine already had the drivers |