Comprender el rendimiento de la aplicación con el modelado de la línea de techo

Con el cálculo del rendimiento de una aplicación es que el rendimiento del mundo real y el rendimiento teórico pueden diferir. Con un ecosistema de productos que crecen con necesidades de alto rendimiento, como la computación de alto rendimiento (HPC), los juegos o en el panorama actual: modelos de lenguaje grande (LLM), es esencial calcular con precisión el rendimiento de una aplicación.

Simplemente medir las GFLOP teóricas (operaciones de punto flotante por segundo) no es suficiente, ya que las aplicaciones rara vez alcanzan estos máximos en el mundo real. Aquí es donde entra el modelo de línea de techo, ofreciendo un método visual claro para estimar el rendimiento de una aplicación y destacando el papel crítico de las optimizaciones específicas de hardware.

Por qué las métricas simples no son suficientes

Cuando pensamos en medir el rendimiento, hay algunas métricas que vienen a la mente:

  • Tiempo de ejecución: Esto te dice cuánto tiempo Se tomó una tarea pero no ofrece información sobre por qué.
  • Ciclos por instrucciones (CPI): Esta only mide el rendimiento de cálculo del procesador.
  • Ejecución serial vs paralela: Mide el rendimiento de calcular con vista a Cualquier optimización de hardware.
  • Operaciones de punto flotante por segundo (flop/s): Esta only representa un máximo teórico que a menudo no se puede alcanzar en un escenario del mundo real.

Si bien estas son buenas métricas, generalmente no proporcionan suficiente información. Por ejemplo, el uso de las operaciones del punto flotante por segundo es un límite teórico que no se logra a menudo. Entonces, usando eso como el solo La métrica no es suficiente ya que ignora un movimiento de limitador de rendimiento común: el movimiento de datos.

Modelado de la línea de techo

El modelo de línea de techo es una herramienta poderosa que mapea visualmente el rendimiento de una aplicación con las capacidades de una arquitectura de hardware específica, como una CPU o GPU. El modelo obtiene su nombre de la forma del gráfico que produce, que presenta un “techo” compuesto por una línea inclinada y una línea horizontal plana. Esta forma representa los límites de rendimiento definitivos impuestos por el hardware.

A partir de esta técnica de modelado, hay dos parámetros que definen los límites alcanzables con el hardware:

  • Movimiento de datos: El tiempo que lleva mover datos, calculado como el tamaño total de datos dividido por el ancho de banda de memoria máxima del sistema.
  • Cálculo: El tiempo requerido para los cálculos, determinado dividiendo el número total de operaciones de punto flotante por el rendimiento de cómputo máximo del sistema (comúnmente medido en GFLOP/S).

El tiempo de ejecución total de una aplicación está determinado por el mayor de estos dos valores: max {data_movement, computation}.

A pesar de que el hardware tiene un mejor rendimiento de cálculo, el movimiento de datos a menudo puede convertirse en el cuello de botella. El modelado de la línea de techo presenta el concepto de Intensidad aritmética (IA). La IA es la relación de las operaciones de punto flotante realizadas para cada byte de datos movidos de la memoria.

  • Un algoritmo con alta intensidad aritmética se considera hambriento de cómputo. Su rendimiento está limitado por la rapidez con que se pueden realizar los cálculos.
  • Un algoritmo con baja intensidad aritmética se considera hambriento de datos. Su rendimiento está limitado por la rapidez con que se pueden mover los datos.

Comprender el gráfico

https://commons.wikimedia.org/wiki/file:example_of_a_naive_roofline_model.svg
Comunics creativos Darga de atribución por igual 4.0 International

Un gráfico de la línea de techo traza el flop/s alcanzable (eje y) contra la intensidad aritmética (eje x). El “techo” en sí muestra las limitaciones del hardware. La parte inclinada del techo representa el ancho de banda de datos máximos (en GB/S), mientras que la parte plana representa el rendimiento computacional máximo (en GFLOPS). Tenga en cuenta que todo en la imagen está en una escala logarítmica.

  • Puntos debajo del techo: Indique un rendimiento subóptimo que indica un alcance de mejora.
  • Puntos que llegan a la línea inclinada: Aplicación hambrienta de datos. Su rendimiento está limitado por el ancho de banda de datos.
  • Puntos que llegan a la línea plana: Calcule la aplicación hambrienta. Está utilizando el poder computacional completo del procesador.

¿Por qué es importante el modelado de la línea del techo?

El modelado de la línea de techo proporciona una forma visual e intuitiva de comprender el rendimiento de la aplicación, que muestra características clave como la intensidad operativa, las capacidades de GPU y el flop/s alcanzable. Este tipo de modelado ayuda al programador a hacer optimizaciones específicas a su aplicación de hardware con la que se pueden obtener mejores resultados.

  • Análisis de cuello de botella: Tener una ayuda visual facilita al desarrollador descubrir dónde está el cuello de botella: memoria o rendimiento. Si la aplicación es intensiva en memoria, un desarrollador puede centrarse en mejorar la localidad de datos con técnicas como almacenamiento en caché o mosaico de bucle. Si es intensivo en el cómputo, el enfoque puede cambiar a habilitar cálculos más paralelos o aprovechar las optimizaciones del compilador.
  • Diseño de hardware y software: Los ingenieros de software no deben temer el hardware subyacente. En cambio, el diseño de hardware debe ser adoptado y optimizado. Los ingenieros de software pueden usar información del modelado de la línea de techo para adoptar y optimizar para la arquitectura específica que están utilizando.

Modelado de la línea de techo en acción

Para realizar el modelado de la línea de techo, necesitamos perfilar la aplicación para comprender el rendimiento. Desde el perfil, podemos obtener métricas como operaciones de puntos flotantes (FLOPS) y uso de ancho de banda de memoria, que se requieren para el modelado de la línea de techo. Este artículo explora dos de estas herramientas: Nvidia’s ncu que es la CLI de cómputo NSIGHT para el análisis de GPU y el perfilador de Pytorch, específicamente para aplicaciones que usan Pytorch.

Para la optimización detallada del núcleo de CUDA y cálculos precisos de flop/byte, ncu Proporciona información directa de contador de hardware de GPU. En contraste, torch.profiler.profile Ofrece una perspectiva de nivel superior dentro de Pytorch, ayudando en la comprensión del rendimiento a nivel de operador, el uso de la memoria del tensor y el comportamiento general de la aplicación que abarca las actividades de CPU y GPU.

Perfiles con NCU

ncu es la interfaz de línea de comando que se utiliza para perfilar los núcleos Cuda [2]. Puede mostrar resultados directamente en el terminal o guardarlos en un archivo de registro para un análisis posterior. Para construir un modelo de línea de techo, necesitamos capturar las métricas específicas que nos permitirán calcular la intensidad aritmética.

Usaremos el repositorio de Pytorch ImageNet [3] Como nuestro ejemplo. Es una buena opción porque es fácil de entender, bien documentado por Pytorch, y trabaja con su perfilador, por lo que realmente podemos profundizar en el rendimiento.

Paso 1: ejecute el comando NCU para recopilar métricas

El primer paso es ejecutar la aplicación a través de NCU para recopilar los datos de nivel de hardware necesarios. El comando se ve así:

ncu --log-file <log_file_name> \
    --metrics <list_of_metrics_separated_by_comma> \
    --target-processes all \
    python3 <your_application.py application_arguments>
  • File de registro: el archivo de registro en el que queremos almacenar los resultados.
  • Métricas: este es el parámetro más importante y representa las métricas que queremos capturar. Para calcular la intensidad aritmética, consideramos:
    • dram__sectors_write.sum : Suma de sectores DRAM escrito
    • dram__sectors_read.sum : Suma de los sectores DRAM se lee
    • smsp__sass_thread_inst_executed_op_fadd_pred_on.sum : suma de adiciones de punto flotante
    • smsp__sass_thread_inst_executed_op_fmul_pred_on.sum : Suma de multiplicaciones de punto flotante
    • smsp__sass_thread_inst_executed_op_ffma_pred_on.sum : Suma de operaciones de agrega de múltiples fusionadas de punto flotante
  • proceso objetivo: all La bandera asegura que perfilemos toda la aplicación.

Nuestro comando NCU cambia a:

ncu --log-file logs_example --metrics dram__sectors_write.sum, \
dram__sectors_read.sum, \
smsp__sass_thread_inst_executed_op_fadd_pred_on.sum, \ 
smsp__sass_thread_inst_executed_op_fmul_pred_on.sum, \
smsp__sass_thread_inst_executed_op_ffma_pred_on.sum \
--target-processes all python3 \
main.py /imagenet --arch resnet50 --epochs 1 --batch-size 10 \
--print-freq 10 --seed 42

Paso 2: Cálculo de fracasos a partir de las métricas

Una vez que el Profiler ha ejecutado, podemos agregar las métricas recolectadas para calcular las operaciones totales de punto flotante. La fórmula es:

\[FLOPs = 2 * FMA\_count + FADD\_count + FMUL\_count\]

  • Flops: Recuento de operaciones de puntos flotantes.
  • FMA_COUNT: Las operaciones fusionadas multiplicando (FMA) generalmente cuentan como 2 flops (una multiplicación y una adición). Esto está representado por el smsp__sass_thread_inst_executed_op_ffma_pred_on.sum métrico.
  • Fadd_count: Esto está representado por el smsp__sass_thread_inst_executed_op_fadd_pred_on.sum métrico.
  • Fmul_count: Esto está representado por el smsp__sass_thread_inst_executed_op_fmul_pred_on.sum métrico.

Paso 3: Calcule los bytes transferidos

A continuación, calculamos los datos totales transferidos hacia y desde DRAM. Las métricas de NCU proporcionan el número de sectores DRAM leídos y escritos. Suponiendo un tamaño del sector común de 32 bytes para GPU modernas:

\[Total\_DRAM\_bytes = (dram\_\_sectors\_read.sum + dram\_\_sectors\_write.sum) * 32\]

Paso 4: Calcule la intensidad aritmética

Con fracasos y bytes totales, ahora podemos calcular la intensidad aritmética:

\[AI = FLOPs / Total\_DRAM\_Bytes\]

Paso 5: Calcule el tiempo de ejecución

Para encontrar el rendimiento de la aplicación en Flop/S, también necesitamos el tiempo de ejecución. Para esto, podemos usar NVIDIA NSIGHT Systems (NSYS), un perfilador de todo el sistema que puede medir con precisión el tiempo de ejecución de los segmentos de aplicación. Ejecutamos nuestra aplicación nuevamente, esta vez con NSYS, para generar un informe basado en el tiempo. A partir de este informe, podemos extraer el tiempo de ejecución total de GPU.

nsys profile -f true -o <your_nsys_output_file.qdrep> python3 \
<your_application.py application_arguments>

Nuestro comando NSYS cambia a:

nsys profile -f true -o time.qdrep python3 main.py /imagenet \
--arch resnet50 --epochs 1 --batch-size 10 --print-freq 10 \
--seed 42

Después de ejecutar este comando, podemos obtener el GPU_RUNNING_TIME.

Paso 6: Calcule el rendimiento de la aplicación

Finalmente, calculamos el rendimiento alcanzado en flop/s dividiendo los fracasos totales por el tiempo de ejecución:

\[FLOP/s = FLOPs / GPU\_RUNNING\_TIME\]

Este valor nos da el “flop/s alcanzable” que podemos trazar en nuestro gráfico de línea de techo.

Perfil con antorcha

Para aplicaciones escritas en Pytorch, el incorporado torch.profiler.profile Ofrece una forma fácil de usar para recopilar datos de rendimiento. Hay 2 opciones que se proporcionan a los desarrolladores:

  • Use el administrador de contextores de perfilador
  • Dirigir el perfil para capas de red neuronales específicas

Gerente de contexto de Profiler

La parte del código que queremos perfil se puede envolver dentro del con torch.profiler.profile() Gerente de contexto. En el with declaración, puede definir el activities Para rastrear (CPU, CUDA o ambos), establece un schedule para perfilar pasos de entrenamiento específicos y elija si registrar formas de tensor, uso de memoria o fracasos. Una vez dentro del contexto, debes llamar prof.step() Al final de cada iteración para indicar al perfilador que avance, especialmente cuando se usa un horario.

with profile(
    activities=<arguments>,
    schedule=torch.profiler.schedule(<arguments>),
    record_shapes=<True|False>,
    profile_memory=<True|False>,
    with_flops=<True|False>
) as prof:

    ....
    prof.step()
  • actividades: Especifique si perfilan la CPU, CUDA o ambos.
  • cronograma: Útil para perfilar múltiples pasos en el bucle de entrenamiento. Si se usa el parámetro de programación, el Profiler debe llamar a Prof.step () para pasar al siguiente paso.
  • Record_sapes: Si grabar las formas de los tensores.
  • perfil_memory: Para capturar el uso de la memoria
  • with_flops: Esto es experimental pero se usa para fracasar con los operadores.

Nuestro comando de perfilador cambia a:

with profile(
    activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
    schedule=torch.profiler.schedule(wait=1, warmup=1, active=3, repeat=2),
    record_shapes=True,
    profile_memory=True,
    with_flops=True
) as prof:

Dirigir el perfil para capas de red neuronales específicas

El Profiler también se puede utilizar de manera más específica para analizar capas específicas de una red neuronal. Esto es útil para verificar si alguna capa específica está contribuyendo más al rendimiento que las otras capas, lo que le da al desarrollador la opción de modificar capas específicas. Si bien usar esto es muy fácil de usar, en la mayoría de los casos, la primera opción funciona mejor. Los resultados de Pytorch Profiler también se pueden exportar y visualizar en una placa tensorial.

profiler.start()
self.conv2(x)
profiler.stop()

LLMS y modelado de la línea de techo

Llegando al tema que todos han estado esperando: ¿ayuda a modelar la línea de techo con el cálculo de rendimiento de LLM? La respuesta corta es sí.

Los LLM son arquitecturas de redes neuronales complejas con miles de millones de parámetros y los conjuntos de datos masivos que procesan. Si bien la capacitación es una tarea muy intensiva en recursos, la inferencia y el ajuste fino del modelo también deben ser eficientes.

  • Cuellos de botella: Los LLM durante la inferencia pueden sufrir cuellos de botella debido a la gran cantidad de parámetros con los que está trabajando. Estos parámetros son los pesos de los modelos y causan problemas de ancho de banda de memoria. Usando el modelado de la línea de techo, las capas exactas se pueden perfilar para los cuellos de botella.
  • Selección de hardware: Como la mayoría de las organizaciones ajustan los modelos existentes en lugar de capacitarlos desde cero, elegir la infraestructura adecuada es crucial para administrar los costos. Esto subraya la importancia de elegir una infraestructura óptima para el entrenamiento. Por ejemplo, elegir el hardware de acuerdo con su arquitectura LLM u optimizar su modelo para ejecutarse en una arquitectura específica puede reducir los costos de capacitación e inferencia.

Conclusión

El modelo de línea de techo ofrece un poderoso análisis visual de la optimización del rendimiento de la aplicación. Al visualizar el rendimiento de la aplicación en la memoria y el cálculo, se proporciona una guía clara para elegir la mejor manera de abordar las optimizaciones. Si bien este artículo solo consideraba modelos ingenuos de la línea de techo, existen técnicas más avanzadas, como modelos de línea de techo jerárquico o agregar techos para optimizaciones de cómputo específicas.

Referencias

[1] https://docs.nersc.gov/tools/performance/roofline/

[2] https://docs.nvidia.com/nsight-compute/nsightcomputecli/index.html

[3] https://github.com/pytorch/examples/tree/main/imagenet

[4] https://developer.nvidia.com/nsight-systems