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
pip install -U transformers torchtorch 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"])
python hallo.pyAuf 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:
| Format | Speicher pro Milliarde Parameter |
|---|---|
| FP16 / BF16 (Standard-Gewichte) | ca. 2 GB |
| 8-bit quantisiert | ca. 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
revisionmit 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.
HF_HUB_OFFLINE=1 python hallo.pyLä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=Truesetzt,- und mit
HF_HUB_OFFLINE=1genauso 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.