Part 04 · LLM Internals
Decoding and Context Windows
Send চাপার পরে কী হয়: token ধরে ধরে generation, temperature আর top-p, যে KV cache এটাকে দ্রুত করে, আর context window এর সীমা আসলে কতটা খরচের।
⏱ 1-2 ঘণ্টা · Station 29 / 92
জিনিসটা কী, কেন দরকার
Send চাপার পরে model পুরো answer একবারে লেখে না। সে একটা token বানায়, text এর সাথে জুড়ে দেয়, আর পরেরটা বানাতে আবার চলে, যতক্ষণ না একটা end token দেয় বা দৈর্ঘ্যের সীমায় পৌঁছায়। 300 token এর একটা answer মানে model এর ভেতর দিয়ে 300 বার যাওয়া।
এই loop এর ভেতরে দুইটা engineering প্রশ্ন। Probability থেকে কোন token বাছবে: সবসময় সবচেয়ে সম্ভাব্যটা, নাকি random তোলা, আর কতটা random? এটাই decoding, আর temperature, top-p এর মতো setting user কী দেখবে বদলে দেয়। আর প্রতিটা step কীভাবে দ্রুত করবে: KV cache ছাড়া প্রতিটা step এখন পর্যন্ত পুরো text এর কাজ আবার করত। দুইটা মিলে context window ও ব্যাখ্যা করে, কতগুলো token আঁটবে তার শক্ত সীমা, আর লম্বা কথোপকথন কেন ধীর আর দামি হয়।
ভেতরে কীভাবে চলে
Autoregressive generation। Model prompt পড়ে (prefill, prompt এর সব token এর জন্য একসাথে), vocabulary র প্রতিটা token এর score (logits) দেয়, একটা token বাছা হয়, জোড়া হয়, আর loop আবার চলে (decode phase, একবারে একটা token)।
Token বাছা:
- Greedy: সবসময় সবচেয়ে বেশি probability টা নাও। Deterministic, ছোট তথ্যভিত্তিক বা structured output এ ভালো, কিন্তু লম্বা text এ ঘুরপাক খেয়ে একই কথা বলতে পারে।
- Temperature T: softmax এর আগে logits কে T দিয়ে ভাগ করো। T < 1 distribution কে ধারালো করে (নিরাপদ, একঘেয়ে), T > 1 চ্যাপ্টা করে (বৈচিত্র্য বেশি, ভুলও বেশি)। T → 0 মানে greedy।
- Top-k: শুধু সবচেয়ে সম্ভাব্য k টা token থেকে sample করো।
- Top-p (nucleus): সবচেয়ে ছোট সেই token এর দল থেকে sample করো যাদের probability যোগ করলে p হয় (ধরো 0.9)। Top-k এর মতো না, দলটা নিজেকে মানিয়ে নেয়: model confident হলে অল্প কয়েকটা token যোগ্য হয়; দ্বিধায় থাকলে বেশি।
Sampling অসম্ভাব্য token এর লম্বা লেজটা কেটে দেয়, যেখানে আবোল তাবোল থাকে, আর বৈচিত্র্য রাখে।
KV cache। Self-attention এ প্রতিটা নতুন token এর আগের প্রতিটা token এর key আর value লাগে। একবার হিসাব হলে এগুলো আর বদলায় না (causal mask মানে আগের token পরেরগুলো কখনো দেখে না), তাই প্রতি layer এ একটা cache এ জমিয়ে রাখা হয়। তখন প্রতিটা step শুধু নতুন একটা token এর K আর V হিসাব করে আর বাকিটা memory থেকে পড়ে। Cache ছাড়া n টা token generate করতে মোটামুটি n² বার token-pass লাগে; cache সহ n।
Context window। Model সর্বোচ্চ কয়টা token সামলায় (prompt + history + answer), এটা ঠিক হয় তার training আর positional ব্যবস্থা দিয়ে (lesson 2)। এর বেশি হলে text বাদ দিতে বা সারাংশ করতে হয় (Part 6)। আর এর ভেতরেও, লম্বা context মানে বড় KV cache, প্রতি step এ বেশি attention এর কাজ, বেশি latency, বেশি খরচ।
একটা product-catalogue bot কে prompt থেকে হুবহু product ID টা JSON এ দিতে হবে। User রা বলছে মাঝে মাঝে সে একটু আলাদা ID দেয়। কোন setting বদলালে সবচেয়ে কাজ হবে?
ছবিতে দেখো
Code এ decoding এর setting
আসল মনে হয় এমন একটা পরের-token distribution এ decoding এর setting: temperature, top-k আর top-p, তারপর প্রতিটা setting এ 1,000 টা sample। তারপর KV cache এর হিসাব।
import math
import random
# The model's scores (logits) for the next word after "Your order will arrive ..."
words = ["today", "tomorrow", "soon", "late", "never", "yesterday"]
logits = [3.0, 2.6, 2.2, 0.8, -1.0, -2.5]
def softmax(xs, temperature=1.0):
xs = [x / temperature for x in xs]
m = max(xs)
e = [math.exp(x - m) for x in xs]
return [v / sum(e) for v in e]
print("temperature reshapes the distribution:")
for t in (0.2, 1.0, 2.0):
p = softmax(logits, t)
print(f" T={t:<4}" + " ".join(f"{w} {q:.2f}" for w, q in zip(words, p)))
def top_k(p, k):
keep = sorted(range(len(p)), key=lambda i: -p[i])[:k]
return [p[i] if i in keep else 0 for i in range(len(p))]
def top_p(p, threshold):
order = sorted(range(len(p)), key=lambda i: -p[i])
keep, total = set(), 0.0
for i in order: # smallest set of words whose probability reaches the threshold
keep.add(i); total += p[i]
if total >= threshold:
break
return [p[i] if i in keep else 0 for i in range(len(p))]
p = softmax(logits)
print("\nkept by top-k=3: ", [w for w, q in zip(words, top_k(p, 3)) if q])
print("kept by top-p=0.9:", [w for w, q in zip(words, top_p(p, 0.9)) if q])
def sample(probs, rng):
total = sum(probs)
return rng.choices(range(len(probs)), weights=[q / total for q in probs])[0]
print("\n1,000 samples per setting:")
for name, probs in [("greedy", None), ("T=1.0", p), ("T=1.0, top-p 0.9", top_p(p, 0.9)), ("T=2.0", softmax(logits, 2.0))]:
rng = random.Random(42)
counts = [0] * len(words)
for _ in range(1000):
i = max(range(len(words)), key=lambda j: logits[j]) if probs is None else sample(probs, rng)
counts[i] += 1
print(f" {name:<17}" + " ".join(f"{w} {c}" for w, c in zip(words, counts) if c))
# KV cache: generating token n means attending over all n tokens so far.
# Without a cache, the keys and values of every earlier token are recomputed each step.
prompt, new = 1000, 200
without = sum(range(prompt, prompt + new)) # K,V computations if recomputed every step
with_cache = prompt + new # each token's K,V computed once, then reused
print(f"\nK/V computations for a {prompt}-token prompt + {new} new tokens:")
print(f" without cache {without:,} with cache {with_cache:,} ({without // with_cache}x less work)")
d_model, layers, bytes_ = 4096, 32, 2 # a 7B-class model in float16
kv_per_token = 2 * layers * d_model * bytes_
print(f" cache memory for a 7B-class model: {kv_per_token / 1024:.0f} KB per token, "
f"{kv_per_token * 8192 / 2**30:.1f} GB for one 8,192-token conversation")
Output:
temperature reshapes the distribution:
T=0.2 today 0.87 tomorrow 0.12 soon 0.02 late 0.00 never 0.00 yesterday 0.00
T=1.0 today 0.44 tomorrow 0.30 soon 0.20 late 0.05 never 0.01 yesterday 0.00
T=2.0 today 0.33 tomorrow 0.27 soon 0.22 late 0.11 never 0.04 yesterday 0.02
kept by top-k=3: ['today', 'tomorrow', 'soon']
kept by top-p=0.9: ['today', 'tomorrow', 'soon']
1,000 samples per setting:
greedy today 1000
T=1.0 today 429 tomorrow 300 soon 202 late 55 never 9 yesterday 5
T=1.0, top-p 0.9 today 454 tomorrow 322 soon 224
T=2.0 today 327 tomorrow 254 soon 231 late 111 never 51 yesterday 26
K/V computations for a 1000-token prompt + 200 new tokens:
without cache 219,900 with cache 1,200 (183x less work)
cache memory for a 7B-class model: 512 KB per token, 4.0 GB for one 8,192-token conversation
কী দেখলাম:
- Temperature: T = 0.2 এ "today" পায় 87%: প্রায় greedy। T = 2.0 এ "never" আর "yesterday" ও চোখে পড়ার মতো chance পায়। Logits বদলায়নি, শুধু কতটা ধারালোভাবে probability হচ্ছে সেটা বদলেছে।
- Top-k = 3 আর top-p = 0.9 এখানে একই তিনটা শব্দ রাখে (today, tomorrow, soon মিলে 94%)। আরো চ্যাপ্টা distribution এ top-p বেশি শব্দ রাখত; ধারালো হলে কম।
- Sample গুলোই গল্পটা বলে। Greedy 1,000 এ 1,000 বার "today" তোলে। সাধারণ T = 1.0 এ 1,000 এ 5 বার "arrive yesterday" আর 9 বার "never": কম, কিন্তু কোনো না কোনো user দেখবেই। Top-p 0.9 যোগ করলে ওগুলো পুরো বাদ, আর অর্থপূর্ণ শব্দগুলোর মধ্যে বৈচিত্র্য থাকে। T = 2.0 এ "yesterday" 26 বার।
- KV cache: 1,000 token এর prompt আর 200 টা নতুন token এ প্রতি step এ key আর value আবার হিসাব করলে 219,900 বার token এর হিসাব; cache এ লাগে 1,200, 183 গুণ কম।
- কিন্তু cache memory খায়: float16 এ 7B ধরনের model এ প্রতি token এ 512 KB, তাই 8,192 token এর একটা কথোপকথনেই 4 GB cache। এজন্যই অনেকগুলো লম্বা কথোপকথন serve করতে এত GPU memory লাগে (Part 9)।
Production এ যা মাথায় রাখবে
- কাজ অনুযায়ী setting দাও। Extraction, classification, code, JSON: temperature 0 থেকে 0.3। Chat: 0.7 এর আশেপাশে, top-p 0.9। Brainstorming: আরো বেশি। স্পষ্ট করে set করো; provider এর default আলাদা আলাদা।
- Temperature 0 পুরোপুরি deterministic না। Batching আর GPU র floating-point ক্রম মাঝে মাঝে তবুও output বদলায়। এমন test বানিও না যেটা প্রতিবার হুবহু একই text আশা করে; তার বদলে ধর্ম check করো।
- Output এর সীমা দাও। সবসময় নতুন token এর একটা সর্বোচ্চ সংখ্যা set করো, যাতে ঘুরপাক খাওয়া model bill বাড়াতে বা worker আটকে রাখতে না পারে।
- Latency র দুইটা অংশ: time to first token (prefill, prompt লম্বা হলে বাড়ে) আর time per output token (decode)। লম্বা prompt এ কিছু দেখার আগেই user অপেক্ষা করে; লম্বা answer এ stream ধীর লাগে। দুইটাই মাপো (Part 9)।
- Token stream করো। Generate হওয়ার সাথে সাথে token দেখালে 10 second এর answer ও দ্রুত লাগে। Tokenization lesson এর অর্ধেক অক্ষরের সমস্যাটা মনে রেখো।
- Stop sequence একটা চিহ্ন (যেমন বন্ধ করার tag) এলেই generation আগেভাগে থামায়, token বাঁচায় আর output পরিষ্কার রাখে।
এক কথায়
LLM একবারে একটা token বানায়: score, বাছো, জোড়ো, আবার। Temperature, top-k আর top-p ঠিক করে বাছাই কীভাবে হবে (ধারালো আর নিরাপদ বনাম বৈচিত্র্যময় আর ঝুঁকির), KV cache আগের key আর value রেখে দেয় যাতে প্রতি step এ শুধু নতুন token process হয়, আর context window মোট token এর সীমা টানে, লম্বা context এ memory আর সময় দুইটাই বেশি লাগে।
নিজে করে দেখো
Example এর logits দিয়ে, কোন temperature এ "today" অন্তত 99% probability পায়? আর একই 7B ধরনের model এ একসাথে 50 জন user, প্রত্যেকের 4,000 token এর কথোপকথন হলে কত KV cache memory লাগবে?
Answer
"today" আর "tomorrow" এর logits এ পার্থক্য 0.4। T = 0.05 এ সেই ফাঁক হয় 8, আর e^−8 ≈ 0.0003, তাই "today" পায় প্রায় 99.97%; T = 0.1 এ ফাঁক 4, "today" পায় প্রায় 98%। 99% পার হয় T = 0.09 (98.8%) আর T = 0.08 (99.3%) এর মাঝখানে। Cache: 512 KB × 4,000 token ≈ প্রতি user এ 2 GB, × 50 user ≈ 100 GB, শুধু cache এর জন্যই একটা 80 GB GPU র চেয়ে বেশি, 14 GB weight এর আগেই। PagedAttention আর grouped-query attention ঠিক এই সমস্যাতেই আঘাত করে (Part 9)।
Station stamp
Chapter শেষ করো
পড়া শেষ হলে Complete চাপো, progress save হবে। XP শুধু Pro তে — Pro থাকলে এই chapter এ +10 XP পেতে।
Pro নিয়ে XP পাও →