Self-hosted LLM 5 users पर धीमा क्यों हो जाता है?
एक user पर LLM सही चलता है लेकिन पांच पर अटक जाता है। जानें कि कैसे KV cache, batching और queue depth आपके सर्वर की क्षमता तय करते हैं और इसे कैसे सुधारें।
जब अधिक उपयोगकर्ता आते हैं तो self-hosted LLM धीमा क्यों हो जाता है?
एक self-hosted LLM 5 concurrent उपयोगकर्ताओं पर इसलिए रुक जाता है क्योंकि सर्वर अभी भी एक बार में केवल एक ही उत्तर generate कर रहा है, और बाकी चार कतार (queue) में खड़े हैं। Ollama का documentation default सेटिंग के बारे में स्पष्ट है: OLLAMA_NUM_PARALLEL का अर्थ है "अधिकतम समानांतर requests जिन्हें प्रत्येक model एक ही समय में process करेगा, default 1 है।" कुछ भी खराब नहीं है। आपके पाँच में से चार लोग अपनी बारी का इंतज़ार कर रहे हैं।
इसका समाधान शायद ही कभी एक बड़ा सर्वर होता है। इसके लिए एक ऐसे serving engine की आवश्यकता होती है जो एक ही forward pass में कई requests को model के माध्यम से push करे, साथ ही इतनी अतिरिक्त memory हो कि वह यह काम करते समय सभी की बातचीत को सुरक्षित रख सके। दोनों हिस्से महत्वपूर्ण हैं, और दूसरा हिस्सा ही वास्तव में आपकी क्षमता की सीमा तय करता है।
हर request जिन दो चरणों से गुजरती है
Prefill पूरे prompt को एक साथ पढ़ता है और इसके लिए attention cache बनाता है। हर prompt token मॉडल से एक साथ गुजरता है, इसलिए prefill एक बड़ा matrix multiply है, और यह arithmetic throughput द्वारा सीमित होता है। इसके बाद Decode एक बार में एक token करके उत्तर लिखता है। प्रत्येक token के लिए मॉडल के पूरे weights को फिर से memory से पढ़ना पड़ता है, जबकि उस एक token पर की जाने वाली arithmetic गणना बहुत कम होती है। Decode memory bandwidth द्वारा सीमित होता है।
यह विषमता ही वह मुख्य कारण है जिसकी वजह से batching काम करती है। एक user के लिए decoding करने पर प्रति token लगभग 5 GB weights पढ़े जाते हैं और अधिकांश arithmetic units खाली रहते हैं। यदि दूसरा request जोड़ दिया जाए, तो engine उन्हीं 5 GB weights को एक बार पढ़ता है और उनसे दो tokens की गणना कर लेता है। दूसरे user के लिए लगभग कोई अतिरिक्त समय नहीं लगता। requests को एक के बाद एक प्रोसेस करने से यह लाभ खत्म हो जाता है।
दो संख्याएँ यह बताती हैं कि user को कैसा अनुभव मिल रहा है। TTFT (time to first token) का मतलब queue wait और prefill का योग है। ITL (inter-token latency) streamed tokens के बीच का अंतराल है, और यह decode द्वारा निर्धारित होता है। एक धीमा सर्वर आमतौर पर इनमें से किसी एक चरण में धीमा होता है, और दोनों के समाधान अलग-अलग हैं।
Static batching के कारण सभी को सबसे धीमे रिस्पॉन्स का इंतज़ार करना पड़ता है
Static batching एक साधारण तरीका है, जो तब मिलता है जब आप एप्लिकेशन कोड में खुद requests को ग्रुप करते हैं। इंजन N requests को इकट्ठा करता है, उन्हें एक साथ चलाता है, और ग्रुप में सबसे लंबी generation पूरी होने तक हर स्लॉट को होल्ड करके रखता है।
एक यूज़र जो 1,200 टोकन का सारांश मांग रहा है, वह चार एक-लाइन वाले जवाबों को बैच में लॉक कर देता है, क्योंकि बैच अपने सबसे धीमे मेंबर के पूरा होने तक कोई भी स्लॉट खाली नहीं करता।
इसके दो नुकसान होते हैं। जो sequences पूरी हो चुकी हैं, वे ऐसे स्लॉट घेरे रहती हैं जो कुछ भी उपयोगी compute नहीं कर रहे होते, इसलिए जैसे-जैसे आउटपुट की लंबाई बदलती है, प्रभावी थ्रूपुट (throughput) गिर जाता है, और चैट आउटपुट की लंबाई काफी बदलती रहती है। बैच बनने के एक स्टेप बाद आने वाली request को प्रीफिल (prefill) शुरू करने से पहले पूरे बैच के खत्म होने का इंतज़ार करना पड़ता है, जिसका मतलब है कि उसका TTFT किसी और के लंबे जवाब द्वारा निर्धारित होता है।
Continuous batching हर token पर requests को स्वीकार और समाप्त करता है
Continuous batching एक single decoding step के स्तर पर scheduling करता है। प्रत्येक step के बाद, scheduler उन sequences को हटा देता है जिन्होंने अभी-अभी अपना stop token emit किया है, और फिर खाली slots में प्रतीक्षा कर रही requests को शामिल कर लेता है। जो reply step 40 पर समाप्त होता है, वह अपना slot step 40 पर ही खाली कर देता है, न कि batch के अंत में।
यह कोई असामान्य तकनीक नहीं है। llama-server में -cb, --cont-batching को "continuous batching (जिसे dynamic batching भी कहते हैं) सक्षम करना है या नहीं (default: enabled)" के रूप में document किया गया है, और vLLM इसी अवधारणा पर आधारित है। Ollama भी parallel requests को serve करता है। Default setting में यह संख्या केवल एक तक सीमित रहती है, यही कारण है कि बहुत से लोग यह निष्कर्ष निकाल लेते हैं कि उनका hardware concurrency को handle नहीं कर सकता, जबकि वास्तव में यह उनकी configuration थी जिसने इसे मना किया था।
Continuous batching के प्रकाशित परिणाम आमतौर पर उन datacenter cards पर मापे जाते हैं जिनमें अतिरिक्त compute और cache के लिए दसियों gigabytes memory होती है। उन परिणामों का स्वरूप आपके सिस्टम पर भी लागू होता है, लेकिन उनका आकार नहीं, और नीचे दिया गया memory section इसका कारण बताता है।
Prefill और decode एक ही compute संसाधन के लिए प्रतिस्पर्धा करते हैं
जब चार replies stream हो रहे हों और उसी समय एक नया request आता है, तो उसके prompt को पहले prefill करना पड़ता है, और prefill में बहुत अधिक compute खर्च होता है। यदि scheduler उस prefill को अपना एक अलग step देता है, तो उस दौरान चार streaming users को कोई token प्राप्त नहीं होता। लंबे prompt के मामले में, हर open window में यह एक स्पष्ट ठहराव (pause) के रूप में दिखता है। जब लोग कहते हैं कि "किसी और के send बटन दबाते ही सर्वर अटक जाता है", तो उनका मतलब इसी stutter से होता है।
Chunked prefill एक लंबे prompt को टुकड़ों में तोड़ देता है और प्रत्येक टुकड़े को चल रहे decodes के साथ उसी step में मिला देता है। vLLM की tuning guide इस tradeoff को सीधे तौर पर स्पष्ट करती है: छोटे chunk budgets "बेहतर ITL प्राप्त करते हैं क्योंकि decodes को धीमा करने वाले prefills कम होते हैं", जबकि उच्च मान "बेहतर time to first token (TTFT) प्राप्त करते हैं क्योंकि आप एक batch में अधिक prefill tokens process कर सकते हैं"। आप यह चुन रहे हैं कि किसका अनुभव सुरक्षित रखना है: वह व्यक्ति जो reply शुरू होने का इंतज़ार कर रहा है, या वे लोग जो text stream होते हुए देख रहे हैं।
Prompt की लंबाई यह तय करती है कि इसका कितना बुरा प्रभाव पड़ेगा। 200 tokens के उत्तर के साथ 6,000 tokens का prompt, 200 decode steps के मुकाबले 6,000 tokens का prefill कार्य है। Retrieval-augmented chat और लंबे system prompts दोनों ही आपको इस स्थिति में धकेल देते हैं, इसलिए prefill अब एक मामूली त्रुटि (rounding error) नहीं रह जाता, बल्कि वह मुख्य बाधा बन जाता है जिस पर users इंतज़ार करते हैं। जब लंबा हिस्सा दोहराया जाता है तो Prefix caching मदद करती है: vLLM --enable-prefix-caching को expose करता है, जो हर request के लिए उसे फिर से compute करने के बजाय एक साझा prompt prefix के लिए cache का पुन: उपयोग करता है।
सबसे पहले KV cache की मेमोरी समाप्त होती है
प्रत्येक सक्रिय conversation में हर token, model की प्रत्येक layer में एक key vector और एक value vector छोड़ता है। इसे KV cache (key/value cache) कहते हैं, और यही वह चीज़ है जो decode प्रक्रिया को प्रत्येक नए token के लिए पूरे prompt को फिर से compute करने से बचाती है। प्रति token इसका आकार model की बनावट द्वारा निर्धारित होता है: 2 (एक key, एक value) गुणा layer की संख्या, गुणा key/value heads की संख्या, गुणा head dimension, गुणा प्रति value bytes। इन संख्याओं को model की config.json से पढ़ें।
इसे एक बार calculate करें और फिर memory की सीमा कोई रहस्य नहीं रहेगी। 36 layers, 8 key/value heads और 128 के head dimension वाला एक सामान्य 8B model, यदि cache को 16-bit में रखे, तो प्रति token 2 36 8 128 2 bytes खर्च करता है। यह 147,456 bytes है, यानी लगभग 144 KiB। इसलिए, 8,192 tokens की एक conversation के लिए लगभग 1.2 GB cache की आवश्यकता होती है। weights के अलावा, इनमें से पाँच conversations के लिए लगभग 6 GB की आवश्यकता होती है, और यही वह वास्तविक उत्तर है कि कितने users को support किया जा सकता है।
Concurrency context को बढ़ा देती है, और tools इसे स्पष्ट रूप से बताते हैं। Ollama का FAQ कहता है: "किसी दिए गए model के लिए parallel request processing से context size, parallel requests की संख्या के अनुसार बढ़ जाती है। उदाहरण के लिए, 4 parallel requests के साथ 2K context का परिणाम 8K context और अतिरिक्त memory allocation होगा।" आवश्यक RAM, OLLAMA_NUM_PARALLEL को OLLAMA_CONTEXT_LENGTH से गुणा करने पर प्राप्त होती है। llama-server में, -c के साथ आप जो context मांगते हैं, वह -np slots में बंट जाता है, इसलिए केवल slot count बढ़ाने से प्रत्येक request की क्षमता कम हो जाती है। अनुमान लगाने के बजाय startup log से प्रति-slot context पढ़ें।
इसके विपरीत, vLLM पहले से ही memory allocate कर लेता है। --gpu-memory-utilization (default 0.92) "model executor के लिए उपयोग की जाने वाली GPU memory का अंश" है। weights के बाद जो कुछ भी बचता है, वह paged KV pool बन जाता है, और जब वह pool कम पड़ जाता है, तो scheduler request को fail करने के बजाय उसे evict कर देता है:
WARNING 05-09 00:49:33 scheduler.py:1057] Sequence group 0 is preempted by PreemptionMode.RECOMPUTE mode because there is not enough KV cache space.vLLM के V1 engine में default preemption mode RECOMPUTE है, इसलिए एक evicted request अपनी cache को discard कर देता है और पुनः प्रवेश (readmit) मिलने पर फिर से prefill करता है। यह कार्य दो बार किया जाता है। documentation चेतावनी देता है कि "preemption और recomputation end-to-end latency को प्रतिकूल रूप से प्रभावित कर सकते हैं", और यह log line इस बात का सबसे अच्छा स्पष्टीकरण है कि क्यों एक दुर्भाग्यशाली user को दूसरों की तुलना में बहुत अधिक प्रतीक्षा करनी पड़ी, जबकि आपका average सही दिख रहा था। cumulative count को log करने के लिए disable_log_stats=False सेट करें, या vLLM द्वारा expose किए गए Prometheus metrics से preemption counter पढ़ें।
2, 5 और 20 concurrent users पर क्या बदलाव आते हैं
दो users. GPU पर, जिसमें cache खाली हो, यह लगभग अदृश्य होता है, क्योंकि दूसरा decode stream बहुत कम अतिरिक्त समय में पहले के साथ ही प्रोसेस हो जाता है। 4 से 8 GB RAM वाले केवल CPU आधारित VPS पर यह मुफ्त नहीं है: दोनों streams समान vCPUs और RAM bandwidth साझा करते हैं, इसलिए प्रत्येक user को लगभग आधे tokens प्रति सेकंड मिलते हैं, और cache की मांग बहुत कम बजट के मुकाबले दोगुनी हो जाती है।
पाँच users. यहाँ defaults पर्याप्त नहीं रहते, और यह एक queue की समस्या बन जाती है। यदि OLLAMA_NUM_PARALLEL 1 पर है, तो चार लोग उस व्यक्ति की प्रतीक्षा करते हैं जिसने लंबा उत्तर माँगा है, और जैसे ही उनकी बारी आती है, उनमें से प्रत्येक को सामान्य गति मिलती है। parallel count बढ़ाने पर समस्या का स्वरूप बदल जाता है: 8K context वाले पाँच slots का मतलब है 40K token cache की आवश्यकता। यदि यह VRAM में फिट नहीं होता है, तो engine layers को system RAM में offload कर देता है, और यदि यह RAM में भी नहीं समाता है, तो system swap करने लगता है और tokens प्रति सेकंड की गति गिर जाती है।
बीस users. chat UI पर बीस मनुष्य आमतौर पर बीस concurrent requests नहीं होते हैं, और hardware खरीदने से पहले यह समझना सबसे महत्वपूर्ण है। एक व्यक्ति उत्तर पढ़ता है और अपनी बारी के बीच 20 से 60 सेकंड तक सोचता है, इसलिए उनके session का अधिकांश समय idle होता है। बीस agents, या बीस document summarisation jobs, बीस वास्तविक streams हैं जिनमें idle time बिल्कुल नहीं होता है। उसके लिए एक अलग मशीन की आवश्यकता होती है।
क्या आपके उपयोगकर्ता concurrent हैं, या केवल logged in हैं?
किसी भी चीज़ का आकार निर्धारित करने से पहले इन-फ्लाइट (in-flight) requests की गणना करें। इसका गणित सामान्य है: इन-फ्लाइट requests की संख्या बराबर होती है उपयोगकर्ताओं की संख्या, गुणा प्रति टर्न generation में लगा समय, जिसे टर्न के बीच के समय से विभाजित किया जाता है।
- सबसे पहले अपनी single-stream गति मापें, prefill और decode दोनों के लिए। किसी और के कार्ड से प्राप्त आंकड़ों का उपयोग न करें: अपने स्वयं के बॉक्स पर tokens per second मापें और जो परिणाम मिले उसका उपयोग करें।
- Duty cycle का अनुमान लगाएं। बीस चैट उपयोगकर्ता, प्रति टर्न 12 सेकंड की generation, और हर 90 सेकंड में एक टर्न, तो यह 20 * 12 / 90 होता है, जो लगभग 2.7 इन-फ्लाइट requests है।
- Slot count को इससे थोड़ा ऊपर सेट करें, फिर इसे memory के विरुद्ध जांचें: slots की संख्या गुणा प्रति-request context का मान आपके पास उपलब्ध cache tokens के भीतर आना चाहिए।
- Queue को छोटा रखें ताकि overflow जल्दी और स्पष्ट रूप से विफल हो जाए।
उपलब्ध cache tokens वह free memory है जो weights के बाद बचती है, जिसे ऊपर दिए गए अनुभाग के प्रति-token लागत से विभाजित किया जाता है। 16-bit में 8B model चलाने वाला 24 GB का कार्ड लगभग 16 GB weights पर खर्च करता है और डिफ़ॉल्ट उपयोग पर लगभग 6 GB usable cache रखता है, जो लगभग पांच 8K conversations के बराबर है। अधिक फिट करने के लिए, प्रति-request context को छोटा करें, या cache को 8-bit में स्टोर करें (llama-server के लिए --cache-type-k q8_0 लगता है)। दोनों ही कुछ समझौता करके concurrency बढ़ाते हैं, और उस समझौते का ईमानदार संस्करण हार्डवेयर पर पैसा लगाने से पहले पढ़ना उचित है: GPU VPS कब API tokens की तुलना में किफायती होता है।
जहाँ Ollama के defaults अपर्याप्त हो जाते हैं
Service unit के माध्यम से parallel count बढ़ाएं, क्योंकि shell export, systemd द्वारा प्रबंधित daemon तक नहीं पहुँच पाएगा।
sudo systemctl edit ollama.service[Service]
Environment="OLLAMA_NUM_PARALLEL=4"
Environment="OLLAMA_CONTEXT_LENGTH=8192"
Environment="OLLAMA_MAX_QUEUE=64"sudo systemctl daemon-reload
sudo systemctl restart ollama
systemctl show ollama --property=Environment
ollama pssystemctl show को आपके द्वारा अभी सेट किए गए तीनों variables को print करना चाहिए। यदि ऐसा नहीं होता है, तो drop-in save नहीं हुआ है और आपके द्वारा किया गया अन्य कोई भी कार्य प्रभावी नहीं होगा। ollama ps फिर loaded model को उसके weights से अधिक आकार के साथ सूचीबद्ध करता है, क्योंकि 8,192 tokens के चार slots अपने साथ 32,768 tokens का cache reserve करते हैं। यदि PROCESSOR column में model का कुछ हिस्सा CPU पर दिखाई देता है जबकि आप उसे पूरी तरह GPU पर चाहते थे, तो इसका अर्थ है कि आपने card की क्षमता से अधिक cache की मांग की है। इन दोनों संख्याओं में से किसी एक को कम करें।
Queue के default मान पर दोबारा विचार करना आवश्यक है। Ollama OLLAMA_MAX_QUEUE requests तक को queue करता है, और "default मान 512 है"। इसके बाद यह "503 error के साथ जवाब देता है जो दर्शाता है कि सर्वर overloaded है"। एक ऐसे सर्वर पर जो एक बार में चार requests process करता है, 512 की queue एक ऐसा वादा है जिसे आप पूरा नहीं कर सकते, क्योंकि 300वें स्थान पर मौजूद client की बारी आने से बहुत पहले ही उसका connection time out हो जाएगा। एक छोटी queue वह error लौटाती है जिसे आपका application retry कर सकता है या report कर सकता है, जो कि कभी न खत्म होने वाले spinner से बेहतर है।
इसका वास्तविक परीक्षण करें। दो terminals से एक ही समय पर दो requests भेजें और दोनों को देखें। यदि पहली request पूरी होने तक दूसरी कुछ भी output नहीं देती है, तो parallel setting प्रभावी नहीं हुई है।
जब एक वास्तविक सर्विंग इंजन अपनी लागत वसूलने लगता है
vLLM तब अपनी अतिरिक्त सेटअप लागत को सार्थक बनाता है जब आपके पास GPU में अतिरिक्त क्षमता (headroom) हो और चार से अधिक requests वास्तव में process हो रही हों। इसका शेड्यूलर प्रति टोकन काम करता है, इसका कैश paged होता है ताकि खाली fragments का पुन: उपयोग हो सके, और यह खाली VRAM को बेकार छोड़ने के बजाय उसे concurrency में बदल देता है। अगस्त 2026 तक, इसका दस्तावेजीकरण किया गया इंस्टॉलेशन और लॉन्च केवल दो commands में होता है:
uv pip install vllm --torch-backend=auto
vllm serve Qwen/Qwen2.5-1.5B-Instructcurl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen2.5-1.5B-Instruct",
"messages": [{"role": "user", "content": "Say hello."}]
}'choices array वाला उत्तर मिलने का अर्थ है कि सर्वर चालू है और मॉडल लोड हो चुका है। लोड के दौरान दो महत्वपूर्ण सेटिंग्स --max-num-seqs, जो "एक iteration में प्रोसेस की जाने वाली sequences की अधिकतम संख्या" है, और --max-num-batched-tokens, जो "एक iteration में प्रोसेस किए जा सकने वाले टोकन की अधिकतम संख्या" है, मायने रखती हैं। पहली सेटिंग concurrency को सीमित करती है। दूसरी सेटिंग पहले वर्णित chunked prefill बजट है।
चार से कम requests के साथ, या किसी ऐसे सिस्टम पर जिसमें समर्थित GPU नहीं है, vLLM केवल जटिलता बढ़ाता है और लाभ बहुत कम देता है। यह CUDA-class कार्ड की अपेक्षा करता है और स्टार्टअप पर अधिकांश मेमोरी ले लेता है, जो 4 से 8 GB वाले VPS पर एक गलत निर्णय है। वहां बेहतर विकल्प एक छोटा मॉडल है जिसमें छोटा context और आपके द्वारा नियंत्रित queue हो। Ollama और vLLM सर्विंग इंजन के रूप में कैसे भिन्न हैं इस विकल्प को विस्तार से कवर करता है, और VPS पर Qwen 3 8B चलाना यह दर्शाता है कि एक मध्यम आकार के मॉडल को एक भी अतिरिक्त उपयोगकर्ता जोड़ने से पहले कितनी संसाधनों की आवश्यकता होती है।
वह ट्रेड-ऑफ जिसे लोककथाएँ छिपाती हैं
Continuous batching कुल थ्रूपुट को बढ़ाता है, और यह आमतौर पर median latency में भी सुधार करता है, क्योंकि कतारबद्ध अनुरोध जल्दी शुरू हो जाता है। Tail latency इसके विपरीत व्यवहार करती है, और इस पहलू का उल्लेख शायद ही कभी किया जाता है।
एक स्टेप में हर अतिरिक्त सीक्वेंस थोड़ा काम बढ़ा देता है, इसलिए जैसे-जैसे बैच भरता है, सभी के लिए ITL बढ़ जाता है। एक नए आने वाले अनुरोध का prefill उस स्टेप का एक हिस्सा ले लेता है जो अन्यथा स्ट्रीमिंग उपयोगकर्ताओं के पास होता। Cache के दबाव में शेड्यूलर preemption करता है, जो आधे-अधूरे जेनरेट हुए अनुरोध को वापस उसके prefill की शुरुआत में भेज देता है।
एक चैट UI औसत (averages) नहीं, बल्कि टेल्स (tails) दिखाता है। एक स्ट्रीम जो वाक्य के बीच में दो सेकंड के लिए रुक जाती है, वह टूटी हुई महसूस होती है, भले ही पूरा होने का कुल समय अच्छा हो। अपनी अपेक्षित लोड के तहत p95 TTFT और p95 ITL को मापें, और mean tokens per second को अनुभव के विवरण के बजाय क्षमता संख्या (capacity number) के रूप में देखें।
व्यावहारिक सेटिंग इसी से तय होती है। Concurrency को मेमोरी द्वारा अनुमत सीमा से थोड़ा कम रखें, ताकि इंजन को कभी भी preempt न करना पड़े। एक छोटी और अनुमानित कतार, थ्रैशिंग (thrashing) करने वाले बड़े बैच से बेहतर होती है, क्योंकि वह उपयोगकर्ता जो चार सेकंड प्रतीक्षा करने के बाद सुचारू रूप से स्ट्रीम करता है, उस उपयोगकर्ता से अधिक खुश रहता है जो तुरंत शुरू तो होता है लेकिन दो बार अटक जाता है।
धीमी गति होने पर क्या जाँचें
प्रत्येक user सामान्य है, लेकिन प्रतीक्षा समय लंबा है। यह एक queue की समस्या है, न कि गति की। सबसे पहले parallel सेटिंग की जाँच करें। मॉडल सही ढंग से काम कर रहा है, लेकिन एक बार में केवल एक request प्रोसेस हो रही है।
Ollama से HTTP 503 त्रुटि। queue भर चुकी है। या तो सर्वर अपनी पूरी क्षमता पर है, या फिर OLLAMA_MAX_QUEUE को जानबूझकर कम रखा गया है ताकि load को नियंत्रित किया जा सके, जो कि अपेक्षित व्यवहार है।
CPU आधारित सर्वर पर load बढ़ने पर Tokens per second की गति गिर जाती है। जब ऐसा हो, तो vmstat 1 चलाएँ। si और so कॉलम में शून्य से अधिक मान होने का अर्थ है कि मशीन swapping कर रही है, यानी हर token के लिए weights को disk से पढ़ा जा रहा है। इसे किसी configuration बदलाव से ठीक नहीं किया जा सकता। मॉडल का आकार या slot की संख्या कम करें।
दस में से एक user को बाकी लोगों की तुलना में बहुत अधिक प्रतीक्षा करनी पड़ती है। vLLM log में preempted खोजें। इसका सामान्य कारण Preemption और उसका recompute होना है, जिसका अर्थ है कि आपके द्वारा अनुमति दी गई context length के लिए cache की क्षमता कम पड़ रही है।
सर्वर idle होने पर भी TTFT खराब है। यह prefill की समस्या है, concurrency की नहीं। लंबे prompts के कारण पहला token दिखाई देने से पहले वास्तविक समय लगता है, इसलिए hardware देखने से पहले prompt के आकार और prefix caching की जाँच करें।
FAQ
मेरा self-hosted LLM दूसरे व्यक्ति के उपयोग करने पर धीमा क्यों हो जाता है?
ज्यादातर मामलों में यह धीमा नहीं होता, बल्कि कतार (queue) में लग जाता है। Ollama में OLLAMA_NUM_PARALLEL का मान 1 पर सेट होता है, इसलिए दूसरा अनुरोध पहले अनुरोध के अंतिम टोकन के आने तक प्रतीक्षा करता है। इन दोनों स्थितियों में अंतर करने के लिए एक उपयोगकर्ता के स्ट्रीम के समय को मापें जबकि दूसरा प्रतीक्षा कर रहा हो: यदि शुरू होने के बाद उनके टोकन प्रति सेकंड (tokens per second) सामान्य हैं, तो इसका मतलब है कि आप कतार में हैं और parallel count बढ़ाने से यह समस्या हल हो जाएगी। यदि दोनों स्ट्रीम आधी गति से चल रही हैं, तो आप वास्तव में मेमोरी बैंडविड्थ साझा कर रहे हैं, जो कि एक हार्डवेयर सीमा है।
एक छोटा GPU कितने समवर्ती (concurrent) उपयोगकर्ताओं को सेवा दे सकता है?
उपयोगकर्ताओं की नहीं, मेमोरी की गणना करें। पहले weights, फिर KV cache, जिसकी लागत 2 गुना layers गुना key/value heads गुना head dimension गुना bytes होती है, प्रति टोकन, प्रति सक्रिय वार्तालाप। 36 layers, 8 key/value heads और 128 head dimension वाला एक सामान्य 8B मॉडल 16-bit में प्रति टोकन लगभग 144 KiB लेता है, इसलिए 8,192 टोकन वाली बातचीत के लिए लगभग 1.2 GB की आवश्यकता होती है। 24 GB का कार्ड जो उस मॉडल को 16-bit में रखता है, उसमें cache के लिए लगभग 6 GB बचता है, जो पूर्ण context पर लगभग पांच वार्तालापों के बराबर है, या यदि आप context को छोटा करते हैं तो इससे अधिक हो सकता है।
क्या continuous batching प्रत्येक उपयोगकर्ता के उत्तर को धीमा कर देती है?
औसत latency आमतौर पर बेहतर होती है, क्योंकि अनुरोधों को पूरे बैच के समाप्त होने की प्रतीक्षा नहीं करनी पड़ती। Tail latency खराब हो जाती है। प्रत्येक अतिरिक्त sequence हर decoding step में काम बढ़ा देती है, एक नए आने वाले का prefill स्ट्रीमिंग उपयोगकर्ताओं से एक step का हिस्सा छीन लेता है, और एक preempted अनुरोध को दो बार prefill करना पड़ता है। p95 inter-token latency को मापें, न कि औसत को, क्योंकि चैट विंडो में ठहराव (pauses) उस तरह से स्पष्ट होते हैं जिसे औसत छिपा देता है।
क्या मुझे OLLAMA_NUM_PARALLEL बढ़ाना चाहिए या vLLM पर जाना चाहिए?
पहले parallel count बढ़ाएं। यह मुफ्त है और इसके लिए केवल एक drop-in file की आवश्यकता होती है, और यह उस सामान्य स्थिति को ठीक करता है जहाँ चार लोग एक लंबे उत्तर के पीछे कतार में खड़े होते हैं। मेमोरी ही सीमा है: समानांतर अनुरोध उस context को बढ़ा देते हैं जिसे आपको बनाए रखना है, इसलिए ध्यान रखें कि कहीं layers CPU पर न चली जाएं। vLLM पर तब जाएं जब आपके पास अतिरिक्त VRAM वाला GPU हो और चार से अधिक अनुरोध वास्तव में एक साथ चल रहे हों, क्योंकि यही वह बिंदु है जहाँ paged cache और per-token scheduling अपनी लागत से अधिक लाभ प्रदान करते हैं।
क्या अधिक CPU cores एक धीमे LLM सर्वर को ठीक कर देंगे?
उस हिस्से के लिए नहीं जिसे उपयोगकर्ता सबसे अधिक महसूस करते हैं। Decode प्रत्येक टोकन के लिए मेमोरी से पूरे मॉडल को पढ़ता है, इसलिए यह RAM बैंडविड्थ द्वारा सीमित होता है, और बैंडविड्थ के संतृप्त होने के बाद अतिरिक्त cores मदद करना बंद कर देते हैं। Prefill cores के साथ scale होता है, इसलिए अधिक cores लंबे prompts पर पहले टोकन के समय को कम कर देते हैं। 4 से 8 GB के VPS पर, मुख्य बाधा आमतौर पर मेमोरी क्षमता होती है, और इसका प्रभावी समाधान अधिक vCPUs के बजाय एक छोटा मॉडल या छोटा context है।