Puntata 207
Puntata 207 — Positional encoding
Livello: 🧙 Maestro Yoda · Capitolo 19 · Architetture
L’attention non sa l’ordine. Per il modello, “il cane morde l’uomo” e “l’uomo morde il cane” sarebbero la stessa frase. Il positional encoding evita la cronaca giudiziaria.
Il problema, in 30 secondi
La self-attention è permutation-invariant: se mescoli l’ordine dei token in input, l’output del blocco è (a meno della permutazione corrispondente) lo stesso. Il modello non sa che token_3 viene dopo token_2.
Soluzione: iniettare informazione di posizione, tipicamente sommandola all’embedding del token prima del primo blocco, oppure modificandola dentro l’attention.
Negli ultimi 8 anni questa apparentemente semplice scelta è stata reinventata 5-6 volte. Vale la pena conoscere le 3 che dominano nel 2026.
1. Positional encoding sinusoidale (paper originale 2017)
L’idea di Vaswani et al.: per ogni posizione pos, costruisci un vettore di dimensione d_model dove le componenti sono sin e cos di varie frequenze:
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
Le componenti spaziano da frequenze altissime (cambia rapidamente) a frequenze bassissime (cambia lentamente). Caratteristiche:
- Determinista: no parametri appresi. Niente da trainare.
- Estrapolabile in teoria: posizioni mai viste in training sono ancora calcolabili.
- Lineare per traslazioni:
PE(pos+k)è una rotazione diPE(pos), fatto che si dimostra utile per relazioni relative.
In pratica, vecchio: faceva fatica oltre il context di training, addestrare un modello a context 2k e farlo girare a 8k dava risultati pessimi.
2. Learned positional embeddings (GPT-1/2, BERT)
Più stupido, ma per anni più efficace: una embedding table di dimensione max_length × d_model con righe imparate come parametri.
pos_emb = nn.Embedding(max_seq_len, d_model)
x = tok_emb + pos_emb(positions)
- Pro: nessuna assunzione, il modello impara la struttura che vuole.
- Contro: non estrapola affatto. Position 4097 non esiste se hai trainato a 4096. Game over.
Tutti i modelli pre-2022 vivevano con questo vincolo. Estendere il context era un’impresa, non un parametro.
3. ALiBi (Press et al., 2021) — attenzione “naturalmente decadente”
Attention with Linear Biases: niente embedding di posizione. Invece, dentro l’attention, sottrai dai logit una penalità proporzionale alla distanza:
attn_logits = Q K^T - m · |pos_i - pos_j|
m è una pendenza fissa, una diversa per ogni testa (preassegnata, non appresa).
- Pro: estrapola sorprendentemente bene. Modello trainato a 1k token spesso funziona ragionevolmente a 4k, 8k.
- Contro: l’attention diventa monotonicamente decadente sulla distanza, perdendo precisione su “lookup chirurgici” lontani.
- Adottato da BLOOM, MPT, Falcon-180B. Poi caduto in disuso quando RoPE ha vinto.
4. RoPE (Su et al., 2021) — il vincitore
Rotary Position Embedding: invece di sommare un vettore di posizione, ruota le componenti di Q e K di un angolo proporzionale alla posizione.
Intuitivamente: tratti (x_{2i}, x_{2i+1}) come un numero complesso e lo moltiplichi per e^(i · θ_pos), una rotazione nel piano.
Tre proprietà magiche:
- L’inner product
Q_i · K_jdipende solo da(i - j): posizioni relative gratis, dalla geometria. - Invariante per traslazione assoluta — sposta tutto in avanti di k token, niente cambia.
- Fatto dentro l’attention, non sull’embedding iniziale: ogni layer rivede la posizione.
Adottato da: Llama (tutti), Mistral, Gemma, DeepSeek, Qwen, GPT-J, GPT-NeoX. Standard di fatto.
def apply_rope(x, pos, theta_base=10000):
# x: (..., d) con d pari
half_d = x.shape[-1] // 2
freqs = theta_base ** (-torch.arange(half_d) / half_d * 2)
angles = pos[..., None] * freqs
cos = angles.cos()
sin = angles.sin()
x1, x2 = x[..., :half_d], x[..., half_d:]
return torch.cat([x1*cos - x2*sin, x1*sin + x2*cos], dim=-1)
RoPE scaling: estendere il context senza retrainare da zero
Qui le cose interessanti del 2023-2026. Llama-2 era a 4k. Per portarlo a 32k senza un altro full training:
Linear / Position Interpolation (Chen et al., Meta 2023)
“Comprimi” linearmente le posizioni dentro l’intervallo trainato: pos → pos / s con s fattore di scala. Veloce, ma perde risoluzione locale.
NTK-aware scaling (community Reddit, poi formalizzato)
Invece di scalare linearmente, modifica theta_base: θ_base × s^(d/(d-2)). Conserva meglio la high-frequency, estende meglio.
YaRN (Peng et al., 2023)
“Yet another RoPE extensioN”: mix più sofisticato che scala frequenze in modo non uniforme + correzione del prompt iniziale. È quello che usa, ad esempio, DeepSeek-V2/V3 per arrivare a 128k.
LongRoPE (Microsoft, 2024)
Cerca automaticamente lo scaling ottimale per ogni dimensione di RoPE via algoritmo evolutivo. Usato in Phi-3 per arrivare a 128k context.
Tutte queste tecniche richiedono di solito un breve fine-tuning di seguito (~1B token sufficienti) per stabilizzare. Nessuna è “gratuita”.
5. NoPE (Kazemnejad et al., 2023) e oltre
In alcuni paper recenti si è dimostrato che modelli decoder-only senza positional encoding comunque imparano la posizione, perché la maschera causale già spezza la simmetria.
Risultato: NoPE può estrapolare meglio dei modelli con RoPE su context molto lunghi, ma in-distribution è di solito peggio. Usato sperimentalmente, non in produzione.
Confronto pratico (2026)
| Schema | Apprende? | Estrapola? | Stato 2026 |
|---|---|---|---|
| Sinusoidal | No | Mediocre | Storico |
| Learned | Sì | No | Solo BERT-era |
| ALiBi | Solo slopes | Sì (decadente) | Marginale |
| RoPE | No | Sì (con scaling) | Standard |
| RoPE + YaRN/LongRoPE | No + scaling | Sì (fino a 128k–1M) | Production |
| NoPE | Niente | Sì | Sperimentale |
Quanto pesa nella pratica?
Per la maggior parte degli sviluppatori applicativi: niente, se usi transformers o un’API. Il positional encoding è scritto nel modello e amen.
Diventa rilevante in 3 scenari:
- Stai estendendo il context oltre quello nativo. Devi conoscere le tecniche di RoPE scaling, e accettare che certe abilità degradino (matematica, retrieval) anche se il modello “non rifiuta” la lunghezza.
- Stai trainando un modello custom. RoPE è la scelta default; ALiBi solo per casi specifici (modelli di streaming infinito).
- Stai debuggando regression su context lunghi. Spesso il problema è proprio nella scelta del
theta_baseo del fattore di scaling.
Glossario lampo
- Position-relative attention — l’attention dipende dalla differenza di posizione, non dalla posizione assoluta.
- Theta base — la “scala temporale” delle frequenze in RoPE. Default 10000, alzato a 500000 o 1M per context molto lunghi (Llama-3.1, Mistral-Large).
- Context extension — pratica di portare un modello al di là del context su cui è stato pretrainato.
TEST hands-on
- In Python, implementa RoPE in 15 righe (cos/sin + rotazione). Verifica che
Q_rot_i · K_rot_jdipenda solo dai - j. Una funzione, due input, due posizioni a coppie diverse ma con stessa differenza → stesso inner product (a meno di noise numerico). - Su HuggingFace, carica
meta-llama/Llama-3.2-3Be ispezionamodel.config.rope_theta. Default è 500000. - Modifica
rope_thetaa 100000 e prova un prompt lungo (es. 8k token). Verifica che la qualità cali drasticamente: hai “scollegato” il modello dalla sua tabella di posizioni. - Bonus: leggi il paper di YaRN (arXiv:2309.00071) e capisci la differenza tra “interpolazione lineare” e “blended interpolation by frequency”. È matematica leggera ma profonda.
Take-away
Il positional encoding è il dettaglio che separa un transformer giocattolo da uno utilizzabile su context lunghi. RoPE ha vinto perché non aggiunge parametri, non aggiunge calcolo, e si presta a “estensioni” ex-post. Il resto della letteratura è ingegneria attorno a quella scelta.
➡️ Prossima puntata: Multi-head attention — perché serve avere più “occhi” sulla stessa scena.