Initial upload: GUIDO-small 200M bugfix ckpt + Vathos + modeling + README
Browse files- README.md +152 -0
- config.json +44 -0
- inference_example.py +94 -0
- modeling_guido.py +760 -0
- pytorch_model.bin +3 -0
- requirements.txt +5 -0
- vathos/Analyzer.py +706 -0
- vathos/Transformers.py +264 -0
- vathos/__init__.py +1 -0
- vathos/_analytics.py +439 -0
- vathos/_basics.py +1134 -0
- vathos/_experiments.py +155 -0
- vathos/_spatials.py +2124 -0
- vathos/_triton.py +530 -0
- vathos/blocks.py +2110 -0
- vathos/complexity.py +238 -0
- vathos/convolutions.py +119 -0
- vathos/data.py +170 -0
- vathos/functions.py +586 -0
- vathos/mamba.py +27 -0
- vathos/optimizers.py +518 -0
- vathos/training.py +129 -0
README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: other
|
| 3 |
+
library_name: pytorch
|
| 4 |
+
tags:
|
| 5 |
+
- math
|
| 6 |
+
- code
|
| 7 |
+
- pretraining
|
| 8 |
+
- custom-architecture
|
| 9 |
+
- vathos
|
| 10 |
+
- pico
|
| 11 |
+
language:
|
| 12 |
+
- en
|
| 13 |
+
pipeline_tag: text-generation
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+
# GUIDO-small 200M (bugfixed)
|
| 17 |
+
|
| 18 |
+
A 195M-parameter math/code-oriented language model, pretrained from scratch on a math-heavy corpus.
|
| 19 |
+
Custom architecture (Vathos PiCOFormer): gated attention, smear-gate, identity-at-init, UDLP MLP,
|
| 20 |
+
RoPE, tied embeddings, fp32-master weights with bf16 autocast.
|
| 21 |
+
|
| 22 |
+
> ⚠️ This is a **research checkpoint**. Architecture is custom (not HF-compatible) — requires the
|
| 23 |
+
> bundled `vathos/` package + `modeling_guido.py` to load.
|
| 24 |
+
|
| 25 |
+
## Eval
|
| 26 |
+
|
| 27 |
+
| | greedy | maj@16 (T=0.6, top_p=0.95, top_k=40) | pass@16 |
|
| 28 |
+
|---|---|---|---|
|
| 29 |
+
| **GSM8K** | 43.0% | **57.5%** | 77.5% |
|
| 30 |
+
| **MATH-500** | 21.0% | **30.0%** | 53.75% |
|
| 31 |
+
|
| 32 |
+
Numbers from `eval_distill.py` (greedy, N=200/100) and `eval_sc.py` (maj@16, N=80/80).
|
| 33 |
+
|
| 34 |
+
## Architecture
|
| 35 |
+
|
| 36 |
+
- **Shape**: 24 layers × 768 d_model × 12 heads × 64 head_dim × 3072 d_ff
|
| 37 |
+
- **Vocab**: 32,768 (Mathstral SPM tokenizer)
|
| 38 |
+
- **Position**: RoPE base 1e6, max_len 8192
|
| 39 |
+
- **Norm**: RMSNorm, qk-norm
|
| 40 |
+
- **Attention**: Multihead Gated (Modded-NanoGPT PR#117 style sparse output gate, gate_input_dim=12)
|
| 41 |
+
- **MLP**: VariableUDLP with LeakyReLU² activation, identity-at-init
|
| 42 |
+
- **Logit softcap**: 30.0
|
| 43 |
+
- **Tied embedding**: yes
|
| 44 |
+
- **Total params**: 195,092,992 (~195M)
|
| 45 |
+
|
| 46 |
+
## Training
|
| 47 |
+
|
| 48 |
+
- **Tokens seen**: 4.19B (16000 iter × 4 GPUs × 32 batch × 2048 seq)
|
| 49 |
+
- **Optimizer**: Muon (Newton-Schulz5) on 2D matrices (lr=0.01) + Adam on embedding (lr=0.005) + scalars (lr=0.04)
|
| 50 |
+
- **Schedule**: WSD (warmup 300 → plateau → linear warmdown last 60% to lr×1e-3)
|
| 51 |
+
- **Precision**: fp32 master weights + bf16 autocast (cce kernel gets bf16 cast at boundary)
|
| 52 |
+
- **Hardware**: 4× A100 64GB (Leonardo CINECA), ~3h wall
|
| 53 |
+
|
| 54 |
+
### Data mixture (corpus_v2)
|
| 55 |
+
|
| 56 |
+
| weight | shard | tokens (B) |
|
| 57 |
+
|---|---|---|
|
| 58 |
+
| 0.34 | OpenMathInstruct-2 (14M docs) | 6.79 |
|
| 59 |
+
| 0.14 | TinyGSM (full) | 2.70 |
|
| 60 |
+
| 0.10 | OpenMathReasoning subset | 1.50 |
|
| 61 |
+
| 0.07 | NuminaMath-1.5 | 0.41 |
|
| 62 |
+
| 0.05 | NuminaMath-CoT | 0.48 |
|
| 63 |
+
| 0.22 | FineWeb-Edu score≥3 (sample-10BT) | 3.00 |
|
| 64 |
+
| 0.08 | Cosmopedia subset | 1.19 |
|
| 65 |
+
|
| 66 |
+
Math 70% / NL 30%. Tokenizer: `mistralai/Mathstral-7B-v0.1`. Decontamination: canonical-hash vs MATH-500 and GSM8K test (note: not fuzzy-decon).
|
| 67 |
+
|
| 68 |
+
## How to load
|
| 69 |
+
|
| 70 |
+
### Quick start
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
pip install -r requirements.txt
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
```python
|
| 77 |
+
from huggingface_hub import snapshot_download
|
| 78 |
+
local_dir = snapshot_download(repo_id="Paerle/GUIDO_test_200M", token="<your_token>")
|
| 79 |
+
|
| 80 |
+
import sys, torch
|
| 81 |
+
sys.path.insert(0, local_dir + "/vathos")
|
| 82 |
+
sys.path.insert(0, local_dir)
|
| 83 |
+
import modeling_guido as m
|
| 84 |
+
from transformers import AutoTokenizer
|
| 85 |
+
|
| 86 |
+
# Inferenza arch dal ckpt (così non serve config)
|
| 87 |
+
state = torch.load(local_dir + "/pytorch_model.bin", map_location="cpu", weights_only=False)
|
| 88 |
+
sd = state["model"]
|
| 89 |
+
hp = m.HP()
|
| 90 |
+
hp.head_dim = 64
|
| 91 |
+
hp.d_model = sd["backbone.embedder.embedding.weight"].shape[1]
|
| 92 |
+
hp.n_heads = hp.d_model // hp.head_dim
|
| 93 |
+
hp.d_ff = sd["backbone.blocks.0.channel_mixer.expand.weight"].shape[0]
|
| 94 |
+
hp.n_layers = max(int(k.split("blocks.")[1].split(".")[0]) for k in sd if "blocks." in k) + 1
|
| 95 |
+
model = m.PiCOFormerLM(hp, vocab_size=sd["backbone.embedder.embedding.weight"].shape[0])
|
| 96 |
+
model.load_state_dict(sd, strict=False)
|
| 97 |
+
model = model.cuda().bfloat16().eval()
|
| 98 |
+
tok = AutoTokenizer.from_pretrained("mistralai/Mathstral-7B-v0.1")
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
See [`inference_example.py`](./inference_example.py) for a complete generation example
|
| 102 |
+
(temperature, top-p, top-k sampling).
|
| 103 |
+
|
| 104 |
+
## Files
|
| 105 |
+
|
| 106 |
+
| file | description |
|
| 107 |
+
|---|---|
|
| 108 |
+
| `pytorch_model.bin` | model weights (fp32 master, 745MB) |
|
| 109 |
+
| `config.json` | architecture + HP + eval results |
|
| 110 |
+
| `modeling_guido.py` | model definition (`PiCOFormerLM` + Muon optimizer + reader; only PiCOFormerLM/HP needed for inference) |
|
| 111 |
+
| `inference_example.py` | minimal load + generate example |
|
| 112 |
+
| `vathos/` | bundled Vathos package (custom backbone — see github.com/MarioPaerle/Aplos) |
|
| 113 |
+
| `requirements.txt` | deps (torch, transformers, cut-cross-entropy) |
|
| 114 |
+
|
| 115 |
+
## Known limitations
|
| 116 |
+
|
| 117 |
+
- **Decontamination**: canonical-hash only. ~7% of MATH-500 has near-duplicates in OpenMathInstruct-2
|
| 118 |
+
augmentation (audited). Real MATH-500 capability is ~21% greedy, not inflated by overt memorization.
|
| 119 |
+
- **No instruction tuning**: this is a base pretrain. No SFT / RLHF.
|
| 120 |
+
- **Domain**: heavily math-skewed. NL is 30% (FineWeb-Edu + Cosmopedia) — basic fluency but not chat-style.
|
| 121 |
+
- **Tokenizer dependency**: requires `mistralai/Mathstral-7B-v0.1` tokenizer from HF.
|
| 122 |
+
|
| 123 |
+
## Hardware/precision notes
|
| 124 |
+
|
| 125 |
+
- Inference works fine in **bf16** (just call `.bfloat16()`). Weights are stored fp32 — 745MB on disk
|
| 126 |
+
but ~390MB GPU memory after `.bfloat16()`.
|
| 127 |
+
- Custom CUDA kernels: uses `cut_cross_entropy` (Apple) for fused linear+CE in training.
|
| 128 |
+
For inference you can replace with standard `F.linear + F.cross_entropy`.
|
| 129 |
+
|
| 130 |
+
## Architecture sketch
|
| 131 |
+
|
| 132 |
+
```
|
| 133 |
+
input_ids
|
| 134 |
+
│
|
| 135 |
+
embedder (tied) ───────────��──────────────┐
|
| 136 |
+
│ │
|
| 137 |
+
smear_gate (sparse on first 12 dims) ─────│ x0
|
| 138 |
+
│ │
|
| 139 |
+
[24× block] │
|
| 140 |
+
├── spatial: gated_attn + RoPE + qk-norm │
|
| 141 |
+
├── channel: VariableUDLP (LeakyReLU²) │
|
| 142 |
+
└── + x0_lambda × x0 ───────────────────┘
|
| 143 |
+
│
|
| 144 |
+
final_norm
|
| 145 |
+
│
|
| 146 |
+
tied unembed → cce / softcap 30 → logits
|
| 147 |
+
```
|
| 148 |
+
|
| 149 |
+
## License & citation
|
| 150 |
+
|
| 151 |
+
Vathos backbone (custom by Mario Paerle): https://github.com/MarioPaerle/Aplos
|
| 152 |
+
Model weights: research/private. Contact owner for usage terms.
|
config.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model_type": "guido_small",
|
| 3 |
+
"model_name": "GUIDO-small 200M (bugfixed)",
|
| 4 |
+
"architecture": "Vathos PiCOFormer (custom)",
|
| 5 |
+
"version": "v1-bugfix",
|
| 6 |
+
"n_layers": 24,
|
| 7 |
+
"d_model": 768,
|
| 8 |
+
"n_heads": 12,
|
| 9 |
+
"head_dim": 64,
|
| 10 |
+
"d_ff": 3072,
|
| 11 |
+
"vocab_size": 32768,
|
| 12 |
+
"max_seq_len": 8192,
|
| 13 |
+
"rope_base": 1000000.0,
|
| 14 |
+
"logit_softcap": 30.0,
|
| 15 |
+
"tied_embeddings": true,
|
| 16 |
+
"qk_norm": true,
|
| 17 |
+
"use_gated_attn": true,
|
| 18 |
+
"use_smear_gate": true,
|
| 19 |
+
"gate_input_dim": 12,
|
| 20 |
+
"mlp_kind": "udlp",
|
| 21 |
+
"orthogonalizer": "ns5",
|
| 22 |
+
"muon_backend_steps": 5,
|
| 23 |
+
"training_dtype": "fp32-master/bf16-autocast",
|
| 24 |
+
"checkpoint_dtype": "float32",
|
| 25 |
+
"training_steps": 16000,
|
| 26 |
+
"training_tokens": 4194304000,
|
| 27 |
+
"training_data": "corpus_v2 mixture 70/22/8 (math/fineweb/cosmopedia)",
|
| 28 |
+
"training_mixture": {
|
| 29 |
+
"openmath_full": 0.34,
|
| 30 |
+
"tinygsm": 0.14,
|
| 31 |
+
"openmathreasoning": 0.10,
|
| 32 |
+
"numina_15": 0.07,
|
| 33 |
+
"numina_cot": 0.05,
|
| 34 |
+
"fineweb_edu": 0.22,
|
| 35 |
+
"cosmopedia": 0.08
|
| 36 |
+
},
|
| 37 |
+
"tokenizer": "mistralai/Mathstral-7B-v0.1",
|
| 38 |
+
"params_total": 195092992,
|
| 39 |
+
"params_total_human": "195M",
|
| 40 |
+
"eval_gsm8k_greedy_pct": 43.0,
|
| 41 |
+
"eval_gsm8k_maj16_pct": 57.5,
|
| 42 |
+
"eval_math500_greedy_pct": 21.0,
|
| 43 |
+
"eval_math500_maj16_pct": 30.0
|
| 44 |
+
}
|
inference_example.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Load GUIDO-small 200M (bugfix) e genera testo.
|
| 2 |
+
Run: python inference_example.py
|
| 3 |
+
"""
|
| 4 |
+
import sys
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
import torch
|
| 7 |
+
from transformers import AutoTokenizer
|
| 8 |
+
|
| 9 |
+
# Aggiungi questa cartella al sys.path così Vathos viene trovato
|
| 10 |
+
HERE = Path(__file__).resolve().parent
|
| 11 |
+
sys.path.insert(0, str(HERE / "vathos")) # bundled Vathos package
|
| 12 |
+
sys.path.insert(0, str(HERE)) # modeling_guido.py
|
| 13 |
+
|
| 14 |
+
# Il modeling è in modeling_guido.py (fork di train_guido_small.py — usa la classe PiCOFormerLM).
|
| 15 |
+
# `import modeling_guido as m` registra Vathos in production mode + carica le classi.
|
| 16 |
+
import modeling_guido as m
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def load_model(ckpt_path: str, device="cuda"):
|
| 20 |
+
"""Carica il ckpt fp32, infera l'architettura dai pesi, restituisce model+tokenizer."""
|
| 21 |
+
state = torch.load(ckpt_path, map_location="cpu", weights_only=False)
|
| 22 |
+
sd = state["model"] if "model" in state else state
|
| 23 |
+
# strip eventuali prefissi
|
| 24 |
+
sd = {k[len("_orig_mod."):] if k.startswith("_orig_mod.") else k: v for k, v in sd.items()}
|
| 25 |
+
|
| 26 |
+
# Infer arch dal state_dict (così funziona anche se il config.json non c'è)
|
| 27 |
+
hp = m.HP()
|
| 28 |
+
hp.head_dim = 64
|
| 29 |
+
hp.d_model = sd["backbone.embedder.embedding.weight"].shape[1]
|
| 30 |
+
hp.n_heads = hp.d_model // hp.head_dim
|
| 31 |
+
hp.d_ff = sd["backbone.blocks.0.channel_mixer.expand.weight"].shape[0]
|
| 32 |
+
hp.n_layers = max(int(k.split("blocks.")[1].split(".")[0])
|
| 33 |
+
for k in sd if "blocks." in k) + 1
|
| 34 |
+
vocab = sd["backbone.embedder.embedding.weight"].shape[0]
|
| 35 |
+
print(f"[load] arch inferred: {hp.n_layers}L × {hp.d_model} × {hp.n_heads}h × {hp.d_ff}ff vocab={vocab}")
|
| 36 |
+
|
| 37 |
+
model = m.PiCOFormerLM(hp, vocab_size=vocab).to(device).bfloat16()
|
| 38 |
+
missing, unexpected = model.load_state_dict(sd, strict=False)
|
| 39 |
+
if missing: print(f"[load] missing keys ({len(missing)}): {missing[:3]}...")
|
| 40 |
+
if unexpected: print(f"[load] unexpected ({len(unexpected)}): {unexpected[:3]}...")
|
| 41 |
+
model.eval()
|
| 42 |
+
|
| 43 |
+
tok = AutoTokenizer.from_pretrained("mistralai/Mathstral-7B-v0.1")
|
| 44 |
+
return model, tok
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
@torch.no_grad()
|
| 48 |
+
def generate(model, tok, prompt: str, max_new=200, temperature=0.6, top_p=0.95, top_k=40, device="cuda"):
|
| 49 |
+
"""Sampling semplice (T, top-p, top-k)."""
|
| 50 |
+
import torch.nn.functional as F
|
| 51 |
+
ids = tok(prompt, return_tensors="pt").input_ids.to(device)
|
| 52 |
+
eos = tok.eos_token_id or 2
|
| 53 |
+
softcap = model.logit_softcap
|
| 54 |
+
for _ in range(max_new):
|
| 55 |
+
h = model._hidden(ids)
|
| 56 |
+
logits = F.linear(h[:, -1, :], model.classifier_weight)
|
| 57 |
+
if softcap > 0:
|
| 58 |
+
logits = softcap * torch.tanh(logits / softcap)
|
| 59 |
+
logits = logits.float() / temperature
|
| 60 |
+
if top_k and top_k < logits.size(-1):
|
| 61 |
+
kth = logits.topk(top_k, dim=-1).values[:, -1:]
|
| 62 |
+
logits = logits.masked_fill(logits < kth, float("-inf"))
|
| 63 |
+
probs = F.softmax(logits, dim=-1)
|
| 64 |
+
if top_p < 1.0:
|
| 65 |
+
sp, si = probs.sort(dim=-1, descending=True)
|
| 66 |
+
csum = sp.cumsum(dim=-1)
|
| 67 |
+
mask = csum - sp > top_p
|
| 68 |
+
sp = sp.masked_fill(mask, 0.0); sp /= sp.sum(dim=-1, keepdim=True)
|
| 69 |
+
choice = torch.multinomial(sp, 1)
|
| 70 |
+
nxt = si.gather(-1, choice)
|
| 71 |
+
else:
|
| 72 |
+
nxt = torch.multinomial(probs, 1)
|
| 73 |
+
ids = torch.cat([ids, nxt], dim=1)
|
| 74 |
+
if nxt.item() == eos:
|
| 75 |
+
break
|
| 76 |
+
return tok.decode(ids[0], skip_special_tokens=False)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
if __name__ == "__main__":
|
| 80 |
+
# Adatta il path al checkpoint
|
| 81 |
+
CKPT = HERE / "pytorch_model.bin"
|
| 82 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 83 |
+
model, tok = load_model(str(CKPT), device=device)
|
| 84 |
+
|
| 85 |
+
prompts = [
|
| 86 |
+
"Question: Sarah has 5 apples and gives 2 to Tom. How many does she have left?\nAnswer: ",
|
| 87 |
+
"Solve: $x^2 + 3x + 2 = 0$. Step 1:",
|
| 88 |
+
"The largest planet in the solar system is",
|
| 89 |
+
]
|
| 90 |
+
for p in prompts:
|
| 91 |
+
print("=" * 60)
|
| 92 |
+
print("PROMPT:", p)
|
| 93 |
+
out = generate(model, tok, p, max_new=120, device=device)
|
| 94 |
+
print("OUT: ", out)
|
modeling_guido.py
ADDED
|
@@ -0,0 +1,760 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Guido-small — pretrain ~200M (24L×768), math-first chattabile.
|
| 2 |
+
|
| 3 |
+
Fork di `train_distill_start.py` SENZA KD (il teacher non aiuta a questa scala —
|
| 4 |
+
vedi SESSION_HANDOFF.md §A). Differenze chiave:
|
| 5 |
+
- shape 200M (24L × 768 × 12h × 64hd × 3072ff)
|
| 6 |
+
- MultiShardMixture reader: legge corpus_v2 (7 shard per-dataset, NON shufflati a
|
| 7 |
+
write-time) con chunk-shuffle a read-time (NO rimpiazzo, copertura 100%, ordine
|
| 8 |
+
casuale). Pesi mixture 70% math / 22% fineweb / 8% cosmopedia in HP.mixture.
|
| 9 |
+
- loss-trace per-step → CSV (loss, gnorm, lr, math_frac) con UN sync ogni
|
| 10 |
+
`train_log_every` step (buffer su GPU). math_frac correla spike↔batch math-heavy.
|
| 11 |
+
- looping opzionale (env LOOP_STYLE / LOOP_ACTIVATION_FRAC), default OFF per la baseline.
|
| 12 |
+
|
| 13 |
+
Run:
|
| 14 |
+
torchrun --standalone --nproc_per_node=4 train_guido_small.py
|
| 15 |
+
|
| 16 |
+
Pre-req: corpus_v2 tokenizzato (Mathstral SPM 32k, uint32) in $FAST/corpus_v2/<name>/.
|
| 17 |
+
"""
|
| 18 |
+
from __future__ import annotations
|
| 19 |
+
|
| 20 |
+
import csv
|
| 21 |
+
import os
|
| 22 |
+
import re
|
| 23 |
+
import sys
|
| 24 |
+
import time
|
| 25 |
+
from pathlib import Path
|
| 26 |
+
|
| 27 |
+
import numpy as np
|
| 28 |
+
import torch
|
| 29 |
+
import torch.distributed as dist
|
| 30 |
+
import torch.nn as nn
|
| 31 |
+
import torch.nn.functional as F
|
| 32 |
+
from torch import Tensor
|
| 33 |
+
from torch.nn.parallel import DistributedDataParallel as DDP
|
| 34 |
+
|
| 35 |
+
# ---- Vathos backbone ----
|
| 36 |
+
APLOS_PATH = os.environ.get("APLOS_PATH", "/leonardo_work/IscrC_YENDRI/paerle/PiCO/aplos")
|
| 37 |
+
if APLOS_PATH not in sys.path:
|
| 38 |
+
sys.path.insert(0, APLOS_PATH)
|
| 39 |
+
from Vathos._basics import (Builder, RMSNorm as VRMSNorm, ReLU2, LeakyReLU2,
|
| 40 |
+
VariableUDLP, VariableGatedUDLP, set_vathos_mode)
|
| 41 |
+
from Vathos._spatials import MultiheadAttentionMixer, MultiheadGatedAttentionMixer, RoPE
|
| 42 |
+
from Vathos.blocks import PiCOFormer as VathosPiCOFormer, SmearGate
|
| 43 |
+
set_vathos_mode("production")
|
| 44 |
+
|
| 45 |
+
# FAST RMSNorm fused
|
| 46 |
+
def _fast_rmsnorm_forward(self, x):
|
| 47 |
+
return F.rms_norm(x, (x.size(-1),), self.weight, self.eps)
|
| 48 |
+
VRMSNorm.forward = _fast_rmsnorm_forward
|
| 49 |
+
|
| 50 |
+
# ---- Shard reader ----
|
| 51 |
+
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "PiCO2"))
|
| 52 |
+
from pico2.shards import ShardReader
|
| 53 |
+
|
| 54 |
+
from cut_cross_entropy import linear_cross_entropy
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
# =============================================================================
|
| 58 |
+
# HYPERPARAMETERS
|
| 59 |
+
# =============================================================================
|
| 60 |
+
class HP:
|
| 61 |
+
# data — corpus_v2 multi-shard mixture (Mathstral SPM 32k)
|
| 62 |
+
corpus_root = os.environ.get(
|
| 63 |
+
"CORPUS_ROOT",
|
| 64 |
+
"/leonardo_scratch/fast/IscrC_YENDRI/mprignan/corpus_v2",
|
| 65 |
+
)
|
| 66 |
+
# Mixture preset selezionabile via MIXTURE_PRESET env. Default = 70/22/8 (run baseline 200M).
|
| 67 |
+
# "v3_noloops" = fineweb 22%→15% (≈2/3), delta freed → math (77/15/8 total).
|
| 68 |
+
_MIXTURE_PRESETS = {
|
| 69 |
+
"default": {
|
| 70 |
+
"openmath_full": 0.34, "tinygsm": 0.14, "openmathreasoning": 0.10,
|
| 71 |
+
"numina_15": 0.07, "numina_cot": 0.05, # = 0.70 math
|
| 72 |
+
"fineweb_edu": 0.22, "cosmopedia": 0.08, # = 0.30 NL
|
| 73 |
+
},
|
| 74 |
+
"v3_noloops": {
|
| 75 |
+
"openmath_full": 0.37, "tinygsm": 0.15, "openmathreasoning": 0.11,
|
| 76 |
+
"numina_15": 0.08, "numina_cot": 0.06, # = 0.77 math
|
| 77 |
+
"fineweb_edu": 0.15, "cosmopedia": 0.08, # = 0.23 NL (fineweb ridotto a 2/3)
|
| 78 |
+
},
|
| 79 |
+
}
|
| 80 |
+
mixture = _MIXTURE_PRESETS[os.environ.get("MIXTURE_PRESET", "default")]
|
| 81 |
+
nl_datasets = ("fineweb_edu", "cosmopedia") # per math_frac logging
|
| 82 |
+
seq_len = 2048
|
| 83 |
+
bs_per_dev = int(os.environ.get("BS_PER_DEV", 32))
|
| 84 |
+
train_batch_tokens = 4 * bs_per_dev * 2048 # 4×32×2048 = 262k tok/step (4 GPU)
|
| 85 |
+
|
| 86 |
+
# model — shape 200M (scale-up dal 92M: d_model 512→768, d_ff 2048→3072, heads 8→12)
|
| 87 |
+
n_layers = int(os.environ.get("N_LAYERS", 24))
|
| 88 |
+
d_model = int(os.environ.get("D_MODEL", 768))
|
| 89 |
+
n_heads = int(os.environ.get("N_HEADS", 12))
|
| 90 |
+
head_dim = 64
|
| 91 |
+
d_ff = int(os.environ.get("D_FF", 3072))
|
| 92 |
+
rope_base = 1_000_000.0
|
| 93 |
+
logit_softcap = 30.0
|
| 94 |
+
tied_embeddings = True
|
| 95 |
+
qk_norm = True
|
| 96 |
+
# v3 features (locked)
|
| 97 |
+
use_gated_attn = True
|
| 98 |
+
# Channel mixer (FFN) variant. Knob aperto dall'audit A/B 2026-05-28 (vedi analysis/guido_ab/REPORT).
|
| 99 |
+
# "udlp" : VariableUDLP, contract(LeakyReLU²(expand(x))) (current default)
|
| 100 |
+
# "gated_udlp" : VariableGatedUDLP, UDLP·sigmoid(gate_proj(x[..., :K])) (sparse output gate)
|
| 101 |
+
# "leaky_reglu2" : LeakyReGLU² GLU, contract(LeakyReLU²(expand(x)) · up(x)) (act·value, +1 proj)
|
| 102 |
+
mlp_kind = os.environ.get("MLP_KIND", "udlp")
|
| 103 |
+
use_smear_gate = True
|
| 104 |
+
# gate_input_dim: 12 era stato scelto stile parameter-golf/sparse. Sul d=768 è ~1.5% di x:
|
| 105 |
+
# per il prossimo run testare 64 (8%) o 128 (17%) — vedi audit gate-activity (40% dei gate<0.1).
|
| 106 |
+
gate_input_dim = int(os.environ.get("GATE_INPUT_DIM", 12))
|
| 107 |
+
|
| 108 |
+
# optimizer (Muon LOCKED)
|
| 109 |
+
optimizer_kind = "muon"
|
| 110 |
+
# Orthogonalizer per Muon. Knob aperto dall'audit A/B 2026-05-28.
|
| 111 |
+
# "ns5" : Newton-Schulz quintic, coeff (3.4445,-4.7750,2.0315), Keller Jordan
|
| 112 |
+
# "polar_express": Polar Express (arxiv 2505.16932) — schedule di 8 coeff/step,
|
| 113 |
+
# convergenza più rapida. RACCOMANDATO MUON_BACKEND_STEPS=8 (default 5
|
| 114 |
+
# di NS5 è sotto-iterato per PE → convergenza parziale).
|
| 115 |
+
orthogonalizer = os.environ.get("ORTHOGONALIZER", "ns5")
|
| 116 |
+
muon_backend_steps = int(os.environ.get("MUON_BACKEND_STEPS", 5))
|
| 117 |
+
matrix_lr = 0.01
|
| 118 |
+
tied_embed_lr = 0.005
|
| 119 |
+
scalar_lr = 0.04
|
| 120 |
+
muon_momentum = 0.95
|
| 121 |
+
muon_momentum_warmup_start = 0.85
|
| 122 |
+
muon_momentum_warmup_steps = 200
|
| 123 |
+
beta1 = 0.9
|
| 124 |
+
beta2 = 0.95
|
| 125 |
+
adam_eps = 1e-8
|
| 126 |
+
grad_clip = 1.0
|
| 127 |
+
|
| 128 |
+
# schedule (WSD: warmup → plateau → linear warmdown a lr_min_scale)
|
| 129 |
+
iterations = int(os.environ.get("ITERATIONS", 15000)) # ~4B tok; CALIBRA dopo lo smoke
|
| 130 |
+
warmup_steps = int(os.environ.get("WARMUP", 300))
|
| 131 |
+
warmdown_iters = int(os.environ.get("WARMDOWN", int(0.60 * iterations))) # 60%
|
| 132 |
+
lr_min_scale = float(os.environ.get("LR_MIN_SCALE", 0.001))
|
| 133 |
+
|
| 134 |
+
# === Layer looping (opzionale, default OFF per baseline) ===
|
| 135 |
+
# loop_v2 sul 92M: +4.5pp GSM8K @ activation_frac 0.30 (durante plateau LR).
|
| 136 |
+
loop_style = os.environ.get("LOOP_STYLE", "")
|
| 137 |
+
loop_activation_frac = float(os.environ.get("LOOP_ACTIVATION_FRAC", 0.30))
|
| 138 |
+
loop_pre_warm = os.environ.get("LOOP_PRE_WARM", "1") == "1"
|
| 139 |
+
|
| 140 |
+
# logging / checkpoint
|
| 141 |
+
train_log_every = int(os.environ.get("LOG_EVERY", 50)) # dump CSV ogni N (loss PER-STEP nel buffer)
|
| 142 |
+
loss_trace_csv = os.environ.get(
|
| 143 |
+
"LOSS_TRACE_CSV",
|
| 144 |
+
"/leonardo_work/IscrC_YENDRI/paerle/PiCO/Guido-1/PiCO2_test/logs/guido_small_loss_trace.csv",
|
| 145 |
+
)
|
| 146 |
+
ckpt_dir = os.environ.get(
|
| 147 |
+
"CKPT_DIR",
|
| 148 |
+
"/leonardo_work/IscrC_YENDRI/paerle/PiCO/ckpts/pico_guido_small",
|
| 149 |
+
)
|
| 150 |
+
save_final = True
|
| 151 |
+
save_interval = int(os.environ.get("SAVE_INTERVAL", 2000))
|
| 152 |
+
keep_n_recent = 5
|
| 153 |
+
seed = int(os.environ.get("SEED", 1337))
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
# =============================================================================
|
| 157 |
+
# MUON OPTIMIZER
|
| 158 |
+
# =============================================================================
|
| 159 |
+
def zeropower_via_newtonschulz5(G: Tensor, steps: int = 5, eps: float = 1e-7) -> Tensor:
|
| 160 |
+
a, b, c = (3.4445, -4.7750, 2.0315)
|
| 161 |
+
X = G.bfloat16()
|
| 162 |
+
X /= X.norm() + eps
|
| 163 |
+
transposed = G.size(0) > G.size(1)
|
| 164 |
+
if transposed:
|
| 165 |
+
X = X.T
|
| 166 |
+
for _ in range(steps):
|
| 167 |
+
A = X @ X.T
|
| 168 |
+
B = b * A + c * A @ A
|
| 169 |
+
X = a * X + B @ X
|
| 170 |
+
return X.T if transposed else X
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
# Polar Express coefficients (arxiv 2505.16932). Stessa forma di NS5 ma SCHEDULE di 8 set:
|
| 174 |
+
# iter 1 aggressivo per matrici mal condizionate, iter 7-8 = steady-state Halley (3/8, -10/8, 15/8).
|
| 175 |
+
# Source: NVIDIA NeMo Emerging-Optimizers (authoritative, production-tested).
|
| 176 |
+
# https://docs.nvidia.com/nemo/emerging-optimizers/0.1.0/_modules/emerging_optimizers/orthogonalized_optimizers/muon_utils.html
|
| 177 |
+
_POLAR_EXPRESS_COEFFS = (
|
| 178 |
+
(8.2051, -22.9019, 16.4607),
|
| 179 |
+
(4.0664, -2.8612, 0.5184),
|
| 180 |
+
(3.9096, -2.8234, 0.5250),
|
| 181 |
+
(3.2856, -2.4153, 0.4853),
|
| 182 |
+
(2.2779, -1.6198, 0.3985),
|
| 183 |
+
(1.8726, -1.2307, 0.3585),
|
| 184 |
+
(1.8564, -1.2132, 0.3568),
|
| 185 |
+
(1.8750, -1.2500, 0.3750),
|
| 186 |
+
)
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def zeropower_via_polar_express(G: Tensor, steps: int = 8, eps: float = 1e-7) -> Tensor:
|
| 190 |
+
"""Polar Express orthogonalizer — convergenza più rapida di NS5 quintic.
|
| 191 |
+
Iteration: X = a*X + (b*A + c*A@A)@X con A=X@X^T, a/b/c SCHEDULATI per step.
|
| 192 |
+
Normalizzazione Frobenius (identica a NS5). Per convergenza piena servono ≥7 step
|
| 193 |
+
(raccomandato MUON_BACKEND_STEPS=8). Oltre 8 step usa lo steady-state (Halley)."""
|
| 194 |
+
X = G.bfloat16()
|
| 195 |
+
X /= X.norm() + eps
|
| 196 |
+
transposed = G.size(0) > G.size(1)
|
| 197 |
+
if transposed:
|
| 198 |
+
X = X.T
|
| 199 |
+
n_coeffs = len(_POLAR_EXPRESS_COEFFS)
|
| 200 |
+
for k in range(steps):
|
| 201 |
+
a, b, c = _POLAR_EXPRESS_COEFFS[k if k < n_coeffs else n_coeffs - 1]
|
| 202 |
+
A = X @ X.T
|
| 203 |
+
B = b * A + c * A @ A
|
| 204 |
+
X = a * X + B @ X
|
| 205 |
+
return X.T if transposed else X
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
class Muon(torch.optim.Optimizer):
|
| 209 |
+
def __init__(self, params, lr, momentum, backend_steps, nesterov=True):
|
| 210 |
+
super().__init__(params, dict(lr=lr, momentum=momentum,
|
| 211 |
+
backend_steps=backend_steps, nesterov=nesterov))
|
| 212 |
+
|
| 213 |
+
@torch.no_grad()
|
| 214 |
+
def step(self, closure=None):
|
| 215 |
+
distributed = dist.is_available() and dist.is_initialized()
|
| 216 |
+
world_size = dist.get_world_size() if distributed else 1
|
| 217 |
+
rank = dist.get_rank() if distributed else 0
|
| 218 |
+
for group in self.param_groups:
|
| 219 |
+
params = group["params"]
|
| 220 |
+
if not params:
|
| 221 |
+
continue
|
| 222 |
+
lr, mom, ns_steps, nesterov = group["lr"], group["momentum"], group["backend_steps"], group["nesterov"]
|
| 223 |
+
total = sum(int(p.numel()) for p in params)
|
| 224 |
+
updates_flat = torch.zeros(total, device=params[0].device, dtype=torch.bfloat16)
|
| 225 |
+
curr = 0
|
| 226 |
+
for i, p in enumerate(params):
|
| 227 |
+
if i % world_size == rank and p.grad is not None:
|
| 228 |
+
g = p.grad
|
| 229 |
+
state = self.state[p]
|
| 230 |
+
if "momentum_buffer" not in state:
|
| 231 |
+
state["momentum_buffer"] = torch.zeros_like(g)
|
| 232 |
+
buf = state["momentum_buffer"]
|
| 233 |
+
buf.mul_(mom).add_(g)
|
| 234 |
+
if nesterov:
|
| 235 |
+
g = g.add(buf, alpha=mom)
|
| 236 |
+
g = zeropower_via_newtonschulz5(g, steps=ns_steps)
|
| 237 |
+
g *= max(1, g.size(0) / g.size(1)) ** 0.5
|
| 238 |
+
updates_flat[curr:curr + p.numel()] = g.reshape(-1)
|
| 239 |
+
curr += p.numel()
|
| 240 |
+
if distributed:
|
| 241 |
+
dist.all_reduce(updates_flat, op=dist.ReduceOp.SUM)
|
| 242 |
+
curr = 0
|
| 243 |
+
for p in params:
|
| 244 |
+
u = updates_flat[curr:curr + p.numel()].view_as(p).to(p.dtype)
|
| 245 |
+
p.add_(u, alpha=-lr)
|
| 246 |
+
curr += p.numel()
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
# =============================================================================
|
| 250 |
+
# CHANNEL MIXER VARIANT — LeakyReGLU² GLU-style FFN
|
| 251 |
+
# =============================================================================
|
| 252 |
+
class LeakyReGLU2_FFN(nn.Module):
|
| 253 |
+
"""LeakyReLU² GLU: contract(LeakyReLU²(expand(x)) · up(x)).
|
| 254 |
+
|
| 255 |
+
GLU-style channel mixer (à la SwiGLU/ReGLU) con activation LeakyReLU². Aggiunge UN extra
|
| 256 |
+
proiezione `up` (d_model→M) rispetto a VariableUDLP → +50% param nell'FFN. Identity-at-init:
|
| 257 |
+
contract=0 ⇒ branch nullo all'init (gradiente fluisce attraverso `expand` e `up`).
|
| 258 |
+
|
| 259 |
+
Compatibile con Vathos `Builder`: signature (d_model, d_output, M, activation, dropout).
|
| 260 |
+
Param naming: `expand` (act path, ndim==2 → Muon), `up` (value path), `contract` (output).
|
| 261 |
+
"""
|
| 262 |
+
def __init__(self, d_model, d_output, M, activation=None, dropout=0.0):
|
| 263 |
+
super().__init__()
|
| 264 |
+
self.expand = nn.Linear(d_model, M, bias=False) # act path (the W in act(xW))
|
| 265 |
+
self.up = nn.Linear(d_model, M, bias=False) # value path (the V in ·xV)
|
| 266 |
+
self.contract = nn.Linear(M, d_output, bias=False)
|
| 267 |
+
self.activation = activation() if activation is not None else LeakyReLU2()
|
| 268 |
+
self.dropout = nn.Dropout(dropout)
|
| 269 |
+
self._init_weights()
|
| 270 |
+
|
| 271 |
+
def _init_weights(self):
|
| 272 |
+
torch.nn.init.orthogonal_(self.expand.weight)
|
| 273 |
+
torch.nn.init.orthogonal_(self.up.weight)
|
| 274 |
+
torch.nn.init.zeros_(self.contract.weight) # identity-at-init: branch nullo
|
| 275 |
+
|
| 276 |
+
def forward(self, x):
|
| 277 |
+
return self.dropout(self.contract(self.activation(self.expand(x)) * self.up(x)))
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
# =============================================================================
|
| 281 |
+
# DATA — MultiShardMixture: chunk-shuffle a read-time (NO rimpiazzo)
|
| 282 |
+
# =============================================================================
|
| 283 |
+
class ShuffledStream:
|
| 284 |
+
"""Un dataset → finestre (seq_len+1) in ordine PERMUTATO (shuffle senza rimpiazzo).
|
| 285 |
+
Chunk disgiunti per rank; re-permuta a fine epoca (sub-epoca = nessun wrap col budget attuale)."""
|
| 286 |
+
def __init__(self, shard_dir, rank, world_size, seq_len, seed):
|
| 287 |
+
self.reader = ShardReader(shard_dir)
|
| 288 |
+
self.win = seq_len + 1
|
| 289 |
+
self.n_chunks = self.reader.total_tokens // self.win
|
| 290 |
+
self.rank, self.ws, self.seed, self._epoch = rank, world_size, seed, 0
|
| 291 |
+
self._reperm()
|
| 292 |
+
|
| 293 |
+
def _reperm(self):
|
| 294 |
+
rng = np.random.default_rng(self.seed + 104729 * self._epoch)
|
| 295 |
+
self.my = rng.permutation(self.n_chunks)[self.rank::self.ws]
|
| 296 |
+
self.ptr = 0
|
| 297 |
+
self._epoch += 1
|
| 298 |
+
|
| 299 |
+
def next_window(self):
|
| 300 |
+
if self.ptr >= len(self.my):
|
| 301 |
+
self._reperm()
|
| 302 |
+
ci = int(self.my[self.ptr])
|
| 303 |
+
self.ptr += 1
|
| 304 |
+
return self.reader.read(ci * self.win, self.win) # np.uint32 [win]
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
class MultiShardMixture:
|
| 308 |
+
"""Weighted-pick del dataset per-sample + finestra chunk-shuffled. Ogni doc ≤1 volta/epoca."""
|
| 309 |
+
def __init__(self, mixture, corpus_root, rank, world_size, device, seq_len, seed):
|
| 310 |
+
self.names = list(mixture.keys())
|
| 311 |
+
self.w = np.array([mixture[n] for n in self.names], float)
|
| 312 |
+
self.w /= self.w.sum()
|
| 313 |
+
self.streams = {
|
| 314 |
+
n: ShuffledStream(str(Path(corpus_root) / n), rank, world_size, seq_len, seed + i * 7919)
|
| 315 |
+
for i, n in enumerate(self.names)
|
| 316 |
+
}
|
| 317 |
+
self.rng = np.random.default_rng(seed * 31 + rank)
|
| 318 |
+
self.device = device
|
| 319 |
+
|
| 320 |
+
def next_batch(self, bs):
|
| 321 |
+
picks = self.rng.choice(len(self.names), size=bs, p=self.w)
|
| 322 |
+
xs, ys, ids = [], [], []
|
| 323 |
+
for pi in picks:
|
| 324 |
+
w = self.streams[self.names[pi]].next_window()
|
| 325 |
+
t = torch.from_numpy(w.astype(np.int64))
|
| 326 |
+
xs.append(t[:-1])
|
| 327 |
+
ys.append(t[1:])
|
| 328 |
+
ids.append(self.names[pi])
|
| 329 |
+
return (torch.stack(xs).to(self.device, non_blocking=True),
|
| 330 |
+
torch.stack(ys).to(self.device, non_blocking=True), ids)
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
# =============================================================================
|
| 334 |
+
# LAYER LOOPING — parser + scalar gate init=0 (Universal Transformer style)
|
| 335 |
+
# =============================================================================
|
| 336 |
+
_LOOP_SEG_RE = re.compile(r"\(\s*([\d\s,]+?)\s*\)\s*x\s*(\d+)")
|
| 337 |
+
|
| 338 |
+
|
| 339 |
+
def parse_loop_style(s: str, n_layers: int):
|
| 340 |
+
"""Parse loop style stringa → lista di (indices, n_total_executions).
|
| 341 |
+
Esempio: "(3,4,5)x2, (8,9)x3" → [([3,4,5], 2), ([8,9], 3)]. Semantica xN: N esecuzioni
|
| 342 |
+
totali (1 originale + N-1 loop extra con gate). Validation: in-range, contigui, disgiunti, N>=2."""
|
| 343 |
+
s = s.strip()
|
| 344 |
+
if not s:
|
| 345 |
+
return []
|
| 346 |
+
stripped = re.sub(r"\s+", "", s)
|
| 347 |
+
pattern_strict = re.compile(r"\(([\d,]+)\)x(\d+)")
|
| 348 |
+
consumed = "".join(f"({a})x{b}" for a, b in pattern_strict.findall(stripped))
|
| 349 |
+
consumed_with_commas = ",".join(f"({a})x{b}" for a, b in pattern_strict.findall(stripped))
|
| 350 |
+
if stripped != consumed and stripped != consumed_with_commas:
|
| 351 |
+
raise ValueError(f"loop_style mal formato: {s!r}. Atteso: '(i,j,k)xN, (l,m)xM, ...'")
|
| 352 |
+
|
| 353 |
+
segments = []
|
| 354 |
+
used_indices = set()
|
| 355 |
+
for indices_str, n_str in _LOOP_SEG_RE.findall(s):
|
| 356 |
+
indices = sorted({int(x.strip()) for x in indices_str.split(",") if x.strip()})
|
| 357 |
+
n = int(n_str)
|
| 358 |
+
if n < 2:
|
| 359 |
+
raise ValueError(f"loop count xN deve avere N>=2 (segmento ({indices}) ha x{n}). "
|
| 360 |
+
f"Usa loop_style='' per nessun looping.")
|
| 361 |
+
if not indices:
|
| 362 |
+
raise ValueError(f"loop segment vuoto in {s!r}")
|
| 363 |
+
if indices != list(range(min(indices), max(indices) + 1)):
|
| 364 |
+
raise ValueError(f"segmento {tuple(indices)} NON contiguo: i layer in un loop "
|
| 365 |
+
f"devono essere contigui (e.g. (3,4,5), non (3,5)).")
|
| 366 |
+
for i in indices:
|
| 367 |
+
if not 0 <= i < n_layers:
|
| 368 |
+
raise ValueError(f"layer {i} fuori range [0, {n_layers}) nel segmento {tuple(indices)}")
|
| 369 |
+
if i in used_indices:
|
| 370 |
+
raise ValueError(f"layer {i} appare in più segmenti — i segmenti devono essere disgiunti")
|
| 371 |
+
used_indices.add(i)
|
| 372 |
+
segments.append((indices, n))
|
| 373 |
+
segments.sort(key=lambda seg: seg[0][0])
|
| 374 |
+
for s1, s2 in zip(segments, segments[1:]):
|
| 375 |
+
if s1[0][-1] >= s2[0][0]:
|
| 376 |
+
raise ValueError(f"segmenti sovrapposti dopo ordinamento: {s1[0]} e {s2[0]}")
|
| 377 |
+
return segments
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
class LoopGate(nn.Module):
|
| 381 |
+
"""Scalar gate init=0 (additive): h_main + α·(h_post − h_main). α=0 → identity."""
|
| 382 |
+
def __init__(self):
|
| 383 |
+
super().__init__()
|
| 384 |
+
self.alpha = nn.Parameter(torch.zeros(()))
|
| 385 |
+
|
| 386 |
+
def forward(self, h_main: Tensor, h_post: Tensor) -> Tensor:
|
| 387 |
+
return h_main + self.alpha * (h_post - h_main)
|
| 388 |
+
|
| 389 |
+
|
| 390 |
+
# =============================================================================
|
| 391 |
+
# MODEL — wrapper Vathos + layer looping (CE only, no KD)
|
| 392 |
+
# =============================================================================
|
| 393 |
+
class PiCOFormerLM(nn.Module):
|
| 394 |
+
def __init__(self, hp: HP, vocab_size: int, loop_style: str = ""):
|
| 395 |
+
super().__init__()
|
| 396 |
+
self.vocab_size = vocab_size
|
| 397 |
+
self.logit_softcap = hp.logit_softcap
|
| 398 |
+
self.tied_embeddings = hp.tied_embeddings
|
| 399 |
+
self.loop_style = loop_style
|
| 400 |
+
self.loop_segments = parse_loop_style(loop_style, hp.n_layers) if loop_style else []
|
| 401 |
+
self.loop_gates = nn.ModuleList([
|
| 402 |
+
nn.ModuleList([LoopGate() for _ in range(n - 1)])
|
| 403 |
+
for indices, n in self.loop_segments
|
| 404 |
+
])
|
| 405 |
+
self._seg_starts = {indices[0]: seg_id for seg_id, (indices, _) in enumerate(self.loop_segments)}
|
| 406 |
+
self.looped_mode = False
|
| 407 |
+
rope = RoPE(dim=hp.head_dim, max_len=8192, base=hp.rope_base)
|
| 408 |
+
if hp.use_gated_attn:
|
| 409 |
+
attn_builder = Builder(MultiheadGatedAttentionMixer,
|
| 410 |
+
n_heads=hp.n_heads, causal=True, dropout=0.0,
|
| 411 |
+
qk_norm=hp.qk_norm, pos_emb=rope, gate_input_dim=hp.gate_input_dim)
|
| 412 |
+
else:
|
| 413 |
+
attn_builder = Builder(MultiheadAttentionMixer,
|
| 414 |
+
n_heads=hp.n_heads, causal=True, dropout=0.0,
|
| 415 |
+
qk_norm=hp.qk_norm, pos_emb=rope)
|
| 416 |
+
if hp.mlp_kind == "udlp":
|
| 417 |
+
chan_builder = Builder(VariableUDLP,
|
| 418 |
+
d_output=hp.d_model, M=hp.d_ff, activation=LeakyReLU2)
|
| 419 |
+
elif hp.mlp_kind == "gated_udlp":
|
| 420 |
+
chan_builder = Builder(VariableGatedUDLP,
|
| 421 |
+
d_output=hp.d_model, M=hp.d_ff, activation=LeakyReLU2,
|
| 422 |
+
gate_input_dim=hp.gate_input_dim)
|
| 423 |
+
elif hp.mlp_kind == "leaky_reglu2":
|
| 424 |
+
chan_builder = Builder(LeakyReGLU2_FFN,
|
| 425 |
+
d_output=hp.d_model, M=hp.d_ff, activation=LeakyReLU2)
|
| 426 |
+
else:
|
| 427 |
+
raise ValueError(f"unknown mlp_kind={hp.mlp_kind!r}; usa udlp|gated_udlp|leaky_reglu2")
|
| 428 |
+
smear = SmearGate(hp.d_model, gate_input_dim=hp.gate_input_dim) if hp.use_smear_gate else None
|
| 429 |
+
smear_lookback = 1 if hp.use_smear_gate else 0
|
| 430 |
+
self.backbone = VathosPiCOFormer(
|
| 431 |
+
vocab_size=vocab_size, d_model=hp.d_model, n_layers=hp.n_layers,
|
| 432 |
+
spatials=attn_builder, channel=chan_builder, norm=VRMSNorm,
|
| 433 |
+
ve_groups=None, smear_gate=smear, smear_gate_lookback=smear_lookback,
|
| 434 |
+
logit_softcap=hp.logit_softcap, tied_embeddings=hp.tied_embeddings,
|
| 435 |
+
)
|
| 436 |
+
|
| 437 |
+
@property
|
| 438 |
+
def classifier_weight(self):
|
| 439 |
+
if self.tied_embeddings:
|
| 440 |
+
return self.backbone.embedder.embedding.weight
|
| 441 |
+
return self.backbone.unembedder.linear.weight
|
| 442 |
+
|
| 443 |
+
def _hidden(self, input_ids: Tensor) -> Tensor:
|
| 444 |
+
if self.looped_mode and self.loop_segments:
|
| 445 |
+
return self._hidden_looped(input_ids)
|
| 446 |
+
return self._hidden_normal(input_ids)
|
| 447 |
+
|
| 448 |
+
def _hidden_normal(self, input_ids: Tensor) -> Tensor:
|
| 449 |
+
bb = self.backbone
|
| 450 |
+
x0 = bb.embedder(input_ids)
|
| 451 |
+
if getattr(bb, "smear_gate", None) is not None:
|
| 452 |
+
x0 = bb.smear_gate(x0)
|
| 453 |
+
ves = bb._compute_ves(input_ids) if hasattr(bb, "_compute_ves") else [None] * len(bb.blocks)
|
| 454 |
+
h = x0
|
| 455 |
+
for i, block in enumerate(bb.blocks):
|
| 456 |
+
h = block(h, x0, ve=ves[i] if ves is not None else None)
|
| 457 |
+
h = bb.final_norm(h)
|
| 458 |
+
return h
|
| 459 |
+
|
| 460 |
+
def _hidden_looped(self, input_ids: Tensor) -> Tensor:
|
| 461 |
+
bb = self.backbone
|
| 462 |
+
x0 = bb.embedder(input_ids)
|
| 463 |
+
if getattr(bb, "smear_gate", None) is not None:
|
| 464 |
+
x0 = bb.smear_gate(x0)
|
| 465 |
+
ves = bb._compute_ves(input_ids) if hasattr(bb, "_compute_ves") else [None] * len(bb.blocks)
|
| 466 |
+
h = x0
|
| 467 |
+
n_layers = len(bb.blocks)
|
| 468 |
+
i = 0
|
| 469 |
+
while i < n_layers:
|
| 470 |
+
seg_id = self._seg_starts.get(i)
|
| 471 |
+
if seg_id is None:
|
| 472 |
+
h = bb.blocks[i](h, x0, ve=ves[i] if ves is not None else None)
|
| 473 |
+
i += 1
|
| 474 |
+
continue
|
| 475 |
+
indices, n_total = self.loop_segments[seg_id]
|
| 476 |
+
for li in indices:
|
| 477 |
+
h = bb.blocks[li](h, x0, ve=ves[li] if ves is not None else None)
|
| 478 |
+
h_main = h
|
| 479 |
+
for k in range(n_total - 1):
|
| 480 |
+
h_post = h_main
|
| 481 |
+
for li in indices:
|
| 482 |
+
h_post = bb.blocks[li](h_post, x0, ve=ves[li] if ves is not None else None)
|
| 483 |
+
h_main = self.loop_gates[seg_id][k](h_main, h_post)
|
| 484 |
+
h = h_main
|
| 485 |
+
i = indices[-1] + 1
|
| 486 |
+
h = bb.final_norm(h)
|
| 487 |
+
return h
|
| 488 |
+
|
| 489 |
+
def forward(self, input_ids: Tensor, targets: Tensor) -> Tensor:
|
| 490 |
+
"""CE only (cce fused, no logits materialized)."""
|
| 491 |
+
h = self._hidden(input_ids)
|
| 492 |
+
W = self.classifier_weight
|
| 493 |
+
# cce vuole input bf16/fp16; con master fp32 + autocast h/W possono essere fp32 → cast esplicito
|
| 494 |
+
# (input cce identici a prima; il grad risale al master fp32 attraverso il cast).
|
| 495 |
+
return linear_cross_entropy(
|
| 496 |
+
h.reshape(-1, h.size(-1)).bfloat16(), W.bfloat16(), targets.reshape(-1),
|
| 497 |
+
reduction="mean",
|
| 498 |
+
softcap=self.logit_softcap if self.logit_softcap > 0 else None,
|
| 499 |
+
)
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
# =============================================================================
|
| 503 |
+
# MAIN
|
| 504 |
+
# =============================================================================
|
| 505 |
+
def main():
|
| 506 |
+
hp = HP()
|
| 507 |
+
|
| 508 |
+
# Dispatch orthogonalizer (Muon.step chiama il nome globale `zeropower_via_newtonschulz5`,
|
| 509 |
+
# quindi se l'utente sceglie polar_express ri-bindo quel nome alla nuova fn).
|
| 510 |
+
global zeropower_via_newtonschulz5
|
| 511 |
+
if hp.orthogonalizer == "ns5":
|
| 512 |
+
zeropower_via_newtonschulz5 = torch.compile(zeropower_via_newtonschulz5)
|
| 513 |
+
elif hp.orthogonalizer == "polar_express":
|
| 514 |
+
# NotImplementedError lo lancia al primo call dentro Muon.step.
|
| 515 |
+
zeropower_via_newtonschulz5 = torch.compile(zeropower_via_polar_express)
|
| 516 |
+
else:
|
| 517 |
+
raise ValueError(f"unknown orthogonalizer={hp.orthogonalizer!r}; usa ns5|polar_express")
|
| 518 |
+
|
| 519 |
+
distributed = "RANK" in os.environ and "WORLD_SIZE" in os.environ
|
| 520 |
+
rank = int(os.environ.get("RANK", "0"))
|
| 521 |
+
world_size = int(os.environ.get("WORLD_SIZE", "1"))
|
| 522 |
+
local_rank = int(os.environ.get("LOCAL_RANK", "0"))
|
| 523 |
+
device = torch.device("cuda", local_rank)
|
| 524 |
+
torch.cuda.set_device(device)
|
| 525 |
+
if distributed:
|
| 526 |
+
dist.init_process_group(backend="nccl", device_id=device)
|
| 527 |
+
dist.barrier()
|
| 528 |
+
is_main = (rank == 0)
|
| 529 |
+
|
| 530 |
+
torch.backends.cuda.matmul.allow_tf32 = True
|
| 531 |
+
torch.backends.cudnn.allow_tf32 = True
|
| 532 |
+
torch.backends.cuda.enable_flash_sdp(True)
|
| 533 |
+
torch.backends.cuda.enable_mem_efficient_sdp(False)
|
| 534 |
+
torch.backends.cuda.enable_math_sdp(False)
|
| 535 |
+
torch.backends.cuda.enable_cudnn_sdp(False)
|
| 536 |
+
import torch._inductor.config as _ind
|
| 537 |
+
_ind.coordinate_descent_tuning = True
|
| 538 |
+
_ind.fx_graph_cache = True
|
| 539 |
+
_ind.triton.cudagraphs = False
|
| 540 |
+
|
| 541 |
+
torch.manual_seed(hp.seed)
|
| 542 |
+
torch.cuda.manual_seed_all(hp.seed)
|
| 543 |
+
np.random.seed(hp.seed)
|
| 544 |
+
|
| 545 |
+
def log(msg):
|
| 546 |
+
if is_main:
|
| 547 |
+
print(msg, flush=True)
|
| 548 |
+
|
| 549 |
+
log(f"world_size={world_size} rank={rank} device={device}")
|
| 550 |
+
log(f"corpus_root={hp.corpus_root}")
|
| 551 |
+
log(f"mixture={hp.mixture}")
|
| 552 |
+
|
| 553 |
+
# ---- data: verifica vocab consistency su tutti gli shard ----
|
| 554 |
+
vocab_size = None
|
| 555 |
+
eos_id = None
|
| 556 |
+
for name in hp.mixture:
|
| 557 |
+
idx = ShardReader(str(Path(hp.corpus_root) / name)).index
|
| 558 |
+
if vocab_size is None:
|
| 559 |
+
vocab_size, eos_id = idx.vocab_size, idx.eos_id
|
| 560 |
+
assert idx.vocab_size == vocab_size, (
|
| 561 |
+
f"vocab mismatch: {name} ha {idx.vocab_size}, atteso {vocab_size}")
|
| 562 |
+
assert idx.eos_id == eos_id, f"eos mismatch su {name}"
|
| 563 |
+
log(f"vocab={vocab_size} eos={eos_id} (consistente su {len(hp.mixture)} shard)")
|
| 564 |
+
assert vocab_size > eos_id
|
| 565 |
+
|
| 566 |
+
train_loader = MultiShardMixture(hp.mixture, hp.corpus_root, rank, world_size,
|
| 567 |
+
device, hp.seq_len, hp.seed)
|
| 568 |
+
if is_main:
|
| 569 |
+
for n, s in train_loader.streams.items():
|
| 570 |
+
log(f" [stream] {n:<20} chunks={s.n_chunks:,} (per-rank {len(s.my):,}) w={hp.mixture[n]:.2f}")
|
| 571 |
+
|
| 572 |
+
# ---- student model ----
|
| 573 |
+
# FIX (audit bf16): pesi in fp32 = MASTER weights. Il forward gira comunque in bf16 via autocast.
|
| 574 |
+
# Senza master fp32, al floor del warmdown gli update (~lr·1/√fan) cadono sotto la ULP bf16 e
|
| 575 |
+
# p.add_ diventa un no-op → il warmdown non abbassa più la loss. fp32 master lo risolve.
|
| 576 |
+
model = PiCOFormerLM(hp, vocab_size=vocab_size, loop_style=hp.loop_style).to(device)
|
| 577 |
+
n_params = sum(p.numel() for p in model.parameters())
|
| 578 |
+
log(f"model_params={n_params/1e6:.2f}M shape={hp.n_layers}L×{hp.d_model}×{hp.n_heads}h×{hp.d_ff}ff dtype=fp32-master/bf16-autocast")
|
| 579 |
+
log(f"knobs: mlp_kind={hp.mlp_kind} gate_input_dim={hp.gate_input_dim} orthogonalizer={hp.orthogonalizer} muon_steps={hp.muon_backend_steps} mixture_preset={os.environ.get('MIXTURE_PRESET','default')}")
|
| 580 |
+
if hp.loop_style:
|
| 581 |
+
loop_summary = ", ".join(f"({','.join(map(str, idx))})x{n}" for idx, n in model.loop_segments)
|
| 582 |
+
n_extra_passes = sum(len(idx) * (n - 1) for idx, n in model.loop_segments)
|
| 583 |
+
log(f"[loop] style={hp.loop_style!r} segments={loop_summary}")
|
| 584 |
+
log(f"[loop] gates={sum(len(g) for g in model.loop_gates)} (init α=0) "
|
| 585 |
+
f"extra_passes_per_fwd={n_extra_passes} activation_frac={hp.loop_activation_frac}")
|
| 586 |
+
compiled = torch.compile(model, dynamic=False, fullgraph=False, mode="max-autotune-no-cudagraphs")
|
| 587 |
+
ddp_kwargs = dict(device_ids=[local_rank], broadcast_buffers=False, gradient_as_bucket_view=True)
|
| 588 |
+
if hp.loop_style:
|
| 589 |
+
ddp_kwargs["find_unused_parameters"] = True # loop_gates senza grad in NORMAL fwd
|
| 590 |
+
model_for_train = DDP(compiled, **ddp_kwargs) if distributed else compiled
|
| 591 |
+
model.train()
|
| 592 |
+
|
| 593 |
+
# ---- optimizer ----
|
| 594 |
+
block_params = list(model.backbone.blocks.named_parameters())
|
| 595 |
+
matrix_params = [p for n, p in block_params if p.ndim == 2]
|
| 596 |
+
scalar_params = [p for n, p in block_params if p.ndim < 2]
|
| 597 |
+
for n, p in model.backbone.final_norm.named_parameters():
|
| 598 |
+
scalar_params.append(p)
|
| 599 |
+
for n, p in model.loop_gates.named_parameters():
|
| 600 |
+
scalar_params.append(p)
|
| 601 |
+
# FIX (audit): smear_gate è attributo top-level del backbone (NON dentro blocks) → senza questo
|
| 602 |
+
# i suoi param cadono fuori da ogni optimizer e restano congelati a init (smear_lambda=0 → gate
|
| 603 |
+
# no-op per tutto il run). Routing per ndim, coerente col resto (gate.weight 2D→Muon, lambda→scalar).
|
| 604 |
+
if getattr(model.backbone, "smear_gate", None) is not None:
|
| 605 |
+
for n, p in model.backbone.smear_gate.named_parameters():
|
| 606 |
+
(matrix_params if p.ndim == 2 else scalar_params).append(p)
|
| 607 |
+
embed_param = model.backbone.embedder.embedding.weight
|
| 608 |
+
|
| 609 |
+
opt_muon = Muon(matrix_params, lr=hp.matrix_lr, momentum=hp.muon_momentum,
|
| 610 |
+
backend_steps=hp.muon_backend_steps)
|
| 611 |
+
opt_embed = torch.optim.Adam(
|
| 612 |
+
[{"params": [embed_param], "lr": hp.tied_embed_lr, "base_lr": hp.tied_embed_lr}],
|
| 613 |
+
betas=(hp.beta1, hp.beta2), eps=hp.adam_eps, fused=True,
|
| 614 |
+
)
|
| 615 |
+
opt_scalar = torch.optim.Adam(
|
| 616 |
+
[{"params": scalar_params, "lr": hp.scalar_lr, "base_lr": hp.scalar_lr}],
|
| 617 |
+
betas=(hp.beta1, hp.beta2), eps=hp.adam_eps, fused=True,
|
| 618 |
+
)
|
| 619 |
+
optimizers = [opt_muon, opt_embed, opt_scalar]
|
| 620 |
+
for g in opt_muon.param_groups:
|
| 621 |
+
g["base_lr"] = hp.matrix_lr
|
| 622 |
+
log(f"opt: Muon={sum(p.numel() for p in matrix_params)/1e6:.2f}M "
|
| 623 |
+
f"Adam embed={embed_param.numel()/1e6:.2f}M scalars={sum(p.numel() for p in scalar_params)/1e6:.2f}M")
|
| 624 |
+
|
| 625 |
+
local_bs = hp.bs_per_dev
|
| 626 |
+
log(f"bs/rank={local_bs} global_bs={local_bs*world_size} tok/step={local_bs*world_size*hp.seq_len:,}")
|
| 627 |
+
log(f"iterations={hp.iterations} warmup={hp.warmup_steps} warmdown={hp.warmdown_iters} "
|
| 628 |
+
f"→ target_tok={hp.iterations*local_bs*world_size*hp.seq_len/1e9:.2f}B")
|
| 629 |
+
|
| 630 |
+
def lr_scale(step):
|
| 631 |
+
if step < hp.warmup_steps:
|
| 632 |
+
return step / max(hp.warmup_steps, 1)
|
| 633 |
+
decay_start = hp.iterations - hp.warmdown_iters
|
| 634 |
+
if step < decay_start:
|
| 635 |
+
return 1.0
|
| 636 |
+
progress = (step - decay_start) / max(hp.warmdown_iters, 1)
|
| 637 |
+
return max(1.0 - progress * (1.0 - hp.lr_min_scale), hp.lr_min_scale)
|
| 638 |
+
|
| 639 |
+
Path(hp.ckpt_dir).mkdir(parents=True, exist_ok=True)
|
| 640 |
+
|
| 641 |
+
# ---- loss-trace CSV (per-step; UN sync ogni train_log_every) ----
|
| 642 |
+
csv_f = csv_w = None
|
| 643 |
+
if is_main:
|
| 644 |
+
Path(hp.loss_trace_csv).parent.mkdir(parents=True, exist_ok=True)
|
| 645 |
+
csv_f = open(hp.loss_trace_csv, "w", newline="")
|
| 646 |
+
csv_w = csv.writer(csv_f)
|
| 647 |
+
csv_w.writerow(["step", "loss", "gnorm", "lr", "math_frac"])
|
| 648 |
+
log(f"loss_trace_csv={hp.loss_trace_csv}")
|
| 649 |
+
loss_buf, gn_buf, lr_buf, step_buf, mf_buf = [], [], [], [], []
|
| 650 |
+
|
| 651 |
+
# ---- Layer looping: pre-compile both variants ----
|
| 652 |
+
loop_activation_step = int(hp.iterations * hp.loop_activation_frac) if hp.loop_style else None
|
| 653 |
+
loop_active = False
|
| 654 |
+
if hp.loop_style and hp.loop_pre_warm:
|
| 655 |
+
log(f"[loop] pre-compiling NORMAL + LOOPED graphs (one-shot, può richiedere 5-15 min)")
|
| 656 |
+
x_warm = torch.zeros((local_bs, hp.seq_len), dtype=torch.long, device=device)
|
| 657 |
+
y_warm = torch.zeros_like(x_warm)
|
| 658 |
+
with torch.no_grad():
|
| 659 |
+
model.looped_mode = False
|
| 660 |
+
with torch.autocast(device_type="cuda", dtype=torch.bfloat16, enabled=True):
|
| 661 |
+
_ = compiled(x_warm, y_warm)
|
| 662 |
+
torch.cuda.synchronize()
|
| 663 |
+
log(f"[loop] NORMAL fwd graph compiled")
|
| 664 |
+
model.looped_mode = True
|
| 665 |
+
with torch.autocast(device_type="cuda", dtype=torch.bfloat16, enabled=True):
|
| 666 |
+
_ = compiled(x_warm, y_warm)
|
| 667 |
+
torch.cuda.synchronize()
|
| 668 |
+
log(f"[loop] LOOPED fwd graph compiled")
|
| 669 |
+
model.looped_mode = False
|
| 670 |
+
del x_warm, y_warm
|
| 671 |
+
|
| 672 |
+
torch.cuda.synchronize()
|
| 673 |
+
t0 = time.perf_counter()
|
| 674 |
+
total_tok_seen = 0
|
| 675 |
+
nan_abort = False
|
| 676 |
+
|
| 677 |
+
for step in range(1, hp.iterations + 1):
|
| 678 |
+
if loop_activation_step is not None and not loop_active and step >= loop_activation_step:
|
| 679 |
+
log(f"[loop] ACTIVATING looping at step {step} (gates α=0, identity passthrough)")
|
| 680 |
+
model.looped_mode = True
|
| 681 |
+
loop_active = True
|
| 682 |
+
for opt in optimizers:
|
| 683 |
+
opt.zero_grad(set_to_none=True)
|
| 684 |
+
|
| 685 |
+
x, y, ds_ids = train_loader.next_batch(local_bs)
|
| 686 |
+
total_tok_seen += local_bs * world_size * hp.seq_len
|
| 687 |
+
|
| 688 |
+
with torch.autocast(device_type="cuda", dtype=torch.bfloat16, enabled=True):
|
| 689 |
+
loss = model_for_train(x, y)
|
| 690 |
+
|
| 691 |
+
loss.backward()
|
| 692 |
+
|
| 693 |
+
scale = lr_scale(step)
|
| 694 |
+
for opt in optimizers:
|
| 695 |
+
for g in opt.param_groups:
|
| 696 |
+
g["lr"] = g["base_lr"] * scale
|
| 697 |
+
frac = min(step / hp.muon_momentum_warmup_steps, 1.0) if hp.muon_momentum_warmup_steps > 0 else 1.0
|
| 698 |
+
mom = (1 - frac) * hp.muon_momentum_warmup_start + frac * hp.muon_momentum
|
| 699 |
+
for g in opt_muon.param_groups:
|
| 700 |
+
g["momentum"] = mom
|
| 701 |
+
|
| 702 |
+
gn = torch.nn.utils.clip_grad_norm_(model.parameters(), hp.grad_clip)
|
| 703 |
+
for opt in optimizers:
|
| 704 |
+
opt.step()
|
| 705 |
+
|
| 706 |
+
# per-step buffers (NO sync qui — un solo sync ogni train_log_every al flush)
|
| 707 |
+
loss_buf.append(loss.detach())
|
| 708 |
+
gn_buf.append(gn.detach())
|
| 709 |
+
lr_buf.append(opt_muon.param_groups[0]["lr"])
|
| 710 |
+
step_buf.append(step)
|
| 711 |
+
mf_buf.append(sum(d not in hp.nl_datasets for d in ds_ids) / len(ds_ids))
|
| 712 |
+
|
| 713 |
+
if step % hp.train_log_every == 0 or step == 1 or step == hp.iterations:
|
| 714 |
+
torch.cuda.synchronize()
|
| 715 |
+
ls = torch.stack(loss_buf).float().cpu().tolist()
|
| 716 |
+
gs = torch.stack(gn_buf).float().cpu().tolist()
|
| 717 |
+
if not all(np.isfinite(ls)):
|
| 718 |
+
log(f"!! NaN/Inf loss intorno a step {step}, abort")
|
| 719 |
+
nan_abort = True
|
| 720 |
+
if is_main:
|
| 721 |
+
for s, l, g, lr_, mf in zip(step_buf, ls, gs, lr_buf, mf_buf):
|
| 722 |
+
csv_w.writerow([s, f"{l:.5f}", f"{g:.3f}", f"{lr_:.3e}", f"{mf:.2f}"])
|
| 723 |
+
csv_f.flush()
|
| 724 |
+
elapsed = time.perf_counter() - t0
|
| 725 |
+
tps = total_tok_seen / max(elapsed, 1e-9)
|
| 726 |
+
loop_tag = ""
|
| 727 |
+
if loop_active:
|
| 728 |
+
alphas = [g.alpha.detach().abs().item()
|
| 729 |
+
for seg_gates in model.loop_gates for g in seg_gates]
|
| 730 |
+
loop_tag = f" [LOOP |α|avg={sum(alphas)/max(len(alphas),1):.3f}]"
|
| 731 |
+
log(f"step {step:>5d}/{hp.iterations}{loop_tag} loss={ls[-1]:.4f} "
|
| 732 |
+
f"gnorm={gs[-1]:.3f} lr={lr_buf[-1]:.2e} mathfrac={mf_buf[-1]:.2f} "
|
| 733 |
+
f"tok={total_tok_seen/1e9:.2f}B tok/s={tps:>10,.0f} elapsed={elapsed:.1f}s")
|
| 734 |
+
loss_buf.clear(); gn_buf.clear(); lr_buf.clear(); step_buf.clear(); mf_buf.clear()
|
| 735 |
+
|
| 736 |
+
if nan_abort:
|
| 737 |
+
break
|
| 738 |
+
|
| 739 |
+
if (hp.save_interval > 0 and is_main and step % hp.save_interval == 0
|
| 740 |
+
and step != hp.iterations):
|
| 741 |
+
ckpt_path = Path(hp.ckpt_dir) / f"step_{step:06d}.pt"
|
| 742 |
+
torch.save({"step": step, "model": model.state_dict(), "vocab_size": vocab_size}, ckpt_path)
|
| 743 |
+
log(f" [ckpt] saved {ckpt_path.name}")
|
| 744 |
+
intermediates = [p for p in sorted(Path(hp.ckpt_dir).glob("step_*.pt")) if "_final" not in p.name]
|
| 745 |
+
for old in intermediates[:-hp.keep_n_recent]:
|
| 746 |
+
old.unlink()
|
| 747 |
+
|
| 748 |
+
if hp.save_final and is_main and not nan_abort:
|
| 749 |
+
ckpt_path = Path(hp.ckpt_dir) / f"step_{hp.iterations:06d}_final.pt"
|
| 750 |
+
torch.save({"step": hp.iterations, "model": model.state_dict(), "vocab_size": vocab_size}, ckpt_path)
|
| 751 |
+
log(f"saved {ckpt_path}")
|
| 752 |
+
if is_main and csv_f is not None:
|
| 753 |
+
csv_f.close()
|
| 754 |
+
|
| 755 |
+
if distributed:
|
| 756 |
+
dist.destroy_process_group()
|
| 757 |
+
|
| 758 |
+
|
| 759 |
+
if __name__ == "__main__":
|
| 760 |
+
main()
|
pytorch_model.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a69844e97388c554ad1e71d638b627740ee80af32b0018a140c11cde8b33067d
|
| 3 |
+
size 780459194
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch>=2.4
|
| 2 |
+
numpy
|
| 3 |
+
transformers>=4.40
|
| 4 |
+
cut-cross-entropy
|
| 5 |
+
huggingface_hub>=1.0
|
vathos/Analyzer.py
ADDED
|
@@ -0,0 +1,706 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Vathos ModdedFormer Analyzer API
|
| 3 |
+
=================================
|
| 4 |
+
A pure-Python/Jupyter alternative to the Streamlit inspector.
|
| 5 |
+
Designed for rigorous, programmatic topological and statistical inspection
|
| 6 |
+
of Transformer parameters, gradients, and activation manifolds.
|
| 7 |
+
|
| 8 |
+
Usage in a Jupyter Notebook:
|
| 9 |
+
----------------------------
|
| 10 |
+
import torch
|
| 11 |
+
from vathos_analyzer import VathosAnalyzer
|
| 12 |
+
|
| 13 |
+
# 1. Initialize
|
| 14 |
+
analyzer = VathosAnalyzer(model)
|
| 15 |
+
|
| 16 |
+
# 2. Plot Architecture Overview & Parameter distributions
|
| 17 |
+
analyzer.plot_overview()
|
| 18 |
+
analyzer.plot_weight_distributions(layer_idx=0, component="both")
|
| 19 |
+
|
| 20 |
+
# 3. Manifold Extraction via Forward Hooks
|
| 21 |
+
x = torch.randint(0, model.vocab_size, (1, 64))
|
| 22 |
+
analyzer.capture_forward(x) # Installs hooks, runs forward, saves state
|
| 23 |
+
|
| 24 |
+
# 4. Explore Layer Topology (Attention & FFN mapping f(x))
|
| 25 |
+
analyzer.plot_attention_weights(layer_idx=0)
|
| 26 |
+
analyzer.plot_ffn_manifold(layer_idx=0)
|
| 27 |
+
|
| 28 |
+
# 5. Training History
|
| 29 |
+
analyzer.plot_training_history()
|
| 30 |
+
|
| 31 |
+
# --- OR USE THE ONE-LINER MACRO ---
|
| 32 |
+
# from vathos_analyzer import study
|
| 33 |
+
# analyzer = study(model)
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
import math
|
| 37 |
+
import numpy as np
|
| 38 |
+
import torch
|
| 39 |
+
import torch.nn as nn
|
| 40 |
+
import matplotlib.pyplot as plt
|
| 41 |
+
from typing import Optional, Dict, List, Union
|
| 42 |
+
|
| 43 |
+
try:
|
| 44 |
+
from IPython.display import display
|
| 45 |
+
import pandas as pd
|
| 46 |
+
|
| 47 |
+
HAS_IPYTHON = True
|
| 48 |
+
except ImportError:
|
| 49 |
+
HAS_IPYTHON = False
|
| 50 |
+
|
| 51 |
+
# ──────────────────────────────────────────────────────────────────────────────
|
| 52 |
+
# DARK THEME STYLING
|
| 53 |
+
# ──────────────────────────────────────────────────────────────────────────────
|
| 54 |
+
DARK_THEME = {
|
| 55 |
+
"figure.facecolor": "#0D0D1A",
|
| 56 |
+
"axes.facecolor": "#0D0D1A",
|
| 57 |
+
"axes.edgecolor": "#252550",
|
| 58 |
+
"axes.labelcolor": "#C0C0E0",
|
| 59 |
+
"xtick.color": "#9CA3AF",
|
| 60 |
+
"ytick.color": "#9CA3AF",
|
| 61 |
+
"text.color": "#D8D8F0",
|
| 62 |
+
"grid.color": "#1E1E3A",
|
| 63 |
+
"grid.alpha": 0.6,
|
| 64 |
+
"legend.facecolor": "#11111F",
|
| 65 |
+
"legend.edgecolor": "#252550",
|
| 66 |
+
}
|
| 67 |
+
COLORS = ["#A78BFA", "#60A5FA", "#34D399", "#F472B6", "#FB923C",
|
| 68 |
+
"#FBBF24", "#38BDF8", "#A3E635", "#E879F9", "#F87171"]
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def apply_theme():
|
| 72 |
+
plt.rcParams.update(DARK_THEME)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
# ──────────────────────────────────────────────────────────────────────────────
|
| 76 |
+
# CORE ANALYZER CLASS
|
| 77 |
+
# ──────────────────────────────────────────────────────────────────────────────
|
| 78 |
+
class VathosAnalyzer:
|
| 79 |
+
def __init__(self, model: nn.Module, dark_mode: bool = True):
|
| 80 |
+
"""
|
| 81 |
+
Initializes the analyzer.
|
| 82 |
+
:param model: The Vathos ModdedFormer instance.
|
| 83 |
+
"""
|
| 84 |
+
self.model = model
|
| 85 |
+
self.captured_activations: Dict[int, Dict[str, torch.Tensor]] = {}
|
| 86 |
+
if dark_mode:
|
| 87 |
+
apply_theme()
|
| 88 |
+
|
| 89 |
+
# ─── EXTRACTION LOGIC ─────────────────────────────────────────────────────
|
| 90 |
+
|
| 91 |
+
def _is_attention(self, mixer) -> bool:
|
| 92 |
+
cls_name = type(mixer).__name__.lower()
|
| 93 |
+
return "attention" in cls_name or "attn" in cls_name
|
| 94 |
+
|
| 95 |
+
def _tensor_stats(self, t: torch.Tensor) -> dict:
|
| 96 |
+
f = t.detach().float().cpu()
|
| 97 |
+
v = f.numpy().flatten()
|
| 98 |
+
return {
|
| 99 |
+
"shape": tuple(t.shape),
|
| 100 |
+
"numel": t.numel(),
|
| 101 |
+
"mean": float(v.mean()),
|
| 102 |
+
"std": float(v.std()),
|
| 103 |
+
"min": float(v.min()),
|
| 104 |
+
"max": float(v.max()),
|
| 105 |
+
"l2": float(np.linalg.norm(v)),
|
| 106 |
+
"sparsity": float((np.abs(v) < 1e-6).mean()),
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
def _svd_values(self, t: torch.Tensor) -> Optional[np.ndarray]:
|
| 110 |
+
f = t.detach().float().cpu()
|
| 111 |
+
if f.ndim == 2:
|
| 112 |
+
try:
|
| 113 |
+
return torch.linalg.svdvals(f).numpy()
|
| 114 |
+
except Exception:
|
| 115 |
+
return None
|
| 116 |
+
if f.ndim == 3:
|
| 117 |
+
results = []
|
| 118 |
+
for i in range(f.shape[0]):
|
| 119 |
+
try:
|
| 120 |
+
results.append(torch.linalg.svdvals(f[i]).numpy())
|
| 121 |
+
except Exception:
|
| 122 |
+
pass
|
| 123 |
+
return np.stack(results) if results else None
|
| 124 |
+
return None
|
| 125 |
+
|
| 126 |
+
def _get_attention_weights(self, mixer, x: torch.Tensor) -> Optional[np.ndarray]:
|
| 127 |
+
if hasattr(mixer, "get_attention_weights"):
|
| 128 |
+
try:
|
| 129 |
+
with torch.no_grad():
|
| 130 |
+
w = mixer.get_attention_weights(x)
|
| 131 |
+
return w.detach().float().cpu().numpy()
|
| 132 |
+
except Exception:
|
| 133 |
+
pass
|
| 134 |
+
try:
|
| 135 |
+
with torch.no_grad():
|
| 136 |
+
B, L, D = x.shape
|
| 137 |
+
if hasattr(mixer, "qkv"):
|
| 138 |
+
proj = mixer.qkv
|
| 139 |
+
n_heads = mixer.n_heads
|
| 140 |
+
head_dim = mixer.head_dim
|
| 141 |
+
qkv = proj(x).view(B, L, 3, n_heads, head_dim)
|
| 142 |
+
q, k, v = qkv.unbind(dim=2)
|
| 143 |
+
q, k = q.transpose(1, 2).float(), k.transpose(1, 2).float()
|
| 144 |
+
scale = math.sqrt(head_dim)
|
| 145 |
+
scores = torch.matmul(q, k.transpose(-2, -1)) / scale
|
| 146 |
+
if getattr(mixer, "causal", True):
|
| 147 |
+
mask = torch.tril(torch.ones(L, L, device=x.device)).bool()
|
| 148 |
+
scores = scores.masked_fill(~mask, float("-inf"))
|
| 149 |
+
weights = torch.softmax(scores, dim=-1)
|
| 150 |
+
return weights[0].cpu().numpy()
|
| 151 |
+
elif hasattr(mixer, "qk"):
|
| 152 |
+
proj = mixer.qk
|
| 153 |
+
n_heads = mixer.n_heads
|
| 154 |
+
head_dim = mixer.head_dim
|
| 155 |
+
qk = proj(x).view(B, L, 2, n_heads, head_dim)
|
| 156 |
+
q, k = qk.unbind(dim=2)
|
| 157 |
+
q, k = q.transpose(1, 2).float(), k.transpose(1, 2).float()
|
| 158 |
+
scale = math.sqrt(head_dim)
|
| 159 |
+
scores = torch.matmul(q, k.transpose(-2, -1)) / scale
|
| 160 |
+
if getattr(mixer, "causal", True):
|
| 161 |
+
mask = torch.tril(torch.ones(L, L, device=x.device)).bool()
|
| 162 |
+
scores = scores.masked_fill(~mask, float("-inf"))
|
| 163 |
+
weights = torch.softmax(scores, dim=-1)
|
| 164 |
+
return weights[0].cpu().numpy()
|
| 165 |
+
except Exception:
|
| 166 |
+
pass
|
| 167 |
+
return None
|
| 168 |
+
|
| 169 |
+
def get_layer_params(self, layer_idx: int, component: str = "both") -> Dict[str, torch.Tensor]:
|
| 170 |
+
block = self.model.blocks[layer_idx]
|
| 171 |
+
sm = block.spatial_mixer
|
| 172 |
+
cm = block.channel_mixer
|
| 173 |
+
|
| 174 |
+
selected_modules = {}
|
| 175 |
+
if component in ["spatial", "both"]:
|
| 176 |
+
selected_modules["spatial"] = sm
|
| 177 |
+
if component in ["channel", "both"]:
|
| 178 |
+
selected_modules["channel"] = cm
|
| 179 |
+
|
| 180 |
+
all_params = {}
|
| 181 |
+
for prefix, module in selected_modules.items():
|
| 182 |
+
for name, p in module.named_parameters():
|
| 183 |
+
all_params[f"{prefix}.{name}"] = p
|
| 184 |
+
return all_params
|
| 185 |
+
|
| 186 |
+
# ─── FORWARD PASS HOOKING (MANIFOLD CAPTURE) ──────────────────────────────
|
| 187 |
+
|
| 188 |
+
def capture_forward(self, x: torch.Tensor, clear_previous: bool = True):
|
| 189 |
+
"""
|
| 190 |
+
Runs a forward pass while attaching hooks to capture inputs/outputs of
|
| 191 |
+
Spatial Mixers, and the strict pre/post activations of Channel Mixers.
|
| 192 |
+
"""
|
| 193 |
+
if clear_previous:
|
| 194 |
+
self.captured_activations.clear()
|
| 195 |
+
|
| 196 |
+
handles = []
|
| 197 |
+
|
| 198 |
+
for i, block in enumerate(self.model.blocks):
|
| 199 |
+
self.captured_activations[i] = {}
|
| 200 |
+
|
| 201 |
+
# Hook 1: Input to Spatial Mixer (For Q/K/V computations)
|
| 202 |
+
def make_spatial_hook(layer_idx):
|
| 203 |
+
def hook(m, inp):
|
| 204 |
+
self.captured_activations[layer_idx]["spatial_in"] = inp[0].detach()
|
| 205 |
+
|
| 206 |
+
return hook
|
| 207 |
+
|
| 208 |
+
handles.append(block.spatial_mixer.register_forward_pre_hook(make_spatial_hook(i)))
|
| 209 |
+
|
| 210 |
+
cm = block.channel_mixer
|
| 211 |
+
|
| 212 |
+
# Hook 2 & 3: Channel Mixer Expansion / Contraction (Pre and Post Activation)
|
| 213 |
+
if hasattr(cm, 'expand') and hasattr(cm, 'contract'):
|
| 214 |
+
def make_expand_hook(layer_idx):
|
| 215 |
+
def hook(m, inp, out):
|
| 216 |
+
self.captured_activations[layer_idx]["channel_pre_act"] = out.detach()
|
| 217 |
+
|
| 218 |
+
return hook
|
| 219 |
+
|
| 220 |
+
def make_contract_hook(layer_idx):
|
| 221 |
+
def hook(m, inp):
|
| 222 |
+
self.captured_activations[layer_idx]["channel_post_act"] = inp[0].detach()
|
| 223 |
+
|
| 224 |
+
return hook
|
| 225 |
+
|
| 226 |
+
handles.append(cm.expand.register_forward_hook(make_expand_hook(i)))
|
| 227 |
+
handles.append(cm.contract.register_forward_pre_hook(make_contract_hook(i)))
|
| 228 |
+
else:
|
| 229 |
+
# Fallback per block custom
|
| 230 |
+
def make_cm_hook(layer_idx):
|
| 231 |
+
def hook(m, inp):
|
| 232 |
+
self.captured_activations[layer_idx]["channel_in"] = inp[0].detach()
|
| 233 |
+
|
| 234 |
+
return hook
|
| 235 |
+
|
| 236 |
+
handles.append(cm.register_forward_pre_hook(make_cm_hook(i)))
|
| 237 |
+
|
| 238 |
+
# Run forward pass
|
| 239 |
+
self.model.eval()
|
| 240 |
+
try:
|
| 241 |
+
device = next(self.model.parameters()).device
|
| 242 |
+
except StopIteration:
|
| 243 |
+
device = torch.device("cpu")
|
| 244 |
+
|
| 245 |
+
with torch.no_grad():
|
| 246 |
+
self.model(x.to(device))
|
| 247 |
+
|
| 248 |
+
# Cleanup hooks
|
| 249 |
+
for h in handles:
|
| 250 |
+
h.remove()
|
| 251 |
+
|
| 252 |
+
print(f"✅ Manifolds captured for {len(self.model.blocks)} layers.")
|
| 253 |
+
|
| 254 |
+
# ─── PLOTTING ROUTINES ────────────────────────────────────────────────────
|
| 255 |
+
|
| 256 |
+
def show_fig(self, fig: plt.Figure):
|
| 257 |
+
"""Helper to show a figure inline or wait for script end."""
|
| 258 |
+
if HAS_IPYTHON:
|
| 259 |
+
display(fig)
|
| 260 |
+
plt.close(fig)
|
| 261 |
+
else:
|
| 262 |
+
fig.show()
|
| 263 |
+
|
| 264 |
+
def plot_overview(self):
|
| 265 |
+
"""Plots topological params: param norms, skip lambdas, zeroskips."""
|
| 266 |
+
print("=== 🏗️ Model Topological Overview ===")
|
| 267 |
+
total_params = sum(p.numel() for p in self.model.parameters())
|
| 268 |
+
print(f"Total Params: {total_params:,} | Layers: {self.model.n_layer} | Embed Dim: {self.model.embed_dim}")
|
| 269 |
+
|
| 270 |
+
# 1. Param Norms
|
| 271 |
+
layer_names, l2_norms = [], []
|
| 272 |
+
for i, block in enumerate(self.model.blocks):
|
| 273 |
+
for name, p in block.named_parameters():
|
| 274 |
+
layer_names.append(f"L{i}.{name}")
|
| 275 |
+
l2_norms.append(float(p.detach().norm(2)))
|
| 276 |
+
|
| 277 |
+
fig, ax = plt.subplots(figsize=(max(8, len(layer_names) * 0.35), 4))
|
| 278 |
+
ax.bar(range(len(layer_names)), l2_norms, color="#60A5FA", alpha=0.8)
|
| 279 |
+
ax.set_xticks(range(len(layer_names)))
|
| 280 |
+
ax.set_xticklabels(layer_names, rotation=90, fontsize=6)
|
| 281 |
+
ax.set_ylabel("L2 norm")
|
| 282 |
+
ax.set_title("Per-parameter L2 Norms", color="#A78BFA")
|
| 283 |
+
ax.grid(True, ls="--", alpha=0.4, axis="y")
|
| 284 |
+
fig.tight_layout()
|
| 285 |
+
self.show_fig(fig)
|
| 286 |
+
|
| 287 |
+
# 2. Skip Lambdas
|
| 288 |
+
if hasattr(self.model, "skip_lambdas") and self.model.skip_lambdas:
|
| 289 |
+
lambdas = {k: float(v.detach()) for k, v in self.model.skip_lambdas.items()}
|
| 290 |
+
fig2, ax2 = plt.subplots(figsize=(max(6, len(lambdas) * 1.4), 3.5))
|
| 291 |
+
keys = list(lambdas.keys())
|
| 292 |
+
vals = [lambdas[k] for k in keys]
|
| 293 |
+
bars = ax2.bar(range(len(keys)), vals, color=COLORS[:len(keys)], alpha=0.85)
|
| 294 |
+
ax2.set_xticks(range(len(keys)))
|
| 295 |
+
ax2.set_xticklabels([k.replace("route_", "").replace("_to_", "→") for k in keys], rotation=30, ha="right",
|
| 296 |
+
fontsize=8)
|
| 297 |
+
ax2.axhline(0, color="#9CA3AF", linewidth=0.8, ls="--")
|
| 298 |
+
ax2.set_title("Skip-connection Gate Values (λ)", color="#A78BFA")
|
| 299 |
+
for bar, v in zip(bars, vals):
|
| 300 |
+
ax2.text(bar.get_x() + bar.get_width() / 2, v + 0.003, f"{v:.4f}", ha="center", va="bottom", fontsize=8,
|
| 301 |
+
color="#D8D8F0")
|
| 302 |
+
fig2.tight_layout()
|
| 303 |
+
self.show_fig(fig2)
|
| 304 |
+
|
| 305 |
+
# 3. Zeroskip Params
|
| 306 |
+
if hasattr(self.model, "zeroskip") and self.model.zeroskip:
|
| 307 |
+
vals = [float(p.detach()) for p in self.model.zeroskip_params]
|
| 308 |
+
fig3, ax3 = plt.subplots(figsize=(max(6, len(vals) * 0.7), 3.5))
|
| 309 |
+
ax3.plot(vals, marker="o", color="#FBBF24", linewidth=1.5, markersize=6)
|
| 310 |
+
ax3.axhline(0, color="#9CA3AF", linewidth=0.8, ls="--")
|
| 311 |
+
ax3.set_xlabel("layer index")
|
| 312 |
+
ax3.set_ylabel("zeroskip α")
|
| 313 |
+
ax3.set_title("ZeroSkip Parameters (x₀ coefficient)", color="#A78BFA")
|
| 314 |
+
ax3.grid(True, ls="--", alpha=0.4)
|
| 315 |
+
fig3.tight_layout()
|
| 316 |
+
self.show_fig(fig3)
|
| 317 |
+
|
| 318 |
+
def print_layer_stats(self, layer_idx: int, component: str = "both"):
|
| 319 |
+
"""Prints a statistical dataframe of parameters for a given layer."""
|
| 320 |
+
params = self.get_layer_params(layer_idx, component)
|
| 321 |
+
rows = []
|
| 322 |
+
for pname, p in params.items():
|
| 323 |
+
s = self._tensor_stats(p)
|
| 324 |
+
rows.append({
|
| 325 |
+
"Parameter": pname, "Shape": str(s["shape"]), "Numel": s['numel'],
|
| 326 |
+
"Mean": s['mean'], "Std": s['std'], "Min": s['min'],
|
| 327 |
+
"Max": s['max'], "L2": s['l2'], "Sparsity": s['sparsity'],
|
| 328 |
+
"Grad": "Yes" if p.grad is not None else "No",
|
| 329 |
+
})
|
| 330 |
+
if HAS_IPYTHON:
|
| 331 |
+
df = pd.DataFrame(rows)
|
| 332 |
+
display(df)
|
| 333 |
+
else:
|
| 334 |
+
for r in rows:
|
| 335 |
+
print(r)
|
| 336 |
+
|
| 337 |
+
def plot_weight_distributions(self, layer_idx: int, component: str = "both", plot_gradients: bool = False,
|
| 338 |
+
bins: int = 80):
|
| 339 |
+
params = self.get_layer_params(layer_idx, component)
|
| 340 |
+
if plot_gradients:
|
| 341 |
+
params = {k: v.grad for k, v in params.items() if v.grad is not None}
|
| 342 |
+
if not params:
|
| 343 |
+
print("No gradients found. Run loss.backward() first.")
|
| 344 |
+
return
|
| 345 |
+
|
| 346 |
+
n = len(params)
|
| 347 |
+
if n == 0: return
|
| 348 |
+
cols = min(n, 3)
|
| 349 |
+
rows = math.ceil(n / cols)
|
| 350 |
+
fig, axes = plt.subplots(rows, cols, figsize=(5 * cols, 3.5 * rows))
|
| 351 |
+
if n == 1:
|
| 352 |
+
axes = np.array([[axes]])
|
| 353 |
+
elif rows == 1:
|
| 354 |
+
axes = axes.reshape(1, -1)
|
| 355 |
+
|
| 356 |
+
for idx, (name, t) in enumerate(params.items()):
|
| 357 |
+
ax = axes[idx // cols][idx % cols]
|
| 358 |
+
v = t.detach().float().cpu().numpy().flatten()
|
| 359 |
+
st_val = self._tensor_stats(t)
|
| 360 |
+
ax.hist(v, bins=bins, color=COLORS[idx % len(COLORS)], alpha=0.8, density=True)
|
| 361 |
+
ax.set_title(f"{name}\nμ={st_val['mean']:.3e} σ={st_val['std']:.3e}", fontsize=9)
|
| 362 |
+
ax.grid(True, ls="--", alpha=0.4)
|
| 363 |
+
|
| 364 |
+
for idx in range(n, rows * cols):
|
| 365 |
+
axes[idx // cols][idx % cols].set_visible(False)
|
| 366 |
+
|
| 367 |
+
fig.suptitle(f"Layer {layer_idx} - {'Gradient' if plot_gradients else 'Weight'} Distributions", color="#A78BFA",
|
| 368 |
+
fontsize=12)
|
| 369 |
+
fig.tight_layout()
|
| 370 |
+
self.show_fig(fig)
|
| 371 |
+
|
| 372 |
+
def plot_svd_spectrum(self, layer_idx: int, component: str = "both", plot_gradients: bool = False,
|
| 373 |
+
log_scale: bool = True):
|
| 374 |
+
params = self.get_layer_params(layer_idx, component)
|
| 375 |
+
if plot_gradients:
|
| 376 |
+
params = {k: v.grad for k, v in params.items() if v.grad is not None}
|
| 377 |
+
|
| 378 |
+
eligible = {k: v for k, v in params.items() if v.ndim >= 2}
|
| 379 |
+
if not eligible:
|
| 380 |
+
print("No 2D parameters found for SVD.")
|
| 381 |
+
return
|
| 382 |
+
|
| 383 |
+
n = len(eligible)
|
| 384 |
+
cols = min(n, 3)
|
| 385 |
+
rows = math.ceil(n / cols)
|
| 386 |
+
fig, axes = plt.subplots(rows, cols, figsize=(5 * cols, 3.5 * rows))
|
| 387 |
+
if n == 1:
|
| 388 |
+
axes = np.array([[axes]])
|
| 389 |
+
elif rows == 1:
|
| 390 |
+
axes = axes.reshape(1, -1)
|
| 391 |
+
|
| 392 |
+
for idx, (name, t) in enumerate(eligible.items()):
|
| 393 |
+
ax = axes[idx // cols][idx % cols]
|
| 394 |
+
sv = self._svd_values(t)
|
| 395 |
+
if sv is None: continue
|
| 396 |
+
|
| 397 |
+
if sv.ndim == 2:
|
| 398 |
+
for h in range(sv.shape[0]):
|
| 399 |
+
ax.plot(sv[h], color=COLORS[idx % len(COLORS)], alpha=0.3, linewidth=0.8)
|
| 400 |
+
ax.plot(sv.mean(0), color=COLORS[idx % len(COLORS)], linewidth=2, label="mean")
|
| 401 |
+
ax.legend(fontsize=8)
|
| 402 |
+
else:
|
| 403 |
+
ax.plot(sv, color=COLORS[idx % len(COLORS)], linewidth=1.5, marker=".", markersize=3)
|
| 404 |
+
|
| 405 |
+
cond = sv.flatten()[0] / (sv.flatten()[-1] + 1e-12)
|
| 406 |
+
ax.set_title(f"{name} | cond={cond:.1f}", fontsize=9)
|
| 407 |
+
if log_scale: ax.set_yscale("log")
|
| 408 |
+
ax.grid(True, ls="--", alpha=0.4)
|
| 409 |
+
|
| 410 |
+
for idx in range(n, rows * cols):
|
| 411 |
+
axes[idx // cols][idx % cols].set_visible(False)
|
| 412 |
+
|
| 413 |
+
fig.suptitle(f"Layer {layer_idx} - Singular Value Spectra ($\Sigma$)", color="#A78BFA", fontsize=12)
|
| 414 |
+
fig.tight_layout()
|
| 415 |
+
self.show_fig(fig)
|
| 416 |
+
|
| 417 |
+
# ─── MANIFOLD EXPLORATION (ATTENTION E FFN) ───────────────────────────────
|
| 418 |
+
|
| 419 |
+
def plot_attention_weights(self, layer_idx: int, tokens: Optional[List[str]] = None):
|
| 420 |
+
"""Plots the attention map of the layer using the captured manifold."""
|
| 421 |
+
if layer_idx not in self.captured_activations or "spatial_in" not in self.captured_activations[layer_idx]:
|
| 422 |
+
print(f"No captured input for layer {layer_idx}. Run `capture_forward(x)` first.")
|
| 423 |
+
return
|
| 424 |
+
|
| 425 |
+
sm = self.model.blocks[layer_idx].spatial_mixer
|
| 426 |
+
if not self._is_attention(sm):
|
| 427 |
+
print(f"Layer {layer_idx} spatial mixer is not Attention (is {type(sm).__name__}).")
|
| 428 |
+
return
|
| 429 |
+
|
| 430 |
+
x = self.captured_activations[layer_idx]["spatial_in"]
|
| 431 |
+
w = self._get_attention_weights(sm, x)
|
| 432 |
+
if w is None:
|
| 433 |
+
print("Could not extract attention weights.")
|
| 434 |
+
return
|
| 435 |
+
|
| 436 |
+
if w.ndim == 2: w = w[np.newaxis]
|
| 437 |
+
H, L_q, L_k = w.shape
|
| 438 |
+
cols = min(H, 4)
|
| 439 |
+
rows = math.ceil(H / cols)
|
| 440 |
+
|
| 441 |
+
fig, axes = plt.subplots(rows, cols, figsize=(4 * cols, 3.5 * rows))
|
| 442 |
+
axes = np.array(axes).flatten() if H > 1 else [axes]
|
| 443 |
+
|
| 444 |
+
for h in range(H):
|
| 445 |
+
ax = axes[h]
|
| 446 |
+
wh = w[h]
|
| 447 |
+
im = ax.imshow(wh, aspect="auto", cmap="magma", vmin=0, vmax=wh.max() + 1e-9)
|
| 448 |
+
ax.set_title(f"Head {h}", fontsize=10, color="#C4B5FD")
|
| 449 |
+
if tokens and len(tokens) == L_q:
|
| 450 |
+
clean_tokens = [t.replace('Ġ', ' ') for t in tokens]
|
| 451 |
+
fs = max(4, min(10, int(300 / L_q)))
|
| 452 |
+
ax.set_xticks(range(L_k))
|
| 453 |
+
ax.set_yticks(range(L_q))
|
| 454 |
+
ax.set_xticklabels(clean_tokens, rotation=90, fontsize=fs, color="#9CA3AF")
|
| 455 |
+
ax.set_yticklabels(clean_tokens, fontsize=fs, color="#9CA3AF")
|
| 456 |
+
fig.colorbar(im, ax=ax, shrink=0.8)
|
| 457 |
+
|
| 458 |
+
for h in range(H, len(axes)):
|
| 459 |
+
axes[h].set_visible(False)
|
| 460 |
+
|
| 461 |
+
fig.suptitle(f"Layer {layer_idx} - Attention Topology Analysis", color="#A78BFA", fontsize=12)
|
| 462 |
+
fig.tight_layout()
|
| 463 |
+
self.show_fig(fig)
|
| 464 |
+
|
| 465 |
+
def plot_ffn_manifold(self, layer_idx: int):
|
| 466 |
+
"""
|
| 467 |
+
Traccia la densità (PDF empirica) delle attivazioni prima della non linearità (XW_exp),
|
| 468 |
+
sovrapposta alla mappa non-lineare $f(x)$. Questo rivela immediatamente condizioni
|
| 469 |
+
di annullamento gradiente (dead neurons) o eccessiva saturazione.
|
| 470 |
+
Dipende dai Forward Hooks eseguiti tramite `capture_forward()`.
|
| 471 |
+
"""
|
| 472 |
+
if layer_idx not in self.captured_activations:
|
| 473 |
+
print(f"No manifold captured. Run `capture_forward(x)` first.")
|
| 474 |
+
return
|
| 475 |
+
|
| 476 |
+
cap = self.captured_activations[layer_idx]
|
| 477 |
+
cm = self.model.blocks[layer_idx].channel_mixer
|
| 478 |
+
|
| 479 |
+
if "channel_pre_act" in cap and "channel_post_act" in cap:
|
| 480 |
+
pre_act = cap["channel_pre_act"][0].cpu().numpy()
|
| 481 |
+
post_act = cap["channel_post_act"][0].cpu().numpy()
|
| 482 |
+
elif "channel_in" in cap:
|
| 483 |
+
# Fallback se non ci sono expand/contract espliciti
|
| 484 |
+
x = cap["channel_in"]
|
| 485 |
+
data = self._get_ffn_activations(cm, x, temp=1.0)
|
| 486 |
+
if not data:
|
| 487 |
+
print("Could not compute FFN manifold.")
|
| 488 |
+
return
|
| 489 |
+
pre_act = data["pre_act"]
|
| 490 |
+
post_act = data["post_act"]
|
| 491 |
+
else:
|
| 492 |
+
print("Missing necessary hooks data for FFN.")
|
| 493 |
+
return
|
| 494 |
+
|
| 495 |
+
# Estrazione act_fn per mapping analitico
|
| 496 |
+
act_fn = getattr(cm, "activation", getattr(cm, "act", getattr(cm, "act_fn", None)))
|
| 497 |
+
|
| 498 |
+
fig, axes = plt.subplots(1, 2, figsize=(12, 4.5))
|
| 499 |
+
pre_flat = pre_act.flatten()
|
| 500 |
+
post_flat = post_act.flatten()
|
| 501 |
+
|
| 502 |
+
# ── Subplot 1: Pre-act vs f(x) ──
|
| 503 |
+
ax1 = axes[0]
|
| 504 |
+
ax1_twin = ax1.twinx()
|
| 505 |
+
|
| 506 |
+
# Dominio assoluto
|
| 507 |
+
true_min = float(pre_flat.min())
|
| 508 |
+
true_max = float(pre_flat.max())
|
| 509 |
+
x_min = min(-5.0, true_min - 1.0)
|
| 510 |
+
x_max = max(5.0, true_max + 1.0)
|
| 511 |
+
x_vals = np.linspace(x_min, x_max, 1000)
|
| 512 |
+
|
| 513 |
+
y_vals = None
|
| 514 |
+
is_empirical = False
|
| 515 |
+
|
| 516 |
+
# 1. Tentativo analitico
|
| 517 |
+
if act_fn is not None:
|
| 518 |
+
try:
|
| 519 |
+
device = next(cm.parameters()).device if list(cm.parameters()) else "cpu"
|
| 520 |
+
dtype = next(cm.parameters()).dtype if list(cm.parameters()) else torch.float32
|
| 521 |
+
with torch.no_grad():
|
| 522 |
+
xt = torch.tensor(x_vals, dtype=dtype, device=device).view(1, 1, -1)
|
| 523 |
+
yt = act_fn(xt)
|
| 524 |
+
y_vals = yt.cpu().float().numpy().flatten()
|
| 525 |
+
except Exception:
|
| 526 |
+
pass
|
| 527 |
+
|
| 528 |
+
# 2. Empirico / Fallback
|
| 529 |
+
if (y_vals is None or np.array_equal(y_vals, x_vals)) and pre_flat.shape == post_flat.shape:
|
| 530 |
+
if not np.allclose(pre_flat, post_flat):
|
| 531 |
+
sort_idx = np.argsort(pre_flat)
|
| 532 |
+
x_vals = pre_flat[sort_idx]
|
| 533 |
+
y_vals = post_flat[sort_idx]
|
| 534 |
+
is_empirical = True
|
| 535 |
+
else:
|
| 536 |
+
y_vals = x_vals
|
| 537 |
+
elif y_vals is None:
|
| 538 |
+
y_vals = x_vals
|
| 539 |
+
|
| 540 |
+
label_map = "$f(x)$ map (Empirica)" if is_empirical else "$f(x)$ map"
|
| 541 |
+
|
| 542 |
+
# Curve
|
| 543 |
+
ax1.plot(x_vals, y_vals, color="#38BDF8", lw=3.0, label=label_map, zorder=4)
|
| 544 |
+
ax1.axvline(0, color="#4B5563", lw=1.5, ls="--", zorder=1)
|
| 545 |
+
ax1.axhline(0, color="#4B5563", lw=1.5, ls="--", zorder=1)
|
| 546 |
+
|
| 547 |
+
# Min / Max assoluti (Individuazione leak instabili)
|
| 548 |
+
ax1.plot(true_min, 0, marker='v', color='#EF4444', markersize=7, zorder=5)
|
| 549 |
+
ax1.plot(true_max, 0, marker='v', color='#EF4444', markersize=7, zorder=5)
|
| 550 |
+
ax1.text(true_min, 0.05, f"Min:\n{true_min:.1f}", color='#EF4444', ha='center', va='bottom', fontsize=9,
|
| 551 |
+
transform=ax1.get_xaxis_transform())
|
| 552 |
+
ax1.text(true_max, 0.05, f"Max:\n{true_max:.1f}", color='#EF4444', ha='center', va='bottom', fontsize=9,
|
| 553 |
+
transform=ax1.get_xaxis_transform())
|
| 554 |
+
|
| 555 |
+
ax1.set_xlabel("Pre-activation $x = (X W_{exp})$", fontsize=10)
|
| 556 |
+
ax1.set_ylabel("Activation Output $f(x)$", color="#38BDF8", fontsize=10)
|
| 557 |
+
ax1.tick_params(axis='y', labelcolor="#38BDF8")
|
| 558 |
+
|
| 559 |
+
# PDF pre-attivazione
|
| 560 |
+
ax1_twin.hist(pre_flat, bins=150, range=(x_min, x_max), color="#F472B6", alpha=0.4, density=True, zorder=2)
|
| 561 |
+
ax1_twin.set_ylabel("Empirical PDF $p(x)$", color="#F472B6", fontsize=10)
|
| 562 |
+
ax1_twin.tick_params(axis='y', labelcolor="#F472B6")
|
| 563 |
+
|
| 564 |
+
ax1.set_title(f"L{layer_idx} - Overlay: Activation $f(x)$ & Input Distribution", color="#A78BFA", fontsize=11)
|
| 565 |
+
ax1.legend(loc="upper left", fontsize=9)
|
| 566 |
+
|
| 567 |
+
# ── Subplot 2: Post-act distribution (Sparsity check) ──
|
| 568 |
+
ax2 = axes[1]
|
| 569 |
+
post_min, post_max = np.percentile(post_flat, [0.0, 99.9])
|
| 570 |
+
ax2.hist(post_flat, bins=120, range=(post_min, max(post_max, 1e-3)), color="#F472B6", alpha=0.6, density=True)
|
| 571 |
+
ax2.axvline(0, color="#9CA3AF", lw=1, ls="--", alpha=0.5)
|
| 572 |
+
ax2.set_xlabel("Post-activation $Act(X W_{exp})$", fontsize=10)
|
| 573 |
+
ax2.set_ylabel("Density", fontsize=10)
|
| 574 |
+
ax2.set_title("Post-Activation Distribution (Sparsity check)", color="#A78BFA", fontsize=11)
|
| 575 |
+
ax2.grid(True, ls="--", alpha=0.3)
|
| 576 |
+
|
| 577 |
+
fig.tight_layout()
|
| 578 |
+
self.show_fig(fig)
|
| 579 |
+
|
| 580 |
+
# ─── TRAINING HISTORY ───────────────────────────��─────────────────────────
|
| 581 |
+
|
| 582 |
+
def plot_training_history(self):
|
| 583 |
+
"""Plots losses and metrics matching the Vathos logic."""
|
| 584 |
+
losses_dict = getattr(self.model, "_losses_dict", {})
|
| 585 |
+
losses_ep = getattr(self.model, "_losses_per_epoch_dict", {})
|
| 586 |
+
metrics_ep = getattr(self.model, "_metrics_per_epoch", {})
|
| 587 |
+
|
| 588 |
+
if not losses_dict and not metrics_ep:
|
| 589 |
+
print("No training history found in this model.")
|
| 590 |
+
return
|
| 591 |
+
|
| 592 |
+
print("=== 📈 Training History ===")
|
| 593 |
+
print(
|
| 594 |
+
f"Steps: {getattr(self.model, 'steps', '-')} | Epochs: {getattr(self.model, 'epochs', '-')} | Best Loss: {getattr(self.model, 'best_loss', '-')}")
|
| 595 |
+
|
| 596 |
+
if losses_dict or losses_ep:
|
| 597 |
+
fig, ax = plt.subplots(figsize=(10, 4))
|
| 598 |
+
if losses_dict:
|
| 599 |
+
xs = list(losses_dict.keys())
|
| 600 |
+
ys = list(losses_dict.values())
|
| 601 |
+
ax.plot(xs, ys, color="#4c9be8", linewidth=0.8, alpha=0.4, label="Loss (step)")
|
| 602 |
+
# Smooth curve
|
| 603 |
+
if len(ys) > 10:
|
| 604 |
+
win = max(2, len(ys) // 20)
|
| 605 |
+
ys_sm = np.convolve(ys, np.ones(win) / win, mode="valid")
|
| 606 |
+
ax.plot(xs[win - 1:], ys_sm, color="#4c9be8", linewidth=1.5, label=f"Smoothed (w={win})")
|
| 607 |
+
|
| 608 |
+
if losses_ep:
|
| 609 |
+
ex = list(losses_ep.keys())
|
| 610 |
+
ey = list(losses_ep.values())
|
| 611 |
+
ax.plot(ex, ey, color="#f5a623", linewidth=2.2, marker="o", markersize=5, label="Loss per epoch")
|
| 612 |
+
|
| 613 |
+
ax.set_title("Training Loss", color="#A78BFA")
|
| 614 |
+
ax.set_xlabel("Steps / Epochs")
|
| 615 |
+
ax.set_ylabel("Loss")
|
| 616 |
+
ax.grid(True, ls="--", alpha=0.4)
|
| 617 |
+
ax.legend(fontsize=9)
|
| 618 |
+
fig.tight_layout()
|
| 619 |
+
self.show_fig(fig)
|
| 620 |
+
|
| 621 |
+
# Plot Metrics
|
| 622 |
+
for t_idx, (m_name, m_vals) in enumerate(metrics_ep.items()):
|
| 623 |
+
fig, ax = plt.subplots(figsize=(10, 3.5))
|
| 624 |
+
col = COLORS[t_idx % len(COLORS)]
|
| 625 |
+
ax.plot(range(len(m_vals)), m_vals, color=col, linewidth=2.2, marker="o", markersize=5,
|
| 626 |
+
label=f"{m_name} per epoch")
|
| 627 |
+
ax.set_title(f"Metric: {m_name}", color="#A78BFA")
|
| 628 |
+
ax.set_xlabel("Epochs")
|
| 629 |
+
ax.set_ylabel(m_name)
|
| 630 |
+
ax.grid(True, ls="--", alpha=0.4)
|
| 631 |
+
ax.legend(fontsize=9)
|
| 632 |
+
fig.tight_layout()
|
| 633 |
+
self.show_fig(fig)
|
| 634 |
+
|
| 635 |
+
def plot_all(self, layer_idx: int = 0, tokens: Optional[List[str]] = None):
|
| 636 |
+
"""
|
| 637 |
+
Esegue un'analisi topologica e statistica completa, chiamando in sequenza
|
| 638 |
+
tutte le routine grafiche. Genera un referto end-to-end del modello.
|
| 639 |
+
"""
|
| 640 |
+
print(f"\n{'=' * 70}\n🔬 INITIATING FULL TOPOLOGICAL ANALYSIS (Focus Layer {layer_idx})\n{'=' * 70}")
|
| 641 |
+
self.plot_overview()
|
| 642 |
+
|
| 643 |
+
print(f"\n[1/4] Weight Distributions (Layer {layer_idx})")
|
| 644 |
+
self.plot_weight_distributions(layer_idx=layer_idx)
|
| 645 |
+
|
| 646 |
+
print(f"\n[2/4] SVD Spectrum & Condition Numbers (Layer {layer_idx})")
|
| 647 |
+
self.plot_svd_spectrum(layer_idx=layer_idx)
|
| 648 |
+
|
| 649 |
+
if not self.captured_activations:
|
| 650 |
+
print("\n⚠️ Nessun forward pass catturato nello stato. Salto l'analisi del manifold (Attention/FFN).")
|
| 651 |
+
print(" Chiama `capture_forward(x)` prima di `plot_all()` per abilitarla.")
|
| 652 |
+
else:
|
| 653 |
+
print(f"\n[3/4] Attention Topology (Layer {layer_idx})")
|
| 654 |
+
self.plot_attention_weights(layer_idx=layer_idx, tokens=tokens)
|
| 655 |
+
|
| 656 |
+
print(f"\n[4/4] FFN Manifold & Activation Geometry f(x) (Layer {layer_idx})")
|
| 657 |
+
self.plot_ffn_manifold(layer_idx=layer_idx)
|
| 658 |
+
|
| 659 |
+
print("\n--- Training History ---")
|
| 660 |
+
self.plot_training_history()
|
| 661 |
+
print(f"\n{'=' * 70}\n✅ ANALYSIS COMPLETE\n{'=' * 70}")
|
| 662 |
+
|
| 663 |
+
|
| 664 |
+
# ──────────────────────────────────────────────────────────────────────────────
|
| 665 |
+
# TOP-LEVEL MACRO
|
| 666 |
+
# ──────────────────────────────────────────────────────────────────────────────
|
| 667 |
+
|
| 668 |
+
def study(model: nn.Module, layer_idx: int = 0, dummy_seq_len: int = 64,
|
| 669 |
+
device: Optional[Union[str, torch.device]] = None) -> VathosAnalyzer:
|
| 670 |
+
"""
|
| 671 |
+
Macro globale per profilare immediatamente un modello in un Notebook o script nudo.
|
| 672 |
+
1. Assicura il corretto posizionamento e deduce il device dei tensori.
|
| 673 |
+
2. Istanzia il VathosAnalyzer.
|
| 674 |
+
3. Genera un input sintetico coerente col dominio.
|
| 675 |
+
4. Cattura il manifold (iniettando i Forward Hooks estrae i pre-logits).
|
| 676 |
+
5. Esegue tutta la suite grafica (plot_all).
|
| 677 |
+
|
| 678 |
+
Ritorna:
|
| 679 |
+
L'istanza di VathosAnalyzer configurata per ulteriori ispezioni interattive.
|
| 680 |
+
"""
|
| 681 |
+
if device is None:
|
| 682 |
+
try:
|
| 683 |
+
device = next(model.parameters()).device
|
| 684 |
+
except StopIteration:
|
| 685 |
+
device = torch.device("cpu")
|
| 686 |
+
model.to(device)
|
| 687 |
+
|
| 688 |
+
print(f"🔍 Starting Rigorous Study on {type(model).__name__} (Device: {device})")
|
| 689 |
+
|
| 690 |
+
analyzer = VathosAnalyzer(model)
|
| 691 |
+
|
| 692 |
+
# Tentiamo di dedurre il vocab_size rigorosamente, con fallback
|
| 693 |
+
vocab_size = getattr(model, "vocab_size", 1000)
|
| 694 |
+
if not hasattr(model, "vocab_size") and hasattr(model, "embedder") and hasattr(model.embedder, "embedding"):
|
| 695 |
+
vocab_size = getattr(model.embedder.embedding, "num_embeddings", 1000)
|
| 696 |
+
|
| 697 |
+
# Dummy input per forzare il passaggio nei manifold locali ed estrarre la Jacobiana implicita
|
| 698 |
+
dummy_x = torch.randint(0, vocab_size, (1, dummy_seq_len), device=device)
|
| 699 |
+
|
| 700 |
+
print(f"📸 Capturing activation manifold with dummy sequence (L={dummy_seq_len})...")
|
| 701 |
+
analyzer.capture_forward(dummy_x)
|
| 702 |
+
|
| 703 |
+
# Lancia l'analisi completa concentrandosi sul layer indicato (0 di default)
|
| 704 |
+
analyzer.plot_all(layer_idx=layer_idx)
|
| 705 |
+
|
| 706 |
+
return analyzer
|
vathos/Transformers.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Basic Implementation of Famous Transformers Model using Vathos blocks SequenceModel.
|
| 2 |
+
serves firstly as an example on how to build with Vathos SequenceModel"""
|
| 3 |
+
|
| 4 |
+
from Vathos.blocks import *
|
| 5 |
+
import torch.nn as nn
|
| 6 |
+
from Vathos.functions import *
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class GPTTransformer(nn.Module):
|
| 10 |
+
def __init__(self, vocab_size, d_model, n_layers, n_heads, max_len, ff_expand=2, activation='gelu', dropout=0.1,
|
| 11 |
+
pe='sinu'):
|
| 12 |
+
super(GPTTransformer, self).__init__()
|
| 13 |
+
self.vocab_size = vocab_size
|
| 14 |
+
self.d_model = d_model
|
| 15 |
+
self.n_layers = n_layers
|
| 16 |
+
self.n_heads = n_heads
|
| 17 |
+
self.ff_expand = ff_expand
|
| 18 |
+
self.activation = activation
|
| 19 |
+
self.dropout = dropout
|
| 20 |
+
if pe == 'sinu':
|
| 21 |
+
self.pe = True
|
| 22 |
+
self.rope = False
|
| 23 |
+
elif pe == 'rope':
|
| 24 |
+
self.pe = False
|
| 25 |
+
self.rope = True
|
| 26 |
+
elif self.pe == 'nope':
|
| 27 |
+
self.pe = False
|
| 28 |
+
else:
|
| 29 |
+
raise ValueError('pe={} not recognized'.format(pe))
|
| 30 |
+
|
| 31 |
+
self.model = SequenceModel(
|
| 32 |
+
vocab_size=vocab_size,
|
| 33 |
+
d_model=d_model,
|
| 34 |
+
n_layers=n_layers,
|
| 35 |
+
max_len=max_len,
|
| 36 |
+
pos_encoder=self.pe,
|
| 37 |
+
rope=self.rope,
|
| 38 |
+
spatial_args={"causal": True, "n_heads": n_heads, "rope": self.rope},
|
| 39 |
+
channel_args={"expand": ff_expand, "activation": ACTIVS[activation], "depth": 2}
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
def forward(self, x):
|
| 43 |
+
return self.model(x)
|
| 44 |
+
|
| 45 |
+
def __repr__(self):
|
| 46 |
+
return self.model.__repr__()
|
| 47 |
+
|
| 48 |
+
def __str__(self):
|
| 49 |
+
return self.model.__str__()
|
| 50 |
+
|
| 51 |
+
@torch.no_grad()
|
| 52 |
+
def generate(self, *args, **kwargs):
|
| 53 |
+
return self.model.generate(*args, *kwargs)
|
| 54 |
+
|
| 55 |
+
def summary(self):
|
| 56 |
+
self.model.summary()
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class BERTTransformer(nn.Module):
|
| 60 |
+
def __init__(self, vocab_size, d_model, n_layers, n_heads, max_len, ff_expand=2, activation='gelu', dropout=0.1,
|
| 61 |
+
pe='sinu'):
|
| 62 |
+
super(GPTTransformer, self).__init__()
|
| 63 |
+
self.vocab_size = vocab_size
|
| 64 |
+
self.d_model = d_model
|
| 65 |
+
self.n_layers = n_layers
|
| 66 |
+
self.n_heads = n_heads
|
| 67 |
+
self.ff_expand = ff_expand
|
| 68 |
+
self.activation = activation
|
| 69 |
+
self.dropout = dropout
|
| 70 |
+
if pe == 'sinu':
|
| 71 |
+
self.pe = True
|
| 72 |
+
self.rope = False
|
| 73 |
+
elif pe == 'rope':
|
| 74 |
+
self.pe = False
|
| 75 |
+
self.rope = True
|
| 76 |
+
elif self.pe == 'nope':
|
| 77 |
+
self.pe = False
|
| 78 |
+
else:
|
| 79 |
+
raise ValueError('pe={} not recognized'.format(pe))
|
| 80 |
+
|
| 81 |
+
self.model = SequenceModel(
|
| 82 |
+
vocab_size=vocab_size,
|
| 83 |
+
d_model=d_model,
|
| 84 |
+
n_layers=n_layers,
|
| 85 |
+
max_len=max_len,
|
| 86 |
+
pos_encoder=self.pe,
|
| 87 |
+
rope=self.rope,
|
| 88 |
+
spatial_args={"causal": False, "n_heads": n_heads, "rope": self.rope},
|
| 89 |
+
channel_args={"expand": ff_expand, "activation": ACTIVS[activation], "depth": 2}
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
def forward(self, x):
|
| 93 |
+
return self.model(x)
|
| 94 |
+
|
| 95 |
+
def __repr__(self):
|
| 96 |
+
return self.model.__repr__()
|
| 97 |
+
|
| 98 |
+
def __str__(self):
|
| 99 |
+
return self.model.__str__()
|
| 100 |
+
|
| 101 |
+
@torch.no_grad()
|
| 102 |
+
def generate(self, *args, **kwargs):
|
| 103 |
+
raise NotImplementedError("Yet to Implement")
|
| 104 |
+
|
| 105 |
+
def summary(self):
|
| 106 |
+
self.model.summary()
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
class DeiTTransformer(nn.Module):
|
| 110 |
+
def __init__(self, n_class, img_size, patch_size, d_model, n_layers, n_heads, in_chans=3, ff_expand=2,
|
| 111 |
+
activation='gelu', dropout=0.1,
|
| 112 |
+
pe='sinu'):
|
| 113 |
+
super().__init__()
|
| 114 |
+
self.img_size = img_size
|
| 115 |
+
self.n_class = n_class
|
| 116 |
+
self.d_model = d_model
|
| 117 |
+
self.n_layers = n_layers
|
| 118 |
+
self.n_heads = n_heads
|
| 119 |
+
self.ff_expand = ff_expand
|
| 120 |
+
self.activation = activation
|
| 121 |
+
self.dropout = dropout
|
| 122 |
+
if pe == 'sinu':
|
| 123 |
+
self.pe = True
|
| 124 |
+
self.rope = False
|
| 125 |
+
elif pe == 'rope':
|
| 126 |
+
self.pe = False
|
| 127 |
+
self.rope = True
|
| 128 |
+
elif self.pe == 'nope':
|
| 129 |
+
self.pe = False
|
| 130 |
+
else:
|
| 131 |
+
raise ValueError('pe={} not recognized'.format(pe))
|
| 132 |
+
|
| 133 |
+
self.model = SequenceModel(
|
| 134 |
+
vocab_size=n_class,
|
| 135 |
+
d_model=d_model,
|
| 136 |
+
n_layers=n_layers,
|
| 137 |
+
pos_encoder=self.pe,
|
| 138 |
+
rope=self.rope,
|
| 139 |
+
embedder=PatchEmbedder,
|
| 140 |
+
embedder_args={"d_model": d_model, "img_size": img_size, "patch_size": patch_size, "in_chans": in_chans},
|
| 141 |
+
unembedder=ClsHead,
|
| 142 |
+
spatial_args={"causal": False, "n_heads": n_heads, "rope": self.rope},
|
| 143 |
+
channel_args={"expand": ff_expand, "activation": ACTIVS[activation], "depth": 2}
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
def forward(self, x):
|
| 147 |
+
return self.model(x)
|
| 148 |
+
|
| 149 |
+
def __repr__(self):
|
| 150 |
+
return self.model.__repr__()
|
| 151 |
+
|
| 152 |
+
def __str__(self):
|
| 153 |
+
return self.model.__str__()
|
| 154 |
+
|
| 155 |
+
@torch.no_grad()
|
| 156 |
+
def generate(self, *args, **kwargs):
|
| 157 |
+
raise NotImplementedError("Yet to Implement")
|
| 158 |
+
|
| 159 |
+
def summary(self):
|
| 160 |
+
self.model.summary()
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
class ViTTTransformer(nn.Module):
|
| 164 |
+
def __init__(self, n_class, img_size, patch_size, d_model, n_layers, n_heads, in_chans=3, ff_expand=2,
|
| 165 |
+
activation='gelu', dropout=0.1,
|
| 166 |
+
pe='sinu'):
|
| 167 |
+
super().__init__()
|
| 168 |
+
self.img_size = img_size
|
| 169 |
+
self.n_class = n_class
|
| 170 |
+
self.d_model = d_model
|
| 171 |
+
self.n_layers = n_layers
|
| 172 |
+
self.n_heads = n_heads
|
| 173 |
+
self.ff_expand = ff_expand
|
| 174 |
+
self.activation = activation
|
| 175 |
+
self.dropout = dropout
|
| 176 |
+
if pe == 'sinu':
|
| 177 |
+
self.pe = True
|
| 178 |
+
self.rope = False
|
| 179 |
+
elif pe == 'rope':
|
| 180 |
+
self.pe = False
|
| 181 |
+
self.rope = True
|
| 182 |
+
elif self.pe == 'nope':
|
| 183 |
+
self.pe = False
|
| 184 |
+
else:
|
| 185 |
+
raise ValueError('pe={} not recognized'.format(pe))
|
| 186 |
+
|
| 187 |
+
self.model = SequenceModel(
|
| 188 |
+
vocab_size=n_class,
|
| 189 |
+
d_model=d_model,
|
| 190 |
+
n_layers=n_layers,
|
| 191 |
+
pos_encoder=self.pe,
|
| 192 |
+
rope=self.rope,
|
| 193 |
+
embedder=PatchEmbedder,
|
| 194 |
+
embedder_args={"d_model": d_model, "img_size": img_size, "patch_size": patch_size, "in_chans": in_chans},
|
| 195 |
+
unembedder=MeanClassificationHead,
|
| 196 |
+
spatial_args={"causal": False, "n_heads": n_heads, "rope": self.rope},
|
| 197 |
+
channel_args={"expand": ff_expand, "activation": ACTIVS[activation], "depth": 2}
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
def forward(self, x):
|
| 201 |
+
return self.model(x)
|
| 202 |
+
|
| 203 |
+
def __repr__(self):
|
| 204 |
+
return self.model.__repr__()
|
| 205 |
+
|
| 206 |
+
def __str__(self):
|
| 207 |
+
return self.model.__str__()
|
| 208 |
+
|
| 209 |
+
@torch.no_grad()
|
| 210 |
+
def generate(self, *args, **kwargs):
|
| 211 |
+
raise NotImplementedError("Yet to Implement")
|
| 212 |
+
|
| 213 |
+
def summary(self):
|
| 214 |
+
self.model.summary()
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
class DiTTransformer(nn.Module):
|
| 218 |
+
def __init__(self, d_model, n_layers, n_heads, ff_expand=2, activation='gelu', dropout=0.1,
|
| 219 |
+
pe='sinu'):
|
| 220 |
+
super().__init__()
|
| 221 |
+
self.d_model = d_model
|
| 222 |
+
self.n_layers = n_layers
|
| 223 |
+
self.n_heads = n_heads
|
| 224 |
+
self.ff_expand = ff_expand
|
| 225 |
+
self.activation = activation
|
| 226 |
+
self.dropout = dropout
|
| 227 |
+
if pe == 'sinu':
|
| 228 |
+
self.pe = True
|
| 229 |
+
self.rope = False
|
| 230 |
+
elif pe == 'rope':
|
| 231 |
+
self.pe = False
|
| 232 |
+
self.rope = True
|
| 233 |
+
elif self.pe == 'nope':
|
| 234 |
+
self.pe = False
|
| 235 |
+
else:
|
| 236 |
+
raise ValueError('pe={} not recognized'.format(pe))
|
| 237 |
+
|
| 238 |
+
self.model = SequenceModel(
|
| 239 |
+
vocab_size=2,
|
| 240 |
+
d_model=d_model,
|
| 241 |
+
n_layers=n_layers,
|
| 242 |
+
pos_encoder=self.pe,
|
| 243 |
+
rope=self.rope,
|
| 244 |
+
embedder=Identity,
|
| 245 |
+
unembedder=Identity,
|
| 246 |
+
spatial_args={"causal": False, "n_heads": n_heads, "rope": self.rope},
|
| 247 |
+
channel_args={"expand": ff_expand, "activation": ACTIVS[activation], "depth": 2}
|
| 248 |
+
)
|
| 249 |
+
|
| 250 |
+
def forward(self, x):
|
| 251 |
+
return self.model(x)
|
| 252 |
+
|
| 253 |
+
def __repr__(self):
|
| 254 |
+
return self.model.__repr__()
|
| 255 |
+
|
| 256 |
+
def __str__(self):
|
| 257 |
+
return self.model.__str__()
|
| 258 |
+
|
| 259 |
+
@torch.no_grad()
|
| 260 |
+
def generate(self, *args, **kwargs):
|
| 261 |
+
raise NotImplementedError("Yet to Implement")
|
| 262 |
+
|
| 263 |
+
def summary(self):
|
| 264 |
+
self.model.summary()
|
vathos/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from Vathos.blocks import *
|
vathos/_analytics.py
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Vathos Analytics & Interpretability Toolkit
|
| 3 |
+
===========================================
|
| 4 |
+
Modulo standalone per l'analisi spettrale, la topologia dell'attenzione e
|
| 5 |
+
l'interpretazione semantica dei concetti (FFN) nei modelli ModdedFormer.
|
| 6 |
+
|
| 7 |
+
Progettato per Jupyter Notebooks e script Python off-Streamlit.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
import numpy as np
|
| 13 |
+
import matplotlib.pyplot as plt
|
| 14 |
+
from typing import Optional, Dict, List, Tuple, Any
|
| 15 |
+
import math
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
# ──────────────────────────────────────────────────────────────────────────────
|
| 19 |
+
# 1. ANALISI MATEMATICA E SPETTRALE (Core)
|
| 20 |
+
# ──────────────────────────────────────────────────────────────────────────────
|
| 21 |
+
|
| 22 |
+
def compute_tensor_stats(t: torch.Tensor) -> dict:
|
| 23 |
+
"""Calcola i momenti statistici e la sparsità di un tensore o manifold."""
|
| 24 |
+
f = t.detach().float().cpu()
|
| 25 |
+
v = f.numpy().flatten()
|
| 26 |
+
return {
|
| 27 |
+
"shape": tuple(t.shape),
|
| 28 |
+
"numel": t.numel(),
|
| 29 |
+
"mean": float(v.mean()),
|
| 30 |
+
"std": float(v.std()),
|
| 31 |
+
"min": float(v.min()),
|
| 32 |
+
"max": float(v.max()),
|
| 33 |
+
"l2_norm": float(np.linalg.norm(v)),
|
| 34 |
+
"sparsity": float((np.abs(v) < 1e-6).mean())
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def compute_svd_spectrum(t: torch.Tensor) -> Optional[np.ndarray]:
|
| 39 |
+
"""
|
| 40 |
+
Decomposizione in Valori Singolari (SVD) per studiare il collasso dimensionale.
|
| 41 |
+
Ritorna i valori singolari sigma. Se il tensore è 3D (es. Head), media gli spettri.
|
| 42 |
+
"""
|
| 43 |
+
f = t.detach().float().cpu()
|
| 44 |
+
if f.ndim == 2:
|
| 45 |
+
try:
|
| 46 |
+
return torch.linalg.svdvals(f).numpy()
|
| 47 |
+
except Exception:
|
| 48 |
+
return None
|
| 49 |
+
if f.ndim == 3:
|
| 50 |
+
results = []
|
| 51 |
+
for i in range(f.shape[0]):
|
| 52 |
+
try:
|
| 53 |
+
results.append(torch.linalg.svdvals(f[i]).numpy())
|
| 54 |
+
except Exception:
|
| 55 |
+
pass
|
| 56 |
+
return np.stack(results) if results else None
|
| 57 |
+
return None
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def compute_shannon_entropy(attn_weights: np.ndarray) -> List[float]:
|
| 61 |
+
"""
|
| 62 |
+
Calcola l'Entropia di Shannon E[H] lungo l'asse delle Key per ogni Head.
|
| 63 |
+
H = - sum(p * log(p)). Valori bassi indicano forte 'certezza' (es. Induction Heads).
|
| 64 |
+
"""
|
| 65 |
+
w_calc = attn_weights if attn_weights.ndim == 3 else attn_weights[np.newaxis]
|
| 66 |
+
entropies = []
|
| 67 |
+
for h in range(w_calc.shape[0]):
|
| 68 |
+
wh = w_calc[h] + 1e-12 # Epsilon per stabilità numerica nel log
|
| 69 |
+
ent = -(wh * np.log(wh)).sum(axis=-1).mean()
|
| 70 |
+
entropies.append(float(ent))
|
| 71 |
+
return entropies
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
# ──────────────────────────────────────────────────────────────────────────────
|
| 75 |
+
# 2. ESTRAZIONE TOPOLOGICA (Hooks & Forward Passes)
|
| 76 |
+
# ──────────────────────────────────────────────────────────────────────────────
|
| 77 |
+
|
| 78 |
+
def get_attention_matrix(mixer: nn.Module, x: torch.Tensor) -> Optional[np.ndarray]:
|
| 79 |
+
"""Ricalcola rigorosamente Softmax(QK^T / sqrt(d)) per estrarre le matrici d'attenzione."""
|
| 80 |
+
if hasattr(mixer, "get_attention_weights"):
|
| 81 |
+
with torch.no_grad():
|
| 82 |
+
return mixer.get_attention_weights(x).detach().float().cpu().numpy()
|
| 83 |
+
|
| 84 |
+
try:
|
| 85 |
+
with torch.no_grad():
|
| 86 |
+
B, L, D = x.shape
|
| 87 |
+
if hasattr(mixer, "qkv"):
|
| 88 |
+
proj = mixer.qkv
|
| 89 |
+
n_heads = mixer.n_heads
|
| 90 |
+
head_dim = mixer.head_dim
|
| 91 |
+
qkv = proj(x).view(B, L, 3, n_heads, head_dim)
|
| 92 |
+
q, k, _ = qkv.unbind(dim=2)
|
| 93 |
+
q, k = q.transpose(1, 2).float(), k.transpose(1, 2).float()
|
| 94 |
+
scale = math.sqrt(head_dim)
|
| 95 |
+
scores = torch.matmul(q, k.transpose(-2, -1)) / scale
|
| 96 |
+
if getattr(mixer, "causal", True):
|
| 97 |
+
mask = torch.tril(torch.ones(L, L, device=x.device)).bool()
|
| 98 |
+
scores = scores.masked_fill(~mask, float("-inf"))
|
| 99 |
+
return torch.softmax(scores, dim=-1)[0].cpu().numpy()
|
| 100 |
+
elif hasattr(mixer, "qk"):
|
| 101 |
+
proj = mixer.qk
|
| 102 |
+
n_heads = mixer.n_heads
|
| 103 |
+
head_dim = mixer.head_dim
|
| 104 |
+
qk = proj(x).view(B, L, 2, n_heads, head_dim)
|
| 105 |
+
q, k = qk.unbind(dim=2)
|
| 106 |
+
q, k = q.transpose(1, 2).float(), k.transpose(1, 2).float()
|
| 107 |
+
scale = math.sqrt(head_dim)
|
| 108 |
+
scores = torch.matmul(q, k.transpose(-2, -1)) / scale
|
| 109 |
+
if getattr(mixer, "causal", True):
|
| 110 |
+
mask = torch.tril(torch.ones(L, L, device=x.device)).bool()
|
| 111 |
+
scores = scores.masked_fill(~mask, float("-inf"))
|
| 112 |
+
return torch.softmax(scores, dim=-1)[0].cpu().numpy()
|
| 113 |
+
except Exception as e:
|
| 114 |
+
print(f"Errore estrazione QK: {e}")
|
| 115 |
+
return None
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def get_ffn_manifolds(cm: nn.Module, x: torch.Tensor, tau: float = None) -> Optional[Dict[str, np.ndarray]]:
|
| 119 |
+
"""
|
| 120 |
+
Tratta il Channel Mixer come Memoria Associativa.
|
| 121 |
+
Ritorna Logits (XW), Attivazioni (Act(XW)) e Concept Attention.
|
| 122 |
+
"""
|
| 123 |
+
if tau is None:
|
| 124 |
+
tau = math.sqrt(x.shape[-1]) # Default temperature = sqrt(d_model)
|
| 125 |
+
|
| 126 |
+
try:
|
| 127 |
+
with torch.no_grad():
|
| 128 |
+
pre_act = cm.expand(x) # Y = XW_{expand}
|
| 129 |
+
post_act = cm.act(pre_act) if hasattr(cm, "act") else pre_act
|
| 130 |
+
concept_attn = torch.softmax(pre_act / tau, dim=-1)
|
| 131 |
+
|
| 132 |
+
return {
|
| 133 |
+
"pre_act": pre_act[0].cpu().numpy(), # [L, M_dim]
|
| 134 |
+
"post_act": post_act[0].cpu().numpy(), # [L, M_dim]
|
| 135 |
+
"concept_attn": concept_attn[0].cpu().numpy() # [L, M_dim]
|
| 136 |
+
}
|
| 137 |
+
except Exception as e:
|
| 138 |
+
print(f"Errore estrazione FFN Manifold: {e}")
|
| 139 |
+
return None
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def capture_layer_manifolds(model: nn.Module, input_ids: torch.Tensor, layer_idx: int) -> Tuple[
|
| 143 |
+
torch.Tensor, torch.Tensor]:
|
| 144 |
+
"""
|
| 145 |
+
Usa i forward hooks per intercettare i manifold esatti in ingresso
|
| 146 |
+
ai mixer Spaziali e di Canale per un dato strato L.
|
| 147 |
+
"""
|
| 148 |
+
block = model.blocks[layer_idx]
|
| 149 |
+
sm = block.spatial_mixer
|
| 150 |
+
cm = block.channel_mixer
|
| 151 |
+
|
| 152 |
+
captured = {"spatial_x": None, "channel_x": None}
|
| 153 |
+
|
| 154 |
+
def hook_spatial(m, i):
|
| 155 |
+
captured["spatial_x"] = i[0].detach()
|
| 156 |
+
|
| 157 |
+
def hook_channel(m, i):
|
| 158 |
+
captured["channel_x"] = i[0].detach()
|
| 159 |
+
|
| 160 |
+
h1 = sm.register_forward_pre_hook(hook_spatial)
|
| 161 |
+
h2 = cm.register_forward_pre_hook(hook_channel)
|
| 162 |
+
|
| 163 |
+
try:
|
| 164 |
+
model.eval()
|
| 165 |
+
with torch.no_grad():
|
| 166 |
+
model(input_ids)
|
| 167 |
+
finally:
|
| 168 |
+
h1.remove()
|
| 169 |
+
h2.remove()
|
| 170 |
+
|
| 171 |
+
return captured["spatial_x"], captured["channel_x"]
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
# ──────────────────────────────────────────────────────────────────────────────
|
| 175 |
+
# 3. FUNZIONI DI PLOTTING (Matplotlib Figure Generators)
|
| 176 |
+
# ──────────────────────────────────────────────────────────────────────────────
|
| 177 |
+
|
| 178 |
+
def set_dark_style():
|
| 179 |
+
"""Opzionale: Applica il tema scuro (stile Vathos) globale a Matplotlib."""
|
| 180 |
+
plt.rcParams.update({
|
| 181 |
+
"figure.facecolor": "#0D0D1A", "axes.facecolor": "#0D0D1A", "axes.edgecolor": "#252550",
|
| 182 |
+
"axes.labelcolor": "#C0C0E0", "xtick.color": "#9CA3AF", "ytick.color": "#9CA3AF",
|
| 183 |
+
"text.color": "#D8D8F0", "grid.color": "#1E1E3A", "grid.alpha": 0.6
|
| 184 |
+
})
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def plot_weight_distributions(tensors_dict: Dict[str, torch.Tensor], bins: int = 80) -> Tuple[plt.Figure, np.ndarray]:
|
| 188 |
+
n = len(tensors_dict)
|
| 189 |
+
cols = min(n, 3)
|
| 190 |
+
rows = math.ceil(n / cols)
|
| 191 |
+
fig, axes = plt.subplots(rows, cols, figsize=(5 * cols, 3.5 * rows))
|
| 192 |
+
if n == 1:
|
| 193 |
+
axes = np.array([[axes]])
|
| 194 |
+
elif rows == 1:
|
| 195 |
+
axes = axes.reshape(1, -1)
|
| 196 |
+
|
| 197 |
+
colors = ["#A78BFA", "#60A5FA", "#34D399", "#F472B6", "#FB923C", "#FBBF24"]
|
| 198 |
+
|
| 199 |
+
for idx, (name, t) in enumerate(tensors_dict.items()):
|
| 200 |
+
ax = axes[idx // cols][idx % cols]
|
| 201 |
+
v = t.detach().float().cpu().numpy().flatten()
|
| 202 |
+
stats = compute_tensor_stats(t)
|
| 203 |
+
ax.hist(v, bins=bins, color=colors[idx % len(colors)], alpha=0.8, linewidth=0)
|
| 204 |
+
ax.set_title(f"{name}\n$\mu$={stats['mean']:.3e} $\sigma$={stats['std']:.3e}", fontsize=9)
|
| 205 |
+
ax.grid(True, ls="--", alpha=0.4)
|
| 206 |
+
|
| 207 |
+
for idx in range(n, rows * cols): axes[idx // cols][idx % cols].set_visible(False)
|
| 208 |
+
fig.suptitle("Distribuzioni di Densità", fontsize=12)
|
| 209 |
+
fig.tight_layout()
|
| 210 |
+
return fig, axes
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def plot_svd_spectra(tensors_dict: Dict[str, torch.Tensor], log_scale: bool = True) -> Tuple[plt.Figure, np.ndarray]:
|
| 214 |
+
eligible = {k: v for k, v in tensors_dict.items() if v.ndim >= 2}
|
| 215 |
+
n = len(eligible)
|
| 216 |
+
if n == 0: raise ValueError("Nessun tensore 2D+ fornito per la SVD.")
|
| 217 |
+
|
| 218 |
+
cols = min(n, 3)
|
| 219 |
+
rows = math.ceil(n / cols)
|
| 220 |
+
fig, axes = plt.subplots(rows, cols, figsize=(5 * cols, 3.5 * rows))
|
| 221 |
+
if n == 1:
|
| 222 |
+
axes = np.array([[axes]])
|
| 223 |
+
elif rows == 1:
|
| 224 |
+
axes = axes.reshape(1, -1)
|
| 225 |
+
|
| 226 |
+
colors = ["#A78BFA", "#60A5FA", "#34D399", "#F472B6", "#FB923C", "#FBBF24"]
|
| 227 |
+
|
| 228 |
+
for idx, (name, t) in enumerate(eligible.items()):
|
| 229 |
+
ax = axes[idx // cols][idx % cols]
|
| 230 |
+
sv = compute_svd_spectrum(t)
|
| 231 |
+
if sv is None: continue
|
| 232 |
+
|
| 233 |
+
color = colors[idx % len(colors)]
|
| 234 |
+
if sv.ndim == 2:
|
| 235 |
+
for h in range(sv.shape[0]):
|
| 236 |
+
ax.plot(sv[h], color=color, alpha=0.2, linewidth=0.8)
|
| 237 |
+
ax.plot(sv.mean(0), color=color, linewidth=2, label="Mean $\Sigma$")
|
| 238 |
+
ax.legend()
|
| 239 |
+
else:
|
| 240 |
+
ax.plot(sv, color=color, linewidth=1.5, marker=".", markersize=3)
|
| 241 |
+
|
| 242 |
+
cond = sv.flatten()[0] / (sv.flatten()[-1] + 1e-12)
|
| 243 |
+
r_eff = int((sv.flatten() > sv.flatten()[0] * 1e-3).sum())
|
| 244 |
+
ax.set_title(f"{name}\n$\kappa={cond:.1f}$ | Rango Effettivo $\\approx {r_eff}$", fontsize=9)
|
| 245 |
+
if log_scale: ax.set_yscale("log")
|
| 246 |
+
ax.grid(True, ls="--", which="both", alpha=0.4)
|
| 247 |
+
|
| 248 |
+
for idx in range(n, rows * cols): axes[idx // cols][idx % cols].set_visible(False)
|
| 249 |
+
fig.tight_layout()
|
| 250 |
+
return fig, axes
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def plot_attention_topology(attn_weights: np.ndarray, tokens: Optional[List[str]] = None) -> Tuple[
|
| 254 |
+
plt.Figure, np.ndarray]:
|
| 255 |
+
"""Plotta la matrice $L \times L$ per le varie Head d'attenzione."""
|
| 256 |
+
if attn_weights.ndim == 2: attn_weights = attn_weights[np.newaxis]
|
| 257 |
+
H, L_q, L_k = attn_weights.shape
|
| 258 |
+
cols = min(H, 4)
|
| 259 |
+
rows = math.ceil(H / cols)
|
| 260 |
+
|
| 261 |
+
cell_size = max(0.2, min(0.6, 12.0 / L_q)) if tokens else 0.5
|
| 262 |
+
fig_w = max(4 * cols, cell_size * L_q * cols)
|
| 263 |
+
fig_h = max(3.5 * rows, cell_size * L_q * rows + 1)
|
| 264 |
+
|
| 265 |
+
fig, axes = plt.subplots(rows, cols, figsize=(fig_w, fig_h))
|
| 266 |
+
axes = np.array(axes).flatten() if H > 1 else [axes]
|
| 267 |
+
|
| 268 |
+
clean_tokens = [t.replace('Ġ', '').replace(' ', '') for t in tokens] if tokens else None
|
| 269 |
+
|
| 270 |
+
for h in range(H):
|
| 271 |
+
ax = axes[h]
|
| 272 |
+
w = attn_weights[h]
|
| 273 |
+
im = ax.imshow(w, aspect="auto", cmap="magma", vmin=0, vmax=w.max() + 1e-9)
|
| 274 |
+
ax.set_title(f"Head {h}", fontsize=10)
|
| 275 |
+
|
| 276 |
+
if clean_tokens and len(clean_tokens) == L_q:
|
| 277 |
+
ax.set_xticks(range(L_k))
|
| 278 |
+
ax.set_yticks(range(L_q))
|
| 279 |
+
fs = max(5, min(10, int(300 / L_q)))
|
| 280 |
+
ax.set_xticklabels(clean_tokens, rotation=90, fontsize=fs)
|
| 281 |
+
ax.set_yticklabels(clean_tokens, fontsize=fs)
|
| 282 |
+
else:
|
| 283 |
+
ax.set_xlabel("Key Position (Source)")
|
| 284 |
+
ax.set_ylabel("Query Position (Target)")
|
| 285 |
+
|
| 286 |
+
fig.colorbar(im, ax=ax, shrink=0.8)
|
| 287 |
+
|
| 288 |
+
for h in range(H, len(axes)): axes[h].set_visible(False)
|
| 289 |
+
fig.tight_layout()
|
| 290 |
+
return fig, axes
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
def plot_ffn_concept_manifold(matrix: np.ndarray, title: str, tokens: Optional[List[str]] = None,
|
| 294 |
+
max_neurons: int = 128) -> Tuple[plt.Figure, plt.Axes]:
|
| 295 |
+
"""Plotta la mappa $L \times M$ di attivazione dei concetti per il Channel Mixer."""
|
| 296 |
+
mat_to_plot = matrix[:, :max_neurons]
|
| 297 |
+
rows_c, cols_c = mat_to_plot.shape
|
| 298 |
+
|
| 299 |
+
fig_w = max(8, cols_c * 0.15) if tokens else 10
|
| 300 |
+
fig_h = max(6, rows_c * 0.3) if tokens else 6
|
| 301 |
+
|
| 302 |
+
fig, ax = plt.subplots(figsize=(min(fig_w, 20), min(fig_h, 15)))
|
| 303 |
+
im = ax.imshow(mat_to_plot, aspect="auto", cmap="magma")
|
| 304 |
+
|
| 305 |
+
ax.set_title(f"{title} (Primi {cols_c} neuroni su {matrix.shape[1]})", pad=15)
|
| 306 |
+
ax.set_ylabel("Tokens (Queries)")
|
| 307 |
+
ax.set_xlabel("FFN Neurons (Latent Concepts)")
|
| 308 |
+
|
| 309 |
+
if tokens and len(tokens) == rows_c:
|
| 310 |
+
clean_tokens = [t.replace('Ġ', '').replace(' ', '') for t in tokens]
|
| 311 |
+
fs = max(5, min(10, int(400 / rows_c)))
|
| 312 |
+
ax.set_yticks(range(rows_c))
|
| 313 |
+
ax.set_yticklabels(clean_tokens, fontsize=fs)
|
| 314 |
+
|
| 315 |
+
fig.colorbar(im, ax=ax)
|
| 316 |
+
fig.tight_layout()
|
| 317 |
+
return fig, ax
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
def plot_architecture_scalars(model: nn.Module) -> Tuple[Optional[plt.Figure], Optional[plt.Figure]]:
|
| 321 |
+
"""Genera i plot per gli scalari strutturali: ZeroSkip e Skip-connection Lambdas."""
|
| 322 |
+
fig_skip, fig_zero = None, None
|
| 323 |
+
|
| 324 |
+
# 1. Skip Lambdas
|
| 325 |
+
if hasattr(model, "skip_lambdas") and model.skip_lambdas:
|
| 326 |
+
lambdas = {k: float(v.detach()) for k, v in model.skip_lambdas.items()}
|
| 327 |
+
fig_skip, ax = plt.subplots(figsize=(max(6, len(lambdas) * 1.4), 4))
|
| 328 |
+
keys, vals = list(lambdas.keys()), list(lambdas.values())
|
| 329 |
+
ax.bar(range(len(keys)), vals, alpha=0.85, color="#60A5FA")
|
| 330 |
+
ax.set_xticks(range(len(keys)))
|
| 331 |
+
ax.set_xticklabels([k.replace("route_", "").replace("_to_", "→") for k in keys], rotation=30, ha="right")
|
| 332 |
+
ax.axhline(0, color="gray", linewidth=0.8, ls="--")
|
| 333 |
+
ax.set_title("Skip-connection Routing Gates ($\lambda$)")
|
| 334 |
+
fig_skip.tight_layout()
|
| 335 |
+
|
| 336 |
+
# 2. ZeroSkip Parameters
|
| 337 |
+
if hasattr(model, "zeroskip_params") and model.zeroskip:
|
| 338 |
+
vals = [float(p.detach()) for p in model.zeroskip_params]
|
| 339 |
+
fig_zero, ax = plt.subplots(figsize=(max(6, len(vals) * 0.7), 4))
|
| 340 |
+
ax.plot(vals, marker="o", color="#FBBF24", linewidth=1.5)
|
| 341 |
+
ax.axhline(0, color="gray", linewidth=0.8, ls="--")
|
| 342 |
+
ax.set_xlabel("Layer Index")
|
| 343 |
+
ax.set_ylabel("ZeroSkip $\\alpha$")
|
| 344 |
+
ax.set_title("ZeroSkip Coefficients per Layer")
|
| 345 |
+
ax.grid(True, ls="--", alpha=0.4)
|
| 346 |
+
fig_zero.tight_layout()
|
| 347 |
+
|
| 348 |
+
return fig_skip, fig_zero
|
| 349 |
+
|
| 350 |
+
|
| 351 |
+
# ──────────────────────────────────────────────────────────────────────────────
|
| 352 |
+
# 4. HIGH-LEVEL API PER JUPYTER (Sintesi per Speedrun & ARC)
|
| 353 |
+
# ──────────────────────────────────────────────────────────────────────────────
|
| 354 |
+
|
| 355 |
+
def analyze_layer_semantics(model: nn.Module, tokenizer: Any, prompt: str, layer_idx: int,
|
| 356 |
+
add_bos: bool = True, ffn_tau: float = None,
|
| 357 |
+
show_plots: bool = True) -> Dict[str, Any]:
|
| 358 |
+
"""
|
| 359 |
+
Funzione master per i Notebook. Esegue l'analisi completa del layer indicato
|
| 360 |
+
su un prompt specifico, restituendo statistiche e plottando i grafici.
|
| 361 |
+
|
| 362 |
+
Uso:
|
| 363 |
+
results = analyze_layer_semantics(model, tokenizer, "Test sequence", layer_idx=4)
|
| 364 |
+
print("Sparsità FFN:", results["ffn_sparsity"])
|
| 365 |
+
"""
|
| 366 |
+
device = next(model.parameters()).device
|
| 367 |
+
full_prompt = (tokenizer.bos_token if add_bos and tokenizer.bos_token else "") + prompt
|
| 368 |
+
inputs = tokenizer(full_prompt, return_tensors="pt")
|
| 369 |
+
input_ids = inputs["input_ids"].to(device)
|
| 370 |
+
tokens = tokenizer.convert_ids_to_tokens(input_ids[0])
|
| 371 |
+
|
| 372 |
+
# 1. Forward Pass con intercettazione Manifold
|
| 373 |
+
sm_x, cm_x = capture_layer_manifolds(model, input_ids, layer_idx)
|
| 374 |
+
if sm_x is None:
|
| 375 |
+
raise RuntimeError(f"Hook fallito. Il layer {layer_idx} non è stato eseguito.")
|
| 376 |
+
|
| 377 |
+
block = model.blocks[layer_idx]
|
| 378 |
+
sm = block.spatial_mixer
|
| 379 |
+
cm = block.channel_mixer
|
| 380 |
+
|
| 381 |
+
d_model = model.d_models[layer_idx] if hasattr(model, "d_models") else sm_x.shape[-1]
|
| 382 |
+
if ffn_tau is None: ffn_tau = math.sqrt(d_model)
|
| 383 |
+
|
| 384 |
+
# 2. Estrazione Matrici
|
| 385 |
+
attn_w = get_attention_matrix(sm, sm_x)
|
| 386 |
+
ffn_manifolds = get_ffn_manifolds(cm, cm_x, tau=ffn_tau)
|
| 387 |
+
|
| 388 |
+
results = {
|
| 389 |
+
"tokens": tokens,
|
| 390 |
+
"X_spatial": sm_x,
|
| 391 |
+
"X_channel": cm_x,
|
| 392 |
+
"attention_matrix": attn_w,
|
| 393 |
+
"ffn_manifolds": ffn_manifolds,
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
# 3. Calcoli Spettrali e Statistiche (Information Theory)
|
| 397 |
+
if attn_w is not None:
|
| 398 |
+
results["attention_entropy"] = compute_shannon_entropy(attn_w)
|
| 399 |
+
|
| 400 |
+
if ffn_manifolds is not None:
|
| 401 |
+
Y_act = ffn_manifolds["post_act"]
|
| 402 |
+
stats = compute_tensor_stats(torch.tensor(Y_act))
|
| 403 |
+
results["ffn_sparsity"] = stats["sparsity"]
|
| 404 |
+
results["ffn_l2_norm"] = stats["l2_norm"]
|
| 405 |
+
|
| 406 |
+
try:
|
| 407 |
+
svd_vals = np.linalg.svd(Y_act, compute_uv=False)
|
| 408 |
+
results["ffn_eff_rank"] = int(np.sum(svd_vals > svd_vals[0] * 1e-3))
|
| 409 |
+
results["ffn_svd_spectrum"] = svd_vals
|
| 410 |
+
except np.linalg.LinAlgError:
|
| 411 |
+
pass
|
| 412 |
+
|
| 413 |
+
# 4. Rendering Opzionale
|
| 414 |
+
if show_plots:
|
| 415 |
+
print(f"--- ANALISI DEL MANIFOLD | LAYER {layer_idx} ---")
|
| 416 |
+
|
| 417 |
+
if attn_w is not None:
|
| 418 |
+
print("\n>> Topologia Spaziale dell'Attenzione (QK)")
|
| 419 |
+
fig_attn, _ = plot_attention_topology(attn_w, tokens)
|
| 420 |
+
plt.show()
|
| 421 |
+
print(f" E[H] (Entropia per Head): {[f'{e:.3f}' for e in results['attention_entropy']]}")
|
| 422 |
+
|
| 423 |
+
if ffn_manifolds is not None:
|
| 424 |
+
print("\n>> Channel Expansion (Memory Concepts)")
|
| 425 |
+
print(f" Sparsità (Soglia < 1e-6): {results.get('ffn_sparsity', 0):.2%}")
|
| 426 |
+
print(f" Rango Effettivo (SVD): {results.get('ffn_eff_rank', 'N/A')} su {Y_act.shape[1]} M_dim")
|
| 427 |
+
|
| 428 |
+
fig_ffn, _ = plot_ffn_concept_manifold(ffn_manifolds["post_act"], "FFN $Act(X W_{expand})$", tokens)
|
| 429 |
+
plt.show()
|
| 430 |
+
|
| 431 |
+
if "ffn_svd_spectrum" in results:
|
| 432 |
+
fig_svd, ax_svd = plt.subplots(figsize=(6, 3))
|
| 433 |
+
ax_svd.plot(results["ffn_svd_spectrum"], color="red", marker=".")
|
| 434 |
+
ax_svd.set_yscale("log")
|
| 435 |
+
ax_svd.set_title("Spettro $\Sigma$ del Manifold dei Concetti (FFN)")
|
| 436 |
+
ax_svd.grid(True, ls="--", alpha=0.5)
|
| 437 |
+
plt.show()
|
| 438 |
+
|
| 439 |
+
return results
|
vathos/_basics.py
ADDED
|
@@ -0,0 +1,1134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
This modules serves as base for the Torch Basic model Structure, and profilers.
|
| 3 |
+
Class Layer is the base of all Vathos layers, and ereditate from torch.nn.Module class, adding only the structures that help
|
| 4 |
+
profiling, visualization, and debugging.
|
| 5 |
+
Everything built with Layer is totally compatible with torch native modules.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import torch.nn as nn
|
| 10 |
+
from typing import Callable
|
| 11 |
+
from Vathos.functions import *
|
| 12 |
+
from timeit import default_timer as timer
|
| 13 |
+
from collections import OrderedDict, defaultdict
|
| 14 |
+
import re
|
| 15 |
+
import math
|
| 16 |
+
from torch import Tensor
|
| 17 |
+
|
| 18 |
+
ACTIVS = {
|
| 19 |
+
'tanh': nn.Tanh,
|
| 20 |
+
'sigmoid': nn.Sigmoid,
|
| 21 |
+
'relu': nn.ReLU,
|
| 22 |
+
'gelu': nn.GELU,
|
| 23 |
+
'elu': nn.ELU,
|
| 24 |
+
'lrelu': nn.LeakyReLU,
|
| 25 |
+
'leaky_relu': nn.LeakyReLU,
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class VathosConfig:
|
| 30 |
+
"""Global configuration state."""
|
| 31 |
+
_COMPILABLE = False # True = production, torch.compile safe
|
| 32 |
+
_PROFILE_BATCHED = True # True = times divided by batch size
|
| 33 |
+
_GLOBAL_PROFILE = True # True = always profile in debug mode
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def set_vathos_mode(mode: str):
|
| 37 |
+
"""
|
| 38 |
+
Switch Vathos mode globally.
|
| 39 |
+
|
| 40 |
+
'debug' — profiling enabled, torch.compile unfriendly
|
| 41 |
+
'production' — profiling disabled, torch.compile safe
|
| 42 |
+
"""
|
| 43 |
+
if mode.lower() == "production":
|
| 44 |
+
VathosConfig._COMPILABLE = True
|
| 45 |
+
print(f"{SEC}Vathos:{RES} Switched to {GOOD}PRODUCTION{RES} mode.")
|
| 46 |
+
elif mode.lower() == "debug":
|
| 47 |
+
VathosConfig._COMPILABLE = False
|
| 48 |
+
print(f"{SEC}Vathos:{RES} Switched to {NUM}DEBUG{RES} mode.")
|
| 49 |
+
else:
|
| 50 |
+
raise ValueError("Mode must be 'production' or 'debug'")
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
# =============================================================================
|
| 54 |
+
# BASE LAYER
|
| 55 |
+
# =============================================================================
|
| 56 |
+
|
| 57 |
+
class Layer(nn.Module):
|
| 58 |
+
"""
|
| 59 |
+
─────────────────────────────────────────────────────────────
|
| 60 |
+
class MyBlock(Layer):
|
| 61 |
+
def __init__(self, ...):
|
| 62 |
+
super().__init__()
|
| 63 |
+
# define sub-modules here
|
| 64 |
+
self.lin = nn.Linear(...)
|
| 65 |
+
self.attn = MultiheadAttentionMixer(...)
|
| 66 |
+
|
| 67 |
+
def forward(self, x):
|
| 68 |
+
z return self.lin(x)
|
| 69 |
+
─────────────────────────────────────────────────────────────
|
| 70 |
+
|
| 71 |
+
DO NOT override __call__. Profiling is handled via native forward hooks,
|
| 72 |
+
which are registered automatically in debug mode and completely absent
|
| 73 |
+
in production mode — zero overhead, full torch.compile compatibility.
|
| 74 |
+
|
| 75 |
+
IMPORTANT: gli hook sono registrati al __init__. Se il modello è costruito
|
| 76 |
+
PRIMA di chiamare set_vathos_mode('production'), gli hook sopravvivono.
|
| 77 |
+
Soluzioni:
|
| 78 |
+
1) Chiama set_vathos_mode('production') PRIMA di istanziare moduli.
|
| 79 |
+
2) Oppure chiama model.strip_profiling_hooks() dopo lo switch.
|
| 80 |
+
"""
|
| 81 |
+
|
| 82 |
+
_is_vathos_layer = True
|
| 83 |
+
|
| 84 |
+
def __init__(self):
|
| 85 |
+
super().__init__()
|
| 86 |
+
self.complexity = "O(1)"
|
| 87 |
+
self.__name__ = self.__class__.__name__
|
| 88 |
+
|
| 89 |
+
self._timer_unbatched = not VathosConfig._PROFILE_BATCHED
|
| 90 |
+
self._tstart = 0.0
|
| 91 |
+
self._tend = 0.0
|
| 92 |
+
self._time = 0.0
|
| 93 |
+
self._times = []
|
| 94 |
+
|
| 95 |
+
self._sublayers = None
|
| 96 |
+
|
| 97 |
+
# Hooks are registered ONCE at construction in debug mode.
|
| 98 |
+
# In production mode, no hooks exist — no overhead whatsoever.
|
| 99 |
+
if not VathosConfig._COMPILABLE and VathosConfig._GLOBAL_PROFILE:
|
| 100 |
+
self._register_profiling_hooks()
|
| 101 |
+
|
| 102 |
+
# -------------------------------------------------------------------------
|
| 103 |
+
# Profiling hooks — native nn.Module mechanism, __call__ is never touched
|
| 104 |
+
# -------------------------------------------------------------------------
|
| 105 |
+
|
| 106 |
+
def _register_profiling_hooks(self):
|
| 107 |
+
self.register_forward_pre_hook(self._pre_hook)
|
| 108 |
+
self.register_forward_hook(self._post_hook)
|
| 109 |
+
|
| 110 |
+
def _pre_hook(self, module, args):
|
| 111 |
+
self._tstart = timer()
|
| 112 |
+
|
| 113 |
+
def _post_hook(self, module, args, output):
|
| 114 |
+
self._tend = timer()
|
| 115 |
+
bs = 1
|
| 116 |
+
if args and isinstance(args[0], torch.Tensor):
|
| 117 |
+
bs = args[0].shape[0]
|
| 118 |
+
div = bs if not self._timer_unbatched else 1
|
| 119 |
+
self._time = (self._tend - self._tstart) / div
|
| 120 |
+
self._times.append(self._time)
|
| 121 |
+
|
| 122 |
+
# -------------------------------------------------------------------------
|
| 123 |
+
# Sublayer registry — used by profile()
|
| 124 |
+
# -------------------------------------------------------------------------
|
| 125 |
+
|
| 126 |
+
def register_sublayers(self):
|
| 127 |
+
if self._sublayers is None:
|
| 128 |
+
self._sublayers = dict()
|
| 129 |
+
|
| 130 |
+
def get_unique_name(base_name, existing_names):
|
| 131 |
+
if base_name not in existing_names:
|
| 132 |
+
return base_name
|
| 133 |
+
counter = 1
|
| 134 |
+
while f"{base_name}_{counter}" in existing_names:
|
| 135 |
+
counter += 1
|
| 136 |
+
return f"{base_name}_{counter}"
|
| 137 |
+
|
| 138 |
+
def collect_layers(module, level=0):
|
| 139 |
+
layers = []
|
| 140 |
+
for name, child in module.named_children():
|
| 141 |
+
if getattr(child, "_is_vathos_layer", False):
|
| 142 |
+
layers.append((name, child, level))
|
| 143 |
+
layers.extend(collect_layers(child, level=level + 1))
|
| 144 |
+
elif isinstance(child, nn.ModuleList):
|
| 145 |
+
for i, item in enumerate(child):
|
| 146 |
+
if getattr(item, "_is_vathos_layer", False):
|
| 147 |
+
layers.append((f"{name}[{i}]", item, level))
|
| 148 |
+
layers.extend(collect_layers(item, level=level + 1))
|
| 149 |
+
elif isinstance(child, nn.Module):
|
| 150 |
+
layers.extend(collect_layers(child, level=level))
|
| 151 |
+
return layers
|
| 152 |
+
|
| 153 |
+
for original_name, layer, level in collect_layers(self):
|
| 154 |
+
class_name = getattr(layer, "__name__", type(layer).__name__)
|
| 155 |
+
unique_name = get_unique_name(class_name, self._sublayers.keys())
|
| 156 |
+
self._sublayers[unique_name] = {"layer": layer, "level": level}
|
| 157 |
+
|
| 158 |
+
# -------------------------------------------------------------------------
|
| 159 |
+
# Utilities
|
| 160 |
+
# -------------------------------------------------------------------------
|
| 161 |
+
|
| 162 |
+
def get_mean_execution_time(self) -> float:
|
| 163 |
+
return float(np.mean(self._times)) if self._times else 0.0
|
| 164 |
+
|
| 165 |
+
def strip_profiling_hooks(self):
|
| 166 |
+
"""Rimuove gli hook profile da questo modulo e da tutti i sotto-Layer.
|
| 167 |
+
|
| 168 |
+
Utile se il modello è stato istanziato in debug mode e poi si vuole
|
| 169 |
+
passare a production senza ricostruirlo. Idempotente.
|
| 170 |
+
"""
|
| 171 |
+
for module in self.modules():
|
| 172 |
+
if isinstance(module, Layer):
|
| 173 |
+
module._forward_pre_hooks.clear()
|
| 174 |
+
module._forward_hooks.clear()
|
| 175 |
+
return self
|
| 176 |
+
|
| 177 |
+
def has_custom_generate(self) -> bool:
|
| 178 |
+
return type(self).generate is not Layer.generate
|
| 179 |
+
|
| 180 |
+
def generate(self, *args, **kwargs):
|
| 181 |
+
"""Override in subclasses that support autoregressive generation."""
|
| 182 |
+
return None
|
| 183 |
+
|
| 184 |
+
def clear_times(self):
|
| 185 |
+
"""Reset profiling history."""
|
| 186 |
+
self._times.clear()
|
| 187 |
+
|
| 188 |
+
def profile(self, maxlevel=100, avg=False, plot=False, plot_level=1):
|
| 189 |
+
if VathosConfig._COMPILABLE:
|
| 190 |
+
print(f"{BAD}Cannot profile in PRODUCTION mode.{RES} Run set_vathos_mode('debug') first.")
|
| 191 |
+
return
|
| 192 |
+
|
| 193 |
+
batched = not self._timer_unbatched
|
| 194 |
+
print(
|
| 195 |
+
f"Layer {NUM}{self.__name__}{RES} Times Profile (batched: {GOOD if batched else BAD}{batched}{RES}) (averaged: {GOOD if avg else BAD}{avg}{RES}):")
|
| 196 |
+
|
| 197 |
+
grouped_layers = OrderedDict()
|
| 198 |
+
order_map = {}
|
| 199 |
+
order_counter = 0
|
| 200 |
+
|
| 201 |
+
if self._sublayers:
|
| 202 |
+
for sublayer_name, sublayer_info in self._sublayers.items():
|
| 203 |
+
layer = sublayer_info['layer']
|
| 204 |
+
level = sublayer_info['level']
|
| 205 |
+
if level >= maxlevel: continue
|
| 206 |
+
|
| 207 |
+
match = re.match(r'^(.+?)_(\d+)$', sublayer_name)
|
| 208 |
+
base_name = match.group(1) if match else sublayer_name
|
| 209 |
+
key = (base_name, level)
|
| 210 |
+
|
| 211 |
+
if key not in order_map:
|
| 212 |
+
order_map[key] = order_counter
|
| 213 |
+
order_counter += 1
|
| 214 |
+
grouped_layers[key] = []
|
| 215 |
+
grouped_layers[key].append((sublayer_name, layer))
|
| 216 |
+
|
| 217 |
+
if not avg:
|
| 218 |
+
if self._sublayers:
|
| 219 |
+
for sublayer_name, sublayer_info in self._sublayers.items():
|
| 220 |
+
if sublayer_info['level'] < maxlevel:
|
| 221 |
+
indent = " " * sublayer_info['level']
|
| 222 |
+
t = sublayer_info['layer'].get_mean_execution_time()
|
| 223 |
+
print(f"{indent}- {NUM}{sublayer_name}{RES}: {t * 1000:.2f}ms")
|
| 224 |
+
else:
|
| 225 |
+
for (base_name, level), layers_list in grouped_layers.items():
|
| 226 |
+
indent = " " * level
|
| 227 |
+
times = [l.get_mean_execution_time() for _, l in layers_list if len(l._times) > 0]
|
| 228 |
+
if times:
|
| 229 |
+
avg_time = np.mean(times)
|
| 230 |
+
print(
|
| 231 |
+
f"{indent}- {NUM}{base_name}_avg{RES}: {avg_time * 1000:.2f}ms {SEC}(x{len(layers_list)}){RES}")
|
| 232 |
+
else:
|
| 233 |
+
print(f"{indent}- {NUM}{base_name}_avg{RES}: no time recorded")
|
| 234 |
+
|
| 235 |
+
if plot:
|
| 236 |
+
try:
|
| 237 |
+
import matplotlib.pyplot as plt
|
| 238 |
+
level_layers = defaultdict(list)
|
| 239 |
+
|
| 240 |
+
for (base_name, level), layers_list in grouped_layers.items():
|
| 241 |
+
if level == plot_level:
|
| 242 |
+
for _, layer in layers_list:
|
| 243 |
+
if len(layer._times) > 0:
|
| 244 |
+
level_layers[base_name].append(np.mean(layer._times))
|
| 245 |
+
|
| 246 |
+
elif level < plot_level:
|
| 247 |
+
|
| 248 |
+
pass
|
| 249 |
+
|
| 250 |
+
if level_layers:
|
| 251 |
+
layer_times = {k: sum(v) for k, v in level_layers.items()}
|
| 252 |
+
sorted_layers = sorted(layer_times.items(), key=lambda x: x[1], reverse=True)
|
| 253 |
+
labels = [x[0] for x in sorted_layers]
|
| 254 |
+
times = [x[1] * 1000 for x in sorted_layers]
|
| 255 |
+
|
| 256 |
+
fig, ax = plt.subplots(figsize=(12, 8))
|
| 257 |
+
wedges, texts, autotexts = ax.pie(times, autopct='%1.1f%%', startangle=90)
|
| 258 |
+
ax.legend(wedges, [f'{l}: {t:.2f}ms' for l, t in zip(labels, times)],
|
| 259 |
+
loc="center left", bbox_to_anchor=(1, 0, 0.5, 1))
|
| 260 |
+
ax.set_title(f'{self.__name__} - Level {plot_level}')
|
| 261 |
+
plt.tight_layout()
|
| 262 |
+
plt.show()
|
| 263 |
+
else:
|
| 264 |
+
print(f"{SEC}No data for plot level {plot_level}{RES}")
|
| 265 |
+
except ImportError:
|
| 266 |
+
print(f"{BAD}Matplotlib missing{RES}")
|
| 267 |
+
|
| 268 |
+
def __repr__(self):
|
| 269 |
+
return f"{SEC}Vathos{RES}: " + super().__repr__()
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
class Builder:
|
| 273 |
+
def __init__(self, layer, **params):
|
| 274 |
+
self.layer = layer
|
| 275 |
+
self.params = params
|
| 276 |
+
for att in layer.__dict__:
|
| 277 |
+
setattr(self, att, getattr(layer, att))
|
| 278 |
+
|
| 279 |
+
def __call__(self, *args):
|
| 280 |
+
return self.layer(*args, **self.params)
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
class tWrapper(Layer):
|
| 284 |
+
__name__ = "tWrapper"
|
| 285 |
+
|
| 286 |
+
def __init__(self, module: nn.Module):
|
| 287 |
+
super(tWrapper, self).__init__()
|
| 288 |
+
self.module = module
|
| 289 |
+
|
| 290 |
+
def forward(self, *args, **kwargs):
|
| 291 |
+
self.module(*args, **kwargs)
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
class Identity(Layer):
|
| 295 |
+
__name__ = "Identity"
|
| 296 |
+
__complexity__ = "O(1)"
|
| 297 |
+
|
| 298 |
+
def __init__(self, *args, **kwargs):
|
| 299 |
+
super(Identity, self).__init__()
|
| 300 |
+
|
| 301 |
+
def forward(self, x):
|
| 302 |
+
return x
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
class Skip(Layer):
|
| 306 |
+
__name__ = "Skip"
|
| 307 |
+
__complexity__ = "O(1)"
|
| 308 |
+
|
| 309 |
+
def __init__(self, layer):
|
| 310 |
+
super(Skip, self).__init__()
|
| 311 |
+
self.layer = layer
|
| 312 |
+
|
| 313 |
+
def forward(self, x):
|
| 314 |
+
return self.layer(x) + x
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
class LearntSkip(Layer):
|
| 318 |
+
__name__ = "Skip"
|
| 319 |
+
__complexity__ = "O(1)"
|
| 320 |
+
|
| 321 |
+
def __init__(self, layer):
|
| 322 |
+
super().__init__()
|
| 323 |
+
self.layer = layer
|
| 324 |
+
self.w = nn.Parameter(torch.tensor([1.0, 1.0]), requires_grad=True)
|
| 325 |
+
|
| 326 |
+
def forward(self, x):
|
| 327 |
+
return self.layer(x) * self.w[0] + x * self.w[1]
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
class IdentityMixer(Layer):
|
| 331 |
+
__name__ = "Identity"
|
| 332 |
+
__complexity__ = "O(1)"
|
| 333 |
+
|
| 334 |
+
def __init__(self, d_model):
|
| 335 |
+
super(IdentityMixer, self).__init__()
|
| 336 |
+
self.d_model = d_model
|
| 337 |
+
|
| 338 |
+
def forward(self, x):
|
| 339 |
+
return x
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
class LPadder(Layer):
|
| 343 |
+
__name__ = "LPadder"
|
| 344 |
+
__complexity__ = "O(k d)"
|
| 345 |
+
|
| 346 |
+
def __init__(self, right=0, left=0, element=0):
|
| 347 |
+
super(LPadder, self).__init__()
|
| 348 |
+
self.right = right
|
| 349 |
+
self.left = left
|
| 350 |
+
self.element = element
|
| 351 |
+
|
| 352 |
+
def forward(self, x):
|
| 353 |
+
return F.pad(x, (0, 0, self.left, self.right), mode="constant", value=self.element)
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
class dPadder(Layer):
|
| 357 |
+
__name__ = "LPadder"
|
| 358 |
+
__complexity__ = "O(k L)"
|
| 359 |
+
|
| 360 |
+
def __init__(self, up, down=0, element=0):
|
| 361 |
+
super(dPadder, self).__init__()
|
| 362 |
+
self.right = up
|
| 363 |
+
self.left = down
|
| 364 |
+
self.element = element
|
| 365 |
+
|
| 366 |
+
def forward(self, x):
|
| 367 |
+
return F.pad(x, (self.left, self.right), mode="constant", value=self.element)
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
class LUnPadder(Layer):
|
| 371 |
+
__name__ = "LUnPadder"
|
| 372 |
+
__complexity__ = "O(k d)"
|
| 373 |
+
|
| 374 |
+
def __init__(self, right=0, left=0):
|
| 375 |
+
super().__init__()
|
| 376 |
+
self.right = right
|
| 377 |
+
self.left = left
|
| 378 |
+
|
| 379 |
+
def forward(self, x):
|
| 380 |
+
return x[:, self.left:-self.right, :]
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
class dUnPadder(Layer):
|
| 384 |
+
__name__ = "LUnPadder"
|
| 385 |
+
__complexity__ = "O(k d)"
|
| 386 |
+
|
| 387 |
+
def __init__(self, right=0, left=0):
|
| 388 |
+
super().__init__()
|
| 389 |
+
self.right = right
|
| 390 |
+
self.left = left
|
| 391 |
+
|
| 392 |
+
def forward(self, x):
|
| 393 |
+
return x[:, :, self.left:-self.right]
|
| 394 |
+
|
| 395 |
+
|
| 396 |
+
class Linear(Layer):
|
| 397 |
+
__name__ = 'Linear'
|
| 398 |
+
|
| 399 |
+
def __init__(self, input_dim, output_dim, bias=True, **kwargs):
|
| 400 |
+
super().__init__()
|
| 401 |
+
self.input_dim = input_dim
|
| 402 |
+
self.output_dim = output_dim
|
| 403 |
+
self.bias = bias
|
| 404 |
+
self.linear = nn.Linear(input_dim, output_dim, bias=bias)
|
| 405 |
+
|
| 406 |
+
def forward(self, x):
|
| 407 |
+
return self.linear(x)
|
| 408 |
+
|
| 409 |
+
|
| 410 |
+
class BottleneckRepeatedLinear(nn.Module):
|
| 411 |
+
def __init__(self, in_features, out_features, num_repeats, bias=False, scale_variance=False):
|
| 412 |
+
super().__init__()
|
| 413 |
+
|
| 414 |
+
assert out_features % num_repeats == 0, "out_features must be strictly divisible by num_repeats"
|
| 415 |
+
|
| 416 |
+
self.in_features = in_features
|
| 417 |
+
self.out_features = out_features
|
| 418 |
+
self.num_repeats = num_repeats
|
| 419 |
+
self.bottleneck_dim = out_features // num_repeats
|
| 420 |
+
self.scale_variance = scale_variance
|
| 421 |
+
|
| 422 |
+
self.weight = nn.Parameter(torch.Tensor(self.bottleneck_dim, in_features))
|
| 423 |
+
|
| 424 |
+
if bias:
|
| 425 |
+
self.bias = nn.Parameter(torch.Tensor(self.bottleneck_dim))
|
| 426 |
+
else:
|
| 427 |
+
self.register_parameter('bias', None)
|
| 428 |
+
|
| 429 |
+
self.init_parameters()
|
| 430 |
+
|
| 431 |
+
def init_parameters(self):
|
| 432 |
+
nn.init.kaiming_uniform_(self.weight, a=math.sqrt(5))
|
| 433 |
+
if self.bias is not None:
|
| 434 |
+
fan_in, _ = nn.init._calculate_fan_in_and_fan_out(self.weight)
|
| 435 |
+
bound = 1 / math.sqrt(fan_in) if fan_in > 0 else 0
|
| 436 |
+
nn.init.uniform_(self.bias, -bound, bound)
|
| 437 |
+
|
| 438 |
+
def forward(self, x):
|
| 439 |
+
y = F.linear(x, self.weight, self.bias)
|
| 440 |
+
|
| 441 |
+
repeat_shape = [1] * (y.dim() - 1) + [self.num_repeats]
|
| 442 |
+
|
| 443 |
+
out = y.repeat(*repeat_shape)
|
| 444 |
+
|
| 445 |
+
if self.scale_variance:
|
| 446 |
+
out = out / math.sqrt(self.num_repeats)
|
| 447 |
+
|
| 448 |
+
return out
|
| 449 |
+
|
| 450 |
+
|
| 451 |
+
class ProductLinear(Layer):
|
| 452 |
+
__name__ = 'Linear'
|
| 453 |
+
|
| 454 |
+
def __init__(self, input_dim, output_dim):
|
| 455 |
+
super().__init__()
|
| 456 |
+
self.input_dim = input_dim
|
| 457 |
+
self.output_dim = output_dim
|
| 458 |
+
self.weight1 = nn.Parameter(0.1 * torch.randn(output_dim, input_dim))
|
| 459 |
+
self.weight2 = nn.Parameter(torch.ones(output_dim, input_dim + 0.01 * torch.randn(output_dim, input_dim)))
|
| 460 |
+
|
| 461 |
+
def forward(self, x):
|
| 462 |
+
return F.linear(x, self.weight1 * self.weight2)
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
class DoubleLinear(nn.Module):
|
| 466 |
+
def __init__(self, in_features: int, out_features: int,
|
| 467 |
+
device=None, dtype=None):
|
| 468 |
+
factory_kwargs = {'device': device, 'dtype': dtype}
|
| 469 |
+
super().__init__()
|
| 470 |
+
|
| 471 |
+
self.in_features = in_features
|
| 472 |
+
self.out_features = out_features
|
| 473 |
+
|
| 474 |
+
self.weight1 = nn.Parameter(torch.empty((out_features, in_features), **factory_kwargs))
|
| 475 |
+
nn.init.xavier_uniform_(self.weight1)
|
| 476 |
+
|
| 477 |
+
self.weight2 = nn.Parameter(torch.empty((in_features, in_features), **factory_kwargs))
|
| 478 |
+
nn.init.xavier_uniform_(self.weight2)
|
| 479 |
+
|
| 480 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 481 |
+
W_eff = self.weight1 @ self.weight2
|
| 482 |
+
return F.linear(x, W_eff)
|
| 483 |
+
|
| 484 |
+
|
| 485 |
+
class LowRankLinear(Layer):
|
| 486 |
+
__name__ = 'Linear'
|
| 487 |
+
|
| 488 |
+
def __init__(self, input_dim, output_dim, rank=16, bias=False):
|
| 489 |
+
super().__init__()
|
| 490 |
+
self.input_dim = input_dim
|
| 491 |
+
self.output_dim = output_dim
|
| 492 |
+
self.bias = bias
|
| 493 |
+
self.l1 = nn.Linear(input_dim, rank, bias=bias)
|
| 494 |
+
self.l2 = nn.Linear(rank, output_dim, bias=bias)
|
| 495 |
+
|
| 496 |
+
def forward(self, x):
|
| 497 |
+
return self.l2(self.l1(x))
|
| 498 |
+
|
| 499 |
+
|
| 500 |
+
class UnbiasedLinear(Layer):
|
| 501 |
+
__name__ = "UnbiasedLinear"
|
| 502 |
+
__complexity__ = "O(L d^2)"
|
| 503 |
+
|
| 504 |
+
def __init__(self, input_features, output_features):
|
| 505 |
+
super(UnbiasedLinear, self).__init__()
|
| 506 |
+
self.linear = nn.Linear(input_features, output_features, bias=False)
|
| 507 |
+
|
| 508 |
+
def forward(self, x):
|
| 509 |
+
return self.linear(x)
|
| 510 |
+
|
| 511 |
+
|
| 512 |
+
class SwiGLU(Layer):
|
| 513 |
+
gated = True
|
| 514 |
+
__name__ = "SwiGLU"
|
| 515 |
+
__complexity__ = "O(L)"
|
| 516 |
+
|
| 517 |
+
def forward(self, x: torch.Tensor):
|
| 518 |
+
x, gate = x.chunk(2, dim=-1)
|
| 519 |
+
return x * F.silu(gate)
|
| 520 |
+
|
| 521 |
+
|
| 522 |
+
class ReLU2(Layer):
|
| 523 |
+
gated = False
|
| 524 |
+
__name__ = "ReLU^2"
|
| 525 |
+
__complexity__ = "O(L)"
|
| 526 |
+
|
| 527 |
+
def __init__(self):
|
| 528 |
+
super().__init__()
|
| 529 |
+
|
| 530 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 531 |
+
x_pos = F.relu(x)
|
| 532 |
+
return x_pos * x_pos
|
| 533 |
+
|
| 534 |
+
|
| 535 |
+
class LeakyReLU2(Layer):
|
| 536 |
+
gated = False
|
| 537 |
+
__name__ = "LeakyReLU^2"
|
| 538 |
+
__complexity__ = "O(L)"
|
| 539 |
+
|
| 540 |
+
def __init__(self):
|
| 541 |
+
super().__init__()
|
| 542 |
+
|
| 543 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 544 |
+
x_pos = F.leaky_relu(x, 0.5)
|
| 545 |
+
return x_pos * x_pos
|
| 546 |
+
|
| 547 |
+
|
| 548 |
+
class PSiLU2(Layer):
|
| 549 |
+
gated = False
|
| 550 |
+
__name__ = "SiLU^2"
|
| 551 |
+
__complexity__ = "O(L)"
|
| 552 |
+
|
| 553 |
+
def __init__(self):
|
| 554 |
+
super().__init__()
|
| 555 |
+
self.param = nn.Parameter(torch.tensor([5.0, 2.0]), requires_grad=True)
|
| 556 |
+
|
| 557 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 558 |
+
x_pos = self.param[0] * F.silu(x / self.param[1])
|
| 559 |
+
return x_pos * x_pos
|
| 560 |
+
|
| 561 |
+
|
| 562 |
+
class LeLU2(Layer):
|
| 563 |
+
gated = False
|
| 564 |
+
__name__ = "ReLU^2"
|
| 565 |
+
__complexity__ = "O(L)"
|
| 566 |
+
|
| 567 |
+
def __init__(self):
|
| 568 |
+
super().__init__()
|
| 569 |
+
self.a = nn.Parameter(torch.randn(1) - 0.05)
|
| 570 |
+
|
| 571 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 572 |
+
x_pos = F.relu(x - self.a)
|
| 573 |
+
return x_pos * x_pos
|
| 574 |
+
|
| 575 |
+
|
| 576 |
+
class PReLU2(Layer):
|
| 577 |
+
def __init__(self, num_parameters=1):
|
| 578 |
+
super().__init__()
|
| 579 |
+
self.alpha = nn.Parameter(torch.ones(num_parameters))
|
| 580 |
+
|
| 581 |
+
def forward(self, x: torch.Tensor):
|
| 582 |
+
return self.alpha * torch.square(F.relu(x))
|
| 583 |
+
|
| 584 |
+
|
| 585 |
+
class UDLPReLU2(Layer):
|
| 586 |
+
"""Unbiased Dual Layer Perceptron, Relu^2 Activation"""
|
| 587 |
+
|
| 588 |
+
def __init__(self, d_model, expand, dropout=0.075):
|
| 589 |
+
super().__init__()
|
| 590 |
+
self.expand = nn.Linear(d_model, d_model * expand, bias=False)
|
| 591 |
+
self.contract = nn.Linear(d_model * expand, d_model, bias=False)
|
| 592 |
+
self.dropout = nn.Dropout(dropout)
|
| 593 |
+
|
| 594 |
+
nn.init.kaiming_normal_(self.expand.weight, mode='fan_in', nonlinearity='relu')
|
| 595 |
+
nn.init.zeros_(self.contract.weight)
|
| 596 |
+
|
| 597 |
+
def forward(self, x):
|
| 598 |
+
return self.dropout(self.contract(torch.relu(self.expand(x)).square()))
|
| 599 |
+
|
| 600 |
+
|
| 601 |
+
class MLP(Layer):
|
| 602 |
+
__name__ = "MLP"
|
| 603 |
+
__complexity__ = "O(depth L d^2)"
|
| 604 |
+
|
| 605 |
+
def __init__(self, d_model: int, depth: int, expand: int, activation: Callable, dropout=0.1):
|
| 606 |
+
super().__init__()
|
| 607 |
+
hidden_dim = d_model * expand
|
| 608 |
+
self.d_model = d_model
|
| 609 |
+
self.depth = depth
|
| 610 |
+
self.expand = expand
|
| 611 |
+
self.activation = activation
|
| 612 |
+
self.dropout = nn.Dropout(dropout)
|
| 613 |
+
layers = []
|
| 614 |
+
for i in range(depth):
|
| 615 |
+
if i == 0:
|
| 616 |
+
in_dim = d_model
|
| 617 |
+
out_dim = hidden_dim
|
| 618 |
+
elif i == depth - 1:
|
| 619 |
+
in_dim = hidden_dim
|
| 620 |
+
out_dim = d_model
|
| 621 |
+
else:
|
| 622 |
+
in_dim = hidden_dim
|
| 623 |
+
out_dim = hidden_dim
|
| 624 |
+
|
| 625 |
+
if hasattr(activation, 'gated') and i < depth - 1:
|
| 626 |
+
out_dim = out_dim * 2
|
| 627 |
+
|
| 628 |
+
layers.append(nn.Linear(in_dim, out_dim, bias=True))
|
| 629 |
+
if i < depth - 1:
|
| 630 |
+
layers.append(activation())
|
| 631 |
+
|
| 632 |
+
self.layers = nn.Sequential(*layers)
|
| 633 |
+
|
| 634 |
+
def forward(self, x: torch.Tensor):
|
| 635 |
+
return self.dropout(self.layers(x))
|
| 636 |
+
|
| 637 |
+
|
| 638 |
+
class DLPGelu(Layer):
|
| 639 |
+
def __init__(self, d_model, expand, dropout=0.1):
|
| 640 |
+
super().__init__()
|
| 641 |
+
self.expand = nn.Linear(d_model, d_model * expand, bias=True)
|
| 642 |
+
self.contract = nn.Linear(d_model * expand, d_model, bias=True)
|
| 643 |
+
self.dropout = nn.Dropout(dropout)
|
| 644 |
+
|
| 645 |
+
def forward(self, x):
|
| 646 |
+
return self.dropout(self.contract(F.gelu(self.expand(x))))
|
| 647 |
+
|
| 648 |
+
|
| 649 |
+
class DLPSoftmax(Layer):
|
| 650 |
+
def __init__(self, d_model, m, dropout=0.1, copy=False):
|
| 651 |
+
super().__init__()
|
| 652 |
+
self.expand = nn.Linear(d_model, m, bias=False)
|
| 653 |
+
self.q_proj = nn.Linear(d_model, d_model, bias=False)
|
| 654 |
+
self.contract = nn.Linear(m, d_model, bias=False)
|
| 655 |
+
|
| 656 |
+
self.scaler = nn.Parameter(torch.tensor([1 / math.sqrt(d_model)]))
|
| 657 |
+
|
| 658 |
+
self.dropout = nn.Dropout(dropout)
|
| 659 |
+
|
| 660 |
+
if copy:
|
| 661 |
+
with torch.no_grad():
|
| 662 |
+
self.contract.weight.copy_(self.expand.weight.t())
|
| 663 |
+
|
| 664 |
+
def forward(self, x):
|
| 665 |
+
q = self.q_proj(x)
|
| 666 |
+
logits = self.expand(q * self.scaler)
|
| 667 |
+
|
| 668 |
+
attn_weights = logits.softmax(dim=-1)
|
| 669 |
+
attn_weights = self.dropout(attn_weights)
|
| 670 |
+
|
| 671 |
+
return self.contract(attn_weights)
|
| 672 |
+
|
| 673 |
+
|
| 674 |
+
class FlashSDLP(Layer):
|
| 675 |
+
def __init__(self, d_model, m, num_heads, dropout=0.1, outproj=False):
|
| 676 |
+
super().__init__()
|
| 677 |
+
assert d_model % num_heads == 0, "d_model must be divisible by num_heads"
|
| 678 |
+
|
| 679 |
+
self.num_heads = num_heads
|
| 680 |
+
self.head_dim = d_model // num_heads
|
| 681 |
+
self.m = m
|
| 682 |
+
self.dropout_p = dropout
|
| 683 |
+
|
| 684 |
+
self.M1 = nn.Parameter(torch.randn(num_heads, m, self.head_dim))
|
| 685 |
+
self.M2 = nn.Parameter(torch.randn(num_heads, m, self.head_dim))
|
| 686 |
+
|
| 687 |
+
self.scaler = nn.Parameter(torch.tensor([1.0]))
|
| 688 |
+
|
| 689 |
+
if outproj:
|
| 690 |
+
self.out_proj = nn.Linear(d_model, d_model, bias=False)
|
| 691 |
+
else:
|
| 692 |
+
self.out_proj = nn.Identity()
|
| 693 |
+
|
| 694 |
+
self._reset_parameters()
|
| 695 |
+
|
| 696 |
+
def _reset_parameters(self):
|
| 697 |
+
nn.init.xavier_normal_(self.M1)
|
| 698 |
+
nn.init.xavier_normal_(self.M2)
|
| 699 |
+
|
| 700 |
+
if isinstance(self.out_proj, nn.Linear):
|
| 701 |
+
nn.init.xavier_uniform_(self.out_proj.weight)
|
| 702 |
+
|
| 703 |
+
def forward(self, x):
|
| 704 |
+
batch_size, seq_len, _ = x.shape
|
| 705 |
+
|
| 706 |
+
q = x.view(batch_size, seq_len, self.num_heads, self.head_dim).transpose(1, 2)
|
| 707 |
+
|
| 708 |
+
k = self.M1.unsqueeze(0).expand(batch_size, -1, -1, -1)
|
| 709 |
+
v = self.M2.unsqueeze(0).expand(batch_size, -1, -1, -1)
|
| 710 |
+
|
| 711 |
+
attn_output = F.scaled_dot_product_attention(
|
| 712 |
+
query=q * self.scaler,
|
| 713 |
+
key=k,
|
| 714 |
+
value=v,
|
| 715 |
+
dropout_p=self.dropout_p if self.training else 0.0,
|
| 716 |
+
is_causal=False
|
| 717 |
+
)
|
| 718 |
+
attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, seq_len, -1)
|
| 719 |
+
|
| 720 |
+
return self.out_proj(attn_output)
|
| 721 |
+
|
| 722 |
+
|
| 723 |
+
class DLPSwiGLU(Layer):
|
| 724 |
+
def __init__(self, d_model, expand, dropout=0.00):
|
| 725 |
+
super().__init__()
|
| 726 |
+
self.expand = nn.Linear(d_model, d_model * expand * 2, bias=True)
|
| 727 |
+
self.contract = nn.Linear(d_model * expand, d_model, bias=True)
|
| 728 |
+
self.activation = SwiGLU()
|
| 729 |
+
self.dropout = nn.Dropout(dropout)
|
| 730 |
+
|
| 731 |
+
def forward(self, x):
|
| 732 |
+
return self.dropout(self.contract(self.activation(self.expand(x))))
|
| 733 |
+
|
| 734 |
+
|
| 735 |
+
class LowRankGatedDLP(Layer):
|
| 736 |
+
def __init__(self, d_model, expand, dropout=0.00, activation=nn.SiLU, rank=64, gate_activation=None):
|
| 737 |
+
super().__init__()
|
| 738 |
+
self.ga = gate_activation if gate_activation is not None else Identity()
|
| 739 |
+
self.act = activation()
|
| 740 |
+
self.M = d_model * expand
|
| 741 |
+
self.rank = rank
|
| 742 |
+
self.expand = nn.Linear(d_model, d_model * expand + rank, bias=False)
|
| 743 |
+
self.contract = nn.Linear(d_model * expand, d_model, bias=False)
|
| 744 |
+
self.rexp = nn.Linear(rank, d_model * expand, bias=False)
|
| 745 |
+
self.dropout = nn.Dropout(dropout)
|
| 746 |
+
|
| 747 |
+
def forward(self, x):
|
| 748 |
+
out1, gate1 = self.expand(x).split([self.M, self.rank], dim=-1)
|
| 749 |
+
return self.contract(self.act(out1 * self.ga(self.rexp(gate1))))
|
| 750 |
+
|
| 751 |
+
|
| 752 |
+
class UDLPSwiGLU(Layer):
|
| 753 |
+
def __init__(self, d_model, expand, dropout=0.00):
|
| 754 |
+
super().__init__()
|
| 755 |
+
self.expand = nn.Linear(d_model, d_model * expand * 2, bias=False)
|
| 756 |
+
self.contract = nn.Linear(d_model * expand, d_model, bias=False)
|
| 757 |
+
self.activation = SwiGLU()
|
| 758 |
+
self.dropout = nn.Dropout(dropout)
|
| 759 |
+
|
| 760 |
+
def forward(self, x):
|
| 761 |
+
return self.dropout(self.contract(self.activation(self.expand(x))))
|
| 762 |
+
|
| 763 |
+
|
| 764 |
+
class VariableUDLP(Layer):
|
| 765 |
+
def __init__(self, d_model, d_output, M, activation=ReLU2, dropout=0.00):
|
| 766 |
+
super().__init__()
|
| 767 |
+
self.expand = nn.Linear(d_model, M, bias=False)
|
| 768 |
+
self.contract = nn.Linear(M, d_output, bias=False)
|
| 769 |
+
self.activation = activation()
|
| 770 |
+
self.dropout = nn.Dropout(dropout)
|
| 771 |
+
|
| 772 |
+
def _init_weights(self):
|
| 773 |
+
# Identity-at-init: expand orthogonal, contract → 0 ⇒ FFN contributo nullo.
|
| 774 |
+
torch.nn.init.orthogonal_(self.expand.weight)
|
| 775 |
+
torch.nn.init.zeros_(self.contract.weight)
|
| 776 |
+
|
| 777 |
+
def forward(self, x):
|
| 778 |
+
return self.dropout(self.contract(self.activation(self.expand(x))))
|
| 779 |
+
|
| 780 |
+
|
| 781 |
+
class VariableUDLP_Attention(Layer):
|
| 782 |
+
def __init__(self, d_model, d_output, M, activation=ReLU2, dropout=0.00, use_qk_norm=True):
|
| 783 |
+
super().__init__()
|
| 784 |
+
self.d_model = d_model
|
| 785 |
+
self.d_output = d_output
|
| 786 |
+
self.half_dim = d_model // 2
|
| 787 |
+
|
| 788 |
+
# Dimensione delle query/key. Per efficienza la tengo pari a half_dim
|
| 789 |
+
self.d_k = self.half_dim
|
| 790 |
+
|
| 791 |
+
# Q = X W_q
|
| 792 |
+
self.W_q = nn.Linear(self.half_dim, self.d_k, bias=False)
|
| 793 |
+
|
| 794 |
+
# K = W_k (Matrice di parametri appresi, forma: [M, d_k])
|
| 795 |
+
self.W_k = nn.Parameter(torch.empty(M, self.d_k))
|
| 796 |
+
torch.nn.init.normal_(self.W_k, std=self.d_k ** -0.5)
|
| 797 |
+
|
| 798 |
+
# V = W_v corrisponde al tuo 'contract'
|
| 799 |
+
self.contract = nn.Linear(M, d_output, bias=False)
|
| 800 |
+
|
| 801 |
+
self.activation = activation()
|
| 802 |
+
self.dropout = nn.Dropout(dropout)
|
| 803 |
+
|
| 804 |
+
# QK Norm
|
| 805 |
+
self.use_qk_norm = use_qk_norm
|
| 806 |
+
if self.use_qk_norm:
|
| 807 |
+
self.q_norm = nn.RMSNorm(self.d_k)
|
| 808 |
+
self.k_norm = nn.RMSNorm(self.d_k)
|
| 809 |
+
|
| 810 |
+
def _init_weights(self):
|
| 811 |
+
torch.nn.init.zeros_(self.contract.weight)
|
| 812 |
+
|
| 813 |
+
def forward(self, x):
|
| 814 |
+
# 1. Prendiamo solo mezza residual stream
|
| 815 |
+
x_half = x[..., :self.half_dim]
|
| 816 |
+
|
| 817 |
+
# 2. Calcoliamo le Query e prendiamo le Key
|
| 818 |
+
q = self.W_q(x_half)
|
| 819 |
+
k = self.W_k
|
| 820 |
+
|
| 821 |
+
# 3. QK Norm
|
| 822 |
+
if self.use_qk_norm:
|
| 823 |
+
q = self.q_norm(q)
|
| 824 |
+
k = self.k_norm(k)
|
| 825 |
+
|
| 826 |
+
# 4. Score dell'attention: Q K^T
|
| 827 |
+
# q è [..., d_k], k è [M, d_k]. F.linear(q, k) fa esattamente q @ k.T
|
| 828 |
+
scores = F.linear(q, k)
|
| 829 |
+
|
| 830 |
+
# 5. Attivazione generica (es. ReLU^2) e Dropout
|
| 831 |
+
h = self.dropout(self.activation(scores))
|
| 832 |
+
|
| 833 |
+
# 6. Proiezione V (contract) e padding automatico se d_output < d_model
|
| 834 |
+
out = self.contract(h)
|
| 835 |
+
|
| 836 |
+
if self.d_output < self.d_model:
|
| 837 |
+
out = F.pad(out, (0, self.d_model - self.d_output))
|
| 838 |
+
|
| 839 |
+
return out
|
| 840 |
+
|
| 841 |
+
|
| 842 |
+
class VariableUDLP_CrossGate(Layer):
|
| 843 |
+
def __init__(self, d_model, d_output, M, activation=ReLU2, dropout=0.00):
|
| 844 |
+
super().__init__()
|
| 845 |
+
self.d_model = d_model
|
| 846 |
+
self.d_output = d_output
|
| 847 |
+
self.half_dim = d_model // 2
|
| 848 |
+
|
| 849 |
+
self.expand_k = nn.Linear(self.half_dim, M, bias=False)
|
| 850 |
+
|
| 851 |
+
self.expand_g = nn.Linear(d_model - self.half_dim, M, bias=False)
|
| 852 |
+
|
| 853 |
+
self.contract = nn.Linear(M, d_output, bias=False)
|
| 854 |
+
|
| 855 |
+
self.activation = activation()
|
| 856 |
+
self.dropout = nn.Dropout(dropout)
|
| 857 |
+
|
| 858 |
+
def _init_weights(self):
|
| 859 |
+
torch.nn.init.zeros_(self.contract.weight)
|
| 860 |
+
|
| 861 |
+
def forward(self, x):
|
| 862 |
+
x_left = x[..., :self.half_dim]
|
| 863 |
+
x_right = x[..., self.half_dim:]
|
| 864 |
+
|
| 865 |
+
hk = self.expand_k(x_left)
|
| 866 |
+
hg = self.expand_g(x_right)
|
| 867 |
+
|
| 868 |
+
h = self.dropout(self.activation(hk) * hg)
|
| 869 |
+
|
| 870 |
+
out = self.contract(h)
|
| 871 |
+
|
| 872 |
+
return out
|
| 873 |
+
|
| 874 |
+
|
| 875 |
+
class VariableGatedUDLP(Layer):
|
| 876 |
+
"""UDLP + sparse output gate (parameter-golf / Modded-NanoGPT 1667 stile).
|
| 877 |
+
|
| 878 |
+
Gate sigmoid su prima slice di `gate_input_dim` del residual (sparse).
|
| 879 |
+
Output: `contract(activation(expand(x))) * sigmoid(gate_proj(x[..., :gate_input_dim]))`.
|
| 880 |
+
"""
|
| 881 |
+
__name__ = "VariableGatedUDLP"
|
| 882 |
+
|
| 883 |
+
def __init__(self, d_model, d_output, M, activation=ReLU2, dropout=0.00, gate_input_dim=12):
|
| 884 |
+
super().__init__()
|
| 885 |
+
self.gate_input_dim = gate_input_dim
|
| 886 |
+
self.expand = nn.Linear(d_model, M, bias=False)
|
| 887 |
+
self.contract = nn.Linear(M, d_output, bias=False)
|
| 888 |
+
self.gate_proj = nn.Linear(gate_input_dim, d_output, bias=False)
|
| 889 |
+
self.activation = activation()
|
| 890 |
+
self.dropout = nn.Dropout(dropout)
|
| 891 |
+
|
| 892 |
+
def _init_weights(self):
|
| 893 |
+
# Identity-at-init: expand + gate_proj orthogonal, contract → 0
|
| 894 |
+
# (con contract=0 il branch è zero indipendentemente dal gate).
|
| 895 |
+
torch.nn.init.orthogonal_(self.expand.weight)
|
| 896 |
+
torch.nn.init.zeros_(self.contract.weight)
|
| 897 |
+
torch.nn.init.orthogonal_(self.gate_proj.weight)
|
| 898 |
+
|
| 899 |
+
def forward(self, x):
|
| 900 |
+
out = self.contract(self.activation(self.expand(x)))
|
| 901 |
+
gate = torch.sigmoid(self.gate_proj(x[..., :self.gate_input_dim]))
|
| 902 |
+
return self.dropout(out * gate)
|
| 903 |
+
|
| 904 |
+
|
| 905 |
+
class DoubleUDLP(Layer):
|
| 906 |
+
def __init__(self, d_model, d_output, M, activation=ReLU2, dropout=0.00):
|
| 907 |
+
super().__init__()
|
| 908 |
+
self.expand = DoubleLinear(d_model, M)
|
| 909 |
+
self.contract = nn.Linear(M, d_output)
|
| 910 |
+
self.activation = activation()
|
| 911 |
+
self.dropout = nn.Dropout(dropout)
|
| 912 |
+
|
| 913 |
+
def _init_weights(self):
|
| 914 |
+
torch.nn.init.zeros_(self.contract.weight)
|
| 915 |
+
|
| 916 |
+
def forward(self, x):
|
| 917 |
+
return self.dropout(self.contract(self.activation(self.expand(x))))
|
| 918 |
+
|
| 919 |
+
|
| 920 |
+
class M_UDLP(Layer):
|
| 921 |
+
def __init__(self, d_model, d_output, M, activation=ReLU2, dropout=0.00):
|
| 922 |
+
super().__init__()
|
| 923 |
+
self.in_proj = nn.Linear(d_model, d_model)
|
| 924 |
+
self.expand = nn.Linear(d_model, M)
|
| 925 |
+
self.contract = nn.Linear(M, d_output)
|
| 926 |
+
self.activation = activation()
|
| 927 |
+
self.dropout = nn.Dropout(dropout)
|
| 928 |
+
|
| 929 |
+
def _init_weights(self):
|
| 930 |
+
torch.nn.init.zeros_(self.contract.weight)
|
| 931 |
+
|
| 932 |
+
def forward(self, x):
|
| 933 |
+
return self.dropout(self.contract(self.activation(self.expand(x))))
|
| 934 |
+
|
| 935 |
+
|
| 936 |
+
class F_UDLPSwiGLU(Layer):
|
| 937 |
+
def __init__(self, d_model, expand, dropout=0.05, lora_rank=16):
|
| 938 |
+
super().__init__()
|
| 939 |
+
self.expand = nn.Linear(d_model, d_model * expand * 2, bias=False)
|
| 940 |
+
self.contract = nn.Linear(d_model * expand, d_model, bias=False)
|
| 941 |
+
self.LoRA_expand = LowRankLinear(d_model, expand * d_model * 2, lora_rank)
|
| 942 |
+
self.LoRA_contract = LowRankLinear(d_model * expand, d_model, lora_rank)
|
| 943 |
+
self.scale = 1 / lora_rank * 2
|
| 944 |
+
self.finetuning = False
|
| 945 |
+
self.activation = SwiGLU()
|
| 946 |
+
self.dropout = nn.Dropout(dropout)
|
| 947 |
+
|
| 948 |
+
def forward(self, x):
|
| 949 |
+
if not self.finetuning:
|
| 950 |
+
return self.dropout(self.contract(self.activation(self.expand(x))))
|
| 951 |
+
else:
|
| 952 |
+
act = self.activation(self.expand(x) + self.LoRA_expand(x) * self.scale)
|
| 953 |
+
return self.dropout(
|
| 954 |
+
self.contract(act) + self.LoRA_contract(act) * self.scale
|
| 955 |
+
)
|
| 956 |
+
|
| 957 |
+
def finetune(self):
|
| 958 |
+
torch.nn.init.zeros_(self.LORA_expand.l2.weight)
|
| 959 |
+
torch.nn.init.zeros_(self.LoRA_contract.l2.weight)
|
| 960 |
+
self.expand.requires_grad_(False)
|
| 961 |
+
self.contract.requires_grad_(False)
|
| 962 |
+
self.finetuning = True
|
| 963 |
+
|
| 964 |
+
|
| 965 |
+
class ResMLPBlock(Layer):
|
| 966 |
+
def __init__(self, d_model, expand=2, norm=True, activation: Callable = nn.GELU):
|
| 967 |
+
super().__init__()
|
| 968 |
+
self.activation1 = activation()
|
| 969 |
+
self.activation2 = activation()
|
| 970 |
+
self.norm = norm
|
| 971 |
+
self.d_model = d_model
|
| 972 |
+
self.expand = expand
|
| 973 |
+
self.l1 = nn.Linear(d_model, d_model * expand, bias=True)
|
| 974 |
+
self.l2 = nn.Linear(d_model * expand, d_model, bias=True)
|
| 975 |
+
|
| 976 |
+
self.g1 = nn.Linear(d_model, d_model * expand, bias=True)
|
| 977 |
+
self.g2 = nn.Linear(d_model * expand, d_model, bias=True)
|
| 978 |
+
|
| 979 |
+
self.norm = nn.LayerNorm(d_model) if self.norm else nn.Identity()
|
| 980 |
+
|
| 981 |
+
def forward(self, x: torch.Tensor):
|
| 982 |
+
x = self.l2(self.activation1(self.l1(x)))
|
| 983 |
+
x = self.norm(x) + x
|
| 984 |
+
x = self.g2(self.activation2(self.g1(x)))
|
| 985 |
+
return x
|
| 986 |
+
|
| 987 |
+
|
| 988 |
+
class ResMLP(Layer):
|
| 989 |
+
def __init__(self, d_model: int, depth: int, expand: int, activation: Callable, dropout=0.1):
|
| 990 |
+
super().__init__()
|
| 991 |
+
self.d_model = d_model
|
| 992 |
+
self.depth = depth
|
| 993 |
+
self.expand = expand
|
| 994 |
+
self.activation = activation
|
| 995 |
+
self.dropout = nn.Dropout(dropout)
|
| 996 |
+
|
| 997 |
+
layers = []
|
| 998 |
+
for i in range(depth):
|
| 999 |
+
layers.append(ResMLPBlock(d_model, expand=expand, activation=activation))
|
| 1000 |
+
|
| 1001 |
+
self.layers = nn.Sequential(*layers)
|
| 1002 |
+
|
| 1003 |
+
def forward(self, x: torch.Tensor):
|
| 1004 |
+
return self.dropout(self.layers(x))
|
| 1005 |
+
|
| 1006 |
+
|
| 1007 |
+
class ConvResBlock(Layer):
|
| 1008 |
+
__complexity__ = "O(L k^2 in out)"
|
| 1009 |
+
|
| 1010 |
+
def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0, activation=nn.ReLU):
|
| 1011 |
+
super(ConvResBlock, self).__init__()
|
| 1012 |
+
self.bn = nn.BatchNorm2d(out_channels)
|
| 1013 |
+
self.conv1 = nn.Conv2d(in_channels, in_channels, kernel_size, padding='same')
|
| 1014 |
+
self.conv2 = nn.Conv2d(in_channels, in_channels, kernel_size, padding='same')
|
| 1015 |
+
self.convout = nn.Conv2d(in_channels, out_channels, kernel_size, stride=stride, padding=padding)
|
| 1016 |
+
self.activation = activation()
|
| 1017 |
+
|
| 1018 |
+
def forward(self, x):
|
| 1019 |
+
res = x
|
| 1020 |
+
x = self.bn(x)
|
| 1021 |
+
x = self.activation(self.conv1(x))
|
| 1022 |
+
x = self.conv2(x)
|
| 1023 |
+
x = x + res
|
| 1024 |
+
return self.convout(x)
|
| 1025 |
+
|
| 1026 |
+
|
| 1027 |
+
class RMSNorm(nn.Module):
|
| 1028 |
+
def __init__(self, d_model: int, eps: float = 1e-6):
|
| 1029 |
+
super().__init__()
|
| 1030 |
+
self.eps = eps
|
| 1031 |
+
self.weight = nn.Parameter(torch.ones(d_model))
|
| 1032 |
+
|
| 1033 |
+
def forward(self, x):
|
| 1034 |
+
input_dtype = x.dtype
|
| 1035 |
+
|
| 1036 |
+
x_fp32 = x.to(torch.float32)
|
| 1037 |
+
variance = x_fp32.pow(2).mean(dim=-1, keepdim=True)
|
| 1038 |
+
|
| 1039 |
+
x_rsqrt = torch.rsqrt(variance + self.eps)
|
| 1040 |
+
|
| 1041 |
+
return self.weight * (x_fp32 * x_rsqrt).to(input_dtype)
|
| 1042 |
+
|
| 1043 |
+
|
| 1044 |
+
class EMA:
|
| 1045 |
+
def __init__(self, model, decay=0.999):
|
| 1046 |
+
self.decay = decay
|
| 1047 |
+
self.shadow = {name: param.clone().detach() for name, param in model.named_parameters() if param.requires_grad}
|
| 1048 |
+
|
| 1049 |
+
@torch.no_grad()
|
| 1050 |
+
def update(self, model):
|
| 1051 |
+
for name, param in model.named_parameters():
|
| 1052 |
+
if param.requires_grad:
|
| 1053 |
+
self.shadow[name].lerp_(param, 1.0 - self.decay)
|
| 1054 |
+
|
| 1055 |
+
def apply_shadow(self, model):
|
| 1056 |
+
for name, param in model.named_parameters():
|
| 1057 |
+
if param.requires_grad:
|
| 1058 |
+
param.data.copy_(self.shadow[name])
|
| 1059 |
+
|
| 1060 |
+
|
| 1061 |
+
class Nova(Layer):
|
| 1062 |
+
def __init__(self):
|
| 1063 |
+
super().__init__()
|
| 1064 |
+
self.beta = nn.Parameter(torch.tensor([0.7]))
|
| 1065 |
+
|
| 1066 |
+
def forward(self, x):
|
| 1067 |
+
h = -self.beta * x
|
| 1068 |
+
return x * (torch.sigmoid(-h) - (1 / (1 + h ** 2)))
|
| 1069 |
+
|
| 1070 |
+
|
| 1071 |
+
class MinusNova(Layer):
|
| 1072 |
+
def __init__(self):
|
| 1073 |
+
super().__init__()
|
| 1074 |
+
self.beta = nn.Parameter(torch.tensor([0.7]))
|
| 1075 |
+
|
| 1076 |
+
def forward(self, x):
|
| 1077 |
+
h = -self.beta * -x
|
| 1078 |
+
return x * (torch.sigmoid(-h) - (1 / (1 + h ** 2)))
|
| 1079 |
+
|
| 1080 |
+
|
| 1081 |
+
class TaylorAct(nn.Module):
|
| 1082 |
+
def __init__(self, order=3):
|
| 1083 |
+
super().__init__()
|
| 1084 |
+
self.order = order
|
| 1085 |
+
self.coeffs = nn.Parameter(torch.randn(order + 1) * 0.02)
|
| 1086 |
+
|
| 1087 |
+
def forward(self, x):
|
| 1088 |
+
device = x.device
|
| 1089 |
+
exponents = torch.arange(self.order + 1, device=device, dtype=x.dtype)
|
| 1090 |
+
x_pow = x.unsqueeze(-1).pow(exponents)
|
| 1091 |
+
|
| 1092 |
+
return torch.matmul(x_pow, self.coeffs)
|
| 1093 |
+
|
| 1094 |
+
def plot(self, bounds=(-4, 4), n_points=200):
|
| 1095 |
+
x = torch.linspace(bounds[0], bounds[1], n_points)
|
| 1096 |
+
with torch.no_grad():
|
| 1097 |
+
y = self.forward(x)
|
| 1098 |
+
|
| 1099 |
+
plt.figure(figsize=(7, 4))
|
| 1100 |
+
plt.plot(x.numpy(), y.numpy(), label='TaylorAct', color='royalblue')
|
| 1101 |
+
plt.axhline(0, color='gray', linewidth=0.8, linestyle='--')
|
| 1102 |
+
plt.axvline(0, color='gray', linewidth=0.8, linestyle='--')
|
| 1103 |
+
plt.title('TaylorAct Activation Function')
|
| 1104 |
+
plt.xlabel('x')
|
| 1105 |
+
plt.ylabel('f(x)')
|
| 1106 |
+
plt.legend()
|
| 1107 |
+
plt.grid(True, alpha=0.3)
|
| 1108 |
+
plt.tight_layout()
|
| 1109 |
+
plt.show()
|
| 1110 |
+
|
| 1111 |
+
|
| 1112 |
+
class FastPoly3(nn.Module):
|
| 1113 |
+
def __init__(self, d_model: int):
|
| 1114 |
+
super().__init__()
|
| 1115 |
+
self.a0 = nn.Parameter(torch.zeros(d_model))
|
| 1116 |
+
self.a1 = nn.Parameter(torch.ones(d_model))
|
| 1117 |
+
self.a2 = nn.Parameter(torch.zeros(d_model))
|
| 1118 |
+
self.a3 = nn.Parameter(torch.zeros(d_model))
|
| 1119 |
+
|
| 1120 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 1121 |
+
return self.a0 + x * (self.a1 + x * (self.a2 + x * self.a3))
|
| 1122 |
+
|
| 1123 |
+
|
| 1124 |
+
class CastedLinear(nn.Linear):
|
| 1125 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 1126 |
+
bias = self.bias.to(x.dtype) if self.bias is not None else None
|
| 1127 |
+
return F.linear(x, self.weight.to(x.dtype), bias)
|
| 1128 |
+
|
| 1129 |
+
|
| 1130 |
+
if __name__ == '__main__':
|
| 1131 |
+
act = TaylorAct()
|
| 1132 |
+
act.plot() # default -4, 4
|
| 1133 |
+
act.plot(bounds=(-1, 1)) # custom bounds
|
| 1134 |
+
act.plot(bounds=(0, 1), n_points=100)
|
vathos/_experiments.py
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
import numpy as np
|
| 6 |
+
import matplotlib.pyplot as plt
|
| 7 |
+
import matplotlib.colors as mcolors
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class Experiment:
|
| 11 |
+
"""
|
| 12 |
+
Un tracker di esperimenti stile WandB per il framework Vathos.
|
| 13 |
+
Gestisce configurazioni, metriche, salvataggi e visualizzazioni avanzate.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
def __init__(self, project_name: str, exp_name: str = None, config: dict = None, root_dir: str = "runs"):
|
| 17 |
+
self.project_name = project_name
|
| 18 |
+
|
| 19 |
+
# Genera un nome univoco se non fornito
|
| 20 |
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 21 |
+
self.exp_name = exp_name if exp_name else f"run_{timestamp}"
|
| 22 |
+
self.config = config or {}
|
| 23 |
+
|
| 24 |
+
# Setup directory dell'esperimento
|
| 25 |
+
self.run_dir = Path(root_dir) / self.project_name / self.exp_name
|
| 26 |
+
self.run_dir.mkdir(parents=True, exist_ok=True)
|
| 27 |
+
|
| 28 |
+
# Dizionari per le metriche.
|
| 29 |
+
# Formato: {"loss": {"steps": [1,2,3], "values": [0.9, 0.8, 0.7]}}
|
| 30 |
+
self.metrics = {}
|
| 31 |
+
|
| 32 |
+
# Salva subito la configurazione
|
| 33 |
+
self._save_config()
|
| 34 |
+
print(f"🚀 Esperimento '{self.exp_name}' inizializzato in {self.run_dir}")
|
| 35 |
+
|
| 36 |
+
def log(self, metrics_dict: dict, step: int):
|
| 37 |
+
"""
|
| 38 |
+
Registra un dizionario di metriche per un dato step.
|
| 39 |
+
Es: exp.log({"train_loss": 0.5, "val_accuracy": 0.8}, step=100)
|
| 40 |
+
"""
|
| 41 |
+
for k, v in metrics_dict.items():
|
| 42 |
+
# Converte tensori PyTorch o Numpy in float standard
|
| 43 |
+
if hasattr(v, "item"):
|
| 44 |
+
v = v.item()
|
| 45 |
+
|
| 46 |
+
if k not in self.metrics:
|
| 47 |
+
self.metrics[k] = {"steps": [], "values": []}
|
| 48 |
+
|
| 49 |
+
self.metrics[k]["steps"].append(step)
|
| 50 |
+
self.metrics[k]["values"].append(v)
|
| 51 |
+
|
| 52 |
+
def _save_config(self):
|
| 53 |
+
with open(self.run_dir / "config.json", "w") as f:
|
| 54 |
+
json.dump(self.config, f, indent=4)
|
| 55 |
+
|
| 56 |
+
def save(self):
|
| 57 |
+
"""Salva tutte le metriche correnti in un file JSON."""
|
| 58 |
+
with open(self.run_dir / "metrics.json", "w") as f:
|
| 59 |
+
json.dump(self.metrics, f, indent=4)
|
| 60 |
+
# print(f"💾 Metriche salvate in {self.run_dir / 'metrics.json'}")
|
| 61 |
+
|
| 62 |
+
@classmethod
|
| 63 |
+
def load(cls, run_dir: str):
|
| 64 |
+
"""
|
| 65 |
+
Inizializza un oggetto Experiment a partire da una cartella salvata.
|
| 66 |
+
Permette di ricaricare e plottare run vecchie!
|
| 67 |
+
"""
|
| 68 |
+
run_path = Path(run_dir)
|
| 69 |
+
|
| 70 |
+
# Estrai project e exp name dal path (es. runs/Progetto/Run)
|
| 71 |
+
exp_name = run_path.name
|
| 72 |
+
project_name = run_path.parent.name
|
| 73 |
+
|
| 74 |
+
with open(run_path / "config.json", "r") as f:
|
| 75 |
+
config = json.load(f)
|
| 76 |
+
|
| 77 |
+
# Ricostruisce l'oggetto bypassando la creazione della cartella root
|
| 78 |
+
exp = cls(project_name=project_name, exp_name=exp_name, config=config, root_dir=run_path.parent.parent)
|
| 79 |
+
|
| 80 |
+
with open(run_path / "metrics.json", "r") as f:
|
| 81 |
+
exp.metrics = json.load(f)
|
| 82 |
+
|
| 83 |
+
print(f"📂 Esperimento '{exp_name}' caricato con successo.")
|
| 84 |
+
return exp
|
| 85 |
+
|
| 86 |
+
def _smooth_curve(self, points, factor=0.85):
|
| 87 |
+
"""Applica un Exponential Moving Average (EMA) per rendere i plot bellissimi."""
|
| 88 |
+
smoothed_points = []
|
| 89 |
+
for point in points:
|
| 90 |
+
if smoothed_points:
|
| 91 |
+
previous = smoothed_points[-1]
|
| 92 |
+
smoothed_points.append(previous * factor + point * (1 - factor))
|
| 93 |
+
else:
|
| 94 |
+
smoothed_points.append(point)
|
| 95 |
+
return smoothed_points
|
| 96 |
+
|
| 97 |
+
def plot(self, smoothing: float = 0.85, save_fig: bool = False, figsize=(10, 6)):
|
| 98 |
+
"""
|
| 99 |
+
Genera plot in stile accademico/WandB.
|
| 100 |
+
Mostra le curve originali in trasparenza e le curve smoothate in evidenza.
|
| 101 |
+
"""
|
| 102 |
+
if not self.metrics:
|
| 103 |
+
print("Nessuna metrica da plottare!")
|
| 104 |
+
return
|
| 105 |
+
|
| 106 |
+
# Stile estetico "stupendo" (simile a Seaborn/WandB)
|
| 107 |
+
plt.style.use('bmh') # Usa un tema base pulito integrato in matplotlib
|
| 108 |
+
|
| 109 |
+
num_metrics = len(self.metrics)
|
| 110 |
+
cols = 2 if num_metrics > 1 else 1
|
| 111 |
+
rows = (num_metrics + 1) // 2
|
| 112 |
+
|
| 113 |
+
fig, axes = plt.subplots(rows, cols, figsize=(figsize[0] * cols, figsize[1] * rows))
|
| 114 |
+
if num_metrics == 1:
|
| 115 |
+
axes = [axes]
|
| 116 |
+
else:
|
| 117 |
+
axes = axes.flatten()
|
| 118 |
+
|
| 119 |
+
# Colori moderni
|
| 120 |
+
colors = list(mcolors.TABLEAU_COLORS.values())
|
| 121 |
+
|
| 122 |
+
for idx, (metric_name, data) in enumerate(self.metrics.items()):
|
| 123 |
+
ax = axes[idx]
|
| 124 |
+
steps = data["steps"]
|
| 125 |
+
values = data["values"]
|
| 126 |
+
color = colors[idx % len(colors)]
|
| 127 |
+
|
| 128 |
+
# Plotta i dati raw (sbiaditi)
|
| 129 |
+
ax.plot(steps, values, color=color, alpha=0.2, label='Raw')
|
| 130 |
+
|
| 131 |
+
# Plotta i dati smoothati (in evidenza)
|
| 132 |
+
if smoothing > 0 and len(values) > 5:
|
| 133 |
+
smoothed = self._smooth_curve(values, factor=smoothing)
|
| 134 |
+
ax.plot(steps, smoothed, color=color, linewidth=2.5, label=f'Smoothed (α={smoothing})')
|
| 135 |
+
|
| 136 |
+
ax.set_title(metric_name.replace("_", " ").title(), fontsize=14, fontweight='bold')
|
| 137 |
+
ax.set_xlabel("Steps", fontsize=11)
|
| 138 |
+
ax.set_ylabel("Value", fontsize=11)
|
| 139 |
+
ax.grid(True, linestyle='--', alpha=0.6)
|
| 140 |
+
ax.legend(loc="best", frameon=True, shadow=True)
|
| 141 |
+
|
| 142 |
+
for idx in range(num_metrics, len(axes)):
|
| 143 |
+
fig.delaxes(axes[idx])
|
| 144 |
+
|
| 145 |
+
plt.suptitle(f"Experiment: {self.project_name} - {self.exp_name}", fontsize=16, y=1.02)
|
| 146 |
+
plt.tight_layout()
|
| 147 |
+
|
| 148 |
+
if save_fig:
|
| 149 |
+
fig_path = self.run_dir / "plots.png"
|
| 150 |
+
plt.savefig(fig_path, dpi=300, bbox_inches='tight')
|
| 151 |
+
print(f"📊 Plot salvato in {fig_path}")
|
| 152 |
+
|
| 153 |
+
plt.show()
|
| 154 |
+
|
| 155 |
+
|
vathos/_spatials.py
ADDED
|
@@ -0,0 +1,2124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from Vathos._basics import *
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class SinusoidalPositionalEncoding(Layer):
|
| 5 |
+
__name__ = "SinusoidalPositionalEncoding"
|
| 6 |
+
__complexity__ = "O(L^2 d^2)"
|
| 7 |
+
|
| 8 |
+
def __init__(self, d_model: int, max_len: int = 5000):
|
| 9 |
+
super().__init__()
|
| 10 |
+
self.d_model = d_model
|
| 11 |
+
|
| 12 |
+
position = torch.arange(max_len).unsqueeze(1)
|
| 13 |
+
div_term = torch.exp(torch.arange(0, d_model, 2) * (-math.log(10000.0) / d_model))
|
| 14 |
+
|
| 15 |
+
pe = torch.zeros(max_len, d_model)
|
| 16 |
+
pe[:, 0::2] = torch.sin(position * div_term)
|
| 17 |
+
pe[:, 1::2] = torch.cos(position * div_term)
|
| 18 |
+
|
| 19 |
+
self.register_buffer('pe', pe)
|
| 20 |
+
|
| 21 |
+
def forward(self, x: torch.Tensor):
|
| 22 |
+
B, L, D = x.shape
|
| 23 |
+
return x + self.pe[:L]
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class RoPE(Layer):
|
| 27 |
+
__name__ = "RoPE"
|
| 28 |
+
|
| 29 |
+
def __init__(self, dim: int, max_len: int = 8192, base: float = 10000.0):
|
| 30 |
+
super().__init__()
|
| 31 |
+
self.dim = dim
|
| 32 |
+
self.base = base
|
| 33 |
+
self.max_len = max_len
|
| 34 |
+
|
| 35 |
+
inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim))
|
| 36 |
+
self.register_buffer("inv_freq", inv_freq, persistent=False)
|
| 37 |
+
|
| 38 |
+
self._cos_cached = None
|
| 39 |
+
self._sin_cached = None
|
| 40 |
+
self._seq_len_cached = 0
|
| 41 |
+
|
| 42 |
+
def _update_cache(self, seq_len: int, dtype: torch.dtype, device: torch.device):
|
| 43 |
+
if seq_len > self._seq_len_cached or self._cos_cached is None:
|
| 44 |
+
self._seq_len_cached = seq_len
|
| 45 |
+
t = torch.arange(seq_len, device=device, dtype=dtype)
|
| 46 |
+
freqs = torch.outer(t, self.inv_freq.to(device))
|
| 47 |
+
|
| 48 |
+
self._cos_cached = freqs.cos()
|
| 49 |
+
self._sin_cached = freqs.sin()
|
| 50 |
+
|
| 51 |
+
def _apply_rotary_emb(self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor,
|
| 52 |
+
start_pos: int = 0) -> torch.Tensor:
|
| 53 |
+
"""Apply rotary embeddings starting from start_pos"""
|
| 54 |
+
seq_len = x.shape[-2]
|
| 55 |
+
|
| 56 |
+
cos = cos[start_pos:start_pos + seq_len]
|
| 57 |
+
sin = sin[start_pos:start_pos + seq_len]
|
| 58 |
+
|
| 59 |
+
shape = [1] * x.ndim
|
| 60 |
+
shape[-2] = seq_len
|
| 61 |
+
shape[-1] = self.dim // 2
|
| 62 |
+
|
| 63 |
+
cos = cos.view(*shape)
|
| 64 |
+
sin = sin.view(*shape)
|
| 65 |
+
|
| 66 |
+
x1, x2 = x.chunk(2, dim=-1)
|
| 67 |
+
return torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1)
|
| 68 |
+
|
| 69 |
+
def forward(self, q: torch.Tensor, k: torch.Tensor = None, start_pos: int = 0):
|
| 70 |
+
"""
|
| 71 |
+
Args:
|
| 72 |
+
q: Query tensor
|
| 73 |
+
k: Key tensor (optional)
|
| 74 |
+
start_pos: Starting position for RoPE (used during generation)
|
| 75 |
+
"""
|
| 76 |
+
assert q.shape[-1] == self.dim, f"Last dim of q must be {self.dim}, got {q.shape[-1]}"
|
| 77 |
+
if k is not None:
|
| 78 |
+
assert k.shape[-2:] == q.shape[-2:], "k must have same seq_len and head_dim as q"
|
| 79 |
+
|
| 80 |
+
seq_len = q.shape[-2]
|
| 81 |
+
self._update_cache(start_pos + seq_len, q.dtype, q.device)
|
| 82 |
+
|
| 83 |
+
cos = self._cos_cached
|
| 84 |
+
sin = self._sin_cached
|
| 85 |
+
|
| 86 |
+
q_rope = self._apply_rotary_emb(q, cos, sin, start_pos)
|
| 87 |
+
k_rope = self._apply_rotary_emb(k, cos, sin, start_pos) if k is not None else None
|
| 88 |
+
|
| 89 |
+
return (q_rope, k_rope) if k_rope is not None else q_rope
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class ALiBi(Layer):
|
| 93 |
+
__name__ = "ALiBi"
|
| 94 |
+
|
| 95 |
+
def __init__(self, n_heads: int):
|
| 96 |
+
super().__init__()
|
| 97 |
+
self.n_heads = n_heads
|
| 98 |
+
|
| 99 |
+
# Calculate standard ALiBi geometric slopes
|
| 100 |
+
closest_power_of_2 = 2 ** math.floor(math.log2(n_heads))
|
| 101 |
+
base = 2 ** (-(2 ** -(math.log2(closest_power_of_2) - 3)))
|
| 102 |
+
slopes = [math.pow(base, i) for i in range(1, closest_power_of_2 + 1)]
|
| 103 |
+
|
| 104 |
+
# Handle non-power-of-2 head counts (e.g., 12 heads)
|
| 105 |
+
if closest_power_of_2 != n_heads:
|
| 106 |
+
extra_base = 2 ** (-(2 ** -(math.log2(2 * closest_power_of_2) - 3)))
|
| 107 |
+
slopes.extend([math.pow(extra_base, i) for i in range(1, 2 * (n_heads - closest_power_of_2) + 1, 2)])
|
| 108 |
+
|
| 109 |
+
self.register_buffer(
|
| 110 |
+
"slopes",
|
| 111 |
+
torch.tensor(slopes, dtype=torch.float32).view(n_heads, 1, 1),
|
| 112 |
+
persistent=False
|
| 113 |
+
)
|
| 114 |
+
|
| 115 |
+
def forward(self, q_len: int, k_len: int, start_pos: int, device: torch.device, dtype: torch.dtype,
|
| 116 |
+
causal: bool = True) -> torch.Tensor:
|
| 117 |
+
# Create position indices
|
| 118 |
+
q_idx = torch.arange(start_pos, start_pos + q_len, device=device)[:, None]
|
| 119 |
+
k_idx = torch.arange(0, k_len, device=device)[None, :]
|
| 120 |
+
|
| 121 |
+
# Calculate relative distances (j - i).
|
| 122 |
+
# For causal attention, we only attend to the past, so k_idx <= q_idx, meaning distances <= 0.
|
| 123 |
+
distances = k_idx - q_idx
|
| 124 |
+
|
| 125 |
+
# Scale distances by head-specific slopes
|
| 126 |
+
alibi_bias = distances * self.slopes
|
| 127 |
+
alibi_bias = alibi_bias.to(dtype)
|
| 128 |
+
|
| 129 |
+
# Embed the causal mask directly into the ALiBi bias
|
| 130 |
+
if causal:
|
| 131 |
+
causal_mask = distances > 0
|
| 132 |
+
alibi_bias.masked_fill_(causal_mask, float('-inf'))
|
| 133 |
+
|
| 134 |
+
# Add batch dimension for SDPA broadcasting -> (1, n_heads, q_len, k_len)
|
| 135 |
+
return alibi_bias.unsqueeze(0)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
class YaRN(Layer):
|
| 139 |
+
__name__ = "YaRN"
|
| 140 |
+
|
| 141 |
+
def __init__(self, dim: int, original_max_len: int = 4096, scale: float = 1.0,
|
| 142 |
+
base: float = 10000.0, beta_fast: int = 32, beta_slow: int = 1):
|
| 143 |
+
super().__init__()
|
| 144 |
+
self.dim = dim
|
| 145 |
+
self.base = base
|
| 146 |
+
self.original_max_len = original_max_len
|
| 147 |
+
self.scale = scale
|
| 148 |
+
|
| 149 |
+
inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim))
|
| 150 |
+
|
| 151 |
+
if scale > 1.0:
|
| 152 |
+
wavelengths = 2 * math.pi / inv_freq
|
| 153 |
+
|
| 154 |
+
low = original_max_len / beta_slow
|
| 155 |
+
high = original_max_len / beta_fast
|
| 156 |
+
|
| 157 |
+
w = torch.clamp((wavelengths - high) / (low - high), 0.0, 1.0)
|
| 158 |
+
|
| 159 |
+
inv_freq_interpolated = inv_freq / scale
|
| 160 |
+
inv_freq_extrapolated = inv_freq
|
| 161 |
+
|
| 162 |
+
inv_freq = (1 - w) * inv_freq_extrapolated + w * inv_freq_interpolated
|
| 163 |
+
|
| 164 |
+
self.mscale = math.sqrt(0.1 * math.log(scale) + 1.0)
|
| 165 |
+
else:
|
| 166 |
+
self.mscale = 1.0
|
| 167 |
+
|
| 168 |
+
self.register_buffer("inv_freq", inv_freq, persistent=False)
|
| 169 |
+
|
| 170 |
+
self._cos_cached = None
|
| 171 |
+
self._sin_cached = None
|
| 172 |
+
self._seq_len_cached = 0
|
| 173 |
+
|
| 174 |
+
def _update_cache(self, seq_len: int, dtype: torch.dtype, device: torch.device):
|
| 175 |
+
if seq_len > self._seq_len_cached or self._cos_cached is None:
|
| 176 |
+
self._seq_len_cached = seq_len
|
| 177 |
+
t = torch.arange(seq_len, device=device, dtype=dtype)
|
| 178 |
+
freqs = torch.outer(t, self.inv_freq.to(device))
|
| 179 |
+
|
| 180 |
+
# Apply the YaRN mscale directly to the cache
|
| 181 |
+
self._cos_cached = (freqs.cos() * self.mscale)
|
| 182 |
+
self._sin_cached = (freqs.sin() * self.mscale)
|
| 183 |
+
|
| 184 |
+
def _apply_rotary_emb(self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor,
|
| 185 |
+
start_pos: int = 0) -> torch.Tensor:
|
| 186 |
+
"""Apply rotary embeddings starting from start_pos"""
|
| 187 |
+
seq_len = x.shape[-2]
|
| 188 |
+
|
| 189 |
+
cos = cos[start_pos:start_pos + seq_len]
|
| 190 |
+
sin = sin[start_pos:start_pos + seq_len]
|
| 191 |
+
|
| 192 |
+
shape = [1] * x.ndim
|
| 193 |
+
shape[-2] = seq_len
|
| 194 |
+
shape[-1] = self.dim // 2
|
| 195 |
+
|
| 196 |
+
cos = cos.view(*shape).to(x.dtype)
|
| 197 |
+
sin = sin.view(*shape).to(x.dtype)
|
| 198 |
+
|
| 199 |
+
x1, x2 = x.chunk(2, dim=-1)
|
| 200 |
+
return torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1)
|
| 201 |
+
|
| 202 |
+
def forward(self, q: torch.Tensor, k: torch.Tensor = None, start_pos: int = 0):
|
| 203 |
+
assert q.shape[-1] == self.dim, f"Last dim of q must be {self.dim}, got {q.shape[-1]}"
|
| 204 |
+
if k is not None:
|
| 205 |
+
assert k.shape[-2:] == q.shape[-2:], "k must have same seq_len and head_dim as q"
|
| 206 |
+
|
| 207 |
+
seq_len = q.shape[-2]
|
| 208 |
+
self._update_cache(start_pos + seq_len, q.dtype, q.device)
|
| 209 |
+
|
| 210 |
+
cos = self._cos_cached
|
| 211 |
+
sin = self._sin_cached
|
| 212 |
+
|
| 213 |
+
q_rope = self._apply_rotary_emb(q, cos, sin, start_pos)
|
| 214 |
+
k_rope = self._apply_rotary_emb(k, cos, sin, start_pos) if k is not None else None
|
| 215 |
+
|
| 216 |
+
return (q_rope, k_rope) if k_rope is not None else q_rope
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
class NanoYaRN(Layer):
|
| 220 |
+
"""Modded Nano-Gpt implmenetation of YaRN"""
|
| 221 |
+
|
| 222 |
+
def __init__(self, head_dim, max_seq_len, paired=False):
|
| 223 |
+
super().__init__()
|
| 224 |
+
self.head_dim = head_dim
|
| 225 |
+
self.max_seq_len = max_seq_len
|
| 226 |
+
self.paired = paired
|
| 227 |
+
self.reset()
|
| 228 |
+
|
| 229 |
+
def rotary(self, x_BTHD):
|
| 230 |
+
assert self.factor1.size(0) >= x_BTHD.size(-3)
|
| 231 |
+
factor1, factor2 = (
|
| 232 |
+
self.factor1[None, : x_BTHD.size(-3), None, :],
|
| 233 |
+
self.factor2[None, : x_BTHD.size(-3), None, :],
|
| 234 |
+
)
|
| 235 |
+
x_flip = x_BTHD.view(*x_BTHD.shape[:-1], x_BTHD.shape[-1] // 2, 2).flip(-1).view(x_BTHD.shape)
|
| 236 |
+
return factor1 * x_BTHD + factor2 * x_flip
|
| 237 |
+
|
| 238 |
+
def reset(self):
|
| 239 |
+
angular_freq = (1 / 1024) ** torch.linspace(0, 1, steps=self.head_dim // 4, dtype=torch.float32)
|
| 240 |
+
angular_freq = angular_freq.repeat_interleave(2)
|
| 241 |
+
# half-truncate RoPE by @YouJiacheng (w/ base freq tuning)
|
| 242 |
+
angular_freq = torch.cat([angular_freq, angular_freq.new_zeros(self.head_dim // 2)])
|
| 243 |
+
t = torch.arange(2 * self.max_seq_len, dtype=torch.float32)
|
| 244 |
+
if not self.paired:
|
| 245 |
+
theta = torch.outer(t, angular_freq)
|
| 246 |
+
self.factor1 = nn.Buffer(
|
| 247 |
+
theta.cos().to(torch.bfloat16), persistent=False
|
| 248 |
+
)
|
| 249 |
+
self.factor2 = nn.Buffer(
|
| 250 |
+
theta.sin().to(torch.bfloat16), persistent=False
|
| 251 |
+
)
|
| 252 |
+
else:
|
| 253 |
+
t_even = 2 * t
|
| 254 |
+
t_odd = 2 * t + 1
|
| 255 |
+
theta1 = torch.outer(t_even, angular_freq)
|
| 256 |
+
theta2 = torch.outer(t_odd, angular_freq)
|
| 257 |
+
self.factor1 = nn.Buffer(
|
| 258 |
+
torch.cat((theta1.cos(), theta2.cos()), dim=-1).to(torch.bfloat16),
|
| 259 |
+
persistent=False
|
| 260 |
+
)
|
| 261 |
+
self.factor2 = nn.Buffer(
|
| 262 |
+
torch.cat((theta1.sin(), theta2.sin()), dim=-1).to(torch.bfloat16),
|
| 263 |
+
persistent=False
|
| 264 |
+
)
|
| 265 |
+
self.factor2[..., 1::2] *= -1
|
| 266 |
+
self.angular_freq = angular_freq
|
| 267 |
+
# start with 0.1, inspired by 0.12 from @leloykun and learnable scalars used by @brendanh0gan https://x.com/hi_tysam/status/1879693583898591283
|
| 268 |
+
self.attn_scale = 0.1
|
| 269 |
+
|
| 270 |
+
def apply(self, old_window: int, new_window: int, alpha: int = 1, beta: int = 32):
|
| 271 |
+
rotations = old_window * self.angular_freq / (2 * torch.pi)
|
| 272 |
+
scaling_factor = old_window / new_window
|
| 273 |
+
interpolation_weight = torch.clamp((rotations - alpha) / (beta - alpha), 0, 1)
|
| 274 |
+
self.angular_freq *= scaling_factor + interpolation_weight * (1 - scaling_factor)
|
| 275 |
+
t = torch.arange(2 * self.max_seq_len, dtype=torch.float32, device=self.angular_freq.device)
|
| 276 |
+
if not self.paired:
|
| 277 |
+
theta = torch.outer(t, self.angular_freq)
|
| 278 |
+
self.factor1.copy_(theta.cos())
|
| 279 |
+
self.factor2.copy_(theta.sin())
|
| 280 |
+
else:
|
| 281 |
+
t_even = 2 * t
|
| 282 |
+
t_odd = 2 * t + 1
|
| 283 |
+
theta1 = torch.outer(t_even, self.angular_freq)
|
| 284 |
+
theta2 = torch.outer(t_odd, self.angular_freq)
|
| 285 |
+
self.factor1.copy_(torch.cat((theta1.cos(), theta2.cos()), dim=-1))
|
| 286 |
+
self.factor2.copy_(torch.cat((theta1.sin(), theta2.sin()), dim=-1))
|
| 287 |
+
self.factor2[..., 1::2] *= -1
|
| 288 |
+
self.attn_scale *= 0.2 * math.log(new_window / old_window) + 1
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
class MultiheadAttentionMixer(Layer):
|
| 292 |
+
__name__ = "MultiheadAttentionMixer"
|
| 293 |
+
__complexity__ = "O(L^2 d + L d^2)"
|
| 294 |
+
|
| 295 |
+
def __init__(self, d_model: int, n_heads: int, causal: bool = True,
|
| 296 |
+
pos_emb: nn.Module = None, dropout: float = 0.0, qk_norm=False):
|
| 297 |
+
super().__init__()
|
| 298 |
+
assert d_model % n_heads == 0, "d_model must be divisible by n_heads"
|
| 299 |
+
self.causal = causal
|
| 300 |
+
self.d_model = d_model
|
| 301 |
+
self.n_heads = n_heads
|
| 302 |
+
self.head_dim = d_model // n_heads
|
| 303 |
+
self.dropout_p = dropout
|
| 304 |
+
|
| 305 |
+
self.qkv = nn.Linear(d_model, 3 * d_model, bias=False)
|
| 306 |
+
self.out = nn.Linear(d_model, d_model, bias=False)
|
| 307 |
+
|
| 308 |
+
self.pos_emb = pos_emb
|
| 309 |
+
self.kv_cache = None
|
| 310 |
+
self.qk_norm = qk_norm
|
| 311 |
+
if self.qk_norm:
|
| 312 |
+
self.q_norm = RMSNorm(self.head_dim)
|
| 313 |
+
self.k_norm = RMSNorm(self.head_dim)
|
| 314 |
+
|
| 315 |
+
self._init_weights()
|
| 316 |
+
|
| 317 |
+
def _init_weights(self):
|
| 318 |
+
# Identity-at-init: qkv orthogonal (preserva geometria), out → 0.
|
| 319 |
+
nn.init.orthogonal_(self.qkv.weight)
|
| 320 |
+
nn.init.zeros_(self.out.weight)
|
| 321 |
+
|
| 322 |
+
# Alias backward-compat per chi chiama explicit _reset_parameters.
|
| 323 |
+
_reset_parameters = _init_weights
|
| 324 |
+
|
| 325 |
+
def forward(self, x: torch.Tensor, ve=None) -> torch.Tensor:
|
| 326 |
+
"""
|
| 327 |
+
ve: Stands for value embeddings, which should be already weighted byt the caller, of course the dimension must match x
|
| 328 |
+
"""
|
| 329 |
+
B, L, D = x.shape
|
| 330 |
+
|
| 331 |
+
qkv = self.qkv(x).view(B, L, 3, self.n_heads, self.head_dim)
|
| 332 |
+
q, k, v = qkv.unbind(dim=2)
|
| 333 |
+
q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
|
| 334 |
+
|
| 335 |
+
if self.qk_norm:
|
| 336 |
+
q = self.q_norm(q)
|
| 337 |
+
k = self.k_norm(k)
|
| 338 |
+
|
| 339 |
+
if self.pos_emb is not None:
|
| 340 |
+
q, k = self.pos_emb(q, k, start_pos=0)
|
| 341 |
+
if ve is not None:
|
| 342 |
+
v = v + ve.view(B, L, self.n_heads, self.head_dim).transpose(1, 2)
|
| 343 |
+
|
| 344 |
+
attn = F.scaled_dot_product_attention(
|
| 345 |
+
q, k, v,
|
| 346 |
+
is_causal=self.causal,
|
| 347 |
+
dropout_p=self.dropout_p if self.training else 0.0,
|
| 348 |
+
)
|
| 349 |
+
return self.out(attn.transpose(1, 2).contiguous().view(B, L, D))
|
| 350 |
+
|
| 351 |
+
def show_weights_forward(self, x):
|
| 352 |
+
B, L, D = x.shape
|
| 353 |
+
|
| 354 |
+
qkv = self.qkv(x).view(B, L, 3, self.n_heads, self.head_dim)
|
| 355 |
+
q, k, v = qkv.unbind(dim=2)
|
| 356 |
+
q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
|
| 357 |
+
|
| 358 |
+
if self.qk_norm:
|
| 359 |
+
q = self.q_norm(q)
|
| 360 |
+
k = self.k_norm(k)
|
| 361 |
+
|
| 362 |
+
if self.pos_emb is not None:
|
| 363 |
+
q, k = self.pos_emb(q, k, start_pos=0)
|
| 364 |
+
|
| 365 |
+
scale = 1.0 / math.sqrt(self.head_dim)
|
| 366 |
+
scores = torch.matmul(q, k.transpose(-2, -1)) * scale
|
| 367 |
+
|
| 368 |
+
if self.causal:
|
| 369 |
+
mask = torch.triu(torch.ones(L, L, dtype=torch.bool, device=x.device), diagonal=1)
|
| 370 |
+
scores.masked_fill_(mask, float('-inf'))
|
| 371 |
+
|
| 372 |
+
attn_weights = F.softmax(scores, dim=-1)
|
| 373 |
+
attn = torch.matmul(attn_weights, v)
|
| 374 |
+
|
| 375 |
+
out = self.out(attn.transpose(1, 2).contiguous().view(B, L, D))
|
| 376 |
+
return out, attn_weights
|
| 377 |
+
|
| 378 |
+
def generate(self, x: torch.Tensor, ve=None) -> torch.Tensor:
|
| 379 |
+
B, L, D = x.shape
|
| 380 |
+
|
| 381 |
+
qkv = self.qkv(x).view(B, L, 3, self.n_heads, self.head_dim)
|
| 382 |
+
q, k, v = qkv.unbind(dim=2)
|
| 383 |
+
q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
|
| 384 |
+
|
| 385 |
+
if self.qk_norm:
|
| 386 |
+
q = self.q_norm(q)
|
| 387 |
+
k = self.k_norm(k)
|
| 388 |
+
|
| 389 |
+
pos_offset = self.kv_cache[0].shape[2] if self.kv_cache is not None else 0
|
| 390 |
+
|
| 391 |
+
if self.pos_emb is not None:
|
| 392 |
+
q, k = self.pos_emb(q, k, start_pos=pos_offset)
|
| 393 |
+
|
| 394 |
+
if ve is not None:
|
| 395 |
+
v = v + ve.view(B, L, self.n_heads, self.head_dim).transpose(1, 2)
|
| 396 |
+
|
| 397 |
+
if self.kv_cache is not None:
|
| 398 |
+
k_cache, v_cache = self.kv_cache
|
| 399 |
+
k = torch.cat([k_cache, k], dim=2)
|
| 400 |
+
v = torch.cat([v_cache, v], dim=2)
|
| 401 |
+
|
| 402 |
+
self.kv_cache = (k, v)
|
| 403 |
+
|
| 404 |
+
attn = F.scaled_dot_product_attention(
|
| 405 |
+
q, k, v,
|
| 406 |
+
is_causal=self.causal and (L > 1),
|
| 407 |
+
dropout_p=0.0,
|
| 408 |
+
)
|
| 409 |
+
return self.out(attn.transpose(1, 2).contiguous().view(B, L, D))
|
| 410 |
+
|
| 411 |
+
def clear_cache(self):
|
| 412 |
+
self.kv_cache = None
|
| 413 |
+
|
| 414 |
+
def finetune(self):
|
| 415 |
+
self.qkv.weight.requires_grad = False
|
| 416 |
+
self.out.weight.requires_grad = False
|
| 417 |
+
|
| 418 |
+
|
| 419 |
+
class MultiheadGatedAttentionMixer(Layer):
|
| 420 |
+
"""MHA + sparse per-head output gate (Modded-NanoGPT PR #117 / parameter-golf 1667).
|
| 421 |
+
|
| 422 |
+
Gate input: prime `gate_input_dim` (default 12) dimensioni di x — sparse, ottimizzato
|
| 423 |
+
per Muon. Output: un sigmoid scalare per head, broadcast su head_dim. Init zero ⇒
|
| 424 |
+
gate parte a 0.5 (no-op).
|
| 425 |
+
"""
|
| 426 |
+
__name__ = "MultiheadGatedAttentionMixer"
|
| 427 |
+
__complexity__ = "O(L^2 d + L d^2)"
|
| 428 |
+
|
| 429 |
+
def __init__(self, d_model: int, n_heads: int, causal: bool = True,
|
| 430 |
+
pos_emb: nn.Module = None, dropout: float = 0.0, qk_norm: bool = False,
|
| 431 |
+
gate_input_dim: int = 12):
|
| 432 |
+
super().__init__()
|
| 433 |
+
assert d_model % n_heads == 0, "d_model must be divisible by n_heads"
|
| 434 |
+
self.causal = causal
|
| 435 |
+
self.d_model = d_model
|
| 436 |
+
self.n_heads = n_heads
|
| 437 |
+
self.head_dim = d_model // n_heads
|
| 438 |
+
self.dropout_p = dropout
|
| 439 |
+
self.gate_input_dim = gate_input_dim
|
| 440 |
+
|
| 441 |
+
self.qkv = nn.Linear(d_model, 3 * d_model, bias=False)
|
| 442 |
+
self.out = nn.Linear(d_model, d_model, bias=False)
|
| 443 |
+
self.gate_proj = nn.Linear(gate_input_dim, n_heads, bias=False)
|
| 444 |
+
|
| 445 |
+
self.pos_emb = pos_emb
|
| 446 |
+
self.kv_cache = None
|
| 447 |
+
self.qk_norm = qk_norm
|
| 448 |
+
if qk_norm:
|
| 449 |
+
self.q_norm = RMSNorm(self.head_dim)
|
| 450 |
+
self.k_norm = RMSNorm(self.head_dim)
|
| 451 |
+
|
| 452 |
+
self._init_weights()
|
| 453 |
+
|
| 454 |
+
def _init_weights(self):
|
| 455 |
+
# Identity-at-init: qkv orthogonal, out → 0, gate_proj orthogonal
|
| 456 |
+
# (out=0 ⇒ contributo attention nullo all'init, indipendentemente dal gate).
|
| 457 |
+
nn.init.orthogonal_(self.qkv.weight)
|
| 458 |
+
nn.init.zeros_(self.out.weight)
|
| 459 |
+
nn.init.orthogonal_(self.gate_proj.weight)
|
| 460 |
+
|
| 461 |
+
_reset_parameters = _init_weights
|
| 462 |
+
|
| 463 |
+
def _apply_gate(self, attn: torch.Tensor, x_gate_in: torch.Tensor) -> torch.Tensor:
|
| 464 |
+
# attn: [B, H, L, D_h] | x_gate_in: [B, L, gate_input_dim]
|
| 465 |
+
gate = torch.sigmoid(self.gate_proj(x_gate_in)) # [B, L, H]
|
| 466 |
+
return attn * gate.transpose(1, 2).unsqueeze(-1) # broadcast su D_h
|
| 467 |
+
|
| 468 |
+
def forward(self, x: torch.Tensor, ve=None) -> torch.Tensor:
|
| 469 |
+
B, L, D = x.shape
|
| 470 |
+
qkv = self.qkv(x).view(B, L, 3, self.n_heads, self.head_dim)
|
| 471 |
+
q, k, v = qkv.unbind(dim=2)
|
| 472 |
+
q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
|
| 473 |
+
|
| 474 |
+
if self.qk_norm:
|
| 475 |
+
q = self.q_norm(q)
|
| 476 |
+
k = self.k_norm(k)
|
| 477 |
+
if self.pos_emb is not None:
|
| 478 |
+
q, k = self.pos_emb(q, k, start_pos=0)
|
| 479 |
+
if ve is not None:
|
| 480 |
+
v = v + ve.view(B, L, self.n_heads, self.head_dim).transpose(1, 2)
|
| 481 |
+
|
| 482 |
+
attn = F.scaled_dot_product_attention(
|
| 483 |
+
q, k, v, is_causal=self.causal,
|
| 484 |
+
dropout_p=self.dropout_p if self.training else 0.0,
|
| 485 |
+
)
|
| 486 |
+
attn = self._apply_gate(attn, x[..., :self.gate_input_dim])
|
| 487 |
+
return self.out(attn.transpose(1, 2).reshape(B, L, D))
|
| 488 |
+
|
| 489 |
+
def generate(self, x: torch.Tensor, ve=None) -> torch.Tensor:
|
| 490 |
+
B, L, D = x.shape
|
| 491 |
+
qkv = self.qkv(x).view(B, L, 3, self.n_heads, self.head_dim)
|
| 492 |
+
q, k, v = qkv.unbind(dim=2)
|
| 493 |
+
q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
|
| 494 |
+
|
| 495 |
+
if self.qk_norm:
|
| 496 |
+
q = self.q_norm(q)
|
| 497 |
+
k = self.k_norm(k)
|
| 498 |
+
|
| 499 |
+
pos_offset = self.kv_cache[0].shape[2] if self.kv_cache is not None else 0
|
| 500 |
+
if self.pos_emb is not None:
|
| 501 |
+
q, k = self.pos_emb(q, k, start_pos=pos_offset)
|
| 502 |
+
if ve is not None:
|
| 503 |
+
v = v + ve.view(B, L, self.n_heads, self.head_dim).transpose(1, 2)
|
| 504 |
+
|
| 505 |
+
if self.kv_cache is not None:
|
| 506 |
+
k_cache, v_cache = self.kv_cache
|
| 507 |
+
k = torch.cat([k_cache, k], dim=2)
|
| 508 |
+
v = torch.cat([v_cache, v], dim=2)
|
| 509 |
+
self.kv_cache = (k, v)
|
| 510 |
+
|
| 511 |
+
attn = F.scaled_dot_product_attention(
|
| 512 |
+
q, k, v, is_causal=self.causal and (L > 1), dropout_p=0.0,
|
| 513 |
+
)
|
| 514 |
+
attn = self._apply_gate(attn, x[..., :self.gate_input_dim])
|
| 515 |
+
return self.out(attn.transpose(1, 2).reshape(B, L, D))
|
| 516 |
+
|
| 517 |
+
def clear_cache(self):
|
| 518 |
+
self.kv_cache = None
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
class MultiheadAttentionMixerALIBI(Layer):
|
| 522 |
+
__name__ = "MultiheadAttentionMixer"
|
| 523 |
+
__complexity__ = "O(L^2 d + L d^2)"
|
| 524 |
+
|
| 525 |
+
def __init__(self, d_model: int, n_heads: int, causal: bool = True,
|
| 526 |
+
pos_emb: nn.Module = None, alibi: nn.Module = None, dropout: float = 0.0, qk_norm=False):
|
| 527 |
+
super().__init__()
|
| 528 |
+
assert d_model % n_heads == 0, "d_model must be divisible by n_heads"
|
| 529 |
+
self.causal = causal
|
| 530 |
+
self.d_model = d_model
|
| 531 |
+
self.n_heads = n_heads
|
| 532 |
+
self.head_dim = d_model // n_heads
|
| 533 |
+
self.dropout_p = dropout
|
| 534 |
+
|
| 535 |
+
self.qkv = nn.Linear(d_model, 3 * d_model, bias=False)
|
| 536 |
+
self.out = nn.Linear(d_model, d_model, bias=False)
|
| 537 |
+
|
| 538 |
+
self.pos_emb = pos_emb
|
| 539 |
+
self.alibi = alibi # ADDED: ALiBi module injection
|
| 540 |
+
self.kv_cache = None
|
| 541 |
+
self.qk_norm = qk_norm
|
| 542 |
+
|
| 543 |
+
if self.qk_norm:
|
| 544 |
+
self.q_norm = RMSNorm(self.head_dim)
|
| 545 |
+
self.k_norm = RMSNorm(self.head_dim)
|
| 546 |
+
|
| 547 |
+
self._reset_parameters()
|
| 548 |
+
|
| 549 |
+
def _reset_parameters(self):
|
| 550 |
+
nn.init.xavier_uniform_(self.qkv.weight)
|
| 551 |
+
nn.init.xavier_uniform_(self.out.weight)
|
| 552 |
+
|
| 553 |
+
def forward(self, x: torch.Tensor, ve=None) -> torch.Tensor:
|
| 554 |
+
B, L, D = x.shape
|
| 555 |
+
|
| 556 |
+
qkv = self.qkv(x).view(B, L, 3, self.n_heads, self.head_dim)
|
| 557 |
+
q, k, v = qkv.unbind(dim=2)
|
| 558 |
+
q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
|
| 559 |
+
|
| 560 |
+
if self.qk_norm:
|
| 561 |
+
q = self.q_norm(q)
|
| 562 |
+
k = self.k_norm(k)
|
| 563 |
+
|
| 564 |
+
if self.pos_emb is not None:
|
| 565 |
+
q, k = self.pos_emb(q, k, start_pos=0)
|
| 566 |
+
|
| 567 |
+
if ve is not None:
|
| 568 |
+
v = v + ve.view(B, L, self.n_heads, self.head_dim).transpose(1, 2)
|
| 569 |
+
|
| 570 |
+
# ADDED: ALiBi Mask Logic
|
| 571 |
+
attn_mask = None
|
| 572 |
+
is_causal = self.causal
|
| 573 |
+
if self.alibi is not None:
|
| 574 |
+
attn_mask = self.alibi(q_len=L, k_len=L, start_pos=0, device=x.device, dtype=q.dtype, causal=self.causal)
|
| 575 |
+
is_causal = False # PyTorch requires is_causal=False if a custom attn_mask is provided
|
| 576 |
+
|
| 577 |
+
attn = F.scaled_dot_product_attention(
|
| 578 |
+
q, k, v,
|
| 579 |
+
attn_mask=attn_mask,
|
| 580 |
+
is_causal=is_causal,
|
| 581 |
+
dropout_p=self.dropout_p if self.training else 0.0,
|
| 582 |
+
)
|
| 583 |
+
return self.out(attn.transpose(1, 2).contiguous().view(B, L, D))
|
| 584 |
+
|
| 585 |
+
def show_weights_forward(self, x):
|
| 586 |
+
B, L, D = x.shape
|
| 587 |
+
|
| 588 |
+
qkv = self.qkv(x).view(B, L, 3, self.n_heads, self.head_dim)
|
| 589 |
+
q, k, v = qkv.unbind(dim=2)
|
| 590 |
+
q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
|
| 591 |
+
|
| 592 |
+
if self.qk_norm:
|
| 593 |
+
q = self.q_norm(q)
|
| 594 |
+
k = self.k_norm(k)
|
| 595 |
+
|
| 596 |
+
if self.pos_emb is not None:
|
| 597 |
+
q, k = self.pos_emb(q, k, start_pos=0)
|
| 598 |
+
|
| 599 |
+
scale = 1.0 / math.sqrt(self.head_dim)
|
| 600 |
+
scores = torch.matmul(q, k.transpose(-2, -1)) * scale
|
| 601 |
+
|
| 602 |
+
# ADDED: ALiBi logic replaces strict causal mask if active
|
| 603 |
+
if self.alibi is not None:
|
| 604 |
+
alibi_bias = self.alibi(q_len=L, k_len=L, start_pos=0, device=x.device, dtype=q.dtype, causal=self.causal)
|
| 605 |
+
scores = scores + alibi_bias
|
| 606 |
+
elif self.causal:
|
| 607 |
+
mask = torch.triu(torch.ones(L, L, dtype=torch.bool, device=x.device), diagonal=1)
|
| 608 |
+
scores.masked_fill_(mask, float('-inf'))
|
| 609 |
+
|
| 610 |
+
attn_weights = F.softmax(scores, dim=-1)
|
| 611 |
+
attn = torch.matmul(attn_weights, v)
|
| 612 |
+
|
| 613 |
+
out = self.out(attn.transpose(1, 2).contiguous().view(B, L, D))
|
| 614 |
+
return out, attn_weights
|
| 615 |
+
|
| 616 |
+
def generate(self, x: torch.Tensor) -> torch.Tensor:
|
| 617 |
+
B, L, D = x.shape
|
| 618 |
+
|
| 619 |
+
qkv = self.qkv(x).view(B, L, 3, self.n_heads, self.head_dim)
|
| 620 |
+
q, k, v = qkv.unbind(dim=2)
|
| 621 |
+
q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
|
| 622 |
+
|
| 623 |
+
if self.qk_norm:
|
| 624 |
+
q = self.q_norm(q)
|
| 625 |
+
k = self.k_norm(k)
|
| 626 |
+
|
| 627 |
+
pos_offset = self.kv_cache[0].shape[2] if self.kv_cache is not None else 0
|
| 628 |
+
|
| 629 |
+
if self.pos_emb is not None:
|
| 630 |
+
q, k = self.pos_emb(q, k, start_pos=pos_offset)
|
| 631 |
+
|
| 632 |
+
if self.kv_cache is not None:
|
| 633 |
+
k_cache, v_cache = self.kv_cache
|
| 634 |
+
k = torch.cat([k_cache, k], dim=2)
|
| 635 |
+
v = torch.cat([v_cache, v], dim=2)
|
| 636 |
+
|
| 637 |
+
self.kv_cache = (k, v)
|
| 638 |
+
k_len = k.shape[2]
|
| 639 |
+
|
| 640 |
+
# ADDED: ALiBi Mask Logic for Generation
|
| 641 |
+
attn_mask = None
|
| 642 |
+
is_causal = self.causal and (L > 1)
|
| 643 |
+
if self.alibi is not None:
|
| 644 |
+
attn_mask = self.alibi(q_len=L, k_len=k_len, start_pos=pos_offset, device=x.device, dtype=q.dtype,
|
| 645 |
+
causal=self.causal)
|
| 646 |
+
is_causal = False
|
| 647 |
+
|
| 648 |
+
attn = F.scaled_dot_product_attention(
|
| 649 |
+
q, k, v,
|
| 650 |
+
attn_mask=attn_mask,
|
| 651 |
+
is_causal=is_causal,
|
| 652 |
+
dropout_p=0.0,
|
| 653 |
+
)
|
| 654 |
+
return self.out(attn.transpose(1, 2).contiguous().view(B, L, D))
|
| 655 |
+
|
| 656 |
+
# [clear_cache and finetune remain unchanged]
|
| 657 |
+
|
| 658 |
+
|
| 659 |
+
class MultiheadAttentionMixerXSA(Layer):
|
| 660 |
+
__name__ = "MultiheadAttentionMixerXSA"
|
| 661 |
+
__complexity__ = "O(L^2 d + L d^2)"
|
| 662 |
+
|
| 663 |
+
def __init__(self, d_model: int, n_heads: int, causal: bool = True,
|
| 664 |
+
pos_emb: nn.Module = None, dropout: float = 0.0, qk_norm=False):
|
| 665 |
+
super().__init__()
|
| 666 |
+
assert d_model % n_heads == 0, "d_model must be divisible by n_heads"
|
| 667 |
+
self.causal = causal
|
| 668 |
+
self.d_model = d_model
|
| 669 |
+
self.n_heads = n_heads
|
| 670 |
+
self.head_dim = d_model // n_heads
|
| 671 |
+
self.dropout_p = dropout
|
| 672 |
+
|
| 673 |
+
self.qkv = nn.Linear(d_model, 3 * d_model, bias=False)
|
| 674 |
+
self.out = nn.Linear(d_model, d_model, bias=False)
|
| 675 |
+
|
| 676 |
+
self.pos_emb = pos_emb
|
| 677 |
+
self.kv_cache = None
|
| 678 |
+
self.qk_norm = qk_norm
|
| 679 |
+
if self.qk_norm:
|
| 680 |
+
self.q_norm = RMSNorm(self.head_dim)
|
| 681 |
+
self.k_norm = RMSNorm(self.head_dim)
|
| 682 |
+
|
| 683 |
+
self._reset_parameters()
|
| 684 |
+
|
| 685 |
+
def _reset_parameters(self):
|
| 686 |
+
nn.init.xavier_uniform_(self.qkv.weight)
|
| 687 |
+
nn.init.xavier_uniform_(self.out.weight)
|
| 688 |
+
|
| 689 |
+
def forward(self, x: torch.Tensor, ve=None) -> torch.Tensor:
|
| 690 |
+
"""
|
| 691 |
+
ve: Stands for value embeddings, which should be already weighted by the caller, of course the dimension must match x
|
| 692 |
+
"""
|
| 693 |
+
B, L, D = x.shape
|
| 694 |
+
|
| 695 |
+
qkv = self.qkv(x).view(B, L, 3, self.n_heads, self.head_dim)
|
| 696 |
+
q, k, v = qkv.unbind(dim=2)
|
| 697 |
+
q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
|
| 698 |
+
|
| 699 |
+
if self.qk_norm:
|
| 700 |
+
q = self.q_norm(q)
|
| 701 |
+
k = self.k_norm(k)
|
| 702 |
+
|
| 703 |
+
if self.pos_emb is not None:
|
| 704 |
+
q, k = self.pos_emb(q, k, start_pos=0)
|
| 705 |
+
if ve is not None:
|
| 706 |
+
v = v + ve
|
| 707 |
+
|
| 708 |
+
attn = F.scaled_dot_product_attention(
|
| 709 |
+
q, k, v,
|
| 710 |
+
is_causal=self.causal,
|
| 711 |
+
dropout_p=self.dropout_p if self.training else 0.0,
|
| 712 |
+
)
|
| 713 |
+
|
| 714 |
+
v_norm = F.normalize(v, dim=-1)
|
| 715 |
+
attn = attn - (attn * v_norm).sum(dim=-1, keepdim=True) * v_norm
|
| 716 |
+
|
| 717 |
+
return self.out(attn.transpose(1, 2).contiguous().view(B, L, D))
|
| 718 |
+
|
| 719 |
+
def show_weights_forward(self, x):
|
| 720 |
+
B, L, D = x.shape
|
| 721 |
+
|
| 722 |
+
qkv = self.qkv(x).view(B, L, 3, self.n_heads, self.head_dim)
|
| 723 |
+
q, k, v = qkv.unbind(dim=2)
|
| 724 |
+
q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
|
| 725 |
+
|
| 726 |
+
if self.qk_norm:
|
| 727 |
+
q = self.q_norm(q)
|
| 728 |
+
k = self.k_norm(k)
|
| 729 |
+
|
| 730 |
+
if self.pos_emb is not None:
|
| 731 |
+
q, k = self.pos_emb(q, k, start_pos=0)
|
| 732 |
+
|
| 733 |
+
scale = 1.0 / math.sqrt(self.head_dim)
|
| 734 |
+
scores = torch.matmul(q, k.transpose(-2, -1)) * scale
|
| 735 |
+
|
| 736 |
+
if self.causal:
|
| 737 |
+
mask = torch.triu(torch.ones(L, L, dtype=torch.bool, device=x.device), diagonal=1)
|
| 738 |
+
scores.masked_fill_(mask, float('-inf'))
|
| 739 |
+
|
| 740 |
+
attn_weights = F.softmax(scores, dim=-1)
|
| 741 |
+
attn = torch.matmul(attn_weights, v)
|
| 742 |
+
|
| 743 |
+
v_norm = F.normalize(v, dim=-1)
|
| 744 |
+
attn = attn - (attn * v_norm).sum(dim=-1, keepdim=True) * v_norm
|
| 745 |
+
|
| 746 |
+
out = self.out(attn.transpose(1, 2).contiguous().view(B, L, D))
|
| 747 |
+
return out, attn_weights
|
| 748 |
+
|
| 749 |
+
def generate(self, x: torch.Tensor) -> torch.Tensor:
|
| 750 |
+
B, L, D = x.shape
|
| 751 |
+
|
| 752 |
+
qkv = self.qkv(x).view(B, L, 3, self.n_heads, self.head_dim)
|
| 753 |
+
q, k, v = qkv.unbind(dim=2)
|
| 754 |
+
q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)
|
| 755 |
+
|
| 756 |
+
if self.qk_norm:
|
| 757 |
+
q = self.q_norm(q)
|
| 758 |
+
k = self.k_norm(k)
|
| 759 |
+
|
| 760 |
+
pos_offset = self.kv_cache[0].shape[2] if self.kv_cache is not None else 0
|
| 761 |
+
|
| 762 |
+
if self.pos_emb is not None:
|
| 763 |
+
q, k = self.pos_emb(q, k, start_pos=pos_offset)
|
| 764 |
+
|
| 765 |
+
v_self = v
|
| 766 |
+
|
| 767 |
+
if self.kv_cache is not None:
|
| 768 |
+
k_cache, v_cache = self.kv_cache
|
| 769 |
+
k = torch.cat([k_cache, k], dim=2)
|
| 770 |
+
v = torch.cat([v_cache, v], dim=2)
|
| 771 |
+
|
| 772 |
+
self.kv_cache = (k, v)
|
| 773 |
+
|
| 774 |
+
attn = F.scaled_dot_product_attention(
|
| 775 |
+
q, k, v,
|
| 776 |
+
is_causal=self.causal and (L > 1),
|
| 777 |
+
dropout_p=0.0,
|
| 778 |
+
)
|
| 779 |
+
|
| 780 |
+
v_norm = F.normalize(v_self, dim=-1)
|
| 781 |
+
attn = attn - (attn * v_norm).sum(dim=-1, keepdim=True) * v_norm
|
| 782 |
+
|
| 783 |
+
return self.out(attn.transpose(1, 2).contiguous().view(B, L, D))
|
| 784 |
+
|
| 785 |
+
def clear_cache(self):
|
| 786 |
+
self.kv_cache = None
|
| 787 |
+
|
| 788 |
+
def finetune(self):
|
| 789 |
+
self.qkv.weight.requires_grad = False
|
| 790 |
+
self.out.weight.requires_grad = False
|
| 791 |
+
|
| 792 |
+
|
| 793 |
+
class MultiheadAttentionMixerNOV(Layer):
|
| 794 |
+
__name__ = "MultiheadAttentionMixerNOV"
|
| 795 |
+
__complexity__ = "O(L^2 d + L d^2)"
|
| 796 |
+
|
| 797 |
+
def __init__(self, d_model: int, n_heads: int, causal: bool = True,
|
| 798 |
+
pos_emb: nn.Module = None, dropout: float = 0.0, qk_norm=False):
|
| 799 |
+
super().__init__()
|
| 800 |
+
assert d_model % n_heads == 0, "d_model must be divisible by n_heads"
|
| 801 |
+
self.causal = causal
|
| 802 |
+
self.d_model = d_model
|
| 803 |
+
self.n_heads = n_heads
|
| 804 |
+
self.head_dim = d_model // n_heads
|
| 805 |
+
self.dropout_p = dropout
|
| 806 |
+
|
| 807 |
+
self.qk = nn.Linear(d_model, 2 * d_model, bias=False)
|
| 808 |
+
self.out = nn.Linear(d_model, d_model, bias=False)
|
| 809 |
+
|
| 810 |
+
self.pos_emb = pos_emb
|
| 811 |
+
self.kv_cache = None
|
| 812 |
+
|
| 813 |
+
self.qk_norm = qk_norm
|
| 814 |
+
if self.qk_norm:
|
| 815 |
+
self.q_norm = RMSNorm(self.head_dim)
|
| 816 |
+
self.k_norm = RMSNorm(self.head_dim)
|
| 817 |
+
|
| 818 |
+
self._reset_parameters()
|
| 819 |
+
|
| 820 |
+
def _reset_parameters(self):
|
| 821 |
+
nn.init.xavier_uniform_(self.qk.weight)
|
| 822 |
+
nn.init.xavier_uniform_(self.out.weight)
|
| 823 |
+
|
| 824 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 825 |
+
B, L, D = x.shape
|
| 826 |
+
|
| 827 |
+
qk = self.qk(x).view(B, L, 2, self.n_heads, self.head_dim)
|
| 828 |
+
q, k = qk.unbind(dim=2)
|
| 829 |
+
q, k = q.transpose(1, 2), k.transpose(1, 2)
|
| 830 |
+
|
| 831 |
+
if self.qk_norm:
|
| 832 |
+
q = self.q_norm(q)
|
| 833 |
+
k = self.k_norm(k)
|
| 834 |
+
|
| 835 |
+
v = x.view(B, L, self.n_heads, self.head_dim).transpose(1, 2)
|
| 836 |
+
|
| 837 |
+
if self.pos_emb is not None:
|
| 838 |
+
q, k = self.pos_emb(q, k, start_pos=0)
|
| 839 |
+
|
| 840 |
+
attn = F.scaled_dot_product_attention(
|
| 841 |
+
q, k, v,
|
| 842 |
+
is_causal=self.causal,
|
| 843 |
+
dropout_p=self.dropout_p if self.training else 0.0,
|
| 844 |
+
)
|
| 845 |
+
return self.out(attn.transpose(1, 2).contiguous().view(B, L, D))
|
| 846 |
+
|
| 847 |
+
def generate(self, x: torch.Tensor) -> torch.Tensor:
|
| 848 |
+
B, L, D = x.shape
|
| 849 |
+
|
| 850 |
+
qk = self.qk(x).view(B, L, 2, self.n_heads, self.head_dim)
|
| 851 |
+
q, k = qk.unbind(dim=2)
|
| 852 |
+
q, k = q.transpose(1, 2), k.transpose(1, 2)
|
| 853 |
+
|
| 854 |
+
if self.qk_norm:
|
| 855 |
+
q = self.q_norm(q)
|
| 856 |
+
k = self.k_norm(k)
|
| 857 |
+
|
| 858 |
+
v = x.view(B, L, self.n_heads, self.head_dim).transpose(1, 2)
|
| 859 |
+
|
| 860 |
+
pos_offset = self.kv_cache[0].shape[2] if self.kv_cache is not None else 0
|
| 861 |
+
|
| 862 |
+
if self.pos_emb is not None:
|
| 863 |
+
q, k = self.pos_emb(q, k, start_pos=pos_offset)
|
| 864 |
+
|
| 865 |
+
if self.kv_cache is not None:
|
| 866 |
+
k_cache, v_cache = self.kv_cache
|
| 867 |
+
k = torch.cat([k_cache, k], dim=2)
|
| 868 |
+
v = torch.cat([v_cache, v], dim=2)
|
| 869 |
+
|
| 870 |
+
self.kv_cache = (k, v)
|
| 871 |
+
|
| 872 |
+
attn = F.scaled_dot_product_attention(
|
| 873 |
+
q, k, v,
|
| 874 |
+
is_causal=self.causal and (L > 1),
|
| 875 |
+
dropout_p=0.0,
|
| 876 |
+
)
|
| 877 |
+
return self.out(attn.transpose(1, 2).contiguous().view(B, L, D))
|
| 878 |
+
|
| 879 |
+
def clear_cache(self):
|
| 880 |
+
self.kv_cache = None
|
| 881 |
+
|
| 882 |
+
def finetune(self):
|
| 883 |
+
self.qk.weight.requires_grad = False
|
| 884 |
+
self.out.weight.requires_grad = False
|
| 885 |
+
|
| 886 |
+
|
| 887 |
+
class MultiheadAttentionMixerNOVLa2(Layer):
|
| 888 |
+
__name__ = "MultiheadAttentionMixerNOV"
|
| 889 |
+
__complexity__ = "O(L^2 d + L d^2)"
|
| 890 |
+
|
| 891 |
+
def __init__(self, d_model: int, n_heads: int, causal: bool = True,
|
| 892 |
+
pos_emb: nn.Module = None, dropout: float = 0.0, qk_norm=False):
|
| 893 |
+
super().__init__()
|
| 894 |
+
assert d_model % n_heads == 0, "d_model must be divisible by n_heads"
|
| 895 |
+
self.causal = causal
|
| 896 |
+
self.d_model = d_model
|
| 897 |
+
self.n_heads = n_heads
|
| 898 |
+
self.head_dim = d_model // n_heads
|
| 899 |
+
self.dropout_p = dropout
|
| 900 |
+
|
| 901 |
+
self.qk = nn.Linear(d_model, 2 * d_model, bias=False)
|
| 902 |
+
self.sec = nn.Linear(d_model, d_model, bias=False)
|
| 903 |
+
self.out = nn.Linear(d_model, d_model, bias=False)
|
| 904 |
+
|
| 905 |
+
self.pos_emb = pos_emb
|
| 906 |
+
self.kv_cache = None
|
| 907 |
+
|
| 908 |
+
self.qk_norm = qk_norm
|
| 909 |
+
if self.qk_norm:
|
| 910 |
+
self.q_norm = RMSNorm(self.head_dim)
|
| 911 |
+
self.k_norm = RMSNorm(self.head_dim)
|
| 912 |
+
|
| 913 |
+
self._init_weights()
|
| 914 |
+
|
| 915 |
+
def _init_weights(self):
|
| 916 |
+
nn.init.xavier_uniform_(self.qk.weight)
|
| 917 |
+
nn.init.xavier_uniform_(self.out.weight)
|
| 918 |
+
nn.init.xavier_uniform_(self.sec.weight)
|
| 919 |
+
|
| 920 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 921 |
+
B, L, D = x.shape
|
| 922 |
+
|
| 923 |
+
qk = self.qk(self.sec(x)).view(B, L, 2, self.n_heads, self.head_dim)
|
| 924 |
+
q, k = qk.unbind(dim=2)
|
| 925 |
+
q, k = q.transpose(1, 2), k.transpose(1, 2)
|
| 926 |
+
|
| 927 |
+
if self.qk_norm:
|
| 928 |
+
q = self.q_norm(q)
|
| 929 |
+
k = self.k_norm(k)
|
| 930 |
+
v = x.view(B, L, self.n_heads, self.head_dim).transpose(1, 2)
|
| 931 |
+
|
| 932 |
+
if self.pos_emb is not None:
|
| 933 |
+
q, k = self.pos_emb(q, k, start_pos=0)
|
| 934 |
+
|
| 935 |
+
attn = F.scaled_dot_product_attention(
|
| 936 |
+
q, k, v,
|
| 937 |
+
is_causal=self.causal,
|
| 938 |
+
dropout_p=self.dropout_p if self.training else 0.0,
|
| 939 |
+
)
|
| 940 |
+
return self.out(attn.transpose(1, 2).contiguous().view(B, L, D))
|
| 941 |
+
|
| 942 |
+
def generate(self, x: torch.Tensor) -> torch.Tensor:
|
| 943 |
+
B, L, D = x.shape
|
| 944 |
+
|
| 945 |
+
qk = self.qk(x).view(B, L, 2, self.n_heads, self.head_dim)
|
| 946 |
+
q, k = qk.unbind(dim=2)
|
| 947 |
+
q, k = q.transpose(1, 2), k.transpose(1, 2)
|
| 948 |
+
|
| 949 |
+
if self.qk_norm:
|
| 950 |
+
q = self.q_norm(q)
|
| 951 |
+
k = self.k_norm(k)
|
| 952 |
+
|
| 953 |
+
v = x.view(B, L, self.n_heads, self.head_dim).transpose(1, 2)
|
| 954 |
+
|
| 955 |
+
pos_offset = self.kv_cache[0].shape[2] if self.kv_cache is not None else 0
|
| 956 |
+
|
| 957 |
+
if self.pos_emb is not None:
|
| 958 |
+
q, k = self.pos_emb(q, k, start_pos=pos_offset)
|
| 959 |
+
|
| 960 |
+
if self.kv_cache is not None:
|
| 961 |
+
k_cache, v_cache = self.kv_cache
|
| 962 |
+
k = torch.cat([k_cache, k], dim=2)
|
| 963 |
+
v = torch.cat([v_cache, v], dim=2)
|
| 964 |
+
|
| 965 |
+
self.kv_cache = (k, v)
|
| 966 |
+
|
| 967 |
+
attn = F.scaled_dot_product_attention(
|
| 968 |
+
q, k, v,
|
| 969 |
+
is_causal=self.causal and (L > 1),
|
| 970 |
+
dropout_p=0.0,
|
| 971 |
+
)
|
| 972 |
+
return self.out(attn.transpose(1, 2).contiguous().view(B, L, D))
|
| 973 |
+
|
| 974 |
+
def clear_cache(self):
|
| 975 |
+
self.kv_cache = None
|
| 976 |
+
|
| 977 |
+
def finetune(self):
|
| 978 |
+
self.qk.weight.requires_grad = False
|
| 979 |
+
self.out.weight.requires_grad = False
|
| 980 |
+
|
| 981 |
+
|
| 982 |
+
class MultiheadAttentionMixerNOVLa3(Layer):
|
| 983 |
+
__name__ = "MultiheadAttentionMixerNOV"
|
| 984 |
+
__complexity__ = "O(L^2 d + L d^2)"
|
| 985 |
+
|
| 986 |
+
def __init__(self, d_model: int, n_heads: int, causal: bool = True,
|
| 987 |
+
pos_emb: nn.Module = None, dropout: float = 0.0, qk_norm=False):
|
| 988 |
+
super().__init__()
|
| 989 |
+
assert d_model % n_heads == 0, "d_model must be divisible by n_heads"
|
| 990 |
+
self.causal = causal
|
| 991 |
+
self.d_model = d_model
|
| 992 |
+
self.n_heads = n_heads
|
| 993 |
+
self.head_dim = d_model // n_heads
|
| 994 |
+
self.dropout_p = dropout
|
| 995 |
+
|
| 996 |
+
self.qk = DoubleLinear(d_model, 2 * d_model)
|
| 997 |
+
self.out = nn.Linear(d_model, d_model, bias=False)
|
| 998 |
+
|
| 999 |
+
self.pos_emb = pos_emb
|
| 1000 |
+
self.kv_cache = None
|
| 1001 |
+
|
| 1002 |
+
self.qk_norm = qk_norm
|
| 1003 |
+
if self.qk_norm:
|
| 1004 |
+
self.q_norm = RMSNorm(self.head_dim)
|
| 1005 |
+
self.k_norm = RMSNorm(self.head_dim)
|
| 1006 |
+
|
| 1007 |
+
self._init_weights()
|
| 1008 |
+
|
| 1009 |
+
def _init_weights(self):
|
| 1010 |
+
nn.init.xavier_uniform_(self.out.weight)
|
| 1011 |
+
|
| 1012 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 1013 |
+
B, L, D = x.shape
|
| 1014 |
+
|
| 1015 |
+
qk = self.qk(x).view(B, L, 2, self.n_heads, self.head_dim)
|
| 1016 |
+
q, k = qk.unbind(dim=2)
|
| 1017 |
+
q, k = q.transpose(1, 2), k.transpose(1, 2)
|
| 1018 |
+
|
| 1019 |
+
if self.qk_norm:
|
| 1020 |
+
q = self.q_norm(q)
|
| 1021 |
+
k = self.k_norm(k)
|
| 1022 |
+
|
| 1023 |
+
v = x.view(B, L, self.n_heads, self.head_dim).transpose(1, 2)
|
| 1024 |
+
|
| 1025 |
+
if self.pos_emb is not None:
|
| 1026 |
+
q, k = self.pos_emb(q, k, start_pos=0)
|
| 1027 |
+
|
| 1028 |
+
attn = F.scaled_dot_product_attention(
|
| 1029 |
+
q, k, v,
|
| 1030 |
+
is_causal=self.causal,
|
| 1031 |
+
dropout_p=self.dropout_p if self.training else 0.0,
|
| 1032 |
+
)
|
| 1033 |
+
return self.out(attn.transpose(1, 2).contiguous().view(B, L, D))
|
| 1034 |
+
|
| 1035 |
+
def generate(self, x: torch.Tensor) -> torch.Tensor:
|
| 1036 |
+
B, L, D = x.shape
|
| 1037 |
+
|
| 1038 |
+
qk = self.qk(x).view(B, L, 2, self.n_heads, self.head_dim)
|
| 1039 |
+
q, k = qk.unbind(dim=2)
|
| 1040 |
+
q, k = q.transpose(1, 2), k.transpose(1, 2)
|
| 1041 |
+
|
| 1042 |
+
if self.qk_norm:
|
| 1043 |
+
q = self.q_norm(q)
|
| 1044 |
+
k = self.k_norm(k)
|
| 1045 |
+
|
| 1046 |
+
v = x.view(B, L, self.n_heads, self.head_dim).transpose(1, 2)
|
| 1047 |
+
|
| 1048 |
+
pos_offset = self.kv_cache[0].shape[2] if self.kv_cache is not None else 0
|
| 1049 |
+
|
| 1050 |
+
if self.pos_emb is not None:
|
| 1051 |
+
q, k = self.pos_emb(q, k, start_pos=pos_offset)
|
| 1052 |
+
|
| 1053 |
+
if self.kv_cache is not None:
|
| 1054 |
+
k_cache, v_cache = self.kv_cache
|
| 1055 |
+
k = torch.cat([k_cache, k], dim=2)
|
| 1056 |
+
v = torch.cat([v_cache, v], dim=2)
|
| 1057 |
+
|
| 1058 |
+
self.kv_cache = (k, v)
|
| 1059 |
+
|
| 1060 |
+
attn = F.scaled_dot_product_attention(
|
| 1061 |
+
q, k, v,
|
| 1062 |
+
is_causal=self.causal and (L > 1),
|
| 1063 |
+
dropout_p=0.0,
|
| 1064 |
+
)
|
| 1065 |
+
return self.out(attn.transpose(1, 2).contiguous().view(B, L, D))
|
| 1066 |
+
|
| 1067 |
+
def clear_cache(self):
|
| 1068 |
+
self.kv_cache = None
|
| 1069 |
+
|
| 1070 |
+
def finetune(self):
|
| 1071 |
+
self.qk.weight.requires_grad = False
|
| 1072 |
+
self.out.weight.requires_grad = False
|
| 1073 |
+
|
| 1074 |
+
|
| 1075 |
+
class MultiheadAttentionMixerNOVLa4(Layer):
|
| 1076 |
+
__name__ = "MultiheadAttentionMixerNOV"
|
| 1077 |
+
__complexity__ = "O(L^2 d + L d^2)"
|
| 1078 |
+
|
| 1079 |
+
def __init__(self, d_model: int, n_heads: int, causal: bool = True,
|
| 1080 |
+
pos_emb: nn.Module = None, dropout: float = 0.0, qk_norm=False):
|
| 1081 |
+
super().__init__()
|
| 1082 |
+
assert d_model % n_heads == 0, "d_model must be divisible by n_heads"
|
| 1083 |
+
self.causal = causal
|
| 1084 |
+
self.d_model = d_model
|
| 1085 |
+
self.n_heads = n_heads
|
| 1086 |
+
self.head_dim = d_model // n_heads
|
| 1087 |
+
self.dropout_p = dropout
|
| 1088 |
+
|
| 1089 |
+
self.qk = DoubleLinear(d_model, 2 * d_model)
|
| 1090 |
+
self.out = nn.Linear(d_model, d_model, bias=False)
|
| 1091 |
+
|
| 1092 |
+
self.pos_emb = pos_emb
|
| 1093 |
+
self.kv_cache = None
|
| 1094 |
+
|
| 1095 |
+
self.qk_norm = qk_norm
|
| 1096 |
+
if self.qk_norm:
|
| 1097 |
+
self.q_norm = RMSNorm(self.head_dim)
|
| 1098 |
+
self.k_norm = RMSNorm(self.head_dim)
|
| 1099 |
+
|
| 1100 |
+
self._reset_parameters()
|
| 1101 |
+
|
| 1102 |
+
def _reset_parameters(self):
|
| 1103 |
+
nn.init.xavier_uniform_(self.out.weight)
|
| 1104 |
+
|
| 1105 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 1106 |
+
B, L, D = x.shape
|
| 1107 |
+
|
| 1108 |
+
qk = self.qk(x).view(B, L, 2, self.n_heads, self.head_dim)
|
| 1109 |
+
q, k = qk.unbind(dim=2)
|
| 1110 |
+
q, k = q.transpose(1, 2), k.transpose(1, 2)
|
| 1111 |
+
|
| 1112 |
+
if self.qk_norm:
|
| 1113 |
+
q = self.q_norm(q)
|
| 1114 |
+
k = self.k_norm(k)
|
| 1115 |
+
|
| 1116 |
+
v = x.view(B, L, self.n_heads, self.head_dim).transpose(1, 2)
|
| 1117 |
+
|
| 1118 |
+
if self.pos_emb is not None:
|
| 1119 |
+
q, k = self.pos_emb(q, k, start_pos=0)
|
| 1120 |
+
|
| 1121 |
+
attn = F.scaled_dot_product_attention(
|
| 1122 |
+
q, k, v,
|
| 1123 |
+
is_causal=self.causal,
|
| 1124 |
+
dropout_p=self.dropout_p if self.training else 0.0,
|
| 1125 |
+
)
|
| 1126 |
+
return self.out(attn.transpose(1, 2).contiguous().view(B, L, D))
|
| 1127 |
+
|
| 1128 |
+
def generate(self, x: torch.Tensor) -> torch.Tensor:
|
| 1129 |
+
B, L, D = x.shape
|
| 1130 |
+
|
| 1131 |
+
qk = self.qk(x).view(B, L, 2, self.n_heads, self.head_dim)
|
| 1132 |
+
q, k = qk.unbind(dim=2)
|
| 1133 |
+
q, k = q.transpose(1, 2), k.transpose(1, 2)
|
| 1134 |
+
|
| 1135 |
+
if self.qk_norm:
|
| 1136 |
+
q = self.q_norm(q)
|
| 1137 |
+
k = self.k_norm(k)
|
| 1138 |
+
|
| 1139 |
+
v = x.view(B, L, self.n_heads, self.head_dim).transpose(1, 2)
|
| 1140 |
+
|
| 1141 |
+
pos_offset = self.kv_cache[0].shape[2] if self.kv_cache is not None else 0
|
| 1142 |
+
|
| 1143 |
+
if self.pos_emb is not None:
|
| 1144 |
+
q, k = self.pos_emb(q, k, start_pos=pos_offset)
|
| 1145 |
+
|
| 1146 |
+
if self.kv_cache is not None:
|
| 1147 |
+
k_cache, v_cache = self.kv_cache
|
| 1148 |
+
k = torch.cat([k_cache, k], dim=2)
|
| 1149 |
+
v = torch.cat([v_cache, v], dim=2)
|
| 1150 |
+
|
| 1151 |
+
self.kv_cache = (k, v)
|
| 1152 |
+
|
| 1153 |
+
attn = F.scaled_dot_product_attention(
|
| 1154 |
+
q, k, v,
|
| 1155 |
+
is_causal=self.causal and (L > 1),
|
| 1156 |
+
dropout_p=0.0,
|
| 1157 |
+
)
|
| 1158 |
+
return self.out(attn.transpose(1, 2).contiguous().view(B, L, D))
|
| 1159 |
+
|
| 1160 |
+
def clear_cache(self):
|
| 1161 |
+
self.kv_cache = None
|
| 1162 |
+
|
| 1163 |
+
def finetune(self):
|
| 1164 |
+
self.qk.weight.requires_grad = False
|
| 1165 |
+
self.out.weight.requires_grad = False
|
| 1166 |
+
|
| 1167 |
+
|
| 1168 |
+
class GQAGatedMixer(Layer):
|
| 1169 |
+
"""GQA + sparse per-head output gate + VE + KV cache.
|
| 1170 |
+
|
| 1171 |
+
Combina:
|
| 1172 |
+
- GQA: n_kv_heads << n_heads. KV cache compatto (n_kv_heads heads).
|
| 1173 |
+
- Sparse output gate: sigmoid(gate_proj(x[..., :gate_input_dim])) per-head,
|
| 1174 |
+
broadcast su head_dim (Modded-NanoGPT PR #117 stile).
|
| 1175 |
+
- Value embeddings (ve): aggiunti a v_expanded (full n_heads). Quando ve
|
| 1176 |
+
è attivo la cache memorizza v in formato **expanded** (n_heads, costa
|
| 1177 |
+
n_rep× memoria) per preservare per-step ve correttamente. Quando ve è
|
| 1178 |
+
None, cache in formato compresso standard (n_kv_heads).
|
| 1179 |
+
|
| 1180 |
+
Identity-at-init: q_proj/kv_proj/gate_proj orthogonal, o_proj → 0.
|
| 1181 |
+
"""
|
| 1182 |
+
__name__ = "GQAGatedMixer"
|
| 1183 |
+
__complexity__ = "O(L^2 d + L d^2)"
|
| 1184 |
+
|
| 1185 |
+
def __init__(self, d_model: int, n_heads: int, n_kv_heads: int,
|
| 1186 |
+
causal: bool = True, pos_emb: nn.Module = None,
|
| 1187 |
+
dropout: float = 0.0, qk_norm: bool = False,
|
| 1188 |
+
gate_input_dim: int = 12):
|
| 1189 |
+
super().__init__()
|
| 1190 |
+
if d_model % n_heads != 0:
|
| 1191 |
+
raise ValueError(f"d_model ({d_model}) must be divisible by n_heads ({n_heads})")
|
| 1192 |
+
if n_heads % n_kv_heads != 0:
|
| 1193 |
+
raise ValueError(f"n_heads ({n_heads}) must be divisible by n_kv_heads ({n_kv_heads})")
|
| 1194 |
+
|
| 1195 |
+
self.d_model = d_model
|
| 1196 |
+
self.n_heads = n_heads
|
| 1197 |
+
self.n_kv_heads = n_kv_heads
|
| 1198 |
+
self.head_dim = d_model // n_heads
|
| 1199 |
+
self.n_rep = n_heads // n_kv_heads
|
| 1200 |
+
self.causal = causal
|
| 1201 |
+
self.dropout_p = dropout
|
| 1202 |
+
self.gate_input_dim = gate_input_dim
|
| 1203 |
+
|
| 1204 |
+
self.q_proj = nn.Linear(d_model, d_model, bias=False)
|
| 1205 |
+
self.kv_proj = nn.Linear(d_model, n_kv_heads * self.head_dim * 2, bias=False)
|
| 1206 |
+
self.o_proj = nn.Linear(d_model, d_model, bias=False)
|
| 1207 |
+
self.gate_proj = nn.Linear(gate_input_dim, n_heads, bias=False)
|
| 1208 |
+
|
| 1209 |
+
self.pos_emb = pos_emb
|
| 1210 |
+
self.kv_cache = None
|
| 1211 |
+
|
| 1212 |
+
self.qk_norm = qk_norm
|
| 1213 |
+
if qk_norm:
|
| 1214 |
+
self.q_norm = RMSNorm(self.head_dim)
|
| 1215 |
+
self.k_norm = RMSNorm(self.head_dim)
|
| 1216 |
+
|
| 1217 |
+
self._init_weights()
|
| 1218 |
+
|
| 1219 |
+
def _init_weights(self):
|
| 1220 |
+
# Identity-at-init
|
| 1221 |
+
nn.init.orthogonal_(self.q_proj.weight)
|
| 1222 |
+
nn.init.orthogonal_(self.kv_proj.weight)
|
| 1223 |
+
nn.init.zeros_(self.o_proj.weight)
|
| 1224 |
+
nn.init.orthogonal_(self.gate_proj.weight)
|
| 1225 |
+
|
| 1226 |
+
_reset_parameters = _init_weights
|
| 1227 |
+
|
| 1228 |
+
def _expand_kv(self, x: torch.Tensor) -> torch.Tensor:
|
| 1229 |
+
if self.n_rep == 1:
|
| 1230 |
+
return x
|
| 1231 |
+
B, H, L, D = x.shape
|
| 1232 |
+
return (x.unsqueeze(2)
|
| 1233 |
+
.expand(B, H, self.n_rep, L, D)
|
| 1234 |
+
.reshape(B, H * self.n_rep, L, D))
|
| 1235 |
+
|
| 1236 |
+
def _apply_gate(self, attn: torch.Tensor, x_gate_in: torch.Tensor) -> torch.Tensor:
|
| 1237 |
+
# attn: [B, n_heads, L, head_dim] | x_gate_in: [B, L, gate_input_dim]
|
| 1238 |
+
gate = torch.sigmoid(self.gate_proj(x_gate_in)) # [B, L, H]
|
| 1239 |
+
return attn * gate.transpose(1, 2).unsqueeze(-1)
|
| 1240 |
+
|
| 1241 |
+
def forward(self, x: torch.Tensor, ve: torch.Tensor = None) -> torch.Tensor:
|
| 1242 |
+
B, T, D = x.shape
|
| 1243 |
+
|
| 1244 |
+
q = self.q_proj(x).view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
|
| 1245 |
+
kv = self.kv_proj(x).view(B, T, self.n_kv_heads, 2, self.head_dim)
|
| 1246 |
+
k, v = kv.unbind(dim=3)
|
| 1247 |
+
k, v = k.transpose(1, 2), v.transpose(1, 2)
|
| 1248 |
+
|
| 1249 |
+
if self.qk_norm:
|
| 1250 |
+
q = self.q_norm(q)
|
| 1251 |
+
k = self.k_norm(k)
|
| 1252 |
+
if self.pos_emb is not None:
|
| 1253 |
+
q, k = self.pos_emb(q, k, start_pos=0)
|
| 1254 |
+
|
| 1255 |
+
k_exp = self._expand_kv(k)
|
| 1256 |
+
v_exp = self._expand_kv(v)
|
| 1257 |
+
if ve is not None:
|
| 1258 |
+
v_exp = v_exp + ve.view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
|
| 1259 |
+
|
| 1260 |
+
attn = F.scaled_dot_product_attention(
|
| 1261 |
+
q, k_exp, v_exp, is_causal=self.causal,
|
| 1262 |
+
dropout_p=self.dropout_p if self.training else 0.0,
|
| 1263 |
+
)
|
| 1264 |
+
attn = self._apply_gate(attn, x[..., :self.gate_input_dim])
|
| 1265 |
+
return self.o_proj(attn.transpose(1, 2).reshape(B, T, D))
|
| 1266 |
+
|
| 1267 |
+
def generate(self, x: torch.Tensor, ve: torch.Tensor = None) -> torch.Tensor:
|
| 1268 |
+
B, T, D = x.shape
|
| 1269 |
+
|
| 1270 |
+
q = self.q_proj(x).view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
|
| 1271 |
+
kv = self.kv_proj(x).view(B, T, self.n_kv_heads, 2, self.head_dim)
|
| 1272 |
+
k_new, v_new = kv.unbind(dim=3)
|
| 1273 |
+
k_new, v_new = k_new.transpose(1, 2), v_new.transpose(1, 2)
|
| 1274 |
+
|
| 1275 |
+
if self.qk_norm:
|
| 1276 |
+
q = self.q_norm(q)
|
| 1277 |
+
k_new = self.k_norm(k_new)
|
| 1278 |
+
|
| 1279 |
+
pos_offset = self.kv_cache[0].shape[2] if self.kv_cache is not None else 0
|
| 1280 |
+
if self.pos_emb is not None:
|
| 1281 |
+
q, k_new = self.pos_emb(q, k_new, start_pos=pos_offset)
|
| 1282 |
+
|
| 1283 |
+
# Cache k sempre in formato raw (n_kv_heads). Cache v:
|
| 1284 |
+
# - se ve attivo: formato EXPANDED (n_heads), perché ve si applica per-step
|
| 1285 |
+
# - altrimenti: raw (n_kv_heads), standard GQA
|
| 1286 |
+
if ve is not None:
|
| 1287 |
+
v_new = self._expand_kv(v_new) + ve.view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
|
| 1288 |
+
|
| 1289 |
+
if self.kv_cache is not None:
|
| 1290 |
+
k_cache, v_cache = self.kv_cache
|
| 1291 |
+
k_new = torch.cat([k_cache, k_new], dim=2)
|
| 1292 |
+
v_new = torch.cat([v_cache, v_new], dim=2)
|
| 1293 |
+
self.kv_cache = (k_new, v_new)
|
| 1294 |
+
|
| 1295 |
+
# Attention: k_new sempre da espandere; v_new già expanded se ve, raw altrimenti
|
| 1296 |
+
k_for_attn = self._expand_kv(k_new)
|
| 1297 |
+
v_for_attn = v_new if ve is not None else self._expand_kv(v_new)
|
| 1298 |
+
|
| 1299 |
+
attn = F.scaled_dot_product_attention(
|
| 1300 |
+
q, k_for_attn, v_for_attn,
|
| 1301 |
+
is_causal=self.causal and (T > 1), dropout_p=0.0,
|
| 1302 |
+
)
|
| 1303 |
+
attn = self._apply_gate(attn, x[..., :self.gate_input_dim])
|
| 1304 |
+
return self.o_proj(attn.transpose(1, 2).reshape(B, T, D))
|
| 1305 |
+
|
| 1306 |
+
def clear_cache(self):
|
| 1307 |
+
self.kv_cache = None
|
| 1308 |
+
|
| 1309 |
+
|
| 1310 |
+
class GroupedQueryAttention(Layer):
|
| 1311 |
+
__name__ = "GroupedQueryAttention"
|
| 1312 |
+
__complexity__ = "O(L^2 d + L d^2)"
|
| 1313 |
+
|
| 1314 |
+
def __init__(self, d_model: int, n_heads: int, n_kv_heads: int,
|
| 1315 |
+
causal: bool = True, pos_emb: nn.Module = None,
|
| 1316 |
+
dropout: float = 0.0, bias: bool = False, qk_norm=False):
|
| 1317 |
+
super().__init__()
|
| 1318 |
+
if d_model % n_heads != 0:
|
| 1319 |
+
raise ValueError(f"d_model ({d_model}) must be divisible by n_heads ({n_heads})")
|
| 1320 |
+
if n_heads % n_kv_heads != 0:
|
| 1321 |
+
raise ValueError(f"n_heads ({n_heads}) must be divisible by n_kv_heads ({n_kv_heads})")
|
| 1322 |
+
|
| 1323 |
+
self.d_model = d_model
|
| 1324 |
+
self.n_heads = n_heads
|
| 1325 |
+
self.n_kv_heads = n_kv_heads
|
| 1326 |
+
self.head_dim = d_model // n_heads
|
| 1327 |
+
self.n_rep = n_heads // n_kv_heads
|
| 1328 |
+
self.causal = causal
|
| 1329 |
+
self.dropout_p = dropout
|
| 1330 |
+
|
| 1331 |
+
self.q_proj = nn.Linear(d_model, d_model, bias=bias)
|
| 1332 |
+
self.kv_proj = nn.Linear(d_model, n_kv_heads * self.head_dim * 2, bias=bias)
|
| 1333 |
+
self.o_proj = nn.Linear(d_model, d_model, bias=bias)
|
| 1334 |
+
|
| 1335 |
+
self.qk_norm = qk_norm
|
| 1336 |
+
if self.qk_norm:
|
| 1337 |
+
self.q_norm = RMSNorm(self.head_dim)
|
| 1338 |
+
self.k_norm = RMSNorm(self.head_dim)
|
| 1339 |
+
|
| 1340 |
+
self.pos_emb = pos_emb
|
| 1341 |
+
self.kv_cache = None
|
| 1342 |
+
|
| 1343 |
+
self._reset_parameters()
|
| 1344 |
+
|
| 1345 |
+
def _reset_parameters(self):
|
| 1346 |
+
nn.init.xavier_uniform_(self.q_proj.weight)
|
| 1347 |
+
nn.init.xavier_uniform_(self.kv_proj.weight)
|
| 1348 |
+
nn.init.xavier_uniform_(self.o_proj.weight)
|
| 1349 |
+
if self.q_proj.bias is not None:
|
| 1350 |
+
nn.init.zeros_(self.q_proj.bias)
|
| 1351 |
+
nn.init.zeros_(self.kv_proj.bias)
|
| 1352 |
+
nn.init.zeros_(self.o_proj.bias)
|
| 1353 |
+
|
| 1354 |
+
def _expand_kv(self, x: torch.Tensor) -> torch.Tensor:
|
| 1355 |
+
if self.n_rep == 1:
|
| 1356 |
+
return x
|
| 1357 |
+
B, H, L, D = x.shape
|
| 1358 |
+
return (x.unsqueeze(2)
|
| 1359 |
+
.expand(B, H, self.n_rep, L, D)
|
| 1360 |
+
.reshape(B, H * self.n_rep, L, D))
|
| 1361 |
+
|
| 1362 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 1363 |
+
B, T, C = x.shape
|
| 1364 |
+
|
| 1365 |
+
q = self.q_proj(x).view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
|
| 1366 |
+
|
| 1367 |
+
kv = self.kv_proj(x).view(B, T, self.n_kv_heads, 2, self.head_dim)
|
| 1368 |
+
k, v = kv.unbind(dim=3)
|
| 1369 |
+
k, v = k.transpose(1, 2), v.transpose(1, 2)
|
| 1370 |
+
|
| 1371 |
+
if self.qk_norm:
|
| 1372 |
+
q = self.q_norm(q)
|
| 1373 |
+
k = self.k_norm(k)
|
| 1374 |
+
|
| 1375 |
+
if self.pos_emb is not None:
|
| 1376 |
+
q, k = self.pos_emb(q, k, start_pos=0)
|
| 1377 |
+
|
| 1378 |
+
k, v = self._expand_kv(k), self._expand_kv(v)
|
| 1379 |
+
|
| 1380 |
+
attn = F.scaled_dot_product_attention(
|
| 1381 |
+
q, k, v,
|
| 1382 |
+
is_causal=self.causal,
|
| 1383 |
+
dropout_p=self.dropout_p if self.training else 0.0,
|
| 1384 |
+
)
|
| 1385 |
+
return self.o_proj(attn.transpose(1, 2).contiguous().view(B, T, C))
|
| 1386 |
+
|
| 1387 |
+
def generate(self, x: torch.Tensor, ve=None) -> torch.Tensor:
|
| 1388 |
+
B, T, C = x.shape
|
| 1389 |
+
|
| 1390 |
+
q = self.q_proj(x).view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
|
| 1391 |
+
|
| 1392 |
+
kv = self.kv_proj(x).view(B, T, self.n_kv_heads, 2, self.head_dim)
|
| 1393 |
+
k, v = kv.unbind(dim=3)
|
| 1394 |
+
k, v = k.transpose(1, 2), v.transpose(1, 2)
|
| 1395 |
+
|
| 1396 |
+
if self.qk_norm:
|
| 1397 |
+
q = self.q_norm(q)
|
| 1398 |
+
k = self.k_norm(k)
|
| 1399 |
+
|
| 1400 |
+
pos_offset = self.kv_cache[0].shape[2] if self.kv_cache is not None else 0
|
| 1401 |
+
|
| 1402 |
+
if self.pos_emb is not None:
|
| 1403 |
+
q, k = self.pos_emb(q, k, start_pos=pos_offset)
|
| 1404 |
+
|
| 1405 |
+
if ve is not None:
|
| 1406 |
+
raise NotImplementedError(
|
| 1407 |
+
"GroupedQueryAttention.forward does not support value embeddings; "
|
| 1408 |
+
"use MultiheadAttentionMixer if you need ve."
|
| 1409 |
+
)
|
| 1410 |
+
|
| 1411 |
+
if self.kv_cache is not None:
|
| 1412 |
+
k_cache, v_cache = self.kv_cache
|
| 1413 |
+
k = torch.cat([k_cache, k], dim=2)
|
| 1414 |
+
v = torch.cat([v_cache, v], dim=2)
|
| 1415 |
+
|
| 1416 |
+
self.kv_cache = (k, v)
|
| 1417 |
+
|
| 1418 |
+
k, v = self._expand_kv(k), self._expand_kv(v)
|
| 1419 |
+
|
| 1420 |
+
attn = F.scaled_dot_product_attention(
|
| 1421 |
+
q, k, v,
|
| 1422 |
+
is_causal=self.causal and (T > 1),
|
| 1423 |
+
dropout_p=0.0,
|
| 1424 |
+
)
|
| 1425 |
+
return self.o_proj(attn.transpose(1, 2).contiguous().view(B, T, C))
|
| 1426 |
+
|
| 1427 |
+
def clear_cache(self):
|
| 1428 |
+
self.kv_cache = None
|
| 1429 |
+
|
| 1430 |
+
def finetune(self):
|
| 1431 |
+
self.q_proj.weight.requires_grad = False
|
| 1432 |
+
self.kv_proj.weight.requires_grad = False
|
| 1433 |
+
self.o_proj.weight.requires_grad = False
|
| 1434 |
+
|
| 1435 |
+
|
| 1436 |
+
class GroupedQueryAttentionNOV(Layer):
|
| 1437 |
+
|
| 1438 |
+
def __init__(
|
| 1439 |
+
self,
|
| 1440 |
+
d_model: int,
|
| 1441 |
+
n_heads: int,
|
| 1442 |
+
n_kv_heads: int,
|
| 1443 |
+
dropout: float = 0.0,
|
| 1444 |
+
bias: bool = False,
|
| 1445 |
+
causal: bool = True,
|
| 1446 |
+
rope: bool = False,
|
| 1447 |
+
qk_norm: bool = False
|
| 1448 |
+
):
|
| 1449 |
+
super().__init__()
|
| 1450 |
+
|
| 1451 |
+
self.d_model = d_model
|
| 1452 |
+
self.num_heads = n_heads
|
| 1453 |
+
self.num_kv_heads = n_kv_heads
|
| 1454 |
+
self.head_dim = d_model // n_heads
|
| 1455 |
+
self.dropout_prob = dropout
|
| 1456 |
+
self.causal = causal
|
| 1457 |
+
|
| 1458 |
+
self.n_rep = self.num_heads // self.num_kv_heads
|
| 1459 |
+
|
| 1460 |
+
if self.d_model % self.num_heads != 0:
|
| 1461 |
+
raise ValueError(f"embed_dim ({d_model}) must be divisible by num_heads ({n_heads})")
|
| 1462 |
+
if self.num_heads % self.num_kv_heads != 0:
|
| 1463 |
+
raise ValueError(f"num_heads ({n_heads}) must be divisible by num_kv_heads ({n_kv_heads})")
|
| 1464 |
+
|
| 1465 |
+
self.q_proj = nn.Linear(d_model, d_model, bias=bias)
|
| 1466 |
+
self.k_proj = nn.Linear(d_model, n_kv_heads * self.head_dim, bias=bias)
|
| 1467 |
+
self.o_proj = nn.Linear(d_model, d_model, bias=bias)
|
| 1468 |
+
|
| 1469 |
+
self.rope = RoPE(self.head_dim) if rope else None
|
| 1470 |
+
|
| 1471 |
+
self.qk_norm = qk_norm
|
| 1472 |
+
if self.qk_norm:
|
| 1473 |
+
self.q_norm = RMSNorm(self.head_dim)
|
| 1474 |
+
self.k_norm = RMSNorm(self.head_dim)
|
| 1475 |
+
|
| 1476 |
+
self._reset_parameters()
|
| 1477 |
+
|
| 1478 |
+
def _reset_parameters(self):
|
| 1479 |
+
nn.init.xavier_uniform_(self.q_proj.weight)
|
| 1480 |
+
nn.init.xavier_uniform_(self.k_proj.weight)
|
| 1481 |
+
nn.init.xavier_uniform_(self.o_proj.weight)
|
| 1482 |
+
if self.q_proj.bias is not None:
|
| 1483 |
+
nn.init.constant_(self.q_proj.bias, 0)
|
| 1484 |
+
nn.init.constant_(self.k_proj.bias, 0)
|
| 1485 |
+
nn.init.constant_(self.o_proj.bias, 0)
|
| 1486 |
+
|
| 1487 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 1488 |
+
B, T, C = x.shape
|
| 1489 |
+
|
| 1490 |
+
q = self.q_proj(x)
|
| 1491 |
+
k = self.k_proj(x)
|
| 1492 |
+
|
| 1493 |
+
q = q.view(B, T, self.num_heads, self.head_dim).transpose(1, 2)
|
| 1494 |
+
k = k.view(B, T, self.num_kv_heads, self.head_dim).transpose(1, 2)
|
| 1495 |
+
v = x.view(B, T, self.num_heads, self.head_dim).transpose(1, 2)
|
| 1496 |
+
|
| 1497 |
+
if self.qk_norm:
|
| 1498 |
+
q = self.q_norm(q)
|
| 1499 |
+
k = self.k_norm(k)
|
| 1500 |
+
|
| 1501 |
+
if self.rope is not None:
|
| 1502 |
+
q, k = self.rope(q, k)
|
| 1503 |
+
|
| 1504 |
+
if self.n_rep > 1:
|
| 1505 |
+
k = k[:, :, None, :, :].expand(B, self.num_kv_heads, self.n_rep, T, self.head_dim)
|
| 1506 |
+
k = k.reshape(B, self.num_heads, T, self.head_dim)
|
| 1507 |
+
|
| 1508 |
+
attn_output = F.scaled_dot_product_attention(
|
| 1509 |
+
q, k, v,
|
| 1510 |
+
dropout_p=self.dropout_prob if self.training else 0.0,
|
| 1511 |
+
is_causal=self.causal
|
| 1512 |
+
)
|
| 1513 |
+
|
| 1514 |
+
attn_output = attn_output.transpose(1, 2).contiguous().view(B, T, C)
|
| 1515 |
+
return self.o_proj(attn_output)
|
| 1516 |
+
|
| 1517 |
+
|
| 1518 |
+
class GroupedQueryAttentionNOV2(Layer):
|
| 1519 |
+
def __init__(
|
| 1520 |
+
self,
|
| 1521 |
+
d_model: int,
|
| 1522 |
+
n_heads: int,
|
| 1523 |
+
n_kv_heads: int,
|
| 1524 |
+
dropout: float = 0.0,
|
| 1525 |
+
bias: bool = False,
|
| 1526 |
+
causal: bool = True,
|
| 1527 |
+
rope: bool = False
|
| 1528 |
+
):
|
| 1529 |
+
super().__init__()
|
| 1530 |
+
|
| 1531 |
+
self.d_model = d_model
|
| 1532 |
+
self.num_heads = n_heads
|
| 1533 |
+
self.num_kv_heads = n_kv_heads
|
| 1534 |
+
self.head_dim = d_model // n_heads
|
| 1535 |
+
self.dropout_prob = dropout
|
| 1536 |
+
self.causal = causal
|
| 1537 |
+
|
| 1538 |
+
self.n_rep = self.num_heads // self.num_kv_heads
|
| 1539 |
+
|
| 1540 |
+
if self.d_model % self.num_heads != 0:
|
| 1541 |
+
raise ValueError(f"embed_dim ({d_model}) must be divisible by num_heads ({n_heads})")
|
| 1542 |
+
if self.num_heads % self.num_kv_heads != 0:
|
| 1543 |
+
raise ValueError(f"num_heads ({n_heads}) must be divisible by num_kv_heads ({n_kv_heads})")
|
| 1544 |
+
|
| 1545 |
+
self.q_proj = nn.Linear(d_model, d_model, bias=bias)
|
| 1546 |
+
self.k_proj = nn.Linear(d_model, n_kv_heads * self.head_dim, bias=bias)
|
| 1547 |
+
self.o_proj = nn.Linear(d_model, d_model, bias=bias)
|
| 1548 |
+
self.trainv = False
|
| 1549 |
+
self.v_proj = nn.Linear(d_model, d_model, bias=bias)
|
| 1550 |
+
|
| 1551 |
+
self.rope = RoPE(self.head_dim) if rope else None
|
| 1552 |
+
|
| 1553 |
+
self._reset_parameters()
|
| 1554 |
+
|
| 1555 |
+
def _reset_parameters(self):
|
| 1556 |
+
nn.init.xavier_uniform_(self.q_proj.weight)
|
| 1557 |
+
nn.init.xavier_uniform_(self.k_proj.weight)
|
| 1558 |
+
nn.init.xavier_uniform_(self.o_proj.weight)
|
| 1559 |
+
nn.init.eye_(self.v_proj.weight)
|
| 1560 |
+
if self.q_proj.bias is not None:
|
| 1561 |
+
nn.init.constant_(self.q_proj.bias, 0)
|
| 1562 |
+
nn.init.constant_(self.k_proj.bias, 0)
|
| 1563 |
+
nn.init.constant_(self.v_proj.bias, 0)
|
| 1564 |
+
nn.init.constant_(self.o_proj.bias, 0)
|
| 1565 |
+
|
| 1566 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 1567 |
+
B, T, C = x.shape
|
| 1568 |
+
|
| 1569 |
+
q = self.q_proj(x)
|
| 1570 |
+
k = self.k_proj(x)
|
| 1571 |
+
|
| 1572 |
+
q = q.view(B, T, self.num_heads, self.head_dim).transpose(1, 2)
|
| 1573 |
+
k = k.view(B, T, self.num_kv_heads, self.head_dim).transpose(1, 2)
|
| 1574 |
+
|
| 1575 |
+
if not self.trainv:
|
| 1576 |
+
v = x.view(B, T, self.num_heads, self.head_dim).transpose(1, 2)
|
| 1577 |
+
else:
|
| 1578 |
+
v = self.v_proj(x).view(B, T, self.num_heads, self.head_dim).transpose(1, 2)
|
| 1579 |
+
|
| 1580 |
+
# Apply RoPE BEFORE expansion
|
| 1581 |
+
if self.rope is not None:
|
| 1582 |
+
q, k = self.rope(q, k)
|
| 1583 |
+
|
| 1584 |
+
if self.n_rep > 1:
|
| 1585 |
+
k = k[:, :, None, :, :].expand(B, self.num_kv_heads, self.n_rep, T, self.head_dim)
|
| 1586 |
+
k = k.reshape(B, self.num_heads, T, self.head_dim)
|
| 1587 |
+
|
| 1588 |
+
attn_output = F.scaled_dot_product_attention(
|
| 1589 |
+
q, k, v,
|
| 1590 |
+
dropout_p=self.dropout_prob if self.training else 0.0,
|
| 1591 |
+
is_causal=self.causal
|
| 1592 |
+
)
|
| 1593 |
+
|
| 1594 |
+
attn_output = attn_output.transpose(1, 2).contiguous().view(B, T, C)
|
| 1595 |
+
return self.o_proj(attn_output)
|
| 1596 |
+
|
| 1597 |
+
|
| 1598 |
+
class GroupedQueryAttentionNOO(Layer):
|
| 1599 |
+
def __init__(
|
| 1600 |
+
self,
|
| 1601 |
+
d_model: int,
|
| 1602 |
+
n_heads: int,
|
| 1603 |
+
n_kv_heads: int,
|
| 1604 |
+
dropout: float = 0.0,
|
| 1605 |
+
bias: bool = False,
|
| 1606 |
+
causal: bool = True,
|
| 1607 |
+
rope: bool = False
|
| 1608 |
+
):
|
| 1609 |
+
super().__init__()
|
| 1610 |
+
|
| 1611 |
+
self.d_model = d_model
|
| 1612 |
+
self.num_heads = n_heads
|
| 1613 |
+
self.num_kv_heads = n_kv_heads
|
| 1614 |
+
self.head_dim = d_model // n_heads
|
| 1615 |
+
self.dropout_prob = dropout
|
| 1616 |
+
self.causal = causal
|
| 1617 |
+
|
| 1618 |
+
self.n_rep = self.num_heads // self.num_kv_heads
|
| 1619 |
+
|
| 1620 |
+
if self.d_model % self.num_heads != 0:
|
| 1621 |
+
raise ValueError(f"embed_dim ({d_model}) must be divisible by num_heads ({n_heads})")
|
| 1622 |
+
if self.num_heads % self.num_kv_heads != 0:
|
| 1623 |
+
raise ValueError(f"num_heads ({n_heads}) must be divisible by num_kv_heads ({n_kv_heads})")
|
| 1624 |
+
|
| 1625 |
+
self.q_proj = nn.Linear(d_model, d_model, bias=bias)
|
| 1626 |
+
self.kv_proj = nn.Linear(d_model, n_kv_heads * self.head_dim * 2, bias=bias)
|
| 1627 |
+
|
| 1628 |
+
self.rope = RoPE(self.head_dim) if rope else None
|
| 1629 |
+
|
| 1630 |
+
self._reset_parameters()
|
| 1631 |
+
|
| 1632 |
+
def _reset_parameters(self):
|
| 1633 |
+
nn.init.xavier_uniform_(self.q_proj.weight)
|
| 1634 |
+
nn.init.xavier_uniform_(self.kv_proj.weight)
|
| 1635 |
+
if self.q_proj.bias is not None:
|
| 1636 |
+
nn.init.constant_(self.q_proj.bias, 0)
|
| 1637 |
+
nn.init.constant_(self.kv_proj.bias, 0)
|
| 1638 |
+
|
| 1639 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 1640 |
+
B, T, C = x.shape
|
| 1641 |
+
|
| 1642 |
+
q = self.q_proj(x)
|
| 1643 |
+
kv = self.kv_proj(x)
|
| 1644 |
+
|
| 1645 |
+
q = q.view(B, T, self.num_heads, self.head_dim).transpose(1, 2)
|
| 1646 |
+
|
| 1647 |
+
kv = kv.view(B, T, self.num_kv_heads, 2, self.head_dim)
|
| 1648 |
+
k, v = kv.unbind(dim=3)
|
| 1649 |
+
k = k.transpose(1, 2)
|
| 1650 |
+
v = v.transpose(1, 2)
|
| 1651 |
+
|
| 1652 |
+
# Apply RoPE BEFORE expansion
|
| 1653 |
+
if self.rope is not None:
|
| 1654 |
+
q, k = self.rope(q, k)
|
| 1655 |
+
|
| 1656 |
+
if self.n_rep > 1:
|
| 1657 |
+
k = k[:, :, None, :, :].expand(B, self.num_kv_heads, self.n_rep, T, self.head_dim)
|
| 1658 |
+
v = v[:, :, None, :, :].expand(B, self.num_kv_heads, self.n_rep, T, self.head_dim)
|
| 1659 |
+
k = k.reshape(B, self.num_heads, T, self.head_dim)
|
| 1660 |
+
v = v.reshape(B, self.num_heads, T, self.head_dim)
|
| 1661 |
+
|
| 1662 |
+
attn_output = F.scaled_dot_product_attention(
|
| 1663 |
+
q, k, v,
|
| 1664 |
+
dropout_p=self.dropout_prob if self.training else 0.0,
|
| 1665 |
+
is_causal=self.causal
|
| 1666 |
+
)
|
| 1667 |
+
|
| 1668 |
+
attn_output = attn_output.transpose(1, 2).contiguous().view(B, T, C)
|
| 1669 |
+
return attn_output
|
| 1670 |
+
|
| 1671 |
+
|
| 1672 |
+
class MultiheadLatentAttentionMixer(Layer): # Changed Layer to nn.Module for standard torch
|
| 1673 |
+
__name__ = "MultiheadLatentAttentionMixer"
|
| 1674 |
+
# Adjusted complexity notation
|
| 1675 |
+
__complexity__ = "O(L^2 d + L d * d_kv_lora)"
|
| 1676 |
+
|
| 1677 |
+
def __init__(self, d_model: int, n_heads: int, d_kv_lora: int, causal: bool, rope=False, dropout=0.1):
|
| 1678 |
+
super().__init__()
|
| 1679 |
+
self.causal = causal
|
| 1680 |
+
self.d_model = d_model
|
| 1681 |
+
self.n_heads = n_heads
|
| 1682 |
+
self.head_dim = d_model // n_heads
|
| 1683 |
+
self.d_kv_lora = d_kv_lora
|
| 1684 |
+
self.rope_enabled = rope
|
| 1685 |
+
|
| 1686 |
+
self.q = nn.Linear(d_model, d_model, bias=False)
|
| 1687 |
+
|
| 1688 |
+
self.kv_compress = nn.Linear(d_model, d_kv_lora, bias=False)
|
| 1689 |
+
self.k_content_decompress = nn.Linear(d_kv_lora, d_model, bias=False)
|
| 1690 |
+
self.v_decompress = nn.Linear(d_kv_lora, d_model, bias=False)
|
| 1691 |
+
|
| 1692 |
+
self.out = nn.Linear(d_model, d_model, bias=False)
|
| 1693 |
+
|
| 1694 |
+
if self.rope_enabled:
|
| 1695 |
+
self.rope = RoPE(self.head_dim)
|
| 1696 |
+
|
| 1697 |
+
self.dropout = nn.Dropout(dropout)
|
| 1698 |
+
|
| 1699 |
+
nn.init.zeros_(self.out.weight)
|
| 1700 |
+
|
| 1701 |
+
def forward(self, x: torch.Tensor):
|
| 1702 |
+
B, L, D = x.shape
|
| 1703 |
+
H = self.n_heads
|
| 1704 |
+
HD = self.head_dim
|
| 1705 |
+
|
| 1706 |
+
q = self.q(x).view(B, L, H, HD).transpose(1, 2)
|
| 1707 |
+
|
| 1708 |
+
c_kv = self.kv_compress(x)
|
| 1709 |
+
k = self.k_content_decompress(c_kv).view(B, L, H, HD).transpose(1, 2)
|
| 1710 |
+
v = self.v_decompress(c_kv).view(B, L, H, HD).transpose(1, 2)
|
| 1711 |
+
if self.rope_enabled:
|
| 1712 |
+
q, k = self.rope(q, k)
|
| 1713 |
+
|
| 1714 |
+
attn = F.scaled_dot_product_attention(
|
| 1715 |
+
q, k, v,
|
| 1716 |
+
dropout_p=self.dropout.p if self.training else 0.0,
|
| 1717 |
+
is_causal=self.causal
|
| 1718 |
+
)
|
| 1719 |
+
|
| 1720 |
+
attn = attn.transpose(1, 2).contiguous().view(B, L, D)
|
| 1721 |
+
|
| 1722 |
+
return self.out(attn)
|
| 1723 |
+
|
| 1724 |
+
|
| 1725 |
+
class CausalMultiheadAttentionMixer2NOK(nn.Module):
|
| 1726 |
+
__name__ = "CausalMultiheadAttentionMixer"
|
| 1727 |
+
|
| 1728 |
+
def __init__(self, d_model: int, n_heads: int, causal=True, rope=False, dropout=0.0):
|
| 1729 |
+
super().__init__()
|
| 1730 |
+
self.d_model = d_model
|
| 1731 |
+
self.n_heads = n_heads
|
| 1732 |
+
self.head_dim = d_model // n_heads
|
| 1733 |
+
self.dropout_p = dropout
|
| 1734 |
+
|
| 1735 |
+
self.qv = nn.Linear(d_model, 2 * d_model, bias=False)
|
| 1736 |
+
self.out = nn.Linear(d_model, d_model, bias=False)
|
| 1737 |
+
|
| 1738 |
+
self.rope = RoPE(self.head_dim) if rope else None
|
| 1739 |
+
self._reset_parameters()
|
| 1740 |
+
|
| 1741 |
+
def _reset_parameters(self):
|
| 1742 |
+
nn.init.xavier_uniform_(self.qv.weight)
|
| 1743 |
+
nn.init.xavier_uniform_(self.out.weight)
|
| 1744 |
+
|
| 1745 |
+
def forward(self, x: torch.Tensor):
|
| 1746 |
+
B, L, D = x.shape
|
| 1747 |
+
|
| 1748 |
+
qv = self.qv(x)
|
| 1749 |
+
|
| 1750 |
+
qv = qv.view(B, L, 2, self.n_heads, self.head_dim)
|
| 1751 |
+
|
| 1752 |
+
q, v = qv.unbind(dim=2)
|
| 1753 |
+
|
| 1754 |
+
q = q.transpose(1, 2)
|
| 1755 |
+
k = x.view(B, L, self.n_heads, self.head_dim).transpose(1, 2)
|
| 1756 |
+
v = v.transpose(1, 2)
|
| 1757 |
+
|
| 1758 |
+
if self.rope is not None:
|
| 1759 |
+
q, k = self.rope(q, k)
|
| 1760 |
+
|
| 1761 |
+
q = q.contiguous()
|
| 1762 |
+
k = k.contiguous()
|
| 1763 |
+
v = v.contiguous()
|
| 1764 |
+
|
| 1765 |
+
attn = F.scaled_dot_product_attention(
|
| 1766 |
+
q, k, v,
|
| 1767 |
+
is_causal=True,
|
| 1768 |
+
dropout_p=self.dropout_p if self.training else 0.0
|
| 1769 |
+
)
|
| 1770 |
+
|
| 1771 |
+
attn = attn.transpose(1, 2).contiguous().view(B, L, D)
|
| 1772 |
+
|
| 1773 |
+
return self.out(attn)
|
| 1774 |
+
|
| 1775 |
+
|
| 1776 |
+
class MultiheadDecoupledAttention(Layer):
|
| 1777 |
+
__name__ = "DecoupledSelfAttention"
|
| 1778 |
+
__complexity__ = "O(L^2 * d_qk + L * d_model * (d_qk + d_v))"
|
| 1779 |
+
|
| 1780 |
+
def __init__(self, d_model: int, n_heads: int, qk_dim: int, causal: bool, rope=False, dropout=0.1):
|
| 1781 |
+
super().__init__()
|
| 1782 |
+
self.d_model = d_model
|
| 1783 |
+
self.n_heads = n_heads
|
| 1784 |
+
self.qk_dim = qk_dim
|
| 1785 |
+
self.v_dim = d_model // n_heads
|
| 1786 |
+
self.causal = causal
|
| 1787 |
+
self.rope_enabled = rope
|
| 1788 |
+
|
| 1789 |
+
self.qk_proj = nn.Linear(d_model, n_heads * 2 * qk_dim, bias=False)
|
| 1790 |
+
|
| 1791 |
+
self.v_proj = nn.Linear(d_model, n_heads * self.v_dim, bias=False)
|
| 1792 |
+
|
| 1793 |
+
self.out_proj = nn.Linear(n_heads * self.v_dim, d_model, bias=False)
|
| 1794 |
+
|
| 1795 |
+
self.dropout = nn.Dropout(dropout)
|
| 1796 |
+
|
| 1797 |
+
if self.rope_enabled:
|
| 1798 |
+
self.rope = RoPE(qk_dim)
|
| 1799 |
+
|
| 1800 |
+
nn.init.zeros_(self.out_proj.weight)
|
| 1801 |
+
|
| 1802 |
+
def forward(self, x: torch.Tensor):
|
| 1803 |
+
B, L, _ = x.shape
|
| 1804 |
+
H = self.n_heads
|
| 1805 |
+
|
| 1806 |
+
q, k = self.qk_proj(x).view(B, L, H, 2 * self.qk_dim).transpose(1, 2).chunk(2, -1)
|
| 1807 |
+
|
| 1808 |
+
v = self.v_proj(x).view(B, L, H, self.v_dim).transpose(1, 2)
|
| 1809 |
+
|
| 1810 |
+
if self.rope_enabled:
|
| 1811 |
+
q, k = self.rope(q, k)
|
| 1812 |
+
|
| 1813 |
+
attn_out = F.scaled_dot_product_attention(
|
| 1814 |
+
q, k, v,
|
| 1815 |
+
dropout_p=self.dropout.p if self.training else 0.0,
|
| 1816 |
+
is_causal=self.causal
|
| 1817 |
+
)
|
| 1818 |
+
|
| 1819 |
+
attn_out = attn_out.transpose(1, 2).contiguous().view(B, L, H * self.v_dim)
|
| 1820 |
+
|
| 1821 |
+
return self.out_proj(attn_out)
|
| 1822 |
+
|
| 1823 |
+
|
| 1824 |
+
class MultiheadDecoupledAttentionNOV(Layer):
|
| 1825 |
+
__name__ = "DecoupledSelfAttention"
|
| 1826 |
+
__complexity__ = "O(L^2 * d_qk + L * d_model * (d_qk + d_v))"
|
| 1827 |
+
|
| 1828 |
+
def __init__(self, d_model: int, n_heads: int, qk_dim: int, causal: bool, rope=False, dropout=0.1):
|
| 1829 |
+
super().__init__()
|
| 1830 |
+
self.d_model = d_model
|
| 1831 |
+
self.n_heads = n_heads
|
| 1832 |
+
self.qk_dim = qk_dim
|
| 1833 |
+
self.v_dim = d_model // n_heads
|
| 1834 |
+
self.causal = causal
|
| 1835 |
+
self.rope_enabled = rope
|
| 1836 |
+
|
| 1837 |
+
self.qk_proj = nn.Linear(d_model, n_heads * 2 * qk_dim, bias=False)
|
| 1838 |
+
|
| 1839 |
+
self.out_proj = nn.Linear(n_heads * self.v_dim, d_model, bias=False)
|
| 1840 |
+
|
| 1841 |
+
self.dropout = nn.Dropout(dropout)
|
| 1842 |
+
|
| 1843 |
+
if self.rope_enabled:
|
| 1844 |
+
self.rope = RoPE(qk_dim)
|
| 1845 |
+
|
| 1846 |
+
nn.init.zeros_(self.out_proj.weight)
|
| 1847 |
+
|
| 1848 |
+
def forward(self, x: torch.Tensor):
|
| 1849 |
+
B, L, _ = x.shape
|
| 1850 |
+
H = self.n_heads
|
| 1851 |
+
|
| 1852 |
+
q, k = self.qk_proj(x).view(B, L, H, 2 * self.qk_dim).transpose(1, 2).chunk(2, -1)
|
| 1853 |
+
|
| 1854 |
+
v = x.view(B, L, H, self.v_dim).transpose(1, 2)
|
| 1855 |
+
|
| 1856 |
+
if self.rope_enabled:
|
| 1857 |
+
q, k = self.rope(q, k)
|
| 1858 |
+
|
| 1859 |
+
attn_out = F.scaled_dot_product_attention(
|
| 1860 |
+
q, k, v,
|
| 1861 |
+
dropout_p=self.dropout.p if self.training else 0.0,
|
| 1862 |
+
is_causal=self.causal
|
| 1863 |
+
)
|
| 1864 |
+
|
| 1865 |
+
attn_out = attn_out.transpose(1, 2).contiguous().view(B, L, H * self.v_dim)
|
| 1866 |
+
|
| 1867 |
+
return self.out_proj(attn_out)
|
| 1868 |
+
|
| 1869 |
+
|
| 1870 |
+
class FFMultiheadAttentionMixerNOV(Layer):
|
| 1871 |
+
__name__ = "MultiheadAttentionMixer"
|
| 1872 |
+
__complexity__ = "O(L^2 d + L d^2)"
|
| 1873 |
+
|
| 1874 |
+
def __init__(self, d_model: int, n_heads: int, causal: bool, rope=False, dropout=0.05):
|
| 1875 |
+
super().__init__()
|
| 1876 |
+
self.causal = causal
|
| 1877 |
+
self.d_model = d_model
|
| 1878 |
+
self.n_heads = n_heads
|
| 1879 |
+
self.head_dim = d_model // n_heads
|
| 1880 |
+
|
| 1881 |
+
self.q = nn.Linear(d_model, d_model, bias=False)
|
| 1882 |
+
self.out = nn.Linear(d_model, d_model, bias=False)
|
| 1883 |
+
if rope:
|
| 1884 |
+
self.rope = RoPE(self.head_dim)
|
| 1885 |
+
else:
|
| 1886 |
+
self.rope = None
|
| 1887 |
+
|
| 1888 |
+
self.dropout = nn.Dropout(dropout)
|
| 1889 |
+
|
| 1890 |
+
self.kv_cache = None
|
| 1891 |
+
|
| 1892 |
+
def forward(self, x: torch.Tensor):
|
| 1893 |
+
B, L, D = x.shape
|
| 1894 |
+
|
| 1895 |
+
q = self.q(x).reshape(B, L, self.n_heads, self.head_dim)
|
| 1896 |
+
q = q.permute(0, 2, 1, 3)
|
| 1897 |
+
k = x.view(B, L, self.n_heads, self.head_dim).permute(0, 2, 1, 3)
|
| 1898 |
+
v = x.view(B, L, self.n_heads, self.head_dim).permute(0, 2, 1, 3)
|
| 1899 |
+
|
| 1900 |
+
if self.rope is not None:
|
| 1901 |
+
q, k = self.rope(q, k, start_pos=0)
|
| 1902 |
+
|
| 1903 |
+
attn = F.scaled_dot_product_attention(q, k, v, is_causal=self.causal)
|
| 1904 |
+
attn = attn.transpose(1, 2).reshape(B, L, D)
|
| 1905 |
+
attn = self.dropout(attn)
|
| 1906 |
+
return self.out(attn)
|
| 1907 |
+
|
| 1908 |
+
def generate(self, x: torch.Tensor):
|
| 1909 |
+
B, L, D = x.shape
|
| 1910 |
+
|
| 1911 |
+
qk = self.qk(x).reshape(B, L, 2, self.n_heads, self.head_dim)
|
| 1912 |
+
q, k = qk.permute(2, 0, 3, 1, 4)
|
| 1913 |
+
v = x.view(B, L, 1, self.n_heads, self.head_dim).permute(2, 0, 3, 1, 4)[0]
|
| 1914 |
+
|
| 1915 |
+
if self.kv_cache is not None:
|
| 1916 |
+
k_cache, v_cache = self.kv_cache
|
| 1917 |
+
pos_offset = k_cache.shape[2]
|
| 1918 |
+
else:
|
| 1919 |
+
pos_offset = 0
|
| 1920 |
+
k_cache, v_cache = None, None
|
| 1921 |
+
|
| 1922 |
+
if self.rope is not None:
|
| 1923 |
+
q, k = self.rope(q, k, start_pos=pos_offset)
|
| 1924 |
+
|
| 1925 |
+
if k_cache is not None:
|
| 1926 |
+
k = torch.cat([k_cache, k], dim=2)
|
| 1927 |
+
v = torch.cat([v_cache, v], dim=2)
|
| 1928 |
+
|
| 1929 |
+
self.kv_cache = (k, v)
|
| 1930 |
+
use_causal = self.causal and (L > 1)
|
| 1931 |
+
|
| 1932 |
+
attn = F.scaled_dot_product_attention(q, k, v, is_causal=use_causal)
|
| 1933 |
+
|
| 1934 |
+
attn = attn.transpose(1, 2).reshape(B, L, D)
|
| 1935 |
+
attn = self.dropout(attn)
|
| 1936 |
+
return self.out(attn)
|
| 1937 |
+
|
| 1938 |
+
def clear_cache(self):
|
| 1939 |
+
self.kv_cache = None
|
| 1940 |
+
|
| 1941 |
+
def finetune(self):
|
| 1942 |
+
self.qk.weight.data.requires_grad = False
|
| 1943 |
+
self.out.weight.data.requires_grad = False
|
| 1944 |
+
|
| 1945 |
+
|
| 1946 |
+
class FFFMultiheadAttentionMixer(Layer):
|
| 1947 |
+
__name__ = "MultiheadAttentionMixer"
|
| 1948 |
+
__complexity__ = "O(L^2 d + L d^2)"
|
| 1949 |
+
|
| 1950 |
+
def __init__(self, d_model: int, n_heads: int, causal: bool, rope=False, dropout=0.05):
|
| 1951 |
+
super().__init__()
|
| 1952 |
+
self.causal = causal
|
| 1953 |
+
self.d_model = d_model
|
| 1954 |
+
self.n_heads = n_heads
|
| 1955 |
+
self.head_dim = d_model // n_heads
|
| 1956 |
+
|
| 1957 |
+
self.q = LowRankLinear(d_model, d_model, rank=math.isqrt(d_model), bias=False)
|
| 1958 |
+
self.out = nn.Linear(d_model, d_model, bias=False)
|
| 1959 |
+
if rope:
|
| 1960 |
+
self.rope = RoPE(self.head_dim)
|
| 1961 |
+
else:
|
| 1962 |
+
self.rope = None
|
| 1963 |
+
|
| 1964 |
+
self.dropout = nn.Dropout(dropout)
|
| 1965 |
+
|
| 1966 |
+
self.kv_cache = None
|
| 1967 |
+
|
| 1968 |
+
def forward(self, x: torch.Tensor):
|
| 1969 |
+
B, L, D = x.shape
|
| 1970 |
+
|
| 1971 |
+
q = self.q(x).reshape(B, L, self.n_heads, self.head_dim)
|
| 1972 |
+
q = q.permute(0, 2, 1, 3)
|
| 1973 |
+
k = x.view(B, L, self.n_heads, self.head_dim).permute(0, 2, 1, 3)
|
| 1974 |
+
v = x.view(B, L, self.n_heads, self.head_dim).permute(0, 2, 1, 3)
|
| 1975 |
+
|
| 1976 |
+
if self.rope is not None:
|
| 1977 |
+
q, k = self.rope(q, k, start_pos=0)
|
| 1978 |
+
|
| 1979 |
+
attn = F.scaled_dot_product_attention(q, k, v, is_causal=self.causal)
|
| 1980 |
+
attn = attn.transpose(1, 2).reshape(B, L, D)
|
| 1981 |
+
attn = self.dropout(attn)
|
| 1982 |
+
return self.out(attn)
|
| 1983 |
+
|
| 1984 |
+
def generate(self, x: torch.Tensor):
|
| 1985 |
+
B, L, D = x.shape
|
| 1986 |
+
|
| 1987 |
+
qk = self.qk(x).reshape(B, L, 2, self.n_heads, self.head_dim)
|
| 1988 |
+
q, k = qk.permute(2, 0, 3, 1, 4)
|
| 1989 |
+
v = x.view(B, L, 1, self.n_heads, self.head_dim).permute(2, 0, 3, 1, 4)[0]
|
| 1990 |
+
|
| 1991 |
+
if self.kv_cache is not None:
|
| 1992 |
+
k_cache, v_cache = self.kv_cache
|
| 1993 |
+
pos_offset = k_cache.shape[2]
|
| 1994 |
+
else:
|
| 1995 |
+
pos_offset = 0
|
| 1996 |
+
k_cache, v_cache = None, None
|
| 1997 |
+
|
| 1998 |
+
if self.rope is not None:
|
| 1999 |
+
q, k = self.rope(q, k, start_pos=pos_offset)
|
| 2000 |
+
|
| 2001 |
+
if k_cache is not None:
|
| 2002 |
+
k = torch.cat([k_cache, k], dim=2)
|
| 2003 |
+
v = torch.cat([v_cache, v], dim=2)
|
| 2004 |
+
|
| 2005 |
+
self.kv_cache = (k, v)
|
| 2006 |
+
use_causal = self.causal and (L > 1)
|
| 2007 |
+
|
| 2008 |
+
attn = F.scaled_dot_product_attention(q, k, v, is_causal=use_causal)
|
| 2009 |
+
|
| 2010 |
+
attn = attn.transpose(1, 2).reshape(B, L, D)
|
| 2011 |
+
attn = self.dropout(attn)
|
| 2012 |
+
return self.out(attn)
|
| 2013 |
+
|
| 2014 |
+
def clear_cache(self):
|
| 2015 |
+
self.kv_cache = None
|
| 2016 |
+
|
| 2017 |
+
def finetune(self):
|
| 2018 |
+
self.qk.weight.data.requires_grad = False
|
| 2019 |
+
self.out.weight.data.requires_grad = False
|
| 2020 |
+
|
| 2021 |
+
|
| 2022 |
+
class NSAttention(Layer):
|
| 2023 |
+
__name__ = "MultiheadAttentionMixer"
|
| 2024 |
+
__complexity__ = "O(L^2 d + L d^2)"
|
| 2025 |
+
|
| 2026 |
+
def __init__(self, d_model: int, n_heads: int, causal: bool = True,
|
| 2027 |
+
pos_emb: nn.Module = None, dropout: float = 0.0, qk_norm=False):
|
| 2028 |
+
super().__init__()
|
| 2029 |
+
assert d_model % n_heads == 0, "d_model must be divisible by n_heads"
|
| 2030 |
+
self.causal = causal
|
| 2031 |
+
self.d_model = d_model
|
| 2032 |
+
self.n_heads = n_heads
|
| 2033 |
+
self.head_dim = d_model // n_heads
|
| 2034 |
+
self.dropout_p = dropout
|
| 2035 |
+
|
| 2036 |
+
self.qkv = nn.Linear(d_model, 3 * d_model, bias=False)
|
| 2037 |
+
|
| 2038 |
+
self.pos_emb = pos_emb
|
| 2039 |
+
self.kv_cache = None
|
| 2040 |
+
self.qk_norm = qk_norm
|
| 2041 |
+
if self.qk_norm:
|
| 2042 |
+
self.q_norm = RMSNorm(self.head_dim)
|
| 2043 |
+
self.k_norm = RMSNorm(self.head_dim)
|
| 2044 |
+
|
| 2045 |
+
self._reset_parameters()
|
| 2046 |
+
|
| 2047 |
+
def _reset_parameters(self):
|
| 2048 |
+
nn.init.xavier_uniform_(self.qkv.weight)
|
| 2049 |
+
nn.init.xavier_uniform_(self.out.weight)
|
| 2050 |
+
|
| 2051 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 2052 |
+
B, L, D = x.shape
|
| 2053 |
+
|
| 2054 |
+
qkv = self.qkv(x)
|
| 2055 |
+
|
| 2056 |
+
q, k, v = qkv.chunk(3, dim=-1)
|
| 2057 |
+
kv = k.transpose(-1, -2) @ v
|
| 2058 |
+
return q @ kv.transpose(-1, -2)
|
| 2059 |
+
|
| 2060 |
+
|
| 2061 |
+
# MODDED NANO GPT
|
| 2062 |
+
|
| 2063 |
+
class CausalSelfAttention(nn.Module):
|
| 2064 |
+
def __init__(self, dim: int, num_heads: int, rope_base: float, qk_gain_init: float):
|
| 2065 |
+
super().__init__()
|
| 2066 |
+
if dim % num_heads != 0:
|
| 2067 |
+
raise ValueError("model_dim must be divisible by num_heads")
|
| 2068 |
+
|
| 2069 |
+
self.num_heads = num_heads
|
| 2070 |
+
self.head_dim = dim // num_heads
|
| 2071 |
+
|
| 2072 |
+
if self.head_dim % 2 != 0:
|
| 2073 |
+
raise ValueError("head_dim must be even for RoPE")
|
| 2074 |
+
|
| 2075 |
+
# FUSED QKV: Output dimension is 3 * dim
|
| 2076 |
+
self.c_qkv = CastedLinear(dim, 3 * dim, bias=False)
|
| 2077 |
+
|
| 2078 |
+
self.proj = CastedLinear(dim, dim, bias=False)
|
| 2079 |
+
self.proj._zero_init = True
|
| 2080 |
+
|
| 2081 |
+
self.q_gain = nn.Parameter(torch.full((num_heads,), qk_gain_init, dtype=torch.float32))
|
| 2082 |
+
self.rotary = Rotary(self.head_dim, base=rope_base)
|
| 2083 |
+
|
| 2084 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 2085 |
+
bsz, seqlen, dim = x.shape
|
| 2086 |
+
|
| 2087 |
+
# 1. Single fused linear projection
|
| 2088 |
+
# Shape: (bsz, seqlen, 3 * dim)
|
| 2089 |
+
qkv = self.c_qkv(x)
|
| 2090 |
+
|
| 2091 |
+
# 2. Reshape and slice into Q, K, V
|
| 2092 |
+
# Reshape to: (bsz, seqlen, 3, num_heads, head_dim)
|
| 2093 |
+
qkv = qkv.reshape(bsz, seqlen, 3, self.num_heads, self.head_dim)
|
| 2094 |
+
# Permute to: (3, bsz, num_heads, seqlen, head_dim)
|
| 2095 |
+
qkv = qkv.permute(2, 0, 3, 1, 4)
|
| 2096 |
+
|
| 2097 |
+
# Unbind separates the first dimension (the '3') into a tuple
|
| 2098 |
+
q, k, v = qkv.unbind(0)
|
| 2099 |
+
|
| 2100 |
+
# 3. Apply norms
|
| 2101 |
+
q = F.rms_norm(q, (q.size(-1),))
|
| 2102 |
+
k = F.rms_norm(k, (k.size(-1),))
|
| 2103 |
+
|
| 2104 |
+
# 4. RoPE
|
| 2105 |
+
cos, sin = self.rotary(seqlen, x.device, q.dtype)
|
| 2106 |
+
q = apply_rotary_emb(q, cos, sin)
|
| 2107 |
+
k = apply_rotary_emb(k, cos, sin)
|
| 2108 |
+
|
| 2109 |
+
# 5. Q-gain scaling
|
| 2110 |
+
q = q * self.q_gain.to(dtype=q.dtype)[None, :, None, None]
|
| 2111 |
+
|
| 2112 |
+
# 6. Attention
|
| 2113 |
+
y = F.scaled_dot_product_attention(
|
| 2114 |
+
q,
|
| 2115 |
+
k,
|
| 2116 |
+
v,
|
| 2117 |
+
attn_mask=None,
|
| 2118 |
+
is_causal=True,
|
| 2119 |
+
enable_gqa=False, # Standard MHA
|
| 2120 |
+
)
|
| 2121 |
+
|
| 2122 |
+
# 7. Final projection
|
| 2123 |
+
y = y.transpose(1, 2).contiguous().reshape(bsz, seqlen, dim)
|
| 2124 |
+
return self.proj(y)
|
vathos/_triton.py
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Triton-fused two-layer MLPs in the style of `VariableUDLP`:
|
| 3 |
+
out = (act(x @ W1.T))^2 @ W2.T (ReLU^2, LeakyReLU(0.5)^2)
|
| 4 |
+
out = (silu(a) * b) @ W2.T (SwiGLU)
|
| 5 |
+
|
| 6 |
+
The fused ReLU^2 and LeakyReLU^2 kernels are ports of:
|
| 7 |
+
- modded-nanogpt linear_relu_square (Keller Jordan et al.)
|
| 8 |
+
- parameter-golf linear_leaky_relu_square (samacqua / openai/parameter-golf)
|
| 9 |
+
|
| 10 |
+
Both use the Hopper TensorDescriptor (TMA) API, so they need:
|
| 11 |
+
- triton >= 3.2
|
| 12 |
+
- GPU compute capability >= 9.0 (H100 / GH200)
|
| 13 |
+
On older GPUs / when triton is missing, the wrapper modules transparently fall
|
| 14 |
+
back to a pure-torch path that is `torch.compile`-friendly.
|
| 15 |
+
|
| 16 |
+
The autograd.Function wrappers are accepted natively by `torch.compile` (>=2.0):
|
| 17 |
+
the compiler treats `.apply(...)` as an opaque op and traces around it.
|
| 18 |
+
|
| 19 |
+
API: each module mirrors `VariableUDLP(d_model, d_output, M, dropout)`.
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
from __future__ import annotations
|
| 23 |
+
|
| 24 |
+
import torch
|
| 25 |
+
import torch.nn as nn
|
| 26 |
+
import torch.nn.functional as F
|
| 27 |
+
|
| 28 |
+
from Vathos._basics import Layer
|
| 29 |
+
from Vathos.functions import flag
|
| 30 |
+
|
| 31 |
+
try:
|
| 32 |
+
import triton
|
| 33 |
+
import triton.language as tl
|
| 34 |
+
from triton.tools.tensor_descriptor import TensorDescriptor
|
| 35 |
+
_HAS_TRITON = True
|
| 36 |
+
except Exception:
|
| 37 |
+
_HAS_TRITON = False
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _hopper_available() -> bool:
|
| 41 |
+
if not (_HAS_TRITON and torch.cuda.is_available()):
|
| 42 |
+
return False
|
| 43 |
+
major, _ = torch.cuda.get_device_capability()
|
| 44 |
+
return major >= 9
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
# =============================================================================
|
| 48 |
+
# Triton kernels
|
| 49 |
+
# =============================================================================
|
| 50 |
+
|
| 51 |
+
if _HAS_TRITON:
|
| 52 |
+
|
| 53 |
+
@triton.jit
|
| 54 |
+
def _linear_relu_square_kernel(
|
| 55 |
+
a_desc, b_desc, c_desc, aux_desc,
|
| 56 |
+
M, N, K,
|
| 57 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 58 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 59 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 60 |
+
NUM_SMS: tl.constexpr,
|
| 61 |
+
FORWARD: tl.constexpr,
|
| 62 |
+
):
|
| 63 |
+
"""Port of modded-nanogpt's linear_relu_square_kernel.
|
| 64 |
+
|
| 65 |
+
FORWARD: computes c = (x @ W1.T) and aux = relu(c)^2 (c is the pre-activation)
|
| 66 |
+
BACKWARD: computes c = grad_pre = grad_post * 2 * relu(pre), reading pre from aux
|
| 67 |
+
(caller passes aux=pre, grad_post is the input matmul result)
|
| 68 |
+
"""
|
| 69 |
+
dtype = tl.bfloat16
|
| 70 |
+
start_pid = tl.program_id(axis=0)
|
| 71 |
+
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
| 72 |
+
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
|
| 73 |
+
num_tiles = num_pid_m * num_pid_n
|
| 74 |
+
k_tiles = tl.cdiv(K, BLOCK_SIZE_K)
|
| 75 |
+
|
| 76 |
+
for tile_id in tl.range(start_pid, num_tiles, NUM_SMS, flatten=True):
|
| 77 |
+
pid_m = tile_id // num_pid_n
|
| 78 |
+
pid_n = tile_id % num_pid_n
|
| 79 |
+
offs_am = pid_m * BLOCK_SIZE_M
|
| 80 |
+
offs_bn = pid_n * BLOCK_SIZE_N
|
| 81 |
+
|
| 82 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 83 |
+
for ki in range(k_tiles):
|
| 84 |
+
offs_k = ki * BLOCK_SIZE_K
|
| 85 |
+
a = a_desc.load([offs_am, offs_k])
|
| 86 |
+
b = b_desc.load([offs_bn, offs_k])
|
| 87 |
+
accumulator = tl.dot(a, b.T, accumulator)
|
| 88 |
+
|
| 89 |
+
acc = tl.reshape(accumulator, (BLOCK_SIZE_M, 2, BLOCK_SIZE_N // 2))
|
| 90 |
+
acc = tl.permute(acc, (0, 2, 1))
|
| 91 |
+
acc0, acc1 = tl.split(acc)
|
| 92 |
+
|
| 93 |
+
c0 = acc0.to(dtype)
|
| 94 |
+
if not FORWARD:
|
| 95 |
+
pre0 = aux_desc.load([offs_am, offs_bn])
|
| 96 |
+
c0 = 2 * c0 * tl.where(pre0 > 0, pre0, 0)
|
| 97 |
+
c_desc.store([offs_am, offs_bn], c0)
|
| 98 |
+
if FORWARD:
|
| 99 |
+
p0 = tl.maximum(c0, 0)
|
| 100 |
+
aux_desc.store([offs_am, offs_bn], p0 * p0)
|
| 101 |
+
|
| 102 |
+
c1 = acc1.to(dtype)
|
| 103 |
+
if not FORWARD:
|
| 104 |
+
pre1 = aux_desc.load([offs_am, offs_bn + BLOCK_SIZE_N // 2])
|
| 105 |
+
c1 = 2 * c1 * tl.where(pre1 > 0, pre1, 0)
|
| 106 |
+
c_desc.store([offs_am, offs_bn + BLOCK_SIZE_N // 2], c1)
|
| 107 |
+
if FORWARD:
|
| 108 |
+
p1 = tl.maximum(c1, 0)
|
| 109 |
+
aux_desc.store([offs_am, offs_bn + BLOCK_SIZE_N // 2], p1 * p1)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
@triton.jit
|
| 113 |
+
def _linear_leaky_relu_square_kernel(
|
| 114 |
+
a_desc, b_desc, c_desc, aux_desc,
|
| 115 |
+
M, N, K,
|
| 116 |
+
BLOCK_SIZE_M: tl.constexpr,
|
| 117 |
+
BLOCK_SIZE_N: tl.constexpr,
|
| 118 |
+
BLOCK_SIZE_K: tl.constexpr,
|
| 119 |
+
NUM_SMS: tl.constexpr,
|
| 120 |
+
FORWARD: tl.constexpr,
|
| 121 |
+
):
|
| 122 |
+
"""Port of parameter-golf's linear_leaky_relu_square_kernel.
|
| 123 |
+
Uses LeakyReLU with negative slope 0.5, then squares.
|
| 124 |
+
Forward: aux = lrelu(c)^2, Backward: c = grad_post * 2 * (lrelu'(pre) * lrelu(pre))
|
| 125 |
+
= grad_post * (where(pre>0, 2*pre, 0.5*pre))
|
| 126 |
+
"""
|
| 127 |
+
dtype = tl.bfloat16
|
| 128 |
+
start_pid = tl.program_id(axis=0)
|
| 129 |
+
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
|
| 130 |
+
num_pid_m = tl.cdiv(M, BLOCK_SIZE_M)
|
| 131 |
+
num_tiles = num_pid_m * num_pid_n
|
| 132 |
+
k_tiles = tl.cdiv(K, BLOCK_SIZE_K)
|
| 133 |
+
|
| 134 |
+
for tile_id in tl.range(start_pid, num_tiles, NUM_SMS, flatten=True):
|
| 135 |
+
pid_m = tile_id // num_pid_n
|
| 136 |
+
pid_n = tile_id % num_pid_n
|
| 137 |
+
offs_am = pid_m * BLOCK_SIZE_M
|
| 138 |
+
offs_bn = pid_n * BLOCK_SIZE_N
|
| 139 |
+
|
| 140 |
+
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
|
| 141 |
+
for ki in range(k_tiles):
|
| 142 |
+
offs_k = ki * BLOCK_SIZE_K
|
| 143 |
+
a = a_desc.load([offs_am, offs_k])
|
| 144 |
+
b = b_desc.load([offs_bn, offs_k])
|
| 145 |
+
accumulator = tl.dot(a, b.T, accumulator)
|
| 146 |
+
|
| 147 |
+
acc = tl.reshape(accumulator, (BLOCK_SIZE_M, 2, BLOCK_SIZE_N // 2))
|
| 148 |
+
acc = tl.permute(acc, (0, 2, 1))
|
| 149 |
+
acc0, acc1 = tl.split(acc)
|
| 150 |
+
|
| 151 |
+
c0 = acc0.to(dtype)
|
| 152 |
+
c1 = acc1.to(dtype)
|
| 153 |
+
if not FORWARD:
|
| 154 |
+
pre0 = aux_desc.load([offs_am, offs_bn])
|
| 155 |
+
pre1 = aux_desc.load([offs_am, offs_bn + BLOCK_SIZE_N // 2])
|
| 156 |
+
c0 = c0 * tl.where(pre0 > 0, 2.0 * pre0, 0.5 * pre0)
|
| 157 |
+
c1 = c1 * tl.where(pre1 > 0, 2.0 * pre1, 0.5 * pre1)
|
| 158 |
+
c_desc.store([offs_am, offs_bn], c0)
|
| 159 |
+
c_desc.store([offs_am, offs_bn + BLOCK_SIZE_N // 2], c1)
|
| 160 |
+
if FORWARD:
|
| 161 |
+
aux0 = tl.where(c0 > 0, c0, 0.5 * c0)
|
| 162 |
+
aux1 = tl.where(c1 > 0, c1, 0.5 * c1)
|
| 163 |
+
aux_desc.store([offs_am, offs_bn], aux0 * aux0)
|
| 164 |
+
aux_desc.store([offs_am, offs_bn + BLOCK_SIZE_N // 2], aux1 * aux1)
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
# -----------------------------------------------------------------------------
|
| 168 |
+
# T4-friendly LeakyReLU(0.5)^2 kernel (no TMA, fp16, smaller tiles).
|
| 169 |
+
# Designed for sm_75 (Turing, T4): pointer-based loads, BLOCK_M=BLOCK_N=64,
|
| 170 |
+
# BLOCK_K=32, fp16 inputs/outputs, fp32 accumulator. Same fwd/bwd convention
|
| 171 |
+
# as the Hopper kernels: forward stores c=pre and aux=post; backward reads
|
| 172 |
+
# pre from aux and writes grad_pre into c. Output dtype follows the inputs
|
| 173 |
+
# (cast to fp32 fails tl.dot on Turing — keep them fp16).
|
| 174 |
+
# -----------------------------------------------------------------------------
|
| 175 |
+
|
| 176 |
+
if _HAS_TRITON:
|
| 177 |
+
|
| 178 |
+
@triton.jit
|
| 179 |
+
def _linear_lrelu2_kernel_t4(
|
| 180 |
+
a_ptr, b_ptr, c_ptr, aux_ptr,
|
| 181 |
+
M, N, K,
|
| 182 |
+
stride_am, stride_ak,
|
| 183 |
+
stride_bn, stride_bk,
|
| 184 |
+
stride_cm, stride_cn,
|
| 185 |
+
BLOCK_M: tl.constexpr,
|
| 186 |
+
BLOCK_N: tl.constexpr,
|
| 187 |
+
BLOCK_K: tl.constexpr,
|
| 188 |
+
GROUP_M: tl.constexpr,
|
| 189 |
+
FORWARD: tl.constexpr,
|
| 190 |
+
):
|
| 191 |
+
pid = tl.program_id(axis=0)
|
| 192 |
+
num_pid_m = tl.cdiv(M, BLOCK_M)
|
| 193 |
+
num_pid_n = tl.cdiv(N, BLOCK_N)
|
| 194 |
+
num_pid_in_group = GROUP_M * num_pid_n
|
| 195 |
+
group_id = pid // num_pid_in_group
|
| 196 |
+
first_pid_m = group_id * GROUP_M
|
| 197 |
+
group_size_m = tl.minimum(num_pid_m - first_pid_m, GROUP_M)
|
| 198 |
+
pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
|
| 199 |
+
pid_n = (pid % num_pid_in_group) // group_size_m
|
| 200 |
+
|
| 201 |
+
offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
|
| 202 |
+
offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
|
| 203 |
+
offs_k = tl.arange(0, BLOCK_K)
|
| 204 |
+
|
| 205 |
+
a_ptrs = a_ptr + offs_m[:, None] * stride_am + offs_k[None, :] * stride_ak
|
| 206 |
+
b_ptrs = b_ptr + offs_n[:, None] * stride_bn + offs_k[None, :] * stride_bk
|
| 207 |
+
|
| 208 |
+
acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
|
| 209 |
+
for k in range(0, tl.cdiv(K, BLOCK_K)):
|
| 210 |
+
k_remaining = K - k * BLOCK_K
|
| 211 |
+
a_mask = (offs_m[:, None] < M) & (offs_k[None, :] < k_remaining)
|
| 212 |
+
b_mask = (offs_n[:, None] < N) & (offs_k[None, :] < k_remaining)
|
| 213 |
+
a = tl.load(a_ptrs, mask=a_mask, other=0.0)
|
| 214 |
+
b = tl.load(b_ptrs, mask=b_mask, other=0.0)
|
| 215 |
+
acc += tl.dot(a, b.T)
|
| 216 |
+
a_ptrs += BLOCK_K * stride_ak
|
| 217 |
+
b_ptrs += BLOCK_K * stride_bk
|
| 218 |
+
|
| 219 |
+
out_dtype = c_ptr.dtype.element_ty
|
| 220 |
+
c_ptrs = c_ptr + offs_m[:, None] * stride_cm + offs_n[None, :] * stride_cn
|
| 221 |
+
aux_ptrs = aux_ptr + offs_m[:, None] * stride_cm + offs_n[None, :] * stride_cn
|
| 222 |
+
mask = (offs_m[:, None] < M) & (offs_n[None, :] < N)
|
| 223 |
+
|
| 224 |
+
if FORWARD:
|
| 225 |
+
# acc here is pre = x @ W1.T. Save pre into c, post = lrelu²(pre) into aux.
|
| 226 |
+
tl.store(c_ptrs, acc.to(out_dtype), mask=mask)
|
| 227 |
+
scale = tl.where(acc > 0, 1.0, 0.25) # act² = z² * (1 if z>0 else 0.25)
|
| 228 |
+
post = acc * acc * scale
|
| 229 |
+
tl.store(aux_ptrs, post.to(out_dtype), mask=mask)
|
| 230 |
+
else:
|
| 231 |
+
# acc here is grad_post @ W2 (b=W2.T). Multiply elementwise by
|
| 232 |
+
# d(lrelu²)/dpre = where(pre>0, 2*pre, 0.5*pre) using saved pre in aux.
|
| 233 |
+
pre = tl.load(aux_ptrs, mask=mask, other=0.0).to(tl.float32)
|
| 234 |
+
deriv = tl.where(pre > 0, 2.0 * pre, 0.5 * pre)
|
| 235 |
+
tl.store(c_ptrs, (acc * deriv).to(out_dtype), mask=mask)
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
def _launch_lrelu2_t4(a: torch.Tensor, b: torch.Tensor, aux: torch.Tensor | None):
|
| 239 |
+
"""Pointer-based kernel launcher for T4. Returns (pre, post) in fwd, grad_pre in bwd."""
|
| 240 |
+
M, K = a.shape
|
| 241 |
+
N, K2 = b.shape
|
| 242 |
+
assert K == K2
|
| 243 |
+
|
| 244 |
+
c = torch.empty((M, N), device=a.device, dtype=a.dtype)
|
| 245 |
+
forward = aux is None
|
| 246 |
+
if aux is None:
|
| 247 |
+
aux = torch.empty((M, N), device=a.device, dtype=a.dtype)
|
| 248 |
+
|
| 249 |
+
BLOCK_M, BLOCK_N, BLOCK_K = 64, 64, 32
|
| 250 |
+
grid = (triton.cdiv(M, BLOCK_M) * triton.cdiv(N, BLOCK_N),)
|
| 251 |
+
_linear_lrelu2_kernel_t4[grid](
|
| 252 |
+
a, b, c, aux,
|
| 253 |
+
M, N, K,
|
| 254 |
+
a.stride(0), a.stride(1),
|
| 255 |
+
b.stride(0), b.stride(1),
|
| 256 |
+
c.stride(0), c.stride(1),
|
| 257 |
+
BLOCK_M=BLOCK_M, BLOCK_N=BLOCK_N, BLOCK_K=BLOCK_K,
|
| 258 |
+
GROUP_M=8,
|
| 259 |
+
FORWARD=forward,
|
| 260 |
+
num_warps=4,
|
| 261 |
+
num_stages=2,
|
| 262 |
+
)
|
| 263 |
+
return (c, aux) if forward else c
|
| 264 |
+
|
| 265 |
+
|
| 266 |
+
class _FusedLinearLReLU2_T4(torch.autograd.Function):
|
| 267 |
+
@staticmethod
|
| 268 |
+
def forward(ctx, x, W1, W2):
|
| 269 |
+
x_flat = x.reshape(-1, x.shape[-1])
|
| 270 |
+
pre, post = _launch_lrelu2_t4(x_flat, W1, None)
|
| 271 |
+
out = post @ W2.T
|
| 272 |
+
ctx.save_for_backward(x, W1, W2, pre, post)
|
| 273 |
+
return out.view(*x.shape[:-1], W2.shape[0])
|
| 274 |
+
|
| 275 |
+
@staticmethod
|
| 276 |
+
def backward(ctx, grad_out):
|
| 277 |
+
x, W1, W2, pre, post = ctx.saved_tensors
|
| 278 |
+
x_flat = x.reshape(-1, x.shape[-1])
|
| 279 |
+
go = grad_out.reshape(-1, W2.shape[0])
|
| 280 |
+
dW2 = go.T @ post
|
| 281 |
+
dpre = _launch_lrelu2_t4(go, W2.T.contiguous(), pre)
|
| 282 |
+
dW1 = dpre.T @ x_flat
|
| 283 |
+
dx = dpre @ W1
|
| 284 |
+
return dx.view_as(x), dW1, dW2
|
| 285 |
+
|
| 286 |
+
|
| 287 |
+
def _can_use_triton_t4(x) -> bool:
|
| 288 |
+
"""T4 (CC 7.5) requires fp16 inputs."""
|
| 289 |
+
if not (_HAS_TRITON and torch.cuda.is_available()):
|
| 290 |
+
return False
|
| 291 |
+
major, minor = torch.cuda.get_device_capability()
|
| 292 |
+
if (major, minor) != (7, 5):
|
| 293 |
+
return False
|
| 294 |
+
return x.dtype == torch.float16
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
# =============================================================================
|
| 298 |
+
# Host-side launchers (return both pre-activation and post-activation in fwd)
|
| 299 |
+
# =============================================================================
|
| 300 |
+
|
| 301 |
+
def _launch_fused_act_square(kernel_fn, a: torch.Tensor, b: torch.Tensor,
|
| 302 |
+
aux: torch.Tensor | None):
|
| 303 |
+
"""Shared launcher for both relu^2 and leaky_relu^2 kernels.
|
| 304 |
+
Forward (aux=None): returns (pre, post) = (x@W.T, act(pre)^2)
|
| 305 |
+
Backward (aux=pre): returns dpre = grad_post * d(act(pre)^2)/dpre
|
| 306 |
+
"""
|
| 307 |
+
M, K = a.shape
|
| 308 |
+
N, K2 = b.shape
|
| 309 |
+
assert K == K2
|
| 310 |
+
|
| 311 |
+
c = torch.empty((M, N), device=a.device, dtype=a.dtype)
|
| 312 |
+
forward = aux is None
|
| 313 |
+
if aux is None:
|
| 314 |
+
aux = torch.empty((M, N), device=a.device, dtype=a.dtype)
|
| 315 |
+
|
| 316 |
+
BLOCK_SIZE_M, BLOCK_SIZE_N, BLOCK_SIZE_K = 128, 256, 64
|
| 317 |
+
num_stages = 4 if forward else 3
|
| 318 |
+
|
| 319 |
+
a_desc = TensorDescriptor.from_tensor(a, [BLOCK_SIZE_M, BLOCK_SIZE_K])
|
| 320 |
+
b_desc = TensorDescriptor.from_tensor(b, [BLOCK_SIZE_N, BLOCK_SIZE_K])
|
| 321 |
+
c_desc = TensorDescriptor.from_tensor(c, [BLOCK_SIZE_M, BLOCK_SIZE_N // 2])
|
| 322 |
+
aux_desc = TensorDescriptor.from_tensor(aux, [BLOCK_SIZE_M, BLOCK_SIZE_N // 2])
|
| 323 |
+
|
| 324 |
+
num_sms = torch.cuda.get_device_properties(a.device).multi_processor_count
|
| 325 |
+
grid = lambda _meta: (
|
| 326 |
+
min(num_sms, triton.cdiv(M, BLOCK_SIZE_M) * triton.cdiv(N, BLOCK_SIZE_N)),
|
| 327 |
+
)
|
| 328 |
+
kernel_fn[grid](
|
| 329 |
+
a_desc, b_desc, c_desc, aux_desc,
|
| 330 |
+
M, N, K,
|
| 331 |
+
BLOCK_SIZE_M=BLOCK_SIZE_M,
|
| 332 |
+
BLOCK_SIZE_N=BLOCK_SIZE_N,
|
| 333 |
+
BLOCK_SIZE_K=BLOCK_SIZE_K,
|
| 334 |
+
NUM_SMS=num_sms,
|
| 335 |
+
FORWARD=forward,
|
| 336 |
+
num_stages=num_stages,
|
| 337 |
+
num_warps=8,
|
| 338 |
+
)
|
| 339 |
+
return (c, aux) if forward else c
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
# =============================================================================
|
| 343 |
+
# Autograd Functions — composes the fused kernel with the second matmul.
|
| 344 |
+
# Compatible with torch.compile (autograd.Function is opaque to dynamo).
|
| 345 |
+
# =============================================================================
|
| 346 |
+
|
| 347 |
+
class _FusedLinearReLU2(torch.autograd.Function):
|
| 348 |
+
@staticmethod
|
| 349 |
+
def forward(ctx, x, W1, W2):
|
| 350 |
+
x_flat = x.reshape(-1, x.shape[-1])
|
| 351 |
+
pre, post = _launch_fused_act_square(_linear_relu_square_kernel, x_flat, W1, None)
|
| 352 |
+
out = post @ W2.T
|
| 353 |
+
ctx.save_for_backward(x, W1, W2, pre, post)
|
| 354 |
+
return out.view(*x.shape[:-1], W2.shape[0])
|
| 355 |
+
|
| 356 |
+
@staticmethod
|
| 357 |
+
def backward(ctx, grad_out):
|
| 358 |
+
x, W1, W2, pre, post = ctx.saved_tensors
|
| 359 |
+
x_flat = x.reshape(-1, x.shape[-1])
|
| 360 |
+
go = grad_out.reshape(-1, W2.shape[0])
|
| 361 |
+
dW2 = go.T @ post
|
| 362 |
+
dpre = _launch_fused_act_square(_linear_relu_square_kernel, go, W2.T.contiguous(), pre)
|
| 363 |
+
dW1 = dpre.T @ x_flat
|
| 364 |
+
dx = dpre @ W1
|
| 365 |
+
return dx.view_as(x), dW1, dW2
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
class _FusedLinearLReLU2(torch.autograd.Function):
|
| 369 |
+
@staticmethod
|
| 370 |
+
def forward(ctx, x, W1, W2):
|
| 371 |
+
x_flat = x.reshape(-1, x.shape[-1])
|
| 372 |
+
pre, post = _launch_fused_act_square(_linear_leaky_relu_square_kernel, x_flat, W1, None)
|
| 373 |
+
out = post @ W2.T
|
| 374 |
+
ctx.save_for_backward(x, W1, W2, pre, post)
|
| 375 |
+
return out.view(*x.shape[:-1], W2.shape[0])
|
| 376 |
+
|
| 377 |
+
@staticmethod
|
| 378 |
+
def backward(ctx, grad_out):
|
| 379 |
+
x, W1, W2, pre, post = ctx.saved_tensors
|
| 380 |
+
x_flat = x.reshape(-1, x.shape[-1])
|
| 381 |
+
go = grad_out.reshape(-1, W2.shape[0])
|
| 382 |
+
dW2 = go.T @ post
|
| 383 |
+
dpre = _launch_fused_act_square(_linear_leaky_relu_square_kernel, go, W2.T.contiguous(), pre)
|
| 384 |
+
dW1 = dpre.T @ x_flat
|
| 385 |
+
dx = dpre @ W1
|
| 386 |
+
return dx.view_as(x), dW1, dW2
|
| 387 |
+
|
| 388 |
+
|
| 389 |
+
# =============================================================================
|
| 390 |
+
# Pure-torch fallbacks (torch.compile-friendly)
|
| 391 |
+
# =============================================================================
|
| 392 |
+
|
| 393 |
+
def _eager_relu2_mlp(x, W1, W2):
|
| 394 |
+
return F.relu(F.linear(x, W1)).square() @ W2.T
|
| 395 |
+
|
| 396 |
+
def _eager_lrelu2_mlp(x, W1, W2):
|
| 397 |
+
return F.leaky_relu(F.linear(x, W1), 0.5).square() @ W2.T
|
| 398 |
+
|
| 399 |
+
|
| 400 |
+
def _can_use_triton_fused(x, M, N) -> bool:
|
| 401 |
+
"""The TMA kernels require shapes divisible by their tile sizes and a Hopper GPU."""
|
| 402 |
+
if not _hopper_available():
|
| 403 |
+
return False
|
| 404 |
+
if x.dtype not in (torch.bfloat16, torch.float16):
|
| 405 |
+
return False
|
| 406 |
+
return (M % 128 == 0) and (N % 256 == 0)
|
| 407 |
+
|
| 408 |
+
|
| 409 |
+
# =============================================================================
|
| 410 |
+
# VariableUDLP-compatible nn.Modules
|
| 411 |
+
# =============================================================================
|
| 412 |
+
|
| 413 |
+
class _BaseTritonUDLP(Layer):
|
| 414 |
+
"""Shared structure: expand (d_model→M, no bias) -> fused act² -> contract (M→d_output, no bias).
|
| 415 |
+
|
| 416 |
+
Matches `VariableUDLP(d_model, d_output, M, dropout)` so that it can be passed
|
| 417 |
+
as the `UDLP=` argument of `ModdedFormer` without further changes. The
|
| 418 |
+
`activation` parameter accepted by `VariableUDLP` is ignored here — the
|
| 419 |
+
activation is fixed by the chosen kernel (subclass).
|
| 420 |
+
"""
|
| 421 |
+
|
| 422 |
+
_autograd_fn = None
|
| 423 |
+
_eager_fn = None
|
| 424 |
+
_name = "TritonUDLP"
|
| 425 |
+
|
| 426 |
+
def __init__(self, d_model: int, d_output: int, M: int,
|
| 427 |
+
activation=None, dropout: float = 0.0):
|
| 428 |
+
super().__init__()
|
| 429 |
+
self.d_model = d_model
|
| 430 |
+
self.d_output = d_output
|
| 431 |
+
self.M = M
|
| 432 |
+
self.expand = nn.Linear(d_model, M, bias=False)
|
| 433 |
+
self.contract = nn.Linear(M, d_output, bias=False)
|
| 434 |
+
self.dropout = nn.Dropout(dropout)
|
| 435 |
+
if activation is not None:
|
| 436 |
+
flag(f"{self._name}: 'activation' arg ignored — kernel hardcodes the activation.", 3)
|
| 437 |
+
|
| 438 |
+
def _init_weights(self):
|
| 439 |
+
torch.nn.init.zeros_(self.contract.weight)
|
| 440 |
+
|
| 441 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 442 |
+
W1 = self.expand.weight # [M, d_model]
|
| 443 |
+
W2 = self.contract.weight # [d_output, M]
|
| 444 |
+
if _can_use_triton_fused(x, self.M, self.d_output):
|
| 445 |
+
out = self._autograd_fn.apply(x, W1, W2)
|
| 446 |
+
else:
|
| 447 |
+
out = self._eager_fn(x, W1, W2)
|
| 448 |
+
return self.dropout(out)
|
| 449 |
+
|
| 450 |
+
|
| 451 |
+
class TritonReLU2UDLP(_BaseTritonUDLP):
|
| 452 |
+
"""Fused ReLU² UDLP. Drop-in for VariableUDLP with activation=ReLU2."""
|
| 453 |
+
_autograd_fn = _FusedLinearReLU2 if _HAS_TRITON else None
|
| 454 |
+
_eager_fn = staticmethod(_eager_relu2_mlp)
|
| 455 |
+
_name = "TritonReLU2UDLP"
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
class TritonLReLU2UDLP(_BaseTritonUDLP):
|
| 459 |
+
"""Fused LeakyReLU(0.5)² UDLP. Drop-in for VariableUDLP with activation=LeakyReLU2."""
|
| 460 |
+
_autograd_fn = _FusedLinearLReLU2 if _HAS_TRITON else None
|
| 461 |
+
_eager_fn = staticmethod(_eager_lrelu2_mlp)
|
| 462 |
+
_name = "TritonLReLU2UDLP"
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
class TritonLReLU2UDLP_T4(_BaseTritonUDLP):
|
| 466 |
+
"""LeakyReLU(0.5)² UDLP tuned for T4 / Turing (sm_75).
|
| 467 |
+
|
| 468 |
+
Uses a non-TMA fp16 kernel with 64×64×32 tiles. Falls back to the eager
|
| 469 |
+
torch path on non-T4 GPUs or when triton is missing.
|
| 470 |
+
|
| 471 |
+
NOTE: weights are kept fp32 by Linear; cast inputs to fp16 before forward
|
| 472 |
+
(e.g. `model = model.half()` or `with torch.autocast('cuda', dtype=torch.float16)`).
|
| 473 |
+
"""
|
| 474 |
+
_autograd_fn = _FusedLinearLReLU2_T4 if _HAS_TRITON else None
|
| 475 |
+
_eager_fn = staticmethod(_eager_lrelu2_mlp)
|
| 476 |
+
_name = "TritonLReLU2UDLP_T4"
|
| 477 |
+
|
| 478 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 479 |
+
W1 = self.expand.weight
|
| 480 |
+
W2 = self.contract.weight
|
| 481 |
+
if _can_use_triton_t4(x):
|
| 482 |
+
out = self._autograd_fn.apply(x, W1, W2)
|
| 483 |
+
else:
|
| 484 |
+
out = self._eager_fn(x, W1, W2)
|
| 485 |
+
return self.dropout(out)
|
| 486 |
+
|
| 487 |
+
|
| 488 |
+
# -----------------------------------------------------------------------------
|
| 489 |
+
# SwiGLU — torch-only path (compile-friendly).
|
| 490 |
+
#
|
| 491 |
+
# SwiGLU is gated, so the expand projects to 2*M and the activation consumes
|
| 492 |
+
# both halves: silu(a) * b. There is no clean port of this in modded-nanogpt
|
| 493 |
+
# or parameter-golf; Liger-Kernel implements a full fused fwd+bwd for it but
|
| 494 |
+
# that's a separate dependency. The torch path below compiles cleanly and is
|
| 495 |
+
# usually within ~10% of a custom kernel for the typical M sizes we use.
|
| 496 |
+
# -----------------------------------------------------------------------------
|
| 497 |
+
|
| 498 |
+
class TritonSwiGLUUDLP(Layer):
|
| 499 |
+
"""SwiGLU UDLP, drop-in for VariableUDLP with activation=SwiGLU.
|
| 500 |
+
Compile-friendly torch implementation — no custom kernel.
|
| 501 |
+
`M` is the post-gate inner dimension (i.e. expand projects to 2*M).
|
| 502 |
+
"""
|
| 503 |
+
|
| 504 |
+
def __init__(self, d_model: int, d_output: int, M: int,
|
| 505 |
+
activation=None, dropout: float = 0.0):
|
| 506 |
+
super().__init__()
|
| 507 |
+
self.d_model = d_model
|
| 508 |
+
self.d_output = d_output
|
| 509 |
+
self.M = M
|
| 510 |
+
self.expand = nn.Linear(d_model, 2 * M, bias=False)
|
| 511 |
+
self.contract = nn.Linear(M, d_output, bias=False)
|
| 512 |
+
self.dropout = nn.Dropout(dropout)
|
| 513 |
+
if activation is not None:
|
| 514 |
+
flag("TritonSwiGLUUDLP: 'activation' arg ignored — gate is fixed to SiLU.", 3)
|
| 515 |
+
|
| 516 |
+
def _init_weights(self):
|
| 517 |
+
torch.nn.init.zeros_(self.contract.weight)
|
| 518 |
+
|
| 519 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 520 |
+
h = self.expand(x)
|
| 521 |
+
a, b = h.chunk(2, dim=-1)
|
| 522 |
+
return self.dropout(self.contract(F.silu(a) * b))
|
| 523 |
+
|
| 524 |
+
|
| 525 |
+
__all__ = [
|
| 526 |
+
"TritonReLU2UDLP",
|
| 527 |
+
"TritonLReLU2UDLP",
|
| 528 |
+
"TritonLReLU2UDLP_T4",
|
| 529 |
+
"TritonSwiGLUUDLP",
|
| 530 |
+
]
|
vathos/blocks.py
ADDED
|
@@ -0,0 +1,2110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
All the layers included here aim to be already optimized by torch itself, without requiring triton/cuda kernels (explicitly),
|
| 3 |
+
in fact they try to only use matmul and Linear operations which are arguably already optimized implicitly in torch.
|
| 4 |
+
"""
|
| 5 |
+
import time
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
|
| 9 |
+
from Vathos._basics import *
|
| 10 |
+
from typing import Tuple, Optional, Union, List
|
| 11 |
+
import re
|
| 12 |
+
from Vathos.complexity import combine_big_o_product, combine_big_o_sum
|
| 13 |
+
import math
|
| 14 |
+
import torch.nn.functional as F
|
| 15 |
+
from tqdm import tqdm
|
| 16 |
+
from Vathos._spatials import *
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class Block1d(Layer):
|
| 20 |
+
def __init__(self, d_model, channel_mixer: Layer, spatial_mixer: Layer, norm=nn.LayerNorm):
|
| 21 |
+
super().__init__()
|
| 22 |
+
self.spatial_mixer = spatial_mixer
|
| 23 |
+
self.channel_mixer = channel_mixer
|
| 24 |
+
self.norm1 = norm(spatial_mixer.d_model)
|
| 25 |
+
self.norm2 = norm(spatial_mixer.d_model)
|
| 26 |
+
|
| 27 |
+
def forward(self, x: torch.Tensor, ve=None):
|
| 28 |
+
if ve is not None:
|
| 29 |
+
x = x + self.spatial_mixer(self.norm1(x), ve)
|
| 30 |
+
else:
|
| 31 |
+
x = x + self.spatial_mixer(self.norm1(x))
|
| 32 |
+
|
| 33 |
+
x = x + self.channel_mixer(self.norm2(x))
|
| 34 |
+
return x
|
| 35 |
+
|
| 36 |
+
def generate(self, x: torch.Tensor, ve=None):
|
| 37 |
+
h = self.norm1(x)
|
| 38 |
+
if self.spatial_mixer.has_custom_generate():
|
| 39 |
+
spatial_out = _call_with_ve(self.spatial_mixer.generate, h, ve)
|
| 40 |
+
else:
|
| 41 |
+
spatial_out = _call_with_ve(self.spatial_mixer, h, ve)
|
| 42 |
+
x = x + spatial_out
|
| 43 |
+
|
| 44 |
+
if self.channel_mixer.has_custom_generate():
|
| 45 |
+
x = x + self.channel_mixer.generate(self.norm2(x))
|
| 46 |
+
else:
|
| 47 |
+
x = x + self.channel_mixer(self.norm2(x))
|
| 48 |
+
|
| 49 |
+
return x
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _call_with_ve(fn, h, ve):
|
| 53 |
+
"""Call fn(h, ve=ve) if it accepts ve, otherwise fn(h). Used by Block.generate
|
| 54 |
+
so that spatial mixers without ve support stay drop-in."""
|
| 55 |
+
if ve is None:
|
| 56 |
+
return fn(h)
|
| 57 |
+
try:
|
| 58 |
+
return fn(h, ve=ve)
|
| 59 |
+
except TypeError:
|
| 60 |
+
return fn(h)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class CBlock1d(Layer):
|
| 64 |
+
def __init__(self, d_model, channel_mixer: Layer, spatial_mixer: Layer, norm=nn.LayerNorm):
|
| 65 |
+
super().__init__()
|
| 66 |
+
self.spatial_mixer = spatial_mixer
|
| 67 |
+
self.channel_mixer = channel_mixer
|
| 68 |
+
self.norm1 = norm(spatial_mixer.d_model)
|
| 69 |
+
self.norm2 = norm(spatial_mixer.d_model)
|
| 70 |
+
self.l = nn.Parameter(torch.tensor([1.0, 1.0, 1.0, 1.0]))
|
| 71 |
+
|
| 72 |
+
def forward(self, x: torch.Tensor, ve=None):
|
| 73 |
+
if ve is not None:
|
| 74 |
+
x = x * self.l[0] + self.spatial_mixer(self.norm1(x), ve) * self.l[1]
|
| 75 |
+
else:
|
| 76 |
+
x = x * self.l[0] + self.spatial_mixer(self.norm1(x)) * self.l[1]
|
| 77 |
+
x = x * self.l[2] + self.channel_mixer(self.norm2(x)) * self.l[3]
|
| 78 |
+
return x
|
| 79 |
+
|
| 80 |
+
def generate(self, x: torch.Tensor, ve=None):
|
| 81 |
+
h = self.norm1(x)
|
| 82 |
+
if self.spatial_mixer.has_custom_generate():
|
| 83 |
+
spatial_out = _call_with_ve(self.spatial_mixer.generate, h, ve)
|
| 84 |
+
else:
|
| 85 |
+
spatial_out = _call_with_ve(self.spatial_mixer, h, ve)
|
| 86 |
+
x = x * self.l[0] + spatial_out * self.l[1]
|
| 87 |
+
if self.channel_mixer.has_custom_generate():
|
| 88 |
+
x = x * self.l[2] + self.channel_mixer.generate(self.norm2(x)) * self.l[3]
|
| 89 |
+
else:
|
| 90 |
+
x = x * self.l[2] + self.channel_mixer(self.norm2(x)) * self.l[3]
|
| 91 |
+
return x
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
# TODO: Smear
|
| 95 |
+
class SmearBlock1d(Layer):
|
| 96 |
+
def __init__(self, d_model, channel_mixer: Layer, spatial_mixer: Layer, norm=nn.LayerNorm):
|
| 97 |
+
super().__init__()
|
| 98 |
+
self.spatial_mixer = spatial_mixer
|
| 99 |
+
self.channel_mixer = channel_mixer
|
| 100 |
+
self.norm1 = norm(spatial_mixer.d_model)
|
| 101 |
+
self.norm2 = norm(spatial_mixer.d_model)
|
| 102 |
+
|
| 103 |
+
def forward(self, x: torch.Tensor):
|
| 104 |
+
x = x + self.spatial_mixer(self.norm1(x))
|
| 105 |
+
x = x + self.channel_mixer(self.norm2(x))
|
| 106 |
+
return x
|
| 107 |
+
|
| 108 |
+
def generate(self, x: torch.Tensor):
|
| 109 |
+
# Use generate if available, otherwise forward
|
| 110 |
+
if self.spatial_mixer.has_custom_generate():
|
| 111 |
+
x = x + self.spatial_mixer.generate(self.norm1(x))
|
| 112 |
+
else:
|
| 113 |
+
x = x + self.spatial_mixer(self.norm1(x))
|
| 114 |
+
|
| 115 |
+
if self.channel_mixer.has_custom_generate():
|
| 116 |
+
x = x + self.channel_mixer.generate(self.norm2(x))
|
| 117 |
+
else:
|
| 118 |
+
x = x + self.channel_mixer(self.norm2(x))
|
| 119 |
+
|
| 120 |
+
return x
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
class JBlock1d(Layer):
|
| 124 |
+
def __init__(self, d_model, channel_mixer: nn.Module, spatial_mixer: nn.Module, norm=nn.LayerNorm):
|
| 125 |
+
super().__init__()
|
| 126 |
+
self.channel_mixer = channel_mixer
|
| 127 |
+
self.spatial_mixer = spatial_mixer
|
| 128 |
+
self.norm = norm(d_model)
|
| 129 |
+
|
| 130 |
+
def forward(self, x: torch.Tensor, ve=None):
|
| 131 |
+
h = self.norm(x)
|
| 132 |
+
if ve is not None:
|
| 133 |
+
return x + self.spatial_mixer(h, ve) + self.channel_mixer(h)
|
| 134 |
+
return x + self.spatial_mixer(h) + self.channel_mixer(h)
|
| 135 |
+
|
| 136 |
+
def generate(self, x: torch.Tensor, ve=None):
|
| 137 |
+
h = self.norm(x)
|
| 138 |
+
sm_fn = self.spatial_mixer.generate if (hasattr(self.spatial_mixer, 'has_custom_generate') and self.spatial_mixer.has_custom_generate()) else self.spatial_mixer
|
| 139 |
+
cm_fn = self.channel_mixer.generate if (hasattr(self.channel_mixer, 'has_custom_generate') and self.channel_mixer.has_custom_generate()) else self.channel_mixer
|
| 140 |
+
spatial_out = _call_with_ve(sm_fn, h, ve)
|
| 141 |
+
channel_out = cm_fn(h)
|
| 142 |
+
return x + spatial_out + channel_out
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
class ExpandingBlock1d(Layer):
|
| 146 |
+
def __init__(self, d_model, channel_mixer: Layer, spatial_mixer: Layer, expand=1):
|
| 147 |
+
super().__init__()
|
| 148 |
+
self.spatial_mixer = spatial_mixer
|
| 149 |
+
self.channel_mixer = channel_mixer
|
| 150 |
+
self.expander = nn.Linear(d_model, d_model * expand, bias=False)
|
| 151 |
+
self.contractor = nn.Linear(d_model * expand, d_model, bias=False)
|
| 152 |
+
self.norm1 = nn.LayerNorm(spatial_mixer.d_model)
|
| 153 |
+
self.norm2 = nn.LayerNorm(spatial_mixer.d_model)
|
| 154 |
+
|
| 155 |
+
def forward(self, x: torch.Tensor):
|
| 156 |
+
x = x + self.contractor(self.spatial_mixer(self.norm1(self.expander(x))))
|
| 157 |
+
x = x + self.channel_mixer(self.norm2(x))
|
| 158 |
+
return x
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
class Renamer:
|
| 162 |
+
def __init__(self, constructor, renames: dict):
|
| 163 |
+
super().__init__()
|
| 164 |
+
self.constructor = constructor
|
| 165 |
+
self.renames = renames
|
| 166 |
+
|
| 167 |
+
def __call__(self, *args, **kwargs):
|
| 168 |
+
renamed_kwargs = {}
|
| 169 |
+
for key, value in kwargs.items():
|
| 170 |
+
if key in self.renames:
|
| 171 |
+
renamed_kwargs[self.renames[key]] = value
|
| 172 |
+
else:
|
| 173 |
+
renamed_kwargs[key] = value
|
| 174 |
+
return self.constructor(*args, renamed_kwargs)
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
class BlockStack(Layer):
|
| 178 |
+
def __init__(self, blocks: Tuple[Block1d | Layer]):
|
| 179 |
+
super().__init__()
|
| 180 |
+
self.blocks = blocks
|
| 181 |
+
self.stack = nn.ModuleList(blocks)
|
| 182 |
+
|
| 183 |
+
def forward(self, x: torch):
|
| 184 |
+
for block in self.stack:
|
| 185 |
+
x = block(x)
|
| 186 |
+
return x
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
class DepthwiseCausalConv1d(Layer):
|
| 190 |
+
__name__ = "CausalConv1d"
|
| 191 |
+
__complexity__ = "O(L d k)"
|
| 192 |
+
|
| 193 |
+
def __init__(self, d_model, k=3):
|
| 194 |
+
super().__init__()
|
| 195 |
+
self.d_model = d_model
|
| 196 |
+
self.k = k
|
| 197 |
+
self.pad = k - 1
|
| 198 |
+
|
| 199 |
+
self.K = nn.Parameter(torch.randn(d_model, k) / (k ** 0.5))
|
| 200 |
+
|
| 201 |
+
def forward(self, x):
|
| 202 |
+
b, L, d = x.shape
|
| 203 |
+
x = x.transpose(1, 2)
|
| 204 |
+
x_pad = F.pad(x, (self.pad, 0))
|
| 205 |
+
out = F.conv1d(x_pad, self.K.unsqueeze(1), groups=d)
|
| 206 |
+
return out.transpose(1, 2)
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
class CausalConv1d(Layer):
|
| 210 |
+
__name__ = "CausalConv1d"
|
| 211 |
+
__complexity__ = "O(L d k)"
|
| 212 |
+
|
| 213 |
+
def __init__(self, d_model, k=3, groups=None, outproj=False):
|
| 214 |
+
super().__init__()
|
| 215 |
+
self.d_model = d_model
|
| 216 |
+
self.k = k
|
| 217 |
+
self.pad = k - 1
|
| 218 |
+
self.groups = groups if groups is not None else d_model
|
| 219 |
+
self.outproj = nn.Linear(groups, d_model, bias=False) if outproj else Identity()
|
| 220 |
+
if self.groups != d_model and not outproj:
|
| 221 |
+
flag(
|
| 222 |
+
"Output channel numbers will be the number of groups, use outproj=True if you want to enable a linear projection to go back to d_model")
|
| 223 |
+
|
| 224 |
+
self.K = nn.Parameter(torch.randn(d_model, k) / (k ** 0.5))
|
| 225 |
+
|
| 226 |
+
def forward(self, x):
|
| 227 |
+
x = x.transpose(1, 2)
|
| 228 |
+
x_pad = F.pad(x, (self.pad, 0))
|
| 229 |
+
out = F.conv1d(x_pad, self.K.unsqueeze(1), groups=self.groups)
|
| 230 |
+
return out.transpose(1, 2)
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
class LSTM(Layer):
|
| 234 |
+
__name__ = "LSTM"
|
| 235 |
+
__complexity__ = "O(L d^2)"
|
| 236 |
+
|
| 237 |
+
def __init__(self, d_model, d_hidden, bidirectional=False, dropout=0.1, n_layer=1):
|
| 238 |
+
super().__init__()
|
| 239 |
+
self.bidirectional = bidirectional
|
| 240 |
+
self.d_model = d_model
|
| 241 |
+
self.d_hidden = d_hidden
|
| 242 |
+
self.projout = nn.Linear(d_hidden, d_model) if d_model != d_hidden else Identity()
|
| 243 |
+
self.LSTM = nn.LSTM(d_model, d_hidden, bidirectional=bidirectional, batch_first=True, num_layers=n_layer)
|
| 244 |
+
self.dropout = nn.Dropout(dropout)
|
| 245 |
+
|
| 246 |
+
def forward(self, x):
|
| 247 |
+
out, _ = self.LSTM(x)
|
| 248 |
+
out = self.dropout(out)
|
| 249 |
+
return self.projout(out)
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
class GRU(Layer):
|
| 253 |
+
__name__ = "GRU"
|
| 254 |
+
__complexity__ = "O(L d^2)"
|
| 255 |
+
|
| 256 |
+
def __init__(self, d_model, d_hidden, bidirectional=False, dropout=0.1, n_layer=1):
|
| 257 |
+
super().__init__()
|
| 258 |
+
self.bidirectional = bidirectional
|
| 259 |
+
self.d_model = d_model
|
| 260 |
+
self.d_hidden = d_hidden
|
| 261 |
+
self.projout = nn.Linear(d_hidden, d_model) if d_model != d_hidden else Identity()
|
| 262 |
+
self.GRU = nn.GRU(d_model, d_hidden, bidirectional=bidirectional, batch_first=True, num_layers=n_layer)
|
| 263 |
+
self.dropout = nn.Dropout(dropout)
|
| 264 |
+
|
| 265 |
+
def forward(self, x):
|
| 266 |
+
out, _ = self.GRU(x)
|
| 267 |
+
out = self.dropout(out)
|
| 268 |
+
return self.projout(out)
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
class LinearMixer(Layer):
|
| 272 |
+
__name__ = "Linear Mixer"
|
| 273 |
+
__complexity__ = "O(L^2 d)"
|
| 274 |
+
|
| 275 |
+
def __init__(self, d_model, max_len=1000, causal=True):
|
| 276 |
+
super().__init__()
|
| 277 |
+
self.causal = causal
|
| 278 |
+
self.d_model = d_model
|
| 279 |
+
self.W = nn.Parameter(torch.randn(max_len, max_len) * 0.01)
|
| 280 |
+
|
| 281 |
+
def forward(self, x):
|
| 282 |
+
x = x / math.sqrt(self.d_model)
|
| 283 |
+
if self.causal:
|
| 284 |
+
return torch.tril(self.W[:x.shape[1], :x.shape[1]]) @ x
|
| 285 |
+
else:
|
| 286 |
+
return self.W[:x.shape[1], :x.shape[1]] @ x
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
class MLPMixer(Layer):
|
| 290 |
+
__name__ = "Linear Mixer"
|
| 291 |
+
__complexity__ = "O(L^2 d)"
|
| 292 |
+
|
| 293 |
+
def __init__(self, d_model, max_len=1000, L_expand=1, causal=False, activation=nn.GELU):
|
| 294 |
+
super().__init__()
|
| 295 |
+
if causal:
|
| 296 |
+
if L_expand < 1:
|
| 297 |
+
raise ValueError("For the MLPMixer to be causal, L_expand must be greater than 1")
|
| 298 |
+
if L_expand > 1:
|
| 299 |
+
raise NotImplementedError(
|
| 300 |
+
"Causal MLP Mixer with L_expand is currently not implemented") # TODO: Causal Lex
|
| 301 |
+
self.causal = causal
|
| 302 |
+
self.d_model = d_model
|
| 303 |
+
self.W1 = nn.Parameter(torch.randn(max_len * L_expand, max_len) / max_len)
|
| 304 |
+
self.W2 = nn.Parameter(torch.randn(max_len, max_len * L_expand) / max_len)
|
| 305 |
+
self.activation = activation()
|
| 306 |
+
|
| 307 |
+
def forward(self, x):
|
| 308 |
+
x = x / math.sqrt(self.d_model)
|
| 309 |
+
|
| 310 |
+
return self.W2 @ self.activation(self.W1 @ x)
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
class ShortConvGatedMixer(Layer):
|
| 314 |
+
def __init__(self, d_model, mixer, mixer_params, k=4, activation=nn.Sigmoid, k1=None, k2=None):
|
| 315 |
+
super().__init__()
|
| 316 |
+
if k1 is None:
|
| 317 |
+
k1 = k2 = k
|
| 318 |
+
|
| 319 |
+
self.mixer = mixer(mixer_params)
|
| 320 |
+
self.conv_1 = DepthwiseCausalConv1d(d_model, k=k1)
|
| 321 |
+
self.conv_2 = DepthwiseCausalConv1d(d_model, k=k2)
|
| 322 |
+
self.activation = activation()
|
| 323 |
+
|
| 324 |
+
def forward(self, x):
|
| 325 |
+
g1 = self.conv_1(x)
|
| 326 |
+
g2 = self.activation(self.conv_2(x))
|
| 327 |
+
return self.mixer(g1) * g2 + (1 - g2) * x
|
| 328 |
+
|
| 329 |
+
|
| 330 |
+
class SmearGate(Layer):
|
| 331 |
+
"""Smear gate (Modded-NanoGPT 2025-09 record): gate input-dependent + skip token-1.
|
| 332 |
+
|
| 333 |
+
Per ogni posizione t > 0:
|
| 334 |
+
x[t] += smear_lambda * sigmoid(W_gate @ x[t, :gate_input_dim]) * x[t-1]
|
| 335 |
+
|
| 336 |
+
Gate sparse (default 12 dim su d_model), input-dependent. Token 0 invariato.
|
| 337 |
+
smear_lambda init=0 ⇒ no-op iniziale. Receptive field = 1 (lookback=1 in fast gen).
|
| 338 |
+
"""
|
| 339 |
+
__name__ = "SmearGate"
|
| 340 |
+
|
| 341 |
+
def __init__(self, d_model: int, gate_input_dim: int = 12):
|
| 342 |
+
super().__init__()
|
| 343 |
+
self.gate_input_dim = gate_input_dim
|
| 344 |
+
self.gate = nn.Linear(gate_input_dim, d_model, bias=False)
|
| 345 |
+
self.smear_lambda = nn.Parameter(torch.zeros(1))
|
| 346 |
+
|
| 347 |
+
def _init_weights(self):
|
| 348 |
+
# Identity-at-init: gate orthogonal; smear_lambda già 0 da __init__
|
| 349 |
+
# ⇒ shifted = 0 ⇒ no-op iniziale indipendentemente dal gate.
|
| 350 |
+
nn.init.orthogonal_(self.gate.weight)
|
| 351 |
+
|
| 352 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 353 |
+
# x: [B, L, D]. Caso L==1: nessuno "x[t-1]" disponibile → no-op.
|
| 354 |
+
if x.size(1) < 2:
|
| 355 |
+
return x
|
| 356 |
+
gate = torch.sigmoid(self.gate(x[:, 1:, :self.gate_input_dim])) # [B, L-1, D]
|
| 357 |
+
prev = x[:, :-1, :] # [B, L-1, D]
|
| 358 |
+
shifted = self.smear_lambda * gate * prev # [B, L-1, D]
|
| 359 |
+
return torch.cat([x[:, :1, :], x[:, 1:, :] + shifted], dim=1)
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
class Embedder(Layer):
|
| 363 |
+
__name__ = "SymbolicEmbedder"
|
| 364 |
+
__complexity__ = "O(L d)"
|
| 365 |
+
|
| 366 |
+
def __init__(self, vocab_size, d_model: int):
|
| 367 |
+
super().__init__()
|
| 368 |
+
self.vocab_size = vocab_size
|
| 369 |
+
self.d_model = d_model
|
| 370 |
+
self.frozen = False
|
| 371 |
+
self.embedding = nn.Embedding(vocab_size, d_model)
|
| 372 |
+
|
| 373 |
+
def freeze(self):
|
| 374 |
+
if self.frozen:
|
| 375 |
+
flag("Trying to freeze an already frozen Embedder")
|
| 376 |
+
else:
|
| 377 |
+
self.embedding.weight.requires_grad = False
|
| 378 |
+
self.frozen = True
|
| 379 |
+
|
| 380 |
+
def unfreeze(self):
|
| 381 |
+
if self.frozen:
|
| 382 |
+
self.embedding.weight.requires_grad = True
|
| 383 |
+
self.frozen = False
|
| 384 |
+
else:
|
| 385 |
+
flag("Trying to unfreeze an already unfrozen Embedder")
|
| 386 |
+
|
| 387 |
+
def forward(self, x):
|
| 388 |
+
return self.embedding(x)
|
| 389 |
+
|
| 390 |
+
def finetune(self):
|
| 391 |
+
self.freeze()
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
class EasyEmbedder(Layer):
|
| 395 |
+
__name__ = "SymbolicEmbedder"
|
| 396 |
+
__complexity__ = "O(L d)"
|
| 397 |
+
|
| 398 |
+
def __init__(self, vocab_size, d_model: int, dropout=0.13):
|
| 399 |
+
super().__init__()
|
| 400 |
+
self.vocab_size = vocab_size
|
| 401 |
+
self.d_model = d_model
|
| 402 |
+
self.embedding = nn.Embedding(vocab_size, d_model)
|
| 403 |
+
self.dropout = nn.Dropout(dropout)
|
| 404 |
+
|
| 405 |
+
def forward(self, x):
|
| 406 |
+
return self.dropout(self.embedding(x))
|
| 407 |
+
|
| 408 |
+
|
| 409 |
+
class HybridAttentionBlock1d(Layer):
|
| 410 |
+
def __init__(self, d_model, sec_mixer, sec_params, attn_params, n_attn=1, n_sec=3,
|
| 411 |
+
channel_mixer=MLP, channel_params=None):
|
| 412 |
+
super().__init__()
|
| 413 |
+
self.n_sec = n_sec
|
| 414 |
+
self.n_attn = n_attn
|
| 415 |
+
self.d_model = d_model
|
| 416 |
+
self.attn_params = attn_params
|
| 417 |
+
self.sec_params = sec_params
|
| 418 |
+
self.sec_mixer = sec_mixer
|
| 419 |
+
self.channel_mixer = channel_mixer
|
| 420 |
+
self.channel_params = channel_params
|
| 421 |
+
|
| 422 |
+
if channel_params is None and isinstance(channel_mixer, MLP):
|
| 423 |
+
self.channel_params = {'expand': 2}
|
| 424 |
+
else:
|
| 425 |
+
self.channel_params = channel_params
|
| 426 |
+
self.attn_blocks = nn.ModuleList([
|
| 427 |
+
Block1d(
|
| 428 |
+
channel_mixer=self.channel_mixer(d_model=d_model, **self.channel_params),
|
| 429 |
+
spatial_mixer=MultiheadAttentionMixer(d_model=d_model, **self.attn_params)
|
| 430 |
+
)
|
| 431 |
+
for _ in range(self.n_attn)
|
| 432 |
+
])
|
| 433 |
+
self.sec_blocks = nn.ModuleList([
|
| 434 |
+
Block1d(
|
| 435 |
+
channel_mixer=self.channel_mixer(d_model=d_model, **self.channel_params),
|
| 436 |
+
spatial_mixer=self.sec_mixer(d_model=d_model, **self.sec_params)
|
| 437 |
+
)
|
| 438 |
+
for _ in range(self.n_sec)
|
| 439 |
+
])
|
| 440 |
+
|
| 441 |
+
def forward(self, x):
|
| 442 |
+
for sec in self.sec_blocks:
|
| 443 |
+
x = sec(x)
|
| 444 |
+
for attn in self.attn_blocks:
|
| 445 |
+
x = attn(x)
|
| 446 |
+
return x
|
| 447 |
+
|
| 448 |
+
|
| 449 |
+
########################################################################################################################
|
| 450 |
+
# VISION
|
| 451 |
+
########################################################################################################################
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
class PatchEmbedder(Layer):
|
| 455 |
+
__name__ = "PatchEmbedder"
|
| 456 |
+
|
| 457 |
+
def __init__(
|
| 458 |
+
self,
|
| 459 |
+
vocab_size=None,
|
| 460 |
+
d_model: int = 768,
|
| 461 |
+
img_size: Union[int, Tuple[int, int]] = 224,
|
| 462 |
+
patch_size: Union[int, Tuple[int, int]] = 16,
|
| 463 |
+
in_chans: int = 3,
|
| 464 |
+
flatten: bool = True,
|
| 465 |
+
norm_layer: Optional[Layer] = None,
|
| 466 |
+
cls: bool = False,
|
| 467 |
+
):
|
| 468 |
+
super().__init__()
|
| 469 |
+
if isinstance(patch_size, int):
|
| 470 |
+
patch_size = (patch_size, patch_size)
|
| 471 |
+
if isinstance(img_size, int):
|
| 472 |
+
img_size = (img_size, img_size)
|
| 473 |
+
|
| 474 |
+
self.img_size = img_size
|
| 475 |
+
self.patch_size = patch_size
|
| 476 |
+
self.in_chans = in_chans
|
| 477 |
+
self.embed_dim = d_model
|
| 478 |
+
self.flatten = flatten
|
| 479 |
+
self.frozen = False
|
| 480 |
+
self.norm = norm_layer if norm_layer is not None else None
|
| 481 |
+
self.use_cls = cls # ← NEW
|
| 482 |
+
|
| 483 |
+
self.proj = nn.Conv2d(in_chans, d_model, kernel_size=patch_size, stride=patch_size)
|
| 484 |
+
|
| 485 |
+
if self.use_cls:
|
| 486 |
+
assert flatten, "CLS token requires flatten=True"
|
| 487 |
+
self.cls_token = nn.Parameter(torch.zeros(1, 1, d_model)) # ← NEW
|
| 488 |
+
|
| 489 |
+
self.grid_size = None
|
| 490 |
+
if img_size is not None:
|
| 491 |
+
self.grid_size = (math.ceil(img_size[0] / patch_size[0]),
|
| 492 |
+
math.ceil(img_size[1] / patch_size[1]))
|
| 493 |
+
|
| 494 |
+
self._num_patches = None
|
| 495 |
+
if self.grid_size is not None:
|
| 496 |
+
self._num_patches = self.grid_size[0] * self.grid_size[1]
|
| 497 |
+
|
| 498 |
+
def freeze(self):
|
| 499 |
+
if self.frozen:
|
| 500 |
+
flag("Trying to freeze an already frozen PatchEmbedder")
|
| 501 |
+
return
|
| 502 |
+
for p in self.proj.parameters():
|
| 503 |
+
p.requires_grad = False
|
| 504 |
+
if self.norm is not None:
|
| 505 |
+
for p in self.norm.parameters():
|
| 506 |
+
p.requires_grad = False
|
| 507 |
+
if self.use_cls: # ← FREEZE CLS TOKEN TOO
|
| 508 |
+
self.cls_token.requires_grad = False
|
| 509 |
+
self.frozen = True
|
| 510 |
+
|
| 511 |
+
def unfreeze(self):
|
| 512 |
+
if not self.frozen:
|
| 513 |
+
flag("Trying to unfreeze an already unfrozen PatchEmbedder")
|
| 514 |
+
return
|
| 515 |
+
for p in self.proj.parameters():
|
| 516 |
+
p.requires_grad = True
|
| 517 |
+
if self.norm is not None:
|
| 518 |
+
for p in self.norm.parameters():
|
| 519 |
+
p.requires_grad = True
|
| 520 |
+
if self.use_cls:
|
| 521 |
+
self.cls_token.requires_grad = True
|
| 522 |
+
self.frozen = False
|
| 523 |
+
|
| 524 |
+
def num_patches(self, H: Optional[int] = None, W: Optional[int] = None) -> int:
|
| 525 |
+
if H is None or W is None:
|
| 526 |
+
if self.img_size is not None:
|
| 527 |
+
H, W = self.img_size
|
| 528 |
+
else:
|
| 529 |
+
raise ValueError("Must provide H and W or set img_size during init.")
|
| 530 |
+
ph, pw = self.patch_size
|
| 531 |
+
Hp = math.ceil(H / ph)
|
| 532 |
+
Wp = math.ceil(W / pw)
|
| 533 |
+
return Hp * Wp
|
| 534 |
+
|
| 535 |
+
def _pad_to_patch_multiple(self, x: torch.Tensor) -> torch.Tensor:
|
| 536 |
+
_, _, H, W = x.shape
|
| 537 |
+
ph, pw = self.patch_size
|
| 538 |
+
pad_h = (ph - H % ph) % ph
|
| 539 |
+
pad_w = (pw - W % pw) % pw
|
| 540 |
+
if pad_h == 0 and pad_w == 0:
|
| 541 |
+
return x
|
| 542 |
+
return nn.functional.pad(x, (0, pad_w, 0, pad_h), mode='constant', value=0)
|
| 543 |
+
|
| 544 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 545 |
+
assert x.dim() == 4, "Input must be 4D (B, C, H, W)"
|
| 546 |
+
assert x.dtype in (torch.float32, torch.float16, torch.bfloat16), "Input dtype must be float"
|
| 547 |
+
B, C, H, W = x.shape
|
| 548 |
+
assert C == self.in_chans, f"Expected {self.in_chans} channels, got {C}"
|
| 549 |
+
|
| 550 |
+
x = self._pad_to_patch_multiple(x)
|
| 551 |
+
|
| 552 |
+
x = self.proj(x) # (B, embed_dim, H_p, W_p)
|
| 553 |
+
H_p, W_p = x.shape[2], x.shape[3]
|
| 554 |
+
self._num_patches = H_p * W_p
|
| 555 |
+
|
| 556 |
+
if self.flatten:
|
| 557 |
+
x = x.flatten(2).transpose(1, 2) # (B, N, embed_dim)
|
| 558 |
+
|
| 559 |
+
if self.use_cls:
|
| 560 |
+
cls_tok = self.cls_token.expand(B, -1, -1) # (B, 1, D)
|
| 561 |
+
x = torch.cat((cls_tok, x), dim=1) # (B, 1+N, D)
|
| 562 |
+
|
| 563 |
+
if self.norm is not None:
|
| 564 |
+
x = self.norm(x)
|
| 565 |
+
else:
|
| 566 |
+
if self.norm is not None:
|
| 567 |
+
x = self.norm(x)
|
| 568 |
+
|
| 569 |
+
return x
|
| 570 |
+
|
| 571 |
+
|
| 572 |
+
class MeanClassificationHead(Layer):
|
| 573 |
+
__name__ = "MeanClassificationHead"
|
| 574 |
+
|
| 575 |
+
def __init__(self, d_model, vocab_size):
|
| 576 |
+
super().__init__()
|
| 577 |
+
self.proj = nn.Linear(d_model, vocab_size)
|
| 578 |
+
|
| 579 |
+
def forward(self, x):
|
| 580 |
+
x = x.mean(dim=1)
|
| 581 |
+
return self.proj(x)
|
| 582 |
+
|
| 583 |
+
|
| 584 |
+
class ClsHead(Layer):
|
| 585 |
+
__name__ = "Cls Head"
|
| 586 |
+
|
| 587 |
+
def __init__(self, d_model, vocab_size):
|
| 588 |
+
super().__init__()
|
| 589 |
+
self.proj = nn.Linear(d_model, vocab_size)
|
| 590 |
+
|
| 591 |
+
def forward(self, x):
|
| 592 |
+
x = x[:, -1:, :]
|
| 593 |
+
return self.proj(x)[:, 0, :]
|
| 594 |
+
|
| 595 |
+
|
| 596 |
+
class MultiHeadUnembedder(Layer):
|
| 597 |
+
__name__ = "UnbiasedLinear"
|
| 598 |
+
__complexity__ = "O(L d^2)"
|
| 599 |
+
|
| 600 |
+
def __init__(self, d_model, vocab_size, k=4):
|
| 601 |
+
super(MultiHeadUnembedder, self).__init__()
|
| 602 |
+
self.linear = nn.Linear(d_model, vocab_size * k, bias=False)
|
| 603 |
+
|
| 604 |
+
def forward(self, x):
|
| 605 |
+
return self.linear(x)
|
| 606 |
+
|
| 607 |
+
|
| 608 |
+
class VathosModel(Layer):
|
| 609 |
+
__name__ = "VathosModel"
|
| 610 |
+
|
| 611 |
+
def __init__(self):
|
| 612 |
+
super().__init__()
|
| 613 |
+
|
| 614 |
+
self._losses = []
|
| 615 |
+
self._losses_dict = {}
|
| 616 |
+
self._losses_per_epoch = []
|
| 617 |
+
self._losses_per_epoch_dict = {}
|
| 618 |
+
self._losses_this_epoch = []
|
| 619 |
+
self._metrics = dict()
|
| 620 |
+
self._metrics_this_epoch = dict()
|
| 621 |
+
self._metrics_per_epoch = dict()
|
| 622 |
+
self.autosave = True
|
| 623 |
+
self.autosave_overwrite = True
|
| 624 |
+
self.best_loss = float('inf')
|
| 625 |
+
self.checkpoints = 0
|
| 626 |
+
self.steps = 0
|
| 627 |
+
self.steps_per_epoch = 0
|
| 628 |
+
self.epochs = 0
|
| 629 |
+
self.finetuning = False
|
| 630 |
+
self.name = 'VathosModel_defaultName'
|
| 631 |
+
|
| 632 |
+
def flag_not_training(self):
|
| 633 |
+
if not self.training:
|
| 634 |
+
flag("You are not training")
|
| 635 |
+
|
| 636 |
+
def register_loss(self, loss: float) -> bool:
|
| 637 |
+
"""Registra loss + NaN/Inf detection.
|
| 638 |
+
|
| 639 |
+
Ritorna True se loss finita, False se NaN/Inf (con warning).
|
| 640 |
+
Mantiene `self._nan_streak` (consecutive NaN counter).
|
| 641 |
+
"""
|
| 642 |
+
is_bad = (loss != loss) or (loss == float('inf')) or (loss == float('-inf'))
|
| 643 |
+
if is_bad:
|
| 644 |
+
self._nan_streak = getattr(self, "_nan_streak", 0) + 1
|
| 645 |
+
import warnings
|
| 646 |
+
warnings.warn(
|
| 647 |
+
f"VathosModel.register_loss: loss non-finita ({loss}) a step "
|
| 648 |
+
f"{self.steps}. nan_streak={self._nan_streak}.",
|
| 649 |
+
RuntimeWarning, stacklevel=2,
|
| 650 |
+
)
|
| 651 |
+
else:
|
| 652 |
+
self._nan_streak = 0
|
| 653 |
+
self._losses_dict[self.steps] = loss
|
| 654 |
+
self._losses.append(loss)
|
| 655 |
+
self._losses_this_epoch.append(loss)
|
| 656 |
+
self.steps += 1
|
| 657 |
+
return not is_bad
|
| 658 |
+
|
| 659 |
+
def get_last_loss(self):
|
| 660 |
+
return self._losses[-1]
|
| 661 |
+
|
| 662 |
+
@property
|
| 663 |
+
def nan_streak(self) -> int:
|
| 664 |
+
return getattr(self, "_nan_streak", 0)
|
| 665 |
+
|
| 666 |
+
def get_mean_loss(self, epoch=True):
|
| 667 |
+
if epoch:
|
| 668 |
+
return np.mean(self._losses_this_epoch)
|
| 669 |
+
else:
|
| 670 |
+
return np.mean(self._losses)
|
| 671 |
+
|
| 672 |
+
def register_metrics(self, metrics: dict):
|
| 673 |
+
for metric in metrics:
|
| 674 |
+
if metric in self._metrics:
|
| 675 |
+
self._metrics[metric].append(metrics[metric])
|
| 676 |
+
self._metrics_this_epoch[metric].append(metrics[metric])
|
| 677 |
+
else:
|
| 678 |
+
flag(f"Registering a new metric {metric}")
|
| 679 |
+
self._metrics[metric] = [metrics[metric]]
|
| 680 |
+
self._metrics_this_epoch[metric] = [metrics[metric]]
|
| 681 |
+
|
| 682 |
+
def register_epoch(self):
|
| 683 |
+
self.epochs += 1
|
| 684 |
+
self._losses_per_epoch.append(np.mean(self._losses_this_epoch))
|
| 685 |
+
self._losses_per_epoch_dict[self.steps] = np.mean(self._losses_this_epoch)
|
| 686 |
+
self._losses_this_epoch = []
|
| 687 |
+
|
| 688 |
+
for metric in self._metrics_this_epoch:
|
| 689 |
+
if metric in self._metrics_per_epoch:
|
| 690 |
+
self._metrics_per_epoch[metric].append(np.mean(self._metrics_this_epoch[metric]))
|
| 691 |
+
else:
|
| 692 |
+
self._metrics_per_epoch[metric] = [np.mean(self._metrics_this_epoch[metric])]
|
| 693 |
+
self._metrics_this_epoch[metric] = []
|
| 694 |
+
|
| 695 |
+
if self._losses_per_epoch[-1] < self.best_loss:
|
| 696 |
+
self.best_loss = self._losses_per_epoch[-1]
|
| 697 |
+
if self.autosave:
|
| 698 |
+
self.checkpoints += 1
|
| 699 |
+
if self.autosave_overwrite:
|
| 700 |
+
self.save_checkpoint(f'{self.name}-checkpoint.pt')
|
| 701 |
+
else:
|
| 702 |
+
self.save_checkpoint(f'{self.name}-checkpoint-{self.checkpoints}.pt')
|
| 703 |
+
|
| 704 |
+
def save_state_dict(self, path):
|
| 705 |
+
torch.save(self.state_dict(), path)
|
| 706 |
+
|
| 707 |
+
def plot_losses(self):
|
| 708 |
+
print(self.steps_per_epoch)
|
| 709 |
+
plt.plot(
|
| 710 |
+
list(self._losses_dict.keys()),
|
| 711 |
+
list(self._losses_dict.values()),
|
| 712 |
+
label="Losses", linewidth=1)
|
| 713 |
+
plt.plot(
|
| 714 |
+
list(self._losses_per_epoch_dict.keys()),
|
| 715 |
+
list(self._losses_per_epoch_dict.values()),
|
| 716 |
+
label="Losses Per Epoch", linewidth=2)
|
| 717 |
+
|
| 718 |
+
plt.xlabel("steps")
|
| 719 |
+
plt.ylabel("loss")
|
| 720 |
+
plt.title("Model Losses per Steps")
|
| 721 |
+
plt.show()
|
| 722 |
+
|
| 723 |
+
def plot_metrics(self, figsize=(12, 8)):
|
| 724 |
+
n_metrics = len(self._metrics_per_epoch)
|
| 725 |
+
n_plots = 1 + n_metrics
|
| 726 |
+
|
| 727 |
+
n_cols = 2
|
| 728 |
+
n_rows = (n_plots + 1) // 2
|
| 729 |
+
|
| 730 |
+
fig, axes = plt.subplots(n_rows, n_cols, figsize=figsize)
|
| 731 |
+
|
| 732 |
+
if n_plots == 1:
|
| 733 |
+
axes = np.array([axes])
|
| 734 |
+
else:
|
| 735 |
+
axes = axes.flatten()
|
| 736 |
+
|
| 737 |
+
ax = axes[0]
|
| 738 |
+
ax.plot(
|
| 739 |
+
list(self._losses_dict.keys()),
|
| 740 |
+
list(self._losses_dict.values()),
|
| 741 |
+
label="Losses", linewidth=1, alpha=0.6)
|
| 742 |
+
ax.plot(
|
| 743 |
+
list(self._losses_per_epoch_dict.keys()),
|
| 744 |
+
list(self._losses_per_epoch_dict.values()),
|
| 745 |
+
label="Losses Per Epoch", linewidth=2)
|
| 746 |
+
ax.set_xlabel("Steps")
|
| 747 |
+
ax.set_ylabel("Loss")
|
| 748 |
+
ax.set_title("Training Loss")
|
| 749 |
+
ax.legend()
|
| 750 |
+
ax.grid(True, alpha=0.3)
|
| 751 |
+
|
| 752 |
+
for idx, (metric_name, metric_values) in enumerate(self._metrics_per_epoch.items(), start=1):
|
| 753 |
+
ax = axes[idx]
|
| 754 |
+
|
| 755 |
+
if metric_name in self._metrics and len(self._metrics[metric_name]) > 0:
|
| 756 |
+
step_indices = list(range(len(self._metrics[metric_name])))
|
| 757 |
+
ax.plot(step_indices, self._metrics[metric_name],
|
| 758 |
+
label=f"{metric_name}", linewidth=1, alpha=0.6)
|
| 759 |
+
|
| 760 |
+
epoch_indices = list(range(len(metric_values)))
|
| 761 |
+
ax.plot(epoch_indices, metric_values,
|
| 762 |
+
label=f"{metric_name} Per Epoch", linewidth=2, marker='o')
|
| 763 |
+
|
| 764 |
+
ax.set_xlabel("Steps/Epochs")
|
| 765 |
+
ax.set_ylabel(metric_name)
|
| 766 |
+
ax.set_title(f"Metric: {metric_name}")
|
| 767 |
+
ax.legend()
|
| 768 |
+
ax.grid(True, alpha=0.3)
|
| 769 |
+
|
| 770 |
+
# Hide any unused subplots
|
| 771 |
+
for idx in range(n_plots, len(axes)):
|
| 772 |
+
axes[idx].set_visible(False)
|
| 773 |
+
|
| 774 |
+
plt.tight_layout()
|
| 775 |
+
plt.show()
|
| 776 |
+
|
| 777 |
+
def save_checkpoint(self, path):
|
| 778 |
+
"""Save complete model checkpoint including training state"""
|
| 779 |
+
checkpoint = {
|
| 780 |
+
'model_state_dict': self.state_dict(),
|
| 781 |
+
'losses': self._losses,
|
| 782 |
+
'losses_dict': self._losses_dict,
|
| 783 |
+
'losses_per_epoch': self._losses_per_epoch,
|
| 784 |
+
'losses_per_epoch_dict': self._losses_per_epoch_dict,
|
| 785 |
+
'losses_this_epoch': self._losses_this_epoch,
|
| 786 |
+
'metrics': self._metrics,
|
| 787 |
+
'metrics_this_epoch': self._metrics_this_epoch,
|
| 788 |
+
'metrics_per_epoch': self._metrics_per_epoch,
|
| 789 |
+
'best_loss': self.best_loss,
|
| 790 |
+
'checkpoints': self.checkpoints,
|
| 791 |
+
'steps': self.steps,
|
| 792 |
+
'steps_per_epoch': self.steps_per_epoch,
|
| 793 |
+
'epochs': self.epochs,
|
| 794 |
+
'autosave': self.autosave,
|
| 795 |
+
'autosave_overwrite': self.autosave_overwrite,
|
| 796 |
+
}
|
| 797 |
+
torch.save(checkpoint, path)
|
| 798 |
+
|
| 799 |
+
def load_checkpoint(self, path):
|
| 800 |
+
"""Load complete model checkpoint including training state"""
|
| 801 |
+
checkpoint = torch.load(path, weights_only=False)
|
| 802 |
+
self.load_state_dict(checkpoint['model_state_dict'])
|
| 803 |
+
|
| 804 |
+
# Restore training state
|
| 805 |
+
self._losses = checkpoint['losses']
|
| 806 |
+
self._losses_dict = checkpoint['losses_dict']
|
| 807 |
+
self._losses_per_epoch = checkpoint['losses_per_epoch']
|
| 808 |
+
self._losses_per_epoch_dict = checkpoint['losses_per_epoch_dict']
|
| 809 |
+
self._losses_this_epoch = checkpoint['losses_this_epoch']
|
| 810 |
+
self._metrics = checkpoint['metrics']
|
| 811 |
+
self._metrics_this_epoch = checkpoint['metrics_this_epoch']
|
| 812 |
+
self._metrics_per_epoch = checkpoint['metrics_per_epoch']
|
| 813 |
+
self.best_loss = checkpoint['best_loss']
|
| 814 |
+
self.checkpoints = checkpoint['checkpoints']
|
| 815 |
+
self.steps = checkpoint['steps']
|
| 816 |
+
self.steps_per_epoch = checkpoint['steps_per_epoch']
|
| 817 |
+
self.epochs = checkpoint['epochs']
|
| 818 |
+
self.autosave = checkpoint['autosave']
|
| 819 |
+
self.autosave_overwrite = checkpoint['autosave_overwrite']
|
| 820 |
+
|
| 821 |
+
def finetune(self):
|
| 822 |
+
self.finetuning = True
|
| 823 |
+
|
| 824 |
+
def train(self, *args, **kwargs):
|
| 825 |
+
self.finetuning = False
|
| 826 |
+
return super().train(*args, **kwargs)
|
| 827 |
+
|
| 828 |
+
def eval(self):
|
| 829 |
+
self.finetuning = False
|
| 830 |
+
return super().eval()
|
| 831 |
+
|
| 832 |
+
|
| 833 |
+
########################################################################################################################
|
| 834 |
+
# Assemblers
|
| 835 |
+
########################################################################################################################
|
| 836 |
+
|
| 837 |
+
class SequenceModel(VathosModel):
|
| 838 |
+
__name__ = "SequenceModel"
|
| 839 |
+
|
| 840 |
+
def __init__(self, vocab_size: int, d_model: int, n_layers: int,
|
| 841 |
+
max_len=1024,
|
| 842 |
+
pos_encoder: bool | None | Layer | nn.Module = None,
|
| 843 |
+
embedder=Embedder,
|
| 844 |
+
embedder_args: dict = None,
|
| 845 |
+
unembedder=UnbiasedLinear,
|
| 846 |
+
unembedder_args=None,
|
| 847 |
+
channel_mixer=MLP,
|
| 848 |
+
spatial_mixer: Layer | nn.Module = MultiheadAttentionMixer,
|
| 849 |
+
channel_args: dict = None,
|
| 850 |
+
spatial_args: dict = None,
|
| 851 |
+
name='',
|
| 852 |
+
pad='none',
|
| 853 |
+
baseblock=Block1d,
|
| 854 |
+
baseblock_args=None,
|
| 855 |
+
dropout=0.1,
|
| 856 |
+
weight_tying=False,
|
| 857 |
+
norm=nn.LayerNorm,
|
| 858 |
+
d_modifiers: List | None = None,
|
| 859 |
+
unet_skips=False,
|
| 860 |
+
unet_weights=False
|
| 861 |
+
):
|
| 862 |
+
super().__init__()
|
| 863 |
+
self.pad = pad
|
| 864 |
+
flag(
|
| 865 |
+
"If you need to use any RoPE or alternative positional encodings which operate directly in the spatial mixer, be sure to call activate it in the spatial_args (e.g. rope=True)",
|
| 866 |
+
2)
|
| 867 |
+
|
| 868 |
+
if channel_args is None and channel_mixer is MLP:
|
| 869 |
+
channel_args = {"expand": 2, "activation": nn.GELU, "depth": 2}
|
| 870 |
+
if spatial_args is None:
|
| 871 |
+
spatial_args = {}
|
| 872 |
+
if channel_args is None:
|
| 873 |
+
channel_args = {}
|
| 874 |
+
if embedder_args is None:
|
| 875 |
+
embedder_args = {}
|
| 876 |
+
if unembedder_args is None:
|
| 877 |
+
unembedder_args = {'input_features': d_model, 'output_features': vocab_size}
|
| 878 |
+
if baseblock_args is None:
|
| 879 |
+
baseblock_args = {}
|
| 880 |
+
if d_modifiers is None:
|
| 881 |
+
d_modifiers = [1 for _ in range(d_model)]
|
| 882 |
+
else:
|
| 883 |
+
print(
|
| 884 |
+
f"{NUM} Initialized SequenceModel with d_model structure: {[int(d_model * d) for d in d_modifiers]}{RES}")
|
| 885 |
+
|
| 886 |
+
self.pipe = {}
|
| 887 |
+
self.name = name
|
| 888 |
+
self.baseblock = baseblock
|
| 889 |
+
self.spatial_mixer = spatial_mixer
|
| 890 |
+
self.channel_mixer = channel_mixer
|
| 891 |
+
self.baseblock_args = baseblock_args
|
| 892 |
+
|
| 893 |
+
self.spatial_args = spatial_args
|
| 894 |
+
self.channel_args = channel_args
|
| 895 |
+
self.vocab_size = vocab_size
|
| 896 |
+
self.max_len = max_len
|
| 897 |
+
self.d_model = d_model
|
| 898 |
+
self.n_layers = n_layers
|
| 899 |
+
self.unet_skips = unet_skips
|
| 900 |
+
|
| 901 |
+
if self.unet_skips:
|
| 902 |
+
self.skip_weights = nn.Parameter(torch.zeros(self.n_layers // 2), requires_grad=unet_weights)
|
| 903 |
+
if d_modifiers is not None:
|
| 904 |
+
flag("Unet Skips detected, assure d_modifier are symmetrical to make it work!")
|
| 905 |
+
|
| 906 |
+
self.embedder = embedder(vocab_size=vocab_size, d_model=d_model, **embedder_args)
|
| 907 |
+
|
| 908 |
+
self.pos_encoder = pos_encoder(d_model, max_len=max_len) if pos_encoder not in (True, False, None) else \
|
| 909 |
+
(SinusoidalPositionalEncoding(d_model, max_len=max_len) if pos_encoder is True else nn.Identity())
|
| 910 |
+
|
| 911 |
+
self.blocks = nn.ModuleList([
|
| 912 |
+
self.baseblock(
|
| 913 |
+
d_model=int(d_model * d_modifiers[i]),
|
| 914 |
+
channel_mixer=channel_mixer(d_model=int(d_model * d_modifiers[i]), **channel_args),
|
| 915 |
+
spatial_mixer=spatial_mixer(d_model=int(d_model * d_modifiers[i]), **spatial_args),
|
| 916 |
+
norm=norm,
|
| 917 |
+
**baseblock_args
|
| 918 |
+
)
|
| 919 |
+
for i in range(n_layers)
|
| 920 |
+
])
|
| 921 |
+
self._piped_blocks = None
|
| 922 |
+
|
| 923 |
+
self.norm = nn.LayerNorm(d_model)
|
| 924 |
+
self.unembedder = unembedder(**unembedder_args)
|
| 925 |
+
|
| 926 |
+
if weight_tying and hasattr(self.embedder, 'embedding') and hasattr(self.unembedder, 'linear'):
|
| 927 |
+
self.unembedder.linear.weight = self.embedder.embedding.weight
|
| 928 |
+
|
| 929 |
+
elif weight_tying:
|
| 930 |
+
raise TypeError(
|
| 931 |
+
"Automatic weight tying is only possible if the the Embedder has a 'embedding' attribute and Unembedder has a linear attribute"
|
| 932 |
+
"You should manually do weight tying if you aim to use specific layer:"
|
| 933 |
+
"\n e.g. model.unembedder.yourmodule.weight = model.embedder.youembeddings.weight is an auto weight tying example")
|
| 934 |
+
|
| 935 |
+
self.embedder_complexity = embedder.__complexity__ if hasattr(embedder, "__complexity__") else "O(L d)"
|
| 936 |
+
self.unembedder_complexity = unembedder.__complexity__ if hasattr(unembedder, "__complexity__") else "O(L d)"
|
| 937 |
+
self.spatial_complextiy = spatial_mixer.__complexity__ if hasattr(spatial_mixer, "__complexity__") else "O(L d)"
|
| 938 |
+
self.channel_complexity = channel_mixer.__complexity__ if hasattr(channel_mixer, "__complexity__") else "O(L d)"
|
| 939 |
+
self.runned = False
|
| 940 |
+
self.embed_scale = math.sqrt(d_model)
|
| 941 |
+
self._init_weights()
|
| 942 |
+
|
| 943 |
+
def _init_weights(self):
|
| 944 |
+
"""
|
| 945 |
+
Initialize weights following GPT-style conventions:
|
| 946 |
+
- Small std for embeddings
|
| 947 |
+
- Xavier/Kaiming for linear layers
|
| 948 |
+
- Scaled initialization for residual projections
|
| 949 |
+
"""
|
| 950 |
+
std_embed = 0.02
|
| 951 |
+
try:
|
| 952 |
+
nn.init.normal_(self.embedder.embedding.weight, mean=0.0, std=std_embed)
|
| 953 |
+
except:
|
| 954 |
+
pass
|
| 955 |
+
try:
|
| 956 |
+
nn.init.normal_(self.unembedder.weight, mean=0.0, std=std_embed)
|
| 957 |
+
except:
|
| 958 |
+
pass
|
| 959 |
+
|
| 960 |
+
for module in self.modules():
|
| 961 |
+
if isinstance(module, nn.Linear):
|
| 962 |
+
std_init = 0.02
|
| 963 |
+
nn.init.normal_(module.weight, mean=0.0, std=std_init)
|
| 964 |
+
if module.bias is not None:
|
| 965 |
+
nn.init.zeros_(module.bias)
|
| 966 |
+
|
| 967 |
+
elif isinstance(module, nn.LayerNorm):
|
| 968 |
+
nn.init.ones_(module.weight)
|
| 969 |
+
nn.init.zeros_(module.bias)
|
| 970 |
+
|
| 971 |
+
for block_idx, block in enumerate(self.blocks):
|
| 972 |
+
for name, module in block.named_modules():
|
| 973 |
+
if isinstance(module, nn.Linear):
|
| 974 |
+
depth_scale = (2.0 * self.n_layers) ** -0.5
|
| 975 |
+
if 'out' in name.lower() or 'proj' in name.lower() or '.l2' in name or '.g2' in name:
|
| 976 |
+
with torch.no_grad():
|
| 977 |
+
module.weight.data *= depth_scale
|
| 978 |
+
|
| 979 |
+
def forward(self, x: torch.LongTensor, unembed=True):
|
| 980 |
+
# B, L = x.size(0), x.size(1)
|
| 981 |
+
# x = self.embedder(x) * self.embed_scale
|
| 982 |
+
x = self.pos_encoder(x)
|
| 983 |
+
|
| 984 |
+
skips = []
|
| 985 |
+
half_layers = self.n_layers // 2
|
| 986 |
+
|
| 987 |
+
for i, block in enumerate(self.blocks):
|
| 988 |
+
if self.unet_skips:
|
| 989 |
+
if i < half_layers:
|
| 990 |
+
skips.append(x)
|
| 991 |
+
elif i >= self.n_layers - half_layers:
|
| 992 |
+
skip_idx = self.n_layers - 1 - i
|
| 993 |
+
x = x + self.skip_weights[skip_idx] * skips[skip_idx]
|
| 994 |
+
|
| 995 |
+
x = block(x)
|
| 996 |
+
|
| 997 |
+
if unembed:
|
| 998 |
+
x = self.norm(self.unembedder(x))
|
| 999 |
+
|
| 1000 |
+
return x
|
| 1001 |
+
|
| 1002 |
+
def insert_block(self, idx, module):
|
| 1003 |
+
self.blocks.insert(idx, module)
|
| 1004 |
+
|
| 1005 |
+
def append(self, module):
|
| 1006 |
+
self.blocks.append(module)
|
| 1007 |
+
|
| 1008 |
+
@torch.no_grad()
|
| 1009 |
+
def _clear_all_caches(self):
|
| 1010 |
+
"""Clear KV caches in all attention layers"""
|
| 1011 |
+
for block in self.blocks:
|
| 1012 |
+
for module in block.modules():
|
| 1013 |
+
if hasattr(module, 'clear_cache'):
|
| 1014 |
+
module.clear_cache()
|
| 1015 |
+
|
| 1016 |
+
def forward(self, x: torch.LongTensor, unembed=True):
|
| 1017 |
+
B, L = x.size(0), x.size(1)
|
| 1018 |
+
x = self.embedder(x) * self.embed_scale
|
| 1019 |
+
x = self.pos_encoder(x)
|
| 1020 |
+
|
| 1021 |
+
if self.pad == 'sqrt':
|
| 1022 |
+
n = int((x.shape[1] ** 0.5) + 0.999999)
|
| 1023 |
+
x = F.pad(x, (0, 0, 0, n ** 2 - L), mode="constant", value=0)
|
| 1024 |
+
|
| 1025 |
+
for block in self.blocks:
|
| 1026 |
+
x = block(x)
|
| 1027 |
+
|
| 1028 |
+
if unembed:
|
| 1029 |
+
x = self.unembedder(x)
|
| 1030 |
+
|
| 1031 |
+
return x[:, :L, :]
|
| 1032 |
+
|
| 1033 |
+
def _sample_token(self, logits, temperature=1.0, top_p=1.0, top_k=None):
|
| 1034 |
+
logits = logits / (temperature + 1e-8)
|
| 1035 |
+
|
| 1036 |
+
if top_k is not None and top_k > 0:
|
| 1037 |
+
top_k = min(top_k, logits.size(-1))
|
| 1038 |
+
v, _ = torch.topk(logits, top_k)
|
| 1039 |
+
logits[logits < v[:, [-1]]] = float('-inf')
|
| 1040 |
+
|
| 1041 |
+
if top_p < 1.0:
|
| 1042 |
+
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
|
| 1043 |
+
cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)
|
| 1044 |
+
|
| 1045 |
+
sorted_indices_to_remove = cumulative_probs > top_p
|
| 1046 |
+
sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
|
| 1047 |
+
sorted_indices_to_remove[..., 0] = 0
|
| 1048 |
+
|
| 1049 |
+
indices_to_remove = sorted_indices_to_remove.scatter(1, sorted_indices, sorted_indices_to_remove)
|
| 1050 |
+
logits[indices_to_remove] = float('-inf')
|
| 1051 |
+
|
| 1052 |
+
probs = F.softmax(logits, dim=-1)
|
| 1053 |
+
return torch.multinomial(probs, num_samples=1)
|
| 1054 |
+
|
| 1055 |
+
def generate(self, *args, **kwargs):
|
| 1056 |
+
if not 'custom_generate' in kwargs:
|
| 1057 |
+
kwargs['custom_generate'] = False
|
| 1058 |
+
if kwargs['custom_generate']:
|
| 1059 |
+
del kwargs['custom_generate']
|
| 1060 |
+
return self.custom_generate(*args, **kwargs)
|
| 1061 |
+
else:
|
| 1062 |
+
del kwargs['custom_generate']
|
| 1063 |
+
return self.simple_generate(*args, **kwargs)
|
| 1064 |
+
|
| 1065 |
+
@torch.no_grad()
|
| 1066 |
+
def simple_generate(self, prompt: torch.Tensor, max_len=100, temperature=1.0,
|
| 1067 |
+
top_p=1.0, top_k=50, token_end=None, repetition_penalty=1.0):
|
| 1068 |
+
self.eval()
|
| 1069 |
+
if prompt.dim() == 1:
|
| 1070 |
+
prompt = prompt.unsqueeze(0)
|
| 1071 |
+
|
| 1072 |
+
generated = prompt.clone()
|
| 1073 |
+
pbar = tqdm(range(max_len), desc="Simple Gen")
|
| 1074 |
+
|
| 1075 |
+
for _ in pbar:
|
| 1076 |
+
logits = self.forward(generated, unembed=True)
|
| 1077 |
+
next_token_logits = logits[:, -1, :]
|
| 1078 |
+
|
| 1079 |
+
# Apply repetition penalty
|
| 1080 |
+
if repetition_penalty != 1.0:
|
| 1081 |
+
next_token_logits = self._apply_repetition_penalty(
|
| 1082 |
+
next_token_logits, generated, repetition_penalty
|
| 1083 |
+
)
|
| 1084 |
+
|
| 1085 |
+
next_token = self._sample_token(next_token_logits, temperature, top_p, top_k)
|
| 1086 |
+
generated = torch.cat([generated, next_token], dim=1)
|
| 1087 |
+
|
| 1088 |
+
if token_end is not None and (next_token == token_end).all():
|
| 1089 |
+
break
|
| 1090 |
+
|
| 1091 |
+
return generated
|
| 1092 |
+
|
| 1093 |
+
@torch.no_grad()
|
| 1094 |
+
def custom_generate(self, prompt: torch.Tensor, max_len=100, temperature=1.0,
|
| 1095 |
+
top_p=1.0, top_k=50, token_end=None, repetition_penalty=1.0):
|
| 1096 |
+
self.eval()
|
| 1097 |
+
self._clear_all_caches()
|
| 1098 |
+
|
| 1099 |
+
if prompt.dim() == 1:
|
| 1100 |
+
prompt = prompt.unsqueeze(0)
|
| 1101 |
+
|
| 1102 |
+
x = self.embedder(prompt) * self.embed_scale
|
| 1103 |
+
if self.pos_encoder is not None and not isinstance(self.pos_encoder, nn.Identity):
|
| 1104 |
+
x = self.pos_encoder(x)
|
| 1105 |
+
|
| 1106 |
+
for block in self.blocks:
|
| 1107 |
+
if block.has_custom_generate():
|
| 1108 |
+
x = block.generate(x)
|
| 1109 |
+
else:
|
| 1110 |
+
x = block(x)
|
| 1111 |
+
|
| 1112 |
+
generated = prompt.clone()
|
| 1113 |
+
logits = self.unembedder(x)
|
| 1114 |
+
next_token_logits = logits[:, -1, :]
|
| 1115 |
+
|
| 1116 |
+
# Apply repetition penalty
|
| 1117 |
+
if repetition_penalty != 1.0:
|
| 1118 |
+
next_token_logits = self._apply_repetition_penalty(
|
| 1119 |
+
next_token_logits, generated, repetition_penalty
|
| 1120 |
+
)
|
| 1121 |
+
|
| 1122 |
+
next_token = self._sample_token(next_token_logits, temperature, top_p, top_k)
|
| 1123 |
+
generated = torch.cat([generated, next_token], dim=1)
|
| 1124 |
+
|
| 1125 |
+
pbar = tqdm(range(max_len - 1), desc="Fast Gen")
|
| 1126 |
+
for _ in pbar:
|
| 1127 |
+
x_t = self.embedder(next_token) * self.embed_scale
|
| 1128 |
+
current_pos = generated.shape[1] - 1
|
| 1129 |
+
|
| 1130 |
+
if isinstance(self.pos_encoder, SinusoidalPositionalEncoding):
|
| 1131 |
+
pe_slice = self.pos_encoder.pe[current_pos: current_pos + 1].unsqueeze(0)
|
| 1132 |
+
x_t = x_t + pe_slice
|
| 1133 |
+
|
| 1134 |
+
for block in self.blocks:
|
| 1135 |
+
if block.has_custom_generate():
|
| 1136 |
+
x_t = block.generate(x_t)
|
| 1137 |
+
else:
|
| 1138 |
+
x_t = block(x_t)
|
| 1139 |
+
|
| 1140 |
+
logits = self.unembedder(x_t)
|
| 1141 |
+
next_token_logits = logits[:, -1, :]
|
| 1142 |
+
|
| 1143 |
+
# Apply repetition penalty
|
| 1144 |
+
if repetition_penalty != 1.0:
|
| 1145 |
+
next_token_logits = self._apply_repetition_penalty(
|
| 1146 |
+
next_token_logits, generated, repetition_penalty
|
| 1147 |
+
)
|
| 1148 |
+
|
| 1149 |
+
next_token = self._sample_token(next_token_logits, temperature, top_p, top_k)
|
| 1150 |
+
generated = torch.cat([generated, next_token], dim=1)
|
| 1151 |
+
|
| 1152 |
+
if token_end is not None and (next_token == token_end).all():
|
| 1153 |
+
break
|
| 1154 |
+
|
| 1155 |
+
self._clear_all_caches()
|
| 1156 |
+
return generated
|
| 1157 |
+
|
| 1158 |
+
def _apply_repetition_penalty(self, logits: torch.Tensor,
|
| 1159 |
+
generated: torch.Tensor,
|
| 1160 |
+
repetition_penalty: float) -> torch.Tensor:
|
| 1161 |
+
"""
|
| 1162 |
+
Apply repetition penalty to logits based on previously generated tokens.
|
| 1163 |
+
|
| 1164 |
+
Args:
|
| 1165 |
+
logits: Shape (batch_size, vocab_size)
|
| 1166 |
+
generated: Shape (batch_size, seq_len) - previously generated tokens
|
| 1167 |
+
repetition_penalty: Penalty factor (> 1.0 discourages repetition)
|
| 1168 |
+
|
| 1169 |
+
Returns:
|
| 1170 |
+
Modified logits with repetition penalty applied
|
| 1171 |
+
"""
|
| 1172 |
+
batch_size = logits.shape[0]
|
| 1173 |
+
|
| 1174 |
+
for i in range(batch_size):
|
| 1175 |
+
# Get unique tokens in the generated sequence for this batch item
|
| 1176 |
+
unique_tokens = generated[i].unique()
|
| 1177 |
+
|
| 1178 |
+
# Apply penalty: divide logits by penalty if positive, multiply if negative
|
| 1179 |
+
for token_id in unique_tokens:
|
| 1180 |
+
if logits[i, token_id] > 0:
|
| 1181 |
+
logits[i, token_id] /= repetition_penalty
|
| 1182 |
+
else:
|
| 1183 |
+
logits[i, token_id] *= repetition_penalty
|
| 1184 |
+
|
| 1185 |
+
return logits
|
| 1186 |
+
|
| 1187 |
+
def summary(self):
|
| 1188 |
+
complexities = [self.channel_complexity, self.spatial_complextiy, self.embedder_complexity,
|
| 1189 |
+
self.unembedder_complexity]
|
| 1190 |
+
print(f'{NUM}VATHOS{RES} {self.name} Summary:')
|
| 1191 |
+
print(f"{NUM}SequenceModel{RES}(d_model={NUM}{self.d_model}{RES}, n_layer={NUM}{self.n_layers}{RES})")
|
| 1192 |
+
print(f"\t - {NUM}VOCAB_SIZE:{RES}: {NUM}{self.vocab_size}{RES}")
|
| 1193 |
+
print(f"\t - {NUM}D_MODEL:{RES}: {NUM}{self.d_model}{RES}")
|
| 1194 |
+
print(f"\t - {NUM}N_LAYERS:{RES}: {NUM}{self.n_layers}{RES}")
|
| 1195 |
+
print("")
|
| 1196 |
+
print(f"\t - {NUM}Embedder{RES}: {getname(self.embedder)} - {NUM}{self.embedder_complexity}{RES}")
|
| 1197 |
+
print(f"\t - {NUM}Unembedder{RES}: {getname(self.unembedder)} - {NUM}{self.unembedder_complexity}{RES}")
|
| 1198 |
+
print(
|
| 1199 |
+
f"\t - {NUM}Spatial Mixer{RES}: {getname(self.spatial_mixer)}({self.spatial_args}) - {NUM}{self.spatial_complextiy}{RES}")
|
| 1200 |
+
print(
|
| 1201 |
+
f"\t - {NUM}Channel Mixer{RES}: {getname(self.channel_mixer)}({self.channel_args}) - {NUM}{self.channel_complexity}{RES}")
|
| 1202 |
+
print(f"Num Parameters: {NUM}{sum([p.numel() for p in self.parameters()]):_}{RES}")
|
| 1203 |
+
print(f"Num Trainable Parameters: {NUM}{sum([p.numel() for p in self.parameters() if p.requires_grad]):_}{RES}")
|
| 1204 |
+
print(f"Total Complexity: {NUM}{combine_big_o_sum(complexities)}{RES}")
|
| 1205 |
+
|
| 1206 |
+
def finetune(self):
|
| 1207 |
+
flag(
|
| 1208 |
+
"Finetune simply checks for finetune() methods in spatial mixers, channel mixers, embedder and unembedder, "
|
| 1209 |
+
"if a finetune method is not available, the module/Layer will be left as it is")
|
| 1210 |
+
super().finetune()
|
| 1211 |
+
|
| 1212 |
+
if hasattr(self.embedder, "finetune"):
|
| 1213 |
+
self.embedder.finetune()
|
| 1214 |
+
if hasattr(self.unembedder, "finetune"):
|
| 1215 |
+
self.unembedder.finetune()
|
| 1216 |
+
for block in self.blocks:
|
| 1217 |
+
if hasattr(block, "finetune"):
|
| 1218 |
+
block.channel_mixer.finetune()
|
| 1219 |
+
block.spatial_mixer.finetune()
|
| 1220 |
+
|
| 1221 |
+
|
| 1222 |
+
class ModdedFormer(VathosModel):
|
| 1223 |
+
def __init__(self, vocab_size: int, embed_dim: int, d_models: List[int], spatials: List[Builder], expand=3,
|
| 1224 |
+
M_dims: List[int] | None = None, weights_tying=True,
|
| 1225 |
+
baseblock=Block1d, norm=RMSNorm, ffn_act=ReLU2, unet_skips=False, max_len=2400, zeroskip=False,
|
| 1226 |
+
UDLP=VariableUDLP,
|
| 1227 |
+
skips: List[int | None] | None = None, value_embeddings: List[int | None] | None = None,
|
| 1228 |
+
ve_type: str = 'scalar', ve_gate_dim: int | None = None,
|
| 1229 |
+
learnable_pe: bool = False, input_projections: bool | int=False, smear_gate: None | nn.Module | Layer=False):
|
| 1230 |
+
super().__init__()
|
| 1231 |
+
self.vocab_size = vocab_size
|
| 1232 |
+
self.embed_dim = embed_dim
|
| 1233 |
+
assert d_models[0] == embed_dim
|
| 1234 |
+
assert max(d_models) == min(d_models), "Variables d_models is WIP and not working"
|
| 1235 |
+
self.d_models = d_models
|
| 1236 |
+
n_layers = self.n_layer = len(d_models)
|
| 1237 |
+
self.input_projections = input_projections
|
| 1238 |
+
self.smear_gate = smear_gate
|
| 1239 |
+
if M_dims is not None:
|
| 1240 |
+
self.M_dims = M_dims
|
| 1241 |
+
else:
|
| 1242 |
+
self.M_dims = [d * expand for d in d_models]
|
| 1243 |
+
|
| 1244 |
+
self.embedder = Embedder(vocab_size, embed_dim)
|
| 1245 |
+
self.unembedder = UnbiasedLinear(d_models[-1], vocab_size)
|
| 1246 |
+
self.spatials = spatials
|
| 1247 |
+
self.norm = norm
|
| 1248 |
+
self.unet = unet_skips
|
| 1249 |
+
self.max_len = max_len
|
| 1250 |
+
self.zeroskip = zeroskip
|
| 1251 |
+
|
| 1252 |
+
self.skips = skips if skips is not None else [None] * n_layers
|
| 1253 |
+
assert len(self.skips) == n_layers
|
| 1254 |
+
|
| 1255 |
+
self.skip_lambdas = nn.ParameterDict()
|
| 1256 |
+
for source_idx, target_idx in enumerate(self.skips):
|
| 1257 |
+
if target_idx is not None:
|
| 1258 |
+
assert target_idx > source_idx
|
| 1259 |
+
assert target_idx < n_layers
|
| 1260 |
+
self.skip_lambdas[f"route_{source_idx}_to_{target_idx}"] = nn.Parameter(torch.zeros(1))
|
| 1261 |
+
|
| 1262 |
+
if weights_tying:
|
| 1263 |
+
assert d_models[-1] == embed_dim
|
| 1264 |
+
self.unembedder.linear.weight = self.embedder.embedding.weight
|
| 1265 |
+
|
| 1266 |
+
blocks = []
|
| 1267 |
+
for i in range(n_layers):
|
| 1268 |
+
in_dim = embed_dim if i == 0 else self.d_models[i - 1]
|
| 1269 |
+
out_dim = self.d_models[i]
|
| 1270 |
+
blocks.append(
|
| 1271 |
+
baseblock(
|
| 1272 |
+
self.d_models[i],
|
| 1273 |
+
UDLP(in_dim, d_output=out_dim, M=self.M_dims[i], activation=ffn_act),
|
| 1274 |
+
self.spatials[i](self.d_models[i]),
|
| 1275 |
+
norm=norm
|
| 1276 |
+
))
|
| 1277 |
+
|
| 1278 |
+
self.blocks = nn.ModuleList(blocks)
|
| 1279 |
+
|
| 1280 |
+
if zeroskip:
|
| 1281 |
+
self.zeroskip_params = nn.ParameterList([nn.Parameter(torch.tensor([0.0])) for _ in range(n_layers)])
|
| 1282 |
+
if input_projections > 0:
|
| 1283 |
+
if not zeroskip:
|
| 1284 |
+
raise ValueError("Zeroskip must be activated when using input_projections")
|
| 1285 |
+
self.inputs_projection_linears = nn.Linear(in_features=d_models[0], out_features=d_models[0]*input_projections, bias=False)
|
| 1286 |
+
self.inputs_projection_params = nn.ParameterList([nn.Parameter(torch.tensor([0.0])) for _ in range(n_layers) for i in range(input_projections)])
|
| 1287 |
+
|
| 1288 |
+
|
| 1289 |
+
assert ve_type in ('scalar', 'gate'), f"ve_type must be 'scalar' or 'gate', got {ve_type}"
|
| 1290 |
+
self.ve_type = ve_type
|
| 1291 |
+
self.ve_gate_dim = ve_gate_dim if ve_gate_dim is not None else embed_dim
|
| 1292 |
+
self.value_embeddings_cfg = value_embeddings
|
| 1293 |
+
|
| 1294 |
+
# Value embeddings: ModuleDict/ParameterDict come prima (per leggibilità + checkpoint legacy),
|
| 1295 |
+
# MA in più costruiamo strutture INDEXED-BY-LAYER per il forward (no str() runtime, no dict
|
| 1296 |
+
# lookup → compile-friendly).
|
| 1297 |
+
if value_embeddings is not None:
|
| 1298 |
+
assert len(value_embeddings) == n_layers, "value_embeddings length must equal n_layers"
|
| 1299 |
+
unique_groups = sorted(set(v for v in value_embeddings if v is not None))
|
| 1300 |
+
self.ve_embeddings = nn.ModuleDict({
|
| 1301 |
+
str(g): nn.Embedding(vocab_size, embed_dim)
|
| 1302 |
+
for g in unique_groups
|
| 1303 |
+
})
|
| 1304 |
+
if ve_type == 'scalar':
|
| 1305 |
+
self.ve_scales = nn.ParameterDict({
|
| 1306 |
+
str(i): nn.Parameter(torch.zeros(1))
|
| 1307 |
+
for i, v in enumerate(value_embeddings) if v is not None
|
| 1308 |
+
})
|
| 1309 |
+
else: # gate
|
| 1310 |
+
self.ve_gates = nn.ModuleDict({
|
| 1311 |
+
str(i): nn.Linear(self.ve_gate_dim, embed_dim, bias=False)
|
| 1312 |
+
for i, v in enumerate(value_embeddings) if v is not None
|
| 1313 |
+
})
|
| 1314 |
+
|
| 1315 |
+
# Strutture parallele indexed-by-layer per il forward (riferiscono gli stessi param).
|
| 1316 |
+
self._ve_per_layer = nn.ModuleList([
|
| 1317 |
+
self.ve_embeddings[str(v)] if v is not None else nn.Identity()
|
| 1318 |
+
for v in value_embeddings
|
| 1319 |
+
])
|
| 1320 |
+
self._has_ve_per_layer = tuple(v is not None for v in value_embeddings)
|
| 1321 |
+
self._has_any_ve = any(self._has_ve_per_layer)
|
| 1322 |
+
|
| 1323 |
+
if ve_type == 'scalar':
|
| 1324 |
+
self._ve_scale_per_layer = nn.ParameterList([
|
| 1325 |
+
self.ve_scales[str(i)] if value_embeddings[i] is not None
|
| 1326 |
+
else nn.Parameter(torch.zeros(1), requires_grad=False)
|
| 1327 |
+
for i in range(n_layers)
|
| 1328 |
+
])
|
| 1329 |
+
self._ve_gate_per_layer = nn.ModuleList([nn.Identity() for _ in range(n_layers)])
|
| 1330 |
+
else:
|
| 1331 |
+
self._ve_scale_per_layer = nn.ParameterList([
|
| 1332 |
+
nn.Parameter(torch.zeros(1), requires_grad=False) for _ in range(n_layers)
|
| 1333 |
+
])
|
| 1334 |
+
self._ve_gate_per_layer = nn.ModuleList([
|
| 1335 |
+
self.ve_gates[str(i)] if value_embeddings[i] is not None else nn.Identity()
|
| 1336 |
+
for i in range(n_layers)
|
| 1337 |
+
])
|
| 1338 |
+
else:
|
| 1339 |
+
self.ve_embeddings = nn.ModuleDict()
|
| 1340 |
+
self._has_ve_per_layer = tuple([False] * n_layers)
|
| 1341 |
+
self._has_any_ve = False
|
| 1342 |
+
self._ve_per_layer = nn.ModuleList([nn.Identity() for _ in range(n_layers)])
|
| 1343 |
+
self._ve_scale_per_layer = nn.ParameterList()
|
| 1344 |
+
self._ve_gate_per_layer = nn.ModuleList()
|
| 1345 |
+
|
| 1346 |
+
# ---------------------------------------------------------------------
|
| 1347 |
+
# Skip routing: pre-computed plan + ParameterList (no string keys, no dict mutato)
|
| 1348 |
+
# ---------------------------------------------------------------------
|
| 1349 |
+
# Per ogni layer i:
|
| 1350 |
+
# - _skip_save[i]: bool, se salvare l'output come sorgente di skip
|
| 1351 |
+
# - _skip_consume_at[i]: tuple di (source_idx, gate_idx_in_list) da consumare qui
|
| 1352 |
+
self._skip_save = tuple(s is not None for s in self.skips)
|
| 1353 |
+
consume_lists = [[] for _ in range(n_layers)]
|
| 1354 |
+
gate_param_count = 0
|
| 1355 |
+
# Mapping (source, target) -> gate index nella ParameterList
|
| 1356 |
+
self._skip_gate_map = {}
|
| 1357 |
+
for source_idx, target_idx in enumerate(self.skips):
|
| 1358 |
+
if target_idx is not None:
|
| 1359 |
+
self._skip_gate_map[(source_idx, target_idx)] = gate_param_count
|
| 1360 |
+
consume_lists[target_idx].append((source_idx, gate_param_count))
|
| 1361 |
+
gate_param_count += 1
|
| 1362 |
+
self._skip_consume_at = tuple(tuple(lst) for lst in consume_lists)
|
| 1363 |
+
self._has_any_skip = gate_param_count > 0
|
| 1364 |
+
# ParameterList parallela: stessi tensori della ParameterDict legacy, indicizzati posizionalmente.
|
| 1365 |
+
if self._has_any_skip:
|
| 1366 |
+
self._skip_gates_list = nn.ParameterList([
|
| 1367 |
+
self.skip_lambdas[f"route_{s}_to_{t}"]
|
| 1368 |
+
for (s, t), _ in sorted(self._skip_gate_map.items(), key=lambda kv: kv[1])
|
| 1369 |
+
])
|
| 1370 |
+
else:
|
| 1371 |
+
self._skip_gates_list = nn.ParameterList()
|
| 1372 |
+
|
| 1373 |
+
self.learnable_pe = learnable_pe
|
| 1374 |
+
if self.learnable_pe:
|
| 1375 |
+
self.pos_emb = nn.Parameter(torch.zeros(1, max_len, embed_dim))
|
| 1376 |
+
|
| 1377 |
+
self.final_norm = RMSNorm(d_models[-1])
|
| 1378 |
+
self._init_weights()
|
| 1379 |
+
|
| 1380 |
+
# Flag globale fast-path: nessuna feature opzionale → loop pulito senza branch.
|
| 1381 |
+
self._has_any_zeroskip = bool(zeroskip)
|
| 1382 |
+
self._has_input_projections = int(input_projections) > 0
|
| 1383 |
+
self._has_smear_gate = bool(smear_gate)
|
| 1384 |
+
self._has_learnable_pe = bool(learnable_pe)
|
| 1385 |
+
self._fast_path = not any([
|
| 1386 |
+
self._has_any_ve, self._has_any_skip,
|
| 1387 |
+
self._has_any_zeroskip, self._has_input_projections,
|
| 1388 |
+
self._has_smear_gate, self._has_learnable_pe,
|
| 1389 |
+
])
|
| 1390 |
+
|
| 1391 |
+
def _apply_ve_weight(self, ve: torch.Tensor, x: torch.Tensor, layer_idx: int) -> torch.Tensor:
|
| 1392 |
+
"""Legacy helper (mantenuto per backward compat). Il forward usa la versione indicizzata."""
|
| 1393 |
+
if self.ve_type == 'scalar':
|
| 1394 |
+
return ve * self.ve_scales[str(layer_idx)]
|
| 1395 |
+
else:
|
| 1396 |
+
gate = 2.0 * torch.sigmoid(self.ve_gates[str(layer_idx)](x[..., :self.ve_gate_dim]))
|
| 1397 |
+
return ve * gate
|
| 1398 |
+
|
| 1399 |
+
def _compute_ve_indexed(self, input_ids: torch.Tensor, x: torch.Tensor, i: int):
|
| 1400 |
+
"""Versione fast: ModuleList[i] invece di ModuleDict[str(group_id)]."""
|
| 1401 |
+
ve = self._ve_per_layer[i](input_ids)
|
| 1402 |
+
if self.ve_type == 'scalar':
|
| 1403 |
+
return ve * self._ve_scale_per_layer[i]
|
| 1404 |
+
# gate type
|
| 1405 |
+
return ve * (2.0 * torch.sigmoid(self._ve_gate_per_layer[i](x[..., :self.ve_gate_dim])))
|
| 1406 |
+
|
| 1407 |
+
# -------------------------------------------------------------------------
|
| 1408 |
+
# Forward — fast path se niente feature opzionali, slow path altrimenti
|
| 1409 |
+
# -------------------------------------------------------------------------
|
| 1410 |
+
|
| 1411 |
+
def forward(self, x):
|
| 1412 |
+
# FAST PATH: standard transformer pretrain, no skip / no VE / no zeroskip / no PE / no smear.
|
| 1413 |
+
# È il caso di gran lunga più comune (es. PiCO 2 / Guido-1 baseline).
|
| 1414 |
+
if self._fast_path:
|
| 1415 |
+
x = self.embedder(x)
|
| 1416 |
+
for block in self.blocks:
|
| 1417 |
+
x = block(x)
|
| 1418 |
+
return self.unembedder(self.final_norm(x))
|
| 1419 |
+
|
| 1420 |
+
# SLOW PATH: include tutte le feature opzionali. Anche qui niente string keys / dict mutati:
|
| 1421 |
+
# le strutture sono pre-computate al __init__ e indexed-by-layer.
|
| 1422 |
+
input_ids = x # alias per VE lookup
|
| 1423 |
+
|
| 1424 |
+
x0 = self.embedder(x)
|
| 1425 |
+
if self._has_input_projections:
|
| 1426 |
+
xs = self.inputs_projection_linears(x0).chunk(self.input_projections, dim=-1)
|
| 1427 |
+
if self._has_learnable_pe:
|
| 1428 |
+
x0 = x0 + self.pos_emb[:, :x.size(1), :]
|
| 1429 |
+
if self._has_smear_gate:
|
| 1430 |
+
x0 = self.smear_gate(x0)
|
| 1431 |
+
|
| 1432 |
+
x = x0
|
| 1433 |
+
# Buffer di skip: lista Python di lunghezza fissa = n_layers. Allocata solo se serve.
|
| 1434 |
+
skip_buffer = [None] * self.n_layer if self._has_any_skip else None
|
| 1435 |
+
|
| 1436 |
+
for i, block in enumerate(self.blocks):
|
| 1437 |
+
# VE — indexed lookup (no string)
|
| 1438 |
+
ve = None
|
| 1439 |
+
if self._has_ve_per_layer[i]:
|
| 1440 |
+
ve = self._compute_ve_indexed(input_ids, x, i)
|
| 1441 |
+
|
| 1442 |
+
# Block forward + zeroskip variants
|
| 1443 |
+
if self._has_any_zeroskip and self._has_input_projections:
|
| 1444 |
+
proj_params = self.inputs_projection_params[
|
| 1445 |
+
i * self.input_projections: (i + 1) * self.input_projections
|
| 1446 |
+
]
|
| 1447 |
+
x = block(x, ve=ve) + x0 * self.zeroskip_params[i] + sum(
|
| 1448 |
+
[xj * p for xj, p in zip(xs, proj_params)]
|
| 1449 |
+
)
|
| 1450 |
+
elif self._has_any_zeroskip:
|
| 1451 |
+
x = block(x, ve=ve) + x0 * self.zeroskip_params[i]
|
| 1452 |
+
else:
|
| 1453 |
+
x = block(x, ve=ve)
|
| 1454 |
+
|
| 1455 |
+
# Skip routing — pre-computed plan, indexed gates (no dict, no string)
|
| 1456 |
+
if self._has_any_skip:
|
| 1457 |
+
consume = self._skip_consume_at[i]
|
| 1458 |
+
for source_idx, gate_idx in consume:
|
| 1459 |
+
x = x + self._skip_gates_list[gate_idx] * skip_buffer[source_idx]
|
| 1460 |
+
if self._skip_save[i]:
|
| 1461 |
+
skip_buffer[i] = x
|
| 1462 |
+
|
| 1463 |
+
return self.unembedder(self.final_norm(x))
|
| 1464 |
+
|
| 1465 |
+
@torch.no_grad()
|
| 1466 |
+
def _clear_all_caches(self):
|
| 1467 |
+
for block in self.blocks:
|
| 1468 |
+
for module in block.modules():
|
| 1469 |
+
if hasattr(module, 'clear_cache'):
|
| 1470 |
+
module.clear_cache()
|
| 1471 |
+
|
| 1472 |
+
def _embed_input(self, ids: torch.Tensor, pos_start: int) -> torch.Tensor:
|
| 1473 |
+
x0 = self.embedder(ids)
|
| 1474 |
+
if self.learnable_pe:
|
| 1475 |
+
L = ids.size(1)
|
| 1476 |
+
x0 = x0 + self.pos_emb[:, pos_start:pos_start + L, :]
|
| 1477 |
+
if self.smear_gate:
|
| 1478 |
+
x0 = self.smear_gate(x0)
|
| 1479 |
+
return x0
|
| 1480 |
+
|
| 1481 |
+
def _blocks_generate(self, x: torch.Tensor, x0: torch.Tensor,
|
| 1482 |
+
xs, input_ids: torch.Tensor) -> torch.Tensor:
|
| 1483 |
+
"""
|
| 1484 |
+
Mirror of forward()'s block loop, but calls block.generate(...) so
|
| 1485 |
+
that attention mixers populate / consume their KV cache.
|
| 1486 |
+
Works for both prefill (L>1) and single-token increments (L=1).
|
| 1487 |
+
"""
|
| 1488 |
+
active_skips = {}
|
| 1489 |
+
for i, block in enumerate(self.blocks):
|
| 1490 |
+
ve = None
|
| 1491 |
+
if self.value_embeddings_cfg is not None and self.value_embeddings_cfg[i] is not None:
|
| 1492 |
+
group_id = self.value_embeddings_cfg[i]
|
| 1493 |
+
ve = self.ve_embeddings[str(group_id)](input_ids)
|
| 1494 |
+
ve = self._apply_ve_weight(ve, x, i)
|
| 1495 |
+
|
| 1496 |
+
if self.zeroskip and self.input_projections > 0:
|
| 1497 |
+
proj_params = self.inputs_projection_params[
|
| 1498 |
+
i * self.input_projections: (i + 1) * self.input_projections
|
| 1499 |
+
]
|
| 1500 |
+
x = block.generate(x, ve=ve) + x0 * self.zeroskip_params[i] + sum(
|
| 1501 |
+
[xj * p for xj, p in zip(xs, proj_params)]
|
| 1502 |
+
)
|
| 1503 |
+
elif self.zeroskip:
|
| 1504 |
+
x = block.generate(x, ve=ve) + x0 * self.zeroskip_params[i]
|
| 1505 |
+
else:
|
| 1506 |
+
x = block.generate(x, ve=ve)
|
| 1507 |
+
|
| 1508 |
+
for source_idx, target_idx in enumerate(self.skips):
|
| 1509 |
+
if target_idx == i:
|
| 1510 |
+
gate = self.skip_lambdas[f"route_{source_idx}_to_{target_idx}"]
|
| 1511 |
+
x = x + gate * active_skips[source_idx]
|
| 1512 |
+
del active_skips[source_idx]
|
| 1513 |
+
|
| 1514 |
+
if self.skips[i] is not None:
|
| 1515 |
+
active_skips[i] = x
|
| 1516 |
+
return x
|
| 1517 |
+
|
| 1518 |
+
@torch.no_grad()
|
| 1519 |
+
def generate(self, prompt: torch.Tensor, max_len: int = 100,
|
| 1520 |
+
temperature: float = 1.0, top_k: int | None = None,
|
| 1521 |
+
top_p: float = 1.0, repetition_penalty: float = 1.0,
|
| 1522 |
+
token_end=None, simple: bool = False):
|
| 1523 |
+
"""
|
| 1524 |
+
KV-cached AR generation. Prefills the prompt in one shot (every spatial
|
| 1525 |
+
mixer's .generate accepts L>1 and populates its kv_cache), then walks
|
| 1526 |
+
token-by-token reusing the cached keys/values.
|
| 1527 |
+
|
| 1528 |
+
simple=True falls back to simple_ar_generate (O(L^2)) — useful for
|
| 1529 |
+
regression-checking the cached path.
|
| 1530 |
+
"""
|
| 1531 |
+
if simple:
|
| 1532 |
+
return simple_ar_generate(
|
| 1533 |
+
self, prompt, max_len=max_len, temperature=temperature,
|
| 1534 |
+
top_k=top_k, top_p=top_p, repetition_penalty=repetition_penalty,
|
| 1535 |
+
token_end=token_end,
|
| 1536 |
+
)
|
| 1537 |
+
|
| 1538 |
+
self.eval()
|
| 1539 |
+
self._clear_all_caches()
|
| 1540 |
+
|
| 1541 |
+
if self.value_embeddings_cfg is not None:
|
| 1542 |
+
flag("ModdedFormer.generate: value_embeddings only work with spatial mixers "
|
| 1543 |
+
"whose .generate accepts a 've=' kwarg (e.g. MultiheadAttentionMixer). "
|
| 1544 |
+
"On other mixers ve is silently dropped during generation.", 2)
|
| 1545 |
+
|
| 1546 |
+
if prompt.dim() == 1:
|
| 1547 |
+
prompt = prompt.unsqueeze(0)
|
| 1548 |
+
device = next(self.parameters()).device
|
| 1549 |
+
prompt = prompt.to(device)
|
| 1550 |
+
|
| 1551 |
+
L = prompt.size(1)
|
| 1552 |
+
x0 = self._embed_input(prompt, pos_start=0)
|
| 1553 |
+
xs = (self.inputs_projection_linears(x0).chunk(self.input_projections, dim=-1)
|
| 1554 |
+
if self.input_projections > 0 else None)
|
| 1555 |
+
|
| 1556 |
+
x = self._blocks_generate(x0, x0, xs, prompt)
|
| 1557 |
+
logits = self.unembedder(self.final_norm(x))[:, -1, :]
|
| 1558 |
+
|
| 1559 |
+
if repetition_penalty != 1.0:
|
| 1560 |
+
logits = apply_repetition_penalty(logits, prompt, repetition_penalty)
|
| 1561 |
+
next_token = sample_next_token(logits, temperature, top_k, top_p)
|
| 1562 |
+
generated = torch.cat([prompt, next_token], dim=1)
|
| 1563 |
+
|
| 1564 |
+
pos = L
|
| 1565 |
+
pbar = tqdm(range(max_len - 1), desc="ModdedFormer fast gen")
|
| 1566 |
+
for _ in pbar:
|
| 1567 |
+
x0_t = self._embed_input(next_token, pos_start=pos)
|
| 1568 |
+
xs_t = (self.inputs_projection_linears(x0_t).chunk(self.input_projections, dim=-1)
|
| 1569 |
+
if self.input_projections > 0 else None)
|
| 1570 |
+
|
| 1571 |
+
x_t = self._blocks_generate(x0_t, x0_t, xs_t, next_token)
|
| 1572 |
+
logits = self.unembedder(self.final_norm(x_t))[:, -1, :]
|
| 1573 |
+
|
| 1574 |
+
if repetition_penalty != 1.0:
|
| 1575 |
+
logits = apply_repetition_penalty(logits, generated, repetition_penalty)
|
| 1576 |
+
next_token = sample_next_token(logits, temperature, top_k, top_p)
|
| 1577 |
+
generated = torch.cat([generated, next_token], dim=1)
|
| 1578 |
+
pos += 1
|
| 1579 |
+
|
| 1580 |
+
if token_end is not None and (next_token == token_end).all():
|
| 1581 |
+
break
|
| 1582 |
+
|
| 1583 |
+
self._clear_all_caches()
|
| 1584 |
+
return generated
|
| 1585 |
+
|
| 1586 |
+
def _init_weights(self):
|
| 1587 |
+
std_embed = 0.02
|
| 1588 |
+
try:
|
| 1589 |
+
nn.init.normal_(self.embedder.embedding.weight, mean=0.0, std=std_embed)
|
| 1590 |
+
except:
|
| 1591 |
+
pass
|
| 1592 |
+
try:
|
| 1593 |
+
nn.init.normal_(self.unembedder.weight, mean=0.0, std=std_embed)
|
| 1594 |
+
except:
|
| 1595 |
+
pass
|
| 1596 |
+
|
| 1597 |
+
if self.learnable_pe:
|
| 1598 |
+
nn.init.normal_(self.pos_emb, mean=0.0, std=std_embed)
|
| 1599 |
+
|
| 1600 |
+
for block_idx, block in enumerate(self.blocks):
|
| 1601 |
+
block.channel_mixer._init_weights()
|
| 1602 |
+
try:
|
| 1603 |
+
block.spatial_mixer._init_weights()
|
| 1604 |
+
except:
|
| 1605 |
+
pass
|
| 1606 |
+
|
| 1607 |
+
def summary(self):
|
| 1608 |
+
print(f"Vathos {NUM}ModdedFormer{RES} Summary")
|
| 1609 |
+
print(f"Embedding dim: {self.embed_dim}")
|
| 1610 |
+
print(f"Learnable PE: {self.learnable_pe}")
|
| 1611 |
+
print(f"Skips: {self.skips}")
|
| 1612 |
+
for i in range(self.n_layer):
|
| 1613 |
+
if i == 0:
|
| 1614 |
+
in_dim = self.embed_dim
|
| 1615 |
+
else:
|
| 1616 |
+
in_dim = self.d_models[i - 1]
|
| 1617 |
+
out_dim = self.d_models[i]
|
| 1618 |
+
M = self.M_dims[i]
|
| 1619 |
+
print(f"Layer {i}: {in_dim} -> {out_dim}")
|
| 1620 |
+
print(f"Block: {i}: {self.blocks[i]}")
|
| 1621 |
+
print(f"\tFFN Dimension: {M}")
|
| 1622 |
+
print(f"\tFFN Activation: {self.blocks[i].channel_mixer.activation}")
|
| 1623 |
+
|
| 1624 |
+
print(f"Num Parameters: {NUM}{sum([p.numel() for p in self.parameters()]):_}{RES}")
|
| 1625 |
+
print(f"Num Trainable Parameters: {NUM}{sum([p.numel() for p in self.parameters() if p.requires_grad]):_}{RES}")
|
| 1626 |
+
|
| 1627 |
+
|
| 1628 |
+
#######################################################################################################################
|
| 1629 |
+
|
| 1630 |
+
"""def test_causality(module=MTransformer(8, 4, 2)):
|
| 1631 |
+
torch.manual_seed(42)
|
| 1632 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 1633 |
+
|
| 1634 |
+
model = module
|
| 1635 |
+
x = torch.randn(2, 20, model.d_model, device=device)
|
| 1636 |
+
|
| 1637 |
+
print(f"\nInput shape: {x.shape}")
|
| 1638 |
+
|
| 1639 |
+
with torch.no_grad():
|
| 1640 |
+
output = model(x)
|
| 1641 |
+
|
| 1642 |
+
model.eval()
|
| 1643 |
+
with torch.no_grad():
|
| 1644 |
+
full_output = model(x)
|
| 1645 |
+
|
| 1646 |
+
for t in range(1, 20):
|
| 1647 |
+
prefix_output = model(x[:, :t, :])
|
| 1648 |
+
|
| 1649 |
+
max_diff = (full_output[:, :t, :] - prefix_output).abs().max().item()
|
| 1650 |
+
|
| 1651 |
+
if max_diff > 1e-5:
|
| 1652 |
+
print(f"Causality violated")
|
| 1653 |
+
break
|
| 1654 |
+
else:
|
| 1655 |
+
print("Causality verified")
|
| 1656 |
+
|
| 1657 |
+
|
| 1658 |
+
def test_causality_symbolic(module=SequenceModel(128, 16, 4, 2, pos_encoder=True)):
|
| 1659 |
+
torch.manual_seed(42)
|
| 1660 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 1661 |
+
|
| 1662 |
+
model = module
|
| 1663 |
+
x = torch.randint(0, model.vocab_size, (2, model.max_len), device=device)
|
| 1664 |
+
|
| 1665 |
+
print(f"\nInput shape: {x.shape}")
|
| 1666 |
+
|
| 1667 |
+
with torch.no_grad():
|
| 1668 |
+
output = model(x)
|
| 1669 |
+
|
| 1670 |
+
model.eval()
|
| 1671 |
+
with torch.no_grad():
|
| 1672 |
+
full_output = model(x, unembed=False)
|
| 1673 |
+
|
| 1674 |
+
for t in range(1, 20):
|
| 1675 |
+
prefix_output = model(x[:, :t], unembed=False)
|
| 1676 |
+
|
| 1677 |
+
max_diff = (full_output[:, :t] - prefix_output).abs().max().item()
|
| 1678 |
+
|
| 1679 |
+
if max_diff > 1e-5:
|
| 1680 |
+
print(f"Causality violated")
|
| 1681 |
+
break
|
| 1682 |
+
else:
|
| 1683 |
+
print("Causality verified")
|
| 1684 |
+
|
| 1685 |
+
|
| 1686 |
+
def test_symbolic_model(model):
|
| 1687 |
+
vocab_size = model.vocab_size
|
| 1688 |
+
length = model.max_len
|
| 1689 |
+
x = torch.randint(0, vocab_size, (2, length))
|
| 1690 |
+
print(f"Input shape {x.shape}")
|
| 1691 |
+
x = model(x)
|
| 1692 |
+
print(f"Output shape {x.shape}")
|
| 1693 |
+
print(f"Bounds: min:{x.min()}, max:{x.max()}, mean:{x.mean()}, std:{x.std()}, sum_of_a_vector: {x[0, 0, :].sum()}")"""
|
| 1694 |
+
|
| 1695 |
+
########################################################################################################################
|
| 1696 |
+
|
| 1697 |
+
NAMES = {
|
| 1698 |
+
"MLP": MLP,
|
| 1699 |
+
"Attention": MultiheadAttentionMixer,
|
| 1700 |
+
"MHA": MultiheadAttentionMixer,
|
| 1701 |
+
"Embed": Embedder,
|
| 1702 |
+
"EMBED": Embedder,
|
| 1703 |
+
"E": Embedder,
|
| 1704 |
+
"PE": SinusoidalPositionalEncoding,
|
| 1705 |
+
"PatchEmbedder": PatchEmbedder,
|
| 1706 |
+
"ClassificationHead": MeanClassificationHead,
|
| 1707 |
+
"MCH": MeanClassificationHead,
|
| 1708 |
+
"ClsHead": ClsHead,
|
| 1709 |
+
}
|
| 1710 |
+
|
| 1711 |
+
|
| 1712 |
+
def expand_architecture(arch_string):
|
| 1713 |
+
pattern = r"(\(.+?\))x(\d+)"
|
| 1714 |
+
|
| 1715 |
+
def replacer(match):
|
| 1716 |
+
content = match.group(1)
|
| 1717 |
+
count = int(match.group(2))
|
| 1718 |
+
return " -> ".join([content] * count)
|
| 1719 |
+
|
| 1720 |
+
return re.sub(pattern, replacer, arch_string)
|
| 1721 |
+
|
| 1722 |
+
|
| 1723 |
+
def assemble(code, d_model=512):
|
| 1724 |
+
code = code.strip()
|
| 1725 |
+
if "x" in code:
|
| 1726 |
+
code = expand_architecture(code)
|
| 1727 |
+
divided = code.split("->")
|
| 1728 |
+
|
| 1729 |
+
for arch in divided:
|
| 1730 |
+
arch = arch.strip()
|
| 1731 |
+
if arch.startswith("("):
|
| 1732 |
+
layers = []
|
| 1733 |
+
for subarch in arch[1:-1].split(","):
|
| 1734 |
+
subarch = subarch.strip()
|
| 1735 |
+
if subarch in NAMES:
|
| 1736 |
+
layer = NAMES[subarch](d_model=d_model)
|
| 1737 |
+
else:
|
| 1738 |
+
raise ValueError(f"Unknown Layer: {subarch}")
|
| 1739 |
+
layers.append(subarch)
|
| 1740 |
+
block = Block1d(*layers)
|
| 1741 |
+
|
| 1742 |
+
else:
|
| 1743 |
+
if arch in NAMES:
|
| 1744 |
+
layer = NAMES[arch]
|
| 1745 |
+
elif hasattr(nn, arch):
|
| 1746 |
+
layer = getattr(nn, arch)
|
| 1747 |
+
else:
|
| 1748 |
+
raise ValueError(f"Unknown Layer: {arch}")
|
| 1749 |
+
|
| 1750 |
+
|
| 1751 |
+
def wrap(module):
|
| 1752 |
+
return tWrapper(module)
|
| 1753 |
+
|
| 1754 |
+
|
| 1755 |
+
def get_builder(layer, params):
|
| 1756 |
+
pass
|
| 1757 |
+
|
| 1758 |
+
|
| 1759 |
+
########################################################################################################################
|
| 1760 |
+
# Pre Built
|
| 1761 |
+
########################################################################################################################
|
| 1762 |
+
|
| 1763 |
+
|
| 1764 |
+
GQA = GroupedQueryAttention
|
| 1765 |
+
GQANOO = GroupedQueryAttentionNOO
|
| 1766 |
+
GQANOV = GroupedQueryAttentionNOV
|
| 1767 |
+
Attention = MultiheadAttentionMixer
|
| 1768 |
+
AttentionNOV = MultiheadAttentionMixerNOV
|
| 1769 |
+
NOVLa2 = MultiheadAttentionMixerNOVLa2
|
| 1770 |
+
|
| 1771 |
+
|
| 1772 |
+
# =============================================================================
|
| 1773 |
+
# HIGH-LEVEL MODELS
|
| 1774 |
+
# =============================================================================
|
| 1775 |
+
#
|
| 1776 |
+
# Modelli pronti all'uso, ottimizzati per training/inference efficienti. A
|
| 1777 |
+
# differenza di SequenceModel / ModdedFormer (pensati per studio + ablation),
|
| 1778 |
+
# qui ogni Python branch è pre-risolto al __init__, niente ParameterDict /
|
| 1779 |
+
# ModuleDict string-keyed, niente dict mutati nel forward. Tutto torch.compile-
|
| 1780 |
+
# friendly.
|
| 1781 |
+
#
|
| 1782 |
+
# d_model è fissa su tutti i layer (sceltà per semplicità + compile efficiency).
|
| 1783 |
+
# =============================================================================
|
| 1784 |
+
|
| 1785 |
+
|
| 1786 |
+
class BlockX0(Layer):
|
| 1787 |
+
"""Pre-norm block + x0-skip nativo.
|
| 1788 |
+
|
| 1789 |
+
Forward(x, x0) -> x + spatial(norm1(x)) + channel(norm2(x)) + x0_lambda * x0
|
| 1790 |
+
`x0_lambda` init=0 ⇒ comportamento identico a Block1d al primo step.
|
| 1791 |
+
"""
|
| 1792 |
+
__name__ = "BlockX0"
|
| 1793 |
+
|
| 1794 |
+
def __init__(self, d_model: int, channel_mixer: Layer, spatial_mixer: Layer, norm=RMSNorm):
|
| 1795 |
+
super().__init__()
|
| 1796 |
+
self.spatial_mixer = spatial_mixer
|
| 1797 |
+
self.channel_mixer = channel_mixer
|
| 1798 |
+
self.norm1 = norm(d_model)
|
| 1799 |
+
self.norm2 = norm(d_model)
|
| 1800 |
+
self.x0_lambda = nn.Parameter(torch.zeros(1))
|
| 1801 |
+
|
| 1802 |
+
def forward(self, x: torch.Tensor, x0: torch.Tensor) -> torch.Tensor:
|
| 1803 |
+
x = x + self.spatial_mixer(self.norm1(x))
|
| 1804 |
+
x = x + self.channel_mixer(self.norm2(x))
|
| 1805 |
+
return x + self.x0_lambda * x0
|
| 1806 |
+
|
| 1807 |
+
def generate(self, x: torch.Tensor, x0: torch.Tensor) -> torch.Tensor:
|
| 1808 |
+
h = self.norm1(x)
|
| 1809 |
+
sm = self.spatial_mixer
|
| 1810 |
+
x = x + (sm.generate(h) if sm.has_custom_generate() else sm(h))
|
| 1811 |
+
h = self.norm2(x)
|
| 1812 |
+
cm = self.channel_mixer
|
| 1813 |
+
x = x + (cm.generate(h) if cm.has_custom_generate() else cm(h))
|
| 1814 |
+
return x + self.x0_lambda * x0
|
| 1815 |
+
|
| 1816 |
+
|
| 1817 |
+
class PiCOBlock(BlockX0):
|
| 1818 |
+
"""BlockX0 + value-embedding nativo (off di default, zero costo se off).
|
| 1819 |
+
|
| 1820 |
+
Quando `ve_enabled=True`, forward accetta un tensor `ve` esterno e lo passa
|
| 1821 |
+
al spatial_mixer pre-scalato per `ve_scale` (Parameter init=0 ⇒ no-op iniziale).
|
| 1822 |
+
"""
|
| 1823 |
+
__name__ = "PiCOBlock"
|
| 1824 |
+
|
| 1825 |
+
def __init__(self, d_model: int, channel_mixer: Layer, spatial_mixer: Layer,
|
| 1826 |
+
norm=RMSNorm, ve_enabled: bool = False):
|
| 1827 |
+
super().__init__(d_model, channel_mixer, spatial_mixer, norm)
|
| 1828 |
+
self.ve_enabled = ve_enabled
|
| 1829 |
+
if ve_enabled:
|
| 1830 |
+
self.ve_scale = nn.Parameter(torch.zeros(1))
|
| 1831 |
+
|
| 1832 |
+
def forward(self, x: torch.Tensor, x0: torch.Tensor, ve: torch.Tensor = None) -> torch.Tensor:
|
| 1833 |
+
if self.ve_enabled and ve is not None:
|
| 1834 |
+
x = x + self.spatial_mixer(self.norm1(x), ve=ve * self.ve_scale)
|
| 1835 |
+
else:
|
| 1836 |
+
x = x + self.spatial_mixer(self.norm1(x))
|
| 1837 |
+
x = x + self.channel_mixer(self.norm2(x))
|
| 1838 |
+
return x + self.x0_lambda * x0
|
| 1839 |
+
|
| 1840 |
+
def generate(self, x: torch.Tensor, x0: torch.Tensor, ve: torch.Tensor = None) -> torch.Tensor:
|
| 1841 |
+
h = self.norm1(x)
|
| 1842 |
+
sm = self.spatial_mixer
|
| 1843 |
+
if self.ve_enabled and ve is not None:
|
| 1844 |
+
scaled = ve * self.ve_scale
|
| 1845 |
+
x = x + (sm.generate(h, ve=scaled) if sm.has_custom_generate() else sm(h, ve=scaled))
|
| 1846 |
+
else:
|
| 1847 |
+
x = x + (sm.generate(h) if sm.has_custom_generate() else sm(h))
|
| 1848 |
+
h = self.norm2(x)
|
| 1849 |
+
cm = self.channel_mixer
|
| 1850 |
+
x = x + (cm.generate(h) if cm.has_custom_generate() else cm(h))
|
| 1851 |
+
return x + self.x0_lambda * x0
|
| 1852 |
+
|
| 1853 |
+
|
| 1854 |
+
class PiCOFormer(VathosModel):
|
| 1855 |
+
"""High-level transformer. Ultra-efficient, modular, x0-skip nativo.
|
| 1856 |
+
|
| 1857 |
+
Caratteristiche:
|
| 1858 |
+
- `d_model` fissa su tutti i layer.
|
| 1859 |
+
- Spatial mixers heterogeneous (lista di `Builder`, uno per layer).
|
| 1860 |
+
- x0-skip nativo via `BlockX0` / `PiCOBlock`.
|
| 1861 |
+
- Value embeddings opzionali (zero cost se off, indexed-by-layer).
|
| 1862 |
+
- Smear gate opzionale (Modded-NanoGPT style) su x0.
|
| 1863 |
+
- Final logit softcap.
|
| 1864 |
+
- Tied embeddings di default.
|
| 1865 |
+
|
| 1866 |
+
Args:
|
| 1867 |
+
vocab_size: vocab size.
|
| 1868 |
+
d_model: residual stream dim (fissa).
|
| 1869 |
+
n_layers: numero blocchi.
|
| 1870 |
+
spatials: `Builder` (condiviso) o `List[Builder]` di lunghezza n_layers.
|
| 1871 |
+
channel: `Builder` channel mixer (default VariableUDLP M=4*d_model, ReLU²).
|
| 1872 |
+
norm: classe norm (default RMSNorm).
|
| 1873 |
+
ve_groups: `List[int|None]` di lunghezza n_layers. `None` = no VE su quel layer;
|
| 1874 |
+
int = id gruppo (gruppi condividono lo stesso `nn.Embedding`).
|
| 1875 |
+
`None` globalmente = niente VE (default, niente memory cost).
|
| 1876 |
+
smear_gate: `nn.Module` o `None`. Applicato a x0 dopo l'embedding.
|
| 1877 |
+
logit_softcap: softcap finale. 0 disabilita.
|
| 1878 |
+
tied_embeddings: tying input/output embedding (default True).
|
| 1879 |
+
"""
|
| 1880 |
+
__name__ = "PiCOFormer"
|
| 1881 |
+
|
| 1882 |
+
def __init__(self, vocab_size: int, d_model: int, n_layers: int,
|
| 1883 |
+
spatials, channel=None, norm=RMSNorm,
|
| 1884 |
+
ve_groups=None, smear_gate: nn.Module = None,
|
| 1885 |
+
smear_gate_lookback: int = 0,
|
| 1886 |
+
logit_softcap: float = 30.0, tied_embeddings: bool = True):
|
| 1887 |
+
super().__init__()
|
| 1888 |
+
self.vocab_size = vocab_size
|
| 1889 |
+
self.d_model = d_model
|
| 1890 |
+
self.n_layers = n_layers
|
| 1891 |
+
self.softcap = logit_softcap
|
| 1892 |
+
|
| 1893 |
+
# Spatials: lista di Builder o Builder singolo (condiviso)
|
| 1894 |
+
spatials_list = spatials if isinstance(spatials, list) else [spatials] * n_layers
|
| 1895 |
+
assert len(spatials_list) == n_layers, "len(spatials) must equal n_layers"
|
| 1896 |
+
|
| 1897 |
+
# Channel default = VariableUDLP, M=4*d_model, ReLU²
|
| 1898 |
+
if channel is None:
|
| 1899 |
+
channel = Builder(VariableUDLP, d_output=d_model, M=4 * d_model, activation=ReLU2)
|
| 1900 |
+
|
| 1901 |
+
# VE: pre-compute embedding condiviso per group + indice per layer
|
| 1902 |
+
self._has_any_ve = ve_groups is not None and any(g is not None for g in ve_groups)
|
| 1903 |
+
if self._has_any_ve:
|
| 1904 |
+
assert len(ve_groups) == n_layers, "len(ve_groups) must equal n_layers"
|
| 1905 |
+
unique_groups = sorted({g for g in ve_groups if g is not None})
|
| 1906 |
+
self.ve_embeddings = nn.ModuleList([nn.Embedding(vocab_size, d_model) for _ in unique_groups])
|
| 1907 |
+
group_to_idx = {g: i for i, g in enumerate(unique_groups)}
|
| 1908 |
+
self._ve_idx_per_layer = tuple(
|
| 1909 |
+
group_to_idx[g] if g is not None else -1 for g in ve_groups
|
| 1910 |
+
)
|
| 1911 |
+
else:
|
| 1912 |
+
self.ve_embeddings = nn.ModuleList()
|
| 1913 |
+
self._ve_idx_per_layer = (-1,) * n_layers
|
| 1914 |
+
self._ve_enabled_per_layer = tuple(idx >= 0 for idx in self._ve_idx_per_layer)
|
| 1915 |
+
|
| 1916 |
+
# Embeddings + final norm + unembedder
|
| 1917 |
+
self.embedder = Embedder(vocab_size, d_model)
|
| 1918 |
+
self.unembedder = UnbiasedLinear(d_model, vocab_size)
|
| 1919 |
+
if tied_embeddings:
|
| 1920 |
+
self.unembedder.linear.weight = self.embedder.embedding.weight
|
| 1921 |
+
self.final_norm = norm(d_model)
|
| 1922 |
+
|
| 1923 |
+
# Blocks
|
| 1924 |
+
self.blocks = nn.ModuleList([
|
| 1925 |
+
PiCOBlock(
|
| 1926 |
+
d_model,
|
| 1927 |
+
channel_mixer=channel(d_model),
|
| 1928 |
+
spatial_mixer=spatials_list[i](d_model),
|
| 1929 |
+
norm=norm,
|
| 1930 |
+
ve_enabled=self._ve_enabled_per_layer[i],
|
| 1931 |
+
)
|
| 1932 |
+
for i in range(n_layers)
|
| 1933 |
+
])
|
| 1934 |
+
|
| 1935 |
+
# Smear gate opzionale (nn.Module [B,L,D] -> [B,L,D]).
|
| 1936 |
+
# smear_gate_lookback: 0 = stateless (per-token pointwise) — il fast path
|
| 1937 |
+
# generate() lo applica al singolo token. Se >0, il fast path mantiene una
|
| 1938 |
+
# rolling window di `lookback` token raw embeddings e ricomputa smear su
|
| 1939 |
+
# `window + new_token` (necessario per conv causali kernel>1, RNN, ecc.).
|
| 1940 |
+
self.smear_gate = smear_gate
|
| 1941 |
+
self._smear_lookback = int(smear_gate_lookback)
|
| 1942 |
+
self._x_window = None
|
| 1943 |
+
self._smear_gate_warned = False
|
| 1944 |
+
|
| 1945 |
+
self._init_weights()
|
| 1946 |
+
|
| 1947 |
+
def _init_weights(self):
|
| 1948 |
+
# Embeddings: normal std=0.02 (standard). LM head tied di default ⇒ stesso peso.
|
| 1949 |
+
std_embed = 0.02
|
| 1950 |
+
nn.init.normal_(self.embedder.embedding.weight, mean=0.0, std=std_embed)
|
| 1951 |
+
if self.unembedder.linear.weight is not self.embedder.embedding.weight:
|
| 1952 |
+
nn.init.normal_(self.unembedder.linear.weight, mean=0.0, std=std_embed)
|
| 1953 |
+
for emb in self.ve_embeddings:
|
| 1954 |
+
nn.init.normal_(emb.weight, mean=0.0, std=std_embed)
|
| 1955 |
+
|
| 1956 |
+
# Sub-modules: ciascuno definisce la propria policy di identity-init.
|
| 1957 |
+
if self.smear_gate is not None and hasattr(self.smear_gate, "_init_weights"):
|
| 1958 |
+
self.smear_gate._init_weights()
|
| 1959 |
+
for block in self.blocks:
|
| 1960 |
+
if hasattr(block.channel_mixer, "_init_weights"):
|
| 1961 |
+
block.channel_mixer._init_weights()
|
| 1962 |
+
if hasattr(block.spatial_mixer, "_init_weights"):
|
| 1963 |
+
block.spatial_mixer._init_weights()
|
| 1964 |
+
# x0_lambda (BlockX0) e ve_scale (PiCOBlock) sono già zero-init da nn.Parameter(torch.zeros(1)).
|
| 1965 |
+
|
| 1966 |
+
def _compute_ves(self, input_ids: torch.Tensor):
|
| 1967 |
+
"""Pre-computa la lista di tensori VE per ogni layer (None se non abilitato)."""
|
| 1968 |
+
if not self._has_any_ve:
|
| 1969 |
+
return (None,) * self.n_layers
|
| 1970 |
+
# Computa unique embeddings UNA volta, poi distribuisce per layer
|
| 1971 |
+
unique_ves = [emb(input_ids) for emb in self.ve_embeddings]
|
| 1972 |
+
return tuple(unique_ves[idx] if idx >= 0 else None for idx in self._ve_idx_per_layer)
|
| 1973 |
+
|
| 1974 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 1975 |
+
input_ids = x
|
| 1976 |
+
x0 = self.embedder(x)
|
| 1977 |
+
if self.smear_gate is not None:
|
| 1978 |
+
x0 = self.smear_gate(x0)
|
| 1979 |
+
|
| 1980 |
+
ves = self._compute_ves(input_ids)
|
| 1981 |
+
|
| 1982 |
+
x = x0
|
| 1983 |
+
for i, block in enumerate(self.blocks):
|
| 1984 |
+
x = block(x, x0, ve=ves[i])
|
| 1985 |
+
|
| 1986 |
+
logits = self.unembedder(self.final_norm(x))
|
| 1987 |
+
if self.softcap > 0:
|
| 1988 |
+
logits = self.softcap * torch.tanh(logits / self.softcap)
|
| 1989 |
+
return logits
|
| 1990 |
+
|
| 1991 |
+
@torch.no_grad()
|
| 1992 |
+
def _clear_caches(self):
|
| 1993 |
+
for block in self.blocks:
|
| 1994 |
+
for mod in block.modules():
|
| 1995 |
+
if hasattr(mod, "clear_cache"):
|
| 1996 |
+
mod.clear_cache()
|
| 1997 |
+
self._x_window = None
|
| 1998 |
+
|
| 1999 |
+
@torch.no_grad()
|
| 2000 |
+
def generate(self, prompt: torch.Tensor, max_len: int = 100,
|
| 2001 |
+
temperature: float = 1.0, top_k: int | None = None,
|
| 2002 |
+
top_p: float = 1.0, repetition_penalty: float = 1.0,
|
| 2003 |
+
token_end=None, simple: bool = False) -> torch.Tensor:
|
| 2004 |
+
"""KV-cached AR generation. Prefill in un colpo, poi token-by-token."""
|
| 2005 |
+
if simple:
|
| 2006 |
+
return simple_ar_generate(
|
| 2007 |
+
self, prompt, max_len=max_len, temperature=temperature,
|
| 2008 |
+
top_k=top_k, top_p=top_p, repetition_penalty=repetition_penalty,
|
| 2009 |
+
token_end=token_end,
|
| 2010 |
+
)
|
| 2011 |
+
|
| 2012 |
+
self.eval()
|
| 2013 |
+
self._clear_caches()
|
| 2014 |
+
|
| 2015 |
+
# Warning una tantum se smear_gate è non-trivial ma lookback=0
|
| 2016 |
+
if (self.smear_gate is not None
|
| 2017 |
+
and not isinstance(self.smear_gate, nn.Identity)
|
| 2018 |
+
and self._smear_lookback == 0
|
| 2019 |
+
and not self._smear_gate_warned):
|
| 2020 |
+
import warnings
|
| 2021 |
+
warnings.warn(
|
| 2022 |
+
"PiCOFormer.generate(simple=False): smear_gate non-Identity con "
|
| 2023 |
+
"smear_gate_lookback=0. Se il modulo ha state temporale (conv "
|
| 2024 |
+
"kernel>1, RNN, ecc.), il fast path divergerà da simple=True. "
|
| 2025 |
+
"Setta smear_gate_lookback=<receptive_field-1> o usa simple=True.",
|
| 2026 |
+
RuntimeWarning, stacklevel=2,
|
| 2027 |
+
)
|
| 2028 |
+
self._smear_gate_warned = True
|
| 2029 |
+
|
| 2030 |
+
if prompt.dim() == 1:
|
| 2031 |
+
prompt = prompt.unsqueeze(0)
|
| 2032 |
+
device = next(self.parameters()).device
|
| 2033 |
+
prompt = prompt.to(device)
|
| 2034 |
+
|
| 2035 |
+
# --- Prefill --------------------------------------------------------
|
| 2036 |
+
x0_raw = self.embedder(prompt)
|
| 2037 |
+
if self.smear_gate is not None:
|
| 2038 |
+
x0 = self.smear_gate(x0_raw)
|
| 2039 |
+
if self._smear_lookback > 0:
|
| 2040 |
+
# Salva ultimi `lookback` raw embeddings come window (esclude i nuovi)
|
| 2041 |
+
self._x_window = x0_raw[:, -self._smear_lookback:, :].clone()
|
| 2042 |
+
else:
|
| 2043 |
+
x0 = x0_raw
|
| 2044 |
+
ves = self._compute_ves(prompt)
|
| 2045 |
+
x = x0
|
| 2046 |
+
for i, block in enumerate(self.blocks):
|
| 2047 |
+
x = block.generate(x, x0, ve=ves[i])
|
| 2048 |
+
logits = self.unembedder(self.final_norm(x))[:, -1, :]
|
| 2049 |
+
if self.softcap > 0:
|
| 2050 |
+
logits = self.softcap * torch.tanh(logits / self.softcap)
|
| 2051 |
+
if repetition_penalty != 1.0:
|
| 2052 |
+
logits = apply_repetition_penalty(logits, prompt, repetition_penalty)
|
| 2053 |
+
next_token = sample_next_token(logits, temperature, top_k, top_p)
|
| 2054 |
+
generated = torch.cat([prompt, next_token], dim=1)
|
| 2055 |
+
|
| 2056 |
+
# --- Step-by-step ---------------------------------------------------
|
| 2057 |
+
pbar = tqdm(range(max_len - 1), desc="PiCOFormer fast gen")
|
| 2058 |
+
for _ in pbar:
|
| 2059 |
+
x0_t_raw = self.embedder(next_token)
|
| 2060 |
+
if self.smear_gate is not None:
|
| 2061 |
+
if self._smear_lookback > 0:
|
| 2062 |
+
# combina window + new, applica smear, prendi ultimo token
|
| 2063 |
+
combined = torch.cat([self._x_window, x0_t_raw], dim=1)
|
| 2064 |
+
smeared = self.smear_gate(combined)
|
| 2065 |
+
x0_t = smeared[:, -1:, :]
|
| 2066 |
+
# aggiorna window: tieni gli ultimi `lookback` raw embeddings
|
| 2067 |
+
self._x_window = combined[:, -self._smear_lookback:, :]
|
| 2068 |
+
else:
|
| 2069 |
+
x0_t = self.smear_gate(x0_t_raw)
|
| 2070 |
+
else:
|
| 2071 |
+
x0_t = x0_t_raw
|
| 2072 |
+
ves_t = self._compute_ves(next_token)
|
| 2073 |
+
x_t = x0_t
|
| 2074 |
+
for i, block in enumerate(self.blocks):
|
| 2075 |
+
x_t = block.generate(x_t, x0_t, ve=ves_t[i])
|
| 2076 |
+
logits = self.unembedder(self.final_norm(x_t))[:, -1, :]
|
| 2077 |
+
if self.softcap > 0:
|
| 2078 |
+
logits = self.softcap * torch.tanh(logits / self.softcap)
|
| 2079 |
+
if repetition_penalty != 1.0:
|
| 2080 |
+
logits = apply_repetition_penalty(logits, generated, repetition_penalty)
|
| 2081 |
+
next_token = sample_next_token(logits, temperature, top_k, top_p)
|
| 2082 |
+
generated = torch.cat([generated, next_token], dim=1)
|
| 2083 |
+
if token_end is not None and (next_token == token_end).all():
|
| 2084 |
+
break
|
| 2085 |
+
|
| 2086 |
+
self._clear_caches()
|
| 2087 |
+
return generated
|
| 2088 |
+
|
| 2089 |
+
|
| 2090 |
+
if __name__ == "__main__":
|
| 2091 |
+
d_models = [64 for i in range(4)]
|
| 2092 |
+
m_dims = [128, 64, 32, 16]
|
| 2093 |
+
attn = Builder(GQA, n_heads=4, n_kv_heads=2)
|
| 2094 |
+
model = ModdedFormer(
|
| 2095 |
+
vocab_size=100,
|
| 2096 |
+
embed_dim=64,
|
| 2097 |
+
d_models=d_models,
|
| 2098 |
+
spatials=[attn for _ in range(len(d_models))],
|
| 2099 |
+
M_dims=m_dims,
|
| 2100 |
+
input_projections=3,
|
| 2101 |
+
zeroskip=True
|
| 2102 |
+
)
|
| 2103 |
+
model.summary()
|
| 2104 |
+
|
| 2105 |
+
out = model(
|
| 2106 |
+
torch.randint(0, 99, (2, 128))
|
| 2107 |
+
)
|
| 2108 |
+
print(out.shape)
|
| 2109 |
+
|
| 2110 |
+
model.profile()
|
vathos/complexity.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
from collections import defaultdict
|
| 3 |
+
from typing import List, Tuple, Dict
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def parse_complexity(complexity: str) -> Tuple[Dict[str, int], List[str]]:
|
| 7 |
+
"""
|
| 8 |
+
Parse a Big O complexity string.
|
| 9 |
+
|
| 10 |
+
Returns:
|
| 11 |
+
Tuple of (variable_exponents dict, log_terms list)
|
| 12 |
+
where log_terms contains the variables inside log (e.g., ['n', 'L'])
|
| 13 |
+
"""
|
| 14 |
+
match = re.search(r'O\((.*?)\)', complexity)
|
| 15 |
+
if not match:
|
| 16 |
+
return {}, []
|
| 17 |
+
|
| 18 |
+
content = match.group(1).strip()
|
| 19 |
+
|
| 20 |
+
log_terms = []
|
| 21 |
+
log_matches = re.findall(r'\blog\s+(\w+)', content, flags=re.IGNORECASE)
|
| 22 |
+
log_terms.extend(log_matches)
|
| 23 |
+
|
| 24 |
+
standalone_logs = re.findall(r'\blog(?!\s+\w)', content, flags=re.IGNORECASE)
|
| 25 |
+
log_terms.extend(['n'] * len(standalone_logs))
|
| 26 |
+
|
| 27 |
+
content_no_log = re.sub(r'\blog\s*\w*', '', content, flags=re.IGNORECASE)
|
| 28 |
+
|
| 29 |
+
exponents = defaultdict(int)
|
| 30 |
+
tokens = re.findall(r'([a-zA-Z_]\w*)(?:\^(\d+))?', content_no_log)
|
| 31 |
+
|
| 32 |
+
for var, exp in tokens:
|
| 33 |
+
if var and var.lower() != 'log':
|
| 34 |
+
exponent = int(exp) if exp else 1
|
| 35 |
+
exponents[var] = max(exponents[var], exponent)
|
| 36 |
+
|
| 37 |
+
return dict(exponents), log_terms
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def compare_complexities(c1: Tuple[Dict[str, int], List[str]],
|
| 41 |
+
c2: Tuple[Dict[str, int], List[str]]) -> int:
|
| 42 |
+
"""
|
| 43 |
+
Compare two complexities.
|
| 44 |
+
Returns: 1 if c1 > c2, -1 if c1 < c2, 0 if equal
|
| 45 |
+
"""
|
| 46 |
+
exp1, log1 = c1
|
| 47 |
+
exp2, log2 = c2
|
| 48 |
+
|
| 49 |
+
all_vars = set(exp1.keys()) | set(exp2.keys())
|
| 50 |
+
|
| 51 |
+
for var in sorted(all_vars):
|
| 52 |
+
e1 = exp1.get(var, 0)
|
| 53 |
+
e2 = exp2.get(var, 0)
|
| 54 |
+
if e1 > e2:
|
| 55 |
+
return 1
|
| 56 |
+
elif e1 < e2:
|
| 57 |
+
return -1
|
| 58 |
+
|
| 59 |
+
if len(log1) > len(log2):
|
| 60 |
+
return 1
|
| 61 |
+
elif len(log1) < len(log2):
|
| 62 |
+
return -1
|
| 63 |
+
|
| 64 |
+
return 0
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def combine_big_o_product(complexities: List[str]) -> str:
|
| 68 |
+
"""
|
| 69 |
+
Combines multiple Big O complexity strings by taking the dominant complexity.
|
| 70 |
+
|
| 71 |
+
Args:
|
| 72 |
+
complexities: List of Big O notation strings
|
| 73 |
+
|
| 74 |
+
Returns:
|
| 75 |
+
Combined Big O notation representing the dominant complexity
|
| 76 |
+
|
| 77 |
+
Examples:
|
| 78 |
+
>>> combine_big_o(["O(n^2)", "O(n log n)", "O(n^3)"])
|
| 79 |
+
'O(n^3)'
|
| 80 |
+
>>> combine_big_o(["O(n)", "O(n log n)"])
|
| 81 |
+
'O(n log n)'
|
| 82 |
+
>>> combine_big_o(["O(L^2 d^2)", "O(L d^2 k)", "O(L^2 d^2)"])
|
| 83 |
+
'O(L^2 d^2 k)'
|
| 84 |
+
"""
|
| 85 |
+
if not complexities:
|
| 86 |
+
return "O(1)"
|
| 87 |
+
|
| 88 |
+
parsed = [parse_complexity(c) for c in complexities]
|
| 89 |
+
|
| 90 |
+
# Find the dominant complexity
|
| 91 |
+
dominant = parsed[0]
|
| 92 |
+
dominant_str = complexities[0]
|
| 93 |
+
|
| 94 |
+
for i in range(1, len(parsed)):
|
| 95 |
+
if compare_complexities(parsed[i], dominant) > 0:
|
| 96 |
+
dominant = parsed[i]
|
| 97 |
+
dominant_str = complexities[i]
|
| 98 |
+
|
| 99 |
+
# Find the maximum exponents for all variables
|
| 100 |
+
max_exponents = defaultdict(int)
|
| 101 |
+
for exponents, log_terms in parsed:
|
| 102 |
+
for var, exp in exponents.items():
|
| 103 |
+
max_exponents[var] = max(max_exponents[var], exp)
|
| 104 |
+
|
| 105 |
+
# Collect all log terms from complexities with maximum polynomial degree
|
| 106 |
+
all_log_terms = []
|
| 107 |
+
for exponents, log_terms in parsed:
|
| 108 |
+
has_max_degree = True
|
| 109 |
+
|
| 110 |
+
# Check if this has any variable at less than max exponent
|
| 111 |
+
for var, exp in exponents.items():
|
| 112 |
+
if exp < max_exponents[var]:
|
| 113 |
+
has_max_degree = False
|
| 114 |
+
break
|
| 115 |
+
|
| 116 |
+
# If it has max degree for its variables, keep its log terms
|
| 117 |
+
if has_max_degree:
|
| 118 |
+
all_log_terms.extend(log_terms)
|
| 119 |
+
|
| 120 |
+
if not max_exponents and not all_log_terms:
|
| 121 |
+
return "O(1)"
|
| 122 |
+
|
| 123 |
+
# Build the result string
|
| 124 |
+
terms = []
|
| 125 |
+
|
| 126 |
+
# Add variable terms
|
| 127 |
+
for var in sorted(max_exponents.keys()):
|
| 128 |
+
exp = max_exponents[var]
|
| 129 |
+
if exp == 1:
|
| 130 |
+
terms.append(var)
|
| 131 |
+
else:
|
| 132 |
+
terms.append(f"{var}^{exp}")
|
| 133 |
+
|
| 134 |
+
# Add log terms if applicable
|
| 135 |
+
if all_log_terms:
|
| 136 |
+
log_str = " ".join([f"log {var}" for var in sorted(set(all_log_terms))])
|
| 137 |
+
if terms:
|
| 138 |
+
terms.append(log_str)
|
| 139 |
+
else:
|
| 140 |
+
return f"O({log_str})"
|
| 141 |
+
|
| 142 |
+
return f"O({' '.join(terms)})"
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
from typing import List
|
| 146 |
+
from collections import defaultdict
|
| 147 |
+
import re
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def combine_big_o_sum(complexities: List[str]) -> str:
|
| 151 |
+
"""
|
| 152 |
+
Combines multiple Big O complexity strings by summing them and simplifying
|
| 153 |
+
like terms to return a human-readable dominant complexity.
|
| 154 |
+
|
| 155 |
+
Args:
|
| 156 |
+
complexities: List of Big O notation strings
|
| 157 |
+
|
| 158 |
+
Returns:
|
| 159 |
+
Combined Big O notation representing the simplified sum
|
| 160 |
+
|
| 161 |
+
Examples:
|
| 162 |
+
>>> combine_big_o_sum(["O(n^2)", "O(n)", "O(1)"])
|
| 163 |
+
'O(n^2)'
|
| 164 |
+
>>> combine_big_o_sum(["O(L d^2)", "O(L d^2)", "O(L^2 d)"])
|
| 165 |
+
'O(L d^2 + L^2 d)'
|
| 166 |
+
>>> combine_big_o_sum(["O(depth L d^2)", "O(L^2 d)", "O(L d^2)", "O(L d^2)", "O(L d)"])
|
| 167 |
+
'O(L d^2 + L^2 d)'
|
| 168 |
+
"""
|
| 169 |
+
if not complexities:
|
| 170 |
+
return "O(1)"
|
| 171 |
+
|
| 172 |
+
all_terms = []
|
| 173 |
+
for c in complexities:
|
| 174 |
+
term = c.strip()
|
| 175 |
+
if term.startswith("O(") and term.endswith(")"):
|
| 176 |
+
term = term[2:-1].strip()
|
| 177 |
+
if term and term != "1":
|
| 178 |
+
# Split by + to handle multiple terms in one complexity
|
| 179 |
+
all_terms.extend([t.strip() for t in term.split('+')])
|
| 180 |
+
|
| 181 |
+
if not all_terms:
|
| 182 |
+
return "O(1)"
|
| 183 |
+
|
| 184 |
+
term_groups = defaultdict(list)
|
| 185 |
+
|
| 186 |
+
for term in all_terms:
|
| 187 |
+
var_signature = []
|
| 188 |
+
parts = term.split()
|
| 189 |
+
|
| 190 |
+
for part in parts:
|
| 191 |
+
if part.replace('.', '').isdigit() or part in ['depth', 'batch', 'const']:
|
| 192 |
+
continue
|
| 193 |
+
|
| 194 |
+
# Parse variable with optional exponent
|
| 195 |
+
if '^' in part:
|
| 196 |
+
var, exp = part.split('^')
|
| 197 |
+
var_signature.append((var, int(exp)))
|
| 198 |
+
else:
|
| 199 |
+
var_signature.append((part, 1))
|
| 200 |
+
|
| 201 |
+
# Sort to create canonical signature
|
| 202 |
+
var_signature = tuple(sorted(var_signature))
|
| 203 |
+
term_groups[var_signature].append(term)
|
| 204 |
+
|
| 205 |
+
unique_terms = []
|
| 206 |
+
for sig, terms in term_groups.items():
|
| 207 |
+
representative = min(terms, key=lambda t: (len(t), t))
|
| 208 |
+
unique_terms.append((sig, representative))
|
| 209 |
+
|
| 210 |
+
if not unique_terms:
|
| 211 |
+
return "O(1)"
|
| 212 |
+
|
| 213 |
+
def complexity_score(sig_term):
|
| 214 |
+
sig, term = sig_term
|
| 215 |
+
if not sig:
|
| 216 |
+
return (0, 0, term)
|
| 217 |
+
# Total degree and max degree
|
| 218 |
+
total_degree = sum(exp for var, exp in sig)
|
| 219 |
+
max_degree = max(exp for var, exp in sig)
|
| 220 |
+
return (max_degree, total_degree, term)
|
| 221 |
+
|
| 222 |
+
unique_terms.sort(key=complexity_score, reverse=True)
|
| 223 |
+
|
| 224 |
+
# If clearly dominated, return just the dominant term(s)
|
| 225 |
+
if len(unique_terms) > 1:
|
| 226 |
+
top_score = complexity_score(unique_terms[0])
|
| 227 |
+
# Keep all terms with the same max complexity
|
| 228 |
+
result_terms = [term for sig_term in unique_terms
|
| 229 |
+
if complexity_score(sig_term)[0] == top_score[0]]
|
| 230 |
+
else:
|
| 231 |
+
result_terms = [unique_terms[0][1]]
|
| 232 |
+
|
| 233 |
+
# Build result
|
| 234 |
+
if len(result_terms) == 1:
|
| 235 |
+
return f"O({result_terms[0]})"
|
| 236 |
+
else:
|
| 237 |
+
return f"O({' + '.join(result_terms)})"
|
| 238 |
+
|
vathos/convolutions.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import torch.nn.functional as F
|
| 4 |
+
from torch import Tensor
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
class FastShiftConv1d_K3(nn.Module):
|
| 8 |
+
def __init__(self, dim: int):
|
| 9 |
+
super().__init__()
|
| 10 |
+
self.w0 = nn.Parameter(torch.ones(dim))
|
| 11 |
+
self.w1 = nn.Parameter(torch.zeros(dim))
|
| 12 |
+
self.w2 = nn.Parameter(torch.zeros(dim))
|
| 13 |
+
|
| 14 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 15 |
+
x1 = F.pad(x[:, :-1, :], (0, 0, 1, 0))
|
| 16 |
+
x2 = F.pad(x[:, :-2, :], (0, 0, 2, 0))
|
| 17 |
+
|
| 18 |
+
return (x * self.w0.to(x.dtype)) + (x1 * self.w1.to(x.dtype)) + (x2 * self.w2.to(x.dtype))
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class FastShiftConv1d_K4(nn.Module):
|
| 22 |
+
def __init__(self, dim: int):
|
| 23 |
+
super().__init__()
|
| 24 |
+
self.w0 = nn.Parameter(torch.ones(dim))
|
| 25 |
+
self.w1 = nn.Parameter(torch.zeros(dim))
|
| 26 |
+
self.w2 = nn.Parameter(torch.zeros(dim))
|
| 27 |
+
self.w3 = nn.Parameter(torch.zeros(dim))
|
| 28 |
+
|
| 29 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 30 |
+
x1 = F.pad(x[:, :-1, :], (0, 0, 1, 0))
|
| 31 |
+
x2 = F.pad(x[:, :-2, :], (0, 0, 2, 0))
|
| 32 |
+
x3 = F.pad(x[:, :-3, :], (0, 0, 3, 0))
|
| 33 |
+
|
| 34 |
+
return (x * self.w0.to(x.dtype)) + \
|
| 35 |
+
(x1 * self.w1.to(x.dtype)) + \
|
| 36 |
+
(x2 * self.w2.to(x.dtype)) + \
|
| 37 |
+
(x3 * self.w3.to(x.dtype))
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class FastShiftConv1d_K2(nn.Module):
|
| 41 |
+
def __init__(self, dim: int):
|
| 42 |
+
super().__init__()
|
| 43 |
+
# Dirac/Identity Init: 1 for current token, 0 for past tokens
|
| 44 |
+
self.w0 = nn.Parameter(torch.ones(dim))
|
| 45 |
+
self.w1 = nn.Parameter(torch.zeros(dim))
|
| 46 |
+
|
| 47 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 48 |
+
x1 = F.pad(x[:, :-1, :], (0, 0, 1, 0))
|
| 49 |
+
|
| 50 |
+
return (x * self.w0.to(x.dtype)) + (x1 * self.w1.to(x.dtype))
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class RWKVTimeMix(nn.Module):
|
| 54 |
+
def __init__(self, dim: int):
|
| 55 |
+
super().__init__()
|
| 56 |
+
self.time_mix = nn.Parameter(torch.ones(dim, dtype=torch.float32))
|
| 57 |
+
|
| 58 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 59 |
+
x_prev = F.pad(x[:, :-1, :], (0, 0, 1, 0))
|
| 60 |
+
mix = self.time_mix.to(x.dtype)
|
| 61 |
+
|
| 62 |
+
return x * mix + x_prev * (1.0 - mix)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class SmearGate2(nn.Module):
|
| 66 |
+
def __init__(self, dim: int, gate_dim: int = 12):
|
| 67 |
+
super().__init__()
|
| 68 |
+
self.gate_dim = min(gate_dim, dim)
|
| 69 |
+
self.W_g = nn.Parameter(torch.zeros(self.gate_dim, dim))
|
| 70 |
+
self.smear_lambda = nn.Parameter(torch.zeros(1))
|
| 71 |
+
|
| 72 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 73 |
+
x_prev = F.pad(x[:, :-1, :], (0, 0, 1, 0))
|
| 74 |
+
x_slice = x[..., :self.gate_dim]
|
| 75 |
+
g = torch.sigmoid(torch.matmul(x_slice, self.W_g.to(x.dtype)))
|
| 76 |
+
|
| 77 |
+
return x + self.smear_lambda * g * x_prev
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
class SmearGateLookback3(nn.Module):
|
| 81 |
+
def __init__(self, dim: int, gate_dim: int = 12):
|
| 82 |
+
super().__init__()
|
| 83 |
+
self.gate_dim = min(gate_dim, dim)
|
| 84 |
+
|
| 85 |
+
self.W_g = nn.Parameter(torch.zeros(self.gate_dim, dim * 3))
|
| 86 |
+
|
| 87 |
+
self.smear_lambdas = nn.Parameter(torch.zeros(3))
|
| 88 |
+
|
| 89 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 90 |
+
x_slice = x[..., :self.gate_dim]
|
| 91 |
+
g_all = torch.sigmoid(torch.matmul(x_slice, self.W_g.to(x.dtype)))
|
| 92 |
+
|
| 93 |
+
g1, g2, g3 = g_all.chunk(3, dim=-1)
|
| 94 |
+
|
| 95 |
+
x1 = F.pad(x[:, :-1, :], (0, 0, 1, 0))
|
| 96 |
+
x2 = F.pad(x[:, :-2, :], (0, 0, 2, 0))
|
| 97 |
+
x3 = F.pad(x[:, :-3, :], (0, 0, 3, 0))
|
| 98 |
+
|
| 99 |
+
return x + (self.smear_lambdas[0] * g1 * x1) \
|
| 100 |
+
+ (self.smear_lambdas[1] * g2 * x2) \
|
| 101 |
+
+ (self.smear_lambdas[2] * g3 * x3)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
class ModdedSmearGate(nn.Module):
|
| 105 |
+
def __init__(self, gate_dim: int = 12):
|
| 106 |
+
super().__init__()
|
| 107 |
+
self.gate_dim = gate_dim
|
| 108 |
+
|
| 109 |
+
self.smear_gate = nn.Linear(gate_dim, 1, bias=False)
|
| 110 |
+
|
| 111 |
+
with torch.no_grad():
|
| 112 |
+
nn.init.zeros_(self.smear_gate.weight)
|
| 113 |
+
|
| 114 |
+
def forward(self, x: Tensor) -> Tensor:
|
| 115 |
+
x_prev = F.pad(x[:, :-1, :], (0, 0, 1, 0))
|
| 116 |
+
x_slice = x[..., :self.gate_dim]
|
| 117 |
+
|
| 118 |
+
g = torch.sigmoid(self.smear_gate(x_slice))
|
| 119 |
+
return x + g * (x_prev - x)
|
vathos/data.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from torch.utils.data import Dataset, DataLoader
|
| 2 |
+
from transformers import AutoTokenizer
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
from typing import List, Dict, Any, Union, Optional
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class TextDataset(Dataset):
|
| 9 |
+
"""Text dataset supporting lists, files, and folder structures."""
|
| 10 |
+
|
| 11 |
+
def __init__(self, source: Union[List[str], str, Path]):
|
| 12 |
+
"""
|
| 13 |
+
Args:
|
| 14 |
+
source: Can be:
|
| 15 |
+
- List of strings (text data)
|
| 16 |
+
- Path to single .txt file
|
| 17 |
+
- Path to folder containing .txt files (recursive)
|
| 18 |
+
"""
|
| 19 |
+
self.texts = []
|
| 20 |
+
self.file_paths = [] # Track source files for debugging
|
| 21 |
+
|
| 22 |
+
if isinstance(source, list):
|
| 23 |
+
self.texts = source
|
| 24 |
+
self.file_paths = [None] * len(source)
|
| 25 |
+
else:
|
| 26 |
+
source = Path(source)
|
| 27 |
+
if source.is_file():
|
| 28 |
+
self._load_file(source)
|
| 29 |
+
elif source.is_dir():
|
| 30 |
+
self._load_directory(source)
|
| 31 |
+
else:
|
| 32 |
+
raise ValueError(f"Source not found: {source}")
|
| 33 |
+
|
| 34 |
+
if len(self.texts) == 0:
|
| 35 |
+
raise ValueError("No text data loaded!")
|
| 36 |
+
|
| 37 |
+
def _load_file(self, file_path: Path):
|
| 38 |
+
"""Load a single text file."""
|
| 39 |
+
with open(file_path, 'r', encoding='utf-8') as f:
|
| 40 |
+
text = f.read().strip()
|
| 41 |
+
if text:
|
| 42 |
+
self.texts.append(text)
|
| 43 |
+
self.file_paths.append(str(file_path))
|
| 44 |
+
|
| 45 |
+
def _load_directory(self, dir_path: Path):
|
| 46 |
+
"""Recursively load all .txt files from directory."""
|
| 47 |
+
txt_files = sorted(dir_path.rglob("*.txt"))
|
| 48 |
+
|
| 49 |
+
for file_path in txt_files:
|
| 50 |
+
try:
|
| 51 |
+
self._load_file(file_path)
|
| 52 |
+
except Exception as e:
|
| 53 |
+
print(f"Warning: Failed to load {file_path}: {e}")
|
| 54 |
+
|
| 55 |
+
def __len__(self):
|
| 56 |
+
return len(self.texts)
|
| 57 |
+
|
| 58 |
+
def __getitem__(self, idx):
|
| 59 |
+
return self.texts[idx]
|
| 60 |
+
|
| 61 |
+
def get_file_path(self, idx: int) -> Optional[str]:
|
| 62 |
+
"""Get source file path for debugging."""
|
| 63 |
+
return self.file_paths[idx]
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
@dataclass
|
| 67 |
+
class TokenizerConfig:
|
| 68 |
+
"""Tokenizer configuration."""
|
| 69 |
+
model_name: str = "bert-base-uncased"
|
| 70 |
+
max_length: int = 512
|
| 71 |
+
truncation: bool = True
|
| 72 |
+
padding: str = "max_length" # or "longest"
|
| 73 |
+
return_tensors: str = "pt"
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
class TokenizerWrapper:
|
| 77 |
+
"""Wrapper for HuggingFace tokenizer with collating dataloader."""
|
| 78 |
+
|
| 79 |
+
def __init__(self, config: TokenizerConfig = None):
|
| 80 |
+
self.config = config or TokenizerConfig()
|
| 81 |
+
self.tokenizer = AutoTokenizer.from_pretrained(self.config.model_name)
|
| 82 |
+
|
| 83 |
+
def collate_fn(self, batch: List[str]) -> Dict[str, Any]:
|
| 84 |
+
"""Collate function for DataLoader."""
|
| 85 |
+
return self.tokenizer(
|
| 86 |
+
batch,
|
| 87 |
+
max_length=self.config.max_length,
|
| 88 |
+
truncation=self.config.truncation,
|
| 89 |
+
padding=self.config.padding,
|
| 90 |
+
return_tensors=self.config.return_tensors
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
def decode(self, token_ids, skip_special_tokens: bool = True) -> Union[str, List[str]]:
|
| 94 |
+
"""
|
| 95 |
+
Decode token IDs back to text.
|
| 96 |
+
|
| 97 |
+
Args:
|
| 98 |
+
token_ids: Single sequence or batch of token IDs (tensor or list)
|
| 99 |
+
skip_special_tokens: Whether to remove [CLS], [SEP], [PAD] etc.
|
| 100 |
+
|
| 101 |
+
Returns:
|
| 102 |
+
Decoded text string or list of strings
|
| 103 |
+
"""
|
| 104 |
+
return self.tokenizer.decode(token_ids, skip_special_tokens=skip_special_tokens)
|
| 105 |
+
|
| 106 |
+
def batch_decode(self, token_ids, skip_special_tokens: bool = True) -> List[str]:
|
| 107 |
+
"""
|
| 108 |
+
Decode a batch of token IDs back to texts.
|
| 109 |
+
|
| 110 |
+
Args:
|
| 111 |
+
token_ids: Batch of token IDs (shape: [batch_size, seq_len])
|
| 112 |
+
skip_special_tokens: Whether to remove special tokens
|
| 113 |
+
|
| 114 |
+
Returns:
|
| 115 |
+
List of decoded text strings
|
| 116 |
+
"""
|
| 117 |
+
return self.tokenizer.batch_decode(token_ids, skip_special_tokens=skip_special_tokens)
|
| 118 |
+
|
| 119 |
+
def get_dataloader(
|
| 120 |
+
self,
|
| 121 |
+
source: Union[List[str], str, Path],
|
| 122 |
+
batch_size: int = 8,
|
| 123 |
+
shuffle: bool = True,
|
| 124 |
+
num_workers: int = 0
|
| 125 |
+
) -> DataLoader:
|
| 126 |
+
"""
|
| 127 |
+
Create a DataLoader with tokenization collation.
|
| 128 |
+
|
| 129 |
+
Args:
|
| 130 |
+
source: List of texts, file path, or directory path
|
| 131 |
+
batch_size: Batch size
|
| 132 |
+
shuffle: Whether to shuffle data
|
| 133 |
+
num_workers: Number of workers for data loading
|
| 134 |
+
"""
|
| 135 |
+
dataset = TextDataset(source)
|
| 136 |
+
print(f"Loaded {len(dataset)} texts")
|
| 137 |
+
|
| 138 |
+
return DataLoader(
|
| 139 |
+
dataset,
|
| 140 |
+
batch_size=batch_size,
|
| 141 |
+
shuffle=shuffle,
|
| 142 |
+
num_workers=num_workers,
|
| 143 |
+
collate_fn=self.collate_fn
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
if __name__ == "__main__":
|
| 148 |
+
texts = "_tests"
|
| 149 |
+
|
| 150 |
+
config = TokenizerConfig(
|
| 151 |
+
model_name="bert-base-uncased",
|
| 152 |
+
max_length=32,
|
| 153 |
+
padding="longest"
|
| 154 |
+
)
|
| 155 |
+
wrapper = TokenizerWrapper(config)
|
| 156 |
+
|
| 157 |
+
print("=== Example 1: List of texts ===")
|
| 158 |
+
dataloader = wrapper.get_dataloader(texts, batch_size=2, shuffle=False)
|
| 159 |
+
|
| 160 |
+
for batch_idx, batch in enumerate(dataloader):
|
| 161 |
+
print(f"\nBatch {batch_idx}: {batch['input_ids'].shape}")
|
| 162 |
+
|
| 163 |
+
decoded_batch = wrapper.batch_decode(batch['input_ids'], skip_special_tokens=True)
|
| 164 |
+
print(f"Decoded texts: {decoded_batch}")
|
| 165 |
+
|
| 166 |
+
single_decoded = wrapper.decode(batch['input_ids'][0], skip_special_tokens=True)
|
| 167 |
+
print(f"First sequence: {single_decoded}")
|
| 168 |
+
|
| 169 |
+
with_special = wrapper.decode(batch['input_ids'][0], skip_special_tokens=False)
|
| 170 |
+
print(f"With special tokens: {with_special}")
|
vathos/functions.py
ADDED
|
@@ -0,0 +1,586 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn.functional as F
|
| 3 |
+
import time
|
| 4 |
+
import numpy as np
|
| 5 |
+
|
| 6 |
+
# matplotlib è usato solo dalle utility di plot/benchmark, non dal codice del modello.
|
| 7 |
+
# Lazy + optional import per non rompere l'import di Vathos in ambienti headless.
|
| 8 |
+
try:
|
| 9 |
+
import matplotlib.pyplot as plt
|
| 10 |
+
plt.style.use('ggplot')
|
| 11 |
+
except ImportError:
|
| 12 |
+
plt = None # le funzioni di plot solleveranno errore esplicito se chiamate
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
from colorama import Fore
|
| 16 |
+
|
| 17 |
+
GOOD = Fore.GREEN
|
| 18 |
+
BAD = Fore.RED
|
| 19 |
+
RES = Fore.RESET
|
| 20 |
+
SEC = Fore.LIGHTBLACK_EX
|
| 21 |
+
HM = Fore.YELLOW
|
| 22 |
+
NUM = Fore.BLUE
|
| 23 |
+
except:
|
| 24 |
+
GOOD = ""
|
| 25 |
+
BAD = ""
|
| 26 |
+
RES = ""
|
| 27 |
+
SEC = ""
|
| 28 |
+
NUM = ""
|
| 29 |
+
HM = ""
|
| 30 |
+
|
| 31 |
+
FLAG_PASS = 3
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _require_matplotlib():
|
| 35 |
+
if plt is None:
|
| 36 |
+
raise ImportError(
|
| 37 |
+
"Funzione di plotting chiamata ma matplotlib non è installato. "
|
| 38 |
+
"Installa con `pip install matplotlib` per usarla."
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def plot(*args, **kwargs):
|
| 43 |
+
_require_matplotlib()
|
| 44 |
+
for x_i in args:
|
| 45 |
+
plt.plot(x_i)
|
| 46 |
+
for x_i in kwargs:
|
| 47 |
+
plt.plot(kwargs[x_i], label=str(x_i))
|
| 48 |
+
|
| 49 |
+
plt.legend()
|
| 50 |
+
plt.show()
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def cumsum(x):
|
| 54 |
+
return torch.cumsum(x, dim=1)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def batched_channelwise_conv1d(K, V):
|
| 58 |
+
"""
|
| 59 |
+
Computes a convolution between two batched tensors
|
| 60 |
+
K: [B, L, d] kernel
|
| 61 |
+
V: [B, L, d] signal
|
| 62 |
+
Output: [B, L, d]
|
| 63 |
+
"""
|
| 64 |
+
B, L, d = V.shape
|
| 65 |
+
V_reshaped = V.view(1, B * L, d)
|
| 66 |
+
|
| 67 |
+
K_reshaped = K.view(B * L, 1, d).flip(-1)
|
| 68 |
+
|
| 69 |
+
pad_total = d - 1
|
| 70 |
+
pad_left = pad_total // 2
|
| 71 |
+
pad_right = pad_total - pad_left
|
| 72 |
+
V_padded = F.pad(V_reshaped, (pad_left, pad_right))
|
| 73 |
+
|
| 74 |
+
output = F.conv1d(V_padded, K_reshaped, groups=B * L)
|
| 75 |
+
|
| 76 |
+
return output.view(B, L, d)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def holographic_binding(K, V):
|
| 80 |
+
"""
|
| 81 |
+
Computes Batched Circular Convolution (Binding) using FFT.
|
| 82 |
+
This is O(d log d) and numerically stable.
|
| 83 |
+
|
| 84 |
+
K: [..., d] kernel
|
| 85 |
+
V: [..., d] signal
|
| 86 |
+
Output: [..., d]
|
| 87 |
+
"""
|
| 88 |
+
V_f = torch.fft.rfft(V, dim=-1, norm='ortho')
|
| 89 |
+
K_f = torch.fft.rfft(K, dim=-1, norm='ortho')
|
| 90 |
+
|
| 91 |
+
output_f = V_f * K_f
|
| 92 |
+
output = torch.fft.irfft(output_f, n=V.shape[-1], dim=-1, norm='ortho')
|
| 93 |
+
|
| 94 |
+
return output
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def power_weigthed_cumsum(x, a=0.999, rescale=True):
|
| 98 |
+
"""
|
| 99 |
+
a torch implementation of a power weighted cumsum,
|
| 100 |
+
which can be applied to batched inputs of shapes [B, L, d] or [B, L, d1, d2], summing over L
|
| 101 |
+
"""
|
| 102 |
+
if a - 1 == 0:
|
| 103 |
+
return cumsum(x)
|
| 104 |
+
if len(x.shape) == 3:
|
| 105 |
+
alpha_pow = torch.full([x.shape[1]], a, dtype=x.dtype, device=x.device).cumprod(dim=0).unsqueeze(0).unsqueeze(2)
|
| 106 |
+
elif len(x.shape) == 4:
|
| 107 |
+
alpha_pow = torch.full([x.shape[1]], a, dtype=x.dtype, device=x.device).cumprod(dim=0).unsqueeze(0).unsqueeze(
|
| 108 |
+
2).unsqueeze(2)
|
| 109 |
+
else:
|
| 110 |
+
raise RuntimeError("x shapes mus be of form [B, L, d] or [B, L, d, d]")
|
| 111 |
+
|
| 112 |
+
return torch.cumsum(x / alpha_pow, dim=1) * alpha_pow * (alpha_pow[:, 0, ...] if rescale else 1)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def stable_power_weighted_cumsum(x: torch.Tensor, a: float = 0.999, rescale: bool = True) -> torch.Tensor:
|
| 116 |
+
B, L = x.shape[:2]
|
| 117 |
+
|
| 118 |
+
dtype = x.dtype
|
| 119 |
+
if L > 1024 and x.dtype == torch.float32:
|
| 120 |
+
x = x.to(torch.float64)
|
| 121 |
+
a_tensor = torch.tensor(a, dtype=torch.float64, device=x.device)
|
| 122 |
+
else:
|
| 123 |
+
a_tensor = torch.tensor(a, dtype=x.dtype, device=x.device)
|
| 124 |
+
|
| 125 |
+
t = torch.arange(1, L + 1, dtype=a_tensor.dtype, device=x.device)
|
| 126 |
+
|
| 127 |
+
alpha_pow = torch.pow(a_tensor, t)
|
| 128 |
+
|
| 129 |
+
reshape_dims = [1, L] + [1] * (len(x.shape) - 2)
|
| 130 |
+
alpha_pow = alpha_pow.view(*reshape_dims)
|
| 131 |
+
|
| 132 |
+
scaled_input = x / alpha_pow
|
| 133 |
+
|
| 134 |
+
s_cumulative = torch.cumsum(scaled_input, dim=1)
|
| 135 |
+
|
| 136 |
+
s = s_cumulative * alpha_pow
|
| 137 |
+
|
| 138 |
+
if rescale:
|
| 139 |
+
s = s * (1.0 - a_tensor)
|
| 140 |
+
|
| 141 |
+
return s.to(dtype)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def precompute_power_weigthed_cumsum(x, alpha_pow):
|
| 145 |
+
return torch.cumsum(x / alpha_pow, dim=1) * alpha_pow
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def flag(text, level=1):
|
| 149 |
+
if level <= FLAG_PASS:
|
| 150 |
+
print(f"{SEC}||{HM}FLAG LV.{level}{SEC}||{HM} {text}{RES}")
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def getname(obj):
|
| 154 |
+
return obj.__name__ if hasattr(obj, "__name__") else str(type(obj)).split('.')[-1]
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def benchmark_ar_symbolic_model(model, n, input_shape):
|
| 158 |
+
"""
|
| 159 |
+
Benchmarks the autoregressive symbolic model on three tasks: forward pass,
|
| 160 |
+
forward + backward, and forward + backward + optimizer step. Uses random
|
| 161 |
+
generated data. Reports average times and shows a bar plot.
|
| 162 |
+
|
| 163 |
+
Additionally, benchmarks the ratio of forward time / (forward + backward) time
|
| 164 |
+
with increasing sequence length L, and plots it.
|
| 165 |
+
|
| 166 |
+
Args:
|
| 167 |
+
model: The model to benchmark (e.g., SequenceModel instance).
|
| 168 |
+
n: Number of iterations to average over for each benchmark.
|
| 169 |
+
input_shape: Tuple (batch_size, seq_len) for initial input shape.
|
| 170 |
+
"""
|
| 171 |
+
device = next(model.parameters()).device
|
| 172 |
+
is_cuda = device.type == 'cuda'
|
| 173 |
+
model.train() # Ensure model is in training mode for gradients
|
| 174 |
+
optimizer = torch.optim.Adam(model.parameters())
|
| 175 |
+
|
| 176 |
+
# Helper to sync if CUDA
|
| 177 |
+
def sync():
|
| 178 |
+
if is_cuda:
|
| 179 |
+
torch.cuda.synchronize()
|
| 180 |
+
|
| 181 |
+
# Part 1: Benchmark forward, forward+backward, forward+backward+opt
|
| 182 |
+
times_fwd = []
|
| 183 |
+
times_fb = []
|
| 184 |
+
times_fbo = []
|
| 185 |
+
|
| 186 |
+
for _ in range(n):
|
| 187 |
+
x = torch.randint(0, model.vocab_size, input_shape, device=device, requires_grad=False)
|
| 188 |
+
|
| 189 |
+
# Forward only
|
| 190 |
+
sync()
|
| 191 |
+
start = time.perf_counter()
|
| 192 |
+
out = model(x)
|
| 193 |
+
sync()
|
| 194 |
+
end = time.perf_counter()
|
| 195 |
+
times_fwd.append(end - start)
|
| 196 |
+
|
| 197 |
+
# Forward + backward
|
| 198 |
+
optimizer.zero_grad()
|
| 199 |
+
sync()
|
| 200 |
+
start = time.perf_counter()
|
| 201 |
+
out = model(x)
|
| 202 |
+
loss = out.sum() # Dummy loss
|
| 203 |
+
loss.backward()
|
| 204 |
+
sync()
|
| 205 |
+
end = time.perf_counter()
|
| 206 |
+
times_fb.append(end - start)
|
| 207 |
+
|
| 208 |
+
# Forward + backward + optimizer step
|
| 209 |
+
optimizer.zero_grad()
|
| 210 |
+
sync()
|
| 211 |
+
start = time.perf_counter()
|
| 212 |
+
out = model(x)
|
| 213 |
+
loss = out.sum()
|
| 214 |
+
loss.backward()
|
| 215 |
+
optimizer.step()
|
| 216 |
+
sync()
|
| 217 |
+
end = time.perf_counter()
|
| 218 |
+
times_fbo.append(end - start)
|
| 219 |
+
|
| 220 |
+
avg_fwd = np.mean(times_fwd)
|
| 221 |
+
avg_fb = np.mean(times_fb)
|
| 222 |
+
avg_fbo = np.mean(times_fbo)
|
| 223 |
+
|
| 224 |
+
print(f"Average Forward Time: {avg_fwd:.6f} s")
|
| 225 |
+
print(f"Average Forward + Backward Time: {avg_fb:.6f} s")
|
| 226 |
+
print(f"Average Forward + Backward + Opt Time: {avg_fbo:.6f} s")
|
| 227 |
+
|
| 228 |
+
# Bar plot for the three tasks
|
| 229 |
+
labels = ['Forward', 'Forward + Backward', 'Forward + Backward + Opt']
|
| 230 |
+
times = [avg_fwd, avg_fb, avg_fbo]
|
| 231 |
+
plt.figure(figsize=(8, 5))
|
| 232 |
+
plt.bar(labels, times, color=['blue', 'orange', 'green'])
|
| 233 |
+
plt.ylabel('Average Time (s)')
|
| 234 |
+
plt.title(f'Benchmark Averages over {n} Iterations')
|
| 235 |
+
plt.show()
|
| 236 |
+
|
| 237 |
+
# Part 2: Ratio of forward / (forward + backward) with increasing L
|
| 238 |
+
batch_size = input_shape[0]
|
| 239 |
+
Ls = [16, 32, 64, 128, 256, 512, 1024] # Reasonable sequence lengths, adjust as needed
|
| 240 |
+
ratios = []
|
| 241 |
+
avg_times_fwd = []
|
| 242 |
+
avg_times_fb = []
|
| 243 |
+
inner_n = max(10, n // 10) # Average over fewer iterations for speed, but at least 10
|
| 244 |
+
|
| 245 |
+
for L in Ls:
|
| 246 |
+
shape = (batch_size, L)
|
| 247 |
+
x = torch.randint(0, model.vocab_size, shape, device=device, requires_grad=False)
|
| 248 |
+
|
| 249 |
+
# Time forward (average over inner_n)
|
| 250 |
+
fwd_times = []
|
| 251 |
+
for _ in range(inner_n):
|
| 252 |
+
sync()
|
| 253 |
+
start = time.perf_counter()
|
| 254 |
+
out = model(x)
|
| 255 |
+
sync()
|
| 256 |
+
end = time.perf_counter()
|
| 257 |
+
fwd_times.append(end - start)
|
| 258 |
+
time_fwd = np.mean(fwd_times)
|
| 259 |
+
avg_times_fwd.append(time_fwd)
|
| 260 |
+
|
| 261 |
+
# Time forward + backward (average over inner_n)
|
| 262 |
+
fb_times = []
|
| 263 |
+
for _ in range(inner_n):
|
| 264 |
+
optimizer.zero_grad()
|
| 265 |
+
sync()
|
| 266 |
+
start = time.perf_counter()
|
| 267 |
+
out = model(x)
|
| 268 |
+
loss = out.sum()
|
| 269 |
+
loss.backward()
|
| 270 |
+
sync()
|
| 271 |
+
end = time.perf_counter()
|
| 272 |
+
fb_times.append(end - start)
|
| 273 |
+
time_fb = np.mean(fb_times)
|
| 274 |
+
avg_times_fb.append(time_fb)
|
| 275 |
+
|
| 276 |
+
ratio = time_fwd / time_fb if time_fb > 0 else 0
|
| 277 |
+
ratios.append(ratio)
|
| 278 |
+
|
| 279 |
+
# Plot the ratio
|
| 280 |
+
plt.figure(figsize=(8, 5))
|
| 281 |
+
plt.plot(Ls, ratios, marker='o', color='red')
|
| 282 |
+
plt.xlabel('Sequence Length L')
|
| 283 |
+
plt.ylabel('Time Forward / Time (Forward + Backward)')
|
| 284 |
+
plt.title('Efficiency Ratio vs Sequence Length')
|
| 285 |
+
plt.grid(True)
|
| 286 |
+
plt.show()
|
| 287 |
+
|
| 288 |
+
# Optional: Plot absolute times for reference
|
| 289 |
+
plt.figure(figsize=(8, 5))
|
| 290 |
+
plt.plot(Ls, avg_times_fwd, marker='o', label='Forward', color='blue')
|
| 291 |
+
plt.plot(Ls, avg_times_fb, marker='o', label='Forward + Backward', color='orange')
|
| 292 |
+
plt.xlabel('Sequence Length L')
|
| 293 |
+
plt.ylabel('Average Time (s)')
|
| 294 |
+
plt.title('Absolute Times vs Sequence Length')
|
| 295 |
+
plt.legend()
|
| 296 |
+
plt.grid(True)
|
| 297 |
+
plt.show()
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
def apply_repetition_penalty(logits: torch.Tensor, generated: torch.Tensor,
|
| 301 |
+
penalty: float) -> torch.Tensor:
|
| 302 |
+
"""
|
| 303 |
+
Standard HF-style repetition penalty, vectorized.
|
| 304 |
+
logits: [B, V] (last-position logits)
|
| 305 |
+
generated: [B, L] (token ids generated so far, including prompt)
|
| 306 |
+
Divides logits>0 by penalty, multiplies logits<=0 by penalty, in-place-safe.
|
| 307 |
+
"""
|
| 308 |
+
if penalty == 1.0:
|
| 309 |
+
return logits
|
| 310 |
+
score = torch.gather(logits, 1, generated)
|
| 311 |
+
score = torch.where(score > 0, score / penalty, score * penalty)
|
| 312 |
+
logits = logits.scatter(1, generated, score)
|
| 313 |
+
return logits
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
def sample_next_token(logits: torch.Tensor, temperature: float = 1.0,
|
| 317 |
+
top_k: int | None = None, top_p: float = 1.0) -> torch.Tensor:
|
| 318 |
+
"""
|
| 319 |
+
Sample one token per row from [B, V] logits with temperature, top-k, top-p.
|
| 320 |
+
Returns [B, 1] long tensor.
|
| 321 |
+
"""
|
| 322 |
+
logits = logits / max(temperature, 1e-8)
|
| 323 |
+
|
| 324 |
+
if top_k is not None and top_k > 0:
|
| 325 |
+
k = min(top_k, logits.size(-1))
|
| 326 |
+
v, _ = torch.topk(logits, k)
|
| 327 |
+
logits = logits.masked_fill(logits < v[:, [-1]], float('-inf'))
|
| 328 |
+
|
| 329 |
+
if top_p < 1.0:
|
| 330 |
+
sorted_logits, sorted_idx = torch.sort(logits, descending=True)
|
| 331 |
+
cum = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)
|
| 332 |
+
remove = cum > top_p
|
| 333 |
+
remove[..., 1:] = remove[..., :-1].clone()
|
| 334 |
+
remove[..., 0] = 0
|
| 335 |
+
mask = remove.scatter(1, sorted_idx, remove)
|
| 336 |
+
logits = logits.masked_fill(mask, float('-inf'))
|
| 337 |
+
|
| 338 |
+
probs = F.softmax(logits, dim=-1)
|
| 339 |
+
return torch.multinomial(probs, num_samples=1)
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
@torch.no_grad()
|
| 343 |
+
def simple_ar_generate(model, prompt, max_len=100, temperature=1.0,
|
| 344 |
+
top_k=None, top_p=1.0, repetition_penalty=1.0,
|
| 345 |
+
token_end=None, verbose=False):
|
| 346 |
+
"""
|
| 347 |
+
Dumb autoregressive generation for debugging.
|
| 348 |
+
|
| 349 |
+
Runs a full forward pass on the whole sequence each step, takes logits at the
|
| 350 |
+
last position, samples, appends. O(L^2) total — useless for big models, but it
|
| 351 |
+
works on *anything* whose forward(ids) returns [B, L, V] logits, with no
|
| 352 |
+
assumptions about KV caches, custom generate(), pos encoders, ve, skips, etc.
|
| 353 |
+
Good as a ground-truth reference when debugging more sophisticated generators.
|
| 354 |
+
"""
|
| 355 |
+
flag("simple_ar_generate is O(L^2) and ignores KV caches: use it for debugging, "
|
| 356 |
+
"not for real inference. If your model has a fast .generate(), prefer that.", 2)
|
| 357 |
+
model.eval()
|
| 358 |
+
if prompt.dim() == 1:
|
| 359 |
+
prompt = prompt.unsqueeze(0)
|
| 360 |
+
device = next(model.parameters()).device
|
| 361 |
+
generated = prompt.clone().to(device)
|
| 362 |
+
|
| 363 |
+
for _ in range(max_len):
|
| 364 |
+
logits = model(generated)
|
| 365 |
+
next_logits = logits[:, -1, :]
|
| 366 |
+
|
| 367 |
+
if repetition_penalty != 1.0:
|
| 368 |
+
next_logits = apply_repetition_penalty(next_logits, generated, repetition_penalty)
|
| 369 |
+
|
| 370 |
+
next_token = sample_next_token(next_logits, temperature, top_k, top_p)
|
| 371 |
+
generated = torch.cat([generated, next_token], dim=1)
|
| 372 |
+
|
| 373 |
+
if verbose:
|
| 374 |
+
print(next_token.flatten().tolist())
|
| 375 |
+
|
| 376 |
+
if token_end is not None and (next_token == token_end).all():
|
| 377 |
+
break
|
| 378 |
+
|
| 379 |
+
return generated
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
def toeplitz_init(tensor: torch.Tensor, alpha: float, causal: bool = True, mul=0.1):
|
| 383 |
+
"""
|
| 384 |
+
Initializes a square 2D tensor to a custom Toeplitz matrix with decaying kernels
|
| 385 |
+
generated from alpha^t for t=1...L, where L is the side length of the matrix.
|
| 386 |
+
|
| 387 |
+
If causal is True, the matrix is lower triangular (tril), containing values only
|
| 388 |
+
where row index i >= column index j.
|
| 389 |
+
|
| 390 |
+
Args:
|
| 391 |
+
tensor (torch.Tensor): The square 2D tensor to initialize in place.
|
| 392 |
+
alpha (float): The base for the exponential decay.
|
| 393 |
+
causal (bool, optional): If True, makes the matrix lower triangular. Defaults to False.
|
| 394 |
+
|
| 395 |
+
Returns:
|
| 396 |
+
torch.Tensor: The initialized tensor (modified in place).
|
| 397 |
+
"""
|
| 398 |
+
if tensor.dim() != 2 or tensor.size(0) != tensor.size(1):
|
| 399 |
+
raise ValueError("Tensor must be a square 2D tensor.")
|
| 400 |
+
|
| 401 |
+
L = tensor.size(0)
|
| 402 |
+
i, j = torch.meshgrid(
|
| 403 |
+
torch.arange(L, device=tensor.device, dtype=tensor.dtype),
|
| 404 |
+
torch.arange(L, device=tensor.device, dtype=tensor.dtype),
|
| 405 |
+
indexing='ij'
|
| 406 |
+
)
|
| 407 |
+
dist = torch.abs(i - j)
|
| 408 |
+
t = dist + 1
|
| 409 |
+
matrix = alpha ** t
|
| 410 |
+
|
| 411 |
+
if causal:
|
| 412 |
+
matrix = torch.where(i >= j, matrix, torch.tensor(0.0, dtype=tensor.dtype, device=tensor.device))
|
| 413 |
+
|
| 414 |
+
with torch.no_grad():
|
| 415 |
+
tensor.copy_(matrix)
|
| 416 |
+
|
| 417 |
+
return tensor * mul
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
def zero_layer_mtp_loss( # TODO: Vibecoded for now
|
| 421 |
+
logits: torch.Tensor,
|
| 422 |
+
targets: torch.Tensor,
|
| 423 |
+
mtp_weights: list[float],
|
| 424 |
+
ignore_index: int = -100,
|
| 425 |
+
softcap_val: float = 30.0
|
| 426 |
+
) -> torch.Tensor:
|
| 427 |
+
"""
|
| 428 |
+
Production-grade 0-layer MTP loss.
|
| 429 |
+
|
| 430 |
+
Optimizations:
|
| 431 |
+
1. Computes LogSumExp exactly once per token.
|
| 432 |
+
2. Uses index gathering for target logits to minimize memory bandwidth.
|
| 433 |
+
3. Casts to FP32 for numerical stability before reduction.
|
| 434 |
+
"""
|
| 435 |
+
assert sum(mtp_weights) == 1, "the sum of MTP weights must be 1.0"
|
| 436 |
+
|
| 437 |
+
if softcap_val > 0.0:
|
| 438 |
+
logits = softcap_val * torch.tanh(logits / softcap_val)
|
| 439 |
+
|
| 440 |
+
lse = torch.logsumexp(logits, dim=-1).float()
|
| 441 |
+
|
| 442 |
+
total_loss = torch.tensor(0.0, device=logits.device, dtype=torch.float32)
|
| 443 |
+
|
| 444 |
+
for k, w in enumerate(mtp_weights):
|
| 445 |
+
if w <= 0.0:
|
| 446 |
+
continue
|
| 447 |
+
|
| 448 |
+
if k == 0:
|
| 449 |
+
valid_logits = logits
|
| 450 |
+
valid_targets = targets
|
| 451 |
+
valid_lse = lse
|
| 452 |
+
else:
|
| 453 |
+
valid_logits = logits[:, :-k]
|
| 454 |
+
valid_targets = targets[:, k:]
|
| 455 |
+
valid_lse = lse[:, :-k]
|
| 456 |
+
|
| 457 |
+
mask = (valid_targets != ignore_index)
|
| 458 |
+
|
| 459 |
+
safe_targets = torch.where(mask, valid_targets, torch.zeros_like(valid_targets))
|
| 460 |
+
|
| 461 |
+
target_logits = valid_logits.gather(
|
| 462 |
+
dim=-1,
|
| 463 |
+
index=safe_targets.unsqueeze(-1)
|
| 464 |
+
).squeeze(-1).float()
|
| 465 |
+
|
| 466 |
+
ce_loss = valid_lse - target_logits
|
| 467 |
+
|
| 468 |
+
masked_loss = ce_loss * mask
|
| 469 |
+
num_valid_tokens = mask.sum().clamp(min=1)
|
| 470 |
+
loss_k = masked_loss.sum() / num_valid_tokens
|
| 471 |
+
|
| 472 |
+
total_loss += w * loss_k
|
| 473 |
+
|
| 474 |
+
return total_loss
|
| 475 |
+
|
| 476 |
+
|
| 477 |
+
def plot_tensor_diagnostics(
|
| 478 |
+
name: str,
|
| 479 |
+
tensor: torch.Tensor,
|
| 480 |
+
on_grad: bool = False,
|
| 481 |
+
top_k_svd: int | None = None,
|
| 482 |
+
bins: int = 100
|
| 483 |
+
):
|
| 484 |
+
|
| 485 |
+
has_grad = on_grad and hasattr(tensor, 'grad') and tensor.grad is not None
|
| 486 |
+
rows = 2 if has_grad else 1
|
| 487 |
+
fig, axes = plt.subplots(rows, 2, figsize=(12, 5 * rows))
|
| 488 |
+
if rows == 1:
|
| 489 |
+
axes = np.expand_dims(axes, axis=0) # standardize indexing
|
| 490 |
+
|
| 491 |
+
def _compute_and_plot(t: torch.Tensor, row: int, title_prefix: str):
|
| 492 |
+
t_cpu = t.detach().float().cpu()
|
| 493 |
+
|
| 494 |
+
if t_cpu.ndim == 1:
|
| 495 |
+
mat = t_cpu.unsqueeze(0)
|
| 496 |
+
else:
|
| 497 |
+
mat = t_cpu.view(t_cpu.size(0), -1)
|
| 498 |
+
|
| 499 |
+
s_vals = torch.linalg.svdvals(mat).numpy()
|
| 500 |
+
if top_k_svd is not None:
|
| 501 |
+
s_vals = s_vals[:top_k_svd]
|
| 502 |
+
|
| 503 |
+
ax_svd = axes[row, 0]
|
| 504 |
+
ax_svd.plot(s_vals, marker='.', linestyle='-', color='b', markersize=4)
|
| 505 |
+
ax_svd.set_title(f"{title_prefix} Singular Values\nMax: {s_vals[0]:.4f}, Min: {s_vals[-1]:.4f}")
|
| 506 |
+
ax_svd.set_xlabel("Index")
|
| 507 |
+
ax_svd.set_ylabel("Singular Value $\sigma_i$")
|
| 508 |
+
ax_svd.set_yscale('log') # Log scale is crucial to see the long tail
|
| 509 |
+
ax_svd.grid(True, which="both", ls="--", alpha=0.5)
|
| 510 |
+
|
| 511 |
+
ax_hist = axes[row, 1]
|
| 512 |
+
vals_flat = t_cpu.numpy().flatten()
|
| 513 |
+
ax_hist.hist(vals_flat, bins=bins, color='g', alpha=0.7, log=True)
|
| 514 |
+
mean, std = vals_flat.mean(), vals_flat.std()
|
| 515 |
+
ax_hist.set_title(f"{title_prefix} Distribution\n$\mu$: {mean:.2e}, $\sigma$: {std:.2e}")
|
| 516 |
+
ax_hist.set_xlabel("Value")
|
| 517 |
+
ax_hist.set_ylabel("Count (Log Scale)")
|
| 518 |
+
ax_hist.grid(True, ls="--", alpha=0.5)
|
| 519 |
+
|
| 520 |
+
_compute_and_plot(tensor, row=0, title_prefix=f"Weight: {name}")
|
| 521 |
+
|
| 522 |
+
if has_grad:
|
| 523 |
+
_compute_and_plot(tensor.grad, row=1, title_prefix=f"Gradient: {name}")
|
| 524 |
+
elif on_grad:
|
| 525 |
+
print(f"Warning: on_grad=True for '{name}', but tensor.grad is None.")
|
| 526 |
+
|
| 527 |
+
plt.tight_layout()
|
| 528 |
+
plt.show()
|
| 529 |
+
|
| 530 |
+
|
| 531 |
+
import math
|
| 532 |
+
|
| 533 |
+
def compute_normalized_grad_norm(model: torch.nn.Module) -> float:
|
| 534 |
+
"""
|
| 535 |
+
Returns the RMS gradient norm across all trainable parameters.
|
| 536 |
+
|
| 537 |
+
RMS = sqrt( sum(g_i^2) / N ) where N = total number of scalar gradients.
|
| 538 |
+
|
| 539 |
+
Because this *averages* rather than sums, the result is independent of
|
| 540 |
+
model size: a 1 M-param and a 100 M-param model both live in [0, ~1]
|
| 541 |
+
during healthy training, making cross-run comparisons meaningful.
|
| 542 |
+
|
| 543 |
+
Returns 0.0 if no gradients are present yet.
|
| 544 |
+
"""
|
| 545 |
+
total_sq = 0.0
|
| 546 |
+
total_n = 0
|
| 547 |
+
|
| 548 |
+
for p in model.parameters():
|
| 549 |
+
if p.grad is not None:
|
| 550 |
+
total_sq += p.grad.detach().float().pow(2).sum().item()
|
| 551 |
+
total_n += p.grad.numel()
|
| 552 |
+
|
| 553 |
+
if total_n == 0:
|
| 554 |
+
return 0.0
|
| 555 |
+
|
| 556 |
+
return math.sqrt(total_sq / total_n)
|
| 557 |
+
|
| 558 |
+
|
| 559 |
+
def compute_layer_grad_norms(model: torch.nn.Module) -> dict[str, float]:
|
| 560 |
+
"""
|
| 561 |
+
Returns a dict { layer_name -> RMS grad norm } for every named module
|
| 562 |
+
that owns at least one parameter with a gradient.
|
| 563 |
+
|
| 564 |
+
Same RMS normalisation as compute_normalized_grad_norm so per-layer
|
| 565 |
+
values are still comparable across models of different sizes.
|
| 566 |
+
Leaf modules only (skips container modules to avoid double-counting).
|
| 567 |
+
"""
|
| 568 |
+
norms = {}
|
| 569 |
+
|
| 570 |
+
for name, module in model.named_modules():
|
| 571 |
+
# only leaf modules that directly own parameters
|
| 572 |
+
own_params = list(module.parameters(recurse=False))
|
| 573 |
+
if not own_params:
|
| 574 |
+
continue
|
| 575 |
+
|
| 576 |
+
sq_sum = 0.0
|
| 577 |
+
n = 0
|
| 578 |
+
for p in own_params:
|
| 579 |
+
if p.grad is not None:
|
| 580 |
+
sq_sum += p.grad.detach().float().pow(2).sum().item()
|
| 581 |
+
n += p.grad.numel()
|
| 582 |
+
|
| 583 |
+
if n > 0:
|
| 584 |
+
norms[name] = math.sqrt(sq_sum / n)
|
| 585 |
+
|
| 586 |
+
return norms
|
vathos/mamba.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from Vathos.blocks import *
|
| 2 |
+
|
| 3 |
+
try:
|
| 4 |
+
from mamba_ssm import Mamba
|
| 5 |
+
except ImportError:
|
| 6 |
+
raise ImportError("If you're trying to use Mamba, please install mamba-ssm, via pip install mamba-ssm. \n"
|
| 7 |
+
"probably you'll need to also install the right torch version:\n"
|
| 8 |
+
"pip install -q -U torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu126")
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class MambaMixer(Layer):
|
| 12 |
+
def __init__(self, d_model, expand=4, d_state=64, k=3):
|
| 13 |
+
super(MambaMixer).__init__()
|
| 14 |
+
self.expand = expand
|
| 15 |
+
self.d_model = d_model
|
| 16 |
+
self.d_state = d_state
|
| 17 |
+
self.k = k
|
| 18 |
+
self.mamba = Mamba(
|
| 19 |
+
d_model=d_model,
|
| 20 |
+
d_state=d_state,
|
| 21 |
+
d_conv=d_state,
|
| 22 |
+
expand=expand,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
def forward(self, x):
|
| 26 |
+
x = self.mamba(x)
|
| 27 |
+
return x
|
vathos/optimizers.py
ADDED
|
@@ -0,0 +1,518 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import random
|
| 2 |
+
import torch
|
| 3 |
+
import torch.optim as optim
|
| 4 |
+
from Vathos.functions import flag
|
| 5 |
+
from torch.optim import Optimizer, AdamW, SGD
|
| 6 |
+
|
| 7 |
+
try:
|
| 8 |
+
from muon import Muon
|
| 9 |
+
except ImportError:
|
| 10 |
+
flag("Muon library is not installed, usage of Muon dependent optimizer will not be possible", 2)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class ValueScheduler:
|
| 14 |
+
def __init__(self):
|
| 15 |
+
self.step = 0
|
| 16 |
+
self.value = 0
|
| 17 |
+
self.functions = {}
|
| 18 |
+
self.values = []
|
| 19 |
+
self.compiled = False
|
| 20 |
+
|
| 21 |
+
def set(self, f, l=0, u=float('inf')):
|
| 22 |
+
assert l < u
|
| 23 |
+
self.functions[(l, u)] = f
|
| 24 |
+
|
| 25 |
+
def compile(self, l, m):
|
| 26 |
+
if self.compiled:
|
| 27 |
+
flag("ValueScheduler Already Compiled, recompiling...")
|
| 28 |
+
self.values = []
|
| 29 |
+
for l, u in self.functions:
|
| 30 |
+
i = l
|
| 31 |
+
f = self.functions[(l, u)]
|
| 32 |
+
while i <= u:
|
| 33 |
+
self.values.append(f(i))
|
| 34 |
+
|
| 35 |
+
def get(self, step):
|
| 36 |
+
assert self.compiled
|
| 37 |
+
return self.values[step]
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class SignSGD(optim.Optimizer):
|
| 41 |
+
|
| 42 |
+
def __init__(self, params, lr=0.01, rand_zero=True):
|
| 43 |
+
defaults = dict(lr=lr, rand_zero=rand_zero)
|
| 44 |
+
super().__init__(params, defaults)
|
| 45 |
+
|
| 46 |
+
@torch.no_grad()
|
| 47 |
+
def step(self, closure=None):
|
| 48 |
+
loss = None
|
| 49 |
+
if closure is not None:
|
| 50 |
+
with torch.enable_grad():
|
| 51 |
+
loss = closure()
|
| 52 |
+
|
| 53 |
+
for group in self.param_groups:
|
| 54 |
+
lr = group['lr']
|
| 55 |
+
rand_zero = group['rand_zero']
|
| 56 |
+
|
| 57 |
+
for p in group['params']:
|
| 58 |
+
if p.grad is None:
|
| 59 |
+
continue
|
| 60 |
+
|
| 61 |
+
grad = torch.sign(p.grad)
|
| 62 |
+
|
| 63 |
+
if rand_zero:
|
| 64 |
+
zero_mask = (grad == 0)
|
| 65 |
+
if zero_mask.any():
|
| 66 |
+
grad[zero_mask] = torch.randint(
|
| 67 |
+
0, 2, (zero_mask.sum().item(),),
|
| 68 |
+
dtype=grad.dtype,
|
| 69 |
+
device=grad.device
|
| 70 |
+
) * 2 - 1
|
| 71 |
+
|
| 72 |
+
p.add_(grad, alpha=-lr)
|
| 73 |
+
|
| 74 |
+
return loss
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
"""
|
| 78 |
+
stochastic_sign_muon.py
|
| 79 |
+
=======================
|
| 80 |
+
StochasticSignMuon: per-step stochastic interpolation between Muon and SignSGD
|
| 81 |
+
for 2-D hidden weight matrices, with AdamW for everything else.
|
| 82 |
+
|
| 83 |
+
Design principle
|
| 84 |
+
----------------
|
| 85 |
+
Both Muon and SignSGD operate on the *same* Nesterov momentum buffer:
|
| 86 |
+
|
| 87 |
+
buf ← β·buf + (1-β)·grad # EMA (identical for both)
|
| 88 |
+
pre = (1-β)·grad + β·buf # Nesterov blend (identical for both)
|
| 89 |
+
|
| 90 |
+
Muon branch : update = NS(pre) · scale # orthogonalise
|
| 91 |
+
Sign branch : update = sign(pre) / √n # sign-normalised
|
| 92 |
+
|
| 93 |
+
Because the momentum path is fully shared, τ can be freely scheduled — or even
|
| 94 |
+
changed every step — without resetting or corrupting any state.
|
| 95 |
+
|
| 96 |
+
Frobenius-norm alignment
|
| 97 |
+
------------------------
|
| 98 |
+
Muon's NS output has F-norm ≈ √m for an m×n matrix (m = rows).
|
| 99 |
+
sign(pre) has F-norm = √(m·n).
|
| 100 |
+
Dividing by √n makes the Sign branch produce the same F-norm as Muon,
|
| 101 |
+
so the learning rate carries the same meaning in both branches.
|
| 102 |
+
|
| 103 |
+
Usage (single device)
|
| 104 |
+
---------------------
|
| 105 |
+
from stochastic_sign_muon import SingleDeviceStochasticSignMuon
|
| 106 |
+
|
| 107 |
+
hidden_weights = [p for p in model.body.parameters() if p.ndim >= 2]
|
| 108 |
+
rest = (
|
| 109 |
+
[p for p in model.body.parameters() if p.ndim < 2]
|
| 110 |
+
+ list(model.head.parameters())
|
| 111 |
+
+ list(model.embed.parameters())
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
optimizer = SingleDeviceStochasticSignMuon(
|
| 115 |
+
[
|
| 116 |
+
dict(params=hidden_weights, use_muon=True, lr=0.02, weight_decay=0.01),
|
| 117 |
+
dict(params=rest, use_muon=False, lr=3e-4, betas=(0.9, 0.95),
|
| 118 |
+
weight_decay=0.01),
|
| 119 |
+
],
|
| 120 |
+
tau=0.0, # start as pure Muon; schedule upward to inject Sign noise
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
# Inside the training loop you can change tau at any time:
|
| 124 |
+
optimizer.set_tau(0.3) # 30 % of steps will now be Sign steps
|
| 125 |
+
"""
|
| 126 |
+
|
| 127 |
+
import torch
|
| 128 |
+
import torch.distributed as dist
|
| 129 |
+
try:
|
| 130 |
+
from muon import zeropower_via_newtonschulz5, adam_update
|
| 131 |
+
except ImportError:
|
| 132 |
+
flag("Unable to import muon, consider downloading it via pip install git+https://github.com/KellerJordan/Muon.git to use Muon backed optimizers")
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
# ---------------------------------------------------------------------------
|
| 136 |
+
# Shared update primitives
|
| 137 |
+
# ---------------------------------------------------------------------------
|
| 138 |
+
|
| 139 |
+
def _nesterov_momentum(
|
| 140 |
+
grad: torch.Tensor,
|
| 141 |
+
buf: torch.Tensor,
|
| 142 |
+
beta: float,
|
| 143 |
+
) -> torch.Tensor:
|
| 144 |
+
"""
|
| 145 |
+
EMA update followed by a Nesterov combination.
|
| 146 |
+
|
| 147 |
+
Modifies `buf` and `grad` in-place (safe inside @torch.no_grad).
|
| 148 |
+
Returns the Nesterov pre-update vector.
|
| 149 |
+
"""
|
| 150 |
+
buf.lerp_(grad, 1.0 - beta) # buf ← β·buf + (1-β)·grad
|
| 151 |
+
return grad.lerp_(buf, beta) # returns (1-β)·grad + β·buf [Nesterov]
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def _muon_branch(pre: torch.Tensor, ns_steps: int = 5) -> torch.Tensor:
|
| 155 |
+
"""
|
| 156 |
+
Newton-Schulz orthogonalisation + anisotropic scaling.
|
| 157 |
+
Output F-norm ≈ √m for an m×n pre-update.
|
| 158 |
+
Returns a tensor of the same shape as `pre`.
|
| 159 |
+
"""
|
| 160 |
+
u = pre.view(len(pre), -1) if pre.ndim == 4 else pre
|
| 161 |
+
u = zeropower_via_newtonschulz5(u, steps=ns_steps)
|
| 162 |
+
u = u * max(1.0, u.size(-2) / u.size(-1)) ** 0.5
|
| 163 |
+
return u
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def _sign_branch(pre: torch.Tensor, rand_zero: bool = True) -> torch.Tensor:
|
| 167 |
+
"""
|
| 168 |
+
Signed update normalised to match Muon's output F-norm.
|
| 169 |
+
|
| 170 |
+
sign(pre) has F-norm √(m·n). Dividing by √n yields √m, matching Muon.
|
| 171 |
+
Zero entries are randomised to ±1 when rand_zero=True.
|
| 172 |
+
Returns a tensor of the same shape as `pre`.
|
| 173 |
+
"""
|
| 174 |
+
u = pre.view(len(pre), -1) if pre.ndim == 4 else pre
|
| 175 |
+
|
| 176 |
+
s = torch.sign(u)
|
| 177 |
+
|
| 178 |
+
if rand_zero:
|
| 179 |
+
zero_mask = s == 0
|
| 180 |
+
if zero_mask.any():
|
| 181 |
+
s[zero_mask] = (
|
| 182 |
+
torch.randint(
|
| 183 |
+
0, 2,
|
| 184 |
+
(zero_mask.sum().item(),),
|
| 185 |
+
dtype=u.dtype,
|
| 186 |
+
device=u.device,
|
| 187 |
+
) * 2 - 1
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
# Normalise: √(m·n) / √n = √m → same F-norm as the Muon branch
|
| 191 |
+
s = s / (u.size(-1) ** 0.5)
|
| 192 |
+
return s
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
# ---------------------------------------------------------------------------
|
| 196 |
+
# Single-device variant
|
| 197 |
+
# ---------------------------------------------------------------------------
|
| 198 |
+
|
| 199 |
+
class SingleDeviceStochasticSignMuon(torch.optim.Optimizer):
|
| 200 |
+
"""
|
| 201 |
+
Non-distributed stochastic Muon ↔ SignSGD interpolation with AdamW aux.
|
| 202 |
+
|
| 203 |
+
Parameters
|
| 204 |
+
----------
|
| 205 |
+
param_groups : list[dict]
|
| 206 |
+
Each group must contain ``use_muon`` (bool).
|
| 207 |
+
|
| 208 |
+
use_muon=True groups recognise: lr, momentum, weight_decay
|
| 209 |
+
use_muon=False groups recognise: lr, betas, eps, weight_decay
|
| 210 |
+
|
| 211 |
+
tau : float
|
| 212 |
+
Probability of taking a Sign step instead of a Muon step. [0, 1].
|
| 213 |
+
0.0 → pure Muon (default). 1.0 → pure SignSGD.
|
| 214 |
+
Schedule via :meth:`set_tau`. No state reset required.
|
| 215 |
+
|
| 216 |
+
sign_lr_scale : float
|
| 217 |
+
Multiplicative downscale applied to lr when the Sign branch fires.
|
| 218 |
+
Compensates for Sign's lower signal-to-noise ratio vs Muon at equal
|
| 219 |
+
F-norm. Default 0.1; tune in [0.05, 0.2].
|
| 220 |
+
Schedule via :meth:`set_sign_lr_scale`. No state reset required.
|
| 221 |
+
Weight decay is always applied at the base group lr regardless.
|
| 222 |
+
|
| 223 |
+
rand_zero : bool
|
| 224 |
+
Randomise zero-gradient sign entries in the Sign branch (default True).
|
| 225 |
+
|
| 226 |
+
nesterov : bool
|
| 227 |
+
Use Nesterov momentum (default True, matching canonical Muon).
|
| 228 |
+
|
| 229 |
+
ns_steps : int
|
| 230 |
+
Newton-Schulz iteration count for the Muon branch (default 5).
|
| 231 |
+
"""
|
| 232 |
+
|
| 233 |
+
def __init__(
|
| 234 |
+
self,
|
| 235 |
+
param_groups,
|
| 236 |
+
tau: float = 0.0,
|
| 237 |
+
sign_lr_scale: float = 0.1,
|
| 238 |
+
rand_zero: bool = True,
|
| 239 |
+
nesterov: bool = True,
|
| 240 |
+
ns_steps: int = 5,
|
| 241 |
+
):
|
| 242 |
+
assert 0.0 <= tau <= 1.0, f"tau must be in [0, 1], got {tau}"
|
| 243 |
+
assert sign_lr_scale > 0.0, f"sign_lr_scale must be > 0, got {sign_lr_scale}"
|
| 244 |
+
|
| 245 |
+
self.tau = tau
|
| 246 |
+
self.sign_lr_scale = sign_lr_scale
|
| 247 |
+
self.rand_zero = rand_zero
|
| 248 |
+
self.nesterov = nesterov
|
| 249 |
+
self.ns_steps = ns_steps
|
| 250 |
+
|
| 251 |
+
for group in param_groups:
|
| 252 |
+
assert "use_muon" in group, "Every param group must have a 'use_muon' key."
|
| 253 |
+
if group["use_muon"]:
|
| 254 |
+
group.setdefault("lr", 0.02)
|
| 255 |
+
group.setdefault("momentum", 0.95)
|
| 256 |
+
group.setdefault("weight_decay", 0.0)
|
| 257 |
+
allowed = {"params", "lr", "momentum", "weight_decay", "use_muon"}
|
| 258 |
+
assert set(group.keys()) == allowed, (
|
| 259 |
+
f"Unexpected keys in Muon group: {set(group.keys()) - allowed}"
|
| 260 |
+
)
|
| 261 |
+
else:
|
| 262 |
+
group.setdefault("lr", 3e-4)
|
| 263 |
+
group.setdefault("betas", (0.9, 0.95))
|
| 264 |
+
group.setdefault("eps", 1e-10)
|
| 265 |
+
group.setdefault("weight_decay", 0.0)
|
| 266 |
+
allowed = {"params", "lr", "betas", "eps", "weight_decay", "use_muon"}
|
| 267 |
+
assert set(group.keys()) == allowed, (
|
| 268 |
+
f"Unexpected keys in Adam group: {set(group.keys()) - allowed}"
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
super().__init__(param_groups, {})
|
| 272 |
+
|
| 273 |
+
# ------------------------------------------------------------------
|
| 274 |
+
# Scheduling API
|
| 275 |
+
# ------------------------------------------------------------------
|
| 276 |
+
|
| 277 |
+
def set_tau(self, tau: float) -> None:
|
| 278 |
+
"""
|
| 279 |
+
Set the Muon ↔ Sign mixing probability.
|
| 280 |
+
Safe to call at any point; does not reset any optimizer state.
|
| 281 |
+
"""
|
| 282 |
+
assert 0.0 <= tau <= 1.0, f"tau must be in [0, 1], got {tau}"
|
| 283 |
+
self.tau = tau
|
| 284 |
+
|
| 285 |
+
def set_sign_lr_scale(self, scale: float) -> None:
|
| 286 |
+
"""
|
| 287 |
+
Set the lr multiplier for Sign steps.
|
| 288 |
+
Safe to call at any point; does not reset any optimizer state.
|
| 289 |
+
"""
|
| 290 |
+
assert scale > 0.0, f"sign_lr_scale must be > 0, got {scale}"
|
| 291 |
+
self.sign_lr_scale = scale
|
| 292 |
+
|
| 293 |
+
# ------------------------------------------------------------------
|
| 294 |
+
# Step
|
| 295 |
+
# ------------------------------------------------------------------
|
| 296 |
+
|
| 297 |
+
@torch.no_grad()
|
| 298 |
+
def step(self, closure=None):
|
| 299 |
+
loss = None
|
| 300 |
+
if closure is not None:
|
| 301 |
+
with torch.enable_grad():
|
| 302 |
+
loss = closure()
|
| 303 |
+
|
| 304 |
+
# One global Bernoulli draw: all 2-D params follow the same branch this step.
|
| 305 |
+
use_sign: bool = (torch.rand(1).item() < self.tau)
|
| 306 |
+
|
| 307 |
+
for group in self.param_groups:
|
| 308 |
+
|
| 309 |
+
if group["use_muon"]:
|
| 310 |
+
beta = group["momentum"]
|
| 311 |
+
base_lr = group["lr"]
|
| 312 |
+
effective_lr = base_lr * self.sign_lr_scale if use_sign else base_lr
|
| 313 |
+
|
| 314 |
+
for p in group["params"]:
|
| 315 |
+
if p.grad is None:
|
| 316 |
+
p.grad = torch.zeros_like(p)
|
| 317 |
+
|
| 318 |
+
state = self.state[p]
|
| 319 |
+
if not state:
|
| 320 |
+
state["momentum_buffer"] = torch.zeros_like(p)
|
| 321 |
+
|
| 322 |
+
# Shared momentum step (identical regardless of branch)
|
| 323 |
+
pre = _nesterov_momentum(p.grad, state["momentum_buffer"], beta)
|
| 324 |
+
|
| 325 |
+
# Branch selection
|
| 326 |
+
if use_sign:
|
| 327 |
+
update = _sign_branch(pre, rand_zero=self.rand_zero)
|
| 328 |
+
else:
|
| 329 |
+
update = _muon_branch(pre, ns_steps=self.ns_steps)
|
| 330 |
+
|
| 331 |
+
# Weight decay at base lr; parameter update at effective lr
|
| 332 |
+
p.mul_(1.0 - base_lr * group["weight_decay"])
|
| 333 |
+
p.add_(update.reshape(p.shape), alpha=-effective_lr)
|
| 334 |
+
|
| 335 |
+
else: # AdamW
|
| 336 |
+
for p in group["params"]:
|
| 337 |
+
if p.grad is None:
|
| 338 |
+
p.grad = torch.zeros_like(p)
|
| 339 |
+
|
| 340 |
+
state = self.state[p]
|
| 341 |
+
if not state:
|
| 342 |
+
state["exp_avg"] = torch.zeros_like(p)
|
| 343 |
+
state["exp_avg_sq"] = torch.zeros_like(p)
|
| 344 |
+
state["step"] = 0
|
| 345 |
+
|
| 346 |
+
state["step"] += 1
|
| 347 |
+
update = adam_update(
|
| 348 |
+
p.grad,
|
| 349 |
+
state["exp_avg"],
|
| 350 |
+
state["exp_avg_sq"],
|
| 351 |
+
state["step"],
|
| 352 |
+
group["betas"],
|
| 353 |
+
group["eps"],
|
| 354 |
+
)
|
| 355 |
+
p.mul_(1.0 - group["lr"] * group["weight_decay"])
|
| 356 |
+
p.add_(update, alpha=-group["lr"])
|
| 357 |
+
|
| 358 |
+
return loss
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
# ---------------------------------------------------------------------------
|
| 362 |
+
# Distributed variant
|
| 363 |
+
# ---------------------------------------------------------------------------
|
| 364 |
+
|
| 365 |
+
class StochasticSignMuon(torch.optim.Optimizer):
|
| 366 |
+
"""
|
| 367 |
+
Distributed stochastic Muon ↔ SignSGD interpolation with AdamW aux.
|
| 368 |
+
|
| 369 |
+
Requires an initialised ``torch.distributed`` process group.
|
| 370 |
+
Parameters are sorted by size and sharded across ranks exactly as in the
|
| 371 |
+
original ``MuonWithAuxAdam``.
|
| 372 |
+
|
| 373 |
+
The Bernoulli draw is made on rank 0 and broadcast to all ranks so that
|
| 374 |
+
every GPU takes the same branch on every step.
|
| 375 |
+
|
| 376 |
+
See ``SingleDeviceStochasticSignMuon`` for the full parameter docstring.
|
| 377 |
+
"""
|
| 378 |
+
|
| 379 |
+
def __init__(
|
| 380 |
+
self,
|
| 381 |
+
param_groups,
|
| 382 |
+
tau: float = 0.0,
|
| 383 |
+
sign_lr_scale: float = 0.1,
|
| 384 |
+
rand_zero: bool = True,
|
| 385 |
+
nesterov: bool = True,
|
| 386 |
+
ns_steps: int = 5,
|
| 387 |
+
):
|
| 388 |
+
assert 0.0 <= tau <= 1.0, f"tau must be in [0, 1], got {tau}"
|
| 389 |
+
assert sign_lr_scale > 0.0, f"sign_lr_scale must be > 0, got {sign_lr_scale}"
|
| 390 |
+
|
| 391 |
+
self.tau = tau
|
| 392 |
+
self.sign_lr_scale = sign_lr_scale
|
| 393 |
+
self.rand_zero = rand_zero
|
| 394 |
+
self.nesterov = nesterov
|
| 395 |
+
self.ns_steps = ns_steps
|
| 396 |
+
|
| 397 |
+
for group in param_groups:
|
| 398 |
+
assert "use_muon" in group, "Every param group must have a 'use_muon' key."
|
| 399 |
+
if group["use_muon"]:
|
| 400 |
+
group["params"] = sorted(
|
| 401 |
+
group["params"], key=lambda x: x.size(), reverse=True
|
| 402 |
+
)
|
| 403 |
+
group.setdefault("lr", 0.02)
|
| 404 |
+
group.setdefault("momentum", 0.95)
|
| 405 |
+
group.setdefault("weight_decay", 0.0)
|
| 406 |
+
allowed = {"params", "lr", "momentum", "weight_decay", "use_muon"}
|
| 407 |
+
assert set(group.keys()) == allowed
|
| 408 |
+
else:
|
| 409 |
+
group.setdefault("lr", 3e-4)
|
| 410 |
+
group.setdefault("betas", (0.9, 0.95))
|
| 411 |
+
group.setdefault("eps", 1e-10)
|
| 412 |
+
group.setdefault("weight_decay", 0.0)
|
| 413 |
+
allowed = {"params", "lr", "betas", "eps", "weight_decay", "use_muon"}
|
| 414 |
+
assert set(group.keys()) == allowed
|
| 415 |
+
|
| 416 |
+
super().__init__(param_groups, {})
|
| 417 |
+
|
| 418 |
+
# ------------------------------------------------------------------
|
| 419 |
+
# Scheduling API
|
| 420 |
+
# ------------------------------------------------------------------
|
| 421 |
+
|
| 422 |
+
def set_tau(self, tau: float) -> None:
|
| 423 |
+
"""Schedule the Muon ↔ Sign mixing probability. Safe from any rank."""
|
| 424 |
+
assert 0.0 <= tau <= 1.0, f"tau must be in [0, 1], got {tau}"
|
| 425 |
+
self.tau = tau
|
| 426 |
+
|
| 427 |
+
def set_sign_lr_scale(self, scale: float) -> None:
|
| 428 |
+
"""Schedule the lr multiplier for Sign steps. Safe from any rank."""
|
| 429 |
+
assert scale > 0.0, f"sign_lr_scale must be > 0, got {scale}"
|
| 430 |
+
self.sign_lr_scale = scale
|
| 431 |
+
|
| 432 |
+
# ------------------------------------------------------------------
|
| 433 |
+
# Step
|
| 434 |
+
# ------------------------------------------------------------------
|
| 435 |
+
|
| 436 |
+
@torch.no_grad()
|
| 437 |
+
def step(self, closure=None):
|
| 438 |
+
loss = None
|
| 439 |
+
if closure is not None:
|
| 440 |
+
with torch.enable_grad():
|
| 441 |
+
loss = closure()
|
| 442 |
+
|
| 443 |
+
world_size = dist.get_world_size()
|
| 444 |
+
rank = dist.get_rank()
|
| 445 |
+
|
| 446 |
+
# Broadcast the Bernoulli draw from rank 0 so every GPU takes the same branch.
|
| 447 |
+
use_sign_t = torch.zeros(1, dtype=torch.float32,
|
| 448 |
+
device=torch.device("cuda", rank))
|
| 449 |
+
if rank == 0:
|
| 450 |
+
use_sign_t[0] = 1.0 if torch.rand(1).item() < self.tau else 0.0
|
| 451 |
+
dist.broadcast(use_sign_t, src=0)
|
| 452 |
+
use_sign: bool = use_sign_t.item() > 0.5
|
| 453 |
+
|
| 454 |
+
for group in self.param_groups:
|
| 455 |
+
|
| 456 |
+
if group["use_muon"]:
|
| 457 |
+
beta = group["momentum"]
|
| 458 |
+
base_lr = group["lr"]
|
| 459 |
+
effective_lr = base_lr * self.sign_lr_scale if use_sign else base_lr
|
| 460 |
+
|
| 461 |
+
params = group["params"]
|
| 462 |
+
params_pad = params + [torch.empty_like(params[-1])] * (
|
| 463 |
+
world_size - len(params) % world_size
|
| 464 |
+
)
|
| 465 |
+
|
| 466 |
+
for base_i in range(0, len(params), world_size):
|
| 467 |
+
local_idx = base_i + rank
|
| 468 |
+
if local_idx < len(params):
|
| 469 |
+
p = params[local_idx]
|
| 470 |
+
if p.grad is None:
|
| 471 |
+
p.grad = torch.zeros_like(p)
|
| 472 |
+
|
| 473 |
+
state = self.state[p]
|
| 474 |
+
if not state:
|
| 475 |
+
state["momentum_buffer"] = torch.zeros_like(p)
|
| 476 |
+
|
| 477 |
+
pre = _nesterov_momentum(
|
| 478 |
+
p.grad, state["momentum_buffer"], beta
|
| 479 |
+
)
|
| 480 |
+
|
| 481 |
+
if use_sign:
|
| 482 |
+
update = _sign_branch(pre, rand_zero=self.rand_zero)
|
| 483 |
+
else:
|
| 484 |
+
update = _muon_branch(pre, ns_steps=self.ns_steps)
|
| 485 |
+
|
| 486 |
+
# Weight decay at base lr; parameter update at effective lr
|
| 487 |
+
p.mul_(1.0 - base_lr * group["weight_decay"])
|
| 488 |
+
p.add_(update.reshape(p.shape), alpha=-effective_lr)
|
| 489 |
+
|
| 490 |
+
dist.all_gather(
|
| 491 |
+
params_pad[base_i: base_i + world_size],
|
| 492 |
+
params_pad[base_i + rank],
|
| 493 |
+
)
|
| 494 |
+
|
| 495 |
+
else: # AdamW
|
| 496 |
+
for p in group["params"]:
|
| 497 |
+
if p.grad is None:
|
| 498 |
+
p.grad = torch.zeros_like(p)
|
| 499 |
+
|
| 500 |
+
state = self.state[p]
|
| 501 |
+
if not state:
|
| 502 |
+
state["exp_avg"] = torch.zeros_like(p)
|
| 503 |
+
state["exp_avg_sq"] = torch.zeros_like(p)
|
| 504 |
+
state["step"] = 0
|
| 505 |
+
|
| 506 |
+
state["step"] += 1
|
| 507 |
+
update = adam_update(
|
| 508 |
+
p.grad,
|
| 509 |
+
state["exp_avg"],
|
| 510 |
+
state["exp_avg_sq"],
|
| 511 |
+
state["step"],
|
| 512 |
+
group["betas"],
|
| 513 |
+
group["eps"],
|
| 514 |
+
)
|
| 515 |
+
p.mul_(1.0 - group["lr"] * group["weight_decay"])
|
| 516 |
+
p.add_(update, alpha=-group["lr"])
|
| 517 |
+
|
| 518 |
+
return loss
|
vathos/training.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from tqdm.notebook import tqdm
|
| 2 |
+
from Vathos.blocks import *
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def symbolic_1d_ar_target_train(model, train_loader, val_loader, optimizer, scheduler, criterion,
|
| 6 |
+
device, NUM_EPOCHS=100, use_amp=False, CLIP_NORM=1.0, div=1):
|
| 7 |
+
scaler = torch.cuda.amp.GradScaler(enabled=use_amp)
|
| 8 |
+
|
| 9 |
+
steps_per_epoch = len(train_loader) // div
|
| 10 |
+
|
| 11 |
+
for epoch in range(NUM_EPOCHS):
|
| 12 |
+
model.train()
|
| 13 |
+
running_loss = 0.0
|
| 14 |
+
pbar = tqdm(train_loader, desc=f"Epoch {epoch + 1}/{NUM_EPOCHS} Train", total=steps_per_epoch)
|
| 15 |
+
steps = 0
|
| 16 |
+
|
| 17 |
+
for batch_x, batch_tgt in pbar:
|
| 18 |
+
batch_x = batch_x.to(device)
|
| 19 |
+
steps += 1
|
| 20 |
+
|
| 21 |
+
optimizer.zero_grad()
|
| 22 |
+
with torch.amp.autocast('cuda'):
|
| 23 |
+
logits = model(batch_x) # [B,S,V]
|
| 24 |
+
loss = criterion(
|
| 25 |
+
logits[:, :-1, :].reshape(-1, logits.size(-1)),
|
| 26 |
+
batch_x[:, 1:].reshape(-1)
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
scaler.scale(loss).backward()
|
| 30 |
+
|
| 31 |
+
scaler.unscale_(optimizer)
|
| 32 |
+
torch.nn.utils.clip_grad_norm_(model.parameters(), CLIP_NORM)
|
| 33 |
+
scaler.step(optimizer)
|
| 34 |
+
scaler.update()
|
| 35 |
+
|
| 36 |
+
scheduler.step()
|
| 37 |
+
|
| 38 |
+
running_loss += loss.item()
|
| 39 |
+
lr_now = optimizer.param_groups[0]['lr']
|
| 40 |
+
pbar.set_postfix({'loss': running_loss / (pbar.n + 1), 'lr': lr_now})
|
| 41 |
+
|
| 42 |
+
if steps >= steps_per_epoch:
|
| 43 |
+
break
|
| 44 |
+
|
| 45 |
+
model.eval()
|
| 46 |
+
val_loss = 0.0
|
| 47 |
+
# with torch.no_grad():
|
| 48 |
+
if True:
|
| 49 |
+
for batch_x, batch_tgt, composer_idx in tqdm(val_loader, desc=f"Epoch {epoch + 1}/{NUM_EPOCHS} Val"):
|
| 50 |
+
batch_x = batch_x.to(device).requires_grad_(False)
|
| 51 |
+
|
| 52 |
+
logits = model(batch_x)
|
| 53 |
+
loss = criterion(
|
| 54 |
+
logits[:, :-1, :].reshape(-1, logits.size(-1)),
|
| 55 |
+
batch_x[:, 1:].reshape(-1)
|
| 56 |
+
)
|
| 57 |
+
val_loss += loss.item()
|
| 58 |
+
if isinstance(model, VathosModel):
|
| 59 |
+
model.module.register_loss(loss.item())
|
| 60 |
+
|
| 61 |
+
val_avg = val_loss / len(val_loader)
|
| 62 |
+
if isinstance(model, VathosModel):
|
| 63 |
+
model.module.register_epoch()
|
| 64 |
+
print(f"Epoch {epoch + 1} val avg loss: {val_avg:.4f}")
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def symbolic_1d_ar_input_ids_train(model, train_loader, val_loader, optimizer, scheduler, criterion,
|
| 68 |
+
device, NUM_EPOCHS=100, use_amp=False, CLIP_NORM=1.0, spe=1000, val_steps=float('inf')):
|
| 69 |
+
scaler = torch.cuda.amp.GradScaler(enabled=use_amp)
|
| 70 |
+
|
| 71 |
+
steps_per_epoch = spe
|
| 72 |
+
|
| 73 |
+
for epoch in range(NUM_EPOCHS):
|
| 74 |
+
model.train()
|
| 75 |
+
running_loss = 0.0
|
| 76 |
+
pbar = tqdm(train_loader, desc=f"Epoch {epoch + 1}/{NUM_EPOCHS} Train", total=steps_per_epoch)
|
| 77 |
+
steps = 0
|
| 78 |
+
|
| 79 |
+
for batch_x in pbar:
|
| 80 |
+
batch_x = batch_x['input_ids'].to(device)
|
| 81 |
+
steps += 1
|
| 82 |
+
|
| 83 |
+
optimizer.zero_grad()
|
| 84 |
+
with torch.amp.autocast('cuda'):
|
| 85 |
+
logits = model(batch_x) # [B,S,V]
|
| 86 |
+
loss = criterion(
|
| 87 |
+
logits[:, :-1, :].reshape(-1, logits.size(-1)),
|
| 88 |
+
batch_x[:, 1:].reshape(-1)
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
scaler.scale(loss).backward()
|
| 92 |
+
|
| 93 |
+
scaler.unscale_(optimizer)
|
| 94 |
+
torch.nn.utils.clip_grad_norm_(model.parameters(), CLIP_NORM)
|
| 95 |
+
scaler.step(optimizer)
|
| 96 |
+
scaler.update()
|
| 97 |
+
if scheduler is not None:
|
| 98 |
+
scheduler.step()
|
| 99 |
+
|
| 100 |
+
running_loss += loss.item()
|
| 101 |
+
lr_now = optimizer.param_groups[0]['lr']
|
| 102 |
+
pbar.set_postfix({'loss': running_loss / (pbar.n + 1), 'lr': lr_now})
|
| 103 |
+
|
| 104 |
+
if steps >= steps_per_epoch:
|
| 105 |
+
break
|
| 106 |
+
|
| 107 |
+
model.eval()
|
| 108 |
+
val_loss = 0.0
|
| 109 |
+
with torch.no_grad():
|
| 110 |
+
# if True:
|
| 111 |
+
val_s = 0
|
| 112 |
+
for batch_x in tqdm(val_loader, desc=f"Epoch {epoch + 1}/{NUM_EPOCHS} Val"):
|
| 113 |
+
batch_x = batch_x['input_ids'].to(device).requires_grad_(False)
|
| 114 |
+
val_s += 1
|
| 115 |
+
if val_s >= val_steps:
|
| 116 |
+
break
|
| 117 |
+
logits = model(batch_x)
|
| 118 |
+
loss = criterion(
|
| 119 |
+
logits[:, :-1, :].reshape(-1, logits.size(-1)),
|
| 120 |
+
batch_x[:, 1:].reshape(-1)
|
| 121 |
+
)
|
| 122 |
+
model.module.register_loss(loss.item())
|
| 123 |
+
|
| 124 |
+
val_avg = model.module.get_mean_loss()
|
| 125 |
+
model.module.register_epoch()
|
| 126 |
+
print(f"Epoch {epoch + 1} val avg loss: {val_avg:.4f}")
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
|