---
# System prepended metadata

title: 'Funciones básicas para CUDA C/C++, MPI, y todo lo demás   : /'

---

# 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.

