Files
mlc-ai--mlc-llm/python/mlc_llm/model/bert/bert_model.py
T
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

274 lines
10 KiB
Python

"""
Implementation for BERT architecture.
"""
import dataclasses
from functools import partial
from typing import Any, Dict, Optional # noqa: UP035
from tvm import te, tirx
from tvm.relax.frontend import nn
from tvm.relax.frontend.nn import Tensor, op
from mlc_llm import op as op_ext
from mlc_llm.support import logging
from mlc_llm.support.config import ConfigBase
from mlc_llm.support.style import bold
logger = logging.getLogger(__name__)
@dataclasses.dataclass
class BertConfig(ConfigBase):
"""Configuration of the BERT model."""
vocab_size: int
hidden_size: int
num_hidden_layers: int
num_attention_heads: int
intermediate_size: int
hidden_act: str
layer_norm_eps: float
context_window_size: int = 0
prefill_chunk_size: int = 0
tensor_parallel_shards: int = 1
type_vocab_size: int = 2
pad_token_id: int = 0
position_offset: int = 0
head_dim: int = 0
max_batch_size: int = 1
kwargs: Dict[str, Any] = dataclasses.field(default_factory=dict) # noqa: UP006
def __post_init__(self):
if self.intermediate_size is None or self.intermediate_size == -1:
self.intermediate_size = 4 * self.hidden_size
if self.context_window_size == 0:
for name in ["max_position_embeddings", "max_sequence_length"]:
if name in self.kwargs:
self.context_window_size = self.kwargs.pop(name)
logger.info(
"%s not found in config.json. Falling back to %s (%d)",
bold("context_window_size"),
bold(name),
self.context_window_size,
)
break
else:
raise ValueError(
"Unable to determine the maximum sequence length, because none of "
"`context_window_size`, `max_position_embeddings` or `max_sequence_length` is "
"provided in `config.json`."
)
if self.head_dim == 0:
self.head_dim = self.hidden_size // self.num_attention_heads
assert self.head_dim * self.num_attention_heads == self.hidden_size
if self.prefill_chunk_size == 0:
logger.info(
"%s defaults to %s (%d)",
bold("prefill_chunk_size"),
bold("context_window_size"),
self.context_window_size,
)
self.prefill_chunk_size = self.context_window_size
elif self.prefill_chunk_size > self.context_window_size:
logger.info(
"Overriding %s from %d to %d (%s)",
bold("prefill_chunk_size"),
self.prefill_chunk_size,
self.context_window_size,
bold("context_window_size"),
)
self.prefill_chunk_size = self.context_window_size
class BertSelfAttention(nn.Module):
def __init__(self, config: BertConfig):
if config.num_attention_heads % config.tensor_parallel_shards != 0:
raise ValueError(
f"Cannot split {config.num_attention_heads} attention heads"
f"evenly to {config.tensor_parallel_shards} GPUs."
)
self.num_heads = config.num_attention_heads // config.tensor_parallel_shards
self.head_dim = config.head_dim
self.qkv = nn.Linear(
in_features=config.hidden_size,
out_features=3 * self.num_heads * self.head_dim,
bias=True,
)
def forward(self, hidden_states: Tensor, attention_mask: Tensor):
d, h = self.head_dim, self.num_heads
b, s, _ = hidden_states.shape
qkv = self.qkv(hidden_states)
qkv = op.reshape(qkv, (b, s, 3 * h, d))
q, k, v = op.split(qkv, 3, axis=2)
# Attention
output = op_ext.attention(q, k, v, attention_mask)
return output
class BertSelfOutput(nn.Module):
def __init__(self, config: BertConfig):
self.dense = nn.Linear(config.hidden_size, config.hidden_size)
self.LayerNorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
def forward(self, hidden_states: Tensor, input_tensor: Tensor):
hidden_states = self.dense(hidden_states)
hidden_states = self.LayerNorm(hidden_states + input_tensor)
return hidden_states
class BertAttention(nn.Module):
def __init__(self, config: BertConfig):
self.self = BertSelfAttention(config)
self.output = BertSelfOutput(config)
def forward(self, hidden_states: Tensor, attention_mask: Tensor):
self_output = self.self(hidden_states, attention_mask)
attention_output = self.output(self_output, hidden_states)
return attention_output
ACT2FN = {
"gelu": partial(nn.gelu, approximate=False),
"relu": nn.relu,
"silu": nn.silu,
"swish": nn.silu,
"gelu_new": partial(nn.gelu, approximate=True),
}
class BertIntermediate(nn.Module):
def __init__(self, config: BertConfig):
self.dense = nn.Linear(config.hidden_size, config.intermediate_size)
self.intermediate_act_fn = ACT2FN[config.hidden_act]
def forward(self, hidden_states: Tensor):
hidden_states = self.dense(hidden_states)
hidden_states = self.intermediate_act_fn(hidden_states)
return hidden_states
class BertOutput(nn.Module):
def __init__(self, config: BertConfig):
self.dense = nn.Linear(config.intermediate_size, config.hidden_size)
self.LayerNorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
def forward(self, hidden_states: Tensor, input_tensor: Tensor):
hidden_states = self.dense(hidden_states)
hidden_states = self.LayerNorm(hidden_states + input_tensor)
return hidden_states
class BertLayer(nn.Module):
def __init__(self, config: BertConfig):
self.attention = BertAttention(config)
self.intermediate = BertIntermediate(config)
self.output = BertOutput(config)
def forward(self, hidden_states: Tensor, attention_mask: Tensor):
attention_output = self.attention(hidden_states, attention_mask)
intermediate_output = self.intermediate(attention_output)
layer_output = self.output(intermediate_output, attention_output)
return layer_output
class BertEncoder(nn.Module):
def __init__(self, config: BertConfig):
self.layer = nn.ModuleList([BertLayer(config) for _ in range(config.num_hidden_layers)])
def forward(self, hidden_states: Tensor, attention_mask: Tensor):
for layer in self.layer:
hidden_states = layer(hidden_states, attention_mask)
return hidden_states
class BertEmbeddings(nn.Module):
def __init__(self, config: BertConfig):
self.word_embeddings = nn.Embedding(config.vocab_size, config.hidden_size, dtype="float32")
self.position_embeddings = nn.Embedding(
config.context_window_size, config.hidden_size, dtype="float32"
)
self.token_type_embeddings = nn.Embedding(
config.type_vocab_size, config.hidden_size, dtype="float32"
)
self.LayerNorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
def forward(self, input_ids: Tensor, token_type_ids: Tensor, position_ids: Tensor):
words_embeddings = self.word_embeddings(input_ids)
position_embeddings = self.position_embeddings(position_ids)
token_type_embeddings = self.token_type_embeddings(token_type_ids)
embeddings = words_embeddings + position_embeddings + token_type_embeddings
embeddings = self.LayerNorm(embeddings)
return embeddings
class BertModel(nn.Module):
def __init__(self, config: BertConfig):
self.embeddings = BertEmbeddings(config)
self.encoder = BertEncoder(config)
self.dtype = "float32"
def to(self, dtype: Optional[str] = None):
super().to(dtype=dtype)
if dtype is not None:
self.dtype = dtype
def forward(self, inputs: Tensor, attention_mask: Tensor):
# TODO: XLM-RoBERTa models use position indices starting from pad_token_id + 1
# (e.g., [2, 3, 4, ...] when pad_token_id=1), while this implementation uses
# [0, 1, 2, ...]. For XLM-RoBERTa models (e.g., bge-m3), the position_embeddings
# weights need to be shifted during weight conversion to compensate.
def _input_positions(inputs: te.Tensor):
b, s = inputs.shape
return te.compute((b, s), lambda _, j: j.astype("int32"), name="input_positions")
input_positions = op.tensor_expr_op(
_input_positions,
name_hint="input_positions",
args=[inputs],
)
token_type_ids = op.zeros(inputs.shape, dtype="int32")
embeddings = self.embeddings(inputs, token_type_ids, input_positions)
encoder_output = self.encoder(embeddings, attention_mask)
return encoder_output
def prefill(self, inputs: Tensor, attention_mask: Tensor):
def _attention_mask(mask: te.Tensor, zero, batch_size, seq_len):
return te.compute(
(batch_size, 1, seq_len, seq_len),
lambda b, _, i, j: tirx.if_then_else(
tirx.any(mask[b, i] == zero, mask[b, j] == zero),
tirx.min_value(self.dtype),
tirx.max_value(self.dtype),
),
name="attention_mask_prefill",
)
batch_size, seq_len = inputs.shape
attention_mask_2d = op.tensor_expr_op(
_attention_mask,
name_hint="attention_mask_prefill",
args=[attention_mask, tirx.IntImm("int32", 0), batch_size, seq_len],
)
return self.forward(inputs, attention_mask_2d)
def get_default_spec(self):
mod_spec = {
"prefill": {
"inputs": nn.spec.Tensor(["batch_size", "seq_len"], "int32"),
"attention_mask": nn.spec.Tensor(["batch_size", "seq_len"], "int32"),
"$": {
"param_mode": "packed",
"effect_mode": "none",
},
},
}
return nn.spec.ModuleSpec.from_raw(mod_spec, self)