Zum Inhalt springen
CASOON

Tensoroperationen, die KI antreiben

Matrixmultiplikation, Convolution, Broadcasting – die Rechenoperationen hinter Deep Learning

10 Minuten
Tensoroperationen, die KI antreiben
#Tensoren #Matrixmultiplikation #Convolution #Broadcasting
SerieTensoren verstehen
Teil 5 von 8

Tensoren als Datenstruktur — was sie sind, wie sie durch Schichten fließen, welche Shapes dabei entstehen — haben die ersten Teile der Serie behandelt. Dieser Teil geht eine Ebene tiefer: Was passiert rechnerisch mit diesen Tensoren? Welche Operationen treiben tatsächlich das Netz an?

Die Antwort lässt sich auf fünf Operationsklassen verdichten. Zwei davon sind die eigentlichen Rechenkernel — dicht und aufwendig. Die anderen drei sind Daten-Choreographie: Sie sorgen dafür, dass die ersten zwei überhaupt laufen können.

Matrixmultiplikation: Die Basisoperation

Fast jede Fully-Connected-Schicht, jeder Linear-Layer, jede Attention-Berechnung im Transformer läuft auf dasselbe hinaus: eine Matrixmultiplikation.

import torch

# Eingabe: Batch von 32 Samples, je 128 Features
x = torch.randn(32, 128)

# Gewichtsmatrix: 128 Eingabe- → 64 Ausgabe-Neuronen
W = torch.randn(128, 64)
b = torch.randn(64)

# Linear Layer = Matmul + Bias
out = x @ W + b  # Shape: (32, 64)

Die Regel: (m, k) @ (k, n) = (m, n). Die innere Dimension muss übereinstimmen, die äußeren bestimmen die Ausgabe. Bei Batches erweitert sich das auf drei Dimensionen — PyTorch wendet die Multiplikation dann parallel über die Batch-Dimension an:

# Batched MatMul in Attention-Berechnungen
queries = torch.randn(32, 8, 64)   # (batch, heads, dim)
keys    = torch.randn(32, 8, 64)

scores = queries @ keys.transpose(-2, -1)  # (32, 8, 8)

Warum genau diese Operation so zentral ist: Matrixmultiplikation ist massiv parallelisierbar. Jede Ausgabe-Zelle hängt von anderen Eingabe-Zellen ab als ihre Nachbarn — tausende Berechnungen können gleichzeitig laufen. Wie Hardware das ausnutzt, behandelt Teil 6 der Serie.

Convolution: Lokale Mustererkennung

Matrixmultiplikation verbindet jeden Eingabewert mit jedem Ausgabewert — global, vollständig. Convolution arbeitet anders: Ein kleiner Filterkern gleitet über die räumlichen Dimensionen und verarbeitet jeweils nur einen lokalen Ausschnitt.

import torch.nn as nn

# 2D-Convolution: 3 Eingangskanäle (RGB), 16 Ausgangskanäle, 3×3-Kernel
conv = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, padding=1)

# Eingabe: Batch von 8 Bildern, je 224×224 Pixel, 3 Kanäle
img = torch.randn(8, 3, 224, 224)

out = conv(img)  # Shape: (8, 16, 224, 224)

Der Kernel — eine kleine Gewichtsmatrix, z.B. 3×3 — wird an jeder Position des Bildes mit dem lokalen Ausschnitt multipliziert und summiert. Das Ergebnis ist ein Feature-Map: eine Aktivierungskarte, die zeigt, wo der gelernte Filter angeschlagen hat.

Eingabe (5×5)          Kernel (3×3)        Feature Map (3×3)
┌─────────────┐        ┌───────┐           ┌───────────┐
│ 1  2  3  0  1│       │ 1  0 -1│           │  Σ  Σ  Σ  │
│ 0  1  2  3  0│   ×   │ 2  0 -2│    =      │  Σ  Σ  Σ  │
│ 1  0  1  2  3│       │ 1  0 -1│           │  Σ  Σ  Σ  │
│ 2  3  0  1  1│       └───────┘           └───────────┘
│ 0  1  3  2  0│
└─────────────┘
Kernel gleitet mit Stride 1 über alle Positionen

Der entscheidende Unterschied zur Matrixmultiplikation: Weight Sharing. Derselbe Kernel wird an allen Positionen angewendet — das Netz muss nicht für jede Position eigene Gewichte lernen. Das macht CNNs effizient für Bilddaten, wo lokale Muster unabhängig von ihrer Position relevant sind (eine Kante ist eine Kante, egal wo im Bild).

Broadcasting: Formen aufeinander abstimmen

Tensoren mit unterschiedlichen Shapes direkt zu kombinieren würde ohne Broadcasting einen Fehler erzeugen. Broadcasting löst das, indem fehlende Dimensionen implizit aufgefüllt werden — ohne die Daten tatsächlich zu kopieren.

# Bias-Addition: Feature-Vektor auf ganze Batch anwenden
batch  = torch.randn(32, 64)   # (batch, features)
bias   = torch.randn(64)       # (features,)

out = batch + bias   # bias wird auf (32, 64) expandiert

Die Regeln dahinter sind deterministisch: Dimensionen werden von rechts ausgerichtet. Eine Dimension mit Size 1 — oder eine fehlende Dimension — wird auf die passende Größe der anderen Seite expandiert.

a = torch.randn(3, 1, 5)
b = torch.randn(1, 4, 5)
c = a + b    # Shape: (3, 4, 5)

# Normalisierung über Kanal-Achse (Batch Norm intern)
x     = torch.randn(8, 16, 32, 32)   # (batch, C, H, W)
mean  = torch.randn(16)              # pro Kanal

# mean auf (1, 16, 1, 1) reshapen → broadcast über batch, H, W
x_norm = x - mean.view(1, 16, 1, 1)

Broadcasting ist konzeptuell einfach, aber eine häufige Fehlerquelle: Wenn Shapes zufällig kompatibel sind, aber nicht so gemeint, entsteht kein Fehler — nur ein falsches Ergebnis. Teil 8 der Serie behandelt genau diese Broadcasting-Fallstricke.

Reshaping und Permutation: Daten umordnen

Tensoren haben denselben Speicherinhalt unabhängig von ihrer Form. Reshaping ändert nur die Interpretation — keine Datenkopie, nur neue Shape-Metadaten.

x = torch.randn(8, 3, 32, 32)   # (batch, C, H, W)

# Flatten vor Fully-Connected-Layer
flat = x.reshape(8, -1)   # (8, 3072) — das -1 berechnet automatisch
# oder: x.view(8, -1)     # view() ist schneller, setzt flachen Speicher voraus

# Unsqueeze: Dimension hinzufügen
vec    = torch.randn(64)
matrix = vec.unsqueeze(0)   # (1, 64) — für Batch-Kompatibilität

# Squeeze: Dimensionen der Size 1 entfernen
out = torch.randn(1, 64, 1)
out.squeeze()   # (64,)

Permutation geht einen Schritt weiter: Sie ändert die Reihenfolge der Achsen. Das wird nötig, wenn Frameworks unterschiedliche Konventionen erwarten — oder wenn eine Operation eine bestimmte Achsenanordnung voraussetzt.

# PyTorch arbeitet mit (batch, channels, H, W) — NCHW
# TensorFlow / einige Ops erwarten (batch, H, W, channels) — NHWC

x_nchw = torch.randn(8, 3, 224, 224)
x_nhwc = x_nchw.permute(0, 2, 3, 1)   # (8, 224, 224, 3)

# Attention: (batch, heads, seq, dim) → MatMul braucht (batch, seq, heads*dim)
attn = torch.randn(4, 8, 64, 32)
out  = attn.permute(0, 2, 1, 3).reshape(4, 64, 256)

permute() ändert die logische Achsenreihenfolge, macht den Tensor aber nicht automatisch speicher-kontinuierlich. Ein .contiguous() danach ist oft nötig, bevor weitere Operationen darauf laufen — sonst folgt ein Laufzeitfehler.

Warum genau diese fünf?

Die fünf Operationen teilen sich in zwei Rollen:

Matmul und Convolution sind die rechenintensiven Kernel. Hier liegt der Löwenanteil der Floating-Point-Operationen eines Forward-Pass. Hier optimiert Hardware — Tensor Cores auf NVIDIA-GPUs sind explizit für 4×4-Matrixmultiplikationen gebaut. Hier lohnt Quantisierung (INT8 statt FP32), weil Annäherungen akzeptabel sind.

Broadcasting, Reshaping und Permutation leisten die Daten-Choreographie. Sie kosten vergleichsweise wenig — Reshaping ist im Idealfall kostenlos, Broadcasting vermeidet Datenkopien — aber ohne sie würden Matmul und Convolution schlicht nicht laufen. Die Shapes passen sonst nicht zusammen.

Daten-Choreographie          Rechenkernel
─────────────────────────    ──────────────────────────
Broadcasting                 Matrixmultiplikation
  ↓ Shapes anpassen             ↓ Dense / Linear / Attention
Reshaping                    Convolution
  ↓ Dimensionen umformen        ↓ CNNs / lokale Merkmale
Permutation
  ↓ Achsen sortieren

Diese Arbeitsteilung erklärt auch, warum Hardware-Optimierungen für KI so spezialisiert sind: Fast alle Rechenzeit fließt in zwei Operationstypen. TPUs, Tensor Cores, AMD Matrix Cores — sie alle zielen auf genau dieses Muster. Wie das funktioniert, behandelt der nächste Teil der Serie.

Und wie Matrixmultiplikation in Transformer-Architekturen konkret auftritt — in Query, Key, Value und den Attention-Scores — folgt in Teil 7.