File size: 5,515 Bytes
9483064
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
---
license: apache-2.0
---

# ScratchLM-95M

A 95-million-parameter transformer language model built **entirely from scratch** in PyTorch β€” no pretrained weights, no `from_pretrained`. Trained on WikiText-103 and fine-tuned for question answering using two-stage LoRA.

> Beats GPT-3 Small (PPL 26.0) on WikiText-103 with 30% fewer parameters.

---

## Model Details

| Property | Value |
|---|---|
| **Parameters** | ~95M (88.7M base + embedding table) |
| **Architecture** | 12-layer causal transformer |
| **Position Encoding** | RoPE (Rotary Position Embedding) |
| **FFN Activation** | SwiGLU |
| **Attention** | Causal + Flash Attention fallback |
| **Context Length** | 512 tokens |
| **Tokenizer** | GPT-2 BPE (vocab size 50,257) |
| **Framework** | PyTorch (from scratch) |
| **Base Pretraining** | WikiText-103 |
| **Fine-tuning Method** | Two-stage LoRA (R128 β†’ R64) |
| **Trainable Params (LoRA)** | ~1.6M (1.8% of base) |

---

## Model Configuration

```python
VOCAB_SIZE  = 50257   # GPT-2 tokenizer
MAX_SEQ_LEN = 512     # Context window
EMBED_DIM   = 512     # Model dimension
NUM_HEADS   = 8       # Attention heads
NUM_LAYERS  = 12      # Transformer blocks
D_FF        = 2730    # SwiGLU intermediate dim
DROPOUT     = 0.1
```

---

## Performance

| Checkpoint | PPL |
|---|---|
| Base model (WikiText-103, Epoch 17) | 24.40 |
| GPT-3 Small (reference) | 26.0 |
| Stage 1 LoRA β€” R128, Ξ±=256, Epoch 7 | 21.48 |
| **Stage 2 LoRA β€” R64, Ξ±=128, Epoch 2** | **20.83** |

- **Total PPL improvement:** 3.57 points (24.40 β†’ 20.83)
- **Train/Val gap:** 0.1216 (healthy, no overfitting)
- **Fine-tune hardware:** Single RTX 5050 (8.5GB VRAM), ~6 hours

---

## Training Details

### Base Pretraining (V3)

| Hyperparameter | Value |
|---|---|
| Dataset | WikiText-103 |
| Effective batch size | 32 (8 Γ— 4 grad accum) |
| Learning rate | 2e-4 (cosine decay) |
| Warmup steps | 800 |
| Optimizer | AdamW (Ξ²1=0.9, Ξ²2=0.95) |
| Gradient clipping | 0.8 |
| Best checkpoint | Epoch 17 |

### Fine-Tuning (Two-Stage LoRA)

Full fine-tuning (all 95M params) caused catastrophic forgetting β€” PPL rose from 24.40 to 35+. LoRA was adopted to freeze 99% of weights.

**LoRA config:**
```python
LORA_R       = 32     # Final rank
LORA_ALPHA   = 64     # Scaling factor (2Γ— rank)
LORA_DROPOUT = 0.05
# Target modules: qkv_proj, out_proj
```

**Two-stage strategy:**
- **Stage 1** β€” LoRA R=128, Ξ±=256 β†’ best PPL 21.48 at Epoch 7
- **Stage 2** β€” Merge Stage 1 weights into base, restart with LoRA R=64, Ξ±=128 β†’ best PPL **20.83** at Epoch 2

Weight merge equation:
```python
W_merged = W_base + (B @ A) * (alpha / rank)
```

### Dataset Engineering

355k clean QA pairs from three sources, after rejecting 460k noisy alternatives:

| Source | Pairs | Notes |
|---|---|---|
| FreebaseQA / TriviaQA | 205k | Factual pairs |
| ELI5 (cleaned) | 90k | Reddit markdown stripped |
| SciQ | 60k | Science explanations |

Cleaning steps: regex removal of filler phrases (`"the correct answer is..."`), Reddit markdown stripping, deduplication, length filtering (20–80 word answers), proper punctuation enforcement. ~22,000 entries removed.

---

## Usage

```python
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch

tokenizer = AutoTokenizer.from_pretrained("Debarun12/ScratchLM-95M")
model = AutoModelForCausalLM.from_pretrained("Debarun12/ScratchLM-95M")
model.eval()

prompt = "What is the official language of Brazil?"
inputs = tokenizer(prompt, return_tensors="pt")

with torch.no_grad():
    output = model.generate(
        **inputs,
        max_new_tokens=100,
        repetition_penalty=3.0,
        do_sample=False
    )

print(tokenizer.decode(output[0], skip_special_tokens=True))
# β†’ "The official language is Portuguese, used in national and international contexts."
```

> **Note:** A repetition penalty of 3.0 (window 128 tokens) is strongly recommended β€” it is required to suppress repetition loops at this model scale.

---

## Limitations

- Hallucinates facts for questions outside training data coverage (e.g., precise dates, counts)
- Struggles with exact numerical facts
- 95M parameter size caps final PPL around 20–22 without architectural scaling
- Context window limited to 512 tokens

---

## Key Engineering Decisions

**Why RoPE?** Superior relative position awareness over learned absolute embeddings; no position table to overfit. Same choice as LLaMA, Mistral, and Gemma.

**Why SwiGLU?** Gated linear unit consistently outperforms GELU in practice; requires 3 linear layers (w1, w2, w3).

**Why LoRA over full fine-tune?** Full fine-tune destroyed base knowledge (PPL 24.40 β†’ 35+) due to domain shift between long encyclopedia text and short factual QA. LoRA preserves base weights while adapting ~1.8% of parameters.

**Why two-stage LoRA?** Stage 1 (R128) showed widening train/val gap at Epoch 7. Merging and restarting with lower rank (R64) gave a fresh second stage without continued overfitting.

---

## Citation

If you use this model or find this work helpful, please cite:

```bibtex
@misc{scratchlm95m,
  author       = {Debarun Das},
  title        = {ScratchLM-95M: A 95M Parameter Language Model Built From Scratch},
  year         = {2025},
  publisher    = {HuggingFace},
  url          = {https://huggingface.co/Debarun12/ScratchLM-95M}
}
```

---

## Author

**Debarun Das**
- GitHub: [github.com/Debarun12](https://github.com/Debarun12)
- HuggingFace: [huggingface.co/Debarun12](https://huggingface.co/Debarun12)