SSD Nodes Learn 🎉 VPS $5.50/माह से
गाइड Matt Connorलेखक: Matt Connor

LLM में Prefill और Decode के बीच अंतर क्या है?

Prefill compute bound है जो Time to first token तय करता है, जबकि Decode memory bandwidth bound है जो Tokens per second को नियंत्रित करता है। इन्हें अलग-अलग मापें।

Prefill बनाम decode, एक पैराग्राफ में

Self-hosted LLM (large language model) से संबंधित अधिकांश latency संबंधी प्रश्नों को समझने के लिए Prefill बनाम decode का अंतर सबसे महत्वपूर्ण है। Prefill पूरे prompt को एक ही बार में पढ़ता है और इसकी गति compute क्षमता द्वारा सीमित होती है। Decode उत्तर को एक बार में एक token करके लिखता है और यह memory bandwidth द्वारा सीमित होता है। Time to first token एक prefill संख्या है। Tokens per second एक decode संख्या है।

ये दोनों चरण एक ही GPU (graphics processing unit) पर, एक ही weights के साथ, एक ही process के भीतर चलते हैं, इसलिए इन्हें एक ही workload मानना स्वाभाविक है। ये एक ही device को साझा करने वाले दो अलग-अलग programs की तरह व्यवहार करते हैं। यदि आप इन्हें अलग-अलग करके देखें, तो भ्रमित करने वाले परिणामों की लंबी सूची का अर्थ स्पष्ट हो जाता है।

Prefill compute bound क्यों है?

Prefill पूरे prompt को हर layer से एक बार गुजारता है। 2,000 token का prompt हर matrix multiply को 2,000 rows का काम देता है, इसलिए GPU लोड किए गए हर byte के लिए बहुत अधिक arithmetic करता है। इस अनुपात को, यानी प्रति byte move होने वाली arithmetic, arithmetic intensity कहा जाता है, और prefill में यह बहुत अधिक होती है। डिवाइस अपनी compute limit के करीब चलता है और memory bus पर दबाव कम रहता है।

Prefill दो चीजें तैयार करता है: हर prompt token के लिए KV cache (key और value tensors), और पहला output token। जब तक यह pass पूरा नहीं होता, तब तक user को कुछ नहीं मिलता, यही कारण है कि prefill time और time to first token (TTFT) लगभग एक ही माप हैं।

Prefill की लागत prompt की लंबाई के साथ बढ़ती है। इसका linear हिस्सा प्रति layer matrix का काम है। quadratic हिस्सा attention है, जहाँ हर token पिछले हर token पर ध्यान देता है, और लंबे context में यह महत्वपूर्ण हो जाता है। इसलिए prompt को दोगुना करने पर TTFT कम से कम दोगुना हो जाता है।

आप इसे एक मिनट में देख सकते हैं। अपने server पर 200 token का prompt भेजें, फिर 2,000 token का prompt भेजें, और दोनों बार समान संख्या में output tokens मांगें। TTFT तेजी से बढ़ेगा। पहले token के बाद streaming की गति में बहुत कम बदलाव आएगा।

Decode memory bandwidth bound क्यों है?

Decode हर चरण में एक टोकन उत्पन्न करता है। उस एक टोकन को उत्पन्न करने के लिए GPU को मेमोरी से मॉडल के प्रत्येक weight को पढ़ना पड़ता है, प्रत्येक weight का कुछ ऑपरेशन्स के लिए उपयोग करना पड़ता है, और फिर उसे हटाना पड़ता है। Arithmetic intensity 1 के करीब होती है, इसलिए compute units अपना अधिकांश समय प्रतीक्षा में बिताते हैं।

Decode धीमा है क्योंकि प्रत्येक टोकन के लिए पूरे मॉडल को मेमोरी से पढ़ना पड़ता है, इसलिए memory bus गति निर्धारित करती है और compute units खाली बैठे रहते हैं।

यह सिंगल स्ट्रीम decode गति की अधिकतम सीमा को वह गणित बनाता है जिसे आप कागज पर कर सकते हैं। Memory bandwidth को उन bytes से विभाजित करें जो weights द्वारा घेरे जाते हैं।

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 कॉलम में प्रत्येक विक्रेता का प्रकाशित विनिर्देश (specification) आंकड़ा है। Ceiling कॉलम वह आंकड़ा है जिसे 16 GB से विभाजित किया गया है, जो 16 bit precision पर संग्रहीत 8 बिलियन पैरामीटर वाले मॉडल का आकार है। यह गणित है, न कि कोई benchmark परिणाम। आपकी मापी गई दर इससे नीचे रहेगी, और यह जानना उपयोगी है कि वह कितनी नीचे है, क्योंकि इससे आपको पता चलता है कि आपको अपने serving stack को ठीक करना है या अपने hardware को।

6 पंक्तियों को क्रम में पढ़ें और पैटर्न स्पष्ट है। Dual channel DDR5 पर एक CPU लगभग 90 GB/s की गति से डेटा स्थानांतरित करता है, जो उस मॉडल के लिए decode को लगभग 6 टोकन प्रति सेकंड पर सीमित कर देता है। एक L40S लगभग 54 पर आता है। एक H100 SXM, जिसकी प्रकाशित bandwidth 3350 GB/s है, लगभग 209 पर स्थित है।

यही कारण है कि quantization decode गति पर सबसे शक्तिशाली एकल लीवर है। उसी मॉडल को 16 bits के बजाय 8 bits पर स्टोर करें और आप प्रति टोकन पढ़े जाने वाले bytes को आधा कर देते हैं, इसलिए अधिकतम सीमा लगभग दोगुनी हो जाती है। आपने कोई compute नहीं बढ़ाया। आपने कम मेमोरी का उपयोग किया।

मैं अपने सर्वर पर प्रत्येक चरण को कैसे मापूँ?

Ollama रिस्पॉन्स बॉडी में विभाजन (split) लौटाता है। एक नॉन-स्ट्रीमिंग कंप्लीशन (non-streaming completion) का अनुरोध करें और काउंटर्स को पढ़ें।

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}'

उस मॉडल टैग का उपयोग करें जिसे आपने वास्तव में पुल (pull) किया है, जिसे ollama list आपको दिखाएगा। prompt_eval_count और prompt_eval_duration प्रीफिल (prefill) हैं: प्रॉम्प्ट टोकन की संख्या और उस पर खर्च किया गया समय। eval_count और eval_duration डिकोड (decode) हैं। अवधि नैनोसेकंड में है, इसलिए डिकोड गति eval_count / eval_duration * 1e9 है और प्रीफिल गति prompt_eval_count / prompt_eval_duration * 1e9 है। उम्मीद करें कि एक ही अनुरोध पर प्रीफिल दर, डिकोड दर से काफी अधिक होगी। वह अंतर ही वह चीज है जिसे यहाँ बाकी सब कुछ समझाता है।

vLLM जैसे OpenAI संगत सर्वर के लिए, 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 वह क्षण है जब पहला बॉडी बाइट प्राप्त हुआ, इसलिए "stream": true के साथ यह TTFT और कनेक्शन सेटअप है। सेटअप लागत को हटाने के लिए time_pretransfer को घटाएं। इसे दो बार चलाएं और दूसरे परिणाम को रखें, क्योंकि पहली कॉल में कोल्ड मॉडल लोड शामिल हो सकता है।

vLLM विभाजन को /metrics पर प्रोमेथियस मेट्रिक्स (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 मिलेंगे। कतार की गहराई (queue depth) के लिए vllm:num_requests_running और vllm:num_requests_waiting, और कैश प्रेशर (cache pressure) के लिए vllm:kv_cache_usage_perc जोड़ें। वे पाँच नाम ही पूरा डैशबोर्ड हैं।

लोड के तहत, vllm bench serve --model <name> --num-prompts 200 --request-rate 4 चल रहे सर्वर को संचालित करता है और परसेंटाइल के साथ पहले टोकन तक के समय और प्रति आउटपुट टोकन लेटेंसी की रिपोर्ट करता है, जो इन दो चरणों को एक-दूसरे से मुकाबला करते हुए देखने का एकमात्र तरीका है। किसी भी चीज़ को ट्यून करने से पहले, एक स्पष्ट बेसलाइन लें: लोकल LLM पर टोकन प्रति सेकंड मापने की विधि आपको एक ऐसी बेसलाइन देती है जो रीबूट के बाद भी बनी रहती है।

लंबा सिस्टम प्रॉम्प्ट पहले टोकन में देरी क्यों करता है, लेकिन स्ट्रीमिंग की गति में नहीं?

इसका कारण यह है कि सिस्टम प्रॉम्प्ट केवल प्रीफिल (prefill) का कार्य है और कुछ नहीं। पहला टोकन दिखाई देने से पहले, इसे प्रॉम्प्ट के बाकी हिस्सों के साथ एक ही बार में प्रोसेस किया जाता है। उस पास के बाद, यह केवल KV cache प्रविष्टियों के रूप में मौजूद रहता है, और डिकोड (decode) प्रक्रिया बाकी सब चीजों के साथ इन्हें भी पढ़ती है। इसलिए, 3,000 टोकन का सिस्टम प्रॉम्प्ट हर एक रिक्वेस्ट पर TTFT (Time To First Token) को बढ़ा देता है, जबकि प्रति सेकंड टोकन की गति लगभग अपरिवर्तित रहती है।

लगभग, पूरी तरह से नहीं। उन अतिरिक्त KV प्रविष्टियों को हर डिकोड चरण में फिर से पढ़ा जाता है, इसलिए बहुत लंबा प्रॉम्प्ट डिकोड को थोड़ा धीमा कर देता है। अगला सेक्शन इसे कवर करता है।

इसका समाधान एक ही प्रीफिक्स (prefix) को बार-बार रीकम्प्यूट करना बंद करना है। प्रीफिक्स कैशिंग वाला सर्वर एक साझा प्रीफिक्स के KV cache को सुरक्षित रखता है और उसका पुन: उपयोग करता है, इसलिए समान सिस्टम प्रॉम्प्ट वाली दूसरी रिक्वेस्ट प्रीफिल के उस हिस्से को पूरी तरह से छोड़ देती है। vLLM इसे automatic prefix caching कहता है; अपने वर्ज़न पर vllm serve --help चेक करें, क्योंकि डिफ़ॉल्ट सेटिंग रिलीज़ के साथ बदलती रहती है। वह इन-GPU KV cache उस प्रॉम्प्ट कैश से अलग है जिसके लिए API प्रदाता आपसे शुल्क लेता है, और KV cache और प्रॉम्प्ट कैश के बीच का अंतर को पढ़ना उपयोगी है, इससे पहले कि आप इनमें से किसी को भी ट्यून करें।

Context भरने पर decode धीमा क्यों हो जाता है?

इसके दो कारण हैं, और दोनों KV cache से संबंधित हैं।

पहला कारण bandwidth है। हर decode step पर, attention mechanism पिछले सभी tokens की keys और values को पढ़ता है। Weights की लागत हर token के लिए स्थिर रहती है, लेकिन KV cache का आकार बढ़ता रहता है। आप इसके आकार की गणना model के config.json से कर सकते हैं: bytes per token का मान 2 को num_hidden_layers, num_key_value_heads, head dimension (hidden_size को num_attention_heads से विभाजित करने पर) और bytes per element से गुणा करने पर मिलता है। शुरुआती 2 का अंक एक key और एक value को दर्शाता है।

एक सामान्य 8 billion parameter layout के लिए, जिसमें 32 layers, GQA (grouped query attention) के तहत 8 key और value heads, 128 head dimension और 16 bit precision हो, तो यह 2 x 32 x 8 x 128 x 2 = 131,072 bytes, यानी प्रति token लगभग 128 KiB होता है। इसलिए, 8,000 tokens की एक conversation में प्रति request लगभग 1 GB का KV cache होता है।

दूसरा कारण capacity है। वह 1 GB ऐसी memory है जिसमें weights या किसी अन्य user का context नहीं रखा जा सकता। सर्वर startup के समय ही KV pool का आकार निर्धारित कर लेता है, vLLM में यह --gpu-memory-utilization के माध्यम से होता है, और जब pool भर जाता है तो नई requests को प्रतीक्षा करनी पड़ती है। vllm:num_requests_waiting का बढ़ना जबकि vllm:kv_cache_usage_perc का 1 के करीब बने रहना, इसी स्थिति का सटीक संकेत है। कुछ stacks queue में लगाने के बजाय चल रही request को preempt कर देते हैं और बाद में उसका cache recompute करते हैं, जिसे user stream के बीच में एक रुकावट (stall) के रूप में अनुभव करता है।

Long context आपको दो बार महंगा पड़ता है: शुरुआत में अधिक prefill work, और उत्तर के शेष भाग के लिए प्रति token अधिक memory read।

Batching थ्रूपुट (throughput) को बेहतर और टेल लेटेंसी (tail latency) को खराब क्यों करती है?

चूंकि decode प्रक्रिया बैंडविड्थ (bandwidth) पर निर्भर होती है, इसलिए compute साइड पर अतिरिक्त requests लगभग मुफ्त होती हैं। weights को एक बार पढ़ने से batch में मौजूद हर sequence के लिए एक token तैयार किया जा सकता है। इसलिए, कुल थ्रूपुट batch size के साथ लगभग रैखिक (linearly) रूप से बढ़ता है, जब तक कि या तो KV pool खत्म न हो जाए या batch इतना बड़ा न हो जाए कि वह फिर से compute bound हो जाए। Continuous batching हर step पर batch को फिर से तैयार करती है, इसलिए एक पूरी हो चुकी request बाहर निकल जाती है और एक queued request अपने पड़ोसियों का इंतज़ार किए बिना उसमें शामिल हो जाती है।

इसका असर percentiles में दिखाई देता है। अब प्रत्येक user का अगला token एक shared step के सबसे धीमे हिस्से का इंतज़ार करता है। इस कारण p50 (median) तो स्वीकार्य बना रहता है, लेकिन p99 (100 में से सबसे धीमी 1 request) बढ़ जाती है। लोग p99 पर ही ध्यान देते हैं, क्योंकि यह वाक्य के बीच में आने वाला ठहराव (pause) है।

Prefill इसे और अधिक स्पष्ट कर देता है। stream के बीच में आने वाला एक बड़ा prompt डिवाइस को एक लंबे step के लिए व्यस्त कर देता है, और जो भी इस समय streaming कर रहे होते हैं, उन्हें एक अंतराल (gap) महसूस होता है। Chunked prefill एक लंबे prompt को टुकड़ों में काटकर और हर टुकड़े को decode batches में मिलाकर इस समस्या को काफी हद तक दूर कर देता है। अगस्त 2026 तक, vLLM V1 engine डिफ़ॉल्ट रूप से ऐसा करता है और --max-num-batched-tokens के माध्यम से संतुलन को नियंत्रित करने की सुविधा देता है। vLLM ट्यूनिंग दस्तावेज़ इस ट्रेड-ऑफ को स्पष्ट रूप से बताते हैं: लगभग 2048 जैसे छोटे मान बेहतर inter token latency (ITL) देते हैं क्योंकि कम prefills, decodes में बाधा डालते हैं, और बड़े मान बेहतर TTFT देते हैं क्योंकि एक batch में अधिक prefill tokens समा सकते हैं। वह एक flag prefill बनाम decode का है, जिसे एक ऐसी संख्या के रूप में प्रस्तुत किया गया है जिसे आप बदल सकते हैं। p99 कब स्वीकार्य नहीं रहता, यह क्षमता (capacity) का प्रश्न है, और एक self-hosted LLM कितने concurrent users को सेवा दे सकता है लेख में इन्हीं metrics के साथ इसका समाधान समझाया गया है।

बड़ा GPU होने पर भी कभी-कभी कोई बदलाव क्यों नहीं होता?

क्योंकि बड़ा होने का अर्थ आमतौर पर अधिक compute क्षमता है, और decode प्रक्रिया को compute की आवश्यकता नहीं होती है।

ऊपर दिए गए चार्ट की दो पंक्तियों की तुलना करें। A100 80GB की प्रकाशित bandwidth 2039 GB/s है, जबकि L40S की 864 GB/s है, और decode की सीमा बिल्कुल इसी के अनुसार है: 127 tokens प्रति सेकंड बनाम 54। RTX 4090 अधिकांश पैमानों पर एक बहुत तेज़ कार्ड है और इसकी 1008 GB/s की bandwidth इसकी सीमा को 63 पर निर्धारित करती है। दो कार्डों के बीच अन्य कोई भी अंतर हो, single stream decode हमेशा specification sheet पर दी गई bandwidth लाइन का अनुसरण करता है।

इसलिए decode को तेज़ करने के दो ही तरीके हैं: प्रति token कम bytes पढ़ना (weights को quantize करना, या छोटा model चलाना), या अधिक bandwidth खरीदना। Prefill इसके विपरीत है। इसे compute की आवश्यकता होती है, इसलिए एक तेज़ कार्ड लंबे prompts पर TTFT को वास्तव में कम कर देता है। यदि शिकायत यह है कि पहला token आने में चार सेकंड लगते हैं, तो बेहतर hardware इसे ठीक कर सकता है। यदि शिकायत यह है कि text धीरे-धीरे टाइप हो रहा है, तो hardware बदलने से शायद ही कोई सुधार होगा।

क्या आपको prefill और decode को अलग-अलग workers पर चलाना चाहिए?

बड़े serving stacks बिल्कुल यही करते हैं, और इस तकनीक को prefill और decode disaggregation कहा जाता है। workers का एक pool केवल prefill चलाता है, दूसरा pool केवल decode चलाता है, और पहले द्वारा बनाया गया KV cache एक fast interconnect के माध्यम से दूसरे में transfer किया जाता है। यह इसलिए काम करता है क्योंकि इन चरणों को अलग-अलग hardware और अलग-अलग scheduling की आवश्यकता होती है। Prefill को compute और बड़े token batches चाहिए होते हैं। Decode को bandwidth और कई concurrent sequences की आवश्यकता होती है। इन्हें अलग करने से प्रत्येक pool को स्वतंत्र रूप से scale किया जा सकता है, और यह एक विशाल prompt को हर active stream को रोकने से रोकता है।

एक GPU वाले single VPS (virtual private server) पर ऐसा करना लगभग कभी भी फायदेमंद नहीं होता है। आप एक ही device को खुद के विरुद्ध विभाजित कर रहे होंगे, और आप एक pointer को gigabytes cache के network transfer में बदल देंगे। यह तकनीक तब कारगर होती है जब आपके पास प्रत्येक चरण के लिए पूरी मशीनें समर्पित करने हेतु पर्याप्त accelerators हों, और दोनों pools को व्यस्त रखने के लिए पर्याप्त steady traffic हो। इससे कम traffic होने पर, chunked prefill एक flag के साथ लगभग वही isolation प्रदान कर देता है।

संख्या गलत होने पर क्या बदलें

जब TTFT बहुत अधिक हो:

  • Prompt को छोटा करें। Prefill cost में prompt tokens की गणना होती है, और system prompt के लिए हर request पर भुगतान करना पड़ता है।
  • Prefix caching को चालू करें ताकि बार-बार आने वाले prefix को हर बार compute करने के बजाय एक ही बार किया जाए।
  • --max-num-batched-tokens को बढ़ाएं ताकि प्रत्येक step में अधिक prefill कार्य हो सके।
  • Model को दोष देने से पहले queue की जाँच करें। शून्य से ऊपर vllm:num_requests_waiting का मतलब है कि request शुरू ही नहीं हुई थी, जो कि एक capacity समस्या है।

जब tokens per second बहुत कम हो:

  • Weights को Quantize करें। प्रति weight कम bytes का मतलब है प्रति token कम bytes पढ़ना।
  • अपने card की प्रकाशित memory bandwidth की तुलना ऊपर दिए गए chart से करें और देखें कि आप उसकी सीमा के कितने करीब हैं।
  • --max-num-batched-tokens को कम करें ताकि prefills, decode में कम बाधा डालें।
  • Context length की जाँच करें। हजारों tokens तक बढ़ चुकी बातचीत हर step पर बहुत बड़ा KV cache पढ़ती है।

यहाँ runtime भी मायने रखता है, क्योंकि Ollama और vLLM prefill और decode को अलग-अलग तरीके से schedule करते हैं, और जो setting एक के लिए मददगार है, वह दूसरे पर कोई असर नहीं डाल सकती। पहले दोनों चरणों में मापें, फिर एक बार में केवल एक चीज बदलें।

FAQ

पहला टोकन आने में कुछ सेकंड क्यों लगते हैं जबकि बाकी तेजी से स्ट्रीम होते हैं?

प्रतीक्षा का समय प्रीफिल (prefill) है, और स्ट्रीमिंग डिकोड (decode) है। प्रीफिल किसी भी आउटपुट के आने से पहले पूरे प्रॉम्प्ट को एक compute-bound पास में प्रोसेस करता है, इसलिए इसकी लागत प्रॉम्प्ट की लंबाई के साथ बढ़ती है। डिकोड फिर मेमोरी बैंडविड्थ द्वारा निर्धारित दर पर प्रति चरण एक टोकन उत्सर्जित करता है, जो इस बात से लगभग स्वतंत्र है कि प्रॉम्प्ट कितना लंबा था। हर अनुरोध पर एक लंबा सिस्टम प्रॉम्प्ट इसका सामान्य कारण है। प्रीफिक्स कैशिंग (prefix caching) उस लागत के दोहराव वाले हिस्से को हटा देती है।

क्या लंबा प्रॉम्प्ट टोकन प्रति सेकंड की गति को धीमा कर देता है?

थोड़ा सा, और यह TTFT से अलग कारण से होता है। हर डिकोड चरण पिछले सभी टोकन की keys और values को पढ़ता है, इसलिए बड़े KV कैश का मतलब है प्रति टोकन अधिक बाइट्स का पढ़ा जाना। एक सामान्य 8 बिलियन पैरामीटर लेआउट के लिए कैश लगभग 128 KiB प्रति टोकन होता है, इसलिए 8,000 टोकन का संदर्भ लगभग 1 GB है जिसे हर चरण में एक्सेस किया जाता है। लंबे प्रॉम्प्ट का बड़ा प्रभाव अभी भी TTFT पर होता है, स्ट्रीमिंग गति पर नहीं।

कौन सा GPU स्पेसिफिकेशन डिकोड गति का अनुमान लगाता है?

मेमोरी बैंडविड्थ। प्रकाशित बैंडविड्थ को मेमोरी में मौजूद वेट्स (weights) के आकार से विभाजित करें और आपको एक स्ट्रीम के लिए अंकगणितीय सीमा (arithmetic ceiling) मिल जाएगी। अधिक कंप्यूट लेकिन समान बैंडविड्थ वाला कार्ड तेजी से स्ट्रीम नहीं करेगा। यही कारण है कि 8 बिट्स में क्वांटाइज करने से डिकोड गति लगभग दोगुनी हो जाती है: यह कंप्यूट को छुए बिना प्रति टोकन पढ़े जाने वाले बाइट्स को आधा कर देता है।

जब मैं अधिक उपयोगकर्ता जोड़ता हूँ तो थ्रूपुट क्यों बढ़ जाता है लेकिन प्रत्येक उपयोगकर्ता को गति धीमी क्यों महसूस होती है?

वेट्स (weights) का एक बार पढ़ा जाना बैच में प्रत्येक अनुक्रम के लिए एक टोकन प्रदान करता है, इसलिए प्रति सेकंड कुल टोकन बैच आकार के साथ बढ़ते हैं। अब प्रत्येक व्यक्तिगत टोकन एक साझा चरण की प्रतीक्षा करता है, इसलिए उसी समय प्रति उपयोगकर्ता लेटेंसी बढ़ जाती है। कुल थ्रूपुट संख्या के बजाय p99 इंटर-टोकन लेटेंसी पर नज़र रखें, और यह देखने के लिए vllm:num_requests_waiting की जाँच करें कि क्या अनुरोध चल रहे हैं या कतार में लगे हैं।