MODUL 3 - LEKCIJA 2

Hardware, Kvantizacija i Optimizacija

CPU vs GPU inferenza, VRAM kalkulacije, kvantizacija i praktično mjerenje performansi

⏱️ Trajanje: ~3.5 časa 📚 Nivo: Srednji 🎯 Praktični primjeri: 6

🖥️ CPU vs GPU — Fundamentalna Razlika za AI

Da bismo razumjeli ZAŠTO GPU ubrzava LLM inferenzu 10-100x, moramo razumjeti fundamentalnu razliku između CPU i GPU arhitektura.

📌 CPU (Central Processing Unit) — Arhitektura

CPU je dizajniran za sekvencijalne, kompleksne zadatke. Tipičan server CPU ima 2-128 jegara (cores). Svako jezgro može izvršavati složene sekvencijalne instrukcije sa brzom cache memorijom (L1/L2/L3). Odlični za: web servere, baze podataka, operativni sistem, poslovnu logiku — gdje se izvršava mnogo različitih zadataka u sekvenci.

📌 GPU (Graphics Processing Unit) — Arhitektura

GPU je dizajniran za masivno paralelne, jednostavne operacije. Npr. NVIDIA RTX 4090 ima 16,384 CUDA cores. Svako jezgro je "gluplje" od CPU jezgra, ali imajući 16.000+ njih koji simultano rade istu operaciju na različitim podacima — GPU je nezamjenjiv za matrično množenje.

💡 Analogija: Restoran vs Fabrika

CPU je kao vrsni kuhar u restoranu: priprema svako jelo od nule, po kompleksnim receptima, personalizovano za svakog gosta — ali može kuhati samo jedno jelo odjednom.

GPU je kao fabrika prehrambenih proizvoda: ima 16.000 radnika koji svaki radi istu jednostavnu operaciju (npr. punjenje flaše) simultano. Ne mogu kuhati kompleksna jela, ali mogu napuniti 16.000 flaša u sekundi.

Transformer matematika (matrično množenje) je savršena za GPU model — ista operacija na hiljadama elemenata simultano.

Zašto je Matrično Množenje Ključno?

Sva Transformer arhitektura se svodi na jedno: matrično množenje (Matrix Multiplication). Kada model "misli" (vrši inferenzu), dešava se hiljadama matričnih množenja u svakom sloju. Za 7B model sa 32 sloja, jedan forward pass (generisanje jednog tokena) zahtijeva:

PRIMJER: Llama 3 8B - Forward Pass za 1 token ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Input Embedding lookup: [1 × 4096] matrica 32 Transformer sloja, svaki ima: ├── Query projekcija: [4096 × 1024] matrično množenje ├── Key projekcija: [4096 × 1024] matrično množenje ├── Value projekcija: [4096 × 1024] matrično množenje ├── Attention scores: [1 × seq_len] softmax ├── Output projekcija: [1024 × 4096] matrično množenje ├── FFN Layer 1: [4096 × 14336] matrično množenje ← OGROMNO! └── FFN Layer 2: [14336 × 4096] matrično množenje Ukupno po tokenu (32 sloja × ~6 množenja): ~192 matrična množenja Svako množenje ima milione floating-point operacija. CPU (bez GPU): ~1-3 tokena/sekundi (sporo, sekvencijalno) GPU sa 16K CUDA cores: ~30-80 tokena/sekundi (simultano po svim elements) ↑ 20-50x brže!

💾 VRAM Kalkulacija — Koliko Memorije Treba?

VRAM (Video RAM) je memorija unutar GPU-a. Za LLM inferenzu, cijeli model mora stati u VRAM. Ako model ne stane — pada na RAM (System RAM), i postaje puno sporiji.

📌 Formula za Izračun VRAM Potrebe

Osnovna formula (za inferenzu, bez treninga):

VRAM (GB) ≈ (Broj Parametara × Bajta po Parametru) / 1,073,741,824 + ~20% overhead

  • FP32 (4 bajta/param): Llama 3 8B → 8B × 4 = 32 GB VRAM
  • FP16 (2 bajta/param): Llama 3 8B → 8B × 2 = 16 GB VRAM
  • INT8 (1 bajt/param): Llama 3 8B → 8B × 1 = 8 GB VRAM
  • INT4 (0.5 bajta/param): Llama 3 8B → 8B × 0.5 = 4 GB VRAM

VRAM Zahtjevi Popularnih Modela

Model Parametri FP16 VRAM Q4 VRAM Preporučeni GPU
Llama 3.2 3B 3 B 6 GB 2 GB GTX 1060 (6 GB)
Llama 3 / Mistral 7B 7-8 B 14-16 GB 5-6 GB RTX 3060 (12 GB)
Llama 3 13B / Mistral NeMo 12B 12-13 B 24-26 GB 8-10 GB RTX 3090 (24 GB)
Llama 3 70B 70 B 140 GB 40-50 GB A100 80 GB ili 2×RTX 3090
Mistral Small / Gemma 2 27B 27 B 54 GB 16-20 GB RTX 4090 (24 GB) + RAM offload

ℹ️ Šta ako nema dovoljno VRAM-a?

Ollama (i llama.cpp) automatski offloadaju slojeve modela na System RAM ako GPU VRAM nije dovoljan. Na primjer, 7B model sa Q4 kvantizacijom treba ~5 GB VRAM. Ako imate GPU sa 4 GB VRAM, llama.cpp može staviti 60% slojeva na GPU, a 40% na RAM — dobivate ~40-50% performansi punog GPU-a, što je i dalje znatno brže od čistog CPU-a.

Ovo se podešava u Ollami opcijom: OLLAMA_GPU_LAYERS=20 (broj slojeva na GPU-u)

🔢 Kvantizacija — Kompresija Modela bez Gubljenja Kvalitete

Kvantizacija je proces smanjivanja preciznosti numeričkih vrijednosti (težina modela) radi uštede memorije. Ovo je ključni koncept koji omogućava pokretanje velikih modela na dostupnom hardveru.

📌 Floating Point formati i preciznost

Kompjuteri reprezenuju decimalne brojeve u binarnom formatu. Različiti formati zauzimaju različito memoria:

  • FP32 (Float 32-bit, "full precision"): 32 bita = 4 bajta po broju. Raspon: ±3.4 × 1038. Maksimalna preciznost. Koristi se za trening.
  • FP16 (Float 16-bit, "half precision"): 16 bita = 2 bajta. Raspon: ±65,504. Manji raspon, ali dovoljno za inferenzu.
  • INT8 (Integer 8-bit): 8 bita = 1 bajt. Samo cijeli brojevi 0-255 (ili -128 do 127). Drastično smanjenje preciznosti — ali uz scaling trick, prihvatljiv kvalitet.
  • INT4 (4-bit): Samo 16 mogućih vrijednosti. Agresivna kompresija, ali moderni LLM-ovi su dovoljno robusni da zadrže ~95% kvaliteta.

💡 Analogija: Kvantizacija kao Jpeg kompresija

Zamislite originalnu fotografiju visoke rezolucije (FP32). Kada je snimite kao JPEG sa 90% kvaliteta (FP16), skoro ne vidite razliku. Na 70% kvaliteta (INT8), vidite minimalnu degradaciju. Na 40% (INT4), vidite blago zamućivanje, ali fotografija je i dalje jasno prepoznatljiva.

Kvantizacija LLM-a funkcionira slično: niste izgubili "znanje" modela, samo ste smanjili preciznost načina na koji je to znanje pohranjeno. Za većinu zadataka, razlika je neprimjetna.

GGUF Kvantizacijski Nivoi

Ollama koristi GGUF format koji definira više nivoa kvantizacije. Najvažniji:

Q2_K
2.5 GB za 7B
❌ Nizak kvalitet, samo za CPU bez RAM-a
Q4_K_M
4.1 GB za 7B
✅ Preporučeno: dobar balans kvalitet/veličina
Q5_K_M
4.8 GB za 7B
✅ Bolji kvalitet, malo više RAM-a
Q8_0
7.7 GB za 7B
✅ Visok kvalitet, ~8 GB VRAM treba
F16
14 GB za 7B
⭐ Maksimalni kvalitet, ~16 GB VRAM

📌 Šta znače sufiksi _K, _K_M, _K_S?

GGUF kvantizacijski tip poput Q4_K_M znači:

  • Q4: 4-bit kvantizacija za većinu težina
  • _K: k-quants metoda (naprednija, bolja od naivne kvantizacije)
  • _M: Medium — kritičniji slojevi (attention layers) su kvantizovani na 6-bit za bolji kvalitet
  • _S: Small — agresivnija kompresija, manji fajl, malo slabiji kvalitet
  • _L: Large — manje agresivna, bolji kvalitet ali veći fajl

Preporuka za početnike: Koristite Q4_K_M — to je "standard" i Ollama ga koristi po defaultu.

Uticaj Kvantizacije na Performanse — Benchmark

Kvantizacija Veličina (7B) VRAM Kvalitet odgovora Brzina (tok/s na RTX 3060)
F32 26 GB 28 GB+ 100% (baseline) ~5 tok/s
F16 13 GB 14 GB 99% ~12 tok/s
Q8_0 7.7 GB 8 GB 98.5% ~22 tok/s
Q4_K_M 4.1 GB 5 GB 96% ~35 tok/s
Q2_K 2.5 GB 3 GB 85-88% ~45 tok/s

🔑 Ključni Zaključak

Sa Q4_K_M kvantizacijom, model gubi samo ~4% kvaliteta u usporedbi sa punim FP32 preciznosti, ali zauzima 6.3x manje memorije i generira tekst 7x brže. Ovo je razlog zašto je kvantizacija industrijski standard za lokalnu LLM inferenzu.

🖥️ Hardware Preporuke po Scenariju

Scenarij 1: Minimalni Setup (bez namjenskog GPU-a)

Komponenta Minimum Preporučeno
RAM 16 GB 32 GB
CPU Intel i5-8th gen / AMD Ryzen 5 3600 Intel i7-12th gen / AMD Ryzen 7 5800X
Disk SSD sa 20+ GB slobodnog prostora NVMe SSD
Modeli Phi-3 Mini (3.8B), Llama 3.2 3B Llama 3.2 3B, Q4 Mistral 7B
Brzina 1-3 tok/s (sporo ali radi) 3-8 tok/s

Scenarij 2: Namjenski GPU Setup (preporučeno)

Komponenta Budget Setup Profesionalni Setup
GPU RTX 3060 12 GB (~350€) RTX 4090 24 GB (~1500€)
RAM 32 GB DDR4 64 GB DDR5
Modeli Mistral 7B, Llama 3 8B Q4 Llama 3 70B Q4, DeepSeek 67B
Brzina (7B) 30-40 tok/s 80-120 tok/s

⚠️ NVIDIA vs AMD GPU za LLM-ove

Za LLM inferenzu, NVIDIA GPU-ovi su znato bolji zbog CUDA ekosistema. Gotovo svi LLM frameworki (llama.cpp, vLLM, ExLlamaV2) imaju primarnu podršku za CUDA.

AMD GPU-ovi rade putem ROCm platforme, ali podrška je manje stabilna i performanse su tipično 20-40% niže od ekvivalentnog NVIDIA GPU-a za LLM zadatke. Apple Silicon (M1/M2/M3) je odlična opcija za Mac korisnike — Metal acceleration daje izvrsne rezultate u odnosu na potrošnju energije.

💻

LAB 3C: Mjerenje Performansi i Poređenje Kvantizacija

U ovom labu ćemo: (1) provjeriti hardware konfiguraciju, (2) mjeriti performanse modela, i (3) kreirati Python benchmark alat.

1

Provjera Hardware Konfiguracije

powershell
# Provjera RAM-a (ukupni i dostupni)
Get-WmiObject Win32_PhysicalMemory | Measure-Object Capacity -Sum |
  Select-Object @{N="Total RAM (GB)"; E={[Math]::Round($_.Sum / 1GB, 1)}}

# Provjera GPU-a i VRAM-a
Get-WmiObject Win32_VideoController |
  Select-Object Name,
    @{N="VRAM (GB)"; E={[Math]::Round($_.AdapterRAM / 1GB, 1)}},
    DriverVersion

# Provjera CPU-a
Get-WmiObject Win32_Processor |
  Select-Object Name, NumberOfCores, NumberOfLogicalProcessors, MaxClockSpeed

# Provjera slobodnog prostora na disku
Get-PSDrive C | Select-Object @{N="Slobodno (GB)"; E={[Math]::Round($_.Free / 1GB, 1)}}
2

Provjera Ollama konfiguracije i GPU detekcija

powershell
# Provjera Ollama okoline i GPU detekcije
ollama --version

# Provjera detektovanih GPU-ova (Ollama automatski prikazuje info o GPU-u)
# Pokrenite Ollamu u posebnom terminalu ako nije pokrenuta:
# ollama serve

# Ispis detalja o modelu (uključuje kvantizacijski nivo)
ollama show llama3.2

# Pokretanje modela sa verbose outputom koji pokazuje GPU korištenje
# Otvorite novi PowerShell terminal i pokrenite:
OLLAMA_DEBUG=1 ollama run llama3.2
3

Python Benchmark Skript za Mjerenje Brzine

Napravite fajl benchmark.py u VS Code:

python
"""
benchmark.py
Mjerenje performansi lokalnih Ollama modela:
- Tokens per second (tok/s) - ključna metrika brzine
- Time to first token (TTFT) - latency do prvog odgovora
- Ukupno generisanje za standardni test prompt
"""

import ollama
import time
import json

# ================================================================
# KONFIGURACIJA BENCHMARKA
# ================================================================
# Lista modela za testiranje (morat ćete imati ih preuzete!)
MODELI_ZA_TEST = ['llama3.2']  # Dodajte više modela po želji

# Standardni test prompt (isti za sve modele radi poređenja)
TEST_PROMPT = """Explain in exactly 200 words how a TCP/IP handshake works.
Include the three phases: SYN, SYN-ACK, ACK.
Describe what happens at each phase and why it's important."""

# Broj ponavljanja za svaki model (za pouzdanije rezultate)
BROJ_PONAVLJANJA = 2

# ================================================================
# BENCHMARK FUNKCIJA
# ================================================================
def benchmark_model(model_ime: str, prompt: str) -> dict:
    """
    Mjeri performanse jednog modela.
    Vraća dict sa svim metrikama.
    """
    print(f"\n🔬 Testiram model: {model_ime}")
    print(f"📝 Prompt ({len(prompt)} znakova)...")

    rezultati_ponavljanja = []

    for ponavljanje in range(BROJ_PONAVLJANJA):
        print(f"   Ponavljanje {ponavljanje + 1}/{BROJ_PONAVLJANJA}...", end='', flush=True)

        # Bilježimo vrijeme početka
        start_ukupno = time.perf_counter()

        # Varijabla za vrijeme prvog tokena
        start_prvog_tokena = time.perf_counter()
        prvi_token_zabiljezen = False
        ttft = 0.0

        # Capture tekst odgovora za analizu
        svi_tokeni = []

        try:
            # Streaming mode radi da mjerimo TTFT (Time to First Token)
            stream = ollama.chat(
                model=model_ime,
                messages=[{'role': 'user', 'content': prompt}],
                stream=True,
                options={
                    'temperature': 0,    # 0 = deterministički = reproducibilni rezultati
                    'num_predict': 250,  # Ograničavamo output za konzistentno mjerenje
                }
            )

            for chunk in stream:
                # Bilježimo kada je stigao prvi token
                if not prvi_token_zabiljezen:
                    ttft = time.perf_counter() - start_prvog_tokena
                    prvi_token_zabiljezen = True

                # Skupljamo generirani tekst
                token_tekst = chunk['message']['content']
                svi_tokeni.append(token_tekst)

            # Kraj generisanja
            end_ukupno = time.perf_counter()

            ukupno_trajanje = end_ukupno - start_ukupno
            ukupni_tekst = ''.join(svi_tokeni)
            # Aproksimacija: 1 token ≈ 4 znaka (gruba procjena za engleski)
            aproks_tokeni = len(ukupni_tekst) / 4
            tok_per_sekundi = aproks_tokeni / ukupno_trajanje

            rezultat = {
                'ttft_ms': ttft * 1000,               # Milisekunde
                'ukupno_trajanje_s': ukupno_trajanje,
                'aproks_tokeni': int(aproks_tokeni),
                'tok_per_sekundi': tok_per_sekundi,
                'znakova_generisano': len(ukupni_tekst),
            }

            rezultati_ponavljanja.append(rezultat)
            print(f" {tok_per_sekundi:.1f} tok/s ✅")

        except Exception as e:
            print(f" GREŠKA: {e} ❌")
            continue

    if not rezultati_ponavljanja:
        return None

    # Prosječne vrijednosti svih ponavljanja
    prosjek = {
        'model': model_ime,
        'ttft_ms': sum(r['ttft_ms'] for r in rezultati_ponavljanja) / len(rezultati_ponavljanja),
        'ukupno_trajanje_s': sum(r['ukupno_trajanje_s'] for r in rezultati_ponavljanja) / len(rezultati_ponavljanja),
        'aproks_tokeni': int(sum(r['aproks_tokeni'] for r in rezultati_ponavljanja) / len(rezultati_ponavljanja)),
        'tok_per_sekundi': sum(r['tok_per_sekundi'] for r in rezultati_ponavljanja) / len(rezultati_ponavljanja),
        'znakova_generisano': int(sum(r['znakova_generisano'] for r in rezultati_ponavljanja) / len(rezultati_ponavljanja)),
    }
    return prosjek

# ================================================================
# POKRETANJE BENCHMARKA
# ================================================================
if __name__ == "__main__":
    print("="*65)
    print("⚡ OLLAMA PERFORMANCE BENCHMARK")
    print("="*65)
    print(f"📋 Modeli za test: {', '.join(MODELI_ZA_TEST)}")
    print(f"🔁 Ponavljanja: {BROJ_PONAVLJANJA}")
    print(f"📝 Prompt: {TEST_PROMPT[:80]}...")

    svi_rezultati = []

    # Testiranje svakog modela
    for model in MODELI_ZA_TEST:
        rezultat = benchmark_model(model, TEST_PROMPT)
        if rezultat:
            svi_rezultati.append(rezultat)

    # Ispis rezultata
    print("\n" + "="*65)
    print("📊 REZULTATI BENCHMARKA")
    print("="*65)

    for r in svi_rezultati:
        print(f"\n🤖 Model: {r['model']}")
        print(f"   ⚡ Brzina: {r['tok_per_sekundi']:.1f} tok/s")
        print(f"   ⏱️  Trajanje: {r['ukupno_trajanje_s']:.2f} s")
        print(f"   🚀 TTFT: {r['ttft_ms']:.0f} ms (do 1. tokena)")
        print(f"   📝 ~{r['aproks_tokeni']} tokena generisano")
        print(f"   📄 {r['znakova_generisano']} znakova")

    print("\n" + "="*65)
    print("💡 INTERPRETACIJA REZULTATA:")
    print("   • > 30 tok/s = Odlično (GPU inferenza)")
    print("   • 10-30 tok/s = Dobro (GPU ili brz CPU)")
    print("   • 3-10 tok/s = Prihvatljivo (CPU inferenza)")
    print("   • < 3 tok/s = Sporo (slab CPU ili prevelik model)")
    print("   • TTFT < 500 ms = Dobra latency")
    print("   • TTFT > 2000 ms = Visoka latency (model se učitava)")
    print("="*65)
powershell
cd C:\Users\$env:USERNAME\Desktop\AI_Kurs
.\venv\Scripts\Activate.ps1
python benchmark.py
  • Tok/s (Tokens per Second): Ovo je najvažnija metrika. Označava koliko brzo model generira tekst. Tipično: 30+ tok/s znači da razgovor teče prirodno.
  • TTFT (Time to First Token): Koliko brzo model "počne" odgovarati. Visoka TTFT može biti uzrokovana sporim učitavanjem modela u memoriju.
  • Za kućni setap (CPU only, 16 GB RAM): Očekujte 1-5 tok/s za 7B model. Gore navedeni benchmark to jasno prikazuje.
4

Preuzimanje i poređenje različitih kvantizacijskih verzija

powershell
# Možete preuzeti specifičnu kvantizacijsku verziju modela:
# Format: ollama pull [ime]:[tag]

# Q4_K_M (default - ~4 GB fajl, preporučeno)
ollama pull llama3.2

# Isti model u punoj F16 preciznosti (~6 GB, za poređenje kvaliteta)
ollama pull llama3.2:latest

# Provjera svih verzija koje imate
ollama list

# Pokrenite benchmark za oba i usporedite tok/s i kvalitet odgovora!
# Zatim modificirajte MODELI_ZA_TEST listu u benchmark.py

⚙️ Optimizacijske Tehnike za Bolje Performanse

Ollama Environment Varijable

powershell
# Postavljanje Ollama environment varijabli (na Windows-u):
# Ove se postavljaju PRIJE pokretanja Ollama servisa

# OLLAMA_NUM_PARALLEL: koliko simultanih zahtjeva Ollama može obraditi
# Default je 1. Povećajte ako imate jak GPU i više korisnika.
$env:OLLAMA_NUM_PARALLEL = 2

# OLLAMA_MAX_LOADED_MODELS: koliko modela simultano čuvati u memoriji
# Default je 1 (zamjenjuje model pri svakom prebacivanju)
$env:OLLAMA_MAX_LOADED_MODELS = 2

# OLLAMA_FLASH_ATTENTION: Uključuje Flash Attention 2 optimizaciju
# Smanjuje VRAM korištenje za ~30% sa minimalnim gubitkom kvalitete
$env:OLLAMA_FLASH_ATTENTION = 1

# OLLAMA_GPU_OVERHEAD: Rezervisani VRAM za GPU overhead (u MB)
$env:OLLAMA_GPU_OVERHEAD = 512

# Pokretanje servera sa novim postavkama:
ollama serve

Odabir Pravog Modela za Zadatak

Zadatak Preporučeni Model Zašto
Generisanje koda, debugging CodeLlama 7B, Mistral 7B Finetuned na kod
Opća konverzacija, dokumentacija Llama 3.2 3B ili 8B Dobar razmjer kvalitet/brzina
Matematika, logičko razmišljanje Qwen2.5, Phi-3 Finetuned za STEM
Brze odgovore, slabi PC Phi-3 Mini 3.8B Odlično za veličinu
Embedding generisanje nomic-embed-text Optimizan za vektore

✅ Checkpoint — Provjera Razumijevanja

  • GPU je 10-100x brži od CPU-a za LLM inferenzu jer matrično množenje je masivno paralelna operacija.
  • VRAM = GPU memorija. Cijeli model mora stati u VRAM za optimalnu brzinu.
  • Kvantizacija smanjuje preciznost težina (FP32→INT4) uz minimalan gubitak kvaliteta.
  • Q4_K_M je industrijski standard za lokalne LLM-ove: 6x manje memorije, 7x brže, 96% kvaliteta.
  • Benchmark metrike: tok/s (brzina) i TTFT ms (latency do prvog odgovora).
  • Ollama automatski offloaduje slojeve na RAM ako VRAM nije dovoljan.

✅ Zaključak

Razumijevanje hardware zahtjeva i kvantizacije je ključno za svaki IT tim koji planira rasporediti lokalne LLM-ove. Kvantizacija nam omogućava pokretanje snažnih neruonskih mreža na standardnom office hardveru — što je revolucionarno za privatnost podataka u institucijama.

U Modulu 4 prelazimo na Prompt Engineering — nauku formulisanja efektivnih upita koji iz modela izvlače precizne, pouzdane i korisne odgovore za IT scenarije.