Tokenwerk · Das LLM-Lehrbuch

Kapitel 25 · V · Moderne Sprachmodelle · 7 Minuten

RMSNorm, RoPE und SwiGLU

Drei gezielte Veränderungen am Decoder. Du lernst die Motivation, die Rechnung und die Grenzen jeder Technik.

Modernisieren durch kontrollierten Austausch

Wir ändern nicht alle Komponenten gleichzeitig und behaupten dann, jede einzelne hätte geholfen. Der Referenzcode bietet eine moderne Konfiguration; für wissenschaftliche Vergleiche ergänzt du einzelne Schalter und hältst Parameter- oder Rechenbudget möglichst vergleichbar. Im Folgenden leiten wir die Grundrechnungen selbst her.

RMSNorm

RMSNorm normiert nach der Wurzel des mittleren Quadrats der Komponenten. Anders als LayerNorm zieht sie keinen Mittelwert ab: Rechne zuerst [3,4]: quadrieren ergibt [9,16], mitteln ergibt 12.5 und Wurzelziehen ungefähr 3.536. Teile jede ursprüngliche Komponente durch diese Zahl. Erst dann folgt die allgemeine Schreibweise:

RMSNorm⁡(x)i=γixi1D∑jxj2+ϵ.\operatorname{RMSNorm}(x)_i=\gamma_i\frac{x_i}{\sqrt{\frac1D\sum_jx_j^2+\epsilon}}.

Hier ist x die Zahlenliste einer Position, i ihre ausgewählte Komponente und D ihre Länge. Der Index j läuft über alle D Komponenten. Die Summe der Quadrate, geteilt durch D, ist deren mittleres Quadrat. Epsilon ist wieder eine kleine Schutzkonstante. Gamma ist ein pro Komponente trainierbarer Multiplikator. RMS bedeutet „root mean square“, also Wurzel des mittleren Quadrats.

Für x=[3,4]x=[3,4] beträgt der RMS (9+16)/2≈3,536\sqrt{(9+16)/2}\approx3{,}536. Mit Skala eins entstehen ungefähr [0,849,1,131][0{,}849,1{,}131]. Der Mittelwert der Ausgabe ist nicht null. Das ist der wesentliche Unterschied zu einer zentrierenden Normalisierung.

python
class RMSNorm(nn.Module):
    def __init__(self, dim, eps=1e-6):
        super().__init__()
        self.weight = nn.Parameter(torch.ones(dim))
        self.eps = eps

    def forward(self, x):
        xf = x.float()
        y = xf * torch.rsqrt(xf.square().mean(-1, keepdim=True) + self.eps)
        return y.to(x.dtype) * self.weight.to(x.dtype)

Die Statistik wird hier in Float32 berechnet. Das hilft bei niedrigpräzisen Aktivierungen. RMSNorm ist kein zusätzlicher Speicher für Fakten; sie verändert die Skalierung und die Optimierungseigenschaften des Rechenwegs. Originalarbeit zu RMSNorm.

Position als Rotation

RoPE ergänzt keine gelernte Positionstabelle zum Eingabeembedding. Es dreht Paare von Komponenten in Q und K abhängig von ihrer Position. Betrachte [1,0] als Pfeil, der nach rechts zeigt. Eine Vierteldrehung macht daraus [0,1], einen Pfeil nach oben. Die Länge bleibt 1.

Sinus und Kosinus geben die Koordinaten eines gedrehten Pfeils der Länge 1 an: Kosinus die waagerechte, Sinus die senkrechte Koordinate. Bei null Grad sind sie 1 und 0; bei 90 Grad sind sie 0 und 1. Winkel werden im Code in Radiant gemessen: eine volle Drehung entspricht 2π, eine Vierteldrehung π/2. π (pi) ist ungefähr 3.14159. Für ein Paar [x1,x2][x_1,x_2] und Winkel θ\theta gilt:

Rθx=[x1cos⁡θ−x2sin⁡θ, x1sin⁡θ+x2cos⁡θ].R_\theta x=[x_1\cos\theta-x_2\sin\theta,\ x_1\sin\theta+x_2\cos\theta].

R bedeutet Rotation, Theta (θ\theta) den Drehwinkel und x₁ sowie x₂ die beiden ursprünglichen Komponenten. Setze für eine Vierteldrehung cos θ = 0 und sin θ = 1 ein: Aus [1,0] wird [0,1]. Die Vektornorm ist die Länge des Pfeils, hier die Wurzel aus der Summe beider Komponentenquadrate. Eine Rotation erhält diese Vektornorm. Q an Position mm und K an Position nn haben durch die Rotationen ein Skalarprodukt, das vom relativen Winkel beziehungsweise von n−mn-m abhängt. Das erklärt, warum relative Positionsbeziehungen in die Attention-Scores eingehen.

Für die Featurepaare verwenden wir verschiedene Frequenzen. Eine verbreitete Form ist ωi=base−2i/d\omega_i=base^{-2i/d}. Hier ist i die Nummer des Komponentenpaars, beginnend bei 0, d die gesamte Kopfbreite und base eine fest gewählte positive Basis, größer als 1. Omega (ωi\omega_i) ist die Drehung pro Positionsschritt für dieses Paar. Der negative Exponent bedeutet einen Kehrwert: base hoch −1 ist 1 / base. Zum Beispiel liefern base = 100 und d = 4 für Paar 0 die Frequenz 1 und für Paar 1 die Frequenz 0.1. Der Winkel an Position t lautet tωit\omega_i. An Position 3 sind das im Beispiel 3 beziehungsweise 0.3 Radiant. Hohe und niedrige Frequenzen erfassen verschiedene Abstandsbereiche. Unsere Implementierung benutzt benachbarte gerade/ungerade Komponenten als Paar; andere Implementierungen können die Features anders anordnen.

python
even, odd = x[..., 0::2], x[..., 1::2]
y_even = even * cos - odd * sin
y_odd  = even * sin + odd * cos
y = torch.stack((y_even, y_odd), dim=-1).flatten(-2)

cos und sin müssen über Batch- und Kopfachsen korrekt broadcasten. Die Kopfdimension muss gerade sein. Im Referenzmodell rotieren wir Q und K, nicht V. RoFormer / RoPE.

RoPE macht Kontext nicht automatisch unbegrenzt

Die Formel kann Positionen jenseits des Trainingsfensters berechnen. Daraus folgt keine zuverlässige Leistung dort. Das Modell kann auf unbekannten Abständen schlechter arbeiten. Längere Kontexte benötigen geeignete Trainingsdaten, Anpassungen und Evaluation. Gängige Anpassungen skalieren die RoPE-Frequenzen (z. B. Position Interpolation oder YaRN) und trainieren kurz auf längeren Texten nach. Im Lehrprojekt bleibt auch der moderne Decoder auf seine konfigurierte Kontextlänge begrenzt.

SwiGLU

Ein klassisches MLP hat zwei Projektionen mit einer Nichtlinearität dazwischen. SwiGLU benutzt zwei Eingangsprojektionen, multipliziert sie komponentenweise und projiziert anschließend zurück:

SwiGLU⁡(x)=Wdown(SiLU⁡(Wgatex)⊙Wupx).\operatorname{SwiGLU}(x)=W_{down}\left(\operatorname{SiLU}(W_{gate}x)\odot W_{up}x\right).

Das Zeichen ⊙\odot bedeutet komponentenweise Multiplikation: [2,3] und [4,5] ergeben [8,15], kein Skalarprodukt. W_gate und W_up sind zwei verschiedene gelernte Gewichtstabellen, die jeweils aus x eine breitere Liste berechnen. W_down macht die Liste danach wieder so breit wie x. „Gate“ heißt Tor: Ein Zahlenpfad verändert die Stärke des anderen.

SiLU ist xσ(x)x\sigma(x). Sigma (σ\sigma) steht hier für eine Funktion, nicht für die Standardabweichung aus LayerNorm. Diese Funktion heißt Sigmoid und berechnet σ(x)=1/(1+exp⁡(−x))\sigma(x)=1/(1+\exp(-x)). Sie bildet jede Zahl auf einen Wert zwischen 0 und 1 ab. Bei x = 0 ergibt sie 1/2; bei x = 1 ungefähr 0.731. SiLU multipliziert diesen Wert mit x. Für x = 1 ergibt SiLU also ungefähr 0.731, für x = −1 ungefähr −0.269. Beispiel für einen einzelnen Gate-Ausgang 1 und einen Up-Ausgang 2: Ihr Produkt nach SiLU ist ungefähr 1.462, bevor W_down darauf angewandt wird. Ein Pfad wirkt als gelerntes Gate, der andere liefert Werte. Dieses Gate ist kein binärer Schalter; seine Komponenten können kontinuierlich und auch negativ wirken.

python
h = F.silu(self.gate(x)) * self.up(x)
out = self.down(h)

Es gibt drei große Matrizen statt zwei. Für eine vergleichbare Parameterzahl wählt man die Hiddenbreite deshalb häufig kleiner als die klassische 4D4D-Breite, grob 8D/38D/3 und dann auf eine praktische Blockgröße gerundet. Das Referenzprojekt rundet auf ein Vielfaches von 32. GLU-Varianten.

Ein moderner Lernlauf

bash
python train.py --data runs/data --out runs/modern \
  --modern --dim 128 --layers 4 --heads 4 --kv-heads 2 \
  --context 128 --steps 2000

Das aktiviert zusätzlich GQA, das wir im nächsten Kapitel erklären. Diese kleine Implementierung verdeutlicht etablierte Bausteine, aber enthält keine verteilte Trainingspipeline oder hochoptimierte Inferenzmaschine.

Was du vergleichen solltest

Notiere tatsächliche Parameterzahl, verarbeitete Tokens, Laufzeit, Validierungs-Loss und feste Samples. Bei mehreren zugleich ausgetauschten Teilen kannst du nur den Gesamtvergleich interpretieren. Um die Wirkung einer Komponente zu bestimmen, brauchst du eine Ablation: denselben Versuchsaufbau mit genau diesem einen Unterschied.