MODUL 2 - LEKCIJA 3

GPU & VRAM Optimizacija za LLM Deployment

Razumijevanje GPU arhitekture, VRAM matematike, i KV Cache-a za pravilno dimenzioniranje AI infrastrukture

⏱️ Trajanje: 1h 30min (14:00 - 15:30) 📚 Nivo: Napredni (Infrastrukturni) 🎯 Lab: Python VRAM Kalkulator za Nabavku Hardvera

🔲 Zašto GPU, a Ne CPU?

Arhitektura: CPU vs GPU za AI Inference CPU (Npr. 8-jezgreni) Kontrolna Logika (Složeno) ALU jezgre (brze i velike) VS GPU (Npr. 10,000 jezgri) Kontrolna Logika (Jednostavno) ALU jezgre (Matrice) Brzo za 1 kompleksan zadatak Ekstremno brzo za matematičke sekvence

🎯 Osnovno Pravilo Moderne AI Infrastrukture

CPU (Central Processing Unit) je dizajniran za kompleksne, sekvencijalne zadatke sa mnogo grananja i raznih instrukcija. GPU (Graphics Processing Unit) je dizajniran za ogromne paralelne matematičke operacije. LLM forward pass = milioni matričnih množenja istovremeno. GPU je jedina opcija za produkcijsku inferencijalnu latenciju.

🖥️ CPU (npr. Intel Core i9)

  • 8-32 kompleksnih jezgri (cores)
  • Visoka clock speed (5+ GHz)
  • Veliki keševi (L1, L2, L3)
  • Branch prediction i out-of-order execution
  • Odlično za OS, baze, web servere, poslovnu logiku
  • RAM: ograničen bandwithom (50-100 GB/s)
  • AI Inference: sporo! Llama 3.1 8B: 3-8 tok/s

🎮 GPU (npr. NVIDIA RTX 4090)

  • 16,384 CUDA cores (paralelne jezgre)
  • Specijalizirani Tensor Cores za matričnu matematiku
  • VRAM: 24 GB GDDR6X memorije
  • Memorijski bandwidth: 1,008 GB/s (10× brže od CPU RAM-a!)
  • Odlično za rasterizaciju, rendering, ML trening i inferencijalnost
  • AI Inference: brzo! Llama 3.1 8B: 60-80 tok/s

🍎 Apple Silicon (M3/M4 Pro/Max)

  • Unified Memory Architecture (CPU i GPU dijele isti DRAM)
  • M4 Max: 128 GB Unified Memory (!) dostupno GPU-u
  • Memorijski bandwidth: 500+ GB/s
  • Odlično za lokalni AI development
  • AI Inference: dobro! Llama 3.1 70B fituje u jednu mašinu!
  • Cijena: $4000-7000 (Macbook Pro M4 Max)

📌 Šta je CUDA i Zašto Je NVIDIA Dominantna?

CUDA (Compute Unified Device Architecture) je NVIDIA-in proprietarni parallel computing platform i programski model. Lansirano 2006., CUDA daje programerima API za direktnu komunikaciju sa NVIDIA GPU jezgrama koristeći C/C++/Python.

Zašto je CUDA problem za konkurenciju? Praktično sav AI/ML softver (PyTorch, TensorFlow, cuDNN, cuBLAS) je optimiziran za CUDA. Decenija optimizacije stvorila je ogromno jezerovanje: AMD i Intel imaju alternativne platforme (ROCm, oneAPI), ali kompatibilnost je daleko iza. Ako instalirate Llama.cpp ili PyTorch sa dokumentacijom koja kaže "instalirajte CUDA 12.x" — radi samo na NVIDIA-i.

Conclusion za sysadmina: Za AI workloads u produkciji (2025.) — koristite NVIDIA GPU. Tačka. (Osim Apple Silicon za macOS development, koji ima odlične Metal dryvere.)

💾 VRAM = Ograničavajući Faktor

VRAM (Video RAM) je brza memorija na grafičkoj kartici. Razlika od sistemskog RAM-a: GPU može pristupiti VRAM-u sa bandwithom od 1-5 TB/s, dok je sistemski RAM ograničen na 50-100 GB/s. Za matričnu matematiku Transformer modela — bandwidth je kritičan.

📌 Osnovna Formula: Koliko VRAM Treba za Model?

Minimalni VRAM za parametre modela:

VRAM (GB) = (Broj_Parametara_u_Milijardama × Bajta_po_Parametru × 1.1) + KV_Cache_VRAM

Bajta po parametru po preciznosti:

  • FP32 (32-bit float) = 4 Bajta/parametar
  • FP16 / BF16 (16-bit) = 2 Bajta/parametar ← Najčešći za inferencijalnost
  • Q8 (8-bit INT) = 1 Bajt/parametar
  • Q4 (4-bit INT) = 0.5 Bajta/parametar ← Ollama default

Faktor 1.1 = bufer za aktivacije, međurezu ltate i overhead PyTorch runtime-a.

Primjeri Kalkulacija

Izračunajmo VRAM za popularne modele (samo parametri, bez KV Cache):

🧮 VRAM Kalkulacija — Primjeri (bez KV Cache)
┌─────────────────────────┬──────────────┬────────┬────────────────────┐
│ Model                   │ Parametri    │ Prec.  │ Potreban VRAM      │
├─────────────────────────┼──────────────┼────────┼────────────────────┤
│ GPT-2 Small             │ 0.117B       │ FP16   │ ~0.25 GB           │
│ Llama 3.1 8B            │ 8B           │ FP16   │ 8×2×1.1 = ~17.6GB  │
│ Llama 3.1 8B            │ 8B           │ Q4     │ 8×0.5×1.1 = ~4.4GB │
│ Llama 3.1 70B           │ 70B          │ FP16   │ 70×2×1.1 = ~154GB  │
│ Llama 3.1 70B           │ 70B          │ Q4     │ 70×0.5×1.1 = ~38GB │
│ GPT-4 (procjena)        │ 1,000B (1T)  │ FP16   │ >2,000 GB!         │
│ GPT-4 (procjena MoE)    │ ~200B active │ FP16   │ ~440 GB (multi-GPU)│
└─────────────────────────┴──────────────┴────────┴────────────────────┘

Zaključak: Llama 3.1 8B Q4 (~4.4 GB) = radi na gaming GPU-u ili CPU+RAM!
Llama 3.1 70B Q4 (~38 GB) = treba server-grade GPU (A100 80GB ili 2× 4090)

🗝️ KV Cache — Skriveni Potrošač VRAM-a

VRAM kalkulacija ne završava s parametrima modela. Tokom inferencije, model koristi dodatnu memoriju za Key-Value Cache (KV Cache).

📌 Šta je KV Cache i Zašto Postoji?

Iz Lekcije 1.1 znamo da Self-Attention računa K (Key) i V (Value) vektore za svaki token u kontekstu. Svaki put kada model generira sljedeći token, mora "gledati" sve prethodne tokene. Bez cache-a, moramo ponovo računati K i V za sve prethodne tokene do sada — O(n²) kompleksnost!

KV Cache rješava ovo: Jednom izračunate K i V vektore za prethodne tokene pohranite u VRAM. Svaki novi token generiše samo vlastite K, V, i Q vektore i može direktno čitati cached vrijednosti za sve prethodne tokene. Dramatično ubrzanje — ali troši VRAM!

KV Cache: Vizualizacija rasta za dugi razgovor

Model: Llama 3.1 8B | 32 sloja | 32 głava | dim 4096

Token 1  (sistem prompt):  KV Cache = 0.01 MB
Token 500 (poruka 1):       KV Cache = 5.2 MB
Token 4096 (dugi razgovor): KV Cache = 42.7 MB
Token 16384 (~50 stranica): KV Cache = 170.8 MB
Token 128000 (max context): KV Cache = 1.3 GB  ← Uz 4.4GB parametra = 5.7GB ukupno!

Formula (aproksimacija):
KV_Cache_MB = 2 × num_layers × num_heads × head_dim × seq_len × sizeof(dtype) / 1024² 
            = 2 × 32 × 32 × 128 × seq_len × 2 bytes / 1,048,576
VRAM Potrošnja: Model Parametri vs KV Cache (Llama 3 8B Q4) 0 GB 6 GB 12 GB 18 GB 24 GB 8k 32k 64k 100k 128k (Max) Fiksni VRAM (Model Parametri = ~4.4 GB) Dinamički KV Cache (10 korisnika) Raste linearno sa veličinom konteksta! Limit: RTX 4060 Ti 16GB OOM Crash!

🏗️ Praktično: VRAM Planiranje za Produkciju

Kada planirate server za lokalni LLM deployment s više korisnika istovremeno:

Ukupni VRAM = VRAM(model parametri) + VRAM(KV Cache po korisniku × concurrent users)

Primjer: Llama 3.1 8B Q4 s 10 simultanih korisnika svaki u dugom razgovoru (16,384 tokena):

= 4.4GB (model) + 10 × 170MB (KV Cache 16k kontekst) ≈ 6.1 GB VRAM

To staje na RTX 3080 (10 GB VRAM) ili moderan RTX 4060 Ti (16 GB VRAM).

🖥️ Pregled GPU-ova za AI Deployment (2025.)

GPU Model VRAM Bandwidth Cijena (aprox.) Preporučen za
🏠 Consumer (Gaming) GPU — Za Development i Manje Deployment-e
RTX 3060 Ti 8 GB 448 GB/s ~350€ Llama 3.1 8B Q4 (solo korisnik)
🏢 Professional (Workstation) GPU — Za Produkciju
NVIDIA RTX 6000 Ada 48 GB 960 GB/s ~7,000€ 70B kvantizovano, višestruki korisnici
🏭 Data Center GPU — Za Enterprise AI Servere
NVIDIA A100 (40GB) 40 GB HBM2e 1,555 GB/s ~8,000€ (rabljeni) 70B FP16; batch inferencijalnost
NVIDIA H100 (80GB) 80 GB HBM3 3,350 GB/s ~25,000€ State-of-the-art trening + inferencijalnost
💡 CPU Fallback — Bez GPU-a
CPU + 32GB RAM (Ollama) N/A ~80 GB/s Već imate! Llama 3.1 8B Q4 (1-2 tok/s — za demo, ne produkciju)
🧮

LAB O7: VRAM Kalkulator za AI Infrastrukturu

Izgradićemo interaktivni Python kalkulator koji automatizira procjenu VRAM potreba za specifičan deployment scenarij. Alat koji možete prezentirati menadžmentu pri planiranju AI serverske nabavke.

1

Napravite fajl vram_calculator.py

python
"""
vram_calculator.py - Kalkulator VRAM potreba za AI Deployment
Modul 2.3: GPU & VRAM Optimizacija
"""

from dataclasses import dataclass
from typing import Optional

# ================================================================
# DEFINICIJE MODELA
# ================================================================

@dataclass
class LLMModel:
    """Opisuje jedan LLM model i njegove specifikacije."""
    naziv: str
    parametri_B: float        # Broj parametara u milijardama
    num_layers: int           # Broj transformer slojeva
    num_attention_heads: int  # Broj attention głava
    hidden_dim: int           # Dimenzija hidden state-a
    max_context_tokens: int   # Maksimalni context window

@dataclass
class QuantizationFormat:
    """Opisuje quantizacijski format modela."""
    naziv: str
    bajta_po_parametru: float
    opis: str
    gubitak_kvaliteta: str

@dataclass
class GPUModel:
    """Opisuje GPU model i njegove specifikacije."""
    naziv: str
    vram_gb: float
    bandwidth_gbs: float
    cijena_eur: int
    tip: str  # "consumer", "professional", "datacenter"

    def fituje_model(self, potreban_vram: float) -> bool:
        return self.vram_gb >= potreban_vram * 1.1  # 10% sigurnosni bufer

# ================================================================
# BAZA PODATAKA MODELA I GPU-OVA
# ================================================================

MODELI = {
    "llama_3_8b": LLMModel("Llama 3.1 8B", 8, 32, 32, 4096, 131072),
    "llama_3_70b": LLMModel("Llama 3.1 70B", 70, 80, 64, 8192, 131072),
    "mistral_7b": LLMModel("Mistral 7B v0.3", 7, 32, 32, 4096, 32768),
    "phi3_mini": LLMModel("Phi-3 Mini (3.8B)", 3.8, 32, 32, 3072, 128000),
}

QUANTIZATION_FORMATS = {
    "fp16": QuantizationFormat("FP16", 2.0, "Puna 16-bit preciznost", "Minimalan"),
    "q8": QuantizationFormat("Q8 (8-bit INT)", 1.0, "8-bit quantizacija", "Zanemariv"),
    "q4": QuantizationFormat("Q4 (4-bit INT)", 0.5, "4-bit quantizacija (Ollama default)", "Prihvatljiv"),
    "q3": QuantizationFormat("Q3 (3-bit INT)", 0.375, "3-bit quantizacija", "Primjetan"),
    "q2": QuantizationFormat("Q2 (2-bit INT)", 0.25, "Agresivna quantizacija", "Značajan"),
}

GPU_MODELI = [
    GPUModel("RTX 3080 (10 GB)", 10, 760, 650, "consumer"),
    GPUModel("RTX 4060 Ti (16 GB)", 16, 288, 450, "consumer"),
    GPUModel("RTX 4090 (24 GB)", 24, 1008, 1800, "consumer"),
    GPUModel("NVIDIA RTX A6000 (48 GB)", 48, 768, 5000, "professional"),
    GPUModel("NVIDIA A100 (80 GB)", 80, 2000, 15000, "datacenter"),
    GPUModel("NVIDIA H100 (80 GB)", 80, 3350, 25000, "datacenter"),
]

# ================================================================
# KALKULACIJE
# ================================================================

def izracunaj_vram_parametri(model: LLMModel, q_format: QuantizationFormat) -> float:
    """Izračunava VRAM za model parametre (bez KV Cache)."""
    # Bazni VRAM za parametre
    VRAM_bazni = model.parametri_B * 1e9 * q_format.bajta_po_parametru
    # Pretvaramo u GB + 10% overhead za runtime
    VRAM_gb = (VRAM_bazni / (1024**3)) * 1.10
    return VRAM_gb

def izracunaj_kv_cache(model: LLMModel, q_format: QuantizationFormat,
                        seq_len: int) -> float:
    """Izračunava VRAM za KV Cache za jednog korisnika sa seq_len tokena."""
    # KV Cache formula:
    # za svaki  sloj (layer), svaka głava (head) drži K i V vektore
    head_dim = model.hidden_dim // model.num_attention_heads
    # 2 (K + V) × num_layers × num_heads × head_dim × seq_len × sizeof(dtype)
    kv_bytes = 2 * model.num_layers * model.num_attention_heads * head_dim * seq_len * q_format.bajta_po_parametru
    return kv_bytes / (1024**3)  # Prebacujemo u GB

def preporuci_gpu(ukupni_vram_gb: float) -> list:
    """Vraća listu GPU-ova koji mogu podnijeti potrebni VRAM."""
    return [gpu for gpu in GPU_MODELI if gpu.fituje_model(ukupni_vram_gb)]

# ================================================================
# IZVJEŠTAJ
# ================================================================

def generiraj_izvjestaj(
    model_key: str,
    format_key: str,
    concurrent_users: int,
    avg_context_tokens: int
):
    """Generiše potpuni VRAM izvještaj za zadani scenarij."""
    model = MODELI[model_key]
    qformat = QUANTIZATION_FORMATS[format_key]

    SEPARATOR = "═" * 65

    print(f"\n{SEPARATOR}")
    print(f"  🧮 VRAM KALKULATOR — AI DEPLOYMENT PLAN")
    print(SEPARATOR)
    print(f"\n📋 SCENARIJ:")
    print(f"   Model:               {model.naziv}")
    print(f"   Quantizacija:        {qformat.naziv}")
    print(f"   Simultani korisnici: {concurrent_users}")
    print(f"   Prosj. kontekst:     {avg_context_tokens:,} tokena")

    # Komponente VRAM
    vram_model = izracunaj_vram_parametri(model, qformat)
    vram_kv_jednog = izracunaj_kv_cache(model, qformat, avg_context_tokens)
    vram_kv_ukupno = vram_kv_jednog * concurrent_users
    vram_ukupno = vram_model + vram_kv_ukupno

    print(f"\n💾 VRAM BREAKDOWN:")
    print(f"   Model parametri ({qformat.naziv}): {vram_model:.2f} GB")
    print(f"   KV Cache / korisnik:               {vram_kv_jednog:.3f} GB")
    print(f"   KV Cache ukupno ({concurrent_users} kor.):        {vram_kv_ukupno:.2f} GB")
    print(f"   ──────────────────────────────────────────")
    print(f"   🎯 UKUPNO POTREBNO:                {vram_ukupno:.2f} GB VRAM")

    print(f"\n🖥️  PREPORUČENI GPU MODELI (sortiran po cijeni):")
    preporuke = preporuci_gpu(vram_ukupno)
    if preporuke:
        for gpu in sorted(preporuke, key=lambda g: g.cijena_eur):
            marker = "✅" if gpu.cijena_eur < 10000 else "💎"
            print(f"   {marker} {gpu.naziv}")
            print(f"      VRAM: {gpu.vram_gb} GB | BW: {gpu.bandwidth_gbs} GB/s | Cijena: ~€{gpu.cijena_eur:,}")
    else:
        print("   ❌ Ni jedan GPU ne zadovoljava — razmotrite Multi-GPU setup!")

    print(f"\n⚠️  NAPOMENA O KVALITETU ({qformat.naziv}):")
    print(f"   Gubitak vs FP16: {qformat.gubitak_kvaliteta}")

    return vram_ukupno

# ================================================================
# POKRETANJE KALKULATORA ZA VIŠE SCENARIJA
# ================================================================

if __name__ == "__main__":
    print("🧮 AI Infrastruktura VRAM Kalkulator\n")

    # SCENARIJ 1: Mali tim (10 korisnika, 8B model)
    generiraj_izvjestaj("llama_3_8b", "q4", concurrent_users=10, avg_context_tokens=8192)

    # SCENARIJ 2: Srednja firma (50 korisnika, 8B model)
    generiraj_izvjestaj("llama_3_8b", "q4", concurrent_users=50, avg_context_tokens=8192)

    # SCENARIJ 3: Enterprise (30 korisnika, 70B model, dugi kontekst)
    generiraj_izvjestaj("llama_3_70b", "q4", concurrent_users=30, avg_context_tokens=32768)

    print("\n" + "═" * 65)
    print("  💡 SAVJETI ZA PLANIRANJE AI INFRASTRUKTURE")
    print("═" * 65)
    print("""
  1. Uvijek planirajte s 20-30% VRAM buffera za peak opterećenje
  2. Q4 quantizacija daje dobar quality/cost tradeoff za interne alate
  3. Za dulje kontekste (100k+ tokena), KV Cache dominira nad modelom!
  4. Pre-kupovinu GPU-a: testirajte sa Ollama CPU modo za validaciju use-case-a
  5. RTX 4060 Ti 16GB = idealan startni lokalni AI server za manje timove
""")
powershell
python vram_calculator.py
  • Za 10 korisnika s Llama 3.1 8B Q4 i 8k kontekstom — trebate oko 5-6 GB VRAM. RTX 4060 Ti (16 GB) je dovoljan s velikim bufferom za rast.
  • Za enterprise scenarij s 70B modelom i 50 simultanih korisnika — prekoračujemo i A100 80GB! U tom slučaju, multi-GPU setup ili smanjenje Q preciznosti je neophodna.
  • Primijetite da KV Cache raste linearno s brojem korisnika i dužinom konteksta. Za 100 simultanih korisnika svaki u 128k kontekstu — KV Cache može nadmašiti model parametre! Ovo je ključna razlika data između "dev/test" i "produkcija".

✅ Checkpoint — Provjera Razumijevanja

  • GPU ima 1000+ paralelnih jezgri vs 8-32 CPU jezgara — AI Inference je inherentno paralelna operacija.
  • NVIDIA CUDA je de-facto standard — rok za AI workloads u produkciji (2025.) = NVIDIA GPU.
  • VRAM = GPU-in RAM. Cijelii model mora fitovati u VRAM za GPU inferencijalnost.
  • KV Cache formulu uvijek uzimajte u obzir — za dugi kontekst može nadmašiti model parametre!
  • Q4 quantizacija (Ollama default) smanjuje VRAM 4× uz prihvatljiv gubitak kvaliteta za internu upotrebu.
  • Formula: Ukupni VRAM = VRAM(model) + VRAM(KV Cache) × concurrent_users.

✅ Zaključak

Sada imamo kompletan alat za dimenzioniranje AI infrastrukture. Razumijemo zašto GPU nadmašuje CPU za AI zadatke, šta je CUDA i zašto je NVIDIA dominantna, i kako precizno kalkulisati VRAM potrebe uzimajući u obzir i KV Cache.

U završnoj lekciji ovog dana (Modul 2.4) zakoračujemo u Hugging Face ekosistem — najveći hub za AI modele i datasete, te naučimo kako preuzimati i koristiti specijalizirane modele direktno u Python kodu.