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

LLM-এর ভেতরে — tokenizer থেকে logits, context window আর hallucination

Inside an LLM: From Tokenizer to Logits, Context Window and Hallucination

Taught in Bangla, this lesson traces a prompt through chat template, token ids, embeddings, blocks, logits and softmax, printing shapes in an in-page toy pipeline and a real model. Real desktop CPU runs show silent template and truncation errors, why models miscount letters, how fluent wrong answers arise and what log-probabilities reveal about hallucination. It also covers context window arithmetic, knowledge cutoff, calibration and the logit lens on GPT-2.

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

  • একটি prompt-এর পুরো পথ, প্রতিটি ধাপে tensor-এর shape আর KV cache-সহ
  • Chat template আর special token নিজে print করা, আর template-এর ভুল ধরা
  • Context window-এর হিসাব, আর পুরনো বার্তা নিরাপদে বাদ দেওয়া
  • অক্ষর গোনায় ভুল, knowledge cutoff আর hallucination-এর যান্ত্রিক কারণ
  • নিজে মাপা ডেটায় calibration আর GPT-2-তে logit lens

এই পর্বে দুটি সত্যিকারের model চলবে: Qwen/Qwen2.5-0.5B-Instruct (Apache 2.0, প্রায় ৪৯ কোটি parameter) আর openai-community/gpt2 (MIT); template-এর তুলনায় SmolLM2-135M-Instruct-এর tokenizer (Apache 2.0). সবই token ছাড়া নামানো যায়; model card আর লাইসেন্স দেখার নিয়ম AI-এর ভিত্তি কোর্সের পর্ব ৭১-এ। Desktop-এর সব run CPU-তে, torch 2.15.0, transformers 5.22.2, Python 3.12, লেখার সময়ে। ফল একটি স্থানীয় run-এর; ছোট model দিয়ে বড় পণ্যের মান বিচার করা যায় না। LLM-এর মূল ধারণা AI-এর ভিত্তি কোর্সের পর্ব ৭০-এ আছে; এখানে আমরা ভেতরে ঢুকব।

১ · একটি প্রশ্নের পুরো পথ

কুরিয়ারের একটি পার্সেলের কথা ভাবুন। আপনি ঠিকানা লিখলেন (বার্তা)। কাউন্টারে নির্দিষ্ট ফরমে তোলা হলো (template). বারকোড বসল (token id). এরপর কয়েকটি হাব পার হলো (block), আর শেষে একজন সিদ্ধান্ত নিলেন, কোন ভ্যানে যাবে (logits থেকে পরের token). কাউন্টারে ফরম ভুল হলে পরের সব হাব ঠিকঠাক কাজ করেও পার্সেল ভুল জায়গায় যায়। কেউ অভিযোগও করে না। LLM-এর অনেক ভুল ঠিক এই ধরনের।

One prompt through an LLM with shapes messageschat templatetokenizer embedding24 blocksnorm, lm_headsoftmax, pick KV cache 202 chars(1, 41) (1, 41, 896)(1, 41, 151936)next id (1, 1) After step 1 only the new token enters
একটি chat বার্তা থেকে পরের token, আমাদের run-এর shape-সহ। প্রথম ধাপের পর শুধু নতুন token ঢোকে, কারণ K আর V জমা থাকে।

নিচের stepper-এর সব সংখ্যা সত্যিকারের run থেকে। প্রশ্ন: “What is the capital of Bangladesh? Answer in one word.” শেষ ধাপটি মন দিয়ে দেখুন।

এই ধাপে
ধাপ
Shape
ভেতরে
  1. বার্তা. আপনার লেখা ৫৪টি অক্ষর। Model এখনো কিছুই পায়নি।
  2. Chat template. Tokenizer-এর সঙ্গে থাকা নিয়ম বার্তাটিকে একটি লেখায় সাজায়। আপনি system বার্তা না দিলেও Qwen-এর template নিজে একটি বসায়। লেখা এখন ২০২ অক্ষর।
  3. Tokenizer. লেখা হলো ৪১টি id. শেষের পাঁচটি id-ই বলে “এবার assistant-এর পালা”। Shape (1, 41): batch ১, দৈর্ঘ্য ৪১।
  4. Embedding. প্রতিটি id একটি ৮৯৬ মাত্রার vector হয়। এটি একটি table থেকে পড়া মাত্র; এখনো প্রসঙ্গ মেশেনি।
  5. Block. ২৪টি block একই shape রাখে, ভেতরের সংখ্যা বদলায়। প্রতিটি স্তর K আর V জমায়। Key-র head ২টি (grouped-query), দৈর্ঘ্য ৪১, মাত্রা ৮৯৬ ÷ ১৪ = ৬৪।
  6. Logits আর softmax. lm_head প্রতিটি অবস্থানে ১,৫১,৯৩৬টি score দেয়। পরের token-এর জন্য লাগে শুধু শেষ সারি। Softmax-এর পর সবচেয়ে বেশি পেল 'Ch', ০.৫২। “Dhaka”-র শুরু 'D' আর ' Dh' অনেক পেছনে।
  7. পরের token. Greedy নিল 'Ch'. পরের ধাপে শুধু এই একটি id ঢোকে, shape (1, 1). Model লিখল Chittagong, তারপর <|im_end|> দিয়ে থামল। পুরো শিকল ঠিক চলেছে, উত্তর ভুল। কোনো ধাপে error নেই।

ঢাকা বাংলাদেশের রাজধানী; এই ছোট model-এর উত্তরটি ভুল। অন্য model হয়তো ঠিক বলবে, তাই এটি “LLM কত খারাপ”-এর মাপ নয়। এটি দেখায়, পথের কোনো ধাপে সত্য যাচাইয়ের কিছু নেই। অংশ ৭-এ এই বিষয়ে ফিরব।

২ · Chat template আর special token

Model আসলে “বার্তা” চেনে না, চেনে একটি লম্বা লেখা। Instruction tuning-এর সময় যে ছাঁচে উদাহরণ দেখানো হয়েছে, চালানোর সময়ও সেই ছাঁচ দরকার। Chat template সেই ছাঁচ। এর সংকেতগুলো special token: লেখার অংশ নয়, সীমানার চিহ্ন। পর্ব ৪০ · Tokenization-এ এদের round-trip দেখেছেন; এবার কাজের জায়গায়।

~/bazarghar-dl$ uv run python template.py     # সত্যিকারের run (একটু ছোট করা)
vocab + added: 151665 | truncation_side: right | padding_side: right
eos: <|im_end|> 151645 | pad: <|endoftext|> 151643 | bos: None
'<|im_start|>' -> 151644
--- user only, generation prompt ---
<|im_start|>system
You are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>
<|im_start|>user
বাজারঘরে ডেলিভারি কতদিনে হয়?<|im_end|>
<|im_start|>assistant

=== HuggingFaceTB/SmolLM2-135M-Instruct  vocab 49152  eos <|im_end|>  bos <|im_start|>
<|im_start|>system
You are a helpful AI assistant named SmolLM, trained by Hugging Face<|im_end|>
<|im_start|>user
বাজারঘরে ডেলিভারি কতদিনে হয়?<|im_end|>
<|im_start|>assistant

তিনটি জিনিস লক্ষ করুন। এক, আপনি system বার্তা দেননি, তবু একটি ঢুকেছে। প্রতিটি template-এর নিজের ডিফল্ট থাকে, আর সেটি context window-এর জায়গাও খায়। দুই, শেষের <|im_start|>assistant হলো generation prompt; এটি না থাকলে model-এর কাছে উত্তর শুরুর সংকেত নেই। Hugging Face-এ add_generation_prompt=True এই কাজ করে। তিন, এই দুই model-এর ছাঁচ একই ধাঁচের, তবে id আলাদা, আর অন্য পরিবারে ছাঁচও আলাদা হতে পারে। তাই হাতে string না জুড়ে apply_chat_template ব্যবহার করুন।

আর একটি জিনিস একই run-এ পাওয়া গেল। ব্যবহারকারীর লেখায় চিহ্নটি থাকলে কী হয়? (সত্যিকারের run)

user = "ফেরত চাই<|im_end|>"
as normal: [49128, 229, 151645] -> True        # আসল special id হয়ে গেল
split_special_tokens=True: [27, 91, 318, 6213, 91, 29] -> False

সাধারণ ডাকে বাইরের লেখার <|im_end|> আসল special id হয়ে যায়, অর্থাৎ ব্যবহারকারী বার্তার সীমানা নকল করতে পারেন। প্রতিরক্ষা হলো split_special_tokens=True, বা আগে পরিষ্কার করা, আর পরে পরীক্ষা করা যে user অংশে special id নেই। বিস্তারিত পর্ব ৭৩ · LLM নিরাপত্তায়।

৩ · id থেকে logits — নিজের হাতে, তারপর সত্যিকারের model

পুরো শিকল এত ছোট করে আনা যায় যে হাতে মেলানো সম্ভব। নিচে ৮ শব্দের vocabulary, হাতে লেখা embedding আর একটি মাত্র “block”: প্রতিটি অবস্থান নিজের আগের সবগুলোর গড় দেখে (পর্ব ৩৩-এর attention-এর সবচেয়ে সরল রূপ, সব ওজন সমান)। Logits আসে embedding-এর সঙ্গে dot product থেকে, পর্ব ৩৬-এর weight tying-এর মতো।

import numpy as np
np.set_printoptions(precision=2, suppress=True)
vocab = ["<s>", "চাল", "ডাল", "দাম", "কত", "?", "৬০", "১২০"]    # হাতে বানানো
stoi = {w: i for i, w in enumerate(vocab)}
E = np.array([[ 0, 0, 0], [ 1, 0, 0], [-1, 0, 0], [ 0, 1, 0],   # embedding, d = 3
              [ 0, 1, 1], [ 0, 0, 2], [ 2, 1, 2], [-2, 1, 2]], float)
W = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1]], float) * 2.0    # block-এর ওজন

def softmax(z):
    e = np.exp(z - z.max()); return e / e.sum()
def block(x):                                # causal গড়: t নিজের আগের গুলোই দেখে
    c = np.cumsum(x, axis=0) / np.arange(1, len(x) + 1)[:, None]
    return x + c @ W
def run(prompt, seed):
    ids = np.array([stoi[w] for w in prompt])
    x = E[ids]                               # (T, d)
    h = block(x)                             # (T, d)
    logits = h @ E.T                         # (T, V): weight tying
    p = softmax(logits[-1])                  # শুধু শেষ সারি
    tok = np.random.default_rng(seed).choice(len(vocab), p=p)
    return ids, x, h, logits, p, vocab[tok]

prompt = ["<s>", "চাল", "দাম", "কত", "?"]
ids, x, h, logits, p, nxt = run(prompt, seed=52)
print("ids     ", ids, "shape", ids.shape)
print("x[-1]   ", x[-1], "shape", x.shape)
print("h[-1]   ", h[-1], "shape", h.shape)
print("logits  ", logits[-1], "shape", logits.shape)
print("p       ", p.round(3), "sum", round(p.sum(), 3))
print("greedy  ", vocab[int(p.argmax())], "| sampled (seed 52):", nxt)
print("ডাল দিলে:", vocab[int(run(["<s>", "ডাল", "দাম", "কত", "?"], 52)[4].argmax())])
n = 2000
draws = np.random.default_rng(1).choice(len(vocab), n, p=p)
print("২০০০ বার sampling, ১২০ এসেছে:", int((draws == stoi["১২০"]).sum()), "| p×n =", round(p[stoi["১২০"]] * n, 1))

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

ids      [0 1 3 4 5] shape (5,)
x[-1]    [0. 0. 2.] shape (5, 3)
h[-1]    [0.4 0.8 3.2] shape (5, 3)
logits   [ 0.   0.4 -0.4  0.8  4.   6.4  8.   6.4] shape (5, 8)
p        [0.   0.   0.   0.   0.01 0.14 0.7  0.14] sum 1.0
greedy   ৬০ | sampled (seed 52): ৬০
ডাল দিলে: ১২০
২০০০ বার sampling, ১২০ এসেছে: 282 | p×n = 283.6

হাতে মিলিয়ে নিন। গড় c = (০ + ১ + ০ + ০ + ০, ০ + ০ + ১ + ১ + ০, ০ + ০ + ০ + ১ + ২) ÷ ৫ = (০.২, ০.৪, ০.৬)। তাই h = (০, ০, ২) + ২c = (০.৪, ০.৮, ৩.২)। “৬০”-এর logit: ০.৪ × ২ + ০.৮ × ১ + ৩.২ × ২ = ৮.০। “চাল” বদলে “ডাল” দিলেই গড় বদলায়, উত্তরও। Model-এর কাছে “দাম” কোনো জিনিস নয়, কেবল সংখ্যার সারি। তবু সবচেয়ে বড় সম্ভাবনা ০.৭, আর ১.০ নয়; মাঝে মাঝে ১২০-ও আসবে।

সত্যিকারের model-এ একই ধাপ

Forward hook দিয়ে প্রতিটি অংশের output ধরা হয়েছে। হাতে লেখা শিকলের সঙ্গে মিলিয়ে পড়ুন। সত্যিকারের run, কয়েকটি লাইন বাদ দেওয়া।

torch 2.15.0+cpu | transformers 5.22.2
layers 24 | d_model 896 | heads 14 | kv_heads 2 | vocab 151936 | max_pos 32768
params 494032768
tied: True
[2] ids shape: (1, 41) first 6: [151644, 8948, 198, 2610, 525, 1207]
[3] embed       shape (1, 41, 896)  last-position norm 0.6
[3] block 0     shape (1, 41, 896)  last-position norm 3.3
[3] block 11    shape (1, 41, 896)  last-position norm 14.4
[3] block 23    shape (1, 41, 896)  last-position norm 71.8
[3] final norm  shape (1, 41, 896)  last-position norm 242.0
[3] lm_head     shape (1, 41, 151936)  last-position norm 1365.7
[5] raw logit min/max: -15.67 16.96
    id   1143  'Ch'            logit  16.96  p 0.5207
    id     35  'D'             logit  14.97  p 0.0714
    id  43227  ' Dh'           logit  14.78  p 0.0586
    id  62819  'Bang'          logit  14.78  p 0.0586
    id  93793  'Islam'         logit  14.36  p 0.0387
[6] sum p = 1.0001 | chosen greedy id: 1143
[7] cache layers: 24 | key shape layer0: (1, 2, 41, 64)
[8] step 2 input shape: (1, 1) | logits shape (1, 1, 151936) | max abs diff vs full pass: 2.19e-05
    argmax same: True | next token: 'itt'
    key shape after step 2: (1, 2, 42, 64)
[9] greedy reply: 'Chittagong<|im_end|>'
    sample seed 52: 'Wittchs.<|im_end|>'
    generation_config defaults: {'do_sample': True, 'temperature': 0.7, 'top_p': 0.8, 'top_k': 20, 'repetition_penalty': 1.1}
[10] prompt 250 tokens + 60 new, use_cache=True: 4.98 s
[10] prompt 250 tokens + 60 new, use_cache=False: 50.15 s

পড়ার নিয়ম: Logit-এর ফারাক ১.৯৯ (১৬.৯৬ − ১৪.৯৭) হলে সম্ভাবনার অনুপাত e১.৯৯ ≈ ৭.৩; সত্যিই ০.৫২০৭ ÷ ০.০৭১৪ ≈ ৭.৩। Softmax মূলত এই ফারাকই সম্ভাবনায় নেয়। Probability-র যোগফল ১.০০০১ ঘটেছে float32-এর গোলকরণে, এটি ভুল নয়।

Cache: পর্ব ৩৭-এর দাবি এখানে মিলল: দ্বিতীয় ধাপে input (1, 1), key-এর দৈর্ঘ্য ৪১ থেকে ৪২, logit-এর পার্থক্য সর্বোচ্চ ২.১৯ × ১০−৫, যা float32-এর গোলকরণের মাত্রায়, আর পরের token এক। ২৫০ token-এর prompt-এ ৬০টি নতুন token: cache-সহ ৪.৯৮ সেকেন্ড, ছাড়া ৫০.১৫ সেকেন্ড। CPU-তে একটি run, মেশিনে অন্য কাজও চলছিল।

ফাঁদ: generation_config-এর ডিফল্টে sampling চালু (temperature ০.৭, repetition penalty ১.১)। generate()-এ কিছু না বললে এগুলো খাটে। Temperature আর top-p-এর বিস্তারিত পর্ব ৫০ · Decoding-এ। Seed ৫২-এর sample “Wittchs.” দেখায়, T = ১-এ বিশুদ্ধ sampling কত এলোমেলো হতে পারে।

৪ · Context window — হিসাব আর truncation

Context window হলো একবারে সামলানো token-এর সীমা। ভুল বোঝাবুঝি হয় তিন জায়গায়। Window token-এ মাপা, অক্ষরে নয়। Model-এর উত্তরও সাধারণত এর মধ্যে পড়ে। আর model-এর নিজের কোনো “স্মৃতি” নেই: chat মনে থাকে কারণ আপনার কোড প্রতিবার পুরো ইতিহাস আবার পাঠায়। চায়ের দোকানের ছোট খাতার মতো: পাতা ভরলে পুরনো হিসাব কেটে দিতেই হবে।

হিসাবের খাতা (token)
Input-এর বাজেট
স্থির অংশ
ইতিহাস
মোট
ফল
  1. আগে উত্তরের জায়গা. Window ৪,০৯৬। উত্তরের জন্য ৫১২ রাখতে হবে, নইলে উত্তর মাঝপথে কাটা যেতে পারে। Input-এর বাজেট ৩,৫৮৪।
  2. যা বাদ যাবে না. System বার্তা ১২০, নতুন প্রশ্ন ৪০। মোট ১৬০; ইতিহাসের জন্য থাকে ৩,৪২৪।
  3. ইতিহাস ভরে গেছে. ৩০টি পুরনো বার্তা, প্রতিটি ১২০ token. মোট ৩,৭৬০, বাজেটের চেয়ে ১৭৬ বেশি।
  4. সবচেয়ে পুরনো জোড়া বাদ. একটি প্রশ্ন আর তার উত্তর, ২৪০ token. মোট ৩,৫২০, ফাঁকা ৬৪। জোড়া ধরে বাদ দিলে উত্তর একা ঝুলে থাকে না।
  5. পরের পালা. আগের প্রশ্ন (৪০) আর উত্তর (ধরুন ৩০০) ইতিহাসে ঢুকল; নতুন প্রশ্ন ৪০। মোট হতো ৩,৮৬০, ২৭৬ বেশি। তাই আরও দুটি জোড়া (৪৮০) বাদ: ৩,৩৮০, ফাঁকা ২০৪। Chat যত লম্বা, পুরনো তথ্য তত হারায়।

এবার সেই হিসাব কোডে। একটি বিপদ লক্ষ করুন: ব্যবহারকারী শুরুতে অর্ডার নম্বর বলেছিলেন। tokens() একটি ছোট বাংলা token-গোনার ফাংশন: ভান্ডারে শব্দ পেলে এক token, না পেলে প্রতি অক্ষরের UTF-8 byte আলাদা, যেভাবে পর্ব ৪০-এ byte-level BPE বাংলা ভাঙে।

VOCAB = {"অর্ডার", "নম্বর", "বাজার", "ঘর", "কত", "আমার", "ডেলিভারি", "ঢাকা", "দাম", "ফেরত"}
def tokens(text):                          # longest match, না মিললে UTF-8 byte
    out, i = [], 0
    while i < len(text):
        hit = max((v for v in VOCAB if text.startswith(v, i)), key=len, default=None)
        if hit: out.append(hit); i += len(hit)
        elif text[i] == " ": i += 1        # সরলতার জন্য space বাদ
        else: out += [f"b{b:02x}" for b in text[i].encode()]; i += 1
    return out
def count(m): return len(tokens(m["t"])) + 4   # প্রতি বার্তায় template-এর ৪ token

chat = [dict(r="system", t="বাজারঘর সহকারী"),
        dict(r="user", t="আমার অর্ডার নম্বর BG417"), dict(r="asst", t="ধন্যবাদ"),
        dict(r="user", t="ডেলিভারি কত দিনে"), dict(r="asst", t="ঢাকা ২ দিন"),
        dict(r="user", t="ফেরত কত দিনে"), dict(r="asst", t="৭ দিন"),
        dict(r="user", t="আমার অর্ডার নম্বর কত")]
def fit(chat, window, reserve):
    budget = window - reserve
    keep = list(chat); dropped = []
    while sum(map(count, keep)) > budget and len(keep) > 2:
        dropped.append(keep.pop(1))        # system রেখে সবচেয়ে পুরোনো
    return keep, dropped
for m in chat: print(f"{m['r']:6} {count(m):>3}  {m['t']}")
print("মোট:", sum(map(count, chat)), "token | 'বাজারঘর':", tokens("বাজারঘর"), "| 'ধন্য':", len(tokens("ধন্য")))
for w in (160, 120, 70):
    keep, drop = fit(chat, w, 16)
    used = sum(map(count, keep))
    print(f"\nwindow {w}, উত্তরের জন্য 16 | input-এর বাজেট {w-16}, ব্যবহার {used}")
    print(" বাদ:", [m["t"] for m in drop] or "কিছু না")
    print(" BG417 এখনো আছে?", any("BG417" in m["t"] for m in keep))

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

system  24  বাজারঘর সহকারী
user    12  আমার অর্ডার নম্বর BG417
asst    25  ধন্যবাদ
user    18  ডেলিভারি কত দিনে
asst    17  ঢাকা ২ দিন
user    18  ফেরত কত দিনে
asst    16  ৭ দিন
user     8  আমার অর্ডার নম্বর কত
মোট: 138 token | 'বাজারঘর': ['বাজার', 'ঘর'] | 'ধন্য': 12

window 160, উত্তরের জন্য 16 | input-এর বাজেট 144, ব্যবহার 138
 বাদ: কিছু না
 BG417 এখনো আছে? True

window 120, উত্তরের জন্য 16 | input-এর বাজেট 104, ব্যবহার 101
 বাদ: ['আমার অর্ডার নম্বর BG417', 'ধন্যবাদ']
 BG417 এখনো আছে? False

window 70, উত্তরের জন্য 16 | input-এর বাজেট 54, ব্যবহার 48
 বাদ: ['আমার অর্ডার নম্বর BG417', 'ধন্যবাদ', 'ডেলিভারি কত দিনে', 'ঢাকা ২ দিন', 'ফেরত কত দিনে']
 BG417 এখনো আছে? False

Window ১২০-এ প্রথম জোড়াই বাদ গেল, আর সেখানেই ছিল BG417. শেষ প্রশ্ন “আমার অর্ডার নম্বর কত” এখনো model-এর কাছে যাচ্ছে, উত্তরটি আর যাচ্ছে না। কোনো error নেই। একটি ছোট model তখন উত্তর বানিয়ে দিতে পারে। “ধন্যবাদ” ২৫ token নিল, কারণ শব্দটি ভান্ডারে নেই, আর বাংলার প্রতিটি code point ৩ byte (খেলনা হিসাব)। প্রতিকার: দরকারি তথ্য (অর্ডার নম্বর) আলাদা করে system-এর কাছে রাখা বা ডেটাবেস থেকে আনা, পুরনো অংশ সারাংশ করা, বা RAG.

সত্যিকারের tokenizer-এর ফাঁদ

১৪টি বার্তার একটি বাংলা chat, ৩৬৫ token. সরাসরি max_length=60 দিয়ে truncation করলে দুই দিকে কী হয়? সত্যিকারের run; প্রথম লাইনটি ডিফল্ট, ডান দিক কাটা।

full chat tokens: 365
default truncation max_length=60, kept 60 tail: '�়?<|im_end|>\n<|im_start|>assistant\nঅর্ডা�'
left truncation, kept 60 head: '� 6 ঢাকায় �'
left truncation tail: '�ে দেব?<|im_end|>\n<|im_start|>assistant\n'

ডান দিক কাটলে শেষে থাকে প্রথম দিকের একটি উত্তরের ভাঙা টুকরো। শেষ প্রশ্ন আর generation prompt, দুটোই গেছে। বাঁ দিক কাটলে শেষটা ঠিক থাকে, কিন্তু system বার্তা হারায়, আর শুরুতে বার্তার মাঝখানে একটি অক্ষরও ভেঙে যায়। এই পরীক্ষায় � সেই ভাঙা byte. তাই token ধরে নয়, পুরো বার্তা ধরে বাদ দিন, তারপর template বসান। বাংলার দামও মাপা: একই অর্থের একটি বাক্য ইংরেজিতে ১১০ অক্ষর, ২৬ token; বাংলায় ৮৯ অক্ষর, ৯১ token (এই tokenizer-এ, একটি বাক্যে)।

পণ্যের window, তাদের নিজের নথিতে

২০২৬ সালের নভেম্বরে অফিসিয়াল নথিতে যেমন দেখা গেছে। এগুলো পরিবর্তন হতে পারে, আর এই তালিকা কোনো ক্রম নয়; বড় window মানে ভালো উত্তর, এমনও নয়।

Model (নথির নাম)Context windowনথিতে লেখা cutoff
Claude Opus 5.5 (Anthropic)1M tokenReliable knowledge: Jun 2026
GPT-6.1 Terra (OpenAI)1.05M tokenJun 1, 2026
Gemini 4 Argon (Google)1M tokenযে পাতা পড়েছি, সেখানে নেই
Grok 4.7 (xAI)500k tokenMay 2026
Kimi K3 সিরিজ (Moonshot AI)1,048,576 tokenযে পাতা পড়েছি, সেখানে নেই
DeepSeekযে পাতা পড়েছি, সেখানে মান নেই; input আর output মিলিয়ে সীমার কথা আছেযে পাতা পড়েছি, সেখানে নেই
Qwen2.5-0.5B-Instruct (আমাদের run)max_position_embeddings 32768এখানে যাচাই করিনি

সীমা পার হলে কী হয়, সেটিও পণ্যভেদে আলাদা। Anthropic-এর নথি অনুযায়ী input একাই window ছাড়ালে API error দেয়, আর chat ইন্টারফেস পুরনো অংশ নিজে সামলাতে পারে। নিজের কোডে তাই নিজের সিস্টেমের নথি পড়ুন, আর token গুনুন।

৫ · অক্ষর গুনতে কেন ভুল হয়

একটি প্রশ্ন LLM-এর দুর্বলতা হিসেবে বিখ্যাত হয়েছে: “strawberry”-তে কয়টি r? কারণ বুঝতে আগে দেখুন, model আসলে কী পায়। নিচে সত্যিকারের run, greedy, কয়েকটি লাইন ছোট করা।

strawberry -> ['str', 'aw', 'berry']
banana -> ['banana']
bazarghar -> ['baz', 'arg', 'har']
mississippi -> ['miss', 'issippi']
বাজারঘর -> codepoints 7  utf8 bytes 21  tokens 8
ডেলিভারি -> codepoints 8  utf8 bytes 24  tokens 8

('strawberry', 'r', 3, '1', '1')     # শব্দ, অক্ষর, আসল গোনা, জোড়া শব্দে উত্তর, আলাদা অক্ষরে উত্তর
('banana', 'a', 3, '1', '1')
('mississippi', 's', 4, '3', '1')
('delivery', 'e', 2, '1', '1')
English n=20: joined word correct 3, spaced letters correct 3, always-say-1 baseline 3
Bangla n=15, 60 new tokens: gave a number 2, correct 0, always-say-1 baseline 6
spell: strawberry, s, r, w, r, b, t, r, i, l, e

Model-এর কাছে “strawberry” তিনটি id, “banana” একটি। এর ভেতরে অক্ষর সাজানো নেই। বানানের জ্ঞান থাকলে সেটি training-এ পরোক্ষে শেখা; id থেকে সরাসরি পড়া যায় না। ফল: ২০টি ইংরেজি প্রশ্নে ৩টি ঠিক, “সব সময় ১ বলা”-র সমান। সত্যি বলতে, আমরা অক্ষরগুলো আলাদা করে দিয়েও (s t r a w…) ৩টিই পেয়েছি। অর্থাৎ এই ছোট model-এ শুধু tokenization দায়ী নয়; গোনা নিজেও সে পারছে না। বানান চাইলেও “strawberry”-র অক্ষর ভুল এলো। বাংলায় ৬০ token পর্যন্ত দিয়েও ১৫টির ২টিতে মাত্র সংখ্যা এলো, আর একটিও ঠিক নয়। এই ফল একটি ছোট model আর ছোট প্রশ্নতালিকার; বড় model ভালো করতে পারে, তবে সে ক্ষেত্রেও যাচাই করে নিতে হয়।

Token কেন একটি বাধা, তা আলাদা করে মাপতে একটি খেলনা পরীক্ষা নিন। একই শব্দ, একই সরল “model” (প্রতি id-এর জন্য একটি সংখ্যা শেখে, least squares), শুধু input আলাদা: token id, না অক্ষরের id. মাত্র ২৫টি উদাহরণে শেখানো হলো। এটি LLM নয়; এটি শুধু দেখায়, অক্ষর-প্রতি id থাকলে তথ্যটি কত সহজে পাওয়া যায়।

import numpy as np
chunks = ("বাজার ঘর রিভিউ ডেলিভারি চাল ডাল তরকারি বাক্স পণ্য রসিদ দাম কম বেশি শহর নগর গ্রাম মাছ ফল "
          "সবজি কাপড় জামা জুতা ব্যাগ ফোন চার্জার কুকার হাঁড়ি থালা বাটি পরিবার সরকার ব্যবসা খরচ হিসাব "
          "খাতা দোকান মালিক গ্রাহক অর্ডার রাজশাহী রংপুর বরিশাল").split()
rng = np.random.default_rng(52)
zipf = 1 / np.arange(1, len(chunks) + 1); zipf /= zipf.sum()     # কিছু token বিরল
def word():
    return tuple(rng.choice(len(chunks), rng.integers(2, 4), p=zipf).tolist())
data = [word() for _ in range(160)]
text = ["".join(chunks[i] for i in w) for w in data]
y = np.array([t.count("র") for t in text], float)
chars = sorted(set("".join(text)))
cid = {c: i for i, c in enumerate(chars)}
def bag_token(w):
    v = np.zeros(len(chunks)); np.add.at(v, list(w), 1); return v
def bag_char(t):
    v = np.zeros(len(chars)); np.add.at(v, [cid[c] for c in t], 1); return v
tr, te = np.arange(25), np.arange(25, 160)                       # মাত্র ২৫টি শেখার উদাহরণ
for name, X in (("token ids", np.array([bag_token(w) for w in data])),
                ("char ids ", np.array([bag_char(t) for t in text]))):
    b = np.linalg.lstsq(X[tr], y[tr], rcond=None)[0]             # id-প্রতি একটি সংখ্যা শেখে
    pred = np.round(X[te] @ b)
    print(f"{name}: vocab {X.shape[1]:>2} | test ঠিক {(pred == y[te]).mean():.3f}")
seen = set(i for w in [data[j] for j in tr] for i in w)
X = np.array([bag_token(w) for w in data]); b = np.linalg.lstsq(X[tr], y[tr], rcond=None)[0]
pred = np.round(X[te] @ b)
has_new = np.array([any(i not in seen for i in data[j]) for j in te])
print("token: না-দেখা token আছে এমন শব্দ", int(has_new.sum()), "টি, ঠিক", round((pred == y[te])[has_new].mean(), 3),
      "| বাকি", int((~has_new).sum()), "টি, ঠিক", round((pred == y[te])[~has_new].mean(), 3))
j = te[np.where(pred != y[te])[0][0]]
print("ভুলের উদাহরণ:", [chunks[i] for i in data[j]], "ids", list(data[j]), "| আসল র:", int(y[j]), "| অনুমান:", int(np.round(X[j] @ b)))
print("গণনার সাধারণ কোড (str.count):", text[j].count("র"))
print("baseline, সবসময় গড়:", round((np.round(y[tr].mean()) == y[te]).mean(), 3))

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

token ids: vocab 42 | test ঠিক 0.881
char ids : vocab 37 | test ঠিক 1.000
token: না-দেখা token আছে এমন শব্দ 63 টি, ঠিক 0.746 | বাকি 72 টি, ঠিক 1.0
ভুলের উদাহরণ: ['রসিদ', 'বাজার', 'খরচ'] ids [9, 0, 32] | আসল র: 3 | অনুমান: 2
গণনার সাধারণ কোড (str.count): 3
baseline, সবসময় গড়: 0.4

দেখা token-এ ১০০% ঠিক, না-দেখা token থাকলে ৭৪.৬%. কারণ একটি token-এ কয়টি র, তা শুধু সেই id দেখে মুখস্থ করতে হয়। অক্ষরের id-তে এই সমস্যা নেই: ৩৭টি চিহ্ন সবই শেখার সময় এসেছে। এই খেলনায় ভান্ডার মাত্র ৪২; বাস্তবের লাখ token-এ “বিরল” token অনেক বেশি। আর সাধারণ str.count বিনা শিক্ষায় ঠিক। কাজের নিয়ম: গোনা, sort বা পাটিগণিতের কাজ কোডে করান, আর LLM-কে কোড লিখতে বা tool ডাকতে দিন (পর্ব ৭১ · Tool use-এ বিস্তারিত)।

৬ · Knowledge cutoff — model-এর ক্যালেন্ডার থেমে আছে

Training-এর ডেটা একটি তারিখে থামে। এরপরের ঘটনা model-এর ওজনে নেই; একে knowledge cutoff বলে। আগের অংশের সারণিতে কয়েকটি পণ্যের নথিতে লেখা তারিখ আছে। সবাই একে একভাবে বলে না: Anthropic-এর নথিতে “reliable knowledge cutoff” আর “training data cutoff” আলাদা সারি, অর্থাৎ শেষের দিকের তথ্য অসম্পূর্ণ হতে পারে। লাইব্রেরির API-র জন্য এটি সরাসরি বিপদ: পুরনো ডেটায় শেখা model পুরনো ফাংশন আত্মবিশ্বাসে লিখে দেয়।

"What is today's date? ..."               -> "Today's date is January 1, 2023."
'What year is it now? ...'                -> '2023'
'Who won the 2031 Cricket World Cup? ...' -> 'The 2031 Cricket World Cup was won by South Africa, defeating Australia in the final.'
with date in system: '2026'
capital with a note: 'Dhaka'
shop with a note: 'Bazarghar takes 3 days to deliver to Sylhet.'

সত্যিকারের run, greedy, Qwen2.5-0.5B. লেখার সময় ২০২৬ সালের নভেম্বর। ২০৩১-এর বিশ্বকাপ এখনো হয়নি, তবু একটি সাবলীল ফল এলো। শেষ তিন লাইনে system বার্তায় একটি তথ্য দেওয়া হয়েছিল (আজকের তারিখ, রাজধানী, বাজারঘরের বানানো নিয়ম)। তখন উত্তর মিলে গেল, “Chittagong”-ও “Dhaka” হলো। Model-কে উৎস দেওয়া যে কেন এত কাজের, এটি তার ছোট প্রমাণ; RAG-এর ভিত্তিও এই (পর্ব ৬৯)। তবে তিনটি উদাহরণ নিশ্চয়তা নয়: উৎস থাকলেও model ভুল পড়তে পারে, আর সেই মূল্যায়ন পর্ব ৭০-এর কাজ।

৭ · Hallucination — কারণ আর একটি মাপা পরীক্ষা

পরীক্ষার হলে একজন ছাত্রের কথা ভাবুন, যিনি উত্তর জানেন না কিন্তু ফাঁকা খাতা জমা দিতে চান না। হাতের লেখা সুন্দর, বাক্য গোছানো, তথ্য বানানো। Hallucination-এর কাছাকাছি একটি ছবি এটি; তবে model-এর কোনো ভয় বা ইচ্ছা নেই। যান্ত্রিক কারণ সরল: pretraining-এর objective শুধু পরের token-এর log-probability বাড়ায় (পর্ব ১৩)। “এটি সত্য কি না” কোথাও একটি পদ হিসেবে নেই। উত্তর না জানলেও softmax সব token-এ কিছু না কিছু সম্ভাবনা দেয়, আর decoding একটি বেছে নেয়।

পরীক্ষা: Qwen2.5-0.5B-কে ৭০টি প্রশ্ন, greedy. ৪০টি বাস্তব (রাজধানী, মৌল, পাটিগণিত, ইতিহাস), আর ৩০টি বাজারঘর নিয়ে। বাজারঘর এই কোর্সের বানানো দোকান, তাই প্রতিষ্ঠার সাল বা ওয়াটের কোনো সত্য উত্তর নেই; ঠিক আচরণ একটিই, “জানি না”। ইংরেজিতে ২৪ নতুন token পর্যন্ত, বাংলায় ৯০। প্রতি উত্তরের token-প্রতি গড় log-probability মাপা হলো, আর seed ৫২-এ ৫টি sample নিয়ে শব্দের মিল। ঠিক-ভুল গোনা হয়েছে keyword মিলিয়ে, তাই গোনা নিখুঁত নয়।

ইংরেজি প্রশ্ন, greedyউত্তর (যেমন এসেছে)গড় logp
Capital of Bangladesh?The capital of Bangladesh is Dhaka.−0.02
12 * 12?12 * 12 equals 144.−0.12
Largest planet?…is Jupiter, with a diameter of… (২৪ token-এ কাটা)−0.20
Who wrote the national anthem?…"Hawa Bangla," was written by… (ভুল)−0.55
Bazarghar: warranty of Jholok-300?…is typically 24 months from… (বানানো)−0.14
Bazarghar: wattage of Jholok-300?…is 100 watts. (বানানো)−0.18
Bazarghar: year founded?…was founded in 2015. (বানানো)−0.39
Bazarghar: head office?…in the city of Mumbai, India. (বানানো)−0.46

কিছু উত্তরে বানানো ব্যক্তির নাম আর একটি ফোন নম্বর এসেছিল; সেগুলো এখানে দেখানো হয়নি, কারণ কারও আসল তথ্যের সঙ্গে মিলে যেতে পারে। ওপরের লাইনগুলোর সামনের অংশ জায়গার জন্য “…” করা হয়েছে। নিচের সারাংশ কয়েকটি script-এর সত্যিকারের আউটপুট, একসঙ্গে বসিয়ে একটু ছোট করা।

-- en
real, correct             n=28  mean logprob -0.083  agree 0.748
real, wrong               n= 2  mean logprob -0.438  agree 0.348
invented, gave an answer  n=14  mean logprob -0.531  agree 0.345
invented, abstained       n= 1  mean logprob -0.330  agree 0.165
English only: correct 28 | false or invented 16
 AUC mean_lp 0.978 | AUC agree 0.911
 keep mean_lp >= -0.1: correct kept 18/28, false kept 0/16
 keep mean_lp >= -0.2: correct kept 26/28, false kept 2/16
 keep mean_lp >= -0.5: correct kept 28/28, false kept 8/16
-- bn, 90 new tokens
real n 10  correct 1   abstain 0  echo-start 9   mean lp -0.323
fake n 15  correct 0   abstain 0  echo-start 13  mean lp -0.305
with 'say you do not know' system message: invented abstained 3 / 15 | real abstained 0 / 20
same subsets without it: invented 0 / 15 | real 0 / 20

সৎভাবে পড়লে: আমরা ধরে নিয়েছিলাম log-probability একেবারেই আলাদা করতে পারবে না। ইংরেজির এই ছোট তালিকায় ফল তার চেয়ে ভালো: গড়ে সঠিক −০.০৮৩, বানানো −০.৫৩১, AUC ০.৯৮ (AUC-র মানে AI-এর ভিত্তি কোর্সের পর্ব ৬০-এ)। তবে একে “logprob দিয়ে সত্য ধরা যায়” পড়া যাবে না। প্রশ্নগুলো ছিল খুব পরিচিত বনাম সম্পূর্ণ অজানা, অর্থাৎ সহজতম দুই প্রান্ত। মোট ৪৪টি উত্তর, একটি model, একটি run. −০.২ সীমায়ও ১৬টি মিথ্যার ২টি পার হয়, আর সঠিক ২টি বাদ যায়। “ভুল” ২টির একটি বাংলাদেশের দীর্ঘতম নদীর প্রশ্ন, যার উত্তর সংজ্ঞাভেদে তর্কসাপেক্ষ।

বাংলায় সংকেতটি নেই: বাস্তব −০.৩২৩, বানানো −০.৩০৫। প্রায় সব উত্তর প্রশ্নটিই আবার লিখে এলোমেলো হয়ে গেছে, যেমন “বাংলাদেশ স্বাধীন হয় ১৯৯৬ সালে” (আসলে ১৯৭১)। এই ছোট model-এর এটি দুর্বলতা; বড় multilingual model-এর ফল আলাদা হতে পারে। তবু শিক্ষাটি রইল: এক ভাষায় মাপা সীমা অন্য ভাষায় খাটবে, এমন ধরে নেওয়া যায় না। “জানি না বলুন” লেখা system বার্তায় ১৫টির ৩টিতে থেমেছে, বাকি ১২টিতে থামেনি (২৪ token-এর সীমায় গোনা, তাই বাংলা অংশে কিছু থামা ধরা না-ও পড়তে পারে)।

আর অংশ ১-এর “Chittagong”? একই প্রশ্নে “Answer in one word” বদলে “one short sentence” লিখতেই উত্তর হলো Dhaka. ছোট model-এ prompt-এর ছোট বদলও ফল বদলায়, তাই একটি উদাহরণে কিছু প্রমাণ হয় না।

গড় সংখ্যাটি কেন ধোঁকা দিতে পারে? “100 watts” উত্তরের −০.১৮৪ ধাপে ধাপে ভাঙা যাক। সব p সত্যিকারের run থেকে।

একটি উত্তর, token ধরে
Token
p
অন্য বিকল্প
Σ log p
  1. প্রশ্ন. “Jholok-300 রাইস কুকারের ওয়াট কত?” পণ্যটি নেই, উত্তরও নেই। Model-এর কাছে আছে শেখা ছাঁচ: প্রশ্নের শব্দ ফিরিয়ে লেখা, তারপর একটি সংখ্যা।
  2. প্রশ্ন নকল করা. প্রথম ১৮ token প্রশ্ন থেকেই আসে, বেশিরভাগ p প্রায় ১। Σ log p মাত্র −০.৯৩৫। নকল সহজ, তাই সংখ্যাটি সুন্দর দেখায়; তথ্য এখনো একটিও আসেনি।
  3. দ্বিধা. “is”-এর পরে ' ' ০.৫১৬ আর ' approximately' ০.৩৫৪। বণ্টন প্রথমবার বেশ ছড়ায়।
  4. আসল “তথ্য”. সংখ্যার token-এ p নেমে যায়: '1' ০.৩৩৯, পরে '0' ০.৩২৯; পাশের অঙ্কগুলোও কাছাকাছি। বণ্টন এখানে সবচেয়ে ছড়ানো, তবু greedy একটি বাছে, আর লেখায় কোনো দ্বিধা থাকে না।
  5. শেষ. বাকি token আবার সহজ। Σ log p = −৪.৪১৭, ২৪ token. লেখাটি গোছানো একটি পূর্ণ বাক্য।
  6. পড়ার নিয়ম. গড় −০.১৮৪, অর্থাৎ জ্যামিতিক গড় p ≈ ০.৮৩, ভরসা জাগায়। অথচ ৫টি sample-এ সংখ্যা এলো ৩০০ বা ৫০০। পুরো বাক্যের মিল তবু ০.৭, কারণ নকল অংশ মিলেছে। গড় নকলকেও মাপে; “তথ্যের” token-এর p আলাদা দেখতে হয়, আর সেটিও সত্যের প্রমাণ নয়।
মূল ধারণা

Model-এর সংখ্যা বলে “এই প্রসঙ্গে কোন token প্রত্যাশিত”, “এই কথাটি পৃথিবীতে সত্য” নয়। তাই কাজের ব্যবস্থা স্তরে স্তরে: উৎস দেওয়া (অংশ ৬-এর “3 days” উত্তর), উৎস না পেলে abstention বা মানুষের কাছে পাঠানো, sample করে self-consistency check (ধারণাটি SelfCheckGPT, Manakul ও সহলেখক, ২০২৩), আর শেষে মূল উৎসে যাচাই।

৮ · Calibration — আত্মবিশ্বাস কি নির্ভুলতার সঙ্গে মেলে?

খোলা প্রশ্নে সত্য মাপা কঠিন, তাই সীমিত একটি প্রশ্ন নিন: যেখানে সঠিক উত্তরের চাবি আছে, সেখানে আত্মবিশ্বাসের সংখ্যা কতটা মেলে? আবহাওয়ার পূর্বাভাসের মতো: ৭০% বৃষ্টির দিনগুলোর প্রায় ৭০%-এ বৃষ্টি হলে পূর্বাভাসটি calibrated. Reliability diagram, ECE আর temperature scaling-এর বিস্তারিত মেশিন লার্নিং কোর্সের পর্ব ২৬-এ; deep model-এর দিক পর্ব ১৩-এ। এখানে আমরা সেগুলো নিজের মাপা LLM-এর ফলে খাটাব।

পরীক্ষা: CommonsenseQA-র validation অংশের ১,২২১টি প্রশ্ন (tau/commonsense_qa, Hugging Face, MIT), পাঁচটি বিকল্প, ইংরেজি। Qwen2.5-0.5B-কে বলা হলো শুধু অক্ষর লিখতে। A থেকে E-এর পাঁচটি logit নিয়ে softmax, সবচেয়ে বড় মানটি আত্মবিশ্বাস। পুরো vocabulary-র ৯৯.৬% সম্ভাবনা এই পাঁচ অক্ষরেই ছিল। নিচে সেই ফলের ঝুড়িগুলো ডেটা হিসেবে বসানো।

import numpy as np, matplotlib.pyplot as plt
# প্রতি ঝুড়ি: নিচের সীমা, ওপরের সীমা, n, গড় আত্মবিশ্বাস, ঠিক উত্তর (desktop-এ মাপা)
B = np.array([(.2, .3,  15, .2766,   4), (.3, .4,  94, .3540,  31),
              (.4, .5, 150, .4483,  47), (.5, .6, 147, .5519,  69),
              (.6, .7, 153, .6509,  73), (.7, .8, 139, .7517,  72),
              (.8, .9, 151, .8540,  81), (.9, 1., 372, .9673, 279)])
lo, hi, n, conf, ok = B.T
acc, N = ok / n, n.sum()
gap = conf - acc
print(" ঝুড়ি       n   আত্মবিশ্বাস  নির্ভুলতা  ফারাক")
for r in zip(lo, hi, n, conf, acc, gap):
    print(f"{r[0]:.1f}-{r[1]:.1f}  {r[2]:4.0f}   {r[3]:.3f}      {r[4]:.3f}     {r[5]:+.3f}")
print("মোট n:", int(N), "| নির্ভুলতা:", round(ok.sum() / N, 4), "| গড় আত্মবিশ্বাস:", round((n * conf).sum() / N, 4))
print("ECE:", round((n / N * np.abs(gap)).sum(), 4), "| MCE:", round(np.abs(gap).max(), 4))
for t in (.2, .5, .7, .9):                 # শুধু আত্মবিশ্বাসী উত্তর রাখলে
    m = lo >= t - 1e-9
    print(f"আত্মবিশ্বাস > {t:.1f}: উত্তর রাখা {n[m].sum() / N:.3f}, নির্ভুলতা {ok[m].sum() / n[m].sum():.3f}")
plt.figure(figsize=(4.6, 3.6))
plt.plot([0, 1], [0, 1], "k--", label="perfect")
plt.bar(lo + .05, acc, width=.09, alpha=.55, label="accuracy")
plt.plot(lo + .05, conf, "o-", color="tab:red", label="mean confidence")
plt.xlabel("confidence bin"); plt.ylabel("value"); plt.legend(); plt.show()

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

 ঝুড়ি       n   আত্মবিশ্বাস  নির্ভুলতা  ফারাক
0.2-0.3    15   0.277      0.267     +0.010
0.3-0.4    94   0.354      0.330     +0.024
0.4-0.5   150   0.448      0.313     +0.135
0.5-0.6   147   0.552      0.469     +0.083
0.6-0.7   153   0.651      0.477     +0.174
0.7-0.8   139   0.752      0.518     +0.234
0.8-0.9   151   0.854      0.536     +0.318
0.9-1.0   372   0.967      0.750     +0.217
মোট n: 1221 | নির্ভুলতা: 0.5373 | গড় আত্মবিশ্বাস: 0.7196
ECE: 0.1824 | MCE: 0.3176
আত্মবিশ্বাস > 0.2: উত্তর রাখা 1.000, নির্ভুলতা 0.537
আত্মবিশ্বাস > 0.5: উত্তর রাখা 0.788, নির্ভুলতা 0.597
আত্মবিশ্বাস > 0.7: উত্তর রাখা 0.542, নির্ভুলতা 0.653
আত্মবিশ্বাস > 0.9: উত্তর রাখা 0.305, নির্ভুলতা 0.750

দুটি কথা একসঙ্গে সত্য। Model overconfident: সব ঝুড়িতে ফারাক ধনাত্মক; ৯০%-এর বেশি বলা ৩৭২টি উত্তরের ৭৫% ঠিক, আর ০.৮–০.৯ ঝুড়িতে মাত্র ৫৩.৬%. তবু আত্মবিশ্বাসে তথ্য আছে: ওপরের ঝুড়ির দিকে নির্ভুলতা বাড়ে, আর শুধু আত্মবিশ্বাসী উত্তর রাখলে ৫৩.৭% থেকে ৭৫%, যদিও তখন প্রায় ৭০% প্রশ্ন ফেলে দিতে হয়। অর্থাৎ বাছাই করা এই পাঁচ-বিকল্পের কাজে সংখ্যাটি ক্রম হিসেবে কাজে লাগে, সম্ভাবনা হিসেবে নয়।

accuracy 0.5373 | mean confidence 0.7196 | ECE10 0.1824 | chance 0.2
ECE by n bins: {5: 0.1824, 10: 0.1824, 15: 0.1856, 20: 0.1832}
bootstrap 95% CI for ECE10: [0.1605, 0.212]
temperature fitted on 610: T=2.15 | test n=611 acc 0.5172 -> 0.5172 | ECE 0.2101 -> 0.0514 | NLL 1.4909 -> 1.2207
confident wrong: 'What do people need to do to change their lives?' | said better themselves (0.999) | key: face problems
confident wrong: 'Where would a cat snuggle up with their human?' | said bed (0.998) | key: comfortable chair

সত্যিকারের desktop run, CPU-তে, seed ৫২ (bootstrap আর ভাগের জন্য)। মাঝখানে memory-র চাপে script কয়েকবার checkpoint থেকে আবার শুরু হয়েছিল; model আর ডেটা একই। ৫ আর ১০ ঝুড়িতে ECE এক, কারণ সব ফারাকের চিহ্ন এক হলে ঝুড়ি জোড়া দিলে যোগফল বদলায় না। অর্ধেক ডেটায় T = ২.১৫ শেখা হলো, বাকি অর্ধেকে ECE ০.২১ থেকে ০.০৫। নির্ভুলতা একই ৫১.৭২%: temperature কোনো উত্তর বদলায় না, শুধু গলার জোর কমায়। আর “ভুল” দুটি দেখুন: বিছানায় বেড়ালের গুটিসুটি মারা হয়তো ভুল নয়, চাবিটিই একমাত্র ঠিক উত্তর নয়। লেবেলের সীমাও ফলের অংশ। এই ফল ইংরেজি সাধারণ জ্ঞানের বহু-বিকল্প প্রশ্নের; খোলা উত্তরে বা বাংলায় এই T খাটবে, এমন বলা যায় না।

৯ · Logit lens — স্তরে স্তরে অনুমান দেখা

এখন পর্যন্ত আমরা শুধু শেষের logit দেখেছি। Logit lens একটি সরল প্রশ্ন করে: মাঝের কোনো স্তরের vector-কে সরাসরি শেষ normalization আর lm_head-এ দিলে কোন token এগিয়ে থাকে? ডাক্তার যেমন রোগ ধরার সময় ধাপে ধাপে অনুমান বদলান, অনেকটা সেরকম একটি ছবি মেলে; তবে এটি একটি মাপ, “চিন্তা” নয়। পর্ব ৪৯ · GPT-তে GPT-2-এর গঠন আছে; এখানে শুধু hook বসানো।

store = {}                       # condensed from the real script
def capture(i):
    def f(m, inp, out): store[i] = out[0] if isinstance(out, tuple) else out
    return f
hooks = [b.register_forward_hook(capture(i)) for i, b in enumerate(model.transformer.h)]
def lens(h): return model.lm_head(model.transformer.ln_f(h))

সত্যিকারের run, CPU-তে, seed লাগেনি কারণ sampling নেই। লম্বা সারণি থেকে কয়েকটি স্তর রাখা হলো। প্রথম প্রশ্নের লক্ষ্য token ' Paris', দ্বিতীয়টির ' 2000'.

prompt: 'The Eiffel Tower is located in the city of'
layer | top-1 at last position | p(top-1) | rank target | p(target) | norm
    4 | ' the'         | 0.318 |    448 | 0.0001 |  70.4
    7 | ' Ing'         | 0.230 |     95 | 0.0013 | 104.7
    8 | ' Rome'        | 0.168 |     12 | 0.0150 | 121.9
    9 | ' London'      | 0.248 |      1 | 0.1935 | 150.2
   10 | ' Paris'       | 0.165 |      0 | 0.1649 | 264.7
   11 | ' Paris'       | 0.064 |      0 | 0.0638 | 444.2
block 11 through lens vs the model's real logits, max abs diff: 0.00e+00

prompt: 'Bazarghar, the online shop, was founded in the year'
layer | top-1 at last position | p(top-1) | rank target | p(target) | norm
    4 | 'long'         | 0.569 |      5 | 0.0067 |  68.6
    6 | ' 2000'        | 0.315 |      0 | 0.3152 |  87.9
    9 | ' 2000'        | 0.902 |      0 | 0.9021 | 146.8
   11 | ' 2000'        | 0.233 |      0 | 0.2329 | 432.7
final top5: [(' 2000', 0.233), (' of', 0.029), (' 2013', 0.024), (' 2015', 0.023), (' 2001', 0.021)]

prompt: 'The capital of Bangladesh is'   target ' Dhaka'
   10 | ' Bangladesh'  | 0.480 |      1 | 0.1415 | 278.6
   11 | ' the'         | 0.114 |     13 | 0.0116 | 446.7

Bangla in gpt2: 14 tokens, every one decodes to '�'

কী দেখলাম: Block ১১-এর lens আর model-এর আসল logit হুবহু এক (পার্থক্য ০), তাই hook ঠিক আছে। Eiffel-এর prompt-এ স্তর ৯ পর্যন্ত “London” এগিয়ে, স্তর ১০-এ “Paris”. বাজারঘরের বেলায় স্তর ৬ থেকেই ' 2000' এগিয়ে যায়: দোকানটি বানানো, তবু “founded in the year”-এর পরে একটি সাল আসবে, ছাঁচটি model ধরেছে। সত্য তথ্য আর ছাঁচ, দুটোই একই পথে এগোয়; লেন্সে আলাদা রং নেই।

সতর্কতা: স্তর ৯-এ ' 2000'-এর ০.৯০২ model-এর চূড়ান্ত সম্ভাবনা নয়; চূড়ান্তটি ০.২৩৩। “Paris”-এর শেষ সম্ভাবনাও মাত্র ০.০৬৪। মাঝের সংখ্যাগুলো lm_head-কে এমন কাজে লাগানোর ফল, যার জন্য সেটি শেখানো হয়নি। এই কয়েকটি prompt কোনো সাধারণ নিয়ম দেয় না। আর GPT-2-তে বাংলা ১৪টি byte-টুকরো (পর্ব ৪৯ · GPT আর decoder-only model-এর বিষয়)।

আরও গভীরে: কৌশলটি nostalgebraist-এর ২০২০ সালের LessWrong লেখা “interpreting GPT: the logit lens”-এ পরিচিতি পায়। Belrose ও সহলেখকদের Eliciting Latent Predictions from Transformers with the Tuned Lens (২০২৩) জানায়, logit lens কিছু token-এর দিকে হেলে থাকে; প্রতি স্তরে একটি affine map শিখে tuned lens শেষ বণ্টনের সঙ্গে বেশি মেলে। Hallucination-এর পরিসংখ্যানগত যুক্তির জন্য Kalai ও Vempala (STOC ২০২৪) দেখুন, আর আত্মবিশ্বাস মাপার জন্য Kadavath ও সহলেখক, Language Models (Mostly) Know What They Know (২০২২)।

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

  • KV cache-এ দ্বিতীয় ধাপের input? শুধু নতুন token, shape (B, 1); logit (B, 1, V).
  • Window ৮,১৯২, input ৭,৭০০, max output ১,০০০? ৮,৭০০ > ৮,১৯২; ৫০৮ token কমাতে হবে, অথবা output সীমা ৪৯২ করতে হবে।
  • Log-probability কি confidence-এর সমান? না। এটি বণ্টনে প্রত্যাশা মাপে; সত্যের সঙ্গে মেলে কি না তা calibration দিয়ে যাচাই করতে হয়।
  • ECE কম কিন্তু accuracy কম হতে পারে? হ্যাঁ। ৩০% accuracy-তে সব সময় ০.৩ বলা model calibrated, কিন্তু কাজের নয়।
  • Chat template ভুল হলে লক্ষণ? Error নয়; উত্তর শুরু হয় না, থামে না, বা মান পড়ে। পরীক্ষা: লেখাটি print করে নথির সঙ্গে মেলানো।

১০ · AI-এর যুগে — LLM-এর ভেতরের কোড আর দাবি যাচাই

এই পর্বের কোড AI assistant মিনিটে লিখে দেয়। তবে এখানকার ভুলগুলো সাধারণত চুপচাপ ভুল ফল দেয়, ক্র্যাশ করে না।

AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
Prompt হাতে string জুড়ে বানায়Model-এর template না মিললে নীরবে মান পড়েapply_chat_template, আর লেখাটি print
truncation=True পুরো chat-এশেষ প্রশ্ন বা system হারায়কাটা id decode করে পড়া
পুরনো torch_dtype বা বানানো argumentVersion-ভেদে বদলায়; আমাদের script-এ dtype চলেছেনিজের version-এর docs আর tiny run
model.eval() বা torch.no_grad() নেইDropout থাকলে ফল বদলাতে পারে, memory বাড়েএকই input-এ দুবার চালিয়ে মেলানো
“Logprob বেশি, তাই উত্তর ঠিক” বা “model বুঝেছে”গড়ে সংকেত থাকলেও মিথ্যাও উচ্চ logprob পায়; ভাষা বদলালে সংকেত হারাতে পারেনিজের ডেটায় নিয়ে calibration আর AUC
Context window-এর সংখ্যা স্মৃতি থেকেসংখ্যা বদলায়, আর cutoff পেরোনো তথ্য তার অজানাঅফিসিয়াল নথি, তারিখসহ
বানানো citationছাঁচ সহজ, সত্য যাচাই নেইPaper আর মামলা মূল উৎসে খোলা

কোড চাওয়ার সময় যাচাইও চেয়ে নিন:

আমি Hugging Face transformers-এ (version লিখে দিচ্ছি) একটি ছোট open model দিয়ে chat চালাতে চাই। ১. apply_chat_template দিয়ে prompt বানাও; হাতে string জুড়বে না। শেষের generation prompt-এর কারণ বলো। ২. Context window-এর হিসাব দাও: window থেকে উত্তরের জায়গা বিয়োগ, system রেখে সবচেয়ে পুরনো বার্তা জোড়া ধরে বাদ। Token দিয়ে truncate করবে না। ৩. ব্যবহারকারীর লেখায় special token যেন না ঢোকে, তার ব্যবস্থা আর একটি test দাও। ৪. model.eval() আর torch.inference_mode() কোথায় লাগবে বলো, আর generation-এর সব parameter স্পষ্ট লিখো। ৫. প্রতিটি ধাপের shape একটি tiny input-এ print করার কোড দাও। ৬. যে API নিয়ে নিশ্চিত নও, তা অনুমান বলে চিহ্নিত করো। কোনো সংখ্যা, version বা citation বানিয়ে লিখো না। ৭. Output-এর সত্যতা নিয়ে কোনো দাবি কোরো না; log-probability-কে সত্যের প্রমাণ বলবে না।

AI বা LLM-এর উত্তর হাতে পেলে — যাচাইয়ের চেকলিস্ট

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

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

প্র ০১LLM “জানি না” না বলে বানিয়ে উত্তর দেয় কেন? এটি কি শুধু ভালো ডেটার অভাব?

দুটি আলাদা স্তরে কারণ খোঁজা হয়। প্রথম স্তর pretraining-এর লক্ষ্য। Cross-entropy শুধু জিজ্ঞেস করে, ডেটায় পরের token কোনটি ছিল। “এই বাক্যটি সত্য কি না” কোথাও মাপা হয় না। তাই সাবলীল, বিশ্বাসযোগ্য ধারাবাহিকতাকে পুরস্কার দেওয়া হয়, সত্যকে আলাদা করে নয়।

দ্বিতীয় স্তর পরিসংখ্যান। Kalai আর Vempala তাঁদের “Calibrated Language Models Must Hallucinate” প্রবন্ধে (arXiv ২০২৩, STOC ২০২৪) যুক্তি দেন: যেসব বিচ্ছিন্ন তথ্য training ডেটা থেকে যাচাই করা যায় না, সেখানে একটি calibrated model-এর hallucination-এর একটি পরিসংখ্যানগত নিচের সীমা থাকে। হারটি প্রায় ডেটায় ঠিক একবার দেখা তথ্যের ভাগের কাছাকাছি। যুক্তিটি একটি নির্দিষ্ট ধরনের তথ্য নিয়ে। পাটিগণিত বা বারবার আসা তথ্যে এমন পরিসংখ্যানগত বাধা নেই বলে তাঁরা লিখেছেন।

২০২৫ সালে Kalai, Nachum, Vempala আর Zhang “Why Language Models Hallucinate” প্রবন্ধে আরেকটি দিকে আঙুল তোলেন: মূল্যায়ন। যে পরীক্ষায় ভুলের কাটা নম্বর নেই, সেখানে না জানলেও আন্দাজে দাগানোই লাভজনক। Benchmark যদি “জানি না”-কে শূন্য দেয়, আর আন্দাজকে মাঝে মাঝে পুরো নম্বর, তাহলে model-এর উন্নয়ন আন্দাজের দিকেই ঠেলে। তাঁদের প্রস্তাব, score করার নিয়ম বদলানো। এটি একটি প্রস্তাব; সবাই একমত, এমন নয়।

তাহলে ডেটা বাড়ালে সমাধান? আংশিক। বেশি দেখা তথ্যে ভুল সাধারণত কমে, কিন্তু পৃথিবীর তথ্যের একটি লম্বা লেজ সবসময় বিরল থাকে। বাজারঘরের মতো নিজস্ব তথ্য তো ডেটায় একবারও নেই। তাই ব্যবহারিক ব্যবস্থা হলো নথি থেকে উত্তর দেওয়া (পর্ব ৬৯ · RAG), উৎস দেখানো, আর উত্তর যাচাই করা।

মূল উপলব্ধি: Hallucination কোনো একটি bug নয়, যা একটি patch-এ যাবে। এটি লক্ষ্য, মূল্যায়ন আর তথ্যের বিরলতার যৌথ ফল, তাই প্রতিরক্ষাও কয়েক স্তরের হতে হয়।

প্র ০২আদালতে ChatGPT-র বানানো মামলার উদ্ধৃতি জমা পড়েছিল। ঘটনাটি থেকে কাজের কী শেখার আছে?

মার্কিন মামলা Mata v. Avianca, Inc. (দক্ষিণ নিউ ইয়র্ক জেলা আদালত)। আদালতের ২২ জুন ২০২৩-এর আদেশ আর সে সময়ের প্রতিবেদন অনুযায়ী, বাদীপক্ষের আইনজীবীরা একটি লিখিত যুক্তিতে কয়েকটি পূর্ববর্তী মামলার উদ্ধৃতি দেন, যেগুলোর অস্তিত্ব ছিল না। সেগুলো ChatGPT থেকে এসেছিল। বিচারক P. Kevin Castel দুই আইনজীবী আর তাঁদের ফার্মকে আর্থিক জরিমানা করেন, আর যেসব বিচারকের নাম ভুয়া রায়ে বসেছিল, তাঁদের জানাতে বলেন।

আদেশের একটি কথা গুরুত্বপূর্ণ: AI ব্যবহারে নিজে থেকে অন্যায় কিছু নেই, কিন্তু নিজের দাখিল করা জিনিসের সত্যতা যাচাইয়ের দায় আইনজীবীর। আদালত bad faith খুঁজে পেয়েছিল মূলত ভুল ধরা পড়ার পরের আচরণে। অর্থাৎ শাস্তিটি শুধু টুলের ভুলের জন্য ছিল না, যাচাই না করা আর পরে ঠিকভাবে না স্বীকার করার জন্যও।

প্রযুক্তির দিক থেকে ঘটনাটি প্রায় পাঠ্যবইয়ের মতো। মামলার উদ্ধৃতির একটি পরিষ্কার ছাঁচ আছে: পক্ষের নাম, খণ্ড, পাতা, সাল। পরের token-এর model এই ছাঁচ দারুণ শেখে। ছাঁচ যত পরিচিত, লেখা তত বিশ্বাসযোগ্য, যদিও মামলাটি নেই। প্রতিবেদন অনুযায়ী, আইনজীবী ChatGPT-কেই জিজ্ঞেস করে নিশ্চিত হতে চেয়েছিলেন, মামলাগুলো সত্য কি না। এটিও পরের token-এর অনুমান, স্বাধীন যাচাই নয়।

বাজারঘরে একই ভুল দেখতে অন্যরকম হবে: বানানো ফেরতের নিয়ম, ভুল ওয়ারেন্টি, অস্তিত্বহীন অর্ডার। তাই গুরুত্বপূর্ণ দাবির জন্য model-এর বাইরের উৎস লাগে: ডেটাবেস, মূল নথি, লাইব্রেরির docs. ব্যবহারকারীকেও দেখাতে হবে, উত্তরটি কোথা থেকে এসেছে।

মূল উপলব্ধি: Model-কে দিয়ে model-কে যাচাই করা একই উৎসে দুবার তাকানো। যাচাই মানে স্বাধীন উৎসে মেলানো, আর দায় থাকে যিনি ব্যবহার করলেন তাঁর।

প্র ০৩Context window ১০ লাখ token হয়ে গেলে কি পুরো কোম্পানির নথি একবারে দিয়ে দিলেই হয়?

Window-এ ঢোকা আর ব্যবহার হওয়া এক কথা নয়। Liu ও সহলেখকদের “Lost in the Middle” প্রবন্ধে (২০২৩, TACL-এ গৃহীত) দেখা যায়, দরকারি তথ্য লম্বা লেখার মাঝখানে থাকলে তখনকার পরীক্ষিত model-গুলোর নির্ভুলতা শুরু বা শেষের তুলনায় লক্ষণীয়ভাবে কমে। এই পরীক্ষা নির্দিষ্ট model আর task-এর; নতুন model-এ মাত্রা আলাদা হতে পারে। তাই নিজের ডেটায় মেপে দেখাই নিরাপদ।

দ্বিতীয় বাধা খরচ আর সময়। পর্ব ৩৭ · দক্ষ attention-এ দেখেছেন, প্রথম pass-এ পুরো prompt হিসাব হয়, আর cache-এর memory T-এর সঙ্গে বাড়ে। প্রথম token দেরিতে আসে। Hosted API-তে সাধারণত input token ধরেই বিল হয়, তবে নির্দিষ্ট দাম আর ছাড় প্রতিষ্ঠানভেদে ও সময়ভেদে বদলায়।

তৃতীয়টি বাংলার নিজের হিসাব। একই বাক্যে আমাদের মাপা tokenizer-এ বাংলা ইংরেজির তিন গুণের বেশি token নিল। “১০ লাখ token” তাই ইংরেজি নথির হিসাবে যত বড় শোনায়, বাংলা নথিতে তত বড় নাও হতে পারে। কত হবে, তা model-এর tokenizer দিয়েই গুনতে হয়।

চতুর্থ বাধা নিরাপত্তা আর বিশৃঙ্খলা। সব নথি একবারে দিলে পুরনো আর নতুন নিয়ম পাশাপাশি থাকে, আর যে নথিতে কেউ নির্দেশ লুকিয়েছে সেটিও ভেতরে ঢোকে (prompt injection; পর্ব ৭৩ · LLM নিরাপত্তায় বিস্তারিত)। Access control-ও ঝাপসা হয়: যে কর্মীর যে নথি দেখার কথা নয়, উত্তরে সেটি ফাঁস হতে পারে।

তাই বড় window RAG-কে অদরকারি করেনি। দুটি মিলিয়েই কাজ হয়: আগে বেছে নেওয়া, তারপর পর্যাপ্ত প্রসঙ্গ দেওয়া। কোনটি কখন, তার উত্তর ডেটার আকার, প্রশ্নের ধরন আর বাজেট ধরে আসে; সার্বজনীন কোনো নিয়ম নেই।

মূল উপলব্ধি: Context window হলো ধারণক্ষমতার সীমা, মনোযোগের নিশ্চয়তা নয়। বেশি দিলে ব্যবহারও বেশি হবে, এটি পরীক্ষা করে জানতে হয়।

প্র ০৪Chat template ভুল হলে error আসে না কেন? আর এটি নিরাপত্তার প্রশ্ন হয়ে ওঠে কীভাবে?

Tokenizer-এর চোখে template শুধু লেখা। ভুল template-ও বৈধ token id দেয়, tensor-এর shape ঠিক থাকে, loss-ও হিসাব হয়। Model আসলে কোন ছাঁচে শিখেছিল, তা কোনো type checker জানে না। ফলে ভুলের লক্ষণ হয় নরম: উত্তর শুরু না হয়ে প্রশ্নটাই চলতে থাকে, থামে না, বা মান খারাপ হয়। মানুষ ভাবে “model-টাই দুর্বল”, যদিও model-কে ভুল ফরম্যাটে ডাকা হয়েছে।

ঐতিহাসিকভাবে ছাঁচগুলো আলাদা হয়েছে। OpenAI ২০২৩ সালের দিকে Chat Markup Language (ChatML) নামে <|im_start|> ধরনের ছাঁচের কথা প্রকাশ করেছিল; Qwen আর SmolLM2-এর template-এ সেই ধাঁচের চিহ্ন আমাদের run-এই দেখা যায়। অন্য পরিবারের ছাঁচ আলাদা। Hugging Face-এর apply_chat_template tokenizer-এর সঙ্গে থাকা template ব্যবহার করে, তাই হাতে string না জোড়াই ভালো। পর্ব ৫৬ · Instruction tuning-এ এই একই ফাঁদ training ডেটায় ফিরে আসবে: শেখানোর সময়ের ছাঁচ আর চালানোর সময়ের ছাঁচ না মিললে নীরবে মান পড়ে।

নিরাপত্তার দিকটি সহজ। Template-এর চিহ্নগুলো “কে বলছে” তা ঠিক করে। আমাদের run-এ দেখা গেছে, ব্যবহারকারীর লেখায় চিহ্নটি থাকলে সাধারণ ডাকে সেটি আসল special token id হয়ে যায়। অর্থাৎ বাইরের লেখা বার্তার সীমানা নকল করার সুযোগ পায়। প্রতিরক্ষা: বাইরের লেখার ক্ষেত্রে split_special_tokens=True বা আগেই চিহ্ন পরিষ্কার করা, tokenize করার পর পরীক্ষা করা যে user অংশে special id নেই, আর model-এর উত্তরকে কখনো অনুমতির উৎস না ধরা। তবে এতে prompt injection শেষ হয় না: সাধারণ ভাষায় লেখা নির্দেশের জন্য কোনো special token লাগে না। তাই template-এর চূড়ান্ত লেখা একটি test-এ জমা রাখুন, আর model বা library বদলালে সেটি আবার মিলিয়ে দেখুন।

মূল উপলব্ধি: Template হলো model আর কোডের চুক্তি। চুক্তি ভাঙলে compiler কিছু বলে না, তাই নিজের test লিখে রাখতে হয়।

প্র ০৫Logit lens কি model-এর “মনের ভেতর” দেখায়? এই ধরনের পরিদর্শন থেকে কতটুকু দাবি করা যায়?

“মন” শব্দটিই এখানে ভুল পথে নেয়। Logit lens একটি যান্ত্রিক প্রশ্ন করে: যদি এই স্তরেই থেমে শেষের normalization আর lm_head লাগাই, কোন token-এ বেশি score পড়ে? কৌশলটি সবচেয়ে পরিচিত হয় nostalgebraist ছদ্মনামের লেখকের ২০২০ সালের LessWrong লেখা “interpreting GPT: the logit lens” থেকে। এটি peer-reviewed প্রবন্ধ নয়, একটি অনুসন্ধানী ব্লগ।

সীমাগুলো গুরুত্বপূর্ণ। এক, lm_head শেখানো হয়েছে শেষ স্তরের জন্য। মাঝের স্তরের vector একই ভাষায় লেখা হবে, এমন কোনো গ্যারান্টি নেই। Belrose ও সহলেখকেরা ২০২৩ সালের “Eliciting Latent Predictions from Transformers with the Tuned Lens” প্রবন্ধে জানান, logit lens কিছু token-এর দিকে নিয়মিতভাবে হেলে থাকে, আর প্রতি স্তরের জন্য একটি affine map শিখলে (tuned lens) শেষ স্তরের বণ্টনের সঙ্গে মিল বাড়ে। দুই, একটি স্তরে “Paris” দেখা মানে সেই স্তর তথ্যটি তৈরি করেছে, এমন নয়; হয়তো আগের স্তরে অন্য রূপে ছিল। লেন্স একটি পর্যবেক্ষণ, কারণ নয়।

কারণ দাবি করতে হলে হস্তক্ষেপ লাগে: একটি স্তরের activation বদলে দেখা, উত্তর বদলায় কি না। Activation patching-এর মতো পদ্ধতি এই কাজ করে। এমনকি তখনও একটি prompt-এর ফল সব prompt-এ খাটে না। তাই একজন গবেষকের মতো ভাবুন: অনেক prompt, control, আর “কী দেখলে আমার ধারণা ভুল প্রমাণ হতো”।

ব্যবহারিক দিক থেকে এই পরিদর্শন কাজের। ভুল token কোন স্তরে এগিয়ে গেল, দুটি prompt কোথায় আলাদা হলো, debugging-এ এসব প্রশ্ন ধরা যায়। তবে “model বুঝেছে” বা “model জানে” লেখার প্রমাণ এটি নয়। আমাদের GPT-2 run একটি ছোট, পুরনো model-এর কয়েকটি prompt মাত্র; বড় model-এ একই নকশা থাকবে, এমন দাবি করা যায় না।

মূল উপলব্ধি: ভেতরে তাকানোর টুল প্রশ্ন তৈরিতে ভালো, উত্তরের চূড়ান্ত প্রমাণে নয়। দেখা, হস্তক্ষেপ, আর অনেক উদাহরণে পরীক্ষা: তিনটি মিলেই দাবি দাঁড়ায়।

অনুশীলন

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

  1. Context window-এর হিসাব: Window ৮,১৯২ token, উত্তরের জন্য রাখা ১,০২৪। System prompt ৩৫০, নথি ৩,০০০, নতুন প্রশ্ন ১২০, আগের ১২টি বার্তা, প্রতিটি গড়ে ১৮০ token আর template-এর ৪ token. (ক) কত token ফাঁকা থাকে? (খ) আরও কতটি গড় বার্তা ধরবে? (গ) এক token-এ প্রায় একটি code point ধরে আর কত অক্ষরের বাংলা নথি ঢুকবে?

    বার্তা: ১২ × (১৮০ + ৪) = ২,২০৮। Input: ৩৫০ + ৩,০০০ + ১২০ + ২,২০৮ = ৫,৬৭৮। উত্তরের জায়গাসহ ৬,৭০২।

    (ক) ৮,১৯২ − ৬,৭০২ = ১,৪৯০ token ফাঁকা। (খ) ১,৪৯০ ÷ ১৮৪ = ৮.০৯, তাই ৮টি বার্তা। (গ) প্রায় ১,৪৯০টি code point. এই অনুপাত আমাদের মাপা একটি বাক্যের (প্রায় ১.০২ token প্রতি code point) কাছাকাছি ধরা; অন্য tokenizer-এ এটি মিলবে না, তাই চূড়ান্ত হিসাব tokenizer দিয়েই করতে হবে।

  2. আউটপুট অনুমান করুন: কোড না চালিয়ে দুটি লাইনের আউটপুট লিখুন।
    import numpy as np
    E = np.array([[1, 0], [0, 1], [1, 1]], float)
    h = np.array([2, -1], float)
    print(h @ E.T, int((h @ E.T).argmax()))
    ids = [1, 2, 3, 4, 5, 6, 7, 8]
    print(ids[:5], ids[-5:])
    [ 2. -1.  1.] 0
    [1, 2, 3, 4, 5] [4, 5, 6, 7, 8]

    E-এর প্রতিটি সারির সঙ্গে h-এর dot product হলো ওই token-এর logit: ২, −১, ২ − ১ = ১। সবচেয়ে বড়টির index ০। দ্বিতীয় লাইন দুই দিকের truncation: [:5] ডান দিক কাটে, অর্থাৎ নতুন অংশ হারায়; [-5:] বাঁ দিক কাটে, অর্থাৎ পুরনো অংশ।

  3. ECE হাতে করুন: ১০০টি উত্তর, তিনটি ঝুড়ি: (n = ৫০, গড় আত্মবিশ্বাস ০.৫৫, ঠিক ২০), (n = ৩০, ০.৭৫, ঠিক ১৫), (n = ২০, ০.৯৫, ঠিক ১২)। মোট নির্ভুলতা, গড় আত্মবিশ্বাস আর ECE বের করুন। Model কোন দিকে ভুল করছে?

    নির্ভুলতা: (২০ + ১৫ + ১২) ÷ ১০০ = ০.৪৭. গড় আত্মবিশ্বাস: ০.৫ × ০.৫৫ + ০.৩ × ০.৭৫ + ০.২ × ০.৯৫ = ০.৬৯.

    ECE = ০.৫ × |০.৫৫ − ০.৪০| + ০.৩ × |০.৭৫ − ০.৫০| + ০.২ × |০.৯৫ − ০.৬০| = ০.০৭৫ + ০.০৭৫ + ০.০৭ = ০.২২. প্রতিটি ঝুড়িতে আত্মবিশ্বাস বেশি, তাই model overconfident.

  4. নকশা সমালোচনা করুন: বাজারঘরের একটি সহায়তা-বট কোনো নথি ছাড়া সরাসরি LLM-কে ডাকে, tokenizer(text, truncation=True, max_length=512) দিয়ে chat কাটে, আর উত্তরের গড় log-probability −০.২-এর বেশি হলে “যাচাই করা উত্তর” ব্যাজ দেখায়। অন্তত চারটি সমস্যা বলুন, প্রতিটির সমাধানসহ।
    • ডিফল্ট truncation: ডান দিক কাটলে নতুন প্রশ্ন আর generation prompt-ই হারাতে পারে। সমাধান: পুরো বার্তা ধরে পুরনো পালা বাদ, system থাকবে, শেষে template আবার বসানো।
    • উত্তরের জায়গা নেই: Input-এর সীমায় output-এর হিসাব ধরা হয়নি। সমাধান: window থেকে উত্তরের token আগে বিয়োগ।
    • নিজস্ব তথ্যের উৎস নেই: ফেরতের নিয়ম বা অর্ডারের অবস্থা model জানার কথা নয়। সমাধান: ডেটাবেস বা নথি থেকে উত্তর, উৎসসহ; না পেলে “জানি না” আর মানুষের কাছে পাঠানো।
    • ব্যাজের ভিত্তি ভুল: Log-probability সত্য মাপে না। “যাচাই করা” বলা যাবে শুধু বাইরের উৎসে মিললে। সংখ্যাটি রাখলে সেটি নিজের ডেটায় calibration মেপে নিতে হবে, আর নাম হবে অন্য কিছু।

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

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

ক্রমএকটি chat বার্তা থেকে পরের token পর্যন্ত ধাপগুলো সাজান (▲▼ চেপে সরান)

  1. Chat template বার্তাগুলোকে একটি লেখায় সাজায়
  2. Tokenizer লেখাকে token id-তে ভাঙে
  3. Embedding id থেকে (T, d) আকারের vector বানায়
  4. Transformer block-গুলো vector বদলায়, K আর V জমা হয়
  5. lm_head শেষ সারিকে vocabulary-র logit-এ নেয়
  6. Softmax আর decoding পরের token বেছে নেয়

একটি chatbot পুরো chat-কে একটি লেখায় বানিয়ে ডিফল্ট right truncation দিয়ে সীমায় কাটে। সবচেয়ে বড় ঝুঁকি কোনটি?

  • System বার্তা হারানো
  • নতুন প্রশ্ন আর generation prompt হারানো
  • Tokenizer error দিয়ে থেমে যাওয়া
  • Embedding-এর ওজন বদলে যাওয়া

V = 151936 আকারের একটি decoder-এ KV cache চালু। প্রথম pass-এর পর একটি নতুন token পাঠানো হলো, batch আকার ১। লজিটের shape কত?

  • (1, T + 1, 151936)
  • (1, 1, d)
  • (1, 1, 151936)
  • (151936,)

একটি LLM বলল “strawberry”-তে ২টি r আছে। সবচেয়ে সরাসরি যান্ত্রিক কারণ কোনটি?

  • Model অক্ষর নয়, token id দেখে; id বানান প্রকাশ করে না
  • Softmax অক্ষর গুনতে দেয় না
  • Context window ভরে গেছে
  • Temperature বেশি ছিল বলেই শুধু এটি হয়

একটি বানানো দোকানের প্রতিষ্ঠার সাল জিজ্ঞেস করলে LLM উচ্চ log-probability-তে “2010” লিখল। এটি থেকে কী বলা যায়?

  • তথ্যটি সম্ভবত সত্য
  • Model কোনো ডেটাবেসে তথ্যটি পেয়েছে
  • Model প্রশ্নটি পড়তে পারেনি, তাই সংখ্যাটি এলোমেলো
  • এই প্রসঙ্গে ওই token model-এর কাছে প্রত্যাশিত; সত্য কি না তা এই সংখ্যায় নেই

৯০% আত্মবিশ্বাসের ঝুড়িতে একটি model-এর নির্ভুলতা ৬০%. সবচেয়ে সঠিক বর্ণনা কোনটি?

  • Model underconfident
  • এই ঝুড়িতে model overconfident
  • Model এই ঝুড়িতে ভালোভাবে calibrated
  • Model-এর মোট নির্ভুলতা অবশ্যই ৬০%

Temperature scaling-এ সব logit-কে T > 0 দিয়ে ভাগ করা হয়। একই প্রশ্নে কী অপরিবর্তিত থাকে?

  • সবচেয়ে বড় সম্ভাবনার মান
  • ECE
  • কোন বিকল্পটি বেছে নেওয়া হলো (argmax)
  • Negative log-likelihood

শব্দকোষ

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

মনে রাখুন

  1. পথটি হলো template → token id → embedding → block → logits → softmax → পরের token; KV cache-এ পরের ধাপে শুধু নতুন token ঢোকে।
  2. Chat template model-এর চুক্তি। ভুল হলে error আসে না, মান পড়ে; বাইরের লেখায় special token আটকাতে হয়।
  3. Context window input আর output মিলিয়ে। পুরো বার্তা ধরে পুরনো পালা বাদ দিন; ডিফল্ট truncation-এ ভরসা নয়।
  4. Model id দেখে, অক্ষর নয়। গোনা, তারিখ আর পাটিগণিতের জন্য কোড বা tool ভালো পথ।
  5. Log-probability সত্য মাপে না। উৎস দিয়ে যাচাই করুন, নিজের ডেটায় calibration মাপুন; logit lens পর্যবেক্ষণ, প্রমাণ নয়।

পরের পর্বে: মডিউল ৯ শুরু। পর্ব ৫৩-এ Hugging Face ecosystem: এই পর্বে যে tokenizer আর model হাতে ডেকেছেন, তাদের চারপাশের transformers, datasets আর pipeline-এর কাঠামো।

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

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