実行モデル
GPUは数千のスレッドを歩調をそろえたグループ(ワープ)で実行し、それらをブロックにまとめてstreaming multiprocessorへ割り当てます。ワープ内のスレッドは1本の命令列を共有するため、分岐が分かれると直列化してレーンを無駄にします。SMあたり十分な数のワープを常駐させること(occupancy)が、メモリレイテンシを隠す手段です。
サービング基盤のさらに下の層です。モデルのあらゆる演算は最終的にGPUカーネルとして実行され、カーネルの質が、支払ったハードウェアのうち実際に使われる割合を決めます。自分でカーネルを書く機会はまれですが、この層を読むと、その上のシステムがなぜそう振る舞うのかが分かります。
GPUは数千のスレッドを歩調をそろえたグループ(ワープ)で実行し、それらをブロックにまとめてstreaming multiprocessorへ割り当てます。ワープ内のスレッドは1本の命令列を共有するため、分岐が分かれると直列化してレーンを無駄にします。SMあたり十分な数のワープを常駐させること(occupancy)が、メモリレイテンシを隠す手段です。
レジスタ、次にオンチップの小さく高速な共有メモリとキャッシュ、そしてオフチップの大きなHBMという順です。オンチップとオフチップの帯域差は桁違いなので、カーネル性能はほぼ、データがその境界をどれだけ渡らずに済むか、そしてアクセスがどれだけ整列するかで決まります。
行列の積和演算に特化した専用ユニットが、現代のGPUのFLOPSの大半を担います。対応するのは低めの精度(FP16/BF16、FP8、INT8)です。Tensor Coreを使い切れないカーネルや、ハードウェアが持たない精度を使うカーネルは、チップの大部分を遊ばせます。
どのカーネルも、演算スループットかメモリ帯域のうち先に尽きたほうで頭打ちになります。LLMのprefillは計算が重く、decodeは帯域が重い処理です。ある演算が境界のどちら側にあるかを知れば、FLOPSを増やすべきかデータ移動を減らすべきかが分かります。
カーネルの作業はシステムレベルの取り組みを補完します。continuous batchingやprefix cachingは「どの処理を行うか」を変え、カーネルは「各処理をどれだけ速く実行するか」を変えます。前者についてはサービング最適化をご覧ください。
標準的なattentionはN x Nのスコア行列をHBM上に実体化するため、メモリトラフィックが系列長の二乗で増え、演算は帯域律速になります。FlashAttentionは計算を組み替えます。query、key、valueの各行列をタイル単位でオンチップメモリに流し、タイルが届くたびにsoftmaxの正規化を逐次的に更新し、完全なスコア行列は一度も書き出しません。数学的な結果は同じで、メモリトラフィックはごく一部で済みます。これが長いコンテキストのサービングを実用にしました。
後継バージョンはハードウェア世代ごとに再調整されてきました(作業分割の改善、Tensor Coreの非同期パイプライン、低精度の経路など)。この技術はいまやPyTorchと主要なサービングエンジンすべてに入っています。ここから得られる一般的な教訓は、カーネルの最大の改善は命令の微調整ではなく、メモリ階層を軸に計算を組み替えることから生まれるということです。
制御の細かさとコストが上がる順に並べています。上から始め、プロファイリングによって上の段では足りないと分かったときだけ下りてください。
判断材料は、自分の演算子がどれだけ標準的か、最大性能にどれだけの価値があるか、どのハードウェアに対応する必要があるか、そしてチームの強みがコンパイラかカーネルか、です。まずプロファイルを取ってください。ボトルネックは直感が指す場所にないことがよくあります。