pipeline-gpu-kernel
정보
이 스킬은 타일링된 GPU 커널에 소프트웨어 파이프라이닝(더블 버퍼링)을 적용하여 글로벌 메모리 로드와 Tensor Core 연산을 오버랩합니다. 커널을 프롤로그/메인 루프/에필로그 섹션으로 재구성하며, 연산 대 로드 비율에 따라 LDG 레지스터 또는 cp.async(LDGSTS) 기반 변형을 제공합니다. 워프 인터리빙만으로는 DRAM 지연 시간을 숨기기 어려운 낮은 연산 대 로드 비율의 메모리 바운드 커널에 사용됩니다.
빠른 설치
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에서 이 명령을 복사하여 붙여넣어 스킬을 설치하세요
문서
流水 GPU 核
施軟體流水(雙緩衝)於分塊 GPU 核,使第 N+1 塊之全域記憶體載入與第 N 塊之 Tensor Core 算覆於一時。化序之載-同步-算-同步 K 環為序章/環/尾章之構,依算載比擇 LDG 寄存器或 cp.async (LDGSTS) 之變體,驗共享記憶體不逾架構占用懸崖,終於 SASS 確載算之覆。
用時
analyze-kernel-bottleneck識為記憶體受限之核、每塊算載比低乃用- 唯倚 warp 交錯不足以掩 DRAM 延遲(GA104 約 300 週期)乃用
- 核含序之載-同步-算-同步 K 環、可重構乃用
- 算載比高(>20:1)且 8+ warp 活者,不需
入
- 必要:CUDA 核源文件(
.cu),含分塊 K 環,載與算分立 - 必要:目標 GPU 架構(如 GA104 / sm_86 — 定 smem 懸崖與占用上限)
- 必要:當前塊大小(BM, BN, BK)及數據型(FP16, FP32, INT8)
- 可選:每塊算載比(自
analyze-kernel-bottleneck;闕則估之) - 可選:基準(目標問題大小下未流水之效)
法
第一步:驗前提
確認核含分塊 K 環、載與算二相隔以 __syncthreads()。算雙倍共享記憶體之費,驗其不逾架構占用懸崖。
- 於核中尋 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):每 SM 最大 100 KB smem,懸崖在每塊 50 KB(逾 50 KB = 每 SM 1 塊 = 4 warp,占用減半)。
單緩衝:smem_a[BM*BK] + smem_b[BK*BN] = 2 KB + 2 KB = 4 KB
雙緩衝:smem_a[2][BM*BK] + smem_b[2][BK*BN] = 4 KB + 4 KB = 8 KB
8 KB << 50 KB 懸崖 -> 每 SM 2 塊 -> 8 warp
- 驗環迭次:
num_tiles = K / BK。流水需num_tiles >= 2(至少一序章 + 一主環迭)。
得:共享記憶體預算之表,列單緩衝與雙緩衝之費,確認雙倍配置不逾架構懸崖、每 SM 至少 2 塊占用。
敗則:若雙緩衝逾懸崖,減塊大小(BK 或 BM 減半)至 smem_doubled <= 50 KB(GA104)。或用唯寄存器之預取(LDG 變體)不雙倍共享記憶體——預取數據存於寄存器,於 __syncthreads() 後寫入同一單緩衝。
第二步:擇變體
依每塊算載比於 LDG 寄存器與 cp.async (LDGSTS) 間擇之。
- 算算載比:GEMM 類核之
ratio = (2 * BM * BN * BK) / ((BM * BK + BK * BN) * sizeof(T))(每乘加 2 FLOP,每塊載入字節)。 - 用此規則:
LDG 寄存器變體(ratio >= 5 或 CUDA < 11.0):
- LDG 第 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第 N+1 塊直入buf[(N+1) % 2](異步,繞寄存器檔)。- 算前
__pipeline_commit()。 - 於
buf[N % 2]算。 - 算後
__pipeline_wait_prior(0)+__syncthreads()。 - 覆更佳,預取無寄存器壓力。需
#include <cuda_pipeline.h>。
- 決閾(測於 GA104,IGEMM 4096x4096x4096):
- 比 < 5:1 — 取 cp.async(IGEMM 測得 +35%)。
- 比 5-20:1 — 二者皆實作而基準擇之。
- 比 > 20:1 — 流水未必有益(warp 交錯已足)。
得:所擇變體並依算載比與目標架構之說明。
敗則:若比模糊(5-20:1 之間),二變體皆實作而基準。CUDA 版本支持時,cp.async 為較穩之預設。
第三步:重構 K 環
化序之載-同步-算-同步環為流水之序章/環/尾章構。
-
識三段:原環體化為三段:
- 序章:載第 0 塊入
buf[0],同步,後入主環。 - 主環:第 1 塊至
num_tiles - 1塊,使第 N+1 塊之載入與第 N 塊之算覆。 - 尾章:算最後一塊(已於主環末迭載入)。
- 序章:載第 0 塊入
-
LDG 寄存器變體之構:
// === 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迭(指算之第 0 至num_tiles - 2塊,載入第 1 至num_tiles - 1塊)。尾章算末迭所載之塊。
得:所擇變體之重構 K 環源碼,序章、主環、尾章三段分明。
敗則:最常見之誤為緩衝索引差一或忘尾章算。驗:序章載入 buf[0],主環首迭算 buf[0] 而載入 buf[1],次迭算 buf[1] 而載入 buf[0],餘類推。尾章算 buf[(num_tiles - 1) & 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];
- cp.async 變體者,以流水 API 實作異步載入函數:
__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。
第五步:驗正確
行流水核對 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 標量 (FFMA):
abs=1e-3, rel=1e-3
- INT8 Tensor Core (IMMA):
- 流水不變算術——唯重序載入。若正確敗,誤在緩衝索引、非算邏輯。
- 於目標問題大小(如 4096x4096x4096)測之,驗邊界處理。
得:小與目標二大小皆 PASS,誤差界與未流水基準相同。
敗則:緩衝索引誤為最可能之因。驗:算讀自 buf[tile & 1],載寫至 buf[1 - (tile & 1)]。察尾章處理之緩衝索引為 (num_tiles - 1) & 1、非 num_tiles & 1。cp.async 者,驗 __pipeline_wait_prior(0) 於 __syncthreads() 前畢——否則算或讀部分寫之數據。
第六步:基準與比
於目標問題大小,量流水核對未流水基準。
- 行未流水基準,記 GFLOPS 或頻寬(依核型)。
- 行各流水變體,記同指標。
- 算加速:
speedup = pipelined_metric / baseline_metric。 - 依算載比之預期所得(測於 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 |
得:效能比較表示提升。所擇變體當顯可量加速,與算載比之預測相合。
敗則:若效退,察三事:(1) SASS 有未料之指令額外開銷(額外 BAR.SYNC、寄存器溢)。(2) 共享記憶體未越占用懸崖——以 nvcc --resource-usage 或 cuobjdump -res-usage 驗。(3) 問題大小生足塊(K / BK >= 4)以攤序章/尾章之開銷。
第七步:驗 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
得:注釋 SASS 摘錄示載先算後之模式,無中介屏障。零寄存器溢。
敗則:若編譯器將載入重序於算後(破覆),試:(1) 主環上 #pragma unroll 1 防過激展開。(2) 將載與算分至獨立行內函數以為序之提示。(3) 用 asm volatile("" ::: "memory") 為載算塊間之編譯器籬笆(末手段——或抑他優化)。
驗
- 雙緩衝 smem 不逾架構懸崖(GA104:每塊 50 KB)
- 二緩衝交替而用(
buf[tile & 1]模式) - 序章載第 0 塊入
buf[0] - 尾章算末塊自
buf[(num_tiles - 1) & 1] - 對 CPU 參考於小與目標大小皆 PASS
- SASS 確載算之覆(LDGSTS/LDG 與 IMMA/HMMA 間無
BAR.SYNC) - 效優於未流水基準
- LDG 變體無寄存器溢(察
nvcc --resource-usage)
陷
- 倍緩衝越 smem 懸崖 — GA104 懸崖在每塊 50 KB、非 64 KB。實作前必算
smem_doubled。單緩衝用 28 KB 之核,雙倍後跳至 56 KB 越懸崖、占用減半。流水之 +20% 益可化為 -50% 之占用退。 - 忘尾章算 — 主環末迭所載之末塊,環外需自身之算相。闕之,K 維末 BK 列默墮,致誤而似小數值差、非顯敗。
- 緩衝索引差一 — 算用
buf[tile & 1],載用buf[1 - (tile & 1)]。常誤為以buf[(tile + 1) & 1]為下一緩衝,緩衝數為 2 時等同buf[1 - (tile & 1)]——然若誤施於算索引則讀錯。 - cp.async 提交/等待之序 —
__pipeline_commit()必於算相前呼之(封異步副本之批)。__pipeline_wait_prior(0)必於算相後呼之(阻至所提之副本皆畢)。互換之,異步副本變同步,盡失覆益。 - 缺 __syncthreads — LDG 變體者,算與 STS 排空間需
__syncthreads()(俾算先讀畢當前緩衝再被覆)。STS 排空後另需__syncthreads()(俾諸線程寫畢,下迭再讀)。cp.async 變體者,__pipeline_wait_prior(0)後之__syncthreads()確諸線程見畢之異步副本。 - cp.async 之邊界處理 —
__pipeline_memcpy_async需源址有效且對齊。矩陣邊處K不為BK之倍時,末塊或越界讀。對末塊回退用標量載入並界檢,或將輸入矩陣補至 BK 之倍。
參
analyze-kernel-bottleneck— 識核是否為記憶體受限,並算驅變體擇之算載比
GitHub 저장소
연관 스킬
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개 이상의 독립적인 문제를 동시에 조사하고 해결하기 위해 다중 에이전트를 배치합니다. 공유 상태나 의존성 없이 해결 가능한 무관련 장애 시나리오에 맞게 설계되었습니다. 핵심 기능은 병렬 문제 해결로, 각 독립 문제 영역마다 하나의 에이전트를 할당하여 효율성을 극대화합니다.
