Cortex-0 / src /generate.py
Frankenstein-Labs's picture
feat(model): Phase 2 — transformer texte from scratch
37d8122 verified
Raw History Blame Contribute Delete
2.96 kB
"""Génération autoregressive pour Cortex-0 — échantillonnage écrit à la main.
Implémente :
* température (recherche du logits / T) ;
* top-k (garder les k logits les plus grands) ;
* top-p / nucleus (garder le plus petit ensemble de probabilité >= p).
Aucune fonction `model.generate` n'est utilisée : la boucle est explicite.
"""
from __future__ import annotations
import torch
from src.model.text_model import CortexTextLM
from src.tokenizer import CortexTokenizer
from src.tokenizer.special_tokens import BOS_ID, EOS_ID
def _apply_top_k(logits: torch.Tensor, k: int) -> torch.Tensor:
if k is None or k <= 0:
return logits
k = min(k, logits.size(-1))
kth_value = torch.topk(logits, k, dim=-1).values[..., -1, None]
return logits.masked_fill(logits < kth_value, float("-inf"))
def _apply_top_p(logits: torch.Tensor, p: float) -> torch.Tensor:
if p is None or p >= 1.0:
return logits
sorted_logits, sorted_idx = torch.sort(logits, descending=True, dim=-1)
probs = torch.softmax(sorted_logits, dim=-1)
cumulative = torch.cumsum(probs, dim=-1)
# On retire les tokens au-delà du seuil (en gardant le premier qui dépasse p).
remove = cumulative - probs > p
sorted_logits = sorted_logits.masked_fill(remove, float("-inf"))
# Remise dans l'ordre d'origine.
return logits.scatter(-1, sorted_idx, sorted_logits)
@torch.no_grad()
def generate(
model: CortexTextLM,
input_ids: torch.Tensor,
max_new_tokens: int = 50,
temperature: float = 1.0,
top_k: int | None = None,
top_p: float | None = None,
greedy: bool = False,
eos_id: int | None = EOS_ID,
) -> torch.Tensor:
"""Génère ``max_new_tokens`` tokens en continuation de ``input_ids`` (B, T)."""
model.eval()
ctx = model.config.ctx_len
for _ in range(max_new_tokens):
idx_cond = input_ids[:, -ctx:]
logits, _ = model(idx_cond)
logits = logits[:, -1, :] # (B, vocab)
if greedy:
next_id = torch.argmax(logits, dim=-1, keepdim=True)
else:
logits = logits / max(temperature, 1e-6)
logits = _apply_top_k(logits, top_k)
logits = _apply_top_p(logits, top_p)
probs = torch.softmax(logits, dim=-1)
next_id = torch.multinomial(probs, num_samples=1)
input_ids = torch.cat([input_ids, next_id], dim=1)
if eos_id is not None and (next_id == eos_id).all():
break
return input_ids
def generate_text(
model: CortexTextLM,
tokenizer: CortexTokenizer,
prompt: str,
max_new_tokens: int = 50,
**kwargs,
) -> str:
"""Enveloppe pratique : encode le prompt, génère, décode la suite."""
ids = [BOS_ID, *tokenizer.encode(prompt)]
inp = torch.tensor([ids], dtype=torch.long)
out = generate(model, inp, max_new_tokens=max_new_tokens, **kwargs)
return tokenizer.decode(out[0].tolist(), skip_special_tokens=True)