ホームページ  >  記事  >  バックエンド開発  >  Taichi をベースにした Python によるハイパフォーマンス コンピューティングの入門ガイド

Taichi をベースにした Python によるハイパフォーマンス コンピューティングの入門ガイド

王林
王林転載
2023-04-12 08:46:131239ブラウズ

Python プログラミング言語の誕生以来、その中心となる哲学は、コードの読みやすさと単純さを最大限に高めることでした。 Python の読みやすさとシンプルさの追求は、ほとんど狂気の沙汰です。これは 1 つの事実から確認できます。Python システムのルート ディレクトリに「import this」コマンドを入力して Enter キーを押すと、すぐに小さな英語の詩が表示されます。これを中国語に翻訳すると、おおよそ次のような意味になります。

"美しいことは醜いことよりも優れており、明示的なことは暗黙的なことよりも優れています。

単純なほうが複雑なより優れており、複雑なほうが複雑なほうが優れています。

フラットなほうがネストされたよりも優れており、スパースなほうがより優れています。

読みやすさは非常に重要です...」

単純さは複雑さより優れており、読みやすさは非常に重要です。 Python が実際にこれらの目標を達成するのに非常に成功していることは疑いの余地がありません。Python は学習するのに断然最もユーザーフレンドリーな言語であり、通常の Python プログラムは通常、同等の C コードよりも 5 ~ 10 倍短いです。残念ながら、そこには落とし穴があります: Python の単純さはパフォーマンスを犠牲にしています! 実際、Python プログラムは C プログラムよりも 10 ~ 100 倍遅いです。したがって、速度と単純さの間には恒久的なトレードオフが存在し、どのプログラミング言語も両方を兼ね備えることは不可能であると考えられます。

しかし、心配しないでください。すべての希望が失われるわけではありません。

Taichi は両方の長所を提供します

Taichi プログラミング言語は、汎用の高性能コンピューティングをサポートする構造で Python プログラミング言語を拡張する試みです。マルチコア CPU 機能や、さらに重要な GPU パフォーマンスなど、コンピューターのすべてのコンピューティング能力を活用しながら、Python へのシームレスな埋め込みをサポートします。

この記事では、Taichi を使用して作成したサンプル プログラムを紹介します。このプログラムは GPU を使用して、球体上に落ちる布片のリアルタイムの物理シミュレーションを実行し、同時に結果をレンダリングします。

リアルタイム GPU 物理シミュレーターを作成するのは簡単な作業ではありませんが、このルーチンを実装する Taichi ソース コードは非常にシンプルです。この記事の残りの部分では、実装全体を説明して、Taichi が提供するものと、それがいかに強力で使いやすいかを理解できるようにします。

始める前に、このプログラムが何行のコードで構成されているか推測したほうがよいでしょう。もちろん、答えは記事の最後にあります。

アルゴリズムの概要

私たちのプログラムは、一枚の布を質量バネシステムとしてモデル化します。より具体的には、このクロスを点塊の N×N グリッドとして表し、隣接する点がバネで接続されています。スタンフォード大学の Matthew Fisher が提供した以下の図は、この構造を示しています。

Taichi をベースにした Python によるハイパフォーマンス コンピューティングの入門ガイド

この質量バネ システムの動きは、次の 4 つの要素の影響を受けます:

    重力
  • バネの内部力
  • ダンプニング
  • 真ん中に挟まれた赤いボールとの衝突
簡単のため、布の自己衝突は無視します。プログラムは t=0 から始まります。次に、シミュレーションの各ステップで、小さな定数 dt だけ時間を進めます。プログラムは、上記の 4 つの要因のそれぞれの影響を評価することで、この短い期間にシステムに何が起こるかを推定し、タイム ステップの終了時に各質点の位置と速度を更新します。更新された粒子の位置は、画面上にレンダリングされるイメージを更新するために使用されます。

プログラムの開始

Taichi はそれ自体がプログラミング言語ですが、Python パッケージとして存在しており、pip install Taichi を実行するだけでインストールできます。

Taichi を Python プログラムで使用するには、まずエイリアス ti を使用して Taichi をインポートする必要があります:

import taichi as ti

マシンに CUDA をサポートする Nvidia GPU が搭載されている場合、Taichi プログラムのパフォーマンスは最大化されます。この場合は、上記の import ステートメントの後に次のコード行を追加します。

ti.init(arch=ti.cuda)

CUDA GPU がない場合でも、ti.metal などの他のグラフィック API を介して Taichi にアクセスできます。 ti.vulkan および ti.opengl ) を使用して GPU と対話します。ただし、Taichi のこれらの API のサポートは、CUDA のサポートほど包括的ではありません。したがって、今のところ、計算バックエンドとして CPU を使用します。

ti.init(arch=ti.cpu)

心配しないでください。Taichi は、CPU のみで実行される場合でも、非常に高速に実行されます。 Taichi を初期化した後、マス スプリング クロスを記述するために使用されるデータ構造の宣言を開始できます。これを行うには、次のコード行を追加します。

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

これらの 3 行は、x と v をサイズ N × N の 2 次元配列として宣言します。ここで、配列の各要素は次の 3 次元ベクトルです。浮動小数点数。 Taichi では、配列は「フィールド」と呼ばれ、2 つのフィールドはそれぞれ点の位置と速度を記録します。 CUDA GPU で実行するように Taichi を初期化すると、これらのフィールド/配列は GPU メモリに自動的に保存されることに注意してください。布に加えて、中央のボールも定義する必要があります。

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

ここでは、ボールの中心はサイズ 1 の 1D フィールドで、その個々のコンポーネントは 3D float ベクトルです。必須フィールドを宣言した後、これらのフィールドを t=0 の対応するデータで初期化しましょう。同じ行または列上の隣接する点のペアについて、それらの間の距離が cell_size=1.0/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])

ここでは、各 x[i,j] 値の背後にある意味を心配する必要はありません。単純に、クロスが 45 になるように選択されています。度角は下図を参照してください。

Taichi をベースにした Python によるハイパフォーマンス コンピューティングの入門ガイド

模拟

在每个时间步中,我们的程序都会模拟影响布料运动的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行!

挑战任务

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

  • [簡単] パラメータを気軽に調整します。剛性、減衰、dt パラメータを変更すると、プログラムの動作がどのように変化するかを観察します。
  • [簡単] プログラム内の vsync=True を vsync=False に変更します。これにより、プログラムの 60 フレーム/秒の制限が解除され、プログラムがマシン上でどのように実行されるかを監視します。
  • 【中難易度】布とボールの間の少し複雑な相互作用を実現します。布を貫通せずにボールの上を滑り落ちます。
  • 【中難易度】ボールを追加: 布を複数のボールと相互作用させます。
  • 【難易度上級】2 番目の課題を完了したら、Taichi を使用せずに、同じプログラムを別のプログラミング言語または Python で実装してみます。取得できる最大 FPS (1 秒あたりのフレーム数) と、同様のパフォーマンスを得るために記述する必要があるコードの量を観察します。

まとめ

最後に、Taichi によって上記の 91 行の Python コードで実装できるようになった内容を確認してみましょう。10,000 個の質点と約 100,000 個のバネを備えた質量バネ システムです。

    @ti.kernel アノテーションを使用して、CUDA GPU または CPU 上のマルチスレッドを介してシミュレーションを自動的に並列化します
  • GPU レンダラーを介してリアルタイムで結果をレンダリングします
  • Taichi notこれにより、これらすべての複雑な関数を少量のコードで実装できるようになり、CUDA、マルチスレッド プログラミング、または GPU レンダリングを学習する手間が省けます。 Taichi を使えば誰でも高性能なプログラムを書くことができます。コードのアルゴリズムの側面に焦点を当て、パフォーマンスの側面はプログラミング言語自体に任せることができます。これにより、Taichi のモットーである「並列プログラミングをすべての人に!
Taichi について詳しく知りたい場合は、その

Github ページ

にアクセスしてください。詳細なドキュメントと Taichi プロジェクトの多くの例が見つかり、どれも興味深いものです。最後に、並列コンピューティング用のフレンドリーで強力な言語を開発するという使命を信じているのであれば、オープンソースのコントリビューターとして Taichi に参加することを大歓迎です。

次回の記事では、Taichi の内部動作と、Taichi が計算とレンダリングのためにさまざまなプラットフォーム上の GPU とどのように対話するかについて説明します。それまでに、あなたは幸せな Taichi プログラミングを始めるでしょう!

翻訳者紹介

Zhu Xianzhong 氏、51CTO コミュニティー編集者、51CTO エキスパートブロガー、講師、濰坊の大学のコンピューター教師、そして大学のベテランフリーランスのプログラミング業界。初期の頃は、さまざまな Microsoft テクノロジに焦点を当てていました (ASP.NET AJX および Cocos 2d-X に関連する 3 冊の技術書籍を編集しました)。過去 10 年間は、オープンソースの世界に専念してきました (人気のある完全なソースに精通しています)。スタックWeb開発技術)を学び、OneNet/AliOS Arduino/ESP32/Raspberry PiなどのIoT開発技術やScala Hadoop Spark Flinkなどのビッグデータ開発技術について学びました。

原題: A Beginner's Guide to High-Performance Computing in Python、著者: Dunfan Lu

以上がTaichi をベースにした Python によるハイパフォーマンス コンピューティングの入門ガイドの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事は51cto.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。