সরাসরি পর্বে যান
পর্ব ৮৫ · ৯১-এর মধ্যে · মডিউল ১৩

GenAI system design — chatbot, RAG app আর guardrail

GenAI System Design: Chatbot, RAG App and Guardrails

Taught in Bangla, this lesson designs a Bangla shop assistant as a full system: requirements and SLOs, capacity planning from daily requests to tokens to replicas using a measured throughput, and a request's path through gateway, rate limiter, cache, router, RAG, serving, guardrails and logs. Real desktop runs measure semantic-cache false hits with e5-small and rule-based PII masking, and the page covers timeouts, retries, fallback, observability, eval in CI and build versus buy, with in-page Python.

এই পর্বে যা শিখবেন

  • একটি GenAI পণ্যের functional আর non-functional প্রয়োজন লেখা, SLO সহ
  • অনুরোধ থেকে token, token থেকে GPU replica: capacity planning হাতে আর কোডে
  • API gateway থেকে log পর্যন্ত একটি অনুরোধের যাত্রা, আর প্রতিটি অংশ কেন আছে
  • Model routing, exact আর semantic cache, guardrail আর PII masking, মাপা ফলসহ
  • Timeout, retry, fallback, observability, CI-তে eval, ডেটা রাখার নিয়ম আর build বনাম buy

এই পর্ব আগের অনেকগুলো পর্বের টুকরো জোড়া দেয়: পর্ব ৬৫-এর token আর latency, পর্ব ৬৯-এর RAG, পর্ব ৭০-এর মূল্যায়ন, পর্ব ৭৩-এর নিরাপত্তা। Load balancing, cache, rate limiting আর observability-র সাধারণ তত্ত্ব সিস্টেম ডিজাইন কোর্সে আছে; এখানে শুধু LLM-এর বেলায় যা আলাদা, সেটুকু। Desktop run: Python 3.12, torch 2.15.0, transformers 5.22.2, sentence-transformers 6.2.0, লেখার সময়ে। Model দুটি, token ছাড়া নামানো যায়: intfloat/multilingual-e5-small (MIT) cache আর খোঁজায়, Qwen/Qwen2.5-0.5B-Instruct (Apache 2.0) throughput মাপায়। বাজারঘরের নীতি, প্রশ্ন, নম্বর আর অর্ডার সম্পূর্ণ কাল্পনিক। সব সংখ্যা একটি স্থানীয় run-এর, ছোট model আর ছোট ডেটায়; সাধারণ ফল নয়।

১ · প্রয়োজন আগে — সহায়ক কী করবে, কত দ্রুত, কতটা নিরাপদ

বিয়েবাড়ির রান্নার কথা ভাবুন। বাবুর্চি যত ভালোই হোন, আগে জানতে হয় অতিথি কতজন, কয়টায় খাবার দিতে হবে, কারও খাবারে নিষেধ আছে কি না, আর চুলা নিভে গেলে বিকল্প কী। তারপর ঠিক হয় কয়টি ডেকচি, কতজন সহকারী, আর পরিবেশনের লাইন কোথায়। LLM হলো বাবুর্চি; system design হলো বাকি সব আয়োজন।

System design-এর প্রথম ধাপ প্রয়োজন লেখা (সিস্টেম ডিজাইন কোর্সের পর্ব ২)। GenAI পণ্যে প্রয়োজনের তালিকায় কয়েকটি নতুন লাইন যোগ হয়: উত্তর কতটা নির্ভুল হবে, না জানলে কী বলবে, আর কোন তথ্য model-এর কাছে যাবেই না। বাজারঘরের বাংলা help chatbot-এর জন্য আমাদের লেখা প্রয়োজন:

ধরনপ্রয়োজনকীভাবে মাপবেন
Functionalনীতির প্রশ্নে help-centre থেকে উৎসসহ উত্তর; বাংলা, Banglish আর ইংরেজি প্রশ্ন; না থাকলে “উৎসে নেই” আর মানুষের কাছে পাঠানোEval set-এ সঠিকতা, abstention (পর্ব ৭০)
Functionalঅর্ডারের অবস্থা: শুধু লগইন করা গ্রাহকের নিজের অর্ডার, শুধু পড়ার toolমালিকানা যাচাইয়ের test (পর্ব ৭৩)
Latencyপ্রথম token ২ সেকেন্ডের মধ্যে (p95), পুরো উত্তর ৮ সেকেন্ডের মধ্যেTTFT আর মোট সময়ের percentile
Availabilityকাস্টমার কেয়ারের সময়ে (সকাল ৯টা–রাত ১০টা) মাসে ৯৯.৫% অনুরোধে কোনো না কোনো উত্তরসফল অনুরোধের অনুপাত, একটি SLI
নিরাপত্তানথির ভেতরের নির্দেশ মানবে না; অনুমোদনহীন লিংক বা ফোন নম্বর দেবে নাCanary test, red-team set (পর্ব ৭৩)
গোপনীয়তাফোন, email আর অর্ডার id log আর cache-এ ঢাকা; কথোপকথন নির্দিষ্ট দিনের পর মুছে ফেলাPII detector-এর precision/recall, মোছার কাজের audit
খরচপ্রতি অনুরোধে গড় token-এর সীমা, ব্যস্ত সময়েও GPU-র নির্দিষ্ট সংখ্যাToken গোনা, GPU-সেকেন্ড প্রতি অনুরোধ

দুটি জিনিস লক্ষ করুন। প্রথমত, availability-তে “কোনো না কোনো উত্তর”: বড় model বন্ধ থাকলে ছোট model বা “প্রতিনিধির কাছে পাঠানো হলো”-ও গ্রহণযোগ্য। এটি graceful degradation, §৭-এ মাপা হবে। দ্বিতীয়ত, প্রতিটি প্রয়োজনের পাশে একটি মাপ আছে। “ভালো উত্তর দেবে” প্রয়োজন নয়; “এই ৪১টি প্রশ্নের eval set-এ এত শতাংশ, উত্তরহীন প্রশ্নে এত শতাংশ abstain” প্রয়োজন।

সংখ্যাগুলো কোথা থেকে? ২ সেকেন্ড বা ৯৯.৫% আমাদের বানানো লক্ষ্য, মানদণ্ড নয়; বাস্তবে আসে ব্যবহারকারীর গবেষণা আর পুরনো মাপ থেকে। SLO আর error budget-এর ধারণা Google-এর SRE বই (২০১৬) জনপ্রিয় করেছে; বিস্তারিত সিস্টেম ডিজাইন কোর্সের পর্ব ৫০-এ।

২ · Token-এ ধারণক্ষমতার হিসাব — দিনে কত অনুরোধ, কয়টি GPU

ঈদের আগে বাস কাউন্টারের কথা ভাবুন। সারাদিনে কত যাত্রী আসেন, সেটি জানা যথেষ্ট নয়; সন্ধ্যার এক ঘণ্টায় কতজন আসেন, তা দিয়েই ঠিক হয় কয়টি কাউন্টার খোলা রাখতে হবে। আর একজন যাত্রী এক টিকিট কাটেন, অন্যজন পুরো পরিবারের দশটি; তাই গোনার একক “যাত্রী” নয়, “টিকিট”। LLM-এ সেই টিকিট হলো token.

সাধারণ back-of-the-envelope হিসাব (সিস্টেম ডিজাইন কোর্সের পর্ব ৩) চার ধাপে: দিনে অনুরোধ → ব্যস্ততম সময়ে প্রতি সেকেন্ডে অনুরোধ (RPS) → প্রতি সেকেন্ডে token → মাপা ক্ষমতা দিয়ে ভাগ। সূত্রে:

peak RPS = Rday × p ÷ ৩,৬০০  ·  replica = ⌈peak RPS ÷ (μ × u)⌉

এখানে Rday দিনে মোট অনুরোধ, p ব্যস্ততম ঘণ্টায় তার অংশ (peak-এর ভাগ), μ (মিউ) একটি replica প্রতি সেকেন্ডে কতগুলো অনুরোধ শেষ করে (নিজের prompt-দৈর্ঘ্যে মাপা), আর u ব্যবহারের লক্ষ্য, যেমন ০.৬: পুরো ক্ষমতায় চালালে queue লম্বা হয়, তাই কিছু ফাঁকা রাখা। শেষে একটি বাড়তি replica (N+1). Input (prefill) আর output (decode) GPU-র কাছে আলাদা কাজ, hosted API-তে দামও আলাদা (পর্ব ৬৫), তাই token দুই ভাগে গোনা।

μ কোথা থেকে? মুখস্থ সংখ্যা থেকে নয়, নিজের যন্ত্রে নিজের ধরনের prompt চালিয়ে। নিচে Qwen2.5-0.5B-Instruct, GPU-তে bf16, HF transformers-এর সাধারণ generate দিয়ে: বাজারঘরের তিনটি নথিসহ RAG prompt (প্রায় ১,০০০ token), প্রতিটি উত্তরে ঠিক ১২৮টি নতুন token, greedy, batch ১ থেকে ৩২.

~/bazarghar-dl$ uv run python tput.py   # সত্যিকারের run, GPU-তে
torch 2.15.0+cu132 transformers 5.22.2 | bf16, greedy, new tokens 128
batch  1  input    948 tok/req  prefill   46.8 ms (  20264 in-tok/s)  generate  2.31 s  ->   55.5 out-tok/s,  0.43 req/s
batch  4  input   1003 tok/req  prefill  217.8 ms (  18422 in-tok/s)  generate  2.85 s  ->  179.5 out-tok/s,  1.40 req/s
batch 16  input   1000 tok/req  prefill  862.0 ms (  18561 in-tok/s)  generate  4.14 s  ->  494.1 out-tok/s,  3.86 req/s
batch 32  input   1000 tok/req  prefill 2001.2 ms (  15990 in-tok/s)  generate  7.87 s  ->  520.3 out-tok/s,  4.06 req/s
peak GPU memory allocated: 2.63 GiB

১. Batch বাড়ালে throughput বাড়ে, latency-ও. Batch ১-এ প্রতি সেকেন্ডে ৫৫.৫টি output token, মানে ০.৪৩টি অনুরোধ। Batch ১৬-এ ৪৯৪টি token আর ৩.৮৬টি অনুরোধ, প্রায় ৯ গুণ; কারণ decode-এর প্রতিটি ধাপে GPU একবার পুরো model পড়ে, আর সেই পড়া ১৬টি অনুরোধে ভাগ হয়। দাম হলো প্রতিটি অনুরোধের সময়: ২.৩১ থেকে ৪.১৪ সেকেন্ড।

২. একটি জায়গায় এসে লাভ থামে. Batch ৩২-এ throughput মাত্র ৫% বেশি (৫২০), অথচ প্রতিটি অনুরোধ ৭.৮৭ সেকেন্ড, ৮ সেকেন্ডের সীমার গায়ে। তাই μ = ৩.৮৬ (batch ১৬)। Batch ১৬-এর ১৬,০০০ token-এর prompt পড়তেই (prefill) ০.৮৬ সেকেন্ড, প্রথম token-এর আগের অপেক্ষা; generate-এর সময়ে এটিও ধরা। সবচেয়ে বেশি GPU মেমরি ২.৬৩ GiB.

৩. সংখ্যাটি সাধারণ নয়. ছোট (০.৫B) model, static batch, একটি স্থানীয় run; বড় model ধীর হবে, serving engine বেশি দিতে পারে (পর্ব ৮৩)। পদ্ধতিটিই সাধারণ।

ধরা হলো: ব্যস্ততম ঘণ্টায় ১২%, input ১,০০০ আর output ১২৮ token; μ মাপা (batch ১৬), u = ০.৬
ধাপ
হিসাব
ফল
  1. দিনে. বাজারঘরে দিনে ৬০,০০০ অনুরোধ (নমুনা অনুমান)। সন্ধ্যার ব্যস্ততম ঘণ্টায় তার ১২%, মানে ৭,২০০টি। দিনের গড় ৬০,০০০ ÷ ৮৬,৪০০ = ০.৬৯ অনুরোধ/সেকেন্ড দিয়ে হিসাব করলে GPU কম পড়বে।
  2. Peak RPS. ৭,২০০ ÷ ৩,৬০০ = ২.০ অনুরোধ/সেকেন্ড, গড়ের প্রায় ২.৯ গুণ। ঘণ্টার ভেতরেও মিনিটে মিনিটে ওঠানামা থাকে; u = ০.৬ সেই ফাঁকটুকু রাখে।
  3. Token/s. প্রতি অনুরোধে ১,০০০ input (তিনটি নথিসহ RAG prompt, মাপা) আর ১২৮ output token: সেকেন্ডে ২,০০০ input আর ২৫৬ output token. Hosted API-তে বিলও এই দুই সংখ্যায়।
  4. Replica-র ক্ষমতা. মাপা μ = ৩.৮৬ অনুরোধ/সেকেন্ড (batch ১৬)। ৬০% ব্যবহারে একটি replica নেবে ২.৩২.
  5. Replica. ⌈২.০ ÷ ২.৩২⌉ = ১টি যথেষ্ট; একটি নষ্ট হলে বা আপডেটের সময় চালু রাখতে আরেকটি, মোট ২টি।
  6. যাচাই. Little-এর সূত্রে গড়ে একসঙ্গে ২.০ × ৪.১৪ = ৮.৩টি অনুরোধ GPU-তে, যা batch ১৬-এর slot-এ আঁটে। ক্যাম্পেইনে traffic তিন গুণ হলে ৬.০ ÷ ২.৩২ → ৩টি, সঙ্গে একটি বাড়তি।

নিচের কোড একই হিসাব কয়েকটি অবস্থায় চালায়: সাধারণ দিন, cache-এ ১৯% ঠিক hit (§৫-এর সবচেয়ে নিরাপদ সেটিংয়ে মাপা হার), আর ক্যাম্পেইনের দিনে তিন গুণ traffic. শেষ কলামে Little-এর সূত্র L = λW: গড়ে একসঙ্গে কতগুলো অনুরোধ GPU-তে চলছে, যা batch-এর slot-এর সঙ্গে মেলাতে হয়।

import math
MU = 3.86       # মাপা: একটি replica, batch 16, ~1000 input + 128 output token-এ অনুরোধ/সেকেন্ড
W = 4.14         # মাপা: সেই অবস্থায় একটি অনুরোধের মোট সময় (সেকেন্ড)

def plan(name, per_day, peak_share=0.12, tin=1000, tout=128, hit=0.0, util=0.6):
    rps = per_day * peak_share / 3600            # ব্যস্ততম ঘণ্টার গড় অনুরোধ/সেকেন্ড
    llm = rps * (1 - hit)                        # cache-এ ঠিক hit বাদে যা LLM-এ যায়
    n = math.ceil(llm / (MU * util))             # ব্যবহারের লক্ষ্য util রেখে replica
    inflight = llm * W                           # Little's law: L = λW
    print(f"{name}: peak {rps:.2f} req/s, in {rps*tin:.0f} tok/s, out {rps*tout:.0f} tok/s")
    print(f"   LLM {llm:.2f} req/s -> {n} replica (+1 = {n+1}), in-flight {inflight:.1f}")
    return n

plan("সাধারণ দিন", 60_000)
plan("cache 19%", 60_000, hit=0.19)
plan("ক্যাম্পেইন x3", 180_000)
plan("ক্যাম্পেইন+cache", 180_000, hit=0.19)
# বাংলা prompt-এ token বেশি হলে MU কমে; সেটি নতুন করে মাপতে হয়, অনুপাতে ভাগ করে নয়
for util in (0.5, 0.6, 0.8):
    print(f"util {util}: capacity per replica {MU*util:.2f} req/s")

আউটপুট (পাতার Python-এ চালানো):

সাধারণ দিন: peak 2.00 req/s, in 2000 tok/s, out 256 tok/s
   LLM 2.00 req/s -> 1 replica (+1 = 2), in-flight 8.3
cache 19%: peak 2.00 req/s, in 2000 tok/s, out 256 tok/s
   LLM 1.62 req/s -> 1 replica (+1 = 2), in-flight 6.7
ক্যাম্পেইন x3: peak 6.00 req/s, in 6000 tok/s, out 768 tok/s
   LLM 6.00 req/s -> 3 replica (+1 = 4), in-flight 24.8
ক্যাম্পেইন+cache: peak 6.00 req/s, in 6000 tok/s, out 768 tok/s
   LLM 4.86 req/s -> 3 replica (+1 = 4), in-flight 20.1
util 0.5: capacity per replica 1.93 req/s
util 0.6: capacity per replica 2.32 req/s
util 0.8: capacity per replica 3.09 req/s

সাধারণ দিনে একটি replica যথেষ্ট, বাড়তিসহ ২টি; ক্যাম্পেইনে ৩টি, বাড়তিসহ ৪টি। Cache-এর ১৯% ঠিক hit LLM-এর চাপ ৬.০০ থেকে ৪.৮৬-এ নামায়, কিন্তু replica-র সংখ্যা বদলায় না (৪.৮৬ ÷ ২.৩২ = ২.১, তবু ৩)। Replica পূর্ণসংখ্যা, তাই সাশ্রয় দেখা যায় token-এ আর headroom-এ। u = ০.৮ নিলে কম GPU লাগে, কিন্তু ব্যস্ত মুহূর্তে queue লম্বা হয় (§৭)।

৩ · পুরো নকশা — একটি অনুরোধের যাত্রা

নিচের ছবিতে বাজারঘরের সহায়কের অংশগুলো। ওপর থেকে নিচে একটি অনুরোধের পথ; ডান দিকে cache hit-এর সংক্ষিপ্ত পথ আর fallback; নিচে সব অংশ থেকে log আর trace.

GenAI help chatbot architecture গ্রাহক: অ্যাপ বা ওয়েব gateway: auth + rate limit input guard: PII mask cache: exact → semantic router: ছোট / বড় model RAG: খোঁজা + rerank vector LLM serving (replica) output guard: citation উত্তর (streaming) log · trace · token গোনা · eval (শুধু ঢাকা লেখা, নির্দিষ্ট দিনের পর মোছা) hit fallback 429
প্রতিটি বাক্সের নিজের timeout আর নিজের log. বাঁ দিকের ডটেড দাগ বোঝায়, প্রতিটি অংশ একই trace id-তে log লেখে।

প্রতিটি অংশের কাজ এক লাইনে। Gateway (সিস্টেম ডিজাইন পর্ব ১১) লগইন যাচাই করে, trace id দেয়। Rate limiter একজন গ্রাহক বা একটি ক্লায়েন্টের অনুরোধের হার বাঁধে; LLM-এ সীমা শুধু অনুরোধে নয়, token-এও রাখা যায়, কারণ একটি লম্বা অনুরোধ দশটি ছোটর সমান খরচ করে (সিস্টেম ডিজাইন পর্ব ৪৯)। Input guard PII ঢাকে, যাতে cache, log বা hosted API আসল নম্বর না পায়। Cache (§৫), router (§৪) আর RAG (পর্ব ৬৯)-এর পরে LLM serving GPU-তে অনেক অনুরোধ একসঙ্গে batch করে চালায় (পর্ব ৮৩ · LLM serving). Output guard শেষ পরীক্ষা (§৬), আর log ছাড়া কিছুরই মান মাপা যায় না (§৮)।

নিচের stepper-এ একটি সত্যিকারের অনুরোধ এই পথে যায়। PII ঢাকা, cache-এর cosine, খোঁজার ফল, token-এর সংখ্যা আর উত্তর desktop run থেকে; rate limiter-এর অবস্থা আর span-এর সময় উদাহরণ হিসেবে বসানো (নমুনা)।

গ্রাহকের বার্তা: “আমার অর্ডার BG-2026-004817 ফেরত দিয়েছি, bKash-এ রিফান্ড কবে আসবে? নম্বর ০১৭০০-০০০০০১” (বানানো)
অংশ
কী হলো
সিদ্ধান্ত
  1. Gateway. লগইন যাচাই, trace id দেওয়া, আর input guard: অর্ডার id আর ফোন নম্বর ঢাকা পড়ল (বাংলা অঙ্কের নম্বরও)। এরপর কোনো অংশ আসল নম্বর দেখে না।
  2. Rate limiter. এই গ্রাহকের bucket-এ ৪.২টি token ছিল, একটি খরচ হলো। সীমা ছাড়ালে এখানেই 429, GPU পর্যন্ত যেত না।
  3. Cache. ঢাকা লেখার সবচেয়ে কাছের entry “bKash-এ রিফান্ড কত দিনে আসে?”, cosine ০.৮৭৩. কিন্তু বার্তায় [ORDER] আছে, তাই নিয়ম অনুযায়ী ভাগ করা cache বাদ।
  4. Router. দুই অংশ: অর্ডারের অবস্থা (মালিকানা যাচাই করা tool, এই উদাহরণে চালানো হয়নি) আর রিফান্ডের নীতি (RAG).
  5. RAG. e5-small-এ শীর্ষ ৩: “রিফান্ড কত দিনে পাবেন” (০.৮৬৪), “ফেরত নীতি” আর “অর্ডার বাতিল” (দুটিই ০.৮৪০)। সঠিক নথি প্রথমে; বাকি দুটি বাড়তি token.
  6. LLM. Prompt-এ ১,৪৫৭ token (system prompt, তিনটি উৎস, ঢাকা প্রশ্ন); Qwen2.5-0.5B GPU-তে ১.৩২ সেকেন্ডে ৬৯ token লিখল। সত্যিকারের উত্তর হুবহু: “[1] (রিফান্ড কত দিনে পাবেন, 2026-09-15)” আর “[2] (ফেরত নীতি (Return policy), 2026-09-15)” — শুধু উৎসের লেবেল নকল, “৩ কর্মদিবস” নেই।
  7. Output guard. Citation নম্বর বৈধ, কিন্তু উত্তরে প্রশ্নের কোনো তথ্য নেই; নিয়ম “উত্তরে উৎসের লেবেল ছাড়া কিছু নেই → অচল” ধরে ফেলে। গ্রাহক এই লেখা দেখেন না; অনুরোধ বড় model-এ বা মানুষের কাছে যায়। ছোট model-এর এমন ভুল পর্ব ৬৯-এও দেখা গেছে।
  8. Log. Trace-এ থাকে ঢাকা লেখা, নথির id আর score, token-এর সংখ্যা, কোন পথ, প্রতিটি অংশের সময়। পরে অভিযোগ এলে এখান থেকেই খোঁজা শুরু।

৪ · Model routing আর fallback — ছোট model, বড় model, আর না পারলে কী

হাসপাতালের জরুরি বিভাগের মতো। দরজায় একজন রোগী দেখে ঠিক করেন: সাধারণ জ্বর হলে ইন্টার্ন, জটিল হলে বিশেষজ্ঞ, আর বিশেষজ্ঞ ব্যস্ত থাকলে প্রাথমিক চিকিৎসা দিয়ে পরে ডাকা।

Model routing মানে প্রতিটি অনুরোধের জন্য উপযুক্ত model বা পথ বেছে নেওয়া। বাজারঘরে তিনটি সহজ নিয়ম যথেষ্ট শুরুতে: (ক) অর্ডার id থাকলে অর্ডার-tool-এর পথ, cache বাদ; (খ) সাধারণ এক-তথ্যের নীতি প্রশ্ন ছোট model-এ; (গ) দুই নথির, হিসাবের বা অভিযোগের প্রশ্ন বড় model-এ। Router নিজে হতে পারে নিয়ম, একটি ছোট classifier (পর্ব ৪২, পর্ব ৪৮) বা একটি ছোট LLM. পর্ব ৮৬ · ক্যাপস্টোনে এই router-এর classifier আপনি নিজে বানাবেন।

গবেষণায় এর দুটি পরিচিত রূপ আছে। Cascade: আগে ছোট model, তার উত্তরের আত্মবিশ্বাস কম হলে বড় model; Chen, Zaharia ও Zou-র ২০২৩ সালের FrugalGPT প্রবন্ধ এভাবে খরচ কমানোর কথা রিপোর্ট করে। আগে থেকে route: প্রশ্ন দেখেই একটি শেখা router ঠিক করে কোন model; ২০২৪ সালের RouteLLM প্রবন্ধ এমন router শেখানোর একটি উপায় দেখায়। তাদের সাশ্রয়ের সংখ্যা সেই প্রবন্ধের ডেটার; বাজারঘরে নিজের eval set-এ মাপতে হবে।

Fallback হলো পরিকল্পিত দ্বিতীয় পথ, যখন প্রথম পথ সময়মতো উত্তর দেয় না। ক্রমটি সাধারণত: বড় model → (timeout বা ত্রুটি) → ছোট model → (তাও না হলে) নির্দিষ্ট বার্তা আর মানুষের কাছে টিকিট। এখানে মাপা তথ্য মনে রাখুন: পর্ব ৬৯-এ Qwen2.5-1.5B RAG-সহ ৩৩টি উত্তরযোগ্য প্রশ্নের ১৯–২০টি ঠিক করেছিল, আর উত্তরহীন ৮টির মাত্র ১–২টিতে “নেই” বলেছিল। ছোট model-এ fallback মানে মান কমানো; তাই fallback-এর উত্তরেও output guard চলবে, আর সেই পথের মান আলাদা করে মাপতে হবে। Circuit breaker (সিস্টেম ডিজাইন পর্ব ৪৮) বড় model বারবার ব্যর্থ হলে কিছুক্ষণ সরাসরি fallback-এ পাঠায়, যাতে প্রতিটি অনুরোধ timeout পর্যন্ত অপেক্ষা না করে।

Fallback-এর চুপচাপ ক্ষতি. Fallback চালু হলে dashboard-এ সব সবুজ দেখায়, কারণ প্রতিটি অনুরোধ “সফল”। কিন্তু উত্তরের মান নেমে গেছে। তাই কোন পথে কতগুলো উত্তর গেল, তা আলাদা metric হিসেবে রাখুন, আর fallback-এর হার একটি সীমা ছাড়ালে alert দিন।

৫ · Cache — exact আর semantic, আর ভুল hit-এর দাম

সাধারণ cache-এর তত্ত্ব (cache-aside, TTL, invalidation) সিস্টেম ডিজাইন কোর্সের পর্ব ২৮ আর পর্ব ২৯-এ। LLM ব্যবস্থায় তিন রকম cache থাকে, আর এরা আলাদা জিনিস:

Prefix (prompt) cache

একই শুরুর অংশের হিসাব (KV cache) আবার ব্যবহার। উত্তর বদলায় না, শুধু দ্রুত আর সস্তা। পর্ব ৬৫-এ মাপা: TTFT ৮.০৫ থেকে ০.৩৭ সেকেন্ড।

Exact cache

হুবহু একই প্রশ্ন (ফাঁকা জায়গা আর যতিচিহ্ন বাদে) এলে আগের উত্তর। ভুল hit প্রায় অসম্ভব, কিন্তু hit কম।

Semantic cache

অর্থে কাছাকাছি প্রশ্ন (embedding-এর cosine ≥ τ) এলে আগের উত্তর। Hit বেশি, কিন্তু অন্য প্রশ্নের উত্তর দিয়ে দেওয়ার ঝুঁকি।

Semantic cache-এর ধারণা সহজ: আগের প্রশ্নগুলোর embedding রাখা, নতুন প্রশ্নের সবচেয়ে কাছেরটি খোঁজা (পর্ব ৬৭), আর cosine একটি সীমা τ (tau) ছাড়ালে তার উত্তর ফেরত দেওয়া। GPTCache-এর মতো open-source project এই নকশা জনপ্রিয় করেছে। প্রশ্ন হলো, বাংলা help desk-এ τ কত হবে, আর তাতে কতটা ভুল hit (false hit) আসে। মাপার জন্য হাতে লেখা: পর্ব ৬৯-এর প্রশ্ন থেকে ১৬টি cache entry আর ৫৯টি নতুন প্রশ্ন; model card-এর কথামতো symmetric কাজে দুই পাশেই "query: " prefix. এর ৩৯টির উত্তর কোনো একটি entry-র সঙ্গে হুবহু এক (২টি হুবহু পুনরাবৃত্তি, ১৬টি বাংলায় অন্যভাবে বলা, ১৩টি Banglish, ৮টি ইংরেজি)। বাকি ২০টি দেখতে কাছাকাছি, কিন্তু উত্তর আলাদা: “ঢাকা সিটিতে ফ্রিজের ডেলিভারি চার্জ কত?” (৫০০ টাকা, ৬০ নয়), “শনিবার কাস্টমার কেয়ার কখন খোলে?” (সকাল ৯টা, বিকেল ৩টা নয়)।

~/bazarghar-dl$ uv run python cache_run.py   # সত্যিকারের run, CPU, ছোট করে দেখানো
torch 2.15.0 transformers 5.22.2 sentence-transformers 6.2.0 | CPU threads 3
cache entries 16, incoming 59 (should-hit 39, near-miss 20); embed per query 7.4 ms (CPU, batch 1)
exact cache (normalised text): hits 2/59, wrong 0
nearest entry is the right one for 35/39 should-hit queries
tau   hit-rate  right-hits  false-hits  false-hit-share
0.84  0.915     34/39       20          0.370
0.88  0.746     26/39       18          0.409
0.92  0.441     16/39       10          0.385
0.95  0.254      9/39        6          0.400
0.97  0.119      5/39        2          0.286
repeat    n= 2 cos min 0.976 median 0.986 max 0.996
bn        n=16 cos min 0.861 median 0.947 max 0.989
banglish  n=13 cos min 0.791 median 0.873 max 0.914
en        n= 8 cos min 0.869 median 0.897 max 0.927
near      n=20 cos min 0.835 median 0.920 max 0.976

১. Exact cache নিরাপদ, কিন্তু প্রায় অকেজো. ৫৯টির মাত্র ২টিতে hit, দুটিই ঠিক।

২. Semantic cache-এ ভুল hit কোনো threshold-এ কমে না. τ = ০.৮৪-এ ৩৪টি ঠিক hit আর ২০টি ভুল; τ = ০.৯৫-এ ৯টি ঠিক আর ৬টি ভুল। প্রতিটি τ-তে hit-এর প্রায় ৩০–৪০% ভুল। কারণ শেষ লাইনগুলোতে: “উত্তর আলাদা” প্রশ্নগুলোর (near) cosine-এর median ০.৯২০, যা Banglish (০.৮৭৩) আর ইংরেজিতে (০.৮৯৭) বলা একই প্রশ্নের চেয়ে বেশি। “শুক্রবার” আর “শনিবার” embedding-এর কাছে প্রায় একই।

৩. Banglish-এ cache কম কাজ করে. ৩৯টির ৪টিতে সবচেয়ে কাছের entry-ই ভুল, সবই Banglish: “gift wrap er charge koto?”-এর কাছে Rocket-এর প্রশ্ন। Lookup সস্তা: CPU-তে ৭.৪ মিলিসেকেন্ড।

নিচের stepper-এ একই cache entry (“ঢাকা সিটিতে ডেলিভারি চার্জ কত?”, উত্তর ৬০ টাকা) আর τ = ০.৯৫-এ তিনটি সত্যিকারের প্রশ্ন।

Cache-এ: “ঢাকা সিটিতে ডেলিভারি চার্জ কত?” → “৬০ টাকা” · cosine e5-small-এর, সত্যিকারের run
ধাপ
প্রশ্ন
cosine
সিদ্ধান্ত
ঠিক?
  1. নিয়ম. নতুন প্রশ্নের embedding নিয়ে cache-এর সবচেয়ে কাছের entry; cosine ০.৯৫ বা বেশি হলে LLM না ডেকে আগের উত্তর।
  2. প্রশ্ন ক. “সিটি” বদলে “শহর”, সঙ্গে “টাকা”। Cosine ০.৯৮৯, hit. উত্তর সত্যিই এক, তাই ঠিক hit: কয়েক সেকেন্ডের GPU-কাজ বাঁচল।
  3. প্রশ্ন খ. শুধু “বড় পণ্যের” যোগ হয়েছে, অথচ উত্তর বদলে ৫০০ টাকা। Cosine ০.৯৭৬, তবু hit: গ্রাহক ভুল চার্জ জানলেন। এটিই false hit.
  4. প্রশ্ন গ. একই প্রশ্ন Banglish-এ, উত্তর একই ৬০ টাকা। Cosine ০.৮৬৫, তাই miss; প্রশ্নটি RAG আর LLM-এ গেল। উত্তর ঠিক আসবে, শুধু সাশ্রয়টি হলো না।
  5. হিসাব. এখানে উল্টো ক্রম: ভুল প্রশ্নের cosine (০.৯৭৬) ঠিক প্রশ্নের (০.৮৬৫) চেয়ে বেশি। কোনো একটি τ দুটিকেই ঠিক ভাগ করতে পারে না।

তাহলে উপায়? তিনটি পথ। (ক) শুধু cosine নয়, অর্থের মূল শব্দও মেলানো: জায়গা, পণ্য, দিন আর পেমেন্টের শব্দ দুই প্রশ্নে একই থাকলেই hit. (খ) প্রশ্ন নয়, intent ধরে cache: router-এর classifier প্রশ্নকে “ঢাকার ডেলিভারি চার্জ”-এর মতো একটি নির্দিষ্ট FAQ-id দেয়, আর cache-এর key সেই id; ভুল hit তখন classifier-এর ভুলের সমান। (গ) Cache শুধু স্থির উত্তরে, নীতি বদলালে পুরো cache মোছা। নিচের কোড সত্যিকারের run-এর ৫৯টি cosine (পাতায় cache_rows.tsv হিসেবে রাখা) নিয়ে sweep করে, আর পথ (ক)-এর একটি ছোট নিয়ম যোগ করে দেখায়। প্রথমবার চালাতে pandas আর matplotlib নামতে কয়েক সেকেন্ড লাগে।

import numpy as np, pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv("cache_rows.tsv", sep="\t")     # সত্যিকারের run-এর cosine, ৫৯টি প্রশ্ন
# ok = 1: সবচেয়ে কাছের cache entry-র উত্তর এই প্রশ্নেও হুবহু ঠিক
SLOTS = [("ঢাকা", "dhaka"), ("গাজীপুর", "gazipur"), ("চট্টগ্রাম",), ("ভারত", "india"),
         ("ফ্রিজ", "বড় পণ্য"), ("express",), ("কার্ড",), ("bkash", "বিকাশ"), ("রিফান্ড", "refund"),
         ("শুক্রবার", "friday"), ("শনিবার",), ("১২ মাস",), ("ব্যাটারি", "battery"), ("ফ্রি", "free")]
def same_slots(a, b):                 # দুই প্রশ্নে একই জায়গা, পণ্য, দিন, পেমেন্টের শব্দ আছে কি
    a, b = a.lower(), b.lower()
    return all(any(w in a for w in g) == any(w in b for w in g) for g in SLOTS)
df["slot_ok"] = [same_slots(q, c) for q, c in zip(df["query"], df["cached"])]

rows = []
for tau in np.arange(0.84, 0.985, 0.02):
    hit = df["cos"] >= tau - 1e-9
    g = hit & df["slot_ok"]
    rows.append([round(tau, 2), hit.mean(), int((hit & (df.ok == 1)).sum()), int((hit & (df.ok == 0)).sum()),
                 int((g & (df.ok == 1)).sum()), int((g & (df.ok == 0)).sum())])
t = pd.DataFrame(rows, columns=["tau", "hit_rate", "right", "false", "right+slot", "false+slot"])
print(t.round(3).to_string(index=False))
print(df.groupby("kind")["cos"].median().round(3).to_dict())

plt.figure(figsize=(5.2, 3.4))
plt.plot(t.tau, t.right, "o-", label="right hits")
plt.plot(t.tau, t["false"], "s-", label="false hits")
plt.plot(t.tau, t["false+slot"], "s--", label="false hits + slot guard")
plt.xlabel("similarity threshold (tau)"); plt.ylabel("queries (of 59)")
plt.title("Semantic cache: e5-small, one local run"); plt.legend(); plt.grid(alpha=.3)
plt.show()

আউটপুট (পাতার Python-এ চালানো; ছবিটি কোডের নিচে আঁকা হয়):

 tau  hit_rate  right  false  right+slot  false+slot
0.84     0.915     34     20          34           6
0.86     0.881     33     19          33           6
0.88     0.746     26     18          26           6
0.90     0.644     21     17          21           6
0.92     0.458     16     11          16           2
0.94     0.305     11      7          11           0
0.96     0.203      7      5           7           0
0.98     0.068      4      0           4           0
{'banglish': 0.873, 'bn': 0.947, 'en': 0.897, 'near': 0.921, 'repeat': 0.986}

পাতার ডেটায় cosine তিন দশমিক পর্যন্ত রাখা, তাই τ = ০.৯২-এর সংখ্যা desktop-এর সারণির চেয়ে একটু আলাদা (একটি প্রশ্নের cosine ঠিক ০.৯২০)। মূল ফল: শব্দ মেলানোর পাহারা ভুল hit ২০ থেকে ৬-এ নামাল (τ = ০.৮৪), আর τ = ০.৯৪-এ শূন্যে, একটিও ঠিক hit না হারিয়ে। কিন্তু সাবধান: শব্দের তালিকাটি আমি এই ২০টি ভুল দেখে লিখেছি। একই ডেটায় নিয়ম বানিয়ে একই ডেটায় মাপা মানে উত্তর দেখে পরীক্ষা দেওয়া; নতুন, আলাদা প্রশ্নের set-এ আবার মাপা না হলে এই “শূন্য” বিশ্বাসযোগ্য নয়। τ = ০.৯৪-এ ৩৯টি পুনরাবৃত্ত প্রশ্নের মাত্র ১১টি hit: নিরাপদ semantic cache-এর সাশ্রয় ছোট।

ব্যক্তিগত উত্তর কখনো ভাগ করা cache-এ নয়. “আমার অর্ডার কোথায়?”-এর উত্তর একজনের; অন্য গ্রাহকের কাছে গেলে তা তথ্য ফাঁস। Cache-এর key-তে PII ঢাকা লেখা থাকলেও উত্তর ব্যক্তিগত হতে পারে। নিয়ম: router অর্ডার বা অ্যাকাউন্টের প্রশ্ন চিনলে cache পুরো বাদ, আর cache-এ শুধু নীতির সাধারণ উত্তর, প্রতিটির সঙ্গে নীতির সংস্করণ, যাতে নীতি বদলালে পুরনো উত্তর মোছা যায়।

৬ · Guardrail — input আর output-এর পাহারা, PII ঢাকা

Guardrail হলো model-এর বাইরে বসানো পরীক্ষা, যা model-কে বিশ্বাস না করেই নিয়ম প্রয়োগ করে। পর্ব ৭৩-এ মাপা শিক্ষাটি এখানে নকশার নিয়ম: নিয়মভিত্তিক input detector চেনা আক্রমণ ধরে কিন্তু নতুন শব্দের আক্রমণ ছাড়ে, আর output filter (canary নিয়ম আর URL allowlist) ১.৫B-এর ৩৯৬টি উত্তরের সব ১০টি ক্ষতিকর উত্তর থামিয়েছিল। তাই ভারী কাজ output-এর দিকে, আর কোনো একটি স্তরের ওপর পুরো ভরসা নয় (defense in depth).

কোথায়কী পরীক্ষাধরা পড়লে
InputPII ঢাকা (ফোন, email, অর্ডার id); দৈর্ঘ্যের সীমা; পরিচিত injection ধাঁচঢাকা লেখা নিয়ে এগোনো; খুব লম্বা হলে ফেরত
Retrievalশুধু active নথি; গ্রাহক যা দেখার অনুমতি পানবাদ দেওয়া (পর্ব ৬৯-এর filter)
Toolঅর্ডারের মালিকানা, argument-এর allowlisttool না চালানো, মানুষের অনুমোদন
Output[n] বৈধ আর সংখ্যা উৎসে আছে; অনুমোদনহীন URL বা ফোন নেই; উত্তরে PII নেইউত্তর আটকে নিরাপদ বার্তা বা মানুষের কাছে

PII masking শুরু হয় সবচেয়ে সস্তা স্তর দিয়ে: regex. বাংলাদেশের মোবাইল নম্বর সাধারণত ০১ দিয়ে শুরু ১১ অঙ্ক, সামনে +৮৮০ থাকতে পারে; বাংলা অঙ্কে লেখাও সাধারণ। নিচের কোড তিন ধরনের PII ঢাকে আর হাতে লেখা ১০টি বানানো লেখায় precision আর recall মাপে (AI-এর ভিত্তি কোর্সের পর্ব ৬০)। সব নম্বর ইচ্ছে করে বানানো (০০০০০-এ ভরা), email-এর domain .invalid, যা কখনো আসল ঠিকানা হয় না। কোনো বাক্য যেমন ইনভয়েস নং 01800000006 ইচ্ছে করে ফাঁদ।

import re
from collections import Counter
B2E = str.maketrans("০১২৩৪৫৬৭৮৯", "0123456789")   # এক অক্ষরে এক অক্ষর: অবস্থান বদলায় না
RULES = {"PHONE": r"(?<!\d)(?:\+?880[\s-]?|0)1[3-9]\d{2}[\s-]?\d{3}[\s-]?\d{3}(?!\d)",
         "EMAIL": r"[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+",
         "ORDER": r"(?i)\bBG-?\d{4}-?\d{6}\b"}
def mask(text):
    t = text.translate(B2E)
    found = [(m.start(), m.end(), k) for k, p in RULES.items() for m in re.finditer(p, t)]
    for s, e, k in sorted(found, reverse=True):     # পেছন থেকে বদলালে আগের অবস্থান ঠিক থাকে
        text = text[:s] + f"[{k}]" + text[e:]
    return text, Counter((k, t[s:e]) for s, e, k in found)
P, O, E = "PHONE", "ORDER", "EMAIL"                 # সব লেখা আর নম্বর বানানো; GOLD = যা ঢাকা উচিত
DATA = [("আমার নম্বর ০১৭০০-০০০০০১, অর্ডার BG-2026-004817 এখনো আসেনি।", [(P, "01700-000001"), (O, "BG-2026-004817")]),
 ("call me at +880 1800 000 002 or mail rahim.k@example.invalid", [(P, "+880 1800 000 002"), (E, "rahim.k@example.invalid")]),
 ("order bg2026004817 er status ki? phone 8801500000003", [(O, "bg2026004817"), (P, "8801500000003")]),
 ("ট্র্যাকিং নম্বর 01900000000008 দিয়ে খুঁজুন", []),
 ("বিকাশ নম্বর 019 0000 0004, টাকা পাঠিয়েছি", [(P, "019 0000 0004")]),
 ("মেইল: karim_bd(at)example.invalid", [(E, "karim_bd(at)example.invalid")]),
 ("ইনভয়েস নং 01800000006, দাম ১,২৫০ টাকা", []),
 ("অর্ডার BG-2026-118204 আর BG-2026-118205 দুটোই ভাঙা", [(O, "BG-2026-118204"), (O, "BG-2026-118205")]),
 ("nadia.h+orders@shop.example.invalid-এ রসিদ, ফোন ০১৩০০০০০০০৫", [(E, "nadia.h+orders@shop.example.invalid"), (P, "01300000005")]),
 ("রেফারেন্স 017000000007, সময় 10:30", [])]
tp, fp, fn = Counter(), Counter(), Counter()
for text, gold in DATA:
    masked, pred = mask(text); g = Counter(gold)
    for (k, v), n in pred.items(): tp[k] += min(n, g[(k, v)]); fp[k] += max(0, n - g[(k, v)])
    for (k, v), n in g.items(): fn[k] += max(0, n - pred[(k, v)])
    print(masked)
for k in RULES:
    pr = tp[k] / (tp[k] + fp[k]) if tp[k] + fp[k] else 0.0
    print(f"{k:5} TP {tp[k]} FP {fp[k]} FN {fn[k]}  precision {pr:.2f}  recall {tp[k] / (tp[k] + fn[k]):.2f}")

আউটপুট (পাতার Python-এ চালানো):

আমার নম্বর [PHONE], অর্ডার [ORDER] এখনো আসেনি।
call me at [PHONE] or mail [EMAIL]
order [ORDER] er status ki? phone [PHONE]
ট্র্যাকিং নম্বর 01900000000008 দিয়ে খুঁজুন
বিকাশ নম্বর 019 0000 0004, টাকা পাঠিয়েছি
মেইল: karim_bd(at)example.invalid
ইনভয়েস নং [PHONE], দাম ১,২৫০ টাকা
অর্ডার [ORDER] আর [ORDER] দুটোই ভাঙা
[EMAIL]এ রসিদ, ফোন [PHONE]
রেফারেন্স 017000000007, সময় 10:30
PHONE TP 4 FP 1 FN 1  precision 0.80  recall 0.80
EMAIL TP 1 FP 1 FN 2  precision 0.50  recall 0.33
ORDER TP 4 FP 0 FN 0  precision 1.00  recall 1.00

বাংলা অঙ্কের নম্বর (০১৭০০-০০০০০১) ধরা পড়েছে, কারণ খোঁজার আগে অঙ্ক এক অক্ষর থেকে এক অক্ষরে বদলানো হয়, তাই অবস্থান ঠিক থাকে। ১৪ অঙ্কের ট্র্যাকিং নম্বর আর ১২ অঙ্কের রেফারেন্স নিরাপদে বাদ (lookaround (?<!\d) আর (?!\d)). ভুলগুলো শিক্ষণীয়। ফোন: “019 0000 0004”-এর মতো ৩-৪-৪ ভাগে লেখা নম্বর নিয়মে নেই (FN), আর ১১ অঙ্কের ইনভয়েস নম্বর ফোন বলে ঢাকা পড়েছে (FP); নিয়ম প্রসঙ্গ বোঝে না। Email: “(at)” দিয়ে লুকানো ঠিকানা ধরা পড়েনি; আর “…invalid-এ” লেখায় বাংলা বিভক্তির আগের “-” email-এর অংশ ধরে নেওয়ায় কাটা অংশ gold-এর সঙ্গে মেলেনি, তাই একই ঠিকানা একবার FP আর একবার FN. ঠিকানাটি ঢাকা পড়েছে, তবু কড়া span-মাপে এটি ভুল। অর্ডার id-এর নির্দিষ্ট ছাঁচ (BG-বছর-৬ অঙ্ক) থাকায় চারটিই ঠিক। ১০টি লেখা খুব ছোট নমুনা। নাম আর ঠিকানার জন্য NER model লাগে (পর্ব ৪৩), যারও ভুল আছে।

৭ · ব্যর্থতার নকশা — timeout, retry, rate limit আর degraded mode

LLM ব্যবস্থায় ব্যর্থতা রোজকার ঘটনা: GPU-র slot ভরা, hosted API-র rate limit, vector store-এর বিরতি। প্রতিটি অংশের জন্য চারটি প্রশ্ন আগে থেকে ঠিক রাখুন: কতক্ষণ অপেক্ষা (timeout), আবার চেষ্টা কি না (retry), না পারলে বিকল্প কী (fallback), আর কখন অনুরোধ নেওয়াই বন্ধ (load shedding). বাজারঘরে মোট সময়ের সীমা ৮ সেকেন্ড; তার ভেতরে RAG-এর ভাগ ০.৫ সেকেন্ড, বড় model-এর প্রথম token-এর ২ সেকেন্ড। Retry শুধু এক স্তরে, একবার, backoff-সহ (ভাবনার প্রশ্ন ৪)। ক্রমটি এমন: বড় model → ছোট model → “আপনার প্রশ্নটি একজন প্রতিনিধির কাছে পাঠানো হলো” আর টিকিট। Rate limiter-এর token bucket ছোট burst দেয়, কিন্তু গড় হার বাঁধে।

নকশাটি কাজ করে কি না, আগে simulator-এ দেখা সস্তা। নিচের কোড ক্যাম্পেইনের আধা ঘণ্টা চালায়: প্রতি সেকেন্ডে গড়ে ৬.০টি অনুরোধ (এলোমেলো, seed ৭), একটি ক্লায়েন্ট সব অনুরোধের ৪% পাঠায় (ভুল কোড বা অপব্যবহার), প্রতিটি replica-তে ১৬টি slot, প্রতিটি অনুরোধের সময় §২-এর মাপা ৪.১৪ সেকেন্ডের ±২০%. মোট সময় ৮ সেকেন্ড পেরোবে মনে হলে অনুরোধ ছোট model-এ যায় (ধরা হয়েছে ১.৫ সেকেন্ড)। এটি একটি সরল মডেল (নমুনা অনুমানসহ), আসল serving engine নয়: batch বড় হলে প্রতিটি অনুরোধ ধীর হয়, এখানে তা ধরা নেই।

import numpy as np
SVC, SLOTS = 4.14, 16            # মাপা: batch 16-এ একটি অনুরোধের সময় (সে.), replica প্রতি slot
TOK, BUDGET, LAM = 1000 + 128, 8.0, 6.0   # token/অনুরোধ, মোট সময়ের সীমা, ক্যাম্পেইনের peak req/s

def run(replicas, p_hit, seed=7, T=1800):
    rng = np.random.default_rng(seed)
    t = np.cumsum(rng.exponential(1 / LAM, int(LAM * T * 1.2))); t = t[t < T]
    user = rng.integers(0, 3000, t.size)
    user[rng.random(t.size) < 0.04] = 7                 # একটি ক্লায়েন্ট সব traffic-এর ৪%
    free, bucket, res = np.zeros(SLOTS * replicas), {}, []
    for ti, u in zip(t, user):
        tok, last = bucket.get(u, (5.0, ti))            # token bucket: burst ৫, প্রতি ২০ সে. ১টি
        tok = min(5.0, tok + (ti - last) / 20)
        if tok < 1:
            bucket[u] = (tok, ti); res.append(("429", 0.0, 0)); continue
        bucket[u] = (tok - 1, ti)
        if rng.random() < p_hit:
            res.append(("cache", 0.05, 0)); continue
        k = free.argmin(); wait = max(0.0, free[k] - ti); svc = SVC * rng.uniform(0.8, 1.2)
        if wait + svc > BUDGET:                         # সীমা পেরোবে: ছোট model-এ fallback
            res.append(("fallback", 1.5, TOK)); continue
        free[k] = ti + wait + svc; res.append(("big", wait + svc, TOK))
    kind = np.array([r[0] for r in res]); lat = np.array([r[1] for r in res])
    ok = kind != "429"
    p50, p95 = np.percentile(lat[ok], [50, 95])
    c = {k: int((kind == k).sum()) for k in ["big", "fallback", "cache", "429"]}
    big_tok = sum(r[2] for r in res if r[0] == "big") / 1e6
    print(f"replica {replicas}, hit {p_hit:.2f}: {c}")
    print(f"   p50 {p50:.2f}s  p95 {p95:.2f}s  big-model tokens {big_tok:.2f}M")

for rep, h in [(1, 0.0), (1, 0.19), (2, 0.0), (2, 0.19)]:
    run(rep, h)

আউটপুট (পাতার Python-এ চালানো):

replica 1, hit 0.00: {'big': 7300, 'fallback': 3139, 'cache': 0, '429': 312}
   p50 7.01s  p95 7.90s  big-model tokens 8.23M
replica 1, hit 0.19: {'big': 7117, 'fallback': 1355, 'cache': 1967, '429': 312}
   p50 6.45s  p95 7.83s  big-model tokens 8.03M
replica 2, hit 0.00: {'big': 10439, 'fallback': 0, 'cache': 0, '429': 312}
   p50 4.16s  p95 4.91s  big-model tokens 11.78M
replica 2, hit 0.19: {'big': 8472, 'fallback': 0, 'cache': 1967, '429': 312}
   p50 3.94s  p95 4.87s  big-model tokens 9.56M

১. এক replica-তে ক্যাম্পেইন সামলানো যায় না. একটি replica-র ক্ষমতা ১৬ ÷ ৪.১৪ = ৩.৮৬ অনুরোধ/সেকেন্ড, আসছে ৬.০. Queue বাড়তে বাড়তে অপেক্ষা ৮ সেকেন্ডের সীমা ছোঁয়, তাই ১০,৪৩৯টি অনুরোধের ৩,১৩৯টি (প্রায় ৩০%) ছোট model-এ গেছে, আর বড় model-এর উত্তরেও p50 ৭.০১ সেকেন্ড। Fallback না থাকলে এগুলো timeout হতো। Cache fallback ১,৩৫৫-এ নামায়।

২. দুই replica-তে স্বাভাবিক. কোনো fallback নেই; p50 ৪.১৬ আর p95 ৪.৯১ সেকেন্ড, প্রায় পুরোটা নিজের কাজের সময়, অপেক্ষা নয়। এখানে cache latency প্রায় বদলায় না, কিন্তু বড় model-এর token ১১.৭৮ থেকে ৯.৫৬ মিলিয়নে নামায় (১৯%). §২-এর হিসাব ৩টি চেয়েছিল; ২টিতেও চলে, তবে প্রায় ৭৮% ব্যবহারে, ফাঁক ছাড়া। একটি নষ্ট হলেই প্রথম লাইনের অবস্থা।

৩. Rate limiter একজনকে থামায়, বাকিদের নয়. ৩১২টি 429-এর সবই সেই একটি ক্লায়েন্টের (আলাদা করে গুনে দেখা): সে ৪০৬টি পাঠিয়েছিল, bucket অনুমতি দিয়েছে ৯৪টি (৫টি burst আর প্রতি ২০ সেকেন্ডে একটি)। অন্য কোনো গ্রাহক 429 পাননি। সংখ্যাগুলো একটি seed-এর; কয়েকটি seed-এ চালিয়ে দেখুন।

৮ · Observability, CI-তে eval আর ডেটার গোপনীয়তা

সাধারণ সেবায় observability মানে log, metric আর trace (সিস্টেম ডিজাইন পর্ব ৫০)। LLM সেবায় প্রতিটি trace-এ আরও কয়েকটি ক্ষেত্র লাগে: কোন model আর কোন prompt-সংস্করণ, input আর output token, TTFT, কোন নথি এল (id আর score), cache hit বা miss, কোন guard আটকাল, আর কোন পথ (বড় model, fallback, মানুষ)। খরচ টাকায় না লিখে token আর GPU-সেকেন্ডে লিখলে দাম বদলালেও হিসাব টেকে। অভিযোগ এলে trace id ধরে দেখা যায়: ভুল নথি এসেছিল, না model ভুল পড়েছিল (পর্ব ৭০)।

CI-তে eval. Prompt, model, chunking বা threshold বদলানো মানে কোড বদলানো; প্রতিটি বদল একটি স্থির eval set-এ চলবে, ঠিক unit test-এর মতো (পর্ব ৬৬)। বাজারঘরের জন্য তিনটি set: (ক) RAG-এর প্রশ্ন, উত্তরহীন প্রশ্নসহ (পর্ব ৭০); (খ) নিরাপত্তার canary আর red-team লেখা (পর্ব ৭৩); (গ) cache আর PII-এর set, এই পর্বের মতো। নতুন সংস্করণ কোনো set-এ উল্লেখযোগ্যভাবে খারাপ হলে merge নয় (CI দেখে)। তারপর traffic-এর ছোট অংশে canary release আর মানুষের পর্যালোচনা (মেশিন লার্নিং কোর্সের পর্ব ৬৯)।

গোপনীয়তা আর ডেটা রাখা. Data minimisation-এর নীতি এখানে সরাসরি নকশা: log-এ শুধু ঢাকা লেখা; trace-এ গ্রাহকের id-এর বদলে একটি ছদ্ম-id; পুরো কথোপকথন রাখলে নির্দিষ্ট দিনের পর মুছে ফেলা (data retention-এর নিয়ম), আর eval বা fine-tuning-এ ব্যবহারের আগে আলাদা অনুমতি আর আবার ঢাকা। Hosted API-র প্রদাতা লেখা কতদিন রাখে, তা তাঁদের নথি আর চুক্তিতে, তারিখসহ পড়ুন (ভাবনার প্রশ্ন ৫)। বাংলাদেশে ২০২৬ সালে ব্যক্তিগত উপাত্ত সুরক্ষা আইন ২০২৬ (৬৩ নং), জাতীয় উপাত্ত ব্যবস্থাপনা আইন ২০২৬ (৮০ নং) আর সাইবার সুরক্ষা আইন ২০২৬ (৮১ নং) হয়েছে। এগুলো প্রাসঙ্গিক হতে পারে; কোন ধারা কীভাবে প্রযোজ্য, তা নিয়ে আইনজ্ঞের পরামর্শ নিন। সাধারণ privacy engineering সাইবার নিরাপত্তা কোর্সের পর্ব ৭০-এ।

৯ · নিজে চালানো, নাকি hosted API — build বনাম buy

LLM-এর দুটি মূল পথ: hosted API, অথবা নিজের GPU-তে open-weight model (Qwen, Llama-র মতো)। দাম বদলায়, তাই এখানে তুলনা দায়িত্বের, দামের নয়।

দিকHosted APIনিজের GPU-তে open model
শুরু করাএক দিনে; key আর কয়েক লাইন কোডGPU, serving engine, monitoring; কয়েক সপ্তাহ
খরচের এককপ্রতি token (input আর output আলাদা)GPU-ঘণ্টা; ব্যবহার কম হলেও GPU-র খরচ চলে
মানসাধারণত বড় model; বাংলায় মান নিজের eval-এ মাপতে হয়ছোট model-এ মান কম হতে পারে (পর্ব ৬৯-এর ১.৫B); fine-tune করা যায় (পর্ব ৫৫)
ডেটালেখা প্রদাতার কাছে যায়; রাখার শর্ত চুক্তিতেডেটা নিজের কাছে; নিরাপত্তার দায়ও নিজের
নিয়ন্ত্রণপ্রদাতা model বদলাতে বা বন্ধ করতে পারে; rate limit তাঁদেরVersion স্থির রাখা যায়; ধারণক্ষমতা নিজের হিসাব

অনেক দল দুটি একসঙ্গে রাখে: সহজ প্রশ্ন নিজের ছোট model-এ, কঠিনগুলো hosted API-তে, বা উল্টো দিকে fallback. দুই পথেই নিজের eval set লাগে: বাংলা প্রশ্নে মান আপনাকেই মাপতে হবে। Container আর Kubernetes-এ নিজের serving চালানোর ভিত্তি ক্লাউড ও DevOps কোর্সের পর্ব ৩৬ থেকে; model export আর deploy-এর পথ পর্ব ৮৪ · Deep learning model deploy-এ।

আরও গভীরে. LLM serving-এর throughput বাড়ানোর মূল কৌশল continuous batching (Yu ও সহকর্মীদের Orca, OSDI ২০২২) আর KV cache-এর পাতা-ভিত্তিক ব্যবস্থাপনা (Kwon ও সহকর্মীদের PagedAttention, SOSP ২০২৩, যা vLLM-এর ভিত্তি); এগুলো পর্ব ৮৩-এর বিষয়। নিরাপত্তার ঝুঁকির তালিকার জন্য OWASP-এর LLM অ্যাপ্লিকেশনের Top 10 (পর্ব ৭৩), আর ঝুঁকি ব্যবস্থাপনার কাঠামো হিসেবে NIST-এর AI Risk Management Framework (২০২৩) পড়ে দেখতে পারেন। Semantic cache-এর ভুল hit নিয়ে প্রকাশিত মাপ কম; নিজের ডেটায় মাপাই ভরসা।

পরীক্ষা ও ইন্টারভিউতে.

  • দিনে ৫০,০০০ অনুরোধ, ব্যস্ততম ঘণ্টায় ১২%; peak RPS? ৫০,০০০ × ০.১২ ÷ ৩,৬০০ ≈ ১.৬৭ অনুরোধ/সেকেন্ড।
  • Semantic cache-এর ঝুঁকি কী? কাছাকাছি কিন্তু আলাদা প্রশ্নে পুরনো উত্তর (false hit); শুধু threshold দিয়ে থামে না, intent বা মূল শব্দ মেলাতে হয়।
  • Fallback থাকলে availability ১০০%? না; উত্তর এলেও মান কমতে পারে, তাই পথ ধরে মান মাপতে হয়।
  • LLM-এ rate limit অনুরোধে না token-এ? দুটোতেই; একটি লম্বা অনুরোধ অনেক ছোটর সমান GPU খায়।
  • PII কোথায় ঢাকবেন? সবার আগে, gateway-র পরেই, যাতে cache, log আর বাইরের API আসল তথ্য না পায়।

১০ · AI-এর যুগে — GenAI নকশার দাবি যাচাই

AI assistant একটি “production-ready chatbot architecture” কয়েক সেকেন্ডে এঁকে দেয়: বাক্স, তির, সব নাম ঠিক। ভুলগুলো থাকে সংখ্যায় আর অনুমানে:

AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
Throughput বা GPU সংখ্যা মুখস্থ সংখ্যায়Model, prompt-এর দৈর্ঘ্য, batch আর engine-এ বহুগুণ বদলায়নিজের prompt-এ নিজের যন্ত্রে মাপা (§২)
Semantic cache-এ “τ = ০.৯ দিন”Embedding model আর ভাষাভেদে মান আলাদা; ভুল hit মাপা হয় নাউত্তর-আলাদা প্রশ্নসহ set-এ sweep (§৫)
“Guardrail model সব ধরবে”Detector-এর FN আর FP দুটোই আছেনিজের লেখায় precision/recall; output-এ কোডের পাহারা (§৬)
পুরনো library API (সরানো argument)transformers আর serving engine দ্রুত বদলায়নিজের version-এর নথি পড়া
Retry-তে সীমা নেইব্যস্ত সময়ে retry-ই চাপ বাড়ায়সীমিত retry, backoff, budget (§৭)
Log-এ পুরো prompt আর উত্তরPII ফাঁসের সবচেয়ে সহজ পথআগে ঢাকা, তারপর log; রাখার মেয়াদ

নকশা চাওয়ার সময় সংখ্যা আর যাচাইয়ের পথও চেয়ে নিন। আসল গ্রাহকের লেখা বা অভ্যন্তরীণ নথি hosted assistant-এ দেবেন না; বানানো নমুনা দিন।

আমি একটি বাংলা e-commerce help chatbot-এর নকশা করছি (RAG, নিজের GPU-তে ছোট open model, পাশে hosted API fallback). ১. প্রয়োজনের একটি সারণি দাও: functional, latency (TTFT p95), availability, গোপনীয়তা; প্রতিটির পাশে কীভাবে মাপব। ২. দিনে ৬০,০০০ অনুরোধ, ব্যস্ততম ঘণ্টায় ১২%, প্রতি অনুরোধে ১,২৫০ input আর ১৫০ output token ধরে peak token/s হিসাব করো; GPU সংখ্যার জন্য কোন মাপ আমাকে নিজে নিতে হবে, তা বলো, নিজে throughput বানিয়ে দেবে না। ৩. Semantic cache-এর threshold বাছা আর false hit মাপার set বানানোর পদ্ধতি লেখো। ৪. যে library API বা version নিশ্চিত নও, তা বলো; কোনো benchmark সংখ্যা বানিয়ে লিখবে না।

GenAI নকশার চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।

প্র ০১Chatbot ভুল নীতি বললে দায় কার — model-এর, না দোকানের?

একটি পরিচিত ঘটনা প্রশ্নটিকে স্পষ্ট করে। ২০২৪ সালের ফেব্রুয়ারিতে কানাডার British Columbia-র Civil Resolution Tribunal “Moffatt v. Air Canada” মামলায় রায় দেয়। প্রকাশিত রায় অনুযায়ী, এয়ারলাইনের ওয়েবসাইটের chatbot একজন যাত্রীকে শোকজনিত ছাড়ের ভাড়া নিয়ে এমন নিয়ম বলেছিল, যা কোম্পানির নিজের নীতির পাতার সঙ্গে মেলেনি। কোম্পানির যুক্তি ছিল, chatbot-এর কথার দায় আলাদা; tribunal তা মানেনি, আর বলেছে ওয়েবসাইটের সব তথ্যের দায় কোম্পানির, chatbot-এরও। ক্ষতিপূরণের আদেশ হয়েছিল। খুঁটিনাটি রায়ের মূল লেখায় পড়ে নিন; শিক্ষাটি সরল।

গ্রাহকের চোখে chatbot দোকানের প্রতিনিধি। “৬০ টাকা” বললে গ্রাহক ৬০ টাকাই আশা করবেন, আর বিরোধ হলে সেই কথোপকথনই প্রমাণ। তাই নকশার প্রতিটি অংশ এই দায় মাথায় রেখে: উত্তর শুধু উৎস থেকে, উৎসের নম্বরসহ (পর্ব ৬৯); উৎসে না থাকলে “নেই” আর মানুষের পথ; উত্তরের সংখ্যা উৎসে আছে কি না কোডে যাচাই; semantic cache-এর ভুল hit যেন “বড় পণ্যের চার্জ ৬০ টাকা” না বলে (§৫); আর trace-এ কোন নথি থেকে উত্তর এল, তা রাখা, যাতে পরে দেখা যায় ভুলটি নথির, না model-এর।

নীতি বদলালে index, cache আর eval set তিনটিই হালনাগাদ করতে হয়; একটিও পুরনো থাকলে chatbot পুরনো নিয়ম বলবে। বাংলাদেশে এমন দায় নিয়ে আইনজ্ঞের পরামর্শ নিন; নকশার নিরাপদ অনুমান একটাই: chatbot যা বলে, দোকান তা বলেছে।

মূল উপলব্ধি: Chatbot আলাদা কেউ নয়, দোকানের মুখ। তাই উৎস, যাচাই, “নেই” বলার পথ আর trace — এগুলো সাজসজ্জা নয়, দায় সামলানোর ব্যবস্থা।

প্র ০২Semantic cache কি আদৌ রাখা উচিত, নাকি exact আর prefix cache-ই যথেষ্ট?

লাভ আর ঝুঁকি দুটোই মাপা দরকার, আর এই পর্বের মাপে ঝুঁকিটি বড় দেখা গেছে। e5-small দিয়ে ৫৯টি প্রশ্নে প্রতিটি threshold-এ hit-এর প্রায় ৩০–৪০% ভুল; “উত্তর আলাদা” প্রশ্নের cosine Banglish-এ বলা একই প্রশ্নের চেয়ে বেশি। মানে শুধু embedding-এর দূরত্ব দেখে “একই প্রশ্ন কি না” বিচার করা যায়নি। Embedding model বাক্যের সামগ্রিক অর্থ ধরতে শেখানো, “শুক্রবার” বনাম “শনিবার”-এর মতো ছোট কিন্তু উত্তর-বদলানো পার্থক্য ধরতে নয়।

Help desk-এ একই প্রশ্ন বারবার আসে, কিন্তু কতটা হুবহু আর কতটা অন্যভাবে বলা, তা নিজের log থেকে (ঢেকে নিয়ে) মাপতে হয়। Prefix cache (পর্ব ৬৫) কোনো ঝুঁকি ছাড়াই স্থির system prompt আর নীতির অংশের হিসাব বাঁচায়, কারণ উত্তর তখনো model-ই লেখে। Exact cache-এ ভুল প্রায় শূন্য, কিন্তু আমাদের set-এ hit মাত্র ২টি।

মাঝামাঝি পথ intent-ভিত্তিক cache: একটি classifier প্রশ্নকে নির্দিষ্ট FAQ-id দেয়, আর সেই id-এর যাচাই করা উত্তর ফেরত যায়। এখানে ভুলের হার classifier-এর ভুলের হার, যা সাধারণ classification metric দিয়ে মাপা আর উন্নত করা যায়, আর উত্তরগুলো মানুষ আগে থেকে পড়ে অনুমোদন করতে পারেন। আরেক পথ cosine-এর সঙ্গে মূল শব্দ মেলানো, যা আমাদের run-এ ভুল hit অনেক কমিয়েছে, তবে নিয়মটি একই ডেটা দেখে লেখা বলে আলাদা set-এ যাচাই বাকি। সিদ্ধান্তের মানদণ্ড: একটি ভুল উত্তরের দাম কত (ভুল চার্জ, বিরোধ, আস্থা) বনাম একটি LLM-ডাকের দাম কত (কয়েক সেকেন্ড আর হাজারখানেক token). গ্রাহকসেবায় প্রথমটি সাধারণত বড়।

মূল উপলব্ধি: Prefix cache প্রায় সবসময় লাভ; semantic cache শুধু তখন, যখন ভুল hit মাপা হয়েছে আর তার দাম জেনে নেওয়া হয়েছে।

প্র ০৩ধারণক্ষমতার হিসাব অনুরোধে না করে token-এ করতে হয় কেন, আর বাংলায় এর বাড়তি দিক কী?

সাধারণ web সেবায় একটি অনুরোধের খরচ মোটামুটি স্থির, তাই requests per second দিয়েই সার্ভার গোনা যায় (সিস্টেম ডিজাইন কোর্সের পর্ব ৩)। LLM-এ একটি অনুরোধের কাজ নির্ভর করে তার token-এর ওপর, আর তাও দুই ভাগে। Input token-এর হিসাব (prefill) একসঙ্গে, সমান্তরালে হয়; output token একটি একটি করে (decode), প্রতিটির জন্য পুরো model একবার চলে। তাই ১,২০০ token-এর RAG prompt আর ১৫০ token-এর উত্তরের অনুরোধ, আর ২০০ token-এর প্রশ্ন আর ৫০০ token-এর লম্বা উত্তরের অনুরোধ — দুটি “একটি অনুরোধ” হলেও GPU-র কাছে সম্পূর্ণ আলাদা কাজ। KV cache-এর মেমরিও token-এর সংখ্যায় বাড়ে, তাই একসঙ্গে কয়টি অনুরোধ চলতে পারবে, তাও token ঠিক করে (পর্ব ৮৩ · LLM serving).

বাংলায় বাড়তি দিক হলো token-এর হার। পর্ব ৬৫-এর মাপে একই অর্থের বাংলা লেখা tokenizer-ভেদে ইংরেজির ১.১৩ থেকে ৬.৪৯ গুণ token নেয়। ইংরেজি chatbot-এর জন্য বানানো হিসাব বাংলায় কপি করলে GPU কম পড়বে, latency বাড়বে, আর hosted API-র বিলও বাড়বে। Banglish-এর হার আবার আলাদা। তাই হিসাবের token সংখ্যা আসবে নিজের prompt-কে নিজের model-এর tokenizer দিয়ে গুনে, অনুমানে নয়।

শেষে throughput-এর মাপও token-এ এবং নির্দিষ্ট অবস্থায়: কোন model, কোন precision, কত batch, কত লম্বা prompt, কোন serving engine. এই পর্বের মাপ HF transformers-এর সাধারণ generate দিয়ে, যা serving engine নয়; vLLM-এর মতো engine continuous batching আর KV cache-এর ভালো ব্যবস্থাপনায় একই GPU-তে বেশি throughput দিতে পারে বলে তাদের প্রবন্ধ আর নথি রিপোর্ট করে, কিন্তু নিজের prompt-এ সেটিও মেপে নিতে হয়।

মূল উপলব্ধি: LLM-এর খরচের একক token, আর input ও output-এর কাজ আলাদা। হিসাবের প্রতিটি সংখ্যা নিজের prompt আর নিজের যন্ত্রে মাপা হওয়া চাই।

প্র ০৪Retry ভালো জিনিস, তবু ব্যস্ত সময়ে retry কেন বিপদ ডেকে আনে?

একটি অনুরোধ ক্ষণিকের নেটওয়ার্ক-ত্রুটিতে ব্যর্থ হলে আবার পাঠালে সাধারণত কাজ হয়; এই ক্ষেত্রে retry সত্যিই উপকারী। কিন্তু ব্যর্থতার কারণ যদি হয় চাপ — GPU-র সব slot ভরা, queue লম্বা — তাহলে প্রতিটি retry আরেকটি অনুরোধ যোগ করে, চাপ আরও বাড়ে, আরও অনুরোধ timeout হয়, আরও retry আসে। সিস্টেম ডিজাইনে এর নাম retry storm বা retry amplification (সিস্টেম ডিজাইন পর্ব ৪৮)। কয়েক স্তরে retry থাকলে (অ্যাপ, gateway, RAG service, LLM client) গুণফল আরও ভয়ংকর: তিন স্তরের প্রতিটিতে ৩ বার চেষ্টা মানে নিচের স্তরে ২৭ গুণ পর্যন্ত।

LLM-এ এর দাম আরও বেশি, কারণ একটি অনুরোধ কয়েক সেকেন্ড GPU ধরে রাখে। Timeout হওয়া অনুরোধের কাজ অনেক সময় বাতিলও হয় না: client চলে গেছে, কিন্তু GPU তখনো সেই উত্তর লিখছে। তাই নকশার নিয়ম: retry শুধু এক স্তরে; সর্বোচ্চ ১–২ বার; প্রতিবার অপেক্ষা বাড়িয়ে আর একটু এলোমেলো করে (exponential backoff with jitter); মোট retry-এর একটি budget (যেমন মোট অনুরোধের ১০%); client চলে গেলে সার্ভারে generation থামানো; আর বড় model ব্যর্থ হতে থাকলে circuit breaker খুলে সরাসরি ছোট model বা “মানুষের কাছে পাঠাই”।

যে tool অর্ডার বাতিল করে, তা দুবার চললে সমস্যা; এমন কাজে idempotency key লাগে, যাতে একই অনুরোধ দুবার এলেও কাজ একবারই হয়। §৭-এর simulator-এ fallback আর rate limiter আছে, retry নেই; retry যোগ করলে ব্যস্ত সময়ে কী হয়, তা কোডে যোগ করে দেখতে পারেন।

মূল উপলব্ধি: Retry ক্ষণিকের ত্রুটির ওষুধ, চাপের নয়। সীমা, backoff, budget আর fallback ছাড়া retry ব্যস্ত সময়ে আগুনে ঘি।

প্র ০৫গ্রাহকের কথোপকথন hosted API-তে পাঠানোর আগে কী কী ভাববেন?

সংবাদমাধ্যমের খবর অনুযায়ী, ২০২৩ সালে Samsung-এর কয়েকজন কর্মী গোপন source code আর মিটিংয়ের নোট ChatGPT-তে বসিয়েছিলেন, আর এরপর কোম্পানিটি কর্মীদের generative AI ব্যবহারে সীমা টানে। ঘটনার খুঁটিনাটি নিয়ে খবরভেদে পার্থক্য আছে, কিন্তু শিক্ষাটি পরিষ্কার: বাইরের সেবায় পাঠানো লেখা আপনার নিয়ন্ত্রণের বাইরে চলে যায়, আর প্রদাতা তা কতদিন রাখবেন বা কীভাবে ব্যবহার করবেন, তা তাঁদের শর্তে।

বাজারঘরের ক্ষেত্রে প্রশ্নগুলো এমন। এক, পাঠানোর দরকারটা কতটুকু: নীতির প্রশ্নের জন্য গ্রাহকের ফোন নম্বর বা অর্ডার id লাগে না, তাই input guard আগেই ঢাকবে (data minimisation). দুই, প্রদাতার শর্ত: অনুরোধের লেখা কতদিন রাখা হয়, training-এ যায় কি না, কোন দেশে প্রক্রিয়া হয়; এসব প্রদাতার নথি আর চুক্তিতে লেখা, আর বদলায়, তাই তারিখসহ পড়ে সংরক্ষণ করুন। তিন, গ্রাহককে জানানো: chatbot যে স্বয়ংক্রিয় ব্যবস্থা, আর কথোপকথন কীভাবে ব্যবহার হয়, তা গোপনীয়তার নীতিতে লেখা থাকা উচিত। চার, নিজের দিকের রাখা: log-এ শুধু ঢাকা লেখা, নির্দিষ্ট মেয়াদের পর মোছা, আর কে log দেখতে পারেন তার সীমা।

আইনের দিকে, বাংলাদেশে ২০২৬ সালে ব্যক্তিগত উপাত্ত সুরক্ষা আইন ২০২৬ (৬৩ নং) আর জাতীয় উপাত্ত ব্যবস্থাপনা আইন ২০২৬ (৮০ নং) হয়েছে। ব্যক্তিগত তথ্য দেশের বাইরের সেবায় পাঠানো বা প্রক্রিয়া করার ক্ষেত্রে এগুলোর কোন বিধান প্রযোজ্য, তা অনুমান করে বলা ঠিক নয়; আইনজ্ঞের পরামর্শ নিন। নকশার দিক থেকে নিরাপদ নীতি: যা পাঠাতে হয় না, তা পাঠাবেন না; যা পাঠাতেই হয়, তা ঢেকে; আর নিজের open model দিয়ে কোন অংশ ঘরে রাখা যায়, তা বিবেচনা করুন (§৯)।

মূল উপলব্ধি: Hosted API-তে পাঠানো মানে ডেটা হাতছাড়া করা। কম পাঠানো, ঢেকে পাঠানো আর শর্ত পড়ে রাখা — তিনটিই নকশার কাজ।

অনুশীলন

খাতা-কলমে করুন, তারপর উত্তর মেলান। বিশ্ববিদ্যালয়, চাকরি বা ইন্টারভিউর পরীক্ষায় এই ধরনের হিসাব কাজে লাগে।

  1. Token-এ ধারণক্ষমতা: একটি সহায়কে দিনে ১,২০,০০০ অনুরোধ আসে, ব্যস্ততম ঘণ্টায় তার ১০%. প্রতি অনুরোধে input ১,০০০ আর output ১২০ token. (ক) ব্যস্ততম সময়ে প্রতি সেকেন্ডে কতগুলো অনুরোধ, আর input ও output token/s কত? (খ) একটি replica মাপে প্রতি সেকেন্ডে ৩.০টি অনুরোধ শেষ করে (এই দৈর্ঘ্যে)। ৬০% ব্যবহারের লক্ষ্যে কয়টি replica? (গ) Cache-এর ঠিক hit ২০% হলে?

    (ক) ১,২০,০০০ × ০.১০ ÷ ৩,৬০০ = ৩.৩৩ অনুরোধ/সেকেন্ড। Input ৩.৩৩ × ১,০০০ ≈ ৩,৩৩৩ token/s, output ৩.৩৩ × ১২০ = ৪০০ token/s.

    (খ) ৬০% লক্ষ্যে একটি replica নেবে ৩.০ × ০.৬ = ১.৮ অনুরোধ/সেকেন্ড। ৩.৩৩ ÷ ১.৮ = ১.৮৫, ওপরের দিকে ২টি replica; একটি নষ্ট হলেও চলার জন্য (N+1) ৩টি।

    (গ) LLM-এ যায় ৩.৩৩ × ০.৮ = ২.৬৭; ২.৬৭ ÷ ১.৮ = ১.৪৮, তবু ২টি: replica পূর্ণসংখ্যা, cache শুধু headroom বাড়াল।

  2. আউটপুট অনুমান করুন: কোড না চালিয়ে আউটপুট লিখুন। Token bucket-এ সর্বোচ্চ ৩টি token, প্রতি ১০ সেকেন্ডে ১টি যোগ হয়।
    cap, rate, tok, last, out = 3.0, 0.1, 3.0, 0.0, []
    for t in [0, 1, 2, 3, 4, 25, 26, 27]:
        tok = min(cap, tok + (t - last) * rate); last = t
        if tok >= 1: tok -= 1; out.append("ok")
        else: out.append("429")
    print(out)
    ['ok', 'ok', 'ok', '429', '429', 'ok', 'ok', '429']

    প্রথম তিনটি burst-এ ঠিক (প্রতিবার ০.১ করে যোগ হয়: ৩ → ২ → ১.১ → ০.২)। t = ৩-এ ০.৩, t = ৪-এ ০.৪: দুটিই ১-এর নিচে, তাই 429. t = ২৫-এ ০.৪ + ২.১ = ২.৫ → ১.৫ বাকি; t = ২৬-এ ১.৬ → ০.৬; t = ২৭-এ ০.৭, তাই 429. গড় হার প্রতি ১০ সেকেন্ডে একটি, কিন্তু ৩টি পর্যন্ত একসঙ্গে চলে।

  3. Semantic cache-এর হিসাব: একটি দিনে ১০,০০০ প্রশ্ন। একটি threshold-এ ২,৫০০টি cache থেকে উত্তর পায়, যার ৯০০টি ভুল hit (অন্য প্রশ্নের উত্তর)। প্রতিটি LLM-ডাকে ১,৩৫০ token. (ক) Hit rate আর ভুল hit-এর অংশ কত? (খ) দিনে কত token বাঁচল? (গ) প্রতি ভুল উত্তরে গড়ে একটি মানুষের টিকিট আর ১০% ক্ষেত্রে বিরোধ হলে, সিদ্ধান্ত নিতে আর কী জানতে হবে?

    (ক) Hit rate ২,৫০০ ÷ ১০,০০০ = ২৫%; ভুল hit-এর অংশ ৯০০ ÷ ২,৫০০ = ৩৬%, আর সব প্রশ্নের ৯%.

    (খ) ২,৫০০ × ১,৩৫০ = ৩৩,৭৫,০০০ token (ঠিক hit-এর ১,৬০০টি ধরলে ২১,৬০,০০০ token “ভালো” সাশ্রয়)।

    (গ) ৯০০টি বাড়তি টিকিট আর প্রায় ৯০টি বিরোধ। তুলনা করতে হবে একটি টিকিট আর বিরোধের খরচ (সময়, ক্ষতিপূরণ, আস্থা) বনাম ৩৩.৭৫ লাখ token-এর GPU-সময়। গ্রাহকসেবায় এই threshold সাধারণত গ্রহণযোগ্য নয়।

  4. নকশা সমালোচনা করুন: একটি দলের পরিকল্পনা: “সব প্রশ্ন সবচেয়ে বড় hosted model-এ; গ্রাহকের পুরো বার্তা (ফোন নম্বরসহ) প্রতিটি অনুরোধে পাঠাব; semantic cache τ = ০.৮৫, অর্ডারের প্রশ্নসহ সবকিছুতে; প্রতিটি স্তরে ৩ বার retry; log-এ পুরো prompt আর উত্তর চিরকাল; নতুন prompt সরাসরি production-এ, কারণ ‘কয়েকটি প্রশ্নে ভালো লেগেছে’।” অন্তত ছয়টি সমস্যা আর সমাধান বলুন।
    • সব বড় model-এ: খরচ আর latency বেশি। সমাধান: router, মান মেপে।
    • PII পাঠানো: data minimisation ভাঙে। সমাধান: input guard-এ ঢাকা, precision/recall জেনে।
    • τ = ০.৮৫ সবকিছুতে: আমাদের মাপে এই সীমায় অনেক ভুল hit; অর্ডারের উত্তর অন্যের কাছে যাওয়া তথ্য ফাঁস। সমাধান: ব্যক্তিগত প্রশ্ন cache-এর বাইরে, মূল শব্দ মেলানো বা intent cache, ভুল hit মেপে τ.
    • প্রতি স্তরে ৩ বার retry: ব্যস্ত সময়ে ২৭ গুণ পর্যন্ত চাপ। সমাধান: এক স্তরে ১–২ বার, backoff, budget, circuit breaker আর fallback.
    • চিরকালের পুরো log: ফাঁসের ঝুঁকি। সমাধান: ঢাকা লেখা, নির্দিষ্ট মেয়াদে মোছা।
    • কয়েকটি প্রশ্নে ভালো লাগা: regression ধরা পড়ে না। সমাধান: CI-তে স্থির eval set (উত্তরহীন আর নিরাপত্তার প্রশ্নসহ), তারপর canary release.

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি GenAI help chatbot-এ নীতির প্রশ্নের অনুরোধ যে ক্রমে অংশগুলো পেরোয়, তা সাজান (▲▼ চেপে সরান)

  1. Gateway-তে লগইন যাচাই আর rate limit
  2. Input guard-এ ফোন, email আর অর্ডার id ঢাকা
  3. Cache-এ আগের উত্তর খোঁজা
  4. Router-এ model আর পথ বাছা
  5. RAG-এ নথি খুঁজে LLM দিয়ে উত্তর লেখা
  6. Output guard-এ citation আর সংখ্যা যাচাই, তারপর উত্তর পাঠানো

একটি সহায়কে দিনে ৭২,০০০ অনুরোধ, ব্যস্ততম ঘণ্টায় তার ১৫%. প্রতি অনুরোধে output ২০০ token. ব্যস্ততম সময়ে output token/s কত?

  • প্রায় ১৬৭
  • ৬০০
  • ৩৬,০০০
  • ১০,৮০০

Semantic cache-এ cosine threshold τ বাড়ালে সাধারণত কী ঘটে?

  • Hit rate বাড়ে আর ভুল hit কমে
  • Hit rate আর ভুল hit দুটোই বাড়ে
  • Hit rate কমে, ভুল hit-এর সংখ্যা সাধারণত কমে, তবে শূন্য হবে এমন নিশ্চয়তা নেই
  • কিছুই বদলায় না, কারণ embedding একই থাকে

“আমার অর্ডার BG-2026-004817 কোথায়?” ধরনের প্রশ্নে ভাগ করা semantic cache নিয়ে সঠিক নীতি কোনটি?

  • Cache পুরো বাদ; মালিকানা যাচাই করে অর্ডার-tool-এর পথ
  • অর্ডার id ঢেকে cache-এ রাখা
  • শুধু τ = ০.৯৯ দিয়ে cache রাখা
  • Exact cache-এ রাখা, semantic-এ নয়

একটি PII detector ২০টি আসল ফোন নম্বরের ১৬টি ঢেকেছে, আর ৪টি এমন সংখ্যাও ঢেকেছে যা ফোন নয়। Precision আর recall কত?

  • Precision ০.৮০, recall ১.০০
  • Precision ০.৬০, recall ০.৮০
  • Precision ০.৯০, recall ০.৮০
  • Precision ০.৮০, recall ০.৮০

বড় model-এর সার্ভার ব্যস্ত সময়ে বারবার timeout দিচ্ছে। কোন পদক্ষেপ পরিস্থিতি সবচেয়ে বেশি খারাপ করতে পারে?

  • Circuit breaker খুলে কিছুক্ষণ সরাসরি ছোট model-এ পাঠানো
  • অ্যাপ, gateway আর LLM client — প্রতিটি স্তরে ৩ বার করে retry
  • কিছু অনুরোধ দ্রুত “পরে চেষ্টা করুন” বলে ফিরিয়ে দেওয়া
  • “আপনার প্রশ্নটি একজন প্রতিনিধির কাছে পাঠানো হলো” বলে টিকিট খোলা

একটি LLM সেবার trace-এ নিচের কোনটি রাখা সবচেয়ে কম যুক্তিসঙ্গত?

  • Input আর output token-এর সংখ্যা
  • খুঁজে আনা নথির id আর score
  • গ্রাহকের ঢাকা ছাড়া পুরো বার্তা, মেয়াদ ছাড়া
  • উত্তরটি কোন পথে গেল (cache, বড় model, fallback)

Hosted API বনাম নিজের GPU-তে open model — নিচের কোন বক্তব্যটি সঠিক?

  • Hosted API-তে খরচ ব্যবহারের token-এ, নিজের GPU-তে ব্যবহার কম হলেও GPU-র খরচ চলে
  • Hosted API ব্যবহার করলে নিজের eval set লাগে না
  • নিজের model চালালে নিরাপত্তার কোনো দায় থাকে না
  • Open model সবসময় hosted model-এর সমান মান দেয়

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. প্রয়োজন মাপসহ: functional, latency (TTFT p95), availability, নিরাপত্তা, গোপনীয়তা আর খরচ, প্রতিটির পাশে একটি মাপ।
  2. ধারণক্ষমতা token-এ: অনুরোধ/দিন × ব্যস্ত অংশ ÷ ৩,৬০০ → peak RPS → × token → token/s; replica = ⌈peak ÷ (মাপা ক্ষমতা × ব্যবহারের লক্ষ্য)⌉, সঙ্গে N+1.
  3. Cache তিন রকম: prefix cache ঝুঁকিহীন; exact cache নিরাপদ কিন্তু কম hit; semantic cache-এ ভুল hit মাপুন, ব্যক্তিগত প্রশ্ন বাইরে রাখুন।
  4. Guardrail স্তরে স্তরে: আগে PII ঢাকা, retrieval-এ filter, tool-এ মালিকানা, শেষে output-এ citation আর URL যাচাই; প্রতিটির precision/recall জানুন।
  5. ব্যর্থতার নকশা: timeout, এক স্তরে সীমিত retry, circuit breaker, fallback আর মানুষ; প্রতিটি পথের হার আর মান মাপুন, eval CI-তে চালান।

পরের পর্বে: পর্ব ৮৬ · ক্যাপস্টোন — বাজারঘরের বাংলা সহায়ক: RAG আর fine-tuned classifier. এই পর্বের নকশা আপনি নিজে ধাপে ধাপে বানাবেন: router-এর classifier, RAG, guardrail, eval harness আর latency-র রিপোর্ট।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান