PaPoo
cover
technews
Author
technews
世界の技術ニュースをリアルタイムでキャッチし、日本語でわかりやすく発信。AI・半導体・スタートアップから規制動向まで、グローバルテックシーンの「今」をお届けします。

UnslothとNVIDIAでLLM学習を速くする方法を解説

この記事のキーポイント

まず何が起きたのか

Unslothのブログ「How to Make LLM Training Faster with Unsloth and NVIDIA」は、かなり実践的でおもしろい記事です。ざっくり言うと、​NVIDIAと一緒にLLM学習のボトルネックを3つ潰して、さらに速くしたという話です。

ここで重要なのは、ただ「GPUを速いものにした」わけではないことです。
やっているのはもっと地味だけど効く仕事、つまり:

image_0001.png

こういう改善です。個人的には、こういうアルゴリズムとシステムの両方をいじって速くする話がいちばん面白いです。派手さはないけど、効き方が本物だからです。

しかも今回の改善は、Unslothがもともと持っていた2〜5倍の高速化に“さらに上乗せ”される形です。ここ、かなり大きいです。


1. Packed sequence metadataをキャッシュして無駄を消す

image_0002.png

そもそも packed sequence って何?

LLMの学習では、長さの違う文章をまとめてGPUに流します。
そのとき、短い文章を無理やり同じ長さまで引き伸ばすと、​padding tokenという“空白”が増えて無駄になります。

そこで使うのが packed sequence です。
複数の短い例を連結して、1本の長い列として扱います。これでpaddingを減らせます。

でも、ここで新しい問題が出ます。
「元の文章がどこからどこまでだったか」を、モデルがちゃんと知っていないといけないからです。

そのために必要なのが、たとえば以下の情報です。

image_0003.png

何がムダだったのか

UnslothとNVIDIAが見つけたポイントは、​このメタデータは1回のforward passの中では全レイヤーで同じだということです。

つまり、Transformerが何層もあるなら、本来は

image_0004.png

わけです。

ところが、実際には層ごとに同じ情報を何度も組み立て直すことがありました。
これはかなりもったいない。しかも単なる計算コストだけでなく、​GPUとCPUの同期が入ってしまうことがあるのが痛いです。GPUが「ちょっと待って、CPUから情報が来るまで止まるね」となるわけで、これが積み重なると地味に効きます。

改善内容

そこでやったのが、

image_0005.png

など、再利用できる情報をキャッシュすることです。
要するに、同じ材料を毎回こね直さず、​一度作ったものを使い回すようにした、ということです。

こういう改善って、地味なんですが本当に効きます。
LLMの学習速度は「大きな演算」だけで決まるわけではなく、​細かい段取りの悪さでもかなり削られるからです。

ベンチマーク結果

Qwen3-14BのQLoRA SFTでは:

image_0006.png

特にforwardが大きく伸びています。
これは、メタデータやmaskの準備がforward側で何度も出てくるからです。

個人的には、この数字はかなり納得感があります。
「学習そのもの」より「準備作業」を削ると、forwardで効きやすいんですよね。


image_0008.png

2. Double-buffered checkpointingでコピー待ちを隠す

checkpointingって何?

activation checkpointing は、メモリを節約するための定番技術です。
普通はforward中の中間結果(activation)をたくさん保存しますが、それを全部持っておくとVRAMを食います。

そこでcheckpointingでは、必要なところだけ保存して、backwardのときに再計算します。
メモリは節約できるけど、そのぶん計算が少し増える、というトレードオフです。

これは大きいモデルではとても有効です。
ただし問題は、保存しなかったactivationをどうやってbackward時にGPUへ戻すかです。

image_0009.png

1本のバッファだと待ちが発生する

Unslothのsmart checkpointingでは、activationをpinned CPU memoryに置いておき、必要になったらGPUにコピーします。
ここでまずいのが、1つのバッファをコピーと計算で共用すると、次のような順番になりやすいことです。

  1. CPU→GPUコピー
  2. コピーが終わるのを待つ
  3. backward計算
  4. 次のコピー

つまり、​コピーと計算が交互に順番待ちになってしまうんです。
これではせっかくGPUが強くても、待ち時間が目立ってしまいます。

2本のバッファで重ねる

そこで使うのが double buffering です。
バッファAでbackwardをしている間に、バッファBへ次のactivationを先読みしておく。終わったら役割を入れ替える。これでコピーと計算を重ねることができます。

image_0010.png

もちろん、完全に重なるわけではありません。でも、待ち時間をかなり隠せます。

こういうのは、いかにもシステム最適化らしい改善です。
計算そのものを減らすのではなく、​**“待つ”という無駄を見えなくする**のがうまいです。

ベンチマーク結果

NVIDIA B200 Blackwell GPUでの大きめdense modelの結果はこうです。

image_0011.png

メモリ増加も比較的小さめです。

lossもほぼ変わっていないとのことなので、​速くしたのに学習の中身は変えていないのがポイントです。

image_0013.png

個人的には、この「追加のVRAMは少しだけ、でも効き目はちゃんとある」というバランスがかなり良いと思います。
実運用では、速さだけでなくメモリの余裕も大事なので。


3. MoE routingでもう少し賢くする

MoEって何?

MoE(Mixture of Experts)​ は、モデルの中に複数の“専門家”を持たせて、入力ごとに使う専門家を切り替える仕組みです。
全員を毎回フル稼働させるのではなく、必要な人だけ呼ぶイメージです。

image_0014.png

うまくハマると効率が良いのですが、ルーティング処理がややこしくなります。

何が遅かったのか

記事では、PyTorchベースのGPT-OSSのMoEパスで、各expertにどのtokenを送るかを調べる処理が重かったと説明しています。

素朴な実装だと、expertごとに

torch.where(router_indices == expert_idx)

image_0015.png

みたいなことを繰り返してしまうことがあります。
でもこれ、expertの数だけ動的な問い合わせが走るので、無駄が増えやすいです。しかもデータ依存なので、CPU-GPU同期っぽいコストが見えにくく出ることがあります。

改善内容

より良いやり方は、まとめて一気に処理することです。

image_0016.png

要するに、​1回でまとめて整列してから切り分けるやり方です。
こういう改善は、地味だけど「ちゃんとプログラムしてるなあ」と感じます。動くコードと速いコードは別物、というやつですね。

ベンチマーク結果

gpt-ossのtrainingでは、これで15%高速化したとのことです。

MoEは本質的に“分岐の多い仕組み”なので、こうしたルーティングのムダが効きやすいのだと思います。


image_0017.png

この記事を読んで感じること

正直、今回のブログはかなり好印象でした。
理由は、単に「速くなりました」と言うだけではなく、​なぜ速くなるのかをかなり丁寧に説明しているからです。

特に良いのは、各改善について

まで追っている点です。こういう記事は、読んでいて信頼しやすいです。

image_0018.png

また、今回の改善はすべて「魔法」ではありません。
どれも、

という、かなり王道の最適化です。
でも、王道だからこそ強い。LLMは巨大なので、こうした“細かい無駄”が積み重なると、最終的に大きな差になります。

個人的には、こういう改善は今後もかなり伸びしろがあると思います。
モデルが大きくなるほど、GPUの演算能力だけでなく、​周辺の段取りがボトルネックになりやすいからです。

image_0019.png


まとめ


参考: How to Make LLM Training Faster with Unsloth and NVIDIA

同じ著者の記事