SSD Nodes Learn 🎉 VPS $5.50/ماہ سے
تعلیمی Matt Connorتحریر: Matt Connor

Prefill بمقابلہ decode: پہلا token سست کیوں ہے؟

Prefill compute bound ہوتا ہے اور time to first token طے کرتا ہے، جبکہ decode memory bandwidth bound ہو کر tokens per second طے کرتا ہے۔ دونوں کو الگ ناپیں۔

Prefill بمقابلہ decode، ایک پیراگراف میں

Prefill بمقابلہ decode وہ بنیادی فرق ہے جو self-hosted LLM (large language model) سے متعلق latency کے زیادہ تر سوالات کی وضاحت کرتا ہے۔ Prefill پورے prompt کو ایک ہی pass میں پڑھتا ہے اور compute سے محدود ہوتا ہے۔ Decode جواب ایک وقت میں ایک token لکھتا ہے اور memory bandwidth سے محدود ہوتا ہے۔ پہلے token تک کا وقت prefill کی پیمائش ہے۔ فی سیکنڈ tokens کی تعداد decode کی پیمائش ہے۔

دونوں phases ایک ہی GPU (graphics processing unit) پر، ایک ہی weights کے ساتھ، ایک ہی process کے اندر چلتے ہیں، اس لیے انہیں ایک ہی workload سمجھنا فطری ہے۔ لیکن یہ ایسے کام کرتے ہیں جیسے دو مختلف programs ایک ہی device کو share کر رہے ہوں۔ انہیں الگ سمجھنے سے بظاہر الجھانے والے نتائج کی ایک طویل فہرست قابلِ فہم ہو جاتی ہے۔

‏Prefill compute کا پابند کیوں ہوتا ہے؟

‏Prefill پورے prompt کو ہر layer سے ایک بار گزارتی ہے۔ 2,000 token کے prompt میں ہر matrix multiply کو 2,000 rows کا کام ملتا ہے، اس لیے GPU اپنے load کیے گئے weights کے ہر byte کے لیے بہت زیادہ arithmetic انجام دیتا ہے۔ منتقل کیے گئے ہر byte کے مقابل arithmetic کی اس نسبت کو arithmetic intensity کہتے ہیں، اور prefill میں یہ نسبت زیادہ ہوتی ہے۔ Device اپنی compute limit کے قریب چلتا ہے، جبکہ memory bus میں گنجائش باقی رہتی ہے۔

‏Prefill دو چیزیں تیار کرتی ہے: prompt کے ہر token کے لیے KV cache، یعنی key اور value tensors، اور output کا پہلا token۔ یہ pass مکمل ہونے تک reader تک کچھ نہیں پہنچتا۔ اسی لیے prefill time اور time to first token (TTFT) تقریباً ایک ہی پیمائش ہوتے ہیں۔

‏Prefill کی لاگت prompt کی لمبائی کے ساتھ بڑھتی ہے۔ اس کا linear حصہ ہر layer میں ہونے والا matrix work ہے۔ quadratic حصہ attention ہے، جس میں ہر token اپنے سے پہلے آنے والے ہر token پر attention دیتا ہے، اور طویل context میں یہ نمایاں ہونے لگتا ہے۔ اس لیے prompt کو دوگنا کرنے سے TTFT کم از کم دوگنا ہو جاتا ہے۔

‏آپ اسے ایک منٹ میں دیکھ سکتے ہیں۔ اپنے server کو پہلے 200 token کا prompt بھیجیں، پھر 2,000 token کا prompt بھیجیں، اور دونوں مرتبہ output tokens کی یکساں تعداد طلب کریں۔ TTFT تیزی سے بڑھ جائے گا۔ پہلے token کے بعد streaming speed میں بمشکل کوئی تبدیلی آئے گی۔

ڈیکوڈ کی رفتار memory bandwidth کی وجہ سے محدود کیوں ہوتی ہے؟

ڈیکوڈ ہر step میں ایک token تیار کرتا ہے۔ یہ ایک token تیار کرنے کے لیے GPU کو model کے تمام weights memory سے پڑھنے، ہر weight کو چند operations میں استعمال کرنے، اور پھر اسے خارج کرنے کی ضرورت ہوتی ہے۔ Arithmetic intensity تقریباً 1 ہوتی ہے، اس لیے compute units زیادہ تر وقت انتظار کرتے رہتے ہیں۔

ڈیکوڈ سست ہے کیونکہ ہر token کے لیے پورا model memory سے پڑھنا پڑتا ہے۔ اس لیے رفتار memory bus متعین کرتی ہے اور compute units idle رہتے ہیں۔

اس وجہ سے single stream decode speed کی بالائی حد وہ arithmetic ہے جو کاغذ پر کیا جا سکتا ہے۔ Memory bandwidth کو weights کے زیر استعمال bytes سے تقسیم کریں۔

ChartPublished memory bandwidth and the decode ceiling it implies for a 16 GB model
The data behind this chart
[
  {
    "device": "CPU, dual channel DDR5-5600",
    "mem_bandwidth_gb_s": 90,
    "decode_ceiling_tok_s": 6
  },
  {
    "device": "NVIDIA A10G",
    "mem_bandwidth_gb_s": 600,
    "decode_ceiling_tok_s": 38
  },
  {
    "device": "NVIDIA L40S",
    "mem_bandwidth_gb_s": 864,
    "decode_ceiling_tok_s": 54
  },
  {
    "device": "NVIDIA RTX 4090",
    "mem_bandwidth_gb_s": 1008,
    "decode_ceiling_tok_s": 63
  },
  {
    "device": "NVIDIA A100 80GB SXM",
    "mem_bandwidth_gb_s": 2039,
    "decode_ceiling_tok_s": 127
  },
  {
    "device": "NVIDIA H100 SXM",
    "mem_bandwidth_gb_s": 3350,
    "decode_ceiling_tok_s": 209
  }
]

Bandwidth کالم میں ہر vendor کی شائع کردہ specification figure درج ہے۔ Ceiling کالم اسی figure کو 16 GB سے تقسیم کر کے حاصل کیا گیا ہے۔ 16 GB سے مراد 16 bit precision پر محفوظ 8 billion parameter model کا سائز ہے۔ یہ صرف arithmetic ہے، benchmark کا نتیجہ نہیں۔ آپ کی ناپی گئی rate اس حد سے کم ہوگی۔ یہ جاننا مفید ہے کہ فرق کتنا ہے، کیونکہ اس سے معلوم ہوتا ہے کہ آپ کو serving stack درست کرنا چاہیے یا hardware۔

6 rows کو ترتیب سے پڑھیں تو pattern واضح ہو جاتا ہے۔ Dual channel DDR5 پر CPU تقریباً 90 GB/s منتقل کرتا ہے، جو اس model کے لیے decode کو تقریباً 6 tokens per second تک محدود کر دیتا ہے۔ L40S تقریباً 54 تک پہنچتا ہے۔ H100 SXM، جس کی شائع کردہ bandwidth 3350 GB/s ہے، تقریباً 209 پر رہتا ہے۔

اسی لیے quantization، decode speed بڑھانے کا سب سے مؤثر واحد طریقہ ہے۔ اسی model کو 16 کے بجائے 8 bits پر محفوظ کریں تو ہر token کے لیے پڑھے جانے والے bytes نصف ہو جاتے ہیں، اس لیے بالائی حد تقریباً دوگنی ہو جاتی ہے۔ آپ نے کوئی compute شامل نہیں کیا۔ آپ نے کم memory منتقل کی۔

میں اپنے سرور پر ہر مرحلے کی پیمائش کیسے کروں؟

Ollama response body میں تقسیم شدہ اعداد واپس کرتا ہے۔ non streaming completion کی درخواست کریں اور counters پڑھیں۔

curl -s http://localhost:11434/api/generate -d '{
  "model": "llama3.2",
  "prompt": "Explain memory bandwidth in two sentences.",
  "stream": false
}' | jq '{prompt_eval_count, prompt_eval_duration, eval_count, eval_duration}'

ایسا model tag استعمال کریں جسے آپ واقعی pull کر چکے ہوں، اور ollama list اسے دکھائے گا۔ prompt_eval_count اور prompt_eval_duration prefill سے متعلق ہیں: prompt token count اور اس پر صرف ہونے والا وقت۔ eval_count اور eval_duration decode سے متعلق ہیں۔ Durations nanoseconds میں ہوتی ہیں، اس لیے decode speed eval_count / eval_duration * 1e9 اور prefill speed prompt_eval_count / prompt_eval_duration * 1e9 ہے۔ اسی request میں prefill rate کے decode rate سے کہیں زیادہ ہونے کی توقع رکھیں۔ یہی فرق اس دستاویز کے باقی نکات کی وضاحت کرتا ہے۔

vLLM جیسے OpenAI compatible server کے لیے curl آپ کی طرف سے first byte کا وقت ناپ سکتا ہے۔

curl -N -s -o /dev/null \
  -w 'pretransfer %{time_pretransfer}s  first_byte %{time_starttransfer}s\n' \
  http://localhost:8000/v1/completions \
  -H 'Content-Type: application/json' \
  -d '{"model": "meta-llama/Llama-3.1-8B-Instruct", "prompt": "Explain memory bandwidth.", "max_tokens": 128, "stream": true}'

time_starttransfer وہ وقت ہے جب body کا پہلا byte موصول ہوا، اس لیے "stream": true کے ساتھ یہ TTFT plus connection setup ہے۔ setup cost ختم کرنے کے لیے time_pretransfer منہا کریں۔ اسے دو بار چلائیں اور دوسرا نتیجہ رکھیں، کیونکہ پہلی call میں cold model load شامل ہو سکتا ہے۔

vLLM /metrics پر split کو Prometheus metrics کے طور پر بھی شائع کرتا ہے۔ curl -s http://localhost:8000/metrics | grep -E 'time_to_first_token|inter_token_latency' چلانے سے آپ کو vllm:time_to_first_token_seconds اور vllm:inter_token_latency_seconds histograms ملتے ہیں۔ queue depth کے لیے vllm:num_requests_running اور vllm:num_requests_waiting، اور cache pressure کے لیے vllm:kv_cache_usage_perc شامل کریں۔ یہ پانچ names ہی مکمل dashboard ہیں۔

Load کے دوران vllm bench serve --model <name> --num-prompts 200 --request-rate 4 چلتے ہوئے server کو load کرتا ہے اور percentiles کے ساتھ first token تک کا وقت اور ہر output token کی latency رپورٹ کرتا ہے۔ یہی واحد طریقہ ہے جس سے معلوم ہو سکتا ہے کہ دونوں phases ایک دوسرے کو کیسے متاثر کرتے ہیں۔ کسی بھی tuning سے پہلے صاف baseline حاصل کریں: local LLM پر tokens per second کی پیمائش کا طریقہ ایسا baseline دیتا ہے جو reboot کے بعد بھی برقرار رہتا ہے۔

طویل system prompt پہلے token میں تاخیر کیوں پیدا کرتا ہے، لیکن streaming speed کو کیوں نہیں؟

کیونکہ system prompt صرف prefill کا کام ہے۔ اسے باقی prompt کے ساتھ اسی pass میں ایک بار process کیا جاتا ہے، اور یہ عمل پہلے token کے ظاہر ہونے سے پہلے مکمل ہوتا ہے۔ اس pass کے بعد یہ صرف KV cache entries کی صورت میں موجود رہتا ہے، اور decode ہر دوسری چیز کے ساتھ انہیں بھی پڑھتا ہے۔ اس لیے 3,000 token کا system prompt ہر request میں TTFT بڑھاتا ہے، جبکہ tokens per second تقریباً تبدیل نہیں ہوتے۔

تقریباً، مکمل طور پر نہیں۔ یہ اضافی KV entries ہر decode step پر دوبارہ پڑھی جاتی ہیں، اس لیے بہت طویل prompt decode کو معمولی حد تک سست کرتا ہے۔ اگلا section اسی موضوع کا احاطہ کرتا ہے۔

حل یہ ہے کہ ایک ہی prefix کو بار بار recompute نہ کیا جائے۔ prefix caching والا server مشترکہ prefix کا KV cache محفوظ رکھتا ہے اور اسے دوبارہ استعمال کرتا ہے۔ اس طرح اسی system prompt والی دوسری request prefill کے اس حصے کو مکمل طور پر چھوڑ دیتی ہے۔ vLLM اسے automatic prefix caching کہتا ہے؛ اپنے version میں vllm serve --help دیکھیں، کیونکہ default مختلف releases میں تبدیل ہوتا رہا ہے۔ یہ in-GPU KV cache اس prompt cache سے مختلف ہے جس کے لیے API provider آپ سے billing کرتا ہے، اور KV cache اور prompt cache کے درمیان فرق کو پڑھنا مفید ہے، اس سے پہلے کہ آپ ان میں سے کسی ایک کو tune کریں۔

تکمیل ہونے پر context بھرنے کے ساتھ decode سست کیوں ہو جاتا ہے؟

اس کی دو وجوہات ہیں، اور دونوں کا تعلق KV cache سے ہے۔

پہلی وجہ bandwidth ہے۔ ہر decode step پر attention ہر سابقہ token کی keys اور values پڑھتا ہے۔ Weights ہر token کے لیے ایک مستقل لاگت ہیں۔ KV cache بڑھتی رہنے والی لاگت ہے۔ اس کا سائز model کے config.json سے معلوم کیا جا سکتا ہے: فی token bytes برابر ہیں 2 کو num_hidden_layers، num_key_value_heads، head dimension (hidden_size کو num_attention_heads سے تقسیم کرنے پر حاصل ہونے والی قدر)، اور فی element bytes سے ضرب دینے کے۔ ابتدائی 2 ایک key اور ایک value کو شمار کرتا ہے۔

ایک عام 8 billion parameter layout میں، 32 layers، GQA (grouped query attention) کے تحت 8 key اور value heads، head dimension 128، اور 16 bit precision کے ساتھ حساب 2 x 32 x 8 x 128 x 2 = 131,072 bytes بنتا ہے، یعنی فی token تقریباً 128 KiB۔ اس لیے 8,000 token کی conversation میں ہر request کے لیے تقریباً 1 GB کا KV cache ہوتا ہے۔

دوسری وجہ capacity ہے۔ یہ 1 GB ایسی memory ہے جس میں weights یا کسی دوسرے user کا context نہیں رکھا جا سکتا۔ Server startup کے وقت اپنے KV pool کا سائز متعین کرتا ہے؛ vLLM میں یہ --gpu-memory-utilization کے ذریعے کیا جاتا ہے۔ Pool بھرنے پر نئی requests انتظار کرتی ہیں۔ vllm:num_requests_waiting کا بڑھنا، جبکہ vllm:kv_cache_usage_perc تقریباً 1 کے قریب رہنا، اسی حالت کی واضح علامت ہے۔ کچھ stacks کسی جاری request کو عارضی طور پر روک کر اس کا cache بعد میں دوبارہ بناتے ہیں، بجائے اس کے کہ request کو queue میں رکھا جائے۔ User کو stream کے درمیان یہ عمل stall کی صورت میں محسوس ہوتا ہے۔

Long context کی لاگت دو مرتبہ ادا کرنی پڑتی ہے: شروع میں زیادہ prefill کام، اور جواب کے باقی حصے کے دوران ہر token کے لیے زیادہ memory read۔

بیچنگ تھروپٹ کو بہتر اور tail latency کو خراب کیوں کرتی ہے؟

چونکہ decode، bandwidth bound ہوتا ہے، اس لیے اضافی requests compute کے لحاظ سے تقریباً مفت ہوتی ہیں۔ Weights کو ایک بار پڑھنے سے batch میں موجود ہر sequence کے لیے ایک token تیار ہو سکتا ہے۔ اس لیے batch size بڑھنے کے ساتھ مجموعی throughput تقریباً خطی طور پر بڑھتا ہے، یہاں تک کہ یا تو KV pool ختم ہو جائے یا batch اتنا بڑا ہو جائے کہ وہ دوبارہ compute bound بن جائے۔ Continuous batching ہر step پر batch دوبارہ بناتی ہے۔ اس طرح مکمل ہونے والی request نکل جاتی ہے اور queue میں موجود request اپنے ساتھیوں کا انتظار کیے بغیر شامل ہو جاتی ہے۔

اس کا اثر percentiles میں ظاہر ہوتا ہے۔ اب ہر صارف کا اگلا token مشترکہ step کے سب سے سست حصے کا انتظار کرتا ہے۔ اس لیے p50، یعنی median، قابل قبول رہتا ہے، جبکہ p99، یعنی ہر 100 requests میں سب سے سست 1 request، زیادہ دیر لیتا ہے۔ لوگ p99 کو محسوس کرتے ہیں، کیونکہ یہ جملے کے درمیان آنے والا وقفہ ہوتا ہے۔

Prefill اس فرق کو مزید نمایاں کرتا ہے۔ stream کے دوران آنے والا بڑا prompt device کو ایک طویل step کے لیے مصروف رکھتا ہے، اور اس وقت streaming کرنے والے تمام صارفین کو وقفہ محسوس ہوتا ہے۔ Chunked prefill اس مسئلے کا زیادہ تر حصہ ختم کرتا ہے۔ یہ طویل prompt کو حصوں میں تقسیم کرتا ہے اور ہر حصے کو decode batches میں شامل کرتا ہے۔ August 2026 تک vLLM V1 engine یہ عمل default طور پر کرتا ہے اور اس توازن کو --max-num-batched-tokens کے ذریعے کنٹرول کرنے دیتا ہے۔ vLLM tuning documentation اس tradeoff کو واضح طور پر بیان کرتی ہے: تقریباً 2048 جیسی چھوٹی values بہتر inter token latency (ITL) دیتی ہیں، کیونکہ کم prefills، decodes میں خلل ڈالتی ہیں؛ جبکہ بڑی values بہتر TTFT دیتی ہیں، کیونکہ ایک batch میں زیادہ prefill tokens شامل ہو جاتے ہیں۔ یہ واحد flag، prefill اور decode کے درمیان توازن کو ایک ایسی number کے طور پر ظاہر کرتا ہے جسے آپ تبدیل کر سکتے ہیں۔ p99 کس حد پر ناقابل قبول ہوتا ہے، یہ capacity کا سوال ہے، اور ایک self-hosted LLM کتنے concurrent users کو serve کر سکتا ہے اسی metrics کے ذریعے اس کا جائزہ لیتا ہے۔

بڑا GPU کبھی کبھی کوئی فرق کیوں نہیں ڈالتا؟

کیونکہ بڑا GPU عموماً زیادہ compute فراہم کرتا ہے، جبکہ decode کو compute کی ضرورت نہیں ہوتی۔

اوپر دیے گئے chart کی دو rows کا موازنہ کریں۔ A100 80GB کی شائع شدہ bandwidth 2039 GB/s ہے، جبکہ L40S کی 864 GB/s ہے۔ decode ceiling بھی عین اسی نسبت سے ہے: بالترتیب 127 tokens per second اور 54۔ RTX 4090 زیادہ تر پیمانوں کے لحاظ سے بہت تیز card ہے، لیکن اس کی 1008 GB/s bandwidth اس کی ceiling کو 63 تک محدود کرتی ہے۔ دو cards کے درمیان دیگر خصوصیات میں جو بھی فرق ہو، single stream decode specification sheet میں دی گئی bandwidth line کی پیروی کرتا ہے۔

اس لیے decode کو تیز کرنے کے دو طریقے ہیں: ہر token کے لیے کم bytes پڑھیں، یعنی weights کو quantize کریں یا چھوٹا model چلائیں؛ یا زیادہ bandwidth خریدیں۔ Prefill اس کے برعکس ہے۔ اسے compute درکار ہوتی ہے، اس لیے تیز card طویل prompts پر TTFT کو واقعی کم کرتا ہے۔ اگر شکایت یہ ہے کہ پہلا token آنے میں چار seconds لگتے ہیں تو بہتر hardware مسئلہ حل کر سکتا ہے۔ اگر شکایت یہ ہے کہ text آہستہ آہستہ type ہوتا ہے تو غالباً بہتر hardware اس میں مدد نہیں کرے گا۔

کیا آپ کو prefill اور decode الگ workers پر چلانے چاہییں؟

بڑے serving stacks عین یہی کام کرتے ہیں، اور اس تکنیک کو prefill اور decode disaggregation کہا جاتا ہے۔ workers کا ایک pool صرف prefill چلاتا ہے، جبکہ دوسرا pool صرف decode چلاتا ہے۔ پہلے pool کی تیار کردہ KV cache تیز interconnect کے ذریعے دوسرے pool کو منتقل کی جاتی ہے۔ یہ اس لیے کام کرتا ہے کہ دونوں phases کو مختلف hardware اور مختلف scheduling درکار ہوتی ہے۔ Prefill کو زیادہ compute اور بڑے token batches درکار ہوتے ہیں۔ Decode کو زیادہ bandwidth اور بیک وقت چلنے والی بہت سی sequences درکار ہوتی ہیں۔ انہیں الگ کرنے سے ہر pool کو اپنی ضرورت کے مطابق scale کیا جا سکتا ہے، اور کوئی ایک بہت بڑا prompt تمام فعال streams کو روک نہیں دیتا۔

ایک GPU والے single VPS (virtual private server) پر ایسا کرنا تقریباً کبھی فائدہ مند نہیں ہوتا۔ آپ ایک ہی device کو اپنے خلاف تقسیم کر رہے ہوں گے، اور pointer کو کئی gigabytes کی cache کے network transfer میں تبدیل کر دیں گے۔ یہ تکنیک اس وقت فائدہ دیتی ہے جب آپ کے پاس اتنے accelerators ہوں کہ ہر phase کے لیے پوری machines مختص کی جا سکیں، اور اتنا مسلسل traffic ہو کہ دونوں pools مصروف رہیں۔ اس سے کم scale پر ایک flag کے ذریعے chunked prefill تقریباً وہی isolation فراہم کرتا ہے۔

خراب نمبروں کی صورت میں کیا تبدیل کریں

جب TTFT بہت زیادہ ہو:

  • prompt مختصر کریں۔ Prefill کی لاگت prompt tokens کے ساتھ بڑھتی ہے، اور system prompt ہر request پر دوبارہ ادا کرنا پڑتا ہے۔
  • prefix caching فعال کریں تاکہ بار بار آنے والا prefix ہر بار دوبارہ compute ہونے کے بجائے ایک مرتبہ compute ہو۔
  • --max-num-batched-tokens بڑھائیں تاکہ ہر step میں prefill کا زیادہ کام مکمل ہو۔
  • model کو موردِ الزام ٹھہرانے سے پہلے queue چیک کریں۔ vllm:num_requests_waiting کا صفر سے زیادہ ہونا ظاہر کرتا ہے کہ request شروع نہیں ہوئی تھی؛ یہ capacity کا مسئلہ ہے۔

جب tokens per second بہت کم ہوں:

  • weights کو quantize کریں۔ ہر weight کے لیے کم bytes کا مطلب ہے کہ ہر token کے لیے کم bytes read ہوں گے۔
  • اپنے card کی شائع شدہ memory bandwidth کو اوپر دیے گئے chart سے ملائیں اور دیکھیں کہ آپ ceiling کے کتنے قریب ہیں۔
  • --max-num-batched-tokens کم کریں تاکہ prefill، decode میں کم مرتبہ مداخلت کرے۔
  • context length چیک کریں۔ ہزاروں tokens تک پھیل جانے والی conversation ہر step میں کہیں بڑا KV cache read کرتی ہے۔

یہاں runtime بھی اہم ہے، کیونکہ Ollama اور vLLM prefill اور decode کو مختلف طریقے سے schedule کرتے ہیں، اور جو setting ایک میں مدد دیتی ہے وہ دوسرے میں کچھ بھی نہیں کر سکتی۔ پہلے دونوں phases میں measurement کریں، پھر ایک وقت میں صرف ایک چیز تبدیل کریں۔

FAQ

میرے پہلے token میں کئی seconds کیوں لگتے ہیں، لیکن باقی tokens تیزی سے stream ہوتے ہیں؟

یہ انتظار prefill ہے، جبکہ streaming decode ہے۔ Prefill کسی بھی output کے وجود میں آنے سے پہلے پورے prompt کو ایک ہی compute-bound pass میں process کرتا ہے، اس لیے اس کی لاگت prompt کی لمبائی کے ساتھ بڑھتی ہے۔ Decode اس کے بعد ہر step میں ایک token جاری کرتا ہے، اور اس کی رفتار memory bandwidth سے متعین ہوتی ہے، جو prompt کی لمبائی سے تقریباً آزاد ہوتی ہے۔ ہر request کے ساتھ ایک طویل system prompt بھیجنا اس کی عام وجہ ہے۔ Prefix caching اس لاگت کے دہرائے جانے والے حصے کو ختم کرتی ہے۔

کیا طویل prompt tokens per second کی رفتار کم کرتا ہے؟

کچھ حد تک، لیکن اس کی وجہ TTFT سے مختلف ہے۔ ہر decode step میں پچھلے تمام tokens کی keys اور values پڑھی جاتی ہیں، اس لیے بڑا KV cache ہر token کے لیے زیادہ bytes پڑھنے کا باعث بنتا ہے۔ عام 8 billion parameter layout میں cache تقریباً 128 KiB فی token ہوتا ہے، اس لیے 8,000 token کا context تقریباً 1 GB بنتا ہے، جسے ہر step پر access کیا جاتا ہے۔ طویل prompt کا بڑا اثر اب بھی TTFT پر ہوتا ہے، streaming speed پر نہیں۔

کون سی GPU specification decode speed کا اندازہ دیتی ہے؟

Memory bandwidth۔ شائع شدہ bandwidth کو memory میں موجود weights کے size سے تقسیم کریں تو ایک stream کے لیے arithmetic ceiling حاصل ہو جاتی ہے۔ جس card میں compute زیادہ ہو لیکن bandwidth وہی ہو، وہ زیادہ تیزی سے stream نہیں کرے گا۔ اسی وجہ سے 8 bits پر quantizing کرنے سے decode speed تقریباً دوگنی ہو جاتی ہے: compute کو تبدیل کیے بغیر ہر token کے لیے پڑھے جانے والے bytes نصف ہو جاتے ہیں۔

users شامل کرنے سے throughput کیوں بڑھتا ہے، لیکن ہر user کو سروس سست کیوں محسوس ہوتی ہے؟

Weights کو ایک مرتبہ پڑھنا batch میں موجود ہر sequence کے لیے ایک token serve کرتا ہے، اس لیے batch size بڑھنے کے ساتھ total tokens per second بڑھتا ہے۔ اب ہر individual token کو مشترکہ step کے لیے انتظار کرنا پڑتا ہے، اس لیے اسی وقت فی user latency بھی بڑھ جاتی ہے۔ Aggregate throughput number کے بجائے p99 inter token latency کو monitor کریں، اور vllm:num_requests_waiting دیکھ کر معلوم کریں کہ requests queueing میں ہیں یا چل رہی ہیں۔