- A medida que la ley de Moore llega a sus límites, el hardware ha evolucionado desde acelerar un solo núcleo hacia un enfoque centrado en múltiples núcleos y procesamiento paralelo
- El procesamiento paralelo se divide en varias formas, como paralelismo de datos, paralelismo de modelo y paralelismo de canalización, y se usa de forma combinada en los sistemas modernos de aprendizaje profundo
- La paralelización ocurre en varias capas, como SIMD (paralelismo de datos a nivel de instrucción), paralelismo de hilos/núcleos y paralelismo masivo en GPU
- Las lenguajes, bibliotecas y herramientas para procesamiento paralelo son, en su mayoría, extensiones “agregadas” a lenguajes secuenciales existentes, por lo que está ganando atención la tendencia de integrar el paralelismo de forma nativa en el lenguaje (como Mojo)
- La optimización práctica del rendimiento, como la optimización del uso compartido de líneas de caché (interacciones innecesarias), la partición eficiente de memoria y la vectorización automática, es una tarea clave
Motivación del paralelismo y evolución del hardware
- Al principio, el rendimiento mejoraba de forma natural gracias a la miniaturización de transistores y al aumento de la frecuencia de reloj, pero se alcanzaron límites físicos por las restricciones térmicas y de fabricación
- Después, la arquitectura multinúcleo se volvió el estándar, con decenas o incluso cientos de núcleos en una sola CPU
Formas generales de paralelismo
- Paralelismo de datos: aplicar la misma operación a muchos datos al mismo tiempo (por ejemplo, suma de vectores)
- Paralelismo de modelo: distribuir un modelo entre varios dispositivos
- Paralelismo de canalización: dividir el cálculo en varias etapas para que cada una opere simultáneamente
SIMD (Single Instruction, Multiple Data) y vectorización
- SIMD es un método para procesar varios datos (vectores) con una sola instrucción, y es compatible con varias ISA como ARM NEON y x86 SSE/AVX
- Mediante intrinsics de C/C++ se puede controlar explícitamente la operación vectorial; el compilador también admite vectorización automática, aunque con limitaciones
- En la práctica, se procesa repetidamente según la longitud del vector y luego los datos restantes se corrigen con operaciones escalares
Paralelización en CPU
- Se aprovechan los hilos para ejecutar en paralelo sobre múltiples núcleos, con soporte del scheduler del SO y APIs según el lenguaje
- Como el costo de crear y destruir hilos es alto, resulta eficiente dividir el trabajo con una cantidad adecuada de hilos en relación con el tamaño de los datos (similar al número de núcleos)
- Es importante optimizar el 'false sharing' de líneas de caché (degradación de rendimiento cuando distintos hilos acceden a variables independientes dentro de la misma línea de caché); pueden usarse herramientas como
std::hardware_destructive_interference_size de C++17
- Es necesario aplicar padding/alineación para que cada hilo escriba en una región de datos separada
Paralelización en GPU
- La GPU está especializada en procesamiento paralelo masivo de datos mediante miles de núcleos pequeños
- CUDA/OpenCL: ejecuta funciones kernel en unidades de decenas hasta decenas de miles de hilos/bloques; internamente usa el modelo SIMT (Single Instruction, Multiple Threads)
- El funcionamiento por unidades de work group/warp y la minimización de la divergencia de ramas (branch divergence) son extremadamente importantes para el rendimiento
- Jerarquía de memoria: se requiere optimización por niveles entre registros de hilo, memoria compartida por bloque y memoria global total
Triton: DSL de kernels GPU basado en Python
- Triton es un DSL integrado en Python, con soporte para compilación JIT y múltiples backends (MLIR/LLVM/PTX, etc.)
- Permite escribir código kernel en Python de alto nivel, con soporte para paralelización, partición y enmascaramiento automáticos
- Logra entre 75% y 90% del rendimiento de NVIDIA cuDNN, mientras reduce drásticamente la complejidad de desarrollo
Paralelismo nativo en el lenguaje: Mojo
- Mojo es un nuevo lenguaje creado por Chris Lattner, desarrollador de LLVM/MLIR, que admite paralelismo y compilación especializada para hardware a nivel de lenguaje
- Los tipos vectoriales SIMD, las funciones vectorizadas y la distinción entre memoria de host/dispositivo muestran que el paralelismo está integrado en el sistema de tipos y en la estructura del lenguaje
- Incluso los bucles con estilo Python pueden vectorizarse automáticamente, lo que permite obtener rendimiento sin control explícito de bajo nivel
Conclusión y perspectivas
- La programación paralela moderna surge de la combinación de distintos hardwares y modelos de paralelismo, y el soporte del propio lenguaje se vuelve cada vez más importante
- Con el auge de lenguajes y herramientas de paralelismo de nueva generación como Mojo, Triton y JAX, la paralelización está evolucionando para ser más intuitiva y productiva
- La programación paralela puede maximizar el rendimiento real solo cuando la arquitectura del hardware, la optimización de memoria y el soporte del lenguaje se combinan de forma orgánica
Aún no hay comentarios.