CUDA 18VOL · T2 KERNEL · CONTENT-FIRST · A4 LANDSCAPE · 15p

병렬 Primitive 고급 단권화

Warp Intrinsics · Cooperative Groups · Reduction/Scan Variants · Split-K · Stream-K
Volume V05 / 18
Tier T2 Kernel 패턴
선행 V01 (PMPP Core)
용도 패턴 인출 · 알고리즘 선택 지도

목차

1. Warp Intrinsic 전체 계보 — shfl · ballot · matchp.2
2. Cooperative Groups API — thread_block · tile · gridp.3
3. Reduction 7단계 진화 — naive → CUBp.4
4. Scan 전체 계보 — Hillis · Kogge · Brent · Sklanskyp.5
5. Histogram 고급 — privatization · aggregationp.6
6. Top-K selection — bitonic · radix · CUBp.7
7. Split-K 전략 — atomic vs buffer+reducep.8
8. Stream-K 전략 — work partitioning · fixupp.9
9. Persistent Kernel Patternp.10
10. Producer-Consumer (single-GPU ring)p.11
11. Sorting 상세 — bitonic · merge-path · radixp.12
12. Sparse kernel — SpMV · SpMM · SDDMMp.13
13. CUB & Thrust API 맵p.14
14. Cheat Sheet — complexity · decision treep.15

범례

핵심 용어 (노랑)
매우 중요·표 헤더
정의·수식 박스
예시
빨강주의·실수 지점
Must-have 핵심
(!)니모닉
Wwork complexity
Ddepth / step complexity
타 권 교차참조
Out of Scope CUTLASS 내부 구현 ↗ V06 · attention kernel ↗ V07
PMPP Ch10-14 · CUB docs · CUDA CPG · Stream-K (Osama 2023)

1 warp-level 동기화 모델 ★

모델 warp = 32 thread, lockstep 아닌 independent thread scheduling (Volta+). 모든 warp intrinsic은 _sync 접미사로 active mask 명시.
mask: uint32_t, bit i = lane i 참여 여부
0xffffffff = 32 lane 전원
divergent branch 내부에서는 부분 mask 필수

2 shfl 4-variant ★

intrinsicsrc lane용도
__shfl_sync임의 idxbroadcast
__shfl_up_synclane − dprefix scan
__shfl_down_synclane + dreduction
__shfl_xor_synclane ^ dbutterfly

각 intrinsic: (mask, val, delta, width=32). width는 logical warp 크기 32 나누기.

3 shfl_xor butterfly reduce·all-reduce

// warp 32-wide sum, result lane0..31 동일
for(int d=16; d>0; d>>=1)
  v += __shfl_xor_sync(0xffffffff, v, d);

4 vote intrinsic ★

함수반환의미
__all_syncint 0/1전원이 pred true
__any_syncint 0/1하나라도 true
__ballot_syncuint32lane별 pred를 bit로
mask = __ballot_sync(0xffffffff, x > 0)__popc(mask) = positive 개수.

5 match intrinsic sm_70+

정의 __match_any_sync(mask, val) → 같은 val 가진 lane의 bitmask.
__match_all_sync(mask, val, *pred) → 전원 동일하면 mask, 아니면 0.
  • atomic 대신 peer-group aggregation에 사용
  • histogram conflict-free 업데이트 패턴의 핵심

6 activemask / sync_warp

  • __activemask(): 현재 convergent lane bitmask 조회
  • __syncwarp(mask): warp 내부 barrier
  • Volta ITS 이후 __syncwarp 필수 — lockstep 가정 금지

7 mask 생성 규칙 ★

상황mask 생성
warp 전체0xffffffff
divergent if__ballot_sync(0xff..ff, cond)
활성만__activemask()
sub-warp 160x0000ffff

8 warp reduce → broadcast

float warp_sum(float v){
  for(int d=16; d>0; d>>=1)
    v += __shfl_down_sync(0xff..ff, v, d);
  return __shfl_sync(0xff..ff, v, 0); // bcast
}

9 PTX 대응 ↗ V03 §9

  • shfl.sync.{idx,up,down,bfly}.b32
  • vote.sync.{all,any,ballot}.pred
  • match.any.sync.b{32,64}
함정: mask에 포함된 lane이 intrinsic 호출 누락 → UB. divergent path 양쪽 모두 호출하거나 __ballot_sync로 실제 active만 전달.

1 계층 객체 ★

타입크기sync 범위
thread_block≤1024__syncthreads
thread_block_tile<N>N ∈ {2,4,8,16,32}warp-local
coalesced_group변동active lane
cluster_group≤16 blockcluster.sync (sm_90)
grid_group전체 gridcooperative launch

헤더: <cooperative_groups.h>, namespace cg = cooperative_groups.

2 기본 패턴

namespace cg = cooperative_groups;
auto block = cg::this_thread_block();
auto warp  = cg::tiled_partition<32>(block);
auto quad  = cg::tiled_partition<4>(warp);

block.sync();        // == __syncthreads
warp.thread_rank();  // 0..31
warp.size();         // 32

3 tiled_partition 계층

규칙 tile size는 2의 거듭제곱, 상위 group의 sub-divide. tile > 32은 block 내부만, tile <= 32은 warp 내부.
tile.meta_group_rank() = 이 tile이 parent의 몇 번째
tile.meta_group_size() = parent 안의 tile 개수

4 collective 연산 ★

함수의미
cg::reduce(t, v, op)tile 내 reduction
cg::exclusive_scanexclusive scan
cg::inclusive_scaninclusive scan
t.ballot(pred)bitmask 반환
t.shfl(v, src)tile-local shfl

5 coalesced_group

  • divergent branch 내부의 active lane만 모음
  • cg::coalesced_threads()로 생성
  • atomic 대체 aggregation에 사용

6 grid_group grid-level sync

요건 cooperative launch 필요: cudaLaunchCooperativeKernel.
grid size ≤ GPU의 concurrent block 한도. SM 수 × block/SM.
auto grid = cg::this_grid();
grid.sync();   // 모든 block 대기

persistent kernel · multi-pass reduction을 1-kernel로 구현 가능. ↗ §9

7 cluster_group Hopper 전용

  • __cluster_dims__(x,y,z) 또는 launch API
  • DSMEM 접근 ↗ V04 §3
  • cluster.sync() = cluster 내 block 전체 barrier

8 labeled_partition

정의 label 값이 같은 thread끼리 동적 group 생성.
cg::labeled_partition(warp, key) → match_any 기반.

histogram privatization, sort-within-warp 등에 활용.

CG 계층: 쓰타워블그크 (thread · tile · warp · block · grid · cluster)

1 7단계 요약표 ★★

#기법핵심 1-liner
1Naive interleavedif(tid%(2*s)==0) s[tid]+=s[tid+s]
2Interleaved fixi = 2*s*tid; s[i]+=s[i+s]
3Sequential addrif(tid<s) s[tid]+=s[tid+s]
4First-add on loads[tid]=g[i]+g[i+BS]
5Unroll last warptid<32 구간 volatile unroll
6Warp shufflev+=__shfl_down_sync(m,v,d)
7Multi-block + CUBcub::DeviceReduce::Sum

PMPP Ch10 / Harris reduction talk 기반. 단계 번호는 관례적 표기.

2 1. Naive (divergent)

for(int s=1; s<BS; s*=2){
  if(tid % (2*s) == 0)
    sd[tid] += sd[tid+s];
  __syncthreads();
}

warp 내 절반 꺼짐 → SIMT divergence.

3 2. Interleaved (no mod)

for(int s=1; s<BS; s*=2){
  int i = 2*s*tid;
  if(i < BS) sd[i] += sd[i+s];
  __syncthreads();
}

divergence ↓ but bank conflict (stride 2,4,8…).

4 3. Sequential addressing ★

for(int s=BS/2; s>0; s>>=1){
  if(tid < s)
    sd[tid] += sd[tid+s];
  __syncthreads();
}

conflict ↓ · divergence ↓. warp 앞쪽이 활성.

5 4. First-add during load

int i = blockIdx.x*(2*BS) + tid;
sd[tid] = g[i] + g[i+BS];   // ½ grid
__syncthreads();

global load 효율 2×, grid ½.

6 5. Unroll last warp

if(tid < 32){
  volatile float* v = sd;
  v[tid]+=v[tid+32]; v[tid]+=v[tid+16];
  v[tid]+=v[tid+8];  v[tid]+=v[tid+4];
  v[tid]+=v[tid+2];  v[tid]+=v[tid+1];
}

warp 내부 __syncthreads 제거. volatile 필수 캐시 우회.

7 6. Warp shuffle ★

float v = sd[tid];
for(int d=16; d>0; d>>=1)
  v += __shfl_down_sync(0xff..ff, v, d);
if(tid==0) out[blockIdx.x] = v;

smem 경유 제거 · register only · Kepler+.

8 7. Multi-block + CUB

  • 2단계: block partial → grid reduce
  • 또는 atomicAdd로 최종 합산
  • 또는 grid.sync()로 1-kernel
  • 실전: cub::DeviceReduce::Sum · thrust::reduce
수치 재현성: FP 합은 비결합 → block 수 / scheduling 달라지면 값 미세 변화.

1 Scan 정의 ↗ V01 §12

정의 Inclusive: y[i] = x[0]⊕…⊕x[i]
Exclusive: y[i] = x[0]⊕…⊕x[i−1], y[0] = id
⊕ 는 결합법칙 성립 연산자 (+, max, min, ·) 교환법칙은 불필요

2 복잡도 비교표 ★★

알고리즘work Wstep D비고
SequentialO(N)O(N)기준
Hillis-SteeleO(N log N)O(log N)warp scan
Kogge-StoneO(N log N)O(log N)regular
Brent-KungO(N)O(2 log N)work-eff ★
SklanskyO(N log N)O(log N)divide·conq

Hillis-Steele은 Kogge-Stone의 상위 계열 구조 동일.

3 Kogge-Stone regular 구조

for(int o=1; o<BS; o*=2){
  float v=0;
  if(tid>=o) v = sd[tid-o];
  __syncthreads();
  if(tid>=o) sd[tid] += v;
  __syncthreads();
}

W = N log N · D = log N · 모든 단계 all-active.

4 Brent-Kung up·down ★

  1. Up-sweep: reduction tree 올리기 N 비용
  2. Root = identity로 초기화
  3. Down-sweep: 좌측 자식으로 부모를, 우측으로 (부모 ⊕ 좌측 기존값)
W ≈ 2N, D = 2·log N
work-optimal · step 두 배

5 Sklansky divide-and-conquer

[ L ][ R ] → scan(L), scan(R)
                 → R += last(L)

step = log N · 동일 level 모두 독립 → bank conflict 주의.

6 Warp scan (shfl_up) ★

float v = x;
for(int d=1; d<32; d*=2){
  float u = __shfl_up_sync(0xff..ff, v, d);
  if(lane >= d) v += u;
}

inclusive warp scan · 32-wide · 5-step.

7 3-phase hierarchical ★

  1. block 내 local scan (warp → block)
  2. block 총합 수집 → aux[ ]
  3. aux 자체를 scan
  4. block i 원소에 aux[i−1] 가산
blk0    blk1    blk2
 ↓        ↓        ↓
local   local   local
  Σ0     Σ1     Σ2
  └─ scan aux ──┘
  +0     +Σ0    +Σ0+Σ1

8 Segmented scan

정의 head-flag 배열 H로 segment 경계 표시. flag=1인 곳에서 scan 초기화.
결합연산: (v_a, h_a) ⊕ (v_b, h_b) = (h_b ? v_b : v_a⊕v_b, h_a|h_b)

응용: SpMV reduce, RLE, 계층적 reduction.

Scan 4종: 힐코브스 (Hillis · Kogge · Brent · Sklansky)

1 Histogram 문제

정의 입력 N개 → bin K개에 count. 본질은 atomic RMW.
naive: atomicAdd(&hist[bin(x)], 1)
contention × K·BS·… 따라 수십 배 차이

2 privatization 4-level ★

level저장소복사본
L0global1
L1sharedper block
L2registerper thread
L3hybridhot bin reg + cold smem

3 shared privatization

__shared__ int sh[K];
// init
for(...) atomicAdd(&sh[b], 1);
__syncthreads();
// flush
if(tid<K) atomicAdd(&hist[tid], sh[tid]);

4 aggregation ★

아이디어 warp 내 동일 bin 쓰는 lane을 match_any로 묶어 1 atomic으로 합침.
int b = bin(x);
int peers = __match_any_sync(0xff..ff, b);
int cnt = __popc(peers);
int leader = __ffs(peers)-1;
if(lane == leader)
  atomicAdd(&sh[b], cnt);

contention K → contention warp 수준으로 축소.

5 thread-coarsening

  • thread 하나가 C 개 원소 처리
  • block 수 ↓ · smem init/flush overhead 분산
  • register pressure 와 trade

6 sort-then-reduce

  1. 입력을 bin id로 radix sort
  2. segmented reduce로 count
  3. K 크거나 분포 skewed 시 atomic보다 유리

7 기법 선택 결정표 ★

상황권장
K 작음 (≤256)shared privat.
K 큼 (≫smem)global + aggregation
skew 심함sort-then-reduce
N 큼·K 중간hybrid (hot reg)
librarycub::DeviceHistogram

8 다차원 히스토그램

  • bin 공간 = 차원들의 카르테시안 곱
  • 선형화 후 동일 pattern
  • 2D 이미지 hist: tile 분할 후 shared privat

9 CUB API ↗ §13

  • cub::DeviceHistogram::HistogramEven
  • cub::DeviceHistogram::HistogramRange
  • cub::DeviceHistogram::MultiHistogramEven (channel)
atomic contention은 lane 수, bin 분포, HW arch 세 요인. skew 있는 자연 데이터는 privatization만으로 부족.

1 문제 정의

Top-K N개 중 상위 K개 선택. sampling, beam search, attention sparsity 등.
full sort: O(N log N)
selection: O(N log K) or O(N) 기대 K ≪ N일 때 큰 차이

2 접근 3분류 ★

방법복잡도특징
bitonicO(N·K)small K 고속
radixO(N·b)K 중간·float
full sort + sliceO(N log N)K 큼·여러 Top

3 Bitonic Top-K

  1. 각 block이 local bitonic sort
  2. 상위 K만 global heap/buffer에 push
  3. 두 번째 pass로 K·B → K 축소

4 Radix Top-K ★

  1. 최상위 b-bit로 bucket 분류
  2. bucket count 누적 → K 들어가는 경계 bucket 결정
  3. 경계 bucket만 다시 b-bit 재귀
  4. 나머지 bit 확정까지 반복

b = 4 or 8. float은 IEEE monotone reorder 필요 부호·지수 처리.

5 Float bit reorder

x ≥ 0 : y = bits(x) ^ 0x80000000
x < 0 : y = ~bits(x) unsigned로 해석하면 실수 대소 유지

6 warp-level top-K

  • warp 내 32 lane이 자기 값 보유
  • shfl_xor butterfly sort
  • K ≤ 32이면 1-warp로 종결

7 CUB · lib 활용

요구API
정확한 Top-Kcub::DeviceRadixSort::SortKeys + slice
K작음·fastcub::BlockRadixSort
approx·samplingGumbel top-K · categorical
Thrustthrust::sort + copy_n

8 적용 맥락

  • LLM sampling top-k ↗ V08 §10
  • MoE router top-k ↗ V08 §1
  • sparse attention indexing
tie-break: 동점 처리 규칙이 kernel별로 다름. 결정론 필요 시 (value, index) 쌍으로 비교.

9 결정 트리

K ≤ 32 ──► warp bitonic
K ≤ 256 ─► block bitonic
K 중간 ──► radix select
K 큼 ───► full sort

1 왜 Split-K인가 ★

맥락 GEMM tile partition = (M/BM) × (N/BN) CTA. M, N이 작고 K가 크면 CTA 수 < SM → occupancy 부족.
#CTA(standard) = ⌈M/BM⌉ · ⌈N/BN⌉
M=N=128, BM=BN=128 → CTA 1개. SM 132개 유휴.

2 Split-K 아이디어

정의 K 차원을 S 조각으로 분할: 각 조각이 독립 부분곱을 계산, 끝에서 합산.
C = Σs=0..S-1 A[:, s·k:(s+1)·k] · B[s·k:(s+1)·k, :]
#CTA × S 만큼 증가

3 합산 방식 2종 ★

방식writeback비용
A. atomicatomicAdd(C)contention
B. buffer + reduceC_part[S] → 별도 reduce kernelmem 2× · 정확

4 Atomic variant 의사코드

// slice s, tile (m,n)
accum = 0;
for(k=s·kseg; k<(s+1)·kseg; k+=BK)
  accum += A[m,k:k+BK] · B[k:k+BK,n];
atomicAdd(&C[m,n], accum);

tile 단위 하나의 atomic. FP32면 hardware atomic 가능.

5 Buffer + Reduce variant

// stage 1: partial
for(s, m, n) C_part[s,m,n] = A_s · B_s;

// stage 2: reduce
C[m,n] = Σ_s C_part[s,m,n];

stage 2는 O(S·M·N) reduce kernel. FP 합 순서 deterministic.

6 S (split 수) 선택

  • 목표: #CTA · S ≳ #SM · 2
  • S 과대: K 조각당 K_seg가 너무 작아져 MMA latency 은폐 실패
  • 실전 S ∈ {2, 4, 8, 16}

7 Atomic vs Buffer 선택표 ★

요인권장
FP32·tile 큼atomic
FP16·FP8 accumbuffer (HW atomic 제한)
결정론 요구buffer
메모리 tightatomic
S 작음(2-4)atomic
S 큼(≥8)buffer+tree reduce

8 Split-K의 한계

  • 각 CTA에 동일 K_seg → work balance 정적
  • M,N이 BM·BN과 정합 안 되면 tile 경계 낭비
  • 이 문제를 해결하는 게 Stream-K ↗ §8

9 CUTLASS 제공 ↗ V06

  • SplitKSerial (buffer+reduce)
  • SplitKParallel (atomic)
  • epilogue reduce kernel 자동 생성
FP atomic 비결정성: atomic 순서 불확정 → 결과 미세 차이. reproducibility test 주의.

1 동기 ★

문제 Split-K는 S를 고정 → (M,N,K)와 SM 수의 나머지에서 waste. Stream-K: work를 균일하게 분할.
total_iter = ⌈M/BM⌉·⌈N/BN⌉·⌈K/BK⌉
per_SM = ⌈total_iter / #SM⌉ 각 CTA가 연속 iter 슬라이스 담당

2 핵심 아이디어

  • CTA = SM 수와 동일 (persistent)
  • 각 CTA가 MMA iteration의 flat range를 담당
  • tile 경계에 걸리면 부분 accumulator 저장
  • fixup kernel이 조각을 합산

3 work 분할

iter: 0 1 2 3 4 5 6 7 8 9 ...
CTA0: [ 0 .. 3 ]
CTA1:         [ 4 .. 6 ]
CTA2:                 [ 7 .. 9 ]
                       ↑ tile 경계에서 split

Osama et al. 2023 Stream-K. CUTLASS 3.x에 정식 통합.

4 3 variant

모드설명
Data-parallel기존 tile-per-CTA (M·N 병렬)
Split-KK 분할 S-way
Stream-Kiter flat 분할 · hybrid

Stream-K는 실제로 DP 블록 + Stream-K 블록을 혼합 운용하기도 한다.

5 Fixup kernel

역할 tile 경계를 넘은 부분 accumulator들을 대상 tile의 최종 C에 합산.
// per partial slab
for(tile) C[tile] = Σ partials[tile][*];

partial은 on-chip 또는 global workspace에 저장. semaphore/flag로 dependency.

6 Semaphore 패턴

  • tile-owner CTA만 최종 write
  • 보조 CTA는 partial + flag set
  • owner가 flag poll → 합산 → C 기록

7 Split-K vs Stream-K 결정표 ★★

상황선택
M·N ≫ SM, K 보통Data-parallel
M·N 작고 K 큼Split-K
(M·N·K) % SM 크게 안 맞음Stream-K
tail wave 긴 경우Stream-K
결정론 + 간단Split-K buffer
generic dispatchStream-K (CUTLASS 3.x)

8 핵심 수식

tail_ratio = (wave_count − ⌊wave_count⌋)
Stream-K 이득 ≈ tail_ratio · SM_util
tail이 짧으면 이득도 작음

9 CUTLASS 구현 개요 ↗ V06 §13

  • TileScheduler 추상
  • StreamKScheduler dispatch policy
  • fixup은 별도 kernel or fused epilogue
  • workspace = (#SM · BM · BN) partial buffer
debug 난이도: partial·flag·owner 관계가 복잡 → 라이브러리 사용 권장, 직접 작성은 V06 숙지 후.

1 정의 ★

정의 grid size = #SM (또는 #SM × k)로 고정. CTA가 장수하며 work queue를 drain.
grid = (#SM, 1, 1) · block = (BS, 1, 1)
각 CTA가 atomic counter로 work item 획득 launch overhead 1회만

2 왜 쓰는가

  • uneven work (tile별 iter 수 변동)
  • launch overhead 누적 회피
  • grid-level sync 필요 시 (cooperative)
  • Stream-K의 기반 ↗ §8

3 기본 스켈레톤

__global__ void persist(..., int* ctr){
  while(1){
    int id = (tid==0) ?
      atomicAdd(ctr, 1) : 0;
    id = __shfl_sync(0xff..ff, id, 0);
    if(id >= N_WORK) break;
    process_work(id);
  }
}

4 work scheduling 3 패턴

패턴description
Dynamicatomic counter로 pull
Static blockid = bid·stride + i
Hybridlarge chunk static + tail dynamic

5 grid size 선택 ★

grid = #SM · CTA_per_SM
CTA_per_SM = min(reg, smem, warp 제약 하 max) occupancy 극대 grid = SM · max CTA

cooperative launch 시: grid ≤ GPU가 동시에 launch 가능한 상한.

6 grid.sync() 활용

  • multi-pass reduction을 1-kernel로
  • partial → sync → final
  • launch overhead × pass 회피

7 사용 상황 표

상황적합?
work uniform + 다수 tileDP 권장
work unevenpersistent
tail wave 문제persistent
launch 수 과다 batchpersistent
decoding long looppersistent

8 주의 사항

  • debug 어려움: grid-level race
  • atomic counter는 cudaMemsetAsync로 매 invocation 초기화
  • early-exit CTA가 있으면 cooperative sync 막힘
  • register pressure ↑ (loop body 길어짐)

9 활용 예

  • FA forward persistent ↗ V07
  • Stream-K GEMM
  • graph BFS frontier expand
  • inference decoder loop
Persistent 체크: SM카운드싱 (grid = SM · 카운터 · 레인 · grid.크)

1 패턴 개요 ★

구성 일부 warp/CTA가 load(producer), 다른 일부가 compute(consumer). 중간 ring buffer를 shared 또는 global에 둔다.
  • Hopper WGMMA warp specialization ↗ V04 §9
  • CUTLASS cooperative / pingpong
  • single-GPU에선 shared ring 중심

2 Ring buffer 자료구조

size = N_STAGE (2, 3, 4 …)
head · tail ∈ [0, N_STAGE)
empty: head == tail
full : (head+1) % N == tail

3 stage 수 선택

stage용도
2double buffer
3-4표준 pipelined GEMM
≥5HBM latency 큼·Hopper TMA

stage↑ → smem ↑ → occupancy ↓ trade.

4 Sync mechanism 3종

방식arch
__syncthreads모든 arch
cp.async.commit/waitAmpere+
mbarrier asyncHopper ↗ V04 §6

5 Lock-free ring 의사코드

// producer (warp P)
while((head+1)%N == tail) ;
buf[head] = load_tile();
__threadfence_block();
head = (head+1)%N;

// consumer (warp C)
while(head == tail) ;
t = buf[tail];
compute(t);
tail = (tail+1)%N;

head/tail은 shared, 각각 전담 warp만 write. memory fence로 ordering.

6 mbarrier phase 기반 Hopper

  • 각 stage에 full-bar · empty-bar
  • producer: arrive(full), wait(empty)
  • consumer: wait(full), arrive(empty)
  • phase bit 토글로 2-stage 라운드트립

7 warp specialization

warp 0-3   : producer (TMA load)
warp 4-7   : consumer A (WGMMA)
warp 8-11  : consumer B (WGMMA)  pingpong

Hopper setmaxnreg로 producer ↓ reg / consumer ↑ reg 재분배.

8 성능 이득 요인

  • compute ↔ load 완전 중첩
  • register file 효율 재분배
  • instruction issue port 분산
  • global load latency 은폐

9 단점·함정

  • deadlock: arrive/wait 짝 안 맞으면 영구 정지
  • stage 수 잘못 잡으면 stall
  • smem 사용량 stage·tile 크기 곱으로 팽창
  • debug 비용 ↑ 코드보다 설계
single-GPU ringmulti-GPU ring은 다름. 후자는 NCCL ring-allreduce ↗ V15 §2.

1 GPU 정렬 3-family ★

family복잡도GPU 적합
BitonicO(N log²N)regular pattern ○
Merge (path)O(N log N)balanced ○
RadixO(N·b)int/float best ★

2 Bitonic Sort 개념

정의 bitonic sequence(오름→내림 혹은 반대)를 재귀적으로 merge. 비교 네트워크가 입력 independent.
stage k (k=1..log N):
  substage j (j=k..1):
    compare-swap (i, i ^ (1<<(j-1)))

3 Bitonic 코드

for(k=2; k<=N; k*=2)
  for(j=k/2; j>0; j>>=1){
    p = tid ^ j;
    if(p > tid){
      up = ((tid & k) == 0);
      if((a[tid]>a[p])==up) swap(a[tid],a[p]);
    }
    __syncthreads();
  }

data-independent → warp divergence ↓. power-of-2 크기.

4 Merge Path ★

정의 두 정렬 배열 A·B를 merge할 때, 각 thread가 결과의 한 chunk를 co-rank로 찾아 독립 수행.
co-rank: (i, j) s.t. i+j = k ∧ A[i-1] ≤ B[j] ∧ B[j-1] ≤ A[i]
binary search on diagonal

↗ V01 §13 PMPP Ch12 co-rank 함수 정의.

5 Radix Sort 개요 ★

  1. b-bit씩 digit 추출 (LSB → MSB)
  2. 각 digit에 대해 counting sort (stable)
  3. counting sort 내부 = histogram + scan + scatter
  4. b=4 (16 bucket) 또는 b=8 (256) 일반적

6 Radix 한 iteration

input → digit(input) → local hist
      → scan hist → global hist
      → scan global → offsets
      → scatter to output

Scan · Histogram 재사용 ↗ §4, §5.

7 Float 정렬 bit trick

f ≥ 0: u = bits(f) ^ 0x80000000
f < 0: u = ~bits(f) IEEE 754 monotone mapping

radix sort를 float·double에 적용하는 표준 전처리.

8 Merge vs Radix 선택

요인선택
int·float keyRadix ★
key 복잡 structMerge
안정성 요구둘 다 stable 가능
key 길이 ≫ 64bMerge 유리
GPU 표준CUB Radix

9 Segmented sort

  • 여러 작은 정렬을 동시에
  • 각 segment 독립 key·value
  • cub::DeviceSegmentedRadixSort
  • MoE · grouped GEMM 전처리 ↗ V08
직접 구현 금물: 정렬은 라이브러리가 거의 항상 이김. 단권화에 자료구조는 담되 실전은 cub::Device*Sort.

1 연산 3종 ★

op수식출력
SpMVy = A·xdense vector
SpMMY = A·Bdense matrix
SDDMMC = (A·B) ⊙ Mmask된 dense

A: sparse · B,x,Y: dense · M: sparsity mask.

2 Storage 포맷 ↗ V01 §14

포맷구성특성
COO(row, col, val)random 순
CSRrow_ptr, col_idx, valrow 연속 ★
CSCcol_ptr, row_idx, valcol 연속
ELLpadded fixed rowregular row

3 SpMV 전략 3종 ★

전략단위적합
scalar1 thread / rowrow 짧음
vector1 warp / rowrow 중간
CSR-streamblock strideuniform 분포
merge-basednnz 균등 splitskew 심함 ★

4 Vector SpMV 의사코드

// warp = 32 thread / row
r = blockIdx.x;
float s = 0;
for(j=row_ptr[r]+lane; j<row_ptr[r+1]; j+=32)
  s += val[j] * x[col_idx[j]];
// warp reduce
for(d=16; d>0; d>>=1)
  s += __shfl_down_sync(0xff..ff, s, d);
if(lane==0) y[r] = s;

5 Merge-based SpMV

핵심 nnz 축을 균등 분할 → 각 thread가 동일 work. merge path로 (row, nnz) 좌표 찾기.

Merrill & Garland 2016. skew 심한 그래프에 강함.

6 SpMM 전략

  • 출력 열을 여러 묶음으로 tiling
  • 각 tile에 대해 A의 row 순회
  • B의 해당 row를 dense로 load
  • accumulate

row-by-row × B-column-tile = 일반적 패턴. cuSPARSE 기본 경로.

7 SDDMM 특성

구조 C의 비영 위치는 M으로 주어짐. 그 위치 (i,j)에 대해서만 A[i,:]·B[:,j] 계산.
  • nnz(M) 만큼만 inner product
  • Graph attention, GNN에 핵심

8 2:4 structured sparsity

4원소 그룹마다 2원소만 비영
Ampere Tensor Core 2× throughput sparse MMA ↗ V03 §8

9 Library 맵

  • cuSPARSE: CSR/COO SpMV/SpMM
  • cuSPARSELt: 2:4 structured
  • CUB: primitive block
  • DGL·PyG: graph frontend
load imbalance가 sparse kernel의 본질적 어려움. row 분포 histogram으로 strategy 선택.

1 CUB 계층 ★

scopeprefix사용
Devicecub::Device*full array
Blockcub::Block*CTA 협력
Warpcub::Warp*warp 협력

CUB = "CUDA UnBound". NVIDIA/cub on GitHub.

2 Device API 핵심

opAPI
reduceDeviceReduce::{Sum,Min,Max,Reduce}
scanDeviceScan::{Inclusive,Exclusive}Sum
sortDeviceRadixSort::Sort{Keys,Pairs}
seg reduceDeviceSegmentedReduce::Sum
selectDeviceSelect::{If,Unique,Flagged}
histogramDeviceHistogram::Histogram{Even,Range}
run-lengthDeviceRunLengthEncode::Encode

3 CUB 2-call pattern ★

void* d_temp = nullptr;
size_t temp_bytes = 0;
cub::DeviceReduce::Sum(
  d_temp, temp_bytes, d_in, d_out, N);
cudaMalloc(&d_temp, temp_bytes);
cub::DeviceReduce::Sum(
  d_temp, temp_bytes, d_in, d_out, N);

1회차: 필요한 temp 크기 조회. 2회차: 실제 실행.

4 Block primitive

class역할
BlockReduce<T,BS>block sum/min/max
BlockScan<T,BS>in/exclusive scan
BlockRadixSortblock-local sort
BlockLoad/Storecoalesced transfer

5 Warp primitive

  • WarpReduce<T>
  • WarpScan<T>
  • shfl 기반 내부 구현

6 Thrust 대응 맵 ★

ThrustCUB
thrust::reduceDeviceReduce::Sum
thrust::inclusive_scanDeviceScan::InclusiveSum
thrust::sortDeviceRadixSort
thrust::copy_ifDeviceSelect::If
thrust::uniqueDeviceSelect::Unique
thrust::transformkernel (no CUB)

7 선택 기준

  • Thrust: STL-like, prototyping 빠름, iterator 추상
  • CUB: lower level, custom kernel 내부에서 block/warp collective
  • Thrust는 내부적으로 CUB 호출

8 기타 library

lib강점
cuBLASdense GEMM ↗ V06
cuSPARSEsparse ↗ §12
cuRANDPRNG
cuDNNDL primitive
cuSOLVERdense solve
직접 Reduction·Scan 작성 금지가 실전 원칙. 학습은 1회, 운영은 CUB/Thrust.

1 Primitive complexity ★

primitiveworkdepth
ReductionO(N)O(log N)
Scan (BK)O(N)O(log N)
Scan (KS)O(N log N)O(log N)
Radix sortO(N·b)O(b)
Bitonic sortO(N log²N)O(log²N)
Merge pathO(N+M)O(log N)
HistogramO(N)O(log N)
SpMVO(nnz)O(log(row))

2 Warp intrinsic 표

intrinsic용도
shfl_downreduce
shfl_upscan
shfl_xorbutterfly
ballotmask 수집
match_anypeer group
popc(mask)count bit

3 Reduction 단계 1-liner

  1. naive mod
  2. interleaved index
  3. sequential addr
  4. first-add load
  5. unroll last warp
  6. shfl_down warp
  7. multi-block/CUB

4 Scan 4종 (W / D)

algoWD
Hillis-SteeleN log Nlog N
Kogge-StoneN log Nlog N
Brent-Kung2N2 log N
SklanskyN log Nlog N

5 GEMM 분할 선택 ★

shape전략
M,N 큼Data-parallel
K 큼·M,N 작음Split-K
tail imbalanceStream-K
batched 다수Grouped
uneven tilePersistent

6 결정 트리 (primitive)

Task ?
├ scalar out ──► Reduction
├ per-elem out ──► Scan
├ bin count ───► Histogram
├ sorted perm ─► Radix sort
├ k largest ───► Top-K (radix)
├ y = A·x ────► SpMV (merge)
└ mask sel ───► Compact (scan)

7 구현 단계 원칙

  1. 라이브러리(CUB/Thrust) 우선
  2. 안 맞으면 block primitive + custom kernel
  3. hot path만 warp shuffle로 최적화
  4. 결정론 · 수치 오차 검증

8 출처

  • PMPP 4e Ch 10–14
  • CUDA CPG §7 warp-level
  • Cooperative Groups guide (CUDA 11+)
  • CUB docs · Thrust docs
  • Stream-K: Osama et al. 2023
  • Merge-based SpMV: Merrill & Garland 2016
  • Harris: Optimizing Parallel Reduction (NVIDIA)
V05 총정리: WP워라 (Work · Depth · Warp primitive · 이브러리 우선)