GPUFlightAll performance labs

Asynchronous Prefetch: Double-Buffer Shared Tiles

Experimental — correctness is graded; the performance target awaits GPU calibration. The starter waits for each global-to-shared tile load before doing any arithmetic. Build a two-stage cuda::pipeline so tile n+1 moves into the unused shared buffer while the block computes tile n.

Sign in to run this lab. GPU availability and credits are shown in the app.

Languages: CUDA C++, Rust

Lesson

On compute capability 8.0 and newer, aligned global-to-shared cuda::memcpy_async operations can use hardware asynchronous copies that avoid an intermediate register. The performance benefit comes from overlap: issuing an async copy and immediately waiting for it is still serial execution.

SM global-to-shared asynchronous copies differ from host-device DMA engines. Synchronize producer/consumer stages and validate tails before interpreting overlap.

What you will learn

  • Create a thread-scoped pipeline: each thread tracks only its own copies, so no shared pipeline state is needed.
  • Prime the pipeline before entering the consume loop.
  • Prefetch the next tile into the opposite shared buffer while computing the current tile.
  • Pair producer and consumer operations without reusing a live stage.
  • Keep every block thread converged around pipeline waits and block synchronization.

Starter code

    __shared__ alignas(128) float tile[kAsyncTileElements];
    int blockBase = static_cast<int>(blockIdx.x) * kAsyncElementsPerBlock;

    for (int stage = 0; stage < kAsyncTilesPerBlock; ++stage) {
        int tileBase = blockBase + stage * kAsyncTileElements;
        for (int i = static_cast<int>(threadIdx.x); i < kAsyncTileElements; i += blockDim.x) {
            tile[i] = input[tileBase + i];
        }
        __syncthreads();
        for (int i = static_cast<int>(threadIdx.x); i < kAsyncTileElements; i += blockDim.x) {
            output[tileBase + i] = asyncTransform(tile[i]);
        }
        __syncthreads();
    }

Sign in to run