Skip to content

Fine-Tuning Federado de Modelos de Linguagem

Esse laboratório apresenta o Fine-tuning federado de modelo de linguagem para uma aplicação de detecção de anomalias em Logs de sistemas. O laboratório utiliza como base o trabalho Fine-Tuning Eficiente de Modelos de Linguagem Para Detectar Anomalias em Logs Privados usando Aprendizado Federado e também baseado no conteúdo do minicurso MC2 - Fine-tuning de LLMs aprensetado no SBRC 2025

Para executar o laboratório utilize o Google Colab

Dependências

from google.colab import drive
drive.mount('/content/drive/', force_remount=True)

import sys

sys.path.append('/content/drive/MyDrive/Colab Notebooks/Minicurso SBRC')  # Path to your folder

Instalação de Bibliotecas

!pip install -q transformers sentencepiece evaluate "flwr[simulation]" trl bitsandbytes

!export WANDB_DISABLED=true

Download e preparação do dataset

  • Crie uma pasta dataset no seu drive.
  • Copie o conteúdo do diretório compartilhado para a pasta criada:

Imports

from flwr.common import Context
from flwr.server import ServerConfig
import torch
import os
import json
from datasets import Dataset
from flwr.client import NumPyClient, ClientApp
from flwr.common import ndarrays_to_parameters
from flwr.server import ServerAppComponents, ServerApp
from flwr.server.strategy import FedAvg
from flwr.simulation import run_simulation

from utils import FitConfigFactory, get_evaluate_fn
from utils import set_parameters, cosine_learning_rate, TraningConfigBuilder, TrainerBuilder, get_parameters, \
    ModelBuilder, get_tokenizer

Variáveis de configuração da simulação

#paths
testset_path = "/content/drive/MyDrive/Colab Notebooks/Minicurso SBRC/dataset/test.csv"
results_path = "/content/drive/MyDrive/Colab Notebooks/Minicurso SBRC/results"
dataset_path = "/content/drive/MyDrive/Colab Notebooks/Minicurso SBRC/dataset"

#model
model_name = "HuggingFaceTB/SmolLM-135M"
lora_rank = 8
lora = True
initial_lr = 1e-3
min_lr = 1e-5

#training
num_supernodes = 5
num_rounds = 2
fraction_fit = 0.4
fraction_eval = 0.0

train_context_dict = {
    "num-rounds": num_rounds,
    "initial-lr": initial_lr,
    "min-lr": min_lr,
    "dataset-path": dataset_path,
    "results-path": results_path,
    "model-name": model_name,
    "lora": lora
}

#eval
experiment_name = "experimento_sbrc"

eval_context_dict = {
  "model-name": model_name,
  "lora-rank": lora_rank,
  "lora": lora,
  "testset-path": testset_path,
  "results-path": results_path,
  "nrows": None,
  "experiment-name": experiment_name
}
if torch.cuda.is_available():
  device = torch.device("cuda")
  os.environ["CUDA_VISIBLE_DEVICES"] = "0"
  torch.cuda.empty_cache()
else:
  device = torch.device("cpu")

Cliente

class LLMClient(NumPyClient):
    def __init__(self, cid, model, tokenizer) -> None:
        super().__init__()

        self.cid = cid
        self.model = model
        self.tokenizer = tokenizer

    def fit(self, parameters, config):
        current_round = config["current_round"]
        total_rounds = config["num_rounds"]
        initial_lr = config["initial_lr"]
        min_lr = config["min_lr"]
        dataset_path = config["dataset_path"]
        results_path = config["results_path"]
        model_name = config["model_name"].lower()
        sim_name = "experimento_sbrc"
        lora = config["lora"]

        # Obtém dataset do cliente
        client_dataset = Dataset.load_from_disk(f"{dataset_path}/client_{self.cid}")
        # Obtém o modelo a ser treinado
        set_parameters(self.model, parameters, lora)
        self.model.to(device)

        # Configuração do treinamento
        # Calcula a nova taxa de aprendizado
        new_lr = cosine_learning_rate(current_round=current_round,
                                      total_rounds=total_rounds,
                                      initial_lr=initial_lr,
                                      min_lr=min_lr)
        # Cria a configuração do treinamento
        training_args = TraningConfigBuilder().with_output_dir(results_path).with_logging_dir(
            results_path).with_learning_rate(new_lr).build()

        # Cria objeto responsável por treinar o modelo
        trainer = TrainerBuilder().with_cid(self.cid).with_model(self.model).with_args(
            training_args).with_train_dataset(client_dataset).with_tokenizer(
            self.tokenizer).with_eval_dataset(client_dataset).with_model_name(model_name).build()

        # Realiza treinamento
        print(f"Rodada {current_round}: Treinando Cliente {self.cid} com lr {new_lr}")

        trainer.train()

        parameters = get_parameters(self.model, lora)
        dataset_size = len(client_dataset['labels'])

        print(f"Rodada {current_round}: Cliente {self.cid} treinou")

        # Save losses
        output_dir = f"{results_path}/fl-results/{sim_name}/round_{current_round}/client_{self.cid}"
        os.makedirs(output_dir, exist_ok=True)

        with open(f"{output_dir}/training_losses.json", "w") as f:
            json.dump(trainer.train_losses, f)

        with open(f"{output_dir}/validation_losses.json", "w") as f:
            json.dump(trainer.validation_losses, f)

        return parameters, dataset_size, {}


def client_fn(context: Context):
    """Returns a FlowerClient containing its data partition."""

    initial_model = ModelBuilder().with_model_name(model_name).enable_lora(lora).with_lora_rank(lora_rank).build()
    tokenizer = get_tokenizer(model_name)

    cid = int(context.node_config["partition-id"])

    return LLMClient(cid, initial_model, tokenizer).to_client()

Servidor

def server_fn(context: Context):
    # Objeto callable para criar configuração do cliente
    on_fit_config_fn = FitConfigFactory(train_context_dict)

    # Construção do modelo global
    global_model = ModelBuilder().with_model_name(model_name).enable_lora(lora).with_lora_rank(lora_rank).build()

    # 1 - Entender a inicialização realizada pelo notebook do allan
    # 2 - Entender a manipulação de parâmetros realizada por ela
    initial_ndarrays = get_parameters(global_model, lora)
    initial_parameters = ndarrays_to_parameters(initial_ndarrays)
    evaluate_fn = get_evaluate_fn(eval_context_dict, device)

    # 1 - próximo passo é usar uma injeção de dependências na inversão de contexto.
    strategy = FedAvg(initial_parameters=initial_parameters, fraction_fit=fraction_fit, fraction_evaluate=fraction_eval,
                      on_fit_config_fn=on_fit_config_fn, evaluate_fn=evaluate_fn)

    # Construct ServerConfig
    config = ServerConfig(num_rounds=num_rounds)

    # Wrap everything into a `ServerAppComponents` object
    return ServerAppComponents(strategy=strategy, config=config)

Simulando a Detecção de Anomalias com LLM

client_app = ClientApp(client_fn)
server_app = ServerApp(server_fn=server_fn)

# https://flower.ai/docs/framework/how-to-run-simulations.html
run_simulation(
    server_app=server_app, client_app=client_app, num_supernodes=num_supernodes, backend_config={"client_resources": {"num_cpus": 0.25,"num_gpus": 1}}
)