DINOv2 Technical Reference¶
spatialhub.models.dinov2 provides an ONNX Runtime adapter for DINOv2, extracting global L2-normalized CLS token feature embeddings from images.
Supported Model Variants¶
The DINOv2Adapter supports 4 Vision Transformer backbone variants via model_variant:
Model Variant (model_variant) |
ONNX File | Parameter Count | Embedding Dimension (\(D\)) | Architecture & Role |
|---|---|---|---|---|
"vits14" |
dinov2_vits14.onnx |
~21M | \(D = 384\) | ViT-Small/14 backbone (high throughput, minimal memory footprint). |
"vitb14" |
dinov2_vitb14.onnx |
~86M | \(D = 768\) | ViT-Base/14 backbone (balanced descriptor capacity). |
"vitl14" (Default) |
dinov2_vitl14.onnx |
~300M | \(D = 1024\) | ViT-Large/14 backbone (high semantic representation capacity). |
"vitg14" |
dinov2_vitg14.onnx |
~1.1B | \(D = 1536\) | ViT-Giant/14 backbone (maximum representation capacity). |
Preprocessing & Post-processing Flow¶
DINOv2 extracts global feature embeddings from input images through square padding, bilinear resizing, and ImageNet channel normalization.
graph TD
A["Input Image(s): NumPy / Path / List"] --> B["Square Pad (Preserve Aspect Ratio)"]
B --> C["Bilinear Resize (target_size x target_size)"]
C --> D["Scale [0.0, 1.0] & ImageNet Channel Normalization"]
D --> E["ONNX Runtime Forward Pass"]
E --> F["Extract Raw CLS Token Embedding (N, D)"]
F --> G{"l2_normalize=True?"}
G -- "Yes" --> H["Feature L2 Normalization"]
G -- "No" --> I["Keep Raw Embeddings"]
H --> J["FeatureExtractionResult"]
I --> J
Square Padding & Spatial Resizing¶
Given an input image with native dimensions \((W, H)\), the image is zero-padded along the shorter dimension to form a square canvas of side \(S = \max(W, H)\), preserving aspect ratio:
The square canvas is resized to spatial dimension \(S_{\text{target}} \times S_{\text{target}}\) (target_size, default \(224 \times 224\)), which must be a positive integer multiple of the ViT patch size (\(14\text{ px}\)):
ImageNet Normalization¶
Pixel values scaled to \([0.0, 1.0]\) are normalized per channel \(c \in \{R, G, B\}\):
Where \(\mu = [0.485, 0.456, 0.406]\) and \(\sigma = [0.229, 0.224, 0.225]\).
L2 Feature Normalization¶
When l2_normalize=True, extracted CLS token embedding vectors \(v \in \mathbb{R}^D\) are normalized to unit Euclidean length:
Numerical Parity Verification¶
Numerical parity evaluates mathematical agreement between the original PyTorch Hub reference models (facebookresearch/dinov2) and the SpatialHub ONNX Runtime adapter on \(20\) benchmark images sampled from the COCO 2017 validation dataset (val2017.zip extracted to .cache/images/) at \(224 \times 224\) resolution.
| Model Variant | Cosine Sim | Feature L2 Dist | Feature MAE | Max Diff | Rel Error (%) |
|---|---|---|---|---|---|
vits14 |
0.999999 | 0.001066 | \(4.32 \times 10^{-5}\) | \(3.20 \times 10^{-4}\) | 0.11% |
vitb14 |
1.000000 | 0.000874 | \(2.50 \times 10^{-5}\) | \(1.38 \times 10^{-4}\) | 0.09% |
vitl14 |
1.000000 | 0.000816 | \(2.02 \times 10^{-5}\) | \(1.96 \times 10^{-4}\) | 0.08% |
vitg14 |
1.000000 | 0.000305 | \(6.19 \times 10^{-6}\) | \(3.90 \times 10^{-5}\) | 0.03% |
To evaluate parity on local sample images:
uv run tools/benchmark/parity_dinov2.py \
--variant all \
--data-dir .cache/images \
--output-file .profile/parity_dinov2.md
Performance Benchmarks¶
Latency, throughput, and memory footprint measured across \(10\) unmeasured warmup iterations and \(50\) timed measurement iterations at \(224 \times 224\) resolution.
| Variant | Stage | Latency Mean (ms) | Median (ms) | P95 (ms) | Throughput (FPS) | Peak RAM Delta | Peak VRAM Delta |
|---|---|---|---|---|---|---|---|
vits14 |
Preprocess | 1.07 +/- 0.13 | 1.08 | 1.29 | 931.7 | +0.6 MB | 0.0 MB |
| Inference | 4.73 +/- 0.58 | 4.61 | 5.64 | 211.4 | +0.2 MB | +6.0 MB | |
| End-to-End | 5.44 +/- 0.66 | 5.38 | 6.37 | 183.7 | +2.9 MB | 0.0 MB | |
vitb14 |
Preprocess | 1.28 +/- 0.16 | 1.22 | 1.56 | 784.3 | +1.1 MB | 0.0 MB |
| Inference | 8.50 +/- 0.59 | 8.28 | 9.66 | 117.7 | 0.0 MB | +10.0 MB | |
| End-to-End | 9.20 +/- 0.51 | 9.05 | 10.35 | 108.7 | +1.1 MB | 0.0 MB | |
vitl14 |
Preprocess | 1.24 +/- 0.15 | 1.20 | 1.63 | 806.4 | +1.1 MB | 0.0 MB |
| Inference | 29.00 +/- 2.60 | 29.76 | 31.70 | 34.5 | 0.0 MB | +14.0 MB | |
| End-to-End | 32.71 +/- 0.98 | 32.24 | 34.47 | 30.6 | +1.0 MB | 0.0 MB | |
vitg14 |
Preprocess | 1.17 +/- 0.11 | 1.13 | 1.42 | 854.4 | +1.1 MB | 0.0 MB |
| Inference | 133.93 +/- 16.64 | 121.71 | 156.30 | 7.5 | +0.6 MB | +20.0 MB | |
| End-to-End | 192.66 +/- 37.56 | 188.61 | 277.27 | 5.2 | +2.7 MB | 0.0 MB |
Hardware Power & Thermal Scaling on vitg14
On vitg14 (~1.1B parameters), isolated inference reflects peak GPU boost clocks (~1575 MHz at 140W TGP, ~133.9 ms). Under continuous sequential load across benchmark stages, GPU thermal management and power limits throttle operating clocks to steady-state frequencies (~1110–1200 MHz at ~89W, ~192.7 ms).
| Variant | Stage | Latency Mean (ms) | Median (ms) | P95 (ms) | Throughput (FPS) | Peak RAM Delta |
|---|---|---|---|---|---|---|
vits14 |
Preprocess | 1.26 +/- 0.14 | 1.24 | 1.54 | 794.6 | +1.7 MB |
| Inference | 43.96 +/- 4.27 | 42.78 | 52.61 | 22.7 | +1.1 MB | |
| End-to-End | 44.94 +/- 1.97 | 44.79 | 47.99 | 22.3 | 0.0 MB | |
vitb14 |
Preprocess | 1.38 +/- 0.17 | 1.36 | 1.74 | 722.2 | 0.0 MB |
| Inference | 141.89 +/- 6.92 | 140.81 | 156.49 | 7.0 | +0.1 MB | |
| End-to-End | 144.51 +/- 8.15 | 143.84 | 153.65 | 6.9 | 0.0 MB | |
vitl14 |
Preprocess | 1.26 +/- 0.04 | 1.27 | 1.34 | 791.5 | +1.7 MB |
| Inference | 512.34 +/- 9.32 | 512.58 | 530.10 | 2.0 | 0.0 MB | |
| End-to-End | 521.78 +/- 15.71 | 520.54 | 542.00 | 1.9 | 0.0 MB | |
vitg14 |
Preprocess | 1.48 +/- 0.11 | 1.46 | 1.69 | 674.3 | +1.9 MB |
| Inference | 1959.85 +/- 38.62 | 1963.05 | 2030.01 | 0.5 | +3.2 MB | |
| End-to-End | 1916.91 +/- 26.12 | 1912.71 | 1967.96 | 0.5 | 0.0 MB |
SpatialHub Adapter API & Usage¶
from spatialhub.models.dinov2 import DINOv2Adapter
# Initialize DINOv2 adapter with ViT-L/14 backbone (1024-dim)
with DINOv2Adapter(
model_variant="vitl14",
target_size=224,
providers=["CUDAExecutionProvider", "CPUExecutionProvider"],
) as extractor:
# Single image input (path or array)
result_single = extractor.extract_features("sample.jpg", l2_normalize=True)
print("Single embedding shape:", result_single.features.shape) # (1, 1024)
# Batch or collection input (list of images or paths)
result_batch = extractor.extract_features(["image_0.jpg", "image_1.jpg"], l2_normalize=True)
print("Batch embedding shape:", result_batch.features.shape) # (2, 1024)
Tooling & Verification Commands¶
Export DINOv2 backbone models from PyTorch Hub to ONNX format:
# Export a specific variant
uv run tools/export/export_dinov2.py \
--variant vitl14 \
--output-folder onnx_weight \
--opset 17
# Export all variants
uv run tools/export/export_dinov2.py \
--variant all \
--output-folder onnx_weight
| Parameter | Type | Default | Description |
|---|---|---|---|
--variant |
str |
"vitl14" |
Model variant to export (vits14, vitb14, vitl14, vitg14, or all). |
--output-folder |
str |
"onnx_weight" |
Destination directory for exported .onnx models. |
--width |
int |
224 |
Input image width in pixels (must be a multiple of 14). |
--height |
int |
224 |
Input image height in pixels (must be a multiple of 14). |
--image-size |
int |
None |
Convenience parameter to set square dimensions (width = height = image_size). |
--opset |
int |
17 |
ONNX Operator Set version. |
Evaluate numerical parity between PyTorch Hub reference models and SpatialHub ONNX Runtime adapter:
uv run tools/benchmark/parity_dinov2.py \
--variant all \
--data-dir .cache/images \
--output-file .profile/parity_dinov2.md
| Parameter | Type | Default | Description |
|---|---|---|---|
--variant |
str |
"all" |
Model variant to evaluate (vits14, vitb14, vitl14, vitg14, or all). |
--data-dir |
str |
".cache/images" |
Directory containing benchmark image files. |
--model-dir |
str |
None |
Optional directory containing local .onnx weight files. |
--max-images |
int |
20 |
Maximum number of images to evaluate. |
--output-file |
str |
None |
Optional path to output markdown report file. |
Benchmark stage-by-stage latency, throughput, and memory footprint:
uv run tools/benchmark/profile_dinov2.py \
--variant all \
--provider all \
--data-dir .cache/images \
--warmup 10 \
--iters 50 \
--output-file .profile/profile_dinov2.md
| Parameter | Type | Default | Description |
|---|---|---|---|
--variant |
str |
"all" |
Model variant to profile (vits14, vitb14, vitl14, vitg14, or all). |
--provider |
str |
"all" |
Target execution provider filter (cuda, cpu, or all). |
--data-dir |
str |
".cache/images" |
Directory containing sample image for profiling. |
--model-dir |
str |
None |
Optional directory containing local .onnx weight files. |
--warmup |
int |
10 |
Number of unmeasured warmup iterations. |
--iters |
int |
50 |
Number of timed measurement iterations. |
--output-file |
str |
None |
Optional path to save markdown benchmark report. |
Returned Result Data Structure¶
Returns a FeatureExtractionResult dataclass:
| Attribute | Type | Shape | Description |
|---|---|---|---|
images |
np.ndarray |
(H, W, 3) or (N, H, W, 3) |
Original input image array or stacked batch array. |
features |
np.ndarray |
(N, D) float32 |
Extracted global CLS token feature embedding vectors. |
embedding_type |
str |
N/A | Feature scope ("global"). |
l2_normalized |
bool |
N/A | Flag indicating whether feature embeddings are unit L2-normalized. |