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 "अधिकतम parallel requests की संख्या है जिन्हें प्रत्येक model एक ही समय में process करेगा, default 1 है।" कुछ भी खराब नहीं है। आपके पाँच में से चार लोग अपनी बारी का इंतजार कर रहे हैं।
इसे ठीक करने का उपाय शायद ही कभी एक बड़ा सर्वर (bigger box) होता है। इसका समाधान एक ऐसा serving engine है जो एक ही forward pass में कई requests को model के माध्यम से भेजता है, साथ ही इतनी अतिरिक्त memory भी हो जो ऐसा करते समय हर किसी की conversation को 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 किया गया है, और vLLM इसी विचार पर आधारित है। Ollama भी parallel requests को serve करता है। Default setting केवल संख्या को एक पर सीमित कर देती है, यही कारण है कि बहुत से लोग यह निष्कर्ष निकालते हैं कि उनका hardware concurrency को संभालने में सक्षम नहीं है, जबकि वास्तव में उनकी configuration ने ही इसे मना किया होता है।
Continuous batching के प्रकाशित परिणाम आमतौर पर उन datacenter cards पर मापे जाते हैं जिनमें अतिरिक्त compute और cache के लिए दर्जनों gigabytes memory उपलब्ध होती है। उन परिणामों का स्वरूप आपके hardware पर भी लागू होता है। उनका आकार लागू नहीं होता, और नीचे दिया गया 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, 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 की आवश्यकता होती है। ऐसी पाँच conversations के लिए model weights के अलावा लगभग 6 GB की आवश्यकता होगी, और यही वह वास्तविक उत्तर है कि कितने users एक साथ काम कर सकते हैं।
Concurrency context को बढ़ा देती है, और tools इसे स्पष्ट रूप से बताते हैं। Ollama का FAQ कहता है: "किसी दिए गए model के लिए parallel request processing का परिणाम parallel requests की संख्या के अनुसार context size को बढ़ाना होता है। उदाहरण के लिए, 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 है, इसलिए एक हटाई गई request अपना cache discard कर देती है और दोबारा admit होने पर फिर से prefill करती है। वह काम दो बार किया जाता है। documentation चेतावनी देता है कि "preemption और recomputation end-to-end latency को प्रतिकूल रूप से प्रभावित कर सकते हैं", और यह log line इस बात का सबसे अच्छा स्पष्टीकरण है कि क्यों एक unlucky 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 नहीं लेता।
क्या आपके उपयोगकर्ता एक साथ सक्रिय हैं, या केवल लॉग इन हैं?
किसी भी चीज़ का आकार निर्धारित करने से पहले यह गणना करें कि कितने अनुरोध (requests) प्रोसेस हो रहे हैं। इसका गणित सामान्य है: प्रोसेस हो रहे अनुरोधों की संख्या बराबर है उपयोगकर्ताओं की संख्या, गुणा प्रति टर्न जनरेशन में लगने वाले सेकंड, जिसे टर्न के बीच के सेकंड से विभाजित किया गया है।
- सबसे पहले अपनी सिंगल-स्ट्रीम गति मापें, इसमें प्रीफिल और डिकोड दोनों शामिल करें। किसी और के कार्ड के नंबर का उपयोग न करें: अपने सिस्टम पर tokens per second मापें और जो परिणाम मिले उसका उपयोग करें।
- ड्यूटी साइकिल का अनुमान लगाएं। बीस चैट उपयोगकर्ता, प्रति टर्न 12 सेकंड की जनरेशन, हर 90 सेकंड में एक टर्न, तो यह 20 * 12 / 90, यानी लगभग 2.7 अनुरोध प्रोसेस में होंगे।
- स्लॉट की संख्या इससे थोड़ी अधिक सेट करें, फिर इसे मेमोरी के साथ जांचें: स्लॉट्स की संख्या गुणा प्रति-अनुरोध कॉन्टेक्स्ट, आपके पास उपलब्ध कैश टोकन के भीतर फिट होना चाहिए।
- कतार (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 pssystemctl 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 तक, दस्तावेजीकरण के अनुसार इंस्टॉलेशन और लॉन्च के लिए केवल दो 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 वाला उत्तर मिलने का अर्थ है कि सर्वर चालू है और मॉडल लोड हो चुका है। लोड के दौरान दो महत्वपूर्ण knobs हैं: --max-num-seqs, जो "एक iteration में process की जाने वाली sequences की अधिकतम संख्या" है, और --max-num-batched-tokens, जो "एक iteration में process किए जा सकने वाले tokens की अधिकतम संख्या" है। पहला concurrency को सीमित करता है, जबकि दूसरा पहले वर्णित chunked prefill बजट है।
चार से कम requests के साथ, या किसी ऐसे सिस्टम पर जिसमें समर्थित GPU नहीं है, vLLM जटिलता बढ़ाता है और लाभ बहुत कम देता है। यह CUDA-class कार्ड की अपेक्षा करता है और स्टार्टअप पर अधिकांश मेमोरी ले लेता है, जो 4 से 8 GB वाले VPS पर गलत निर्णय है। वहां बेहतर विकल्प छोटे context वाला एक छोटा मॉडल और आपके द्वारा नियंत्रित queue है। Ollama और vLLM सर्विंग इंजन के रूप में कैसे भिन्न हैं इस विकल्प को विस्तार से कवर करता है, और VPS पर Qwen 3 8B चलाना दिखाता है कि एक मध्यम आकार का मॉडल एक भी अतिरिक्त उपयोगकर्ता जोड़ने से पहले क्या मांग करता है।
वह ट्रेड-ऑफ जिसे लोककथाएं छिपाती हैं
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 पर सेट होता है, इसलिए दूसरा अनुरोध पहले अनुरोध के अंतिम टोकन के आने तक प्रतीक्षा करता है। इन दोनों स्थितियों में अंतर करने के लिए एक उपयोगकर्ता के स्ट्रीम के समय को मापें जबकि दूसरा प्रतीक्षा कर रहा हो: यदि टोकन प्रति सेकंड की गति सामान्य है, तो समस्या कतार की है और 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 चरण में काम बढ़ा देती है, एक नए आने वाले का prefill स्ट्रीमिंग उपयोगकर्ताओं से चरण का कुछ हिस्सा छीन लेता है, और एक 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 के साथ स्केल करता है, इसलिए अधिक cores लंबे prompts पर पहले टोकन के समय को कम कर देते हैं। 4 से 8 GB के VPS पर मुख्य बाधा आमतौर पर मेमोरी क्षमता होती है, और इसका प्रभावी समाधान अधिक vCPUs के बजाय छोटा मॉडल या छोटा context है।