Inteligência artificial, sem ruído.
Tutoriais10 min

Como Criar um LLM Focado em Raciocínio: Guia Prático de Streaming, Curadoria e Fine-Tuning do Corpus SupraLabs

Aprenda a transformar o corpus de raciocínio SupraLabs em um LLM compacto: streaming no Hugging Face, filtros de qualidade e fine-tuning com LoRA.

Como Criar um LLM Focado em Raciocínio: Guia Prático de Streaming, Curadoria e Fine-Tuning do Corpus SupraLabs

O que mudou em 2026: dados de raciocínio agora são acessíveis a qualquer um

Até pouco tempo, treinar um modelo com capacidade de raciocínio explícito — aquele que “pensa” passo a passo antes de responder — era território exclusivo de grandes laboratórios com clusters caros e datasets proprietários. O cenário mudou: hoje existem corpora públicos massivos de raciocínio, como o SupraLabs Reasoning Corpus, e técnicas de ajuste eficiente como LoRA que cabem em uma GPU gratuita do Google Colab.

Neste tutorial, você vai acompanhar um pipeline completo e reproduzível: transmitir (streaming) um subconjunto do corpus direto do Hugging Face Hub, analisar sua composição, aplicar filtros de qualidade, converter os dados para o formato de chat com tags <think> e ajustar o modelo SmolLM2-135M-Instruct usando o SFTTrainer da biblioteca TRL. Ao final, você terá um modelo compacto focado em raciocínio — e um conjunto de dados curado exportado em Parquet para reutilização.

✅ O que você ganha

  • Acesso escalável a dados: o streaming do Hugging Face evita baixar o corpus inteiro para a memória do Colab — você materializa apenas 8.000 exemplos representativos.
  • Curadoria orientada por dados: você aprende a medir razão de raciocínio, repetição e completude antes de treinar, em vez de aceitar qualquer exemplo.
  • Fine-tuning barato: LoRA com r=16 ajusta poucos milhões de parâmetros, viabilizando o treino em uma GPU T4 gratuita em ~10–20 minutos.
  • Pipeline reutilizável: o código serve de base para mistura de fontes, curriculum learning e modelos maiores.
  • Dataset exportável: ao final, você tem os subconjuntos de treino e avaliação em Parquet, prontos para experimentos futuros.

⚠️ O que você NÃO ganha

  • Um modelo no nível dos grandes laboratórios: o SmolLM2-135M é um modelo pequeno, para aprendizado e prototipagem — não compete com modelos de fronteira.
  • Raciocínio “puro” garantido: o filtro de razão de raciocínio (0,15–0,97) é heurístico; exemplos passam sem verificação humana.
  • Escala de produção: 1.500 exemplos de treino e 1 época são suficientes para demonstrar a técnica, não para um produto final.
ComponenteMínimoRecomendadoIdeal
GPUCPU (lento)NVIDIA T4 (Colab free)GPU com 16 GB+ VRAM
Memória RAM8 GB12 GB16 GB+
Armazenamento5 GB livres10 GB20 GB (datasets maiores)
Python3.9+3.103.11
Conhecimento prévioPython básicoNoções de PyTorchExperiência com Hugging Face/TRL
Tempo estimado~30 min~45 min~1h (com análise)
Requisitos para executar o pipeline completo.

Passo 1 — Preparar o ambiente no Google Colab

O primeiro bloco instala as bibliotecas de machine learning e remove o pacote torchao, que é incompatível com a versão atual do PEFT. Em seguida, detecta o dispositivo de computação disponível e conecta-se ao corpus via streaming do Hugging Face.

import subprocess, sys
def pip_install(pkgs):
    subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", *pkgs])
subprocess.call([sys.executable, "-m", "pip", "uninstall", "-y", "-q", "torchao"])
pip_install(["datasets>=3.0.0", "transformers>=4.46.0", "trl>=0.12.0",
             "peft>=0.13.0", "accelerate>=1.0.0", "bitsandbytes",
             "matplotlib", "pandas"])

DATASET_ID = "SupraLabs/reasoning-corpus-4K-5M-v1"
SAMPLE_SIZE = 8_000
stream = load_dataset(DATASET_ID, split="train", streaming=True)
stream = stream.shuffle(seed=42, buffer_size=30_000)
rows = list(itertools.islice(stream, SAMPLE_SIZE))
ds = Dataset.from_list(rows)

O ponto-chave aqui é o streaming: em vez de baixar o corpus inteiro (que é grande), você embaralha um buffer e “fatia” os 8.000 primeiros registros. Cada exemplo tem os campos repo_id (repositório de origem), tok_len (comprimento em tokens), user (a pergunta), thought_trace (a cadeia de raciocínio) e assistant (a resposta final).

Passo 2 — Análise exploratória dos dados

Antes de treinar, vale entender o que há dentro do corpus. O segundo bloco converte a amostra em DataFrame do pandas e calcula distribuições de repositórios de origem, comprimento de tokens e a razão de raciocínio — a proporção entre o “pensamento” e a resposta final.

df = ds.to_pandas()
df["think_chars"] = df["thought_trace"].str.len()
df["answer_chars"] = df["assistant"].str.len()
df["reason_ratio"] = df["think_chars"] / (df["think_chars"] + df["answer_chars"] + 1)
df["reason_ratio"].hist(bins=50)  # visualiza a distribuição

O código também classifica cada exemplo por tarefa usando heurísticas de texto: se a resposta contém def, class ou import, é código; se a pergunta traz termos como prove, integral ou theorem, é matemática; se fala de diagnosis ou treatment, é médica; e assim por diante. Essa visão por tarefa ajuda a decidir se o seu subconjunto está balanceado.

Passo 3 — Filtros de qualidade

Nem todo exemplo do corpus é bom para treino. O pipeline aplica quatro filtros em sequência:

def filter_length(row, min_tok=200, max_tok=3000):
    return min_tok <= row["tok_len"] <= max_tok

def filter_degenerate(row):
    return len(row["thought_trace"]) > 100 and len(row["assistant"]) > 20

def filter_repetition(row, max_line_repeat=0.30):
    lines = [l.strip() for l in row["thought_trace"].split("\n") if l.strip()]
    if len(lines) < 5:
        return True
    most_common = Counter(lines).most_common(1)[0][1]
    return (most_common / len(lines)) <= max_line_repeat

def filter_reason_ratio(row, lo=0.15, hi=0.97):
    t, a = len(row["thought_trace"]), len(row["assistant"])
    r = t / (t + a + 1)
    return lo <= r <= hi

ds_f = ds.filter(filter_length).filter(filter_degenerate)
ds_f = ds_f.filter(filter_repetition).filter(filter_reason_ratio)

Cada filtro tem um propósito claro: comprimento mantém exemplos dentro de um orçamento de tokens amigável ao treino; degeneração descarta pensamentos ou respostas vazios; repetição elimina rastros de modelos “em loop” que repetem a mesma linha; e razão de raciocínio garante que o exemplo realmente raciocina — sem ser só raciocínio, sem resposta.

Passo 4 — Converter para formato de chat com tags <think>

O próximo passo transforma cada exemplo em uma conversa estruturada: uma instrução de sistema, a pergunta do usuário e a resposta do assistente com o raciocínio entre tags <think>.

SYSTEM_PROMPT = ("You are a careful reasoning assistant. Think step by step "
                 "inside <think>...</think> tags, then give your final answer.")

def to_chat(row):
    return {"messages": [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": row["user"]},
        {"role": "assistant", "content": f"<think>\n{row['thought_trace']}\n</think>\n\n{row['assistant']}"},
    ]}

train_ds = ds_f.map(to_chat, remove_columns=ds_f.column_names)
train_ds = train_ds.shuffle(seed=42)
N_TRAIN, N_EVAL = 1_500, 100
eval_ds = train_ds.select(range(N_TRAIN, min(N_TRAIN + N_EVAL, len(train_ds))))
train_ds = train_ds.select(range(min(N_TRAIN, len(train_ds))))

As tags <think> são a convenção que ensina o modelo a separar o raciocínio interno da resposta final — o mesmo padrão usado por modelos como DeepSeek-R1. Ao dividir 1.500 exemplos para treino e 100 para avaliação, você consegue medir a perda de validação durante o ajuste.

Passo 5 — Fine-tuning com LoRA via SFTTrainer

Agora vem o treino. O modelo base é o SmolLM2-135M-Instruct, carregado em bfloat16 quando há GPU. A configuração LoRA com r=16 e alpha=32 adiciona apenas um pequeno conjunto de parâmetros treináveis.

from trl import SFTTrainer, SFTConfig
from peft import LoraConfig

model = AutoModelForCausalLM.from_pretrained(
    MODEL_ID, dtype=torch.bfloat16 if DEVICE == "cuda" else torch.float32).to(DEVICE)

peft_config = LoraConfig(r=16, lora_alpha=32, lora_dropout=0.05,
                         bias="none", task_type="CAUSAL_LM")

sft_config = SFTConfig(
    output_dir="smollm2-reasoning-demo", max_length=2048,
    per_device_train_batch_size=2, gradient_accumulation_steps=8,
    num_train_epochs=1, learning_rate=2e-4, lr_scheduler_type="cosine",
    warmup_steps=10, eval_strategy="steps", eval_steps=50,
    save_strategy="no", bf16=(DEVICE == "cuda"), gradient_checkpointing=True,
    report_to="none")

trainer = SFTTrainer(model=model, args=sft_config, train_dataset=train_ds,
                     eval_dataset=eval_ds, peft_config=peft_config,
                     processing_class=tokenizer)
trainer.train()  # ≈10–20 min em uma T4

O artigo usa 1 época com learning rate de 2e-4 e gradient checkpointing para economizar VRAM. O detalhe do torchao é importante: há um workaround que desativa a detecção automática do torchao no PEFT para evitar conflito de dependências — por isso o pacote foi removido no Passo 1.

Passo 6 — Inferência estruturada e exportação

O bloco final define a função de geração, testa o modelo com um problema de lógica e exporta os datasets curados.

def generate(question, max_new_tokens=512, temperature=0.7):
    msgs = [{"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": question}]
    prompt = tokenizer.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True)
    inputs = tokenizer(prompt, return_tensors="pt").to(DEVICE)
    with torch.no_grad():
        out = trainer.model.generate(**inputs, max_new_tokens=max_new_tokens,
                                     temperature=temperature, top_p=0.9, do_sample=True,
                                     pad_token_id=tokenizer.pad_token_id)
    # separa  da resposta final via regex
    ...

generate("If all bloops are razzies and all razzies are lazzies, "
         "are all bloops definitely lazzies? Explain briefly.")

train_ds.to_parquet("reasoning_subset_train.parquet")
eval_ds.to_parquet("reasoning_subset_eval.parquet")

A função de geração usa o mesmo system prompt do treino para manter consistência, e depois separa o bloco <think> da resposta final com uma expressão regular — permitindo inspecionar o raciocínio do modelo. A exportação em Parquet fecha o ciclo: os dados curados ficam prontos para experimentos maiores.

Casos de uso reais

  • Assistente de matemática local: rode o modelo ajustado em um notebook para resolver problemas passo a passo sem chamar APIs cloud.
  • Prototipagem de agentes: use o padrão <think> para que um agente explique o raciocínio antes de executar uma ação.
  • Ensino de NLP: o pipeline é um material didático completo sobre curadoria de dados e PEFT.
  • Base para modelos maiores: reutilize os filtros e o formato de chat para ajustar um modelo de 1B ou 3B com o mesmo corpus.
  • Benchmark interno: compare a razão de raciocínio antes e depois do fine-tuning para medir ganho real.

Troubleshooting — erros comuns e soluções

  • ❌ Erro de dependência com torchao ao importar PEFT. Causa: versões conflitantes. Solução: desinstale torchao antes de instalar as demais e aplique o workaround de is_torchao_available.
  • ❌ Memória insuficiente (CUDA OOM) no treino. Causa: batch_size alto ou max_length grande demais. Solução: reduza para per_device_train_batch_size=1 e ative gradient_checkpointing.
  • ❌ Dataset parece vazio após os filtros. Causa: limites muito restritivos. Solução: cheque a taxa de retenção impressa e afrouxe filter_reason_ratio ou filter_length.
  • ❌ Treino lento demais na CPU. Causa: dispositivo cpu detectado. Solução: ative a GPU no Colab (Runtime → Change runtime type) e confira DEVICE == "cuda".
  • ❌ A resposta gerada não mostra <think>. Causa: o modelo não aprendeu a convenção ou o system prompt da inferência difere do treino. Solução: mantenha o mesmo SYSTEM_PROMPT nos dois momentos e aumente max_new_tokens.

FAQ

  • Preciso pagar por GPU? Não. O Google Colab oferece GPU T4 gratuita, suficiente para o treino descrito (~10–20 min).
  • O que significa “4K-5M” no nome do dataset? Refere-se à escala do corpus: cerca de 4 mil repositórios de origem e 5 milhões de exemplos, dos quais você usa apenas 8.000.
  • Posso trocar o SmolLM2 por outro modelo? Sim. Basta mudar MODEL_ID para qualquer modelo causal compatível com Hugging Face, como Qwen ou Llama pequenos.
  • Por que usar LoRA em vez de treinar tudo? LoRA ajusta poucos parâmetros, reduzindo custo de memória e tempo — ideal para demonstrações e protótipos.
  • O modelo resultante serve para produção? Para produção, você precisaria de mais dados, mais épocas e um modelo base maior. Este tutorial é o ponto de partida.

Para onde isso vai

A tendência é clara: dados de raciocínio abertos e técnicas de ajuste eficiente estão democratizando o acesso a modelos que “pensam”. O pipeline descrito aqui é uma fundação reutilizável — a partir dele, dá para evoluir para mistura de fontes ponderada, curriculum learning (treinar primeiro em exemplos fáceis) e contextos mais longos. Se hoje um modelo de 135M aprende a raciocinar em uma GPU gratuita, em pouco tempo veremos fluxos equivalentes rodando em escala, com modelos de bilhões de parâmetros ajustados em ambientes acessíveis.



Descubra mais sobre noticiAI

Assine para receber nossas notícias mais recentes por e-mail.

R
Sobre o autorRedação Noticiai

Equipe editorial dedicada a explicar inteligência artificial com clareza, independência e contexto.