Ir al contenido
YFYass Fuentes

Orbafall XVIII: Un tablero de juguete a millones de decisiones por segundo

Editar en GitHub
· 16 min

Banner del devlog de Orbafall Legions, entrega 18: el tablero, reducido a sus reglas y medido contra el de verdad.

En la entrega 12 presumí de un modelo del tablero que jugaba 30 000 partidas por segundo en una gráfica, y confieso que me lo creí bastante. Aquella cifra se midió en cuadrados, en el mapa más pequeño y con cuatro fichas por bando, y nadie la había vuelto a medir desde que el tablero pasó a hexágonos. A primeros de noviembre, antes de firmar en qué lenguaje se iba a escribir el modelo de verdad, lo medí con el tablero de ahora y con el ejército que compra el juego. ¿Resultado? 278 partidas por segundo en un mapa pequeño. De cómo aquel juguete llegó a millones de decisiones por segundo, y de cómo comprobé que juega a lo mismo que el juego antes de dejarle entrenar nada, va esta entrega.

1 - El tablero de verdad, fuera de Godot

Antes del juguete hacía falta el original, y la entrega 12 terminaba justo ahí: portar la partida de tablero entera, línea a línea, con paridadQue dos programas distintos den exactamente el mismo resultado con las mismas entradas. Aquí, el juego y su simulador, duelo a duelo y bit a bit. partida a partida contra Godot y una puertaUn punto del plan en el que no se sigue hasta que juego lo hecho y doy el visto bueno, o digo qué no me gusta. de rendimiento antes de seguir. Ese porte lleva las reglas, la sesión de partida y la IA de tablero completa, de la compra a los pasos del turno. ¿En qué lenguaje? El código del tablero está lleno de ramas y de diccionarios, justo lo que peor corre en un hilo de GPU, así que la primera apuesta fue Python normal, una partida por proceso y tantos procesos como hilos. La medida de la primera puerta, el 2 de noviembre, cambió el plan: el alcance de las fichas, ya portado, iba 1,7 veces más rápido que Godot en el Python de siempre y 9,6 veces en PyPyUn intérprete alternativo de Python que compila al vuelo el código que más se repite. Corre el mismo Python, a menudo varias veces más rápido, pero no carga muchas bibliotecas con partes en C, como PyTorch o Warp., un intérprete con JITCompilación al vuelo (just in time): el intérprete detecta el código que más se ejecuta y lo traduce a código de máquina mientras el programa corre. Tarda un rato en calentarse.. Firmé PyPy ese mismo día, con una pega que todavía arrastro, porque ni Warp ni PyTorch cargan en PyPy y los combates los juega la arena del simulador en un proceso aparte.

La segunda puerta la crucé el mismo día. El tablero y las vistas portados salieron entre 5 y 8 veces más rápidos que Godot sobre las 14 144 llamadas de un lote, con cero diferencias, y di paso a las reglas y a la IA. Por la tarde el plan estaba cerrado, con cada pieza a cero diferencias frente a Godot y partidas completas idénticas a las suyas. Con ocho procesos, un loteUn conjunto de partidas o combates simulados de una tacada, con la misma configuración, para medir algo con cifras en lugar de con impresiones. de 80 partidas pasó de 266 a 34 segundos, y una muestra de 120 partidas largas, de 2 227 a 220. La ganancia va de 4 a 10 veces según el lote (3,9 en el de partidas cortas, 7,7 en el normal y 10,1 en la muestra larga), porque PyPy calienta su JIT en cada proceso y una partida corta no le da tiempo a amortizarlo.

Con esa velocidad calibré los niveles de la IA de tablero, y de lo que salió de ahí va la entrega anterior. Para entrenar estrategias no basta: con ocho procesos, el porte juega entre media partida y seis por segundo según el lote, y una red que aprende jugando pide del orden de un millón de decisiones por segundo. El 3 de noviembre pedí adelantar a esta fase el modelo abstracto del tablero, que estaba previsto para la versión 1.0, de forma eficiente y sin tirar trabajo, y el plan quedó firmado entre ese día y el siguiente.

2 - 278 partidas por segundo

El modelo abstracto es el tablero reducido a sus reglas y jugado en lote sobre la GPU, el mismo prototipo de la entrega 12 con lo que le faltaba: miles de partidas a la vez en tensoresUna matriz de cualquier número de dimensiones. Es el tipo de dato con el que trabajan las GPU y las bibliotecas de aprendizaje automático. y cada regla escrita como una operación sobre todas ellas. Entran ahora las revanchas cuando mueren a la vez los dos últimos portadores, la niebla de guerraSolo se ve la parte del tablero que alcanzan las propias figuras; el resto queda a oscuras hasta que alguien lo explora. con lo que ve cada jugador y una memoria de lo último que vio, y el ciclo del día. Los poderes, las trampas, los objetos y la compra esperan a la fase 6A, cuando lleguen las figuras definitivas, y para que nada se cuele sin avisar hay un comprobador que recorre el paquete de datos y se niega a cargar cualquier clave que el modelo no sepa jugar.

Aquella medida fue la primera en serio, con hexágonos, el tope de piezas del producto (12 por bando en un mapa s) y 32 768 partidas en el lote. El prototipo daba 278 partidas por segundo en el mapa s y 5 100 en el xs, y el perfilMedir en qué partes del código se va el tiempo, con una herramienta que lo cronometra función a función. señaló el culpable sin titubeos: el 81 % del paso se iba en calcular el alcance de cada ficha, aquella dilataciónOperación de tratamiento de imagen que expande una región un paso en todas direcciones. Repetida N veces, marca todo lo que está a N pasos. de máscarasUna matriz de verdaderos y falsos que marca qué casillas o qué partidas participan en una operación. Es como se escriben los condicionales cuando se opera sobre miles de partidas a la vez. de la entrega 12, que con el triple de fichas por bando pesaba lo suyo. Se probaron dos prototipos del alcance. Uno lo empaquetaba en bits sin salir de PyTorch y subía el mapa s a unas 2 270 partidas por segundo; el otro lo escribía como un núcleoUna función que se ejecuta en la GPU, en miles de hilos a la vez. En inglés, kernel. de NVIDIA Warp, la herramienta con la que se portó la arena, y llegaba a 5 950. En el mapa xs, las mismas tres versiones daban 5 100, 29 800 y 81 600.

Barras de partidas por segundo con tres versiones del alcance: en el mapa s, 278 el modelo de entonces, 2 270 con el alcance en bits en PyTorch y 5 950 con el alcance en Warp; en el xs, 5 100, 29 800 y 81 600.

Imagen 1. Partidas por segundo en hexágonos según cómo se calcula el alcance, medidas antes de firmar el lenguaje del modelo.

¿Y ahora qué? Ampliar lo que había en PyTorchLa biblioteca de aprendizaje automático más usada en investigación, de Meta. Opera con tensores en CPU o en GPU. tenía a favor sus pruebas, sus grafos de CUDAGrabar una secuencia de operaciones de GPU una vez y relanzarla entera, sin que la CPU tenga que mandar cada una por separado. y la red en la misma memoria, aunque unos poderes muy variados saldrían caros escritos como máscaras; pasarlo todo a Warp, un hilo por partida como en la arena, obligaba a reescribir lo que ya funcionaba, y Rust ni siquiera estaba instalado [JAX sigue sin gráfica en Windows]. Firmé un híbrido el 4 de noviembre: el estado, las reglas sencillas y la red en PyTorch, y los puntos calientes como núcleos de Warp que leen y escriben los mismos tensores sin copiarlos, empezando por el alcance. Cada núcleo tiene su prueba de que da los mismos bits que su versión en PyTorch, que se queda como referencia, y si los poderes de 6A no caben en un paso de forma fija se vuelve a decidir entonces.

Ese día cambié también la vara de medir. Una partida en un mapa grande dura diez veces más que en uno pequeño, así que contar partidas por segundo castiga a los mapas grandes por serlo. Lo que consume un entrenamiento son decisiones, una orden del jugador en turno en cada partida del lote, y el suelo firmado quedó en un millón por segundo con un jugador aleatorio y medio millón con uno codicioso (el que siempre toma la mejor jugada inmediata) en los mapas xs, s y m y en una sola gráfica. Como todos los presupuestos de rendimiento del proyecto, avisa y nunca bloquea.

3 - Del 60 al 97 por ciento

En el modelo, el combate no se juega; se sortea. En la entrega 12 la tabla del sorteo se la pedía a Godot en el contexto base, y frente a 809 combates reales acertaba el desenlace el 60 % de las veces y daba muertes dobles en el 82 %, cuando en el juego eran el 48 %. La causa estaba a la vista, porque los luchadores de una partida de verdad pelean en el terreno de su casilla y a una hora del día, y la tabla no sabía ni lo uno ni lo otro.

La tabla nueva la genera la arena del simulador, sin Godot, con los luchadores montados por el mismo código que los monta en una partida del porte. Su clave lleva las dos unidades, el pilotoEl perfil de destreza con el que la IA lleva una figura en la arena: cuánto tarda en reaccionar, cuánto adelanta al apuntar, cuánto duda. de cada asiento, quién lleva el orbe, la arena de la casilla del defensor, la fase del día y la vida de cada lado en cinco tramos. Son 2 250 000 claves con ocho semillas cada una, casi trece millones de duelos que una 4090 juega en unos cinco minutos [una vez tardó 33 y todavía no sé por qué], y el fichero ocupa 0,7 MB. Lleva además la huella del manifiesto de la arena, el de la entrega 12, y se niega a cargar si la arena cambió desde que se generó.

Frente a 4 730 combates de partidas reales, acierta el desenlace el 97,4 % de las veces y da muertes dobles en el 20,6 %, cuando hubo un 20,3 %. Una rejilla de Godot regenerada con los pilotos, sobre los mismos combates, se queda en el 92,6 % y se equivoca seis veces más en la vida con que acaba el ganador. Lo que falta hasta el cien es sobre todo la interpolación entre los tramos de vida, porque jugando cada combate con su vida exacta se llega al 99,2 %.

4 - Cinco maneras de fiarse

A mí me preocupaba más otra cosa que la velocidad, que un modelo rápido que juega a otro juego me entrenase estrategias para ese otro juego sin que yo lo notase. Así que la fidelidad se midió contra el porte del tablero en cinco niveles, del más estricto al más estadístico. El primero repite las mismas jugadas en los dos motores, con cada combate impuesto, y compara posiciones, vidas y movimientos tras cada orden, y dio cero diferencias en 800 partidas y 164 527 pasos. El segundo es la tabla de combate de la sección anterior. El tercero juega partidas completas con el mismo jugador en los dos motores y compara quién gana, cuántas rondas y combates hay y por qué acaban, con un margen de tres puntos. El cuarto carga 3 839 posiciones de partidas reales en los dos y comprueba si coinciden las órdenes legales, lo que ve cada jugador y la jugada del codicioso; coinciden en las 3 839. El quinto ordena por EloEl sistema de puntuación del ajedrez. Cada partida mueve la puntuación de los dos jugadores según el resultado y la diferencia que había entre ellos. a los siete jugadores de un panel en cada motor y pide que el orden sea el mismo, y sale idéntico, con una correlación de SpearmanUna medida de cuánto se parecen dos clasificaciones de los mismos elementos. Vale 1 si el orden es idéntico, 0 si no tienen nada que ver y -1 si uno es el otro al revés. de 1,000.

Escalera de cinco peldaños, del más estricto al más estadístico, cada uno con su cifra.

Imagen 2. Los cinco niveles de fidelidad del modelo frente al tablero exacto, tras la revisión del cierre.

La primera versión del informe decía que todo cumplía, y la revisión independienteUn agente distinto del que escribió el código lo lee entero antes de fusionarlo, con las decisiones de diseño delante, y puede parar la fusión. del cierre encontró tres cosas que lo matizaban. Cuando una ficha avanzaba hacia un enemigo que no veía, el modelo trazaba en hexágonos un camino distinto del exacto; en un tablero vacío de prueba pasaba en 36 de 72 destinos lejanos. La memoria de lo que había visto cada jugador se actualizaba también durante el turno del rival. Y el tercer nivel aprobaba la cifra agregada gracias a dos mapas, el m y el l, en los que el codicioso, que no explora, nunca llega a ver al rival, de modo que todas las partidas acaban en tablas por el tope de rondas en los dos motores y eso cuenta como un acuerdo perfecto. Esa es la que más me escuece, porque yo habría firmado el informe tan contento.

Se arreglaron las tres, con la prueba del camino escrita antes del arreglo, y los cinco niveles se volvieron a medir. Ahora el tercero solo juzga los mapas donde hay combate, y ahí la diferencia en la tasa de victoria es de medio punto, con un intervalo que no llega a dos. Los niveles uno y cuatro no habrían visto nunca los dos primeros fallos, porque el conjunto de fidelidad casi nunca pone una pieza oculta en el camino de otra. Lo que nadie ha probado todavía es cómo se porta con poderes y objetos, que no existen en su panel, ni con otra compra y otra colocación que las de la IA base.

5 - El suelo, y una palanca la misma tarde

Antes de medir contra el suelo hubo una tanda de recortes que no cambian un bit (el jugador en turno dejó de calcular de paso lo que veía el otro, entre otros) y que en el mapa s llevaron al aleatorio de 0,92 a 2 millones de decisiones por segundo; una corrida de referencia con 432 tensores salió idéntica antes y después de cada uno.

Aun así, la primera medida del cierre cumplía el suelo en xs, pero el codicioso en s y los dos jugadores en m se quedaban por debajo. El codicioso hace del orden de N² consultas a la tabla de combate en cada paso, una por pareja de fichas, y dos veces, porque mira ataques y amenazas; en el mapa s su decisión era el 78 % del paso. La palanca era la misma que con el alcance, llevar la consulta a Warp con las mismas operaciones en el mismo orden que la versión de PyTorch y la prueba de que da los mismos bits, y entró esa misma tarde. El codicioso ganó un 98 % en xs y un 69 % en s, donde pasó de unas 390 000 a 657 000 decisiones por segundo y dejó de avisar.

Barras de millones de decisiones por segundo por tamaño de mapa, aleatorio y codicioso, con las líneas de los dos suelos.

Imagen 3. Decisiones por segundo en una gráfica, con la consulta de la tabla de combate ya en Warp.

Con el mejor lote y una sola 4090, el aleatorio juega casi 5,6 millones de decisiones por segundo en xs, que son unas 47 800 partidas, y 2,1 millones en s; el codicioso, 2,2 millones y 657 000. El mapa m sigue avisando, con 567 000 y 167 000, el 57 y el 33 % de su suelo, y los mapas grandes solo se anotan: en xxl, con 73 piezas por bando, el aleatorio se queda en 8 843. Con una red pequeña decidiendo cada orden, el techo lo pone la propia red, unas 550 000 decisiones en xs.

Hubo también un tropiezo. Una de las medidas del quinto nivel corrió por error en el procesador en lugar de en la gráfica y se tiró más de cuatro horas; repetida en la GPU, tardó 26 minutos. Pregunté por qué tardaba tanto, y de la respuesta salió una norma: antes de una tirada larga se cronometra una muestra y se anota cuánto debería durar, y el modelo corre siempre en una GPU. Ese mismo día la orden que arranca el modelo aprendió a negarse a arrancar cuando ve más de una gráfica hasta que le digo en cuál.

6 - Dos gráficas, el doble de tablero

En casa hay dos 4090, y la pregunta salía sola: ¿se duplica? Con el tablero, sí. Dos procesos a la vez, uno por gráfica y cada uno con sus propias partidas, sin repartir ninguna entre las dos. En xs con 131 072 partidas por lote, una gráfica sola daba 4,33 millones de decisiones por segundo y las dos juntas 8,73; en s, 2,11 y 4,22. La medida es de antes de llevar la consulta a Warp, y por eso la cifra de una gráfica es menor que la de la sección anterior, pero la cuenta sale igual, con la suma en el doble dentro del ruido.

¿Por qué? Cada proceso gasta un núcleo de CPU y un hilo que lanza el trabajo, y las partidas de una gráfica no esperan nunca a las de la otra. Dentro de cada gráfica, además, todas las partidas hacen exactamente lo mismo en cada paso: el paso tiene forma fija, va grabado en un grafo de CUDA, y una partida que ya acabó o que no ataca paga el mismo trabajo que las demás, tapado por una máscara.

Con la arena pasa lo contrario, y en la entrega 12 ya lo apunté de pasada. La arena corre un hilo por duelo, con un código serie y lleno de ramas, y un lanzamiento dura lo que su duelo más lento. Por eso rinde más cuanto más grande es la tanda: con obstáculos, 4 096 duelos tardan 1,7 segundos y 49 152, doce veces más, tardan 3,5. Repartir 100 000 duelos con obstáculos entre las dos gráficas da los mismos resultados en 8,95 segundos, frente a 9,55 con una sola, porque cada gráfica recibe una tanda más corta y vuelve a esperar a su duelo más lento.

Dos paneles: a la izquierda, el tablero en xs y s, con una gráfica y con dos, la suma en el doble; a la derecha, la arena, 100 000 duelos con obstáculos en 9,55 s con una gráfica y 8,95 s con dos.

Imagen 4. El tablero suma lo que pone cada gráfica; en la arena, la segunda gráfica apenas recorta medio segundo.

El mismo día probé a llevar a la gráfica la búsqueda de los pilotos de arena más altos, los que simulan varios futuros antes de decidir, y salió peor todavía: resultados idénticos, pero 18,6 duelos por segundo frente a 189 de la CPU con todos sus núcleos. Cada duelo explora futuros distintos, y una GPU solo brilla cuando miles de hilos hacen lo mismo a la vez, así que la búsqueda se quedó en la CPU.

7 - Palabras finales

El plan del modelo abstracto se cerró el 4 de noviembre, el mismo día que firmé su última decisión, y el modelo queda apto para entrenar en lo que miden sus cinco niveles. El entrenamiento de verdad, el autojuegoEntrenar un agente haciéndolo jugar contra copias de sí mismo. por aprendizaje por refuerzoLa rama del aprendizaje automático en la que un agente aprende probando acciones y recibiendo recompensas, sin ejemplos etiquetados., espera a la fase 6A, cuando las figuras tengan sus poderes y la compra entre en el modelo; entonces se vuelven a anotar las cifras y se revisan los suelos. Nada de lo que aprenda entrará en el juego como lógica, porque el modelo es un instrumento de medida y lo que salga de él llegará como parámetros de la IA que ya conocéis. Os dejo una pregunta: ¿cómo decidís cuándo un modelo simplificado se parece lo bastante al juego real como para fiaros de lo que aprende en él?

7.1 - Para el futuro

La siguiente entrega vuelve al tablero de verdad y a una pregunta que parecía sencilla, la de cómo acaba una partida.

Continúa leyendo la serie en la entrega 19, «Cómo acaba una partida».

Orbafall Legions es un proyecto personal no oficial, no afiliado con Silicon Knights, SSI, Mindscape ni Ubisoft.


Versión 1.0R0 (07/11/2026)

Changelog