Files
wehub-resource-sync 770d92cb1f
Lint / lint (push) Has been cancelled
Build Docs / Deploy Docs (push) Has been cancelled
Windows CI / Windows (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:23:58 +08:00

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]