
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, TrainingArguments, Trainer, BitsAndBytesConfig, DataCollatorForSeq2Seq
from peft import LoraConfig, get_peft_model
from datasets import load_dataset
# Liberar Memoria Antes de Cargar el Modelo
torch.cuda.empty_cache()
torch.cuda.reset_peak_memory_stats()
# Ruta del modelo base (Phi-3-mini-4k-instruct)
model_path = "C:/Phi-3-mini-4k-instruct"
# Cuantización en 4-bit con `float32`
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float32,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4"
)
# Cargar el modelo con 4-bit y optimización de memoria
model = AutoModelForCausalLM.from_pretrained(
model_path,
device_map="auto",
quantization_config=bnb_config,
torch_dtype=torch.float32
)
# Cargar el tokenizador
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
# **Asegurar que `use_cache=False`**
model.config.use_cache = False
# Configurar LoRA con los módulos correctos de Phi-3-mini-4k
lora_config = LoraConfig(
r=8,
lora_alpha=32,
lora_dropout=0.1, #Dato de aleatoriedad, antes en 0.05
bias="none",
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
# Habilitar `gradient_checkpointing` para ahorro de memoria
model.gradient_checkpointing_disable()
# Asegurar que solo los parámetros de LoRA sean entrenables
for param in model.parameters():
param.requires_grad = False
# Activar solo los parámetros de LoRA
for name, param in model.named_parameters():
if "lora" in name or "adapter" in name:
param.requires_grad = True
# Asegurar que el modelo esté en modo entrenamiento
model.train()
# Verificar si hay parámetros entrenables
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
total_params = sum(p.numel() for p in model.parameters())
print(f" Parámetros entrenables: {trainable_params} / {total_params}")
if trainable_params == 0:
raise ValueError(" ERROR: No hay parámetros entrenables en el modelo.")
print(f" Modelo cargado en: {model.device}")
# Cargar dataset JSONL
dataset_path = r"C:/Finetuning/dataset.jsonl"
dataset = load_dataset("json", data_files=dataset_path)["train"]
# Verificar estructura del dataset antes de tokenizar
print("Ejemplo de dataset antes de tokenizar:", dataset[0])
# Función de Tokenización con Labels
def tokenize_function(examples):
if "prompt" not in examples or "completion" not in examples:
raise ValueError(" Error: El dataset no tiene los campos 'prompt' y 'completion'.")
combined_texts = [p + " " + c for p, c in zip(examples["prompt"], examples["completion"]) if p and c]
if not combined_texts:
raise ValueError(" Error: Algunos ejemplos en el dataset están vacíos o mal formateados.")
tokenized = tokenizer(combined_texts, truncation=True, padding="max_length", max_length=100)
# Agregar labels (necesarios para calcular loss)
tokenized["labels"] = tokenized["input_ids"].copy()
return tokenized
# Tokenizar dataset
tokenized_dataset = dataset.map(tokenize_function, batched=True)
# Configuración del Entrenamiento Optimizado
training_args = TrainingArguments(
output_dir="D:/finetuned_phi3",
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
save_strategy="epoch",
save_steps=50,
logging_dir="D:/logs",
logging_steps=5,
max_steps=500,
fp16=True,
evaluation_strategy="no",
push_to_hub=False,
learning_rate=5e-5 # Ajustado para mejor precisión
#learning_rate=1e-4
)
# **Añadir `data_collator` optimizado**
data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, padding="longest")
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset,
data_collator=data_collator
)
# Iniciar el Fine-Tuning
trainer.train()
# Guardar el modelo fine-tuneado
save_path = "C:/finetuned_phi3"
model.save_pretrained(save_path)
tokenizer.save_pretrained(save_path)
print(f" Modelo fine-tuneado guardado en: {save_path}")
Contenido
Explicación del código
El entrenamiento de modelos de lenguaje de gran tamaño puede ser un proceso costoso en términos de computación y memoria. Para optimizar este proceso, se pueden utilizar técnicas como la cuantización en 4-bit y el fine-tuning con LoRA (Low-Rank Adaptation). En este artículo explicaremos paso a paso cómo realizar el fine-tuning de Phi-3-mini-4k utilizando estas técnicas en una GPU con recursos limitados.
1. Introducción al Fine-Tuning con LoRA
LoRA es una técnica que permite entrenar solo un pequeño subconjunto de los parámetros de un modelo de lenguaje, en lugar de ajustar todos los pesos. Esto reduce significativamente el uso de memoria y el tiempo de entrenamiento, sin sacrificar la calidad de los resultados.
El objetivo de este tutorial es ajustar Phi-3-mini-4k en una GPU con cuantización en 4-bit, asegurando un consumo eficiente de VRAM.
2. Requisitos
Antes de comenzar, es necesario instalar las siguientes dependencias:
pip install torch transformers peft datasets
También es recomendable contar con una GPU NVIDIA compatible con cuantización en 4-bit.
3. Configuración del entorno
Para evitar problemas de memoria, lo primero que se hace es liberar la caché de la GPU:
import torch
torch.cuda.empty_cache()
torch.cuda.reset_peak_memory_stats()
4. Carga del modelo Phi-3-mini-4k con cuantización 4-bit
Para reducir el consumo de memoria, el modelo se carga con la configuración de BitsAndBytesConfig, que permite la cuantización en 4-bit.
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
model_path = "C:/Phi-3-mini-4k-instruct"
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float32,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4"
)
model = AutoModelForCausalLM.from_pretrained(
model_path,
device_map="auto",
quantization_config=bnb_config,
torch_dtype=torch.float32
)
Esta configuración permite que el modelo funcione con una menor cantidad de VRAM sin perder precisión.
5. Carga del tokenizador
El tokenizador es necesario para convertir el texto en tokens que el modelo puede interpretar.
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
6. Configuración de LoRA
Se configura LoRA para entrenar únicamente los módulos más relevantes del modelo, reduciendo el consumo de memoria y mejorando la eficiencia del entrenamiento.
from peft import LoraConfig, get_peft_model
lora_config = LoraConfig(
r=8,
lora_alpha=32,
lora_dropout=0.1,
bias="none",
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
Se han seleccionado los módulos clave de Phi-3-mini-4k para optimizar el entrenamiento.
7. Congelación de parámetros
Para asegurar que solo los parámetros de LoRA sean entrenables, se congelan los demás parámetros del modelo.
for param in model.parameters():
param.requires_grad = False
for name, param in model.named_parameters():
if "lora" in name or "adapter" in name:
param.requires_grad = True
Esto reduce significativamente la cantidad de parámetros que necesitan ser ajustados.
8. Carga del dataset
El dataset debe estar en formato JSONL con las claves prompt y completion. Se carga con la librería datasets.
from datasets import load_dataset
dataset_path = r"C:/Finetuning/dataset.jsonl"
dataset = load_dataset("json", data_files=dataset_path)["train"]
print("Ejemplo de dataset antes de tokenizar:", dataset[0])
Un ejemplo de estructura del dataset:
{"prompt": "¿Qué es la inteligencia artificial?", "completion": "La inteligencia artificial es la simulación de procesos humanos por parte de máquinas."}
9. Tokenización del dataset
Antes de entrenar, es necesario tokenizar los datos y preparar los labels para la función de pérdida.
def tokenize_function(examples):
if "prompt" not in examples or "completion" not in examples:
raise ValueError("Error: El dataset no tiene los campos 'prompt' y 'completion'.")
combined_texts = [p + " " + c for p, c in zip(examples["prompt"], examples["completion"]) if p and c]
if not combined_texts:
raise ValueError("Error: Algunos ejemplos en el dataset están vacíos o mal formateados.")
tokenized = tokenizer(combined_texts, truncation=True, padding="max_length", max_length=100)
tokenized["labels"] = tokenized["input_ids"].copy()
return tokenized
tokenized_dataset = dataset.map(tokenize_function, batched=True)
10. Configuración del entrenamiento
Se establecen los parámetros de entrenamiento para asegurar un proceso eficiente.
from transformers import TrainingArguments, Trainer, DataCollatorForSeq2Seq
training_args = TrainingArguments(
output_dir="D:/finetuned_phi3",
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
save_strategy="epoch",
save_steps=50,
logging_dir="D:/logs",
logging_steps=5,
max_steps=500,
fp16=True,
evaluation_strategy="no",
push_to_hub=False,
learning_rate=5e-5
)
11. Entrenamiento del modelo
El entrenamiento se ejecuta con el objeto Trainer.
data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, padding="longest")
trainer = Trainer(
model=model,
args=training_args,
train_dataset=tokenized_dataset,
data_collator=data_collator
)
trainer.train()
12. Guardado del modelo fine-tuneado
Una vez finalizado el entrenamiento, se guarda el modelo ajustado.
save_path = "C:/finetuned_phi3"
model.save_pretrained(save_path)
tokenizer.save_pretrained(save_path)
print(f"Modelo fine-tuneado guardado en: {save_path}")
Conclusión
Este proceso ha permitido entrenar Phi-3-mini-4k con un consumo reducido de VRAM utilizando:
- Cuantización en 4-bit para optimizar memoria.
- Fine-tuning con LoRA para reducir los parámetros ajustables.
- Uso de Trainer para gestionar el entrenamiento de manera eficiente.
Este método es ideal para ajustar modelos de lenguaje en equipos con hardware limitado, manteniendo un rendimiento óptimo.
Deja una respuesta Cancelar la respuesta