Deje de desperdiciar tokens LLM. Agrupar sus entradas puede generar… | por Tobias Schnabel | agosto, 2024

Agrupar sus insumos puede generar ahorros sustanciales sin comprometer el rendimiento.

Foto por Orgalux en Dejar de salpicar

Si utiliza LLM para anotar o procesar conjuntos de datos más grandes, es probable que ni siquiera se dé cuenta de que está desperdiciando una gran cantidad de tokens de entrada. A medida que llama repetidamente a un LLM para procesar fragmentos de texto o documentos completos, las instrucciones de su tarea y los ejemplos estáticos de pocos intentos se repiten para cada Ejemplo de entrada. Así como apilar los platos de forma ordenada ahorra espacio, agrupar los ingredientes puede generar ahorros sustanciales.

Supongamos que desea etiquetar un corpus de documentos más pequeño de 1000 documentos de una sola página con instrucciones y ejemplos breves de aproximadamente media página de extensión. Anotar cada documento por separado le costaría aproximadamente 1 millón de tokens de entrada. Sin embargo, si anotara diez documentos en la misma llamada, ahorraría aproximadamente 300K tokens de entrada (o 30%) porque no tenemos que repetir las instrucciones. Como mostraremos en el ejemplo a continuación, esto puede suceder a menudo con una pérdida mínima de rendimiento (o incluso una ganancia de rendimiento), especialmente cuando optimizas tu mensaje al mismo tiempo.

A continuación, he graficado los ahorros asumiendo que la longitud promedio de nuestro documento es D tokens y nuestras instrucciones y ejemplos de pocos disparos tienen R*D tokens. El escenario de ejemplo del párrafo anterior donde las instrucciones tienen la mitad de la longitud del documento (a = 0,5) aparece en azul a continuación. Para instrucciones compartidas más extensas, nuestros ahorros pueden ser incluso mayores:

Las principales conclusiones son:

  • Incluso con instrucciones relativamente cortas (línea azul), el procesamiento en minibatch tiene valor
  • No es necesario utilizar tamaños de minibatch realmente grandes. La mayoría de los ahorros se pueden obtener incluso con tamaños de minibatch moderados (B ≤ 10).

Pasemos a la práctica con una tarea en la que queremos clasificar fragmentos de texto para analizarlos más a fondo. Usaremos una tarea divertida de la Punto de referencia de Natural Instructions donde necesitamos anotar oraciones en debates con una de cuatro categorías (valor, hecho, testimonio o política).

Mirando un ejemplo, vemos que obtenemos el tema actual para el contexto y luego necesitamos categorizar la oración en cuestión.

{
"input": {
"topic": "the fight for justice,equality,peaceand love is futile",
"sentence": "What matters is what I am personally doing to ensure that I am filling the cup!"
},
"output": "Value"
}

Una pregunta que aún no hemos respondido:

¿Cómo elegimos el tamaño correcto de minibatch?

Trabajo previo Se ha demostrado que el tamaño óptimo de minibatch depende tanto de la tarea como del modelo. Básicamente, tenemos dos opciones:

  1. Elegimos un tamaño de minibatch razonable, digamos 5, y esperamos no ver ninguna caída.
  2. Optimizamos el tamaño del minibatch junto con otras opciones, por ejemplo, la cantidad de ejemplos de pocas tomas.

Como habrás adivinado, aquí optaremos por la opción 2. Para realizar nuestros experimentos, utilizaremos SAMOun marco para la convocatoria de LLM y la optimización rápida.

Los avisos se codifican en SAMMO como programas de avisos (que son simplemente clases de Python anidadas que se llamarán con datos de entrada). Estructuraremos nuestra tarea en tres secciones y formatearemos nuestros minilotes en formato JSON.

def prompt_program(fewshot_data, n_fewshot_examples=5, minibatch_size=1):
return Output(
MetaPrompt(
[
Section("Instructions", task["Definition"]),
Section(
"Examples",
FewshotExamples(
fewshot_data, n_fewshot_examples
),
),
Section("Output in same format as above", InputData()),
],
data_formatter=JSONDataFormatter(),
render_as="markdown",
).with_extractor(on_error="empty_result"),
minibatch_size=minibatch_size,
on_error="empty_result",
)

Al ejecutar esto sin minibatching y utilizando cinco ejemplos de pocos disparos, obtenemos un precisión de 0,76 y tener que pagar 58255 tokens de entrada.

Ahora, exploremos cómo el procesamiento en lotes pequeños afecta los costos y el rendimiento. Dado que el procesamiento en lotes pequeños reduce los costos totales de insumos, ahora podemos usar parte de esos ahorros para agregar más ejemplos de pocos intentos. Podemos estudiar esas compensaciones configurando un espacio de búsqueda en SAMMO:

def search_space(fewshot_data):
minibatch_size = search_op.one_of([1, 5, 10], name="minibatch_size")
n_fewshot_examples = search_op.one_of([5, 20], name="n_fewshot")

return prompt_program(fewshot_data, n_fewshot_examples, minibatch_size)

Al ejecutar esto, podemos ver la gama completa de compensaciones:

  setting                                  objective    costs                              parse_errors
--------------------------------------- ----------- --------------------------------- --------------
* {'minibatch_size': 1, 'n_fewshot': 5} 0.76 {'input': 58255, 'output': 5817} 0.0
{'minibatch_size': 1, 'n_fewshot': 20} 0.76 {'input': 133355, 'output': 6234} 0.0
{'minibatch_size': 5, 'n_fewshot': 5} 0.75 {'input': 15297, 'output': 5695} 0.0
{'minibatch_size': 5, 'n_fewshot': 20} 0.77 {'input': 30317, 'output': 5524} 0.0
{'minibatch_size': 10, 'n_fewshot': 5} 0.73 {'input': 9928, 'output': 5633} 0.0
* {'minibatch_size': 10, 'n_fewshot': 20} 0.77 {'input': 17438, 'output': 5432} 0.0

Así, incluso con 20 ejemplos de pocos disparos, ahorramos casi 70 % costes de insumos ([58255–17438]/58255) todo mientras ¡Manteniendo la precisión general! A modo de ejercicio, puede implementar su propio objetivo para tener en cuenta automáticamente los costos o incluir diferentes formas de formatear los datos en el espacio de búsqueda.

En todo esto está implícito que (i) tenemos suficientes ejemplos de entrada que utilizan las instrucciones compartidas y (ii) tenemos cierta flexibilidad con respecto a la latencia. El primer supuesto se cumple en muchos escenarios de anotación, pero obviamente no se cumple en consultas puntuales. En la anotación u otras tareas de procesamiento fuera de línea, la latencia tampoco es súper crítica ya que el rendimiento es lo más importante. Sin embargo, si su tarea es proporcionarle al usuario la respuesta lo más rápido posible, podría tener más sentido emitir B llamadas paralelas que una llamada con B ejemplos de entrada.