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)

このコード例では、以下の処理が行われます。

  1. デバイスを CUDA デバイスに設定します。
  2. テンソル x を CUDA デバイスに移動します。
  3. カーネル y = x + 2 を実行します。
  4. torch.cuda.synchronize を呼び出して、カーネルの実行を同期します。
  5. 結果 y を CPU にコピーします。
  6. 結果 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}秒")
  1. デバイスを CUDA デバイスに設定します。
  2. ランダムなテンソル x を CUDA デバイスに移動します。
  3. 開始時刻を記録します。
  4. カーネル y = x.mm(x) を実行します。
  5. torch.cuda.synchronize を呼び出して、カーネルの実行を同期します。
  6. 終了時刻を記録します。
  7. 実行時間を計算してプリントします。

複数の 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}")
  1. デバイスを CUDA デバイスに設定します。
  2. 2つの CUDA ストリーム stream1stream2 を作成します。
  3. ランダムなテンソル x を CUDA デバイスに移動します。
  4. ストリーム1 でカーネル y1 = x.mm(x) を実行します。
  5. ストリーム2 でカーネル y2 = x + x を実行します。
  6. torch.cuda.synchronize(stream=stream1) を呼び出して、ストリーム1 のカーネル実行を同期します。
  7. torch.cuda.synchronize(stream=stream2) を呼び出して、ストリーム2 のカーネル実行を同期します。
  8. 結果 y1y2 を CPU にコピーします。
  9. 結果 z1z2 をプリントします。

このコード例では、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")
  1. デバイスを


以下に、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 は、カーネル実行を論理的にグループ化し、実行順序を制御する方法を提供します。互換性のない操作間で同期が必要になる場合を除き、異なるストリームで実行されるカーネル間での同期は不要です。