chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:21 +08:00
commit bc34f6df14
1149 changed files with 328099 additions and 0 deletions
@@ -0,0 +1,18 @@
from FlagEmbedding.abc.finetune.embedder import (
AbsEmbedderDataArguments as DecoderOnlyEmbedderDataArguments,
AbsEmbedderTrainingArguments as DecoderOnlyEmbedderTrainingArguments,
)
from .arguments import DecoderOnlyEmbedderModelArguments
from .modeling import BiDecoderOnlyEmbedderModel
from .trainer import DecoderOnlyEmbedderTrainer
from .runner import DecoderOnlyEmbedderRunner
__all__ = [
'DecoderOnlyEmbedderDataArguments',
'DecoderOnlyEmbedderTrainingArguments',
'DecoderOnlyEmbedderModelArguments',
'BiDecoderOnlyEmbedderModel',
'DecoderOnlyEmbedderTrainer',
'DecoderOnlyEmbedderRunner',
]
@@ -0,0 +1,31 @@
from transformers import HfArgumentParser
from FlagEmbedding.finetune.embedder.decoder_only.base import (
DecoderOnlyEmbedderDataArguments,
DecoderOnlyEmbedderTrainingArguments,
DecoderOnlyEmbedderModelArguments,
DecoderOnlyEmbedderRunner,
)
def main():
parser = HfArgumentParser((
DecoderOnlyEmbedderModelArguments,
DecoderOnlyEmbedderDataArguments,
DecoderOnlyEmbedderTrainingArguments
))
model_args, data_args, training_args = parser.parse_args_into_dataclasses()
model_args: DecoderOnlyEmbedderModelArguments
data_args: DecoderOnlyEmbedderDataArguments
training_args: DecoderOnlyEmbedderTrainingArguments
runner = DecoderOnlyEmbedderRunner(
model_args=model_args,
data_args=data_args,
training_args=training_args
)
runner.run()
if __name__ == "__main__":
main()
@@ -0,0 +1,76 @@
from typing import Optional, List
from dataclasses import dataclass, field
from FlagEmbedding.abc.finetune.embedder import AbsEmbedderModelArguments
def default_target_modules() -> List[int]:
return ['v_proj', 'q_proj', 'k_proj', 'gate_proj', 'down_proj', 'o_proj', 'up_proj']
@dataclass
class DecoderOnlyEmbedderModelArguments(AbsEmbedderModelArguments):
"""
Model argument class for decoder only base model.
"""
peft_model_path: str = field(
default='', metadata={"help": "The peft model checkpoint for initialization."}
)
use_lora: bool = field(
default=True,
metadata={"help": "If passed, will use LORA (low-rank parameter-efficient training) to train the model."}
)
lora_rank: int = field(
default=64,
metadata={"help": "The rank of lora."}
)
lora_alpha: float = field(
default=16,
metadata={"help": "The alpha parameter of lora."}
)
lora_dropout: float = field(
default=0.1,
metadata={"help": "The dropout rate of lora modules."}
)
target_modules: List[str] = field(
default_factory=default_target_modules,
metadata={"help": "The target modules to apply LORA."}
)
use_flash_attn: bool = field(
default=False,
metadata={"help": "If passed, will use flash attention to train the model."}
)
use_slow_tokenizer: bool = field(
default=False,
metadata={"help": "If passed, will use a slow tokenizer (not backed by the 🤗 Tokenizers library)."}
)
# low_cpu_mem_usage: bool = field(
# default=False,
# metadata={"help": "It is an option to create the model as an empty shell,"
# "then only materialize its parameters when the pretrained weights are loaded."
# "If passed, LLM loading time and RAM consumption will be benefited."}
# )
from_peft: str = field(
default=None
)
modules_to_save: List[str] = field(
default=None,
)
raw_peft: str = field(
default=None
)
additional_special_tokens: Optional[str] = field(
default=None,
metadata={"help": "additional special tokens", "nargs": "+"}
)
save_merged_lora_model: bool = field(
default=False,
metadata={"help": "If passed, will merge the lora modules and save the entire model."}
)
only_merge_lora_model: bool = field(
default=False,
metadata={"help": "If passed, will only merge the lora modules and save the entire model."}
)
@@ -0,0 +1,185 @@
import os
import re
import torch
import logging
from transformers import AutoConfig, AutoModel, AutoTokenizer
from peft import LoraConfig, TaskType, get_peft_model, PeftModel
from .arguments import DecoderOnlyEmbedderModelArguments
logger = logging.getLogger(__name__)
def find_largest_checkpoint(checkpoint_dir):
"""Find the largest checkpoint from directory.
Args:
checkpoint_dir (str): Directory to the checkpoint.
Returns:
str: Directory to the checkpoint, None no matching found.
"""
checkpoint_pattern = re.compile(r'checkpoint-(\d+)')
max_number = -1
max_checkpoint_file = None
for file in os.listdir(checkpoint_dir):
match = checkpoint_pattern.search(file)
if match:
number = int(match.group(1))
if number > max_number:
max_number = number
max_checkpoint_file = file
if max_checkpoint_file:
return os.path.join(checkpoint_dir, max_checkpoint_file)
else:
return None
def get_model(model_args: DecoderOnlyEmbedderModelArguments, output_dir: str, resize: bool, resize_tokens: int):
"""Get the model.
Args:
model_args (DecoderOnlyEmbedderModelArguments): Model arguments instance.
output_dir (str): Directory to save the model.
resize (bool): Whether to resize the number of tokens.
resize_tokens (int): The new token size.
Returns:
transformers.PreTrainedModel or PeftModel: The loaded model.
"""
if model_args.config_name:
config = AutoConfig.from_pretrained(
model_args.config_name,
token=model_args.token,
cache_dir=model_args.cache_dir,
trust_remote_code=model_args.trust_remote_code,
)
elif model_args.model_name_or_path:
config = AutoConfig.from_pretrained(
model_args.model_name_or_path,
token=model_args.token,
cache_dir=model_args.cache_dir,
trust_remote_code=model_args.trust_remote_code,
)
else:
raise ValueError(
"You are instantiating a new config instance from scratch. This is not supported by this script."
)
config.use_cache = False
if model_args.model_name_or_path:
model = AutoModel.from_pretrained(
model_args.model_name_or_path,
# torch_dtype=torch.bfloat16,
attn_implementation = "flash_attention_2" if model_args.use_flash_attn else None,
token=model_args.token,
cache_dir=model_args.cache_dir,
from_tf=bool(".ckpt" in model_args.model_name_or_path),
config=config,
trust_remote_code=model_args.trust_remote_code,
)
else:
logger.info("Training new model from scratch")
model = model_args.from_config(config)
if model_args.raw_peft is not None:
model.set_input_embeddings(torch.load(os.path.join(model_args.raw_peft, 'embedding', 'emb.pth')))
model = PeftModel.from_pretrained(model, model_args.raw_peft)
model = model.merge_and_unload()
if resize:
model.resize_token_embeddings(resize_tokens)
os.makedirs(os.path.join(output_dir, 'embedding'), exist_ok=True)
torch.save(model.embed_tokens, os.path.join(output_dir, 'embedding', 'emb.pth'))
target_modules = model_args.target_modules
else:
target_modules = model_args.target_modules
if 'embed_tokens' in target_modules:
target_modules.remove('embed_tokens')
if model_args.from_peft is not None:
if os.path.exists(os.path.join(model_args.from_peft, 'embedding')):
model.set_input_embeddings(torch.load(os.path.join(model_args.from_peft, 'embedding', 'emb.pth')))
torch.save(model.embed_tokens, os.path.join(output_dir, 'embedding', 'emb.pth'))
model = PeftModel.from_pretrained(model, model_args.from_peft, is_trainable=True)
model.print_trainable_parameters()
else:
if model_args.use_lora:
peft_config = LoraConfig(
task_type=TaskType.FEATURE_EXTRACTION,
inference_mode=False,
r=model_args.lora_rank,
target_modules=target_modules,
modules_to_save=model_args.modules_to_save,
lora_alpha=model_args.lora_alpha,
lora_dropout=model_args.lora_dropout
)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
return model
def save_merged_model(model_args: DecoderOnlyEmbedderModelArguments, output_dir: str):
"""
Loads a model with specified configurations, merges it with PEFT layers if available.
Args:
model_args (DecoderOnlyEmbedderModelArguments): Model arguments instance.
output_dir (str): Directory to save the model.
"""
if model_args.config_name:
config = AutoConfig.from_pretrained(
model_args.config_name,
token=model_args.token,
cache_dir=model_args.cache_dir,
trust_remote_code=model_args.trust_remote_code,
)
elif model_args.model_name_or_path:
config = AutoConfig.from_pretrained(
model_args.model_name_or_path,
token=model_args.token,
cache_dir=model_args.cache_dir,
trust_remote_code=model_args.trust_remote_code,
)
else:
raise ValueError(
"You are instantiating a new config instance from scratch. This is not supported by this script."
)
config.use_cache = False
if model_args.model_name_or_path:
model = AutoModel.from_pretrained(
model_args.model_name_or_path,
# torch_dtype=torch.bfloat16,
attn_implementation = "flash_attention_2" if model_args.use_flash_attn else None,
token=model_args.token,
cache_dir=model_args.cache_dir,
from_tf=bool(".ckpt" in model_args.model_name_or_path),
config=config,
trust_remote_code=model_args.trust_remote_code,
)
else:
model = model_args.from_config(config)
if model_args.raw_peft is not None:
model.set_input_embeddings(torch.load(os.path.join(model_args.raw_peft, 'embedding', 'emb.pth')))
model = PeftModel.from_pretrained(model, model_args.raw_peft)
model = model.merge_and_unload()
if os.path.exists(os.path.join(output_dir, 'embedding', 'emb.pth')):
model.set_input_embeddings(torch.load(os.path.join(output_dir, 'embedding', 'emb.pth')))
# modify the vocab size in the model configuration
model.config.vocab_size = len(tokenizer)
try:
model = PeftModel.from_pretrained(model, output_dir)
model = model.merge_and_unload()
except:
model = PeftModel.from_pretrained(model, find_largest_checkpoint(output_dir))
model = model.merge_and_unload()
tokenizer = AutoTokenizer.from_pretrained(output_dir, trust_remote_code=model_args.trust_remote_code)
tokenizer.save_pretrained(os.path.join(output_dir, 'merged_model'))
model.save_pretrained(os.path.join(output_dir, 'merged_model'))
@@ -0,0 +1,207 @@
import logging
from typing import List
import torch
from transformers import AutoModel, PreTrainedModel, PreTrainedTokenizer
from FlagEmbedding.abc.finetune.embedder import AbsEmbedderModel
logger = logging.getLogger(__name__)
class BiDecoderOnlyEmbedderModel(AbsEmbedderModel):
"""Embedder model class for decoder only model.
Args:
base_model (PreTrainedModel): The base model to train on.
tokenizer (PreTrainedTokenizer, optional): The tokenizer to use. Defaults to ``None``.
negatives_cross_device (bool, optional): If True, will compute cross devices negative loss. Defaults to ``False``.
temperature (float, optional): Temperature to control the scale of scores. Defaults to ``1.0``.
sub_batch_size (int, optional): Sub-batch size during encoding. If negative, will not split to sub-batch.
Defaults to ``-1``.
kd_loss_type (str, optional): Type of knowledge distillation loss. Defaults to ``'kl_div'``.
use_mrl (bool, optional): Whether to use MRL for training. Defaults to ``False``.
mrl_dims (List[int], optional): The dimensions of MRL layers. Defaults to ``[]``.
sentence_pooling_method (str, optional): Pooling method to get sentence embedding. Defaults to ``'last_token'``.
normalize_embeddings (bool, optional): If True, normalize the embedding vector. Defaults to ``False``.
"""
TRANSFORMER_CLS = AutoModel
def __init__(
self,
base_model: PreTrainedModel,
tokenizer: PreTrainedTokenizer = None,
negatives_cross_device: bool = False,
temperature: float = 1.0,
sub_batch_size: int = -1,
kd_loss_type: str = 'kl_div',
use_mrl: bool = False,
mrl_dims: List[int] = [],
sentence_pooling_method: str = 'last_token',
normalize_embeddings: bool = False,
):
super().__init__(
base_model,
tokenizer=tokenizer,
negatives_cross_device=negatives_cross_device,
temperature=temperature,
sub_batch_size=sub_batch_size,
kd_loss_type=kd_loss_type,
use_mrl=use_mrl,
mrl_dims=mrl_dims,
)
self.sentence_pooling_method = sentence_pooling_method
self.normalize_embeddings = normalize_embeddings
self.cross_entropy = torch.nn.CrossEntropyLoss(reduction='mean')
def encode(self, features):
"""
Encode and get the embedding.
Args:
features (Union[list, dict]): Features feed to the model.
Returns:
Union[torch.Tensor, List[torch.Tensor]]: The embedding vectors or a list of embedding vectors if MRL is used.
"""
if features is None:
return None
if not isinstance(features, list):
if self.sub_batch_size is not None and self.sub_batch_size > 0:
all_p_reps = []
for i in range(0, len(features['attention_mask']), self.sub_batch_size):
end_inx = min(i + self.sub_batch_size, len(features['attention_mask']))
sub_features = {}
for k, v in features.items():
sub_features[k] = v[i:end_inx]
last_hidden_state = self.model(**sub_features, return_dict=True).last_hidden_state
p_reps = self._sentence_embedding(last_hidden_state, sub_features['attention_mask'])
all_p_reps.append(p_reps)
all_p_reps = torch.cat(all_p_reps, 0).contiguous()
else:
last_hidden_state = self.model(**features, return_dict=True).last_hidden_state
all_p_reps = self._sentence_embedding(last_hidden_state, features['attention_mask'])
else:
all_p_reps = []
for sub_features in features:
last_hidden_state = self.model(**sub_features, return_dict=True).last_hidden_state
p_reps = self._sentence_embedding(last_hidden_state, sub_features['attention_mask'])
all_p_reps.append(p_reps)
all_p_reps = torch.cat(all_p_reps, 0).contiguous()
if self.use_mrl:
p_reps_list = []
ori_dim = all_p_reps.size(-1)
for dim in self.mrl_dims:
if dim > ori_dim:
logger.warning(f"MRL dim {dim} is larger than original dimension {ori_dim}, using original dimension instead.")
dim = min(dim, ori_dim)
dim_p_reps = all_p_reps[:, :dim]
if self.normalize_embeddings:
dim_p_reps = torch.nn.functional.normalize(dim_p_reps, dim=-1)
p_reps_list.append(dim_p_reps.contiguous())
return p_reps_list
else:
if self.normalize_embeddings:
all_p_reps = torch.nn.functional.normalize(all_p_reps, dim=-1)
return all_p_reps.contiguous()
def _sentence_embedding(self, last_hidden_state, attention_mask):
"""Use the pooling method to get the sentence embedding.
Args:
last_hidden_state (torch.Tensor): The model output's last hidden state.
attention_mask (torch.Tensor): Mask out padding tokens during pooling.
Raises:
NotImplementedError: Specified pooling method not implemented.
Returns:
torch.Tensor: The sentence embeddings.
"""
if self.sentence_pooling_method == "cls":
return last_hidden_state[:, 0]
elif self.sentence_pooling_method == "mean":
s = torch.sum(
last_hidden_state * attention_mask.unsqueeze(-1).float(), dim=1
)
d = attention_mask.sum(dim=1, keepdim=True).float()
return s / d
elif self.sentence_pooling_method == "last_token":
left_padding = attention_mask[:, -1].sum() == attention_mask.shape[0]
if left_padding:
return last_hidden_state[:, -1]
else:
sequence_lengths = attention_mask.sum(dim=1) - 1
batch_size = last_hidden_state.shape[0]
return last_hidden_state[
torch.arange(batch_size, device=last_hidden_state.device),
sequence_lengths,
]
else:
raise NotImplementedError(f"pooling method {self.sentence_pooling_method} not implemented")
def compute_score(self, q_reps, p_reps):
"""Computes the scores between query and passage representations.
Args:
q_reps (torch.Tensor): Query representations.
p_reps (torch.Tensor): Passage representations.
Returns:
torch.Tensor: The computed scores, adjusted by temperature.
"""
scores = self._compute_similarity(q_reps, p_reps) / self.temperature
scores = scores.view(q_reps.size(0), -1)
return scores
def _compute_similarity(self, q_reps, p_reps):
"""Computes the similarity between query and passage representations using inner product.
Args:
q_reps (torch.Tensor): Query representations.
p_reps (torch.Tensor): Passage representations.
Returns:
torch.Tensor: The computed similarity matrix.
"""
if len(p_reps.size()) == 2:
return torch.matmul(q_reps, p_reps.transpose(0, 1))
return torch.matmul(q_reps, p_reps.transpose(-2, -1))
def compute_loss(self, scores, target):
"""Compute the loss using cross entropy.
Args:
scores (torch.Tensor): Computed score.
target (torch.Tensor): The target value.
Returns:
torch.Tensor: The computed cross entropy loss.
"""
return self.cross_entropy(scores, target)
def gradient_checkpointing_enable(self, **kwargs):
"""
Activates gradient checkpointing for the current model.
"""
self.model.gradient_checkpointing_enable(**kwargs)
def enable_input_require_grads(self, **kwargs):
"""
Enables the gradients for the input embeddings.
"""
self.model.enable_input_require_grads(**kwargs)
def save(self, output_dir: str):
"""Save the model to the directory.
Args:
output_dir (str): Directory for saving the model.
"""
state_dict = self.model.state_dict()
state_dict = type(state_dict)(
{k: v.clone().cpu()
for k,
v in state_dict.items()})
self.model.save_pretrained(output_dir, state_dict=state_dict)
@@ -0,0 +1,134 @@
import logging
from typing import Tuple
from pathlib import Path
from transformers import AutoConfig, AutoTokenizer, PreTrainedTokenizer
from FlagEmbedding.abc.finetune.embedder.AbsArguments import AbsEmbedderDataArguments, AbsEmbedderTrainingArguments
from FlagEmbedding.abc.finetune.embedder import AbsEmbedderRunner, AbsEmbedderModel, EmbedderTrainerCallbackForDataRefresh
from .arguments import DecoderOnlyEmbedderModelArguments
from .trainer import DecoderOnlyEmbedderTrainer
from .modeling import BiDecoderOnlyEmbedderModel
from .load_model import get_model, save_merged_model
logger = logging.getLogger(__name__)
class DecoderOnlyEmbedderRunner(AbsEmbedderRunner):
"""Runner class for decoder only embedding model.
Args:
model_args (DecoderOnlyEmbedderModelArguments): Model arguments instance.
data_args (AbsEmbedderDataArguments): Data arguments instance.
training_args (AbsEmbedderTrainingArguments): Trainer arguments.
"""
def __init__(
self,
model_args: DecoderOnlyEmbedderModelArguments,
data_args: AbsEmbedderDataArguments,
training_args: AbsEmbedderTrainingArguments
):
super().__init__(model_args, data_args, training_args)
self.model_args: DecoderOnlyEmbedderModelArguments
self.data_args: AbsEmbedderDataArguments
self.training_args: AbsEmbedderTrainingArguments
def load_tokenizer_and_model(self) -> Tuple[PreTrainedTokenizer, AbsEmbedderModel]:
"""Load tokenizer and model.
Returns:
Tuple[PreTrainedTokenizer, AbsEmbedderModel]: Tokenizer and model instances.
"""
tokenizer = AutoTokenizer.from_pretrained(
self.model_args.tokenizer_name if self.model_args.tokenizer_name else self.model_args.model_name_or_path,
token=self.model_args.token,
cache_dir=self.model_args.cache_dir,
use_fast=self.model_args.use_fast_tokenizer,
add_eos_token=True,
trust_remote_code=self.model_args.trust_remote_code,
)
if tokenizer.pad_token is None:
if tokenizer.unk_token is not None:
tokenizer.pad_token = tokenizer.unk_token
tokenizer.pad_token_id = tokenizer.unk_token_id
else:
tokenizer.pad_token = tokenizer.eos_token
tokenizer.pad_token_id = tokenizer.eos_token_id
tokenizer.padding_side = 'left'
resize = False
if self.model_args.additional_special_tokens is not None:
special_tokens_dict = {'additional_special_tokens': self.model_args.additional_special_tokens}
add_num = tokenizer.add_special_tokens(special_tokens_dict)
if add_num > 0:
resize = True
logger.info(f"Add {add_num} special tokens to the tokenizer. Special tokens: {self.model_args.additional_special_tokens}")
else:
logger.warning(f"Special tokens {self.model_args.additional_special_tokens} already exists in the tokenizer.")
base_model = get_model(self.model_args, self.training_args.output_dir, resize, len(tokenizer))
num_labels = 1
config = AutoConfig.from_pretrained(
self.model_args.config_name if self.model_args.config_name else self.model_args.model_name_or_path,
num_labels=num_labels,
cache_dir=self.model_args.cache_dir,
token=self.model_args.token,
trust_remote_code=self.model_args.trust_remote_code,
)
logger.info('Config: %s', config)
model = BiDecoderOnlyEmbedderModel(
base_model,
tokenizer=tokenizer,
negatives_cross_device=self.training_args.negatives_cross_device,
temperature=self.training_args.temperature,
sub_batch_size=self.training_args.sub_batch_size,
kd_loss_type=self.training_args.kd_loss_type,
use_mrl=self.training_args.use_mrl,
mrl_dims=self.training_args.mrl_dims,
sentence_pooling_method=self.training_args.sentence_pooling_method,
normalize_embeddings=self.training_args.normalize_embeddings
)
if self.training_args.gradient_checkpointing:
model.enable_input_require_grads()
if self.training_args.fix_position_embedding:
for k, v in model.named_parameters():
if "position_embeddings" in k:
logging.info(f"Freeze the parameters for {k}")
v.requires_grad = False
return tokenizer, model
def load_trainer(self) -> DecoderOnlyEmbedderTrainer:
"""Load the trainer.
Returns:
DecoderOnlyEmbedderTrainer: Loaded trainer instance.
"""
trainer = DecoderOnlyEmbedderTrainer(
model=self.model,
args=self.training_args,
train_dataset=self.train_dataset,
data_collator=self.data_collator,
processing_class=self.tokenizer
)
if self.data_args.same_dataset_within_batch:
trainer.add_callback(EmbedderTrainerCallbackForDataRefresh(self.train_dataset))
return trainer
def run(self):
"""
Run the finetune.
"""
if not self.model_args.only_merge_lora_model:
Path(self.training_args.output_dir).mkdir(parents=True, exist_ok=True)
# Training
self.trainer.train(resume_from_checkpoint=self.training_args.resume_from_checkpoint)
self.trainer.save_model()
# save merged model
if self.model_args.save_merged_lora_model and self.training_args.process_index == 0:
save_merged_model(self.model_args, self.training_args.output_dir)
@@ -0,0 +1,45 @@
import os
import torch
import logging
from typing import Optional
from FlagEmbedding.abc.finetune.embedder import AbsEmbedderTrainer
logger = logging.getLogger(__name__)
class DecoderOnlyEmbedderTrainer(AbsEmbedderTrainer):
"""
Trainer class for base encoder models.
"""
def _save(self, output_dir: Optional[str] = None, state_dict=None):
"""Save the model to directory.
Args:
output_dir (Optional[str], optional): Output directory to save the model. Defaults to ``None``.
Raises:
NotImplementedError
"""
output_dir = output_dir if output_dir is not None else self.args.output_dir
os.makedirs(output_dir, exist_ok=True)
logger.info("Saving model checkpoint to %s", output_dir)
# Save a trained model and configuration using `save_pretrained()`.
# They can then be reloaded using `from_pretrained()`
if not hasattr(self.model, 'save'):
raise NotImplementedError(
f'MODEL {self.model.__class__.__name__} '
f'does not support save interface')
else:
self.model.save(output_dir)
if self.tokenizer is not None and self.is_world_process_zero():
self.tokenizer.save_pretrained(output_dir)
torch.save(self.args, os.path.join(output_dir, "training_args.bin"))
# save the checkpoint for sentence-transformers library
# if self.is_world_process_zero():
# save_ckpt_for_sentence_transformers(output_dir,
# pooling_mode=self.args.sentence_pooling_method,
# normlized=self.args.normlized)
@@ -0,0 +1,24 @@
from FlagEmbedding.abc.finetune.embedder import (
AbsEmbedderTrainingArguments as DecoderOnlyEmbedderICLTrainingArguments,
)
from .arguments import (
DecoderOnlyEmbedderICLModelArguments,
DecoderOnlyEmbedderICLDataArguments
)
from .dataset import (
DecoderOnlyEmbedderICLSameDatasetTrainDataset,
AbsEmbedderSameDatasetCollator
)
from .modeling import BiDecoderOnlyEmbedderICLModel
from .trainer import DecoderOnlyEmbedderICLTrainer
from .runner import DecoderOnlyEmbedderICLRunner
__all__ = [
'DecoderOnlyEmbedderICLModelArguments',
'DecoderOnlyEmbedderICLDataArguments',
'DecoderOnlyEmbedderICLTrainingArguments',
'BiDecoderOnlyEmbedderICLModel',
'DecoderOnlyEmbedderICLTrainer',
'DecoderOnlyEmbedderICLRunner',
]
@@ -0,0 +1,31 @@
from transformers import HfArgumentParser
from FlagEmbedding.finetune.embedder.decoder_only.icl import (
DecoderOnlyEmbedderICLDataArguments,
DecoderOnlyEmbedderICLTrainingArguments,
DecoderOnlyEmbedderICLModelArguments,
DecoderOnlyEmbedderICLRunner,
)
def main():
parser = HfArgumentParser((
DecoderOnlyEmbedderICLModelArguments,
DecoderOnlyEmbedderICLDataArguments,
DecoderOnlyEmbedderICLTrainingArguments
))
model_args, data_args, training_args = parser.parse_args_into_dataclasses()
model_args: DecoderOnlyEmbedderICLModelArguments
data_args: DecoderOnlyEmbedderICLDataArguments
training_args: DecoderOnlyEmbedderICLTrainingArguments
runner = DecoderOnlyEmbedderICLRunner(
model_args=model_args,
data_args=data_args,
training_args=training_args
)
runner.run()
if __name__ == "__main__":
main()
@@ -0,0 +1,102 @@
from typing import Optional, List
from dataclasses import dataclass, field
from FlagEmbedding.abc.finetune.embedder import (
AbsEmbedderModelArguments,
AbsEmbedderDataArguments,
)
def default_target_modules() -> List[int]:
return ['v_proj', 'q_proj', 'k_proj', 'gate_proj', 'down_proj', 'o_proj', 'up_proj']
@dataclass
class DecoderOnlyEmbedderICLModelArguments(AbsEmbedderModelArguments):
"""
Model argument class for decoder only icl model.
"""
peft_model_path: str = field(
default='', metadata={"help": "The peft model checkpoint for initialization."}
)
use_lora: bool = field(
default=True,
metadata={"help": "If passed, will use LORA (low-rank parameter-efficient training) to train the model."}
)
lora_rank: int = field(
default=64,
metadata={"help": "The rank of lora."}
)
lora_alpha: float = field(
default=16,
metadata={"help": "The alpha parameter of lora."}
)
lora_dropout: float = field(
default=0.1,
metadata={"help": "The dropout rate of lora modules."}
)
target_modules: List[str] = field(
default_factory=default_target_modules,
metadata={"help": "The target modules to apply LORA."}
)
use_flash_attn: bool = field(
default=False,
metadata={"help": "If passed, will use flash attention to train the model."}
)
use_slow_tokenizer: bool = field(
default=False,
metadata={"help": "If passed, will use a slow tokenizer (not backed by the 🤗 Tokenizers library)."}
)
# low_cpu_mem_usage: bool = field(
# default=False,
# metadata={"help": "It is an option to create the model as an empty shell,"
# "then only materialize its parameters when the pretrained weights are loaded."
# "If passed, LLM loading time and RAM consumption will be benefited."}
# )
from_peft: str = field(
default=None
)
modules_to_save: List[str] = field(
default=None,
)
raw_peft: str = field(
default=None
)
additional_special_tokens: Optional[str] = field(
default=None,
metadata={"help": "additional special tokens", "nargs": "+"}
)
save_merged_lora_model: bool = field(
default=False,
metadata={"help": "If passed, will merge the lora modules and save the entire model."}
)
only_merge_lora_model: bool = field(
default=False,
metadata={"help": "If passed, will only merge the lora modules and save the entire model."}
)
@dataclass
class DecoderOnlyEmbedderICLDataArguments(AbsEmbedderDataArguments):
"""
Data argument class for decoder only icl model.
"""
example_query_max_len: int = field(
default=64,
metadata={"help": "The max length of example query."}
)
example_passage_max_len: int = field(
default=96,
metadata={"help": "The max length of example passage."}
)
retrieval_use_examples: bool = field(
default=True,
metadata={"help": "If passed, will use examples for retrieval."}
)
icl_suffix_str: str = field(
default='\nResponse:',
metadata={"help": "The suffix string for ICL dataset."}
)
@@ -0,0 +1,291 @@
import math
import random
import logging
from dataclasses import dataclass
from transformers import (
PreTrainedTokenizer,
DataCollatorWithPadding,
)
from FlagEmbedding.abc.finetune.embedder import AbsEmbedderSameDatasetTrainDataset
from .arguments import DecoderOnlyEmbedderICLDataArguments
logger = logging.getLogger(__name__)
class DecoderOnlyEmbedderICLSameDatasetTrainDataset(AbsEmbedderSameDatasetTrainDataset):
"""Dataset class for icl model.
Args:
args (DecoderOnlyEmbedderICLDataArguments): Data argument class for icl model.
default_batch_size (int): The default batch size.
seed (int): Random seed to use.
tokenizer (PreTrainedTokenizer): Tokenzier.
process_index (int, optional): Current process index. Defaults to 0.
num_processes (int, optional): Total number of processes. Defaults to 1.
"""
def __init__(
self,
args: DecoderOnlyEmbedderICLDataArguments,
default_batch_size: int,
seed: int,
tokenizer: PreTrainedTokenizer,
process_index: int=0,
num_processes: int=1
):
super().__init__(
args=args,
default_batch_size=default_batch_size,
seed=seed,
tokenizer=tokenizer,
process_index=process_index,
num_processes=num_processes
)
self.args: DecoderOnlyEmbedderICLDataArguments
self.suffix = self.tokenizer(f"{self.args.icl_suffix_str}{self.tokenizer.eos_token}", add_special_tokens=False)['input_ids']
self.prefix = self.tokenizer(f"{self.tokenizer.bos_token}", add_special_tokens=False)['input_ids']
def _create_batch_data(self, batch_raw_data):
"""Create a comple batch of data with queries, documents and teacher scores.
Args:
batch_raw_data (datasets.Dataset): One batch of raw data.
Returns:
List[str]: Queries with instruction format.
List[str]: Documents with instruction format.
List[float]: Teacher scores for model distillation.
"""
queries, passages, teacher_scores = [], [], []
train_group_size, data_type = self._get_train_group_size(batch_raw_data)
icl_pairs = []
for i in range(len(batch_raw_data['query'])):
if data_type is not None:
assert batch_raw_data['type'][i] == data_type, f"Data type is not consistent in the same batch"
queries.append(
self.args.query_instruction_format.format(
batch_raw_data['prompt'][i] if 'prompt' in batch_raw_data else self.args.query_instruction_for_retrieval,
batch_raw_data['query'][i]
)
)
tmp_passages = []
pos_idx = random.choice(list(range(len(batch_raw_data['pos'][i]))))
pos = self._shuffle_text(batch_raw_data['pos'][i][pos_idx])
tmp_passages.append(pos)
neg_all_idx = list(range(len(batch_raw_data['neg'][i])))
if len(batch_raw_data['neg'][i]) < train_group_size - 1:
num = math.ceil((train_group_size - 1) / len(batch_raw_data['neg'][i]))
neg_idxs = random.sample(neg_all_idx * num, train_group_size - 1)
else:
neg_idxs = random.sample(neg_all_idx, train_group_size - 1)
for neg_idx in neg_idxs:
tmp_passages.append(batch_raw_data['neg'][i][neg_idx])
if self.args.knowledge_distillation:
if 'pos_scores' in batch_raw_data and batch_raw_data['pos_scores'][i] is not None:
teacher_scores.append(batch_raw_data['pos_scores'][i][pos_idx])
for neg_idx in neg_idxs:
if 'neg_scores' in batch_raw_data and batch_raw_data['neg_scores'][i] is not None:
teacher_scores.append(batch_raw_data['neg_scores'][i][neg_idx])
else:
teacher_scores = None
if data_type is not None and data_type in ['symmetric_sts', 'symmetric_clustering']:
tmp_passages = [
self.args.query_instruction_format.format(
batch_raw_data['prompt'][i] if 'prompt' in batch_raw_data else self.args.query_instruction_for_retrieval,
p
) for p in tmp_passages
]
tmp_passages = self.tokenizer.batch_decode(
self.tokenizer(
tmp_passages,
max_length=self.args.passage_max_len - 1 - len(self.suffix),
truncation=True,
add_special_tokens=False,
)['input_ids']
)
for j in range(len(tmp_passages)):
tmp_passages[j] += self.args.icl_suffix_str
else:
if self.args.passage_instruction_for_retrieval is not None:
tmp_passages = [
self.args.passage_instruction_format.format(
self.args.passage_instruction_for_retrieval, p
) for p in tmp_passages
]
passages.extend(tmp_passages)
if teacher_scores is not None:
if len(teacher_scores) > 0 and len(passages) > 0:
assert len(teacher_scores) == len(passages)
# add icl pairs
if self.args.retrieval_use_examples or (
data_type in ['symmetric_sts', 'symmetric_clustering', 'symmetric_class']
):
if data_type == 'symmetric_clustering':
icl_pairs.append((
self.tokenizer.decode(
self.tokenizer(
queries[-1],
add_special_tokens=False
)['input_ids'][:self.args.example_query_max_len]
),
self.tokenizer.decode(
self.tokenizer(
batch_raw_data['category'][i], # use category as example
add_special_tokens=False
)['input_ids'][:self.args.example_passage_max_len]
)
))
else:
icl_pairs.append((
self.tokenizer.decode(
self.tokenizer(
queries[-1],
add_special_tokens=False
)['input_ids'][:self.args.example_query_max_len]
),
self.tokenizer.decode(
self.tokenizer(
pos,
add_special_tokens=False
)['input_ids'][:self.args.example_passage_max_len]
)
))
else:
icl_pairs = []
# handle queries
for i in range(len(queries)):
choices = random.choice([0, 1, 2, 3, 4, 5])
if choices > 0 and len(icl_pairs) > 0:
prefix_ids = random.sample(list(range(len(icl_pairs))), min(choices + 1, len(icl_pairs)))
if i in prefix_ids:
prefix_ids.remove(i)
prefix_ids = prefix_ids[:choices]
prefix = ''
for idx in prefix_ids:
tmp = prefix + self.args.icl_suffix_str.join(icl_pairs[idx]) + '\n\n'
if len(self.tokenizer(tmp)['input_ids']) > self.args.query_max_len - 512:
break
prefix = tmp
else:
prefix = ''
queries[i] = prefix + queries[i]
queries[i] = self.tokenizer.decode(
self.tokenizer(
queries[i],
max_length=self.args.query_max_len - len(self.prefix) - len(self.suffix),
truncation=True,
add_special_tokens=False
)['input_ids']
) + self.args.icl_suffix_str
return queries, passages, teacher_scores
@dataclass
class AbsEmbedderSameDatasetCollator(DataCollatorWithPadding):
"""
EmbedCollator for SameDataset.
Note that after using this collator, the training_args should be set as:
``training_args.per_device_train_batch_size = 1``
``training_args.dataloader_num_workers = 0 # avoid multi-processing``
"""
query_max_len: int = 32
passage_max_len: int = 128
sub_batch_size: int = -1
def __call__(self, features):
queries = features[0][0]
passages = features[0][1]
teacher_scores = features[0][2]
no_in_batch_neg_flag = features[0][3]
queries_inputs = self.tokenizer(
queries,
truncation=True,
max_length=self.query_max_len,
return_tensors=None
)
passages_inputs = self.tokenizer(
passages,
truncation=True,
max_length=self.passage_max_len,
return_tensors=None
)
if self.sub_batch_size is None or self.sub_batch_size <= 0:
q_collated = self.tokenizer.pad(
queries_inputs,
padding=self.padding,
max_length=self.query_max_len,
pad_to_multiple_of=self.pad_to_multiple_of,
return_tensors=self.return_tensors,
)
d_collated = self.tokenizer.pad(
passages_inputs,
padding=self.padding,
max_length=self.passage_max_len,
pad_to_multiple_of=self.pad_to_multiple_of,
return_tensors=self.return_tensors,
)
else:
batch_size = self.sub_batch_size
q_collated = []
for i in range(0, len(queries_inputs['attention_mask']), batch_size):
start = i
end = min(len(queries_inputs['attention_mask']), i + batch_size)
sub_features = {}
for k, v in queries_inputs.items():
sub_features[k] = v[start:end]
q_collated.append(self.tokenizer.pad(
sub_features,
padding=self.padding,
max_length=self.passage_max_len,
pad_to_multiple_of=self.pad_to_multiple_of,
return_tensors=self.return_tensors,
))
d_collated = []
for i in range(0, len(passages_inputs['attention_mask']), batch_size):
start = i
end = min(len(passages_inputs['attention_mask']), i + batch_size)
sub_features = {}
for k, v in passages_inputs.items():
sub_features[k] = v[start:end]
d_collated.append(self.tokenizer.pad(
sub_features,
padding=self.padding,
max_length=self.passage_max_len,
pad_to_multiple_of=self.pad_to_multiple_of,
return_tensors=self.return_tensors,
))
if isinstance(teacher_scores, list) and len(teacher_scores) == 0:
teacher_scores = None
return {
"queries": q_collated,
"passages": d_collated,
"teacher_scores": teacher_scores,
"no_in_batch_neg_flag": no_in_batch_neg_flag
}
@@ -0,0 +1,183 @@
import os
import re
import torch
import logging
from transformers import AutoConfig, AutoModel, AutoTokenizer
from peft import LoraConfig, TaskType, get_peft_model, PeftModel
from .arguments import DecoderOnlyEmbedderICLModelArguments
logger = logging.getLogger(__name__)
def find_largest_checkpoint(checkpoint_dir):
"""Find the largest checkpoint from directory.
Args:
checkpoint_dir (str): Directory to the checkpoint.
Returns:
str: Directory to the checkpoint, None no matching found.
"""
checkpoint_pattern = re.compile(r'checkpoint-(\d+)')
max_number = -1
max_checkpoint_file = None
for file in os.listdir(checkpoint_dir):
match = checkpoint_pattern.search(file)
if match:
number = int(match.group(1))
if number > max_number:
max_number = number
max_checkpoint_file = file
if max_checkpoint_file:
return os.path.join(checkpoint_dir, max_checkpoint_file)
else:
return None
def get_model(model_args: DecoderOnlyEmbedderICLModelArguments, output_dir: str, resize: bool, resize_tokens: int):
"""Get the model.
Args:
model_args (DecoderOnlyEmbedderModelArguments): Model arguments instance.
output_dir (str): Directory to save the model.
resize (bool): Whether to resize the number of tokens.
resize_tokens (int): The new token size.
Returns:
transformers.PreTrainedModel or PeftModel: The loaded model.
"""
if model_args.config_name:
config = AutoConfig.from_pretrained(
model_args.config_name,
token=model_args.token,
cache_dir=model_args.cache_dir,
trust_remote_code=model_args.trust_remote_code,
)
elif model_args.model_name_or_path:
config = AutoConfig.from_pretrained(
model_args.model_name_or_path,
token=model_args.token,
cache_dir=model_args.cache_dir,
trust_remote_code=model_args.trust_remote_code,
)
else:
raise ValueError(
"You are instantiating a new config instance from scratch. This is not supported by this script."
)
config.use_cache = False
if model_args.model_name_or_path:
model = AutoModel.from_pretrained(
model_args.model_name_or_path,
# torch_dtype=torch.bfloat16,
attn_implementation = "flash_attention_2" if model_args.use_flash_attn else None,
token=model_args.token,
cache_dir=model_args.cache_dir,
from_tf=bool(".ckpt" in model_args.model_name_or_path),
config=config,
trust_remote_code=model_args.trust_remote_code,
)
else:
logger.info("Training new model from scratch")
model = model_args.from_config(config)
if model_args.raw_peft is not None:
model.set_input_embeddings(torch.load(os.path.join(model_args.raw_peft, 'embedding', 'emb.pth')))
model = PeftModel.from_pretrained(model, model_args.raw_peft)
model = model.merge_and_unload()
if resize:
model.resize_token_embeddings(resize_tokens)
os.makedirs(os.path.join(output_dir, 'embedding'), exist_ok=True)
torch.save(model.embed_tokens, os.path.join(output_dir, 'embedding', 'emb.pth'))
target_modules = model_args.target_modules
else:
target_modules = model_args.target_modules
if 'embed_tokens' in target_modules:
target_modules.remove('embed_tokens')
if model_args.from_peft is not None:
if os.path.exists(os.path.join(model_args.from_peft, 'embedding')):
model.set_input_embeddings(torch.load(os.path.join(model_args.from_peft, 'embedding', 'emb.pth')))
torch.save(model.embed_tokens, os.path.join(output_dir, 'embedding', 'emb.pth'))
model = PeftModel.from_pretrained(model, model_args.from_peft, is_trainable=True)
model.print_trainable_parameters()
else:
if model_args.use_lora:
peft_config = LoraConfig(
task_type=TaskType.FEATURE_EXTRACTION,
inference_mode=False,
r=model_args.lora_rank,
target_modules=target_modules,
modules_to_save=model_args.modules_to_save,
lora_alpha=model_args.lora_alpha,
lora_dropout=model_args.lora_dropout
)
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
return model
def save_merged_model(model_args: DecoderOnlyEmbedderICLModelArguments, output_dir: str):
"""
Loads a model with specified configurations, merges it with PEFT layers if available.
Args:
model_args (DecoderOnlyEmbedderModelArguments): Model arguments instance.
output_dir (str): Directory to save the model.
"""
if model_args.config_name:
config = AutoConfig.from_pretrained(
model_args.config_name,
token=model_args.token,
cache_dir=model_args.cache_dir
)
elif model_args.model_name_or_path:
config = AutoConfig.from_pretrained(
model_args.model_name_or_path,
token=model_args.token,
cache_dir=model_args.cache_dir
)
else:
raise ValueError(
"You are instantiating a new config instance from scratch. This is not supported by this script."
)
config.use_cache = False
if model_args.model_name_or_path:
model = AutoModel.from_pretrained(
model_args.model_name_or_path,
# torch_dtype=torch.bfloat16,
attn_implementation = "flash_attention_2" if model_args.use_flash_attn else None,
token=model_args.token,
cache_dir=model_args.cache_dir,
from_tf=bool(".ckpt" in model_args.model_name_or_path),
config=config,
trust_remote_code=model_args.trust_remote_code,
)
else:
model = model_args.from_config(config)
if model_args.raw_peft is not None:
model.set_input_embeddings(torch.load(os.path.join(model_args.raw_peft, 'embedding', 'emb.pth')))
model = PeftModel.from_pretrained(model, model_args.raw_peft)
model = model.merge_and_unload()
if os.path.exists(os.path.join(output_dir, 'embedding', 'emb.pth')):
model.set_input_embeddings(torch.load(os.path.join(output_dir, 'embedding', 'emb.pth')))
try:
model = PeftModel.from_pretrained(model, output_dir)
model = model.merge_and_unload()
except:
model = PeftModel.from_pretrained(model, find_largest_checkpoint(output_dir))
model = model.merge_and_unload()
tokenizer = AutoTokenizer.from_pretrained(output_dir, trust_remote_code=model_args.trust_remote_code)
tokenizer.save_pretrained(os.path.join(output_dir, 'merged_model'))
# modify the vocab size in the model configuration
model.config.vocab_size = len(tokenizer)
model.save_pretrained(os.path.join(output_dir, 'merged_model'))
@@ -0,0 +1,207 @@
import logging
from typing import List
import torch
from transformers import AutoModel, PreTrainedModel, PreTrainedTokenizer
from FlagEmbedding.abc.finetune.embedder import AbsEmbedderModel
logger = logging.getLogger(__name__)
class BiDecoderOnlyEmbedderICLModel(AbsEmbedderModel):
"""Embedder model class for decoder only model.
Args:
base_model (PreTrainedModel): The base model to train on.
tokenizer (PreTrainedTokenizer, optional): The tokenizer to use. Defaults to ``None``.
negatives_cross_device (bool, optional): If True, will compute cross devices negative loss. Defaults to ``False``.
temperature (float, optional): Temperature to control the scale of scores. Defaults to ``1.0``.
sub_batch_size (int, optional): Sub-batch size during encoding. If negative, will not split to sub-batch.
Defaults to ``-1``.
kd_loss_type (str, optional): Type of knowledge distillation loss. Defaults to ``'kl_div'``.
use_mrl (bool, optional): Whether to use MRL for training. Defaults to ``False``.
mrl_dims (List[int], optional): The dimensions of MRL layers. Defaults to ``[]``.
sentence_pooling_method (str, optional): Pooling method to get sentence embedding. Defaults to ``'last_token'``.
normalize_embeddings (bool, optional): If True, normalize the embedding vector. Defaults to ``False``.
"""
TRANSFORMER_CLS = AutoModel
def __init__(
self,
base_model: PreTrainedModel,
tokenizer: PreTrainedTokenizer = None,
negatives_cross_device: bool = False,
temperature: float = 1.0,
sub_batch_size: int = -1,
kd_loss_type: str = 'kl_div',
use_mrl: bool = False,
mrl_dims: List[int] = [],
sentence_pooling_method: str = 'last_token',
normalize_embeddings: bool = False,
):
super().__init__(
base_model,
tokenizer=tokenizer,
negatives_cross_device=negatives_cross_device,
temperature=temperature,
sub_batch_size=sub_batch_size,
kd_loss_type=kd_loss_type,
use_mrl=use_mrl,
mrl_dims=mrl_dims,
)
self.sentence_pooling_method = sentence_pooling_method
self.normalize_embeddings = normalize_embeddings
self.cross_entropy = torch.nn.CrossEntropyLoss(reduction='mean')
def encode(self, features):
"""
Encode and get the embedding.
Args:
features (Union[list, dict]): Features feed to the model.
Returns:
Union[torch.Tensor, List[torch.Tensor]]: The embedding vectors or a list of embedding vectors if MRL is used.
"""
if features is None:
return None
if not isinstance(features, list):
if self.sub_batch_size is not None and self.sub_batch_size > 0:
all_p_reps = []
for i in range(0, len(features['attention_mask']), self.sub_batch_size):
end_inx = min(i + self.sub_batch_size, len(features['attention_mask']))
sub_features = {}
for k, v in features.items():
sub_features[k] = v[i:end_inx]
last_hidden_state = self.model(**sub_features, return_dict=True).last_hidden_state
p_reps = self._sentence_embedding(last_hidden_state, sub_features['attention_mask'])
all_p_reps.append(p_reps)
all_p_reps = torch.cat(all_p_reps, 0).contiguous()
else:
last_hidden_state = self.model(**features, return_dict=True).last_hidden_state
all_p_reps = self._sentence_embedding(last_hidden_state, features['attention_mask'])
else:
all_p_reps = []
for sub_features in features:
last_hidden_state = self.model(**sub_features, return_dict=True).last_hidden_state
p_reps = self._sentence_embedding(last_hidden_state, sub_features['attention_mask'])
all_p_reps.append(p_reps)
all_p_reps = torch.cat(all_p_reps, 0).contiguous()
if self.use_mrl:
p_reps_list = []
ori_dim = all_p_reps.size(-1)
for dim in self.mrl_dims:
if dim > ori_dim:
logger.warning(f"MRL dim {dim} is larger than original dimension {ori_dim}, using original dimension instead.")
dim = min(dim, ori_dim)
dim_p_reps = all_p_reps[:, :dim]
if self.normalize_embeddings:
dim_p_reps = torch.nn.functional.normalize(dim_p_reps, dim=-1)
p_reps_list.append(dim_p_reps.contiguous())
return p_reps_list
else:
if self.normalize_embeddings:
all_p_reps = torch.nn.functional.normalize(all_p_reps, dim=-1)
return all_p_reps.contiguous()
def _sentence_embedding(self, last_hidden_state, attention_mask):
"""Use the pooling method to get the sentence embedding.
Args:
last_hidden_state (torch.Tensor): The model output's last hidden state.
attention_mask (torch.Tensor): Mask out padding tokens during pooling.
Raises:
NotImplementedError: Specified pooling method not implemented.
Returns:
torch.Tensor: The sentence embeddings.
"""
if self.sentence_pooling_method == "cls":
return last_hidden_state[:, 0]
elif self.sentence_pooling_method == "mean":
s = torch.sum(
last_hidden_state * attention_mask.unsqueeze(-1).float(), dim=1
)
d = attention_mask.sum(dim=1, keepdim=True).float()
return s / d
elif self.sentence_pooling_method == "last_token":
left_padding = attention_mask[:, -1].sum() == attention_mask.shape[0]
if left_padding:
return last_hidden_state[:, -1]
else:
sequence_lengths = attention_mask.sum(dim=1) - 1
batch_size = last_hidden_state.shape[0]
return last_hidden_state[
torch.arange(batch_size, device=last_hidden_state.device),
sequence_lengths,
]
else:
raise NotImplementedError(f"pooling method {self.sentence_pooling_method} not implemented")
def compute_score(self, q_reps, p_reps):
"""Computes the scores between query and passage representations.
Args:
q_reps (torch.Tensor): Query representations.
p_reps (torch.Tensor): Passage representations.
Returns:
torch.Tensor: The computed scores, adjusted by temperature.
"""
scores = self._compute_similarity(q_reps, p_reps) / self.temperature
scores = scores.view(q_reps.size(0), -1)
return scores
def _compute_similarity(self, q_reps, p_reps):
"""Computes the similarity between query and passage representations using inner product.
Args:
q_reps (torch.Tensor): Query representations.
p_reps (torch.Tensor): Passage representations.
Returns:
torch.Tensor: The computed similarity matrix.
"""
if len(p_reps.size()) == 2:
return torch.matmul(q_reps, p_reps.transpose(0, 1))
return torch.matmul(q_reps, p_reps.transpose(-2, -1))
def compute_loss(self, scores, target):
"""Compute the loss using cross entropy.
Args:
scores (torch.Tensor): Computed score.
target (torch.Tensor): The target value.
Returns:
torch.Tensor: The computed cross entropy loss.
"""
return self.cross_entropy(scores, target)
def gradient_checkpointing_enable(self, **kwargs):
"""
Activates gradient checkpointing for the current model.
"""
self.model.gradient_checkpointing_enable(**kwargs)
def enable_input_require_grads(self, **kwargs):
"""
Enables the gradients for the input embeddings.
"""
self.model.enable_input_require_grads(**kwargs)
def save(self, output_dir: str):
"""Save the model to the directory.
Args:
output_dir (str): Directory for saving the model.
"""
state_dict = self.model.state_dict()
state_dict = type(state_dict)(
{k: v.clone().cpu()
for k,
v in state_dict.items()})
self.model.save_pretrained(output_dir, state_dict=state_dict)
@@ -0,0 +1,159 @@
import logging
from typing import Tuple
from pathlib import Path
from transformers import AutoConfig, AutoTokenizer, PreTrainedTokenizer
from FlagEmbedding.abc.finetune.embedder.AbsArguments import AbsEmbedderTrainingArguments
from FlagEmbedding.abc.finetune.embedder import AbsEmbedderRunner, AbsEmbedderModel, EmbedderTrainerCallbackForDataRefresh
from .arguments import DecoderOnlyEmbedderICLModelArguments, DecoderOnlyEmbedderICLDataArguments
from .trainer import DecoderOnlyEmbedderICLTrainer
from .modeling import BiDecoderOnlyEmbedderICLModel
from .dataset import DecoderOnlyEmbedderICLSameDatasetTrainDataset
from .load_model import get_model, save_merged_model
logger = logging.getLogger(__name__)
class DecoderOnlyEmbedderICLRunner(AbsEmbedderRunner):
"""Runner class for decoder only icl model.
Args:
model_args (DecoderOnlyEmbedderICLModelArguments): Model arguments instance.
data_args (DecoderOnlyEmbedderICLDataArguments): Data arguments instance.
training_args (AbsEmbedderTrainingArguments): Trainer arguments.
"""
def __init__(
self,
model_args: DecoderOnlyEmbedderICLModelArguments,
data_args: DecoderOnlyEmbedderICLDataArguments,
training_args: AbsEmbedderTrainingArguments
):
super().__init__(model_args, data_args, training_args)
self.model_args: DecoderOnlyEmbedderICLModelArguments
self.data_args: DecoderOnlyEmbedderICLDataArguments
self.training_args: AbsEmbedderTrainingArguments
def load_tokenizer_and_model(self) -> Tuple[PreTrainedTokenizer, AbsEmbedderModel]:
"""Load tokenizer and model.
Returns:
Tuple[PreTrainedTokenizer, AbsEmbedderModel]: Tokenizer and model instances.
"""
tokenizer = AutoTokenizer.from_pretrained(
self.model_args.tokenizer_name if self.model_args.tokenizer_name else self.model_args.model_name_or_path,
token=self.model_args.token,
cache_dir=self.model_args.cache_dir,
use_fast=self.model_args.use_fast_tokenizer,
add_eos_token=True,
trust_remote_code=self.model_args.trust_remote_code,
)
if tokenizer.pad_token is None:
if tokenizer.unk_token is not None:
tokenizer.pad_token = tokenizer.unk_token
tokenizer.pad_token_id = tokenizer.unk_token_id
else:
tokenizer.pad_token = tokenizer.eos_token
tokenizer.pad_token_id = tokenizer.eos_token_id
tokenizer.padding_side = 'left'
resize = False
if self.model_args.additional_special_tokens is not None:
special_tokens_dict = {'additional_special_tokens': self.model_args.additional_special_tokens}
add_num = tokenizer.add_special_tokens(special_tokens_dict)
if add_num > 0:
resize = True
logger.info(f"Add {add_num} special tokens to the tokenizer. Special tokens: {self.model_args.additional_special_tokens}")
else:
logger.warning(f"Special tokens {self.model_args.additional_special_tokens} already exists in the tokenizer.")
base_model = get_model(self.model_args, self.training_args.output_dir, resize, len(tokenizer))
num_labels = 1
config = AutoConfig.from_pretrained(
self.model_args.config_name if self.model_args.config_name else self.model_args.model_name_or_path,
num_labels=num_labels,
cache_dir=self.model_args.cache_dir,
token=self.model_args.token,
trust_remote_code=self.model_args.trust_remote_code,
)
logger.info('Config: %s', config)
model = BiDecoderOnlyEmbedderICLModel(
base_model,
tokenizer=tokenizer,
negatives_cross_device=self.training_args.negatives_cross_device,
temperature=self.training_args.temperature,
sub_batch_size=self.training_args.sub_batch_size,
kd_loss_type=self.training_args.kd_loss_type,
use_mrl=self.training_args.use_mrl,
mrl_dims=self.training_args.mrl_dims,
sentence_pooling_method=self.training_args.sentence_pooling_method,
normalize_embeddings=self.training_args.normalize_embeddings
)
if self.training_args.gradient_checkpointing:
model.enable_input_require_grads()
if self.training_args.fix_position_embedding:
for k, v in model.named_parameters():
if "position_embeddings" in k:
logging.info(f"Freeze the parameters for {k}")
v.requires_grad = False
return tokenizer, model
def load_trainer(self) -> DecoderOnlyEmbedderICLTrainer:
"""Load the trainer.
Returns:
DecoderOnlyEmbedderICLTrainer: Loaded trainer instance.
"""
trainer = DecoderOnlyEmbedderICLTrainer(
model=self.model,
args=self.training_args,
train_dataset=self.train_dataset,
data_collator=self.data_collator,
processing_class=self.tokenizer
)
if self.data_args.same_dataset_within_batch:
trainer.add_callback(EmbedderTrainerCallbackForDataRefresh(self.train_dataset))
return trainer
def load_train_dataset(self) -> DecoderOnlyEmbedderICLSameDatasetTrainDataset:
"""Load the dataset instance for training.
Raises:
NotImplementedError: Only support `same_dataset_within_batch` for `DecoderOnlyEmbedderICLRunner`.
Returns:
DecoderOnlyEmbedderICLSameDatasetTrainDataset: The dataset instance.
"""
if self.data_args.same_dataset_within_batch:
train_dataset = DecoderOnlyEmbedderICLSameDatasetTrainDataset(
args=self.data_args,
default_batch_size=self.training_args.per_device_train_batch_size,
seed=self.training_args.seed,
tokenizer=self.tokenizer,
process_index=self.training_args.process_index,
num_processes=self.training_args.world_size
)
self.training_args.per_device_train_batch_size = 1
self.training_args.dataloader_num_workers = 0 # avoid multi-processing
else:
raise NotImplementedError("Only support `same_dataset_within_batch` for `DecoderOnlyEmbedderICLRunner`.")
return train_dataset
def run(self):
"""
Run the finetune.
"""
if not self.model_args.only_merge_lora_model:
Path(self.training_args.output_dir).mkdir(parents=True, exist_ok=True)
# Training
self.trainer.train(resume_from_checkpoint=self.training_args.resume_from_checkpoint)
self.trainer.save_model()
# save merged model
if self.model_args.save_merged_lora_model and self.training_args.process_index == 0:
save_merged_model(self.model_args, self.training_args.output_dir)
@@ -0,0 +1,45 @@
import os
import torch
import logging
from typing import Optional
from FlagEmbedding.abc.finetune.embedder import AbsEmbedderTrainer
logger = logging.getLogger(__name__)
class DecoderOnlyEmbedderICLTrainer(AbsEmbedderTrainer):
"""
Trainer class for base encoder models.
"""
def _save(self, output_dir: Optional[str] = None, state_dict=None):
"""Save the model to directory.
Args:
output_dir (Optional[str], optional): Output directory to save the model. Defaults to ``None``.
Raises:
NotImplementedError
"""
output_dir = output_dir if output_dir is not None else self.args.output_dir
os.makedirs(output_dir, exist_ok=True)
logger.info("Saving model checkpoint to %s", output_dir)
# Save a trained model and configuration using `save_pretrained()`.
# They can then be reloaded using `from_pretrained()`
if not hasattr(self.model, 'save'):
raise NotImplementedError(
f'MODEL {self.model.__class__.__name__} '
f'does not support save interface')
else:
self.model.save(output_dir)
if self.tokenizer is not None and self.is_world_process_zero():
self.tokenizer.save_pretrained(output_dir)
torch.save(self.args, os.path.join(output_dir, "training_args.bin"))
# save the checkpoint for sentence-transformers library
# if self.is_world_process_zero():
# save_ckpt_for_sentence_transformers(output_dir,
# pooling_mode=self.args.sentence_pooling_method,
# normlized=self.args.normlized)