Kostenoptimalisatie voor LLM-systemen: waar het geld echt naartoe gaat

Besteed tokens waar het echt toe doet.

Inhoud

De kosten van LLM’s schalen lineair met het gebruik. Een systeem dat 10.000 verzoeken per dag verwerkt tegen $0,01 per verzoek kost dagelijks $100 — jaarlijks $365. Op enterprise-schaal is dat meer dan $10.000.

Kostenoptimalisatie gaat niet om hoekjes afsnijden. Het gaat erom tokens te besteden waar het echt toe doet.

Elke token die je verspilt, is een token die je had kunnen besteden aan een beter antwoord.

LLM kostenoptimalisatiestrategieën

Tokenbudgettering

De eenvoudigste manier om kosten te beheersen, is het instellen van limieten. Per sessie, per taak of per dag.

Strategie 1: Budgetten per sessie

Budgetten per sessie zijn eenvoudig:

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: Budgetten per taak

Budgetten per taak zijn praktischer. Verschillende taken vereisen verschillende hoeveelheden context:

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: Adaptieve budgetten

Adaptieve budgetten passen zich aan op basis van wat daadwerkelijk gebeurt. Als classificatietaken consistent 80 tokens gebruiken, stop dan met het toewijzen van 100:

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
            )

De exponentiële bewegende gemiddelde (met een gewicht van 0,9) betekent dat recent gebruik zwaarder weegt dan historische gegevens. Pas het gewicht aan op basis van hoe volatil uw werklasten zijn.

API versus lokale inferentie

Lokale inferentie is goedkoper op schaal. Het break-even punt hangt af van uw hardware en API-tarieven.

Model API ($/M tokens) Lokale kosten/uur Break-even
GPT-4o $2,50 / $10,00 N.V.T.
Claude Sonnet 4 $3,00 / $15,00 N.V.T.
Qwen2.5-72B $0,50 / $2,00 ~$0,50 ~4 uur/dag
qwen3-32b $0,30 / $1,20 ~$0,20 ~2 uur/dag
qwen3-8b $0,10 / $0,40 ~$0,05 ~1 uur/dag

De hardware-rekenkamer:

Hardware Vooruitbetaling Maandelijkse stroomkosten Break-even vs API
RTX 3090 (gebruikt) $600 $15 ~4 maanden
RTX 4090 $1.500 $20 ~6 maanden
RTX 5080 $1.000 $18 ~5 maanden
DGX Spark $2.000 $30 ~8 maanden

Bij matig gebruik — een uur of meer per dag — verdient lokale inferentie zichzelf terug. Bij hoog gebruik zijn de besparingen aanzienlijk. Het nadeel is de initiële kapitaalinvestering. Een RTX 5080 kost $1.000. Een API-rekening kunt u pauzeren. Hardware niet.

Fallback-strategieën

Wanneer uw voorkeurmodel te duur of te traag is, valt u terug op een goedkoper alternatief. De sleutel is weten wanneer de kwaliteit “goed genoeg” is.

Strategie 1: Kwaliteitsgebaseerde fallback

Kwaliteitsgebaseerde fallback probeert modellen totdat de uitvoer een bepaalde drempel bereikt:

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)

Het probleem is de evaluatie zelf. Hoe meet u kwaliteit zonder een ander model aan te roepen? Sommige systemen gebruiken een kleine classifier. Anderen gebruiken heuristische controles — lengte, structuur, aanwezigheid van trefwoorden. Geen van deze is perfect.

Strategie 2: Latentiegebaseerde fallback

Latentiegebaseerde fallback is eenvoudiger. Routeer naar het snelste model dat binnen uw tijdslimiet past:

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 is de meest onderschatte kostenoptimalisatie. Identieke prompts komen vaker voor dan u denkt — classificatieverzoeken, FAQ-achtige queries, herhaalde tool-aanroepen.

Strategie 1: Promptcaching

Exacte promptcaching is eenvoudig:

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: Semantische caching

Semantische caching is praktischer. Het vangt prompts die verschillend zijn, maar hetzelfde betekenen:

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

De drempelwaarde is belangrijk. 0,95 is agressief — alleen zeer vergelijkbare prompts komen overeen. 0,85 is toleranter, maar loopt het risico om verkeerde antwoorden te retourneren. Meet uw miss-rate en pas deze aan.

Reactiecaching voor veelvoorkomende queries is ook de moeite waard. Als gebruikers herhaaldelijk vragen “wat is het weer” of “hoe laat is het”, cacheer dan het patroon, niet alleen de exacte 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

Dit is niet verfijnd, maar het werkt. Veelvoorkomende queries zijn veelvoorkomend om een reden.

Wanneer optimalisatie helpt

Optimalisatie is van belang wanneer u hoge volumes verwerkt, gemengde werklasten uitvoert of API-kosten betaalt die oplopen.

Het doet er niet toe wanneer u prototype, één model gebruikt of lage volumes verwerkt. De complexiteit van budgettering, fallback en caching weegt niet op tegen de voordelen voor een systeem dat slechts 100 verzoeken per dag doet.

Krijg eerst de basisflow werkend. Voeg optimalisatie toe wanneer de rekening arriveert.

Afwegingen

Strategie Kosten Kwaliteit Complexiteit
Geen optimalisatie Hoogst Consistent Laagst
Tokenbudgettering Matig Variabel Middel
Fallback-modellen Laag-Matig Variabel Middel
Caching Laagst Hoog (voor cache-hits) Middel
Hybride Optimaliseerd Optimaliseerd Hoogst

Productiesystemen draaien meestal hybride. Budget per sessie, fallback op kwaliteit of latentie, en cache wat u kunt. De complexiteit is werkelijk, maar de besparingen ook.

Gerelateerd

Abonneren

Ontvang nieuwe berichten over systemen, infrastructuur en AI-engineering.