File size: 12,142 Bytes
5d0eaa3 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
#!/usr/bin/env python3
import sys
import os
# Passo 1: Parsear os argumentos de linha de comando antecipadamente para definir variáveis de ambiente
# Antes de importar transformers, parseamos os argumentos que podem afetar a configuração do modelo
def early_parse_args():
import argparse
parser = argparse.ArgumentParser(description="Scorer de Qualidade para Datasets do Hugging Face (Early Parsing)")
parser.add_argument(
"--4bit",
action="store_true",
dest="fourbit",
help="Carrega o modelo em precisão de 4 bits utilizando BitsAndBytes."
)
# Parse apenas os argumentos necessários para BitsAndBytes
args, _ = parser.parse_known_args()
return args
early_args = early_parse_args()
# Definir a variável de ambiente antes de importar transformers, se --4bit for usado
if early_args.fourbit:
os.environ["LLM_INT8_ENABLE_FP32_CPU_OFFLOAD"] = "true"
# Passo 2: Importar as bibliotecas restantes após definir variáveis de ambiente
import argparse
import json
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
import numpy as np
from scipy.special import softmax
from datasets import load_dataset
import pyarrow.parquet as pq
from tqdm import tqdm
import torch
def infer_quality(model, tokenizer, input_text, resp_text, device):
"""
Calcula a pontuação de qualidade para uma resposta baseada na entrada e na resposta fornecida.
Args:
model: O modelo de linguagem causal carregado.
tokenizer: O tokenizer correspondente ao modelo.
input_text (str): O texto da instrução/pergunta.
resp_text (str): O texto da resposta a ser avaliada.
device: O dispositivo (CPU ou GPU) para realizar os cálculos.
Returns:
float: A pontuação de qualidade calculada.
"""
# Template traduzido para português
quality_template = (
"Você é um assistente útil. Por favor, identifique a pontuação de qualidade da Resposta correspondente à Pergunta.\n"
"#Pergunta#:\n{instruction}\n"
"#Resposta#:\n{output}\n"
"##Qualidade: "
)
# Formatar o input do usuário
user_input = quality_template.format(instruction=input_text, output=resp_text)
# Tokenizar a entrada
input_ids = tokenizer.encode(user_input, return_tensors="pt").to(device)
max_length = 512 # Definir comprimento máximo
# Gerar a resposta do modelo
with torch.no_grad():
outputs = model.generate(
input_ids,
max_length=max_length,
num_return_sequences=1,
return_dict_in_generate=True,
output_scores=True,
do_sample=False # Desativar amostragem para obter logits determinísticos
)
# Extrair os scores das saídas
scores = outputs.scores # Tuple of tensors, one for each generated token
score_logits = []
# Mapeamento de IDs de tokens para pontuações
id2score = {
29896: "1", # Token ID para "1"
29906: "2", # Token ID para "2"
29941: "3", # Token ID para "3"
29946: "4", # Token ID para "4"
29945: "5", # Token ID para "5"
29953: "6" # Token ID para "6"
}
score_template = np.array([1, 2, 3, 4, 5, 6])
# Verificar se há scores gerados
if not scores:
return 0.0 # Retorna 0 se não houver scores
# Assumindo que queremos a última posição gerada
last_score = scores[-1] # Último conjunto de logits, shape: (batch_size * num_beams, vocab_size)
# Garantir que last_score tenha o formato esperado
if last_score.ndim != 2 or last_score.size(0) != 1:
print("Formato inesperado para 'last_score'.")
return 0.0
last_score = last_score[0] # Shape: (vocab_size,)
# Extrair logits para os token_ids específicos
for token_id in id2score:
if token_id < last_score.size(0):
logit = last_score[token_id].item()
score_logits.append(logit)
else:
# Se o token_id estiver fora do alcance, atribuir um valor muito baixo
score_logits.append(-1e10)
score_logits = np.array(score_logits)
# Aplicar softmax para obter probabilidades
score_probs = softmax(score_logits)
# Calcular a pontuação ponderada
quality_score = np.sum(score_probs * score_template)
return quality_score
def process_dataset(model, tokenizer, dataset_repo, input_field, output_field, output_dir, device):
"""
Processa um dataset específico, calculando a pontuação de qualidade para cada exemplo e salvando o resultado.
Args:
model: O modelo de linguagem causal carregado.
tokenizer: O tokenizer correspondente ao modelo.
dataset_repo (str): O repositório do dataset no Hugging Face.
input_field (str): O nome do campo de entrada no dataset.
output_field (str): O nome do campo de resposta no dataset.
output_dir (str): O diretório onde os datasets processados serão salvos.
device: O dispositivo (CPU ou GPU) para realizar os cálculos.
"""
print(f"\nProcessando dataset: {dataset_repo}")
try:
dataset = load_dataset(dataset_repo)
except Exception as e:
print(f"Erro ao carregar o dataset {dataset_repo}: {e}")
return
# Considerar apenas splits que contêm dados (por exemplo, 'train', 'validation', 'test')
for split in dataset.keys():
split_data = dataset[split]
if len(split_data) == 0:
continue
print(f" Processando split: {split} com {len(split_data)} exemplos")
# Preparar listas para armazenar os scores
quality_scores = []
# Iterar sobre os exemplos com tqdm para visualizar o progresso
for example in tqdm(split_data, desc=f" Avaliando {split}"):
input_text = example.get(input_field, "")
output_text = example.get(output_field, "")
if not isinstance(input_text, str) or not isinstance(output_text, str):
quality_scores.append(0.0)
continue
score = infer_quality(model, tokenizer, input_text, output_text, device)
quality_scores.append(score)
# Adicionar a nova coluna ao dataset
split_data = split_data.add_column("quality_score", quality_scores)
# Definir o caminho de salvamento
dataset_name = dataset_repo.split('/')[-1]
split_output_dir = os.path.join(output_dir, dataset_name)
os.makedirs(split_output_dir, exist_ok=True)
output_path = os.path.join(split_output_dir, f"{split}.parquet")
# Salvar o dataset como .parquet
try:
split_data.to_parquet(output_path)
print(f" Salvado em {output_path}")
except Exception as e:
print(f" Erro ao salvar {output_path}: {e}")
def verify_token_ids(tokenizer, id2score):
"""
Verifica se os token_ids mapeados correspondem aos tokens corretos.
Args:
tokenizer: O tokenizer correspondente ao modelo.
id2score (dict): Mapeamento de token_id para pontuação.
"""
print("Verificando mapeamento de token IDs:")
for token_id, label in id2score.items():
try:
token = tokenizer.convert_ids_to_tokens(token_id)
print(f"Token ID {token_id}: '{token}' -> {label}")
except Exception as e:
print(f"Erro ao converter token_id {token_id}: {e}")
def main():
parser = argparse.ArgumentParser(description="Scorer de Qualidade para Datasets do Hugging Face")
parser.add_argument(
"--datasets",
type=str,
required=True,
help='Lista de datasets no formato JSON. Exemplo: \'[{{ "repo": "orion-research/gsmqnaoa-pt_BR", "input": "INSTRUCTION", "output": "RESPONSE" }}, {{ "repo": "orion-research/Aura-CoT-Multilang-v1", "input": "input", "output": "output" }}]\''
)
parser.add_argument(
"--output",
type=str,
required=True,
help="Diretório de saída onde os datasets processados serão salvos."
)
parser.add_argument(
"--cpu",
action="store_true",
help="Força o uso da CPU em vez da GPU."
)
parser.add_argument(
"--4bit",
action="store_true",
dest="fourbit",
help="Carrega o modelo em precisão de 4 bits utilizando BitsAndBytes."
)
args = parser.parse_args()
# Parsear o argumento datasets
try:
datasets_list = json.loads(args.datasets)
if not isinstance(datasets_list, list):
raise ValueError("O argumento --datasets deve ser uma lista de objetos JSON.")
except json.JSONDecodeError as e:
print(f"Erro ao parsear o argumento --datasets: {e}")
sys.exit(1)
except ValueError as ve:
print(ve)
sys.exit(1)
# Verificar se o diretório de saída existe, senão criar
if not os.path.exists(args.output):
try:
os.makedirs(args.output)
print(f"Criado diretório de saída: {args.output}")
except Exception as e:
print(f"Erro ao criar o diretório de saída {args.output}: {e}")
sys.exit(1)
# Determinar o dispositivo a ser usado
if args.cpu:
device = torch.device("cpu")
print("Forçando o uso da CPU.")
else:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Usando dispositivo: {device}")
# Carregar modelo e tokenizer
model_name = "/ors/models/LLM/Orion-Quality-Scorer"
print("Carregando tokenizer e modelo...")
try:
if args.fourbit:
# Verificar se bitsandbytes está instalado
try:
import bitsandbytes as bnb
except ImportError:
print("Erro: bitsandbytes não está instalado. Instale com 'pip install bitsandbytes'.")
sys.exit(1)
from transformers import BitsAndBytesConfig
# Configuração para 4-bit
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.float16
)
# Definir o device_map para permitir offloading
device_map = "auto" if not args.cpu else {"": "cpu"}
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=bnb_config,
device_map=device_map,
trust_remote_code=True # Se o modelo requer código remoto
)
else:
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)
model.to(device)
model.eval()
print("Modelo e tokenizer carregados com sucesso.")
# Verificar mapeamento de token IDs
verify_token_ids(tokenizer, {
29896: "1",
29906: "2",
29941: "3",
29946: "4",
29945: "5",
29953: "6"
})
except Exception as e:
print(f"Erro ao carregar o modelo ou tokenizer: {e}")
sys.exit(1)
# Processar cada dataset
for dataset_info in datasets_list:
repo = dataset_info.get("repo")
input_field = dataset_info.get("input")
output_field = dataset_info.get("output")
if not repo or not input_field or not output_field:
print(f"Informações incompletas para o dataset: {dataset_info}. Pulando...")
continue
process_dataset(model, tokenizer, repo, input_field, output_field, args.output, device)
print("\nProcessamento concluído.")
if __name__ == "__main__":
main()
|