# Funciones básicas para CUDA C/C++, MPI, y todo lo demás : /
_Debimos llamarlo cheatsheet en este caso, porque chuletita se oía algo mal._
## Modificadores para declaración de funciones:
Declaración de Kernel (Se lanza en código ded CPU, se corre en la GPU, o para paralelismo dinámico)
~~~
__global__ nombreFuncion()
~~~
Declaración de función solo en dispositivo (Se lanza en GPU y se corre en GPU)
~~~
__device__ numbreFuncion()
~~~
## Operaciones básicas de memoria:
Apartar el espacio en memoria de la GPU para un apuntador.
~~~
cudaMalloc(&d_A.elements, size);
~~~
Copiar datos desde un espacio de memoria a otro (en espacio de CPU o GPU). El último parámetro determina la dirección en la que se realiza la copia:
- cudaMemcpyHostToDevice - de CPU a GPU
- cudaMemcpyDeviceToHost - de GPU a CPU
- cudaMemcpyDeviceToDevice - de GPU a GPU
~~~
cudaMemcpy(d_A.elements, A.elements, size, cudaMemcpyHostToDevice);
~~~
Para liberar la memoría apartada con cudaMallocHost
~~~
__host__ __device__ cudaError_t cudaMalloc ( void** devPtr, size_t size )
~~~
## Kernels:
Lanzamiento de Kernel especificando la configuración de ejecución, en este caso con los dos parámetros obligatorios (Número de bloques, número de hilos por bloque).
~~~
MatMulKernel<<<dimGrid,dimBlock>>>(d_A, d_B, d_C);
~~~
Recordar que se pueden usar variables del tipo dim3 para establecer los bloques y los hilos por bloque hasta en tres dimensiones. Por ejemplo:
~~~
dim3 dimBlock
dim3 dimGrid
~~~
Recordar la "fórmula" para calcular las hilos y los bloques de tal suerte que puedan cubrir un vector o matriz con el menor desperdicio de memoria (N tamaño del vector, M número de hilos por bloque):
~~~
<<<(N + M-1) / M,M>>>
~~~
## Otros:
Buscar los ejemplos en el github de nvidia:
https://github.com/NVIDIA/cuda-samples
## Streams
~~~
cudaStream_t stream1, stream2, stream3, stream4 ;
cudaStream_t stream[nGPU];
~~~
~~~
cudaStreamCreate(&stream1);
cudaStreamDestroy(stream1);
~~~
~~~
for (int i = 0; i < 2; ++i)
cudaMemcpyAsync(inputDevPtr + i * size, hostPtr + i * size,
size, cudaMemcpyHostToDevice, stream[i]);
for (int i = 0; i < 2; ++i)
MyKernel<<<100, 512, 0, stream[i]>>>
(outputDevPtr + i * size, inputDevPtr + i * size, size);
for (int i = 0; i < 2; ++i)
cudaMemcpyAsync(hostPtr + i * size, outputDevPtr + i * size,
size, cudaMemcpyDeviceToHost, stream[i]);
~~~
## Guane y otras plataformas
De acuerdo con Luis Alejandro, usar la dirección 167.249.40.26 en lugar de toctoc.sc3.uis.edu.co puede ayudar a disminuir problemas de latencia, los que a su vez generan incidentes como el que ocurrió en clase. Creo que igual es mejor tratar de buscar alternativas, al menos para compilar y hacer pruebas iniciales.
## MPI
### Generalidades
Para inicializar el paso de mensajes:
```c
MPI_Init(&argc, &argv);
```
Para hallar el número máximo de tareas (o elementos de procesamiento, etc). MPI_COMM_WORLD es el communicator, en este caso, todos los nodos posibles:
```c
MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
```
Para hallar la tarea activa:
```c
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
```
Para recibir un mensaje. Buffer se refiere al apuntador a la memoria donde se recibirán los datos, count es el número de elementos a recibir, MPI_CHAR es el tipo de datos a recibir (lista más adelante). Los siguientes parámetros son el origen del mensaje, el tag y el communicator.
```c
MPI_Recv(&buffer, count, MPI_CHAR, MPI_ANY_SOURCE, 10, MPI_COMM_WORLD, &status);
```
Para enviar un mensaje. El primer parámetro es el apuntador a la memoria donde están los datos a enviar. El segundo es el número de elementos a recibir, MPI_CHAR es el tipo de datos a recibir. Los siguientes parámetros son el origen del mensaje, el tag y el communicator.
```c
MPI_Send(buffer, size, MPI_CHAR, 0, 10, MPI_COMM_WORLD);
```
### Definiendo Structs dentro de MPI
En el caso de querer hacer las cosas bien y no mandar 21 `MPI_Send` para poder pasar un struct, se deben definir los `struct` en términos de MPI. En este caso lo estamos definiendo un vector de 3 dimensiones con tipos `double`.
En este caso lo estamos definiendo un vector de 3 dimensiones con tipos `double`.
```c
typedef struct vector3d {
double x;
double y;
double z;
} vector3d;
```
Lo primero está en definir la cantidad de campos presentes en el `struct`, al igual que el largo de esots.
```c
const int n_items_vector = 3; // Tenemos 3 campos, entonces 3 para n_items
int blocklength_vector[3] = {1, 1, 1}; // Cantidad de elementos dentro del campo
```
Seguidamente, definimos los campos de nuestro `struct` en términos de tipos de MPI, en este caso, nuestro vector estará compuesto de `MPI_DOUBLE`.
```c
MPI_Datatype types_vector[3] = {MPI_DOUBLE, MPI_DOUBLE, MPI_DOUBLE};
```
Luego, tenemos que darle un nombre al `struct`, se recomienda usar el prefijo `MPI_` seguido del nombre del struct.
```c
MPI_Datatype MPI_vector_3d;
```
Usando `MPI_Aint`, definimos los offsets con tamaño del número de campos del `struct`. Los nombres de los campos se definen pasando a cada uno de los espacios de `offsets_vector`, la función `offsetof` dando como parámetros el `struct` definido originalmente, seguido del nombre del campo a registrar.
```c
MPI_Aint offsets_vector[3];
offsets_vector[0] = offsetof(vector3d, x);
offsets_vector[1] = offsetof(vector3d, y);
offsets_vector[3] = offsetof(vector3d, z);
```
Finalmente, es cuestión de crear el `struct` usando la función `MPI_Type_create_struct` y confirmar usando `MPI_Type_commit`.
```c
MPI_Type_create_struct(
n_items_vector,
blocklength_vector,
offsets_vector,
types_vector,
&MPI_vector_3d
);
MPI_Type_commit(&MPI_vector_3d);
```
Para compilar en c y c++ con MPI
```
mpicc -o salida.o programa.c
mpic++ -o salida.o programa.cpp
```
Para correr con MPI
```
mpirun -n <numero de tareas> salida.o
```
Y más, mucho más, por llegar.
Mucho, mucho más.