LLM memproses teks lewat tokenizer subword seperti BPE dengan ratusan ribu token; model mini kita menurunkan skala tersebut menjadi tokenisasi per karakter dengan 25 token. Konsekuensinya sederhana namun fundamental: satu kalimat menjadi rangkaian token yang lebih panjang, dan loss dihitung per posisi karakter alih-alih per kata. Bagian ini membahas tiga fungsi di src/model_bahasa.py yang mengubah data/korpus.txt menjadi tensor masukan dan target — pembahasan level karakter ini adalah padanan langsung dari chapter Tokenization untuk LLM
.
Dari Teks ke Indeks Token #
Dua fungsi pertama membangun kamus dua arah antara karakter dan indeks:
def buat_kosakata(kalimat_latih: list[str]) -> tuple[dict, list]:
"""Kosakata karakter dari seluruh teks latih (urutan terurut stabil)."""
huruf = sorted(set("".join(kalimat_latih)))
sto = {h: i for i, h in enumerate(huruf)}
return sto, huruf
def enkode(teks: str, sto: dict) -> list[int]:
return [sto[h] for h in teks if h in sto]
def dekode(indeks: list[int], huruf: list[str]) -> str:
return "".join(huruf[i] for i in indeks)Fungsi buat_kosakata memakai sorted(set(...)) sehingga urutan token stabil antar eksekusi — indeks 0 selalu karakter spasi, bukan bergantung pada urutan kemunculan. Fungsi enkode menyaring karakter yang tidak ada di kamus (if h in sto); karena kosakata dibangun dari kalimat terpisah per baris, karakter baris baru tidak punya token dan otomatis hilang saat kalimat-kalimat dirangkai menjadi satu stream oleh gabung_stream. Jendela latih karena itu boleh melintasi batas kalimat — perilaku yang lazim juga pada pra-latih LLM yang memotong korpus besar menjadi potongan berukuran tetap. Penyaringan if h in sto sekaligus menjadi penjaga masukan baru: teks dengan huruf asing (misalnya “é”) tidak menggugat kamus, karakter tak dikenalnya hanya dilewati — padanan sederhana dari masalah token di luar kosakata (OOV) pada tokenizer nyata yang dibahas pada chapter Vocabulary dan Token ID
.
Jendela Latih dengan Target Bergeser Satu #
Fungsi susun_jendela memotong stream token menjadi pasangan (masukan, target):
def susun_jendela(token: list[int], panjang: int = 32, langkah: int = 16):
"""Potong satu stream token menjadi jendela (masukan, target) geser 1.
Setiap jendela berisi `panjang` token masukan dan `panjang` token
target yang bergeser satu posisi (tugas memprediksi karakter berikut).
"""
daftar_x, daftar_y = [], []
for awal in range(0, len(token) - panjang, langkah):
daftar_x.append(token[awal:awal + panjang])
daftar_y.append(token[awal + 1:awal + 1 + panjang])
if not daftar_x: # stream terlalu pendek: pakai satu jendela penuh
return torch.tensor([], dtype=torch.long).view(0, 0)
return (torch.tensor(daftar_x, dtype=torch.long),
torch.tensor(daftar_y, dtype=torch.long))Mari kita telaah fungsi di atas baris per baris:
token[awal:awal + panjang]: mengambil 32 token masukan mulai posisiawal— konteks yang dibaca model.token[awal + 1:awal + 1 + panjang]: target adalah potongan yang sama digeser satu posisi, sehingga pada setiap langkah t model belajar memprediksi karakter di posisi t+1 — persis tugas next-token prediction.langkah=16: jendela berikutnya mulai 16 token kemudian, sehingga dua jendela bertetangga tumpang-tindih setengah panjangnya; korpus kecil menghasilkan lebih banyak sampel tanpa menambah teks.- Penjaga stream pendek: bila stream lebih pendek dari jendela, fungsi mengembalikan tensor kosong berbentuk
(0, 0)agar pemanggil tidak menerima galat indeks.
Menjalankan python3 src/model_bahasa.py mencetak hasil penyusunan jendela beserta anatomi model:
jendela pra-latih : 46 (panjang 32, langkah 16)
loss pra-latih ep1 : 3.2306
loss pra-latih ep60 : 2.1119
ppl pra-latih ep60 : 8.16
== demo selesai; skrip lain memakai modul ini ==Subset umum menghasilkan 46 jendela; subset kopi latih menghasilkan 17 jendela, dan dua kalimat validasi kopi menghasilkan 3 jendela — angka-angka yang tercetak ulang oleh src/evaluasi.py pada Bagian 4. Loss turun dari 3.2306 ke 2.1119 setelah 60 epoch demo, pertanda arsitektur dan format data sudah benar sebelum kita melangkah ke pra-latih penuh.
Format data selesai: tiga subset telah menjadi jendela 32 token dengan target geser satu. Bagian berikutnya membekukan model dasar hasil pra-latih lalu menempelkan adapter LoRA pada lapisan terberatnya.