[Multi-Core & GPU Programming] CUDA - Stream

"CUDA - Steam"

[Pinned Memory]

▣ CPU-GPU 구조
· GPU Kernel이 아무리 빨라도 데이터를 CPU에서 GPU로 옮기고, 다시 GPU에서 CPU로 옮기는 시간↑

▣ Memcpy Time
> 8192 x 8192 Transpose
· Host Memory → Device Memory 복사 : 44ms
· Kernel 실행 : 2.3ms
· Device Memory → Host Memory 복사 : 44ms
→ Memory 복사 시간 > Kernel 실행 시간

> 2048 x 2048 Matmul
· Host Memory → Device Memory 복사 : 5ms
· Kernel 실행 : 25ms
· Device Memory → Host Memory 복사 : 2.5ms
→ Memory 복사 시간 < Kernel 실행 시간

▣ Memcpy
· CPU가 직접 모든 데이터를 GPU로 복사 X
· DMA(Direct Memory Access)가 Host Memory와 Device Memory 사이 데이터를 전송

** 현실에서는 Host Memory의 일반 Memory(Pageable Memory)에서 바로 DMA를 수행하기 어려움
∵ OS가 Memory Page를 Disk로 Swap Out 할 수 있는데, DMA는 CPU처럼 Page Table을 자유롭게 해석하면서 추적 불가
∴ CPU가 먼저 데이터를 OS가 물리 Memory에 고정해 둔 영역인 Pinned Memory Region으로 복사
→ DMA가 Pinned memory에서 GPU Device Memory로 데이터를 복사

** Pinned Memory가 항상 좋은 것은 아니기 때문에 처음부터 모든 데이터를 Pinned Memory에 두면 안됨
· Pinned Memory는 OS가 해당 Memory를 Disk로 내보내지 못하게 고정
→ 너무 많이 사용하면 OS의 Memory 관리가 어려워지고, 전체 시스템 성능 저하
∴ 큰 데이터 전송이 자주 필요한 경우에만 선택적으로 사용하는 것이 좋음

▣ Pinned Memory 사용
> cudaMallocHost(void** ptr, size_t size)
· 처음부터 Pinned Host Memory를 할당

> cudaHostRegister(void* ptr, size_t size, unsigned int flags)
· 일반 Malloc으로 할당한 Memory를 나중에 CUDA에 등록해서 Pinned Memory처럼 사용

→ 새로 담당하는 경우 : "cudaMallocHost" 사용 / 이미 있는 Host Pointer를 등록하는 경우 : "cudaHost Register" 사용

▶ Memcpy Time With Pinned Memory
> 8192 x 8192 Transpose
· Host Memory → Device Memory 복사 : 44ms ▶ 20ms
· Kernel 실행 : 2.3ms
· Device Memory → Host Memory 복사 : 44ms ▶ 20ms

> 2048 x 2048 Matmul
· Host Memory → Device Memory 복사 : 5ms ▶ 2.5ms
· Kernel 실행 : 25ms
· Device Memory → Host Memory 복사 : 2.5ms ▶ 1.3ms

> 복사 Bandwidth
· 6.5GB/s ▶ 13GB/s

** Pinned Memory는 Kernel 자체를 빠르게 하는 것이 아니라, Host와 Device 사이 데이터 전송을 빠르게 하는 방법

[Stream]

▣ Pipeline
· Memcpy : 주로 PCI-e/NVLINK와 Copy Engine을 사용
· Kernel : GPU의 SM 연산 자원을 사용
→ Memcpy와 Kernel은 서로 다른 자원 사용하므로 Pipeline 가능

▣ CUDA Stream
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cudaStream_t stream1, stream2, stream3, stream4;
 
cudaStreamCreate(&stream1);
cudaStreamCreate(&stream2);
cudaStreamCreate(&stream3);
cudaStreamCreate(&stream4);
 
cudaMemcpyAsync(d_M1.elements, M1.elements, matSize, cudaMemcpyHostToDevice, stream1);
cudaMemcpyAsync(d_M2.elements, M2.elements, matSize, cudaMemcpyHostToDevice, stream2);
cudaMemcpyAsync(d_M3.elements, M3.elements, matSize, cudaMemcpyHostToDevice, stream3);
cudaMemcpyAsync(d_M4.elements, M4.elements, matSize, cudaMemcpyHostToDevice, stream4);
 
MatMulKernel<<<dimGrid, dimBlock, 0, stream1>>>(d_M1.elements, …
MatMulKernel<<<dimGrid, dimBlock, 0, stream2>>>(d_M2.elements, …
MatMulKernel<<<dimGrid, dimBlock, 0, stream3>>>(d_M3.elements, …
MatMulKernel<<<dimGrid, dimBlock, 0, stream4>>>(d_M4.elements, …
 
cudaMemcpyAsync(P1.elements, d_P1.elements, matSize, cudaMemcpyDeviceToHost, stream1);
cudaMemcpyAsync(P2.elements, d_P2.elements, matSize, cudaMemcpyDeviceToHost, stream2);
cudaMemcpyAsync(P3.elements, d_P3.elements, matSize, cudaMemcpyDeviceToHost, stream3);
cudaMemcpyAsync(P4.elements, d_P4.elements, matSize, cudaMemcpyDeviceToHost, stream4);
cs

1 : 여러 개의 Stream 생성
3~6 : 초기화
8~11 : 각 Stream에 Host Memory 값을 Device Memory에 복사
    ** CPU가 Memory 복사 완료를 기다리지 않고 바로 다음 코드를 실행할 수 있도록 하여 동시 복사 허용
        → cudaMemcpy는 동기식으로 복사가 끝날 때까지 CPU가 대기
    ** "cudaMemcpyAsync"로 실제 비동기 복사를 하려면 Host Memory가 Pinned Memory여야 함
13~16 : 각 Stream에 Kernel Launch 할당
18~21 : 각 Stream에 Device Memory 값을 Host Memory에 복사

· 서로 다른 Stream의 순서 중요 X / 같은 Stream 내의 내부 순서 중요 O
· 같은 Stream 안의 작업은 직렬로 진행 / 서로 다른 Stream 작업은 병렬로 진행
· Stream을 지정하지 않은 CUDA 호출은 Default Stream에 할당
    → Default Stream을 섞어 쓰면 의도치 않게 동기화가 발생할 수 있으므로 주의 필요

> "cudaStreamSynchronize(stream1);" : Stream 1에 들어간 모든 작업이 끝날 때까지 CPU가 대기
> "cudaDeviceSynchronize( );" : 모든 Stream의 모든 작업이 끝날 때까지 대기
> NVVP(NVIDIA Visual Profiler) : 작성한 Code가 실제 HW에서 어떻게 실행되는지 확인 가능한 NVIDIA의 시각화 Profiling 도구

** Stream을 사용하면 한 데이터의 Kernel 실행 중에 다른 데이터의 복사 작업을 수행 가능
    → GPU 연산 자원과 Copy Engine을 동시에 활용해서 전체 실행 시간 감소 가능

· Kernel : 순서 상관 X(1 → 2 → 3 / 1 → 3 → 2)
· Stream 1에서 D ▶ H에 의한 DMA 사용 → Resource Conflict → Stream 1의 DMA 사용 종료 후 Stream 4의 Copy 진행
    → Stream 4가 먼저 H ▶ D에 의한 DMA 사용이 가능하며 이 경우 Stream 4의 DMA 사용 종료 후 Stream 1의 Copy 진행

[Reference]

· 12_cuda_stream_mgp_2026 (MGP) - Yongjun Park

댓글

이 블로그의 인기 게시물

[Mini-NPU RTL] NN Reference Model

[Mini-NPU RTL] TPU (Study Paper)