GPU Architecture · Code example

01-device-query.cu

⤓ raw .cu (compilable)

Queries the GPU and prints SMs, cores, register file, caches, and memory bandwidth — confirming the lesson numbers on your own hardware — then runs a first vector-add kernel.

// 01-device-query.cu — prove the toolchain works and see Lessons 1–5 on your real GPU.
//
//   Build:  nvcc -arch=sm_120 01-device-query.cu -o devq
//   Run:    ./devq
//
// (sm_120 = Blackwell / compute capability 12.0. nvcc 13.3 also defaults to it.)

#include <cstdio>
#include <cstdlib>
#include <cuda_runtime.h>

// Minimal error check: every CUDA call can fail; never ignore the return code.
#define CHECK(call) do {                                              \
    cudaError_t _e = (call);                                          \
    if (_e != cudaSuccess) {                                          \
      printf("CUDA error at line %d: %s\n", __LINE__,                 \
             cudaGetErrorString(_e));                                 \
      return 1;                                                       \
    }                                                                 \
  } while (0)

// FP32 lanes per SM by compute capability (Lesson 2). Blackwell/Hopper = 128.
static int coresPerSM(int major, int minor) {
  if (major == 12) return 128;                 // Blackwell
  if (major == 9)  return 128;                 // Hopper
  if (major == 8)  return (minor == 0) ? 64 : 128;  // Ampere GA100 vs GA10x/Ada
  return 64;                                   // conservative fallback
}

// A trivial kernel. Each thread computes its unique global index (Lesson 3) and
// does one add. The `if (i < n)` guard matters because the grid rounds up past n.
__global__ void vadd(const float* a, const float* b, float* c, int n) {
  int i = blockIdx.x * blockDim.x + threadIdx.x;   // the single most-used line in CUDA
  if (i < n) c[i] = a[i] + b[i];
}

int main() {
  cudaDeviceProp p;
  CHECK(cudaGetDeviceProperties(&p, 0));

  int cores = coresPerSM(p.major, p.minor);

  // CUDA 13 removed memoryClockRate / memoryBusWidth from cudaDeviceProp;
  // query them via cudaDeviceGetAttribute instead.
  int memClockKHz = 0, busWidth = 0;
  CHECK(cudaDeviceGetAttribute(&memClockKHz, cudaDevAttrMemoryClockRate, 0));
  CHECK(cudaDeviceGetAttribute(&busWidth,   cudaDevAttrGlobalMemoryBusWidth, 0));
  // Standard deviceQuery bandwidth estimate: 2 (DDR) * clock * (bus bytes).
  double bwGBs = 2.0 * memClockKHz * (busWidth / 8) / 1.0e6;

  printf("== %s  (compute capability %d.%d) ==\n\n", p.name, p.major, p.minor);
  printf("  SMs                     : %d\n", p.multiProcessorCount);
  printf("  CUDA cores (estimated)  : %d   (%d SMs x %d lanes)\n",
         p.multiProcessorCount * cores, p.multiProcessorCount, cores);
  printf("  Warp size               : %d threads\n", p.warpSize);
  printf("  Max threads / block     : %d\n", p.maxThreadsPerBlock);
  printf("  Registers / SM          : %d (32-bit)\n", p.regsPerMultiprocessor);
  printf("  Registers / block (max) : %d\n", p.regsPerBlock);
  printf("  Shared mem / SM         : %zu KB\n", p.sharedMemPerMultiprocessor / 1024);
  printf("  Shared mem / block      : %zu KB\n", p.sharedMemPerBlock / 1024);
  printf("  L2 cache                : %d MB\n", p.l2CacheSize / (1024 * 1024));
  printf("  Global memory (DRAM)    : %.1f GB\n", p.totalGlobalMem / 1.0e9);
  printf("  Memory bus width        : %d-bit\n", busWidth);
  printf("  Memory bandwidth (est.) : %.0f GB/s\n", bwGBs);

  // --- Prove kernel execution end to end (host<->global movement + a launch). ---
  const int n = 1 << 20;                       // 1,048,576 elements
  size_t bytes = (size_t)n * sizeof(float);

  float *ha = (float*)malloc(bytes);
  float *hb = (float*)malloc(bytes);
  float *hc = (float*)malloc(bytes);
  for (int i = 0; i < n; ++i) { ha[i] = 1.0f; hb[i] = 2.0f; }

  float *da, *db, *dc;
  CHECK(cudaMalloc(&da, bytes));
  CHECK(cudaMalloc(&db, bytes));
  CHECK(cudaMalloc(&dc, bytes));
  CHECK(cudaMemcpy(da, ha, bytes, cudaMemcpyHostToDevice));   // host -> global
  CHECK(cudaMemcpy(db, hb, bytes, cudaMemcpyHostToDevice));

  int threads = 256;                           // block shape (Lesson 3)
  int blocks  = (n + threads - 1) / threads;   // grid shape, rounded up to cover n
  vadd<<<blocks, threads>>>(da, db, dc, n);
  CHECK(cudaGetLastError());                    // catch a bad launch configuration
  CHECK(cudaDeviceSynchronize());               // wait, and surface any runtime error

  CHECK(cudaMemcpy(hc, dc, bytes, cudaMemcpyDeviceToHost));   // global -> host
  printf("\n  Kernel check: c[0]=%.1f  c[%d]=%.1f   (expected 3.0)\n",
         hc[0], n - 1, hc[n - 1]);

  free(ha); free(hb); free(hc);
  cudaFree(da); cudaFree(db); cudaFree(dc);
  return 0;
}
⌂ Home Glossary →