Kostenoptimierung für LLM-Systeme: Wohin das Geld wirklich fließt

Verwenden Sie Token dort, wo sie wirklich wichtig sind.

Inhaltsverzeichnis

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

Kostenoptimierung bedeutet nicht, an falschen Stellen zu sparen. Sie bedeutet, 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 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 praktischer. Verschiedene Aufgaben benötigen unterschiedlich viel Kontext:

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

Strategie 3: Adaptive Budgets

Adaptive Budgets passen sich an die tatsächliche Nutzung an. Wenn Klassifizierungsaufgaben konsistent 80 Tokens verbrauchen, sollten Sie nicht länger 100 Tokens zuweisen:

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 jüngste Nutzung stärker gewichtet wird als die Vergangenheit. 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-Tarifen 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
Qwen2.5-32B $0,30 / $1,20 ~$0,20 ~2 Stunden/Tag
Qwen2.5-7B $0,10 / $0,40 ~$0,05 ~1 Stunde/Tag

Die Hardware-Rechnung:

Hardware Vorabkosten Monatlicher Strom 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 lokale Inferenz. Bei hoher Nutzung sind die Einsparungen dramatisch. Der Haken sind die initialen Kapitalkosten. 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, wechseln Sie auf eine günstigere Alternative. Der Schlüssel liegt darin zu wissen, wann die Qualität „gut genug“ ist.

Strategie 1: Qualitätsbasierter Fallback

Qualitätsbasierter Fallback versucht verschiedene Modelle, bis die Ausgabe einen bestimmten 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": "qwen2.5-32b", "cost": 0.001},
            {"model": "qwen2.5-7b", "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 messen Sie die Qualität, ohne ein weiteres Modell aufzurufen? Einige Systeme verwenden einen kleinen Klassifizierer. Andere nutzen heuristische Prüfungen — Länge, Struktur, Vorhandensein von Schlüsselwörtern. Keine dieser Methoden ist perfekt.

Strategie 2: Latenzbasierter Fallback

Latenzbasierter Fallback ist einfacher. Leiten Sie Anfragen 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": "qwen2.5-1.5b", "latency": 0.5},
            {"model": "qwen2.5-7b", "latency": 2.0},
            {"model": "qwen2.5-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 Form der Kostenoptimierung. Identische Prompts treten häufiger auf, als man denkt — Klassifizierungsanfragen, FAQ-ähnliche 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 ein, 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

Der Schwellenwert ist entscheidend. 0,95 ist aggressiv — nur sehr ähnliche Prompts werden gematcht. 0,85 ist nachsichtiger, birgt aber das Risiko, falsche Antworten zurückzugeben. Messen Sie Ihre Verfehlungsrate (Miss Rate) und passen Sie den Wert an.

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

Wann Optimierung hilft

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

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

Stellen Sie zunächst sicher, dass der grundlegende Ablauf funktioniert. Fügen Sie Optimierung hinzu, wenn die Rechnung kommt.

Trade-offs

Strategie Kosten Qualität Komplexität
Keine Optimierung Höchst Konsistent Niedrigst
Token-Budgetierung Moderat Variabel Mittel
Fallback-Modelle Niedrig-Mittel Variabel Mittel
Caching Niedrigst Hoch (bei Cache-Treffer) Mittel
Hybrid Optimal Optimal Höchst

Produktionssysteme laufen meist hybrid. Budgetieren Sie pro Sitzung, nutzen Sie Fallback basierend auf Qualität oder Latenz und cachen Sie, was möglich ist. Die Komplexität ist real, aber auch die Einsparungen.

Verwandte Themen

Abonnieren

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