25 min

Das erste Modell, lokal

Fünf Zeilen genügen - die interessanten Entscheidungen stecken in den Parametern, die man weglassen könnte.

Meilenstein Ein Skript erzeugt auf reiner CPU einen deutschen Satz, mit auf einen Commit-Hash gepinntem Modell und erzwungenem safetensors-Format.

Jetzt läuft etwas. Fünf Zeilen, keine GPU, und in der Zeit, die der erste Import braucht, kannst du dir einen Kaffee holen.

Danach machen wir die fünf Zeilen wieder auf. Denn die interessanten Entscheidungen stecken nicht in dem, was dort steht, sondern in dem, was fehlt.

Schritt 1 - Die fünf Zeilen

bash
pip install -U transformers torch

torch ist der große Brocken, rund 2 GB. Auf einem Rechner ohne NVIDIA-Karte holt pip automatisch die CPU-Variante.

# hallo.py
from transformers import pipeline

pipe = pipeline("text-generation", model="Qwen/Qwen2.5-0.5B-Instruct")
antwort = pipe(
    [{"role": "user", "content": "Erkläre in einem Satz, was ein Tokenizer macht."}],
    max_new_tokens=60,
)
print(antwort[0]["generated_text"][-1]["content"])
bash
python hallo.py

Auf CPU dauert das ein paar Sekunden. Das Ergebnis ist nicht brillant - es ist ein 0,5-Milliarden-Parameter-Modell, das kleinste sinnvolle. Es soll auch nicht brillieren, es soll laufen.

pipeline() hat dabei einiges für dich erledigt: das Modell aus dem Cache geladen (heruntergeladen hast du es im letzten Kapitel), den passenden Tokenizer dazugeholt, die Chat-Vorlage des Modells auf deine Nachricht angewandt, das Ergebnis wieder in Text zurückverwandelt. Wer das alles einzeln machen will, nimmt AutoTokenizer und AutoModelForCausalLM - das ist dieselbe Sache mit mehr Zeilen.

Schritt 2 - Passt das überhaupt in deinen Speicher?

Bevor du das nächstgrößere Modell probierst, rechne. Die Faustregel ist einfach genug, um sie im Kopf zu haben:

FormatSpeicher pro Milliarde Parameter
FP16 / BF16 (Standard-Gewichte)ca. 2 GB
8-bit quantisiertca. 1 GB
4-bit quantisiert (Q4)ca. 0,5 bis 0,6 GB

Ein 7B-Modell in FP16 sind also rund 14 GB, dasselbe Modell als Q4 rund 4 bis 5 GB. Ohne GPU zählt dieselbe Rechnung für deinen Arbeitsspeicher - und dazu kommt noch der Platz für den Kontext, der mit der Länge der Unterhaltung wächst.

Was welche Datei im Repo überhaupt enthält - Gewichte, Konfiguration, Tokenizer, und warum es GGUF neben safetensors gibt - steht im Baustein Modell-Dateien. Der ist die Ergänzung zu diesem Kapitel: Hier benutzt du die Dateien, dort siehst du hinein.

Schritt 3 - Die Parameter, die fehlen

Jetzt die ehrliche Fassung derselben fünf Zeilen:

from transformers import pipeline

MODELL = "Qwen/Qwen2.5-0.5B-Instruct"
COMMIT = "<hier dein Commit-Hash>"  # der volle 40-stellige SHA, kein Branchname

pipe = pipeline(
    "text-generation",
    model=MODELL,
    revision=COMMIT,
    model_kwargs={"use_safetensors": True},
)

Den Hash trägst du nicht aus einer Anleitung ab - er gehört zu dem Stand, den du heute geprüft hast. Hol ihn dir so:

from huggingface_hub import HfApi
print(HfApi().model_info("Qwen/Qwen2.5-0.5B-Instruct").sha)

Zwei Zusätze, zwei völlig verschiedene Sorgen.

use_safetensors=True - eine Formatfrage, die eine Sicherheitsfrage ist

Modellgewichte gibt es in zwei Formaten. safetensors enthält einen JSON-Kopf und rohe Zahlen, sonst nichts. Pickle (.bin, .pt) ist das Serialisierungsformat von Python - und Pickle kann beim Laden beliebigen Code ausführen. Das ist kein Fehler im Format, das ist seine Aufgabe.

transformers bevorzugt safetensors von sich aus, wenn beides im Repo liegt. use_safetensors=True macht daraus eine Bedingung: Gibt es keine safetensors-Datei, bricht das Laden mit einem Fehler ab, statt still auf Pickle auszuweichen. Genau das willst du - ein Fehler ist besser als ein leiser Rückfall auf das Format, das Code ausführen darf.

revision=<commit-sha> - Reproduzierbarkeit

Ohne Angabe lädst du main. Das ist ein beweglicher Zeiger: Der Modellanbieter kann morgen einen neuen Commit schieben, und dein Skript lädt etwas anderes als heute, ohne dass sich bei dir eine Zeile geändert hat. Bei einem Tippfehler in der README ist das egal. Bei neu quantisierten Gewichten oder einer geänderten Chat-Vorlage ist es genau der Fehler, den niemand findet, weil er nicht im Code steckt.

Den Hash holst du dir aus dem Reiter Commits auf der Modellseite oder aus dem Cache-Pfad, den hf download ausgegeben hat. Für einen Versuch ist das Fleißarbeit. Für alles, was länger als eine Woche lebt, ist es Pflicht.

Und der dritte, der hier bewusst fehlt

# NICHT einfach so:
pipe = pipeline("text-generation", model="jemand/exotisches-modell",
                trust_remote_code=True)

Manche Repos bringen eigenen Python-Code mit, weil ihre Architektur in transformers noch nicht enthalten ist. trust_remote_code=True erlaubt, dass dieser Code beim Laden ausgeführt wird.

Der Parameter heißt, wie er heißt. Es ist keine Kompatibilitätsoption, es ist die Aussage „ich habe den Code gelesen und vertraue ihm“. Wenn du ihn setzt:

  • Öffne die .py-Dateien im Repo und lies sie. Nicht überfliegen.
  • Setz immer revision mit dazu. Sonst vertraust du nicht dem Code, den du gelesen hast, sondern dem, der beim nächsten Start dort liegt.
  • Frag dich, warum es diesen Code gibt. Ein Repo mit einem loader.py, obwohl ganz normale safetensors danebenliegen, verlangt eine Erklärung.

Schritt 4 - Ohne Netz weiterarbeiten

Wenn das Modell einmal im Cache liegt, brauchst du den Hub nicht mehr. Das ist mehr als Komfort: Es beweist, dass die Bibliotheken und die Plattform zwei verschiedene Dinge sind.

bash
HF_HUB_OFFLINE=1 python hallo.py

Läuft. Kein Netzwerkzugriff, keine Telemetrie, keine Anfrage. Für den Betrieb in abgeschotteten Umgebungen ist genau das der Weg: Modelle einmal in einen gemeinsamen Cache holen, HF_HOME darauf zeigen lassen, HF_HUB_OFFLINE=1 setzen.

Der Meilenstein

Ein Skript hallo.py, das

  • auf reiner CPU läuft,
  • einen deutschen Satz erzeugt,
  • ein revision= mit einem echten Commit-Hash trägt,
  • use_safetensors=True setzt,
  • und mit HF_HUB_OFFLINE=1 genauso durchläuft wie ohne.

Der letzte Punkt ist der Beweis für die anderen: Was offline läuft, hat nichts nachgeladen, was du nicht kennst.