SSD Nodes Learn Hosting plans →
गाइड Matt Connorलेखक: Matt Connor · अपडेट किया गया: 2026-10-26

Self-hosted LLM 5 users पर धीमा क्यों हो जाता है?

Ollama में default parallel requests 1 पर सेट होती हैं। batching, KV cache और prefill की सीमाओं को समझें जो आपके सर्वर की क्षमता को सीमित करती हैं। जानें इसे कैसे सुधारें।

जब अधिक उपयोगकर्ता आते हैं तो self-hosted LLM धीमा क्यों हो जाता है?

जब एक self-hosted LLM 5 concurrent users पर रुक जाता है, तो इसका सामान्य कारण यह है कि सर्वर को एक बार में एक ही उत्तर generate करने के लिए सेट किया गया है, और अन्य चार कतार (queue) में खड़े हैं। Ollama का documentation इसके default के बारे में स्पष्ट है: OLLAMA_NUM_PARALLEL "प्रत्येक model द्वारा एक ही समय में process की जाने वाली parallel requests की अधिकतम संख्या है, जिसका default 1 है।" कुछ भी खराब नहीं है, और serving software एक साथ कई requests पर काम कर सकता है। आपके पाँच में से चार लोग अपनी बारी का इंतज़ार कर रहे हैं क्योंकि एक setting ने ऐसा निर्देश दिया है।

इसे ठीक करने के लिए शायद ही कभी किसी बड़े सर्वर (bigger box) की आवश्यकता होती है। इसके लिए एक ऐसे serving engine की जरूरत होती है जो एक ही forward pass में model के माध्यम से कई requests को push करे, साथ ही इतनी अतिरिक्त memory हो कि वह सभी की बातचीत को उस दौरान hold कर सके। दोनों हिस्से महत्वपूर्ण हैं, और दूसरा हिस्सा ही वास्तव में आपकी क्षमता की सीमा (ceiling) निर्धारित करता है।

हर request जिन दो चरणों से गुजरती है

Prefill पूरे prompt को एक साथ पढ़ता है और इसके लिए attention cache बनाता है। हर prompt token मॉडल से एक साथ गुजरता है, इसलिए prefill एक बड़ा matrix multiply है, और यह arithmetic throughput द्वारा सीमित होता है। इसके बाद Decode एक बार में एक token करके उत्तर लिखता है। प्रत्येक token के लिए मॉडल के पूर्ण weights को मेमोरी से दोबारा पढ़ना पड़ता है, जबकि उस एक token पर की जाने वाली arithmetic गणना बहुत कम होती है। Decode मेमोरी बैंडविड्थ द्वारा सीमित होता है।

यह विषमता ही वह मुख्य कारण है जिससे batching काम करती है। एक user के लिए decoding प्रति token लगभग 5 GB weights पढ़ती है और अधिकांश arithmetic units को खाली छोड़ देती है। एक दूसरी request जोड़ने पर engine उन्हीं 5 GB weights को एक बार पढ़ता है, और फिर उनसे दो tokens की गणना करता है। दूसरे user के लिए लगभग कोई अतिरिक्त समय नहीं लगता। requests को एक के बाद एक सख्ती से serve करने से यह लाभ खत्म हो जाता है।

दो संख्याएँ यह बताती हैं कि user को क्या अनुभव हो रहा है। TTFT (time to first token) का अर्थ है queue wait और prefill का योग। ITL (inter-token latency) streamed tokens के बीच का अंतराल है, और यह decode द्वारा निर्धारित होता है। एक धीमा सर्वर आमतौर पर इनमें से किसी एक चरण में धीमा होता है, और उनके समाधान अलग-अलग हैं। कोई भी सेटिंग बदलने से पहले यह स्थापित करना महत्वपूर्ण है कि आप किस समस्या का सामना कर रहे हैं, और prefill और decode की टाइमिंग अलग-अलग मापना ही वह तरीका है जिससे आप इसका पता लगा सकते हैं।

Static batching के कारण सभी को सबसे धीमे रिप्लाई का इंतज़ार करना पड़ता है

Static batching एक सामान्य तरीका है, जिसे आप तब अपनाते हैं जब आप स्वयं application code में requests को group करते हैं। Engine N requests को इकट्ठा करता है, उन्हें एक साथ चलाता है, और जब तक group में सबसे लंबी generation पूरी नहीं हो जाती, तब तक हर slot को रोके रखता है।

एक user द्वारा 1,200 token के summary की मांग करने पर, चार एक-लाइन वाले answers batch में ही अटके रहते हैं, क्योंकि जब तक batch का सबसे धीमा सदस्य अपना काम पूरा नहीं कर लेता, तब तक batch कोई भी slot खाली नहीं करता।

इसके दो नुकसान होते हैं। पूरी हो चुकी sequences उन slots को घेरे रखती हैं जो कोई उपयोगी computation नहीं कर रहे होते, इसलिए जैसे-जैसे output की लंबाई बदलती है, effective throughput कम हो जाता है, और chat output की लंबाई अक्सर बहुत बदलती रहती है। जो request batch बनने के एक step बाद आती है, उसे prefill शुरू करने से पहले पूरे batch के खत्म होने का इंतज़ार करना पड़ता है, जिसका अर्थ है कि उसका TTFT किसी और के लंबे लेख (essay) द्वारा निर्धारित होता है।

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 करता है, अपनी विशेषताओं में "multi-user support के साथ parallel decoding" को सूचीबद्ध करता है, और नवंबर 2025 में merge किए गए एक बदलाव के बाद से यह default रूप से 4 slots के साथ शुरू होता है (-np default रूप से -1, यानी auto पर सेट है)। vLLM इसी विचार पर आधारित है। Ollama भी parallel requests को serve करता है। Default सेटिंग केवल संख्या को एक पर सीमित कर देती है, यही कारण है कि बहुत से लोग यह निष्कर्ष निकालते हैं कि उनका hardware concurrency नहीं कर सकता, जबकि वास्तव में यह उनका 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 में यह रुकावट स्पष्ट रूप से दिखाई देती है। जब लोग कहते हैं कि किसी और के 'send' दबाते ही सर्वर अटक जाता है, तो उनका मतलब इसी stutter से होता है।

Chunked prefill एक लंबे prompt को टुकड़ों में तोड़ देता है और प्रत्येक टुकड़े को चल रहे decodes के साथ एक ही step में मिला देता है। vLLM की tuning guide इस tradeoff को सीधे तौर पर बताती है: छोटे chunk budgets "बेहतर ITL प्राप्त करते हैं क्योंकि decodes को धीमा करने वाले prefills कम होते हैं", जबकि उच्च values "बेहतर time to first token (TTFT) प्राप्त करते हैं क्योंकि आप एक batch में अधिक prefill tokens process कर सकते हैं"। आप यह चुन रहे हैं कि किसका अनुभव सुरक्षित रखना है: वह व्यक्ति जो reply शुरू होने का इंतज़ार कर रहा है, या वे लोग जो text stream होते हुए देख रहे हैं।

Prompt की लंबाई यह तय करती है कि इससे कितना नुकसान होगा। 200 token के उत्तर के साथ 6,000 token का prompt, 200 decode steps के मुकाबले 6,000 tokens का prefill कार्य है। Retrieval-augmented chat और लंबे system prompts दोनों ही आपको इस स्थिति में धकेल देते हैं, इसलिए prefill अब एक नगण्य त्रुटि नहीं रह जाता, बल्कि वह मुख्य चीज़ बन जाता है जिसका उपयोगकर्ता इंतज़ार करते हैं। जब लंबा हिस्सा दोहराया जाता है तो Prefix caching मदद करती है: vLLM --enable-prefix-caching को expose करता है, जो हर request के लिए उसे फिर से compute करने के बजाय एक साझा prompt prefix के लिए cache का पुन: उपयोग करता है।

सबसे पहले समाप्त होने वाली मेमोरी KV cache है

प्रत्येक सक्रिय conversation में हर token, मॉडल की हर layer में एक key vector और एक value vector छोड़ता है। इसे KV cache (key/value cache) कहते हैं, और यही वह चीज़ है जो decode प्रक्रिया को प्रत्येक नए token के लिए पूरे prompt को फिर से compute करने से बचाती है। प्रति token cached values की संख्या मॉडल के आकार (shape) द्वारा निर्धारित होती है: 2 (एक key, एक value) गुना layer count, गुना key/value heads की संख्या, गुना head dimension। इन संख्याओं को मॉडल की config.json से पढ़ें। आकार प्राप्त करने के लिए इसे प्रति value bytes से गुणा करें, और यह अंतिम कारक एक runtime setting है, न कि मॉडल का हिस्सा: llama-server डिफ़ॉल्ट रूप से cache को f16 (2 bytes) में store करता है और q8_0 से लेकर -ctk और -ctv जैसे छोटे types को स्वीकार करता है।

इसे एक बार हल करें और मेमोरी की सीमा एक रहस्य नहीं रहेगी। 36 layers, 8 key/value heads और 128 के head dimension वाला एक सामान्य 8B मॉडल, जो cache को 16-bit में रखता है, प्रति token 2 36 8 128 2 bytes खर्च करता है। यह 147,456 bytes है, जो लगभग 144 KiB है। इसलिए, 8,192 token वाले एक conversation के लिए लगभग 1.2 GB cache की आवश्यकता होती है। पाँच ऐसे conversations के लिए, weights के अलावा लगभग 6 GB की आवश्यकता होती है, और यही वह वास्तविक उत्तर है कि कितने users इसमें समा सकते हैं।

Concurrency context को गुणा करती है, और tools इसे स्पष्ट रूप से बताते हैं। Ollama का FAQ: "किसी दिए गए मॉडल के लिए Parallel request processing, parallel requests की संख्या के आधार पर context size को बढ़ा देती है। उदाहरण के लिए, 4 parallel requests के साथ 2K context का परिणाम 8K context और अतिरिक्त memory allocation होगा।" आवश्यक RAM, OLLAMA_NUM_PARALLEL को OLLAMA_CONTEXT_LENGTH से गुणा करने पर scale होती है। llama-server में, डिफ़ॉल्ट auto slot count हर slot को एक unified KV buffer पर चलाता है, इसलिए पूरा -c context एक single pool होता है जिससे slots संसाधन लेते हैं। यदि आप स्वयं -np सेट करते हैं, तो unified buffer तब तक बंद रहता है जब तक आप --kv-unified पास न करें, इसलिए -c के साथ आप जो context मांगते हैं, वह -np slots के बीच साझा किया जाता है, और केवल slot count बढ़ाने से प्रत्येक request की क्षमता कम हो जाती है। अनुमान लगाने के बजाय startup log से प्रति-slot context पढ़ें।

vLLM इसके बजाय preallocate करता है। --gpu-memory-utilization (डिफ़ॉल्ट 0.92) "GPU memory का वह अंश है जिसका उपयोग model executor के लिए किया जाना है"। जब तक आप --kv-cache-memory-bytes सेट नहीं करते, vLLM उस अंश से KV cache size का अनुमान लगाता है: weights और engine की profiled working memory के हिसाब-किताब के बाद बजट में जो कुछ भी बचता है, वह 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 में डिफ़ॉल्ट preemption mode RECOMPUTE है, इसलिए एक evicted request अपनी cache को discard कर देता है और फिर से admit होने पर दोबारा 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। यहाँ से default settings पर्याप्त नहीं रहतीं और यह एक 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 नहीं होता। वह एक अलग मशीन की मांग करता है। एक developer जो अपने Ollama server पर coding agent का उपयोग कर रहा है वह पहले मामले की तुलना में दूसरे मामले के अधिक करीब है, क्योंकि agent तब तक requests भेजता रहता है जब तक कार्य चलता है और इंसान की तरह पढ़ने के लिए कोई pause नहीं लेता।

क्या आपके उपयोगकर्ता एक साथ सक्रिय (concurrent) हैं, या केवल लॉग-इन हैं?

किसी भी चीज़ का आकार निर्धारित करने से पहले यह गणना करें कि कितने अनुरोध (requests) प्रक्रिया में हैं। इसका गणित सामान्य है: प्रक्रिया में मौजूद अनुरोधों की संख्या बराबर होती है, उपयोगकर्ताओं की संख्या गुणा प्रति टर्न जनरेशन में लगने वाले सेकंड, जिसे टर्न के बीच के सेकंड से विभाजित किया जाता है।

  1. सबसे पहले अपनी सिंगल-स्ट्रीम गति मापें, प्रीफिल और डिकोड दोनों के लिए। किसी और के कार्ड से प्राप्त संख्या का उपयोग न करें: अपने सिस्टम पर tokens per second मापें और जो परिणाम मिले उसका उपयोग करें।
  2. ड्यूटी साइकिल का अनुमान लगाएँ। बीस चैट उपयोगकर्ता, प्रति टर्न 12 सेकंड की जनरेशन, हर 90 सेकंड में एक टर्न, तो यह 20 * 12 / 90 होगा, जो लगभग 2.7 अनुरोध प्रक्रिया में होने के बराबर है।
  3. स्लॉट की संख्या इससे थोड़ी अधिक सेट करें, फिर इसे मेमोरी के आधार पर जाँचें: स्लॉट की संख्या गुणा प्रति-अनुरोध कॉन्टेक्स्ट, आपके पास मौजूद वास्तविक कैश टोकन के भीतर फिट होना चाहिए।
  4. कतार (queue) को छोटा रखें ताकि ओवरफ्लो होने पर विफलता तुरंत और स्पष्ट रूप से दिखाई दे।

उपलब्ध कैश टोकन, वेट्स (weights) के बाद बची हुई फ्री मेमोरी है, जिसे ऊपर दिए गए अनुभाग के प्रति-टोकन लागत से विभाजित किया जाता है। 16-bit में 8B मॉडल चलाने वाला एक 24 GB कार्ड लगभग 16 GB वेट्स पर खर्च करता है और डिफ़ॉल्ट उपयोग पर अधिकतम लगभग 6 GB उपयोगी कैश रखता है। वर्किंग मेमोरी अलग रखने के बाद यह थोड़ा कम हो जाता है, जो लगभग पाँच 8K कन्वर्सेशन के बराबर है। अधिक फिट करने के लिए, प्रति-अनुरोध कॉन्टेक्स्ट को छोटा करें, या कैश को 8-bit में स्टोर करें (llama-server के लिए --cache-type-k q8_0 लगता है)। दोनों ही कुछ समझौता करके कॉन्करेंसी (concurrency) बढ़ाते हैं, और हार्डवेयर पर पैसा खर्च करने से पहले इस समझौते के वास्तविक पहलुओं को पढ़ना उचित है: GPU VPS बनाम API टोकन का ब्रेक-ईवन पॉइंट कहाँ है।

जहाँ Ollama की डिफॉल्ट सेटिंग्स अपर्याप्त हो जाती हैं

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 ps

systemctl show को आपके द्वारा अभी सेट किए गए तीनों variables को प्रिंट करना चाहिए। यदि ऐसा नहीं होता है, तो drop-in सेव नहीं हुआ है और आपके द्वारा किया गया अन्य कोई भी कार्य प्रभावी नहीं होगा। ollama ps फिर लोड किए गए मॉडल को उसके weights से बड़े आकार के साथ लिस्ट करता है, क्योंकि 8,192 tokens के चार slots अपने साथ 32,768 tokens का cache रिज़र्व करते हैं। यदि PROCESSOR कॉलम में मॉडल का कुछ हिस्सा CPU पर दिखता है जबकि आप पूरी तरह से GPU पर होने की उम्मीद कर रहे थे, तो इसका मतलब है कि आपने कार्ड की क्षमता से अधिक cache की मांग की है। इन दो संख्याओं में से किसी एक को कम करें। आमतौर पर context को कम करना अधिक सुरक्षित होता है, लेकिन बहुत छोटी window त्रुटि देने के बजाय चुपचाप लंबे prompts को काट (truncate) देती है, इसलिए num_ctx का आकार सोच-समझकर तय करना उसे तब तक कम करने से बेहतर है जब तक मॉडल फिट न हो जाए।

Queue डिफॉल्ट पर फिर से विचार करना आवश्यक है। Ollama OLLAMA_MAX_QUEUE requests तक को queue में रखता है, और "डिफॉल्ट 512 है"। इसके बाद यह "503 error के साथ प्रतिक्रिया देता है जो दर्शाता है कि सर्वर ओवरलोड है"। एक ऐसे सर्वर पर जो एक बार में चार requests को प्रोसेस करता है, 512-deep queue एक ऐसा वादा है जिसे आप पूरा नहीं कर सकते, क्योंकि 300वें स्थान पर मौजूद client की बारी आने से बहुत पहले ही उसका समय समाप्त (timeout) हो जाता है। एक छोटी queue वह error लौटाती है जिसे आपका application retry कर सकता है या रिपोर्ट कर सकता है, जो उस spinner से बेहतर है जो कभी हल नहीं होता।

इसे वास्तविक रूप में टेस्ट करें। दो terminals से एक ही समय पर दो requests भेजें और दोनों को देखें। यदि दूसरा request पहले के समाप्त होने तक कुछ भी परिणाम नहीं देता है, तो parallel सेटिंग प्रभावी नहीं हुई है।

जब एक वास्तविक सर्विंग इंजन अपनी लागत निकालने लगता है

vLLM का अतिरिक्त सेटअप तब सार्थक होता है जब आपके GPU में पर्याप्त headroom हो और एक साथ चार से अधिक requests process हो रही हों। इसका scheduler प्रति token काम करता है, इसका cache paged होता है ताकि खाली fragments का पुन: उपयोग हो सके, और यह खाली VRAM को idle छोड़ने के बजाय concurrency में बदल देता है। अक्टूबर 2026 तक, documented GPU install और launch के लिए केवल तीन commands की आवश्यकता होती है। vllm download-kernels उन precompiled kernels को fetch करता है जिन्हें vLLM अन्यथा startup के समय download और compile करता:

uv pip install vllm --torch-backend=auto
vllm download-kernels
vllm serve Qwen/Qwen2.5-1.5B-Instruct
curl 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 वाला उत्तर मिलने का अर्थ है कि सर्वर चालू है और model load हो चुका है। लोड के दौरान दो महत्वपूर्ण knobs होते हैं: --max-num-seqs, जो "एक iteration में process की जाने वाली sequences की अधिकतम संख्या" है, और --max-num-batched-tokens, जो "एक iteration में process किए जा सकने वाले tokens की अधिकतम संख्या" है। पहला concurrency को सीमित करता है, जबकि दूसरा पहले वर्णित chunked prefill बजट है।

चार से कम concurrent requests होने पर, vLLM की जटिलता का लाभ कम मिलता है। ऐसी स्थिति में GPU की आवश्यकता नहीं रहती: अक्टूबर 2026 तक, CPU install guide में x86 और Arm CPUs पर "basic model inferencing and serving" का उल्लेख है, जिसमें version 0.17.0 से prebuilt x86 wheels और एक CPU Docker image उपलब्ध है। हालाँकि, इसकी memory संबंधी आदतें बनी रहती हैं। CPU पर यह system RAM के 0.92 उपयोग अंश (utilisation fraction) को लागू करता है और weights के बाद बची हुई memory को KV cache को दे देता है। इसलिए, यदि आप --kv-cache-memory-bytes के साथ cache को सीमित नहीं करते हैं, तो यह startup पर ही machine की अधिकांश memory ले लेता है। 4 से 8 GB RAM वाले VPS पर यह सही विकल्प नहीं है। वहां बेहतर समाधान एक छोटा model, छोटा context और आपके द्वारा नियंत्रित queue है। Ollama और vLLM सर्विंग इंजन के रूप में कैसे भिन्न हैं इस विकल्प का पूर्ण विवरण देता है, और VPS पर Qwen 3 8B चलाना यह दर्शाता है कि एक अतिरिक्त user जोड़ने से पहले एक मध्यम आकार का model क्या मांग करता है।

वह ट्रेड-ऑफ जिसे लोककथाएं छिपाती हैं

Continuous batching कुल थ्रूपुट को बढ़ाता है, और यह आमतौर पर median latency में भी सुधार करता है, क्योंकि कतार में खड़ी request जल्दी शुरू हो जाती है। Tail latency इसके विपरीत व्यवहार करती है, और इस पहलू का उल्लेख शायद ही कभी किया जाता है।

एक step में हर अतिरिक्त sequence थोड़ा काम बढ़ा देता है, इसलिए जैसे-जैसे batch भरता है, सभी के लिए ITL बढ़ जाता है। एक नई arrival का prefill उस step का एक हिस्सा ले लेता है जो अन्यथा streaming users को मिलता। Cache pressure के तहत scheduler preempt कर देता है, जो एक आधी-generate हुई request को उसके prefill की शुरुआत में वापस भेज देता है।

एक chat UI औसत (averages) नहीं, बल्कि tails दिखाता है। एक stream जो वाक्य के बीच में दो सेकंड के लिए रुक जाती है, वह टूटी हुई लगती है, भले ही completion का कुल समय अच्छा हो। अपनी अपेक्षित load के तहत p95 TTFT और p95 ITL को मापें, और mean tokens per second को अनुभव के विवरण के बजाय capacity संख्या के रूप में देखें।

व्यावहारिक सेटिंग इसी से तय होती है। Concurrency को memory द्वारा अनुमत सीमा से थोड़ा कम रखें, ताकि engine को कभी भी preempt न करना पड़े। एक छोटी और अनुमानित कतार, थ्रैशिंग (thrashing) करने वाले बड़े batch से बेहतर होती है, क्योंकि वह user जो चार सेकंड प्रतीक्षा करने के बाद सुचारू रूप से stream करता है, उस user से अधिक खुश रहता है जो तुरंत शुरू तो होता है लेकिन दो बार अटक जाता है।

धीमी गति होने पर क्या जाँचें

प्रत्येक उपयोगकर्ता सामान्य है, लेकिन प्रतीक्षा समय लंबा है। यह एक कतार (queue) की समस्या है, न कि गति की। सबसे पहले parallel सेटिंग की जाँच करें। मॉडल सही ढंग से काम कर रहा है, लेकिन एक समय में केवल एक अनुरोध प्रोसेस हो रहा है।

Ollama से HTTP 503 त्रुटि। कतार भर चुकी है। या तो सर्वर वास्तव में अपनी क्षमता पर है, या फिर OLLAMA_MAX_QUEUE को जानबूझकर कम सेट किया गया है ताकि लोड को कम किया जा सके, जो कि अपेक्षित व्यवहार है।

CPU बॉक्स पर लोड बढ़ने पर Tokens per second की गति गिर जाती है। जब ऐसा हो, तो vmstat 1 चलाएँ। si और so कॉलम में शून्य से अधिक मान होने का अर्थ है कि मशीन swapping कर रही है, यानी हर टोकन के लिए weights को डिस्क से पढ़ा जा रहा है। इसे किसी कॉन्फ़िगरेशन बदलाव से ठीक नहीं किया जा सकता। मॉडल का आकार या स्लॉट की संख्या कम करें।

दस में से एक उपयोगकर्ता को बाकी लोगों की तुलना में बहुत अधिक प्रतीक्षा करनी पड़ती है। vLLM लॉग में preempted खोजें। इसका सामान्य कारण Preemption और उसका recompute है, जिसका अर्थ है कि आपके द्वारा अनुमति दी गई context length के लिए cache की क्षमता कम पड़ रही है।

सर्वर खाली होने पर भी TTFT खराब है। यह prefill की समस्या है, concurrency की नहीं। लंबे प्रॉम्प्ट्स के कारण पहला टोकन आने में वास्तविक समय लगता है, इसलिए हार्डवेयर देखने से पहले प्रॉम्प्ट के आकार और prefix caching की जाँच करें। यदि लंबी प्रतीक्षा केवल शांत अवधि के बाद पहले व्यक्ति को होती है और उसके बाद के सभी उपयोगकर्ताओं के लिए सब कुछ ठीक है, तो यह prefill नहीं है, बल्कि Ollama द्वारा मॉडल को अनलोड करना और weights को फिर से डिस्क से पढ़ना है। इसे अनुरोधों के बीच मॉडल को मेमोरी में बनाए रखकर हल करना उचित है।

FAQ

मेरा self-hosted LLM तब धीमा क्यों हो जाता है जब दूसरा व्यक्ति इसका उपयोग करता है?

अक्सर यह धीमा नहीं होता, बल्कि queue में चला जाता है। Ollama में OLLAMA_NUM_PARALLEL का मान 1 पर सेट होता है, इसलिए दूसरा अनुरोध पहले अनुरोध के अंतिम token के emit होने तक प्रतीक्षा करता है। इन दोनों स्थितियों में अंतर करने के लिए एक उपयोगकर्ता के stream का समय मापें जबकि दूसरा प्रतीक्षा कर रहा हो: यदि शुरू होने के बाद उनका tokens per second सामान्य है, तो इसका मतलब है कि आप queue का सामना कर रहे हैं और parallel count बढ़ाने से यह समस्या हल हो जाएगी। यदि दोनों streams आधी गति से चल रही हैं, तो आप वास्तव में memory bandwidth साझा कर रहे हैं, जो कि एक hardware सीमा है।

एक छोटा GPU कितने concurrent उपयोगकर्ताओं को सेवा दे सकता है?

उपयोगकर्ताओं की संख्या नहीं, memory की गणना करें। पहले weights, फिर KV cache, जिसकी लागत प्रति token, प्रति सक्रिय conversation, 2 गुना layers गुना key/value heads गुना head dimension गुना bytes होती है। 36 layers, 8 key/value heads और 128 head dimension वाले एक सामान्य 8B model की लागत 16-bit में लगभग 144 KiB प्रति token होती है, इसलिए 8,192 token वाले conversation के लिए लगभग 1.2 GB की आवश्यकता होती है। उस model को 16-bit में रखने वाले 24 GB card में cache के लिए अधिकतम लगभग 6 GB बचता है, जो पूर्ण context पर लगभग पाँच conversations के बराबर है, या यदि आप context को छोटा करते हैं तो इससे अधिक हो सकता है।

क्या continuous batching प्रत्येक उपयोगकर्ता के उत्तर को धीमा कर देती है?

Median latency आमतौर पर बेहतर होती है, क्योंकि अनुरोधों को पूरे batch के समाप्त होने की प्रतीक्षा नहीं करनी पड़ती। Tail latency खराब हो जाती है। प्रत्येक अतिरिक्त sequence हर decoding step में काम बढ़ा देती है, नए आने वाले अनुरोध का prefill streaming उपयोगकर्ताओं के step का कुछ हिस्सा ले लेता है, और एक preempted अनुरोध को दो बार prefill करना पड़ता है। औसत के बजाय p95 inter-token latency को मापें, क्योंकि chat window में ठहराव (pauses) उस तरह से स्पष्ट होते हैं जिसे औसत छिपा देता है।

क्या मुझे OLLAMA_NUM_PARALLEL बढ़ाना चाहिए या vLLM पर जाना चाहिए?

पहले parallel count बढ़ाएं। यह मुफ्त है और इसके लिए केवल एक drop-in file की आवश्यकता होती है, और यह उस सामान्य स्थिति को ठीक करता है जहाँ चार लोग एक लंबे उत्तर के पीछे queue में खड़े होते हैं। सीमा memory है: parallel अनुरोध आपके द्वारा रखे जाने वाले context को बढ़ा देते हैं, इसलिए ध्यान रखें कि कहीं layers CPU पर न जाने लगें। vLLM पर तब जाएँ जब आपके पास अतिरिक्त VRAM वाला GPU हो और लगभग चार से अधिक अनुरोध वास्तव में चल रहे हों, क्योंकि यही वह बिंदु है जहाँ paged cache और per-token scheduling अपनी लागत से अधिक लाभ प्रदान करते हैं।

क्या अधिक CPU cores धीमे LLM सर्वर को ठीक कर देंगे?

उस हिस्से के लिए नहीं जिसे उपयोगकर्ता सबसे अधिक महसूस करते हैं। Decode प्रत्येक token के लिए memory से पूरा model पढ़ता है, इसलिए यह RAM bandwidth द्वारा सीमित होता है, और bandwidth के संतृप्त होने के बाद अतिरिक्त cores मदद करना बंद कर देते हैं। Prefill cores के साथ scale करता है, इसलिए अधिक cores लंबे prompts पर पहले token तक पहुँचने के समय को कम कर देते हैं। 4 से 8 GB के VPS पर मुख्य बाधा आमतौर पर memory क्षमता होती है, और इसका प्रभावी समाधान अधिक vCPUs के बजाय एक छोटा model या छोटा context है।

क्या यह लेख उपयोगी था?