Producto de matrices. Versión tiling

Conceptos que pondremos en práctica

Implementaremos a continuación una versión optimizada del producto de dos matrices, A x B, en la que cada bloque de hilos computa una submatriz o tile de la matrix resultado C. Esto permite reducir el cuello de botella del ancho de banda de la memoria, puesto que varios elementos del bloque amortizan la misma fila de A y columna de B. A su vez, esta localidad de acceso será aprovechada para utilizar la memoria compartida, lo que también nos obligará a realizar algunas sincronizaciones entre hilos dentro de un bloque. La memoria compartida dentro de cada multiprocesador se utilizará para almacenar cada submatriz antes de los cálculos, acelerando el acceso a memoria global. Los objetivos principales son:

  • Aplicar computación por bloques sobre el producto de matrices.
  • Utilizar memoria compartida en la GPU.
  • Aplicar correctamente esquemas de sincronización entre threads dentro de cada bloque.

Como en otras ocasiones, tú eliges el nivel de dificultad. Esta vez, ponemos a tu disposición tres niveles distintos. Elige el que mejor se adapte a tus habilidades:

  • Nivel de di ficultad 1 : Elige el directorio lab2.1-matrixmul.1.
  • Nivel de difi cultad 2 : Escoge el directorio lab2.1-matrixmul.2.
  • Nivel de difi cultad 3 : Elige el directorio lab2.1-matrixmul.3.

 

Cómo acceder a los ficheros fuente

Los tres directorios anteriores se encuentran dentro de tu cuenta de usuario, en la ruta NVIDIA_GPU_Computing_SDK/C/src.

 

Modificaciones a realizar sobre el código CUDA

Paso 1:

Edita la función matrixMul(. . . ) en el fichero matrixmul_kernel.cu para completar la funcionalidad del producto de matrices sobre GPU. El código de la parte del host está completo. No es necesario modificar ningún otro fichero.

  • Primera parte del código:

Determina los valores correctos para los índices de bloque dentro del bucle.

  • Segunda parte del código:

Implementa el producto de matrices dentro de cada bloque.

  • Tercera parte del código:

Almacena los resultados en memoria global.

Paso 2:

Compila y ejecuta el programa de igual forma que hiciste para los ejemplos anteriores. En este caso utilizaremos únicamente matrices de dimensiones 128x128 y 512x512.

Ejecuta el programa (cambia "tamaño" por el valor 128 ó 512 según corresponda):
$> ./matrixmul tamaño

La salida del programa debería ser similar a:

Input matrix file name:
Setup host side environment and launch kernel:
Allocate host memory for matrices M and N.
M:
N:
Allocate memory for the result on host side.
Initialize the input matrix

Copy host memory data to device.
Allocate device memory for results.
Setup kernel execution parameters.
# of threads in a block:
# of blocks in a grid :
Executing the kernel...
Copy result from device to host.
Memory transfer time to/from GPU:
GPU computation time :
GPU processing time :
Check results with those computed by CPU.
Computing reference solution.
CPU Processing time :
CPU checksum:
GPU checksum:
Comparing file lab2.1-matrixmul.bin with lab2.1-matrixmul.gold ...
Check ok? Passed.

Guarda los tiempos de ejecución para cada uno de los dos tamaños de matriz y completa la siguiente tabla:

Tamaño de la matriz Tiempo para las transferencias entre CPU y GPU Tiempo de ejecución
128x128    
512x512    

¿Han mejorado los tiempos en comparación con la anterior implementación del producto de matrices? ¿En qué medida?

La matriz de 512x512 es 16 veces más grande que la de 128x128. ¿Son sus tiempos superiores o inferiores a un factor 16? ¿Por qué?

En este algoritmo del producto de matrices, ¿qué beneficia más a la GPU por la forma que tiene de desplegar el paralelismo, las dimensiones grandes o pequeñas para las matrices de entrada?

Paso 3:

Edita la macro BLOCK_SIZE que encontrarás dentro del fi chero matrixmul.h para probar dos tamaños de bloque distintos y cómo éstos afectan al rendimiento de la GPU sobre matrices de dimensiones 512x512 (es necesario también cambiar el valor asignado a la variable block_size en el fichero matrixmul.cu). A partir de estas dos ejecuciones, rellena la tabla siguiente:

Tamaño de bloque Tiempo para las transferencias entre CPU y GPU Tiempo de ejecución
8x8    
16x16    

¿Qué tamaño de bloque resulta mejor? ¿Por qué? ¿Es posible probar otros tamaños de bloque (6, 12, 24, 32, . . .) ?