Esercitazione sull'ottimizzazione di GPT-4o mini di Azure OpenAI (versione classica)

Si applica solo a:Portale di Foundry (versione classica). Questo articolo non è disponibile per il nuovo portale foundry. Altre informazioni sul nuovo portale.

Nota

I collegamenti in questo articolo potrebbero aprire contenuto nella nuova documentazione di Microsoft Foundry anziché nella documentazione di Foundry (versione classica) visualizzata.

Questa esercitazione illustra l'ottimizzazione di un gpt-4o-mini-2024-07-18 modello.

In questa esercitazione si apprenderà come:

  • Creare set di dati di ottimizzazione di esempio.
  • Creare variabili di ambiente per l'endpoint della risorsa e la chiave API.
  • Preparate i set di dati di addestramento e convalida di esempio per la messa a punto.
  • Caricare il file di training e il file di convalida per l'ottimizzazione.
  • Creare un processo di ottimizzazione per gpt-4o-mini-2024-07-18.
  • Distribuire un modello personalizzato ottimizzato.

Prerequisiti

Importante

È consigliabile esaminare le informazioni sui prezzi per l'ottimizzazione e familiarizzare con i costi associati. Il test di questa esercitazione ha comportato la fatturazione di 48.000 token (4.800 token di training x 10 epoche di training). I costi di training si aggiungono ai costi associati all'inferenza di ottimizzazione e ai costi orari di hosting di un modello ottimizzato. Dopo avere completato l'esercitazione è necessario eliminare la distribuzione del modello ottimizzata; in caso contrario si continuerà a sostenere il costo di hosting orario.

Configurare

librerie Python

Questa esercitazione fornisce esempi di alcune delle funzionalità OpenAI più recenti, tra cui seed/events/checkpoints. Per sfruttare queste funzionalità, potrebbe essere necessario eseguire pip install openai --upgrade per eseguire l'aggiornamento alla versione più recente.

pip install openai requests tiktoken numpy

Recuperare la chiave e l'endpoint

Per effettuare correttamente una chiamata a Azure OpenAI, è necessario un endpoint e un key.

Nome variabile Valore
ENDPOINT L'endpoint del servizio è disponibile nella sezione Keys e Endpoint quando si esamina la risorsa dal Portale di Azure. In alternativa, è possibile trovare l'endpoint tramite la pagina Deployments nel portale di Microsoft Foundry. Un endpoint di esempio è: https://docs-test-001.openai.azure.com/.
API-KEY Questo valore è disponibile nella Keys e Endpoint quando esamini la risorsa dal portale di Azure. È possibile usare KEY1 o KEY2.

Passare alla risorsa nel portale di Azure. La sezione Chiavi ed endpoint è disponibile nella sezione Gestione risorse . Copiare l'endpoint e la chiave di accesso perché saranno necessari entrambi per l'autenticazione delle chiamate API. È possibile usare KEY1 o KEY2. Avere sempre due chiavi consente di ruotare e rigenerare le chiavi in modo sicuro senza causare un'interruzione del servizio.

Screenshot dell'interfaccia utente di panoramica per una risorsa Azure OpenAI nel portale di Azure con l'endpoint e la posizione dei tasti di scelta in rosso.

Variabili di ambiente

Creare e assegnare variabili di ambiente persistenti per la chiave e l'endpoint.

Importante

Usare le chiavi API con cautela. Non includere la chiave API direttamente nel codice e non pubblicarla mai pubblicamente. Se si usa una chiave API, archiviarla in modo sicuro in Azure Key Vault. Per altre informazioni sull'uso sicuro delle chiavi API nelle app, vedere ChiaviAPI con Azure Key Vault.

Per altre informazioni sulla sicurezza dei servizi di intelligenza artificiale, vedere Autorizzazione delle richieste a Servizi di Azure AI.

setx AZURE_OPENAI_API_KEY "REPLACE_WITH_YOUR_KEY_VALUE_HERE"

Creare un set di dati di esempio

L'ottimizzazione gpt-4o-mini-2024-07-18 richiede un file di training JSONL formattato in modo specifico. OpenAI fornisce l'esempio seguente nella relativa documentazione:

{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What's the capital/major city of France?"}, {"role": "assistant", "content": "Paris, as if everyone doesn't know that already."}]}
{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "Who wrote 'Romeo and Juliet'?"}, {"role": "assistant", "content": "Oh, just some guy named William Shakespeare. Ever heard of him?"}]}
{"messages": [{"role": "system", "content": "Marv is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "How far is the Moon from Earth?"}, {"role": "assistant", "content": "Around 384,400 kilometers. Give or take a few, like that really matters."}]}

Per questo esempio si modifica leggermente questo valore modificando in:

{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What's the capital/major city of France?"}, {"role": "assistant", "content": "Paris, as if everyone doesn't know that already."}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "Who wrote 'Romeo and Juliet'?"}, {"role": "assistant", "content": "Oh, just some guy named William Shakespeare. Ever heard of him?"}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "How far is the Moon from Earth?"}, {"role": "assistant", "content": "Around 384,400 kilometers. Give or take a few, like that really matters."}]}

Anche se questi tre esempi sono utili per fornire il formato generale, se si vuole impostare il modello personalizzato ottimizzato per rispondere in modo analogo, sono necessari altri esempi. In genere si vogliono iniziare almeno 50 esempi di alta qualità . Tuttavia, è del tutto possibile avere un caso d'uso che potrebbe richiedere la riuscita di 1.000 di esempi di formazione di alta qualità.

In generale, il raddoppio delle dimensioni del set di dati può comportare un aumento lineare della qualità del modello. Tenere tuttavia presente che gli esempi di bassa qualità possono influire negativamente sulle prestazioni. Se si esegue il training del modello su una grande quantità di dati interni senza prima eseguire l'eliminazione del set di dati solo per gli esempi di qualità più elevati, è possibile che si verifichi un modello con prestazioni peggiori del previsto.

Sarà necessario creare due file training_set.jsonl e validation_set.jsonl.

Creare i file nella stessa directory in cui si esegue il Jupyter Notebook e copiare il contenuto dei blocchi di codice seguenti nei file corrispondenti:

training_set.jsonl

{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "Who discovered Antarctica?"}, {"role": "assistant", "content": "Some chaps named Fabian Gottlieb von Bellingshausen and Mikhail Lazarev, as if they don't teach that in every school!"}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What is the biggest ocean?"}, {"role": "assistant", "content": "The Pacific Ocean. It's not like it's a small pond or anything."}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What is the largest planet?"}, {"role": "assistant", "content": "It's called Jupiter, you might have heard of it...or not."}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "Who painted the Mona Lisa?"}, {"role": "assistant", "content": "Oh, some obscure artist named Leonardo da Vinci. Not that he is famous or anything."}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What's the boiling point of water?"}, {"role": "assistant", "content": "Exciting fact of the day: it's 100 degrees Celsius. Life-changing, isn't it?"}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "Who wrote 'The Odyssey'?"}, {"role": "assistant", "content": "Oh, just some poet named Homer. Nothing special, really."}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What is the largest mammal?"}, {"role": "assistant", "content": "Blue Whale. Quite a big fish isn't it? Oh wait, it's not a fish!"}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What's the highest mountain peak?"}, {"role": "assistant", "content": "Everest. Brace yourself, it's not a walk in the park."}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "Who invented the telephone?"}, {"role": "assistant", "content": "A guy named Alexander Graham Bell. Bet you didn't guess that one!"}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What is the distance from Earth to the Sun?"}, {"role": "assistant", "content": "About 93 million miles. Just a quick drive, really."}]}

validation_set.jsonl

{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What's the capital of Australia?"}, {"role": "assistant", "content": "It's Canberra, not Sydney. Shocking, I know!"}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "Who wrote 'The Great Gatsby'?"}, {"role": "assistant", "content": "Some guy named F. Scott Fitzgerald. Ever heard of him?"}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What's the deepest part of the ocean?"}, {"role": "assistant", "content": "Mariana Trench, makes your swimming pool seem a bit shallow, doesn't it?"}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "Who composed the 'Moonlight Sonata'?"}, {"role": "assistant", "content": "Just a little-known guy named Beethoven. No big deal."}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What are the colors of the rainbow?"}, {"role": "assistant", "content": "ROYGBIV. Red, orange, yellow, green, blue, indigo, violet. Not that we learned this in preschool or anything!"}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What is the smallest planet?"}, {"role": "assistant", "content": "Meet Mercury, the runt of the solar system family."}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "Who wrote 'Pride and Prejudice'?"}, {"role": "assistant", "content": "Do the words Jane Austen ring a bell? No? Well, she wrote it."}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What's the largest desert?"}, {"role": "assistant", "content": "Antarctica. Surprise, surprise! Deserts aren't just full of sand, you know."}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What is the longest river?"}, {"role": "assistant", "content": "The Nile River. It's not like it's famous or anything."}]}
{"messages": [{"role": "system", "content": "Clippy is a factual chatbot that is also sarcastic."}, {"role": "user", "content": "What's the capital of Germany?"}, {"role": "assistant", "content": "Berlin. Shocking news, right?"}]}

È ora necessario eseguire alcuni controlli preliminari sui file di training e convalida.

# Run preliminary checks

import json

# Load the training set
with open('training_set.jsonl', 'r', encoding='utf-8') as f:
    training_dataset = [json.loads(line) for line in f]

# Training dataset stats
print("Number of examples in training set:", len(training_dataset))
print("First example in training set:")
for message in training_dataset[0]["messages"]:
    print(message)

# Load the validation set
with open('validation_set.jsonl', 'r', encoding='utf-8') as f:
    validation_dataset = [json.loads(line) for line in f]

# Validation dataset stats
print("\nNumber of examples in validation set:", len(validation_dataset))
print("First example in validation set:")
for message in validation_dataset[0]["messages"]:
    print(message)

Output:

Number of examples in training set: 10
First example in training set:
{'role': 'system', 'content': 'Clippy is a factual chatbot that is also sarcastic.'}
{'role': 'user', 'content': 'Who discovered America?'}
{'role': 'assistant', 'content': "Some chap named Christopher Columbus, as if they don't teach that in every school!"}

Number of examples in validation set: 10
First example in validation set:
{'role': 'system', 'content': 'Clippy is a factual chatbot that is also sarcastic.'}
{'role': 'user', 'content': "What's the capital of Australia?"}
{'role': 'assistant', 'content': "It's Canberra, not Sydney. Shocking, I know!"}

In questo caso sono disponibili solo 10 esempi di addestramento e 10 di convalida, quindi, mentre questo dimostra i meccanismi di base dell'ottimizzazione di un modello, è improbabile che questo numero di esempi sia sufficiente per produrre un effetto costantemente evidente.

È ora possibile usare la libreria tiktoken per convalidare i conteggi dei token. Il conteggio dei token utilizzando questo metodo non fornirà i conteggi esatti dei token utilizzati per la messa a punto, ma dovrebbe fornire una buona stima.

Nota

I singoli esempi devono rimanere entro il limite di lunghezza del contesto dell'esempio di training corrente del modello gpt-4o-mini-2024-07-18 pari a 64.536 token. Il limite di token di input del modello rimane di 128.000 token.

# Validate token counts

import json
import tiktoken
import numpy as np
from collections import defaultdict

encoding = tiktoken.get_encoding("o200k_base") # default encoding for gpt-4o models. This requires the latest version of tiktoken to be installed.

def num_tokens_from_messages(messages, tokens_per_message=3, tokens_per_name=1):
    num_tokens = 0
    for message in messages:
        num_tokens += tokens_per_message
        for key, value in message.items():
            num_tokens += len(encoding.encode(value))
            if key == "name":
                num_tokens += tokens_per_name
    num_tokens += 3
    return num_tokens

def num_assistant_tokens_from_messages(messages):
    num_tokens = 0
    for message in messages:
        if message["role"] == "assistant":
            num_tokens += len(encoding.encode(message["content"]))
    return num_tokens

def print_distribution(values, name):
    print(f"\n#### Distribution of {name}:")
    print(f"min / max: {min(values)}, {max(values)}")
    print(f"mean / median: {np.mean(values)}, {np.median(values)}")
    print(f"p5 / p95: {np.quantile(values, 0.1)}, {np.quantile(values, 0.9)}")

files = ['training_set.jsonl', 'validation_set.jsonl']

for file in files:
    print(f"Processing file: {file}")
    with open(file, 'r', encoding='utf-8') as f:
        dataset = [json.loads(line) for line in f]

    total_tokens = []
    assistant_tokens = []

    for ex in dataset:
        messages = ex.get("messages", {})
        total_tokens.append(num_tokens_from_messages(messages))
        assistant_tokens.append(num_assistant_tokens_from_messages(messages))

    print_distribution(total_tokens, "total tokens")
    print_distribution(assistant_tokens, "assistant tokens")
    print('*' * 50)

Output:

Processing file: training_set.jsonl

#### Distribution of total tokens:
min / max: 46, 59
mean / median: 49.8, 48.5
p5 / p95: 46.0, 53.599999999999994

#### Distribution of assistant tokens:
min / max: 13, 28
mean / median: 16.5, 14.0
p5 / p95: 13.0, 19.9
**************************************************
Processing file: validation_set.jsonl

#### Distribution of total tokens:
min / max: 41, 64
mean / median: 48.9, 47.0
p5 / p95: 43.7, 54.099999999999994

#### Distribution of assistant tokens:
min / max: 8, 29
mean / median: 15.0, 12.5
p5 / p95: 10.7, 19.999999999999996
****************************

Caricare file di ottimizzazione

# Upload fine-tuning files

import os
from openai import OpenAI

client = OpenAI(
  api_key = os.getenv("AZURE_OPENAI_API_KEY"),  
  base_url="https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/"
)

training_file_name = 'training_set.jsonl'
validation_file_name = 'validation_set.jsonl'

# Upload the training and validation dataset files to Azure OpenAI with the SDK.

training_response = client.files.create(
    file = open(training_file_name, "rb"), purpose="fine-tune"
)
training_file_id = training_response.id

validation_response = client.files.create(
    file = open(validation_file_name, "rb"), purpose="fine-tune"
)
validation_file_id = validation_response.id

print("Training file ID:", training_file_id)
print("Validation file ID:", validation_file_id)

Output:

Training file ID: file-0e3aa3f2e81e49a5b8b96166ea214626
Validation file ID: file-8556c3bb41b7416bb7519b47fcd1dd6b

Iniziare l'ottimizzazione

Dopo aver caricato correttamente i file di ottimizzazione è possibile inviare il processo di ottimizzazione:

In questo esempio viene passato anche il parametro di seed. Il seme controlla la riproducibilità del processo. Il passaggio dello stesso seed e degli stessi parametri di lavoro dovrebbe produrre gli stessi risultati, ma in rari casi potrebbe differire. Se non viene specificato un seme, ne viene generato uno automaticamente.

# Submit fine-tuning training job

response = client.fine_tuning.jobs.create(
    training_file = training_file_id,
    validation_file = validation_file_id,
    model = "gpt-4o-mini-2024-07-18", # Enter base model name. Note that in Azure OpenAI the model name contains dashes and cannot contain dot/period characters.
    seed = 105 # seed parameter controls reproducibility of the fine-tuning job. If no seed is specified one will be generated automatically.
)

job_id = response.id

# You can use the job ID to monitor the status of the fine-tuning job.
# The fine-tuning job will take some time to start and complete.

print("Job ID:", response.id)
print("Status:", response.status)
print(response.model_dump_json(indent=2))

Output:

Job ID: ftjob-900fcfc7ea1d4360a9f0cb1697b4eaa6
Status: pending
{
  "id": "ftjob-900fcfc7ea1d4360a9f0cb1697b4eaa6",
  "created_at": 1715824115,
  "error": null,
  "fine_tuned_model": null,
  "finished_at": null,
  "hyperparameters": {
    "n_epochs": -1,
    "batch_size": -1,
    "learning_rate_multiplier": 1
  },
  "model": "gpt-4o-mini-2024-07-18",
  "object": "fine_tuning.job",
  "organization_id": null,
  "result_files": null,
  "seed": 105,
  "status": "pending",
  "trained_tokens": null,
  "training_file": "file-0e3aa3f2e81e49a5b8b96166ea214626",
  "validation_file": "file-8556c3bb41b7416bb7519b47fcd1dd6b",
  "estimated_finish": null,
  "integrations": null
}

Tenere traccia dello stato del lavoro di addestramento

Se si desidera verificare lo stato del lavoro di training fino al completamento, è possibile eseguire:

# Track training status

from IPython.display import clear_output
import time

start_time = time.time()

# Get the status of our fine-tuning job.
response = client.fine_tuning.jobs.retrieve(job_id)

status = response.status

# If the job isn't done yet, poll it every 10 seconds.
while status not in ["succeeded", "failed"]:
    time.sleep(10)

    response = client.fine_tuning.jobs.retrieve(job_id)
    print(response.model_dump_json(indent=2))
    print("Elapsed time: {} minutes {} seconds".format(int((time.time() - start_time) // 60), int((time.time() - start_time) % 60)))
    status = response.status
    print(f'Status: {status}')
    clear_output(wait=True)

print(f'Fine-tuning job {job_id} finished with status: {status}')

# List all fine-tuning jobs for this resource.
print('Checking other fine-tune jobs for this resource.')
response = client.fine_tuning.jobs.list()
print(f'Found {len(response.data)} fine-tune jobs.')

Output:

Job ID: ftjob-900fcfc7ea1d4360a9f0cb1697b4eaa6
Status: pending
{
  "id": "ftjob-900fcfc7ea1d4360a9f0cb1697b4eaa6",
  "created_at": 1715824115,
  "error": null,
  "fine_tuned_model": null,
  "finished_at": null,
  "hyperparameters": {
    "n_epochs": -1,
    "batch_size": -1,
    "learning_rate_multiplier": 1
  },
  "model": "gpt-4o-mini-2024-07-18",
  "object": "fine_tuning.job",
  "organization_id": null,
  "result_files": null,
  "seed": 105,
  "status": "pending",
  "trained_tokens": null,
  "training_file": "file-0e3aa3f2e81e49a5b8b96166ea214626",
  "validation_file": "file-8556c3bb41b7416bb7519b47fcd1dd6b",
  "estimated_finish": null,
  "integrations": null
}

Non è insolito che il training richiede più di un'ora per il completamento. Una volta completato il training, il messaggio di output diventa simile al seguente:

Fine-tuning job ftjob-900fcfc7ea1d4360a9f0cb1697b4eaa6 finished with status: succeeded
Checking other fine-tune jobs for this resource.
Found 4 fine-tune jobs.

Elencare gli eventi di ottimizzazione

Versione API: 2024-08-01-preview o versione successiva è necessaria per questo comando.

Anche se non è necessario completare l'ottimizzazione, può essere utile esaminare i singoli eventi di ottimizzazione generati durante il training. I risultati completi del training possono essere esaminati anche dopo il completamento del training nel file dei risultati del training.

response = client.fine_tuning.jobs.list_events(fine_tuning_job_id=job_id, limit=10)
print(response.model_dump_json(indent=2))

Output:

{
  "data": [
    {
      "id": "ftevent-179d02d6178f4a0486516ff8cbcdbfb6",
      "created_at": 1715826339,
      "level": "info",
      "message": "Training hours billed: 0.500",
      "object": "fine_tuning.job.event",
      "type": "message"
    },
    {
      "id": "ftevent-467bc5e766224e97b5561055dc4c39c0",
      "created_at": 1715826339,
      "level": "info",
      "message": "Completed results file: file-175c81c590074388bdb49e8e0d91bac3",
      "object": "fine_tuning.job.event",
      "type": "message"
    },
    {
      "id": "ftevent-a30c44da4c304180b327c3be3a7a7e51",
      "created_at": 1715826337,
      "level": "info",
      "message": "Postprocessing started.",
      "object": "fine_tuning.job.event",
      "type": "message"
    },
    {
      "id": "ftevent-ea10a008f1a045e9914de98b6b47514b",
      "created_at": 1715826303,
      "level": "info",
      "message": "Job succeeded.",
      "object": "fine_tuning.job.event",
      "type": "message"
    },
    {
      "id": "ftevent-008dc754dc9e61b008dc754dc9e61b00",
      "created_at": 1715825614,
      "level": "info",
      "message": "Step 100: training loss=0.001647822093218565",
      "object": "fine_tuning.job.event",
      "type": "metrics",
      "data": {
        "step": 100,
        "train_loss": 0.001647822093218565,
        "train_mean_token_accuracy": 1,
        "valid_loss": 1.5170825719833374,
        "valid_mean_token_accuracy": 0.75,
        "full_valid_loss": 1.7539110545870624,
        "full_valid_mean_token_accuracy": 0.7215189873417721
      }
    },
    {
      "id": "ftevent-008dc754dc3f03a008dc754dc3f03a00",
      "created_at": 1715825604,
      "level": "info",
      "message": "Step 90: training loss=0.00971441250294447",
      "object": "fine_tuning.job.event",
      "type": "metrics",
      "data": {
        "step": 90,
        "train_loss": 0.00971441250294447,
        "train_mean_token_accuracy": 1,
        "valid_loss": 1.3702410459518433,
        "valid_mean_token_accuracy": 0.75,
        "full_valid_loss": 1.7371194453179082,
        "full_valid_mean_token_accuracy": 0.7278481012658228
      }
    },
    {
      "id": "ftevent-008dc754dbdfa59008dc754dbdfa5900",
      "created_at": 1715825594,
      "level": "info",
      "message": "Step 80: training loss=0.0032251903321594",
      "object": "fine_tuning.job.event",
      "type": "metrics",
      "data": {
        "step": 80,
        "train_loss": 0.0032251903321594,
        "train_mean_token_accuracy": 1,
        "valid_loss": 1.4242165088653564,
        "valid_mean_token_accuracy": 0.75,
        "full_valid_loss": 1.6554046099698996,
        "full_valid_mean_token_accuracy": 0.7278481012658228
      }
    },
    {
      "id": "ftevent-008dc754db80478008dc754db8047800",
      "created_at": 1715825584,
      "level": "info",
      "message": "Step 70: training loss=0.07380199432373047",
      "object": "fine_tuning.job.event",
      "type": "metrics",
      "data": {
        "step": 70,
        "train_loss": 0.07380199432373047,
        "train_mean_token_accuracy": 1,
        "valid_loss": 1.2011798620224,
        "valid_mean_token_accuracy": 0.75,
        "full_valid_loss": 1.508960385865803,
        "full_valid_mean_token_accuracy": 0.740506329113924
      }
    },
    {
      "id": "ftevent-008dc754db20e97008dc754db20e9700",
      "created_at": 1715825574,
      "level": "info",
      "message": "Step 60: training loss=0.245253324508667",
      "object": "fine_tuning.job.event",
      "type": "metrics",
      "data": {
        "step": 60,
        "train_loss": 0.245253324508667,
        "train_mean_token_accuracy": 0.875,
        "valid_loss": 1.0585949420928955,
        "valid_mean_token_accuracy": 0.75,
        "full_valid_loss": 1.3787144045286541,
        "full_valid_mean_token_accuracy": 0.7341772151898734
      }
    },
    {
      "id": "ftevent-008dc754dac18b6008dc754dac18b600",
      "created_at": 1715825564,
      "level": "info",
      "message": "Step 50: training loss=0.1696014404296875",
      "object": "fine_tuning.job.event",
      "type": "metrics",
      "data": {
        "step": 50,
        "train_loss": 0.1696014404296875,
        "train_mean_token_accuracy": 0.8999999761581421,
        "valid_loss": 0.8862184286117554,
        "valid_mean_token_accuracy": 0.8125,
        "full_valid_loss": 1.2814022257358213,
        "full_valid_mean_token_accuracy": 0.7151898734177216
      }
    }
  ],
  "has_more": true,
  "object": "list"
}

Elencare i checkpoint

Versione API: 2024-08-01-preview o versione successiva è necessaria per questo comando.

Alla conclusione di ogni epoca di training, viene generato un checkpoint. Un checkpoint è una versione completamente funzionale di un modello che può essere distribuita e usata come modello di destinazione per i successivi processi di ottimizzazione. I checkpoint possono risultare utili in quanto restituiscono un'istantanea del modello prima che si verifichi l'overfitting. Al termine di un processo di ottimizzazione, sono disponibili le tre versioni più recenti del modello da distribuire. L'ultima epoca sarà rappresentata dal tuo modello ottimizzato e messo a punto, le due epoche precedenti sono disponibili come punti di controllo.

response = client.fine_tuning.jobs.checkpoints.list(job_id)
print(response.model_dump_json(indent=2))

Output:

{
  "data": [
    {
      "id": "ftchkpt-148ab69f0a404cf9ab55a73d51b152de",
      "created_at": 1715743077,
      "fine_tuned_model_checkpoint": "gpt-4o-mini-2024-07-18.ft-0e208cf33a6a466994aff31a08aba678",
      "fine_tuning_job_id": "ftjob-372c72db22c34e6f9ccb62c26ee0fbd9",
      "metrics": {
        "full_valid_loss": 1.8258173013035255,
        "full_valid_mean_token_accuracy": 0.7151898734177216,
        "step": 100.0,
        "train_loss": 0.004080486483871937,
        "train_mean_token_accuracy": 1.0,
        "valid_loss": 1.5915886163711548,
        "valid_mean_token_accuracy": 0.75
      },
      "object": "fine_tuning.job.checkpoint",
      "step_number": 100
    },
    {
      "id": "ftchkpt-e559c011ecc04fc68eaa339d8227d02d",
      "created_at": 1715743013,
      "fine_tuned_model_checkpoint": "gpt-4o-mini-2024-07-18.ft-0e208cf33a6a466994aff31a08aba678:ckpt-step-90",
      "fine_tuning_job_id": "ftjob-372c72db22c34e6f9ccb62c26ee0fbd9",
      "metrics": {
        "full_valid_loss": 1.7958603267428241,
        "full_valid_mean_token_accuracy": 0.7215189873417721,
        "step": 90.0,
        "train_loss": 0.0011079151881858706,
        "train_mean_token_accuracy": 1.0,
        "valid_loss": 1.6084896326065063,
        "valid_mean_token_accuracy": 0.75
      },
      "object": "fine_tuning.job.checkpoint",
      "step_number": 90
    },
    {
      "id": "ftchkpt-8ae8beef3dcd4dfbbe9212e79bb53265",
      "created_at": 1715742984,
      "fine_tuned_model_checkpoint": "gpt-4o-mini-2024-07-18.ft-0e208cf33a6a466994aff31a08aba678:ckpt-step-80",
      "fine_tuning_job_id": "ftjob-372c72db22c34e6f9ccb62c26ee0fbd9",
      "metrics": {
        "full_valid_loss": 1.6909511662736725,
        "full_valid_mean_token_accuracy": 0.7088607594936709,
        "step": 80.0,
        "train_loss": 0.000667572021484375,
        "train_mean_token_accuracy": 1.0,
        "valid_loss": 1.4677599668502808,
        "valid_mean_token_accuracy": 0.75
      },
      "object": "fine_tuning.job.checkpoint",
      "step_number": 80
    }
  ],
  "has_more": false,
  "object": "list"
}

Risultati finali della sessione di training

Per ottenere i risultati finali, eseguire quanto segue:

# Retrieve fine_tuned_model name

response = client.fine_tuning.jobs.retrieve(job_id)

print(response.model_dump_json(indent=2))
fine_tuned_model = response.fine_tuned_model

Distribuire un modello ottimizzato

A differenza dei comandi precedenti di Python SDK in questa esercitazione, poiché l'introduzione della funzionalità di quota, la distribuzione del modello deve essere eseguita usando l'API REST, che richiede un'autorizzazione separata, un percorso API diverso e una versione diversa dell'API.

In alternativa, è possibile distribuire il modello ottimizzato usando uno qualsiasi degli altri metodi di distribuzione comuni, ad esempio il portale Foundry o interfaccia della riga di comando di Azure.

Variabile Definizione
Token Esistono diversi modi per generare un token di autorizzazione. Il metodo più semplice per i test iniziali consiste nell'avviare il Cloud Shell dal portale di Azure. Quindi eseguire az account get-access-token. È possibile usare questo token come token di autorizzazione temporaneo per il test dell'API. È consigliabile archiviare questo valore in una nuova variabile di ambiente
Sottoscrizione ID sottoscrizione per la risorsa OpenAI associata Azure
gruppo di risorse Nome del gruppo di risorse per la risorsa Azure OpenAI
nome_risorsa Nome della risorsa OpenAI Azure
model_deployment_name Nome personalizzato per la nuova distribuzione del modello affinato. Questo è il nome a cui viene fatto riferimento nel codice quando si effettuano chiamate di completamento della chat.
modello ottimizzato Recuperare questo valore dai risultati del processo di ottimizzazione nel passaggio precedente. Sembra che sia gpt-4o-mini-2024-07-18.ft-0e208cf33a6a466994aff31a08aba678. È necessario aggiungere tale valore al deploy_data json.

Importante

Dopo aver distribuito un modello personalizzato, se in qualsiasi momento la distribuzione rimane inattiva per più di 15 giorni, la distribuzione viene eliminata. La distribuzione di un modello personalizzato è inattiva se il modello è stato distribuito più di 15 giorni fa e non sono stati effettuati completamenti di chat o chiamate API di risposta durante un periodo di 15 giorni continuo.

L'eliminazione di una distribuzione inattiva non elimina o influisce sul modello personalizzato sottostante. Il modello personalizzato può essere ridistribuibile in qualsiasi momento.

Come descritto nei prezzi dei modelli Microsoft Foundry in Azure OpenAI, ogni modello personalizzato (ottimizzato) distribuito comporta un costo di hosting orario, indipendentemente dall'esecuzione di completamenti chat o chiamate API al modello. Per altre informazioni sulla pianificazione e la gestione dei costi con Azure OpenAI, vedere Plan e gestire i costi per Azure OpenAI.

# Deploy fine-tuned model

import json
import requests

token = os.getenv("TEMP_AUTH_TOKEN")
subscription = "<YOUR_SUBSCRIPTION_ID>"
resource_group = "<YOUR_RESOURCE_GROUP_NAME>"
resource_name = "<YOUR_AZURE_OPENAI_RESOURCE_NAME>"
model_deployment_name = "gpt-4o-mini-2024-07-18-ft" # Custom deployment name you chose for your fine-tuning model

deploy_params = {'api-version': "2024-10-01"} # Control plane API version
deploy_headers = {'Authorization': 'Bearer {}'.format(token), 'Content-Type': 'application/json'}

deploy_data = {
    "sku": {"name": "standard", "capacity": 1},
    "properties": {
        "model": {
            "format": "OpenAI",
            "name": "<YOUR_FINE_TUNED_MODEL>", #retrieve this value from the previous call, it will look like gpt-4o-mini-2024-07-18.ft-0e208cf33a6a466994aff31a08aba678
            "version": "1"
        }
    }
}
deploy_data = json.dumps(deploy_data)

request_url = f'https://management.azure.com/subscriptions/{subscription}/resourceGroups/{resource_group}/providers/Microsoft.CognitiveServices/accounts/{resource_name}/deployments/{model_deployment_name}'

print('Creating a new deployment...')

r = requests.put(request_url, params=deploy_params, headers=deploy_headers, data=deploy_data)

print(r)
print(r.reason)
print(r.json())

È possibile verificare lo stato di avanzamento della distribuzione nel portale Foundry.

Non è insolito che questo processo possa richiedere del tempo per il completamento quando si gestiscono la distribuzione di modelli ottimizzati.

Usare un modello personalizzato distribuito

Dopo aver distribuito il modello ottimizzato, è possibile usarlo come qualsiasi altro modello distribuito nel portale chat Playground of Foundry o tramite l'API di completamento della chat. Ad esempio, è possibile inviare una chiamata di completamento della chat al modello distribuito, come illustrato nell'esempio di Python seguente. È possibile continuare a usare gli stessi parametri con il modello personalizzato, ad esempio temperatura e max_tokens, come è possibile usare altri modelli distribuiti.

# Use the deployed customized model

import os
from openai import OpenAI

client = OpenAI(
  api_key = os.getenv("AZURE_OPENAI_API_KEY"),  
  base_url="https://YOUR-RESOURCE-NAME.openai.azure.com/openai/v1/"
)

response = client.chat.completions.create(
    model = "gpt-4o-mini-2024-07-18-ft", # model = "Custom deployment name you chose for your fine-tuning model"
    messages = [
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Does Azure OpenAI support customer managed keys?"},
        {"role": "assistant", "content": "Yes, customer managed keys are supported by Azure OpenAI."},
        {"role": "user", "content": "Do other Azure services support this too?"}
    ]
)

print(response.choices[0].message.content)

Eliminare una distribuzione

A differenza di altri tipi di modelli OpenAI Azure, i modelli ottimizzati o personalizzati hanno costi di hosting orari associati a essi dopo la distribuzione. Una volta completato questo tutorial e dopo aver testato alcune chiamate di completamento della chat sul modello ottimizzato, è fortemente consigliato che si elimini la distribuzione del modello.

L'eliminazione della distribuzione non influirà sul modello stesso, quindi puoi ridistribuire il modello ottimizzato che hai addestrato per questo tutorial in qualsiasi momento.

È possibile eliminare la distribuzione nel portale di Foundry tramite APIREST, interfaccia della riga di comando di Azure o altri metodi di distribuzione supportati.

Risoluzione dei problemi

Come si abilita l'ottimizzazione? L'opzione per creare un modello personalizzato è disattivata.

Per accedere correttamente all'ottimizzazione è necessario avere il ruolo di Azure AI User assegnato. Anche un utente con autorizzazioni di amministratore del servizio di alto livello avrebbe comunque bisogno di questo account impostato in modo esplicito per accedere all'ottimizzazione. Per altre informazioni, vedere le linee guida per il controllo degli accessi in base al ruolo.

Passaggi successivi