Ajustar o Llama 3.2 1B com o LoRA usando o AI Runtime

Este notebook demonstra como aperfeiçoar um modelo de linguagem grande usando o SFT (ajuste fino supervisionado) com LoRA (adaptação Low-Rank) no Databricks AI Runtime. O notebook usa a biblioteca TRL (Transformers Reinforcement Learning) com otimização do DeepSpeed ZeRO Stage 3 para treinar com eficiência o Llama 3.2 1B em um único nó com 8 GPUs H100.

Principais conceitos:

  • LoRA (Adaptação de Baixa Classificação): uma técnica de ajuste fino eficiente em parâmetros que reduz o número de parâmetros treináveis adicionando pequenas matrizes treináveis de decomposição de baixa classificação às camadas do modelo.
  • TRL (Aprendizado de Reforço em Transformadores): uma biblioteca que fornece ferramentas para treinamento de modelos de linguagem com aprendizado de reforço e ajuste fino supervisionado.
  • DeepSpeed ZeRO Stage 3: uma técnica de otimização de memória que particiona parâmetros de modelo, gradientes e estados de otimizador entre GPUs para habilitar o treinamento de modelos grandes.
  • AI Runtime: computação de GPU gerenciada pelo Databricks que provisiona e dimensiona automaticamente os recursos de GPU para cargas de trabalho de treinamento.

Para obter mais informações, consulte AI Runtime.

Requisitos

Este notebook requer o seguinte:

  • AI Runtime: o notebook usa o Databricks AI Runtime com 8 GPUs H100 para treinamento distribuído. Nenhuma configuração de cluster é necessária.
  • Catálogo do Unity: um catálogo e um esquema do Catálogo do Unity para armazenar pontos de verificação de modelo e registrar o modelo treinado.
  • Token HuggingFace: um token de acesso do HuggingFace armazenado nos segredos do Databricks para baixar o modelo base e o conjunto de dados.
  • Pacotes do Python: os pacotes necessários (peft, trl, deepspeed, mlflow, hf_transfer) são instalados na seção de instalação abaixo.

Instalar pacotes necessários

Instale os pacotes do Python necessários para ajuste fino:

  • peft: fornece a implementação de LoRA para ajuste fino eficiente de parâmetros
  • trl: Biblioteca de Aprendizado de Reforço Transformers para ajuste fino supervisionado
  • deepspeed: habilita o treinamento distribuído eficiente em termos de memória com a otimização ZeRO
  • mlflow: Rastreia experimentos e registra modelos treinados
  • hf_transfer: acelera os downloads de modelos do Hugging Face Hub

Após a instalação, reinicie o kernel do Python para garantir que todos os pacotes sejam carregados corretamente.

%pip install --upgrade transformers==4.56.1
%pip install peft==0.17.1
%pip install trl==0.18.1
%pip install deepspeed>=0.15.4
%pip install mlflow>=3.6.0
%pip install hf_transfer==0.1.9
%restart_python

Configurar o Catálogo do Unity e variáveis de ambiente

Configurar locais no Unity Catalog para armazenar pontos de verificação do modelo e registrar o modelo treinado. O notebook utiliza parâmetros de consulta para configurar:

  • Catálogo e esquema: namespace do Catálogo do Unity para organizar modelos e pontos de verificação
  • Nome do modelo: nome do modelo registrado no Catálogo do Unity
  • Volume: Volume do Catálogo do Unity para armazenar pontos de verificação de modelo durante o treinamento

A configuração também recupera o token HuggingFace dos segredos do Databricks e configura o experimento do MLflow para acompanhar as métricas de treinamento.

dbutils.widgets.text("uc_catalog", "main")
dbutils.widgets.text("uc_schema", "default")
dbutils.widgets.text("uc_model_name", "llama3_2-1b")
dbutils.widgets.text("uc_volume", "checkpoints")

UC_CATALOG = dbutils.widgets.get("uc_catalog")
UC_SCHEMA = dbutils.widgets.get("uc_schema")
UC_MODEL_NAME = dbutils.widgets.get("uc_model_name")
UC_VOLUME = dbutils.widgets.get("uc_volume")

# Get HuggingFace token and username
hf_token = dbutils.secrets.get(scope="sgc-nightly-notebook", key="hf_token")
username = spark.sql("SELECT session_user()").collect()[0][0]

REGISTERED_MODEL_NAME = f"{UC_CATALOG}.{UC_SCHEMA}.{UC_MODEL_NAME}"
CHECKPOINT_DIR = f"/Volumes/{UC_CATALOG}/{UC_SCHEMA}/{UC_VOLUME}/{UC_MODEL_NAME}"
FINE_TUNED_MODEL_PATH = f"{CHECKPOINT_DIR}/fine-tuned-peft-model"
MLFLOW_EXPERIMENT_NAME = f"/Users/{username}/{UC_MODEL_NAME}"

# Create the Unity Catalog volume if it doesn't exist
spark.sql(f"CREATE VOLUME IF NOT EXISTS {UC_CATALOG}.{UC_SCHEMA}.{UC_VOLUME}")

print(f"👤 Username: {username}")
print("🔑 HuggingFace token configured")
print(f"UC_CATALOG: {UC_CATALOG}")
print(f"UC_SCHEMA: {UC_SCHEMA}")
print(f"UC_MODEL_NAME: {UC_MODEL_NAME}")
print(f"UC_VOLUME: {UC_VOLUME}")
print(f"CHECKPOINT_DIR: {CHECKPOINT_DIR}")
print(f"MLFLOW_EXPERIMENT_NAME: {MLFLOW_EXPERIMENT_NAME}")

import os
import json
import tempfile
import torch
import mlflow
from huggingface_hub import constants
from datasets import load_dataset
from transformers import AutoTokenizer, AutoModelForCausalLM
from trl import SFTTrainer, SFTConfig, ModelConfig, ScriptArguments, setup_chat_format
from peft import LoraConfig, get_peft_model, PeftModel

if mlflow.get_experiment_by_name(MLFLOW_EXPERIMENT_NAME) is None:
    mlflow.create_experiment(name=MLFLOW_EXPERIMENT_NAME)
mlflow.set_experiment(MLFLOW_EXPERIMENT_NAME)

Criar configuração do DeepSpeed ZeRO Stage 3

O DeepSpeed ZeRO (Otimizador de Redundância Zero) Estágio 3 particiona parâmetros de modelo, gradientes e estados de otimizador em todas as GPUs para reduzir o consumo de memória por GPU. Isso permite o treinamento de modelos grandes que não caberiam na memória de uma única GPU.

Configurações principais de configuração

  • bf16 habilitado: usa a precisão bfloat16 para treinamento mais rápido e uso reduzido de memória
  • Otimização do estágio 3: particiona todos os estados de modelo entre GPUs
  • Sem descarregamento de CPU: mantém todos os dados em GPUs para o desempenho máximo no hardware H100
  • Comunicação de sobreposição: para maior eficiência, sobrepõe a comunicação de gradiente com a computação
def create_deepspeed_config():
    """Create DeepSpeed ZeRO Stage 3 configuration for single node A10 training."""

    deepspeed_config = {
        "fp16": {
            "enabled": False
        },
        "bf16": {
            "enabled": True
        },
        "zero_optimization": {
            "stage": 3,
            "offload_optimizer": {
                "device": "none"
            },
            "offload_param": {
                "device": "none"
            },
            "overlap_comm": True,
            "contiguous_gradients": True,
            "sub_group_size": 1e9,
            "reduce_bucket_size": "auto",
            "stage3_prefetch_bucket_size": "auto",
            "stage3_param_persistence_threshold": "auto",
            "stage3_max_live_parameters": 1e9,
            "stage3_max_reuse_distance": 1e9,
            "stage3_gather_16bit_weights_on_model_save": True
        },
        "gradient_accumulation_steps": 1,
        "gradient_clipping": "auto",
        "steps_per_print": 2000,
        "train_batch_size": "auto",
        "train_micro_batch_size_per_gpu": "auto",
        "wall_clock_breakdown": False
    }

    return deepspeed_config


# Create DeepSpeed configuration
deepspeed_config = create_deepspeed_config()
print("⚙️  DeepSpeed ZeRO Stage 3 configuration created")

Definir parâmetros de treinamento e configuração do LoRA

Configure os parâmetros de ajuste fino supervisionados:

  • Modelo: Llama 3.2 1B Instrução, um modelo compacto adequado para GPUs H100
  • Conjunto de dados: Conjunto de dados Capybara da biblioteca TRL para treinamento de IA conversacional
  • Tamanho do lote: 2 por dispositivo com 4 etapas de acúmulo de gradiente para o tamanho efetivo do lote de 64
  • Taxa de aprendizagem: 2e-4 com agendador de cosseno e aquecimento inicial
  • Etapas de treinamento: 60 etapas para demonstração (aumento para treinamento completo)
  • Parâmetros de LoRA: Rank 16 com alfa 32, focando na atenção e nas camadas de projeção MLP

A configuração usa precisão bfloat16 e ponto de verificação de gradiente para otimizar o uso de memória.

def create_training_config():
    """Create training configuration for TRL SFT with LoRA."""

    # Model and dataset configuration (not part of TrainingArguments)
    model_config = {
        "model_name": "meta-llama/Llama-3.2-1B-Instruct",  # Small Llama model for A10
        "dataset_name": "trl-lib/Capybara"
    }

    # Training arguments that will be passed directly to TrainingArguments
    training_args_config = {
        "output_dir": CHECKPOINT_DIR,
        "per_device_train_batch_size": 2,
        "per_device_eval_batch_size": 2,
        "gradient_accumulation_steps": 1,
        "learning_rate": 2e-4,
        "max_steps": 60,   # TO DO remove when fine-tuning on full dataset. Demo purposes only.
        # "num_train_epochs": 1, # TO DO update to >= 1 when fine-tuning on full dataset
        "logging_steps": 10,
        "save_steps": 30,
        "eval_steps": 30,
        "eval_strategy": "steps",
        "warmup_steps": 10,
        "lr_scheduler_type": "cosine",
        "gradient_checkpointing": False,
        "fp16": False,
        "bf16": True,
        "optim": "adamw_torch",
        "remove_unused_columns": False,
        "run_name": f"llama3.2-1b-lora",
        "report_to": "mlflow",
        "save_total_limit": 2,
        "load_best_model_at_end": True,
        "metric_for_best_model": "eval_loss",
        "greater_is_better": False,
    }

    # LoRA configuration
    lora_config = {
        "r": 16,
        "lora_alpha": 32,
        "target_modules": ["q_proj", "v_proj", "k_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
        "lora_dropout": 0.1,
        "bias": "none",
        "task_type": "CAUSAL_LM"
    }

    return model_config, training_args_config, lora_config

# Create training configuration
model_config, training_args_config, lora_config = create_training_config()

print("📊 Training Configuration:")
print(f"  🤖 Model: {model_config['model_name']}")
print(f"  📚 Dataset: {model_config['dataset_name']}")
print(f"  🎯 Batch size: {training_args_config['per_device_train_batch_size']}")
print(f"  📈 Learning rate: {training_args_config['learning_rate']}")
print(f"  🧠 LoRA rank: {lora_config['r']}")

Definir a função de treinamento distribuído

O decorador @distributed da biblioteca serverless_gpu permite a execução perfeita de cargas de trabalho de GPU no Databricks AI Runtime. O decorador provisiona 8 GPUs H100 e manipula automaticamente a configuração de treinamento distribuído.

Parâmetros de chave:

  • gpus=8: Solicita 8 GPUs para treinamento distribuído
  • gpu_type='H100': especifica o hardware da GPU H100

A função de treinamento:

  1. Carrega o modelo base e o tokenizador do HuggingFace
  2. Configura a formatação de chat para IA conversacional
  3. Configura o LoRA para ajuste fino com eficiência de parâmetro
  4. Carrega o conjunto de dados de treinamento
  5. Inicializa o TRL SFTTrainer com otimização DeepSpeed
  6. Treina o modelo e salva pontos de verificação
  7. Retorna resultados de treinamento e ID de execução do MLflow

Para obter mais informações, consulte a documentação da API de Runtime de IA.

from serverless_gpu import distributed

os.environ['MLFLOW_EXPERIMENT_NAME'] = MLFLOW_EXPERIMENT_NAME
@distributed(
    gpus=8,
    gpu_type='H100',
)
def run_distributed_trl_sft():
    """
    Distributed TRL SFT training function using AI Runtime.

    This function will be executed on the H100 GPU with DeepSpeed optimization.
    """

    # Set up environment variables for remote jobs
    import os
    import tempfile
    import json
    from huggingface_hub import constants
    from datasets import load_dataset
    from transformers import AutoTokenizer, AutoModelForCausalLM, TrainingArguments
    from trl import SFTTrainer, setup_chat_format
    from peft import LoraConfig, get_peft_model

    # HuggingFace configuration
    os.environ["HUGGING_FACE_HUB_TOKEN"] = hf_token
    os.environ['HF_TOKEN'] = hf_token
    constants.HF_HUB_ENABLE_HF_TRANSFER = True

    # Set up temporary directories
    temp_dir = tempfile.mkdtemp()

    print("🚀 Starting TRL SFT training on H100 GPU...")

    try:
        # Load tokenizer and model
        print(f"📥 Loading model: {model_config['model_name']}")
        tokenizer = AutoTokenizer.from_pretrained(model_config['model_name'])

        # Add pad token if not present
        if tokenizer.pad_token is None:
            tokenizer.pad_token = tokenizer.eos_token

        model = AutoModelForCausalLM.from_pretrained(
            model_config['model_name'],
            dtype="auto"
        )

        # Setup chat template if the model doesn't have one
        # This is crucial for conversational AI models and TRL SFTTrainer
        if tokenizer.chat_template is None:
            print("🗨️  Setting up chat template...")
            model, tokenizer = setup_chat_format(model, tokenizer, format="chatml")

        # Configure LoRA
        print("🔧 Setting up LoRA configuration...")
        peft_config = LoraConfig(**lora_config)

        # Load dataset
        print(f"📚 Loading dataset: {model_config['dataset_name']}")
        dataset = load_dataset(model_config['dataset_name'])

        # Create temporary DeepSpeed config file
        deepspeed_config_path = os.path.join(temp_dir, "deepspeed_config.json")
        with open(deepspeed_config_path, "w") as f:
            json.dump(deepspeed_config, f, indent=2)

        # Training arguments - dynamically pass all config parameters
        training_args = TrainingArguments(
            **training_args_config,
            deepspeed=deepspeed_config_path,  # Override deepspeed with the config file path
        )

        # Initialize SFT Trainer
        print("🏋️ Initializing SFT Trainer with DeepSpeed...")
        trainer = SFTTrainer(
            model=model,
            args=training_args,
            train_dataset=dataset["train"],
            eval_dataset=dataset["test"] if "test" in dataset else None,
            processing_class=tokenizer,
            peft_config=peft_config
        )

        # Start training
        print("🎯 Starting training...")
        trainer.train()

        # Save the model
        print("💾 Saving trained model...")
        trainer.save_model()

        # Get training results
        train_results = trainer.state.log_history
        final_loss = train_results[-1].get('train_loss', 'N/A') if train_results else 'N/A'

        print("✅ Training completed successfully!")
        print(f"📊 Final training loss: {final_loss}")

        mlflow_run_id = None
        if mlflow.last_active_run() is not None:
            mlflow_run_id = mlflow.last_active_run().info.run_id

        return {
            "status": "success",
            "final_loss": final_loss,
            "output_dir": training_args_config['output_dir'],
            "model_name": model_config['model_name'],
            "mlflow_run_id": mlflow_run_id,
        }

    except Exception as e:
        print(f"❌ Training failed: {e}")
        import traceback
        traceback.print_exc()
        return {
            "status": "failed",
            "error": str(e)
        }

Executar o trabalho de treinamento distribuído

Execute a função de treinamento ao chamar a função decorada .distributed(). Isso provisiona os recursos do AI Runtime, executa o treinamento em 8 GPUs H100 com otimização de DeepSpeed e retorna os resultados.

O processo de treinamento:

  • Provisiona automaticamente 8 GPUs H100
  • Baixa o modelo e o conjunto de dados do HuggingFace
  • Treina o modelo com ajuste fino do LoRA
  • Salva pontos de verificação no volume do Unity Catalog
  • Registra métricas no MLflow
  • Retorna status de treinamento, perda final e ID de execução do MLflow
# Execute the distributed training
results = run_distributed_trl_sft.distributed()

print("🏁 Training execution completed!")
print(f"📊 Results: {results}")

if results and results[0].get('status') == 'success':
    print("✅ Training completed successfully!")
    print(f"💾 Model saved to: {results[0].get('output_dir', 'N/A')}")
    print(f"📈 Final loss: {results[0].get('final_loss', 'N/A')}")
    print(f"🎉 MLflow run ID: {results[0].get('mlflow_run_id', 'N/A')}")
else:
    print("❌ Training failed!")
    if results and 'error' in results:
        print(f"🔍 Error: {results['error']}")

Salvar o modelo ajustado e a inferência de teste

Esta etapa opcional carrega o adaptador LoRA treinado, mescla-o com o modelo base e salva o modelo ajustado completo. O modelo mesclado pode então ser testado com prompts de exemplo para verificar os resultados de ajuste fino.

O processo:

  1. Carrega o modelo base Llama 3.2 1B
  2. Aplica os pesos treinados do adaptador LoRA
  3. Mescla o adaptador no modelo base
  4. Salva o modelo mesclado no volume do Catálogo do Unity
  5. Testa o modelo com um prompt de conversação de exemplo
%pip install hf_transfer
def save_and_load_trained_model():
    """Load the trained model from the Unity Catalog volume."""

    import torch
    from transformers import AutoModelForCausalLM, AutoTokenizer
    from peft import PeftModel

    # Load base model and tokenizer
    base_model = AutoModelForCausalLM.from_pretrained(
        model_config['model_name'],
        dtype=torch.bfloat16,
        token=hf_token,
        trust_remote_code=True,
        device_map="auto"
    )
    tokenizer = AutoTokenizer.from_pretrained(model_config['model_name'], token=hf_token, trust_remote_code=True)

    # Load LoRA weights
    model = PeftModel.from_pretrained(base_model, training_args_config['output_dir'])

    # Merge LoRA weights into base model
    model = model.merge_and_unload()

    # Save the merged model
    model.save_pretrained(FINE_TUNED_MODEL_PATH)
    tokenizer.save_pretrained(FINE_TUNED_MODEL_PATH)

    # Return the merged model and tokenizer
    return model, tokenizer

def test_trained_model(model, tokenizer):
    """Test the trained model with simple inference."""

    try:
        import torch
        # Test prompt
        # Create a conversation following the schema
        conversation = [
            {
                "content": "What is machine learning?",
                "role": "user"
            }
        ]

        # Convert conversation to chat format
        prompt = ""
        for message in conversation:
            if message["role"] == "user":
                prompt += f"### User: {message['content']}\n### Response:"
            else:
                prompt += f" {message['content']}\n\n"

        # Tokenize
        inputs = tokenizer(prompt, return_tensors="pt").to("cuda")

        # Generate
        with torch.no_grad():
            outputs = model.generate(
                **inputs,
                max_new_tokens=500,
                temperature=0.7,
                do_sample=True,
                pad_token_id=tokenizer.eos_token_id
            )

        # Decode
        response = tokenizer.decode(outputs[0], skip_special_tokens=True)
        print("🤖 Model Response:")
        print(response)
        return response

    except Exception as e:
        print(f"❌ Model testing failed: {e}")

# Save and load the trained model
model, tokenizer = save_and_load_trained_model()

# Test the trained model
response = test_trained_model(model, tokenizer)

Registrar o modelo no Catálogo do Unity

Registre o modelo afinado no MLflow e registre-o no Catálogo da Unity para implantação e disponibilização. O modelo é registrado com:

  • Modelo e tokenizador: ambos os componentes necessários para inferência
  • Tipo de tarefa: configurado como llm/v1/chat para IA de conversa
  • Exemplo de entrada: formato de mensagem de chat de exemplo para teste
  • Registro do Catálogo do Unity: registra automaticamente o modelo no catálogo e no esquema configurados

Depois de registrado, o modelo pode ser implantado em endpoints de serviço de modelo ou usado para inferência em lote.

run_id = results[0].get('mlflow_run_id')
mlflow.set_registry_uri("databricks-uc")

# log the model to mlflow using the latest run id and register to Unity Catalog
with mlflow.start_run(run_id=run_id) as run:
    components = {
        "model": model,
        "tokenizer": tokenizer
    }
    logged_model = mlflow.transformers.log_model(
        transformers_model=components,
        name="model",
        task="llm/v1/chat",
        input_example={
            "messages": [
                {"role": "user", "content": "What is machine learning?"}
            ]
        },
        registered_model_name=REGISTERED_MODEL_NAME
        )
    print(f"🔍 Model logged to: {logged_model}")

Próximas Etapas 

Notebook de exemplo

Ajustar o Llama 3.2 1B com o LoRA usando o AI Runtime

Obter laptop