Hu Yuanming: ¡Importe una biblioteca de "Tai Chi" para acelerar el código de Python 100 veces!

Este artículo se reproduce de Qubit.

Fengse se envía desde el qubit cóncavo no si
| cuenta pública QbitAI

Todos sabemos que la simplicidad y la legibilidad de Python se obtienen a expensas del rendimiento——

Especialmente en casos computacionalmente intensivos como bucles for múltiples.

Pero ahora, el jefe Hu Yuanming dijo:

¡Simplemente importe una biblioteca llamada "Taichi" y podrá acelerar su código 100 veces !

0581b86ea05a6fab054daabcd4e4ac25.png

¿No creen?

Veamos tres ejemplos.

Calcular el número de números primos, velocidad x120

El primer ejemplo es muy, muy simple, encuentra todos los números primos menores que un entero positivo dado N.

La respuesta estándar es la siguiente:

e65401e0cd358051ed718057cd787020.png

Guardamos el código anterior y lo ejecutamos.

Cuando N es 1 millón, se necesitan 2,235 s para obtener el resultado:

e9357a1d3e1c66fcfd45ab04652572ae.png

Ahora, hagamos la magia.

Sin cambiar ningún cuerpo de función, importe la biblioteca "taichi" y luego agregue dos decoradores:

9f8fd9ab1a3f50bf02ca779e02e60780.png

¡Bingo! El mismo resultado solo toma 0.363s, casi 6 veces más rápido.

64ce41c2ab66e22f4e57efa1f07775b6.png

Si N = 10 millones, solo toma 0,8 s; ya sabes, sin él, toma 55 s, ¡lo que es 70 veces más rápido !

No solo eso, también podemos agregar un parámetro a ti.init() a ti.init(arch=ti.gpu) para permitir que taich realice cálculos en la GPU .

Entonces, en este momento, solo se necesitan 0,45 s para calcular todos los números primos menores de 10 millones, ¡lo cual es 120 veces más rápido que el código Python original !

¿Es genial?

¿Qué? ¿Crees que este ejemplo es demasiado simple y no lo suficientemente convincente? Veamos uno un poco más complicado.

Programación dinámica, velocidad x500

No hace falta decir que la programación dinámica, como algoritmo de optimización, reduce el tiempo de cálculo al almacenar dinámicamente los resultados de los cálculos intermedios.

Tomemos como ejemplo el caso clásico de programación dinámica "Problema de la subsecuencia común más larga (LCS)" en el libro de texto clásico "Introducción a los algoritmos" .

Por ejemplo, para la secuencia a = [0, 1, 0, 2, 4, 3, 1, 2, 1] y la secuencia b = [4, 0, 1, 4, 5, 3, 1, 2], su LCS es:

MC(a, b) = [0, 1, 4, 3, 1, 2]。

Calcular LCS con la idea de programación dinámica es primero resolver la longitud de la subsecuencia común más larga de los primeros i elementos de la secuencia a y los primeros j elementos de la secuencia b, y luego aumentar gradualmente el valor de i o j, repetir el proceso y obtener el resultado.

Usamos f[i, j] para referirnos a la longitud de esta subsecuencia, es decir, LCS((prefix(a, i), prefix(b, j). Entre ellos, prefix(a, i) representa la primera i elementos de la secuencia a , a saber a[0], a[1], ..., a[i - 1], se obtiene la siguiente relación de recurrencia:

6ded319769ef349862dfcfd50c63b6bf.png

El código completo es el siguiente:

a41a129625573060e2c13bfe0ce5456a.png

Ahora, usamos Taichi para acelerar:

2f7659b07ea9082be62aa7b110b8fb68.png

El resultado es el siguiente:

68e3a613d57aae4bff8418eb598349a8.png

El programa en la computadora de Hu Yuanming se puede completar en 0,9 segundos como máximo , mientras que se necesitan 476 segundos para implementarlo con NumPy, ¡la diferencia es más de 500 veces!

Finalmente, tomemos un ejemplo diferente.

La ecuación de reacción-difusión con efectos sorprendentes

En la naturaleza, siempre hay algunos animales con patrones que parecen desordenados pero que no son completamente aleatorios.

6f9cb69426c92e92a02fd9a768470009.png

Alan Turing, el inventor de la máquina de Turing, fue el primero en idear un modelo para describir este fenómeno.

En este modelo, se utilizan dos productos químicos (U y V) para simular la generación de patrones. La relación entre estos dos es similar a la de presa y depredador, se mueven e interactúan por sí mismos:

  1. Inicialmente, U y V se distribuyen aleatoriamente sobre un dominio;

  2. En cada paso de tiempo, se difunden gradualmente en espacios adyacentes;

  3. Cuando U y V se encuentran, una parte de U es tragada por V. Por tanto, la concentración de V aumenta;

  4. Para evitar que U sea erradicado por V, agregamos un cierto porcentaje (f) de U y eliminamos un cierto porcentaje (k) de V en cada paso de tiempo.

El proceso anterior se describe como la "ecuación de reacción-difusión":

750d2d0635de1fe17a3f77e51585c9d6.png

Hay cuatro parámetros clave: Du ( la velocidad de difusión de U), D v (la velocidad de difusión de V), f (la abreviatura de feed, que controla la adición de U) y k (la abreviatura de kill, que controla la eliminación de V).

Si esta ecuación se implementa en Taichi, primero cree cuadrículas para representar dominios y use vec2 para representar los valores de concentración de U, V en cada cuadrícula.

El cálculo de los valores laplacianos requiere acceso a cuadrículas adyacentes. Para evitar actualizar y leer datos en el mismo bucle, debemos crear dos cuadrículas de la misma forma W×H×2.

Cada vez que se accede a los datos desde una cuadrícula, escribimos los datos actualizados en otra cuadrícula y luego cambiamos a la siguiente cuadrícula. Entonces el diseño de la estructura de datos es así:

7a35020556b71a805ac539f36cb1b6f8.png

Inicialmente, establecemos la concentración de U en la cuadrícula en 1 y colocamos V en 50 ubicaciones elegidas al azar:

36b8cc1da05fa0578df084dca0275dc3.png

Luego, el cálculo real se puede realizar en menos de 10 líneas de código:

@ti.kernel
def compute(phase: int):
    for i, j in ti.ndrange(W, H):
        cen = uv[phase, i, j]
        lapl = uv[phase, i + 1, j] + uv[phase, i, j + 1] + uv[phase, i - 1, j] + uv[phase, i, j - 1] - 4.0 * cen
        du = Du * lapl[0] - cen[0] * cen[1] * cen[1] + feed * (1 - cen[0])
        dv = Dv * lapl[1] + cen[0] * cen[1] * cen[1] - (feed + kill) * cen[1]
        val = cen + 0.5 * tm.vec2(du, dv)
        uv[1 - phase, i, j] = val

Aquí usamos una fase entera (0 o 1) para controlar de qué cuadrícula leemos los datos.

El último paso es colorear el resultado de acuerdo con la concentración de V, y puedes obtener un patrón tan sorprendente :

aec8a98c46ea550e762a9a1c57d600e5.gif

Curiosamente, Hu Yuanming introdujo que incluso si la concentración inicial de V se establece al azar, se pueden obtener resultados similares cada vez.

Y en comparación con la implementación de Numba que solo puede alcanzar unos 30 fps, la implementación de Taichi puede superar fácilmente los 300 fps porque la GPU se puede seleccionar como backend.

instalar pip para instalar

Después de leer los tres ejemplos anteriores, ¿lo crees ahora?

De hecho, Taichi es un DSL (Dynamic Scripting Language) integrado en Python. Utiliza su propio compilador para compilar las funciones decoradas por @ti.kernel en varios hardware, incluidos CPU y GPU, y luego realiza una computación de alto rendimiento.

Con él, ya no tendrá que envidiar el rendimiento de C++/CUDA.

Como sugiere el nombre, Taichi proviene del equipo de Taichi Graphics Hu Yuanming. Ahora solo necesita usar pip install para instalar esta biblioteca e interactuar con otras bibliotecas de Python, incluidas NumPy, Matplotlib y PyTorch.

Por supuesto, ¿cuál es la diferencia entre Taichi y estas bibliotecas y otros métodos de aceleración? Hu Yuanming también proporcionó una comparación detallada de las ventajas y desventajas. Los amigos interesados ​​pueden hacer clic en el siguiente enlace para ver en detalle:

https://docs.taichi-lang.org/blog/accelerate-python-code-100x

Supongo que te gusta

Origin blog.csdn.net/u014333051/article/details/126844632
Recomendado
Clasificación