Modell-Fine-Tuning

Wenn Sie ein Modell speziell für die Generierung von E-Commerce-Produktbeschreibungen benötigen, gibt es drei Hauptoptionen.

  • Die erste ist das Vortraining von Grund auf. Dies erfordert das Sammeln massiver Textdaten (möglicherweise Billionen von Token), das Anmieten von Dutzenden A100-GPUs für monatelanges Training und das Aufwenden von Millionen von Dollar. Dies tun Giganten wie OpenAI und Google, und es ist für die allermeisten Menschen unrealistisch.

  • Die zweite ist Prompt Engineering. Man gibt einem allgemeinen großen Modell einen sorgfältig gestalteten Prompt, wie zum Beispiel: „Sie sind ein professioneller E-Commerce-Texter. Bitte generieren Sie eine attraktive Beschreibung basierend auf den folgenden Produktinformationen...“ Dieser Ansatz ist mit keinen Kosten verbunden und sofort einsatzbereit, aber die Probleme sind: Sie müssen jedes Mal lange Prompts schreiben, die Ergebnisse sind instabil, er wird leicht durch irrelevante Eingaben gestört und der Token-Verbrauch ist hoch.

  • Die dritte ist das Fine-Tuning. Man nimmt ein trainiertes allgemeines Modell, trainiert es eine kurze Weile mit Ihren proprietären Daten weiter und lässt es Ihre spezifische Aufgabe „lernen“. Das resultierende Modell behält seine allgemeinen Fähigkeiten bei, während es bei Ihrer Aufgabe hervorragende Leistungen erbringt, und die Kosten sind kontrollierbar.

Vergleich der drei Ansätze:

AnsatzKostenEffektivitätAnwendbare Szenarien
Vortraining von Grund aufExtrem hoch (Millionen von Dollar)Vollständig angepasstErstellung eines neuen Basismodells (nur für Giganten)
Prompt-EngineeringNahezu nullInstabil, abhängig von PromptsEinfache Aufgaben, schnelle Validierung
Fine-TuningMittel (Hunderte bis Tausende von Dollar)Stabil und professionellAufgabenspezifische Domänen, Produktisierung

Verwenden wir eine einfache Analogie:

  • Das Vortraining von Grund auf ist wie die Heranbildung eines Arztes von Null an durch Sie selbst, was mehr als ein Jahrzehnt dauert.
  • Prompt Engineering ist wie das Aushändigen eines detaillierten Handbuchs an einen gewöhnlichen Arzt.
  • Fine-Tuning ist wie das Ermöglichen einer fachspezifischen Weiterbildung für einige Monate für einen bereits approbierten Arzt, um ein Facharzt-Experte zu werden.

Warum Fine-Tuning erforderlich ist

Nicht jede Aufgabe benötigt Fine-Tuning; klären Sie zuerst, wann es zu verwenden ist und wann nicht.

Einschränkungen des Prompt Engineerings

Prompt Engineering ist leistungsstark, hat jedoch mehrere Obergrenzen, die schwer zu durchbrechen sind.

  • Die erste Einschränkung ist die Begrenzung des Kontextfensters.Ihre Prompts und Beispiele müssen in das Kontextfenster des Modells passen (z. B. 4K, 8K, 128K, 1024K Token). Wenn Ihre Aufgabe Hunderte von Beispielen erfordert, um klar beschrieben zu werden, kann der Prompt diese nicht enthalten.

  • Die zweite Einschränkung sind instabile Ergebnisse.Derselbe Prompt kann bei unterschiedlicher Formulierung sehr unterschiedliche Ergebnisse liefern. Wenn das Modell in „guter Stimmung“ ist (Stichproben-Zufälligkeit), ist die Ausgabe gut; wenn nicht, weicht sie möglicherweise völlig vom Thema ab.

  • Die dritte Einschränkung sind hohe Inferenzkosten.Jede Inferenz erfordert das Senden des gesamten langen Prompts, wodurch viele Token verbraucht und die Antwortzeit verlangsamt wird. In Szenarien mit hoher Nebenläufigkeit können die Kosten in die Höhe schnellen.

  • Die vierte Einschränkung ist das Vergessensproblem.Obwohl das Modell Ihren Prompt gelesen hat, ziehen die massiven Vortrainierungsdaten davor die Sache möglicherweise immer noch herunter. Wenn Sie es beispielsweise auffordern, JSON auszugeben, fügt es möglicherweise dennoch viele Erklärungen in natürlicher Sprache hinzu.

Anwendbare Szenarien für Fine-Tuning

Fine-Tuning ist eine Überlegung wert, wenn Ihre Aufgabe die folgenden Merkmale aufweist.

  • Merkmal 1: Klare Aufgabendefinition und festes Ausgabeformat. Zum Beispiel „technische Dokumentation in einen leicht verständlichen Blog übersetzen“, „Ticket-Zusammenfassungen aus Kundendienstprotokollen generieren“, „natürliche Sprache in SQL-Abfragen konvertieren“.

  • Merkmal 2: Sie verfügen über ein paar Hundert bis ein paar Tausend hochwertige, beschriftete Datenproben. Zu wenig Daten führen beim Fine-Tuning zu keinen Ergebnissen, und zu viele sind unnötig – im Allgemeinen ist der Bereich von 500 bis 5.000 Proben ideal.

  • Merkmal 3: Sie benötigen professionelle Leistung in einer bestimmten Domäne. Zum Beispiel Interpretation medizinischer Berichte, Zusammenfassung juristischer Dokumente, Analyse von Finanznachrichten. Allgemeine Modelle verstehen Branchenterminologie und -konventionen möglicherweise nicht; Fine-Tuning kann ihnen helfen, „in die Branche einzusteigen“.

  • Merkmal 4: Sie reagieren empfindlich auf Antwortgeschwindigkeit und Kosten. Ein kleines feinabgestimmtes Modell kann ein allgemeines großes Modell übertreffen, mit einer 10-mal schnelleren Inferenzgeschwindigkeit und 100-mal geringeren Kosten.

  • Merkmal 5: Sie benötigen die strikte Einhaltung des Ausgabeformats. Die Ausgabe muss beispielsweise JSON sein, einen bestimmten Tonfall verwenden und bestimmte Felder enthalten. Durch Fine-Tuning kann sich das Modell diese Anforderungen „merken“, ohne sie jedes Mal im Prompt wiederholen zu müssen.

Kosten-Nutzen-Analyse des Fine-Tunings

Machen Sie vor dem Fine-Tuning zuerst die Rechnung.

Die Kosten stammen hauptsächlich aus drei Aspekten: Datenaufbereitung, Rechenressourcen und manuelles Debugging. Die Datenaufbereitung erfordert in der Regel die meiste Arbeit – Sie müssen Daten sammeln, bereinigen und kennzeichnen. Rechenressourcen sind mittlerweile sehr günstig; mit LoRA + QLoRA kann eine verbraucherorientierte GPU (RTX 3090/4090) 7B/13B-Modelle feinabstimmen.

Die Vorteile spiegeln sich in mehreren Aspekten wider: bessere Ergebnisse, geringere Inferenzkosten, schnellere Antwortzeiten und stabilere Ausgaben. Wenn Ihr Modell Dienste extern bereitstellt, häufen sich diese Vorteile weiter an.

Wann sollten Sie kein Fine-Tuning durchführen? Wenn sich Aufgaben rasant ändern und sich die Datenformate jede Woche ändern, ist Prompt Engineering flexibler. Wenn Sie nur eine einmalige Erkundung durchführen oder nur ein Dutzend Proben haben, lohnt sich ein Fine-Tuning ebenfalls nicht.

FaktorBesser geeignet für PromptBesser geeignet für Fine-Tuning
Datenmenge< 100 Proben> 500 Proben
AufgabenstabilitätÄndert sich häufigRelativ stabil
Häufigkeit der NutzungGelegentliche NutzungHochfrequente Nutzung
KostensensitivitätNiedrigHoch
QualitätsanforderungenGut genug reicht ausAn streben nach dem Besten

Vollständiges Fine-Tuning

Verstehen Sie zuerst die traditionellste Fine-Tuning-Methode und sehen Sie sich dann an, warum wir sie heutzutage selten verwenden.

Prinzipien und Prozess

Die Idee des vollständigen Fine-Tunings ist einfach: Man nimmt alle Parameter des vortrainierten Modells und trainiert mit dem eigenen Datensatz weiter, wobei alle Gewichte aktualisiert werden.

Der Ablauf sieht in etwa so aus:

  • Erster Schritt: Daten vorbereiten. Die Aufgabendaten in "Eingabe-Ausgabe"-Paare organisieren.

  • Zweiter Schritt: Das vortrainierte Modell laden. Zum Beispiel Open-Source-Modelle wie LLaMA-2-7B und Mistral-7B herunterladen.

  • Dritter Schritt: Trainingsparameter festlegen. Die Lernrate sollte sehr klein eingestellt werden (z. B. 1e-5 bis 5e-5), da das während des Vortrainings gelernte Wissen nicht vollständig überschrieben werden soll.

  • Schritt 4: Das Training starten. Das Modell ein paar Epochen auf den Daten laufen lassen (den Datensatz mehrmals vollständig durchlaufen).

  • Fünfter Schritt: Das Modell speichern. Nach dem Training erhält man eine brandneue Gewichtsdatei in der gleichen Größe wie das Originalmodell.

Beispiel

# ============================================
# Konzeptdemonstration des vollständigen Fine-Tunings (Pseudocode, Framework-unabhängig)
# Zeigt die Kernidee; der eigentliche Code wird HuggingFace verwenden.
# ============================================

def full_finetuning_concept():
    Demonstration der Kernkonzepte des vollständigen Fine-Tunings
    # 1. Vortrainiertes Modell laden (alle Parameter sind trainierbar)
    model = load_pretrained_model("llama-2-7b")
    # Das Modell hat 7 Milliarden Parameter und jeder einzelne Parameter wird aktualisiert.
    print(fTotal model parameters: {count_parameters(model)})
    # Ausgabe: Gesamtzahl der Modellparameter: 7000000000

    2. Aufgabenspezifische Daten vorbereiten.
    train_data = [
        {"input": Product: Insulated cup\nGenerate e-commerce description.,
         "output": This high-quality thermos cup features a 304 stainless steel inner liner and vacuum insulation technology, keeping beverages hot for up to 12 hours.},
        {"input": Product: Wireless Earphones\nGenerate e-commerce description.,
         "output": True wireless Bluetooth earphones, stable Bluetooth 5.3 connection, active noise cancellation, 24-hour battery life, comfortable to wear.},
        # ... Hunderte oder Tausende ähnlicher Dateneinträge
    ]

    # 3. Optimierer und eine sehr kleine Lernrate einstellen
    optimizer = create_optimizer(model, learning_rate=2e-5)

    # 4. Trainingsschleife
    for epoch in range(3):  # Normalerweise 2-5 Epochen trainieren
        for batch in create_batches(train_data):
            Vorwärtspropagierung
            loss = model.compute_loss(batch)
            Rückwärtspropagierung, alle Parameter aktualisieren!
            loss.backward()
            optimizer.step()
            optimizer.zero_grad()
        print(fEpoch {epoch} complete)

    # 5. Vollständiges Modell speichern (7 Milliarden Parameter, ca. 13 GB)
    model.save("llama-2-7b-ecommerce-finetuned")
    print(Full fine-tuning complete, new model saved.)

Dies ist nur eine Konzeptdemonstration; das eigentliche Training erfordert eine große Menge an VRAM.

Das Problem des katastrophalen Vergessens

Das vollständige Fine-Tuning hat ein schwerwiegendes Problem: das katastrophale Vergessen.

Was bedeutet das? Wenn man mit den eigenen Daten trainiert, "vergisst" das Modell nach und nach das allgemeine Wissen, das es während des Vortrainings erworben hat. Das ist wie bei einer Person, die panisch Matheaufgaben für eine Prüfung büffelt und am Ende das zuvor gelernte Chinesisch und Englisch vergisst.

Warum passiert das? Weil das vollständige Fine-Tuning alle Parameter aktualisiert. Das beim Vortraining gelernte Wissen ist in diesen Parametern kodiert, und drastische Änderungen können das ursprüngliche Wissen zerstören.

Das Resultat ist: Das feinabgestimmte Modell schneidet bei der spezifischen Aufgabe gut ab, aber seine Leistung bei anderen allgemeinen Aufgaben sinkt. Schlimmer noch, es kann einige grundlegende Fähigkeiten verlieren, wie das Befolgen von Anweisungen und das Verstehen komplexer Probleme.

Wie lässt sich das abmildern? Man kann Vortrainingsdaten für ein gemeinsames Training beimischen, aber das führt uns zum alten Problem zurück, dass "massenhaft Daten benötigt werden". Alternativ kann man Regularisierung verwenden, um zu verhindern, dass sich Parameter zu stark verändern, aber der Effekt ist begrenzt.

Berechnung des VRAM-Bedarfs

Ein weiteres Problem des vollständigen Fine-Tunings ist der viel zu große VRAM-Bedarf. Rechnen wir nach:

Für das Training eines Modells benötigter VRAM = Modellparameter + Gradienten + Optimierer-Zustände + Aktivierungen.

Für ein LLaMA-7B-Modell in einfacher Präzision (FP32) gilt: 7 Milliarden Parameter × 4 Byte = 28 GB. Gradienten beanspruchen ebenfalls 28 GB und die Optimierer-Zustände (Adam-Optimierer) erfordern 56 GB (das Doppelte der Gradienten). Aktivierungen variieren je nach Batch-Größe und Sequenzlänge und benötigen ebenfalls mindestens einige GB.

Zusammengefasst erfordert das vollständige Fine-Tuning eines 7B-Modells etwa 100+ GB VRAM — das bedeutet, es werden mindestens 8 A100-GPUs (40 GB) oder 2 A100-GPUs (80 GB) benötigt.

Wird Halbpräzision (FP16/BF16) verwendet, halbiert sich der Speicherbedarf, es werden aber immer noch rund 50-60 GB VRAM benötigt. Das ist für eine Normalperson nach wie vor unerschwinglich.

ModellgrößeVRAM für vollständiges Fine-Tuning (FP16)Benötigte GPUs
7B50-60 GB2×A100 (40GB) oder 1×A100 (80GB)
13B90-100 GB3×A100 (40GB) oder 2×A100 (80GB)
70B400+ GB12×A100 (40GB) oder 6×A100 (80GB)

Vollständiges Fine-Tuning ist nicht unbrauchbar, aber es ist kostspielig und riskant. Sofern kein zwingender Grund vorliegt (z. B. eine sehr große Datenmenge und die Notwendigkeit, das fundamentale Verhalten des Modells zu ändern), sollten parametereffiziente Fine-Tuning-Methoden (PEFT) priorisiert werden.


Parametereffizientes Fine-Tuning (PEFT)

PEFT (Parameter-Efficient Fine-Tuning) ist ein Sammelbegriff für eine Klasse von Methoden, bei denen nur eine kleine Anzahl von Parametern trainiert wird. Es behält die Effektivität des Fine-Tunings bei und reduziert gleichzeitig die Kosten enorm.

Kernidee von PEFT

Die Kernaussage von PEFT ist:Das vortrainierte Modell enthält bereits ausreichend Wissen. Wir müssen nicht alle Parameter modifizieren; wir müssen nur einen kleinen Teil der Parameter "anpassen", um uns an die neue Aufgabe anzupassen.。

Das ist wie bei einem Klavier, das bereits gebaut wurde. Man muss nicht das gesamte Klavier neu bauen; man muss nur ein paar Saiten feinjustieren, um den gewünschten Ton zu erzeugen.

Die Vorteile von PEFT liegen auf der Hand:

  • Erstens wird der Speicherbedarf drastisch reduziert. Wo früher 60 GB nötig waren, reicht es jetzt vielleicht schon mit 6 GB.

  • Zweitens gibt es kein katastrophales Vergessen. Die ursprünglichen Modellparameter bleiben unverändert; wir fügen lediglich etwas Neues hinzu, sodass das vorhandene Wissen nicht zerstört wird.

  • Drittens: geringe Speicherkosten. PEFT speichert typischerweise nur eine kleine Anzahl neu hinzugefügter Parameter (einige MB bis einige hundert MB) und muss nicht das gesamte Modell speichern.

  • Viertens ermöglicht es "Aufgabenkomposition". Trainieren Sie mehrere kleine Adapter, laden Sie sie bei der Inferenz bei Bedarf, und ein Modell kann als mehrere Modelle dienen.

Überblick über gängige PEFT-Methoden

PEFT ist keine einzelne Methode, sondern ein Sammelbegriff für eine Klasse von Methoden. Sehen wir uns einige gängige Methoden an:

  • Die erste ist Adapter. Fügen Sie kleine neuronale Netzwerkmodule in die Mitte jeder Transformer-Schicht ein. Während des Trainings werden nur diese Adapter trainiert, und die ursprünglichen Modellparameter werden eingefroren. Dies ist eine frühe PEFT-Methode und wird heute seltener verwendet.

  • Die zweite ist Prefix Tuning. Fügen Sie vor jeder Schicht des Modells lernbare "Präfix"-Vektoren hinzu. Diese Präfixvektoren machen nur einen kleinen Teil aus, aber sie können die Ausgabe des Modells steuern.

  • Die dritte ist Prompt Tuning. Fügen Sie nur in der Eingabeschicht lernbare Soft Prompts hinzu. Dies ist die einfachste Methode, aber ihre Wirkung ist relativ begrenzt.

  • Die vierte ist LoRA (Low-Rank Adaptation). Fügen Sie neben den Attention-Schichten des Modells niedrigrangige Matrizen hinzu. Dies ist derzeit die beliebteste und effektivste PEFT-Methode.

  • Die fünfte ist QLoRA (Quantized LoRA). Aufbauend auf LoRA fügt sie eine Modellquantisierung hinzu, um den Speicherbedarf weiter zu reduzieren. Dies ist die heutige Mainstream-Wahl.

MethodePrinzipAnzahl trainierbarer ParameterWirkung
Vollständiges Fine-TuningAlle Parameter aktualisieren100%Gut, aber kann vergessen
AdapterKleines Netzwerk einfügen0.1%-1%Mittel
Prefix TuningPräfixvektoren hinzufügen0.1%-1%Mittel
Prompt TuningSoft Prompts hinzufügen< 0.1%Durchschnittlich
LoRANiedrigrang-Matrixanpassung0.1%-1%Nahe an vollständigem Fine-Tuning
QLoRAQuantisierung + LoRA0.1%-1%Nahe an vollständigem Fine-Tuning

Die heutige Best Practice ist klar:Bevorzugen Sie LoRA und verwenden Sie QLoRA, wenn der Speicher nicht ausreicht. Diese beiden Methoden sind effektiv, kostengünstig und haben ein ausgereiftes Ökosystem, was sie in den meisten Fällen zur optimalen Wahl macht.


LoRA(Low-Rank Adaptation)

LoRA ist derzeit die gängigste Fine-Tuning-Methode, und es ist wichtig, ihr Prinzip zu verstehen.

Prinzip der Niedrigrang-Zerlegung (Intuitives Verständnis)

Verstehen Sie zunächst, was "Rang" ist und was "niedrigrangig" bedeutet.

Angenommen, Sie haben eine 100 × 100 Matrix, und die darin enthaltenen Daten sind möglicherweise nicht völlig zufällig. Wenn diese Daten aus nur wenigen "Mustern" zusammengesetzt werden können, sagen wir, die Matrix hat einen niedrigen Rang.

Zum Beispiel: Wenn jede Zeile ein Vielfaches der ersten Zeile ist, dann ist der Rang dieser Matrix 1. Wenn jede Zeile eine Linearkombination der ersten beiden Zeilen ist, dann ist der Rang 2. Und so weiter.

Die zentrale Erkenntnis von LoRA ist:Wenn sich ein großes Modell an eine neue Aufgabe anpasst, ist die Änderung der Gewichte normalerweise "niedrigrangig" — das heißt, diese Änderung kann durch sehr wenige Parameter dargestellt werden.。

Das ist ähnlich, als gäbe es 1000 verschiedene E-Commerce-Produktbeschreibungen, deren Schreibstile möglicherweise nur durch wenige Kernfaktoren bestimmt werden: Ton, Länge, Schwerpunkte. Sie brauchen nicht 1000 verschiedene Richtungen, um die Variation zu beschreiben; 8, 16 oder 32 reichen aus.

Der Ansatz von LoRA ist also: Alle Parameter des ursprünglichen Modells einfrieren und zwei kleine Matrizen A und B neben den Attention-Schichten hinzufügen. A ist d × r, B ist r × d, wobei r der Rang (Rank) ist, üblicherweise eine kleine Zahl wie 8, 16 oder 32.

Ausgabe des ursprünglichen Modells = Forward Pass des ursprünglichen Modells.

LoRA-Ausgabe = Forward Pass des ursprünglichen Modells + (Eingabe × A × B) × Skalierungsfaktor.

Während des Trainings werden nur A und B trainiert; die ursprünglichen Modellparameter bleiben völlig unverändert.

Mathematische Herleitung von LoRA

Keine Angst, die mathematische Herleitung ist einfach, nur ein paar Zeilen.

Angenommen, eine bestimmte Schicht des ursprünglichen Modells ist eine lineare Transformation: h = W₀x, wobei W₀ die vortrainierte Gewichtsmatrix mit Form d × d ist.

Während des Fine-Tunings möchten wir W₀ aktualisieren, aber nicht zu stark. LoRAs Ansatz besteht darin, die Änderung von W₀ als Produkt zweier kleiner Matrizen darzustellen: ΔW = BA.

Die Ausgabe nach dem Fine-Tuning ist also: h = W₀x + (BA)x.

Hier hat B die Form d × r, A hat die Form r × d, und r ist der Rang (Rank).

Genauer gesagt lautet die Formel im LoRA-Papier:

h = W₀x + α * (BA)x

wobei α ein Skalierungsfaktor ist, der normalerweise auf ein Vielfaches von r gesetzt wird, sodass bei einer Änderung von r die Hyperparameter nicht neu angepasst werden müssen.

Während des Trainings ist W₀ vollständig eingefroren; nur A und B sind trainierbar.

Bei der Initialisierung wird A mit einer zufälligen Gauß-Verteilung initialisiert und B auf Null initialisiert, sodass der LoRA-Teil zu Trainingsbeginn Null ausgibt und das Verhalten des ursprünglichen Modells nicht beeinflusst.

Beispiel

# ============================================
# Minimalimplementierung von LoRAs Kernprinzip
# Keine Framework-Abhängigkeiten, reine NumPy-Demo
# ============================================

import numpy as np


class LoRALayer:
    """Minimalimplementierung der LoRA-Schicht"""

    def __init__(self, d: int, r: int, alpha: float = 1.0):
        """
d: Eingabe-/Ausgabedimension (z. B. 4096)
r: Rang (z. B. 8, 16, 32)
alpha: Skalierungskoeffizient
        """

        self.d = d
        self.r = r
        self.alpha = alpha

        # Eingefrorene ursprüngliche Gewichte (Simulation eines vortrainierten Modells)
        self.W_0 = np.random.randn(d, d) * 0.01
        self.W_0.flags.writeable = False  # Als nicht trainierbar markiert

        # LoRAs A- und B-Matrizen, trainierbar
        # A: r × d, mit zufälliger Gauß-Verteilung initialisiert
        self.A = np.random.randn(r, d) * 0.01
        # B: d × r, auf Null initialisiert (beeinflusst das ursprüngliche Modell zu Trainingsbeginn nicht)
        self.B = np.zeros((d, r))

    def forward(self, x: np.ndarray) -> np.ndarray:
        """Vorwärtsdurchlauf: Ausgabe des ursprünglichen Modells + LoRA-Ausgabe"""
        # Ausgabe des ursprünglichen Modells
        original_output = x @ self.W_0.T
        # LoRA-Ausgabe: (x @ A.T) @ B.T = x @ (B @ A).T
        lora_output = (x @ self.A.T) @ self.B.T
        # Zusammengeführte Ausgabe
        return original_output + self.alpha * lora_output

    def get_trainable_params(self) -> dict:
        """Trainierbare Parameter abrufen (nur A und B)"""
        return {"A": self.A, "B": self.B}

    def count_params(self) -> dict:
        """Anzahl der Parameter zählen"""
        total_original = self.d * self.d
        total_lora = self.r * self.d + self.d * self.r
        return {
            "original_params": total_original,
            "lora_params": total_lora,
            "ratio": total_lora / total_original * 100,
        }


# ============================================
# LoRAs Parameterersparnis demonstrieren
# ============================================

# Angenommen d = 4096 (ähnlich der Dimension der verborgenen Schicht von LLaMA-7B)
# r = 8 (üblicherweise verwendete Ranggröße)
d = 4096
r = 8

lora = LoRALayer(d=d, r=r, alpha=r)
params_info = lora.count_params()

print(fOriginal parameter count: {params_info['original_params']:,})
print(fLoRA parameter count: {params_info['lora_params']:,})
print(fParameter ratio: {params_info['ratio']:.4f}%)

# Example output:
# Original parameter count: 16,777,216
# LoRA parameter count: 65,536
# Parameter ratio: 0.3906%

Sehen Sie, LoRA muss nur 0,4% der Parameter trainieren und kann dennoch Ergebnisse erzielen, die denen des vollständigen Fine-Tunings nahekommen. Das ist das Magische daran.

Auswahl von r (Rang) und α-Parametern

LoRA hat zwei wichtige Hyperparameter: den Rang r und den Skalierungskoeffizienten α.

r bestimmt die Kapazität von LoRA. Je größer r ist, desto komplexere Änderungen kann es darstellen, aber die Anzahl der Parameter steigt ebenfalls.

Typische r-Werte: 8, 16, 32, 64. Für die meisten Aufgaben reicht r = 8 oder 16 aus. Werte über r = 64 bringen normalerweise abnehmende Erträge und erhöhen die Trainingskosten.

Wie wählt man r aus? Ein praktischer Vorschlag: Probieren Sie zuerst r = 8 aus und erhöhen Sie es auf 16 oder 32, wenn die Ergebnisse schlecht sind. Fangen Sie nicht mit einem sehr großen r an.

α ist der Skalierungskoeffizient. Eine gängige Einstellung ist α = r, sodass die Skala von ΔW = BA ungefähr stabil bleibt. Alternativ kann man α = 1 setzen und den Benutzer dies selbst anpassen lassen.

Weitere zu berücksichtigende Parameter:

Zielmodule: Auf welche Schichten sollte LoRA angewendet werden? Normalerweise funktioniert es gut, nur die Abfrage- und Wert-Projektionen (q_proj, v_proj) der Attention-Schichten hinzuzufügen. Es kann auch zu weiteren Schichten hinzugefügt werden, aber das erhöht die Anzahl der Parameter.

Dropout: LoRA kann Dropout hinzufügen, um Overfitting zu verhindern; gängige Werte sind 0.05 oder 0.1.

ParameterHäufiger WertBeschreibung
r (Rang)8, 16, 32Größer bedeutet höhere Kapazität, aber auch mehr Parameter
Alphar oder 1Skalierungskoeffizient, wird normalerweise auf r gesetzt
target_modules["q_proj", "v_proj"]Schichten, auf die LoRA angewendet werden soll
lora_dropout0.0, 0.05, 0.1Overfitting verhindern

Zusammenführen von LoRA-Gewichten

Nach dem Training von LoRA gibt es bei der Inferenz zwei Optionen.

  • Die erste ist, LoRA getrennt zu halten. Laden Sie bei der Inferenz das Originalmodell, laden Sie dann die LoRA-Gewichte und berechnen Sie die zusammengeführte Ausgabe dynamisch. Der Vorteil ist, dass mehrere LoRAs flexibel gewechselt werden können; der Nachteil ist, dass die Inferenz etwas langsamer ist (normalerweise vernachlässigbar).

  • Die zweite ist, die LoRA-Gewichte zusammenzuführen. Addieren Sie das LoRA-ΔW = BA zum W₀ des Originalmodells hinzu, was W = W₀ + α×BA ergibt. Dies erzeugt eine einzige Modelldatei, und die Inferenz funktioniert genau wie bei einem normalen Modell.

Der Vorteil des Zusammenführens ist eine schnelle Inferenz und es ist keine spezielle LoRA-Unterstützung erforderlich. Der Nachteil ist, dass es nach dem Zusammenführen nicht rückgängig gemacht werden kann und bei mehreren LoRAs jedes in eine separate Modelldatei eingearbeitet werden muss.

Beispiel

# ============================================
# Demonstration des Zusammenführens von LoRA-Gewichten
# ============================================

import numpy as np


def merge_lora_weights(W_0: np.ndarray, A: np.ndarray, B: np.ndarray,
                      alpha: float = 1.0) -> np.ndarray:
    """
LoRA-Gewichte mit Originalgewichten zusammenführen
    W = W_0 + alpha * (B @ A)

Parameter:
W_0: Originalgewichte (d, d)
A: LoRA-Matrix A (r, d)
B: LoRA-Matrix B (d, r)
alpha: Skalierungskoeffizient

Rückgabe:
Zusammengeführte Gewichte
    """

    # delta_W = alpha * (B @ A) berechnen
    delta_W = alpha * (B @ A)
    # in Originalgewichte zusammenführen
    W_merged = W_0 + delta_W
    return W_merged


# ============================================
# Den Zusammenführungsprozess demonstrieren
# ============================================

d = 4096  # Dimension der verdeckten Schicht
r = 8     # LoRA-Rang

# Originalgewichte und LoRA-Gewichte simulieren
W_0 = np.random.randn(d, d) * 0.01
A = np.random.randn(r, d) * 0.01
B = np.zeros((d, r))  # in der Praxis ist B das Ergebnis nach dem Training

# zusammengeführte Gewichte
W_merged = merge_lora_weights(W_0, A, B, alpha=r)

print(f"original weight shape: {W_0.shape}")
print(f"merged weight shape: {W_merged.shape}")
print("weight merging complete, can be used like a normal model")

QLoRA (Quantized LoRA)

QLoRA treibt die geringen Kosten von LoRA auf die Spitze – das Fine-Tuning von 70B-Modellen ist auf GPUs für Verbraucher möglich.

4-Bit-Quantisierung + LoRA

Die Idee von QLoRA ist einfach: Quantisieren Sie das Modell auf 4 Bit und führen Sie dann ein LoRA-Fine-Tuning darüber durch.

Was ist Quantisierung? Dabei werden Gleitkommazahlen (FP16/BF16, die jeweils 2 Byte beanspruchen) in Darstellungen mit geringerer Präzision umgewandelt. Die 4-Bit-Quantisierung verwendet 4 Bits (0,5 Byte), um ein Gewicht darzustellen.

Dies reduziert die Modellgröße um 75%: Ein 7B-Modell schrumpft von 13 GB auf etwa 3,5 GB, ein 13B-Modell von 26 GB auf etwa 6,5 GB und ein 70B-Modell von 140 GB auf etwa 35 GB.

Aber direkte Quantisierung geht mit einem Präzisionsverlust einher. Die Innovation von QLoRA ist ein Datentyp namens NF4 (Normalized Float 4), der die Informationen in den Gewichten neuronaler Netze besser bewahrt.

Der Ansatz von QLoRA:

  • 1. Das vortrainierte Modell auf 4 Bit quantisieren (NF4-Format).

  • 2. LoRA-Adapter oben drauf setzen.

  • 3. Während des Trainings wird das mit 4 Bit quantisierte Basismodell vollständig eingefroren; nur die LoRA-FP16-Gewichte werden trainiert.

Das Ergebnis: Der VRAM-Bedarf sinkt um weitere 75% bei nahezu keinem Leistungsverlust.

bitsandbytes-Bibliothek

bitsandbytes ist die von Tim Dettmers entwickelte Kernbibliothek für die Quantisierung.

Sie bietet:

  • 1. 4-Bit-Quantisierung (NF4).

  • 2. Double Quantization – quantisiert auch die Quantisierungskonstanten, wodurch noch etwas mehr VRAM gespart wird.

  • 3. Paged Optimizer – verschiebt einige Daten in den CPU-Speicher, wenn der VRAM nicht ausreicht.

Normale Benutzer müssen nicht direkt mit bitsandbytes interagieren; die Bibliotheken peft und transformers wrappen es bereits. Aber es ist nützlich zu wissen, dass es existiert.

Fine-Tuning-Lösungen für Verbraucher-GPUs

Mit QLoRA können sich auch normale Menschen ein Fine-Tuning leisten.

RTX 3090/4090 (24 GB VRAM) können problemlos 7B/13B-Modelle fine-tunen und sich sogar an 70B-Modellen versuchen (was längere Sequenzlängen oder kleinere Batch-Größen erfordert).

Schätzung des VRAM-Bedarfs:

ModellgrößeQLoRA-VRAM-BedarfGeeignete GPU
7B5-8 GBRTX 3060/4060 und höher
13B8-12 GBRTX 3070/4070 und höher
34B15-20 GBRTX 3090/4090
70B30-40 GBRTX 3090/4090 × 2 oder A100

QLoRA ist ein echter Wendepunkt. Es hat das Fine-Tuning großer Modelle von „das können nur große Unternehmen“ in „gewöhnliche Leute können es auf ihren eigenen Gaming-Laptops machen“ verwandelt. Dies ist einer der wichtigsten Fortschritte in der Open-Source-KI.


Vorbereitung der Trainingsdaten

Die Datenaufbereitung ist der zeitaufwändigste und wichtigste Teil des Fine-Tuning-Prozesses. Schlechte Eingaben führen zu schlechten Ergebnissen – die Datenqualität bestimmt die Obergrenze des Modells.

Datenformate (Alpaca-, ShareGPT-Formate)

Es gibt mehrere gängige Datenformen; wählen Sie eine aus und behalten Sie diese konsequent bei.

Das erste ist das Alpaca-Format. Es ist eines der beliebtesten Formate, populär gemacht durch das Stanford-Alpaca-Projekt.

Das Format lautet: Anweisung (instruction), Eingabe (input, optional), Ausgabe (output).

Beispiel

# ============================================
# Alpaca-Datenformat-Beispiel
# ============================================

alpaca_data = [
    {
        "instruction": Generiere eine E-Commerce-Produktbeschreibung.,
        "input": Produktname: Intelligente Sportuhr\nFunktionen: Herzfrequenzüberwachung, Schlaftracking, GPS-Ortung, 50 Meter Wasserdichtigkeit.,
        "output": Diese intelligente Sportuhr ist ein idealer Begleiter für Fitnessbegeisterte. Sie verfügt über eine präzise Herzfrequenzüberwachung und wissenschaftliches Schlaftracking, damit Sie Ihren Gesundheitszustand vollständig verstehen können. Mit dem integrierten GPS können Sie Ihre Trainingsrouten aufzeichnen, ohne Ihr Telefon mitnehmen zu müssen. Mit einer Wasserdichtigkeit von 50 Metern können Sie sie beim Schwimmen bedenkenlos tragen.
    },
    {
        "instruction": Schreibe den folgenden Text in einen umgangssprachlicheren Ausdruck um.,
        "input": Dieses Produkt nutzt fortschrittliche Technologie und zeichnet sich durch hohe Leistung und geringen Stromverbrauch aus.,
        "output": Dieses Produkt nutzt sehr fortschrittliche Technologie mit starker Leistung und Energieeffizienz!
    },
    Mehr Daten
]

print(Das Alpaca-Format enthält drei Felder:)
print(- instruction: Aufgabenanweisung (erforderlich))
print(- input: zusätzliche Eingabe (optional, kann ein leerer String sein, falls keine vorhanden ist))
print(- output: erwartete Ausgabe (erforderlich))

Das zweite ist das ShareGPT-Format. Dieses Format wird für Dialogdaten verwendet und unterstützt Unterhaltungen mit mehreren Runden.

Das Format ist: conversations (Dialogliste), jede Runde hat from (Rolle) und value (Inhalt).

Beispiel

# ============================================
Beispiel für das ShareGPT-Datenformat
# ============================================

sharegpt_data = [
    {
        "conversations": [
            {
                "from": "human",
                "value": Hallo, ich möchte mehr über Ihre Smartwatches erfahren.
            },
            {
                "from": "gpt",
                "value": "Hallo! Unsere Smartwatch verfügt über Funktionen wie Herzfrequenzüberwachung, Schlaftracking und GPS-Ortung sowie eine Wasserdichtigkeit von 50 Metern. Gibt es etwas Bestimmtes, das Sie wissen möchten?"
            },
            {
                "from": "human",
                "value": Wie sieht es mit der Akkulaufzeit aus?
            },
            {
                "from": "gpt",
                "value": Bei normaler Nutzung hält der Akku 7 Tage; bei aktiviertem GPS-Sportmodus sind es 12 Stunden.
            }
        ]
    },
    # ... Weitere Unterhaltungen
]

print(Das ShareGPT-Format wird für mehrrunden-Konversationsdaten verwendet:)
print(- conversations: Konversationsliste)
print(- from: Rolle (normalerweise human und gpt))
print(- value: Konversationsinhalt)

Es gibt auch ein einfaches Eingabe-Ausgabe-Paarformat, das sich für einfache Aufgaben eignet.

Beispiel

# ============================================
# Einfaches Eingabe-Ausgabe-Paarformat
# ============================================

simple_data = [
    {
        "text": "User: Produktbeschreibung generieren\nProdukt: Thermobecher\n\nAssistant: Dieser hochwertige Thermobecher verwendet..."
    },
    {
        "text": "User: Produktbeschreibung generieren\nProdukt: Kabellose Ohrhörer\n\nAssistant: Echte kabellose Bluetooth-Ohrhörer, Bluetooth 5.3..."
    },
    # ... weitere Daten
]

print("Dieses Format verkettet alle Inhalte direkt in einem einzigen Textfeld, was am flexibelsten, aber auch am wenigsten standardisiert ist.")

Welches Format ist am besten? Das Alpaca-Format ist am gebräuchlichsten; die meisten Trainings-Frameworks unterstützen es. Für Konversationsaufgaben ist ShareGPT besser geeignet.

Wichtige Punkte für die Datenbereinigung

Die Datenbereinigung ist viel wichtiger, als Sie vielleicht denken. 80 % der Leistung Ihres Modells hängen von der Datenqualität ab.

  • Erstens: Deduplizierung. Doppelte Daten führen dazu, dass das Modell diese sich wiederholenden Muster überanpasst (overfitting). Überprüfen und entfernen Sie vollständig identische oder hochgradig ähnliche Daten.

  • Zweitens: Filtern Sie Daten von schlechter Qualität. Entfernen Sie Daten, die zu kurz, zu lang, schlecht formatiert sind oder offensichtliche Fehler aufweisen. Zum Beispiel Ausgaben mit nur wenigen Zeichen oder mit Kauderwelsch.

  • Drittens: Vereinheitlichen Sie Formate. Einige Ausgaben verwenden beispielsweise chinesische Anführungszeichen, andere englische Anführungszeichen; einige enden mit einem Punkt, andere nicht. Versuchen Sie, sie zu vereinheitlichen, damit das Modell schneller lernt.

  • Viertens: Ausgleichen der Verteilung. Wenn einige Datenkategorien besonders reichhaltig und andere knapp sind, wird das Modell zu Gunsten der Mehrheitskategorie verzerrt sein. Erwägen Sie ein Oversampling der Minderheitenklasse oder ein Undersampling der Mehrheitenklasse.

  • Fünftens: Auf Datenlecks prüfen. Stellen Sie sicher, dass Test- und Validierungsset nicht im Trainingsset vorkommen.

Beispiel

# ============================================
# Einfaches Beispiel zur Datenbereinigung
# ============================================

import json
from typing import List, Dict


def clean_alpaca_data(data: List[Dict]) -> List[Dict]:
    """Einfache Datenbereinigungsfunktion"""
    cleaned = []
    seen_outputs = set()

    for item in data:
        # 1. Prüfen, ob erforderliche Felder vorhanden sind
        if "instruction" not in item or "output" not in item:
            continue

        instruction = item["instruction"].strip()
        output = item["output"].strip()

        # 2. Zu kurze Ausgaben herausfiltern
        if len(output) < 10:
            continue

        # 3. Zu lange Ausgaben herausfiltern (je nach Aufgabe anpassen)
        if len(output) > 2000:
            continue

        # 4. Deduplizieren (ungefähre Deduplizierung über die Ausgabe)
        if output in seen_outputs:
            continue
        seen_outputs.add(output)

        # 5. Einfache Formatvereinheitlichung
        # Ändern Sie beispielsweise chinesische Anführungszeichen in englische
        output = output.replace(""", "\"").replace(""", "\"")
        # Führende und nachfolgende Leerzeichen entfernen
        output = output.strip()

        cleaned.append({
            "instruction": instruction,
            "input": item.get("input", "").strip(),
            "output": output
        })

    return cleaned


# Daten laden
with open("data/training_data_raw.json", "r", encoding="utf-8") as f:
    raw_data = json.load(f)

# Daten bereinigen
cleaned_data = clean_alpaca_data(raw_data)

print(f"Anzahl der Rohdaten: {len(raw_data)}")
print(f"Anzahl der bereinigten Daten: {len(cleaned_data)}")
print(f"{len(raw_data) - len(cleaned_data)} Einträge von minderwertigen Daten herausgefiltert")

# Bereinigte Daten speichern
with open("data/training_data_cleaned.json", "w", encoding="utf-8") as f:
    json.dump(cleaned_data, f, ensure_ascii=False, indent=2)

print("Bereinigte Daten in data/training_data_cleaned.json gespeichert")

Heuristische Regeln für das Datenvolumen

Wie viele Daten werden benötigt? Dies ist eine der am häufigsten gestellten Fragen.

Die Antwort lautet: Es hängt von der Schwierigkeit der Aufgabe ab.

  • Einfache Aufgaben (z. B. Text konversationeller gestalten): 100-300 Einträge können ausreichen.

  • Mittlere Aufgaben (z. B. Zusammenfassungen in einem bestimmten Format generieren): 300-1000 Einträge sind geeignet.

  • Komplexe Aufgaben (z. B. Q&A in professionellen Bereichen): 1000-5000 Einträge sind ideal.

Natürlich sind mehr Daten in der Regel besser, allerdings mit abnehmendem Grenzertrag. 1000 Einträge sind viel besser als 500, aber 10000 sind möglicherweise nicht viel besser als 5000.

Ein praktischer Vorschlag: Bereiten Sie zunächst 500 hochwertige Dateneinträge vor und führen Sie einen Testlauf durch. Wenn die Ergebnisse schlecht sind, analysieren Sie, ob dies auf unzureichende Daten, schlechte Datenqualität oder Hyperparameter-Probleme zurückzuführen ist, und nehmen Sie gezielte Verbesserungen vor.

Datenqualität >> Datenquantität. 1000 sorgfältig erstellte Einträge funktionieren viel besser als 10000 eilig zusammengestellte.

Es lohnt sich, mehr Zeit in die Datenaufbereitung zu investieren. Das Modelltraining ist nur eine Sache von "Knopf drücken", aber die Datenqualität bestimmt, ob das Drücken dieses Knopfes wertvoll ist.


Supervised Fine-tuning (SFT) in der Praxis

Endlich sind wir beim praktischen Teil angelangt. Wir verwenden Unsloth oder LLaMA-Factory, um den vollständigen Fine-Tuning-Workflow zu implementieren.

Umgebungskonfiguration

Schritt 1: Installieren Sie die notwendigen Bibliotheken.

Umgebungskonfiguration

# ============================================
# Abhängigkeiten installieren (es wird empfohlen, eine neue conda-Umgebung zu erstellen)
# ============================================

# Eine neue conda-Umgebung erstellen
conda create -n finetuning python=3.10 -y
conda activate finetuning

# PyTorch installieren (wählen Sie den passenden Befehl basierend auf Ihrer CUDA-Version)
# Besuchen Sie https://pytorch.org/get-started/locally/ für die neuesten Befehle
conda install pytorch pytorch-cuda=12.1 -c pytorch -c nvidia

# HuggingFace-Ökosystem-Bibliotheken installieren
pip install transformers datasets accelerate peft bitsandbytes

# Unsloth installieren (optional, für schnelleres Training)
pip install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"
pip install --no-deps trl xformers

# Oder LLaMA-Factory verwenden (All-in-One-Trainings-Framework)
git clone --depth 1 https://github.com/hiyouga/LLaMA-Factory.git
cd LLaMA-Factory
pip install -e ".[torch,metrics]"

echo "Umgebungseinrichtung abgeschlossen!"

Verwendung von Unsloth/LLaMA-Factory

Unsloth ist eine optimierte Trainingsbibliothek, die schnell und speichereffizient ist. LLaMA-Factory ist ein All-in-One-Trainings-Framework, das mehrere Modelle und Methoden unterstützt.

Schauen wir uns zuerst den Code für das Training mit Unsloth + peft an:

Beispiel

# ============================================
# Verwendung von Unsloth für LoRA Fine-Tuning (Vollständige Version)
# Läuft auf RTX 3090/4090
# ============================================

import torch
from datasets import load_dataset, Dataset
from transformers import (
    AutoModelForCausalLM,
    AutoTokenizer,
    TrainingArguments,
    BitsAndBytesConfig,
)
from peft import LoraConfig, get_peft_model
from trl import SFTTrainer
import json


# ============================================
# 1. Konfigurationsparameter
# ============================================

MODEL_NAME = "unsloth/mistral-7b-v0.2-bnb-4bit"  # oder andere Modelle
DATA_PATH = "data/training_data_cleaned.json"    # Ihre Daten
OUTPUT_DIR = "output/example-ecommerce-model"     # Ausgabeverzeichnis

# Konfiguration für 4-Bit-Quantisierung
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_use_double_quant=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16
)

# LoRA-Konfiguration
lora_config = LoraConfig(
    r=16,                    # Rang, 8-64 sind alle in Ordnung, 16 ist ein guter Startpunkt
    lora_alpha=16,           # Skalierungsfaktor, normalerweise gleich r
    target_modules=[         # Schichten, auf die LoRA angewendet werden soll, dies sind gängige Optionen
        "q_proj",
        "k_proj",
        "v_proj",
        "o_proj",
        "gate_proj",
        "up_proj",
        "down_proj",
    ],
    lora_dropout=0.05,       # Dropout zur Vermeidung von Overfitting
    bias="none",
    task_type="CAUSAL_LM"    # Kausales Sprachmodell
)

# Trainingsparameter
training_args = TrainingArguments(
    output_dir=OUTPUT_DIR,
    per_device_train_batch_size=4,       # Batch-Größe pro GPU
    per_device_eval_batch_size=4,
    gradient_accumulation_steps=4,       # Gradientenakkumulation, entspricht einem größeren Batch
    learning_rate=2e-5,                  # Lernrate, 2e-5 bis 5e-5 ist ein üblicher Bereich
    num_train_epochs=3,                  # Anzahl der Epochen, 2-5 Epochen sind angemessen
    logging_steps=10,                    # Alle 10 Schritte protokollieren
    evaluation_strategy="epoch",         # Einmal pro Epoche evaluieren
    save_strategy="epoch",               # Einmal pro Epoche speichern
    fp16=True,                           # Gemischtes Präzisionstraining verwenden
    bf16=False,                          # Für Ampere- und neuere GPUs (30/40er Serie) können Sie bf16=True setzen
    optim="paged_adamw_32bit",           # Gepaging-Optimierer zur Vermeidung von OOM
    report_to="none",                    # Nicht an Plattformen wie wandb melden
    save_total_limit=3,                  # Maximal 3 Checkpoints speichern
    load_best_model_at_end=True,         # Schließlich das beste Modell laden
    metric_for_best_model="eval_loss",   # Das beste Modell anhand des Eval-Loss auswählen
)


# ============================================
# 2. Daten laden und formatieren
# ============================================

def format_example(example):
    """Alpaca-Format-Daten in Modelleingabe formatieren"""
    instruction = example["instruction"]
    input_text = example.get("input", "")
    output = example["output"]

    if input_text.strip() == "":
        # Fall ohne zusätzliche Eingabe
        prompt = f"""Unten steht eine Anweisung, die eine Aufgabe beschreibt. Schreibe eine Antwort, die die Anfrage angemessen vervollständigt.

### Anweisung:
{instruction}

### Antwort:
"""

    else:
        # Fall mit zusätzlicher Eingabe
        prompt = f"""Unten steht eine Anweisung, die eine Aufgabe beschreibt, gekoppelt mit einer Eingabe, die weiteren Kontext liefert. Schreibe eine Antwort, die die Anfrage angemessen vervollständigt.

### Anweisung:
{instruction}

### Eingabe:
{input_text}

### Antwort:
"""


    # Der vollständige Text ist prompt + output
    return {
        "prompt": prompt,
        "completion": output,
        "text": prompt + output
    }


# Daten laden
dataset = load_dataset("json", data_files=DATA_PATH, split="train")

# Trainings- und Validierungsdaten aufteilen (90% Training, 10% Validierung)
dataset = dataset.train_test_split(test_size=0.1, seed=42)
train_dataset = dataset["train"]
eval_dataset = dataset["test"]

# Daten formatieren
train_dataset = train_dataset.map(format_example)
eval_dataset = eval_dataset.map(format_example)

print(f"Trainingsset-Größe: {len(train_dataset)}")
print(f"Größe des Validierungssatzes: {len(eval_dataset)}")


# ============================================
# 3. Modell und Tokenizer laden
# ============================================

print("Lade Modell...")

tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
tokenizer.pad_token = tokenizer.eos_token  # Pad-Token festlegen
tokenizer.padding_side = "right"           # Von rechts auffüllen (padding)

model = AutoModelForCausalLM.from_pretrained(
    MODEL_NAME,
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True,
    use_auth_token=True,  # Falls HuggingFace-Anmeldung erforderlich ist
)

# LoRA einbinden
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()  # Anteil trainierbarer Parameter ausgeben


# ============================================
# 4. Trainer erstellen und Training starten
# ============================================

trainer = SFTTrainer(
    model=model,
    train_dataset=train_dataset,
    eval_dataset=eval_dataset,
    args=training_args,
    tokenizer=tokenizer,
    peft_config=lora_config,
    dataset_text_field="text",  # Welches Feld den vollständigen Text enthält
    max_seq_length=2048,        # Maximale Sequenzlänge
    packing=False,              # Beispiele nicht packen
)

print("Starte Training...")
trainer.train()
print("Training abgeschlossen!")


# ============================================
# 5. Modell speichern
# ============================================

# LoRA-Adapter speichern (geringe Größe, einige MB bis zu Dutzenden MB)
model.save_pretrained(f"{OUTPUT_DIR}/lora_adapter")
tokenizer.save_pretrained(f"{OUTPUT_DIR}/lora_adapter")
print(f"LoRA-Adapter gespeichert unter {OUTPUT_DIR}/lora_adapter")

# Falls Sie LoRA-Gewichte zusammenführen möchten (optional)
# merged_model = model.merge_and_unload()
# merged_model.save_pretrained(f"{OUTPUT_DIR}/merged_model")
# tokenizer.save_pretrained(f"{OUTPUT_DIR}/merged_model")
# print(f"Zusammengeführtes Modell gespeichert unter {OUTPUT_DIR}/merged_model")

Wenn Sie einen einfacheren Ansatz bevorzugen, können Sie LLaMA-Factory verwenden, das sowohl eine Web-UI als auch eine Befehlszeilenschnittstelle bereitstellt:

Beispiel

# ============================================
# LLaMA-Factory für das Training verwenden
# ============================================

# Methode 1: Web-UI starten (am intuitivsten)
cd LLaMA-Factory
python src/train_web.py

# Methode 2: Training über die Befehlszeile (geeignet für Automatisierung)
python src/train.py \
    --model_name_or_path mistralai/Mistral-7B-v0.2 \
    --dataset example_ecommerce \
    --dataset_dir ./data \
    --output_dir ./output/example-model \
    --finetuning_type lora \
    --quantization_bit 4 \
    --lora_rank 16 \
    --lora_alpha 16 \
    --lora_dropout 0.05 \
    --lora_target q_proj,v_proj,k_proj,o_proj,gate_proj,up_proj,down_proj \
    --per_device_train_batch_size 4 \
    --gradient_accumulation_steps 4 \
    --lr_scheduler_type cosine \
    --logging_steps 10 \
    --save_strategy epoch \
    --learning_rate 2e-5 \
    --num_train_epochs 3 \
    --fp16 true \
    --plot_loss true

Einstellungen für Trainings-Hyperparameter

Die Auswahl von Hyperparametern ist eine Kunst, aber es gibt einige empirische Regeln.

Lernrate: Dies ist der wichtigste Hyperparameter. Für LoRA ist 2e-5 bis 5e-5 ein sicherer Startpunkt. Wenn das Training instabil ist (der Loss schwankt), verringern Sie ihn ein wenig. Wenn das Training zu langsam ist (der Loss sinkt sehr langsam), erhöhen Sie ihn ein wenig.

Batch-Größe: Je größer, desto besser, aber sie wird durch den VRAM begrenzt. per_device_train_batch_size kann auf 2, 4 oder 8 gesetzt werden. Wenn der VRAM nicht ausreicht, verwenden Sie gradient_accumulation_steps, um Gradienten zu akkumulieren. Zum Beispiel entspricht batch_size=2, accumulation_steps=4 einem batch_size=8.

Epochen: 2-5 Epochen sind angemessen. Zu wenige und das Modell lernt nicht genug; zu viele können zu Overfitting führen. Sie können den Evaluierungs-Loss beobachten und stoppen, wenn dieser nicht weiter sinkt.

LoRA-Rang (r): 8-64 sind alle in Ordnung. 16 ist ein guter Startpunkt. Verwenden Sie 8 für einfache Aufgaben und 32 oder 64 für komplexe Aufgaben.

Maximale Sequenzlänge: Entscheiden Sie basierend auf Ihren Daten. 512, 1024, 2048, 4096 sind alle gängig. Je länger sie ist, desto mehr VRAM wird benötigt.

HyperparameterEmpfohlener StartwertAnpassungsrichtung
learning_rate2e-5Wenn der Loss oszilliert, verringern; wenn er zu langsam sinkt, erhöhen
per_device_train_batch_size4Wenn ausreichend VRAM vorhanden ist, erhöhen; wenn nicht, verringern
gradient_accumulation_steps4Äquivalenter Batch = batch_size × accumulation
num_train_epochs3Bei Overfitting reduzieren; bei Underfitting erhöhen
lora_rank (r)16Einfache Aufgaben 8, komplexe Aufgaben 32-64
max_seq_length1024 oder 2048Entsprechend Ihrer Datenlänge anpassen

Trainingsüberwachung und Loss-Interpretation

Achten Sie während des Trainings auf einige Metriken.

  • Train Loss: Der Loss auf dem Trainingsdatensatz, sollte kontinuierlich sinken. Wenn er schnell sinkt, ist die Lernrate möglicherweise zu groß oder es sind zu wenig Daten vorhanden. Wenn er langsam sinkt, ist die Lernrate zu klein oder die Modellkapazität unzureichend.

  • Eval Loss: Der Loss auf dem Validierungsdatensatz, dies ist die wichtigste Metrik. Er sollte zusammen mit dem Train Loss sinken. Wenn der Eval Loss zu steigen beginnt, während der Train Loss weiter sinkt, liegt ein Overfitting vor und das Training sollte gestoppt werden.

Anzeichen für Overfitting: Der Eval Loss steigt nach einem anfänglichen Rückgang, während der Train Loss weiter sinkt. Lösungen: Epochen reduzieren, Dropout erhöhen, Daten hinzufügen, LoRA-Rang verringern.

Anzeichen für Underfitting: Sowohl Train Loss als auch Eval Loss sind hoch und sinken nur langsam. Lösungen: Epochen erhöhen, LoRA-Rang erhöhen, Daten hinzufügen, Lernrate leicht erhöhen.


Bewertung der Fine-Tuning-Effekte

Woher wissen Sie nach dem Training, ob das Modell gut ist?

Auswahl der Evaluierungsmetriken

Die Evaluierungsmethode hängt von Ihrem Aufgabentyp ab.

Die erste ist die manuelle Evaluierung. Lassen Sie Personen die Ausgabe des Modell lesen und deren Qualität bewerten. Dies ist die zuverlässigste, aber auch zeitaufwändigste Methode.

Sie können eine einfache Bewertungstabelle erstellen:

Beispiel

# ============================================
# Einfache Vorlage für die manuelle Evaluierung
# ============================================

evaluation_prompts = [
    {
        "instruction": "Generiere eine E-Commerce-Produktbeschreibung",
        "input": "Produktname: Tragbare Kaffeemaschine\nFunktion: One-Touch-Extraktion, tragbares Design, USB-Aufladung",
        "reference": "Mit dieser tragbaren Kaffeemaschine können Sie jederzeit und überall frisch gemahlenen Kaffee genießen. Die One-Touch-Extraktion ist einfach zu bedienen und das kompakte, tragbare Design eignet sich für Büro, Reisen und den Außeneinsatz. Dank des USB-Ladedesigns müssen Sie sich keine Sorgen um die Stromversorgung machen."
    },
    {
        "instruction": "Generiere eine E-Commerce-Produktbeschreibung",
        "input": "Produktname: Mechanische Tastatur\nMerkmale: Blue Switch, RGB-Hintergrundbeleuchtung, 87 Tasten, PBT-Tastenkappen",
        "reference": "Diese mechanische 87-Tasten-Tastatur verwendet knackige Blue Switches für ein hervorragendes Tippgefühl. Die vollständige RGB-Hintergrundbeleuchtung sorgt nachts für gute Lesbarkeit. PBT-Tastenkappen sind langlebig und entwickeln keine glänzende Oberfläche, sodass sie auch nach längerem Gebrauch wie neu aussehen."
    },
    # ... 10-20 Testfälle
]


def evaluate_model(model, tokenizer, prompts):
    """Bewertet das Modell mit einer Reihe von Prompts"""
    results = []
    for item in prompts:
        # Ausgabe generieren
        inputs = format_example_for_inference(item)
        output = generate_with_model(model, tokenizer, inputs)

        results.append({
            "instruction": item["instruction"],
            "input": item["input"],
            "model_output": output,
            "reference": item["reference"],
            # Für manuelle Bewertung leer lassen
            "score_relevance": None,  # 1-5, Relevanz
            "score_quality": None,    # 1-5, Qualität
            "score_format": None,     # 1-5, Formatkorrektheit
        })

    return results


# Evaluierung ausführen
results = evaluate_model(model, tokenizer, evaluation_prompts)

# Ergebnisse für die spätere manuelle Bewertung speichern
with open("evaluation_results.json", "w", encoding="utf-8") as f:
    json.dump(results, f, ensure_ascii=False, indent=2)

print(Die Evaluierungsergebnisse wurden gespeichert. Bitte bewerten Sie diese manuell vor der Analyse.)

Die zweite ist die automatische Evaluierung. Verwenden Sie Metriken wie BLEU, ROUGE und BLEURT. Diese Metriken sind nicht perfekt, ermöglichen aber eine schnelle Iteration.

Die dritte ist A/B-Testing. Mischen Sie die Ausgaben des alten und des neuen Modells für eine Blinevaluierung, um zu sehen, ob das neue Modell wirklich besser ist.

Verhindern von Overfitting

Overfitting ist das häufigste Problem beim Fine-Tuning. Es zeigt eine gute Leistung auf dem Trainingsdatensatz, aber eine schlechte Leistung bei neuen Daten.

Methoden zur Vermeidung von Overfitting:

1. Early Stopping: Überwachen Sie den Eval Loss und stoppen Sie das Training, wenn er nicht weiter sinkt.

2. Mehr Daten hinzufügen: Mehr und vielfältigere Daten sind der beste Weg, um Overfitting zu verhindern.

3. Datenaugmentation: Schreiben Sie bestehende Daten um, um Variationen zu erzeugen.

4. Modellkapazität reduzieren: Verringern Sie den LoRA-Rang und reduzieren Sie die Anzahl der LoRA-Schichten.

5. Dropout erhöhen: Setzen Sie lora_dropout in der LoRA-Konfiguration auf 0.05 oder 0.1.

6. Weight Decay: Fügen Sie weight_decay=0.01 zu den Trainingsparametern hinzu.


Bereitstellung von feinabgestimmten Modellen

Wie verwenden Sie das Modell, sobald es trainiert ist?

Laden von LoRA für die Inferenz

Der erste Weg besteht darin, das Basismodell und dann den LoRA-Adapter zu laden.

Beispiel

# ============================================
# LoRA laden und Inferenz durchführen
# ============================================

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel


def load_finetuned_model(base_model_name, lora_adapter_path):
    """Basismodell und LoRA-Adapter laden"""
    # Tokenizer laden
    tokenizer = AutoTokenizer.from_pretrained(base_model_name)
    tokenizer.pad_token = tokenizer.eos_token

    # Basismodell laden
    model = AutoModelForCausalLM.from_pretrained(
        base_model_name,
        device_map="auto",
        torch_dtype=torch.float16,
        trust_remote_code=True,
    )

    # LoRA-Adapter laden
    model = PeftModel.from_pretrained(model, lora_adapter_path)

    # Gewichte zusammenführen (optional, beschleunigt die Inferenz)
    model = model.merge_and_unload()

    return model, tokenizer


def generate_product_description(model, tokenizer, product_name, features):
    """E-Commerce-Produktbeschreibung generieren"""
    # Prompt konstruieren (genau wie beim Training!)
    prompt = f"""Unten steht eine Anweisung, die eine Aufgabe beschreibt. Schreibe eine Antwort, die die Anfrage angemessen vervollständigt.

### Anweisung:
Generiere eine E-Commerce-Produktbeschreibung

### Eingabe:
Produktname: {product_name}
Eigenschaften: {features}

### Antwort:
"""


    # Generieren
    inputs = tokenizer(prompt, return_tensors="pt").to(model.device)

    with torch.no_grad():
        outputs = model.generate(
            **inputs,
            max_new_tokens=200,
            temperature=0.7,      # Zufälligkeit steuern, 0-1, größer bedeutet zufälliger
            top_p=0.9,
            do_sample=True,
            pad_token_id=tokenizer.eos_token_id,
        )

    # Ausgabe dekodieren
    generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)

    # Nur den Antwort-Teil nehmen
    response = generated_text.split("### Response:")[-1].strip()

    return response


# ============================================
# Verwendungsbeispiel
# ============================================

BASE_MODEL = "mistralai/Mistral-7B-v0.2"
LORA_PATH = "output/example-ecommerce-model/lora_adapter"

print("Lade Modell...")
model, tokenizer = load_finetuned_model(BASE_MODEL, LORA_PATH)
print("Modell erfolgreich geladen!")

# Testgenerierung
product_name = "Intelligenter Saugroboter"
features = "Lasernavigation, automatisches Aufladen, APP-Steuerung, 2000Pa starke Saugkraft"

description = generate_product_description(
    model, tokenizer, product_name, features
)

print(f"\nProduktname: {product_name}")
print(fGenerierte Beschreibung:\n{description}")

Bereitstellungsmethoden

Es gibt mehrere gängige Bereitstellungsoptionen.

  • Die erste besteht darin, vLLM zu verwenden. Es ist eine Hochleistungs-Inferenz-Engine, die LoRA, kontinuierliches Batching und andere Funktionen unterstützt. Empfohlen für Produktionsumgebungen.

  • Die zweite besteht darin, Text Generation Inference zu verwenden. HuggingFaces offizieller Inferenzdienst, funktionsreich.

  • Die dritte besteht darin, es selbst mit FastAPI zu umhüllen. Einfach und direkt, geeignet für kleine Bereitstellungen.

  • Die vierte besteht darin, Ollama zu verwenden. Konvertieren Sie das Modell in das GGUF-Format und führen Sie es mit Ollama aus, geeignet für lokale Bereitstellungen.

Andere Erweiterungen