Skip to content
Transformaciones eficientes de cuDF mediante compilación JIT
Source: developer.nvidia.com

Transformaciones eficientes de cuDF mediante compilación JIT

Sources: https://developer.nvidia.com/blog/efficient-transforms-in-cudf-using-jit-compilation, developer.nvidia.com

TL;DR

  • cuDF utiliza NVRTC para compilar kernels personalizados en tiempo de ejecución, permitiendo la fusión de kernels durante la ejecución.
  • El enfoque de transformación JIT reduce el número total de kernels y mejora la localidad de caché y la utilización de memoria de la GPU frente a rutas precompiladas.
  • A partir de cuDF 25.08, JIT soporta operadores no cubiertos por AST, incluyendo el operador ternario (if-else) y funciones de cadena como find y substring.
  • La primera ejecución de JIT suele costar alrededor de 600 ms por kernel; una vez en caché, la carga suele ser de ~3 ms. Los beneficios aumentan con el tamaño de los datos, con mejoras reportadas de 2x a 4x en varios casos.

Contexto y antecedentes

RAPIDS cuDF ofrece un conjunto amplio de algoritmos ETL para procesar datos con GPUs. Para usuarios de pandas, cuDF brinda algoritmos acelerados sin cambios de código mediante cudf.pandas. Para desarrolladores de C++, cuDF expone un submódulo que acepta vistas no propietarias como entradas y devuelve tipos que poseen la memoria como salida, lo que facilita razonar sobre la duración de vida de los datos en la GPU y favorece la composabilidad de las APIs. Este modelo puede generar intermediarios y múltiples transferencias de memoria en la GPU. La fusión de kernels es una solución clave: un único kernel puede realizar varias operaciones sobre los mismos datos de entrada, reduciendo el tráfico de memoria y mejorando el rendimiento general. Este artículo explica cómo la compilación JIT trae la fusión de kernels al modelo de programación C++ de cuDF, ofreciendo mayor rendimiento en el procesamiento de datos y una utilización más eficiente de la memoria y la computación en GPU. En cuDF, las expresiones se representan típicamente como un árbol de operandos y operadores, donde cada hoja es una columna o escalar y cada intersección es un operador (Figura 1). En el caso típico, una expresión escalar convierte una o más entradas en una columna de salida. Para expresiones aritméticas, cuDF ofrece tres opciones de evaluación: precompilado, AST y transformación JIT. El enfoque precompilado invoca la API pública libcudf para cada operador, calculando la estructura de árbol. Las llamadas precompiladas presentan la mayor cobertura de tipos y operadores, pero generan intermediarios en la memoria global de la GPU. El enfoque AST utiliza la API compute_column para procesar el árbol completo con un kernel especializado y paralelismo de una fila por hebra. El kernel interpretado de AST facilita la fusión de kernels, pero tiene limitaciones en soporte de tipos y operadores. El enfoque de transformación JIT en cuDF usa NVRTC para compilar en tiempo de ejecución un kernel personalizado para completar transformaciones arbitrarias, creando kernels fusionados en tiempo real. NVRTC es una biblioteca de compilación en tiempo de ejecución para CUDA C++ que genera kernels fusionados en tiempo de ejecución. El enfoque de JIT tiene la ventaja de usar kernels optimizados para la expresión, en lugar de reservar recursos para un caso extremo. Hasta cuDF 25.08, la transformación JIT añade soporte para operadores clave que la ejecución AST no soporta, incluyendo el operador ternario y funciones de cadena como find y substring. La desventaja principal de la compilación JIT es que el tiempo de compilación de kernel (~600 ms) debe ser pagado en tiempo de ejecución o gestionado mediante la precarga de la caché JIT. Este tema se aborda con más detalle en secciones siguientes de este post. El repositorio rapidsai/cudf en GitHub proporciona una colección de ejemplos string_transforms para demostrar manipulación de cadenas con enfoques precompilado y JIT. Los casos de ejemplo extraen_email_jit y extract_email_precompiled se centran en una tarea que toma una cadena de entrada, verifica el formato básico y extrae el proveedor de la dirección de correo. Normalmente, una dirección como [email protected] devuelve provider. En entradas malformadas, se devuelve unknown. La Figura 2 ilustra extract_email_precompiled, con lógica destacada en verde para posiciones de “@” y “.”, en rosa para un campo is_valid y en azul para recortar el proveedor, substituyendo en caso de entrada no válida. Este enfoque genera resultados correctos, pero utiliza memoria y cómputo adicionales para materializar las posiciones de caracteres, múltiples columnas booleanas y la columna de alternancia. Con la compilación JIT, se puede crear un kernel de GPU que realiza el mismo trabajo de manera más eficiente. La Figura 3 muestra extract_email_jit, que utiliza una cadena cruda “udf” que define la transformación. El proceso comienza encontrando y cortando en el carácter “@”, luego en el carácter “.”. Este enfoque facilita la validación mediante un flujo de control más simple y retornos tempranos. Se alienta a revisar otros ejemplos para ver estas diferencias en acción. El uso de la transformación JIT para procesar UDFs resulta en tiempos de ejecución más rápidos que el enfoque precompilado. La principal fuente de mejora se debe a un menor conteo total de kernels, lo que mejora la localidad de caché y permite que los registros de la GPU contengan intermedios que de otro modo tendrían que almacenarse en la memoria global para un kernel subsiguiente. La Figura 4 muestra la línea de tiempo de lanzamientos de kernels para los tres ejemplos de transformación de cadenas, donde las barras azules representan kernels lanzados por el enfoque precompilado y las barras verdes las del enfoque JIT. Las medidas de la muestra se tomaron con 200M de filas, 12.5 GB de entrada, en hardware NVIDIA GH200 Grace Hopper usando un recurso de memoria CUDA asíncrona. Nótese que los kernels con tiempos de ejecución

Referencias

More news