For the complete documentation index, see llms.txt. This page is also available as Markdown.

Tutorial: Wie man gpt-oss mit RL trainiert

Lerne, OpenAI gpt-oss mit GRPO zu trainieren, um 2048 autonom lokal oder auf Colab zu schlagen.

LLMs haben oft Schwierigkeiten mit Aufgaben, die komplexe Umgebungen beinhalten. Durch das Anwenden von bestärkendem Lernen (RL) und das Entwerfen einer benutzerdefinierten Belohnungsfunktionkönnen diese Herausforderungen jedoch überwunden werden.

RL kann für Aufgaben wie automatische Kernel- oder Strategieerstellung angepasst werden. Dieses Tutorial zeigt, wie man gpt-oss mit GRPO und Unsloth trainiert, um 2048 autonom zu schlagen.

2048-Notebook (Offizielles OpenAI-Beispiel)

Was Sie erstellen werden:

  • Trainiere gpt-oss-20b so, dass das Modell 2048 automatisch gewinnt

  • Erstelle eine minimale 2048-Umgebung, mit der das Modell interagieren kann

  • Definiere Belohnungsfunktionen die:

    1. Überprüfen, ob die generierte Strategie kompiliert und ausgeführt werden kann,

    2. Reward-Hacking verhindern (externe Imports verbieten) und

    3. den tatsächlichen Spielerfolg belohnen

  • Inference ausführen und das Modell exportieren (MXFP4 4-Bit oder zusammengeführtes FP16)

Hardware: Das 2048-Beispiel läuft auf einer kostenlosen Colab T4, aber das Training wird langsam sein. A100/H100 ist deutlich schneller. 4-Bit-Laden + LoRA ermöglicht es, ein 20B-Modell in moderatem VRAM unterzubringen.

1

Unsloth installieren

Führen Sie diese Zelle am Anfang eines Notebooks aus (funktioniert auf Colab).

!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.56.2" \\
    "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
2

gpt-oss mit Unsloth laden

Lade das 20B-Modell in 4-Bit-QLoRA für Speichereffizienz und wickle es dann mit einem LoRA-Adapter ein. Sie können es auch in 16-Bit-LoRA trainieren, aber es verwendet 4x mehr Speicher. Weitere Einstellungen finden Sie in unserem Konfigurationsleitfaden.

from unsloth import FastLanguageModel
import torch

max_seq_length = 768        # Erhöhen, wenn Ihre Aufgabe längere Ausgaben erfordert
lora_rank      = 4          # Höherer Rang → besser, aber mehr VRAM/Rechenleistung

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name        = "unsloth/gpt-oss-20b",  # oder unsloth/gpt-oss-20b-BF16 auf H100
    max_seq_length    = max_seq_length,
    load_in_4bit      = True,                    # False für 16-Bit
    offload_embedding = True,                    # spart ~1 GB VRAM
)

model = FastLanguageModel.get_peft_model(
    model,
    r = lora_rank,
    target_modules = [
        "q_proj", "k_proj", "v_proj", "o_proj",
        "gate_proj", "up_proj", "down_proj",
    ],
    lora_alpha = lora_rank * 2,
    use_gradient_checkpointing = "unsloth",     # großer Speicher-Sparer
    random_state = 3407,
)

Wenn ein OOM auftritt, versuchen Sie, max_seq_length, lora_rankoder num_generations (später) zu verringern, und lassen Sie load_in_4bit=True.

3

2048-Spielumgebung (minimal)

  • Ein GameBoard -Klasse, die W/A/S/D -Züge unterstützt

  • Merge-/Punkte-Logik

  • execute_with_time_limit -Wrapper, damit schlecht geschriebene Strategien den Kernel nicht aufhängen können

Sie können schnell einen Smoke-Test mit einer trivialen Politik durchführen:

def always_move_left(board):
    return "W"

steps, outcome = execute_strategy(always_move_left, GameBoard(size=8, seed=42, target=2048, probability_fours=0.10))
4

Sichere Codeausführung und Anti-Cheat-Prüfungen

Generierte Strategien sind Python-Funktionen. Um die Ausführung sicher zu halten und Reward-Hacking zu verhindern:

  • Prüfung der Modulliste — nur Python-Stdlib-Symbole erlauben:

    from unsloth import check_python_modules
    ok, info = check_python_modules("""
    def strategy(board):
        import math
        from typing import Callable
        return "W"
    """)
    # ok == True bedeutet, dass nur Python-Level-Imports verwendet wurden
  • Verbotene Imports blockieren (z. B. NumPy):

    sample = """
    def strategy(board):
        from numpy import matmul
        return "W"
    """
    ok, info = check_python_modules(sample)  # ok => False
  • Die Ausführung absichern in eine Sandbox-Funktion:

    from unsloth import create_locked_down_function
    function = """
    def add(a, b):
        def adder(a):
            return a + b
        return adder(b) + b
    """
    f = create_locked_down_function(function)  # Fehler, wenn Globals / Imports verwendet werden
  • Eine harte Laufzeitbegrenzung erzwingen für Strategieläufe:

    from unsloth import execute_with_time_limit
    @execute_with_time_limit(2)
    def execute_strategy(strategy, game):
        # Schleife bis zum Spielende oder Timeout
        ...
5

### Prompt & Datensatz

Wir fordern das Modell auf, eine kurze Strategie-Funktion auszugeben innerhalb von dreifachen Backticks:

Erstelle eine neue kurze 2048-Strategie, die nur nativen Python-Code verwendet.
Dir wird eine Liste von Listen von Zahlen für den aktuellen Spielbrettzustand gegeben.
Gib eine Aktion für "W", "A", "S", "D" aus, basierend auf dem optimalen nächsten Schritt.
Gib deine neue kurze Funktion in Backticks im folgenden Format aus:
```python
def strategy(board):
    return "W"  # Beispiel

Alle Hilfsfunktionen sollten innerhalb von def strategy liegen. Gib nur die kurze Funktion aus strategy.


Erstelle einen winzigen synthetischen Datensatz (unter Wiederverwendung desselben Prompts) und berechne die Prompt-Länge, damit GRPO weiß, wie viele Completion-Tokens gesampelt werden sollen:

```python
from datasets import Dataset

prompt = ...  # wie oben

maximum_length = len(tokenizer.apply_chat_template(
    [{"role": "user", "content": prompt}], add_generation_prompt=True
))

dataset = Dataset.from_list([
    {"prompt": [{"role": "user", "content": prompt}], "answer": 0, "reasoning_effort": "low"}
] * 1000)

{% hint style="info" %} Sie können diesen Datensatz durch echte Prompts für Ihre eigene RL-Aufgabe ersetzen. {% endhint %} {% endstep %}

{% step %}

Zeit für die Belohnungsfunktion!

  1. Extrahiere den Codeblock aus der Antwort des Modells:

    def extract_function(text):
        if text.count("```") >= 2:
            first = text.find("```") + 3
            second = text.find("```", first)
            fx = text[first:second].strip()
            fx = fx.removeprefix("python\n")
            fx = fx[fx.find("def"):]
            if fx.startswith("def strategy(board):"):
                return fx
        return None
  2. function_works - Kompiliert es und erzeugt es eine aufrufbare Funktion?

    from unsloth import create_locked_down_function, check_python_modules
    
    def function_works(completions, **kwargs):
        scores = []
        for completion in completions:
            response = completion[0]["content"]
            function = extract_function(response)
            if function is None:
                scores.append(-2.0)
                continue
            ok, info = check_python_modules(function)
            if "error" in info:
                scores.append(-2.0)
                continue
            try:
                _ = create_locked_down_function(function)
                scores.append(1.0)
            except Exception:
                scores.append(-0.5)
        return scores
  3. no_cheating - Keine Imports, die nicht aus der Stdlib stammen, erlaubt:

    def no_cheating(completions, **kwargs):
        scores = []
        for completion in completions:
            response = completion[0]["content"]
            function = extract_function(response)
            if function is None:
                scores.append(-1.0)
                continue
            ok, _ = check_python_modules(function)
            scores.append(1.0 if ok else -20.0)  # hohe Strafe bei Schummeln
        return scores
  4. strategy_succeeds - Spiele ein zufälliges Board; Erfolg belohnen:

    import numpy as np
    
    PRINTER = 0  # gelegentlich zum Debuggen ausgeben
    
    def strategy_succeeds(completions, **kwargs):
        global PRINTER
        scores = []
        seed = np.random.randint(10000)
        for completion in completions:
            response = completion[0]["content"]
            function = extract_function(response)
            if function is None:
                scores.append(-2.0)
                continue
            try:
                new_strategy = create_locked_down_function(function)
            except Exception:
                scores.append(0.0)
                continue
            try:
                game = GameBoard(size=6, seed=seed, target=2048, probability_fours=0.10)
                steps, state = execute_strategy(new_strategy, game)
                if PRINTER % 5 == 0:
                    print(function)
                    print(f"Steps={steps} State={state}")
                    print(game.board().pretty())
                PRINTER += 1
                if state == "success":
                    scores.append(20.0)
                else:
                    scores.append(2.0)   # funktionierte, erreichte aber nicht 2048
            except TimeoutError:
                scores.append(-1.0)      # Timeout
            except Exception:
                scores.append(-3.0)      # abgestürzt
        return scores

{% endstep %}

{% step %}

GRPO konfigurieren

Wir werden den GRPOTrainerverwenden. Legen Sie die Prompt-/Completion-Längen fest und erstellen Sie dann eine GRPOConfig. Beachten Sie, dass Sie den RL-Algorithmus-Typ auch auf andere wie GSPO oder Dr. GRPO setzen könnten.

from trl import GRPOConfig, GRPOTrainer

max_prompt_length     = maximum_length + 1
max_completion_length = max_seq_length - max_prompt_length

training_args = GRPOConfig(
    temperature=1.0,
    learning_rate=5e-5,
    weight_decay=0.01,
    warmup_ratio=0.1,
    lr_scheduler_type="linear",
    optim="adamw_8bit",
    logging_steps=1,
    per_device_train_batch_size=1,
    gradient_accumulation_steps=1,    # auf 4 erhöhen für glattere Belohnungssignale
    num_generations=2,                # verringern, wenn OOM auftritt
    max_prompt_length=max_prompt_length,
    max_completion_length=max_completion_length,
    max_steps=1000,                   # oder num_train_epochs=1 setzen
    save_steps=100,
    report_to="none",
    output_dir="outputs",
)

trainer = GRPOTrainer(
    model=model,
    processing_class=tokenizer,
    reward_funcs=[function_works, no_cheating, strategy_succeeds],
    args=training_args,
    train_dataset=dataset,
    # Optionaler Eval-Split:
    # train_dataset=new_dataset["train"],
    # eval_dataset=new_dataset["test"],
)

{% hint style="info" %} Logs lesen: Achten Sie auf reward und reward_std. Es ist normal, anfangs niedrige/null Belohnungen zu sehen (in den ersten ~100–200 Schritten auf kleinen GPUs). {% endhint %} {% endstep %}

{% step %}

Trainiere Ihr Modell

trainer.train()

Dies startet die vollständige RL-Schleife: Completions sampeln → mit Ihren Belohnungen bewerten → die Policy optimieren (LoRA). {% endstep %}

{% step %}

Inference (nach dem Training)

Generiere eine frische Strategie mit dem trainierten Adapter:

from transformers import TextStreamer

text = tokenizer.apply_chat_template(
    [{"role": "user", "content": prompt}],
    tokenize=False,
    add_generation_prompt=True,
    reasoning_effort="low",
)

_ = model.generate(
    **tokenizer(text, return_tensors="pt").to("cuda"),
    temperature=1.0,
    max_new_tokens=1024,
    streamer=TextStreamer(tokenizer, skip_prompt=False)

{% endstep %}

{% step %}

Speichern / Exportieren Sie Ihr feinabgestimmtes Modell

  • 4-Bit zusammenführen & speichern (MXFP4)

python model.save_pretrained_merged("finetuned_model", tokenizer, save_method="mxfp4") # oder model.push_to_hub_merged("<org_or_user>/", tokenizer, token="<hf_token>", save_method="mxfp4") ```

  • 16-Bit zusammenführen & speichern

    model.save_pretrained_merged("finetuned_model", tokenizer, save_method="merged_16bit")
    # oder pushen
    model.push_to_hub_merged("<org_or_user>/<repo>", tokenizer, token="<hf_token>", save_method="merged_16bit")
6

Fehlerbehebung & Tipps

  • OOM / langsam: reduzieren max_seq_length, num_generations, lora_rank; 4-Bit beibehalten; falls verfügbar, A100 ausprobieren.

  • Keine Verbesserung der Belohnung: Trainingsschritte erhöhen, Strafen abschwächen oder Curriculum hinzufügen (mit kleineren Brettern / niedrigeren Zielen beginnen).

  • Reward-Hacking: check_python_modules streng halten; das Verhalten der Strategie über mehrere Zufalls-Seeds hinweg validieren.

  • Instabiles Training: erhöhen gradient_accumulation_steps um Updates zu glätten; learning_rate verringern (z. B. 2e-5).

  • Lange Hänger: sicherstellen, execute_with_time_limit dass jede Strategiausführung umschließt.

7

An Ihre eigene RL-Aufgabe anpassen

  • Ersetzen Sie die 2048-Umgebung durch Ihre eigene Umgebung und drei Belohnungen: (a) Syntax/Kompilierung, (b) Anti-Cheat/Sicherheit, (c) Aufgabenerfolg.

  • Aktualisieren Sie den Prompt um die Art von Funktion oder Ausgabe anzufordern, die Sie benötigen.

  • Behalten Sie denselben Unsloth- + GRPO-Rahmen bei; tauschen Sie nur Umgebung und Belohnungen aus.

Zuletzt aktualisiert

War das hilfreich?