Home › Programming languages › Specialized
guide

CUDA and GPU languages

Problem How should you program a GPU, and when is it worth writing custom kernels rather than using existing libraries? This guide helps pick between libraries, Triton and CUDA C++ for numeric workloads.

Programming GPUs: thousands of lockstep threads, bound by memory bandwidth; libraries first, Triton for fused kernels, CUDA C++ for peak speed.

cudagputritonhipmetalsimtkernelshpc
Overview

A GPU runs one instruction across groups of 32 threads (a warp; 64 on many AMD GPUs) and hides memory latency by switching among thousands of resident threads. Most kernels are limited by memory bandwidth, not FLOPs, so speed comes from coalesced loads, on-chip reuse and fusing operations (roofline). CUDA C++ is the most complete toolchain; Triton writes block-level kernels in Python.

Key facts

Use when

  • An op isn't covered by cuBLAS/cuDNN/PyTorch, or is slow because it's split into many memory-bound ops.
  • Real uses by field (NVIDIA): deep learning (H100 Tensor Cores and Transformer Engine), scientific simulation (molecular systems stepped thousands of times, CUDA Graphs article), data analytics (H100 data-center page), HPC.

Avoid when

  • torch.compile or a library already does it; data is small; code is branchy and pointer-chasing.

Deep dive

Decision table

JobPickWhy
Standard DL opsPyTorch / JAX built-ins, cuBLAS, cuDNNVendor-tuned, hard to beat
Fuse a chain of elementwise/reduction opstorch.compile first, then TritonGenerates fused kernels
Custom attention, quantized matmul, fused MLPTritonBlock-level Python, near-CUDA speed for many kernels
Peak performance on newest NVIDIA featuresCUDA C++ with CUTLASS/CuTeFull control: TMA, warp specialization, async copies
AMD GPUsHIP (CUDA-like, hipify), ROCm libraries; Triton supports AMDPortability from CUDA
Apple SiliconMetal Shading Language, MLXUnified memory, Metal-only
Browser / cross-vendor graphics appsWebGPU (WGSL) or Vulkan computeRuns anywhere, fewer features
Intel GPUs, heterogeneousSYCL / oneAPIStandard C++ model
TPUsJAX + PallasXLA toolchain
Scientific computing in C++/FortranOpenACC / OpenMP offload, or CUDADirective-based porting

Execution and memory model

  • Grid → blocks (up to 1024 threads, run on one SM) → warps of 32 lanes. A block's threads share fast shared memory and __syncthreads().
  • Memory hierarchy (per H100-class GPU, order of): registers (64K × 32-bit per SM), shared memory/L1 (up to about 228 KB per SM), L2 (tens of MB), HBM (80-192 GB).
  • Coalescing: a warp's 32 loads should hit consecutive addresses (one 128-byte transaction); strided access multiplies traffic.
  • Divergence: if lanes in a warp take different branches, both paths run serially.
  • Occupancy: resident warps per SM limited by registers and shared memory per block; enough to hide latency, not maximal.
  • Asynchrony: kernels and copies are queued on streams; overlap compute with transfers using pinned host memory and multiple streams.

Code

CUDA C++: grid-stride loop, warp-shuffle reduction, one atomic per block (nvcc -O3 -arch=sm_80 saxpy.cu).

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

#define CUDA_CHECK(x) do { cudaError_t e = (x); if (e != cudaSuccess) { \
  std::fprintf(stderr, "%s:%d %s\n", __FILE__, __LINE__, cudaGetErrorString(e)); std::exit(1); } } while (0)

__device__ float warp_sum(float v) {
  for (int off = 16; off > 0; off >>= 1)
    v += __shfl_down_sync(0xffffffffu, v, off);  // register exchange inside the warp
  return v;
}

// y = a*x + y, and accumulate sum(y) in the same pass (fusion: one read, one write).
__global__ void saxpy_sum(int n, float a, const float* __restrict__ x,
                          float* __restrict__ y, float* total) {
  __shared__ float partial[32];
  float acc = 0.f;
  for (int i = blockIdx.x * blockDim.x + threadIdx.x; i < n; i += gridDim.x * blockDim.x) {
    float v = a * x[i] + y[i];  // neighbouring lanes touch neighbouring addresses: coalesced
    y[i] = v;
    acc += v;
  }
  acc = warp_sum(acc);
  const int lane = threadIdx.x % 32, warp = threadIdx.x / 32;
  if (lane == 0) partial[warp] = acc;
  __syncthreads();
  if (warp == 0) {
    acc = lane < blockDim.x / 32 ? partial[lane] : 0.f;
    acc = warp_sum(acc);
    if (lane == 0) atomicAdd(total, acc);  // float atomics: result order is nondeterministic
  }
}

int main() {
  const int n = 1 << 24;
  std::vector<float> hx(n, 1.f), hy(n, 2.f);
  float *x, *y, *total;
  CUDA_CHECK(cudaMalloc(&x, n * sizeof(float)));
  CUDA_CHECK(cudaMalloc(&y, n * sizeof(float)));
  CUDA_CHECK(cudaMalloc(&total, sizeof(float)));
  CUDA_CHECK(cudaMemcpy(x, hx.data(), n * sizeof(float), cudaMemcpyHostToDevice));
  CUDA_CHECK(cudaMemcpy(y, hy.data(), n * sizeof(float), cudaMemcpyHostToDevice));
  CUDA_CHECK(cudaMemset(total, 0, sizeof(float)));

  int sms = 0;
  CUDA_CHECK(cudaDeviceGetAttribute(&sms, cudaDevAttrMultiProcessorCount, 0));
  saxpy_sum<<<sms * 4, 256>>>(n, 3.f, x, y, total);
  CUDA_CHECK(cudaGetLastError());                       // launch configuration errors
  float h_total = 0.f;
  CUDA_CHECK(cudaMemcpy(&h_total, total, sizeof(float), cudaMemcpyDeviceToHost)); // waits, surfaces faults
  std::printf("sum = %.0f (expected %.0f)\n", h_total, 5.0 * n);
  cudaFree(x); cudaFree(y); cudaFree(total);
}

Triton: the same blocked thinking from Python (pip install triton torch).

import torch
import triton
import triton.language as tl

@triton.jit
def add_relu_kernel(x_ptr, y_ptr, out_ptr, n, BLOCK: tl.constexpr):
    pid = tl.program_id(0)                       # one program instance per block of elements
    offs = pid * BLOCK + tl.arange(0, BLOCK)
    mask = offs < n                              # tail handling
    x = tl.load(x_ptr + offs, mask=mask)
    y = tl.load(y_ptr + offs, mask=mask)
    tl.store(out_ptr + offs, tl.maximum(x + y, 0.0), mask=mask)  # fused: 2 reads, 1 write

def add_relu(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
    out = torch.empty_like(x)
    n = out.numel()
    grid = lambda meta: (triton.cdiv(n, meta["BLOCK"]),)
    add_relu_kernel[grid](x, y, out, n, BLOCK=1024)
    return out

x = torch.randn(1 << 24, device="cuda"); y = torch.randn_like(x)
torch.testing.assert_close(add_relu(x, y), torch.relu(x + y))

Efficiency

  • Cost: H100 HBM bandwidth is 3.35 TB/s (SXM) to 3.9 TB/s (NVL) (NVIDIA H100 page); PCIe to the host is order of 32-64 GB/s per direction, so keep data on the GPU. Launching a kernel is not free: on a Tesla V100 a 2.9 µs kernel cost 9.6 µs per launch with per-kernel synchronization, 3.8 µs with overlapped launches and 3.4 µs with a captured CUDA Graph (NVIDIA blog). Most kernels are bound by memory bandwidth, not FLOPs.
  • Levers:
    • CUDA Graphs — 9.6 → 3.4 µs per short launch on V100 (NVIDIA blog) — loops of many tiny kernels
    • fuse chains of elementwise ops (torch.compile, Triton) — one read and write of HBM instead of one per op (order of) — memory-bound op chains
    • coalesced access and shared-memory tiling — consecutive lanes hit consecutive addresses, fewer transactions (CUDA Best Practices Guide) — any global-memory kernel
    • pinned host memory and streams — copies overlap compute (CUDA Best Practices Guide) — host-device transfer loops
    • FP16/BF16/FP8 on tensor cores — up to 4x faster GPT-3 training on H100 vs the prior generation (NVIDIA claim) — matmul-heavy deep learning
  • Naive → standard → state of the art:
    • one tiny kernel per op, pageable copies, FP32 · launch- and bandwidth-bound
    • cuBLAS/cuDNN and torch.compile fusion, pinned memory, BF16 · library-tuned
    • Triton or CUTLASS kernels fused for the model, CUDA Graphs, FP8 where the model tolerates it · near the bandwidth or tensor-core roofline

Build and deploy

  • CUDA: nvcc or Clang; compile for the target architectures (-gencode for each sm_XX, plus PTX for forward compatibility). Driver must be at least as new as the toolkit requires.
  • Containers: NVIDIA base images (nvidia/cuda:*-runtime) with the NVIDIA Container Toolkit on the host.
  • Profile with Nsight Systems (timeline: launches, copies, gaps) then Nsight Compute (per-kernel: bandwidth, occupancy, stalls).
  • Triton kernels JIT-compile on first call and cache; use @triton.autotune over block sizes and warps.

Pitfalls

  • Forgetting that launches are asynchronous: timing without cudaDeviceSynchronize()/events measures nothing; errors appear at the next sync.
  • Host-device copies inside loops; pageable host memory is slower and can't overlap with compute (use pinned memory).
  • Uncoalesced or strided global memory access; array-of-structs layouts (use struct-of-arrays).
  • Shared-memory bank conflicts (32 banks of 4 bytes); pad tiles by one element.
  • Too many tiny kernels (launch-bound); fuse or capture with CUDA Graphs.
  • Float atomics and parallel reductions are nondeterministic; tests need tolerances.
  • Assuming FP32 tensor-core matmuls are exact FP32 (TF32 is often enabled by default in frameworks).
  • Writing a custom kernel before checking whether the time is actually in that op (profile first).

Implementations

namelangrepoinstallverdictlicencehealthas_ofnotes
CUDA Toolkit (nvcc)C++ / CUDAdeveloper.nvidia.com/cuda-toolkitn/a (installer or distro package from developer.nvidia.com/cuda-downloads)————compiler, cuBLAS, Nsight tools; 13.4.3 released October 2026
TritonPython / C++triton-lang/tritonpip install triton————block-level kernels in Python
CUTLASS / CuTeC++NVIDIA/cutlassn/a (header-only, clone the repo)————peak-performance GEMM templates
CCCL (Thrust, CUB)C++NVIDIA/cccln/a (ships with the CUDA Toolkit)————parallel algorithms and block primitives
HIPC++ROCm/HIPn/a (ROCm packages, rocm.docs.amd.com)————CUDA-like model for AMD GPUs

Sources

  • NVIDIA, CUDA C++ Programming Guide docs and CUDA C++ Best Practices Guide docs.
  • Tillet, Kung, Cox, Triton: An Intermediate Language and Compiler for Tiled Neural Network Computations, MAPL 2019. paper · code
  • Williams, Waterman, Patterson, Roofline: An Insightful Visual Performance Model for Multicore Architectures, CACM 2009. paper
  • Kirk, Hwu, Programming Massively Parallel Processors, Morgan Kaufmann (4th ed. 2022, with El Hajj).
  • AMD, HIP Programming Guide (ROCm documentation). docs
  • NVIDIA, NVIDIA H100 Tensor Core GPU. site · Gray, Getting Started with CUDA Graphs, NVIDIA Developer Blog. post

Used by

Systems (21):

Alternatives and related

Choosing a languageWhich programming language should you use for a given job, and when is a rewrite or a mix of languages worthwhile? This guide compares languages by speed, memory, hiring and build cost.PythonIs Python the right language for ML, data work, scripting or a backend, and how do you avoid its slowness? This guide covers where it pays off and where it does not.C and C++Is C or C++ the right language for a project? This guide weighs maximum performance and the huge native ecosystem against the memory-safety and undefined-behaviour risks you must manage.FlashAttentionTraining or running a transformer on long sequences is slow and runs out of GPU memory, because the attention step builds and moves a huge intermediate table. Engineers want exact attention that is faster and leaner.Kernel fusionGPU programs made of many small operations, such as normalisation, activations and optimizer steps, run well below hardware capability because each step re-reads and rewrites memory. Engineers want to speed these up.Mixed-precision trainingNeural network training is slow and memory-hungry in full 32-bit arithmetic. Engineers want to use faster, smaller number formats on modern GPUs without losing accuracy or hitting numerical failures.Roofline modelAn engineer optimising a compute-heavy kernel needs to know whether it is limited by arithmetic or by memory bandwidth, and how much faster it could possibly get, before deciding what to work on.SIMD programmingHow do you speed up hot loops over arrays by processing several values per instruction, across different CPU types, while still running safely on machines that lack the newer instructions? This guide covers when and how to do it.GPU cloudTeams training or serving neural nets find GPU time is the biggest line on the bill and prices vary widely between providers. This guide answers where to rent GPUs and what trade-offs each kind of provider brings.