MCP HubMCP Hub
스킬 목록으로 돌아가기

pipeline-gpu-kernel

pjt222
업데이트됨 2 days ago
3 조회
17
2
17
GitHub에서 보기
기타aiapi

정보

이 스킬은 타일 형태의 GPU 커널을 변환하여 소프트웨어 파이프라이닝을 구현하고, 글로벌 메모리 로드와 텐서 코어 연산을 중첩시켜 레이턴시를 숨깁니다. 컴퓨트/로드 비율을 기반으로 LDG-레지스터와 cp.async 방식을 선택하도록 도우며, 공유 메모리 사용량을 점유율 제한에 맞춰 검증합니다. 텐서 코어 워크로드에서 메모리 전송이 병목 현상을 일으킬 때 고급 CUDA 최적화에 활용하세요.

빠른 설치

Claude Code

추천
기본
npx skills add pjt222/agent-almanac -a claude-code
플러그인 명령대체
/plugin add https://github.com/pjt222/agent-almanac
Git 클론대체
git clone https://github.com/pjt222/agent-almanac.git ~/.claude/skills/pipeline-gpu-kernel

Claude 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 ループを持つことを確認する。倍にされた共有メモリコストを計算しアーキテクチャ占有率クリフ未満に留まるか検証する。

  1. カーネル内の K ループを位置特定する。順次構造を持たねばならない: グローバルから共有メモリへ A と B タイルをロード、__syncthreads()、共有メモリタイル上で計算(HMMA/IMMA/FFMA)、__syncthreads()
  2. 単一バッファ共有メモリサイズを記録: smem_a_size = BM * BK * sizeof(T)smem_b_size = BK * BN * sizeof(T)
  3. 倍バッファコストを計算: smem_doubled = smem_a_size * 2 + smem_b_size * 2
  4. アーキテクチャクリフと比較。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
  1. ループ反復カウントを検証: 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)の間で選択する。

  1. compute/load 比を計算: GEMM ライクカーネルには ratio = (2 * BM * BN * BK) / ((BM * BK + BK * BN) * sizeof(T))(multiply-add ごとに 2 FLOPs、タイルあたりロードバイト)。
  2. 決定ルールを適用:

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_async tile N+1 を buf[(N+1) % 2] に直接(async、レジスタファイルバイパス)。
  • 計算前に __pipeline_commit()
  • buf[N % 2] 上で計算。
  • 計算後に __pipeline_wait_prior(0) + __syncthreads()
  • より良い重ね、プリフェッチ用ゼロレジスタ圧。#include <cuda_pipeline.h> を要求。
  1. 決定閾値(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 構造に変換する。

  1. 3 つのセクションを特定: 元のループ本体は 3 つの部分になる:

    • Prologue: tile 0 を buf[0] にロード、同期、それからメインループに入る。
    • Main loop: tile 1 から num_tiles - 1 まで、tile N+1 のロードを tile N の計算と重ねる。
    • Epilogue: 最後のタイルを計算(最後のメインループ反復で既にロード済み)。
  2. 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);
  1. 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);
  1. ループカウントを検証: メインループは 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: 倍バッファを実装する

倍バッファ共有メモリを宣言しロード関数を実装する。

  1. 単一バッファ共有メモリ宣言を倍バッファ配列で置換:
// 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];
  1. 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);
    }
}
  1. 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];
}
  1. コンパイラに正確な占有率情報を与えるためカーネルに __launch_bounds__(BLOCK_SIZE) を保つ。
  2. コンパイル: 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 参照に対してパイプライン化されたカーネルを動かして同一の数値出力を確認する。

  1. ベンチマークをコンパイル: nvcc -arch=sm_86 -O2 -o bench bench.cu -lcuda -I../../phase2/common
  2. スケールアップ前にインデックスバグを捕まえるため、まず小問題サイズ(512x512x512)で実行する。
  3. データ型に正しい公差を適用:
    • 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
  4. パイプライニングは算術を変えない — ロードを並べ替えるだけ。正確性が失敗したら、バグはバッファインデックスにあり計算ロジックではない。
  5. ターゲット問題サイズ(例: 4096x4096x4096)でテストして境界処理を検証する。

期待結果: 非パイプラインベースラインと同一のエラー境界で小・ターゲット問題サイズの両方で PASS。

失敗時: バッファインデックスバグが最も可能性の高い原因。検証: 計算は buf[tile & 1] から読み、ロードは buf[1 - (tile & 1)] に書く。Epilogue がバッファインデックス (num_tiles - 1) & 1num_tiles & 1 ではなく、を処理するか確認する。cp.async については、__pipeline_wait_prior(0)__syncthreads() 前に完了することを検証する — そうでないと計算が部分書き込みデータを読みうる。

ステップ6: ベンチマークと比較

ターゲット問題サイズで非パイプラインベースラインに対してパイプラインカーネルを計測する。

  1. 非パイプラインベースラインを実行し GFLOPS または帯域幅(カーネルタイプによる)を記録。
  2. 各パイプラインバリアントを実行し同じ指標を記録。
  3. スピードアップを計算: speedup = pipelined_metric / baseline_metric
  4. 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% またはリグレッション。
  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 命令が重なるか確認する。

  1. 逆アセンブル: cuobjdump -sass kernel.sm_86.cubin | grep -E 'IMMA|HMMA|LDGSTS|LDG|BAR'
  2. メインループ本体で、この順序パターンを検証:
    • LDGSTS または LDG 命令が HMMA または IMMA 命令の に現れる。
    • ロード命令と計算命令の間に BAR.SYNC がない(warp スケジューラで重ねるため自由でなければならない)。
    • BAR.SYNC が計算ブロックの に現れ、ロードされたデータの次の反復での使用をゲートする。
  3. HMMA/IMMA 命令のストールコードを確認 — HMMA パイプライン遅延の S08 が予想されかつ避けられない。IMMA の S01-S04 は普通。LDG/LDGSTS のストールは低い(S01)はず、warp スケジューラがロードが飛行中に計算に切り替えられるから。
  4. ループ反復あたりの総 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 はソースアドレスが有効でアラインメントされていることを要求する。KBK の倍数でない行列エッジでは、最後のタイルが境界外を読みうる。最終タイルには境界チェック付きスカラーロードにフォールバックするか、入力行列を BK の倍数までパディングする。

関連スキル

  • analyze-kernel-bottleneck — カーネルが memory-bound かを特定し、バリアント選択を駆動する compute/load 比を計算する

GitHub 저장소

pjt222/agent-almanac
경로: i18n/ja/skills/pipeline-gpu-kernel
0
agentsagentskillsai-assisted-developmentclaude-codeskillsteams

연관 스킬

llamaguard

기타

LlamaGuard는 폭력 및 혐오 발언 등 6가지 안전 범주에서 LLM 입력과 출력을 조정하기 위한 Meta의 70-80억 파라미터 모델입니다. 94-95% 정확도를 제공하며 vLLM, Hugging Face 또는 Amazon SageMaker를 사용해 배포할 수 있습니다. 이 기술을 사용하여 AI 애플리케이션에 콘텐츠 필터링 및 안전 가드레일을 손쉽게 통합하세요.

스킬 보기

cost-optimization

기타

이 Claude Skill은 리소스 적정화, 태깅 전략, 지출 분석을 통해 개발자들이 클라우드 비용을 최적화할 수 있도록 지원합니다. AWS, Azure, GCP에서 클라우드 비용을 절감하고 비용 거버넌스를 구현하기 위한 프레임워크를 제공합니다. 인프라 비용을 분석하거나, 리소스를 적정화하거나, 예산 제약을 충족해야 할 때 사용하세요.

스킬 보기

quantizing-models-bitsandbytes

기타

이 스킬은 bitsandbytes를 사용하여 LLM을 8비트 또는 4비트 정밀도로 양자화하며, 최소한의 정확도 손실로 50-75%의 메모리 감소를 달성합니다. 제한된 GPU 메모리에서 더 큰 모델을 실행하거나 추론을 가속화하는 데 이상적이며, INT8, NF4, FP4와 같은 형식을 지원합니다. 이 스킬은 HuggingFace Transformers와 통합되어 QLoRA 학습 및 8비트 옵티마이저를 가능하게 합니다.

스킬 보기

dispatching-parallel-agents

기타

이 Claude Skill은 3개 이상의 독립적인 문제를 동시에 조사하고 해결하기 위해 다중 에이전트를 배치합니다. 공유 상태나 의존성 없이 해결 가능한 무관련 장애 시나리오에 맞게 설계되었습니다. 핵심 기능은 병렬 문제 해결로, 각 독립 문제 영역마다 하나의 에이전트를 할당하여 효율성을 극대화합니다.

스킬 보기