পাঠ ০৬ · ৪০-এর মধ্যে · মডিউল ১

Forward pass — ছবি থেকে অনুমান

Forward pass — input → prediction
৬ মিনিট পড়া মাঝারি · Intermediate PyTorch

এই পাঠে যা শিখবেন

  • Forward pass-এর গাণিতিক সূত্র — layer-by-layer
  • Batching ও tensor shape — কীভাবে অনেক input একসাথে process হয়
  • একটি বাস্তব উদাহরণ — MNIST digit-এর forward pass হাতে-কলমে
  • PyTorch-এ forward pass implementation — `forward()` method

১ · Forward Pass কী

Network-এ input থেকে output পর্যন্ত গণনার পথ — forward pass (বা inference)। ওজন ($\mathbf{W}$) ও bias ($\mathbf{b}$) — fixed (training-এর সময় updated হয়, inference-এ unchanged)। শুধু input-এর উপর গণনা।

দু'টি pass-এর পার্থক্য

১) Forward pass: input → prediction। Inference-এ এটাই যথেষ্ট।
২) Backward pass: loss → gradient → weight update। শুধু training-এর সময়।

২ · Layer-by-Layer Computation

একটি $L$-layer network-এ forward pass:

$$\mathbf{a}^{(0)} = \mathbf{x} \quad (\text{input})$$ $$\mathbf{z}^{(l)} = \mathbf{W}^{(l)} \mathbf{a}^{(l-1)} + \mathbf{b}^{(l)}$$ $$\mathbf{a}^{(l)} = f^{(l)}(\mathbf{z}^{(l)})$$ $$\hat{\mathbf{y}} = \mathbf{a}^{(L)} \quad (\text{output})$$

প্রতিটি স্তরে দু'টি ধাপ — pre-activation $\mathbf{z}$ ও post-activation $\mathbf{a}$।

৩ · MNIST উদাহরণ — হাতে-কলমে

ছবি: ২৮×২৮ greyscale digit। Network: ৭৮৪ → ১২৮ → ১০।

Step Operation Tensor shape
১Image flatten(1, 784)
২Linear: W₁ @ x + b₁(1, 128)
৩ReLU activation(1, 128)
৪Linear: W₂ @ h + b₂(1, 10)
৫Softmax(1, 10)
৬argmax → digitscalar

মোট compute: $784 \times 128 + 128 \times 10 \approx 101{,}632$ multiply-add operation per image। আধুনিক CPU-তে ms-এর কম, GPU-তে μs।

Forward pass — MNIST digit recognition 28×28 image → digit 0-9 7 Input 28×28 = 784 flatten Linear 784 → 128 + bias W₁, b₁ ReLU max(0, z) non-linearity Linear 128 → 10 + bias W₂, b₂ Softmax probability (10,) → "7" argmax প্রতিটি Linear layer-এর গণনা: z = W @ x + b, তারপর activation forward pass = sequential matrix operations + nonlinearities batching: many images parallelly (B, 784) → (B, 128) → (B, 10)
একটি MNIST digit-এর forward pass — flatten → linear → ReLU → linear → softmax → digit। প্রতিটি ধাপ tensor operation।

৪ · Batching — অনেক input একসাথে

Real-world inference-এ — শুধু একটা ছবি না, ৩২/৬৪/২৫৬টি ছবি একসাথে process হয়। Batching = vectorization-এর extension।

Single image: $\mathbf{x} \in \mathbb{R}^{784}$
Batch of $B$: $\mathbf{X} \in \mathbb{R}^{B \times 784}$

Linear layer একই — শুধু shape বদলায়:

$$\mathbf{Z} = \mathbf{X} \mathbf{W}^T + \mathbf{b} \quad \text{(broadcasting)}$$

  • $\mathbf{X}$: $(B, 784)$
  • $\mathbf{W}$: $(128, 784)$
  • $\mathbf{Z}$: $(B, 128)$
Batching GPU-র জন্য critical। Single image-এ GPU-র ৯৫% capacity idle। Batch ৩২+-এ GPU saturated, throughput ৩২x বাড়ে। GPU memory limit-এ batch size ঠিক হয়।

৫ · PyTorch-এ Forward Pass — দু'ভাবে

উপায় ১: nn.Sequential

Python · PyTorch
import torch
import torch.nn as nn

model = nn.Sequential(
    nn.Linear(784, 128),
    nn.ReLU(),
    nn.Linear(128, 10),
)

# Random batch: 4 images, 784 pixels each
X = torch.randn(4, 784)

# Forward pass — শুধু model(X)
logits = model(X)
print("logits shape:", logits.shape)   # (4, 10)

# Probability ও prediction
probs = torch.softmax(logits, dim=1)
preds = probs.argmax(dim=1)
print("predictions:", preds.tolist())

    

উপায় ২: Custom forward()

Python · PyTorch
import torch
import torch.nn as nn
import torch.nn.functional as F

class MNISTNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(784, 128)
        self.fc2 = nn.Linear(128, 10)

    def forward(self, x):
        # x: (B, 784)
        z1 = self.fc1(x)         # (B, 128)
        a1 = F.relu(z1)          # (B, 128)
        z2 = self.fc2(a1)        # (B, 10)
        return z2                # logits, softmax বাহিরে

model = MNISTNet()
X = torch.randn(4, 784)
logits = model(X)
print(logits.shape)  # (4, 10)

    
Custom forward() — flexibility। CNN, RNN, Transformer-এ skip connection, branching ইত্যাদি লাগে। Sequential simple cases-এ যথেষ্ট।

৬ · Inference Mode — Optimization

  • torch.no_grad(): gradient computation off — memory ও compute কম।
  • model.eval(): dropout, batchnorm-এর behavior switch করে।
  • torch.inference_mode(): stricter, faster than no_grad।
Python · PyTorch
model.eval()
with torch.inference_mode():
    logits = model(X)
    preds = logits.argmax(dim=1)

    

৭ · GPU-এ Forward Pass

  • Device transfer: X.to('cuda'), model.to('cuda') — দু'টোই same device-এ থাকতে হবে।
  • Mixed precision: FP16 inference — ২x faster।
  • Compilation: torch.compile(model) — graph optimization।
  • Quantization: INT8 inference — mobile-এ usable।
Forward pass — DL inference-এর কেন্দ্র। ChatGPT-এর প্রতিটি token = একটি forward pass। Latency, throughput, cost — সবই forward pass-এর efficiency-র উপর নির্ভর।

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

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ একটি GPT-4 inference-এ একটি token generate করতে কত compute লাগে? কেন token-by-token generation এত expensive?

আজকের LLM economics-এর কেন্দ্রে এই প্রশ্ন।

GPT-4 size estimate:

  • Parameter count: ~১.৭৬ trillion (rumored, MoE architecture)।
  • Active parameters per token: ~২২০ billion।
  • Per token forward pass: ~৪৪০ billion FLOPs (২x parameters)।

একটি token-এর journey:

  • ১. Input embedding lookup।
  • ২. Position embedding যোগ।
  • ৩. ১২০+ Transformer blocks — প্রতিটিতে attention + MLP।
  • ৪. Final linear layer — vocabulary projection (50K+ tokens)।
  • ৫. Softmax + sample।

Sequential nature:

  • Token $t$-এর জন্য — token $1, 2, \ldots, t-1$ লাগে।
  • Parallel possible না autoregressive generation-এ।
  • ১০০ token output = ১০০ sequential forward pass।

KV cache optimization:

  • Past tokens-এর Key, Value state cache।
  • প্রতি new token-এর জন্য — শুধু সেই token-এর Q compute।
  • Massive speedup, কিন্তু memory cost।
  • Long context-এ KV cache GB+ — main bottleneck।

Latency breakdown:

  • Time to first token: ~১০০ms (prefill phase)।
  • Per token after: ~২০-৫০ms।
  • ১০০০ token response: ২০-৫০ second।

Cost economics (২০২৪):

  • GPT-4 input: $30/1M tokens।
  • GPT-4 output: $60/1M tokens।
  • Output বেশি costly — sequential decode।
  • Caching, speculative decoding — cost কমাতে।

Optimization frontier:

  • Speculative decoding: small model draft, large verify। ২-৩x speedup।
  • Batching: multiple users together — GPU utilization ৯০%+।
  • Quantization: INT8/INT4 weights — memory ২-৪x কম।
  • Distillation: small model trained from large।
  • Architecture: MoE — same compute, more capacity।

Bangladesh perspective:

  • Self-hosting LLM impractical for most।
  • API-based usage — cost-aware design।
  • Smaller models (7B-13B) — local Bangla LLM possible।
  • Edge device — quantized 1-3B model।

মূল উপলব্ধি: Forward pass — DL-এর computational reality। Token-level cost, latency, memory — সবই engineering challenges। Algorithm-level optimization (attention) থেকে hardware-level (GPU, TPU) — সব stack-এ। ChatGPT-এর response speed = forward pass engineering miracle।

প্র ০২ "Batching" আশ্চর্যজনকভাবে GPU performance বাড়ায়। কিন্তু latency-sensitive applications-এ (যেমন real-time speech) batching কঠিন। কীভাবে balance করা হয়?

Production ML system-এর সবচেয়ে practical engineering trade-off।

Batching-এর সুবিধা:

  • GPU memory bandwidth — high throughput।
  • Compute efficiency — matrix multiply scaled।
  • Cost per inference — ১০-১০০x কমে।

Latency cost:

  • Batch fill-এর জন্য wait — first user delayed।
  • Single user-এর latency বাড়ে।
  • Tail latency unpredictable।

Throughput vs latency curve:

  • Batch size 1: low throughput, low latency।
  • Batch size 32: 30x throughput, 1.5x latency।
  • Batch size 256: 80x throughput, 3x latency।
  • Saturation — large batch GPU-saturated।

Dynamic batching:

  • Server requests pool — fill batch within time window (e.g., 50ms)।
  • Triton, TorchServe, TF Serving — built-in।
  • Latency SLA-এর সাথে balance।

Continuous batching (LLM):

  • vLLM-এর innovation।
  • Different users-এর different sequence length একসাথে।
  • Token-level batch — fixed batch size না।
  • ২-৪x throughput improvement।

Use case-নির্ভর strategies:

  • Real-time speech (voice assistant): small batch, low latency priority।
  • Bulk inference (batch prediction): max batch size।
  • Web chatbot: dynamic batching, ~৫০ms wait acceptable।
  • Real-time CV (autonomous car): single-image, hardware acceleration।

Asynchronous architecture:

  • Streaming output — first token দ্রুত, rest stream।
  • WebSocket/SSE — incremental delivery।
  • User perception of speed > total latency।

Hardware considerations:

  • GPU (A100, H100): high batch preferred।
  • TPU: static batch size — recompile expensive।
  • CPU: small batch acceptable।
  • Edge (mobile): single inference, latency-only।

Bangladesh use cases:

  • Bangla speech-to-text — streaming, low batch।
  • Document OCR — bulk batch।
  • Chatbot — dynamic batching।
  • Recommendation API — high QPS, max batch।

মূল উপলব্ধি: Batching — DL inference-এর core efficiency lever। কিন্তু blanket "batch বেশি = ভাল" — wrong। Application-নির্ভর tuning। Production ML engineer-এর core skill।

প্র ০৩ Forward pass-এ model.eval() ও torch.no_grad() — দু'টোর কী পার্থক্য? কখন কোনটি use করবেন?

PyTorch-এর সবচেয়ে confusing — কিন্তু critical — detail।

model.eval():

  • Model-এর internal flag toggle।
  • Affects: Dropout, BatchNorm, certain custom layers।
  • Dropout — training-এ random drop, eval-এ identity।
  • BatchNorm — training-এ batch statistics, eval-এ running statistics।
  • Gradient computation-এ effect নাই।

torch.no_grad():

  • Context manager — autograd off।
  • Affects: gradient tracking — disabled।
  • Memory savings — intermediate activation save করতে হয় না।
  • Speed — backward graph build skipped।
  • Layer behavior-এ effect নাই।

Difference summary:

  • eval() = layer mode change।
  • no_grad() = autograd off।
  • Independent — দু'টো আলাদা।

Common mistake (১):

  • শুধু no_grad() — Dropout এখনো random! Test-এ inconsistent prediction।
  • Solution: eval()-ও call করুন।

Common mistake (২):

  • শুধু eval() — gradient compute হচ্ছে, memory waste।
  • Solution: no_grad()-ও wrap করুন।

Best practice:

model.eval()
with torch.no_grad():
    pred = model(X)

বা even better (PyTorch 1.9+):

model.eval()
with torch.inference_mode():  # stricter, faster
    pred = model(X)

torch.inference_mode():

  • no_grad-এর evolved version।
  • Tensor mutation also disabled।
  • Slightly faster।
  • Some restrictions — output tensor in-place modification disallowed।

Training-এর সাথে contrast:

model.train()      # Dropout, BatchNorm-এ train mode
optimizer.zero_grad()
output = model(X)
loss = loss_fn(output, y)
loss.backward()    # gradient compute
optimizer.step()

Validation loop pattern:

for epoch in range(epochs):
    # Training phase
    model.train()
    for X, y in train_loader:
        ...

    # Validation phase
    model.eval()
    with torch.no_grad():
        for X, y in val_loader:
            ...

Subtle pitfall:

  • BatchNorm running stats train mode-এ update। eval-এ frozen।
  • Test-এ ভুলে train mode — performance vary।

মূল উপলব্ধি: দু'টো mechanism — different concerns. Inference-এ দুটোই দরকার। Production code-এ ভুলে গেলে — hard to debug bug। Always pair them।

প্র ০৪ Forward pass-এ "logits" শব্দটা কেন? কেন output layer-এ softmax চাপাতে নেই, loss function-এ ছেড়ে দেওয়া হয়?

PyTorch convention-এর গভীর — যা beginner-দের কাছে cryptic।

"Logits" শব্দটির অর্থ:

  • Statistics-এ logit = log-odds, $\log(p/(1-p))$।
  • DL-এ — pre-softmax raw output।
  • Convention — "scores" বা "raw activations"।
  • Range: $(-\infty, +\infty)$।

Logits → probability:

  • Binary: $p = \sigma(\text{logit})$।
  • Multi-class: $\mathbf{p} = \text{softmax}(\text{logits})$।

কেন softmax model-এ না:

  • Numerical stability: raw logits-এ extreme values — softmax overflow। Log-softmax + NLL combine করে stable।
  • PyTorch convention: CrossEntropyLoss = log_softmax + NLLLoss internally।
  • Direct softmax + log = numerical disaster: $\log(0) = -\infty$।
  • LogSumExp trick: CrossEntropy internally uses।

PyTorch's CrossEntropyLoss:

# Input: raw logits (no softmax!)
# Target: class indices
loss_fn = nn.CrossEntropyLoss()
logits = model(X)              # (B, num_classes)
targets = torch.tensor([3, 7, 1, 5])  # class indices
loss = loss_fn(logits, targets)

Beginner mistake:

# WRONG — double softmax!
output = nn.Softmax(dim=1)(logits)
loss = nn.CrossEntropyLoss()(output, targets)  # BUG

Inference-এ softmax কখন apply:

  • Probability দরকার — যেমন confidence display।
  • Top-k sampling — sampling-এর জন্য probability।
  • Calibration analysis।
  • আর্গmax-এ — softmax লাগে না (softmax monotonic)।

Binary classification special:

  • BCEWithLogitsLoss: sigmoid + BCE combined। Stable।
  • BCELoss: sigmoid first (less stable)।
  • Convention — WithLogits version preferred।

Why "logits" terminology:

  • Multinomial logistic regression থেকে inherited।
  • Statistics-এ formal usage।
  • "Pre-softmax score" — verbose, "logits" — short।

Practical implications:

  • Save model — save logits-producing version।
  • Export to ONNX — softmax separate।
  • Distillation — KL divergence on logits।
  • Interpretation — logits-এর magnitude meaningful (calibration)।

Logit space-এর gain:

  • Linear arithmetic — concept editing (control vectors)।
  • Knowledge distillation — soft targets।
  • Adversarial robustness analysis।

মূল উপলব্ধি: "Logits" — PyTorch idiom। Output-এ softmax না — convention, performance, ও stability-র জন্য। CrossEntropyLoss expects logits — beginner-দের ৩৩% bug এই double-softmax। Convention মেনে চলা = bug-free code।

অনুশীলন

  1. Compute: একটি network: input=100, hidden=64, output=5। Batch size 32। প্রতিটি tensor-এর shape কী?
    • X: (32, 100)
    • W₁: (64, 100), b₁: (64,)
    • z₁ = X @ W₁.T + b₁: (32, 64)
    • a₁ = ReLU(z₁): (32, 64)
    • W₂: (5, 64), b₂: (5,)
    • z₂ = a₁ @ W₂.T + b₂: (32, 5)
    • output (logits): (32, 5)
  2. FLOPs: উপরের network-এ একটি forward pass (single image)-এ approximate FLOPs?
    • Layer 1: 100 × 64 = 6400 multiply-add ≈ 12,800 FLOPs।
    • Layer 2: 64 × 5 = 320 multiply-add ≈ 640 FLOPs।
    • Activation: ~64 FLOPs।
    • Total: ~13,500 FLOPs per image। Batch 32: ~432,000 FLOPs।
    • Modern GPU (RTX 4090) ~১০¹⁴ FLOPs/sec — microsecond-এর কম।
  3. Code: উপরের MNIST network-এ forward pass-এ একটি bug — কোথায়?
    def forward(self, x):
        z1 = self.fc1(x)
        z2 = self.fc2(z1)  # bug?
        return z2

    Activation missing! z₁-এর পর ReLU না দেওয়ায় — পুরো network = একটি single linear function (composition of linear)। UAT-এর কোনো লাভ নেই — শুধু linear classifier।

    Fix:

    z1 = self.fc1(x)
    a1 = F.relu(z1)
    z2 = self.fc2(a1)
    return z2

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

কোড রানার কাজ না করলে? ব্রাউজারে কাজ না করলে Google Colab ব্যবহার করুন — Google-এর ফ্রি অনলাইন Python পরিবেশ, শুধু Gmail অ্যাকাউন্ট লাগে।
পূর্ববর্তী পাঠ
পাঠ ০৫ · Universal approximation