BelajarKoding Logobelajarkoding

Platform belajar web development Indonesia. Artikel, cheat sheets, roadmap, dan code challenges untuk developer Indonesia.

Navigasi

  • Artikel
  • Cheat Sheets
  • Roadmap
  • Challenges
  • Pricing
  • Search

Produk Lain

  • JagoHermes
  • KelasClaude
  • KilatKoding
  • BelajarVibeCoding
  • JualanKoding

Support

  • Privacy Policy
  • Terms of Service
  • Email

© 2026 BelajarKoding. All rights reserved.

Galih PratamaBagian dari ekosistem Galih Pratama
belajarkoding LogobyGalih Pratama
RoadmapArtikelCheat SheetsChallengesUpgrade
belajarkoding LogobyGalih Pratama
RoadmapArtikelCheat SheetsChallengesUpgrade
belajarkoding LogobyGalih Pratama
RoadmapArtikelCheat SheetsChallengesUpgrade

Daftar Isi

Mengapa AttentionScaled Dot-Product Attention dari NolMulti-Head AttentionPositional EncodingTransformer EncoderMasking: Padding dan KausalTransformer Decoder dan Cross-AttentionTransformer Lengkap di PyTorchTransformer Encoder-Decoder Sekali JalanBawaanInference dan DecodingDecoder-Only dan KV CacheRotary Positional Embedding (RoPE)Suhu dan SamplingDebugging TransformerGlossary
AIDeep LearningTransformerPyTorchPython

Transformers & Attention Cheat Sheet

Referensi cepat transformer dan attention mechanism. Scaled dot-product, multi-head attention, positional encoding, encoder-decoder, masking, dan inference. Buat kamu yang belajar AI engineering.

Python12 min read2.272 kata
Silakan login atau daftar untuk membaca cheat sheet ini.

#Mengapa Attention

Sebelum transformer, model sekuensial seperti RNN membaca token satu per satu. Setiap token hanya bisa melihat hidden state token sebelumnya. Ini lambat untuk paralelisasi dan sulit membawa informasi jarak jauh.

Attention membalik pendekatan itu. Setiap token melihat seluruh token lain sekaligus dan memutuskan seberapa penting masing-masing. Tidak ada urutan wajib baca. Semua posisi diproses paralel, dan informasi antar token yang jauh tetap bisa dihubungkan langsung.

text
sumber:   "saya"  "suka"  "belajar"  "koding"
target:   "aku"   "senang"  "coding"
 
perhatian token target "senang" tersebar ke token sumber
yang paling relevan, misalnya "suka" dan "belajar"

Bobot perhatian adalah distribusi probabilitas. Jumlahnya satu untuk tiap posisi query. Nilai besar berarti token itu dianggap relevan.

#Scaled Dot-Product Attention dari Nol

Rumus inti attention adalah satu baris. Query dikalikan dengan key, dibagi akar dimensi key, di-softmax, lalu dipakai membobot value.

text
attention(Q, K, V) = softmax(Q K^T / sqrt(d_k)) V

Pembagian dengan sqrt(d_k) menjaga dot product agar tidak membesar saat dimensi key naik. Tanpa pembagian itu, nilai dot product masuk ke daerah saturasi softmax dan gradient jadi kecil.

python
import math
 
import torch
import torch.nn.functional as F
 
 
def scaled_dot_product_attention(query, key, value, mask=None):
    """Query, key, value ber-shape (batch, heads, seq_len, dim)."""
    dim_key = key.size(-1)
    scores = query @ key.transpose(-2, -1) / math.sqrt(dim_key)
 
    if mask is not None:
        scores = scores.masked_fill(mask == 0, float("-inf"))
 
    weights = F.softmax(scores, dim=-1)
    return weights @ value
 
 
batch, heads, seq_len, dim = 2, 4, 10, 32
query = torch.randn(batch, heads, seq_len, dim)
key = torch.randn(batch, heads, seq_len, dim)
value = torch.randn(batch, heads, seq_len, dim)
 
output = scaled_dot_product_attention(query, key, value)
assert output.shape == (batch, heads, seq_len, dim)

key.transpose(-2, -1) menukar dua dimensi terakhir sehingga perkalian menghasilkan skor antara tiap query dan tiap key. masked_fill menempatkan -inf pada posisi yang harus diabaikan. Softmax dari -inf menjadi nol.

#Multi-Head Attention

Satu kepala attention hanya menangkap satu jenis hubungan. Delapan kepala memakai proyeksi berbeda sehingga model bisa melihat hubungan sintaksis dan semantik dalam subspace yang terpisah.

text
MultiHead(Q, K, V) = concat(head_1, ..., head_h) W_O
head_i = attention(Q W_Q_i, K W_K_i, V W_V_i)
python
import torch
from torch import nn
 
 
class MultiHeadAttention(nn.Module):
    def __init__(self, dim: int, num_heads: int, dropout: float = 0.0) -> None:
        super().__init__()
        assert dim % num_heads == 0, "dim harus habis dibagi num_heads"
        self.dim = dim
        self.num_heads = num_heads
        self.head_dim = dim // num_heads
 
        self.wq = nn.Linear(dim, dim, bias=False)
        self.wk = nn.Linear(dim, dim, bias=False)
        self.wv = nn.Linear(dim, dim, bias=False)
        self.wo = nn.Linear(dim, dim, bias=False)
        self.dropout = nn.Dropout(dropout)
 
    def forward(self, query, key, value, mask=None):
        batch, q_len, _ = query.shape
        _, kv_len, _ = key.shape
 
        q = self.wq(query).view(batch, q_len, self.num_heads, self.head_dim)
        k = self.wk(key).view(batch, kv_len, self.num_heads, self.head_dim)
        v = self.wv(value).view(batch, kv_len, self.num_heads, self.head_dim)
 
        q = q.transpose(1, 2)  # (batch, heads, q_len, head_dim)
        k = k.transpose(1, 2)
        v = v.transpose(1, 2)
 
        scores = q @ k.transpose(-2, -1) / math.sqrt(self.head_dim)
        if mask is not None:
            scores = scores.masked_fill(mask == 0, float("-inf"))
        weights = self.dropout(F.softmax(scores, dim=-1))
 
        context = weights @ v
        context = context.transpose(1, 2).contiguous().view(batch, q_len, self.dim)
        return self.wo(context)

bias=False pada proyeksi mengikuti praktik umum transformer modern, tapi ini bukan keharusan. view lalu transpose mengubah layout dari (batch, seq, heads, dim) menjadi (batch, heads, seq, dim) supaya operasi per kepala lebih rapi.

#Positional Encoding

Attention tidak peduli urutan token. ["kucing", "kejar", "tikus"] dan ["tikus", "kejar", "kucing"] menghasilkan distribusi perhatian yang berbeda makna, namun tanpa informasi posisi keduanya terlihat sama secara aritmetika.

Dua cara memberi informasi posisi: positional encoding sinyal sinus yang ditambahkan sebelum layer pertama, atau positional embedding yang ikut dipelajari.

python
class SinusoidalPositionalEncoding(nn.Module):
    def __init__(self, dim: int, max_len: int = 512) -> None:
        super().__init__()
        position = torch.arange(max_len).unsqueeze(1)
        div_term = torch.exp(
            torch.arange(0, dim, 2) * (-math.log(10000.0) / dim)
        )
        encoding = torch.zeros(max_len, dim)
        encoding[:, 0::2] = torch.sin(position * div_term)
        encoding[:, 1::2] = torch.cos(position * div_term)
        self.register_buffer("encoding", encoding.unsqueeze(0))
 
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return x + self.encoding[:, : x.size(1)]
 
 
seq_len, dim = 20, 64
pos_encoder = SinusoidalPositionalEncoding(dim)
x = torch.randn(2, seq_len, dim)
out = pos_encoder(x)
assert out.shape == (2, seq_len, dim)

register_buffer memastikan tensor encoding ikut pindah ke device dan tersimpan di state_dict, tetapi tidak diperbarui optimizer karena bukan parameter. Sinyal sinus memakai frekuensi berbeda tiap dimensi sehingga model dapat belajar hubungan posisi relatif.

#Transformer Encoder

Encoder memproses seluruh input sekaligus. Setiap block berisi multi-head self-attention, skip connection, layer norm, lalu feed-forward network. Skip connection menjaga gradient mengalir ke layer yang dalam.

text
block:
  x = x + attention(layer_norm(x))
  x = x + feed_forward(layer_norm(x))

Urutan pre-norm ini adalah standar transformer modern. Layer norm ditaruh sebelum sublayer, bukan sesudah, supaya training lebih stabil.

python
class TransformerEncoderBlock(nn.Module):
    def __init__(self, dim: int, num_heads: int, ff_dim: int, dropout: float = 0.1) -> None:
        super().__init__()
        self.attention = MultiHeadAttention(dim, num_heads, dropout)
        self.feed_forward = nn.Sequential(
            nn.Linear(dim, ff_dim),
            nn.GELU(),
            nn.Dropout(dropout),
            nn.Linear(ff_dim, dim),
            nn.Dropout(dropout),
        )
        self.norm1 = nn.LayerNorm(dim)
        self.norm2 = nn.LayerNorm(dim)
        self.dropout = nn.Dropout(dropout)
 
    def forward(self, x: torch.Tensor, mask=None) -> torch.Tensor:
        attended = self.attention(self.norm1(x), self.norm1(x), self.norm1(x), mask)
        x = x + self.dropout(attended)
        x = x + self.feed_forward(self.norm2(x))
        return x

Self-attention memakai query == key == value, karena token membandingkan dirinya dengan token lain dalam urutan yang sama. Cross-attention nanti memakai query dari satu urutan dan key/value dari urutan lain.

#Masking: Padding dan Kausal

Dua mask muncul terus. Padding mask menyembunyikan token kosong supaya tidak ikut memengaruhi perhatian. Causal mask mencegah token melihat token sesudahnya, wajib untuk decoder autoregressive.

python
def create_padding_mask(tokens: torch.Tensor, pad_idx: int) -> torch.Tensor:
    # 1 untuk posisi valid, 0 untuk padding. Shape (batch, seq).
    return (tokens != pad_idx).to(torch.long)
 
 
def create_causal_mask(seq_len: int) -> torch.Tensor:
    # Segitiga bawah bernilai 1, token hanya melihat posisi <= dirinya.
    return torch.tril(torch.ones(seq_len, seq_len, dtype=torch.long))
 
 
pad_mask = create_padding_mask(torch.tensor([[2, 5, 0, 0]]), pad_idx=0)
causal_mask = create_causal_mask(4)
 
print(pad_mask)     # tensor([[1, 1, 0, 0]])
print(causal_mask)

Pada attention dengan shape (batch, heads, q_len, kv_len), mask juga harus ber-shape broadcastable ke (q_len, kv_len) atau (batch, 1, q_len, kv_len). Satu baris masked_fill(mask == 0, float("-inf")) sudah cukup selama mask mengembang ke dimensi batch dan head.

#Transformer Decoder dan Cross-Attention

Decoder autoregressive menghasilkan output satu token per langkah. Setiap block decoder punya dua attention: self-attention dengan causal mask, lalu cross-attention yang menarik informasi dari encoder.

python
class TransformerDecoderBlock(nn.Module):
    def __init__(self, dim: int, num_heads: int, ff_dim: int, dropout: float = 0.1) -> None:
        super().__init__()
        self.self_attention = MultiHeadAttention(dim, num_heads, dropout)
        self.cross_attention = MultiHeadAttention(dim, num_heads, dropout)
        self.feed_forward = nn.Sequential(
            nn.Linear(dim, ff_dim),
            nn.GELU(),
            nn.Linear(ff_dim, dim),
        )
        self.norm1 = nn.LayerNorm(dim)
        self.norm2 = nn.LayerNorm(dim)
        self.norm3 = nn.LayerNorm(dim)
 
    def forward(self, x, encoder_output, causal_mask=None, padding_mask=None):
        # Self-attention memakai query, key, value dari urutan target.
        x = x + self.self_attention(self.norm1(x), self.norm1(x), self.norm1(x), causal_mask)
        # Cross-attention: query dari target, key dan value dari encoder.
        x = x + self.cross_attention(self.norm2(x), encoder_output, encoder_output, padding_mask)
        x = x + self.feed_forward(self.norm3(x))
        return x

Perhatikan perbedaan isi tiap attention. Self-attention di decoder memakai tiga input yang sama. Cross-attention memakai query dari decoder dan key/value dari encoder. Di sinilah informasi sumber masuk ke proses generasi.

#Transformer Lengkap di PyTorch

PyTorch menyediakan komponen jadi: nn.TransformerEncoderLayer, nn.TransformerEncoder, dan nn.Transformer untuk model encoder-decoder utuh. Tulis sendiri saat belajar supaya paham, lalu pakai komponen bawaan untuk produksi.

python
import torch
from torch import nn
 
encoder_layer = nn.TransformerEncoderLayer(
    d_model=512,
    nhead=8,
    dim_feedforward=2048,
    dropout=0.1,
    activation="gelu",
    batch_first=True,
    norm_first=True,
)
encoder = nn.TransformerEncoder(encoder_layer, num_layers=6)
 
src = torch.randn(2, 10, 512)  # (batch, seq_len, d_model) karena batch_first
memory = encoder(src)
assert memory.shape == src.shape

batch_first=True membuat input ber-shape (batch, seq_len, dim) seperti kebiasaan umum. Default PyTorch tanpa flag itu adalah (seq_len, batch, dim), jadi pastikan kamu konsisten antara layer dan input.

#Transformer Encoder-Decoder Sekali Jalan

nn.Transformer menggabungkan encoder dan decoder. Target memakai embedding sendiri dan mask kausal untuk menutupi token masa depan.

python
transformer = nn.Transformer(
    d_model=512,
    nhead=8,
    num_encoder_layers=6,
    num_decoder_layers=6,
    dim_feedforward=2048,
    dropout=0.1,
    batch_first=True,
)
 
src = torch.randint(0, 1000, (2, 10))       # token sumber
tgt = torch.randint(0, 1000, (2, 15))       # token target
src_embed = nn.Embedding(1000, 512)(src)
tgt_embed = nn.Embedding(1000, 512)(tgt)
 
tgt_mask = transformer.generate_square_subsequent_mask(tgt.size(1))
output = transformer(src_embed, tgt_embed, tgt_mask=tgt_mask)
assert output.shape == (2, 15, 512)

generate_square_subsequent_mask membuat mask kausal untuk panjang urutan target. Output decoder sepanjang target lalu diproyeksikan ke distribusi vocab memakai layer linear terpisah.

#scaled_dot_product_attention Bawaan

PyTorch punya implementasi fused yang lebih cepat dan hemat memori dari menulis attention manual. Gunakan ini di kode produksi.

python
import torch.nn.functional as F
 
query = torch.randn(2, 8, 10, 64)   # (batch, heads, seq, dim)
key = torch.randn(2, 8, 10, 64)
value = torch.randn(2, 8, 10, 64)
 
# Attention biasa
out = F.scaled_dot_product_attention(query, key, value)
 
# Attention kausal tanpa membangun mask manual
causal_out = F.scaled_dot_product_attention(
    query, key, value, is_causal=True
)
 
# Dengan dropout selama training
dropout_out = F.scaled_dot_product_attention(
    query, key, value, dropout_p=0.1, training=True
)

is_causal=True menggantikan mask segitiga bawah manual dan membuka kernel yang lebih efisien. nn.MultiheadAttention bawaan juga memakai kernel fused ini otomatis saat need_weights=False.

#Inference dan Decoding

Saat menghasilkan teks, decoder dipanggil berulang. Tiap langkah menghasilkan satu token baru, token itu ditambahkan ke urutan, lalu proses diulang sampai token akhir atau panjang maksimum tercapai.

python
@torch.no_grad()
def greedy_decode(transformer, src_tokens, tgt_embed, projection,
                  bos_idx, eos_idx, max_len=50):
    transformer.eval()
    src_embed = tgt_embed.weight  # contoh sederhana, sharing embedding
    memory = transformer.encoder(src_embed[src_tokens].unsqueeze(0))
 
    generated = [bos_idx]
    for _ in range(max_len):
        tgt_tokens = torch.tensor([generated]).to(src_tokens.device)
        tgt_mask = transformer.generate_square_subsequent_mask(len(generated))
        tgt_hidden = tgt_embed(tgt_tokens)
        out = transformer.decoder(tgt_hidden, memory, tgt_mask=tgt_mask)
        next_logits = projection(out[:, -1, :])
        next_token = next_logits.argmax(dim=-1).item()
        generated.append(next_token)
        if next_token == eos_idx:
            break
 
    return generated

argmax tiap langkah adalah greedy decoding. top-k dan nucleus sampling memilih dari subset token berprobabilitas tinggi untuk hasil lebih beragam. Suhu menajamkan atau meratakan distribusi sebelum sampling. Selalu kirim tgt_mask saat decode supaya model tidak melihat token masa depan.

#Decoder-Only dan KV Cache

Model generatif modern seperti GPT memakai arsitektur decoder-only. Tidak ada encoder terpisah, dan self-attention memakai causal mask. Saat menghasilkan teks, menghitung ulang attention untuk seluruh urutan di tiap langkah itu boros.

KV cache menyimpan key dan value dari langkah sebelumnya. Pada langkah baru, model hanya menghitung key dan value untuk token terakhir, lalu menyambungkannya ke cache. Biaya komputasi tiap langkah turun drastis untuk urutan panjang.

text
langkah 1: hitung K, V untuk [t1]            -> simpan di cache
langkah 2: hitung K, V untuk [t2] saja       -> gabung dengan cache
langkah 3: hitung K, V untuk [t3] saja       -> gabung dengan cache

Konsekuensinya penting untuk inferensi: batch dengan urutan beda panjang memakai padding, cache tiap sample dipotong sesuai panjang aslinya, dan memori tumbuh seiring panjang urutan dikali jumlah layer. Implementasi yang cermat menyimpan cache per layer dan memakai cache_position untuk indexing.

#Rotary Positional Embedding (RoPE)

Model terbaru sering meninggalkan positional encoding sinus tambahan dan memakai RoPE. Alih-alih menambahkan vektor posisi ke embedding, RoPE memutar query dan key berdasarkan posisi relatifnya. Informasi posisi masuk ke perkalian dot product lewat sudut rotasi.

python
def apply_rotary_embedding(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
    """Rotasi separuh dimensi pertama x dan separuh terakhir."""
    half = x.shape[-1] // 2
    x1, x2 = x[..., :half], x[..., half:]
    rotated = torch.cat((-x2, x1), dim=-1)
    return x * cos + rotated * sin

Keuntungan utama RoPE adalah kemampuannya menangani posisi relatif secara alami, sehingga model lebih mudah melakukan ekstrapolasi ke panjang urutan melebihi data training. RoPE juga tidak membutuhkan tabel posisi maksimum yang besar.

#Suhu dan Sampling

Greedy decoding memilih token dengan probabilitas tertinggi dan bisa menghasilkan teks berulang. Sampling memakai distribusi probabilitas dengan suhu yang mengatur keacakan. Suhu rendah mendekati greedy, suhu tinggi membuat output lebih bervariasi.

python
@torch.no_grad()
def sample_next_token(logits: torch.Tensor, temperature: float = 1.0) -> torch.Tensor:
    if temperature <= 0:
        return logits.argmax(dim=-1)
    scaled = logits / temperature
    probabilities = F.softmax(scaled, dim=-1)
    return torch.multinomial(probabilities, num_samples=1).squeeze(-1)

torch.multinomial menarik sampel berdasarkan distribusi probabilitas. Gabungkan dengan top_k atau top_p untuk memotong ekor distribusi yang berisi token sangat tidak mungkin. Jangan pakai suhu sembarangan pada output yang harus faktual.

#Debugging Transformer

Bug di transformer biasanya muncul dari shape dan mask yang tidak sejajar. Periksa hal ini lebih dulu:

  1. Pastikan dimensi terakhir query, key, dan value sama, yaitu d_model.
  2. Pastikan d_model habis dibagi num_heads.
  3. Cek mask ber-shape (batch, 1, q_len, kv_len) atau (q_len, kv_len).
  4. Pastikan masked_fill memakai float("-inf"), bukan nol.
  5. Pastikan self-attention decoder memakai causal mask.
  6. Pastikan batch_first konsisten antara layer dan tensor input.
  7. Cek embedding dan projection berbagi vocab size yang sama.
  8. Uji pada urutan pendek sebelum naik ke urutan panjang.
python
# Tes perhatian pada satu token. Bobot harus menyebar, dan
# tiap baris bobot harus berjumlah 1.
q = torch.randn(1, 1, 3, 8)
k = torch.randn(1, 1, 5, 8)
v = torch.randn(1, 1, 5, 8)
out = F.scaled_dot_product_attention(q, k, v)
assert out.shape == (1, 1, 3, 8)
 
# Softmax per baris harus berjumlah 1.
scores = (q @ k.transpose(-2, -1)) / math.sqrt(8)
row_sum = F.softmax(scores, dim=-1).sum(dim=-1)
assert torch.allclose(row_sum, torch.ones_like(row_sum))

Kalau loss nan, cek pembagian sqrt(dim_key), pastikan tidak ada softmax pada nilai inf, dan periksa apakah learning rate terlalu besar. Bobot perhatian yang seragam ke semua token biasanya tanda model belum belajar atau positional encoding hilang.

#Glossary

Attention: mekanisme pembobotan nilai berdasarkan kesesuaian query dan key.

Autoregressive: model menghasilkan token satu per satu memakai output sebelumnya.

Causal mask: mask yang mencegah token melihat posisi sesudahnya.

Cross-attention: attention yang query-nya dari satu urutan dan key/value dari urutan lain.

Decoder: bagian transformer yang menghasilkan output secara autoregressive.

Encoder: bagian transformer yang memproses seluruh input sekaligus.

Feed-forward network: dua layer linear dengan activation di antara, diterapkan per posisi.

Layer norm: normalisasi per sample yang menstabilkan training.

Logits: skor mentah sebelum softmax, dipakai untuk memilih token berikutnya.

Multi-head attention: beberapa kepala attention dengan proyeksi terpisah.

Padding mask: mask yang menyembunyikan token kosong.

Positional encoding: representasi posisi token yang ditambahkan ke embedding.

Self-attention: attention dengan query, key, dan value dari urutan yang sama.

Softmax: fungsi yang mengubah skor menjadi distribusi probabilitas berjumlah satu.

Token: satuan input, bisa kata, subword, atau karakter.

Vocab: daftar seluruh token yang dikenal model.

Baca Cheat Sheet Lengkap

Login atau daftar akun gratis untuk membaca cheat sheet ini.

LoginDaftar Gratis
Share: