정보
이 스킬은 타일 형태의 GPU 커널을 변환하여 소프트웨어 파이프라이닝을 구현하고, 글로벌 메모리 로드와 텐서 코어 연산을 중첩시켜 레이턴시를 숨깁니다. 컴퓨트/로드 비율을 기반으로 LDG-레지스터와 cp.async 방식을 선택하도록 도우며, 공유 메모리 사용량을 점유율 제한에 맞춰 검증합니다. 텐서 코어 워크로드에서 메모리 전송이 병목 현상을 일으킬 때 고급 CUDA 최적화에 활용하세요.
빠른 설치
Claude Code
추천npx skills add pjt222/agent-almanac -a claude-code/plugin add https://github.com/pjt222/agent-almanacgit clone https://github.com/pjt222/agent-almanac.git ~/.claude/skills/pipeline-gpu-kernelClaude Code에서 이 명령을 복사하여 붙여넣어 스킬을 설치하세요
문서
Pipeline GPU Kernel
タイル化された GPU カーネルにソフトウェアパイプライニング(double-buffering)を適用し、tile N+1 のグローバルメモリロードを tile N の Tensor Core 計算と重ねる。順次の load-sync-compute-sync K ループを prologue/loop/epilogue 構造に変換し、タイルあたりの compute/load 比に基づいて LDG-register と cp.async(LDGSTS)バリアントの間で選択し、共有メモリがアーキテクチャ占有率クリフ未満に留まるか検証し、最終 SASS でロード/計算の重ねを確認する。
使用タイミング
analyze-kernel-bottleneckがタイルあたり低 compute/load 比の memory-bound カーネルを特定したとき- warp インタリーブ単独では DRAM レイテンシ(GA104 で ~300 サイクル)を隠せないとき
- カーネルが再構築可能な順次 load-sync-compute-sync K ループを持つとき
- compute/load 比が高い(>20:1)で 8+ warp がアクティブなときは不要
入力
- 必須: 別個のロードと計算フェーズを含むタイル化された K ループを持つ CUDA カーネルソースファイル(
.cu) - 必須: ターゲット GPU アーキテクチャ(例: GA104 / sm_86 — smem クリフと占有率制限を決定)
- 必須: 現在のタイルサイズ(BM、BN、BK)とデータ型(FP16、FP32、INT8)
- 任意: タイルあたり compute/load 比(
analyze-kernel-bottleneckから; 提供されなければ推定される) - 任意: ベンチマークベースライン(ターゲット問題サイズでの非パイプライン性能)
手順
ステップ1: 前提条件を検証する
カーネルが __syncthreads() で分離された別個のロードと計算フェーズを持つタイル化された K ループを持つことを確認する。倍にされた共有メモリコストを計算しアーキテクチャ占有率クリフ未満に留まるか検証する。
- カーネル内の K ループを位置特定する。順次構造を持たねばならない: グローバルから共有メモリへ A と B タイルをロード、
__syncthreads()、共有メモリタイル上で計算(HMMA/IMMA/FFMA)、__syncthreads()。 - 単一バッファ共有メモリサイズを記録:
smem_a_size = BM * BK * sizeof(T)とsmem_b_size = BK * BN * sizeof(T)。 - 倍バッファコストを計算:
smem_doubled = smem_a_size * 2 + smem_b_size * 2。 - アーキテクチャクリフと比較。GA104 (sm_86): 100 KB max smem/SM、クリフは 50 KB/block(50 KB 超 = 1 block/SM = 4 warp、2x 占有率崩壊)。
Single buffer: smem_a[BM*BK] + smem_b[BK*BN] = 2 KB + 2 KB = 4 KB
Double buffer: smem_a[2][BM*BK] + smem_b[2][BK*BN] = 4 KB + 4 KB = 8 KB
8 KB << 50 KB cliff -> 2 blocks/SM -> 8 warps
- ループ反復カウントを検証:
num_tiles = K / BK。パイプライニングはnum_tiles >= 2(少なくとも一つの prologue + 一つのメインループ反復)を要求する。
期待結果: 単一バッファと倍バッファコストを示す共有メモリ予算表、倍にされた割り当てがアーキテクチャクリフ未満に少なくとも 2 blocks/SM 占有率で留まることを確認。
失敗時: 倍バッファがクリフを超えるなら、smem_doubled <= 50 KB(GA104)になるまでタイルサイズを減らす(BK または BM を半分に)。代替として、共有メモリを倍にせずレジスタのみのプリフェッチ(LDG バリアント)を使う — プリフェッチデータをレジスタに保存し、__syncthreads() 後に同じ単一バッファに書く。
ステップ2: バリアントを選ぶ
タイルあたりの compute/load 比に基づいて LDG-register と cp.async(LDGSTS)の間で選択する。
- compute/load 比を計算: GEMM ライクカーネルには
ratio = (2 * BM * BN * BK) / ((BM * BK + BK * BN) * sizeof(T))(multiply-add ごとに 2 FLOPs、タイルあたりロードバイト)。 - 決定ルールを適用:
LDG-register バリアント(ratio >= 5 または CUDA < 11.0):
- LDG tile N+1 をレジスタへ(非ブロッキンググローバルロード)。
buf[N % 2]上で計算(未処理 LDG と重なる)。__syncthreads()、それから STS レジスタをbuf[(N+1) % 2]へ、__syncthreads()。- より単純な実装、パイプライン API 依存なし。
- レジスタ圧を加える: ステージング用にスレッドごと ~
(BM * BK + BK * BN) / BLOCK_SIZEレジスタ。
cp.async(LDGSTS)バリアント(ratio < 5、CUDA >= 11.0):
__pipeline_memcpy_asynctile N+1 をbuf[(N+1) % 2]に直接(async、レジスタファイルバイパス)。- 計算前に
__pipeline_commit()。 buf[N % 2]上で計算。- 計算後に
__pipeline_wait_prior(0)+__syncthreads()。 - より良い重ね、プリフェッチ用ゼロレジスタ圧。
#include <cuda_pipeline.h>を要求。
- 決定閾値(4096x4096x4096 で IGEMM を使い GA104 で計測):
- Ratio < 5:1 — cp.async を選好(IGEMM で +35% 計測)。
- Ratio 5-20:1 — 両方を実装してベンチマークで決定。
- Ratio > 20:1 — パイプライニングはおそらく有益でない(warp インタリーブで十分)。
期待結果: compute/load 比とターゲットアーキテクチャに基づく根拠付きで選ばれたバリアント。
失敗時: 比が曖昧(5-20:1 範囲)なら、両バリアントを実装してベンチマークする。CUDA バージョンがサポートするとき cp.async バリアントが安全な既定。
ステップ3: K ループを再構築する
順次 load-sync-compute-sync ループをパイプライン prologue/loop/epilogue 構造に変換する。
-
3 つのセクションを特定: 元のループ本体は 3 つの部分になる:
- Prologue: tile 0 を
buf[0]にロード、同期、それからメインループに入る。 - Main loop: tile 1 から
num_tiles - 1まで、tile N+1 のロードを tile N の計算と重ねる。 - Epilogue: 最後のタイルを計算(最後のメインループ反復で既にロード済み)。
- Prologue: tile 0 を
-
LDG-register バリアント構造:
// === LDG-register variant ===
// Prologue: load tile 0 into buf[0]
cooperative_load_tile(smem_a[0], smem_b[0], global_a, global_b, /*k_offset=*/0);
__syncthreads();
for (int tile = 0; tile < num_tiles - 1; tile++) {
int cur_buf = tile & 1;
int next_buf = 1 - cur_buf;
// Phase 1: LDG next tile into registers (non-blocking)
float reg_a[ELEMS_PER_THREAD_A], reg_b[ELEMS_PER_THREAD_B];
prefetch_tile_to_registers(reg_a, reg_b, global_a, global_b,
(tile + 1) * BK);
// Phase 2: Compute on current buffer (overlaps with LDG flight)
tensor_core_mma(smem_a[cur_buf], smem_b[cur_buf], acc);
// Phase 3: Drain registers into next buffer
__syncthreads();
store_registers_to_smem(smem_a[next_buf], smem_b[next_buf],
reg_a, reg_b);
__syncthreads();
}
// Epilogue: compute last tile
tensor_core_mma(smem_a[(num_tiles - 1) & 1], smem_b[(num_tiles - 1) & 1], acc);
- cp.async バリアント構造:
// === cp.async variant ===
#include <cuda_pipeline.h>
// Prologue: async load tile 0 into buf[0]
cpasync_load_tile(smem_a[0], smem_b[0], global_a, global_b, /*k_offset=*/0);
__pipeline_commit();
__pipeline_wait_prior(0);
__syncthreads();
for (int tile = 0; tile < num_tiles - 1; tile++) {
int cur_buf = tile & 1;
int next_buf = 1 - cur_buf;
// Phase 1: cp.async next tile into next buffer (async, direct to smem)
cpasync_load_tile(smem_a[next_buf], smem_b[next_buf],
global_a, global_b, (tile + 1) * BK);
__pipeline_commit();
// Phase 2: Compute on current buffer (overlaps with LDGSTS in flight)
tensor_core_mma(smem_a[cur_buf], smem_b[cur_buf], acc);
// Phase 3: Wait for async copies to complete
__pipeline_wait_prior(0);
__syncthreads();
}
// Epilogue: compute last tile
tensor_core_mma(smem_a[(num_tiles - 1) & 1], smem_b[(num_tiles - 1) & 1], acc);
- ループカウントを検証: メインループは
num_tiles - 1反復走る(tile 0 からnum_tiles - 2がどのタイルを計算するかをインデックス、tile 1 からnum_tiles - 1をロード)。Epilogue が最後の反復でロードされたタイルを計算する。
期待結果: 選ばれたバリアント用に明確な prologue、メインループ、epilogue セクションを持つ再構築された K ループソースコード。
失敗時: 最も一般的なバグはバッファインデックスでの off-by-one または epilogue 計算パスを忘れること。検証: prologue は buf[0] にロード、最初のメインループ反復は buf[0] で計算し buf[1] にロード、二番目反復は buf[1] で計算し buf[0] にロード、等。Epilogue は buf[(num_tiles - 1) & 1] を計算する。
ステップ4: 倍バッファを実装する
倍バッファ共有メモリを宣言しロード関数を実装する。
- 単一バッファ共有メモリ宣言を倍バッファ配列で置換:
// Before (single buffer)
__shared__ half smem_a[BM * BK];
__shared__ half smem_b[BK * BN];
// After (double buffer)
__shared__ half smem_a[2][BM * BK];
__shared__ half smem_b[2][BK * BN];
- cp.async バリアントには、パイプライン API を使って async ロード関数を実装:
__device__ void cpasync_load_tile(half* dst_a, half* dst_b,
const half* src_a, const half* src_b,
int k_offset) {
// Each thread copies its portion (16 bytes = 8 half values per cp.async)
int tid = threadIdx.x;
int bytes_per_thread = 16; // cp.async.cg supports 4, 8, or 16 bytes
// A tile: BM * BK elements, distributed across BLOCK_SIZE threads
int elems_a = BM * BK / BLOCK_SIZE;
for (int i = 0; i < elems_a; i += 8) {
int idx = tid * elems_a + i;
__pipeline_memcpy_async(dst_a + idx,
src_a + k_offset * BM + idx,
bytes_per_thread);
}
// B tile: BK * BN elements, distributed similarly
int elems_b = BK * BN / BLOCK_SIZE;
for (int i = 0; i < elems_b; i += 8) {
int idx = tid * elems_b + i;
__pipeline_memcpy_async(dst_b + idx,
src_b + k_offset * BN + idx,
bytes_per_thread);
}
}
- LDG バリアントには、レジスタステージング配列とストア関数を実装:
// Declare register staging (size = elements per thread)
half reg_a[BM * BK / BLOCK_SIZE];
half reg_b[BK * BN / BLOCK_SIZE];
// Prefetch: LDG from global to registers (non-blocking, issued early)
for (int i = 0; i < BM * BK / BLOCK_SIZE; i++) {
int idx = threadIdx.x * (BM * BK / BLOCK_SIZE) + i;
reg_a[i] = global_a[k_offset * BM + idx];
}
// ... similarly for reg_b
// Store: STS from registers to shared memory (after __syncthreads)
for (int i = 0; i < BM * BK / BLOCK_SIZE; i++) {
int idx = threadIdx.x * (BM * BK / BLOCK_SIZE) + i;
smem_a[next_buf][idx] = reg_a[i];
}
- コンパイラに正確な占有率情報を与えるためカーネルに
__launch_bounds__(BLOCK_SIZE)を保つ。 - コンパイル:
nvcc --cubin -arch=sm_86 -O2 -o kernel.sm_86.cubin kernel.cu。
期待結果: 倍バッファ共有メモリと選ばれたロード機構を持つコンパイル可能なカーネル。エラーなしの成功 cubin 生成。
失敗時: パイプライン API 呼び出しでコンパイルが失敗したら、#include <cuda_pipeline.h> が存在し CUDA toolkit が >= 11.0 か保証する。レジスタスピルが起こる(nvcc --resource-usage を確認)なら、BLOCK_SIZE を増やすか BK を減らしてレジスタステージング配列サイズを減らす。
ステップ5: 正確性を検証する
CPU 参照に対してパイプライン化されたカーネルを動かして同一の数値出力を確認する。
- ベンチマークをコンパイル:
nvcc -arch=sm_86 -O2 -o bench bench.cu -lcuda -I../../phase2/common。 - スケールアップ前にインデックスバグを捕まえるため、まず小問題サイズ(512x512x512)で実行する。
- データ型に正しい公差を適用:
- INT8 Tensor Core (IMMA):
abs=0.5, rel=0.1 - FP16 Tensor Core (HMMA):
abs=1e-2, rel=1e-2 - FP32 scalar (FFMA):
abs=1e-3, rel=1e-3
- INT8 Tensor Core (IMMA):
- パイプライニングは算術を変えない — ロードを並べ替えるだけ。正確性が失敗したら、バグはバッファインデックスにあり計算ロジックではない。
- ターゲット問題サイズ(例: 4096x4096x4096)でテストして境界処理を検証する。
期待結果: 非パイプラインベースラインと同一のエラー境界で小・ターゲット問題サイズの両方で PASS。
失敗時: バッファインデックスバグが最も可能性の高い原因。検証: 計算は buf[tile & 1] から読み、ロードは buf[1 - (tile & 1)] に書く。Epilogue がバッファインデックス (num_tiles - 1) & 1、num_tiles & 1 ではなく、を処理するか確認する。cp.async については、__pipeline_wait_prior(0) が __syncthreads() 前に完了することを検証する — そうでないと計算が部分書き込みデータを読みうる。
ステップ6: ベンチマークと比較
ターゲット問題サイズで非パイプラインベースラインに対してパイプラインカーネルを計測する。
- 非パイプラインベースラインを実行し GFLOPS または帯域幅(カーネルタイプによる)を記録。
- 各パイプラインバリアントを実行し同じ指標を記録。
- スピードアップを計算:
speedup = pipelined_metric / baseline_metric。 - compute/load 比による期待利得(GA104 で計測):
- 低比(<5:1): cp.async から +15-35%(IGEMM で計測: LDG +18%、cp.async +35% で 4096x4096x4096)。
- 中比(5-20:1): +5-15%。
- 高比(>20:1): 0-5% またはリグレッション。
- 両バリアントが実装されていれば、本番使用には速い方を選ぶ。
| Variant | GFLOPS | Speedup vs Baseline |
|------------------|--------|---------------------|
| Baseline | XXX | 1.00x |
| LDG-register | XXX | X.XXx |
| cp.async (LDGSTS)| XXX | X.XXx |
期待結果: 改善を示す性能比較表。選ばれたバリアントは compute/load 比予測と一致する計測可能なスピードアップを示すべき。
失敗時: 性能がリグレッションするなら、3 つを確認: (1) 予期しない命令オーバーヘッドの SASS(余分な BAR.SYNC、レジスタスピル)。(2) 共有メモリが占有率クリフを越えなかった — nvcc --resource-usage または cuobjdump -res-usage で検証。(3) 問題サイズが prologue/epilogue オーバーヘッドを償却するに十分なタイル(K / BK >= 4)を生む。
ステップ7: SASS 重ねを検証する
コンパイルされた SASS を検査してメインループ本体内でグローバルロードと Tensor Core 命令が重なるか確認する。
- 逆アセンブル:
cuobjdump -sass kernel.sm_86.cubin | grep -E 'IMMA|HMMA|LDGSTS|LDG|BAR'。 - メインループ本体で、この順序パターンを検証:
LDGSTSまたはLDG命令がHMMAまたはIMMA命令の 前 に現れる。- ロード命令と計算命令の間に
BAR.SYNCがない(warp スケジューラで重ねるため自由でなければならない)。 BAR.SYNCが計算ブロックの 後 に現れ、ロードされたデータの次の反復での使用をゲートする。
- HMMA/IMMA 命令のストールコードを確認 — HMMA パイプライン遅延の S08 が予想されかつ避けられない。IMMA の S01-S04 は普通。LDG/LDGSTS のストールは低い(S01)はず、warp スケジューラがロードが飛行中に計算に切り替えられるから。
- ループ反復あたりの総 HMMA/IMMA 命令を数える — これは非パイプラインバージョンと一致するべき(パイプライニングは計算量を変えるべきでない)。
# Full SASS pipeline verification
cuobjdump -sass kernel.sm_86.cubin | grep -E 'IMMA|HMMA|LDGSTS|LDG|BAR'
# Count compute instructions per loop
cuobjdump -sass kernel.sm_86.cubin | grep -c 'HMMA\|IMMA'
# Check for register spills
nvcc --resource-usage --cubin -arch=sm_86 -O2 kernel.cu 2>&1 | grep -i spill
期待結果: 介在バリアなしで load-before-compute パターンを示す注釈付き SASS 抜粋。レジスタスピルゼロ。
失敗時: コンパイラが計算後にロードを並べ替えた(重ねを破る)なら試す: (1) 過攻撃的な展開を防ぐためメインループに #pragma unroll 1。(2) シーケンシングヒントを作るためロードと計算を別個のインライン関数に分ける。(3) ロードと計算ブロック間のコンパイラフェンスとして asm volatile("" ::: "memory") を使う(最後の手段 — 他の最適化を阻害するかも)。
バリデーション
- 倍バッファ smem がアーキテクチャクリフ未満に留まる(GA104: 50 KB/block)
- 両バッファが交互に使われる(
buf[tile & 1]パターン) - Prologue が tile 0 を
buf[0]にロード - Epilogue が
buf[(num_tiles - 1) & 1]から最後のタイルを計算 - CPU 参照に対し小・ターゲットサイズの両方で正確性 PASS
- SASS がロード/計算重ねを確認(LDGSTS/LDG と IMMA/HMMA 間に
BAR.SYNCなし) - 性能が非パイプラインベースラインを超えて改善
- LDG バリアントからレジスタスピルなし(
nvcc --resource-usageを確認)
よくある落とし穴
- バッファ倍化で smem クリフを越える — GA104 クリフは 50 KB/block、64 KB ではない。実装前に常に
smem_doubledを計算する。28 KB 単一バッファのカーネルが倍で 56 KB に跳ね、クリフを越え占有率を半減する。これがパイプライニング +20% 利得を -50% 占有率リグレッションに変えうる。 - Epilogue 計算パスを忘れる — 最後のメインループ反復でロードされた最後のタイルはループ外で自身の計算フェーズを必要とする。それなしでは、K 次元の最後の BK 列が静かに落とされ、明らかな失敗ではなく小さな数値エラーとして現れうる誤った結果を生む。
- バッファインデックス off-by-one — 現在の計算バッファに
buf[tile & 1]を、次のロードバッファにbuf[1 - (tile & 1)]を使う。一般的な誤りは次のバッファにbuf[(tile + 1) & 1]を使うこと。これはバッファ数が 2 のときのみbuf[1 - (tile & 1)]と等価 — しかし計算インデックスに誤って適用されると間違って読む。 - cp.async commit/wait 順序 —
__pipeline_commit()は計算フェーズの 前 に呼ばねばならない(async コピーのバッチを封印する)。__pipeline_wait_prior(0)は計算フェーズの 後 に呼ばねばならない(コミットされたコピーすべてが完了するまでブロックする)。これらを入れ替えると async コピーが同期的になり、すべての重ね利益を排除する。 - __syncthreads が欠落 — LDG バリアントでは、計算と STS ドレインの間に
__syncthreads()が必要(計算が現バッファの読み取りを終えてから上書きされる)。STS ドレイン後にもう一つ__syncthreads()が必要(次の反復が読む前にすべてのスレッドが書き終える)。cp.async バリアントでは、__pipeline_wait_prior(0)の後の__syncthreads()がすべてのスレッドが完了した async コピーを見ることを保証する。 - cp.async での境界処理 —
__pipeline_memcpy_asyncはソースアドレスが有効でアラインメントされていることを要求する。KがBKの倍数でない行列エッジでは、最後のタイルが境界外を読みうる。最終タイルには境界チェック付きスカラーロードにフォールバックするか、入力行列を BK の倍数までパディングする。
関連スキル
analyze-kernel-bottleneck— カーネルが memory-bound かを特定し、バリアント選択を駆動する compute/load 比を計算する
GitHub 저장소
Frequently asked questions
What is the pipeline-gpu-kernel skill?
pipeline-gpu-kernel is a Claude Skill by pjt222. Skills package instructions and resources that Claude loads on demand, so Claude can perform pipeline-gpu-kernel-related tasks without extra prompting.
How do I install pipeline-gpu-kernel?
Use the install commands on this page: add pipeline-gpu-kernel to Claude Code as a plugin, or clone its repository into your skills directory, then restart Claude so it picks up the skill.
What category does pipeline-gpu-kernel belong to?
pipeline-gpu-kernel is in the Other category, tagged ai and api.
Is pipeline-gpu-kernel free to use?
Yes. pipeline-gpu-kernel is listed on AIMCP and free to install. It runs inside Claude, so no separate service account is required to use the skill itself.
연관 스킬
LlamaGuard는 폭력 및 혐오 발언 등 6가지 안전 범주에서 LLM 입력과 출력을 조정하기 위한 Meta의 70-80억 파라미터 모델입니다. 94-95% 정확도를 제공하며 vLLM, Hugging Face 또는 Amazon SageMaker를 사용해 배포할 수 있습니다. 이 기술을 사용하여 AI 애플리케이션에 콘텐츠 필터링 및 안전 가드레일을 손쉽게 통합하세요.
이 Claude Skill은 리소스 적정화, 태깅 전략, 지출 분석을 통해 개발자들이 클라우드 비용을 최적화할 수 있도록 지원합니다. AWS, Azure, GCP에서 클라우드 비용을 절감하고 비용 거버넌스를 구현하기 위한 프레임워크를 제공합니다. 인프라 비용을 분석하거나, 리소스를 적정화하거나, 예산 제약을 충족해야 할 때 사용하세요.
이 Claude Skill은 스프레드, 오버/언더, 프로프 베트를 포함한 스포츠 베팅 시장을 분석합니다. 역사적 추이와 상황별 통계를 검토하여 가치 베트를 발견하고, 교육적 목적으로 실행 가능한 권장 사항이 담긴 구조화된 마크다운 결과를 제공합니다. 개발자는 이 기능을 스포츠 베팅 분석 도구에 활용할 수 있으며, 단순히 엔터테인먼트/교육 목적으로만 설계되었음을 유의해야 합니다.
이 스킬은 bitsandbytes를 사용하여 LLM을 8비트 또는 4비트 정밀도로 양자화하며, 최소한의 정확도 손실로 50-75%의 메모리 감소를 달성합니다. 제한된 GPU 메모리에서 더 큰 모델을 실행하거나 추론을 가속화하는 데 이상적이며, INT8, NF4, FP4와 같은 형식을 지원합니다. 이 스킬은 HuggingFace Transformers와 통합되어 QLoRA 학습 및 8비트 옵티마이저를 가능하게 합니다.
