Warum kleine Sprachmodelle Anweisungen ignorieren, und was dagegen hilft

Warum kleine Sprachmodelle Anweisungen ignorieren, und was dagegen hilft

Das Symptom

Ein Sprachmodell mit 1,2 Milliarden Parametern, lokal auf einem iPhone. Deutscher Text rein, englische Antwort raus. Jedes Mal.

Der Fall stammt aus SpeakFlow AI, meiner iOS-App für Sprache-zu-Text und Text-zu-Sprache, in der die KI wahlweise komplett ohne Cloud läuft.

Im System-Prompt stand die Anweisung, die praktisch überall so drinsteht:

„respond in the same language as the text below"

Das ist keine exotische Formulierung. Die findest du in unzähligen Tutorials, und mit den großen Modellen funktioniert sie zuverlässig. Hier nicht.

Der Fall ist es wert, auseinandergenommen zu werden, weil die naheliegende Vermutung falsch war und die richtige Erklärung über dieses eine Projekt hinaus gilt.

Kam der Prompt überhaupt an?

Erste Vermutung, und ehrlich gesagt die bequemste: Das Modell bekommt den System-Prompt gar nicht. Vielleicht wirft die Bibliothek ihn weg, vielleicht kennt das Modell die Rolle nicht.

Diese Vermutung lässt sich prüfen, statt sie zu glauben. Jedes dieser Modelle bringt eine Datei mit, die beschreibt, wie eine Unterhaltung für das Modell zusammengebaut wird, inklusive der Rollen. Ein Blick hinein zeigte: Die System-Rolle wird sauber verarbeitet und landet an der richtigen Stelle im Text, der beim Modell ankommt.

Damit war die bequeme Erklärung tot. Der Prompt kam an. Das Modell befolgte ihn einfach nicht.

Das eigentliche Problem: du stellst eine Denkaufgabe

Schau dir die Anweisung noch einmal genau an. „Antworte in derselben Sprache wie der Text unten."

Das ist keine Anweisung. Das sind zwei Arbeitsschritte:

  1. Bestimme, in welcher Sprache der Text unten verfasst ist.
  2. Wende das Ergebnis dieser Bestimmung auf deine eigene Ausgabe an.

Ein Modell der GPT-Klasse macht das nebenbei. Ein Modell mit ein bis zwei Milliarden Parametern nicht zuverlässig. Es sieht einen englischen Prompt, es sieht englische Anweisungen drumherum, und es tut das Naheliegendste: Es antwortet englisch.

Das ist der Kern, und er lässt sich verallgemeinern. Wenn ein kleines Modell eine Anweisung nicht befolgt, lohnt zuerst die Frage, ob du ihm eine Überlegung abverlangst, die du selbst deterministisch lösen könntest.

Kleine Modelle sind schlecht in Meta-Aufgaben. Sie sind ordentlich darin, eine klare Ansage auszuführen. Der Unterschied klingt nach Haarspalterei und entscheidet in der Praxis, ob etwas funktioniert.

Die Lösung ist fast peinlich einfach

Die App kennt den Text ja. Sie hat ihn selbst entgegengenommen, bevor sie ihn an das Modell weitergibt. Also bestimmt sie die Sprache selbst.

Dafür braucht es kein weiteres Modell. Ein paar hundert Byte Heuristik über häufige Funktionswörter und Umlaute reichen für die Sprachen, die hier in Frage kommen. Aus dem Ergebnis wird eine direkte Ansage:

„Answer in German."

Kein Zwischenschritt, keine Bedingung, keine Ableitung. Dasselbe Modell, dieselbe Bibliothek, und es funktioniert.

Zwei Details haben zusätzlich den Ausschlag gegeben, und die sind übertragbar:

Die Sprachanweisung steht am Ende des Prompts. Kleine Modelle gewichten das Prompt-Ende stärker. Stand die Anweisung mittendrin, gefolgt von zwei englischen Absätzen, ging sie unter.

Sie wird nach dem eigentlichen Text noch einmal wiederholt. Alles dazwischen ist englisch, und das Modell driftet ohne Erinnerung dorthin zurück.

Beides ist kein Geheimwissen, aber es steht auch in keinem Tutorial, weil Tutorials mit großen Modellen geschrieben werden, bei denen es egal ist.

Wenn das Modell plötzlich Silben halluziniert

Ein zweiter Fall aus demselben Projekt, der gut zeigt, wie sich Fehler bei lokalen Modellen tarnen.

Ein 488 MB großes Spracherkennungsmodell lieferte nach dem Download nur noch nda nda nda nda. Endlos, bei jeder Aufnahme.

Die Prüfung „ist das Modell vollständig heruntergeladen?" arbeitete mit einem Prozent Toleranz auf die Dateigröße. Klingt vernünftig. Bei 488 MB sind ein Prozent aber 4,9 MB, die fehlen dürfen. Eine abgeschnittene Datei galt damit als sauber installiert.

Und jetzt kommt der Teil, der wehtut: Die Bibliothek, die das Modell ausführt, beantwortet eine korrupte Datei nicht mit einem Fehler. Sie liefert endlos wiederholte Silben. Es gibt keine Ausnahme, keinen Log-Eintrag, nichts. Nur Unsinn, der aussieht wie ein schlechtes Modell.

Die Lehre daraus ist allgemeiner Natur: Toleranzen bei Integritätsprüfungen sind gefährlich, wenn die nachgelagerte Komponente Korruption nicht erkennen kann. Wenn dein System die Beschädigung nicht bemerkt, darf deine Prüfung sie nicht durchwinken. Jetzt wird exakt verglichen, und eine unvollständige Datei wird gelöscht statt behalten.

Viermal war fremder Code die Ursache, nie die eigene Logik

Eine Beobachtung aus derselben Arbeitswoche, die mich selbst überrascht hat. Bei vier Fehlern, an denen ich länger als eine Stunde gesessen habe, war die Ursache jedes Mal eine Annahme über fremden Code. Kein einziges Mal die eigene Logik.

Zwei Beispiele, weil sie unterschiedliche Fallen zeigen.

Ein Fortschrittsbalken blieb bei 100 Prozent stehen, mit einem Abbrechen-Knopf, den nichts mehr auflöste. Der Blick in den Quellcode der verwendeten Bibliothek zeigte: Die Download-Funktion meldet einen Fortschritts-Empfänger an und nie wieder ab. Beim Hochladen ist das korrekt gepaart, beim fortsetzbaren Download nicht. Ein bereits unterwegs befindliches Ereignis wird also zugestellt, nachdem der Download längst fertig ist, und schreibt den gerade zurückgesetzten Fortschritt wieder auf 100 Prozent.

Das steht in keiner Typdefinition. Das steht in keiner Dokumentation. Das steht in zwei Zeilen Quellcode, 29 Zeilen auseinander.

Eine Versionsnummer war überall fest verdrahtet. Die App nutzt Updates, die ohne App-Store-Umweg ausgeliefert werden. Welche App ein Update annimmt, entscheidet eine Laufzeit-Version, und die stand in jedem je gebauten Paket auf 1.0.0. Auch in der Version im Store. Ein Update aus einem Zweig mit neuen nativen Bausteinen hätte diesen Code an eine App verteilt, der genau diese Bausteine fehlen.

Der naheliegende Fix funktionierte lokal einwandfrei und zerlegte den Build auf dem Server. Zwei verschiedene Prüfsummen, lokal und remote. Erst die Fehlermeldung eines völlig anderen Befehls lieferte die Erklärung: In diesem Projekt-Aufbau werden automatisch abgeleitete Laufzeit-Versionen gar nicht unterstützt. Die Lösung ist unspektakulär, eine manuell gepflegte Nummer, abgesichert durch einen Test, der fehlschlägt, sobald zwei Dateien auseinanderlaufen.

Nebenbei entlarvt: Ich hatte 25 GB lokale Build-Reste im Verdacht, die Prüfsumme zu verfälschen. Löschen brachte keine Änderung, der Wert blieb identisch. Gut, dass gemessen und nicht geraten wurde.

Wie du überhaupt ein Modell auswählst

Wenn du lokale Modelle anbietest, ist die Auswahl selbst eine Design-Entscheidung. Sieben Spracherkennungs-Modelle von 78 MB bis 1,08 GB nebeneinanderzustellen hilft niemandem, wenn die Unterschiede unklar sind.

Meine Regel dafür: Jeder Eintrag muss seine Nachbarn in mindestens einer Dimension schlagen. Größe, Tempo oder Genauigkeit. Sobald zwei Einträge in allen drei Dimensionen fast gleich sind, fliegt einer raus. Eine Liste aus gleichwertigen Optionen macht die Wahl nicht leichter, sondern schwerer.

Dazu kommt eine Prüfung vor dem Download: Was das Gerät nicht ausführen kann, wird ausgegraut, mit Begründung. Ein Modell, das mehr Arbeitsspeicher braucht als vorhanden, lässt das Betriebssystem die App mitten in der Berechnung abschießen. Für den Nutzer sieht das aus wie ein Absturz, und er wird es auch so bewerten.

Wie funktioniert das technisch?

Jetzt wird es kurz noch technischer. Wer die Prinzipien mitgenommen hat, kann zum Fazit springen.

Die Sprachbestimmung läuft als reine Funktion vor dem Modellaufruf, ohne Zustand und ohne Netzwerk. Sie schaut auf häufige Funktionswörter und typische Zeichen und liefert einen Sprachcode zurück. Dieser Code wird an zwei Stellen in den Prompt eingesetzt, einmal am Ende der Anweisungen und einmal nach dem Nutzertext.

Der Aufbau dahinter ist bewusst schlicht: Der Prompt wird an einer einzigen Stelle zusammengebaut, nicht verteilt über mehrere Module. Wenn das Verhalten eines Modells von der Reihenfolge im Prompt abhängt, und das tut es bei kleinen Modellen deutlich, dann darf diese Reihenfolge nicht an drei Orten entstehen. Sonst debuggst du beim nächsten Mal wieder von vorn.

Für Anwendungen mit personenbezogenen Daten gilt bei mir zusätzlich: Läuft es nicht lokal, läuft es über europäische Anbieter mit Servern in Deutschland. Nur die nötigen Inhalte gehen verschlüsselt raus, werden dort nicht gespeichert und nicht zum Training verwendet.

Fazit

Kleine Sprachmodelle sind keine kleinen Versionen der großen. Sie haben ein anderes Verhalten, und der wichtigste Unterschied ist ihre Schwäche bei Anweisungen, die eine Zwischenüberlegung verlangen.

Die Konsequenz für die Praxis ist unbequem, aber nützlich: Jede Entscheidung, die du selbst berechnen kannst, solltest du selbst berechnen und dem Modell als fertige Ansage geben. Sprache erkennen, Format festlegen, Auswahl treffen. Das Modell macht danach den Teil, den nur es kann.

Und der zweite Gedanke, der mich diese Woche mehr gekostet hat als jede Prompt-Frage: Bei fremden Bibliotheken lohnt der Blick in den Quellcode mehr als in die Typdefinition. Vier von vier Fehlern lagen dort. Eine Annahme über fremden Code ist kein Wissen, sie fühlt sich nur so an.

Verwandte Artikel

Du baust gerade etwas mit lokalen oder kleinen Modellen?

Lass uns in 25 Minuten über deinen Aufbau schauen, bevor du Wochen in die falsche Vermutung steckst. Kostenlos und unverbindlich.

Kostenloses Erstgespräch

Keine Cookies. Ehrenwort.

Kein Tracking, das dich verfolgt. Deine Privatsphäre ist mir wichtig.