Blog / AI
AI

Feinabstimmung von GPT-OSS: Eine Schritt-für-Schritt-Anleitung mit Unsloth

Erfahren Sie, wie Sie GPT-OSS mit der Unsloth-Bibliothek feinabstimmen können, indem Sie hochwertige Trainingsdaten verwenden, die über die Web-Scraping API von Bright Data gesammelt wurden.
11 min lesen
Fine-tuning GPT OSS

In diesem Leitfaden zur Feinabstimmung von GPT-OSS mit Webdaten erfahren Sie:

  • Was Unsloth ist und warum es die Feinabstimmung beschleunigt
  • Wie man mit den Scraping-APIs von Bright Data hochwertige Trainingsdaten sammelt
  • Wie Sie Ihre Umgebung für eine effiziente Feinabstimmung einrichten
  • Wie Sie GPT-OSS mit einer vollständigen Schritt-für-Schritt-Anleitung feineinstellen

Los geht’s!

Was ist Unsloth und warum sollte man es für die Feinabstimmung verwenden?

Unsloth ist eine leichtgewichtige Bibliothek, die die LLM-Feinabstimmung erheblich beschleunigt und gleichzeitig vollständig mit dem Hugging Face-Ökosystem (Hub, Transformatoren, PEFT, TRL) kompatibel ist. Die Bibliothek unterstützt die meisten NVIDIA-GPUs, von GTX 1070 bis hin zu H100s, und arbeitet nahtlos mit der gesamten Trainer-Suite der TRL-Bibliothek zusammen.

Die Leistungsverbesserungen, die Unsloth liefert, sind beeindruckend. In Benchmarks erreicht es im Vergleich zu Standard-Transformatoren-Implementierungen eine 2x schnellere Trainingsgeschwindigkeit und benötigt dabei 40% weniger Speicher. Das bedeutet, dass Sie auf derselben Hardware größere Modelle trainieren oder größere Stapelgrößen verwenden können. Am wichtigsten ist vielleicht, dass die Genauigkeit um 0 % sinkt, so dass Sie all diese Vorteile ohne Einbußen bei der Modellqualität nutzen können.

Verständnis der GPT-OSS-Modelle

Die Veröffentlichung von GPT-OSS durch OpenAI stellt einen bedeutenden Wandel in ihrem Ansatz zur Entwicklung von KI dar. Zum ersten Mal haben wir Zugang zu echten GPT-Modellen ohne API-Beschränkungen, nutzungsabhängige Abrechnung oder Tarifgrenzen.

GPT-OSS gibt es in zwei Hauptvarianten:

  • GPT-OSS-120B: Dieses größere Modell entspricht der Qualität von GPT-4, erfordert jedoch mindestens 80 GB GPU-Speicher
  • GPT-OSS-20B: Vergleichbar mit der Leistung von GPT-3.5, läuft dieses Modell effizient auf 16GB GPUs (perfekt für unser Tutorial)

Ein einzigartiges Merkmal, das GPT-OSS von anderen offenen Modellen abhebt, ist die Steuerung des Denkaufwands. Sie können einstellen, wie tief das Modell Probleme durchdenkt, indem Sie die Argumentationsstufe auf “niedrig”, “mittel” oder “hoch” setzen. So können Sie je nach Anwendungsfall ein Gleichgewicht zwischen Geschwindigkeit und Genauigkeit herstellen.

Warum Qualitätsdaten für das Fine-Tuning wichtig sind

DieFeinabstimmung ist nur so gut wie die Daten, mit denen Sie sie füttern. Wir können noch so ausgeklügelte Trainingseinstellungen haben, aber wenn unsere Daten verrauscht, inkonsistent oder schlecht formatiert sind, wird Ihr Modell genau diese Probleme lernen. Aus diesem Grund verwenden wir die Web Scraper APIs von Bright Data für saubere, gut formatierte und genaue Daten.

Bright Data kümmert sich um die komplexen Aspekte des Web-Scraping, die bei benutzerdefinierten Lösungen oft zu Problemen führen. Es verwaltet die IP-Rotation zur Vermeidung von Ratenbeschränkungen, löst CAPTCHAs automatisch auf, verarbeitet dynamische, mit JavaScript gerenderte Inhalte und sorgt für eine konsistente Datenqualität bei Millionen von Anfragen.

In unserem Tutorial werden wir die API von Bright Data verwenden, um Python-Dokumentation zu sammeln, die wir dann in Trainingsdaten für unser Modell umwandeln werden.

Voraussetzungen und Einrichten der Umgebung

Bevor wir beginnen, müssen wir sicherstellen, dass Sie alles haben, was Sie für eine erfolgreiche Feinabstimmung benötigen. Wir verwenden Google Colab, weil es kostenlosen GPU-Zugriff bietet, aber der gleiche Prozess funktioniert auf jedem Rechner mit mindestens 16 GB VRAM.

Hardware-Anforderungen

Für dieses Tutorial benötigen Sie:

  • Eine GPU mit mindestens 16 GB VRAM (T4, V100 oder besser)
  • 25 GB freien Festplattenspeicher für Modellgewichte und Kontrollpunkte
  • Stabile Internetverbindung für das Herunterladen von Modellen und Abhängigkeiten

In Google Colab können Sie kostenlos auf einen T4-Grafikprozessor zugreifen, indem Sie:

  1. Öffnen Sie ein neues Notizbuch
  2. Gehen Sie zu Runtime → Change runtime type
  3. Wählen Sie GPU als Hardware-Beschleuniger
  4. Klicken Sie auf Speichern, um die Änderungen zu übernehmen.
colab-gpu-runtime

Installieren von Unsloth und Abhängigkeiten

Sobald Ihre GPU-Laufzeit fertig ist, installieren wir Unsloth und alle notwendigen Abhängigkeiten. Der Installationsprozess ist optimiert, um Konflikte zwischen verschiedenen Paketversionen zu vermeiden:

%%capture
# Unsloth und Kernabhängigkeiten installieren
!pip install --upgrade -qqq uv
try: import numpy; get_numpy = f "numpy=={numpy.__version__}"
except: get_numpy = "numpy"
!uv pip install -qqq 
    "torch>=2.8.0" "triton>=3.4.0" {get_numpy} torchvision bitsandbytes "transformers>=4.55.3" 
    "unsloth_zoo[base] @ git+https://github.com/unslothai/unsloth-zoo" 
    "unsloth[base] @ git+https://github.com/unslothai/unsloth" 
    git+https://github.com/triton-lang/triton.git@05b2c186c1b6c9a08375389d5efe9cb4c401c075#subdirectory=python/triton_kernels
!uv pip install --upgrade --no-deps transformers==4.56.2 tokenizers
!uv pip install --no-deps trl==0.22.2
!pip install -q brightdata-sdk

Dieses Installationsskript behandelt mehrere wichtige Details. Zunächst verwendet es uv für eine schnellere Paketauflösung. Außerdem setzt es bestimmte Versionen fest, um Kompatibilitätsprobleme zu vermeiden, installiert Unsloths benutzerdefinierten Triton-Kernel für optimale Leistung und schließt das Bright Data SDK für unseren Datenerfassungsschritt ein.

Überprüfen der GPU-Einrichtung

Nach der Installation müssen wir überprüfen, ob Ihr Grafikprozessor richtig erkannt wird und über ausreichend Speicher verfügt:

torch importieren

# GPU-Informationen abrufen
gpu_stats = torch.cuda.get_device_properties(0)
max_memory = round(gpu_stats.total_memory / 1024 / 1024 / 1024, 3)

print(f "GPU = {gpu_stats.name}")
print(f "Maximaler Speicher = {max_memory} GB")
print(f "CUDA-Version = {torch.version.cuda}")
print(f "PyTorch Version = {torch.__version__}")

# Überprüfen der Mindestanforderungen
if max_memory < 15:
    print("⚠️ Warning: Your GPU might not have enough memory for GPT-OSS-20B")
sonst:
    print("✅ Ihr Grafikprozessor verfügt über ausreichend Speicher für die Feinabstimmung")

Sie sollten mindestens 15 GB verfügbaren GPU-Speicher sehen. Die T4-GPU im freien Colab bietet 16 GB, was für unsere Bedürfnisse mit den Optimierungen von Unsloth perfekt ist.

Laden von GPT-OSS mit Unsloth

Nun laden wir das GPT-OSS-Modell mit dem optimierten Lader von Unsloth. Der Prozess ist im Vergleich zu Standardtransformatoren bemerkenswert einfach, da Unsloth alle Optimierungsdetails automatisch handhabt.

unsloth-loading

Laden des Basismodells

from unsloth importiere FastLanguageModel
importieren torch

# Konfiguration
max_seq_length = 1024 # Anpassen auf Basis Ihrer Daten
dtype = None # Automatische Erkennung des besten dtype für Ihre GPU

# Unsloth bietet vorquantisierte Modelle für schnelleres Laden
fourbit_models = [
    "unsloth/gpt-oss-20b-unsloth-bnb-4bit", # BitsAndBytes 4bit
    "unsloth/gpt-oss-120b-unsloth-bnb-4bit",
    "unsloth/gpt-oss-20b", # MXFP4-Format
    "unsloth/gpt-oss-120b",
]

# Laden Sie das Modell
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/gpt-oss-20b",
    dtype = dtype,
    max_seq_length = max_seq_length,
    load_in_4bit = True, # Wesentlich für die Anpassung in 16GB
    full_finetuning = False, # LoRA für Effizienz verwenden
)

print(f"✅ Modell erfolgreich geladen!")
print(f "Modellgröße: {model.num_parameters():,} Parameter")
print(f "Gerät verwenden: {model.device}")

Die Methode FastLanguageModel.from_pretrained() erledigt mehrere Dinge hinter den Kulissen. Sie erkennt automatisch Ihre GPU-Fähigkeiten und optimiert sie entsprechend, wendet eine 4-Bit-Quantisierung an, um die Speichernutzung um 75 % zu reduzieren, richtet das Modell für das LoRA-Training anstelle einer vollständigen Feinabstimmung ein und konfiguriert speichereffiziente Aufmerksamkeitsmechanismen.

Konfigurieren von LoRA-Adaptern

LoRA (Low-Rank Adaptation) macht die Feinabstimmung auf Consumer-Hardware erst möglich. Anstatt alle Modellparameter zu aktualisieren, trainieren wir nur kleine Adaptermatrizen, die in Schlüsselschichten eingefügt werden:

model = FastLanguageModel.get_peft_model(
    model,
    r = 8, # LoRA-Rang - höher = mehr Kapazität, aber langsamer
    target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
                      "gate_proj", "up_proj", "down_proj"],
    lora_alpha = 16, # LoRA-Skalierungsfaktor
    lora_dropout = 0, # Dropout deaktiviert für schnelleres Training
    bias = "none", # Bias-Terme nicht trainieren
    use_gradient_checkpointing = "unsloth", # Kritisch für Speichereinsparungen
    random_state = 3407,
    use_rslora = False, # Standard-LoRA funktioniert für die meisten Fälle am besten
    loftq_config = Keine,
)

# Trainingsstatistiken anzeigen
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
all_params = sum(p.numel() for p in model.parameters())
trainable_percent = 100 * trainable_params / all_params

print(f "Training von {trainable_params:,} Parametern aus {all_params:,}")
print(f "Das sind nur {trainable_percent:.2f}% aller Parameter!")
print(f "Eingesparter Speicher: ~{(1 - trainable_percent/100) * 40:.1f}GB")

Diese Konfiguration stellt ein Gleichgewicht zwischen Trainingseffizienz und Modellkapazität her. Mit r=8 trainieren wir weniger als 1 % der Gesamtparameter und erzielen dennoch hervorragende Feinabstimmungsergebnisse. Allein durch das Gradient Checkpointing werden etwa 30 % Speicherplatz eingespart, was den Unterschied zwischen der Anpassung des Modells im Speicher und OOM-Fehlern (Out of Memory) ausmachen kann.

Testen der GPT-OSS Reasoning-Aufwandskontrolle

Bevor wir mit der Feinabstimmung beginnen, wollen wir die einzigartige GPT-OSS-Funktion “Reasoning Effort” testen. Damit können Sie steuern, wie viel das Modell “denkt”, bevor es antwortet:

from transformers import TextStreamer

# Testproblem, das mathematische Überlegungen erfordert
messages = [
    {"role": "user", "content": "Löse x^5 + 3x^4 - 10 = 3. Erkläre deinen Ansatz."},
]

# Test mit GERINGEM Denkaufwand
print("="*60)
print("LOW REASONING (Schnell, aber weniger gründlich)")
print("="*60)

inputs = tokenizer.apply_chat_template(
    Nachrichten,
    add_generation_prompt = True,
    return_tensors = "pt",
    return_dict = True,
    reasoning_effort = "niedrig",  
).to("cuda")

text_streamer = TextStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
_ = model.generate(**inputs, max_new_tokens = 128, streamer = text_streamer)

# Test mit HOHEM Argumentationsaufwand
print("n" + "="*60)
print("HIGH REASONING (Langsamer, aber genauer)")
print("="*60)

inputs = tokenizer.apply_chat_template(
    Nachrichten,
    add_generation_prompt = True,
    return_tensors = "pt",
    return_dict = True,
    reasoning_effort = "hoch",  
).to("cuda")

_ = model.generate(**inputs, max_new_tokens = 512, streamer = text_streamer)

Wenn wir diesen Code ausführen, werden wir sehen, dass das Modell bei “niedrigem” Reasoning eine schnelle ungefähre Antwort gibt, während “hohes” Reasoning eine detailliertere Lösung mit schrittweisem Vorgehen erzeugt. Diese Funktion ist von unschätzbarem Wert, wenn es darum geht, Geschwindigkeit und Genauigkeit in Produktionsumgebungen auszugleichen.

Sammeln von Trainingsdaten mit Bright Data

Jetzt sammeln wir hochwertige Trainingsdaten mit der Web Scraper API von Bright Data. Dieser Ansatz ist wesentlich zuverlässiger als die Entwicklung eines eigenen Scrapers, da Bright Data die gesamte komplexe Infrastruktur für das Web-Scraping in großem Maßstab übernimmt.

Einrichten des Datensammlers

from brightdata import bdclient
from typing import List, Dict
importieren re
importieren json

class DataCollector:
    def __init__(self, api_token: str):
        """
        Initialisierung des Bright Data-Clients für Web-Scraping.

        Args:
            api_token: Ihr Bright Data-API-Token
        """
        self.client = bdclient(api_token=api_token)
        self.collected_data = []
        print("✅ Bright Data-Client initialisiert")

    def collect_documentation(self, urls: List[str]) -> List[Dict]:
        """
        Dokumentations-Seiten auslesen und in Trainingsdaten umwandeln.

        Diese Methode verarbeitet sowohl Batch- als auch individuelles URL-Scraping,
        Sie greift automatisch auf individuelle Anfragen zurück, wenn der Stapel fehlschlägt.
        """
        print(f "Beginnt mit dem Scrapen von {len(urls)} URLs...")

        try:
            # Versuch eines Batch-Scrapings für mehr Effizienz
            results = self.client.scrape(urls, data_format="markdown")

            if isinstance(results, str):
                # Einzelnes Ergebnis zurückgegeben
                print("Processing single result...")
                training_data = self.process_single_result(results)

            elif isinstance(results, list):
                # Mehrere Ergebnisse zurückgegeben
                print(f "Verarbeitung von {len(results)} results...")
                training_data = []
                for i, inhalt in enumerate(ergebnisse, 1):
                    if content:
                        print(f" Verarbeitung von Ergebnis {i}/{len(results)}")
                        examples = self.process_single_result(content)
                        training_data.extend(examples)
            sonst:
                print(f "Unerwarteter Ergebnistyp: {type(results)}")
                training_data = []

        except Exception as e:
            print(f "Batch Scraping fehlgeschlagen: {e}")
            print("Rückgriff auf individuelles URL-Scraping...")

            # Fallback: URLs einzeln scrapen
            training_data = []
            for url in urls:
                try:
                    print(f" Scraping: {url}")
                    Inhalt = self.client.scrape(url, data_format="markdown")
                    if content:
                        examples = self.process_single_result(content)
                        training_data.extend(examples)
                        print(f" ✓ Extrahierte {len(examples)} Beispiele")
                except Exception as url_error:
                    print(f" ✗ Fehlgeschlagen: {url_error}")

        self.collected_data = training_data
        print(f"n✅ Sammlung vollständig: {len(self.collected_data)} Trainingsbeispiele")
        return self.collected_data

Was dieser Code macht:

  • Intelligente Fallback-Strategie: Aus Gründen der Effizienz versucht der Collector zunächst, Batch Scraping durchzuführen. Wenn dies fehlschlägt (aufgrund von Netzwerkproblemen oder API-Beschränkungen), wird automatisch auf individuelles URL-Scraping zurückgegriffen.
  • Fortschrittsverfolgung: Aktualisierungen in Echtzeit zeigen uns genau, was während des Scraping-Prozesses passiert, was die Fehlersuche erleichtert.
  • Fehlerunempfindlichkeit: Jede URL ist in einen eigenen try-catch-Block eingeschlossen, so dass eine fehlgeschlagene URL nicht den gesamten Erfassungsprozess stoppt.
  • Markdown-Format: Wir fordern Daten im Markdown-Format an, weil es sauberer als HTML ist und sich leichter zu Trainingsdaten verarbeiten lässt.

Der Bright Data-Client erledigt mehrere komplexe Aufgaben für uns:

  • Rotieren von IP-Adressen zur Vermeidung von Ratenbegrenzungen
  • Automatisches Lösen von CAPTCHAs
  • Rendering von JavaScript-lastigen Seiten
  • Wiederholung fehlgeschlagener Anfragen mit exponentiellem Backoff

Verarbeitung von gescrapten Inhalten zu Trainingsdaten

Der Schlüssel zu einer guten Feinabstimmung sind saubere, gut formatierte Daten. Im Folgenden wird beschrieben, wie wir rohe gescrapte Inhalte in Frage-Antwort-Paare verarbeiten:

   def process_single_result(self, content: str) -> List[Dict]:
        """
        Verarbeitet den gescrapten Inhalt in saubere Frage-Antwort-Trainings-Paare.

        Diese Methode führt eine aggressive Bereinigung durch, um alle
        Formatierungsartefakte zu entfernen und natürlich klingende Beispiele zu erstellen.
        """
        examples = []

        # Schritt 1: Entfernen aller HTML- und Markdown-Formatierungen
        content = re.sub(r'<[^>]+>', '', content) # HTML-Tags
        content = re.sub(r'![.*?](.*?)', '', content) # Bilder
        content = re.sub(r'[([^]]+)]([^)]+)', r'1', content) # Links
        content = re.sub(r'```[^`]*```', '', content) # Code-Blöcke
        content = re.sub(r'`[^`]+`', '', content) # Inline-Code
        content = re.sub(r'[#*_~>`|-]+', ' ', content) # Markdown-Symbole
        content = re.sub(r'\(.)', r'1', content) # Escape-Sequenzen
        content = re.sub(r'https?://[^s]+', '', content) # URLs
        content = re.sub(r'S+.w+', '', content) # Dateipfade
        content = re.sub(r's+', ' ', content) # Leerzeichen normalisieren

        # Schritt 2: In Sätze aufteilen
        sätze = re.split(r'(?<=[.!?])s+', inhalt)

        # Schritt 3: Herausfiltern von Navigations- und Textbausteininhalten
        clean_sentences = []
        skip_patterns = ['navigation', 'copyright', 'index', 
                        'Inhaltsverzeichnis', 'vorheriges', 'nächstes',
                        'hier klicken', 'herunterladen', 'teilen']

        for sent in sentences:
            sent = sent.strip()
            # Nur wesentliche Sätze beibehalten
            if (len(sent) > 30 und 
                not any(skip in sent.lower() for skip in skip_patterns)):
                clean_sentences.append(sent)

        # Schritt 4: Q&A-Paare aus aufeinanderfolgenden Sätzen erstellen
        for i in range(0, len(clean_sentences) - 1):
            instruction = clean_sentences[i][:200].strip()
            Antwort = clean_sentences[i + 1][:300].strip()

            # Sicherstellen, dass beide Teile wesentlich sind
            if len(instruction) > 20 und len(response) > 30:
                examples.append({
                    "Anweisung": Anweisung,
                    "Antwort": Antwort
                })

        return examples

Wie die Verarbeitung funktioniert:

Die process_single_result-Methode verwandelt rohe Webinhalte in vier entscheidende Schritte in saubere Trainingsdaten:

  • Schritt 1 – Aggressive Bereinigung: Wir entfernen alle Formatierungsartefakte, die das Modell verwirren könnten:
    • HTML-Tags, die die Markdown-Konvertierung überlebt haben könnten
    • Bildverweise und Links, die keinen Mehrwert für das Textverständnis bieten
    • Codeblöcke und Inline-Code (wir wollen Prosa, keine Codebeispiele)
    • Sonderzeichen und Escape-Sequenzen, die Rauschen erzeugen
  • Schritt 2 – Segmentierung von Sätzen: Wir unterteilen den Inhalt mit Hilfe von Satzzeichen in einzelne Sätze. So erhalten wir logische Texteinheiten, mit denen wir arbeiten können.
  • Schritt 3 – Qualitätsfilterung: Wir entfernen:
    • Kurze Sätze (unter 30 Zeichen), denen es an Substanz fehlt
    • Navigationselemente wie “hier klicken” oder “nächste Seite”
    • Boilerplate-Inhalte wie Copyright-Hinweise
    • Jeder Satz, der gängige Navigationsmuster enthält
  • Schritt 4 – Erstellung von Paaren: Wir erstellen Trainingspaare, indem wir aufeinanderfolgende Sätze als Frage-Antwort-Paare behandeln. Dies funktioniert, weil Dokumentationen oft dem Muster folgen, ein Konzept zu erklären und es dann zu erläutern.

Das Ergebnis sind saubere, kontextbezogene Trainingsdaten, aus denen das Modell natürliche Abläufe und Antwortmuster lernt.

Sammeln und Validieren der Daten

Fügen wir nun alles zusammen und sammeln wir unsere Trainingsdaten:

# Initialisieren Sie den Collector mit Ihrem API-Token.
# Holen Sie Ihr Token von: /cp/api_tokens
BRIGHTDATA_API_TOKEN = "ihr_brightdata_api_token_hier"  

collector = DataCollector(api_token=BRIGHTDATA_API_TOKEN)

# URLs zum Scrapen - Python-Dokumentation eignet sich hervorragend als Trainingsdaten
urls = [
    "https://docs.python.org/3/tutorial/introduction.html",
    "https://docs.python.org/3/tutorial/controlflow.html",
    "https://docs.python.org/3/tutorial/datastructures.html",
    "https://docs.python.org/3/tutorial/modules.html",
    "https://docs.python.org/3/tutorial/classes.html",
]

print("="*60)
print("STARTENDE DATENSAMMLUNG")
print("="*60)

training_data = collector.collect_documentation(urls)

# Überprüfen, ob wir Daten erhalten haben
if len(training_data) == 0:
    print("⚠️ ERROR: Keine Trainingsdaten gesammelt!")
    print("nSchritte zur Fehlerbehebung:")
    print("1. Überprüfen Sie, ob Ihr Bright Data API-Token korrekt ist")
    print("2. Überprüfen Sie, ob Ihr Konto über ausreichend Guthaben verfügt")
    print("3. Versuchen Sie es zunächst mit einer einzelnen URL, um die Konnektivität zu testen")
    raise ValueError("Keine Trainingsdaten gesammelt")

Die Einrichtung der Datenerfassung verstehen:

  • API Token: Sie müssen sich für ein Bright Data-Konto anmelden, um Ihr API-Token zu erhalten. Es wird eine kostenlose Testversion mit Guthaben angeboten, damit Sie loslegen können.
  • URL-Auswahl: Wir verwenden die Python-Dokumentation, weil:
  • Sie ist gut strukturiert und konsistent
  • Sie enthält technische Inhalte, die sich perfekt für die Schulung eines Programmierassistenten eignen.
  • Der erklärende Stil lässt sich gut in das Q&A-Format übertragen.
  • Sie ist öffentlich zugänglich und hat eine ethische Quelle.
  • Fehlerbehandlung: Die Validierungsprüfung stellt sicher, dass Sie nicht mit einem leeren Datensatz fortfahren, was später zum Scheitern des Trainings führen würde. Die Schritte zur Fehlerbehebung helfen bei der Diagnose von häufigen Problemen.

Abschließende Datenvalidierung und -bereinigung

Bevor die Daten für das Training verwendet werden, führen wir einen letzten Bereinigungsdurchgang durch:

# Abschließende Validierung und Bereinigung
def final_validation(examples: List[Dict]) -> List[Dict]:
    """
    Endgültige Validierung und Deduplizierung von Trainingsbeispielen durchführen.
    """
    clean_data = []
    seen_instructions = set()

    for ex in examples:
        Anweisung = ex.get('Anweisung', '').strip()
        Antwort = ex.get('Antwort', '').strip()

        # Letzter Reinigungsdurchgang
        Anweisung = re.sub(r'[^a-zA-Z0-9s.,?!]', '', Anweisung)
        Antwort = re.sub(r'[^a-zA-Z0-9s.,?!]', '', Antwort)

        # Duplikate entfernen und Qualität sicherstellen
        if (len(Anweisung) > 10 und 
            len(Antwort) > 20 und 
            Anweisung nicht in seen_instructions):

            seen_instructions.add(instruction)
            clean_data.append({
                "Anweisung": Anweisung,
                "Antwort": Antwort
            })

    return clean_data

training_data = final_validation(training_data)

print(f"n✅ Endgültiger Datensatz: {len(training_data)} eindeutige Beispiele")
print("nSample Trainingsbeispiele:")
print("="*60)

for i, example in enumerate(training_data[:3], 1):
    print(f"nBeispiel {i}:")
    print(f "Q: {example['instruction']}")
    print(f "A: {Beispiel['Antwort']}")

Was die Validierung bewirkt:

  • Deduplizierung: Die Menge der gesehenen Anweisungen stellt sicher, dass es keine doppelten Fragen gibt, die beim Training zu einer Überanpassung führen könnten.
  • Abschließende Zeichenbereinigung: Wir entfernen alle verbleibenden Sonderzeichen mit Ausnahme der einfachen Satzzeichen, um sicherzustellen, dass der Text sauber und konsistent ist.
  • Überprüfung der Länge: Wir setzen Mindestlängen durch, um sicherzustellen, dass die Beispiele Substanz haben:
  • Anweisungen müssen mindestens 10 Zeichen lang sein.
  • Antworten müssen mindestens 20 Zeichen lang sein.
  • Qualitätssicherung: Durch den Ausdruck von Beispielen können Sie die Datenqualität visuell überprüfen, bevor Sie mit dem Training fortfahren.

Die endgültige Ausgabe sollte saubere, lesbare F&A-Paare enthalten, die als Trainingsdaten sinnvoll sind. Wenn die Beispiele unsinnig oder schlecht formatiert aussehen, müssen Sie möglicherweise die Verarbeitungsparameter anpassen oder andere Quell-URLs wählen.

Pro-Tipp: Für produktive Anwendungsfälle sollten Sie den Bright Data-Marktplatz für bereits gesammelte Datensätze nutzen. Er bietet kuratierte Datensätze für verschiedene Bereiche, mit denen Sie viel Zeit sparen und eine gleichbleibende Qualität sicherstellen können.

Formatierung von Daten für GPT-OSS Training

GPT-OSS erwartet Daten in einem bestimmten Chat-Format. Wir werden die Dienstprogramme von Unsloth verwenden, um sicherzustellen, dass unsere Daten für optimale Trainingsergebnisse richtig formatiert sind:

from unsloth.chat_templates import standardize_sharegpt
from datasets import Dataset

def prepare_dataset(raw_data: List[Dict]):
    """
    Konvertiert rohe Q&A-Paare in einen korrekt formatierten Trainingsdatensatz.

    Diese Funktion behandelt:
    1. Konvertierung in das Nachrichtenformat
    2. Anwenden der GPT-OSS-Chatvorlage
    3. Behebung von Formatierungsproblemen
    """

    print("Datensatz für das Training vorbereiten...")

    # Schritt 1: Konvertierung in das Chat-Nachrichtenformat
    formatierte_Daten = []
    for item in raw_data:
        formatted_data.append({
            "messages": [
                {"role": "user", "content": item["instruction"]},
                {"role": "Assistent", "Inhalt": item["Antwort"]}
            ]
        })

    # Schritt 2: HuggingFace-Datensatz erstellen
    Datensatz = Datensatz.from_list(formatierte_Daten)
    print(f "Erstellter Datensatz mit {len(dataset)} Beispielen")

    # Schritt 3: Standardisieren auf das ShareGPT-Format
    Datensatz = standardize_sharegpt(Datensatz)

Was in diesem ersten Teil passiert:

  • Konvertierung des Nachrichtenformats: Wir wandeln unsere einfachen Q&A-Paare in ein Gesprächsformat um, das GPT-Modelle erwarten. Jedes Trainingsbeispiel wird zu einer Konversation mit zwei Runden mit einer Benutzerfrage und einer Assistentenantwort.
  • Erstellung von Datensätzen: Die HuggingFace-Datensatzklasse bietet eine effiziente Datenverarbeitung, einschließlich:
  • Memory-Mapped-Zugriff für große Datensätze
  • Integriertes Stapeln und Mischen von Daten
  • Kompatibilität mit dem gesamten HuggingFace-Ökosystem
  • Standardisierung von ShareGPT: Die Funktion standardize_sharegpt stellt sicher, dass unsere Daten dem ShareGPT-Format entsprechen, das zum De-facto-Standard für das Training von Chatmodellen geworden ist. Damit werden Randfälle behandelt und die Konsistenz sichergestellt.

Anwenden der Chat-Vorlage

Jetzt wenden wir die spezifischen Formatierungsanforderungen von GPT-OSS an:

   # Schritt 4: Anwendung der GPT-OSS-spezifischen Chat-Vorlage
    def formatting_prompts_func(examples):
        """Wende die GPT-OSS-Chatvorlage auf jedes Beispiel an."""
        convos = examples["messages"]
        texts = []

        for convo in convos:
            # Vorlage ohne Generierungsaufforderung anwenden (wir trainieren)
            text = tokenizer.apply_chat_template(
                konvo, 
                tokenize = False, 
                add_generation_prompt = False
            )
            texts.append(text)

        return {"text": texts}

    datensatz = datensatz.map(
        formatierung_prompts_func, 
        batched = Wahr,
        desc = "Chatvorlage anwenden"
    )

Verstehen der Anwendung der Vorlage:

  • Chat-Vorlage Zweck: Jede Modellfamilie hat ihre eigenen speziellen Token und Formatierungen. GPT-OSS verwendet Tags wie <|start|>, <|message|> und <|channel|>, um verschiedene Teile der Konversation abzugrenzen.
  • Keine Generierungsaufforderung: Wir setzen add_generation_prompt = False, weil wir trainieren, nicht generieren. Während des Trainings soll das Modell vollständige Unterhaltungen sehen, keine Aufforderungen, die auf den Abschluss warten.
  • Batched-Verarbeitung: Der Parameter batched = True verarbeitet mehrere Beispiele auf einmal, was den Formatierungsprozess für große Datensätze erheblich beschleunigt.
  • Text-Ausgabe: Wir belassen die Ausgabe in diesem Stadium als Text (nicht tokenisiert), da der Trainer die Tokenisierung mit seinen eigenen Einstellungen vornimmt.

Überprüfen und Beheben von Formatierungsproblemen

GPT-OSS hat eine spezielle Anforderung für das Kanal-Tag, das wir überprüfen müssen:

   # Schritt 5: Überprüfen und korrigieren Sie das Kanal-Tag, falls erforderlich
    sample_text = dataset[0]['text']
    print("nPrüfung des Formats...")
    print(f "Probe (erste 200 Zeichen): {sample_text[:200]}")

    wenn "<|Kanal|>" nicht in sample_text:
        print("⚠️ Fehlender Kanal-Tag, Format überprüfen...")

        def fix_formatting(examples):
            """Füge den Kanal-Tag für GPT-OSS-Kompatibilität hinzu."""
            fixed_texts = []
            for text in examples["text"]:
                # GPT-OSS erwartet Kanal-Tag zwischen Rolle und Nachricht
                text = text.replace(
                    "<|start|>Assistent<|Meldung|>", 
                    "<|start|>Assistent<|Kanal|>endgültig<|Nachricht|>"
                )
                fest_texte.append(text)
            return {"text": fixed_texts}

        datensatz = datensatz.map(
            fix_formatting, 
            batched = Wahr,
            desc = "Hinzufügen von Kanal-Tags"
        )
        print("✅ Formatierung korrigiert")

    print(f"n✅ Datensatz fertig: {len(dataset)} formatierte Beispiele")
    Datensatz zurückgeben

# Vorbereiten des Datensatzes
Datensatz = prepare_dataset(training_data)

Warum das Channel-Tag wichtig ist:

  • Channel Tag Function: Das <|channel|>final-Tag teilt GPT-OSS mit, dass es sich um die endgültige Antwort handelt und nicht um einen zwischengeschalteten Argumentationsschritt. Dies ist Teil des einzigartigen Systems von GPT-OSS zur Kontrolle des Schlussfolgerungsaufwands.
  • Format-Überprüfung: Wir prüfen, ob das Tag existiert und fügen es hinzu, wenn es fehlt. Dies verhindert Trainingsfehler aufgrund von Formatfehlern.
  • Automatische Fixierung: Der Ersetzungsvorgang sorgt für Kompatibilität, ohne dass manuelle Eingriffe erforderlich sind. Dies ist besonders wichtig, wenn verschiedene Tokenizer-Versionen verwendet werden, die möglicherweise unterschiedliche Standardverhaltensweisen haben.

Statistik und Validierung der Datensätze

Lassen Sie uns zum Schluss unseren vorbereiteten Datensatz überprüfen:

# Statistik anzeigen
print("nDatensatz-Statistik:")
print(f "Anzahl der Beispiele: {len(dataset)}")
print(f "Durchschnittliche Textlänge: {sum(len(x['text']) for x in dataset) / len(dataset):.0f} chars")

# Ein vollständig formatiertes Beispiel anzeigen
print("nFormatiertes Beispiel:")
print("="*60)
print(datensatz[0]['text'][:500])
print("="*60)

# Überprüfen, ob alle Beispiele das richtige Format haben
format_checks = {
    "has_user_tag": all("<|start|>user" in ex['text'] for ex in dataset),
    "has_assistant_tag": all("<|start|>assistant" in ex['text'] for ex in dataset),
    "has_channel_tag": all("<|channel|>" in ex['text'] for ex in dataset),
    "has_message_tags": all("<|message|>" in ex['text'] for ex in dataset),
}

print("nFormatüberprüfung:")
for check, passed in format_checks.items():
    status = "✅" if passed else "❌"
    print(f"{status} {prüfung}: {bestanden}")

Worauf bei der Validierung zu achten ist:

  • Längenstatistik: Die durchschnittliche Textlänge hilft Ihnen, eine angemessene Sequenzlänge für das Training festzulegen. Wenn sie zu lang ist, müssen Sie eventuell kürzen oder eine größere max_seq_length verwenden.
  • Vollständigkeit des Formats: Alle vier Prüfungen sollten bestanden werden:
    • Benutzer-Tags zeigen an, wo die Benutzereingabe beginnt
    • Assistenten-Tags markieren Modellantworten
    • Channel-Tags geben den Antworttyp an
    • Message-Tags enthalten den eigentlichen Inhalt
  • Visuelle Inspektion: Anhand des gedruckten Beispiels können Sie genau sehen, worauf das Modell trainiert wird. Es sollte wie folgt aussehen:
 <|Start|>Benutzer<|Nachricht|>Ihre Frage hier<|Ende|>
  <|Start|>Assistent<|Kanal|>Ende<|Nachricht|>Die Antwort hier<|Ende|>

Wenn eine Validierung fehlschlägt, funktioniert das Training möglicherweise nicht richtig oder das Modell lernt falsche Muster. Die automatische Korrektur sollte die meisten Probleme beheben, aber eine manuelle Überprüfung hilft dabei, auch Grenzfälle zu erkennen.

Konfigurieren des Trainings mit Unsloth und TRL

Nun werden wir die Trainingskonfiguration einrichten. Unsloth lässt sich nahtlos in die TRL-Bibliothek von Hugging Face integrieren, sodass wir das Beste aus beiden Welten nutzen können: die Geschwindigkeitsoptimierungen von Unsloth und die bewährten Trainingsalgorithmen von TRL.

von trl importieren SFTConfig, SFTTrainer
from unsloth.chat_templates import train_on_responses_only

# Erstellen Sie die Trainingskonfiguration
training_config = SFTConfig(
    # Grundeinstellungen
    per_device_train_batch_size = 2, # Anpassen basierend auf Ihrem GPU-Speicher
    gradient_accumulation_steps = 4, # Effektive Stapelgröße = 2 * 4 = 8
    warmup_steps = 5,
    max_steps = 60, # Für schnelle Tests; für die Produktion erhöhen

    # Einstellungen für die Lernrate
    lern_rate = 2e-4,
    lr_scheduler_type = "linear",

    # Einstellungen für die Optimierung
    optim = "adamw_8bit", # 8-Bit-Optimierer spart Speicher
    weight_decay = 0.01,

    # Protokollierung und Speicherung
    logging_steps = 1,
    save_steps = 20,
    output_dir = "outputs",

    # Erweiterte Einstellungen
    seed = 3407, # Für die Reproduzierbarkeit
    fp16 = True, # Training mit gemischter Präzision
    report_to = "none", # Für die Experimentverfolgung auf "wandb" setzen
)

print("Trainingskonfiguration:")
print(f" Effektive Stapelgröße: {training_config.per_device_train_batch_size * training_config.gradient_accumulation_steps}")
print(f" Trainingsschritte insgesamt: {training_config.max_steps}")
print(f" Lernrate: {training_config.learning_rate}")

Einrichten des Trainers

Der SFTTrainer (Supervised Fine-Tuning Trainer) übernimmt die gesamte Komplexität des Trainings:

# Initialisieren des Trainers
trainer = SFTTrainer(
    model = model,
    tokenizer = tokenizer,
    train_dataset = dataset,
    args = training_config,
)

print("✅ Trainer initialisiert")

# Konfigurieren Sie, dass nur die Antworten von Assistenten trainiert werden.
# Das ist wichtig - wir wollen nicht, dass das Modell lernt, Benutzerfragen zu generieren.
gpt_oss_kwargs = dict(
    instruction_part = "<|start|>Benutzer<|Nachricht|>",
    response_part = "<|Start|>Assistent<|Kanal|>Ende<|Nachricht|>"
)

trainer = train_on_responses_only(
    trainer,
    **gpt_oss_kwargs,
)

print("✅ Konfiguriert für reines Antworttraining")

Die Einrichtung des Trainers verstehen:

  • SFTTrainer-Integration: Der Trainer kombiniert mehrere Komponenten:
    • Ihr LoRA-konfiguriertes Modell
    • Der Tokenizer zur Verarbeitung von Text
    • Ihr vorbereiteter Datensatz
    • Konfigurationsparameter für das Training
  • Nur-Antwort-Training: Dies ist entscheidend für Chat-Modelle. Durch die Verwendung von train_on_responses_only stellen wir sicher:
    • Das Modell berechnet den Verlust nur für die Antworten des Assistenten
    • Es lernt nicht, Benutzerfragen zu generieren
    • Das Training ist effizienter (weniger zu optimierende Token)
    • Das Modell behält seine Fähigkeit bei, verschiedene Benutzereingaben zu verstehen.
  • GPT-OSS-spezifische Tags: Die Anweisungs- und Antwortteile müssen genau dem entsprechen, was Ihre formatierten Daten enthalten. Diese Tags teilen dem Trainer mit, was ignoriert werden soll (Benutzereingaben) und was trainiert werden soll (Antworten des Assistenten).

Überprüfen der Trainingsmaske

Es ist wichtig zu überprüfen, dass nur die Antworten des Assistenten trainiert werden und nicht die Fragen des Benutzers:

# Überprüfen Sie, ob die Trainingsmaske korrekt ist
print("nÜberprüfung der Trainingsmaske...")
Beispiel = trainer.train_dataset[0]

# Dekodieren Sie die Bezeichnungen, um zu sehen, worauf wir trainieren
-100 zeigt an, dass die Token nicht trainiert werden (maskiert)
visible_tokens = []
for token_id, label_id in zip(sample["input_ids"], sample["labels"]):
    if label_id != -100:
        visible_tokens.append(token_id)

if visible_tokens:
    decoded = tokenizer.decode(visible_tokens)
    print(f "Training am: {decoded[:200]}...")
    print("✅ Maske verifiziert - nur Training auf Antworten")
sonst:
    print("⚠️ Warning: No visible training tokens detected")

Was Ihnen die Maskenüberprüfung sagt:

  • Das -100 Label: In PyTorch ist -100 ein spezieller Wert, der der Verlustfunktion sagt, dass sie diese Token ignorieren soll. Auf diese Weise implementieren wir ein reines Antworttraining:
    • Benutzereingabe-Token werden als -100 (ignoriert) gekennzeichnet.
    • Die Antwort-Token des Assistenten behalten ihre tatsächlichen Token-IDs (trainiert)
  • Sichtbare Token prüfen: Indem wir nur die nicht maskierten Token extrahieren, können wir genau sehen, woraus das Modell lernen wird. Sie sollten nur den Antworttext des Assistenten sehen, nicht die Frage des Benutzers.
  • Warum dies wichtig ist: Ohne angemessene Maskierung:
    • Das Modell könnte lernen, Benutzerfragen statt Antworten zu generieren.
    • Das Training wäre weniger effizient (Optimierung von unnötigen Token)
    • Das Modell könnte unerwünschte Verhaltensweisen entwickeln, wie z. B. das Echo von Benutzereingaben
  • Tipps zur Fehlersuche: Wenn Sie Benutzereingaben im dekodierten Text sehen, prüfen Sie:
    • Ihre instruction_part- und response_part-Strings stimmen genau überein
    • Die Formatierung des Datensatzes enthält alle erforderlichen Tags
    • Der Tokenizer wendet die Chat-Vorlage korrekt an.

Starten des Trainingsprozesses

Nachdem alles konfiguriert ist, können wir mit dem Training beginnen. Überwachen wir die GPU-Speichernutzung und verfolgen wir den Trainingsfortschritt:

importiere Zeit
importieren torch

# GPU-Cache vor dem Training leeren
torch.cuda.empty_cache()

# Aufzeichnung des anfänglichen GPU-Status
start_gpu_memory = torch.cuda.max_memory_reserved() / 1024**3
start_time = time.time()

print("="*60)
print("STARTEN DES TRAININGS")
print("="*60)
print(f "Anfangs reservierter GPU-Speicher: {start_gpu_memory:.2f} GB")
print(f "Training für {training_config.max_steps} Schritte...")
print("nTrainingsfortschritt:")

# Training starten
trainer_stats = trainer.train()

# Trainingsstatistiken berechnen
train_time = time.time() - start_time
final_gpu_memory = torch.cuda.max_memory_reserved() / 1024**3
memory_used = final_gpu_memory - start_gpu_memory

print("n" + "="*60)
print("TRAINING ABGESCHLOSSEN")
print("="*60)
print(f "Zeit benötigt: {training_time/60:.1f} Minuten")
print(f "Endverlust: {trainer_stats.metrics['train_loss']:.4f}")
print(f "Für das Training verwendeter GPU-Speicher: {memory_used:.2f} GB")
print(f "Maximaler GPU-Speicher: {final_gpu_memory:.2f} GB")
print(f "Trainingsgeschwindigkeit: {trainer_stats.metrics.get('train_steps_per_second', 0):.2f} Schritte/Sekunde")

Verstehen der Trainingsmetriken:

  • GPU-Speicherverwaltung:
    • Das Löschen des Caches vor dem Training gibt ungenutzten Speicher frei
    • Die Überwachung der Speichernutzung hilft Ihnen bei der Optimierung der Stapelgrößen für zukünftige Läufe
    • Die Differenz zwischen Start und Ende zeigt den tatsächlichen Trainings-Overhead
    • Anhand des Spitzenspeichers können Sie erkennen, wie nah Sie an OOM-Fehlern sind.
  • Indikatoren für den Trainingsfortschritt:
  • Verlust: Sollte mit der Zeit abnehmen. Bleibt er früh stehen, ist Ihre Lernrate möglicherweise zu niedrig.
  • Schritte/Sekunde: Hilft Ihnen, die Trainingszeit für größere Datensätze abzuschätzen
  • Benötigte Zeit: Auf einem T4-Grafikprozessor sollten Sie etwa 10-15 Minuten für 60 Schritte veranschlagen.
  • Worauf Sie beim Training achten sollten:
    • Stetig abnehmender Verlust (gut)
    • Verlust springt unregelmäßig (Lernrate zu hoch)
    • Verlust ändert sich nicht (Lernrate zu niedrig oder Datenprobleme)
    • Speicherfehler (Stapelgröße oder Sequenzlänge reduzieren)
  • Leistungserwartungen:
    • T4 GPU: 0,5-1,0 Schritte/Sekunde
    • V100: 1,5-2,5 Schritte/Sekunde
    • A100: 3-5 Schritte/Sekunde

Das Training sollte ohne Fehler abgeschlossen werden, und Sie sollten sehen, dass der Verlust von anfänglich 2-3 auf unter 1,0 sinkt.

Testen Ihres fein abgestimmten Modells

Jetzt kommt der spannende Teil: Wir testen, ob unsere Feinabstimmung tatsächlich funktioniert hat! Wir erstellen eine umfassende Testfunktion und bewerten das Modell anhand verschiedener Fragen, die mit Python zusammenhängen:

from transformers import TextStreamer

def test_model(prompt: str, reasoning_effort: str = "medium", max_length: int = 256):
    """
    Teste das fein abgestimmte Modell mit einer gegebenen Aufforderung.

    Args:
        prompt: Die Frage oder Anweisung
        Argumentation_Aufwand: "niedrig", "mittel", oder "hoch"
        max_length: Maximal zu erzeugende Token

    Rückgabe:
        Die generierte Antwort
    """

    # Erstellen des Nachrichtenformats
    messages = [
        {"role": "System", "Inhalt": "Sie sind ein Python-Expertenassistent"},
        {"role": "Benutzer", "Inhalt": prompt}
    ]

    # Chat-Vorlage anwenden
    inputs = tokenizer.apply_chat_template(
        messages,
        add_generation_prompt = True,
        return_tensors = "pt",
        return_dict = True,
        reasoning_effort = reasoning_effort,
    ).to("cuda")

    # Streaming für die Echtzeitausgabe einrichten
    streamer = TextStreamer(
        tokenizer, 
        skip_prompt=True, 
        skip_special_tokens=True
    )

    # Antwort generieren
    outputs = model.generate(
        **inputs,
        max_neue_tokens = max_length,
        streamer = streamer,
        temperatur = 0.7,
        top_p = 0.9,
        do_sample = True,
    )

    # Dekodierung und Rückgabe der Antwort
    response = tokenizer.decode(outputs[0], skip_special_tokens=True)
    Rückgabe der Antwort

# Test zu verschiedenen Python-Themen
test_questions = [
    "Was ist ein Python-Generator und wann sollte ich einen verwenden?",
    "Wie lese ich eine CSV-Datei in Python?",
    "Erkläre async/await in Python anhand eines einfachen Beispiels",
    "Was ist der Unterschied zwischen einer Liste und einem Tupel in Python?",
    "Wie behandle ich Ausnahmen in Python richtig?",
]

print("="*60)
print("TESTING FINE-TUNED MODEL")
print("="*60)

for i, question in enumerate(test_questions, 1):
    print(f"n{'='*60}")
    print(f "Frage {i}: {Frage}")
    print(f"{'='*60}")
    print("Antwort:")
    _ = test_model(Frage, Argumentationsaufwand="mittel")
    print()
colab-finetuned-action

Sie sollten feststellen, dass das Modell nun detailliertere, Python-spezifische Antworten liefert als vor der Feinabstimmung. Die Antworten sollten den Dokumentationsstil und die technische Tiefe Ihrer Trainingsdaten widerspiegeln.

Testen verschiedener Reasoning-Stufen

Lassen Sie uns auch testen, wie sich der Begründungsaufwand auf die Antworten auswirkt:

complex_question = "Schreibe eine Python-Funktion, die alle Primzahlen bis n mit Hilfe des Siebs von Eratosthenes findet"

print("="*60)
print("TESTING REASONING EFFORT LEVELS")
print("="*60)

for effort in ["niedrig", "mittel", "hoch"]:
    print(f"n{'='*40}")
    print(f "Reasoning Aufwand: {Aufwand.upper()}")
    print(f"{'='*40}")
    _ = test_model(komplexe_frage, schlussfolgernder_aufwand=aufwand, max_length=300)
    print()

Wenn Sie den Code ausführen, werden Sie sehen, dass “niedrig” eine grundlegende Implementierung bietet, “mittel” ein gutes Gleichgewicht zwischen Erklärung und Code, während “hoch” detaillierte Erklärungen und Optimierungen enthält.

Speichern und Bereitstellen Ihres Modells

Nach erfolgreicher Feinabstimmung sollten Sie Ihr Modell für die spätere Verwendung speichern. Je nach Ihren Anforderungen an die Bereitstellung haben Sie mehrere Möglichkeiten:

Lokales Speichern

os importieren

# Verzeichnis zum Speichern erstellen
save_dir = "gpt-oss-python-expert"
os.makedirs(save_dir, exist_ok=True)

print("Modell lokal speichern...")

# Option 1: Nur LoRA-Adapter speichern (klein, ~200MB)
lora_save_dir = f"{save_dir}-lora"
model.save_pretrained(lora_save_dir)
tokenizer.save_pretrained(lora_save_dir)
print(f"✅ LoRA-Adapter gespeichert in {lora_save_dir}")

# Überprüfen Sie die Größe
lora_size = sum(
    os.path.getsize(os.path.join(lora_save_dir, f))
    for f in os.listdir(lora_save_dir)
) / (1024**2)
print(f" Größe: {lora_size:.1f} MB")

# Option 2: Zusammengeführtes Modell speichern (volle Größe, ~20GB)
merged_save_dir = f"{save_dir}-merged"
model.save_pretrained_merged(
    merged_save_dir,
    tokenizer,
    save_method = "merged_16bit" # Optionen: "merged_16bit", "mxfp4"
)
print(f"✅ Zusammengeführtes Modell gespeichert in {merged_save_dir}")

Pushen zum Hugging Face Hub

Zur einfachen Freigabe und Bereitstellung können Sie Ihr Modell auf Hugging Face übertragen:

from huggingface_hub import login

# Anmeldung bei Hugging Face (Sie benötigen Ihr Token)
# Token abrufen von: https://huggingface.co/settings/tokens
login(token="hf_...") # Ersetze mit deinem Token

# Push LoRA-Adapter (empfohlen für die gemeinsame Nutzung)
model_name = "ihr-benutzername/gpt-oss-python-expert-lora"
print(f "LoRA-Adapter in {model_name} einfügen...")

model.push_to_hub(
    model_name,
    use_auth_token=True,
    commit_message="Feinabstimmung von GPT-OSS in der Python-Dokumentation"
)

tokenizer.push_to_hub(
    model_name,
    use_auth_token=True
)

print(f"✅ Modell verfügbar unter: https://huggingface.co/{model_name}")

# Optional das zusammengeführte Modell pushen (dauert länger)
if False:  # Auf True setzen, wenn Sie das vollständige Modell pushen wollen
    merged_model_name = "ihr-benutzername/gpt-oss-python-expert"
    model.push_to_hub_merged(
        merged_model_name,
        tokenizer,
        save_method = "mxfp4", # 4-bit für kleinere Größe
        use_auth_token=True
    )

Laden Ihres fein abgestimmten Modells

So laden Sie Ihr Modell später für die Inferenz:

from unsloth import FastLanguageModel

# Laden aus lokalem Verzeichnis
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "gpt-oss-python-expert-lora",  
    max_seq_length = 1024,
    dtype = None,
    load_in_4bit = True,
)

# Oder von Hugging Face Hub laden
model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "ihr-benutzername/gpt-oss-python-expert-lora",
    max_seq_length = 1024,
    dtype = None,
    load_in_4bit = True,
)

print("✅ Modell geladen und bereit für Inferenz!")

Optimierungsstrategien für bessere Ergebnisse

Hier sind einige der Strategien, die ich für die Optimierung der Modellfeinabstimmung als nützlich empfunden habe:

Speicher-Optimierungstechniken

Wenn Sie mit begrenztem GPU-Speicher arbeiten, können diese Techniken den Unterschied zwischen Erfolg und OOM-Fehlern ausmachen:

# 1. Gradient Checkpointing - Tauscht Rechenleistung gegen Speicher
model.gradient_checkpointing_enable()

# 2. Reduzieren Sie die Sequenzlänge, wenn es Ihre Daten erlauben
max_seq_length = 512 # Statt 1024

# 3. Verwenden Sie kleinere Losgrößen mit mehr Akkumulation
per_device_train_batch_size = 1
gradient_accumulation_steps = 16 # Immer noch effektive Chargengröße von 16

# 4. Aktivieren Sie speichereffiziente Aufmerksamkeit (falls unterstützt)
model.config.use_flash_attention_2 = True

# 5. Cache während des Trainings regelmäßig leeren
gc importieren
gc.collect()
torch.cuda.empty_cache()

Bewährte Praktiken beim Training

Erfahrungsgemäß führen diese Praktiken zu besseren Ergebnissen bei der Feinabstimmung:

  1. Klein anfangen: Testen Sie zunächst mit 100 Beispielen. Wenn das funktioniert, erhöhen Sie die Anzahl schrittweise.
  2. Metriken überwachen: Achten Sie auf Überanpassung – wenn der Trainingsverlust sinkt, aber der Validierungsverlust steigt, sollten Sie frühzeitig aufhören.
  3. Mischen Sie Ihre Daten: Kombinieren Sie bereichsspezifische Daten mit allgemeinen Instruktionsdaten, um ein katastrophales Vergessen zu verhindern.
  4. Zeitplan für die Lernrate: Beginnen Sie mit dem Standardwert 2e-4, aber scheuen Sie sich nicht, zu experimentieren. Bei kleineren Datensätzen habe ich gute Ergebnisse mit 5e-5 gesehen.
  5. Checkpointing-Strategie: Speichern Sie alle N Schritte, damit Sie sich vom besten Prüfpunkt aus erholen können:
training_config = SFTConfig(
    save_steps = 50,
    save_total_limit = 3, # Nur 3 beste Checkpoints behalten
    load_best_model_at_end = True,
    metric_for_best_model = "loss",
)

Optimierungen der Geschwindigkeit

Um die Trainingsgeschwindigkeit zu maximieren:

# PyTorch 2.0 compile für schnelleres Training verwenden
if hasattr(torch, 'compile'):
    model = torch.compile(model)
    print("✅ Modell kompiliert für schnelleres Training")

# TF32 auf Ampere-GPUs (A100, RTX 30xx) aktivieren
torch.backends.cuda.matmul.allow_tf32 = True
torch.backends.cudnn.allow_tf32 = True

# Größere Stapelgrößen verwenden, wenn der Speicher dies zulässt
# Größere Batches trainieren im Allgemeinen schneller.
optimal_batch_size = find_optimal_batch_size(model, max_memory=0.9)

Bereitstellungsoptionen für die Produktion

Nach der Feinabstimmung Ihres Modells haben Sie mehrere Bereitstellungsoptionen:

Schnelle lokale API mit FastAPI

Für ein schnelles Prototyping erstellen Sie eine einfache API:

# speichern unter: api.py
from fastapi import FastAPI, HTTPException
von pydantic importieren BaseModel
importieren uvicorn
from unsloth importiere FastLanguageModel

app = FastAPI()

# Modell einmal beim Start laden
model, tokenizer = Keine, Keine

@app.on_event("startup")
async def load_model():
    global model, tokenizer
    model, tokenizer = FastLanguageModel.from_pretrained(
        "gpt-oss-python-expert-lora",
        max_seq_length = 1024,
        load_in_4bit = True,
    )

class GenerateRequest(BaseModel):
    prompt: str
    Begründung_Aufwand: str = "mittel"
    max_tokens: int = 256

@app.post("/generate")
async def generate(request: GenerateRequest):
    if not model:
        raise HTTPException(status_code=503, detail="Model not loaded")

    messages = [{"role": "user", "content": request.prompt}]
    inputs = tokenizer.apply_chat_template(
        messages,
        add_generation_prompt = True,
        return_tensors = "pt",
        reasoning_effort = request.reasoning_effort,
    ).to("cuda")

    outputs = model.generate(
        **Inputs,
        max_new_tokens = request.max_tokens,
        Temperatur = 0,7,
    )

    response = tokenizer.decode(outputs[0], skip_special_tokens=True)
    return {"response": antwort}

# Ausführen mit: uvicorn api:app --host 0.0.0.0 --port 8000

Produktionsbereitstellung mit vLLM

Für den Produktionsbetrieb mit hohem Durchsatz bietet vLLM eine hervorragende Leistung:

# vLLM installieren
pip install vllm

# Serve Ihr Modell
python -m vllm.entrypoints.openai.api_server 
    --model gpt-oss-python-expert-merged 
    --tensor-parallel-size 1 
    --max-model-len 1024 
    --dtype float16

Optionen für die Cloud-Bereitstellung

Jede Cloud-Plattform hat ihre Vorteile:

Hugging Face Inference Endpunkte

  • Einfachste Einrichtung – einfach pushen und bereitstellen
  • Hervorragend geeignet für Tests und kleine Produktionsumgebungen
  • Automatische Skalierung verfügbar

Modal

  • Perfekt für den serverlosen Einsatz
  • Bezahlen Sie nur für die tatsächliche Nutzung
  • Hervorragend geeignet für stoßartige Arbeitslasten

RunPod

  • Kostengünstigste Lösung für 24/7-Betrieb
  • Volle Kontrolle über die Umgebung
  • Gut für Anwendungen mit hohem Durchsatz

AWS SageMaker

  • Unternehmenstauglich mit vollständiger AWS-Integration
  • Erweiterte Überwachung und Protokollierung
  • Am besten geeignet für groß angelegte Produktionseinsätze

Fehlersuche bei häufigen Problemen

Selbst mit den Optimierungen von Unsloth können Sie auf einige Probleme stoßen. Hier erfahren Sie, wie Sie die häufigsten Probleme lösen können:

CUDA Out of Memory-Fehler

Dies ist das häufigste Problem bei der Feinabstimmung großer Modelle:

# Lösung 1: Verringern Sie die Stapelgröße
training_config = SFTConfig(
    per_device_train_batch_size = 1, # Minimale Batch-Größe
    gradient_accumulation_steps = 8, # Kompensieren mit Akkumulation
)

# Lösung 2: Sequenzlänge reduzieren
max_seq_length = 512 # Statt 1024

# Lösung 3: Aggressivere Quantisierung verwenden
model = FastLanguageModel.from_pretrained(
    model_name = "unsloth/gpt-oss-20b",
    load_in_4bit = True,
    use_double_quant = True, # Noch mehr Speichereinsparungen
)

# Lösung 4: Alle Speicheroptimierungen einschalten
use_gradient_checkpointing = "unsloth"
use_flash_attention = True

Langsame Trainingsgeschwindigkeit

Wenn das Training zu lange dauert:

# Verwenden Sie die vollständige Optimierungssuite von Unsloth
model = FastLanguageModel.get_peft_model(
    model,
    use_gradient_checkpointing = "unsloth", # Kritisch
    lora_dropout = 0, # 0 ist schneller als dropout
    bias = "none", # "none" ist schneller als Trainingsverzerrungen
    use_rslora = False, # Standard LoRA ist schneller
)

# Überprüfen Sie, ob Sie den richtigen dtype verwenden
torch.set_float32_matmul_precision('medium') # Oder 'high'

Modell lernt nicht

Wenn Ihr Verlust nicht abnimmt:

  1. Überprüfen Sie das Datenformat: Stellen Sie sicher, dass Ihre Daten genau dem GPT-OSS-Format entsprechen.
  2. Überprüfen Sie die Maskierung von Antworten: Bestätigen Sie, dass Sie nur auf Antworten trainieren
  3. Lernrate anpassen: Versuchen Sie 5e-4 oder 1e-4 anstelle von 2e-4
  4. Erhöhen Sie die Datenqualität: Entfernen Sie Beispiele schlechter Qualität
  5. Fügen Sie mehr Daten hinzu: 500+ Beispiele funktionieren in der Regel besser als 100

Inkonsistente Ergebnisse

Wenn das Modell inkonsistente oder qualitativ schlechte Ergebnisse erzeugt:

# Niedrigere Temperatur für konsistentere Ausgaben verwenden
outputs = model.generate(
    temperature = 0.3, # Niedriger = konsistenter
    top_p = 0.9,
    repetition_penalty = 1.1, # Reduce repetition
)

# Feinabstimmung für mehr Schritte
max_steps = 200 # Statt 60

# Datenfilterung mit höherer Qualität verwenden
min_response_length = 50 # Anstelle von 30

Schlussfolgerung

Schlussfolgerung

Die Feinabstimmung von GPT-OSS ist schneller und einfacher, wenn Sie die Geschwindigkeit von Unsloth mit hochwertigen, strukturierten Trainingsdaten kombinieren, die von einem der führenden Unternehmen für KI-Trainingsdaten bereitgestellt werden. Die Verwendung der Lösungen von Bright Data für KI stellt sicher, dass Sie Zugang zu den zuverlässigen Daten haben, die für eine effektive Feinabstimmung erforderlich sind, sodass Sie maßgeschneiderte KI-Modelle für jeden Anwendungsfall erstellen können.

Weitere Informationen zu KI-gesteuerten Datenextraktionsstrategien finden Sie in diesen zusätzlichen Ressourcen: