<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>GPU - まったり勉強ノート</title>
	<atom:link href="https://www.mattari-benkyo-note.com/tag/gpu/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.mattari-benkyo-note.com</link>
	<description>shuの日々の勉強まとめ</description>
	<lastBuildDate>Tue, 11 Feb 2025 23:50:01 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.3</generator>
<site xmlns="com-wordpress:feed-additions:1">189243286</site>	<item>
		<title>小型LLM PLaMo 2 1BをGoogle Colabの無料枠の範囲で使ってみる</title>
		<link>https://www.mattari-benkyo-note.com/2025/02/12/plamo-2-1b-infernece/</link>
					<comments>https://www.mattari-benkyo-note.com/2025/02/12/plamo-2-1b-infernece/#respond</comments>
		
		<dc:creator><![CDATA[Shuji Suzuki (shu)]]></dc:creator>
		<pubDate>Tue, 11 Feb 2025 23:30:00 +0000</pubDate>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[GPU]]></category>
		<category><![CDATA[llm]]></category>
		<category><![CDATA[python]]></category>
		<guid isPermaLink="false">https://www.mattari-benkyo-note.com/?p=3195</guid>

					<description><![CDATA[<p>先日Preferred Networksとその子会社のPreferred Elementsが共同で開発した1Bサイズの小型のLLM、PLaMo 2 1Bがリリースされました。 私自身、開発にかかわっているメンバーの一人で [&#8230;]</p>
<p>The post <a href="https://www.mattari-benkyo-note.com/2025/02/12/plamo-2-1b-infernece/">小型LLM PLaMo 2 1BをGoogle Colabの無料枠の範囲で使ってみる</a> first appeared on <a href="https://www.mattari-benkyo-note.com">まったり勉強ノート</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>先日Preferred Networksとその子会社のPreferred Elementsが共同で開発した1Bサイズの小型のLLM、<a href="https://huggingface.co/pfnet/plamo-2-1b" title="PLaMo 2 1B">PLaMo 2 1B</a>がリリースされました。</p>



<p>私自身、開発にかかわっているメンバーの一人です。このモデルは1Bという小さいサイズで手軽に動かすことができます。このため、今回はGoogle Colabの無料枠の範囲で簡単に使う方法を紹介します。</p>



<p>ちなみに今回紹介するGoogle Colabのコードはこちらにあげてあります。<br><a href="https://github.com/shu65/plamo-2-1b-examples/blob/main/plamo_2_1b_inference_example_google_colab_t4.ipynb" target="_blank" rel="noopener" title="">https://github.com/shu65/plamo-2-1b-examples/blob/main/plamo_2_1b_inference_example_google_colab_t4.ipynb</a></p>



<p><br>コードだけ見たいという方はこちらをご覧ください。動作に関しては2025/02/11現在のGoogle Colabで動くことは確認してありますが、時間がたつと動かなくなる可能性があるので注意してください。</p>



<h2 class="wp-block-heading">そもそもPLaMo 2 1Bとはどういうモデルか？</h2>



<p>まず、このモデルがどういうモデルか知っておくと、いろいろトラブルに対処する心構えができると思ったので、簡単にこのモデルについて説明します。</p>



<p>このLLMは、LLaMaなどに代表されるような、よくあるオープンな他のLLMとは違い、独自路線を突っ走っているモデルです。一番際立って違う点として、状態空間モデル（Sate Space Model, SSM）とSliding Window Attensionを組み合わせた<a href="https://arxiv.org/abs/2406.07522v1" title="Samba">Samba</a>で提案されたアーキテクチャベースのモデルになっています。知っている人からすると「マジで？」と思うかもしれませんが、マジです。詳しくはこちらをご覧ください。（アーキテクチャパートはこの辺いろいろ頑張ってくれてたPFEメンバーの力作の説明になっています）</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-preferred-networks-research-amp-development wp-block-embed-preferred-networks-research-amp-development"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="te92HKKFxD"><a href="https://tech.preferred.jp/ja/blog/plamo-2/">大規模言語モデルの次期バージョン PLaMo 2 の事前検証: SSMの採用と合成データによる性能改善の取り組み</a></blockquote><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;大規模言語モデルの次期バージョン PLaMo 2 の事前検証: SSMの採用と合成データによる性能改善の取り組み&#8221; &#8212; Preferred Networks Research &amp; Development" src="https://tech.preferred.jp/ja/blog/plamo-2/embed/#?secret=te92HKKFxD" data-secret="te92HKKFxD" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>これ以外にも際立って違う部分としてTokenizerがあります。こちらも特に既存のモデルと違う点として、トークン効率を上げるために英語でもスペース区切りをやめている点などがあります。私も最初「スペース区切りやめます！」って言われた時は「マジかよ」って思いましたが、マジで採用しました。これ以外の工夫も開発した方が熱い思いを記事にしていますので興味がある人はご覧ください。</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-preferred-networks-research-amp-development wp-block-embed-preferred-networks-research-amp-development"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="uDU82SGZDa"><a href="https://tech.preferred.jp/ja/blog/plamo-2-tokenizer/">大規模言語モデル PLaMo 2 のためのトークナイザ性能改善</a></blockquote><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;大規模言語モデル PLaMo 2 のためのトークナイザ性能改善&#8221; &#8212; Preferred Networks Research &amp; Development" src="https://tech.preferred.jp/ja/blog/plamo-2-tokenizer/embed/#?secret=uDU82SGZDa" data-secret="uDU82SGZDa" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>さて、ここでいろいろ既存のモデルと違うということが伝わったかと思いますが、このようにいろいろ独自な部分があり、面白いモデルになっていると思いますが、一方で他のLLMの感覚で使っているとハマる部分があるので、ここからはPLaMo 2 1Bの推論するやり方に焦点を絞って説明していきます。</p>



<h2 class="wp-block-heading">PLaMo 2 1BをGoogle Colabの無料枠で使う</h2>



<p>Google Colabでの使い方に関してはすでにあげてくれている方もいます。記事にしていただきありがとうございます。</p>



<p><a href="https://qiita.com/autotaker1984/items/32109944a6a058161eee">https://qiita.com/autotaker1984/items/32109944a6a058161eee</a></p>



<p>こちらを見ると、有料でしか使えないL4というGPUでのみ動作確認が取れたと報告をいただきました。私も同じように試したところ、L4での動作は確認できたのですが、いろいろな人に使ってもらうためにはやはり無料枠の範囲で試せるほうが良いだろうと思っています。</p>



<p>このため、ここからは無料で使えるT4というGPUでPLaMo 2 1Bを動かす手順を紹介します。</p>



<p>まず、Google ColabでT4が使えるようにメニューバーから「ランタイム」→「ランタイムのタイプを変更」をクリックして、T4 GPUを選択しておいてください。</p>



<p>そして、まずは最初にPyTorchのバージョンを以下のように2.4系に落とします。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>!pip install torch==2.4.1 torchvision==0.19.1 torchaudio==2.4.1 --index-url https://download.pytorch.org/whl/cu124</code></pre></div>



<p>T4でPLaMo 2 1Bを動かすにはこのPyTorchのバージョンを落とすということが重要でした。</p>



<p>この後は以下のように他に必要なパッケージをインストールするだけになります。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>!pip install transformers&gt;=4.44.2 numba&gt;=0.60.0 causal-conv1d==1.4.0 mamba-ssm==2.2.2</code></pre></div>



<p>2025/02/11現在Googel Colab上で上記のコマンドを叩くと以下のようなバージョンのパッケージが入りました。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>causal-conv1d                      1.4.0
mamba-ssm                          2.2.2
numba                              0.61.0
numba-cuda                         0.0.17.1
sentence-transformers              3.4.1
torch                              2.4.1+cu124
torchaudio                         2.4.1+cu124
torchsummary                       1.5.1
torchvision                        0.19.1+cu124
transformers                       4.48.2</code></pre></div>



<p>インストールが終わればあとは簡単で、PLaMo 2 1BのREADMEにある通りに実行するだけになります。実行コードの例としては以下の通りです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>from transformers import AutoTokenizer, AutoModelForCausalLM

tokenizer = AutoTokenizer.from_pretrained(&quot;pfnet/plamo-2-1b&quot;, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(&quot;pfnet/plamo-2-1b&quot;, trust_remote_code=True)

text = &quot;これからの人工知能技術は&quot;
input_ids = tokenizer(text, return_tensors=&quot;pt&quot;).input_ids
generated_tokens = model.generate(
    inputs=input_ids,
    max_new_tokens=32,
    do_sample=True,
    top_k=50,
    top_p=0.95,
    temperature=1.0,
)[0]
generated_text = tokenizer.decode(generated_tokens)
print(generated_text)</code></pre></div>



<p>私が実行した際は以下のように出力されました。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>&lt;|plamo:bos|&gt;これからの人工知能技術は人間の脳機能をコンピュータに取り入れ、私たちの生活に様々な影響を与えると言われています。人工知能技術の進化により、社会に混乱が生じる可能性があり、AIの規制に関して議論</code></pre></div>



<p>入力で与えた文章の続きとして問題ない文章がちゃんと出力できていると思われます。</p>



<p>このようにT4でも問題なくPLaMo 2 1Bを動かすことができました。</p>



<h2 class="wp-block-heading">終わりに</h2>



<p>この記事ではGoogle ColabのPLaMo 2 1BをGoogle Colabで動かす手順を紹介しました。おそらくGoogle Colabじゃなくても<code>causal-conv1d</code>と<code>mamba-ssm</code> がサポートされている環境であれば動作すると思われます。逆に言えばこの二つがサポートしてない環境では使うのにいろいろ魔改造が必要な可能性があります。</p>



<p>この辺りはハマる人が多いと思われるので、知見がたまったらまた記事にしようと思います。</p>



<p>この記事を参考にみなさんもPLaMo 2 1Bで遊んでもらえればと思います。</p>



<p>PLaMo 2 1BをSupervised Fine-Tuning（SFT）するコードに関しても準備中で、動作確認は済んだので今週中にあげようと思いますのでお楽しみに！</p><p>The post <a href="https://www.mattari-benkyo-note.com/2025/02/12/plamo-2-1b-infernece/">小型LLM PLaMo 2 1BをGoogle Colabの無料枠の範囲で使ってみる</a> first appeared on <a href="https://www.mattari-benkyo-note.com">まったり勉強ノート</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://www.mattari-benkyo-note.com/2025/02/12/plamo-2-1b-infernece/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3195</post-id>	</item>
		<item>
		<title>PyTorch 2.0の新機能「torch.compile」使ってみた</title>
		<link>https://www.mattari-benkyo-note.com/2023/03/18/torch-compile/</link>
					<comments>https://www.mattari-benkyo-note.com/2023/03/18/torch-compile/#respond</comments>
		
		<dc:creator><![CDATA[Shuji Suzuki (shu)]]></dc:creator>
		<pubDate>Fri, 17 Mar 2023 22:20:45 +0000</pubDate>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[GPU]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[pytorch]]></category>
		<guid isPermaLink="false">https://www.mattari-benkyo-note.com/?p=2261</guid>

					<description><![CDATA[<p>今回は3/16についに出たPyTorch 2.0の目玉機能である「torch.comple」について実際に動かしてみて計算時間を測定してみたので、そのまとめになります。 時間計測の部分で測定に使ったコードはここにあげてあ [&#8230;]</p>
<p>The post <a href="https://www.mattari-benkyo-note.com/2023/03/18/torch-compile/">PyTorch 2.0の新機能「torch.compile」使ってみた</a> first appeared on <a href="https://www.mattari-benkyo-note.com">まったり勉強ノート</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>今回は3/16についに出たPyTorch 2.0の目玉機能である「torch.comple」について実際に動かしてみて計算時間を測定してみたので、そのまとめになります。</p>



<p>時間計測の部分で測定に使ったコードはここにあげてあります。</p>



<p><a href="https://github.com/shu65/pytorch_2_compile_example/blob/main/torch_2_0_compile.ipynb">https://github.com/shu65/pytorch_2_compile_example/blob/main/torch_2_0_compile.ipynb</a></p>



<h2 class="wp-block-heading">torch.compileとは？</h2>



<p>torch.compileはPyTorch 2.0の新機能で、PyTorchの複数の機能を組み合わせて使い関数や深層学習のモデルを実行時に最適化して、その後の呼び出して高速に実行できるようにする機能です。</p>



<p>torch.compileの中身の詳しい説明はここにかかれています。</p>



<p><a href="https://pytorch.org/get-started/pytorch-2.0/#technology-overview">https://pytorch.org/get-started/pytorch-2.0/#technology-overview</a></p>



<p>簡単に説明するとtorch.compileの中身としては以下の３つで構成されています。</p>



<ol class="wp-block-list">
<li>Graph acquisition: 計算グラフの構築</li>



<li>Graph lowering: PyTorchのオペレーションをバックエンドのデバイス（CPUやGPU）に特化した細かい命令に分解</li>



<li>Graph compilation: バックエンドのデバイス特化の命令を呼び出し</li>
</ol>



<p>これらのステップを経ることで、より効率よく計算リソースを使えるようにし、高速化を実現しています。</p>



<p>また、この機能のすばらしいところは使い方も非常に簡単であるというものがあります。以下にデコレータで使う方法とtorch.compileの関数を呼び出して使う方法を示します。</p>



<h3 class="wp-block-heading">デコレータで使うやり方</h3>



<p>まずデコレータで使う方法です。これは以下のようになります (このチュートリアルの例：<a href="https://pytorch.org/tutorials/intermediate/torch_compile_tutorial.html#basic-usage" target="_blank" rel="noopener" title="">https://pytorch.org/tutorials/intermediate/torch_compile_tutorial.html#basic-usage</a>)</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-file="torch_compile_decorate " data-lang="Python"><code>@torch.compile
def opt_foo2(x, y):
    a = torch.sin(x)
    b = torch.cos(x)
    return a + b
opt_foo2(torch.randn(10, 10), torch.randn(10, 10))</code></pre></div>



<p>torch.jit.scriptを使ったことがある方は、それと同じ感覚で使えるというと使い方がイメージしやすいかもしれません。</p>



<h3 class="wp-block-heading">torch.compileの関数を呼び出して使うやり方</h3>



<p>torch.compileの関数を呼び出してコンパイルする場合は以下のようにやります。(このチュートリアルの例：<a href="https://pytorch.org/tutorials/intermediate/torch_compile_tutorial.html#basic-usage" target="_blank" rel="noopener" title="">https://pytorch.org/tutorials/intermediate/torch_compile_tutorial.html#basic-usage</a>)</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-file="torch_compile" data-lang="Python"><code>class MyModule(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.lin = torch.nn.Linear(100, 10)

    def forward(self, x):
        return torch.nn.functional.relu(self.lin(x))

mod = MyModule()
opt_mod = torch.compile(mod)
opt_mod(torch.randn(10, 100))</code></pre></div>



<p>こちらもtorch.jit.scriptのときと同じような使い方だと思います。</p>



<h2 class="wp-block-heading">torch.compileによるパフォーマンスの評価</h2>



<p>次にtorch.compileを実際に使ってみたときの計算時間を計測したので、その紹介です。今回は以下の二つのGPUで測定しました。</p>



<ol class="wp-block-list">
<li>T4</li>



<li>V100</li>
</ol>



<p>T4はTuringなので公式のドキュメントでtorch.compileのサポートが書かれてないものになっています。ただ、やってみたら少し早くなったので、測定結果を載せています。GitHubにあげたコードはT4で測定したほうです。</p>



<p>また、CUDAのバージョンはどちらのケースも12.0利用し、測定に使ったモデルはチュートリアルにあったtorchvisionのResNet18を使用しました。</p>



<p>また、torch.compileにはモードが以下の３つあります。</p>



<ol class="wp-block-list">
<li>デフォルト</li>



<li>reduce-overhead</li>



<li>max-autotune</li>
</ol>



<p>これらと何もしてない場合も含めて合計４つパターンの測定をしています。</p>



<p>具体的な測定方法が分かりやすいようにコードの一部を紹介します（torch.compleのデフォルトの場合）。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-file="performance_measure" data-lang="Python"><code>import time 

import torch
import torchvision.models as models
import torch._dynamo

batch_size = 64
n_warmup_iters = 10
n_iters = 500

x = torch.randn(batch_size, 3, 224, 224).cuda()

def get_mode():
    return models.resnet18()

torch._dynamo.reset()

model = get_mode().cuda()
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

# compile
compiled_model = torch.compile(model)
for i in range(n_warmup_iters):
    optimizer.zero_grad()
    torch.cuda.synchronize()
    start = time.time()
    out = compiled_model(x)
    torch.cuda.synchronize()
    forward_elapsed_time = time.time() - start
    torch.cuda.synchronize()
    start = time.time()
    out.sum().backward()
    backward_elapsed_time = time.time() - start
    print(f&quot;with compile {i} iter forward: {forward_elapsed_time/1000:.3e} msec., backward: {backward_elapsed_time/1000:.3e} msec.&quot;)
    optimizer.step()

print(&quot;-&quot;*10)

torch.cuda.synchronize()
start = time.time()
for i in range(n_iters):
    optimizer.zero_grad()
    out = compiled_model(x)
    out.sum().backward()
    optimizer.step()
torch.cuda.synchronize()
elapsed_time = time.time() - start

print(f&quot;with compile total:{elapsed_time:.3e} sec. {batch_size*n_iters/elapsed_time:.3e} imgs/sec.&quot;)</code></pre></div>



<p>最初に、モデルの入力とモデルを作ったあと、コンパイルする場合は<code>torch.compile(model)</code>でコンパイルします。このときコンパイルのモードを変える場合は引数の<code>mode</code>にモードの名前を渡します。</p>



<p>その後、最初の数回はforward、backwardの呼び出し時にコンパイルなどのオーバーヘッドが入って遅いので、あらかじめ何度か呼びます。そして最後に実際に時間を計測します。今回は10回あらかじめforwardとbackwardを呼んでおいて、その後500回イテレーションを回したときの時間を測定しています。バッチサイズに関しては変化させると高速化率が変化することはわかっていますが、今回固定で64で実行しています。</p>



<p>T4, V100ともに同様の方法でtorch.compileのありなし等を測定しています。</p>



<p>では、時間計測の結果です。500回イテレーションを回したときの実際の計算時間を順番に示していきます。まずはT4の場合です。</p>



<figure class="wp-block-table"><table><tbody><tr><td></td><td>計算時間 (sec.)</td><td>torch.compileなしからの高速化率</td></tr><tr><td>torch.compileなし</td><td>78.68</td><td>1.00</td></tr><tr><td>torch.compile (default)</td><td>73.37</td><td>1.07</td></tr><tr><td>torch.compile (reduce-overhead)</td><td>77.52</td><td>1.01</td></tr><tr><td>torch.compile (max-autotune)</td><td>73.35</td><td>1.07</td></tr></tbody></table><figcaption class="wp-element-caption">T4を使ったResNet18の結果</figcaption></figure>



<p>T4はtorch.compileのサポートが書かれてない世代のGPUなので、効果が全くでないのかと思ったのですが、そんなことはなかったです。ただ、10％は満たない高速化にとどまっているという印象です。ちなみにT4を使ったケースではtorch.compileのmodeをmax-autotuneに変えると以下のようにサポートされてないGPUであると警告がでてきます。</p>



<pre class="wp-block-preformatted">[2023-03-17 18:31:06,314] torch._inductor.utils: [WARNING] not enough cuda cores to use max_autotune mode</pre>



<p>次にV100のResNet18の結果です。</p>



<figure class="wp-block-table"><table><tbody><tr><td></td><td>計算時間 (sec.)</td><td>torch.compileなしからの高速化率</td></tr><tr><td>torch.compileなし</td><td>26.6</td><td>1.00</td></tr><tr><td>torch.compile (default)</td><td>24.7</td><td>1.08</td></tr><tr><td>torch.compile (reduce-overhead)</td><td>24.2</td><td>1.10</td></tr><tr><td>torch.compile (max-autotune)</td><td>24.1</td><td>1.10</td></tr></tbody></table><figcaption class="wp-element-caption">V100を使ったResNet18の結果</figcaption></figure>



<p>V100のほうはtorch.compileのサポートされていると書かれているGPUです。実際、V100はtorch.compileのmodeをmax-autotuneに変えると確かにより速くなり、高速化率も最大値は10%台に入っています。</p>



<h2 class="wp-block-heading">現状のtorch.compileの注意点</h2>



<p>最後にtorch.compileの注意したほうがよさそうな点を書いておきます。</p>



<p>まず、公式で書かれいたものの紹介です。基本的な注意点はこのドキュメントに書いてあります。</p>



<p><a href="https://pytorch.org/get-started/pytorch-2.0/#pytorch-2x-faster-more-pythonic-and-as-dynamic-as-ever">https://pytorch.org/get-started/pytorch-2.0/#pytorch-2x-faster-more-pythonic-and-as-dynamic-as-ever</a></p>



<p>重要なものとして、現在提供されているtorch.compileの機能を最大限活かせるのはCPU、NVIDIAのVoltaとAmpere世代のGPUのみになっています。他のGPUでは使おうとすると警告が出てきます。ただ、私が試した範囲では警告がでるだけで現状では使えないわけではなさそうです。</p>



<p>また、私が使ったときに感じた注意点としては</p>



<ol class="wp-block-list">
<li>おそらくforwardとbackwardで別々にコンパイルが走るので、forward、backwardの両方とも最初は遅い</li>



<li>実行が遅いのは最初の１回目だけでなく、最初の数回の呼び出しが遅いケースがある</li>



<li>Google ColabなどでCellの実行を一度止めて再度実行しようとするとエラーがでて、ランタイムの再起動をしないと復帰できないケースがある</li>
</ol>



<p>1と２は時間計測をしようとしたときにはまったポイントです。まず、１に関してです。torch.compileの直後の呼び出しはコンパイルが走るので、遅いというのはドキュメントにも書かれています。ただ、forwadだけがおそいのかな？と思ってました。ただ、torch.compileの説明をちゃんと読めば想像できると思いますが、backwardも最初の実行のときは遅いです。なので、時間を計測するときは、forwardとbackwardの両方が遅いことを考慮して測定する必要があります。</p>



<p>次に２です。これに関しては私が見逃してなければドキュメントに明示的に説明が書いてあるわけではないのですが、<a href="https://pytorch.org/tutorials/intermediate/torch_compile_tutorial.html#torch-compile-tutorial" target="_blank" rel="noopener" title="チュートリアル">チュートリアル</a>の時間計測の結果や実際に測定してみるとどうやら遅いのは最初の１回目の呼び出しだけではなく、そのあと数回遅いケースが存在しているようです。このため、計算時間の測定の際、最初に数回呼び出してから測定しないとtorch.compileを使ったときよりも遅いみたいな誤った結果になるので注意してください。</p>



<p>最後に３です。これは何度かはまったのですが、どこかにキャッシュか何か残っているのか変なところで止めるとコード的には問題ないはずなのに、エラーがでるようになるときがあります。調べても解決方法が分からなかったので、エラーがでるようになったらランタイムごと再起動するということを何度かやりました。Google Colabでやるときは注意してください。</p>



<h2 class="wp-block-heading">終わりに</h2>



<p>今回はtorch.compileについて使ってみたのでまとめを書きました。去年発表があったときから楽しみにしていましたが、期待通りのものとなっていました。なにより使い方が非常に簡単なことには驚きました。</p>



<p>今回はT4とV100の測定結果でしたが、A100だとどうなるのかも今度測定しようかなと思っています。</p>



<p>この記事がみなさんのお役に立てば幸いです。</p><p>The post <a href="https://www.mattari-benkyo-note.com/2023/03/18/torch-compile/">PyTorch 2.0の新機能「torch.compile」使ってみた</a> first appeared on <a href="https://www.mattari-benkyo-note.com">まったり勉強ノート</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://www.mattari-benkyo-note.com/2023/03/18/torch-compile/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2261</post-id>	</item>
		<item>
		<title>CUDAの高速化の復習2023年版 Histogram（主にatomicAdd）編</title>
		<link>https://www.mattari-benkyo-note.com/2023/02/19/cuda-histogram/</link>
					<comments>https://www.mattari-benkyo-note.com/2023/02/19/cuda-histogram/#respond</comments>
		
		<dc:creator><![CDATA[Shuji Suzuki (shu)]]></dc:creator>
		<pubDate>Sat, 18 Feb 2023 22:34:39 +0000</pubDate>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[CUDA]]></category>
		<category><![CDATA[GPU]]></category>
		<guid isPermaLink="false">https://www.mattari-benkyo-note.com/?p=1804</guid>

					<description><![CDATA[<p>Reduction、vectrized memory accessに続き、今回はhistogramを題材にして主にatomicAddのパフォーマンスが最近どうなっているのかを見ていきたいと思います。 Histogramは [&#8230;]</p>
<p>The post <a href="https://www.mattari-benkyo-note.com/2023/02/19/cuda-histogram/">CUDAの高速化の復習2023年版 Histogram（主にatomicAdd）編</a> first appeared on <a href="https://www.mattari-benkyo-note.com">まったり勉強ノート</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>Reduction、vectrized memory accessに続き、今回はhistogramを題材にして主に<code>atomicAdd</code>のパフォーマンスが最近どうなっているのかを見ていきたいと思います。</p>



<p>HistogramはCUDA Samplesの中にもありますが、全然違う実装が、<a href="https://developer.download.nvidia.com/video/gputechconf/gtc/2020/presentations/s21819-optimizing-applications-for-nvidia-ampere-gpu-architecture.pdf" target="_blank" rel="noopener" title="NVIDIAのA100の最適化に関する発表の資料">NVIDIAのA100の最適化に関する発表の資料</a>の中で紹介されています。この資料では<code>atomicAdd</code>とL2キャッシュの「persistent data accesses」を利用してhistogramの実装をしています。このpersistent data accessesは発表当時気になっていて、あとで調べようと思って忘れてたのですが、最近調べたのでせっかくなので記事にしました。</p>



<p>今回調査するうえで特に知りたかった点としてpersistent data accessesを使う場合とshared memoryを使う場合だとどっちが速いのかというものがあります。A100の最適化の資料の中にはこれについて特に書いてなかったので、この二つの性能にどれくらい差があるのかの比較を行いました。</p>



<p>検証で使ったコードはこちらにあげてあります。</p>



<p><a href="https://github.com/shu65/cuda-histogram">https://github.com/shu65/cuda-histogram</a></p>



<h2 class="wp-block-heading">Histogramと今回対象とする部分に関して</h2>



<p>Histogramに関して知っている方も多いと思いますが、どういうものか簡単に紹介します。Histogramはデータの範囲をいくつかのbinに区切り、データの中の各要素がどのbinに含まれるかを計算し、binごとに含まれる要素の個数をカウントするというものになります。</p>



<p>上記の説明の通り、histogramを計算するうえで、大まかに3つくらいのステップに分けることができます。</p>



<ol class="wp-block-list">
<li>各binの範囲を決める</li>



<li>データの各要素がどのbinに入るのかを計算する</li>



<li>bin毎に何個のデータの要素が含まれるかをカウントする</li>
</ol>



<p>このHistogramは入力データによってどういう風にbinの範囲を決めればよいかが変わるため、データに応じて1,2あたりの処理はデータに応じて変化させる必要があります。また、GPU的にも難しいのは3のところなため、今回は3に注目して説明します。</p>



<p>3の部分は入力としては各要素がどのbinに入るかを表したbinのidの配列を受け取り、bin毎に何個要素があるかをカウントするという処理になります。どういう処理かイメージしやすいようにCPU版のコードを以下に示します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="histogram_cpu" data-lang="C++"><code>#include &lt;stdint.h&gt;

void HistogramCPU(const int *data, const uint32_t n, const uint32_t n_bins, uint32_t *bin_counts)
{
  for (uint32_t i = 0; i &lt; n_bins; ++i)
  {
    bin_counts[i] = 0;
  }

  for (uint32_t i = 0; i &lt; n; ++i)
  {
    const int bin_i = data[i];
    ++bin_counts[bin_i];
  }
}</code></pre></div>



<p>このあと、このコードと同じ結果になるようなGPUコードを紹介していきます。</p>



<h2 class="wp-block-heading">GPUでHistogramが難しい理由</h2>



<p>先にGPUでhistogramを計算する際、難しいポイントに関して触れておきます。GPUに限らず並列処理でhistogramを計算する際、bin毎のカウントをするところで複数のスレッドが同じメモリ領域にアクセスすることになるので、bin毎のカウント部分で排他制御が必要になります。</p>



<p>GPUで簡単に実装するなら、後ほど示す通り<code>atomicAdd</code>を使えばいいのですが、<code>atomicAdd</code>は遅いという問題があります。特にglobal memoryに対しての<code>atomicAdd</code>はshared memoryに対するものよりも遅いです。このため、個人的には<code>atomicAdd</code>、特にglobal memoryに対するものは注意が必要な計算という認識でした。</p>



<p>それがA100の最適化の資料でL2キャッシュのpersistent data accessesを使うとましになるよ、ということが書かれています。次にこのL2キャッシュのpersistent data accessesについて詳しく説明します。</p>



<h2 class="wp-block-heading">L2キャッシュのpersistent data accessesについて</h2>



<p>L2キャッシュのpersistent data accessesは、L2キャッシュのメモリ領域を分割してpersistent data用の領域を確保して、よくアクセスするものはpersistent data用の領域にキャッシュしてメモリアクセスを高速化するための機能です。</p>



<p>A100最適化の資料の18ページ目あたりからこの機能の紹介があります。</p>



<p><a href="https://developer.download.nvidia.com/video/gputechconf/gtc/2020/presentations/s21819-optimizing-applications-for-nvidia-ampere-gpu-architecture.pdf">https://developer.download.nvidia.com/video/gputechconf/gtc/2020/presentations/s21819-optimizing-applications-for-nvidia-ampere-gpu-architecture.pdf</a></p>



<p>CUDAのprogramming guideでは以下の部分に説明があります。</p>



<p><a href="https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#device-memory-l2-access-management">https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#device-memory-l2-access-management</a></p>



<p>これを使うと、よくデータアクセスする一部の領域とそれ以外の領域のキャッシュを分けることができます。結果として一部だけ何度もアクセスするという場合はこの機能を使うことで高速化が狙えます。</p>



<p>制限としてはL2キャッシュのすべてをpersistent dataにすることはできず、最大値が決まっています。最大値は以下のようにすると確認できます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>  cudaDeviceProp prop;
  CheckCudaErrors(cudaGetDeviceProperties(&prop, device_id));
  cout &lt;&lt; &quot;persistingL2CacheMaxSize:&quot; &lt;&lt;   prop.persistingL2CacheMaxSize &lt;&lt; endl;</code></pre></div>



<p>A100で確認すると30MBが最大値になっています。</p>



<p>使い方としてはprogramming guideにある通り、以下の手順で使うことができます。</p>



<h3 class="wp-block-heading">Persistent data accesses用の領域を確保</h3>



<p>以下のようにpersistent data accesses用の領域として最大どれくらい使うかを設定します。コード中の<code>size</code> にpersistent data accesses用の領域のサイズを入れて<code>cudaDeviceSetLimit</code>を呼ぶことで、使用するpersistent data accesses用の領域の最大値を設定します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-lang="C++"><code>cudaDeviceSetLimit(cudaLimitPersistingL2CacheSize, size); </code></pre></div>



<h3 class="wp-block-heading">Persistent data accessesの設定適用</h3>



<p>次にstream、もしくはcuda graphのnodeに対してpersistent data accessesの設定を行います。ここではstreamに対しての設定方法を示します。programming guideにある通り、以下のように設定していきます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>cudaStreamAttrValue stream_attribute;                                         // Stream level attributes data structure
stream_attribute.accessPolicyWindow.base_ptr  = reinterpret_cast&lt;void*&gt;(ptr); // Global Memory data pointer
stream_attribute.accessPolicyWindow.num_bytes = num_bytes;                    // Number of bytes for persistence access.
                                                                              // (Must be less than cudaDeviceProp::accessPolicyMaxWindowSize)
stream_attribute.accessPolicyWindow.hitRatio  = 1.0;                          // Hint for cache hit ratio
stream_attribute.accessPolicyWindow.hitProp   = cudaAccessPropertyPersisting; // Type of access property on cache hit
stream_attribute.accessPolicyWindow.missProp  = cudaAccessPropertyStreaming;  // Type of access property on cache miss.

//Set the attributes to a CUDA stream of type cudaStream_t
cudaStreamSetAttribute(stream, cudaStreamAttributeAccessPolicyWindow, &stream_attribute);</code></pre></div>



<p>注意する点としては<code>hitRatio</code> の値です。<code>hitRatio</code> はアクセスするglobal memoryのサイズが<code>num_bytes</code>よりも大きい場合は適切に指定しないとパフォーマンスが落ちることが以下の部分で示されています。</p>



<p><a href="https://docs.nvidia.com/cuda/cuda-c-best-practices-guide/#tuning-the-access-window-hit-ratio">https://docs.nvidia.com/cuda/cuda-c-best-practices-guide/#tuning-the-access-window-hit-ratio</a></p>



<p>このため、<code>hitRatio</code> は自分のケースでどのくらいのサイズにすべきか？を考えて設定したほうがよさそうです。</p>



<p>ただ、今回のhistogramの例では後ほど示す通り、global memoryで最大20MB分の領域をpersistent data accessesに利用するので、persistent data accessesで指定できるサイズに収まります。このため、<code>hitRatio</code> は1.0でOKです。</p>



<h2 class="wp-block-heading">HistogramのGPU実装</h2>



<p>ここからは今回検証に使うhistogramのGPU実装に関してです。3つありますので、順番にどういうものかを説明していきます。</p>



<h3 class="wp-block-heading">GPU実装のベースライン</h3>



<p>まずはGPU実装のベースラインです。コードとしてはCPUをそのままCUDAで実装したような形になっています。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="histogram_gpuv1" data-lang="C++"><code>__global__ void HistogramGPUv1Kernel(const int *data, const uint32_t n, uint32_t *bin_counts)
{
  const int tid = blockIdx.x * blockDim.x + threadIdx.x;
  if (tid &gt;= n)
  {
    return;
  }
  const int bin_i = data[tid];
  atomicAdd(bin_counts + bin_i, 1);
}</code></pre></div>



<p>こちらのコードがA100の最適化の資料で示されているhistogramのコードとほぼ同じものになっています。<code>bin_counts</code>へのアクセスは全スレッドが同時に行うため、<code>atomicAdd</code>を使って排他制御しながらカウントするようにしています。</p>



<h3 class="wp-block-heading">GPU実装のshared memory版</h3>



<p>Histogramの計算で<code>bin_counts</code>がshared memoryに収まる範囲であれば、shared memoryを使うという手があります。</p>



<p>先ほど説明した通り、shared memoryへの<code>atomicAdd</code>はglobal memoryに比べて速いので、shared memoryを使ってblock毎に集計し、その後各blockの結果を<code>atomicAdd</code>を使ってglobal memoryの領域に加算するという方法で計算します。こうすることでglobal memoryへの<code>atomicAdd</code>の回数は減らすことができます。コードとしては以下の通りです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="histogram_gpuv2" data-lang="C++"><code>__global__ void HistogramGPUv2Kernel(const int *data, const uint32_t n, const uint32_t n_bins, uint32_t *bin_counts)
{
  cg::thread_block cta = cg::this_thread_block();
  extern __shared__ uint32_t s_bin_counts[];
  const int tid = blockIdx.x * blockDim.x + threadIdx.x;
  const int stride = gridDim.x * blockDim.x;
  if (threadIdx.x &lt; n_bins)
  {
    s_bin_counts[threadIdx.x] = 0;
  }
  cg::sync(cta);
  for (int i = tid; i &lt; n; i += stride)
  {
    const int bin_i = data[i];
    atomicAdd(s_bin_counts + bin_i, 1);
  }
  cg::sync(cta);
  if (threadIdx.x &lt; n_bins)
  {
    uint32_t sum = s_bin_counts[threadIdx.x];
    atomicAdd(bin_counts + threadIdx.x, sum);
  }
}</code></pre></div>



<p>注意点としてはshared memoryのサイズは最大でA100の場合でも164KBらしいので、<code>bin_counts</code> に必要なサイズがこれ以上のときはこの戦略はそのまま使うことができません。</p>



<h3 class="wp-block-heading">GPU実装のshared memory + reduction版</h3>



<p>shared memory版では最後global memoryへの加算は<code>atomicAdd</code>を利用しましたが、この部分も<code>atomicAdd</code>なしで実行するようにします。具体的にはCUDAにおけるreductionのような戦略をとり、各blockが計算した結果をCUDAにおけるparallel reductionに似たアルゴリズムで集計します。コードとしては以下の通り。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="histogram_gpuv3" data-lang="C++"><code>__global__ void HistogramGPUv3Kernel(const int *data, const uint32_t n, const uint32_t n_bins, uint32_t *tmp_bin_counts)
{
  cg::thread_block cta = cg::this_thread_block();
  extern __shared__ uint32_t s_bin_counts[];
  const int tid = blockIdx.x * blockDim.x + threadIdx.x;
  const int tmp_bin_counts_offset = blockIdx.x * n_bins;
  const int stride = gridDim.x * blockDim.x;
  if (threadIdx.x &lt; n_bins)
  {
    s_bin_counts[threadIdx.x] = 0;
  }
  cg::sync(cta);
  for (int i = tid; i &lt; n; i += stride)
  {
    const int bin_i = data[i];
    atomicAdd(s_bin_counts + bin_i, 1);
  }
  cg::sync(cta);
  if (threadIdx.x &lt; n_bins)
  {
    uint32_t sum = s_bin_counts[threadIdx.x];
    tmp_bin_counts[tmp_bin_counts_offset + threadIdx.x] = sum;
  }
}

__global__ void HistogramGPUv3MergeKernel(const uint32_t *tmp_bin_counts, const int n, uint32_t *bin_counts)
{
  cg::thread_block cta = cg::this_thread_block();
  extern __shared__ uint32_t s_data[];

  uint32_t sum = 0;
  for (int i = threadIdx.x; i &lt; n; i += blockDim.x)
  {
    sum += tmp_bin_counts[blockIdx.x + i * blockDim.x];
  }
  s_data[threadIdx.x] = sum;
  for (uint stride = blockDim.x / 2; stride &gt; 0; stride &gt;&gt;= 1)
  {
    cg::sync(cta);
    if (threadIdx.x &lt; stride)
    {
      s_data[threadIdx.x] += s_data[threadIdx.x + stride];
    }
  }

  if (threadIdx.x == 0)
  {
    bin_counts[blockIdx.x] = s_data[0];
  }
}</code></pre></div>



<p>ちなみに、アルゴリズム的にこれはほぼCUDA Samplesに含まれる<a href="https://github.com/NVIDIA/cuda-samples/tree/master/Samples/2_Concepts_and_Techniques/histogram" target="_blank" rel="noopener" title="">histogram</a>と同じになります。</p>



<h2 class="wp-block-heading">評価</h2>



<p>今回、binの数で傾向が変わったので、以下の2種類のデータで比較します。</p>



<ol class="wp-block-list">
<li>入力データ数は256M個、binの数が256</li>



<li>入力データ数は256M個、binの数が5M個</li>
</ol>



<p>1つ目のほうがCUDA Samplesに含まれるhistogramの条件に近いもので、2つ目がA100の最適化の資料に書かれている条件になります。また、1のほうは先ほど紹介したアルゴリズムすべてが実行できますが、2つ目のほうはshared memoryが足りないのでベースラインのみとなっています。</p>



<p>また、persistent data accessesのありなしでどれくらい計算結果が変化するのかも知りたいので、各アルゴリズムで<code>bin_counts</code>の部分にpersistent data accessesを使う場合と使わなかった場合も比較します。</p>



<p>時間の計測方法としては10回の平均時間を算出して比較します。実行環境としてはCUDA 12.0、A100を利用しています。</p>



<p>計測した計算時間はそれぞれ以下の通りです。</p>



<figure class="wp-block-table"><table><tbody><tr><td></td><td>persistent data accessesなしの計算時間 (sec.)</td><td>persistent data accessesありの計算時間 (sec.)</td></tr><tr><td>ベースライン</td><td>0.0876</td><td>0.0876</td></tr><tr><td>shared memory版</td><td>0.0033</td><td>0.0033</td></tr><tr><td>shared memory + reduction版</td><td>0.0008</td><td>0.0008</td></tr></tbody></table><figcaption class="wp-element-caption">入力データ数は256M個、binの数が256のときの結果</figcaption></figure>



<figure class="wp-block-table"><table><tbody><tr><td></td><td>persistent data accessesなしの計算時間</td><td>persistent data accessesありの計算時間</td></tr><tr><td>ベースライン</td><td>0.0046</td><td>0.0043</td></tr></tbody></table><figcaption class="wp-element-caption">入力データ数は256M個、binの数が5M個</figcaption></figure>



<p>表からわかる通り、1のデータではglobal memoryへの<code>atomicAdd</code>が少なければ少ないほど高速化できていることがわかります。また、persistent data accessesは1のデータでは効果がありませんでした。</p>



<p>また、2のデータに関してはpersistent data accessesありなしで若干差がありますが、今回は約7%の向上と効果は小さいという結果になりました。A100の最適化のほうの資料では43%向上とあるのでどこか設定を間違えているのかもしれません。（いろいろ試しましたがわからなかったのでご存じの方いたら教えていただけるとありがたいです。）</p>



<p>ただ、結果からshared memoryが使える状況下であればL2キャッシュのチューニングするよりもshared memoryを使ったほうが速くなりそうという印象を持ちました。</p>



<h2 class="wp-block-heading">終わりに</h2>



<p>今回、気になっていた<code>atomicAdd</code>とL2キャッシュのpersistent data accessesのパフォーマンスについて調べました。結果としてやっぱりglobal memoryへの<code>atomicAdd</code>はできるだけ避けたほうがいいということが確認できてよかったです。</p>



<p>これまでも最近のGPU、CUDAを使ってreduction、vectorized memory accessに関しても調査したまとめを書いたのでもしよろしければそちらもご覧ください。</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-まったり勉強ノート wp-block-embed-まったり勉強ノート"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="iH6LeRTIHK"><a href="https://www.mattari-benkyo-note.com/2023/01/29/cuda-reduction2023/">CUDAの高速化の復習2023年版 Reduction編</a></blockquote><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;CUDAの高速化の復習2023年版 Reduction編&#8221; &#8212; まったり勉強ノート" src="https://www.mattari-benkyo-note.com/2023/01/29/cuda-reduction2023/embed/#?secret=HUywJF4LTh#?secret=iH6LeRTIHK" data-secret="iH6LeRTIHK" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<figure class="wp-block-embed is-type-wp-embed is-provider-まったり勉強ノート wp-block-embed-まったり勉強ノート"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="KWeKRdbYRw"><a href="https://www.mattari-benkyo-note.com/2023/02/14/cuda-vectorized-memory-access/">CUDAの高速化の復習2023年版 Vectorized Memory Access編</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;CUDAの高速化の復習2023年版 Vectorized Memory Access編&#8221; &#8212; まったり勉強ノート" src="https://www.mattari-benkyo-note.com/2023/02/14/cuda-vectorized-memory-access/embed/#?secret=d4XFrzEims#?secret=KWeKRdbYRw" data-secret="KWeKRdbYRw" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure><p>The post <a href="https://www.mattari-benkyo-note.com/2023/02/19/cuda-histogram/">CUDAの高速化の復習2023年版 Histogram（主にatomicAdd）編</a> first appeared on <a href="https://www.mattari-benkyo-note.com">まったり勉強ノート</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://www.mattari-benkyo-note.com/2023/02/19/cuda-histogram/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1804</post-id>	</item>
		<item>
		<title>CUDAの高速化の復習2023年版 Vectorized Memory Access編</title>
		<link>https://www.mattari-benkyo-note.com/2023/02/14/cuda-vectorized-memory-access/</link>
					<comments>https://www.mattari-benkyo-note.com/2023/02/14/cuda-vectorized-memory-access/#respond</comments>
		
		<dc:creator><![CDATA[Shuji Suzuki (shu)]]></dc:creator>
		<pubDate>Tue, 14 Feb 2023 00:42:18 +0000</pubDate>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[CUDA]]></category>
		<category><![CDATA[GPU]]></category>
		<guid isPermaLink="false">https://www.mattari-benkyo-note.com/?p=1797</guid>

					<description><![CDATA[<p>前回Reductionを例に今時のCUDAの高速化で何が効いているのか？を確認したまとめの記事を書きました。今回はその中には登場しなかったCUDAの高速化テクニックの「Vectorized Memory Access」が [&#8230;]</p>
<p>The post <a href="https://www.mattari-benkyo-note.com/2023/02/14/cuda-vectorized-memory-access/">CUDAの高速化の復習2023年版 Vectorized Memory Access編</a> first appeared on <a href="https://www.mattari-benkyo-note.com">まったり勉強ノート</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>前回Reductionを例に今時のCUDAの高速化で何が効いているのか？を確認したまとめの記事を書きました。今回はその中には登場しなかったCUDAの高速化テクニックの「Vectorized Memory Access」が今でも有効なのか確認したまとめになります。</p>



<p>このvectorized memory accessは昔からあるテクニックです。ただ、最近CUDAの高速化をしようとして、vectorized memory accessを試してみるのですが、いまいち効果がなさそうな気配があったので、ちゃんと調べようと思い今回記事をかきました。ちなみに結論からいうと今でもちょっとは効果ありそうでした。</p>



<p>検証に利用したコードはこちらにあげてあります。</p>



<p><a href="https://github.com/shu65/cuda-vectorized-memory-access">https://github.com/shu65/cuda-vectorized-memory-access</a></p>



<p>検証環境はCUDA 12.0、GPUはA100を使っています。</p>



<p>今回のVectorized Memory Accessは少しマニアックなテクニックなので、CUDAの高速化全般に関して簡単に知りたいという方はReductionの記事のほうがおすすめです。リンクは以下の通りです。</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-まったり勉強ノート wp-block-embed-まったり勉強ノート"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="SghGSgFy7R"><a href="https://www.mattari-benkyo-note.com/2023/01/29/cuda-reduction2023/">CUDAの高速化の復習2023年版 Reduction編</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;CUDAの高速化の復習2023年版 Reduction編&#8221; &#8212; まったり勉強ノート" src="https://www.mattari-benkyo-note.com/2023/01/29/cuda-reduction2023/embed/#?secret=gK27CwolO8#?secret=SghGSgFy7R" data-secret="SghGSgFy7R" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<h2 class="wp-block-heading">Vectorized Memory Accessとは？</h2>



<p>Vectorized Memory AccessとはCUDAにおいて連続するグローバルメモリへのアクセスを高速化するテクニックの一つです。このテクニックは結構昔から知られていてNVIDIAのblogでも2013年に紹介されています。</p>



<p><a href="https://developer.nvidia.com/blog/cuda-pro-tip-increase-performance-with-vectorized-memory-access/">https://developer.nvidia.com/blog/cuda-pro-tip-increase-performance-with-vectorized-memory-access/</a></p>



<p>詳細はこちらのNVIDIAの記事を見ていただきたいと思いますが、ざっくり簡単に説明すると、連続したグローバルメモリにアクセスする際にintなど32 bit単位でアクセスするよりもint2やint4でアクセスするほうが速いよ、というものです。int2、int4はCUDAで定義されている構造体でintを2つ、または4つもった構造体です。なので、普通のintが32 bitなのにたいしてint2だと64 bit, int4だと128 bitのサイズになってintよりも大きいデータに一気にアクセスすることになります。</p>



<h2 class="wp-block-heading">Vectorized Memory Accessの検証コード</h2>



<p>今回、配列の要素数を1Kから1Gまで増加させたとき、配列の全要素を別の配列にコピーする単純なカーネルで測定します。Vectorized memory accessを使わない場合は以下のようなコードになります。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="copy_scalar" data-lang="C++"><code>template &lt;typename T&gt;
__global__ void CopyScalarKernel(T *d_in, T *d_out, const size_t n)
{
  const int idx = blockIdx.x * blockDim.x + threadIdx.x;
  const int stride = blockDim.x * gridDim.x;
  for (int i = idx; i &lt; n; i += stride)
  {
    d_out[i] = d_in[i];
  }
}

template &lt;typename T&gt;
void CopyScalar(T *d_in, T *d_out, size_t n)
{
  int max_blocks = 4096;
  int threads = 1024;
  int blocks = min((int)(n + threads - 1) / threads, max_blocks);
  CopyScalarKernel&lt;T&gt;&lt;&lt;&lt;blocks, threads&gt;&gt;&gt;(d_in, d_out, n);
}</code></pre></div>



<p>thread数やblock数を変化させるとパフォーマンスが若干変化するのですが、ここのチューニングするのは大変なので、すべて同じ方法で決めて使います。</p>



<p>このコードでvectorized memory accessを使って64 bit, 128 bitでアクセスするときはこのようなカーネルになります。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="copy_vector2_kernel" data-lang="C++"><code>template &lt;typename T&gt;
__global__ void CopyVector2Kernel(T *d_in, T *d_out, const size_t n)
{
  const float ratio = ((float)sizeof(int2)) / sizeof(T);
  const int idx = blockIdx.x * blockDim.x + threadIdx.x;
  const int stride = blockDim.x * gridDim.x;
  const int m = n / ratio;
  for (int i = idx; i &lt; m; i += stride)
  {
    reinterpret_cast&lt;int2 *&gt;(d_out)[i] = reinterpret_cast&lt;int2 *&gt;(d_in)[i];
  }
}</code></pre></div>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="copy_vector4_kernel" data-lang="C++"><code>template &lt;typename T&gt;
__global__ void CopyVector4Kernel(T *d_in, T *d_out, const size_t n)
{
  const float ratio = ((float)sizeof(int4)) / sizeof(T);
  const int idx = blockIdx.x * blockDim.x + threadIdx.x;
  const int stride = blockDim.x * gridDim.x;
  const int m = n / ratio;
  for (int i = idx; i &lt; m; i += stride)
  {
    reinterpret_cast&lt;int4 *&gt;(d_out)[i] = reinterpret_cast&lt;int4 *&gt;(d_in)[i];
  }
}</code></pre></div>



<p>重要な点として、グローバルメモリからデータと読み込むときと書き込むときでint2やint4など大きい型のポインタにキャストしてからアクセスするということをしています。</p>



<h2 class="wp-block-heading">Vectorized Memory Accessの結果</h2>



<p>では、さきほどのコードを動かして実際にどのくらいのスループットになるかを示します。計測する際は10回の平均時間を出してスループットを算出しました。比較には最近よく使う、halfの配列とfloatの配列の２種類を使います。そしてデータにアクセスするときは、何もしないscalerのまま、32 bit, 64 bit, 128 bitでアクセスする場合の合計4つを示します。</p>



<p>halfとfloatの結果のグラフを以下に示します。</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img loading="lazy" decoding="async" src="https://www.mattari-benkyo-note.com/wp-content/uploads/2023/02/half-1-1024x614.png" alt="" class="wp-image-1801" width="464" height="278" srcset="https://www.mattari-benkyo-note.com/wp-content/uploads/2023/02/half-1-1024x614.png 1024w, https://www.mattari-benkyo-note.com/wp-content/uploads/2023/02/half-1-300x180.png 300w, https://www.mattari-benkyo-note.com/wp-content/uploads/2023/02/half-1-768x461.png 768w, https://www.mattari-benkyo-note.com/wp-content/uploads/2023/02/half-1-1536x922.png 1536w, https://www.mattari-benkyo-note.com/wp-content/uploads/2023/02/half-1.png 1650w" sizes="auto, (max-width: 464px) 100vw, 464px" /><figcaption class="wp-element-caption">halfの結果</figcaption></figure></div>

<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img loading="lazy" decoding="async" src="https://www.mattari-benkyo-note.com/wp-content/uploads/2023/02/float-1024x614.png" alt="" class="wp-image-1802" width="448" height="269" srcset="https://www.mattari-benkyo-note.com/wp-content/uploads/2023/02/float-1024x614.png 1024w, https://www.mattari-benkyo-note.com/wp-content/uploads/2023/02/float-300x180.png 300w, https://www.mattari-benkyo-note.com/wp-content/uploads/2023/02/float-768x461.png 768w, https://www.mattari-benkyo-note.com/wp-content/uploads/2023/02/float-1536x922.png 1536w, https://www.mattari-benkyo-note.com/wp-content/uploads/2023/02/float.png 1650w" sizes="auto, (max-width: 448px) 100vw, 448px" /><figcaption class="wp-element-caption">floatの結果</figcaption></figure></div>


<p>結果を見るとコピーするサイズが小さいときはvectorized memory accessなし、ありでそれほど差がなく、数MBくらいでちょっとずつ差がでるという感じの結果でした。ちなみにfloatで32bitでアクセスするとscalerよりも遅くなっていますが、これはキャストのオーバーヘッドがあるためだと思われます。</p>



<p>やってみた感想としては今も多少は効果があるけど、そこまで劇的に変化するわけではなさそうという印象です。なので、最適化をできるだけ頑張って、もう次やることがないってなったときに試してみるくらいでよいかなということを思いました。</p>



<h2 class="wp-block-heading">終わりに</h2>



<p>今回は昔からあるCUDAの高速化テクニックの一つのvectorized memory accessが今でも有効なのか確認したので、そのまとめを書きました。CUDAのコンパイラやGPUのアーキテクチャもどんどん変化しているので、昔は効果あったけど今はない、ってものも少なからずあるので、今後もこういう高速化テクニックの確認をしていければと思います。</p><p>The post <a href="https://www.mattari-benkyo-note.com/2023/02/14/cuda-vectorized-memory-access/">CUDAの高速化の復習2023年版 Vectorized Memory Access編</a> first appeared on <a href="https://www.mattari-benkyo-note.com">まったり勉強ノート</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://www.mattari-benkyo-note.com/2023/02/14/cuda-vectorized-memory-access/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1797</post-id>	</item>
		<item>
		<title>PyTorchのPERFORMANCE TUNING GUIDEの効果を確認してみる その2 「Fuse pointwise operations」</title>
		<link>https://www.mattari-benkyo-note.com/2021/05/10/pytorch-performance-tuning-guide-2-fuse-pointwise-operations/</link>
					<comments>https://www.mattari-benkyo-note.com/2021/05/10/pytorch-performance-tuning-guide-2-fuse-pointwise-operations/#respond</comments>
		
		<dc:creator><![CDATA[Shuji Suzuki (shu)]]></dc:creator>
		<pubDate>Mon, 10 May 2021 00:27:04 +0000</pubDate>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[GPU]]></category>
		<category><![CDATA[pytorch]]></category>
		<guid isPermaLink="false">https://www.mattari-benkyo-note.com/?p=118</guid>

					<description><![CDATA[<p>PyTorchには「PERFORMANCE TUNING GUIDE」という学習を速くするためのテクニック集があります。このドキュメントでは個々のテクニックでどれくらい速くなるか具体的な数値が示されていないので、それを確 [&#8230;]</p>
<p>The post <a href="https://www.mattari-benkyo-note.com/2021/05/10/pytorch-performance-tuning-guide-2-fuse-pointwise-operations/">PyTorchのPERFORMANCE TUNING GUIDEの効果を確認してみる その2 「Fuse pointwise operations」</a> first appeared on <a href="https://www.mattari-benkyo-note.com">まったり勉強ノート</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>PyTorchには「<a href="https://pytorch.org/tutorials/recipes/recipes/tuning_guide.html#performance-tuning-guide" target="_blank" rel="noreferrer noopener">PERFORMANCE TUNING GUIDE</a>」という学習を速くするためのテクニック集があります。このドキュメントでは個々のテクニックでどれくらい速くなるか具体的な数値が示されていないので、それを確認するということをここ最近やっています。この記事はそのシリーズの第二弾として、「Fuse pointwise operations」を試してみたまとめです。</p>



<p>ちなみに、測定するときにいろいろ気を付けないといけないポイントがあったので、Fuse pointwise operationsのために利用した<code>torch.jit.script</code> の謎現象で困る人が減るように、それについても後半で説明していきます。</p>



<p>第一弾の<a href="https://www.mattari-benkyo-note.com/2021/04/27/pytorch-performance-tuning-guide-1-parameter-grad-none/" target="_blank" rel="noreferrer noopener" title="https://www.mattari-benkyo-note.com/2021/04/27/pytorch-performance-tuning-guide-1-parameter-grad-none/">「parameter.grad = Noneを使う」</a>というのもありますので、PyTorchの高速化に興味がある方はそちらも合わせてご覧ください。</p>



<h2 class="wp-block-heading">Fuse pointwise operationsとは？</h2>



<p>elementwiseの加算や乗算、<code>sin()</code>, <code>cos()</code>, <code>sigmoid() </code>などなど、行列やベクトルの要素単位で実行される演算をまとめてpointwise operationsと呼ぶときがあります。これらの演算は一つの演算にかかる時間は非常に短いため、GPUのような関数1回の実行のオーバーヘッドやメモリアクセスのオーバーヘッドが大きい演算器では計算量のわりに長い計算時間がかかってしまいます。</p>



<p>このようなメモリアクセスや関数の実行のオーバーヘッドを削減する工夫として、複数の独立した演算を一つの関数にまとめる(fuse)という方法が良く用いられます。</p>



<p>PyTorchでも演算をまとめる仕組みがあります。その中でもpointwise operationsをfuseする仕組みとしてよく例で用いられるのが <code>torch.jit.script</code> です。</p>



<p>今回はこの<code>torch.jit.script</code> によって、どれくらいfuseしたpointwise operationsが速くなるのかを確認していきます。</p>



<h2 class="wp-block-heading">実際に効果を測定してみる</h2>



<p><code>torch.jit.script</code> でfuseするとどれくらい速くなるのか？を測定するための環境と実際に用いたコードは以下の通りです。</p>



<ul class="wp-block-list"><li>実行環境：Google Colab</li><li>GPU: Tesla T4</li><li>PyTorch: 1.8.1</li><li>torchvision: 0.9.1</li><li>測定に使ったnotebook: <a href="https://github.com/shu65/pyorch_performance_tuning_guide_examples/blob/main/Fuse_pointwise_operations.ipynb">https://github.com/shu65/pyorch_performance_tuning_guide_examples/blob/main/Fuse_pointwise_operations.ipynb</a></li></ul>



<p>また、今回測定に利用した関数は「<a href="https://pytorch.org/tutorials/recipes/recipes/tuning_guide.html#performance-tuning-guide" target="_blank" rel="noreferrer noopener">PERFORMANCE TUNING GUIDE</a>」で示されていたGELUです。実装自体は単純で、以下の通りです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>def gelu(x):
    return x * 0.5 * (1.0 + torch.erf(x / 1.41421))</code></pre></div>



<p>また、今回はGPUのだけでなく念のためCPUも測定しました。</p>



<p>測定した結果は以下の通りです。</p>



<figure class="wp-block-table"><table><tbody><tr><td></td><td>平均実行時間 (sec.)</td><td>デフォルトとの速度比</td></tr><tr><td>CPU デフォルト</td><td>0.106</td><td>1.00</td></tr><tr><td>CPU torch.jit.scriptあり</td><td>0.105</td><td>1.00</td></tr></tbody></table><figcaption>CPUの場合</figcaption></figure>



<figure class="wp-block-table"><table><tbody><tr><td></td><td>平均実行時間 (sec.)</td><td>デフォルトとの速度比</td></tr><tr><td>GPU デフォルト</td><td>0.00356</td><td>1.00</td></tr><tr><td>GPU torch.jit.scriptあり</td><td>0.000789</td><td>4.51</td></tr></tbody></table><figcaption>GPUの場合</figcaption></figure>



<p>CPUのほうはあまり期待してなかったですが、予想通りほぼ変わらずという結果でした。一方、GPUのほうは劇的に速度が変化し、今回のGULEの例では4.5倍速くなることが確認できました。個人的には<code>torch.jit.script</code> で速くなることはあまりないようなイメージだったので、シンプルなFuse pointwise operationsならちゃんと速くなるというのがわかって少し感動してます。</p>



<h2 class="wp-block-heading">torch.jit.script を使った実行時間測定の注意点</h2>



<p>さて、この記事を書くにあたってかなり苦労したので、その苦労話もちゃんと書いておこうと思います。この分量の内容の記事なら数時間で実験して書けるだろうと当初は思っていたのですが、<code>torch.jit.script</code>の謎現象に悩まされて実験がちゃんと安定して取れるようになるまで、実は数日かかりました。なので、<code>torch.jit.script</code>を使った計算時間測定の注意点をまとめておきます。</p>



<h3 class="wp-block-heading">1. GPUの計算時間を正しく測定する</h3>



<p>以前自分で「<a href="https://www.mattari-benkyo-note.com/2021/03/21/pytorch-cuda-time-measurement/" target="_blank" rel="noreferrer noopener">PyTorchでGPUの計算時間を正しく計測する</a>」という記事を書きましたが、恥ずかしながら最初は正しく測定するのを忘れていました。なので、自戒も込めて何度も書きますが、GPUの計算時間を測定するときは注意してください。</p>



<h3 class="wp-block-heading">2. torch.jit.scriptの1回目の実行はオーバーヘッドが大きいので無視する</h3>



<p><code>torch.jit.script</code>は名前の通りJITなので、1回目の実行時はオーバーヘッドが大きいです。このため、1回も含めていて、かつ、少ない実行回数で平均を取るとtorch.jit.scriptを使っているのに速くなっていないというような状態になります。このため、ちゃんと測定する場合は1回目の実行は別にするようにするとよいかと思います。</p>



<h3 class="wp-block-heading">3. 入力のTensorのshapeやdeviceが違う場合はtorch.jit.script()の実行前にキャッシュをクリアする</h3>



<p>今回の測定で気が付くのに苦労した点がこれです。PyTorch 1.8.1現在、<code>torch.jit.script()</code>は一度関数オブジェクトをtorch.jit.script化したあと、2回目以降はこの部分をスキップするためにキャッシュしています。このため、全く別のshapeやdeviceのTensorを入力に使う場合は<code>torch.jit.script()</code> を実行する前にキャッシュをクリアしておかないと、本来はJITを使って速くなるはずなのにキャッシュに残ったものがそのまま使われて全然速くならないという現象が発生します。</p>



<p>今回の測定に用いたnotebookではCPUを測定したあとGPUの測定をしています。このため、何もしていないとCPUでJITが走っているので、その後、いくら入力をGPUにしていてもGPU用のJITが走らず、<code>torch.jit.script</code>をGPUで使っているのに全然速くならないという状態になります。</p>



<p>これを回避するために以下のようにキャッシュのクリアしてからtorch.jit.script化して測定を行うようにしています。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>torch.jit._state._jit_function_overload_caching.clear()
torch.jit._state._jit_caching_layer.clear()
scripted_gelu = torch.jit.script(gelu)</code></pre></div>



<p>ちなみにちゃんと最適化が走っているか確認する際は<code>torch.jit.last_executed_optimized_graph()</code> で直前の関数の実行時のグラフが出力できるので、JITが走っているはずの1回目の実行で「prim::profile」というものが出てきているか確認してください。現状のPyTorchのデフォルトだと最初の1回目はプロファイル測定のためにこのようなIRが挿入されるようになっています。</p>



<h2 class="wp-block-heading">最後に</h2>



<p>PyTorchには「<a href="https://pytorch.org/tutorials/recipes/recipes/tuning_guide.html#performance-tuning-guide" target="_blank" rel="noreferrer noopener">PERFORMANCE TUNING GUIDE</a>」の「Fuse pointwise operations」を試したときのまとめを書きました。個人的には<code>torch.jit.script()</code> を使う際の注意点がいろいろわかってかなり勉強になりました。他にもまだまだ試したい高速化テクニックがあるので、試した際はまたこうしたまとめ記事を書こうと思います。</p><p>The post <a href="https://www.mattari-benkyo-note.com/2021/05/10/pytorch-performance-tuning-guide-2-fuse-pointwise-operations/">PyTorchのPERFORMANCE TUNING GUIDEの効果を確認してみる その2 「Fuse pointwise operations」</a> first appeared on <a href="https://www.mattari-benkyo-note.com">まったり勉強ノート</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://www.mattari-benkyo-note.com/2021/05/10/pytorch-performance-tuning-guide-2-fuse-pointwise-operations/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">118</post-id>	</item>
		<item>
		<title>PyTorchでGPUの計算時間を正しく計測する</title>
		<link>https://www.mattari-benkyo-note.com/2021/03/21/pytorch-cuda-time-measurement/</link>
					<comments>https://www.mattari-benkyo-note.com/2021/03/21/pytorch-cuda-time-measurement/#respond</comments>
		
		<dc:creator><![CDATA[Shuji Suzuki (shu)]]></dc:creator>
		<pubDate>Sun, 21 Mar 2021 01:45:28 +0000</pubDate>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[CUDA]]></category>
		<category><![CDATA[GPU]]></category>
		<category><![CDATA[pytorch]]></category>
		<guid isPermaLink="false">https://www.mattari-benkyo-note.com/?p=37</guid>

					<description><![CDATA[<p>今回の記事ではPyTorchでGPUで実行した関数の計算時間を正しく測定する方法とその後に詳しい説明をしていきます。 はじめに 仕事がらPyTorchで高速な学習方法をいろいろ調べることがよくあります。その際、blog記 [&#8230;]</p>
<p>The post <a href="https://www.mattari-benkyo-note.com/2021/03/21/pytorch-cuda-time-measurement/">PyTorchでGPUの計算時間を正しく計測する</a> first appeared on <a href="https://www.mattari-benkyo-note.com">まったり勉強ノート</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>今回の記事ではPyTorchでGPUで実行した関数の計算時間を正しく測定する方法とその後に詳しい説明をしていきます。</p>



<h2 class="wp-block-heading">はじめに</h2>



<p>仕事がらPyTorchで高速な学習方法をいろいろ調べることがよくあります。<br>その際、blog記事などで、Pythonの <code>time() </code>を利用して計算時間を測定して「こんなに速くなりました！」という紹介記事を見かけることがあります。ただ、そこに載っているコードがGPU用の測定方法を用いていないため、正しく測定できていなくて数値が参考にならないということが本当によくあります。</p>



<p>せっかく、いいまとめなのにもったいない・・・ということが多いため、この記事では少しでもそういうものが減ってくれればと思い、PyTorchのGPUの処理の正しい計算時間測定方法についてまとめました。ちなみに、profilerを使ったやり方は別の記事にしようとかと思うので今回は言及しません。</p>



<p>サンプルコードはこちらにありますので、わからないところがあれば実際に動かして確かめてみてください。</p>



<p><a href="https://github.com/shu65/pytorch-cuda-time-measurement/blob/main/Pytorch_GPU_Time_Measurement.ipynb">https://github.com/shu65/pytorch-cuda-time-measurement/blob/main/Pytorch_GPU_Time_Measurement.ipynb</a></p>



<h2 class="wp-block-heading">具体的なGPUの計算時間の測定方法</h2>



<p>今回は <code>torch.cuda.synchronize() </code>と <code>torch.cuda.Event</code> を利用した2種類の方法を紹介します。</p>



<h3 class="wp-block-heading">torch.cuda.synchronize() を利用した方法</h3>



<p><code>torch.cuda.synchronize()</code> を利用する場合は以下のように <code>time()</code>の前に <code>torch.cuda.synchronize()</code> を実行するようにします。</p>



<pre class="wp-block-code"><code>torch.cuda.synchronize()
start = time.time()
# 測定したい部分開始
with torch.no_grad():
  out = model_gpu(input_batch_gpu)  
# 測定したい部分終了
torch.cuda.synchronize()
elapsed_time = time.time() - start

print(elapsed_time, 'sec.')</code></pre>



<h3 class="wp-block-heading">torch.cuda.Eventを利用した方法</h3>



<p><code>torch.cuda.Event</code> を利用する場合は開始用と終了用の<code>torch.cuda.Event</code> を作り、測定したい関数の前後で<code>record()</code>を呼びます。その後、GPUの処理が終わるまで待つために<code> torch.cuda.synchronize()</code> を呼び、<code>elapsed_time()</code> で計算時間を取得するという流れになります。</p>



<pre class="wp-block-code"><code>start = torch.cuda.Event(enable_timing=True)
end = torch.cuda.Event(enable_timing=True)

start.record()
# 測定したい部分開始
with torch.no_grad():
  out = model_gpu(input_batch_gpu)  
# 測定したい部分終了
end.record()
torch.cuda.synchronize()
elapsed_time = start.elapsed_time(end)

print(elapsed_time / 1000, 'sec.')</code></pre>



<h2 class="wp-block-heading">詳しい説明</h2>



<p>まず、よくある間違えがどんなコードか？を説明します。よくある間違えているコードのサンプルとしては以下の通りです。</p>



<pre class="wp-block-code"><code>start = time.time()
# 測定したい部分開始
with torch.no_grad():
  out = model_gpu(input_batch_gpu)
# 測定したい部分終了
elapsed_time = time.time() - start

print(elapsed_time, 'sec.')</code></pre>



<p>このように普通に <code>time()</code> を呼ぶという間違いが多い印象です。CPUではこれで問題ないのですが、GPUを使った場合はこれでは正しく計算時間が測定できていません。その理由はCPUとGPUの処理が非同期で行われているからです。</p>



<h3 class="wp-block-heading">CPUとGPUの処理が非同期とは？</h3>



<p>PyTorchではGPUの処理を実行する際にCUDAを利用しています。このCUDAではGPUで処理する関数をkernel関数、または単にkernelと呼びます。このkernel関数はCPUとGPUのリソースを最大限に活用できるように、基本的にはCPUがkernel関数の実行を依頼するGPUのタスクキューに積むところまで行い、kernel関数の処理が終わるのを待たないで返ってくるということをしています。</p>



<p>この結果、GPUが処理している最中もCPUが別の処理を実行でき、計算リソースを有効活用することができます。</p>



<p>GPUを活用するという意味ではこの非同期処理という仕組みは非常に有用なのですが、GPUで行っている処理の計算時間を測定する場合には注意が必要になります。というのも、kernel関数を呼んで返ってきたタイミングではGPUの処理が終わってないためです。このため、処理の時間を測定する場合はCPUとGPUとの同期をしたり、CUDA Eventなどの特別な方法で測定する必要があります。</p>



<h2 class="wp-block-heading">正しく測定してない場合と正しく測定した場合どれくらい差がでるのか？</h2>



<p>間違っていたらどれくらいひどいことになるか？を実感してもらうために、Google ColabでResNet50というモデルの推論を行ったときの結果を紹介します。コードはこちらになります </p>



<p><a href="https://github.com/shu65/pytorch-cuda-time-measurement/blob/main/Pytorch_GPU_Time_Measurement.ipynb">https://github.com/shu65/pytorch-cuda-time-measurement/blob/main/Pytorch_GPU_Time_Measurement.ipynb</a></p>



<p>実行環境は以下の通りです。</p>



<ul class="wp-block-list"><li>GPU: T4</li><li>CUDA: 10.1</li><li>PyTorch: 1.8.0</li><li>torchvision: 0.9.0</li></ul>



<p>また、ResNet50の入力バッチサイズは128として実行します。</p>



<p>この条件で実行した結果は以下の通りです</p>



<figure class="wp-block-table"><table><tbody><tr><td></td><td class="has-text-align-left" data-align="left">時間 (sec.)</td></tr><tr><td>CPU</td><td class="has-text-align-left" data-align="left">18.83</td></tr><tr><td>GPU (間違った測定方法の場合)</td><td class="has-text-align-left" data-align="left">0.01</td></tr><tr><td>GPU (torch.cuda.synchronize()利用時)</td><td class="has-text-align-left" data-align="left">0.32</td></tr><tr><td>GPU (torch.cuda.Event利用時)</td><td class="has-text-align-left" data-align="left"><br>0.32</td></tr></tbody></table><figcaption>測定結果</figcaption></figure>



<p>結果として、間違った測定方法だとCPUとGPUを比較すると「1883倍速くなりました！」という主張をしてしまうことになります。ちなみにGPUで1000倍なんて数字が出てきたら確実にどこか間違えています。実際、今回のケースでは本当は「約59倍速くなりました！」というのが正しい結果になります。</p>



<h2 class="wp-block-heading">torch.cuda.synchronize()とtorch.cuda.Eventを使った場合の違い</h2>



<p>今回<code>torch.cuda.synchronize()</code>と<code>torch.cuda.Event</code> の2種類を紹介しました。場合によっては使い分けをしたほうがいいのでこの二つの違いを説明していきます。</p>



<p><code>torch.cuda.synchronize()</code> を利用した場合、簡単なので測定しやすいのでいいので、ぱっと測定したい場合はこちらの方法が楽でよいかと思います。ただ、こちらの方法はkernel関数の発行と測定終了の<code>torch.cuda.synchronize()</code> の終了までの時間も含むことになります。kernel関数の発行も<code>torch.cuda.synchronize()</code> も時間としては十分短いことが多いので、ほとんどの場合は無視できると思います。ただ、常時監視する目的で測定する際には、<code>torch.cuda.synchronize()</code> を測定したい部分の終了時に呼ぶため、CPUの処理が<code>torch.cuda.synchronize()</code>のところで止まってしまうのでオーバーヘッドが大きすぎるという問題があります。<br>一方、<code>torch.cuda.Event</code> を利用した場合は<code>elapsed_time()</code> を呼ぶ直前に何等かの方法でCPUと同期すればいいので、学習のイテレーションの最後に同期するなど工夫することができ、この結果、オーバーヘッドを小さくすることができます。このため、常時監視する目的で測定する際は<code>torch.cuda.Event</code> の利用をお勧めします。</p>



<h2 class="wp-block-heading">終わりに</h2>



<p>今回はPyTorchのGPUの計算時間を正しく計測する方法について紹介しました。この記事でGPUの計算時間の測定方法を間違えておかしなことを主張する記事が少しでも減ってくれれば幸いです。<br>profilerについても今度調べて記事にできればと思っています。</p><p>The post <a href="https://www.mattari-benkyo-note.com/2021/03/21/pytorch-cuda-time-measurement/">PyTorchでGPUの計算時間を正しく計測する</a> first appeared on <a href="https://www.mattari-benkyo-note.com">まったり勉強ノート</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://www.mattari-benkyo-note.com/2021/03/21/pytorch-cuda-time-measurement/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">37</post-id>	</item>
	</channel>
</rss>
