CUDA y GPU remota

CUDA es bueno para todos, siempre que tenga a mano una tarjeta de video de Nvidia. Pero, ¿qué hacer cuando no hay una tarjeta gráfica Nvidia en su computadora portátil favorita? ¿O necesita realizar el desarrollo en una máquina virtual?


Intentaré considerar en este artículo una solución como el marco rCUDA (Remote CUDA), que ayudará cuando hay una tarjeta de video Nvidia, pero no está instalada en la máquina en la que se supone que se inician las aplicaciones CUDA. Para aquellos que estén interesados, bienvenidos a cat.


TLDR

rCUDA (CUDA remota): un marco que implementa la API de CUDA, lo que le permite utilizar una tarjeta de video remota. Está en una versión beta funcional, disponible solo bajo Linux. El objetivo principal de rCUDA es la compatibilidad total con la API de CUDA, no necesita modificar su código de ninguna manera, solo configure variables de entorno especiales.


¿Qué es rCUDA?


rCUDA (Remote CUDA) es un marco que implementa la API de CUDA, lo que le permite utilizar una tarjeta de video ubicada en la máquina remota para la computación CUDA sin realizar ningún cambio en su código. Desarrollado en la Universidad Politécnica de Valencia ( equipo rcuda ).


Limitaciones


Actualmente solo se admiten sistemas GNU / Linux, sin embargo, los desarrolladores prometen compatibilidad con Windows en el futuro. La versión actual de rCUDA, 18.03beta, es compatible con CUDA 5-8, es decir, CUDA 9 no es compatible. Los desarrolladores declararon compatibilidad total con la API de CUDA, con la excepción de los gráficos.


Posibles casos de uso


  1. Ejecutar aplicaciones CUDA en una máquina virtual cuando reenvía una tarjeta de video es inconveniente o imposible, por ejemplo, cuando la tarjeta de video está ocupada por un host o cuando hay más de una máquina virtual.
  2. Portátil sin una tarjeta gráfica discreta.
  3. El deseo de usar múltiples tarjetas de video (agrupamiento). Teóricamente, puede usar todas las tarjetas de video disponibles en el equipo, incluso en forma conjunta.

Instrucciones breves


Configuración de prueba


Las pruebas se llevaron a cabo en la siguiente configuración:


Servidor:
Ubuntu 16.04, GeForce GTX 660


Cliente:
Una máquina virtual con Ubuntu 16.04 en una computadora portátil sin una tarjeta gráfica discreta.


Obteniendo rCUDA


La etapa más difícil. Desafortunadamente, en este momento, la única forma de obtener su copia de este marco es completar el formulario de solicitud correspondiente en el sitio web oficial. Sin embargo, los desarrolladores prometen responder dentro de 1-2 días. En mi caso, me enviaron una distribución el mismo día.


Instalar CUDA


Primero debe instalar el kit de herramientas CUDA en el servidor y el cliente (incluso si el cliente no tiene una tarjeta de video nvidia). Para hacer esto, puede descargarlo desde el sitio oficial o usar el repositorio. Lo principal es utilizar una versión no superior a 8. En este ejemplo, se utiliza el instalador .run del sitio oficial .


chmod +x cuda_8.0.61_375.26_linux.run ./cuda_8.0.61_375.26_linux.run 

Importante! En el cliente, debe negarse a instalar el controlador nvidia. Por defecto, el Kit de herramientas de CUDA estará disponible en / usr / local / cuda /. Instale muestras de CUDA, las necesitará.


Instalar rCUDA


Descomprimiremos el archivo recibido de los desarrolladores en nuestro directorio de inicio en el servidor y en el cliente.


 tar -xvf rCUDA*.tgz -C ~/ mv ~/rCUDA* ~/rCUDA 

Debe realizar estas acciones tanto en el servidor como en el cliente.


Inicio del demonio rCUDA en el servidor


 export PATH=$PATH/usr/local/cuda/bin export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/cuda/lib64:/home/<XXX>/rCUDA/lib/cudnn cd ~/rCUDA/bin ./rCUDAd 

Reemplace <XXX> con su nombre de usuario. Use ./rCUDAd -iv si desea ver resultados detallados.


Configuración del cliente


Abramos el terminal en el cliente, en el que ejecutaremos el código CUDA en el futuro. En el lado del cliente, necesitamos "reemplazar" las bibliotecas CUDA estándar con las bibliotecas rCUDA, para lo cual agregamos las rutas apropiadas a la variable de entorno LD_LIBRARY_PATH. También necesitamos especificar el número de servidores y sus direcciones (en mi ejemplo, será uno).


 export PATH=$PATH/usr/local/cuda/bin export LD_LIBRARY_PATH=/home/<XXX>/rCUDA/lib/:$LD_LIBRARY_PATH export RCUDA_DEVICE_COUNT=1 #    (),     export RCUDA_DEVICE_0=<IP  >:0 #     

Montaje y lanzamiento


Intentemos construir y ejecutar algunos ejemplos.


Ejemplo 1


Comencemos con un ejemplo simple de deviceQuery que simplemente muestra la configuración de CUDA para un dispositivo compatible, es decir, en nuestro caso, el GTX660 remoto.


 cd <YYY>/NVIDIA_CUDA-8.0_Samples/1_Utilities/deviceQuery make EXTRA_NVCCFLAGS=--cudart=shared 

Importante! Sin EXTRA_NVCCFLAGS = - cudart = compartido, el milagro no funcionará
Reemplace <YYY> con la ruta que especificó para las muestras CUDA al instalar CUDA.


Ejecute el ejemplo ensamblado:


 ./deviceQuery 

Si hiciste todo correctamente, el resultado será algo como esto:


Resultado
 ./deviceQuery Starting... CUDA Device Query (Runtime API) version (CUDART static linking) Detected 1 CUDA Capable device(s) Device 0: "GeForce GTX 660" CUDA Driver Version / Runtime Version 9.0 / 8.0 CUDA Capability Major/Minor version number: 3.0 Total amount of global memory: 1994 MBytes (2090991616 bytes) ( 5) Multiprocessors, (192) CUDA Cores/MP: 960 CUDA Cores GPU Max Clock rate: 1072 MHz (1.07 GHz) Memory Clock rate: 3004 Mhz Memory Bus Width: 192-bit L2 Cache Size: 393216 bytes Maximum Texture Dimension Size (x,y,z) 1D=(65536), 2D=(65536, 65536), 3D=(4096, 4096, 4096) Maximum Layered 1D Texture Size, (num) layers 1D=(16384), 2048 layers Maximum Layered 2D Texture Size, (num) layers 2D=(16384, 16384), 2048 layers Total amount of constant memory: 65536 bytes Total amount of shared memory per block: 49152 bytes Total number of registers available per block: 65536 Warp size: 32 Maximum number of threads per multiprocessor: 2048 Maximum number of threads per block: 1024 Max dimension size of a thread block (x,y,z): (1024, 1024, 64) Max dimension size of a grid size (x,y,z): (2147483647, 65535, 65535) Maximum memory pitch: 2147483647 bytes Texture alignment: 512 bytes Concurrent copy and kernel execution: Yes with 1 copy engine(s) Run time limit on kernels: Yes Integrated GPU sharing Host Memory: No Support host page-locked memory mapping: Yes Alignment requirement for Surfaces: Yes Device has ECC support: Disabled Device supports Unified Addressing (UVA): Yes Device PCI Domain ID / Bus ID / location ID: 0 / 1 / 0 Compute Mode: < Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) > deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 9.0, CUDA Runtime Version = 8.0, NumDevs = 1, Device0 = GeForce GTX 660 Result = PASS 

Lo más importante que deberíamos ver:


Dispositivo 0 = GeForce GTX 660
Resultado = PASA

Genial Logramos construir y ejecutar la aplicación CUDA en una máquina sin una tarjeta gráfica discreta, utilizando para este propósito una tarjeta de video instalada en un servidor remoto.


Importante! Si el resultado de la aplicación comienza con líneas del formulario:


 mlock error: Cannot allocate memory rCUDA warning: 1007.461 mlock error: Cannot allocate memory 

significa que es necesario agregar las siguientes líneas al archivo "/etc/security/limits.conf" en el servidor y en el cliente:


 * hard memlock unlimited * soft memlock unlimited 

Por lo tanto, permitirá a todos los usuarios (*) memoria de bloqueo ilimitada (ilimitada) (memlock). Sería aún mejor reemplazar * con el usuario deseado, y en lugar de elegir de forma ilimitada los derechos menos gordos.


Ejemplo 2


Ahora intentemos algo más interesante. Probamos la implementación del producto escalar de vectores usando memoria compartida y sincronización ("Tecnología CUDA en Ejemplos" Sanders J. Kendrot E. 5.3.1).


En este ejemplo, calculamos el producto escalar de dos vectores de dimensión 33 * 1024, comparando la respuesta con el resultado obtenido en la CPU.


dotProd.cu
 #include <stdio.h> #define imin(a,b) (a<b?a:b) const int N = 33 * 1024; const int threadsPerBlock = 256; const int blocksPerGrid = imin(32, (N+threadsPerBlock-1) / threadsPerBlock); __global__ void dot(float* a, float* b, float* c) { __shared__ float cache[threadsPerBlock]; int tid = threadIdx.x + blockIdx.x * blockDim.x; int cacheIndex = threadIdx.x; float temp = 0; while (tid < N){ temp += a[tid] * b[tid]; tid += blockDim.x * gridDim.x; } // set the cache values cache[cacheIndex] = temp; // synchronize threads in this block __syncthreads(); // for reductions, threadsPerBlock must be a power of 2 // because of the following code int i = blockDim.x/2; while (i != 0){ if (cacheIndex < i) cache[cacheIndex] += cache[cacheIndex + i]; __syncthreads(); i /= 2; } if (cacheIndex == 0) c[blockIdx.x] = cache[0]; } int main (void) { float *a, *b, c, *partial_c; float *dev_a, *dev_b, *dev_partial_c; // allocate memory on the cpu side a = (float*)malloc(N*sizeof(float)); b = (float*)malloc(N*sizeof(float)); partial_c = (float*)malloc(blocksPerGrid*sizeof(float)); // allocate the memory on the gpu cudaMalloc((void**)&dev_a, N*sizeof(float)); cudaMalloc((void**)&dev_b, N*sizeof(float)); cudaMalloc((void**)&dev_partial_c, blocksPerGrid*sizeof(float)); // fill in the host memory with data for(int i=0; i<N; i++) { a[i] = i; b[i] = i*2; } // copy the arrays 'a' and 'b' to the gpu cudaMemcpy(dev_a, a, N*sizeof(float), cudaMemcpyHostToDevice); cudaMemcpy(dev_b, b, N*sizeof(float), cudaMemcpyHostToDevice); dot<<<blocksPerGrid, threadsPerBlock>>>(dev_a, dev_b, dev_partial_c); // copy the array 'c' back from the gpu to the cpu cudaMemcpy(partial_c,dev_partial_c, blocksPerGrid*sizeof(float), cudaMemcpyDeviceToHost); // finish up on the cpu side c = 0; for(int i=0; i<blocksPerGrid; i++) { c += partial_c[i]; } #define sum_squares(x) (x*(x+1)*(2*x+1)/6) printf("GPU - %.6g \nCPU - %.6g\n", c, 2*sum_squares((float)(N-1))); // free memory on the gpu side cudaFree(dev_a); cudaFree(dev_b); cudaFree(dev_partial_c); // free memory on the cpu side free(a); free(b); free(partial_c); } 

Construye y ejecuta:


 /usr/local/cuda/bin/nvcc --cudart=shared dotProd.cu -o dotProd ./dotProd 

Este resultado nos dice que todo está bien con nosotros:


GPU - 2.57236e + 13
CPU - 2.57236e + 13

Ejemplo 3


Ejecute otra prueba estándar CUDA-matrixMulCUBLAS (multiplicación de matriz).


 cd < YYY>/NVIDIA_CUDA-8.0_Samples/0_Simple/matrixMulCUBLAS make EXTRA_NVCCFLAGS=--cudart=shared ./matrixMulCUBLAS 

Resultado

[Matrix Multiply CUBLAS] - Comenzando ...
Dispositivo GPU 0: "GeForce GTX 660" con capacidad de cálculo 3.0


Matriz A (640,480), Matriz B (480,320), Matriz C (640,320)
Resultado de la computación usando CUBLAS ... hecho.
Rendimiento = 436.24 GFlop / s, Tiempo = 0.451 mseg, Tamaño = 196608000 Ops
Resultado de la computación utilizando la CPU del host ... hecho
Comparación de CUBLAS Matrix Multiply con resultados de CPU: PASS


NOTA: Las muestras CUDA no están destinadas a mediciones de rendimiento. Los resultados pueden variar cuando GPU Boost está habilitado.


Interesante para nosotros:


Rendimiento = 436.24 GFlop / s,
Comparación de CUBLAS Matrix Multiply con resultados de CPU: PASS

Seguridad


No encontré mención de ningún método de autorización en la documentación de rCUDA. Creo que en este momento lo más simple que se puede hacer es abrir el acceso al puerto deseado (8308) solo desde una dirección específica.


Usando iptables, se verá así:


 iptables -A INPUT -m state --state NEW -p tcp -s < > --dport 8308 -j ACCEPT 

Por lo demás, dejo el problema de seguridad más allá del alcance de esta publicación.


Fuentes y enlaces

[1] http://www.rcuda.net/pub/rCUDA_guide.pdf
[2] http://www.rcuda.net/pub/rCUDA_QSG.pdf
[3] C. Reaño, F. Silla, G. Shainer y S. Schultz, "Las GPU locales y remotas funcionan de manera similar con EDR 100G InfiniBand", en las actas de la Conferencia Internacional de Middleware, Vancouver, BC, Canadá, diciembre de 2015.
[4] C. Reaño y F. Silla, "Una comparación de rendimiento de los marcos de virtualización de GPU remota CUDA", en los procedimientos de la Conferencia Internacional sobre Computación en Clúster, Chicago, IL, EE. UU., Septiembre de 2015.

Source: https://habr.com/ru/post/es416127/


All Articles