Kostenoptimierung für LLM-Systeme: Wo das Geld tatsächlich hingeht

Setze Token gezielt dort ein, wo es wirklich zählt.

Inhaltsverzeichnis

LLM-Kosten skalieren linear mit der Nutzung. Ein System, das 10.000 Anfragen pro Tag zu 0,01 USD pro Anfrage verarbeitet, kostet 100 USD täglich – 365 USD pro Jahr. Im Unternehmensumfeld belaufen sich diese Kosten auf über 10.000 USD.

Kostenoptimierung bedeutet nicht, Abstriche zu machen. Es geht darum, Tokens dort auszugeben, wo sie wirklich zählen.

Jeder verschwendete Token ist ein Token, den man für eine bessere Antwort hätte ausgeben können.

Strategien zur LLM-Kostenoptimierung

Token-Budgetierung

Der einfachste Weg, Kosten zu kontrollieren, ist das Setzen von Limits. Pro Sitzung, pro Aufgabe oder pro Tag.

Strategie 1: Budgets pro Sitzung

Budgets pro Sitzung sind unkompliziert:

class SessionBudget:
    def __init__(self, budget_tokens: int = 10000):
        self.budget = budget_tokens
        self.used = 0

    def allocate(self, tokens: int) -> bool:
        if self.used + tokens <= self.budget:
            self.used += tokens
            return True
        return False

    def remaining(self) -> int:
        return self.budget - self.used

Strategie 2: Budgets pro Aufgabe

Budgets pro Aufgabe sind nützlicher. Unterschiedliche Aufgaben erfordern unterschiedlich viel Kontext:

task_budgets:
  classify:
    max_tokens: 100
    model: qwen3-1.7b
  summarize:
    max_tokens: 500
    model: qwen3-8b
  code_review:
    max_tokens: 2000
    model: qwen2.5-coder-7b
  reason:
    max_tokens: 4000
    model: qwen3-32b

Strategie 3: Adaptive Budgets

Adaptive Budgets passen sich an an, was tatsächlich passiert. Wenn Klassifizierungsaufgaben konsequent 80 Tokens verbrauchen, sollte man aufhören, 100 zuzuweisen:

class AdaptiveBudget:
    def __init__(self):
        self.task_history = {}

    def allocate(self, task_type: str) -> int:
        if task_type in self.task_history:
            return int(self.task_history[task_type] * 1.5)
        return 1000

    def record(self, task_type: str, tokens_used: int):
        if task_type not in self.task_history:
            self.task_history[task_type] = tokens_used
        else:
            self.task_history[task_type] = (
                0.9 * self.task_history[task_type] + 0.1 * tokens_used
            )

Das exponentielle gleitende Mittel (Gewichtung 0,9) bedeutet, dass kürzliche Nutzung wichtiger ist als die Historie. Passen Sie die Gewichtung an die Volatilität Ihrer Workloads an.

API vs. lokale Inferenz

Lokale Inferenz ist im großen Maßstab günstiger. Der Break-even-Punkt hängt von Ihrer Hardware und den API-Tarifen ab.

Modell API (USD/Millionen Tokens) Lokale Kosten/Stunde Break-even
GPT-4o 2,50 USD / 10,00 USD — N/A
Claude Sonnet 4 3,00 USD / 15,00 USD — N/A
Qwen2.5-72B 0,50 USD / 2,00 USD ca. 0,50 USD ca. 4 Stunden/Tag
qwen3-32b 0,30 USD / 1,20 USD ca. 0,20 USD ca. 2 Stunden/Tag
qwen3-8b 0,10 USD / 0,40 USD ca. 0,05 USD ca. 1 Stunde/Tag

Die Hardware-Berechnung:

Hardware Vorabkosten Monatlicher Stromverbrauch Break-even gegenüber API
RTX 3090 (gebraucht) 600 USD 15 USD ca. 4 Monate
RTX 4090 1.500 USD 20 USD ca. 6 Monate
RTX 5080 1.000 USD 18 USD ca. 5 Monate
DGX Spark 2.000 USD 30 USD ca. 8 Monate

Bei mäßiger Nutzung – eine Stunde oder mehr pro Tag – amortisiert sich die lokale Inferenz. Bei hoher Nutzung sind die Ersparnisse erheblich. Der Haken ist das Vorabkapital. Eine RTX 5080 kostet 1.000 USD. Eine API-Rechnung kann man pausieren. Hardware nicht.

Diese Break-even-Berechnung setzt voraus, dass man frei wechseln kann, was oft die größere Unbekannte ist als die Hardwarekosten. Wenn Ihre Fine-Tuning-Daten, Evaluierungs-Harnesses und Tool-Schemata bereits um die API eines bestimmten Anbieters herum aufgebaut sind, besteht die eigentliche Migrationskosten aus dem Engineering-Aufwand, dieses aufzulösen – nicht aus dem Preis einer GPU. Datenanziehung: Die wahren Kosten von API-first AI behandelt diesen Lock-in-Mechanismus und eine Checkliste, um zu bewerten, wie tief man bereits darin steckt, bevor man diese Zahlen durchrechnet.

Fallback-Strategien

Wenn Ihr bevorzugtes Modell zu teuer oder zu langsam ist, weichen Sie auf etwas Günstigeres aus. Der Schlüssel ist zu wissen, wann die Qualität „gut genug“ ist.

Strategie 1: Qualität-basierter Fallback

Qualität-basierter Fallback probiert Modelle aus, bis die Ausgabe einen Schwellenwert erreicht:

class QualityFallback:
    def __init__(self, quality_threshold: float = 0.8):
        self.threshold = quality_threshold
        self.models = [
            {"model": "claude-sonnet-4", "cost": 0.015},
            {"model": "qwen2.5-72b", "cost": 0.002},
            {"model": "qwen3-32b", "cost": 0.001},
            {"model": "qwen3-8b", "cost": 0.0004},
        ]

    def route(self, prompt: str) -> str:
        for model_config in self.models:
            result = self.call_model(model_config["model"], prompt)
            if self.evaluate_quality(result) >= self.threshold:
                return result
        return self.call_model(self.models[0]["model"], prompt)

Das Problem ist die Bewertung selbst. Wie misst man Qualität, ohne ein anderes Modell aufzurufen? Einige Systeme verwenden einen kleinen Klassifizierer. Andere nutzen heuristische Prüfungen – Länge, Struktur, Vorhandensein von Schlüsselwörtern. Keine davon ist perfekt.

Strategie 2: Latenz-basierter Fallback

Latenz-basierter Fallback ist einfacher. Leiten Sie an das schnellste Modell weiter, das Ihr Zeitbudget erfüllt:

class LatencyFallback:
    def __init__(self, max_latency: float = 5.0):
        self.max_latency = max_latency
        self.models = [
            {"model": "qwen3-1.7b", "latency": 0.5},
            {"model": "qwen3-8b", "latency": 2.0},
            {"model": "qwen3-32b", "latency": 10.0},
            {"model": "claude-sonnet-4", "latency": 5.0},
        ]

    def route(self, prompt: str) -> str:
        for model_config in sorted(self.models, key=lambda x: x["latency"]):
            if model_config["latency"] <= self.max_latency:
                return self.call_model(model_config["model"], prompt)
        return self.call_model(self.models[0]["model"], prompt)

Caching

Caching ist die am meisten unterschätzte Kostenoptimierung. Identische Prompts passieren öfter, als man denkt – Klassifizierungsanfragen, FAQ-artige Abfragen, wiederholte Tool-Aufrufe.

Strategie 1: Prompt-Caching

Exaktes Prompt-Caching ist einfach:

import hashlib

class PromptCache:
    def __init__(self, max_size: int = 1000):
        self.cache = {}
        self.max_size = max_size

    def get(self, prompt: str) -> str | None:
        key = hashlib.sha256(prompt.encode()).hexdigest()
        return self.cache.get(key)

    def set(self, prompt: str, response: str):
        key = hashlib.sha256(prompt.encode()).hexdigest()
        if len(self.cache) >= self.max_size:
            self.cache.pop(next(iter(self.cache)))
        self.cache[key] = response

Strategie 2: Semantisches Caching

Semantisches Caching ist nützlicher. Es fängt Prompts ab, die sich unterscheiden, aber dasselbe bedeuten:

from sentence_transformers import SentenceTransformer

class SemanticCache:
    def __init__(self, similarity_threshold: float = 0.95):
        self.model = SentenceTransformer('all-MiniLM-L6-v2')
        self.cache = {}
        self.threshold = similarity_threshold

    def get(self, prompt: str) -> str | None:
        prompt_embedding = self.model.encode([prompt])[0]
        for cached_prompt, cached_response in self.cache.items():
            cached_embedding = self.model.encode([cached_prompt])[0]
            similarity = self.cosine_similarity(
                prompt_embedding, cached_embedding
            )
            if similarity >= self.threshold:
                return cached_response
        return None

    def set(self, prompt: str, response: str):
        self.cache[prompt] = response

Der Schwellenwert ist entscheidend. 0,95 ist aggressiv – nur sehr ähnliche Prompts werden abgeglichen. 0,85 ist nachsichtiger, birgt aber das Risiko, falsche Antworten zurückzugeben. Messen Sie Ihre Trefferquote und passen Sie den Wert an.

Antwort-Caching für häufige Abfragen lohnt sich ebenfalls. Wenn Benutzer wiederholt fragen „Wie ist das Wetter“ oder „Wie spät ist es“, cachen Sie das Muster, nicht nur den exakten Prompt:

class ResponseCache:
    def __init__(self):
        self.common_queries = {
            "what is the weather": "Check weather API",
            "what is the time": "Check system time",
            "who is the president": "Check current president",
        }

    def get(self, query: str) -> str | None:
        query_lower = query.lower()
        for common_query, response in self.common_queries.items():
            if common_query in query_lower:
                return response
        return None

Das ist nicht hochkomplex, aber es funktioniert. Häufige Abfragen sind häufig aus einem Grund.

Wann sich Optimierung lohnt

Optimierung ist wichtig, wenn Sie große Volumen verarbeiten, gemischte Workloads betreiben oder API-Kosten zahlen, die sich aufsummieren.

Sie ist nicht relevant, wenn Sie Prototypen bauen, ein einzelnes Modell verwenden oder geringe Volumina verarbeiten. Die Komplexität von Budgetierung, Fallback und Caching ist für ein System, das 100 Anfragen pro Tag macht, nicht lohnenswert.

Bringen Sie zuerst den grundlegenden Ablauf zum Laufen. Fügen Sie Optimierung hinzu, wenn die Rechnung eingeht.

Kompromisse

Strategie Kosten Qualität Komplexität
Keine Optimierung Höchste Konstant Niedrigste
Token-Budgetierung Mittel Variabel Mittel
Fallback-Modelle Niedrig-Mittel Variabel Mittel
Caching Niedrigste Hoch (bei Cache-Treffern) Mittel
Hybrid Optimiert Optimiert Höchste

Produktionsysteme laufen in der Regel hybrid. Budgetierung pro Sitzung, Fallback basierend auf Qualität oder Latenz, Caching was möglich ist. Die Komplexität ist real, aber auch die Ersparnisse.

Verwandte Artikel

Abonnieren

Neue Beiträge zu Systemen, Infrastruktur und KI-Engineering.