TL;DR
Der Bau eines minimalen Sprachmodells von Grund auf erfordert weniger als 300 Zeilen Python. Dieser Prozess enthüllt genau, wie Tokenisierung, Attention und Inferenz funktionieren, was Sie zu einem wesentlich besseren API-Konsumenten macht, wenn Sie produktive LLMs in Ihre Anwendungen integrieren.
Einleitung
Die meisten Entwickler behandeln Sprachmodelle als Black Boxes. Sie senden Text ein, Tokens kommen heraus, und irgendwo dazwischen geschieht Magie. Dieses mentale Modell funktioniert gut, bis Sie eine fehlerhafte API-Integration debuggen, Sampling-Parameter optimieren oder herausfinden müssen, warum Ihr Modell ständig strukturierte Daten halluziniert.
GuppyLM, ein Projekt, das kürzlich mit 842 Punkten die Titelseite von HackerNews erreichte, macht die Interna sichtbar. Es ist ein 8,7 Millionen Parameter starker Transformer, der von Grund auf in Python geschrieben wurde. Er trainiert in weniger als einer Stunde auf einer Consumer-GPU. Der Code passt in eine einzige Datei. Das Ziel ist nicht, mit GPT-4 zu konkurrieren, sondern zu entmystifizieren, was LLMs tatsächlich tun.
Dieser Artikel erklärt, wie man ein winziges LLM baut, was jede Komponente tut und welche Erkenntnisse Sie aus dem Verständnis der Interna gewinnen, wenn Sie beruflich mit KI-APIs arbeiten.
💡 Wenn Sie KI-API-Integrationen testen, können Sie mit den Test-Szenarien von Apidog Streaming-Antworten überprüfen, die Token-Struktur bestätigen und Edge-Case-Vervollständigungen simulieren, ohne Produktionsguthaben zu verbrauchen. Mehr dazu später.
Was macht ein Sprachmodell "winzig"?
Produktive LLMs wie GPT-4 haben Hunderte Milliarden Parameter. Winzige LLMs bewegen sich im Bereich von 1 bis 25 Millionen Parametern, z.B. GuppyLM (8,7M), nanoGPT (124M), MicroLM (1-2M).
Vorteile winziger LLMs:
- Training auf Laptop/Colab möglich
- Passen komplett in den CPU-Speicher
- Gewichte können einfach inspiziert und modifiziert werden
Limitationen:
- Keine komplexen Schlussfolgerungen
- Kein langer, kohärenter Text
- Geringere faktische Tiefe
Der eigentliche Wert liegt im Verständnis des Modells, nicht im Output.
Kernkomponenten: Wie ein LLM tatsächlich funktioniert
Bevor Sie eigenen Code schreiben, kennen Sie die vier Hauptbestandteile:
Tokenizer
Der Tokenizer wandelt Rohtext in Ganzzahl-IDs um. Beispiel: "Hallo, Welt!" → [15496, 11, 995, 0]. Jeder Wert steht für ein Subwort aus dem Vokabular.
Praxis: Die Anzahl der Tokens beeinflusst Kosten und Latenz Ihrer API-Nutzung. Verstehen Sie, wie Tokenizer Text aufteilen, um Prompts effizient zu schreiben.
GuppyLM nutzt einen simplen Zeichen-basierten Tokenizer. Produktionsmodelle wie GPT-4 setzen auf BPE mit 50.000+ Tokens.
Einbettungsschicht
Token-IDs werden in Vektoren transformiert (z.B. 384 Dimensionen). Ähnliche Tokens liegen im Vektorraum nah beieinander. Positions-Einbettungen codieren die Reihenfolge.
Transformer-Blöcke
Jeder Block enthält:
- Self-Attention: Tokens berücksichtigen frühere Tokens für die nächste Vorhersage. GuppyLM nutzt 6 Attention-Köpfe, 6 Schichten.
- Feed-Forward-Netzwerk: Zweischichtiges MLP pro Token. GuppyLM verwendet ReLU, neuere Modelle oft SwiGLU.
Ausgabekopf
Nach dem letzten Block projiziert eine lineare Schicht auf die Vokabulargröße. Softmax liefert Wahrscheinlichkeiten, dann wird das nächste Token ausgewählt.
Ein minimales LLM in Python bauen
Hier ein minimal lauffähiges LLM nach GuppyLM-Vorbild. Läuft mit PyTorch.
import torch
import torch.nn as nn
import torch.nn.functional as F
# Hyperparameter
VOCAB_SIZE = 256 # Zeichen-basiert: ein Platz pro ASCII-Zeichen
D_MODEL = 128 # Einbettungsdimension
N_HEADS = 4 # Attention-Köpfe
N_LAYERS = 3 # Transformer-Blöcke
SEQ_LEN = 64 # Kontextfenster
DROPOUT = 0.1
class SelfAttention(nn.Module):
def __init__(self, d_model, n_heads):
super().__init__()
self.n_heads = n_heads
self.head_dim = d_model // n_heads
self.qkv = nn.Linear(d_model, 3 * d_model, bias=False)
self.proj = nn.Linear(d_model, d_model, bias=False)
self.dropout = nn.Dropout(DROPOUT)
def forward(self, x):
B, T, C = x.shape
qkv = self.qkv(x).reshape(B, T, 3, self.n_heads, self.head_dim)
q, k, v = qkv.unbind(dim=2)
q = q.transpose(1, 2)
k = k.transpose(1, 2)
v = v.transpose(1, 2)
# Kausale Maske: Jedes Token kann nur frühere Tokens berücksichtigen
scale = self.head_dim ** -0.5
attn = (q @ k.transpose(-2, -1)) * scale
mask = torch.triu(torch.ones(T, T, device=x.device), diagonal=1).bool()
attn = attn.masked_fill(mask, float('-inf'))
attn = F.softmax(attn, dim=-1)
attn = self.dropout(attn)
out = (attn @ v).transpose(1, 2).reshape(B, T, C)
return self.proj(out)
class TransformerBlock(nn.Module):
def __init__(self, d_model, n_heads):
super().__init__()
self.attn = SelfAttention(d_model, n_heads)
self.ff = nn.Sequential(
nn.Linear(d_model, 4 * d_model),
nn.ReLU(),
nn.Linear(4 * d_model, d_model),
nn.Dropout(DROPOUT),
)
self.ln1 = nn.LayerNorm(d_model)
self.ln2 = nn.LayerNorm(d_model)
def forward(self, x):
x = x + self.attn(self.ln1(x))
x = x + self.ff(self.ln2(x))
return x
class TinyLLM(nn.Module):
def __init__(self):
super().__init__()
self.embed = nn.Embedding(VOCAB_SIZE, D_MODEL)
self.pos_embed = nn.Embedding(SEQ_LEN, D_MODEL)
self.blocks = nn.ModuleList([
TransformerBlock(D_MODEL, N_HEADS) for _ in range(N_LAYERS)
])
self.ln_f = nn.LayerNorm(D_MODEL)
self.head = nn.Linear(D_MODEL, VOCAB_SIZE, bias=False)
def forward(self, idx):
B, T = idx.shape
tok_emb = self.embed(idx)
pos = torch.arange(T, device=idx.device)
pos_emb = self.pos_embed(pos)
x = tok_emb + pos_emb
for block in self.blocks:
x = block(x)
x = self.ln_f(x)
logits = self.head(x)
return logits
# Modell initialisieren und Parameter zählen
model = TinyLLM()
total_params = sum(p.numel() for p in model.parameters())
print(f"Modellgröße: {total_params:,} Parameter") # ~1.2M
Trainingsschleife
import torch.optim as optim
def train(model, data, epochs=100, lr=3e-4):
optimizer = optim.AdamW(model.parameters(), lr=lr)
model.train()
for epoch in range(epochs):
# data: Tensor von Token-IDs, Shape [Batch, Seq_len+1]
x = data[:, :-1] # Eingabe: alle Tokens außer dem letzten
y = data[:, 1:] # Ziel: alle Tokens um 1 verschoben
logits = model(x)
loss = F.cross_entropy(logits.reshape(-1, VOCAB_SIZE), y.reshape(-1))
optimizer.zero_grad()
loss.backward()
optimizer.step()
if epoch % 10 == 0:
print(f"Epoche {epoch}, Verlust: {loss.item():.4f}")
Inferenz (Texterzeugung)
@torch.no_grad()
def generate(model, prompt_ids, max_new_tokens=50, temperature=1.0, top_k=10):
model.eval()
ids = torch.tensor([prompt_ids])
for _ in range(max_new_tokens):
idx_cond = ids[:, -SEQ_LEN:] # Auf Kontextfenster kürzen
logits = model(idx_cond)
logits = logits[:, -1, :] / temperature # Nur letztes Token
# Top-k Sampling
v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
logits[logits < v[:, [-1]]] = float('-inf')
probs = F.softmax(logits, dim=-1)
next_id = torch.multinomial(probs, num_samples=1)
ids = torch.cat([ids, next_id], dim=1)
return ids[0].tolist()
Was dies über das Verhalten von KI-APIs lehrt
Das Nachbauen eines LLMs bringt praktische Erkenntnisse für API-Konsumenten:
Temperatur und Sampling sind mechanisch, nicht magisch
Die Temperatur teilt die Logits vor Softmax. Höhere Temperatur = flachere Verteilung = mehr Zufall. Geringe Temperatur = deterministisch. temperature=0.0 ist ein gieriges Argmax – viele APIs setzen sie bewusst leicht über Null.
Kontextfenster sind harte Grenzen
Die Zeile idx_cond = ids[:, -SEQ_LEN:] zeigt: Das Modell verwirft ältere Tokens. Ihre API merkt sich den Gesprächsverlauf nicht unendlich. Siehe [intern: how-ai-agent-memory-works] für Lösungen.
Streaming-Tokens sind sichtbare Inferenzschritte
Streaming-APIs senden jedes Token direkt nach der Generierung. Ein unterbrochener Stream kann nicht fortgesetzt werden, sondern muss neu starten.
Logits erklären strukturierte Ausgabe
Das Modell vergibt pro Schritt Wahrscheinlichkeiten für jedes Token. Gültiges JSON zu generieren bedeutet, dass an jedem Schritt das richtige Token "gewinnt". Tools wie Outlines/Guidance erzwingen Grammatik durch Einschränkung der Logits. "Strukturierte Ausgabe"-Modi in APIs machen intern genau das.
Wie man KI-API-Integrationen mit Apidog testet
Mit dem Verständnis der LLM-Inferenz können Sie gezielt KI-API-Tests gestalten. Apidog (apidog.com) erlaubt, Test-Szenarien zu erstellen, API-Aufrufe zu verketten und KI-Antworten zu validieren.
Praxis-Beispiel: Streaming-Chat-API testen
- Legen Sie ein Test-Szenario in Apidog für Ihren
/v1/chat/completions-Endpunkt an. - Definieren Sie Assertions wie:
response.choices[0].finish_reason == "stop"response.usage.total_tokens < 4096
- Senden Sie die Antwort als Kontext in einen Folgeschritt, um Multi-Runden-Konversation zu simulieren.
- Nutzen Sie Apidogs Smart Mock, um Fehlerfälle zu testen:
- Simulieren Sie
finish_reason: "length", -
finish_reason: "content_filter", - Netzwerk-Timeout im Stream.
- Simulieren Sie
So testen Sie KI-Integrationen, ohne bei jedem CI-Lauf API-Guthaben zu verbrauchen. Siehe [intern: api-testing-tutorial] für umfassendere Testansätze.
Testen von Token-Zählungs-Assertions
{
"assertions": [
{
"field": "response.usage.completion_tokens",
"operator": "less_than",
"value": 512
},
{
"field": "response.choices[0].finish_reason",
"operator": "equals",
"value": "stop"
},
{
"field": "response.choices[0].message.content",
"operator": "not_empty"
}
]
}
Führen Sie dieses Szenario über mehrere Modelle (GPT-4o, Claude 3.5 Sonnet, Gemini 1.5 Pro) aus, um API-Schema-Unterschiede frühzeitig zu erkennen.
Fortgeschritten: Quantisierung und Inferenzoptimierung
Nach dem Bau eines kleinen LLMs sind folgende Techniken direkt praxisrelevant:
Quantisierung
Die Modellgewichte liegen standardmäßig als 32-Bit-Floats vor. Quantisierung reduziert auf z.B. INT8 oder INT4. So lässt sich der Speicherbedarf um das 4- bis 8-fache senken – mit minimalem Genauigkeitsverlust.
# Beispiel: dynamische INT8-Quantisierung in PyTorch
import torch.quantization
quantized_model = torch.quantization.quantize_dynamic(
model, {nn.Linear}, dtype=torch.qint8
)
Viele Produktions-APIs nutzen quantisierte Modelle. Unterschiedliche Ausgabequalitäten verschiedener Modell-"Versionen" können oft auf Quantisierung zurückgeführt werden.
KV-Cache
In der Inferenzschleife wird die Attention bei jedem Schritt neu berechnet. Produktionssysteme cachen die Key-Value-Paare (KV-Cache), sodass jedes neue Token effizienter generiert werden kann. Das erste Token in einer Streaming-Antwort dauert daher länger als die folgenden.
Winziges LLM vs. Produktions-API: Wann man welches verwendet
| Anwendungsfall | Winziges LLM | Produktions-API |
|---|---|---|
| Modell-Interna lernen | Am besten geeignet | Überdimensioniert |
| Prototyping einer neuen App | Unzureichende Qualität | Am besten geeignet |
| Private/sensible Daten | Gute Option | Abhängig vom Anbieter |
| Offline/Edge-Bereitstellung | Machbar | Nicht möglich |
| Kostenempfindlich, hohes Volumen | Möglich mit Kompromissen | Teuer im großen Maßstab |
| Aufwändige Denkaufgaben | Nicht machbar | Erforderlich |
Die Empfehlung: Nutzen Sie die Produktions-API für echte Anwendungen, bauen Sie ein winziges Modell, um die Funktionsweise zu verstehen. Beide Ansätze ergänzen sich. [intern: open-source-coding-assistants-2026] zeigt, wie BYOM-Setups die Grenze aufweichen.
Fazit
Ein winziges LLM ist an einem Wochenende gebaut. Es ist kein Produktionssystem, aber vermittelt ein präzises mentales Modell – von GuppyLM bis GPT-4o. Dieses Wissen hilft bei jedem Debugging, Sampling-Tuning oder API-Test.
Das GuppyLM-Projekt ist ein idealer Startpunkt. Klonen, mit beliebigen Texten trainieren, Inferenzschleife nachvollziehen – und mit geschärftem Verständnis zurück zu Ihren API-Integrationen gehen.
Nutzen Sie die Test-Szenarien von Apidog (apidog.com), um KI-APIs so gründlich zu testen wie jedes andere Backend-System.
FAQ
Wie viele Parameter benötigt ein "winziges" LLM, um kohärenten Text zu generieren?
Etwa 10 bis 50 Millionen Parameter mit einem anständigen Trainingsdatensatz können lokal kohärente Sätze produzieren. Unter 1 Million erhält man bei den meisten Aufgaben Kauderwelsch. GuppyLM mit 8,7 Millionen funktioniert für kurze Unterhaltungen in seinem Trainingsbereich (60 Themen).
Kann ich ein winziges LLM ohne GPU ausführen?
Ja. Modelle unter 100 Millionen Parametern laufen gut auf der CPU, obwohl die Inferenz langsamer ist. Das obige Modell (1,2 Millionen Parameter) generiert Tokens in Millisekunden auf einer Laptop-CPU.
Welchen Datensatz soll ich zum Training verwenden?
Zeichen-basierte Modelle funktionieren gut mit Texten von Project Gutenberg, Wikipedia-Teilmengen oder jedem einfachen Textkorpus. GuppyLM verwendet einen Konversationsdatensatz mit 60.000 Einträgen auf HuggingFace (arman-bd/guppylm-60k-generic). Für die Codegenerierung verwenden Sie The Stack oder CodeParrot.
Was ist der Unterschied zwischen Temperatur und Top-K-Sampling?
Die Temperatur skaliert die Logit-Verteilung (kontrolliert die Gesamtzufälligkeit). Top-K beschränkt den Sampling-Pool auf die k wahrscheinlichsten Tokens, bevor die Temperatur angewendet wird. Sie werden zusammen angewendet: Zuerst filtert Top-K die Kandidaten, dann formt die Temperatur die Wahrscheinlichkeiten innerhalb dieser Menge.
Warum wiederholt sich mein LLM manchmal?
Wiederholung ist ein Fehlermodus, bei dem das Modell Tokens, die es gerade generiert hat, eine hohe Wahrscheinlichkeit zuweist, weil sie im Kontext erschienen sind. Produktions-APIs verwenden Wiederholungsstrafen (eine Logit-Anpassung, die kürzlich generierte Tokens abwertet). Fügen Sie repetition_penalty=1.1 in Ihren API-Aufruf ein, um dies zu reduzieren.
Wie lange dauert es, ein winziges LLM zu trainieren?
Das obige Modell trainiert in weniger als 2 Stunden auf einer einzelnen GPU (RTX 3060 oder gleichwertig) zu kohärenter Ausgabe. GuppyLM trainiert in Colab in etwa der gleichen Zeit. Größere Modelle (100 Millionen+) benötigen Multi-GPU-Setups und Tage des Trainings.
Was ist der schnellste Weg, um von einem winzigen LLM zu einem echten API-Endpunkt zu gelangen?
Exportieren Sie in das GGUF-Format mit dem Konvertierungsskript von llama.cpp und stellen Sie es dann mit llama-server bereit. Dies gibt Ihnen einen OpenAI-kompatiblen API-Endpunkt, der lokal läuft. Sie können dann Apidog darauf zeigen, um ihn zu testen, siehe [intern: rest-api-best-practices].
Wie gehen Produktions-LLMs mit Kontext um, der länger ist als ihr Trainingsfenster?
Techniken wie RoPE (Rotary Position Embedding) mit erweiterter Skalierung, Sliding-Window-Attention und Retrieval-Augmented Generation erweitern alle den effektiven Kontext. Die Kern-Transformer-Architektur ändert sich nicht; dies sind Modifikationen der Art und Weise, wie Positionsinformationen kodiert und das Aufmerksamkeitsfenster angewendet wird.
Top comments (0)