Pruebas de rendimiento de los modelos fundacionales de AWS Bedrock

Cliente
Ubicación
Industria
Servicios y tecnología

Resumen

En este blog, exploramos cómo se comportan los modelos fundacionales de AWS Bedrock en diversas condiciones de carga e introducimos una metodología de prueba reutilizable para ayudarle a comprender las características de rendimiento en sus propias aplicaciones. Nuestros hallazgos revelan que el tamaño del prompt influye significativamente en los patrones de escalado, y los prompts grandes degradan el rendimiento más rápidamente a medida que aumenta la concurrencia en comparación con los prompts más pequeños. Demostramos cómo las pruebas de rendimiento conectan los valores de cuota abstractos con la planificación de la capacidad en el mundo real, lo que ayuda a construir casos más sólidos para las solicitudes de aumento de cuota cuando sea necesario.

 

Introducción: por qué son importantes las pruebas de rendimiento

Al integrar modelos de lenguaje grandes (LLM) en sistemas de producción, en particular los que se sirven en servicios gestionados como AWS Bedrock, la comprensión de las características de rendimiento es fundamental tanto para el éxito técnico como para el empresarial. Las pruebas de rendimiento se vuelven aún más cruciales al considerar las cuotas de servicio que AWS impone al uso de Bedrock.

 

Qué pretende lograr este blog

En este blog, nos proponemos lograr varios objetivos clave:

  • Establecer una metodología reutilizable para las pruebas de rendimiento de los modelos fundacionales de AWS Bedrock que pueda adaptarse a diferentes casos de uso
  • Desarrollar un marco de pruebas robusto utilizando las capacidades asíncronas de Python para simular patrones de carga del mundo real
  • Medir y analizar métricas de rendimiento críticas, en particular, tokens por segundo, niveles de concurrencia y tiempos de respuesta
  • Proporcionar información práctica sobre cómo se comportan los modelos en diversas condiciones de carga
  • Demostrar cómo visualizar los datos de rendimiento para tomar decisiones de escalado informadas
  • Mostrar cómo las pruebas de rendimiento respaldan la gestión de cuotas y ayudan a construir casos para las solicitudes de aumento de cuota

Al compartir nuestro enfoque y nuestros hallazgos, pretendemos ayudar a comprender mejor las características de rendimiento de los modelos de AWS Bedrock y a tomar decisiones informadas al diseñar sus aplicaciones.

 

Cuotas de AWS Bedrock y su impacto

Las cuentas de AWS tienen cuotas predeterminadas para Amazon Bedrock que restringen el número de solicitudes que puede realizar dentro de plazos específicos. Estas cuotas afectan directamente a la escalabilidad y a la experiencia del usuario de su aplicación.

 

Las cuotas de servicio clave para Amazon Bedrock incluyen:

  • Tokens por minuto (TPM): controla el rendimiento total de tokens en su cuenta
  • Solicitudes por minuto (RPM): limita el número de llamadas a la API que puede realizar

Estas cuotas se aplican específicamente a la inferencia bajo demanda y representan el uso combinado en las API de Converse, ConverseStream, InvokeModel e InvokeModelWithResponseStream para cada modelo fundacional, región de AWS y cuenta de AWS. Tenga en cuenta que la inferencia por lotes opera bajo cuotas de servicio separadas. Para obtener más información sobre cómo funcionan estas cuotas, consulte Cuotas para Amazon Bedrock.

El conocimiento de estas cuotas es crucial al diseñar la arquitectura de su aplicación. Si sus requisitos de uso superan las cuotas predeterminadas asignadas a su cuenta de AWS, deberá solicitar un aumento, un proceso que requiere justificación. Para obtener orientación sobre el escalado con Amazon Bedrock, consulte Escalado con Amazon Bedrock.

 

Traducción de cuotas abstractas a capacidad del mundo real

Uno de los mayores desafíos con las cuotas de AWS Bedrock es la traducción de métricas abstractas como TPM y RPM en capacidad de aplicación práctica. ¿Qué significan realmente estos números para su caso de uso específico?

Las pruebas de rendimiento ayudan a responder preguntas críticas como:

  • Capacidad de usuario: ¿Cuántos usuarios concurrentes puede soportar realmente su cuota?
  • Una cuota de 10.000 TPM podría soportar 100 usuarios que realizan solicitudes complejas o 500 usuarios que realizan solicitudes sencillas
  • Rendimiento de respuesta: ¿Cuál es la velocidad real de generación de tokens en diferentes patrones de carga?
  • La comprensión de cómo se escalan los tokens por segundo con las solicitudes concurrentes revela su verdadera capacidad de rendimiento
  • Implicaciones de latencia: ¿Cómo afecta a la experiencia del usuario el hecho de acercarse a los límites de la cuota?
  • Las pruebas revelan si se enfrentará a una degradación gradual del rendimiento o a fallos repentinos a medida que se acerque a los límites
  • Patrones de uso del mundo real: ¿Cómo se traducen sus prompts específicos y las longitudes de respuesta al consumo de cuota?
  • Un prompt de 5.000 tokens con una respuesta de 500 tokens tiene implicaciones de cuota muy diferentes a las de un prompt de 500 tokens con una respuesta de 5.000 tokens

Mediante la realización de pruebas de rendimiento específicas con sus prompts reales y los patrones de uso esperados, puede traducir los valores de cuota abstractos en una planificación de capacidad concreta: “Con nuestra cuota actual, podemos soportar X usuarios concurrentes con tiempos de respuesta de Y segundos”.

Las pruebas de rendimiento no son meramente un ejercicio técnico, sino una necesidad estratégica al implementar aplicaciones LLM en AWS Bedrock. Al establecer un enfoque sistemático para medir y analizar el rendimiento del modelo en diversas condiciones de carga, obtiene información crítica que informa todo, desde las decisiones arquitectónicas hasta la planificación de la capacidad y la gestión de cuotas.

Ahora vamos a explorar cómo implementar esta metodología de prueba en la práctica, examinando el código, las métricas y las técnicas de visualización que le darán una visión profunda de las características de rendimiento de su modelo.

 

Metodología: construcción de un marco de pruebas robusto

Antes de sumergirse en los resultados de las pruebas específicas, es esencial comprender cómo abordamos el desafío de probar sistemáticamente los modelos de AWS Bedrock. Nuestra metodología prioriza la reproducibilidad, las variables controladas y la simulación realista de las condiciones de producción. Al crear un marco estructurado en lugar de pruebas ad hoc, podemos obtener información significativa que se traduce directamente a los entornos de producción.

  • Test Runner (run_all_tests): Función de orquestación principal que gestiona la programación de solicitudes concurrentes
  • Request Handler (call_chat): Envuelve las llamadas a la API con mediciones de tiempo precisas
  • Concurrency Monitor: Realiza un seguimiento de los recuentos de solicitudes activas durante las pruebas
  • Data Collector: Agrega métricas de rendimiento
  • Visualization System: Genera información a partir de los datos recopilados

Este diseño modular nos permite aislar cada componente para la optimización y la depuración, manteniendo al mismo tiempo un enfoque de prueba cohesivo.

 

Modos de prueba

El marco admite dos enfoques de prueba distintos:

 

Modo secuencial

En este modo, cada lote de solicitudes concurrentes se completa totalmente antes de que comience el siguiente lote. Este enfoque proporciona mediciones limpias y aisladas sin interferencias entre lotes, lo que lo hace ideal para:

  • Establecer las características de rendimiento de referencia
  • Medir la respuesta del sistema a niveles de concurrencia específicos de forma aislada
  • Crear puntos de referencia reproducibles para comparar diferentes configuraciones
  • Determinar el rendimiento máximo en varios niveles de concurrencia

 

Modo de intervalo

En este modo más realista, los nuevos lotes de solicitudes se lanzan a intervalos fijos, independientemente de si los lotes anteriores se han completado. Este enfoque simula mejor los patrones de tráfico del mundo real donde:

  • Las nuevas solicitudes de usuario llegan continuamente a intervalos variables
  • Es posible que las solicitudes anteriores sigan procesándose cuando lleguen otras nuevas
  • El sistema debe gestionar los niveles de concurrencia fluctuantes
  • La contención de recursos entre las solicitudes revela los cuellos de botella del sistema

Este enfoque de modo dual nos permite tanto establecer líneas de base de rendimiento claras (secuencial) como observar cómo se comporta el sistema en condiciones de carga más realistas (intervalo).

 

Configuración del grupo de subprocesos

El marco de pruebas necesita gestionar muchas solicitudes concurrentes sin convertirse en un cuello de botella en sí mismo. Configuramos un grupo de subprocesos con capacidad suficiente utilizando el ThreadPoolExecutor de Python como se muestra a continuación:

custom_executor = ThreadPoolExecutor(max_workers)

loop = asyncio.get_running_loop()

loop.set_default_executor(custom_executor)

Esto proporciona capacidad suficiente para gestionar cientos de solicitudes concurrentes sin que el propio marco de pruebas se convierta en un cuello de botella. El marco está diseñado para ser configurable, lo que le permite probar diferentes niveles de concurrencia, patrones de solicitud y configuraciones de modelo con una metodología coherente.

 

Implementación de la gestión de solicitudes

El núcleo de nuestro marco de pruebas es la función call_chat, que gestiona las llamadas individuales a la API de AWS Bedrock mientras realiza un seguimiento de las métricas de tiempo precisas:

async def call_chat(chat, prompt):

“””

Incrementa el contador, registra la hora de inicio, realiza una llamada síncrona a la API en un subproceso separado,

luego registra la hora de finalización y disminuye el contador antes de devolver el resultado analizado

con las horas de inicio y finalización de la solicitud añadidas.

“””

global active_invoke_calls

async with active_invoke_lock:

active_invoke_calls += 1

try:

# Registre la hora de inicio antes de llamar a la API.

request_start = datetime.now()

result = await asyncio.to_thread(chat.invoke_stream_parsed, prompt)

# Registre la hora de finalización después de que se complete la llamada.

request_end = datetime.now()

 

# Añada las horas de inicio y finalización al resultado.

result[‘request_start’] = request_start.isoformat()

result[‘request_end’] = request_end.isoformat()

retorna resultado

finally:

async with active_invoke_lock:

active_invoke_calls -= 1

Esta función proporciona varias capacidades críticas:

  • Realiza un seguimiento preciso de los recuentos de llamadas concurrentes mediante operaciones atómicas
  • Captura información de tiempo precisa para el análisis del rendimiento
  • Ejecuta llamadas a la API en subprocesos separados para evitar el bloqueo del bucle de eventos
  • Asocia metadatos con cada solicitud para permitir un análisis detallado

 

Orquestación de pruebas

Nuestra función run_all_tests sirve como orquestador para todo el proceso de prueba:

async def run_all_tests(

chat,

prompt,

n_runs=60,

num_calls=40,

use_logger=True,

schedule_mode=”interval”,

batch_interval=1.0

):

# La implementación gestiona la programación en función del modo seleccionado

# Recopila y procesa los resultados

# Devuelve datos estructurados para el análisis

La función run_all_tests lanza varios lotes de llamadas de acuerdo con el modo de programación especificado:

  • En el modo “interval”, programa un nuevo lote cada batch_intervalsegundos, independientemente de si los lotes anteriores se han completado. Esto simula patrones de tráfico del mundo real donde las solicitudes llegan continuamente.
  • En el modo “sequential”, espera a que cada lote se complete antes de lanzar el siguiente. Esto ayuda a establecer líneas de base de rendimiento claras sin interferencias entre lotes.

Al mantener este enfoque de orquestación unificado, garantizamos una metodología de prueba coherente en diferentes modelos y configuraciones, lo que permite realizar comparaciones válidas.

 

Comprensión de las métricas clave de rendimiento

Para evaluar eficazmente el rendimiento de LLM, debe realizar un seguimiento de las métricas correctas. En esta sección se exploran las métricas esenciales que proporcionan información significativa sobre las características de rendimiento de sus modelos de AWS Bedrock.

Al evaluar los modelos fundacionales, varias métricas son particularmente relevantes:

  • Tokens de entrada: número de tokens en el prompt
  • Tokens de salida: número de tokens generados por el modelo
  • Tiempo hasta el primer token (TTFT): Retraso antes de recibir el primer token de respuesta, fundamental para la capacidad de respuesta percibida
  • Tiempo hasta el último token (TTLT): Tiempo total hasta que se recibe la respuesta completa
  • Tiempo por token de salida (TPOT): Tiempo promedio para generar cada token después del primero
  • Tokens por segundo (TPS): Medida de rendimiento que muestra la tasa de generación de tokens
  • Llamadas concurrentes activas: Número de solicitudes que se procesan simultáneamente

Estas métricas se recopilan para cada solicitud y luego se agregan para el análisis.

Para calcular los tokens por segundo (TPS), utilizamos la inversa del tiempo por token de salida:

tokens_per_second = 1/time_per_output_token

Los TPS determinan cuántos tokens puede generar el modelo por segundo. Los valores más altos indican velocidades de generación más rápidas.

Nuestro marco calcula automáticamente las distribuciones estadísticas para estas métricas, lo que le permite comprender no solo el rendimiento promedio, sino también la varianza y los valores atípicos:

tps_metric = “tokens_per_second”

mean_val = df[tps_metric].mean()

median_val = df[tps_metric].median()

min_val = df[tps_metric].min()

max_val = df[tps_metric].max()

Estas estadísticas de resumen revelan la estabilidad y la coherencia del rendimiento de su modelo. Una gran diferencia entre la media y la mediana podría indicar un rendimiento sesgado, mientras que una alta varianza entre los valores mínimo y máximo sugiere un rendimiento inestable.

 

Visualización de datos de rendimiento

Los datos sin procesar por sí solos no son suficientes para obtener información práctica de las pruebas de rendimiento. Las técnicas de visualización eficaces transforman los patrones de rendimiento complejos en representaciones visuales fáciles de entender que resaltan las tendencias, las anomalías y las oportunidades de optimización.
Nuestro marco incluye funciones de visualización especializadas que proporcionan múltiples perspectivas sobre los datos de rendimiento:

 

Panel de control de métricas completo

La función plot_all_metrics crea un panel de control consolidado con múltiples visualizaciones:

def plot_all_metrics(df, n_runs, metrics=None):

“””

Crea una figura con 8 subgráficos utilizando un diseño de mosaico de subgráficos “ABC;DEF;GGI”:

– 6 histogramas/kde para las métricas dadas (A-F).

– 1 histograma/kde para tokens_per_second específicamente (G).

– 1 diagrama de caja para tokens_per_second (I).

 

Un supertítulo en la parte superior incluye detalles del modelo/ejecución, leídos del df:

– df[‘model_id’]

– df[‘max_tokens’]

– df[‘temperature’]

– df[‘performance’]

– df[‘num_concurrent_calls’]

– Número de muestras (filas en df).

“””

Esta función genera un informe visual completo que incluye:

  • Histogramas de distribución para métricas clave como tokens de entrada, tokens de salida y varias mediciones de tiempo
  • Curvas de estimación de densidad del kernel (KDE) para enfatizar los patrones de distribución subyacentes aproximados
  • Diagramas de caja detallados para tokens_per_second para mostrar la mediana, los cuartiles y los valores atípicos
  • Estadísticas de referencia mostradas prominentemente para una evaluación rápida
  • Encabezado de metadatos con detalles de configuración de prueba para la reproducibilidad

Las visualizaciones están dispuestas en un diseño de cuadrícula cuidadosamente diseñado que facilita la comparación entre métricas al tiempo que mantiene la claridad visual (véase la Figura 1).

 

 

Análisis de rendimiento y concurrencia

La función plot_tokens_and_calls proporciona dos perspectivas críticas:

def plot_tokens_and_calls(df, df_calls, xlim_tps=None):

# Crea una figura con dos subgráficos

fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(25, 6))

 

# Primer subgráfico: Histograma de tokens_per_second

# …

 

# Segundo subgráfico: Concurrencia a lo largo del tiempo

# …

Esta visualización proporciona dos perspectivas críticas:

  • Un histograma de distribución de tokens por segundo en todas las solicitudes, que muestra la variabilidad del rendimiento
  • Una serie temporal que muestra los patrones de solicitud concurrentes a lo largo de la prueba

 

 

El subgráfico izquierdo de la Figura 2 revela el patrón de distribución del rendimiento. Un pico estrecho y alto indica un rendimiento constante, mientras que una distribución amplia y más plana sugiere un rendimiento variable. Las líneas de referencia verticales ayudan a identificar rápidamente las estadísticas clave:

  • Media (rojo): Rendimiento promedio
  • Mediana (negro): Valor medio, menos afectado por los valores atípicos que la media
  • Mínimo (azul): Rendimiento en el peor de los casos
  • Máximo (naranja): Rendimiento en el mejor de los casos

El subgráfico derecho realiza un seguimiento de la concurrencia a lo largo del tiempo, revelando cómo el sistema gestionó el patrón de carga. Esto ayuda a identificar si la prueba alcanzó los niveles de concurrencia previstos y los mantuvo durante toda la duración de la prueba.

 

Análisis comparativo entre ejecuciones de prueba

Para un análisis exhaustivo en múltiples ejecuciones de prueba, utilizamos una función mejorada que genera informes consolidados:

def create_all_plots_and_summary(df_dict, calls_dict, time_freq=’30S’):

# Calcula el mínimo/máximo global para un trazado consistente

global_min, global_max = get_min_max_tokens_per_second(*df_dict.values())

 

# Genera visualizaciones y compila estadísticas de resumen

summary_stats = {}

 

for run_name in df_dict.keys():

# Extrae métricas y crea visualizaciones

# …

 

# Calcula las métricas de concurrencia a partir de los eventos

# …

 

# Recopila estadísticas de resumen

summary_stats[run_name] = {

‘Model’: model_id,

‘Concurrent Calls’: int(num_concurrent),

‘Input Tokens’: input_tokens,

‘Average output Tokens’: df[‘output_tokens’].mean(),

‘Temperature’: temperature,

‘Average TPS’: df[‘tokens_per_second’].mean(),

‘Median TPS’: df[‘tokens_per_second’].median(),

‘Min TPS’: df[‘tokens_per_second’].min(),

‘Max TPS’: df[‘tokens_per_second’].max(),

‘Avg Response Time (s)’: avg_ttlt,

‘Average Concurrent Requests’: avg_concurrency,

‘Max Concurrent Requests’: max_concurrency

}

 

# Crea un dataframe de resumen

summary_df = pd.DataFrame.from_dict(summary_stats, orient=’index’)

 

return figures, summary_df

Esta función genera visualizaciones individuales para cada ejecución de prueba y una tabla resumen que permite una fácil comparación entre diferentes niveles de concurrencia. La tabla resumen se vuelve particularmente valiosa al optimizar para métricas específicas como tokens por segundo promedio o tiempo de respuesta (véase la Figura 3).

 

 

Análisis de la tendencia de escalado del rendimiento

Para visualizar la relación entre los niveles de concurrencia y las métricas de rendimiento, creamos gráficos de líneas estadísticas con intervalos de confianza. Estos gráficos revelan tanto la tendencia central como la variabilidad del rendimiento a medida que aumenta la concurrencia (véase la Figura 4).

# Establecer un estilo agradable

sns.set(style=”whitegrid”)

 

# Crear gráficos estadísticos con características avanzadas

sns.lineplot(data=df,

x=”num_concurrent_calls”,

y=”tokens_per_second”,

marker=”o”,

estimator=”median”,

errorbar=(“pi”, 50))

 

 

Resultados de las pruebas y análisis

Con nuestra metodología y métricas establecidas, ahora podemos examinar los datos de rendimiento reales de los modelos AWS Bedrock. Esta sección presenta hallazgos detallados de nuestras pruebas utilizando el modelo LLaMA 3.3 70B Instruct, revelando patrones importantes en cómo el modelo escala con solicitudes concurrentes. Estos conocimientos van más allá de los números brutos para identificar puntos operativos óptimos y posibles cuellos de botella.

Para nuestras pruebas con Llama 3 en AWS Bedrock, utilizamos la siguiente configuración:

model_id=”us.meta.llama3-3-70b-instruct-v1:0″

max_tokens=350

temperature=0.5

performance=”standard”

max_pool_connections = 20000

region_name = “us-west-2”

Utilizamos un mensaje filosófico simple para nuestras pruebas: “¿Cuál es el significado de la vida?”

 

Línea de base de rendimiento de una sola solicitud

Examinemos primero el rendimiento de una sola solicitud para establecer nuestra línea de base:

{‘response_text’: “La pregunta del significado de la vida…”,

‘input_tokens’: 43,

‘output_tokens’: 350,

‘time_to_first_token’: 0.38360680200275965,

‘time_to_last_token’: 2.9637207640043925,

‘time_per_output_token’: 0.007392876681953103,

‘model_id’: ‘us.meta.llama3-3-70b-instruct-v1:0’,

‘provider’: ‘Bedrock:us-west-2’}

A partir de esta única invocación, podemos extraer varias métricas clave de rendimiento:

  • Tiempo hasta el primer token (TTFT): ~0,38 segundos – Esto mide la latencia de respuesta inicial
  • Tiempo hasta el último token (TTLT): ~2,96 segundos – Esto mide el tiempo de respuesta total
  • Tiempo por token de salida: ~0,0074 segundos – Cuánto tiempo se tarda en generar cada token
  • Tokens por segundo: ~135 tokens/segundo (calculado como 1/0,007392) – Velocidad de generación bruta

Estas métricas de línea de base representan el rendimiento óptimo sin carga concurrente y sirven como nuestro punto de referencia para evaluar cómo cambia el rendimiento bajo una concurrencia creciente.

 

Escalado con concurrencia

Para evaluar exhaustivamente cómo escala el rendimiento con la concurrencia, probamos una amplia gama de niveles de concurrencia de 1 a 50 solicitudes concurrentes. Para cada nivel, ejecutamos 60 iteraciones de prueba para garantizar la significación estadística de nuestros resultados:

calls = [1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50]

# Ejecutar pruebas para num_calls

for num_calls in calls:

# Ejecutar 60 solicitudes en cada nivel de concurrencia

df_run, df_calls = await run_all_tests(

chat=my_chat,

prompt=my_prompt,

n_runs=60,

num_calls=num_calls,

use_logger=False,

schedule_mode=”interval”,

batch_interval=1.0

)

Después de recopilar datos en todos los niveles de concurrencia, observamos una amplia gama de rendimiento:

Tokens por segundo mínimos: 9,23

Tokens por segundo máximos: 187,30

Este rango significativo de ~9 a ~187 tokens por segundo demuestra cómo puede variar drásticamente el rendimiento bajo diferentes condiciones de carga.

 

 

El gráfico de la Figura 5 muestra la relación entre el nivel de concurrencia (eje x) y los tokens por segundo (eje y), con la línea azul representando el rendimiento medio y el área sombreada mostrando el intervalo de confianza del 50%. Esta visualización revela varios patrones clave:

  • Degradación del rendimiento con mayor concurrencia: La línea muestra una clara tendencia descendente, lo que indica que el rendimiento de la solicitud individual (tokens por segundo) disminuye gradualmente a medida que aumenta la concurrencia.
  • Eficiencia por solicitud: En el nivel de concurrencia 1, la mediana de tokens por segundo es de aproximadamente 136, disminuyendo a aproximadamente 117 en el nivel de concurrencia 50, una reducción del rendimiento de aproximadamente el 14%.
  • Ampliación de la varianza: El intervalo de confianza (área sombreada) se amplía ligeramente en los niveles de concurrencia más altos, lo que sugiere un rendimiento más variable a medida que el sistema gestiona más solicitudes simultáneas.

Cuando combinamos estos datos de rendimiento por solicitud con el hecho de que el rendimiento total es igual al rendimiento por solicitud multiplicado por la concurrencia, podemos derivar el rendimiento total del sistema:

  • En concurrencia 1: ~136 tokens/segundo de rendimiento total
  • En concurrencia 10: ~134 tokens/segundo por solicitud × 10 solicitudes = ~1.340 tokens/segundo en total
  • En concurrencia 30: ~126 tokens/segundo por solicitud × 30 solicitudes = ~3.780 tokens/segundo en total
  • En concurrencia 50: ~117 tokens/segundo por solicitud × 50 solicitudes = ~5.850 tokens/segundo en total

Este análisis revela que, si bien el rendimiento de la solicitud individual disminuye con la concurrencia, el rendimiento total del sistema continúa aumentando, aunque con rendimientos decrecientes en los niveles de concurrencia más altos.

 

 

Para las consideraciones de la experiencia del usuario, el patrón TTFT (capacidad de respuesta) es particularmente interesante. El gráfico de la Figura 6 muestra que el TTFT en realidad permanece notablemente estable en la mayoría de los niveles de concurrencia, contrariamente a lo que podría esperarse:

  • En concurrencia 1-30: El TTFT se mantiene constantemente alrededor de 0,28-0,29 segundos con una variación mínima
  • En concurrencia 30-40: El TTFT comienza una ligera pero notable tendencia ascendente
  • En concurrencia 40-50: El TTFT aumenta más significativamente, alcanzando aproximadamente 0,33 segundos en el nivel de concurrencia 50

Esto representa solo un aumento de aproximadamente el 17% en el tiempo de respuesta inicial desde el nivel de concurrencia 1 al 50, lo cual es sorprendentemente eficiente. La ampliación del intervalo de confianza en los niveles de concurrencia más altos indica una mayor variabilidad en el TTFT, lo que sugiere que, si bien el rendimiento medio sigue siendo relativamente bueno, algunas solicitudes pueden experimentar retrasos más significativos.

 

Impacto de los mensajes grandes en el rendimiento

Para comprender mejor cómo el tamaño de la entrada afecta las características de rendimiento, realizamos un segundo conjunto de experimentos utilizando un mensaje significativamente más grande: un marco de escritura creativa detallado para generar una narrativa épica de fantasía. Este mensaje pesó 6.027 tokens, en comparación con solo 43 tokens en nuestro mensaje inicial.

Aquí está el rendimiento de referencia para una sola solicitud con este mensaje grande:

{‘response_text’: ‘Este mensaje es una guía completa y detallada para generar una narrativa épica titulada “Las Crónicas de Eldrath: Una Saga de Luz y Sombra.”…’,

‘input_tokens’: 6027,

‘output_tokens’: 350,

‘time_to_first_token’: 0.9201213920023292,

‘time_to_last_token’: 3.2595945539942477,

‘time_per_output_token’: 0.006703361495678849,

‘model_id’: ‘us.meta.llama3-3-70b-instruct-v1:0’,

‘provider’: ‘Bedrock:us-west-2’}

Observaciones clave de la solicitud única de mensaje grande:

  • El TTFT aumentó drásticamente a ~0,92 segundos (en comparación con ~0,38 con el mensaje pequeño)
  • El TTLT aumentó moderadamente a ~3,26 segundos (en comparación con ~2,96 con el mensaje pequeño)
  • Los tokens por segundo en realidad mejoraron ligeramente a ~149 (calculado como 1/0,006703)

Esta ligera mejora en la velocidad de generación de tokens con mensajes más grandes en niveles de concurrencia bajos es interesante, pero probablemente podría atribuirse a la variabilidad natural del servicio en lugar de representar una ventaja de rendimiento consistente. Lo que es más significativo es cómo cambian estas características de rendimiento bajo una concurrencia creciente, donde vemos una divergencia clara y sustancial entre mensajes pequeños y grandes.

 

 

Al escalar la concurrencia con el mensaje grande, el patrón cambia significativamente, como se muestra en la Figura 7:

  • Curva de degradación mucho más pronunciada: La métrica de tokens por segundo cae de una mediana de ~126 en el nivel de concurrencia 1 a ~58 en el nivel de concurrencia 50, una reducción de más del 50%, en comparación con solo el 14% con el mensaje pequeño.
  • Rendimiento general más bajo: En el nivel de concurrencia 50, la mediana de tokens por segundo es solo ~58, con algunas solicitudes cayendo tan bajo como ~8 tokens por segundo.

 

 

Mirando específicamente el comportamiento del TTFT con el mensaje grande, el gráfico recién proporcionado (Figura 8) revela un patrón de degradación mucho más pronunciado en comparación con el mensaje pequeño:

  • En concurrencia 1-30: El TTFT permanece relativamente estable alrededor de 0,82-0,86 segundos
  • En concurrencia 30-40: El TTFT comienza a aumentar más pronunciadamente, alcanzando aproximadamente 0,92 segundos
  • En concurrencia 40-50: El TTFT se acelera dramáticamente hacia arriba a más de 1,03 segundos

Lo más notable es que el intervalo de confianza se amplía significativamente en los niveles de concurrencia más altos, con el límite superior superando los 1,3 segundos en el nivel de concurrencia 50. Esto indica que, si bien el TTFT medio aumenta en aproximadamente un 25% desde los niveles 1 a 50, algunos usuarios pueden experimentar retrasos en la respuesta inicial que son más del 50% más largos que la línea de base.

Este perfil de degradación para TTFT con mensajes grandes es sustancialmente diferente de lo que observamos con mensajes pequeños, donde el TTFT permaneció relativamente estable hasta niveles de concurrencia mucho más altos. La degradación más temprana y pronunciada sugiere que AWS Bedrock prioriza diferentes aspectos del rendimiento al tratar con contextos de entrada grandes bajo carga.

 

 

Al analizar el nivel de concurrencia de 50 con más detalle (Figura 8), el histograma de tokens por segundo muestra:

  • Media: 60,02 tokens/segundo
  • Mediana: 57,94 tokens/segundo
  • Mínimo: 7,89 tokens/segundo
  • Máximo: 122,38 tokens/segundo

Esto representa un impacto en el rendimiento mucho más significativo que el observado con el mensaje pequeño, lo que demuestra que los contextos de entrada grandes no solo aumentan la latencia inicial, sino que también reducen la capacidad del sistema para gestionar eficientemente las solicitudes concurrentes.

Al calcular el rendimiento total del sistema con el mensaje grande:

  • En concurrencia 1: ~126 tokens/segundo de rendimiento total
  • En concurrencia 10: ~121 tokens/segundo por solicitud × 10 solicitudes = ~1.210 tokens/segundo en total
  • En concurrencia 30: ~85 tokens/segundo por solicitud × 30 solicitudes = ~2.550 tokens/segundo en total
  • En concurrencia 50: ~58 tokens/segundo por solicitud × 50 solicitudes = ~2.900 tokens/segundo en total

Esto muestra que los rendimientos decrecientes se establecen mucho antes: el sistema logra solo ganancias de rendimiento mínimas más allá del nivel de concurrencia 30. El lado derecho de la Figura 8 ilustra la concurrencia real lograda durante la prueba, mostrando fluctuaciones entre 250-400 solicitudes concurrentes a medida que el sistema procesa la carga de trabajo.

 

Comparación del rendimiento del sistema: mensajes pequeños vs. grandes

Al comparar el rendimiento total del sistema entre mensajes pequeños y grandes en diferentes niveles de concurrencia, observamos diferencias sorprendentes en los patrones de escalado:

Nivel de concurrencia rendimiento de mensajes pequeños rendimiento de mensajes grandes diferencia (%)
1 ~136 tokens/seg ~126 tokens/seg -7.4%
10 ~1,340 tokens/seg ~1,210 tokens/seg -9.7%
30 ~3,780 tokens/seg ~2,550 tokens/seg -32.5%
50 ~5,850 tokens/seg ~2,900 tokens/seg -50.4%

 

En niveles de concurrencia bajos (1-10), la diferencia en el rendimiento total del sistema es relativamente pequeña, solo alrededor del 7-10% más bajo con mensajes grandes. Sin embargo, esta brecha se amplía drásticamente a medida que aumenta la concurrencia. En el nivel de concurrencia 30, el sistema de mensajes grandes logra solo alrededor de dos tercios del rendimiento del sistema de mensajes pequeños. En el nivel de concurrencia 50, la diferencia se vuelve aún más pronunciada, con el sistema de mensajes grandes entregando solo la mitad del rendimiento del sistema de mensajes pequeños.

Esta comparación revela varios conocimientos clave:

  • Diferentes curvas de escalado: Los mensajes pequeños exhiben un escalado casi lineal hasta el nivel de concurrencia 50, mientras que los mensajes grandes muestran rendimientos severamente decrecientes después del nivel de concurrencia 30.
  • Los cuellos de botella del sistema emergen antes con mensajes grandes: La fuerte caída en las ganancias de rendimiento sugiere que el procesamiento de contextos de entrada grandes crea cuellos de botella de recursos que no son tan significativos con entradas más pequeñas.
  • Los puntos de concurrencia óptimos difieren: Para mensajes pequeños, aumentar la concurrencia a 50 o potencialmente más aún produce beneficios de rendimiento significativos. Para mensajes grandes, el punto de concurrencia óptimo parece estar alrededor de 30-35, después de lo cual las solicitudes concurrentes adicionales proporcionan beneficios de rendimiento mínimos al tiempo que degradan el rendimiento de la solicitud individual.

 

Conclusión: traducir los conocimientos de rendimiento en aplicaciones prácticas

El enfoque de prueba de rendimiento descrito en este blog proporciona información valiosa sobre cómo se comportan los modelos fundacionales de AWS Bedrock bajo diversas condiciones de carga. Al medir sistemáticamente métricas clave como tokens por segundo, tiempos de respuesta e impactos de concurrencia, hemos establecido una comprensión integral que puede informar directamente las implementaciones de producción.

 

Conclusiones clave

  • Las implicaciones de la cuota de servicio son significativas: Todos los resultados en nuestras pruebas se calcularon utilizando cuotas de servicio elevadas especiales que se otorgaron específicamente para las pruebas con LLaMA 3.3 70B Instruct en Bedrock. Estas cuotas mejoradas de 6.000 RPM y 36 millones de TPM fueron sustancialmente más altas que los valores predeterminados de 800 RPM y 600.000 TPM disponibles para las cuentas estándar de AWS. Esta asignación especial fue necesaria para explorar a fondo el rendimiento en niveles de alta concurrencia, y no es representativa de a lo que la mayoría de los clientes tendrán acceso inicialmente. Esta dramática diferencia subraya la importancia crítica de comprender sus asignaciones de cuota específicas y planificar las solicitudes de aumento de cuota con mucha anticipación al diseñar aplicaciones LLM escalables.
  • El tamaño de la entrada impacta significativamente el escalado: Como se demostró en nuestras pruebas con LLaMA 3.3, los mensajes grandes (más de 6.000 tokens) escalan de manera muy diferente a los mensajes pequeños. Si bien los mensajes pequeños mantienen un escalado de rendimiento relativamente lineal hasta el nivel de concurrencia 50, los mensajes grandes experimentan una degradación dramática del rendimiento más allá del nivel de concurrencia 30.
  • Las características del tiempo de respuesta varían: El tiempo hasta el primer token (TTFT) permanece notablemente estable para mensajes pequeños incluso bajo carga alta, pero se degrada mucho más severamente con mensajes grandes. Esto tiene implicaciones directas para el diseño de la experiencia del usuario y la planificación de la capacidad.
  • Los puntos de concurrencia óptimos difieren según el caso de uso: Las aplicaciones que utilizan mensajes más pequeños pueden beneficiarse de niveles de concurrencia más altos (40-50+), mientras que aquellas con mensajes grandes deben considerar limitar la concurrencia a alrededor de 30-35 para mantener un rendimiento aceptable.

 

Gestión de cuotas de servicio

Las cuotas predeterminadas de AWS Bedrock (800 RPM y 600.000 TPM) restringirían significativamente la escalabilidad demostrada en nuestras pruebas. Nuestras pruebas utilizaron cuotas elevadas especialmente otorgadas de 6.000 RPM y 36 millones de TPM, lo que requirió una justificación formal y no está disponible automáticamente para los clientes de AWS. Sin estas asignaciones especiales, la concurrencia máxima alcanzable sería considerablemente menor. Esto destaca por qué la gestión de cuotas no es simplemente una preocupación operativa, sino una consideración arquitectónica fundamental que debe abordarse al principio de su proceso de diseño de aplicaciones.

Al planificar los aumentos de cuota, es esencial comprender que la relación entre los límites de cuota y el rendimiento real no siempre es sencilla. Asumiendo una correspondencia lineal entre las solicitudes concurrentes y el rendimiento de tokens (que, como se ve en el blog, no siempre es cierto dependiendo del tamaño del mensaje), las cuotas más altas teóricamente deberían permitir proporcionalmente más solicitudes concurrentes. Sin embargo, nuestras pruebas revelaron que el rendimiento comienza a degradarse de forma no lineal con mensajes grandes más allá de ciertos umbrales de concurrencia, lo que significa que simplemente aumentar estas cuotas puede no generar mejoras de rendimiento proporcionales en todos los escenarios.

Este último punto se basa en patrones observados y sigue siendo algo especulativo, ya que no tenemos visibilidad de cómo la infraestructura de AWS Bedrock gestiona exactamente estos escenarios entre bastidores o cómo se asignan los recursos entre diferentes patrones de solicitud a escala.

Al realizar pruebas de rendimiento similares con sus mensajes y patrones de respuesta reales, puede:

  • Calcular sus verdaderos requisitos de capacidad en función de los patrones de uso esperados
  • Determinar los niveles de concurrencia óptimos para sus flujos de trabajo específicos
  • Respaldar las solicitudes de aumento de cuota con datos de rendimiento concretos que justifiquen sus necesidades de recursos específicas

 

Recomendaciones de implementación

  • Solicite aumentos de cuota adecuados al principio del ciclo de vida de su proyecto; las cuotas predeterminadas pueden no ser suficientes para muchas cargas de trabajo de producción.
  • Para aplicaciones con grandes contextos de entrada, limite la cantidad de solicitudes que pueden ejecutarse al mismo tiempo para mantener un buen rendimiento.
  • Realice un seguimiento tanto de los tiempos de respuesta típicos como de los tiempos de respuesta más lentos (p95, p99) para asegurarse de que todos los usuarios tengan una buena experiencia, no solo el usuario promedio.

Las pruebas de rendimiento conectan los valores de cuota abstractos con la planificación de la capacidad en el mundo real. A través de la medición y el análisis sistemáticos del rendimiento del modelo en diversas condiciones, obtendrá información que puede servir de base para las decisiones, la planificación de la capacidad y la gestión de cuotas, lo que en última instancia conducirá a implementaciones de LLM más eficientes y eficaces.

 

Obtenga el código

La implementación completa del marco de pruebas que se analiza en este blog está disponible en GitHub:

El repositorio contiene la base de código completa de Python, las utilidades de visualización y los cuadernos de ejemplo para guiarle en la realización de sus propias pruebas de rendimiento utilizando los modelos básicos de AWS Bedrock.

Recomendamos encarecidamente a Avahi como un socio tecnológico fiable e innovador. Su experiencia en tecnologías de vanguardia fue fundamental para construir nuestra prueba de concepto (PoC) y desarrollar nuestro producto mínimo viable (MVP). Avahi siempre ofreció soluciones de alta calidad a tiempo, manteniendo un enfoque colaborativo y receptivo. Fueron más allá de las expectativas al identificar oportunidades de mejora, garantizando la escalabilidad y el cumplimiento de nuestros productos centrados en la aplicación de la ley. Avahi es la elección clara si necesita un socio tecnológico con conocimiento de la industria, profesionalidad y un compromiso con la innovación.

Brandon Puhlman

Fundador, bravo foxtrot

¿Listo para transformar su negocio con la IA?

Reserve su taller gratuito de IA de activación

Exploremos juntos sus oportunidades de IA de alto impacto en una sesión gratuita de medio día

Vea nuestros casos prácticos

Vea cómo hemos ofrecido resultados medibles para empresas como la suya