Download src/generate.py from Frankenstein-Labs/Cortex-0: direct link, hf CLI and curl.
- Browser
- Download file 2.96 kB
-
https://huggingface.co/Frankenstein-Labs/Cortex-0/resolve/main/src/generate.py
- Command line
-
hf download hf://Frankenstein-Labs/Cortex-0/src/generate.py
-
curl -L -o generate.py https://huggingface.co/Frankenstein-Labs/Cortex-0/resolve/main/src/generate.py
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) | |
| 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) | |