Ottimizzare Gemini su Vertex AI

1. Introduzione

In questo lab imparerai a eseguire il workflow completo di fine-tuning supervisionato su un modello Google Gemini per adattarlo a un'attività specifica: il riepilogo degli articoli. Sebbene i modelli linguistici di grandi dimensioni siano potenti, la loro natura per uso generico significa che possono essere resi ancora più efficaci per casi d'uso specifici tramite il fine-tuning. Addestrando il modello su un set di dati di esempi di alta qualità, puoi migliorarne la coerenza, la qualità e l'efficienza per l'attività target.

Utilizzerai Gemini 2.5 Flash, un modello leggero ed economico, ed eseguirai il fine-tuning utilizzando Vertex AI.

Panoramica dell'architettura

Ecco cosa creeremo:

  • Cloud Shell: il tuo ambiente di sviluppo.
  • Cloud Storage: archivia i dati di addestramento/convalida in formato JSONL.
  • Vertex AI Training: gestisce il job di fine-tuning.
  • Endpoint Vertex AI: ospita il modello ottimizzato.

Obiettivi didattici

  • Prepara set di dati di alta qualità per il fine-tuning supervisionato.
  • Configura e avvia i job di fine-tuning utilizzando l'SDK Vertex AI Python.
  • Valuta i modelli utilizzando metriche automatizzate (punteggi ROUGE).
  • Confronta i modelli di base e ottimizzati per quantificare i miglioramenti.

2. Configurazione del progetto

Account Google

Se non hai già un Account Google personale, devi creare un Account Google.

Utilizza un account personale anziché un account di lavoro o scolastico.

Accedi alla console Google Cloud

Accedi alla console Google Cloud utilizzando un Account Google personale.

Abilita fatturazione

Riscatta 5 $di crediti Google Cloud (facoltativo)

Per eseguire questo workshop, devi avere un account di fatturazione con alcuni crediti. Se prevedi di utilizzare la tua fatturazione, puoi saltare questo passaggio.

  1. Fai clic su questo link e accedi con un Account Google personale.Vedrai qualcosa di simile a questo:Fai clic per autorizzare Cloud Shell
  2. Fai clic sul pulsante FAI CLIC QUI PER ACCEDERE AI TUOI CREDITI.Verrà visualizzata una pagina per configurare il profilo di fatturazioneFai clic per autorizzare Cloud Shell
  3. Fai clic su Conferma

Ora sei connesso a un account di fatturazione di prova di Google Cloud Platform.

Screenshot della panoramica della fatturazione

Creare un progetto (facoltativo)

Se non hai un progetto attuale che vuoi utilizzare per questo lab, creane uno nuovo qui.

3. Apri editor di Cloud Shell

  1. Fai clic su questo link per passare direttamente all'editor di Cloud Shell
  2. Se ti viene richiesto di autorizzare in qualsiasi momento, fai clic su Autorizza per continuare.Fai clic per autorizzare Cloud Shell
  3. Se il terminale non viene visualizzato nella parte inferiore dello schermo, aprilo:
    • Fai clic su Visualizza
    • Fai clic su TerminaleApri un nuovo terminale nell'editor di Cloud Shell
  4. Nel terminale, imposta il progetto con questo comando:
    gcloud config set project [PROJECT_ID]
    
    • Esempio:
      gcloud config set project lab-project-id-example
      
    • Se non ricordi l'ID progetto, puoi elencare tutti gli ID progetto con:
      gcloud projects list
      
      Imposta l'ID progetto nel terminale dell'editor di Cloud Shell
  5. Dovresti vedere questo messaggio:
    Updated property [core/project].
    

4. Abilita API

Per utilizzare Vertex AI e altri servizi, devi abilitare le API necessarie nel tuo progetto Google Cloud.

  1. Nel terminale, abilita le API:
    • API Vertex AI (aiplatform.googleapis.com): consente l'utilizzo di Vertex AI per l'ottimizzazione e l'erogazione dei modelli.
    • API Cloud Storage (storage.googleapis.com): consente l'archiviazione di set di dati e artefatti del modello.
    gcloud services enable aiplatform.googleapis.com \
        storage.googleapis.com
    

5. Configurare l'ambiente del progetto

Crea una directory di lavoro

  1. Nel terminale, crea una directory per il progetto e spostati al suo interno.
    mkdir gemini-finetuning
    cd gemini-finetuning
    

Imposta le variabili di ambiente

  1. Nel terminale, definisci le variabili di ambiente per il progetto. Creeremo un file env.sh per archiviare queste variabili in modo che possano essere ricaricate facilmente se la sessione si disconnette.
    cat <<EOF > env.sh
    export PROJECT_ID=\$(gcloud config get-value project)
    export REGION="us-central1"
    export BUCKET_NAME="\${PROJECT_ID}-gemini-tuning"
    EOF
    
    source env.sh
    

Crea un bucket Cloud Storage

  1. Nel terminale, crea un bucket per archiviare il set di dati e gli artefatti del modello.
    gcloud storage buckets create gs://$BUCKET_NAME --project=$PROJECT_ID --location=$REGION
    

Configura l'ambiente virtuale

  1. Utilizzeremo uv per gestire il nostro ambiente Python. Nel terminale, esegui:
    uv venv .venv
    source .venv/bin/activate
    
  2. Nel terminale, installa i pacchetti Python richiesti.
    uv pip install google-cloud-aiplatform rouge-score matplotlib pandas tqdm
    

6. prepara i dati di addestramento

I dati di qualità sono la base di un fine-tuning efficace. Utilizzerai il set di dati WikiLingua, lo trasformerai nel formato JSONL specifico richiesto da Gemini e lo caricherai nel bucket di archiviazione.

  1. Nel terminale, crea un file denominato prepare_data.py.
    cloudshell edit prepare_data.py
    
  2. Incolla il seguente codice in prepare_data.py.
    import json
    import os
    import pandas as pd
    from google.cloud import storage
    import subprocess
    
    # Configuration
    BUCKET_NAME = os.environ["BUCKET_NAME"]
    PROJECT_ID = os.environ["PROJECT_ID"]
    
    def download_data():
        print("Downloading WikiLingua dataset...")
        # Using gsutil to copy from public bucket
        subprocess.run(["gsutil", "cp", "gs://github-repo/generative-ai/gemini/tuning/summarization/wikilingua/*", "."], check=True)
    
    def convert_to_gemini_format(input_file, output_file, max_samples=1000):
        print(f"Converting {input_file} to Gemini format (first {max_samples} samples)...")
        converted_data = []
        with open(input_file, 'r') as f:
            for i, line in enumerate(f):
                if i >= max_samples:
                    break
                obj = json.loads(line)
                messages = obj.get("messages", [])
    
                # Convert messages to Gemini 2.5 format
                # Input: {"messages": [{"role": "user", "content": "..."}, {"role": "model", "content": "..."}]}
                # Output: {"contents": [{"role": "user", "parts": [{"text": "..."}]}, {"role": "model", "parts": [{"text": "..."}]}]}
    
                contents = []
                for msg in messages:
                    role = msg["role"]
                    content = msg["content"]
                    contents.append({
                        "role": role,
                        "parts": [{"text": content}]
                    })
    
                converted_data.append({"contents": contents})
    
        with open(output_file, 'w') as f:
            for item in converted_data:
                f.write(json.dumps(item) + "\n")
    
        print(f"Saved {len(converted_data)} examples to {output_file}")
    
    def upload_to_gcs(local_file, destination_blob_name):
        print(f"Uploading {local_file} to gs://{BUCKET_NAME}/{destination_blob_name}...")
        storage_client = storage.Client(project=PROJECT_ID)
        bucket = storage_client.bucket(BUCKET_NAME)
        blob = bucket.blob(destination_blob_name)
        blob.upload_from_filename(local_file)
        print("Upload complete.")
    
    def main():
        download_data()
    
        # Process Training Data
        convert_to_gemini_format("sft_train_samples.jsonl", "train_gemini.jsonl")
        upload_to_gcs("train_gemini.jsonl", "datasets/train/train_gemini.jsonl")
    
        # Process Validation Data
        convert_to_gemini_format("sft_val_samples.jsonl", "val_gemini.jsonl")
        upload_to_gcs("val_gemini.jsonl", "datasets/val/val_gemini.jsonl")
    
        print("Data preparation complete!")
    
    if __name__ == "__main__":
        main()
    
  3. Nel terminale, esegui lo script di preparazione dei dati.
    python prepare_data.py
    

7. Stabilisci le prestazioni di riferimento

Prima del fine-tuning, devi avere un benchmark. Misurerai il rendimento del modello gemini-2.5-flash di base nell'attività di riepilogo utilizzando i punteggi ROUGE.

  1. Nel terminale, crea un file denominato evaluate.py.
    cloudshell edit evaluate.py
    
  2. Incolla il seguente codice in evaluate.py.
    import argparse
    import json
    import os
    import pandas as pd
    from google.cloud import aiplatform
    import vertexai
    from vertexai.generative_models import GenerativeModel, GenerationConfig, HarmCategory, HarmBlockThreshold
    from rouge_score import rouge_scorer
    from tqdm import tqdm
    import matplotlib.pyplot as plt
    import time
    
    # Configuration
    PROJECT_ID = os.environ["PROJECT_ID"]
    REGION = os.environ["REGION"]
    
    aiplatform.init(project=PROJECT_ID, location=REGION)
    
    def evaluate(model_name, test_file, max_samples=50, output_json="results.json"):
        print(f"Evaluating model: {model_name}")
    
        # Load Test Data
        test_df = pd.read_csv(test_file)
        test_df = test_df.head(max_samples)
    
        model = GenerativeModel(model_name)
    
        safety_settings = {
            HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
            HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_ONLY_HIGH,
            HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
            HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_ONLY_HIGH,
        }
    
        generation_config = GenerationConfig(
            temperature=0.1,
            max_output_tokens=1024,
        )
    
        scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=True)
        results = []
    
        for index, row in tqdm(test_df.iterrows(), total=len(test_df)):
            input_text = row['input_text']
            reference_summary = row['output_text']
    
            try:
                response = model.generate_content(
                    input_text,
                    generation_config=generation_config,
                    safety_settings=safety_settings
                )
                generated_summary = response.text
    
                scores = scorer.score(reference_summary, generated_summary)
    
                results.append({
                    "generated": generated_summary,
                    "reference": reference_summary,
                    "rouge1": scores['rouge1'].fmeasure,
                    "rouge2": scores['rouge2'].fmeasure,
                    "rougeL": scores['rougeL'].fmeasure
                })
            except Exception as e:
                print(f"Error processing example {index}: {e}")
                # Sleep briefly to avoid quota issues if hitting limits
                time.sleep(1)
    
        # Save results
        with open(output_json, 'w') as f:
            json.dump(results, f, indent=2)
    
        return pd.DataFrame(results)
    
    def plot_results(df, title, filename):
        os.makedirs("plots", exist_ok=True)
    
        metrics = ['rouge1', 'rouge2', 'rougeL']
        fig, axes = plt.subplots(1, 3, figsize=(15, 5))
    
        for i, metric in enumerate(metrics):
            axes[i].hist(df[metric], bins=10, alpha=0.7, color='skyblue', edgecolor='black')
            axes[i].set_title(f'{metric} Distribution')
            axes[i].set_xlabel('Score')
            axes[i].set_ylabel('Count')
    
        plt.suptitle(title)
        plt.tight_layout()
        plt.savefig(f"plots/{filename}")
        print(f"Plot saved to plots/{filename}")
    
    def compare_results(baseline_file, tuned_file):
        with open(baseline_file, 'r') as f:
            baseline_data = pd.DataFrame(json.load(f))
        with open(tuned_file, 'r') as f:
            tuned_data = pd.DataFrame(json.load(f))
    
        print("\n--- Comparison ---")
        metrics = ['rouge1', 'rouge2', 'rougeL']
        for metric in metrics:
            base_mean = baseline_data[metric].mean()
            tuned_mean = tuned_data[metric].mean()
            diff = tuned_mean - base_mean
            print(f"{metric}: Base={base_mean:.4f}, Tuned={tuned_mean:.4f}, Diff={diff:+.4f}")
    
        # Comparative Plot
        os.makedirs("plots", exist_ok=True)
        comparison_df = pd.DataFrame({
            'Metric': metrics,
            'Baseline': [baseline_data[m].mean() for m in metrics],
            'Tuned': [tuned_data[m].mean() for m in metrics]
        })
    
        comparison_df.plot(x='Metric', y=['Baseline', 'Tuned'], kind='bar', figsize=(10, 6))
        plt.title('Baseline vs Tuned Model Performance')
        plt.ylabel('Average Score')
        plt.xticks(rotation=0)
        plt.tight_layout()
        plt.savefig("plots/comparison.png")
        print("Comparison plot saved to plots/comparison.png")
    
    def main():
        parser = argparse.ArgumentParser()
        parser.add_argument("--model", type=str, default="gemini-2.5-flash", help="Model resource name")
        parser.add_argument("--baseline", type=str, help="Path to baseline results json for comparison")
        parser.add_argument("--output", type=str, default="results.json", help="Output file for results")
        args = parser.parse_args()
    
        # Ensure test data exists (it was downloaded in prepare_data step)
        if not os.path.exists("sft_test_samples.csv"):
            # Fallback download if needed
            subprocess.run(["gsutil", "cp", "gs://github-repo/generative-ai/gemini/tuning/summarization/wikilingua/sft_test_samples.csv", "."], check=True)
    
        df = evaluate(args.model, "sft_test_samples.csv", output_json=args.output)
    
        print("\n--- Results Summary ---")
        print(df.describe())
    
        plot_filename = "baseline_dist.png" if not args.baseline else "tuned_dist.png"
        plot_results(df, f"ROUGE Scores - {args.model}", plot_filename)
    
        if args.baseline:
            compare_results(args.baseline, args.output)
    
    if __name__ == "__main__":
        main()
    
  3. Nel terminale, esegui la valutazione di riferimento.
    python evaluate.py --model "gemini-2.5-flash" --output "baseline.json"
    
    Verranno generati un file baseline.json e un grafico in plots/baseline_dist.png.

8. Configura e avvia il fine-tuning

Ora avvierai un job di fine-tuning gestito su Vertex AI.

  1. Nel terminale, crea un file denominato tune.py.
    cloudshell edit tune.py
    
  2. Incolla il seguente codice in tune.py.
    import os
    import time
    from google.cloud import aiplatform
    import vertexai
    from vertexai.preview.tuning import sft
    
    # Configuration
    PROJECT_ID = os.environ["PROJECT_ID"]
    REGION = os.environ["REGION"]
    BUCKET_NAME = os.environ["BUCKET_NAME"]
    
    aiplatform.init(project=PROJECT_ID, location=REGION)
    
    def train():
        print("Launching fine-tuning job...")
    
        sft_tuning_job = sft.train(
            source_model="gemini-2.5-flash", # Using specific version for stability
            train_dataset=f"gs://{BUCKET_NAME}/datasets/train/train_gemini.jsonl",
            validation_dataset=f"gs://{BUCKET_NAME}/datasets/val/val_gemini.jsonl",
            epochs=1, # Keep it short for the lab
            adapter_size=4,
            learning_rate_multiplier=1.0,
            tuned_model_display_name="gemini-2.5-flash-wikilingua",
        )
    
        print(f"Job started: {sft_tuning_job.resource_name}")
        print("Waiting for job to complete... (this may take ~45 minutes)")
    
        # Wait for the job to complete
        while not sft_tuning_job.has_ended:
            time.sleep(60)
            sft_tuning_job.refresh()
            print(f"Status: {sft_tuning_job.state.name}")
    
        print("Job completed!")
        print(f"Tuned Model Endpoint: {sft_tuning_job.tuned_model_endpoint_name}")
        return sft_tuning_job.tuned_model_endpoint_name
    
    if __name__ == "__main__":
        train()
    
  3. Nel terminale, esegui lo script di fine-tuning.
    python tune.py
    
    Nota: questo processo può richiedere circa 45 minuti. Puoi monitorare il job in the Vertex AI Console.

9. comprendi il codice di addestramento

Mentre il job è in esecuzione, esaminiamo più da vicino lo script tune.py per capire come funziona il fine-tuning.

Fine-tuning supervisionato gestito

Lo script utilizza il metodo vertexai.tuning.sft.train per inviare un job di ottimizzazione gestito. In questo modo si astrae la complessità del provisioning dell'infrastruttura, della distribuzione dell'addestramento e della gestione dei checkpoint.

sft_tuning_job = sft.train(
    source_model="gemini-2.5-flash",
    train_dataset=f"gs://{BUCKET_NAME}/datasets/train/train_gemini.jsonl",
    # ...
)

Configurazione LoRA

Anziché definire manualmente un LoraConfig come potresti fare nei framework open source, Vertex AI semplifica questa operazione in alcuni parametri chiave:

  • adapter_size: questo parametro (impostato su 4 nel nostro script) controlla il rango degli adattatori LoRA. Una dimensione maggiore consente al modello di apprendere adattamenti più complessi, ma aumenta il numero di parametri addestrabili.
  • epochs: per questo lab, abbiamo impostato questo valore su 1 per ridurre il tempo di addestramento (circa 20 minuti). In uno scenario di produzione, potresti aumentare questo valore per consentire al modello di apprendere più a fondo dai tuoi dati, anche se devi fare attenzione all'overfitting.

Selezione del modello

Specifichiamo esplicitamente source_model="gemini-2.5-flash". Vertex AI supporta varie versioni di Gemini e l'aggiunta di una versione specifica garantisce che la pipeline rimanga stabile e riproducibile.

10. Confronta i modelli

Al termine del job di fine-tuning, puoi confrontare il rendimento del nuovo modello con quello di riferimento.

  1. Recupera l'endpoint del modello ottimizzato. È stato stampato alla fine dello script tune.py. Avrà un aspetto simile a projects/.../locations/.../endpoints/....
  2. Esegui di nuovo lo script di valutazione, questa volta passando il modello ottimizzato e i risultati di riferimento per il confronto.
    # Replace [YOUR_TUNED_MODEL_ENDPOINT] with the actual endpoint name
    export TUNED_MODEL="projects/[YOUR_PROJECT_ID]/locations/[YOUR_REGION]/endpoints/[YOUR_ENDPOINT_ID]"
    
    python evaluate.py --model "$TUNED_MODEL" --baseline "baseline.json" --output "tuned.json"
    
  3. Visualizza i risultati. Lo script restituirà un confronto dei punteggi ROUGE e genererà un grafico plots/comparison.png che mostra il miglioramento.Puoi visualizzare i grafici aprendo la cartella plots nell'editor di Cloud Shell.

11. Libera spazio

Per evitare addebiti, elimina le risorse che hai creato.

  1. Nel terminale, elimina il bucket Cloud Storage e il modello ottimizzato.
    gcloud storage rm -r gs://$BUCKET_NAME
    # Note: You can delete the model endpoint from the Vertex AI Console
    

12. Complimenti!

Hai eseguito correttamente il fine-tuning di Gemini 2.5 Flash su Vertex AI.

Riepilogo

In questo lab imparerai a:

  • Prepara un set di dati in formato JSONL per il fine-tuning di Gemini.
  • Stabilisci una base di riferimento utilizzando il modello Gemini 2.5 Flash di base.
  • Avvia un job di fine-tuning supervisionato su Vertex AI.
  • Valuta e confronta il modello ottimizzato con la base di riferimento.

Passaggi successivi

Questo lab fa parte del percorso di apprendimento AI pronta per la produzione con Google Cloud.

Esplora il curriculum completo per colmare il divario tra prototipo e produzione.

Condividi i tuoi progressi con l'hashtag #ProductionReadyAI.