Tutorial de afinação do Azure OpenAI GPT-4o-mini: clássico

Aplica-se somente a:Portal Foundry (clássico). Este artigo não está disponível para o novo portal do Foundry. Saiba mais sobre o novo portal.

Nota

Links neste artigo podem abrir conteúdo na nova documentação do Microsoft Foundry em vez da documentação da Foundry (clássica) que você está exibindo agora.

Este tutorial guia você no ajuste fino de um modelo gpt-4o-mini-2024-07-18.

Neste tutorial, você aprenderá a:

  • Crie conjuntos de dados de exemplo de ajuste fino.
  • Crie variáveis de ambiente para o endpoint de recurso e a chave de API.
  • Prepare seus conjuntos de dados de treinamento e validação de exemplo para ajuste fino.
  • Carregue o arquivo de treinamento e o arquivo de validação para ajuste fino.
  • Crie um trabalho de ajuste fino para gpt-4o-mini-2024-07-18.
  • Implantar um modelo personalizado aperfeiçoado.

Pré-requisitos

Importante

Recomendamos revisar as informações de preços para ajuste fino para se familiarizar com os custos associados. O teste deste tutorial resultou na cobrança de 48.000 tokens (4.800 tokens de treinamento * 10 épocas de treinamento). Os custos de treinamento são além dos custos associados à inferência de ajuste fino e aos custos de hospedagem por hora de ter um modelo ajustado implantado. Depois de concluir o tutorial, você deve excluir sua implantação de modelo ajustado, senão continuará a incorrer no custo de hospedagem por hora.

Configurar

bibliotecas de Python

Este tutorial fornece exemplos de alguns dos recursos mais recentes do OpenAI, que incluem semente/eventos/pontos de verificação. Para aproveitar esses recursos, talvez seja necessário executar pip install openai --upgrade para atualizar para a versão mais recente.

pip install openai requests tiktoken numpy

Recuperar chave e ponto de extremidade

Para fazer uma chamada com êxito contra Azure OpenAI, você precisa de um endpoint e um key.

Nome da variável Valor
ENDPOINT O ponto de extremidade de serviço pode ser encontrado na seção Keys & Endpoint ao visualizar o recurso no portal do Azure. Como alternativa, você pode encontrar o endpoint por meio da página Deployments no portal Microsoft Foundry. Um exemplo de ponto de extremidade é: https://docs-test-001.openai.azure.com/.
API-KEY Esse valor pode ser encontrado na seção Keys & Endpoint ao examinar seu recurso no portal do Azure. Você pode usar um KEY1 ou KEY2.

Acesse seu recurso no portal do Azure. A seção Chaves & Ponto de Extremidade está localizada em Gerenciamento de Recursos. Copie o ponto de extremidade e a chave de acesso, pois você precisará de ambos para autenticar suas chamadas à API. Você pode usar um KEY1 ou KEY2. Sempre ter duas chaves permite girar e regenerar chaves com segurança sem causar uma interrupção de serviço.

Screenshot da interface da visão geral de um recurso Azure OpenAI no portal Azure com o ponto de extremidade e a localização das chaves de acesso circulados em vermelho.

Variáveis de ambiente

Crie e atribua variáveis de ambiente persistentes para sua chave e ponto de extremidade.

Importante

Use chaves de API com cuidado. Não inclua a chave de API diretamente em seu código e nunca a publique publicamente. Se você usar uma chave de API, armazene-a com segurança em Azure Key Vault. Para obter mais informações sobre como usar chaves de API com segurança em seus aplicativos, consulte as chaves API com Azure Key Vault.

Para obter mais informações sobre a segurança dos serviços de IA, consulte Autenticar solicitações para Serviços de IA do Azure.

setx AZURE_OPENAI_API_KEY "REPLACE_WITH_YOUR_KEY_VALUE_HERE"

Criar um conjunto de dados de exemplo

O ajuste fino gpt-4o-mini-2024-07-18 requer um arquivo de treinamento JSONL especialmente formatado. O OpenAI fornece o seguinte exemplo em sua documentação:

{"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."}]}

Para este exemplo, modificamos isso ligeiramente alterando para:

{"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."}]}

Embora esses três exemplos sejam úteis para fornecer o formato geral, se você quiser orientar seu modelo personalizado ajustado para responder de maneira semelhante, você precisaria de mais exemplos. Geralmente, você deseja que pelo menos 50 exemplos de alta qualidade comecem. No entanto, é totalmente possível ter um caso de uso que possa exigir 1.000 exemplos de treinamento de alta qualidade para ter êxito.

Em geral, dobrar o tamanho do conjunto de dados pode levar a um aumento linear na qualidade do modelo. Mas tenha em mente que exemplos de baixa qualidade podem afetar negativamente o desempenho. Se você treinar o modelo em uma grande quantidade de dados internos sem antes podar o conjunto de dados apenas para os exemplos de mais alta qualidade, você poderá acabar com um modelo que tenha um desempenho pior do que o esperado.

Você precisará criar dois arquivos training_set.jsonl e validation_set.jsonl.

Crie os arquivos no mesmo diretório que você está executando o Jupyter Notebook e copie o conteúdo dos seguintes blocos de código para os arquivos correspondentes:

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?"}]}

Agora você precisa executar algumas verificações preliminares em nossos arquivos de treinamento e validação.

# 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)

Saída:

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!"}

Nesse caso, temos apenas 10 exemplos de treinamento e 10 de validação, portanto, embora isso demonstre a mecânica básica de ajustar um modelo, isso dificilmente será um grande número suficiente de exemplos para produzir um efeito consistentemente perceptível.

Agora você pode usar a biblioteca tiktoken para validar as contagens de token. A contagem de tokens usando esse método não fornecerá as contagens exatas de tokens usadas para ajuste fino, mas deve fornecer uma boa estimativa.

Nota

Exemplos individuais precisam permanecer abaixo do tamanho do contexto do exemplo de treinamento atual do modelo de: 64.536 tokens. O limite de tokens de entrada do modelo continua sendo 128.000 tokens.

# 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)

Saída:

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
****************************

Carregar arquivos de ajuste fino

# 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)

Saída:

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

Iniciar ajuste fino

Agora que os arquivos de ajuste fino são carregados com êxito, você pode enviar seu trabalho de treinamento de ajuste fino:

Neste exemplo, também estamos passando o parâmetro de semente. A semente controla a reprodutibilidade do trabalho. Passar os mesmos parâmetros de semente e trabalho deve produzir os mesmos resultados, mas pode diferir em casos raros. Se uma semente não for especificada, uma será gerada para você.

# 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))

Saída:

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
}

Acompanhar o status da tarefa de treinamento

Se você quiser verificar o status do trabalho de treinamento até que ele seja concluído, você pode executar:

# 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.')

Saída:

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
}

Não é incomum que o treinamento leve mais de uma hora para ser concluído. Depois que o treinamento for concluído, a mensagem de saída será alterada para algo como:

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

Listar eventos de ajuste fino

Versão da API: 2024-08-01-preview ou posterior é necessária para este comando.

Embora não seja necessário concluir o ajuste fino, pode ser útil examinar os eventos de ajuste fino individuais que foram gerados durante o treinamento. Os resultados completos do treinamento também podem ser examinados após a conclusão do treinamento no arquivo de resultados do treinamento.

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

Saída:

{
  "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"
}

Listar pontos de verificação

Versão da API: 2024-08-01-preview ou posterior é necessária para este comando.

Quando cada época de treinamento é concluída, um ponto de verificação é gerado. Um ponto de verificação é uma versão totalmente funcional de um modelo que pode ser implantado e usado como o modelo de destino para trabalhos de ajuste fino subsequentes. Os pontos de verificação podem ser úteis, pois podem fornecer um instantâneo do modelo antes da sobreajuste ter ocorrido. Quando um trabalho de ajuste fino for concluído, você terá as três versões mais recentes do modelo disponíveis para implantação. A época final será representada pelo seu modelo ajustado, as duas épocas anteriores estão disponíveis como pontos de verificação.

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

Saída:

{
  "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"
}

Resultados do último treinamento

Para obter os resultados finais, execute o seguinte:

# 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

Implantar modelo refinado

Ao contrário dos comandos anteriores do SDK Python neste tutorial, uma vez que a introdução do recurso de cota, a implantação de modelo deve ser feita usando a API REST, que requer autorização separada, um caminho de API diferente e uma versão de API diferente.

Como alternativa, você pode implantar seu modelo ajustado usando qualquer um dos outros métodos de implantação comuns, como o portal Foundry ou CLI do Azure.

variável Definição
símbolo Há várias maneiras de gerar um token de autorização. O método mais fácil para o teste inicial é iniciar o Cloud Shell do Azure portal. Em seguida, execute az account get-access-token. Você pode usar esse token como seu token de autorização temporária para teste de API. Recomendamos armazenar isso em uma nova variável de ambiente
assinatura O ID da assinatura do recurso Azure OpenAI associado
grupo de recursos O nome do grupo de recursos do seu recurso Azure OpenAI
Resource_name O nome do recurso Azure OpenAI
model_deployment_name O nome personalizado para sua nova implantação de modelo ajustado. Esse é o nome referenciado em seu código ao fazer chamadas de conclusão de chat.
modelo_ajustado Recupere esse valor dos resultados do trabalho de ajuste fino na etapa anterior. Parece que gpt-4o-mini-2024-07-18.ft-0e208cf33a6a466994aff31a08aba678. Você precisa adicionar esse valor ao json deploy_data.

Importante

Depois de implantar um modelo personalizado, se a qualquer momento a implantação permanecer inativa por mais de 15 dias, a implantação será excluída. A implantação de um modelo personalizado será inativa se o modelo tiver sido implantado há mais de 15 dias e nenhuma conclusão de chat ou chamadas à API de resposta tiverem sido feitas durante um período contínuo de 15 dias.

A exclusão de uma implantação inativa não exclui ou afeta o modelo personalizado subjacente. O modelo personalizado pode ser reimplantado a qualquer momento.

Conforme descrito em Azure OpenAI em preços de Modelos de Fábrica da Microsoft, cada modelo personalizado (ajustado) implantado incorre em um custo de hospedagem por hora, independentemente de serem feitas conclusões de chat ou chamadas à API de resposta ao modelo. Para saber mais sobre como planejar e gerenciar custos com Azure OpenAI, consulte Plano e gerencie custos para 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())

Você pode verificar o progresso da implantação no portal do Foundry.

Não é incomum que esse processo leve algum tempo para ser concluído ao lidar com a implantação de modelos ajustados.

Usar um modelo personalizado implantado

Depois que seu modelo ajustado for implantado, você poderá usá-lo como qualquer outro modelo implantado no portal do Chat Playground of Foundry ou por meio da API de conclusão do chat. Por exemplo, você pode enviar uma chamada de conclusão de chat para seu modelo implantado, conforme mostrado no exemplo de Python a seguir. Você pode continuar a usar os mesmos parâmetros com seu modelo personalizado, como temperatura e max_tokens, como você pode com outros modelos implantados.

# 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)

Excluir implantação

Ao contrário de outros tipos de modelos Azure OpenAI, modelos ajustados/personalizados têm um custo de hospedagem por hora associados a eles quando são implantados. É altamente recomendável que, após concluir este tutorial e testar algumas chamadas de conclusão de chat no seu modelo ajustado, exclua a implantação do modelo.

Excluir a implantação não afetará o próprio modelo, portanto, você pode implantar novamente o modelo ajustado que você treinou para este tutorial a qualquer momento.

Você pode excluir a implantação no portal Foundry, por meio da API REST, CLI do Azure ou outros métodos de implantação com suporte.

Solucionando problemas

Como habilitar o ajuste fino? Criar modelo personalizado está desativado.

Para acessar com êxito o ajuste fino, você precisa do papel de usuário do Azure AI atribuído. Mesmo alguém com permissões de Administrador de Serviços de alto nível ainda precisaria dessa conta definida explicitamente para acessar o ajuste fino. Para obter mais informações, examine as diretrizes de controle de acesso baseadas em função.

Próximas etapas