El Portal de las Tecnologías para la Innovación

IO Remoto de Alto Rendimiento con NVIDIA KviKIO

Las cargas de trabajo que procesan grandes cantidades de datos, especialmente las que se ejecutan en la nube, a menudo utilizan un servicio de almacenamiento de objetos (S3, Google Cloud Storage, Azure Blob Storage, etc.) como fuente de datos. 

Los servicios de almacenamiento de objetos pueden almacenar y servir grandes cantidades de datos, pero obtener el mejor rendimiento puede requerir adaptar su carga de trabajo a cómo se comportan los almacenes de objetos remotos. Esta publicación es para usuarios de RAPIDS que desean leer o escribir datos en el almacenamiento de objetos lo más rápido posible para que IO no bloquee su carga de trabajo.

Parte de su conocimiento sobre cómo se comportan los sistemas de archivos locales se traduce en almacenes de objetos remotos, pero son fundamentalmente diferentes. Probablemente la mayor diferencia entre los dos, al menos para las cargas de trabajo de análisis de datos, es que las operaciones de lectura y escritura en el almacenamiento de objetos tienen latencia más alta y más variable. Cada servicio de almacenamiento tiene su propio conjunto de mejores prácticas y pautas de rendimiento (AWSAzure). Aquí, weizll dar algunas pautas generales que se centran en las cargas de trabajo de análisis de datos.

Ubicación

Colocar sus nodos de cómputo cerca del servicio de almacenamiento (idealmente, en la misma región de la nube) le dará la red más rápida y confiable entre las máquinas que ejecutan su carga de trabajo y las máquinas que sirven los datos. Y, al final del día, la transferencia estará limitada por la velocidad de la luz, por lo que minimizar la distancia física no duele.

Formato de archivo 

“Los formatos de archivo Cloud-native” se han desarrollado para funcionar bien con el almacenamiento de objetos. Estos formatos de archivo generalmente proporcionan un acceso rápido y fácil a los metadatos (que incluye información de alto nivel como los nombres de columna o los tipos de datos, e información de nivel inferior como dónde se encuentran los subconjuntos de datos específicos del archivo). Parquet ApacheZarr, y GeoTIFF Optimizado en la Nube son algunos ejemplos de formatos de archivo nativos de la nube para varios tipos de datos.

GTC 2025 NVIDIA

Porque los servicios de almacenamiento de objetos generalmente admiten solicitudes de rango, clientes (como cuDF) puede leer los metadatos y luego descargar solo los datos que realmente necesitas. Por ejemplo, cuDF puede leer solo unas pocas columnas de un archivo de parquet con muchas columnas, o un cliente de Zarr puede leer un solo fragmento de una gran matriz n-dimensional. Estas lecturas se realizan en solo unas pocas solicitudes HTTP, y sin necesidad de descargar un montón de datos extraños que simplemente se filtran.

Tamaño del archivo

Debido a que cada operación de lectura requiere (al menos) una petición HTTP, weedd prefiere amortizar la sobrecarga de cada petición HTTP sobre un número razonablemente grande de bytes. Si controla el proceso de escritura de datos, querrá asegurarse de que los archivos sean lo suficientemente grandes como para que sus tareas de procesamiento posteriores obtengan un buen rendimiento. El valor óptimo depende de su carga de trabajo, pero en algún lugar de las docenas a cientos de MB es común para los archivos de parquet (ver más abajo para algunos ejemplos específicos).

Dicho esto, deberá tener cuidado con la forma en que el tamaño del archivo interactúa con la siguiente herramienta de nuestro kit: concurrencia.

Concurrencia

El uso de concurrencia para descargar múltiples blobs (o múltiples piezas de un solo blob) al mismo tiempo es esencial para obtener un buen rendimiento de un servicio de almacenamiento remoto. Ya que es un remoto servicio, su proceso va a pasar algún tiempo (quizás mucho tiempo) esperando no hacer nada. Esta espera abarca el tiempo entre el momento en que se envía la solicitud HTTP y la respuesta recibida. Durante este tiempo, esperamos a que la red lleve la solicitud, el servicio de almacenamiento la procese y envíe la respuesta, y la red lleve la respuesta (posiblemente grande). Mientras que partes de esa escala de ciclo de solicitud/respuesta con la cantidad de datos involucrados, otras partes son solo gastos generales fijos.

Los servicios de almacenamiento de objetos están diseñados para manejar muchas solicitudes concurrentes. Podemos combinar eso con el hecho de que cada solicitud implica algún tiempo esperando no hacer nada, para hacer muchas solicitudes concurrentes para aumentar nuestro rendimiento general. En Python, esto se haría típicamente usando un piscina de hilo:

pool =concurrent.futures.ThreadPoolExecutor()futures =pool.map(request_chunk, chunks)

O con asincio:

tasks =[request_chunk_async(chunk) forchunk inchunks]awaitasyncio.gather(*tasks)

Podemos tener muchas lecturas esperando sin hacer nada al mismo tiempo, lo que mejora nuestro rendimiento. Debido a que cada hilo/tarea no está haciendo nada en su mayoría, está bien tener más hilos/tareas de las que su máquina tiene núcleos. Dadas las suficientes solicitudes concurrentes, eventualmente saturará su servicio de almacenamiento, que tiene algunas solicitudes por segundo y objetivos de ancho de banda que intenta cumplir. Pero esos objetivos son altos; por lo general, necesitará muchas máquinas para saturar el servicio de almacenamiento y debería lograr un rendimiento muy alto.

Bibliotecas

Todo lo anterior se aplica esencialmente a cualquier biblioteca que haga IO remoto desde un servicio de almacenamiento de objetos. En el contexto de RAPIDS, NVIDIA KviKIO es notable porque

  1. Automáticamente divide las solicitudes grandes en varias más pequeñas y realiza esas solicitudes simultáneamente.
  2. Puede leer eficientemente en la memoria del host o del dispositivo, especialmente si almacenamiento Directo de GPU está habilitado.
  3. Es rápido.

Como se mencionó en el Anuncio de lanzamiento de RADIDS 24.12, KvikIO puede lograr un rendimiento impresionante al leer desde S3. Echemos un vistazo a algunos puntos de referencia para ver cómo funciona.

Puntos de referencia

Cuando lee un archivo, KvikIO divide esa lectura en lecturas más pequeñas de kvikio.defaults.task_size bytes. Hace esas solicitudes de lectura en paralelo usando un grupo de hilos con kvikio.defaults.num_threads trabajadores. Estos pueden controlarse utilizando las variables de entorno KVIKIO_TASK_SIZE y KVIKIO_NTHREADS, o a través de Python con:

with kvikio.defaults.set_num_threads(num_threads), kvikio.defaults.set_task_size(size):    ...

Ver Configuración de tiempo de ejecución para más.

Este gráfico muestra el rendimiento, en megabits por segundo, de leer una gota de 1 GB S3 a g4dn instancia EC2 en la misma región para varios tamaños de la agrupación de hilos (más alto es mejor).

Un gráfico de barras que muestra el rendimiento de S3 a EC2 para varios números de hilos en el grupo de hilos.
Figura 1. Desde un punto de referencia que lee un archivo de 1 GB de S3 a una instancia de g4dn.xlarge EC2, que tiene un ancho de banda publicado de hasta 25 Gbps. Este es el rendimiento de kvikio.RemoteFile.read para diversos valores de kvikio.defaults.num_threads y un tamaño de tarea de 16 MiB. El rendimiento aumenta a medida que agregamos más hilos y paralelizamos las lecturas, hasta cierto punto.

Menos hilos (menos de cuatro) logran un rendimiento más bajo y tardan más en leer el archivo. Más subprocesos (64, 128, 256) logran un mayor rendimiento al paralelizar las solicitudes al servicio de almacenamiento, que las sirve en paralelo. Hay rendimientos decrecientes e incluso negativos a medida que alcanzamos los límites del servicio de almacenamiento, la red u otros cuellos de botella en nuestro sistema.

Con IO remoto, cada hilo pasa un tiempo inactivo relativamente largo esperando la respuesta, por lo que un mayor número de hilos (en relación con su número de núcleos) podría ser apropiado para su carga de trabajo. Vemos que el rendimiento es más alto entre 64 y 128 hilos en este caso.

Como se muestra en la siguiente figura, el tamaño de la tarea también afecta al rendimiento máximo.

Un rendimiento de mapa de calor de S3 a EC2 para varios tamaños de tareas y recuentos de hilos. El pico se alcanza alrededor de 16 MiB para el tamaño de la tarea y 64 hilos.
Figura 2. De un punto de referencia que lee un archivo de 1 GB de S3 a un g4dn.xgrande EC2 instancia, que tiene un ancho de banda publicado de hasta 25 Gbps. Esto muestra un mapa de calor del rendimiento de kvikio.RemoteFile.read. El eje horizontal muestra el rendimiento para varios tamaños de tarea, mientras que el eje vertical muestra varios recuentos de roscas.

Siempre que el tamaño de la tarea no sea demasiado pequeño (alrededor o por debajo de 4 MiB) o demasiado grande (alrededor o por encima de 128 MiB), entonces obtenemos alrededor de 10 Gbps de rendimiento. Con un tamaño de tarea demasiado pequeño, la sobrecarga de hacer muchas solicitudes HTTP reduce el rendimiento. Con un tamaño de tarea demasiado grande, no obtenemos suficiente concurrencia para maximizar el rendimiento.

KvikIO logra un mayor rendimiento en esta carga de trabajo en comparación con boto3, el SDK de AWS para Python, incluso cuando boto3 se usa en un grupo de subprocesos para ejecutar solicitudes simultáneamente.

Un gráfico de barras que muestra que KvikIO obtiene un mayor rendimiento al leer un blob binario (aproximadamente 9,000 Mbps para KvikIO, en comparación con aproximadamente 2,000 Mbps para Boto3).
Figura 3. De un punto de referencia que lee un 1 GB de S3 a un g4dn.xgrande EC2instancia, que tiene un ancho de banda publicado de hasta 25 Gbps. El punto de referencia KvikIO utilizó 64 hilos y 16 tamaños de tarea MiB. El punto de referencia Boto3 utilizó un ThreadPool para leer muchos fragmentos de 4 MB de bytes en paralelo, lo que una búsqueda de parámetros mostró ser el tamaño de fragmento más rápido para boto3.

Como una carga de trabajo un poco más realista, aunque todavía solo una centrada únicamente en IO, comparamos el rendimiento leyendo un lote de 360 archivos de parquet, cada uno de aproximadamente 128 MB. Esto se ejecutó en un AWS g4dn.12xlarge instancia, que tiene 4 T4 NVIDIA GPU y 48 vCPU.

Un gráfico de barras que muestra que Dask-cuDF obtiene un mayor rendimiento con KvikIO (aproximadamente 20,000 Mbps para KvikIO, en comparación con aproximadamente 5,000 Mbps para Boto3).
Figura 4. De un punto de referencia que lee un conjunto de datos de parquet de S3 a un g4dn.12xgrande EC2instancia, que tiene un ancho de banda publicado de hasta 50 Gbps. El conjunto de datos tenía 360 archivos de Apache Parquet de aproximadamente 128 MB cada uno, para un total de aproximadamente 46 GB. El grupo Dask tenía 4 trabajadores. Estos resultados utilizan cuDF 25.04 que incluirá una optimizaciónpara leer los pies de parquet en paralelo.

Con KvikIO habilitado, los cuatro procesos de trabajo de Dask pueden alcanzar colectivamente casi 20 Gbps de rendimiento de S3 a este único nodo.

Conclusión

A medida que RAPIDS acelera otras partes de su carga de trabajo, IO puede convertirse en un cuello de botella. Si está utilizando el almacenamiento de objetos y está cansado de esperar a que se carguen sus datos, pruebe algunas de las recomendaciones de esta publicación. Háganos saber cómo funcionan las cosas con KvikIO en GitHub. También puede unirse a más de 3.500 miembros de la comunidad RAPIDS Slack para hablar sobre el procesamiento de datos acelerado por GPU.

Recursos relacionados

NVIDIA Blog. T. A. Traducido al español

Artículos relacionados

Scroll al inicio