Intisari
Membangun model bahasa minimal dari awal hanya membutuhkan kurang dari 300 baris kode Python. Proses ini mengungkap dengan tepat bagaimana tokenisasi, perhatian, dan inferensi bekerja, yang membuat Anda menjadi konsumen API yang jauh lebih baik saat mengintegrasikan LLM produksi ke dalam aplikasi Anda.
Pendahuluan
Sebagian besar pengembang memperlakukan model bahasa sebagai kotak hitam. Anda mengirim teks masuk, token keluar, dan di antara itu semua, keajaiban terjadi. Model mental itu berfungsi dengan baik sampai Anda perlu men-debug integrasi API yang rusak, menyetel parameter pengambilan sampel, atau mencari tahu mengapa model Anda terus berhalusinasi data terstruktur.
GuppyLM, sebuah proyek yang baru-baru ini mencapai halaman depan HackerNews dengan 842 poin, membuat internalnya terlihat. Ini adalah transformer berparameter 8.7M yang ditulis dari awal dalam Python. Model ini dilatih dalam waktu kurang dari satu jam pada GPU konsumen. Kodenya muat dalam satu file. Tujuannya bukan untuk bersaing dengan GPT-4; tujuannya adalah untuk mengungkap apa sebenarnya yang dilakukan LLM.
Artikel ini menjelaskan langkah demi langkah membangun LLM kecil, komponennya, dan apa yang sebaiknya Anda pahami saat bekerja dengan API AI secara profesional.
💡 Jika Anda menguji integrasi API AI, Skenario Uji Apidog memungkinkan Anda memverifikasi respons streaming, menegaskan struktur token, dan mensimulasikan penyelesaian kasus ekstrem tanpa membakar kredit produksi. Lebih lanjut tentang itu nanti
Apa yang membuat model bahasa "kecil"?
LLM produksi seperti GPT-4 memiliki ratusan miliar parameter. LLM "kecil" berada di kisaran 1M hingga 25M parameter. Contohnya: GuppyLM (8.7M), nanoGPT Karpathy (124M), MicroLM (1-2M).
LLM kecil dapat:
- Dilatih di laptop atau Google Colab
- Muat sepenuhnya di memori CPU
- Diinspeksi, dimodifikasi, di-debug hingga level bobot
LLM kecil tidak dapat:
- Menangani penalaran kompleks
- Menghasilkan teks panjang yang koheren secara andal
- Menyamai kedalaman faktual model produksi
Nilai utamanya adalah pemahaman mendalam ketika Anda membangunnya sendiri.
Komponen inti: bagaimana LLM sebenarnya bekerja
Sebelum coding, pahami 4 bagian utama ini:
Tokenisasi
Tokenizer mengubah teks mentah menjadi ID bilangan bulat. Contoh: "Hello, world!" → [15496, 11, 995, 0]. Setiap bilangan bulat adalah subkata dari kosakata tetap.
Impak ke API: Jumlah token langsung memengaruhi latensi dan biaya. Anda perlu tahu bagaimana tokenizer memecah teks agar prompt Anda selalu muat dalam jendela konteks.
- GuppyLM: tokenizer karakter sederhana.
- Model produksi (GPT-4, dsb): BPE (byte-pair encoding), kosakata 50K–100K token.
Lapisan embedding
Lapisan embedding mengubah ID token menjadi vektor padat (misal 384 dimensi di GuppyLM). Vektor ini mengandung makna semantik — token serupa akan berdekatan di ruang vektor.
Positional embedding ditambahkan agar model tahu urutan token.
Blok Transformer
Bagian komputasi utama terdiri dari dua bagian:
- Self-attention: setiap token melihat semua token lain dalam urutan, memilih mana yang penting, dan memprediksi token berikutnya. GuppyLM memakai 6 head pada 6 layer.
- Feed-forward network: MLP dua lapis diterapkan pada representasi token setelah attention. GuppyLM memakai ReLU, lebih sederhana dari SwiGLU pada arsitektur baru.
Kepala output
Setelah blok transformer terakhir, layer linear memproyeksikan token ke vektor sebesar kosakata. Softmax diterapkan untuk probabilitas, token berikutnya dipilih (atau sampling), lalu diulang.
Membangun LLM minimal dalam Python
Berikut LLM minimal berbasis GuppyLM. Cukup gunakan PyTorch standar.
import torch
import torch.nn as nn
import torch.nn.functional as F
# Hyperparameters
VOCAB_SIZE = 256 # karakter: satu slot per ASCII
D_MODEL = 128 # dimensi embedding
N_HEADS = 4 # attention heads
N_LAYERS = 3 # transformer blocks
SEQ_LEN = 64 # context window
DROPOUT = 0.1
class SelfAttention(nn.Module):
def __init__(self, d_model, n_heads):
super().__init__()
self.n_heads = n_heads
self.head_dim = d_model // n_heads
self.qkv = nn.Linear(d_model, 3 * d_model, bias=False)
self.proj = nn.Linear(d_model, d_model, bias=False)
self.dropout = nn.Dropout(DROPOUT)
def forward(self, x):
B, T, C = x.shape
qkv = self.qkv(x).reshape(B, T, 3, self.n_heads, self.head_dim)
q, k, v = qkv.unbind(dim=2)
q = q.transpose(1, 2)
k = k.transpose(1, 2)
v = v.transpose(1, 2)
# Mask kausal: token hanya boleh melihat token sebelumnya
scale = self.head_dim ** -0.5
attn = (q @ k.transpose(-2, -1)) * scale
mask = torch.triu(torch.ones(T, T, device=x.device), diagonal=1).bool()
attn = attn.masked_fill(mask, float('-inf'))
attn = F.softmax(attn, dim=-1)
attn = self.dropout(attn)
out = (attn @ v).transpose(1, 2).reshape(B, T, C)
return self.proj(out)
class TransformerBlock(nn.Module):
def __init__(self, d_model, n_heads):
super().__init__()
self.attn = SelfAttention(d_model, n_heads)
self.ff = nn.Sequential(
nn.Linear(d_model, 4 * d_model),
nn.ReLU(),
nn.Linear(4 * d_model, d_model),
nn.Dropout(DROPOUT),
)
self.ln1 = nn.LayerNorm(d_model)
self.ln2 = nn.LayerNorm(d_model)
def forward(self, x):
x = x + self.attn(self.ln1(x))
x = x + self.ff(self.ln2(x))
return x
class TinyLLM(nn.Module):
def __init__(self):
super().__init__()
self.embed = nn.Embedding(VOCAB_SIZE, D_MODEL)
self.pos_embed = nn.Embedding(SEQ_LEN, D_MODEL)
self.blocks = nn.ModuleList([
TransformerBlock(D_MODEL, N_HEADS) for _ in range(N_LAYERS)
])
self.ln_f = nn.LayerNorm(D_MODEL)
self.head = nn.Linear(D_MODEL, VOCAB_SIZE, bias=False)
def forward(self, idx):
B, T = idx.shape
tok_emb = self.embed(idx)
pos = torch.arange(T, device=idx.device)
pos_emb = self.pos_embed(pos)
x = tok_emb + pos_emb
for block in self.blocks:
x = block(x)
x = self.ln_f(x)
logits = self.head(x)
return logits
# Inisialisasi dan hitung parameter
model = TinyLLM()
total_params = sum(p.numel() for p in model.parameters())
print(f"Model size: {total_params:,} parameters") # ~1.2M
Loop pelatihan
import torch.optim as optim
def train(model, data, epochs=100, lr=3e-4):
optimizer = optim.AdamW(model.parameters(), lr=lr)
model.train()
for epoch in range(epochs):
# data: tensor ID token, shape [batch, seq_len+1]
x = data[:, :-1] # input: semua token kecuali terakhir
y = data[:, 1:] # target: semua token digeser 1
logits = model(x)
loss = F.cross_entropy(logits.reshape(-1, VOCAB_SIZE), y.reshape(-1))
optimizer.zero_grad()
loss.backward()
optimizer.step()
if epoch % 10 == 0:
print(f"Epoch {epoch}, loss: {loss.item():.4f}")
Inferensi (pembuatan teks)
@torch.no_grad()
def generate(model, prompt_ids, max_new_tokens=50, temperature=1.0, top_k=10):
model.eval()
ids = torch.tensor([prompt_ids])
for _ in range(max_new_tokens):
idx_cond = ids[:, -SEQ_LEN:] # potong ke context window
logits = model(idx_cond)
logits = logits[:, -1, :] / temperature # hanya token terakhir
# top-k sampling
v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
logits[logits < v[:, [-1]]] = float('-inf')
probs = F.softmax(logits, dim=-1)
next_id = torch.multinomial(probs, num_samples=1)
ids = torch.cat([ids, next_id], dim=1)
return ids[0].tolist()
Apa yang diajarkan ini tentang perilaku API AI
Beberapa insight praktis untuk konsumen API setelah membangun LLM sendiri:
Suhu dan sampling bersifat mekanis, bukan magis
suhu membagi logit sebelum softmax. Suhu tinggi = distribusi datar = output acak. Suhu rendah = distribusi tajam = output deterministik. Jika API Anda mengembalikan hasil tidak konsisten pada temperature=0.0, itu bukan bug. Banyak API menurunkan sedikit suhu nol untuk menghindari output yang membosankan.
Jendela konteks adalah batasan keras
Baris idx_cond = ids[:, -SEQ_LEN:] di loop inferensi memperlihatkan: token lama otomatis dihapus. Jangan anggap model mengingat riwayat penuh. Untuk solusi, cek [internal: how-ai-agent-memory-works].
Token streaming = iterasi loop inferensi
API streaming hanya menjalankan loop inferensi dan mengirim token satu per satu. Jika sambungan streaming terputus, tidak bisa dilanjutkan — harus dimulai ulang.
Logit menjelaskan mengapa output terstruktur sulit
Setiap langkah, model menetapkan probabilitas pada semua token. Membuat JSON valid berarti setiap token harus benar. Library seperti Outlines dan Guidance membatasi distribusi logit agar output sesuai tata bahasa. Mode "output terstruktur" di API AI bekerja seperti ini.
Cara menguji integrasi API AI dengan Apidog
Dengan memahami inferensi LLM, Anda dapat membuat pengujian API yang jauh lebih baik. Skenario Uji Apidog memungkinkan Anda merangkai panggilan API dan menegaskan struktur respons AI.
Contoh pengujian API chat streaming:
- Buat Skenario Uji di Apidog dengan endpoint
/v1/chat/completionsAnda. - Tambahkan assertion:
response.choices[0].finish_reason == "stop"response.usage.total_tokens < 4096
- Tambah langkah untuk mengirim respons sebagai konteks ke giliran berikutnya (simulasi percakapan multi-turn).
- Gunakan Smart Mock Apidog untuk endpoint AI dan pengujian penanganan error:
Simulasikan
finish_reason: "length",finish_reason: "content_filter", dan timeout jaringan di tengah stream.
Dengan cara ini, Anda bisa menguji integrasi AI tanpa membakar kredit API pada setiap run CI. Lihat [internal: api-testing-tutorial] untuk gambaran lebih luas pendekatan pengujian API.
Menguji assertion jumlah token
{
"assertions": [
{
"field": "response.usage.completion_tokens",
"operator": "less_than",
"value": 512
},
{
"field": "response.choices[0].finish_reason",
"operator": "equals",
"value": "stop"
},
{
"field": "response.choices[0].message.content",
"operator": "not_empty"
}
]
}
Jalankan assertion ini di beberapa model (GPT-4o, Claude 3.5 Sonnet, Gemini 1.5 Pro) dalam satu skenario untuk menangkap perbedaan skema API sebelum produksi.
Lanjutan: kuantisasi dan optimisasi inferensi
Setelah punya LLM kecil yang berjalan, dua teknik berikut sangat relevan untuk deployment model produksi.
Kuantisasi
Bobot model default-nya float 32 bit. Kuantisasi menurunkan ke INT8 atau INT4, mengurangi penggunaan memori 4–8x dengan sedikit penurunan akurasi.
# Contoh: dynamic INT8 quantization di PyTorch
import torch.quantization
quantized_model = torch.quantization.quantize_dynamic(
model, {nn.Linear}, dtype=torch.qint8
)
Model produksi biasanya sudah dikuantisasi. Jika Anda lihat perbedaan kualitas pada "versi" model yang sama, kuantisasi sering jadi penyebab.
Cache KV
Dalam inferensi di atas, perhatian dihitung ulang setiap langkah. Sistem produksi menyimpan cache pasangan key-value (KV) token sebelumnya, sehingga setiap token baru hanya perlu satu komputasi attention. Itulah mengapa token pertama pada streaming lebih lambat dari berikutnya.
LLM kecil vs. API produksi: kapan menggunakan masing-masing
| Kasus Penggunaan | LLM Kecil | API Produksi |
|---|---|---|
| Mempelajari internal model | Terbaik untuk | Berlebihan |
| Membuat prototipe aplikasi baru | Kualitas tidak memadai | Terbaik untuk |
| Data pribadi/sensitif | Pilihan bagus | Tergantung penyedia |
| Penyebaran offline/edge | Layak | Tidak mungkin |
| Sensitif biaya, volume tinggi | Mungkin dengan kompromi | Mahal pada skala besar |
| Tugas yang membutuhkan penalaran berat | Tidak layak | Diperlukan |
Sebagian besar pengembang sebaiknya: gunakan API produksi untuk aplikasi Anda, tapi jalankan model kecil untuk memahami mekanisme internal. Keduanya saling melengkapi. Lihat artikel [internal: open-source-coding-assistants-2026] untuk alat dengan mode "bring-your-own-model".
Kesimpulan
Membangun LLM kecil dari awal bisa dilakukan dalam satu akhir pekan. Anda tidak akan mendapat sistem produksi, tapi Anda akan punya model mental yang nyata tentang cara kerja model bahasa — dari GuppyLM hingga GPT-4o. Pemahaman ini sangat berguna saat debugging integrasi streaming, menyetel parameter sampling, atau merancang assertion pengujian API AI.
Proyek GuppyLM sangat direkomendasikan sebagai titik awal. Kloning, latih dengan dataset teks apa pun, dan pelajari loop inferensinya. Setelah itu, integrasi API produksi Anda akan terasa jauh lebih transparan.
Coba Skenario Uji Apidog dan bawa ketelitian pengujian API AI ke level backend production.
FAQ
Berapa banyak parameter yang dibutuhkan LLM "kecil" untuk menghasilkan teks koheren?
Sekitar 10M–50M parameter dengan dataset yang cukup bisa menghasilkan kalimat koheren secara lokal. Di bawah 1M, hasilnya cenderung tidak masuk akal. GuppyLM (8.7M) bisa untuk percakapan singkat di domain pelatihannya (60 topik).
Bisakah saya menjalankan LLM kecil tanpa GPU?
Bisa. Model di bawah 100M parameter berjalan baik di CPU, walaupun lebih lambat. Model contoh di atas (1.2M parameter) menghasilkan token dalam milidetik di laptop.
Dataset apa yang sebaiknya digunakan untuk pelatihan?
Model tingkat karakter cocok untuk teks Project Gutenberg, subset Wikipedia, atau korpus teks biasa. GuppyLM memakai dataset percakapan 60K entri di HuggingFace (arman-bd/guppylm-60k-generic). Untuk pembuatan kode: The Stack atau CodeParrot.
Perbedaan suhu vs. top-k sampling?
Suhu mengubah distribusi logit (mengontrol keacakan). Top-k membatasi pilihan sampling pada k token paling mungkin sebelum suhu diterapkan. Biasanya, top-k diterapkan dulu, baru suhu.
Mengapa LLM saya kadang mengulang output?
Pengulangan adalah mode kegagalan di mana model menetapkan probabilitas tinggi pada token yang baru dibuat. API produksi memakai penalti pengulangan (repetition_penalty=1.1) untuk mengurangi ini.
Berapa lama waktu pelatihan LLM kecil?
Model di atas bisa dilatih hingga output koheren dalam waktu kurang dari 2 jam di satu GPU (RTX 3060 atau setara). GuppyLM dilatih di Colab dalam waktu serupa. Model >100M butuh multi-GPU dan waktu lebih lama.
Cara tercepat beralih dari LLM kecil ke endpoint API nyata?
Ekspor ke format GGUF (skrip konversi llama.cpp), lalu sajikan dengan llama-server — endpoint API kompatibel OpenAI secara lokal. Apidog bisa diarahkan ke sini untuk testing, lihat [internal: rest-api-best-practices].
Bagaimana LLM produksi menangani konteks lebih panjang dari jendela pelatihan?
Teknik seperti RoPE (Rotary Position Embedding) dengan penskalaan, sliding window attention, dan retrieval-augmented generation memperluas konteks efektif. Inti arsitektur transformer tetap, hanya encoding posisi dan window attention yang dimodifikasi.
Top comments (0)