use crate::errors::ModelError;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SSLMethod {
DINO,
MAE,
IJEPA,
VJEPA2,
EUPE,
CLIP,
SigLIP,
}
impl std::fmt::Display for SSLMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SSLMethod::DINO => write!(f, "DINO"),
SSLMethod::MAE => write!(f, "MAE"),
SSLMethod::IJEPA => write!(f, "I-JEPA"),
SSLMethod::VJEPA2 => write!(f, "V-JEPA 2"),
SSLMethod::EUPE => write!(f, "EUPE"),
SSLMethod::CLIP => write!(f, "CLIP"),
SSLMethod::SigLIP => write!(f, "SigLIP"),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum AvailabilityStatus {
Ready,
Planned,
}
impl std::fmt::Display for AvailabilityStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AvailabilityStatus::Ready => write!(f, "ready"),
AvailabilityStatus::Planned => write!(f, "planned"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Availability {
pub status: AvailabilityStatus,
pub phase: String,
pub note: String,
}
impl Availability {
fn ready(note: impl Into<String>) -> Self {
Self {
status: AvailabilityStatus::Ready,
phase: "Phase 1".to_string(),
note: note.into(),
}
}
fn planned(phase: impl Into<String>, note: impl Into<String>) -> Self {
Self {
status: AvailabilityStatus::Planned,
phase: phase.into(),
note: note.into(),
}
}
pub fn is_ready(&self) -> bool {
matches!(self.status, AvailabilityStatus::Ready)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Checksum {
Sha256(String),
Pending { reason: String },
}
impl Checksum {
pub fn label(&self) -> &'static str {
match self {
Checksum::Sha256(_) => "sha256",
Checksum::Pending { .. } => "pending",
}
}
pub fn note(&self) -> Option<&str> {
match self {
Checksum::Sha256(_) => None,
Checksum::Pending { reason } => Some(reason.as_str()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelInfo {
pub name: String,
pub architecture: String,
pub patch_size: u32,
pub embed_dim: u32,
pub num_layers: u32,
pub num_heads: u32,
pub method: SSLMethod,
pub input_size: u32,
pub params_m: u32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum TensorRole {
PatchSequence,
PatchAndClsSequence,
}
impl std::fmt::Display for TensorRole {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TensorRole::PatchSequence => write!(f, "patch sequence"),
TensorRole::PatchAndClsSequence => write!(f, "patch+cls sequence"),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PreprocessContract {
pub input_size: u32,
pub resize_filter: String,
pub color_space: String,
pub layout: String,
pub mean: [f32; 3],
pub std: [f32; 3],
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TensorContract {
pub name: String,
pub role: TensorRole,
pub cls_expected: bool,
pub batch_size: usize,
pub patch_count: usize,
pub embedding_dim: usize,
}
impl TensorContract {
pub fn expected_sequence_len(&self) -> usize {
self.patch_count + usize::from(self.cls_expected)
}
pub fn expected_shape(&self) -> Vec<usize> {
vec![
self.batch_size,
self.expected_sequence_len(),
self.embedding_dim,
]
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ParityTolerances {
pub patch_count_abs: usize,
pub embedding_dim_abs: usize,
pub patch_mean_abs: f32,
pub patch_std_abs: f32,
pub cls_l2_abs: f32,
#[serde(default = "default_parity_signal_tolerance")]
pub patch_rms_abs: f32,
#[serde(default = "default_parity_signal_tolerance")]
pub patch_signature_abs: f32,
#[serde(default = "default_parity_signal_tolerance")]
pub cls_signature_abs: f32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ModelValidationProfile {
pub source: String,
pub evidence_timestamp: String,
pub fixture_set: String,
pub preprocess: PreprocessContract,
pub tensor: TensorContract,
pub tolerances: ParityTolerances,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelArtifact {
pub relative_path: String,
pub download_url: String,
pub checksum: Checksum,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegistryEntry {
pub info: ModelInfo,
pub availability: Availability,
pub artifacts: Vec<ModelArtifact>,
pub norm_mean: [f32; 3],
pub norm_std: [f32; 3],
pub input_name: String,
pub output_name: String,
pub video_frames: Option<u32>,
pub validation: ModelValidationProfile,
}
impl RegistryEntry {
pub fn is_ready(&self) -> bool {
self.availability.is_ready()
}
pub fn availability_summary(&self) -> String {
format!("{}: {}", self.availability.phase, self.availability.note)
}
pub fn ensure_ready(&self) -> Result<(), ModelError> {
if self.is_ready() {
Ok(())
} else {
Err(ModelError::Unavailable {
name: self.info.name.clone(),
reason: self.availability_summary(),
})
}
}
pub fn primary_artifact(&self) -> Result<&ModelArtifact, ModelError> {
self.artifacts
.first()
.ok_or_else(|| ModelError::MissingDownloadMetadata(self.info.name.clone()))
}
pub fn verification_label(&self) -> &'static str {
self.artifacts
.first()
.map(|artifact| artifact.checksum.label())
.unwrap_or("pending")
}
pub fn verification_note(&self) -> Option<&str> {
self.artifacts
.first()
.and_then(|artifact| artifact.checksum.note())
}
}
fn default_parity_signal_tolerance() -> f32 {
1e-3
}
fn default_validation_profile(
source: &str,
preprocess: PreprocessContract,
tensor: TensorContract,
) -> ModelValidationProfile {
ModelValidationProfile {
source: source.to_string(),
evidence_timestamp: "2026-03-27T12:00:00Z".to_string(),
fixture_set: "standard".to_string(),
preprocess,
tensor,
tolerances: ParityTolerances {
patch_count_abs: 0,
embedding_dim_abs: 0,
patch_mean_abs: 1e-3,
patch_std_abs: 1e-3,
cls_l2_abs: 1e-3,
patch_rms_abs: default_parity_signal_tolerance(),
patch_signature_abs: default_parity_signal_tolerance(),
cls_signature_abs: default_parity_signal_tolerance(),
},
}
}
pub fn registry() -> Vec<RegistryEntry> {
let phase_three_note =
"Reserved for the multi-model milestone once ONNX output mapping and validation are in place.";
vec![
RegistryEntry {
info: ModelInfo {
name: "dinov2-vit-l14".to_string(),
architecture: "ViT-L/14".to_string(),
patch_size: 14,
embed_dim: 1024,
num_layers: 24,
num_heads: 16,
method: SSLMethod::DINO,
input_size: 224,
params_m: 304,
},
availability: Availability::ready(
"Reference Phase 1 model with preprocessing, caching, and ONNX session loading wired end-to-end.",
),
artifacts: vec![ModelArtifact {
relative_path: "dinov2-vit-l14.onnx".to_string(),
download_url:
"https://huggingface.co/onnx-community/dinov2-large/resolve/main/onnx/model.onnx".to_string(),
checksum: Checksum::Sha256(
"305351060a1939d944e2dbe97dd64e4937ce5a220dce254e4cd74c7e4777d6ac"
.to_string(),
),
}],
norm_mean: [0.485, 0.456, 0.406],
norm_std: [0.229, 0.224, 0.225],
input_name: "pixel_values".to_string(),
output_name: "last_hidden_state".to_string(),
video_frames: None,
validation: default_validation_profile(
"facebookresearch/dinov2",
PreprocessContract {
input_size: 224,
resize_filter: "lanczos3".to_string(),
color_space: "rgb".to_string(),
layout: "nchw".to_string(),
mean: [0.485, 0.456, 0.406],
std: [0.229, 0.224, 0.225],
},
TensorContract {
name: "last_hidden_state".to_string(),
role: TensorRole::PatchAndClsSequence,
cls_expected: true,
batch_size: 1,
patch_count: 256,
embedding_dim: 1024,
},
),
},
RegistryEntry {
info: ModelInfo {
name: "dinov3-vit-l14".to_string(),
architecture: "ViT-L/14".to_string(),
patch_size: 14,
embed_dim: 1024,
num_layers: 24,
num_heads: 16,
method: SSLMethod::DINO,
input_size: 224,
params_m: 304,
},
availability: Availability::planned(
"Phase 3",
"Reserved for the multi-model milestone; artifact metadata still needs to be pinned.",
),
artifacts: Vec::new(),
norm_mean: [0.485, 0.456, 0.406],
norm_std: [0.229, 0.224, 0.225],
input_name: "pixel_values".to_string(),
output_name: "last_hidden_state".to_string(),
video_frames: None,
validation: default_validation_profile(
"meta/dinov3",
PreprocessContract {
input_size: 224,
resize_filter: "lanczos3".to_string(),
color_space: "rgb".to_string(),
layout: "nchw".to_string(),
mean: [0.485, 0.456, 0.406],
std: [0.229, 0.224, 0.225],
},
TensorContract {
name: "last_hidden_state".to_string(),
role: TensorRole::PatchAndClsSequence,
cls_expected: true,
batch_size: 1,
patch_count: 256,
embedding_dim: 1024,
},
),
},
RegistryEntry {
info: ModelInfo {
name: "mae-vit-l16".to_string(),
architecture: "ViT-L/16".to_string(),
patch_size: 16,
embed_dim: 1024,
num_layers: 24,
num_heads: 16,
method: SSLMethod::MAE,
input_size: 224,
params_m: 304,
},
availability: Availability::planned("Phase 3", phase_three_note),
artifacts: vec![ModelArtifact {
relative_path: "mae-vit-l16.onnx".to_string(),
download_url:
"https://huggingface.co/facebook/vit-mae-large/resolve/main/model.onnx".to_string(),
checksum: Checksum::Pending {
reason: "Download metadata will be validated when MAE support lands in the comparison milestone."
.to_string(),
},
}],
norm_mean: [0.5, 0.5, 0.5],
norm_std: [0.5, 0.5, 0.5],
input_name: "pixel_values".to_string(),
output_name: "last_hidden_state".to_string(),
video_frames: None,
validation: default_validation_profile(
"facebookresearch/mae",
PreprocessContract {
input_size: 224,
resize_filter: "lanczos3".to_string(),
color_space: "rgb".to_string(),
layout: "nchw".to_string(),
mean: [0.5, 0.5, 0.5],
std: [0.5, 0.5, 0.5],
},
TensorContract {
name: "last_hidden_state".to_string(),
role: TensorRole::PatchSequence,
cls_expected: false,
batch_size: 1,
patch_count: 196,
embedding_dim: 1024,
},
),
},
RegistryEntry {
info: ModelInfo {
name: "clip-vit-l14".to_string(),
architecture: "ViT-L/14".to_string(),
patch_size: 14,
embed_dim: 1024,
num_layers: 24,
num_heads: 16,
method: SSLMethod::CLIP,
input_size: 224,
params_m: 304,
},
availability: Availability::planned("Phase 3", phase_three_note),
artifacts: vec![ModelArtifact {
relative_path: "clip-vit-l14.onnx".to_string(),
download_url:
"https://huggingface.co/openai/clip-vit-large-patch14/resolve/main/onnx/visual.onnx".to_string(),
checksum: Checksum::Pending {
reason: "Download metadata will be validated when CLIP support lands in the comparison milestone."
.to_string(),
},
}],
norm_mean: [0.481_454_67, 0.457_827_5, 0.408_210_73],
norm_std: [0.268_629_54, 0.261_302_6, 0.275_777_1],
input_name: "pixel_values".to_string(),
output_name: "last_hidden_state".to_string(),
video_frames: None,
validation: default_validation_profile(
"openai/clip-vit-large-patch14",
PreprocessContract {
input_size: 224,
resize_filter: "lanczos3".to_string(),
color_space: "rgb".to_string(),
layout: "nchw".to_string(),
mean: [0.481_454_67, 0.457_827_5, 0.408_210_73],
std: [0.268_629_54, 0.261_302_6, 0.275_777_1],
},
TensorContract {
name: "last_hidden_state".to_string(),
role: TensorRole::PatchAndClsSequence,
cls_expected: true,
batch_size: 1,
patch_count: 256,
embedding_dim: 1024,
},
),
},
RegistryEntry {
info: ModelInfo {
name: "ijepa-vit-h14".to_string(),
architecture: "ViT-H/14".to_string(),
patch_size: 14,
embed_dim: 1280,
num_layers: 32,
num_heads: 16,
method: SSLMethod::IJEPA,
input_size: 224,
params_m: 632,
},
availability: Availability::ready(
"ONNX community export with verified SHA-256 hashes for both model graph and external data.",
),
artifacts: vec![
ModelArtifact {
relative_path: "ijepa-vit-h14/model.onnx".to_string(),
download_url:
"https://huggingface.co/onnx-community/ijepa_vith14_1k/resolve/main/onnx/model.onnx".to_string(),
checksum: Checksum::Sha256(
"10b70b2151a5db382f03d52cfa0c223b0c6ea3c79e0f2068e0bc8f4ee2d6bfb8"
.to_string(),
),
},
ModelArtifact {
relative_path: "ijepa-vit-h14/model.onnx_data".to_string(),
download_url:
"https://huggingface.co/onnx-community/ijepa_vith14_1k/resolve/main/onnx/model.onnx_data".to_string(),
checksum: Checksum::Sha256(
"82ab3565d733de48f2e142a2289b92f15b990bd461fe18c4d79635d4c34ade6f"
.to_string(),
),
},
],
norm_mean: [0.485, 0.456, 0.406],
norm_std: [0.229, 0.224, 0.225],
input_name: "pixel_values".to_string(),
output_name: "last_hidden_state".to_string(),
video_frames: None,
validation: default_validation_profile(
"facebookresearch/ijepa",
PreprocessContract {
input_size: 224,
resize_filter: "lanczos3".to_string(),
color_space: "rgb".to_string(),
layout: "nchw".to_string(),
mean: [0.485, 0.456, 0.406],
std: [0.229, 0.224, 0.225],
},
TensorContract {
name: "last_hidden_state".to_string(),
role: TensorRole::PatchSequence,
cls_expected: false,
batch_size: 1,
patch_count: 256,
embedding_dim: 1280,
},
),
},
RegistryEntry {
info: ModelInfo {
name: "vjepa2-vitl-fpc2-256".to_string(),
architecture: "ViT-L/16".to_string(),
patch_size: 16,
embed_dim: 1024,
num_layers: 24,
num_heads: 16,
method: SSLMethod::VJEPA2,
input_size: 256,
params_m: 304,
},
availability: Availability::ready(
"V-JEPA 2 ViT-L encoder exported to ONNX with 2-frame video input for single-image latent inspection.",
),
artifacts: vec![
ModelArtifact {
relative_path: "vjepa2-vitl-fpc2-256/model.onnx".to_string(),
download_url:
"https://huggingface.co/abdelstark/vjepa2-vitl-fpc2-256-onnx/resolve/main/model.onnx"
.to_string(),
checksum: Checksum::Sha256(
"942f72f0f1afe2bea855160c8f11080cd4d322b54a04e5e671fb96beb8ce6537"
.to_string(),
),
},
ModelArtifact {
relative_path: "vjepa2-vitl-fpc2-256/model.onnx_data".to_string(),
download_url:
"https://huggingface.co/abdelstark/vjepa2-vitl-fpc2-256-onnx/resolve/main/model.onnx_data"
.to_string(),
checksum: Checksum::Sha256(
"3a82e4d3cb3eaf1c13209daa08bb8ad7a408a4cc8bff3bee48978a8bfd34e640"
.to_string(),
),
},
],
norm_mean: [0.485, 0.456, 0.406],
norm_std: [0.229, 0.224, 0.225],
input_name: "pixel_values_videos".to_string(),
output_name: "last_hidden_state".to_string(),
video_frames: Some(2),
validation: default_validation_profile(
"facebookresearch/vjepa2",
PreprocessContract {
input_size: 256,
resize_filter: "lanczos3".to_string(),
color_space: "rgb".to_string(),
layout: "ntchw".to_string(),
mean: [0.485, 0.456, 0.406],
std: [0.229, 0.224, 0.225],
},
TensorContract {
name: "last_hidden_state".to_string(),
role: TensorRole::PatchSequence,
cls_expected: false,
batch_size: 1,
patch_count: 256,
embedding_dim: 1024,
},
),
},
RegistryEntry {
info: ModelInfo {
name: "eupe-vit-b16".to_string(),
architecture: "ViT-B/16".to_string(),
patch_size: 16,
embed_dim: 768,
num_layers: 12,
num_heads: 12,
method: SSLMethod::EUPE,
input_size: 224,
params_m: 86,
},
availability: Availability::ready(
"EUPE ViT-B/16 distilled from multiple domain-expert teachers into a compact efficient encoder.",
),
artifacts: vec![
ModelArtifact {
relative_path: "eupe-vit-b16/model.onnx".to_string(),
download_url:
"https://huggingface.co/abdelstark/eupe-vit-b16-onnx/resolve/main/model.onnx"
.to_string(),
checksum: Checksum::Sha256(
"01e5483095a6e3e171394e00436c0ca1b38e9d6b478cdb2266df9fbf4f068c8d"
.to_string(),
),
},
ModelArtifact {
relative_path: "eupe-vit-b16/model.onnx_data".to_string(),
download_url:
"https://huggingface.co/abdelstark/eupe-vit-b16-onnx/resolve/main/model.onnx_data"
.to_string(),
checksum: Checksum::Sha256(
"10a459ecc03a82fd48a54dae62f019d591d09b2dbb0c48fe765aef8534842749"
.to_string(),
),
},
],
norm_mean: [0.485, 0.456, 0.406],
norm_std: [0.229, 0.224, 0.225],
input_name: "pixel_values".to_string(),
output_name: "last_hidden_state".to_string(),
video_frames: None,
validation: default_validation_profile(
"facebookresearch/eupe",
PreprocessContract {
input_size: 224,
resize_filter: "lanczos3".to_string(),
color_space: "rgb".to_string(),
layout: "nchw".to_string(),
mean: [0.485, 0.456, 0.406],
std: [0.229, 0.224, 0.225],
},
TensorContract {
name: "last_hidden_state".to_string(),
role: TensorRole::PatchAndClsSequence,
cls_expected: true,
batch_size: 1,
patch_count: 196,
embedding_dim: 768,
},
),
},
RegistryEntry {
info: ModelInfo {
name: "siglip-so400m".to_string(),
architecture: "ViT-SO400M/14".to_string(),
patch_size: 14,
embed_dim: 1152,
num_layers: 27,
num_heads: 16,
method: SSLMethod::SigLIP,
input_size: 224,
params_m: 400,
},
availability: Availability::planned("Phase 3", phase_three_note),
artifacts: vec![ModelArtifact {
relative_path: "siglip-so400m.onnx".to_string(),
download_url:
"https://huggingface.co/google/siglip-so400m-patch14-224/resolve/main/onnx/model.onnx".to_string(),
checksum: Checksum::Pending {
reason: "Download metadata will be validated when SigLIP support lands in the comparison milestone."
.to_string(),
},
}],
norm_mean: [0.5, 0.5, 0.5],
norm_std: [0.5, 0.5, 0.5],
input_name: "pixel_values".to_string(),
output_name: "last_hidden_state".to_string(),
video_frames: None,
validation: default_validation_profile(
"google/siglip-so400m-patch14-224",
PreprocessContract {
input_size: 224,
resize_filter: "lanczos3".to_string(),
color_space: "rgb".to_string(),
layout: "nchw".to_string(),
mean: [0.5, 0.5, 0.5],
std: [0.5, 0.5, 0.5],
},
TensorContract {
name: "last_hidden_state".to_string(),
role: TensorRole::PatchAndClsSequence,
cls_expected: true,
batch_size: 1,
patch_count: 256,
embedding_dim: 1152,
},
),
},
]
}
pub fn find(name: &str) -> Option<RegistryEntry> {
registry().into_iter().find(|entry| entry.info.name == name)
}
pub fn find_ready(name: &str) -> Result<RegistryEntry, ModelError> {
let entry = find(name).ok_or_else(|| ModelError::NotFound(name.to_string()))?;
entry.ensure_ready()?;
Ok(entry)
}
pub fn model_names() -> Vec<String> {
registry()
.into_iter()
.map(|entry| entry.info.name)
.collect()
}
pub fn ready_model_names() -> Vec<String> {
registry()
.into_iter()
.filter(RegistryEntry::is_ready)
.map(|entry| entry.info.name)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registry_contains_current_and_planned_models() {
let names = model_names();
assert_eq!(names.len(), 8);
assert!(names.contains(&"dinov2-vit-l14".to_string()));
assert!(names.contains(&"dinov3-vit-l14".to_string()));
assert!(names.contains(&"mae-vit-l16".to_string()));
assert!(names.contains(&"clip-vit-l14".to_string()));
assert!(names.contains(&"ijepa-vit-h14".to_string()));
assert!(names.contains(&"vjepa2-vitl-fpc2-256".to_string()));
assert!(names.contains(&"eupe-vit-b16".to_string()));
assert!(names.contains(&"siglip-so400m".to_string()));
}
#[test]
fn test_ready_models() {
let ready = ready_model_names();
assert_eq!(
ready,
vec![
"dinov2-vit-l14".to_string(),
"ijepa-vit-h14".to_string(),
"vjepa2-vitl-fpc2-256".to_string(),
"eupe-vit-b16".to_string(),
]
);
}
#[test]
fn test_find_ready_rejects_planned_models() {
let error = find_ready("clip-vit-l14").unwrap_err();
assert!(matches!(error, ModelError::Unavailable { .. }));
}
}