967 lines
35 KiB
Python
967 lines
35 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 __future__ import annotations
|
|
|
|
import glob
|
|
import math
|
|
import os
|
|
import shutil
|
|
import struct
|
|
from typing import List, Optional
|
|
|
|
import numpy as np
|
|
import paddle
|
|
import paddle.distributed as dist
|
|
import paddle.incubate.multiprocessing as mp
|
|
from paddle.distributed import fleet
|
|
from sklearn.metrics import accuracy_score
|
|
|
|
from paddlenlp.datasets import ZeroPaddingIterableDataset
|
|
from paddlenlp.generation import GenerationConfig
|
|
from paddlenlp.trainer import TrainerCallback
|
|
from paddlenlp.trainer.trainer_utils import IterableDatasetShard
|
|
from paddlenlp.transformers import (
|
|
AutoTokenizer,
|
|
ChatGLMv2Tokenizer,
|
|
DeepseekV2ForCausalLMPipe,
|
|
DeepseekV3ForCausalLMPipe,
|
|
LlamaForCausalLMPipe,
|
|
PretrainedConfig,
|
|
Qwen2ForCausalLMPipe,
|
|
Qwen2MoeForCausalLMPipe,
|
|
)
|
|
from paddlenlp.transformers.tokenizer_utils import PretrainedTokenizer
|
|
from paddlenlp.utils.log import logger
|
|
|
|
|
|
def compute_metrics(eval_preds):
|
|
flattened_preds = np.array(eval_preds.predictions).flatten()
|
|
flattened_labels = np.array(eval_preds.label_ids).flatten()
|
|
filtered_preds = flattened_preds[flattened_labels != -100]
|
|
filtered_labels = flattened_labels[flattened_labels != -100]
|
|
accuracy = accuracy_score(y_true=filtered_labels, y_pred=filtered_preds)
|
|
return {
|
|
"accuracy": accuracy,
|
|
}
|
|
|
|
|
|
def get_prefix_tuning_params(model):
|
|
if model.base_model_prefix == "chatglm":
|
|
from paddlenlp.peft.prefix import chatglm_postprocess_past_key_value
|
|
|
|
num_attention_heads = model.config.num_attention_heads
|
|
num_hidden_layers = model.config.num_hidden_layers
|
|
hidden_size = model.config.hidden_size
|
|
postprocess_past_key_value = chatglm_postprocess_past_key_value
|
|
multi_query_group_num = None
|
|
elif model.base_model_prefix == "chatglm_v2":
|
|
from paddlenlp.peft.prefix import chatglm_postprocess_past_key_value
|
|
|
|
num_attention_heads = model.config.num_attention_heads
|
|
num_hidden_layers = model.config.num_layers
|
|
hidden_size = model.config.hidden_size
|
|
postprocess_past_key_value = chatglm_postprocess_past_key_value
|
|
multi_query_group_num = model.config.multi_query_group_num # num_key_value_heads
|
|
elif model.base_model_prefix == "bloom":
|
|
from paddlenlp.peft.prefix import bloom_postprocess_past_key_value
|
|
|
|
num_attention_heads = model.config.num_attention_heads
|
|
num_hidden_layers = model.config.n_layer
|
|
hidden_size = model.config.n_embed
|
|
postprocess_past_key_value = bloom_postprocess_past_key_value
|
|
multi_query_group_num = None
|
|
elif model.base_model_prefix == "llama":
|
|
from paddlenlp.peft.prefix import llama_postprocess_past_key_value
|
|
|
|
num_attention_heads = model.config.n_head
|
|
num_hidden_layers = model.config.n_layer
|
|
hidden_size = model.config.hidden_size
|
|
postprocess_past_key_value = llama_postprocess_past_key_value
|
|
multi_query_group_num = None
|
|
elif model.base_model_prefix == "mistral":
|
|
from paddlenlp.peft.prefix import mistral_postprocess_past_key_value
|
|
|
|
num_attention_heads = model.config.num_attention_heads
|
|
num_hidden_layers = model.config.num_hidden_layers
|
|
hidden_size = model.config.hidden_size
|
|
postprocess_past_key_value = mistral_postprocess_past_key_value
|
|
multi_query_group_num = model.config.num_key_value_heads
|
|
elif model.base_model_prefix == "qwen":
|
|
from paddlenlp.peft.prefix import qwen_postprocess_past_key_value
|
|
|
|
num_attention_heads = model.config.num_attention_heads
|
|
num_hidden_layers = model.config.num_hidden_layers
|
|
hidden_size = model.config.hidden_size
|
|
postprocess_past_key_value = qwen_postprocess_past_key_value
|
|
multi_query_group_num = None
|
|
elif model.base_model_prefix == "qwen2":
|
|
from paddlenlp.peft.prefix import qwen_postprocess_past_key_value
|
|
|
|
num_attention_heads = model.config.num_attention_heads
|
|
num_hidden_layers = model.config.num_hidden_layers
|
|
hidden_size = model.config.hidden_size
|
|
postprocess_past_key_value = qwen_postprocess_past_key_value
|
|
multi_query_group_num = model.config.num_key_value_heads # num_key_value_heads
|
|
else:
|
|
raise ValueError(f"Unknown base_model_prefix: {model.base_model_prefix}. ")
|
|
return dict(
|
|
num_attention_heads=num_attention_heads,
|
|
num_hidden_layers=num_hidden_layers,
|
|
hidden_size=hidden_size,
|
|
postprocess_past_key_value=postprocess_past_key_value,
|
|
multi_query_group_num=multi_query_group_num,
|
|
)
|
|
|
|
|
|
def get_lora_target_modules(model):
|
|
# Not yet support RowParallelLinear
|
|
if model.base_model_prefix == "chatglm":
|
|
target_modules = [".*query_key_value.*", ".*dense.*", ".*dense_h_to_4h.*", ".*dense_4h_to_h.*"]
|
|
elif model.base_model_prefix == "chatglm_v2":
|
|
target_modules = [
|
|
".*query.*",
|
|
".*key.*",
|
|
".*value.*",
|
|
".*dense.*",
|
|
".*dense_h_to_4h.*",
|
|
".*dense_4h_to_h.*",
|
|
]
|
|
elif model.base_model_prefix == "gpt":
|
|
target_modules = [
|
|
".*qkv_proj.*",
|
|
".*q_proj.*",
|
|
".*k_proj.*",
|
|
".*v_proj.*",
|
|
".*linear1.*",
|
|
".*linear2.*",
|
|
".*out_proj.*",
|
|
]
|
|
elif model.base_model_prefix == "bloom":
|
|
target_modules = [".*query_key_value.*", ".*dense.*", ".*dense_h_to_4h.*", ".*dense_4h_to_h.*"]
|
|
elif model.base_model_prefix in ["llama", "jamba"] or isinstance(model, LlamaForCausalLMPipe):
|
|
target_modules = [
|
|
".*q_proj.*",
|
|
".*v_proj.*",
|
|
".*k_proj.*",
|
|
".*o_proj.*",
|
|
".*qkv_proj.*",
|
|
".*gate_proj.*",
|
|
".*down_proj.*",
|
|
".*up_proj.*",
|
|
".*gate_up_fused_proj.*",
|
|
]
|
|
elif model.base_model_prefix == "opt":
|
|
target_modules = [
|
|
".*project_in.*",
|
|
".*project_out.*",
|
|
".*q_proj.*",
|
|
".*k_proj.*",
|
|
".*v_proj.*",
|
|
".*qkv_proj.*",
|
|
".*out_proj.*",
|
|
".*linear1.*",
|
|
".*linear2.*",
|
|
]
|
|
elif model.base_model_prefix == "qwen":
|
|
target_modules = [
|
|
".*attn.c_attn.*",
|
|
".*attn.c_proj.*",
|
|
".*mlp.w1.*",
|
|
".*mlp.w2.*",
|
|
".*mlp.c_proj.*",
|
|
]
|
|
# TODO maybe changed to use model.config.model_type for more generality as Paddleformers
|
|
elif model.base_model_prefix == "qwen2" or isinstance(model, Qwen2ForCausalLMPipe):
|
|
# qwen2 dense lora support
|
|
target_modules = [
|
|
# fused
|
|
".*qkv_proj.*",
|
|
".*gate_up_fused_proj.*",
|
|
# independent
|
|
".*q_proj.*",
|
|
".*k_proj.*",
|
|
".*v_proj.*",
|
|
".*gate_proj.*",
|
|
".*up_proj.*",
|
|
# always exist
|
|
".*o_proj.*",
|
|
".*down_proj.*",
|
|
]
|
|
|
|
elif getattr(model, "config", None) is not None and model.config.model_type == "qwen3":
|
|
# qwen3 dense lora support
|
|
target_modules = [
|
|
# fused
|
|
".*qkv_proj.*",
|
|
".*gate_up_fused_proj.*",
|
|
# independent
|
|
".*q_proj.*",
|
|
".*k_proj.*",
|
|
".*v_proj.*",
|
|
".*gate_proj.*",
|
|
".*up_proj.*",
|
|
# always exist
|
|
".*o_proj.*",
|
|
".*down_proj.*",
|
|
]
|
|
elif model.base_model_prefix == "mixtral":
|
|
target_modules = [
|
|
".*q_proj.*",
|
|
".*k_proj.*",
|
|
".*v_proj.*",
|
|
".*o_proj.*",
|
|
# ".*gate.*", # TODO(DrownFish19): Does the gate weight require training?
|
|
".*w1.*",
|
|
".*w2.*",
|
|
".*w3.*",
|
|
]
|
|
elif model.base_model_prefix == "mistral":
|
|
target_modules = [
|
|
".*q_proj.*",
|
|
".*k_proj.*",
|
|
".*v_proj.*",
|
|
".*o_proj.*",
|
|
".*gate.*",
|
|
".*w1.*",
|
|
".*w2.*",
|
|
".*w3.*",
|
|
]
|
|
elif model.base_model_prefix == "qwen2_moe" or isinstance(model, Qwen2MoeForCausalLMPipe):
|
|
target_modules = [
|
|
".*q_proj.*",
|
|
".*k_proj.*",
|
|
".*v_proj.*",
|
|
".*o_proj.*",
|
|
# ".*gate.*", # TODO(DrownFish19): Does the gate weight require training?
|
|
".*gate_proj.*",
|
|
".*up_proj.*",
|
|
".*down_proj.*",
|
|
]
|
|
elif model.base_model_prefix in ["deepseek_v2", "deepseek_v3"] or isinstance(
|
|
model, (DeepseekV2ForCausalLMPipe, DeepseekV3ForCausalLMPipe)
|
|
):
|
|
target_modules = [
|
|
".*q_proj.*",
|
|
".*q_a_proj.*",
|
|
".*q_b_proj.*",
|
|
".*kv_a_proj_with_mqa.*",
|
|
".*kv_b_proj.*",
|
|
".*kv_b_proj.*",
|
|
".*o_proj.*",
|
|
".*mlp.gate_proj.*",
|
|
".*mlp.up_proj.*",
|
|
".*mlp.down_proj.*",
|
|
]
|
|
elif model.base_model_prefix == "yuan":
|
|
target_modules = [
|
|
".*q_proj.*",
|
|
".*k_proj.*",
|
|
".*v_proj.*",
|
|
".*o_proj.*",
|
|
".*gate_proj.*",
|
|
".*up_proj.*",
|
|
".*down_proj.*",
|
|
]
|
|
else:
|
|
raise ValueError(f"Unknown base_model_prefix: {model.base_model_prefix}.")
|
|
return target_modules
|
|
|
|
|
|
class ZeroPaddingIterDatasetCallback(TrainerCallback):
|
|
"""
|
|
A [`TrainerCallback`] that handles early stopping.
|
|
|
|
"""
|
|
|
|
def on_step_end(self, args, state, control, **kwargs):
|
|
train_dataloader = kwargs["train_dataloader"]
|
|
if isinstance(train_dataloader.dataset, ZeroPaddingIterableDataset):
|
|
dataset = train_dataloader.dataset
|
|
elif isinstance(train_dataloader.dataset, IterableDatasetShard) and isinstance(
|
|
train_dataloader.dataset.dataset, ZeroPaddingIterableDataset
|
|
):
|
|
dataset = train_dataloader.dataset.dataset
|
|
else:
|
|
raise ValueError(
|
|
"Unexpected dataset format: ZeroPaddingIterDatasetCallback expects `paddlenlp.datasets.ZeroPaddingIterableDataset`"
|
|
)
|
|
if state.trial_params is None:
|
|
state.trial_params = {}
|
|
state.trial_params["zero_padding_global_step"] = dataset.zero_padding_global_step
|
|
|
|
|
|
def get_infer_model_path(input_dir, model_prefix):
|
|
if dist.get_world_size() > 1:
|
|
local_rank = dist.get_rank()
|
|
return os.path.join(input_dir, "rank_{}".format(local_rank), model_prefix)
|
|
else:
|
|
return os.path.join(input_dir, model_prefix)
|
|
|
|
|
|
def deserialize_from_file(fp):
|
|
x_type = fp.read(1)
|
|
x_type_out = struct.unpack("c", x_type)[0]
|
|
# data
|
|
data_list = []
|
|
if x_type_out == b"0":
|
|
data = fp.read(4)
|
|
data_out = struct.unpack("f", data)[0]
|
|
while data:
|
|
data_out = struct.unpack("f", data)[0]
|
|
data_list.append(data_out)
|
|
data = fp.read(4)
|
|
elif x_type_out == b"1":
|
|
data = fp.read(8)
|
|
while data:
|
|
data_out = struct.unpack("l", data)[0]
|
|
data_list.append(data_out)
|
|
data = fp.read(8)
|
|
elif x_type_out == b"2":
|
|
data = fp.read(4)
|
|
while data:
|
|
data_out = struct.unpack("i", data)[0]
|
|
data_list.append(data_out)
|
|
data = fp.read(4)
|
|
else:
|
|
print("type error")
|
|
data_arr = np.array(data_list)
|
|
return data_arr
|
|
|
|
|
|
def get_alibi_slopes(num_heads):
|
|
closest_power_of_2 = 2 ** math.floor(math.log2(num_heads))
|
|
base = 2 ** (-(2 ** -(math.log2(closest_power_of_2) - 3)))
|
|
powers = np.arange(1, 1 + closest_power_of_2)
|
|
slopes = np.power(base, powers)
|
|
|
|
if closest_power_of_2 != num_heads:
|
|
extra_base = 2 ** (-(2 ** -(math.log2(2 * closest_power_of_2) - 3)))
|
|
num_remaining_heads = min(closest_power_of_2, num_heads - closest_power_of_2)
|
|
extra_powers = np.arange(1, 1 + 2 * num_remaining_heads, 2)
|
|
slopes = np.concatenate([slopes, np.power(extra_base, extra_powers)], axis=0)
|
|
|
|
return slopes.astype("float32")
|
|
|
|
|
|
def pad_batch_data(insts, masks=None, pad_id=0, return_seq_len=False, pad_style="right"):
|
|
"""Pad sequences to the max sequence length in batch."""
|
|
max_len = max(map(len, insts))
|
|
if pad_style == "left":
|
|
inst_data = np.array([[pad_id] * (max_len - len(inst)) + list(inst) for inst in insts])
|
|
else:
|
|
inst_data = np.array([list(inst) + [pad_id] * (max_len - len(inst)) for inst in insts])
|
|
|
|
if masks is not None:
|
|
if pad_style == "left":
|
|
inst_mask = np.array([[0] * (max_len - len(inst)) + list(inst) for inst in masks])
|
|
else:
|
|
inst_mask = np.array([list(inst) + [0] * (max_len - len(inst)) for inst in masks])
|
|
|
|
if return_seq_len:
|
|
seq_len = np.array([len(inst) for inst in insts])
|
|
if masks is None:
|
|
return inst_data.astype("int64").reshape([-1, max_len]), seq_len
|
|
else:
|
|
return (
|
|
inst_data.astype("int64").reshape([-1, max_len]),
|
|
inst_mask.astype("int64").reshape([-1, max_len]),
|
|
seq_len,
|
|
)
|
|
else:
|
|
return inst_data.astype("int64").reshape([-1, max_len])
|
|
|
|
|
|
def dybatch_preprocess(
|
|
tokenizer,
|
|
texts: list[str],
|
|
src_length: int,
|
|
max_length: int,
|
|
architectures: str,
|
|
top_p: float,
|
|
temperature: float,
|
|
eos_token_id: int | list[list[int]],
|
|
pre_caches_length: int = 0,
|
|
benchmark: bool = False,
|
|
pad_style: str = "None",
|
|
):
|
|
"""Pre-process generation inputs."""
|
|
inputs = {}
|
|
if "chatglmforcausallm" == architectures.lower():
|
|
input_ids = []
|
|
position_ids = []
|
|
|
|
for text in texts:
|
|
tokens = tokenizer(
|
|
text,
|
|
return_tensors="np",
|
|
padding=True,
|
|
max_length=src_length,
|
|
# if use chat_template, it will not add special_tokens
|
|
add_special_tokens=tokenizer.chat_template is None or isinstance(tokenizer, ChatGLMv2Tokenizer),
|
|
)
|
|
input_ids.append(tokens["input_ids"][0])
|
|
position_ids.append(tokens["position_ids"][0])
|
|
|
|
pad_token_id = tokenizer([tokenizer.pad_token], return_tensors="np")["input_ids"][0][0]
|
|
inputs["input_ids"], seq_len = pad_batch_data(input_ids, pad_id=pad_token_id, return_seq_len=True)
|
|
bs = inputs["input_ids"].shape[0]
|
|
max_len = max(map(len, input_ids))
|
|
|
|
inst_data_pos = []
|
|
for i in range(len(position_ids)):
|
|
inst_data_pos.append(np.array([list(inst) + [0] * (max_len - len(inst)) for inst in position_ids[i]]))
|
|
inputs["position_ids"] = paddle.to_tensor(np.array(inst_data_pos))
|
|
elif "gpt" in architectures:
|
|
input_ids = []
|
|
if isinstance(texts, str):
|
|
texts = [texts]
|
|
|
|
for text in texts:
|
|
tokens = tokenizer(
|
|
text,
|
|
return_tensors="np",
|
|
padding=False,
|
|
max_length=src_length,
|
|
return_attention_mask=False,
|
|
return_token_type_ids=False,
|
|
)
|
|
input_ids.append(tokens["input_ids"][0])
|
|
|
|
pad_token_id = tokenizer([tokenizer.pad_token], return_tensors="np")["input_ids"][0][-1]
|
|
inputs["input_ids"], seq_len = pad_batch_data(input_ids, pad_id=pad_token_id, return_seq_len=True)
|
|
bs = inputs["input_ids"].shape[0]
|
|
max_len = max(map(len, input_ids))
|
|
|
|
position_ids = paddle.arange(sum(seq_len), dtype="int64")
|
|
pre_len = seq_len[0]
|
|
for length in seq_len[1:]:
|
|
position_ids[pre_len : length + pre_len] = position_ids[pre_len : length + pre_len] - pre_len
|
|
pre_len += length
|
|
inputs["position_ids"] = position_ids
|
|
else:
|
|
input_ids = []
|
|
attention_mask = []
|
|
if isinstance(texts, str):
|
|
texts = [texts]
|
|
|
|
if pad_style == "left":
|
|
return_attention_mask = True
|
|
truncation = True
|
|
for text in texts:
|
|
tokens = tokenizer(
|
|
text,
|
|
return_tensors="np",
|
|
padding="max_length",
|
|
max_length=src_length,
|
|
truncation=truncation,
|
|
return_attention_mask=return_attention_mask,
|
|
return_token_type_ids=False,
|
|
add_special_tokens=tokenizer.chat_template is None or isinstance(tokenizer, ChatGLMv2Tokenizer),
|
|
)
|
|
input_ids.append(tokens["input_ids"][0])
|
|
attention_mask.append(tokens["attention_mask"][0])
|
|
|
|
pad_token_id = tokenizer([tokenizer.pad_token], return_tensors="np")["input_ids"][0][-1]
|
|
inputs["input_ids"], inputs["attention_mask"], seq_len = pad_batch_data(
|
|
input_ids, attention_mask, pad_id=pad_token_id, return_seq_len=True, pad_style=pad_style
|
|
)
|
|
bs = inputs["input_ids"].shape[0]
|
|
max_len = max(map(len, input_ids))
|
|
|
|
position_ids = paddle.zeros(shape=[bs, max_length + src_length], dtype="int64")
|
|
|
|
for i in range(bs):
|
|
position_ids[
|
|
i, pre_caches_length + max_len - seq_len[i] : pre_caches_length + max_len
|
|
] = paddle.arange(seq_len[i]).unsqueeze(axis=0)
|
|
seq_len[i] = max_len
|
|
inputs["position_ids"] = position_ids
|
|
else:
|
|
for text in texts:
|
|
tokens = tokenizer(
|
|
text,
|
|
return_tensors="np",
|
|
padding=False,
|
|
max_length=src_length,
|
|
return_attention_mask=False,
|
|
return_token_type_ids=False,
|
|
add_special_tokens=tokenizer.chat_template is None or isinstance(tokenizer, ChatGLMv2Tokenizer),
|
|
)
|
|
input_ids.append(tokens["input_ids"][0])
|
|
|
|
pad_token_id = tokenizer([tokenizer.pad_token], return_tensors="np")["input_ids"][0][-1]
|
|
inputs["input_ids"], seq_len = pad_batch_data(input_ids, pad_id=pad_token_id, return_seq_len=True)
|
|
bs = inputs["input_ids"].shape[0]
|
|
max_len = max(map(len, input_ids))
|
|
|
|
position_ids = paddle.zeros(shape=[bs, max_length + src_length], dtype="int64")
|
|
|
|
for i in range(bs):
|
|
position_ids[i, pre_caches_length : pre_caches_length + seq_len[i]] = paddle.arange(seq_len[i])
|
|
inputs["position_ids"] = position_ids
|
|
|
|
tgt_ids = [input[-1:] for input in input_ids]
|
|
tgt_pos = []
|
|
for i, valid_len in enumerate(map(len, input_ids)):
|
|
tgt_pos.append(valid_len - 1)
|
|
|
|
step_idx = [
|
|
0,
|
|
] * bs
|
|
tgt_pos = np.array(tgt_pos).astype("int64")
|
|
|
|
if isinstance(eos_token_id, int):
|
|
eos_token_id = [eos_token_id]
|
|
|
|
inputs["eos_token_id"] = np.array(eos_token_id * bs).reshape(-1, 1).astype("int64")
|
|
|
|
inputs["top_p"] = (
|
|
np.array(
|
|
[
|
|
top_p,
|
|
]
|
|
* bs
|
|
)
|
|
.reshape(-1, 1)
|
|
.astype("float32")
|
|
)
|
|
inputs["temperature"] = (
|
|
np.array(
|
|
[
|
|
temperature,
|
|
]
|
|
* bs
|
|
)
|
|
.reshape(-1, 1)
|
|
.astype("float32")
|
|
)
|
|
inputs["seq_len_encoder"] = seq_len.astype("int32").reshape(-1, 1)
|
|
inputs["seq_len_decoder"] = (seq_len + pre_caches_length).astype("int32").reshape(-1, 1)
|
|
inputs["step_idx"] = np.array(step_idx).astype("int64").reshape(-1, 1)
|
|
inputs["tgt_ids"] = np.array(tgt_ids).astype("int64").reshape(-1, 1)
|
|
inputs["tgt_pos"] = tgt_pos.reshape(-1, 1)
|
|
inputs["max_length"] = np.array(max_length - pre_caches_length).astype("int64").reshape((-1, 1))
|
|
inputs["min_length"] = (
|
|
np.array(
|
|
[
|
|
1
|
|
if not benchmark
|
|
else max_length
|
|
- pre_caches_length, # Note(Zhengzekang): When in benchmark mode, we need to set a fixed decode length.
|
|
]
|
|
* bs
|
|
)
|
|
.astype("int64")
|
|
.reshape((-1, 1))
|
|
)
|
|
inputs["penalty_score"] = (
|
|
np.array(
|
|
[
|
|
1.0,
|
|
]
|
|
* bs
|
|
)
|
|
.astype("float32")
|
|
.reshape((-1, 1))
|
|
)
|
|
inputs["frequency_score"] = (
|
|
np.array(
|
|
[
|
|
0.0,
|
|
]
|
|
* bs
|
|
)
|
|
.astype("float32")
|
|
.reshape((-1, 1))
|
|
)
|
|
inputs["presence_score"] = (
|
|
np.array(
|
|
[
|
|
0.0,
|
|
]
|
|
* bs
|
|
)
|
|
.astype("float32")
|
|
.reshape((-1, 1))
|
|
)
|
|
inputs["stop_flags"] = (
|
|
np.array(
|
|
[
|
|
0,
|
|
]
|
|
* bs
|
|
)
|
|
.astype("bool")
|
|
.reshape((-1, 1))
|
|
)
|
|
inputs["stop_nums"] = np.array([bs]).astype("int64")
|
|
return inputs
|
|
|
|
|
|
def load_real_time_tokens():
|
|
tokens = []
|
|
files = glob.glob(os.path.join("./real_time_save.*"))
|
|
for j in range(1, len(files) + 1):
|
|
filename = "./real_time_save.temp_ids_rank_0_step_{}".format(j)
|
|
if not os.path.exists(filename):
|
|
break
|
|
fp = open(filename, "rb+")
|
|
fp.read(1)
|
|
data_list = deserialize_from_file(fp)
|
|
fp.close()
|
|
tokens.append(np.array(data_list).reshape(-1, 1))
|
|
os.system("rm -f ./real_time_save.temp_ids_rank_*")
|
|
tokens = np.concatenate(tokens, axis=1)
|
|
return tokens
|
|
|
|
|
|
def init_chat_template(
|
|
tokenizer: PretrainedTokenizer, model_name_or_path: str, chat_template_file: Optional[str] = None
|
|
):
|
|
"""init chat template for the given tokenizer.
|
|
|
|
If is None, it will not use `chat_template.json`;
|
|
If is equal with `model_name_or_path`, it will use the default loading;
|
|
If is directory, it will find the `chat_template.json` under the directory;
|
|
If is file, it will load it.
|
|
|
|
Args:
|
|
tokenizer (PretrainedTokenizer): the instance of tokenizer
|
|
model_name_or_path (str): _description_
|
|
chat_template_file (Optional[str], optional): _description_. Defaults to None.
|
|
"""
|
|
# 1. use the default chat_template file
|
|
if chat_template_file is None:
|
|
return
|
|
|
|
if str(chat_template_file).lower() == "none":
|
|
# delete the chat_template from tokenizer if not use chat_template.
|
|
# why do this: it will load the `chat_template.json` file by default
|
|
tokenizer.chat_template = None
|
|
return
|
|
|
|
# it will load the `chat_template.json` file by default, so do nothing
|
|
if chat_template_file == model_name_or_path:
|
|
if tokenizer.chat_template is None:
|
|
logger.warning(f"there is not `chat_template.json` file in the `{model_name_or_path}`")
|
|
return
|
|
|
|
if os.path.isdir(chat_template_file):
|
|
local_chat_template_file_path = os.path.join(chat_template_file, "chat_template.json")
|
|
if os.path.exists(local_chat_template_file_path):
|
|
chat_template_file = local_chat_template_file_path
|
|
else:
|
|
logger.warning(f"there is not `chat_template.json` file in the `{model_name_or_path}`")
|
|
return
|
|
|
|
if not os.path.exists(chat_template_file):
|
|
logger.warning(f"there is not `chat_template.json` file from path<`{model_name_or_path}`>")
|
|
return
|
|
|
|
logger.info(f"loading `chat_template.json` from `{chat_template_file}`")
|
|
tokenizer.init_chat_template(chat_template_file)
|
|
|
|
|
|
def get_model_max_position_embeddings(config: PretrainedConfig) -> Optional[int]:
|
|
names = [
|
|
"max_position_embeddings", # most of models
|
|
"max_sequence_length", # GLM model
|
|
"seq_length", # llama model
|
|
]
|
|
for name in names:
|
|
max_length = config.get(name, None)
|
|
if max_length is not None:
|
|
return max_length
|
|
return None
|
|
|
|
|
|
def read_res(
|
|
model_name_or_path: str,
|
|
tensor_queue: mp.Queue,
|
|
result_queue: mp.Queue,
|
|
done_event: mp.Event,
|
|
):
|
|
from paddlenlp.utils.env import USE_FAST_TOKENIZER
|
|
|
|
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, padding_side="left", use_fast=USE_FAST_TOKENIZER)
|
|
|
|
paddle.device.set_device("cpu")
|
|
paddle.disable_static()
|
|
outputs = []
|
|
output_tensor = tensor_queue.get(timeout=1)
|
|
done_event.set()
|
|
logger.info("Start read result message")
|
|
logger.info(f"Current path is {os.getcwd()}")
|
|
|
|
from paddlenlp_ops import get_output
|
|
|
|
while True:
|
|
get_output(output_tensor, 0, True)
|
|
if int(output_tensor[0, 0]) == -2: # read none
|
|
continue
|
|
bsz = int(output_tensor[1, 0])
|
|
output_numpy = output_tensor[2 : bsz + 2].numpy()
|
|
output_numpy[output_numpy == -1] = tokenizer.eos_token_id
|
|
outputs.append(output_numpy)
|
|
if int(output_tensor[0, 0]) == -1:
|
|
break
|
|
output = np.concatenate(outputs, axis=1).tolist()
|
|
seqs = tokenizer.batch_decode(output, skip_special_tokens=True, clean_up_tokenization_spaces=False)
|
|
for i, (out, seq) in enumerate(zip(output, seqs)):
|
|
result_queue.put([i, out, seq])
|
|
|
|
logger.info("Finish read result message")
|
|
|
|
|
|
def read_res_dynamic_insert(
|
|
model_name_or_path: str,
|
|
task_queue: mp.Queue,
|
|
result_queue: mp.Queue,
|
|
done_event: mp.Event,
|
|
total_request_num: int,
|
|
detokenize: bool,
|
|
):
|
|
from paddlenlp.utils.env import USE_FAST_TOKENIZER
|
|
|
|
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, padding_side="left", use_fast=USE_FAST_TOKENIZER)
|
|
|
|
paddle.device.set_device("cpu")
|
|
paddle.disable_static()
|
|
|
|
outputs = [[] for _ in range(total_request_num)]
|
|
count = 0
|
|
|
|
done_event.set()
|
|
logger.info("Start read result dynamic insert")
|
|
|
|
while count < total_request_num:
|
|
try:
|
|
task_id, token_ids = task_queue.get(block=True, timeout=None)
|
|
|
|
if task_id < 0 or task_id >= total_request_num:
|
|
logger.warning(f"Invalid task ID received: {task_id}")
|
|
continue
|
|
|
|
if len(outputs[task_id]) == 0:
|
|
output_numpy = token_ids.reshape([1, -1])
|
|
output_numpy[output_numpy == -1] = tokenizer.eos_token_id
|
|
outputs[task_id] = output_numpy
|
|
count += 1
|
|
logger.info(f"Post-processing task {task_id} ({count}/{total_request_num})")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error processing task: {str(e)}")
|
|
continue
|
|
output = np.concatenate(outputs, axis=0).tolist()
|
|
if detokenize:
|
|
seqs = tokenizer.batch_decode(output, skip_special_tokens=True, clean_up_tokenization_spaces=False)
|
|
else:
|
|
seqs = [None] * len(output)
|
|
for i, (out, seq) in enumerate(zip(output, seqs)):
|
|
result_queue.put([i, out, seq])
|
|
logger.info("Finish read result message")
|
|
|
|
|
|
def speculate_read_res(
|
|
model_name_or_path: str,
|
|
tensor_queue: mp.Queue,
|
|
result_queue: mp.Queue,
|
|
done_event: mp.Event,
|
|
):
|
|
from paddlenlp.utils.env import USE_FAST_TOKENIZER
|
|
|
|
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, padding_side="left", use_fast=USE_FAST_TOKENIZER)
|
|
paddle.device.set_device("cpu")
|
|
paddle.disable_static()
|
|
outputs = []
|
|
from paddlenlp.utils.env import MAX_DRAFT_TOKENS, SPECULATE_MAX_BSZ
|
|
|
|
for _ in range(SPECULATE_MAX_BSZ):
|
|
outputs.append([])
|
|
output_tensor = tensor_queue.get(timeout=1)
|
|
done_event.set()
|
|
logger.info("Start speculate read result message")
|
|
logger.info(f"Current path is {os.getcwd()}")
|
|
|
|
from paddlenlp_ops import speculate_get_output
|
|
|
|
while True:
|
|
speculate_get_output(output_tensor, 0, True)
|
|
if int(output_tensor[0, 0]) == -2: # read none
|
|
continue
|
|
bsz = int(output_tensor[1])
|
|
accept_num = output_tensor[2 : bsz + 2].numpy()
|
|
for bi in range(bsz):
|
|
output_numpy = output_tensor[
|
|
2
|
|
+ SPECULATE_MAX_BSZ
|
|
+ bi * MAX_DRAFT_TOKENS : 2
|
|
+ SPECULATE_MAX_BSZ
|
|
+ bi * MAX_DRAFT_TOKENS
|
|
+ int(accept_num[bi]),
|
|
0,
|
|
].numpy()
|
|
output_numpy[output_numpy == -1] = tokenizer.eos_token_id
|
|
outputs[bi].extend(output_numpy.tolist())
|
|
if int(output_tensor[0, 0]) == -1:
|
|
break
|
|
|
|
seqs = tokenizer.batch_decode(outputs, skip_special_tokens=True, clean_up_tokenization_spaces=False)
|
|
for i, (out, seq) in enumerate(zip(outputs, seqs)):
|
|
result_queue.put([i, out, seq])
|
|
|
|
logger.info("Finish read result message")
|
|
|
|
|
|
def get_rotary_position_embedding(position_ids, head_dim, rope_theta=10000.0, rope_scaling: dict = None):
|
|
"""
|
|
Pre-calculate rotary position embedding for position_ids.
|
|
|
|
Args:
|
|
position_ids: [1, S]
|
|
head_dim: D
|
|
|
|
Returns:
|
|
rot_emb: [2, 1, S, 1, D], cos + sin
|
|
"""
|
|
bsz, max_seq_len = position_ids.shape[:2]
|
|
rot_emb = paddle.zeros((2, bsz, max_seq_len, 1, head_dim), dtype="float32")
|
|
inv_freq = rope_theta ** (-paddle.arange(0, head_dim, 2, dtype="float32") / head_dim)
|
|
|
|
if rope_scaling is not None:
|
|
rope_type = rope_scaling.get("rope_type", None)
|
|
if rope_type is not None and rope_type == "llama3":
|
|
factor = rope_scaling.get("factor", 8.0)
|
|
low_freq_factor = rope_scaling.get("low_freq_factor", 1.0)
|
|
high_freq_factor = rope_scaling.get("high_freq_factor", 4.0)
|
|
original_max_position_embeddings = rope_scaling.get("original_max_position_embeddings", 8192)
|
|
|
|
low_freq_wavelen = original_max_position_embeddings / low_freq_factor
|
|
high_freq_wavelen = original_max_position_embeddings / high_freq_factor
|
|
new_freqs = []
|
|
for freq in inv_freq:
|
|
wavelen = 2 * math.pi / freq
|
|
if wavelen < high_freq_wavelen:
|
|
new_freqs.append(freq)
|
|
elif wavelen > low_freq_wavelen:
|
|
new_freqs.append(freq / factor)
|
|
else:
|
|
assert low_freq_wavelen != high_freq_wavelen
|
|
smooth = (original_max_position_embeddings / wavelen - low_freq_factor) / (
|
|
high_freq_factor - low_freq_factor
|
|
)
|
|
new_freqs.append((1 - smooth) * freq / factor + smooth * freq)
|
|
inv_freq = paddle.to_tensor(new_freqs, dtype=inv_freq.dtype)
|
|
|
|
# shape: [B, S, D/2]
|
|
freqs = paddle.einsum("ij,k->ijk", position_ids.cast("float32"), inv_freq)
|
|
# shape: [B, S, 1, D]
|
|
emb = paddle.concat([freqs, freqs], axis=-1).reshape((bsz, max_seq_len, 1, head_dim))
|
|
|
|
rot_emb[0] = paddle.cos(emb)
|
|
rot_emb[1] = paddle.sin(emb)
|
|
return rot_emb
|
|
|
|
|
|
def init_dist_env():
|
|
"""
|
|
Initialize the distributed environment and obtain tensor parallel degree and rank.
|
|
|
|
Returns:
|
|
tuple: A tuple containing tensor parallel rank and degree.
|
|
"""
|
|
world_size = paddle.distributed.get_world_size() # Get the total number of distributed nodes
|
|
|
|
if world_size > 1:
|
|
is_fleet_init = True
|
|
try:
|
|
# Try to get the hybrid communicate group to check if Fleet has been initialized
|
|
hcg = fleet.get_hybrid_communicate_group()
|
|
except AttributeError:
|
|
is_fleet_init = False # Fleet has not been initialized
|
|
|
|
if is_fleet_init:
|
|
# If Fleet is already initialized, get tensor parallel degree and rank
|
|
tensor_parallel_degree = hcg.get_model_parallel_world_size()
|
|
tensor_parallel_rank = hcg.get_model_parallel_rank()
|
|
else:
|
|
# If Fleet is not initialized, set up the distributed strategy and initialize Fleet
|
|
strategy = fleet.DistributedStrategy()
|
|
strategy.hybrid_configs = {
|
|
"dp_degree": 1, # Data parallelism degree
|
|
"mp_degree": world_size, # Model parallelism degree (to be determined or set)
|
|
"pp_degree": 1, # Pipeline parallelism degree
|
|
"sharding_degree": 1, # Sharding parallelism degree
|
|
}
|
|
fleet.init(is_collective=True, strategy=strategy) # Initialize Fleet
|
|
hcg = fleet.get_hybrid_communicate_group() # Get the hybrid communicate group after initialization
|
|
|
|
# Get tensor parallel degree and rank after Fleet initialization
|
|
tensor_parallel_degree = hcg.get_model_parallel_world_size()
|
|
tensor_parallel_rank = hcg.get_model_parallel_rank()
|
|
else:
|
|
# If not in a distributed environment, set tensor parallel degree and rank to 1 and 0 respectively
|
|
tensor_parallel_degree = 1
|
|
tensor_parallel_rank = 0
|
|
|
|
return tensor_parallel_rank, tensor_parallel_degree
|
|
|
|
|
|
def get_eos_token_id(
|
|
tokenizer: PretrainedTokenizer, generation_config: Optional[GenerationConfig] = None
|
|
) -> List[List[int]]:
|
|
"""get eos_token_id from generation_config or tokenizer
|
|
|
|
Returns:
|
|
List[int]: eos_token_id to stop the generation
|
|
"""
|
|
eos_token_ids = []
|
|
if tokenizer.eos_token_id is not None:
|
|
eos_token_ids.append(tokenizer.eos_token_id)
|
|
|
|
if generation_config is not None and generation_config.eos_token_id is not None:
|
|
if isinstance(generation_config.eos_token_id, int):
|
|
eos_token_ids.append(generation_config.eos_token_id)
|
|
else:
|
|
eos_token_ids.extend(generation_config.eos_token_id)
|
|
|
|
eos_token_ids_dict = {str(item): item for item in eos_token_ids}
|
|
return list(eos_token_ids_dict.values())
|
|
|
|
|
|
def set_triton_cache(model_name_or_path, mode):
|
|
"""
|
|
Set triton cache.
|
|
"""
|
|
valid_modes = {"export", "static", "dynamic"}
|
|
if mode not in valid_modes:
|
|
raise ValueError(f"Invalid mode: {mode}. Valid modes are: {valid_modes}")
|
|
mp_id = paddle.distributed.get_rank()
|
|
triton_dir = f"triton_ops_rank_{mp_id}"
|
|
triton_kernel_cache_dir = f"{model_name_or_path}/{triton_dir}"
|
|
if mode == "export":
|
|
os.environ["TRITON_KERNEL_CACHE_DIR"] = triton_kernel_cache_dir
|
|
if os.path.exists(triton_kernel_cache_dir):
|
|
# del old triton_ops
|
|
shutil.rmtree(triton_kernel_cache_dir)
|
|
elif mode == "static":
|
|
os.environ["TRITON_KERNEL_CACHE_DIR"] = triton_kernel_cache_dir
|
|
for root, dirs, files in os.walk(triton_kernel_cache_dir):
|
|
for file in files:
|
|
if file.endswith("_package.so"):
|
|
so_full_path = os.path.join(root, file)
|
|
paddle.utils.cpp_extension.load_op_meta_info_and_register_op(so_full_path)
|
|
else:
|
|
os.environ["TRITON_KERNEL_CACHE_DIR"] = f"/root/.paddlenlp/{triton_dir}"
|