Skip to content

Repository files navigation

Project 1 - Build an LLM Playground

Production-Level Large Language Model Training & Deployment Framework


A complete, research-grade implementation of the modern LLM pipeline — from raw web data through pre-training, instruction tuning, reinforcement learning from human feedback, and production API deployment. Built for engineers who want to understand and build every layer themselves.

To better understand this project, first visit this link for a visualization of the project and what I built: Link

Then, if you want to learn each topic in a tutorial format, read this file thoroughly: Link


Quick Start • Architecture • Training • API • Evaluation • Contributing


Table of Contents

  1. Overview
  2. What Makes This Different
  3. System Requirements
  4. Quick Start
  5. Project Structure
  6. Architecture Deep Dive
  7. Training Pipeline
  8. Evaluation & Benchmarks
  9. API Reference
  10. UI Guide
  11. Configuration
  12. Docker Deployment
  13. Performance & Scaling
  14. Troubleshooting
  15. Contributing
  16. References

Overview

LLM Playground is a production-grade framework implementing the complete lifecycle of a Large Language Model from first principles. Every component — from the BPE tokenizer to the PPO trainer — is written to be readable, extensible, and correct.

The Full Pipeline at a Glance

Raw Web Data
    │
    ▼
┌─────────────────────────────────────────────────────────┐
│                    DATA PIPELINE                        │
│  Web Crawler → CommonCrawl → RefinedWeb / Dolma /       │
│  FineWeb cleaning → BPE Tokenization → Binary Shards   │
└────────────────────────┬────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────┐
│                 MODEL ARCHITECTURES                     │
│  GPT │ LLaMA-style │ DeepSeek (MLA+MoE) │ Qwen │ Gemma │
└────────────────────────┬────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────┐
│                  TRAINING PIPELINE                      │
│  Pre-training → SFT (LoRA) → Reward Model → PPO/RLHF   │
└────────────────────────┬────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────┐
│              EVALUATION SUITE                           │
│  Perplexity │ BLEU │ ROUGE │ MMLU │ GSM8K │ ELO Arena  │
└────────────────────────┬────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────┐
│           PRODUCTION DEPLOYMENT                         │
│  FastAPI (OpenAI-compatible) + Gradio UI + Docker       │
└─────────────────────────────────────────────────────────┘

What Makes This Different

Most LLM repositories either wrap an existing framework (HuggingFace, LitGPT) or implement only one piece of the puzzle. This project is different:

Feature This Project HuggingFace nanoGPT LitGPT
Full data pipeline ✅ ❌ ❌ ❌
Custom BPE tokenizer ✅ ❌ ❌ ❌
Multiple architectures ✅ ✅ ❌ ✅
MoE implementation ✅ partial ❌ partial
MLA (DeepSeek) ✅ ❌ ❌ ❌
Custom LoRA (no PEFT dep) ✅ ❌ ❌ ❌
Full PPO RLHF ✅ partial ❌ ❌
Verifiable rewards ✅ ❌ ❌ ❌
ELO leaderboard ✅ ❌ ❌ ❌
OpenAI-compatible API ✅ ❌ ❌ ❌
Production UI ✅ ❌ ❌ ❌
Fully documented ✅ partial ✅ partial

System Requirements

Minimum (Development / CPU)

CPU:    8-core modern processor
RAM:    16 GB
Disk:   50 GB
Python: 3.10+

Recommended (Training Small Models)

GPU:    1× NVIDIA A100 40GB or 2× RTX 3090 24GB
RAM:    64 GB
Disk:   500 GB NVMe SSD
CUDA:   12.1+
Python: 3.11

Production (7B+ Models)

GPU:    8× NVIDIA A100 80GB (640GB total VRAM)
RAM:    512 GB
Disk:   4 TB NVMe (RAID)
Network: InfiniBand 400Gb/s
CUDA:   12.1+

GPU Memory Requirements by Model Size

Model Size FP32 BF16 INT8 Minimum GPU
125M 0.5 GB 0.25 GB 0.13 GB RTX 3060
1.3B 5.2 GB 2.6 GB 1.3 GB RTX 3080
7B 28 GB 14 GB 7 GB A100 40GB
13B 52 GB 26 GB 13 GB 2× A100 40GB
70B 280 GB 140 GB 70 GB 8× A100 80GB

Quick Start

Option 1: Docker (Recommended — 3 Commands)

# Clone
git clone https://github.com/AdilShamim8/LLM-Playground-101.git
cd llm-playground

# Configure
cp .env.example .env
# Edit .env: set CUDA_VISIBLE_DEVICES, WANDB_API_KEY, etc.

# Launch all services
docker-compose up --build

Services will be available at:


Option 2: Manual Installation

# 1. Clone and enter
git clone https://github.com/AdilShamim8/LLM-Playground-101.git
cd llm-playground

# 2. Create isolated environment
python3.11 -m venv .venv
source .venv/bin/activate        # Linux/Mac
# .venv\Scripts\activate         # Windows

# 3. Install PyTorch with CUDA 12.1
pip install torch==2.1.0 torchvision \
  --index-url https://download.pytorch.org/whl/cu121

# 4. Install project (development mode)
pip install -e ".[dev]"

# 5. (Optional) Flash Attention — significant speedup
pip install flash-attn --no-build-isolation

# 6. (Optional) DeepSpeed — multi-GPU training
pip install deepspeed

# 7. Configure
cp .env.example .env

Option 3: Run Everything End-to-End (Script)

# Verify installation first
python run_verification.py

# Expected output:
# ============================================================
# LLM PLAYGROUND — FULL RUNTIME VERIFICATION
# ============================================================
# ✅ RMSNorm forward
# ✅ RotaryEmbedding forward
# ... (40 tests)
# RESULTS: 40 passed / 0 failed / 40 total
# ✅ ALL TESTS PASSED — PROJECT IS COMPLETE

# Then run the full pipeline
chmod +x scripts/run_pipeline.sh
./scripts/run_pipeline.sh

Minimal Working Example (5 Minutes)

# minimal_example.py
import torch
from models import GPTModel, ModelConfig
from generation import TextGenerator
from generation.strategies import GenerationConfig

# 1. Define a small model (runs on CPU)
config = ModelConfig(
    vocab_size=50257,
    hidden_size=256,
    num_layers=4,
    num_heads=4,
    intermediate_size=1024,
    max_position_embeddings=512,
    use_rope=True,
    use_rmsnorm=True,
    use_swiglu=True,
)

# 2. Build model
model = GPTModel(config)
print(f"Parameters: {model.num_parameters()/1e6:.1f}M")

# 3. Forward pass
input_ids = torch.randint(0, 50257, (1, 16))
output = model(input_ids=input_ids)
print(f"Logits shape: {output.logits.shape}")
# → torch.Size([1, 16, 50257])

# 4. Compute loss
labels = torch.randint(0, 50257, (1, 16))
output_with_loss = model(input_ids=input_ids, labels=labels)
print(f"Loss: {output_with_loss.loss.item():.4f}")
# → Loss: 10.8xxx (random init ≈ log(50257))

# 5. Train one step
optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4)
output_with_loss.loss.backward()
optimizer.step()
optimizer.zero_grad()
print("✅ Forward + backward pass successful")
python minimal_example.py

📁 Project Structure

llm_playground/
│
├── 📄 README.md                    ← You are here
├── 📄 requirements.txt             ← All dependencies
├── 📄 setup.py                     ← Package installation
├── 📄 docker-compose.yml           ← Multi-service orchestration
├── 📄 Dockerfile                   ← CUDA-enabled container
├── 📄 .env.example                 ← Environment template
├── 📄 run_verification.py          ← 40-test runtime verification
│
├── 📁 configs/
│   ├── base_config.yaml            ← Model & data defaults
│   ├── training_config.yaml        ← Pre-train / SFT / PPO hyperparams
│   └── evaluation_config.yaml      ← Benchmark settings
│
├── 📁 data/                        ← Complete data pipeline
│   ├── crawling/
│   │   ├── web_crawler.py          ← Async polite web crawler
│   │   ├── common_crawl.py         ← WARC/WET downloader
│   │   └── utils.py                ← URL normalization, dedup
│   ├── cleaning/
│   │   ├── quality_filters.py      ← 10+ heuristic filters
│   │   ├── refined_web.py          ← MinHash LSH deduplication
│   │   ├── dolma_cleaner.py        ← Gopher + PII + toxicity
│   │   └── fineweb_cleaner.py      ← Educational quality scoring
│   └── tokenization/
│       ├── bpe_tokenizer.py        ← Full byte-level BPE
│       └── utils.py                ← Corpus → binary shards
│
├── 📁 models/                      ← All model architectures
│   ├── layers.py                   ← RMSNorm, RoPE, SwiGLU, etc.
│   ├── attention.py                ← MHA, GQA, KV-Cache
│   ├── transformer.py              ← Pre-LN Transformer block
│   ├── gpt.py                      ← GPT causal language model
│   └── architectures/
│       ├── deepseek.py             ← MLA + MoE (DeepSeek-V2/V3)
│       ├── gemma.py                ← Logit softcap (Gemma 1/2)
│       └── qwen.py                 ← Dynamic NTK RoPE (Qwen 1/2)
│
├── 📁 training/                    ← Training pipelines
│   ├── pretrain.py                 ← Pre-training loop
│   ├── sft.py                      ← SFT + custom LoRA
│   ├── utils.py                    ← Collators, samplers, checkpointing
│   └── rlhf/
│       ├── reward_model.py         ← Bradley-Terry preference training
│       ├── ppo_trainer.py          ← Full PPO with GAE + KL control
│       └── verifiable_tasks.py     ← Math / Code / Format rewards
│
├── 📁 generation/                  ← Text generation
│   ├── strategies.py               ← Greedy, beam, top-k, top-p
│   └── sampler.py                  ← Unified TextGenerator interface
│
├── 📁 evaluation/                  ← Evaluation suite
│   ├── metrics.py                  ← Perplexity, BLEU, ROUGE
│   ├── benchmarks.py               ← MMLU, GSM8K, HellaSwag
│   └── human_eval.py               ← ELO rating + leaderboard
│
├── 📁 api/                         ← FastAPI backend
│   ├── main.py                     ← App factory + middleware
│   ├── model_registry.py           ← Shared model registry
│   ├── routes/
│   │   ├── chat.py                 ← OpenAI-compatible chat
│   │   ├── models.py               ← Load/unload/info endpoints
│   │   └── evaluation.py           ← Eval jobs + leaderboard
│   └── middleware/
│       └── auth.py                 ← JWT + API key + rate limiting
│
├── 📁 ui/                          ← Gradio frontend
│   ├── app.py                      ← Main application
│   └── components/
│       ├── chat_panel.py           ← Chat interface
│       ├── param_panel.py          ← Parameter controls
│       ├── eval_panel.py           ← Evaluation panel
│       └── model_panel.py          ← Model management
│
└── 📁 tests/                       ← Test suite
    ├── test_models.py              ← Architecture tests
    ├── test_generation.py          ← Generation strategy tests
    ├── test_data.py                ← Data pipeline tests
    └── test_training.py            ← Training component tests

Architecture Deep Dive

Data Pipeline

The data pipeline transforms raw internet text into tokenized training data through a multi-stage cleaning and filtering process, inspired by the best practices from RefinedWeb, Dolma, and FineWeb.

Stage 1: Data Collection

Web Crawler (data/crawling/web_crawler.py)

Our async BFS crawler implements production-grade politeness:

from data import WebCrawler, CrawlConfig
import asyncio

config = CrawlConfig(
    max_depth=3,            # BFS depth limit
    max_pages=50_000,       # Total page cap
    delay=1.0,              # Seconds between requests per domain
    max_concurrent=10,      # Simultaneous connections
    respect_robots=True,    # Always honour robots.txt
    allowed_domains=[       # Optional domain whitelist
        "wikipedia.org",
        "arxiv.org",
    ],
)

crawler = WebCrawler(config)
pages = asyncio.run(crawler.crawl([
    "https://en.wikipedia.org/wiki/Machine_learning",
    "https://arxiv.org/abs/2303.08774",
]))

crawler.save_results("./data/raw/crawl.jsonl")
print(f"Collected: {len(pages)} pages")

Key design decisions:

  • Async I/O: aiohttp + semaphore for controlled concurrency
  • robots.txt cache: Fetched once per domain, not per URL
  • Content extraction: trafilatura first, BeautifulSoup fallback
  • Deduplication: SHA-256 content hashing during crawl

Common Crawl (data/crawling/common_crawl.py)

from data import CommonCrawlProcessor, CommonCrawlConfig

config = CommonCrawlConfig(
    crawl_id="CC-MAIN-2023-50",    # Specific crawl snapshot
    num_segments=10,                # WET file segments to process
    max_pages_per_segment=100_000,  # Pages per segment
    languages=["en"],               # Language filter
    output_dir="./data/raw/cc",
)

processor = CommonCrawlProcessor(config)
total = processor.process_and_save("./data/raw/cc/output.jsonl")
print(f"Extracted: {total:,} documents")

Stage 2: Data Cleaning

Three complementary cleaning pipelines, each targeting different quality dimensions:

RefinedWeb (Falcon LLM approach)

Input documents
    │
    ├─→ URL-based filtering (block known spam/adult domains)
    ├─→ Text quality heuristics (10+ filters)
    │      - Length: 100 ≤ chars ≤ 100,000
    │      - Word count: 20 ≤ words ≤ 100,000
    │      - Average word length: 3.0 ≤ avg ≤ 10.0
    │      - Symbol ratio: < 10%
    │      - Digit ratio: < 20%
    │      - Uppercase ratio: < 30%
    │      - Line repetition ratio: < 30%
    │      - Unique word ratio: > 20%
    │      - HTML remnant ratio: < 5%
    │      - Bullet line ratio: < 90%
    ├─→ Content safety (PII redaction, harmful content)
    ├─→ Exact deduplication (SHA-256)
    └─→ Near-duplicate removal (MinHash LSH, threshold=0.85)
         └─→ 128 hash permutations, band size=4

Dolma (Allen AI approach)

Input documents
    │
    ├─→ Text normalization (Unicode NFC, whitespace)
    ├─→ Gopher quality filters (8 checks)
    │      - Word count thresholds
    │      - Mean word length (3.0–10.0)
    │      - Symbol/word ratio < 0.1
    │      - Bullet/ellipsis line ratios
    │      - Duplicate line character fraction < 20%
    │      - Duplicate paragraph character fraction < 20%
    │      - Top n-gram repetition fraction < 20%
    ├─→ Language identification (fasttext, confidence ≥ 0.65)
    ├─→ Toxicity filtering (pattern matching + domain check)
    ├─→ PII redaction (SSN, CC, email, phone, IP, passport)
    └─→ Exact deduplication

FineWeb (HuggingFace approach)

Input documents
    │
    ├─→ Quality filtering (same as RefinedWeb)
    └─→ Educational quality scoring
           - High-value keywords: "therefore", "hypothesis",
             "evidence", "research", "analysis" (+2.0 weight)
           - Medium keywords: "because", "explain" (+1.0)
           - Low-value: "click", "subscribe", "sale" (-3.0)
           - Structure bonus: paragraphs + sentence count
           - Sentence length bonus: 10–30 words ideal
           └─→ Keep if educational_score ≥ 0.2
from data import RefinedWebCleaner, RefinedWebConfig
from data import DolmaTextCleaner, DolmaConfig
from data import FineWebCleaner, FineWebConfig

# RefinedWeb
rw = RefinedWebCleaner(RefinedWebConfig())
stats = rw.process_file("./raw.jsonl", "./refined.jsonl")
print(f"Passed: {stats['passed']}/{stats['total']}")

# Dolma
dolma = DolmaTextCleaner(DolmaConfig())
stats = dolma.process_file("./raw.jsonl", "./dolma.jsonl")

# FineWeb (educational filter)
fw = FineWebCleaner(FineWebConfig(min_educational_score=0.3))
stats = fw.process_file("./raw.jsonl", "./fineweb.jsonl")

Tokenization

Byte-Level BPE (data/tokenization/bpe_tokenizer.py)

We implement GPT-2 style Byte-Pair Encoding from scratch, with no dependency on the tokenizers library:

How it works:

Step 1: Pre-tokenization (regex split)
  "Hello, world!" → ["Hello", ",", " world", "!"]

Step 2: Byte encoding (256 base tokens)
  "Hello" → ('H', 'e', 'l', 'l', 'o')
  Each byte mapped to printable unicode character

Step 3: BPE merge loop (vocab_size - 256 - special_tokens) times:
  Iteration 1: Find most frequent pair ("l", "l") → merge to "ll"
  Iteration 2: Find most frequent pair ("He", "llo") → merge to "Hello"
  ... continues until vocab_size reached

Step 4: Encode by applying learned merges greedily
from data.tokenization.bpe_tokenizer import BPEConfig, ByteLevelBPETokenizer

config = BPEConfig(
    vocab_size=50257,
    min_frequency=2,
    special_tokens={
        "bos_token":  "<|startoftext|>",
        "eos_token":  "<|endoftext|>",
        "unk_token":  "<|unk|>",
        "pad_token":  "<|pad|>",
    }
)

# Train on corpus
tokenizer = ByteLevelBPETokenizer(config)
with open("./data/processed/clean.jsonl") as f:
    texts = [json.loads(l)["text"] for l in f]

tokenizer.train(texts)
tokenizer.save("./tokenizers/bpe_50k")

# Use
ids = tokenizer.encode("Hello, world!")
# → [1, 31373, 11, 995, 0, 2]  (with BOS/EOS)

text = tokenizer.decode(ids, skip_special_tokens=True)
# → "Hello, world!"

# Batch encoding with padding
batch = tokenizer.encode_batch(
    ["Short text", "A longer sentence here"],
    padding=True,
    max_length=128,
    truncation=True,
)
# → {"input_ids": [[...], [...]], "attention_mask": [[...], [...]]}

Corpus Preparation:

from data.tokenization.utils import (
    tokenize_corpus_to_bin,
    split_dataset,
    estimate_token_count,
)

# Estimate before committing to full tokenization
estimate = estimate_token_count(
    "./data/processed/",
    tokenizer,
    sample_size=1000,
)
print(f"Estimated: {estimate['estimated_total_tokens_billions']:.2f}B tokens")

# Tokenize → binary shards
stats = tokenize_corpus_to_bin(
    input_path="./data/processed/",   # Directory of JSONL files
    output_dir="./data/tokenized/",
    tokenizer=tokenizer,
    max_tokens_per_file=100_000_000,  # 100M tokens per shard
    add_eos=True,                      # Separate documents with EOS
)
print(f"Shards: {stats['num_shards']}, Tokens: {stats['total_tokens']:,}")

# Split train/val/test
splits = split_dataset(
    data_dir="./data/tokenized/",
    output_dir="./data/split/",
    val_ratio=0.005,     # 0.5% validation
    test_ratio=0.001,    # 0.1% test
)
print(f"Train: {splits.train_tokens:,} tokens")

Model Architectures

All architectures share the same ModelConfig base and forward interface (input_ids → GPTOutput), making them interchangeable in the training pipeline.

GPT (Base Architecture)

Input IDs  [B, S]
    │
    ▼
Token Embedding  [B, S, H]    ← nn.Embedding(vocab_size, hidden_size)
    │
    ├─ (if not use_rope): + Position Embedding  [B, S, H]
    │
    ▼
┌──────────────────────────────────────────┐
│         TransformerBlock × N             │
│                                          │
│  x → RMSNorm → Attention → x + residual │
│  x → RMSNorm → FFN       → x + residual │
│                                          │
│  Attention variants:                     │
│    MHA:  Q,K,V all have num_heads heads  │
│    GQA:  K,V have num_kv_heads < heads   │
│                                          │
│  FFN variants:                           │
│    GPT2MLP:  fc1 → GeLU → fc2           │
│    SwiGLU:   gate(SiLU) ⊙ up → down    │
└──────────────────────────────────────────┘
    │
    ▼
Final RMSNorm  [B, S, H]
    │
    ▼
LM Head  [B, S, V]   ← Linear (weights tied to embedding)
    │
    ▼
Logits / Loss
from models import GPTModel, ModelConfig

# GPT-2 small equivalent
config = ModelConfig(
    vocab_size=50257,
    hidden_size=768,
    num_layers=12,
    num_heads=12,
    num_kv_heads=12,         # = num_heads → standard MHA
    intermediate_size=3072,
    max_position_embeddings=2048,
    use_rope=False,          # GPT-2 uses absolute positions
    use_rmsnorm=False,       # GPT-2 uses LayerNorm
    use_swiglu=False,        # GPT-2 uses GeLU
    use_gqa=False,
    tie_word_embeddings=True,
)

# LLaMA-style
config = ModelConfig(
    vocab_size=32000,
    hidden_size=4096,
    num_layers=32,
    num_heads=32,
    num_kv_heads=8,          # GQA: 4 queries share 1 KV head
    intermediate_size=11008,
    max_position_embeddings=4096,
    use_rope=True,           # Rotary embeddings
    use_rmsnorm=True,        # RMSNorm
    use_swiglu=True,         # SwiGLU
    use_gqa=True,            # Grouped Query Attention
    rope_theta=10000.0,
)

model = GPTModel(config)
print(f"Parameters: {model.num_parameters()/1e9:.2f}B")

DeepSeek (MLA + MoE)

DeepSeek-V2/V3 introduces two major innovations:

Multi-head Latent Attention (MLA) — Compresses the KV cache:

Standard MHA KV cache per token:
  2 × num_heads × head_dim × num_layers bytes

MLA KV cache per token:
  kv_lora_rank × num_layers bytes  (≈ 5-13× smaller)

How:
  c_KV = W_DKV × x              # Compress to latent (kv_lora_rank)
  K_nope, V = W_UK × c_KV       # Up-project: non-RoPE K and V
  K_rope = W_KR × x             # Separate RoPE key
  K = concat(K_rope, K_nope)    # Final key

Mixture of Experts (MoE) — Sparse FFN:

Token x
    │
    ├──→ Shared Experts (always active)
    │      Expert_1(x) + Expert_2(x) + ...
    │
    └──→ Router: softmax(W_g × x)
           │
           └──→ Top-K experts selected
                  w_1×E_i(x) + w_2×E_j(x) + ...
                  (only K of N experts compute)
    │
    └──→ Sum: shared_out + routed_out
from models.architectures.deepseek import DeepSeekConfig, DeepSeekModel

config = DeepSeekConfig(
    vocab_size=102400,
    hidden_size=5120,
    num_layers=60,
    num_heads=128,
    num_kv_heads=128,
    intermediate_size=12288,
    # MLA
    use_mla=True,
    kv_lora_rank=512,
    q_lora_rank=1536,
    qk_rope_head_dim=64,
    qk_nope_head_dim=128,
    v_head_dim=128,
    # MoE
    use_moe=True,
    num_experts=160,
    num_experts_per_token=6,
    num_shared_experts=2,
    moe_intermediate_size=1536,
    first_k_dense_layers=1,
    # Load balancing
    expert_load_balance_coef=0.01,
    router_z_loss_coef=0.001,
)
model = DeepSeekModel(config)

Gemma (Logit Soft-Capping)

from models.architectures.gemma import GemmaConfig, GemmaModel

config = GemmaConfig(
    vocab_size=256000,
    hidden_size=3072,
    num_layers=28,
    num_heads=16,
    num_kv_heads=16,
    intermediate_size=24576,
    # Gemma-specific
    logit_soft_cap=30.0,        # Final logit tanh cap
    attn_logit_softcap=50.0,    # Attention logit tanh cap
    use_logit_softcap=True,
    hidden_act="gelu",          # "gelu" or "relu2"
    # Embedding scaling: multiply by sqrt(hidden_size)
    # GemmaRMSNorm: uses (1 + weight) formulation
)
model = GemmaModel(config)

Qwen (Dynamic NTK RoPE)

from models.architectures.qwen import QwenConfig, QwenModel

config = QwenConfig(
    vocab_size=151936,
    hidden_size=4096,
    num_layers=32,
    num_heads=32,
    num_kv_heads=32,
    intermediate_size=22016,
    kv_channels=128,
    # Qwen-specific
    seq_length=8192,            # Training context length
    use_dynamic_ntk=True,       # Auto-scale RoPE beyond training len
    use_logn_attn=True,         # Log-N attention scaling
    # Dynamic NTK: if seq_len > seq_length:
    #   alpha = (seq_len/seq_length)^(dim/(dim-2))
    #   base = original_base * alpha
)
model = QwenModel(config)

Text Generation

All strategies support batched generation, KV-caching, and streaming:

Decoding Strategies Compared

Greedy:        Always argmax. Fast, deterministic, repetitive.
               x_t = argmax P(x | x_{<t})

Top-k:         Sample from top-k tokens only.
               Filter: keep top-k by probability
               Sample: x_t ~ Uniform(top-k)

Top-p:         Nucleus sampling. Dynamic vocabulary.
               Sort by prob descending, keep CDF ≤ p
               x_t ~ Categorical(filtered)

Temperature:   Scale logits before softmax.
               T < 1: sharper (more confident)
               T > 1: flatter (more random)
               logits_scaled = logits / T

Beam Search:   Maintain B hypotheses in parallel.
               Score = log P(y₁...yₜ) / length_penalty
               Best final hypothesis selected.
from generation import TextGenerator
from generation.strategies import GenerationConfig

gen_config = GenerationConfig(
    max_new_tokens=512,
    temperature=0.7,
    top_k=50,
    top_p=0.9,
    repetition_penalty=1.1,
    no_repeat_ngram_size=3,
)

generator = TextGenerator(model, tokenizer, gen_config)

# Standard generation
text = generator.generate(
    "The theory of neural scaling laws suggests",
    strategy="sampling",       # "greedy" | "sampling" | "beam_search"
)
print(text)

# Streaming (token by token)
for token in generator.generate_stream("Explain transformers:"):
    print(token, end="", flush=True)

# Batch generation
results = generator.batch_generate(
    prompts=["Question 1:", "Question 2:", "Question 3:"],
    strategy="greedy",
)

Training Pipeline

1. Pre-Training

Pre-training learns the base language model from billions of tokens using next-token prediction (causal language modeling).

Key design choices:

  • Cosine LR decay with linear warmup (GPT-3 style)
  • AdamW with decoupled weight decay
  • Weight decay only on 2D+ parameters (not bias/norm)
  • Gradient clipping at 1.0
  • Mixed precision BF16 (preferred over FP16 for stability)
  • Gradient checkpointing to reduce VRAM usage
from models import GPTModel, ModelConfig
from training import PretrainTrainer, PretrainConfig, PretrainDataset

# Model
model = GPTModel(ModelConfig(
    vocab_size=50257,
    hidden_size=768,
    num_layers=12,
    num_heads=12,
    intermediate_size=3072,
    use_rope=True,
    use_rmsnorm=True,
    use_swiglu=True,
))

# Config
config = PretrainConfig(
    data_path="./data/split",
    max_seq_length=2048,
    batch_size=32,
    gradient_accumulation_steps=8,   # Effective batch = 256
    max_steps=100_000,
    warmup_steps=2_000,
    learning_rate=3e-4,
    min_lr=3e-5,                     # 10× decay at end
    weight_decay=0.1,
    bf16=True,
    output_dir="./checkpoints/pretrain",
    save_steps=5_000,
    eval_steps=1_000,
    wandb_project="llm_playground",
)

# Data
train_data = PretrainDataset(config.data_path, config.max_seq_length, "train")
val_data   = PretrainDataset(config.data_path, config.max_seq_length, "val")

# Train
trainer = PretrainTrainer(model, config)
trainer.train(train_data, val_data)

Learning Rate Schedule:

LR
 │  /‾‾‾‾‾‾‾‾‾‾──────────────────────────────────\
 │ /                                               \
 │/                                                 ──── min_lr
 └────────────────────────────────────────────────────► steps
   ↑warmup↑                                    ↑max_steps↑

Expected Training Metrics (125M model on 10B tokens):

Step Loss Perplexity LR
1K 6.8 897 1.5e-4
5K 4.2 66.7 3.0e-4
20K 3.1 22.2 2.8e-4
50K 2.7 14.9 1.8e-4
100K 2.4 11.0 3.0e-5

2. Supervised Fine-Tuning (SFT)

SFT teaches the model to follow instructions by training only on assistant responses (loss masking on user/system tokens).

Loss Masking — Critical Detail:

Conversation:
  [SYSTEM] You are helpful.          → labels = -100 (masked)
  [USER]   What is 2+2?              → labels = -100 (masked)
  [ASST]   It equals 4.              → labels = real tokens ✓
  [USER]   Why?                      → labels = -100 (masked)
  [ASST]   Because 2+2=4 by def.    → labels = real tokens ✓

Custom LoRA (no HuggingFace dependency):

# LoRA: W' = W + (B @ A) × (α/r)
# A: (r, in_features)  - kaiming init
# B: (out_features, r) - zeros init → starts identical to base
# Trainable params: r×(in+out) vs in×out baseline
from models import GPTModel
from training import SFTTrainer, SFTConfig, SFTDataset

model = GPTModel.from_pretrained("./checkpoints/pretrain/best")

config = SFTConfig(
    data_path="./data/sft",
    max_length=2048,
    batch_size=8,
    gradient_accumulation_steps=4,
    num_epochs=3,
    learning_rate=2e-5,
    use_lora=True,
    lora_r=16,               # Rank (higher = more capacity)
    lora_alpha=32,           # Scaling = alpha/r = 2.0
    lora_dropout=0.05,
    lora_target_modules=[
        "q_proj", "k_proj", "v_proj", "out_proj",
        "gate_proj", "up_proj", "down_proj",
    ],
    bf16=True,
    output_dir="./checkpoints/sft",
)

# Data format options (all supported):
# 1. ChatML messages format
# 2. Alpaca instruction format
# 3. Simple prompt/response format
dataset = SFTDataset("./data/sft", tokenizer, config)
trainer = SFTTrainer(model, tokenizer, config)
trainer.train(dataset)

Preparing SFT Data:

{"messages": [
  {"role": "system", "content": "You are helpful."},
  {"role": "user", "content": "Explain gradient descent."},
  {"role": "assistant", "content": "Gradient descent is an optimization algorithm..."}
]}
{"instruction": "Write a sorting function.", "output": "def sort(arr):\n    return sorted(arr)"}
{"prompt": "What is RLHF?", "response": "RLHF stands for..."}

3. Reward Modeling

The reward model learns to predict human preference scores from pairwise comparison data (chosen vs. rejected responses).

Bradley-Terry Model:

P(response_a > response_b) = sigmoid(r_a - r_b)

Training loss: L = -E[log(sigmoid(r_chosen - r_rejected))]

Where r = scalar reward head output
from models import GPTModel
from training.rlhf import RewardModel, RewardModelConfig, RewardModelTrainer
from training.rlhf import PreferenceDataset

# Build reward model from SFT checkpoint
backbone = GPTModel.from_pretrained("./checkpoints/sft/final")
rm = RewardModel(backbone, RewardModelConfig(hidden_size=768))

# Preference data format:
# {"prompt": "...", "chosen": "...", "rejected": "..."}
dataset = PreferenceDataset("./data/preference", tokenizer)
trainer = RewardModelTrainer(rm, RewardModelConfig())
trainer.train(dataset)

# Evaluate
chosen_ids  = tokenizer.encode("Good response", ...)
rejected_ids = tokenizer.encode("Bad response", ...)
reward_good = rm(chosen_ids)
reward_bad  = rm(rejected_ids)
print(f"Good: {reward_good.item():.3f}, Bad: {reward_bad.item():.3f}")
# Good: 2.341, Bad: -1.872

4. PPO / RLHF

PPO (Proximal Policy Optimization) fine-tunes the SFT model to maximize reward while staying close to the reference policy via KL divergence penalty.

Full Algorithm:

For each training step:

1. ROLLOUT PHASE (no gradients)
   ├─ Sample batch of prompts from dataset
   ├─ Generate responses with current policy π_θ
   ├─ Score responses: r = RM(prompt, response)
   ├─ Compute KL penalty: KL = log π_θ(a|s) - log π_ref(a|s)
   ├─ Total reward: R_t = r × 1[last token] - β × KL_t
   ├─ Compute values: V(s_t) with critic network
   └─ Compute GAE advantages: A_t = Σ (γλ)^k δ_{t+k}
                               δ_t = r_t + γV(s_{t+1}) - V(s_t)

2. UPDATE PHASE (with gradients, PPO_EPOCHS times)
   ├─ Actor loss (PPO clip):
   │    ratio = π_θ(a|s) / π_old(a|s)
   │    L_actor = -min(ratio × A, clip(ratio, 1±ε) × A)
   ├─ Critic loss (MSE):
   │    L_critic = MSE(V(s), returns)
   └─ Total: L = L_actor + vf_coef × L_critic - ent_coef × H

3. ADAPTIVE KL CONTROL
   ├─ If KL > 1.5 × target_kl: β *= 2
   └─ If KL < 0.67 × target_kl: β /= 2
from models import GPTModel
from training.rlhf import PPOTrainer, PPOConfig

actor   = GPTModel.from_pretrained("./checkpoints/sft/final")
ref     = GPTModel.from_pretrained("./checkpoints/sft/final")  # frozen
critic  = GPTModel.from_pretrained("./checkpoints/sft/final")
rm      = RewardModel(GPTModel.from_pretrained("./checkpoints/rm/final"), ...)

config = PPOConfig(
    total_steps=1000,
    batch_size=64,
    ppo_epochs=4,            # Inner optimization epochs
    gamma=1.0,               # Discount (1.0 for language tasks)
    lam=0.95,                # GAE lambda
    clip_range=0.2,          # PPO epsilon
    vf_coef=0.1,             # Value loss weight
    kl_coef=0.1,             # Initial KL penalty coefficient
    target_kl=6.0,           # Target KL for adaptive control
    max_new_tokens=256,
    normalize_rewards=True,  # Normalize RM scores per batch
    reward_clip=10.0,        # Clip extreme rewards
)

trainer = PPOTrainer(actor, critic, ref, rm, tokenizer, config)

# Load prompts
with open("./data/ppo_prompts.txt") as f:
    prompts = f.readlines()

trainer.train(prompts)

5. Verifiable Rewards

For tasks with ground-truth answers, we bypass the reward model entirely and use verifiable signals directly.

from training.rlhf import MathVerifier, CodeVerifier, FormatVerifier

# Math: compare extracted numbers
math_v = MathVerifier()
result = math_v.compute_reward(
    prompt="Solve: 15 × 23 = ?",
    response="Let me calculate: 15 × 23 = 15 × 20 + 15 × 3 "
             "= 300 + 45 = 345. The answer is 345.",
    ground_truth="345"
)
print(f"Reward: {result.reward}, Passed: {result.passed}")
# Reward: 1.1 (bonus for showing work), Passed: True

# Code: execute and compare output
code_v = CodeVerifier(timeout=10)
result = code_v.compute_reward(
    prompt="Write code to print 'Hello, World!'",
    response="```python\nprint('Hello, World!')\n```",
    ground_truth="Hello, World!"
)
print(f"Reward: {result.reward}")  # 1.0 if output matches

# Format: check structural requirements
fmt_v = FormatVerifier()
result = fmt_v.compute_reward(
    prompt="Return a JSON object",
    response='{"name": "Alice", "age": 30}',
    ground_truth="json"
)
print(f"Reward: {result.reward}")  # 1.0 for valid JSON

Evaluation & Benchmarks

Automated Metrics

from evaluation import EvaluationSuite

suite = EvaluationSuite(model=model, tokenizer=tokenizer)

# Language modeling quality
ppl_result = suite.evaluate_lm(test_texts)
print(f"Perplexity: {ppl_result['perplexity']:.2f}")

# Generation quality
results = suite.evaluate_generation(
    hypotheses=generated_texts,
    references=reference_texts,
)
print(f"BLEU-4:  {results['bleu']:.1f}")
print(f"ROUGE-1: {results['rouge1']:.1f}")
print(f"ROUGE-L: {results['rougeL']:.1f}")

Task Benchmarks

from evaluation import MMLUBenchmark, GSM8KBenchmark
from generation import TextGenerator

# MMLU — 57 academic subjects, 5-shot
mmlu = MMLUBenchmark(model, tokenizer)
results = mmlu.evaluate(
    data_dir="./data/benchmarks/mmlu",
    num_few_shot=5,
)
print(f"MMLU Average: {results['mmlu_avg']*100:.1f}%")
print(f"Best subject: {max(results['per_subject'], key=results['per_subject'].get)}")

# GSM8K — grade school math, chain-of-thought
generator = TextGenerator(model, tokenizer, GenerationConfig())
gsm8k = GSM8KBenchmark(model, tokenizer, generator)
result = gsm8k.evaluate(gsm8k_examples, num_few_shot=8)
print(f"GSM8K: {result.accuracy*100:.1f}%")

Benchmark Reference Scores:

Model MMLU GSM8K Perplexity
GPT-2 (124M) 32.4% 1.2% 29.4
GPT-2 XL (1.5B) 41.8% 3.8% 18.3
LLaMA-7B 63.4% 11.0% 7.8
LLaMA-13B 69.7% 17.8% 6.9
GPT-4 (est.) 86.4% 92.0% —

Human Evaluation & ELO Leaderboard

We implement the Chatbot Arena (LMSYS) methodology:

from evaluation import HumanEvalCollector

collector = HumanEvalCollector(storage_path="./data/human_eval")

# Submit comparisons
collector.submit_comparison(
    prompt="Explain quantum entanglement simply.",
    model_a="llm-playground-v1",
    response_a="Quantum entanglement is when two particles...",
    model_b="llm-playground-v2",
    response_b="Imagine two coins that always show opposite faces...",
    winner="b",              # Human judge preferred model B
    judge_id="evaluator_01",
)

# Get leaderboard
board = collector.get_leaderboard()
for rank in board:
    print(
        f"#{rank['rank']} {rank['model']:30s} "
        f"ELO: {rank['elo']:6.1f}  "
        f"Win Rate: {rank['win_rate']:5.1f}%  "
        f"95% CI: [{rank['ci_95_low']:.1f}, {rank['ci_95_high']:.1f}]"
    )

ELO System:

Expected score: E_a = 1 / (1 + 10^((R_b - R_a) / 400))

Update rule:    R_a_new = R_a + K × (S_a - E_a)
                K = 32 (sensitivity factor)
                S_a = 1 (win), 0.5 (tie), 0 (loss)

Properties:
  - New models start at ELO 1000
  - ELO difference of 400 → 91% expected win rate
  - Converges to stable rankings given enough comparisons

API Reference

The API is fully compatible with the OpenAI Chat Completions specification.

Authentication

# JWT Token
curl -H "Authorization: Bearer <token>" http://localhost:8000/api/v1/...

# API Key (simpler for server-to-server)
curl -H "X-API-Key: your-api-key" http://localhost:8000/api/v1/...

Chat Completions

# Non-streaming
curl -X POST http://localhost:8000/api/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "X-API-Key: dev-key-123" \
  -d '{
    "model": "llm-playground-7b",
    "messages": [
      {"role": "system", "content": "You are a helpful assistant."},
      {"role": "user",   "content": "What is the capital of France?"}
    ],
    "temperature": 0.7,
    "max_tokens": 256,
    "top_p": 0.9,
    "top_k": 50,
    "repetition_penalty": 1.1
  }'

# Response
{
  "id": "chatcmpl-a3f9b2c1",
  "object": "chat.completion",
  "created": 1703001234,
  "model": "llm-playground-7b",
  "choices": [{
    "index": 0,
    "message": {"role": "assistant", "content": "The capital of France is Paris."},
    "finish_reason": "stop"
  }],
  "usage": {
    "prompt_tokens": 28,
    "completion_tokens": 9,
    "total_tokens": 37,
    "latency_seconds": 0.423
  }
}
# Streaming
curl -X POST http://localhost:8000/api/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "X-API-Key: dev-key-123" \
  -d '{"model": "demo", "messages": [...], "stream": true}'

# Server-Sent Events response:
data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":"The"},...}]}
data: {"id":"chatcmpl-xxx","choices":[{"delta":{"content":" capital"},...}]}
...
data: [DONE]

Model Management

# Load a model (admin only)
curl -X POST http://localhost:8000/api/v1/models/load \
  -H "X-API-Key: dev-key-123" \
  -d '{
    "model_name": "my-model",
    "model_path": "./checkpoints/sft/final",
    "tokenizer_path": "./tokenizers/bpe_50k",
    "device": "auto",
    "dtype": "bfloat16"
  }'

# List loaded models
curl http://localhost:8000/api/v1/models/list \
  -H "X-API-Key: dev-key-123"

# Model info
curl http://localhost:8000/api/v1/models/my-model/info \
  -H "X-API-Key: dev-key-123"

# Unload
curl -X DELETE http://localhost:8000/api/v1/models/my-model \
  -H "X-API-Key: dev-key-123"

Evaluation

# Start evaluation job
curl -X POST http://localhost:8000/api/v1/evaluate \
  -H "X-API-Key: dev-key-123" \
  -d '{
    "model_name": "my-model",
    "task": "bleu",
    "hypotheses": ["the cat sat on the mat"],
    "references": ["the cat sat on the mat"]
  }'
# → {"job_id": "eval-a3b9c2d1", "status": "pending"}

# Poll result
curl http://localhost:8000/api/v1/evaluate/eval-a3b9c2d1 \
  -H "X-API-Key: dev-key-123"

# Get leaderboard
curl http://localhost:8000/api/v1/leaderboard \
  -H "X-API-Key: dev-key-123"

# Submit human preference
curl -X POST "http://localhost:8000/api/v1/leaderboard/compare" \
  -H "X-API-Key: dev-key-123" \
  -d "model_a=model-v1&model_b=model-v2&winner=b&prompt=Hello&..."

Python Client

import requests

class LLMPlaygroundClient:
    def __init__(self, base_url="http://localhost:8000", api_key="dev-key-123"):
        self.base = base_url
        self.headers = {"X-API-Key": api_key}

    def chat(self, messages, model="demo", **kwargs):
        resp = requests.post(
            f"{self.base}/api/v1/chat/completions",
            json={"model": model, "messages": messages, **kwargs},
            headers=self.headers,
        )
        return resp.json()["choices"][0]["message"]["content"]

    def stream(self, messages, model="demo"):
        import json
        resp = requests.post(
            f"{self.base}/api/v1/chat/completions",
            json={"model": model, "messages": messages, "stream": True},
            headers=self.headers,
            stream=True,
        )
        for line in resp.iter_lines():
            if line and line.startswith(b"data: "):
                data = line[6:]
                if data.strip() == b"[DONE]":
                    break
                chunk = json.loads(data)
                token = chunk["choices"][0]["delta"].get("content", "")
                if token:
                    yield token

# Usage
client = LLMPlaygroundClient()

# Standard call
answer = client.chat([{"role": "user", "content": "Hello!"}])
print(answer)

# Streaming
for token in client.stream([{"role": "user", "content": "Tell me a story"}]):
    print(token, end="", flush=True)

UI Guide

Launch the Gradio interface:

# Local
python ui/app.py

# Or via API server (recommended)
docker-compose up ui

Navigate to http://localhost:7860

Chat Tab

┌─────────────────────────────────────┬──────────────────┐
│  Conversation                       │  ⚙️ Parameters   │
│                                     │                  │
│  👤 Hello! How are you?             │  Model: [demo ▼] │
│  🤖 I'm doing great! How can I...   │                  │
│                                     │  🌡️ Temperature  │
│  ┌───────────────────────────────┐  │  [──●────] 0.70  │
│  │ Your message...               │  │                  │
│  └───────────────────────────────┘  │  🎯 Top-p        │
│  [Send 🚀]  [🗑️ Clear]             │  [────●──] 0.90  │
│                                     │                  │
│  System Prompt:                     │  🔝 Top-k        │
│  [You are a helpful assistant...]   │  [──●────] 50    │
└─────────────────────────────────────┴──────────────────┘

Generation Lab

Compare all three strategies side-by-side with the same prompt:

Prompt: [The future of artificial intelligence will...]

Greedy          Sampling (top-p)    Beam Search (B=4)
──────────────  ────────────────    ─────────────────
The future of   The future of       The future of
AI will be      AI could be very    AI will likely be
determined by   different from      shaped by advances
the development what we imagine...  in both compute...

Evaluation Tab

Run benchmarks directly from the UI:

  • Select task (BLEU, ROUGE, Perplexity, MMLU, GSM8K)
  • Paste hypotheses and references
  • View results as formatted JSON
  • Refresh ELO leaderboard in real time

⚙️ Configuration

All settings controlled via YAML configs and .env:

# configs/base_config.yaml
model:
  architecture: "gpt"          # gpt | deepseek | gemma | qwen
  hidden_size: 768
  num_layers: 12
  num_heads: 12
  num_kv_heads: 12             # = num_heads → MHA; < → GQA
  intermediate_size: 3072
  max_position_embeddings: 2048
  vocab_size: 50257
  use_rope: true
  use_rmsnorm: true
  use_swiglu: true
  use_gqa: false
  dropout: 0.1

# configs/training_config.yaml
pretraining:
  batch_size: 32
  gradient_accumulation_steps: 8
  learning_rate: 3.0e-4
  min_lr: 3.0e-5
  warmup_steps: 2000
  max_steps: 100000
  weight_decay: 0.1
  bf16: true
  gradient_checkpointing: true

sft:
  learning_rate: 2.0e-5
  num_epochs: 3
  use_lora: true
  lora_r: 16
  lora_alpha: 32

rlhf:
  ppo:
    kl_coef: 0.1
    target_kl: 6.0
    clip_range: 0.2
    ppo_epochs: 4
# .env
CUDA_VISIBLE_DEVICES=0,1,2,3
WANDB_API_KEY=your-key
JWT_SECRET_KEY=your-long-random-secret
DEV_MODE=false
RATE_LIMIT_REQUESTS=100
RATE_LIMIT_WINDOW=3600

🐳 Docker Deployment

# docker-compose.yml services:
#   api      — FastAPI on port 8000
#   ui       — Gradio on port 7860
#   redis    — Rate limiting / caching
#   postgres — Experiment tracking

# Start all services
docker-compose up -d

# GPU-specific service
docker-compose up -d api

# Scale API workers
docker-compose up -d --scale api=3

# View logs
docker-compose logs -f api

# Health check
curl http://localhost:8000/health
# {"status":"healthy","uptime":42.1,"requests":157,"version":"1.0.0"}

# Stop everything
docker-compose down

Performance & Scaling

Single GPU Optimizations

# 1. Flash Attention (2-4× faster attention)
pip install flash-attn --no-build-isolation
# Automatically used when available (checked in MultiHeadAttention)

# 2. Gradient checkpointing (reduce VRAM by ~40%)
model.enable_gradient_checkpointing()

# 3. Mixed precision
config = PretrainConfig(bf16=True)  # BF16 preferred for stability

# 4. Gradient accumulation (simulate larger batch)
config = PretrainConfig(
    batch_size=4,
    gradient_accumulation_steps=32,  # Effective batch = 128
)

# 5. Compile (PyTorch 2.0+)
model = torch.compile(model)

Multi-GPU with DeepSpeed

# Install DeepSpeed
pip install deepspeed

# ZeRO Stage 2: optimizer state sharding
deepspeed --num_gpus=4 training/pretrain.py \
  --deepspeed configs/deepspeed_zero2.json

# ZeRO Stage 3: full parameter sharding (largest models)
deepspeed --num_gpus=8 training/pretrain.py \
  --deepspeed configs/deepspeed_zero3.json

Throughput Reference

Setup Model Tokens/sec GPU Util
1× A100 40GB, BF16 125M ~450K 78%
1× A100 40GB, Flash 125M ~820K 91%
4× A100 40GB, DDP 125M ~3.1M 88%
8× A100 80GB, DS-Z3 7B ~112K 94%

Troubleshooting

Installation Issues

# CUDA version mismatch
nvcc --version            # Check CUDA version
python -c "import torch; print(torch.version.cuda)"

# Flash Attention build fails
pip install ninja packaging
pip install flash-attn --no-build-isolation

# Out of memory during import
export PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb:512

Training Issues

# Loss is NaN
# → Reduce learning rate (3e-4 → 1e-4)
# → Enable gradient clipping (already in our trainer: max_grad_norm=1.0)
# → Check for bad data (empty strings, corrupted tokens)

# Loss not decreasing
# → Check data is shuffled
# → Verify labels are not all -100 (SFT masking bug)
# → Check learning rate schedule (print scheduler.get_lr())

# VRAM OOM during training
config = PretrainConfig(
    batch_size=4,                      # Reduce batch
    gradient_accumulation_steps=64,    # Compensate
    bf16=True,                         # Half precision
)
model.enable_gradient_checkpointing()  # Trade compute for memory

# SFT: model not following instructions
# → Check loss masking is working (some labels should be real, not -100)
# → Increase training data diversity
# → Reduce learning rate (avoid catastrophic forgetting)

API Issues

# Port already in use
lsof -i :8000 && kill -9 <PID>

# Model not found error
# → Load model first: POST /api/v1/models/load
# → Check model name matches exactly

# Streaming not working
# → Ensure client reads SSE properly (iter_lines, not iter_content)
# → Check firewall/proxy isn't buffering responses

Running Tests

# Full test suite
pytest tests/ -v --tb=short

# Specific module
pytest tests/test_models.py -v

# With coverage
pytest tests/ --cov=. --cov-report=html

# Runtime verification (most comprehensive)
python run_verification.py

Contributing

We welcome contributions at every level.

Development Setup

git clone https://github.com/your-org/llm-playground.git
cd llm-playground
pip install -e ".[dev]"

# Code style
black .
isort .

# Type checking
mypy . --ignore-missing-imports

# Tests must pass
python run_verification.py
pytest tests/ -v

Contribution Areas

Area Difficulty Impact
Add BERTScore metric Easy Medium
Add HellaSwag benchmark Easy Medium
Implement DPO trainer Medium High
Add speculative decoding Medium High
Implement MoE with Expert Parallelism Hard Very High
Add multi-modal (vision) support Hard Very High

Pull Request Checklist

  • Tests pass: python run_verification.py
  • New features have unit tests
  • Docstrings updated
  • No circular imports introduced
  • Config changes documented in YAML files
  • Memory-efficient (no unnecessary .cuda() calls)

References

Papers

[1] Radford et al. (2019) - Language Models are Unsupervised Multitask Learners
    → GPT-2 architecture, BPE tokenization

[2] Brown et al. (2020) - Language Models are Few-Shot Learners
    → GPT-3, scaling laws, few-shot prompting

[3] Touvron et al. (2023) - LLaMA: Open and Efficient Foundation Models
    → RoPE, RMSNorm, SwiGLU, GQA

[4] Ouyang et al. (2022) - Training language models to follow instructions
    → InstructGPT, PPO for RLHF, reward modeling

[5] Penedo et al. (2023) - The RefinedWeb Dataset for Falcon LLM
    → Web data cleaning, MinHash deduplication

[6] Soldaini et al. (2024) - Dolma: an Open Corpus for LM Pretraining
    → Gopher quality filters, PII redaction, toxicity filtering

[7] Lozhkov et al. (2024) - FineWeb-Edu: The finest collection of educational content
    → Educational quality scoring

[8] Liu et al. (2024) - DeepSeek-V2: A Strong, Economical, and Efficient MoE LM
    → Multi-head Latent Attention, Mixture of Experts

[9] Team et al. (2024) - Gemma: Open Models Based on Gemini Research
    → Logit soft-capping, embedding scaling

[10] Bai et al. (2023) - Qwen Technical Report
     → Dynamic NTK RoPE, Log-N attention

[11] Dao et al. (2022) - FlashAttention: Fast Memory-Efficient Exact Attention
     → Flash Attention algorithm

[12] Hu et al. (2021) - LoRA: Low-Rank Adaptation of Large Language Models
     → LoRA fine-tuning

[13] Schulman et al. (2017) - Proximal Policy Optimization Algorithms
     → PPO algorithm

[14] Su et al. (2021) - RoFormer: Enhanced Transformer with Rotary Position Embedding
     → RoPE implementation

[15] Ziegler et al. (2019) - Fine-Tuning Language Models from Human Preferences
     → RLHF foundations, KL reward shaping

[16] Zheng et al. (2023) - Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena
     → ELO rating for LLMs, arena methodology

Recommended Reading Order

1. Start here: Radford 2019 (GPT-2) → understand base architecture
2. Scale up:   Brown 2020 (GPT-3)   → understand training at scale
3. Modernize:  Touvron 2023 (LLaMA) → RoPE, RMSNorm, SwiGLU, GQA
4. Align:      Ouyang 2022 (InstructGPT) → SFT + RLHF pipeline
5. Data:       Penedo 2023 (RefinedWeb)  → production data cleaning
6. Efficiency: Dao 2022 (FlashAttn)      → attention optimization
7. PEFT:       Hu 2021 (LoRA)           → parameter-efficient tuning
8. Frontier:   Liu 2024 (DeepSeek-V2)   → MLA + MoE architectures

License

This project is licensed under the MIT License - see the LICENSE file for details.


Contact


GitHub Profile   LinkedIn Profile   Kaggle Profile   Twitter/X Profile   Medium Profile

⭐ If you find this repository helpful, please consider giving it a star! ⭐

About

Production-Level Large Language Model Training & Deployment Framework

Topics

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages