A GPU is thousands of tiny cores that all run the same code at once. CUDA and Triton are the two ways you write that code — CUDA is low-level C++, Triton is a Python dialect that hides most of the bookkeeping. The whole game is feeding those cores with data fast enough.
Where you meet it
- Every LLM forward pass — attention and matmul kernels run on the GPU.
- Training and inference frameworks (PyTorch, JAX) call into thousands of CUDA kernels under the hood.
- FlashAttention, custom fused ops, quantized matmuls — written by hand in CUDA or Triton.
- Anything embarrassingly parallel: image processing, simulations, ray tracing.
What it does
It trades single-thread speed for raw width. A CPU has a handful of fast, clever cores; a GPU has thousands of simple ones. Give it a job where the same operation runs over millions of independent data elements, and it finishes in a fraction of the time — because all those elements are processed in parallel.
A GPU is rarely starved for math — it's starved for data. Memory bandwidth, not FLOPs, is what your kernel usually waits on.
How it works
The GPU model is SIMT — Single Instruction, Multiple Threads. You write one function, a kernel, and it executes N times in parallel across N threads. Threads are grouped into blocks, blocks into a grid. Each thread looks up its own index and handles one slice of the data:
// CUDA: add two vectors, one element per thread
__global__ void add(float* a, float* b, float* c, int n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) c[i] = a[i] + b[i];
}
// launch a grid of blocks of threads
add<<<numBlocks, threadsPerBlock>>>(a, b, c, n);
Performance lives and dies on the memory hierarchy. Each thread has fast private registers; a block shares low-latency shared memory; everything else sits in big, slow global memory. Good kernels stage data through shared memory and read global memory in coalesced patterns (neighbouring threads touch neighbouring addresses) so one wide memory transaction feeds a whole group.
Triton does the same job from Python. You think in blocks of data, not individual threads, and the compiler handles tiling and memory movement:
@triton.jit
def add_kernel(a_ptr, b_ptr, c_ptr, n, BLOCK_SIZE: tl.constexpr):
pid = tl.program_id(0)
off = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
mask = off < n
a = tl.load(a_ptr + off, mask=mask)
b = tl.load(b_ptr + off, mask=mask)
tl.store(c_ptr + off, a + b, mask=mask)
Watch out
- Memory bandwidth, not FLOPs, is usually the bottleneck. For LLM inference the cores sit idle waiting on data — fuse ops and keep data on-chip.
- Thread divergence: when threads in a group take different
ifbranches, the hardware runs both serially. Low occupancy wastes cores the same way. - Host–device transfers are expensive. Copying between CPU and GPU over PCIe can cost more than the compute — keep data resident on the GPU.
- Only data-parallel work pays off. Branchy, sequential, or pointer-chasing code maps badly to a GPU; some problems just aren't a fit.
Go deeper
Eine GPU sind tausende winzige Kerne, die alle denselben Code gleichzeitig ausführen. CUDA und Triton sind die zwei Wege, diesen Code zu schreiben — CUDA ist low-level C++, Triton ein Python-Dialekt, der den größten Teil der Buchführung übernimmt. Die ganze Kunst: diese Kerne schnell genug mit Daten zu füttern.
Wo es vorkommt
- Jeder LLM-Forward-Pass — Attention- und Matmul-Kernel laufen auf der GPU.
- Frameworks für Training und Inferenz (PyTorch, JAX) rufen im Hintergrund tausende CUDA-Kernel auf.
- FlashAttention, eigene fusionierte Ops, quantisierte Matmuls — von Hand in CUDA oder Triton geschrieben.
- Alles peinlich Parallele: Bildverarbeitung, Simulationen, Raytracing.
Was es tut
Es tauscht Einzel-Thread-Tempo gegen schiere Breite. Eine CPU hat eine Handvoll schneller, kluger Kerne; eine GPU tausende einfache. Gib ihr eine Aufgabe, bei der dieselbe Operation über Millionen unabhängiger Datenelemente läuft, und sie ist in einem Bruchteil der Zeit fertig — weil all diese Elemente parallel verarbeitet werden.
Einer GPU fehlt selten die Rechenleistung — ihr fehlen die Daten. Meist wartet dein Kernel auf die Speicherbandbreite, nicht auf FLOPs.
Wie es funktioniert
Das GPU-Modell ist SIMT — Single Instruction, Multiple Threads. Du schreibst eine Funktion, einen Kernel, und sie wird N-mal parallel über N Threads ausgeführt. Threads stecken in Blocks, Blocks in einem Grid. Jeder Thread liest seinen eigenen Index und bearbeitet ein Stück der Daten:
// CUDA: zwei Vektoren addieren, ein Element pro Thread
__global__ void add(float* a, float* b, float* c, int n) {
int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i < n) c[i] = a[i] + b[i];
}
// starte ein Grid aus Blocks aus Threads
add<<<numBlocks, threadsPerBlock>>>(a, b, c, n);
Performance steht und fällt mit der Memory-Hierarchie. Jeder Thread hat schnelle private Register; ein Block teilt sich Shared Memory mit niedriger Latenz; alles andere liegt im großen, langsamen Global Memory. Gute Kernel schleusen Daten durch Shared Memory und lesen Global Memory in coalesced Mustern (benachbarte Threads greifen auf benachbarte Adressen zu), sodass eine breite Speicher-Transaktion eine ganze Gruppe versorgt.
Triton macht dasselbe aus Python heraus. Du denkst in Blöcken von Daten, nicht in einzelnen Threads, und der Compiler übernimmt Tiling und Speicherbewegung:
@triton.jit
def add_kernel(a_ptr, b_ptr, c_ptr, n, BLOCK_SIZE: tl.constexpr):
pid = tl.program_id(0)
off = pid * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
mask = off < n
a = tl.load(a_ptr + off, mask=mask)
b = tl.load(b_ptr + off, mask=mask)
tl.store(c_ptr + off, a + b, mask=mask)
Worauf achten
- Meist ist die Speicherbandbreite der Flaschenhals, nicht die FLOPs. Bei LLM-Inferenz warten die Kerne untätig auf Daten — Ops fusionieren und Daten on-chip halten.
- Thread-Divergenz: nehmen Threads einer Gruppe verschiedene
if-Zweige, führt die Hardware beide seriell aus. Niedrige Occupancy verschenkt Kerne genauso. - Host-Device-Transfers sind teuer. Kopieren zwischen CPU und GPU über PCIe kann mehr kosten als die Rechnung selbst — Daten auf der GPU residieren lassen.
- Nur datenparallele Arbeit lohnt sich. Verzweigter, sequenzieller oder zeiger-jagender Code passt schlecht auf eine GPU; manche Probleme eignen sich einfach nicht.



