defmodule Bumblebee.Vision.DinoV2 do alias Bumblebee.Shared options = [ image_size: [ default: 518, doc: """ the size of the input spatial dimensions. The model is trained for this size, however the model supports any other input size by interpolating position embeddings """ ], num_channels: [ default: 3, doc: "the number of channels in the input" ], patch_size: [ default: 14, doc: "the size of the patch spatial dimensions" ], hidden_size: [ default: 384, doc: "the dimensionality of hidden layers" ], num_blocks: [ default: 12, doc: "the number of Transformer blocks in the encoder" ], num_attention_heads: [ default: 12, doc: "the number of attention heads for each attention layer in the encoder" ], intermediate_size_ratio: [ default: 4, doc: """ the dimensionality of the intermediate layer in the transformer feed-forward network (FFN) in the encoder, expressed as a multiplier of `:hidden_size` """ ], use_attention_bias: [ default: true, doc: "whether to use bias in query, key, and value projections" ], activation: [ default: :gelu, doc: "the activation function" ], ffn_swiglu_activation: [ default: false, doc: "whether to use the gated SwiGLU activation function in the feed-forward network (FFN)" ], scale_initial_value: [ default: 1.0, doc: "the initial value for scaling layers" ], dropout_rate: [ default: 0.0, doc: "the dropout rate for encoder and decoder" ], attention_dropout_rate: [ default: 0.0, doc: "the dropout rate for attention weights" ], layer_norm_epsilon: [ default: 1.0e-6, doc: "the epsilon used by the layer normalization layers" ], initializer_scale: [ default: 0.02, doc: "the standard deviation of the normal initializer used for initializing kernel parameters" ], backbone_output_indices: [ default: nil, doc: """ list of indices indicating which feature maps to include in the output. If not specified, only the last feature map is included """ ], backbone_use_norm: [ default: true, doc: "whether to add layer normalization layer to each of the feature maps returned by the backbone" ] ] ++ Shared.common_options([:num_labels, :id_to_label]) @moduledoc """ DINOv2 model family. ## Architectures * `:base` - plain DINOv2 without any head on top * `:for_image_classification` - DINOv2 with head for image classification * `:backbone` - DINOv2 with feature maps output ## Inputs * `"pixel_values"` - `{batch_size, image_size, image_size, num_channels}` Featurized image pixel values. * `"patch_mask"` - `{batch_size, num_patches}` Mask to nullify selected embedded patches. ## Global layer options #{Shared.global_layer_options_doc([:output_hidden_states, :output_attentions])} ## Configuration #{Shared.options_doc(options)} ## References * [DINOv2: Learning Robust Visual Features without Supervision](https://arxiv.org/abs/2304.07193) """ defstruct [architecture: :base] ++ Shared.option_defaults(options) @behaviour Bumblebee.ModelSpec @behaviour Bumblebee.Configurable import Bumblebee.Utils.Model, only: [join: 2] alias Bumblebee.Layers @impl true def architectures(), do: [:base, :backbone, :for_image_classification] @impl true def config(spec, opts) do spec |> Shared.put_config_attrs(opts) |> Shared.validate_label_options() end @impl true def input_template(spec) do %{ "pixel_values" => Nx.template({1, spec.image_size, spec.image_size, spec.num_channels}, :f32) } end @impl true def model(%__MODULE__{architecture: :base} = spec) do inputs = inputs(spec) outputs = core(inputs, spec) hidden_state = Axon.layer_norm(outputs.hidden_state, epsilon: spec.layer_norm_epsilon, name: "norm") pooled_state = Layers.take_token(hidden_state, index: 0, axis: 1) Layers.output(%{ hidden_state: hidden_state, pooled_state: pooled_state, hidden_states: outputs.hidden_states, attentions: outputs.attentions }) end def model(%__MODULE__{architecture: :for_image_classification} = spec) do inputs = inputs(spec) outputs = core(inputs, spec) hidden_state = Axon.layer_norm(outputs.hidden_state, epsilon: spec.layer_norm_epsilon, name: "norm") class_token = Layers.take_token(hidden_state, index: 0, axis: 1) patch_embeddings_mean = Axon.nx(hidden_state, fn hidden_state -> patch_embeddings = hidden_state[[.., 1..-1//1, ..]] Nx.mean(patch_embeddings, axes: [1]) end) logits = Axon.concatenate(class_token, patch_embeddings_mean) |> Axon.dense(spec.num_labels, kernel_initializer: kernel_initializer(spec), name: "image_classification_head.output" ) Layers.output(%{ logits: logits, hidden_states: outputs.hidden_states, attentions: outputs.attentions }) end def model(%__MODULE__{architecture: :backbone} = spec) do inputs = inputs(spec) outputs = core(inputs, spec) feature_maps = feature_maps(outputs.hidden_states, inputs["pixel_values"], spec) Layers.output(%{ feature_maps: feature_maps, hidden_states: outputs.hidden_states, attentions: outputs.attentions }) end defp inputs(spec) do shape = {nil, nil, nil, spec.num_channels} Bumblebee.Utils.Model.inputs_to_map([ Axon.input("pixel_values", shape: shape), Axon.input("patch_mask", shape: {nil, nil}, optional: true) ]) end defp core(inputs, spec, opts \\ []) do name = opts[:name] embeddings = embedder(inputs["pixel_values"], inputs["patch_mask"], spec, name: join(name, "embedder")) encoder_outputs = encoder(embeddings, spec, name: join(name, "encoder")) %{ hidden_state: encoder_outputs.hidden_state, hidden_states: encoder_outputs.hidden_states, attentions: encoder_outputs.attentions } end defp feature_maps(hidden_states, pixel_values, spec, opts \\ []) do name = opts[:name] num_feature_maps = spec.num_blocks + 1 output_indices = spec.backbone_output_indices || [num_feature_maps - 1] for index <- output_indices do hidden_state = Axon.nx(hidden_states, &elem(&1, index)) hidden_state = if spec.backbone_use_norm do Axon.layer_norm(hidden_state, epsilon: spec.layer_norm_epsilon, name: join(name, "norm") ) else hidden_state end Axon.layer( fn hidden_state, pixel_values, _opts -> {batch_size, height, width, _channels} = Nx.shape(pixel_values) hidden_state = hidden_state[[.., 1..-1//1, ..]] Nx.reshape( hidden_state, {batch_size, div(height, spec.patch_size), div(width, spec.patch_size), :auto} ) end, [hidden_state, pixel_values] ) end |> List.to_tuple() |> Axon.container() end defp embedder(pixel_values, patch_mask, spec, opts) do name = opts[:name] patch_embeddings = pixel_values |> patch_embedding(spec, name: join(name, "patch_embedding")) |> Layers.apply_vision_patch_mask(patch_mask, name: join(name, "mask_tokens")) class_embedding = Layers.learned_embeddings(1, spec.hidden_size, name: join(name, "class_embedding")) input_embeddings = Layers.concatenate_embeddings([class_embedding, patch_embeddings]) num_patches = div(spec.image_size, spec.patch_size) ** 2 position_embeddings = Layers.learned_embeddings(num_patches + 1, spec.hidden_size, initializer: :zeros, name: join(name, "position_embedding") ) |> interpolate_position_embeddings(pixel_values, spec) Axon.add(input_embeddings, position_embeddings) |> Axon.dropout(rate: spec.dropout_rate, name: join(name, "dropout")) end defp patch_embedding(pixel_values, spec, opts) do name = opts[:name] pixel_values |> Axon.conv(spec.hidden_size, kernel_size: spec.patch_size, strides: spec.patch_size, padding: :valid, kernel_initializer: kernel_initializer(spec), name: join(name, "projection") ) |> Axon.reshape({:batch, :auto, spec.hidden_size}, name: join(name, "reshape")) end defp interpolate_position_embeddings(position_embeddings, pixel_values, spec) do Axon.layer( fn position_embeddings, pixel_values, _opts -> original_positions = div(spec.image_size, spec.patch_size) {batch_size, height, width, _channels} = Nx.shape(pixel_values) resized_height = div(height, spec.patch_size) resized_width = div(width, spec.patch_size) class_position_embedding = position_embeddings[[.., 0..0//1, ..]] input_position_embeddings = position_embeddings[[.., 1..-1//1, ..]] interpolated_position_embeddings = input_position_embeddings |> Nx.reshape({batch_size, original_positions, original_positions, spec.hidden_size}) |> Axon.Layers.resize( size: {resized_height, resized_width}, method: :bicubic, antialias: false ) |> Nx.reshape({batch_size, :auto, spec.hidden_size}) Nx.concatenate([class_position_embedding, interpolated_position_embeddings], axis: 1) end, [position_embeddings, pixel_values], op_name: :interpolate_position_embeddings ) end defp encoder(hidden_state, spec, opts) do name = opts[:name] ffn = if spec.ffn_swiglu_activation do intermediate_size = div(floor(floor(spec.hidden_size * spec.intermediate_size_ratio) * 2 / 3 + 7), 8) * 8 &ffn_swiglu(&1, intermediate_size, spec.hidden_size, name: &2) else intermediate_size = floor(spec.hidden_size * spec.intermediate_size_ratio) [ intermediate_size: intermediate_size, activation: spec.activation ] end Layers.Transformer.blocks(hidden_state, num_blocks: spec.num_blocks, num_attention_heads: spec.num_attention_heads, hidden_size: spec.hidden_size, kernel_initializer: kernel_initializer(spec), dropout_rate: spec.dropout_rate, attention_dropout_rate: spec.attention_dropout_rate, query_use_bias: spec.use_attention_bias, key_use_bias: spec.use_attention_bias, value_use_bias: spec.use_attention_bias, layer_norm: [ epsilon: spec.layer_norm_epsilon ], ffn: ffn, block_type: &block_impl(&1, &2, &3, spec), name: join(name, "blocks") ) end # A feed-forward network with SwiGLU nonlinearity as in https://arxiv.org/abs/2002.05202 defp ffn_swiglu(x, intermediate_size, output_size, opts) do name = opts[:name] dropout = opts[:dropout] || 0.0 {gate, x} = x |> Axon.dense(intermediate_size * 2, name: join(name, "intermediate")) |> Axon.split(2, axis: -1) x = Axon.multiply(x, Axon.silu(gate)) x |> Axon.dropout(rate: dropout, name: join(name, "dropout")) |> Axon.dense(output_size, name: join(name, "output")) end # :norm_first block with additional scaling layers defp block_impl(hidden_state, steps, name, spec) do shortcut = hidden_state {hidden_state, attention_info} = hidden_state |> steps.self_attention_norm.() |> steps.self_attention.() hidden_state = hidden_state |> Bumblebee.Layers.scale( scale_initializer: Axon.Initializers.full(spec.scale_initial_value), name: join(name, "self_attention_scale") ) |> Axon.add(shortcut) {_hidden_state, cross_attention_info} = steps.cross_attention_maybe.(hidden_state, fn _hidden_state -> raise "cross attention not supported" end) shortcut = hidden_state hidden_state = hidden_state |> steps.output_norm.() |> steps.ffn.() |> Bumblebee.Layers.scale( scale_initializer: Axon.Initializers.full(spec.scale_initial_value), name: join(name, "output_scale") ) |> Axon.add(shortcut) {hidden_state, attention_info, cross_attention_info} end defp kernel_initializer(spec) do Axon.Initializers.normal(scale: spec.initializer_scale) end defimpl Bumblebee.HuggingFace.Transformers.Config do def load(spec, data) do import Shared.Converters opts = convert!(data, image_size: {"image_size", number()}, num_channels: {"num_channels", number()}, patch_size: {"patch_size", number()}, hidden_size: {"hidden_size", number()}, num_blocks: {"num_hidden_layers", number()}, num_attention_heads: {"num_attention_heads", number()}, intermediate_size_ratio: {"mlp_ratio", number()}, activation: {"hidden_act", activation()}, use_attention_bias: {"qkv_bias", boolean()}, dropout_rate: {"hidden_dropout_prob", number()}, attention_dropout_rate: {"attention_probs_dropout_prob", number()}, layer_norm_epsilon: {"layer_norm_eps", number()}, initializer_scale: {"initializer_range", number()}, scale_initial_value: {"layerscale_value", number()}, ffn_swiglu_activation: {"use_swiglu_ffn", boolean()}, backbone_output_indices: {"out_indices", list(number())}, backbone_use_norm: {"use_backbone_norm", boolean()} ) ++ Shared.common_options_from_transformers(data, spec) @for.config(spec, opts) end end defimpl Bumblebee.HuggingFace.Transformers.Model do def params_mapping(spec) do %{ "embedder.patch_embedding.projection" => "dinov2.embeddings.patch_embeddings.projection", "embedder.class_embedding" => %{ "embeddings" => { [{"dinov2.embeddings", "cls_token"}], fn [value] -> Nx.squeeze(value, axes: [0]) end } }, "embedder.position_embedding" => %{ "embeddings" => { [{"dinov2.embeddings", "position_embeddings"}], fn [value] -> Nx.squeeze(value, axes: [0]) end } }, "encoder.blocks.{n}.self_attention_norm" => "dinov2.encoder.layer.{n}.norm1", "encoder.blocks.{n}.self_attention.key" => "dinov2.encoder.layer.{n}.attention.attention.key", "encoder.blocks.{n}.self_attention.query" => "dinov2.encoder.layer.{n}.attention.attention.query", "encoder.blocks.{n}.self_attention.value" => "dinov2.encoder.layer.{n}.attention.attention.value", "encoder.blocks.{n}.self_attention.output" => "dinov2.encoder.layer.{n}.attention.output.dense", "encoder.blocks.{n}.self_attention_scale" => %{ "scale" => { [{"dinov2.encoder.layer.{n}.layer_scale1", "lambda1"}], fn [lambda1] -> lambda1 end } }, "encoder.blocks.{n}.ffn.intermediate" => if(spec.ffn_swiglu_activation, do: "dinov2.encoder.layer.{n}.mlp.weights_in", else: "dinov2.encoder.layer.{n}.mlp.fc1" ), "encoder.blocks.{n}.ffn.output" => if(spec.ffn_swiglu_activation, do: "dinov2.encoder.layer.{n}.mlp.weights_out", else: "dinov2.encoder.layer.{n}.mlp.fc2" ), "encoder.blocks.{n}.output_norm" => "dinov2.encoder.layer.{n}.norm2", "encoder.blocks.{n}.output_scale" => %{ "scale" => { [{"dinov2.encoder.layer.{n}.layer_scale2", "lambda1"}], fn [lambda1] -> lambda1 end } }, "norm" => "dinov2.layernorm", "image_classification_head.output" => "classifier" } end end end