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) |