04 > Built-in Variables
Every kernel has five read-only built-in variables, gridDim, blockDim, blockIdx, threadIdx, and warpSize. You do not pass or declare them. The GPU fills them in for each thread at launch, based on the launch configuration. This lesson prints all five from every thread, so you can see which ones change and which stay the same.
[!NOTE] All outputs on this page come from an NVIDIA L40S with CUDA 13.0 on Ubuntu 24.
gridDim
gridDim holds the number of blocks in each direction. With <<<2, 4>>>, gridDim.x is 2, and gridDim.y and gridDim.z are 1. The grid size is fixed at launch, so every thread sees the same gridDim.
blockDim
blockDim holds the number of threads per block in each direction. With <<<2, 4>>>, blockDim.x is 4, and blockDim.y and blockDim.z are 1. Every thread sees the same blockDim.
The global ID formula from Lesson 02, blockIdx.x * blockDim.x + threadIdx.x, uses it. With <<<2, 4>>>, thread 3 in block 1 gets 1 * 4 + 3 = 7, the last of the 8 threads.
blockIdx
blockIdx is the index of the thread's block. With 2 blocks, blockIdx.x is 0 for all threads in block 0 and 1 for all threads in block 1. It is always less than gridDim.x.
threadIdx
threadIdx is the index of the thread inside its block. It restarts at 0 in every block. In a block of 4 threads, threadIdx.x is 0, 1, 2, 3. It is always less than blockDim.x.
gridDim, blockDim, blockIdx, and threadIdx all have .x, .y, .z fields. gridDim and blockDim are of type dim3. If you write <<<2, 4>>> with plain numbers, CUDA sets .y = 1 and .z = 1 for you. So <<<2, 4>>> is the same as <<<dim3(2, 1, 1), dim3(4, 1, 1)>>>.
warpSize
warpSize is the number of threads per warp. It is 32 on every NVIDIA GPU so far. CUDA gives it to you as a variable, so your code does not have to write the number 32 by hand.
[!TIP] Writing 32 works today. Reading
warpSizekeeps your code correct even if a future GPU uses another size.
Hardware Limits
Before running a kernel, the CUDA runtime checks the launch configuration against hardware limits. If any value is too large, the kernel does not launch. These are the limits for compute capability 3.0 and later, from Kepler to Blackwell.
| Variable | Dimension | Max value |
|---|---|---|
gridDim.x |
blocks in x | 2^31 - 1 |
gridDim.y |
blocks in y | 65535 |
gridDim.z |
blocks in z | 65535 |
blockDim.x |
threads in x | 1024 |
blockDim.y |
threads in y | 1024 |
blockDim.z |
threads in z | 64 |
| threads/block | total | 1024 |
blockDim.x * blockDim.y * blockDim.z must not be more than 1024, even if each single value is within its limit. This is the same 1024 threads-per-block limit from Lesson 02. Here are two examples.
dim3(16, 16, 4)has every value within its limit, and 16 x 16 x 4 = 1024 threads. Valid.dim3(32, 32, 2)has every value within its limit, but 32 x 32 x 2 = 2048 threads. Invalid, the kernel does not run.
[!WARNING] A launch that breaks a limit compiles and runs without any message, but the kernel never starts. Check
cudaGetLastError()after the launch, as Lesson 08 does.
Enter your own block and grid sizes to see if the launch is valid.
Code
This program launches one kernel that prints all five built-in variables from every thread, so you can see which values change and which stay the same.
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
__global__ void printBuiltins()
{
printf("\ngridDim=(%d,%d,%d) blockDim=(%d,%d,%d) blockIdx=(%d,%d,%d) threadIdx=(%d,%d,%d) warpSize=%d",
gridDim.x, gridDim.y, gridDim.z,
blockDim.x, blockDim.y, blockDim.z,
blockIdx.x, blockIdx.y, blockIdx.z,
threadIdx.x, threadIdx.y, threadIdx.z,
warpSize);
}
int main()
{
printBuiltins<<<2, 4>>>();
cudaDeviceSynchronize();
return 0;
}
- The two CUDA headers declare the runtime functions (such as
cudaDeviceSynchronize) and the built-in variables.stdio.hprovidesprintf. __global__marksprintBuiltinsas a kernel. It runs on the GPU and is launched from the CPU.- The
printfinside the kernel runs once per thread. Each%dis filled with one field, in the order listed below the format string. printBuiltins<<<2, 4>>>()launches 2 blocks of 4 threads, so 8 threads run the kernel and print 8 lines.cudaDeviceSynchronize()makes the CPU wait until the kernel is done. A kernel launch returns right away, so without this waitmaincould end before the GPU output appears.
Code Walkthrough
Step through the program in the order you would write it. The work is in the long printf, which takes one format string and then one value for each %d.
1-3 cpuHeaders. The same three#includelines as in the earlier lessons. The built-in variables need no header withnvcc, butdevice_launch_parameters.hlets some editors know them too.5-6,13 gpuThe empty kernel. Write__global__ void printBuiltins()and its braces. The kernel takes no arguments, because everything it prints is a built-in variable that the GPU fills in for each thread.7 gpuThe format string. Write the text with 13%dplaceholders, 3 for each of the four variables with.x,.yand.z, and 1 forwarpSize. Start with\nso each thread's output goes on its own line. End the line with a comma, because the values follow.8-12 gpuThe values. List the 13 values in the same order as the placeholders, one variable per line so the order is easy to check. The rule is one value per%d, in order. A missing value can still compile, and thenprintfprints wrong numbers, so count both sides.15-16,19-20 cpuThe main function. Writemainwithreturn 0;at the end. It is the same frame as in the earlier lessons.17 cpuThe launch.<<<2, 4>>>setsgridDim.xto 2 andblockDim.xto 4. Plain numbers leave the.yand.zsizes at 1.18 cpuWait for the GPU.cudaDeviceSynchronize();keeps the program alive until all 8 lines are printed. Without it,maincan end before the GPU output appears.
Compile and Run
The first command compiles the code into a program. The second command runs it.
nvcc -o first_kernel first_kernel.cu
./first_kernel
nvccis the CUDA compiler.-o first_kernelnames the programfirst_kernel. Without it the name isa.out.first_kernel.cuis the source file with the code above../first_kernelruns the program from the current folder.
Output
The program prints 8 lines, one per thread.
gridDim=(2,1,1) blockDim=(4,1,1) blockIdx=(1,0,0) threadIdx=(0,0,0) warpSize=32
gridDim=(2,1,1) blockDim=(4,1,1) blockIdx=(1,0,0) threadIdx=(1,0,0) warpSize=32
gridDim=(2,1,1) blockDim=(4,1,1) blockIdx=(1,0,0) threadIdx=(2,0,0) warpSize=32
gridDim=(2,1,1) blockDim=(4,1,1) blockIdx=(1,0,0) threadIdx=(3,0,0) warpSize=32
gridDim=(2,1,1) blockDim=(4,1,1) blockIdx=(0,0,0) threadIdx=(0,0,0) warpSize=32
gridDim=(2,1,1) blockDim=(4,1,1) blockIdx=(0,0,0) threadIdx=(1,0,0) warpSize=32
gridDim=(2,1,1) blockDim=(4,1,1) blockIdx=(0,0,0) threadIdx=(2,0,0) warpSize=32
gridDim=(2,1,1) blockDim=(4,1,1) blockIdx=(0,0,0) threadIdx=(3,0,0) warpSize=32
gridDimandblockDimare the same on every line, because the launch configuration is the same for all threads.blockIdxchanges per block.threadIdxchanges per thread and restarts at 0 in the second block.- The
.yand.zsizes are 1 and the.yand.zindices are 0, because<<<2, 4>>>used plain numbers. warpSizeis always 32.- Block 1 printed before block 0 here. The GPU runs blocks independently and in no fixed order, so the order of blocks, and of threads inside each block, can change between runs.
Write It Yourself
Read the built-in variables to work out the size of a launch, and pass the sizes as dim3 values.
- Create
launch_size.cuwith the skeleton below. - Let only thread 0 of block 0 print, so the line appears once.
- Print the number of blocks, the threads per block, the total thread count and the warp size.
- Launch with
dim3 grid(3)anddim3 block(64).
#include "cuda_runtime.h"
#include <stdio.h>
__global__ void launchSize()
{
// TODO: only the first thread of the first block prints
// TODO: print blocks, threads per block, total threads and warp size
}
int main()
{
// TODO: make a dim3 grid of 3 blocks and a dim3 block of 64 threads
// TODO: launch launchSize with them
cudaDeviceSynchronize();
return 0;
}
Hint
Check blockIdx.x == 0 && threadIdx.x == 0. The total thread count is gridDim.x * blockDim.x. A dim3 goes into the launch like a number, as in <<<grid, block>>>.
Solution
#include "cuda_runtime.h"
#include <stdio.h>
__global__ void launchSize()
{
if (blockIdx.x == 0 && threadIdx.x == 0) {
printf("blocks: %d, threads per block: %d, total threads: %d, warp size: %d\n",
gridDim.x, blockDim.x, gridDim.x * blockDim.x, warpSize);
}
}
int main()
{
dim3 grid(3);
dim3 block(64);
launchSize<<<grid, block>>>();
cudaDeviceSynchronize();
return 0;
}
Compile and run it with nvcc -o launch_size launch_size.cu and ./launch_size. You should see one line, blocks: 3, threads per block: 64, total threads: 192, warp size: 32.
Glossary
- GPU (Graphics Processing Unit): the processor that runs kernels.
- CPU (Central Processing Unit): the main processor that runs
main()and launches kernels. - CUDA (Compute Unified Device Architecture): NVIDIA's platform and language extensions for writing programs that run on the GPU.
- compute capability (CC): the version number of a GPU generation. It sets the limits in the table above (Lesson 03).
gridDim: number of blocks in each direction (x, y, z). Same for every thread in the launch.blockDim: number of threads per block in each direction. Same for every thread in the launch.blockIdx: index of the thread's block. Always less thangridDimin each direction.threadIdx: index of the thread inside its block. Restarts at zero in every block.warpSize: number of threads per warp. 32 on all current hardware.dim3: a CUDA struct with.x,.y,.zinteger fields, used for grid and block sizes. Plain numbers in<<<>>>become adim3with.y=1and.z=1.- launch configuration: the
<<<blocks, threads>>>part of a launch. The GPU copies it intogridDimandblockDim. - grid: all blocks of one launch. Its size is
gridDim. - warp: a group of 32 threads that the GPU runs together (Lesson 01).
- CUDA runtime: the library behind calls such as
cudaDeviceSynchronize(). It checks the launch configuration against the hardware limits. cudaGetLastError(): returns the last CUDA error, for example from a launch that broke a limit.__global__: marks a function as a kernel, launched from the CPU and run on the GPU.- format string: the first argument of
printf. Each%din it is replaced by the next argument, in order. cudaDeviceSynchronize(): makes the CPU wait until the GPU has finished. A launch alone returns right away.nvcc: the CUDA compiler. It compiles the CPU and GPU parts of a.cufile into one program.-o: sets the name of the output program. Without it the name isa.out.