use crate::models::lazy_dinov2::{Dinov2Config, Dinov2Model, Dinov2Weights};
use fuel_ir::Shape;
use fuel_ir::error::Result;
use fuel_tensor::{lazy::Tensor, lazy::WeightStorage};
use std::sync::Arc;
#[derive(Debug, Clone, PartialEq)]
pub struct DepthAnythingV2Config {
pub out_channel_sizes: [usize; 4],
pub in_channel_size: usize,
pub num_features: usize,
pub use_batch_norm: bool,
pub use_class_token: bool,
pub layer_ids_vits: Vec<usize>,
pub input_image_size: usize,
pub target_patch_size: usize,
}
impl DepthAnythingV2Config {
pub fn vit_small() -> Self {
Self {
out_channel_sizes: [48, 96, 192, 384],
in_channel_size: 384,
num_features: 64,
use_batch_norm: false,
use_class_token: false,
layer_ids_vits: vec![2, 5, 8, 11],
input_image_size: 518,
target_patch_size: 518 / 14,
}
}
pub fn vit_base() -> Self {
Self {
out_channel_sizes: [96, 192, 384, 768],
in_channel_size: 768,
num_features: 128,
use_batch_norm: false,
use_class_token: false,
layer_ids_vits: vec![2, 5, 8, 11],
input_image_size: 518,
target_patch_size: 518 / 14,
}
}
pub fn vit_large() -> Self {
Self {
out_channel_sizes: [256, 512, 1024, 1024],
in_channel_size: 1024,
num_features: 256,
use_batch_norm: false,
use_class_token: false,
layer_ids_vits: vec![4, 11, 17, 23],
input_image_size: 518,
target_patch_size: 518 / 14,
}
}
}
#[derive(Debug, Clone)]
pub struct Conv2dWeights {
pub w: Arc<[f32]>,
pub b: Option<Arc<[f32]>>,
pub c_in: usize,
pub c_out: usize,
pub k: usize,
pub stride: usize,
pub pad: usize,
}
#[derive(Debug, Clone)]
pub struct ConvTranspose2dWeights {
pub w: Arc<[f32]>,
pub b: Option<Arc<[f32]>>,
pub c_in: usize,
pub c_out: usize,
pub k: usize,
pub stride: usize,
}
#[derive(Debug, Clone)]
pub struct ResidualConvUnitWeights {
pub conv1: Conv2dWeights,
pub conv2: Conv2dWeights,
}
#[derive(Debug, Clone)]
pub struct FeatureFusionBlockWeights {
pub res_conv_unit1: ResidualConvUnitWeights,
pub res_conv_unit2: ResidualConvUnitWeights,
pub output_conv: Conv2dWeights,
pub target_patch_size: usize,
}
#[derive(Debug, Clone)]
pub enum ResizeLayer {
ConvTranspose(ConvTranspose2dWeights),
Identity,
ConvStride2(Conv2dWeights),
}
#[derive(Debug, Clone)]
pub struct ScratchWeights {
pub layer1_rn: Conv2dWeights,
pub layer2_rn: Conv2dWeights,
pub layer3_rn: Conv2dWeights,
pub layer4_rn: Conv2dWeights,
pub refine_net1: FeatureFusionBlockWeights,
pub refine_net2: FeatureFusionBlockWeights,
pub refine_net3: FeatureFusionBlockWeights,
pub refine_net4: FeatureFusionBlockWeights,
pub output_conv1: Conv2dWeights,
pub output_conv2a: Conv2dWeights,
pub output_conv2b: Conv2dWeights,
}
#[derive(Debug, Clone)]
pub struct DPTHeadWeights {
pub projections: [Conv2dWeights; 4],
pub resize_layers: [ResizeLayer; 4],
pub scratch: ScratchWeights,
}
#[derive(Debug, Clone)]
pub struct DepthAnythingV2Weights {
pub dinov2: Dinov2Weights,
pub depth_head: DPTHeadWeights,
}
#[derive(Debug, Clone)]
pub struct DepthAnythingV2Model {
pub config: DepthAnythingV2Config,
pub dinov2_config: Dinov2Config,
pub weights: DepthAnythingV2Weights,
}
impl DepthAnythingV2Model {
pub fn forward(&self, image: &Tensor) -> Result<Tensor> {
let cfg = &self.config;
assert!(
!cfg.use_batch_norm,
"DepthAnythingV2 v1: use_batch_norm=true is future work",
);
assert!(
!cfg.use_class_token,
"DepthAnythingV2 v1: use_class_token=true is future work",
);
assert_eq!(
cfg.layer_ids_vits.len(),
4,
"layer_ids_vits must have length 4"
);
let dinov2 = Dinov2Model {
config: self.dinov2_config.clone(),
weights: self.weights.dinov2.clone(),
};
let features = dinov2.forward_intermediate_layers(image, &cfg.layer_ids_vits)?;
self.depth_head_forward(image, &features)
}
#[allow(clippy::needless_range_loop)]
fn depth_head_forward(&self, anchor: &Tensor, features: &[Tensor]) -> Result<Tensor> {
assert_eq!(features.len(), 4, "DPT head expects 4 feature levels");
let cfg = &self.config;
let n_patches_per_side = cfg.target_patch_size;
let head = &self.weights.depth_head;
let mut pyramid: Vec<Tensor> = Vec::with_capacity(4);
for i in 0..4 {
let f = &features[i];
let dims = f.shape();
let dims = dims.dims();
let batch = dims[0];
let embed_dim = dims[2];
let patches = f.slice(1_usize, 1, dims[1] - 1)?;
let x = patches
.permute([0, 2, 1_usize])?
.reshape(Shape::from_dims(&[
batch,
embed_dim,
n_patches_per_side,
n_patches_per_side,
]))?;
let projected = apply_conv2d(&x, &head.projections[i], anchor)?;
let resized = apply_resize_layer(&projected, &head.resize_layers[i], anchor)?;
pyramid.push(resized);
}
let layer_1_rn = apply_conv2d(&pyramid[0], &head.scratch.layer1_rn, anchor)?;
let layer_2_rn = apply_conv2d(&pyramid[1], &head.scratch.layer2_rn, anchor)?;
let layer_3_rn = apply_conv2d(&pyramid[2], &head.scratch.layer3_rn, anchor)?;
let layer_4_rn = apply_conv2d(&pyramid[3], &head.scratch.layer4_rn, anchor)?;
let path4 =
apply_feature_fusion_block(&layer_4_rn, None, &head.scratch.refine_net4, anchor)?;
let path3 = apply_feature_fusion_block(
&layer_3_rn,
Some(&path4),
&head.scratch.refine_net3,
anchor,
)?;
let path2 = apply_feature_fusion_block(
&layer_2_rn,
Some(&path3),
&head.scratch.refine_net2,
anchor,
)?;
let path1 = apply_feature_fusion_block(
&layer_1_rn,
Some(&path2),
&head.scratch.refine_net1,
anchor,
)?;
let out = apply_conv2d(&path1, &head.scratch.output_conv1, anchor)?;
let out = out.interpolate2d(cfg.input_image_size, cfg.input_image_size)?;
let out = apply_conv2d(&out, &head.scratch.output_conv2a, anchor)?;
let out = out.relu();
let out = apply_conv2d(&out, &head.scratch.output_conv2b, anchor)?;
let out = out.relu();
Ok(out.relu())
}
}
fn apply_conv2d(x: &Tensor, c: &Conv2dWeights, anchor: &Tensor) -> Result<Tensor> {
let w = anchor.const_f32_like(
Arc::clone(&c.w),
Shape::from_dims(&[c.c_out, c.c_in, c.k, c.k]),
)?;
let b = c
.b
.as_ref()
.map(|b| {
let storage = WeightStorage::F32(Arc::clone(b));
match storage {
WeightStorage::F32(arr) => anchor.const_f32_like(arr, Shape::from_dims(&[c.c_out])),
_ => unreachable!(),
}
})
.transpose()?;
let out = x.conv2d(&w, b.as_ref(), (c.stride, c.stride), (c.pad, c.pad), 1)?;
Ok(out)
}
fn apply_conv_transpose2d(
x: &Tensor,
c: &ConvTranspose2dWeights,
anchor: &Tensor,
) -> Result<Tensor> {
let w = anchor.const_f32_like(
Arc::clone(&c.w),
Shape::from_dims(&[c.c_in, c.c_out, c.k, c.k]),
)?;
let mut out = x.conv_transpose2d(&w, (c.stride, c.stride), (0, 0), (0, 0), (1, 1), 1)?;
if let Some(b) = &c.b {
let bias = anchor
.const_f32_like(Arc::clone(b), Shape::from_dims(&[c.c_out]))?
.reshape(Shape::from_dims(&[1, c.c_out, 1, 1]))?;
out = out.broadcast_add(&bias)?;
}
Ok(out)
}
fn apply_resize_layer(x: &Tensor, rl: &ResizeLayer, anchor: &Tensor) -> Result<Tensor> {
match rl {
ResizeLayer::ConvTranspose(ct) => apply_conv_transpose2d(x, ct, anchor),
ResizeLayer::Identity => Ok(x.clone()),
ResizeLayer::ConvStride2(c) => apply_conv2d(x, c, anchor),
}
}
fn apply_residual_conv_unit(
x: &Tensor,
rcu: &ResidualConvUnitWeights,
anchor: &Tensor,
) -> Result<Tensor> {
let y = x.relu();
let y = apply_conv2d(&y, &rcu.conv1, anchor)?;
let y = y.relu();
let y = apply_conv2d(&y, &rcu.conv2, anchor)?;
x.add(&y)
}
fn apply_feature_fusion_block(
bottom_input: &Tensor,
top_input: Option<&Tensor>,
ffb: &FeatureFusionBlockWeights,
anchor: &Tensor,
) -> Result<Tensor> {
let acc = if let Some(top) = top_input {
let bottom_refined = apply_residual_conv_unit(bottom_input, &ffb.res_conv_unit1, anchor)?;
top.add(&bottom_refined)?
} else {
bottom_input.clone()
};
let y = apply_residual_conv_unit(&acc, &ffb.res_conv_unit2, anchor)?;
let y = y.interpolate2d(ffb.target_patch_size, ffb.target_patch_size)?;
apply_conv2d(&y, &ffb.output_conv, anchor)
}
impl DepthAnythingV2Weights {
pub fn load_from_mmapped(
_st: &fuel_loaders::safetensors::MmapedSafetensors,
_cfg: &DepthAnythingV2Config,
) -> Result<Self> {
Err(fuel_ir::error::Error::Msg(
"DepthAnythingV2Weights::load_from_mmapped: DPT scratch/fusion \
block naming pending; construct DepthAnythingV2Weights via \
the explicit struct literal or contribute the loader."
.to_string(),
)
.bt())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::lazy_dinov2::{Dinov2BlockWeights, Dinov2Weights};
use fuel_tensor::Device;
fn rng_seed(seed: u32) -> impl FnMut() -> f32 {
let mut s = seed;
move || {
s = s.wrapping_mul(1103515245).wrapping_add(12345);
((s >> 16) as u16 as f32 / 65535.0 - 0.5) * 0.05
}
}
fn vec_of(n: usize, nb: &mut dyn FnMut() -> f32) -> Arc<[f32]> {
Arc::from((0..n).map(|_| nb()).collect::<Vec<_>>())
}
fn tiny_dinov2_config() -> Dinov2Config {
Dinov2Config {
embed_dim: 8,
depth: 4,
num_heads: 2,
num_channels: 3,
image_size: 14,
patch_size: 7,
mlp_ratio: 4,
layer_norm_eps: 1e-5,
num_classes: 10,
}
}
fn tiny_dinov2_weights(cfg: &Dinov2Config) -> Dinov2Weights {
let mut nb = rng_seed(1234);
let e = cfg.embed_dim;
let n_patches = cfg.num_patches();
let blocks: Vec<Dinov2BlockWeights> = (0..cfg.depth)
.map(|_| Dinov2BlockWeights {
norm1_gain: Arc::from(vec![1.0_f32; e]),
norm1_bias: Arc::from(vec![0.0_f32; e]),
qkv: WeightStorage::F32(vec_of(e * 3 * e, &mut nb)),
qkv_bias: vec_of(3 * e, &mut nb),
proj: WeightStorage::F32(vec_of(e * e, &mut nb)),
proj_bias: vec_of(e, &mut nb),
ls1_gamma: Arc::from(vec![1.0_f32; e]),
norm2_gain: Arc::from(vec![1.0_f32; e]),
norm2_bias: Arc::from(vec![0.0_f32; e]),
fc1: WeightStorage::F32(vec_of(e * cfg.mlp_hidden(), &mut nb)),
fc1_bias: vec_of(cfg.mlp_hidden(), &mut nb),
fc2: WeightStorage::F32(vec_of(cfg.mlp_hidden() * e, &mut nb)),
fc2_bias: vec_of(e, &mut nb),
ls2_gamma: Arc::from(vec![1.0_f32; e]),
})
.collect();
Dinov2Weights {
patch_proj: vec_of(
e * cfg.num_channels * cfg.patch_size * cfg.patch_size,
&mut nb,
),
patch_proj_bias: vec_of(e, &mut nb),
cls_token: vec_of(e, &mut nb),
pos_embed: vec_of((n_patches + 1) * e, &mut nb),
blocks,
final_ln_gain: Arc::from(vec![1.0_f32; e]),
final_ln_bias: Arc::from(vec![0.0_f32; e]),
head: WeightStorage::F32(vec_of(2 * e * cfg.num_classes, &mut nb)),
head_bias: vec_of(cfg.num_classes, &mut nb),
}
}
fn tiny_da_config() -> DepthAnythingV2Config {
DepthAnythingV2Config {
out_channel_sizes: [2, 4, 6, 8],
in_channel_size: 8,
num_features: 4,
use_batch_norm: false,
use_class_token: false,
layer_ids_vits: vec![0, 1, 2, 3],
input_image_size: 14,
target_patch_size: 2,
}
}
fn conv2d_w(
c_in: usize,
c_out: usize,
k: usize,
stride: usize,
pad: usize,
bias: bool,
nb: &mut dyn FnMut() -> f32,
) -> Conv2dWeights {
Conv2dWeights {
w: vec_of(c_out * c_in * k * k, nb),
b: if bias { Some(vec_of(c_out, nb)) } else { None },
c_in,
c_out,
k,
stride,
pad,
}
}
fn conv_transpose2d_w(
c_in: usize,
c_out: usize,
k: usize,
stride: usize,
bias: bool,
nb: &mut dyn FnMut() -> f32,
) -> ConvTranspose2dWeights {
ConvTranspose2dWeights {
w: vec_of(c_in * c_out * k * k, nb),
b: if bias { Some(vec_of(c_out, nb)) } else { None },
c_in,
c_out,
k,
stride,
}
}
fn residual_conv_unit_w(
num_features: usize,
nb: &mut dyn FnMut() -> f32,
) -> ResidualConvUnitWeights {
ResidualConvUnitWeights {
conv1: conv2d_w(num_features, num_features, 3, 1, 1, true, nb),
conv2: conv2d_w(num_features, num_features, 3, 1, 1, true, nb),
}
}
fn feature_fusion_block_w(
num_features: usize,
target_patch_size: usize,
nb: &mut dyn FnMut() -> f32,
) -> FeatureFusionBlockWeights {
FeatureFusionBlockWeights {
res_conv_unit1: residual_conv_unit_w(num_features, nb),
res_conv_unit2: residual_conv_unit_w(num_features, nb),
output_conv: conv2d_w(num_features, num_features, 1, 1, 0, true, nb),
target_patch_size,
}
}
fn tiny_da_weights(
cfg: &DepthAnythingV2Config,
dino_w: Dinov2Weights,
) -> DepthAnythingV2Weights {
let mut nb = rng_seed(7777);
let in_ch = cfg.in_channel_size;
let nf = cfg.num_features;
let oc = cfg.out_channel_sizes;
let projections = [
conv2d_w(in_ch, oc[0], 1, 1, 0, true, &mut nb),
conv2d_w(in_ch, oc[1], 1, 1, 0, true, &mut nb),
conv2d_w(in_ch, oc[2], 1, 1, 0, true, &mut nb),
conv2d_w(in_ch, oc[3], 1, 1, 0, true, &mut nb),
];
let resize_layers = [
ResizeLayer::ConvTranspose(conv_transpose2d_w(oc[0], oc[0], 4, 4, true, &mut nb)),
ResizeLayer::ConvTranspose(conv_transpose2d_w(oc[1], oc[1], 2, 2, true, &mut nb)),
ResizeLayer::Identity,
ResizeLayer::ConvStride2(conv2d_w(oc[3], oc[3], 3, 2, 1, true, &mut nb)),
];
let scratch = ScratchWeights {
layer1_rn: conv2d_w(oc[0], nf, 3, 1, 1, false, &mut nb),
layer2_rn: conv2d_w(oc[1], nf, 3, 1, 1, false, &mut nb),
layer3_rn: conv2d_w(oc[2], nf, 3, 1, 1, false, &mut nb),
layer4_rn: conv2d_w(oc[3], nf, 3, 1, 1, false, &mut nb),
refine_net4: feature_fusion_block_w(nf, cfg.target_patch_size, &mut nb),
refine_net3: feature_fusion_block_w(nf, cfg.target_patch_size * 2, &mut nb),
refine_net2: feature_fusion_block_w(nf, cfg.target_patch_size * 4, &mut nb),
refine_net1: feature_fusion_block_w(nf, cfg.target_patch_size * 8, &mut nb),
output_conv1: conv2d_w(nf, nf / 2, 3, 1, 1, true, &mut nb),
output_conv2a: conv2d_w(nf / 2, 32, 3, 1, 1, true, &mut nb),
output_conv2b: conv2d_w(32, 1, 1, 1, 0, true, &mut nb),
};
DepthAnythingV2Weights {
dinov2: dino_w,
depth_head: DPTHeadWeights {
projections,
resize_layers,
scratch,
},
}
}
#[test]
fn forward_shape_and_finite() {
let dino_cfg = tiny_dinov2_config();
let dino_w = tiny_dinov2_weights(&dino_cfg);
let da_cfg = tiny_da_config();
let weights = tiny_da_weights(&da_cfg, dino_w);
let model = DepthAnythingV2Model {
config: da_cfg.clone(),
dinov2_config: dino_cfg.clone(),
weights,
};
let image: Vec<f32> = (0..(3 * 14 * 14)).map(|i| (i as f32) * 0.01).collect();
let img_tensor =
Tensor::from_f32(image, Shape::from_dims(&[1, 3, 14, 14]), &Device::cpu()).unwrap();
let depth = model.forward(&img_tensor).unwrap();
let dims = depth.shape();
let dims = dims.dims();
assert_eq!(
dims,
&[1, 1, da_cfg.input_image_size, da_cfg.input_image_size]
);
for &v in &depth.realize_f32() {
assert!(v.is_finite(), "non-finite depth: {v}");
assert!(v >= 0.0, "ReLU'd depth value should be ≥ 0, got {v}");
}
}
#[test]
fn depth_head_responds_to_input() {
let dino_cfg = tiny_dinov2_config();
let dino_w = tiny_dinov2_weights(&dino_cfg);
let da_cfg = tiny_da_config();
let weights = tiny_da_weights(&da_cfg, dino_w.clone());
let model = DepthAnythingV2Model {
config: da_cfg.clone(),
dinov2_config: dino_cfg.clone(),
weights,
};
let n = 3 * 14 * 14;
let img_a = Tensor::from_f32(
(0..n).map(|i| (i as f32) * 0.01).collect::<Vec<_>>(),
Shape::from_dims(&[1, 3, 14, 14]),
&Device::cpu(),
)
.unwrap();
let img_b = Tensor::from_f32(
(0..n).map(|i| (i as f32) * 0.01 + 0.7).collect::<Vec<_>>(),
Shape::from_dims(&[1, 3, 14, 14]),
&Device::cpu(),
)
.unwrap();
let dino = Dinov2Model {
config: dino_cfg.clone(),
weights: dino_w,
};
let fa = dino
.forward_intermediate_layers(&img_a, &da_cfg.layer_ids_vits)
.unwrap();
let fb = dino
.forward_intermediate_layers(&img_b, &da_cfg.layer_ids_vits)
.unwrap();
let a = fa[0].realize_f32();
let b = fb[0].realize_f32();
let mut max_diff = 0.0_f32;
for (x, y) in a.iter().zip(b.iter()) {
max_diff = max_diff.max((x - y).abs());
}
assert!(
max_diff > 1e-7,
"DINOv2 features must respond to input changes, max_diff = {max_diff}"
);
let _ = model.forward(&img_a).unwrap().realize_f32();
let _ = model.forward(&img_b).unwrap().realize_f32();
}
}