232 lines
7.9 KiB
Python
232 lines
7.9 KiB
Python
"""
|
|
Implements the CLIP Vision Encoder.
|
|
"""
|
|
|
|
import dataclasses
|
|
import logging
|
|
from typing import Any, Dict, Tuple # noqa: UP035
|
|
|
|
from tvm import relax
|
|
from tvm.relax.frontend import nn
|
|
from tvm.relax.frontend.nn import Module, Tensor
|
|
from tvm.relax.frontend.nn.modules import Conv2D
|
|
from tvm.relax.frontend.nn.op import (
|
|
add,
|
|
broadcast_to,
|
|
concat,
|
|
permute_dims,
|
|
reshape,
|
|
wrap_nested,
|
|
)
|
|
from tvm.relax.op import arange
|
|
|
|
from mlc_llm import op as op_ext
|
|
from mlc_llm.support.config import ConfigBase
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class CLIPVisionConfig(ConfigBase):
|
|
"""
|
|
Config for the vision encoder
|
|
"""
|
|
|
|
hidden_size: int
|
|
image_size: int
|
|
intermediate_size: int
|
|
num_attention_heads: int
|
|
num_hidden_layers: int
|
|
patch_size: int
|
|
projection_dim: int
|
|
vocab_size: int
|
|
num_channels: int = 3
|
|
layer_norm_eps: float = 1e-06
|
|
kwargs: Dict[str, Any] = dataclasses.field(default_factory=dict) # noqa: UP006
|
|
|
|
|
|
class CLIPVisionEmbeddings(Module):
|
|
def __init__(self, config: CLIPVisionConfig):
|
|
super().__init__()
|
|
self.config = config
|
|
self.embed_dim = config.hidden_size
|
|
self.image_size = config.image_size
|
|
self.patch_size = config.patch_size
|
|
self.class_embedding = nn.Parameter((self.embed_dim,))
|
|
self.patch_embedding = Conv2D(
|
|
in_channels=config.num_channels,
|
|
out_channels=self.embed_dim,
|
|
kernel_size=self.patch_size,
|
|
stride=self.patch_size,
|
|
bias=False,
|
|
)
|
|
|
|
self.num_patches = (self.image_size // self.patch_size) ** 2
|
|
self.num_positions = self.num_patches + 1
|
|
self.position_embedding = nn.Embedding(num=self.num_positions, dim=self.embed_dim)
|
|
|
|
def forward(self, pixel_values: Tensor) -> Tensor:
|
|
batch_size = pixel_values.shape[0]
|
|
patch_embeds = self.patch_embedding(pixel_values) # shape = [*, width, grid, grid]
|
|
patch_embeds = reshape(patch_embeds, shape=(batch_size, self.embed_dim, -1))
|
|
patch_embeds = permute_dims(
|
|
patch_embeds, axes=(0, 2, 1)
|
|
) # shape = [batch,grid*grid,embed_dim]
|
|
class_embeds = broadcast_to(
|
|
self.class_embedding, shape=(batch_size, 1, self.embed_dim)
|
|
) # shape of (batch,1,embed_dim)
|
|
embeddings = concat([class_embeds, patch_embeds], dim=1)
|
|
|
|
posi_ids = reshape(
|
|
wrap_nested(arange(0, self.num_positions, dtype="int32"), name="arange"),
|
|
shape=(1, -1),
|
|
)
|
|
batch_position_embedding = broadcast_to(
|
|
self.position_embedding(posi_ids),
|
|
shape=(batch_size, self.num_positions, self.embed_dim),
|
|
)
|
|
embeddings = add(embeddings, batch_position_embedding)
|
|
return embeddings
|
|
|
|
|
|
def sigmoid(x: Tensor, name: str = "sigmoid") -> Tensor:
|
|
"""Sigmoid of a Tensor
|
|
|
|
Parameters
|
|
----------
|
|
x : Tensor
|
|
Input tensor to expand.
|
|
name : str
|
|
Name hint for this operator.
|
|
|
|
Returns
|
|
-------
|
|
result : Tensor
|
|
Sigmoid result.
|
|
"""
|
|
return wrap_nested(relax.op.sigmoid(x._expr), name)
|
|
|
|
|
|
class QuickGELU(Module):
|
|
def forward(self, input_tensor: Tensor) -> Tensor:
|
|
return input_tensor * sigmoid(input_tensor * 1.702)
|
|
|
|
|
|
class CLIPMLP(Module):
|
|
def __init__(self, config: CLIPVisionConfig):
|
|
super().__init__()
|
|
self.activation_fn = QuickGELU()
|
|
self.fc1 = nn.Linear(config.hidden_size, config.intermediate_size)
|
|
self.fc2 = nn.Linear(config.intermediate_size, config.hidden_size)
|
|
|
|
def forward(self, hidden_states: Tensor) -> Tensor:
|
|
hidden_states = self.fc1(hidden_states)
|
|
hidden_states = self.activation_fn(hidden_states)
|
|
hidden_states = self.fc2(hidden_states)
|
|
return hidden_states
|
|
|
|
|
|
class CLIPAttention(Module):
|
|
def __init__(self, config: CLIPVisionConfig):
|
|
super().__init__()
|
|
self.embed_dim = config.hidden_size
|
|
self.num_heads = config.num_attention_heads
|
|
self.head_dim = self.embed_dim // self.num_heads
|
|
if (self.head_dim * self.num_heads) != self.embed_dim:
|
|
raise ValueError(
|
|
f"embed_dim must be divisible by num_heads (got `embed_dim`: {self.embed_dim}"
|
|
f" and `num_heads`: {self.num_heads})."
|
|
)
|
|
self.scale = self.head_dim**-0.5
|
|
self.k_proj = nn.Linear(self.embed_dim, self.embed_dim)
|
|
self.v_proj = nn.Linear(self.embed_dim, self.embed_dim)
|
|
self.q_proj = nn.Linear(self.embed_dim, self.embed_dim)
|
|
self.out_proj = nn.Linear(self.embed_dim, self.embed_dim)
|
|
|
|
def forward(
|
|
self,
|
|
hidden_states: Tensor,
|
|
) -> Tensor:
|
|
d, h = self.head_dim, self.num_heads
|
|
b, s, _ = hidden_states.shape # batch_size, seq_len, embed_dim
|
|
|
|
q = self.q_proj(hidden_states).reshape(b, s, h, d)
|
|
k = self.k_proj(hidden_states).reshape(b, s, h, d)
|
|
v = self.v_proj(hidden_states).reshape(b, s, h, d)
|
|
|
|
attn_output = op_ext.attention(q, k, v, None)
|
|
attn_output = self.out_proj(attn_output)
|
|
return attn_output
|
|
|
|
|
|
class CLIPEncoderLayer(Module):
|
|
def __init__(self, config: CLIPVisionConfig):
|
|
super().__init__()
|
|
self.embed_dim = config.hidden_size
|
|
self.self_attn = CLIPAttention(config)
|
|
self.layer_norm1 = nn.LayerNorm(normalized_shape=self.embed_dim, eps=config.layer_norm_eps)
|
|
self.mlp = CLIPMLP(config)
|
|
self.layer_norm2 = nn.LayerNorm(normalized_shape=self.embed_dim, eps=config.layer_norm_eps)
|
|
|
|
def forward(self, hidden_states: Tensor) -> Tensor:
|
|
residual = hidden_states
|
|
hidden_states = self.layer_norm1(hidden_states)
|
|
hidden_states = self.self_attn(hidden_states=hidden_states)
|
|
hidden_states = residual + hidden_states
|
|
residual = hidden_states
|
|
hidden_states = self.layer_norm2(hidden_states)
|
|
hidden_states = self.mlp(hidden_states)
|
|
hidden_states = residual + hidden_states
|
|
|
|
outputs = (hidden_states,)
|
|
return outputs
|
|
|
|
|
|
class CLIPEncoder(Module):
|
|
def __init__(self, config: CLIPVisionConfig):
|
|
super().__init__()
|
|
self.layers = nn.ModuleList(
|
|
[CLIPEncoderLayer(config) for _ in range(config.num_hidden_layers)]
|
|
)
|
|
|
|
def forward(self, inputs_embeds: Tensor) -> Tensor:
|
|
hidden_states = inputs_embeds
|
|
encoder_states: Tuple[Any, ...] = () # noqa: UP006
|
|
for _, encoder_layer in enumerate(self.layers):
|
|
encoder_states = (*encoder_states, hidden_states)
|
|
layer_outputs = encoder_layer(hidden_states)
|
|
hidden_states = layer_outputs[0]
|
|
encoder_states = (*encoder_states, hidden_states)
|
|
return encoder_states
|
|
|
|
|
|
class CLIPVisionTransformer(Module):
|
|
def __init__(self, config: CLIPVisionConfig):
|
|
super().__init__()
|
|
embed_dim = config.hidden_size
|
|
self.embeddings = CLIPVisionEmbeddings(config)
|
|
self.pre_layrnorm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps)
|
|
self.encoder = CLIPEncoder(config)
|
|
self.post_layernorm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps)
|
|
|
|
def forward(self, pixel_values: Tensor) -> Tensor:
|
|
hidden_states = self.embeddings(pixel_values)
|
|
hidden_states = self.pre_layrnorm(hidden_states)
|
|
encoder_outputs = self.encoder(inputs_embeds=hidden_states)
|
|
# Apply post_layernorm to the final encoder hidden state, matching
|
|
# the HuggingFace CLIPVisionTransformer which returns post-normed
|
|
# last_hidden_state. Intermediate states remain unnormalized.
|
|
last_hidden_state = self.post_layernorm(encoder_outputs[-1])
|
|
return (*encoder_outputs[:-1], last_hidden_state)
|
|
|
|
|
|
class CLIPVisionModel(Module):
|
|
no_quantization: bool = True
|
|
|
|
def __init__(self, config: CLIPVisionConfig):
|
|
super().__init__()
|
|
self.vision_model = CLIPVisionTransformer(config)
|
|
|
|
def forward(self, pixel_values: Tensor) -> Tensor:
|
|
return self.vision_model(pixel_values)[-2]
|