634 lines
32 KiB
Python
634 lines
32 KiB
Python
import torch
|
|
import importlib
|
|
from packaging.version import Version
|
|
from transformers import PreTrainedModel, AutoConfig, AutoModel, AutoModelForCausalLM
|
|
from typing import Optional, List
|
|
|
|
from utils.config import LlmConfig
|
|
from utils.tokenizer import LlmTokenizer
|
|
from utils.model_mapper import ModelMapper
|
|
from utils.transformers import Embedding, Rotary, Decoder, Lm
|
|
|
|
class LlmModel(PreTrainedModel):
|
|
config_class = LlmConfig
|
|
|
|
def __init__(self, config, args=None):
|
|
super().__init__(config)
|
|
self.config = config
|
|
self.args = args
|
|
self.tokenizer = None
|
|
self.model = None
|
|
self.visual = None
|
|
self.audio = None
|
|
self.talker = None
|
|
self.mtp = None
|
|
self.scale_emb = None
|
|
|
|
def _init_weights(self, module):
|
|
pass
|
|
|
|
@staticmethod
|
|
def _sanitize_skip_weight_tensors(model):
|
|
# Some small parameters remain ONNX Consts even in skeleton mode; keep them finite for JSON export.
|
|
def fill_tensor(name, tensor):
|
|
if tensor is None or getattr(tensor, "is_meta", False):
|
|
return
|
|
if not tensor.is_floating_point() or tensor.dim() > 1:
|
|
return
|
|
try:
|
|
with torch.no_grad():
|
|
if name.endswith("weight") or name.endswith("gamma") or "scale" in name:
|
|
tensor.fill_(1.0)
|
|
else:
|
|
tensor.zero_()
|
|
except (NotImplementedError, RuntimeError):
|
|
pass
|
|
|
|
for name, param in model.named_parameters():
|
|
fill_tensor(name, param)
|
|
for name, buffer in model.named_buffers():
|
|
fill_tensor(name, buffer)
|
|
|
|
def get_config(self):
|
|
llm_config = {}
|
|
models = ['visual', 'audio', 'talker']
|
|
for m in models:
|
|
if hasattr(self, m) and getattr(self, m) is not None:
|
|
m_config = getattr(self, m).get_config()
|
|
llm_config.update(m_config)
|
|
return llm_config
|
|
|
|
@staticmethod
|
|
def get_model_class(model_type: str):
|
|
# Same as in LlmExporter
|
|
MODEL_CLASS_MAPPING = {
|
|
'qwen3_5': 'Qwen3_5ForConditionalGeneration',
|
|
'qwen3_5_moe': 'Qwen3_5MoeForConditionalGeneration',
|
|
'qwen3_vl': 'Qwen3VLForConditionalGeneration',
|
|
'qwen3_vl_moe': 'Qwen3VLMoeForConditionalGeneration',
|
|
'qwen2_5_omni': 'Qwen2_5OmniForConditionalGeneration',
|
|
'qwen2_5_vl': 'Qwen2_5_VLForConditionalGeneration',
|
|
'qwen2_vl': 'Qwen2VLForConditionalGeneration',
|
|
'qwen2_audio': 'Qwen2AudioForConditionalGeneration',
|
|
'smolvlm': 'AutoModelForImageTextToText',
|
|
'idefics3': 'AutoModelForVision2Seq',
|
|
'funaudiochat': 'AutoModelForSeq2SeqLM',
|
|
'glm_ocr': 'GlmOcrForConditionalGeneration',
|
|
'lfm2_vl': 'Lfm2VlForConditionalGeneration',
|
|
'gemma4': 'Gemma4ForConditionalGeneration',
|
|
}
|
|
if model_type is None or model_type not in MODEL_CLASS_MAPPING:
|
|
return AutoModelForCausalLM
|
|
class_name = MODEL_CLASS_MAPPING[model_type]
|
|
try:
|
|
module = importlib.import_module('transformers')
|
|
return getattr(module, class_name)
|
|
except (ImportError, AttributeError):
|
|
return AutoModelForCausalLM
|
|
|
|
@classmethod
|
|
def from_pretrained(cls, pretrained_model_name_or_path, args=None, **kwargs):
|
|
config = LlmConfig.from_pretrained(pretrained_model_name_or_path, **kwargs)
|
|
config.export_args = args
|
|
model_type = config.model_type
|
|
model_class = cls.get_model_class(model_type)
|
|
|
|
load_kwargs = {'trust_remote_code': True}
|
|
if Version(importlib.metadata.version("transformers")) >= Version("4.56.0"):
|
|
load_kwargs['dtype'] = 'auto'
|
|
else:
|
|
load_kwargs['torch_dtype'] = 'auto'
|
|
|
|
if model_type == 'internvl_chat':
|
|
load_kwargs['use_flash_attn'] = False
|
|
|
|
# Check if skip_weight mode is enabled (load structure only, no weights)
|
|
skip_weight = args is not None and hasattr(args, 'skip_weight') and args.skip_weight
|
|
|
|
if skip_weight:
|
|
# Load model skeleton without weights using accelerate
|
|
from accelerate import init_empty_weights
|
|
with init_empty_weights():
|
|
original_config = AutoConfig.from_pretrained(pretrained_model_name_or_path, trust_remote_code=True)
|
|
# Try different methods to create model from config (some models don't have from_config)
|
|
if hasattr(model_class, 'from_config'):
|
|
original_model = model_class.from_config(original_config, trust_remote_code=True)
|
|
elif hasattr(model_class, '_from_config'):
|
|
original_model = model_class._from_config(original_config)
|
|
else:
|
|
original_model = AutoModelForCausalLM.from_config(original_config, trust_remote_code=True)
|
|
original_model.to_empty(device="cpu")
|
|
cls._sanitize_skip_weight_tensors(original_model)
|
|
elif model_type == 'lfm2_audio':
|
|
# LFM2-Audio uses liquid_audio package, not standard HF class
|
|
from pathlib import Path
|
|
from liquid_audio import LFM2AudioModel
|
|
original_model = LFM2AudioModel.from_pretrained(
|
|
Path(pretrained_model_name_or_path), device='cpu', dtype=torch.bfloat16
|
|
)
|
|
# Force sdpa attention on CPU (flash_attention_2 requires GPU)
|
|
original_model.lfm.set_attn_implementation('sdpa')
|
|
else:
|
|
# Normal loading with weights
|
|
try:
|
|
original_model = model_class.from_pretrained(pretrained_model_name_or_path, **load_kwargs)
|
|
except Exception:
|
|
original_model = AutoModel.from_pretrained(pretrained_model_name_or_path, **load_kwargs)
|
|
|
|
# print(f"Loading model type: {model_type}\n{original_model}")
|
|
|
|
# LoRA
|
|
if (args is not None
|
|
and hasattr(args, 'lora_path') and args.lora_path is not None
|
|
and (not hasattr(args, 'lora_split') or not args.lora_split)):
|
|
from peft import PeftModel
|
|
adapter = PeftModel.from_pretrained(original_model, model_id=args.lora_path)
|
|
original_model = adapter.merge_and_unload(progressbar=True)
|
|
|
|
original_model = original_model.eval()
|
|
model = cls(config, args)
|
|
|
|
ModelMapper.do_map(model, original_model, config.model_map['model'])
|
|
|
|
model.tokenizer = LlmTokenizer.from_pretrained(
|
|
pretrained_model_name_or_path,
|
|
model_type=model_type
|
|
)
|
|
|
|
# Rebuild modules
|
|
if model.lm is None:
|
|
out_features, in_features = model.embed.weight.shape
|
|
model.lm = torch.nn.Linear(in_features, out_features, bias=False)
|
|
model.lm.weight = model.embed.weight
|
|
elif not isinstance(model.lm, torch.nn.Linear):
|
|
weight = model.lm.weight
|
|
out_features, in_features = weight.shape
|
|
model.lm = torch.nn.Linear(in_features, out_features, bias=False)
|
|
model.lm.weight = weight
|
|
|
|
model.embed = Embedding(model.embed, config)
|
|
|
|
# gemma3/gemma3_text/gemma4: dual rotary for sliding vs full attention layers
|
|
# rope_parameters has form {'sliding_attention': {'rope_theta': ...}, 'full_attention': {'rope_theta': ...}}
|
|
_rp = getattr(config, 'rope_parameters', None)
|
|
_is_dual_rope = (model_type in ('gemma3', 'gemma3_text', 'gemma4')
|
|
and _rp is not None
|
|
and isinstance(_rp, dict)
|
|
and any(isinstance(v, dict) for v in _rp.values()))
|
|
if _is_dual_rope:
|
|
rp = config.rope_parameters
|
|
origin_config = config.origin_config
|
|
text_config = origin_config.text_config if hasattr(origin_config, 'text_config') else origin_config
|
|
# Sliding attention rotary
|
|
sliding_rp = rp.get('sliding_attention', {})
|
|
sliding_config = type('Config', (), {
|
|
'rope_theta': sliding_rp.get('rope_theta', 10000.0),
|
|
'rope_ratio': None,
|
|
'head_dim': text_config.head_dim,
|
|
'model_type': model_type,
|
|
'rope_parameters': None,
|
|
'rope_scaling': None,
|
|
'max_position_embeddings': config.max_position_embeddings if hasattr(config, 'max_position_embeddings') else 131072,
|
|
})()
|
|
model.rotary_sliding = Rotary(sliding_config)
|
|
# Full attention rotary
|
|
full_rp = rp.get('full_attention', {})
|
|
global_head_dim = getattr(text_config, 'global_head_dim', text_config.head_dim)
|
|
partial_factor = full_rp.get('partial_rotary_factor', 1.0)
|
|
full_config = type('Config', (), {
|
|
'rope_theta': full_rp.get('rope_theta', 1000000.0),
|
|
'rope_ratio': None,
|
|
'head_dim': global_head_dim,
|
|
'model_type': model_type,
|
|
'rope_parameters': None,
|
|
'rope_scaling': None,
|
|
'max_position_embeddings': config.max_position_embeddings if hasattr(config, 'max_position_embeddings') else 131072,
|
|
})()
|
|
model.rotary_full = Rotary(full_config)
|
|
# Adjust rotary_dim for partial rotary factor
|
|
if partial_factor < 1.0:
|
|
rotary_dim = int(global_head_dim * partial_factor)
|
|
model.rotary_full.rotary_dim = rotary_dim
|
|
model.rotary_full.theta = 1.0 / (full_rp.get('rope_theta', 1000000.0) ** (torch.arange(0, rotary_dim, 2, dtype=torch.float32) / global_head_dim))
|
|
model.rotary = model.rotary_sliding # default rotary for config reference
|
|
else:
|
|
model.rotary = Rotary(config)
|
|
model.rotary_sliding = None
|
|
model.rotary_full = None
|
|
|
|
model.blocks = torch.nn.ModuleList([
|
|
Decoder(block, i, config, model.rotary, config.model_map) for i, block in enumerate(model.blocks.children())
|
|
])
|
|
# Check for final_logit_softcapping (gemma4, gemma2)
|
|
origin_config = config.origin_config
|
|
text_config = origin_config.text_config if hasattr(origin_config, 'text_config') else origin_config
|
|
final_logit_softcapping = getattr(text_config, 'final_logit_softcapping', None)
|
|
model.lm = Lm(model.lm, final_logit_softcapping=final_logit_softcapping)
|
|
|
|
embed_scale = getattr(model.embed, 'embed_scale', None)
|
|
if embed_scale is not None:
|
|
if isinstance(embed_scale, torch.Tensor):
|
|
is_identity_scale = embed_scale.numel() == 1 and embed_scale.detach().cpu().item() == 1.0
|
|
else:
|
|
is_identity_scale = embed_scale == 1.0
|
|
if not is_identity_scale:
|
|
model.scale_emb = embed_scale
|
|
|
|
# Multi-modal parts
|
|
if model.visual is not None:
|
|
from utils.vision import Vision
|
|
vision_cls = Vision.get_vision(model_type)
|
|
if vision_cls is not None:
|
|
model.visual = vision_cls(model.visual.float(), model).float()
|
|
else:
|
|
model.visual = None
|
|
if hasattr(model, 'audio') and model.audio is not None:
|
|
from utils.audio import Audio
|
|
audio_type = model.audio.config.model_type if hasattr(model.audio, 'config') else model_type
|
|
audio_cls = Audio.get_audio(audio_type)
|
|
if audio_cls is not None:
|
|
model.audio = audio_cls(model.audio, model)
|
|
else:
|
|
model.audio = None
|
|
if hasattr(model, 'talker') and model.talker is not None:
|
|
from utils.talker import Talker
|
|
model.talker = Talker.get_talker(model_type)(model.talker, model.token2wav, model)
|
|
if model_type == 'poi_qwen2_mtp':
|
|
model.mtp = [model.mtp1, model.mtp2]
|
|
if model.mtp is not None:
|
|
from utils.mtp import Mtp
|
|
model.mtp = Mtp.get_mtp(model_type)(model.mtp, model)
|
|
|
|
return model
|
|
|
|
def embedding(self, input_ids):
|
|
# Store original input_ids for PLE (gemma4)
|
|
self._last_input_ids = input_ids
|
|
if self.visual is not None and input_ids.numel() > 1:
|
|
result = self.visual.embed(input_ids)
|
|
# Also apply audio embeddings if audio module has pending embeddings
|
|
if self.audio is not None and self.audio.audio_embeds is not None:
|
|
audio_pad_id = self.audio.config.audio_token_id
|
|
audio_mask = (input_ids == audio_pad_id).squeeze()
|
|
if audio_mask.any():
|
|
embed_scale = self.config.hidden_size ** 0.5
|
|
result[audio_mask] = self.audio.audio_embeds.to(result.dtype) / embed_scale
|
|
self.audio.audio_embeds = None
|
|
return result
|
|
if self.audio is not None and input_ids.numel() > 1:
|
|
return self.audio.embed(input_ids)
|
|
return self.embed(input_ids)
|
|
|
|
def compute_ple_from_embeddings(self, hidden_states, ple_embeddings):
|
|
"""Compute PLE from pre-looked-up embeddings (for export/C++ mode).
|
|
ple_embeddings: [1, seq_len, num_layers * ple_dim] — already scaled by embed_scale.
|
|
"""
|
|
num_layers = self.config.num_hidden_layers
|
|
ple_dim = ple_embeddings.shape[-1] // num_layers
|
|
per_layer_inputs = ple_embeddings.reshape(*ple_embeddings.shape[:2], num_layers, ple_dim)
|
|
# Project from main embeddings
|
|
hs_for_proj = hidden_states.view(1, -1, self.config.hidden_size)
|
|
per_layer_proj = self.per_layer_model_projection(hs_for_proj) * (self.config.hidden_size ** -0.5)
|
|
per_layer_proj = per_layer_proj.reshape(*hs_for_proj.shape[:-1], num_layers, ple_dim)
|
|
per_layer_proj = self.per_layer_projection_norm(per_layer_proj)
|
|
return (per_layer_proj + per_layer_inputs) * (2.0 ** -0.5)
|
|
|
|
def compute_ple(self, hidden_states, input_ids=None):
|
|
"""Compute Per-Layer Embeddings for gemma4."""
|
|
if not (hasattr(self, 'embed_tokens_per_layer') and self.embed_tokens_per_layer is not None):
|
|
return None
|
|
if input_ids is None:
|
|
input_ids = getattr(self, '_last_input_ids', None)
|
|
if input_ids is None:
|
|
return None
|
|
# Replace multimodal token IDs with pad_token_id for PLE lookup
|
|
# (matches HF behavior: llm_input_ids[multimodal_mask] = pad_token_id)
|
|
ple_ids = input_ids.clone()
|
|
oc = getattr(self.config, 'origin_config', self.config)
|
|
tc = getattr(oc, 'text_config', oc)
|
|
pad_token_id = getattr(tc, 'pad_token_id', 0) or 0
|
|
for attr in ['image_token_id', 'audio_token_id', 'video_token_id']:
|
|
token_id = getattr(oc, attr, None)
|
|
if isinstance(token_id, int):
|
|
ple_ids[ple_ids == token_id] = pad_token_id
|
|
num_layers = self.config.num_hidden_layers
|
|
ple_dim = self.embed_tokens_per_layer.embedding_dim // num_layers
|
|
# 1. Lookup per-layer embeddings (ScaledWordEmbedding applies scale internally)
|
|
per_layer_inputs = self.embed_tokens_per_layer(ple_ids)
|
|
per_layer_inputs = per_layer_inputs.reshape(*input_ids.shape, num_layers, ple_dim)
|
|
# 2. Project from main embeddings
|
|
hs_for_proj = hidden_states.view(1, -1, self.config.hidden_size)
|
|
per_layer_proj = self.per_layer_model_projection(hs_for_proj) * (self.config.hidden_size ** -0.5)
|
|
per_layer_proj = per_layer_proj.reshape(*hs_for_proj.shape[:-1], num_layers, ple_dim)
|
|
per_layer_proj = self.per_layer_projection_norm(per_layer_proj)
|
|
# 3. Combine
|
|
return (per_layer_proj + per_layer_inputs) * (2.0 ** -0.5)
|
|
|
|
def forward(self,
|
|
input_ids: torch.Tensor,
|
|
attention_mask: torch.Tensor,
|
|
position_ids: torch.Tensor,
|
|
logits_index: torch.Tensor = torch.tensor([-1], dtype=torch.int32),
|
|
deepstack_embeds: torch.Tensor = None,
|
|
ple_embeddings: torch.Tensor = None
|
|
):
|
|
hidden_states = input_ids # llm forward without embedding
|
|
|
|
# gemma4: compute PLE
|
|
# For ONNX export: scale_emb is NOT in forward(), it's applied externally.
|
|
# For Python test: scale_emb is applied in forward() above (selective scaling).
|
|
# Both cases: hidden_states at this point has text positions scaled.
|
|
per_layer_inputs = None
|
|
if hasattr(self, 'per_layer_model_projection') and self.per_layer_model_projection is not None:
|
|
ple_proj_input = hidden_states
|
|
# For multimodal inputs, PLE projection uses pad embeddings at multimodal positions
|
|
# (matches HF: llm_inputs_embeds = where(multimodal_mask, pad_embedding, inputs_embeds))
|
|
ids = getattr(self, '_last_input_ids', None)
|
|
if ids is not None:
|
|
oc = getattr(self.config, 'origin_config', self.config)
|
|
mm_mask = torch.zeros_like(ids, dtype=torch.bool)
|
|
for attr in ['image_token_id', 'audio_token_id', 'video_token_id']:
|
|
token_id = getattr(oc, attr, None)
|
|
if isinstance(token_id, int):
|
|
mm_mask = mm_mask | (ids == token_id)
|
|
if mm_mask.any():
|
|
tc = getattr(oc, 'text_config', oc)
|
|
pad_id = getattr(tc, 'pad_token_id', 0) or 0
|
|
pad_emb = self.embed(torch.tensor([[pad_id]])) # [1, 1, hidden_size]
|
|
ple_proj_input = hidden_states.clone()
|
|
mm_flat = mm_mask.squeeze()
|
|
ple_proj_input[mm_flat] = pad_emb.squeeze()
|
|
if ple_embeddings is None and hasattr(self, 'embed_tokens_per_layer') and self.embed_tokens_per_layer is not None:
|
|
if ids is not None:
|
|
per_layer_inputs = self.compute_ple(ple_proj_input, ids)
|
|
elif ple_embeddings is not None:
|
|
per_layer_inputs = self.compute_ple_from_embeddings(ple_proj_input, ple_embeddings)
|
|
|
|
# scale_emb: multiply ALL positions uniformly (text + vision).
|
|
# Vision positions are pre-divided by scale_emb, so after this multiply they restore.
|
|
if self.scale_emb is not None:
|
|
hidden_states = hidden_states * self.scale_emb
|
|
|
|
spec_hidden_states = []
|
|
eagle_layer_ids = set()
|
|
dflash_layer_ids = set()
|
|
if self.args and self.args.eagle_path:
|
|
eagle_layer_ids = {len(self.blocks)-3, len(self.blocks)//2, 2}
|
|
elif self.args and hasattr(self.args, 'dflash_target_layer_ids') and self.args.dflash_target_layer_ids:
|
|
dflash_layer_ids = set(self.args.dflash_target_layer_ids)
|
|
|
|
rotary_pos_emb = self.rotary(position_ids)
|
|
if self.args and self.args.test and rotary_pos_emb.dtype != hidden_states.dtype:
|
|
rotary_pos_emb = rotary_pos_emb.type(hidden_states.dtype)
|
|
|
|
# gemma4: compute separate rotary for full attention layers
|
|
rotary_pos_emb_full = None
|
|
if self.rotary_full is not None:
|
|
rotary_pos_emb_full = self.rotary_full(position_ids)
|
|
if self.args and self.args.test and rotary_pos_emb_full.dtype != hidden_states.dtype:
|
|
rotary_pos_emb_full = rotary_pos_emb_full.type(hidden_states.dtype)
|
|
|
|
# KV sharing cache (gemma4: layers 15-34 share KV with layers 13/14)
|
|
shared_kv_cache = {}
|
|
for i in range(len(self.blocks)):
|
|
# Set shared KV cache reference on attention
|
|
if hasattr(self.blocks[i].self_attn, 'is_kv_shared_layer'):
|
|
self.blocks[i].self_attn._shared_kv_cache = shared_kv_cache
|
|
|
|
# eagle: collect hidden states BEFORE the layer (input to layer)
|
|
if i in eagle_layer_ids:
|
|
spec_hidden_states.append(hidden_states)
|
|
# sliding or full attn mask
|
|
if self.config.attention_type == 'mix':
|
|
is_sliding = i in self.config.sliding_attn_layers
|
|
layer_attention_mask = attention_mask[int(is_sliding)]
|
|
else:
|
|
layer_attention_mask = attention_mask
|
|
|
|
# gemma4: use different rotary for full vs sliding layers
|
|
if rotary_pos_emb_full is not None and not (hasattr(self.config, 'sliding_attn_layers') and i in self.config.sliding_attn_layers):
|
|
layer_rotary = rotary_pos_emb_full
|
|
else:
|
|
layer_rotary = rotary_pos_emb
|
|
|
|
# Set per-layer input for PLE
|
|
if per_layer_inputs is not None:
|
|
self.blocks[i]._per_layer_input = per_layer_inputs[:, :, i, :]
|
|
hidden_states = self.blocks[i](hidden_states, layer_rotary, layer_attention_mask)
|
|
if deepstack_embeds is not None and i in range(deepstack_embeds.shape[0]):
|
|
hidden_states += deepstack_embeds[i]
|
|
|
|
# dflash: collect hidden states AFTER the layer (output of layer)
|
|
if i in dflash_layer_ids:
|
|
spec_hidden_states.append(hidden_states)
|
|
|
|
talker_embeds = None
|
|
if hasattr(self, 'talker') and self.talker is not None:
|
|
talker_embeds = self.final_layernorm(hidden_states) + input_ids.permute([1, 0, 2])
|
|
self.talker.add_talker_embeds(talker_embeds)
|
|
|
|
final_layernorm = hidden_states
|
|
logits_index_long = logits_index.to(torch.int64)
|
|
if self.mtp is None:
|
|
hidden_states = hidden_states[:, logits_index_long:, :]
|
|
hidden_states = self.final_layernorm(hidden_states)
|
|
# default: set hidden_state before lm_head as output node
|
|
final_layernorm = hidden_states
|
|
else:
|
|
# final_layernorm need compute all logists
|
|
if self.config.model_type == 'mimo':
|
|
final_layernorm = hidden_states # mimo
|
|
hidden_states = self.final_layernorm(hidden_states)
|
|
if self.config.model_type == 'poi_qwen2_mtp':
|
|
final_layernorm = hidden_states # poi
|
|
hidden_states = hidden_states[:, logits_index_long:, :]
|
|
logits = self.lm(hidden_states)
|
|
|
|
if self.args and (self.args.eagle_path is not None or (hasattr(self.args, 'dflash_target_layer_ids') and self.args.dflash_target_layer_ids)):
|
|
final_layernorm = torch.cat(spec_hidden_states, dim=-1)
|
|
|
|
return logits, final_layernorm, talker_embeds
|
|
|
|
def get_attention_mask(self, seq_len: int, new_tokens: int = 0):
|
|
if self.config.model_type == 'chatglm':
|
|
return self.chatglm_attention_mask()
|
|
if self.config.attention_type == 'full':
|
|
return self.full_attention_mask(seq_len, new_tokens)
|
|
elif self.config.attention_type == 'sliding':
|
|
return self.sliding_attention_mask(self.config.sliding_window, seq_len, new_tokens)
|
|
elif self.config.attention_type == 'mix':
|
|
full_mask = self.full_attention_mask(seq_len, new_tokens)
|
|
sliding_mask = self.sliding_attention_mask(self.config.sliding_window, seq_len, new_tokens)
|
|
return torch.stack([full_mask, sliding_mask], dim=0)
|
|
return None
|
|
|
|
def full_attention_mask(self, seq_len, new_tokens):
|
|
if new_tokens:
|
|
return torch.zeros([1, 1, 1, seq_len], dtype=torch.float32)
|
|
return (1 - torch.tril(torch.ones([1, 1, seq_len, seq_len]))) * torch.finfo(torch.float32).min
|
|
|
|
def sliding_attention_mask(self, sliding_window, seq_len, new_tokens):
|
|
if new_tokens:
|
|
sliding_mask = torch.zeros([1, 1, 1, seq_len], dtype=torch.float32)
|
|
num_tokens_to_mask = seq_len - sliding_window
|
|
if num_tokens_to_mask > 0:
|
|
sliding_mask[..., :num_tokens_to_mask] = torch.finfo(torch.float32).min
|
|
return sliding_mask
|
|
causal_mask = torch.tril(torch.ones(seq_len, seq_len, dtype=torch.bool))
|
|
query_indices = torch.arange(seq_len).view(-1, 1)
|
|
key_indices = torch.arange(seq_len).view(1, -1)
|
|
window_mask = (key_indices > query_indices - sliding_window)
|
|
final_mask_bool = causal_mask & window_mask
|
|
sliding_mask = torch.where(final_mask_bool, 0.0, torch.finfo(torch.float32).min)
|
|
return sliding_mask.view(1, 1, seq_len, seq_len)
|
|
|
|
def get_position_ids(self, seq_len, new_tokens=0, input_ids=None):
|
|
if self.visual is not None and hasattr(self.visual, 'get_position_ids'):
|
|
return self.visual.get_position_ids(input_ids, seq_len, new_tokens)
|
|
if self.config.model_type == 'chatglm':
|
|
return self.chatglm_position_ids(seq_len, new_tokens)
|
|
if new_tokens:
|
|
position_ids = torch.tensor([seq_len - 1], dtype=torch.int)
|
|
else:
|
|
position_ids = torch.arange(seq_len, dtype=torch.int)
|
|
|
|
if self.rotary.is_mrope:
|
|
position_ids = torch.stack([position_ids] * 3)
|
|
else:
|
|
position_ids = position_ids.unsqueeze(0)
|
|
return position_ids
|
|
|
|
def chatglm_attention_mask(self, seq_len, is_decode):
|
|
if is_decode:
|
|
return torch.zeros([1]).bool().reshape([1, 1, 1, 1])
|
|
attention_mask = torch.zeros([seq_len, seq_len], dtype=torch.bool)
|
|
for i in range(seq_len - 1):
|
|
attention_mask[i][-1] = True
|
|
return attention_mask.reshape([1, 1, seq_len, seq_len])
|
|
|
|
def chatglm_position_ids(self, seq_len, new_tokens):
|
|
if new_tokens:
|
|
return torch.tensor([seq_len - 2, new_tokens + 1]).reshape([1, 2, 1])
|
|
position_ids_0 = torch.arange(seq_len, dtype=torch.int)
|
|
position_ids_1 = torch.zeros(seq_len, dtype=torch.int)
|
|
position_ids_0[-1] = position_ids_0[-2]
|
|
position_ids_1[-1] = 1
|
|
return torch.stack([position_ids_0, position_ids_1]).view(1, 2, -1)
|
|
|
|
class EmbeddingModel(LlmModel):
|
|
def __init__(self, config, args=None):
|
|
super().__init__(config, args)
|
|
self.is_reranker = False
|
|
|
|
@classmethod
|
|
def from_pretrained(cls, pretrained_model_name_or_path, args=None, **kwargs):
|
|
config = AutoConfig.from_pretrained(pretrained_model_name_or_path, trust_remote_code=True)
|
|
model_type = config.model_type
|
|
if model_type == 'qwen3':
|
|
model = super(EmbeddingModel, cls).from_pretrained(pretrained_model_name_or_path, args=args).float().eval()
|
|
return model
|
|
# gte, bge
|
|
config._attn_implementation = 'eager'
|
|
model = cls(config, args)
|
|
if model_type == 'new' and 'NewForSequenceClassification' in config.architectures:
|
|
model.is_reranker = True
|
|
from transformers import AutoModelForSequenceClassification
|
|
origin_model = AutoModelForSequenceClassification.from_pretrained(pretrained_model_name_or_path, config=config, trust_remote_code=True).float().eval()
|
|
model.classifier = origin_model.classifier
|
|
origin_model = origin_model.new
|
|
else:
|
|
origin_model = AutoModel.from_pretrained(pretrained_model_name_or_path, config=config, trust_remote_code=True).float().eval()
|
|
|
|
transformer = origin_model.encoder
|
|
model.lm = origin_model.pooler
|
|
model.embed = origin_model.embeddings
|
|
model.word_embeddings = model.embed.word_embeddings
|
|
model.token_type_embeddings = model.embed.token_type_embeddings.weight.data[0]
|
|
model.embedding_layernorm = model.embed.LayerNorm
|
|
if hasattr(model.embed, 'position_embeddings'):
|
|
model.position_embeddings = model.embed.position_embeddings
|
|
model.hidden_size = model.word_embeddings.weight.shape[-1]
|
|
model.blocks = transformer.layer
|
|
# some wrapper
|
|
model.num_hidden_layers = len(model.blocks)
|
|
# transformers>=5.x zeroes non-persistent buffers during from_pretrained,
|
|
# force-recompute RoPE inv_freq / cos_sin cache so the exported graph carries valid values.
|
|
rope = getattr(model.embed, 'rotary_emb', None)
|
|
if rope is not None and hasattr(rope, '_set_cos_sin_cache') and rope.inv_freq.abs().sum().item() == 0:
|
|
max_pos = rope.max_position_embeddings
|
|
if hasattr(rope, 'scaling_factor'):
|
|
max_pos = int(max_pos * rope.scaling_factor)
|
|
rope._set_cos_sin_cache(max_pos, rope.inv_freq.device, torch.float32)
|
|
return model
|
|
|
|
def forward(self, inputs_embeds, attention_mask, position_ids):
|
|
if self.config.model_type == 'bert':
|
|
return self.bge_forward(inputs_embeds, attention_mask, position_ids)
|
|
if self.config.model_type == 'new':
|
|
return self.gte_forward(inputs_embeds, attention_mask, position_ids)
|
|
if self.config.model_type == 'qwen3':
|
|
return self.qwen3_forward(inputs_embeds, attention_mask, position_ids)
|
|
raise RuntimeError(f'Not support embedding model: {self.config.model_type}!')
|
|
|
|
def word_embed(self, input_ids):
|
|
if hasattr(self, 'word_embeddings'):
|
|
return self.word_embeddings(input_ids.view(1, -1))
|
|
return self.embed(input_ids.view(1, -1))
|
|
|
|
def bge_forward(self, inputs_embeds, attention_mask, position_ids):
|
|
inputs_embeds = inputs_embeds.reshape(1, -1, self.config.hidden_size)
|
|
position_embeddings = self.position_embeddings(position_ids)
|
|
embeddings = inputs_embeds + position_embeddings + self.token_type_embeddings
|
|
hidden_states = self.embedding_layernorm(embeddings)
|
|
for i in range(self.config.num_hidden_layers):
|
|
hidden_states = self.blocks[i](hidden_states, attention_mask)[0]
|
|
sentence_embeddings = hidden_states[:, 0]
|
|
sentence_embeddings = torch.nn.functional.normalize(sentence_embeddings, p=2, dim=1)
|
|
return sentence_embeddings
|
|
|
|
def gte_reranker_forward(self, inputs_embeds, attention_mask, position_ids):
|
|
freqs = position_ids.float().reshape(-1, 1) * self.embed.rotary_emb.inv_freq
|
|
emb = torch.cat((freqs, freqs), dim=-1)
|
|
rope_embeds = torch.stack([emb.cos(), emb.sin()]).unsqueeze(-2).unsqueeze(1)
|
|
hidden_states = self.embedding_layernorm(inputs_embeds + self.token_type_embeddings)
|
|
for i in range(self.config.num_hidden_layers):
|
|
hidden_states = self.blocks[i](hidden_states, attention_mask, rope_embeds)[0]
|
|
pooled_output = self.lm(hidden_states)
|
|
logits = self.classifier(pooled_output)
|
|
return logits
|
|
|
|
def gte_embedding_forward(self, inputs_embeds, attention_mask, position_ids):
|
|
inputs_embeds = inputs_embeds.reshape(1, -1, self.config.hidden_size)
|
|
freqs = position_ids.float().reshape(-1, 1) * self.embed.rotary_emb.inv_freq
|
|
emb = torch.cat((freqs, freqs), dim=-1)
|
|
rope_embeds = torch.stack([emb.cos(), emb.sin()]).unsqueeze(-2).unsqueeze(1)
|
|
attention_bias = 1 - attention_mask.float()
|
|
hidden_states = self.embedding_layernorm(inputs_embeds + self.token_type_embeddings)
|
|
for i in range(self.config.num_hidden_layers):
|
|
hidden_states = self.blocks[i](hidden_states, attention_bias, rope_embeds)[0]
|
|
sentence_embeddings = hidden_states[:, 0]
|
|
sentence_embeddings = torch.nn.functional.normalize(sentence_embeddings, p=2, dim=1)
|
|
return sentence_embeddings
|
|
|
|
def gte_forward(self, inputs_embeds, attention_mask, position_ids):
|
|
if self.is_reranker:
|
|
return self.gte_reranker_forward(inputs_embeds, attention_mask, position_ids)
|
|
return self.gte_embedding_forward(inputs_embeds, attention_mask, position_ids)
|
|
|
|
def qwen3_forward(self, inputs_embeds, attention_mask, position_ids):
|
|
hidden_states = inputs_embeds
|
|
rotary_pos_emb = self.rotary(position_ids)
|
|
for i in range(len(self.blocks)):
|
|
hidden_states = self.blocks[i](hidden_states, rotary_pos_emb, attention_mask)
|
|
last_hidden_states = hidden_states[:, -1, :]
|
|
last_hidden_states = self.final_layernorm(last_hidden_states)
|
|
return last_hidden_states
|
|
|
|
def get_position_ids(self, seq_len) -> torch.Tensor:
|
|
return torch.arange(seq_len, dtype=torch.long).unsqueeze(0)
|
|
|
|
def get_attention_mask(self, seq_len) -> torch.Tensor:
|
|
if self.config.model_type == 'qwen3':
|
|
return super().get_attention_mask(seq_len, 0)
|
|
return torch.ones([1, 1, seq_len, seq_len], dtype=torch.float)
|