오늘은 기본적으로 환경설정 하면서, 간단하게 gpu 하드웨어 속성을 확인한다.
#include <iostream>
#include "cuda_runtime.h"
int main()
{
int count;
cudaError_t err = cudaGetDeviceCount(&count);
if (cudaSuccess != err)
{
std::printf("cudaGetDeviceCount failed : %s\n", cudaGetErrorString(err));
return 1;
}
std::printf("Device count: %d\n", count);
for (int i = 0; i < count; i++)
{
cudaDeviceProp prop{};
cudaGetDeviceProperties(&prop, i);
std::printf("---- Device %d ----\n", i);
std::printf("Name: %s\n", prop.name);
std::printf("Compute capability: %d.%d\n", prop.major, prop.minor);
std::printf("Global memory: %.2f GB\n", prop.totalGlobalMem / (1024.0 * 1024.0 * 1024.0));
std::printf("SM count: %d\n", prop.multiProcessorCount);
std::printf("Max threads per block: %d\n", prop.maxThreadsPerBlock);
std::printf("Max threads per SM: %d\n", prop.maxThreadsPerMultiProcessor);
}
return 0;
}
Device count: 1
---- Device 0 ----
Name: NVIDIA GeForce RTX 3060
Compute capability: 8.6
Global memory: 12.00 GB
SM count: 28
Max threads per block: 1024
Max threads per SM: 1536
cudaGetDeviceCount: PC에 붙어있는 nvidia gpu 개수
cudaGetDeviceProperties: 각 gpu의 스펙
gpu 속성(이름, 메모리, SM개수, 블록당 스레드 제한 등)을 담는 구조체임. 그냥,, 팩트라서 받아들이면됨ㅋㅋ
아, 초기화 차이였음... {}를 하면, 값이 전부 0/NULL로 초기화됨..이걸 깜빡하다니,,ㄷㄷ 반성하장.....
gpu 아키텍처 버전을 의미함.
gpu 전체 VRAM 용량이지만, 실제로는 드라이버/컨텍스트/런타임이 조금씩 떼어가서 12GB전체를 사용하진 못해.
VRAM은 Video Random Access Memory의 약자로, gpu에서 사용하는 메모리라고 생각하면 된다. cpu에서 사용하는 memory(RAM)과는 독립적인 다른 영역이다.
-> 뒤에서 좀더 자세히 설명함.
SM의 개수를 의미하는데, SM은 Streaming Multiprocessor 로, gpu안에 있는 연산 블록 수라고 보면됨. gpu에서는 연산이 각 SM단위로 수행되게 되는데, SM이 많을 수록 병렬 처리가 많아진다고 보면됨.
정확히 말하면, block하나가 하나의 SM에 배정돼서 실행되고, 여러 SM에 여러 데이터가 동시에 처리되어서, SM이 많을 수록 병렬 처리 능력이 좋아지는 경향이 있음. 그냥 단편적으로 생각하면, 16개의 block이 있을때, 수용 가능한 SM이 2개가있으면 8번 반복하고, 4개가 있으면 4번 반복하면 되기 때문에 시간은 절반으로 줄어듬.
maxThreadsPerBlock = Block 최대 스레드
: 병렬처리 가능한 집합의 크기 제한
maxThreadsPerMultiProcessor은 = SM 최대 스레드
: 여러 block을 동시에 올려서 메모리 지연을 숨기고 처리량을 뽑는 실행 단위로, 한 SM에서 동시에 상주 가능한 thread의 최대치(occupancy 상한)
thread수가 최대 1024라면, 블럭이 2d일때 32 x 32를 초과할 수 없다는것이다. (이때 block은 행,열 크기가 달라도 상관없다)
warp는 32-lane SIMD처럼 동작하므로, block이 1 thread면 한 warp가 생성되더라도 31 lane이 비활성화되어 연산/메모리 처리량이 크게 낭비된다. 그래서 보통 blockDim은 최소 32의 배수로 잡는다.
block 크기는 보통 128/256/512부터 시작한다. 이유는 warp 단위(32)의 배수라 낭비가 적고, occupancy(활성 warp 수) 확보에 유리하고, 메모리 coalescing에도 유리한 패턴이 많기 때문이다.
SM 내부에서는 warp단위로 스케줄링되면, 매 사이클(gpu 클럭이라 생각하면 됨)마다 실행가능한 warp에 명령을 발행한다. warp scheduler는 여러개 존재해서, 그 스케줄러가 병렬처리 되는 것이다. 여튼, 스케줄러를 통해 계속 switching되어 gpu가 쉴새없이 동작하도록 스케줄링되어 있고, 1536개의 thread를 한번에 처리하는 것이 아니라, 엄청나게 빠른 교대 실행인 것이다!