Files
wehub-resource-sync 2aaeece67c
Codestyle Check / Lint (push) Has been cancelled
Codestyle Check / Check bypass (push) Has been cancelled
Pipelines-Test / Pipelines-Test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:37:14 +08:00

534 lines
22 KiB
Python

# Copyright (c) 2024 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from dataclasses import dataclass
from typing import Dict, List, Optional, Tuple, Union
import numpy as np
import paddle
import paddle.distributed as dist
import paddle.nn as nn
import tqdm
from paddle.distributed.fleet.utils import recompute
from ...utils.log import logger
from .. import AutoTokenizer, MistralModel, PretrainedConfig, PretrainedModel
from ..model_outputs import BaseModelOutputWithPast, ModelOutput
__all__ = ["NVEncodeModel"]
@dataclass
class EncoderOutput(ModelOutput):
q_reps: Optional[paddle.Tensor] = None
p_reps: Optional[paddle.Tensor] = None
loss: Optional[paddle.Tensor] = None
scores: Optional[paddle.Tensor] = None
def scaled_dot_product_attention(q, k, v): # [bs, len, num_heads, dim]
matmul_qk = paddle.matmul(q.transpose([0, 2, 1, 3]), k.transpose([0, 2, 3, 1]))
dk = paddle.to_tensor(k.shape[-1], dtype=paddle.float32)
scaled_attention_logits = matmul_qk / paddle.sqrt(dk)
attention_weights = paddle.nn.functional.softmax(scaled_attention_logits, axis=-1) # [bs, num_heads, q_len, k_len]
output = paddle.matmul(attention_weights, v.transpose([0, 2, 1, 3])) # [bs, num_heads, q_len, dim]
output = output.transpose([0, 2, 1, 3]) # [bs, q_len, num_heads, dim]
return output
def _make_bidirection_mask(
input_ids_shape: paddle.shape,
dtype: paddle.dtype,
past_key_values_length: int = 0,
):
"""
Make bidirection mask used for sliding window attention
"""
bsz, tgt_len = input_ids_shape
tensor = paddle.full(
(tgt_len, tgt_len),
fill_value=1,
)
mask = paddle.tril(tensor, diagonal=0)
mask = paddle.ones_like(mask) # here is for bidirection attention
mask = paddle.log(mask).astype(dtype)
if past_key_values_length > 0:
mask = paddle.concat([paddle.zeros([tgt_len, past_key_values_length], dtype=dtype), mask], axis=-1)
return mask[None, None, :, :].expand([bsz, 1, tgt_len, tgt_len + past_key_values_length])
def _expand_mask(mask: paddle.Tensor, dtype: paddle.dtype, tgt_len):
expanded_mask = mask
if len(mask.shape) == 2:
"""
Expands attention_mask from `[bsz, seq_len]` to `[bsz, 1, tgt_seq_len, src_seq_len]`.
"""
bsz, src_len = mask.shape
tgt_len = tgt_len if tgt_len is not None else src_len
expanded_mask = mask[:, None, None, :].expand([bsz, 1, tgt_len, src_len]).astype(dtype)
elif len(mask.shape) == 3:
"""
Expands attention_mask from `[bsz, tgt_seq_len, seq_len]` to `[bsz, 1, tgt_seq_len, src_seq_len]`.
"""
expanded_mask = mask.unsqueeze(1).astype(dtype)
inverted_mask = 1.0 - expanded_mask
return paddle.where(inverted_mask > 0.5, paddle.full_like(inverted_mask, paddle.finfo(dtype).min), inverted_mask)
class LatentModel(PretrainedModel):
config_class = PretrainedConfig
def __init__(self, config):
super().__init__(config)
self.cross_attend_blocks_0_fn_to_kv = paddle.nn.Linear(
in_features=config.hidden_size, out_features=2 * config.max_position_embeddings, bias_attr=False
)
self.cross_attend_blocks_0_fn_to_out = paddle.nn.Linear(
in_features=config.max_position_embeddings, out_features=config.hidden_size, bias_attr=False
)
self.cross_attend_blocks_0_fn_to_q = paddle.nn.Linear(
in_features=config.hidden_size, out_features=config.max_position_embeddings, bias_attr=False
)
self.cross_attend_blocks_0_norm = paddle.nn.LayerNorm(config.hidden_size)
self.cross_attend_blocks_0_norm_context = paddle.nn.LayerNorm(config.hidden_size)
self.cross_attend_blocks_1_fn_net_0 = paddle.nn.Linear(
in_features=config.hidden_size, out_features=config.max_position_embeddings
)
self.cross_attend_blocks_1_fn_net_2 = paddle.nn.Linear(
in_features=config.max_position_embeddings // 2, out_features=config.hidden_size
)
self.cross_attend_blocks_1_norm = paddle.nn.LayerNorm(config.hidden_size)
self.latents = paddle.nn.Linear(in_features=config.hidden_size, out_features=512, bias_attr=False)
def forward(self, last_hidden_states, pool_mask):
one = paddle.eye(
num_rows=self.config.hidden_size,
num_columns=self.config.hidden_size,
dtype=self.latents.weight.dtype,
)
self_latents_weight_T = self.latents(one).T
# latents = repeat(self_latents_weight_T, "d h -> b d h", b=last_hidden_states.shape[0]) # from einops import repeat
latents = paddle.tile(self_latents_weight_T, repeat_times=last_hidden_states.shape[0]).reshape(
[self_latents_weight_T.shape[0], last_hidden_states.shape[0], self_latents_weight_T.shape[1]]
)
latents = latents.transpose([1, 0, 2])
normed_x = self.cross_attend_blocks_0_norm(last_hidden_states)
normed_context = self.cross_attend_blocks_0_norm_context(latents)
q = self.cross_attend_blocks_0_fn_to_q(normed_x)
kv = self.cross_attend_blocks_0_fn_to_kv(normed_context)
k = kv[:, :, : self.config.max_position_embeddings]
v = kv[:, :, self.config.max_position_embeddings :]
# q, k, v = map(lambda t: rearrange(t, "b n (h d) -> b n h d", h=self.config.num_key_value_heads), (q, k, v)) # from einops import rearrange
q = q.reshape(
[q.shape[0], q.shape[1], self.config.num_key_value_heads, q.shape[2] // self.config.num_key_value_heads]
)
k = k.reshape(
[k.shape[0], k.shape[1], self.config.num_key_value_heads, k.shape[2] // self.config.num_key_value_heads]
)
v = v.reshape(
[v.shape[0], v.shape[1], self.config.num_key_value_heads, v.shape[2] // self.config.num_key_value_heads]
)
# k.stop_gradient = False
# v.stop_gradient = False
# out = paddle.nn.functional.scaled_dot_product_attention(q, k, v) # if use this, must set k and v stop_gradient to False
out = scaled_dot_product_attention(q, k, v) # if use this, no need to manually set k and v
# out = rearrange(out, "b n h d -> b n (h d)", h=self.config.num_key_value_heads) # from einops import rearrange
out = out.reshape([out.shape[0], out.shape[1], out.shape[2] * out.shape[3]])
out_of_layer1 = self.cross_attend_blocks_0_fn_to_out(out) + last_hidden_states
normed_x = self.cross_attend_blocks_1_norm(out_of_layer1)
before_geglu = self.cross_attend_blocks_1_fn_net_0(normed_x)
x_in_gegle = before_geglu[:, :, : self.config.max_position_embeddings // 2]
gate_in_geglu = before_geglu[:, :, self.config.max_position_embeddings // 2 :]
x_after_geglu = x_in_gegle * paddle.nn.functional.gelu(gate_in_geglu)
after_geglu = self.cross_attend_blocks_1_fn_net_2(x_after_geglu)
out_of_layer2 = after_geglu + out_of_layer1
pool_mask = pool_mask.astype(out_of_layer2.dtype)
s = paddle.sum(
out_of_layer2 * pool_mask.unsqueeze(-1),
axis=1,
dtype=str(self.cross_attend_blocks_1_fn_net_2.weight.dtype).split(".")[-1],
)
d = paddle.sum(
pool_mask, axis=1, keepdim=True, dtype=str(self.cross_attend_blocks_1_fn_net_2.weight.dtype).split(".")[-1]
)
hiddens = s / d
hiddens = paddle.nn.functional.normalize(hiddens, p=2, axis=-1)
return hiddens
class NVEncodeModel(MistralModel):
def __init__(
self,
config,
tokenizer_path,
query_instruction,
document_instruction,
eval_batch_size=999,
normalized=True,
negatives_cross_device=False,
temperature_=1,
margin=0.01,
use_inbatch_neg=True,
matryoshka_dims=None,
matryoshka_loss_weights=None,
max_seq_length=4096,
):
super().__init__(config) # get mistral model structure
self.latent_model = LatentModel(config=config) # get latent model structure
self.tokenizer = AutoTokenizer.from_pretrained(tokenizer_path, padding_side="right")
if self.tokenizer.pad_token is None:
self.tokenizer.pad_token = self.tokenizer.eos_token
self.query_instruction = query_instruction
self.document_instruction = document_instruction
self.eval_batch_size = eval_batch_size
self.normalized = normalized
self.negatives_cross_device = negatives_cross_device
if self.negatives_cross_device:
if not dist.is_initialized():
raise ValueError("Distributed training has not been initialized for representation all gather.")
self.process_rank = dist.get_rank()
self.world_size = dist.get_world_size()
self.temperature = temperature_
self.margin = margin
self.use_inbatch_neg = use_inbatch_neg
self.matryoshka_dims = matryoshka_dims
self.matryoshka_loss_weights = matryoshka_loss_weights
self.max_seq_length = max_seq_length
self.cross_entropy = nn.CrossEntropyLoss(reduction="mean")
def _prepare_decoder_attention_mask(self, attention_mask, input_shape, inputs_embeds, past_key_values_length):
combined_attention_mask = _make_bidirection_mask(
input_shape,
inputs_embeds.dtype,
past_key_values_length=past_key_values_length,
)
if attention_mask is not None:
# [bsz, seq_len] -> [bsz, 1, tgt_seq_len, src_seq_len]
expanded_attn_mask = _expand_mask(attention_mask, inputs_embeds.dtype, tgt_len=input_shape[-1])
combined_attention_mask = (
expanded_attn_mask if combined_attention_mask is None else expanded_attn_mask + combined_attention_mask
)
return combined_attention_mask
def get_model_config(
self,
):
return self.model_config.to_dict()
def encode(self, features, instruction_len):
last_hidden_states = self.m_forward(**features)[0] # get bs*len*4096
pool_mask = features["attention_mask"]
pool_mask[:, :instruction_len] = 0
embeddings = self.latent_model.forward(last_hidden_states, pool_mask)
embeddings = paddle.nn.functional.normalize(embeddings, p=2, axis=1)
return embeddings
def compute_similarity(self, q_reps, p_reps):
# q_reps [batch_size, embedding_dim]
# p_reps [batch_size, embedding_dim]
return paddle.matmul(q_reps, p_reps.transpose([1, 0]))
def hard_negative_loss(self, q_reps, p_reps):
scores = self.compute_similarity(q_reps, p_reps)
scores = scores / self.temperature
scores = scores.reshape([q_reps.shape[0], -1])
target = paddle.arange(scores.shape[0], dtype="int64")
target = target * (p_reps.shape[0] // q_reps.shape[0])
loss = self.compute_loss(scores, target)
return scores, loss
def in_batch_negative_loss(self, q_reps, p_reps):
# In batch negatives
scores = self.compute_similarity(q_reps, p_reps)
# Subtract margin from all positive samples cosine_sim()
margin_diag = paddle.full(shape=[q_reps.shape[0]], fill_value=self.margin, dtype=q_reps.dtype)
scores = scores - paddle.diag(margin_diag)
# Scale cosine to ease training converge
scores = scores / self.temperature
target = paddle.arange(0, q_reps.shape[0], dtype="int64")
loss = self.compute_loss(scores, target)
return scores, loss
def forward(
self,
query: Dict[str, paddle.Tensor] = None,
passage: Dict[str, paddle.Tensor] = None,
teacher_score: paddle.Tensor = None,
):
instruction_len = len(self.tokenizer.encode(self.query_instruction, add_special_tokens=False)["input_ids"])
q_reps = self.encode(query, instruction_len)
instruction_len = len(self.tokenizer.encode(self.document_instruction, add_special_tokens=False)["input_ids"])
p_reps = self.encode(passage, instruction_len)
# For non-matryoshka loss, we normalize the representations
if not self.matryoshka_dims:
if self.normalized:
q_reps = paddle.nn.functional.normalize(q_reps, axis=-1)
p_reps = paddle.nn.functional.normalize(p_reps, axis=-1)
if self.training:
# Cross device negatives
if self.negatives_cross_device:
q_reps = self._dist_gather_tensor(q_reps)
p_reps = self._dist_gather_tensor(p_reps)
if self.matryoshka_dims:
loss = 0.0
scores = 0.0
for loss_weight, dim in zip(self.matryoshka_loss_weights, self.matryoshka_dims):
reduced_q = q_reps[:, :dim]
reduced_d = p_reps[:, :dim]
if self.normalized:
reduced_q = paddle.nn.functional.normalize(reduced_q, axis=-1)
reduced_d = paddle.nn.functional.normalize(reduced_d, axis=-1)
if self.use_inbatch_neg:
dim_score, dim_loss = self.in_batch_negative_loss(reduced_q, reduced_d)
else:
dim_score, dim_loss = self.hard_negative_loss(reduced_q, reduced_d)
scores += dim_score
loss += loss_weight * dim_loss
elif self.use_inbatch_neg:
scores, loss = self.in_batch_negative_loss(q_reps, p_reps)
else:
scores, loss = self.hard_negative_loss(q_reps, p_reps)
else:
scores = self.compute_similarity(q_reps, p_reps)
loss = None
return EncoderOutput(
loss=loss,
scores=scores,
q_reps=q_reps,
p_reps=p_reps,
)
def compute_loss(self, scores, target):
return self.cross_entropy(scores, target)
def _dist_gather_tensor(self, t: Optional[paddle.Tensor]):
if t is None:
return None
all_tensors = [paddle.empty_like(t) for _ in range(self.world_size)]
dist.all_gather(all_tensors, t)
all_tensors[self.process_rank] = t
all_tensors = paddle.concat(all_tensors, axis=0)
return all_tensors
def save_pretrained(self, output_dir: str, **kwargs):
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)
def m_forward(
self,
input_ids: paddle.Tensor = None,
attention_mask: Optional[paddle.Tensor] = None,
position_ids: Optional[paddle.Tensor] = None,
past_key_values: Optional[List[paddle.Tensor]] = None,
inputs_embeds: Optional[paddle.Tensor] = None,
use_cache: Optional[bool] = None,
output_attentions: Optional[bool] = None,
output_hidden_states: Optional[bool] = None,
return_dict: Optional[bool] = None,
) -> Union[Tuple, BaseModelOutputWithPast]:
output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
output_hidden_states = (
output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
)
use_cache = use_cache if use_cache is not None else self.config.use_cache
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
# retrieve input_ids and inputs_embeds
if input_ids is not None and inputs_embeds is not None:
raise ValueError("You cannot specify both decoder_input_ids and decoder_inputs_embeds at the same time")
elif input_ids is not None:
batch_size, seq_length = input_ids.shape
elif inputs_embeds is not None:
batch_size, seq_length, _ = inputs_embeds.shape
else:
raise ValueError("You have to specify either decoder_input_ids or decoder_inputs_embeds")
seq_length_with_past = seq_length
past_key_values_length = 0
if past_key_values is not None:
past_key_values_length = past_key_values[0][0].shape[2]
seq_length_with_past = seq_length_with_past + past_key_values_length
if position_ids is None:
position_ids = paddle.arange(
past_key_values_length, seq_length + past_key_values_length, dtype=paddle.int64
)
position_ids = position_ids.unsqueeze(0).expand((batch_size, seq_length))
else:
position_ids = position_ids.reshape([-1, seq_length]).astype("int64")
if inputs_embeds is None:
inputs_embeds = self.embed_tokens(input_ids)
attention_mask = self._prepare_decoder_attention_mask(
attention_mask,
(batch_size, seq_length),
inputs_embeds,
past_key_values_length,
)
hidden_states = inputs_embeds
if self.enable_recompute and self.training:
if use_cache:
logger.warning_once(
"`use_cache=True` is incompatible with gradient checkpointing. Setting `use_cache=False`..."
)
use_cache = False
# decoder layers
all_hidden_states = () if output_hidden_states else None
all_self_attns = () if output_attentions else None
next_decoder_cache = () if use_cache else None
for idx, decoder_layer in enumerate(self.layers):
if output_hidden_states:
all_hidden_states += (hidden_states,)
past_key_value = past_key_values[idx] if past_key_values is not None else None
has_gradient = not hidden_states.stop_gradient
if self.enable_recompute and has_gradient:
def create_custom_forward(module):
def custom_forward(*inputs):
# None for past_key_value
return module(*inputs, past_key_value, output_attentions)
return custom_forward
layer_outputs = recompute(
create_custom_forward(decoder_layer),
hidden_states,
attention_mask,
position_ids,
)
else:
layer_outputs = decoder_layer(
hidden_states,
attention_mask=attention_mask,
position_ids=position_ids,
past_key_value=past_key_value,
output_attentions=output_attentions,
use_cache=use_cache,
)
hidden_states = layer_outputs[0]
if use_cache:
next_decoder_cache += (layer_outputs[2 if output_attentions else 1],)
if output_attentions:
all_self_attns += (layer_outputs[1],)
hidden_states = self.norm(hidden_states)
# add hidden states from the last decoder layer
if output_hidden_states:
all_hidden_states += (hidden_states,)
next_cache = next_decoder_cache if use_cache else None
if not return_dict:
return tuple(v for v in [hidden_states, next_cache, all_hidden_states, all_self_attns] if v is not None)
return BaseModelOutputWithPast(
last_hidden_state=hidden_states,
past_key_values=next_cache,
hidden_states=all_hidden_states,
attentions=all_self_attns,
)
@paddle.no_grad()
def encode_sentences(self, sentences: List[str], instruction_len, **kwargs) -> np.ndarray:
all_embeddings = []
for start_index in tqdm.tqdm(list(range(0, len(sentences), self.eval_batch_size)), desc="Batches"):
sentences_batch = sentences[start_index : start_index + self.eval_batch_size]
inputs = self.tokenizer(
sentences_batch,
max_length=self.max_seq_length,
padding=True,
return_attention_mask=True,
return_token_type_ids=False,
return_tensors="pd",
truncation=True,
)
last_hidden_states = self.m_forward(**inputs)[0] # get bs*len*4096
pool_mask = inputs["attention_mask"]
pool_mask[:, :instruction_len] = 0
embeddings = self.latent_model.forward(last_hidden_states, pool_mask)
embeddings = paddle.nn.functional.normalize(embeddings, p=2, axis=1)
all_embeddings.append(embeddings.cpu().numpy().astype("float32"))
return np.concatenate(all_embeddings, axis=0)
def encode_queries(self, queries: List[str], **kwargs) -> np.ndarray:
input_texts = [self.query_instruction + q + self.tokenizer.eos_token for q in queries]
instruction_len = len(self.tokenizer.encode(self.query_instruction, add_special_tokens=False)["input_ids"])
return self.encode_sentences(input_texts, instruction_len)
def encode_corpus(self, corpus: List[Union[Dict[str, str], str]], **kwargs) -> np.ndarray:
if isinstance(corpus[0], dict):
input_texts = ["{} {}".format(doc.get("title", ""), doc["text"]).strip() for doc in corpus]
else:
input_texts = corpus
input_texts = [self.document_instruction + doc + self.tokenizer.eos_token for doc in input_texts]
instruction_len = len(self.tokenizer.encode(self.document_instruction, add_special_tokens=False)["input_ids"])
return self.encode_sentences(input_texts, instruction_len)