| 항목 | CPU (latency-oriented) | GPU (throughput-oriented) |
|---|---|---|
| 목표 | 단일 thread 지연 ↓ | 총 throughput ↑ |
| ALU | 소수·대형·저지연 | 다수·소형·장지연 허용 |
| 캐시 | 큰 L1/L2/L3 | 작음 (대역폭 관리용) |
| 제어 | 분기예측·OoO 복잡 | 단순 in-order pipeline |
| 메모리 | legacy 일관성 | relaxed, ~10× 대역폭 |
| 쓰레드 | 수~수십 | 수만 |
| 지표 | A100 | CPU(24-c) |
|---|---|---|
| FP64 peak | 9.7 TF | 0.33 TF |
| FP32 peak | 156 TF | 0.66 TF |
| FP16 peak | 312 TF | — |
| 대역폭 | ~1.5 TB/s | ~0.2 TB/s |
cf. H100 FP64 ~34 TF (SM 수↑, TMA, async copy)
| 모델 | 범위 | 특징 |
|---|---|---|
| CUDA | single node GPU | NVIDIA 전용 · explicit control · C/C++ ext |
| OpenCL | 여러 vendor | CUDA와 개념 유사 · API 위주 |
| OpenMP | shared-mem multi-core | pragma 기반 · GPU 확장 중 |
| MPI | cluster, 100k+ node | message passing · no shared mem |
HPC 실무: MPI + CUDA 혼합 (multi-GPU · NCCL) — Ch20
cudaMalloc(&d_A, size)cudaMemcpy(d_A, h_A, n, H2D)kernel<<<grid, block>>>(...)cudaMemcpy(h_A, d_A, n, D2H)cudaFree(d_A)| 한정자 | 호출 | 실행 |
|---|---|---|
__host__ | host | host |
__global__ | host(&dyn.) | device |
__device__ | device | device |
__global__ 반드시 void return
__global__ void vecAdd(float* A, float* B, float* C, int n){ int i = blockIdx.x*blockDim.x + threadIdx.x; if(i < n) C[i] = A[i] + B[i]; }
// host launch int T=256, B=(n+T-1)/T; vecAdd<<<B,T>>>(d_A,d_B,d_C,n);
if(i<n) boundary check 필수 — n이 blockDim 배수가 아니면 OOB.
__syncthreads(), shared mem OKgrid[ block(0,0) block(1,0) ... ]
└─ block[ t(0,0) t(1,0) ... t(31,0) ← warp
t(0,1) ... ]
| 변수 | 의미 |
|---|---|
gridDim.{x,y,z} | grid 크기 (block 수) |
blockDim.{x,y,z} | block 크기 (thread 수) |
blockIdx.{x,y,z} | block의 grid 내 좌표 |
threadIdx.{x,y,z} | thread의 block 내 좌표 |
cudaError_t e = cudaMalloc(&d,n); if(e != cudaSuccess){ printf("%s\n",cudaGetErrorString(e)); exit(-1); }
실무 매크로: CUDA_CHECK(x) 래퍼 권장
cudaMallocManaged: UM, page fault로 자동 migratecudaMallocHost: pinned host mem → H↔D 대역폭 ↑cudaStream_t: 비동기 복사/커널 겹침 (H2D || kernel || D2H)cudaDeviceSynchronize()cudaFreecudaMemcpy 방향 플래그 혼동
(cudaMemcpyHostToDevice vs DeviceToHost).
dim3 block(16,16); dim3 grid((W+15)/16,(H+15)/16); kernel<<<grid,block>>>(...);
.x는 col(너비축), .y는 row(높이축).
__global__ void rgb2gray(uchar* out, uchar* in, int W, int H){ int c = blockIdx.x*blockDim.x + threadIdx.x; int r = blockIdx.y*blockDim.y + threadIdx.y; if(r<H && c<W){ int i = r*W + c; uchar* p = &in[3*i]; out[i] = 0.21f*p[0]+0.72f*p[1]+0.07f*p[2]; } }
__global__ void matMul(float* C,float* A,float* B,int N){ int r = blockIdx.y*blockDim.y+threadIdx.y; int c = blockIdx.x*blockDim.x+threadIdx.x; if(r<N && c<N){ float s=0; for(int k=0;k<N;++k) s += A[r*N+k]*B[k*N+c]; C[r*N+c]=s; } }
dim3 blk(8,8,8); dim3 grd((W+7)/8,(H+7)/8,(D+7)/8); i = z*(H*W) + y*W + x;
MRI·CT·유체 시뮬에 사용
GPU
└─ SM (Streaming Multiprocessor) × N
├─ warp scheduler × 2~4
├─ SP/CUDA core × 64~128
├─ Tensor Core × 4
├─ Register File (64K × 32b)
├─ Shared Mem / L1 (128~192 KB)
└─ LD/ST, SFU
| 칩 | SM | CUDA core |
|---|---|---|
| A100 | 108 | 6912 |
| H100 | 132 | 16896 |
| 항목 | SIMD | SIMT |
|---|---|---|
| 레인 | 벡터 폭 고정 | thread 마다 ID |
| 분기 | 마스크 프로그램 | HW가 자동 |
| 메모리 | aligned gather | thread별 주소 |
| 모델 | data | SPMD |
if(tid % 2 == 0) // warp 내부 ½ mask even_work(); else odd_work(); // 두 경로 직렬화
if(tid/32 ...) → divergence 없음.
y = c ? a : b → y = a*c + b*(1-c)cycle: w0┐ w1┐ w2┐ w0┐
│ │ │ │
issue issue issue issue
(scoreboard-free warps rotate)
atomicAdd / Max / CAS 등| 자원 | A100 /SM | 영향 |
|---|---|---|
| Blocks | 32 | block 수 상한 |
| Threads | 2048 | warp 수 상한 |
| Regs | 65536 | thread당 레지스터 |
| Smem | 164 KB | block당 shared |
-Xptxas -v로 레지스터/smem 확인__launch_bounds__로 컴파일러 힌트__threadfence(): 메모리 쓰기 가시성 보장volatile: 레지스터 캐시 금지 (polling시)__syncwarp() 필수| 종류 | 범위 | 속도 | 크기 | 선언 |
|---|---|---|---|---|
| Global | grid·host | ~400 cyc | 40~80 GB | __device__ |
| Constant | device RO | L1 캐시 | 64 KB | __constant__ |
| Local | thread | Global 동일 | thread별 | auto (spill) |
| Shared | block | ~30 cyc | 164 KB/SM | __shared__ |
| Register | thread | 1 cyc | 65K × 4B/SM | auto |
| Texture | grid RO | spatial cache | — | tex API |
__global__ void k(...){ __shared__ float s[256]; int t = threadIdx.x; s[t] = d_in[...]; // load __syncthreads(); // 여러 thread가 s[] 공유 사용 }
extern __shared__ float s[];__constant__ float F[25]; cudaMemcpyToSymbol(F, h_F, sizeof(F));
__launch_bounds__(T, minBlocks)로 상한 지시GFLOPS
▲ _______ peak compute
│ /
│ / BW·AI line
│ /
└─────────────────▶ AI (FLOP/B)
↑knee = peak/BW
AI < knee → memory-bound · AI ≥ knee → compute-bound
__syncthreads()__syncthreads() · 다음 타일#define T 16 __global__ void mm(float* C,float* A,float* B,int N){ __shared__ float As[T][T], Bs[T][T]; int bx=blockIdx.x, by=blockIdx.y; int tx=threadIdx.x, ty=threadIdx.y; int r=by*T+ty, c=bx*T+tx; float s=0; for(int ph=0; ph<N/T; ++ph){ As[ty][tx]=A[r*N + ph*T+tx]; // coalesced Bs[ty][tx]=B[(ph*T+ty)*N + c]; __syncthreads(); for(int k=0;k<T;++k) s += As[ty][k]*Bs[k][tx]; __syncthreads(); } C[r*N+c]=s; }
| TILE | smem/block | CGMA | Occupancy |
|---|---|---|---|
| 8 | 0.5 KB | 8 | 높음 |
| 16 | 2 KB | 16 | 좋음 |
| 32 | 8 KB | 32 | 낮음 |
0.0f 로드 (padding)if(r<N && c<N) 검사if(r<N && ph*T+tx<N) As[ty][tx]=A[r*N+ph*T+tx]; else As[ty][tx]=0.0f;
sync·계산·sync)
__syncthreads() 빼면
다음 phase에서 아직 쓰이는 As/Bs를 덮어써 race condition.
✓ coalesced: t0→a[0], t1→a[1], ... t31→a[31] ✗ strided: t0→a[0], t1→a[N], ... (N transactions) ✗ random: t0→a[?] (최대 32 trans)
| 접근 | row-major A[r][c] | 결과 |
|---|---|---|
| tid→c | A[r][tid] | ✓ coalesced |
| tid→r | A[tid][c] | ✗ strided |
s[tid] → 서로 다른 bank, OK s[2*tid] → 2-way conflict (stride 2)s[32*tid] → 32-way (완전 serialize)
nvprof (구) / Nsight Compute: kernel 프로파일cuda-memcheck: OOB, race__syncthreads() 분기 안쪽__global__ void conv1d(float* y,float* x,float* F,int N,int R){ int i=blockIdx.x*blockDim.x+threadIdx.x; if(i>=N) return; float s=0; for(int k=-R;k<=R;++k){ int j=i+k; if(j>=0 && j<N) s += x[j]*F[k+R]; } y[i]=s; }
┌──────────── tile+halo ────────────┐ │ halo ▒▒▒▒▒▒▒ 내부 tile ▒▒▒▒▒▒▒ │ │ ↑R cells R cells↑│ └──────────────────────────────────┘ smem 크기 = (TILE + 2R)
__syncthreads() 뒤 계산.
z0 slab → z1 slab (register reuse) ┌──────┐ │ +R │ smem: xy-plane │ core │ reg : z-1, z, z+1 │ +R │ └──────┘
__global__ void histNaive(int* h,uchar* x,int N){ int i=blockIdx.x*blockDim.x+threadIdx.x; if(i<N) atomicAdd(&h[x[i]], 1); }
__syncthreads()__global__ void histP(int* h,uchar* x,int N,int B){ __shared__ int s[256]; for(int t=threadIdx.x;t<B;t+=blockDim.x) s[t]=0; __syncthreads(); for(int i=blockIdx.x*blockDim.x+threadIdx.x; i<N; i+=gridDim.x*blockDim.x) atomicAdd(&s[x[i]], 1); __syncthreads(); for(int t=threadIdx.x;t<B;t+=blockDim.x) atomicAdd(&h[t], s[t]); }
__shared__ float s[BS]; s[tid]=input[i]; __syncthreads(); for(int st=1; st<blockDim.x; st*=2){ if(tid % (2*st)==0) // ❌ divergence ↑ s[tid] += s[tid+st]; __syncthreads(); }
for(int st=blockDim.x/2; st>0; st>>=1){ if(tid < st) s[tid] += s[tid+st]; // ✓ warp 경계 uniform __syncthreads(); }
s[tid] = in[i] + in[i+BS] (2개 합산 후 저장)// 마지막 warp (tid<32) unroll
if(tid<32){
v = s[tid] + s[tid+32];
for(int o=16; o>0; o/=2)
v += __shfl_down_sync(0xffffffff, v, o);
if(tid==0) out[blk] = v;
}
__shfl_down_sync: shared mem 없이 warp 내 교환 → 레지스터만 사용.
for(int o=1; o<BS; o*=2){ float v=0; if(tid>=o) v=s[tid-o]; __syncthreads(); if(tid>=o) s[tid]+=v; __syncthreads(); }
depth log N · 병렬 ↑ · 중복 작업 많음
┌ blk0 ┐┌ blk1 ┐┌ blk2 ┐ local local local Σ0 Σ1 Σ2 └──── scan aux ───┘ +0 +Σ0 +Σ0+Σ1
| 알고리즘 | work | depth | 비고 |
|---|---|---|---|
| Sequential | N | N | 기준 |
| Kogge-Stone | N log N | log N | 단순 |
| Brent-Kung | 2N | 2 log N | 권장 |
| 방법 | 특징 |
|---|---|
| Merge sort | 병합 기반 · 위 Ch12 활용 |
| Radix sort | bit별 scan + scatter · O(N·k) |
| Bitonic | 네트워크형 · 고정 크기 |
실무: cub::DeviceRadixSort · thrust::sort
| format | 저장 | 장단 |
|---|---|---|
| COO | (row,col,val) | 단순·랜덤 어려움 |
| CSR | row_ptr, col_idx, val | 표준 · SpMV 좋음 |
| ELL | 고정 폭 padded | 균일 row에 유리 |
| JDS / Hybrid | 정렬·혼합 | load balance ↑ |
| 항목 | OK? |
|---|---|
| warp 정렬된 분기 (divergence ↓) | □ |
| Coalesced global access | □ |
| Shared mem으로 재사용 | □ |
| Bank conflict 회피 (padding) | □ |
| occupancy 50%+ · 레지스터 과다 X | □ |
| atomic은 smem에서 먼저 | □ |
| boundary if 빠짐없음 | □ |
__syncthreads uniform한 위치 | □ |
| kernel err / memcheck 통과 | □ |
공통 변환: tile + shared + coalesce + atomic 최소화 + scan 기반
CUB·thrust·cuBLAS·cuSPARSE
먼저 고려. 직접 구현은 특수 케이스만.
row_ptr[V+1], col_idx[E].col_idx[row_ptr[v] .. row_ptr[v+1])
V=5, E=7 row_ptr: [0,2,3,5,6,7] col_idx: [1,2, 3, 0,4, 4, 2]
Ch14의 SpMV와 동일 구조 → Scan으로 빌드 (Ch11 재활용)
__global__ void bfsV(int* rp,int* ci,int* lvl,int L,int* done){ int v=blockIdx.x*blockDim.x+threadIdx.x; if(lvl[v]==L){ for(int k=rp[v]; k<rp[v+1]; ++k){ int u=ci[k]; if(lvl[u]==INF){ lvl[u]=L+1; *done=0; } } } }
mini-batch = data-parallel 병렬 축 ★
gemm 호출이 실무 표준5중 loop → naive 커널은 CGMA 낮음. 재구성 필요.
Conv = (unfold X) × (reshape W) X_col: [K·Kh·Kw, N·Oh·Ow] W_row: [M, K·Kh·Kw] Y : W_row · X_col → GEMM!
| 방법 | 유리 조건 |
|---|---|
| Direct | 큰 채널 + 작은 spatial |
| im2col+GEMM | 범용, 안정 |
| Winograd | 3×3 필터, F(2,3)/F(4,3) |
| FFT | 큰 필터 (≥7×7) |
cuDNN findAlgorithm이 자동 벤치
직접 커널: fused activation/norm 정도. 핵심 GEMM/Conv는 벤더 라이브러리.
Node0 [GPU0 GPU1 ...] ─┐ Node1 [GPU0 GPU1 ...] ─┼─ IB / NVLink Switch ... │ └─ MPI_COMM_WORLD ───┘
| 호출 | 의미 |
|---|---|
MPI_Init | 초기화 |
MPI_Comm_rank | 내 프로세스 ID |
MPI_Comm_size | 총 프로세스 수 |
MPI_Send/Recv | P2P (blocking) |
MPI_Isend/Irecv | non-blocking + Wait |
| MPI_Allreduce | 모든 rank가 reduce 결과 |
MPI_Bcast | rank0 → all |
MPI_Barrier | 동기화 |
┌──────┬──────┬──────┐ │rank0 │rank1 │rank2 │ │ ▒▒▒│▒▒▒▒▒▒│▒▒▒ │ ← halo 교환 └──────┴──────┴──────┘
for(step){ MPI_Isend(halo_right, right_rank); // 비동기 MPI_Irecv(halo_left, left_rank); compute_interior<<<>>>(...); // 통신과 겹침 MPI_Waitall(...); compute_boundary<<<>>>(...); }
MPI_Send에 직접 전달 OKPyTorch DDP, Horovod 내부 엔진
| 종류 | 고정 | 확장 |
|---|---|---|
| Strong | 총 문제 크기 | rank ↑ → time ↓ |
| Weak | rank당 크기 | rank ↑ → 크기 ↑ |
Amdahl (Ch1) ↔ Gustafson 대응