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

Token, খরচ আর latency — caching, model বাছাই আর সংক্ষিপ্ত prompt

Tokens, Cost and Latency: Caching, Model Choice and Shorter Prompts

Taught in Bangla, this lesson treats the token as the unit of cost, time and context space. It counts Bangla versus English tokens across several real tokenizers, builds a monthly cost formula with caching and batch savings in symbols rather than real prices, explains why prompt caching works only on prefixes, and measures TTFT, decode speed and a local prefix-cache speedup on the CPU with small open models, then checks how much accuracy a shorter prompt loses, with in-page Python.

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

  • বাংলা আর ইংরেজির token কয়েকটি আসল tokenizer-এ গোনা, আর token fertility-র মানে
  • প্রতি ১০ লাখ token-এর দাম থেকে মাসের খরচ, caching আর batch-এর সাশ্রয় হাতে হিসাব
  • Prompt caching কেন শুধু prefix-এ কাজ করে, আর ছয়টি API কীভাবে তা নথিতে লেখে
  • TTFT আর tokens per second মাপা, আর নিজের যন্ত্রে prefix cache-এর লাভ
  • Model বাছাই, ছোট prompt আর context বাজেট, নির্ভুলতা মেপে

এই পর্বের desktop run: Python 3.12, torch 2.15.0 (CPU), transformers 5.22.2, tiktoken 0.14.0, লেখার সময়ে। Model Qwen/Qwen2.5-0.5B-Instruct (Apache 2.0) আর HuggingFaceTB/SmolLM2-360M-Instruct (Apache 2.0), দুটিই token ছাড়া নামানো যায়; CPU-তে ৩টি thread. সব বার্তা, নীতি আর পণ্যের লেখা এই পর্বের জন্য হাতে লেখা কাল্পনিক বাজারঘর ডেটা। সব সময়ের সংখ্যা একটি স্থানীয় যন্ত্রের একটি run-এর; hosted API-র সময় নেটওয়ার্ক, সারি আর server-এর ওপর নির্ভর করে, আর বড় পণ্য অন্যভাবে আচরণ করতে পারে। কোনো আসল দাম এই পাতায় নেই; হিসাব প্রতীকে আর আপনার বসানো দামে। Token আর LLM-এর মূল ধারণা AI-এর ভিত্তি কোর্সের পর্ব ৭০-এ।

১ · Token — খরচ, সময় আর জায়গার একক, আর বাংলার বাড়তি দাম

কুরিয়ারের বিলের কথা ভাবুন। কুরিয়ার পার্সেল গোনে না, ওজন গোনে। একই শাড়ি পাতলা কাগজে মুড়লে কম দাম, ভারী বাক্সে বেশি। LLM-এর API-ও কথা গোনে না, গোনে token. একই কথা কোন ভাষায় আর কোন tokenizer-এ লেখা, তার ওপর নির্ভর করে “ওজন” কত দাঁড়াবে।

Token দিয়ে তিনটি জিনিস মাপা হয়। খরচ: hosted API input আর output token-এর দাম আলাদা ধরে। সময়: প্রতিটি output token-এর জন্য model-এর একটি পূর্ণ pass লাগে (পর্ব ৪৯)। জায়গা: context window token-এ মাপা (পর্ব ৫২)। তাই একই কথা যত বেশি token-এ ভাঙে, তত বেশি টাকা, তত বেশি সময়, আর তত কম জায়গা বাকি থাকে।

পর্ব ৪০-এ দেখেছেন, BPE-ধরনের tokenizer যে ভাষার ডেটা বেশি দেখে, সে ভাষার শব্দ বড় টুকরোয় রাখে। বাংলার প্রতিটি অক্ষর UTF-8-এ ৩ byte, আর অনেক tokenizer-এর vocabulary-তে বাংলার টুকরো কম। এবার একই অর্থের তিনটি বাজারঘর লেখা (গ্রাহকের বার্তা, পণ্যের বর্ণনা, ফেরতের নীতি; হাতে লেখা কাল্পনিক) বাংলা আর ইংরেজিতে, সাতটি খোলা tokenizer আর tiktoken-এর দুটি public encoding-এ গোনা হলো।

~/bazarghar-dl$ uv run python tokcount.py      # সত্যিকারের run; warning-এর লাইন বাদ
tiktoken:cl100k_base                bn= 577 en= 128 ratio=4.51 banglish=50
tiktoken:o200k_base                 bn= 192 en= 126 ratio=1.52 banglish=42
Qwen/Qwen2.5-0.5B-Instruct          bn= 514 en= 133 ratio=3.86 banglish=53
Qwen/Qwen3-0.6B                     bn= 514 en= 133 ratio=3.86 banglish=53
HuggingFaceTB/SmolLM2-360M-Instruct bn= 882 en= 136 ratio=6.49 banglish=55
deepseek-ai/DeepSeek-V3             bn= 226 en= 129 ratio=1.75 banglish=48
bigscience/bloom-560m               bn= 142 en= 126 ratio=1.13 banglish=43
FacebookAI/xlm-roberta-base         bn= 173 en= 135 ratio=1.28 banglish=43
csebuetnlp/banglabert               bn= 102 en= 197 ratio=0.52 banglish=55

Python 3.12, transformers 5.22.2, tiktoken 0.14.0, লেখার সময়ে। শুধু tokenizer-এর ফাইল নামানো হয়েছে, model নয়; special token বাদ দিয়ে গোনা। নিচের কোডে এই সংখ্যাগুলো ডেটা হিসেবে বসানো, আর প্রতি শব্দে token (token fertility) আর ৮,০০০ token-এর context-এ কত বাংলা শব্দ আঁটে, তা হিসাব করা।

import pandas as pd
# সত্যিকারের run: একই অর্থের তিনটি বাজারঘর লেখা (বার্তা, পণ্যের বর্ণনা, নীতি)
# বাংলা: ৭৭ শব্দ, ৪৮২ অক্ষর; ইংরেজি: ৯৬ শব্দ, ৫৭০ অক্ষর
COUNTS = {  # tokenizer: (বাংলা token, ইংরেজি token, Banglish বার্তা, বাংলা বার্তা)
    "tiktoken cl100k_base": (577, 128, 50, 169),
    "tiktoken o200k_base":  (192, 126, 42, 60),
    "Qwen2.5 (= Qwen3)":    (514, 133, 53, 149),
    "SmolLM2":              (882, 136, 55, 251),
    "DeepSeek-V3":          (226, 129, 48, 69),
    "BLOOM":                (142, 126, 43, 44),
    "XLM-R":                (173, 135, 43, 50),
    "BanglaBERT":           (102, 197, 55, 29),
}
BN_WORDS, EN_WORDS = 77, 96
df = pd.DataFrame(COUNTS, index=["bn", "en", "banglish", "bn_msg"]).T
df["bn/en"] = (df.bn / df.en).round(2)
df["tok/শব্দ bn"] = (df.bn / BN_WORDS).round(2)
# একটি ৮,০০০ token-এর context-এ কত বাংলা শব্দ আঁটে?
df["8k-তে শব্দ"] = (8000 / df["tok/শব্দ bn"]).round(-1).astype(int)
cols = ["bn", "en", "bn/en", "tok/শব্দ bn", "8k-তে শব্দ"]
print(df.sort_values("bn/en", ascending=False)[cols].to_string())
print("ইংরেজি token/শব্দ:", (df.en / EN_WORDS).round(2).min(), "থেকে",
      (df.en / EN_WORDS).round(2).max())
m = df[["bn_msg", "banglish"]].loc[["Qwen2.5 (= Qwen3)", "tiktoken o200k_base"]]
print("শুধু বার্তাটি, বাংলা বনাম Banglish:")
print(m.to_string())

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

                       bn   en  bn/en  tok/শব্দ bn  8k-তে শব্দ
SmolLM2               882  136   6.49        11.45         700
tiktoken cl100k_base  577  128   4.51         7.49        1070
Qwen2.5 (= Qwen3)     514  133   3.86         6.68        1200
DeepSeek-V3           226  129   1.75         2.94        2720
tiktoken o200k_base   192  126   1.52         2.49        3210
XLM-R                 173  135   1.28         2.25        3560
BLOOM                 142  126   1.13         1.84        4350
BanglaBERT            102  197   0.52         1.32        6060
ইংরেজি token/শব্দ: 1.31 থেকে 2.05
শুধু বার্তাটি, বাংলা বনাম Banglish:
                     bn_msg  banglish
Qwen2.5 (= Qwen3)       149        53
tiktoken o200k_base      60        42

ইংরেজিতে সবাই প্রায় সমান: প্রতি শব্দে ১.৩১ থেকে ১.৪২ token, শুধু BanglaBERT ২.০৫ (বাংলার জন্য বানানো, ইংরেজির টুকরো কম)। বাংলায় ফারাক বিশাল: একই কথা SmolLM2-এ ৮৮২ token, BLOOM-এ ১৪২, ছয় গুণের বেশি। Qwen2.5-এ বাংলা ইংরেজির ৩.৮৬ গুণ; Qwen3-এর tokenizer এই লেখাগুলোতে হুবহু একই সংখ্যা দিয়েছে। tiktoken-এর পুরনো cl100k_base-এ ৪.৫১ গুণ, নতুন o200k_base-এ ১.৫২ গুণ: বড় vocabulary-তে বাংলার টুকরো বেশি জায়গা পেয়েছে।

তিনটি সতর্কতা। এক, মাত্র তিনটি ছোট লেখা; লেখার ধরন বদলালে অনুপাত বদলাবে। দুই, কোন hosted model কোন tokenizer ব্যবহার করে, তা সব কোম্পানি প্রকাশ করে না; নিশ্চিত গোনার জন্য কোম্পানির নিজের token গোনার উপায় (§৬-এর সারণি) ব্যবহার করুন। তিন, Banglish-এ বার্তাটি Qwen-এ ৫৩ token, বাংলা হরফে ১৪৯; সস্তা, কিন্তু ছোট model Banglish কতটা ঠিক বোঝে, তা আলাদা প্রশ্ন (পর্ব ৫৮-এ বাংলা আর ইংরেজির ফারাক মাপা হয়েছে)।

কোম্পানিগুলোর নিজের নথি “প্রতি token-এ কত অক্ষর” নিয়ে কী বলে, লেখার সময়ে (নভেম্বর ২০২৬): Google-এর Gemini নথি ইংরেজিতে প্রায় ৪ অক্ষরে এক token; Anthropic-এর শব্দকোষ পুরনো model-এ প্রায় ৩.৫ ইংরেজি অক্ষর, আর নতুন কিছু model একই লেখায় প্রায় ৩০% বেশি token দেয় বলে token গোনার নথিতে লেখা; DeepSeek-এর নথি ইংরেজির এক অক্ষরে প্রায় ০.৩ token, চীনা এক অক্ষরে প্রায় ০.৬; Kimi-র নথি ৩–৪ ইংরেজি অক্ষরে এক token. OpenAI-এর token গোনার নথি উল্টো সতর্ক করে, “অক্ষর ÷ ৪” ধরনের আন্দাজ ভুল। বাংলার কথা কোনো নথিতে পাইনি। তাই নিয়ম একটাই: আন্দাজ নয়, নিজের লেখা নিজের tokenizer-এ গুনুন।

২ · খরচের হিসাব — দাম প্রতি ১০ লাখ token, input আর output আলাদা

API-র দাম সাধারণত লেখা থাকে প্রতি ১০ লাখ (1M) token-এ, input আর output আলাদা। একটি অনুরোধের খরচ:

C = (Tin × Pin + Tout × Pout) ÷ 106

এখানে Tin হলো input token (system prompt, কথোপকথনের ইতিহাস, গ্রাহকের বার্তা, সব মিলে), Tout output token, আর Pin, Pout প্রতি ১০ লাখ token-এর দাম। মাসের খরচ = অনুরোধের সংখ্যা R × গড় C. আসল দাম model-ভেদে আলাদা আর প্রায়ই বদলায়, তাই এই পাতায় টাকার কোনো অঙ্ক নেই, শুধু আপেক্ষিক খরচ-একক: Pin = ১.০০ আর Pout = ৪.০০ “একক”; output token-কে input-এর চার গুণ ভারী ধরা হলো। নিজের হিসাবে নিজের model-এর দামের পাতা থেকে সংখ্যা বসান।

প্রতিটি অনুরোধে পুরো ইতিহাস আবার পাঠাতে হয়, কারণ API নিজে থেকে আগের কথা মনে রাখে না (পর্ব ৫২)। তাই চার বার্তার কথোপকথনের চতুর্থ অনুরোধে আগের তিনটির সব token আবার input হিসেবে গোনা হয়। বাজারঘরের সহায়তা-bot-এর একটি মাস ধাপে ধাপে:

Pin = 1.00, Pout = 4.00 (খরচ-একক, প্রতি 1M token)
ধাপ
হিসাব
ফল
লক্ষ করুন
  1. অনুরোধ গোনা. ৩০ দিন × দিনে ১,২০০ কথোপকথন × কথোপকথনে গড়ে ৪টি অনুরোধ = ১,৪৪,০০০ অনুরোধ। কথোপকথন নয়, অনুরোধ গুনতে হয়, কারণ প্রতিটি অনুরোধ আলাদা বিল।
  2. Input token. প্রতিটি অনুরোধে ১,৫০০ token-এর system prompt (দোকানের নীতি) আর গড়ে ৪৬০ token ইতিহাস ও নতুন বার্তা: ১,৯৬০. মাসে ১,৪৪,০০০ × ১,৯৬০ = ২৮,২২,৪০,০০০, অর্থাৎ ২৮২.২৪M token.
  3. Output token. উত্তর গড়ে ১২০ token: ১,৪৪,০০০ × ১২০ = ১৭.২৮M. Input-এর তুলনায় খুব কম।
  4. Input-এর খরচ. ২৮২.২৪ × ১.০০ = ২৮২.২৪ একক। মোট token-এর ৯৪% input.
  5. Output-এর খরচ. ১৭.২৮ × ৪.০০ = ৬৯.১২ একক। Token-এর মাত্র ৬%, কিন্তু দাম চার গুণ বলে খরচে ভাগ বেশি।
  6. মোট. ২৮২.২৪ + ৬৯.১২ = ৩৫১.৩৬ একক। Output খরচের প্রায় ২০%; বাকি ৮০% input, যার বড় অংশ প্রতিবার একই system prompt. পরের অংশের caching ঠিক এখানেই কাজে লাগে।

এবার একই হিসাব কোডে, যাতে আপনি নিজের দাম আর সংখ্যা বসিয়ে দেখতে পারেন। P_IN, P_OUT, READ, WRITE আর BATCH_DISC বদলে আবার চালান; caching আর batch-এর গুণক §৩ আর §৬-এর সারণি থেকে নিজের API-র জন্য বেছে নিন।

# বাজারঘরের সহায়তা-bot-এর এক মাসের খরচ। দাম নিজের API-র নথি থেকে বসান।
P_IN, P_OUT = 1.00, 4.00   # খরচ-একক (টাকা নয়): প্রতি ১০ লাখ (1M) input/output token-এ
READ, WRITE = 0.10, 1.25   # cache-এ পড়া আর লেখা token-এর গুণক (নথিভেদে আলাদা)
BATCH_DISC = 0.50          # batch-এ ছাড় (নথিভেদে আলাদা, কোথাও নেই)

def month_cost(R, sys_tok, other_in, out_tok, hit=0.0, batch=0.0):
    """R অনুরোধ; sys_tok সবার এক অংশ, other_in বাকি input, out_tok উত্তর"""
    M = 1e6
    cached = R * sys_tok * hit                 # cache থেকে পড়া
    written = R * sys_tok * (1 - hit) if hit else 0
    plain = R * other_in + (0 if hit else R * sys_tok)
    c_in = (cached * READ + written * WRITE + plain) * P_IN / M
    c_out = R * out_tok * P_OUT / M
    f = 1 - batch * BATCH_DISC                 # batch-এ যাওয়া ভাগে ছাড়
    return (c_in + c_out) * f, c_in * f, c_out * f

R = 30 * 1200 * 4          # ৩০ দিন × ১,২০০ কথোপকথন × ৪টি অনুরোধ
base = dict(R=R, sys_tok=1500, other_in=460, out_tok=120)
print(f"অনুরোধ/মাস: {R:,}")
print(f"{'অবস্থা':28}{'input':>9}{'output':>9}{'মোট':>9}")
for name, kw in [("cache নেই", {}),
                 ("cache, hit 90%", {"hit": 0.9}),
                 ("cache 90% + 20% batch-এ", {"hit": 0.9, "batch": 0.2}),
                 ("system prompt ৬০০ token-এ ছাঁটা", {"sys_tok": 600}),
                 ("উত্তর ১২০ → ৬০ token", {"out_tok": 60})]:
    tot, ci, co = month_cost(**{**base, **kw})
    print(f"{name:28}{ci:9.2f}{co:9.2f}{tot:9.2f}")
# বাংলা বনাম ইংরেজি: একই কথা, token ৩.৮৬ গুণ (Qwen2.5-এর tokenizer, নিচে §১-এর সারণি)
tot_bn, *_ = month_cost(**base)
tot_en, *_ = month_cost(R=R, sys_tok=1500 / 3.86, other_in=460 / 3.86, out_tok=120 / 3.86)
print(f"একই bot ইংরেজিতে হলে: {tot_en:.2f}  (বাংলায় {tot_bn:.2f}, {tot_bn / tot_en:.2f} গুণ)")

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

অনুরোধ/মাস: 144,000
অবস্থা                          input   output      মোট
cache নেই                      282.24    69.12   351.36
cache, hit 90%                 112.68    69.12   181.80
cache 90% + 20% batch-এ        101.41    62.21   163.62
system prompt ৬০০ token-এ ছাঁটা   152.64    69.12   221.76
উত্তর ১২০ → ৬০ token           282.24    34.56   316.80
একই bot ইংরেজিতে হলে: 91.03  (বাংলায় 351.36, 3.86 গুণ)

প্রথম সারি stepper-এর ৩৫১.৩৬. বাকি সারিগুলো একেকটি কৌশল আলাদা করে: system prompt-এর ৯০% cache থেকে এলে মোট প্রায় অর্ধেক (১৮১.৮০); তার ওপর ২০% অনুরোধ (যেমন রাতের রিভিউ-সারাংশ, যেখানে তাড়া নেই) batch-এ পাঠালে ১৬৩.৬২; system prompt ১,৫০০ থেকে ৬০০ token-এ ছাঁটলে ২২১.৭৬; উত্তর অর্ধেক করলে ৩১৬.৮০. এই bot-এ বড় লাভ input-এ, কারণ input-ই বেশি। শেষ লাইনটি বাংলার দাম: Qwen2.5-এর tokenizer-এর অনুপাত ধরলে একই bot ইংরেজিতে চালাতে খরচ ৩.৮৬ ভাগের এক ভাগ। কম অনুপাতের tokenizer-ওয়ালা model-এ (§১-এর সারণি) এই ফারাক অনেক ছোট।

হিসাবটি সরল (ইতিহাস গড়ে ধরা, retry ধরা হয়নি)। আসল বিল মেলাতে প্রতিটি উত্তরের usage-এর token-সংখ্যা log করুন।

৩ · Prompt caching — একই শুরু একবার হিসাব, তারপর সস্তায় পড়া

পর্ব ৩৭-এ KV cache দেখেছেন: generation-এর সময় আগের token-গুলোর key আর value জমিয়ে রাখা হয়, যাতে আবার হিসাব না লাগে। Prompt caching একই ধারণা, কিন্তু অনুরোধের পর অনুরোধ জুড়ে। বাজারঘরের প্রতিটি অনুরোধ শুরু হয় হুবহু একই ১,৫০০ token-এর নীতি দিয়ে। প্রথম অনুরোধে সেই অংশের হিসাব জমা থাকলে পরের অনুরোধ শুধু নতুন অংশটুকু হিসাব করে। একে বলে prefix caching: মিল থাকতে হবে একদম শুরু থেকে, token ধরে। নীতির মাঝখানে একটি শব্দ বা শুরুতে আজকের তারিখ বসালে সেখান থেকে বাকিটা আর মেলে না।

তাই prompt সাজানোর নিয়ম: যা সবার জন্য এক আর কম বদলায় (নির্দেশ, নীতি, tool-এর বর্ণনা, few-shot উদাহরণ) তা আগে; যা প্রতিবার বদলায় (গ্রাহকের নাম, তারিখ, নতুন বার্তা) তা শেষে। Google আর OpenAI দুজনের নথিই বড় আর সাধারণ অংশ শুরুতে রাখতে বলে।

নিচের সারণি প্রতিটি কোম্পানির নিজের API নথি থেকে, লেখার সময়ে (নভেম্বর ২০২৬) পড়া। নিয়ম model-ভেদে আলাদা আর পরিবর্তন হতে পারে; ব্যবহারের আগে নথিটি নিজে খুলে মিলিয়ে নিন। দাম টাকায় নয়, নথি যেভাবে বলে সেভাবে, সাধারণ input-এর তুলনায় লেখা। যেখানে বিষয়টি নথিতে খুঁজে পাইনি, সেখানে তা-ই লেখা। কোনটি “ভালো”, এই সারণি তা বলে না।

API (কোম্পানি)কীভাবে চালু, কী cache হয়ন্যূনতম দৈর্ঘ্যCache-এর token-এর দাম (সাধারণ input-এর তুলনায়)আয়ুউত্তরে কোন ঘরে
OpenAI (ChatGPT-এর API)নিজে থেকে চালু; হুবহু মেলা prefix: নির্দেশ, tool, ইতিহাস, ছবি। নতুন model-এ চাইলে নিজে breakpointনতুন model-এ ১,০২৪ token; পুরনোয় model-ভেদেনতুন model-এ পড়া ০.১ গুণ, লেখা ১.২৫ বা ২.৫ গুণ (আয়ু অনুযায়ী); পুরনোয় model-ভেদে আলাদা ছাড়, লেখার বাড়তি দাম নেইনতুন model-এ ৩০ মিনিট (ডিফল্ট) বা ১২ ঘণ্টা; পুরনোয় কয়েক মিনিট থেকে ১ ঘণ্টা, বা ২৪ ঘণ্টাcached_tokens (input_tokens_details-এ)
Google (Gemini API)দুই রকম: নিজে থেকে (implicit), আর নিজে বানানো cache (explicit, cachedContents)model-ভেদে ২,০৪৮ বা ৪,০৯৬ token (implicit)দামের পাতায় প্রায় ১০%, কোনো model-এ ৫%; explicit-এ আয়ু ধরে জমা রাখার আলাদা খরচexplicit-এ নিজে ঠিক করা TTL, না দিলে ১ ঘণ্টাusage_metadata-এ
Anthropic (Claude API)cache_control দিয়ে চিহ্ন (সর্বোচ্চ ৪টি) বা স্বয়ংক্রিয়; ক্রম tools → system → messagesনতুন model-এ ৫১২; পুরনোয় ১,০২৪ থেকে ৪,০৯৬লেখা ১.২৫ গুণ (৫ মিনিট) বা ২ গুণ (১ ঘণ্টা); পড়া ০.১ গুণ, কিছু নতুন model-এ আরও কম৫ মিনিট, প্রতি ব্যবহারে নতুন করে; চাইলে ১ ঘণ্টাcache_creation_input_tokens, cache_read_input_tokens
xAI (Grok API)নিজে থেকে; শুরুর হুবহু মেলা বার্তা। একই কথোপকথনের id দিলে মেলার সম্ভাবনা বাড়ে বলে নথিনথিতে পাইনি“substantially lower”; গুণক নথিতে পাইনিনথিতে পাইনি; যেকোনো সময় মুছে যেতে পারেcached_tokens
DeepSeekসবার জন্য নিজে থেকে চালু; prefix-এর একক ধরে, পুরো মিল লাগেবর্তমান নথিতে পাইনিদামের সারণিতে “cache hit” আর “cache miss” আলাদা সারি, hit অনেক কমব্যবহার থামলে কয়েক ঘণ্টা থেকে কয়েক দিনে মুছে যায়prompt_cache_hit_tokens, prompt_cache_miss_tokens
Moonshot AI (Kimi API)নিজে থেকে (implicit); block ধরে, অসম্পূর্ণ block cache হয় নানথিতে পাইনিhit-এর দাম miss-এর ১/১০; কোনো model-এ ১ ঘণ্টার লেখা ২ গুণ৫ মিনিট (ডিফল্ট) বা ১ ঘণ্টাcached_tokens

সারণির তিন শিক্ষা: cache অল্প সময় বাঁচে; ন্যূনতম দৈর্ঘ্যের নিচে cache হয় না; আর কোথাও লেখার দাম বাড়তি, তাই hit কম হলে লোকসানও হতে পারে। নিচের stepper §২-এর একই bot-এ সেই হিসাব দেখায়।

১,৪৪,০০০ অনুরোধ; system prompt ১,৫০০ token; বাকি input ৪৬০
ধাপ
হিসাব
ফল
সাশ্রয়
  1. ভাগ করা অংশ. প্রতিটি অনুরোধের ১,৫০০ token হুবহু এক: মাসে ২১৬M token. বাকি ৪৬০ (ইতিহাস আর বার্তা) প্রতিবার আলাদা, ৬৬.২৪M; এগুলো cache হয় না।
  2. Hit আর miss. ধরা যাক ৯০% অনুরোধ cache পায়: ১৯৪.৪M token পড়া হয় cache থেকে। বাকি ১০%-এ (cache মুছে গেছে, প্রথম অনুরোধ) ২১.৬M token নতুন করে লেখা হয়।
  3. পড়ার খরচ. পড়া সাধারণ দামের ০.১ গুণ: ১৯৪.৪ × ০.১০ × ১.০০ = ১৯.৪৪, পুরো দামে হতো ১৯৪.৪০.
  4. লেখার খরচ. যে নথি লেখার বাড়তি দাম রাখে, সেখানে ধরুন ১.২৫ গুণ: ২১.৬ × ১.২৫ = ২৭.০০. পুরো দামে হতো ২১.৬০, তাই এখানে ৫.৪০ বেশি।
  5. মোট input. ১৯.৪৪ + ২৭.০০ + ৬৬.২৪ = ১১২.৬৮, আগে ছিল ২৮২.২৪. Output (৬৯.১২) যোগ করে মোট ১৮১.৮০; সাশ্রয় ১৬৯.৫৬, প্রায় ৪৮%.
  6. ভারসাম্যের hit rate. প্রতি hit-এ বাঁচে ১ − ০.১ = ০.৯, প্রতি miss-এ বাড়তি ১.২৫ − ১ = ০.২৫. লাভ হয় যখন h × ০.৯ > (১ − h) × ০.২৫, অর্থাৎ h > ০.২৫ ÷ ১.১৫ ≈ ০.২১৭. লেখার বাড়তি দাম না থাকলে যেকোনো hit-ই লাভ।

সারণির শেষ কলামটি এখানে কাজে লাগে: আন্দাজ নয়, প্রতিটি উত্তরের usage-এ cache-এর token গুনে আসল cache hit rate বের করুন। Hit কম হলে প্রায়ই কারণ prompt-এর শুরুতে বদলানো কিছু, বা অনুরোধের মাঝে বিরতি cache-এর আয়ু (TTL)-এর চেয়ে বেশি।

Cache বিলের হিসাব, উত্তরের নয়। নথিগুলো অনুযায়ী cache থেকে পড়লেও model একই prompt দেখে; উত্তরের মান বদলানোর কথা নয়। কিন্তু গোপনীয়তার দিক ভাবুন: এক গ্রাহকের ব্যক্তিগত তথ্য prompt-এর ভাগ করা শুরুতে রাখবেন না। ভাগ করা অংশে থাকুক শুধু সবার জন্য এক নীতি।

৪ · Latency-র দুই অংশ — প্রথম token পর্যন্ত, তারপর প্রতি সেকেন্ডে কত

চায়ের দোকানের কথা ভাবুন। দশজনের অর্ডার এলে দোকানি আগে সব কাপ সাজান, পানি ফুটান: এটি একবারের প্রস্তুতি, অর্ডার যত বড় তত বেশি সময়। তারপর এক এক কাপ ঢালেন, প্রতিটিতে প্রায় একই সময়। LLM-ও তাই: আগে পুরো prompt একসঙ্গে পড়া, তারপর এক এক token লেখা।

প্রথম অংশের নাম prefill: পুরো prompt একটি pass-এ model-এ চলে আর KV cache ভরে। এর শেষে প্রথম token আসে; অনুরোধ পাঠানো থেকে এই পর্যন্ত সময় time to first token (TTFT), যাকে first-token latency-ও বলে। দ্বিতীয় অংশ decode: প্রতি ধাপে একটি নতুন token, আগের KV cache-এর সাহায্যে। এর গতি মাপা হয় tokens per second (r) দিয়ে। N token-এর উত্তরে:

মোট সময় ≈ TTFT + (N − 1) ÷ r

Hosted API-তে TTFT-এর ভেতরে নেটওয়ার্ক আর server-এর সারিতে অপেক্ষাও থাকে। Anthropic-এর latency নথি TTFT শব্দটি সংজ্ঞা দিয়ে ব্যবহার করে, আর OpenAI-এর latency নথির সাতটি নীতির মধ্যে আছে কম token লেখা, কম input, কম অনুরোধ আর ব্যবহারকারীকে কম অপেক্ষা করানো (লেখার সময়ে, নভেম্বর ২০২৬)।

Request timeline: prefill, time to first token and decode, with and without prefix cache cache ছাড়া prefill: পুরো prompt প্রথম token (TTFT) prefix cache সহ cache TTFT অনেক কম decode-এর গতি একই নেটওয়ার্ক, সারি prefill decode: ১ token মোট সময় ≈ TTFT + (N − 1) ÷ r N = output tokens, r = tokens per second
TTFT-এর বড় অংশ prefill, যা prompt-এর দৈর্ঘ্যে বাড়ে; cache সেই অংশ ছোট করে। প্রথম token-এর পরের গতি r মূলত model আর যন্ত্রের ওপর নির্ভর করে, prompt-এর ওপর কম।

এবার নিজের যন্ত্রে মাপা। Streaming-এ প্রতিটি token আসার মুহূর্ত লিখে রাখা যায়। transformers-এর generate() একটি streamer নেয়, যার put() প্রতিটি নতুন token-এ ডাকা হয়। মূল অংশ (একটু ছোট করে দেখানো):

class Clock(BaseStreamer):          # প্রথম put() = prompt নিজেই, বাদ
    def __init__(self): self.t, self.first = [], True
    def put(self, value):
        if self.first: self.first = False; return
        self.t.append(time.perf_counter())
    def end(self): pass

c = Clock(); t0 = time.perf_counter()
model.generate(ids, max_new_tokens=48, min_new_tokens=48, do_sample=False, streamer=c)
ttft = c.t[0] - t0
tps = (len(c.t) - 1) / (c.t[-1] - c.t[0])

System prompt-এ §৩-এর নীতির ২, ৬, ১২ আর ২৫টি নিয়ম, তারপর পুরো নীতি দুবার আর চারবার, যাতে prompt ২৩৪ থেকে ৮,২২৮ token পর্যন্ত যায়। প্রতি দৈর্ঘ্যে তিনবার, আগে একবার warm-up বাদ দিয়ে:

~/bazarghar-dl$ uv run python latency.py length      # সত্যিকারের run, CPU-তে ৩ thread
2     prompt=  234  ttft=1.185s tps=10.3  ttft=1.018s tps=12.0  ttft=1.009s tps=10.1
6     prompt=  524  ttft=2.359s tps=10.8  ttft=2.283s tps=9.3  ttft=2.228s tps=10.5
12    prompt=  978  ttft=3.947s tps=10.2  ttft=3.873s tps=9.6  ttft=3.936s tps=10.7
25    prompt= 2099  ttft=9.346s tps=10.1  ttft=8.921s tps=10.3  ttft=9.049s tps=10.1
25x2  prompt= 4142  ttft=19.651s tps=8.2  ttft=20.444s tps=8.9  ttft=19.686s tps=8.9
25x4  prompt= 8228  ttft=43.278s tps=7.1  ttft=42.125s tps=7.4  ttft=38.511s tps=6.4

এই সংখ্যাগুলো পাতার কোডে বসিয়ে একটি latency model fit করা হলো: TTFT prompt-এর দৈর্ঘ্যের রৈখিক আর দ্বিঘাত function হিসেবে, আর decode-এর গতিও দৈর্ঘ্যের function হিসেবে। তারপর কয়েকটি বাস্তব অবস্থার মোট সময় অনুমান।

import numpy as np
import matplotlib.pyplot as plt
# সত্যিকারের run: Qwen2.5-0.5B-Instruct, CPU-তে ৩টি thread, greedy, ৪৮টি নতুন token
# প্রতিটি prompt-দৈর্ঘ্যে ৩ বার (warm-up বাদ): TTFT (সেকেন্ড) আর tokens/s
T = np.array([234, 524, 978, 2099, 4142, 8228])
TTFT = np.array([[1.185, 1.018, 1.009], [2.359, 2.283, 2.228], [3.947, 3.873, 3.936],
                 [9.346, 8.921, 9.049], [19.651, 20.444, 19.686], [43.278, 42.125, 38.511]])
TPS = np.array([[10.3, 12.0, 10.1], [10.8, 9.3, 10.5], [10.2, 9.6, 10.7],
                [10.1, 10.3, 10.1], [8.2, 8.9, 8.9], [7.1, 7.4, 6.4]])
ttft, tps = TTFT.mean(1), TPS.mean(1)
lin = np.polyfit(T, ttft, 1)            # TTFT ≈ a·T + b
quad = np.polyfit(T, ttft, 2)           # attention-এর T² অংশসহ
r_fit = np.polyfit(T, tps, 1)           # লম্বা context-এ decode-ও ধীর
print(f"রৈখিক: TTFT ≈ {lin[1]:.2f} + {lin[0]*1000:.2f} s × (T/1000)")
print(f"prefill-এর গতি ≈ {1/lin[0]:.0f} token/s; decode ≈ {tps[:4].mean():.1f} token/s")
for n, f in (("রৈখিক", lin), ("দ্বিঘাত", quad)):
    err = np.abs(np.polyval(f, T) - ttft).max()
    print(f"{n:8} fit-এর সর্বোচ্চ ভুল: {err:.2f} s")

def total(T_in, n_out):                  # মোট সময় = TTFT + (N − 1) ÷ r
    return np.polyval(quad, T_in) + (n_out - 1) / np.polyval(r_fit, T_in)
for T_in, n in ((300, 120), (1500, 120), (1500, 400), (6000, 120)):
    s = total(T_in, n)
    print(f"prompt {T_in:5}, উত্তর {n:3}: মোট {s:5.1f} s, TTFT-এর ভাগ "
          f"{np.polyval(quad, T_in) / s:.0%}")

fig, ax = plt.subplots(figsize=(5.2, 3.2))
ax.errorbar(T, ttft, yerr=TTFT.std(1), fmt="o", label="measured (mean of 3)")
x = np.linspace(0, 8500, 100)
ax.plot(x, np.polyval(lin, x), "--", label="linear fit")
ax.plot(x, np.polyval(quad, x), label="quadratic fit")
ax.set_xlabel("prompt tokens"); ax.set_ylabel("TTFT (s)"); ax.legend()

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

রৈখিক: TTFT ≈ -0.76 + 5.07 s × (T/1000)
prefill-এর গতি ≈ 197 token/s; decode ≈ 10.3 token/s
রৈখিক    fit-এর সর্বোচ্চ ভুল: 0.78 s
দ্বিঘাত  fit-এর সর্বোচ্চ ভুল: 0.40 s
prompt   300, উত্তর 120: মোট  12.3 s, TTFT-এর ভাগ 9%
prompt  1500, উত্তর 120: মোট  18.5 s, TTFT-এর ভাগ 36%
prompt  1500, উত্তর 400: মোট  46.4 s, TTFT-এর ভাগ 14%
prompt  6000, উত্তর 120: মোট  44.1 s, TTFT-এর ভাগ 66%

Prefill: এই যন্ত্রে প্রতি ১,০০০ prompt token-এ প্রায় ৫ সেকেন্ড, অর্থাৎ prefill চলে প্রায় ২০০ token/s গতিতে, decode-এর (প্রায় ১০ token/s) প্রায় ২০ গুণ। কারণ prefill-এ সব token একসঙ্গে matrix-গুণে যায়, decode-এ একটি একটি করে। রৈখিক fit-এর ছেদবিন্দু ঋণাত্মক (−০.৭৬ সেকেন্ড), যা বাস্তবে অসম্ভব; এটি বলে, রেখাটি সোজা নয়, বাঁকা। দ্বিঘাত fit-এর ভুল অর্ধেক: attention-এর খরচ দৈর্ঘ্যের বর্গে বাড়ে (পর্ব ৩৩), লম্বা prompt-এ সেই অংশ চোখে পড়ে। Decode-ও লম্বা context-এ ধীর (৮,২২৮ token-এ প্রায় ৭ token/s), কারণ প্রতিটি নতুন token-কে বেশি KV পড়তে হয়।

কোন অংশ প্রাধান্য পায়: ছোট prompt আর ১২০ token-এর উত্তরে TTFT মোট সময়ের ৯%, তখন উত্তর ছোট করাই লাভ। ৬,০০০ token-এর prompt-এ TTFT ৬৬%, তখন prompt ছোট করা বা cache লাভ। এটি ছোট model, CPU-তে, অন্য কাজ চলা যন্ত্রে; GPU বা hosted server-এ সংখ্যা আলাদা, যুক্তি একই।

A: ছোট prompt (২৩৪ token) · B: পুরো নীতি (২,০৯৯) · C: B, prefix cache সহ
ধাপ
হিসাব
মোট
TTFT-এর ভাগ
  1. মাপা সংখ্যা. A আর B এসেছে উপরের run-এর তিনবারের গড় থেকে; C §৫-এর cache run-এর পাঁচটি প্রশ্নের গড়। তিন অবস্থায় decode-এর গতি প্রায় একই, ১০–১১ token/s; ফারাক শুধু TTFT-এ।
  2. A, ১২০ token. ১.০৭ + ১১৯ ÷ ১০.৮ = ১.০৭ + ১১.০২ = ১২.০৯ সেকেন্ড। TTFT মাত্র ৯%; এখানে সময় যায় লেখায়।
  3. B, ১২০ token. ৯.১১ + ১১৯ ÷ ১০.১৭ = ৯.১১ + ১১.৭০ = ২০.৮১ সেকেন্ড। একই উত্তর, কিন্তু লম্বা নীতি পড়তেই প্রায় ৯ সেকেন্ড; ব্যবহারকারী streaming-এও ৯ সেকেন্ড খালি পর্দা দেখেন।
  4. C, ১২০ token. নীতির KV আগে থেকে জমা, শুধু নতুন ৩৩–৫৬ token-এর prefill: TTFT ০.৩৭. মোট ১২.০৪ সেকেন্ড, A-র প্রায় সমান, অথচ পুরো নীতি model-এর সামনে আছে।
  5. ২০ token-এর উত্তর. ছোট উত্তরে decode মাত্র ১.৮–১.৯ সেকেন্ড, তাই TTFT প্রাধান্য পায়: B-তে ৮৩%. শ্রেণিকরণ বা হ্যাঁ/না উত্তরের মতো কাজে TTFT-ই অভিজ্ঞতা ঠিক করে।
  6. কী শিখলাম. “X tokens/s” একা যথেষ্ট নয়; দুই সংখ্যাই লাগে। লম্বা উত্তরে r, ছোট উত্তর আর লম্বা prompt-এ TTFT. আর স্থির লম্বা prompt-এর TTFT সমস্যার সরাসরি ওষুধ prefix cache.

৫ · নিজের যন্ত্রে prefix cache — একই নীতি, প্রায় ২২ গুণ দ্রুত প্রথম token

Hosted API-র cache ভেতরে কী করে, তা বাইরে থেকে দেখা যায় না; কিন্তু ধারণাটি নিজের যন্ত্রে চালানো যায়। §৩-এর ২৫টি নিয়মের নীতি (chat template-সহ ২,০৪৭ token) একবার model-এ চালিয়ে তার KV cache রাখা হলো। তারপর পাঁচটি আলাদা গ্রাহক-প্রশ্নে দুইভাবে উত্তর: পুরো prompt নতুন করে, আর জমা cache-এর একটি কপি থেকে শুরু করে। মূল অংশ (একটু ছোট করে দেখানো):

pre_ids = tok(apply_chat_template([system_only]), return_tensors="pt").input_ids
base = DynamicCache()
model(pre_ids, past_key_values=base, use_cache=True)     # নীতি একবার, ২,০৪৭ token

for q in QUESTIONS:
    ids = ids_for(POLICY, q)                  # শুরুর ২,০৪৭ token হুবহু একই
    full   = run(ids, 40)                                   # cache ছাড়া
    cached = run(ids, 40, past=copy.deepcopy(base))         # বাকিটুকুর prefill
    same = full.text == cached.text
~/bazarghar-dl$ uv run python latency.py cache       # সত্যিকারের run, CPU-তে ৩ thread
prefix tokens=2047 prefill once=12.039s
prompt=2099 new= 52 ttft full=8.108s cached=0.374s x21.7 same=True
prompt=2083 new= 36 ttft full=7.819s cached=0.317s x24.6 same=True
prompt=2101 new= 54 ttft full=7.824s cached=0.421s x18.6 same=True
prompt=2080 new= 33 ttft full=8.266s cached=0.314s x26.3 same=True
prompt=2103 new= 56 ttft full=8.241s cached=0.437s x18.9 same=True

সময়: cache ছাড়া TTFT গড়ে ৮.০৫ সেকেন্ড, cache থেকে ০.৩৭; প্রায় ২২ গুণ দ্রুত। নতুন অংশ মাত্র ৩৩ থেকে ৫৬ token, বাকি ২,০৪৭টির হিসাব আর লাগেনি। একবারের খরচ ছিল নীতির prefill, ১২.০৪ সেকেন্ড; দ্বিতীয় প্রশ্নেই তা উঠে এসেছে। Hosted API-তে এই একবারের খরচই “cache লেখা”, যার দাম কোনো কোনো নথিতে বাড়তি।

উত্তর হুবহু এক: greedy decoding-এ পাঁচটিতেই same=True. এটি ভাবনার প্রশ্ন ৩-এর যুক্তির হাতে-কলমে প্রমাণ: causal model-এ শুরুর token-এর KV পরের কিছুর ওপর নির্ভর করে না। দুটি সতর্কতা। এক, একটি cache-কে সরাসরি দুই প্রশ্নে চালালে প্রথম প্রশ্নের KV জুড়ে যায়, তাই প্রতিবার deepcopy; এটি memory খায়। দুই, ভিন্ন hardware বা batch-এ floating-point হিসাবের ক্রম বদলালে খুব ছোট পার্থক্য আসতে পারে।

উত্তরের মান: cache গতি দেয়, বুদ্ধি নয়। দুটি আসল উত্তর (৪০ token-এ কাটা, হুবহু): খুলনার প্রশ্নে “চার্জ ১৩০ টাকা।”, অথচ নীতিতে ঢাকার বাইরে ১২০ টাকা; ছয় হাজার টাকার ক্যাশ অন ডেলিভারির প্রশ্নে “চলবে না। আমরা একটি সম্পূর্ণ সাধারণ বিশে”, যার শুরুটা নীতির সঙ্গে মেলে (পাঁচ হাজারের বেশিতে আগাম পেমেন্ট), বাকিটা অর্থহীন। এত ছোট model লম্বা বাংলা নীতি থেকে নির্ভরযোগ্য উত্তর দিতে পারছে না; বড় model এখানে অন্যরকম হবে, আর মান মাপার পদ্ধতি পর্ব ৬৬-এ।

আরও গভীরে: Server-এ cache রাখার জায়গা সীমিত, তাই কোন prefix রাখা আর কোনটি মোছা, তা একটি cache-eviction সমস্যা (সিস্টেম ডিজাইন কোর্সের পর্ব ২৮)। Kwon ও সহকর্মীদের PagedAttention (২০২৩) KV cache-কে ছোট block-এ রেখে block-স্তরে ভাগ করার পথ দেখায়, আর SGLang-এর RadixAttention prefix-গুলো একটি radix গাছে সাজায়। আরেকটি সূক্ষ্ম দিক: cache থাকলে একটি অনুরোধ আরেকটির চেয়ে দ্রুত হয়, আর সময় মেপে কোন prefix আগে কেউ পাঠিয়েছে, তা আঁচ করার একটি side-channel ঝুঁকিও গবেষণায় আলোচিত হয়েছে। Serving-এর পুরো ছবি পর্ব ৮৩ · LLM serving-এ।

৬ · Batch, streaming আর model বাছাই

Batch: সব কাজের তাড়া নেই। রাতে দিনের সব রিভিউর সারাংশ, পুরনো অর্ডারের শ্রেণিকরণ, eval set চালানো: এগুলো একসঙ্গে জমা দিয়ে কয়েক ঘণ্টা পরে নেওয়া যায়। কয়েকটি কোম্পানি এর জন্য কম দাম রাখে, কারণ তারা কাজটি ফাঁকা সময়ে চালাতে পারে। Streaming: মোট সময় কমায় না, কিন্তু ব্যবহারকারী TTFT-এর পরেই লেখা দেখতে শুরু করেন (ভাবনার প্রশ্ন ২)। নিচের সারণি আবার প্রতিটি কোম্পানির নিজের নথি থেকে, লেখার সময়ে (নভেম্বর ২০২৬); পরিবর্তন হতে পারে।

APIToken গোনার উপায়Batch (নথির ভাষায়)অন্য স্তর বা ছাড়
OpenAIinput token গোনার endpoint; tiktoken (শুধু সাধারণ লেখায়)সাধারণের তুলনায় ৫০% ছাড়, ২৪ ঘণ্টার window“flex” স্তর batch-এর দামে, ধীর উত্তর
Google (Gemini)countTokensসাধারণ দামের ৫০%, লক্ষ্য ২৪ ঘণ্টাflex-এ ছাড়, priority-তে বাড়তি দাম
Anthropic (Claude)count_tokens endpoint, বিনা খরচে, আনুমানিকসাধারণ দামের ৫০%; ২৪ ঘণ্টায় শেষ না হলে মেয়াদ শেষনথিতে service tier-কে “legacy” বলা
xAI (Grok)tokenize-text endpoint“reduced pricing”; দামের পাতায় কিছু model-এ ২০%priority স্তর: কম TTFT, বাড়তি দাম
DeepSeekনামানো যায় এমন tokenizerনথিতে পাইনিব্যস্ত সময়ের বাইরে অর্ধেক দাম (UTC-তে নির্দিষ্ট ঘণ্টা)
Moonshot AI (Kimi)token অনুমানের endpointসাধারণ দামের ৬০%, window ২৪ ঘণ্টা বা বেশিনথিতে পাইনি

ছয়টির সবকটিতে streaming চালু হয় অনুরোধে stream ধরনের একটি parameter দিয়ে। নিচে অনুরোধের আকার, নথির কাঠামো অনুসরণ করে; চালানো হয়নি, কারণ এখানে কোনো API key নেই:

# নমুনা (চালানো হয়নি): শুধু body-র মূল অংশ; model-এর নাম নিজে বসান
# Claude: স্থির নীতির শেষে cache-এর চিহ্ন, তারপর প্রশ্ন; উত্তর streaming-এ
{"model": "…", "max_tokens": 300, "stream": true,
 "system": [{"type": "text", "text": "…বাজারঘরের ২৫টি নিয়ম…",
             "cache_control": {"type": "ephemeral"}}],
 "messages": [{"role": "user", "content": "খুলনায় ডেলিভারি চার্জ কত?"}]}
# OpenAI, xAI, DeepSeek, Kimi: caching নিজে থেকে; স্থির অংশ আগে রাখলেই হয়
{"model": "…", "stream": true,
 "messages": [{"role": "system", "content": "…বাজারঘরের ২৫টি নিয়ম…"},
              {"role": "user", "content": "খুলনায় ডেলিভারি চার্জ কত?"}]}

Model বাছাই: ছোট model সাধারণত প্রতি token-এ সস্তা আর দ্রুত। কিন্তু বাংলায় দুটি বাড়তি প্রশ্ন: নির্ভুলতা কতটা নামে, আর tokenizer বাংলাকে কত টুকরোয় ভাঙে। একই ৪০টি বার্তার শ্রেণিকরণে (§৭-এর কাজ, পুরো prompt) দুই ছোট খোলা model:

Modelগড় input tokenপ্রতি বার্তায় সময়লিখিয়ে নির্ভুলতাlog-likelihood নির্ভুলতা
SmolLM2-360M-Instruct১,৭৭৪৫.৮৭ s (CPU)০.০০০০.২৭৫
Qwen2.5-0.5B-Instruct১,০৫৬৩.৮৮ s (CPU)০.২০০০.৩০০
Qwen2.5-1.5B-Instruct১,০৫৬GPU-তে, তুলনীয় নয়০.৭৫০০.৭৭৫

৭ · সংক্ষিপ্ত prompt — কত token বাঁচে, কতটা নির্ভুলতা যায়

সবচেয়ে সহজ সাশ্রয় মনে হয় prompt ছোট করা। কিন্তু কী কাটছেন? বাজারঘরের ৪০টি গ্রাহক-বার্তা (বাংলা আর Banglish, পাঁচ শ্রেণিতে ৮টি করে: delivery, return, payment, product, complaint; হাতে লেখা কাল্পনিক) শ্রেণিতে ফেলার জন্য পাঁচটি system prompt:

  • full (১,০০৮ token): কাজের বর্ণনা, প্রতিটি শ্রেণির সংজ্ঞা, পাঁচটি উদাহরণ, আর “শুধু নামটি লেখো”।
  • medium আর medium2 (১৯৭ আর ২১৪): সংজ্ঞা এক লাইনে, উদাহরণ নেই; দুটি আলাদা ভাষ্য।
  • short আর short2 (৪৫ আর ৬৪): শুধু পাঁচটি নাম; দুটি ভাষ্য।

Model Qwen2.5-1.5B-Instruct (Apache 2.0), GPU-তে, bfloat16. মাপ দুইভাবে: (ক) পাঁচটি শ্রেণি-নামের log-likelihood তুলনা (পর্ব ৫৮-এর পদ্ধতি), যাতে format-এর ভুল বাদ দিয়ে শুধু বোঝা মাপা যায়; (খ) greedy-তে সর্বোচ্চ ৬টি token লিখিয়ে উত্তর থেকে নাম খোঁজা, যা আসল ব্যবহারের কাছাকাছি।

~/bazarghar-dl$ uv run python gpu_run.py        # সত্যিকারের run, GPU-তে; লোডের লাইন বাদ
full     ll_acc=0.775 gen_acc=0.750 unparsed=1 in_tok=1055.7
medium   ll_acc=0.600 gen_acc=0.425 unparsed=15 in_tok=244.7
medium2  ll_acc=0.550 gen_acc=0.600 unparsed=2 in_tok=261.7
short    ll_acc=0.350 gen_acc=0.025 unparsed=39 in_tok=92.7
short2   ll_acc=0.525 gen_acc=0.425 unparsed=8 in_tok=111.7
peak GPU memory allocated: 3.25 GiB

প্রতিটি বার্তার ফল (সঠিক ১, ভুল ০) নিচের কোডে বসানো। কোড bootstrap দিয়ে নির্ভুলতার CI, একই বার্তায় full-এর সঙ্গে জোড়া পার্থক্য (মেশিন লার্নিং কোর্সের পর্ব ২২), আর আপনার বসানো দামে ১০,০০০ বার্তার input-খরচ হিসাব করে।

import numpy as np
import matplotlib.pyplot as plt
# সত্যিকারের run: Qwen2.5-1.5B-Instruct, ৪০টি বার্তা (প্রতি শ্রেণিতে ৮টি),
# পাঁচ শ্রেণির log-likelihood তুলনা। ১ = সঠিক, ০ = ভুল; বার্তার ক্রম সব prompt-এ এক
RUNS = {  # নাম: (গড় input token, ৪০টি ফল)
 "full":    (1055.7, "1111011111011111111011111111111001110000"),
 "medium":  (244.7,  "1111010110111110111011111110011000000000"),
 "medium2": (261.7,  "1111011111111111110010110010001000000000"),
 "short":   (92.7,   "1111010110010010000000101011100000000000"),
 "short2":  (111.7,  "1111111110011111000001110000000001110100"),
}
P_IN = 1.00                                # আপনার input-এর দাম, প্রতি 1M token
rng = np.random.default_rng(65)
X = {k: np.array([int(c) for c in s]) for k, (_, s) in RUNS.items()}
idx = rng.integers(0, 40, (5000, 40))      # একই বার্তা-নমুনা সব prompt-এ (জোড়া)
print(f"{'prompt':8}{'token':>6}{'খরচ/10k':>9}{'নির্ভুল':>8}   95% CI        full-এর তুলনায়")
for k, (tok, _) in RUNS.items():
    a = X[k][idx].mean(1)
    d = (X[k] - X["full"])[idx].mean(1)
    lo, hi = np.percentile(a, [2.5, 97.5])
    dl, dh = np.percentile(d, [2.5, 97.5])
    diff = "—" if k == "full" else f"{X[k].mean()-X['full'].mean():+.2f} [{dl:+.2f},{dh:+.2f}]"
    print(f"{k:8}{tok:6.0f}{tok * 10000 * P_IN / 1e6:9.2f}{X[k].mean():8.3f}"
          f"   [{lo:.2f}, {hi:.2f}]  {diff}")
per_class = {k: [int(X[k][i*8:(i+1)*8].sum()) for i in range(5)] for k in X}
print("শ্রেণি অনুযায়ী সঠিক (৮টির মধ্যে): delivery, return, payment, product, complaint")
for k, v in per_class.items():
    print(f"  {k:8}{v}")

fig, ax = plt.subplots(figsize=(5, 3))
for k, (tok, _) in RUNS.items():
    ax.scatter(tok, X[k].mean()); ax.annotate(k, (tok, X[k].mean()), xytext=(4, 4),
                                              textcoords="offset points")
ax.set_xscale("log"); ax.set_xlabel("input tokens per message (log)")
ax.set_ylabel("accuracy (n = 40)"); ax.set_ylim(0, 1)

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

prompt   token  খরচ/10k নির্ভুল   95% CI        full-এর তুলনায়
full      1056    10.56   0.775   [0.65, 0.90]  —
medium     245     2.45   0.600   [0.45, 0.75]  -0.18 [-0.33,-0.05]
medium2    262     2.62   0.550   [0.40, 0.70]  -0.22 [-0.38,-0.07]
short       93     0.93   0.350   [0.20, 0.50]  -0.43 [-0.57,-0.28]
short2     112     1.12   0.525   [0.38, 0.68]  -0.25 [-0.42,-0.07]
শ্রেণি অনুযায়ী সঠিক (৮টির মধ্যে): delivery, return, payment, product, complaint
  full    [7, 7, 7, 7, 3]
  medium  [6, 6, 7, 5, 0]
  medium2 [7, 8, 5, 2, 0]
  short   [6, 3, 1, 4, 0]
  short2  [8, 6, 3, 0, 4]

লাভ-ক্ষতি: full থেকে medium-এ token প্রায় ৭৭% কম, কিন্তু নির্ভুলতা ০.১৮ কম, আর জোড়া CI [−০.৩৩, −০.০৫] শূন্য ছোঁয়নি: ৪০টি বার্তাতেই ক্ষতিটি স্পষ্ট। short-এ ক্ষতি আরও বড়। শ্রেণি-ভাগ দেখায় কী হারাল: medium-এর দুই ভাষ্যেই ৮টি complaint-এর ৮টি গেছে delivery-তে, যেমন “ডেলিভারি ম্যান খুব খারাপ ব্যবহার করেছে”। এক লাইনের সংজ্ঞা ছিল, কিন্তু full-এর বিস্তারিত সংজ্ঞা আর উদাহরণ (“আপনাদের কলসেন্টার খুব বাজে → complaint”) ছিল না। full-এও complaint মাত্র ৩/৮; এই শ্রেণিতে model নিজেই দুর্বল, আর ৮টি বার্তায় দোলাও বড়।

ভাষ্যের দোলা: একই দৈর্ঘ্যের দুই short ভাষ্যে ০.৩৫০ বনাম ০.৫২৫. শব্দ বদলালেই এত ফারাক, তাই একটি ভাষ্য দিয়ে “ছোট prompt চলে” বা “চলে না” বলা যায় না (পর্ব ৬০-এ এ নিয়ে বিস্তারিত)।

Format-এর দাম: লিখিয়ে নেওয়া উত্তরে short ভাষ্য প্রায় পুরো ব্যর্থ (৪০টির ৩৯টিতে কোনো শ্রেণির নামই নেই; model লিখেছে “বার্তা”, “হ্যাঁ,” ইত্যাদি)। full-এর উদাহরণগুলো উত্তরের ধাঁচ দেখিয়ে দিয়েছিল। ছাঁটার সময় উদাহরণ সবার আগে কাটার লোভ হয়, অথচ ছোট model-এ উদাহরণই প্রায়ই format ধরে রাখে (পর্ব ৬১ দেখুন)।

Caching হিসাব বদলে দেয়। full-এর ১,০৫৬ token-এর ১,০০৮টি স্থির system prompt; বার্তাভেদে বদলায় গড়ে মাত্র ৪৮টি। পড়া ০.১ গুণ ধরলে full-এর কার্যকর input প্রায় ১,০০৮ × ০.১ + ৪৮ ≈ ১৪৯ token-সমান, short2-এর (১১২) কাছাকাছি, অথচ নির্ভুলতা অনেক বেশি। তবে একটি ফাঁদ: ১,০০৮ হলো Qwen-এর tokenizer-এ গোনা; অন্য model-এ সংখ্যা আলাদা হবে, আর §৩-এর সারণির ১,০২৪ বা ২,০৪৮-এর মতো ন্যূনতম দৈর্ঘ্যের নিচে পড়লে cache হবেই না। তাই সিদ্ধান্ত তিনটি সংখ্যা একসঙ্গে দেখে: নির্ভুলতা (CI-সহ), cache-এর পরের কার্যকর token, আর TTFT. এই run একটি model, একটি task আর ৪০টি বার্তার; নিজের কাজে আবার মাপুন।

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

  • মাসের খরচের সূত্র? R × (Tin × Pin + Tout × Pout) ÷ 106; প্রতিটি অনুরোধে পুরো ইতিহাস input.
  • Prompt caching কেন শুধু prefix-এ? Causal attention-এ শুরুর token-এর KV পরের token-এর ওপর নির্ভর করে না।
  • লেখা w গুণ, পড়া r গুণ: ভারসাম্যের hit rate? (w − 1) ÷ ((w − 1) + (1 − r)); w = ১.২৫, r = ০.১ হলে ≈ ০.২২.
  • TTFT বনাম tokens/s? TTFT ≈ সারি + prefill, prompt-এর দৈর্ঘ্যে বাড়ে; tokens/s decode-এর গতি। মোট ≈ TTFT + (N − 1) ÷ r.
  • বাংলায় খরচ বেশি কেন? Tokenizer বাংলাকে বেশি টুকরোয় ভাঙে; অনুপাত tokenizer-ভেদে আমাদের মাপে ১.১৩ থেকে ৬.৪৯ গুণ।

৮ · Context window-এর বাজেট — কোন অংশ কত token পাবে

Context window একটি নির্দিষ্ট আকারের থলে। কোম্পানিগুলো model-ভেদে আলাদা আকার লেখে, আর তা বদলায়; তাই এখানে একটি সাধারণ হিসাব। একটি context বাজেট লিখে রাখুন:

W ≥ Tsystem + Tইতিহাস + Tতথ্য + Tবার্তা + Tউত্তরের জন্য রাখা

W হলো window-এর আকার, Tতথ্য হলো খুঁজে আনা নথি (পর্ব ৬৯ · RAG-এ বিস্তারিত)। ধরুন W = ৮,১৯২: system ১,৫০০, উত্তরের জন্য ৫১২, নতুন বার্তা ১০০; ইতিহাস আর তথ্যের জন্য বাকি ৬,০৮০ token. Qwen2.5-এর tokenizer-এ বাংলা প্রতি শব্দে ৬.৬৮ token (§১), তাই এটি প্রায় ৯১০ বাংলা শব্দ; o200k_base-এর মতো অনুপাতে (২.৪৯) প্রায় ২,৪৪০ শব্দ। একই window, বাংলায় আঁটে অনেক কম।

বাজেট ছাড়ালে কী কাটবেন, তা আগে থেকে ঠিক রাখুন: প্রথমে পুরনো ইতিহাস (শেষ কয়েকটি বার্তা রেখে বাকিগুলোর ছোট সারাংশ), তারপর কম প্রাসঙ্গিক তথ্য; system-এর নীতি আর উত্তরের জায়গা কখনো নয়। উত্তরের জায়গা না রাখলে উত্তর মাঝপথে কাটা পড়ে। কথোপকথনের ইতিহাস সামলানোর নকশা পর্ব ৬৪-এ।

৯ · AI-এর যুগে — খরচ আর গতির দাবি যাচাই

খরচ আর গতির প্রশ্নে AI assistant আত্মবিশ্বাসী সংখ্যা দেয়, যা প্রায়ই পুরনো বা বানানো। ভুলগুলো চুপচাপ বিলে আসে।

AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
কোনো model-এর “বর্তমান দাম” স্মৃতি থেকে বলাদাম বদলায়; training-এর পরের পরিবর্তন জানে নাকোম্পানির দামের পাতা, তারিখসহ
“১ token ≈ ৪ অক্ষর” বাংলাতেওনথিগুলো এটি ইংরেজির জন্য বলে; বাংলায় অনেক আলাদানিজের লেখা নিজের tokenizer বা গোনার endpoint-এ
এক কোম্পানির caching-এর parameter অন্যটিতেনাম, ন্যূনতম দৈর্ঘ্য, আয়ু, দাম সব আলাদা (§৩)নিজের API-র নথি; usage-এ cache-এর token দেখা
একবারের সময় মেপে “X tokens/s”প্রথম run-এ warm-up, আর TTFT আর decode মিশে যায়warm-up বাদ, কয়েকবার, TTFT আলাদা
“Prompt ছোট করো, মান একই থাকবে”ছোট model-এ শব্দ বদলেই ফল নড়েeval set-এ জোড়া তুলনা, CI-সহ
HF-এর পুরনো argument, যেমন cache-এর পুরনো ধরনtransformers-এর cache API version-এ বদলেছেনিজের version-এর নথি; ছোট উদাহরণে চালিয়ে দেখা

খরচের হিসাব চাওয়ার সময় সূত্র আর অনুমানগুলো আলাদা করে চেয়ে নিন, যাতে নিজে মিলিয়ে নিতে পারেন। আসল গ্রাহকের কথোপকথন হিসাবের উদাহরণ হিসেবে hosted assistant-এ দেবেন না; বানানো উদাহরণ দিন।

আমি একটি বাংলা গ্রাহক-সহায়তা bot-এর মাসিক খরচ আর latency হিসাব করব। সংখ্যা আমি দেব; তুমি কোনো দাম বা গুণক নিজে থেকে বসাবে না। ১. অনুরোধ, input token (system, ইতিহাস, বার্তা) আর output token আলাদা করে মাসের খরচের সূত্র লেখো, প্রতি 1M token-এর দাম P_in আর P_out প্রতীকে। ২. Prompt caching যোগ করো: hit rate h, পড়ার গুণক r, লেখার গুণক w. লাভের ভারসাম্যের h-এর সূত্র দাও। ৩. TTFT আর tokens/s থেকে মোট সময়ের সূত্র দাও, আর বলো কোন অংশ prompt-এর দৈর্ঘ্যে বাড়ে। ৪. Hugging Face transformers-এ streaming-সহ TTFT মাপার ছোট Python কোড দাও; warm-up বাদ দেবে, তিনবার মাপবে। আমার version লিখে দিচ্ছি; নিশ্চিত না হলে বলো। ৫. কোন কথা কোন নথি থেকে, লিংক দাও; না জানলে বলো, অনুমান লিখবে না।

খরচ আর latency-র চেকলিস্ট

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

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

প্র ০১একই কথা বাংলায় লিখলে কি বাংলাভাষী ব্যবহারকারী বেশি দাম দেন?

Token ধরে দাম হলে, হ্যাঁ, প্রায়ই দেন, আর গবেষণাতেও বিষয়টি এসেছে। Petrov ও সহকর্মীরা ২০২৩ সালে (“Language Model Tokenizers Introduce Unfairness Between Languages”) দেখান, একই অর্থের লেখা ভাষাভেদে কয়েক গুণ পর্যন্ত বেশি token-এ ভাঙতে পারে। একই বছরে Ahia ও সহকর্মীরা (“Do All Languages Cost the Same?”) API-র বিলের দিক থেকে একই প্রশ্ন তোলেন। দুটি প্রবন্ধের সংখ্যা তাদের নিজস্ব ডেটা আর tokenizer-এর; আমাদের §১-এর ছোট মাপে Qwen2.5-এ ৩.৮৬ গুণ আর o200k_base-এ ১.৫২ গুণ, ধারণাটি একই।

খরচ শুধু টাকায় নয়। একই context window-এ বাংলায় কম কথা আঁটে, তাই লম্বা নথি বা ইতিহাস আগে কাটতে হয়। প্রতিটি output token-এ সময় লাগে, তাই বাংলা উত্তর একই দৈর্ঘ্যের ইংরেজি উত্তরের চেয়ে ধীরে শেষ হয়। আর পর্ব ৫৮-এ দেখেছেন, ছোট model-এ বাংলার নির্ভুলতাও কম হতে পারে।

কারণটি প্রযুক্তিগত, ইচ্ছাকৃত নয়: tokenizer train হয় যে ডেটায়, সেখানে ইংরেজি বেশি, তাই ইংরেজির টুকরো বড়। বড় vocabulary আর বেশি বহুভাষিক ডেটা ফারাক কমায়, o200k_base আর BLOOM-এর সংখ্যাই তার উদাহরণ। ব্যবহারকারী হিসেবে উপায়: নিজের লেখায় কয়েকটি model-এর tokenizer মেপে দেখা, শুধু প্রতি token-এর দাম নয়, প্রতি কাজের দাম তুলনা করা, আর উত্তরের ভাষা ও দৈর্ঘ্য নির্দেশে বেঁধে দেওয়া। নির্দেশ ইংরেজিতে আর উত্তর বাংলায় রাখলে input সস্তা হয়, কিন্তু ছোট model-এ মান বদলাতে পারে; মেপে সিদ্ধান্ত নিন।

মূল উপলব্ধি: দাম প্রতি token-এ, কিন্তু কাজ প্রতি কথায়। বাংলায় প্রতি কাজের দাম tokenizer ঠিক করে, আর সেটি নিজে মাপতে হয়।

প্র ০২Streaming কি উত্তর দ্রুত করে, নাকি শুধু দ্রুত মনে হয়?

মোট সময় প্রায় একই থাকে; বদলায় অপেক্ষার অভিজ্ঞতা। Model একটি করে token লেখে; streaming ছাড়া server সব token শেষ হওয়া পর্যন্ত ধরে রাখে, তারপর একসঙ্গে পাঠায়। Streaming-এ প্রতিটি টুকরো লেখা হতেই পাঠানো হয়, সাধারণত server-sent events দিয়ে। ব্যবহারকারী প্রথম শব্দ দেখেন TTFT-এর পরেই, আর পড়তে পড়তে বাকিটা আসে। মানুষ গড়ে যত দ্রুত পড়েন, অনেক model তার চেয়ে দ্রুত লেখে, তাই অপেক্ষা প্রায় মিলিয়ে যায়। Anthropic-এর latency নথি streaming-কে latency কমানোর তিন উপায়ের একটি বলে, আর OpenAI-এর নথিতে “ব্যবহারকারীকে কম অপেক্ষা করানো” একটি নীতি।

কিন্তু streaming বিনামূল্যে নয়। এক, পুরো উত্তর না দেখে যাচাই করা কঠিন: structured output-এ (পর্ব ৬২) JSON শেষ না হলে parse হয় না, আর নিরাপত্তার ছাঁকনি পুরো বাক্য না দেখে সিদ্ধান্ত নিতে পারে না। দুই, ভুল উত্তর ব্যবহারকারী আগেই দেখে ফেলেন, পরে মুছলেও। তিন, কোড জটিল হয়: সংযোগ মাঝপথে কাটলে কী হবে, token-এর হিসাব কোথায় আসে (কোনো কোনো API-তে streaming-এ usage পেতে আলাদা option লাগে, যেমন Kimi-র নথিতে লেখা)।

তাই নিয়ম কাজভেদে। মানুষের সঙ্গে কথোপকথনে streaming প্রায় সবসময় ভালো অভিজ্ঞতা দেয়। যন্ত্রের জন্য উত্তর (শ্রেণি, JSON, রাতের batch) streaming-এ কোনো লাভ নেই, কারণ প্রোগ্রাম পুরো উত্তরের জন্যই অপেক্ষা করবে; সেখানে মোট সময় আর খরচই আসল মাপ।

মূল উপলব্ধি: Streaming মোট সময় নয়, প্রথম দেখার সময় কমায়। মানুষের জন্য উপকারী, যন্ত্রের জন্য প্রায় অর্থহীন।

প্র ০৩Cache কেন শুধু prompt-এর শুরুর মিলে যাওয়া অংশেই কাজ করে?

উত্তর লুকিয়ে আছে causal attention-এ (পর্ব ৪৯)। Decoder-only model-এ token i-এর প্রতিটি স্তরের key আর value নির্ভর করে শুধু token ১ থেকে i পর্যন্ত, পরের কিছুর ওপর নয়। তাই দুটি prompt-এর প্রথম ১,৫০০ token হুবহু এক হলে ওই ১,৫০০টির KV-ও হুবহু এক, পরে যা-ই আসুক। এটি গাণিতিকভাবে নিখুঁত পুনর্ব্যবহার, আন্দাজ নয়; §৫-এর run-এ cache-সহ আর ছাড়া উত্তর হুবহু মিলেছে।

উল্টোটাও একই কারণে সত্য। Prompt-এর মাঝখানে একটি token বদলালে তার পরের সব token-এর KV বদলে যায়, কারণ তারা বদলানো token-টিকে “দেখে”। তাই মাঝের অংশ আলাদা করে cache করা যায় না। এ কারণেই নিয়ম: স্থির অংশ আগে, পরিবর্তনশীল অংশ শেষে। শুরুতে “আজ ৮ নভেম্বর” লিখলে প্রতিদিনের প্রথম অনুরোধে পুরো cache নতুন করে লিখতে হয়।

Server-এর দিকে এটি এখন সাধারণ কৌশল। Kwon ও সহকর্মীদের ২০২৩ সালের PagedAttention প্রবন্ধ (vLLM) KV cache-কে ছোট block-এ রাখার পদ্ধতি দেয়, যাতে একই prefix-এর block একাধিক অনুরোধে ভাগ করা যায়; SGLang প্রকল্প RadixAttention নামে prefix-গুলোকে একটি গাছে সাজায়। Kimi আর DeepSeek-এর নথিতেও “block” বা “prefix unit” শব্দ আছে, আর অসম্পূর্ণ block cache না হওয়ার কথা। পর্ব ৮৩ · LLM serving-এ এই ব্যবস্থা নিজে চালানো হবে।

মূল উপলব্ধি: Causal attention-এ আগের token পরের token-এর ওপর নির্ভর করে না, তাই মিলে যাওয়া শুরুটুকু নিখুঁতভাবে আবার ব্যবহার করা যায়, মাঝখানটা নয়।

প্র ০৪সব অনুরোধ সবচেয়ে সক্ষম বড় model-এ পাঠানোই কি নিরাপদ পথ?

নিরাপদ মনে হয়, কিন্তু প্রায়ই অপচয়। বাজারঘরের বেশিরভাগ বার্তা সরল: “পার্সেল কবে আসবে”, “বিকাশে দেওয়া যায়?” এগুলো শ্রেণিতে ফেলা বা ছোট নীতি থেকে উত্তর দেওয়া ছোট model-ও পারতে পারে, কম খরচে আর কম সময়ে। কঠিন অংশ (রাগী গ্রাহক, জটিল ফেরত) বড় model বা মানুষের কাছে যেতে পারে। Chen, Zaharia ও Zou-এর ২০২৩ সালের FrugalGPT প্রবন্ধ এ ধরনের “cascade” প্রস্তাব করে: আগে সস্তা model, উত্তরে আস্থা কম হলে দামি model; তাদের পরীক্ষায় খরচ অনেক কমার ফল রিপোর্ট করা হয়, তবে সংখ্যাগুলো তাদের task-এর।

কিন্তু ছোট model বাছাইয়ের দুটি ফাঁদ এই পর্বেই দেখেছেন। এক, বাংলায় ছোট model-এর নির্ভুলতা অনেক কমতে পারে (§৬-এর মাপে)। দুই, “ছোট” মানেই বাংলায় দ্রুত নয়: tokenizer বাংলাকে বেশি token-এ ভাঙলে প্রতি কথায় বেশি pass লাগে। তাই তুলনা করতে হয় প্রতি কাজে: নির্ভুলতা, প্রতি কাজে token, প্রতি কাজে সময়।

Routing-এর নিজের খরচও আছে: কোন বার্তা কঠিন, তা কে ঠিক করবে? একটি শ্রেণিকরণকারী, নিয়ম, বা model-এর নিজের আস্থা, যা প্রায়ই calibrated নয় (পর্ব ৫২)। ভুল route-এ কঠিন প্রশ্ন ছোট model-এ গেলে গ্রাহক ভুল উত্তর পান। তাই routing-ও eval set-এ মাপতে হয়, পর্ব ৬৬-এর পদ্ধতিতে।

মূল উপলব্ধি: Model বাছাই প্রতি কাজের হিসাব: নির্ভুলতা, token আর সময় একসঙ্গে। ছোট model অনেক কাজে যথেষ্ট, কিন্তু বাংলায় তা মেপে নিশ্চিত হতে হয়।

প্র ০৫লম্বা context window থাকলে prompt ছোট করার দরকার কী?

তিনটি কারণে। প্রথম, টাকা আর সময়: প্রতিটি input token প্রতিটি অনুরোধে বিল হয়, আর prefill-এর সময় prompt-এর সঙ্গে বাড়ে (§৪-এ মাপা)। জানালা বড় হলেই ভরিয়ে ফেলা মানে প্রতিবার বেশি দাম।

দ্বিতীয়, মনোযোগ। Liu ও সহকর্মীদের ২০২৩ সালের “Lost in the Middle” প্রবন্ধে কিছু model-এ দেখা যায়, দরকারি তথ্য লম্বা context-এর মাঝখানে থাকলে নির্ভুলতা কমে, শুরু বা শেষে থাকলে ভালো। নতুন model-এ এই প্রভাব কম হতে পারে, কিন্তু সাধারণ শিক্ষা থাকে: অপ্রাসঙ্গিক লেখা বিনা খরচের নয়, তা ভুলের সুযোগও বাড়ায়।

তৃতীয়, রক্ষণাবেক্ষণ। লম্বা prompt-এ পরস্পরবিরোধী নির্দেশ ঢুকে যায়, আর কোন লাইন আসলে কাজ করছে, কেউ জানে না। ছোট prompt পরীক্ষা করা সহজ।

তবে ছোট করাও ঝুঁকির। §৭-এর run-এ উদাহরণ আর সংজ্ঞা ছাঁটার ফল মাপা হয়েছে; কোন লাইন কাটা যায়, তা আন্দাজে নয়, eval set-এ ঠিক করতে হয়। গবেষণায় স্বয়ংক্রিয় উপায়ও আছে: Jiang ও সহকর্মীদের ২০২৩ সালের LLMLingua একটি ছোট model দিয়ে কম তথ্যবহ token বাদ দেয়; ফল task-ভেদে আলাদা। আর caching-এর সঙ্গে হিসাবটি বদলায়: স্থির অংশ cache হলে তার দাম অনেক কম, তখন ছাঁটার লাভ মূলত সময় আর মনোযোগে, টাকায় কম। Prompt compression তাই একটি পরীক্ষা, নিয়ম নয়।

মূল উপলব্ধি: Context window সীমা, লক্ষ্য নয়। যতটুকু কাজে লাগে ততটুকু দিন, আর কতটুকু কাজে লাগে তা মাপুন।

অনুশীলন

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

  1. মাসের খরচ আর cache: একটি রিভিউ-সারাংশ সেবা দিনে ২,০০০টি অনুরোধ পাঠায়, ৩০ দিন। প্রতিটিতে ২,০০০ token-এর স্থির নির্দেশ, ৫০০ token রিভিউ, আর ২০০ token উত্তর। Pin = ২.০০, Pout = ৮.০০ (খরচ-একক, প্রতি ১০ লাখ token; টাকার অঙ্ক নয়)। (ক) Cache ছাড়া মাসের খরচ? (খ) নির্দেশের ৮০% cache থেকে পড়া হয়, পড়া ০.১ গুণ, লেখা ১.২৫ গুণ: নতুন খরচ? (গ) কত hit rate-এর নিচে caching লোকসান?

    R = ২,০০০ × ৩০ = ৬০,০০০. (ক) Input = ৬০,০০০ × ২,৫০০ = ১৫০M token → ১৫০ × ২.০০ = ৩০০. Output = ৬০,০০০ × ২০০ = ১২M → ১২ × ৮.০০ = ৯৬. মোট ৩৯৬.

    (খ) নির্দেশ ৬০,০০০ × ২,০০০ = ১২০M. পড়া ৯৬M × ০.১ × ২.০০ = ১৯.২০; লেখা ২৪M × ১.২৫ × ২.০০ = ৬০.০০; রিভিউ ৩০M × ২.০০ = ৬০.০০. Input ১৩৯.২০, output ৯৬, মোট ২৩৫.২০, প্রায় ৪১% কম।

    (গ) h × (১ − ০.১) = (১ − h) × (১.২৫ − ১) → h = ০.২৫ ÷ ১.১৫ ≈ ০.২২. দাম বা token-সংখ্যার ওপর নির্ভর করে না, শুধু দুই গুণকের ওপর।

  2. আউটপুট অনুমান করুন: কোড না চালিয়ে আউটপুট লিখুন।
    ttft, tps = 0.4, 25.0          # সেকেন্ড, token প্রতি সেকেন্ড
    for n in (1, 26, 251):
        total = ttft + (n - 1) / tps
        print(n, round(total, 2), round(n / total, 1))
    1 0.4 2.5
    26 1.4 18.6
    251 10.4 24.1

    প্রথম token আসে TTFT-এ; বাকি n − ১টি token প্রতিটি ১/২৫ সেকেন্ডে। ২৬ token-এ ০.৪ + ১.০ = ১.৪ সেকেন্ড, গড় গতি ২৬ ÷ ১.৪ ≈ ১৮.৬. লম্বা উত্তরে গড় গতি decode-এর গতির (২৫) কাছে যায়, ছোট উত্তরে TTFT প্রাধান্য পায়। তাই “X tokens/s” একটি সংখ্যা দিয়ে ছোট উত্তরের অভিজ্ঞতা বোঝা যায় না।

  3. নকশা সমালোচনা করুন: একটি দলের পরিকল্পনা: “খরচ কমাতে সবচেয়ে ছোট model নেব। System prompt-এর শুরুতে গ্রাহকের নাম আর আজকের তারিখ বসাব, যাতে উত্তর ব্যক্তিগত হয়। প্রতিটি অনুরোধে পুরো কথোপকথন পাঠাব, কোনো সীমা ছাড়া। খরচ আন্দাজ করব ‘অক্ষর ÷ ৪’ দিয়ে। Streaming চালু থাকবে JSON উত্তরেও।” অন্তত পাঁচটি সমস্যা আর সমাধান বলুন।
    • মাপ ছাড়া ছোট model: বাংলায় নির্ভুলতা কমতে পারে, আর খারাপ tokenizer-এ সময়ও বাড়ে। সমাধান: eval set-এ নির্ভুলতা, প্রতি কাজে token আর সময় মেপে বাছাই।
    • শুরুতে নাম আর তারিখ: প্রতিটি অনুরোধের prefix আলাদা, cache প্রায় কখনো মেলে না। সমাধান: স্থির নীতি আগে, নাম ও তারিখ শেষে।
    • সীমাহীন ইতিহাস: প্রতি অনুরোধে input বাড়তেই থাকে, শেষে context window ছাড়ায়। সমাধান: context বাজেট, শেষ কয়েকটি বার্তা রাখা, পুরনোগুলোর সারাংশ।
    • “অক্ষর ÷ ৪”: বাংলায় ভীষণ ভুল; Qwen2.5-এ বাংলা লেখায় এক token-এ এক অক্ষরেরও কম। সমাধান: নিজের tokenizer বা কোম্পানির token গোনার উপায়, আর usage log.
    • JSON-এ streaming: প্রোগ্রাম পুরো JSON-এর জন্যই অপেক্ষা করে, লাভ নেই, জটিলতা বাড়ে। সমাধান: যন্ত্রের উত্তরে streaming বন্ধ, মানুষের চ্যাটে চালু।
    • গোপনীয়তা: নাম ভাগ করা prefix-এ রাখা ঠিক নয়। শুধু সবার জন্য এক নীতি ভাগ করা অংশে।
  4. TTFT বনাম গতি: Model A: TTFT ০.৩ সেকেন্ড, ৩০ token/s. Model B: TTFT ১.২ সেকেন্ড, ৯০ token/s. (ক) ৩০ token-এর উত্তরে কোনটি আগে শেষ? (খ) কত token-এর বেশি হলে B আগে শেষ করে?

    (ক) A: ০.৩ + ২৯ ÷ ৩০ ≈ ১.২৭ সেকেন্ড; B: ১.২ + ২৯ ÷ ৯০ ≈ ১.৫২ সেকেন্ড। A আগে।

    (খ) সমান হলে ০.৩ + (n − ১)/৩০ = ১.২ + (n − ১)/৯০ → (n − ১)(১/৩০ − ১/৯০) = ০.৯ → (n − ১) × ২/৯০ = ০.৯ → n − ১ = ৪০.৫, n ≈ ৪১.৫. অর্থাৎ ৪২ token বা বেশি হলে B আগে শেষ। ছোট উত্তরের chat-এ A, লম্বা লেখায় B; আর streaming-এ প্রথম শব্দ সবসময় A-তে আগে দেখা যায়।

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

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

ক্রমএকটি streaming অনুরোধে ঘটনাগুলো সময়ের ক্রমে সাজান (▲▼ চেপে সরান)

  1. অনুরোধ server-এ পৌঁছায়, আর দরকার হলে সারিতে অপেক্ষা করে
  2. Prompt-এর cache-এ মেলা শুরুটুকু খুঁজে নেওয়া হয়
  3. বাকি prompt-এর prefill চলে আর KV cache ভরে
  4. প্রথম token ব্যবহারকারীর কাছে আসে (TTFT)
  5. একটি একটি করে বাকি token আসে, শেষে usage-এর হিসাব

১,০০০টি অনুরোধ, প্রতিটিতে ৩,০০০ input আর ৫০০ output token. Pin = ১, Pout = ৪ (খরচ-একক, প্রতি ১০ লাখ token-এ)। মোট খরচ কত একক?

  • ৩.০
  • ৩.৫
  • ৫.০
  • ১৪.০

Prompt caching-এ লাভ পেতে prompt-এর কোন বিন্যাস ঠিক?

  • শুরুতে আজকের তারিখ, তারপর নীতি, শেষে প্রশ্ন
  • শুরুতে স্থির নীতি আর উদাহরণ, শেষে গ্রাহকের নাম আর প্রশ্ন
  • স্থির নীতি মাঝখানে, যাতে দুই পাশে বদলানো অংশ থাকে
  • প্রতিবার নীতির লাইনগুলো এলোমেলো ক্রমে

Cache থেকে পড়া token ০.১ গুণ দামে, cache-এ লেখা ১.২৫ গুণ দামে। কোন hit rate-এর ওপরে caching লাভজনক?

  • ১০%
  • ৯০%
  • ৫০%
  • প্রায় ২২%

লম্বা system prompt সহ একটি chatbot-এ TTFT বেশি, কিন্তু প্রথম token-এর পরের গতি ঠিক আছে। সবচেয়ে সম্ভাব্য কারণ আর প্রতিকার কোনটি?

  • Prefill-এর সময় বেশি; prompt ছোট করা বা prefix cache
  • max output token কম করা
  • Temperature কমানো
  • Streaming বন্ধ করা

একই বাংলা লেখা tokenizer X-এ ৫১৪ token, Y-তে ১৯২ token. দুই model-এর প্রতি 1M token-এর দাম সমান। কোন বক্তব্যটি ঠিক?

  • X বাংলা ভালো বোঝে, কারণ বেশি token-এ ভাঙে
  • দাম সমান, তাই খরচও সমান
  • এই লেখায় X-এ প্রায় ২.৭ গুণ খরচ; মান আলাদা মাপতে হবে
  • দুই tokenizer-এর সংখ্যা তুলনা করা যায় না

৪০টি বার্তায় লম্বা prompt-এর নির্ভুলতা ০.৭৫, ছোট prompt-এর ০.৭০, একই বার্তায়; ছোট prompt-এ input token ৭০% কম। সবচেয়ে যুক্তিসঙ্গত পরের কাজ কোনটি?

  • ছোট prompt নির্ভুলতা কমায়, তাই বাদ
  • জোড়া পার্থক্যের CI দেখা, দরকারে বেশি বার্তায় মাপা
  • শুধু token দেখা, নির্ভুলতা নয়
  • একটি ভাষ্যের ফল সব model-এ সাধারণ নিয়ম হিসেবে লেখা

শব্দকোষ

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

মনে রাখুন

  1. Token-ই একক: টাকা, সময় আর context-এর জায়গা, তিনটিই token-এ। একই বাংলা লেখা tokenizer-ভেদে কয়েক গুণ আলাদা token; আন্দাজ নয়, নিজে গুনুন।
  2. খরচ = (Tin × Pin + Tout × Pout) ÷ 106, প্রতি অনুরোধে, আর প্রতিটি অনুরোধে পুরো ইতিহাস আবার input. দাম আর গুণক নথি থেকে, তারিখসহ।
  3. Prompt caching মেলে শুধু হুবহু শুরু থেকে: স্থির অংশ আগে। লেখার বাড়তি দাম থাকলে ভারসাম্যের hit rate = (w − 1) ÷ ((w − 1) + (1 − r)).
  4. Latency = TTFT + (N − 1) ÷ r. TTFT-এ prefill, যা prompt-এর দৈর্ঘ্যে বাড়ে; r decode-এর গতি। Streaming প্রথম দেখার সময় কমায়, মোট সময় নয়।
  5. ছোট prompt, ছোট model, batch সবই খরচ কমায়, কিন্তু নির্ভুলতা eval set-এ জোড়া CI-সহ মেপে তবেই সিদ্ধান্ত।

পরের পর্বে: পর্ব ৬৬ · Prompt পরীক্ষা: eval set, regression আর failure mode. এই মডিউলের সব কৌশল কীভাবে একটি স্থির পরীক্ষা-ব্যবস্থায় মাপা হয়, prompt বা model বদলালে কী ভাঙল তা ধরতে।

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

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