Home Nieuws Waarom uw LLM-rekening explodeert – en hoe semantische caching deze met 73%...

Waarom uw LLM-rekening explodeert – en hoe semantische caching deze met 73% kan verminderen

10
0
Waarom uw LLM-rekening explodeert – en hoe semantische caching deze met 73% kan verminderen

Onze LLM API-factuur groeide maand op maand met 30%. Het verkeer nam toe, maar niet zo snel. Toen ik onze zoekopdrachtlogboeken analyseerde, ontdekte ik het echte probleem: gebruikers stellen dezelfde vragen op verschillende manieren.

“Wat is uw retourbeleid?”, “Hoe kan ik iets retourneren?” en “Kan ik mijn geld terugkrijgen?” ze kwamen allemaal afzonderlijk op onze LLM terecht en genereerden vrijwel identieke reacties, waarbij elk de volledige API-kosten met zich meebracht.

Exact-match caching, de voor de hand liggende eerste oplossing, ving slechts 18% van deze redundante oproepen op. Dezelfde semantische vraag, anders geformuleerd, omzeilde de cache volledig.

Daarom heb ik semantische caching geïmplementeerd op basis van wat zoekopdrachten betekenen, niet hoe ze zijn geformuleerd. Na de implementatie steeg ons cachetrefferpercentage naar 67%, waardoor de LLM API-kosten met 73% daalden. Maar om daar te komen, zijn problemen nodig die naïeve implementaties over het hoofd zien.

Waarom exacte match-caching tekortschiet

Bij traditionele caching wordt querytekst gebruikt als cachesleutel. Dit werkt als zoekopdrachten identiek zijn:

# Caching met exacte match

cache_key = hash(querytekst)

als cache_key in cache:

retour cache(cache_key)

Maar gebruikers formuleren vragen niet op dezelfde manier. Mijn analyse van 100.000 productiequery’s vond het volgende:

  • Slechts 18% waren exacte duplicaten van eerdere zoekopdrachten

  • 47% semantisch vergelijkbaar met eerdere zoekopdrachten (dezelfde intentie, andere formulering)

  • 35% waren echt nieuwe vragen

De 47% vertegenwoordigde enorme kostenbesparingen die we misten. Elke semantisch vergelijkbare zoekopdracht veroorzaakte een volledige LLM-oproep, waardoor een antwoord ontstond dat vrijwel identiek was aan het antwoord dat we al hadden berekend.

Semantische caching-architectuur

Semantische caching vervangt op tekst gebaseerde sleutels door op insluitingen gebaseerde overeenkomsten op te zoeken:

klasse SemantischeCache:

def __init__(zelf, genest model, gelijkenisdrempel = 0,92):

self.embedding_model = embeddingModel

self.threshold = gelijkheidsdrempel

self.vector_store = VectorWinkel() # FAISS, Dennenappel, enz.

self.response_store = ResponseStore() # Redis, DynamoDB, enz.

def get(self, query: str) -> Optioneel(str):

“””Retourneer het in de cache opgeslagen antwoord als een semantisch vergelijkbare zoekopdracht wordt gevonden.”””

query_embedding = self.embedding_model.encode(query)

# Vind de meest vergelijkbare in de cache opgeslagen zoekopdracht

komt overeen met = self.vector_store.search(query_embedding, top_k=1)

if komt overeen met en komt overeen met(0).similarity >= self.threshold:

cache_id = komt overeen met(0).id

return self.response_store.get(cache_id)

retour Geen

def set(zelf, vraag: str, antwoord: str):

“””Cacheverzoek-antwoordpaar.”””

query_embedding = self.embedding_model.encode(query)

cache_id = genereer_id()

self.vector_store.add(cache_id, query_embedding)

self.response_store.set(cache_id, {

‘vraag’: vraag,

‘antwoord’: antwoord,

’tijdstempel’: datetime.utcnow()

})

Het belangrijkste inzicht: in plaats van de tekst van de zoekopdracht te hashen, sluit ik zoekopdrachten in de vectorruimte in en vind ik in de cache opgeslagen zoekopdrachten binnen een gelijkenisdrempel.

Het drempelprobleem

De gelijkenisdrempel is de kritische parameter. Als u deze te hoog instelt, mist u geldige cachehits. Stel het te laag in en u krijgt onjuiste antwoorden.

Onze oorspronkelijke drempel van 0,85 leek redelijk; 85% vergelijkbaar zou “dezelfde vraag” moeten zijn, toch?

Fout. Bij 0,85 kregen we cachehits zoals:

Dit zijn verschillende vragen met verschillende antwoorden. Het retourneren van het in de cache opgeslagen antwoord zou onjuist zijn.

Ik ontdekte dat optimale drempels variëren per querytype:

Zoektype

Optimale drempel

Rechtvaardiging

Vragen in FAQ-stijl

0,94

Hoge precisie vereist; Verkeerde antwoorden schaden het vertrouwen

Zoeken naar producten

0,88

Meer tolerantie voor nauwe wedstrijden

Ondersteuningsvragen

0,92

Balans tussen dekking en nauwkeurigheid

Transactionele vragen

0,97

Zeer lage tolerantie voor fouten

Ik heb specifieke drempels voor het querytype geïmplementeerd:

klasse AdaptiveSemanticCache:

def __init__(zelf):

zelf.drempels = {

‘veelgestelde vragen’: 0.94,

‘zoeken’: 0,88,

‘ondersteuning’: 0,92,

’transactioneel’: 0,97,

‘standaard’: 0,92

}

self.query_classifier = QueryClassifier()

def get_threshold(self, query: str) -> float:

query_type = self.query_classifier.classify(query)

return self.thresholds.get(query_type, self.thresholds(‘standaard’))

def get(self, query: str) -> Optioneel(str):

drempel = self.get_threshold(query)

query_embedding = self.embedding_model.encode(query)

komt overeen met = self.vector_store.search(query_embedding, top_k=1)

if komt overeen met en match(0).similarity >= drempelwaarde:

return self.response_store.get(matches(0).id)

retour Geen

Drempelafstemmingsmethode

Ik kon niet blindelings drempels stellen. Ik had grondwaarheid nodig over welke zoekparen eigenlijk “hetzelfde” waren.

Onze werkwijze:

Stap 1: Voorbeeldquerypaar. Ik heb 5000 zoekparen op verschillende gelijkenisniveaus (0,80-0,99) onderzocht.

Stap 2: Menselijke etikettering. Annotators bestempelden elk paar als “dezelfde bedoeling” of “tweede bedoeling.” Ik gebruikte drie annotators per paar en kreeg een meerderheid van stemmen.

Stap 3: Bereken precisie/recall-curven. Voor elke drempel hebben we berekend:

  • Precisie: welk deel van de cachehits had dezelfde bedoeling?

  • Onthoud: welk deel van de paren hebben we met dezelfde intentie gevonden?

def compute_precision_recall(paar, labels, drempelwaarde):

“””Bereken precisie en herinnering bij een gegeven gelijkenisdrempel.”””

voorspellingen = (1 als paar.similariteit >= drempel anders 0 voor paar in paar)

true_positives = som(1 voor p, li zip(voorspellingen, labels) als p == 1 en l == 1)

false_positives = sum(1 voor p, li zip(voorspellingen, labels) als p == 1 en l == 0)

false_negatives = sum(1 voor p, li zip(voorspellingen, labels) als p == 0 en l == 1)

precisie = waar_positieven / (waar_positieven + onwaar_positieven) if (waar_positieven + onwaar_positieven) > 0 anders 0

herinneren = waar_positieven / (waar_positieven + onwaar_negatieven) if (waar_positieven + onwaar_negatieven) > 0 anders 0

terugkeerprecisie, terugroepen

Stap 4: Kies een drempel op basis van de faalkosten. Voor veelgestelde vragen waarbij verkeerde antwoorden het vertrouwen schaden, heb ik geoptimaliseerd voor precisie (drempel van 0,94 gaf 98% nauwkeurigheid). Voor zoekopdrachten waarbij het missen van een cachehit alleen maar geld kost, heb ik geoptimaliseerd voor terugroepen (drempelwaarde van 0,88).

Latency-overhead

Semantische caching voegt latentie toe: u moet de zoekopdracht nesten en de vectoropslag doorzoeken voordat u weet of u LLM moet aanroepen.

Onze doelen:

Operatie

Latentie (p. 50)

Latentie (pag. 99)

Query-nesten

12 ms

28 ms

Vector zoeken

8 ms

19 ms

Totaal cache-opzoeken

20 ms

47 ms

LLM API-oproepen

850 ms

2400 ms

De overhead van 20 ms is verwaarloosbaar vergeleken met de LLM-oproep van 850 ms die we vermijden bij cachehits. Zelfs op p99 is een overhead van 47 ms acceptabel.

Maar cache-missers duren nu 20 ms langer dan voorheen (insluiten + zoeken + LLM-oproep). Met ons hitpercentage van 67% pakt de berekening positief uit:

Netto latentieverbetering van 65% naast de kostenverlaging.

Cache-invalidatie

Gecachte antwoorden raken verouderd. Wijzigingen in productinformatie, beleidsupdates en de juiste antwoorden van gisteren worden de onjuiste antwoorden van vandaag.

Ik heb drie valideringsstrategieën geïmplementeerd:

  1. Op tijd gebaseerde TTL

Eenvoudige vervaldatum op basis van inhoudstype:

TTL_BY_CONTENT_TYPE = {

‘prijzen’: hoursdelta(uren=4), # Verandert vaak

‘beleid’: timedelta(dagen=7), # Veranderingen zelden

‘product_info’: timedelta(dagen=1), # Dagelijkse update

‘general_faq’: timedelta(dagen=14), # Zeer stabiel

}

  1. Op gebeurtenissen gebaseerde ongeldigverklaring

Wanneer onderliggende gegevens veranderen, maakt u gerelateerde cachegegevens ongeldig:

klasse CacheInvalidator:

def on_content_update(self, content_id: str, content_type: str):

“””Ongeldige cache-invoer gerelateerd aan bijgewerkte inhoud.”””

# Zoek in de cache opgeslagen zoekopdrachten die naar deze inhoud verwijzen

beïnvloed_queries = self.find_queries_referencing(content_id)

voor query_id in beïnvloede_queries:

self.cache.invalidate(query_id)

self.log_invalidation(content_id, len(beïnvloede_queries))

  1. Detectie van veroudering

Voor reacties die zonder expliciete gebeurtenissen verouderd kunnen raken, heb ik periodieke versheidscontroles geïmplementeerd:

def check_freshness(self, cached_response: dict) -> bool:

“””Controleer of het in de cache opgeslagen antwoord nog steeds geldig is.”””

# Voer de query opnieuw uit op basis van de huidige gegevens

fresh_response = self.generate_response(cached_response(‘query’))

# Vergelijk semantische gelijkenis tussen antwoorden

cached_embedding = self.embed(cached_response(‘reactie’))

fresh_embedding = zelf.embed(fresh_response)

gelijkenis = cosinus_similarity(cached_embedding, verse_embedding)

# Als de antwoorden aanzienlijk uiteenlopen, maak ze dan ongeldig

als gelijkenis < 0,90:

self.cache.invalidate(cached_response(‘id’))

retour vals

terugkeer Waar

We voeren dagelijks controles uit op de versheid van een aantal in de cache opgeslagen records en ontdekken verouderde gegevens die TTL en op gebeurtenissen gebaseerde invalidatie missen.

Productieresultaten

Na drie maanden in productie:

Metrisch

Voor

Na

Wijziging

Cachehitpercentage

18%

67%

+272%

LLM API-kosten

$ 47.000/maand

$ 12,7K/maand

-73%

Gemiddelde latentie

850 ms

300 ms

-65%

Vals-positief percentage

BEREIK

0,8%

Klachten van klanten (foute antwoorden)

Basislijn

+0,3%

Minimale stijging

Het fout-positieve percentage van 0,8% (query’s waarbij we een in de cache opgeslagen antwoord retourneerden dat semantisch onjuist was) lag binnen aanvaardbare grenzen. Deze gevallen deden zich voornamelijk voor aan de grenzen van onze drempel, waar de gelijkenis net boven de grens lag, maar de bedoeling enigszins verschilde.

Valkuilen om te vermijden

Gebruik geen enkele globale drempel. Verschillende querytypen hebben verschillende toleranties voor fouten. Stem drempels per categorie af.

Sla de neststap bij cachehits niet over. U kunt in de verleiding komen om de nestoverhead over te slaan bij het retourneren van in de cache opgeslagen antwoorden, maar u hebt het nesten nodig om cachesleutels te genereren. Overhead is onvermijdelijk.

Vergeet de ongeldigverklaring niet. Semantische caching zonder een invalidatiestrategie leidt tot verouderde reacties die het vertrouwen van de gebruiker ondermijnen. Bouw vanaf dag één invaliditeit op.

Cache niet alles. Sommige zoekopdrachten mogen niet in de cache worden opgeslagen: persoonlijke antwoorden, tijdgevoelige informatie, transactiebevestigingen. Stel uitsluitingsregels op.

def Should_cache(self, query: str, respons: str) -> bool:

“””Bepaal of u het antwoord in de cache wilt opslaan.””

# Bewaar persoonlijke antwoorden niet in de cache

als self.contains_personal_info(antwoord):

retour vals

# Bewaar geen tijdgevoelige informatie in de cache

if self.is_time_sensitive(query):

retour vals

# Bewaar transactiebevestigingen niet in de cache

als self.is_transactional(query):

retour vals

terugkeer Waar

Belangrijkste afhaalrestaurants

Semantische caching is een praktisch patroon voor LLM-kostenbeheersing dat redundantie en exacte match-caching-missers opvangt. De belangrijkste uitdagingen zijn het afstemmen van drempels (gebruik querytypespecifieke drempels op basis van precisie-/herinneringsanalyse) en cache-invalidatie (combineer TTL, op gebeurtenissen gebaseerde detectie en detectie van veroudering).

Met een kostenreductie van 73% was dit onze hoogste ROI-optimalisatie voor productie-LLM-systemen. De complexiteit van de implementatie is matig, maar de aanpassing van de drempel vereist zorgvuldige aandacht om kwaliteitsverlies te voorkomen.

Sreenivasa Reddy Hulebeedu Reddy is een toonaangevende software-ingenieur.

Welkom bij de VentureBeat-community!

In ons gastpostprogramma delen technische experts inzichten en bieden ze neutrale, onbevooroordeelde diepgaande inzichten in AI, data-infrastructuur, cyberbeveiliging en andere geavanceerde technologieën die de toekomst van het bedrijfsleven vormgeven.

Lees meer uit ons gastpostprogramma – en bekijk ons richtlijnen als u geïnteresseerd bent om uw eigen artikel bij te dragen!

Nieuwsbron

LAAT EEN REACTIE ACHTER

Vul alstublieft uw commentaar in!
Vul hier uw naam in