Kostnadsminimering för LLM-system: Vart pengarna verkligen går

Använd token där de verkligen spelar roll.

Sidinnehåll

Kostnader för stora språkmodeller (LLM) ökar linjärt med användningen. Ett system som bearbetar 10 000 förfrågningar per dag till $0,01 per förfrågan kostar $100 dagligen – vilket innebär $365 per år. I enterprise-skala blir det mer än $10 000.

Kostnadsoptimering handlar inte om att skära ner på kvalitet. Det handlar om att spendera token där de verkligen gör skillnad.

Varje token du slösar bort är en token som hade kunnat användas för ett bättre svar.

Strategier för kostnadsoptimering av LLM

Tokenbudgetering

Det enklaste sättet att kontrollera kostnader är att sätta gränser. Per session, per uppgift eller per dag.

Strategi 1: Budget per session

Budget per session är rakt igenom:

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

Strategi 2: Budget per uppgift

Budget per uppgift är mer användbara. Olika uppgifter kräver olika mängder 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

Strategi 3: Adaptiva budgetar

Adaptiva budgetar justeras baserat på vad som faktiskt händer. Om klassificeringsuppgifter konsekvent använder 80 token, sluta tilldela 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
            )

Det exponentiella rörliga medelvärdet (0,9-vikt) betyder att ny användning väger tyngre än historik. Justera vikten baserat på hur volatil din arbetsbelastning är.

API kontra lokal inferens

Lokal inferens är billigare i stor skala. När du går över tröskeln beror på din hårdvara och API-tariffer.

Modell API ($/M token) Lokal kostnad/timme Tröskel (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 timmar/dag
qwen3-32b $0,30 / $1,20 ~$0,20 ~2 timmar/dag
qwen3-8b $0,10 / $0,40 ~$0,05 ~1 timme/dag

Hårdvarumatematiken:

Hårdvara Startkostnad Månadslägre el Tröskel vs API
RTX 3090 (begagnad) $600 $15 ~4 månader
RTX 4090 $1 500 $20 ~6 månader
RTX 5080 $1 000 $18 ~5 månader
DGX Spark $2 000 $30 ~8 månader

Vid måttlig användning – en timme eller mer per dag – löser lokal inferens ut sig själv. Vid hög användning är besparingarna dramatiska. Införningen är dock initialkapitalet. En RTX 5080 kostar $1 000. En API-faktura kan du pausa. Hårdvara kan du inte.

Fallback-strategier

När din föredragna modell är för dyr eller för långsam, fall tillbaka på något billigare. Nyckeln är att veta när kvaliteten är “tillräckligt bra”.

Strategi 1: Kvalitetsbaserad fallback

Kvalitetsbaserad fallback provar modeller tills utdata uppfyller en tröskel:

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)

Problemet är själva utvärderingen. Hur mäter du kvalitet utan att anropa en annan modell? Vissa system använder en liten klassificerare. Andra använder heuristiska kontroller – längd, struktur, förekomst av nyckelord. Ingen av dessa är perfekta.

Strategi 2: Latensbaserad fallback

Latensbaserad fallback är enklare. Routa till den snabbaste modellen som uppfyller din tidsbudget:

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 är den mest undervärderade kostnadsoptimeringen. Identiska prompts händer oftare än man tänker – klassificeringsförfrågningar, FAQ-liknande frågor, upprepade verktygsanrop.

Strategi 1: Prompt-caching

Exakt prompt-caching är enkelt:

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

Strategi 2: Semantisk caching

Semantisk caching är mer användbar. Den fångar prompts som är olika men betyder samma sak:

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

Tröskeln spelar roll. 0,95 är aggressivt – endast mycket liknande prompts matchar. 0,85 är mer förlåtande men riskerar att returnera fel svar. Mät din missrate och justera.

Det är också värt det att cachera svar för vanliga frågor. Om användare upprepade gånger frågar “vad är vädret” eller “vad är klockan”, cachera mönstret, inte bara den exakta prompten:

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

Detta är inte sofistikerat, men det fungerar. Vanliga frågor är vanliga av en anledning.

När optimering hjälper

Optimering är viktig när du bearbetar stora volymer, kör blandade arbetsbelastningar eller betalar API-kostnader som staplas upp.

Det spelar ingen roll när du prototyperar, använder en enda modell eller bearbetar små volymer. Komplexiteten med budgetering, fallback och caching är inte värd det för ett system som gör 100 förfrågningar per dag.

Få den grundläggande flödet att fungera först. Lägg till optimering när räkningen kommer.

Kompromisser

Strategi Kostnad Kvalitet Komplexitet
Ingen optimering Högst Konsistent Lägst
Tokenbudgetering Medel Variabel Medel
Fallback-modeller Låg-Medel Variabel Medel
Caching Lägst Hög (för cache-hits) Medel
Hybrid Optimerad Optimerad Högst

Produktionssystem kör oftast hybrid. Budgetera per session, falla tillbaka på kvalitet eller latens, och cachera vad du kan. Komplexiteten är verklig, men det är besparingarna också.

Relaterat

Prenumerera

Få nya inlägg om system, infrastruktur och AI-ingenjörskonst.