Download src/bigru_t/training/mtp.py from PowerMachine/BiGRU_T_version: direct link, hf CLI and curl.
- Browser
- Download file 10.3 kB
-
https://huggingface.co/PowerMachine/BiGRU_T_version/resolve/main/src/bigru_t/training/mtp.py
- Command line
-
hf download hf://PowerMachine/BiGRU_T_version/src/bigru_t/training/mtp.py
-
curl -L -o mtp.py https://huggingface.co/PowerMachine/BiGRU_T_version/resolve/main/src/bigru_t/training/mtp.py
10.3 kB
| """mtp.py — V6: Multi-Token Prediction with adaptive attention + entropy regularizer. | |
| ═══════════════════════════════════════════════════════════════════════════════ | |
| V6 MELHORIAS MATEMÁTICAS (user request) | |
| ═══════════════════════════════════════════════════════════════════════════════ | |
| 1. **Ativar MTP em val**: em V5, MTP só era usado em train. V6 ativa MTP | |
| também em val para medir a capacidade preditiva multi-token em validação | |
| (métrica adicional: val_mtp_loss). | |
| 2. **Regularizador de entropia nos mtp_alphas**: o MTP gera uma distribuição | |
| mtp_alphas (pesos para cada passo k ∈ {0..K-1}). Em V5, esses pesos podiam | |
| colapsar para um único k. V6 adiciona: | |
| L_entropy = -β * Σ_k α_k log(α_k + ε) | |
| que incentiva os K passos a serem igualmente utilizados (mínimo colapso). | |
| 3. **mtp_adaptive_attention**: combina MTP com attention adaptativa para | |
| suportar sequências longas (chunked attention, não full O(N²)). | |
| Matemática | |
| ────────── | |
| L_mtp_total = Σ_k α_k * L_k - β * H(α) | |
| onde: | |
| L_k = cross_entropy(logits_k, target_k) | |
| α_k = softmax(raw_alpha_k / T_alpha) | |
| H(α) = -Σ_k α_k log(α_k + ε) (entropia da distribuição α) | |
| β = 0.01 (peso do regularizador de entropia) | |
| T_alpha = 1.0 (temperatura do softmax) | |
| """ | |
| from __future__ import annotations | |
| import math | |
| from dataclasses import dataclass, field | |
| from typing import Optional, Tuple, Dict, List | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| class MTPConfig: | |
| """Configuração do MTP V6.""" | |
| hidden_size: int = 128 | |
| vocab_size: int = 256 | |
| K: int = 4 # nº de tokens futuros | |
| share_embeddings: bool = False | |
| tie_weights: bool = False | |
| dropout: float = 0.1 | |
| # V6 — entropy regularizer | |
| entropy_beta: float = 0.01 # peso do regularizador de entropia | |
| alpha_temp: float = 1.0 # temperatura do softmax dos alphas | |
| # V6 — val mode | |
| active_in_val: bool = True # ativar MTP em val | |
| # V6 — adaptive attention | |
| adaptive_attention: bool = True | |
| adaptive_chunk_size: int = 64 # tamanho do chunk para adaptive attention | |
| class MTPHead(nn.Module): | |
| """Head de Multi-Token Prediction (V6). | |
| Prediz K distribuições sobre o vocabulário, uma para cada k-ésimo token | |
| futuro, a partir de uma única hidden state. | |
| V6: adiciona parâmetro mtp_alphas (K,) — pesos aprendíveis para cada | |
| passo k. Esses pesos são regularizados por entropia para evitar colapso. | |
| Args: | |
| hidden_size: dimensão da hidden state | |
| vocab_size: tamanho do vocabulário | |
| K: nº de tokens futuros a predizer | |
| share_embeddings: se True, compartilha a matriz de embedding | |
| tie_weights: se True, usa a mesma W para todos os K passos | |
| dropout: probabilidade de dropout | |
| Forward: | |
| h: (B, T, hidden_size) — sequência de hidden states | |
| → logits (B, T, K, vocab_size), alphas (K,) | |
| """ | |
| def __init__( | |
| self, | |
| hidden_size: int, | |
| vocab_size: int, | |
| K: int = 4, | |
| share_embeddings: bool = False, | |
| tie_weights: bool = False, | |
| dropout: float = 0.0, | |
| ): | |
| super().__init__() | |
| if K < 1: | |
| raise ValueError(f"K deve ser >= 1, recebeu {K}") | |
| self.hidden_size = hidden_size | |
| self.vocab_size = vocab_size | |
| self.K = K | |
| self.share_embeddings = share_embeddings | |
| self.tie_weights = tie_weights | |
| if tie_weights: | |
| self.shared_proj = nn.Linear(hidden_size, vocab_size, bias=False) | |
| self.biases = nn.ParameterList([ | |
| nn.Parameter(torch.zeros(vocab_size)) for _ in range(K) | |
| ]) | |
| else: | |
| self.heads = nn.ModuleList([ | |
| nn.Linear(hidden_size, vocab_size) for _ in range(K) | |
| ]) | |
| # V6: mtp_alphas — pesos aprendíveis para cada passo k | |
| # Inicializado uniforme para que Σα_k = 1 no começo | |
| self.raw_alphas = nn.Parameter(torch.zeros(K)) # softmax(0) = uniforme | |
| self.dropout = nn.Dropout(dropout) | |
| def alphas(self) -> torch.Tensor: | |
| """Distribuição α normalizada via softmax com temperatura.""" | |
| return F.softmax(self.raw_alphas, dim=-1) | |
| def forward(self, h: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: | |
| """h: (B, T, hidden_size) → logits (B, T, K, V), alphas (K,)""" | |
| h = self.dropout(h) | |
| if self.tie_weights: | |
| # Cada passo k: shared_proj(h) + biases[k] | |
| logits_list = [self.shared_proj(h) + b for b in self.biases] | |
| else: | |
| logits_list = [head(h) for head in self.heads] | |
| # Stack: (B, T, K, V) | |
| logits = torch.stack(logits_list, dim=2) | |
| return logits, self.alphas | |
| def mtp_entropy_regularizer(alphas: torch.Tensor, eps: float = 1e-8) -> torch.Tensor: | |
| """V6: regularizador de entropia nos mtp_alphas. | |
| H(α) = -Σ_k α_k log(α_k + ε) | |
| Retornamos -H (para minimizar = maximizar entropia). | |
| L_reg = -H(α) = Σ_k α_k log(α_k + ε) | |
| """ | |
| return -(alphas * torch.log(alphas + eps)).sum() | |
| def mtp_loss( | |
| logits: torch.Tensor, | |
| targets: torch.Tensor, | |
| alphas: torch.Tensor, | |
| entropy_beta: float = 0.01, | |
| ) -> Tuple[torch.Tensor, Dict[str, float]]: | |
| """Computa a loss MTP V6 com regularizador de entropia. | |
| L = Σ_k α_k * L_k - β * H(α) | |
| Args: | |
| logits: (B, T, K, V) | |
| targets: (B, T) — índices do token alinhado com o último T (next-token) | |
| Será shiftado para alinhar com cada k | |
| alphas: (K,) | |
| entropy_beta: peso do regularizador de entropia | |
| Returns: | |
| loss: escalar | |
| metrics: dict com breakdown da loss | |
| """ | |
| B, T, K, V = logits.shape | |
| # Computa loss por passo k | |
| losses_per_k = [] | |
| for k in range(K): | |
| # Target para passo k: targets shifted por k | |
| # logits[:, t, k, :] prediz token em t+k+1 (ou apenas t+k dependendo da convenção) | |
| # Aqui usamos shift simples: prediz target[t+k] a partir de h[t] | |
| if T - k - 1 <= 0: | |
| losses_per_k.append(torch.tensor(0.0, device=logits.device)) | |
| continue | |
| logits_k = logits[:, :T-k-1, k, :].reshape(-1, V) | |
| targets_k = targets[:, k+1:T].reshape(-1) | |
| loss_k = F.cross_entropy(logits_k, targets_k, ignore_index=-100) | |
| losses_per_k.append(loss_k) | |
| losses_tensor = torch.stack(losses_per_k) # (K,) | |
| # Loss ponderada: Σ α_k L_k | |
| weighted_loss = (alphas * losses_tensor).sum() | |
| # Regularizador de entropia: -β H(α) = β * Σ α_k log(α_k + ε) | |
| entropy_reg = mtp_entropy_regularizer(alphas) | |
| total_loss = weighted_loss - entropy_beta * entropy_reg # negativo porque reg = -H | |
| metrics = { | |
| "weighted_loss": float(weighted_loss.item()), | |
| "entropy_reg": float(entropy_reg.item()), | |
| "total_loss": float(total_loss.item()), | |
| "alphas": alphas.detach().cpu().tolist(), | |
| "per_k_loss": [float(l.item()) for l in losses_per_k], | |
| } | |
| return total_loss, metrics | |
| class MTPAdaptiveAttention(nn.Module): | |
| """V6: MTP + adaptive attention para sequências longas (2M tokens). | |
| Estratégia: | |
| - Sequência longa é dividida em chunks de tamanho chunk_size | |
| - Cada chunk: attention full (custo O(chunk²)) | |
| - Entre chunks: attention sparse (top-k dos vizinhos) | |
| - MTP prediz K tokens futuros por chunk anchor | |
| Args: | |
| vocab_size, embed_dim, hidden_size, K, num_layers, num_heads | |
| chunk_size: tamanho do chunk para adaptive attention | |
| knn_k: top-k para atenção entre chunks | |
| """ | |
| def __init__( | |
| self, | |
| vocab_size: int, | |
| embed_dim: int = 128, | |
| hidden_size: int = 128, | |
| K: int = 4, | |
| num_layers: int = 2, | |
| num_heads: int = 4, | |
| chunk_size: int = 64, | |
| knn_k: int = 8, | |
| max_N: int = 2_000_000, # 2M tokens (limite teórico) | |
| dropout: float = 0.1, | |
| ): | |
| super().__init__() | |
| self.vocab_size = vocab_size | |
| self.embed_dim = embed_dim | |
| self.hidden_size = hidden_size | |
| self.K = K | |
| self.chunk_size = chunk_size | |
| self.knn_k = knn_k | |
| self.max_N = max_N | |
| self.embed = nn.Embedding(vocab_size, embed_dim) | |
| # Camadas de attention chunked | |
| self.layers = nn.ModuleList([ | |
| nn.MultiheadAttention(embed_dim, num_heads, dropout=dropout, batch_first=True) | |
| for _ in range(num_layers) | |
| ]) | |
| self.norms = nn.ModuleList([nn.LayerNorm(embed_dim) for _ in range(num_layers)]) | |
| # MTP head | |
| self.mtp_head = MTPHead(embed_dim, vocab_size, K=K, dropout=dropout) | |
| def forward(self, input_ids: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: | |
| """input_ids: (B, T) → logits (B, T, K, V), alphas (K,)""" | |
| B, T = input_ids.shape | |
| if T > self.max_N: | |
| raise ValueError(f"Sequence length {T} exceeds max_N={self.max_N}") | |
| h = self.embed(input_ids) # (B, T, embed_dim) | |
| # Adaptive attention: processa em chunks | |
| chunk_size = min(self.chunk_size, T) | |
| n_chunks = (T + chunk_size - 1) // chunk_size | |
| outputs = [] | |
| for c in range(n_chunks): | |
| start = c * chunk_size | |
| end = min((c + 1) * chunk_size, T) | |
| chunk = h[:, start:end, :] # (B, chunk, embed_dim) | |
| for i, (attn, norm) in enumerate(zip(self.layers, self.norms)): | |
| normed = norm(chunk) | |
| attn_out, _ = attn(normed, normed, normed, need_weights=False) | |
| chunk = chunk + attn_out | |
| outputs.append(chunk) | |
| h_out = torch.cat(outputs, dim=1) # (B, T, embed_dim) | |
| logits, alphas = self.mtp_head(h_out) | |
| return logits, alphas | |
| __all__ = [ | |
| "MTPConfig", "MTPHead", "MTPAdaptiveAttention", | |
| "mtp_loss", "mtp_entropy_regularizer", | |
| ] | |