Pular para o conteúdo principal

Tutorial do Tinker: Ajustando LLMs com o Thinking Machines Lab

Um guia prático para implementar LoRA via Tinker AI para treinar Qwen3-8B em dados financeiros Chain-of-Thought (CoT), otimizando para generalização.
Atualizado 5 de dez. de 2025  · 15 min lido

Ajustar modelos de linguagem grandes geralmente significa lidar com infraestrutura de GPU distribuída, gerenciar falhas de cluster e depurar scripts de treinamento complexos. O Tinker está aqui para resolver isso.

Lançado pelo Thinking Machines Lab de Mira Murati em 1º de outubro de 2025, o Tinker é uma API de treinamento que cuida de toda a complexidade da infraestrutura, enquanto te dá controle total sobre seus algoritmos e dados. Ele permite escrever scripts Python simples com quatro funções principais, e o Tinker os executa em GPUs distribuídas para uma ampla gama de modelos de código aberto, como Llama 70B ou Qwen 235B.

A plataforma usa o ajuste fino Low-Rank Adaptation (LoRA) para reduzir custos e dá suporte a tudo, desde aprendizado supervisionado até aprendizado por reforço. Equipes de pesquisa em Princeton, Stanford e Berkeley já usam isso no trabalho delas.

Neste tutorial, vou te mostrar como instalar o Tinker, entender sua API e ajustar um modelo completo de perguntas e respostas financeiras usando o modelo base Qwen3-8B. Se você quiser saber mais sobre ajuste fino com um pouco de prática, recomendo dar uma olhada neste curso sobre Ajuste Fino com Llama 3.

O que é o Tinker?

O Thinking Machines Lab criou o Tinker pra quem quer personalizar como os modelos aprendem sem precisar ser especialista em infraestrutura.

Quem usa o Tinker?

A plataforma é voltada para três grupos: 

  • Pesquisadores explorando um novo método de treinamento
  • Desenvolvedores que criam produtos de IA que precisam de um comportamento personalizado
  • Construtores que querem resultados com qualidade de produção sem precisar de recursos empresariais. 

Cada grupo tem uma necessidade em comum: eles sabem o que querem que seu modelo faça, mas as interfaces padrão de ajuste fino não oferecem controle suficiente.

Por exemplo, equipes acadêmicas poderiam usá-lo para testar novos algoritmos. Um laboratório de química pode querer treinar modelos em padrões de raciocínio específicos do domínio que não se encaixam nos modelos típicos de ajuste de instruções. Uma startup que tá criando um bot de consultoria financeira pode usar o modelo pra seguir formatos de saída e cadeias de raciocínio específicos. 

Todos esses casos de uso têm em comum a necessidade de modificar o próprio processo de treinamento, e não apenas trocar conjuntos de dados.

O que torna a Tinker diferente?

A maioria das plataformas é otimizada para uma de duas coisas: facilidade de uso ou flexibilidade. A ideia do Tinker é que esses dois não precisam entrar em conflito. A plataforma te dá acesso básico ao treinamento por meio de quatro operações principais, mas cuida de tudo o mais automaticamente:

  • Controle do ciclo de treinamento: Escreva suas próprias funções de perda, estratégias de acumulação de gradiente ou padrões de amostragem.
  • Atualizações do modelo: Diz exatamente como os pesos devem mudar durante a otimização.
  • Avaliação: Crie resultados ou calcule probabilidades em qualquer momento do treinamento.
  • Gestão do Estado: Salve e retome o treinamento com controle total sobre o que é mantido.

O Tinker mostra essas funcionalidades por meio de quatro primitivas de API que você vai aprender na seção prática.

Isso funciona porque a maior parte da complexidade do treinamento vem da infraestrutura, não dos próprios algoritmos. É bem fácil rodar um circuito de treinamento personalizado no seu laptop. É complicado rodar isso em 100 GPUs com recuperação de falhas e programação de recursos. O Tinker cuida da segunda parte, pra você poder se concentrar na primeira.

Onde o Tinker se encaixa

O ecossistema de ferramentas de IA divide-se, grosso modo, em três camadas: provedores de computação em nuvem que fornecem GPUs brutas, plataformas gerenciadas que executam fluxos de trabalho predefinidos e estruturas que ajudam a construir sistemas de treinamento a partir do zero. O Tinker fica entre as plataformas gerenciadas e as estruturas. Você tem mais controle do que em plataformas como o Hugging Face AutoTrain, mas menos trabalho de infraestrutura do que configurar um cluster personalizado no Google Cloud Platform (GCP).

Preços e créditos do Tinker

A Tinker usa preços transparentes por token, que variam de acordo com o tamanho do modelo e o tipo de operação. O treinamento do Qwen3-8B custa US$ 0,40 por milhão de tokens. A boa notícia: os novos usuários ganham US$ 150 em créditos quando saem da lista de espera, o que dá pra cobrir várias tentativas de treinamento, tipo o que a gente faz neste tutorial.

Preços do Tinker

Fonte: Laboratório de Máquinas Pensantes

Exemplo do Tinker: Ajustando o Qwen3-8B para perguntas e respostas financeiras

Nesta seção, vou te mostrar como criar um fluxo de trabalho completo de ajuste fino usando a API do Tinker. Vamos ajustar o Qwen3-8B em dados de perguntas e respostas financeiras usando LoRA, aprendendo todas as quatro primitivas API principais enquanto vemos as melhores práticas de produção em ação.

Pra ver mais detalhes sobre o modelo, dá uma olhada nesse artigo sobre o Qwen3.

Observação importante: O Tinker está atualmente em fase beta, com acesso baseado em lista de espera. Se você tiver acesso, pode acompanhar e executar o código. Se você está na lista de espera, ainda pode ler para entender como o processo de ajuste fino é simples com o Tinker. Quando você conseguir acesso, vai estar pronto para começar imediatamente.

Resultados do treinamento e escolha dos pontos de verificação

Vamos ver primeiro os resultados do treinamento. Entender o que rolou ajuda você a identificar esses padrões quando estiver fazendo seus próprios ajustes.

A gente ajustou o Qwen3-8B no conjunto de dados FinCoT, um conjunto de dados de raciocínio financeiro em cadeia de pensamentos, usando LoRA com classificação 32. Depois de filtrar as sequências com menos de 10.000 tokens, ficamos com 7.244 exemplos de treinamento e 500 exemplos de validação para trabalhar. 

O treinamento rolou por 904 iterações em quatro épocas, levando mais ou menos três horas pra terminar, enquanto o Tinker cuidou de toda a coordenação distribuída da GPU. 

Uma época é uma passagem completa por todo o conjunto de dados de treinamento. Como havia 226 lotes por época (7.244 exemplos divididos por um tamanho de lote de 32), as quatro épocas totalizaram 904 iterações.

Analisando os resultados do treinamento

O gráfico a seguir acompanha três métricas: perda de treinamento (azul), perda de validação (vermelho) e taxa de aprendizagem (verde).

Progresso do treinamento do Tinker

O treinamento inicial (iterações 0-400) parece estar indo bem: tanto as perdas de treinamento quanto as de validação caem bastante, passando de mais de 1,4 para menos de 0,8 e ficando a cerca de 0,1 uma da outra. 

Esse período mostra que o modelo está realmente aprendendo padrões generalizáveis nos dados. A taxa de aprendizagem (linha verde) aumenta suavemente durante as primeiras 200 iterações, à medida que o aquecimento estabiliza o treinamento. 

Por volta da iteração 400-600, as coisas mudam. A perda de treinamento continua caindo, mas a perda de validação fica em torno de 0,75-0,8, aumentando a diferença para 0,13 à medida que o modelo começa a se ajustar demais. Entre as iterações 600 e 900, a diferença fica bem clara: a perda de treinamento cai para 0,39, enquanto a perda de validação sobe para 0,8, mostrando que o modelo passou de aprender padrões de raciocínio para memorizar exemplos. 

Esse padrão é comum quando a gente está ajustando modelos de raciocínio. Se você estiver interessado em saber como as capacidades de raciocínio se desenvolvem durante o treinamento, fique à vontade para conferir este tutorial sobre o ajuste fino do DeepSeek R1, que explora dinâmicas semelhantes.

O modelo salvo no ponto de verificação 400 oferece a melhor generalização, com uma perda de treinamento de 0,68, uma perda de validação de 0,73 e uma pequena diferença de apenas 0,05. Mesmo tendo uma perda de treinamento maior do que o ponto de verificação final, ele lida melhor com novas perguntas. A menor perda de treinamento raramente produz o melhor modelo, e é por isso que as métricas de validação são importantes.

Vamos conferir isso depois, comparando o modelo no ponto de verificação 400 com o modelo base usando um juiz LLM. Por enquanto, vamos ver como montar esse fluxo de trabalho de ajuste fino, passo a passo.

Passo 1: Instale o Tinker e configure seu ambiente

Observação: Os passos a seguir detalham um script com cerca de 300 linhas. Como as explicações estão espalhadas pelo código, a gente recomenda que você abra o script completo em uma nova aba enquanto acompanha, pra ter uma visão geral.

Você vai precisar do Python 3.11 ou mais recente e uma conexão de internet estável. Como a Tinker cuida do treinamento da GPU nos seus servidores, ter uma boa conexão é mais importante do que ter um computador potente.

Comece fazendo login no console do Tinker e criando uma chave API. Guarde essa chave em um arquivo .env no diretório do seu projeto:

TINKER_API_KEY=your_key_here

Depois de configurar, o console Tinker vira o seu centro de controle pra monitorar os treinos e gerenciar os pontos de verificação.

Treinos de corrida do Tinker

O console Tinker programa todos os seus treinos com detalhes importantes: IDs exclusivos de corrida, modelo básico, classificação LoRA e horários das últimas solicitações. Você pode pesquisar, filtrar e gerenciar várias experiências a partir desta interface.

Instale os pacotes necessários. O Tinker tem dependências que não são instaladas automaticamente, então especifique-as explicitamente:

pip install tinker transformers torch datasets python-dotenv numpy

Os pacotes transformers e torch oferecem tokenização e operações tensoriais que o Tinker usa internamente. A biblioteca datasets cuida de carregar os dados de treinamento do Hugging Face.

Agora configure suas importações e uma função auxiliar de repetição. Ao treinar em servidores remotos, você pode ver erros temporários de conexão que não querem dizer necessariamente que algo esteja errado. Seu trabalho de treinamento continua funcionando bem na infraestrutura da Tinker. Esse wrapper de repetição lida com essas falhas temporárias automaticamente:

import time
import numpy as np
from dotenv import load_dotenv
from datasets import load_dataset
import tinker
from tinker import types

def with_retry(future, max_attempts=3, delay=5):
    """Simple retry logic for API futures"""
    for attempt in range(max_attempts):
        try:
            return future.result()
        except Exception as e:
            if attempt == max_attempts - 1:
                raise
            time.sleep(delay)

Essa função pega um objeto futuro (um objeto que representa uma operação que ainda não foi concluída) da API do Tinker e tenta obter o resultado até três vezes, esperando cinco segundos entre cada tentativa. A maioria das falhas temporárias é resolvida dentro desse período.

Passo 2: Inicialize o ServiceClient e carregue o conjunto de dados

O ServiceClient é o seu ponto de entrada para o Tinker. Ele encontra os modelos disponíveis e cuida da autenticação:

load_dotenv()
service_client = tinker.ServiceClient()

Você não vai usar muito depois dessa configuração inicial. Ele existe principalmente para criar treinamento e amostragem de clientes para modelos específicos.

Agora é hora de carregar o conjunto de dados FinCoT.  Tem perguntas financeiras com raciocínios passo a passo e respostas finais. Diferente dos conjuntos de dados simples de perguntas e respostas, o FinCoT ensina o modelo a mostrar como ele trabalha antes de dar as respostas, uma habilidade importante para aplicações de consultoria financeira, onde os usuários precisam entender o raciocínio por trás das recomendações.

dataset = load_dataset("TheFinAI/FinCoT")

Conjunto de dados Tinker

O conjunto de dados inclui uma divisão SFT (ajuste fino supervisionado) com cadeias de raciocínio completas e uma divisão RL para aprendizagem por reforço. Use a divisão SFT para treinamento e amostras da divisão RL para validação, pra garantir que o modelo se generalize para diferentes exemplos.

train_data_raw = dataset["SFT"]  # 7,686 examples with reasoning chains
val_data_raw = dataset["RL"].shuffle(seed=42).select(range(500))  # 500 validation examples

print(f"Loaded {len(train_data_raw)} training examples")
print(f"Loaded {len(val_data_raw)} validation examples")

Passo 3: Crie seu cliente de treinamento LoRA

Agora crie um cliente de treinamento LoRA para Qwen3-8B:

training_client = service_client.create_lora_training_client(
    base_model="Qwen/Qwen3-8B",
    rank=32
)

Vamos ver o que tá rolando aqui:

  • Qwen/Qwen3-8B: Esse é um modelo com 8 bilhões de parâmetros, poderoso o suficiente para tarefas reais, mas ainda assim eficiente para treinar. É um ótimo lugar pra aprender. Você pode ver todos os modelos disponíveis na linha de modelos da Tinker.
  • LoRA (Adaptação de Baixo Rank): Em vez de atualizar todos os 8 bilhões de parâmetros, o LoRA treina pequenas camadas “adaptadoras” que mudam o comportamento do modelo básico. Pense nisso como adicionar lentes personalizadas a uma câmera, em vez de reconstruir toda a câmera. O modelo básico fica congelado nos servidores da Tinker; você só está treinando esses pequenos adaptadores.
  • rank=32: Isso controla quantos parâmetros treináveis seus adaptadores LoRA têm. Uma classificação de 32 funciona bem para conjuntos de dados com 5.000 a 10.000 exemplos. Conjuntos de dados maiores podem precisar de rank=64 ou rank=128 para ter mais capacidade de adaptação.

Pra saber mais sobre como o LoRA funciona matematicamente e como ajustar os parâmetros de classificação, dá uma olhada no manual básico do LoRA da Tinker ou no guia completo do DataCamp sobre como dominar a adaptação de baixa classificação.

Os modelos de linguagem não funcionam diretamente com palavras. Eles usam “tokens”, que são pedaços de texto. Um tokenizador divide o texto nessas partes. Por exemplo, “financeiro” pode virar um único token, enquanto “compreensão” pode ser dividido em “compreensão” e “entendimento”. 

O tokenizador transforma o texto em números (IDs de token) que o modelo consegue processar. Ao pegar o tokenizador do cliente de treinamento, você garante que vai usar exatamente a mesma tokenização que o Qwen3-8B espera.

tokenizer = training_client.get_tokenizer()

Passo 4: Transforme os dados no formato do Tinker

O Tinker precisa dos seus dados de treinamento num formato chamado types.Datum. Esse formato separa a entrada do modelo da configuração da função de perda, permitindo um controle preciso sobre quais tokens contribuem para o treinamento.

Ao ajustar modelos de linguagem, você normalmente quer que o modelo aprenda a gerar boas respostas, não memorizar as perguntas. Você faz isso dando pesos diferentes para diferentes partes da entrada:

  • Tokens de prompt (a pergunta): Peso = 0,0 (não aprenda com estes)
  • Tokens de conclusão (a resposta): Peso = 1,0 (aprenda com isso)

Vamos construir a função de transformação de dados passo a passo. Comece com a assinatura da função:

def prepare_datum(example, max_length=10000):

Formate a conversa usando o modelo de chat do Qwen3.

Modelos diferentes esperam formatações diferentes para conversas com várias voltas. O Qwen3 usa tokens especiais como <|im_start|> e <|im_end|> para marcar os limites das mensagens. Divida cada mensagem em partes de “observação” (cabeçalhos de função) e partes de “ação” (conteúdo real):

   # Qwen3 chat format - split into observation and action parts
    user_ob = "<|im_start|>user\n"
    user_ac = f"{example['Question']}<|im_end|>"
    
    assistant_ob = "\n<|im_start|>assistant\n"
    # Include both reasoning process AND final answer
    assistant_ac = f"{example['Reasoning_process']}\n\nFinal Answer: {example['Final_response']}<|im_end|>"

O conjunto de dados FinCoT fornece um raciocínio estruturado, por isso concatenamos os campos Reasoning_process e Final_response. Isso ensina o modelo a mostrar seu trabalho antes de responder.

Tokenize cada parte separadamente 

Para saber onde os pesos devem ser aplicados, cada parte da mensagem precisa ser tokenizada separadamente.

   # Tokenize each part separately to track weight boundaries
    user_ob_tokens = tokenizer.encode(user_ob, add_special_tokens=False)
    user_ac_tokens = tokenizer.encode(user_ac, add_special_tokens=False)
    assistant_ob_tokens = tokenizer.encode(assistant_ob, add_special_tokens=False)
    assistant_ac_tokens = tokenizer.encode(assistant_ac, add_special_tokens=False)
    
    # Combine all tokens
    all_tokens = user_ob_tokens + user_ac_tokens + assistant_ob_tokens + assistant_ac_tokens

Filtrar sequências que são muito longas

Alguns exemplos do FinCoT incluem cadeias de raciocínio super detalhadas que vão além da janela de contexto confortável do Qwen3-8B. Embora o modelo suporte tecnicamente 32.768 tokens, sequências muito longas podem causar instabilidade no treinamento:

   # Check if sequence exceeds max length
    if len(all_tokens) > max_length:
        return None  # Skip this example

Filtrar exemplos acima de 10.000 tokens tira só 5-6% dos dados, mas melhora bastante a confiabilidade do treinamento.

Atribua pesos para controlar o que o modelo aprende a partir de

Defina pesos de 0,0 para a pergunta (para que o modelo não aprenda a prever perguntas) e 1,0 para a resposta (para que ele aprenda a gerar boas respostas):

   # Weights: only train on assistant's answer (action part)
    weights = np.array(
        [0.0] * len(user_ob_tokens) +
        [0.0] * len(user_ac_tokens) +
        [0.0] * len(assistant_ob_tokens) +
        [1.0] * len(assistant_ac_tokens)
    )

Tokens de mudança e pesos para previsão do próximo token

Os modelos de linguagem tentam adivinhar o próximo símbolo numa sequência. Quando o modelo vê os tokens 0-9, ele deve prever o token 10. Então, seus alvos são sempre os dados inseridos deslocados uma posição para a frente:

   # CRITICAL: Shift tokens AND weights for next-token prediction
    input_tokens_model = all_tokens[:-1]
    target_tokens = all_tokens[1:]
    weights_shifted = weights[1:]

É importante lembrar que você precisa mover os pesos junto com as fichas. Se você não mudar os pesos, o cálculo da perda vai ficar errado: o modelo seria treinado para prever o primeiro token de resposta, mas o peso nessa posição ainda seria 0,0 do prompt. Esse bug de alinhamento pode impedir que o modelo aprenda direito.

Retorne os dados formatados

Por fim, a função prepare_datum() devolve os dados formatados, que estão prontos para serem processados.

   return types.Datum(
        model_input=types.ModelInput.from_ints(tokens=input_tokens_model),
        loss_fn_inputs=dict(weights=weights_shifted, target_tokens=target_tokens),
    )

Ao aplicar a função definida aos subconjuntos brutos de treinamento e validação, agora podemos processar todo o conjunto de dados:

# Process and filter training data
training_data_raw = [prepare_datum(example) for example in train_data_raw]
training_data = [d for d in training_data_raw if d is not None]
skipped = len(train_data_raw) - len(training_data)

print(f"Processed {len(training_data)} examples (skipped {skipped} too-long sequences)")
# Output: Processed 7,244 examples (skipped 442 too-long sequences)

# Process validation data
val_data_raw_processed = [prepare_datum(example) for example in val_data_raw]
val_data = [d for d in val_data_raw_processed if d is not None]

print(f"Validation set: {len(val_data)} examples")

Passo 5: Treine o modelo com o loop de treinamento

O loop de treinamento usa duas das primitivas principais da API do Tinker: forward_backward e optim_step. Vamos construir passo a passo com acompanhamento de validação para detectar o sobreajuste logo no início.

Defina uma função de perda de validação

Primeiro, defina uma função de perda de validação. Diferente da perda de treinamento, a perda de validação mostra como seu modelo se sai com dados que você nunca viu antes. Isso é super importante pra detectar o sobreajuste.

def compute_validation_loss(val_data, batch_size=100):
    """Compute loss on validation set (forward only, no backward)"""
    batch_indices = np.random.choice(
        len(val_data), size=min(batch_size, len(val_data)), replace=False
    )
    batch = [val_data[i] for i in batch_indices]
    
    # Forward pass only (no backward!)
    fwd_future = training_client.forward(batch, loss_fn="cross_entropy")
    fwd_result = with_retry(fwd_future)
    
    # Calculate per-token loss
    loss_sum = fwd_result.metrics["loss:sum"]
    total_completion_tokens = sum(
        np.sum(np.array(val_data[i].loss_fn_inputs["weights"].data) > 0)
        for i in batch_indices
    )
    return loss_sum / total_completion_tokens if total_completion_tokens > 0 else 0

O método forward() calcula a perda sem calcular gradientes, tornando-o eficiente para avaliações em que você só precisa medir o desempenho, sem atualizar pesos. Calcular a perda de validação adiciona uma sobrecarga mínima (é só uma passagem para a frente, sem cálculo de gradiente), mas dá um aviso antecipado quando a diferença entre a perda de treinamento e a perda de validação passa de 0,2, mostrando que seu modelo está memorizando em vez de aprender.

Configure os parâmetros de treinamento

Precisamos definir o número de épocas e também a taxa de aprendizagem. O LoRA precisa de taxas de aprendizagem bem maiores do que o ajuste fino completo, geralmente de 10 a 20 vezes maiores.

# Training configuration
n_samples = len(training_data)  # 7,244
n_epochs = 4  # More epochs for smaller dataset
batch_size = 32

# Calculate optimal LR for Qwen3-8B with LoRA
# Formula: base_lr * lora_multiplier * (2000 / hidden_size) ** exponent
# For Qwen: base=5e-5, multiplier=10, hidden_size=4096, exponent=0.0775
learning_rate = 5e-5 * 10.0 * (2000 / 4096) ** 0.0775  # ≈ 4.7e-4

A fórmula leva em conta o tamanho do modelo (o Qwen3-8B tem um tamanho oculto de 4.096) e usa expoentes determinados empiricamente que variam de acordo com a família do modelo, conforme documentado no livro de receitas do Tinker. Para os modelos Qwen, o expoente é 0,0775, enquanto os modelos Llama usam 0,781. No nosso caso, isso gera uma taxa de aprendizagem em torno de 4,7e-4, proporcionando uma convergência estável sem explosões de gradiente.

Configurar o aquecimento da taxa de aprendizagem

Começar com a taxa de aprendizagem total pode causar explosões de gradiente no início do treinamento, quando o modelo ainda não se adaptou à tarefa. O aquecimento aumenta gradualmente a taxa de aprendizagem de quase zero até o valor alvo nas primeiras 200 iterações. Você pode ver isso claramente na visualização do treinamento anterior, onde a taxa de aprendizagem (linha verde) aumenta suavemente durante as primeiras 200 iterações.

# Warmup configuration
warmup_steps = 200

# Calculate iterations from epochs
num_iterations = n_epochs * (n_samples // batch_size)  # 4 * 226 = 904
checkpoint_interval = 200
validation_interval = 50

Comece o ciclo de treinamento

Depois de inicializar as listas para acompanhar as perdas, podemos começar o ciclo de treinamento:

losses = []
per_token_losses = []
val_losses = []

for iteration in range(num_iterations):
    # Sample random batch
    batch_indices = np.random.choice(len(training_data), size=batch_size, replace=False)
    batch = [training_data[i] for i in batch_indices]
    
    # Apply learning rate warmup
    if iteration < warmup_steps:
        current_lr = learning_rate * (iteration + 1) / warmup_steps
    else:
        current_lr = learning_rate

A lógica de aquecimento aumenta gradualmente a taxa de aprendizagem durante as primeiras 200 iterações, evitando instabilidade precoce.

Chamar as primitivas de treinamento principais do Tinker

Em cada iteração, as primitivas principais do Tinker são chamadas assim:

   # API Primitive 1: forward_backward - compute gradients
    fwdbwd_future = training_client.forward_backward(batch, loss_fn="cross_entropy")

    # API Primitive 2: optim_step - update parameters
    optim_future = training_client.optim_step(types.AdamParams(learning_rate=current_lr))

    # Wait for results with retry logic
    fwdbwd_result = with_retry(fwdbwd_future)
    optim_result = with_retry(optim_future)

forward_backward calcula os gradientes descobrindo o quanto as previsões do modelo estão erradas e em que direção ajustar cada parâmetro. Ele usa a perda de entropia cruzada, que mede o quão bem a distribuição de probabilidade prevista pelo modelo corresponde aos próximos tokens reais. O optim_step, na verdade, atualiza os parâmetros do modelo com base nesses gradientes usando o otimizador Adam.

Observe como os dois métodos retornam objetos “futuros” imediatamente. Isso permite que o Tinker processe várias operações ao mesmo tempo, o que deixa tudo mais rápido. Chamar with_retry(future) espera que essa operação específica termine.

Programa e mostre as métricas de perda

Calculamos as perdas totais e por token e as adicionamos às respectivas listas:

   # Track loss
    loss_sum = fwdbwd_result.metrics["loss:sum"]
    total_completion_tokens = sum(
        np.sum(np.array(training_data[i].loss_fn_inputs["weights"].data) > 0)
        for i in batch_indices
    )
    per_token_loss = loss_sum / total_completion_tokens

    losses.append(loss_sum)
    per_token_losses.append(per_token_loss)

    if iteration % 10 == 0:
        warmup_indicator = "🔥" if iteration < warmup_steps else ""
        print(f"{warmup_indicator} Iteration {iteration} | Train: {per_token_loss:.4f} | LR: {current_lr:.6f}")

A perda por token normaliza a perda pelo número de tokens de resposta, facilitando a interpretação em diferentes tamanhos de lote. Um bom treinamento geralmente começa com uma perda por token em torno de 1,2-1,5 e diminui para menos de 0,8 no ponto de verificação 400. Se sua perda começar acima de 2,0 ou não cair depois de 50 iterações, dá uma olhada na preparação dos dados e na taxa de aprendizado.

Adicione verificações de validação e pontos de verificação periódicos

Salvar pesos a cada 200 iterações cria instantâneos que você pode comparar. Como você viu nos resultados do treinamento antes, o checkpoint-400 generaliza melhor do que o checkpoint final, mesmo com uma perda de treinamento maior. Sempre salve vários pontos de verificação e avalie-os com base nos dados retidos. Os pontos de verificação continuam nos servidores do Tinker depois que seu script termina.

   # Compute validation loss periodically
    if iteration % validation_interval == 0:
        val_loss = compute_validation_loss(val_data)
        val_losses.append((iteration, val_loss))
        gap = val_loss - per_token_loss
        print(f"  📊 Iteration {iteration} | Train: {per_token_loss:.4f} | Val: {val_loss:.4f} | Gap: {gap:+.4f}")

    # Checkpoint every 200 iterations
    if iteration > 0 and iteration % checkpoint_interval == 0:
        training_client.save_weights_for_sampler(name=f"fincot-checkpoint-{iteration}")

Pontos de verificação Tinker

O Tinker divide os pontos de verificação em duas categorias: pontos de verificação completos (pra continuar o treinamento se ele for interrompido) e pesos de amostragem (pontos de verificação leves pra inferência). Observe que o checkpoint-400 aparece nos dois formatos, facilitando tanto continuar o treinamento quanto implantar o modelo.

Se o seu treinamento for interrompido, você pode continuar a partir de qualquer ponto de verificação salvo usando os pesos de estado completo, o que permite continuar o treinamento sem precisar recomeçar.

Para ver a referência completa da API sobre esses métodos, dá uma olhada na documentação de treinamento e amostragem do Tinker.

Passo 6: Salve seu modelo ajustado

Depois que o treinamento terminar, salve os pesos finais do modelo:

sampling_client = training_client.save_weights_and_get_sampling_client(
    name="financial-qa-qwen3-8b-lora"
)

Os pesos são salvos nos servidores do Tinker, não no seu computador. Os adaptadores LoRA normalmente só precisam de algumas dezenas de megabytes. O Tinker pode exportá-los opcionalmente para uso com provedores de inferência externos, caso você queira implantar o modelo fora da infraestrutura deles.

Passo 7: Teste o modelo com perguntas de exemplo

Por fim, teste seu modelo ajustado usando a primitiva da API sample. Primeiro, configure os parâmetros de amostragem:

sampling_params = types.SamplingParams(
    max_tokens=200,
    temperature=0.7,
    top_p=0.9,
    stop_sequences=["<|im_end|>"]
)

Vamos explicar esses parâmetros:

  • max_tokens: Comprimento máximo da resposta gerada (200 tokens equivalem a cerca de 150 palavras).
  • temperature: Controla a aleatoriedade na geração. Valores mais baixos (0,1-0,5) tornam a saída mais focada e determinística; valores mais altos (0,7-1,0) permitem maior criatividade. Um valor de 0,7 equilibra a coerência com a variedade.
  • top_p (amostragem do núcleo): Só considera o menor conjunto de tokens cuja probabilidade acumulada passa desse valor. 0,9 quer dizer “só considerar tokens que, juntos, representam 90% da massa de probabilidade”. Isso evita que o modelo escolha tokens muito improváveis.
  • stop_sequences: Diz ao modelo para parar de gerar quando chegar nessa sequência. <|im_end|> é o marcador de fim de sequência.

Crie respostas para as perguntas do teste:

questions = [
    "What are the main risks associated with investing in stocks?",
    "How does diversification help reduce portfolio risk?"
]

for question in questions:
    model_input = types.ModelInput.from_ints(tokenizer.encode(question))

    response = with_retry(
        sampling_client.sample(
            prompt=model_input,
            num_samples=1,
            sampling_params=sampling_params
        )
    )

    answer = tokenizer.decode(response.sequences[0].tokens)
    print(f"Q: {question}")
    print(f"A: {answer}\n")

Aqui estão dois exemplos de resultados do modelo ajustado (truncados para ilustração):

Q: What are the main risks associated with investing in stocks?
A: Investing in stocks carries several key risks. Market risk represents the potential for stock prices to decline due to economic downturns or market volatility. Company-specific risk arises from poor management decisions, competitive pressures, or operational failures that affect individual stocks. Liquidity risk occurs when you cannot sell shares quickly without significantly impacting the price. Additionally, investors face inflation risk, where returns may not keep pace with rising prices...

Q: How does diversification help reduce portfolio risk?
A: Diversification reduces portfolio risk by spreading investments across different asset classes, sectors, and geographic regions. When you hold multiple uncorrelated assets, losses in one investment can be offset by gains in others. For example, if technology stocks decline but healthcare stocks rise, a diversified portfolio experiences less volatility than one concentrated in technology alone. The key principle is that different assets respond differently to market conditions...

O objeto de resposta tem uma lista de amostras ( sequences ) porque você pode pedir várias amostras por prompt. Acesse os tokens em sequences[0].tokens e decodifique-os de volta para texto usando o mesmo tokenizador.

Você acabou de mostrar com sucesso todas as quatro primitivas principais da API:

  1. forward_backward: Calcule os gradientes a partir dos seus dados de treinamento
  2. optim_step: Atualize os parâmetros do modelo com o otimizador Adam
  3. save_weights_and_get_sampling_client: Persista no seu modelo ajustado
  4. sample: Gerar previsões a partir do modelo ajustado

Esse mesmo padrão de fluxo de trabalho se estende a paradigmas de treinamento mais avançados. Para o aprendizado por reforço ou otimização de preferências, você usaria os mesmos primitivos, mas com diferentes funções de perda e formatos de dados. Se você está interessado em alinhar modelos com as preferências humanas, confira as técnicas de ajuste fino de preferências, que se baseiam no mesmo fundamento. O livro de receitas do Tinker tem exemplos desses cenários mais avançados.

Mas como saber se o ajuste realmente melhorou o desempenho? Em vez de confiar em impressões subjetivas, você precisa de uma avaliação rigorosa. Na próxima seção, você vai ver os resultados de uma avaliação do LLM como juiz, que compara o modelo ajustado com o Qwen3-8B básico em 10 perguntas financeiras diferentes.

Avaliando o modelo ajustado com um juiz LLM

Para medir o desempenho de forma objetiva, usamos o LLM como juiz: O GPT-4o avaliou o checkpoint-400 em relação ao Qwen3-8B básico em 10 perguntas financeiras diferentes, pontuando cada resposta em termos de precisão, clareza, completude e terminologia financeira.

O modelo ajustado teve uma pontuação de 8,5/10, comparado com 6,5 do modelo básico, ganhando todas as 10 comparações. Os veredictos mostram o porquê: ele dá explicações completas com um raciocínio confiante e estruturado, enquanto a base fica na dúvida com “eu acho” e “talvez”, deixando os pensamentos incompletos.

Isso mostra que o checkpoint-400 tem um desempenho melhor no mundo real. A menor perda de treinamento na iteração 900 teria nos dado memorização, não raciocínio.

Dá uma olhada no script de comparação e nos resultados detalhados pra fazer avaliações parecidas nos seus modelos.

Conclusão

Você já viu como o Tinker simplifica o ajuste fino sem perder o controle. Quatro primitivas API principais, forward_backward, optim_step, save_weights_and_get_sampling_client e sample, fornecem os blocos de construção para fluxos de trabalho de treinamento personalizados, enquanto o Tinker lida com a infraestrutura distribuída.

O modelo de perguntas e respostas financeiras que criamos mostra as melhores práticas de produção: o rastreamento de validação detectou o sobreajuste logo no início, o checkpoint-400 superou o modelo final ao focar na generalização e a avaliação LLM-as-judge confirmou uma melhoria de 2 pontos em relação ao modelo base. Esses padrões valem tanto para ajustar modelos de parâmetros 8B quanto 70B.

A plataforma está em fase beta com acesso por lista de espera, mas os conceitos que você aprendeu podem ser aplicados diretamente assim que você tiver acesso. Os treinos que fiz para este tutorial (incluindo os que falharam e os experimentais) custaram 150 créditos iniciais, então, com os créditos iniciais, você pode fazer este tutorial 6 vezes mais experimentos adicionais, o que dá bastante espaço para aprender e iterar seus próprios projetos de ajuste fino.

E agora, o que vem a seguir? Tente ajustar os dados específicos do seu domínio, experimente diferentes classificações LoRA e taxas de aprendizagem ou explore paradigmas de treinamento avançados, como o aprendizado por reforço a partir do feedback humano. O livro de receitas do Tinker tem exemplos para esses casos. Comece a pensar agora no seu caso de uso — que comportamento você quer que seu modelo aprenda?

Se você quer desenvolver um conjunto mais amplo de habilidades no treinamento e ajuste dos modelos mais recentes de IA para produção, não deixe de conferir o programa de estudo Engenheiro Associado de IA para Desenvolvedores.


Bex Tuychiev's photo
Author
Bex Tuychiev
LinkedIn

Sou um criador de conteúdo de ciência de dados com mais de 2 anos de experiência e um dos maiores seguidores no Medium. Gosto de escrever artigos detalhados sobre IA e ML com um estilo um pouco sarcástico, porque você precisa fazer algo para torná-los um pouco menos monótonos. Produzi mais de 130 artigos e um curso DataCamp, e estou preparando outro. Meu conteúdo foi visto por mais de 5 milhões de pessoas, das quais 20 mil se tornaram seguidores no Medium e no LinkedIn. 

Tópicos

Cursos de Engenharia de IA

Programa

Desenvolvimento de aplicativos de IA

21 h
Aprenda a criar aplicativos com tecnologia de IA com as mais recentes ferramentas de desenvolvimento de IA, incluindo a API OpenAI, Hugging Face e LangChain.
Ver detalhesRight Arrow
Iniciar curso
Ver maisRight Arrow
Relacionado

Tutorial

Guia de Introdução ao Ajuste Fino de LLMs

O ajuste fino dos grandes modelos de linguagem (LLMs, Large Language Models) revolucionou o processamento de linguagem natural (PLN), oferecendo recursos sem precedentes em tarefas como tradução de idiomas, análise de sentimentos e geração de textos. Essa abordagem transformadora aproveita modelos pré-treinados como o GPT-2, aprimorando seu desempenho em domínios específicos pelo processo de ajuste fino.
Josep Ferrer's photo

Josep Ferrer

Tutorial

Como treinar um LLM com o PyTorch

Domine o processo de treinamento de grandes modelos de linguagem usando o PyTorch, desde a configuração inicial até a implementação final.
Zoumana Keita 's photo

Zoumana Keita

Tutorial

Como criar aplicativos LLM com o tutorial LangChain

Explore o potencial inexplorado dos modelos de linguagem grandes com o LangChain, uma estrutura Python de código aberto para criar aplicativos avançados de IA.
Moez Ali's photo

Moez Ali

Tutorial

Guia para iniciantes do LlaMA-Factory WebUI: Ajuste fino dos LLMs

Saiba como fazer o ajuste fino dos LLMs em conjuntos de dados personalizados, avaliar o desempenho e exportar e servir modelos com facilidade usando a estrutura com pouco ou nenhum código do LLaMA-Factory.
Abid Ali Awan's photo

Abid Ali Awan

Tutorial

Criando agentes LangChain para automatizar tarefas em Python

Um tutorial abrangente sobre a criação de agentes LangChain com várias ferramentas para automatizar tarefas em Python usando LLMs e modelos de bate-papo usando OpenAI.
Bex Tuychiev's photo

Bex Tuychiev

Tutorial

RAG With Llama 3.1 8B, Ollama e Langchain: Tutorial

Aprenda a criar um aplicativo RAG com o Llama 3.1 8B usando Ollama e Langchain, configurando o ambiente, processando documentos, criando embeddings e integrando um retriever.
Ryan Ong's photo

Ryan Ong

Ver maisVer mais