Crear un bot para participar en la mini copa AI. Experiencia GPU


Continuación del artículo 1 y del artículo 2 .


A continuación, debajo del corte, hablaré sobre la experiencia del autor en el uso de la GPU para los cálculos, incluso como parte de la creación de un bot para participar en la mini copa AI. Pero más bien, este es un ensayo sobre el tema de GPU.


- Tu nombre es mágico ...
-¿Sabes, Joel? ... La magia se va ...


En la infancia, hablamos de la edad en que la química todavía no está en curso en la escuela o apenas comienza a tener lugar, el autor estaba fascinado por la reacción de ardor, sucedió que sus padres no interfirieron con él y el páramo de Moscú cerca de la casa ocasionalmente se iluminó con destellos de la actividad de varios niños, un cohete casero en negro pólvora, caramelo de nitrato de azúcar, etc. Dos circunstancias limitaron las fantasías de los niños: la descomposición de nitroglicerina en un laboratorio casero con un techo sibilante de ácidos y el traslado a una sala de policía en un intento de obtener productos químicos en una de las empresas de defensa generosamente esparcidas por el área metropolitana de Aviamotornaya.


Y luego apareció una escuela de física con computadoras yamaha msx, una calculadora MK programable en casa, y no había tiempo para la química. Los intereses del niño se han trasladado a las computadoras. Y lo que le faltaba al autor desde el primer contacto con la computadora fue la reacción ardiente, sus programas estaban ardiendo, no había esta sensación de poder natural. Se podía ver el proceso de optimización de los cálculos en los juegos, pero en ese momento el autor no sabía cómo reemplazar el cálculo de sin () con la tabla de valores de esta función, no había Internet ...


Por lo tanto, el autor pudo tener una sensación de alegría de la potencia informática, la grabación limpia, utilizo la GPU en los cálculos.


En un habr hay algunos buenos artículos sobre cálculos en GPU. También hay muchos ejemplos en Internet, por lo que se decidió escribir el sábado por la mañana sobre sentimientos personales y es posible empujar a otros hacia el paralelismo masivo.


Comencemos con formas simples. La informática de GPU admite varios marcos, pero los más famosos son NVIDIA CUDA y OpenCL. Tomaremos CUDA e inmediatamente tendremos que reducir nuestro conjunto de lenguajes de programación a C ++. Hay bibliotecas para conectarse a CUDA en otros lenguajes de programación, por ejemplo, ALEA GPU en C #, pero este es más bien el tema de un artículo de revisión por separado.


Como no podían fabricar un automóvil de masa con un motor a reacción al mismo tiempo, aunque algunos de sus indicadores son más altos que los de un motor de combustión interna, no siempre es posible realizar cálculos paralelos en problemas reales. La aplicación principal para la computación paralela: necesita una tarea que contenga algún elemento de carácter masivo, multiplicidad. En nuestro caso de crear un bot, una red neuronal (muchas neuronas, conexiones neuronales) cae bajo la masa y una población de bots (el cálculo de la dinámica del movimiento, las colisiones de cada bot toman algo de tiempo, si los bots son de 300-1000, entonces el procesador central se rinde y usted observará lentamente latente de su programa, como largas pausas entre cuadros de visualización).


La mejor opción de masa es cuando cada elemento de los cálculos no depende del resultado de los cálculos en otro elemento de la lista, por ejemplo, la simple tarea de ordenar una matriz ya está cubierta de todo tipo de trucos, ya que la posición del número en la matriz depende de otros números y no se puede tomar en la frente en un ciclo paralelo . Para simplificar la redacción: el primer signo de un carácter de masa exitoso es que si no necesita cambiar la posición de un elemento en la matriz, puede realizar cálculos libremente sobre él, tomar los valores de otros elementos para esto, pero no moverlo de su lugar. Algo así como un cuento de hadas: no cambies el orden de los elementos, de lo contrario, la GPU se convertirá en una calabaza.


En los lenguajes de programación modernos, hay construcciones que se pueden ejecutar en paralelo en varios núcleos de un procesador central o hilos lógicos y se usan ampliamente, pero el autor enfoca al lector en el paralelismo masivo, cuando el número de módulos en ejecución excede cientos o miles de unidades.


Aparecieron los primeros elementos de estructuras paralelas: un ciclo paralelo . Para la mayoría de las tareas, será suficiente. En un sentido amplio, esta es la quintaesencia
computación paralela.


Un ejemplo de cómo escribir el bucle principal en CUDA (kernel):


int tid = blockIdx.x * blockDim.x + threadIdx.x; int threadN = gridDim.x * blockDim.x; for (int pos = tid; pos < numElements; pos += threadN) { //    pos,     ,       thread     pos.  :    thread    ,  thread   pos=1146     thread c  pos=956.        .           . } 

Mucho se ha escrito en la documentación y las revisiones de CUDA , sobre los bloques de GPU, sobre los subprocesos que se producen en estos bloques, cómo paralelizar la tarea en ellos. Pero si tiene una matriz de datos y claramente consta de elementos de masa, use la forma de bucle anterior, ya que es visualmente similar a un bucle regular en forma, lo cual es agradable, pero desafortunadamente no en contenido.


Creo que el lector ya comprende que la clase de tareas se está reduciendo rápidamente en relación con la programación paralela masiva. Si estamos hablando de crear juegos, motores de renderizado 3D, redes neuronales, edición de video y otras tareas similares, entonces la limpieza para acciones de lectores independientes está muy desgastada, hay grandes programas, pequeños programas, marcos, bibliotecas conocidas y desconocidas para estas tareas. Es decir, el área permanece solo desde el tema, para crear su propio cohete informático pequeño, no SpaceX y Roscosmos, sino algo hogareño, pero completamente malo para los cálculos.



Aquí hay una imagen de un cohete completamente en llamas representado.


Hablando de tareas que un ciclo paralelo en tus manos no podrá resolver. Y los creadores de CUDA en la persona de los desarrolladores de NVIDIA ya han pensado en esto.


Hay una biblioteca Thrust en algunos lugares útil hasta que "no hay opciones" se hace de manera diferente. Por cierto, no encontró su revisión completa sobre Habré.


Para entender cómo funciona, primero debe decir tres oraciones sobre los principios de CUDA. Si necesita más palabras, puede leer el enlace.


Los principios de CUDA:


Los cálculos se llevan a cabo en la GPU, cuyo programa es el núcleo, y debe escribirlo en C. El núcleo, a su vez, solo se comunica con la memoria de la GPU y debe cargar los datos en la memoria del procesador de video desde el programa principal y cargarlos de nuevo al programa. Algoritmos sofisticados en CUDA requieren flexibilidad mental.


Entonces, la biblioteca Thrust elimina la rutina y asume algunas de las tareas "complejas" para CUDA, como sumar matrices u ordenarlas. Ya no necesita escribir un núcleo separado, cargar punteros en la memoria y copiar datos de estos punteros a la memoria de la GPU. Todo el misterio sucederá ante tus ojos en el programa principal y con una velocidad ligeramente inferior a CUDA. La biblioteca Thrust está escrita en CUDA, por lo que este es un solo campo de bayas en términos de rendimiento.


Lo que debe hacer en Thrust es crear una matriz (thrust :: vector) dentro de su biblioteca, que sea compatible con las matrices regulares (std :: vector). Es decir, por supuesto, no todo es tan simple, pero el significado de lo que dijo el autor es similar a la verdad. Realmente hay dos matrices, una en la GPU (dispositivo), la otra en el programa principal (host).


Un ejemplo mostrará la simplicidad de la sintaxis (matrices X, Y, Z):


 // initialize X to 0,1,2,3, .... thrust::sequence(X.begin(), X.end()); // compute Y = -X thrust::transform(X.begin(), X.end(), Y.begin(), thrust::negate<int>()); // fill Z with twos thrust::fill(Z.begin(), Z.end(), 2); // compute Y = X mod 2 thrust::transform(X.begin(), X.end(), Z.begin(), Y.begin(), thrust::modulus<int>()); // replace all the ones in Y with tens thrust::replace(Y.begin(), Y.end(), 1, 10); 

Puede ver cuán inofensivo se ve en el contexto de la creación del núcleo CUDA, y el conjunto de funciones en Thrust es grande . Comenzando por trabajar con variables aleatorias, que en CUDA se realiza mediante una biblioteca cuRAND separada (preferiblemente ejecutada por un núcleo separado), para ordenar, sumar y escribir sus funciones de acuerdo con la funcionalidad cercana a las funciones del núcleo.


El autor tiene poca experiencia en el uso de CUDA y C ++, dos meses. Sobre este año sobre C #. Esto, por supuesto, contradice levemente el comienzo del artículo sobre su conocimiento temprano de las computadoras, la física escolar y las matemáticas aplicadas como educación. Yo lo diré Pero, ¿por qué estoy escribiendo este artículo? No es que haya dominado todo de esa manera, sino que C ++ resultó ser un lenguaje cómodo (solía tenerle un poco de miedo, en el contexto de los artículos en Haber tipo "funciones lambda → sobrecarga de operadores internos, como redefinir todo "), está claro que los años de su desarrollo han llevado a entornos de desarrollo (IDE) bastante amigables. El lenguaje en sí en su última versión, parece que recoge basura de la memoria, no sé cómo era antes. Al menos, los programas escritos por el autor sobre las construcciones algorítmicas más simples impulsaron algoritmos computacionales para bots durante días y no hubo pérdidas de memoria y otras fallas a alta carga. Esto también se aplica a CUDA, al principio parece complicado, pero se basa en principios simples y, por supuesto, es difícil inicializar lugares en la GPU en lugares si hay muchos de ellos, pero luego tendrá su propio cohete pequeño, con humo de la tarjeta de video.


De las clases de objetos para entrenar con la GPU, el autor recomienda autómatas celulares . Hubo un tiempo en que hubo un aumento de popularidad y moda para ellos, pero luego las redes neuronales se apoderaron de las mentes de los desarrolladores.
Hasta:


"Cada cantidad en física, incluido el tiempo y el espacio, es finita y discreta".
que no un autómata celular.


Pero es hermoso cuando tres fórmulas simples pueden crear esto:



Si será interesante leer sobre autómatas celulares en CUDA, escriba en los comentarios, habrá material escrito para un pequeño artículo.
Y esta es la fuente de autómatas celulares (debajo del video hay enlaces a las fuentes):



La idea de escribir un artículo después del desayuno, de una sola vez, me parece que funciona. Segunda hora del café. Que tengas un buen lector de fin de semana.

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


All Articles