GPU カーネルを手書きするには、忍耐と努力、そして正気も必要です。私は最新の LLM(Claude Opus 5、GPT 5.6 Sol)を使って、文字通りカーネルを出力してもらう実験をしてきました。そこで学んだことについてお話しします。
正直に言うと、GPU カーネルを書くというタスクは、恥ずかしいほど検証が簡単です。
まず、カーネルを書きたい操作を決めます。次に、最初のバージョンを書き、明らかなエラーなしにコンパイルできることを確認します。そして、遅いリファレンス実装と照合して正しさをチェックします。一致しない場合は、正しさの問題を修正します。それが完了したら、カーネルの実行時間をベンチマークします。以降のバージョンはこれを土台に、ローフライン指標に到達するか、満足するまで最適化を続けます。

検証可能なループ:GPU カーネル開発
上の図は、これを検証可能なループとして示しています:
- コンパイルチェックは、正しさを考える前の、タイトなローカルループです(B <-> C)。
- 正しさのチェック(D <-> E <-> F)は、検証報酬ループの核です。ここが、カーネル作成を自動検証に適した問題にしている部分であり、照合すべき正解リファレンスが存在します。
- 最適化(G -> H -> D に戻る)は、新しいバージョンごとに同じ正しさのループを再利用します。速くても間違ったカーネルには価値がないため、各バージョンで正しさの検証が必要です。
- ローフライン/満足度チェック(I)は、最適化を続けるか止めるかを決める外側のループです。
最初のバージョン
上の図はまだ高レベルの視点であり、悪魔は細部に宿ります。LLM エージェントが良い最初のバージョンを書き始めるには、必要なコンテキストをすべて持っていることを確認する必要があります。
ここで CUDA DSL が登場します。Triton、CuTeDSL、Tilelang は、Python で始めやすい DSL です。CUDA C++ に比べると学習曲線は緩やかですが、それらの DSL の抽象化によってエージェントがさらに混乱する可能性があります。それらの抽象化のコンテキストをエージェントに渡す方法が必要です。
最新の LLM はすでに「良い」Triton の書き方を知っています。コンテキストがなくても Triton の抽象化をうまく扱えます。しかし、Triton よりもはるかに制御性の高い CuTeDSL のような他の DSL では、エージェントが DSL の抽象化を理解するために参照できるコンテキストディレクトリを用意することが非常に役立つと感じています。
例えば、
NVIDIA cutlass
リポジトリをコンテキストディレクトリにクローンしておくことは、CuTeDSL でカーネルを書く際に
レイアウト代数、Copy/GEMM アトム、メモリ階層、カーネル例
などに関連する抽象化をエージェントに探させる良い方法です。
私の経験では、良い最初のバージョンは、明らかなエラーなくコンパイルでき、後述する正しさのテストを通過します。
テスト、ベンチマーク、プロファイリング
エージェントに十分なコンテキストを与えれば、実際のボトルネックは検証に移ります。リファレンス実装そのものと、それに対する検証がますます重要になります。私はこのフェーズを正しさのテスト、または単にテストと呼んでいます。リファレンス実装の速度よりも、その意図が重要です。測定・検証しようとすることが、エージェントが最適化する対象になります。
通常、計算を低精度で行わない場合は、最大絶対/相対誤差(MAE)、平均二乗誤差(MSE/RMSE)、PSNR(ピーク信号対雑音比)を測定します。低精度が関わる場合は、PSNR とコサイン類似度(cossim)を測定する傾向があります。
カーネルのバージョンを実際に GPU で実行する方法は、GPU がローカルにあるかクラウド経由かによって異なります。いずれにしても、エージェントは何らかの方法でその出力にアクセスできる必要があります。
以下のラング(rung)手法は、N 個のテスト関数を用意する良い方法だと感じています:
1def rung(name):2 def deco(fn):3 try:4 out = fn()5 results[name] = {"ok": True, **(out or {})}6 print(f"[{name}] ok " + " ".join(f"{k}={v}" for k, v in (out or {}).items()))7 except Exception as e:8 results[name] = {"ok": False, "err": f"{type(e).__name__}: {e}"}9 print(f"[{name}] FAILED {type(e).__name__}: {e}")10 traceback.print_exc()11 return fn12 return deco
これは次のように呼び出せます:
1out = {}23@rung("pre-checks")4def _():5 run_pure_checks()6 run_dsl_checks()78@rung("run")9def _():10 out["o"] = custom_kernel(*inputs)11 torch.cuda.synchronize()12 return {"shape": tuple(out["o"].shape),13 "finite": bool(torch.isfinite(out["o"]).all())}
ベンチマークのラングでは、複数のことができます:
- 全体的な所要時間に対するエンドツーエンドのカーネル実行時間のベンチマーク
- カーネル内トレーシングを使用して、カーネル内のセクションをベンチマークし、出力にダンプする(カスタムトレーサーまたは CUPTI を使用)
- 生成された IR、PTX、SASS、CUBIN を dumps ディレクトリにダンプし、エージェントに読ませる
最後のポイントはもう少し詳しく説明できます。DSL が最適とは言えない PTX(最終的には SASS)を生成するように低レベル化され、代わりに使用できるより良い命令やシェイプが見つかることがあります。エージェントは PTX/SASS テキストファイルを読み、DSL に最適でない部分を処理させる代わりに、低レベルのコードをインライン化できます。ここでも、「検索可能な」PTX ドキュメントをコンテキストとして渡すことが非常に役立ちます。
すべてをまとめる最後の要素はプロファイリングです。エージェントが NCU(Nsight Compute Systems)CLI にアクセスできる場合は、上記の検証フィードバックループの一部として、カーネルのプロファイルとレポート生成を依頼できます。
まとめ
「では、GPU カーネル開発は終わったのか?」
「まあ、イエスでもありノーでもある」
イエスと言えるのは、レイアウト、インデックス、抽象化、全体的な構造といった難しい部分は、十分なコンテキストを持つエージェントがほぼ解決できるからです。2〜3 週間の作業を 1〜2 日に簡単に短縮できます。ノーと言えるのは、真のボトルネックがカーネルから検証に移ったからです。今はやり方は一つではありません。コンテキストとテストハーネスが優れているほど、プロセスは速くなります。専門的なケースではさらに効果が大きく、優れたテストハーネスがあれば十分です。
最後に、エージェントを自律的な存在として扱うのではなく、導くことができる非常に賢いアシスタントとして扱うことが依然として必要です。ここで、GPU とカーネルに関する基本的な理解が役立ちます。人間の部分(あなた)が今も必要です。
なんとも切ない気持ちですよね :)





