Kostenoptimierung für LLM-Systeme: Wo das Geld tatsächlich fließt

Verwende Tokens dort, wo es wirklich zählt.

Inhaltsverzeichnis

Die Kosten für LLMs steigen linear mit der Nutzung. Ein System, das täglich 10.000 Anfragen mit $0,01 pro Anfrage verarbeitet, kostet täglich $100 — also $365 pro Jahr. Im Unternehmensmaßstab belaufen sich die Kosten auf über $10.000.

Kostenoptimierung bedeutet nicht, an Ecken und Kanten zu sparen. Es geht darum, Tokens dort einzusetzen, wo sie wirklich zählen.

Jeder Token, den Sie verschwenden, ist ein Token, den Sie für eine bessere Antwort hätten ausgeben können.

Strategien zur Optimierung der LLM-Kosten

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 praktischer. Verschiedene Aufgaben benötigen unterschiedliche Kontextmengen:

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 das tatsächliche Geschehen an. Wenn Klassifizierungsaufgaben konsistent 80 Tokens verbrauchen, hören Sie auf, 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
            )

Der exponentielle gleitende Durchschnitt (mit einem Gewicht von 0,9) bedeutet, dass die aktuelle Nutzung stärker gewichtet wird als die Historie. Passen Sie das Gewicht an die Volatilität Ihrer Arbeitslasten 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-Raten ab.

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

Die Hardware-Rechnung:

Hardware Vorabkosten Monatlicher Stromverbrauch Break-even vs. API
RTX 3090 (gebraucht) $600 $15 ~4 Monate
RTX 4090 $1.500 $20 ~6 Monate
RTX 5080 $1.000 $18 ~5 Monate
DGX Spark $2.000 $30 ~8 Monate

Bei moderater Nutzung — eine Stunde oder mehr pro Tag — amortisiert sich die lokale Inferenz. Bei hoher Nutzung sind die Einsparungen erheblich. Der Haken ist das Vorabkapital. Eine RTX 5080 kostet $1.000. Eine API-Rechnung können Sie pausieren. Hardware nicht.

Fallback-Strategien

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

Strategie 1: Qualität basierter Fallback

Qualitätsbasierter Fallback versucht Modelle, bis die Ausgabe eine Schwelle 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 Evaluation selbst. Wie messen Sie die Qualität, ohne ein weiteres Modell aufzurufen? Einige Systeme nutzen einen kleinen Klassifizierer. Andere verwenden heuristische Prüfungen — Länge, Struktur, Vorhandensein von Schlüsselwörtern. Keines davon ist perfekt.

Strategie 2: Latenz basierter Fallback

Latenzbasierter Fallback ist einfacher. Leiten Sie zum schnellsten Modell weiter, das Ihrem Zeitbudget entspricht:

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 wenigsten geschätzte Kostenoptimierung. Identische Prompts treten häufiger auf, 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 erkennt Prompts, die unterschiedlich sind, 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

Die Schwelle ist wichtig. 0,95 ist aggressiv — nur sehr ähnliche Prompts werden übereinstimmend erkannt. 0,85 ist nachsichtiger, birgt aber das Risiko, falsche Antworten zurückzugeben. Messen Sie Ihre Fehlerrate und passen Sie an.

Auch das Caching von Antworten bei häufigen Abfragen lohnt sich. Wenn Nutzer immer wieder 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 ausgefeilt, aber es funktioniert. Häufige Abfragen sind aus einem Grund häufig.

Wann Optimierung hilft

Optimierung ist relevant, wenn Sie große Volumina verarbeiten, gemischte Arbeitslasten ausführen oder API-Kosten zahlen, die sich summieren.

Sie ist irrelevant, wenn Sie prototypen, ein einzelnes Modell verwenden oder niedrige Volumina verarbeiten. Die Komplexität von Budgetierung, Fallback und Caching lohnt sich nicht für ein System, das täglich 100 Anfragen stellt.

Lassen Sie zuerst den grundlegenden Workflow funktionieren. Fügen Sie Optimierung hinzu, wenn die Rechnung kommt.

Zielkonflikte

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

Produktsysteme laufen normalerweise hybrid. Budgetieren Sie pro Sitzung, nutzen Sie Fallbacks basierend auf Qualität oder Latenz und cachen Sie, was Sie können. Die Komplexität ist real, aber auch die Einsparungen.

Verwandte Themen

Abonnieren

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