Add LFM2.5 embedding model support (#28691)
This commit is contained in:
@@ -1706,6 +1706,7 @@ def is_generation_model(model_architectures: List[str], is_embedding: bool = Fal
|
|||||||
or "XLMRobertaModel" in model_architectures
|
or "XLMRobertaModel" in model_architectures
|
||||||
or "XLMRobertaForSequenceClassification" in model_architectures
|
or "XLMRobertaForSequenceClassification" in model_architectures
|
||||||
or "Gemma2ForSequenceClassification" in model_architectures
|
or "Gemma2ForSequenceClassification" in model_architectures
|
||||||
|
or "Lfm2BidirectionalModel" in model_architectures
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -32,8 +32,9 @@ from sglang.srt.layers.linear import (
|
|||||||
RowParallelLinear,
|
RowParallelLinear,
|
||||||
)
|
)
|
||||||
from sglang.srt.layers.logits_processor import LogitsProcessor
|
from sglang.srt.layers.logits_processor import LogitsProcessor
|
||||||
|
from sglang.srt.layers.pooler import EmbeddingPoolerOutput, Pooler, PoolingType
|
||||||
from sglang.srt.layers.quantization.base_config import QuantizationConfig
|
from sglang.srt.layers.quantization.base_config import QuantizationConfig
|
||||||
from sglang.srt.layers.radix_attention import RadixAttention
|
from sglang.srt.layers.radix_attention import AttentionType, RadixAttention
|
||||||
from sglang.srt.layers.rotary_embedding import get_rope
|
from sglang.srt.layers.rotary_embedding import get_rope
|
||||||
from sglang.srt.layers.vocab_parallel_embedding import (
|
from sglang.srt.layers.vocab_parallel_embedding import (
|
||||||
ParallelLMHead,
|
ParallelLMHead,
|
||||||
@@ -111,6 +112,7 @@ class Lfm2Attention(nn.Module):
|
|||||||
config: Lfm2Config,
|
config: Lfm2Config,
|
||||||
layer_id: int,
|
layer_id: int,
|
||||||
quant_config: Optional[QuantizationConfig] = None,
|
quant_config: Optional[QuantizationConfig] = None,
|
||||||
|
attn_type: AttentionType = AttentionType.DECODER,
|
||||||
prefix: str = "",
|
prefix: str = "",
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -167,6 +169,7 @@ class Lfm2Attention(nn.Module):
|
|||||||
scaling=self.scaling,
|
scaling=self.scaling,
|
||||||
num_kv_heads=self.num_local_kv_heads,
|
num_kv_heads=self.num_local_kv_heads,
|
||||||
layer_id=layer_id,
|
layer_id=layer_id,
|
||||||
|
attn_type=attn_type,
|
||||||
prefix=add_prefix("attn", prefix),
|
prefix=add_prefix("attn", prefix),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -304,6 +307,59 @@ class Lfm2ShortConv(nn.Module):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
class Lfm2BidirectionalShortConv(Lfm2ShortConv):
|
||||||
|
"""Same-padding non-causal short convolution for bidirectional LFM2 encoders."""
|
||||||
|
|
||||||
|
def forward(
|
||||||
|
self,
|
||||||
|
hidden_states: torch.Tensor,
|
||||||
|
forward_batch: ForwardBatch,
|
||||||
|
) -> torch.Tensor:
|
||||||
|
if forward_batch.forward_mode.is_idle():
|
||||||
|
return hidden_states
|
||||||
|
|
||||||
|
proj, _ = self.in_proj(hidden_states)
|
||||||
|
B_gate, C_gate, x = proj.chunk(3, dim=-1)
|
||||||
|
Bx = B_gate * x
|
||||||
|
|
||||||
|
seq_lens = forward_batch.extend_seq_lens_cpu
|
||||||
|
if seq_lens is None:
|
||||||
|
seq_lens = forward_batch.extend_seq_lens.detach().cpu().tolist()
|
||||||
|
max_len = max(seq_lens)
|
||||||
|
padded = Bx.new_zeros((len(seq_lens), max_len, Bx.shape[-1]))
|
||||||
|
|
||||||
|
offset = 0
|
||||||
|
for batch_idx, seq_len in enumerate(seq_lens):
|
||||||
|
seq_len = int(seq_len)
|
||||||
|
end = offset + seq_len
|
||||||
|
padded[batch_idx, :seq_len] = Bx[offset:end]
|
||||||
|
offset = end
|
||||||
|
|
||||||
|
conv_out = F.conv1d(
|
||||||
|
padded.transpose(1, 2),
|
||||||
|
weight=self.conv_weight.unsqueeze(1),
|
||||||
|
bias=self.conv_bias,
|
||||||
|
padding=self.conv_kernel // 2,
|
||||||
|
groups=self.hidden_size_per_partition,
|
||||||
|
)
|
||||||
|
if conv_out.shape[-1] > max_len:
|
||||||
|
conv_out = conv_out[..., :max_len]
|
||||||
|
elif conv_out.shape[-1] < max_len:
|
||||||
|
conv_out = F.pad(conv_out, (0, max_len - conv_out.shape[-1]))
|
||||||
|
conv_out = conv_out.transpose(1, 2)
|
||||||
|
|
||||||
|
unpadded = Bx.new_empty(Bx.shape)
|
||||||
|
offset = 0
|
||||||
|
for batch_idx, seq_len in enumerate(seq_lens):
|
||||||
|
seq_len = int(seq_len)
|
||||||
|
end = offset + seq_len
|
||||||
|
unpadded[offset:end] = conv_out[batch_idx, :seq_len]
|
||||||
|
offset = end
|
||||||
|
|
||||||
|
output, _ = self.out_proj(C_gate * unpadded)
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
class Lfm2DecoderLayer(nn.Module):
|
class Lfm2DecoderLayer(nn.Module):
|
||||||
"""Decoder layer - either attention or conv based on config."""
|
"""Decoder layer - either attention or conv based on config."""
|
||||||
|
|
||||||
@@ -312,6 +368,7 @@ class Lfm2DecoderLayer(nn.Module):
|
|||||||
config: Lfm2Config,
|
config: Lfm2Config,
|
||||||
layer_id: int,
|
layer_id: int,
|
||||||
quant_config: Optional[QuantizationConfig] = None,
|
quant_config: Optional[QuantizationConfig] = None,
|
||||||
|
bidirectional: bool = False,
|
||||||
prefix: str = "",
|
prefix: str = "",
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -326,10 +383,16 @@ class Lfm2DecoderLayer(nn.Module):
|
|||||||
config=config,
|
config=config,
|
||||||
layer_id=layer_id,
|
layer_id=layer_id,
|
||||||
quant_config=quant_config,
|
quant_config=quant_config,
|
||||||
|
attn_type=(
|
||||||
|
AttentionType.ENCODER_ONLY
|
||||||
|
if bidirectional
|
||||||
|
else AttentionType.DECODER
|
||||||
|
),
|
||||||
prefix=add_prefix("self_attn", prefix),
|
prefix=add_prefix("self_attn", prefix),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.conv = Lfm2ShortConv(
|
conv_cls = Lfm2BidirectionalShortConv if bidirectional else Lfm2ShortConv
|
||||||
|
self.conv = conv_cls(
|
||||||
config=config,
|
config=config,
|
||||||
layer_idx=layer_id,
|
layer_idx=layer_id,
|
||||||
quant_config=quant_config,
|
quant_config=quant_config,
|
||||||
@@ -373,6 +436,7 @@ class Lfm2Model(nn.Module):
|
|||||||
self,
|
self,
|
||||||
config: Lfm2Config,
|
config: Lfm2Config,
|
||||||
quant_config: Optional[QuantizationConfig] = None,
|
quant_config: Optional[QuantizationConfig] = None,
|
||||||
|
bidirectional: bool = False,
|
||||||
prefix: str = "",
|
prefix: str = "",
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -395,11 +459,12 @@ class Lfm2Model(nn.Module):
|
|||||||
config=config,
|
config=config,
|
||||||
layer_id=idx,
|
layer_id=idx,
|
||||||
quant_config=quant_config,
|
quant_config=quant_config,
|
||||||
|
bidirectional=bidirectional,
|
||||||
prefix=prefix,
|
prefix=prefix,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.layers = make_layers(
|
self.layers = make_layers(
|
||||||
config.num_hidden_layers, get_layer, prefix=f"{prefix}.layers"
|
config.num_hidden_layers, get_layer, prefix=add_prefix("layers", prefix)
|
||||||
)
|
)
|
||||||
self.embedding_norm = RMSNorm(config.hidden_size, eps=config.norm_eps)
|
self.embedding_norm = RMSNorm(config.hidden_size, eps=config.norm_eps)
|
||||||
|
|
||||||
@@ -427,6 +492,100 @@ class Lfm2Model(nn.Module):
|
|||||||
return self.embedding_norm(hidden_states)
|
return self.embedding_norm(hidden_states)
|
||||||
|
|
||||||
|
|
||||||
|
class Lfm2BidirectionalModel(Lfm2Model):
|
||||||
|
"""Bidirectional LFM2 encoder for dense embedding models."""
|
||||||
|
|
||||||
|
fall_back_to_pt_during_load = False
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
config: Lfm2Config,
|
||||||
|
quant_config: Optional[QuantizationConfig] = None,
|
||||||
|
prefix: str = "",
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
config,
|
||||||
|
quant_config=quant_config,
|
||||||
|
bidirectional=True,
|
||||||
|
prefix=prefix,
|
||||||
|
)
|
||||||
|
self.pooler = Pooler(pooling_type=PoolingType.CLS, normalize=True)
|
||||||
|
|
||||||
|
def get_num_kv_cache_layers(self) -> int:
|
||||||
|
return self.num_attention_layers
|
||||||
|
|
||||||
|
def get_input_embeddings(self) -> nn.Embedding:
|
||||||
|
return self.embed_tokens
|
||||||
|
|
||||||
|
@torch.no_grad()
|
||||||
|
def forward(
|
||||||
|
self,
|
||||||
|
input_ids: torch.Tensor,
|
||||||
|
positions: torch.Tensor,
|
||||||
|
forward_batch: ForwardBatch,
|
||||||
|
input_embeds: Optional[torch.Tensor] = None,
|
||||||
|
get_embedding: bool = True,
|
||||||
|
) -> EmbeddingPoolerOutput:
|
||||||
|
assert get_embedding, f"{self.__class__.__name__} is only used for embedding"
|
||||||
|
hidden_states = super().forward(
|
||||||
|
input_ids=input_ids,
|
||||||
|
positions=positions,
|
||||||
|
forward_batch=forward_batch,
|
||||||
|
input_embeds=input_embeds,
|
||||||
|
)
|
||||||
|
return self.pooler(hidden_states, forward_batch)
|
||||||
|
|
||||||
|
def load_weights(
|
||||||
|
self, weights: Iterable[Tuple[str, torch.Tensor]], is_mtp: bool = False
|
||||||
|
) -> Set[str]:
|
||||||
|
stacked_params_mapping = [
|
||||||
|
("qkv_proj", "q_proj", "q"),
|
||||||
|
("qkv_proj", "k_proj", "k"),
|
||||||
|
("qkv_proj", "v_proj", "v"),
|
||||||
|
]
|
||||||
|
|
||||||
|
params_dict = dict(self.named_parameters())
|
||||||
|
loaded_params: Set[str] = set()
|
||||||
|
|
||||||
|
for name, loaded_weight in weights:
|
||||||
|
if "rotary_emb.inv_freq" in name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Handle conv weight/bias naming: HF uses conv.conv, we use conv_weight/conv_bias
|
||||||
|
if ".conv.conv.weight" in name:
|
||||||
|
name = name.replace(".conv.conv.weight", ".conv.conv_weight")
|
||||||
|
loaded_weight = loaded_weight.squeeze(1) # (D, 1, K) -> (D, K)
|
||||||
|
if ".conv.conv.bias" in name:
|
||||||
|
name = name.replace(".conv.conv.bias", ".conv.conv_bias")
|
||||||
|
|
||||||
|
# Handle QKV stacking
|
||||||
|
for param_name, weight_name, shard_id in stacked_params_mapping:
|
||||||
|
if weight_name not in name:
|
||||||
|
continue
|
||||||
|
name = name.replace(weight_name, param_name)
|
||||||
|
if name.endswith(".bias") and name not in params_dict:
|
||||||
|
break
|
||||||
|
if name not in params_dict:
|
||||||
|
break
|
||||||
|
param = params_dict[name]
|
||||||
|
weight_loader = getattr(param, "weight_loader")
|
||||||
|
weight_loader(param, loaded_weight, shard_id)
|
||||||
|
loaded_params.add(name)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if name.endswith(".bias") and name not in params_dict:
|
||||||
|
continue
|
||||||
|
if name not in params_dict:
|
||||||
|
continue
|
||||||
|
|
||||||
|
param = params_dict[name]
|
||||||
|
weight_loader = getattr(param, "weight_loader", default_weight_loader)
|
||||||
|
weight_loader(param, loaded_weight)
|
||||||
|
loaded_params.add(name)
|
||||||
|
|
||||||
|
return loaded_params
|
||||||
|
|
||||||
|
|
||||||
class Lfm2ForCausalLM(nn.Module):
|
class Lfm2ForCausalLM(nn.Module):
|
||||||
"""LFM2 for causal language modeling with hybrid attention/conv architecture."""
|
"""LFM2 for causal language modeling with hybrid attention/conv architecture."""
|
||||||
|
|
||||||
@@ -538,4 +697,4 @@ class Lfm2ForCausalLM(nn.Module):
|
|||||||
return loaded_params
|
return loaded_params
|
||||||
|
|
||||||
|
|
||||||
EntryClass = [Lfm2ForCausalLM]
|
EntryClass = [Lfm2ForCausalLM, Lfm2BidirectionalModel]
|
||||||
|
|||||||
Reference in New Issue
Block a user