PyTorch CUDAでカーネル実行時間を測定する方法とは? `torch.cuda.synchronize` を使いこなそう
torch.cuda.synchronize
は、PyTorch CUDA で実行中のすべてのカーネルが完了するまで CPU をブロックする関数です。CUDA カーネルは非同期に実行されるため、この関数は、以下の状況で役立ちます。
- 複数の CUDA ストリームを使用する場合
複数のストリームでカーネルを実行している場合、torch.cuda.synchronize
を使用して、ストリーム間の同期を取ることができます。 - カーネルの結果を CPU で使用する前に、確実に完了していることを確認する場合
例えば、カーネルの結果を別の計算に使用したり、ファイルに保存したりする前に、torch.cuda.synchronize
を呼び出して同期を取ることが重要です。 - カーネルの実行時間を測定する場合
torch.cuda.synchronize
をカーネル実行の前後に呼び出すことで、正確な実行時間を測定できます。
使い方
torch.cuda.synchronize
の使い方は非常に簡単です。以下のコード例のように、関数呼び出しだけで使用できます。
import torch
# デバイスを CUDA デバイスに設定
device = torch.device("cuda")
# テンソルを CUDA デバイスに移動
x = torch.tensor([1, 2, 3], device=device)
# カーネルを実行
y = x + 2
# カーネルの実行を同期
torch.cuda.synchronize()
# 結果を CPU にコピー
z = y.cpu()
print(z)
このコード例では、以下の処理が行われます。
- デバイスを CUDA デバイスに設定します。
- テンソル
x
を CUDA デバイスに移動します。 - カーネル
y = x + 2
を実行します。 torch.cuda.synchronize
を呼び出して、カーネルの実行を同期します。- 結果
y
を CPU にコピーします。 - 結果
z
をプリントします。
- 複数の CUDA ストリームを使用する場合、
torch.cuda.synchronize
でどのストリームを同期するかを指定できます。詳細については、PyTorch のドキュメントを参照してください。 - カーネルの結果を CPU で使用する必要がない場合は、
torch.cuda.synchronize
を呼び出す必要はありません。 torch.cuda.synchronize
は、CPU をブロックするため、多用するとパフォーマンスが低下する可能性があります。
カーネル実行時間の測定
import torch
import time
# デバイスを CUDA デバイスに設定
device = torch.device("cuda")
# テンソルを CUDA デバイスに移動
x = torch.randn(1000, 1000, device=device)
# 開始時刻を記録
start = time.time()
# カーネルを実行
y = x.mm(x)
# カーネルの実行を同期
torch.cuda.synchronize()
# 終了時刻を記録
end = time.time()
# 実行時間を計算
elapsed_time = end - start
print(f"カーネル実行時間: {elapsed_time:.3f}秒")
- デバイスを CUDA デバイスに設定します。
- ランダムなテンソル
x
を CUDA デバイスに移動します。 - 開始時刻を記録します。
- カーネル
y = x.mm(x)
を実行します。 torch.cuda.synchronize
を呼び出して、カーネルの実行を同期します。- 終了時刻を記録します。
- 実行時間を計算してプリントします。
複数の CUDA ストリームの同期
このコード例では、torch.cuda.synchronize
を使用して複数の CUDA ストリームを同期する方法を示します。
import torch
import torch.cuda.streams as streams
# デバイスを CUDA デバイスに設定
device = torch.device("cuda")
# ストリームを作成
stream1 = streams.Stream(device=device)
stream2 = streams.Stream(device=device)
# テンソルを CUDA デバイスに移動
x = torch.randn(1000, 1000, device=device)
# ストリーム1 でカーネルを実行
with torch.cuda.stream(stream1):
y1 = x.mm(x)
# ストリーム2 でカーネルを実行
with torch.cuda.stream(stream2):
y2 = x + x
# ストリーム1 とストリーム2 を同期
torch.cuda.synchronize(stream=stream1)
torch.cuda.synchronize(stream=stream2)
# 結果を CPU にコピー
z1 = y1.cpu()
z2 = y2.cpu()
print(f"ストリーム1 の結果: {z1}")
print(f"ストリーム2 の結果: {z2}")
- デバイスを CUDA デバイスに設定します。
- 2つの CUDA ストリーム
stream1
とstream2
を作成します。 - ランダムなテンソル
x
を CUDA デバイスに移動します。 - ストリーム1 でカーネル
y1 = x.mm(x)
を実行します。 - ストリーム2 でカーネル
y2 = x + x
を実行します。 torch.cuda.synchronize(stream=stream1)
を呼び出して、ストリーム1 のカーネル実行を同期します。torch.cuda.synchronize(stream=stream2)
を呼び出して、ストリーム2 のカーネル実行を同期します。- 結果
y1
とy2
を CPU にコピーします。 - 結果
z1
とz2
をプリントします。
このコード例では、torch.cuda.synchronize
を使用してカーネル結果をファイルに保存する方法を示します。
import torch
import torch.cuda.streams as streams
import torch.save
# デバイスを CUDA デバイスに設定
device = torch.device("cuda")
# ストリームを作成
stream = streams.Stream(device=device)
# テンソルを CUDA デバイスに移動
x = torch.randn(1000, 1000, device=device)
# カーネルを実行
with torch.cuda.stream(stream):
y = x.mm(x)
# カーネルの実行を同期
torch.cuda.synchronize(stream=stream)
# 結果を CPU にコピー
z = y.cpu()
# 結果をファイルに保存
torch.save(z, "result.pt")
- デバイスを
以下に、torch.cuda.synchronize
の代替方法として検討すべき3つのアプローチをご紹介します。
イベントを使用した同期
CUDA イベントは、カーネルの実行完了を通知するために使用できるオブジェクトです。torch.cuda.Event
を使用して、カーネル実行前後でイベントを作成し、event.wait()
を呼び出すことで同期を取ることができます。
import torch
import torch.cuda.events as events
# デバイスを CUDA デバイスに設定
device = torch.device("cuda")
# テンソルを CUDA デバイスに移動
x = torch.randn(1000, 1000, device=device)
# イベントを作成
event = events.Event(device=device)
# カーネルを実行
with torch.cuda.stream(stream=stream):
y = x.mm(x)
# カーネルの実行完了をイベントに記録
event.record(stream=stream)
# イベントが完了するまで待機
event.wait()
# 結果を CPU にコピー
z = y.cpu()
# 結果をファイルに保存
torch.save(z, "result.pt")
この方法の利点は、torch.cuda.synchronize
と比較してよりきめ細かな制御が可能であることです。特定のカーネルのみを同期したり、複数のストリーム間で同期を取ったりすることができます。
非同期転送を使用したデータコピー
カーネルの結果を CPU にコピーする必要がある場合は、torch.cuda.synchronize
を使用する代わりに、非同期転送を使用することができます。torch.asynchronous_copy
関数を使用して、カーネルの実行中にデータコピーを非同期で開始できます。
import torch
import torch.cuda.memory as memory
# デバイスを CUDA デバイスに設定
device = torch.device("cuda")
# テンソルを CUDA デバイスに移動
x = torch.randn(1000, 1000, device=device)
# カーネルを実行
y = x.mm(x)
# 非同期で結果を CPU にコピー
z = memory.empty(y.size(), dtype=y.dtype, device="cpu")
memory.copy_async(z, y)
# カーネルの実行を継続
# ...
# 結果を使用可能になるまで待機
z.wait()
# 結果を処理
# ...
この方法の利点は、torch.cuda.synchronize
を使用するよりもパフォーマンスが向上する場合があることです。ただし、データが CPU にコピーされるまで、結果を使用できないことに注意する必要があります。
CUDA Streams を適切に使用する
CUDA Streams を適切に使用する事で、明示的な同期操作を減らすことができます。CUDA Streams は、カーネル実行を論理的にグループ化し、実行順序を制御する方法を提供します。互換性のない操作間で同期が必要になる場合を除き、異なるストリームで実行されるカーネル間での同期は不要です。