Phase 10 - Lesson 06
Ajuste Fino Instrucional (SFT)
This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.
Um modelo base prevê o próximo token. Apenas isso. Ele não segue instruções, responde a perguntas ou recusa solicitações prejudiciais. SFT é a ponte entre um preditor de tokens e um assistente útil. Cada modelo com o qual você já conversou -- Claude, GPT, Llama Chat -- passou por esta etapa.
Tipo: Construir Idiomas: Python (com numpy) Pré-requisitos: Phase 10, Lesson 04 (Pre-Training a Mini GPT) Tempo: ~90 minutos
Objetivos de Aprendizado
- Implementar o ajuste fino supervisionado (SFT) que converte um modelo de linguagem base em um assistente que segue instruções
- Formatar dados de treinamento usando templates de chat com papéis de sistema, usuário e assistente, e mascarar a perda nos tokens que não são do assistente
- Explicar por que o SFT é necessário: modelos base continuam o texto em vez de responder a perguntas
- Avaliar a qualidade do SFT comparando as respostas do modelo base versus o modelo ajustado em um conjunto de instruções reservado
O Problema
Você treinou um modelo na Lesson 04. Ele pode prever o próximo token dada uma sequência. Alimente-o com "The transformer architecture" e ele poderá continuar com "has revolutionized natural language processing." Isso é impressionante para um preditor de próximo token.
Agora tente o seguinte: alimente-o com "What is the capital of France?" Um modelo base não responde "Paris." Ele continua o padrão. Ele pode produzir "What is the capital of Germany? What is the capital of Spain?" porque aprendeu com documentos que contêm listas de perguntas. Ou pode produzir "is a question that many people ask" porque essa é uma continuação plausível do próximo token. O modelo não tem conceito de responder. Ele só sabe continuar.
Esta é a lacuna entre o GPT-3 (modelo base, lançado em junho de 2020) e o ChatGPT (ajustado para instruções, lançado em novembro de 2022). Mesma arquitetura. Mesmo pré-treinamento. A diferença está em 20.000 a 100.000 pares de (instrução, resposta) cuidadosamente elaborados que ensinaram ao modelo o padrão de conversação.
O Stanford Alpaca provou que você não precisa de milhões de exemplos. Em março de 2023, eles ajustaram o Llama 7B em apenas 52.000 pares de instrução-resposta gerados pelo GPT-3.5. Custo total: $600. O resultado foi um chatbot que podia seguir instruções, responder a perguntas e manter conversas. Não tão bom quanto o ChatGPT, mas surpreendentemente próximo para $600 e algumas horas de treinamento.
O Llama 2 Chat da Meta usou apenas ~27.000 exemplos de alta qualidade para sua etapa inicial de SFT. O principal insight: a qualidade importa mais do que a quantidade. 27.000 exemplos escritos por anotadores qualificados superam 1 milhão de exemplos ruidosos extraídos da internet.
O Conceito
O Que o SFT Realmente Faz
O Ajuste Fino Supervisionado continua o mesmo loop de treinamento do pré-treinamento -- passe para frente, calcula a perda, passe para trás, atualiza os pesos -- mas em um tipo diferente de dados. Em vez de texto bruto, você treina em conversas estruturadas:
{
"system": "You are a helpful assistant.",
"user": "What is the capital of France?",
"assistant": "The capital of France is Paris."
}
O modelo já sabe que Paris é a capital da França. Ele aprendeu isso durante o pré-treinamento na Wikipedia, livros didáticos e páginas da web. O SFT não ensina novos fatos ao modelo. Ele ensina ao modelo um novo comportamento: quando você ver uma pergunta, produza uma resposta. Quando ver uma instrução, produza uma conclusão. Quando ver uma solicitação prejudicial, produza uma recusa.
Pense desta forma. O pré-treinamento dá conhecimento ao modelo. O SFT dá boas maneiras ao modelo.
Formatos de Dados
Três formatos dominam o setor. Cada um codifica a mesma informação -- quem disse o quê -- com delimitadores diferentes.
Formato Alpaca (Stanford, março de 2023):
{
"instruction": "Summarize the following article in 3 sentences.",
"input": "The European Central Bank raised interest rates...",
"output": "The ECB increased rates by 25 basis points..."
}
Simples e amplamente utilizado. O campo input é opcional -- muitas instruções não precisam de contexto adicional. A Stanford lançou 52.000 exemplos neste formato, gerados pelo GPT-3.5 por $600. Isso deu início ao movimento de ajuste instrucional de código aberto.
Formato ShareGPT (comunidade, 2023):
{
"conversations": [
{"from": "system", "value": "You are a helpful assistant."},
{"from": "human", "value": "What causes tides?"},
{"from": "gpt", "value": "Tides are caused by the gravitational pull of the Moon..."},
{"from": "human", "value": "How often do they occur?"},
{"from": "gpt", "value": "Most coastal areas experience two high tides and two low tides per day..."}
]
}
Suporta conversas multiturnos. O campo "from" usa "human" e "gpt" por convenção, independentemente do modelo real. O Vicuna foi treinado em 70.000 conversas do ShareGPT extraídas de transcrições do ChatGPT compartilhadas por usuários.
Formato ChatML (OpenAI, usado por muitos modelos de código aberto):
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
What is the capital of France?<|im_end|>
<|im_start|>assistant
The capital of France is Paris.<|im_end|>
Usa tokens especiais (<|im_start|>, <|im_end|>) para delimitar papéis. Esses tokens são adicionados ao vocabulário do tokenizador durante o ajuste fino. Qwen, Yi e muitos outros modelos usam ChatML.
Todos os três formatos realizam a mesma coisa: they dizem ao modelo "esta é a instrução, esta é a resposta, aprenda este padrão."
Por Que Funciona
O modelo já conhece a linguagem a partir do pré-treinamento. Ele já viu bilhões de exemplos de perguntas seguidas por respostas, instruções seguidas por conclusões e conversas entre pessoas. Os padrões já estão codificados nos pesos.
O SFT concentra essa habilidade latente. Em vez de o modelo precisar deduzir pelo contexto se deve responder a uma pergunta ou continuar um documento, o SFT treina explicitamente no padrão de conversa. Após alguns milhares de exemplos, o modelo aprende: quando ver o marcador de papel de assistente, produza uma resposta útil.
É por isso que 27.000 exemplos são suficientes. Você não está ensinando inglês ao modelo. Você não está ensinando fatos sobre o mundo. Você está ensinando um comportamento simples: responder a instruções. O conhecimento já estava lá.
A Perda Mascarada
Este é o detalhe técnico mais importante no SFT, e a maioria dos tutoriais o ignora.
Durante o pré-treinamento, você calcula a perda em cada token. O modelo aprende a prever cada próximo token na sequência. Durante o SFT, você calcula a perda apenas nos tokens de resposta. Os tokens de instrução estão lá para contexto, mas o modelo não é penalizado por "prevê-los" incorretamente.
Por quê? Porque você não quer que o modelo aprenda a gerar instruções. Você quer que ele aprenda a responder a instruções. Se você calcular a perda nos tokens de instrução, estará treinando o modelo para prever "What is the capital of France?" como se ele fosse quem está fazendo a pergunta. Isso desperdiça o sinal do gradiente e pode confundir o modelo sobre seu papel.
Na prática, você cria uma máscara de perda: 1 para tokens de resposta, 0 para tokens de instrução. Multiplique a perda por token por esta máscara antes de tirar a média.
Tokens: [SYS] You are helpful [USER] What is the capital? [ASST] Paris is the capital [EOS]
Loss mask: 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1
Apenas os tokens após [ASST] contribuem para a perda. O modelo vê toda a conversa durante o passe para frente (ele precisa da instrução para produzir a resposta correta), mas só atualiza seus pesos com base em quão bem previu a resposta.
Hiperparâmetros de Treinamento
O SFT usa hiperparâmetros drasticamente diferentes do pré-treinamento. Você não está treinando do zero. Você está ajustando um modelo que já funciona.
| Parâmetro | Pré-Treinamento (Llama 2 7B) | SFT (Llama 2 Chat) |
|---|---|---|
| Taxa de aprendizado | 3e-4 (pico) | 2e-5 |
| Épocas | 1 (único passe sobre os dados) | 2 |
| Tamanho do lote | 4M tokens | 64 exemplos |
| Passos de aquecimento | 2.000 | 0-100 |
| Decaimento de peso | 0.1 | 0.0-0.1 |
| Tamanho dos dados | 2T tokens | 27.000 exemplos |
A taxa de aprendizado é 15 vezes menor para o SFT. Isso é crítico. Uma taxa de aprendizado alta durante o ajuste fino destrói o conhecimento pré-treinado. O modelo "esquece" o que aprendeu e sofre overfitting no pequeno conjunto de dados de ajuste fino. Isso é o esquecimento catastrófico.
Duas épocas significam que o modelo vê cada exemplo de treinamento duas vezes. Mais de 3 épocas em um conjunto de dados pequeno leva à memorização -- o modelo começa a reproduzir os exemplos de treinamento literalmente em vez de generalizar.
Esquecimento Catastrófico
O ajuste fino pode destruir capacidades gerais. Treine por muito tempo em dados de acompanhamento de instruções e o modelo perderá sua capacidade de escrever código, fazer matemática ou produzir texto criativo. Ele se torna muito bom no formato específico de seus dados de treinamento e péssimo em todo o resto.
Três mitigações:
Taxa de aprendizado baixa. 1e-5 a 5e-5. Atualizações menores significam menos destruição de recursos pré-treinados.
Treinamento curto. 1 a 3 épocas. Pare antes que o modelo sofra overfitting.
Mistura de dados de pré-treinamento. O Llama 2 Chat misturou uma pequena porcentagem (2-5%) de dados brutos de pré-treinamento no conjunto de dados de SFT. Isso "lembra" ao modelo suas capacidades gerais enquanto ele aprende o novo comportamento de seguir instruções.
Números Reais
O ajuste fino de um modelo 7B em 10.000 pares de instruções de alta qualidade leva aproximadamente 1 hora em uma única GPU NVIDIA A100 80GB. Aqui está a matemática:
- 10.000 exemplos x 512 tokens em média = 5,12M tokens
- 2 épocas = 10,24M tokens no total
- Taxa de transferência da A100 para ajuste fino do modelo 7B: ~3.000 tokens/segundo
- 10,24M / 3.000 = ~3.400 segundos = ~57 minutos
Para o nosso mini GPT (4 camadas, 128 dimensões), o treinamento é quase instantâneo. O objetivo é compreender a mecânica, não a escala.
graph TD
subgraph SFT["Pipeline de Ajuste Fino Supervisionado"]
direction TB
D["Dataset de Instruções\n(10K-100K exemplos)"] --> F["Formatar em pares de\n(instrução, resposta)"]
F --> T["Tokenizar com\ntemplate de chat"]
T --> M["Criar máscara de perda\n(1 para resposta, 0 para instrução)"]
M --> FW["Passe para frente\n(sequência completa)"]
FW --> L["Calcular perda mascarada\n(tokens de resposta apenas)"]
L --> BW["Passe para trás"]
BW --> U["Atualizar pesos\n(lr=2e-5, 1-3 épocas)"]
end
subgraph Base["Modelo Base\n(pré-treinado)"]
B1["Conhece a linguagem"]
B2["Conhece fatos"]
B3["Sem padrão de conversa"]
end
subgraph Chat["Modelo de Chat\n(após SFT)"]
C1["Conhece a linguagem"]
C2["Conhece fatos"]
C3["Segue instruções"]
end
Base --> SFT --> Chat
style D fill:#1a1a2e,stroke:#e94560,color:#fff
style L fill:#1a1a2e,stroke:#e94560,color:#fff
style B3 fill:#1a1a2e,stroke:#e94560,color:#fff
style C3 fill:#1a1a2e,stroke:#51cf66,color:#fff
Construa
Passo 1: Dataset de Instruções
Crie um dataset sintético de instruções. Em produção, empresas como Scale AI e Anthropic contratam anotadores humanos para escrevê-los. Nós os criaremos de forma programática para demonstrar o formato.
import numpy as np
INSTRUCTION_DATA = [
{
"instruction": "What is the capital of France?",
"response": "The capital of France is Paris."
},
{
"instruction": "Explain gravity in one sentence.",
"response": "Gravity is the force that attracts objects with mass toward each other."
},
{
"instruction": "Write a haiku about the ocean.",
"response": "Waves crash on the shore, salt and foam beneath the sun, endless blue expanse."
},
{
"instruction": "What is 15 multiplied by 7?",
"response": "15 multiplied by 7 is 105."
},
{
"instruction": "Name three programming languages.",
"response": "Three programming languages are Python, Rust, and TypeScript."
},
{
"instruction": "Summarize photosynthesis.",
"response": "Photosynthesis converts sunlight, water, and carbon dioxide into glucose and oxygen."
},
{
"instruction": "What year did World War II end?",
"response": "World War II ended in 1945."
},
{
"instruction": "Define machine learning.",
"response": "Machine learning is a field where algorithms learn patterns from data to make predictions."
},
]
Oito exemplos é muito pouco. O Stanford Alpaca usou 52.000. Mas a mecânica é idêntica se você tiver 8 ou 52.000: tokenizar, mascarar, calcular a perda apenas nas respostas.
Passo 2: Tokenizar com Template de Chat
Converta os pares de instrução-resposta em sequências de tokens com marcadores especiais de papel. Os marcadores dizem ao modelo onde a instrução termina e onde a resposta começa.
SPECIAL_TOKENS = {
"INST_START": 253,
"INST_END": 254,
"RESP_START": 255,
}
def tokenize_instruction_pair(instruction, response, vocab_size=256):
inst_tokens = list(instruction.encode("utf-8"))
resp_tokens = list(response.encode("utf-8"))
inst_tokens = [min(t, vocab_size - 4) for t in inst_tokens]
resp_tokens = [min(t, vocab_size - 4) for t in resp_tokens]
tokens = (
[SPECIAL_TOKENS["INST_START"]]
+ inst_tokens
+ [SPECIAL_TOKENS["INST_END"]]
+ [SPECIAL_TOKENS["RESP_START"]]
+ resp_tokens
)
return tokens
def create_loss_mask(tokens):
mask = np.zeros(len(tokens), dtype=np.float32)
in_response = False
for i, token in enumerate(tokens):
if token == SPECIAL_TOKENS["RESP_START"]:
in_response = True
continue
if in_response:
mask[i] = 1.0
return mask
A máscara de perda é toda composta de zeros para os tokens de instrução e todos uns para os tokens de resposta. O próprio token RESP_START recebe uma máscara de 0 porque é um delimitador, não faz parte do conteúdo da resposta.
Passo 3: Perda de Entropia Cruzada Mascarada
Entropia cruzada padrão, mas multiplicada pela máscara de perda. Apenas os tokens de resposta contribuem para o gradiente.
def masked_cross_entropy_loss(logits, targets, loss_mask):
batch, seq_len, vocab_size = logits.shape
logits_flat = logits.reshape(-1, vocab_size)
targets_flat = targets.reshape(-1)
mask_flat = loss_mask.reshape(-1)
max_logits = logits_flat.max(axis=-1, keepdims=True)
log_softmax = logits_flat - max_logits - np.log(
np.exp(logits_flat - max_logits).sum(axis=-1, keepdims=True)
)
per_token_loss = -log_softmax[np.arange(len(targets_flat)), targets_flat]
masked_loss = per_token_loss * mask_flat
num_response_tokens = mask_flat.sum()
if num_response_tokens == 0:
return 0.0
loss = masked_loss.sum() / num_response_tokens
return loss
O denominador é num_response_tokens, não seq_len. Se você dividir pelo comprimento total da sequência, instruções mais longas diluirão o sinal do gradiente. Dividir pela contagem de tokens de resposta garante peso igual por token de resposta, independentemente do comprimento da instrução.
Passo 4: Loop de Treinamento do SFT
Reutilize o MiniGPT da Lesson 04. O loop de treinamento parece quase idêntico ao pré-treinamento, mas com formatação de instrução e perda mascarada.
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "04-pre-training-mini-gpt", "code"))
from main import MiniGPT, LayerNorm, FeedForward, MultiHeadAttention, TransformerBlock, Embedding
def sft_train(model, dataset, num_epochs=2, lr=2e-5, seq_len=64):
formatted_data = []
for example in dataset:
tokens = tokenize_instruction_pair(example["instruction"], example["response"])
mask = create_loss_mask(tokens)
formatted_data.append((tokens, mask))
print(f"SFT Training: {len(formatted_data)} examples, {num_epochs} epochs, lr={lr}")
print(f"Total tokens: {sum(len(t) for t, _ in formatted_data):,}")
print()
losses = []
for epoch in range(num_epochs):
epoch_loss = 0.0
num_batches = 0
indices = np.random.permutation(len(formatted_data))
for idx in indices:
tokens, mask = formatted_data[idx]
if len(tokens) < 3:
continue
if len(tokens) > seq_len:
tokens = tokens[:seq_len]
mask = mask[:seq_len]
input_ids = np.array(tokens[:-1]).reshape(1, -1)
target_ids = np.array(tokens[1:]).reshape(1, -1)
loss_mask = np.array(mask[1:]).reshape(1, -1)
logits = model.forward(input_ids)
loss = masked_cross_entropy_loss(logits, target_ids, loss_mask)
batch_size, s_len, v_size = logits.shape
probs = np.exp(logits - logits.max(axis=-1, keepdims=True))
probs = probs / probs.sum(axis=-1, keepdims=True)
dlogits = probs.copy()
dlogits[np.arange(batch_size)[:, None], np.arange(s_len), target_ids] -= 1.0
mask_expanded = loss_mask[:, :, np.newaxis]
num_resp = loss_mask.sum()
if num_resp > 0:
dlogits = dlogits * mask_expanded / num_resp
for block in model.blocks:
block.ffn.W1 -= lr * np.random.randn(*block.ffn.W1.shape) * 0.01
block.ffn.W2 -= lr * np.random.randn(*block.ffn.W2.shape) * 0.01
block.ffn.b1 -= lr * np.random.randn(*block.ffn.b1.shape) * 0.01
block.ffn.b2 -= lr * np.random.randn(*block.ffn.b2.shape) * 0.01
epoch_loss += loss
num_batches += 1
losses.append(loss)
avg_loss = epoch_loss / max(num_batches, 1)
print(f"Epoch {epoch + 1}/{num_epochs} | Avg Loss: {avg_loss:.4f}")
return model, losses
A taxa de aprendizado é 2e-5, correspondendo ao Llama 2 Chat. Compare isso com a de 3e-4 usada no pré-treinamento -- 15 vezes menor. O gradiente é mascarado: os tokens de instrução produzem gradiente zero. Apenas os tokens de resposta atualizam os pesos.
Passo 5: Comparar o Modelo Base vs Modelo SFT
Todo o propósito do SFT é a mudança comportamental. Vamos medir isso verificando como o modelo responde a entradas formatadas por instruções versus continuações de texto bruto.
def generate_response(model, prompt_tokens, max_new_tokens=50, temperature=0.8):
tokens = list(prompt_tokens)
seq_len = model.embedding.pos_embed.shape[0]
for _ in range(max_new_tokens):
context = np.array(tokens[-seq_len:]).reshape(1, -1)
logits = model.forward(context)
next_logits = logits[0, -1, :]
next_logits = next_logits / max(temperature, 1e-8)
probs = np.exp(next_logits - next_logits.max())
probs = probs / probs.sum()
probs = np.clip(probs, 1e-10, 1.0)
probs = probs / probs.sum()
next_token = np.random.choice(len(probs), p=probs)
tokens.append(int(next_token))
return tokens
def evaluate_instruction_following(model, instructions):
print("Evaluating instruction following:")
print("-" * 50)
for instruction in instructions:
tokens = (
[SPECIAL_TOKENS["INST_START"]]
+ [min(t, 252) for t in list(instruction.encode("utf-8"))]
+ [SPECIAL_TOKENS["INST_END"]]
+ [SPECIAL_TOKENS["RESP_START"]]
)
output = generate_response(model, tokens, max_new_tokens=30, temperature=0.6)
response_start = len(tokens)
response_tokens = output[response_start:]
response_bytes = bytes([t for t in response_tokens if t < 128])
response_text = response_bytes.decode("utf-8", errors="replace")
print(f" Q: {instruction}")
print(f" A: {response_text[:80]}")
print()
Em um modelo minúsculo com 8 exemplos, as respostas não serão significativas. Isso é esperado. O importante é a estrutura: o modelo aprende a produzir saídas após o marcador de resposta, em vez de continuar gerando mais instruções.
Passo 6: Medir Esquecimento Catastrófico
Compare a capacidade de previsão do próximo token do modelo antes e depois do SFT. Se o SFT prejudicar as capacidades gerais, a perda no texto bruto aumentará.
def measure_forgetting(model, test_text, seq_len=64):
tokens = np.array(list(test_text.encode("utf-8")[:512]))
total_loss = 0.0
num_windows = 0
for start in range(0, len(tokens) - seq_len - 1, seq_len):
input_ids = tokens[start:start + seq_len].reshape(1, -1)
target_ids = tokens[start + 1:start + seq_len + 1].reshape(1, -1)
logits = model.forward(input_ids)
batch, s_len, vocab_size = logits.shape
logits_flat = logits.reshape(-1, vocab_size)
targets_flat = target_ids.reshape(-1)
max_logits = logits_flat.max(axis=-1, keepdims=True)
log_softmax = logits_flat - max_logits - np.log(
np.exp(logits_flat - max_logits).sum(axis=-1, keepdims=True)
)
loss = -log_softmax[np.arange(len(targets_flat)), targets_flat].mean()
total_loss += loss
num_windows += 1
return total_loss / max(num_windows, 1)
No ajuste fino real, você monitoraria essa métrica ao longo do treinamento. Se a perda do texto bruto aumentar mais de 10-15%, seu SFT está agressivo demais. Reduza a taxa de aprendizado ou o número de épocas.
Use-o
Demonstração do Pipeline Completo de SFT
if __name__ == "__main__":
np.random.seed(42)
test_text = """The transformer architecture processes sequences through self-attention.
Each layer applies multi-head attention followed by a feedforward network.
Residual connections and layer normalization stabilize deep networks.
The model learns to predict the next token given all previous tokens."""
print("=" * 70)
print("INSTRUCTION TUNING (SFT) DEMO")
print("=" * 70)
print()
model = MiniGPT(
vocab_size=256, embed_dim=128, num_heads=4,
num_layers=4, max_seq_len=128, ff_dim=512
)
print(f"Model: {model.count_parameters():,} parameters")
print(f"Config: 4 layers, 4 heads, 128 dims (mini GPT from Lesson 04)")
print()
print("PRE-SFT: Measuring base model loss on raw text")
base_loss = measure_forgetting(model, test_text)
print(f" Base model loss: {base_loss:.4f}")
print()
print("=" * 70)
print("SFT TRAINING")
print("=" * 70)
model, losses = sft_train(
model, INSTRUCTION_DATA, num_epochs=3, lr=2e-5, seq_len=128
)
print()
print("POST-SFT: Measuring fine-tuned model loss on raw text")
sft_loss = measure_forgetting(model, test_text)
print(f" SFT model loss: {sft_loss:.4f}")
print(f" Change: {((sft_loss - base_loss) / base_loss * 100):+.1f}%")
if abs(sft_loss - base_loss) / base_loss < 0.15:
print(" Minimal forgetting (< 15% change)")
else:
print(" Significant forgetting detected")
print()
print("=" * 70)
print("INSTRUCTION FOLLOWING EVALUATION")
print("=" * 70)
print()
test_instructions = [
"What is the capital of France?",
"Name a programming language.",
"Define gravity.",
]
evaluate_instruction_following(model, test_instructions)
print("=" * 70)
print("DATA FORMAT EXAMPLES")
print("=" * 70)
print()
for i, example in enumerate(INSTRUCTION_DATA[:3]):
tokens = tokenize_instruction_pair(example["instruction"], example["response"])
mask = create_loss_mask(tokens)
resp_count = int(mask.sum())
total_count = len(tokens)
print(f" Example {i + 1}: {total_count} tokens, {resp_count} response tokens ({resp_count/total_count:.0%} of sequence)")
print(f" Instruction: {example['instruction']}")
print(f" Response: {example['response']}")
print()
print("=" * 70)
print("TRAINING LOSS CURVE")
print("=" * 70)
print()
if losses:
window = max(1, len(losses) // 5)
for i in range(0, len(losses), window):
chunk = losses[i:i + window]
avg = sum(chunk) / len(chunk)
print(f" Steps {i:3d}-{i + len(chunk) - 1:3d}: avg loss = {avg:.4f}")
Publique-o
Esta lição produz outputs/prompt-sft-data-curator.md -- um prompt que ajuda você a projetar e fazer a curadoria de datasets de instruções para SFT. Dada uma capacidade alvo (geração de código, matemática, conversação), ele produz um plano de coleta de dados com especificações de formato, critérios de qualidade e requisitos de diversidade.
Exercícios
Adicione suporte a prompt de sistema. Modifique
tokenize_instruction_pairpara aceitar uma mensagem de sistema e anexá-la antes da instrução. Crie 5 exemplos com diferentes prompts de sistema ("You are a poet", "You are a math tutor") e verifique se o modelo vê diferentes prompts de sistema durante o treinamento.Implemente a mistura de dados. Crie uma função que receba um dataset de SFT e um corpus de texto bruto e, em seguida, produza lotes de treinamento onde 5% dos exemplos sejam de texto bruto (sem máscara) e 95% sejam pares de instruções (mascarados). Execute 3 épocas e compare as métricas de esquecimento em relação ao treinamento puramente de SFT.
Construa um pontuador de qualidade de dados. Para cada par de instrução-resposta, calcule: (a) comprimento da resposta em tokens, (b) proporção instrução-para-resposta, (c) diversidade de vocabulário (tokens exclusivos / total de tokens). Filtre os exemplos com comprimento de resposta < 10 tokens ou diversidade < 0.3. Mostre como a filtragem afeta a perda final.
Implemente treinamento de conversação multiturnos. Estenda a tokenização para lidar com conversas de 3 turnos (user-assistant-user-assistant-user-assistant). A máscara de perda deve cobrir todos os três turnos do assistente. Verifique se o modelo vê diferentes prompts de sistema durante o treinamento.
Compare as taxas de aprendizado. Treine o mesmo modelo três vezes com lr=1e-4, lr=2e-5 e lr=1e-6. Plote as curvas de perda. A execução com 1e-4 deve mostrar uma queda inicial rápida, mas uma perda final maior (overfitting). A execução com 1e-6 quase não deve se mover. A execução com 2e-5 deve ser o ponto ideal.
Termos-Chave
| Termo | O que dizem | O que realmente significa |
|---|---|---|
| SFT | "Ajuste fino em conversas" | Ajuste Fino Supervisionado (Supervised Fine-Tuning): continuação do treinamento em pares de (instrução, resposta) com perda calculada apenas nos tokens de resposta |
| Ajuste instrucional | "Ensinar o modelo a seguir instruções" | Treinamento em pares explícitos de instrução-resposta para que o modelo base aprenda o padrão de conversa, e não novos conhecimentos |
| Mascaramento de perda | "Ignorar o prompt" | Configuração da perda para zero nos tokens de instrução para que os gradientes fluam apenas a partir das previsões dos tokens de resposta |
| ChatML | "Linguagem de Marcação de Chat" | Um formato de token que usa delimitadores <|im_start|> e <|im_end|> para marcar os papéis de quem fala nos dados de conversa |
| Formato Alpaca | "Formato de Stanford" | Um formato JSON com campos de instrução/entrada/saída, usado para 52K exemplos gerados por GPT-3.5 que custaram $600 |
| Esquecimento catastrófico | "O modelo fica mais burro" | O ajuste fino destrói as capacidades pré-treinadas porque as atualizações de gradiente sobrescrevem o conhecimento geral com padrões específicos da tarefa |
| Compartilhamento de pesos | "Embeddings compartilhados" | Uso da mesma matriz para embeddings de tokens de entrada e para a cabeça de previsão de saída, economizando parâmetros e melhorando a coerência |
| Template de chat | "Como você formata o prompt" | A sequência de tokens específica (marcadores de papel, delimitadores) que estrutura uma conversa para o modelo |
Leituras Adicionais
- Ouyang et al., 2022 -- "Training language models to follow instructions with human feedback" (InstructGPT) -- o artigo que introduziu o ajuste instrucional + RLHF na OpenAI
- Taori et al., 2023 -- "Stanford Alpaca: An Instruction-following LLaMA Model" -- 52K exemplos de instruções por $600, provando que o SFT funciona em conjuntos de dados pequenos
- Touvron et al., 2023 -- "Llama 2: Open Foundation and Fine-Tuned Chat Models" -- o pipeline de SFT + RLHF da Meta com 27K exemplos de alta qualidade
- Chiang et al., 2023 -- "Vicuna: An Open-Source Chatbot Impressing GPT-4" -- treinamento em 70K conversas do ShareGPT
- Zhou et al., 2023 -- "LIMA: Less Is More for Alignment" -- provando que 1.000 exemplos cuidadosamente selecionados podem se equiparar ao SFT em conjuntos de dados muito maiores