Hoppa till innehållet
AI-grafen
E· Universitetdatavetenskap· ca 60 min· utvecklande· verifierad 2026-09-20

Parallellism och varför GPU:er

Kunna förklara varför matrismultiplikation parallelliseras väl och vad en GPU gör annorlunda än en CPU.

Förkunskaper

Intuition

En matrismultiplikation består av miljoner oberoende skalärprodukter. Inget resultat behöver något annat resultat — de kan alla räknas samtidigt. Det kallas pinsamt parallellt, och det är precis vad en GPU är byggd för.

CPUGPU
Kärnor8–64 komplexa5 000–20 000 enkla
Optimerad förlåg latens per uppgifthög genomströmning
Bra påförgreningar, sekventiell logiksamma operation på mycket data
Minnesbandbredd~100 GB/s1–3 TB/s

Bandbredden är ofta den verkliga skillnaden. Vid LLM-dekodning läses alla vikter per token — då är det minnesbandbredden, inte beräkningen, som sätter taket.

Formellt

Amdahls lag sätter gränsen för vad parallellisering kan ge. Om andelen pp av arbetet kan parallelliseras över NN enheter: speedup=1(1−p)+p/N\text{speedup} = \frac{1}{(1-p) + p/N}

Med p=0,95p = 0{,}95 och oändligt många enheter är taket 20× — den seriella femprocenten dominerar. Det är därför dataladdning, förbehandling och Python-loopar i träningsloopen kan äta upp hela GPU-vinsten.

Aritmetisk intensitet (FLOPs per läst byte) avgör om en operation är beräknings- eller minnesbunden:

OperationIntensitetBunden av
Elementvis addition~0,1minne
Matris × vektor (dekodning)~2minne
Matris × matris (träning, stor batch)~100+beräkning

Därför är träning med stora batchar beräkningsbunden (GPU:n utnyttjas väl) medan dekodning en token i taget är minnesbunden (GPU:n står och väntar på minnet). Det förklarar varför batching hjälper så mycket vid servering — den flyttar arbetet från minnesbundet till beräkningsbundet.

MFU (model FLOPs utilization) mäter hur stor andel av GPU:ns teoretiska kapacitet som faktiskt används. 40–50 % är bra vid träning; under 20 % betyder att något annat är flaskhalsen.

Kod

import torch, time

def mat(n=4096, upprepningar=20, device="cuda"):
    a = torch.randn(n, n, device=device, dtype=torch.bfloat16)
    b = torch.randn(n, n, device=device, dtype=torch.bfloat16)
    for _ in range(3):                      # uppvärmning
        a @ b
    torch.cuda.synchronize()
    t0 = time.perf_counter()
    for _ in range(upprepningar):
        a @ b
    torch.cuda.synchronize()
    sek = (time.perf_counter() - t0) / upprepningar
    return {"ms": round(sek * 1000, 2), "TFLOPs": round(2 * n**3 / sek / 1e12, 1)}

print(mat(device="cpu"))     # {'ms': 2100.0, 'TFLOPs': 0.07}
print(mat(device="cuda"))    # {'ms': 4.6,    'TFLOPs': 29.9}

def amdahl(p, N):
    return 1 / ((1 - p) + p / N)

for p in (0.5, 0.9, 0.99):
    print(p, [round(amdahl(p, n), 1) for n in (2, 8, 1000)])
# 0.5  [1.3, 1.8, 2.0]     ← taket är 2× oavsett antal enheter
# 0.9  [1.8, 4.7, 9.9]
# 0.99 [2.0, 7.5, 91.0]

När GPU inte hjälper: små modeller där overheaden dominerar, kraftigt förgrenad logik, dataladdning som inte hinner med (fixa med fler workers och prefetch), och all Python-kod i loopen.

Behärskning innebär

  • Förklarar varför matrisoperationer parallelliseras väl
  • Beskriver skillnaden mellan CPU och GPU
  • Identifierar när GPU inte hjälper

Logga in för att göra övningarna och bygga upp din behärskning.

Källor

Alla källor och licenser