Home Tecnología Por qué su factura de LLM se está disparando y cómo el...

Por qué su factura de LLM se está disparando y cómo el almacenamiento en caché semántico puede reducirla en un 73%

22
0

Nuestra factura de LLM API estaba creciendo un 30% mes tras mes. El tráfico aumentaba, pero no tan rápido. Cuando analicé nuestros registros de consultas, encontré el verdadero problema: los usuarios hacen las mismas preguntas de diferentes maneras.

“¿Cuál es su política de devolución?”, “¿Cómo devuelvo algo?” y “¿Puedo obtener un reembolso?” Todos llegamos a nuestro LLM por separado, generando respuestas casi idénticas y cada una incurriendo en costos completos de API.

El almacenamiento en caché de coincidencia exacta, la primera solución obvia, capturó sólo el 18% de estas llamadas redundantes. La misma pregunta semántica, formulada de manera diferente, pasó por alto el caché por completo.

Entonces, implementé el almacenamiento en caché semántico en función del significado de las consultas, no de cómo están redactadas. Después de implementarlo, nuestra tasa de aciertos de caché aumentó al 67 %, lo que redujo los costos de API de LLM en un 73 %. Pero llegar allí requiere resolver problemas que las implementaciones ingenuas pasan por alto.

Por qué el almacenamiento en caché de coincidencias exactas se queda corto

El almacenamiento en caché tradicional utiliza texto de consulta como clave de caché. Esto funciona cuando las consultas son idénticas:

# Almacenamiento en caché de coincidencia exacta

clave_caché = hash(texto_consulta)

si cache_key en caché:

devolver caché[cache_key]

Pero los usuarios no formulan las preguntas de manera idéntica. Mi análisis de 100.000 consultas de producción encontró:

  • Sólo el 18% eran duplicados exactos de consultas anteriores

  • 47% fueron semánticamente similares a consultas anteriores (misma intención, redacción diferente)

  • 35% fueron consultas realmente novedosas

Ese 47% representó enormes ahorros de costos que nos estábamos perdiendo. Cada consulta semánticamente related desencadenó una llamada LLM completa, generando una respuesta casi idéntica a una que ya habíamos calculado.

Arquitectura de almacenamiento en caché semántica

El almacenamiento en caché semántico reemplaza las claves basadas en texto con una búsqueda de similitudes basada en incrustaciones:

clase SemanticCache:

def __init__(self, embedding_model, similarity_threshold=0.92):

self.embedding_model = modelo_incrustación

self.threshold = similitud_umbral

self.vector_store = VectorStore() #FAISS, Piña, and many others.

self.response_store = TiendaRespuesta() # Redis, DynamoDB, and many others.

def get(self, consulta: str) -> Opcional[str]:

“””Devolver respuesta almacenada en caché si existe una consulta semánticamente related.”””

query_embedding = self.embedding_model.encode(consulta)

# Encuentra la consulta en caché más related

coincidencias = self.vector_store.search(query_embedding, top_k=1)

si coincide y coincide[0].similitud >= self.threshold:

cache_id = coincidencias[0].identificación

devolver self.response_store.get(cache_id)

regresar Ninguno

def set(self, consulta: str, respuesta: str):

“””Par de consulta-respuesta en caché.”””

query_embedding = self.embedding_model.encode(consulta)

cache_id = generar_id()

self.vector_store.add(cache_id, query_embedding)

self.response_store.set(cache_id, {

‘consulta’: consulta,

‘respuesta’: respuesta,

‘marca de tiempo’: fecha y hora.utcnow()

})

La thought clave: en lugar de aplicar hash al texto de la consulta, incrusto consultas en el espacio vectorial y encuentro consultas almacenadas en caché dentro de un umbral de similitud.

El problema del umbral

El umbral de similitud es el parámetro crítico. Si lo configuras demasiado alto, perderás accesos de caché válidos. Si lo estableces demasiado bajo, obtendrás respuestas incorrectas.

Nuestro umbral inicial de 0,85 parecía razonable; 85% related debería ser “la misma pregunta”, ¿verdad?

Equivocado. En 0,85, obtuvimos aciertos de caché como:

Son preguntas diferentes con respuestas diferentes. Devolver la respuesta almacenada en caché sería incorrecto.

Descubrí que los umbrales óptimos varían según el tipo de consulta:

Tipo de consulta

Umbral óptimo

Razón basic

Preguntas estilo preguntas frecuentes

0,94

Se necesita alta precisión; Las respuestas incorrectas dañan la confianza.

Búsquedas de productos

0,88

Más tolerancia para los casi partidos

Consultas de soporte

0,92

Equilibrio entre cobertura y precisión

Consultas transaccionales

0,97

Tolerancia muy baja a los errores.

Implementé umbrales específicos del tipo de consulta:

clase AdaptiveSemanticCache:

def __init__(yo):

auto.umbrales = {

‘preguntas frecuentes’: 0,94,

‘búsqueda’: 0,88,

‘soporte’: 0,92,

‘transaccional’: 0,97,

‘predeterminado’: 0,92

}

self.query_classifier=QueryClassifier()

def get_threshold(self, consulta: str) -> flotante:

tipo_consulta = self.query_classifier.classify(consulta)

devolver self.thresholds.get(query_type, self.thresholds[‘default’])

def get(self, consulta: str) -> Opcional[str]:

umbral = self.get_threshold (consulta)

query_embedding = self.embedding_model.encode(consulta)

coincidencias = self.vector_store.search(query_embedding, top_k=1)

si coincide y coincide[0].similitud >= umbral:

devolver self.response_store.get(coincide[0].identificación)

regresar Ninguno

Metodología de ajuste de umbrales

No podía ajustar los umbrales a ciegas. Necesitaba información básica sobre qué pares de consultas eran en realidad “iguales”.

Nuestra metodología:

Paso 1: Pares de consultas de ejemplo. Probé 5000 pares de consultas en varios niveles de similitud (0,80-0,99).

Paso 2: Etiquetado humano. Los anotadores etiquetaron cada par como “la misma intención” o “intención diferente”. Utilicé tres anotadores por par y obtuve una mayoría de votos.

Paso 3: Calcular curvas de precisión/recuperación. Para cada umbral, calculamos:

  • Precisión: de los aciertos de caché, ¿qué fracción tuvo la misma intención?

  • Recuerde: de pares con la misma intención, ¿qué fracción almacenamos en caché?

def Compute_precision_recall (pares, etiquetas, umbral):

“””Calcular la precisión y recuperarla en un umbral de similitud dado.”””

predicciones = [1 if pair.similarity >= threshold else 0 for pair in pairs]

true_positives = suma(1 para p, l en zip(predicciones, etiquetas) si p == 1 y l == 1)

false_positives = suma(1 para p, l en zip(predicciones, etiquetas) si p == 1 y l == 0)

false_negatives = suma(1 para p, l en zip(predicciones, etiquetas) si p == 0 y l == 1)

precisión = verdaderos_positivos / (verdaderos_positivos + falsos_positivos) si (verdaderos_positivos + falsos_positivos) > 0 más 0

recordar = verdaderos_positivos / (verdaderos_positivos + falsos_negativos) si (verdaderos_positivos + falsos_negativos) > 0 más 0

precisión de retorno, recuperación

Paso 4: seleccione el umbral según el costo de los errores. Para las consultas frecuentes en las que las respuestas incorrectas dañan la confianza, optimicé para obtener precisión (el umbral de 0,94 dio una precisión del 98%). Para consultas de búsqueda en las que perder un hit de caché solo cuesta dinero, optimicé para la recuperación (umbral de 0,88).

Sobrecarga de latencia

El almacenamiento en caché semántico agrega latencia: debe incrustar la consulta y buscar en el almacén de vectores antes de saber si debe llamar al LLM.

Nuestras medidas:

Operación

Latencia (p50)

Latencia (p99)

Incrustación de consultas

12 ms

28ms

Búsqueda de vectores

8ms

19ms

Búsqueda complete de caché

20 ms

47ms

Llamada API de LLM

850 ms

2400ms

La sobrecarga de 20 ms es insignificante en comparación con la llamada LLM de 850 ms que evitamos en los aciertos de caché. Incluso en p99, la sobrecarga de 47 ms es aceptable.

Sin embargo, los errores de caché ahora tardan 20 ms más que antes (incrustación + búsqueda + llamada LLM). Con nuestra tasa de acierto del 67%, las matemáticas funcionan favorablemente:

Mejora de la latencia neta del 65% junto con la reducción de costos.

invalidación de caché

Las respuestas almacenadas en caché quedan obsoletas. La información del producto cambia, las políticas se actualizan y la respuesta correcta de ayer se convierte en la respuesta incorrecta de hoy.

Implementé tres estrategias de invalidación:

  1. TTL basado en tiempo

Caducidad easy según el tipo de contenido:

TTL_BY_CONTENT_TYPE = {

‘precios’: timedelta(horas=4), # Cambia con frecuencia

‘política’: timedelta(días=7), # Cambia raramente

‘product_info’: timedelta(días=1), # Actualización diaria

‘general_faq’: timedelta(días=14), # Muy estable

}

  1. Invalidación basada en eventos

Cuando los datos subyacentes cambian, invalide las entradas de caché relacionadas:

clase CacheInvalidator:

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

“””Invalidar entradas de caché relacionadas con contenido actualizado.”””

# Buscar consultas en caché que hagan referencia a este contenido

consultas_afectadas = self.find_queries_referencing(content_id)

para query_id en consultas_afectadas:

self.cache.invalidate(query_id)

self.log_invalidation(content_id, len(consultas_afectadas))

  1. Detección de estancamiento

Para las respuestas que podrían quedar obsoletas sin eventos explícitos, implementé controles de actualización periódicos:

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

“””Verificar que la respuesta almacenada en caché aún sea válida.”””

# Vuelva a ejecutar la consulta con los datos actuales

respuesta_fresca = self.generate_response(respuesta_caché[‘query’])

# Comparar similitud semántica de respuestas

cached_embedding = self.embed(cached_response[‘response’])

Fresh_embedding = self.embed(fresh_response)

similitud = coseno_similaridad(cached_embedding, Fresh_embedding)

# Si las respuestas divergieron significativamente, invalidar

si similitud < 0,90:

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

devolver falso

devolver verdadero

Realizamos comprobaciones de actualización en una muestra de entradas almacenadas en caché diariamente, detectando la obsolescencia que TTL y la invalidación basada en eventos pasan por alto.

Resultados de producción

Después de tres meses en producción:

Métrico

Antes

Después

Cambiar

Tasa de aciertos de caché

18%

67%

+272%

Costos de API de LLM

$47K/mes

$12.7K/mes

-73%

Latencia media

850 ms

300 ms

-65%

Tasa de falsos positivos

N / A

0,8%

Quejas de clientes (respuestas incorrectas)

Base

+0,3%

Aumento mínimo

La tasa de falsos positivos del 0,8% (consultas en las que devolvimos una respuesta almacenada en caché que period semánticamente incorrecta) estuvo dentro de límites aceptables. Estos casos ocurrieron principalmente en los límites de nuestro umbral, donde la similitud estaba justo por encima del límite pero la intención difería ligeramente.

Escollos a evitar

No utilice un único umbral world. Los diferentes tipos de consultas tienen diferente tolerancia a los errores. Ajuste los umbrales por categoría.

No omita el paso de incrustación en los aciertos de caché. Es posible que tenga la tentación de omitir la sobrecarga de incrustación al devolver respuestas almacenadas en caché, pero necesita la incrustación para la generación de claves de caché. Los gastos generales son inevitables.

No olvides la invalidación. El almacenamiento en caché semántico sin una estrategia de invalidación genera respuestas obsoletas que erosionan la confianza del usuario. Invalidación de compilación desde el primer día.

No guardes todo en caché. Algunas consultas no deben almacenarse en caché: respuestas personalizadas, información urgente, confirmaciones de transacciones. Construya reglas de exclusión.

def debería_cache(self, consulta: str, respuesta: str) -> bool:

“””Decide si la respuesta debe almacenarse en caché.””

# No almacene en caché las respuestas personalizadas

si self.contains_personal_info(respuesta):

devolver falso

# No almacene en caché información urgente

si self.is_time_SENSITIVE (consulta):

devolver falso

# No almacene en caché las confirmaciones transaccionales

si self.is_transactional(consulta):

devolver falso

devolver verdadero

Conclusiones clave

El almacenamiento en caché semántico es un patrón práctico para el management de costos de LLM que captura errores de almacenamiento en caché de coincidencia exacta de redundancia. Los desafíos clave son el ajuste de umbrales (use umbrales específicos del tipo de consulta basados ​​en análisis de precisión/recuperación) y la invalidación de caché (mix TTL, detección basada en eventos y obsolescencia).

Con una reducción de costos del 73 %, esta fue nuestra optimización de mayor retorno de la inversión para sistemas LLM de producción. La complejidad de la implementación es moderada, pero el ajuste del umbral requiere una atención cuidadosa para evitar la degradación de la calidad.

Sreenivasa Reddy Hulebeedu Reddy es un ingeniero de software program líder.

¡Bienvenido a la comunidad VentureBeat!

Nuestro programa de publicaciones invitadas es donde los expertos técnicos comparten conocimientos y brindan análisis profundos neutrales y no adquiridos sobre inteligencia synthetic, infraestructura de datos, ciberseguridad y otras tecnologías de vanguardia que dan forma al futuro de las empresas.

Leer más de nuestro programa de publicaciones de invitados y consulte nuestro pautas ¡Si estás interesado en contribuir con un artículo propio!

avotas