SSD Nodes Learn Hosting plans →
मार्गदर्शक Matt Connorद्वारे Matt Connor · अपडेटेड 2026-08-30

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

एक वापरकर्ता सुरळीत, पण पाच जणांवर वेग का घटतो? batching, KV cache, prefill आणि queue depth तुमचा LLM server एकावेळी किती वापरकर्ते हाताळेल ते ठरवतात.

अधिक वापरकर्ते आल्यावर self-hosted LLM धीमा का होतो?

Self-hosted LLM 5 समांतर वापरकर्त्यांवर अडकतो, कारण सर्व्हर अजूनही एका वेळी एकच उत्तर तयार करत असतो आणि उर्वरित चार विनंत्या रांगेत थांबलेल्या असतात. Ollama च्या दस्तऐवजात default बाबत स्पष्टपणे नमूद केले आहे: OLLAMA_NUM_PARALLEL म्हणजे “प्रत्येक model एकाच वेळी process करणार्‍या समांतर विनंत्यांची कमाल संख्या; default 1.” काहीही बिघडलेले नसते. तुमच्या पाचपैकी चार वापरकर्ते त्यांच्या क्रमाची वाट पाहत असतात.

यावर उपाय म्हणून सहसा मोठा server आवश्यक नसतो. त्याऐवजी, एकाच forward pass मध्ये model मार्फत अनेक विनंत्या process करणारे serving engine आणि त्या प्रक्रियेदरम्यान सर्व वापरकर्त्यांच्या संभाषणांसाठी पुरेशी अतिरिक्त memory आवश्यक असते. या दोन्ही बाबी महत्त्वाच्या आहेत. प्रत्यक्ष मर्यादा मात्र दुसरी बाब निश्चित करते.

प्रत्येक विनंती ज्या दोन टप्प्यांतून जाते

Prefill संपूर्ण prompt एकाच वेळी वाचतो आणि त्यासाठी attention cache तयार करतो. प्रत्येक prompt token एकत्रितपणे model मधून जातो. त्यामुळे prefill हा एक मोठा matrix multiply असतो आणि त्याची मर्यादा arithmetic throughput मुळे ठरते. Decode त्यानंतर उत्तर एकावेळी एक token लिहितो. प्रत्येक token साठी model चे संपूर्ण weights पुन्हा memory मधून वाचावे लागतात, तर त्या एकाच token वर होणारी arithmetic फारच कमी असते. त्यामुळे decode ची मर्यादा memory bandwidth मुळे ठरते.

Batching कार्यक्षम ठरण्याचे संपूर्ण कारण या असममितीत आहे. एका user साठी decode करताना प्रत्येक 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 मुळे ठरते. Server मंद असल्यास यांपैकी एका घटकात समस्या असते आणि दोन्हींसाठीचे उपाय वेगवेगळे असतात. कोणत्या घटकाशी तुमची समस्या आहे हे setting बदलण्यापूर्वी निश्चित करणे उपयुक्त ठरते. त्यासाठी prefill आणि decode चे timing स्वतंत्रपणे मोजा.

Static batching मुळे सर्वांना सर्वात उशिरा येणाऱ्या प्रतिसादाची वाट पाहावी लागते

Static batching ही त्याची साधी आवृत्ती आहे. अॅप्लिकेशन कोडमध्ये विनंत्या स्वतः गटबद्ध केल्यास हेच मिळते. Engine N विनंत्या जमा करतो, त्या एकत्र चालवतो आणि गटातील सर्वात दीर्घ generation पूर्ण होईपर्यंत प्रत्येक slot रोखून ठेवतो.

एखादा user 1,200 token चा सारांश मागत असेल, तर batch मधील चार एक-ओळींची उत्तरेही त्याच batch मध्ये अडकून राहतात. कारण batch मधील सर्वात धीम्या सदस्याचे काम पूर्ण होईपर्यंत batch कोणताही slot मोकळा करत नाही.

यातून दोन खर्च निर्माण होतात. पूर्ण झालेल्या sequences कोणतेही उपयुक्त compute न करताही slots व्यापून ठेवतात. त्यामुळे output length बदलत असल्यास effective throughput कमी होते; chat मध्ये output lengths मध्ये मोठा फरक असतो. Batch तयार झाल्यानंतर एका step ने आलेली विनंती सुरू होण्यापूर्वी संपूर्ण batch रिकामा होईपर्यंत थांबते. त्यामुळे तिचा TTFT दुसऱ्या user च्या मोठ्या मजकुरामुळे ठरतो.

सतत batching प्रत्येक token नंतर requests स्वीकारते आणि पूर्ण झालेल्या requests काढून टाकते

सतत batching एका decoding step च्या पातळीवर scheduling करते. प्रत्येक step नंतर scheduler नुकतेच stop token निर्माण केलेले sequences काढून टाकतो आणि रिकाम्या slots मध्ये प्रतीक्षेत असलेल्या requests स्वीकारतो. step 40 ला पूर्ण होणाऱ्या reply साठी slot step 40 लाच मोकळा होतो; batch संपेपर्यंत थांबावे लागत नाही.

ही काही असामान्य पद्धत नाही. llama-server मध्ये -cb, --cont-batching चे वर्णन "whether to enable continuous batching (a.k.a dynamic batching) (default: enabled)" असे केले आहे आणि vLLM ची रचना याच संकल्पनेवर आधारित आहे. Ollama देखील parallel requests हाताळते. मात्र default configuration ही संख्या एकपर्यंत मर्यादित ठेवते. त्यामुळे अनेकांना त्यांच्या hardware वर concurrency शक्य नाही असे वाटते, प्रत्यक्षात नकार देणारी गोष्ट त्यांची configuration असते.

प्रकाशित continuous batching परिणाम सामान्यतः अशा datacenter cards वर मोजले जातात, ज्यांच्याकडे अतिरिक्त compute capacity आणि cache साठी दहा-दहा gigabytes memory उपलब्ध असते. त्या परिणामांचा नमुना तुमच्या box वरही लागू होतो. मात्र त्यांचे परिमाण लागू होत नाही. याचे कारण खालील memory section मध्ये स्पष्ट केले आहे.

Prefill त्याच compute साठी decode शी स्पर्धा करते

चार replies stream होत असताना नवीन request आल्यास, तिचा prompt आधी prefill करावा लागतो आणि prefill साठी मोठ्या प्रमाणात compute लागतो. Scheduler ने त्या prefill साठी स्वतंत्र step दिल्यास, त्या काळात चारही streaming users ना एकही token मिळत नाही. मोठ्या prompt मुळे प्रत्येक उघड्या window मध्ये हा स्पष्ट pause दिसतो. इतर कोणी send दाबल्यावर server hiccup होतो असे म्हणताना लोक याच stutter बद्दल बोलतात.

Chunked prefill मोठ्या prompt चे तुकडे करते आणि प्रत्येक तुकडा चालू decode च्या त्याच step मध्ये मिसळते. vLLM च्या tuning guide मध्ये हा tradeoff थेट सांगितला आहे: लहान chunk budgets मुळे “decode धीमे करणारे prefill कमी असल्याने चांगले ITL मिळते”, तर मोठ्या values मुळे “batch मध्ये अधिक prefill tokens process करता येत असल्याने चांगला time to first token (TTFT) मिळतो”. तुम्ही कोणाचा अनुभव जपायचा हे निवडत आहात: reply सुरू होण्याची वाट पाहणारी व्यक्ती की text stream होताना पाहणारे users.

या समस्येची तीव्रता prompt च्या लांबीवर अवलंबून असते. 200 token च्या उत्तरासाठी 6,000 token चा prompt असल्यास, 200 decode steps च्या तुलनेत 6,000 tokens चे prefill काम करावे लागते. Retrieval-augmented chat आणि मोठे system prompts दोन्ही तुम्हाला या परिस्थितीत नेतात. त्यामुळे prefill हा किरकोळ अतिरिक्त वेळ राहत नाही; users ज्याची वाट पाहतात तेच मुख्य काम बनते. मोठा भाग वारंवार समान असल्यास prefix caching मदत करते: vLLM मध्ये --enable-prefix-caching उपलब्ध आहे. प्रत्येक request साठी shared prompt prefix पुन्हा compute करण्याऐवजी ते त्याचा cache पुन्हा वापरते.

पहिले संपणारी मेमरी म्हणजे KV cache

सक्रिय असलेल्या प्रत्येक संभाषणातील प्रत्येक token मॉडेलच्या प्रत्येक layer मध्ये एक key vector आणि एक value vector ठेवतो. याला KV cache (key/value cache) म्हणतात. प्रत्येक नवीन token साठी संपूर्ण prompt पुन्हा compute करण्याची गरज decode ला पडत नाही, कारण ही cache वापरली जाते. प्रत्येक token साठी तिचा आकार मॉडेलच्या रचनेवर निश्चित होतो: 2 (एक key आणि एक value) गुणिले layer count, गुणिले key/value heads ची संख्या, गुणिले head dimension, गुणिले प्रत्येक value साठी लागणारे bytes. हे आकडे मॉडेलच्या config.json मधून वाचा.

एकदा हा हिशोब केल्यावर कमाल मर्यादा स्पष्ट होते. 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 tokens असलेल्या एका संभाषणासाठी सुमारे 1.2 GB cache लागते. अशा पाच संभाषणांसाठी 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 ने गुणल्यावर मिळणाऱ्या मूल्याच्या प्रमाणात वाढते. llama-server मध्ये -c द्वारे मागितलेला context -np slots मध्ये विभागला जातो. त्यामुळे slot count स्वतंत्रपणे वाढवल्यास प्रत्येक request साठी उपलब्ध context कमी होतो. त्याचा अंदाज न लावता startup log मधून प्रत्येक slot साठीचा context वाचा.

vLLM याउलट आधीच memory allocate करते. --gpu-memory-utilization (default 0.92) म्हणजे "model executor साठी वापरायच्या GPU memory चे fraction". Weights allocate केल्यानंतर उरलेली 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 मध्ये default preemption mode RECOMPUTE आहे. त्यामुळे evict केलेल्या request ची cache discard केली जाते आणि request पुन्हा admit केल्यावर तिचे prefill पुन्हा केले जाते. हे काम दोनदा करावे लागते. Documentation मध्ये इशारा दिला आहे की "preemption आणि recomputation मुळे end-to-end latency वर प्रतिकूल परिणाम होऊ शकतो". तुमचा average latency चांगला दिसत असताना एखाद्या user ला इतर सर्वांपेक्षा खूप जास्त वेळ का प्रतीक्षा करावी लागली, याचे हे log line सर्वांत स्पष्ट स्पष्टीकरण देते. cumulative count log करण्यासाठी disable_log_stats=False सेट करा किंवा vLLM उपलब्ध करून देत असलेल्या Prometheus metrics मधून preemption counter वाचा.

2, 5 आणि 20 एकाचवेळी वापरकर्ते असताना काय बदलते

दोन वापरकर्ते. अतिरिक्त cache असलेल्या GPU वर याचा परिणाम जवळजवळ जाणवत नाही, कारण दुसरा decode stream पहिल्या stream सोबत चालतो आणि त्यासाठी फारच कमी अतिरिक्त वेळ लागतो. केवळ CPU असलेल्या 4 ते 8 GB RAM च्या VPS वर ते विनामूल्य नसते: दोन्ही stream समान काही vCPU आणि समान RAM bandwidth वापरतात. त्यामुळे प्रत्येक वापरकर्त्याला साधारणपणे निम्मे tokens per second मिळतात आणि तुलनेने खूपच कमी असलेल्या budget च्या तुलनेत cache ची मागणी दुप्पट होते.

पाच वापरकर्ते. या टप्प्यावर default settings पुरेशा राहत नाहीत आणि सुरुवात queue च्या समस्येपासून होते. OLLAMA_NUM_PARALLEL 1 वर असल्यास, चार जणांना दीर्घ उत्तराची विनंती करणाऱ्या व्यक्तीची प्रतीक्षा करावी लागते. मात्र प्रत्येकाची पाळी आल्यावर त्यांना नेहमीचा वेग मिळतो. parallel count वाढवल्यावर समस्येचे स्वरूप बदलते: प्रत्येकी 8K context असलेले पाच slots मिळून 40K tokens च्या cache साठी जागा आवश्यक असते. ते VRAM मध्ये मावत नसेल, तर engine काही layers system RAM मध्ये offload करते. ते RAM मध्येही मावत नसेल, तर मशीन swap वापरते आणि tokens per second झपाट्याने कमी होतात.

वीस वापरकर्ते. chat UI मधील वीस माणसे म्हणजे सामान्यतः वीस एकाचवेळी सुरू असलेल्या requests नसतात. Hardware खरेदी करण्यापूर्वी समजून घेण्याची ही सर्वात महत्त्वाची बाब आहे. एखादी व्यक्ती reply वाचते आणि पुढील turn साठी 20 ते 60 seconds विचार करते. त्यामुळे तिचे session बहुतांश वेळ idle असते. वीस agents किंवा वीस document summarisation jobs म्हणजे मात्र idle time नसलेले वीस वास्तविक streams असतात. त्यासाठी वेगळ्या क्षमतेचे machine आवश्यक असते. coding agent ला स्वतःच्या Ollama server कडे निर्देशित करणारा एक developer पहिल्या परिस्थितीपेक्षा दुसऱ्या परिस्थितीच्या अधिक जवळ असतो, कारण task सुरू असेपर्यंत agent requests पाठवत राहतो आणि एखादी व्यक्ती घेते तसे reading pauses ठेवत नाही.

तुमचे वापरकर्ते concurrent आहेत की फक्त logged in?

काहीही आकारमान ठरवण्यापूर्वी प्रत्यक्ष प्रक्रियेत असलेल्या requests ची संख्या ठरवा. गणित सोपे आहे: प्रक्रियेत असलेल्या requests = वापरकर्त्यांची संख्या * प्रत्येक turn साठी generation ला लागणारे seconds / दोन turns मधील seconds.

  1. सर्वप्रथम तुमच्या स्वतःच्या single-stream speed चे मापन करा; prefill आणि decode दोन्ही तपासा. दुसऱ्याच्या card वरील आकडा वापरू नका: तुमच्या स्वतःच्या box वर प्रति सेकंद tokens चे मापन करा आणि मिळालेला आकडा वापरा.
  2. duty cycle चा अंदाज घ्या. 20 chat वापरकर्ते, प्रत्येक turn साठी generation ला 12 seconds आणि प्रत्येक 90 seconds ला 1 turn असल्यास, 20 * 12 / 90 = अंदाजे 2.7 requests प्रक्रियेत असतात.
  3. slot count हा त्यापेक्षा थोडा जास्त ठेवा. त्यानंतर memory शी त्याची पडताळणी करा: slots * प्रत्येक request साठीचा context हा प्रत्यक्ष उपलब्ध cache tokens मध्ये बसला पाहिजे.
  4. queue लहान ठेवा, जेणेकरून अतिरिक्त requests जलद आणि स्पष्टपणे fail होतील.

उपलब्ध cache tokens म्हणजे weights लोड केल्यानंतर उरलेली free memory, भागिले वरील section मधील प्रति-token cost. 16-bit मध्ये 8B model चालवणारे 24 GB card weights साठी सुमारे 16 GB वापरते. Default utilisation मध्ये usable cache सुमारे 6 GB उरतो, जो अंदाजे पाच 8K conversations साठी पुरतो. अधिक conversations बसवण्यासाठी प्रत्येक request चा context लहान करा किंवा cache 8-bit मध्ये साठवा (llama-server ला --cache-type-k q8_0 लागते). दोन्ही पर्याय concurrency वाढवतात, पण त्यासाठी काहीतरी त्याग करावा लागतो. Hardware खरेदी करण्यापूर्वी या trade-off चे स्पष्ट विश्लेषण वाचा: GPU VPS API tokens च्या तुलनेत break even कुठे ठरतो ते पाहा.

Ollama चे डीफॉल्ट पुरेसे ठरत नाहीत तेव्हा

parallel count service unit मधून वाढवा, कारण shell export systemd-managed 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 save झालेले नाही आणि तुम्ही केलेली इतर कोणतीही कृती उपयोगी ठरणार नाही. ollama ps नंतर loaded model ची size दाखवते. ही size फक्त weights पेक्षा मोठी असते, कारण 8,192 tokens चे चार slots त्यांच्यासोबत 32,768 tokens cache साठी राखून ठेवतात. सर्व model GPU वर अपेक्षित असताना PROCESSOR column मध्ये model चा काही भाग CPU वर दिसत असल्यास, card वर उपलब्ध असलेल्या cache पेक्षा तुम्ही अधिक cache मागितला आहे. दोनपैकी एक संख्या कमी करा. दोन्हीपैकी context कमी करणे सहसा अधिक सुरक्षित असते. मात्र window खूप लहान असल्यास error न देता long prompts शांतपणे truncate होतात. त्यामुळे model बसेपर्यंत संख्या कमी करत जाण्याऐवजी num_ctx जाणीवपूर्वक योग्य आकारात ठरवा.

queue default कडे पुन्हा पाहणे आवश्यक आहे. Ollama कमाल OLLAMA_MAX_QUEUE requests queue करते आणि "the default is 512". ही मर्यादा ओलांडल्यावर ते "with a 503 error indicating the server is overloaded" असा प्रतिसाद देते. एकावेळी चार requests serve करणाऱ्या box वर 512-deep queue ठेवणे म्हणजे पाळता न येणारे आश्वासन आहे, कारण 300 व्या क्रमांकावरील client ची पाळी येण्यापूर्वीच timeout होतो. लहान queue मुळे application retry किंवा report करू शकेल असा error मिळतो. कधीही पूर्ण न होणाऱ्या spinner पेक्षा हे अधिक उपयुक्त आहे.

प्रत्यक्ष चाचणी करा. दोन terminals मधून एकाच वेळी दोन requests पाठवा आणि दोन्हींचे निरीक्षण करा. पहिली request पूर्ण होईपर्यंत दुसरीकडून काहीही output मिळत नसेल, तर parallel setting लागू झालेली नाही.

प्रत्यक्ष serving engine चा अतिरिक्त खर्च कधी सार्थ ठरतो

GPU मध्ये पुरेशी मोकळी क्षमता आणि प्रत्यक्षात एकाच वेळी साधारण चारपेक्षा जास्त विनंत्या सुरू असतील, तर vLLM साठी करावी लागणारी अतिरिक्त मांडणी सार्थ ठरते. त्याचे scheduler प्रत्येक token नुसार काम करते. त्याची cache paged पद्धतीने व्यवस्थापित केली जाते, त्यामुळे मोकळे fragments पुन्हा वापरता येतात. तसेच, रिकामी ठेवण्याऐवजी उरलेली VRAM अधिक concurrency साठी वापरली जाते. August 2026 पर्यंत documented install आणि launch साठी दोन commands आहेत:

uv pip install vllm --torch-backend=auto
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 असलेला reply म्हणजे server सुरू आहे आणि model loaded आहे. Load असताना महत्त्वाचे दोन knobs म्हणजे --max-num-seqs — “एका iteration मध्ये process करता येणाऱ्या sequences ची maximum संख्या” — आणि --max-num-batched-tokens — “एका iteration मध्ये process करता येणाऱ्या tokens ची maximum संख्या”. पहिला concurrency ची कमाल मर्यादा ठरवतो. दुसरा यापूर्वी वर्णन केलेला chunked prefill budget आहे.

एकाच वेळी सुरू असलेल्या विनंत्या चारपेक्षा कमी असतील किंवा supported GPU नसलेली कोणतीही मशीन असेल, तर vLLM मुळे complexity वाढते आणि फायदा कमी मिळतो. त्याला CUDA-class card अपेक्षित असतो आणि startup वेळी तो बहुतेक memory वापरून ठेवतो. 4 ते 8 GB VPS साठी हा अयोग्य तडजोडीचा पर्याय आहे. अशा ठिकाणी shorter context असलेले छोटे model आणि तुम्ही नियंत्रित करू शकता अशी queue हा योग्य पर्याय आहे. Ollama आणि vLLM serving engines म्हणून कसे वेगळे आहेत या निवडीचे संपूर्ण स्पष्टीकरण देते, तर VPS वर Qwen 3 8B चालवणे एकही अतिरिक्त user जोडण्यापूर्वी mid-sized model कडून कोणत्या संसाधनांची आवश्यकता असते ते दाखवते.

लोककथेत लपलेला तडजोडीचा मुद्दा

Continuous batching मुळे एकूण throughput वाढतो आणि median latency देखील सहसा सुधारते, कारण queue मध्ये असलेली request लवकर सुरू होते. Tail latency मात्र उलट दिशेने बदलते आणि या बाजूचा उल्लेख क्वचितच केला जातो.

एखाद्या step मध्ये प्रत्येक अतिरिक्त sequence मुळे थोडे अधिक काम वाढते. त्यामुळे batch भरत जाताना सर्वांसाठी ITL वाढतो. नवीन आलेल्या request च्या prefill साठी streaming वापरकर्त्यांना मिळू शकणाऱ्या step मधील काही वेळ वापरला जातो. Cache वर दबाव आल्यास scheduler preempt करतो. त्यामुळे अर्धवट तयार झालेली request तिच्या prefill च्या सुरुवातीला पुन्हा पाठवली जाते.

Chat UI मध्ये averages नव्हे, तर tails दिसतात. वाक्याच्या मध्यात stream दोन सेकंद थांबला, तर completion साठी लागणारा एकूण वेळ चांगला असला तरी अनुभव बिघडलेला वाटतो. अपेक्षित load अंतर्गत p95 TTFT आणि p95 ITL मोजा. Mean tokens per second कडे अनुभवाचे वर्णन म्हणून नव्हे, तर capacity number म्हणून पाहा.

यावरून practical setting ठरते. Memory जितकी परवानगी देते त्यापेक्षा concurrency किंचित कमी मर्यादित करा, जेणेकरून engine ला preempt करण्याची गरज पडणार नाही. खोल batch वारंवार thrash होण्यापेक्षा लहान आणि अंदाज करता येणारी queue चांगली असते. कारण चार सेकंद प्रतीक्षा करून नंतर सुरळीत stream मिळणारा वापरकर्ता, लगेच सुरू होऊन दोनदा अडखळणाऱ्या stream पेक्षा अधिक समाधानी असतो.

मंद गती असताना काय तपासावे

प्रत्येक वापरकर्त्यासाठी गती सामान्य आहे, पण प्रतीक्षा वेळ जास्त आहे. ही रांग आहे; गतीची समस्या नाही. सर्वप्रथम parallel setting तपासा. मॉडेल योग्यरीत्या सेवा देत आहे, मात्र एका वेळी एकच विनंती हाताळत आहे.

Ollama कडून HTTP 503 मिळतो. रांग पूर्ण भरली आहे. सर्व्हरची क्षमता खरोखरच संपली असू शकते किंवा OLLAMA_MAX_QUEUE जाणूनबुजून कमी ठेवलेले असू शकते, जेणेकरून load कमी करता येईल. या परिस्थितीत तेच अपेक्षित वर्तन आहे.

CPU सर्व्हरवर load असताना tokens per second मोठ्या प्रमाणात कमी होतो. हे घडत असताना vmstat 1 चालवा. si आणि so स्तंभांमध्ये शून्यापेक्षा मोठी मूल्ये दिसत असल्यास मशीन swapping करत आहे. त्यामुळे प्रत्येक token साठी weights disk वरून पुन्हा वाचले जात आहेत. कोणताही configuration बदल ही समस्या दूर करू शकत नाही. मॉडेलचा आकार किंवा slot count कमी करा.

दहा वापरकर्त्यांपैकी एकाची प्रतीक्षा इतरांपेक्षा खूप जास्त असते. vLLM log मध्ये preempted शोधा. Preemption आणि त्यानंतर होणारे recompute हे याचे नेहमीचे कारण आहे. तुम्ही अनुमत केलेल्या context length साठी cache ची मागणी उपलब्ध क्षमतेपेक्षा जास्त आहे, असा याचा अर्थ होतो.

सर्व्हर idle असतानाही TTFT खराब आहे. ही prefill ची समस्या आहे, concurrency ची नाही. पहिला token दिसण्यापूर्वी मोठ्या prompt साठी प्रत्यक्ष वेळ लागतो. त्यामुळे hardware तपासण्यापूर्वी prompt size आणि prefix caching तपासा. शांत कालावधीनंतर परत आलेल्या पहिल्या वापरकर्त्यालाच दीर्घ प्रतीक्षा करावी लागत असेल आणि त्यानंतरचे सर्व वापरकर्ते ठीक असतील, तर ही prefill ची समस्या नाही. Ollama ने मॉडेल unload करून weights पुन्हा disk वरून वाचलेली असू शकतात. ही शक्यता विनंत्यांदरम्यान मॉडेल resident ठेवणे याची खात्री करून नाकारता येते.

FAQ

माझा self-hosted LLM दुसरा वापरकर्ता वापरू लागल्यावर धीमा का होतो?

बहुतेक वेळा तो प्रत्यक्षात धीमा होत नाही. विनंत्या रांगेत थांबतात. Ollama मध्ये OLLAMA_NUM_PARALLEL चे मूल्य 1 असते. त्यामुळे दुसरी विनंती पहिल्या विनंतीचा अंतिम token येईपर्यंत थांबते. एक वापरकर्ता stream पाहत असताना दुसरी विनंती प्रतीक्षेत ठेवा आणि वेळ मोजा. सुरू झाल्यावर तिचा tokens per second दर सामान्य असेल, तर ही queue आहे आणि parallel count वाढवल्याने समस्या सुटते. दोन्ही streams अर्ध्या वेगाने चालत असतील, तर memory bandwidth खरोखरच दोन्हीमध्ये विभागली जात आहे. ही hardware मर्यादा आहे.

एक लहान GPU किती concurrent users हाताळू शकतो?

वापरकर्त्यांची संख्या नव्हे, तर memory मोजा. प्रथम weights, त्यानंतर KV cache मोजा. प्रत्येक active conversation मधील प्रत्येक token साठी त्याची किंमत 2 times layers times key/value heads times head dimension times bytes इतकी असते. 36 layers, 8 key/value heads आणि 128 head dimension असलेल्या सामान्य 8B model ला 16-bit मध्ये प्रत्येक token साठी सुमारे 144 KiB लागतात. त्यामुळे 8,192 token conversation साठी साधारण 1.2 GB लागतो. 16-bit मध्ये तो model ठेवणाऱ्या 24 GB card वर cache साठी सुमारे 6 GB उरतात. त्यामुळे full context वर सुमारे पाच conversations हाताळता येतात. Context लहान केल्यास त्यापेक्षा अधिक conversations हाताळता येतात.

continuous batching मुळे प्रत्येक वापरकर्त्याचा reply धीमा होतो का?

Median latency सहसा सुधारते, कारण विनंत्यांना संपूर्ण batch पूर्ण होईपर्यंत थांबावे लागत नाही. Tail latency मात्र वाढते. प्रत्येक अतिरिक्त sequence मुळे प्रत्येक decoding step मध्ये अधिक काम वाढते. नवीन विनंती आल्यावर streaming users साठी त्या step चा काही भाग वापरला जातो. Preempt केलेल्या विनंतीसाठी prefill दोनदा करावे लागते. Average ऐवजी p95 inter-token latency मोजा. Chat window मध्ये pauses स्पष्ट दिसतात, पण average मध्ये त्या लपतात.

OLLAMA_NUM_PARALLEL वाढवावे की vLLM वापरावे?

प्रथम parallel count वाढवा. यासाठी कोणताही अतिरिक्त खर्च नाही आणि एक drop-in file पुरेशी असते. एका लांब उत्तरामागे चार जणांच्या विनंत्या रांगेत थांबत असतील, तर ही सामान्य समस्या दूर होते. मर्यादा memory ची आहे. Parallel requests मुळे साठवावा लागणारा context वाढतो. त्यामुळे layers CPU वर spill होत आहेत का ते monitor करा. GPU मध्ये VRAM उपलब्ध असेल आणि चारपेक्षा अधिक requests प्रत्यक्षात एकाच वेळी चालू असतील, तेव्हा vLLM कडे जा. त्या टप्प्यावर paged cache आणि per-token scheduling यांचा फायदा त्यांच्या खर्चापेक्षा अधिक होतो.

अधिक CPU cores मुळे धीमा LLM server सुधारेल का?

वापरकर्त्यांना जाणवणाऱ्या मुख्य भागासाठी नाही. Decode करताना प्रत्येक token साठी संपूर्ण model memory मधून वाचला जातो. त्यामुळे कार्यक्षमता RAM bandwidth ने मर्यादित होते. Bandwidth पूर्णपणे वापरली गेल्यावर अतिरिक्त cores मदत करत नाहीत. Prefill मात्र cores सोबत scale होते. त्यामुळे अधिक cores मुळे लांब prompts साठी time to first token कमी होतो. 4 to 8 GB VPS वर मुख्य मर्यादा सहसा memory capacity असते. त्यामुळे अधिक vCPUs देण्याऐवजी लहान model किंवा लहान context वापरणे हा अधिक प्रभावी उपाय असतो.