C.W.K.
Stream
Lesson 04 of 05 · published

Operator 변주 & 용량 zone

~10 min · vectors, operators, vram, capacity

Level 0Beginner
0 XP0/38 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete

커널 한 개, 맛 4가지 — 그리고 사이즈 reality check

Vector add 되면 operator만 바꾸면 sub, mul, div 공짜로 얻어. launch geometry, 메모리 layout, 검증 동일 — ALU op만 바뀜. 근데 비용은 항상 동일 아님:

Op본문 변경비고
AddC[i] = A[i] + B[i]Baseline.
Sub+-Add랑 같은 throughput — single FP unit.
Mul+*FMA pipeline 사용, 같은 throughput.
Div+/5–10× 느림 — non-pipelined reciprocal 때문. NaN/inf 피하려면 B[i] == 0 가드.

VRAM 용량 zone — 너무 많이 요청하면 무슨 일

Zone일어나는 일탈출구
🟢 Green: VRAM에 들어감풀 속도, 데이터 GPU에 resident조치 불필요
🟡 Yellow: UVM pagingUnified Memory가 페이지 migrate — 10–50× 느려짐청크로 처리
🔴 Red: 하드 alloc 실패cudaMalloccudaErrorMemoryAllocation returnSlice-and-stream loop
🟣 Crimson: grid 한계gridDim.x가 2³¹⁻¹ 초과Outer host loop, base-index arg

Apple Silicon은 zone 자체가 옮겨감: 분리 VRAM 없으니까 'Green'이 OS랑 다른 앱이 잡고 있는 거 빼고 unified pool 전체로 확장. Yellow zone (swap에 paging)은 여전히 존재하는데 훨씬 늦게 발동.

Code

Op별 커널 한 개 generic 패턴 (CUDA)·cuda
// Template + functor 또는 preprocessor.
// Functor 버전이 디버거 친화적:

struct AddOp { __device__ float operator()(float a, float b) const { return a + b; } };
struct SubOp { __device__ float operator()(float a, float b) const { return a - b; } };
struct MulOp { __device__ float operator()(float a, float b) const { return a * b; } };
struct DivOp { __device__ float operator()(float a, float b) const {
    return b == 0.f ? 0.f : a / b;   // 가드
} };

template <typename Op>
__global__ void elementwise(const float* A, const float* B, float* C, size_t N, Op op) {
    size_t i = (size_t)blockIdx.x * blockDim.x + threadIdx.x;
    if (i < N) C[i] = op(A[i], B[i]);
}

// 원하는 Op로 launch; nvcc가 호출 inline:
// elementwise<<<blocks, threads>>>(dA, dB, dC, N, MulOp{});
데이터가 VRAM 초과할 때 slice-and-stream·cuda
// VRAM에 들어갈 CHUNK 크기로 N 원소 처리.
const size_t CHUNK = 1 << 24;        // 16M 원소 ≈ buffer당 64 MB
for (size_t off = 0; off < N; off += CHUNK) {
    size_t this_n = std::min(CHUNK, N - off);

    cudaMemcpyAsync(dA, hA + off, this_n*sizeof(float),
                    cudaMemcpyHostToDevice, stream);
    cudaMemcpyAsync(dB, hB + off, this_n*sizeof(float),
                    cudaMemcpyHostToDevice, stream);

    int threads = 256;
    int blocks  = (this_n + threads - 1) / threads;
    vec_add<<<blocks, threads, 0, stream>>>(dA, dB, dC, this_n);

    cudaMemcpyAsync(hC + off, dC, this_n*sizeof(float),
                    cudaMemcpyDeviceToHost, stream);
}
cudaStreamSynchronize(stream);

External links

Exercise

같은 64M-원소 벡터에 operator 4개 (add, sub, mul, div) 다 timing. add ≈ sub ≈ mul, div가 눈에 띄게 느려야 함 (GPU 세대에 따라 3–7× 자주). 그 다음 VRAM의 2× 크기 vector size 골라서 커널이 alloc error로 crash (CUDA)하거나 paging으로 기어가는 (Apple Silicon) 거 봐. Slice-and-stream 버전 구현해서 in-VRAM 케이스랑 같은 원소당 속도 나오는지 확인.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.