世界を動かす技術を、日本語で。

CUDAカーネルを実行すると何が起こるか?

2026年6月29日原文(fergusfinn.com)

概要

  • CUDAプログラムが ベクトル加算 を実行する流れを解説
  • コードの コンパイル過程 から GPU実行 までを詳細に追跡
  • PTXSASS への変換、バイナリ構造の説明
  • ホストからGPUへの カーネル起動手順 を解説
  • ドライバとハードウェアの連携 の仕組みも紹介

CUDAベクトル加算プログラムの全体像

  • CUDAプログラム例: 2つのfloat配列の要素ごと加算
  • カーネル__global__ void vadd(...)内で スレッドごとに加算結果を格納
  • main()配列メモリ確保・初期化・デバイス転送・カーネル起動・結果取得 を実施
  • vadd<<<4096, 256>>>1,048,576スレッド を生成、各要素に1スレッド割当
  • RTX 4090 などのGPUで実行可能な構成

nvccによるコンパイルと生成物

  • nvcc は複数のコンパイラを連携させるドライバプログラム
  • --keepオプションで 中間生成物 をディスクに保存可能
    • vadd.ptx:PTX(仮想ISA、cicc生成)
    • vadd.sm_89.cubin:SASS(実機コード、ptxas生成)
    • vadd.fatbin:PTXとcubinを束ねたファットバイナリ
    • vadd.cudafe1.stub.c:ホスト側起動スタブ・カーネル登録
    • vadd.o:最終ホストオブジェクト(fatbin埋込済み)

PTX(仮想ISA)の特徴

  • 無限個の型付きレジスタ を仮想的に利用可能
  • アドレス計算は複数命令に分解される
  • CUDAポインタはデフォルトで「generic」型、cvta命令でグローバルメモリに変換
  • PTXは デバイス非依存 の記述

SASS(実機コード)の特徴

  • PTXから SASS(実デバイス命令列) へ変換
  • 実レジスタ数 に制約される
  • アドレス計算や特殊レジスタ(blockIdx.x, threadIdx.x)の扱いがハードウェアに最適化
  • カーネル引数や起動ジオメトリは constant bank 0 に格納
    • 例:a, b, c, nの各ポインタや値は固定オフセットに配置

fatbin・バイナリ構造

  • fatbin はcubin(SASS)とPTX(テキスト)を束ねて格納
    • PTXは将来的な互換性のために同梱
    • 未対応アーキテクチャではPTXからJITでSASS生成
  • fatbinは ELF形式 でホストバイナリに埋め込まれる
    • .nv_fatbinセクション等に配置

ホストからGPUへのカーネル起動の流れ

  • cudafe++ が隠れたコンストラクタを挿入し、fatbinをCUDAランタイムに登録

  • カーネル呼び出しvadd<<<4096, 256>>>自動生成されたスタブ関数 に置き換え

  • スタブ関数は 引数をバッファにパック し、__cudaLaunchを呼び出し

    • da, db, dc, nの各引数は16バイトアライメントで配置
    • この配置はSASSがconstant bank 0から読むオフセットと一致
  • __cudaLaunchホスト側vaddのアドレス をキーに、デバイス側カーネル名を対応付け

  • CUDAランタイムは libcuda.so.1 (ユーザーモードドライバ)を動的にオープン

    • 初回呼び出し時に GPUコンテキスト を作成
    • コンテキストにはデバイス通信チャネル等の情報が含まれる

GPUへのカーネル展開と実行

  • CUDA 12.2以降、 モジュールロードは遅延(LAZY) がデフォルト
    • 初回カーネル起動時にのみSASSをGPUメモリへコピー
  • libcudaの下層には カーネルモードドライバ(nvidia.ko)
    • ホストとGPU間は ioctl とデバイスファイルで通信
  • cuLaunchKernelは コマンドストリーム をPCIe経由でGPUに送信
    • GPUは関数呼び出しやスタックではなく、 命令ストリームを逐次実行

この流れにより、 シンプルなCUDAプログラム でも、コンパイル・バイナリ構成・カーネル起動・デバイス実行まで、多層的な仕組みが連携し、最終的に GPU上で高速な並列演算 が実現される。

Hackerたちの意見

ハードウェアにはオープンなドキュメントがいくつかあるよ。メソッドのドキュメントやqmdフォーマットを見つけるのに、カーネルソースを読む必要はないんだ。詳細はここを見てね: https://github.com/NVIDIA/open-gpu-doc/blob/master/classes/c...

最初に、すごく詳しく書かれていて良い記事だね。ただ、CUDAの「ランタイムAPI」を使わないと、ユーザースペースの「魔法」はかなり消えちゃうよ。ドライバーAPIを使う場合は、カーネルソースを文字列として取り込んで、NVIDIAのランタイムコンパイラでコンパイルすると、何が起こっているかがもっと見えるようになるよ(全部じゃないけど)。この「生」のバージョンについては、こちらを見てね: https://github.com/NVIDIA/cuda-samples/tree/master/cpp/0_Int... でも、もっと読みやすくて、完全に透明なモダンC++ APIのバージョンはこれを試してみて: https://github.com/eyalroz/cuda-api-wrappers/blob/master/exa... これは、私のCUDA APIラッパー(ヘッダーオンリー)ライブラリのサンプルプログラムだよ。

ドライバーAPIが好きなのは、Cudaカーネルをホットリロード可能なシェーダーのように扱えるからだね。ランタイムでコードを変更できるのが楽しいんだ。

現在、カーネルを最適化して速く動かすことが仕事の会社があるよね。そういう会社が、オープンソースのライブラリに取って代わられるのかな(Nvidiaがいつでもリリースできると思うけど)。それとも、彼らが繁栄して大手プロバイダーに買収されて、インファレンスを早めるための「防壁」になるのかな。

短期的な買収は確かにありそうだと思う。でも、kernelbench [1] のような関連ベンチマークでのモデルの進展を考えると、よりコモディティ化されたソリューションのセットも避けられないと思う。ただし、各世代のハードウェアは、新しい制約や機能を伴うことが多いから、特定の世代のモデルが見たことのないものになることもあるよ(例えば、blackwellのtcgen05は一時的にOODだった)。モデルがより一般化していくと、これが致命的な問題にはならないかもしれないけど、少なくとも今は問題だね。[1] https://kernelbench.com/

スケールでCUDAを実行すると、NVIDIAのドライバーやライブラリのバグに対処するのに、エンジニアの時間のかなりの割合を取られちゃう。もっとNVIDIAのライブラリに頼ることを楽しみにしている人はあまりいないと思うよ。

たぶん違うと思う。ワークロードの具体的な内容、つまり正確なパラメータやメモリ内のデータの表現、値の範囲とかが、かなり異なる最適化戦略に導くから。

面白い記事だったね。デフォルトストリームのセマフォについての話も楽しめたよ。CUDAがユーザーのためにコマンドの同期を自動的に処理して、並列コマンドをオプションにしているのは素晴らしいね。Vulkanとは違って、最初からユーザーに全ての同期の複雑さを押し付けないのがいい。

HPCの修士課程を終えたばかりで、CUDA、MPI+CUDA、OpenCLの授業を受けたんだ。授業の前にこの記事を読んでいたら、すごく役立っただろうな!特に「ワープが適格であるとはどういうことか?」の前後の部分が。

すごく役立ったよ。特にドアベルとQMDの部分が一番役に立った。CUDAの起動構文が実際にGPUに送信されるものとつながるからね。ほとんどの説明はカーネルやブロック、ワープのところで止まっちゃうけど、これのおかげでCPUからドライバー、GPUへの流れがずっとわかりやすくなった。

制御コードは、投稿で説明されているよりちょっと複雑だよ。実際には制御ワードのビットだけじゃなくて、テーブルルックアップなんだ。

バーラメタルで?