self-hosted LLM 5 জনে ধীর কেন হয়?
একজন ব্যবহারকারী ঠিক থাকলেও 5 জনে গতি কমে কেন? `OLLAMA_NUM_PARALLEL`-এর ডিফল্ট 1, batching, KV cache, prefill ও queue depth কীভাবে সীমা ঠিক করে জানুন।
একাধিক ব্যবহারকারী এলে self-hosted LLM ধীর হয়ে যায় কেন?
একসঙ্গে 5 জন ব্যবহারকারী থাকলে self-hosted LLM থেমে থেমে চলার সাধারণ কারণ হলো, সার্ভারটি একবারে একটি উত্তর তৈরি করার জন্য সেট করা আছে এবং বাকি চারটি অনুরোধ queue-তে অপেক্ষা করছে। Ollama-এর documentation-এ default আচরণটি স্পষ্টভাবে বলা আছে: OLLAMA_NUM_PARALLEL হলো “প্রতিটি model একই সময়ে যতগুলো parallel request process করবে, তার সর্বোচ্চ সংখ্যা; default 1।” কিছু নষ্ট হয়নি, এবং serving software একসঙ্গে একাধিক request process করতে পারে। একটি setting-এর কারণে 5 জনের মধ্যে 4 জনকে তাদের পালার জন্য অপেক্ষা করতে হচ্ছে।
সমাধান সাধারণত বড় server নেওয়া নয়। এমন একটি serving engine দরকার, যা একই forward pass-এ model-এর মধ্য দিয়ে অনেকগুলো request process করে। পাশাপাশি, এই কাজের সময় সবার conversation ধরে রাখার জন্য পর্যাপ্ত অতিরিক্ত memory দরকার। দুটি দিকই গুরুত্বপূর্ণ। তবে দ্বিতীয় দিকটিই আপনার সর্বোচ্চ সীমা নির্ধারণ করে।
প্রতিটি অনুরোধ যে দুটি ধাপের মধ্য দিয়ে যায়
Prefill পুরো প্রম্পটটি একসাথে পড়ে এবং এর জন্য attention cache তৈরি করে। প্রতিটি প্রম্পট টোকেন মডেলের মধ্য দিয়ে একসাথে যায়, তাই prefill হলো একটি বড় matrix multiply, যা arithmetic throughput দ্বারা সীমাবদ্ধ। অন্যদিকে, Decode উত্তরটি একটি একটি করে টোকেন তৈরি করে। প্রতিটি টোকেনের জন্য মডেলের সম্পূর্ণ weights মেমোরি থেকে পুনরায় পড়তে হয়, যদিও সেই একটি টোকেনের জন্য প্রয়োজনীয় গাণিতিক হিসাব খুবই সামান্য। Decode মূলত memory bandwidth দ্বারা সীমাবদ্ধ।
এই অসামঞ্জস্যই হলো batching কার্যকর হওয়ার মূল কারণ। একজন ব্যবহারকারীর জন্য ডিকোডিং করার সময় প্রতি টোকেনে প্রায় 5 GB weights পড়তে হয় এবং বেশিরভাগ arithmetic unit অলস বসে থাকে। দ্বিতীয় একটি অনুরোধ যোগ করলে ইঞ্জিন একই 5 GB একবার পড়ে এবং তা থেকে দুটি টোকেন হিসাব করে। দ্বিতীয় ব্যবহারকারীর জন্য প্রায় কোনো অতিরিক্ত সময়ের প্রয়োজন হয় না। অনুরোধগুলোকে একের পর এক সিরিয়াল অনুযায়ী সার্ভ করলে এই সুবিধাটি নষ্ট হয়ে যায়।
ব্যবহারকারীর অভিজ্ঞতা দুটি সংখ্যার মাধ্যমে বোঝা যায়। TTFT (time to first token) হলো কিউতে অপেক্ষার সময় এবং prefill-এর যোগফল। ITL (inter-token latency) হলো স্ট্রিম করা টোকেনগুলোর মধ্যবর্তী বিরতি, যা decode দ্বারা নির্ধারিত হয়। একটি ধীরগতির সার্ভার সাধারণত এই দুটির যেকোনো একটির কারণে ধীর হয় এবং এর সমাধানগুলো ভিন্ন। কোনো সেটিং পরিবর্তন করার আগে আপনি ঠিক কোনটির সাথে লড়াই করছেন তা নিশ্চিত হওয়া জরুরি, এবং prefill এবং decode-এর সময় আলাদাভাবে পরিমাপ করা হলো এটি খুঁজে বের করার উপায়।
Static batching-এর কারণে সবাইকে সবচেয়ে ধীরগতির উত্তরের জন্য অপেক্ষা করতে হয়
Static batching হলো এর প্রাথমিক সংস্করণ, যা আপনি অ্যাপ্লিকেশন কোডে নিজেই রিকোয়েস্টগুলো গ্রুপ করলে পাবেন। ইঞ্জিন N সংখ্যক রিকোয়েস্ট সংগ্রহ করে, সেগুলোকে একসাথে চালায় এবং গ্রুপের সবচেয়ে দীর্ঘ জেনারেশন শেষ না হওয়া পর্যন্ত প্রতিটি স্লট আটকে রাখে।
একজন ব্যবহারকারী 1,200 টোকেনের একটি সামারি চাইলে তা চারটি এক লাইনের উত্তরকে ব্যাচের মধ্যে আটকে রাখে, কারণ ব্যাচটি তার সবচেয়ে ধীরগতির সদস্যের কাজ শেষ না হওয়া পর্যন্ত কোনো স্লট মুক্ত করে না।
এর ফলে দুটি খরচ বাড়ে। সম্পন্ন হওয়া সিকোয়েন্সগুলো এমন স্লট দখল করে রাখে যা কোনো কার্যকর কম্পিউটেশন করছে না, তাই আউটপুটের দৈর্ঘ্য ভিন্ন হওয়ার সাথে সাথে কার্যকর থ্রুপুট কমে যায়, আর চ্যাট আউটপুটের দৈর্ঘ্য অনেক বেশি পরিবর্তিত হয়। ব্যাচ তৈরি হওয়ার ঠিক এক ধাপ পরে আসা একটি রিকোয়েস্টকে পুরো ব্যাচ শেষ না হওয়া পর্যন্ত অপেক্ষা করতে হয়, এমনকি প্রিফিল (prefill) শুরু করার জন্যও। এর মানে হলো, রিকোয়েস্টটির TTFT অন্য কারো লেখা দীর্ঘ উত্তরের ওপর নির্ভর করে নির্ধারিত হয়।
একটি token সম্পন্ন হলেই continuous batching request গ্রহণ ও অবসান ঘটায়
Continuous batching একটি decoding step-এর স্তরে scheduling করে। প্রতিটি step-এর পরে scheduler যে sequence-গুলো সদ্য তাদের stop token দিয়েছে, সেগুলো বাদ দেয়। এরপর free slot-এ অপেক্ষমাণ request গ্রহণ করে। কোনো reply step 40-এ শেষ হলে তার slot batch শেষ হওয়ার সময় নয়, step 40-এই free হয়।
এটি কোনো বিরল কৌশল নয়। llama-server-এর নথিতে -cb, --cont-batching-কে “continuous batching (a.k.a dynamic batching) চালু করা হবে কি না (default: enabled)” হিসেবে বর্ণনা করা হয়েছে। এর feature তালিকায় “multi-user support-সহ parallel decoding” রয়েছে। এছাড়া November 2025-এ merge হওয়া একটি পরিবর্তনের পর এটি default হিসেবে 4টি slot দিয়ে শুরু হয় (-np-এর default -1, অর্থাৎ auto)। vLLM-এর নকশাই এই ধারণার ওপর ভিত্তি করে তৈরি। Ollama-ও parallel request পরিবেশন করে। তবে default মান request-এর সংখ্যা একটিতে সীমাবদ্ধ রাখে। তাই অনেকেই মনে করেন তাদের hardware concurrency চালাতে পারে না, যদিও আসলে তাদের configuration সেটি নিষ্ক্রিয় করে রেখেছিল।
প্রকাশিত continuous batching-এর ফলাফল সাধারণত datacenter card-এ মাপা হয়। এসব card-এ অতিরিক্ত compute capacity এবং cache-এর জন্য কয়েক দশক gigabyte memory—দুটিই থাকে। সেই ফলাফলের ধরণ আপনার system-এও প্রযোজ্য। তবে ফলাফলের পরিমাণ একই হবে না। নিচের memory section-এ এর কারণ ব্যাখ্যা করা হয়েছে।
Prefill এবং decode একই compute রিসোর্সের জন্য প্রতিযোগিতা করে
যখন চারটি রিপ্লাই স্ট্রিমিং অবস্থায় থাকে এবং নতুন একটি রিকোয়েস্ট আসে, তখন সেটির প্রম্পটকে প্রথমে prefill করতে হয়, আর prefill প্রচুর compute ব্যবহার করে। যদি শিডিউলার এই prefill-কে আলাদা একটি ধাপ হিসেবে গণ্য করে, তবে সেই সময়ে স্ট্রিমিং করা চারজন ব্যবহারকারী কোনো টোকেন পান না। দীর্ঘ প্রম্পটের ক্ষেত্রে প্রতিটি ওপেন উইন্ডোতে এটি একটি দৃশ্যমান বিরতি তৈরি করে। সার্ভারে অন্য কেউ কিছু পাঠানোর সাথে সাথে যে 'hiccup' বা হেঁচকি ওঠার কথা বলা হয়, এটিই সেই stutter।
Chunked prefill একটি দীর্ঘ প্রম্পটকে ছোট ছোট অংশে ভাগ করে এবং প্রতিটি অংশকে চলমান decode-এর সাথে একই ধাপে মিশ্রিত করে। vLLM-এর টিউনিং গাইড এই ট্রেড-অফটি স্পষ্টভাবে উল্লেখ করেছে: ছোট chunk budget "ভালো ITL নিশ্চিত করে কারণ এতে decode-কে ধীর করে দেয় এমন prefill-এর সংখ্যা কমে", অন্যদিকে বড় মানগুলো "ভালো time to first token (TTFT) দেয় কারণ আপনি একটি ব্যাচে আরও বেশি prefill টোকেন প্রসেস করতে পারেন"। আপনি কার অভিজ্ঞতাকে প্রাধান্য দেবেন তা আপনাকে বেছে নিতে হবে: যে ব্যক্তি উত্তরের অপেক্ষায় আছেন, নাকি যারা টেক্সট স্ট্রিম হতে দেখছেন।
প্রম্পটের দৈর্ঘ্য নির্ধারণ করে এই সমস্যাটি কতটা প্রকট হবে। 6,000 টোকেনের একটি প্রম্পট যার উত্তর 200 টোকেনের, সেখানে 200টি decode ধাপের বিপরীতে 6,000 টোকেনের prefill কাজ করতে হয়। Retrieval-augmented chat এবং দীর্ঘ সিস্টেম প্রম্পট উভয়ই আপনাকে এই অবস্থায় নিয়ে যায়, ফলে prefill তখন আর সামান্য কোনো বিষয় থাকে না, বরং ব্যবহারকারীরা যেটির জন্য অপেক্ষা করেন সেটিই হয়ে দাঁড়ায়। দীর্ঘ প্রম্পটের অংশগুলো বারবার ব্যবহৃত হলে Prefix caching সাহায্য করে: vLLM --enable-prefix-caching সুবিধাটি প্রদান করে, যা প্রতিটি রিকোয়েস্টের জন্য পুনরায় গণনা না করে একটি শেয়ার্ড প্রম্পট প্রিফিক্সের জন্য ক্যাশ পুনরায় ব্যবহার করে।
প্রথমে যে মেমরি শেষ হয়, সেটি হলো KV cache
মডেলের প্রতিটি layer-এ সক্রিয় প্রতিটি conversation-এর প্রতিটি token একটি key vector এবং একটি value vector রেখে যায়। এটিই KV cache (key/value cache)। নতুন প্রতিটি token-এর জন্য পুরো prompt আবার গণনা না করেই decode করা সম্ভব হয় এই cache-এর কারণে। প্রতি token-এ cache করা value-এর সংখ্যা মডেলের গঠন দ্বারা নির্ধারিত: 2 (একটি key, একটি value) গুণ layer-এর সংখ্যা, গুণ key/value head-এর সংখ্যা, গুণ head dimension। মডেলের config.json থেকে এই সংখ্যাগুলো পড়ুন। প্রতি value-এর byte সংখ্যার সঙ্গে গুণ করলে মোট আকার পাওয়া যায়। শেষের এই factor-টি model-এর অংশ নয়; এটি runtime setting। llama-server ডিফল্টভাবে cache f16-এ (2 bytes) সংরক্ষণ করে এবং -ctk ও -ctv-এর মাধ্যমে q8_0-এর মতো ছোট type গ্রহণ করে।
একবার হিসাব করলে সর্বোচ্চ সীমাটি আর রহস্য থাকে না। 36টি layer, 8টি key/value head এবং 128 head dimension-এর একটি সাধারণ 8B model 16-bit cache ব্যবহার করলে প্রতি token-এর জন্য খরচ হয় 2 36 8 128 2 bytes। এটি 147,456 bytes, অর্থাৎ প্রায় 144 KiB। 8,192 token-এর একটি conversation-এর জন্য তাই প্রায় 1.2 GB cache প্রয়োজন। এমন 5টি conversation-এর জন্য weights-এর অতিরিক্ত প্রায় 6 GB লাগে। কতজন user একসঙ্গে চলতে পারবেন, তার প্রকৃত উত্তর এটিই।
Concurrency context-কে গুণ করে, এবং tools-ও তা স্পষ্টভাবে জানায়। Ollama FAQ-তে বলা হয়েছে: "একটি নির্দিষ্ট model-এর জন্য parallel request processing করলে parallel request-এর সংখ্যা অনুযায়ী context size বৃদ্ধি পায়। উদাহরণস্বরূপ, 4টি parallel request-সহ 2K context হলে 8K context এবং অতিরিক্ত memory allocation তৈরি হবে।" প্রয়োজনীয় RAM, OLLAMA_NUM_PARALLEL-কে OLLAMA_CONTEXT_LENGTH দিয়ে গুণ করলে যে মান পাওয়া যায়, তার সঙ্গে সমানুপাতিক। llama-server-এ default auto slot count সব slot-কে একটি unified KV buffer-এ চালায়। তাই সম্পূর্ণ -c context একটি একক pool, যেখান থেকে slot-গুলো memory নেয়। নিজে -np সেট করলে, --kv-unified-ও না দিলে unified buffer বন্ধ থাকে। সে ক্ষেত্রে -c দিয়ে চাওয়া context, -np সংখ্যক slot-এর মধ্যে ভাগ হয়ে যায়। শুধু slot count বাড়ালে প্রতিটি request যে context ধারণ করতে পারে, তা কমে যায়। অনুমান না করে startup log থেকে প্রতি slot-এর context পড়ুন।
vLLM আগে থেকেই allocation করে। --gpu-memory-utilization (default 0.92) হলো "model executor-এর জন্য ব্যবহৃত GPU memory-এর fraction"। --kv-cache-memory-bytes সেট না করলে vLLM ওই fraction থেকে KV cache-এর আকার নির্ধারণ করে। weights এবং engine-এর profiled working memory-এর হিসাব বাদ দেওয়ার পর budget-এ যত memory অবশিষ্ট থাকে, তা paged KV pool-এ পরিণত হয়। এই pool কমে গেলে scheduler request ব্যর্থ না করে সেটিকে 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 বাদ দেওয়া হয় এবং আবার readmit হলে request-টি পুনরায় prefill করা হয়। এই কাজটি 2 বার সম্পন্ন হয়। 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 জন কনকারেন্ট ব্যবহারকারীর ক্ষেত্রে কী পরিবর্তন হয়
দুইজন ব্যবহারকারী। ক্যাশ মেমোরি পর্যাপ্ত থাকলে একটি GPU-তে এটি প্রায় অদৃশ্য, কারণ দ্বিতীয় ডিকোড স্ট্রিমটি প্রথমটির সাথে খুব সামান্য বাড়তি সময়েই সম্পন্ন হয়। 4 থেকে 8 GB RAM-এর একটি CPU-only VPS-এ এটি বিনামূল্যে হয় না: উভয় স্ট্রিম একই সংখ্যক vCPU এবং RAM ব্যান্ডউইথ শেয়ার করে, তাই প্রতিটি ব্যবহারকারী প্রতি সেকেন্ডে প্রায় অর্ধেক টোকেন পায় এবং অনেক কম বাজেটের বিপরীতে ক্যাশের চাহিদা দ্বিগুণ হয়ে যায়।
পাঁচজন ব্যবহারকারী। এই পর্যায়ে ডিফল্ট সেটিংস আর যথেষ্ট থাকে না এবং এটি একটি কিউ (queue) সমস্যা হিসেবে দেখা দেয়। OLLAMA_NUM_PARALLEL 1-এ সেট করা থাকলে, চারজন ব্যবহারকারীকে অপেক্ষা করতে হয় যে ব্যক্তি দীর্ঘ উত্তরের জন্য অনুরোধ করেছে তার জন্য, এবং তাদের পালা আসার সাথে সাথেই তারা স্বাভাবিক গতি পায়। প্যারালাল কাউন্ট বা সমান্তরাল সংখ্যা বাড়ালে সমস্যার ধরন বদলে যায়: প্রতিটি 8K কনটেক্সট হিসেবে পাঁচটি স্লটের জন্য 40K টোকেন ক্যাশ খুঁজে বের করতে হয়। যদি এটি VRAM-এ না ধরে, তবে ইঞ্জিন লেয়ারগুলোকে সিস্টেম RAM-এ অফলোড করে, আর যদি RAM-এও না ধরে, তবে সিস্টেম সোয়াপ (swap) করতে শুরু করে এবং প্রতি সেকেন্ডে টোকেন পাওয়ার হার ধসে পড়ে।
বিশজন ব্যবহারকারী। চ্যাট UI-তে বিশজন মানুষ সাধারণত বিশটি কনকারেন্ট রিকোয়েস্ট নয়, এবং হার্ডওয়্যার কেনার আগে এটি বোঝা সবচেয়ে জরুরি। একজন মানুষ উত্তর পড়ার সময় এবং পরবর্তী উত্তরের কথা ভাবার জন্য 20 থেকে 60 সেকেন্ড সময় নেয়, তাই তাদের সেশনের বেশিরভাগ সময় অলস (idle) থাকে। বিশজন এজেন্ট বা বিশটি ডকুমেন্ট সামারাইজেশন জব হলো বিশটি প্রকৃত স্ট্রিম যেখানে কোনো অলস সময় থাকে না। এটি সম্পূর্ণ ভিন্ন একটি মেশিনের কাজ। একজন ডেভেলপার যিনি নিজের Ollama সার্ভারে একটি কোডিং এজেন্ট যুক্ত করেছেন, তিনি প্রথম ক্ষেত্রের চেয়ে দ্বিতীয় ক্ষেত্রের কাছাকাছি থাকেন, কারণ যতক্ষণ টাস্কটি চলে ততক্ষণ এজেন্ট ক্রমাগত রিকোয়েস্ট পাঠাতে থাকে এবং মানুষের মতো পড়ার জন্য কোনো বিরতি নেয় না।
আপনার ব্যবহারকারীরা কি একসঙ্গে সক্রিয়, নাকি শুধু লগ ইন করা?
কোনো আকার নির্ধারণের আগে একই সময়ে চলমান request-এর সংখ্যা হিসাব করুন। হিসাবটি সরল: একই সময়ে চলমান request = ব্যবহারকারীর সংখ্যা × প্রতি turn-এ generation-এ ব্যয় হওয়া সেকেন্ড ÷ turn-এর মধ্যবর্তী সেকেন্ড।
- প্রথমে নিজের single-stream গতি মাপুন; prefill এবং decode—দুটিই মাপুন। অন্য কারও card-এর সংখ্যা ব্যবহার করবেন না: নিজের box-এ প্রতি সেকেন্ডে token মাপুন এবং প্রাপ্ত মান ব্যবহার করুন।
- duty cycle অনুমান করুন। 20 জন chat ব্যবহারকারী, প্রতি turn-এ 12 সেকেন্ড generation, এবং প্রতি 90 সেকেন্ডে 1টি turn হলে 20 * 12 / 90, অর্থাৎ প্রায় 2.7টি request একই সময়ে চলবে।
- slot count এই সংখ্যার সামান্য বেশি নির্ধারণ করুন। এরপর memory-এর সঙ্গে মিলিয়ে দেখুন: slot সংখ্যা × প্রতি request-এর context অবশ্যই আপনার কাছে থাকা cache token-এর মধ্যে থাকতে হবে।
- queue ছোট রাখুন, যাতে সীমা ছাড়ালে দ্রুত এবং স্পষ্টভাবে ব্যর্থ হয়।
উপলভ্য cache token হলো weights লোড করার পর অবশিষ্ট free memory-কে আগের section-এ দেওয়া প্রতি-token cost দিয়ে ভাগ করলে যে পরিমাণ পাওয়া যায়। 24 GB card-এ 16-bit 8B model চালালে weights-এর জন্য প্রায় 16 GB লাগে। default utilisation-এ usable cache সর্বোচ্চ প্রায় 6 GB থাকে। working memory-এর জন্য কিছু বরাদ্দ রাখলে তা আরও কমে যায়। এই পরিমাণে প্রায় পাঁচটি 8K conversation রাখা যায়। আরও বেশি request চালাতে চাইলে প্রতি request-এর context ছোট করুন, অথবা cache 8-bit-এ সংরক্ষণ করুন (llama-server-এর জন্য --cache-type-k q8_0 লাগে)। উভয় পদ্ধতিই কিছু ত্যাগের বিনিময়ে concurrency বাড়ায়। hardware কেনার আগে এই বিনিময়ের বাস্তব হিসাব পড়ে নেওয়া উপযোগী: GPU VPS কখন API token-এর তুলনায় সাশ্রয়ী হয়।
যেখানে Ollama-এর ডিফল্ট সেটিংস যথেষ্ট নয়
সার্ভিস ইউনিটের মাধ্যমে প্যারালাল কাউন্ট বা সমান্তরাল অনুরোধের সংখ্যা বৃদ্ধি করুন, কারণ শেল এক্সপোর্ট systemd দ্বারা পরিচালিত ডেমনের কাছে পৌঁছাবে না।
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 কমান্ডটি আপনার সেট করা তিনটি ভেরিয়েবল প্রদর্শন করবে। যদি তা না দেখায়, তবে ড্রপ-ইন ফাইলটি সেভ হয়নি এবং পরবর্তী কোনো পদক্ষেপই কার্যকর হবে না। ollama ps কমান্ডটি লোড করা মডেলের আকার দেখাবে, যা শুধুমাত্র ওয়েট (weights) ফাইলের চেয়ে বড় হবে; কারণ 8,192 টোকেনের চারটি স্লট অতিরিক্ত 32,768 টোকেনের ক্যাশ রিজার্ভ করে রাখে। PROCESSOR কলামে যদি মডেলের কিছু অংশ CPU-তে দেখায় অথচ আপনি পুরোটা GPU-তে আশা করেছিলেন, তবে এর অর্থ হলো আপনি কার্ডের ধারণক্ষমতার চেয়ে বেশি ক্যাশ চেয়েছেন। এই দুটি সংখ্যার যেকোনো একটি কমিয়ে দিন। সাধারণত কনটেক্সট কমানো নিরাপদ, কিন্তু উইন্ডো খুব ছোট হলে তা কোনো এরর না দেখিয়েই দীর্ঘ প্রম্পটগুলোকে কেটে ফেলে। তাই num_ctx-এর আকার সচেতনভাবে নির্ধারণ করা ভালো, শুধুমাত্র মডেল ফিট করার জন্য এটি কমিয়ে দেওয়া উচিত নয়।
কিউ (queue) ডিফল্ট সেটিংসটি পুনরায় বিবেচনা করা প্রয়োজন। Ollama সর্বোচ্চ OLLAMA_MAX_QUEUE টি অনুরোধ কিউতে রাখতে পারে এবং "ডিফল্ট মান হলো 512"। এর বেশি হলে সার্ভার "503 error" প্রদান করে, যা নির্দেশ করে যে সার্ভারটি ওভারলোডেড। যে সার্ভার একসাথে চারটি অনুরোধ প্রসেস করে, সেখানে 512টি অনুরোধের কিউ রাখা একটি অবাস্তব প্রতিশ্রুতি, কারণ 300 নম্বর অবস্থানে থাকা ক্লায়েন্ট তার সুযোগ আসার অনেক আগেই টাইম-আউট হয়ে যাবে। কিউ ছোট রাখলে সার্ভার দ্রুত এরর প্রদান করে, যা আপনার অ্যাপ্লিকেশনকে পুনরায় চেষ্টা (retry) করতে বা রিপোর্ট করতে সাহায্য করে; এটি এমন একটি প্রসেসের চেয়ে ভালো যা কখনোই শেষ হয় না।
বাস্তব পরীক্ষা করুন। দুটি আলাদা টার্মিনাল থেকে একই সময়ে দুটি অনুরোধ পাঠান এবং উভয়ই পর্যবেক্ষণ করুন। যদি প্রথমটি শেষ না হওয়া পর্যন্ত দ্বিতীয়টি কোনো আউটপুট না দেয়, তবে বুঝতে হবে প্যারালাল সেটিংস কার্যকর হয়নি।
যখন একটি বাস্তব serving engine-এর খরচ উঠে আসতে শুরু করে
যখন আপনার পর্যাপ্ত headroom-সহ একটি GPU থাকে এবং প্রায় চারটির বেশি request সত্যিই একসঙ্গে in flight থাকে, তখন vLLM-এর অতিরিক্ত setup যুক্তিসঙ্গত হয়। এর scheduler প্রতি token অনুযায়ী কাজ করে, এর cache paged হওয়ায় অব্যবহৃত fragment পুনরায় ব্যবহার করা যায়, এবং এটি অব্যবহৃত অবস্থায় ফেলে না রেখে অতিরিক্ত VRAM-কে concurrency-তে রূপান্তর করে। October 2026 অনুযায়ী নথিভুক্ত GPU install ও launch তিনটি command-এ করা যায়। vllm download-kernels precompiled kernel সংগ্রহ করে, যেগুলো না হলে vLLM startup-এর সময় download ও compile করত:
uv pip install vllm --torch-backend=auto
vllm download-kernels
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-সহ একটি reply পাওয়া মানে server চালু হয়েছে এবং model loaded হয়েছে। Load-এর অধীনে গুরুত্বপূর্ণ দুটি knob হলো --max-num-seqs, অর্থাৎ “একটি iteration-এ process করা সর্বাধিক sequence-এর সংখ্যা”, এবং --max-num-batched-tokens, অর্থাৎ “একটি iteration-এ process করা সর্বাধিক token-এর সংখ্যা”। প্রথমটি concurrency সীমাবদ্ধ করে। দ্বিতীয়টি আগে বর্ণিত chunked prefill budget নিয়ন্ত্রণ করে।
চারটির কম request in flight থাকলে vLLM জটিলতা বাড়ায়, কিন্তু সুবিধা কম দেয়। GPU আর বাধ্যতামূলক নয়: October 2026 অনুযায়ী CPU install guide-এ x86 ও Arm CPU-তে “basic model inferencing and serving” নথিভুক্ত আছে; version 0.17.0 থেকে prebuilt x86 wheel এবং একটি CPU Docker image-ও রয়েছে। তবে এর memory ব্যবহারের ধরন একই থাকে। CPU-তে এটি system RAM-এর জন্য একই 0.92 utilisation fraction প্রয়োগ করে এবং weights ব্যবহারের পর অবশিষ্ট memory KV cache-এ দেয়। তাই --kv-cache-memory-bytes দিয়ে cache সীমাবদ্ধ না করলে startup-এর সময় এটি সাধারণত মেশিনের অধিকাংশ memory দখল করে। 4 থেকে 8 GB VPS-এ এটি ভুল সমঝোতা। সেখানে কম context-সহ ছোট model এবং আপনার নিয়ন্ত্রণে থাকা queue ব্যবহার করাই ভালো। Ollama ও vLLM serving engine হিসেবে কীভাবে আলাদা-তে এই নির্বাচন বিস্তারিতভাবে ব্যাখ্যা করা হয়েছে, আর একটি VPS-এ Qwen 3 8B চালানো-তে এক জন অতিরিক্ত user যোগ করার আগেই একটি মাঝারি আকারের model কী দাবি করে তা দেখানো হয়েছে।
লোককথায় যে বিনিময় বা ট্রেড-অফ এড়িয়ে যাওয়া হয়
Continuous batching মোট throughput বৃদ্ধি করে এবং সাধারণত median latency-ও উন্নত করে, কারণ একটি queued request দ্রুত শুরু হতে পারে। তবে tail latency-র ক্ষেত্রে উল্টোটি ঘটে এবং এই বিষয়টি খুব কমই আলোচিত হয়।
একটি step-এ প্রতিটি অতিরিক্ত sequence সামান্য বাড়তি কাজের চাপ তৈরি করে, তাই batch পূর্ণ হওয়ার সাথে সাথে সবার জন্য ITL বৃদ্ধি পায়। নতুন কোনো request-এর prefill এমন একটি step-এর অংশ দখল করে নেয়, যা অন্যথায় streaming ব্যবহারকারীদের জন্য বরাদ্দ থাকত। Cache-এর ওপর চাপ বাড়লে scheduler preempt করে, যার ফলে অর্ধেক তৈরি হওয়া একটি request পুনরায় তার prefill-এর শুরুতে ফিরে যায়।
একটি chat UI গড় মানের পরিবর্তে tail latency প্রদর্শন করে। কোনো stream যদি বাক্যের মাঝখানে দুই সেকেন্ডের জন্য থেমে যায়, তবে মোট completion time ভালো হলেও ব্যবহারকারীর কাছে তা ত্রুটিপূর্ণ মনে হয়। আপনার প্রত্যাশিত load-এর অধীনে p95 TTFT এবং p95 ITL পরিমাপ করুন এবং mean tokens per second-কে অভিজ্ঞতার বর্ণনা হিসেবে না দেখে বরং capacity-র সূচক হিসেবে বিবেচনা করুন।
এর থেকে ব্যবহারিক সেটিংস নির্ধারিত হয়। Concurrency-কে memory-র অনুমোদিত সীমার সামান্য নিচে রাখুন, যাতে engine-কে কখনোই preempt করতে না হয়। একটি ছোট এবং অনুমানযোগ্য queue এমন বড় batch-এর চেয়ে ভালো যা thrashing তৈরি করে; কারণ যে ব্যবহারকারী চার সেকেন্ড অপেক্ষা করার পর নির্বিঘ্নে stream করতে পারেন, তিনি সেই ব্যবহারকারীর চেয়ে বেশি সন্তুষ্ট থাকেন যিনি তাৎক্ষণিকভাবে শুরু করার পরও দুবার বাধার সম্মুখীন হন।
ধীরগতির ক্ষেত্রে যা পরীক্ষা করবেন
প্রতিটি ব্যবহারকারী স্বাভাবিক, কিন্তু অপেক্ষা দীর্ঘ। এটি একটি কিউ (queue) সংক্রান্ত সমস্যা, গতির সমস্যা নয়। প্রথমে parallel সেটিং পরীক্ষা করুন। মডেলটি সঠিকভাবে কাজ করছে, কিন্তু একবারে একটি অনুরোধ প্রসেস করছে।
Ollama থেকে HTTP 503 ত্রুটি। কিউ পূর্ণ হয়ে গেছে। হয় সার্ভারটি সত্যিই তার সক্ষমতার সর্বোচ্চ পর্যায়ে আছে, অথবা লোড কমানোর জন্য OLLAMA_MAX_QUEUE ইচ্ছাকৃতভাবে কম রাখা হয়েছে, যা আপনি আসলে চাইছেন।
CPU বক্সে লোডের সময় Tokens per second কমে যাওয়া। এমনটি ঘটার সময় vmstat 1 চালান। si এবং so কলামে শূন্য ছাড়া অন্য কোনো মান থাকা মানে মেশিনটি swapping করছে, যার ফলে প্রতিটি টোকেনের জন্য ডিস্ক থেকে ওয়েট (weights) পড়তে হচ্ছে। কোনো কনফিগারেশন পরিবর্তন এটি ঠিক করতে পারবে না। মডেলের আকার বা স্লট সংখ্যা কমিয়ে দিন।
দশজনের মধ্যে একজনের অপেক্ষা অন্যদের চেয়ে অনেক বেশি। vLLM লগে preempted অনুসন্ধান করুন। এর সাধারণ কারণ হলো Preemption এবং এর পুনরায় গণনা (recompute), যার অর্থ হলো আপনার অনুমোদিত কনটেক্সট দৈর্ঘ্যের তুলনায় ক্যাশে (cache) অতিরিক্ত ব্যবহৃত হচ্ছে।
সার্ভার অলস থাকা সত্ত্বেও TTFT খারাপ। এটি কনকারেন্সি নয়, বরং প্রিফিল (prefill) সংক্রান্ত সমস্যা। দীর্ঘ প্রম্পটের ক্ষেত্রে প্রথম টোকেন আসার আগে প্রকৃত সময় ব্যয় হয়, তাই হার্ডওয়্যার দেখার আগে প্রম্পটের আকার এবং প্রিফিক্স ক্যাশিং (prefix caching) পরীক্ষা করুন। যদি দীর্ঘ অপেক্ষা কেবল দীর্ঘ বিরতির পর প্রথম ব্যবহারকারীর ক্ষেত্রেই ঘটে এবং পরবর্তী সবার জন্য ঠিক থাকে, তবে এটি প্রিফিল নয়, বরং Ollama মডেলটিকে আনলোড করে পুনরায় ডিস্ক থেকে ওয়েট পড়ছে। এটি নিশ্চিত করতে অনুরোধের মাঝে মডেলটিকে মেমরিতে রাখা কার্যকর হতে পারে।
FAQ
দ্বিতীয় ব্যক্তি ব্যবহার করলে আমার self-hosted LLM ধীর হয়ে যায় কেন?
বেশির ভাগ ক্ষেত্রে এটি মোটেও ধীর হয় না। অনুরোধ queue-তে অপেক্ষা করে। Ollama-তে OLLAMA_NUM_PARALLEL ডিফল্টভাবে 1 থাকে, তাই প্রথম অনুরোধ তার final token তৈরি করা পর্যন্ত দ্বিতীয় অনুরোধ অপেক্ষা করে। একজন ব্যবহারকারীর stream চলার সময় অন্যজন অপেক্ষা করলে সময় মেপে দুটি পরিস্থিতি আলাদা করুন: দ্বিতীয় stream শুরু হওয়ার পর token প্রতি সেকেন্ডের হার স্বাভাবিক থাকলে এটি queue সমস্যা, এবং parallel count বাড়ালে তা ঠিক হবে। উভয় stream অর্ধেক গতিতে চললে memory bandwidth সত্যিই ভাগ হচ্ছে। এটি hardware-এর সীমা।
একটি ছোট GPU কতজন concurrent user সামলাতে পারে?
ব্যবহারকারীর সংখ্যা নয়, memory হিসাব করুন। প্রথমে weights, তারপর KV cache। সক্রিয় প্রতিটি conversation-এর প্রতিটি token-এর জন্য এর খরচ হলো 2 times layers times key/value heads times head dimension times bytes। 36 layers, 8 key/value heads এবং head dimension 128-সহ একটি সাধারণ 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-এ প্রায় পাঁচটি conversation রাখা যায়। Context ছোট করলে আরও বেশি রাখা সম্ভব।
Continuous batching কি প্রতিটি ব্যবহারকারীর reply ধীর করে?
Median latency সাধারণত কমে, কারণ অনুরোধগুলোকে পুরো batch শেষ হওয়া পর্যন্ত অপেক্ষা করতে হয় না। Tail latency বেড়ে যায়। প্রতিটি অতিরিক্ত sequence প্রতিটি decoding step-এ কাজ বাড়ায়। নতুন কোনো অনুরোধ এলে streaming user-দের জন্য একটি step-এর কিছু অংশ prefill-এ চলে যায়। Preempt করা অনুরোধকে আবার দুবার prefill করতে হয়। Average নয়, p95 inter-token latency মাপুন। Chat window-এ pause স্পষ্ট দেখা যায়, কিন্তু average-এ তা আড়াল হয়ে যায়।
আমার কি OLLAMA_NUM_PARALLEL বাড়ানো উচিত, নাকি vLLM-এ যাওয়া উচিত?
প্রথমে parallel count বাড়ান। এটি বিনামূল্যে করা যায় এবং একটি drop-in file-ই যথেষ্ট। একজনের দীর্ঘ answer-এর পেছনে চারজন queue-তে অপেক্ষা করার সাধারণ সমস্যা এতে ঠিক হয়। সীমা হলো memory। Parallel request বাড়লে ধরে রাখতে হওয়া context-ও বাড়ে, তাই কোনো layer CPU-তে spill হচ্ছে কি না দেখুন। GPU-তে অতিরিক্ত VRAM থাকলে এবং প্রায় চারটির বেশি request সত্যিই একসঙ্গে in flight থাকলে vLLM-এ যান। এই পর্যায়ে paged cache এবং per-token scheduling-এর সুবিধা সাধারণত তাদের খরচের চেয়ে বেশি হয়।
আরও CPU core কি ধীর LLM server ঠিক করতে পারে?
ব্যবহারকারীরা যে অংশটি সবচেয়ে বেশি লক্ষ্য করেন, সেটির ক্ষেত্রে নয়। Decode প্রতিটি token-এর জন্য memory থেকে পুরো model পড়ে। তাই এটি RAM bandwidth-এর ওপর নির্ভরশীল। Bandwidth পূর্ণ হয়ে গেলে অতিরিক্ত core আর সহায়তা করে না। Prefill অবশ্য core বাড়লে দ্রুত হয়। তাই দীর্ঘ prompt-এর ক্ষেত্রে প্রথম token পাওয়ার সময় কমে। 4 থেকে 8 GB VPS-এ সাধারণত প্রধান সীমা হলো memory capacity। কার্যকর সমাধান হিসেবে বেশি vCPU নয়, ছোট model বা ছোট context ব্যবহার করুন।