Inference: generasiyadan serving-ə
Inference: generasiyadan serving-ə
Training model-in bir dəfə ödənilən qiymətidir. Inference isə hər gün, hər istifadəçi sorğusunda ödənilir. Fine-tune etdiyiniz model nə qədər yaxşı olsa da, onu lazımi latency və qiymətlə serve edə bilmirsinizsə, o model production-da yoxdur. Bu fəsil generasiyanın hardware səviyyəsində necə işlədiyini, darboğazların harada olduğunu və onları aradan qaldıran texnikaları izah edir. Məqsəd hər hansı serving engine-in arxasında baş verənləri oxuya bilməkdir.
İki mərhələ: prefill və decode
Fəsil 3-də gördük ki, autoregressive model hər addımda bir token yaradır və əvvəlki token-lərin key və value vektorlarını KV cache-də saxlayır. Bir sorğunun icrası iki fərqli mərhələdən keçir.
Prefill. Prompt-dakı bütün token-lər paralel emal olunur. Model hər layer üçün onların K və V vektorlarını hesablayıb cache-ə yazır və ilk çıxış token-ini verir. Bu mərhələ böyük matrix multiplication-lardan ibarətdir, GPU-nun hesablama gücündən tam istifadə edir və compute-bound-dur.
Decode. Bundan sonra hər addımda yalnız bir yeni token emal olunur. Amma bu bir token üçün model-in bütün weight-lərini yaddaşdan oxumaq və bütün KV cache-ə attention etmək lazımdır. Hesablama azdır, oxunan data isə çoxdur. Decode memory-bandwidth-bound-dur.
Bu fərq inference mühəndisliyinin ən vacib faktıdır. Kobud hesab belədir:
decode sürətinin yuxarı həddi (batch=1) ≈ memory bandwidth / weight-lərin ölçüsü
H100: ~3.35 TB/s, 8B model bf16-da: ~16 GB
3350 / 16 ≈ 200 token/s (praktikada daha az)
Yəni tək istifadəçi üçün GPU-nun hesablama gücünün böyük hissəsi boş qalır. Aşağıdakı optimizasiyaların demək olar hamısı bu boşluğu doldurmağa xidmət edir: ya oxunan baytları azaltmaq (quantization), ya da bir oxunuşla daha çox iş görmək (batching, speculative decoding).
Sampling: paylanmadan token seçmək
Model hər addımda vocabulary üzərində logit-lər qaytarır. Növbəti token-in necə seçiləcəyini sampling strategiyası müəyyən edir.
- Greedy. Həmişə ən yüksək ehtimallı token seçilir. Deterministikdir, amma uzun mətnlərdə təkrarlara və darıxdırıcı nəticələrə meyllidir.
- Temperature. Logit-lər softmax-dan əvvəl T-yə bölünür. T < 1 paylanmanı kəskinləşdirir, T > 1 düzləşdirir. T → 0 greedy-yə yaxınlaşır.
- Top-k. Yalnız ən yüksək k token saxlanılır, qalanları atılır. Sabit k bəzən çox dar, bəzən çox geniş olur.
- Top-p (nucleus). Ehtimalların cəmi p-yə çatana qədər ən yüksək token-lər saxlanılır. Paylanma kəskin olanda az, düz olanda çox namizəd qalır.
- Min-p. Ən yüksək ehtimalın müəyyən faizindən (məsələn 0.05 × p_max) aşağı olan token-lər atılır. Yüksək temperature-da top-p-dən daha sabit davranır və son illərdə open-source alətlərdə geniş yayılıb.
def sample(logits, temperature=0.7, min_p=0.05):
probs = softmax(logits / temperature)
probs[probs < min_p * probs.max()] = 0
return random_choice(probs / probs.sum())
Praktik qayda: extraction, classification və kod üçün aşağı temperature, yaradıcı mətn üçün daha yüksək temperature. Reasoning model-lərin çoxu isə provider-in tövsiyə etdiyi parametrlərlə train edilir və onlardan uzaqlaşmaq keyfiyyəti aşağı salır.
Structured və constrained decoding
Model-in çıxışı başqa proqram tərəfindən oxunacaqsa, "zəhmət olmasa JSON qaytar" demək kifayət deyil. Constrained decoding hər addımda qrammatikaya uyğun gəlməyən token-lərin logit-lərini mənfi sonsuzluğa endirir. JSON schema, regex və ya context-free grammar sonlu avtomata çevrilir və decoder yalnız icazə verilən keçidləri seçə bilir. Nəticə sintaktik baxımdan həmişə etibarlıdır.
Bunun iki incəliyi var. Birincisi, tokenizer sərhədləri qrammatika sərhədləri ilə üst-üstə düşmür, ona görə yaxşı implementasiyalar mask-ları vocabulary üzərində əvvəlcədən hesablayır ki, overhead az olsun. İkincisi, sintaktik düzgünlük semantik düzgünlük demək deyil. Model-i çox dar formata məcbur etmək, xüsusilə reasoning-dən əvvəl, keyfiyyəti aşağı sala bilər. Ona görə tez-tez əvvəl sərbəst düşünmə, sonra strukturlaşdırılmış cavab sxemi istifadə olunur.
VRAM hesabı: model GPU-ya sığırmı?
Serving üçün yaddaş iki əsas hissədən ibarətdir: weight-lər və KV cache. Llama 3 8B tipli bir model götürək: 32 layer, GQA ilə 8 KV head, head_dim = 128.
Weight-lər (bf16): 8e9 parametr × 2 bayt ≈ 16 GB
KV cache bir token üçün:
2 (K və V) × 32 layer × 8 KV head × 128 head_dim × 2 bayt
= 131 072 bayt ≈ 128 KiB
8 192 token-lik bir sequence: 8192 × 128 KiB = 1 GiB
32 paralel sequence × 8K: ≈ 32 GiB
Deməli, 80 GB-lıq GPU-da 16 GB weight-dən sonra qalan yaddaş eyni anda neçə istifadəçiyə xidmət edə biləcəyinizi müəyyən edir. Buraya activation-lar və framework overhead-i üçün ehtiyat da əlavə olunmalıdır. GQA olmasaydı (32 KV head), KV cache dörd dəfə böyük olardı. Fəsil 3-dəki arxitektura qərarlarının serving qiymətinə birbaşa təsiri belədir. Uzun context-li tətbiqlərdə KV cache tez-tez weight-lərdən də böyük olur.
Quantization
Quantization weight-ləri (bəzən activation-ları və KV cache-i də) daha az bitlə saxlamaqdır. Decode bandwidth-bound olduğu üçün baytları azaltmaq birbaşa sürət qazancıdır: int4 weight-lər bf16-ya nisbətən təxminən dörd dəfə az oxunur.
- int8 / fp8. Keyfiyyət itkisi adətən praktik olaraq hiss olunmur. Fəsil 5-də gördüyümüz fp8 müasir GPU-larda həm weight, həm activation üçün native dəstəklənir.
- int4. 8B model ~4.5–5 GB-a enir və istehlakçı GPU-suna, hətta laptop-a sığır. İtki kiçik model-lərdə və riyaziyyat, kod kimi dəqiqlik tələb edən tapşırıqlarda daha çox hiss olunur.
Sadə rounding aşağı bitlərdə pis işləyir, çünki bəzi weight-lər və activation kanalları outlier-dir. Əsas yanaşmaların intuisiyası belədir:
- GPTQ layer-ləri bir-bir quantize edir və hər addımda yaranan xətanı hələ quantize olunmamış weight-ləri düzəltməklə kompensasiya edir. Bunun üçün kiçik calibration dataset lazımdır.
- AWQ activation-lara baxaraq hansı weight kanallarının vacib olduğunu tapır və onları quantization-dan əvvəl scale edərək qoruyur.
- GGUF konkret alqoritm deyil, əsasən CPU və Apple Silicon üzərində lokal inference üçün geniş yayılmış fayl formatıdır. İçində müxtəlif block-wise quantization tipləri var (məsələn Q4_K_M, Q8_0) və hər block-un öz scale-i olur.
Qayda: quantize edilmiş model-i həmişə öz eval set-inizdə yoxlayın (Fəsil 8). Ümumi benchmark-da 1 bal itki sizin domain-də 10 bal ola bilər.
Batching: GPU-nu doldurmaq
Decode zamanı weight-ləri bir dəfə oxuyub bir neçə sorğu üçün istifadə etmək olar. Batch-dəki hər əlavə sequence demək olar pulsuz gəlir, ta ki iş compute-bound olana qədər. Throughput-un əsas mənbəyi budur.
Static batching batch-dəki ən uzun sorğunun bitməsini gözləyir. Qısa cavablar bitəndən sonra onların yeri boş qalır. Continuous batching isə qərarı hər decode addımında verir: bitmiş sequence çıxır, növbədəki yeni sorğu dərhal onun yerini tutur. Real trafikdə bu, throughput-u bir neçə dəfə artırır.
Continuous batching-in qarşısındakı maneə KV cache yaddaşının fraqmentasiyasıdır. Hər sorğu üçün maksimal uzunluqda bitişik yaddaş ayırmaq israfdır. PagedAttention əməliyyat sistemindəki virtual yaddaş ideyasını tətbiq edir: KV cache sabit ölçülü block-lara bölünür və hər sequence-in block table-i olur. Yaddaş lazım olduqca ayrılır, israf demək olar sıfıra enir. Eyni system prompt-u paylaşan sorğular isə eyni block-ları paylaşa bilir (prefix caching). Agent-lərdə və uzun system prompt-lu tətbiqlərdə bu, prefill xərcini kəskin azaldır.
Böyük sistemlərdə növbəti addım prefill və decode-u ayrı GPU qruplarına bölməkdir (disaggregated serving), çünki onların hardware ehtiyacları fərqlidir.
Speculative decoding
Batch=1 halında da boş qalan compute-dan istifadə etmək olar. Kiçik və sürətli draft model növbəti k token-i təxmin edir. Böyük model bu k token-i bir forward pass-da, prefill kimi paralel yoxlayır. Qəbul edilən prefix saxlanılır, ilk rədd edilən token-dən sonra böyük model-in öz seçimi götürülür. Düzgün qurulmuş qəbul qaydası ilə çıxış paylanması böyük model-in paylanması ilə eyni qalır, yəni keyfiyyət itkisi yoxdur.
Qazanc draft-ın nə qədər tez-tez düz tapmasından asılıdır. Kod və strukturlaşdırılmış mətndə 2–3 dəfə sürətlənmə adi haldır. Ayrıca draft model əvəzinə əsas model-ə əlavə prediction head-lər qoşan variantlar da var (Medusa, EAGLE ailəsi).
Latency metrikləri
İstifadəçi təcrübəsi bir rəqəmlə ölçülmür.
- TTFT (time to first token): sorğudan ilk token-ə qədər keçən vaxt. Növbədə gözləmə və prefill tərəfindən müəyyən olunur. Chat-də "cavab gəlir" hissi budur.
- TPOT (time per output token) və ya inter-token latency: ilk token-dən sonra hər token-ə düşən vaxt. Decode sürətidir.
- End-to-end latency ≈ TTFT + TPOT × çıxış token-lərinin sayı.
- Throughput: sistemin saniyədə bütün istifadəçilər üçün yaratdığı token sayı.
Latency və throughput bir-biri ilə ziddiyyət təşkil edir. Batch böyüdükcə throughput artır, amma hər istifadəçi üçün TPOT pisləşir. Ölçərkən ortalamaya yox, p50 və p99-a baxın. SLO-nu da konkret yazın, məsələn "p99 TTFT < 1 s, TPOT < 50 ms".
Serving engine seçimi
Bu texnikaları sıfırdan yazmağa ehtiyac yoxdur. Serving engine-lər bir neçə kateqoriyaya bölünür:
- Yüksək throughput-lu GPU server-ləri. Continuous batching, PagedAttention, prefix caching, speculative decoding və tensor parallelism-i hazır verir. Production API-ləri üçün əsas seçimdir.
- Vendor-optimallaşdırılmış runtime-lar. Konkret hardware üçün xüsusi kernel-lər yazılır. Maksimal performans verir, qarşılığında daha mürəkkəb build və daha az çeviklik gəlir.
- Lokal/edge runtime-lar. CPU, Apple Silicon və istehlakçı GPU-ları üçündür, adətən GGUF kimi quantize edilmiş formatlarla işləyir. Prototip, offline və məxfi istifadə üçün yaxşıdır.
Seçim meyarları: dəstəklənən model arxitekturaları (yeni open model-lər çıxanda nə qədər tez dəstəklənir), quantization formatları, structured output, LoRA adapter-lərini dinamik yükləmə (Hissə II-də vacib olacaq), metrik ixracı və OpenAI-uyğun API.
Token başına qiymət
Self-hosting-in iqtisadiyyatı sadə bir bölmədir:
GPU qiyməti: $2.5 / saat
Real ölçülmüş aggregate throughput: 2 500 token/s
Saatlıq token: 2500 × 3600 = 9 000 000
1M token-in qiyməti ≈ 2.5 / 9 ≈ $0.28
Tələ "real ölçülmüş" sözlərindədir. Bu rəqəm GPU tam yüklü olanda keçərlidir. Trafik gecə düşürsə və GPU 30% yüklüdürsə, faktiki qiymət üç dəfə artır. Input və output token-lərinin qiyməti də fərqlidir: prefill ucuzdur, decode bahadır. API provider-ləri də buna görə output token-lərinə daha yüksək qiymət qoyur. Self-hosting adətən sabit və yüksək trafikdə, data məxfiliyi tələb olunanda və ya fine-tune edilmiş xüsusi model lazım olanda özünü doğruldur.
Tapşırıqlar
- Seçdiyiniz open model-in config faylından layer sayını, KV head sayını və head_dim-i götürün. bf16 və int4 üçün weight ölçüsünü, bir token-in KV cache ölçüsünü hesablayın və 24 GB-lıq GPU-da 4K context ilə eyni anda neçə sequence serve edə biləcəyinizi tapın.
- Bir model-i lokal runtime-da bf16 (və ya int8) və int4 variantlarında işə salın. Eyni 50 sorğu üçün TTFT, TPOT və cavab keyfiyyətini müqayisə edin. Quantization keyfiyyəti harada pozdu?
- Sadə load test yazın: paralel istifadəçi sayını 1, 4, 16, 64 edin və hər səviyyədə throughput ilə p99 TPOT-u qeyd edin. Qrafikdə SLO-nuzu pozmadan əldə edilən maksimal throughput-u tapın və ondan 1M token-in qiymətini hesablayın.