Heim  >  Artikel  >  Backend-Entwicklung  >  Eine Einführung in das Hochleistungsrechnen mit Python basierend auf Taichi

Eine Einführung in das Hochleistungsrechnen mit Python basierend auf Taichi

王林
王林nach vorne
2023-04-12 08:46:131181Durchsuche

Seit der Einführung der Programmiersprache Python besteht ihre Kernphilosophie darin, die Lesbarkeit und Einfachheit des Codes zu maximieren. Pythons Streben nach Lesbarkeit und Einfachheit ist fast verrückt. Eine Tatsache kann dies bestätigen: Solange Sie im Stammverzeichnis des Python-Systems den Befehl „import this“ eingeben und die Eingabetaste drücken, wird sofort ein kleines englisches Gedicht ausgedruckt, was ins Chinesische übersetzt ungefähr bedeutet:

„Schön ist besser als hässlich, explizit ist besser als implizit.

Einfach ist besser als komplex, komplex ist besser als kompliziert,

Flach ist besser als verschachtelt,

Lesbarkeit ist wichtig …“

Einfach. Besser als kompliziert, Lesbarkeit ist wichtig. Es besteht kein Zweifel, dass Python diese Ziele tatsächlich sehr erfolgreich erreicht: Es ist bei weitem die benutzerfreundlichste Sprache zum Erlernen, und ein gewöhnliches Python-Programm ist normalerweise fünf- bis zehnmal kürzer als der entsprechende C++-Code. Leider gibt es einen Haken: Die Einfachheit von Python geht auf Kosten der Leistung! Tatsächlich sind Python-Programme 10 bis 100 Mal langsamer als ihre C++-Gegenstücke. Daher scheint es einen permanenten Kompromiss zwischen Geschwindigkeit und Einfachheit zu geben, und es ist unmöglich, dass eine Programmiersprache beides hat.

Aber keine Sorge, es ist noch nicht alle Hoffnung verloren.

Taichi bietet das Beste aus beiden Welten

Die Programmiersprache Taichi ist ein Versuch, die Programmiersprache Python um eine Struktur zu erweitern, die universelles Hochleistungsrechnen unterstützt. Es unterstützt die nahtlose Einbettung in Python und nutzt gleichzeitig die gesamte Rechenleistung Ihres Computers – einschließlich der Multi-Core-CPU-Fähigkeiten und, was noch wichtiger ist, der GPU-Leistung.

In diesem Artikel zeigen wir ein Beispielprogramm, das mit Taichi geschrieben wurde. Dieses Programm nutzt die GPU, um in Echtzeit eine physikalische Simulation eines auf eine Kugel fallenden Stücks Stoff durchzuführen und gleichzeitig die Ergebnisse zu rendern.

Das Schreiben eines Echtzeit-GPU-Physiksimulators ist nie einfach, aber der Taichi-Quellcode, der diese Routine implementiert, ist äußerst einfach. Der Rest dieses Artikels führt Sie durch die gesamte Implementierung, damit Sie ein Gefühl dafür bekommen, was Taichi bietet und wie leistungsstark und benutzerfreundlich sie sind.

Bevor wir beginnen, können Sie genauso gut erraten, aus wie vielen Codezeilen dieses Programm besteht. Die Antwort finden Sie natürlich am Ende des Artikels.

Algorithmusübersicht

Unser Programm modelliert ein Stück Stoff als Masse-Feder-System. Genauer gesagt stellen wir dieses Tuch als N×N-Gitter aus Punktmassen dar, wobei benachbarte Punkte durch Federn verbunden sind. Die folgende Abbildung von Matthew Fisher von der Stanford University veranschaulicht diese Struktur.

Eine Einführung in das Hochleistungsrechnen mit Python basierend auf Taichi

Die Bewegung dieses Masse-Feder-Systems wird durch 4 Faktoren beeinflusst:

  • Schwerkraft
  • Die innere Kraft der Feder
  • Dämpfung
  • Kollision mit der roten Kugel in der Mitte

Der Einfachheit halber: Wir ignorieren den Stoff der Selbstkollision. Unser Programm beginnt bei t=0. Dann wird bei jedem Schritt der Simulation die Zeit um eine kleine Konstante dt verlängert. Das Programm schätzt, was mit dem System in diesem kurzen Zeitraum passieren wird, indem es den Einfluss jedes der vier oben genannten Faktoren bewertet, und aktualisiert die Position und Geschwindigkeit jedes Massenpunkts am Ende des Zeitschritts. Die aktualisierten Partikelpositionen werden dann verwendet, um das auf dem Bildschirm gerenderte Bild zu aktualisieren.

Das Programm beginnt

Obwohl Taichi eine eigenständige Programmiersprache ist, liegt es als Python-Paket vor, das durch einfaches Ausführen von pip install Taichi installiert werden kann.

Um Taichi in einem Python-Programm zu verwenden, müssen Sie Taichi zunächst mit dem Alias ​​ti importieren:

import taichi as ti

Wenn Ihr Computer über eine Nvidia-GPU verfügt, die CUDA unterstützt, wird die Leistung von Taichi-Programmen maximiert. Wenn dies der Fall ist, fügen Sie nach der obigen Importanweisung die folgende Codezeile hinzu:

ti.init(arch=ti.cuda)

Wenn Sie keine CUDA-GPU haben, kann Taichi weiterhin mit Ihrer über andere Grafik-APIs wie ti.metal, ti verwendet werden. vulkan und ti.opengl GPU-Interaktion. Allerdings ist die Unterstützung von Taichi für diese APIs nicht so umfassend wie die Unterstützung für CUDA. Daher verwenden wir vorerst die CPU als Rechen-Backend:

ti.init(arch=ti.cpu)

Keine Sorge, Taichi läuft schnell, auch wenn es nur auf der CPU läuft. Nach der Initialisierung von Taichi können wir mit der Deklaration der Datenstruktur beginnen, die zur Beschreibung des Massefedergewebes verwendet wird. Dazu fügen wir die folgenden Codezeilen hinzu:

N = 128
x = ti.Vector.field(3, float, (N, N)) 
v = ti.Vector.field(3, float, (N, N))

Diese drei Zeilen deklarieren x und v als zweidimensionale Arrays der Größe N×N, wobei jedes Element des Arrays ein dreidimensionaler Vektor aus Gleitkommazahlen ist. Im Taichi werden die Arrays „Felder“ genannt und die beiden Felder zeichnen jeweils die Position und Geschwindigkeit der Punktmasse auf. Beachten Sie, dass diese Felder/Arrays automatisch im GPU-Speicher gespeichert werden, wenn Sie Taichi für die Ausführung auf einer CUDA-GPU initialisieren. Zusätzlich zum Stoff müssen wir auch den Ball in der Mitte definieren:

ball_radius = 0.2
ball_center = ti.Vector.field(3, float, (1,))

Hier ist der Mittelpunkt des Balls ein 1D-Feld der Größe 1 und seine einzelne Komponente ist ein 3D-Float-Vektor. Nachdem wir die erforderlichen Felder deklariert haben, initialisieren wir diese Felder mit den entsprechenden Daten bei t=0. Wir möchten sicherstellen, dass für jedes Paar benachbarter Punkte in derselben Zeile oder Spalte der Abstand zwischen ihnen gleich cell_size=1,0/N ist. Dies wird mit der folgenden Initialisierungsroutine erreicht:

def init_scene(): 
for i, j in ti.ndrange(N, N):
x[i, j] = ti.Vector([i * cell_size, 
j * cell_size / ti.sqrt(2), 
(N - j) * cell_size / ti.sqrt(2)]) 
ball_center[0] = ti.Vector([0.5, -0.5, 0.0])

Hier müssen Sie sich keine Gedanken über die Bedeutung jedes x[i,j]-Werts machen – er wird einfach so gewählt, dass das Tuch in eine 45-Grad-Ecke fällt, siehe zum Bild unten.

Eine Einführung in das Hochleistungsrechnen mit Python basierend auf Taichi

模拟

在每个时间步中,我们的程序都会模拟影响布料运动的4个因素:重力、弹簧内力、阻尼和与红球的碰撞。其中,重力是最容易处理的。

下面是实现这一点的代码:

@ti.kernel
def step():
for i in ti.grouped(v):
v[i].y -= gravity * dt

这里有两点需要注意。首先,语句for i in ti.grouped(x)意味着将循环迭代x的所有元素,而不管x中有多少维度。其次,也是最重要的是:注解@ti.kernel意味着Taichi将自动并行运行函数中的任何顶级for循环。在本例中,Taichi将并行更新v中每个N*N向量的y分量。

接下来,我们来处理弦线的内力计算问题。首先,请注意前面图形中的每个质点最多连接到八个邻接质点。这些连接在我们的程序中表示如下:

 links = [[-1, 0], [1, 0], [0, -1], [0, 1], [-1, -1], [1, -1], [-1, 1], [1, 1]
links = [ti.Vector(v) for v in links]

从物理角度来看,系统中的每个弹簧s都用固定长度l(s,0)初始化。在任何时间t,如果s的当前长度l(s,t)超过l(s,0),则弹簧将在其端点上施加力,将它们拉在一起。相反,如果l(s,t)小于l(s,0),则弹簧会将端点彼此推开。这些力的大小始终与l(s,0)-l(s,0)的绝对值成正比。此交互由以下代码段捕获:

 for i in ti.grouped(x):
force = ti.Vector([0.0,0.0,0.0]) 
for d in ti.static(links): 
j = min(max(i + d, 0), [N-1,N-1])
relative_pos = x[j] - x[i]
current_length = relative_pos.norm()
original_length = cell_size * float(i-j).norm()
if original_length != 0:
force +=stiffness * relative_pos.normalized() *
(current_length - original_length) /
original_length
v[i] +=force * dt

请注意,这个for循环仍应作为substep函数中的顶级for循环,该函数用@ti.kernel注解。这样可以确保并行计算施加到每个质点的弹簧力。stiffness在此是一个常数,用于控制弹簧长度变化的程度。在上述程序中,我们使用stiffness =1600指定它的值。在现实世界中,当弹簧振动时,弹簧中储存的能量会消散到周围环境中,其振动最终停止。为了捕捉这种效应,在每个时间步,我们稍微降低每个点的速度大小:

for i in ti.grouped(x):
v[i] *= ti.exp(-damping * dt)

在此,damping取固定值2。

我们还需要处理布料和红球之间的碰撞。要做到这一点,我们只需将质点与球接触时的速度降低到0。这样可以确保布料“挂”在球上,而不是穿透球或向下滑动:

if (x[i]-ball_center[0]).norm() <= ball_radius: 
v[i] = ti.Vector([0.0, 0.0, 0.0])

最后,我们用每个质点的速度更新其自身的位置:

x[i] += dt * v[i]

这就是我们对一块质量弹簧布料进行并行模拟所需的全部代码。

渲染

我们将使用Taichi内置的基于GPU的GUI系统(昵称是“GGUI”)渲染布料。GGUI使用Vulkan图形API进行渲染,因此请确保您的计算机上安装了Vulkan(https://docs.taichi.graphics/lang/articles/misc/ggui)。GGUI支持渲染两种类型的3D对象:三角形网格和粒子。在我们的示例中,将把布料渲染为三角形网格,把红色球渲染为单个粒子。

GGUI表示一个三角形网格,包含两个Taichi场:一个顶点(vertices)场和一个索引(indices)场。顶点场是一个一维场,其中每个元素提取是一个表示顶点位置的三维向量,可能由多个三角形共享。在我们的应用程序中,每个点质量都是一个三角形顶点,因此我们可以简单地将数据从x复制到vertices:

 vertices = ti.Vector.field(3, float, N * N)
@ti.kernel
def set_vertices():
for i, j in ti.ndrange(N, N):
vertices[i * N + j] = x[i, j]

请注意,每一帧都需要调用set_vertices,因为顶点位置不断被模拟更新。

我们的布料是用一个质点的N×N网格表示,也可以被看作一个由(N-1)×(N-1)小正方形组成的网格。每个正方形都将渲染为两个三角形。因此,总共有(N-1)×(N-1)×2个三角形。每个三角形将在顶点场中表示为3个整数,该场记录顶点场中三角形顶点的索引。以下代码片段捕获了这一结构:

num_triangles = (N - 1) * (N - 1) * 2
indices = ti.field(int, num_triangles * 3)
@ti.kernel
def set_indices():
for i, j in ti.ndrange(N, N):
if i < N - 1 and j < N - 1:
square_id = (i * (N - 1)) + j
#正方形的第一个小三角形
indices[square_id * 6 + 0] = i * N + j
indices[square_id * 6 + 1] = (i + 1) * N + j
indices[square_id * 6 + 2] = i * N + (j + 1)
#正方形的第二个小三角形
indices[square_id * 6 + 3] = (i + 1) * N + j + 1
indices[square_id * 6 + 4] = i * N + (j + 1)
indices[square_id * 6 + 5] = (i + 1) * N + j

请注意,与函数set_vertices不同,函数set_indices只需要调用一次。这是因为三角形顶点的索引实际上并没有改变——只是位置在改变。

为了将红球渲染为粒子,我们实际上不需要准备任何数据,我们之前定义的ball_center和ball_radius变量就是GGUI所需要的全部内容。

完整代码

至此,我们已经介绍完本文示例程序的所有核心函数!下面代码展示了我们如何调用这些函数:

 init()
set_indices()
window = ti.ui.Window("Cloth", (800, 800), vsync=True)
canvas = window.get_canvas()
scene = ti.ui.Scene()
camera = ti.ui.make_camera()
while window.running:
for i in range(30):
step()
set_vertices()
camera.position(0.5, -0.5, 2)
camera.lookat(0.5, -0.5, 0)
scene.set_camera(camera)
scene.point_light(pos=(0.5, 1, 2), color=(1, 1, 1))
scene.mesh(vertices, indices=indices, color=(0.5, 0.5, 0.5), two_sided = True)
scene.particles(ball_center, radius=ball_radius, color=(0.5, 0, 0))
canvas.scene(scene)
window.show()

需要注意的一个小细节是,我们将在主程序循环中的每一帧调用函数step()30次,而不是调用一次。这样做的目的就是让动画不会运行得太慢。把上述所有代码放在一起,整个程序应该是这样的:

 import taichi as ti
ti.init(arch=ti.cuda) # 另一种可选择方案: ti.init(arch=ti.cpu)
N = 128
cell_size = 1.0 / N
gravity = 0.5
stiffness = 1600
damping = 2
dt = 5e-4
ball_radius = 0.2
ball_center = ti.Vector.field(3, float, (1,))
x = ti.Vector.field(3, float, (N, N))
v = ti.Vector.field(3, float, (N, N))
num_triangles = (N - 1) * (N - 1) * 2
indices = ti.field(int, num_triangles * 3)
vertices = ti.Vector.field(3, float, N * N)
def init_scene(): 
for i, j in ti.ndrange(N, N): 
x[i, j] = ti.Vector([i * cell_size , 
 j * cell_size / ti.sqrt(2), 
 (N - j) * cell_size / ti.sqrt(2)])
ball_center[0] = ti.Vector([0.5, -0.5, -0.0])
@ti.kernel
def set_indices(): 
for i, j in ti.ndrange(N, N): 
if i < N - 1 and j < N - 1: 
square_id = (i * (N - 1)) + j 
# 1st triangle of the square 
indices[square_id * 6 + 0] = i * N + j 
indices[square_id * 6 + 1] = (i + 1) * N + j 
indices[square_id * 6 + 2] = i * N + (j + 1) 
# 2nd triangle of the square 
indices[square_id * 6 + 3] = (i + 1) * N + j + 1 
indices[square_id * 6 + 4] = i * N + (j + 1) 
indices[square_id * 6 + 5] = (i + 1) * N + j
links = [[-1, 0], [1, 0], [0, -1], [0, 1], [-1, -1], [1, -1], [-1, 1], [1, 1]]
links = [ti.Vector(v) for v in links]
@ti.kernel
def step(): 
for i in ti.grouped(x): 
v[i].y -= gravity * dt 
for i in ti.grouped(x): 
force = ti.Vector([0.0,0.0,0.0]) 
for d in ti.static(links): 
j = min(max(i + d, 0), [N-1,N-1]) 
relative_pos = x[j] - x[i] 
current_length = relative_pos.norm() 
original_length = cell_size * float(i-j).norm() 
if original_length != 0: 
force +=stiffness * relative_pos.normalized() * (current_length - original_length) / original_length 
v[i] +=force * dt 
for i in ti.grouped(x): 
v[i] *= ti.exp(-damping * dt) 
if (x[i]-ball_center[0]).norm() <= ball_radius: 
v[i] = ti.Vector([0.0, 0.0, 0.0]) 
x[i] += dt * v[i]
@ti.kernel
def set_vertices(): 
for i, j in ti.ndrange(N, N): 
vertices[i * N + j] = x[i, j]
init_scene()
set_indices()
window = ti.ui.Window("Cloth", (800, 800), vsync=True)
canvas = window.get_canvas()
scene = ti.ui.Scene()
camera = ti.ui.make_camera()
while window.running: 
for i in range(30): 
step()
set_vertices()
camera.position(0.5, -0.5, 2) 
camera.lookat(0.5, -0.5, 0) 
scene.set_camera(camera)
scene.point_light(pos=(0.5, 1, 2), color=(1, 1, 1)) 
scene.mesh(vertices, indices=indices, color=(0.5, 0.5, 0.5), two_sided = True) 
scene.particles(ball_center, radius=ball_radius, color=(0.5, 0, 0)) 
canvas.scene(scene) 
window.show()

注意到,上述代码总行数仅有91行!

挑战任务

我希望你喜欢本文中提供的上述示例程序!如果的确如此,下面几个不同挑战等级的任务留给你:

  • 【Einfach】Passen Sie Parameter beiläufig an: Beobachten Sie, wie Änderungen an Steifigkeits-, Dämpfungs- und dt-Parametern das Verhalten des Programms verändern.
  • 【Einfach】Ändern Sie vsync=True im Programm in vsync=False. Dadurch wird die Beschränkung auf 60 Bilder pro Sekunde für das Programm aufgehoben und Sie können beobachten, wie das Programm auf Ihrem Computer ausgeführt wird.
  • 【Mittlere Schwierigkeit】Erzielen Sie eine leicht komplexe Interaktion zwischen dem Tuch und dem Ball: Lassen Sie es am Ball heruntergleiten, ohne ihn zu durchdringen.
  • 【Mittlere Schwierigkeit】Weitere Bälle hinzufügen: Lassen Sie das Tuch mit mehreren Bällen interagieren.
  • 【Schwierigkeitsgrad für Fortgeschrittene】 Versuchen Sie nach Abschluss der zweiten Herausforderung, dasselbe Programm in einer anderen Programmiersprache oder Python zu implementieren, jedoch ohne Taichi. Beobachten Sie, wie hoch die maximale FPS (Bilder pro Sekunde) ist, die Sie erreichen können, und wie viel Code Sie schreiben müssen, um eine ähnliche Leistung zu erzielen.

Zusammenfassung

Lassen Sie uns abschließend noch einmal überprüfen, was Taichi uns ermöglicht hat, in den 91 Zeilen des Python-Codes oben zu implementieren:

  • simulierte ein Masse-Feder-System mit über zehntausend Massepunkten und etwa einhunderttausend Federn.
  • Automatische Parallelisierung von Simulationen über CUDA-Multithreading auf GPU oder CPU mithilfe der @ti.kernel-Annotation.
  • Rendern Sie Ergebnisse in Echtzeit über den GPU-Renderer.

Taichi ermöglicht es uns nicht nur, all diese komplexen Funktionen mit einer kleinen Menge an zu implementieren Code, aber es erspart uns die Mühe, CUDA, Multi-Thread-Programmierung oder GPU-Rendering zu lernen. Mit Taichi kann jeder leistungsstarke Programme schreiben. Sie können sich auf die algorithmischen Aspekte des Codes konzentrieren und die Leistungsaspekte der Programmiersprache selbst überlassen. Das bringt uns zu Taichis Motto: Parallele Programmierung für alle!

Um mehr über Taichi zu erfahren, besuchen Sie bitte seine ​​Github-Seite​​, auf der Sie auch eine ausführliche Dokumentation finden viele Beispiele von Taichi-Projekten, die alle sehr interessant sind. Wenn Sie schließlich auch an die Mission glauben, eine benutzerfreundliche und leistungsstarke Sprache für paralleles Rechnen zu entwickeln, sind Sie herzlich willkommen, sich Taichi als Open-Source-Mitwirkender anzuschließen.

In meinem nächsten Artikel werde ich das Innenleben von Taichi besprechen und wie es mit der GPU auf verschiedenen Plattformen für Berechnung und Rendering interagiert. Bis dahin werden Sie mit dem fröhlichen Taichi-Programmieren beginnen!

Übersetzer-Einführung

Zhu Xianzhong, 51CTO-Community-Redakteur, 51CTO-Experten-Blogger, Dozent, Computerlehrer an einer Universität in Weifang und ein Veteran in der freiberuflichen Programmierbranche. In den Anfängen konzentrierte er sich auf verschiedene Microsoft-Technologien (stellte drei technische Bücher zu ASP.NET AJX und Cocos 2d-X zusammen). In den letzten zehn Jahren widmete er sich der Open-Source-Welt (vertraut mit beliebten Vollversionen). Stack-Webentwicklungstechnologie) und lernte OneNet/AliOS+Arduino/ESP32/Raspberry Pi und andere IoT-Entwicklungstechnologien sowie Scala+Hadoop+Spark+Flink und andere Big-Data-Entwicklungstechnologien kennen.

Originaltitel: A Beginner's Guide to High-Performance Computing in Python, Autor: Dunfan Lu

Das obige ist der detaillierte Inhalt vonEine Einführung in das Hochleistungsrechnen mit Python basierend auf Taichi. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:51cto.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen