Download src/dynamic_blueprint_evolver.py from YSamchuk/Hetman-2b: direct link, hf CLI and curl.
- Browser
- Download file 32.4 kB
-
https://huggingface.co/YSamchuk/Hetman-2b/resolve/main/src/dynamic_blueprint_evolver.py
- Command line
-
hf download hf://YSamchuk/Hetman-2b/src/dynamic_blueprint_evolver.py
-
curl -L -o dynamic_blueprint_evolver.py https://huggingface.co/YSamchuk/Hetman-2b/resolve/main/src/dynamic_blueprint_evolver.py
32.4 kB
| """ | |
| Hetman-2-NCM 2.0B: Dynamic Blueprint Evolver & Residual Intent Tracking Engine | |
| Module: src/dynamic_blueprint_evolver.py | |
| Specification: spec/15_DYNAMIC_BLUEPRINT_EVOLUTION_MECHANICS.md | |
| RFC: RFC-HETMAN-2-NCM-DYNAMIC-BLUEPRINT-EVOLUTION | |
| Architectural Role: | |
| Phase 3 Multi-Scale Block Decoder / Latent State Dynamics | |
| Implements Dynamic Blueprint Evolution via Residual Intent Tracking (RIT): | |
| B_k^{(t+1)} = Normalize_Spherically( (1 - alpha_done) * (B_k^{(t)} - gamma_t * delta_h) + alpha_done * B_completed ) | |
| with Gram-Schmidt Subspace Protection against Over-Pruning, | |
| Cosine Annealing Schedule for gamma_t in [0.10, 0.80], | |
| and Zero-Vector Collapse Safeguards with transition to attractor [COMPLETED]. | |
| Target Hardware: Consumer GPUs (RTX 2060 6GB, RTX 3060 12GB), Apple Silicon, AVX-512 / AVX2 CPU. | |
| """ | |
| import math | |
| from typing import Dict, List, Optional, Tuple, Union | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| # ============================================================================== | |
| # 1. Сферична нормалізація зі спектральним демпфуванням Якобіана (Section 4.3) | |
| # ============================================================================== | |
| class SphericalNormFunction(torch.autograd.Function): | |
| """ | |
| Автоград-оператор проекції вектора на одиничну ріманову гіперсферу S^(d-1) | |
| зі спектральним демпфуванням Якобіана (Spectral Jacobian Clamping). | |
| Формулювання: | |
| Прямий прохід: | |
| y = v / max(||v||_2, eps) | |
| Зворотний прохід (Theorem 4.3): | |
| J_norm(v) = (1 / ||v||_2) * (I - y y^T) | |
| Для запобігання вибуху градієнтів (Exploding Gradients) множник | |
| 1 / ||v||_2 обмежується зверху константою clamp_max (за замовчуванням 1.0): | |
| J_tilde = min(clamp_max, 1.0 / ||v||_2) * (I - y y^T) | |
| Це гарантує нерозтягуваність оператора: ||J_tilde||_2 <= 1.0. | |
| """ | |
| def forward(ctx, v: torch.Tensor, eps: float = 1e-8, clamp_max: float = 1.0) -> torch.Tensor: | |
| norm = torch.norm(v, p=2, dim=-1, keepdim=True) | |
| norm_safe = torch.clamp(norm, min=eps) | |
| y = v / norm_safe | |
| ctx.save_for_backward(y, norm_safe) | |
| ctx.clamp_max = clamp_max | |
| return y | |
| def backward(ctx, grad_output: torch.Tensor) -> Tuple[torch.Tensor, None, None]: | |
| y, norm_safe = ctx.saved_tensors | |
| clamp_max = ctx.clamp_max | |
| # Спектральне демпфування множника | |
| scale = torch.clamp(1.0 / norm_safe, max=clamp_max) | |
| # Проектор (I - y y^T) dL/dy = dL/dy - y <y, dL/dy> | |
| inner_prod = torch.sum(grad_output * y, dim=-1, keepdim=True) | |
| grad_v = scale * (grad_output - inner_prod * y) | |
| return grad_v, None, None | |
| class SphericalNorm(nn.Module): | |
| """ | |
| Модуль сферичної нормалізації на гіперсферу S^(d-1). | |
| Забезпечує строгу унітарну норму ||x||_2 = 1.0 та стійкий градієнтний потік. | |
| """ | |
| def __init__(self, eps: float = 1e-8, clamp_max: float = 1.0, use_spectral_clamping: bool = True): | |
| super().__init__() | |
| self.eps = eps | |
| self.clamp_max = clamp_max | |
| self.use_spectral_clamping = use_spectral_clamping | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| if torch.isnan(x).any() or torch.isinf(x).any(): | |
| x = torch.nan_to_num(x, nan=0.0, posinf=1.0, neginf=-1.0) | |
| norm = torch.norm(x, p=2, dim=-1, keepdim=True) | |
| zero_mask = (norm < self.eps) | |
| if zero_mask.any(): | |
| fallback = torch.zeros_like(x) | |
| fallback[..., 0] = 1.0 | |
| x = torch.where(zero_mask, fallback, x) | |
| norm = torch.norm(x, p=2, dim=-1, keepdim=True) | |
| if self.use_spectral_clamping and x.requires_grad: | |
| return SphericalNormFunction.apply(x, self.eps, self.clamp_max) | |
| norm_safe = torch.clamp(norm, min=self.eps) | |
| return x / norm_safe | |
| # ============================================================================== | |
| # 2. Фільтр ортогоналізації Грама-Шмідта (Subspace Protection, Section 4.2) | |
| # ============================================================================== | |
| class GramSchmidtSubspaceFilter(nn.Module): | |
| """ | |
| Фільтр ортогоналізації Грама-Шмідта для захисту майбутніх тез від Over-Pruning. | |
| Математичний сенс (Теорема 1 зі специфікації 15): | |
| Коли екстрактор реалізованого змісту детектує згенеровані токени, | |
| він може мати паразитну проекцію на невирішені майбутні тези u_j in M_future. | |
| Фільтр проектує вектор віднімання виключно на підпростір активних тез M_active, | |
| зберігаючи скалярний добуток <B_unnorm, u_j> == <B_curr, u_j> для всіх j in M_future. | |
| Підтримує два режими: | |
| 1. Слотова проекція (якщо передано матрицю слотів U або B_slots [B, M, d_model]). | |
| 2. Траєкторна колінеарно-ортогональна фільтрація відносно початкового креслення B_initial. | |
| """ | |
| def __init__( | |
| self, | |
| d_model: int = 1536, | |
| num_slots: int = 16, | |
| active_threshold: float = 0.15, | |
| orthogonal_leak: float = 0.25 | |
| ): | |
| super().__init__() | |
| self.d_model = d_model | |
| self.num_slots = num_slots | |
| self.active_threshold = active_threshold | |
| self.orthogonal_leak = orthogonal_leak | |
| self.slot_dim = max(1, d_model // num_slots) | |
| def project_slots( | |
| self, | |
| delta_h: torch.Tensor, | |
| slots: torch.Tensor, | |
| B_current: torch.Tensor, | |
| active_threshold: Optional[float] = None | |
| ) -> torch.Tensor: | |
| """ | |
| Стійка слотова ортогоналізація Мура-Пенроуза (Theorem 1, RFC-HETMAN-2-NCM): | |
| P_active = U_act (U_act^T U_act)^+ U_act^T | |
| delta_h_safe = P_active * delta_h | |
| Захищена від поганої обумовленості матриці Грама (kappa(G) -> inf) | |
| при майже колінеарних або лінійно залежних векторах слотів. | |
| slots: [Batch, M, d_model] - вектори слотів креслення | |
| B_current: [Batch, d_model] - поточний вектор наміру | |
| delta_h: [Batch, d_model] - сирий вектор віднімання | |
| """ | |
| thresh = active_threshold if active_threshold is not None else self.active_threshold | |
| # Обчислюємо проекцію поточного наміру на кожен слот: c_m = <B_curr, u_m> | |
| c_m = torch.sum(slots * B_current.unsqueeze(1), dim=-1) # [B, M] | |
| # Маска активних слотів: c_m >= thresh | |
| active_mask = (c_m >= thresh).to(delta_h.dtype) # [B, M] | |
| U_act = slots * active_mask.unsqueeze(-1) # [B, M, d] | |
| # Матриця Грама активних слотів G = U_act @ U_act^T [B, M, M] | |
| G = torch.bmm(U_act, U_act.transpose(1, 2)) | |
| # Псевдообернення Мура-Пенроуза гарантує стійкість до лінійної залежності: | |
| # P = U_act^T @ (U_act @ U_act^T)^+ @ U_act | |
| G_pinv = torch.linalg.pinv(G) # [B, M, M] | |
| proj_coeff = torch.bmm(U_act, delta_h.unsqueeze(-1)) # [B, M, 1] | |
| alpha = torch.bmm(G_pinv, proj_coeff) # [B, M, 1] | |
| # Реконструкція безпечного вектору без ампліфікації норми | |
| delta_h_safe = torch.bmm(U_act.transpose(1, 2), alpha).squeeze(-1) # [B, d_model] | |
| # Для ортогонального залишку додаємо контрольовану частину orthogonal_leak | |
| uncovered_delta = delta_h - delta_h_safe | |
| filtered_delta = delta_h_safe + self.orthogonal_leak * uncovered_delta | |
| return filtered_delta | |
| def forward( | |
| self, | |
| delta_h: torch.Tensor, | |
| B_initial: torch.Tensor, | |
| B_current: torch.Tensor, | |
| slots: Optional[torch.Tensor] = None, | |
| active_threshold: Optional[float] = None | |
| ) -> torch.Tensor: | |
| """ | |
| delta_h: [Batch, d_model] - сирий вектор змісту | |
| B_initial: [Batch, d_model] або [Batch, M, d_model] - початковий начерк | |
| B_current: [Batch, d_model] - поточний намір | |
| slots: опціональний тензор слотів [Batch, M, d_model] | |
| """ | |
| if slots is not None: | |
| return self.project_slots(delta_h, slots, B_current, active_threshold) | |
| # Якщо B_initial передано як багатослотовий тензор [B, M, d] | |
| if B_initial.dim() == 3: | |
| return self.project_slots(delta_h, B_initial, B_current, active_threshold) | |
| # Режим декомпозиції підпростору B_initial / B_current | |
| # Обчислюємо збереженість початкового наміру в поточному | |
| proj = torch.sum(B_current * B_initial, dim=-1, keepdim=True) # [Batch, 1] | |
| # Колінеарна складова поточної траєкторії | |
| delta_parallel = proj * B_current | |
| # Ортогональна паразитна складова | |
| delta_orthogonal = delta_h - delta_parallel | |
| # Придушуємо ортогональний шум, захищаючи майбутні незвідані підпростори | |
| filtered_delta = delta_parallel + self.orthogonal_leak * delta_orthogonal | |
| return filtered_delta | |
| # ============================================================================== | |
| # 3. Екстрактор реалізованого семантичного змісту (Section 2.1) | |
| # ============================================================================== | |
| class SemanticContentExtractor(nn.Module): | |
| """ | |
| Екстрактор реалізованого змісту з блоку 400 латентних токенів. | |
| Використовує Attention-Pooling відносно поточного вектора наміру B_k^(t) | |
| та проектор SwiGLU з подальшим обмеженням довжини. | |
| """ | |
| def __init__( | |
| self, | |
| d_model: int = 1536, | |
| d_attn: Optional[int] = None, | |
| d_mid: Optional[int] = None, | |
| eps: float = 1e-6 | |
| ): | |
| super().__init__() | |
| self.d_model = d_model | |
| self.d_attn = d_attn or max(64, d_model // 6) | |
| self.d_mid = d_mid or max(128, int(d_model * 4 / 3)) | |
| self.layer_norm = nn.LayerNorm(d_model, eps=eps) | |
| # Проекції для механізму уваги (Cross-Attention Pooling) | |
| self.w_q = nn.Linear(d_model, self.d_attn, bias=False) | |
| self.w_k = nn.Linear(d_model, self.d_attn, bias=False) | |
| self.scale = 1.0 / math.sqrt(self.d_attn) | |
| # SwiGLU MLP проектор змісту у простір наміру | |
| self.w_gate = nn.Linear(d_model, self.d_mid, bias=False) | |
| self.w_up = nn.Linear(d_model, self.d_mid, bias=False) | |
| self.w_down = nn.Linear(self.d_mid, d_model, bias=False) | |
| def forward(self, H_gen: torch.Tensor, B_intent: torch.Tensor) -> torch.Tensor: | |
| """ | |
| H_gen: [Batch, L_block, d_model] - згенеровані латентні стани (L_block = 400) | |
| B_intent: [Batch, d_model] - поточний вектор наміру B_k^(t) | |
| Повертає: | |
| delta_h_clamped: [Batch, d_model] - вектор реалізованого змісту, нормований за довжиною <= 1.0 | |
| """ | |
| H_norm = self.layer_norm(H_gen) # [B, L, d_model] | |
| # Оптимізація: Асоціативна факторизація уваги (-92.5% FLOPs) | |
| # Замість проекції L токенів K = H_norm @ W_k^T [B, L, d_attn] (314.57 MFLOPs), | |
| # обчислюємо еквівалентний вектор запиту в просторі моделі: | |
| # v_eff = (Q @ W_k) * scale [B, 1, d_model] (2.80 MFLOPs) | |
| Q = self.w_q(B_intent).unsqueeze(1) # [B, 1, d_attn] | |
| v_eff = torch.matmul(Q, self.w_k.weight) * self.scale # [B, 1, d_model] | |
| # Скалярні добутки уваги релевантності токенів до поточного наміру | |
| attn_scores = torch.bmm(v_eff, H_norm.transpose(1, 2)) # [B, 1, L] | |
| attn_weights = F.softmax(attn_scores, dim=-1) # [B, 1, L] | |
| # Зважена агрегація відбитку згенерованого тексту | |
| H_pooled = torch.bmm(attn_weights, H_norm).squeeze(1) # [B, d_model] | |
| # Нелінійна SwiGLU проекція | |
| gate = F.silu(self.w_gate(H_pooled)) | |
| up = self.w_up(H_pooled) | |
| delta_h = self.w_down(gate * up) # [B, d_model] | |
| # Нормування довжини видобутого змісту: delta_h / max(||delta_h||_2, 1.0) | |
| delta_norm = torch.norm(delta_h, p=2, dim=-1, keepdim=True) | |
| delta_h_clamped = delta_h / torch.clamp(delta_norm, min=1.0) | |
| return delta_h_clamped | |
| # ============================================================================== | |
| # 4. Трьохшкальна субпросторова факторизація (Section 3.1 - 3.2) | |
| # ============================================================================== | |
| class MultiScaleSubspaceFactorization(nn.Module): | |
| """ | |
| Субпросторова факторизація вектора наміру B_k у відповідності до 3 шкал NCM: | |
| B_k = [B_syn (25%), B_morph (25%), B_lex (50%)]^T = 1536. | |
| Масштабно-зважений фільтр Omega(t): | |
| t in [1, 2] (Scale 1: Синтаксис): [0.80, 0.15, 0.05] | |
| t in [3, 4] (Scale 2: Морфологія): [0.10, 0.70, 0.20] | |
| t in [5, 8] (Scale 3: Словник): [0.05, 0.05, 0.90] | |
| """ | |
| def __init__( | |
| self, | |
| d_model: int = 1536, | |
| d_syn: Optional[int] = None, | |
| d_morph: Optional[int] = None, | |
| d_lex: Optional[int] = None | |
| ): | |
| super().__init__() | |
| if d_syn is None or d_morph is None or d_lex is None: | |
| # За замовчуванням 25% синтаксис (384/1536), 25% морфологія (384/1536), 50% словник (768/1536) | |
| d_syn = d_model // 4 | |
| d_morph = d_model // 4 | |
| d_lex = d_model - d_syn - d_morph | |
| assert d_syn + d_morph + d_lex == d_model, f"Subspaces sum {d_syn+d_morph+d_lex} != {d_model}" | |
| self.d_model = d_model | |
| self.d_syn = d_syn | |
| self.d_morph = d_morph | |
| self.d_lex = d_lex | |
| def get_scale_weights(self, step_idx: int) -> Tuple[float, float, float]: | |
| """ | |
| Повертає вагові коефіцієнти (w_syn, w_morph, w_lex) для кроку step_idx (1..8). | |
| """ | |
| if step_idx in (1, 2): | |
| return (0.80, 0.15, 0.05) | |
| elif step_idx in (3, 4): | |
| return (0.10, 0.70, 0.20) | |
| else: # 5..8 | |
| return (0.05, 0.05, 0.90) | |
| def apply_filter(self, delta_h: torch.Tensor, step_idx: int) -> torch.Tensor: | |
| """ | |
| Масштабує відповідні субпростори вектора delta_h згідно з поточною шкалою NCM. | |
| delta_h: [Batch, d_model] | |
| """ | |
| w_syn, w_morph, w_lex = self.get_scale_weights(step_idx) | |
| syn_part = delta_h[..., :self.d_syn] * w_syn | |
| morph_part = delta_h[..., self.d_syn:self.d_syn + self.d_morph] * w_morph | |
| lex_part = delta_h[..., self.d_syn + self.d_morph:] * w_lex | |
| return torch.cat([syn_part, morph_part, lex_part], dim=-1) | |
| # ============================================================================== | |
| # 5. AdaLN-Zero Модуляція шарів декодера (Section 3.3) | |
| # ============================================================================== | |
| class AdaLNZeroModulator(nn.Module): | |
| """ | |
| Генератор параметрів адаптивної нормалізації шарів (AdaLN-Zero) з нульовою ініціалізацією: | |
| [gamma_1, beta_1, alpha_1, gamma_2, beta_2, alpha_2] = W_adaln * B_k^(t) | |
| де B_k^(t) динамічно перебудовує селективність нейронів кожного шару. | |
| """ | |
| def __init__(self, d_model: int = 1536, num_layers: int = 8): | |
| super().__init__() | |
| self.d_model = d_model | |
| self.num_layers = num_layers | |
| # Проекції 6 параметрів на кожен шар: 6 * d_model параметрів | |
| self.layer_projections = nn.ModuleList([ | |
| nn.Linear(d_model, 6 * d_model, bias=True) | |
| for _ in range(num_layers) | |
| ]) | |
| # Нульова ініціалізація (AdaLN-Zero) для забезпечення тотожного відображення на старті | |
| for proj in self.layer_projections: | |
| nn.init.zeros_(proj.weight) | |
| nn.init.zeros_(proj.bias) | |
| def forward( | |
| self, | |
| B_intent: torch.Tensor, | |
| layer_idx: Optional[int] = None | |
| ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]: | |
| """ | |
| B_intent: [Batch, d_model] | |
| layer_idx: 0-indexed шар декодера [0..num_layers-1]. Якщо None, використовується шар 0. | |
| Повертає: | |
| (gamma_1, beta_1, alpha_1, gamma_2, beta_2, alpha_2), кожен форми [Batch, d_model] | |
| """ | |
| target_idx = 0 if layer_idx is None else layer_idx | |
| params = self.layer_projections[target_idx](B_intent) # [B, 6 * d_model] | |
| chunks = torch.chunk(params, 6, dim=-1) | |
| return chunks[0], chunks[1], chunks[2], chunks[3], chunks[4], chunks[5] | |
| # ============================================================================== | |
| # 6. Головний модуль: DynamicBlueprintEvolver (Section 2, 4, 5) | |
| # ============================================================================== | |
| class DynamicBlueprintEvolver(nn.Module): | |
| """ | |
| Виробничий модуль Dynamic Blueprint Evolver на базі Residual Intent Tracking (RIT). | |
| Основні функції: | |
| 1. Віднімання виконаного наміру: | |
| B_cand = SpherNorm(B_k^(t) - gamma_t * delta_h) | |
| 2. Захист майбутніх тез від over-pruning через ортогоналізацію Грама-Шмідта. | |
| 3. Косинусний розклад gamma_t в діапазоні [0.10, 0.80] (з підтримкою exp/linear). | |
| 4. Захист від Zero-Vector Collapse через автоматичний перехід до атрактора [COMPLETED] | |
| при падінні залишкової норми нижче collapse_threshold (0.25). | |
| 5. Багатошкальна фільтрація підпросторів за фазами NCM (Syntax -> Morph -> Lexicon). | |
| """ | |
| def __init__( | |
| self, | |
| d_model: int = 1536, | |
| total_steps: int = 8, | |
| gamma_min: float = 0.10, | |
| gamma_max: float = 0.80, | |
| collapse_threshold: float = 0.25, | |
| schedule_type: str = "cosine", | |
| use_subspace_factorization: bool = True, | |
| use_riemannian_step: bool = False, | |
| final_attractor_force: float = 0.85 | |
| ): | |
| super().__init__() | |
| self.d_model = d_model | |
| self.total_steps = total_steps | |
| self.gamma_min = gamma_min | |
| self.gamma_max = gamma_max | |
| self.collapse_threshold = collapse_threshold | |
| self.schedule_type = schedule_type.lower() | |
| self.use_subspace_factorization = use_subspace_factorization | |
| self.use_riemannian_step = use_riemannian_step | |
| self.final_attractor_force = final_attractor_force | |
| # Модулі | |
| self.spher_norm = SphericalNorm(eps=1e-8, clamp_max=1.0) | |
| self.extractor = SemanticContentExtractor(d_model=d_model) | |
| self.filter = GramSchmidtSubspaceFilter(d_model=d_model) | |
| if self.use_subspace_factorization: | |
| self.scale_factorizer = MultiScaleSubspaceFactorization(d_model=d_model) | |
| else: | |
| self.scale_factorizer = None | |
| # Навчальний вектор атрактора завершеності [COMPLETED] на одиничній сфері S^(d-1) | |
| # Ініціалізується нормованим випадковим вектором | |
| init_attractor = F.normalize(torch.randn(1, d_model), p=2, dim=-1) | |
| self.completed_attractor = nn.Parameter(init_attractor) | |
| # Класифікатор готовності блоку (Termination Gate) | |
| self.done_gate = nn.Linear(d_model, 1) | |
| def get_gamma(self, step_idx: int) -> float: | |
| """ | |
| Обчислює коефіцієнт віднімання gamma_t для кроку step_idx (1-indexed). | |
| Підтримує безпечний режим роботи при t > total_steps (наприклад, 11 проходів | |
| в екстреному режимі відновлення): насичує прогрес на 1.0 та гарантує gamma <= gamma_max < 1.0. | |
| """ | |
| step = max(1, step_idx) | |
| if self.total_steps <= 1: | |
| return float(self.gamma_min) | |
| # Нормалізований насичуваний прогрес: clamp до [0.0, 1.0] | |
| progress = min(1.0, (step - 1) / float(self.total_steps - 1)) | |
| if self.schedule_type == "cosine": | |
| # Косинусний розклад від gamma_min до gamma_max (Equation 258) | |
| gamma = self.gamma_min + 0.5 * (self.gamma_max - self.gamma_min) * (1.0 - math.cos(math.pi * progress)) | |
| elif self.schedule_type == "exponential": | |
| # Експоненційний розклад (Equation 286) | |
| gamma = self.gamma_min * ((self.gamma_max / self.gamma_min) ** progress) | |
| elif self.schedule_type == "linear": | |
| gamma = self.gamma_min + (self.gamma_max - self.gamma_min) * progress | |
| else: | |
| raise ValueError(f"Unknown schedule_type '{self.schedule_type}'. Use 'cosine', 'exponential', or 'linear'.") | |
| # Гарантія суворого інваріанту: gamma_min <= gamma <= gamma_max | |
| return float(min(self.gamma_max, max(self.gamma_min, gamma))) | |
| def riemannian_geodesic_subtraction( | |
| self, | |
| B_curr: torch.Tensor, | |
| delta_h: torch.Tensor, | |
| gamma: float | |
| ) -> torch.Tensor: | |
| """ | |
| Строге ріманове віднімання на дотичному просторі T_{B} S^(d-1) (Section 2.2): | |
| 1. v_tangent = delta_h - <delta_h, B> B | |
| 2. theta = gamma * max(0, <delta_h, B>) | |
| 3. Exp_B(-theta v) = cos(theta) B - sin(theta) (v / ||v||) | |
| """ | |
| dot = torch.sum(delta_h * B_curr, dim=-1, keepdim=True) | |
| v_tangent = delta_h - dot * B_curr | |
| v_norm = torch.norm(v_tangent, p=2, dim=-1, keepdim=True) | |
| v_safe = torch.clamp(v_norm, min=1e-8) | |
| v_dir = v_tangent / v_safe | |
| kappa = torch.clamp(dot, min=0.0) | |
| theta = gamma * kappa | |
| # Зсув вздовж геодезичної | |
| B_geo = torch.cos(theta) * B_curr - torch.sin(theta) * v_dir | |
| return B_geo | |
| def step( | |
| self, | |
| B_curr: torch.Tensor, | |
| B_init: torch.Tensor, | |
| H_gen: torch.Tensor, | |
| step_idx: int, | |
| slots: Optional[torch.Tensor] = None | |
| ) -> Tuple[torch.Tensor, Dict[str, float]]: | |
| """ | |
| Один крок еволюції вектора наміру: B_k^{(t)} -> B_k^{(t+1)}. | |
| Аргументи: | |
| B_curr: [Batch, d_model] - поточний вектор наміру на кроці t | |
| B_init: [Batch, d_model] або [Batch, M, d_model] - початковий начерк B_k^(0) | |
| H_gen: [Batch, L_block, d_model] - згенеровані латентні токени | |
| step_idx: int - номер кроку (1-indexed: 1..total_steps) | |
| slots: опціональний тензор ортогональних слотів [Batch, M, d_model] | |
| Повертає: | |
| B_next: [Batch, d_model] - оновлений знормований вектор наміру на S^(d-1) | |
| metrics: dict - діагностичні показники (норми, залишок, альфа, тощо) | |
| """ | |
| batch_size = B_curr.size(0) | |
| gamma = self.get_gamma(step_idx) | |
| # 1. Екстракція реалізованого семантичного змісту з H_gen через Cross-Attention Pooling | |
| delta_h_raw = self.extractor(H_gen, B_curr) # [Batch, d_model] | |
| # 2. Фільтрація Грама-Шмідта для захисту нерозкритих тез (Anti-Over-Pruning) | |
| delta_h_safe = self.filter( | |
| delta_h=delta_h_raw, | |
| B_initial=B_init, | |
| B_current=B_curr, | |
| slots=slots | |
| ) # [Batch, d_model] | |
| # 3. Багатошкальна субпросторова фільтрація (Scale 1 -> Scale 2 -> Scale 3) | |
| if self.scale_factorizer is not None: | |
| delta_h_directed = self.scale_factorizer.apply_filter(delta_h_safe, step_idx) | |
| else: | |
| delta_h_directed = delta_h_safe | |
| # 4. Векторне віднімання наміру | |
| if self.use_riemannian_step: | |
| B_unnorm = self.riemannian_geodesic_subtraction(B_curr, delta_h_directed, gamma) | |
| else: | |
| B_unnorm = B_curr - gamma * delta_h_directed | |
| # Обчислюємо евклідову норму залишку | |
| residual_norm = torch.norm(B_unnorm, p=2, dim=-1, keepdim=True) # [Batch, 1] | |
| # 5. Захист від Zero-Vector Collapse та детекція завершеності | |
| done_logits = self.done_gate(B_unnorm) # [Batch, 1] | |
| done_prob = torch.sigmoid(done_logits) # [Batch, 1] | |
| # Індикатор просідання залишкової норми нижче безпечного порогу (0.25) | |
| # Коли residual_norm -> 0, collapse_weight -> 1.0 | |
| norm_collapse_weight = torch.clamp( | |
| (self.collapse_threshold - residual_norm) / self.collapse_threshold, | |
| min=0.0, | |
| max=1.0 | |
| ) | |
| # Підсумковий коефіцієнт захоплення атрактором | |
| alpha_done = torch.maximum(done_prob, norm_collapse_weight) | |
| # Якщо крок фінальний (або аварійний t >= total_steps), примусово спрямовуємо до атрактора | |
| if step_idx >= self.total_steps: | |
| target_force = torch.tensor(self.final_attractor_force, device=B_curr.device, dtype=B_curr.dtype) | |
| alpha_done = torch.maximum(alpha_done, target_force) | |
| # 6. Інтерполяція між залишковим наміром та атрактором [COMPLETED] | |
| attractor_normed = F.normalize(self.completed_attractor, p=2, dim=-1) | |
| attractor_expanded = attractor_normed.expand(batch_size, -1) | |
| B_blended = (1.0 - alpha_done) * B_unnorm + alpha_done * attractor_expanded | |
| # 7. Сферична нормалізація на S^(d-1) зі спектральним демпфуванням | |
| B_next = self.spher_norm(B_blended) | |
| # Діагностичні метрики | |
| B_init_vec = B_init.mean(dim=1) if B_init.dim() == 3 else B_init | |
| metrics = { | |
| "step": float(step_idx), | |
| "gamma": float(gamma), | |
| "residual_norm_mean": float(residual_norm.mean().item()), | |
| "alpha_done_mean": float(alpha_done.mean().item()), | |
| "cos_sim_to_init": float(F.cosine_similarity(B_next, B_init_vec, dim=-1).mean().item()), | |
| "cos_sim_to_completed": float(F.cosine_similarity(B_next, attractor_expanded, dim=-1).mean().item()), | |
| "final_norm": float(torch.norm(B_next, p=2, dim=-1).mean().item()) | |
| } | |
| return B_next, metrics | |
| def simulate_8_passes( | |
| self, | |
| B_initial: torch.Tensor, | |
| H_passes: List[torch.Tensor], | |
| slots: Optional[torch.Tensor] = None | |
| ) -> Tuple[List[torch.Tensor], List[Dict[str, float]]]: | |
| """ | |
| Повна симуляція 8 проходів декодера для заданого батчу. | |
| Аргументи: | |
| B_initial: [Batch, d_model] або [Batch, M, d_model] - початкове креслення | |
| H_passes: List з 8 тензорів розмірності [Batch, L_block, d_model] | |
| slots: опціональний тензор слотів [Batch, M, d_model] | |
| Повертає: | |
| trajectory: List з 9 тензорів: [B_0, B_1, ..., B_8] | |
| metrics_history: List з 8 словників метрик для кожного кроку | |
| """ | |
| assert len(H_passes) == self.total_steps, ( | |
| f"Expected {self.total_steps} passes, got {len(H_passes)}" | |
| ) | |
| trajectory: List[torch.Tensor] = [] | |
| metrics_history: List[Dict[str, float]] = [] | |
| # B_0: початковий намір, знормований на сферу | |
| if B_initial.dim() == 3: | |
| B_curr = self.spher_norm(B_initial.sum(dim=1)) | |
| else: | |
| B_curr = self.spher_norm(B_initial) | |
| trajectory.append(B_curr) | |
| for t in range(1, self.total_steps + 1): | |
| H_gen = H_passes[t - 1] | |
| B_next, step_metrics = self.step( | |
| B_curr=B_curr, | |
| B_init=B_initial, | |
| H_gen=H_gen, | |
| step_idx=t, | |
| slots=slots | |
| ) | |
| trajectory.append(B_next) | |
| metrics_history.append(step_metrics) | |
| B_curr = B_next | |
| return trajectory, metrics_history | |
| def simulate_passes( | |
| self, | |
| B_initial: torch.Tensor, | |
| H_passes: List[torch.Tensor], | |
| slots: Optional[torch.Tensor] = None | |
| ) -> Tuple[List[torch.Tensor], List[Dict[str, float]]]: | |
| """ | |
| Аліас для simulate_8_passes для сумісності з різними конвенціями імен. | |
| """ | |
| return self.simulate_8_passes(B_initial, H_passes, slots=slots) | |
| def forward( | |
| self, | |
| B_curr: torch.Tensor, | |
| B_init: torch.Tensor, | |
| H_gen: torch.Tensor, | |
| step_idx: int, | |
| slots: Optional[torch.Tensor] = None | |
| ) -> Tuple[torch.Tensor, Dict[str, float]]: | |
| """ | |
| Стандартний forward метод для PyTorch nn.Module. | |
| """ | |
| return self.step(B_curr, B_init, H_gen, step_idx, slots=slots) | |