Las aplicaciones que corren en GPU procesan conjuntos de datos cada vez más grandes con aceleradores cada vez más rápidos. Según el AI Index 2025 de Stanford, el cómputo de los modelos de IA destacados se duplica más o menos cada cinco meses, y el tamaño de los conjuntos de datos con que se entrenan los LLM se duplica cada ocho. Si el cómputo y los datos crecen juntos, mover los datos desde el almacenamiento sin pérdidas pasa a ser una necesidad para que los aceleradores no se queden esperando.

NVIDIA Magnum IO GPUDirect Storage (GDS) ataca ese problema con una ruta directa entre el almacenamiento compatible y la memoria de la GPU. Así se evita el paso intermedio por la memoria de la CPU y baja el uso de CPU del sistema. GDS tiene soporte de un ecosistema creciente que incluye a muchos de los grandes fabricantes de almacenamiento.

En la capa de aplicación manda Python. A agosto de 2025, casi la mitad de los proyectos nuevos de IA en GitHub estaban escritos principalmente en Python, mientras que la interfaz principal de GDS para aplicaciones es la API en C de cuFile.

NVIDIA KvikIO, una biblioteca de Python y C++ para entrada y salida de archivos de alto rendimiento, cubre esa brecha. Ofrece interfaces de alto nivel, idiomáticas en Python y C++, y controles adicionales sobre cómo se ejecuta cada petición de lectura o escritura.

El tutorial de NVIDIA muestra cómo usar la API de Python de KvikIO para configurar la entrada y salida de almacenamiento, leer datos hacia la memoria de la GPU y confirmar que se activó la ruta acelerada de GDS. También enseña a revisar los ajustes de KvikIO y cuFile que deciden qué camino toma cada operación.

¿Cómo aprovecha KvikIO a GDS?

GDS es la tecnología general que permite mover datos acelerados entre el almacenamiento compatible y la memoria de la GPU. Para almacenamiento basado en sistema de archivos, cuFile es la biblioteca en C y la API con que las aplicaciones y los frameworks acceden a GDS. KvikIO se monta sobre cuFile, con interfaces de más alto nivel en Python y C++ y sus propios controles sobre cómo se ejecutan las peticiones. La figura 1 muestra esas capas y sus dos puntos de selección de ruta, separados entre sí. Por claridad se omiten las fallas de inicialización, las configuraciones no compatibles y el manejo de errores.

Figura 1. Flujo de ejecución exitoso de una lectura con KvikIO

La aplicación entrega una ruta del sistema de archivos y un búfer en la GPU, y KvikIO decide si manda la petición por cuFile o usa su ruta de compatibilidad POSIX, que pasa los datos por la memoria del host. Cuando elige cuFile, KvikIO registra el archivo y emite la operación con el búfer de la GPU como destino.

Después cuFile decide la ruta real de transferencia según el soporte del sistema, el sistema de archivos y su configuración. Puede usar la ruta acelerada de GDS o su propia ruta de compatibilidad, que pasa por la CPU. Por eso, llegar a la API de cuFile confirma que KvikIO eligió cuFile, pero no confirma por sí solo que se haya activado la ruta acelerada de GDS.

Las secciones siguientes recorren cómo configurar el sistema, hacer una lectura con KvikIO y verificar la ruta que tomó.

Requisitos previos

Para seguir el tutorial hace falta lo siguiente.

  • Un sistema Linux con GPU NVIDIA y GPUDirect Storage instalado.
  • Una configuración de almacenamiento y sistema de archivos compatible con GDS.
  • Permiso para crear, leer y escribir archivos en el almacenamiento de destino, y acceso a las herramientas de GDS como gdscheck y gds_stats.

Los requisitos completos de instalación y plataforma están en la documentación de NVIDIA GPUDirect Storage.

El sistema de prueba

NVIDIA probó el tutorial con esta configuración.

  • Un sistema x86_64 con una GPU NVIDIA H100, CUDA 13.3 y cuFile 1.18.
  • Un dispositivo NVMe local formateado en ext4.
  • Una conexión PIX entre la GPU y el NVMe elegidos, o sea que el camino atraviesa como máximo un puente PCIe.
  • IOMMU y ACS desactivados para conservar las transferencias PCIe de par a par.
  • El módulo de kernel nvidia-fs cargado, que es el que habilita la ruta acelerada.

La topología entre GPU y almacenamiento se puede revisar con este comando.

Código
$ nvidia-smi topo -nvme

Paso 1. Instalar NVIDIA KvikIO

Primero se crea un entorno de Python y se instala la versión de KvikIO para CUDA 13. CuPy se instala como dependencia.

Código
$ python3 -m venv kvikio-env
$ source kvikio-env/bin/activate
$ python -m pip install kvikio-cu13

La instalación se verifica así.

Código
$ python -m pip show kvikio-cu13 cupy-cuda13x

Paso 2. Revisar la configuración

Antes de cualquier lectura conviene revisar los ajustes activos de KvikIO y confirmar que el sistema reconoce los componentes de GDS. En la API de valores por defecto de KvikIO, el ajuste de modo de compatibilidad se llama compat_mode, y su atributo de nombre informa el modo activo como AUTO, ON u OFF. Los comandos siguientes muestran las versiones cargadas de KvikIO y cuFile, el modo de compatibilidad activo y el tamaño mínimo de petición que KvikIO manda a cuFile.

Código
import kvikio
import kvikio.cufile_driver as cufile_driver
import kvikio.defaults as defaults

kvikio.__version__
cufile_driver.libcufile_version()
defaults.get("compat_mode").name
defaults.get("gds_threshold")

En el sistema de prueba, KvikIO informa el modo de compatibilidad AUTO y un umbral de GDS de 16 KiB. AUTO le indica a KvikIO que intente usar cuFile y, si no está disponible, recurra a su backend POSIX. Las lecturas más chicas que el umbral van directo por la ruta POSIX de KvikIO. Estos ajustes también se controlan con KVIKIO_COMPAT_MODE y KVIKIO_GDS_THRESHOLD. La lista completa está en los ajustes de ejecución de KvikIO.

Luego se revisa la plataforma GDS y la configuración de cuFile.

Código
$ /usr/local/cuda-13.3/gds/tools/gdscheck.py -p 2>&1 | less -R

La salida lista los componentes de GDS instalados, los sistemas de archivos compatibles, las GPU detectadas y la configuración activa de cuFile. La figura 2 muestra cómo se revisó la configuración de KvikIO y GDS usada en el tutorial.

Figura 2. Una sesión de terminal que revisa la versión de KvikIO, la versión de cuFile, el modo de compatibilidad y el umbral de GDS, y luego corre un diagnóstico de GDS

Los ajustes de KvikIO también se pueden cambiar desde Python. Por ejemplo, defaults.set acepta un diccionario para cambiar varios ajustes de una vez, o un nombre y un valor para cambiar uno solo.

Código
defaults.set({"task_size": 1024**3, "num_threads": 8})
defaults.set("compat_mode", kvikio.CompatMode.OFF)

Paso 3. Leer un archivo hacia la memoria de la GPU

Antes de correr el ejemplo en Python hay que crear el archivo de entrada una sola vez. Este comando escribe un archivo de 1 GiB con datos aleatorios usando entrada y salida directa, y lo vacía al almacenamiento.

Código
$ dd if=/dev/urandom \
    of=/mnt/nvme1/kvikio-tutorial/kvikio-example.bin \
    bs=1M count=1024 \
    oflag=direct conv=fsync \
    status=progress

El programa siguiente reserva un arreglo de CuPy de 1 GiB en la memoria de la GPU y se lo entrega a KvikIO como búfer de destino.

Código
from pathlib import Path
import cupy as cp
import kvikio

file_path = Path(
    "/mnt/nvme1/kvikio-tutorial/kvikio-example.bin"
)

# Allocate a GPU buffer large enough to hold the file.
gpu_buffer = cp.empty(
    file_path.stat().st_size,
    dtype=cp.uint8,
)

# Read the file into the GPU buffer.
with kvikio.CuFile(file_path, "r") as file:
    bytes_read = file.read(gpu_buffer)

# Validate that 1 GiB was read.
assert bytes_read == gpu_buffer.nbytes

El administrador de contexto abre el archivo y completa la lectura hacia la memoria de la GPU. El valor de retorno confirma que se leyó el archivo completo, pero no dice qué ruta de datos se usó.

¿Cómo saber qué ruta tomó la lectura?

Una forma de validar la ruta de datos en cualquier aplicación basada en cuFile es mirar las estadísticas por proceso del runtime de cuFile. Se activan con CUFILE_STATS_LEVEL=3 y se revisan con gds_stats mientras el proceso siga vivo.

En este ejemplo con KvikIO, la variable de entorno limita el ajuste a una sola ejecución, aunque también se puede fijar en cufile.json.

Código
$ CUFILE_STATS_LEVEL=3 python3 -i read_with_kvikio.py

Correr Python en modo interactivo mantiene vivo el proceso después de la lectura, y eso permite que gds_stats revise sus contadores acumulados.

La figura 3 muestra ese flujo. En la salida por GPU, nvfs cuenta las operaciones que pasaron por el módulo de kernel nvidia-fs, y posix cuenta las que resolvió la ruta de compatibilidad de cuFile.

Figura 3. Sesión de terminal con las estadísticas de cuFile de una lectura con KvikIO, que muestra 256 lecturas nvfs por un total de 1.024 MiB

El tamaño de tarea por defecto de KvikIO, 4 MiB, divide la petición de 1 GiB en 256 operaciones de lectura. Las estadísticas por GPU informan n=256, nvfs=256 y posix=0, o sea que las 256 operaciones usaron la ruta de nvidia-fs.

Paso 5. Comparar las rutas disponibles

La misma lectura se puede repetir con distintos ajustes para ver las dos capas de control. Primero se obliga a KvikIO a usar cuFile.

Código
$ CUFILE_STATS_LEVEL=3 \
    KVIKIO_COMPAT_MODE=off \
    python3 -i read_with_kvikio.py

En el sistema de prueba, gds_stats informa operaciones nvfs, lo que confirma que cuFile eligió la ruta acelerada. Después se fuerza a cuFile a usar su ruta de compatibilidad.

Código
$ CUFILE_STATS_LEVEL=3 \
    KVIKIO_COMPAT_MODE=off \
    CUFILE_FORCE_COMPAT_MODE=true \
    python3 -i read_with_kvikio.py

KvikIO sigue llamando a cuFile, pero ahora las operaciones aparecen bajo posix y no bajo nvfs. Por último, el modo de compatibilidad de KvikIO se salta cuFile por completo.

Código
$ KVIKIO_COMPAT_MODE=on \
    python3 read_with_kvikio.py

En ese caso KvikIO hace la lectura con su backend POSIX, que pasa por la CPU, y cuFile no registra la operación.

Paso 6. La configuración de cuFile en una lectura con KvikIO

Una vez que KvikIO elige la ruta de cuFile, cuFile aplica otro conjunto de controles que deciden cómo se ejecuta la petición. Esos controles se definen en cufile.json y funcionan con independencia de los ajustes de KvikIO.

División de peticiones y ejecución en paralelo

Tanto KvikIO como cuFile pueden dividir una operación grande en partes más chicas. KvikIO aplica primero KVIKIO_TASK_SIZE, y KVIKIO_NTHREADS controla cómo se reparten esas tareas. Cada tarea que va por la ruta de cuFile se convierte en una petición de cuFile separada. Cuando cuFile recibe una de esas peticiones, su configuración de ejecución decide si se puede volver a dividir y procesar con el conjunto interno de hilos de cuFile.

Una lectura de 1 GiB, paso a paso

La configuración usada tiene cuatro hilos de trabajo de cuFile, max_request_parallelism en cuatro y min_io_threshold_size_kb en 8192 (8 MiB). Con el tamaño de tarea por defecto de KvikIO, de 4 MiB, una lectura de 1 GiB se convierte en 256 peticiones a cuFile. cuFile procesa cada petición de 4 MiB como una sola pieza. Esa es la ejecución que produjo nvfs=256 en las estadísticas de más arriba.