Pruebas de rendimiento de los modelos fundacionales de AWS Bedrock

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, especialmente los tokens por segundo, los niveles de concurrencia y los tiempos de respuesta
  • Proporcionar información práctica sobre cómo se comportan los modelos bajo diversas condiciones de carga
  • Demostrar cómo visualizar los datos de rendimiento para tomar decisiones de escalado fundamentadas
  • Mostrar cómo las pruebas de rendimiento apoyan la gestión de cuotas y ayudan a fundamentar las solicitudes de aumento de estas

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 toda 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 usuarios: ¿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 bajo 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 la 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 en el 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 profundizar en los resultados específicos de las pruebas, 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 realizar pruebas ad hoc, podemos obtener información valiosa que se traslada directamente a los entornos de producción.

Arquitectura del marco de trabajo

El marco de pruebas aprovecha la biblioteca asyncio de Python para crear patrones de carga controlados y reproducibles con varios componentes clave:

  • Ejecutor de pruebas (run_all_tests): función de orquestación principal que gestiona la programación de solicitudes concurrentes
  • Controlador de solicitudes (call_chat): envuelve las llamadas a la API con mediciones de tiempo precisas
  • Monitor de concurrencia: rastrea el recuento de solicitudes activas durante las pruebas
  • Colector de datos: agrega métricas de rendimiento
  • Sistema de visualización: 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):

“””

Increments the counter, records the start time, makes a synchronous API call in a separate thread,

then records the end time and decrements the counter before returning the parsed result

with the request start and end times added.

“””

global active_invoke_calls

async with active_invoke_lock:

active_invoke_calls += 1

try:

# Record the start time before calling the API.

request_start = datetime.now()

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

# Record the end time after the call completes.

request_end = datetime.now()

 

# Add the start and end times to the result.

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

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

return result

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

):

# Implementation handles scheduling based on the selected mode

# Collects and processes results

# Returns structured data for analysis

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 percepción de la capacidad de respuesta
  • Tiempo hasta el último token (TTLT): tiempo total hasta que se recibe la respuesta completa
  • Tiempo por token de salida (TPOT): tiempo medio 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):

“””

Creates one figure with 8 subplots using a subplot_mosaic layout “ABC;DEF;GGI”:

– 6 histograms/kde for the given metrics (A-F).

– 1 histogram/kde for tokens_per_second specifically (G).

– 1 boxplot for tokens_per_second (I).

A suptitle at the top includes model/run details, read from the df:
 
– df[‘model_id’]

– df[‘max_tokens’]

– df[‘temperature’]

– df[‘performance’]

– df[‘num_concurrent_calls’]

– Number of samples (rows in 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 diversas mediciones de tiempo
  • Curvas de estimación de densidad de 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 de forma destacada para una evaluación rápida
  • Encabezado de metadatos con detalles de la configuración de la prueba para su 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).

Figura 1: Ejemplo de salida de la función plot_all_metrics que muestra la distribución de las métricas de rendimiento clave.

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):

# Creates a figure with two subplots

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

 

# First subplot: Histogram of tokens_per_second

# …

 

# Second subplot: Concurrency over time

# …

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
Figura 2: Ejemplo de salida de plot_tokens_and_calls.
Izquierda: Distribución de tokens por segundo con líneas de referencia estadísticas.
Derecha: Solicitudes concurrentes a lo largo del tiempo.

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’):

# Calculate global min/max for consistent plotting

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

 

# Generate visualizations and compile summary statistics

summary_stats = {}

 

for run_name in df_dict.keys():

# Extract metrics and create visualizations

# …

 

# Calculate concurrency metrics from events

# …

 

# Collect summary statistics

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

}

 

# Create summary dataframe

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).

Figura 3: Tabla resumen de rendimiento: Métricas comparativas en diferentes niveles de concurrencia que muestran los tokens por segundo y los tiempos de respuesta.

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).


# Set a nice style

sns.set(style=”whitegrid”)

 

# Create statistical plots with advanced features

sns.lineplot(data=df,

x=”num_concurrent_calls”,

y=”tokens_per_second”,

marker=”o”,

estimator=”median”,

errorbar=(“pi”, 50))

 
Figura 4: Patrón de tokens por segundo a medida que aumenta la concurrencia con intervalos de confianza (ejemplo).

 

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’: “The question of the meaning of life…”,

‘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 total de respuesta
  • 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]

# Run tests for num_calls

for num_calls in calls:

# Run 60 requests at each concurrency level

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:


Minimum tokens_per_second: 9.23

Maximum tokens_per_second: 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.

Figura 5: Escalado del rendimiento con prompts pequeños: Relación entre tokens por segundo y nivel de concurrencia con una entrada de 43 tokens.

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 el aumento de la concurrencia: La línea muestra una clara tendencia a la baja, lo que indica que el rendimiento de las solicitudes individuales (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 unos 117 en el nivel de concurrencia 50, lo que supone 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 niveles de concurrencia más altos, lo que sugiere un rendimiento más variable a medida que el sistema maneja 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.

Figura 6: Capacidad de respuesta con prompts pequeños: Estabilidad del tiempo hasta el primer token (TTFT) en niveles de concurrencia crecientes con una entrada de 43 tokens.

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’: ‘This prompt is a comprehensive and detailed guide for generating an epic narrative titled “The Chronicles of Eldrath: A Saga of Light and Shadow.”…’,

‘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 prompt pequeño)
  • El TTLT aumentó moderadamente a ~3,26 segundos (en comparación con ~2,96 con el prompt pequeño)
  • Los tokens por segundo 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.

Figura 7: Escalado del rendimiento con prompts grandes: Degradación pronunciada de los tokens por segundo a medida que aumenta la concurrencia con una entrada de 6.027 tokens.

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 prompt pequeño.
  • Menor rendimiento general: En el nivel de concurrencia 50, la mediana de tokens por segundo es de solo ~58, con algunas solicitudes que caen hasta ~8 tokens por segundo.
Figura 8: Capacidad de respuesta con prompts grandes: Patrón de degradación del tiempo hasta el primer token (TTFT) que muestra un aumento de la latencia y la variabilidad a medida que aumenta la concurrencia con una entrada de 6.027 tokens.

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.

Figura 8: Prompt grande con concurrencia máxima: Izquierda: distribución de tokens por segundo en 50 llamadas concurrentes. Derecha: Fluctuaciones reales de concurrencia durante la duración de la prueba.

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 con prompt pequeño Rendimiento con prompt grande 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 prompts pequeños muestran un escalado casi lineal hasta el nivel de concurrencia 50, mientras que los prompts grandes muestran rendimientos decrecientes severos después del nivel de concurrencia 30.
  • Los cuellos de botella del sistema surgen antes con prompts 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 los prompts pequeños, aumentar la concurrencia a 50 o potencialmente más sigue produciendo beneficios significativos en el rendimiento. Para los prompts grandes, el punto de concurrencia óptimo parece estar alrededor de 30-35, después de lo cual las solicitudes concurrentes adicionales proporcionan beneficios mínimos en el rendimiento mientras degradan el rendimiento de las solicitudes individuales.

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 de nuestras pruebas se calcularon utilizando cuotas de servicio elevadas especiales que se concedieron 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 concurrencia altos, y no es representativa de 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 antelación al diseñar aplicaciones LLM escalables.
  • El tamaño de la entrada afecta significativamente al escalado: Como se demostró en nuestras pruebas con LLaMA 3.3, los prompts grandes (más de 6.000 tokens) escalan de forma muy diferente a los prompts pequeños. Mientras que los prompts pequeños mantienen un escalado de rendimiento relativamente lineal hasta el nivel de concurrencia 50, los prompts grandes experimentan una degradación drástica 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 los prompts pequeños incluso bajo alta carga, pero se degrada mucho más severamente con los prompts 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 prompts más pequeños pueden beneficiarse de niveles de concurrencia más altos (40-50+), mientras que aquellas con prompts grandes deberían 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 exacta de cómo la infraestructura de AWS Bedrock gestiona estos escenarios entre bastidores o cómo se asignan los recursos a través de 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.

¿Listo para transformar su negocio con la IA?

Exploremos juntos sus oportunidades de IA de alto impacto en una sesión gratuita