11 Min. Lesezeit

Lokale KI, Teil 2: Schnell genug, und wo die Grenze wirklich liegt

Teil 1 zeigte: ein kleines Modell trifft das große. Teil 2 beantwortet die offene Frage nach dem Tempo auf einer 450-Euro-GPU und zeigt, wo kleine lokale Modelle leise falsch werden.

Im ersten Teil habe ich fünf Sprachmodelle auf demselben schmalen Test-Set verglichen. Vierzehn anonymisierte Mails, vier Klassen, strukturierte Extraktion. Das Ergebnis: ein 12-Milliarden-Parameter-Modell trifft auf diesen Mails so gut wie ein 70-Milliarden-Modell, auch lokal in 4-Bit-Quantisierung. Den lokalen Lauf hatte ich damals auf der CPU gemacht, mit Minuten pro Mail, also nicht alltagstauglich.

Die offene Frage war also: schnell genug auf bezahlbarer Hardware?

Ergebnis vorne: Auf einer gebrauchten RTX 3060 mit 12 GB Grafikspeicher für rund 450 Euro liegt dasselbe Modell bei etwa 2,5 Sekunden pro Mail, warm. Das ist der Unterschied zwischen “läuft im Prinzip” und “läuft im Alltag”. Interessanter als diese Zahl sind aber zwei andere Dinge, die ich seither gemessen habe: warum ein größeres Modell auf derselben Karte plötzlich langsamer ist, und an welchen Stellen kleine Modelle nicht langsam oder kaputt sind, sondern leise falsch.

Die Hardware

Kein Rechenzentrum, kein Workstation-Budget. Ein gebrauchter PC von Kleinanzeigen: RTX 3060 mit 12 GB, Ryzen 5 3600, headless unter Ubuntu Server, Ollama als Inferenz-Dienst im LAN. Gesamtpreis inklusive Versand knapp 469 Euro. Das ist bewusst die untere Kante. Es geht nicht darum, was mit einer 4090 ginge, sondern darum, was die billigste ernsthafte Karte schon kann.

Stromverbrauch im Leerlauf grob 30 Watt, also etwa 7,50 Euro im Monat, eine geschätzte Größenordnung.

Der Messlauf

Gleiches Setup wie in Teil 1, gleiche vierzehn Fixtures, gleicher Prompt, gleiche Schema-Validierung. Einziger Unterschied: das Backend zeigt jetzt auf den Ollama-Dienst auf der GPU-Maschine statt auf die CPU. Modell ist wieder Mistral-Nemo 12B, diesmal in der Ollama-Standard-Quantisierung Q4_0, etwa 7 GB.

Qualität: 14/14, null Halluzinationen, null Schema-Brüche. Identisch zum Cloud-Lauf und zur 70B-Baseline. Auch die drei Bestandskunden-Mails, an denen kleinere Modelle weiter unten zerbrechen, kommen sauber durch, samt korrekt erkannter Dringlichkeit bei der eskalierenden.

Tempo: Die vierzehn Calls zusammen brauchten 53 Sekunden. Davon entfällt der erste Call mit 17,5 Sekunden fast komplett auf das Laden des Modells ins VRAM, das ist ein einmaliger Kaltstart, keine Inferenz. Die warmen Calls liegen zwischen 1,3 und 3,8 Sekunden, im Mittel bei etwa 2,5 Sekunden pro Mail.

Der Kaltstart gehört dazu, ehrlich genannt: Wer den Dienst nur gelegentlich anstößt und das Modell zwischendurch aus dem VRAM fällt, zahlt diese 17,5 Sekunden beim ersten Zugriff erneut. Für einen Posteingang, der laufend arbeitet, fällt das einmal an. Für eine selten genutzte Funktion ist es spürbar.

VRAM ist die Wand, nicht die Rechenleistung

Der interessanteste Befund kam aus dem Quervergleich. Ich habe denselben Lauf zusätzlich mit gpt-oss 20B gefahren, einem deutlich größeren Modell.

ModellTrefferSchema-Brüche⌀ Latenz pro Mail (warm)
Mistral-Nemo 12B (Q4_0)14/140~2,5 s
Mistral-Nemo 12B (Q4_K_M)13/141~2,4 s
gpt-oss 20B14/140~17,6 s

Zwei Dinge stehen darin.

Erstens, der Quantisierungs-Vergleich. Q4_0 und Q4_K_M liefern praktisch dasselbe. Der eine Schema-Bruch bei Q4_K_M trat an genau der Mail auf, die Q4_0 sauber löste, das ist Sampling-Rauschen an einer einzelnen Fixture bei einem Test-Set von vierzehn, kein messbarer Qualitäts-Verlust durch die andere Quantisierung. Bei n gleich vierzehn ist plus minus eins Rauschen, nicht Signal.

Zweitens, und das ist die eigentliche Lektion: gpt-oss 20B liefert perfekte Qualität, ist auf dieser Karte aber siebenmal langsamer als das kleinere Nemo. Der Grund ist nicht, dass die GPU zu schwach rechnet. Der Grund ist, dass das Modell mit knapp 14 GB nicht in die 12 GB VRAM passt. Ein Teil läuft dann über den langsameren Hauptspeicher, und genau dieser ausgelagerte Teil bremst alles aus. Dazu kommt, dass gpt-oss sichtbar mehr “nachdenkt” und dadurch ein Vielfaches an Output erzeugt.

Daraus folgt eine Faustregel, die für lokale Inferenz auf Consumer-Hardware allgemein gilt: Das größte Modell, das vollständig ins VRAM passt, ist fast immer die bessere Wahl als das nächstgrößere, das nur halb hineinpasst. Nicht weil es klüger ist, sondern weil es nicht über die Speicher-Grenze ausgelagert wird. Modellwahl ist hier zuerst eine VRAM-Frage, dann eine Qualitäts-Frage. Für diese Karte heißt das: Nemo 12B bleibt gesetzt.

Robustheit: was passiert, wenn das Modell danebenliegt

Kleine Modelle haben eine bekannte Schwäche. Sie brechen gelegentlich das Ausgabe-Schema. Im GPU-Lauf zeigte sich das deutlich an Llama 3.1 8B, dem kleinsten getesteten Modell: drei von vierzehn Mails kamen strukturell kaputt zurück, immer dasselbe Muster, ein zweites JSON-Objekt hinten angehängt. Schnell war das Modell mit etwa 1,5 Sekunden pro Mail, aber unzuverlässig.

Wie real das ist, habe ich gesehen, als ich die Pipeline einmal nicht gegen Fixtures laufen ließ, sondern gegen mein echtes Postfach. Dreiundzwanzig Mails aus dem eigenen Eingang, durch Klassifikation und Extraktion. Zwei davon brachen das Schema, ein angehängtes zweites JSON-Objekt beim einen, ein null statt einer Zahl beim anderen. Zu dem Zeitpunkt gab es keine Absicherung, beide Mails fielen hart auf die Nase und landeten als Fehler im Log. Das war kein sauberes Test-Set, sondern ein einmaliger, informeller Lauf, und dieses Modul gibt es inzwischen nicht mehr. Was bleibt, ist die Lehre: zwei von dreiundzwanzig echten Mails reichten, um zu zeigen, dass hartes Scheitern bei jedem Aussetzer keine Option ist.

Die Antwort darauf war nicht ein größeres Modell, sondern ein Validate-and-Repair-Schritt. Statt bei einem kaputten Ergebnis hart zu scheitern, prüft die Pipeline die Antwort an der Grenze. Ist sie nicht parsebar, wird zunächst versucht, das erste gültige JSON-Objekt herauszuschneiden, das kostet nichts. Reicht das nicht, geht ein gezielter zweiter Prompt an dasselbe Modell, begrenzt auf wenige Versuche.

public interface SchemaRepairingChatCaller {
    <T> Result<T> call(String taskPrompt, Class<T> type, LlmCall call);
}

Der Aufrufer übergibt nur seinen fachlichen Prompt und den erwarteten Typ. Das Format-Handling, das Isolieren und das begrenzte Nachfassen stecken hinter dieser Schnittstelle, die Fachschicht sieht davon nichts. Der Schritt sitzt im Adapter, hinter dem Ausgangs-Port, die Domäne bleibt sauber. Denselben Mechanismus nutzt inzwischen auch der Bild-Pfad weiter unten.

Begrenzt ist hier das Schlüsselwort. Ohne harte Obergrenze, im Default zwei Versuche, dreht ein Modell, das die Aufgabe schlicht nicht kann, eine Endlosschleife. Lokal kostet jeder Versuch zwar nichts an Geld, aber rund 2,5 Sekunden Zeit, das ist nur tragbar, solange Reparaturen selten bleiben.

Eine ehrliche Einschränkung gehört dazu: Dieser Schritt fängt bislang nur kaputte Struktur ab. Eine Antwort, die formal sauber ist, aber fachlich falsch, ein Betrag ohne Währung, eine Rechnungsnummer, die keinem Muster folgt, läuft heute durch. Diese zweite, inhaltliche Prüfstufe ist gedacht, aber noch nicht gebaut. Und die zwei echten Brüche von oben sind genau die Sorte Fehler, für die dieser Schritt gebaut wurde.

Wo kleine Modelle wirklich an Grenzen stoßen

Tempo und Schema-Robustheit sind das eine. Spannender sind die Stellen, an denen das Modell nicht langsam oder kaputt ist, sondern leise falsch.

Confidence ist kein “weiß nicht”

Bei der Klassifikation, dem Schritt vor der Extraktion, erfand das Modell auf echten Mails freie Labels statt der vorgegebenen Kategorien, “Marketing Email” oder “Legal Text” statt der erlaubten Liste. Knapp ein Viertel der Mails landete dadurch still in der Restkategorie. Auf einem sauberen synthetischen Test-Korpus war das nie passiert. Der Fehler ist eine Eigenschaft echter, unsauberer Mails, kein Laborbefund. Methodisch heißt das: synthetische Tests zeichnen reale Schwächen zu schön.

Der eigentlich kontraintuitive Befund liegt eine Ebene tiefer. Das Modell gibt von sich aus nie “weiß nicht” zurück. Eine Mail, die in keine Kategorie passt, presst es trotzdem hochkonfident in den nächstbesten Nachbarn, mit Werten zwischen 0,95 und 1,00. Wer also die Confidence des Modells als Steuerungssignal nimmt, nach dem Muster “unter 0,7 schaut ein Mensch drauf”, baut auf Sand. Das Modell ist sich auch dann sicher, wenn es danebenliegt.

Geholfen hat nicht ein größeres Modell, sondern eine schärfere Kategorie-Definition im Prompt. Auf unserem kleinen Eval-Korpus stieg die Trefferquote dadurch von rund 62 auf 81 Prozent. Das ist eine Richtung, kein Versprechen für die Praxis, der Korpus ist klein und synthetisch.

Ein zweiter Hebel hatte eine überraschende Kehrseite. Wenn man dem Modell das Vokabular technisch hart vorschreibt, sodass es nur noch aus der erlaubten Liste wählen kann, verschwinden die erfundenen Labels auf null. Aber der sichtbare Fehler, bei dem alles Unklare in der Restkategorie auflief, verwandelt sich in einen unsichtbaren: Das Modell ordnet jetzt selbstbewusst falsch zu, eine Bestellung wird zum Newsletter. Erzwungenes Vokabular repariert die Wortwahl, nicht das Verständnis.

Fotografierte Belege: das Vision-Modell ersetzt die OCR

Im Mittelstand kommen Belege oft als Handy-Foto, nicht als sauberer Scan. An einem echten Thermo-Kassenbon getestet, lieferten die klassischen Werkzeuge wie pdftotext exakt null Zeichen, weil im PDF nur ein Bild steckt, kein Textlayer. Ein Vision-Modell, also ein Sprachmodell, das Bilder mitliest, ersetzt an dieser Stelle die klassische Texterkennung in einem Schritt, ohne separate OCR-Bibliothek.

Lokal läuft dafür ein Vision-Modell der 7-Milliarden-Klasse, auf derselben Karte wie das Textmodell.

Die Grenze zeigte sich klar an mehrseitigen Scans. Ein Bild pro Aufruf ist stabil und braucht auf der 3060 rund 10 Sekunden. Ab drei Bildern in einem Aufruf läuft das Modell in eine Endlos-Wiederholung, der Kontext platzt; ein größeres Kontext-Fenster behebt das, kostet aber spürbar Zeit. Inhaltlich kippt es zusätzlich: Das Modell behandelt faktisch jede Seite als eigenes Dokument, fasst einen Mehrseiter nicht zusammen, und bei einem sechsseitigen Scan vertauschte es die Seiten-Zuordnung und verlor eine Seite ganz. Die Architektur-Lehre ist simpel: ein Bild pro Aufruf ist der verlässliche Pfad, das Zusammensetzen gehört in den Code, nicht ins Modell.

Und auch das Vision-Modell halluziniert Felder, es las einmal einen Wert als den falschen Posten. Der Validierungs- und Reparatur-Schritt von oben und ein Mensch an der richtigen Stelle bleiben Pflicht, nicht Kür.

Neuer und größer ist nicht automatisch besser

Ein naheliegender Reflex ist, beim nächsten Problem das neuere, größere Modell zu nehmen. Ich habe ein neueres Vision-Modell ausprobiert, das intern “nachdenkt”, bevor es antwortet. Lokal kostet dieses Nachdenken kein Geld, aber jede Antwort hat eine feste Obergrenze, wie viele Tokens sie erzeugen darf. Genau die hat das interne Nachdenken komplett verbraucht, für die eigentliche Antwort blieb nichts übrig, sie kam leer zurück. Erst das Abschalten des Denk-Modus machte das Modell brauchbar. Ein moderneres Modell hat die Aufgabe also zunächst schlechter gelöst als das schlichtere. Neuer und größer ist eine Hypothese, kein Upgrade.

Die größere Lektion liegt nicht im Modell

Tempo, VRAM und Schema-Robustheit sind Detailfragen. Der eigentliche Lerneffekt dieser Wochen hatte mit Modellen wenig zu tun.

Einzelne Mails stur in Kategorien einzustufen ist die falsche Flughöhe. Die Confidence-Geschichte oben ist nur das Symptom. Der bessere Zuschnitt sind nicht abstrakte Klassen, sondern die Vorgänge, die in jedem Betrieb wiederkehren: eine Bestellung, eine Sendungsverfolgung, eine Rechnung, eine Zahlungserinnerung, ein Skonto-Hinweis. Die Frage ist nicht “was für eine Art Mail ist das”, sondern “zu welchem Geschäftsvorgang gehört sie”. Einen Posteingang ohne diese Vorgänge zu sortieren bringt wenig.

Und Kontext schlägt Modellgröße. Die Einordnung wird viel einfacher, wenn man nicht jede Mail nackt betrachtet, sondern zuerst nach Absender vorsortiert und ein Adressbuch einbezieht. Ist der Absender ein bekannter Kunde, ein Lieferant, oder steht er gar nicht im Adressbuch? Diese eine Information grenzt die möglichen Vorgänge so weit ein, dass das Modell deutlich weniger raten muss. Kontakte zu kennen und zu sortieren ersetzt einen guten Teil dessen, wofür man sonst ein größeres Modell bemühen würde.

Die Anhänge habe ich dabei unterschätzt. Ein PDF mit mehreren abfotografierten Quittungen in einer Datei ist kein Sonderfall, sondern Alltag, und es zu zerlegen ist mehr Arbeit als die Mail selbst. Das Parsen und Deuten der Anhänge ist der unauffällige Brocken im ganzen Ablauf.

Was bei alldem getragen hat, ist der nüchterne Aufbau. Klar geschnittene, hexagonale Komponenten, jede mit einer Aufgabe, die wie Bausteine zu einer Verarbeitungs-Strecke zusammengesteckt werden, im Grunde die klassischen Enterprise Integration Patterns, nur lokal und mit einem Sprachmodell als einem der Bausteine. Das ist die Adapter-Linie aus Teil 1, eine Ebene weitergedacht. Und eine Vermutung zum Schluss, die ich noch nicht belegen kann: Am meisten vereinfacht am Ende vermutlich nicht ein Kniff im Code, sondern der Blick auf den Geschäftsprozess dahinter. Wer den Vorgang erst ordnet und dann automatisiert, bekommt vermutlich die einfachere und die wartbarere Lösung. Beweisen muss ich das noch.

Was die Messung nicht beweist

Wie in Teil 1 sind die Zahlen Datenpunkte, keine Statistik.

Die Mengen sind klein, vierzehn Fixtures, dreiundzwanzig echte Mails, einzelne Scans. Das belegt, dass etwas funktioniert oder bricht, nicht in wie viel Prozent der Fälle. Prozent-Quoten als Qualitätsversprechen gibt dieser Stand nicht her.

Die Latenz von rund 2,5 Sekunden pro Mail stammt aus dem Fixture-Benchmark und ist spezifisch für diese Karte. Für den Lauf gegen echte Mails habe ich keine saubere Pro-Mail-Zeit erfasst, und auch die Quantisierung war dort nicht protokolliert. Gemessen ist außerdem nur der Extraktions-Schritt, nicht die vorgelagerte Klassifikation, die echte Zeit pro Mail im Vollbetrieb liegt also etwas höher.

Der Stromverbrauch ist geschätzt, nicht über einen Betriebsmonat gemessen. Und der stärkste fehlende Beleg wäre ein Wiederholungslauf der dreiundzwanzig echten Mails mit dem inzwischen gebauten Reparatur-Schritt: Er würde zeigen, dass die beiden Brüche jetzt aufgefangen werden. Behaupten kann ich das bis dahin nicht, nur, dass sie genau die Sorte Fehler sind, für die der Schritt gebaut wurde. Das steht aus.

Fazit

Damit ist die Frage aus Teil 1 beantwortet: Die Qualität reicht, das Tempo reicht auch, auf Hardware, die ein kleiner Betrieb sich hinstellen kann. Das Modell war am Ende der einfachste Teil.

Was bleibt, ist der Schritt von der Messung zum laufenden Werkzeug, eine Mail kommt rein, wird einem Vorgang zugeordnet, und löst je nach Vorgang etwas aus. Das wäre der nächste, der interessante Teil. Versprechen tue ich ihn nicht. Wenn daraus etwas wird, das man zeigen kann, zeige ich es, und nicht vorher.


Zur Methodik: Die Messwerte in diesem Artikel sind über viele Entwicklungs-Sessions entstanden. Das Zusammentragen und PII-freie Aufbereiten aus den Protokollen hat ein KI-Agent übernommen, ich habe es geprüft und eingeordnet. Wo ein Lauf nur noch im Protokoll belegt ist, weil das ausführende Modul inzwischen gelöscht wurde, steht das im Text. Das gehört für mich zur selben Mess-Ehrlichkeit wie die offen genannten Grenzen.

Womit ich mich sonst beschäftige, steht hier.