Aqui está o conteúdo reescrito no estilo do Lucas Tech, seguindo todas as suas instruções:
DPO Desvendado: Como Eu Ensinei uma IA a Escolher as MELHORES Respostas e Otimizei Modelos de Linguagem!
Olá, pessoal! Aqui é o Lucas Tech, e hoje a gente vai mergulhar de cabeça em um dos tópicos mais quentes do universo da Inteligência Artificial: o Direct Preference Optimization, ou DPO! Já pensou em como as IAs aprendem a ‘preferir’ uma resposta em vez de outra? Como a gente ensina elas a terem um ‘bom gosto’ e a serem realmente úteis pra gente?
Pois é, o DPO é a chave! Prepare-se para uma jornada incrível onde vamos construir um fluxo completo de aprendizado de preferências, usando um dataset superimportante da Anthropic. Vamos nessa?
Preparando o Terreno: Nosso Laboratório de DPO
A primeira coisa que fazemos em qualquer projeto sério de IA é preparar o ambiente, né? Imagine que estamos montando nosso laboratório de DPO aqui no Colab! Garanto que todas as bibliotecas necessárias estão instaladas – e olha que às vezes dá um trabalhinho com as dependências, mas a gente resolve! O importante é ter um lugar estável e configurado, com todas as ferramentas à mão, pra garantir que a gente possa focar no que realmente interessa: treinar nossa IA sem dores de cabeça.
A gente ainda dá uma olhada no hardware disponível (GPU, CPU), nos modos de precisão (bf16, fp16) e nas interfaces do TRL pra ter certeza que está tudo pronto.
php
import dataclasses
import importlib.util
import inspect
import os
import re
import subprocess
import sys
import warnings
warnings.filterwarnings("ignore", category=UserWarning)
REQUIRED = ["trl>=0.12", "transformers>=4.45", "accelerate", "datasets", "peft", "scikit-learn"]
def ensure_deps():
"""Install in ONE pip call so the resolver picks a mutually compatible set."""
try:
import trl
import transformers
return False
except ImportError:
print("Installing dependencies…")
subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", "-U", *REQUIRED])
return True
def drop_broken_torchao():
"""Colab ships torchao 0.10.0; peft demands >0.16 and raises rather than skipping.
Nothing here uses torchao, so removing it is safer than upgrading (an upgrade can
drag in a torch build that does not match this runtime)."""
if importlib.util.find_spec("torchao") is None:
return False
try:
from peft.import_utils import is_torchao_available
is_torchao_available()
return False
except ImportError:
print("Removing incompatible torchao (unused, but peft raises on it)…")
subprocess.call([sys.executable, "-m", "pip", "uninstall", "-y", "-q", "torchao"])
return True
except Exception:
return False
_installed = ensure_deps()
_removed = drop_broken_torchao() if not _installed else False
if _installed or _removed:
print("\nEnvironment changed. RESTART THE RUNTIME (Runtime > Restart session), "
"then run this cell again.")
raise SystemExit(0)
import numpy as np
import pandas as pd
import torch
import matplotlib.pyplot as plt
from datasets import load_dataset, concatenate_datasets
from transformers import AutoModelForCausalLM, AutoTokenizer, set_seed
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report, roc_auc_score
import transformers
import trl
from trl import DPOConfig, DPOTrainer
def patch_peft_torchao():
"""Belt and braces: if torchao survived the uninstall, stop peft raising on it."""
try:
from peft import import_utils
from peft.tuners.lora import torchao as lora_torchao
except ImportError:
return
try:
import_utils.is_torchao_available()
except ImportError as exc:
print(f" neutralising peft’s torchao check ({exc})")
import_utils.is_torchao_available = lambda: False
lora_torchao.is_torchao_available = lambda: False
MODEL_ID = "Qwen/Qwen2.5-0.5B-Instruct"
SUBSETS = ["helpful-base", "helpful-rejection-sampled", "helpful-online", "harmless-base"]
N_TRAIN_PER_SUBSET = 120
N_TEST_PER_SUBSET = 30
MAX_LENGTH = 512
MAX_PROMPT_LENGTH = 256
BETA = 0.1
MAX_STEPS = 30
BATCH_SIZE = 1
GRAD_ACCUM = 8
LEARNING_RATE = 5e-6
WARMUP_RATIO = 0.1
LOGGING_STEPS = 5
USE_LORA = True
N_REWARD_EVAL = 40
SEED = 17
OUTPUT_DIR = "/content/dpo-hh" if os.path.isdir("/content") else "./dpo-hh"
set_seed(SEED)
rng = np.random.default_rng(SEED)
def report_environment():
from transformers import TrainingArguments
cuda = torch.cuda.is_available()
bf16 = bool(cuda and torch.cuda.is_bf16_supported())
fp16 = bool(cuda and not bf16)
device = "cuda" if cuda else "cpu"
print(f"python : {sys.version.split()[0]}")
print(f"torch : {torch.version}")
print(f"transformers : {transformers.version}")
print(f"trl : {trl.version}")
print(f"Device: {device} | bf16={bf16} | fp16={fp16}")
if not cuda:
print("CPU fallback is enabled; training is intentionally shortened.")
cfg_fields = {f.name for f in dataclasses.fields(DPOConfig)}
trainer_params = set(inspect.signature(DPOTrainer.init).parameters)
print(f"DPOConfig subclasses TrainingArguments : {issubclass(DPOConfig, TrainingArguments)}")
print(f"DPOConfig fields : {len(cfg_fields)}")
for probe in ("warmup_ratio", "warmup_steps", "beta", "max_length", "max_prompt_length"):
where = [c for c, s in (("DPOConfig", cfg_fields), ("DPOTrainer", trainer_params))
if probe in s]
print(f" {probe:<20} -> {‘, ‘.join(where) if where else ‘NOT ACCEPTED ANYWHERE’}")
if not issubclass(DPOConfig, TrainingArguments) or "per_device_train_batch_size" not in cfg_fields:
print("\n!! DPOConfig looks broken. Reinstall in one command, then restart:")
print(" pip install -U trl transformers accelerate datasets peft")
return device, bf16, fp16, cfg_fields, trainer_params
DEVICE, BF16, FP16, CFG_FIELDS, TRAINER_PARAMS = report_environment()
Dissecando o Dataset Anthropic HH-RLHF
Agora que o ambiente está tinindo, é hora de apresentar a estrela da nossa base de dados: o Anthropic HH-RLHF! Ele é superimportante porque contém milhares de pares de respostas "escolhidas" e "rejeitadas". Basicamente, é como se tivéssemos várias conversas onde um humano disse: "Gostei mais dessa resposta do que daquela".
A gente carrega essas amostras, cria conjuntos de treino e teste bem equilibrados e, o mais importante, organiza cada conversa em mensagens estruturadas de usuário e assistente. É crucial garantir que a parte "escolhida" e a "rejeitada" tenham o mesmo início de conversa, sabe? Assim, a gente filtra qualquer par ‘problemático’ e trabalha só com exemplos de preferência bem alinhados. É como ter certeza que estamos comparando maçãs com maçãs!
php
def sample_split(ds, n, seed):
return ds.shuffle(seed=seed).select(range(min(n, len(ds)))).flatten_indices()
def load_hh():
train_parts, test_parts = [], []
for i, subset in enumerate(SUBSETS):
ds = load_dataset("Anthropic/hh-rlhf", data_dir=subset)
tr = sample_split(ds["train"], N_TRAIN_PER_SUBSET, SEED + i)
te = sample_split(ds["test"], N_TEST_PER_SUBSET, SEED + i)
train_parts.append(tr.add_column("source", [subset] len(tr)))
test_parts.append(te.add_column("source", [subset] len(te)))
return concatenate_datasets(train_parts), concatenate_datasets(test_parts)
raw_train, raw_test = load_hh()
print(f"\nRaw sampled rows -> train={len(raw_train)}, test={len(raw_test)}")
print(pd.Series(raw_train["source"]).value_counts().sort_index().to_string())
TURN_RE = re.compile(r"\n\n(Human|Assistant):[ ]?")
def parse_transcript(text):
if not isinstance(text, str) or not text.strip():
return None
parts = TURN_RE.split(text)
if parts[0].strip():
return None
roles, contents = parts[1::2], parts[2::2]
if len(roles) != len(contents) or len(roles) < 2:
return None
msgs = [{"role": "user" if r == "Human" else "assistant", "content": c.strip()}
for r, c in zip(roles, contents)]
if msgs[0]["role"] != "user" or msgs[-1]["role"] != "assistant":
return None
if any(a["role"] == b["role"] for a, b in zip(msgs, msgs[1:])):
return None
if any(not m["content"] for m in msgs):
return None
return msgs
def to_pair(row):
ch = parse_transcript(row["chosen"])
rj = parse_transcript(row["rejected"])
ok = ch is not None and rj is not None and ch[:-1] == rj[:-1]
return {
"ok": bool(ok),
"prompt": ch[:-1] if ok else [],
"chosen": [ch[-1]] if ok else [],
"rejected": [rj[-1]] if ok else [],
"prompt_turns": len(ch) – 1 if ok else 0,
"source": row["source"],
}
parsed_train = raw_train.map(to_pair, remove_columns=raw_train.column_names).filter(lambda r: r["ok"])
parsed_test = raw_test.map(to_pair, remove_columns=raw_test.column_names).filter(lambda r: r["ok"])
print(f"\nValid parsed rows -> train={len(parsed_train)}, test={len(parsed_test)}")
identical = sum(1 for c, r in zip(parsed_train["chosen"], parsed_train["rejected"])
if c[0]["content"] == r[0]["content"])
print(f"Identical completion pairs in sampled train: {identical}")
Auditando as Preferências: Evitando ‘Trapaças’ da IA
Antes de treinar, a gente precisa ser um bom detetive! Vamos auditar esses pares de preferência para ver se não tem nenhuma ‘armadilha’. A gente analisa coisas como a diferença no tamanho das respostas (será que o modelo prefere só respostas mais longas?), a profundidade da conversa e até comportamentos específicos de cada parte do dataset.
Além disso, aplicamos um diagnóstico lexical usando TF-IDF e regressão logística. Isso é pra descobrir se a IA não está sendo ‘preguiçosa’, sabe? Tipo, aprendendo padrões linguísticos simples (como certas palavras ou frases) em vez de entender a verdadeira preferência por trás. Nosso objetivo é que ela aprenda o sinal de preferência real, e não um ‘atalho’ linguístico bobo!
php
audit = pd.DataFrame({
"source": parsed_train["source"],
"prompt_turns": parsed_train["prompt_turns"],
"chosen_words": [len(c[0]["content"].split()) for c in parsed_train["chosen"]],
"rejected_words": [len(r[0]["content"].split()) for r in parsed_train["rejected"]],
})
audit["length_delta"] = audit["chosen_words"] – audit["rejected_words"]
summary = audit.groupby("source").agg(
pairs=("chosen_words", "size"),
chosen_words_mean=("chosen_words", "mean"),
rejected_words_mean=("rejected_words", "mean"),
median_turns=("prompt_turns", "median"),
mean_length_delta=("length_delta", "mean"),
).round(2)
print("\nPreference-pair audit:")
print(summary.to_string())
fig, axes = plt.subplots(1, 2, figsize=(11, 4))
summary["mean_length_delta"].plot(kind="barh", ax=axes[0], color="#4c72b0")
axes[0].axvline(0, color="0.3", lw=1)
axes[0].set_title("mean(chosen − rejected) words")
axes[0].set_ylabel("")
for src, grp in audit.groupby("source"):
axes[1].hist(grp["length_delta"], bins=30, histtype="step", lw=1.6, label=src)
axes[1].axvline(0, color="0.3", lw=1)
axes[1].set_title("per-pair length delta")
axes[1].legend(fontsize=7)
plt.tight_layout()
plt.show()
print("\nSanitized structural preview (user text is not printed):")
for i in range(min(3, len(audit))):
r = audit.iloc[i]
print({"source": r["source"], "prompt_turns": int(r["prompt_turns"]),
"chosen_words": int(r["chosen_words"]), "rejected_words": int(r["rejected_words"])})
def build_lexical_dataset(ds):
chosen_txt = [c[0]["content"] for c in ds["chosen"]]
rejected_txt = [r[0]["content"] for r in ds["rejected"]]
texts = chosen_txt + rejected_txt
labels = np.concatenate([np.ones(len(chosen_txt), int), np.zeros(len(rejected_txt), int)])
pair_id = np.concatenate([np.arange(len(chosen_txt)), np.arange(len(rejected_txt))])
assert texts[: len(chosen_txt)] == chosen_txt and labels[: len(chosen_txt)].all()
assert not labels[len(chosen_txt):].any()
return np.array(texts, dtype=object), labels, pair_id
def run_lexical_diagnostic(texts, labels, pair_id, tag="observed"):
pairs = np.unique(pair_id)
shuffled = rng.permutation(pairs)
test_pairs = set(shuffled[: len(shuffled) // 2].tolist())
is_test = np.array([p in test_pairs for p in pair_id])
vec = TfidfVectorizer(ngram_range=(1, 2), min_df=2, max_features=20000, sublinear_tf=True)
Xtr = vec.fit_transform(texts[~is_test])
Xte = vec.transform(texts[is_test])
clf = LogisticRegression(max_iter=2000).fit(Xtr, labels[~is_test])
pred = clf.predict(Xte)
prob = clf.predict_proba(Xte)[:, 1]
acc = accuracy_score(labels[is_test], pred)
auc = roc_auc_score(labels[is_test], prob)
print(f"Lexical diagnostic ({tag}) accuracy: {acc:.3f}")
print(f"Lexical diagnostic ({tag}) ROC-AUC: {auc:.3f}")
return acc, auc, clf, labels[is_test], pred
print("\nTraining a lexical diagnostic to detect easy preference shortcuts…")
texts, labels, pair_id = build_lexical_dataset(parsed_train)
acc, auc, clf, y_true, y_pred = run_lexical_diagnostic(texts, labels, pair_id)
print(classification_report(y_true, y_pred, targetnames=["rejected", "chosen"], digits=3))
perm = rng.permutation(len(labels))
, aucperm, , , = run_lexical_diagnostic(texts, labels[perm], pair_id, tag="permuted labels")
print(f"Chance baseline from permuted labels: AUC {auc_perm:.3f}")
if abs(auc – 0.5) <= abs(aucperm – 0.5) + 0.02:
print("-> observed AUC is within permutation noise: no detectable lexical shortcut.")
elif auc < 0.5:
print("-> observed AUC is BELOW chance beyond noise: inspect label ordering upstream.")
else:
print("-> observed AUC is ABOVE chance: a real lexical shortcut exists in this sample.")
coefs = np.sort(np.abs(clf.coef.ravel()))[-20:]
print(f"Top-20 absolute lexical coefficient range: {coefs[0]:.3f} to {coefs[-1]:.3f}")
print("Feature strings are intentionally not printed because the source corpus may contain offensive text.")
Preparando os Dados e Montando o Treinador DPO
Com os dados auditados, é hora de ‘fatiar’ e ‘temperar’ tudo para o nosso DPO! Primeiro, a gente prepara o tokenizer – ele é quem transforma nosso texto em números que a IA consegue entender. Aplicamos um template de chat específico pra ele saber onde começa e termina cada turno de conversa.
Calculamos o tamanho de cada resposta em tokens e filtramos aquelas que são muito longas (não queremos sobrecarregar o modelo, certo?). O mais legal é que configuramos os parâmetros do DPO de forma dinâmica, adaptando-nos à versão do TRL que estamos usando – é flexibilidade pura! Depois, carregamos nosso modelo base (o Qwen2.5-0.5B-Instruct, no caso), ativamos o LoRA (que é uma forma super eficiente de treinar modelos gigantes sem precisar de uma supermáquina!) e montamos o nosso ‘treinador’ DPO, pronto para a ação!
php
print("\nPreparing conversational DPO data…")
tok = AutoTokenizer.from_pretrained(MODEL_ID)
if tok.pad_token is None:
tok.pad_token = tok.eos_token
CHATML = (
"{% for m in messages %}"
"{{ ‘<|im_start|>’ + m[‘role’] + ‘\n’ + m[‘content’] + ‘<|im_end|>\n’ }}"
"{% endfor %}"
"{% if add_generation_prompt %}{{ ‘<|im_start|>assistant\n’ }}{% endif %}"
)
if getattr(tok, "chat_template", None) is None:
tok.chat_template = CHATML
print("Tokenizer had no chat template; installed a ChatML fallback.")
def add_lengths(row):
prompt_txt = tok.apply_chat_template(row["prompt"], tokenize=False, add_generation_prompt=True)
n_prompt = len(tok(prompt_txt, add_special_tokens=False)["input_ids"])
n_ch = len(tok(row["chosen"][0]["content"], add_special_tokens=False)["input_ids"])
n_rj = len(tok(row["rejected"][0]["content"], add_special_tokens=False)["input_ids"])
return {"n_prompt": n_prompt, "n_total": n_prompt + max(n_ch, n_rj)}
def fits(row):
return row["n_prompt"] <= MAX_PROMPT_LENGTH and row["n_total"] <= MAX_LENGTH
dpo_train_full = parsed_train.map(add_lengths).filter(fits)
dpo_test_full = parsed_test.map(add_lengths).filter(fits)
test_sources = list(dpo_test_full["source"])
test_prompts = list(dpo_test_full["prompt"])
test_chosen = list(dpo_test_full["chosen"])
test_rejected = list(dpo_test_full["rejected"])
DPO_COLS = ["prompt", "chosen", "rejected"]
dpo_train = dpo_train_full.remove_columns([c for c in dpo_train_full.column_names if c not in DPO_COLS])
dpo_test = dpo_test_full.remove_columns([c for c in dpo_test_full.column_names if c not in DPO_COLS])
print(f"DPO-ready rows after {MAX_LENGTH}-token filter -> train={len(dpo_train)}, test={len(dpo_test)}")
print("DPO schema:", dict(dpo_train.features))
def split_kwargs(wanted, valid):
return ({k: v for k, v in wanted.items() if k in valid},
{k: v for k, v in wanted.items() if k not in valid})
def build_dpo_config(wanted):
kept, dropped = split_kwargs(wanted, CFG_FIELDS)
if "warmup_ratio" in dropped and "warmup_steps" in CFG_FIELDS:
steps = max(1, int(dropped.pop("warmup_ratio") * wanted.get("max_steps", 100)))
kept["warmup_steps"] = steps
print(f" warmup_ratio unsupported here -> converted to warmup_steps={steps}")
forwarded, truly_dropped = split_kwargs(dropped, TRAINER_PARAMS)
if forwarded:
print(" forwarded to DPOTrainer:", sorted(forwarded))
if truly_dropped:
print(" dropped (accepted nowhere in this build):", sorted(truly_dropped))
if "max_prompt_length" in truly_dropped:
print(" -> harmless: the token filter in section 7 already caps prompts")
return DPOConfig(kept), forwarded
wanted_args = dict(
output_dir=OUTPUT_DIR,
max_steps=MAX_STEPS,
per_device_train_batch_size=BATCH_SIZE,
per_device_eval_batch_size=BATCH_SIZE,
gradient_accumulation_steps=GRAD_ACCUM,
learning_rate=LEARNING_RATE,
warmup_ratio=WARMUP_RATIO,
logging_steps=LOGGING_STEPS,
save_strategy="no",
report_to=[],
remove_unused_columns=False,
bf16=BF16,
fp16=FP16,
seed=SEED,
beta=BETA,
max_length=MAX_LENGTH,
max_prompt_length=MAX_PROMPT_LENGTH,
)
print("\nBuilding DPOConfig for the installed TRL…")
args, forwarded_to_trainer = build_dpo_config(wanted_args)
print(" DPOConfig built OK")
def build_model():
dtype = torch.bfloat16 if BF16 else torch.float32
try:
model = AutoModelForCausalLM.from_pretrained(MODEL_ID, dtype=dtype)
except TypeError:
model = AutoModelForCausalLM.from_pretrained(MODEL_ID, torch_dtype=dtype)
model.config.use_cache = False
return model
peft_config = None
if USE_LORA:
try:
from peft import LoraConfig
peft_config = LoraConfig(
r=16, lora_alpha=32, lora_dropout=0.05, bias="none",
task_type="CAUSAL_LM",
)
print(" LoRA enabled (the frozen base doubles as the reference model)")
except ImportError:
print(" peft not installed -> full fine-tune with an explicit reference model")
def build_trainer(model, args, train_ds, eval_ds, tokenizer, peft_config, extra):
kwargs = dict(model=model, args=args, train_dataset=train_ds, eval_dataset=eval_ds)
if "processing_class" in TRAINER_PARAMS:
kwargs["processing_class"] = tokenizer
elif "tokenizer" in TRAINER_PARAMS:
kwargs["tokenizer"] = tokenizer
if peft_config is not None and "peft_config" in TRAINER_PARAMS:
kwargs["peft_config"] = peft_config
elif peft_config is None and "ref_model" in TRAINER_PARAMS:
kwargs["ref_model"] = None
kwargs.update(extra)
print(" DPOTrainer kwargs:", sorted(kwargs))
return DPOTrainer(kwargs)
print("\nBuilding DPOTrainer…")
patch_peft_torchao()
model = build_model()
trainer = build_trainer(model, args, dpo_train, dpo_test, tok, peft_config, forwarded_to_trainer)
print(" DPOTrainer built OK")
A Hora da Verdade: Treinamento e Avaliação da Preferência
Agora é o momento mais esperado: o treinamento! A gente coloca o modelo para ‘aprender’ as preferências usando o DPO, com os parâmetros que configuramos. É aqui que ele ajusta seus neurônios para entender por que uma resposta é melhor que a outra.
Depois que ele treina, a gente não só cruza os dedos, mas avalia a ‘política’ de preferência que ele aprendeu em dados que ele nunca viu antes. Olhamos métricas como a ‘loss’ (o quão ‘errado’ ele está), as margens de recompensa e a precisão da recompensa. E claro, sempre bom ter um gráfico pra ver como o aprendizado evoluiu ao longo do tempo. É fascinante ver a IA se tornar mais ‘sábia’!
php
print(f"\nTraining for {MAX_STEPS} steps on {DEVICE} "
f"(effective batch {BATCH_SIZE * GRAD_ACCUM})…")
train_result = trainer.train()
print("\nTraining metrics:")
for k, v in sorted(train_result.metrics.items()):
print(f" {k:<28} {v}")
print("\nEvaluating on held-out pairs…")
eval_metrics = trainer.evaluate()
for k, v in sorted(eval_metrics.items()):
if any(t in k for t in ("accuracies", "margins", "rewards", "loss")):
print(f" {k:<34} {v:.4f}" if isinstance(v, float) else f" {k:<34} {v}")
log_df = pd.DataFrame(trainer.state.log_history)
if "loss" in log_df.columns:
fig, ax = plt.subplots(figsize=(7, 3.5))
d = log_df.dropna(subset=["loss"])
ax.plot(d["step"], d["loss"], marker="o", ms=3, label="train loss")
acc_col = next((c for c in log_df.columns if c.endswith("rewards/accuracies")), None)
if acc_col:
d2 = log_df.dropna(subset=[acc_col])
ax.plot(d2["step"], d2[acc_col], marker="s", ms=3, label="reward accuracy")
ax.axhline(0.5, color="0.6", lw=0.8, ls="–")
ax.set_xlabel("step")
ax.legend(fontsize=8)
ax.set_title("DPO training")
plt.tight_layout()
plt.show()
Análise Detalhada e Gerações Iniciais
Depois do treinamento, a gente não para por aí. Mergulhamos mais fundo para entender se o modelo realmente está preferindo as respostas certas, ou se ele está pegando algum ‘macete’. Calculamos a precisão da recompensa por cada ‘fonte’ do dataset e comparamos as probabilidades do nosso modelo com um modelo de referência.
Isso nos ajuda a ver se o que ele aprendeu é uma preferência genuína ou se ele está apenas optando por respostas mais longas, por exemplo. Por último, mas não menos importante, pedimos para a nossa IA gerar algumas respostas para ver, na prática, como ela se comporta depois de todo o treinamento. É sempre um momento de ‘uau’! E claro, salvamos tudo: o modelo treinado e o tokenizer, pra poder usar essa inteligência em projetos futuros.
php
@torch.no_grad()
def completion_logprob(policy, messages_prompt, message_completion, use_ref=False):
prompt_txt = tok.apply_chat_template(messages_prompt, tokenize=False, add_generation_prompt=True)
full_txt = prompt_txt + message_completion["content"] + tok.eos_token
p_ids = tok(prompt_txt, add_special_tokens=False, return_tensors="pt")["input_ids"]
f_ids = tok(full_txt, add_special_tokens=False, return_tensors="pt",
truncation=True, max_length=MAX_LENGTH)["input_ids"].to(policy.device)
start = min(p_ids.shape[1], f_ids.shape[1] – 1)
ctx = policy.disable_adapter() if (use_ref and hasattr(policy, "disable_adapter")) else None
if ctx is not None:
with ctx:
logits = policy(f_ids).logits
else:
logits = policy(f_ids).logits
logprobs = torch.log_softmax(logits[:, :-1].float(), dim=-1)
targets = f_ids[:, 1:]
picked = logprobs.gather(-1, targets.unsqueeze(-1)).squeeze(-1)
return picked[:, start:].sum().item()
def per_source_reward_accuracy(n=N_REWARD_EVAL):
policy = trainer.model
policy.eval()
if not hasattr(policy, "disable_adapter") and getattr(trainer, "ref_model", None) is None:
print(" no reference model reachable; skipping per-source analysis")
return None
idx = rng.permutation(len(test_sources))[:min(n, len(test_sources))]
rows = []
for i in idx:
i = int(i)
rc = completion_logprob(policy, test_prompts[i], test_chosen[i][0])
rr = completion_logprob(policy, test_prompts[i], test_rejected[i][0])
refc = completion_logprob(policy, test_prompts[i], test_chosen[i][0], use_ref=True)
refr = completion_logprob(policy, test_prompts[i], test_rejected[i][0], use_ref=True)
rows.append({
"source": test_sources[i],
"margin": BETA ((rc – refc) – (rr – refr)),
"correct": BETA ((rc – refc) – (rr – refr)) > 0,
"len_delta": len(test_chosen[i][0]["content"].split())
- len(test_rejected[i][0]["content"].split()),
})
df = pd.DataFrame(rows)
out = df.groupby("source").agg(
n=("correct", "size"),
reward_accuracy=("correct", "mean"),
mean_margin=("margin", "mean"),
mean_len_delta=("len_delta", "mean"),
).round(3)
print(out.to_string())
longer_wins = (df["correct"] == (df["len_delta"] > 0)).mean()
print(f"\n agreement between ‘model prefers chosen’ and ‘chosen is longer’: {longer_wins:.3f}")
print(" (near 0.5 = no length shortcut; near 1.0 = the policy is mostly ranking by length)")
return out
print(f"\nPer-source reward accuracy on {N_REWARD_EVAL} held-out pairs:")
try:
per_source = per_source_reward_accuracy()
except Exception as exc:
print(f" skipped: {type(exc).name}: {exc}")
per_source = None
def generate(messages, max_new_tokens=96):
text = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
enc = tok(text, return_tensors="pt").to(trainer.model.device)
with torch.no_grad():
out = trainer.model.generate(**enc, max_new_tokens=max_new_tokens, do_sample=False,
pad_token_id=tok.pad_token_id)
return tok.decode(out[0][enc["input_ids"].shape[1]:], skip_special_tokens=True).strip()
probes = [
[{"role": "user", "content": "My laptop fan is suddenly very loud. What should I check first?"}],
[{"role": "user", "content": "Explain in two sentences why DPO does not need a separate reward model."}],
]
print("\nSample generations from the tuned policy:")
for p in probes:
print(f"\n user : {p[0][‘content’]}")
print(f" assistant : {generate(p)}")
trainer.save_model(OUTPUT_DIR)
tok.save_pretrained(OUTPUT_DIR)
print(f"\nSaved to {OUTPUT_DIR}")
print("""
Reading the results- At MAX_STEPS=30 on CPU this is a smoke test, not a trained model. Reward accuracy
near 0.5 is the expected outcome; raise MAX_STEPS on a GPU before concluding anything. - The number to watch is the per-source table, not the aggregate. If harmless-base
reward accuracy drops while the helpful subsets rise, the policy is learning the
length asymmetry visible in the section 5 audit rather than the intended preference. - If a TRL call fails after an upgrade, the environment report at the top names which
class accepts which argument in your build; extendwanted_argsfrom that list.
""")
- At MAX_STEPS=30 on CPU this is a smoke test, not a trained model. Reward accuracy
Minha Visão
Olha, pessoal, essa jornada pelo DPO é mais do que um simples tutorial técnico. Pra mim, ela representa um passo GIGANTE na forma como construímos IAs. Não é só sobre fazer um modelo responder; é sobre fazer um modelo entender o que é uma boa resposta para nós. Auditar os dados, procurar vieses, garantir que a IA não pegue atalhos… tudo isso nos leva a modelos mais robustos, mais éticos e, acima de tudo, mais úteis. Chega de IAs que só dão respostas genéricas! Com DPO, estamos ensinando IAs a ter nuances, a ter ‘senso crítico’. É o futuro da interação humano-IA acontecendo agora!
E aí, o que vocês acham? Qual é o próximo passo para IAs que realmente entendem e nos servem melhor? Compartilhem suas ideias nos comentários!
Referência: Matéria Original
Posts relacionados:

THINK: The user wants me to rewrite a long, technical English title into a short, curious Portuguese title suitable for Google Discover. I need to focus on the "autonomous machine learning research loop" and "hyperparameter discovery" aspects, making it sound intriguing without being overly complex. I must only return the final title.
Analyze the original title:
- "How to Build an Autonomous Machine Learning Research Loop in Google Colab Using Andrej Karpathy’s AutoResearch Framework for Hyperparameter Discovery and Experiment Tracking"
- Core idea 1: Autonomous ML Research Loop
- Core idea 2: Hyperparameter Discovery / Experiment Tracking
- Tool: Google Colab
- Framework: Andrej Karpathy’s AutoResearch Framework (this is too specific/long for Discover)
Identify key concepts for curiosity:
- "Autonomous" -> automatizado, sozinho, sem você
- "Research Loop" -> pesquisa, descoberta, otimização
- "Machine Learning" -> IA, aprendizado de máquina
- "Hyperparameter Discovery" -> melhores resultados, segredos, IA que se aprimora
- "Google Colab" -> fácil, acessível
Brainstorm short, curious Portuguese phrases:
- "Sua IA faz pesquisa sozinha?" (Too informal/simplistic)
- "Colab: sua IA descobre os segredos sozinha."
- "Máquina que aprende sozinha: o loop de pesquisa."
- "Automação ML: deixe a IA otimizar tudo."
- "Descoberta de hiperparâmetros: a IA faz por você."
- "Colab: IA autônoma em pesquisa."
- "Sua IA pesquisa e se aprimora sozinha."
- "Colab: Sua IA pesquisa e se aprimora SOZINHA?"
Refine for Discover style (short, curious, engaging):
- Needs to hint at the "loop" and "discovery" without being explicit about the technical terms.
- "Autonomous" is strong. "Máquina que aprende sozinha" is good.
- "Colab" is a recognizable platform.
- Focus on the benefit: better results, faster discovery, less manual work.
Draft options and evaluate:
- Option 1: "Sua IA pesquisa e se aprimora sozinha no Colab?" (Good, curious, short, uses "sozinha" for autonomous and "aprimora" for hyperparameter discovery/experiment tracking implicitly).
- Option 2: "Colab: Deixe sua IA descobrir os melhores parâmetros." (A bit less about the "loop" and "research," more specific to parameters).
- Option 3: "Máquina que aprende sozinha: o segredo no Colab." (Intriguing, but "segredo" might be too vague).
- Option 4: "Crie uma IA que pesquisa sozinha no Google Colab." (Good, but "crie" might imply building the AI, not just the loop).
- Option 5: "Colab: Sua IA já otimiza os próprios resultados?" (Good, focuses on optimization and "próprios" implies autonomous).
Select the best fit: Option 1 or 5 are strong. Option 1 "Sua IA pesquisa e se aprimora sozinha no Colab?" directly addresses the "research loop" (pesquisa) and "hyperparameter discovery/experiment tracking" (aprimora), while keeping it curious and short. "Sozinha" captures "autonomous." It’s also a question, which is good for Discover engagement.
Let’s try to make it even more punchy, maybe less of a question. "Colab: Sua IA Otimiza Sozinha?" (Too short, loses "research") "Colab: A IA Que Pesquisa e Se Aprimora SOZINHA" (Strong, emphasizes "sozinha") "Descubra: Sua IA Pesquisa E Otimiza Sozinha?" (Adds "Descubra" for curiosity) "Colab: Sua IA já se aprimora sozinha?" (Good, concise, highlights the autonomous self-improvement)
Let’s go with a direct, intriguing question that hints at the core functionality. The "loop" implies continuous research and improvement.
"Sua IA Pesquisa e Otimiza SOZINHA no Colab?" This captures:
- "Pesquisa" (Research loop)
- "Otimiza" (Hyperparameter discovery, experiment tracking, improvement)
- "SOZINHA" (Autonomous)
- "Colab" (Platform)
- It’s a question, making it curious.
- It’s short.
I think "Sua IA Pesquisa e Otimiza SOZINHA no Colab?" is a very good fit. It’s direct, curious, and covers the main points succinctly.
Let’s try one more: "Colab: Sua IA Já Otimiza os Próprios Experimentos?" This is also good, "otimiza os próprios experimentos" maps well to the hyperparameter discovery and experiment tracking loop. "Próprios" implies autonomous.
I will go with something that highlights the "alone/autonomous" aspect clearly, and the "improvement" or "research" part.
"Colab: Sua IA pesquisa e se aprimora SOZINHA?" – This uses "pesquisa" (research) and "aprimora" (improves/optimizes) and "sozinha" (autonomous) and "Colab". It’s a question, short, and curious. This feels like a strong contender.Colab: Sua IA pesquisa e se aprimora SOZINHA?

THINKING PROCESS:
Analyze the original title: "I just played Crimson Desert on PS5 Pro — here’s how this massive RPG looks and runs on console"
- Core subject: Crimson Desert
- Platform: PS5 Pro (and "console" in general)
- Main aspects discussed: Looks (graphics), runs (performance)
- Implied tone: Personal experience, informative review/first impression.
- Keywords: Crimson Desert, PS5 Pro, RPG, looks, runs, console.
Understand the target platform: Google Discover.
- Characteristics: Visual-driven, snackable content, engaging, curiosity-inducing, often uses questions, bold claims, or intriguing statements. Personalization is key.
- Goal: Make people click now.
Understand the requirements:
- Language: Portuguese (BR, implied).
- Length: Short.
- Tone: Curious.
- Output: ONLY the final title text.
Brainstorm Portuguese equivalents for key concepts:
- "I just played" -> Joguei, Acabei de jogar, Minha experiência com
- "Crimson Desert" -> Crimson Desert (keep original)
- "PS5 Pro" -> PS5 Pro (keep original)
- "massive RPG" -> RPG massivo, RPG gigante, mundo aberto gigante
- "looks and runs" -> Gráficos e desempenho, Como roda, Como está, O que esperar
- "on console" -> No console, No PS5 Pro
Focus on "short and curious" for Google Discover:
Curiosity triggers:
- Questions (e.g., "Impressionado?", "Como será?", "Vale a pena?")
- Intriguing statements (e.g., "Você não vai acreditar!", "Chocante!", "Surpreendente!")
- Direct address ("Você precisa ver")
- Highlighting the impressiveness or challenge of the game/platform.
- Using strong adjectives (massivo, gigante, surpreendente, incrível).
- Shortness: Avoid long clauses, get straight to the point.
Drafting – Iteration 1 (Mental or scratchpad):
- "Joguei Crimson Desert no PS5 Pro: veja como roda!" (Too plain)
- "Crimson Desert no PS5 Pro: gráficos e desempenho surpreendem?" (Better, but maybe more active)
- "Como Crimson Desert roda no PS5 Pro? É gigante!" (A bit clunky)
- "O RPG massivo Crimson Desert no PS5 Pro: veja como ele se saiu!" (A bit long)
Focus on the "massive RPG" aspect combined with "PS5 Pro":
- The challenge for an "RPG massivo" to run well on a console is the implicit hook. The PS5 Pro is the specific new tech.
Drafting – Iteration 2 (Closer to final):
- "Crimson Desert no PS5 Pro: o RPG massivo surpreende no console?" (Good, but "surpreende" might be too weak, or implies it might not surprise).
- "Crimson Desert no PS5 Pro: É assim que o RPG massivo roda!" (A bit bland).
- "Joguei Crimson Desert no PS5 Pro: o RPG massivo impressiona!" (Good, but "joguei" might make it sound too personal, not for a broader audience seeking a quick info bite). Discover often uses more declarative or question-based styles.
Refine for "curious" and "short":
What makes people curious about a huge game on a powerful console?
- Does it push the limits?
- Is it smooth?
- Are the graphics mind-blowing?
Combine game, platform, and key performance aspects.
- Option 1 (Question-based): "Crimson Desert no PS5 Pro: O RPG massivo impressiona?" (Good, short, curious. "Impressiona" covers both looks and runs implicitly).
- Option 2 (Statement-based with intrigue): "Crimson Desert no PS5 Pro: O RPG gigante em ação!" (A bit less specific on performance).
- Option 3 (Focus on the how): "Crimson Desert no PS5 Pro: Veja como o RPG massivo se saiu!" (A bit long, "se saiu" is okay but not super punchy).
Final selection strategy:
- Choose the one that best combines "Crimson Desert," "PS5 Pro," "massive RPG," and the core idea of "looks and runs" in a curious and short way.
- The question format is excellent for Discover.
"Impressiona" in Portuguese is a strong verb that covers both visual appeal and performance satisfaction.
- Consider: "Crimson Desert no PS5 Pro: como o RPG massivo roda?" (Good, direct, but maybe a bit too much "como roda" which is expected.)
- Consider: "Crimson Desert no PS5 Pro: gráficos e desempenho no RPG gigante!" (A bit descriptive, less curious).
Consider: "Crimson Desert no PS5 Pro: O RPG massivo te surpreende?" (Very good, direct to the reader).
- Let’s try one more: "Crimson Desert no PS5 Pro: O RPG massivo te convence?" (A bit strong on "convence" – implies skepticism).
"Crimson Desert no PS5 Pro: Vale a pena?" (Too generic).
Let’s go back to focusing on the impact of the game running on the Pro. "Impressiona" is good because it covers both visual fidelity and smooth performance, and it directly asks if the combination delivers.
- "Crimson Desert no PS5 Pro: o RPG massivo impressiona?"
- Short: Yes.
- Curious: Yes, it asks a direct question that makes the reader wonder about the answer.
- Covers key elements: Game title, platform, genre, and the core "how it performs" implied by "impressiona."
- Final check: Is it only the title text? Yes.
The chosen title is concise, uses the game and console names, highlights its genre ("RPG massivo") as a key challenge/feature, and asks a direct question ("impressiona?") to pique curiosity about its performance and visuals.
"Crimson Desert no PS5 Pro: O RPG massivo impressiona?"Crimson Desert no PS5 Pro: O RPG massivo impressiona?
EverOS: A Memória para Agentes de IA que Aprende e Evolui Sozinha!
IA: Desvendando o baço em 3D.