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

586 lines
26 KiB
Python

# Copyright (c) 2023 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 typing import Optional
import numpy as np
import paddle
from paddlenlp.data import DataCollatorWithPadding
from paddlenlp.transformers import AutoModel, AutoTokenizer, ErnieDualEncoder
from ..utils.log import logger
from .task import Task
from .utils import dygraph_mode_guard, static_mode_guard
ENCODER_TYPE = {
"rocketqa-zh-dureader-query-encoder": "query",
"rocketqa-zh-dureader-para-encoder": "paragraph",
"rocketqa-zh-base-query-encoder": "query",
"rocketqa-zh-base-para-encoder": "paragraph",
"rocketqa-zh-medium-query-encoder": "query",
"rocketqa-zh-medium-para-encoder": "paragraph",
"rocketqa-zh-mini-query-encoder": "query",
"rocketqa-zh-mini-para-encoder": "paragraph",
"rocketqa-zh-micro-query-encoder": "query",
"rocketqa-zh-micro-para-encoder": "paragraph",
"rocketqa-zh-nano-query-encoder": "query",
"rocketqa-zh-nano-para-encoder": "paragraph",
"rocketqav2-en-marco-query-encoder": "query",
"rocketqav2-en-marco-para-encoder": "paragraph",
"ernie-search-base-dual-encoder-marco-en": "query_paragraph",
}
usage = r"""
from paddlenlp import Taskflow
import paddle.nn.functional as F
# Text feature_extraction with rocketqa-zh-base-query-encoder
text_encoder = Taskflow("feature_extraction", model='rocketqa-zh-base-query-encoder')
text_embeds = text_encoder(['春天适合种什么花?','谁有狂三这张高清的?'])
text_features1 = text_embeds["features"]
print(text_features1)
'''
Tensor(shape=[2, 768], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[[ 0.27640465, -0.13405125, 0.00612330, ..., -0.15600294,
-0.18932408, -0.03029604],
[-0.12041329, -0.07424965, 0.07895312, ..., -0.17068857,
0.04485796, -0.18887770]])
'''
text_embeds = text_encoder('春天适合种什么菜?')
text_features2 = text_embeds["features"]
print(text_features2)
'''
Tensor(shape=[1, 768], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[[ 0.32578075, -0.02398480, -0.18929179, -0.18639392, -0.04062131,
0.06708499, -0.04631376, -0.41177100, -0.23074438, -0.23627219,
......
'''
probs = F.cosine_similarity(text_features1, text_features2)
print(probs)
'''
Tensor(shape=[2], dtype=float32, place=Place(gpu:0), stop_gradient=True,
[0.86455142, 0.41222256])
'''
"""
class TextFeatureExtractionTask(Task):
resource_files_names = {
"model_state": "model_state.pdparams",
"config": "config.json",
"vocab_file": "vocab.txt",
"special_tokens_map": "special_tokens_map.json",
"tokenizer_config": "tokenizer_config.json",
}
resource_files_urls = {
"rocketqa-zh-dureader-query-encoder": {
"model_state": [
"https://paddlenlp.bj.bcebos.com/taskflow/feature_extraction/rocketqa-zh-dureader-query-encoder/model_state.pdparams",
"6125930530fd55ed715b0595e65789aa",
],
"config": [
"https://paddlenlp.bj.bcebos.com/taskflow/feature_extraction/rocketqa-zh-dureader-query-encoder/config.json",
"efc1280069bb22b5bd06dc44b780bc6a",
],
"vocab_file": [
"https://paddlenlp.bj.bcebos.com/taskflow/feature_extraction/rocketqa-zh-dureader-query-encoder/vocab.txt",
"062f696cad47bb62da86d8ae187b0ef4",
],
"special_tokens_map": [
"https://paddlenlp.bj.bcebos.com/taskflow/feature_extraction/rocketqa-zh-dureader-query-encoder/special_tokens_map.json",
"8b3fb1023167bb4ab9d70708eb05f6ec",
],
"tokenizer_config": [
"https://paddlenlp.bj.bcebos.com/taskflow/feature_extraction/rocketqa-zh-dureader-query-encoder/tokenizer_config.json",
"3a50349b8514e744fed72e59baca51b5",
],
},
"rocketqa-zh-base-query-encoder": {
"model_state": [
"https://paddlenlp.bj.bcebos.com/taskflow/feature_extraction/rocketqa-zh-base-query-encoder/model_state.pdparams",
"3bb1a7870792146c6dd2fa47a45e15cc",
],
"config": [
"https://paddlenlp.bj.bcebos.com/taskflow/feature_extraction/rocketqa-zh-base-query-encoder/config.json",
"be88115dd8a00e9de6b44f8c9a055e1a",
],
"vocab_file": [
"https://paddlenlp.bj.bcebos.com/taskflow/feature_extraction/rocketqa-zh-base-query-encoder/vocab.txt",
"1c1c1f4fd93c5bed3b4eebec4de976a8",
],
"special_tokens_map": [
"https://paddlenlp.bj.bcebos.com/taskflow/feature_extraction/rocketqa-zh-base-query-encoder/special_tokens_map.json",
"8b3fb1023167bb4ab9d70708eb05f6ec",
],
"tokenizer_config": [
"https://paddlenlp.bj.bcebos.com/taskflow/feature_extraction/rocketqa-zh-base-query-encoder/tokenizer_config.json",
"be86466f6769fde498690269d099ea7c",
],
},
}
def __init__(
self,
task: str = None,
model: str = None,
batch_size: int = 1,
max_seq_len: int = 128,
_static_mode: bool = True,
return_tensors: str = "pd",
reinitialize: bool = False,
share_parameters: bool = False,
is_paragraph: bool = False,
output_emb_size: Optional[int] = None,
**kwargs
):
super().__init__(task=task, model=model, **kwargs)
self._seed = None
self.export_type = "text"
self._batch_size = batch_size
self.max_seq_len = max_seq_len
self.model = model
self._static_mode = _static_mode
self.return_tensors = return_tensors
self.reinitialize = reinitialize
self.share_parameters = share_parameters
self.output_emb_size = output_emb_size
self.is_paragraph = is_paragraph
self._check_para_encoder()
# self._check_task_files()
self._check_predictor_type()
self._construct_tokenizer()
# self._get_inference_model()
if self._static_mode:
self._get_inference_model()
else:
self._construct_model(model)
def _check_para_encoder(self):
if self.model in ENCODER_TYPE:
if ENCODER_TYPE[self.model] == "paragraph":
self.is_paragraph = True
else:
self.is_paragraph = False
else:
self.is_paragraph = False
def _construct_model(self, model):
"""
Construct the inference model for the predictor.
"""
# self._model = ErnieDualEncoder(self._task_path)
self._model = ErnieDualEncoder(
query_model_name_or_path=self.model,
output_emb_size=self.output_emb_size,
reinitialize=self.reinitialize,
share_parameters=self.share_parameters,
)
self._model.eval()
def _construct_tokenizer(self):
"""
Construct the tokenizer for the predictor.
"""
self._tokenizer = AutoTokenizer.from_pretrained(self.model)
if self._static_mode:
self._collator = DataCollatorWithPadding(self._tokenizer, return_tensors="np")
else:
self._collator = DataCollatorWithPadding(self._tokenizer, return_tensors="pd")
def _construct_input_spec(self):
"""
Construct the input spec for the convert dygraph model to static model.
"""
self._input_spec = [
paddle.static.InputSpec(shape=[None, None], dtype="int64", name="input_ids"),
paddle.static.InputSpec(shape=[None, None], dtype="int64", name="token_type_ids"),
]
def _batchify(self, data, batch_size):
"""
Generate input batches.
"""
def _parse_batch(batch_examples):
if self.is_paragraph:
# The input of the passage encoder is [CLS][SEP]...[SEP].
tokenized_inputs = self._tokenizer(
text=[""] * len(batch_examples),
text_pair=batch_examples,
padding="max_length",
truncation=True,
max_seq_len=self.max_seq_len,
)
else:
tokenized_inputs = self._tokenizer(
text=batch_examples,
padding="max_length",
truncation=True,
max_seq_len=self.max_seq_len,
)
return tokenized_inputs
# Separates data into some batches.
one_batch = []
for example in data:
one_batch.append(example)
if len(one_batch) == batch_size:
yield _parse_batch(one_batch)
one_batch = []
if one_batch:
yield _parse_batch(one_batch)
def _preprocess(self, inputs):
"""
Transform the raw inputs to the model inputs, two steps involved:
1) Transform the raw text/image to token ids/pixel_values.
2) Generate the other model inputs from the raw text/image and token ids/pixel_values.
"""
inputs = self._check_input_text(inputs)
batches = self._batchify(inputs, self._batch_size)
outputs = {"batches": batches, "inputs": inputs}
return outputs
def _run_model(self, inputs, **kwargs):
"""
Run the task model from the outputs of the `_preprocess` function.
"""
all_feats = []
if self._static_mode:
with static_mode_guard():
for batch_inputs in inputs["batches"]:
batch_inputs = self._collator(batch_inputs)
if self._predictor_type == "paddle-inference":
if "input_ids" in batch_inputs:
self.input_handles[0].copy_from_cpu(batch_inputs["input_ids"])
self.input_handles[1].copy_from_cpu(batch_inputs["token_type_ids"])
self.predictor.run()
text_features = self.output_handle[0].copy_to_cpu()
all_feats.append(text_features)
else:
# onnx mode
if "input_ids" in batch_inputs:
input_dict = {}
input_dict["input_ids"] = batch_inputs["input_ids"]
input_dict["token_type_ids"] = batch_inputs["token_type_ids"]
text_features = self.predictor.run(None, input_dict)[0].tolist()
all_feats.append(text_features)
else:
with dygraph_mode_guard():
for batch_inputs in inputs["batches"]:
batch_inputs = self._collator(batch_inputs)
text_features = self._model.get_pooled_embedding(
input_ids=batch_inputs["input_ids"], token_type_ids=batch_inputs["token_type_ids"]
)
all_feats.append(text_features.detach().numpy())
inputs.update({"features": all_feats})
return inputs
def _postprocess(self, inputs):
inputs["features"] = np.concatenate(inputs["features"], axis=0)
if self.return_tensors == "pd":
inputs["features"] = paddle.to_tensor(inputs["features"])
return inputs
def _convert_dygraph_to_static(self):
"""
Convert the dygraph model to static model.
"""
assert (
self._model is not None
), "The dygraph model must be created before converting the dygraph model to static model."
assert (
self._input_spec is not None
), "The input spec must be created before converting the dygraph model to static model."
logger.info("Converting to the inference model cost a little time.")
static_model = paddle.jit.to_static(self._model.get_pooled_embedding, input_spec=self._input_spec)
paddle.jit.save(static_model, self.inference_model_path)
logger.info("The inference model save in the path:{}".format(self.inference_model_path))
def text_length(text):
# {key: value} case
if isinstance(text, dict):
return len(next(iter(text.values())))
# Object has no len() method
elif not hasattr(text, "__len__"):
return 1
# Empty string or list of ints
elif len(text) == 0 or isinstance(text[0], int):
return len(text)
# Sum of length of individual strings
else:
return sum([len(t) for t in text])
class SentenceFeatureExtractionTask(Task):
resource_files_names = {
"model_state": "model_state.pdparams",
"config": "config.json",
"vocab_file": "vocab.txt",
"special_tokens_map": "special_tokens_map.json",
"tokenizer_config": "tokenizer_config.json",
}
def __init__(
self,
task: str = None,
model: str = None,
batch_size: int = 1,
max_seq_len: int = 512,
_static_mode: bool = True,
return_tensors: str = "pd",
pooling_mode: str = "cls_token",
**kwargs
):
super().__init__(
task=task,
model=model,
pooling_mode=pooling_mode,
**kwargs,
)
self._seed = None
self.export_type = "text"
self._batch_size = batch_size
self.max_seq_len = max_seq_len
self.model = model
self._static_mode = _static_mode
self.return_tensors = return_tensors
self.pooling_mode = pooling_mode
self._check_predictor_type()
self._construct_tokenizer()
if self._static_mode:
self._get_inference_model()
else:
self._construct_model(model)
def _construct_model(self, model):
"""
Construct the inference model for the predictor.
"""
self._model = AutoModel.from_pretrained(self.model)
self._model.eval()
def _construct_tokenizer(self):
"""
Construct the tokenizer for the predictor.
"""
self._tokenizer = AutoTokenizer.from_pretrained(self.model)
self.pad_token_id = self._tokenizer.convert_tokens_to_ids(self._tokenizer.pad_token)
if self._static_mode:
self._collator = DataCollatorWithPadding(self._tokenizer, return_tensors="np")
else:
self._collator = DataCollatorWithPadding(self._tokenizer, return_tensors="pd")
def _construct_input_spec(self):
"""
Construct the input spec for the convert dygraph model to static model.
"""
self._input_spec = [
paddle.static.InputSpec(shape=[None, None], dtype="int64", name="input_ids"),
paddle.static.InputSpec(shape=[None, None], dtype="int64", name="token_type_ids"),
]
def _batchify(self, data, batch_size):
"""
Generate input batches.
"""
def _parse_batch(batch_examples, max_seq_len=None):
if isinstance(batch_examples[0], str):
to_tokenize = [batch_examples]
else:
batch1, batch2 = [], []
for text_tuple in batch_examples:
batch1.append(text_tuple[0])
batch2.append(text_tuple[1])
to_tokenize = [batch1, batch2]
to_tokenize = [[str(s).strip() for s in col] for col in to_tokenize]
if max_seq_len is None:
max_seq_len = self.max_seq_len
tokenized_inputs = self._tokenizer(
to_tokenize[0],
padding=True,
truncation="longest_first",
max_seq_len=max_seq_len,
)
return tokenized_inputs
# Separates data into some batches.
one_batch = []
self.length_sorted_idx = np.argsort([-text_length(sen) for sen in data])
sentences_sorted = [data[idx] for idx in self.length_sorted_idx]
for example in range(len(sentences_sorted)):
one_batch.append(sentences_sorted[example])
if len(one_batch) == batch_size:
yield _parse_batch(one_batch)
one_batch = []
if one_batch:
yield _parse_batch(one_batch)
def _preprocess(self, inputs):
"""
Transform the raw inputs to the model inputs, two steps involved:
1) Transform the raw text/image to token ids/pixel_values.
2) Generate the other model inputs from the raw text/image and token ids/pixel_values.
"""
inputs = self._check_input_text(inputs)
batches = self._batchify(inputs, self._batch_size)
outputs = {"batches": batches, "inputs": inputs}
return outputs
def _run_model(self, inputs, **kwargs):
"""
Run the task model from the outputs of the `_preprocess` function.
"""
pooling_mode = kwargs.get("pooling_mode", None)
if pooling_mode is None:
pooling_mode = self.pooling_mode
all_feats = []
if self._static_mode:
with static_mode_guard():
for batch_inputs in inputs["batches"]:
batch_inputs = self._collator(batch_inputs)
if self._predictor_type == "paddle-inference":
if "input_ids" in batch_inputs:
self.input_handles[0].copy_from_cpu(batch_inputs["input_ids"])
self.input_handles[1].copy_from_cpu(batch_inputs["token_type_ids"])
self.predictor.run()
token_embeddings = self.output_handle[0].copy_to_cpu()
if pooling_mode == "max_tokens":
attention_mask = (batch_inputs["input_ids"] != self.pad_token_id).astype(
token_embeddings.dtype
)
input_mask_expanded = np.expand_dims(attention_mask, -1).repeat(
token_embeddings.shape[-1], axis=-1
)
token_embeddings[input_mask_expanded == 0] = -1e9
max_over_time = np.max(token_embeddings, 1)
all_feats.append(max_over_time)
elif pooling_mode == "mean_tokens" or pooling_mode == "mean_sqrt_len_tokens":
attention_mask = (batch_inputs["input_ids"] != self.pad_token_id).astype(
token_embeddings.dtype
)
input_mask_expanded = np.expand_dims(attention_mask, -1).repeat(
token_embeddings.shape[-1], axis=-1
)
sum_embeddings = np.sum(token_embeddings * input_mask_expanded, 1)
sum_mask = input_mask_expanded.sum(1)
sum_mask = np.clip(sum_mask, a_min=1e-9, a_max=np.max(sum_mask))
if pooling_mode == "mean_tokens":
all_feats.append(sum_embeddings / sum_mask)
elif pooling_mode == "mean_sqrt_len_tokens":
all_feats.append(sum_embeddings / np.sqrt(sum_mask))
else:
cls_token = token_embeddings[:, 0]
all_feats.append(cls_token)
else:
# onnx mode
if "input_ids" in batch_inputs:
input_dict = {}
input_dict["input_ids"] = batch_inputs["input_ids"]
input_dict["token_type_ids"] = batch_inputs["token_type_ids"]
token_embeddings = self.predictor.run(None, input_dict)[0]
if pooling_mode == "max_tokens":
attention_mask = (batch_inputs["input_ids"] != self.pad_token_id).astype(
token_embeddings.dtype
)
input_mask_expanded = np.expand_dims(attention_mask, -1).repeat(
token_embeddings.shape[-1], axis=-1
)
token_embeddings[input_mask_expanded == 0] = -1e9
max_over_time = np.max(token_embeddings, 1)
all_feats.append(max_over_time)
elif pooling_mode == "mean_tokens" or pooling_mode == "mean_sqrt_len_tokens":
attention_mask = (batch_inputs["input_ids"] != self.pad_token_id).astype(
token_embeddings.dtype
)
input_mask_expanded = np.expand_dims(attention_mask, -1).repeat(
token_embeddings.shape[-1], axis=-1
)
sum_embeddings = np.sum(token_embeddings * input_mask_expanded, 1)
sum_mask = input_mask_expanded.sum(1)
sum_mask = np.clip(sum_mask, a_min=1e-9, a_max=np.max(sum_mask))
if pooling_mode == "mean_tokens":
all_feats.append(sum_embeddings / sum_mask)
elif pooling_mode == "mean_sqrt_len_tokens":
all_feats.append(sum_embeddings / np.sqrt(sum_mask))
else:
cls_token = token_embeddings[:, 0]
all_feats.append(cls_token)
else:
with dygraph_mode_guard():
for batch_inputs in inputs["batches"]:
batch_inputs = self._collator(batch_inputs)
token_embeddings = self._model(input_ids=batch_inputs["input_ids"])[0]
if pooling_mode == "max_tokens":
attention_mask = (batch_inputs["input_ids"] != self.pad_token_id).astype(
self._model.pooler.dense.weight.dtype
)
input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.shape)
token_embeddings[input_mask_expanded == 0] = -1e9
max_over_time = paddle.max(token_embeddings, 1).detach().numpy()
all_feats.append(max_over_time)
elif pooling_mode == "mean_tokens" or pooling_mode == "mean_sqrt_len_tokens":
attention_mask = (batch_inputs["input_ids"] != self.pad_token_id).astype(
self._model.pooler.dense.weight.dtype
)
input_mask_expanded = attention_mask.unsqueeze(-1).expand(token_embeddings.shape)
sum_embeddings = paddle.sum(token_embeddings * input_mask_expanded, 1)
sum_mask = input_mask_expanded.sum(1)
sum_mask = paddle.clip(sum_mask, min=1e-9)
if pooling_mode == "mean_tokens":
text_features = sum_embeddings / sum_mask
all_feats.append(text_features.detach().numpy())
elif pooling_mode == "mean_sqrt_len_tokens":
text_features = sum_embeddings / paddle.sqrt(sum_mask)
all_feats.append(text_features.detach().numpy())
else:
cls_token = token_embeddings[:, 0].detach().numpy()
all_feats.append(cls_token)
inputs.update({"features": all_feats})
return inputs
def _postprocess(self, inputs):
inputs["features"] = np.concatenate(inputs["features"], axis=0)
inputs["features"] = [inputs["features"][idx] for idx in np.argsort(self.length_sorted_idx)]
if self.return_tensors == "pd":
inputs["features"] = paddle.to_tensor(inputs["features"])
return inputs
def _convert_dygraph_to_static(self):
"""
Convert the dygraph model to static model.
"""
assert (
self._model is not None
), "The dygraph model must be created before converting the dygraph model to static model."
assert (
self._input_spec is not None
), "The input spec must be created before converting the dygraph model to static model."
logger.info("Converting to the inference model cost a little time.")
static_model = paddle.jit.to_static(self._model, input_spec=self._input_spec)
paddle.jit.save(static_model, self.inference_model_path)
logger.info("The inference model save in the path:{}".format(self.inference_model_path))