Startseite / Artikel / Lohnt sich ein größeres LoRA? Genauigkeit gegen Kosten messen statt nach Gefühl

Lohnt sich ein größeres LoRA? Genauigkeit gegen Kosten messen statt nach Gefühl

Ein kleines Test-Harness, das LoRA-Rezepte als Daten behandelt und Genauigkeit, Trainingszeit, Latenz und Speicher ausgibt, damit Entscheidungen über Rang und Iterationen auf Zahlen beruhen.

1242 Wörter

Ein Ordner mit dreizehn Experimenten

Stellen Sie sich einen Ordner mit dreizehn Fine-Tuning-Experimenten vor. Darin geht es um Stiltransfer, JSON-Extraktion, Tool-Routing, Preference-Tuning für Commit-Nachrichten, Chain-of-Thought-Destillation und einen multimodalen Lauf, der Balkendiagramme liest. Alles ist LoRA auf einem 1B-Llama, lokal auf einem Mac mit M-Chip über MLX trainiert. Experiment Nummer zwölf hat den Arbeitsablauf verändert. Es bringt dem Modell nichts Neues bei. Es misst nur die anderen ordentlich.

Die Frage, die aus dem Bauch heraus beantwortet wird

Nehmen wir eine Klassifikationsaufgabe. Das könnte das Routing von Support-Tickets sein. Hier ging es darum, GitHub-Pull-Requests mit Conventional-Commit-Kategorien zu versehen: bug, feat, docs, refactor, test, chore, perf und style. Das ergibt acht Klassen mit sauber getrennten Trainings- und Testdaten.

Sie feintunen ein kleines Modell, und es funktioniert. Dann kommt die Frage, die immer kommt: Wäre Rang 16 besser als Rang 4?

Meist wird sie per Überlegung beantwortet. Der Rang bestimmt die Dimension des Low-Rank-Updates, mehr Rang heißt mehr Kapazität, die Aufgabe hat acht Klassen und gewisse sprachliche Feinheiten, also wahrscheinlich ja. Dann wird der Rang erhöht, die Genauigkeit verschiebt sich um einen Betrag, der leicht Rauschen sein könnte, und die Änderung bleibt, weil die Zeit ohnehin investiert ist. Das ist keine Ingenieursarbeit. Das ist eine Ahnung mit angehängter Konfigurationsdatei.

Die eigentliche Lücke bestand darin, dass niemand fragte, ob sich ein größerer Adapter lohnt, und dafür braucht man etwas, durch das man teilen kann. Ein Adapter mit Rang 16 über 16 Schichten hat etwa achtmal so viele trainierbare Parameter wie Rang 4 über 8 Schichten. Er trainiert länger, braucht länger zum Fusionieren und ist ein anderes Artefakt, das ausgeliefert werden muss. Bringt er einen Prozentpunkt Genauigkeit, ist das ein schlechter Tausch. Bringt er fünfzehn, ist die Sache klar. Ohne die Kostenseite je zu messen, ließ sich nicht sagen, welcher Fall vorlag.

Rezepte als schlichte Daten

Die Lösung war ein Harness, das auf einer langweiligen Idee beruht, und genau darum geht es: Eine Trainingskonfiguration ist Daten, kein Code.

RECIPES = [
    {"name": "base-zs",      "train": None, "shots": 0},
    {"name": "base-fs",      "train": None, "shots": 4},
    {"name": "lora-r4-l8",   "train": {"lora_rank": 4,  "num_layers": 8,  "iters": 600}},
    {"name": "lora-r16-l16", "train": {"lora_rank": 16, "num_layers": 16, "iters": 600}},
    {"name": "lora-long",    "train": {"lora_rank": 8,  "num_layers": 8,  "iters": 1500}},
]

Fünf Listeneinträge, mehr nicht. Für jeden erledigt der Sweep alles: Er trainiert den Adapter, fusioniert ihn mit den Basisgewichten, fügt das fusionierte Modell über ein generiertes Modelfile zu Ollama hinzu, evaluiert es auf dem zurückgehaltenen Testset und hält das Ergebnis fest.

Zwei der Rezepte kommen ganz ohne Training aus. Das ist Absicht, und diese Idee lohnt sich am meisten zu übernehmen. base-zs ist das rohe 1B-Modell im Zero-Shot-Betrieb. base-fs ist dasselbe Modell mit vier Beispielen im System-Prompt:

FS_EXAMPLES = (
    "Title: fix crash when checkout receives empty payload\nBody: Guard added.\nLabel: bug\n\n"
    "Title: add bulk delete to dashboard\nBody: New endpoint + UI.\nLabel: feat\n\n"
    "Title: bump dependencies for billing\nBody: Routine maintenance.\nLabel: chore\n\n"
    "Title: speed up search list query with index\nBody: p95 480ms -> 120ms.\nLabel: perf\n"
)

Few-Shot-Prompting kostet fast nichts: einen längeren Prompt und etwa zehn Minuten, um Beispiele zu schreiben. Fine-Tuning bringt einen Trainingslauf, ein fusioniertes Modellartefakt, eine Deployment-Geschichte und die Verpflichtung, neu zu trainieren, sobald sich die Labels ändern. Ohne Vergleich mit der kostenlosen Variante kann man nicht behaupten, dass die teure nötig war. Keines der elf früheren Projekte hatte eine Few-Shot-Baseline, was im Nachhinein peinlich wirkt.

Vier Zahlen pro Rezept

Jedes Rezept bekommt vier Messwerte, und der Nutzen entsteht dadurch, dass man sie nebeneinander sieht:

  • Genauigkeit, um die es bei all dem überhaupt geht
  • Trainingszeit als Wall-Clock-Zeit, gemessen mit perf_counter um den echten Subprozess herum
  • Inferenzlatenz als Median über das gesamte Testset, nicht als Mittelwert
  • Spitzenspeicher, der zeigt, ob das Ganze auf Ihren eigenen Rechner passt

Die Zeit wird gemessen statt geschätzt:

t0 = time.perf_counter()
subprocess.run(cmd, check=True)
train_secs = time.perf_counter() - t0

Der Median ist dem Mittelwert vorzuziehen, weil ein einzelner Kaltstart-Ausreißer zu Beginn eines Laufs den Mittelwert auf einen sinnlosen Wert ziehen kann, und dann verbringen Sie einen Nachmittag damit, einem Modell nachzujagen, mit dem alles in Ordnung ist.

Auf einem M1 Pro dauert der komplette Sweep etwa 45 Minuten, und das ist der eigentliche Gewinn. Fünfundvierzig unbeaufsichtigte Minuten sind so billig, dass es einfacher ist, den Sweep laufen zu lassen, als darüber zu diskutieren, und das ändert Ihr Standardverhalten. Meinungen über Hyperparameter weichen einer Tabelle.

Was der Sweep auseinanderhalten kann

Eine unerwartete Erkenntnis hatte mit keiner einzelnen Zahl zu tun. Das Rezept lora-long (Rang 8, 8 Schichten, 1500 Iterationen) dient dazu, zwei Variablen zu trennen, die ständig verwechselt werden. „Ein größeres LoRA hat geholfen“ heißt meist, dass der Rang erhöht wurde und das Training zugleich länger lief, weil die Konfiguration einmal geändert und ein Ergebnis abgelesen wurde. Das sind getrennte Hebel. Mehr Rang bringt Kapazität, mehr Iterationen bedeuten, mit der vorhandenen Kapazität weiter zu konvergieren. Einer Aufgabe kann es am einen fehlen, während sie vom anderen genug hat, und die Abhilfe ist je nach Fall völlig verschieden.

Halten Sie den Rang bei 8 fest und erhöhen Sie die Iterationen von 600 auf 1500, dann ist der Effekt isoliert. Sie zahlen die zweieinhalbfache Trainingszeit bei gleicher Artefaktgröße und gleichen Inferenzkosten. Bleibt das Ergebnis nahe am Lauf mit 600 Iterationen, war die Aufgabe nie durch Kapazität begrenzt, und der Rang war eine falsche Fährte. Springt es nach oben, war das Modell die ganze Zeit untertrainiert, und die Rang-Debatte hat am falschen Regler gedreht. Kein noch so langes Starren auf eine Konfigurationsdatei beantwortet das.

Ein kurzer Schlussabsatz im Bericht wird nach einfachen Regeln gewählt: Er nennt das Rezept mit der höchsten Genauigkeit und das mit der besten Genauigkeit pro Trainingssekunde. Zwei verschiedene Sieger sind der Normalfall. Beide zu nennen erzwingt eine echte Entscheidung, wo „die höchste Zahl gewinnt“ sie stillschweigend für Sie getroffen hätte.

Eine Grenze, die man nennen sollte

Das Harness deckt Klassifikation ab, und das ist der einfache Fall: Genauigkeit ist ein exakter Abgleich mit einem bekannten Label, die Bewertung also nur ein kostenloser String-Vergleich. Bei Generierungsaufgaben funktioniert das nicht. Ein ähnlicher Vergleich für ein Projekt mit einer Selbstverbesserungsschleife musste ein LLM als Richter einsetzen, mit exakten Abgleichen nur für die strukturierten Felder, und dann wurde die Zuverlässigkeit des Richters zu einer eigenen Frage.

Die Struktur bleibt trotzdem erhalten. Rezepte bleiben Daten, Kosten werden auf derselben Uhr gemessen wie Qualität, und die Tabelle behält ihre vier Spalten. Nur die Genauigkeitsfunktion wird schwieriger.

Worauf es hinausläuft

Die meisten Diskussionen über Fine-Tuning drehen sich um Qualität, dabei geht es bei den meisten Entscheidungen, vor denen Sie tatsächlich stehen, um den Preis. Rang, Schichten und Iterationen kosten jeweils etwas anderes. Um sie fair zu vergleichen, brauchen Sie ein Harness, das so schnell läuft, dass niemand stattdessen lieber diskutiert. Ein 45-minütiger unbeaufsichtigter Job ist billiger als ein selbstsicherer Engineer und liegt seltener falsch, also erst die Tabelle erstellen und danach die Meinung bilden.