<?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>CUDA - まったり勉強ノート</title>
	<atom:link href="https://www.mattari-benkyo-note.com/tag/cuda/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.mattari-benkyo-note.com</link>
	<description>shuの日々の勉強まとめ</description>
	<lastBuildDate>Mon, 31 Mar 2025 12:27:45 +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>PFNの最新LLM PLaMo 2 8BをGoogle ColabでLoRAで学習してみる</title>
		<link>https://www.mattari-benkyo-note.com/2025/04/01/plamo-2-8b-lora/</link>
					<comments>https://www.mattari-benkyo-note.com/2025/04/01/plamo-2-8b-lora/#respond</comments>
		
		<dc:creator><![CDATA[Shuji Suzuki (shu)]]></dc:creator>
		<pubDate>Mon, 31 Mar 2025 23:30:00 +0000</pubDate>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[CUDA]]></category>
		<category><![CDATA[llm]]></category>
		<category><![CDATA[python]]></category>
		<guid isPermaLink="false">https://www.mattari-benkyo-note.com/?p=3255</guid>

					<description><![CDATA[<p>少し前になりますが、PFNが開発しているLLM、PLaMo 2の8Bモデル（事前学習モデル）が公開されました。こちら特殊なライセンスになっていますが、個人が使う分には商業利用も可能なライセンスで公開されています。 このモ [&#8230;]</p>
<p>The post <a href="https://www.mattari-benkyo-note.com/2025/04/01/plamo-2-8b-lora/">PFNの最新LLM PLaMo 2 8BをGoogle ColabでLoRAで学習してみる</a> first appeared on <a href="https://www.mattari-benkyo-note.com">まったり勉強ノート</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>少し前になりますが、PFNが開発しているLLM、<a href="https://huggingface.co/pfnet/plamo-2-8b" target="_blank" rel="noopener" title="PLaMo 2の8Bモデル（事前学習モデル）">PLaMo 2の8Bモデル（事前学習モデル）</a>が公開されました。こちら特殊なライセンスになっていますが、個人が使う分には商業利用も可能なライセンスで公開されています。</p>



<p>このモデルは事前学習モデルなので、そのままではChataGPTなどの普通の人が良く使うLLMと違ってうまく指示を聞くようになっていませんがSFTなどの事後学習を行えばいろいろなタスクをこなせるようにすることが可能です。</p>



<p>この記事では、このような事後学習のやり方の一つとしてGoogle Colabで比較的安く使えるL4というGPUを使って、LoRAという方法で学習するやり方を紹介します。</p>



<p>今回紹介するコードは以下のところにありますので、参考にしてください。</p>



<p><a href="https://github.com/shu65/plamo-2-8b-lora-sft-example/blob/main/PLaMo_2_8B_LoRA_SFT.ipynb">https://github.com/shu65/plamo-2-8b-lora-sft-example/blob/main/PLaMo_2_8B_LoRA_SFT.ipynb</a></p>



<h2 class="wp-block-heading">LoRAとは</h2>



<p>LoRAとは「Low-Rank Adaptation」の略で、大規模言語モデルのような大きなモデルの学習を、メモリが小さいGPU１枚など小規模な計算機環境で学習するために提案された手法です。</p>



<p>具体的にはモデルの一部のLayerに対して低ランクの行列を導入し、低ランクの行列のみ学習することでモデル全体を学習するのと比べて非常に少ないメモリで学習できるようにしています。</p>



<p>このLoRAを使った学習はライブラリがそろっていることもあり、簡単なものはかなり短いコードで書くことができます。</p>



<h2 class="wp-block-heading">PLaMo 2 8B のLoRA</h2>



<p>ここからPLaMo 2 8BでLoRAを使って学習する方法を説明していきます。</p>



<h3 class="wp-block-heading">PLaMo 2 8B 利用規約への同意</h3>



<p>先ほども説明した通りPLaMo 2 8Bは特殊なライセンスということもあり、事前に同意しておく必要があります。</p>



<p>これにはまず、Hugging FaceのサイトのPLaMo 2 8Bのページに行きます。URLは以下の通りです。</p>



<p><a href="https://huggingface.co/pfnet/plamo-2-8b">https://huggingface.co/pfnet/plamo-2-8b</a></p>



<p>このページに行くとまだライセンスに同意していない場合は以下のようにライセンスの一部が表示されていると思います。</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img fetchpriority="high" decoding="async" width="1024" height="849" src="https://www.mattari-benkyo-note.com/wp-content/uploads/2025/03/スクリーンショット-2025-03-26-215250-1024x849.png" alt="" class="wp-image-3257" style="width:561px;height:auto" srcset="https://www.mattari-benkyo-note.com/wp-content/uploads/2025/03/スクリーンショット-2025-03-26-215250-1024x849.png 1024w, https://www.mattari-benkyo-note.com/wp-content/uploads/2025/03/スクリーンショット-2025-03-26-215250-300x249.png 300w, https://www.mattari-benkyo-note.com/wp-content/uploads/2025/03/スクリーンショット-2025-03-26-215250-768x637.png 768w, https://www.mattari-benkyo-note.com/wp-content/uploads/2025/03/スクリーンショット-2025-03-26-215250-1536x1273.png 1536w, https://www.mattari-benkyo-note.com/wp-content/uploads/2025/03/スクリーンショット-2025-03-26-215250.png 1567w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure></div>


<p>この場合はライセンスを確認の上、同意してください。同意すると以下のような表示になります。</p>


<div class="wp-block-image">
<figure class="aligncenter size-large is-resized"><img decoding="async" width="1024" height="209" src="https://www.mattari-benkyo-note.com/wp-content/uploads/2025/03/スクリーンショット-2025-03-26-220439-1024x209.png" alt="" class="wp-image-3259" style="width:588px;height:auto" srcset="https://www.mattari-benkyo-note.com/wp-content/uploads/2025/03/スクリーンショット-2025-03-26-220439-1024x209.png 1024w, https://www.mattari-benkyo-note.com/wp-content/uploads/2025/03/スクリーンショット-2025-03-26-220439-300x61.png 300w, https://www.mattari-benkyo-note.com/wp-content/uploads/2025/03/スクリーンショット-2025-03-26-220439-768x157.png 768w, https://www.mattari-benkyo-note.com/wp-content/uploads/2025/03/スクリーンショット-2025-03-26-220439-1536x313.png 1536w, https://www.mattari-benkyo-note.com/wp-content/uploads/2025/03/スクリーンショット-2025-03-26-220439.png 1550w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure></div>


<p>これでPLaMo 2 8Bを使う準備ができました。</p>



<h3 class="wp-block-heading">Google ColabのランタイムでL4を使うようにする</h3>



<p>次に、Google ColabでL4を使う準備をします。Google ColabでL4が使えるように課金が必要になりますので、まずは課金をします。</p>



<p>課金についてはこちらをご覧ください。</p>



<p><a href="https://colab.research.google.com/signup?hl=ja">https://colab.research.google.com/signup?hl=ja</a></p>



<p>今回のコードを動かすだけであれば「Pay As You Go」で100 コンピューティング ユニットを購入すれば十分です。この記事を執筆時点では1200円に満たない程度で購入できます。</p>



<p>課金が済んだら、メニューバーから「ランタイム」→「ランタイムのタイプを変更」をクリックします。すると無料枠では選択できないL4 GPUが選択できるようになっていると思うので、L4 GPUを選択します。</p>



<p>これでGPUを使う準備ができました。</p>



<h3 class="wp-block-heading">必要パッケージのインストール</h3>



<p>次に今回の学習で必要なパッケージをインストールします。コマンドとしては以下の通りです。</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
!pip install trl numba&gt;=0.60.0 mamba-ssm&gt;=2.2.2 causal-conv1d&gt;=1.4.0 transformers&gt;=4.44.2</code></pre></div>



<p>最初にPytorchのバージョンを少し下げていますが、これはPLaMo 2の中で使われているライブラリの一部が最新のPyTorchに対応させるのが結構大変なため、簡単に実行できるようにするために少し古いPyTorchを入れています。</p>



<p>上記のコマンドを実行したあとは、以下のバージョンになっていました。</p>



<pre class="wp-block-preformatted">causal-conv1d                      1.5.0.post8<br>mamba-ssm                          2.2.4<br>numba                              0.60.0<br>numba-cuda                         0.2.0<br>sentence-transformers              3.4.1<br>torch                              2.4.1+cu124<br>torchaudio                         2.4.1+cu124<br>torchsummary                       1.5.1<br>torchvision                        0.19.1+cu124<br>transformers                       4.50.0<br></pre>



<h3 class="wp-block-heading">LoRAのコード</h3>



<p>パッケージをインストールしたら次は以下のようにHugging Faceにログインします。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>from huggingface_hub import login

login()</code></pre></div>



<p>これを実行するとHugging Faceのtoken を聞かれますのでHugging Faceのtokeを入力してください。</p>



<p>次に各パッケージをimportしておきます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>from transformers import AutoModelForCausalLM
from transformers import AutoTokenizer
import datasets
import string
from trl import DataCollatorForCompletionOnlyLM
from trl import SFTConfig, SFTTrainer
from peft import LoraConfig
import torch</code></pre></div>



<p>そして、PLaMo 2 8Bのモデルとtokenizerを以下のようにロードします。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>model_name = &quot;pfnet/plamo-2-8b&quot;
model = AutoModelForCausalLM.from_pretrained(model_name, trust_remote_code=True, torch_dtype=torch.bfloat16).to(&quot;cuda&quot;)
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)</code></pre></div>



<p>「PLaMo 2 8B 利用規約への同意」の部分の手順ができていなかったり、Hugging Faceのログインがうまくできていないと、この部分でエラーがでると思われます。その場合は利用規約の同意ができているかや、正しくHugging Faceのログインができているかなどを確認してください。</p>



<p>次に今回使うinstructionデータをダウウンロードして、前処理します。今回は<code>kunishou/databricks-dolly-15k-ja</code> のinputがないデータだけを取り出して利用します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>dataset = datasets.load_dataset(&quot;kunishou/databricks-dolly-15k-ja&quot;)
train_dataset = dataset[&quot;train&quot;].filter(lambda data: &quot;instruction&quot; in data and &quot;output&quot; in data and data[&quot;input&quot;] == &quot;&quot;).select(range(2000))

data_collator = DataCollatorForCompletionOnlyLM(
    response_template=tokenizer.encode(&quot; Answer:\n&quot;, add_special_tokens=False),
    tokenizer=tokenizer
)</code></pre></div>



<p>次にLoRAとSFTの引数を指定します。PLaMo 2特有の部分として<code>LoraConfig</code> でLoRAを使って学習するレイヤーを指定する<code>target_modules</code> という引数があります。ここでLLaMa系のようなTransformerの場合、AttensionのQeury, Keyを作るLinearレイヤーをLoRAで学習することが多い印象なので、PLaMo 2でも同じようにAttensionのQueryとKeyを作るLinearレイヤーをLoRAで学習するようにします。PLaMo 2の場合は、Qeury, Key、Valueを作るLinearレイヤーをすべてまとめた<code>qkv_proj</code> というLinearレイヤーがありますので、この<code>qkv_proj</code> を<code>target_modules</code> に指定しています。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>peft_config = LoraConfig(
    task_type=&quot;CAUSAL_LM&quot;,
    target_modules=[
        &quot;qkv_proj&quot;,
    ],
)

sft_args = SFTConfig(
    output_dir=&quot;./outputs&quot;,
    evaluation_strategy=&quot;no&quot;,
    per_device_train_batch_size=1,
    gradient_accumulation_steps=4,
    learning_rate=5e-5,
    num_train_epochs=1.0,
    lr_scheduler_type=&quot;cosine&quot;,
    warmup_ratio=0.3,
    logging_steps=10,
    save_strategy=&quot;epoch&quot;,
    report_to=&quot;tensorboard&quot;,
    bf16=True,
    max_seq_length=1024,
    gradient_checkpointing=True,
)</code></pre></div>



<p>あとはデータのサンプルに対してフォーマットに合わせて１つのテキストを生成する<code>formatting_func</code> を定義します。今回は以下のようなものを使います。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>INSTRUCTION_TEMPLATE = string.Template(
    &quot;&quot;&quot;### Question:
{input} ### Answer:
{response}&lt;|plamo:eos|&gt;
&quot;&quot;&quot;
)

def formatting_func(example):
  text = INSTRUCTION_TEMPLATE.substitute(input=example[&quot;instruction&quot;], response=example[&quot;output&quot;])
  return text</code></pre></div>



<p>最後に、ここまで用意したものを<code>SFTTrainer</code> に渡して学習を開始します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>trainer = SFTTrainer(
    model=model,
    args=sft_args,
    peft_config=peft_config,
    data_collator=data_collator,
    train_dataset=train_dataset,
    formatting_func=formatting_func,
)

trainer.train()
trainer.save_model()
     </code></pre></div>



<p>今回の設定では500イテレーション回ることになるはずです。私が試した限りは15分程度で処理が完了しました。</p>



<p>できたモデルの出力が正しいかは以下のコードで確認できます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>import torch

# プロンプトの準備
prompt = &quot;### Question:\n埼玉の県庁所在地は何市？\n\n### Answer:\n&quot;

# 推論の実行
inputs = tokenizer(prompt, return_tensors=&quot;pt&quot;).to(&quot;cuda&quot;)
generated_tokens = trainer.model.generate(
    **inputs,
    max_new_tokens=64,
    pad_token_id=tokenizer.pad_token_id,
)[0]
generated_text = tokenizer.decode(generated_tokens)
print(generated_text)</code></pre></div>



<p>おそらく以下のような出力がでるはずです</p>



<pre class="wp-block-code"><code>&lt;|plamo:bos|&gt;### Question:
埼玉の県庁所在地は何市？

### Answer:
さいたま市&lt;|plamo:eos|&gt;</code></pre>



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



<p>いかがだったでしょうか？今回はPLaMo 2 8Bに対してLoRAによって学習する方法を紹介しました。LoRAを使えばメモリが少ないL4のようなGPUでも8Bモデル程度で学習することができます。今回のコードを使えば事後学習が簡単にできると思われますので、みなさんもいろいろ試していただければと思っています。無料枠で使えるGPUのT4でも量子化などを頑張れば8Bモデルも学習できる気がしますが、うまくできそうであればそちらも記事にしようと思います。</p>



<p>この他にもPLaMoを含めたLLMの技術も紹介できればと思っています。</p><p>The post <a href="https://www.mattari-benkyo-note.com/2025/04/01/plamo-2-8b-lora/">PFNの最新LLM PLaMo 2 8BをGoogle ColabでLoRAで学習してみる</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/04/01/plamo-2-8b-lora/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3255</post-id>	</item>
		<item>
		<title>小型LLM PLaMo 2 1BをGoogle ColabでSFTしてみる</title>
		<link>https://www.mattari-benkyo-note.com/2025/02/13/plamo-2-1b-sft/</link>
					<comments>https://www.mattari-benkyo-note.com/2025/02/13/plamo-2-1b-sft/#respond</comments>
		
		<dc:creator><![CDATA[Shuji Suzuki (shu)]]></dc:creator>
		<pubDate>Wed, 12 Feb 2025 23:30:00 +0000</pubDate>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[CUDA]]></category>
		<category><![CDATA[llm]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[pytorch]]></category>
		<category><![CDATA[機械学習]]></category>
		<guid isPermaLink="false">https://www.mattari-benkyo-note.com/?p=3210</guid>

					<description><![CDATA[<p>今回はPreferred Networksとその子会社のPreferred Elementsが共同で開発した1Bサイズの小型のLLM、PLaMo 2 1Bに対してSFTをするコードの紹介になります。 Google Col [&#8230;]</p>
<p>The post <a href="https://www.mattari-benkyo-note.com/2025/02/13/plamo-2-1b-sft/">小型LLM PLaMo 2 1BをGoogle ColabでSFTしてみる</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>に対してSFTをするコードの紹介になります。</p>



<p>Google Colabの無料枠で推論を回す方法は前回記事にしましたので、そもそもPLaMo 2 1Bって何と思った方や推論を回してみたいという方はそちらをご覧ください。</p>



<figure class="wp-block-embed is-type-wp-embed"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="ZZZgUkUP2R"><a href="https://www.mattari-benkyo-note.com/2025/02/12/plamo-2-1b-infernece/">小型LLM PLaMo 2 1BをGoogle Colabの無料枠の範囲で使ってみる</a></blockquote><iframe class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;小型LLM PLaMo 2 1BをGoogle Colabの無料枠の範囲で使ってみる&#8221; &#8212; まったり勉強ノート" src="https://www.mattari-benkyo-note.com/2025/02/12/plamo-2-1b-infernece/embed/#?secret=V6iPyeE1qa#?secret=ZZZgUkUP2R" data-secret="ZZZgUkUP2R" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>また、今回説明に使うコードはこちらに置いてありますので、適宜参照してください。</p>



<p><a href="https://github.com/shu65/plamo-2-1b-sft-example">https://github.com/shu65/plamo-2-1b-sft-example</a></p>



<p>Google Colabにおける一連の実行に関してはJupyter Notebookにまとめてありますので、細かい実行方法がわからないという方はこちらをご覧ください</p>



<p><a href="https://github.com/shu65/plamo-2-1b-sft-example/blob/main/run_sft_google_colab.ipynb">https://github.com/shu65/plamo-2-1b-sft-example/blob/main/run_sft_google_colab.ipynb</a></p>



<h2 class="wp-block-heading">Supervised Fine-Tuning(SFT)とは？</h2>



<p>SFTを知らない方に簡単に説明すると、SFTは指示と想定されている回答のペアを用意し、LLMに対して学習を行い、指示に従いやすいモデルを作る方法になります。</p>



<p>特にPLaMo 2 1Bのような事前学習モデルでは、特に指示に従うように学習されていないケースもあり、そのまま利用した際、余計なことをだらだらと出力し続けたり、頓珍漢な回答が返ってきたりという問題が発生することがあります。</p>



<p>このため指示に適切にこたえてもらうための技術がいろいろあるのですが、そのうちの一つにSFTというものがあります。</p>



<h2 class="wp-block-heading">Google ColabでPLaMo 2 1BをSFTする</h2>



<p>それでは本題のGoogle ColabでPLaMo 2 1BをSFTする方法について説明します。今回はGPUメモリの関係上、おそらく無料で使えるT4だと無改造では実行できない気がするのでL4を使った説明をします。</p>



<h3 class="wp-block-heading">L4 GPUの利用</h3>



<p>まず、Google ColabでL4が使えるように、課金が必要になります。</p>



<p>課金についてはこちらをご覧ください。</p>



<p><a href="https://colab.research.google.com/signup?hl=ja">https://colab.research.google.com/signup?hl=ja</a></p>



<p>今回のコードを動かすだけであれば「Pay As You Go」で100 コンピューティング ユニットを購入すれば十分です。この記事を執筆時点では1200円に満たない程度で購入できます。</p>



<p>課金が済んだら、メニューバーから「ランタイム」→「ランタイムのタイプを変更」をクリックします。すると無料枠では選択できないL4 GPUが選択できるようになっていると思うので、L4 GPUを選択します。</p>



<p>これでGPUを使う準備ができました。</p>



<h3 class="wp-block-heading">実行環境準備</h3>



<p>L4を利用するようにしたら、実行するコードのダウンロードやPythonパッケージのインストールを行います。</p>



<p>まずGithubよりコードをcloneしてきます</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>!git clone https://github.com/shu65/plamo-2-1b-sft-example.git</code></pre></div>



<p>次に、PyTorchのバージョンを現在の最新版よりも前の以下のものに変更します。</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>この後は以下のようにPyTorch以外のPLaMo 2 1Bの実行に必要なパッケージやSFTに必要なパッケージなどをインストールします。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>!pip install -r plamo-2-1b-sft-example/requirements.txt</code></pre></div>



<p>ここまで実行すると2025/02/12現在以下のようなバージョンがインストールされました。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>causal-conv1d                      1.5.0.post8
fastrlock                          0.8.3
mamba-ssm                          2.2.4
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
trl                                0.14.0</code></pre></div>



<p>これであとはSFTのコードを実行すれば、SFTをすることができます。このSFTの中身に関しては次で紹介していきます。</p>



<h3 class="wp-block-heading">PLaMo 2 1BをSFTする</h3>



<p>SFTをする部分は<code>sft.py</code>　というスクリプトにまとめてあります。このスクリプトの重要な部分について簡単にですが説明していきます。</p>



<p>まず、今回はすぐに実行が終わるように少量の質問と回答のペアのデータを用います。</p>



<p>今回は日本語の指示学習でよく使われる<a href="https://huggingface.co/datasets/kunishou/databricks-dolly-15k-ja" target="_blank" rel="noopener" title="kunishou/databricks-dolly-15k-ja">kunishou/databricks-dolly-15k-ja</a>というデータセットのうち、<code>input</code> がなく<code>instruction</code> と<code>output</code> のペアになっているデータのみを取り出しその一部だけを利用します。一つ例を見せると以下のようなデータを利用します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>{
  &quot;output&quot;: &quot;イコクエイラクブカ&quot;,
  &quot;input&quot;: &quot;&quot;,
  &quot;index&quot;: &quot;1&quot;,
  &quot;category&quot;: &quot;classification&quot;,
  &quot;instruction&quot;: &quot;魚の種類はどっち？イコクエイラクブカとロープ&quot;
}</code></pre></div>



<p>一部だけ取り出すコードは以下の通りです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>    dataset = datasets.load_dataset(&quot;kunishou/databricks-dolly-15k-ja&quot;)
    train_dataset = dataset[&quot;train&quot;].filter(lambda data: data[&quot;input&quot;] == &quot;&quot;)</code></pre></div>



<p>次に<code>SFTConfig</code> というSFTの実行の設定のクラスのインスタンスを用意します。具体的には以下の通りです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>    sft_args = SFTConfig(
        output_dir=&quot;./outputs&quot;,
        evaluation_strategy=&quot;no&quot;,
        per_device_train_batch_size=1,
        gradient_accumulation_steps=4,
        learning_rate=5e-5,
        num_train_epochs=0.1,
        lr_scheduler_type=&quot;cosine&quot;,
        warmup_ratio=0.3,
        logging_steps=10,
        save_strategy=&quot;epoch&quot;,
        report_to=&quot;tensorboard&quot;,
        bf16=True,
        max_seq_length=1024,
        gradient_checkpointing=True,
        deepspeed=&#39;./deepspeed_config.json&#39;,
    )</code></pre></div>



<p>重要なこととして、今回はGPUのメモリが少ないため、DeepSpeedのStage 3という学習時に一部のデータをCPU側に置いておくモードを利用します。</p>



<p>これによりGPUメモリが少ない環境でもSFTを回すことができます。</p>



<p>DeepSpeed周りの設定は<code>deepspeed_config.json</code> に書いてありますので気になる方はご覧ください。</p>



<p>また、今回は学習データの10%だけを利用するようにしています。これはこの学習を早く終わらせるためであり、本来はもっと回す必要があると考えられますので、本気でSFTをする場合は注意してください。</p>



<p>次にデータをどのようなフォーマットでLLMに入力するかを指定する<code>formatting_func</code> という関数を用意します。今回は以下のようにしました。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>INSTRUCTION_TEMPLATE = string.Template(
    &quot;&quot;&quot;### Question:
${input}

### Answer:
${response}&lt;|plamo:eos|&gt;
&quot;&quot;&quot;
)


def formatting_func(examples):
    output_texts = []
    for i in range(len(examples[&#39;instruction&#39;])):
        text = INSTRUCTION_TEMPLATE.substitute(input=examples[&#39;instruction&#39;][i], response=examples[&#39;output&#39;][i])
        output_texts.append(text)
    return output_texts</code></pre></div>



<p><code>INSTRUCTION_TEMPLATE</code> が今回のフォーマットで、<code>### Question:\n</code> の後に指示、<code>### Answer:\n</code> のあとに回答が続き、最後にend of sequenceである<code>&lt;|plamo:eos|&gt;</code> が来るようになっています。</p>



<p>また、学習時には回答部分だけを学習してほしいので、どこからが回答かがわかるように<code>‎DataCollatorForCompletionOnlyLM</code> のインスタンスも用意します。これは以下の通りです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>    data_collator = DataCollatorForCompletionOnlyLM(
        response_template=tokenizer.encode(&quot; Answer:\n&quot;, add_special_tokens=False),
        tokenizer=tokenizer
    )
</code></pre></div>



<p><code>response_template</code> のところで回答前の部分がどのようなtoken idになるかを指定する部分があるので、上記のように指定します。前後の文字の影響で指定したtoken idが出現しないケースがあるので、その時はいろいろ<code>response_template</code> に指定する文字列を調整してみてください。</p>



<p>最後にSFTを実行するためのクラスの<code>‎SFTTrainer</code> を以下のように用意します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>    trainer = SFTTrainer(
        model=model,
        args=sft_args,
        data_collator=data_collator,
        train_dataset=train_dataset,
        tokenizer=tokenizer,
        formatting_func=formatting_func,
    )</code></pre></div>



<p>そして、以下のように実行し、結果を保存します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>    trainer.train()
    trainer.save_model()</code></pre></div>



<p>これで学習が終わると<code>SFTConfig</code> の<code>output_dir</code> で指定した<code>./outputs</code> に結果が出力されます。試しに私がGoogle Colabで実行した際は13分程度で学習が終わりました。コンピューティングユニットとしてはパッケージなどのインストールも含めて4だけ消費しました。</p>



<h3 class="wp-block-heading">SFTされたモデルで推論してみる</h3>



<p>最後にSFTされたモデルで推論するというのを行います。</p>



<p>これはPLaMo 2 1Bのexampleとほぼ同じでpromptだけ少し変えたものを例として用います。コードとしては以下の通りです。</p>



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


model_name = &quot;./plamo-2-1b-sft-example/outputs&quot;

tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(model_name, trust_remote_code=True)


# プロンプトの準備
prompt = &quot;### Question:\n埼玉の県庁所在地は何市？\n\n### Answer:\n&quot;

# 推論の実行
inputs = tokenizer(prompt, return_tensors=&quot;pt&quot;)
generated_tokens = model.generate(
    **inputs,
    max_new_tokens=64,
    pad_token_id=tokenizer.pad_token_id,
)[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;### Question:
埼玉の県庁所在地は何市？

### Answer:
埼玉県の県庁所在地はさいたま市です。&lt;|plamo:eos|&gt;</code></pre></div>



<p>ちゃんと学習で指定されたように<code>### Answer:\n</code> の後に質問に対する回答をし、その後<code>&lt;|plamo:eos|&gt;</code> を出力するということができています。</p>



<p>ちなみにSFTしていないモデルではどうなるかというと、以下のように余計なことを出力するうえ、出力が止まらないという状態になっています。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>&lt;|plamo:bos|&gt;### Question:
埼玉の県庁所在地は何市？

### Answer:
さいたま市

### 解説
「県庁所在地」とは、都道府県庁が置かれている都市のことです。
「さいたま市」は埼玉県の県庁所在地です。

### 関連記事
### 取り急ぎお知らせ
「埼玉の県庁所在地は何市？」の解説は以上です。
「埼玉の県庁所在地は何市？」の解説は以上です。</code></pre></div>



<p>このため、SFTでうまくフォーマットに従うよう学習できたと考えられます。</p>



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



<p>今回はPLaMo 2 1Bを使ってSFTをする例を示しました。今回示したように簡単なSFTなら十分Google Colabで実行することができます。みなさんもぜひいろいろ試していただければと思います。</p><p>The post <a href="https://www.mattari-benkyo-note.com/2025/02/13/plamo-2-1b-sft/">小型LLM PLaMo 2 1BをGoogle ColabでSFTしてみる</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/13/plamo-2-1b-sft/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3210</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 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=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>CUDAの高速化の復習2023年版 Reduction編</title>
		<link>https://www.mattari-benkyo-note.com/2023/01/29/cuda-reduction2023/</link>
					<comments>https://www.mattari-benkyo-note.com/2023/01/29/cuda-reduction2023/#respond</comments>
		
		<dc:creator><![CDATA[Shuji Suzuki (shu)]]></dc:creator>
		<pubDate>Sun, 29 Jan 2023 05:37:29 +0000</pubDate>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[CUDA]]></category>
		<guid isPermaLink="false">https://www.mattari-benkyo-note.com/?p=1496</guid>

					<description><![CDATA[<p>今回は最近のCUDA Samplesのコードを参考にCUDAでreductionを速くするテクニックのまとめになります。 私はCUDAを2009年のころから研究で使っていました。当時は頑張って勉強していたので自分の研究分 [&#8230;]</p>
<p>The post <a href="https://www.mattari-benkyo-note.com/2023/01/29/cuda-reduction2023/">CUDAの高速化の復習2023年版 Reduction編</a> first appeared on <a href="https://www.mattari-benkyo-note.com">まったり勉強ノート</a>.</p>]]></description>
										<content:encoded><![CDATA[<p>今回は最近のCUDA Samplesのコードを参考にCUDAでreductionを速くするテクニックのまとめになります。</p>



<p>私はCUDAを2009年のころから研究で使っていました。当時は頑張って勉強していたので自分の研究分野以外のCUDA応用の論文などを読んで高速化テクニックを勉強していました。ただ、2015年に企業に就職して以降、GPUを使うことはあってもCUDAのコードを直接自分で書くことはほとんどなくなってしまいました。このため、最近CUDAのコードを速くするためにどうすればいいのか？みたいな議論のときに、「昔は～」みたいな老害なコメントしかできない状態になってしまっていました。</p>



<p>この状態はさすがにまずいということでCUDAの勉強をし直そうと思い、この記事はそのまとめの第一弾でreductionをテーマで勉強した内容のまとめになります。私のように昔（2009年ごろ）CUDAを勉強したけど最近のCUDAわからんって人向けに記事は書いています。</p>



<p>今回測定のために書いたコードはこちらです。</p>



<p><a href="https://github.com/shu65/cuda_reduction">https://github.com/shu65/cuda_reduction</a></p>



<p>この記事にはほとんど登場しないですが、昔風に書いたコードも一緒に含めています。</p>



<p>また元にしたNVIDIAのCUDA Samplesはこちらです。</p>



<p><a href="https://github.com/NVIDIA/cuda-samples/tree/v11.8/Samples/2_Concepts_and_Techniques/reduction">https://github.com/NVIDIA/cuda-samples/tree/v11.8/Samples/2_Concepts_and_Techniques/reduction</a></p>



<p>計算時間に関してはCUDA Versionは12.0、GPUはV100を使った結果になります。計測はwarmupとしてreductionを実行したあと100回測定して平均計算時間を示しています。reductionの要素数は32M個に固定して測定します。また、reductionの計算では最初の段階はGPUを使いますが、CUDAの各thread blockの結果をさらにreductionする段階では簡単のためにCPUを使うことにします。</p>



<h2 class="wp-block-heading">Reductionとは？</h2>



<p>Reductionとは配列を入力としてとり、配列の全要素の和などを計算する処理になります。C++のコードを見るのが一番わかりやすいと思うので、以下にC++のコードを示します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="reduce_cpu" data-lang="C++"><code>int reduce(const int *in, size_t n)
{
    int ret = 0;
    for (size_t i = 0; i &lt; n; ++i)
    {
        ret += in[i];
    }
    return ret;
}</code></pre></div>



<p>「和など」と書きましたが、各要素に対して使える演算は加算以外にも積、max, minなどいくつかあります。今回は説明を簡単にするために、加算で説明します。</p>



<p>そもそもなんでreductionをテーマにしようとしたかというと、私が勉強した当時、CUDAの高速化のテクニックがいろいろ詰まった題材としてreductionがよく紹介されていて、私もreductionを写経して勉強した経験がありました。このため、昔と現在の違いが分かりやすいということで選びました。</p>



<p>ちなみに昔の私が読んでたNVIDIAのreductionの資料はまだ公開されているようです。</p>



<p><a href="https://developer.download.nvidia.com/assets/cuda/files/reduction.pdf" target="_blank" rel="noopener" title="">https://developer.download.nvidia.com/assets/cuda/files/reduction.pdf</a></p>



<p>現在のCUDA Samplesで公開されているコードも基本的にはこの高速化テクニックに沿って実装されているようなので、この記事でも同じように沿って説明します。</p>



<h2 class="wp-block-heading">Reductionを高速化していく</h2>



<h3 class="wp-block-heading">Reduce1: Baseline</h3>



<p>まずはできるだけシンプルなreducetionのコードを最近のCUDA Sampleのコードを参考にしながら書いたコードを示します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain" data-file="reduce1"><code>__global__ void reduce_gpu_v1_kernel(const int *g_in, size_t n, int *g_out)
{
    cg::thread_block cta = cg::this_thread_block();
    extern __shared__ int sdata[];
    int tid = threadIdx.x;
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i &lt; n)
    {
        sdata[tid] = g_in[i];
    }
    else
    {
        sdata[tid] = 0;
    }

    cg::sync(cta);

    for (int s = 1; s &lt; blockDim.x; s *= 2)
    {
        if (tid % (2 * s) == 0)
        {
            sdata[tid] += sdata[tid + s];
        }
        cg::sync(cta);
    }
    if (tid == 0)
    {
        g_out[blockIdx.x] = sdata[0];
    }
}</code></pre></div>



<p>このコードを見てblock内のスレッドの同期をする関数として<code>__syncthreads()</code> を使うんじゃないの？と思った方、いますよね？ちなみに私は思いました。昔の資料を参考にすると昔のreductionのコードは以下のような感じでした。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="reduce1_old" data-lang="C++"><code>__global__ void reduce_gpu_old_v1_kernel(const int *g_in, size_t n, int *g_out)
{
    extern __shared__ int sdata[];
    int tid = threadIdx.x;
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i &lt; n)
    {
        sdata[tid] = g_in[i];
    }
    else
    {
        sdata[tid] = 0;
    }

    __syncthreads();

    for (int s = 1; s &lt; blockDim.x; s *= 2)
    {
        if (tid % (2 * s) == 0)
        {
            sdata[tid] += sdata[tid + s];
        }
        __syncthreads();
    }
    if (tid == 0)
    {
        g_out[blockIdx.x] = sdata[0];
    }
}</code></pre></div>



<p>ほとんど同じですが、同期の部分で昔のコードでは<code>__syncthreads()</code> を使っているのに対して最近のコードでは<code>cooperative_groups:sync()</code> を使うようになっています。</p>



<p>最近のCUDAを追ってない方はCooperative Groupsって何？と思った方もいると思うので簡単に説明します。Cooperative GroupsはCUDA 9から導入されたもので、様々な単位でスレッドの同期などを行うための仕組みになります。</p>



<p>CUDAでは通常スレッドのまとまりとしてblock、gridなどの単位があります。ただ、同期ができるスレッドの単位はこれまでblockくらいで、CUDAのカーネル関数内で他の単位で同期するのは結構面倒でした。</p>



<p>この問題を解決して、様々な単位、例えばblockよりも少ないスレッド数や、grid単位で同期したりできる仕組みがCooperative Groupsです。より詳しく知りたい方はこれらの資料に詳しく書かれていますのでご覧ください。</p>



<ul class="wp-block-list">
<li><a href="https://developer.nvidia.com/blog/cooperative-groups/" target="_blank" rel="noopener" title="">https://developer.nvidia.com/blog/cooperative-groups/</a></li>



<li><a href="https://www.nvidia.com/content/apac/gtc/ja/pdf/2017/1041.pdf" target="_blank" rel="noopener" title="">https://www.nvidia.com/content/apac/gtc/ja/pdf/2017/1041.pdf</a></li>
</ul>



<p>このreduce1のほうの平均時間は以下の通りです。</p>



<figure class="wp-block-table aligncenter"><table><tbody><tr><td></td><td>平均計算時間(msec.)</td><td>高速化率</td><td>トータルの高速化率</td></tr><tr><td>reduce1 </td><td>0.968</td><td>1.000</td><td>1.000</td></tr></tbody></table><figcaption class="wp-element-caption">reduce1 の結果</figcaption></figure>



<p>これをベースにして他の改良をしたらどうなるか？を示していきます。</p>



<h3 class="wp-block-heading">Reduce2: Branch Divergenceの削減</h3>



<p>Reduce1のコードの問題点の1つ目として以下のif文で実行されるスレッドが飛び飛びになってしまっているという問題点があります。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="reduce1_problem" data-lang="C++"><code>    for (int s = 1; s &lt; blockDim.x; s *= 2)
    {
        if (tid % (2 * s) == 0)
        {
            sdata[tid] += sdata[tid + s];
        }
        cg::sync(cta);
    }</code></pre></div>



<p>この結果、1つのwarpでバラバラの処理が実行されることになり、branch divergenceが発生することになります。これはCUDAのコードの高速化をする際には注意する点の一つとなっています。詳しく知りたい方はこちらをご覧ください。</p>



<p><a href="https://docs.nvidia.com/cuda/cuda-c-best-practices-guide/#branching-and-divergence">https://docs.nvidia.com/cuda/cuda-c-best-practices-guide/#branching-and-divergence</a></p>



<p>これを解決するためにblock内のスレッドの0番から連続したスレッドがif文の中を実行するようにします。コードとしては以下の通りです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>    for (uint32_t s = 1; s &lt; blockDim.x; s *= 2)
    {
        uint32_t index = 2 * s * tid;
        if (index &lt; blockDim.x)
        {
            sdata[index] += sdata[index + s];
        }
        cg::sync(cta);
    }</code></pre></div>



<p>コード全体としては以下のようになります。</p>



<p><a href="https://github.com/shu65/cuda_reduction/blob/main/src/reduction_gpu_old.cu#L90-L119">https://github.com/shu65/cuda_reduction/blob/main/src/reduction_gpu_old.cu#L90-L119</a></p>



<p>このwarpのdivergenceの削減は昔から重要な高速化ポイントの一つで、昔の資料でも2.33倍高速化すると書かれています。ではこれを今回の環境で測定すると以下の通りです。</p>



<figure class="wp-block-table aligncenter"><table><tbody><tr><td></td><td>平均計算時間(msec.)</td><td>高速化率</td><td>トータルの高速化率</td></tr><tr><td>reduce1 </td><td>0.968</td><td>1.000</td><td>1.000</td></tr><tr><td>reduce2</td><td>0.564</td><td>1.716</td><td>1.716</td></tr></tbody></table><figcaption class="wp-element-caption">reduce2 の結果</figcaption></figure>



<p>ご覧の通り、現在でもwap divergenceはちゃんと削減すると効果があることがわかりました。</p>



<h3 class="wp-block-heading">Reduce3: shared memoryのbank conflictの削減</h3>



<p>次はshared memoryのbank conflictの削減のための工夫です。shared memoryは高速アクセスできるのですがbank confictに注意する必要があります。memory bankは連続アドレスに割り当てられ、複数のスレッドが同じbankを使う場合は遅延が発生します。V100の場合、以下の資料にいくつか例でどういうときにbank conflictが起きるかが書かれているので、詳しく知りたい方は参考にしてください。</p>



<p><a href="https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#shared-memory-5-x">https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#shared-memory-5-x</a></p>



<p>注意点として昔のGPUとはmemory bankが16でしたが、最近のGPUはmemory bankが32らしいので注意してください。</p>



<p>bank conflictを削減するためには、飛び飛びのアドレスにアクセスするのではなく、各スレッドが連続したアドレスにアクセスするように改良します。先ほどしめしましたが、元々のコードは以下の通りです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="reduce3_before" data-lang="C++"><code>    for (uint32_t s = 1; s &lt; blockDim.x; s *= 2)
    {
        uint32_t index = 2 * s * tid;
        if (index &lt; blockDim.x)
        {
            sdata[index] += sdata[index + s];
        }
        cg::sync(cta);
    }</code></pre></div>



<p>このコードでは例えば0番と1番のスレッドは5回目のイテレーションで32個隣をみるようになるので、このタイミングでmemory bankが衝突するようになります。</p>



<p>このコードに対してshared memoryのbank confilict削減するために以下のようにforループを工夫します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="reduce3" data-lang="C++"><code>    for (unsigned int s = blockDim.x / 2; s &gt; 0; s &gt;&gt;= 1)
    {
        if (tid &lt; s)
        {
            sdata[tid] += sdata[tid + s];
        }
        cg::sync(cta);
    }</code></pre></div>



<p>こうすることでmemory bankが衝突することはなくなります。本当にbank conflictが削減しているのか？を確認したい場合、Nsight Computeというプロファイラを使うと確認できます。ちなみに昔からあったプロファイラのnvprofとnvvpはAmpereからサポートされなくなったので注意してください。</p>



<p>このコードを測定すると結果は以下の通りです。</p>



<figure class="wp-block-table aligncenter"><table><tbody><tr><td></td><td>平均計算時間(msec.)</td><td>高速化率</td><td>トータルの高速化率</td></tr><tr><td>reduce1 </td><td>0.968</td><td>1.000</td><td>1.000</td></tr><tr><td>reduce2</td><td>0.564</td><td>1.716</td><td>1.716</td></tr><tr><td>reduce3</td><td>0.453</td><td>1.246</td><td>2.137</td></tr></tbody></table><figcaption class="wp-element-caption">reduce3 の結果</figcaption></figure>



<p>reduce3では1.2倍なので若干速くなっていますが、昔の資料をみると2倍速くなってたらしいのでだいぶ効果がうすれたなかという印象があります。</p>



<h3 class="wp-block-heading">Reduce4: スレッドの実行効率向上</h3>



<p>次にスレッドの実行効率の向上を図ります。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="reduce4_before" data-lang="C++"><code>    for (unsigned int s = blockDim.x / 2; s &gt; 0; s &gt;&gt;= 1)
    {
        if (tid &lt; s)
        {
            sdata[tid] += sdata[tid + s];
        }
        cg::sync(cta);
    }</code></pre></div>



<p>このループはよく見ると最初のイテレーションでblockの半分のスレッドはif文に入らないことが分かります。この結果半分のスレッドは一度もreductionの加算を実行しないことになります。これではせっかくスレッドを立ち上げたのにもったいないことになります。このため、効率をもう少しあげるために、以下のように最初にshared memoryに代入する部分を工夫します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="reduce4" data-lang="C++"><code>    int tid = threadIdx.x;
    int i = blockIdx.x * (blockDim.x * 2) + threadIdx.x;
    int sum_value = 0;
    if (i &lt; n)
    {
        sum_value = g_in[i];
    }
    if ((i + blockDim.x) &lt; n)
    {
        sum_value += g_in[i + blockDim.x];
    }
    sdata[tid] = sum_value;</code></pre></div>



<p>このように最初shared memoryに足す前に各スレッドが2か所データを読み込んで加算してshared memoryに足すようにします。これで少なくとも1回は各スレッドがreductionの加算を実行することになります。またこの結果、カーネル実行時に起動するblock数を半分にできます。この結果計算時間は以下のようになります。</p>



<figure class="wp-block-table aligncenter"><table><tbody><tr><td></td><td>平均計算時間(msec.)</td><td>高速化率</td><td>トータルの高速化率</td></tr><tr><td>reduce1 </td><td>0.968</td><td>1.000</td><td>1.000</td></tr><tr><td>reduce2</td><td>0.564</td><td>1.716</td><td>1.716</td></tr><tr><td>reduce3</td><td>0.453</td><td>1.246</td><td>2.137</td></tr><tr><td>reduce4</td><td>0.250</td><td>1.814</td><td>3.876</td></tr></tbody></table><figcaption class="wp-element-caption">reduce4 の結果</figcaption></figure>



<p>結果からわかる通りやってみると1.8倍速くなっていて、昔も1.7倍の高速化があったらしいので、この改良は今でも効果的なことがわかります。</p>



<h3 class="wp-block-heading">Reduce6: 完全なloop unroll</h3>



<p>さて、次は1つ飛んでReduce6のloop unrollについて説明します。</p>



<p>昔のReduce5はwarp周りの改良なのですが、このwarp周りの事情が昔と今で変わっているのと、同じ部分に別の最適化の話が新しくCUDA Sampleに追加されているので最後にまとめて説明します。</p>



<p>では、reduce6の完全なloop unrollについてです。loop unrollはCUDAをやっていれば最後の手段的に出てくるテクニックで、ループの回数が分かっているならfor文やwhile文を使わずに直書きするというテクニックです。こうすることで、ループを抜けるかの条件判定などをなくすことができます。この結果、速くなるというものです。</p>



<p>今回のreductionでいえば、blockのスレッド数に依存してfor文のループ回数が決まっているので、blockのスレッド数を決め打ちしてあげればfor文なしで書けることになります。この時templateを使えば条件分岐で指定のblockのスレッド数の関数を呼び出すということもでき、ある程度柔軟にスレッド数を指定できるカーネル関数もできます。reductionの加算部分のfor文をunrollするとコードになります(長いので一部省略してます)。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>    if (kBlockSize &gt;= 512)
    {
        if (tid &lt; 256)
        {
            sdata[tid] += sdata[tid + 256];
        }
        cg::sync(cta);
    }
    if (kBlockSize &gt;= 256)
    {
        if (tid &lt; 128)
        {
            sdata[tid] += sdata[tid + 128];
        }
        cg::sync(cta);
    }
　　...
    if (kBlockSize &gt;= 2)
    {
        if (tid &lt; 1)
        {
            sdata[tid] += sdata[tid + 1];
        }
        cg::sync(cta);
    }</code></pre></div>



<p>今回私の書いたコードは簡単のためにカーネル関数の呼び出しもとでblock数を512で決め打ちで書いています。</p>



<p><a href="https://github.com/shu65/cuda_reduction/blob/main/src/reduction_gpu.cu#L284-L285">https://github.com/shu65/cuda_reduction/blob/main/src/reduction_gpu.cu#L284-L285</a></p>



<p>templateを使ったスレッド数の分岐に関して詳しく知りたい場合はCUDAのSampleの以下のコードをご覧ください。</p>



<p><a href="https://github.com/NVIDIA/cuda-samples/blob/v11.8/Samples/2_Concepts_and_Techniques/reduction/reduction_kernel.cu#L650-L703">https://github.com/NVIDIA/cuda-samples/blob/v11.8/Samples/2_Concepts_and_Techniques/reduction/reduction_kernel.cu#L650-L703</a></p>



<p>この変更を加えた実行時間は以下のようになります。</p>



<figure class="wp-block-table aligncenter"><table><tbody><tr><td></td><td>平均計算時間(msec.)</td><td>高速化率</td><td>トータルの高速化率</td></tr><tr><td>reduce1 </td><td>0.968</td><td>1.000</td><td>1.000</td></tr><tr><td>reduce2</td><td>0.564</td><td>1.716</td><td>1.716</td></tr><tr><td>reduce3</td><td>0.453</td><td>1.246</td><td>2.137</td></tr><tr><td>reduce4</td><td>0.250</td><td>1.814</td><td>3.876</td></tr><tr><td>reduce6</td><td>0.239</td><td>1.043</td><td>4.043</td></tr></tbody></table><figcaption class="wp-element-caption">reduce6 の結果</figcaption></figure>



<p>この変更も昔は1.41倍速くなっていたらしいのですが、ほとんど効果がなくなっているような印象です。個人的にはloop をunrollするとコードのメンテナンス性が非常に悪くなって嫌いなので、これの効果が小さくなっていることは私としてはちょっとうれしいです。</p>



<h3 class="wp-block-heading">Reduce7: 1スレッドあたりの仕事を増やす</h3>



<p>Reduce7では、昔の資料の最後の最適化で起動するスレッド数を減らしつつ、1スレッドあたりのreductionの加算の回数を増やすということをします。これを実現するためにreductionの最初はシーケンシャルに加算を実行していき、その後、いままでの加算する担当のスレッドを半分ずつ減らすというアルゴリズムにします。まず変更前のコードは以下のとおりです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="reduction7_before" data-lang="C++"><code>    int tid = threadIdx.x;
    int i = blockIdx.x * (blockDim.x * 2) + threadIdx.x;
    int sum_value = 0;
    if (i &lt; n)
    {
        sum_value = g_in[i];
    }
    if ((i + blockDim.x) &lt; n)
    {
        sum_value += g_in[i + blockDim.x];
    }
    sdata[tid] = sum_value;
    cg::sync(cta);</code></pre></div>



<p>この部分、以下のようにwhileループを追加します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="reduce7" data-lang="C++"><code>    int tid = threadIdx.x;
    int i = blockIdx.x * (blockDim.x * 2) + threadIdx.x;
    int grid_size = 2 * blockDim.x * gridDim.x;
    int sum_value = 0;

    while (i &lt; n)
    {
        sum_value += g_in[i];
        if ((i + kBlockSize) &lt; n)
        {
            sum_value += g_in[i + kBlockSize];
        }
        i += grid_size;
    }
    sdata[tid] = sum_value;
    cg::sync(cta);</code></pre></div>



<p>これに加えてカーネル関数の起動時のblock数を減らします。今回は決め打ちで128に固定しています。結果は以下の通りです。</p>



<figure class="wp-block-table aligncenter"><table><tbody><tr><td></td><td>平均計算時間(msec.)</td><td>高速化率</td><td>トータルの高速化率</td></tr><tr><td>reduce1 </td><td>0.968</td><td>1.000</td><td>1.000</td></tr><tr><td>reduce2</td><td>0.564</td><td>1.716</td><td>1.716</td></tr><tr><td>reduce3</td><td>0.453</td><td>1.246</td><td>2.137</td></tr><tr><td>reduce4</td><td>0.250</td><td>1.814</td><td>3.876</td></tr><tr><td>reduce6</td><td>0.239</td><td>1.043</td><td>4.043</td></tr><tr><td>reduce7</td><td>0.214</td><td>1.119</td><td>4.523</td></tr></tbody></table><figcaption class="wp-element-caption">reduce7 の結果</figcaption></figure>



<p>この最適化に関しては昔も1.42倍と効果が小さかったですが、さらに効果が小さくなっている印象です。</p>



<h3 class="wp-block-heading">Reduce8 : スレッド数がwarpサイズになった以降の同期の最適化(Reduce5の部分) </h3>



<p>昔の資料ではreduce7までの最適化ですが、ここからCUDAの新しい機能による最適化に関して紹介していきます。まずはスレッド数がwarpサイズになったところからの同期に関してです。昔、勉強してた人は「え、reduce5がそれでは？」と思った方もいるかもしれませんが、reduce5のコードそのままだと実は危険なので、改良したのものを紹介します。</p>



<p>昔、warpサイズは32で、連続したwarpサイズ分のスレッドは同時に実行されるので同期が必要ないという話がありました。これに基づいて同期を削除して高速化したのがreduce5です。ただ、CUDA 9からwarpに関するいろいろな機能追加があり、その中でwarpサイズ分のスレッドがすべて同時に実行されるという保証がなくなってしまいました。このためwarpサイズ分以下のスレッド数しかないとはいえ、同期なしだと何等かのエラーが発生する可能性があります。</p>



<p>このため、reduce5に同期を加える形にします。コードとしては以下の通りです(長いので一部省略してます)。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="reduce8" data-lang="C++"><code>    cg::thread_block_tile&lt;32&gt; tile32 = cg::tiled_partition&lt;32&gt;(cta);
    if (cta.thread_rank() &lt; 32)
    {
        if (kBlockSize &gt;= 64)
        {
            if (tid &lt; 32)
            {
                sdata[tid] += sdata[tid + 32];
            }
            tile32.sync();
        }
        if (kBlockSize &gt;= 32)
        {
            if (tid &lt; 16)
            {
                sdata[tid] += sdata[tid + 16];
            }
            tile32.sync();
        }
        ...
        if (kBlockSize &gt;= 2)
        {
            if (tid &lt; 1)
            {
                sdata[tid] += sdata[tid + 1];
            }
            tile32.sync();
        }
    }</code></pre></div>



<p>こちらのコードではまず、warpサイズの32でスレッド数が十分になったタイミングで<code>tiled_partition</code>を使って32区切りの<code>thread_block_tile</code> を作り、そのうち0番から31番までのスレッドだけその後の処理をするようにします。さらに同期はこのタイル単位で行うことでwarpサイズ内での同期にしています。<code>thread_block_tile</code> を使わずに<code>__syncwarp()</code> を呼んで同期するという手もありますが、速度的にはそこまで変化しなかったので、ここでは<code>thread_block_tile</code> を使うバージョンだけ測定します。</p>



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



<figure class="wp-block-table aligncenter"><table><tbody><tr><td></td><td>平均計算時間(msec.)</td><td>高速化率</td><td>トータルの高速化率</td></tr><tr><td>reduce1 </td><td>0.968</td><td>1.000</td><td>1.000</td></tr><tr><td>reduce2</td><td>0.564</td><td>1.716</td><td>1.716</td></tr><tr><td>reduce3</td><td>0.453</td><td>1.246</td><td>2.137</td></tr><tr><td>reduce4</td><td>0.250</td><td>1.814</td><td>3.876</td></tr><tr><td>reduce6</td><td>0.239</td><td>1.043</td><td>4.043</td></tr><tr><td>reduce7</td><td>0.214</td><td>1.119</td><td>4.523</td></tr><tr><td>reduce8</td><td>0.214</td><td>0.999</td><td>4.519</td></tr></tbody></table><figcaption class="wp-element-caption">reduce8 の結果</figcaption></figure>



<p>やってみるとこれに関しては全然速くならないという結果でした。ただ、ループのunrollはreduce6ですでにやってあるし、同期なしにはできなくなってしまっているのでこんなものかもしれないと思っています。</p>



<h3 class="wp-block-heading">Reduce9 : shfl_down()の使用</h3>



<p>最後に<code>shfl_down()</code> の使用による高速化です。先ほど紹介した通りCUDA 9のタイミングでwarp levelの関数がいくつか追加されました。warpレベルの同期もその一つですが、それに加えてデータ交換の関数もいくつか追加されています。これらの関数はCooperative Groupsにも同様の関数があり、今回はそちらを使うようにします。reduce8の段階のコードは以下の通りです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="reduce9_before" data-lang="C++"><code>    cg::thread_block_tile&lt;32&gt; tile32 = cg::tiled_partition&lt;32&gt;(cta);
    if (cta.thread_rank() &lt; 32)
    {
        if (kBlockSize &gt;= 64)
        {
            if (tid &lt; 32)
            {
                sdata[tid] += sdata[tid + 32];
            }
            tile32.sync();
        }
        if (kBlockSize &gt;= 32)
        {
            if (tid &lt; 16)
            {
                sdata[tid] += sdata[tid + 16];
            }
            tile32.sync();
        }
        ...
        if (kBlockSize &gt;= 2)
        {
            if (tid &lt; 1)
            {
                sdata[tid] += sdata[tid + 1];
            }
            tile32.sync();
        }
    }</code></pre></div>



<p><code>shfl_down()</code> を使う場合は以下のようになります。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-cpp" data-file="reduce9" data-lang="C++"><code>    cg::thread_block_tile&lt;32&gt; tile32 = cg::tiled_partition&lt;32&gt;(cta);
    if (cta.thread_rank() &lt; 32)
    {
        sum_value = sdata[tid];
        if (kBlockSize &gt;= 64)
        {
            sum_value += sdata[tid + 32];
        }
        for (int offset = tile32.size() / 2; offset &gt; 0; offset /= 2)
        {
            sum_value += tile32.shfl_down(sum_value, offset);
        }
    }</code></pre></div>



<p>このときCUDA Sampleのコードに合わせてループのunrollをやめているので注意してください。また<code>shfl_down()</code>を使うケースではshared memoryを使わなくなっているので同期が不要になります。それ以外は基本処理の流れは同じです。</p>



<p>このコードの実行結果は以下の通りです。</p>



<figure class="wp-block-table aligncenter"><table><tbody><tr><td></td><td>平均計算時間(msec.)</td><td>高速化率</td><td>トータルの高速化率</td></tr><tr><td>reduce1 </td><td>0.968</td><td>1.000</td><td>1.000</td></tr><tr><td>reduce2</td><td>0.564</td><td>1.716</td><td>1.716</td></tr><tr><td>reduce3</td><td>0.453</td><td>1.246</td><td>2.137</td></tr><tr><td>reduce4</td><td>0.250</td><td>1.814</td><td>3.876</td></tr><tr><td>reduce6</td><td>0.239</td><td>1.043</td><td>4.043</td></tr><tr><td>reduce7</td><td>0.214</td><td>1.119</td><td>4.523</td></tr><tr><td>reduce8</td><td>0.214</td><td>0.999</td><td>4.519</td></tr><tr><td>reduce9</td><td>0.213</td><td>1.008</td><td>4.553</td></tr></tbody></table><figcaption class="wp-element-caption">reduce9 の結果</figcaption></figure>



<p>結果をみると少しだけ速くなっていることがわかります。</p>



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



<p>今回、改めてCUDAの復習ということでreductionについての今時のコードの解説を書きました。やってみると結構昔と結果が変わって驚きました。なので、ちゃんと改めて復習してよかったと思っています。とりあえず、これで少しは老害発言をへらせるんじゃないかと思っています。</p>



<p>CUDA Sampleには今回紹介しなかった他のバージョンのreductionも書かれているのでもし気になる方は見てみてください。</p><p>The post <a href="https://www.mattari-benkyo-note.com/2023/01/29/cuda-reduction2023/">CUDAの高速化の復習2023年版 Reduction編</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/01/29/cuda-reduction2023/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1496</post-id>	</item>
		<item>
		<title>Visual Studio Codeを使ってCUDAのコードを書く</title>
		<link>https://www.mattari-benkyo-note.com/2022/12/20/cuda-vscode/</link>
					<comments>https://www.mattari-benkyo-note.com/2022/12/20/cuda-vscode/#respond</comments>
		
		<dc:creator><![CDATA[Shuji Suzuki (shu)]]></dc:creator>
		<pubDate>Mon, 19 Dec 2022 22:23:43 +0000</pubDate>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[CUDA]]></category>
		<category><![CDATA[vscode]]></category>
		<guid isPermaLink="false">https://www.mattari-benkyo-note.com/?p=1458</guid>

					<description><![CDATA[<p>はじめに 最近、久しぶりにCUDAでコードを書きそうな状況になってきました。このため、ここ数日CUDAの開発環境の準備をしているところです。その際、周りの人が結構Visual Studio Code (VSCode) を [&#8230;]</p>
<p>The post <a href="https://www.mattari-benkyo-note.com/2022/12/20/cuda-vscode/">Visual Studio Codeを使ってCUDAのコードを書く</a> first appeared on <a href="https://www.mattari-benkyo-note.com">まったり勉強ノート</a>.</p>]]></description>
										<content:encoded><![CDATA[<h1 class="wp-block-heading">はじめに</h1>



<p>最近、久しぶりにCUDAでコードを書きそうな状況になってきました。このため、ここ数日CUDAの開発環境の準備をしているところです。その際、周りの人が結構Visual Studio Code (VSCode) を使っていろいろな言語のコードを書いているという話を聞いたことを思い出し、私自身、まだVSCodeを使ったことなかったので、せっかくならCUDAの開発をVSCodeを使ってやってみるかーと思ってCUDAをVSCodeを使って書くための準備を開始しした次第です。 </p>



<p>そして、VSCodeが何もわからない状態からCUDAのコードをある程度VSCodeを使って書くところまでできたので、今回調べたことをまとめたものを記事にしました。</p>



<p>前提として、CUDAのセットアップは終わっている状態であると仮定して説明をします。また、開発環境はWindows11のマシンでVSCodeを起動して、VSCodeからリモートのUbuntusマシンに接続して開発することを想定して説明します。</p>



<p>今回のVSCodeの設定をして、CUDAの簡単なコード作成したものをGitHubに以下のところにあげてあります。コード全体を見たいという方は見てみてください。</p>



<p><a href="https://github.com/shu65/cuda_vscode_sample">https://github.com/shu65/cuda_vscode_sample</a></p>



<h1 class="wp-block-heading">Visual Studio Codeインストール</h1>



<p>まずは以下のURLからVSCodeの本家のサイトからVSCodeのインストーラーを自分の環境に合わせてダウンロードしてください。</p>



<p><a href="https://code.visualstudio.com/">https://code.visualstudio.com/</a></p>



<p>その後、インストーラーを実行してVSCodeをインストールしてください。</p>



<h1 class="wp-block-heading">Visual Studio CodeのExtensionsインストール</h1>



<p>CUDAを開発するにあたり最低限必要なextensionを入れます。まず、VSCodeのExtensionsのビューを開きます。ビューは以下のボタンをクリックするか <code>Ctrl</code> + <code>Shift</code> + <code>x</code> で開くことができます。</p>


<div class="wp-block-image">
<figure class="aligncenter size-full is-resized"><img loading="lazy" decoding="async" src="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/extension.png" alt="" class="wp-image-1459" width="115" height="326" srcset="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/extension.png 207w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/extension-106x300.png 106w" sizes="auto, (max-width: 115px) 100vw, 115px" /><figcaption class="wp-element-caption">VSCode Extension</figcaption></figure></div>


<p>Extensionsのビューを開いたら以下の二つのものを検索して[Install]ボタンを押します。</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/2022/12/C-1024x299.png" alt="" class="wp-image-1460" width="511" height="149" srcset="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/C-1024x299.png 1024w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/C-300x88.png 300w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/C-768x225.png 768w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/C.png 1026w" sizes="auto, (max-width: 511px) 100vw, 511px" /><figcaption class="wp-element-caption">C/C++</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/2022/12/CUDA-1024x234.png" alt="" class="wp-image-1461" width="521" height="119"/><figcaption class="wp-element-caption">Nsight Visual Studio Code Edition</figcaption></figure></div>


<p>C/C++ のほうは名前の通り、C/C++のコードを書くためのextensionで、Nsight Visual Studio Code EditionはCUDAのdebuggerをVSCodeを使ってGUIで操作するためのものになっています。</p>



<h1 class="wp-block-heading">CUDA開発で必要なVisual Studio Codeの設定ファイルを作る</h1>



<h2 class="wp-block-heading">c_cpp_properties.jsonの作成と編集</h2>



<p><code>c_cpp_properties.json</code> はコード補完や各種C++に関する設定を書くファイルです。VSCodeのウインドウの上部にある[View]→[Command Pallet]を選択するか<code>Ctrl</code> + <code>Shift</code> + <code>p</code> というショートカットキーで以下のような入力がでてきます。</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="163" src="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/ctrl_shift_p-1024x163.png" alt="" class="wp-image-1464" srcset="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/ctrl_shift_p-1024x163.png 1024w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/ctrl_shift_p-300x48.png 300w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/ctrl_shift_p-768x122.png 768w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/ctrl_shift_p.png 1249w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">Command Pallet</figcaption></figure></div>


<p>ここで[C/C++: Edit Configurations]を選択するとデフォルトの <code>c_cpp_properties.json</code> を<code>.vscode</code> というディレクトリ下に作ってくれます。</p>



<p>C++のコードであれば基本はデフォルトのままでもよいですが、CUDAのコードの開発の場合はインクルードパスにCUDAのディレクトリを追記します。具体的には以下のようにします。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-json" data-file="sub_c_cpp_properties.json " data-lang="JSON"><code>            &quot;includePath&quot;: [
                &quot;${workspaceFolder}/**&quot;,
                &quot;/usr/local/cuda/include&quot;
            ],</code></pre></div>



<p>ただし、WindowsにCUDAを入れている方やLinux系でもインストール時のパスの指定などが違うと上記のパスにincludeディレクトリがない可能性があるので注意してください。</p>



<h2 class="wp-block-heading">tasks.jsonの作成と編集</h2>



<p>次にビルドを簡単に実行できるように <code>tasks.json</code> を準備します。VSCodeの場合、taskでコンパイラを叩いて実行する例をよくみます。ただ、この形式だとVSCodeを使わないでビルドする方法が謎だったので、今回はよくあるMakefileを用意して<code>make</code>コマンドでビルドすることにします。</p>



<p>Makefileは以下のものを予めディレクトリに追加しておきます。</p>



<p><a href="https://github.com/shu65/cuda_vscode_sample/blob/main/Makefile">https://github.com/shu65/cuda_vscode_sample/blob/main/Makefile</a></p>



<p><code>c_cpp_properties.json</code> と同様にVSCodeのウインドウの上部にある[View]→[Command Pallet]を選択するか<code>Ctrl</code> + <code>Shift</code> + <code>p</code> を叩きます。</p>



<p>そして[Tasks: Configure Task]を選択します。</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="233" src="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/config_task-1024x233.png" alt="" class="wp-image-1465" srcset="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/config_task-1024x233.png 1024w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/config_task-300x68.png 300w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/config_task-768x175.png 768w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/config_task.png 1220w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">Command Pallet (taskファイル作成)</figcaption></figure></div>


<p>次に以下のように[Create tasksjsonfile from template]を選択します。</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="671" src="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/create_task1-1024x671.png" alt="" class="wp-image-1466" srcset="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/create_task1-1024x671.png 1024w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/create_task1-300x197.png 300w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/create_task1-768x503.png 768w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/create_task1.png 1234w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">Create tasks.jsonの選択</figcaption></figure></div>


<p>その後、[Others]を選択してファイルを作ります。</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="233" src="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/create_task2-1024x233.png" alt="" class="wp-image-1467" srcset="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/create_task2-1024x233.png 1024w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/create_task2-300x68.png 300w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/create_task2-768x175.png 768w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/create_task2.png 1243w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure></div>


<p>これで<code>tasks.json</code>を<code>.vscode</code> 下に作ってくれます。あとは以下のようにmakeを叩くtaskを追加します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-json" data-file="sub_tasks.json" data-lang="JSON"><code>        {
            &quot;label&quot;: &quot;make&quot;,
            &quot;type&quot;: &quot;shell&quot;,
            &quot;command&quot;: &quot;make&quot;,
            &quot;args&quot;: [],
            &quot;problemMatcher&quot;: [],
            &quot;group&quot;: {
                &quot;kind&quot;: &quot;build&quot;,
                &quot;isDefault&quot;: true
            }
        }</code></pre></div>



<p>これで<code>Ctrl</code> + <code>Shift</code> + <code>b</code> で上記のmakeを実行するtaskを実行できます。</p>



<h2 class="wp-block-heading">launch.jsonの作成と編集</h2>



<p>最後に<code>launch.json</code> の作成と必要な部分の編集についてです。</p>



<p><code>launch.json</code> を作成する際は、ウインドウ上部の[Run]→[Add Configuration&#8230;]を選択するか<code>Ctrl</code> + <code>Shift</code> + <code>p</code> を叩いて、以下のように[Debug: Add Configuration&#8230;]を選択します。</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="226" src="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/lanch1-1024x226.png" alt="" class="wp-image-1468" srcset="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/lanch1-1024x226.png 1024w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/lanch1-300x66.png 300w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/lanch1-768x169.png 768w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/lanch1.png 1224w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">lanch.jsonの作成</figcaption></figure></div>


<p>次に[CUDA C++ (CUDA-GDB)]を選択します。</p>


<div class="wp-block-image">
<figure class="aligncenter size-large"><img loading="lazy" decoding="async" width="1024" height="226" src="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/lanch2-1024x226.png" alt="" class="wp-image-1469" srcset="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/lanch2-1024x226.png 1024w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/lanch2-300x66.png 300w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/lanch2-768x170.png 768w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/lanch2.png 1203w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">CUDA C++(CUDA-GDB)の選択</figcaption></figure></div>


<p>そしてprogramの部分を以下のように変更します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-json" data-file="sub_lanch.json" data-lang="JSON"><code> &quot;program&quot;: &quot;${workspaceFolder}/main&quot;</code></pre></div>



<p>実行ファイル名はMakefileで<code>main</code> という名前を指定していたので、それをそのまま書いています。この部分はプロジェクトごとに変わると思うので注意してください。</p>



<p><code>launch.json</code>の修正が完了したら<code>F5</code> で<code>cuda-gdb</code> を使ったデバッグを実行できます。</p>



<h1 class="wp-block-heading">Visual Studio CodeでCUDAのDebuggerを試す</h1>



<p>ここまででCUDA開発する最低限の設定ができました。ここからは<code>cuda-gdb</code> の使い方に関して少し説明します。基本的な使い方はC++ の<code>gdb</code> を使ったデバッグと同じだと思いますが、<code>cuda-gdb</code>を使うとCUDAはカーネル関数内の各スレッドの変数の状況も見ることができます。ただ、デバッガーで注目しているスレッドを切り替える方法を見つけるのに苦労したので、同じように苦労する人が減ることを願ってその部分だけここでは説明します。</p>



<p>デバッグするコードはこちらのリポジトリにあるもので説明します。</p>



<p><a href="https://github.com/shu65/cuda_vscode_sample">https://github.com/shu65/cuda_vscode_sample</a></p>



<p>ちなみにドキュメントのほうにはここに詳しく書いてあります。</p>



<p><a href="https://docs.nvidia.com/nsight-visual-studio-code-edition/cuda-inspect-state/index.html#abstract">https://docs.nvidia.com/nsight-visual-studio-code-edition/cuda-inspect-state/index.html#abstract</a></p>



<p>まず、CUDAカーネルの関数のデバッグをするときはコンパイル時に以下のように<code>-G</code> を付けてコンパイルしておきます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-file="nvcc-G.bash" data-lang="Bash"><code>nvcc -c -g -G src/main.cu -o src/main.o </code></pre></div>



<p>そして、ブレイクポイントを指定したい行番号の左隣をクリックしてブレイクポイントを指定します。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="221" src="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/breakpoint-1-1024x221.png" alt="" class="wp-image-1471" srcset="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/breakpoint-1-1024x221.png 1024w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/breakpoint-1-300x65.png 300w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/breakpoint-1-768x166.png 768w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/breakpoint-1.png 1037w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">breakpoint</figcaption></figure>



<p>これで指定された位置でプログラムの実行が止まるようになります。</p>



<p>準備ができたら<code>F5</code> でデバッガーを使って実行します。すると以下のようにブレイクポイントの地点でとまります。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="208" src="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/cuda-gdb_stop-1024x208.png" alt="" class="wp-image-1472" srcset="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/cuda-gdb_stop-1024x208.png 1024w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/cuda-gdb_stop-300x61.png 300w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/cuda-gdb_stop-768x156.png 768w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/cuda-gdb_stop.png 1104w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /><figcaption class="wp-element-caption">cuda-gdb 停止</figcaption></figure>



<p>ちなみにデバッガーを起動したタイミングではblockとスレッドのインデックスがそれぞれ<code>（0, 0, 0）(0, 0, 0)</code>のスレッドの情報が表示されています。現在どのスレッドが見えているかは右下のこの部分に表示されています。</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="599" height="127" src="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/block-thread-1.png" alt="" class="wp-image-1474" srcset="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/block-thread-1.png 599w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/block-thread-1-300x64.png 300w" sizes="auto, (max-width: 599px) 100vw, 599px" /><figcaption class="wp-element-caption">block thread</figcaption></figure></div>


<p>見たいスレッドを切り替える場合は<code>Ctrl</code> + <code>Shift</code> + <code>p</code> でコマンドパレットを開いて[CUDA: Change CUDA debug focus]を選択するか、上の図のスレッドのインデックス部分をクリックすると以下のような入力が出てきます。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="861" height="207" src="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/block-thread2.png" alt="" class="wp-image-1475" srcset="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/block-thread2.png 861w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/block-thread2-300x72.png 300w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/block-thread2-768x185.png 768w" sizes="auto, (max-width: 861px) 100vw, 861px" /><figcaption class="wp-element-caption">block thread指定</figcaption></figure>



<p>ここに以下のように<code>block (X, Y, Z) thread (X, Y, Z)</code> の形式で見たいスレッドのインデックスを入力してエンターを押します。</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img loading="lazy" decoding="async" width="756" height="172" src="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/block-thread3.png" alt="" class="wp-image-1476" srcset="https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/block-thread3.png 756w, https://www.mattari-benkyo-note.com/wp-content/uploads/2022/12/block-thread3-300x68.png 300w" sizes="auto, (max-width: 756px) 100vw, 756px" /><figcaption class="wp-element-caption">block threadの入力例</figcaption></figure></div>


<p>上の図の例ではblock threadのインデックスが<code>（1, 0, 0）(14, 0, 0)</code>のものを指定しています。こうすると表示するスレッドを切り替えることができます。</p>



<p>他にもデバッガーの使い方は基本的にはドキュメントに書いてありますので、詳しく知りたい方は参考資料のリンクからドキュメントに飛んでご確認ください。</p>



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



<p>今回はVSCodeがそもそも初めてだったこともあり、VSCodeの使い方を調べるのに苦労した感があります。また、デバッガーの使い方も昔使っていたeclipse版と結構違う部分があって苦労しました。同じようにどうすればいいかわからない人の参考になれば幸いです。</p>



<h1 class="wp-block-heading">参考資料</h1>



<ul class="wp-block-list">
<li><a href="https://docs.nvidia.com/nsight-visual-studio-code-edition/" target="_blank" rel="noopener" title="">https://developer.nvidia.com/nsight-visual-studio-code-edition</a></li>



<li><a href="https://docs.nvidia.com/nsight-visual-studio-code-edition/" target="_blank" rel="noopener" title="">https://docs.nvidia.com/nsight-visual-studio-code-edition/</a></li>



<li><a href="https://www.youtube.com/watch?v=gN3XeFwZ4ng" target="_blank" rel="noopener" title="">https://www.youtube.com/watch?v=gN3XeFwZ4ng</a></li>
</ul><p>The post <a href="https://www.mattari-benkyo-note.com/2022/12/20/cuda-vscode/">Visual Studio Codeを使ってCUDAのコードを書く</a> first appeared on <a href="https://www.mattari-benkyo-note.com">まったり勉強ノート</a>.</p>]]></content:encoded>
					
					<wfw:commentRss>https://www.mattari-benkyo-note.com/2022/12/20/cuda-vscode/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1458</post-id>	</item>
		<item>
		<title>PyTorch 1.10の新機能「CUDA Graphs」のパフォーマンスを測定してみる</title>
		<link>https://www.mattari-benkyo-note.com/2021/10/23/pytorch-cuda-graphs/</link>
					<comments>https://www.mattari-benkyo-note.com/2021/10/23/pytorch-cuda-graphs/#respond</comments>
		
		<dc:creator><![CDATA[Shuji Suzuki (shu)]]></dc:creator>
		<pubDate>Sat, 23 Oct 2021 06:39:41 +0000</pubDate>
				<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[CUDA]]></category>
		<category><![CDATA[pytorch]]></category>
		<guid isPermaLink="false">https://www.mattari-benkyo-note.com/?p=143</guid>

					<description><![CDATA[<p>はじめに 10/21にPyTorch 1.10がリリースされ、今回も面白そうな機能が追加されました。個人的には楽しみにしていた「CUDA Graphs」のAPIのベータ版が追加されたということで早速試してみました。今回は [&#8230;]</p>
<p>The post <a href="https://www.mattari-benkyo-note.com/2021/10/23/pytorch-cuda-graphs/">PyTorch 1.10の新機能「CUDA Graphs」のパフォーマンスを測定してみる</a> first appeared on <a href="https://www.mattari-benkyo-note.com">まったり勉強ノート</a>.</p>]]></description>
										<content:encoded><![CDATA[<h2 class="wp-block-heading">はじめに </h2>



<p>10/21に<a href="https://pytorch.org/blog/pytorch-1.10-released/" target="_blank" rel="noreferrer noopener">PyTorch 1.10がリリース</a>され、今回も面白そうな機能が追加されました。個人的には楽しみにしていた「CUDA Graphs」のAPIのベータ版が追加されたということで早速試してみました。今回はこの試した結果の記事になります。</p>



<h2 class="wp-block-heading">CUDA Graphsとは？</h2>



<p>CUDA Graphsは<a href="https://developer.nvidia.com/blog/cuda-10-features-revealed/" title="https://developer.nvidia.com/blog/cuda-10-features-revealed/" target="_blank" rel="noreferrer noopener">CUDA 10</a>で追加されたCUDAの機能の一つで、複数のCUDA Kernelの実行にかかるオーバーヘッドを減らすための機能です。</p>



<p>基本的には依存関係表すことができるグラフにCUDA Kernelを登録して、依存関係を考慮して順番にCUDA Kernelを実行するという仕組みです。このCUDA Graphsを通して実行すると普通にCUDA Knernelを実行するのに比べてCUDA Kernelの実行オーバーヘッドを減らすことができます。</p>



<p>詳しくはNVIDIA Developer Blogに記事があるのでご覧ください。</p>



<figure class="wp-block-embed is-type-wp-embed is-provider-nvidia-developer-blog wp-block-embed-nvidia-developer-blog"><div class="wp-block-embed__wrapper">
<blockquote class="wp-embedded-content" data-secret="s2m4mwXP73"><a href="https://developer.nvidia.com/blog/cuda-graphs/">Getting Started with CUDA Graphs</a></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;Getting Started with CUDA Graphs&#8221; &#8212; NVIDIA Developer Blog" src="https://developer.nvidia.com/blog/cuda-graphs/embed/#?secret=s2m4mwXP73" data-secret="s2m4mwXP73" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<h2 class="wp-block-heading">PyTorchでCUDA Graphsを使う</h2>



<p>PyTorchでCUDA Graphsを使うには主に以下の2つのステップを踏みます。</p>



<ol class="wp-block-list"><li>CUDA GraphsのStream Captureの機能を使ってグラフを構築</li><li>構築したグラフを実行</li></ol>



<p>それぞれについて順番に説明します。</p>



<p>また、ディープラーニングにおいてすべてのレイヤーがグラフに登録できるものでなかった場合、ネットワークの一部部分だけグラフを構築する方法も用意されています。こちらは今回は触れません。詳しく知りたい方は以下のドキュメントをご覧ください。</p>



<p><a href="https://pytorch.org/docs/master/notes/cuda.html#partial-network-capture">https://pytorch.org/docs/master/notes/cuda.html#partial-network-capture</a></p>



<h3 class="wp-block-heading"><span style="font-size: revert;">CUDA GraphsのStream Captureの機能を使ってグラフを構築</span> </h3>



<p>PyTorchではCUDA Graphsのグラフ構築の一つに<a href="https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#creating-a-graph-using-stream-capture" target="_blank" rel="noreferrer noopener" title="https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#creating-a-graph-using-stream-capture">Stream Capture</a>ベースの方法が提供されています。これは<code>torch.cuda.graph()</code> 以下の実行された関数を自動的にグラフに登録するというものです。<br>注意点としてはグラフ構築の前のwarmupでは別streamで実行したほうが良いらしいです。詳しくは参考資料の公式ドキュメントをご覧ください。</p>



<p>warmupも含めたグラフ構築は以下の通りです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>static_input = torch.empty((5,), device=&quot;cuda&quot;)
# Warmup before capture
s = torch.cuda.Stream()
s.wait_stream(torch.cuda.current_stream())
with torch.cuda.stream(s):
    for _ in range(3):
        static_output = static_input * 2
torch.cuda.current_stream().wait_stream(s)

# Captures the graph
g = torch.cuda.CUDAGraph()
with torch.cuda.graph(g):
    static_output = static_input * 2</code></pre></div>



<p>これで入力を<code>static_input</code>、出力を<code>static_output</code>とし、入力を2倍にする計算のグラフ<code>g</code>が準備できました。</p>



<h3 class="wp-block-heading">構築したグラフを実行 </h3>



<p>構築されたグラフ<code>g</code> を実行する際には入力データを<code>static_input</code> に上書きして、<code>replay()</code>を実行します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>static_input.copy_(torch.full((5,), 3, device=&quot;cuda&quot;))
print(&quot;input of cuda graph&quot;, static_input)
g.replay()
# static_output holds the results
print(&quot;output of cuda graph&quot;, static_output) </code></pre></div>



<p>出力は以下の通りです。</p>



<pre class="wp-block-code"><code>input of cuda graph tensor(&#091;3., 3., 3., 3., 3.], device='cuda:0')
output of cuda graph tensor(&#091;6., 6., 6., 6., 6.], device='cuda:0')</code></pre>



<h3 class="wp-block-heading">注意事項</h3>



<p>CUDA Graphsは簡単に使えそうですが、入力のtensorのshapeが変えられないなど制約がいくつかあります。詳しくはこちらをご覧ください。</p>



<p><a href="https://pytorch.org/docs/master/notes/cuda.html#constraints">https://pytorch.org/docs/master/notes/cuda.html#constraints</a></p>



<h2 class="wp-block-heading">パフォーマンスの評価</h2>



<p>使い方がわかったところで、どれくらい速くなるのか？ということが気になったので測定してみました。測定したときのnotebookは以下のところに置いておきます。</p>



<p><a href="https://github.com/shu65/blog-pytorch-notebooks/blob/main/pytorch_CUDA_Graphs.ipynb">https://github.com/shu65/blog-pytorch-notebooks/blob/main/pytorch_CUDA_Graphs.ipynb</a></p>



<p>今回は気になった2つのパターンで評価しました。</p>



<ul class="wp-block-list"><li>GELU </li><li>シンプルなLinearとDropoutのモデルの学習</li></ul>



<p>評価環境は以下の通り。</p>



<ul class="wp-block-list"><li>実行環境：Google Colab<ul><li>PyTorch: 1.10.0</li><li>CUDA: 11.1</li><li>GPU: K80 (たまたま取れた)</li></ul></li></ul>



<h3 class="wp-block-heading">GELU</h3>



<p>簡単な例として以下ようなGELUをCUDA Graphsで実行してみます。</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>また、この際、入力のtensorで小さい例と大きい例の2種類を使って測定してみます。</p>



<p>それぞれのtensorのshapeとしては以下の通りです。</p>



<ul class="wp-block-list"><li>小さいtensor: (1, 3, 224, 224)</li><li>大きいtensor: (32, 3, 224, 224)</li></ul>



<p>上記のサイズのtensorそれぞれを10000回実行して平均計算時間を測定しました。結果は以下の通りです。</p>



<figure class="wp-block-table"><table><tbody><tr><td></td><td>平均計算時間 (sec.)</td><td>defaultを1とした時の速度向上率</td></tr><tr><td>default</td><td>7.09e-05</td><td>1.00</td></tr><tr><td>CUDA Graphs</td><td>6.49e-05</td><td>1.09</td></tr></tbody></table><figcaption>GELUの小さいtensorの評価結果</figcaption></figure>



<figure class="wp-block-table"><table><tbody><tr><td></td><td>平均計算時間 (sec.)</td><td>defaultを1とした時の速度向上率</td></tr><tr><td>default</td><td>1.32e-03</td><td>1.00</td></tr><tr><td>CUDA Graphs</td><td>1.34e-03</td><td>0.99</td></tr></tbody></table><figcaption>GELUの大きいtensorの評価結果</figcaption></figure>



<p>評価結果としては個人的には思った通りの結果という印象で、CUDA Kernelのオーバーヘッドの割合が大きい、小さいtensorの時は効果がある程度出ているが、大きいtensorの時はオーバーヘッドの割合が小さいため、ほぼ変わらないという結果になりました。</p>



<h3 class="wp-block-heading"><span style="font-size: revert;">シンプルなLinearとDropoutのモデルの学習</span> </h3>



<p>PyTorchでCUDA Graphsの真価を発揮するのは学習のタイミングかと思いますので、公式ドキュメントにあった例の評価をしてみます。CUDA Graphsに登録する関数<code>train_step()</code>とモデル、各種入力は以下の通りです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-python" data-lang="Python"><code>def training_step(model, loss_fn, optimizer, data, target):
    y_pred = model(data)
    loss = loss_fn(y_pred, target)
    loss.backward()
    optimizer.step()

N, D_in, H, D_out = 32, 128, 256, 16
model = torch.nn.Sequential(
    torch.nn.Linear(D_in, H),
    torch.nn.Dropout(p=0.2),
    torch.nn.Linear(H, D_out),
    torch.nn.Dropout(p=0.1)
).cuda()
loss_fn = torch.nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.1)

# Placeholders used for capture
static_input = torch.randn(N, D_in, device=&#39;cuda&#39;)
static_target = torch.randn(N, D_out, device=&#39;cuda&#39;)</code></pre></div>



<p>ちなみにGoogle Colabで実行しようとしたとき、公式ドキュメントの入力サイズそのままだとcuBLASの内部でエラーが発生して実行できなかったため、サイズを小さくしてあります。</p>



<p>これらを10イテレーション分実行したときの評価結果は以下の通りです。</p>



<figure class="wp-block-table"><table><tbody><tr><td></td><td> 1イテレーションあたりの平均計算時間 (sec.) </td><td> defaultを1とした時の速度向上率 </td></tr><tr><td>default</td><td>1.11e-03</td><td>1.00</td></tr><tr><td>CUDA Graphs</td><td>4.71e-04</td><td>2.36</td></tr></tbody></table><figcaption>シンプルなLinearとDropoutのモデルの学習の評価結果</figcaption></figure>



<p>こちらは思ったよりも速度に差がでました。CUDA Graphsを利用できる場合は使うと効果的かもしれません。</p>



<h2 class="wp-block-heading">おまけ</h2>



<p>CUDA Graphsの制限を見ていて思いましたが、これなら<code>torch.jit.trace</code>や<code>torch.jit.script</code>も併用できるのでは？と思ってやってみました。以前、以下の記事で行ったように <code>torch.jit.script</code> + GELUを使用して評価しました。</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="P4UheysnJ7"><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></blockquote><iframe loading="lazy" class="wp-embedded-content" sandbox="allow-scripts" security="restricted"  title="&#8220;PyTorchのPERFORMANCE TUNING GUIDEの効果を確認してみる その2 「Fuse pointwise operations」&#8221; &#8212; まったり勉強ノート" src="https://www.mattari-benkyo-note.com/2021/05/10/pytorch-performance-tuning-guide-2-fuse-pointwise-operations/embed/#?secret=P4UheysnJ7" data-secret="P4UheysnJ7" width="600" height="338" frameborder="0" marginwidth="0" marginheight="0" scrolling="no"></iframe>
</div></figure>



<p>評価結果は以下の通りです。</p>



<figure class="wp-block-table"><table><tbody><tr><td></td><td>平均計算時間 (sec.)</td><td>defaultを1とした時の速度向上率</td></tr><tr><td>default</td><td>7.09e-05</td><td>1.00</td></tr><tr><td>CUDA Graphs</td><td>6.49e-05</td><td>1.09</td></tr><tr><td>torch.jit.script</td><td>3.89e-05</td><td>1.82</td></tr><tr><td>torch.jit.script  +  CUDA Graphs </td><td>3.56e-05</td><td>1.99</td></tr></tbody></table><figcaption>GELUの小さいtensorの評価結果</figcaption></figure>



<figure class="wp-block-table"><table><tbody><tr><td></td><td>平均計算時間 (sec.)</td><td>defaultを1とした時の速度向上率</td></tr><tr><td>default</td><td>1.32e-03</td><td>1.00</td></tr><tr><td>CUDA Graphs</td><td>1.34e-03</td><td>0.99</td></tr><tr><td>torch.jit.script </td><td>4.25e-04</td><td>3.11</td></tr><tr><td>torch.jit.script  +  CUDA Graphs  </td><td>3.74e-04 </td><td>3.53</td></tr></tbody></table><figcaption>GELUの大きいtensorの評価結果</figcaption></figure>



<p><code>torch.jit.script</code> の効果が大きいですが、CUDA Graphsを使うことでさらに速くなることが確認できました。個人的には CUDA Graphs が使える状況なら <code>torch.jit.trace</code> や <code>torch.jit.script</code> も使えると思われるので併用してよいのではないかと思います。</p>



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



<p>楽しみにしていたCUDA GraphsがPyTorchで使えるようになったということで、評価してみました。一部思った以上の効果を発揮したところもあるので、仕事でも使ってみてノウハウを貯めていこうと思います。</p>



<h2 class="wp-block-heading">参考資料</h2>



<ul class="wp-block-list"><li>PyTorchの公式ドキュメントのCUDA Graphsの説明部分: <a href="https://pytorch.org/docs/master/notes/cuda.html#cuda-graphs">https://pytorch.org/docs/master/notes/cuda.html#cuda-graphs</a></li></ul><p>The post <a href="https://www.mattari-benkyo-note.com/2021/10/23/pytorch-cuda-graphs/">PyTorch 1.10の新機能「CUDA Graphs」のパフォーマンスを測定してみる</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/10/23/pytorch-cuda-graphs/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">143</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>
